Tree Gen 1.38.0: ein Weg zum Blattwerk, generischer Baum entsaeult
1) Zwei-Pfade-Fehler behoben (von der Spielseite gemeldet) Blattwerk entstand an zwei Stellen - Haekchen "_Leaf anlegen" beim Erzeugen und Knopf "Blattwerk streuen" - und nur der zweite rechnete die Wachstums-Staffel. Am Modifier nachgemessen trug der S1-Setzling aus dem Create-Pfad Size 0.85, Max Radius 0.04, Min Height 0.25: die Altbaum-Werte. Zum dritten Mal dieselbe Fehlerklasse (nach Stufen-Deckung und build_group): zwei Quellen fuer dieselbe Frage. Die Staffel liegt jetzt in make_leaves selbst und zieht growth_t/preset vom Baum-Objekt - ein Aufrufer KANN sie nicht mehr vergessen. Darueber blattwerk_streuen() als der eine Einstieg fuer beide Knoepfe, inklusive der Dichte-Regelung, die vorher ebenfalls nur der Operator hatte. Nebenbei bekommt der Create-Pfad damit auch die Card-SAMMLUNG, die ihm bisher fehlte. test_zwei_pfade.py prueft die DIFFERENZ zwischen beiden Wegen, nicht die absoluten Werte - ein Test, der nur einen Weg anschaut, findet so etwas nie. Plus die Gegenprobe im Test selbst: waeren beide Wege gleich falsch, muss auffallen, dass S1 und S4 dieselbe Card-Groesse tragen. Gegenprobe gefahren: mit dem alten Create-Pfad meldet der Test 10 Fehler und reproduziert exakt die gemeldeten 0.85 / 0.04 / 0.25. 2) Generischer "baum" entsaeult Gemessen B/H 0.18 ueber fuenf Seeds - schmaler als jedes andere Laubgehoelz. Ursache dieselbe wie bei der Birke: Branch Up 1.3 aus den DEFAULTS. Zielband der Spielseite 0.45-0.60, jetzt 0.55 (0.52-0.56). Die DEFAULTS bleiben unangetastet - sie sind die Socket-Vorgaben, an denen die haendischen Regler haengen. Nur "baum" bekommt eigene Werte. 26 Tests bestehen. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -563,6 +563,38 @@ 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.
|
||||||
|
|
||||||
|
### Zwei Knoepfe, ein Weg
|
||||||
|
|
||||||
|
Blattwerk entsteht an zwei Stellen: ueber das Haekchen "_Leaf anlegen" beim
|
||||||
|
Erzeugen und ueber den Knopf "Blattwerk streuen". Nur der zweite rechnete die
|
||||||
|
Wachstums-Staffel. Von der Spielseite am Modifier nachgemessen trug der
|
||||||
|
S1-Setzling aus dem Create-Pfad **Size 0.85, Max Radius 0.04, Min Height 0.25** -
|
||||||
|
exakt die Altbaum-Werte.
|
||||||
|
|
||||||
|
Das ist zum dritten Mal dieselbe Fehlerklasse (nach der Stufen-Deckung und
|
||||||
|
`build_group`): **zwei Quellen fuer dieselbe Frage**. Die Staffel liegt deshalb
|
||||||
|
jetzt in `make_leaves` selbst, die sie aus `growth_t` und `preset` am
|
||||||
|
Baum-Objekt zieht - ein Aufrufer KANN sie nicht mehr vergessen. Darueber sitzt
|
||||||
|
`blattwerk_streuen()` als der eine Einstieg, den beide Knoepfe benutzen; dort
|
||||||
|
haengt auch die Dichte-Regelung, die vorher ebenfalls nur der Operator hatte.
|
||||||
|
|
||||||
|
`tests/test_zwei_pfade.py` prueft nicht die absoluten Werte, sondern die
|
||||||
|
DIFFERENZ zwischen beiden Wegen - ein Test, der nur einen Weg anschaut, findet
|
||||||
|
so etwas grundsaetzlich nie. Zusaetzlich prueft er, dass sich S1 und S4
|
||||||
|
ueberhaupt unterscheiden: waeren beide Wege gleich falsch, waere der Vergleich
|
||||||
|
allein zufrieden.
|
||||||
|
|
||||||
|
### Der generische Baum war auch eine Saeule
|
||||||
|
|
||||||
|
Gemessen Breite/Hoehe **0.18** ueber fuenf Seeds - schmaler als jedes andere
|
||||||
|
Laubgehoelz im Satz. Ursache dieselbe wie bei der Birke: `Branch Up` 1.3 aus den
|
||||||
|
DEFAULTS. Zielband der Spielseite 0.45-0.60, gemessen jetzt **0.55**
|
||||||
|
(0.52-0.56).
|
||||||
|
|
||||||
|
Die DEFAULTS bleiben unangetastet - sie sind die Socket-Vorgaben der
|
||||||
|
Node-Gruppe, an denen jeder haendische Regler haengt. Nur `baum` bekommt eigene
|
||||||
|
Werte; alle anderen Presets ueberschreiben `Branch Up` ohnehin.
|
||||||
|
|
||||||
### Obstbaum: die Krone muss groesser sein als eine Card
|
### Obstbaum: die Krone muss groesser sein als eine Card
|
||||||
|
|
||||||
Das Preset ist nicht nur eine Silhouette, es ist eine FUNKTIONALE Bedingung.
|
Das Preset ist nicht nur eine Silhouette, es ist eine FUNKTIONALE Bedingung.
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
schema_version = "1.0.0"
|
schema_version = "1.0.0"
|
||||||
|
|
||||||
id = "stylized_tree_generator"
|
id = "stylized_tree_generator"
|
||||||
version = "1.37.0"
|
version = "1.38.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.37.0.zip?repository=.%2Findex.json&blender_version_min=4.2.0">stylized_tree_generator-1.37.0</a></tt></td>
|
<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>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>44.2KB</td>
|
<td>45.0KB</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<center><p>Built 2026-08-06, 10:59</p></center>
|
<center><p>Built 2026-08-06, 11:22</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.37.0",
|
"version": "1.38.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.37.0.zip",
|
"archive_url": "./stylized_tree_generator-1.38.0.zip",
|
||||||
"archive_size": 45213,
|
"archive_size": 46032,
|
||||||
"archive_hash": "sha256:189494bb426bb757671a8da62baf1fb341e6147532a1d0a7eb7d9a7a745d15f3"
|
"archive_hash": "sha256:0d65140f04d1ba8289a72c69c44c95b8e7b7300681695b43c7cba7a8f2ec6c9b"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
BIN
Binary file not shown.
BIN
Binary file not shown.
+102
-53
@@ -1,7 +1,7 @@
|
|||||||
bl_info = {
|
bl_info = {
|
||||||
"name": "Stylized Tree Generator",
|
"name": "Stylized Tree Generator",
|
||||||
"author": "D4rkst3r",
|
"author": "D4rkst3r",
|
||||||
"version": (1, 37, 0),
|
"version": (1, 38, 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",
|
||||||
@@ -86,7 +86,21 @@ DEFAULTS = {
|
|||||||
|
|
||||||
# Presets: nur die Abweichungen von DEFAULTS.
|
# Presets: nur die Abweichungen von DEFAULTS.
|
||||||
PRESETS = {
|
PRESETS = {
|
||||||
"baum": {},
|
# Der generische Laubbaum war eine SAEULE: gemessen Breite/Hoehe 0.18 ueber
|
||||||
|
# fuenf Seeds, schmaler als jedes andere Laubgehoelz hier. Ursache war
|
||||||
|
# dieselbe wie bei der Birke - "Branch Up" 1.3 aus den DEFAULTS liess die
|
||||||
|
# Aeste fast senkrecht nach oben zeigen und am Stamm kleben.
|
||||||
|
#
|
||||||
|
# Zielband der Spielseite: 0.45-0.60, "zwischen Birke (0.51) und einem
|
||||||
|
# klassischen Parkbaum".
|
||||||
|
#
|
||||||
|
# Die DEFAULTS bleiben unangetastet - sie sind die Socket-Vorgaben der
|
||||||
|
# Node-Gruppe, an denen jeder haendische Regler haengt. Nur "baum" bekommt
|
||||||
|
# eigene Werte; alle anderen Presets ueberschreiben Branch Up ohnehin.
|
||||||
|
"baum": {
|
||||||
|
"Branch Up": 0.5, "Branch Length": 2.1,
|
||||||
|
"Attraction Up": 0.15, # spreizen statt hochschwingen
|
||||||
|
},
|
||||||
# Palme - am TreeIt-Referenzbaum ausgemessen (palm07_0 + frond stem_1):
|
# Palme - am TreeIt-Referenzbaum ausgemessen (palm07_0 + frond stem_1):
|
||||||
# Stamm 4.33 m, Radius 0.133 unten -> ~0.086 Mitte -> 0.106 OBEN
|
# Stamm 4.33 m, Radius 0.133 unten -> ~0.086 Mitte -> 0.106 OBEN
|
||||||
# (Kronenschaft!), 11 Seiten, nur 150 Tris.
|
# (Kronenschaft!), 11 Seiten, nur 150 Tris.
|
||||||
@@ -1753,8 +1767,46 @@ def _dichte_einregeln(context, ob, md, ids, ziel, start_dichte, pro_stueck,
|
|||||||
return bestes[1], bestes[2]
|
return bestes[1], bestes[2]
|
||||||
|
|
||||||
|
|
||||||
def make_leaves(context, tree_obj, card_obj, name=None, card_coll=None, **overrides):
|
def make_leaves(context, tree_obj, card_obj, name=None, card_coll=None,
|
||||||
"""Erzeugt/aktualisiert ein <Baum>_Leaf-Objekt mit dem Blattwerk-Modifier."""
|
stufen=True, **overrides):
|
||||||
|
"""Erzeugt/aktualisiert ein <Baum>_Leaf-Objekt mit dem Blattwerk-Modifier.
|
||||||
|
|
||||||
|
Die WACHSTUMS-STAFFEL sitzt hier drin, nicht beim Aufrufer. Uebergeben
|
||||||
|
werden die Grundwerte (Size, Max Radius); welche Stufe daraus wird, zieht
|
||||||
|
diese Funktion selbst aus `growth_t` und `preset` am Baum-Objekt.
|
||||||
|
|
||||||
|
Warum hier und nicht beim Aufrufer: es gab zwei Wege zum Blattwerk - den
|
||||||
|
Knopf "Blattwerk streuen" und das Haekchen "_Leaf anlegen" beim Erzeugen -
|
||||||
|
und nur der erste rechnete die Staffel. Gemessen am S1-Modifier des
|
||||||
|
Create-Pfads: Size 0.85, Max Radius 0.04, Min Height 0.25, also exakt die
|
||||||
|
Altbaum-Werte. Das ist dieselbe Fehlerklasse wie bei der Stufen-Deckung und
|
||||||
|
bei build_group: zwei Quellen fuer dieselbe Frage. Solange die Staffel hier
|
||||||
|
liegt, KANN ein Aufrufer sie nicht vergessen.
|
||||||
|
|
||||||
|
`stufen=False` schaltet sie ab - fuer Aufrufer, die bewusst rohe Werte
|
||||||
|
setzen.
|
||||||
|
"""
|
||||||
|
if stufen:
|
||||||
|
# Stufe aus dem OBJEKT lesen, nicht aus dem Kontext: der Blattwerk-Lauf
|
||||||
|
# kommt spaeter und getrennt vom Erzeugen.
|
||||||
|
gt = float(tree_obj.get("growth_t", 1.0))
|
||||||
|
_ziel, g_anteil, h_anteil = _stufen_anteile(gt, tree_obj.get("preset"))
|
||||||
|
|
||||||
|
# Der RADIUS-FILTER muss mit dem Baum schrumpfen. Er trennt duenne
|
||||||
|
# Zweige (Laub) von dickem Geaest (kein Laub) - aber ein Setzling ist
|
||||||
|
# KOMPLETT duenner als die Schwelle. Gemessen mit festem 0.04 m: bei S1
|
||||||
|
# sassen 92 % der Cards am Stamm, bei S2 noch 81 %, das Laub klebte also
|
||||||
|
# am Stiel statt in der Krone. Boden bei LEAF_RADIUS_YOUNG, weil der
|
||||||
|
# reine Stammradius-Faktor (0.064 bei t = 0) unter die duennsten Zweige
|
||||||
|
# fiel und der Setzling wieder kahl stand (gemessen 0 Cards).
|
||||||
|
r_faktor = LEAF_RADIUS_YOUNG + (1.0 - LEAF_RADIUS_YOUNG) * gt
|
||||||
|
|
||||||
|
basis = dict(LEAF_DEFAULTS)
|
||||||
|
basis.update(overrides)
|
||||||
|
overrides["Size"] = basis["Size"] * g_anteil
|
||||||
|
overrides["Max Radius"] = basis["Max Radius"] * r_faktor
|
||||||
|
overrides["Min Height"] = h_anteil
|
||||||
|
|
||||||
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)
|
||||||
if ob is None:
|
if ob is None:
|
||||||
@@ -1784,6 +1836,41 @@ def make_leaves(context, tree_obj, card_obj, name=None, card_coll=None, **overri
|
|||||||
return ob
|
return ob
|
||||||
|
|
||||||
|
|
||||||
|
def blattwerk_streuen(context, tree_obj, s):
|
||||||
|
"""DER Weg zum Blattwerk - fuer beide Knoepfe derselbe.
|
||||||
|
|
||||||
|
Streut die Cards und regelt die Dichte auf die Ziel-Cardzahl der Stufe ein.
|
||||||
|
Wer Blattwerk erzeugt, ruft das hier und sonst nichts: die frueheren zwei
|
||||||
|
Wege liefen genau so lange auseinander, bis es jemandem im Spiel auffiel.
|
||||||
|
|
||||||
|
Gibt (Blatt-Objekt, gezaehlte Cards, Ziel-Cards) zurueck; ohne Regelung
|
||||||
|
sind die letzten beiden None.
|
||||||
|
"""
|
||||||
|
lf = make_leaves(context, tree_obj, s.leaf_card,
|
||||||
|
card_coll=s.leaf_cards,
|
||||||
|
Density=s.leaf_density,
|
||||||
|
Size=s.leaf_size,
|
||||||
|
**{"Size Var": s.leaf_size_var,
|
||||||
|
"Max Radius": s.leaf_max_radius,
|
||||||
|
"Tilt": s.leaf_tilt, "Seed": s.seed})
|
||||||
|
|
||||||
|
# Dichte auf die ZIEL-CARDZAHL einregeln. Muss NACH dem Anlegen passieren,
|
||||||
|
# weil dafuer gemessen werden muss, was tatsaechlich herauskommt - die
|
||||||
|
# Zweigflaeche faellt je Preset voellig anders aus.
|
||||||
|
gt = float(tree_obj.get("growth_t", 1.0))
|
||||||
|
ziel_cards, _g, _h = _stufen_anteile(gt, tree_obj.get("preset"))
|
||||||
|
if not ziel_cards:
|
||||||
|
return lf, None, None
|
||||||
|
md = lf.modifiers.get("GN_Leaves")
|
||||||
|
ng = md.node_group if md is not None else None
|
||||||
|
if ng is None:
|
||||||
|
return lf, None, None
|
||||||
|
pro = _cards_pro_stueck(s.leaf_card, s.leaf_cards)
|
||||||
|
dichte, ist = _dichte_einregeln(context, lf, md, leaf_socket_ids(ng),
|
||||||
|
ziel_cards, s.leaf_density, pro)
|
||||||
|
print("[TreeGen] %s: %d Cards (Ziel %d) bei Dichte %.0f"
|
||||||
|
% (tree_obj.name, ist, ziel_cards, dichte))
|
||||||
|
return lf, ist, ziel_cards
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -2909,48 +2996,10 @@ class TREEGEN_OT_leaves(Operator):
|
|||||||
total_tree = total_leaf = 0
|
total_tree = total_leaf = 0
|
||||||
for t in trees:
|
for t in trees:
|
||||||
try:
|
try:
|
||||||
# Bueschelgroesse mit dem Wachstum skalieren: ein Setzling
|
# Staffel und Dichte-Regelung stecken in blattwerk_streuen -
|
||||||
# traegt kleinere Bueschel. Baeume ohne Wachstums-Stufe haben
|
# damit laeuft dieser Knopf durch dieselbe Stelle wie das
|
||||||
# growth_t = 1.0 und bleiben unveraendert.
|
# Haekchen beim Erzeugen.
|
||||||
gt = float(t.get("growth_t", 1.0))
|
lf, _ist, _ziel = blattwerk_streuen(context, t, s)
|
||||||
ziel_cards, g_anteil, h_anteil = _stufen_anteile(
|
|
||||||
gt, t.get("preset"))
|
|
||||||
|
|
||||||
# Der RADIUS-FILTER muss mit dem Baum schrumpfen. Er trennt
|
|
||||||
# duenne Zweige (Laub) von dickem Geaest (kein Laub) - aber ein
|
|
||||||
# Setzling ist KOMPLETT duenner als die Schwelle. Gemessen mit
|
|
||||||
# festem 0.04 m: bei S1 sassen 92 % der Cards am Stamm, bei S2
|
|
||||||
# noch 81 %, das Laub klebte also am Stiel statt in der Krone.
|
|
||||||
# Skaliert wird mit dem Wachstum, aber mit BODEN bei 0.20.
|
|
||||||
# Der reine Stammradius-Faktor (0.064 bei t=0) war zu scharf:
|
|
||||||
# damit fiel die Schwelle unter die duennsten Zweige und der
|
|
||||||
# Setzling stand wieder kahl (gemessen 0 Cards). Bei 0.20
|
|
||||||
# bekommt er 12 Cards, davon nur 12 % am Stamm - vorher waren
|
|
||||||
# es 92 %.
|
|
||||||
r_faktor = LEAF_RADIUS_YOUNG + (1.0 - LEAF_RADIUS_YOUNG) * gt
|
|
||||||
|
|
||||||
lf = make_leaves(context, t, s.leaf_card,
|
|
||||||
card_coll=s.leaf_cards,
|
|
||||||
Density=s.leaf_density,
|
|
||||||
Size=s.leaf_size * g_anteil,
|
|
||||||
**{"Size Var": s.leaf_size_var,
|
|
||||||
"Max Radius": s.leaf_max_radius * r_faktor,
|
|
||||||
"Min Height": h_anteil,
|
|
||||||
"Tilt": s.leaf_tilt, "Seed": s.seed})
|
|
||||||
# Dichte auf die ZIEL-CARDZAHL einregeln. Muss nach dem Anlegen
|
|
||||||
# passieren, weil dafuer gemessen werden muss, was tatsaechlich
|
|
||||||
# herauskommt - die Zweigflaeche faellt je Preset voellig
|
|
||||||
# anders aus.
|
|
||||||
if ziel_cards:
|
|
||||||
md = lf.modifiers.get("GN_Leaves")
|
|
||||||
ng = md.node_group if md is not None else None
|
|
||||||
if ng is not None:
|
|
||||||
pro = _cards_pro_stueck(s.leaf_card, s.leaf_cards)
|
|
||||||
dichte, ist = _dichte_einregeln(
|
|
||||||
context, lf, md, leaf_socket_ids(ng),
|
|
||||||
ziel_cards, s.leaf_density, pro)
|
|
||||||
print("[TreeGen] %s: %d Cards (Ziel %d) bei Dichte %.0f"
|
|
||||||
% (t.name, ist, ziel_cards, dichte))
|
|
||||||
except Exception as exc: # noqa: BLE001
|
except Exception as exc: # noqa: BLE001
|
||||||
self.report({'WARNING'}, "%s: %s" % (t.name, exc))
|
self.report({'WARNING'}, "%s: %s" % (t.name, exc))
|
||||||
continue
|
continue
|
||||||
@@ -3071,13 +3120,13 @@ class TREEGEN_OT_create(Operator):
|
|||||||
for ob in created:
|
for ob in created:
|
||||||
if s.make_leaf:
|
if s.make_leaf:
|
||||||
if s.leaf_card is not None and s.leaf_card.type == 'MESH':
|
if s.leaf_card is not None and s.leaf_card.type == 'MESH':
|
||||||
# Card gesetzt -> Blattwerk direkt streuen
|
# Card gesetzt -> Blattwerk direkt streuen, ueber
|
||||||
extra.append(make_leaves(
|
# DIESELBE Stelle wie der Knopf "Blattwerk streuen".
|
||||||
context, ob, s.leaf_card,
|
# Vorher stand hier ein eigener make_leaves-Aufruf mit
|
||||||
Density=s.leaf_density, Size=s.leaf_size,
|
# rohem s.leaf_size - die Wachstums-Staffel fehlte
|
||||||
**{"Size Var": s.leaf_size_var,
|
# komplett, gemessen trug der S1-Setzling
|
||||||
"Max Radius": s.leaf_max_radius,
|
# Altbaum-Bueschel.
|
||||||
"Tilt": s.leaf_tilt, "Seed": s.seed}))
|
extra.append(blattwerk_streuen(context, ob, s)[0])
|
||||||
else:
|
else:
|
||||||
# keine Card -> leeres _Leaf zum Selbermodellieren
|
# keine Card -> leeres _Leaf zum Selbermodellieren
|
||||||
extra.append(_add_layer(context, ob, "_Leaf"))
|
extra.append(_add_layer(context, ob, "_Leaf"))
|
||||||
|
|||||||
@@ -0,0 +1,127 @@
|
|||||||
|
"""Beide Wege zum Blattwerk muessen dasselbe ergeben.
|
||||||
|
|
||||||
|
Es gibt zwei Knoepfe, die Blattwerk erzeugen:
|
||||||
|
* "_Leaf anlegen" beim Erzeugen des Baums (TREEGEN_OT_create)
|
||||||
|
* "Blattwerk streuen" auf einer Auswahl (TREEGEN_OT_leaves)
|
||||||
|
|
||||||
|
Nur der zweite rechnete die Wachstums-Staffel. Von der Spielseite am Modifier
|
||||||
|
nachgemessen: der S1-Setzling aus dem Create-Pfad trug Size 0.85, Max Radius
|
||||||
|
0.04, Min Height 0.25 - exakt die Altbaum-Werte.
|
||||||
|
|
||||||
|
Das ist dieselbe Fehlerklasse, die vorher schon die Stufen-Deckung und
|
||||||
|
build_group erwischt hat: ZWEI QUELLEN fuer dieselbe Frage. Ein Test, der nur
|
||||||
|
einen der beiden Wege prueft, findet so etwas nie - deshalb prueft dieser hier
|
||||||
|
die DIFFERENZ zwischen ihnen und nicht die absoluten Werte.
|
||||||
|
|
||||||
|
Aufruf: blender --background --factory-startup --python tests/test_zwei_pfade.py
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import importlib.util
|
||||||
|
|
||||||
|
import bpy
|
||||||
|
|
||||||
|
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
spec = importlib.util.spec_from_file_location(
|
||||||
|
"tg", os.path.join(ROOT, "stylized_tree_generator.py"))
|
||||||
|
tg = importlib.util.module_from_spec(spec)
|
||||||
|
sys.modules["tg"] = tg
|
||||||
|
spec.loader.exec_module(tg)
|
||||||
|
tg.register()
|
||||||
|
|
||||||
|
fails = []
|
||||||
|
|
||||||
|
for o in list(bpy.data.objects):
|
||||||
|
bpy.data.objects.remove(o, do_unlink=True)
|
||||||
|
|
||||||
|
me = bpy.data.meshes.new("Leaf_Card")
|
||||||
|
me.from_pydata([(-0.5, 0, 0), (0, 0, 0.7), (0.5, 0, 0), (0, 0, -0.1)],
|
||||||
|
[], [(0, 1, 2), (0, 2, 3)])
|
||||||
|
me.update()
|
||||||
|
card = bpy.data.objects.new("Leaf_Card", me)
|
||||||
|
bpy.context.scene.collection.objects.link(card)
|
||||||
|
|
||||||
|
s = bpy.context.scene.tree_gen_settings
|
||||||
|
s.leaf_card = card
|
||||||
|
s.apply_modifier = True
|
||||||
|
s.preset = "birke"
|
||||||
|
s.variant = 1
|
||||||
|
s.use_growth = True
|
||||||
|
s.stages = 4
|
||||||
|
|
||||||
|
# Die Werte, die die Staffel anfasst. "Density" steht bewusst NICHT dabei: sie
|
||||||
|
# wird nach dem Streuen per Bisektion eingeregelt und darf sich zwischen zwei
|
||||||
|
# Laeufen minimal unterscheiden.
|
||||||
|
GESTAFFELT = ("Size", "Max Radius", "Min Height")
|
||||||
|
|
||||||
|
|
||||||
|
def werte(leaf_obj):
|
||||||
|
md = leaf_obj.modifiers.get("GN_Leaves")
|
||||||
|
ids = tg.leaf_socket_ids(md.node_group)
|
||||||
|
return {k: round(float(md[ids[k]]), 6) for k in GESTAFFELT if k in ids}
|
||||||
|
|
||||||
|
|
||||||
|
def bauen(mit_haken):
|
||||||
|
for o in list(bpy.context.scene.objects):
|
||||||
|
if o is not card:
|
||||||
|
bpy.data.objects.remove(o, do_unlink=True)
|
||||||
|
s.art_name = "P" if mit_haken else "Q"
|
||||||
|
s.make_leaf = mit_haken
|
||||||
|
bpy.ops.object.treegen_create()
|
||||||
|
staemme = sorted((o for o in bpy.context.scene.objects
|
||||||
|
if o.type == 'MESH' and o is not card
|
||||||
|
and not o.name.endswith(("_Leaf", "_Frucht"))),
|
||||||
|
key=lambda o: o.name)
|
||||||
|
if not mit_haken:
|
||||||
|
for o in bpy.context.scene.objects:
|
||||||
|
o.select_set(o in staemme)
|
||||||
|
bpy.context.view_layer.objects.active = staemme[0]
|
||||||
|
bpy.ops.object.treegen_leaves()
|
||||||
|
out = []
|
||||||
|
for st in staemme:
|
||||||
|
bl = bpy.data.objects.get(st.name + "_Leaf")
|
||||||
|
out.append((st.name, float(st.get("growth_t", 1.0)),
|
||||||
|
werte(bl) if bl else None))
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
erzeugt = bauen(True) # Haekchen "_Leaf anlegen"
|
||||||
|
operator = bauen(False) # Knopf "Blattwerk streuen"
|
||||||
|
|
||||||
|
print("%-5s %7s | %-34s | %-34s"
|
||||||
|
% ("Stufe", "growth", "beim Erzeugen", "per Operator"))
|
||||||
|
for (n1, gt, w1), (n2, _gt, w2) in zip(erzeugt, operator):
|
||||||
|
def zeig(w):
|
||||||
|
if w is None:
|
||||||
|
return "keine Blattebene"
|
||||||
|
return " ".join("%s %.3f" % (k[:3], w[k]) for k in GESTAFFELT if k in w)
|
||||||
|
gleich = "" if w1 == w2 else " <-- WEICHT AB"
|
||||||
|
print("%-5s %7.2f | %-34s | %-34s%s"
|
||||||
|
% (n1.split("_")[-1], gt, zeig(w1), zeig(w2), gleich))
|
||||||
|
if w1 is None or w2 is None:
|
||||||
|
fails.append("%s: eine der beiden Ebenen fehlt" % n1)
|
||||||
|
continue
|
||||||
|
for k in GESTAFFELT:
|
||||||
|
if k not in w1 or k not in w2:
|
||||||
|
continue
|
||||||
|
if abs(w1[k] - w2[k]) > 1e-6:
|
||||||
|
fails.append("%s: '%s' ist beim Erzeugen %.4f, per Operator %.4f - "
|
||||||
|
"die Wachstums-Staffel greift nur auf einem Weg"
|
||||||
|
% (n1, k, w1[k], w2[k]))
|
||||||
|
|
||||||
|
# Und die Staffel muss ueberhaupt greifen: waeren beide Wege gleich FALSCH,
|
||||||
|
# waere der Vergleich oben zufrieden. Also zusaetzlich, dass sich S1 von S4
|
||||||
|
# unterscheidet.
|
||||||
|
if len(erzeugt) >= 2 and erzeugt[0][2] and erzeugt[-1][2]:
|
||||||
|
s1, s4 = erzeugt[0][2], erzeugt[-1][2]
|
||||||
|
if s1.get("Size") is not None and abs(s1["Size"] - s4["Size"]) < 1e-6:
|
||||||
|
fails.append("S1 und S4 tragen dieselbe Card-Groesse (%.3f) - die "
|
||||||
|
"Staffel greift auf KEINEM der beiden Wege" % s1["Size"])
|
||||||
|
|
||||||
|
print("")
|
||||||
|
if fails:
|
||||||
|
print("ERGEBNIS: %d FEHLER" % len(fails))
|
||||||
|
for f in fails:
|
||||||
|
print(" - " + f)
|
||||||
|
sys.exit(1)
|
||||||
|
print("ERGEBNIS: ALLE CHECKS OK")
|
||||||
Reference in New Issue
Block a user