feat(facades): 0.1.4 — module kit, textured materials, theatre trim #30

Merged
zwitschi merged 13 commits from facades-0.1.4 into main 2026-11-08 20:29:12 +01:00
60 changed files with 154 additions and 97 deletions
Showing only changes of commit 1b069188a2 - Show all commits

No files matched your search

Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+14
View File
@@ -612,3 +612,17 @@ Scope: facts and traps that cost time to establish. Planned work lives in [TODO.
- Lesson: after any blockout regeneration, probe the staged buildings' world X spans - Lesson: after any blockout regeneration, probe the staged buildings' world X spans
and compare them with `Geo/verification/2026-11-06-el-capitan-block.md` before and compare them with `Geo/verification/2026-11-06-el-capitan-block.md` before
trusting a render. A shift of this size is invisible in a screenshot. trusting a render. A shift of this size is invisible in a screenshot.
## 2026-11-09 — kit naming + topology validation (blender-modeler checklist)
- Kit assets now follow the Unreal convention: the Blender object keeps its readable
name (`Kit_Pilaster`), the exported asset and file are `SM_Kit_Pilaster`. The
kit loader strips the `SM_` prefix so the layout code still addresses pieces by
short name.
- `facade_kit_build.py::validate()` runs the blender-modeler cleanup checklist on
every piece before handoff — non-manifold edges, loose vertices, triangle count —
and records the result in the part report. Current kit: 16 pieces, **all
manifold, no loose geometry, 12 tris each**.
- `ue_import_facade_kit.py` now deletes kit assets that the current build no longer
produces, so a rename cannot leave two generations of the same piece in the
folder (the rename from `Kit_*` to `SM_Kit_*` did exactly that until it did).
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+31 -1
View File
@@ -76,11 +76,39 @@ def make_material(name, value):
return mat return mat
def validate(obj, tri_budget=4000):
"""Topology check before handoff (blender-modeler cleanup checklist).
Reports non-manifold edges, loose geometry and the triangle count. Kit pieces
are primitive solids, so any non-manifold edge means a modelling slip rather
than an intentional hole.
"""
import bmesh
me = obj.data
bm = bmesh.new()
bm.from_mesh(me)
bm.edges.ensure_lookup_table()
non_manifold = [e for e in bm.edges if len(e.link_faces) != 2]
loose_verts = [v for v in bm.verts if not v.link_edges]
tri_count = sum(len(f.verts) - 2 for f in bm.faces)
bm.free()
if non_manifold or loose_verts:
print(f"[kit] WARNING {obj.name}: {len(non_manifold)} non-manifold edge(s), "
f"{len(loose_verts)} loose vertex/vertices")
if tri_count > tri_budget:
print(f"[kit] WARNING {obj.name}: {tri_count} tris exceeds the {tri_budget} budget")
return {"manifold": not non_manifold, "loose_verts": len(loose_verts),
"tris": tri_count}
def note(obj, note_text): def note(obj, note_text):
PARTS.append({ PARTS.append({
"object": obj.name, "object": obj.name,
"asset": "SM_" + obj.name,
"dims_m": [round(d, 3) for d in obj.dimensions], "dims_m": [round(d, 3) for d in obj.dimensions],
"location_m": [round(c, 3) for c in obj.location], "location_m": [round(c, 3) for c in obj.location],
"topology": validate(obj),
"evidence": note_text, "evidence": note_text,
}) })
print(f"[kit] {obj.name:22} {obj.dimensions.x:.2f} x {obj.dimensions.y:.2f} " print(f"[kit] {obj.name:22} {obj.dimensions.x:.2f} x {obj.dimensions.y:.2f} "
@@ -235,7 +263,9 @@ def export_group(objects, out_dir):
bpy.ops.object.select_all(action="DESELECT") bpy.ops.object.select_all(action="DESELECT")
obj.select_set(True) obj.select_set(True)
bpy.context.view_layer.objects.active = obj bpy.context.view_layer.objects.active = obj
path = os.path.join(out_dir, obj.name + ".fbx") # Unreal naming convention for static meshes is SM_<Name>; the Blender
# object keeps its readable name, the asset the importable one.
path = os.path.join(out_dir, "SM_" + obj.name + ".fbx")
fbx_preset.export_static(path, selection=True) fbx_preset.export_static(path, selection=True)
exported.append(path) exported.append(path)
return exported return exported
+4 -1
View File
@@ -53,7 +53,10 @@ def load_kit() -> dict:
for data in registry.get_assets(filt): for data in registry.get_assets(filt):
mesh = unreal.EditorAssetLibrary.load_asset(str(data.package_name)) mesh = unreal.EditorAssetLibrary.load_asset(str(data.package_name))
if mesh: if mesh:
kit[mesh.get_name()] = mesh # Assets are SM_-prefixed (Unreal convention); the layout code refers to
# pieces by their short name, so key on the name without the prefix.
name = mesh.get_name()
kit[name[3:] if name.startswith("SM_") else name] = mesh
return kit return kit
+10
View File
@@ -42,6 +42,16 @@ def main() -> None:
log(f"no FBX files in {SRC}") log(f"no FBX files in {SRC}")
return return
# Drop kit assets that the current build no longer produces (the pieces were
# renamed to follow the SM_ convention), so the folder cannot accumulate two
# generations of the same piece.
expected = {os.path.splitext(f)[0] for f in files}
for existing in unreal.EditorAssetLibrary.list_assets(DEST, recursive=False):
name = existing.split("/")[-1].split(".")[0]
if name not in expected:
unreal.EditorAssetLibrary.delete_asset(f"{DEST}/{name}")
log(f"removed stale kit asset {name}")
for filename in files: for filename in files:
asset_name = os.path.splitext(filename)[0] asset_name = os.path.splitext(filename)[0]
task = unreal.AssetImportTask() task = unreal.AssetImportTask()