Files
stylized-rock-generator/README.md
T
D4rkst3randClaude Opus 4.8 d02562fb53 Tree Gen 1.33.0: Hoehen-Filter - der Setzling bekommt einen freien Fuss
Befund aus der Turnhalle: die Setzlings-Blaetter verteilen sich ueber die ganze
Stammhoehe, es entsteht eine belaubte Saeule statt einer Krone. Nachgemessen:
44 % der Cards sassen unter 40 % der Baumhoehe, die tiefste bei 6 %.

Der Radius-Filter kann das nicht loesen - er trennt duenn von dick, aber ein
junger Stamm ist UEBERALL duenn. Also ein zweiter Filter ueber die Hoehe: Laub
erst ab dem Kronenansatz, als Anteil der Baumhoehe. Die Schwelle kommt aus der
Bounding Box des Baums, nicht als Meterwert - fest waere sie beim Setzling die
halbe Pflanze und beim Altbaum wirkungslos.

Kronenansatz je Stufe (LEAF_STUFEN): S1 40 %, S2 35 %, S3 30 %, S4 25 %.

               unter Ansatz          tiefste Card
        vorher      nachher      vorher    nachher
  S1      44 %          0 %        6 %       46 %
  S2      32 %          0 %       24 %       36 %
  S3       9 %          0 %       31 %       34 %
  S4       3 %          0 %       34 %       34 %   (unveraendert)

Beim Altbaum ist der Filter praktisch ein Sicherheitsnetz - dort sitzt ohnehin
nichts tief, genau wie gemeldet.

Dichte von S1/S2 nachgezogen: der Filter nimmt dem Setzling sonst die Haelfte
der Cards (9 -> 4), weil dessen Laub vorher ueber die ganze Stammhoehe verteilt
war. Jetzt 11 Cards bei S1, und 486 Tris gegen 800 Budget.

23 Tests bestehen (Suite zweimal durchlaufen).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-06 10:09:04 +02:00

45 KiB
Raw Blame History

Stylized Asset Generators (Blender Addons)

Dieses Repo liefert vier Extensions ueber eine URL:

Addon Panel Zweck
Stylized Rock Generator Rock Gen Batch-Rocks + Textur-Bake + FBX/LOD-Export
Stylized Tree Generator Tree Gen Baeume/Palmen/Bueschen/Kakteen (Geometry Nodes) mit Wachstums-Stufen
Stylized Grass Generator Grass Gen Gras/Blumen/Farne mit Wind-UV fuer SimpleGrassWind
Stylized Asset Utils Asset Utils Handgebaute Assets auf die Konvention bringen

Ein Blender-Extension-Repo kann mehrere Extensions ausliefern — server-generate listet jedes Zip in dist/. Ein „Check for Updates" aktualisiert also alle vier.


Panel-Aufbau (alle vier Addons)

Jedes Addon hat oben im N-Panel nur das, was man bei jedem Asset anfasst - Vorlage, Name, den grossen Erzeugen-Knopf, Zufall. Alles andere sitzt in nummerierten, eingeklappten Unter-Panels in der Reihenfolge des Arbeitsablaufs:

Addon Unter-Panels
Rock Gen 1 Benennung & Groesse · 2 Form · 3 Detail & Cleanup · 4 Shading · 5 Vertex-Farben · 6 UV/LOD/Material · 7 Batch & Transform · 8 FBX-Export · 9 Textur-Bake
Tree Gen 1 Wachstum · 2 Blattwerk · 3 Zusatz-Ebenen · 4 Export · 5 Faell-Teile
Grass Gen 1 Form · 2 Haltung · 3 Batch & Budget · 4 Kanal-Belegung
Asset Utils 1 Vorbereiten · 2 Vertex-Farben · 3 Benennung · 4 Pruefen · 5 Export

Schalter, die ein ganzes Unter-Panel an- und ausknipsen (Vertex-Farben backen, Wachstums-Stufen), sitzen als Haekchen im Panel-Kopf - so sieht man den Zustand, ohne aufzuklappen.

In den Generatoren sind alle Unter-Panels zugeklappt, weil es Einstellungen sind, die man selten aendert. In Asset Utils ist jedes Unter-Panel ein Arbeitsschritt; 1, 2 und 4 macht man bei jedem Asset und bleiben deshalb offen. tests/test_panels.py haelt diese Absicht fest, damit sie nicht versehentlich verrutscht.

Falle beim Bauen: Unter-Panels brauchen dieselbe bl_category wie das Eltern-Panel - sonst landen sie in einem anderen Tab und sind praktisch unsichtbar. Und das Eltern-Panel muss vor seinen Kindern registriert werden, sonst findet Blender die bl_parent_id nicht.


EcoGame-Konventionen (alle Addons)

Abgestimmt auf A:\eco\EcoGame (UE 5.8). Kurzfassung:

Benennung

  • Felsen: Rock_<Form>_<Klasse> — Form = Buchstabe (A, B, …), Klasse = S/M/L/XL. Das Praefix ist Rock (Standard) — die eingefrorene EcoGame-Konvention. Die Klasse setzt eine Basis-Groesse (S=0.45, M=1.0, L=1.9, XL=3.2) und ist damit ein bewusst anderes Asset — gemessen: S 0.85 m, M 1.9 m, L 3.5 m, XL 6.0 m. Davon strikt getrennt ist die Zufalls-Streuung (scale_min/max): die variiert nur die FORM und wird von „Gesamtgroesse konstant halten" auf konstantes Volumen normiert (1.11× statt 1.92×), damit sie sich nicht mit den ScaleMin/Max der Engine multipliziert.
  • Baeume: <Art>_<Variante>_S<Stufe>, z. B. Birke_01_S1Birke_01_S4. Zusatz-Ebenen haengen hinten an: _Leaf, _Frucht.
  • Texturen: <name>_albedo und <name>_normal — mehr nicht.

Textur-Bake

  • Nur Albedo + Normal. Roughness ist im Master-Material eine Zahl, keine Textur.

  • AO/Cavity wird ins Albedo multipliziert (Option „AO ins Albedo einrechnen", Staerke regelbar). Dunkle Ritzen und helle Platten stecken im Farbbild — das traegt den Stylized-Look. Separate Rough-/AO-Paesse sind Default aus.

  • Plattenstil (Genshin-Referenz): Die Textur besteht aus wenigen grossen, fast leeren Platten mit schmalen dunklen Fugen — das Detail steckt in der Fuge, nicht in der Flaeche. Regler: Plattengroesse (kleiner = groessere Platten), Fugenbreite (klein = harte Fuge, flache Platte), Platten flach (1 = Flaeche nahezu einfarbig). Ein dichtes Zellmuster ueber die ganze Oberflaeche erschlaegt die Form und passt NICHT zu diesem Look.

  • Vertex-Farb-Layout v2 (kanal-getrennt), Attribut Cavity:

    Kanal Inhalt
    R Cavity pur — 1 = offene Flaeche, 0 = tiefe Fuge (ohne Hoehen-Verlauf)
    G Moos-/Schmutzmaske — 1 = hier waechst was (Oberseite + Fugennaehe + Noise)
    B Hoehen-Verlauf — 0 = Unterseite, 1 = oben
    A Erz-Maske, invertiert: 1.0 = kein Erz, 0.0 = Erz. Ohne Erz-Bake bleibt der Kanal durchgehend auf 1.0

    Getrennt statt eingebacken: der erdige Boden-Saum am Stein-Fuss und die Moosfarbe werden pro Material-Instanz geregelt, nicht im Mesh festgelegt. Regler: Cavity-Staerke, Weichzeichnen, Moos-Menge, Moos-Aufbruch, Moos-Seed. G alternativ als Teil-Maske: Haken G = Teil-Maske (Shells) markiert getrennte Mesh-Teile statt Moos — der oberste/breiteste Teil bekommt 1, der Rest 0. Assets, die aus mehreren Objekten mit Strg+J verbunden wurden (Pilz = Hut + Stiel), lassen sich damit EXAKT trennen statt ueber Normalen geschaetzt. Ein Material toent dann beide Teile unterschiedlich — kein zweiter Material-Slot noetig. Einteilige Meshes bleiben unmarkiert (G=0). Der Button Vertex-Cavity auf Auswahl backt das Layout auf JEDES markierte Mesh — also auch auf handgebaute Assets, nicht nur auf generierte Steine. Verifiziert: R ohne Hoehenabhaengigkeit (unten 0.62 / oben 0.59), B steigt sauber (0.08 -> 0.92), G oben staerker (0.20 -> 0.57), A konstant 1, Seed aendert die Verteilung, und alle Kanaele ueberleben den FBX-Roundtrip.

  • Texturfreie Alternative — Vertex-Cavity: Statt zu baken laesst sich die Cavity in die Vertex-Farben schreiben (Attribut Cavity, ueberlebt den FBX-Roundtrip verlustfrei — gemessen 0.250..0.947 vor und nach dem Import). Das Material braucht dann keine Textur: Base Color = Farbton x VertexColor, dazu Kantenlicht (Fresnel) und Schattentint. Vorteil gegenueber Triplanar: die dunklen Fugen sitzen auf den ECHTEN Kanten des Meshes statt darueberzuschweben. Grenze: die Aufloesung haengt an der Vertex-Dichte — fuer flaechige Platten reicht sie, fuer feine Struktur nicht.

  • 1024 px reicht fuer stylized Formen.

  • Tileable (4D-Torus) passt zum triplanar mappenden Master-Material.

Material-Slots (Teil des Vertrags, keine Kosmetik)

  • Die UE-Skripte mappen ueber den Slot-Namen. Ohne Slot legt Unreal nur einen Default an und die Konvention greift nicht mehr — deshalb ist Material zuweisen Standard an.
  • Normale Steine rotieren durch M_Rock_Granite/Basalt/Sandstone/Mossy.
  • Das Erzfels-Set bekommt einen neutralen Slot M_Rock_Ore — ein Set fuer alle Erzsorten, welche Sorte es ist macht die Material-Instanz.

Mesh

  • Echte Meter, Ursprung unten-mittig, FBX pro Asset bzw. LOD-Gruppe.
  • UV1 Lightmap ist Default aus — EcoGame beleuchtet dynamisch.
  • Meshes brauchen keine eigenen Materialien; in UE bekommt alles die Material-Instanz vom Master. Ein Platzhalter im FBX ist okay.
  • Scale-Randomisierung nur fuer die Form (nicht-uniform) nutzen, nicht fuer die Gesamtgroesse — sonst multipliziert sie sich mit ScaleMin/Max der Engine.

Schnellstart-Sorten

Ganz oben im Panel: Sorte waehlen, Haken druecken, erzeugen. Deckt den Alltag ab — die neun Unter-Panels bleiben fuer den Feinschliff.

Sorte Form Dissolve gemessen
Findling rund, weiche Kanten 1725° ~1160 V / ~2320 Tris
Platte flach, liegt satt auf 1725° ~670 V / ~1340 Tris
Kantig Bevel + Weighted Normal, Flat 814° ~350 V / ~690 Tris
Erzbrocken dicht genug fuer die Nugget-Maske, Slot M_Rock_Ore 1725° ~1280 V / ~2560 Tris

Der Dissolve-Winkel ist ein Bereich, kein Wert. Ein fester Winkel passt nie fuer alle Steine einer Sorte — man stellt ihn sonst bei jedem Stein nach. Je Stein wird einer aus dem Bereich gezogen; gemessen an sechs Findlingen bei 1725°: 19362928 Tris. Beide Regler gleich setzen = fester Winkel wie frueher.

Warum die Sorten unterschiedlich dicht sind. Die Erz-Maske haengt an der Vertex-Dichte, ein normaler Stein braucht das nicht. Deshalb hat der Erzbrocken ein hoeheres Decimate als der Findling — sonst waere entweder das Erz verschmiert oder der normale Stein ueber dem Tri-Fenster. Der Satz hinter dem S+M-Set ist noch etwas dichter (0.70/0.92), weil dort auch kleine S-Brocken vorkommen: mit dem Findling-Satz fielen die im schlechtesten Fall auf 708 Verts und damit unter die Schwelle fuer eine saubere Maske.

Erzfels-Set (A-Kanal)

EIN neutrales Set fuer ALLE Erzsorten. Die Maske im A-Kanal sagt nur, wo Erz sitzt. Welches Erz es ist (Kupfer/Eisen/Gold = Tint + Gluehen), entscheidet die Material-Instanz in UE. Knopf: Erzfels-Set erzeugen (S + M) erzeugt Rock_Ore_S_NN und Rock_Ore_M_NN und stellt danach alle Einstellungen zurueck.

Regler: Erz-Anteil, Nugget-Groesse (Radius in m), Erz-Seed.

Richtung des A-Kanals — wichtig. Die Nuggets laufen auf A = 0 zu, nicht auf 1. Der Master rechnet:

Erz = (1 - A) * ErzMenge

ErzMenge ist ein MI-Regler mit Standard 0. Damit ignorieren normale Stein-MIs den A-Kanal vollstaendig, und nur die Erz-MIs drehen ihn auf. Ein Stein ohne Erz-Bake behaelt seine durchgehende 1.0 und liefert (1-A) = 0 — deshalb musste das bestehende 25er-Set nicht neu exportiert werden.

Andersherum gebacken waere das Erz unsichtbar und der ganze restliche Stein wuerde als Erz gelesen. tests/test_erz.py prueft deshalb direkt gegen diese Formel und nicht gegen den Rohwert.

Die Nugget-Groesse bleibt absolut (Radius in Metern): ein kleiner Brocken bekommt weniger, dafuer proportional groessere Einschluesse. Das ist physikalisch richtig, und das Spiel skaliert die Meshes per ScaleMin/Max ohnehin zufaellig - eine groessenrelative Optik waere dadurch sowieso wieder zerlegt.

Nuggets statt Rauschen: gestreute Zentren mit weichem Abfall. Ein Noise-Feld gaebe wolkige Schlieren ueber den ganzen Stein, Erz sitzt aber in abgegrenzten Einschluessen. Die Zentren bevorzugen Fugen — Adern folgen Rissen, und das Gluehen liest sich in einer Vertiefung besser als auf einer flachen Platte.

Die Maskenaufloesung haengt an der Vertex-Dichte. Bei den bisherigen ~150-Vert-Steinen war jedes dritte Vertex ein Nugget-Zentrum: die Nuggets liefen zu grossen Schlieren zusammen. Deshalb erzeugt das Erz-Set dicht (siehe unten) — dort liest es sich als einzelne Einschluesse. Die Nuggetzahl ist zusaetzlich auf ein Zentrum je ~10 Verts gedeckelt.

Gemessen (dicht, ~2700 Tris): Erz-Anteil 0.08 → 4 %, 0.18 → 9 %, 0.35 → 15 % der Oberflaeche. A faellt auf 0.00 und erreicht 1.00, und die Maske ueberlebt den FBX-Roundtrip.

Bruchstuecke (Steine zerschlagen)

Kein Laufzeit-Fracturing im Spiel: die Teile werden beim Anschlagen als Physik-Actors gespawnt. Sie entstehen deshalb hier als vollwertige Assets. Knopf In Bruchstuecke zerlegen arbeitet auf der Auswahl und liefert <Name>_Piece_01..NN. Regler: Bruchstuecke (2-4), Bruch-Seed, Ein Stueck mit Erz, Mutterstein ausblenden.

Jedes Teil bekommt: Vertex-Layout v2, Ursprung unten-mittig, die gleichen Material-Slots wie der Mutterstein (die UE-Skripte mappen darueber) und beim Export eine eigene UCX_-Huelle.

Punkt 4a: mit Ein Stueck mit Erz heisst ein zufaelliges Teil <Name>_Piece_Erz und traegt Erz-Adern im A-Kanal (Richtung 0). Alle anderen Teile behalten durchgehend A = 1.0 und sind damit fuer den Master erzfrei. Ob das Erz-Stueck gespawnt wird, entscheidet die Loot-Logik im Spiel.

Wie die Bruchkanten zusammenpassen: Voronoi ueber Halbraeume. Fuer jedes andere Zentrum wird an der Mittelsenkrechten geschnitten und die abgewandte Seite verworfen. Benachbarte Zellen schneiden dadurch an genau derselben Ebene - die Bruchflaechen passen zusammen, ohne dass man sie abstimmen muesste. Bewusst ohne das Cell-Fracture-Addon: das muesste beim User aktiviert sein und haengt an Operator-Kontext, bmesh laeuft immer.

Die entscheidende Falle. Jeder Schnitt muss sofort geschlossen werden, und zwar ueber genau die Kanten, die bisect_plane als geom_cut zurueckgibt. Der naheliegende Weg - am Ende alle Randkanten suchen und fuellen - erzeugte Kanten mit vier Flaechen; raeumte man die ab, blieben Loecher. Aufgefallen ist das am Volumen: die Teile kamen zusammen auf 72.7 % des Muttersteins, weil calc_volume bei offenen Meshes Unsinn liefert. Jetzt sind es 100.2 % bei 2, 3 und 4 Teilen, mit 0 offenen und 0 non-manifold Kanten.

tests/test_bruchstuecke.py prueft genau diesen Volumen-Vergleich - er ist zugleich das Warnsignal fuer kaputte Topologie.

Smooth & dicht

Knopf im Form-Panel: setzt Unterteilung 5, Decimate 0.40/0.80 und Auto-Smooth. Gemessen ueber drei Seeds: 2708/2658/2716 Tris (Schnitt 2694) — im Zielfenster 15003000. Kein Low-Poly-Look, und alle Vertex-Masken (Cavity, Moos, Erz) werden feiner.


Export-Haertung (Rock Gen)

Drei Dinge passieren beim FBX-Export jetzt automatisch. Alles laeuft auf temporaeren Kopien - die Szene bleibt unveraendert.

1. Ursprung wird erzwungen (keine Option). Jedes Objekt wandert an den Welt-Ursprung, der Ursprung sitzt unten-mittig der Bounding-Box. Anlass: das 25er-Set trug seine Szenen-Rasterpositionen (bis 20 m) im Mesh, die Steine schwebten im Spiel neben ihrem Platzierungspunkt. Nachgemessen am reimportierten FBX: Bbox-Mitte 0.000/0.000, min.Z 0.000.

2. Flache Steine werden hingelegt (Flache Steine hinlegen, Default an). Die kuerzeste Bbox-Achse wird nach oben gedreht. Gedreht wird nur, wenn es etwas bringt - bei einem 2.53/1.40/1.54-Stein wuerde Kippen die Form nur willkuerlich drehen. Im Spiel gibt es nur zufaelligen Yaw, die gebackene Ausrichtung ist also endgueltig.

3. UCX-Kollision (UCX-Kollision mitexportieren, Default an). Konvexe Huelle als UCX_<Name> im selben FBX, Unreal nimmt sie als Simple Collision. Budget ueber UCX max. Verts (Default 32).

Drei Fallen, die dabei aufgefallen sind:

  • Blender haengt an Kopien .001 an. Unreal ordnet die Kollision aber ueber den exakten Mesh-Namen zu - das Original bekommt fuer die Dauer des Exports einen Hilfsnamen, die Kopie den echten.
  • bmesh.from_mesh bringt die Originalflaechen mit, convex_hull legt seine Huelle dann obendrauf: gemessen 16-21 offene Kanten. Es duerfen nur die Punkte eingelesen werden.
  • Jede Vereinfachung der Huelle schneidet Ecken ab, sie wird dadurch kleiner als der Stein - eine Kollision kleiner als das Mesh laesst den Spieler einsinken. Deshalb wird sie danach exakt so weit aufgeblasen, dass wieder alles hineinpasst.

LOD-Gruppen + Vertex-Farben (war offen, jetzt geklaert)

Ging nicht: der LOD-Zweig sprang komplett am Cavity-Bake vorbei, LOD-Gruppen kamen ohne Farbattribut aus dem Export - auf keiner der vier Stufen. Jetzt wird auf LOD0 gebacken, bevor dupliziert wird; das Decimate interpoliert die Farben mit. Wuerde je Stufe neu gebacken, haette jede Stufe ihre eigene Cavity und der LOD-Wechsel wuerde poppen. Nach dem FBX-Roundtrip gemessen:

Stufe Verts R-Bereich
LOD0 40 0.30..1.00
LOD1 21 0.41..0.92
LOD2 11 0.45..0.92
LOD3 6 0.49..0.88

Dazu die zweite Falle: ob.matrix_world = Identity loescht die Transformation nicht, wenn das Objekt ein Eltern-Objekt hat - Blender rechnet eine lokale Gegen-Matrix aus. Bei einer LOD-Gruppe auf (13/-7.5/2.2) landete das Mesh dadurch bei genau MINUS dieser Position. matrix_basis und matrix_parent_inverse muessen direkt genullt werden.

Die UCX-Huelle haengt bewusst neben dem LodGroup-Empty, nicht darunter - Unreal wuerde jedes Kind der Gruppe als weitere LOD-Stufe lesen.


Stylized Asset Utils

Fuer alles, was von Hand gebaut wird (Pilze, Baumstumpf, Erzfels, Props) und deshalb nicht durch einen Generator laeuft. Panel in fuenf einklappbaren Schritten:

  1. Vorbereiten — Rotation/Scale anwenden, Ursprung unten-mittig, optional Normalen neu berechnen. Damit steht das Asset in UE bei Scale 1 auf dem Boden.
  2. Vertex-Farben — dasselbe Layout wie beim Rock Generator (R = Cavity, G = Maske, B = Hoehe, A = 1). G wahlweise als Teil-Maske: bei Assets, die mit Strg+J aus mehreren Objekten verbunden wurden (Pilz = Hut + Stiel), bekommt der oberste Teil 1 und der Rest 0 — exakt statt geschaetzt. Ein Material toent damit beide Teile, ein zweiter Material-Slot entfaellt. Einteilige Meshes bleiben unmarkiert.
  3. Benennung — Auswahl nach <Basis>_<NN> umbenennen, Mesh-Daten ziehen mit.
  4. Pruefen — Asset-Check: nicht angewendete Transforms, Ursprung nicht unten, Tri-Budget, fehlende UV/Vertex-Farben, lose Vertices, Custom Split Normals. Details in der System-Konsole.
  5. Export — FBX je Mesh mit colors_type='SRGB', Triangulate und waehlbarem Smoothing.

Falle, die dabei auffiel: bpy.ops.paint.vertex_color_set braucht den Vertex-Paint-Modus und scheitert im Object-Mode mit „poll() failed". Steht es mit vertex_color_dirt im selben try, wird der ganze Bake stillschweigend uebersprungen. Die Initialisierung laeuft deshalb direkt ueber Python.


Stylized Grass Generator

Gras-, Blumen- und Farn-Bueschel. Der Kern ist die Wind-UV:

  • UV0.V = 0 an der Wurzel, 1 an der Spitze — genau die Maske, die UEs SimpleGrassWind als WindWeight braucht. Ohne sie rutscht der Halm beim Wehen vom Boden weg oder bewegt sich gar nicht. Am Zielmaterial M_Grass geprueft: es liest TextureCoordinate, aber keine VertexColor — deshalb liefert der Generator die Maske PRIMAER ueber die UV. Die Assets funktionieren damit ohne Materialaenderung.
  • Vertex-Farben zusaetzlich (nicht als einzige Quelle): R = Kopie der Windmaske · G = Variation je HALM · B = AO (unten dunkel). G ist die Ebene unter dem Biom-Tint, den die Engine ueber PerInstanceCustomData 0/1 pro Instanz setzt — im Material multiplizieren, nicht ersetzen.
  • Presets: gras, gras_hoch, steppengras, farn, blume.
  • Tri-Budget-Warnung (Default 400 laut ASSETS.md fuer Gras/Blume). Gemessen: ein Standard-Bueschel mit 14 Halmen = 112 Tris.
  • Ursprung unten-mittig (min_z = 0.0000), UV und Vertex-Farben ueberleben den FBX-Roundtrip.

Stylized Tree Generator

Spielfertiger Stand-Baum (Faellsystem)

Knopf Spielfertig exportieren im Export-Panel: Stamm und _Leaf-Ebene werden zu einem Mesh mit zwei Material-Slots verschmolzen (Slot 0 = M_Bark, Slot 1 = M_Foliage). Die getrennten Objekte im Blend bleiben die Arbeitsebene — verschmolzen wird nur die Export-Kopie. Ursprung Bbox-Unterseite Mitte, FBX-Einstellungen wie in der Fels-Pipeline.

Kugel-Normalen (Haken Kugel-Normalen auf Blaetter, Standard an): alle Blatt-Normalen zeigen radial vom Kronen-Schwerpunkt nach aussen. Ohne das wirkt jede Card wie ein eigenes flaches Blech und die Krone flimmert im Licht. Der Schwerpunkt kommt nur aus den Blatt-Faces — der Stamm wuerde die Mitte nach unten ziehen und die Kugel verkippen.

Bewusst direkt ueber normals_split_custom_set statt ueber den Normal-Edit-Modifier: der muesste vor dem Export angewendet werden, und angewendete Modifier plus Custom Normals ist genau die Kombination, bei der der FBX-Export gerne flachbuegelt. Am reimportierten FBX gemessen: mittlere Radialitaet 1.000, 100 % der 510 Blatt-Loops ueber 0.8. Gegenprobe Rinde 0.043.

Card-Varianten: Card-Sammlung nimmt eine Collection mit 46 Diamond-Cards unterschiedlicher Atlas-UVs; je Blattpunkt wird eine zufaellig gezogen (seed-stabil). Eine einzelne Card geht weiter — die Sammlung hat Vorrang.

Zwei Fallen beim Bauen:

  • Ueber bmesh.from_mesh zusammenzufuegen haengt die Geometrie zwar an, wirft aber die Material-Indizes weg: danach lagen alle 748 Faces auf Index 0. Blenders join fuehrt die Slot-Listen zusammen und rechnet die Indizes um.
  • join loescht das zweite Objekt; jede gehaltene Referenz darauf ist danach tot. Die Aufraeumliste fuehrt deshalb Namen statt Objekte.

Blatt-Defaults: Bueschel, nicht Blaettchen

Eine Card ist ein ganzer Zweigbueschel, kein einzelnes Blatt. Die alten Werte stammten aus der Blaettchen-Aera und streuten hunderte winziger Cards, wo ein paar Dutzend grosse hingehoeren. Die neuen Werte sind an einer S4-Eiche in UE gemessen und abgenommen:

vorher jetzt Wirkung
Groesse 0.22 0.85 Bueschel statt Bonsai
Dichte 500 170 wenige grosse statt viele Fussel
Max Radius 0.02 0.04 der Volumen-Hebel
Kippung 0.5 0.9 Rotations-Chaos schliesst Sichtluecken

Max Radius ist der unscheinbare Held: 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.

Deckung pro Stufe, nicht per Formel. Card-Anteil und Groesse stehen als Tabelle (LEAF_STUFEN). Der Dichte-Anteil bei S1/S2 sieht hoch aus — das taeuscht: „Dichte" ist Punkte pro m² Zweigflaeche, und die waechst ueber die Stufen dramatisch. Gemessen hat der S1-Setzling 0.013 m² zulaessige Flaeche; ohne Anhebung bekaeme er null Cards. Was zaehlt, ist die Anzahl:

Stufe Cards Card-Kante Tris Budget
S1 17 0.19 498 800
S2 43 0.29 754 1200
S3 67 0.37 946 4300
S4 130 0.45 1396 4300

Der Hoehen-Filter haelt den Fuss 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 Baumhoehe, die tiefste bei 6 % — eine belaubte Saeule statt einer Krone. Der Kronenansatz steht deshalb je Stufe in LEAF_STUFEN (Anteil der Baumhoehe, aus der Bounding Box abgeleitet — ein fester Meterwert waere beim Setzling die halbe Pflanze):

Stufe Ansatz unter Ansatz vorher nachher tiefste Card
S1 40 % 44 % 0 % 6 % → 46 %
S2 35 % 32 % 0 % 24 % → 36 %
S3 30 % 9 % 0 % 31 % → 34 %
S4 25 % 3 % 0 % 34 % (unveraendert)

Beim Altbaum ist der Filter praktisch ein Sicherheitsnetz — dort sitzt ohnehin nichts tief. Die Dichte von S1/S2 wurde nachgezogen, weil der Filter dem Setzling sonst die Haelfte der Cards nimmt.

Der Radius-Filter skaliert mit. Er trennt duenne Zweige (Laub) von dickem Geaest — aber ein Setzling ist komplett duenner als eine feste Schwelle. Mit starren 0.04 m sassen bei S2 81 % der Cards am Stamm, bei S3 58 %; jetzt 48 bzw. 52 %.

Bei S1 bleiben rund 88 % am Stamm, und das ist kein Defekt: gemessen ist die zulaessige Flaeche dort fuer jeden Schwellwert identisch — der Saemling hat schlicht kein dickes Geaest, an dem sich etwas trennen liesse. Blaetter am Stiel sind bei einem Saemling der Normalfall.

Bueschelgroesse haengt am Wachstum. Der Generator merkt sich growth_t am Objekt; der Blattwerk-Operator skaliert die Cards damit (Setzlings-Anteil, Standard 45 %). Gemessen ueber vier Stufen: Card-Kante 0.19 → 0.28 → 0.37 → 0.45.

Falle dabei: „Dichte" ist Punkte pro m² 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² der Flaeche; die Dichte entsprechend gegenlaeufig anzuheben haelt die Blattdeckung ueber alle Stufen gleich.

Tri-Budget pro Stufe (ASSETS.md Teil 6) statt global: 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). Die 2500 von frueher waren eine konservative Setzung — bei ISM-Instanzen mit LOD-Kette sind Roh-Dreiecke nicht der Engpass, sondern 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 wichtigste Code-Falle in diesem Addon. md[socket] = wert loest keine Neuauswertung des Geometry-Nodes-Modifiers aus. Nachgestellt: 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 brachte das Ergebnis. 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. _modifier_anstossen() macht das jetzt explizit.

LOD-Kette (Stand-Baum)

Haken LOD-Kette erzeugen: exportiert <Name>_LODGroup.fbx mit _LOD0..3 unter einem fbx_type=LodGroup-Empty — dasselbe Schema wie die Fels-Pipeline. Nur der Stand-Baum; die Faell-Teile leben Sekunden als Physik-Actor, dort reicht LOD0.

Die Blatt-Reduktion ist ein Teilset der LOD0-Cards, keine Neustreuung. Das ist der Unterschied zwischen „die Krone wird duenner" und „die Krone springt um": beim Neustreuen laegen die Cards an anderen Stellen und die Silhouette wechselt beim LOD-Uebergang sichtbar. Als Teilset bleibt jede verbleibende Card genau dort, wo sie in LOD0 schon war; damit die Krone nicht loechrig wird, werden die Ueberlebenden groesser. Gemessen: 100 % der Cards auf LOD13 sitzen auf einer LOD0-Position. LOD3 ist der Impostor-Ersatz.

Gearbeitet wird auf zusammenhaengenden Inseln, nicht auf einzelnen Faces — eine Card besteht aus mehreren Dreiecken und muss ganz bleiben oder ganz verschwinden.

Stufe Dichte Card-Groesse Rinde gemessen (Rinde/Blatt/Tris)
LOD0 100 % 100 % 100 % 578 / 170 / 1306
LOD1 60 % 115 % 70 % 477 / 118 / 912
LOD2 30 % 145 % 45 % 364 / 66 / 575
LOD3 12 % 200 % 25 % 221 / 28 / 302

Kugel-Normalen auf jeder Stufe — LOD13 haben eine eigene Card-Verteilung, die Normalen muessen dazu passen. Am reimportierten FBX gemessen: 100 % radial auf allen vier Stufen. LOD3 traegt 16 % der Cards von LOD0.

Der Ursprung kommt aus LOD0 (Unreal platziert danach); alle Stufen werden um denselben Betrag verschoben, damit der Baum beim LOD-Wechsel nicht springt.

Falle, aus UE gemeldet: _origin_bottom_center backt zuerst die Weltmatrix ins Mesh und wendet dann die Zentrierung an — zurueck gibt es aber nur die Zentrierung. Wer den Stufen 13 nur diese aufdrueckt, laesst ihre Weltmatrix ungebacken: sie landen bei minus der Standposition. Gemessen an einem Baum bei x = 18 m: LOD13 bei x = 17.87. Bei einem Baum am Ursprung faellt das nicht auf, weil minus null eben null ist — genau deshalb steht der Baum im Test jetzt bewusst nicht am Ursprung, und die X/Y-Mitte wird je Stufe im reimportierten FBX gemessen. Das Decimate der Rinde laeuft vor dem Verschmelzen — danach wuerde es die Blatt-Cards mit einschmelzen, und eine halbe Card ist keine Card.

Blatt-Streuung: nachgemessen (Punkt 6)

Max Radius filtert die dicken Aeste heraus, damit keine Blaetter am Stamm kleben. Ueber alle Presets gemessen, wie viele Cards tatsaechlich am Stamm landen (moeglich, weil ast_id mitlaeuft):

Preset Cards am Stamm Tris gesamt
baum 85 19 % 1306
palme 246 0 % 1710
busch 118 2 % 876
tanne 176 6 % 2244
eiche 172 2 % 1772
birke 133 9 % 1918
weide 297 1 % 2246
toter_baum 49 22 % 838
kaktus 0 740

Der Filter arbeitet also richtig, und alle Presets bleiben unter dem 2500-Tri-Budget (Maximum weide 2246).

Messfalle dabei: Zuerst hatte ich den Anteil der Vertices unter der Radius-Schwelle gemessen — der lag beim Busch bei 95 % und sah nach einem Problem aus. Das misst aber die Vertex-Verteilung, nicht wo Blaetter landen: ein Busch besteht nun mal fast nur aus duennen Zweigen. Die richtige Frage beantwortet erst ast_id.

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.

Faell-Teile (Punkt 4)

Kein Laufzeit-Zerlegen: beim Faellen tauscht das Spiel die ISM-Instanz gegen einen Actor aus vordefinierten Teilen. Knopf Faell-Teile exportieren:

Teil Inhalt
<Name>_Piece_Stamm01..03 Stammsegmente, Schnittflaechen mit Deckel
<Name>_Piece_Ast01..N je ein Ast samt seinen Sub-Aesten und Blatt-Cards
<Name>_Pieces_All.fbx alle Teile an ihrer Originalposition, gemeinsamer Ursprung

Je Teil: zwei Material-Slots, Pivot = Massenmittelpunkt, eigene UCX_-Huelle.

Die Ast-Zugehoerigkeit ist ein Attribut, keine Schaetzung. Der Generator legt ast_id an den Astpunkten ab (0 = Stamm, 1..N = Aeste); Sub-Aeste erben die ID ihres Astes automatisch, weil sie aus Punkten auf dessen Kurve entstehen — gemessen: bei Sub Count 0 hat jeder Ast 28 Verts, bei 3 dann 64, der Stamm bleibt bei 156. Beim Blatt-Streuen wird ast_id am Punkt abgegriffen und auf die Card gespeichert. „Welche Card gehoert zu welchem Ast" ist damit eine Nachschlage-Frage statt einer Nearest-Neighbor-Heuristik — und exakt nachzaehlbar:

tests/test_faell_teile.py prueft die Blatt-Bilanz: Summe der Cards ueber alle Teile == Cards des Stand-Baums. Gemessen 170 = 170.

Falle dabei: Die Z-Schnitte am Stamm zerschnitten anfangs auch Blatt-Cards — aus einer wurden zwei, die Bilanz stand auf 178 gegen 170, und die Atlas-UVs der Haelften waeren kaputt gewesen. Eine Card ist ein unteilbares Billboard: sie wandert jetzt als Ganzes in das Segment, in dem ihre Inselmitte liegt. Nur die Rinde wird geschnitten.

Alles am reimportierten FBX gemessen: 12 Teile, UCX 1832 Verts im Budget, Pivot maximal 16 mm vom Schwerpunkt, 0 non-manifold Kanten, Gesamtdatei 5.41 m hoch (= volle Baumhoehe, die Teile sind also deckungsgleich rekonstruierbar).

Proportionen: gemessen, nicht geschaetzt

Drei Presets sahen falsch aus. Die Ursachen wurden gemessen bzw. belegt, nicht geraten - tests/test_proportionen.py haelt die Werte fest:

Was vorher (gemessen) jetzt Quelle fuer den Zielwert
Palmenkrone Radius/Hoehe 0.11 - ein Mop-Kopf am Stamm 0.30 tree-gen palm.py (Weber&Penn): Wedellaenge 0.3 x Stamm; TreeIt-Referenzpalme 3.3 m bei 4.33 m = 0.38
Busch 1.81 m hoch, 0.84 m breit (B/H 0.46) - ein Baeumchen 1.20 m hoch, 2.0 m breit (B/H 1.6) Strauch = mehrere Stiele ab Boden, kein Leittrieb; gemessen nach Hoehe UND Breite
Setzling relativ 2.4x dicker als der Altbaum (r/H 0.025 gegen 0.0104) proportional gleich schlank McMahon, elastische Aehnlichkeit: Hoehe ~ Durchmesser^(2/3), also Faktor 0.16^1.5

Der Wedel-Winkel kam dazu: mit Branch Up 0 standen die Wedel als gerade Antennenspeichen ab. tree-gen gibt down_angle 60 + curve 40 an - also schraeg raus und dann ueberbiegen. Gemessen steigen die Wedel jetzt 1.1 m ueber den Ansatz und kippen dann ab.

Nicht jede Pflanze waechst wie ein Laubbaum (GROWTH_OVERRIDES):

  • Palme: Einkeimblaettrige haben kein Dickenwachstum - erst der Durchmesser, dann die Hoehe. „Once a palm stem emerges from the apical meristem, its diameter is fixed for life" (UF/IFAS ENH1212). Der Setzling ist ein kurzer Stumpf in fast voller Dicke, kein Schoessling.
  • Kaktus: Sukkulent, gleiche Logik. Ausserdem ohne Arme - Saguaros bilden die erst mit 50-70 Jahren, bei rund 2 m Hoehe (NPS).
  • Weide: haengt erst, wenn die Zweige lang genug sind. Junge Weiden wachsen aufrecht; mit dem allgemeinen Droop reichten ihre Zweige 0.27 m unter den Stammfuss und das Asset waere in UE geschwebt.

Dabei gefunden: Branch Count = 0 schaltete die Aeste nicht ab - dieselbe Falle wie seinerzeit bei Sub Count (Curve to Points klemmt Count auf mindestens 1). Fuer die Sub-Ebene gab es laengst einen Geometry-Switch, fuer die Hauptaeste nicht. Jetzt liegen beide Ebenen hinter demselben Schalter. Ebenfalls gefunden: im Busch-Preset stand Branch Count zweimal im selben Dict - der zweite Eintrag hat den ersten still ueberschrieben.

Parametrischer Geaest-Generator (Stamm + Aeste + Sub-Aeste) als Geometry-Nodes- Gruppe. Blaetter/Krone macht man selbst — das Addon liefert die Struktur.

  • Presets: baum, palme, busch, tanne, eiche, birke, weide, toter_baum, kaktus — die Silhouette steuert die Kronenform (Crown Taper / Crown Bulge, Idee aus modular_tree / Weber-Penn).
  • Tri-Budget: alle Presets liegen unter dem Projektlimit (Baum ≤2500, Busch ≤800). Stellschrauben: Detail (Segmente) und Sides / Branch Sides / Sub Sides (Profilaufloesung je Ebene — Stamm rund, Zweige nur Dreiecke).
  • Wachstums-Stufen: Haken setzen + Anzahl waehlen → Setzling bis ausgewachsen, 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 <Name>_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 Points, Goldener Winkel = Phyllotaxis) statt einer aus Weltkoordinaten gerechneten Richtung — Sub-Aeste standen vorher wie Nadeln quer zum Ast. Neu: Tip Blunt (Baum laeuft spitz aus, Kaktus endet stumpf).

Der kaktus nutzt Droop Curve fuer den Saguaro-Ellbogen (Arm laeuft flach raus und knickt dann steil hoch). Offen ist dort nur, dass die Rippen (Ribs) im Shading kaum sichtbar sind.


Stylized Rock Generator (Blender Addon)

Batch-Generator fuer stylized Rocks in Blender, ausgelegt auf den Export nach Unreal Engine 5 ueber FBX.

Pipeline pro Rock: Basis-Mesh (Cube / Ico-Sphere) → Subdivision Surface → Displace mit prozeduraler Noise-Textur (Voronoi / Clouds / Distorted Noise) → zweistufiges Decimate → Limited Dissolve → Normalen-Reparatur → UV0 (Smart Project) + UV1 (Lightmap) → optional Bevel/Weighted Normal, Material, LOD-Set, randomisierte Skalierung/Rotation.

Getestet mit Blender 5.1.2 (Minimum laut bl_info: 4.2).


Installation

  1. stylized_rock_generator.py herunterladen.
  2. Blender → Edit → Preferences → Add-ons → Install… → die .py waehlen.
  3. Haken bei Stylized Rock Generator setzen.
  4. Panel: View3D → Sidebar (N) → Reiter "Rock Gen".

Update: einfach die neue .py erneut installieren (gleicher Modulname) oder das Repo lokal ziehen und den Addon-Pfad in den Preferences auf den Repo-Ordner zeigen lassen.

Installation als Extension (via URL, mit Auto-Update)

Ein Repo, vier Extensions. blender --command extension server-generate scannt dist/ und listet jedes gefundene Zip - eine URL liefert in Blender alle vier Addons. build.ps1 ruft das mit --html auf, dann entsteht zusaetzlich eine index.html zum Durchblaettern (auf einem statischen Webserver; Gitea liefert rohes HTML als Text aus).

Kein Versions-Archiv: zwei Zips derselben Extension-ID nebeneinander sind nicht der Weg, eine alte Version zum Zurueckrollen bereitzuhalten - server-generate warnt dann (archive found with duplicates ... conflicting blender versions). Mehrere Versionen einer ID sind nur erlaubt, wenn sie verschiedene Blender-Bereiche abdecken. Alte Version wieder her: per git checkout des Zips.

Ab Blender 4.2 / 5.x als Remote-Extension-Repository einmal die URL eintragen, danach gehen Updates per Klick.

  1. Das Gitea-Repo muss oeffentlich sein (sonst kann Blender die Raw-URL nicht ohne Login laden).

  2. Blender → Edit → Preferences → Get Extensions → oben rechts das Dropdown → Repositories → „Add Remote Repository".

  3. Als URL eintragen:

    https://git.d4rkst3r.de/D4rkst3r/stylized-rock-generator/raw/branch/main/dist/index.json
    
  4. „Check for Updates" aktivieren, bestaetigen. Die Extension erscheint in der Liste → Install.

Updaten (Maintainer-Seite):

  1. version in blender_manifest.toml (und bl_info["version"] in der .py) erhoehen.
  2. ./build.ps1 (oder blender --command extension build … + … server-generate …).
  3. git add -A && git commit -m "vX.Y.Z" && git push.
  4. Nutzer: in Blender Check for UpdatesUpdate.

Das gebaute Zip + index.json liegen in dist/ und werden bewusst mitversioniert, damit die Raw-URL sie ausliefert.


Features

  • Basis-Formen: Wuerfel, Quader oder Ico-Sphere, einstellbare Subsurf-Stufen. Der Quader hat frei einstellbare Quader-Proportionen (X/Y/Z) plus Proportions-Streuung — damit entstehen flache Platten und langgestreckte Riegel statt nur runder Brocken. Gemessen bei Proportion (2.0, 1.5, 0.35): 3.43 / 2.56 / 0.79 m — Z also 23 % von X. Die Proportionen werden auf gleiches Volumen normiert — „M" bedeutet damit bei jeder Form dasselbe. Eine Platte ist dann flacher UND breiter als ein Wuerfel derselben Klasse, aber nicht schwerer; ihre laengste Kante ist entsprechend groesser (das ist Absicht, kein Fehler). Die Proportionen werden auf die MESH-DATEN angewendet, nicht auf das Objekt: der Displace nutzt globale Texturkoordinaten, eine Objekt-Skalierung wuerde das Rauschen mitverzerren statt die Form zu aendern.
  • Noise/Displace: Voronoi / Clouds / Distorted Noise, Scale + Staerke, Seed. Seed-Variation ueber einen moderaten Welt-Offset (±25) vor dem Displace-Apply Blenders prozedurale Texturen haben keinen echten Seed-Parameter.
  • Cleanup: zweistufiges Decimate + Limited Dissolve, danach normals_make_consistent (verhindert eingefallene/dunkle Flaechen).
  • UV fuer Unreal: UV0 (Smart Project) fuer Material-Texturen, UV1 (Lightmap) als separater, nicht ueberlappender Kanal fuer Static-Mesh-Lightmaps.
  • Hard-Surface (optional): Bevel + Weighted Normal fuer einen "clean" Kantenlook.
  • Material (Platzhalter): weist pro Rock ein Material zu und rotiert durch eine kleine Palette (Granite/Basalt/Sandstone/Mossy), damit nichts materiallos exportiert wird.
  • LOD-Set: erzeugt LOD0LODn als LOD-Gruppe fuer Unreal (siehe unten).
  • Presets: eingebaute Presets (Kleiner Kiesel, Grosser Bruchfels, Lava-Rock) plus eigene Presets als JSON (rock_presets.json neben dem Addon).
  • Batch: synchron fuer kleine Mengen, ab modal_threshold (Default 25) automatisch modal mit Progress-Bar die UI bleibt bedienbar, ESC bricht ab.
  • Robustheit: Faellt ein einzelner Rock aus (z. B. 0 Faces nach zu aggressivem Decimate), wird nur dieser uebersprungen und aufgeraeumt, der Rest des Batches bleibt erhalten. Fehler landen in der System-Konsole.

LOD-Export nach Unreal

Blenders FBX-Exporter kennt keine eigene LOD-Option. Unreal erkennt eine LOD-Gruppe aber, wenn das Eltern-Empty die Custom Property fbx_type = "LodGroup" traegt und die LOD-Meshes in Reihenfolge (LOD0…LODn) darunter geparentet sind. Genau das erzeugt der Generator.

Beim FBX-Export darauf achten:

  • Custom Properties aktivieren (sonst geht fbx_type verloren).
  • Nur die LOD-Gruppe (Empty + Kinder) selektieren, use_selection an.

Beim Import in Unreal:

  • Im FBX-Import-Dialog Import LODs aktivieren.

Referenz: Importing Static Mesh LODs Using FBX (UE Docs).


Tests

Alles auf einmal:

pwsh -File run_tests.ps1

Einzeln geht weiterhin blender --background --python tests/<datei>.py, gefiltert .\run_tests.ps1 -Filter panels.

Warum der Runner existiert. Die Tests lagen einzeln herum und wurden nur beim Bauen des jeweiligen Features gestartet. Beim Aufraeumen der Panels stellte sich heraus, dass fuenf davon still veraltet waren: sie prueften gegen Objekt-Namen aus der Zeit vor dem Benennungssystem (Rock_ statt Fels_A_M_01), gegen fest verdrahtete Zip-Versionen, oder zaehlten die spaeter dazugekommenen _Leaf-Objekte mit. Sie haetten eine echte Regression nicht gemeldet. Die drei Muster, die das ausgeloest haben, sind jetzt vermieden: Objekte ueber die Differenz vorher/nachher finden statt ueber ein Namens-Praefix, Zips per glob statt mit fester Version, und Zusatz-Ebenen beim Zaehlen herausfiltern.

Datei prueft
tests/test_baum_export.py Stand-Baum: ein Mesh, zwei Slots, Ursprung, Kugel-Normalen im exportierten FBX
tests/test_blatt_bueschel.py Bueschel-Defaults, Wachstums-Kopplung der Card-Groesse, Stufen-Budget 800/1200/4000
tests/test_baum_lod.py LOD-Kette: 4 Stufen unter LodGroup, Cards werden nach oben duenner, Kugel-Normalen auf jeder Stufe, kein Hoehenversatz
tests/test_faell_teile.py Faell-Teile: Blatt-Bilanz 170=170, Pivot am Massenmittelpunkt, UCX je Teil, Gesamtdatei mit gemeinsamem Ursprung
tests/test_blatt_varianten.py Card-Sammlung: alle Varianten werden gezogen, seed-stabil, Einzelobjekt weiter moeglich
tests/test_proportionen.py Baum-Proportionen: Palmenkrone, Strauch-Breite, Setzling-Schlankheit, Branch Count 0
tests/test_bruchstuecke.py Bruchstuecke: Volumen 100 % des Muttersteins, dichte Topologie, genau ein Erz-Stueck, Material-Slots, Ursprung, UCX je Teil
tests/test_schnellstart.py Schnellstart: jede Sorte liefert ohne Nacharbeit Geometrie + Slot, Erz-Sorte ueber der Dichte-Schwelle, Dissolve-Bereich streut je Stein
tests/test_erz.py Erz-Maske: A=1.0 ohne Erz, Anteil monoton, Set-Namen/Groessen, Einstellungen zurueckgesetzt, FBX-Roundtrip, Tri-Fenster
tests/test_export_robust.py Export am reimportierten FBX: Ursprung erzwungen, flach = liegend, UCX geschlossen/umschliessend/im Budget, LOD+Vertex-Farben
tests/test_panels.py Alle vier Addons: registrieren, jedes Panel zeichnet, Unter-Panels im richtigen Tab, jede Property irgendwo erreichbar
tests/test_tree_addon.py Tree-Addon: Presets, Wachstums-Stufen, UVs, Modifier-Apply
tests/test_ecogame_rock.py Fels-Benennung, Bake liefert nur _albedo/_normal, AO im Albedo
tests/test_ecogame_tree.py Baum-Benennung <Art>_<Variante>_S<Stufe>, Ursprung, Lightmap-Default
tests/test_randomize.py Zufalls-Funktion von Rock und Tree, _Leaf/_Frucht mit gleichem Ursprung
tests/test_leaves.py Blattwerk: Streuung, Ursprung, Tri-Budget, leeres _Leaf ohne Card
tests/test_rock_classes.py Groessen-Klassen S/M/L/XL unterscheiden sich, Streuung bleibt formbezogen

Die Suiten erwarten die gebauten Zips in dist/ bzw. lesen die .py direkt.

Erzeugt 3 Rocks, prueft je Rock: keine losen Vertices, manifold, Normalen nach aussen (signed volume > 0), UV0 + UV1 vorhanden, und gibt eine Zusammenfassung (Vertex-/Face-Count) aus. Exit-Code 0 = alles bestanden, 1 = Fehler.


Changelog v2.3.0

  • Textur-Bake auf Zell-Look umgestellt: Voronoi „Distance to Edge" liefert hand-painted Zellen mit dunklen Outlines statt organischem fBm-Rauschen. Neu regelbar: Grundfarbe und Zellgroesse. Weiterhin nahtlos tileable.

Changelog v2.2.0

  • Textur-Bake-Button: backt ein nahtlos tileables Stylized-Rock-Set (BaseColor / Normal / Roughness / AO) als PNG. Nahtlos durch 4D-Torus- Projektion der UV (cos/sin von U und V) → tilet in beide Richtungen ohne Naht, ideal fuer Triplanar in Unreal.
  • Anleitung zur UE-Material-Verdrahtung: siehe docs/UE_Material_Triplanar.md.

Changelog v2.1.0

  • FBX-Export-Button (UE-ready): exportiert jeden Rock / jede LOD-Gruppe als eigene FBX mit use_custom_props=True (fuer fbx_type=LodGroup), Triangulate und passendem Smoothing (EDGE bei Auto-Smooth, sonst FACE). UE-Import-Tipp: Normal Import Method auf „Import Normals" stellen, „Import LODs" an.
  • Shading-Toggle: Faceted / Smooth / Auto-Smooth-nach-Winkel.
  • Pro-Rock-Varianz: Noise-Scale & Displace-Staerke zwischen Min/Max plus optionale nicht-uniforme Skalierung → ein Batch wirkt variantenreich.
  • Boden abflachen (Bisect) + Origin unten-mittig → Rocks stehen in UE plan auf.

Changelog v2.0.0

Gehaertet & erweitert gegenueber v1.0.0:

  • Bugfix: smart_project(angle_limit=…) ist in Blender 4.x/5.x in Radiant ([0, 1.5708]) der alte Wert 66.0 war ungueltig. Jetzt radians(66°).
  • Bugfix: dissolve_limited(angle_limit=…) erwartet Radiant; der Grad-Wert wurde ungewandelt durchgereicht → uebermaessiges Dissolve. Jetzt radians(dissolve_angle).
  • Bugfix: Der Lightmap-Kanal wurde vor lightmap_pack nicht als aktiver UV-Layer gesetzt → UV0 konnte ueberschrieben werden. Jetzt explizit gesetzt, UV0 danach wieder als Render-Kanal.
  • Fehlerbehandlung um jeden bpy.ops.*-Aufruf; einzelne Rock-Fehler brechen den Batch nicht mehr ab.
  • Nicht-blockierender modaler Batch-Modus mit Progress-Bar.
  • Bevel + Weighted Normal als Toggle.
  • LOD-Set als fbx_type=LodGroup-Gruppe.
  • Preset-System (JSON) mit Panel-Dropdown.
  • Platzhalter-Material-Zuweisung.