feat(facades): 0.1.4 — module kit, textured materials, theatre trim #30
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.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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.
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
Reference in new issue
Block a user