Tree Gen 1.35.0: Node-Gruppen-Waechter + Mindestradius fuer Saemlinge
1) build_group riss Szenen-Baeume ab Die Gruppe GN_Tree wurde bei JEDEM Erzeugen geloescht und neu angelegt - damit zeigten alle bereits erzeugten Baeume auf ein totes Datablock und evaluierten leer. Gemessen: nach vier Arten-Erzeugungen lebte nur noch die letzte. Dass direkt nach dem Erzeugen exportiert wurde, hat das verdeckt. Jetzt ein Waechter mit ZWEI Kriterien: abgelegte Schema-Version (TREE_SCHEMA) UND die tatsaechlich vorhandenen Eingaenge. Die Version allein reicht nicht - wer waehrend der Entwicklung Sockets aendert ohne die Zahl anzufassen, bekaeme eine veraltete Gruppe untergeschoben. Muss doch neu gebaut werden, werden bestehende Modifier auf die neue Gruppe umgehaengt. Beide Pfade gemessen: vier Arten nacheinander leben alle (624/796/924/1036 Verts), und nach build_group(rebuild=True) ebenfalls unveraendert. 2) Saemlinge zu duenn Die McMahon-Skalierung macht duennstaemmige Arten am Setzling zu Draehten: Birke 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. SEEDLING_MIN_RADIUS + SEEDLING_MIN_FLARE heben die duennen Arten an, ohne die dicken anzufassen. Danach liegen alle bei 2.8 cm. test_proportionen laesst die McMahon-Abweichung genau dort zu, wo der Boden greift, und prueft dass sie nicht weiter geht - die Regel bleibt sonst scharf. 23 Tests bestehen. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user