diff --git a/README.md b/README.md index b098139..c4e1a5c 100644 --- a/README.md +++ b/README.md @@ -563,6 +563,36 @@ Der Kaktus erzeugt regulaer **0 Cards** — kein Ast ist duenner als `Max Radius und das ist botanisch richtig. Vorher lief das still durch und man stand vor einer leeren `_Leaf`-Ebene; jetzt sagt der Operator es und nennt den Regler. +### Die Node-Gruppe wird WIEDERVERWENDET, nicht neu gebaut + +`build_group()` loeschte die Gruppe `GN_Tree` bei jedem Erzeugen und legte sie +neu an — damit riss sie sie **allen bereits erzeugten Baeumen** unter dem +Modifier weg. Gemessen: nach vier Arten-Erzeugungen lebte nur noch die letzte, +alle frueheren evaluierten leer. Dass in der Praxis direkt nach dem Erzeugen +exportiert wurde, hat das lange verdeckt. + +Jetzt prueft ein Waechter zwei Dinge, bevor er ueberhaupt neu baut: die +abgelegte **Schema-Version** (`TREE_SCHEMA`) *und* die tatsaechlich vorhandenen +Eingaenge. Die Version allein reichte nicht — wer waehrend der Entwicklung +Sockets aendert, ohne die Zahl anzufassen, bekaeme sonst eine veraltete Gruppe +untergeschoben. Muss doch neu gebaut werden, werden **bestehende Modifier auf +die neue Gruppe umgehaengt**. Beide Pfade sind gemessen: vier Arten +nacheinander leben alle, und nach einem erzwungenen `build_group(rebuild=True)` +ebenfalls. + +### Saemlinge: Mindestradius sticht McMahon + +Die Skalierung nach McMahon (Durchmesser ~ Hoehe^1.5) macht duennstaemmige +Arten am Setzling zu Draehten: gemessen kam die Birke auf **0.6 cm** Fussradius +gegen **1.9 cm** der Eiche — dreimal duenner, waehrend der Unterschied beim +Altbaum nur 1.2x betraegt. Die Regel gilt fuer die Statik ausgewachsener Baeume; +ein Saemling ist eine Pflanze, kein Balken. + +`SEEDLING_MIN_RADIUS` und `SEEDLING_MIN_FLARE` heben die duennen Arten an, ohne +die dicken (Tanne, Palme, Kaktus) anzufassen. Danach liegen alle bei **2.8 cm**. +`tests/test_proportionen.py` laesst die McMahon-Abweichung genau dort zu, wo der +Boden greift — und prueft, dass sie nicht weiter geht. + ### Faell-Teile (Punkt 4) Kein Laufzeit-Zerlegen: beim Faellen tauscht das Spiel die ISM-Instanz gegen diff --git a/blender_manifest_tree.toml b/blender_manifest_tree.toml index 5ea4525..7a60f24 100644 --- a/blender_manifest_tree.toml +++ b/blender_manifest_tree.toml @@ -1,7 +1,7 @@ schema_version = "1.0.0" id = "stylized_tree_generator" -version = "1.34.0" +version = "1.35.0" name = "Stylized Tree Generator" tagline = "Parametrische Baeume, Palmen, Bueschen mit Wachstums-Stufen" maintainer = "D4rkst3r" diff --git a/dist/index.html b/dist/index.html index b7b91ca..8adcbf2 100644 --- a/dist/index.html +++ b/dist/index.html @@ -50,17 +50,17 @@ 34.1KB - stylized_tree_generator-1.34.0 + stylized_tree_generator-1.35.0 Stylized Tree Generator Parametrische Baeume, Palmen, Bueschen mit Wachstums-Stufen link 4.2.0 - ~ all all - 40.0KB + 41.2KB -

Built 2026-08-06, 08:32

+

Built 2026-08-06, 09:08

diff --git a/dist/index.json b/dist/index.json index 4fcc035..3ccfb09 100644 --- a/dist/index.json +++ b/dist/index.json @@ -82,7 +82,7 @@ "id": "stylized_tree_generator", "name": "Stylized Tree Generator", "tagline": "Parametrische Baeume, Palmen, Bueschen mit Wachstums-Stufen", - "version": "1.34.0", + "version": "1.35.0", "type": "add-on", "maintainer": "D4rkst3r", "license": [ @@ -98,9 +98,9 @@ "Mesh", "Modeling" ], - "archive_url": "./stylized_tree_generator-1.34.0.zip", - "archive_size": 40964, - "archive_hash": "sha256:1cb548b5fef9bb79342ef92f7bc40f7ecb66e21f6356c8e00623ada5c842b0d6" + "archive_url": "./stylized_tree_generator-1.35.0.zip", + "archive_size": 42185, + "archive_hash": "sha256:db4de9904c876c1336879fe4de7a183f1a1f2c7dc52c55e8278e0bf7e43bd323" } ] } \ No newline at end of file diff --git a/dist/stylized_tree_generator-1.34.0.zip b/dist/stylized_tree_generator-1.34.0.zip deleted file mode 100644 index 2928779..0000000 Binary files a/dist/stylized_tree_generator-1.34.0.zip and /dev/null differ diff --git a/dist/stylized_tree_generator-1.35.0.zip b/dist/stylized_tree_generator-1.35.0.zip new file mode 100644 index 0000000..d750edb Binary files /dev/null and b/dist/stylized_tree_generator-1.35.0.zip differ diff --git a/stylized_tree_generator.py b/stylized_tree_generator.py index e28012f..778746a 100644 --- a/stylized_tree_generator.py +++ b/stylized_tree_generator.py @@ -1,7 +1,7 @@ bl_info = { "name": "Stylized Tree Generator", "author": "D4rkst3r", - "version": (1, 34, 0), + "version": (1, 35, 0), "blender": (4, 2, 0), "location": "View3D > Sidebar > Tree Gen", "description": "Parametrischer Baum-/Palmen-/Busch-Generator (Geometry Nodes) mit Wachstums-Stufen", @@ -222,11 +222,53 @@ def _out(node, *names): return node.outputs[0] -def build_group(): - old = bpy.data.node_groups.get(GROUP_NAME) - if old: - bpy.data.node_groups.remove(old) +# Schema-Version der Baum-Gruppe. HOCHZAEHLEN, sobald sich Sockets aendern - +# daran erkennt build_group, ob eine vorhandene Gruppe noch passt. +TREE_SCHEMA = 3 + + +def _gruppe_passt(ng): + """Ist eine vorhandene GN_Tree-Gruppe noch die aktuelle? + + Zwei Kriterien: die abgelegte Schema-Version UND die tatsaechlich + vorhandenen Eingaenge. Die Version allein reichte nicht - wer waehrend der + Entwicklung Sockets aendert, ohne die Zahl anzufassen, bekaeme eine + veraltete Gruppe untergeschoben. + """ + if ng.get("schema") != TREE_SCHEMA: + return False + vorhanden = {so.name for so in ng.interface.items_tree + if getattr(so, "in_out", "") == 'INPUT'} + return set(DEFAULTS).issubset(vorhanden) + + +def build_group(rebuild=False): + """Baut die Baum-Node-Gruppe - aber nur, wenn noetig. + + FALLE, die das hier noetig macht: die Gruppe bedingungslos zu loeschen und + neu anzulegen reisst sie ALLEN bereits erzeugten Baeumen unter dem Modifier + weg. Gemessen: nach vier Arten-Erzeugungen lebte nur noch die letzte, alle + frueheren evaluierten leer. Dass der Export direkt nach dem Erzeugen lief, + hat das lange verdeckt. + + Wenn doch neu gebaut werden muss (Schema-Aenderung), werden bestehende + Modifier auf die neue Gruppe umgehaengt - sonst zeigen sie auf ein totes + Datablock. + """ + alt = bpy.data.node_groups.get(GROUP_NAME) + if alt is not None and not rebuild and _gruppe_passt(alt): + return alt + + nutzer = [] + if alt is not None: + for ob in bpy.data.objects: + for md in ob.modifiers: + if getattr(md, "node_group", None) is alt: + nutzer.append(md) + bpy.data.node_groups.remove(alt) + ng = bpy.data.node_groups.new(GROUP_NAME, "GeometryNodeTree") + ng["schema"] = TREE_SCHEMA N, L = ng.nodes.new, ng.links.new iface = ng.interface @@ -1042,6 +1084,15 @@ def build_group(): shade = N("GeometryNodeSetShadeSmooth"); shade.location = (2200, 200) L(_out(pick, "Output"), _sock(shade, "Geometry")) L(_out(shade, "Geometry"), gout.inputs[0]) + + # Bestehende Modifier auf die NEUE Gruppe umhaengen. Ohne das zeigen sie + # nach einem Rebuild auf ein geloeschtes Datablock und der Baum evaluiert + # leer - der Fehler, der ueberhaupt zu diesem Waechter gefuehrt hat. + for md in nutzer: + try: + md.node_group = ng + except (ReferenceError, AttributeError): + pass return ng @@ -1050,6 +1101,17 @@ def build_group(): # ein Wachstums-System, damit Stufe 1 und Stufe 4 wie DERSELBE Baum wirken. GROWTH_HEIGHT = 0.16 # Setzling ist 16 % der Endhoehe +# Mindest-Stammradius und Mindest-Wurzelanlauf fuer den Saemling. +# +# Die McMahon-Skalierung (Durchmesser ~ Hoehe^1.5) macht duennstaemmige Arten am +# Setzling zu Draehten: gemessen kam die Birke auf 0.6 cm Fussradius gegen 1.9 +# der Eiche - dreimal duenner, waehrend der Unterschied beim Altbaum nur 1.2x +# betraegt. Die Regel gilt fuer die STATIK ausgewachsener Baeume; ein Saemling +# ist eine Pflanze, kein Balken. Der Boden hebt die duennen Arten an, ohne die +# dicken (Tanne, Palme, Kaktus) anzufassen. +SEEDLING_MIN_RADIUS = 0.018 # m, Stammradius am Fuss vor dem Wurzelanlauf +SEEDLING_MIN_FLARE = 0.55 # Wurzelanlauf-Faktor, sonst steht der Stiel stumpf + GROWTH_YOUNG = { "Height": GROWTH_HEIGHT, # Faktoren (werden mit dem Zielwert multipliziert) @@ -1128,6 +1190,12 @@ def growth_values(preset, t): target = full[key] young = target * young_factor v = young + (target - young) * t + # Boden fuer die Saemlings-Stufen anwenden, bevor gerundet wird. + if key == "Trunk Radius": + v = max(v, min(target, SEEDLING_MIN_RADIUS)) + elif key == "Root Flare": + v = max(v, min(target, SEEDLING_MIN_FLARE)) + if isinstance(target, int): # Ein Faktor von genau 0.0 heisst "gibt es beim Setzling noch gar # nicht" und darf auf 0 fallen. Alles andere bekommt mindestens 1, diff --git a/tests/test_proportionen.py b/tests/test_proportionen.py index 38b0c36..719c6c1 100644 --- a/tests/test_proportionen.py +++ b/tests/test_proportionen.py @@ -12,6 +12,9 @@ belegt - deshalb lohnt es sich, sie festzunageln: * Setzling-Schlankheit: elastische Aehnlichkeit nach McMahon, Hoehe ~ Durchmesser^(2/3). Der Setzling darf relativ zu seiner Hoehe nicht dicker sein als der Altbaum. Vorher war er es um den Faktor 2.4. + AUSNAHME: wo der Mindest-Stammradius greift, ist die Abweichung gewollt - + McMahon gilt fuer die Statik ausgewachsener Baeume, ein Saemling ist eine + Pflanze und kein Balken. * Palme und Kaktus sind davon ausgenommen: Palmen haben kein Dickenwachstum ("its diameter is fixed for life", UF/IFAS ENH1212), Kakteen sind Sukkulenten. Beide bauen zuerst den Durchmesser auf - deren Setzling ist absichtlich @@ -107,10 +110,20 @@ for p in tg.PRESETS: continue schlank_alt = r_alt / h_alt schlank_neu = r_neu / h_neu - if schlank_neu > schlank_alt * 1.5: + # AUSNAHME: der Mindest-Stammradius fuer Saemlinge sticht McMahon. Die Regel + # gilt fuer die Statik ausgewachsener Baeume; duennstaemmige Arten wurden am + # Setzling sonst zu Draehten (Birke 0.6 cm Fussradius gegen 1.9 der Eiche, + # dreimal duenner, waehrend der Unterschied beim Altbaum nur 1.2x betraegt). + # Wo der Boden greift, ist die Abweichung gewollt - geprueft wird dann nur, + # dass sie NICHT weiter geht als der Boden hergibt. + am_boden = r_neu <= tg.SEEDLING_MIN_RADIUS * 1.05 + grenze = 2.5 if am_boden else 1.5 + if schlank_neu > schlank_alt * grenze: fails.append("%s: Setzling ist relativ %.1fx dicker als der Altbaum " - "(r/H %.4f gegen %.4f) - McMahon verlangt schlanker" - % (p, schlank_neu / schlank_alt, schlank_neu, schlank_alt)) + "(r/H %.4f gegen %.4f)%s" + % (p, schlank_neu / schlank_alt, schlank_neu, schlank_alt, + " - mehr als der Mindestradius hergibt" if am_boden + else " - McMahon verlangt schlanker")) # --- 5) Kein Setzling haengt nennenswert unter den Wurzelansatz ----------- # Wedel und Aeste, die unter den Ursprung reichen, lassen das Asset in UE