Zufalls-Funktion in beiden Addons + Zusatz-Ebenen + Form-statt-Groesse

Rock Generator 2.5.0:
- "Zufaellig"-Button: wuerfelt Seed und Form-Parameter, "Streuung" steuert die
  Abweichung von den aktuellen Werten (0 = nur neuer Seed). Optional werden auch
  Basis-Form und Noise-Typ gewuerfelt. Werte werden auf ihre Grenzen geklemmt.
- Skalierung streut jetzt die FORM, nicht die Groesse: nicht-uniform ist Default,
  und "Gesamtgroesse konstant halten" normiert das geometrische Mittel auf 1.
  Gemessen ueber 6 Rocks: Volumen-Spanne 1.92x -> 1.11x. Wichtig, weil EcoGame
  die Groesse zusaetzlich ueber ScaleMin/Max streut.

Tree Generator 1.19.0:
- "Zufaellig"-Button: neuer Seed plus Streuung auf Hoehe, Radius, Biegung,
  Ast-Anzahl/-Laenge, Kronenform und Wurzelanlauf. Preset-Charakter bleibt, weil
  relativ zu den aktuellen Werten gestreut wird (gemessen: Hoehe 6.65 -> 8.34 m).
- "_Leaf"- und "_Frucht"-Ebenen: leere Objekte mit IDENTISCHEM Ursprung neben dem
  Baum (gemessen: Abstand 0.000000), damit die Ebenen in UE deckungsgleich sitzen.

Vier Test-Suiten liegen jetzt unter tests/ statt nur im Scratchpad.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-29 13:30:11 +02:00
co-authored by Claude Opus 4.8
parent f5bbd1bc35
commit 1bb547c70c
14 changed files with 431 additions and 16 deletions
+99 -1
View File
@@ -1,7 +1,7 @@
bl_info = {
"name": "Stylized Tree Generator",
"author": "D4rkst3r",
"version": (1, 18, 0),
"version": (1, 19, 0),
"blender": (4, 2, 0),
"location": "View3D > Sidebar > Tree Gen",
"description": "Parametrischer Baum-/Palmen-/Busch-Generator (Geometry Nodes) mit Wachstums-Stufen",
@@ -992,6 +992,21 @@ class TreeGenSettings(PropertyGroup):
description="Laufende Variante derselben Art (Birke_01, Birke_02 ...)",
)
spacing: FloatProperty(name="Abstand", default=4.0, min=0.0, max=20.0)
random_amount: FloatProperty(
name="Streuung", default=0.3, min=0.0, max=1.0,
description=("Wie stark 'Zufaellig' die Werte veraendert. 0 = nur neuer Seed, "
"1 = volle Bandbreite. Der Preset-Charakter bleibt erhalten"),
)
make_leaf: BoolProperty(
name="_Leaf-Ebene anlegen", default=False,
description=("Legt pro Baum ein leeres Objekt <Name>_Leaf mit GLEICHEM Ursprung "
"an. Dort modellierst du die Krone - in UE sitzen beide Ebenen "
"dadurch deckungsgleich"),
)
make_frucht: BoolProperty(
name="_Frucht-Ebene anlegen", default=False,
description="Zusaetzlich <Name>_Frucht (gleicher Ursprung) fuer die Frucht-Ebene",
)
apply_modifier: BoolProperty(
name="Modifier anwenden", default=False,
description="Ergebnis als normales Mesh einfrieren (fuer den Export)",
@@ -1038,6 +1053,45 @@ def _add_lightmap_uv(context, ob, op):
ob.data.uv_layers[0].active_render = True
def _add_layer(context, ob, suffix):
"""Leeres Mesh-Objekt mit IDENTISCHEM Ursprung neben dem Baum anlegen.
Gleicher Ursprung ist der Punkt: in UE muessen Stamm, Krone und Fruechte
deckungsgleich sitzen, sonst verrutscht die Krone gegenueber dem Stamm.
"""
name = ob.name + suffix
old = bpy.data.objects.get(name)
if old:
bpy.data.objects.remove(old, do_unlink=True)
me = bpy.data.meshes.new(name)
new = bpy.data.objects.new(name, me)
context.collection.objects.link(new)
new.location = ob.location.copy()
new.rotation_euler = ob.rotation_euler.copy()
new.scale = ob.scale.copy()
return new
class TREEGEN_OT_randomize(Operator):
bl_idname = "object.treegen_randomize"
bl_label = "Zufaellig"
bl_description = ("Wuerfelt Seed und Form-Parameter neu, ohne den Preset-Charakter "
"zu verlieren. 'Streuung' bestimmt die Abweichung")
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
s = context.scene.tree_gen_settings
import random as _r
rng = _r.Random()
s.seed = rng.randint(0, 9999)
# Die Form-Parameter liegen am Modifier, nicht an den Settings. Deshalb
# merken wir uns die Streuung und wenden sie beim Erzeugen an.
s["_rand_jitter"] = s.random_amount
self.report({'INFO'}, "Neuer Seed %d, Streuung %.0f%%."
% (s.seed, s.random_amount * 100))
return {'FINISHED'}
class TREEGEN_OT_create(Operator):
bl_idname = "object.treegen_create"
bl_label = "Baum erzeugen"
@@ -1075,6 +1129,30 @@ class TREEGEN_OT_create(Operator):
self.report({'ERROR'}, "Erzeugen fehlgeschlagen: %s" % exc)
return {'CANCELLED'}
# Streuung auf die Form-Regler anwenden (Preset-Charakter bleibt)
amt = float(s.get("_rand_jitter", 0.0))
if amt > 0.0:
import random as _r
rng = _r.Random(s.seed)
ids = socket_ids(ng)
JITTER = {"Height": 0.35, "Trunk Radius": 0.25, "Bend": 0.5,
"Branch Count": 0.3, "Branch Length": 0.35,
"Branch Up": 0.3, "Branch Bend": 0.4,
"Crown Taper": 0.3, "Crown Bulge": 0.3,
"Root Flare": 0.4, "Root Lobes": 0.3}
for ob in created:
md = ob.modifiers.get("GN_Tree")
if md is None:
continue
for key, rel in JITTER.items():
sid = ids.get(key)
if sid is None:
continue
cur = md[sid]
f = 1.0 + rng.uniform(-rel, rel) * amt
md[sid] = type(cur)(round(cur * f)) if isinstance(cur, int) else cur * f
context.view_layer.update()
if s.apply_modifier:
for ob in created:
context.view_layer.objects.active = ob
@@ -1087,6 +1165,15 @@ class TREEGEN_OT_create(Operator):
if s.generate_lightmap_uv:
_add_lightmap_uv(context, ob, self)
if s.make_leaf or s.make_frucht:
extra = []
for ob in created:
if s.make_leaf:
extra.append(_add_layer(context, ob, "_Leaf"))
if s.make_frucht:
extra.append(_add_layer(context, ob, "_Frucht"))
created.extend(extra)
context.view_layer.update()
total = 0
dg = context.evaluated_depsgraph_get()
@@ -1129,6 +1216,16 @@ class VIEW3D_PT_tree_generator(Panel):
sub.prop(s, "stages")
sub.prop(s, "spacing")
box = layout.box()
box.label(text="Zufall", icon='FILE_REFRESH')
box.prop(s, "random_amount")
box.operator("object.treegen_randomize", icon='FILE_REFRESH')
box = layout.box()
box.label(text="Zusatz-Ebenen")
box.prop(s, "make_leaf")
box.prop(s, "make_frucht")
box = layout.box()
box.label(text="Export")
box.prop(s, "apply_modifier")
@@ -1143,6 +1240,7 @@ class VIEW3D_PT_tree_generator(Panel):
classes = (
TreeGenSettings,
TREEGEN_OT_randomize,
TREEGEN_OT_create,
VIEW3D_PT_tree_generator,
)