From 1bb547c70cfabafc9341c41835d24ec6ba383186 Mon Sep 17 00:00:00 2001 From: D4rkst3r Date: Wed, 29 Jul 2026 13:30:11 +0200 Subject: [PATCH] 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 --- README.md | 11 +++ blender_manifest.toml | 2 +- blender_manifest_tree.toml | 2 +- dist/index.json | 16 ++-- dist/stylized_rock_generator-2.4.0.zip | Bin 16036 -> 0 bytes dist/stylized_rock_generator-2.5.0.zip | Bin 0 -> 16991 bytes dist/stylized_tree_generator-1.18.0.zip | Bin 16777 -> 0 bytes dist/stylized_tree_generator-1.19.0.zip | Bin 0 -> 17827 bytes stylized_rock_generator.py | 82 +++++++++++++++++-- stylized_tree_generator.py | 100 +++++++++++++++++++++++- tests/test_ecogame_rock.py | 51 ++++++++++++ tests/test_ecogame_tree.py | 39 +++++++++ tests/test_randomize.py | 64 +++++++++++++++ tests/test_tree_addon.py | 80 +++++++++++++++++++ 14 files changed, 431 insertions(+), 16 deletions(-) delete mode 100644 dist/stylized_rock_generator-2.4.0.zip create mode 100644 dist/stylized_rock_generator-2.5.0.zip delete mode 100644 dist/stylized_tree_generator-1.18.0.zip create mode 100644 dist/stylized_tree_generator-1.19.0.zip create mode 100644 tests/test_ecogame_rock.py create mode 100644 tests/test_ecogame_tree.py create mode 100644 tests/test_randomize.py create mode 100644 tests/test_tree_addon.py diff --git a/README.md b/README.md index 0d7d09a..f9e20c9 100644 --- a/README.md +++ b/README.md @@ -180,6 +180,17 @@ Referenz: blender --background --python tests/test_rock_gen.py ``` +Weitere Suiten (jeweils `blender --background --python `): + +| Datei | prueft | +|---|---| +| `tests/test_tree_addon.py` | Tree-Addon: Presets, Wachstums-Stufen, UVs, Modifier-Apply | +| `tests/test_ecogame_rock.py` | Fels-Benennung, Bake liefert nur `_albedo`/`_normal`, AO im Albedo | +| `tests/test_ecogame_tree.py` | Baum-Benennung `__S`, Ursprung, Lightmap-Default | +| `tests/test_randomize.py` | Zufalls-Funktion beider Addons, `_Leaf`/`_Frucht` mit gleichem Ursprung | + +Die Suiten erwarten die gebauten Zips in `dist/` bzw. lesen die `.py` direkt. + Erzeugt 3 Rocks, prueft je Rock: keine losen Vertices, manifold, Normalen nach aussen (signed volume > 0), UV0 + UV1 vorhanden, und gibt eine Zusammenfassung (Vertex-/Face-Count) aus. Exit-Code 0 = alles bestanden, 1 = Fehler. diff --git a/blender_manifest.toml b/blender_manifest.toml index d78c793..806bc1a 100644 --- a/blender_manifest.toml +++ b/blender_manifest.toml @@ -1,7 +1,7 @@ schema_version = "1.0.0" id = "stylized_rock_generator" -version = "2.4.0" +version = "2.5.0" name = "Stylized Rock Generator" tagline = "Batch-Generator fuer stylized Rocks mit UE5-Export" maintainer = "D4rkst3r" diff --git a/blender_manifest_tree.toml b/blender_manifest_tree.toml index 6e64508..8dbd292 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.18.0" +version = "1.19.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 52c751c..4466481 100644 --- a/dist/index.json +++ b/dist/index.json @@ -7,7 +7,7 @@ "id": "stylized_rock_generator", "name": "Stylized Rock Generator", "tagline": "Batch-Generator fuer stylized Rocks mit UE5-Export", - "version": "2.4.0", + "version": "2.5.0", "type": "add-on", "maintainer": "D4rkst3r", "license": [ @@ -23,16 +23,16 @@ "Mesh", "Modeling" ], - "archive_url": "./stylized_rock_generator-2.4.0.zip", - "archive_size": 16036, - "archive_hash": "sha256:e7163f6f0c53731f897b8a1ba63db1cdab94aa182dd720ca1a5653a2ca3df9ec" + "archive_url": "./stylized_rock_generator-2.5.0.zip", + "archive_size": 16991, + "archive_hash": "sha256:f89c740398afb7550ccc109980be6d9ea9d785ccc9383e70aa8c7d7cb89006bd" }, { "schema_version": "1.0.0", "id": "stylized_tree_generator", "name": "Stylized Tree Generator", "tagline": "Parametrische Baeume, Palmen, Bueschen mit Wachstums-Stufen", - "version": "1.18.0", + "version": "1.19.0", "type": "add-on", "maintainer": "D4rkst3r", "license": [ @@ -48,9 +48,9 @@ "Mesh", "Modeling" ], - "archive_url": "./stylized_tree_generator-1.18.0.zip", - "archive_size": 16777, - "archive_hash": "sha256:9ea5b7a9c01f3c1b382436d25539ab9ec41ffb8b051d26c17c82783c594680a0" + "archive_url": "./stylized_tree_generator-1.19.0.zip", + "archive_size": 17827, + "archive_hash": "sha256:37f6a0be381178144b36a731b68f8d307c18c714e0d9b2cd9f1c109e4b4de8e6" } ] } \ No newline at end of file diff --git a/dist/stylized_rock_generator-2.4.0.zip b/dist/stylized_rock_generator-2.4.0.zip deleted file mode 100644 index 264d8219591610e07c6fcb1305fc78785c4dc8fd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16036 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 literal 0 HcmV?d00001 diff --git a/dist/stylized_tree_generator-1.18.0.zip b/dist/stylized_tree_generator-1.18.0.zip deleted file mode 100644 index f8b8bd12ba59fc8c2b794a4822afab65db40e779..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16777 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

s 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 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, diff --git a/stylized_tree_generator.py b/stylized_tree_generator.py index 9353768..43a9176 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, 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 _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 _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, ) diff --git a/tests/test_ecogame_rock.py b/tests/test_ecogame_rock.py new file mode 100644 index 0000000..e22ae5c --- /dev/null +++ b/tests/test_ecogame_rock.py @@ -0,0 +1,51 @@ +import bpy, sys, importlib.util, os, glob, tempfile, numpy as np +ADDON=r"A:\eco\stylized_rock_generator\stylized_rock_generator.py" +spec=importlib.util.spec_from_file_location("srg",ADDON) +m=importlib.util.module_from_spec(spec); sys.modules["srg"]=m; spec.loader.exec_module(m) +try: m.unregister() +except Exception: pass +m.register() +fails=[] +s=bpy.context.scene.rock_gen_settings +for o in list(bpy.data.objects): bpy.data.objects.remove(o, do_unlink=True) + +# --- Namensschema --- +s.batch_count=3; s.subsurf_levels=3; s.use_modal=False; s.generate_lods=False +s.name_prefix="Fels"; s.name_form="a"; s.name_class='L' +bpy.ops.object.generate_rocks_batch() +names=sorted(o.name for o in bpy.context.scene.objects if o.type=='MESH') +print("NAMEN:", names) +if not all(n.startswith("Fels_A_L") for n in names): fails.append("naming") + +# Einzeln (ohne Nummer) +for o in list(bpy.data.objects): bpy.data.objects.remove(o,do_unlink=True) +s.batch_count=1 +bpy.ops.object.generate_rocks_batch() +single=[o.name for o in bpy.context.scene.objects if o.type=='MESH'] +print("EINZELN:", single) +if single and single[0]!="Fels_A_L": fails.append("naming_single") +print("LIGHTMAP-DEFAULT aus:", not m.RockGenSettings.bl_rna.properties['generate_lightmap_uv'].default) + +# --- Bake: nur _albedo + _normal, AO drin --- +out=tempfile.mkdtemp() +s.bake_dir=out; s.bake_prefix="fels_a"; s.bake_resolution='512' +s.bake_color=True; s.bake_normal=True; s.bake_roughness=False; s.bake_ao=False +def run(ao_on, strength=1.0): + for f in glob.glob(os.path.join(out,"*.png")): os.remove(f) + s.bake_ao_into_albedo=ao_on; s.bake_ao_strength=strength + bpy.ops.object.bake_rock_textures() + files=sorted(os.path.basename(f) for f in glob.glob(os.path.join(out,"*.png"))) + img=bpy.data.images.load(os.path.join(out,"fels_a_albedo.png")) + px=np.array(img.pixels[:],dtype=np.float32).reshape(-1,4)[:,:3] + bpy.data.images.remove(img) + return files, float(px.mean()), float(px.std()) +f_on, mean_on, std_on = run(True) +f_off, mean_off, std_off = run(False) +print("DATEIEN:", f_on) +print("AO an : mean=%.4f std=%.4f" % (mean_on, std_on)) +print("AO aus: mean=%.4f std=%.4f" % (mean_off, std_off)) +if f_on != ["fels_a_albedo.png","fels_a_normal.png"]: fails.append("dateinamen") +if not (mean_on < mean_off): fails.append("ao_nicht_eingerechnet") +m.unregister() +print("ERGEBNIS:", "ALLE CHECKS OK" if not fails else "FEHLER: "+", ".join(fails)) +sys.exit(1 if fails else 0) diff --git a/tests/test_ecogame_tree.py b/tests/test_ecogame_tree.py new file mode 100644 index 0000000..5811890 --- /dev/null +++ b/tests/test_ecogame_tree.py @@ -0,0 +1,39 @@ +import bpy, sys, importlib.util +ADDON=r"A:\eco\stylized_rock_generator\stylized_tree_generator.py" +spec=importlib.util.spec_from_file_location("stg",ADDON) +m=importlib.util.module_from_spec(spec); sys.modules["stg"]=m; spec.loader.exec_module(m) +try: m.unregister() +except Exception: pass +m.register() +fails=[] +for o in list(bpy.data.objects): bpy.data.objects.remove(o,do_unlink=True) +s=bpy.context.scene.tree_gen_settings + +print("LIGHTMAP-Default aus:", not m.TreeGenSettings.bl_rna.properties['generate_lightmap_uv'].default) +if m.TreeGenSettings.bl_rna.properties['generate_lightmap_uv'].default: fails.append("lightmap_default") + +# Wachstums-Stufen mit Art-Name +s.preset='birke'; s.art_name="Birke"; s.variant=1 +s.use_growth=True; s.stages=4; s.apply_modifier=False +bpy.ops.object.treegen_create() +names=sorted(o.name for o in bpy.context.scene.objects if o.type=='MESH') +print("STUFEN:", names) +if names != ["Birke_01_S1","Birke_01_S2","Birke_01_S3","Birke_01_S4"]: fails.append("stufen_namen") + +# Einzelbaum, Art aus Preset abgeleitet +for o in list(bpy.data.objects): bpy.data.objects.remove(o,do_unlink=True) +s.use_growth=False; s.art_name=""; s.preset='eiche'; s.variant=2 +bpy.ops.object.treegen_create() +single=[o.name for o in bpy.context.scene.objects if o.type=='MESH'] +print("EINZELN:", single) +if single != ["Eiche_02"]: fails.append("einzel_name") + +# Ursprung unten-mittig + echte Meter +ob=bpy.context.scene.objects[single[0]] +ev=ob.evaluated_get(bpy.context.evaluated_depsgraph_get()) +zs=[v.co.z for v in ev.data.vertices] +print("URSPRUNG z=%.3f min_z=%.3f Hoehe=%.2f m" % (ob.location.z, min(zs), max(zs)-min(zs))) +if abs(min(zs)) > 0.02: fails.append("ursprung") +m.unregister() +print("ERGEBNIS:", "ALLE CHECKS OK" if not fails else "FEHLER: "+", ".join(fails)) +sys.exit(1 if fails else 0) diff --git a/tests/test_randomize.py b/tests/test_randomize.py new file mode 100644 index 0000000..d64d96c --- /dev/null +++ b/tests/test_randomize.py @@ -0,0 +1,64 @@ +import bpy, sys, importlib.util +fails=[] + +# ---------- ROCK ---------- +spec=importlib.util.spec_from_file_location("srg", r"A:\eco\stylized_rock_generator\stylized_rock_generator.py") +r=importlib.util.module_from_spec(spec); sys.modules["srg"]=r; spec.loader.exec_module(r) +try: r.unregister() +except Exception: pass +r.register() +s=bpy.context.scene.rock_gen_settings +before=(s.seed, round(s.noise_scale,3), round(s.displace_strength,3), round(s.decimate_ratio_1,3)) +s.random_amount=0.5 +bpy.ops.rockgen.randomize() +after=(s.seed, round(s.noise_scale,3), round(s.displace_strength,3), round(s.decimate_ratio_1,3)) +print("ROCK vorher:", before) +print("ROCK nachher:", after) +if before==after: fails.append("rock_randomize_ohne_wirkung") +# Grenzen eingehalten? +if not (0.01<=s.noise_scale<=10 and 0<=s.displace_strength<=2 and 0.01<=s.decimate_ratio_1<=1): + fails.append("rock_grenzen") +# Erzeugt der Rock danach noch gueltige Geometrie? +for o in list(bpy.data.objects): bpy.data.objects.remove(o,do_unlink=True) +s.batch_count=1; s.use_modal=False; s.subsurf_levels=3 +res=bpy.ops.object.generate_rocks_batch() +meshes=[o for o in bpy.context.scene.objects if o.type=='MESH'] +print("ROCK nach Randomize erzeugt:", [o.name for o in meshes], "faces=", len(meshes[0].data.polygons) if meshes else 0) +if not meshes or len(meshes[0].data.polygons)==0: fails.append("rock_leer") +r.unregister() + +# ---------- TREE ---------- +for o in list(bpy.data.objects): bpy.data.objects.remove(o,do_unlink=True) +spec2=importlib.util.spec_from_file_location("stg", r"A:\eco\stylized_rock_generator\stylized_tree_generator.py") +t=importlib.util.module_from_spec(spec2); sys.modules["stg"]=t; spec2.loader.exec_module(t) +try: t.unregister() +except Exception: pass +t.register() +ts=bpy.context.scene.tree_gen_settings +ts.preset='birke'; ts.art_name="Birke"; ts.variant=1; ts.use_growth=False +ts.make_leaf=True; ts.make_frucht=True; ts.random_amount=0.5 +ts.seed=0 +bpy.ops.object.treegen_randomize() +print("TREE Seed nach Randomize:", ts.seed) +if ts.seed==0: fails.append("tree_seed") +bpy.ops.object.treegen_create() +names=sorted(o.name for o in bpy.context.scene.objects) +print("TREE Objekte:", names) +if not {"Birke_01","Birke_01_Leaf","Birke_01_Frucht"} <= set(names): fails.append("zusatz_ebenen") +# Ursprung identisch? +base=bpy.context.scene.objects["Birke_01"]; leaf=bpy.context.scene.objects["Birke_01_Leaf"] +d=(base.location-leaf.location).length +print("URSPRUNG-Abstand Stamm<->Leaf: %.6f" % d) +if d>1e-6: fails.append("ursprung_leaf") +# Zwei Randomize-Durchlaeufe -> unterschiedliche Geometrie? +def height(): + ev=bpy.context.scene.objects["Birke_01"].evaluated_get(bpy.context.evaluated_depsgraph_get()) + zs=[v.co.z for v in ev.data.vertices]; return round(max(zs)-min(zs),3) +h1=height() +bpy.ops.object.treegen_randomize(); bpy.ops.object.treegen_create() +h2=height() +print("TREE Hoehe Lauf1=%.3f Lauf2=%.3f" % (h1,h2)) +if abs(h1-h2)<1e-4: fails.append("tree_keine_variation") +t.unregister() +print("ERGEBNIS:", "ALLE CHECKS OK" if not fails else "FEHLER: "+", ".join(fails)) +sys.exit(1 if fails else 0) diff --git a/tests/test_tree_addon.py b/tests/test_tree_addon.py new file mode 100644 index 0000000..8d2885f --- /dev/null +++ b/tests/test_tree_addon.py @@ -0,0 +1,80 @@ +"""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.19.0.zip" +fails = [] + +tmp = tempfile.mkdtemp() +with zipfile.ZipFile(ZIP) as z: + z.extractall(tmp) +init = None +for root, _, files in os.walk(tmp): + if "__init__.py" in files: + init = os.path.join(root, "__init__.py"); break +if not init: + print("FAIL: __init__.py nicht im Zip"); sys.exit(1) +print("Zip-Inhalt ok:", os.path.relpath(init, tmp)) + +spec = importlib.util.spec_from_file_location("stylized_tree_generator", init) +m = importlib.util.module_from_spec(spec) +sys.modules["stylized_tree_generator"] = m +try: + spec.loader.exec_module(m) + m.register() + print("registriert ok") +except Exception: + traceback.print_exc(); print("FAIL: register"); sys.exit(1) + +s = bpy.context.scene.tree_gen_settings + +# 1) einzelner Baum je Preset +for p in ("baum", "palme", "busch", "kaktus"): + for o in list(bpy.data.objects): + bpy.data.objects.remove(o, do_unlink=True) + s.preset = p; s.use_growth = False; s.seed = 3 + r = bpy.ops.object.treegen_create() + obs = [o for o in bpy.data.objects if o.type == 'MESH'] + bpy.context.view_layer.update() + dg = bpy.context.evaluated_depsgraph_get() + v = len(obs[0].evaluated_get(dg).data.vertices) if obs else 0 + ok = ('FINISHED' in r) and v > 0 + print("preset %-7s -> %s verts=%d" % (p, "ok" if ok else "FAIL", v)) + if not ok: fails.append(p) + +# 2) Wachstums-Stufen +for o in list(bpy.data.objects): + bpy.data.objects.remove(o, do_unlink=True) +s.preset = "baum"; s.use_growth = True; s.stages = 4 +r = bpy.ops.object.treegen_create() +obs = sorted([o for o in bpy.data.objects if o.type == 'MESH'], key=lambda o: o.name) +bpy.context.view_layer.update() +dg = bpy.context.evaluated_depsgraph_get() +counts = [len(o.evaluated_get(dg).data.vertices) for o in obs] +growing = all(counts[i] < counts[i+1] for i in range(len(counts)-1)) +print("stufen: %d Objekte verts=%s aufsteigend=%s" % (len(obs), counts, growing)) +if len(obs) != 4 or not growing: fails.append("stages") + +# 3) Modifier anwenden (Export-Pfad) +for o in list(bpy.data.objects): + bpy.data.objects.remove(o, do_unlink=True) +s.use_growth = False; s.apply_modifier = True +s.generate_lightmap_uv = True # Default ist AUS (EcoGame: dynamisches Licht) +bpy.ops.object.treegen_create() +obs = [o for o in bpy.data.objects if o.type == 'MESH'] +real = len(obs[0].data.vertices) if obs else 0 # echte Mesh-Daten, nicht evaluated +uvs = [l.name for l in obs[0].data.uv_layers] if obs else [] +print("apply_modifier -> echte Verts=%d mods=%d uvs=%s" % (real, len(obs[0].modifiers) if obs else -1, uvs)) +if real == 0: fails.append("apply") +if "UVMap" not in uvs: fails.append("uv0") +if "Lightmap" not in uvs: fails.append("uv1") +# UV muss world-space sein: V-Bereich ~ Baumhoehe +if obs and obs[0].data.uv_layers.get("UVMap"): + d = obs[0].data.uv_layers["UVMap"].data + vs = [x.uv[1] for x in d] + h = max(v.co.z for v in obs[0].data.vertices) + print("UV world-space check: V-max=%.2f Baumhoehe=%.2f" % (max(vs), h)) + if max(vs) < h * 0.5: fails.append("uv_scale") + +m.unregister() +print("ERGEBNIS:", "ALLE TESTS BESTANDEN" if not fails else ("FEHLER: " + ", ".join(fails))) +sys.exit(1 if fails else 0)