Tree Gen 1.43.0: UCX in _Pieces_All.fbx - und zwei Fehler, die dabei auffielen
Die Gesamtdatei kam ohne Kollision an, waehrend die Einzeldateien welche hatten. Jetzt bekommt jedes Teil auch dort seine Huelle, gleiche Konvention wie beim Stand-Baum: UCX_<Teilname> als Geschwister, kein Elternteil. Sie entstehen VOR dem Umsetzen der Pivots, weil in der Gesamtdatei alle Teile noch an ihrer Originalposition stehen. Beim Nachmessen kamen zwei Fehler heraus, die auch die BESTEHENDEN Einzeldateien betrafen: 1) Die Huellen umschlossen das Laub mit _tree_ucx las alle Vertices eines Teils, also auch die Blatt-Cards. Gemessen am baum-Preset war die Huelle dadurch 1.4- bis 2.1-mal so gross wie das Holz. Jetzt nur noch Material-Slot 0. Die Spielseite hatte das fuer ihre Interim-Boxen laengst so gemacht - zu Recht. 2) Das Aufweiten war fuer schlanke Teile kaputt Nach dem Vereinfachen wurde die Huelle durch Skalieren um die Huellenmitte aufgeblasen. Bei langgestreckten Teilen entgleist das: liegt eine Flaeche fast durch die Mitte, wird der Hebel winzig und der Faktor explodiert - gemessen 4.2 an einem Stammsegment mit Laenge zu Dicke 13:1. An der dicken Eiche blieb es bei 1.0-1.4, deshalb ist es nie aufgefallen; erst der Wechsel auf Rinde-only machte die Teile schlank genug. Jetzt wird je Flaeche der ECHTE Fehlbetrag gemessen und die Vertices um genau diesen Betrag nach aussen geschoben - eine Verschiebung in Metern, kein Verhaeltnis. Iterativ, weil ein Vertex mehreren Flaechen angehoert und entlang seiner eigenen Normale wandert (ein Durchgang liess einen Rest stehen, der Test schlug an). Bleibt danach etwas uebrig, faellt die Funktion auf die unvereinfachte Huelle zurueck. Ergebnis am 12-teiligen Baum: alle Huellen 14-31 Verts (Budget 32), keine groesser als das 1.35-fache ihres Holzes. test_faell_teile prueft die Gesamtdatei jetzt auf: je Teil genau eine Huelle, richtiger Name, kein Elternteil, und Huellengroesse gegen die RINDE. Gegenproben: ohne Rinde-Filter 10 Fehler (1.41x bis 2.06x), ohne Huellen in der Gesamtdatei 1 Fehler. 27 Tests bestehen. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -563,6 +563,43 @@ 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 auch in der Gesamtdatei - und zwei Fehler, die dabei auffielen
|
||||||
|
|
||||||
|
`_Pieces_All.fbx` kam ohne Kollision an, waehrend die Einzeldateien welche
|
||||||
|
hatten. Jetzt bekommt jedes Teil auch dort seine Huelle, gleiche Konvention wie
|
||||||
|
beim Stand-Baum: `UCX_<Teilname>` als Geschwister, kein Elternteil. Sie
|
||||||
|
entstehen VOR dem Umsetzen der Pivots - in der Gesamtdatei stehen alle Teile
|
||||||
|
noch an ihrer Originalposition.
|
||||||
|
|
||||||
|
**Die Huellen umschlossen das Laub mit.** `_tree_ucx` las alle Vertices eines
|
||||||
|
Teils, also auch die Blatt-Cards. Gemessen am `baum`-Preset war die Huelle
|
||||||
|
dadurch **1.4- bis 2.1-mal** so gross wie das Holz - beim obersten Stammsegment,
|
||||||
|
das die Krone traegt, 1.67x. Jetzt nur noch Material-Slot 0 (Rinde). Die
|
||||||
|
Spielseite hatte das fuer ihre Interim-Boxen laengst so gemacht.
|
||||||
|
|
||||||
|
**Und das Aufweiten war fuer schlanke Teile kaputt.** Nach dem Vereinfachen
|
||||||
|
wurde die Huelle wieder aufgeblasen, damit alles hineinpasst - durch Skalieren
|
||||||
|
um die Huellenmitte. Bei langgestreckten Teilen entgleist das: liegt eine
|
||||||
|
Flaeche fast durch die Mitte, wird der Hebel winzig und der noetige Faktor
|
||||||
|
explodiert. Gemessen an einem Stammsegment (Laenge zu Dicke rund 13:1)
|
||||||
|
**Faktor 4.2**. Bei der dicken Eiche blieb es bei 1.0-1.4 - deshalb ist es nie
|
||||||
|
aufgefallen. Erst der Wechsel auf Rinde-only machte die Teile schlank genug,
|
||||||
|
dass es durchschlug.
|
||||||
|
|
||||||
|
Jetzt wird nicht mehr skaliert, sondern je Flaeche der ECHTE Fehlbetrag
|
||||||
|
gemessen (wie weit liegt der schlimmste Quellpunkt vor ihrer Ebene?) und die
|
||||||
|
Vertices um genau diesen Betrag nach aussen geschoben - eine Verschiebung in
|
||||||
|
Metern, kein Verhaeltnis, sie kann nicht explodieren. Das laeuft ITERATIV: ein
|
||||||
|
Vertex gehoert mehreren Flaechen an und wandert entlang seiner eigenen Normale,
|
||||||
|
die Ebene verschiebt sich also nur um den projizierten Anteil. Ein Durchgang
|
||||||
|
liess einen Rest stehen (der Test schlug an), nach wenigen ist er weg. Bleibt
|
||||||
|
doch etwas uebrig, faellt die Funktion auf die unvereinfachte Huelle zurueck -
|
||||||
|
ein paar Vertices mehr kosten nichts, eine vierfach zu grosse Kollision schon.
|
||||||
|
|
||||||
|
Ergebnis am 12-teiligen Baum: alle Huellen 14-31 Verts (Budget 32), keine
|
||||||
|
groesser als das 1.35-fache ihres Holzes. Gegenproben: ohne Rinde-Filter 10
|
||||||
|
Fehler (1.41x bis 2.06x), ohne Huellen in der Gesamtdatei 1 Fehler.
|
||||||
|
|
||||||
### Stamm-UCX fuer den Stand-Baum
|
### Stamm-UCX fuer den Stand-Baum
|
||||||
|
|
||||||
Die Stand-Baeume bekommen eine konvexe Kollisionshuelle um den **Stamm**, vom
|
Die Stand-Baeume bekommen eine konvexe Kollisionshuelle um den **Stamm**, vom
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
schema_version = "1.0.0"
|
schema_version = "1.0.0"
|
||||||
|
|
||||||
id = "stylized_tree_generator"
|
id = "stylized_tree_generator"
|
||||||
version = "1.42.0"
|
version = "1.43.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"
|
||||||
|
|||||||
Vendored
+3
-3
@@ -50,17 +50,17 @@
|
|||||||
<td>34.1KB</td>
|
<td>34.1KB</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><tt><a href="./stylized_tree_generator-1.42.0.zip?repository=.%2Findex.json&blender_version_min=4.2.0">stylized_tree_generator-1.42.0</a></tt></td>
|
<td><tt><a href="./stylized_tree_generator-1.43.0.zip?repository=.%2Findex.json&blender_version_min=4.2.0">stylized_tree_generator-1.43.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>47.6KB</td>
|
<td>49.0KB</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<center><p>Built 2026-08-07, 04:18</p></center>
|
<center><p>Built 2026-08-07, 04:53</p></center>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Vendored
+4
-4
@@ -82,7 +82,7 @@
|
|||||||
"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.42.0",
|
"version": "1.43.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.42.0.zip",
|
"archive_url": "./stylized_tree_generator-1.43.0.zip",
|
||||||
"archive_size": 48711,
|
"archive_size": 50130,
|
||||||
"archive_hash": "sha256:e1b5eb8cb83ecabb60d24ce9c6be75be4c3d168aee86b9a3baa2856bdf440e80"
|
"archive_hash": "sha256:2d87c780f90befc057e6895762c151bd6f4e9514485f5eb98b64993f6304893f"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
BIN
Binary file not shown.
BIN
Binary file not shown.
+118
-19
@@ -1,7 +1,7 @@
|
|||||||
bl_info = {
|
bl_info = {
|
||||||
"name": "Stylized Tree Generator",
|
"name": "Stylized Tree Generator",
|
||||||
"author": "D4rkst3r",
|
"author": "D4rkst3r",
|
||||||
"version": (1, 42, 0),
|
"version": (1, 43, 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",
|
||||||
@@ -2211,6 +2211,13 @@ def _tree_ucx(quell_objs, name, max_verts=32, punkte=None):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
quelle = [v.co.copy() for v in bm.verts]
|
quelle = [v.co.copy() for v in bm.verts]
|
||||||
|
|
||||||
|
# Rohzustand sichern. Das Vereinfachen unten kann bei LANGGESTRECKTEN
|
||||||
|
# Teilen entgleisen - siehe die Notbremse nach dem Aufblasen.
|
||||||
|
bm.verts.index_update()
|
||||||
|
roh_verts = [v.co.copy() for v in bm.verts]
|
||||||
|
roh_faces = [[v.index for v in f.verts] for f in bm.faces]
|
||||||
|
|
||||||
winkel = math.radians(1.0)
|
winkel = math.radians(1.0)
|
||||||
while len(bm.verts) > max_verts and winkel < math.radians(60.0):
|
while len(bm.verts) > max_verts and winkel < math.radians(60.0):
|
||||||
bmesh.ops.dissolve_limit(bm, angle_limit=winkel,
|
bmesh.ops.dissolve_limit(bm, angle_limit=winkel,
|
||||||
@@ -2224,29 +2231,75 @@ def _tree_ucx(quell_objs, name, max_verts=32, punkte=None):
|
|||||||
bm.normal_update()
|
bm.normal_update()
|
||||||
|
|
||||||
# Das Vereinfachen schneidet Ecken ab - die Huelle waere danach KLEINER als
|
# Das Vereinfachen schneidet Ecken ab - die Huelle waere danach KLEINER als
|
||||||
# das Teil und der Spieler wuerde einsinken. Exakt so weit aufblasen, dass
|
# das Teil und der Spieler wuerde einsinken. Also wieder aufweiten.
|
||||||
# wieder alles hineinpasst.
|
#
|
||||||
|
# NICHT durch Skalieren um die Huellenmitte. Das war der erste Ansatz und
|
||||||
|
# entgleist bei langgestreckten Teilen: liegt eine Flaeche fast durch die
|
||||||
|
# Mitte, wird der Hebel winzig und der noetige Faktor explodiert. Gemessen
|
||||||
|
# an einem Stammsegment des schlanken "baum"-Presets (Laenge zu Dicke rund
|
||||||
|
# 13:1) kam Faktor 4.2 heraus - die Huelle war viermal so gross wie das Holz.
|
||||||
|
# Bei der dicken Eiche blieb es bei 1.0-1.4, deshalb ist es lange nicht
|
||||||
|
# aufgefallen.
|
||||||
|
#
|
||||||
|
# Stattdessen je Flaeche den ECHTEN Fehlbetrag messen (wie weit liegt der
|
||||||
|
# schlimmste Quellpunkt vor ihrer Ebene?) und die Vertices um genau diesen
|
||||||
|
# Betrag nach aussen schieben. Das ist eine Verschiebung in Metern, kein
|
||||||
|
# Verhaeltnis - sie kann nicht explodieren.
|
||||||
if bm.faces and quelle:
|
if bm.faces and quelle:
|
||||||
mitte = Vector((0.0, 0.0, 0.0))
|
def _rest():
|
||||||
for v in bm.verts:
|
"""Groesster Abstand, mit dem ein Quellpunkt noch draussen liegt."""
|
||||||
mitte += v.co
|
r = 0.0
|
||||||
mitte /= max(len(bm.verts), 1)
|
|
||||||
faktor = 1.0
|
|
||||||
for f in bm.faces:
|
for f in bm.faces:
|
||||||
n = f.normal
|
n = f.normal
|
||||||
if n.length_squared < 1e-12:
|
if n.length_squared < 1e-12:
|
||||||
continue
|
continue
|
||||||
d = n.dot(f.verts[0].co)
|
d0 = n.dot(f.verts[0].co)
|
||||||
basis = d - n.dot(mitte)
|
r = max(r, max((n.dot(co) - d0) for co in quelle))
|
||||||
if basis <= 1e-9:
|
return r
|
||||||
|
|
||||||
|
# ITERATIV. Ein Vertex gehoert mehreren Flaechen an und wandert entlang
|
||||||
|
# SEINER Normale - die Ebene verschiebt sich dadurch nur um den auf sie
|
||||||
|
# projizierten Anteil, also weniger als noetig. Ein einzelner Durchgang
|
||||||
|
# laesst deshalb einen Rest stehen (gemessen, der Test schlug an). Jeder
|
||||||
|
# weitere Durchgang verkleinert ihn; nach wenigen ist er weg.
|
||||||
|
for _ in range(8):
|
||||||
|
rest = _rest()
|
||||||
|
if rest <= 1e-4:
|
||||||
|
break
|
||||||
|
schuld = {}
|
||||||
|
for f in bm.faces:
|
||||||
|
n = f.normal
|
||||||
|
if n.length_squared < 1e-12:
|
||||||
continue
|
continue
|
||||||
for co in quelle:
|
d0 = n.dot(f.verts[0].co)
|
||||||
noetig = (n.dot(co) - n.dot(mitte)) / basis
|
ueber = max((n.dot(co) - d0) for co in quelle)
|
||||||
if noetig > faktor:
|
if ueber > 1e-9:
|
||||||
faktor = noetig
|
for v in f.verts:
|
||||||
if faktor > 1.0:
|
if ueber > schuld.get(v, 0.0):
|
||||||
for v in bm.verts:
|
schuld[v] = ueber
|
||||||
v.co = mitte + (v.co - mitte) * faktor
|
if not schuld:
|
||||||
|
break
|
||||||
|
for v, d in schuld.items():
|
||||||
|
n = v.normal
|
||||||
|
if n.length_squared < 1e-12:
|
||||||
|
continue
|
||||||
|
v.co = v.co + n.normalized() * d
|
||||||
|
bm.normal_update()
|
||||||
|
|
||||||
|
# Notbremse: geht es nicht auf, ist die vereinfachte Huelle als
|
||||||
|
# Kollision unbrauchbar - dann lieber die unvereinfachte mit ein paar
|
||||||
|
# Vertices mehr.
|
||||||
|
if _rest() > 1e-4 and roh_faces:
|
||||||
|
bm.free()
|
||||||
|
bm = bmesh.new()
|
||||||
|
neu_v = [bm.verts.new(c) for c in roh_verts]
|
||||||
|
bm.verts.ensure_lookup_table()
|
||||||
|
for f in roh_faces:
|
||||||
|
try:
|
||||||
|
bm.faces.new([neu_v[i] for i in f])
|
||||||
|
except ValueError:
|
||||||
|
pass # Doppelflaeche - die gibt es schon
|
||||||
|
bm.normal_update()
|
||||||
|
|
||||||
bmesh.ops.triangulate(bm, faces=bm.faces[:])
|
bmesh.ops.triangulate(bm, faces=bm.faces[:])
|
||||||
me = bpy.data.meshes.new("UCX_" + name)
|
me = bpy.data.meshes.new("UCX_" + name)
|
||||||
@@ -2257,6 +2310,27 @@ def _tree_ucx(quell_objs, name, max_verts=32, punkte=None):
|
|||||||
return ob
|
return ob
|
||||||
|
|
||||||
|
|
||||||
|
def _rinden_punkte(ob):
|
||||||
|
"""Weltkoordinaten der RINDEN-Vertices (Material-Slot 0) eines Faell-Teils.
|
||||||
|
|
||||||
|
Blatt-Cards gehoeren nicht in eine Kollisionshuelle: sie stehen weit vom
|
||||||
|
Holz ab, und der Spieler soll gegen den Ast stossen, nicht gegen das Laub.
|
||||||
|
Gemessen an der Eiche blaeht das Laub die Huellen-Diagonale beim Ast um
|
||||||
|
8-19 % auf - und beim OBERSTEN Stammsegment, das die Krone traegt, um 67 %
|
||||||
|
(1.75 m statt 1.05 m).
|
||||||
|
|
||||||
|
Fallback auf alle Vertices, falls ein Teil keinen zweiten Slot hat: ein
|
||||||
|
reines Stammsegment ist ohnehin komplett Rinde.
|
||||||
|
"""
|
||||||
|
me = ob.data
|
||||||
|
if len(me.materials) > 1:
|
||||||
|
idx = {vi for p in me.polygons if p.material_index == 0
|
||||||
|
for vi in p.vertices}
|
||||||
|
if idx:
|
||||||
|
return [ob.matrix_world @ me.vertices[i].co for i in idx]
|
||||||
|
return [ob.matrix_world @ v.co for v in me.vertices]
|
||||||
|
|
||||||
|
|
||||||
def _stamm_punkte(tree_obj, matrix):
|
def _stamm_punkte(tree_obj, matrix):
|
||||||
"""Stamm-Koordinaten vom Fuss bis zum KRONENANSATZ, in Weltkoordinaten.
|
"""Stamm-Koordinaten vom Fuss bis zum KRONENANSATZ, in Weltkoordinaten.
|
||||||
|
|
||||||
@@ -3044,9 +3118,26 @@ class TREEGEN_OT_export_pieces(Operator):
|
|||||||
# Damit Unreal den zusammengesetzten Baum deckungsgleich rekonstruieren
|
# Damit Unreal den zusammengesetzten Baum deckungsgleich rekonstruieren
|
||||||
# kann: hier stehen alle Teile noch an ihrer Originalposition.
|
# kann: hier stehen alle Teile noch an ihrer Originalposition.
|
||||||
if s.piece_export_combined and teile:
|
if s.piece_export_combined and teile:
|
||||||
|
# Auch die Gesamtdatei bekommt je Teil seine Huelle - dieselbe
|
||||||
|
# Geschwister-Konvention wie beim Stand-Baum: `UCX_<Teilname>`
|
||||||
|
# neben dem Teil, kein Elternteil. Vorher kam die Datei ohne
|
||||||
|
# Kollision an, waehrend die Einzeldateien welche hatten.
|
||||||
|
#
|
||||||
|
# Sie entstehen HIER und nicht in der Schleife weiter unten: dort
|
||||||
|
# sitzt jedes Teil schon auf seinem eigenen Massenmittelpunkt, in
|
||||||
|
# der Gesamtdatei stehen alle noch an ihrer Originalposition.
|
||||||
|
hullen = []
|
||||||
|
if s.piece_ucx:
|
||||||
|
for t in teile:
|
||||||
|
h = _tree_ucx(None, t.name, max_verts=s.piece_ucx_verts,
|
||||||
|
punkte=_rinden_punkte(t))
|
||||||
|
if h is not None:
|
||||||
|
hullen.append(h)
|
||||||
_deselect(context)
|
_deselect(context)
|
||||||
for t in teile:
|
for t in teile:
|
||||||
t.select_set(True)
|
t.select_set(True)
|
||||||
|
for h in hullen:
|
||||||
|
h.select_set(True)
|
||||||
context.view_layer.objects.active = teile[0]
|
context.view_layer.objects.active = teile[0]
|
||||||
bpy.ops.export_scene.fbx(
|
bpy.ops.export_scene.fbx(
|
||||||
filepath=os.path.join(verzeichnis, basis + "_Pieces_All.fbx"),
|
filepath=os.path.join(verzeichnis, basis + "_Pieces_All.fbx"),
|
||||||
@@ -3055,6 +3146,11 @@ class TREEGEN_OT_export_pieces(Operator):
|
|||||||
use_triangles=True, use_custom_props=True, colors_type='SRGB',
|
use_triangles=True, use_custom_props=True, colors_type='SRGB',
|
||||||
add_leaf_bones=False, bake_anim=False, path_mode='AUTO',
|
add_leaf_bones=False, bake_anim=False, path_mode='AUTO',
|
||||||
apply_scale_options='FBX_SCALE_ALL')
|
apply_scale_options='FBX_SCALE_ALL')
|
||||||
|
for h in hullen:
|
||||||
|
hm = h.data
|
||||||
|
bpy.data.objects.remove(h, do_unlink=True)
|
||||||
|
if hm.users == 0:
|
||||||
|
bpy.data.meshes.remove(hm)
|
||||||
|
|
||||||
# --- Je Teil eine Datei, Pivot = Massenmittelpunkt --------------
|
# --- Je Teil eine Datei, Pivot = Massenmittelpunkt --------------
|
||||||
geschrieben = 0
|
geschrieben = 0
|
||||||
@@ -3063,7 +3159,10 @@ class TREEGEN_OT_export_pieces(Operator):
|
|||||||
t.data.transform(Matrix.Translation(-schwer))
|
t.data.transform(Matrix.Translation(-schwer))
|
||||||
t.matrix_basis = Matrix.Identity(4)
|
t.matrix_basis = Matrix.Identity(4)
|
||||||
|
|
||||||
hull = _tree_ucx([t], t.name, max_verts=s.piece_ucx_verts) \
|
# Huelle aus der RINDE, nicht aus allen Vertices: das Laub blaehte
|
||||||
|
# sie beim obersten Stammsegment gemessen um 67 % auf.
|
||||||
|
hull = _tree_ucx(None, t.name, max_verts=s.piece_ucx_verts,
|
||||||
|
punkte=_rinden_punkte(t)) \
|
||||||
if s.piece_ucx else None
|
if s.piece_ucx else None
|
||||||
|
|
||||||
_deselect(context)
|
_deselect(context)
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ Aufruf: blender --background --factory-startup --python tests/test_faell_teile.p
|
|||||||
"""
|
"""
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
import math
|
||||||
import tempfile
|
import tempfile
|
||||||
import importlib.util
|
import importlib.util
|
||||||
from collections import Counter
|
from collections import Counter
|
||||||
@@ -171,13 +172,64 @@ if blatt_summe != cards_gesamt:
|
|||||||
if gesamt:
|
if gesamt:
|
||||||
bpy.ops.wm.read_factory_settings(use_empty=True)
|
bpy.ops.wm.read_factory_settings(use_empty=True)
|
||||||
bpy.ops.import_scene.fbx(filepath=os.path.join(OUT, gesamt[0]))
|
bpy.ops.import_scene.fbx(filepath=os.path.join(OUT, gesamt[0]))
|
||||||
teile = [o for o in bpy.context.scene.objects if o.type == 'MESH']
|
meshes = [o for o in bpy.context.scene.objects if o.type == 'MESH']
|
||||||
|
teile = [o for o in meshes if not o.name.startswith("UCX_")]
|
||||||
|
hullen = [o for o in meshes if o.name.startswith("UCX_")]
|
||||||
alle = [o.matrix_world @ v.co for o in teile for v in o.data.vertices]
|
alle = [o.matrix_world @ v.co for o in teile for v in o.data.vertices]
|
||||||
hoehe = max(c.z for c in alle) - min(c.z for c in alle)
|
hoehe = max(c.z for c in alle) - min(c.z for c in alle)
|
||||||
print("Gesamtdatei: %d Teile, Hoehe %.2f m" % (len(teile), hoehe))
|
print("Gesamtdatei: %d Teile, %d Huellen, Hoehe %.2f m"
|
||||||
|
% (len(teile), len(hullen), hoehe))
|
||||||
if len(teile) != len(einzeln):
|
if len(teile) != len(einzeln):
|
||||||
fails.append("Gesamtdatei hat %d Teile, einzeln waren es %d"
|
fails.append("Gesamtdatei hat %d Teile, einzeln waren es %d"
|
||||||
% (len(teile), len(einzeln)))
|
% (len(teile), len(einzeln)))
|
||||||
|
|
||||||
|
# Die Gesamtdatei kam frueher OHNE Huellen an, waehrend die Einzeldateien
|
||||||
|
# welche hatten - gemeldet von der Spielseite, die interimsweise eigene
|
||||||
|
# Boxen setzen musste. Je Teil GENAU eine Huelle, gleiche Konvention wie
|
||||||
|
# beim Stand-Baum: `UCX_<Teilname>` als Geschwister, kein Elternteil.
|
||||||
|
namen = {o.name for o in teile}
|
||||||
|
for h in hullen:
|
||||||
|
if h.parent is not None:
|
||||||
|
fails.append("Gesamtdatei: Huelle '%s' haengt unter '%s'"
|
||||||
|
% (h.name, h.parent.name))
|
||||||
|
if h.name[4:] not in namen:
|
||||||
|
fails.append("Gesamtdatei: Huelle '%s' gehoert zu keinem Teil"
|
||||||
|
% h.name)
|
||||||
|
fehlend = sorted(n for n in namen if ("UCX_" + n) not in
|
||||||
|
{h.name for h in hullen})
|
||||||
|
if fehlend:
|
||||||
|
fails.append("Gesamtdatei: %d Teile ohne Huelle (%s)"
|
||||||
|
% (len(fehlend), ", ".join(fehlend[:3])))
|
||||||
|
|
||||||
|
# Und die Huelle umschliesst das HOLZ, nicht das Laub. Beim obersten
|
||||||
|
# Stammsegment, das die Krone traegt, war der Unterschied gemessen 67 %
|
||||||
|
# (Diagonale 1.75 m statt 1.05 m) - eine Kollision, die weit ins Laub ragt.
|
||||||
|
nach_name = {o.name: o for o in teile}
|
||||||
|
for h in hullen:
|
||||||
|
koerper = nach_name.get(h.name[4:])
|
||||||
|
if koerper is None:
|
||||||
|
continue
|
||||||
|
hc = [h.matrix_world @ v.co for v in h.data.vertices]
|
||||||
|
rinde_idx = {vi for p in koerper.data.polygons
|
||||||
|
if p.material_index == 0 for vi in p.vertices}
|
||||||
|
if not rinde_idx:
|
||||||
|
continue
|
||||||
|
rc = [koerper.matrix_world @ koerper.data.vertices[i].co
|
||||||
|
for i in rinde_idx]
|
||||||
|
|
||||||
|
def spanne(pts):
|
||||||
|
lo = [min(c[i] for c in pts) for i in range(3)]
|
||||||
|
hi = [max(c[i] for c in pts) for i in range(3)]
|
||||||
|
return math.dist(lo, hi)
|
||||||
|
|
||||||
|
h_d, r_d = spanne(hc), spanne(rc)
|
||||||
|
if h_d < r_d * 0.95:
|
||||||
|
fails.append("%s: Huelle %.2f kleiner als die Rinde %.2f"
|
||||||
|
% (h.name, h_d, r_d))
|
||||||
|
if h_d > r_d * 1.35:
|
||||||
|
fails.append("%s: Huelle %.2f gegen Rinde %.2f (%.2fx) - sie "
|
||||||
|
"umschliesst das Laub mit"
|
||||||
|
% (h.name, h_d, r_d, h_d / r_d))
|
||||||
# Zusammengesetzt muss der Baum wieder seine volle Hoehe haben - das ist
|
# Zusammengesetzt muss der Baum wieder seine volle Hoehe haben - das ist
|
||||||
# der Beleg, dass die Teile deckungsgleich rekonstruierbar sind.
|
# der Beleg, dass die Teile deckungsgleich rekonstruierbar sind.
|
||||||
if hoehe < 3.0:
|
if hoehe < 3.0:
|
||||||
|
|||||||
Reference in New Issue
Block a user