Tree Generator v1.7.0: Tri-Budget, Detail-Regler, Wurzelanlauf

- 'Detail' skaliert alle Resample-Counts -> zentrale Budget-Stellschraube
- Defaults treffen jetzt das Projekt-Budget (baum 1368, busch 532 Tris)
- 'Root Flare': Wurzelanlauf am Stammfuss, kostet keine Tris

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-29 11:13:43 +02:00
co-authored by Claude Opus 4.8
parent 91e30ca05a
commit 57b4c12725
5 changed files with 48 additions and 14 deletions
+43 -9
View File
@@ -1,7 +1,7 @@
bl_info = {
"name": "Stylized Tree Generator",
"author": "D4rkst3r",
"version": (1, 6, 0),
"version": (1, 7, 0),
"blender": (4, 2, 0),
"location": "View3D > Sidebar > Tree Gen",
"description": "Parametrischer Baum-/Palmen-/Busch-Generator (Geometry Nodes) mit Wachstums-Stufen",
@@ -40,12 +40,14 @@ DEFAULTS = {
"Branch Droop": 0.0, # >0 haengt nach unten (Palme), <0 kruemmt nach oben (Kaktus)
"Branch Thickness": 0.32, # Ast-Dicke relativ zum Stamm
"Branch Taper": 0.55, # schwach verjuengen -> Roehre statt Kegel/Dorn
"Sub Count": 3, # Sub-Aeste je Hauptast (0 = aus)
"Sub Count": 2, # Sub-Aeste je Hauptast (0 = aus)
"Sub Length": 0.55,
"Sub Up": 1.4,
"Sides": 6,
"Sides": 5,
"UV Scale": 1.0, # Rinden-Dichte; UVs sind world-space (m)
"Tip Blunt": 0.18, # >0 verhindert Nadelspitzen (0 = spitz, 0.45 = Kaktus)
"Root Flare": 1.1, # Wurzelanlauf: Stamm verbreitert sich am Fuss
"Detail": 0.55, # skaliert alle Resample-Counts = Tri-Budget-Regler
"Branch Collar": 0.4, # Ansatz-Verdickung: Uebergang Ast->Stamm (0 = aus)
"Merge": 0.0, # 1 = Aeste per Voxel-Remesh mit dem Stamm verschmelzen
"Merge Voxel": 0.03, # Voxelgroesse (m): klein = feiner, aber teuer
@@ -69,7 +71,8 @@ PRESETS = {
"Branch Count": 14, "Branch Length": 0.9, "Branch Up": 1.0,
"Branch Start": 0.15, "Branch End": 0.95,
"Branch Bend": 0.6, "Branch Droop": 0.0,
"Sub Count": 2, "Sub Length": 0.35, "Sub Up": 1.6, "Sides": 5,
"Sub Count": 1, "Sub Length": 0.35, "Sub Up": 1.6, "Sides": 4,
"Detail": 0.3, "Branch Count": 10,
},
# Kaktus: dicker, kaum verjuengter Stamm, wenige Arme, die per NEGATIVEM
# Droop nach oben kruemmen (Saguaro-Silhouette). Keine Sub-Aeste.
@@ -112,6 +115,18 @@ def build_group():
N, L = ng.nodes.new, ng.links.new
iface = ng.interface
def _count(base, x, y):
"""Resample-Count = base * Detail, mindestens 3. Der zentrale
Tri-Budget-Regler: die Segmentzahl entlang der Kurven dominiert die
Dreiecke (Tris ~ Segmente * Sides * 2)."""
mul = N("ShaderNodeMath"); mul.location = (x, y); mul.operation = 'MULTIPLY'
mul.inputs[0].default_value = float(base)
L(V["Detail"], mul.inputs[1])
mx = N("ShaderNodeMath"); mx.location = (x + 150, y); mx.operation = 'MAXIMUM'
mx.inputs[1].default_value = 3.0
L(mul.outputs[0], mx.inputs[0])
return mx
def _new_any(*bl_idnames):
"""Falle 2: erster Node-Typ, den diese Blender-Version kennt."""
for bid in bl_idnames:
@@ -153,6 +168,8 @@ def build_group():
add_in("Sides", 'NodeSocketInt', DEFAULTS["Sides"], 3, 32)
add_in("Tip Blunt", 'NodeSocketFloat', DEFAULTS["Tip Blunt"], 0.0, 0.9)
add_in("UV Scale", 'NodeSocketFloat', DEFAULTS["UV Scale"], 0.01, 20.0)
add_in("Detail", 'NodeSocketFloat', DEFAULTS["Detail"], 0.2, 2.0)
add_in("Root Flare", 'NodeSocketFloat', DEFAULTS["Root Flare"], 0.0, 4.0)
add_in("Branch Collar", 'NodeSocketFloat', DEFAULTS["Branch Collar"], 0.0, 3.0)
add_in("Merge", 'NodeSocketFloat', DEFAULTS["Merge"], 0.0, 1.0)
add_in("Merge Voxel", 'NodeSocketFloat', DEFAULTS["Merge Voxel"], 0.005, 0.2)
@@ -170,7 +187,7 @@ def build_group():
L(top.outputs[0], _sock(line, "End"))
res = N("GeometryNodeResampleCurve"); res.location = (-950, 200)
_sock(res, "Count").default_value = 24 # genug Punkte, damit die Kuppel rund wird
L(_count(24, -1150, 380).outputs[0], _sock(res, "Count"))
L(_out(line, "Curve"), _sock(res, "Curve"))
spar = N("GeometryNodeSplineParameter"); spar.location = (-950, -60)
@@ -270,8 +287,25 @@ def build_group():
L(inv.outputs[0], tmulr.inputs[0]); L(tdome.outputs[0], tmulr.inputs[1])
tmaxr = N("ShaderNodeMath"); tmaxr.location = (-110, 60); tmaxr.operation = 'MAXIMUM'
L(tmulr.outputs[0], tmaxr.inputs[0]); L(ttip.outputs[0], tmaxr.inputs[1])
trad = N("ShaderNodeMath"); trad.location = (40, 60); trad.operation = 'MULTIPLY'
L(V["Trunk Radius"], trad.inputs[0]); L(tmaxr.outputs[0], trad.inputs[1])
# Wurzelanlauf: der Stamm verbreitert sich am Fuss. Hoher Exponent -> nur
# das unterste Stueck ist betroffen. Kostet keine Tris, nur Radius.
rfi = N("ShaderNodeMath"); rfi.location = (-420, -220); rfi.operation = 'SUBTRACT'
rfi.inputs[0].default_value = 1.0
L(spar.outputs["Factor"], rfi.inputs[1])
rfp = N("ShaderNodeMath"); rfp.location = (-270, -220); rfp.operation = 'POWER'
rfp.inputs[1].default_value = 9.0
L(rfi.outputs[0], rfp.inputs[0])
rfm = N("ShaderNodeMath"); rfm.location = (-120, -220); rfm.operation = 'MULTIPLY'
L(V["Root Flare"], rfm.inputs[0]); L(rfp.outputs[0], rfm.inputs[1])
rfa = N("ShaderNodeMath"); rfa.location = (30, -220); rfa.operation = 'ADD'
rfa.inputs[0].default_value = 1.0
L(rfm.outputs[0], rfa.inputs[1])
tflare = N("ShaderNodeMath"); tflare.location = (30, -60); tflare.operation = 'MULTIPLY'
L(tmaxr.outputs[0], tflare.inputs[0]); L(rfa.outputs[0], tflare.inputs[1])
trad = N("ShaderNodeMath"); trad.location = (190, 60); trad.operation = 'MULTIPLY'
L(V["Trunk Radius"], trad.inputs[0]); L(tflare.outputs[0], trad.inputs[1])
setrad = N("GeometryNodeSetCurveRadius"); setrad.location = (-80, 200)
L(_out(setpos, "Geometry"), _sock(setrad, "Curve"))
@@ -301,7 +335,7 @@ def build_group():
L(V["Branch Length"], btop.inputs["Z"])
L(btop.outputs[0], _sock(bline, "End"))
bres = N("GeometryNodeResampleCurve"); bres.location = (280, 40)
_sock(bres, "Count").default_value = 14 # zu wenig -> Kuppel wird zum Kegel
L(_count(14, 100, 200).outputs[0], _sock(bres, "Count"))
L(_out(bline, "Curve"), _sock(bres, "Curve"))
# Ast-Verjuengung: 1 - BranchTaper*factor. Bei Kakteen klein halten,
@@ -453,7 +487,7 @@ def build_group():
L(V["Sub Length"], stop.inputs["Z"])
L(stop.outputs[0], _sock(sline, "End"))
sres = N("GeometryNodeResampleCurve"); sres.location = (2250, 760)
_sock(sres, "Count").default_value = 9 # genug Punkte, damit die Biegung sichtbar wird
L(_count(9, 2100, 700).outputs[0], _sock(sres, "Count"))
L(_out(sline, "Curve"), _sock(sres, "Curve"))
# Sub-Aeste: gleiche Verjuengungs-Logik wie Hauptaeste (Branch Taper + Kuppel).