diff --git a/blender_manifest_tree.toml b/blender_manifest_tree.toml index cf7de5f..7e62cf4 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.21.0" +version = "1.22.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 45bf113..74ed23f 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.21.0", + "version": "1.22.0", "type": "add-on", "maintainer": "D4rkst3r", "license": [ @@ -48,9 +48,9 @@ "Mesh", "Modeling" ], - "archive_url": "./stylized_tree_generator-1.21.0.zip", - "archive_size": 18435, - "archive_hash": "sha256:eb093c862dc7192728e86ef07d45fc1a9f4381044c2f8560b399f2a85911ec94" + "archive_url": "./stylized_tree_generator-1.22.0.zip", + "archive_size": 19331, + "archive_hash": "sha256:5e5e3b5e0b73aabe416f93c27bbef4772b561122762dd52daf836f10c0e45b35" } ] } \ No newline at end of file diff --git a/dist/stylized_tree_generator-1.21.0.zip b/dist/stylized_tree_generator-1.21.0.zip deleted file mode 100644 index ee00ac4..0000000 Binary files a/dist/stylized_tree_generator-1.21.0.zip and /dev/null differ diff --git a/dist/stylized_tree_generator-1.22.0.zip b/dist/stylized_tree_generator-1.22.0.zip new file mode 100644 index 0000000..202497e Binary files /dev/null and b/dist/stylized_tree_generator-1.22.0.zip differ diff --git a/stylized_tree_generator.py b/stylized_tree_generator.py index ccf0db0..d9ab1eb 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, 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) ---------- diff --git a/tests/test_tree_addon.py b/tests/test_tree_addon.py index 53860fb..8cd043e 100644 --- a/tests/test_tree_addon.py +++ b/tests/test_tree_addon.py @@ -1,7 +1,7 @@ """Prueft das gebaute Tree-Addon: registrieren, Operator ausfuehren, Ergebnis messen.""" import bpy, sys, zipfile, os, tempfile, importlib.util, traceback -ZIP = r"A:\eco\stylized_rock_generator\dist\stylized_tree_generator-1.21.0.zip" +ZIP = r"A:\eco\stylized_rock_generator\dist\stylized_tree_generator-1.22.0.zip" fails = [] tmp = tempfile.mkdtemp()