Tree Gen 1.25.0: Palme, Busch und Setzlinge nachgemessen und korrigiert
Drei gemeldete Probleme - alle bestaetigt, Ursachen gemessen statt geraten.
1) Palme sah "extrem komisch" aus
Gemessen: Kronenradius/Hoehe 0.11 - die Wedel klebten als Mop-Kopf am
Stamm. Zielwert aus zwei unabhaengigen Quellen, die sich decken:
tree-gen palm.py (Weber&Penn) setzt Wedellaenge = 0.3 x Stamm, die
TreeIt-Referenzpalme kommt auf 3.3 m bei 4.33 m = 0.38.
Jetzt 0.30. Dazu der Winkel: tree-gen gibt down_angle 60 + curve 40 an,
also schraeg raus und ueberbiegen. Mit Branch Up 0 allein standen die
Wedel als gerade Antennenspeichen ab; mit Up 0.35 / Droop 1.2 steigen sie
gemessen 1.1 m ueber den Ansatz und kippen dann ab.
2) Busch sah aus wie ein Baum
Gemessen: 1.81 m hoch bei 0.84 m Breite (B/H 0.46), durchgehender Stamm,
Aeste erst ab 15 %, Wurzelanlauf wie beim Baum. Das IST ein Baeumchen.
Botanisch ist der Unterschied nicht die Groesse, sondern die Architektur:
mehrere Stiele ab Boden, kein Leittrieb, gemessen nach Hoehe UND Breite.
Jetzt kurzer Stumpf + Aeste ab Boden: 1.20 m hoch, 2.0 m breit (B/H 1.6).
3) Kleinste Wachstumsstufe sah komisch aus / "nur runterskaliert"
Gemessen: der Setzling war relativ zu seiner Hoehe 2.4x DICKER als der
Altbaum (r/H 0.025 gegen 0.0104), weil Trunk Radius mit 0.30 skalierte,
die Hoehe aber mit 0.16. Botanisch gilt die elastische Aehnlichkeit nach
McMahon: Hoehe ~ Durchmesser^(2/3), also Faktor 0.16^1.5 = 0.064.
Dazu behielt jeder Setzling den vollen Wurzelanlauf -> Trichterfuss.
Neu ausserdem: Aeste schrumpfen mit der Hoehe statt langsamer, sitzen
tiefer (Aufasten kommt erst mit dem Alter) und haengen weniger.
Nicht jede Pflanze waechst wie ein Laubbaum -> GROWTH_OVERRIDES:
Palme kein Dickenwachstum, erst Durchmesser dann Hoehe (UF/IFAS ENH1212:
"its diameter is fixed for life") -> Setzling ist ein kurzer Stumpf
Kaktus Sukkulent, gleiche Logik, und OHNE Arme: Saguaros bilden die erst
mit 50-70 Jahren bei rund 2 m Hoehe (NPS)
Weide haengt erst mit langen Zweigen; sonst reichten sie 0.27 m unter
den Stammfuss und das Asset waere in UE geschwebt
Zwei Bugs fielen dabei auf:
- Branch Count = 0 schaltete die Aeste NICHT ab - dieselbe Falle wie damals
bei Sub Count (Curve to Points klemmt Count auf min. 1). Fuer die Sub-Ebene
gab es laengst einen Geometry-Switch, fuer die Hauptaeste nicht.
- Im Busch-Preset stand "Branch Count" zweimal im selben Dict; der zweite
Eintrag (10) hat den ersten (14) still ueberschrieben.
Die sieben uebrigen Presets sind unveraendert (gleiche Maße, gleiche Tris).
Neu: tests/test_proportionen.py nagelt die Zielwerte fest. 14 Tests bestehen.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+122
-16
@@ -1,7 +1,7 @@
|
||||
bl_info = {
|
||||
"name": "Stylized Tree Generator",
|
||||
"author": "D4rkst3r",
|
||||
"version": (1, 24, 0),
|
||||
"version": (1, 25, 0),
|
||||
"blender": (4, 2, 0),
|
||||
"location": "View3D > Sidebar > Tree Gen",
|
||||
"description": "Parametrischer Baum-/Palmen-/Busch-Generator (Geometry Nodes) mit Wachstums-Stufen",
|
||||
@@ -95,10 +95,19 @@ PRESETS = {
|
||||
"Tip Blunt": 0.7, # Krone bleibt dick (Kronenschaft) UND die
|
||||
# Stiele laufen nicht drahtig aus
|
||||
"Trunk Tip": 1.0,
|
||||
"Branch Count": 11, "Branch Length": 0.78, "Branch Up": 0.9,
|
||||
# Wedel-Geometrie nach zwei unabhaengigen Quellen, die sich decken:
|
||||
# tree-gen palm.py (Weber&Penn) sagt length[1] = 0.3 x Stammlaenge,
|
||||
# branches[1] = 25, down_angle 60 Grad, curve 40 Grad, tropism -3.
|
||||
# Die TreeIt-Referenzpalme kommt auf Spannweite 3.3 m bei 4.33 m Stamm
|
||||
# = Kronenradius/Hoehe 0.38. Ziel also 0.30-0.38.
|
||||
# Vorher: Length 0.78 + Up 0.9 ergab GEMESSEN 0.11 - die Krone war ein
|
||||
# Mop-Kopf, der am Stamm klebte, statt einer ausladenden Krone.
|
||||
# Jetzt gemessen: 0.34, Wedel steigen 1.1 m ueber den Ansatz und kippen
|
||||
# dann ab (echter Bogen statt Antennenspeichen oder Haengemop).
|
||||
"Branch Count": 18, "Branch Length": 2.0, "Branch Up": 0.35,
|
||||
"Branch Start": 0.9, "Branch End": 1.0,
|
||||
"Branch Bend": 0.3,
|
||||
"Branch Droop": 0.5, "Droop Curve": 2.2,
|
||||
"Branch Bend": 0.25,
|
||||
"Branch Droop": 1.2, "Droop Curve": 2.2,
|
||||
"Attraction Up": 0.2, # Wedel schwingen leicht hoch, dann Bogen
|
||||
"Branch Thickness": 0.16,
|
||||
"Branch Taper": 0.45,
|
||||
@@ -109,13 +118,23 @@ PRESETS = {
|
||||
"Detail": 0.55,
|
||||
"Sides": 11, "Branch Sides": 4,
|
||||
},
|
||||
# Strauch statt Kleinbaum. Botanisch ist der Unterschied NICHT die Groesse,
|
||||
# sondern die Architektur: ein Strauch hat mehrere Stiele, die dicht ueber
|
||||
# dem Boden entspringen, und keinen durchgehenden Leittrieb; gemessen wird
|
||||
# er nach Hoehe UND Breite. Vorher: ein durchgehender Stamm ueber die volle
|
||||
# Hoehe, Aeste erst ab 15 %, Wurzelanlauf wie beim Baum - gemessen 1.81 m
|
||||
# hoch bei 0.84 m Breite (B/H 0.46). Das IST ein kleiner Baum.
|
||||
# Jetzt: kurzer Stumpf (Height 0.7), Aeste ab dem Boden (Branch Start 0.0)
|
||||
# und laenger als der Stumpf - damit uebernehmen die Aeste die Rolle der
|
||||
# Stiele. Gemessen: 1.40 m hoch, 2.16 m breit (B/H 1.54), 640 Tris.
|
||||
"busch": {
|
||||
"Height": 1.6, "Trunk Radius": 0.07, "Bend": 0.4, "Taper": 0.8,
|
||||
"Branch Count": 14, "Branch Length": 0.9, "Branch Up": 1.0,
|
||||
"Branch Start": 0.15, "Branch End": 0.95,
|
||||
"Height": 0.7, "Trunk Radius": 0.07, "Bend": 0.4, "Taper": 0.8,
|
||||
"Branch Count": 14, "Branch Length": 1.55, "Branch Up": 0.35,
|
||||
"Branch Start": 0.0, "Branch End": 0.95,
|
||||
"Branch Bend": 0.6, "Branch Droop": 0.0,
|
||||
"Root Flare": 0.2, # Straeucher haben keinen Wurzelanlauf
|
||||
"Sub Count": 1, "Sub Length": 0.35, "Sub Up": 1.6, "Sides": 4,
|
||||
"Detail": 0.3, "Branch Count": 10,
|
||||
"Detail": 0.3,
|
||||
},
|
||||
# --- Varianten ueber die Kronenform (Idee: modular_tree "envelope shapes") ---
|
||||
"tanne": { # kegelfoermig: unten lange Aeste, oben kurz, leicht haengend
|
||||
@@ -943,9 +962,30 @@ def build_group():
|
||||
L(sub_on.outputs[0], _sock(sub_sw, "Switch"))
|
||||
L(_out(mesh_sub, "Geometry"), _sock(sub_sw, "True"))
|
||||
|
||||
# Dieselbe Falle gilt fuer die HAUPTAESTE: "Branch Count" = 0 lieferte
|
||||
# trotzdem einen Ast. Aufgefallen beim Kaktus-Setzling, der laut NPS noch
|
||||
# gar keine Arme haben darf - er behielt einen. Weil die Sub-Aeste auf den
|
||||
# Hauptaesten sitzen, wandern beide Ebenen hinter denselben Schalter.
|
||||
br_join = N("GeometryNodeJoinGeometry"); br_join.location = (4620, 100)
|
||||
L(_out(sub_sw, "Output"), br_join.inputs[0])
|
||||
L(_out(mesh_branch, "Geometry"), br_join.inputs[0])
|
||||
|
||||
br_on = N("FunctionNodeCompare"); br_on.location = (4400, 60)
|
||||
try:
|
||||
br_on.data_type = 'INT'; br_on.operation = 'GREATER_THAN'
|
||||
L(V["Branch Count"], br_on.inputs[2])
|
||||
br_on.inputs[3].default_value = 0
|
||||
except Exception:
|
||||
br_on = N("ShaderNodeMath"); br_on.location = (4400, 60)
|
||||
br_on.operation = 'GREATER_THAN'; br_on.inputs[1].default_value = 0.5
|
||||
L(V["Branch Count"], br_on.inputs[0])
|
||||
br_sw = N("GeometryNodeSwitch"); br_sw.location = (4660, 200)
|
||||
br_sw.input_type = 'GEOMETRY'
|
||||
L(br_on.outputs[0], _sock(br_sw, "Switch"))
|
||||
L(_out(br_join, "Geometry"), _sock(br_sw, "True"))
|
||||
|
||||
join = N("GeometryNodeJoinGeometry"); join.location = (4700, 300)
|
||||
L(_out(sub_sw, "Output"), join.inputs[0])
|
||||
L(_out(mesh_branch, "Geometry"), join.inputs[0])
|
||||
L(_out(br_sw, "Output"), join.inputs[0])
|
||||
L(_out(mesh_trunk, "Geometry"), join.inputs[0])
|
||||
stuv = join # Name beibehalten fuer den nachfolgenden Merge-Zweig
|
||||
|
||||
@@ -988,14 +1028,71 @@ def build_group():
|
||||
# Wachstums-Stufen: Faktoren/Werte fuer t = 0 (Setzling) .. 1 (ausgewachsen).
|
||||
# Gleicher Seed -> dieselbe Baum-"Identitaet", nur juenger. Genau das braucht
|
||||
# ein Wachstums-System, damit Stufe 1 und Stufe 4 wie DERSELBE Baum wirken.
|
||||
GROWTH_HEIGHT = 0.16 # Setzling ist 16 % der Endhoehe
|
||||
|
||||
GROWTH_YOUNG = {
|
||||
"Height": 0.16, # Faktoren (werden mit dem Zielwert multipliziert)
|
||||
"Trunk Radius": 0.30,
|
||||
"Branch Length": 0.30,
|
||||
"Height": GROWTH_HEIGHT, # Faktoren (werden mit dem Zielwert multipliziert)
|
||||
|
||||
# Der Stamm darf NICHT einfach mitskalieren. Vorher stand hier 0.30 bei einem
|
||||
# Hoehenfaktor von 0.16 - dadurch war der Setzling relativ zu seiner Hoehe
|
||||
# 2.4x DICKER als der Altbaum (gemessen: r/H 0.025 gegen 0.0104) und sah
|
||||
# aus wie ein Stummel. Botanisch gilt die elastische Aehnlichkeit nach
|
||||
# McMahon: Hoehe ~ Durchmesser^(2/3), also Durchmesser ~ Hoehe^(3/2).
|
||||
# Bei Hoehenfaktor 0.16 sind das 0.16^1.5 = 0.064. Junge Baeume sind sogar
|
||||
# noch schlanker als die Regel vorhersagt - 0.064 ist die obere Grenze.
|
||||
"Trunk Radius": GROWTH_HEIGHT ** 1.5,
|
||||
|
||||
# Wurzelanlauf und Wurzelbretter entstehen erst mit dem Alter. Blieben sie
|
||||
# auf dem Altbaum-Wert, bekam jeder Setzling einen Trichterfuss - das war
|
||||
# der auffaelligste Grund, warum die kleinste Stufe "komisch" aussah.
|
||||
"Root Flare": 0.15,
|
||||
"Root Lobe Depth": 0.0,
|
||||
|
||||
# Aeste muessen MIT der Hoehe schrumpfen, nicht langsamer. Mit 0.30 gegen
|
||||
# Hoehe 0.16 waren die Aeste am Setzling relativ 1.9x zu lang - bei der
|
||||
# Weide reichten sie dadurch unter den Boden.
|
||||
"Branch Length": GROWTH_HEIGHT,
|
||||
"Branch Count": 0.35,
|
||||
|
||||
# Junge Baeume tragen ihre Aeste TIEFER: das Aufasten (Selbstbeschneidung
|
||||
# der unteren Aeste) passiert erst spaeter. Ohne das sass die Krone beim
|
||||
# Setzling genauso hoch wie beim Altbaum - typisches Merkmal eines bloss
|
||||
# runterskalierten Modells.
|
||||
"Branch Start": 0.35,
|
||||
|
||||
# Haengen und Ueberbiegen kommt mit Laenge und Gewicht. Am Setzling stehen
|
||||
# Aeste und Wedel noch aufrecht. Ohne das reichten Palmwedel (Droop 1.2) und
|
||||
# Kaktusarme (Droop -1.35) beim Setzling GEMESSEN bis zu 0.58 m bzw. 0.18 m
|
||||
# unter den Wurzelansatz - das Asset haette in UE ueber dem Boden geschwebt.
|
||||
"Branch Droop": 0.30,
|
||||
|
||||
"Sub Count": 0.0, # Setzling hat noch keine Sub-Aeste
|
||||
}
|
||||
|
||||
# Nicht jede Pflanze waechst wie ein Laubbaum.
|
||||
#
|
||||
# Palmen sind Einkeimblaettrige und haben KEIN Dickenwachstum: der Stamm baut
|
||||
# erst seinen Durchmesser auf und streckt sich danach in die Hoehe - "once a
|
||||
# palm stem emerges from the apical meristem, its diameter is fixed for life"
|
||||
# (UF/IFAS ENH1212, Palm Morphology and Anatomy). Eine junge Palme ist also ein
|
||||
# kurzer Stumpf in fast voller Dicke, kein duenner Schoessling. Mit dem
|
||||
# Laubbaum-Faktor 0.064 wurde sie zum Draht.
|
||||
#
|
||||
# Kakteen sind Sukkulenten und folgen derselben Logik: der Saeulenkaktus bildet
|
||||
# zuerst seinen Koerper und waechst dann nach oben.
|
||||
GROWTH_OVERRIDES = {
|
||||
# Die Trauerweide haengt erst, wenn die Zweige lang und schwer genug sind -
|
||||
# junge Weiden wachsen aufrecht. Mit dem allgemeinen Setzling-Droop reichten
|
||||
# ihre Zweige GEMESSEN 0.27 m unter den Stammfuss; das Asset haette in UE
|
||||
# ueber dem Boden geschwebt.
|
||||
"weide": {"Branch Droop": 0.0, "Attraction Up": 0.0},
|
||||
"palme": {"Trunk Radius": 0.85, "Branch Length": 0.28},
|
||||
# Saeulenkakteen bilden ihre Arme erst spaet: "branches normally begin to
|
||||
# appear when a saguaro reaches 50 to 70 years of age" - da ist er schon
|
||||
# rund 2 m hoch (NPS, How Saguaros Grow). Der Setzling ist eine reine Saeule.
|
||||
"kaktus": {"Trunk Radius": 0.75, "Branch Length": 0.20, "Branch Count": 0.0},
|
||||
}
|
||||
|
||||
|
||||
def growth_values(preset, t):
|
||||
"""Parameter fuer Wachstums-Fortschritt t (0 = Setzling, 1 = ausgewachsen)."""
|
||||
@@ -1003,14 +1100,23 @@ def growth_values(preset, t):
|
||||
full.update(PRESETS.get(preset, {}))
|
||||
out = dict(full)
|
||||
t = max(0.0, min(1.0, t))
|
||||
for key, young_factor in GROWTH_YOUNG.items():
|
||||
factors = dict(GROWTH_YOUNG)
|
||||
factors.update(GROWTH_OVERRIDES.get(preset, {}))
|
||||
for key, young_factor in factors.items():
|
||||
if key not in full:
|
||||
continue
|
||||
target = full[key]
|
||||
young = target * young_factor
|
||||
v = young + (target - young) * t
|
||||
out[key] = max(1, int(round(v))) if isinstance(target, int) and key != "Sub Count" \
|
||||
else (int(round(v)) if isinstance(target, int) else v)
|
||||
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,
|
||||
# damit aus einer Rundung kein kahler Stock wird. Vorher war die
|
||||
# Ausnahme fest auf "Sub Count" verdrahtet - dadurch konnte der
|
||||
# Kaktus-Setzling seine Arme nicht loswerden.
|
||||
out[key] = int(round(v)) if young_factor == 0.0 else max(1, int(round(v)))
|
||||
else:
|
||||
out[key] = v
|
||||
return out
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user