diff --git a/README.md b/README.md index 2708624..d76ccd6 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,12 @@ Parametrischer **Geaest**-Generator (Stamm + Aeste + Sub-Aeste) als Geometry-Nodes- Gruppe. Blaetter/Krone macht man selbst — das Addon liefert die Struktur. -- **Presets:** `baum`, `palme`, `busch`, `kaktus` +- **Presets:** `baum`, `palme`, `busch`, `tanne`, `eiche`, `birke`, `weide`, + `toter_baum`, `kaktus` — die Silhouette steuert die **Kronenform** + (`Crown Taper` / `Crown Bulge`, Idee aus modular_tree / Weber-Penn). +- **Tri-Budget:** alle Presets liegen unter dem Projektlimit (Baum ≤2500, + Busch ≤800). Stellschrauben: `Detail` (Segmente) und `Sides` / `Branch Sides` / + `Sub Sides` (Profilaufloesung je Ebene — Stamm rund, Zweige nur Dreiecke). - **Wachstums-Stufen:** Haken setzen + Anzahl waehlen → Setzling bis ausgewachsen, **gleicher Seed = dieselbe Baum-Identitaet** (fuer Wachstums-Systeme im Spiel). - **Live bearbeiten:** Nach dem Erzeugen liegen alle Regler am Modifier `GN_Tree` — diff --git a/blender_manifest_tree.toml b/blender_manifest_tree.toml index b8953a3..b0c7ffd 100644 --- a/blender_manifest_tree.toml +++ b/blender_manifest_tree.toml @@ -1,7 +1,7 @@ schema_version = "1.0.0" id = "stylized_tree_generator" -version = "1.9.0" +version = "1.10.0" name = "Stylized Tree Generator" tagline = "Parametrische Baeume, Palmen, Bueschen mit Wachstums-Stufen" maintainer = "D4rkst3r" diff --git a/dist/index.json b/dist/index.json index 3660413..fe2b845 100644 --- a/dist/index.json +++ b/dist/index.json @@ -32,7 +32,7 @@ "id": "stylized_tree_generator", "name": "Stylized Tree Generator", "tagline": "Parametrische Baeume, Palmen, Bueschen mit Wachstums-Stufen", - "version": "1.9.0", + "version": "1.10.0", "type": "add-on", "maintainer": "D4rkst3r", "license": [ @@ -48,9 +48,9 @@ "Mesh", "Modeling" ], - "archive_url": "./stylized_tree_generator-1.9.0.zip", - "archive_size": 14478, - "archive_hash": "sha256:07c964096cd907dab9e22e1bc936621ed25476e188ebaa6176a18e98c5f76a4c" + "archive_url": "./stylized_tree_generator-1.10.0.zip", + "archive_size": 15650, + "archive_hash": "sha256:995b37759449eab7f767bc87dcd7917bdac4382c3a2a8d37e1e0c9dc9d11eb30" } ] } \ No newline at end of file diff --git a/dist/stylized_tree_generator-1.10.0.zip b/dist/stylized_tree_generator-1.10.0.zip new file mode 100644 index 0000000..a9a1bd9 Binary files /dev/null and b/dist/stylized_tree_generator-1.10.0.zip differ diff --git a/dist/stylized_tree_generator-1.9.0.zip b/dist/stylized_tree_generator-1.9.0.zip deleted file mode 100644 index b8a2023..0000000 Binary files a/dist/stylized_tree_generator-1.9.0.zip and /dev/null differ diff --git a/stylized_tree_generator.py b/stylized_tree_generator.py index a3ca791..fc88c79 100644 --- a/stylized_tree_generator.py +++ b/stylized_tree_generator.py @@ -1,7 +1,7 @@ bl_info = { "name": "Stylized Tree Generator", "author": "D4rkst3r", - "version": (1, 9, 0), + "version": (1, 10, 0), "blender": (4, 2, 0), "location": "View3D > Sidebar > Tree Gen", "description": "Parametrischer Baum-/Palmen-/Busch-Generator (Geometry Nodes) mit Wachstums-Stufen", @@ -49,6 +49,8 @@ DEFAULTS = { "Sub Sides": 3, # Zweige nur Dreiecke - sieht man nicht "UV Scale": 1.0, # Rinden-Dichte; UVs sind world-space (m) "Tip Blunt": 0.18, # >0 verhindert Nadelspitzen (0 = spitz, 0.45 = Kaktus) + "Crown Taper": 0.35, # >0 kegelfoermig, 0 saeulig, <0 besenfoermig + "Crown Bulge": 0.0, # >0 = Bauch in der Mitte (runde Krone) "Root Flare": 1.1, # Wurzelanlauf: Stamm verbreitert sich am Fuss "Root Lobes": 6.0, # Wurzelbretter; sollte in "Sides" aufgehen, # sonst sampelt das Profil die Lappen weg @@ -82,6 +84,47 @@ PRESETS = { "Sub Count": 1, "Sub Length": 0.35, "Sub Up": 1.6, "Sides": 4, "Detail": 0.3, "Branch Count": 10, }, + # --- Varianten ueber die Kronenform (Idee: modular_tree "envelope shapes") --- + "tanne": { # kegelfoermig: unten lange Aeste, oben kurz, leicht haengend + "Height": 7.0, "Trunk Radius": 0.15, "Bend": 0.15, "Taper": 0.95, + "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, + "Branch Bend": 0.3, "Branch Thickness": 0.2, "Sub Count": 1, + "Sub Length": 0.4, "Root Flare": 0.7, + }, + "eiche": { # runde, breite Krone: Bauch in der Mitte, dicker Stamm + "Height": 5.5, "Trunk Radius": 0.24, "Bend": 0.5, "Taper": 0.75, + "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, + "Branch Bend": 0.9, "Branch Thickness": 0.4, "Sub Count": 3, + "Sub Length": 0.8, "Root Flare": 1.6, "Root Lobe Depth": 1.6, + }, + "birke": { # schlank, hoher Ansatz, feine Zweige + "Height": 7.5, "Trunk Radius": 0.09, "Bend": 0.7, "Taper": 0.92, + "Branch Count": 12, "Branch Length": 1.4, "Branch Up": 1.1, + "Branch Start": 0.45, "Branch End": 0.98, + "Crown Taper": 0.1, "Crown Bulge": 0.25, + "Branch Bend": 0.8, "Branch Thickness": 0.22, "Sub Count": 3, + "Sub Length": 0.5, "Root Flare": 0.6, + }, + "weide": { # haengende Zweige (Droop positiv), breit ausladend + "Height": 5.0, "Trunk Radius": 0.18, "Bend": 0.8, "Taper": 0.8, + "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, + "Branch Droop": 1.2, "Branch Bend": 0.7, "Branch Thickness": 0.28, + "Sub Count": 3, "Sub Length": 0.9, "Root Flare": 1.2, + }, + "toter_baum": { # knorrig, kaum Zweige, starke Kruemmung + "Height": 4.5, "Trunk Radius": 0.17, "Bend": 1.5, "Taper": 0.88, + "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, + "Branch Bend": 1.4, "Branch Thickness": 0.3, "Sub Count": 1, + "Sub Length": 0.5, "Root Flare": 1.4, "Tip Blunt": 0.08, + }, # Kaktus: dicker, kaum verjuengter Stamm, wenige Arme, die per NEGATIVEM # Droop nach oben kruemmen (Saguaro-Silhouette). Keine Sub-Aeste. "kaktus": { @@ -179,6 +222,8 @@ def build_group(): 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("Crown Taper", 'NodeSocketFloat', DEFAULTS["Crown Taper"], -1.5, 1.5) + add_in("Crown Bulge", 'NodeSocketFloat', DEFAULTS["Crown Bulge"], -1.0, 2.0) add_in("Root Flare", 'NodeSocketFloat', DEFAULTS["Root Flare"], 0.0, 4.0) add_in("Root Lobes", 'NodeSocketFloat', DEFAULTS["Root Lobes"], 0.0, 12.0) add_in("Root Lobe Depth", 'NodeSocketFloat', DEFAULTS["Root Lobe Depth"], 0.0, 3.0) @@ -422,10 +467,50 @@ def build_group(): pscale.inputs["To Max"].default_value = 1.0 pscale.clamp = True + # ---------- Kronenform (botanische Huellkurve) ---------- + # Idee aus modular_tree / Weber-Penn: die Astlaenge haengt von der relativen + # Hoehe am Stamm ab. Das entscheidet die Silhouette: + # Crown Taper > 0 -> unten lang, oben kurz = kegelfoermig (Tanne) + # Crown Taper = 0 -> ueberall gleich = saeulenfoermig (Pappel) + # Crown Taper < 0 -> unten kurz, oben lang = besenfoermig + # Crown Bulge > 0 -> Maximum in der Mitte = rund (Eiche) + czp = N("GeometryNodeInputPosition"); czp.location = (750, -20) + czs = N("ShaderNodeSeparateXYZ"); czs.location = (900, -20) + L(czp.outputs[0], czs.inputs[0]) + czn = N("ShaderNodeMath"); czn.location = (1050, -20); czn.operation = 'DIVIDE' + L(czs.outputs["Z"], czn.inputs[0]); L(V["Height"], czn.inputs[1]) + czc = N("ShaderNodeMath"); czc.location = (1200, -20); czc.operation = 'MINIMUM' + czc.inputs[1].default_value = 1.0 + L(czn.outputs[0], czc.inputs[0]) + ctm = N("ShaderNodeMath"); ctm.location = (1350, -20); ctm.operation = 'MULTIPLY' + L(V["Crown Taper"], ctm.inputs[0]); L(czc.outputs[0], ctm.inputs[1]) + cts = N("ShaderNodeMath"); cts.location = (1500, -20); cts.operation = 'SUBTRACT' + cts.inputs[0].default_value = 1.0 + L(ctm.outputs[0], cts.inputs[1]) + # Bauch in der Mitte: sin(pi * t) + cbs = N("ShaderNodeMath"); cbs.location = (1350, -180); cbs.operation = 'MULTIPLY' + cbs.inputs[1].default_value = 3.14159 + L(czc.outputs[0], cbs.inputs[0]) + cbi = N("ShaderNodeMath"); cbi.location = (1500, -180); cbi.operation = 'SINE' + L(cbs.outputs[0], cbi.inputs[0]) + cbm = N("ShaderNodeMath"); cbm.location = (1650, -180); cbm.operation = 'MULTIPLY' + L(V["Crown Bulge"], cbm.inputs[0]); L(cbi.outputs[0], cbm.inputs[1]) + cba = N("ShaderNodeMath"); cba.location = (1800, -180); cba.operation = 'ADD' + cba.inputs[0].default_value = 1.0 + L(cbm.outputs[0], cba.inputs[1]) + ccm = N("ShaderNodeMath"); ccm.location = (1800, -20); ccm.operation = 'MULTIPLY' + L(cts.outputs[0], ccm.inputs[0]); L(cba.outputs[0], ccm.inputs[1]) + ccl = N("ShaderNodeMath"); ccl.location = (1950, -20); ccl.operation = 'MAXIMUM' + ccl.inputs[1].default_value = 0.05 + L(ccm.outputs[0], ccl.inputs[0]) + # mit der Elterndicken-Skalierung kombinieren + pfin = N("ShaderNodeMath"); pfin.location = (2100, 120); pfin.operation = 'MULTIPLY' + L(pscale.outputs[0], pfin.inputs[0]); L(ccl.outputs[0], pfin.inputs[1]) + inst = N("GeometryNodeInstanceOnPoints"); inst.location = (1400, 320) L(_out(c2p, "Points"), _sock(inst, "Points")) L(_out(bsetr, "Curve"), _sock(inst, "Instance")) - L(pscale.outputs[0], _sock(inst, "Scale")) + L(pfin.outputs[0], _sock(inst, "Scale")) if align is not None: L(align.outputs[0], _sock(inst, "Rotation"))