Stylized Rock Generator v2.0.0: Haertung, Presets, LODs, Material, Tests
- 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>
This commit is contained in:
@@ -0,0 +1,135 @@
|
||||
"""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()
|
||||
Reference in New Issue
Block a user