Tree Gen 1.30.0: Bueschel-Defaults, Wachstums-Kopplung, Stufen-Budget
Blatt-Defaults auf die Bueschel-Atlas-Aera umgestellt. Eine Card ist ein ganzer
Zweigbueschel, kein einzelnes Blatt - die alten Werte streuten hunderte winziger
Cards, wo ein paar Dutzend grosse hingehoeren. Werte an einer S4-Eiche in UE
gemessen und abgenommen:
Groesse 0.22 -> 0.85
Dichte 500 -> 170
Max Radius 0.02 -> 0.04 <- der Volumen-Hebel
Kippung 0.5 -> 0.9
Max Radius ist der unscheinbare Held: bei 0.02 trugen nur die aeussersten
Zweigspitzen Laub, die Krone hatte nur Kontur und war innen nackt.
Bueschelgroesse haengt jetzt am Wachstum: make_object merkt growth_t am Objekt
(aus dem Namen zu lesen waere bruechig), der Blattwerk-Operator skaliert die
Cards damit. Gemessen ueber vier Stufen: Card-Kante 0.19/0.28/0.37/0.45.
Falle dabei: "Dichte" ist Punkte pro m2 Zweigflaeche, und ein Setzling hat davon
fast keine - bei Stufe 1 kamen exakt NULL Cards heraus, der Setzling stand kahl
da. Da die Bueschel um denselben Faktor kleiner sind, deckt jeder nur faktor^2
der Flaeche; die Dichte gegenlaeufig anzuheben haelt die Blattdeckung gleich.
Tri-Budget PRO STUFE (ASSETS.md Teil 6): S1 <= 800, S2 <= 1200, S3/S4 <= 4000.
Bewusst Stufen und keine Interpolation; die Schwellen greifen auch bei drei
Stufen richtig (t = 0 / 0.5 / 1.0). Gemessen 494 / 878 / 1032 / 1396 - alle im
Rahmen.
WICHTIGSTE FALLE, von der UE-Seite gemeldet und hier nachgestellt:
md[socket] = wert loest KEINE Neuauswertung des GN-Modifiers aus. Die
Blattebene blieb bei 118 Faces stehen, obwohl die Dichte von 90 auf 400
geschrieben war - auch view_layer.update() half nicht, erst ein
show_viewport-Toggle. Der Knopf-Weg funktionierte bisher nur ZUFAELLIG, weil
make_leaves jedes Mal auch die Objekt-Zeiger neu schreibt und das Aendern eines
ID-Zeigers die Depsgraph-Beziehungen umbaut. Wer das spaeter optimiert ("nur
geaenderte Werte schreiben"), haette den Fehler still zurueck.
_modifier_anstossen() macht es jetzt explizit - in make_leaves UND make_object.
Nebenbei: der Variantentest rundete Card-Flaechen auf 5 Stellen; seit die
Kippung bei 0.9 steht streuen sie in der sechsten und eine einzige Card-Sorte
zerfiel in vier Eimer.
Neu: tests/test_blatt_bueschel.py. 23 Tests bestehen.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+110
-17
@@ -1,7 +1,7 @@
|
||||
bl_info = {
|
||||
"name": "Stylized Tree Generator",
|
||||
"author": "D4rkst3r",
|
||||
"version": (1, 29, 0),
|
||||
"version": (1, 30, 0),
|
||||
"blender": (4, 2, 0),
|
||||
"location": "View3D > Sidebar > Tree Gen",
|
||||
"description": "Parametrischer Baum-/Palmen-/Busch-Generator (Geometry Nodes) mit Wachstums-Stufen",
|
||||
@@ -1171,6 +1171,12 @@ def make_object(ng, preset="baum", name=None, growth=None, seed=None):
|
||||
for k, v in values.items():
|
||||
if k in ids:
|
||||
md[ids[k]] = v
|
||||
_modifier_anstossen(md)
|
||||
|
||||
# Wachstums-Fortschritt am Objekt merken. Der Blattwerk-Operator laeuft
|
||||
# spaeter und getrennt - er kann sonst nicht wissen, ob er einen Setzling
|
||||
# oder einen Altbaum vor sich hat. Aus dem Namen zu lesen waere bruechig.
|
||||
ob["growth_t"] = 1.0 if growth is None else max(0.0, min(1.0, float(growth)))
|
||||
|
||||
bpy.context.view_layer.objects.active = ob
|
||||
return ob
|
||||
@@ -1183,15 +1189,21 @@ def make_object(ng, preset="baum", name=None, growth=None, seed=None):
|
||||
|
||||
LEAF_GROUP_NAME = "GN_TreeLeaves"
|
||||
|
||||
# Eine Card ist ein ganzer ZWEIGBUESCHEL, kein einzelnes Blaettchen. Die alten
|
||||
# Werte (Size 0.22, Density 300-500) stammen aus der Blaettchen-Aera: damit
|
||||
# streute der Generator hunderte winziger Cards, wo ein paar Dutzend grosse
|
||||
# hingehoeren. Mit Bueschel-Atlanten liegen die Groessenordnungen bei
|
||||
# Size ~0.7 und Density ~90.
|
||||
LEAF_DEFAULTS = {
|
||||
"Density": 300.0, # Punkte pro m2 Zweigoberflaeche. Gemessen mit der
|
||||
# 6-Tri-Diamond-Card: 300 -> ~180 Cards / 1092 Tris,
|
||||
# zusammen mit dem Baum 2228 von 2500 Tris. 900 waere
|
||||
# mit 4118 Tris deutlich ueber Budget.
|
||||
"Max Radius": 0.02, # nur wo der Ast duenner ist als das (m) -> Zweige
|
||||
"Size": 0.35,
|
||||
"Density": 170.0, # Punkte pro m2 Zweigoberflaeche
|
||||
# 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
|
||||
# UE gegengeprueft, nicht geschaetzt.
|
||||
"Max Radius": 0.04,
|
||||
"Size": 0.85,
|
||||
"Size Var": 0.35,
|
||||
"Tilt": 0.5, # wie stark die Cards aus der Senkrechten kippen (rad)
|
||||
"Tilt": 0.9, # mehr Rotations-Chaos schliesst Sichtluecken
|
||||
"Seed": 0,
|
||||
"Use Collection": False,
|
||||
}
|
||||
@@ -1369,6 +1381,34 @@ def leaf_socket_ids(ng):
|
||||
if getattr(so, "in_out", "") == 'INPUT'}
|
||||
|
||||
|
||||
def _stufen_budget(s, growth_t):
|
||||
"""Tri-Budget fuer einen Baum nach seiner Wachstums-Stufe."""
|
||||
if growth_t < 0.15:
|
||||
return s.leaf_budget_s1
|
||||
if growth_t <= 0.55:
|
||||
return s.leaf_budget_s2
|
||||
return s.leaf_budget
|
||||
|
||||
|
||||
def _modifier_anstossen(md):
|
||||
"""Erzwingt die Neuauswertung nach direktem Schreiben von Socket-Werten.
|
||||
|
||||
FALLE, die von der UE-Seite gemeldet und hier nachgestellt wurde: `md[socket]
|
||||
= wert` loest KEINE Neuauswertung aus. Gemessen blieb die Blattebene bei 118
|
||||
Faces stehen, obwohl die Dichte von 90 auf 400 geschrieben war - auch
|
||||
`view_layer.update()` half nicht. Erst ein show_viewport-Toggle brachte das
|
||||
Ergebnis.
|
||||
|
||||
Der Knopf-Weg funktionierte bisher nur ZUFAELLIG: make_leaves schreibt jedes
|
||||
Mal auch die Objekt-Zeiger neu, und das Aendern eines ID-Zeigers baut die
|
||||
Depsgraph-Beziehungen um, was den Cache verwirft. Wer das spaeter optimiert
|
||||
("nur geaenderte Werte schreiben"), haette den Fehler still zurueck.
|
||||
"""
|
||||
sichtbar = md.show_viewport
|
||||
md.show_viewport = not sichtbar
|
||||
md.show_viewport = sichtbar
|
||||
|
||||
|
||||
def make_leaves(context, tree_obj, card_obj, name=None, card_coll=None, **overrides):
|
||||
"""Erzeugt/aktualisiert ein <Baum>_Leaf-Objekt mit dem Blattwerk-Modifier."""
|
||||
name = name or (tree_obj.name + "_Leaf")
|
||||
@@ -1396,6 +1436,7 @@ def make_leaves(context, tree_obj, card_obj, name=None, card_coll=None, **overri
|
||||
for k, v in vals.items():
|
||||
if k in ids:
|
||||
md[ids[k]] = v
|
||||
_modifier_anstossen(md)
|
||||
return ob
|
||||
|
||||
|
||||
@@ -1820,23 +1861,50 @@ class TreeGenSettings(PropertyGroup):
|
||||
"damit mischt die Krone 4-6 Varianten statt einer"),
|
||||
)
|
||||
leaf_density: FloatProperty(
|
||||
name="Dichte", default=150.0, min=0.0, max=20000.0,
|
||||
name="Dichte", default=170.0, min=0.0, max=20000.0,
|
||||
description=("Cards pro m2 Zweigoberflaeche. Presets mit viel Geaest (Eiche) "
|
||||
"bekommen dadurch automatisch mehr Blaetter als schlanke. 150 haelt "
|
||||
"auch die Eiche im 2500-Tri-Budget; der Button meldet die Summe"),
|
||||
)
|
||||
leaf_budget_s1: IntProperty(
|
||||
name="Budget S1", default=800, min=100, max=20000,
|
||||
description="Tri-Budget der Setzlings-Stufe (ASSETS.md Teil 6)",
|
||||
)
|
||||
leaf_budget_s2: IntProperty(
|
||||
name="Budget S2", default=1200, min=100, max=20000,
|
||||
description="Tri-Budget der zweiten Stufe (ASSETS.md Teil 6)",
|
||||
)
|
||||
leaf_budget: IntProperty(
|
||||
name="Tri-Budget", default=2500, min=0, max=100000,
|
||||
description="Bei Ueberschreitung warnt der Blattwerk-Button (0 = keine Pruefung)",
|
||||
name="Budget S3/S4", default=4000, min=0, max=100000,
|
||||
description=("Tri-Budget der ausgewachsenen Stufe. Die urspruenglichen "
|
||||
"2500 waren eine konservative Setzung - bei ISM-Instanzen "
|
||||
"mit LOD-Kette sind Roh-Dreiecke nicht der Engpass, "
|
||||
"sondern Overdraw und Schatten der Cards. Bei "
|
||||
"Ueberschreitung warnt der Blattwerk-Button (0 = aus)"),
|
||||
)
|
||||
leaf_size: FloatProperty(
|
||||
name="Groesse", default=0.85, min=0.01, max=5.0,
|
||||
description=("Kantenlaenge einer Card. Eine Card ist ein ganzer "
|
||||
"Zweigbueschel, kein einzelnes Blatt - deshalb 0.7 und "
|
||||
"nicht 0.2"),
|
||||
)
|
||||
leaf_size_growth: FloatProperty(
|
||||
name="Setzlings-Anteil", default=0.45, min=0.05, max=1.0,
|
||||
description=("Wie gross die Bueschel auf der KLEINSTEN Wachstumsstufe "
|
||||
"sind, als Anteil der Endgroesse. 1.0 = keine Skalierung. "
|
||||
"Ein Setzling traegt kleinere Bueschel als der Altbaum"),
|
||||
)
|
||||
leaf_size: FloatProperty(name="Groesse", default=0.22, min=0.01, max=5.0)
|
||||
leaf_size_var: FloatProperty(name="Groessen-Streuung", default=0.35, min=0.0, max=1.0)
|
||||
leaf_max_radius: FloatProperty(
|
||||
name="Nur Zweige duenner als", default=0.02, min=0.001, max=0.5,
|
||||
name="Nur Zweige duenner als", default=0.04, min=0.001, max=0.5,
|
||||
description=("Blaetter nur dort, wo der Ast duenner als dieser Radius ist (m). "
|
||||
"Verhindert Blaetter am Stamm"),
|
||||
)
|
||||
leaf_tilt: FloatProperty(name="Kippung", default=0.5, min=0.0, max=3.14159)
|
||||
leaf_tilt: FloatProperty(
|
||||
name="Kippung", default=0.9, min=0.0, max=3.14159,
|
||||
description=("Wie stark die Cards aus der Senkrechten kippen. Mehr "
|
||||
"Rotations-Chaos schliesst Sichtluecken in der Krone"),
|
||||
)
|
||||
make_frucht: BoolProperty(
|
||||
name="_Frucht-Ebene anlegen", default=False,
|
||||
description="Zusaetzlich <Name>_Frucht (gleicher Ursprung) fuer die Frucht-Ebene",
|
||||
@@ -2350,9 +2418,23 @@ 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))
|
||||
faktor = s.leaf_size_growth + (1.0 - s.leaf_size_growth) * gt
|
||||
# Dichte GEGENLAEUFIG mitziehen. "Dichte" ist Punkte pro m2
|
||||
# Zweigoberflaeche, und ein Setzling hat davon fast keine -
|
||||
# gemessen kam bei Stufe 1 exakt NULL Cards heraus, der
|
||||
# Setzling stand kahl da. Da die Bueschel um `faktor` kleiner
|
||||
# sind, deckt jeder nur faktor^2 der Flaeche; die Dichte
|
||||
# entsprechend anzuheben haelt die Blattdeckung ueber alle
|
||||
# Stufen gleich, statt den Setzling auszuduennen.
|
||||
dichte = s.leaf_density / max(faktor * faktor, 1e-3)
|
||||
lf = make_leaves(context, t, s.leaf_card,
|
||||
card_coll=s.leaf_cards,
|
||||
Density=s.leaf_density, Size=s.leaf_size,
|
||||
Density=dichte,
|
||||
Size=s.leaf_size * faktor,
|
||||
**{"Size Var": s.leaf_size_var,
|
||||
"Max Radius": s.leaf_max_radius,
|
||||
"Tilt": s.leaf_tilt, "Seed": s.seed})
|
||||
@@ -2374,7 +2456,14 @@ class TREEGEN_OT_leaves(Operator):
|
||||
total = total_tree + total_leaf
|
||||
msg = ("Blattwerk fuer %d Baum/Baeume: %d Stamm + %d Blatt = %d Tris"
|
||||
% (len(trees), total_tree, total_leaf, total))
|
||||
limit = s.leaf_budget * max(len(trees), 1)
|
||||
# Budget PRO STUFE statt global (ASSETS.md Teil 6): S1 <= 800,
|
||||
# S2 <= 1200, S3/S4 <= 4000. Bewusst STUFEN und keine Interpolation -
|
||||
# so steht es in den Unterlagen, und ein Setzling steht nie im
|
||||
# Vordergrund. Die Schwellen liegen so, dass sie auch bei 3 Stufen
|
||||
# (t = 0 / 0.5 / 1.0) noch richtig greifen.
|
||||
limit = 0
|
||||
for t in trees:
|
||||
limit += _stufen_budget(s, float(t.get("growth_t", 1.0)))
|
||||
if total_leaf == 0:
|
||||
# Passiert regulaer beim Kaktus (kein Ast duenner als Max Radius) -
|
||||
# aber auch, wenn Max Radius fuer den Baum zu klein steht. Beides
|
||||
@@ -2570,10 +2659,14 @@ class VIEW3D_PT_tree_leaves(_TreeSub):
|
||||
col.active = s.leaf_card is not None or s.leaf_cards is not None
|
||||
col.prop(s, "leaf_density")
|
||||
col.prop(s, "leaf_size")
|
||||
col.prop(s, "leaf_size_growth")
|
||||
col.prop(s, "leaf_size_var")
|
||||
col.prop(s, "leaf_max_radius")
|
||||
col.prop(s, "leaf_tilt")
|
||||
col.prop(s, "leaf_budget")
|
||||
row = col.row(align=True)
|
||||
row.prop(s, "leaf_budget_s1", text="S1")
|
||||
row.prop(s, "leaf_budget_s2", text="S2")
|
||||
row.prop(s, "leaf_budget", text="S3/S4")
|
||||
layout.separator()
|
||||
layout.operator("object.treegen_leaves", icon='OUTLINER_OB_POINTCLOUD')
|
||||
if s.leaf_cards is not None:
|
||||
|
||||
Reference in New Issue
Block a user