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
@@ -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