Rock Gen 2.18.0: Material-Slots, Praefix Rock, Warnung bei zu grobem Erz-Mesh

Drei Rueckmeldungen von der Spielseite abgearbeitet.

1) Fehlende Material-Slots
   "Material zuweisen" stand auf Standard AUS, deshalb kamen alle Proben mit
   mats: [] an - Unreal legt dann nur einen Default-Slot an und die
   eingefrorene Konvention M_Rock_<Sorte> greift nicht. Jetzt Standard AN.
   Normale Steine rotieren durch M_Rock_Granite/Basalt/Sandstone/Mossy, das
   Erzfels-Set bekommt einen NEUTRALEN Slot M_Rock_Ore - ein Set fuer alle
   Erzsorten, die Sorte macht die Material-Instanz.

2) Namens-Drift Fels_ statt Rock_
   War ein Versehen, keine Absicht: der Standard-Praefix stammte noch aus der
   ersten Fassung, bevor die Konvention Rock_<Set>_<Groesse>_<NN> stand. Der
   Erz-Set-Knopf setzte "Rock" schon selbst, der allgemeine Standard nicht.
   Standard ist jetzt "Rock" - keine Asset-Zwillinge, keine Migration noetig.

3) Erzfelsen mit ~870 statt ~2700 Tris
   Nachgestellt: aus den Werkseinstellungen liefert der Set-Knopf 2590-2690
   Tris, die Automatik greift also. ~870 entstehen genau dann, wenn man den
   Erz-Haken setzt und den NORMALEN Erzeugen-Knopf nimmt - Werkseinstellung ist
   Decimate 0.20/0.50 statt 0.40/0.80. Das ist eine Bedienluecke, kein Fehler
   in der Automatik: die Dichte wird bewusst nur ueber den Set-Knopf gesetzt,
   damit niemandem seine Einstellungen unterm Hintern weggezogen werden.
   Deshalb warnt der Operator jetzt, wenn mit Erz-Haken ein Mesh unter 800
   Verts entsteht, und nennt den Knopf "Smooth & dicht" beim Namen. Sonst
   faellt das erst in UE auf und sieht dort nach einem Material-Fehler aus.

Tests halten den Vertrag jetzt fest: Auslieferungszustand (assign_material an,
Praefix Rock), neutraler Erz-Slot, Sorten-Slots bei normalen Steinen.
17 Tests bestehen.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-08-03 14:08:21 +02:00
co-authored by Claude Opus 4.8
parent a2d46424ec
commit f996832d75
9 changed files with 123 additions and 25 deletions
+54 -12
View File
@@ -1,7 +1,7 @@
bl_info = {
"name": "Stylized Rock Generator",
"author": "D4rkst3r",
"version": (2, 17, 0),
"version": (2, 18, 0),
"blender": (4, 2, 0),
"location": "View3D > Sidebar > Rock Gen",
"description": (
@@ -35,6 +35,15 @@ from mathutils import Matrix, Vector
TWO_PI = math.pi * 2.0
# Platzhalter-Materialien: (Name, Base-Color RGBA). Werden pro Rock rotiert.
# Neutraler Slot fuer das Erzfels-Set: EIN Set fuer alle Erzsorten, welche
# Sorte es ist (Kupfer/Eisen/Gold) macht die Material-Instanz in UE.
ORE_MATERIAL = ("M_Rock_Ore", (0.30, 0.28, 0.26, 1.0))
# Unterhalb dieser Vertex-Zahl verschmieren die Erz-Nuggets zu Schlieren - die
# Maske haengt an der Vertex-Dichte. Gemessen: bei ~150 Verts ist jedes dritte
# ein Nugget-Zentrum, bei ~1300 liest es sich als einzelne Einschluesse.
ORE_MIN_VERTS = 800
MATERIAL_PALETTE = (
("M_Rock_Granite", (0.32, 0.30, 0.28, 1.0)),
("M_Rock_Basalt", (0.11, 0.11, 0.13, 1.0)),
@@ -251,8 +260,14 @@ def _get_or_create_material(name, color):
return mat
def _assign_material(obj, index):
name, color = MATERIAL_PALETTE[index % len(MATERIAL_PALETTE)]
def _assign_material(obj, index, fixed=None):
"""Setzt den Material-Slot. Die UE-Skripte mappen ueber den SLOT-NAMEN,
deshalb ist die Benennung Teil des Vertrags und kein Kosmetik-Thema.
`fixed` erzwingt einen bestimmten Slot - das Erzfels-Set nutzt das fuer
seinen neutralen `M_Rock_Ore`, statt durch die Sorten zu rotieren.
"""
name, color = fixed if fixed is not None else MATERIAL_PALETTE[index % len(MATERIAL_PALETTE)]
mat = _get_or_create_material(name, color)
obj.data.materials.clear()
obj.data.materials.append(mat)
@@ -781,7 +796,8 @@ def _build_lod_group(context, settings, index, location, rng, base_name=None):
_bake_vertex_cavity(context, lod0, settings)
if settings.assign_material:
_assign_material(lod0, index)
_assign_material(lod0, index,
fixed=ORE_MATERIAL if settings.bake_ore else None)
lod0.name = base_name + "_LOD0"
lod0.data.name = base_name + "_LOD0"
@@ -844,7 +860,8 @@ def make_one_rock(context, settings, index, batch_count=1):
if settings.set_origin_bottom:
_set_origin_bottom(context, obj)
if settings.assign_material:
_assign_material(obj, index)
_assign_material(obj, index,
fixed=ORE_MATERIAL if settings.bake_ore else None)
return [obj]
@@ -858,8 +875,26 @@ def _cleanup_partial(context, names_before):
bpy.data.objects.remove(obj, do_unlink=True)
def _report_result(op, created, failed, cancelled=False):
def _report_result(op, created, failed, cancelled=False, settings=None):
n_mesh = sum(1 for o in created if getattr(o, "type", None) == 'MESH')
# Erz-Haken an, aber das Mesh ist zu grob? Dann verschmieren die Nuggets.
# Das faellt sonst erst in UE auf - und dort sieht es nach einem
# Material-Fehler aus, obwohl es an der Vertex-Dichte liegt.
if settings is not None and settings.bake_ore and not failed:
duenn = [o for o in created
if getattr(o, "type", None) == 'MESH'
and len(o.data.vertices) < ORE_MIN_VERTS]
if duenn:
op.report(
{'WARNING'},
"%d Rock(s) erzeugt - aber %d davon haben unter %d Verts. Die "
"Erz-Nuggets verschmieren dabei zu Schlieren. Knopf "
"'Smooth & dicht' druecken und neu erzeugen."
% (n_mesh, len(duenn), ORE_MIN_VERTS),
)
return
if failed:
op.report(
{'WARNING'},
@@ -1060,8 +1095,11 @@ class RockGenSettings(PropertyGroup):
# --- Material ---
assign_material: BoolProperty(
name="Material zuweisen", default=False,
description="Weist pro Rock ein Platzhalter-Material zu (rotiert durch eine Liste)",
name="Material zuweisen", default=True,
description=("Legt den Material-Slot an (M_Rock_<Sorte>, beim Erz-Set "
"neutral M_Rock_Ore). Die UE-Skripte mappen ueber den "
"Slot-NAMEN - ohne Slot erzeugt Unreal nur einen Default "
"und die Konvention greift nicht mehr"),
)
# --- LOD ---
@@ -1113,7 +1151,7 @@ class RockGenSettings(PropertyGroup):
)
# --- Benennung (EcoGame-Schema: Fels_<Form>_<Klasse>) ---
name_prefix: StringProperty(
name="Praefix", default="Fels",
name="Praefix", default="Rock",
description="Basisname, z.B. 'Fels' oder 'Felsblock'",
)
name_form: StringProperty(
@@ -1268,7 +1306,7 @@ class OBJECT_OT_generate_rocks(Operator):
print("[RockGen] Rock %d fehlgeschlagen: %s" % (i, exc))
traceback.print_exc()
_cleanup_partial(context, names_before)
_report_result(self, created, failed)
_report_result(self, created, failed, settings=settings)
return {'FINISHED'}
# --- Einstiegspunkt: entscheidet synchron vs. modal ---
@@ -1294,13 +1332,15 @@ class OBJECT_OT_generate_rocks(Operator):
if event.type == 'ESC':
self._finish(context)
_report_result(self, self._created, self._failed, cancelled=True)
_report_result(self, self._created, self._failed, cancelled=True,
settings=context.scene.rock_gen_settings)
return {'CANCELLED'}
if event.type == 'TIMER':
if self._index >= settings.batch_count:
self._finish(context)
_report_result(self, self._created, self._failed)
_report_result(self, self._created, self._failed,
settings=context.scene.rock_gen_settings)
return {'FINISHED'}
names_before = set(bpy.data.objects.keys())
@@ -1800,6 +1840,7 @@ class ROCKGEN_OT_ore_set(Operator):
"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,
"assign_material": s.assign_material,
}
vorher.update({k: getattr(s, k) for k in DENSE_PRESET})
# Erz MUSS dicht: die Maske haengt an der Vertex-Dichte. Bei ~150 Verts
@@ -1813,6 +1854,7 @@ class ROCKGEN_OT_ore_set(Operator):
s.name_numbered = True
s.bake_ore = True
s.bake_vertex_cavity = True # A haengt am selben Attribut
s.assign_material = True # Slot M_Rock_Ore - die UE-Skripte mappen darueber
s.use_class_size = True # S und M sollen sich in der Groesse unterscheiden
gesamt = 0