diff --git a/blender_manifest_tree.toml b/blender_manifest_tree.toml index 04f4798..b8953a3 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.8.0" +version = "1.9.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 2d79fbf..3660413 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.8.0", + "version": "1.9.0", "type": "add-on", "maintainer": "D4rkst3r", "license": [ @@ -48,9 +48,9 @@ "Mesh", "Modeling" ], - "archive_url": "./stylized_tree_generator-1.8.0.zip", - "archive_size": 13440, - "archive_hash": "sha256:e96e6ec6f8009708ce0611dea6ea50e3271b6603d60bce84ebeab2e365bba983" + "archive_url": "./stylized_tree_generator-1.9.0.zip", + "archive_size": 14478, + "archive_hash": "sha256:07c964096cd907dab9e22e1bc936621ed25476e188ebaa6176a18e98c5f76a4c" } ] } \ No newline at end of file diff --git a/dist/stylized_tree_generator-1.8.0.zip b/dist/stylized_tree_generator-1.8.0.zip deleted file mode 100644 index 9090f97..0000000 Binary files a/dist/stylized_tree_generator-1.8.0.zip and /dev/null differ diff --git a/dist/stylized_tree_generator-1.9.0.zip b/dist/stylized_tree_generator-1.9.0.zip new file mode 100644 index 0000000..b8a2023 Binary files /dev/null and b/dist/stylized_tree_generator-1.9.0.zip differ diff --git a/stylized_tree_generator.py b/stylized_tree_generator.py index fce99f8..a3ca791 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, 8, 0), + "version": (1, 9, 0), "blender": (4, 2, 0), "location": "View3D > Sidebar > Tree Gen", "description": "Parametrischer Baum-/Palmen-/Busch-Generator (Geometry Nodes) mit Wachstums-Stufen", @@ -43,12 +43,18 @@ DEFAULTS = { "Sub Count": 2, # Sub-Aeste je Hauptast (0 = aus) "Sub Length": 0.55, "Sub Up": 1.4, - "Sides": 8, # Stamm rund (Referenz TreeIt: ~9) + "Sides": 12, # Stamm rund genug fuer die Wurzelbretter + # (+104 Tris ggue. 8 - gemessen, lohnt sich) "Branch Sides": 4, # Aeste sparsamer "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) "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 + "Root Lobe Depth": 1.3, # wie weit die Lappen rausstehen + "Root Height": 1.1, # Hoehe (m) des Anlaufs; zu klein -> zu wenige + # Ringe liegen drin und man sieht nichts "Detail": 0.55, # skaliert alle Resample-Counts = Tri-Budget-Regler "Branch Collar": 0.4, # Ansatz-Verdickung: Uebergang Ast->Stamm (0 = aus) "Merge": 0.0, # 1 = Aeste per Voxel-Remesh mit dem Stamm verschmelzen @@ -174,6 +180,9 @@ def build_group(): add_in("UV Scale", 'NodeSocketFloat', DEFAULTS["UV Scale"], 0.01, 20.0) add_in("Detail", 'NodeSocketFloat', DEFAULTS["Detail"], 0.2, 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) + add_in("Root Height", 'NodeSocketFloat', DEFAULTS["Root Height"], 0.05, 3.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) @@ -664,7 +673,63 @@ def build_group(): L(uv.outputs[0], _sock(sm, "Value")) return sm - mesh_trunk = _to_mesh(setrad, "Curve", V["Sides"], 3400, 900) + mesh_trunk0 = _to_mesh(setrad, "Curve", V["Sides"], 3400, 900) + + # ---------- Wurzelanlauf ausformen (Wurzelbretter) ---------- + # Der reine Radius-Flare ergibt einen glatten Trichter. Echte Wurzelanlaeufe + # sind GELAPPT. Das laesst sich nicht ueber den Kurvenradius machen (der ist + # rundum gleich), sondern erst am fertigen Mesh: Vertices nahe dem Boden + # radial nach aussen schieben, moduliert ueber den Winkel. Kostet 0 Tris. + fpos = N("GeometryNodeInputPosition"); fpos.location = (4750, 1150) + fsep = N("ShaderNodeSeparateXYZ"); fsep.location = (4900, 1150) + L(fpos.outputs[0], fsep.inputs[0]) + + # Hoehen-Abfall: nur das unterste Stueck ist betroffen + fh = N("ShaderNodeMath"); fh.location = (5050, 1300); fh.operation = 'DIVIDE' + L(fsep.outputs["Z"], fh.inputs[0]); L(V["Root Height"], fh.inputs[1]) + fc = N("ShaderNodeMath"); fc.location = (5200, 1300); fc.operation = 'MINIMUM' + fc.inputs[1].default_value = 1.0 + L(fh.outputs[0], fc.inputs[0]) + fi = N("ShaderNodeMath"); fi.location = (5350, 1300); fi.operation = 'SUBTRACT' + fi.inputs[0].default_value = 1.0 + L(fc.outputs[0], fi.inputs[1]) + fp = N("ShaderNodeMath"); fp.location = (5500, 1300); fp.operation = 'POWER' + fp.inputs[1].default_value = 2.5 + L(fi.outputs[0], fp.inputs[0]) + fpc = N("ShaderNodeMath"); fpc.location = (5650, 1300); fpc.operation = 'MAXIMUM' + fpc.inputs[1].default_value = 0.0 + L(fp.outputs[0], fpc.inputs[0]) + + # Lappen ueber den Winkel: cos(Lobes * atan2(y,x)), auf 0..1 gehoben + fat = N("ShaderNodeMath"); fat.location = (5050, 1000); fat.operation = 'ARCTAN2' + L(fsep.outputs["Y"], fat.inputs[0]); L(fsep.outputs["X"], fat.inputs[1]) + fml = N("ShaderNodeMath"); fml.location = (5200, 1000); fml.operation = 'MULTIPLY' + L(fat.outputs[0], fml.inputs[0]); L(V["Root Lobes"], fml.inputs[1]) + fco = N("ShaderNodeMath"); fco.location = (5350, 1000); fco.operation = 'COSINE' + L(fml.outputs[0], fco.inputs[0]) + fno = N("ShaderNodeMath"); fno.location = (5500, 1000); fno.operation = 'MULTIPLY_ADD' + fno.inputs[1].default_value = 0.5; fno.inputs[2].default_value = 0.5 + L(fco.outputs[0], fno.inputs[0]) + + # Betrag = RootLobeDepth * Hoehenabfall * Lappen * Stammradius + fa1 = N("ShaderNodeMath"); fa1.location = (5800, 1150); fa1.operation = 'MULTIPLY' + L(fpc.outputs[0], fa1.inputs[0]); L(fno.outputs[0], fa1.inputs[1]) + fa2 = N("ShaderNodeMath"); fa2.location = (5950, 1150); fa2.operation = 'MULTIPLY' + L(fa1.outputs[0], fa2.inputs[0]); L(V["Root Lobe Depth"], fa2.inputs[1]) + fa3 = N("ShaderNodeMath"); fa3.location = (6100, 1150); fa3.operation = 'MULTIPLY' + L(fa2.outputs[0], fa3.inputs[0]); L(V["Trunk Radius"], fa3.inputs[1]) + + # Richtung: radial nach aussen (XY), Z bleibt unangetastet + fxy = N("ShaderNodeCombineXYZ"); fxy.location = (5200, 850) + L(fsep.outputs["X"], fxy.inputs["X"]); L(fsep.outputs["Y"], fxy.inputs["Y"]) + fnrm = N("ShaderNodeVectorMath"); fnrm.location = (5350, 850); fnrm.operation = 'NORMALIZE' + L(fxy.outputs[0], fnrm.inputs[0]) + foff = N("ShaderNodeVectorMath"); foff.location = (6250, 1000); foff.operation = 'SCALE' + L(fnrm.outputs[0], foff.inputs[0]); L(fa3.outputs[0], _sock(foff, "Scale")) + + mesh_trunk = N("GeometryNodeSetPosition"); mesh_trunk.location = (6400, 900) + L(_out(mesh_trunk0, "Geometry"), _sock(mesh_trunk, "Geometry")) + L(foff.outputs[0], _sock(mesh_trunk, "Offset")) mesh_branch = _to_mesh(rsetp, "Geometry", V["Branch Sides"], 3400, 200) mesh_sub = _to_mesh(sbset, "Geometry", V["Sub Sides"], 3400, -500)