"""Headless-Test fuer den Stylized Rock Generator. Aufruf (aus dem Repo-Root oder beliebig): blender --background --python tests/test_rock_gen.py Exit-Code 0 = alle Checks bestanden, 1 = mindestens ein Check fehlgeschlagen. Der Test: * registriert das Addon * erzeugt einen Batch von 3 Rocks mit Standard-Settings (Subsurf reduziert fuer Speed) * prueft je Rock: keine losen Vertices, manifold, Normalen nach aussen (signed volume > 0), UV0 + UV1 vorhanden * gibt am Ende eine Zusammenfassung (Face-/Vertex-Count) aus """ import os import sys import importlib.util import bpy import bmesh THIS_DIR = os.path.dirname(os.path.abspath(__file__)) ADDON_FILE = os.path.normpath(os.path.join(THIS_DIR, "..", "stylized_rock_generator.py")) BATCH = 3 def load_addon(): spec = importlib.util.spec_from_file_location("stylized_rock_generator", ADDON_FILE) mod = importlib.util.module_from_spec(spec) sys.modules["stylized_rock_generator"] = mod spec.loader.exec_module(mod) try: mod.unregister() except Exception: pass mod.register() return mod def mesh_stats(obj): bm = bmesh.new() bm.from_mesh(obj.data) bm.normal_update() loose = sum(1 for v in bm.verts if not v.link_faces) nonmanifold = sum(1 for e in bm.edges if not e.is_manifold) volume = bm.calc_volume(signed=True) stats = dict( name=obj.name, verts=len(bm.verts), faces=len(bm.faces), loose=loose, nonmanifold=nonmanifold, volume=volume, uv_layers=[layer.name for layer in obj.data.uv_layers], ) bm.free() return stats def main(): failures = [] # 1. Saubere, leere Szene bpy.ops.wm.read_factory_settings(use_empty=True) # 2. Addon registrieren (Scene-Property lebt am Typ -> nach Reset ok) load_addon() settings = bpy.context.scene.rock_gen_settings settings.batch_count = BATCH settings.subsurf_levels = 4 # Test-Speed settings.use_modal = False # synchron -> deterministisch settings.generate_uv0 = True settings.generate_lightmap_uv = True settings.generate_lods = False settings.assign_material = False # 3. Batch erzeugen result = bpy.ops.object.generate_rocks_batch() if 'FINISHED' not in result: failures.append("Operator lieferte nicht {'FINISHED'}: %s" % (result,)) rocks = [o for o in bpy.context.scene.objects if o.type == 'MESH' and o.name.startswith("Rock_")] if len(rocks) != BATCH: failures.append("Erwartet %d Rocks, gefunden %d" % (BATCH, len(rocks))) # 4. Validierung + Zusammenfassung print("\n" + "=" * 64) print(" Rock-Generator Test - Zusammenfassung") print("=" * 64) print(" %-16s %7s %7s %6s %6s %9s %s" % ("Name", "Verts", "Faces", "Loose", "NonMf", "Volume", "UVs")) print("-" * 64) for obj in sorted(rocks, key=lambda o: o.name): st = mesh_stats(obj) print(" %-16s %7d %7d %6d %6d %9.3f %s" % ( st["name"], st["verts"], st["faces"], st["loose"], st["nonmanifold"], st["volume"], ",".join(st["uv_layers"]))) if st["faces"] == 0: failures.append("%s: keine Faces" % st["name"]) if st["loose"] != 0: failures.append("%s: %d lose Vertices" % (st["name"], st["loose"])) if st["nonmanifold"] != 0: failures.append("%s: %d non-manifold Kanten" % (st["name"], st["nonmanifold"])) if st["volume"] <= 0.0: failures.append("%s: signed volume <= 0 (Normalen invertiert?)" % st["name"]) if "Lightmap" not in st["uv_layers"]: failures.append("%s: kein Lightmap-UV (UV1)" % st["name"]) if len(st["uv_layers"]) < 2: failures.append("%s: weniger als 2 UV-Layer" % st["name"]) print("=" * 64) if failures: print("\nFEHLGESCHLAGEN (%d):" % len(failures)) for f in failures: print(" - " + f) print("") sys.exit(1) print("\nAlle Checks bestanden.\n") sys.exit(0) if __name__ == "__main__": main()