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
+77 -5
View File
@@ -1,7 +1,7 @@
bl_info = {
"name": "Stylized Rock Generator",
"author": "D4rkst3r",
"version": (2, 4, 0),
"version": (2, 5, 0),
"blender": (4, 2, 0),
"location": "View3D > Sidebar > Rock Gen",
"description": (
@@ -51,7 +51,7 @@ PRESET_KEYS = (
"batch_count", "spacing",
"shading_mode", "autosmooth_angle",
"randomize_shape", "noise_scale_max", "displace_strength_max", "nonuniform_scale",
"flatten_bottom", "flatten_ratio", "set_origin_bottom",
"flatten_bottom", "flatten_ratio", "set_origin_bottom", "keep_size",
)
# Eingebaute Presets (nicht ueberschreibbar/loeschbar).
@@ -410,9 +410,17 @@ def _apply_random_transform(context, obj, settings, rng):
return
lo, hi = sorted((settings.scale_min, settings.scale_max))
if settings.nonuniform_scale:
obj.scale = (rng.uniform(lo, hi), rng.uniform(lo, hi), rng.uniform(lo, hi))
sx, sy, sz = (rng.uniform(lo, hi), rng.uniform(lo, hi), rng.uniform(lo, hi))
if settings.keep_size:
# Geometrisches Mittel auf 1.0 normieren: die Proportionen streuen,
# das Volumen bleibt. Sonst multipliziert sich die Streuung mit den
# ScaleMin/Max der Engine.
g = (abs(sx) * abs(sy) * abs(sz)) ** (1.0 / 3.0)
if g > 1e-6:
sx, sy, sz = sx / g, sy / g, sz / g
obj.scale = (sx, sy, sz)
else:
s = rng.uniform(lo, hi)
s = 1.0 if settings.keep_size else rng.uniform(lo, hi)
obj.scale = (s, s, s)
obj.rotation_euler = (
rng.uniform(0.0, TWO_PI),
@@ -727,9 +735,16 @@ class RockGenSettings(PropertyGroup):
scale_min: FloatProperty(name="Scale Min", default=0.8, min=0.1, max=5.0)
scale_max: FloatProperty(name="Scale Max", default=1.2, min=0.1, max=5.0)
nonuniform_scale: BoolProperty(
name="Nicht-uniforme Skalierung", default=False,
name="Nicht-uniforme Skalierung", default=True,
description="Skaliert jede Achse einzeln (flache Platten vs. runde Brocken)",
)
keep_size: BoolProperty(
name="Gesamtgroesse konstant halten", default=True,
description=("Normiert die Achsen-Faktoren, sodass nur die FORM streut und nicht "
"die Groesse. Wichtig fuer EcoGame: die Engine streut die Groesse "
"zusaetzlich ueber ScaleMin/Max - beides zusammen multipliziert sich "
"und erzeugt sonst Mini- und Riesen-Exemplare"),
)
# --- Batch ---
batch_count: IntProperty(name="Anzahl Rocks", default=5, min=1, max=500)
@@ -819,6 +834,17 @@ class RockGenSettings(PropertyGroup):
description="Exportiert nur die aktuell selektierten Rocks statt aller",
)
# --- Zufall ---
random_amount: FloatProperty(
name="Streuung", default=0.35, min=0.0, max=1.0,
description=("Wie stark 'Zufaellig' die Werte veraendert. 0 = nur neuer Seed, "
"1 = volle Bandbreite"),
)
random_shape: BoolProperty(
name="Auch Basis-Form/Noise-Typ", default=False,
description="Wuerfelt zusaetzlich Cube/Ico-Sphere und den Noise-Typ",
)
# --- Presets ---
active_preset: EnumProperty(name="Preset", items=_preset_items)
new_preset_name: StringProperty(name="Preset-Name", default="")
@@ -1277,6 +1303,43 @@ class OBJECT_OT_bake_rock_textures(Operator):
# Operatoren: Presets
# ---------------------------------------------------------------------------
class ROCKGEN_OT_randomize(Operator):
bl_idname = "rockgen.randomize"
bl_label = "Zufaellig"
bl_description = ("Wuerfelt Seed und Form-Parameter neu. 'Streuung' bestimmt, wie "
"weit von den aktuellen Werten abgewichen wird")
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
s = context.scene.rock_gen_settings
rng = random.Random()
a = s.random_amount
def jitter(prop, lo, hi):
"""Wert um +-a in Richtung der Grenzen ziehen, dann clampen."""
cur = getattr(s, prop)
span = (hi - lo) * a
val = cur + rng.uniform(-span, span)
setattr(s, prop, max(lo, min(hi, val)))
s.seed = rng.randint(0, 9999)
jitter("noise_scale", 0.4, 3.0)
jitter("displace_strength", 0.12, 0.7)
jitter("decimate_ratio_1", 0.12, 0.45)
jitter("decimate_ratio_2", 0.3, 0.7)
jitter("dissolve_angle", 2.0, 10.0)
jitter("subsurf_levels", 3, 5)
s.subsurf_levels = int(round(s.subsurf_levels))
if s.randomize_shape:
jitter("noise_scale_max", 0.6, 4.0)
jitter("displace_strength_max", 0.2, 0.9)
if s.random_shape:
s.base_shape = rng.choice(['CUBE', 'ICOSPHERE'])
s.texture_type = rng.choice(['VORONOI', 'CLOUDS', 'DISTORTED_NOISE'])
self.report({'INFO'}, "Neue Zufallswerte (Seed %d)." % s.seed)
return {'FINISHED'}
class ROCKGEN_OT_preset_save(Operator):
bl_idname = "rockgen.preset_save"
bl_label = "Preset speichern"
@@ -1452,6 +1515,7 @@ class VIEW3D_PT_rock_generator(Panel):
row.prop(settings, "scale_min")
row.prop(settings, "scale_max")
box.prop(settings, "nonuniform_scale")
box.prop(settings, "keep_size")
box = layout.box()
box.label(text="Batch")
@@ -1462,6 +1526,13 @@ class VIEW3D_PT_rock_generator(Panel):
sub.enabled = settings.use_modal
sub.prop(settings, "modal_threshold")
box = layout.box()
box.label(text="Zufall", icon='FILE_REFRESH')
row = box.row(align=True)
row.prop(settings, "random_amount")
row.prop(settings, "random_shape", text="", icon='MOD_NOISE')
box.operator("rockgen.randomize", icon='FILE_REFRESH')
layout.separator()
layout.operator("object.generate_rocks_batch", icon='MESH_ICOSPHERE')
@@ -1500,6 +1571,7 @@ classes = (
OBJECT_OT_generate_rocks,
OBJECT_OT_export_rocks_fbx,
OBJECT_OT_bake_rock_textures,
ROCKGEN_OT_randomize,
ROCKGEN_OT_preset_save,
ROCKGEN_OT_preset_load,
ROCKGEN_OT_preset_delete,