From 833e929f8229c3b310c95ca4afe9bb33cfd8136c Mon Sep 17 00:00:00 2001 From: D4rkst3r Date: Thu, 30 Jul 2026 11:42:52 +0200 Subject: [PATCH] Tree Generator v1.23.0: Blattwerk auf die Zweige streuen Blatt-Card waehlen -> wird auf die duennen Zweige gestreut (Filter ueber das Radius-Attribut des Meshes) und als _Leaf mit identischem Ursprung abgelegt. Regler fuer Dichte, Groesse, Streuung, Kippung, Zweig-Schwelle. Der Button meldet Stamm + Blatt = Summe und warnt bei Ueberschreitung des Tri-Budgets. Gemessen: Eiche 1428 + Blattwerk 1032 = 2460 von 2500 Tris bei Default-Dichte 150. Co-Authored-By: Claude Opus 4.8 --- README.md | 9 + blender_manifest_tree.toml | 2 +- dist/index.json | 8 +- dist/stylized_tree_generator-1.22.0.zip | Bin 19331 -> 0 bytes dist/stylized_tree_generator-1.23.0.zip | Bin 0 -> 22135 bytes stylized_tree_generator.py | 260 +++++++++++++++++++++++- tests/test_leaves.py | 61 ++++++ 7 files changed, 332 insertions(+), 8 deletions(-) delete mode 100644 dist/stylized_tree_generator-1.22.0.zip create mode 100644 dist/stylized_tree_generator-1.23.0.zip create mode 100644 tests/test_leaves.py diff --git a/README.md b/README.md index f9e20c9..c55f037 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,14 @@ Gruppe. Blaetter/Krone macht man selbst — das Addon liefert die Struktur. **gleicher Seed = dieselbe Baum-Identitaet** (fuer Wachstums-Systeme im Spiel). - **Live bearbeiten:** Nach dem Erzeugen liegen alle Regler am Modifier `GN_Tree` — im Viewport ziehen, der Baum aktualisiert sich sofort. +- **Blattwerk:** Ein Blatt-/Billboard-Objekt als *Blatt-Card* waehlen (z. B. eine + Diamond-Plane mit Alpha-Textur) → der Button streut es auf die **Zweige** und legt + das Ergebnis als `_Leaf` mit identischem Ursprung ab. Gefiltert wird ueber + den Astradius (`Max Radius`), damit keine Blaetter am Stamm kleben. Ohne Card + bleibt `_Leaf` leer zum Selbermodellieren. +- **Tri-Budget:** Der Blattwerk-Button meldet `Stamm + Blatt = Summe` und **warnt** + bei Ueberschreitung. Default-Dichte 150 haelt auch das schwerste Preset (Eiche, + 1428 Tris) mit 2460 von 2500 Tris im Budget. - **Export:** „Modifier anwenden" friert das Ergebnis als normales Mesh ein. **v1.1.0:** Aeste folgen jetzt der Elternkurve (Tangent/Normal aus *Curve to @@ -188,6 +196,7 @@ Weitere Suiten (jeweils `blender --background --python `): | `tests/test_ecogame_rock.py` | Fels-Benennung, Bake liefert nur `_albedo`/`_normal`, AO im Albedo | | `tests/test_ecogame_tree.py` | Baum-Benennung `__S`, Ursprung, Lightmap-Default | | `tests/test_randomize.py` | Zufalls-Funktion beider Addons, `_Leaf`/`_Frucht` mit gleichem Ursprung | +| `tests/test_leaves.py` | Blattwerk: Streuung, Ursprung, Tri-Budget, leeres `_Leaf` ohne Card | Die Suiten erwarten die gebauten Zips in `dist/` bzw. lesen die `.py` direkt. diff --git a/blender_manifest_tree.toml b/blender_manifest_tree.toml index 7e62cf4..9cb4d80 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.22.0" +version = "1.23.0" name = "Stylized Tree Generator" tagline = "Parametrische Baeume, Palmen, Bueschen mit Wachstums-Stufen" maintainer = "D4rkst3r" diff --git a/dist/index.json b/dist/index.json index 74ed23f..82493f3 100644 --- a/dist/index.json +++ b/dist/index.json @@ -32,7 +32,7 @@ "id": "stylized_tree_generator", "name": "Stylized Tree Generator", "tagline": "Parametrische Baeume, Palmen, Bueschen mit Wachstums-Stufen", - "version": "1.22.0", + "version": "1.23.0", "type": "add-on", "maintainer": "D4rkst3r", "license": [ @@ -48,9 +48,9 @@ "Mesh", "Modeling" ], - "archive_url": "./stylized_tree_generator-1.22.0.zip", - "archive_size": 19331, - "archive_hash": "sha256:5e5e3b5e0b73aabe416f93c27bbef4772b561122762dd52daf836f10c0e45b35" + "archive_url": "./stylized_tree_generator-1.23.0.zip", + "archive_size": 22135, + "archive_hash": "sha256:a9b986aaf5e95f7a9600d529eb3eb305af3d1edc6b327862035cecde50fed6e9" } ] } \ No newline at end of file diff --git a/dist/stylized_tree_generator-1.22.0.zip b/dist/stylized_tree_generator-1.22.0.zip deleted file mode 100644 index 202497e8accb10190a152719436f161078b89759..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 19331 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 djZwBI91Kbne*x_a=wtu@ diff --git a/dist/stylized_tree_generator-1.23.0.zip b/dist/stylized_tree_generator-1.23.0.zip new file mode 100644 index 0000000000000000000000000000000000000000..c3f0394f86ed6cfaf644d6c99351a1f78c86a793 GIT binary patch literal 22135 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 W Sidebar > Tree Gen", "description": "Parametrischer Baum-/Palmen-/Busch-Generator (Geometry Nodes) mit Wachstums-Stufen", @@ -1051,6 +1051,159 @@ def make_object(ng, preset="baum", name=None, growth=None, seed=None): +# --------------------------------------------------------------------------- +# Blattwerk: Billboard-Cards auf die duennen Zweige streuen +# --------------------------------------------------------------------------- + +LEAF_GROUP_NAME = "GN_TreeLeaves" + +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, + "Size Var": 0.35, + "Tilt": 0.5, # wie stark die Cards aus der Senkrechten kippen (rad) + "Seed": 0, +} + + +def build_leaf_group(rebuild=False): + """Node-Gruppe, die eine Blatt-Card auf die ZWEIGE eines Baumes streut. + + Der Trick: der Generator legt den Astradius als Attribut "uv_r" am Mesh ab + (gemessen 0.8 mm bis 178 mm). Damit lassen sich Blaetter gezielt nur dort + setzen, wo der Ast duenn ist - sonst klebten sie auch am Stamm. + + WICHTIG: eine vorhandene Gruppe wird WIEDERVERWENDET. Sie bei jedem Aufruf + zu loeschen und neu zu bauen reisst sie allen bereits erzeugten _Leaf- + Objekten unter dem Modifier weg - die standen dann ohne Blattwerk da. + """ + existing = bpy.data.node_groups.get(LEAF_GROUP_NAME) + if existing is not None and not rebuild: + return existing + if existing is not None: + bpy.data.node_groups.remove(existing) + ng = bpy.data.node_groups.new(LEAF_GROUP_NAME, "GeometryNodeTree") + N, L = ng.nodes.new, ng.links.new + iface = ng.interface + iface.new_socket("Geometry", in_out='OUTPUT', socket_type='NodeSocketGeometry') + + def add_in(name, stype, default=None, mn=None, mx=None): + so = iface.new_socket(name, in_out='INPUT', socket_type=stype) + if default is not None: + so.default_value = default + if mn is not None: + so.min_value = mn + if mx is not None: + so.max_value = mx + + add_in("Tree", 'NodeSocketObject') + add_in("Leaf Card", 'NodeSocketObject') + add_in("Density", 'NodeSocketFloat', LEAF_DEFAULTS["Density"], 0.0, 20000.0) + add_in("Max Radius", 'NodeSocketFloat', LEAF_DEFAULTS["Max Radius"], 0.001, 0.5) + add_in("Size", 'NodeSocketFloat', LEAF_DEFAULTS["Size"], 0.01, 5.0) + add_in("Size Var", 'NodeSocketFloat', LEAF_DEFAULTS["Size Var"], 0.0, 1.0) + add_in("Tilt", 'NodeSocketFloat', LEAF_DEFAULTS["Tilt"], 0.0, 3.14159) + add_in("Seed", 'NodeSocketInt', LEAF_DEFAULTS["Seed"], 0, 9999) + + gin = N("NodeGroupInput"); gin.location = (-900, 0) + gout = N("NodeGroupOutput"); gout.location = (900, 0) + V = {so.name: gin.outputs[so.name] for so in iface.items_tree + if getattr(so, "in_out", "") == 'INPUT'} + + tree = N("GeometryNodeObjectInfo"); tree.location = (-700, 200) + L(V["Tree"], _sock(tree, "Object")) + card = N("GeometryNodeObjectInfo"); card.location = (-700, -200) + L(V["Leaf Card"], _sock(card, "Object")) + + # Auswahl: nur duenne Aeste (uv_r < Max Radius) + na = N("GeometryNodeInputNamedAttribute"); na.location = (-700, 20) + na.data_type = 'FLOAT' + _sock(na, "Name").default_value = "uv_r" + cmp = N("FunctionNodeCompare"); cmp.location = (-520, 20) + cmp.data_type = 'FLOAT'; cmp.operation = 'LESS_THAN' + L(_out(na, "Attribute"), cmp.inputs[0]) + L(V["Max Radius"], cmp.inputs[1]) + + dist = N("GeometryNodeDistributePointsOnFaces"); dist.location = (-320, 200) + L(_out(tree, "Geometry"), _sock(dist, "Mesh")) + L(cmp.outputs[0], _sock(dist, "Selection")) + L(V["Density"], _sock(dist, "Density")) + L(V["Seed"], _sock(dist, "Seed")) + + # Zufaellige Drehung: voll um Z, leicht gekippt (Tilt) + rrot = N("FunctionNodeRandomValue"); rrot.location = (-320, -120) + rrot.data_type = 'FLOAT_VECTOR' + L(V["Seed"], _sock(rrot, "Seed")) + tneg = N("ShaderNodeMath"); tneg.location = (-520, -220); tneg.operation = 'MULTIPLY' + tneg.inputs[1].default_value = -1.0 + L(V["Tilt"], tneg.inputs[0]) + vmin = N("ShaderNodeCombineXYZ"); vmin.location = (-520, -320) + L(tneg.outputs[0], vmin.inputs["X"]); L(tneg.outputs[0], vmin.inputs["Y"]) + vmax = N("ShaderNodeCombineXYZ"); vmax.location = (-520, -420) + L(V["Tilt"], vmax.inputs["X"]); L(V["Tilt"], vmax.inputs["Y"]) + vmax.inputs["Z"].default_value = 6.283185 + L(vmin.outputs[0], rrot.inputs[0]) + L(vmax.outputs[0], rrot.inputs[1]) + + # Zufaellige Groesse + rsc = N("FunctionNodeRandomValue"); rsc.location = (-320, -340) + rsc.data_type = 'FLOAT' + L(V["Seed"], _sock(rsc, "Seed")) + lo = N("ShaderNodeMath"); lo.location = (-520, -520); lo.operation = 'SUBTRACT' + lo.inputs[0].default_value = 1.0 + L(V["Size Var"], lo.inputs[1]) + slo = N("ShaderNodeMath"); slo.location = (-370, -520); slo.operation = 'MULTIPLY' + L(V["Size"], slo.inputs[0]); L(lo.outputs[0], slo.inputs[1]) + L(slo.outputs[0], rsc.inputs[2]) + L(V["Size"], rsc.inputs[3]) + + inst = N("GeometryNodeInstanceOnPoints"); inst.location = (100, 200) + L(_out(dist, "Points"), _sock(inst, "Points")) + L(_out(card, "Geometry"), _sock(inst, "Instance")) + L(rrot.outputs["Value"], _sock(inst, "Rotation")) + L(rsc.outputs[1], _sock(inst, "Scale")) + + real = N("GeometryNodeRealizeInstances"); real.location = (400, 200) + L(_out(inst, "Instances", "Geometry"), _sock(real, "Geometry")) + L(_out(real, "Geometry"), gout.inputs[0]) + return ng + + +def leaf_socket_ids(ng): + return {so.name: so.identifier for so in ng.interface.items_tree + if getattr(so, "in_out", "") == 'INPUT'} + + +def make_leaves(context, tree_obj, card_obj, name=None, **overrides): + """Erzeugt/aktualisiert ein _Leaf-Objekt mit dem Blattwerk-Modifier.""" + name = name or (tree_obj.name + "_Leaf") + ob = bpy.data.objects.get(name) + if ob is None: + me = bpy.data.meshes.new(name) + ob = bpy.data.objects.new(name, me) + context.collection.objects.link(ob) + # Gleicher Ursprung wie der Stamm - in UE muessen die Ebenen deckungsgleich sein + ob.location = tree_obj.location.copy() + ob.rotation_euler = tree_obj.rotation_euler.copy() + ob.scale = tree_obj.scale.copy() + + ng = build_leaf_group() + md = ob.modifiers.get("GN_Leaves") or ob.modifiers.new("GN_Leaves", 'NODES') + md.node_group = ng + ids = leaf_socket_ids(ng) + md[ids["Tree"]] = tree_obj + md[ids["Leaf Card"]] = card_obj + vals = dict(LEAF_DEFAULTS); vals.update(overrides) + for k, v in vals.items(): + if k in ids: + md[ids[k]] = v + return ob + + # --------------------------------------------------------------------------- # Addon-UI @@ -1091,11 +1244,34 @@ class TreeGenSettings(PropertyGroup): "1 = volle Bandbreite. Der Preset-Charakter bleibt erhalten"), ) make_leaf: BoolProperty( - name="_Leaf-Ebene anlegen", default=False, + name="_Leaf-Ebene anlegen", default=True, description=("Legt pro Baum ein leeres Objekt _Leaf mit GLEICHEM Ursprung " "an. Dort modellierst du die Krone - in UE sitzen beide Ebenen " "dadurch deckungsgleich"), ) + leaf_card: PointerProperty( + name="Blatt-Card", type=bpy.types.Object, + description=("Objekt, das als Blatt/Billboard gestreut wird (z.B. dein " + "'Diamon Plane'). Leer = _Leaf bleibt leer zum Selbermodellieren"), + ) + leaf_density: FloatProperty( + name="Dichte", default=150.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: IntProperty( + name="Tri-Budget", default=2500, min=0, max=100000, + description="Bei Ueberschreitung warnt der Blattwerk-Button (0 = keine Pruefung)", + ) + 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, + 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) make_frucht: BoolProperty( name="_Frucht-Ebene anlegen", default=False, description="Zusaetzlich _Frucht (gleicher Ursprung) fuer die Frucht-Ebene", @@ -1185,6 +1361,58 @@ class TREEGEN_OT_randomize(Operator): return {'FINISHED'} +class TREEGEN_OT_leaves(Operator): + bl_idname = "object.treegen_leaves" + bl_label = "Blattwerk erzeugen" + bl_description = ("Streut die Blatt-Card auf die ZWEIGE der ausgewaehlten Baeume " + "und legt sie als _Leaf ab") + bl_options = {'REGISTER', 'UNDO'} + + def execute(self, context): + s = context.scene.tree_gen_settings + if s.leaf_card is None or s.leaf_card.type != 'MESH': + self.report({'ERROR'}, "Bitte zuerst eine Blatt-Card waehlen (Mesh-Objekt).") + return {'CANCELLED'} + trees = [o for o in context.selected_objects + if o.type == 'MESH' and not o.name.endswith(("_Leaf", "_Frucht"))] + if not trees: + self.report({'ERROR'}, "Keinen Baum ausgewaehlt.") + return {'CANCELLED'} + dg = context.evaluated_depsgraph_get() + total_tree = total_leaf = 0 + for t in trees: + try: + lf = make_leaves(context, t, 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}) + except Exception as exc: # noqa: BLE001 + self.report({'WARNING'}, "%s: %s" % (t.name, exc)) + continue + context.view_layer.update() + dg = context.evaluated_depsgraph_get() + for ob, acc in ((t, "tree"), (lf, "leaf")): + try: + n = sum(len(p.vertices) - 2 + for p in ob.evaluated_get(dg).data.polygons) + except Exception: + n = 0 + if acc == "tree": + total_tree += n + else: + total_leaf += n + 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) + if s.leaf_budget and total > limit: + self.report({'WARNING'}, msg + " - UEBER Budget (%d). Dichte senken." % limit) + else: + self.report({'INFO'}, msg) + return {'FINISHED'} + + class TREEGEN_OT_create(Operator): bl_idname = "object.treegen_create" bl_label = "Baum erzeugen" @@ -1262,7 +1490,17 @@ class TREEGEN_OT_create(Operator): extra = [] for ob in created: if s.make_leaf: - extra.append(_add_layer(context, ob, "_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})) + else: + # keine Card -> leeres _Leaf zum Selbermodellieren + extra.append(_add_layer(context, ob, "_Leaf")) if s.make_frucht: extra.append(_add_layer(context, ob, "_Frucht")) created.extend(extra) @@ -1314,6 +1552,21 @@ class VIEW3D_PT_tree_generator(Panel): box.prop(s, "random_amount") box.operator("object.treegen_randomize", icon='FILE_REFRESH') + box = layout.box() + box.label(text="Blattwerk", icon='OUTLINER_OB_POINTCLOUD') + box.prop(s, "leaf_card") + sub = box.column(align=True) + sub.enabled = s.leaf_card is not None + sub.prop(s, "leaf_density") + sub.prop(s, "leaf_size") + sub.prop(s, "leaf_size_var") + sub.prop(s, "leaf_max_radius") + sub.prop(s, "leaf_tilt") + sub.prop(s, "leaf_budget") + box.operator("object.treegen_leaves", icon='OUTLINER_OB_POINTCLOUD') + if s.leaf_card is None: + box.label(text="Ohne Card bleibt _Leaf leer", icon='INFO') + box = layout.box() box.label(text="Zusatz-Ebenen") box.prop(s, "make_leaf") @@ -1334,6 +1587,7 @@ class VIEW3D_PT_tree_generator(Panel): classes = ( TreeGenSettings, TREEGEN_OT_randomize, + TREEGEN_OT_leaves, TREEGEN_OT_create, VIEW3D_PT_tree_generator, ) diff --git a/tests/test_leaves.py b/tests/test_leaves.py new file mode 100644 index 0000000..1fff081 --- /dev/null +++ b/tests/test_leaves.py @@ -0,0 +1,61 @@ +import bpy, sys, zipfile, os, tempfile, importlib.util, math, bmesh +ZIP=r"A:\eco\stylized_rock_generator\dist\stylized_tree_generator-1.23.0.zip" +tmp=tempfile.mkdtemp() +with zipfile.ZipFile(ZIP) as z: z.extractall(tmp) +init=[os.path.join(r,"__init__.py") for r,_,f in os.walk(tmp) if "__init__.py" in f][0] +spec=importlib.util.spec_from_file_location("stg",init) +m=importlib.util.module_from_spec(spec); sys.modules["stg"]=m; spec.loader.exec_module(m) +try: m.unregister() +except Exception: pass +m.register() +fails=[] +for o in list(bpy.data.objects): bpy.data.objects.remove(o,do_unlink=True) + +# Card wie beim User +me=bpy.data.meshes.new("DiamondCard"); bm=bmesh.new() +for ang in (0.0, math.pi/3, 2*math.pi/3): + c,s_=math.cos(ang), math.sin(ang) + vs=[bm.verts.new(p) for p in [(-c,-s_,-1.41),(c,s_,-1.41),(c,s_,1.41),(-c,-s_,1.41)]] + bm.faces.new(vs) +bm.to_mesh(me); bm.free(); me.uv_layers.new(name="UVMap") +card=bpy.data.objects.new("Diamon Plane", me); bpy.context.collection.objects.link(card) + +s=bpy.context.scene.tree_gen_settings +s.preset='eiche'; s.art_name="Eiche"; s.variant=1 +s.leaf_card=card; s.leaf_size=0.22 # Dichte: Default verwenden +s.make_leaf=True; s.use_growth=False +r=bpy.ops.object.treegen_create() +names=sorted(o.name for o in bpy.context.scene.objects if o.type=='MESH') +print("OBJEKTE:", [n for n in names if n!="Diamon Plane"]) +if "Eiche_01" not in names or "Eiche_01_Leaf" not in names: fails.append("objekte") +bpy.context.view_layer.update() +dg=bpy.context.evaluated_depsgraph_get() +def tris(n): + ob=bpy.context.scene.objects[n] + return sum(len(p.vertices)-2 for p in ob.evaluated_get(dg).data.polygons) +tt,tl=tris("Eiche_01"), tris("Eiche_01_Leaf") +print("TRIS Stamm=%d Blatt=%d Summe=%d / 2500 %s (Dichte %.0f)" % (tt,tl,tt+tl,"OK" if tt+tl<=2500 else "UEBER", s.leaf_density)) +if tl==0: fails.append("blattwerk_leer") +if tt+tl>2500: fails.append("budget") +# Ursprung deckungsgleich +a=bpy.context.scene.objects["Eiche_01"]; b=bpy.context.scene.objects["Eiche_01_Leaf"] +d=(a.location-b.location).length +print("URSPRUNG-Abstand: %.6f" % d) +if d>1e-6: fails.append("ursprung") +# Operator auf Auswahl +for o in bpy.context.selected_objects: o.select_set(False) +a.select_set(True); bpy.context.view_layer.objects.active=a +r2=bpy.ops.object.treegen_leaves() +print("OPERATOR treegen_leaves:", list(r2)) +if 'FINISHED' not in r2: fails.append("operator") +# Ohne Card -> leeres _Leaf +for o in list(bpy.data.objects): + if o.name.startswith("Eiche"): bpy.data.objects.remove(o,do_unlink=True) +s.leaf_card=None; s.art_name="Birke"; s.preset='birke' +bpy.ops.object.treegen_create() +lf=bpy.context.scene.objects.get("Birke_01_Leaf") +print("ohne Card -> _Leaf vorhanden:", lf is not None, "| leer:", lf and len(lf.data.polygons)==0) +if lf is None: fails.append("leer_leaf") +m.unregister() +print("ERGEBNIS:", "ALLE CHECKS OK" if not fails else "FEHLER: "+", ".join(fails)) +sys.exit(1 if fails else 0)