Skip to content
Edit on GitHub

Generate deterministic blocky mesh assets with Model Foundry

verifiedAgainst 6f58cea · verifiedOn 2026-09-10.

In Voxamine, technical art follows strict project ownership and licensing disciplines (MASTERPLAN.md §14.5 / §14.6). Instead of importing unlicensed third-party 3D meshes or hand-sculpting opaque assets with unknown origins, passive creatures, handheld tools, and laboratory glassware are procedurally synthesized by a family of pure, standard-library Python 3 tools under Tools/BlockyModel/ (blockymodel.py).

You will author a procedural 3D box model on the project’s standard 16-unit grid, configure material colors and translucent alpha channels so chemistry apparatus reads as true glass, enforce strict coordinate conventions (fauna feet grounded on $y=0$ facing $+Z$, items centered at local origin), verify that committed OBJ/MTL files have not drifted from their recipes using headless --check gates, and audit the entire 50-asset library inside the Model Foundry Voxel Workshop module (ModelFoundryModule.cs).

Terminal window
# Generate and verify the entire blocky asset library headlessly:
python3 Tools/FaunaGen/voxel_fauna_gen.py --check
# Auditing 16 fauna species: all in sync.
python3 Tools/ItemGen/voxel_item_gen.py --check
# Auditing 34 item models: all in sync.

The blocky model toolchain bridges offline deterministic Python generators and the Unity Editor asset database:

  • Geometry Core (Tools/BlockyModel/blockymodel.py) provides the pure math primitives (Box, Model, write_obj, validate_obj, check_drift, write_contact_sheet).
  • Recipe Scripts (Tools/FaunaGen/ & Tools/ItemGen/) define the box layouts, symmetric parts, and palette assignments for 16 passive animal species and 34 tools/glassware/drops.
  • Committed Assets (Assets/_Game/Art/Models/) stores the emitted .obj and .mtl files versioned in git.
  • Voxel Workshop (ModelFoundryModule.cs) orchestrates headless script execution via ModelScriptRunner, monitoring live counts and drift status directly inside Unity.
flowchart TD
  subgraph TOOLS["Tools/ (Pure Python 3, zero deps)"]
    CORE["Tools/BlockyModel/blockymodel.py\n(Box math, OBJ writer, drift checker)"]
    FAUNA_GEN["Tools/FaunaGen/voxel_fauna_gen.py\n(16 passive species: chicken, cow, fox...)"]
    ITEM_GEN["Tools/ItemGen/voxel_item_gen.py\n(34 tools, glassware, drops)"]
  end

  subgraph ASSETS["Assets/_Game/Art/Models/"]
    FAUNA_OBJ["Assets/_Game/Art/Models/Fauna/\n(16 species .obj + .mtl)"]
    ITEM_OBJ["Assets/_Game/Art/Models/Items/\n(34 item .obj + .mtl)"]
  end

  subgraph WORKSHOP["Voxel Workshop (Editor)"]
    CATALOG["ModelGeneratorCatalog.cs\n(Shell paths and output asset folders)"]
    RUNNER["ModelScriptRunner.cs\n(Runs --list, --check, regenerate headlessly)"]
    MODULE["ModelFoundryModule.cs\n(Live count, drift status, Regenerate All)"]
  end

  CORE --> FAUNA_GEN
  CORE --> ITEM_GEN
  FAUNA_GEN -->|writes| FAUNA_OBJ
  ITEM_GEN -->|writes| ITEM_OBJ
  CATALOG --> MODULE
  MODULE --> RUNNER
  RUNNER -->|queries --check| FAUNA_GEN
  RUNNER -->|queries --check| ITEM_GEN

Read Tools/BlockyModel/README.md and MASTERPLAN.md §14.5 (“Asset provenance and ground rules”), then review:

Review the mandatory coordinate and orientation standards:

Asset Type Primary Anchor Facing Direction Metric Scale Example
Fauna Species Feet at $y=0$ (ground plane) Forward along $+Z$ $1/16\text{ m}$ per unit ($16\text{ px} = 1\text{ m}$) Chicken, Fox, Cow, Deer
Tools & Glassware Centered at local origin $(0, 0, 0)$ Flat or upright along $+Y$ $1/16\text{ m}$ per unit Beaker, Retort, Pickaxe
Chemistry Glass Centered at origin Upright Translucent ($\alpha < 1$) with illum 4 Condenser, RoundFlask

1. The 16-Unit Pixel Grid and Box Primitives

Section titled “1. The 16-Unit Pixel Grid and Box Primitives”

In blockymodel.py, every model is composed of integer Box primitives on a 16-unit grid where $16\text{ units} = 1\text{ block} = 1\text{ meter}$:

class Box:
def __init__(self, x, y, z, w, h, d, mat, rot=None):
self.x, self.y, self.z = x, y, z # min corner (integer units)
self.w, self.h, self.d = w, h, d # size in width, height, depth
self.mat = mat # material key from PALETTE
self.rot = rot # optional (degrees, axis, pivot)

Symmetric features (such as animal legs, ears, or wings) are added via model.add_symmetric(x, y, z, w, h, d, mat) which automatically mirrors the box across the $X=0$ plane.

Chemistry laboratory vessels (such as beakers, retorts, condensers, and alembics) require visible interior liquid levels. In blockymodel.py, palette values can define an optional alpha channel (r, g, b, alpha):

# Laboratory glassware palette definition:
PALETTE = {
"glass": (0.85, 0.92, 0.95, 0.35), # translucent soft cyan
"glass_frosted": (0.90, 0.95, 0.98, 0.55),
"iron": (0.35, 0.35, 0.38), # opaque metals
"copper": (0.72, 0.45, 0.30),
}

When exporting the material file (.mtl), write_obj checks the alpha channel:

if len(color) == 4 and color[3] < 1.0:
alpha = color[3]
f.write(f"d {alpha:.2f}\n")
f.write(f"Tr {1.0 - alpha:.2f}\n")
f.write("illum 4\n") # glass / transparency illumination model
else:
f.write("illum 2\n") # standard diffuse/specular

This guarantees Unity’s model importer imports glassware with transparent blend modes rather than rendering a solid grey box.

In voxel_item_gen.py, author a laboratory Round-Bottom Flask:

def make_round_flask():
m = Model("RoundFlask", mats("glass", "glass_frosted", "cork"))
# Spherical body approximated by stepped boxes centered at origin:
m.add(-4, -6, -4, 8, 8, 8, "glass")
m.add(-5, -5, -3, 10, 6, 6, "glass")
m.add(-3, -5, -5, 6, 6, 10, "glass")
# Cylindrical neck extending upwards:
m.add(-2, 2, -2, 4, 6, 4, "glass")
# Flanged lip:
m.add(-3, 8, -3, 6, 1, 6, "glass_frosted")
# Optional cork stopper:
m.add(-2, 8, -2, 4, 3, 4, "cork")
return m

Every model function returns a Model instance whose bounding box is automatically calculated and logged.

4. Headless Drift Checking and Contact Sheets

Section titled “4. Headless Drift Checking and Contact Sheets”

To prevent recipes and committed assets from falling out of sync, blockymodel.py implements in-memory regeneration and diffing:

# In blockymodel.py:
def check_drift(builders, committed_root):
drifted = []
for name, builder in builders.items():
obj_path = os.path.join(committed_root, f"{name}.obj")
if not os.path.exists(obj_path):
drifted.append(name)
continue
# Compare regenerated geometry excluding header timestamp comments
if not geometry_matches(builder(), obj_path):
drifted.append(name)
return drifted

The toolchain also generates a combined Contact Sheet (--contact-sheet), arranging all models in an inspection grid with a companion .txt cell map for quick visual review.

5. Interactive Orchestration in Model Foundry

Section titled “5. Interactive Orchestration in Model Foundry”

Open ModelFoundryModule.cs. The module displays each generator registered in ModelGeneratorCatalog.cs:

  1. Live Model Count: Queries --list on each Python script.
  2. In-Sync Badge: Runs --check headlessly and displays green (“All in sync”) or amber (“Drift detected”).
  3. Regenerate All: Runs generate-fauna-models.sh and generate-item-models.sh and triggers an immediate Unity AssetDatabase.Refresh().

1. Execute Headless Self-Tests and Drift Audits

Section titled “1. Execute Headless Self-Tests and Drift Audits”

Run the command-line suite from the repository root:

Terminal window
# Verify fauna generation:
python3 Tools/FaunaGen/voxel_fauna_gen.py --selftest
python3 Tools/FaunaGen/voxel_fauna_gen.py --check
# Verify item generation:
python3 Tools/ItemGen/voxel_item_gen.py --selftest
python3 Tools/ItemGen/voxel_item_gen.py --check

Confirm that:

  • --selftest reports zero index-out-of-bounds or corrupt quad errors.
  • --check confirms zero drifted models between Python recipes and committed files.
  1. Open Tools → Voxel Sandbox → Voxel Workshop → Model Foundry.
  2. Confirm the header reads: All 2 generators are in sync (50 models total).
  3. Click Rescan: verify the status indicators refresh cleanly.
  4. Select any generated model in Assets/_Game/Art/Models/Items/ (e.g. RoundFlask.obj) and verify in Unity’s Model Previewer:
    • Neck and lip are centered cleanly.
    • Material transparency displays correctly.

To author a new blocky model (e.g. a Wolf fauna species or a Büchner Funnel apparatus):

  1. Pick the Target Generator: Add species to voxel_fauna_gen.py or item to voxel_item_gen.py.
  2. Select Materials from PALETTE: Use existing palette keys or declare new stable colors in blockymodel.py.
  3. Sketch Boxes on the 16-Unit Grid:
    • For fauna: ensure feet are placed at $y=0$ and the snout points toward $+Z$.
    • For items: center geometry around $(0, 0, 0)$.
  4. Register in Catalog: Add make_<name> to the generator’s BUILDERS dictionary.
  5. Regenerate and Commit: Run ./generate-item-models.sh to emit .obj and .mtl files under Assets/_Game/Art/Models/.
  6. Verify in Model Foundry: Confirm Model Foundry detects the new model and reports green in-sync status.
Symptom Cause Fix
Animal floats in the air or sinks into the terrain when placed. Box coordinates were authored with min $Y \ne 0$ or without centering on $X=0$. Enforce grounding: the lowest box (hooves/feet) must touch $y=0$, and body symmetry must center across $X=0$.
Chemistry glassware renders as a solid, dark opaque box in Unity. Palette entry had only 3 color channels (r, g, b). Add alpha channel (r, g, b, alpha) where $\alpha < 1.0$. The exporter will automatically write d, Tr, and illum 4.
CI build fails with --check mismatch after changing Python code. The Python recipe was updated but the .obj asset was not regenerated and committed. Run ./generate-fauna-models.sh or ./generate-item-models.sh and stage the resulting .obj / .mtl files.
Model faces render with inverted or black lighting. Quad winding order was inverted or box had negative dimensions ($w < 0$). Always define positive dimensions ($w, h, d > 0$). Box guarantees outward-facing counter-clockwise quad normals.
Non-deterministic diffs appear in git across different machines. Generator relied on dictionary iteration order or floating-point formatting drift. Use pure integer grid math and standard Model structures. blockymodel.py formats floats to fixed decimal precision.

User-contributed notes

Corrections, clarifications, and practical tips for this page. Anonymous is fine — a name is optional. Basic Markdown works: **bold**, *italic*, `code`, and links.

Notes policy

Notes are lightly filtered for spam and may be edited or removed. Keep them about this page — no support requests, no personal data, nothing you would not publish. Links are limited and marked nofollow.

  1. Loading notes…