Rock Gen 2.19.0: Schnellstart-Sorten + Dissolve als Bereich
Weniger einstellen muessen: oben im Panel Sorte waehlen, Haken druecken, erzeugen. Die neun Unter-Panels bleiben fuer den Feinschliff. Findling rund, weiche Kanten ~1160 V / ~2320 Tris Platte flach, liegt satt auf ~670 V / ~1340 Tris Kantig Bevel + Weighted Normal ~350 V / ~690 Tris Erzbrocken dicht genug fuer die Maske ~1280 V / ~2560 Tris, Slot M_Rock_Ore dissolve_angle ist jetzt ein BEREICH statt eines festen Werts - direkt aus der Rueckmeldung "hab ich immer auf 17-25 je nach Stein". Je Stein wird einer gezogen; gemessen an sechs Findlingen bei 17-25 Grad: 1936-2928 Tris. Beide Regler gleich setzen = fester Winkel wie frueher. Zwei Sachen fielen beim Messen auf: 1) Der Dissolve-Winkel frisst deutlich Geometrie. Derselbe dichte Satz liefert bei niedrigem Standardwinkel ~2700 Tris, bei 17-25 Grad nur noch ~1030 und faellt damit unter ORE_MIN_VERTS - das Erz-Set haette seine EIGENE Warnung ausgeloest, sobald vorher eine Sorte mit hohem Winkel gewaehlt war. Der Bereich gehoert deshalb mit in DENSE_PRESET. Ueber vier Seeds und beide Groessenklassen nachgemessen: 0.70/0.92 haelt den Worst Case bei 847 Verts. 2) Erz-Dichte und Tri-Fenster kollidieren. Die Maske braucht ~800 Verts auch beim kleinen S-Brocken, das treibt einen M-Stein auf ~3400 Tris - ueber das Fenster 1500-3000. Deshalb entkoppelt: normale Sorten leichter (0.40/0.80), Erzbrocken knapper (0.45/0.82), das S+M-SET dichter (0.70/0.92), weil dort die kleinen Brocken die Untergrenze setzen. Nebenbei gefunden und behoben: der erzwungene Ursprung wurde aus der GEMEINSAMEN Bbox aller LOD-Stufen berechnet. Eine stark dezimierte Stufe kann tiefer reichen als LOD0 - dann sass LOD0 sieben Zentimeter ueber dem Pivot und der Stein haette in UE geschwebt. Unreal platziert nach LOD0, also gibt LOD0 jetzt den Ursprung vor. Neu: tests/test_schnellstart.py. 18 Tests bestehen. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -127,6 +127,31 @@ Abgestimmt auf `A:\eco\EcoGame` (UE 5.8). Kurzfassung:
|
||||
|
||||
---
|
||||
|
||||
## Schnellstart-Sorten
|
||||
|
||||
Ganz oben im Panel: Sorte waehlen, Haken druecken, erzeugen. Deckt den Alltag
|
||||
ab — die neun Unter-Panels bleiben fuer den Feinschliff.
|
||||
|
||||
| Sorte | Form | Dissolve | gemessen |
|
||||
|---|---|---|---|
|
||||
| **Findling** | rund, weiche Kanten | 17–25° | ~1160 V / ~2320 Tris |
|
||||
| **Platte** | flach, liegt satt auf | 17–25° | ~670 V / ~1340 Tris |
|
||||
| **Kantig** | Bevel + Weighted Normal, Flat | 8–14° | ~350 V / ~690 Tris |
|
||||
| **Erzbrocken** | dicht genug fuer die Nugget-Maske, Slot `M_Rock_Ore` | 17–25° | ~1280 V / ~2560 Tris |
|
||||
|
||||
**Der Dissolve-Winkel ist ein Bereich, kein Wert.** Ein fester Winkel passt nie
|
||||
fuer alle Steine einer Sorte — man stellt ihn sonst bei jedem Stein nach. Je
|
||||
Stein wird einer aus dem Bereich gezogen; gemessen an sechs Findlingen bei
|
||||
17–25°: 1936–2928 Tris. Beide Regler gleich setzen = fester Winkel wie frueher.
|
||||
|
||||
> **Warum die Sorten unterschiedlich dicht sind.** Die Erz-Maske haengt an der
|
||||
> Vertex-Dichte, ein normaler Stein braucht das nicht. Deshalb hat der
|
||||
> Erzbrocken ein hoeheres Decimate als der Findling — sonst waere entweder das
|
||||
> Erz verschmiert oder der normale Stein ueber dem Tri-Fenster. Der Satz hinter
|
||||
> dem **S+M-Set** ist noch etwas dichter (0.70/0.92), weil dort auch kleine
|
||||
> S-Brocken vorkommen: mit dem Findling-Satz fielen die im schlechtesten Fall
|
||||
> auf 708 Verts und damit unter die Schwelle fuer eine saubere Maske.
|
||||
|
||||
## Erzfels-Set (A-Kanal)
|
||||
|
||||
**EIN neutrales Set fuer ALLE Erzsorten.** Die Maske im A-Kanal sagt nur, *wo*
|
||||
@@ -549,6 +574,7 @@ gefiltert `.\run_tests.ps1 -Filter panels`.
|
||||
|---|---|
|
||||
| `tests/test_proportionen.py` | Baum-Proportionen: Palmenkrone, Strauch-Breite, Setzling-Schlankheit, Branch Count 0 |
|
||||
| `tests/test_bruchstuecke.py` | Bruchstuecke: Volumen 100 % des Muttersteins, dichte Topologie, genau ein Erz-Stueck, Material-Slots, Ursprung, UCX je Teil |
|
||||
| `tests/test_schnellstart.py` | Schnellstart: jede Sorte liefert ohne Nacharbeit Geometrie + Slot, Erz-Sorte ueber der Dichte-Schwelle, Dissolve-Bereich streut je Stein |
|
||||
| `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** |
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
schema_version = "1.0.0"
|
||||
|
||||
id = "stylized_rock_generator"
|
||||
version = "2.18.0"
|
||||
version = "2.19.0"
|
||||
name = "Stylized Rock Generator"
|
||||
tagline = "Batch-Generator fuer stylized Rocks mit UE5-Export"
|
||||
maintainer = "D4rkst3r"
|
||||
|
||||
Vendored
+3
-3
@@ -40,14 +40,14 @@
|
||||
<td>4.9KB</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><tt><a href="./stylized_rock_generator-2.18.0.zip?repository=.%2Findex.json&blender_version_min=4.2.0">stylized_rock_generator-2.18.0</a></tt></td>
|
||||
<td><tt><a href="./stylized_rock_generator-2.19.0.zip?repository=.%2Findex.json&blender_version_min=4.2.0">stylized_rock_generator-2.19.0</a></tt></td>
|
||||
<td>Stylized Rock Generator</td>
|
||||
<td>Batch-Generator fuer stylized Rocks mit UE5-Export</td>
|
||||
<td><a href="https://git.d4rkst3r.de/D4rkst3r/stylized-rock-generator">link</a></td>
|
||||
<td>4.2.0 - ~</td>
|
||||
<td>all</td>
|
||||
<td>all</td>
|
||||
<td>32.2KB</td>
|
||||
<td>34.1KB</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><tt><a href="./stylized_tree_generator-1.25.0.zip?repository=.%2Findex.json&blender_version_min=4.2.0">stylized_tree_generator-1.25.0</a></tt></td>
|
||||
@@ -61,6 +61,6 @@
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<center><p>Built 2026-08-03, 12:08</p></center>
|
||||
<center><p>Built 2026-08-03, 12:20</p></center>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Vendored
+4
-4
@@ -57,7 +57,7 @@
|
||||
"id": "stylized_rock_generator",
|
||||
"name": "Stylized Rock Generator",
|
||||
"tagline": "Batch-Generator fuer stylized Rocks mit UE5-Export",
|
||||
"version": "2.18.0",
|
||||
"version": "2.19.0",
|
||||
"type": "add-on",
|
||||
"maintainer": "D4rkst3r",
|
||||
"license": [
|
||||
@@ -73,9 +73,9 @@
|
||||
"Mesh",
|
||||
"Modeling"
|
||||
],
|
||||
"archive_url": "./stylized_rock_generator-2.18.0.zip",
|
||||
"archive_size": 33014,
|
||||
"archive_hash": "sha256:c690c130cff1bfd69f91c4d9685ba9fcb56b017cfc1b1211cbd2df91d15ba001"
|
||||
"archive_url": "./stylized_rock_generator-2.19.0.zip",
|
||||
"archive_size": 34968,
|
||||
"archive_hash": "sha256:2b412f9324263a3951b1b519648ab46025e7511b09f54b6017edf6d834a17bc6"
|
||||
},
|
||||
{
|
||||
"schema_version": "1.0.0",
|
||||
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
+143
-8
@@ -1,7 +1,7 @@
|
||||
bl_info = {
|
||||
"name": "Stylized Rock Generator",
|
||||
"author": "D4rkst3r",
|
||||
"version": (2, 18, 0),
|
||||
"version": (2, 19, 0),
|
||||
"blender": (4, 2, 0),
|
||||
"location": "View3D > Sidebar > Rock Gen",
|
||||
"description": (
|
||||
@@ -51,6 +51,76 @@ MATERIAL_PALETTE = (
|
||||
("M_Rock_Mossy", (0.24, 0.30, 0.18, 1.0)),
|
||||
)
|
||||
|
||||
# Schnellstart: fertige Einstellungs-Saetze fuer die Steinsorten, die in
|
||||
# EcoGame tatsaechlich vorkommen. Deckt den Alltag ab, ohne dass man sich durch
|
||||
# neun Unter-Panels klickt - die bleiben fuer den Feinschliff.
|
||||
#
|
||||
# Der Limited-Dissolve-Winkel ist bewusst ein BEREICH: ein fester Wert passt nie
|
||||
# fuer alle Steine einer Sorte, man stellt ihn sonst bei jedem Stein nach.
|
||||
ROCK_KINDS = {
|
||||
'FINDLING': {
|
||||
"label": "Findling",
|
||||
"desc": "Runder Brocken, weiche Kanten - der Allerwelts-Stein",
|
||||
"values": {
|
||||
"base_shape": 'ICOSPHERE', "subsurf_levels": 6,
|
||||
# Leichter als der Erz-Satz: ein normaler Stein braucht die hohe
|
||||
# Dichte nicht und bleibt so im Tri-Fenster 1500-3000. Nur die
|
||||
# Erz-Maske haengt an der Vertex-Dichte.
|
||||
"decimate_ratio_1": 0.40, "decimate_ratio_2": 0.80,
|
||||
"dissolve_angle": 17.0, "dissolve_angle_max": 25.0,
|
||||
"shading_mode": 'AUTO', "add_bevel": False,
|
||||
"flatten_bottom": True, "randomize_shape": True,
|
||||
},
|
||||
},
|
||||
'PLATTE': {
|
||||
"label": "Platte",
|
||||
"desc": "Flacher Brocken, liegt satt auf dem Boden",
|
||||
"values": {
|
||||
"base_shape": 'BOX', "box_proportions": (2.2, 1.6, 0.5),
|
||||
"box_variation": 0.25, "subsurf_levels": 6,
|
||||
# Hoeher als bei den runden Sorten: eine flache Grundform hat
|
||||
# weniger Oberflaeche, mit 0.40/0.80 landete sie bei 930 Tris.
|
||||
"decimate_ratio_1": 0.60, "decimate_ratio_2": 0.90,
|
||||
# Flache Flaechen verschmelzen beim Dissolve staerker als gewoelbte:
|
||||
# mit 20-28 Grad fiel die Platte auf 848 Tris. Gleicher Bereich wie
|
||||
# die anderen Sorten haelt sie im Fenster.
|
||||
"dissolve_angle": 17.0, "dissolve_angle_max": 25.0,
|
||||
"shading_mode": 'AUTO', "add_bevel": False,
|
||||
"flatten_bottom": True, "randomize_shape": True,
|
||||
},
|
||||
},
|
||||
'KANTIG': {
|
||||
"label": "Kantig",
|
||||
"desc": "Gebrochener Fels mit harten Kanten (Bevel + Weighted Normal)",
|
||||
"values": {
|
||||
"base_shape": 'CUBE', "subsurf_levels": 4,
|
||||
"decimate_ratio_1": 0.35, "decimate_ratio_2": 0.70,
|
||||
"dissolve_angle": 8.0, "dissolve_angle_max": 14.0,
|
||||
"shading_mode": 'FLAT', "add_bevel": True,
|
||||
"add_weighted_normal": True,
|
||||
"flatten_bottom": True, "randomize_shape": True,
|
||||
},
|
||||
},
|
||||
'ERZ': {
|
||||
"label": "Erzbrocken",
|
||||
"desc": ("Dicht genug fuer eine saubere Erz-Maske, Slot M_Rock_Ore. "
|
||||
"Fuer das komplette S+M-Set den Knopf im Vertex-Farben-Panel"),
|
||||
"values": {
|
||||
"base_shape": 'ICOSPHERE', "subsurf_levels": 6,
|
||||
# Knapper als der Satz hinter dem S+M-SET: der einzelne Brocken hier
|
||||
# ist M-gross, das Set enthaelt auch kleine S-Brocken und braucht
|
||||
# dort mehr Reserve, um ORE_MIN_VERTS zu halten. Gemessen ueber
|
||||
# fuenf Seeds: min 1078 Verts, max 2368 Tris.
|
||||
"decimate_ratio_1": 0.45, "decimate_ratio_2": 0.82,
|
||||
"dissolve_angle": 17.0, "dissolve_angle_max": 25.0,
|
||||
"shading_mode": 'AUTO', "add_bevel": False,
|
||||
"flatten_bottom": True, "randomize_shape": True,
|
||||
"bake_vertex_cavity": True, "bake_ore": True,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
# Basis-Groesse je Groessen-Klasse (Meter-Faktor auf das Grundmesh).
|
||||
# WICHTIG - Unterschied zur Zufalls-Streuung: DIESE Groesse ist gewollt und
|
||||
# unterscheidet die Klassen. Die Streuung (scale_min/max) variiert dagegen nur
|
||||
@@ -60,10 +130,21 @@ MATERIAL_PALETTE = (
|
||||
# "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.
|
||||
# Der Dissolve-Winkel gehoert MIT hier hinein. Er frisst deutlich Geometrie:
|
||||
# derselbe Satz liefert bei niedrigem Standardwinkel ~2700 Tris, bei 17-25 Grad
|
||||
# nur noch ~1030 und faellt damit unter ORE_MIN_VERTS - das Erz-Set haette seine
|
||||
# eigene Warnung ausgeloest, sobald jemand vorher eine Sorte mit hohem Winkel
|
||||
# gewaehlt hatte. Gemessen ueber drei Seeds bei 17-25 Grad: 1162 Verts / 2320 Tris.
|
||||
# Decimate bewusst hoch: ueber vier Seeds und beide Groessenklassen gemessen
|
||||
# liefert 0.40/0.80 im schlechtesten Fall nur 708 Verts und faellt damit unter
|
||||
# ORE_MIN_VERTS. 0.70/0.92 haelt den Worst Case bei 847 Verts und bleibt mit
|
||||
# maximal 2806 Tris im Zielfenster.
|
||||
DENSE_PRESET = {
|
||||
"subsurf_levels": 5,
|
||||
"decimate_ratio_1": 0.40,
|
||||
"decimate_ratio_2": 0.80,
|
||||
"subsurf_levels": 6,
|
||||
"decimate_ratio_1": 0.70,
|
||||
"decimate_ratio_2": 0.92,
|
||||
"dissolve_angle": 17.0,
|
||||
"dissolve_angle_max": 25.0,
|
||||
"shading_mode": 'AUTO',
|
||||
}
|
||||
|
||||
@@ -432,7 +513,12 @@ def _build_rock_mesh(context, settings, index, location, rng, name=None):
|
||||
context.view_layer.objects.active = obj
|
||||
bpy.ops.object.mode_set(mode='EDIT')
|
||||
bpy.ops.mesh.select_all(action='SELECT')
|
||||
bpy.ops.mesh.dissolve_limited(angle_limit=math.radians(settings.dissolve_angle))
|
||||
# Der Winkel wird je Stein aus dem Bereich gezogen. Ein fester Wert passt
|
||||
# nie fuer alle Steine einer Sorte - sonst stellt man ihn bei jedem Stein
|
||||
# nach. Bereich zu = fester Wert wie vorher.
|
||||
lo_a, hi_a = sorted((settings.dissolve_angle, settings.dissolve_angle_max))
|
||||
eff_angle = rng.uniform(lo_a, hi_a) if hi_a > lo_a else lo_a
|
||||
bpy.ops.mesh.dissolve_limited(angle_limit=math.radians(eff_angle))
|
||||
bpy.ops.mesh.select_all(action='SELECT')
|
||||
bpy.ops.mesh.normals_make_consistent(inside=False)
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
@@ -957,6 +1043,19 @@ class RockGenSettings(PropertyGroup):
|
||||
# --- Decimate / Cleanup ---
|
||||
decimate_ratio_1: FloatProperty(name="Decimate 1", default=0.2, min=0.01, max=1.0)
|
||||
decimate_ratio_2: FloatProperty(name="Decimate 2", default=0.5, min=0.01, max=1.0)
|
||||
rock_kind: EnumProperty(
|
||||
name="Sorte",
|
||||
items=[(k, v["label"], v["desc"]) for k, v in ROCK_KINDS.items()],
|
||||
default='FINDLING',
|
||||
description="Schnellstart-Sorte - der Knopf daneben uebernimmt die Werte",
|
||||
)
|
||||
dissolve_angle_max: FloatProperty(
|
||||
name="Winkel bis", default=25.0, min=0.0, max=80.0,
|
||||
description=("Obergrenze fuer den Limited-Dissolve-Winkel. Je Stein wird "
|
||||
"ein Wert aus dem Bereich gezogen - gleich gross wie der "
|
||||
"untere Wert heisst fester Winkel"),
|
||||
)
|
||||
|
||||
dissolve_angle: FloatProperty(
|
||||
name="Dissolve Winkel (Grad)", default=5.0, min=0.0, max=45.0,
|
||||
description="Groesserer Winkel = mehr Faces werden zusammengefasst",
|
||||
@@ -1583,8 +1682,13 @@ def _prepare_export_copies(context, top, lay_flat=True, want_ucx=True,
|
||||
top_copy.matrix_parent_inverse = Matrix.Identity(4)
|
||||
context.view_layer.update()
|
||||
|
||||
# 1) Ursprung auf unten-mittig der gemeinsamen Bbox.
|
||||
lo, hi = _bbox_of(meshes)
|
||||
# 1) Ursprung auf unten-mittig - berechnet aus LOD0, NICHT aus der
|
||||
# gemeinsamen Bbox aller Stufen. Eine stark dezimierte Stufe kann tiefer
|
||||
# reichen als LOD0; nimmt man die gemeinsame Box, sitzt LOD0 danach ueber
|
||||
# dem Pivot (gemessen 7 cm) und der Stein schwebt. Unreal platziert nach
|
||||
# LOD0, also gibt LOD0 den Ursprung vor.
|
||||
referenz = max(meshes, key=lambda o: len(o.data.vertices)) if meshes else None
|
||||
lo, hi = _bbox_of([referenz] if referenz is not None else [])
|
||||
if lo is not None:
|
||||
shift = Vector((-(lo.x + hi.x) * 0.5, -(lo.y + hi.y) * 0.5, -lo.z))
|
||||
off = Matrix.Translation(shift)
|
||||
@@ -1798,6 +1902,27 @@ class ROCKGEN_OT_fracture(Operator):
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class ROCKGEN_OT_apply_kind(Operator):
|
||||
"""Uebernimmt die Werte einer Schnellstart-Sorte."""
|
||||
bl_idname = "rockgen.apply_kind"
|
||||
bl_label = "Sorte uebernehmen"
|
||||
bl_description = "Setzt Form, Dichte, Dissolve-Bereich und Shading der gewaehlten Sorte"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def execute(self, context):
|
||||
s = context.scene.rock_gen_settings
|
||||
eintrag = ROCK_KINDS.get(s.rock_kind)
|
||||
if eintrag is None:
|
||||
self.report({'ERROR'}, "Unbekannte Sorte: %s" % s.rock_kind)
|
||||
return {'CANCELLED'}
|
||||
for k, v in eintrag["values"].items():
|
||||
setattr(s, k, v)
|
||||
self.report({'INFO'}, "Sorte '%s' uebernommen - Dissolve %.0f-%.0f Grad."
|
||||
% (eintrag["label"], s.dissolve_angle,
|
||||
s.dissolve_angle_max))
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class ROCKGEN_OT_dense_preset(Operator):
|
||||
"""Stellt auf smooth und dicht um (~2700 Tris statt ~620)."""
|
||||
bl_idname = "rockgen.dense_preset"
|
||||
@@ -2477,6 +2602,13 @@ class VIEW3D_PT_rock_generator(Panel):
|
||||
layout = self.layout
|
||||
s = context.scene.rock_gen_settings
|
||||
|
||||
# Schnellstart zuerst: deckt den Alltag ab, ohne durch neun
|
||||
# Unter-Panels zu klicken.
|
||||
row = layout.row(align=True)
|
||||
row.prop(s, "rock_kind", text="")
|
||||
row.operator("rockgen.apply_kind", text="", icon='CHECKMARK')
|
||||
layout.separator()
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.prop(s, "active_preset", text="")
|
||||
row.operator("rockgen.preset_load", text="", icon='IMPORT')
|
||||
@@ -2565,7 +2697,9 @@ class VIEW3D_PT_rock_detail(_RockSub):
|
||||
col = layout.column(align=True)
|
||||
col.prop(s, "decimate_ratio_1")
|
||||
col.prop(s, "decimate_ratio_2")
|
||||
layout.prop(s, "dissolve_angle")
|
||||
row = layout.row(align=True)
|
||||
row.prop(s, "dissolve_angle", text="Dissolve von")
|
||||
row.prop(s, "dissolve_angle_max", text="bis")
|
||||
layout.separator()
|
||||
layout.prop(s, "add_bevel")
|
||||
sub = layout.column(align=True)
|
||||
@@ -2748,6 +2882,7 @@ classes = (
|
||||
OBJECT_OT_export_rocks_fbx,
|
||||
OBJECT_OT_bake_rock_textures,
|
||||
ROCKGEN_OT_bake_cavity_selection,
|
||||
ROCKGEN_OT_apply_kind,
|
||||
ROCKGEN_OT_dense_preset,
|
||||
ROCKGEN_OT_fracture,
|
||||
ROCKGEN_OT_ore_set,
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
"""Schnellstart-Sorten + Dissolve-Bereich.
|
||||
|
||||
Der Schnellstart soll den Alltag abdecken, ohne dass man sich durch neun
|
||||
Unter-Panels klickt. Damit das etwas wert ist, muss jede Sorte OHNE
|
||||
Nacharbeit ein brauchbares Ergebnis liefern - genau das prueft diese Datei.
|
||||
|
||||
Der Dissolve-Winkel ist ein BEREICH, weil ein fester Wert nie fuer alle Steine
|
||||
einer Sorte passt. Geprueft wird deshalb auch, dass der Bereich tatsaechlich je
|
||||
Stein streut und nicht nur als Zahl im Panel steht.
|
||||
|
||||
Aufruf: blender --background --factory-startup --python tests/test_schnellstart.py
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
importlib_util = __import__("importlib.util", fromlist=["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 = []
|
||||
s = bpy.context.scene.rock_gen_settings
|
||||
s.use_modal = False
|
||||
|
||||
|
||||
def bauen(anzahl=3, seed=0):
|
||||
for o in list(bpy.data.objects):
|
||||
bpy.data.objects.remove(o, do_unlink=True)
|
||||
s.batch_count = anzahl
|
||||
s.seed = seed
|
||||
bpy.ops.object.generate_rocks_batch()
|
||||
return sorted((o for o in bpy.context.scene.objects if o.type == 'MESH'),
|
||||
key=lambda o: o.name)
|
||||
|
||||
|
||||
print("%-12s %7s %7s %10s %s" % ("Sorte", "Verts", "Tris", "Dissolve", "Slot"))
|
||||
for kind, eintrag in rg.ROCK_KINDS.items():
|
||||
s.rock_kind = kind
|
||||
res = bpy.ops.rockgen.apply_kind()
|
||||
if 'FINISHED' not in res:
|
||||
fails.append("Sorte %s: apply_kind lieferte %s" % (kind, res))
|
||||
continue
|
||||
|
||||
obs = bauen()
|
||||
if not obs:
|
||||
fails.append("Sorte %s erzeugt keine Geometrie" % kind)
|
||||
continue
|
||||
|
||||
verts = sum(len(o.data.vertices) for o in obs) // len(obs)
|
||||
tris = sum(sum(len(p.vertices) - 2 for p in o.data.polygons)
|
||||
for o in obs) // len(obs)
|
||||
slots = [m.name for m in obs[0].data.materials]
|
||||
print("%-12s %7d %7d %5.0f-%-4.0f %s"
|
||||
% (eintrag["label"], verts, tris, s.dissolve_angle,
|
||||
s.dissolve_angle_max, slots or "KEINE"))
|
||||
|
||||
if not slots:
|
||||
fails.append("Sorte %s: kein Material-Slot" % eintrag["label"])
|
||||
elif not slots[0].startswith("M_Rock_"):
|
||||
fails.append("Sorte %s: Slot '%s' folgt nicht M_Rock_<Sorte>"
|
||||
% (eintrag["label"], slots[0]))
|
||||
|
||||
# Der Bereich muss sinnvoll sein - untere Grenze nicht ueber der oberen.
|
||||
if s.dissolve_angle > s.dissolve_angle_max:
|
||||
fails.append("Sorte %s: Dissolve-Bereich verdreht (%.0f > %.0f)"
|
||||
% (eintrag["label"], s.dissolve_angle, s.dissolve_angle_max))
|
||||
|
||||
# Die Erz-Sorte muss dicht genug fuer eine saubere Nugget-Maske sein -
|
||||
# sonst loest die eigene Vorlage die eigene Warnung aus.
|
||||
if eintrag["values"].get("bake_ore"):
|
||||
if slots != [rg.ORE_MATERIAL[0]]:
|
||||
fails.append("Sorte %s: Slot %s, erwartet ['%s']"
|
||||
% (eintrag["label"], slots, rg.ORE_MATERIAL[0]))
|
||||
duenn = [o for o in obs if len(o.data.vertices) < rg.ORE_MIN_VERTS]
|
||||
if duenn:
|
||||
fails.append("Sorte %s: %d von %d Steinen unter %d Verts - die "
|
||||
"eigene Vorlage wuerde die eigene Dichte-Warnung "
|
||||
"ausloesen" % (eintrag["label"], len(duenn), len(obs),
|
||||
rg.ORE_MIN_VERTS))
|
||||
else:
|
||||
if slots == [rg.ORE_MATERIAL[0]]:
|
||||
fails.append("Sorte %s traegt den Erz-Slot ohne Erz"
|
||||
% eintrag["label"])
|
||||
|
||||
# --- Der Dissolve-Bereich muss je Stein streuen --------------------------
|
||||
s.rock_kind = 'FINDLING'
|
||||
bpy.ops.rockgen.apply_kind()
|
||||
obs = bauen(anzahl=6, seed=3)
|
||||
tris = [sum(len(p.vertices) - 2 for p in o.data.polygons) for o in obs]
|
||||
print("")
|
||||
print("Findling x6 bei %.0f-%.0f Grad: %s"
|
||||
% (s.dissolve_angle, s.dissolve_angle_max, tris))
|
||||
if len(set(tris)) < 3:
|
||||
fails.append("Dissolve-Bereich streut nicht je Stein: %s" % tris)
|
||||
|
||||
# Bereich zu = fester Winkel wie frueher. Muss weiter funktionieren.
|
||||
s.dissolve_angle_max = s.dissolve_angle
|
||||
obs = bauen(anzahl=2, seed=3)
|
||||
if not obs:
|
||||
fails.append("Geschlossener Dissolve-Bereich erzeugt keine Geometrie")
|
||||
else:
|
||||
print("Bereich zu (%.0f Grad): %d Steine, %d Tris"
|
||||
% (s.dissolve_angle, len(obs),
|
||||
sum(len(p.vertices) - 2 for p in obs[0].data.polygons)))
|
||||
|
||||
print("")
|
||||
if fails:
|
||||
print("ERGEBNIS: %d FEHLER" % len(fails))
|
||||
for x in fails:
|
||||
print(" - " + x)
|
||||
sys.exit(1)
|
||||
print("ERGEBNIS: ALLE CHECKS OK")
|
||||
Reference in New Issue
Block a user