From f5b528a87470140105b0fd8bdc551c5d21d2086f Mon Sep 17 00:00:00 2001 From: D4rkst3r Date: Mon, 3 Aug 2026 08:13:31 +0200 Subject: [PATCH] Rock Gen 2.15.0: Erzfels-Set (Punkt 5) + Smooth/dicht (Punkt 6) Punkt 5 - EIN neutrales Erzfels-Set fuer ALLE Erzsorten A-Kanal traegt die Erz-Maske: sie sagt nur WO Erz sitzt. WELCHES Erz (Kupfer/Eisen/Gold = Tint + Gluehen) macht die Material-Instanz in UE. Knopf "Erzfels-Set erzeugen (S + M)" liefert Rock_Ore_S_NN und Rock_Ore_M_NN und setzt alle Einstellungen danach zurueck. Regler: Erz-Anteil, Nugget-Groesse (Radius in m), Erz-Seed. Nuggets statt Noise: gestreute Zentren mit weichem Abfall. Ein Noise-Feld gaebe wolkige Schlieren, Erz sitzt aber in abgegrenzten Einschluessen. Die Zentren bevorzugen Fugen - Adern folgen Rissen, und das Gluehen liest sich in einer Vertiefung besser. Ohne Erz-Bake bleibt A auf 1.0 - wie im eingefrorenen Layout v2 und wie das bestehende 25er-Set es traegt. Punkt 6 vorgezogen, weil Punkt 5 davon abhaengt Die Maskenaufloesung haengt an der Vertex-Dichte. Bei den bisherigen ~150-Vert-Steinen war jedes dritte Vertex ein Nugget-Zentrum: im Render liefen die Nuggets zu grossen Schlieren zusammen statt einzelne Einschluesse zu bleiben. Bei ~1350 Verts liest es sich richtig. Deshalb gibt es jetzt "Smooth & dicht" (Unterteilung 5, Decimate 0.40/0.80, Auto-Smooth) und das Erz-Set stellt selbst darauf um. Gemessen ueber drei Seeds: 2708/2658/2716 Tris - im Zielfenster 1500-3000. Zusaetzlich ist die Nuggetzahl auf ein Zentrum je ~10 Verts gedeckelt. Nebenbei: _asset_name schrieb die Form-Bezeichnung pauschal gross, daraus wurde "Rock_ORE_S_01" statt des vereinbarten "Rock_Ore_S_01". Einzelne Buchstaben (Form A, B, C) werden weiter gross geschrieben, laengere Bezeichner behalten ihre Schreibweise. Gemessen: Erz-Anteil 0.08 -> 4 %, 0.18 -> 9 %, 0.35 -> 15 % der Oberflaeche, A 0.00..1.00, S bis 0.80 m / M ab 1.71 m, Maske ueberlebt den FBX-Roundtrip. Neu: tests/test_erz.py. 16 Tests bestehen. Co-Authored-By: Claude Opus 4.8 --- README.md | 36 +++- blender_manifest.toml | 2 +- dist/index.html | 6 +- dist/index.json | 8 +- dist/stylized_rock_generator-2.14.0.zip | Bin 26683 -> 0 bytes dist/stylized_rock_generator-2.15.0.zip | Bin 0 -> 29580 bytes stylized_rock_generator.py | 233 +++++++++++++++++++++++- tests/test_erz.py | 166 +++++++++++++++++ 8 files changed, 436 insertions(+), 15 deletions(-) delete mode 100644 dist/stylized_rock_generator-2.14.0.zip create mode 100644 dist/stylized_rock_generator-2.15.0.zip create mode 100644 tests/test_erz.py diff --git a/README.md b/README.md index 7dde1c5..488c4d7 100644 --- a/README.md +++ b/README.md @@ -78,7 +78,7 @@ Abgestimmt auf `A:\eco\EcoGame` (UE 5.8). Kurzfassung: | **R** | Cavity pur — 1 = offene Flaeche, 0 = tiefe Fuge (**ohne** Hoehen-Verlauf) | | **G** | Moos-/Schmutzmaske — 1 = hier waechst was (Oberseite + Fugennaehe + Noise) | | **B** | Hoehen-Verlauf — 0 = Unterseite, 1 = oben | - | **A** | 1 (Reserve) | + | **A** | Erz-Maske — 1 = Erz, 0 = kein Erz. Ohne Erz-Bake bleibt der Kanal auf 1.0 | Getrennt statt eingebacken: der erdige **Boden-Saum** am Stein-Fuss und die **Moosfarbe** werden pro Material-Instanz geregelt, nicht im Mesh festgelegt. @@ -118,6 +118,39 @@ Abgestimmt auf `A:\eco\EcoGame` (UE 5.8). Kurzfassung: --- +## Erzfels-Set (A-Kanal) + +**EIN neutrales Set fuer ALLE Erzsorten.** Die Maske im A-Kanal sagt nur, *wo* +Erz sitzt. *Welches* Erz es ist (Kupfer/Eisen/Gold = Tint + Gluehen), entscheidet +die Material-Instanz in UE. Knopf: **Erzfels-Set erzeugen (S + M)** erzeugt +`Rock_Ore_S_NN` und `Rock_Ore_M_NN` und stellt danach alle Einstellungen zurueck. + +Regler: `Erz-Anteil`, `Nugget-Groesse` (Radius in m), `Erz-Seed`. + +Nuggets statt Rauschen: gestreute Zentren mit weichem Abfall. Ein Noise-Feld +gaebe wolkige Schlieren ueber den ganzen Stein, Erz sitzt aber in abgegrenzten +Einschluessen. Die Zentren bevorzugen Fugen — Adern folgen Rissen, und das +Gluehen liest sich in einer Vertiefung besser als auf einer flachen Platte. + +> **Die Maskenaufloesung haengt an der Vertex-Dichte.** Bei den bisherigen +> ~150-Vert-Steinen war jedes dritte Vertex ein Nugget-Zentrum: die Nuggets +> liefen zu grossen Schlieren zusammen. Deshalb erzeugt das Erz-Set **dicht** +> (siehe unten) — dort liest es sich als einzelne Einschluesse. Die Nuggetzahl +> ist zusaetzlich auf ein Zentrum je ~10 Verts gedeckelt. + +Gemessen (dicht, ~2700 Tris): Erz-Anteil 0.08 → 4 %, 0.18 → 9 %, 0.35 → 15 % +der Oberflaeche. A faellt auf 0.00 und erreicht 1.00, und die Maske ueberlebt +den FBX-Roundtrip. + +## Smooth & dicht + +Knopf im Form-Panel: setzt Unterteilung 5, Decimate 0.40/0.80 und Auto-Smooth. +Gemessen ueber drei Seeds: **2708/2658/2716 Tris** (Schnitt 2694) — im +Zielfenster 1500–3000. Kein Low-Poly-Look, und alle Vertex-Masken (Cavity, Moos, +Erz) werden feiner. + +--- + ## Export-Haertung (Rock Gen) Drei Dinge passieren beim FBX-Export jetzt automatisch. Alles laeuft auf @@ -451,6 +484,7 @@ gefiltert `.\run_tests.ps1 -Filter panels`. | Datei | prueft | |---|---| | `tests/test_proportionen.py` | Baum-Proportionen: Palmenkrone, Strauch-Breite, Setzling-Schlankheit, Branch Count 0 | +| `tests/test_erz.py` | Erz-Maske: A=1.0 ohne Erz, Anteil monoton, Set-Namen/Groessen, Einstellungen zurueckgesetzt, FBX-Roundtrip, Tri-Fenster | | `tests/test_export_robust.py` | Export am **reimportierten** FBX: Ursprung erzwungen, flach = liegend, UCX geschlossen/umschliessend/im Budget, LOD+Vertex-Farben | | `tests/test_panels.py` | Alle vier Addons: registrieren, jedes Panel zeichnet, Unter-Panels im richtigen Tab, **jede Property irgendwo erreichbar** | | `tests/test_tree_addon.py` | Tree-Addon: Presets, Wachstums-Stufen, UVs, Modifier-Apply | diff --git a/blender_manifest.toml b/blender_manifest.toml index a811693..12a6276 100644 --- a/blender_manifest.toml +++ b/blender_manifest.toml @@ -1,7 +1,7 @@ schema_version = "1.0.0" id = "stylized_rock_generator" -version = "2.14.0" +version = "2.15.0" name = "Stylized Rock Generator" tagline = "Batch-Generator fuer stylized Rocks mit UE5-Export" maintainer = "D4rkst3r" diff --git a/dist/index.html b/dist/index.html index bd963b4..474edf9 100644 --- a/dist/index.html +++ b/dist/index.html @@ -40,14 +40,14 @@ 4.9KB - stylized_rock_generator-2.14.0 + stylized_rock_generator-2.15.0 Stylized Rock Generator Batch-Generator fuer stylized Rocks mit UE5-Export link 4.2.0 - ~ all all - 26.1KB + 28.9KB stylized_tree_generator-1.25.0 @@ -61,6 +61,6 @@ -

Built 2026-08-03, 06:02

+

Built 2026-08-03, 06:13

diff --git a/dist/index.json b/dist/index.json index adc6c38..d2d3988 100644 --- a/dist/index.json +++ b/dist/index.json @@ -57,7 +57,7 @@ "id": "stylized_rock_generator", "name": "Stylized Rock Generator", "tagline": "Batch-Generator fuer stylized Rocks mit UE5-Export", - "version": "2.14.0", + "version": "2.15.0", "type": "add-on", "maintainer": "D4rkst3r", "license": [ @@ -73,9 +73,9 @@ "Mesh", "Modeling" ], - "archive_url": "./stylized_rock_generator-2.14.0.zip", - "archive_size": 26683, - "archive_hash": "sha256:f60aa4d5c8d73cabfccbccf19020ea30d31e77ad22dca5f8a7151e46c7ec1c42" + "archive_url": "./stylized_rock_generator-2.15.0.zip", + "archive_size": 29580, + "archive_hash": "sha256:f57604a8176506626352d61e79a5a449f8d1aedced3aeed3a288a1abe5efcd51" }, { "schema_version": "1.0.0", diff --git a/dist/stylized_rock_generator-2.14.0.zip b/dist/stylized_rock_generator-2.14.0.zip deleted file mode 100644 index 6af1f84cf5ad2f92584f5fd91b942cc9aefcbd6c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 26683 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 diff --git a/dist/stylized_rock_generator-2.15.0.zip b/dist/stylized_rock_generator-2.15.0.zip new file mode 100644 index 0000000000000000000000000000000000000000..a1106a8ebdad7558ea892a166a7dc53963cb4897 GIT binary patch literal 29580 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 bgKGYNY)3^|aESjbg8b_~|HgNx|IGdi17_gt literal 0 HcmV?d00001 diff --git a/stylized_rock_generator.py b/stylized_rock_generator.py index b2f62df..d3b818b 100644 --- a/stylized_rock_generator.py +++ b/stylized_rock_generator.py @@ -1,7 +1,7 @@ bl_info = { "name": "Stylized Rock Generator", "author": "D4rkst3r", - "version": (2, 14, 0), + "version": (2, 15, 0), "blender": (4, 2, 0), "location": "View3D > Sidebar > Rock Gen", "description": ( @@ -48,6 +48,16 @@ MATERIAL_PALETTE = ( # die FORM innerhalb einer Klasse und wird von "keep_size" auf konstantes # Volumen normiert, damit sie sich nicht mit den ScaleMin/Max der Engine # multipliziert. +# "Smooth & dicht": kein Low-Poly-Look, und die Vertex-Masken (Cavity, Moos, +# Erz) haengen direkt an der Vertex-Dichte. Gemessen ueber drei Seeds: +# ~1350 Verts / ~2694 Tris - im Zielfenster 1500-3000 Tris. +DENSE_PRESET = { + "subsurf_levels": 5, + "decimate_ratio_1": 0.40, + "decimate_ratio_2": 0.80, + "shading_mode": 'AUTO', +} + CLASS_SIZES = {'S': 0.45, 'M': 1.0, 'L': 1.9, 'XL': 3.2} # Property-Keys, die von Presets gespeichert/geladen werden. @@ -117,9 +127,13 @@ def _apply_modifier(context, obj, mod_name): def _asset_name(settings, index, batch_count): """EcoGame-Schema: Fels_
_, bei Batches mit laufender Nummer.""" + # Einzelne Buchstaben werden gross geschrieben (Form A, B, C ...), laengere + # Bezeichner behalten ihre Schreibweise - sonst wuerde aus dem vereinbarten + # "Rock_Ore_S_01" ein "Rock_ORE_S_01". + form = settings.name_form.strip() or "A" + form = form.upper() if len(form) == 1 else form base = "%s_%s_%s" % (settings.name_prefix.strip() or "Fels", - (settings.name_form.strip() or "A").upper(), - settings.name_class) + form, settings.name_class) if settings.name_numbered and batch_count > 1: return "%s_%02d" % (base, index + 1) return base @@ -480,6 +494,85 @@ def _apply_random_transform(context, obj, settings, rng): bpy.ops.object.transform_apply(location=False, rotation=True, scale=True) +def _ore_mask(me, settings): + """Erz-Maske je VERTEX: gestreute Nuggets mit weichem Abfall. + + Warum Nuggets und kein Noise: ein Noise-Feld ergibt wolkige Schlieren ueber + den ganzen Stein. Erz sitzt aber in abgegrenzten Einschluessen. Deshalb + werden Zentren gestreut und der Abfall darum herum gerechnet - dadurch sind + Menge und Korngroesse unabhaengig voneinander regelbar. + + Die Zentren bevorzugen Fugen (niedrige Cavity): Adern folgen Rissen, und + das Gluehen liest sich in einer Vertiefung deutlich besser als auf einer + flachen Platte. + """ + import random as _rnd + from mathutils import kdtree + + n_verts = len(me.vertices) + if n_verts == 0: + return None + + radius = max(settings.ore_nugget_size, 1e-4) + + # Anzahl aus der Oberflaeche ableiten, nicht fest: sonst haette ein + # S-Brocken dieselbe Nuggetzahl wie ein M-Brocken und waere uebersaet. + area = sum(p.area for p in me.polygons) + pro_nugget = math.pi * radius * radius + n_nuggets = int(round(settings.ore_amount * area / max(pro_nugget, 1e-6))) + + # Deckel an der AUFLOESUNG, nicht nur an einer festen Zahl: die Maske haengt + # an der Vertex-Dichte. Bei einem 150-Vert-Stein waren 46 Zentren jeder + # dritte Vertex - die Nuggets liefen zu grossen Schlieren zusammen statt + # einzelne Einschluesse zu bleiben. Ein Zentrum je ~10 Verts haelt sie + # getrennt. Wer feinere Nuggets will, braucht ein dichteres Mesh. + n_nuggets = max(1, min(n_nuggets, 96, max(1, n_verts // 10))) + + # Fugen-Gewicht je Vertex aus der bereits gebackenen Cavity im R-Kanal. + layer = me.color_attributes.get("Cavity") + gewicht = [1.0] * n_verts + if layer is not None: + summe = [0.0] * n_verts + zahl = [0] * n_verts + for li, loop in enumerate(me.loops): + vi = loop.vertex_index + summe[vi] += layer.data[li].color[0] + zahl[vi] += 1 + for vi in range(n_verts): + if zahl[vi]: + # 1 - Cavity -> in der Fuge gross + gewicht[vi] = 0.25 + 0.75 * (1.0 - summe[vi] / zahl[vi]) + + rng = _rnd.Random(settings.ore_seed) + gesamt = sum(gewicht) or 1.0 + zentren = [] + for _ in range(n_nuggets): + # gewichtetes Ziehen ohne cumsum-Tabelle (Meshes sind klein) + ziel = rng.random() * gesamt + lauf = 0.0 + pick = n_verts - 1 + for vi in range(n_verts): + lauf += gewicht[vi] + if lauf >= ziel: + pick = vi + break + zentren.append(me.vertices[pick].co.copy()) + + baum = kdtree.KDTree(len(zentren)) + for i, co in enumerate(zentren): + baum.insert(co, i) + baum.balance() + + maske = [0.0] * n_verts + for vi, v in enumerate(me.vertices): + _, _, dist = baum.find(v.co) + if dist is None or dist >= radius: + continue + t = 1.0 - dist / radius + maske[vi] = t * t * (3.0 - 2.0 * t) # Smoothstep: weiche Raender + return maske + + def _bake_vertex_cavity(context, obj, settings): """Vertex-Farb-Layout v2 - KANAL-GETRENNT im Attribut "Cavity". @@ -536,6 +629,17 @@ def _bake_vertex_cavity(context, obj, settings): moss = settings.moss_amount mnoise = settings.moss_noise + # --- A: Erz-Maske (Nuggets) --------------------------------------------- + # EIN neutrales Set fuer ALLE Erzsorten: die Maske sagt nur WO Erz sitzt. + # WELCHES Erz (Kupfer/Eisen/Gold = Tint + Gluehen) entscheidet die + # Material-Instanz in UE. + # + # Nuggets statt Rauschen: gestreute Zentren mit weichem Abfall. Ein + # Noise-Feld gaebe wolkige Schlieren, keine Einschluesse. + ore_a = None + if getattr(settings, "bake_ore", False) and settings.ore_amount > 0.0: + ore_a = _ore_mask(me, settings) + for poly in me.polygons: nz = poly.normal.z # 1 = zeigt nach oben for li in poly.loop_indices: @@ -555,7 +659,12 @@ def _bake_vertex_cavity(context, obj, settings): n = 1.0 - mnoise + mnoise * vnoise[vi] g = max(0.0, min(1.0, base * n * moss * 1.6)) - data[li].color = (r, g, b, 1.0) + # A: Erz-Maske. Ohne Erz bleibt der Kanal auf 1.0 - so wie im + # eingefrorenen Layout v2 festgelegt und wie das bestehende + # 25er-Set es bereits traegt. + a = 1.0 if ore_a is None else ore_a[vi] + + data[li].color = (r, g, b, a) def _apply_shading(context, obj, settings): @@ -877,6 +986,27 @@ class RockGenSettings(PropertyGroup): name="Weichzeichnen", default=1, min=0, max=5, description="Glaettet den Cavity-Verlauf ueber benachbarte Vertices", ) + # --- Erz (A-Kanal) --- + bake_ore: BoolProperty( + name="Erz-Maske (A-Kanal)", default=False, + description=("Schreibt Erz-Nuggets in den A-Kanal. EIN neutrales Set fuer " + "ALLE Erzsorten - welches Erz es ist (Tint + Gluehen), " + "entscheidet die Material-Instanz in UE. Aus = A bleibt 1.0"), + ) + ore_amount: FloatProperty( + name="Erz-Anteil", default=0.18, min=0.0, max=1.0, + description=("Wie viel der Oberflaeche Erz traegt. Die Nuggetzahl wird aus " + "der Flaeche abgeleitet, damit ein S-Brocken nicht uebersaet ist"), + ) + ore_nugget_size: FloatProperty( + name="Nugget-Groesse", default=0.10, min=0.01, max=1.0, unit='LENGTH', + description="Radius eines einzelnen Erz-Einschlusses in Metern", + ) + ore_seed: IntProperty( + name="Erz-Seed", default=0, min=0, max=9999, + description="Andere Verteilung der Nuggets bei gleicher Form", + ) + part_mask_from_shells: BoolProperty( name="G = Teil-Maske (Shells)", default=False, description=("Statt der Moos-Maske: getrennte Mesh-Teile markieren. " @@ -1435,6 +1565,84 @@ def _safe_filename(name): return "".join(c for c in name if c.isalnum() or c in keep).strip() or "Rock" +class ROCKGEN_OT_dense_preset(Operator): + """Stellt auf smooth und dicht um (~2700 Tris statt ~620).""" + bl_idname = "rockgen.dense_preset" + bl_label = "Smooth & dicht" + bl_description = ("Setzt Unterteilung, Decimate und Shading auf den dichten " + "Smooth-Look (~1500-3000 Tris). Feinere Cavity- und " + "Erz-Aufloesung, kein Low-Poly-Look") + bl_options = {'REGISTER', 'UNDO'} + + def execute(self, context): + s = context.scene.rock_gen_settings + for k, v in DENSE_PRESET.items(): + setattr(s, k, v) + self.report({'INFO'}, "Smooth & dicht: Unterteilung %d, Decimate %.2f/%.2f, " + "Shading %s" % (DENSE_PRESET["subsurf_levels"], + DENSE_PRESET["decimate_ratio_1"], + DENSE_PRESET["decimate_ratio_2"], + DENSE_PRESET["shading_mode"])) + return {'FINISHED'} + + +class ROCKGEN_OT_ore_set(Operator): + """Erzeugt das Erzfels-Set: EIN neutrales Set in S und M. + + Bewusst kein Set je Erzsorte - die A-Kanal-Maske sagt nur, WO Erz sitzt. + Welches Erz es ist (Kupfer/Eisen/Gold = Tint + Gluehen), entscheidet die + Material-Instanz in UE. S und M, weil die Brocken in Hoehlengaenge passen + muessen. + """ + bl_idname = "rockgen.ore_set" + bl_label = "Erzfels-Set erzeugen (S + M)" + bl_description = ("Erzeugt Rock_Ore_S_NN und Rock_Ore_M_NN mit Erz-Maske im " + "A-Kanal - ein Set fuer alle Erzsorten") + bl_options = {'REGISTER', 'UNDO'} + + def execute(self, context): + s = context.scene.rock_gen_settings + vorher = { + "name_prefix": s.name_prefix, "name_form": s.name_form, + "name_class": s.name_class, "name_numbered": s.name_numbered, + "bake_ore": s.bake_ore, "bake_vertex_cavity": s.bake_vertex_cavity, + "use_class_size": s.use_class_size, "seed": s.seed, + } + vorher.update({k: getattr(s, k) for k in DENSE_PRESET}) + # Erz MUSS dicht: die Maske haengt an der Vertex-Dichte. Bei ~150 Verts + # sind die Nugget-Zentren jeder dritte Vertex und laufen zu Schlieren + # zusammen - gemessen und im Render bestaetigt. Bei ~1350 Verts liest es + # sich als einzelne Einschluesse. + for k, v in DENSE_PRESET.items(): + setattr(s, k, v) + s.name_prefix = "Rock" + s.name_form = "Ore" + s.name_numbered = True + s.bake_ore = True + s.bake_vertex_cavity = True # A haengt am selben Attribut + s.use_class_size = True # S und M sollen sich in der Groesse unterscheiden + + gesamt = 0 + try: + for i, klasse in enumerate(("S", "M")): + s.name_class = klasse + # Anderer Seed je Klasse, sonst ist der M-Brocken nur ein + # aufgeblasener S-Brocken. + s.seed = vorher["seed"] + i * 4177 + res = bpy.ops.object.generate_rocks_batch() + if 'FINISHED' not in res: + self.report({'WARNING'}, "Klasse %s abgebrochen." % klasse) + break + gesamt += s.batch_count + finally: + for k, v in vorher.items(): + setattr(s, k, v) + + self.report({'INFO'}, "Erzfels-Set: %d Brocken (Rock_Ore_S/M_NN), " + "Erz im A-Kanal." % gesamt) + return {'FINISHED'} + + class OBJECT_OT_export_rocks_fbx(Operator): bl_idname = "object.export_rocks_fbx" bl_label = "FBX exportieren" @@ -2098,6 +2306,7 @@ class VIEW3D_PT_rock_shape(_RockSub): layout.prop(s, "box_proportions") layout.prop(s, "box_variation") layout.prop(s, "subsurf_levels") + layout.operator("rockgen.dense_preset", icon='MOD_SMOOTH') layout.separator() layout.prop(s, "texture_type") col = layout.column(align=True) @@ -2171,8 +2380,18 @@ class VIEW3D_PT_rock_vcol(_RockSub): sub.prop(s, "moss_noise") sub.prop(s, "moss_seed") col.prop(s, "part_mask_from_shells") - col.label(text="R=Cavity G=%s B=Hoehe" - % ("Teil-Maske" if s.part_mask_from_shells else "Moos"), + col.separator() + col.prop(s, "bake_ore") + sub = col.column(align=True) + sub.active = s.bake_ore + sub.prop(s, "ore_amount") + sub.prop(s, "ore_nugget_size") + sub.prop(s, "ore_seed") + sub.separator() + sub.operator("rockgen.ore_set", icon='MOD_EXPLODE') + col.label(text="R=Cavity G=%s B=Hoehe A=%s" + % ("Teil-Maske" if s.part_mask_from_shells else "Moos", + "Erz" if s.bake_ore else "1.0"), icon='INFO') # Nicht in der ausgegrauten Spalte: der Knopf arbeitet auf der Auswahl # und ist auch dann sinnvoll, wenn beim Erzeugen nicht gebacken wird. @@ -2283,6 +2502,8 @@ classes = ( OBJECT_OT_export_rocks_fbx, OBJECT_OT_bake_rock_textures, ROCKGEN_OT_bake_cavity_selection, + ROCKGEN_OT_dense_preset, + ROCKGEN_OT_ore_set, ROCKGEN_OT_randomize, ROCKGEN_OT_preset_save, ROCKGEN_OT_preset_load, diff --git a/tests/test_erz.py b/tests/test_erz.py new file mode 100644 index 0000000..11132a6 --- /dev/null +++ b/tests/test_erz.py @@ -0,0 +1,166 @@ +"""Erzfels-Set (Wunschliste Punkt 5) + Smooth/dicht (Punkt 6). + +Kernaussagen, die hier festgenagelt werden: +* EIN neutrales Set - die A-Maske sagt nur WO Erz sitzt, nicht welches. +* Ohne Erz bleibt A auf 1.0 (Layout v2, wie das bestehende 25er-Set). +* Die Maske ueberlebt den FBX-Roundtrip. +* Die Aufloesung der Maske haengt an der Vertex-Dichte - deshalb erzeugt das + Erz-Set dicht. Bei ~150 Verts waren die Nugget-Zentren jeder dritte Vertex + und liefen zu Schlieren zusammen statt einzelne Einschluesse zu bleiben. + +Aufruf: blender --background --factory-startup --python tests/test_erz.py +""" +import os +import sys +import tempfile +import importlib.util + +import bpy + +ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +spec = importlib.util.spec_from_file_location( + "rg", os.path.join(ROOT, "stylized_rock_generator.py")) +rg = importlib.util.module_from_spec(spec) +sys.modules["rg"] = rg +spec.loader.exec_module(rg) +rg.register() + +fails = [] +for o in list(bpy.data.objects): + bpy.data.objects.remove(o, do_unlink=True) + +s = bpy.context.scene.rock_gen_settings +s.use_modal = False +s.assign_material = False +s.generate_lods = False +s.bake_vertex_cavity = True + + +def einer(**kw): + for o in list(bpy.data.objects): + bpy.data.objects.remove(o, do_unlink=True) + s.batch_count = 1 + for k, v in kw.items(): + setattr(s, k, v) + bpy.ops.object.generate_rocks_batch() + ob = [o for o in bpy.context.scene.objects if o.type == 'MESH'][0] + a = [c.color[3] for c in ob.data.color_attributes["Cavity"].data] + tris = sum(len(p.vertices) - 2 for p in ob.data.polygons) + return ob, a, tris + + +# --- 1) Ohne Erz bleibt A auf 1.0 ---------------------------------------- +_, a, _ = einer(bake_ore=False, subsurf_levels=4) +if min(a) < 0.999: + fails.append("Ohne Erz ist A nicht 1.0 (min %.3f) - das bricht das " + "eingefrorene Layout v2 und das bestehende 25er-Set" % min(a)) + +# --- 2) Mit Erz: Maske vorhanden, Anteil steigt monoton ------------------- +print("%-24s %7s %7s %9s %7s" % ("Variante", "A min", "A max", "A>0.5", "Tris")) +anteile = [] +for amt in (0.08, 0.18, 0.35): + ob, a, tris = einer(bake_ore=True, ore_amount=amt, ore_nugget_size=0.10, + subsurf_levels=5, decimate_ratio_1=0.40, + decimate_ratio_2=0.80) + anteil = sum(1 for x in a if x > 0.5) / len(a) + anteile.append(anteil) + print("%-24s %7.2f %7.2f %8.0f%% %7d" + % ("Erz-Anteil %.2f" % amt, min(a), max(a), 100 * anteil, tris)) + if max(a) < 0.9: + fails.append("Erz-Anteil %.2f: A erreicht nur %.2f - keine Nuggets" + % (amt, max(a))) + if min(a) > 0.01: + fails.append("Erz-Anteil %.2f: A faellt nie auf 0 - der ganze Stein " + "waere Erz" % amt) +if not (anteile[0] < anteile[1] < anteile[2]): + fails.append("Erz-Anteil wirkt nicht monoton: %s" + % ", ".join("%.0f%%" % (100 * x) for x in anteile)) + +# --- 3) Smooth & dicht landet im Tri-Fenster 1500-3000 ------------------- +bpy.ops.rockgen.dense_preset() +tris_liste = [] +for seed in (1, 14, 27): + _, _, tris = einer(seed=seed, bake_ore=False) + tris_liste.append(tris) +schnitt = sum(tris_liste) // len(tris_liste) +print("") +print("Smooth & dicht: %s Tris (Schnitt %d)" + % ("/".join(str(t) for t in tris_liste), schnitt)) +if not (1500 <= schnitt <= 3000): + fails.append("Smooth & dicht liefert %d Tris, Zielfenster 1500-3000" % schnitt) +if s.shading_mode != 'AUTO': + fails.append("Smooth & dicht setzt kein Auto-Smooth (shading_mode=%s)" + % s.shading_mode) + +# --- 4) Das Set: Namen, Groessen, Einstellungen wieder wie vorher --------- +for o in list(bpy.data.objects): + bpy.data.objects.remove(o, do_unlink=True) +s.batch_count = 2 +s.subsurf_levels = 4 # absichtlich duenn: das Set muss selbst dicht stellen +s.name_prefix = "Fels" +s.name_form = "A" +s.name_class = 'M' +s.bake_ore = False +merker = (s.name_prefix, s.name_form, s.name_class, s.bake_ore, s.subsurf_levels) + +OUT = tempfile.mkdtemp() +s.export_dir = OUT +bpy.ops.rockgen.ore_set() + +namen = sorted(o.name for o in bpy.context.scene.objects if o.type == 'MESH') +erwartet = ["Rock_Ore_M_01", "Rock_Ore_M_02", "Rock_Ore_S_01", "Rock_Ore_S_02"] +print("") +print("Set: %s" % ", ".join(namen)) +if namen != erwartet: + fails.append("Set-Namen %s, erwartet %s" % (namen, erwartet)) + +hoehen = {} +for ob in bpy.context.scene.objects: + if ob.type != 'MESH': + continue + co = [ob.matrix_world @ v.co for v in ob.data.vertices] + hoehen[ob.name] = max(c.z for c in co) - min(c.z for c in co) + if len(ob.data.vertices) < 800: + fails.append("%s hat nur %d Verts - zu grob fuer eine Nugget-Maske" + % (ob.name, len(ob.data.vertices))) + a = [c.color[3] for c in ob.data.color_attributes["Cavity"].data] + if max(a) < 0.9: + fails.append("%s traegt keine Erz-Maske (A max %.2f)" % (ob.name, max(a))) + +s_hoch = max(v for k, v in hoehen.items() if "_S_" in k) +m_hoch = min(v for k, v in hoehen.items() if "_M_" in k) +print("Groessen: S bis %.2f m, M ab %.2f m" % (s_hoch, m_hoch)) +if s_hoch >= m_hoch: + fails.append("S (%.2f m) ist nicht kleiner als M (%.2f m) - die Brocken " + "muessen in Hoehlengaenge passen" % (s_hoch, m_hoch)) + +if (s.name_prefix, s.name_form, s.name_class, s.bake_ore, s.subsurf_levels) != merker: + fails.append("Das Set hat die Einstellungen nicht zurueckgesetzt: %s" + % str((s.name_prefix, s.name_form, s.name_class, s.bake_ore, + s.subsurf_levels))) + +# --- 5) A ueberlebt den FBX-Roundtrip ------------------------------------ +bpy.ops.object.export_rocks_fbx() +dateien = sorted(f for f in os.listdir(OUT) if f.lower().endswith(".fbx")) +if not dateien: + fails.append("Erz-Set: kein FBX exportiert") +else: + bpy.ops.wm.read_factory_settings(use_empty=True) + bpy.ops.import_scene.fbx(filepath=os.path.join(OUT, dateien[0])) + ob = [o for o in bpy.context.scene.objects + if o.type == 'MESH' and not o.name.startswith("UCX_")][0] + a = [c.color[3] for c in ob.data.color_attributes[0].data] + anteil = sum(1 for x in a if x > 0.5) / len(a) + print("Nach FBX (%s): A %.2f..%.2f, Erz auf %.0f%%" + % (dateien[0], min(a), max(a), 100 * anteil)) + if max(a) < 0.9 or min(a) > 0.01: + fails.append("Erz-Maske ueberlebt den FBX-Roundtrip nicht " + "(A %.2f..%.2f)" % (min(a), max(a))) + +print("") +if fails: + print("ERGEBNIS: %d FEHLER" % len(fails)) + for x in fails: + print(" - " + x) + sys.exit(1) +print("ERGEBNIS: ALLE CHECKS OK")