diff --git a/README.md b/README.md index d50b8c2..3bd646e 100644 --- a/README.md +++ b/README.md @@ -563,6 +563,41 @@ 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 einer leeren `_Leaf`-Ebene; jetzt sagt der Operator es und nennt den Regler. +### Obstbaum-Silhouette: Trichter statt Krone + +Der Baum erfuellte Kronenradius, Deckung, Ansatz und Tri-Budget - und sah +trotzdem falsch aus. Gemessen wurde das erst am BREITENPROFIL der Krone in +Dritteln (unten/mitte/oben): + +| | unten | mitte | oben | oben/mitte | +|---|---|---|---|---| +| Eiche (liest sich als Baum) | 3.0 | 2.8 | 2.6 | **0.90** | +| Obstbaum vorher | 3.3 | 4.5 | 4.7 | **1.07** | + +Nach oben BREITER: die Laubmasse sass an den top-aussen endenden Astspitzen, +unten war die Krone hohl. Ein Trichter, keine Krone. + +Kein einzelner Regler hat das geloest - `Crown Taper`, `Branch Droop`, +`Crown Bulge` und `Attraction Up` einzeln durchgefahren liessen das Verhaeltnis +bei rund 1.0 stehen. Der Grund: die Kronenform folgt nicht der Astlaenge je +Hoehe, sondern dem WEG DER ASTSPITZEN. Solange die Aeste aufwaerts schwingen +(`Attraction Up` positiv), landet das aeusserste Laub oben - egal wie die +Laengen verteilt sind. + +Die Loesung ist eine andere Bauform, kein Feintuning: **kurze Aeste an einem +hohen Stamm** (Height 3.8 -> 4.9, Branch Length 2.55 -> 1.25) statt langer +Aeste an einem kurzen. Dazu `Attraction Up` +0.40 -> -0.05 (kein Aufschwung), +`Crown Taper` -0.30 -> +0.15 (oben kuerzere Aeste) und `Crown Bulge` 0.60 -> +1.10 (Bauch in der Mitte). + +Ueber alle neun Produktions-Seeds: oben/mitte **0.63-0.84**, Kronenradius +1.88-2.01 m, Deckung 100-134 % der Eiche, 3752-4166 Tris, Kronenansatz 39 %. + +Beim Einregeln kippte die Form zwischendurch in eine PILZform (unten am +breitesten, u/m 1.17) - deshalb steht das Profil jetzt als Test da und nicht +als Kommentar. `tests/test_obstbaum.py` prueft oben/mitte <= 0.85; Gegenprobe +mit den Werten aus 1.39 meldet neun Fehler bei 0.98-1.11. + ### Kronenfuellung: es zaehlt die Flaeche, nicht die Card-Zahl Der Obstbaum las sich in der Engine als einzelne belaubte Ast-Schlaeuche mit diff --git a/blender_manifest_tree.toml b/blender_manifest_tree.toml index 607d446..6e38682 100644 --- a/blender_manifest_tree.toml +++ b/blender_manifest_tree.toml @@ -1,7 +1,7 @@ schema_version = "1.0.0" id = "stylized_tree_generator" -version = "1.39.0" +version = "1.40.0" name = "Stylized Tree Generator" tagline = "Parametrische Baeume, Palmen, Bueschen mit Wachstums-Stufen" maintainer = "D4rkst3r" diff --git a/dist/index.html b/dist/index.html index b1e777e..6520cbc 100644 --- a/dist/index.html +++ b/dist/index.html @@ -50,17 +50,17 @@ 34.1KB - stylized_tree_generator-1.39.0 + stylized_tree_generator-1.40.0 Stylized Tree Generator Parametrische Baeume, Palmen, Bueschen mit Wachstums-Stufen link 4.2.0 - ~ all all - 46.0KB + 45.9KB -

Built 2026-08-06, 12:09

+

Built 2026-08-06, 12:32

diff --git a/dist/index.json b/dist/index.json index 40706be..e3274fb 100644 --- a/dist/index.json +++ b/dist/index.json @@ -82,7 +82,7 @@ "id": "stylized_tree_generator", "name": "Stylized Tree Generator", "tagline": "Parametrische Baeume, Palmen, Bueschen mit Wachstums-Stufen", - "version": "1.39.0", + "version": "1.40.0", "type": "add-on", "maintainer": "D4rkst3r", "license": [ @@ -98,9 +98,9 @@ "Mesh", "Modeling" ], - "archive_url": "./stylized_tree_generator-1.39.0.zip", - "archive_size": 47076, - "archive_hash": "sha256:44a7601ffb6cfb8004d213416cdc0fdd5592e0c2596a89ed6a32b2c06f12bb4d" + "archive_url": "./stylized_tree_generator-1.40.0.zip", + "archive_size": 46997, + "archive_hash": "sha256:530a636ee2a8a6750699c482ac718cd1c16e5ee9c2da8d9c78c6efa07927db05" } ] } \ No newline at end of file diff --git a/dist/stylized_tree_generator-1.39.0.zip b/dist/stylized_tree_generator-1.39.0.zip deleted file mode 100644 index 246fc1e..0000000 Binary files a/dist/stylized_tree_generator-1.39.0.zip and /dev/null differ diff --git a/dist/stylized_tree_generator-1.40.0.zip b/dist/stylized_tree_generator-1.40.0.zip new file mode 100644 index 0000000..41803d7 Binary files /dev/null and b/dist/stylized_tree_generator-1.40.0.zip differ diff --git a/stylized_tree_generator.py b/stylized_tree_generator.py index 32a26e7..66ac683 100644 --- a/stylized_tree_generator.py +++ b/stylized_tree_generator.py @@ -1,7 +1,7 @@ bl_info = { "name": "Stylized Tree Generator", "author": "D4rkst3r", - "version": (1, 39, 0), + "version": (1, 40, 0), "blender": (4, 2, 0), "location": "View3D > Sidebar > Tree Gen", "description": "Parametrischer Baum-/Palmen-/Busch-Generator (Geometry Nodes) mit Wachstums-Stufen", @@ -195,18 +195,16 @@ PRESETS = { # Und der Kronenansatz zaehlt gegen die GESAMThoehe: Branch Start 0.37 ergab # gemessen 29 %, nicht 37 %. "obstbaum": { - "Height": 3.8, "Trunk Radius": 0.13, "Bend": 0.3, "Taper": 0.7, + "Height": 4.9, "Trunk Radius": 0.13, "Bend": 0.3, "Taper": 0.7, "Branch Count": 9, # offene Krone: wenige Leitaeste, Licht durch - # Bleibt bei 2.55. Die Krone zu verkleinern waere der zweite Hebel gegen - # den niedrigen Fuellgrad gewesen - bei 2.35 fielen aber SECHS der neun - # Seeds unter den Kronenradius von 1.80 m, den die Frucht-Streuung - # braucht (1.73-1.79). Ueber drei Seeds gemittelt sah es mit 1.83 noch - # gut aus; erst der Neun-Seed-Test hat es aufgedeckt. - "Branch Length": 2.55, "Branch Up": 0.7, - "Branch Start": 0.52, "Branch End": 0.90, - "Crown Taper": -0.30, "Crown Bulge": 0.60, # rund, Bauch in der Mitte - "Attraction Up": 0.40, # Leitaeste steigen schraeg auf ... - "Branch Droop": 0.35, "Droop Curve": 3.0, # ... und bogen aussen ab + # Kurze Aeste an einem HOHEN Stamm. Vorher genau umgekehrt (Height 3.8, + # Branch Length 2.55) - daraus wurde ein Trichter, siehe unten. + "Branch Length": 1.25, "Branch Up": 0.7, + "Branch Start": 0.40, "Branch End": 0.90, + "Crown Taper": 0.15, # oben kuerzere Aeste (war -0.30 = besenfoermig) + "Crown Bulge": 1.1, # Bauch in der Mitte + "Attraction Up": -0.05, # KEIN Aufwaertsschwung mehr ... + "Branch Droop": 0.4, "Droop Curve": 3.0, # ... Spitzen kippen ab "Branch Bend": 0.9, "Branch Thickness": 0.22, "Sub Count": 3, "Sub Length": 0.7, "Root Flare": 1.0, }, diff --git a/tests/test_obstbaum.py b/tests/test_obstbaum.py index 765e4ec..e970e10 100644 --- a/tests/test_obstbaum.py +++ b/tests/test_obstbaum.py @@ -127,12 +127,13 @@ print("") # Birne 3/17/24. Ein Preset, das nur bei einem Seed passt, ist keines. SEEDS = (1, 15, 22, 2, 16, 23, 3, 17, 24) -print("%-6s %7s %6s %9s %9s %9s %7s %9s" - % ("Seed", "Hoehe", "B/H", "KronR", "Card-Max", "KronR/Card", "Ansatz", - "m2/m3")) +print("%-6s %7s %6s %9s %9s %7s %8s %-15s %s" + % ("Seed", "Hoehe", "B/H", "KronR", "KronR/Cd", "Ansatz", "m2/m3", + "Profil u/m/o", "o/m")) verh = [] deckungen = [] +obenmitte = [] for seed in SEEDS: for o in list(bpy.context.scene.objects): if o is not card: @@ -172,6 +173,7 @@ for seed in SEEDS: ev = bl.evaluated_get(dg) m2 = ev.to_mesh() blatt_flaeche = sum(p.area for p in m2.polygons) + blatt_pts = [v.co.copy() for v in m2.vertices] mitten = [] for grp in inseln(m2): pts = [m2.vertices[i].co for i in grp] @@ -186,13 +188,33 @@ for seed in SEEDS: lz = [z for _x, _y, z in mitten] kron_zyl = math.pi * lr ** 2 * (max(lz) - min(lz)) + # BREITENPROFIL der Krone in Dritteln. + # + # Ohne diesen Check kann die Krone alle bisherigen Zahlen erfuellen und + # trotzdem falsch aussehen: der erste Obstbaum war ein TRICHTER - nach oben + # breiter, unten hohl (gemessen oben/mitte 1.07 gegen 0.90 der Eiche). Die + # Laubmasse sass an den top-aussen endenden Astspitzen. Kronenradius, + # Deckung und Tri-Zahl waren dabei alle in Ordnung. + profil = [0.0, 0.0, 0.0] + if blatt_pts: + pz = [p.z for p in blatt_pts] + pz0, pz1 = min(pz), max(pz) + drittel = (pz1 - pz0) / 3.0 + for kk in range(3): + teil = [p for p in blatt_pts + if pz0 + kk * drittel <= p.z <= pz0 + (kk + 1) * drittel] + profil[kk] = 2 * max((math.hypot(p.x, p.y) for p in teil), + default=0.0) + om = profil[2] / profil[1] if profil[1] else 0.0 + obenmitte.append(om) + v = kron_r / card_max if card_max else 0.0 verh.append(v) deckung = blatt_flaeche / kron_zyl if kron_zyl else 0.0 deckungen.append(deckung) - print("%-6d %7.2f %6.2f %9.2f %9.2f %9.2f %6.0f%% %9.1f" - % (seed, hoehe, 2 * kron_r / hoehe, kron_r, card_max, v, ansatz, - deckung)) + print("%-6d %7.2f %6.2f %9.2f %9.2f %6.0f%% %8.1f %.1f/%.1f/%.1f %.2f" + % (seed, hoehe, 2 * kron_r / hoehe, kron_r, v, ansatz, deckung, + profil[0], profil[1], profil[2], om)) # Kronen-DECKUNG: Blattflaeche je m3 Kronenvolumen. # @@ -218,6 +240,14 @@ for seed in SEEDS: "Ast-Schlaeuche" % (seed, deckung, EICHE, 100 * deckung / EICHE)) + # 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. + if om > 0.85: + fails.append("Seed %d: Krone oben %.1f m breit, in der Mitte %.1f " + "(%.2f) - sie wird nach oben breiter statt sich zu " + "schliessen" % (seed, profil[2], profil[1], om)) + # DIE funktionale Bedingung. if v < 1.3: fails.append("Seed %d: Kronenradius %.2f m bei %.2f m Card - die Krone " @@ -241,6 +271,8 @@ for seed in SEEDS: # funktionale (Kronenradius), weil an ihr die Frucht-Streuung haengt. print("") print("Kronenradius/Card ueber alle Seeds: %.2f - %.2f" % (min(verh), max(verh))) +print("Krone oben/mitte ueber alle Seeds: %.2f - %.2f (Eiche 0.90, " + "Trichter war 1.07)" % (min(obenmitte), max(obenmitte))) print("Kronen-Deckung ueber alle Seeds: %.1f - %.1f m2/m3, Eiche %.1f " "(%.0f-%.0f %%)" % (min(deckungen), max(deckungen), EICHE,