Compare commits
3
Commits
1a1c3a5c59
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4cf7097fa3 | ||
|
|
a718476592 | ||
|
|
869b008d4f |
@@ -702,6 +702,83 @@ Gegenprobe ohne die Boden-Klemmung: vier Fehler.
|
|||||||
Drei bestehende Tests zaehlten die Huelle als Render-Mesh mit und schlugen zu
|
Drei bestehende Tests zaehlten die Huelle als Render-Mesh mit und schlugen zu
|
||||||
Recht an - sie filtern jetzt `UCX_`.
|
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
|
### Der Hochstamm muss auf JEDER Stufe stehen
|
||||||
|
|
||||||
Bis 1.46 war am Obstbaum nur S4 kalibriert. Im Welt-Test ist der User auf einen
|
Bis 1.46 war am Obstbaum nur S4 kalibriert. Im Welt-Test ist der User auf einen
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
schema_version = "1.0.0"
|
schema_version = "1.0.0"
|
||||||
|
|
||||||
id = "stylized_tree_generator"
|
id = "stylized_tree_generator"
|
||||||
version = "1.47.0"
|
version = "1.48.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.5KB</td>
|
<td>34.5KB</td>
|
||||||
</tr>
|
</tr>
|
||||||
<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>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>53.3KB</td>
|
<td>55.1KB</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<center><p>Built 2026-08-07, 11:33</p></center>
|
<center><p>Built 2026-08-07, 13:38</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.47.0",
|
"version": "1.48.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.47.0.zip",
|
"archive_url": "./stylized_tree_generator-1.48.0.zip",
|
||||||
"archive_size": 54533,
|
"archive_size": 56393,
|
||||||
"archive_hash": "sha256:0c1ba47d4fe89d1c45dbff090d04feaa136bea349cd30386127bc70ab2acd8eb"
|
"archive_hash": "sha256:90b1d48c25a595f0d8c6e0cd63722aa246914998979cbbec4f370b3a9c56a2b9"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
BIN
Binary file not shown.
BIN
Binary file not shown.
+165
-51
@@ -1,7 +1,7 @@
|
|||||||
bl_info = {
|
bl_info = {
|
||||||
"name": "Stylized Tree Generator",
|
"name": "Stylized Tree Generator",
|
||||||
"author": "D4rkst3r",
|
"author": "D4rkst3r",
|
||||||
"version": (1, 47, 0),
|
"version": (1, 48, 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",
|
||||||
@@ -1381,8 +1381,22 @@ LEAF_DEFAULTS = {
|
|||||||
# CLUSTER-Zentren, nicht mehr einzelne Cards - jedes Zentrum traegt SECHS
|
# CLUSTER-Zentren, nicht mehr einzelne Cards - jedes Zentrum traegt SECHS
|
||||||
# Cards auf seiner Halbkugel. Die Dichte liegt entsprechend niedriger; die
|
# Cards auf seiner Halbkugel. Die Dichte liegt entsprechend niedriger; die
|
||||||
# Bisektion regelt sie ohnehin auf die Ziel-Cardzahl ein.
|
# 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,
|
"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 -
|
# 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
|
# 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
|
# 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("Size Var", 'NodeSocketFloat', LEAF_DEFAULTS["Size Var"], 0.0, 1.0)
|
||||||
add_in("Tilt", 'NodeSocketFloat', LEAF_DEFAULTS["Tilt"], 0.0, 3.14159)
|
add_in("Tilt", 'NodeSocketFloat', LEAF_DEFAULTS["Tilt"], 0.0, 3.14159)
|
||||||
add_in("Seed", 'NodeSocketInt', LEAF_DEFAULTS["Seed"], 0, 9999)
|
add_in("Seed", 'NodeSocketInt', LEAF_DEFAULTS["Seed"], 0, 9999)
|
||||||
# Radius der Bueschel-Kappe um jedes Cluster-Zentrum. Siehe den Block
|
# Radius der Kugeln, aus denen die Kronen-Huelle verschmilzt. Siehe den
|
||||||
# "BUESCHEL-SCHALE" weiter unten.
|
# Block "KRONEN-HUELLE" weiter unten.
|
||||||
add_in("Cluster Radius", 'NodeSocketFloat',
|
add_in("Cluster Radius", 'NodeSocketFloat',
|
||||||
LEAF_DEFAULTS["Cluster Radius"], 0.02, 5.0)
|
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)
|
gin = N("NodeGroupInput"); gin.location = (-900, 0)
|
||||||
gout = N("NodeGroupOutput"); gout.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
|
wmap.inputs["To Max"].default_value = 0.02
|
||||||
wmul = N("ShaderNodeMath"); wmul.location = (-320, 300)
|
wmul = N("ShaderNodeMath"); wmul.location = (-320, 300)
|
||||||
wmul.operation = 'MULTIPLY'
|
wmul.operation = 'MULTIPLY'
|
||||||
L(V["Density"], wmul.inputs[0])
|
L(V["Cluster Density"], wmul.inputs[0])
|
||||||
L(wmap.outputs["Result"], wmul.inputs[1])
|
L(wmap.outputs["Result"], wmul.inputs[1])
|
||||||
|
|
||||||
dist = N("GeometryNodeDistributePointsOnFaces"); dist.location = (-320, 200)
|
dist = N("GeometryNodeDistributePointsOnFaces"); dist.location = (-320, 200)
|
||||||
@@ -1621,40 +1644,29 @@ def build_leaf_group(rebuild=False):
|
|||||||
L(_out(lna, "Attribute"), _sock(lstore, "Value"))
|
L(_out(lna, "Attribute"), _sock(lstore, "Value"))
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
# BUESCHEL-SCHALE: aus jedem gestreuten Punkt wird ein Cluster-ZENTRUM,
|
# KRONEN-HUELLE: die Kugeln VERSCHMELZEN erst, dann wird auf der
|
||||||
# und die Cards sitzen auf der oberen Halbkugel darum herum.
|
# gemeinsamen Oberflaeche gestreut.
|
||||||
#
|
#
|
||||||
# Vorher sassen die Cards direkt auf der Astkurve. Aus der Naehe sah man
|
# Vorher bekam jeder Cluster seine eigene Halbkugel und wurde einzeln
|
||||||
# deshalb nackte Ast-Enden mit einem Mini-Bueschel daran - das Laub folgte
|
# bestueckt. Die Polster blieben dadurch als getrennte Koerper sichtbar -
|
||||||
# der Linie des Astes statt ein Polster zu bilden. Jetzt bildet jeder
|
# ein Laubhandschuh je Ast, im Spiel als "behaarte Finger" gemeldet. Mehr
|
||||||
# Cluster eine Kappe, die AUF dem Ast sitzt; die Astspitze verschwindet
|
# oder groessere Cards heilen das nicht: das Problem ist die STREUFLAECHE,
|
||||||
# darin, weil das Zentrum auf ihr liegt und die Schale sie ueberdeckt.
|
# nicht ihre Belegung.
|
||||||
#
|
#
|
||||||
# Untere Halbkugel weglassen: von unten sieht man die Krone gegen den
|
# Der Weg ist der Standard aus den Produktions-Breakdowns (80.lv "Meadows",
|
||||||
# Himmel, dort braucht es keine Cards - und es waere die Haelfte des
|
# "The Witness"-Baeume): Kugeln auf die Aeste setzen, zu EINER Kronenmasse
|
||||||
# Overdraws fuer nichts.
|
# verschmelzen, Cards ueber deren Oberflaeche streuen. Die Aeste
|
||||||
# Ikosaeder: 12 Vertices, davon 6 auf der oberen Haelfte - also SECHS Cards
|
# verschwinden dabei im Inneren der Krone.
|
||||||
# je Cluster. "Subdivisions" 0 und 1 liefern beide das Ikosaeder (gemessen),
|
#
|
||||||
# erst 2 unterteilt auf 42. Sechs reicht: mehr Cards je Cluster heisst bei
|
# Verschmolzen wird ueber Volumen (Mesh to Volume -> Volume to Mesh) - das
|
||||||
# festem Budget weniger Cluster, und dann bleiben Aeste ganz ohne Polster.
|
# ist die Metaball-Wirkung: sich beruehrende Kugeln werden zu einem Koerper
|
||||||
ico = N("GeometryNodeMeshIcoSphere"); ico.location = (-320, 420)
|
# mit weichem Uebergang, statt sich zu durchdringen.
|
||||||
_sock(ico, "Radius").default_value = 1.0
|
kugel = N("GeometryNodeMeshUVSphere"); kugel.location = (-320, 460)
|
||||||
_sock(ico, "Subdivisions").default_value = 1
|
_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)
|
# Radius mit Streuung, damit die Krone keine Perlenkette wird.
|
||||||
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.
|
|
||||||
rrad = N("FunctionNodeRandomValue"); rrad.location = (-180, 320)
|
rrad = N("FunctionNodeRandomValue"); rrad.location = (-180, 320)
|
||||||
rrad.data_type = 'FLOAT'
|
rrad.data_type = 'FLOAT'
|
||||||
L(V["Seed"], _sock(rrad, "Seed"))
|
L(V["Seed"], _sock(rrad, "Seed"))
|
||||||
@@ -1664,15 +1676,88 @@ def build_leaf_group(rebuild=False):
|
|||||||
L(rlo.outputs[0], rrad.inputs[2])
|
L(rlo.outputs[0], rrad.inputs[2])
|
||||||
L(V["Cluster Radius"], rrad.inputs[3])
|
L(V["Cluster Radius"], rrad.inputs[3])
|
||||||
|
|
||||||
schale = N("GeometryNodeInstanceOnPoints"); schale.location = (120, 420)
|
ballen = N("GeometryNodeInstanceOnPoints"); ballen.location = (120, 460)
|
||||||
L(_out(lstore, "Geometry"), _sock(schale, "Points"))
|
L(_out(lstore, "Geometry"), _sock(ballen, "Points"))
|
||||||
L(_out(m2p, "Points"), _sock(schale, "Instance"))
|
L(_out(kugel, "Mesh"), _sock(ballen, "Instance"))
|
||||||
L(rrad.outputs[1], _sock(schale, "Scale"))
|
L(rrad.outputs[1], _sock(ballen, "Scale"))
|
||||||
sreal = N("GeometryNodeRealizeInstances"); sreal.location = (300, 420)
|
breal = N("GeometryNodeRealizeInstances"); breal.location = (280, 460)
|
||||||
L(_out(schale, "Instances", "Geometry"), _sock(sreal, "Geometry"))
|
L(_out(ballen, "Instances", "Geometry"), _sock(breal, "Geometry"))
|
||||||
|
|
||||||
inst = N("GeometryNodeInstanceOnPoints"); inst.location = (100, 200)
|
m2v = N("GeometryNodeMeshToVolume"); m2v.location = (420, 460)
|
||||||
L(_out(sreal, "Geometry"), _sock(inst, "Points"))
|
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"))
|
L(_out(quelle, "Output"), _sock(inst, "Instance"))
|
||||||
try:
|
try:
|
||||||
L(V["Use Collection"], _sock(inst, "Pick Instance"))
|
L(V["Use Collection"], _sock(inst, "Pick Instance"))
|
||||||
@@ -1771,11 +1856,24 @@ LEAF_RADIUS_YOUNG = 0.20
|
|||||||
# war; jetzt ist sie es nicht mehr.
|
# war; jetzt ist sie es nicht mehr.
|
||||||
#
|
#
|
||||||
# Ziel-Cards Groesse Kronenansatz
|
# 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 = (
|
LEAF_STUFEN = (
|
||||||
(26, 0.24, 0.40), # S1 - EIN Bueschel, Fuss bleibt frei
|
(22, 0.24, 0.40), # S1
|
||||||
(78, 0.46, 0.35), # S2 - drei
|
(66, 0.46, 0.35), # S2
|
||||||
(208, 0.75, 0.30), # S3 - acht
|
(177, 0.75, 0.30), # S3
|
||||||
(390, 1.00, 0.25), # S4 - rund fuenfzehn
|
(330, 1.00, 0.25), # S4
|
||||||
)
|
)
|
||||||
|
|
||||||
# AUSNAHMEN je Preset: {preset: {stufen_index: ziel_cards}}.
|
# 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
|
# Das macht die Tabelle preset-abhaengig - bewusst so entschieden, statt das
|
||||||
# Budget fuer alle anzuheben: die Weide ist der Ausreisser, nicht die Regel.
|
# Budget fuer alle anzuheben: die Weide ist der Ausreisser, nicht die Regel.
|
||||||
LEAF_STUFEN_PRESET = {
|
LEAF_STUFEN_PRESET = {
|
||||||
"weide": {3: 350},
|
"weide": {3: 298},
|
||||||
# Die Birke hat mit 2100 Tris die dickste Rinde im Satz; bei 390 Cards
|
# 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
|
# gemessen 4572 Tris. (4300 - 2100) / 6 = 366 Cards passen, 340 mit Luft
|
||||||
# fuer die 15-%-Toleranz der Regelung.
|
# fuer die 15-%-Toleranz der Regelung.
|
||||||
"birke": {3: 340},
|
"birke": {3: 289},
|
||||||
# Obstbaum: breite offene Krone auf kurzem Stamm gibt viel Zweigflaeche auf
|
# Obstbaum: breite offene Krone auf kurzem Stamm gibt viel Zweigflaeche auf
|
||||||
# wenig Hoehe - ungeregelt 4304-4400 Tris ueber drei Seeds, knapp ueber dem
|
# wenig Hoehe - ungeregelt 4304-4400 Tris ueber drei Seeds, knapp ueber dem
|
||||||
# Budget. Die Regelung haelt eine Toleranz von 15 %, deshalb liegt das Ziel
|
# 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.
|
# 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.
|
# 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.
|
# Card-Groesse, nur eine Ebene hoeher.
|
||||||
c_faktor = LEAF_CLUSTER_PRESET.get(tree_obj.get("preset") or "", 1.0)
|
c_faktor = LEAF_CLUSTER_PRESET.get(tree_obj.get("preset") or "", 1.0)
|
||||||
overrides["Cluster Radius"] = basis["Cluster Radius"] * g_anteil * c_faktor
|
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")
|
name = name or (tree_obj.name + "_Leaf")
|
||||||
ob = bpy.data.objects.get(name)
|
ob = bpy.data.objects.get(name)
|
||||||
|
|||||||
+30
-22
@@ -316,26 +316,34 @@ for seed in SEEDS:
|
|||||||
# Die Krone muss sich nach OBEN schliessen. Die Eiche, die laut Abnahme als
|
# 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
|
# 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.
|
# laesst Spielraum nach oben, schliesst den Trichter aber sicher aus.
|
||||||
# Die Grenzen kommen aus einer Messung ALLER Formen mit DIESEM Mass, nicht
|
# Die Grenzen sind unter der KRONEN-HUELLE neu erhoben und um die Form
|
||||||
# aus dem Wunschband - die Streuung von Seed zu Seed ist breiter als das
|
# gelegt, die der User im Wald ABGENOMMEN hat - nicht umgekehrt. Spannen
|
||||||
# Band, das die Spielseite genannt hat (u/m 0.85-0.95). Gemessen ueber neun
|
# ueber neun Seeds, alle mit demselben Mass:
|
||||||
# Seeds:
|
#
|
||||||
# Eiche u/m 1.04-1.15 o/m 0.77-0.88
|
# Eiche u/m 1.04-1.16 o/m 0.79-0.91
|
||||||
# Trichter u/m 0.66-0.72 o/m 1.18-1.31
|
# ABGENOMMEN u/m 0.78-1.02 o/m 0.68-0.79
|
||||||
# Haube u/m 0.99-1.13 o/m 0.69-0.80
|
# Trichter u/m 0.68-0.78 o/m 1.04-1.14
|
||||||
# Pilz u/m 0.75-0.90 o/m 0.78-0.94
|
# Pilz u/m 0.59-0.90 o/m 0.82-0.95
|
||||||
# Kugel u/m 0.86-1.01 o/m 0.63-0.74
|
# Haube u/m 0.94-1.03 o/m 0.71-0.78
|
||||||
# Die Schranken trennen die Kugel von Trichter und Pilz; von der Haube
|
#
|
||||||
# trennt sie der Kugel-Kennwert weiter unten.
|
# 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:
|
if om > 0.80:
|
||||||
fails.append("Seed %d: Krone oben %.1f m breit, in der Mitte %.1f "
|
fails.append("Seed %d: Krone oben %.1f m breit, in der Mitte %.1f "
|
||||||
"(%.2f) - sie schliesst sich oben nicht (Trichter lag bei "
|
"(%.2f) - abgenommen ist bis 0.79, darueber liegen Pilz "
|
||||||
"1.18-1.31)" % (seed, profil[2], profil[1], om))
|
"(ab 0.82) und Trichter (ab 1.04)"
|
||||||
if um < 0.82:
|
% (seed, profil[2], profil[1], om))
|
||||||
fails.append("Seed %d: Krone unten %.1f m breit, in der Mitte %.1f "
|
# u/m wird nur noch MITGEDRUCKT. Ein Kriterium, das die Fehlformen nicht
|
||||||
"(%.2f) - unten zu schmal, das wird ein Pilz bzw. ein "
|
# mehr ausschliesst, ist schlimmer als keines: es sieht aus wie ein Schutz
|
||||||
"Trichter (0.66-0.90)"
|
# und ist keiner.
|
||||||
% (seed, profil[0], profil[1], um))
|
|
||||||
if kugel > EICHE_KUGEL * 0.65:
|
if kugel > EICHE_KUGEL * 0.65:
|
||||||
fails.append("Seed %d: Kugel-Kennwert %.2f, Eiche %.2f (%.0f %%) - der "
|
fails.append("Seed %d: Kugel-Kennwert %.2f, Eiche %.2f (%.0f %%) - der "
|
||||||
"Obstbaum muss die RUNDESTE Art bleiben, Richtwert <= 65 %%"
|
"Obstbaum muss die RUNDESTE Art bleiben, Richtwert <= 65 %%"
|
||||||
@@ -431,14 +439,14 @@ for _k, _x in enumerate(_st, 1):
|
|||||||
|
|
||||||
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("Krone unten/mitte ueber alle Seeds: %.2f - %.2f (Pilz 0.75-0.90, "
|
print("Krone unten/mitte ueber alle Seeds: %.2f - %.2f (nur Info - trennt "
|
||||||
"Trichter 0.66-0.72)" % (min(untenmitte), max(untenmitte)))
|
"unter der Huelle nichts mehr)" % (min(untenmitte), max(untenmitte)))
|
||||||
print("Kugel (KronH/KronB) ueber alle Seeds: %.2f - %.2f, Eiche %.2f "
|
print("Kugel (KronH/KronB) ueber alle Seeds: %.2f - %.2f, Eiche %.2f "
|
||||||
"(%.0f-%.0f %% - Vorgabe <= 65 %%)"
|
"(%.0f-%.0f %% - Vorgabe <= 65 %%)"
|
||||||
% (min(kugeln), max(kugeln), EICHE_KUGEL,
|
% (min(kugeln), max(kugeln), EICHE_KUGEL,
|
||||||
100 * min(kugeln) / EICHE_KUGEL, 100 * 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)"
|
print("Krone oben/mitte ueber alle Seeds: %.2f - %.2f (Pilz ab 0.82, "
|
||||||
% (min(obenmitte), max(obenmitte)))
|
"Trichter ab 1.04)" % (min(obenmitte), max(obenmitte)))
|
||||||
print("Kronen-Deckung ueber alle Seeds: %.1f - %.1f m2/m3, Eiche %.1f "
|
print("Kronen-Deckung ueber alle Seeds: %.1f - %.1f m2/m3, Eiche %.1f "
|
||||||
"(%.0f-%.0f %%)"
|
"(%.0f-%.0f %%)"
|
||||||
% (min(deckungen), max(deckungen), EICHE,
|
% (min(deckungen), max(deckungen), EICHE,
|
||||||
|
|||||||
Reference in New Issue
Block a user