Materializr
Open-source parametric 3D CAD for makers — constraint sketches, solid modeling, threads, SVG & text engraving, STL/STEP export. Android, iPad, Linux, Mac, Windows. Vibe coded.
Browse low-poly 3D models generated by Python scripts in Blender: search, categories, a 3D viewer, doors and drawers that open, clips that play.
DesignPython · JavaScriptGitHub @khanjalOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
Iron Raptor Lowpoly: low-poly 3D models generated by Python scripts in Blender, with a browsable asset library. Code MIT, models CC BY 4.0.
Translation pending
Taken from the repository README
Loading the full README.
Read the full README ↗
A library of low-poly 3D models that are generated by Python scripts running in Blender, not modelled by hand: change a number, rebuild, get a new model. One shared 16 x 16 colour palette, flat shading, no textures to author. It began as the art pipeline of a co-op game and is being split out so anyone can use it.
Status: extraction in progress. The scripts under art/ are a copy of the game repo's art/ folder (from its main, 2026-10-23); the generic library and the game-specific content are not separated yet. See NOTES.md. There is a browsable asset browser for a first slice of 504 models.
AI disclosure: the Python scripts were written with the help of Claude Code (Anthropic). The models themselves are not made by a text-to-3D or image generator: each one is built by a script from boxes, cylinders and lofted cross-sections, and you can read how.
The models that exist today are in the asset browser (animals and people with clips, vehicles, furniture with doors that open, tools, food and more; search, categories and tags; the count is in the badge above); how each group is made is in art/README.md. What is planned is in the open issues (mostly one per group), so this page does not list every category.
Scope: generic things in the shared style, of any kind, including objects, vehicles, animals (living, extinct and fantasy), people, buildings, tools, weapons and military vehicles (of any era, sci-fi included, generic by type, never a named make or model), clutter and garbage. Not in scope: brands and trademarked designs, characters from other works, scanned or realistic models, and sexually explicit content. See CONTRIBUTING.md.
Contents: How a model is made · Categories and tags · The asset browser · Rebuilding · Using the models in your engine · Tests and CI · Licence · Report a problem or suggest a model
A script describes it. Each file in art/scripts/ builds a family of models: cars.py (sedan, taxi, ambulance, tractor...), interactive.py (a fridge, a wardrobe: their doors and drawers are separate parts), gn_animals.py (animals from a species table), gp_cast.py (people), kit.py (walls, doors, roofs on a 3 m grid)... A prop is a few lines of boxes, cylinders and lofted cross-sections from props_lib.py:
p = Prop("GT_Barrel_01")
p.cyl("wood_dark", (0, 0, 0.45), 0.28, 0.9, n=10) # colour, centre, radius, height
p.box("metal_dark", (0, 0, 0.2), (0.6, 0.6, 0.04)) # colour, centre, size
obj, coll = p.finish() # one mesh, one material, origin on the floor
common.export_fbx(coll, "Assets/Art/Global/Props/GT_Barrel_01.fbx")
Colours come from one palette, common.PALETTE: 256 slots in a 16 x 16 grid, each swatch 4 x 4 pixels of the 64 x 64 texture GlobalPalette.png. A script names a colour ("wood_dark"), never an RGB value; every UV of a face points at the middle of one swatch, so a model needs no unwrapping and a variant is a colour remap. The palette is append-only: indices are baked into the UVs of every exported model. A few swatches glow (glow_cyan, glow_green, glow_red, glow_amber): they are ordinary colours in the texture, and an engine can light them up with an emissive mask (the asset browser does).
Flat shading. Every model is faceted: hard edges, no smoothing groups, no subdivision (about 60 to 700 triangles for a prop or a vehicle, 500 to 1,000 for an animal or a person). The exported normals are per face.
Variants are data, not copies. Modern / Retro / Damaged furniture, paint jobs, building skins and Rotten food are one definition with a colour remap and, where the shape differs, its own function (art/README.md, "Variants").
Conventions (units, axes, origin): see Using the models in your engine. Details of the pipeline, the checks (audit.py, audit_props.py) and the traps: art/README.md.
Every model in the browser has one type, a three-level lowercase path, category/group/kind, for example vehicles/road/sedan, furniture/seating/sofa, appliances/kitchen/fridge. The top-level categories in the first slice are vehicles, animals, people, furniture, appliances, decor, food, farm, building, nature, sports, fixtures, equipment, clothing, tools (hand, garden, household and workshop tools) and sci-fi (alien ship parts, devices and wreckage). It also has many tags (lowercase words): the setting (modern, retro, classic, rural, urban, suburban, sci-fi, diner), the condition (new, damaged, rotten, abandoned, fresh, weathered), colours (red, blue...) and anything that helps a search (kitchen, emergency, livestock...). Models that are the same shape in another skin, style or colour are listed as each other's variants. The table that holds all this is tools/catalog.py; the rules for it are tested (tests/test_catalog.py).
site/ is a static page (no build step, no framework; three.js is vendored under site/vendor/three, MIT) that browses 504 of the models: search as you type, a category tree, tag chips that filter together, a thumbnail grid and a 3D viewer with orbit / zoom / pan, a variant switcher, doors / drawers / lids you click to open (and an "Open all" button) and, for animals and people, a clip dropdown (Idle, Walk, Trot, Run, Sit; the tom turkey's Display) with a speed slider. #GV_Sedan_01 in the URL opens a model; the filters are in the query string. It works at phone width.
Run it locally:
cd site
python -m http.server 8000 # then open http://localhost:8000
It has to be served over http (a file:// page cannot load the models). Publishing: .github/workflows/pages.yml deploys site/ with GitHub Pages to https://khanjal.github.io/iron-raptor-lowpoly/ when a change to site/ reaches main (the repository is public, which Pages needs on the Free plan).
Stress test. stress.html (http://localhost:8000/stress.html with the server above) draws 100, 1,000 or 10,000 copies of one model, as separate meshes or as one InstancedMesh for each mesh of the model, and shows the frame rate, the draw calls and the triangles; a rigged model's copies can play a clip (as separate meshes only), and Repaint half gives every other copy a second paint. The frame rate is capped by the display (and drops to about one a second in a hidden tab); Time frames renders up to 60 frames back to back (fewer when they take more than about 3 seconds) and waits for the GPU after each, which gives the real time a frame takes. The settings are in the query string (?model=GN_Ant_01&count=1000&mode=instanced&clip=1&paint=1). See Instancing and batching below.
Downloading. The viewer's Download GLB button gives one model. Releases have zips of every GLB and of each category (iron-raptor-lowpoly-glb-all.zip, -vehicles.zip...), made by .github/workflows/release.yml when a v* tag is pushed (or by hand from the Actions tab); python tools/make_release_zips.py makes the same zips locally into dist/ (or an OUTDIR inside the project). Each zip has CREDIT.txt and LICENSE-MODELS, and every GLB carries the credit line in its asset.copyright field. FBX files are not published (they are not committed; building them needs Blender).
site/manifest.json describes every model, one entry each:
| Field | Meaning |
|---|---|
id, name | the model's file name without extension (GV_Sedan_01), a display name |
aliases | ids the model had before (GV_Sedan_Blue_01): old links and searches still find it |
type, tags | one category path; many tags |
rigged, interactive | has a skeleton; has moving parts |
triangles, size | triangle count; [width, height, depth] in metres |
glb, thumb | paths under site/ |
parts | moving parts: kind (hinge, slide, or spin for a wheel that turns freely about its axis; its limit is 360), axis (a unit vector), pivot, limit (degrees to open, or metres to slide; the sign is the direction), rest (how far open the exported pose is; 0 = shut), in glTF axes |
clips | animation names in the GLB |
variants | ids of the same model in another skin |
The scripts run inside Blender (5.2.3 here) through the Blender MCP add-on, which runs execute_blender_code:
import runpy
runpy.run_path(r"<repo>\art\scripts\gt_bucket.py", run_name="__main__") # always pass run_name: most scripts build only under __main__
Options go in init_globals (for example {"SPECIES_LIST": ["goat"]}). Exports are written under an output folder: out/ by default, set with common.configure(out=r"<folder>") or the LOWPOLY_OUT environment variable (a path such as Assets/Art/Global/Props/GT_Bucket_01.fbx is relative to it, so pointing it at a Unity project's root gives the old behaviour). common.configure(formats="fbx,glb") also writes GLB files (common.export_glb), only={...} limits the exporters to some names, and prefix= keeps a build from replacing same-named collections in a Blender file that other work shares.
To rebuild the browser's slice:
| Step | Command |
|---|---|
| models | in Blender: runpy.run_path(r"<repo>\tools\build_site_assets.py", init_globals={"GROUPS": ["cars_sedan"]}, run_name="__main__"), one group per call (see tools/catalog.py for the group names; run people, animals and birds on their own, they take about 20 seconds each) |
| manifest | python tools/make_manifest.py |
| thumbnails | python tools/thumb_server.py, then open http://localhost:8766/tools/thumbs.html in a browser (it renders every model with the site's own viewer and saves site/thumbs/<id>.png; ?only=ID,ID for some) |
| credit line | python tools/stamp_glb.py (writes the CC BY 4.0 credit into asset.copyright of every site/models/*.glb that lacks it; Blender's exporter does it for new builds, check_site.py fails without it) |
| check | python tools/check_site.py |
| compare two builds | python tools/compare_builds.py <worktree> <worktree>: the GLBs, out/meta and art/data/interactions.json of two builds (how to prove an art/scripts/ change changed nothing: .claude/skills/sonar-check) |
| silhouette | python tools/silhouette.py <model id>: the model's side (or --view front) outline from its GLB laid over a reference picture of the real thing in out/reference/<id>.png (git-ignored, never committed; --ref for one shared by several models), with the overlap and where they differ; the overlay goes to out/silhouette/ |
What is checked and what is not. The models have been looked at in three.js (this site) and, for the FBX files, imported in Unity in the game repo (per art/README.md, "Unity import checklist"). Nothing here has been imported into Godot or Unreal, and the GLB files have not been imported into Unity. The table and the notes below separate what an engine's own documentation says (with the page) from what has been tried.
| Engine | Format | Status |
|---|---|---|
| three.js (this site's viewer) | GLB | tested: loads, textures nearest-filtered, clips play, moving parts turn about the right pivots |
| Blender 5.2.3 | generation, GLB and FBX export | tested |
| Unity | FBX | tested in the game repo (palette Point-filtered, People as Humanoid, models arrive facing +Z); not re-tested for this repo |
| Unity | GLB | documented: needs the glTFast package; untested |
| Godot 4.x | GLB (glTF 2.0) | documented (the recommended format; its asset front is +Z, which is where glTF puts the front); untested |
| Godot 4.x | FBX | documented (imported with ufbx from 4.3); untested |
| Unreal Engine 5 | FBX | documented (import options); untested |
| Unreal Engine 5 | GLB | documented (Interchange framework); untested |
art/scripts/common.py, export_fbx): -Z forward, Y up, scale 1, flat (face) normals, no leaf bones. Static meshes are exported with the axis conversion baked in; rigged ones are not (baking rotates the bones), so the importer has to apply the conversion (in Unity, "Bake Axis Conversion"). Rigged models are in a T-pose; their clips are separate FBX files named [email protected] (Unity names the clip from the @), except the Insectoid, whose clips are inside its file.common.export_glb, what the site uses): binary glTF 2.0, Y up, metres, per-face normals, the palette as an embedded 64 x 64 PNG with a nearest-neighbour sampler. A rig's clips are animations (the site's people have Idle, Walk, Run, Sit; dogs Idle, Walk, Trot, Run), sampled at 60 fps. A moving part is its own node with its origin on the pivot. Only meshes and armatures are exported: the marker empties (below) are not in the GLBs.art/README.md, "Axes"). The FBX importer's options (Scale Factor, Convert Units, Bake Axis Conversion, Normals: Import) are described at docs.unity3d.com/Manual/FBXImporter-Model.html.docs.godotengine.org, "Introduction to 3D"). Godot's camera and nodes look down −Z, but its convention for assets is the opposite: the "Model export considerations" page says the +Z axis is the front of an asset and −Z its rear, and that for Blender +Y is the rear and −Y the front; Vector3.MODEL_FRONT is (0, 0, 1) and FORWARD is (0, 0, −1) (class reference). So a glTF model with its front at +Z should import facing the right way, with no turn about Y (documented, not tried here). The belief that glTF models come in backwards in Godot because Godot's forward is −Z mixes up the node forward with the asset front. Use look_at(..., use_model_front = true) to turn an asset towards a target. (How the FBX importer orients a file: not tested.)dev.epicgames.com, "FBX Import Options Reference"). Unreal's units are centimetres (general knowledge, not from the pages I read): scale a model in metres by 100 ("Import Uniform Scale"). Facing in Unreal has not been checked: expect to import one model and set the options once.All models share one texture, 64 x 64 pixels, 16 x 16 swatches of 4 x 4 pixels. Sample it with nearest-neighbour filtering and no mipmaps, or the colours of neighbouring swatches blend (stripes on walls at a distance). In the GLBs the sampler already says NEAREST for magnification, but its minification filter is NEAREST_MIPMAP_NEAREST, so an engine that generates mipmaps can still smear the swatches at a distance: turn mipmap generation off. In Unity the game repo does this in ArtImportRules (Point filter, no mipmaps, no compression). In Godot it is the material's texture filter ("Nearest") and the texture's mipmap import setting; in Unreal the texture's filter and mip settings (both from general knowledge of those engines, not checked against their docs).
Every FBX references a material called GlobalPalette; use one shared material for all models, with low smoothness / roughness and no metallic. To make the glowing swatches glow, build a second texture holding only the glow swatches' pixels (the browser does it: site/viewer.js, _glowMap; the swatch indices are in site/manifest.json, palette.glow) and use it as the emissive map.
What limits a scene of many small models is the number of draw calls and, for rigged models, the skinning work on the CPU, not the triangles: an insect is 268 to 922 triangles, most small props under 150. site/stress.html (Stress test below) measures it in the browser; the numbers are in NOTES.md.
GlobalPalette and the one 64 x 64 palette texture; parts that are see-through use its blended twin GlobalPaletteGlass. Different models can be drawn with one material, so an engine's static batching (merging meshes that share a material) can merge a whole room of props, and a material switch never comes between them.InstancedMesh, Godot MultiMeshInstance3D, Unity GPU instancing (the material's Enable GPU Instancing, or Graphics.RenderMeshInstanced), Unreal Instanced Static Mesh components (general knowledge of those engines, not checked against their docs or tried here). A model made of several meshes (a car's body, doors and wheels) costs one instanced draw per mesh; merging its parts that never move into one mesh lowers that.site/viewer.js: setPaint gives the model's material a new texture made by paintedTexture, a copy of the palette with the chosen swatches painted over. A repainted copy has a different texture, so to the renderer it is a different material, and copies in different paints cannot share one instanced draw. The stress test's Repaint half shows it: an InstancedMesh is drawn with one material, so half the copies in a second paint need a second InstancedMesh for each mesh, and the draw calls double. Copies in the same paint still batch. To keep one batch for many paints, put the paints side by side in one larger texture and give each instance an offset into it (an instanced shader property), or tint with a per-instance colour where a whole-model tint is enough; neither is done here.The normals are per face and are in the files. Import them as they are: Unity "Normals: Import" (the default, per the manual) and Unreal "Import Normals" (not "Compute Normals", which discards the file's smoothing and normal information, per its options reference). Godot: leave the normals alone (not checked). Do not recalculate or smooth them, or the facets disappear.
People share one rig: 65 bones, the layout of Quaternius's Universal Animation Library (CC0), listed with head and tail positions in art/data/ual_skeleton.json: root, pelvis, spine_01..spine_03, neck_01, Head, then for each side clavicle_, upperarm_, lowerarm_, hand_, four-joint fingers and a thumb, and thigh_, calf_, foot_, ball_ (suffix _l or _r). The rest pose is a T-pose, facing −Y in Blender (+Z in glTF). The clips (Idle, Walk, Run, Sit, Carry, CarryOne as FBX; the first four are in the GLBs) are in place, with no root motion, and play on every person because they share the skeleton.
docs.unity3d.com/Manual/AvatarCreationandSetup.html; the page I could read did not state a T-pose requirement).BoneMap with the SkeletonProfileHumanoid profile; Godot auto-maps by name patterns and recommends common English bone names; the profile's reference pose is a T-pose facing +Z, which is how these models arrive (docs.godotengine.org, "Retargeting 3D skeletons"). The Retarget import options let clips from one model play on another. Untested here.dev.epicgames.com, "IK Rig Animation Retargeting"). Untested here.art/scripts/quadruped_rig.py) with its own Idle / Walk (dogs also Trot / Run) clips; birds have a biped-bird rig and no clips yet. They are Generic rigs.interactions.jsonBesides the mesh, an FBX can carry empties that mark places, and art/data/interactions.json describes what can be done with each model:
Socket_Front and Socket_Back: just outside the front and back faces (every FBX has them): line Socket_Back up with a wall, stand a character or open a door at Socket_Front.Slot_<name>: where loose items go (a fridge shelf, a drawer floor); a slot that rides on a part moves with it.Light_<name>, Sound_<name>, Switch_<name>: a lamp's bulb, a fridge's hum, a switch; the JSON says what each does (colour, range, which clip, who may use it).parts in the JSON: the moving parts (kind, axis, pivot, limit, rest) in Blender's axes. The browser's manifest has the same parts already converted to glTF axes.These are data. Nothing plays or lights by itself: each engine needs a small importer that reads the empties and the JSON and builds its own lights, audio sources, hinges and slides. art/README.md describes Unity code that does this in the game (PropRuntimeSetup.cs, PropRuntime.cs), but that code is not in this repo and it has not been checked here whether it is on the game's main. The empties are not in the GLB files.
python -m unittest discover -s tests -t . runs the unit tests (axis conversion of the moving parts, the manifest builder, the catalog's type and tag rules) and the site checks; python tools/check_site.py checks that every manifest entry has its GLB and thumbnail, that fields are valid and ids unique, and that every moving part names a node that exists in its GLB at the right pivot. .github/workflows/tests.yml runs both on every pull request, on every push to main and by hand (the repository is public, so Actions on the standard runners are free). .github/workflows/sonar.yml fails a pull request that adds an open SonarCloud issue on its new code (tools/sonar_issues.py, see .claude/skills/sonar-check). A proposal for a Blender job (rebuild a few generators, fail on floating parts or a triangle regression) and for a browser smoke test is in docs/ci.md.
Code: MIT (LICENSE). Models and data: CC BY 4.0 (LICENSE-MODELS): free to use in any project, commercial or not, with credit. LICENSES.md says what applies where and gives a one-line credit to copy; third-party material is in CREDITS.md. Publishing notes (including the commit-email caveat) are in docs/PUBLISHING.md.