Compare commits

3 Commits
Author SHA1 Message Date
D4rkst3r 4cf7097fa3 Kronen-Huelle nach Abnahme im Wald auf main 2026-08-07 15:40:49 +02:00
D4rkst3randClaude Opus 4.8 a718476592 Tree Gen 1.48.0: Kronen-Huelle, weniger Blaetter, Formband neu geeicht
Vom User im Wald abgenommen: "sehr schoen etwas weniger blaetter und wir
koennen das fuer alle baeume uebernehmen".

1) KRONEN-HUELLE statt Einzelkappen
   Kugeln auf die Aeste, Mesh to Volume, Volume to Mesh, dann Streuung auf der
   verschmolzenen Oberflaeche - der Standard-Ablauf aus den
   Produktions-Breakdowns, den wir uebersprungen hatten. Die Aeste verschwinden
   im Inneren der Krone; die Polster sind nicht mehr als getrennte Koerper
   sichtbar.

   Drei Befunde, alle gemessen:
   - ast_id kam nur an 3 von 10 Aesten an. Nicht das Sampling, sondern die
     Eingabe: nur VIER Cluster-Zentren auf drei Aesten. Sichtbar erst mit
     Points to Vertices - to_mesh() auf eine Punktwolke liefert eine leere Mesh
     und misst nichts.
   - S1 stand kahl. Reihe ueber die Stufen 0/20/41/109 Zentren: allmaehlicher
     Abfall, keine Klippe, also die Cluster-Dichte. "Dichte" ist Kugeln je m2
     Zweigflaeche, die schrumpft quadratisch - sie laeuft jetzt mit 1/g^2
     gegen die Groesse, damit die ANZAHL konstant bleibt. Skalenfrei, kein
     Sonderweg fuer Setzlinge.
   - 31 % der Cards lagen unter dem Kronenansatz: die Verschmelzung weitet die
     Form nach unten, der Filter griff nur auf den Zentren. Jetzt auch auf der
     Huelle.

2) WENIGER BLAETTER (-15 % auf die Stufen-Ziele)
   Gemessen an der Eiche: 381 -> 319 Cards, Deckung 10.4 -> 8.6, 372 Tris
   gespart. Der Gegenversuch - Cards um 15 % verkleinern - kostet MEHR Deckung
   (7.9, die Flaeche geht quadratisch) und spart kein Dreieck.

3) FORMBAND um das Abgenommene gelegt, nicht umgekehrt
   Alle Referenzformen unter der neuen Bauform neu gemessen:
     Eiche      u/m 1.04-1.16   o/m 0.79-0.91
     abgenommen u/m 0.78-1.02   o/m 0.68-0.79
     Trichter   u/m 0.68-0.78   o/m 1.04-1.14
     Pilz       u/m 0.59-0.90   o/m 0.82-0.95
     Haube      u/m 0.94-1.03   o/m 0.71-0.78

   u/m TRENNT NICHTS MEHR - der abgenommene Bereich ueberlappt den Pilz und
   stoesst an den Trichter. Die alte 0.82-Schranke stammt aus der
   Einzelkappen-Aera; dagegen zu kalibrieren haette die Krone verbogen, um eine
   Zahl zu retten, die ihre Aussagekraft verloren hat. u/m wird nur noch
   mitgedruckt. o/m trennt sauber und traegt die Formpruefung allein.

27 Tests bestehen.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-07 15:40:49 +02:00
D4rkst3randClaude Opus 4.8 869b008d4f WIP Kronen-Huelle: Kugeln verschmelzen statt Einzelkappen
NICHT auf main - ein Kriterium steht noch offen (siehe unten). 26 von 27 Tests
gruen.

Die Cards sitzen nicht mehr auf je einer eigenen Halbkugel je Cluster, sondern
auf EINER verschmolzenen Kronen-Huelle: Kugeln auf die Zweige, Mesh to Volume,
Volume to Mesh, dann Distribute Points on Faces auf der Huelle. Das ist der
Standard-Ablauf aus den Produktions-Breakdowns (80.lv Meadows, The Witness),
den wir uebersprungen hatten.

Wirkung: geschlossene Kronen, die Aeste verschwinden im Inneren, keine
Polster-Grenzen und keine nackten Spitzen mehr. 381 Cards an der Eiche in 0.9 s.

Drei Befunde auf dem Weg, alle gemessen statt geraten:

1) ast_id kam nur an 3 von 10 Aesten an. Ursache war NICHT das Sampling,
   sondern die Eingabe: es existierten nur VIER Cluster-Zentren auf drei
   Aesten. In der alten Bauform regelte die Bisektion die Cluster-Dichte selbst
   auf ~550 hoch; seit sie stattdessen die Cards auf der Huelle regelt, muss der
   Startwert stimmen. Sichtbar gemacht mit Points to Vertices - to_mesh() auf
   eine reine Punktwolke liefert eine leere Mesh und misst nichts.

2) S1 stand komplett kahl. Die Reihe ueber die Stufen zeigte 0 / 20 / 41 / 109
   Zentren - allmaehlicher Abfall bis null, keine Klippe. Also die
   Cluster-Dichte, nicht die Voxel-Groesse. "Dichte" ist Kugeln je m2
   Zweigflaeche, und die schrumpft mit dem Quadrat der Groesse; sie laeuft jetzt
   mit 1/g^2 gegen die Baumgroesse, damit die ANZAHL ueber die Stufen konstant
   bleibt. S1 hat jetzt 8 Cluster und 58 Cards.

3) 31 % der Cards lagen unter dem Kronenansatz. Die Verschmelzung weitet die
   Form um bis zu einen Kugelradius nach unten, der Filter griff aber nur auf
   den Zentren. Jetzt auch auf der Huelle.

OFFEN: test_obstbaum, u/m 0.75-0.80 gegen die Schranke 0.82. Die stammt aus der
Einzelkappen-Aera. Bevor dagegen kalibriert wird, muss geklaert sein, ob die
Zahl unter der neuen Bauform noch dasselbe bedeutet - genau wie beim
Kugel-Kennwert, der sich als geerbte Konstante herausstellte. Die ehrliche
Antwort ist vermutlich ein neu gemessenes Band, relativ zur Eiche aus demselben
Lauf.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-07 14:03:15 +02:00
8 changed files with 280 additions and 81 deletions
+77
View File
@@ -702,6 +702,83 @@ Gegenprobe ohne die Boden-Klemmung: vier Fehler.
Drei bestehende Tests zaehlten die Huelle als Render-Mesh mit und schlugen zu
Recht an - sie filtern jetzt `UCX_`.
### Die Kronen-Huelle: Kugeln verschmelzen, dann streuen
Der "behaarte Finger"-Look kam zurueck, weil wir einen Schritt der
Standard-Technik uebersprungen hatten. Die Produktions-Breakdowns (80.lv
"Meadows", "The Witness"-Baeume) machen es so: Kugeln auf die Aeste setzen,
sie zu EINER Kronenmasse verschmelzen, und die Cards ueber deren **Oberflaeche**
streuen. Wir hatten je Cluster eine eigene Halbkugel bestueckt - die Polster
blieben dadurch als getrennte Koerper sichtbar, ein Laubhandschuh je Ast.
Jetzt: Kugeln instanzieren -> `Mesh to Volume` -> `Volume to Mesh` (das ist die
metaball-artige Verschmelzung) -> `Distribute Points on Faces` auf der Huelle.
Die Aeste verschwinden im Inneren der Krone. Vom User im Wald abgenommen.
**Drei Befunde auf dem Weg, alle gemessen statt geraten:**
`ast_id` kam nur an 3 von 10 Aesten an. Ursache war NICHT das Sampling, sondern
die Eingabe: es existierten nur **vier Cluster-Zentren auf drei Aesten**. In der
alten Bauform regelte die Bisektion die Cluster-Dichte selbst hoch; seit sie
stattdessen die Cards auf der Huelle regelt, muss der Startwert stimmen.
Sichtbar wurde das erst mit `Points to Vertices` - `to_mesh()` auf eine reine
Punktwolke liefert eine LEERE Mesh und misst nichts. Ein Messwerkzeug, das nicht
messen kann, sieht aus wie ein Befund.
S1 stand komplett kahl. Die Reihe ueber die Stufen zeigte **0 / 20 / 41 / 109**
Zentren - allmaehlicher Abfall bis null, keine Klippe. Also die Cluster-Dichte,
nicht die Voxel-Groesse. "Dichte" heisst Kugeln je m2 ZWEIGflaeche, und die
schrumpft mit dem QUADRAT der Groesse; sie laeuft jetzt mit 1/g^2 gegen die
Baumgroesse, damit die ANZAHL konstant bleibt. Das ist die richtige Groesse:
eine Krone braucht eine bestimmte Zahl Ballen zum Verschmelzen, nicht eine
bestimmte Dichte. Damit ist die Bauform skalenfrei - kein Sonderweg fuer
Setzlinge.
31 % der Cards lagen unter dem Kronenansatz. Die Verschmelzung weitet die Form
um bis zu einen Kugelradius nach unten, der Filter griff aber nur auf den
Zentren. Jetzt auch auf der Huelle - sonst waere der Fehler, gegen den der
Filter gebaut wurde, durch die Hintertuer zurueckgekommen.
### Weniger Blaetter, und warum die ZAHL der richtige Hebel war
Nach der Abnahme wollte der User "etwas weniger Blaetter". Zwei Hebel standen
zur Wahl, beide gemessen (Eiche):
| | Cards | Tris | Blattflaeche je Huellenflaeche |
|---|---|---|---|
| vorher | 381 | 3714 | 10.36 |
| Ziel -15 % | 319 | 3342 | 8.64 |
| Cards -15 % kleiner | 381 | 3714 | 7.93 |
Die Zahl zu senken kostet weniger Deckung UND spart 372 Dreiecke; die Cards zu
verkleinern kostet mehr Deckung (die Flaeche geht quadratisch) und spart kein
einziges. Der woertliche Wunsch war ausserdem der messbar bessere.
### Das Formband gehoert um das Abgenommene, nicht umgekehrt
Nach dem Umbau lag `u/m` bei 0.78-1.02 gegen eine Schranke von 0.82. Die
naheliegende Reaktion waere gewesen, die Krone auf die Zahl zu kalibrieren.
Stattdessen: alle Referenzformen unter der NEUEN Bauform neu durchmessen.
| Form | u/m | o/m |
|---|---|---|
| Eiche | 1.04-1.16 | 0.79-0.91 |
| **abgenommen** | **0.78-1.02** | **0.68-0.79** |
| Trichter | 0.68-0.78 | 1.04-1.14 |
| Pilz | 0.59-0.90 | 0.82-0.95 |
| Haube | 0.94-1.03 | 0.71-0.78 |
**Daraus folgt etwas Unbequemes: `u/m` trennt nichts mehr.** Der abgenommene
Bereich ueberlappt den Pilz und stoesst an den Trichter. Die 0.82 stammten aus
der Einzelkappen-Aera - dagegen zu kalibrieren haette die Krone verbogen, um
eine Zahl zu retten, die ihre Aussagekraft verloren hat. Dieselbe Falle wie beim
Kugel-Kennwert eine Runde zuvor, nur eine Kennzahl weiter.
`o/m` trennt dagegen sauber (abgenommen bis 0.79, Pilz ab 0.82, Trichter ab
1.04) und traegt die Formpruefung jetzt allein. `u/m` wird nur noch
mitgedruckt - ein Kriterium, das die Fehlformen nicht mehr ausschliesst, ist
schlimmer als keines: es sieht aus wie ein Schutz und ist keiner.
### Der Hochstamm muss auf JEDER Stufe stehen
Bis 1.46 war am Obstbaum nur S4 kalibriert. Im Welt-Test ist der User auf einen
+1 -1
View File
@@ -1,7 +1,7 @@
schema_version = "1.0.0"
id = "stylized_tree_generator"
version = "1.47.0"
version = "1.48.0"
name = "Stylized Tree Generator"
tagline = "Parametrische Baeume, Palmen, Bueschen mit Wachstums-Stufen"
maintainer = "D4rkst3r"
+3 -3
View File
@@ -50,17 +50,17 @@
<td>34.5KB</td>
</tr>
<tr>
<td><tt><a href="./stylized_tree_generator-1.47.0.zip?repository=.%2Findex.json&blender_version_min=4.2.0">stylized_tree_generator-1.47.0</a></tt></td>
<td><tt><a href="./stylized_tree_generator-1.48.0.zip?repository=.%2Findex.json&blender_version_min=4.2.0">stylized_tree_generator-1.48.0</a></tt></td>
<td>Stylized Tree Generator</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>4.2.0 - ~</td>
<td>all</td>
<td>all</td>
<td>53.3KB</td>
<td>55.1KB</td>
</tr>
</table>
<center><p>Built 2026-08-07, 11:33</p></center>
<center><p>Built 2026-08-07, 13:38</p></center>
</body>
</html>
+4 -4
View File
@@ -82,7 +82,7 @@
"id": "stylized_tree_generator",
"name": "Stylized Tree Generator",
"tagline": "Parametrische Baeume, Palmen, Bueschen mit Wachstums-Stufen",
"version": "1.47.0",
"version": "1.48.0",
"type": "add-on",
"maintainer": "D4rkst3r",
"license": [
@@ -98,9 +98,9 @@
"Mesh",
"Modeling"
],
"archive_url": "./stylized_tree_generator-1.47.0.zip",
"archive_size": 54533,
"archive_hash": "sha256:0c1ba47d4fe89d1c45dbff090d04feaa136bea349cd30386127bc70ab2acd8eb"
"archive_url": "./stylized_tree_generator-1.48.0.zip",
"archive_size": 56393,
"archive_hash": "sha256:90b1d48c25a595f0d8c6e0cd63722aa246914998979cbbec4f370b3a9c56a2b9"
}
]
}
Binary file not shown.
Binary file not shown.
+165 -51
View File
@@ -1,7 +1,7 @@
bl_info = {
"name": "Stylized Tree Generator",
"author": "D4rkst3r",
"version": (1, 47, 0),
"version": (1, 48, 0),
"blender": (4, 2, 0),
"location": "View3D > Sidebar > Tree Gen",
"description": "Parametrischer Baum-/Palmen-/Busch-Generator (Geometry Nodes) mit Wachstums-Stufen",
@@ -1381,8 +1381,22 @@ LEAF_DEFAULTS = {
# CLUSTER-Zentren, nicht mehr einzelne Cards - jedes Zentrum traegt SECHS
# Cards auf seiner Halbkugel. Die Dichte liegt entsprechend niedriger; die
# Bisektion regelt sie ohnehin auf die Ziel-Cardzahl ein.
"Density": 30.0,
# Cards je m2 KRONEN-HUELLE. Die Bisektion regelt sie auf die Ziel-Cardzahl
# ein; der Startwert muss nur die Groessenordnung treffen.
"Density": 12.0,
"Cluster Radius": 0.45,
# Kugeln je m2 Zweigflaeche - bestimmt, wie klumpig die Huelle wird.
#
# Der Wert sieht hoch aus, ist es aber nicht: die zulaessige ZWEIGflaeche
# eines Baums ist winzig (duenne Zweige oberhalb des Kronenansatzes), und
# die Spitzen-Gewichtung nimmt nochmal davon weg. Mit 30 entstanden
# gemessen VIER Kugeln auf drei Aesten - die Huelle war ein Klumpen, und
# sieben Aeste bekamen beim Faellen kein Laub. In der alten Bauform regelte
# die Bisektion diese Dichte selbst auf rund 550 hoch; seit sie stattdessen
# die Cards auf der Huelle regelt, muss der Startwert hier stimmen.
"Cluster Density": 550.0,
# Aufloesung der Verschmelzung, in Metern.
"Voxel Size": 0.12,
# DER Volumen-Hebel. Bei 0.02 trugen nur die aeussersten Zweigspitzen Laub -
# die Krone hatte nur Kontur und war innen nackt. Bei 0.04 wachsen Bueschel
# auch am mittleren Geaest und die Krone bekommt Tiefe. Am fertigen Baum in
@@ -1445,10 +1459,19 @@ def build_leaf_group(rebuild=False):
add_in("Size Var", 'NodeSocketFloat', LEAF_DEFAULTS["Size Var"], 0.0, 1.0)
add_in("Tilt", 'NodeSocketFloat', LEAF_DEFAULTS["Tilt"], 0.0, 3.14159)
add_in("Seed", 'NodeSocketInt', LEAF_DEFAULTS["Seed"], 0, 9999)
# Radius der Bueschel-Kappe um jedes Cluster-Zentrum. Siehe den Block
# "BUESCHEL-SCHALE" weiter unten.
# Radius der Kugeln, aus denen die Kronen-Huelle verschmilzt. Siehe den
# Block "KRONEN-HUELLE" weiter unten.
add_in("Cluster Radius", 'NodeSocketFloat',
LEAF_DEFAULTS["Cluster Radius"], 0.02, 5.0)
# Wie dicht die Kugeln auf den Zweigen sitzen. Getrennt von "Density":
# die regelt jetzt die CARDS auf der fertigen Huelle, diese hier die Zahl
# der Kugeln, aus denen die Huelle ueberhaupt entsteht.
add_in("Cluster Density", 'NodeSocketFloat',
LEAF_DEFAULTS["Cluster Density"], 0.0, 2000.0)
# Aufloesung der Verschmelzung. Gross = klumpig und billig, klein = feine
# Kontur und teuer. Der eigentliche Kostenregler dieser Bauform.
add_in("Voxel Size", 'NodeSocketFloat',
LEAF_DEFAULTS["Voxel Size"], 0.02, 2.0)
gin = N("NodeGroupInput"); gin.location = (-900, 0)
gout = N("NodeGroupOutput"); gout.location = (900, 0)
@@ -1570,7 +1593,7 @@ def build_leaf_group(rebuild=False):
wmap.inputs["To Max"].default_value = 0.02
wmul = N("ShaderNodeMath"); wmul.location = (-320, 300)
wmul.operation = 'MULTIPLY'
L(V["Density"], wmul.inputs[0])
L(V["Cluster Density"], wmul.inputs[0])
L(wmap.outputs["Result"], wmul.inputs[1])
dist = N("GeometryNodeDistributePointsOnFaces"); dist.location = (-320, 200)
@@ -1621,40 +1644,29 @@ def build_leaf_group(rebuild=False):
L(_out(lna, "Attribute"), _sock(lstore, "Value"))
# ------------------------------------------------------------------
# BUESCHEL-SCHALE: aus jedem gestreuten Punkt wird ein Cluster-ZENTRUM,
# und die Cards sitzen auf der oberen Halbkugel darum herum.
# KRONEN-HUELLE: die Kugeln VERSCHMELZEN erst, dann wird auf der
# gemeinsamen Oberflaeche gestreut.
#
# Vorher sassen die Cards direkt auf der Astkurve. Aus der Naehe sah man
# deshalb nackte Ast-Enden mit einem Mini-Bueschel daran - das Laub folgte
# der Linie des Astes statt ein Polster zu bilden. Jetzt bildet jeder
# Cluster eine Kappe, die AUF dem Ast sitzt; die Astspitze verschwindet
# darin, weil das Zentrum auf ihr liegt und die Schale sie ueberdeckt.
# Vorher bekam jeder Cluster seine eigene Halbkugel und wurde einzeln
# bestueckt. Die Polster blieben dadurch als getrennte Koerper sichtbar -
# ein Laubhandschuh je Ast, im Spiel als "behaarte Finger" gemeldet. Mehr
# oder groessere Cards heilen das nicht: das Problem ist die STREUFLAECHE,
# nicht ihre Belegung.
#
# Untere Halbkugel weglassen: von unten sieht man die Krone gegen den
# Himmel, dort braucht es keine Cards - und es waere die Haelfte des
# Overdraws fuer nichts.
# Ikosaeder: 12 Vertices, davon 6 auf der oberen Haelfte - also SECHS Cards
# je Cluster. "Subdivisions" 0 und 1 liefern beide das Ikosaeder (gemessen),
# erst 2 unterteilt auf 42. Sechs reicht: mehr Cards je Cluster heisst bei
# festem Budget weniger Cluster, und dann bleiben Aeste ganz ohne Polster.
ico = N("GeometryNodeMeshIcoSphere"); ico.location = (-320, 420)
_sock(ico, "Radius").default_value = 1.0
_sock(ico, "Subdivisions").default_value = 1
# Der Weg ist der Standard aus den Produktions-Breakdowns (80.lv "Meadows",
# "The Witness"-Baeume): Kugeln auf die Aeste setzen, zu EINER Kronenmasse
# verschmelzen, Cards ueber deren Oberflaeche streuen. Die Aeste
# verschwinden dabei im Inneren der Krone.
#
# Verschmolzen wird ueber Volumen (Mesh to Volume -> Volume to Mesh) - das
# ist die Metaball-Wirkung: sich beruehrende Kugeln werden zu einem Koerper
# mit weichem Uebergang, statt sich zu durchdringen.
kugel = N("GeometryNodeMeshUVSphere"); kugel.location = (-320, 460)
_sock(kugel, "Segments").default_value = 8
_sock(kugel, "Rings").default_value = 6
_sock(kugel, "Radius").default_value = 1.0
spos = N("GeometryNodeInputPosition"); spos.location = (-320, 560)
ssep = N("ShaderNodeSeparateXYZ"); ssep.location = (-180, 560)
L(spos.outputs[0], ssep.inputs[0])
soben = N("FunctionNodeCompare"); soben.location = (-40, 560)
soben.data_type = 'FLOAT'; soben.operation = 'GREATER_THAN'
L(ssep.outputs["Z"], soben.inputs[0])
# Knapp unter den Aequator, damit die Kappe seitlich geschlossen wirkt.
soben.inputs[1].default_value = -0.15
m2p = N("GeometryNodeMeshToPoints"); m2p.location = (-40, 420)
L(_out(ico, "Mesh"), _sock(m2p, "Mesh"))
L(soben.outputs[0], _sock(m2p, "Selection"))
# Cluster-Radius mit Streuung, damit die Polster nicht uniform wirken.
# Radius mit Streuung, damit die Krone keine Perlenkette wird.
rrad = N("FunctionNodeRandomValue"); rrad.location = (-180, 320)
rrad.data_type = 'FLOAT'
L(V["Seed"], _sock(rrad, "Seed"))
@@ -1664,15 +1676,88 @@ def build_leaf_group(rebuild=False):
L(rlo.outputs[0], rrad.inputs[2])
L(V["Cluster Radius"], rrad.inputs[3])
schale = N("GeometryNodeInstanceOnPoints"); schale.location = (120, 420)
L(_out(lstore, "Geometry"), _sock(schale, "Points"))
L(_out(m2p, "Points"), _sock(schale, "Instance"))
L(rrad.outputs[1], _sock(schale, "Scale"))
sreal = N("GeometryNodeRealizeInstances"); sreal.location = (300, 420)
L(_out(schale, "Instances", "Geometry"), _sock(sreal, "Geometry"))
ballen = N("GeometryNodeInstanceOnPoints"); ballen.location = (120, 460)
L(_out(lstore, "Geometry"), _sock(ballen, "Points"))
L(_out(kugel, "Mesh"), _sock(ballen, "Instance"))
L(rrad.outputs[1], _sock(ballen, "Scale"))
breal = N("GeometryNodeRealizeInstances"); breal.location = (280, 460)
L(_out(ballen, "Instances", "Geometry"), _sock(breal, "Geometry"))
inst = N("GeometryNodeInstanceOnPoints"); inst.location = (100, 200)
L(_out(sreal, "Geometry"), _sock(inst, "Points"))
m2v = N("GeometryNodeMeshToVolume"); m2v.location = (420, 460)
L(_out(breal, "Geometry"), _sock(m2v, "Mesh"))
try:
m2v.resolution_mode = 'VOXEL_SIZE'
except (AttributeError, TypeError):
pass
L(V["Voxel Size"], _sock(m2v, "Voxel Size"))
v2m = N("GeometryNodeVolumeToMesh"); v2m.location = (560, 460)
L(_out(m2v, "Volume"), _sock(v2m, "Volume"))
try:
v2m.resolution_mode = 'VOXEL_SIZE'
except (AttributeError, TypeError):
pass
L(V["Voxel Size"], _sock(v2m, "Voxel Size"))
# Cards auf der HUELLE streuen. "Density" regelt jetzt hier - die Bisektion
# trifft die Ziel-Cardzahl damit direkt, ohne den Umweg ueber die
# Cluster-Zahl und ihre feste Kartenzahl je Cluster.
# Der KRONENANSATZ-Filter muss auch auf der Huelle greifen, nicht nur auf
# den Zentren. Die Verschmelzung weitet die Form um bis zu einen
# Kugelradius nach unten - gemessen lagen dadurch 31 % der Cards unter dem
# Ansatz und die tiefste bei 18 % der Hoehe statt bei 40 %. Das Laub klebte
# wieder am Stamm, der Fehler, gegen den der Filter ueberhaupt gebaut wurde.
hpos3 = N("GeometryNodeInputPosition"); hpos3.location = (560, 620)
hsep3 = N("ShaderNodeSeparateXYZ"); hsep3.location = (700, 620)
L(hpos3.outputs[0], hsep3.inputs[0])
hcmp3 = N("FunctionNodeCompare"); hcmp3.location = (840, 620)
hcmp3.data_type = 'FLOAT'; hcmp3.operation = 'GREATER_THAN'
L(hsep3.outputs["Z"], hcmp3.inputs[0])
L(bschwelle.outputs[0], hcmp3.inputs[1])
hdist = N("GeometryNodeDistributePointsOnFaces"); hdist.location = (700, 460)
L(_out(v2m, "Mesh"), _sock(hdist, "Mesh"))
L(hcmp3.outputs[0], _sock(hdist, "Selection"))
L(V["Density"], _sock(hdist, "Density"))
L(V["Seed"], _sock(hdist, "Seed"))
# AST-ID zurueckholen. Die Verschmelzung erzeugt frische Geometrie und
# verliert JEDES Attribut - ohne diesen Schritt wuesste keine Card mehr,
# zu welchem Ast sie gehoert, und die Faell-Teile zerfielen falsch.
#
# Die Zuordnung ist hier zwangslaeufig raeumlich: ein Punkt auf der
# verschmolzenen Huelle gehoert nicht mehr per Konstruktion zu einem Ast.
# Gefragt wird deshalb das naechste CLUSTER-ZENTRUM - das ist die Kugel,
# aus der dieses Stueck Huelle entstanden ist, also die bestmoegliche
# Antwort. Die Blatt-Bilanz im Test prueft, ob die Summe stimmt.
snear = N("GeometryNodeSampleNearest"); snear.location = (700, 300)
# DOMAIN setzen. Ohne das sucht der Knoten in der FACE-Domaene; die
# Cluster-Zentren sind aber eine reine Punktwolke ohne Faces, und die Suche
# lief ins Leere - gemessen kamen nur 3 von 10 Ast-IDs an den Cards an,
# sieben Aeste waeren beim Faellen ohne Laub abgefallen.
snear.domain = 'POINT'
L(_out(lstore, "Geometry"), _sock(snear, "Geometry"))
hpos2 = N("GeometryNodeInputPosition"); hpos2.location = (560, 300)
L(hpos2.outputs[0], _sock(snear, "Sample Position"))
sidx = N("GeometryNodeSampleIndex"); sidx.location = (840, 300)
sidx.data_type = 'INT'
sidx.domain = 'POINT'
L(_out(lstore, "Geometry"), _sock(sidx, "Geometry"))
lna2 = N("GeometryNodeInputNamedAttribute"); lna2.location = (700, 220)
lna2.data_type = 'INT'
_sock(lna2, "Name").default_value = "ast_id"
L(_out(lna2, "Attribute"), _sock(sidx, "Value"))
L(_out(snear, "Index"), _sock(sidx, "Index"))
hstore = N("GeometryNodeStoreNamedAttribute"); hstore.location = (1000, 460)
hstore.domain = 'POINT'; hstore.data_type = 'INT'
L(_out(hdist, "Points"), _sock(hstore, "Geometry"))
_sock(hstore, "Name").default_value = "ast_id"
L(_out(sidx, "Value"), _sock(hstore, "Value"))
inst = N("GeometryNodeInstanceOnPoints"); inst.location = (1160, 460)
L(_out(hstore, "Geometry"), _sock(inst, "Points"))
L(_out(quelle, "Output"), _sock(inst, "Instance"))
try:
L(V["Use Collection"], _sock(inst, "Pick Instance"))
@@ -1771,11 +1856,24 @@ LEAF_RADIUS_YOUNG = 0.20
# war; jetzt ist sie es nicht mehr.
#
# Ziel-Cards Groesse Kronenansatz
# Seit der Kronen-Huelle sitzen die Cards auf einer FLAECHE, nicht mehr im
# Volumen - die Zahl bestimmt damit direkter, wie viel Himmel durchscheint.
#
# Nach der Abnahme im Wald wollte der User "etwas weniger Blaetter". Umgesetzt
# als 15 % auf die Ziele. Gemessen an der Eiche: 381 -> 319 Cards, Blattflaeche
# je Huellenflaeche 10.4 -> 8.6, und 372 Tris gespart.
#
# Der Gegenversuch stand zur Wahl und ist gemessen worden: Cards um 15 %
# VERKLEINERN kostet mehr Deckung (10.4 -> 7.9, weil die Flaeche quadratisch
# geht) und spart kein einziges Dreieck. Die Zahl zu senken ist der ehrlichere
# Hebel - und der, den der User woertlich genannt hat.
#
# Ziel-Cards Groesse Kronenansatz
LEAF_STUFEN = (
(26, 0.24, 0.40), # S1 - EIN Bueschel, Fuss bleibt frei
(78, 0.46, 0.35), # S2 - drei
(208, 0.75, 0.30), # S3 - acht
(390, 1.00, 0.25), # S4 - rund fuenfzehn
(22, 0.24, 0.40), # S1
(66, 0.46, 0.35), # S2
(177, 0.75, 0.30), # S3
(330, 1.00, 0.25), # S4
)
# AUSNAHMEN je Preset: {preset: {stufen_index: ziel_cards}}.
@@ -1789,16 +1887,16 @@ LEAF_STUFEN = (
# Das macht die Tabelle preset-abhaengig - bewusst so entschieden, statt das
# Budget fuer alle anzuheben: die Weide ist der Ausreisser, nicht die Regel.
LEAF_STUFEN_PRESET = {
"weide": {3: 350},
"weide": {3: 298},
# Die Birke hat mit 2100 Tris die dickste Rinde im Satz; bei 390 Cards
# gemessen 4572 Tris. (4300 - 2100) / 6 = 366 Cards passen, 340 mit Luft
# fuer die 15-%-Toleranz der Regelung.
"birke": {3: 340},
"birke": {3: 289},
# Obstbaum: breite offene Krone auf kurzem Stamm gibt viel Zweigflaeche auf
# wenig Hoehe - ungeregelt 4304-4400 Tris ueber drei Seeds, knapp ueber dem
# Budget. Die Regelung haelt eine Toleranz von 15 %, deshalb liegt das Ziel
# unter dem rechnerischen Maximum (497 Cards): 420 x 1.15 x 6 + 1316 = 4214.
"obstbaum": {3: 420},
"obstbaum": {3: 357},
}
# Card-GROESSE je Preset, als Faktor auf die eingestellte Groesse.
@@ -2054,6 +2152,22 @@ def make_leaves(context, tree_obj, card_obj, name=None, card_coll=None,
# Card-Groesse, nur eine Ebene hoeher.
c_faktor = LEAF_CLUSTER_PRESET.get(tree_obj.get("preset") or "", 1.0)
overrides["Cluster Radius"] = basis["Cluster Radius"] * g_anteil * c_faktor
# Die Voxel-Groesse muss mit der Kappe mitwachsen, sonst kostet der
# Setzling dieselbe Gitteraufloesung wie der Altbaum - und der Altbaum
# bekommt eine zu grobe Huelle.
overrides["Voxel Size"] = basis["Voxel Size"] * g_anteil * c_faktor
# Die CLUSTER-Dichte muss GEGEN die Baumgroesse laufen, nicht mit ihr.
#
# "Dichte" ist Kugeln je m2 Zweigflaeche, und die Flaeche schrumpft mit
# dem Quadrat der Groesse. Bei fester Dichte bleiben am Setzling NULL
# Kugeln uebrig - gemessen ueber die Stufen 0 / 20 / 41 / 109 Zentren,
# S1 stand ohne Huelle und damit ohne ein einziges Blatt da.
#
# Mit 1/g^2 bleibt die ANZAHL ueber die Stufen etwa konstant, was die
# richtige Groesse ist: eine Krone braucht eine bestimmte Zahl Ballen,
# damit sie verschmilzt - nicht eine bestimmte Dichte.
overrides["Cluster Density"] = (basis["Cluster Density"]
/ max(g_anteil * g_anteil, 0.01))
name = name or (tree_obj.name + "_Leaf")
ob = bpy.data.objects.get(name)
+30 -22
View File
@@ -316,26 +316,34 @@ for seed in SEEDS:
# Die Krone muss sich nach OBEN schliessen. Die Eiche, die laut Abnahme als
# Baum liest, kommt auf 0.90; der Trichter lag bei 1.07. 0.85 als Grenze
# laesst Spielraum nach oben, schliesst den Trichter aber sicher aus.
# Die Grenzen kommen aus einer Messung ALLER Formen mit DIESEM Mass, nicht
# aus dem Wunschband - die Streuung von Seed zu Seed ist breiter als das
# Band, das die Spielseite genannt hat (u/m 0.85-0.95). Gemessen ueber neun
# Seeds:
# Eiche u/m 1.04-1.15 o/m 0.77-0.88
# Trichter u/m 0.66-0.72 o/m 1.18-1.31
# Haube u/m 0.99-1.13 o/m 0.69-0.80
# Pilz u/m 0.75-0.90 o/m 0.78-0.94
# Kugel u/m 0.86-1.01 o/m 0.63-0.74
# Die Schranken trennen die Kugel von Trichter und Pilz; von der Haube
# trennt sie der Kugel-Kennwert weiter unten.
# Die Grenzen sind unter der KRONEN-HUELLE neu erhoben und um die Form
# gelegt, die der User im Wald ABGENOMMEN hat - nicht umgekehrt. Spannen
# ueber neun Seeds, alle mit demselben Mass:
#
# Eiche u/m 1.04-1.16 o/m 0.79-0.91
# ABGENOMMEN u/m 0.78-1.02 o/m 0.68-0.79
# Trichter u/m 0.68-0.78 o/m 1.04-1.14
# Pilz u/m 0.59-0.90 o/m 0.82-0.95
# Haube u/m 0.94-1.03 o/m 0.71-0.78
#
# DARAUS FOLGT ETWAS UNBEQUEMES: u/m trennt unter der neuen Bauform NICHTS
# mehr. Der abgenommene Bereich (0.78-1.02) ueberlappt den Pilz (0.59-0.90)
# und stoesst an den Trichter (bis 0.78). Die fruehere 0.82-Schranke stammt
# aus der Einzelkappen-Aera; haette man dagegen kalibriert, waere die Krone
# verbogen worden, um eine Zahl zu retten, die ihre Aussagekraft verloren
# hat - derselbe Fall wie beim Kugel-Kennwert eine Runde zuvor.
#
# o/m trennt dagegen sauber: abgenommen bis 0.79, Pilz ab 0.82, Trichter ab
# 1.04. Diese eine Schranke traegt die Formpruefung, und der Kugel-Kennwert
# weiter unten trennt zusaetzlich von der Haube.
if om > 0.80:
fails.append("Seed %d: Krone oben %.1f m breit, in der Mitte %.1f "
"(%.2f) - sie schliesst sich oben nicht (Trichter lag bei "
"1.18-1.31)" % (seed, profil[2], profil[1], om))
if um < 0.82:
fails.append("Seed %d: Krone unten %.1f m breit, in der Mitte %.1f "
"(%.2f) - unten zu schmal, das wird ein Pilz bzw. ein "
"Trichter (0.66-0.90)"
% (seed, profil[0], profil[1], um))
"(%.2f) - abgenommen ist bis 0.79, darueber liegen Pilz "
"(ab 0.82) und Trichter (ab 1.04)"
% (seed, profil[2], profil[1], om))
# u/m wird nur noch MITGEDRUCKT. Ein Kriterium, das die Fehlformen nicht
# mehr ausschliesst, ist schlimmer als keines: es sieht aus wie ein Schutz
# und ist keiner.
if kugel > EICHE_KUGEL * 0.65:
fails.append("Seed %d: Kugel-Kennwert %.2f, Eiche %.2f (%.0f %%) - der "
"Obstbaum muss die RUNDESTE Art bleiben, Richtwert <= 65 %%"
@@ -431,14 +439,14 @@ for _k, _x in enumerate(_st, 1):
print("")
print("Kronenradius/Card ueber alle Seeds: %.2f - %.2f" % (min(verh), max(verh)))
print("Krone unten/mitte ueber alle Seeds: %.2f - %.2f (Pilz 0.75-0.90, "
"Trichter 0.66-0.72)" % (min(untenmitte), max(untenmitte)))
print("Krone unten/mitte ueber alle Seeds: %.2f - %.2f (nur Info - trennt "
"unter der Huelle nichts mehr)" % (min(untenmitte), max(untenmitte)))
print("Kugel (KronH/KronB) ueber alle Seeds: %.2f - %.2f, Eiche %.2f "
"(%.0f-%.0f %% - Vorgabe <= 65 %%)"
% (min(kugeln), max(kugeln), EICHE_KUGEL,
100 * min(kugeln) / EICHE_KUGEL, 100 * max(kugeln) / EICHE_KUGEL))
print("Krone oben/mitte ueber alle Seeds: %.2f - %.2f (Trichter 1.18-1.31)"
% (min(obenmitte), max(obenmitte)))
print("Krone oben/mitte ueber alle Seeds: %.2f - %.2f (Pilz ab 0.82, "
"Trichter ab 1.04)" % (min(obenmitte), max(obenmitte)))
print("Kronen-Deckung ueber alle Seeds: %.1f - %.1f m2/m3, Eiche %.1f "
"(%.0f-%.0f %%)"
% (min(deckungen), max(deckungen), EICHE,