Weniger einstellen muessen: oben im Panel Sorte waehlen, Haken druecken, erzeugen. Die neun Unter-Panels bleiben fuer den Feinschliff. Findling rund, weiche Kanten ~1160 V / ~2320 Tris Platte flach, liegt satt auf ~670 V / ~1340 Tris Kantig Bevel + Weighted Normal ~350 V / ~690 Tris Erzbrocken dicht genug fuer die Maske ~1280 V / ~2560 Tris, Slot M_Rock_Ore dissolve_angle ist jetzt ein BEREICH statt eines festen Werts - direkt aus der Rueckmeldung "hab ich immer auf 17-25 je nach Stein". Je Stein wird einer gezogen; gemessen an sechs Findlingen bei 17-25 Grad: 1936-2928 Tris. Beide Regler gleich setzen = fester Winkel wie frueher. Zwei Sachen fielen beim Messen auf: 1) Der Dissolve-Winkel frisst deutlich Geometrie. Derselbe dichte Satz liefert bei niedrigem Standardwinkel ~2700 Tris, bei 17-25 Grad nur noch ~1030 und faellt damit unter ORE_MIN_VERTS - das Erz-Set haette seine EIGENE Warnung ausgeloest, sobald vorher eine Sorte mit hohem Winkel gewaehlt war. Der Bereich gehoert deshalb mit in DENSE_PRESET. Ueber vier Seeds und beide Groessenklassen nachgemessen: 0.70/0.92 haelt den Worst Case bei 847 Verts. 2) Erz-Dichte und Tri-Fenster kollidieren. Die Maske braucht ~800 Verts auch beim kleinen S-Brocken, das treibt einen M-Stein auf ~3400 Tris - ueber das Fenster 1500-3000. Deshalb entkoppelt: normale Sorten leichter (0.40/0.80), Erzbrocken knapper (0.45/0.82), das S+M-SET dichter (0.70/0.92), weil dort die kleinen Brocken die Untergrenze setzen. Nebenbei gefunden und behoben: der erzwungene Ursprung wurde aus der GEMEINSAMEN Bbox aller LOD-Stufen berechnet. Eine stark dezimierte Stufe kann tiefer reichen als LOD0 - dann sass LOD0 sieben Zentimeter ueber dem Pivot und der Stein haette in UE geschwebt. Unreal platziert nach LOD0, also gibt LOD0 jetzt den Ursprung vor. Neu: tests/test_schnellstart.py. 18 Tests bestehen. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
119 lines
4.5 KiB
Python
119 lines
4.5 KiB
Python
"""Schnellstart-Sorten + Dissolve-Bereich.
|
|
|
|
Der Schnellstart soll den Alltag abdecken, ohne dass man sich durch neun
|
|
Unter-Panels klickt. Damit das etwas wert ist, muss jede Sorte OHNE
|
|
Nacharbeit ein brauchbares Ergebnis liefern - genau das prueft diese Datei.
|
|
|
|
Der Dissolve-Winkel ist ein BEREICH, weil ein fester Wert nie fuer alle Steine
|
|
einer Sorte passt. Geprueft wird deshalb auch, dass der Bereich tatsaechlich je
|
|
Stein streut und nicht nur als Zahl im Panel steht.
|
|
|
|
Aufruf: blender --background --factory-startup --python tests/test_schnellstart.py
|
|
"""
|
|
import os
|
|
import sys
|
|
importlib_util = __import__("importlib.util", fromlist=["util"])
|
|
|
|
import bpy
|
|
|
|
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
spec = importlib_util.spec_from_file_location(
|
|
"rg", os.path.join(ROOT, "stylized_rock_generator.py"))
|
|
rg = importlib_util.module_from_spec(spec)
|
|
sys.modules["rg"] = rg
|
|
spec.loader.exec_module(rg)
|
|
rg.register()
|
|
|
|
fails = []
|
|
s = bpy.context.scene.rock_gen_settings
|
|
s.use_modal = False
|
|
|
|
|
|
def bauen(anzahl=3, seed=0):
|
|
for o in list(bpy.data.objects):
|
|
bpy.data.objects.remove(o, do_unlink=True)
|
|
s.batch_count = anzahl
|
|
s.seed = seed
|
|
bpy.ops.object.generate_rocks_batch()
|
|
return sorted((o for o in bpy.context.scene.objects if o.type == 'MESH'),
|
|
key=lambda o: o.name)
|
|
|
|
|
|
print("%-12s %7s %7s %10s %s" % ("Sorte", "Verts", "Tris", "Dissolve", "Slot"))
|
|
for kind, eintrag in rg.ROCK_KINDS.items():
|
|
s.rock_kind = kind
|
|
res = bpy.ops.rockgen.apply_kind()
|
|
if 'FINISHED' not in res:
|
|
fails.append("Sorte %s: apply_kind lieferte %s" % (kind, res))
|
|
continue
|
|
|
|
obs = bauen()
|
|
if not obs:
|
|
fails.append("Sorte %s erzeugt keine Geometrie" % kind)
|
|
continue
|
|
|
|
verts = sum(len(o.data.vertices) for o in obs) // len(obs)
|
|
tris = sum(sum(len(p.vertices) - 2 for p in o.data.polygons)
|
|
for o in obs) // len(obs)
|
|
slots = [m.name for m in obs[0].data.materials]
|
|
print("%-12s %7d %7d %5.0f-%-4.0f %s"
|
|
% (eintrag["label"], verts, tris, s.dissolve_angle,
|
|
s.dissolve_angle_max, slots or "KEINE"))
|
|
|
|
if not slots:
|
|
fails.append("Sorte %s: kein Material-Slot" % eintrag["label"])
|
|
elif not slots[0].startswith("M_Rock_"):
|
|
fails.append("Sorte %s: Slot '%s' folgt nicht M_Rock_<Sorte>"
|
|
% (eintrag["label"], slots[0]))
|
|
|
|
# Der Bereich muss sinnvoll sein - untere Grenze nicht ueber der oberen.
|
|
if s.dissolve_angle > s.dissolve_angle_max:
|
|
fails.append("Sorte %s: Dissolve-Bereich verdreht (%.0f > %.0f)"
|
|
% (eintrag["label"], s.dissolve_angle, s.dissolve_angle_max))
|
|
|
|
# Die Erz-Sorte muss dicht genug fuer eine saubere Nugget-Maske sein -
|
|
# sonst loest die eigene Vorlage die eigene Warnung aus.
|
|
if eintrag["values"].get("bake_ore"):
|
|
if slots != [rg.ORE_MATERIAL[0]]:
|
|
fails.append("Sorte %s: Slot %s, erwartet ['%s']"
|
|
% (eintrag["label"], slots, rg.ORE_MATERIAL[0]))
|
|
duenn = [o for o in obs if len(o.data.vertices) < rg.ORE_MIN_VERTS]
|
|
if duenn:
|
|
fails.append("Sorte %s: %d von %d Steinen unter %d Verts - die "
|
|
"eigene Vorlage wuerde die eigene Dichte-Warnung "
|
|
"ausloesen" % (eintrag["label"], len(duenn), len(obs),
|
|
rg.ORE_MIN_VERTS))
|
|
else:
|
|
if slots == [rg.ORE_MATERIAL[0]]:
|
|
fails.append("Sorte %s traegt den Erz-Slot ohne Erz"
|
|
% eintrag["label"])
|
|
|
|
# --- Der Dissolve-Bereich muss je Stein streuen --------------------------
|
|
s.rock_kind = 'FINDLING'
|
|
bpy.ops.rockgen.apply_kind()
|
|
obs = bauen(anzahl=6, seed=3)
|
|
tris = [sum(len(p.vertices) - 2 for p in o.data.polygons) for o in obs]
|
|
print("")
|
|
print("Findling x6 bei %.0f-%.0f Grad: %s"
|
|
% (s.dissolve_angle, s.dissolve_angle_max, tris))
|
|
if len(set(tris)) < 3:
|
|
fails.append("Dissolve-Bereich streut nicht je Stein: %s" % tris)
|
|
|
|
# Bereich zu = fester Winkel wie frueher. Muss weiter funktionieren.
|
|
s.dissolve_angle_max = s.dissolve_angle
|
|
obs = bauen(anzahl=2, seed=3)
|
|
if not obs:
|
|
fails.append("Geschlossener Dissolve-Bereich erzeugt keine Geometrie")
|
|
else:
|
|
print("Bereich zu (%.0f Grad): %d Steine, %d Tris"
|
|
% (s.dissolve_angle, len(obs),
|
|
sum(len(p.vertices) - 2 for p in obs[0].data.polygons)))
|
|
|
|
print("")
|
|
if fails:
|
|
print("ERGEBNIS: %d FEHLER" % len(fails))
|
|
for x in fails:
|
|
print(" - " + x)
|
|
sys.exit(1)
|
|
print("ERGEBNIS: ALLE CHECKS OK")
|