Tree Generator v1.1.0: Aeste folgen der Elternkurve
Kern aus EcoGame tools/blender_tree_gen.py regeneriert. Ast-Richtung jetzt ueber Tangent/Normal von 'Curve to Points' (Goldener Winkel) statt Weltkoordinaten -> Sub-Aeste wachsen entlang ihres Elternastes statt quer dazu. 'Tip Blunt' neu. Headless getestet: alle 4 Presets, Stufen aufsteigend, Modifier-Apply ok. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -24,6 +24,11 @@ Gruppe. Blaetter/Krone macht man selbst — das Addon liefert die Struktur.
|
|||||||
im Viewport ziehen, der Baum aktualisiert sich sofort.
|
im Viewport ziehen, der Baum aktualisiert sich sofort.
|
||||||
- **Export:** „Modifier anwenden" friert das Ergebnis als normales Mesh ein.
|
- **Export:** „Modifier anwenden" friert das Ergebnis als normales Mesh ein.
|
||||||
|
|
||||||
|
**v1.1.0:** Aeste folgen jetzt der Elternkurve (Tangent/Normal aus *Curve to
|
||||||
|
Points*, Goldener Winkel = Phyllotaxis) statt einer aus Weltkoordinaten
|
||||||
|
gerechneten Richtung — Sub-Aeste standen vorher wie Nadeln quer zum Ast.
|
||||||
|
Neu: `Tip Blunt` (Baum laeuft spitz aus, Kaktus endet stumpf).
|
||||||
|
|
||||||
⚠️ **Bekannte Grenze:** Das `kaktus`-Preset ist noch nicht ueberzeugend — die Arme
|
⚠️ **Bekannte Grenze:** Das `kaktus`-Preset ist noch nicht ueberzeugend — die Arme
|
||||||
spreizen im V, statt nach dem Abgang senkrecht hochzulaufen (die Droop-Naeherung
|
spreizen im V, statt nach dem Abgang senkrecht hochzulaufen (die Droop-Naeherung
|
||||||
kann nur einen gleichmaessigen Bogen, keinen Ellbogen). Als Platzhalter brauchbar.
|
kann nur einen gleichmaessigen Bogen, keinen Ellbogen). Als Platzhalter brauchbar.
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
schema_version = "1.0.0"
|
schema_version = "1.0.0"
|
||||||
|
|
||||||
id = "stylized_tree_generator"
|
id = "stylized_tree_generator"
|
||||||
version = "1.0.0"
|
version = "1.1.0"
|
||||||
name = "Stylized Tree Generator"
|
name = "Stylized Tree Generator"
|
||||||
tagline = "Parametrische Baeume, Palmen, Bueschen mit Wachstums-Stufen"
|
tagline = "Parametrische Baeume, Palmen, Bueschen mit Wachstums-Stufen"
|
||||||
maintainer = "D4rkst3r"
|
maintainer = "D4rkst3r"
|
||||||
|
|||||||
Vendored
+4
-4
@@ -32,7 +32,7 @@
|
|||||||
"id": "stylized_tree_generator",
|
"id": "stylized_tree_generator",
|
||||||
"name": "Stylized Tree Generator",
|
"name": "Stylized Tree Generator",
|
||||||
"tagline": "Parametrische Baeume, Palmen, Bueschen mit Wachstums-Stufen",
|
"tagline": "Parametrische Baeume, Palmen, Bueschen mit Wachstums-Stufen",
|
||||||
"version": "1.0.0",
|
"version": "1.1.0",
|
||||||
"type": "add-on",
|
"type": "add-on",
|
||||||
"maintainer": "D4rkst3r",
|
"maintainer": "D4rkst3r",
|
||||||
"license": [
|
"license": [
|
||||||
@@ -48,9 +48,9 @@
|
|||||||
"Mesh",
|
"Mesh",
|
||||||
"Modeling"
|
"Modeling"
|
||||||
],
|
],
|
||||||
"archive_url": "./stylized_tree_generator-1.0.0.zip",
|
"archive_url": "./stylized_tree_generator-1.1.0.zip",
|
||||||
"archive_size": 9292,
|
"archive_size": 9492,
|
||||||
"archive_hash": "sha256:23693ed0476102b52d76e67d1d711ce6c55925b47c9323a06d535e51a2508c01"
|
"archive_hash": "sha256:e563fd7c056b20c3d20c779713b5891baf7843f5158e4276629b8886f1efbd9f"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
BIN
Binary file not shown.
BIN
Binary file not shown.
+32
-24
@@ -1,7 +1,7 @@
|
|||||||
bl_info = {
|
bl_info = {
|
||||||
"name": "Stylized Tree Generator",
|
"name": "Stylized Tree Generator",
|
||||||
"author": "D4rkst3r",
|
"author": "D4rkst3r",
|
||||||
"version": (1, 0, 0),
|
"version": (1, 1, 0),
|
||||||
"blender": (4, 2, 0),
|
"blender": (4, 2, 0),
|
||||||
"location": "View3D > Sidebar > Tree Gen",
|
"location": "View3D > Sidebar > Tree Gen",
|
||||||
"description": "Parametrischer Baum-/Palmen-/Busch-Generator (Geometry Nodes) mit Wachstums-Stufen",
|
"description": "Parametrischer Baum-/Palmen-/Busch-Generator (Geometry Nodes) mit Wachstums-Stufen",
|
||||||
@@ -44,6 +44,7 @@ DEFAULTS = {
|
|||||||
"Sub Length": 0.55,
|
"Sub Length": 0.55,
|
||||||
"Sub Up": 1.4,
|
"Sub Up": 1.4,
|
||||||
"Sides": 6,
|
"Sides": 6,
|
||||||
|
"Tip Blunt": 0.0, # 0 = laeuft spitz aus (Baum), 0.45 = stumpfes Ende (Kaktus)
|
||||||
"Ribs": 0.0, # senkrechte Kanneluren (0 = glatt); Kaktus ~9
|
"Ribs": 0.0, # senkrechte Kanneluren (0 = glatt); Kaktus ~9
|
||||||
"Rib Depth": 0.0,
|
"Rib Depth": 0.0,
|
||||||
}
|
}
|
||||||
@@ -77,6 +78,7 @@ PRESETS = {
|
|||||||
"Branch Thickness": 0.72, # Arme fast so dick wie der Stamm
|
"Branch Thickness": 0.72, # Arme fast so dick wie der Stamm
|
||||||
"Branch Taper": 0.12, # Arme bleiben dick statt spitz zuzulaufen
|
"Branch Taper": 0.12, # Arme bleiben dick statt spitz zuzulaufen
|
||||||
"Sub Count": 0,
|
"Sub Count": 0,
|
||||||
|
"Tip Blunt": 0.45,
|
||||||
"Sides": 18, "Ribs": 9.0, "Rib Depth": 0.09,
|
"Sides": 18, "Ribs": 9.0, "Rib Depth": 0.09,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -145,6 +147,7 @@ def build_group():
|
|||||||
add_in("Sub Length", 'NodeSocketFloat', DEFAULTS["Sub Length"], 0.05, 5.0)
|
add_in("Sub Length", 'NodeSocketFloat', DEFAULTS["Sub Length"], 0.05, 5.0)
|
||||||
add_in("Sub Up", 'NodeSocketFloat', DEFAULTS["Sub Up"], -3.0, 3.0)
|
add_in("Sub Up", 'NodeSocketFloat', DEFAULTS["Sub Up"], -3.0, 3.0)
|
||||||
add_in("Sides", 'NodeSocketInt', DEFAULTS["Sides"], 3, 32)
|
add_in("Sides", 'NodeSocketInt', DEFAULTS["Sides"], 3, 32)
|
||||||
|
add_in("Tip Blunt", 'NodeSocketFloat', DEFAULTS["Tip Blunt"], 0.0, 0.9)
|
||||||
add_in("Ribs", 'NodeSocketFloat', DEFAULTS["Ribs"], 0.0, 20.0)
|
add_in("Ribs", 'NodeSocketFloat', DEFAULTS["Ribs"], 0.0, 20.0)
|
||||||
add_in("Rib Depth", 'NodeSocketFloat', DEFAULTS["Rib Depth"], 0.0, 0.5)
|
add_in("Rib Depth", 'NodeSocketFloat', DEFAULTS["Rib Depth"], 0.0, 0.5)
|
||||||
|
|
||||||
@@ -222,8 +225,8 @@ def build_group():
|
|||||||
# egal wie fein resampled wird. Mit Mindestradius + Fill Caps endet
|
# egal wie fein resampled wird. Mit Mindestradius + Fill Caps endet
|
||||||
# der Arm stumpf/gerundet statt als Dorn.
|
# der Arm stumpf/gerundet statt als Dorn.
|
||||||
mx = N("ShaderNodeMath"); mx.location = (x + 450, y); mx.operation = 'MAXIMUM'
|
mx = N("ShaderNodeMath"); mx.location = (x + 450, y); mx.operation = 'MAXIMUM'
|
||||||
mx.inputs[1].default_value = 0.45
|
|
||||||
L(rt.outputs[0], mx.inputs[0])
|
L(rt.outputs[0], mx.inputs[0])
|
||||||
|
L(V["Tip Blunt"], mx.inputs[1])
|
||||||
return mx
|
return mx
|
||||||
|
|
||||||
tdome = _dome(spar, -900, 220)
|
tdome = _dome(spar, -900, 220)
|
||||||
@@ -285,19 +288,33 @@ def build_group():
|
|||||||
L(_out(bres, "Curve"), _sock(bsetr, "Curve"))
|
L(_out(bres, "Curve"), _sock(bsetr, "Curve"))
|
||||||
L(bscl.outputs[0], _sock(bsetr, "Radius"))
|
L(bscl.outputs[0], _sock(bsetr, "Radius"))
|
||||||
|
|
||||||
|
# ---------- Ausrichtung relativ zur ELTERNKURVE ----------
|
||||||
|
# Curve to Points liefert Tangent + Normal des Ansatzpunktes gratis mit.
|
||||||
|
# Die Normale um die Tangente zu drehen (Index * Goldener Winkel) verteilt
|
||||||
|
# die Aeste spiralig um den Elternast (Phyllotaxis) UND folgt automatisch
|
||||||
|
# dessen Biegung. Vorher wurde die Richtung aus Weltkoordinaten gerechnet ->
|
||||||
|
# Aeste standen wie angeklebte Nadeln quer zum Ast.
|
||||||
|
def _child_dir(points_node, up_socket, x, y):
|
||||||
|
i = N("GeometryNodeInputIndex"); i.location = (x, y + 220)
|
||||||
|
a = N("ShaderNodeMath"); a.location = (x + 150, y + 220); a.operation = 'MULTIPLY'
|
||||||
|
a.inputs[1].default_value = 2.399963 # Goldener Winkel
|
||||||
|
L(i.outputs[0], a.inputs[0])
|
||||||
|
vr = N("ShaderNodeVectorRotate"); vr.location = (x + 320, y + 120)
|
||||||
|
vr.rotation_type = 'AXIS_ANGLE'
|
||||||
|
L(_out(points_node, "Normal"), vr.inputs["Vector"])
|
||||||
|
L(_out(points_node, "Tangent"), vr.inputs["Axis"])
|
||||||
|
L(a.outputs[0], vr.inputs["Angle"])
|
||||||
|
lean = N("ShaderNodeVectorMath"); lean.location = (x + 320, y - 60)
|
||||||
|
lean.operation = 'SCALE'
|
||||||
|
L(_out(points_node, "Tangent"), lean.inputs[0])
|
||||||
|
L(up_socket, _sock(lean, "Scale"))
|
||||||
|
add = N("ShaderNodeVectorMath"); add.location = (x + 500, y + 40)
|
||||||
|
add.operation = 'ADD'
|
||||||
|
L(vr.outputs[0], add.inputs[0]); L(lean.outputs[0], add.inputs[1])
|
||||||
|
return add
|
||||||
|
|
||||||
# ---------- radiale Ausrichtung je Ast ----------
|
# ---------- radiale Ausrichtung je Ast ----------
|
||||||
idx = N("GeometryNodeInputIndex"); idx.location = (100, 560)
|
dirv = _child_dir(c2p, V["Branch Up"], 100, 560)
|
||||||
frac = N("ShaderNodeMath"); frac.location = (260, 560); frac.operation = 'DIVIDE'
|
|
||||||
L(idx.outputs[0], frac.inputs[0]); L(V["Branch Count"], frac.inputs[1])
|
|
||||||
ang = N("ShaderNodeMath"); ang.location = (420, 560); ang.operation = 'MULTIPLY'
|
|
||||||
ang.inputs[1].default_value = 6.283185
|
|
||||||
L(frac.outputs[0], ang.inputs[0])
|
|
||||||
cs = N("ShaderNodeMath"); cs.location = (580, 620); cs.operation = 'COSINE'
|
|
||||||
sn = N("ShaderNodeMath"); sn.location = (580, 500); sn.operation = 'SINE'
|
|
||||||
L(ang.outputs[0], cs.inputs[0]); L(ang.outputs[0], sn.inputs[0])
|
|
||||||
dirv = N("ShaderNodeCombineXYZ"); dirv.location = (740, 560)
|
|
||||||
L(cs.outputs[0], dirv.inputs["X"]); L(sn.outputs[0], dirv.inputs["Y"])
|
|
||||||
L(V["Branch Up"], dirv.inputs["Z"])
|
|
||||||
|
|
||||||
align = _new_any("FunctionNodeAlignRotationToVector", "FunctionNodeAlignEulerToVector")
|
align = _new_any("FunctionNodeAlignRotationToVector", "FunctionNodeAlignEulerToVector")
|
||||||
if align is not None:
|
if align is not None:
|
||||||
@@ -402,16 +419,7 @@ def build_group():
|
|||||||
L(srad2.outputs[0], _sock(ssetr, "Radius"))
|
L(srad2.outputs[0], _sock(ssetr, "Radius"))
|
||||||
|
|
||||||
# Richtung: radial gestreut (Index) + "Sub Up"
|
# Richtung: radial gestreut (Index) + "Sub Up"
|
||||||
sidx = N("GeometryNodeInputIndex"); sidx.location = (2100, 1060)
|
sdir = _child_dir(sp, V["Sub Up"], 2100, 1060)
|
||||||
sfr = N("ShaderNodeMath"); sfr.location = (2250, 1060); sfr.operation = 'MULTIPLY'
|
|
||||||
sfr.inputs[1].default_value = 2.399963 # Goldener Winkel -> gute Streuung
|
|
||||||
L(sidx.outputs[0], sfr.inputs[0])
|
|
||||||
scs = N("ShaderNodeMath"); scs.location = (2400, 1120); scs.operation = 'COSINE'
|
|
||||||
ssn = N("ShaderNodeMath"); ssn.location = (2400, 1000); ssn.operation = 'SINE'
|
|
||||||
L(sfr.outputs[0], scs.inputs[0]); L(sfr.outputs[0], ssn.inputs[0])
|
|
||||||
sdir = N("ShaderNodeCombineXYZ"); sdir.location = (2550, 1060)
|
|
||||||
L(scs.outputs[0], sdir.inputs["X"]); L(ssn.outputs[0], sdir.inputs["Y"])
|
|
||||||
L(V["Sub Up"], sdir.inputs["Z"])
|
|
||||||
|
|
||||||
salign = _new_any("FunctionNodeAlignRotationToVector", "FunctionNodeAlignEulerToVector")
|
salign = _new_any("FunctionNodeAlignRotationToVector", "FunctionNodeAlignEulerToVector")
|
||||||
if salign is not None:
|
if salign is not None:
|
||||||
|
|||||||
Reference in New Issue
Block a user