UCX bei LOD-Gruppen nach dem MESH-Node benennen (Tree 1.45.0, Rock 2.20.0)

Der Legacy-Importer matcht UCX_<Mesh-Node>, nicht das LodGroup-Empty. Eine
Huelle mit dem Gruppennamen liegt in der Datei und ein Reimport nach Blender
zeigt sie an - Unreal verwirft sie aber STILL. Gemeldet von der Spielseite:
convex = 0 an 108 Stand-Baeumen und 57 Fels-Meshes; dieselbe Datei mit der
Huelle auf dem LOD0-Node liefert convex = 1.

UCX_<Basisname> stimmt nur OHNE LOD-Kette, weil der Mesh-Node dann so heisst -
deshalb waren Faell-Teile und _Pieces_All die ganze Zeit in Ordnung.

BEIDE Addons betroffen: der Fels-Export hatte den Fehler zuerst, der
Baum-Export hat ihn 1.42 von dort uebernommen.

Wie es durchrutschen konnte, ist die eigentliche Lehre: fuer 1.42 habe ich die
Konvention am Fels-Export NACHGEMESSEN statt sie zu raten - exportieren,
reimportieren, Name und Hierarchie pruefen. Das war richtig und hat trotzdem
nichts bewiesen, weil die Quelle denselben Fehler hatte. Gemessen wurde die
STRUKTUR, nicht die WIRKUNG. Eine abgeschaute Konvention ist nur so gut wie die
Quelle, von der sie stammt.

Die Tests pruefen jetzt die REGEL statt des Namens: hinter UCX_ muss ein
Mesh-Node aus derselben Datei stehen, und zwar LOD0 (hoechste Vertexzahl). Das
haette den Fehler in beiden Addons gefunden, ohne die Antwort vorher zu kennen.
Gegenproben: Baum 9 Fehler, Fels 1 Fehler.

27 Tests bestehen.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-08-07 07:18:33 +02:00
co-authored by Claude Opus 4.8
parent b5ec4a4bc0
commit 5e6b84369f
13 changed files with 130 additions and 29 deletions
+30
View File
@@ -563,6 +563,36 @@ Der Kaktus erzeugt regulaer **0 Cards** — kein Ast ist duenner als `Max Radius
und das ist botanisch richtig. Vorher lief das still durch und man stand vor und das ist botanisch richtig. Vorher lief das still durch und man stand vor
einer leeren `_Leaf`-Ebene; jetzt sagt der Operator es und nennt den Regler. einer leeren `_Leaf`-Ebene; jetzt sagt der Operator es und nennt den Regler.
### UCX bei LOD-Gruppen: der Name muss auf den MESH-Node zeigen
Der Legacy-Importer von Unreal matcht `UCX_<Mesh-Node>` - **nicht** gegen das
LodGroup-Empty. Eine Huelle mit dem Gruppennamen liegt zwar in der Datei und ein
Reimport nach Blender zeigt sie brav an, aber Unreal verwirft sie STILL:
gemeldet von der Spielseite mit `convex = 0` an 108 Stand-Baeumen und 57
Fels-Meshes. Beweis von dort: dieselbe Datei, Huelle umbenannt auf den
LOD0-Node, liefert `convex = 1`.
`UCX_<Basisname>` ist nur OHNE LOD-Kette richtig, weil der Mesh-Node dann genau
so heisst. Deshalb funktionierten die Faell-Teile und `_Pieces_All.fbx` die
ganze Zeit - die haben keine LODs.
**Wie das durchrutschen konnte, ist die eigentliche Lehre.** Fuer 1.42 habe ich
die Konvention nicht geraten, sondern am Fels-Export nachgemessen: exportieren,
reimportieren, Namen und Hierarchie pruefen. Das war richtig - und hat trotzdem
nichts bewiesen, weil der Fels-Export denselben Fehler hatte. Gemessen wurde die
STRUKTUR ("liegt die Huelle in der Datei, heisst sie wie erwartet, haengt sie
richtig?"), nicht die WIRKUNG ("nimmt Unreal sie an?"). Eine abgeschaute
Konvention ist nur so gut wie die Quelle, von der sie stammt.
Die Tests pruefen deshalb jetzt die REGEL statt des Namens: hinter `UCX_` muss
ein Mesh-Node aus derselben Datei stehen, und zwar LOD0. Das haette den Fehler
in beiden Addons gefunden, ohne dass jemand die Antwort vorher kennt.
Gegenproben: Baum 9 Fehler, Fels 1 Fehler.
Was nur die Engine beantworten kann - ob `convex` nach dem Import wirklich
groesser 0 ist - bleibt bei der Spielseite. Der Test kann nur sicherstellen,
dass die Datei die Bedingung erfuellt, an der es lag.
### Die Gesamtdatei steht jetzt auch auf der Stammbasis ### Die Gesamtdatei steht jetzt auch auf der Stammbasis
`_Pieces_All.fbx` trug den SZENEN-Nullpunkt mit: ein Baum bei x = 18 m `_Pieces_All.fbx` trug den SZENEN-Nullpunkt mit: ein Baum bei x = 18 m
+1 -1
View File
@@ -1,7 +1,7 @@
schema_version = "1.0.0" schema_version = "1.0.0"
id = "stylized_rock_generator" id = "stylized_rock_generator"
version = "2.19.0" version = "2.20.0"
name = "Stylized Rock Generator" name = "Stylized Rock Generator"
tagline = "Batch-Generator fuer stylized Rocks mit UE5-Export" tagline = "Batch-Generator fuer stylized Rocks mit UE5-Export"
maintainer = "D4rkst3r" maintainer = "D4rkst3r"
+1 -1
View File
@@ -1,7 +1,7 @@
schema_version = "1.0.0" schema_version = "1.0.0"
id = "stylized_tree_generator" id = "stylized_tree_generator"
version = "1.44.0" version = "1.45.0"
name = "Stylized Tree Generator" name = "Stylized Tree Generator"
tagline = "Parametrische Baeume, Palmen, Bueschen mit Wachstums-Stufen" tagline = "Parametrische Baeume, Palmen, Bueschen mit Wachstums-Stufen"
maintainer = "D4rkst3r" maintainer = "D4rkst3r"
+5 -5
View File
@@ -40,27 +40,27 @@
<td>4.9KB</td> <td>4.9KB</td>
</tr> </tr>
<tr> <tr>
<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><tt><a href="./stylized_rock_generator-2.20.0.zip?repository=.%2Findex.json&blender_version_min=4.2.0">stylized_rock_generator-2.20.0</a></tt></td>
<td>Stylized Rock Generator</td> <td>Stylized Rock Generator</td>
<td>Batch-Generator fuer stylized Rocks mit UE5-Export</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><a href="https://git.d4rkst3r.de/D4rkst3r/stylized-rock-generator">link</a></td>
<td>4.2.0 - ~</td> <td>4.2.0 - ~</td>
<td>all</td> <td>all</td>
<td>all</td> <td>all</td>
<td>34.1KB</td> <td>34.5KB</td>
</tr> </tr>
<tr> <tr>
<td><tt><a href="./stylized_tree_generator-1.44.0.zip?repository=.%2Findex.json&blender_version_min=4.2.0">stylized_tree_generator-1.44.0</a></tt></td> <td><tt><a href="./stylized_tree_generator-1.45.0.zip?repository=.%2Findex.json&blender_version_min=4.2.0">stylized_tree_generator-1.45.0</a></tt></td>
<td>Stylized Tree Generator</td> <td>Stylized Tree Generator</td>
<td>Parametrische Baeume, Palmen, Bueschen mit Wachstums-Stufen</td> <td>Parametrische Baeume, Palmen, Bueschen mit Wachstums-Stufen</td>
<td><a href="https://git.d4rkst3r.de/D4rkst3r/stylized-rock-generator">link</a></td> <td><a href="https://git.d4rkst3r.de/D4rkst3r/stylized-rock-generator">link</a></td>
<td>4.2.0 - ~</td> <td>4.2.0 - ~</td>
<td>all</td> <td>all</td>
<td>all</td> <td>all</td>
<td>49.5KB</td> <td>49.9KB</td>
</tr> </tr>
</table> </table>
<center><p>Built 2026-08-07, 04:59</p></center> <center><p>Built 2026-08-07, 05:18</p></center>
</body> </body>
</html> </html>
+8 -8
View File
@@ -57,7 +57,7 @@
"id": "stylized_rock_generator", "id": "stylized_rock_generator",
"name": "Stylized Rock Generator", "name": "Stylized Rock Generator",
"tagline": "Batch-Generator fuer stylized Rocks mit UE5-Export", "tagline": "Batch-Generator fuer stylized Rocks mit UE5-Export",
"version": "2.19.0", "version": "2.20.0",
"type": "add-on", "type": "add-on",
"maintainer": "D4rkst3r", "maintainer": "D4rkst3r",
"license": [ "license": [
@@ -73,16 +73,16 @@
"Mesh", "Mesh",
"Modeling" "Modeling"
], ],
"archive_url": "./stylized_rock_generator-2.19.0.zip", "archive_url": "./stylized_rock_generator-2.20.0.zip",
"archive_size": 34968, "archive_size": 35362,
"archive_hash": "sha256:2b412f9324263a3951b1b519648ab46025e7511b09f54b6017edf6d834a17bc6" "archive_hash": "sha256:f88fa99b30bef7ae4ac1319e64e2374166dc8c37f2d42dc72352b15fbffdaae0"
}, },
{ {
"schema_version": "1.0.0", "schema_version": "1.0.0",
"id": "stylized_tree_generator", "id": "stylized_tree_generator",
"name": "Stylized Tree Generator", "name": "Stylized Tree Generator",
"tagline": "Parametrische Baeume, Palmen, Bueschen mit Wachstums-Stufen", "tagline": "Parametrische Baeume, Palmen, Bueschen mit Wachstums-Stufen",
"version": "1.44.0", "version": "1.45.0",
"type": "add-on", "type": "add-on",
"maintainer": "D4rkst3r", "maintainer": "D4rkst3r",
"license": [ "license": [
@@ -98,9 +98,9 @@
"Mesh", "Mesh",
"Modeling" "Modeling"
], ],
"archive_url": "./stylized_tree_generator-1.44.0.zip", "archive_url": "./stylized_tree_generator-1.45.0.zip",
"archive_size": 50734, "archive_size": 51048,
"archive_hash": "sha256:e10cf2efafc751b2e8b7cf6e15a793408f43ffff8b1dc0e98e43ad8ba809659a" "archive_hash": "sha256:c01f5c4e4b25e35ca8ffbe3b7266306bebe718f533784ffa490f581c373bba0b"
} }
] ]
} }
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+19 -4
View File
@@ -1,7 +1,7 @@
bl_info = { bl_info = {
"name": "Stylized Rock Generator", "name": "Stylized Rock Generator",
"author": "D4rkst3r", "author": "D4rkst3r",
"version": (2, 19, 0), "version": (2, 20, 0),
"blender": (4, 2, 0), "blender": (4, 2, 0),
"location": "View3D > Sidebar > Rock Gen", "location": "View3D > Sidebar > Rock Gen",
"description": ( "description": (
@@ -1695,10 +1695,25 @@ def _prepare_export_copies(context, top, lay_flat=True, want_ucx=True,
for o in meshes: for o in meshes:
o.data.transform(off) o.data.transform(off)
# 3) UCX. Bei einer LOD-Gruppe darf die Huelle NICHT unter das LodGroup-Empty: # 3) UCX. Zwei Regeln, beide teuer gelernt:
# Unreal wuerde jedes Kind als LOD-Stufe lesen. Also als Geschwister. #
# a) Bei einer LOD-Gruppe darf die Huelle NICHT unter das LodGroup-Empty:
# Unreal wuerde jedes Kind als LOD-Stufe lesen. Also als Geschwister.
# b) Der Name muss auf den MESH-Node zeigen, nicht auf das Empty. Der
# Legacy-Importer matcht `UCX_<Mesh-Node>`; hiess die Huelle nach der
# LodGroup, lag sie zwar in der Datei, wurde aber STILL verworfen -
# gemeldet von der Spielseite mit convex = 0 an 57 Fels-Meshes.
# `UCX_<Basisname>` stimmt nur OHNE LOD-Kette, weil dort der Mesh-Node
# genau so heisst. Ein Reimport nach Blender zeigt die Huelle in
# beiden Faellen - er beweist nur, dass sie in der Datei liegt, nicht
# dass Unreal sie annimmt.
#
# `referenz` ist LOD0 (hoechste Vertexzahl) - derselbe Bezug, den oben
# schon der Ursprung benutzt. Ohne LOD-Kette ist es das Mesh selbst, dann
# faellt der Name mit export_name zusammen.
if want_ucx and meshes: if want_ucx and meshes:
ucx = _make_ucx(context, [meshes[0]], export_name, ucx_ziel = referenz if referenz is not None else meshes[0]
ucx = _make_ucx(context, [ucx_ziel], ucx_ziel.name,
max_verts=ucx_max_verts) max_verts=ucx_max_verts)
if ucx is not None: if ucx is not None:
made.append(ucx) made.append(ucx)
+15 -3
View File
@@ -1,7 +1,7 @@
bl_info = { bl_info = {
"name": "Stylized Tree Generator", "name": "Stylized Tree Generator",
"author": "D4rkst3r", "author": "D4rkst3r",
"version": (1, 44, 0), "version": (1, 45, 0),
"blender": (4, 2, 0), "blender": (4, 2, 0),
"location": "View3D > Sidebar > Tree Gen", "location": "View3D > Sidebar > Tree Gen",
"description": "Parametrischer Baum-/Palmen-/Busch-Generator (Geometry Nodes) mit Wachstums-Stufen", "description": "Parametrischer Baum-/Palmen-/Busch-Generator (Geometry Nodes) mit Wachstums-Stufen",
@@ -2943,8 +2943,20 @@ class TREEGEN_OT_export_game(Operator):
# und bei LOD1-3 zusaetzlich dezimiert. Sie durchlaufen dieselbe # und bei LOD1-3 zusaetzlich dezimiert. Sie durchlaufen dieselbe
# Kette wie die Stufen: eigene Weltmatrix, dann die gemeinsame # Kette wie die Stufen: eigene Weltmatrix, dann die gemeinsame
# Zentrierung `versatz`. # Zentrierung `versatz`.
stamm_ucx_name = (basis_name + "_LODGroup") if s.generate_lods \ #
else basis_name # NAME nach dem MESH-Node (LOD0), nicht nach dem LodGroup-Empty.
# Der Legacy-Importer matcht `UCX_<Mesh-Node>`; mit dem
# LodGroup-Namen lag die Huelle zwar in der Datei, wurde von
# Unreal aber STILL verworfen - gemeldet mit convex = 0 an 108
# Stand-Baeumen.
#
# 1.42 hatte die Konvention vom Fels-Export abgeschaut und dabei
# nur die STRUKTUR nachgemessen (Reimport nach Blender: Huelle
# da, kein Elternteil, richtiger Name). Dass Unreal sie ANNIMMT,
# war ungeprueft - und genau daran lag es. Ohne LOD-Kette heisst
# der Mesh-Node wie die Basis; deshalb funktionierten die
# Einzel-Teile und Pieces_All die ganze Zeit.
stamm_ucx_name = lods[0].name
hull = None hull = None
if s.stand_ucx: if s.stand_ucx:
pts = _stamm_punkte(baum, baum.matrix_world) pts = _stamm_punkte(baum, baum.matrix_world)
+18
View File
@@ -226,6 +226,24 @@ else:
elif hull[0].parent is not None and hull[0].parent.type == 'EMPTY': elif hull[0].parent is not None and hull[0].parent.type == 'EMPTY':
# Unter dem LodGroup-Empty wuerde Unreal die Huelle als LOD-Stufe lesen. # Unter dem LodGroup-Empty wuerde Unreal die Huelle als LOD-Stufe lesen.
fails.append("LOD: UCX haengt unter dem LodGroup-Empty") fails.append("LOD: UCX haengt unter dem LodGroup-Empty")
if hull and stufen:
# DER Punkt, der lange gefehlt hat: der Legacy-Importer matcht
# `UCX_<MESH-Node>`, nicht das LodGroup-Empty. Eine Huelle mit dem
# Gruppennamen liegt zwar in der Datei - dieser Test hat sie brav
# gefunden und fuer gut befunden -, wird von Unreal aber STILL
# verworfen. Gemeldet von der Spielseite: convex = 0 an 57 Fels-Meshes.
#
# Struktur pruefen reicht nicht. Geprueft wird deshalb die REGEL: hinter
# "UCX_" muss ein Mesh-Node aus derselben Datei stehen, und zwar LOD0.
ziel = hull[0].name[4:]
namen = {o.name for o in stufen}
if ziel not in namen:
fails.append("LOD: Huelle heisst '%s' - '%s' ist kein Mesh-Node "
"(vorhanden: %s). Unreal verwirft sie still."
% (hull[0].name, ziel, sorted(namen)))
elif ziel != stufen[0].name:
fails.append("LOD: Huelle zeigt auf '%s', LOD0 heisst '%s'"
% (ziel, stufen[0].name))
if stufen: if stufen:
co = [stufen[0].matrix_world @ v.co for v in stufen[0].data.vertices] co = [stufen[0].matrix_world @ v.co for v in stufen[0].data.vertices]
lo = [min(c[i] for c in co) for i in range(3)] lo = [min(c[i] for c in co) for i in range(3)]
+33 -7
View File
@@ -6,9 +6,14 @@ Spieler, bevor er den Baum ueberhaupt beruehrt.
Die Namens- und Hierarchie-Konvention ist nicht geraten, sondern vom Die Namens- und Hierarchie-Konvention ist nicht geraten, sondern vom
Fels-Export abgeschaut, der in der Spiel-Pipeline bereits abgenommen ist: Fels-Export abgeschaut, der in der Spiel-Pipeline bereits abgenommen ist:
`UCX_<LODGroup-Name>`, und zwar als GESCHWISTER der LodGroup, nicht als Kind - `UCX_<LOD0-Mesh-Node>`, und zwar als GESCHWISTER der LodGroup, nicht als Kind -
Unreal liest jedes Kind der Gruppe als LOD-Stufe. Unreal liest jedes Kind der Gruppe als LOD-Stufe.
WICHTIG, weil es genau hier schiefging: der Legacy-Importer matcht gegen den
MESH-Node, NICHT gegen das LodGroup-Empty. Eine Huelle mit dem LodGroup-Namen
liegt zwar in der Datei und ein Reimport nach Blender zeigt sie brav an - Unreal
verwirft sie aber still (convex = 0). Struktur pruefen reicht nicht.
Gemessen wird im reimportierten FBX. Gemessen wird im reimportierten FBX.
Aufruf: blender --background --factory-startup --python tests/test_stamm_ucx.py Aufruf: blender --background --factory-startup --python tests/test_stamm_ucx.py
@@ -132,12 +137,33 @@ for preset, mit_lods, pfad, stamm_r, ansatz, blender_h, max_verts in ergebnisse:
continue continue
u = ucx[0] u = ucx[0]
# Name: exakt die Konvention des Fels-Exports. # NAME: er muss auf einen MESH-Node zeigen, und zwar auf LOD0.
erwartet = ("UCX_" + gruppe[0].name) if gruppe else ("UCX_" + rest[0].name) #
if u.name != erwartet: # Die erste Fassung dieses Tests prueft genau das NICHT scharf genug: sie
fails.append("%s: Huelle heisst '%s', erwartet '%s' - Unreal ordnet " # verglich gegen den LodGroup-Namen, weil der Fels-Export es so machte.
"die Kollision ueber den Namen zu" # Struktur und Name waren damit stimmig - Unreal hat die Huelle trotzdem
% (preset, u.name, erwartet)) # still verworfen (convex = 0 an 108 Baeumen), weil der Legacy-Importer
# gegen den MESH-Node matcht. Ein Reimport nach Blender beweist nur, dass
# die Huelle in der Datei liegt, nicht dass sie angenommen wird.
#
# Deshalb steht hier jetzt die Regel und nicht der Name: der Rest hinter
# "UCX_" muss ein Mesh im FBX sein. Und der Vollstaendigkeit halber: er darf
# NICHT das Empty sein.
ziel = u.name[4:]
mesh_namen = {o.name for o in rest}
if ziel not in mesh_namen:
fails.append("%s: Huelle heisst '%s' - '%s' ist kein Mesh-Node im FBX "
"(vorhanden: %s). Unreal verwirft sie dann still."
% (preset, u.name, ziel, sorted(mesh_namen)))
if gruppe and ziel == gruppe[0].name:
fails.append("%s: Huelle zeigt auf das LodGroup-Empty '%s' statt auf "
"einen Mesh-Node" % (preset, ziel))
# Und zwar auf LOD0, nicht auf irgendeine Stufe: Unreal haengt die
# Kollision an das Render-Mesh der obersten Stufe.
lod0 = max(rest, key=lambda o: len(o.data.vertices))
if ziel != lod0.name:
fails.append("%s: Huelle zeigt auf '%s', LOD0 heisst aber '%s'"
% (preset, ziel, lod0.name))
# Hierarchie: NICHT unter der LodGroup, sonst waere sie eine LOD-Stufe. # Hierarchie: NICHT unter der LodGroup, sonst waere sie eine LOD-Stufe.
if u.parent is not None: if u.parent is not None: