Rock Gen 2.19.0: Schnellstart-Sorten + Dissolve als Bereich

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>
This commit is contained in:
2026-08-03 14:21:14 +02:00
co-authored by Claude Opus 4.8
parent f996832d75
commit 74df6dfa1b
8 changed files with 295 additions and 16 deletions
+118
View File
@@ -0,0 +1,118 @@
"""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")