Add a Blender GLB intake inspection command
This commit is contained in:
1 parent
6f3facff43
commit
5dc3ef0f4c
6 files changed
+510
-1
No files matched your search
@@ -95,5 +95,110 @@ class BuiltAssetTests(unittest.TestCase):
|
||||
self.assertTrue((ba.SRC_DIR / ".gdignore").is_file())
|
||||
|
||||
|
||||
class InspectionTests(unittest.TestCase):
|
||||
"""`inspect` reports on imported GLBs; the contract logic is pure and tested
|
||||
here with synthetic measurements so Blender is not a test dependency."""
|
||||
|
||||
def stats(self, category="npc", **overrides):
|
||||
base = {
|
||||
"file": "/tmp/synthetic.glb",
|
||||
"category": category,
|
||||
"objects": 1,
|
||||
"mesh_nodes": ["npc_base"] if category == "npc" else ["prop"],
|
||||
"armatures": ["Armature"] if category == "npc" else [],
|
||||
"helpers": [],
|
||||
"bones": list(ba.BONES) + ["Hips", "Spine", "Neck"],
|
||||
"bone_heads": {},
|
||||
"materials": list(ba.SLOTS),
|
||||
"images": 5,
|
||||
"uv_layers": ["UVMap"],
|
||||
"meshes_without_uv": 0,
|
||||
"meshes_without_material": 0,
|
||||
"unskinned": [],
|
||||
"skinned": category == "npc",
|
||||
"animations": 0,
|
||||
"triangles": 4500,
|
||||
"vertices": 4200,
|
||||
"split_vertices": 4400,
|
||||
"local_bounds": {"min": [-0.3, 0.0, -0.2], "max": [0.3, 1.82, 0.2]},
|
||||
"world_bounds": {"min": [-0.3, 0.0, -0.2], "max": [0.3, 1.82, 0.2]},
|
||||
"transform_delta": 0.0,
|
||||
"transforms_applied": True,
|
||||
}
|
||||
base.update(overrides)
|
||||
return base
|
||||
|
||||
def test_clean_character_passes(self):
|
||||
result = ba.analyse_inspection(self.stats())
|
||||
self.assertTrue(result["ok"], result["issues"])
|
||||
self.assertEqual(result["bones_missing"], [])
|
||||
self.assertEqual(result["budget"]["max_triangles"], ba.CHAR_MAX_TRIS)
|
||||
|
||||
def test_character_contract_failures_are_listed(self):
|
||||
stats = self.stats(mesh_nodes=["Armature"], skinned=False, unskinned=["Armature"],
|
||||
materials=["cloth"], bones=["Head"], meshes_without_uv=1,
|
||||
triangles=9000, split_vertices=9000,
|
||||
world_bounds={"min": [-0.3, -0.4, -0.2], "max": [0.3, 1.2, 0.2]})
|
||||
issues = "\n".join(ba.analyse_inspection(stats)["issues"])
|
||||
self.assertIn("no mesh node named npc_base", issues)
|
||||
self.assertIn("missing bone HipL", issues)
|
||||
self.assertIn("missing material slot skin", issues)
|
||||
self.assertIn("mesh not skinned", issues)
|
||||
self.assertIn("mesh(es) without UVs", issues)
|
||||
self.assertIn(f"triangles 9000 > {ba.CHAR_MAX_TRIS}", issues)
|
||||
self.assertIn(f"glTF vertices 9000 > {ba.CHAR_MAX_VERTS}", issues)
|
||||
self.assertIn("feet not on the ground", issues)
|
||||
self.assertIn("unexpected height", issues)
|
||||
|
||||
def test_budget_gates_follow_the_category(self):
|
||||
gear = ba.analyse_inspection(self.stats("npc_gear", mesh_nodes=["cap"],
|
||||
triangles=ba.GEAR_MAX_TRIS + 1))
|
||||
self.assertIn(f"triangles {ba.GEAR_MAX_TRIS + 1} > {ba.GEAR_MAX_TRIS}",
|
||||
"\n".join(gear["issues"]))
|
||||
prop = ba.analyse_inspection(self.stats("props", mesh_nodes=["bollard"],
|
||||
triangles=ba.PROP_MAX_TRIS + 1))
|
||||
self.assertIn(f"triangles {ba.PROP_MAX_TRIS + 1} > {ba.PROP_MAX_TRIS}",
|
||||
"\n".join(prop["issues"]))
|
||||
external = ba.analyse_inspection(self.stats("external", mesh_nodes=["body"],
|
||||
materials=[], skinned=False,
|
||||
triangles=999_999,
|
||||
world_bounds={"min": [-3.0, -1.0, -3.0],
|
||||
"max": [3.0, 9.0, 3.0]}))
|
||||
self.assertTrue(external["ok"], external["issues"])
|
||||
self.assertIsNone(external["budget"]["max_triangles"])
|
||||
self.assertTrue(any("no project budget" in w for w in external["warnings"]))
|
||||
|
||||
def test_unapplied_transforms_are_reported_not_failed(self):
|
||||
result = ba.analyse_inspection(self.stats(transform_delta=1.9))
|
||||
self.assertTrue(result["ok"])
|
||||
self.assertTrue(any("node transforms changed the pose" in w
|
||||
for w in result["warnings"]))
|
||||
|
||||
def test_missing_mesh_or_bounds_is_a_failure(self):
|
||||
result = ba.analyse_inspection(self.stats(mesh_nodes=[], objects=0, triangles=0,
|
||||
world_bounds={"min": None, "max": None}))
|
||||
self.assertFalse(result["ok"])
|
||||
self.assertIn("no mesh objects", result["issues"])
|
||||
self.assertIn("no triangles", result["issues"])
|
||||
self.assertIn("empty world bounds", result["issues"])
|
||||
|
||||
def test_category_inference_from_path(self):
|
||||
self.assertEqual(ba.category_for_path("assets/models/blender/npc/npc_casual.glb"), "npc")
|
||||
self.assertEqual(ba.category_for_path("assets/models/blender/npc_gear/cap.glb"),
|
||||
"npc_gear")
|
||||
self.assertEqual(ba.category_for_path("assets/models/blender/props/bollard.glb"),
|
||||
"props")
|
||||
self.assertEqual(ba.category_for_path("assets/models/generated/bollard.glb"), "props")
|
||||
self.assertEqual(ba.category_for_path("tmp/quaternius/Worker.glb"), "external")
|
||||
|
||||
def test_inspect_requires_blender(self):
|
||||
# The stdlib test run has no bpy; inspect must fail cleanly, not crash.
|
||||
if ba.bpy is not None: # pragma: no cover - only inside Blender
|
||||
self.skipTest("running inside Blender")
|
||||
result = ba.inspect_glb("does-not-exist.glb")
|
||||
self.assertFalse(result["ok"])
|
||||
self.assertIn("inside Blender", result["error"])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in new issue
Block a user