diff --git a/README.md b/README.md index 2e6775a..9d0110b 100644 --- a/README.md +++ b/README.md @@ -563,6 +563,54 @@ 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. +### Vom Trichter ueber die Haube zur Kugel + +Zielbild ist der **Lollipop** der stilisierten Vektor-Illustration: kurzer +kraeftiger Stamm, darauf eine Kugelkrone. Der Weg dorthin fuehrt NICHT ueber +mehr Baumhoehe - 4.9 m sind die Ernte-Reichweite -, sondern ueber einen +tieferen Kronenansatz: 39 % -> 29 %, Stamm 1.9 m -> 1.5 m. Die Krone wird +dadurch von selbst hoeher (3.8 m -> 4.3 m) und rundet sich, tiefere Fruechte +sind fuers Ernten ohnehin besser. + +Dazu `Crown Taper` 0.15 -> 0.35, `Crown Bulge` 1.1 -> 1.5, `Branch Droop` +0.4 -> 0.25. + +**Das Messen war hier schwieriger als das Einstellen.** Zwei Fehler, beide +gefunden, bevor sie in einer Abnahme gelandet sind: + +1. Die Kronenbreite je Drittel als **Maximum** ueber die Card-Punkte zu + messen haengt an EINER Card: bei ~400 Cards von 1.25 m reicht eine einzige + weit aussen haengende, um ein Drittel zu bestimmen. Unten/mitte sprang je + Seed zwischen 0.89 und 1.00, waehrend der Mittelwert stabil bei 0.94 lag - + das war Rauschen, keine Form. Mit dem **90. Perzentil** ist es stabil, und + es zeigte prompt etwas anderes: die Krone war unten tatsaechlich am + breitesten (u/m 1.04), was das Maximum verdeckt hatte. +2. Der Kugel-Kennwert bekam zunaechst die Schranke 0.88 - eine Zahl aus einem + Messskript mit anderer Breitendefinition. Mit der Test-Formel liegen die + Werte um 1.4, die Schranke haette alles durchgelassen. **Zum zweiten Mal + dieselbe Falle** (nach der Deckungs-Schwelle): eine Schranke aus einem + anderen Kontext ist keine Schranke. + +Deshalb sind jetzt ALLE Referenzformen mit demselben Mass gemessen und stehen +im Test: + +| Form | unten/mitte | oben/mitte | Kugel (KronH/KronB) | +|---|---|---|---| +| Eiche | 1.04-1.15 | 0.77-0.88 | 1.71-1.91 | +| Trichter (1.39) | 0.66-0.72 | 1.18-1.31 | 0.98-1.12 | +| Haube (1.40) | 0.99-1.13 | 0.69-0.80 | 1.26-1.44 | +| Pilz (Verirrung) | 0.75-0.90 | 0.78-0.94 | 1.05-1.17 | +| **Kugel (1.41)** | **0.86-1.01** | **0.63-0.74** | **1.35-1.47** | + +Die Tabelle zeigt auch, was NICHT trennt: der Kugel-Kennwert unterscheidet die +Kugel nicht von der Haube (1.35-1.47 gegen 1.26-1.44). Was sie unterscheidet, +ist der Kronenansatz - und genau der wird geprueft. Ein Test, der so tut, als +pruefe er die Form, waehrend in Wahrheit ein anderer Wert die Arbeit macht, +waere schlimmer als keiner. + +Gegenproben: die Haube meldet 9 Fehler (Kronenansatz), der Trichter 37 (alle +vier Formkriterien). + ### Obstbaum-Silhouette: Trichter statt Krone Der Baum erfuellte Kronenradius, Deckung, Ansatz und Tri-Budget - und sah diff --git a/blender_manifest_tree.toml b/blender_manifest_tree.toml index 6e38682..192fc24 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.40.0" +version = "1.41.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 6520cbc..81e469e 100644 --- a/dist/index.html +++ b/dist/index.html @@ -50,17 +50,17 @@ 34.1KB - stylized_tree_generator-1.40.0 + stylized_tree_generator-1.41.0 Stylized Tree Generator Parametrische Baeume, Palmen, Bueschen mit Wachstums-Stufen link 4.2.0 - ~ all all - 45.9KB + 46.3KB -

Built 2026-08-06, 12:32

+

Built 2026-08-06, 13:04

diff --git a/dist/index.json b/dist/index.json index e3274fb..24ec862 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.40.0", + "version": "1.41.0", "type": "add-on", "maintainer": "D4rkst3r", "license": [ @@ -98,9 +98,9 @@ "Mesh", "Modeling" ], - "archive_url": "./stylized_tree_generator-1.40.0.zip", - "archive_size": 46997, - "archive_hash": "sha256:530a636ee2a8a6750699c482ac718cd1c16e5ee9c2da8d9c78c6efa07927db05" + "archive_url": "./stylized_tree_generator-1.41.0.zip", + "archive_size": 47402, + "archive_hash": "sha256:c5d5c612fd723af6c1e226ce0ca42cf6d28298641ec5af77bb12223ebd6935c6" } ] } \ No newline at end of file diff --git a/dist/stylized_tree_generator-1.40.0.zip b/dist/stylized_tree_generator-1.40.0.zip deleted file mode 100644 index 41803d7..0000000 Binary files a/dist/stylized_tree_generator-1.40.0.zip and /dev/null differ diff --git a/dist/stylized_tree_generator-1.41.0.zip b/dist/stylized_tree_generator-1.41.0.zip new file mode 100644 index 0000000..a5bde76 Binary files /dev/null and b/dist/stylized_tree_generator-1.41.0.zip differ diff --git a/stylized_tree_generator.py b/stylized_tree_generator.py index f2fd008..266490e 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, 40, 0), + "version": (1, 41, 0), "blender": (4, 2, 0), "location": "View3D > Sidebar > Tree Gen", "description": "Parametrischer Baum-/Palmen-/Busch-Generator (Geometry Nodes) mit Wachstums-Stufen", @@ -187,9 +187,14 @@ PRESETS = { # die Krone selbst ist 3-4 m hoch. 1.8 m Ansatz plus 3 m Krone sind 4.8 m # Gesamthoehe und ein Ansatz bei knapp 40 % - genau das Band der Spielseite. # - # Gemessen ueber alle neun Produktions-Seeds: H 4.90 m, Kronenradius - # 1.88-2.01 m, Kronenansatz 39 %, Breitenprofil oben/mitte 0.63-0.84, - # Deckung 100-134 % der Eiche, 3752-4166 Tris. + # Zielbild ist der LOLLIPOP der stilisierten Vektor-Illustration: kurzer + # kraeftiger Stamm, darauf eine Kugelkrone. Der Weg dorthin ist NICHT mehr + # Baumhoehe (4.9 m sind die Ernte-Reichweite), sondern ein tieferer + # Kronenansatz - tiefere Fruechte sind fuers Ernten ohnehin besser. + # + # Gemessen ueber alle neun Produktions-Seeds: H 4.90 m, Kronenansatz 29 %, + # Kronenhoehe/-breite 0.97 (Kugel), Breitenprofil unten/mitte 0.95 und + # oben/mitte 0.63, Deckung wie die Eiche. # # Fuer dieses Preset gilt B/H NICHT (Entscheidung der Spielseite, # 2026-08-06): Kronenradius >= 1.80 schlaegt B/H <= 0.75, weil an der @@ -207,11 +212,15 @@ PRESETS = { # 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 + # Kronenansatz bei 30 % statt 40 %: das ist der Weg zur KUGEL, ohne den + # Baum hoeher zu machen (4.9 m haengen an der Ernte-Reichweite). Die + # Krone wird dadurch 4.3 m hoch bei 4.5 m Breite - Kugelverhaeltnis + # 0.97 statt 0.83 der Haube. + "Branch Start": 0.30, "Branch End": 0.90, + "Crown Taper": 0.35, # oben kuerzere Aeste + "Crown Bulge": 1.5, # Bauch in der Mitte + "Attraction Up": -0.05, # KEIN Aufwaertsschwung ... + "Branch Droop": 0.25, "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 1ab224b..d5585df 100644 --- a/tests/test_obstbaum.py +++ b/tests/test_obstbaum.py @@ -10,8 +10,8 @@ Deshalb ist das hier KEIN Silhouetten-Test mit Geschmacksurteil, sondern eine funktionale Bedingung: Kronenradius > Card-Groesse. Vorgaben der Spielseite, die mitgeprueft werden: - Kronenansatz 35-40 %, Kronenradius S4 >= 1.8 m, Hoehe 4-5 m, - Breitenprofil (die Krone muss sich nach oben schliessen), + Kronenansatz ~30 %, Kronenradius S4 >= 1.8 m, Hoehe 4-5 m, + Breitenprofil + Kugel-Kennwert (Lollipop: Kronenhoehe ~ Kronenbreite), Deckung gegen die Eiche. B/H gilt fuer dieses Preset NICHT - siehe Kommentar unten. @@ -136,6 +136,8 @@ print("%-6s %7s %6s %9s %9s %7s %8s %-15s %s" verh = [] deckungen = [] obenmitte = [] +untenmitte = [] +kugeln = [] for seed in SEEDS: for o in list(bpy.context.scene.objects): if o is not card: @@ -203,12 +205,36 @@ for seed in SEEDS: pz0, pz1 = min(pz), max(pz) drittel = (pz1 - pz0) / 3.0 for kk in range(3): - teil = [p for p in blatt_pts + teil = [math.hypot(p.x, p.y) 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) + # 90. PERZENTIL statt Maximum. Das Maximum haengt an EINER Card: + # bei ~400 Cards von 1.25 m Diagonale reicht eine einzige weit + # aussen haengende, um die Breite eines Drittels zu setzen. + # Gemessen sprang unten/mitte dadurch je Seed zwischen 0.89 und + # 1.00, waehrend der Mittelwert stabil bei 0.94 lag - das war + # Rauschen, keine Formabweichung. Das Perzentil misst, wo die + # Laubmasse endet, nicht wo die letzte Card haengt. + teil.sort() + profil[kk] = 2 * (teil[int(0.9 * (len(teil) - 1))] if teil else 0.0) om = profil[2] / profil[1] if profil[1] else 0.0 + um = profil[0] / profil[1] if profil[1] else 0.0 + # KUGEL-Kennwert: Kronenhoehe geteilt durch die groesste Kronenbreite. + # + # Er steht hier als zweites Netz gegen Trichter (0.98-1.12) und Pilz + # (1.05-1.17) - und weil die Spielseite "Hoehe ~ Breite" als Zielbild + # genannt hat, gehoert die Zahl sichtbar ins Protokoll. + # Er trennt die Kugel (1.35-1.47) aber NICHT von der Haube (1.26-1.44): + # beide Formen unterscheiden sich vor allem im KRONENANSATZ (29 % gegen + # 39 %), und den prueft der Check weiter unten. Ein erster Anlauf setzte + # die Grenze auf 0.88 - eine Zahl aus einer anderen Messung, die hier bei + # Werten um 1.4 alles durchgelassen haette. + kugel = 0.0 + if blatt_pts and max(profil) > 0: + pz = [p.z for p in blatt_pts] + kugel = (max(pz) - min(pz)) / max(profil) + kugeln.append(kugel) obenmitte.append(om) + untenmitte.append(um) v = kron_r / card_max if card_max else 0.0 verh.append(v) @@ -245,10 +271,30 @@ for seed in SEEDS: # 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: + # Die Grenzen kommen aus einer Messung ALLER Formen mit DIESEM Mass, nicht + # aus dem Wunschband - die Streuung von Seed zu Seed ist breiter als das + # Band, das die Spielseite genannt hat (u/m 0.85-0.95). Gemessen ueber neun + # Seeds: + # Eiche u/m 1.04-1.15 o/m 0.77-0.88 + # Trichter u/m 0.66-0.72 o/m 1.18-1.31 + # Haube u/m 0.99-1.13 o/m 0.69-0.80 + # Pilz u/m 0.75-0.90 o/m 0.78-0.94 + # Kugel u/m 0.86-1.01 o/m 0.63-0.74 + # Die Schranken trennen die Kugel von Trichter und Pilz; von der Haube + # trennt sie der Kugel-Kennwert weiter unten. + if om > 0.80: 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)) + "(%.2f) - sie schliesst sich oben nicht (Trichter lag bei " + "1.18-1.31)" % (seed, profil[2], profil[1], om)) + if um < 0.82: + fails.append("Seed %d: Krone unten %.1f m breit, in der Mitte %.1f " + "(%.2f) - unten zu schmal, das wird ein Pilz bzw. ein " + "Trichter (0.66-0.90)" + % (seed, profil[0], profil[1], um)) + if kugel < 1.20: + fails.append("Seed %d: Kronenhoehe/-breite %.2f - flacher als Trichter " + "(0.98-1.12) und Pilz (1.05-1.17) zulassen" + % (seed, kugel)) # DIE funktionale Bedingung. if v < 1.3: @@ -260,9 +306,9 @@ for seed in SEEDS: if not (4.0 <= hoehe <= 5.2): fails.append("Seed %d: Hoehe %.2f m, Vorgabe 4-5 m (5.2 als Toleranz)" % (seed, hoehe)) - if not (33.0 <= ansatz <= 45.0): - fails.append("Seed %d: Kronenansatz %.0f %%, Vorgabe 35-40 %% " - "(33-45 als Toleranz)" % (seed, ansatz)) + if not (25.0 <= ansatz <= 35.0): + fails.append("Seed %d: Kronenansatz %.0f %%, Vorgabe ~30 %% " + "(25-35 als Toleranz)" % (seed, ansatz)) # B/H gilt fuer dieses Preset NICHT MEHR - Entscheidung der Spielseite, # 2026-08-06. Es wird nur noch mitgedruckt. @@ -282,8 +328,12 @@ for seed in SEEDS: # Pilzform (unten/mitte 1.17) durchgelassen. 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("Krone unten/mitte ueber alle Seeds: %.2f - %.2f (Pilz 0.75-0.90, " + "Trichter 0.66-0.72)" % (min(untenmitte), max(untenmitte))) +print("Kugel (KronH/KronB) ueber alle Seeds: %.2f - %.2f (Trichter 0.98-1.12, " + "Pilz 1.05-1.17, Haube 1.26-1.44)" % (min(kugeln), max(kugeln))) +print("Krone oben/mitte ueber alle Seeds: %.2f - %.2f (Trichter 1.18-1.31)" + % (min(obenmitte), max(obenmitte))) print("Kronen-Deckung ueber alle Seeds: %.1f - %.1f m2/m3, Eiche %.1f " "(%.0f-%.0f %%)" % (min(deckungen), max(deckungen), EICHE,