Tree Gen 1.39.0: Kronenfuellung des Obstbaums - Flaeche statt Card-Zahl

Der Obstbaum las sich in der Engine als einzelne belaubte Ast-Schlaeuche mit
Himmel dazwischen. Die Card-Zahl erklaert das nicht; die Blattflaeche pro
Kronenvolumen tut es:
  Eiche (schliesst)   444 Cards, Krone 18.9 m3 -> 24.6 m2/m3
  Obstbaum vorher     399 Cards, Krone 36.2 m3 -> 11.4 m2/m3
Fast doppeltes Volumen bei gleicher Card-Zahl.

Der Vorschlag der Spielseite - mehr und laengere Sub-Aeste als Fuellwerk -
wurde durchgemessen und VERWORFEN: Sub Count 5/7, Sub Length 1.0/1.3, Sub Start
0.20/0.30, Branch Count 12/14 bewegten den Fuellgrad um hoechstens 7 Punkte und
trieben die Dreiecke auf 4310-5162. Strukturell: neue Zweige wachsen aus
denselben wenigen Hauptaesten und vergroessern dabei die Krone, das Volumen
waechst also mit.

Ebenfalls verworfen: mehr Cards (Ziel 740 = 5426 Tris, 26 % ueber Budget, und
der Fuellgrad stieg nur auf 75 %). Und eine kleinere Krone: Branch Length 2.35
sah ueber drei Seeds mit 1.83 m gut aus, im Neun-Seed-Test fielen SECHS Seeds
unter die 1.80 m der Frucht-Streuung.

Was wirkt, ist die Card-GROESSE (neu: LEAF_SIZE_PRESET, Faktor je Preset). Sie
kostet kein Dreieck, weil die Card-Zahl gedeckelt ist - aber OVERDRAW, und der
ist bei Bueschel-Cards der eigentliche Engpass. Deshalb der maessige Faktor
1.47: Deckung von 46 auf 87-114 % der Eiche, Card-Flaeche 414 -> 893 m2, bei
3697 statt 3714 Tris. Faktor 1.71 brachte 79 % Fuellgrad bei 2.7-facher
Flaeche - das waere eine Overdraw-Entscheidung der Spielseite.

test_obstbaum prueft die Deckung jetzt mit, und zwar gegen die im SELBEN Lauf
gemessene Eiche statt gegen eine feste Zahl: die Deckung haengt an der
Card-Flaeche, eine 6-Tri-Card bringt das Dreifache einer flachen Raute. Der
erste Anlauf des Checks ist genau daran gescheitert.
Gegenprobe gefahren: mit Faktor 1.0 meldet der Test 9 Fehler bei 42-54 % der
Eiche.

26 Tests bestehen.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-08-06 14:09:52 +02:00
co-authored by Claude Opus 4.8
parent 9bcb80765a
commit 5a43ead8d1
8 changed files with 191 additions and 14 deletions
+41
View File
@@ -563,6 +563,47 @@ 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.
### Kronenfuellung: es zaehlt die Flaeche, nicht die Card-Zahl
Der Obstbaum las sich in der Engine als einzelne belaubte Ast-Schlaeuche mit
Himmel dazwischen - "behaarte Finger". Die Card-Zahl erklaert das nicht: 400
Cards koennen eine geschlossene Haube sein oder fuenf Schlaeuche. Was es
erklaert, ist die Blattflaeche pro Kronenvolumen:
| | Cards | Krone | Flaeche/Volumen |
|---|---|---|---|
| Eiche (schliesst) | 444 | r 1.26 x 3.78 = 18.9 m3 | **24.6 m2/m3** |
| Obstbaum vorher | 399 | r 2.00 x 2.88 = 36.2 m3 | **11.4 m2/m3** |
Die Obstbaum-Krone hat fast das doppelte Volumen bei gleicher Card-Zahl.
**Durchgemessen und verworfen: mehr Zweigwerk.** Sub Count 5 und 7, Sub Length
1.0 und 1.3, Sub Start 0.20 und 0.30, Branch Count 12 und 14 - der Fuellgrad
bewegte sich um hoechstens 7 Punkte, die Dreiecke stiegen auf 4310-5162. Der
Grund ist strukturell: neue Zweige wachsen aus denselben wenigen Hauptaesten
und vergroessern dabei die Krone. Das Volumen waechst mit, die gedeckelte
Card-Zahl nicht.
**Ebenfalls verworfen: mehr Cards.** Ziel 740 (rechnerisch noetig fuer 25/m3)
ergab 5426 Tris - 26 % ueber Budget - und hob den Fuellgrad nur auf 75 %.
Cards zaehlen ist nicht dasselbe wie Flaeche decken.
**Ebenfalls verworfen: kleinere Krone.** Branch Length 2.35 sah ueber drei
Seeds mit Kronenradius 1.83 m gut aus - im Neun-Seed-Test fielen dann SECHS
Seeds unter die 1.80 m, die die Frucht-Streuung braucht (1.73-1.79).
Was bleibt, ist die Card-GROESSE (`LEAF_SIZE_PRESET`): sie kostet kein einziges
Dreieck, weil die Zahl gedeckelt ist. Sie kostet OVERDRAW, und der ist bei
Bueschel-Cards der eigentliche Engpass. Faktor 1.47 hebt die Deckung von 46 auf
87-114 % der Eiche und verdoppelt die Card-Flaeche (414 -> 893 m2), bei 3697
statt 3714 Tris.
Der Test vergleicht gegen die **im selben Lauf gemessene Eiche**, nicht gegen
eine feste Zahl. Die Deckung haengt an der Card-Flaeche - eine 6-Tri-Card
bringt das Dreifache einer flachen Raute -, und der erste Anlauf des Checks ist
genau daran gescheitert: die Schwelle stammte aus einem Messskript mit anderer
Card und meldete neun Fehler auf einem korrekten Baum.
### Zwei Knoepfe, ein Weg ### Zwei Knoepfe, ein Weg
Blattwerk entsteht an zwei Stellen: ueber das Haekchen "_Leaf anlegen" beim Blattwerk entsteht an zwei Stellen: ueber das Haekchen "_Leaf anlegen" beim
+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.38.0" version = "1.39.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"
+3 -3
View File
@@ -50,17 +50,17 @@
<td>34.1KB</td> <td>34.1KB</td>
</tr> </tr>
<tr> <tr>
<td><tt><a href="./stylized_tree_generator-1.38.0.zip?repository=.%2Findex.json&blender_version_min=4.2.0">stylized_tree_generator-1.38.0</a></tt></td> <td><tt><a href="./stylized_tree_generator-1.39.0.zip?repository=.%2Findex.json&blender_version_min=4.2.0">stylized_tree_generator-1.39.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>45.0KB</td> <td>46.0KB</td>
</tr> </tr>
</table> </table>
<center><p>Built 2026-08-06, 11:22</p></center> <center><p>Built 2026-08-06, 12:09</p></center>
</body> </body>
</html> </html>
+4 -4
View File
@@ -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.38.0", "version": "1.39.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.38.0.zip", "archive_url": "./stylized_tree_generator-1.39.0.zip",
"archive_size": 46032, "archive_size": 47076,
"archive_hash": "sha256:0d65140f04d1ba8289a72c69c44c95b8e7b7300681695b43c7cba7a8f2ec6c9b" "archive_hash": "sha256:44a7601ffb6cfb8004d213416cdc0fdd5592e0c2596a89ed6a32b2c06f12bb4d"
} }
] ]
} }
Binary file not shown.
Binary file not shown.
+45 -2
View File
@@ -1,7 +1,7 @@
bl_info = { bl_info = {
"name": "Stylized Tree Generator", "name": "Stylized Tree Generator",
"author": "D4rkst3r", "author": "D4rkst3r",
"version": (1, 38, 0), "version": (1, 39, 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",
@@ -197,6 +197,11 @@ PRESETS = {
"obstbaum": { "obstbaum": {
"Height": 3.8, "Trunk Radius": 0.13, "Bend": 0.3, "Taper": 0.7, "Height": 3.8, "Trunk Radius": 0.13, "Bend": 0.3, "Taper": 0.7,
"Branch Count": 9, # offene Krone: wenige Leitaeste, Licht durch "Branch Count": 9, # offene Krone: wenige Leitaeste, Licht durch
# Bleibt bei 2.55. Die Krone zu verkleinern waere der zweite Hebel gegen
# den niedrigen Fuellgrad gewesen - bei 2.35 fielen aber SECHS der neun
# Seeds unter den Kronenradius von 1.80 m, den die Frucht-Streuung
# braucht (1.73-1.79). Ueber drei Seeds gemittelt sah es mit 1.83 noch
# gut aus; erst der Neun-Seed-Test hat es aufgedeckt.
"Branch Length": 2.55, "Branch Up": 0.7, "Branch Length": 2.55, "Branch Up": 0.7,
"Branch Start": 0.52, "Branch End": 0.90, "Branch Start": 0.52, "Branch End": 0.90,
"Crown Taper": -0.30, "Crown Bulge": 0.60, # rund, Bauch in der Mitte "Crown Taper": -0.30, "Crown Bulge": 0.60, # rund, Bauch in der Mitte
@@ -1627,6 +1632,41 @@ LEAF_STUFEN_PRESET = {
"obstbaum": {3: 420}, "obstbaum": {3: 420},
} }
# Card-GROESSE je Preset, als Faktor auf die eingestellte Groesse.
#
# Warum das ueberhaupt eine eigene Tabelle braucht: die Kronenfuellung haengt an
# der Card-FLAECHE, nicht an der Card-Zahl - und wie viel Flaeche eine Krone
# braucht, haengt an ihrem Volumen. Gemessen (S4, Cards je m3 Kronenzylinder /
# Anteil belegter 0.4-m-Voxel):
#
# Eiche 444 Cards, Krone r 1.26 x 3.78 = 18.9 m3 -> 23.7/m3, 88 % belegt
# Obstbaum 399 Cards, Krone r 2.00 x 2.88 = 36.2 m3 -> 11.1/m3, 60 % belegt
#
# Die Obstbaum-Krone hat fast das doppelte Volumen bei gleicher Card-Zahl -
# daher las sie sich als einzelne belaubte Ast-Schlaeuche mit Himmel dazwischen
# ("behaarte Finger", Abnahme der Spielseite).
#
# Durchgemessen und VERWORFEN: mehr und laengere Sub-Aeste als Fuellwerk. Sub
# Count 5/7, Sub Length 1.0/1.3, Sub Start 0.20/0.30, Branch Count 12/14 - der
# Fuellgrad bewegte sich um hoechstens 7 Punkte, die Dreiecke stiegen auf
# 4310-5162. Der Grund ist strukturell: neue Zweige wachsen aus denselben wenigen
# Hauptaesten und vergroessern dabei die Krone, waehrend die Card-Zahl gedeckelt
# bleibt. Das Volumen waechst also mit.
#
# Ebenfalls verworfen: mehr Cards. Bei Ziel 740 (was rechnerisch fuer 25/m3
# noetig waere) kamen 5426 Tris heraus - 26 % ueber Budget - und der Fuellgrad
# stieg nur auf 75 %. Cards zaehlen ist nicht dasselbe wie Flaeche decken.
#
# Was wirkt, ist die Card-Groesse: sie kostet KEIN einziges Dreieck, weil die
# Zahl gedeckelt ist. Sie kostet aber OVERDRAW - und der ist bei Bueschel-Cards
# der eigentliche Engpass, nicht der Dreieckszaehler. Deshalb der maessige
# Faktor: 1.47 hebt den Fuellgrad von 60 auf 74 % und verdoppelt die Card-
# Flaeche (414 -> 832 m2). 1.71 brachte 79 % bei 2.7-facher Flaeche - das waere
# eine Overdraw-Entscheidung der Spielseite, keine Generator-Frage.
LEAF_SIZE_PRESET = {
"obstbaum": 1.47,
}
def _stufen_anteile(growth_t, preset=None): def _stufen_anteile(growth_t, preset=None):
"""Ziel-Cardzahl, Groessen- und Kronenansatz-Wert fuer einen Wachstums-Fortschritt. """Ziel-Cardzahl, Groessen- und Kronenansatz-Wert fuer einen Wachstums-Fortschritt.
@@ -1803,7 +1843,10 @@ def make_leaves(context, tree_obj, card_obj, name=None, card_coll=None,
basis = dict(LEAF_DEFAULTS) basis = dict(LEAF_DEFAULTS)
basis.update(overrides) basis.update(overrides)
overrides["Size"] = basis["Size"] * g_anteil # Preset-Faktor auf die Card-Groesse: eine weite Krone braucht mehr
# Card-FLAECHE, um geschlossen zu wirken (siehe LEAF_SIZE_PRESET).
p_faktor = LEAF_SIZE_PRESET.get(tree_obj.get("preset") or "", 1.0)
overrides["Size"] = basis["Size"] * g_anteil * p_faktor
overrides["Max Radius"] = basis["Max Radius"] * r_faktor overrides["Max Radius"] = basis["Max Radius"] * r_faktor
overrides["Min Height"] = h_anteil overrides["Min Height"] = h_anteil
+97 -4
View File
@@ -74,14 +74,65 @@ def inseln(m):
return out return out
def deckung_messen(preset, seed):
"""Blattflaeche je m3 Kronenzylinder - fuer beliebiges Preset.
Der Wert haengt an der verwendeten Card (Flaeche!), taugt also NICHT als
absolute Schwelle. Verglichen wird deshalb gegen die Eiche, die in
demselben Lauf mit DERSELBEN Card gemessen wird.
"""
for o in list(bpy.context.scene.objects):
if o is not card:
bpy.data.objects.remove(o, do_unlink=True)
s.preset = preset
s.art_name = "Ref"
s.variant = 1
s.seed = seed
s.use_growth = False
bpy.ops.object.treegen_create()
baum = [o for o in bpy.context.scene.objects
if o.type == 'MESH' and o is not card
and not o.name.endswith(("_Leaf", "_Frucht"))][0]
for o in bpy.context.scene.objects:
o.select_set(o is baum)
bpy.context.view_layer.objects.active = baum
bpy.ops.object.treegen_leaves()
bl = bpy.data.objects.get(baum.name + "_Leaf")
dg = bpy.context.evaluated_depsgraph_get()
ev = bl.evaluated_get(dg)
m = ev.to_mesh()
flaeche = sum(p.area for p in m.polygons)
mitten = []
for grp in inseln(m):
pts = [m.vertices[i].co for i in grp]
mitten.append((sum(p.x for p in pts) / len(pts),
sum(p.y for p in pts) / len(pts),
sum(p.z for p in pts) / len(pts)))
ev.to_mesh_clear()
lr = max(math.hypot(x, y) for x, y, _z in mitten)
lz = [z for _x, _y, z in mitten]
vol = math.pi * lr ** 2 * (max(lz) - min(lz))
return flaeche / vol if vol else 0.0
# Die EICHE ist der Massstab: sie schliesst laut Abnahme der Spielseite, der
# Obstbaum tat es nicht. Hier gemessen statt als Konstante hingeschrieben,
# weil der Wert an der Card-Flaeche haengt - eine feste Zahl waere nur fuer
# genau diese Testkarte richtig.
EICHE = sum(deckung_messen("eiche", sd) for sd in (1, 2, 3)) / 3.0
print("Massstab Eiche: %.2f m2 Blatt je m3 Krone (mit DIESER Testcard)" % EICHE)
print("")
# Die Seeds sind die der Spielseite: Apfel 1/15/22, Kirsche 2/16/23, # Die Seeds sind die der Spielseite: Apfel 1/15/22, Kirsche 2/16/23,
# Birne 3/17/24. Ein Preset, das nur bei einem Seed passt, ist keines. # Birne 3/17/24. Ein Preset, das nur bei einem Seed passt, ist keines.
SEEDS = (1, 15, 22, 2, 16, 23, 3, 17, 24) SEEDS = (1, 15, 22, 2, 16, 23, 3, 17, 24)
print("%-6s %7s %6s %9s %9s %9s %7s" print("%-6s %7s %6s %9s %9s %9s %7s %9s"
% ("Seed", "Hoehe", "B/H", "KronR", "Card-Max", "KronR/Card", "Ansatz")) % ("Seed", "Hoehe", "B/H", "KronR", "Card-Max", "KronR/Card", "Ansatz",
"m2/m3"))
verh = [] verh = []
deckungen = []
for seed in SEEDS: for seed in SEEDS:
for o in list(bpy.context.scene.objects): for o in list(bpy.context.scene.objects):
if o is not card: if o is not card:
@@ -115,19 +166,57 @@ for seed in SEEDS:
ansatz = 100.0 * (min(astz) - z0) / hoehe if astz else 0.0 ansatz = 100.0 * (min(astz) - z0) / hoehe if astz else 0.0
card_max = 0.0 card_max = 0.0
blatt_flaeche = 0.0
kron_zyl = 0.0
if bl is not None: if bl is not None:
ev = bl.evaluated_get(dg) ev = bl.evaluated_get(dg)
m2 = ev.to_mesh() m2 = ev.to_mesh()
blatt_flaeche = sum(p.area for p in m2.polygons)
mitten = []
for grp in inseln(m2): for grp in inseln(m2):
pts = [m2.vertices[i].co for i in grp] pts = [m2.vertices[i].co for i in grp]
card_max = max(card_max, card_max = max(card_max,
max((a - b).length for a in pts for b in pts)) max((a - b).length for a in pts for b in pts))
mitten.append((sum(p.x for p in pts) / len(pts),
sum(p.y for p in pts) / len(pts),
sum(p.z for p in pts) / len(pts)))
ev.to_mesh_clear() ev.to_mesh_clear()
if mitten:
lr = max(math.hypot(x, y) for x, y, _z in mitten)
lz = [z for _x, _y, z in mitten]
kron_zyl = math.pi * lr ** 2 * (max(lz) - min(lz))
v = kron_r / card_max if card_max else 0.0 v = kron_r / card_max if card_max else 0.0
verh.append(v) verh.append(v)
print("%-6d %7.2f %6.2f %9.2f %9.2f %9.2f %6.0f%%" deckung = blatt_flaeche / kron_zyl if kron_zyl else 0.0
% (seed, hoehe, 2 * kron_r / hoehe, kron_r, card_max, v, ansatz)) deckungen.append(deckung)
print("%-6d %7.2f %6.2f %9.2f %9.2f %9.2f %6.0f%% %9.1f"
% (seed, hoehe, 2 * kron_r / hoehe, kron_r, card_max, v, ansatz,
deckung))
# Kronen-DECKUNG: Blattflaeche je m3 Kronenvolumen.
#
# Die Card-ZAHL sagt das nicht: 400 Cards koennen eine geschlossene Haube
# sein oder fuenf belaubte Schlaeuche mit Himmel dazwischen ("behaarte
# Finger", von der Spielseite abgenommen). Was es sagt, ist die Flaeche pro
# Volumen - und die trennt die beiden Faelle sauber:
# Eiche (schliesst) 465 m2 / 18.9 m3 = 24.6
# Obstbaum vorher (offen) 414 m2 / 36.2 m3 = 11.4
# Die Obstbaum-Krone hat fast das doppelte Volumen; bei gleicher Card-Zahl
# blieb die halbe Deckung uebrig.
#
# Verglichen wird gegen die im selben Lauf gemessene Eiche, NICHT gegen
# eine feste Zahl: die Deckung haengt an der Card-Flaeche, und eine
# 6-Tri-Buescheldkarte bringt das Dreifache einer flachen Raute. Eine
# hart notierte 20 waere nur fuer genau eine Testcard richtig gewesen -
# der erste Anlauf dieses Checks ist genau daran gescheitert.
# 60 % der Eiche: der Obstbaum liegt bauartbedingt darunter (weitere
# Krone bei gleichem Budget), lag vorher aber bei 46 %.
if deckung < EICHE * 0.60:
fails.append("Seed %d: nur %.1f m2 Blatt je m3 Krone, Eiche %.1f "
"(%.0f %%) - die Krone liest sich als einzelne "
"Ast-Schlaeuche" % (seed, deckung, EICHE,
100 * deckung / EICHE))
# DIE funktionale Bedingung. # DIE funktionale Bedingung.
if v < 1.3: if v < 1.3:
@@ -152,6 +241,10 @@ for seed in SEEDS:
# funktionale (Kronenradius), weil an ihr die Frucht-Streuung haengt. # funktionale (Kronenradius), weil an ihr die Frucht-Streuung haengt.
print("") print("")
print("Kronenradius/Card ueber alle Seeds: %.2f - %.2f" % (min(verh), max(verh))) print("Kronenradius/Card ueber alle Seeds: %.2f - %.2f" % (min(verh), max(verh)))
print("Kronen-Deckung ueber alle Seeds: %.1f - %.1f m2/m3, Eiche %.1f "
"(%.0f-%.0f %%)"
% (min(deckungen), max(deckungen), EICHE,
100 * min(deckungen) / EICHE, 100 * max(deckungen) / EICHE))
if fails: if fails:
print("ERGEBNIS: %d FEHLER" % len(fails)) print("ERGEBNIS: %d FEHLER" % len(fails))