Beide Addons auf die EcoGame-Konventionen abgestimmt

Rock Generator 2.4.0:
- Benennung Fels_<Form>_<Klasse> statt Rock_000 (Praefix/Form/Klasse im Panel,
  Klasse S/M/L/XL, optionale Durchnummerierung bei Batches)
- Bake liefert jetzt <name>_albedo und <name>_normal. AO/Cavity wird per Option
  MULTIPLIKATIV ins Albedo gebacken (Staerke regelbar) - separate Roughness- und
  AO-Paesse sind Default aus, weil Roughness in EcoGame eine Zahl im Master-
  Material ist. Gemessen: Albedo-Mittelwert 0.549 -> 0.433 mit AO, Kontrast rauf.
- UV1 Lightmap Default AUS (EcoGame beleuchtet dynamisch)

Tree Generator 1.18.0:
- Benennung <Art>_<Variante>_S<Stufe>, z.B. Birke_01_S1..S4 (statt stage0..3);
  Art-Feld und Varianten-Nummer im Panel, Art leer = Preset-Name
- UV1 Lightmap Default AUS

README: Konventionen dokumentiert.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-29 12:25:09 +02:00
co-authored by Claude Opus 4.8
parent ca13e9f573
commit f5bbd1bc35
10 changed files with 182 additions and 41 deletions
+31
View File
@@ -12,6 +12,37 @@
--- ---
## EcoGame-Konventionen (beide Addons)
Abgestimmt auf `A:\eco\EcoGame` (UE 5.8). Kurzfassung:
**Benennung**
- Felsen: `Fels_<Form>_<Klasse>` — Form = Buchstabe (A, B, …), Klasse = `S/M/L/XL`.
Die Klasse meint eine eigene **Silhouette**, nicht blosse Groesse: rein
groesser/kleiner macht die Engine ueber ScaleMin/Max.
- Baeume: `<Art>_<Variante>_S<Stufe>`, z. B. `Birke_01_S1``Birke_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**.
- 1024 px reicht fuer stylized Formen.
- Tileable (4D-Torus) passt zum triplanar mappenden Master-Material.
**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.
---
## Stylized Tree Generator ## Stylized Tree Generator
Parametrischer **Geaest**-Generator (Stamm + Aeste + Sub-Aeste) als Geometry-Nodes- Parametrischer **Geaest**-Generator (Stamm + Aeste + Sub-Aeste) als Geometry-Nodes-
+1 -1
View File
@@ -1,7 +1,7 @@
schema_version = "1.0.0" schema_version = "1.0.0"
id = "stylized_rock_generator" id = "stylized_rock_generator"
version = "2.3.0" version = "2.4.0"
name = "Stylized Rock Generator" name = "Stylized Rock Generator"
tagline = "Batch-Generator fuer stylized Rocks mit UE5-Export" tagline = "Batch-Generator fuer stylized Rocks mit UE5-Export"
maintainer = "D4rkst3r" maintainer = "D4rkst3r"
+1 -1
View File
@@ -1,7 +1,7 @@
schema_version = "1.0.0" schema_version = "1.0.0"
id = "stylized_tree_generator" id = "stylized_tree_generator"
version = "1.17.0" version = "1.18.0"
name = "Stylized Tree Generator" name = "Stylized Tree Generator"
tagline = "Parametrische Baeume, Palmen, Bueschen mit Wachstums-Stufen" tagline = "Parametrische Baeume, Palmen, Bueschen mit Wachstums-Stufen"
maintainer = "D4rkst3r" maintainer = "D4rkst3r"
+8 -8
View File
@@ -7,7 +7,7 @@
"id": "stylized_rock_generator", "id": "stylized_rock_generator",
"name": "Stylized Rock Generator", "name": "Stylized Rock Generator",
"tagline": "Batch-Generator fuer stylized Rocks mit UE5-Export", "tagline": "Batch-Generator fuer stylized Rocks mit UE5-Export",
"version": "2.3.0", "version": "2.4.0",
"type": "add-on", "type": "add-on",
"maintainer": "D4rkst3r", "maintainer": "D4rkst3r",
"license": [ "license": [
@@ -23,16 +23,16 @@
"Mesh", "Mesh",
"Modeling" "Modeling"
], ],
"archive_url": "./stylized_rock_generator-2.3.0.zip", "archive_url": "./stylized_rock_generator-2.4.0.zip",
"archive_size": 14694, "archive_size": 16036,
"archive_hash": "sha256:a6e15dd94c6b77dc1ef7c631d1d1dce275a8d3550d16369d90675a2f42fa6298" "archive_hash": "sha256:e7163f6f0c53731f897b8a1ba63db1cdab94aa182dd720ca1a5653a2ca3df9ec"
}, },
{ {
"schema_version": "1.0.0", "schema_version": "1.0.0",
"id": "stylized_tree_generator", "id": "stylized_tree_generator",
"name": "Stylized Tree Generator", "name": "Stylized Tree Generator",
"tagline": "Parametrische Baeume, Palmen, Bueschen mit Wachstums-Stufen", "tagline": "Parametrische Baeume, Palmen, Bueschen mit Wachstums-Stufen",
"version": "1.17.0", "version": "1.18.0",
"type": "add-on", "type": "add-on",
"maintainer": "D4rkst3r", "maintainer": "D4rkst3r",
"license": [ "license": [
@@ -48,9 +48,9 @@
"Mesh", "Mesh",
"Modeling" "Modeling"
], ],
"archive_url": "./stylized_tree_generator-1.17.0.zip", "archive_url": "./stylized_tree_generator-1.18.0.zip",
"archive_size": 16523, "archive_size": 16777,
"archive_hash": "sha256:49c3e777aa602ba3c63537aacd866bb5b11ac6484dbc0f91b28d26f497f98c00" "archive_hash": "sha256:9ea5b7a9c01f3c1b382436d25539ab9ec41ffb8b051d26c17c82783c594680a0"
} }
] ]
} }
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+113 -24
View File
@@ -1,7 +1,7 @@
bl_info = { bl_info = {
"name": "Stylized Rock Generator", "name": "Stylized Rock Generator",
"author": "D4rkst3r", "author": "D4rkst3r",
"version": (2, 3, 0), "version": (2, 4, 0),
"blender": (4, 2, 0), "blender": (4, 2, 0),
"location": "View3D > Sidebar > Rock Gen", "location": "View3D > Sidebar > Rock Gen",
"description": ( "description": (
@@ -102,6 +102,16 @@ def _apply_modifier(context, obj, mod_name):
raise RockGenError("Modifier '%s' Apply wurde abgebrochen." % mod_name) raise RockGenError("Modifier '%s' Apply wurde abgebrochen." % mod_name)
def _asset_name(settings, index, batch_count):
"""EcoGame-Schema: Fels_<Form>_<Klasse>, bei Batches mit laufender Nummer."""
base = "%s_%s_%s" % (settings.name_prefix.strip() or "Fels",
(settings.name_form.strip() or "A").upper(),
settings.name_class)
if settings.name_numbered and batch_count > 1:
return "%s_%02d" % (base, index + 1)
return base
def _face_count(obj): def _face_count(obj):
return len(obj.data.polygons) return len(obj.data.polygons)
@@ -279,7 +289,7 @@ def _make_lightmap_uv(context, obj):
# Mesh-Aufbau # Mesh-Aufbau
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
def _build_rock_mesh(context, settings, index, location, rng): def _build_rock_mesh(context, settings, index, location, rng, name=None):
"""Baut das reine Rock-Mesh (ohne Transform/Material). Kann RockGenError werfen.""" """Baut das reine Rock-Mesh (ohne Transform/Material). Kann RockGenError werfen."""
# 1. Basis-Mesh # 1. Basis-Mesh
if settings.base_shape == 'CUBE': if settings.base_shape == 'CUBE':
@@ -289,7 +299,7 @@ def _build_rock_mesh(context, settings, index, location, rng):
subdivisions=1, radius=1.0, location=location subdivisions=1, radius=1.0, location=location
) )
obj = context.active_object obj = context.active_object
obj.name = "Rock_%03d" % index obj.name = name or ("Rock_%03d" % index)
# Effektive Form-Parameter (optional pro Rock variiert) # Effektive Form-Parameter (optional pro Rock variiert)
eff_noise = settings.noise_scale eff_noise = settings.noise_scale
@@ -504,10 +514,10 @@ def _parent_keep_transform(child, parent):
child.matrix_parent_inverse = parent.matrix_world.inverted() child.matrix_parent_inverse = parent.matrix_world.inverted()
def _build_lod_group(context, settings, index, location, rng): def _build_lod_group(context, settings, index, location, rng, base_name=None):
base_name = "Rock_%03d" % index base_name = base_name or ("Rock_%03d" % index)
lod0 = _build_rock_mesh(context, settings, index, location, rng) lod0 = _build_rock_mesh(context, settings, index, location, rng, name=base_name)
_apply_hardsurface(context, lod0, settings) _apply_hardsurface(context, lod0, settings)
_apply_random_transform(context, lod0, settings, rng) _apply_random_transform(context, lod0, settings, rng)
if settings.assign_material: if settings.assign_material:
@@ -556,15 +566,16 @@ def _build_lod_group(context, settings, index, location, rng):
# Ein Rock (bzw. eine LOD-Gruppe) erzeugen # Ein Rock (bzw. eine LOD-Gruppe) erzeugen
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
def make_one_rock(context, settings, index): def make_one_rock(context, settings, index, batch_count=1):
"""Erzeugt genau einen Rock / eine LOD-Gruppe. Liefert Liste der Objekte.""" """Erzeugt genau einen Rock / eine LOD-Gruppe. Liefert Liste der Objekte."""
rng = random.Random(settings.seed + index * 9973) rng = random.Random(settings.seed + index * 9973)
location = (index * settings.spacing, 0.0, 0.0) location = (index * settings.spacing, 0.0, 0.0)
name = _asset_name(settings, index, batch_count)
if settings.generate_lods: if settings.generate_lods:
return _build_lod_group(context, settings, index, location, rng) return _build_lod_group(context, settings, index, location, rng, base_name=name)
obj = _build_rock_mesh(context, settings, index, location, rng) obj = _build_rock_mesh(context, settings, index, location, rng, name=name)
_apply_hardsurface(context, obj, settings) _apply_hardsurface(context, obj, settings)
_apply_random_transform(context, obj, settings, rng) _apply_random_transform(context, obj, settings, rng)
_apply_shading(context, obj, settings) _apply_shading(context, obj, settings)
@@ -645,8 +656,9 @@ class RockGenSettings(PropertyGroup):
description="Fuer Material-Maker-Texturen (Albedo/Normal/Roughness/AO)", description="Fuer Material-Maker-Texturen (Albedo/Normal/Roughness/AO)",
) )
generate_lightmap_uv: BoolProperty( generate_lightmap_uv: BoolProperty(
name="UV1 Lightmap-UV", default=True, name="UV1 Lightmap-UV", default=False,
description="Separates, nicht ueberlappendes UV fuer Unreal Static-Mesh-Lightmaps", description=("Separates, nicht ueberlappendes UV fuer Static-Mesh-Lightmaps. "
"EcoGame beleuchtet dynamisch - dort nicht noetig"),
) )
# --- Hard-Surface (optional) --- # --- Hard-Surface (optional) ---
@@ -736,19 +748,57 @@ class RockGenSettings(PropertyGroup):
name="Bake-Ordner", default="", subtype='DIR_PATH', name="Bake-Ordner", default="", subtype='DIR_PATH',
description="Zielordner fuer die gebackenen Textur-PNGs", description="Zielordner fuer die gebackenen Textur-PNGs",
) )
# --- Benennung (EcoGame-Schema: Fels_<Form>_<Klasse>) ---
name_prefix: StringProperty(
name="Praefix", default="Fels",
description="Basisname, z.B. 'Fels' oder 'Felsblock'",
)
name_form: StringProperty(
name="Form", default="A",
description="Form-Variante als Buchstabe (A, B, C ...)",
)
name_class: EnumProperty(
name="Groessen-Klasse",
items=[('S', "S", "klein"), ('M', "M", "mittel"),
('L', "L", "gross"), ('XL', "XL", "sehr gross")],
default='M',
description=("Groessen-KLASSE mit eigener Geometrie. Reines Groesser/Kleiner "
"macht die Engine ueber ScaleMin/Max - eine eigene Klasse nur, "
"wenn sich die Silhouette aendert"),
)
name_numbered: BoolProperty(
name="Bei Batch durchnummerieren", default=True,
description="Haengt bei mehreren Rocks _01, _02 ... an (sonst Blender-Suffix)",
)
bake_prefix: StringProperty( bake_prefix: StringProperty(
name="Textur-Prefix", default="T_Rock_Stylized", name="Textur-Name", default="fels_a",
description="Dateiname-Prefix (UE-Konvention T_)", description="Basisname; es entstehen <name>_albedo.png und <name>_normal.png",
) )
bake_resolution: EnumProperty( bake_resolution: EnumProperty(
name="Aufloesung", name="Aufloesung",
items=[('512', "512", ""), ('1024', "1024", ""), ('2048', "2048", "")], items=[('512', "512", ""), ('1024', "1024", ""), ('2048', "2048", "")],
default='1024', default='1024', # reicht fuer stylized/flaechige Formen
) )
bake_color: BoolProperty(name="BaseColor", default=True) bake_color: BoolProperty(name="Albedo", default=True)
bake_normal: BoolProperty(name="Normal", default=True) bake_normal: BoolProperty(name="Normal", default=True)
bake_roughness: BoolProperty(name="Roughness", default=True) bake_roughness: BoolProperty(
bake_ao: BoolProperty(name="AO / Cavity", default=True) name="Roughness", default=False,
description="In EcoGame ist Roughness eine ZAHL im Master-Material, keine Textur",
)
bake_ao: BoolProperty(
name="AO separat", default=False,
description=("Eigene AO-Textur. In EcoGame nicht noetig - dort wird AO ins "
"Albedo eingerechnet (siehe unten)"),
)
bake_ao_into_albedo: BoolProperty(
name="AO ins Albedo einrechnen", default=True,
description="Cavity/AO multiplikativ ins Farbbild backen (EcoGame-Konvention)",
)
bake_ao_strength: FloatProperty(
name="AO-Staerke", default=1.0, min=0.0, max=1.0,
description="0 = kein AO im Albedo, 1 = voll",
)
bake_base_color: FloatVectorProperty( bake_base_color: FloatVectorProperty(
name="Grundfarbe", subtype='COLOR', size=4, name="Grundfarbe", subtype='COLOR', size=4,
default=(0.52, 0.47, 0.40, 1.0), min=0.0, max=1.0, default=(0.52, 0.47, 0.40, 1.0), min=0.0, max=1.0,
@@ -796,7 +846,7 @@ class OBJECT_OT_generate_rocks(Operator):
for i in range(settings.batch_count): for i in range(settings.batch_count):
names_before = set(bpy.data.objects.keys()) names_before = set(bpy.data.objects.keys())
try: try:
created.extend(make_one_rock(context, settings, i)) created.extend(make_one_rock(context, settings, i, settings.batch_count))
except Exception as exc: # noqa: BLE001 - bewusst breit, ein Rock darf nicht den Batch killen except Exception as exc: # noqa: BLE001 - bewusst breit, ein Rock darf nicht den Batch killen
failed.append((i, str(exc))) failed.append((i, str(exc)))
print("[RockGen] Rock %d fehlgeschlagen: %s" % (i, exc)) print("[RockGen] Rock %d fehlgeschlagen: %s" % (i, exc))
@@ -839,7 +889,7 @@ class OBJECT_OT_generate_rocks(Operator):
names_before = set(bpy.data.objects.keys()) names_before = set(bpy.data.objects.keys())
try: try:
self._created.extend(make_one_rock(context, settings, self._index)) self._created.extend(make_one_rock(context, settings, self._index, settings.batch_count))
except Exception as exc: # noqa: BLE001 except Exception as exc: # noqa: BLE001
self._failed.append((self._index, str(exc))) self._failed.append((self._index, str(exc)))
print("[RockGen] Rock %d fehlgeschlagen: %s" % (self._index, exc)) print("[RockGen] Rock %d fehlgeschlagen: %s" % (self._index, exc))
@@ -873,7 +923,9 @@ def _collect_export_targets(context, settings):
tops = [] tops = []
for o in context.scene.objects: for o in context.scene.objects:
is_group = (o.type == 'EMPTY' and o.get("fbx_type") == "LodGroup") is_group = (o.type == 'EMPTY' and o.get("fbx_type") == "LodGroup")
is_standalone = (o.type == 'MESH' and o.name.startswith("Rock_") and o.parent is None) is_standalone = (o.type == 'MESH' and o.parent is None
and (o.name.startswith(settings.name_prefix.strip() or "Fels")
or o.name.startswith("Rock_")))
if is_group or is_standalone: if is_group or is_standalone:
tops.append(o) tops.append(o)
if settings.export_selected_only: if settings.export_selected_only:
@@ -1073,6 +1125,19 @@ def _build_bake_material(base_color=(0.52, 0.47, 0.40), cell_scale=2.5):
bump.inputs["Distance"].default_value = 0.4 bump.inputs["Distance"].default_value = 0.4
links.new(cell_fac.outputs[0], bump.inputs["Height"]) links.new(cell_fac.outputs[0], bump.inputs["Height"])
# EcoGame-Konvention: AO/Cavity wird NICHT als eigene Textur exportiert,
# sondern direkt ins Albedo multipliziert - dunkle Ritzen und helle Platten
# sind im Farbbild eingebacken. Das traegt den Stylized-Look.
ao_mix = nodes.new("ShaderNodeMixRGB")
ao_mix.blend_type = 'MULTIPLY'
ao_mix.inputs["Fac"].default_value = 1.0
links.new(ramp.outputs["Color"], ao_mix.inputs["Color1"])
ao_rgb = nodes.new("ShaderNodeCombineXYZ")
links.new(ao.outputs[0], ao_rgb.inputs["X"])
links.new(ao.outputs[0], ao_rgb.inputs["Y"])
links.new(ao.outputs[0], ao_rgb.inputs["Z"])
links.new(ao_rgb.outputs[0], ao_mix.inputs["Color2"])
links.new(ramp.outputs["Color"], bsdf.inputs["Base Color"]) links.new(ramp.outputs["Color"], bsdf.inputs["Base Color"])
links.new(rough.outputs[0], bsdf.inputs["Roughness"]) links.new(rough.outputs[0], bsdf.inputs["Roughness"])
links.new(bump.outputs["Normal"], bsdf.inputs["Normal"]) links.new(bump.outputs["Normal"], bsdf.inputs["Normal"])
@@ -1081,6 +1146,8 @@ def _build_bake_material(base_color=(0.52, 0.47, 0.40), cell_scale=2.5):
sockets = { sockets = {
"out": out, "bsdf": bsdf, "emit": emit, "out": out, "bsdf": bsdf, "emit": emit,
"color": ramp.outputs["Color"], "color": ramp.outputs["Color"],
"color_ao": ao_mix.outputs["Color"], # Albedo mit eingebackenem AO
"ao_mix": ao_mix,
"roughness": rough.outputs[0], "roughness": rough.outputs[0],
"ao": ao.outputs[0], "ao": ao.outputs[0],
} }
@@ -1139,6 +1206,10 @@ class OBJECT_OT_bake_rock_textures(Operator):
base_color=tuple(settings.bake_base_color), base_color=tuple(settings.bake_base_color),
cell_scale=settings.bake_cell_scale, cell_scale=settings.bake_cell_scale,
) )
# AO-Staerke ins Albedo (Fac 0 = aus, 1 = voll)
sk["ao_mix"].inputs["Fac"].default_value = (
settings.bake_ao_strength if settings.bake_ao_into_albedo else 0.0
)
plane.data.materials.clear() plane.data.materials.clear()
plane.data.materials.append(mat) plane.data.materials.append(mat)
@@ -1157,7 +1228,7 @@ class OBJECT_OT_bake_rock_textures(Operator):
if settings.bake_normal: if settings.bake_normal:
links.new(sk["bsdf"].outputs[0], sk["out"].inputs["Surface"]) links.new(sk["bsdf"].outputs[0], sk["out"].inputs["Surface"])
saved.append(_bake_one(context, mat, img_node, prefix + "_N", saved.append(_bake_one(context, mat, img_node, prefix + "_normal",
folder, res, 'NORMAL', non_color=True)) folder, res, 'NORMAL', non_color=True))
def _emit_pass(source_socket, suffix, non_color): def _emit_pass(source_socket, suffix, non_color):
@@ -1167,11 +1238,14 @@ class OBJECT_OT_bake_rock_textures(Operator):
folder, res, 'EMIT', non_color)) folder, res, 'EMIT', non_color))
if settings.bake_color: if settings.bake_color:
_emit_pass(sk["color"], "_BC", non_color=False) # EcoGame braucht nur _albedo und _normal. Das AO steckt bereits
# im Farbbild, sofern "AO ins Albedo einrechnen" aktiv ist.
src = sk["color_ao"] if settings.bake_ao_into_albedo else sk["color"]
_emit_pass(src, "_albedo", non_color=False)
if settings.bake_roughness: if settings.bake_roughness:
_emit_pass(sk["roughness"], "_R", non_color=True) _emit_pass(sk["roughness"], "_roughness", non_color=True)
if settings.bake_ao: if settings.bake_ao:
_emit_pass(sk["ao"], "_AO", non_color=True) _emit_pass(sk["ao"], "_ao", non_color=True)
except Exception as exc: # noqa: BLE001 except Exception as exc: # noqa: BLE001
print("[RockGen] Bake fehlgeschlagen: %s" % exc) print("[RockGen] Bake fehlgeschlagen: %s" % exc)
@@ -1300,6 +1374,16 @@ class VIEW3D_PT_rock_generator(Panel):
row.prop(settings, "new_preset_name", text="") row.prop(settings, "new_preset_name", text="")
row.operator("rockgen.preset_save", text="Speichern", icon='ADD') row.operator("rockgen.preset_save", text="Speichern", icon='ADD')
box = layout.box()
box.label(text="Benennung", icon='SORTALPHA')
row = box.row(align=True)
row.prop(settings, "name_prefix", text="")
row.prop(settings, "name_form", text="")
row.prop(settings, "name_class", text="")
box.prop(settings, "name_numbered")
box.label(text="Ergebnis: %s" % _asset_name(settings, 0, settings.batch_count),
icon='INFO')
box = layout.box() box = layout.box()
box.label(text="Basis") box.label(text="Basis")
box.prop(settings, "base_shape") box.prop(settings, "base_shape")
@@ -1397,6 +1481,11 @@ class VIEW3D_PT_rock_generator(Panel):
row = box.row(align=True) row = box.row(align=True)
row.prop(settings, "bake_color", toggle=True) row.prop(settings, "bake_color", toggle=True)
row.prop(settings, "bake_normal", toggle=True) row.prop(settings, "bake_normal", toggle=True)
box.prop(settings, "bake_ao_into_albedo")
sub = box.row()
sub.enabled = settings.bake_ao_into_albedo
sub.prop(settings, "bake_ao_strength")
row = box.row(align=True)
row.prop(settings, "bake_roughness", toggle=True) row.prop(settings, "bake_roughness", toggle=True)
row.prop(settings, "bake_ao", toggle=True) row.prop(settings, "bake_ao", toggle=True)
box.operator("object.bake_rock_textures", icon='TEXTURE') box.operator("object.bake_rock_textures", icon='TEXTURE')
+28 -7
View File
@@ -1,7 +1,7 @@
bl_info = { bl_info = {
"name": "Stylized Tree Generator", "name": "Stylized Tree Generator",
"author": "D4rkst3r", "author": "D4rkst3r",
"version": (1, 17, 0), "version": (1, 18, 0),
"blender": (4, 2, 0), "blender": (4, 2, 0),
"location": "View3D > Sidebar > Tree Gen", "location": "View3D > Sidebar > Tree Gen",
"description": "Parametrischer Baum-/Palmen-/Busch-Generator (Geometry Nodes) mit Wachstums-Stufen", "description": "Parametrischer Baum-/Palmen-/Busch-Generator (Geometry Nodes) mit Wachstums-Stufen",
@@ -964,7 +964,8 @@ def make_object(ng, preset="baum", name=None, growth=None, seed=None):
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
from bpy.props import ( from bpy.props import (
IntProperty, FloatProperty, EnumProperty, BoolProperty, PointerProperty, IntProperty, FloatProperty, EnumProperty, BoolProperty, StringProperty,
PointerProperty,
) )
from bpy.types import Operator, Panel, PropertyGroup from bpy.types import Operator, Panel, PropertyGroup
@@ -981,18 +982,32 @@ class TreeGenSettings(PropertyGroup):
description="Erzeugt mehrere Stufen (Setzling .. ausgewachsen) mit gleichem Seed", description="Erzeugt mehrere Stufen (Setzling .. ausgewachsen) mit gleichem Seed",
) )
stages: IntProperty(name="Anzahl Stufen", default=4, min=2, max=8) stages: IntProperty(name="Anzahl Stufen", default=4, min=2, max=8)
art_name: StringProperty(
name="Art", default="",
description=("Art-Name fuer den Export, z.B. 'Birke'. Leer = Preset-Name. "
"Schema: <Art>_<Variante>_S<Stufe>"),
)
variant: IntProperty(
name="Variante", default=1, min=1, max=99,
description="Laufende Variante derselben Art (Birke_01, Birke_02 ...)",
)
spacing: FloatProperty(name="Abstand", default=4.0, min=0.0, max=20.0) spacing: FloatProperty(name="Abstand", default=4.0, min=0.0, max=20.0)
apply_modifier: BoolProperty( apply_modifier: BoolProperty(
name="Modifier anwenden", default=False, name="Modifier anwenden", default=False,
description="Ergebnis als normales Mesh einfrieren (fuer den Export)", description="Ergebnis als normales Mesh einfrieren (fuer den Export)",
) )
generate_lightmap_uv: BoolProperty( generate_lightmap_uv: BoolProperty(
name="UV1 Lightmap-UV", default=True, name="UV1 Lightmap-UV", default=False,
description=("Zweiter, nicht ueberlappender UV-Kanal fuer Unreal-Lightmaps. " description=("Zweiter, nicht ueberlappender UV-Kanal fuer Lightmaps. Braucht "
"Braucht 'Modifier anwenden' (nur echte Meshes lassen sich unwrappen)"), "'Modifier anwenden'. EcoGame beleuchtet dynamisch -> nicht noetig"),
) )
def _art(s):
"""Art-Name fuer das EcoGame-Schema <Art>_<Variante>_S<Stufe>."""
return (s.art_name.strip() or s.preset).capitalize()
def _place(ob, x): def _place(ob, x):
ob.location.x = x ob.location.x = x
@@ -1049,12 +1064,12 @@ class TREEGEN_OT_create(Operator):
for i in range(s.stages): for i in range(s.stages):
t = i / float(s.stages - 1) if s.stages > 1 else 1.0 t = i / float(s.stages - 1) if s.stages > 1 else 1.0
ob = make_object(ng, preset=s.preset, growth=t, seed=s.seed, ob = make_object(ng, preset=s.preset, growth=t, seed=s.seed,
name="%s_%s_stage%d" % (OBJ_NAME, s.preset, i)) name="%s_%02d_S%d" % (_art(s), s.variant, i + 1))
_place(ob, x); x += s.spacing _place(ob, x); x += s.spacing
created.append(ob) created.append(ob)
else: else:
ob = make_object(ng, preset=s.preset, seed=s.seed, ob = make_object(ng, preset=s.preset, seed=s.seed,
name="%s_%s" % (OBJ_NAME, s.preset)) name="%s_%02d" % (_art(s), s.variant))
created.append(ob) created.append(ob)
except Exception as exc: # noqa: BLE001 except Exception as exc: # noqa: BLE001
self.report({'ERROR'}, "Erzeugen fehlgeschlagen: %s" % exc) self.report({'ERROR'}, "Erzeugen fehlgeschlagen: %s" % exc)
@@ -1099,6 +1114,12 @@ class VIEW3D_PT_tree_generator(Panel):
box.label(text="Vorlage", icon='PRESET') box.label(text="Vorlage", icon='PRESET')
box.prop(s, "preset", text="") box.prop(s, "preset", text="")
box.prop(s, "seed") box.prop(s, "seed")
row = box.row(align=True)
row.prop(s, "art_name", text="")
row.prop(s, "variant")
box.label(text="Name: %s_%02d%s" % (_art(s), s.variant,
"_S1..S%d" % s.stages if s.use_growth else ""),
icon='INFO')
box = layout.box() box = layout.box()
box.label(text="Wachstum") box.label(text="Wachstum")