1) Neues Preset "obstbaum" (Apfel/Kirsche/Birne) Eigenes Preset statt Umbau von "baum": der wird anderswo verwendet, und ein Hochstamm ist keine Standard-Silhouette. Der Zweck ist funktional, nicht bloss optisch: die Frucht-Streuung setzt Fruechte auf die AUSSENHAUT der Krone. Am generischen baum war der Kronenradius 0.78 m bei 1.28 m Card-Diagonale - die Cards waren groesser als die Krone, es gab keine Haut. Proportion belegt (Hochstamm): Kronenansatz 180-220 cm, Krone selbst 3-4 m. Ergibt 4.8 m Gesamthoehe und knapp 40 % Ansatz - genau das Band der Spielseite. Gemessen ueber alle NEUN Seeds der Spielseite: Hoehe 4.91-5.12 m, Kronenradius 1.83-2.05 m, Ansatz 38-40 %, Kronenradius/Card 2.15-2.42, 3560-3824 Tris. Deckelung auf 420 Cards ueber LEAF_STUFEN_PRESET, sonst 4304-4400 Tris. Zwei Fallen, gemessen statt vermutet: "Height" ist die STAMMlaenge, nicht die Baumhoehe (Height 4.8 ergab 5.4-6.2 m), und der Kronenansatz zaehlt gegen die GESAMThoehe (Branch Start 0.37 ergab 29 %). test_obstbaum.py prueft die funktionale Bedingung ueber alle neun Seeds. B/H ist BEWUSST nicht hart geprueft: B/H 0.55-0.75 und Kronenradius >= 1.80 bei 4-5 m Hoehe lassen zusammen nur ein 4 % breites Fenster, waehrend die Streuung von Seed zu Seed 12 % betraegt. Vorrang hat der Kronenradius. 2) Card-Groesse misst sich an der BAUMHOEHE Mit 0.45/0.63/0.82/1.00 schrumpfte die Card um Faktor 2.2, der Baum um 6.2. Gemessen an der Birke 27/14/11/9 % der Baumhoehe, an der Eiche 33 gegen 11 - der Setzling trug relativ dreimal so grosses Laub wie der Altbaum. Jetzt 0.24/0.46/0.75/1.00: 14/10/10/9 %. Nicht ganz flach, ein junger Baum traegt seine Bueschel relativ groeber; auf Altbaum-Proportion gezwungen (0.18) wird er kahl. test_blatt_bueschel prueft die Spreizung jetzt direkt und ueber Inseln statt ueber Face-Index-Paare. HINWEIS an die Spielseite: der Addon-Pfad skaliert S1 auf 0.45 der S4-Groesse, auf BEIDEN Wegen nachgemessen (Einzelcard und Sammlung). Die dort gemessenen 0.76 entstehen also erst danach - bitte growth_t am S1-Objekt direkt vor treegen_leaves pruefen, das muss 0.0 sein. 25 Tests bestehen. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
376 lines
16 KiB
Python
376 lines
16 KiB
Python
"""Blatt-Defaults fuer BUESCHEL-Atlanten (eine Card = ein ganzer Zweigbueschel).
|
|
|
|
Die alten Werte (Size 0.22, Density 150-500) stammen aus der Blaettchen-Aera:
|
|
damit streute der Generator hunderte winziger Cards, wo ein paar Dutzend grosse
|
|
hingehoeren. Diese Datei nagelt die neuen Groessenordnungen fest und prueft die
|
|
Kopplung an die Wachstums-Stufen.
|
|
|
|
Aufruf: blender --background --factory-startup --python tests/test_blatt_bueschel.py
|
|
"""
|
|
import os
|
|
import sys
|
|
import math
|
|
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
|
|
|
|
# --- 1) Auslieferungszustand ---------------------------------------------
|
|
print("Defaults: Dichte %.0f, Groesse %.2f, Setzlings-Anteil %.2f"
|
|
% (s.leaf_density, s.leaf_size, s.leaf_size_growth))
|
|
if not (0.6 <= s.leaf_size <= 1.1):
|
|
fails.append("leaf_size ist %.2f - fuer Bueschel-Atlanten werden ~0.85 "
|
|
"gebraucht, 0.2 waere ein einzelnes Blaettchen" % s.leaf_size)
|
|
# S3/S4 stehen auf 4300, nicht mehr auf 4000: die Spielseite hebt das Budget
|
|
# in ASSETS.md an, nachdem der 170er-Export abgenommen wurde. Die Doku folgt dem
|
|
# Auge, aber erst nach dem Export - deshalb steht die Zahl hier schon.
|
|
if s.leaf_budget_s1 > 800 or s.leaf_budget_s2 > 1200 or s.leaf_budget > 4300:
|
|
fails.append("Stufen-Budgets (%d/%d/%d) ueber der Vorgabe (800/1200/4300)"
|
|
% (s.leaf_budget_s1, s.leaf_budget_s2, s.leaf_budget))
|
|
if not (130.0 <= s.leaf_density <= 200.0):
|
|
fails.append("leaf_density ist %.0f - der Sweetspot liegt bei 130-170"
|
|
% s.leaf_density)
|
|
# Die Deckung steht als Tabelle pro Stufe, nicht als Formel. Card-ANZAHL muss
|
|
# ueber die Stufen steigen, Card-Groesse auch.
|
|
_z0, _g0, _h0 = tg._stufen_anteile(0.0)
|
|
_z1, _g1, _h1 = tg._stufen_anteile(1.0)
|
|
if _g0 >= _g1:
|
|
fails.append("Groessen-Anteil steigt nicht ueber die Stufen (%.2f -> %.2f)"
|
|
% (_g0, _g1))
|
|
# Der Volumen-Hebel: bei 0.02 trugen nur die aeussersten Zweigspitzen Laub und
|
|
# die Krone war innen nackt.
|
|
if s.leaf_max_radius < 0.035:
|
|
fails.append("leaf_max_radius ist %.3f - unter 0.035 tragen nur die "
|
|
"Zweigspitzen Laub und die Krone bleibt innen nackt"
|
|
% s.leaf_max_radius)
|
|
if s.leaf_tilt < 0.7:
|
|
fails.append("leaf_tilt ist %.2f - unter 0.7 fehlt das Rotations-Chaos, "
|
|
"das die Sichtluecken schliesst" % s.leaf_tilt)
|
|
if abs(tg.LEAF_DEFAULTS["Size"] - s.leaf_size) > 0.2:
|
|
fails.append("LEAF_DEFAULTS['Size'] (%.2f) und die UI (%.2f) laufen "
|
|
"auseinander" % (tg.LEAF_DEFAULTS["Size"], s.leaf_size))
|
|
|
|
|
|
def bauen(preset, wachstum=False, stufen=4):
|
|
for o in list(bpy.context.scene.objects):
|
|
if o is not card:
|
|
bpy.data.objects.remove(o, do_unlink=True)
|
|
s.preset = preset
|
|
s.art_name = preset.capitalize()
|
|
s.variant = 1
|
|
s.apply_modifier = True
|
|
s.use_growth = wachstum
|
|
s.stages = stufen
|
|
s.leaf_card = card
|
|
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)
|
|
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()
|
|
return staemme
|
|
|
|
|
|
def messen(stamm):
|
|
bl = bpy.data.objects.get(stamm.name + "_Leaf")
|
|
if bl is None:
|
|
return 0, 0.0, 0
|
|
dg = bpy.context.evaluated_depsgraph_get()
|
|
ev = bl.evaluated_get(dg)
|
|
m = ev.to_mesh()
|
|
cards = len(m.polygons) // 2
|
|
kante = math.sqrt(sum(p.area for p in m.polygons) / cards) if cards else 0.0
|
|
ev.to_mesh_clear()
|
|
ev2 = stamm.evaluated_get(dg)
|
|
m2 = ev2.to_mesh()
|
|
tris = sum(len(p.vertices) - 2 for p in m2.polygons) + cards * 2
|
|
ev2.to_mesh_clear()
|
|
return cards, kante, tris
|
|
|
|
|
|
# --- 2) Alle Presets: Card-Zahl plausibel, Budget gehalten ---------------
|
|
print("")
|
|
# BUDGET: ASSETS.md nennt 2500 Tris fuer Baum (Stamm + Krone). Mit
|
|
# Bueschel-Atlanten liegt LOD0 darueber - das ist eine bewusste Entscheidung der
|
|
# Spielseite: der dichtere LOD0 traegt den Look aus der Naehe, und die LOD-Kette
|
|
# dampft ihn auf 16 % ein. Die harte Grenze liegt deshalb hier bei 3000; alles
|
|
# zwischen 2500 und 3000 wird als Hinweis ausgegeben, damit es nicht unbemerkt
|
|
# weiterwaechst.
|
|
# Die urspruenglichen 2500 waren eine konservative Setzung. Bei ISM-Instanzen
|
|
# mit LOD-Kette sind Roh-Dreiecke nicht der Engpass, sondern der OVERDRAW der
|
|
# Cards (die GPU zahlt pro Bildschirmflaeche, nicht pro Dreieck) und die
|
|
# Schattenpaesse. Genau deshalb sind 170 grosse Bueschel guenstiger als 500
|
|
# kleine - bei gleichem Tri-Zaehler. Die Zahl hier ist die Hartgrenze fuer die
|
|
# ausgewachsene Stufe; die endgueltige Freigabe kommt aus einer Messung im
|
|
# Level (Testwald, stat unit / stat gpu), nicht aus diesem Test.
|
|
LOD0_HART = s.leaf_budget
|
|
LOD0_HINWEIS = int(s.leaf_budget * 0.8)
|
|
ueber = []
|
|
print("%-12s %6s %11s %9s" % ("Preset", "Cards", "Card-Kante", "Tris ges"))
|
|
for p in tg.PRESETS:
|
|
staemme = bauen(p)
|
|
cards, kante, tris = messen(staemme[0])
|
|
marke = ""
|
|
if tris > LOD0_HART:
|
|
marke = "UEBER HARTGRENZE"
|
|
elif tris > LOD0_HINWEIS:
|
|
marke = "ueber 2500 (LOD0 ok)"
|
|
ueber.append((p, tris))
|
|
print("%-12s %6d %11.2f %9d %s" % (p, cards, kante, tris, marke))
|
|
if tris > LOD0_HART:
|
|
fails.append("%s: %d Tris ueber der LOD0-Hartgrenze von %d"
|
|
% (p, tris, LOD0_HART))
|
|
if p == "kaktus":
|
|
continue # kein Ast duenner als Max Radius - richtig so
|
|
if cards == 0:
|
|
fails.append("%s: keine Cards gestreut" % p)
|
|
# Ob es Bueschel oder Blaettchen sind, entscheidet die GROESSE, nicht die
|
|
# Anzahl: ein grosser Baum darf 600 Bueschel tragen. Die Menge deckelt das
|
|
# Tri-Budget oben ohnehin.
|
|
elif kante < 0.30:
|
|
fails.append("%s: Card-Kante nur %.2f m - das sind Blaettchen, keine "
|
|
"Bueschel" % (p, kante))
|
|
|
|
if ueber:
|
|
print("Hinweis: %s liegen ueber dem Stufenbudget - fuer LOD0 abgesegnet, bei "
|
|
"Bedarf ist die Dichte der Feinregler (130-170)."
|
|
% ", ".join("%s (%d)" % (p, t) for p, t in ueber))
|
|
|
|
# --- 3) Wachstum: Bueschel wachsen mit, Setzling ist NICHT kahl ----------
|
|
staemme = bauen("baum", wachstum=True, stufen=4)
|
|
print("")
|
|
print("%-18s %6s %6s %11s" % ("Stufe", "t", "Cards", "Card-Kante"))
|
|
kanten, zahlen = [], []
|
|
for st in staemme:
|
|
cards, kante, _ = messen(st)
|
|
gt = float(st.get("growth_t", 1.0))
|
|
print("%-18s %6.2f %6d %11.2f" % (st.name, gt, cards, kante))
|
|
kanten.append(kante)
|
|
zahlen.append(cards)
|
|
if cards == 0:
|
|
# Genau das war der Fehler: "Dichte" ist Punkte pro m2 Zweigflaeche,
|
|
# und ein Setzling hat davon fast keine.
|
|
fails.append("%s: keine Cards - ein Setzling darf nicht kahl sein"
|
|
% st.name)
|
|
|
|
for i in range(1, len(kanten)):
|
|
if kanten[i] <= kanten[i - 1]:
|
|
fails.append("Stufe %d hat Card-Kante %.2f, Stufe %d hatte %.2f - die "
|
|
"Bueschel wachsen nicht mit"
|
|
% (i + 1, kanten[i], i, kanten[i - 1]))
|
|
|
|
if kanten and kanten[0] > 0:
|
|
anteil = kanten[0] / kanten[-1]
|
|
print("Setzlings-Bueschel: %.0f%% der Endgroesse (Regler steht auf %.0f%%)"
|
|
% (100 * anteil, 100 * s.leaf_size_growth))
|
|
# Der gemessene Anteil darf nicht ueber dem Regler liegen - dann haette die
|
|
# Skalierung nicht gegriffen.
|
|
if anteil > s.leaf_size_growth + 0.15:
|
|
fails.append("Setzlings-Bueschel sind %.0f%% der Endgroesse, der Regler "
|
|
"steht auf %.0f%% - die Wachstums-Kopplung greift nicht"
|
|
% (100 * anteil, 100 * s.leaf_size_growth))
|
|
|
|
# --- 3z) Card-Groesse gegen die BAUMHOEHE --------------------------------
|
|
# Die absolute Diagonale sagt nichts: sie haengt daran, wie gross die Card im
|
|
# Atlas gezeichnet ist. Aussagekraeftig ist ihr Verhaeltnis zur Baumhoehe, und
|
|
# das war schief. Gemessen an der Birke: S1 27 %, S4 9 % - der Setzling trug
|
|
# relativ dreimal so grosses Laub wie der Altbaum und sah aus wie ein Laubpaket
|
|
# auf einem Stiel (an der Eiche 33 % gegen 11 %).
|
|
#
|
|
# Gemessen wird ueber INSELN, nicht ueber Face-Index-Paare. Die naheliegende
|
|
# Annahme "je zwei aufeinanderfolgende Faces sind eine Card" haelt nach dem
|
|
# Realize nicht zwingend - beide Wege stimmten hier zwar ueberein, aber die
|
|
# Insel-Zaehlung braucht die Annahme gar nicht erst.
|
|
print("")
|
|
print("%-18s %6s %9s %9s" % ("Stufe", "Cards", "Diagonale", "% der Hoehe"))
|
|
verhaeltnisse = []
|
|
for st in staemme:
|
|
bl = bpy.data.objects.get(st.name + "_Leaf")
|
|
if bl is None:
|
|
continue
|
|
dg = bpy.context.evaluated_depsgraph_get()
|
|
ev = bl.evaluated_get(dg)
|
|
m = ev.to_mesh()
|
|
nachbarn = {}
|
|
for e in m.edges:
|
|
a, b = e.vertices
|
|
nachbarn.setdefault(a, []).append(b)
|
|
nachbarn.setdefault(b, []).append(a)
|
|
gesehen, diagonalen = set(), []
|
|
for start in range(len(m.vertices)):
|
|
if start in gesehen:
|
|
continue
|
|
stapel, gruppe = [start], []
|
|
gesehen.add(start)
|
|
while stapel:
|
|
v = stapel.pop()
|
|
gruppe.append(v)
|
|
for w in nachbarn.get(v, ()):
|
|
if w not in gesehen:
|
|
gesehen.add(w)
|
|
stapel.append(w)
|
|
pts = [m.vertices[i].co for i in gruppe]
|
|
diagonalen.append(max((a - b).length for a in pts for b in pts))
|
|
ev.to_mesh_clear()
|
|
if not diagonalen:
|
|
continue
|
|
zs = [v.co.z for v in st.data.vertices]
|
|
hoehe = max(zs) - min(zs)
|
|
mittel = sum(diagonalen) / len(diagonalen)
|
|
anteil = 100.0 * mittel / hoehe
|
|
verhaeltnisse.append(anteil)
|
|
print("%-18s %6d %9.3f %10.0f%%" % (st.name, len(diagonalen), mittel, anteil))
|
|
|
|
if len(verhaeltnisse) >= 2:
|
|
spreizung = max(verhaeltnisse) / min(verhaeltnisse)
|
|
print("Setzling traegt relativ %.1fx groessere Cards als der Altbaum"
|
|
% spreizung)
|
|
# Ein junger Baum DARF seine Bueschel relativ groeber tragen - aber nicht
|
|
# dreimal so grob. Zwingt man ihn auf Altbaum-Proportion, wird er kahl;
|
|
# auch das ist durchgemessen. Faktor 2.0 ist die Grenze.
|
|
if spreizung > 2.0:
|
|
fails.append("Card-Groesse relativ zur Baumhoehe spreizt um Faktor "
|
|
"%.1f ueber die Stufen - der Setzling wird ein Laubpaket"
|
|
% spreizung)
|
|
|
|
# --- 3a) Hoehen-Filter: der Fuss bleibt frei -----------------------------
|
|
# Der Radius-Filter allein reicht nicht. Er trennt duenn von dick, aber ein
|
|
# junger Stamm ist UEBERALL duenn - gemessen sassen beim Setzling 44 % der
|
|
# Cards unter 40 % der Hoehe, die tiefste bei 6 %. Das ergibt eine belaubte
|
|
# Saeule statt einer Krone. Der Hoehen-Filter setzt den Kronenansatz je Stufe.
|
|
print("")
|
|
print("%-18s %7s %6s %11s %11s" % ("Stufe", "Hoehe", "Cards", "unter Ansatz",
|
|
"tiefste Card"))
|
|
for st in staemme:
|
|
bl = bpy.data.objects.get(st.name + "_Leaf")
|
|
if bl is None:
|
|
continue
|
|
zs = [v.co.z for v in st.data.vertices]
|
|
z0, h = min(zs), max(zs) - min(zs)
|
|
gt = float(st.get("growth_t", 1.0))
|
|
_, _, ansatz = tg._stufen_anteile(gt)
|
|
|
|
dg = bpy.context.evaluated_depsgraph_get()
|
|
ev = bl.evaluated_get(dg)
|
|
m = ev.to_mesh()
|
|
# Card-Mitten ueber die Face-Gruppen (die Testkarte hat 2 Faces je Card)
|
|
proc = 2
|
|
mitten = []
|
|
for i in range(0, len(m.polygons), proc):
|
|
gruppe = m.polygons[i:i + proc]
|
|
mitten.append(sum(p.center.z for p in gruppe) / len(gruppe))
|
|
ev.to_mesh_clear()
|
|
if not mitten:
|
|
continue
|
|
|
|
unter = 100.0 * sum(1 for z in mitten if (z - z0) / h < ansatz) / len(mitten)
|
|
tiefste = 100.0 * (min(mitten) - z0) / h
|
|
print("%-18s %7.2f %6d %9.0f%% %10.0f%%"
|
|
% (st.name, h, len(mitten), unter, tiefste))
|
|
|
|
# Ein paar Prozent Toleranz: die Card-Mitte darf leicht unter den Ansatz
|
|
# rutschen, weil die Card um ihren Punkt herum sitzt.
|
|
if unter > 15.0:
|
|
fails.append("%s: %.0f%% der Cards unter dem Kronenansatz (%.0f%% der "
|
|
"Hoehe) - der Fuss ist belaubt" % (st.name, unter,
|
|
100 * ansatz))
|
|
if tiefste < ansatz * 100 * 0.7:
|
|
fails.append("%s: tiefste Card bei %.0f%% der Hoehe, Kronenansatz liegt "
|
|
"bei %.0f%% - Laub klebt am Stamm"
|
|
% (st.name, tiefste, 100 * ansatz))
|
|
|
|
# Der Ansatz muss beim Setzling HOEHER liegen als beim Altbaum: dessen
|
|
# Verteilung passt schon von selbst, der Setzling braucht die Hilfe.
|
|
_, _, a_jung = tg._stufen_anteile(0.0)
|
|
_, _, a_alt = tg._stufen_anteile(1.0)
|
|
print("Kronenansatz: S1 %.0f%%, S4 %.0f%%" % (100 * a_jung, 100 * a_alt))
|
|
if a_jung <= a_alt:
|
|
fails.append("Kronenansatz beim Setzling (%.2f) nicht hoeher als beim "
|
|
"Altbaum (%.2f)" % (a_jung, a_alt))
|
|
|
|
# --- 3b) Budget je Stufe statt global -----------------------------------
|
|
print("")
|
|
print("%-18s %6s %8s %8s" % ("Stufe", "t", "Tris", "Budget"))
|
|
for st in staemme:
|
|
cards, _, tris = messen(st)
|
|
gt = float(st.get("growth_t", 1.0))
|
|
budget = tg._stufen_budget(s, gt)
|
|
print("%-18s %6.2f %8d %8d %s"
|
|
% (st.name, gt, tris, budget, "UEBER" if tris > budget else ""))
|
|
if tris > budget:
|
|
fails.append("%s: %d Tris ueber dem Stufenbudget %d - ein Setzling mit "
|
|
"Altbaum-Dichte ist Verschwendung" % (st.name, tris, budget))
|
|
# Die Budgets muessen nach oben gehen, sonst ist die Staffelung sinnlos.
|
|
if not (s.leaf_budget_s1 < s.leaf_budget_s2 <= s.leaf_budget):
|
|
fails.append("Stufen-Budgets steigen nicht: %d / %d / %d"
|
|
% (s.leaf_budget_s1, s.leaf_budget_s2, s.leaf_budget))
|
|
|
|
# --- 4) Ohne Wachstum bleibt alles unveraendert -------------------------
|
|
staemme = bauen("baum", wachstum=False)
|
|
cards_ohne, kante_ohne, _ = messen(staemme[0])
|
|
print("")
|
|
print("Ohne Wachstum: %d Cards, Kante %.2f (growth_t=%.2f)"
|
|
% (cards_ohne, kante_ohne, float(staemme[0].get("growth_t", 1.0))))
|
|
if abs(kante_ohne - kanten[-1]) > 0.02:
|
|
fails.append("Ein Baum ohne Wachstums-Stufen (%.2f) sieht anders aus als "
|
|
"die letzte Stufe (%.2f)" % (kante_ohne, kanten[-1]))
|
|
|
|
# --- 3c) Ziel-Cardzahl wird preset-UNABHAENGIG eingeregelt ---------------
|
|
# Frueher standen hier Dichte-Faktoren. Die mussten je Preset neu geeicht
|
|
# werden: "Dichte" ist Punkte pro m2 Zweigflaeche, und die verteilt sich je
|
|
# Baum voellig anders - ein Faktor, der am Standardbaum 11 Cards ergab,
|
|
# ueberschoss auf der Eiche um mehr als das Doppelte. Jetzt steht die
|
|
# Stueckzahl da und wird eingeregelt; der Test prueft sie DIREKT.
|
|
print("")
|
|
print("%-10s %8s %8s %8s %8s" % ("Preset", "S1", "S2", "S3", "S4"))
|
|
for pname in ("baum", "eiche", "birke"):
|
|
reihe = bauen(pname, wachstum=True, stufen=4)
|
|
pro = tg._cards_pro_stueck(card, None)
|
|
dg = bpy.context.evaluated_depsgraph_get()
|
|
zellen = []
|
|
for st in reihe:
|
|
bl = bpy.data.objects.get(st.name + "_Leaf")
|
|
n = tg._card_zahl(bl, pro, dg) if bl else 0
|
|
gt = float(st.get("growth_t", 1.0))
|
|
ziel, _, _ = tg._stufen_anteile(gt)
|
|
zellen.append((n, ziel))
|
|
if ziel:
|
|
# 25 % Toleranz: die Bisektion arbeitet auf einer diskreten
|
|
# Punktverteilung, exakt treffen kann sie nicht.
|
|
if abs(n - ziel) > max(2, ziel * 0.25):
|
|
fails.append("%s/%s: %d Cards, Ziel %d - die Regelung trifft "
|
|
"nicht" % (pname, st.name, n, ziel))
|
|
print("%-10s %8s %8s %8s %8s"
|
|
% (pname, *["%d/%s" % (n, z if z else "voll") for n, z in zellen]))
|
|
|
|
print("")
|
|
if fails:
|
|
print("ERGEBNIS: %d FEHLER" % len(fails))
|
|
for x in fails:
|
|
print(" - " + x)
|
|
sys.exit(1)
|
|
print("ERGEBNIS: ALLE CHECKS OK")
|