Tree Generator v1.22.0: Phototropismus (Attraction Up)
Aeste kruemmen sich zur Spitze hin zur Senkrechten - als echte Rotation um den Astansatz, nicht als Z-Versatz. Die Astlaenge bleibt dabei erhalten (gemessen: +0.08 m Hoehe statt +0.93 m beim alten Droop-Hack). Pro Preset abgestimmt. Idee aus UE 5.8 Procedural Vegetation / Weber-Penn. 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, 21, 0),
|
||||
"version": (1, 22, 0),
|
||||
"blender": (4, 2, 0),
|
||||
"location": "View3D > Sidebar > Tree Gen",
|
||||
"description": "Parametrischer Baum-/Palmen-/Busch-Generator (Geometry Nodes) mit Wachstums-Stufen",
|
||||
@@ -40,6 +40,9 @@ DEFAULTS = {
|
||||
"Branch Bend": 0.75, # Krummheit je Ast/Zweig (0 = gerade Staebe)
|
||||
"Branch Droop": 0.0, # >0 haengt nach unten (Palme), <0 kruemmt nach oben (Kaktus)
|
||||
"Droop Curve": 2.0, # 2 = weicher Bogen, 4+ = scharfer Ellbogen
|
||||
"Attraction Up": 0.45, # Phototropismus in Radiant: Ast dreht sich zur
|
||||
# Spitze hin zur Senkrechten. Echte Rotation um
|
||||
# den Ansatzpunkt -> Astlaenge bleibt erhalten
|
||||
"Trunk Tip": 0.3, # Anteil von Tip Blunt fuer die Stammspitze;
|
||||
# klein = laeuft duenn aus, 1.0 = stumpfe Kuppe
|
||||
"Branch Thickness": 0.17, # Ast-Dicke relativ zum Stamm. Am TreeIt-Baum
|
||||
@@ -96,6 +99,7 @@ PRESETS = {
|
||||
"Branch Start": 0.9, "Branch End": 1.0,
|
||||
"Branch Bend": 0.3,
|
||||
"Branch Droop": 0.5, "Droop Curve": 2.2,
|
||||
"Attraction Up": 0.2, # Wedel schwingen leicht hoch, dann Bogen
|
||||
"Branch Thickness": 0.16,
|
||||
"Branch Taper": 0.45,
|
||||
"Root Flare": 0.5, # leichte Verdickung ...
|
||||
@@ -119,6 +123,7 @@ PRESETS = {
|
||||
"Branch Count": 22, "Branch Length": 1.9, "Branch Up": -0.25,
|
||||
"Branch Start": 0.12, "Branch End": 0.97,
|
||||
"Crown Taper": 0.9, "Crown Bulge": 0.0,
|
||||
"Attraction Up": 0.0, # Tannenaeste haengen, streben nicht hoch
|
||||
"Branch Bend": 0.3, "Branch Thickness": 0.12, "Sub Count": 1,
|
||||
"Sub Length": 0.4, "Root Flare": 0.7,
|
||||
},
|
||||
@@ -127,6 +132,7 @@ PRESETS = {
|
||||
"Branch Count": 10, "Branch Length": 2.2, "Branch Up": 0.9,
|
||||
"Branch Start": 0.3, "Branch End": 0.95,
|
||||
"Crown Taper": -0.15, "Crown Bulge": 0.5,
|
||||
"Attraction Up": 0.5, # Eiche: Aeste schwingen deutlich hoch
|
||||
"Branch Bend": 0.9, "Branch Thickness": 0.24, "Sub Count": 3,
|
||||
"Sub Length": 0.8, "Root Flare": 1.5, "Root Lobe Depth": 0.4,
|
||||
},
|
||||
@@ -143,6 +149,7 @@ PRESETS = {
|
||||
"Branch Count": 12, "Branch Length": 2.0, "Branch Up": 0.5,
|
||||
"Branch Start": 0.35, "Branch End": 0.95,
|
||||
"Crown Taper": -0.2, "Crown Bulge": 0.4,
|
||||
"Attraction Up": -0.25, # Weide: Zweige haengen
|
||||
"Branch Droop": 1.2, "Branch Bend": 0.7, "Branch Thickness": 0.16,
|
||||
"Sub Count": 3, "Sub Length": 0.9, "Root Flare": 1.2,
|
||||
},
|
||||
@@ -151,6 +158,7 @@ PRESETS = {
|
||||
"Branch Count": 6, "Branch Length": 1.5, "Branch Up": 0.9,
|
||||
"Branch Start": 0.3, "Branch End": 0.9,
|
||||
"Crown Taper": 0.3, "Crown Bulge": 0.0,
|
||||
"Attraction Up": 0.35,
|
||||
"Branch Bend": 1.4, "Branch Thickness": 0.18, "Sub Count": 1,
|
||||
"Sub Length": 0.5, "Root Flare": 1.4, "Tip Blunt": 0.08,
|
||||
},
|
||||
@@ -161,6 +169,7 @@ PRESETS = {
|
||||
"Branch Count": 2, "Branch Length": 1.9, "Branch Up": 0.12,
|
||||
"Branch Start": 0.30, "Branch End": 0.46,
|
||||
"Branch Bend": 0.0,
|
||||
"Attraction Up": 0.0, # Kaktus nutzt den Droop-Ellbogen
|
||||
"Branch Droop": -1.35, # negativ = Arme kruemmen nach OBEN (Saguaro);
|
||||
# zu gross und die Arme ueberragen den Stamm
|
||||
"Droop Curve": 4.5, # spaeter, dafuer scharfer Ellbogen
|
||||
@@ -244,6 +253,7 @@ def build_group():
|
||||
# (= Kaktus-Arme statt Palmwedel).
|
||||
add_in("Branch Droop", 'NodeSocketFloat', DEFAULTS["Branch Droop"], -3.0, 3.0)
|
||||
add_in("Droop Curve", 'NodeSocketFloat', DEFAULTS["Droop Curve"], 1.0, 8.0)
|
||||
add_in("Attraction Up", 'NodeSocketFloat', DEFAULTS["Attraction Up"], -1.5, 1.5)
|
||||
add_in("Trunk Tip", 'NodeSocketFloat', DEFAULTS["Trunk Tip"], 0.05, 1.0)
|
||||
add_in("Branch Thickness", 'NodeSocketFloat', DEFAULTS["Branch Thickness"], 0.05, 1.0)
|
||||
add_in("Branch Taper", 'NodeSocketFloat', DEFAULTS["Branch Taper"], 0.0, 1.0)
|
||||
@@ -546,8 +556,19 @@ def build_group():
|
||||
pfin = N("ShaderNodeMath"); pfin.location = (2100, 120); pfin.operation = 'MULTIPLY'
|
||||
L(pscale.outputs[0], pfin.inputs[0]); L(ccl.outputs[0], pfin.inputs[1])
|
||||
|
||||
# Ansatzpunkt als Attribut mitgeben. Punkt-Attribute ueberleben
|
||||
# "Instance on Points" + "Realize" (geprueft) - dadurch kennt spaeter JEDER
|
||||
# Punkt eines Astes seinen eigenen Ursprung. Das ist die Voraussetzung fuer
|
||||
# echtes Biegen (Rotation) statt blossem Verschieben.
|
||||
apos = N("GeometryNodeInputPosition"); apos.location = (900, 480)
|
||||
anch = N("GeometryNodeStoreNamedAttribute"); anch.location = (1050, 320)
|
||||
anch.domain = 'POINT'; anch.data_type = 'FLOAT_VECTOR'
|
||||
L(_out(c2p, "Points"), _sock(anch, "Geometry"))
|
||||
_sock(anch, "Name").default_value = "anchor"
|
||||
L(apos.outputs[0], _sock(anch, "Value"))
|
||||
|
||||
inst = N("GeometryNodeInstanceOnPoints"); inst.location = (1400, 320)
|
||||
L(_out(c2p, "Points"), _sock(inst, "Points"))
|
||||
L(_out(anch, "Geometry"), _sock(inst, "Points"))
|
||||
L(_out(bsetr, "Curve"), _sock(inst, "Instance"))
|
||||
L(pfin.outputs[0], _sock(inst, "Scale"))
|
||||
if align is not None:
|
||||
@@ -561,6 +582,42 @@ def build_group():
|
||||
# des jeweiligen Astes. Dieselbe Noise liefert damit pro Ast einen anderen
|
||||
# Wert — vor dem Realize haetten alle Instanzen identische lokale Coords
|
||||
# (und wuerden exakt gleich gebogen).
|
||||
# ---------- Phototropismus (Lichtsuche) ----------
|
||||
# Idee aus UE 5.8 Procedural Vegetation ("light detection") bzw. Weber-Penn
|
||||
# ("attraction up"): Aeste kruemmen sich zur Spitze hin zur Senkrechten.
|
||||
# Umgesetzt als echte ROTATION des Punktes um seinen Astansatz - im
|
||||
# Gegensatz zum Z-Versatz (Droop) bleibt die Astlaenge dabei erhalten.
|
||||
ph_spar = N("GeometryNodeSplineParameter"); ph_spar.location = (1200, 780)
|
||||
ph_pos = N("GeometryNodeInputPosition"); ph_pos.location = (1200, 640)
|
||||
ph_anch = N("GeometryNodeInputNamedAttribute"); ph_anch.location = (1200, 500)
|
||||
ph_anch.data_type = 'FLOAT_VECTOR'
|
||||
_sock(ph_anch, "Name").default_value = "anchor"
|
||||
ph_rel = N("ShaderNodeVectorMath"); ph_rel.location = (1400, 640)
|
||||
ph_rel.operation = 'SUBTRACT'
|
||||
L(ph_pos.outputs[0], ph_rel.inputs[0])
|
||||
L(_out(ph_anch, "Attribute"), ph_rel.inputs[1])
|
||||
ph_up = N("ShaderNodeCombineXYZ"); ph_up.location = (1400, 500)
|
||||
ph_up.inputs["Z"].default_value = 1.0
|
||||
ph_ax = N("ShaderNodeVectorMath"); ph_ax.location = (1550, 570)
|
||||
ph_ax.operation = 'CROSS_PRODUCT'
|
||||
L(ph_rel.outputs[0], ph_ax.inputs[0]); L(ph_up.outputs[0], ph_ax.inputs[1])
|
||||
ph_ang = N("ShaderNodeMath"); ph_ang.location = (1400, 780)
|
||||
ph_ang.operation = 'MULTIPLY'
|
||||
L(V["Attraction Up"], ph_ang.inputs[0])
|
||||
L(ph_spar.outputs["Factor"], ph_ang.inputs[1])
|
||||
ph_rot = N("ShaderNodeVectorRotate"); ph_rot.location = (1750, 640)
|
||||
ph_rot.rotation_type = 'AXIS_ANGLE'
|
||||
L(ph_rel.outputs[0], ph_rot.inputs["Vector"])
|
||||
L(ph_ax.outputs[0], ph_rot.inputs["Axis"])
|
||||
L(ph_ang.outputs[0], ph_rot.inputs["Angle"])
|
||||
ph_new = N("ShaderNodeVectorMath"); ph_new.location = (1900, 640)
|
||||
ph_new.operation = 'ADD'
|
||||
L(_out(ph_anch, "Attribute"), ph_new.inputs[0])
|
||||
L(ph_rot.outputs[0], ph_new.inputs[1])
|
||||
ph_set = N("GeometryNodeSetPosition"); ph_set.location = (2050, 780)
|
||||
L(_out(real, "Geometry"), _sock(ph_set, "Geometry"))
|
||||
L(ph_new.outputs[0], _sock(ph_set, "Position"))
|
||||
|
||||
rspar = N("GeometryNodeSplineParameter"); rspar.location = (1200, 60)
|
||||
rpos = N("GeometryNodeInputPosition"); rpos.location = (1200, -100)
|
||||
rnoise = N("ShaderNodeTexNoise"); rnoise.location = (1350, -140)
|
||||
@@ -599,8 +656,8 @@ def build_group():
|
||||
L(rscale.outputs[0], roffs.inputs[0])
|
||||
L(dvec.outputs[0], roffs.inputs[1])
|
||||
|
||||
rsetp = N("GeometryNodeSetPosition"); rsetp.location = (1950, 320)
|
||||
L(_out(real, "Geometry"), _sock(rsetp, "Geometry"))
|
||||
rsetp = N("GeometryNodeSetPosition"); rsetp.location = (2200, 320)
|
||||
L(_out(ph_set, "Geometry"), _sock(rsetp, "Geometry"))
|
||||
L(roffs.outputs[0], _sock(rsetp, "Offset"))
|
||||
|
||||
# ---------- Sub-Aeste (zweite Verzweigungsebene) ----------
|
||||
|
||||
Reference in New Issue
Block a user