Tree Gen 1.27.0: Blatt-Cards aus einer Collection (Punkt 3)
"Leaf Card" kann jetzt eine COLLECTION sein: je Blattpunkt wird zufaellig eine Card gezogen (Pick Instance, seed-stabil). Damit legt der Nutzer 4-6 Diamond-Cards mit unterschiedlichen Atlas-UVs an und die Krone mischt sie. Rueckwaertskompatibel - ein einzelnes Objekt geht weiter, die Sammlung hat Vorrang. Umsetzung: Collection Info mit Separate Children (sonst kaeme die Sammlung als ein Klumpen an und Pick Instance haette nichts zum Auswaehlen), Geometry-Switch zwischen Einzel- und Sammlungspfad, Instanz-Index aus einem Random Value, dessen Obergrenze aus der TATSAECHLICHEN Anzahl kommt - eine feste Obergrenze wuerde bei weniger Cards Luecken erzeugen. Geometry Nodes kann einen leeren Collection-Zeiger nicht selbst pruefen, deshalb setzt make_leaves einen "Use Collection"-Schalter. Beweisfuehrung im Test: die Zufalls-Groesse wird ABGESCHALTET, dann kommt jede Groessenabweichung nur von der Card-Variante und die Anzahl verschiedener Face-Flaechen ist ein exakter Zaehler. Gemessen: Einzel-Card 2 Flaechen (ein Card-Paar), Sammlung mit 4 Cards 10 Flaechen - alle Varianten kommen vor. Gleicher Seed identisch, anderer Seed anders. Nebenbei: die Vorpruefung des Blattwerk-Operators war eine zusammengesetzte Bedingung, die bei gesetzter Sammlung ohne Einzel-Card auf None zugriff. Neu: tests/test_blatt_varianten.py. 20 Tests bestehen. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,135 @@
|
||||
"""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()
|
||||
flaechen = sorted({round(p.area, 5) 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")
|
||||
Reference in New Issue
Block a user