refactor(kit): SM_ asset naming and topology validation per the modeling checklist
- Kit pieces export as SM_Kit_* (Unreal static-mesh convention) while the Blender objects keep their readable names; the kit loader strips the prefix so layout still addresses pieces by short name. - facade_kit_build.py validates each piece before handoff (non-manifold edges, loose vertices, triangle count) and records the result in the part report: 16 pieces, all manifold, no loose geometry, 12 tris each. - ue_import_facade_kit.py removes kit assets the current build no longer produces, so a rename cannot leave two generations of the same piece behind. - Kit re-imported and re-applied (625 pieces); packaged QA still PASS.
This commit is contained in:
1 parent
32dff056c3
commit
1b069188a2
60 files changed
+154
-97
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
|
||||
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.
|
||||
|
||||
## 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
|
||||
|
||||
|
||||
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):
|
||||
PARTS.append({
|
||||
"object": obj.name,
|
||||
"asset": "SM_" + obj.name,
|
||||
"dims_m": [round(d, 3) for d in obj.dimensions],
|
||||
"location_m": [round(c, 3) for c in obj.location],
|
||||
"topology": validate(obj),
|
||||
"evidence": note_text,
|
||||
})
|
||||
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")
|
||||
obj.select_set(True)
|
||||
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)
|
||||
exported.append(path)
|
||||
return exported
|
||||
|
||||
@@ -53,7 +53,10 @@ def load_kit() -> dict:
|
||||
for data in registry.get_assets(filt):
|
||||
mesh = unreal.EditorAssetLibrary.load_asset(str(data.package_name))
|
||||
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
|
||||
|
||||
|
||||
|
||||
@@ -42,6 +42,16 @@ def main() -> None:
|
||||
log(f"no FBX files in {SRC}")
|
||||
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:
|
||||
asset_name = os.path.splitext(filename)[0]
|
||||
task = unreal.AssetImportTask()
|
||||
|
||||
Reference in new issue
Block a user