Files
stylized-rock-generator/tests/test_blatt_varianten.py
T
D4rkst3randClaude Opus 4.8 b1fdad8106 Tree Gen 1.30.0: Bueschel-Defaults, Wachstums-Kopplung, Stufen-Budget
Blatt-Defaults auf die Bueschel-Atlas-Aera umgestellt. Eine Card ist ein ganzer
Zweigbueschel, kein einzelnes Blatt - die alten Werte streuten hunderte winziger
Cards, wo ein paar Dutzend grosse hingehoeren. Werte an einer S4-Eiche in UE
gemessen und abgenommen:

    Groesse      0.22 -> 0.85
    Dichte        500 -> 170
    Max Radius   0.02 -> 0.04     <- der Volumen-Hebel
    Kippung       0.5 -> 0.9

Max Radius ist der unscheinbare Held: bei 0.02 trugen nur die aeussersten
Zweigspitzen Laub, die Krone hatte nur Kontur und war innen nackt.

Bueschelgroesse haengt jetzt am Wachstum: make_object merkt growth_t am Objekt
(aus dem Namen zu lesen waere bruechig), der Blattwerk-Operator skaliert die
Cards damit. Gemessen ueber vier Stufen: Card-Kante 0.19/0.28/0.37/0.45.

Falle dabei: "Dichte" ist Punkte pro m2 Zweigflaeche, und ein Setzling hat davon
fast keine - bei Stufe 1 kamen exakt NULL Cards heraus, der Setzling stand kahl
da. Da die Bueschel um denselben Faktor kleiner sind, deckt jeder nur faktor^2
der Flaeche; die Dichte gegenlaeufig anzuheben haelt die Blattdeckung gleich.

Tri-Budget PRO STUFE (ASSETS.md Teil 6): S1 <= 800, S2 <= 1200, S3/S4 <= 4000.
Bewusst Stufen und keine Interpolation; die Schwellen greifen auch bei drei
Stufen richtig (t = 0 / 0.5 / 1.0). Gemessen 494 / 878 / 1032 / 1396 - alle im
Rahmen.

WICHTIGSTE FALLE, von der UE-Seite gemeldet und hier nachgestellt:
md[socket] = wert loest KEINE Neuauswertung des GN-Modifiers aus. Die
Blattebene blieb bei 118 Faces stehen, obwohl die Dichte von 90 auf 400
geschrieben war - auch view_layer.update() half nicht, erst ein
show_viewport-Toggle. Der Knopf-Weg funktionierte bisher nur ZUFAELLIG, weil
make_leaves jedes Mal auch die Objekt-Zeiger neu schreibt und das Aendern eines
ID-Zeigers die Depsgraph-Beziehungen umbaut. Wer das spaeter optimiert ("nur
geaenderte Werte schreiben"), haette den Fehler still zurueck.
_modifier_anstossen() macht es jetzt explizit - in make_leaves UND make_object.

Nebenbei: der Variantentest rundete Card-Flaechen auf 5 Stellen; seit die
Kippung bei 0.9 steht streuen sie in der sechsten und eine einzige Card-Sorte
zerfiel in vier Eimer.

Neu: tests/test_blatt_bueschel.py. 23 Tests bestehen.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-06 08:23:27 +02:00

139 lines
5.0 KiB
Python

"""Punkt 3: Blatt-Cards aus einer COLLECTION statt aus einem Einzelobjekt.
Damit kann der Nutzer 4-6 Diamond-Cards mit unterschiedlichen Atlas-UVs anlegen
und die Krone mischt sie. Rueckwaertskompatibel: ein einzelnes Objekt geht
weiter.
Beweisfuehrung: die Zufalls-Groesse wird ABGESCHALTET (Size Var 0). Dann kommt
jede Groessenabweichung ausschliesslich von der Card-Variante, und die Anzahl
verschiedener Face-Flaechen ist ein exakter Zaehler fuer die benutzten Varianten.
Mit eingeschalteter Groessenstreuung waere das Mass unbrauchbar - alles streut.
Aufruf: blender --background --factory-startup --python tests/test_blatt_varianten.py
"""
import os
import sys
import importlib.util
import bpy
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
spec = importlib.util.spec_from_file_location(
"tg", os.path.join(ROOT, "stylized_tree_generator.py"))
tg = importlib.util.module_from_spec(spec)
sys.modules["tg"] = tg
spec.loader.exec_module(tg)
tg.register()
fails = []
for o in list(bpy.data.objects):
bpy.data.objects.remove(o, do_unlink=True)
def card(name, groesse):
"""Diamond-Card in definierter Groesse - die Groesse ist hier der Marker."""
me = bpy.data.meshes.new(name)
g = groesse
me.from_pydata([(-g, 0, 0), (0, 0, g * 1.4), (g, 0, 0), (0, 0, -g * 0.2)],
[], [(0, 1, 2), (0, 2, 3)])
me.update()
ob = bpy.data.objects.new(name, me)
bpy.context.scene.collection.objects.link(ob)
return ob
coll = bpy.data.collections.new("Leaf_Cards")
bpy.context.scene.collection.children.link(coll)
einzel = card("Card_Single", 0.5)
GROESSEN = (0.30, 0.45, 0.60, 0.75)
for i, g in enumerate(GROESSEN):
c = card("Card_%02d" % (i + 1), g)
bpy.context.scene.collection.objects.unlink(c)
coll.objects.link(c)
s = bpy.context.scene.tree_gen_settings
s.preset = 'baum'
s.use_growth = False
s.apply_modifier = True
s.art_name = "Eiche"
s.variant = 1
s.leaf_size_var = 0.0 # entscheidend: Groesse kommt NUR von der Variante
bpy.ops.object.treegen_create()
baum = [o for o in bpy.context.scene.objects
if o.type == 'MESH' and o.name.startswith("Eiche_01")
and not o.name.endswith(("_Leaf", "_Frucht"))][0]
def streuen(collection, einzelcard, seed):
s.leaf_cards = collection
s.leaf_card = einzelcard
s.seed = seed
for o in bpy.context.scene.objects:
o.select_set(o is baum)
bpy.context.view_layer.objects.active = baum
bpy.ops.object.treegen_leaves()
bl = bpy.data.objects.get(baum.name + "_Leaf")
dg = bpy.context.evaluated_depsgraph_get()
ev = bl.evaluated_get(dg)
me = ev.to_mesh()
# Auf 4 Stellen runden, nicht auf 5: die Cards werden gedreht, und seit die
# Kippung bei 0.9 steht streuen die Flaechen in der sechsten Stelle. Mit
# 5 Stellen zerfiel eine einzige Card-Sorte in vier Eimer.
flaechen = sorted({round(p.area, 4) for p in me.polygons})
n = len(me.polygons)
ev.to_mesh_clear()
return n, flaechen
print("%-26s %7s %s" % ("Variante", "Faces", "verschiedene Flaechen"))
# --- 1) Rueckwaertskompatibel: Einzelobjekt ------------------------------
n1, f1 = streuen(None, einzel, 0)
print("%-26s %7d %d" % ("Einzel-Card", n1, len(f1)))
if n1 == 0:
fails.append("Einzel-Card erzeugt keine Blaetter - Rueckwaertskompatibilitaet weg")
# Eine Card = zwei unterschiedlich grosse Tris = genau zwei Flaechen.
if len(f1) > 3:
fails.append("Einzel-Card liefert %d verschiedene Flaechen, erwartet <= 3 - "
"die Groessenstreuung ist nicht wirklich aus" % len(f1))
# --- 2) Collection: alle Varianten kommen vor ----------------------------
n2, f2 = streuen(coll, None, 0)
print("%-26s %7d %d" % ("Collection (%d Cards)" % len(GROESSEN), n2, len(f2)))
if n2 == 0:
fails.append("Collection erzeugt keine Blaetter")
# Vier Cards mit je zwei Tris = acht verschiedene Flaechen, wenn wirklich alle
# vier gezogen werden. Weniger heisst: es kommen nicht alle Varianten vor.
erwartet = 2 * len(GROESSEN)
if len(f2) < erwartet:
fails.append("Nur %d verschiedene Flaechen, erwartet %d - es werden nicht "
"alle %d Card-Varianten gezogen"
% (len(f2), erwartet, len(GROESSEN)))
# --- 3) Seed-stabil ------------------------------------------------------
n3, f3 = streuen(coll, None, 0)
print("%-26s %7d %d" % ("gleicher Seed", n3, len(f3)))
if (n3, f3) != (n2, f2):
fails.append("Gleicher Seed liefert ein anderes Ergebnis - nicht stabil")
n4, f4 = streuen(coll, None, 7)
print("%-26s %7d %d" % ("anderer Seed", n4, len(f4)))
if (n4, f4) == (n2, f2):
fails.append("Anderer Seed liefert dasselbe Ergebnis - der Seed wirkt nicht")
# --- 4) Collection hat Vorrang vor der Einzel-Card -----------------------
n5, f5 = streuen(coll, einzel, 0)
print("%-26s %7d %d" % ("beides gesetzt", n5, len(f5)))
if len(f5) < erwartet:
fails.append("Mit gesetzter Einzel-Card faellt die Collection zurueck - "
"die Sammlung muss Vorrang haben")
print("")
if fails:
print("ERGEBNIS: %d FEHLER" % len(fails))
for x in fails:
print(" - " + x)
sys.exit(1)
print("ERGEBNIS: ALLE CHECKS OK")