Tree Generator v1.6.0: Ansatz-Verdickung (Collar), Stammspitze, optionaler Merge
- 'Branch Collar': Aeste werden an der Basis leicht breiter -> gewachsener Uebergang zum Stamm, ohne einen einzigen zusaetzlichen Vert - Stammspitze laeuft duenn aus statt mit flachem Deckel zu enden - 'Merge'/'Merge Voxel' (Voxel-Remesh) optional, Default AUS: verschmilzt Aeste organisch, kostet aber die UVs und bis zu 14x Verts (gemessen), duenne Zweige zerfallen -> fuer Game-Assets nicht empfohlen Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
schema_version = "1.0.0"
|
||||
|
||||
id = "stylized_tree_generator"
|
||||
version = "1.5.0"
|
||||
version = "1.6.0"
|
||||
name = "Stylized Tree Generator"
|
||||
tagline = "Parametrische Baeume, Palmen, Bueschen mit Wachstums-Stufen"
|
||||
maintainer = "D4rkst3r"
|
||||
|
||||
Vendored
+4
-4
@@ -32,7 +32,7 @@
|
||||
"id": "stylized_tree_generator",
|
||||
"name": "Stylized Tree Generator",
|
||||
"tagline": "Parametrische Baeume, Palmen, Bueschen mit Wachstums-Stufen",
|
||||
"version": "1.5.0",
|
||||
"version": "1.6.0",
|
||||
"type": "add-on",
|
||||
"maintainer": "D4rkst3r",
|
||||
"license": [
|
||||
@@ -48,9 +48,9 @@
|
||||
"Mesh",
|
||||
"Modeling"
|
||||
],
|
||||
"archive_url": "./stylized_tree_generator-1.5.0.zip",
|
||||
"archive_size": 11772,
|
||||
"archive_hash": "sha256:fe45391647074729afaac194801ab55c0d004c38f36b94737fdd6715e9c9501c"
|
||||
"archive_url": "./stylized_tree_generator-1.6.0.zip",
|
||||
"archive_size": 12916,
|
||||
"archive_hash": "sha256:48d7072d65d44c944fe7dd160147f37f496132d6c2583c5251f0d6aba499476b"
|
||||
}
|
||||
]
|
||||
}
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -1,7 +1,7 @@
|
||||
bl_info = {
|
||||
"name": "Stylized Tree Generator",
|
||||
"author": "D4rkst3r",
|
||||
"version": (1, 5, 0),
|
||||
"version": (1, 6, 0),
|
||||
"blender": (4, 2, 0),
|
||||
"location": "View3D > Sidebar > Tree Gen",
|
||||
"description": "Parametrischer Baum-/Palmen-/Busch-Generator (Geometry Nodes) mit Wachstums-Stufen",
|
||||
@@ -46,6 +46,9 @@ DEFAULTS = {
|
||||
"Sides": 6,
|
||||
"UV Scale": 1.0, # Rinden-Dichte; UVs sind world-space (m)
|
||||
"Tip Blunt": 0.18, # >0 verhindert Nadelspitzen (0 = spitz, 0.45 = Kaktus)
|
||||
"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
|
||||
"Ribs": 0.0, # senkrechte Kanneluren (0 = glatt); Kaktus ~9
|
||||
"Rib Depth": 0.0,
|
||||
}
|
||||
@@ -150,6 +153,9 @@ 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("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)
|
||||
add_in("Ribs", 'NodeSocketFloat', DEFAULTS["Ribs"], 0.0, 20.0)
|
||||
add_in("Rib Depth", 'NodeSocketFloat', DEFAULTS["Rib Depth"], 0.0, 0.5)
|
||||
|
||||
@@ -226,17 +232,46 @@ def build_group():
|
||||
# multiplizieren. Wirkt der Mindestwert nur auf die Kuppel, bleibt der bereits
|
||||
# verjuengte Radius trotzdem winzig — gemessen: Sub-Ast-Spitze 0.8 mm bei
|
||||
# 31 mm Basis, also weiterhin eine Nadel.
|
||||
def _shape(inv_taper_socket, dome_node, base_socket, x, y):
|
||||
def _shape(inv_taper_socket, dome_node, base_socket, x, y,
|
||||
spar_node=None, collar=False):
|
||||
mul = N("ShaderNodeMath"); mul.location = (x, y); mul.operation = 'MULTIPLY'
|
||||
L(inv_taper_socket, mul.inputs[0]); L(dome_node.outputs[0], mul.inputs[1])
|
||||
mx = N("ShaderNodeMath"); mx.location = (x + 150, y); mx.operation = 'MAXIMUM'
|
||||
L(mul.outputs[0], mx.inputs[0]); L(V["Tip Blunt"], mx.inputs[1])
|
||||
r = N("ShaderNodeMath"); r.location = (x + 300, y); r.operation = 'MULTIPLY'
|
||||
L(base_socket, r.inputs[0]); L(mx.outputs[0], r.inputs[1])
|
||||
src = mx
|
||||
if collar and spar_node is not None:
|
||||
# Ansatz-Verdickung ("Collar"): der Ast wird an seiner Basis breiter
|
||||
# und laeuft schnell aus -> der Uebergang zum Stamm sieht gewachsen
|
||||
# aus statt wie eine durchgesteckte Roehre. Kostet KEINE Verts.
|
||||
ci = N("ShaderNodeMath"); ci.location = (x, y - 160); ci.operation = 'SUBTRACT'
|
||||
ci.inputs[0].default_value = 1.0
|
||||
L(spar_node.outputs["Factor"], ci.inputs[1])
|
||||
cp = N("ShaderNodeMath"); cp.location = (x + 150, y - 160); cp.operation = 'POWER'
|
||||
cp.inputs[1].default_value = 6.0 # faellt schnell ab -> kein Trichter
|
||||
L(ci.outputs[0], cp.inputs[0])
|
||||
cm = N("ShaderNodeMath"); cm.location = (x + 300, y - 160); cm.operation = 'MULTIPLY'
|
||||
L(V["Branch Collar"], cm.inputs[0]); L(cp.outputs[0], cm.inputs[1])
|
||||
ca = N("ShaderNodeMath"); ca.location = (x + 450, y - 160); ca.operation = 'ADD'
|
||||
ca.inputs[0].default_value = 1.0
|
||||
L(cm.outputs[0], ca.inputs[1])
|
||||
cf = N("ShaderNodeMath"); cf.location = (x + 450, y - 60); cf.operation = 'MULTIPLY'
|
||||
L(mx.outputs[0], cf.inputs[0]); L(ca.outputs[0], cf.inputs[1])
|
||||
src = cf
|
||||
r = N("ShaderNodeMath"); r.location = (x + 620, y); r.operation = 'MULTIPLY'
|
||||
L(base_socket, r.inputs[0]); L(src.outputs[0], r.inputs[1])
|
||||
return r
|
||||
|
||||
tdome = _dome(spar, -900, 220)
|
||||
trad = _shape(inv.outputs[0], tdome, V["Trunk Radius"], -260, 60)
|
||||
# Stammspitze duenner auslaufen lassen als die Aeste: sonst endet der Stamm
|
||||
# mit einem sichtbaren flachen Deckel. Ein eigener, kleinerer Mindestwert.
|
||||
ttip = N("ShaderNodeMath"); ttip.location = (-420, -80); ttip.operation = 'MULTIPLY'
|
||||
L(V["Tip Blunt"], ttip.inputs[0]); ttip.inputs[1].default_value = 0.3
|
||||
tmulr = N("ShaderNodeMath"); tmulr.location = (-260, 60); tmulr.operation = 'MULTIPLY'
|
||||
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])
|
||||
|
||||
setrad = N("GeometryNodeSetCurveRadius"); setrad.location = (-80, 200)
|
||||
L(_out(setpos, "Geometry"), _sock(setrad, "Curve"))
|
||||
@@ -283,7 +318,8 @@ def build_group():
|
||||
bbase = N("ShaderNodeMath"); bbase.location = (600, -140); bbase.operation = 'MULTIPLY'
|
||||
L(V["Trunk Radius"], bbase.inputs[0]); L(V["Branch Thickness"], bbase.inputs[1])
|
||||
bdome = _dome(bspar, 600, -600)
|
||||
bscl = _shape(binv.outputs[0], bdome, bbase.outputs[0], 780, -140)
|
||||
bscl = _shape(binv.outputs[0], bdome, bbase.outputs[0], 780, -140,
|
||||
spar_node=bspar, collar=True)
|
||||
|
||||
bsetr = N("GeometryNodeSetCurveRadius"); bsetr.location = (600, 40)
|
||||
L(_out(bres, "Curve"), _sock(bsetr, "Curve"))
|
||||
@@ -436,7 +472,8 @@ def build_group():
|
||||
L(V["Trunk Radius"], sbase.inputs[0])
|
||||
sbase.inputs[1].default_value = 0.12 # Zweige sind DUENN ...
|
||||
sdome = _dome(sspar, 2550, 1200)
|
||||
srad2 = _shape(sinv.outputs[0], sdome, sbase.outputs[0], 2750, 900)
|
||||
srad2 = _shape(sinv.outputs[0], sdome, sbase.outputs[0], 2750, 900,
|
||||
spar_node=sspar, collar=True)
|
||||
ssetr = N("GeometryNodeSetCurveRadius"); ssetr.location = (2550, 760)
|
||||
L(_out(sres, "Curve"), _sock(ssetr, "Curve"))
|
||||
L(srad2.outputs[0], _sock(ssetr, "Radius"))
|
||||
@@ -605,8 +642,38 @@ def build_group():
|
||||
_sock(stuv, "Name").default_value = "UVMap"
|
||||
L(uvvec.outputs[0], _sock(stuv, "Value"))
|
||||
|
||||
shade = N("GeometryNodeSetShadeSmooth"); shade.location = (1650, 200)
|
||||
L(_out(stuv, "Geometry"), _sock(shade, "Geometry"))
|
||||
# ---------- optional: Aeste mit dem Stamm VERSCHMELZEN ----------
|
||||
# Voxel-Remesh (Mesh to Volume -> Volume to Mesh) macht aus den sich
|
||||
# durchdringenden Roehren EINE organische Oberflaeche mit weichen Uebergaengen.
|
||||
# ACHTUNG, ehrlicher Preis: der Remesh erzeugt komplett neue Topologie ->
|
||||
# die UVs sind danach WEG und der Vert-Count steigt deutlich. Deshalb als
|
||||
# Schalter (Merge = 0 laesst den schnellen, UV-tauglichen Pfad unberuehrt).
|
||||
m2v = N("GeometryNodeMeshToVolume"); m2v.location = (1650, -200)
|
||||
L(_out(stuv, "Geometry"), _sock(m2v, "Mesh"))
|
||||
try:
|
||||
_sock(m2v, "Resolution Mode").default_value = 'Size'
|
||||
except Exception:
|
||||
pass
|
||||
L(V["Merge Voxel"], _sock(m2v, "Voxel Size"))
|
||||
v2m = N("GeometryNodeVolumeToMesh"); v2m.location = (1820, -200)
|
||||
L(_out(m2v, "Volume", "Geometry"), _sock(v2m, "Volume"))
|
||||
try:
|
||||
_sock(v2m, "Resolution Mode").default_value = 'Size'
|
||||
except Exception:
|
||||
pass
|
||||
L(V["Merge Voxel"], _sock(v2m, "Voxel Size"))
|
||||
|
||||
pick = N("GeometryNodeSwitch"); pick.location = (2000, 100)
|
||||
pick.input_type = 'GEOMETRY'
|
||||
mgt = N("ShaderNodeMath"); mgt.location = (1820, 60); mgt.operation = 'GREATER_THAN'
|
||||
mgt.inputs[1].default_value = 0.5
|
||||
L(V["Merge"], mgt.inputs[0])
|
||||
L(mgt.outputs[0], _sock(pick, "Switch"))
|
||||
L(_out(stuv, "Geometry"), _sock(pick, "False"))
|
||||
L(_out(v2m, "Mesh", "Geometry"), _sock(pick, "True"))
|
||||
|
||||
shade = N("GeometryNodeSetShadeSmooth"); shade.location = (2200, 200)
|
||||
L(_out(pick, "Output"), _sock(shade, "Geometry"))
|
||||
L(_out(shade, "Geometry"), gout.inputs[0])
|
||||
return ng
|
||||
|
||||
|
||||
Reference in New Issue
Block a user