- Fix: smart_project/dissolve_limited angle_limit jetzt in Radiant (Blender 4.x/5.x) - Fix: Lightmap-UV-Kanal vor lightmap_pack aktiv setzen (UV0 nicht mehr ueberschrieben) - Fehlerbehandlung pro bpy.ops-Call; einzelner Rock-Fehler bricht Batch nicht ab - Modaler Batch-Modus mit Progress-Bar (ab modal_threshold) - Bevel + Weighted Normal Toggle - LOD-Set als fbx_type=LodGroup-Gruppe fuer Unreal - Preset-System (JSON) + eingebaute Presets - Platzhalter-Material-Zuweisung - Headless-Testskript (tests/test_rock_gen.py) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
136 lines
4.0 KiB
Python
136 lines
4.0 KiB
Python
"""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()
|