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:
+102
-53
@@ -1,7 +1,7 @@
|
||||
bl_info = {
|
||||
"name": "Stylized Tree Generator",
|
||||
"author": "D4rkst3r",
|
||||
"version": (1, 37, 0),
|
||||
"version": (1, 38, 0),
|
||||
"blender": (4, 2, 0),
|
||||
"location": "View3D > Sidebar > Tree Gen",
|
||||
"description": "Parametrischer Baum-/Palmen-/Busch-Generator (Geometry Nodes) mit Wachstums-Stufen",
|
||||
@@ -86,7 +86,21 @@ DEFAULTS = {
|
||||
|
||||
# Presets: nur die Abweichungen von DEFAULTS.
|
||||
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):
|
||||
# Stamm 4.33 m, Radius 0.133 unten -> ~0.086 Mitte -> 0.106 OBEN
|
||||
# (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]
|
||||
|
||||
|
||||
def make_leaves(context, tree_obj, card_obj, name=None, card_coll=None, **overrides):
|
||||
"""Erzeugt/aktualisiert ein <Baum>_Leaf-Objekt mit dem Blattwerk-Modifier."""
|
||||
def make_leaves(context, tree_obj, card_obj, name=None, card_coll=None,
|
||||
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")
|
||||
ob = bpy.data.objects.get(name)
|
||||
if ob is None:
|
||||
@@ -1784,6 +1836,41 @@ def make_leaves(context, tree_obj, card_obj, name=None, card_coll=None, **overri
|
||||
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
|
||||
for t in trees:
|
||||
try:
|
||||
# Bueschelgroesse mit dem Wachstum skalieren: ein Setzling
|
||||
# traegt kleinere Bueschel. Baeume ohne Wachstums-Stufe haben
|
||||
# growth_t = 1.0 und bleiben unveraendert.
|
||||
gt = float(t.get("growth_t", 1.0))
|
||||
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))
|
||||
# Staffel und Dichte-Regelung stecken in blattwerk_streuen -
|
||||
# damit laeuft dieser Knopf durch dieselbe Stelle wie das
|
||||
# Haekchen beim Erzeugen.
|
||||
lf, _ist, _ziel = blattwerk_streuen(context, t, s)
|
||||
except Exception as exc: # noqa: BLE001
|
||||
self.report({'WARNING'}, "%s: %s" % (t.name, exc))
|
||||
continue
|
||||
@@ -3071,13 +3120,13 @@ class TREEGEN_OT_create(Operator):
|
||||
for ob in created:
|
||||
if s.make_leaf:
|
||||
if s.leaf_card is not None and s.leaf_card.type == 'MESH':
|
||||
# Card gesetzt -> Blattwerk direkt streuen
|
||||
extra.append(make_leaves(
|
||||
context, ob, s.leaf_card,
|
||||
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}))
|
||||
# Card gesetzt -> Blattwerk direkt streuen, ueber
|
||||
# DIESELBE Stelle wie der Knopf "Blattwerk streuen".
|
||||
# Vorher stand hier ein eigener make_leaves-Aufruf mit
|
||||
# rohem s.leaf_size - die Wachstums-Staffel fehlte
|
||||
# komplett, gemessen trug der S1-Setzling
|
||||
# Altbaum-Bueschel.
|
||||
extra.append(blattwerk_streuen(context, ob, s)[0])
|
||||
else:
|
||||
# keine Card -> leeres _Leaf zum Selbermodellieren
|
||||
extra.append(_add_layer(context, ob, "_Leaf"))
|
||||
|
||||
Reference in New Issue
Block a user