champions-of-luneraria/tools/blender/bake_pbr.py
Martin Stickelbroeck 07fa4572e0 U5.3: Gebäude, Boss und Ringborke auf PBR; Bildfehler aus U5 behoben
Fortsetzung des angefangenen U5.3-Stands vom 2. Oktober.

- Fünf neue Sätze aus bake_pbr.py (metal, cloth, hide, bone, wood) für
  Türme, Ancients, Grotte und die Schlundkrone. Ringborkes Rinde (RB_Bark)
  auf ihren eigenen UVs mit dem 4k-Satz (Rauigkeit zu ORM gepackt),
  Normalen im Tangentenraum des Meshes. Aufnahmen aller drei Lanes.
- Befund: Seit U5 war die Welt halb so hell und ohne Grün (Mid-Lane
  Helligkeit 0,27, Grün 0 %). Ursachen und Korrekturen:
  - Die PBR-Albedos wurden über die Godot-Farben multipliziert (Rasen
    etwa x0,05). Jetzt teilt der Shader jede Karte durch ihren Mittel-
    wert; die Godot-Farbe bleibt der Durchschnitt, die Karte liefert
    Struktur. Ebenso die Rauigkeit, wo das Material eine eigene hat.
  - Normal- und ORM-Karten waren als sRGB-Farbe importiert. Jetzt
    Normalkarte bzw. linear.
  - bake_pbr.py verzerrte alle Muster 10:1 (Regenstreifen). Stein, Erde,
    Laub, Wasser und die neuen Sätze ohne Maserung neu gebacken.
  - Der Treffer-Flash ersetzte die Grundfarbe aller Materialplätze; im
    Nahkampf waren die Armeen weiß und der Boss gelb. Jetzt Tönung der
    eigenen Farbe je Platz, wenig Emission, 16 Stufen.
  - Nebel 0,0085 verschleierte die Kartenansicht zu 35-50 %; jetzt 0,0045.
  Ergebnis Mid-Lane: Helligkeit 0,41-0,44, Grün 31-39 % (U3: 0,45 / 27 %).
- Tests: EditMode 29/29, PlayMode 13/13, Verify 718/0, Kerntests .NET
  457/457, Fingerabdrücke unverändert (DCE95C96556F3A1A, 7B8E43AEFC0D60C5).
- Leistung: A/B mit einem U5.2-Player unter gleicher Last, kein messbarer
  Unterschied (91,4 gegen 93,8 fps Karte, 124,9 gegen 125,3 fps Mid).
- Messwerkzeug tools/port/png_stats.py; Doku U5-VISUAL (U5.3),
  PORT-STATUS, PORT-QA; Bilder screenshots/u5.3/ als JPEG.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 19:34:02 +02:00

467 lines
20 KiB
Python

"""Seamless PBR texture bakes for Champions of Luneraria (U5A, docs/port/U5-VISUAL.md).
Procedural material bakes in Blender (Cycles, 1 sample, emission of the map), not bitmaps
scaled up. Every set is seamless on all four edges (torus mapping like phase10/bake_bark.py).
U5.3: the two torus radii set the grain. Unequal radii stretch the noise along v (the bark
grain of phase10); U5 used about 10:1 for every set, which drew vertical streaks into stone,
earth, leaves and the river. Sets without a grain now use one radius (isotropic()), equal in
area to the former pair, so the mean feature size stays; bark, wood and rock strata keep theirs.
Writes 8-bit PNGs to Assets/Luneraria/Art/Textures/:
<name>-albedo-2k.png base colour
<name>-normal-2k.png tangent-space normal from the height field (Sobel)
<name>-orm-2k.png R occlusion, G roughness, B metallic (Unity-style ORM)
Sets: limestone (Kluft stone), soil (packed earth), bark (oak ridges), leaf (foliage),
rock (eroded strata), water (wave normals).
Usage (from the Unity project root):
sh tools/blender/bake_pbr.sh
sh tools/blender/bake_pbr.sh limestone,soil
Exit code 0 only if every set passed.
"""
import bpy
import math
import struct
import sys
import zlib
from pathlib import Path
UNITY_ROOT = Path(__file__).resolve().parents[2]
OUT = UNITY_ROOT / "Assets" / "Luneraria" / "Art" / "Textures"
SIZE = 2048
def args():
argv = sys.argv[sys.argv.index("--") + 1:] if "--" in sys.argv else []
result = {"sets": "all"}
for i in range(0, len(argv) - 1, 2):
result[argv[i].lstrip("-")] = argv[i + 1]
return result
def linear(hexcolor):
rgb = [int(hexcolor[i:i + 2], 16) / 255 for i in (0, 2, 4)]
return tuple((c / 12.92 if c <= 0.04045 else ((c + 0.055) / 1.055) ** 2.4) for c in rgb) + (1.0,)
def math_node(nodes, links, op, a, b=None):
obj = nodes.new("ShaderNodeMath")
obj.operation = op
if isinstance(a, (float, int)):
obj.inputs[0].default_value = float(a)
else:
links.new(a, obj.inputs[0])
if b is not None:
if isinstance(b, (float, int)):
obj.inputs[1].default_value = float(b)
else:
links.new(b, obj.inputs[1])
return obj.outputs[0]
def torus_uv(nodes, links, scale_u, scale_v):
"""UV -> a circle in (cos u, sin u, cos v) so noise wraps on every edge."""
uv = nodes.new("ShaderNodeTexCoord")
split = nodes.new("ShaderNodeSeparateXYZ")
links.new(uv.outputs["UV"], split.inputs[0])
u = math_node(nodes, links, "MULTIPLY", split.outputs["X"], math.tau)
v = math_node(nodes, links, "MULTIPLY", split.outputs["Y"], math.tau)
co = nodes.new("ShaderNodeCombineXYZ")
links.new(math_node(nodes, links, "MULTIPLY", math_node(nodes, links, "COSINE", u), scale_u), co.inputs["X"])
links.new(math_node(nodes, links, "MULTIPLY", math_node(nodes, links, "SINE", u), scale_u), co.inputs["Y"])
links.new(math_node(nodes, links, "MULTIPLY", math_node(nodes, links, "COSINE", v), scale_v), co.inputs["Z"])
w = math_node(nodes, links, "MULTIPLY", math_node(nodes, links, "SINE", v), scale_v)
return co.outputs[0], w
def isotropic(scale_u, scale_v):
"""One torus radius with the area of (scale_u, scale_v): same mean feature size, no grain."""
radius = math.sqrt(scale_u * scale_v)
return radius, radius
def noise(nodes, links, co, w, scale, detail, roughness):
tex = nodes.new("ShaderNodeTexNoise")
tex.noise_dimensions = "4D"
links.new(co, tex.inputs["Vector"])
links.new(w, tex.inputs["W"])
tex.inputs["Scale"].default_value = scale
tex.inputs["Detail"].default_value = detail
tex.inputs["Roughness"].default_value = roughness
return tex.outputs["Fac"]
def ramp(nodes, links, source, stops):
"""ValToRGB with (position, hex) stops in sRGB; output linear colours."""
node_ramp = nodes.new("ShaderNodeValToRGB")
elements = node_ramp.color_ramp.elements
while len(elements) > 2:
elements.remove(elements[-1])
elements[0].position = stops[0][0]
elements[0].color = linear(stops[0][1])
elements[1].position = stops[-1][0]
elements[1].color = linear(stops[-1][1])
for position, hexcolor in stops[1:-1]:
elements.new(position).color = linear(hexcolor)
links.new(source, node_ramp.inputs[0])
return node_ramp.outputs[0]
def new_scene(name):
scene = bpy.data.scenes.new(name)
bpy.context.window.scene = scene
scene.render.engine = "CYCLES"
scene.cycles.samples = 1
scene.render.resolution_x = SIZE
scene.render.resolution_y = SIZE
scene.render.resolution_percentage = 100
scene.render.image_settings.file_format = "PNG"
scene.render.image_settings.color_mode = "RGB"
scene.render.image_settings.color_depth = "8"
scene.render.image_settings.compression = 90
scene.render.dither_intensity = 0
scene.view_settings.view_transform = "Standard"
scene.view_settings.look = "None"
scene.render.film_transparent = False
bpy.ops.mesh.primitive_plane_add(size=2)
plane = bpy.context.object
plane.name = name + "_surface"
mat = bpy.data.materials.new(name)
mat.use_nodes = True
plane.data.materials.append(mat)
nodes = mat.node_tree.nodes
nodes.clear()
links = mat.node_tree.links
emit = nodes.new("ShaderNodeEmission")
output = nodes.new("ShaderNodeOutputMaterial")
links.new(emit.outputs[0], output.inputs["Surface"])
bpy.ops.object.camera_add(location=(0, 0, 3))
camera = bpy.context.object
camera.data.type = "ORTHO"
camera.data.ortho_scale = 2
scene.camera = camera
return scene, nodes, links, emit
def render(scene, emit, source, path):
mat = bpy.data.materials.get(scene.name)
links = mat.node_tree.links
for link in list(emit.inputs["Color"].links):
links.remove(link)
if isinstance(source, (float, int)):
emit.inputs["Color"].default_value = (float(source), float(source), float(source), 1.0)
else:
links.new(source, emit.inputs["Color"])
scene.render.filepath = str(path)
bpy.ops.render.render(write_still=True)
print("PBR_WRITE", path.name)
# ---------------------------------------------------------------- PNG helpers (no numpy)
def read_png_gray(path):
"""Returns (width, height, list of 0..255 luminance samples, row-major)."""
image = bpy.data.images.load(str(path))
width, height = image.size
pixels = list(image.pixels)
bpy.data.images.remove(image)
values = [0] * (width * height)
for i in range(width * height):
r = pixels[i * 4]
g = pixels[i * 4 + 1]
b = pixels[i * 4 + 2]
values[i] = int(round((0.2126 * r + 0.7152 * g + 0.0722 * b) * 255))
return width, height, values
def write_png_rgb(path, width, height, pixels_rgb):
"""pixels_rgb: bytes-like length width*height*3, top-left origin."""
raw = bytearray()
stride = width * 3
for y in range(height):
raw.append(0) # filter none
raw.extend(pixels_rgb[y * stride:(y + 1) * stride])
compressed = zlib.compress(bytes(raw), 9)
def chunk(tag, data):
return struct.pack(">I", len(data)) + tag + data + struct.pack(">I", zlib.crc32(tag + data) & 0xFFFFFFFF)
png = b"\x89PNG\r\n\x1a\n"
png += chunk(b"IHDR", struct.pack(">IIBBBBB", width, height, 8, 2, 0, 0, 0))
png += chunk(b"IDAT", compressed)
png += chunk(b"IEND", b"")
path.write_bytes(png)
print("PBR_WRITE", path.name)
def height_to_normal(path_height, path_normal):
width, height, values = read_png_gray(path_height)
def sample(x, y):
return values[(y % height) * width + (x % width)]
strength = 2.0
out = bytearray(width * height * 3)
for y in range(height):
for x in range(width):
left = sample(x - 1, y)
right = sample(x + 1, y)
up = sample(x, y - 1)
down = sample(x, y + 1)
dx = (left - right) * strength / 255.0
dy = (up - down) * strength / 255.0
# Tangent-space normal, OpenGL convention (Y up).
nx, ny, nz = dx, dy, 1.0
length = math.sqrt(nx * nx + ny * ny + nz * nz) or 1.0
nx, ny, nz = nx / length, ny / length, nz / length
i = (y * width + x) * 3
out[i] = int(round((nx * 0.5 + 0.5) * 255))
out[i + 1] = int(round((ny * 0.5 + 0.5) * 255))
out[i + 2] = int(round((nz * 0.5 + 0.5) * 255))
write_png_rgb(path_normal, width, height, out)
def pack_orm(path_ao, path_rough, path_metal, path_orm):
_, _, ao = read_png_gray(path_ao)
width, height, rough = read_png_gray(path_rough)
_, _, metal = read_png_gray(path_metal)
out = bytearray(width * height * 3)
for i in range(width * height):
out[i * 3] = ao[i]
out[i * 3 + 1] = rough[i]
out[i * 3 + 2] = metal[i]
write_png_rgb(path_orm, width, height, out)
def save_set(name, builder):
scene, nodes, links, emit = new_scene("PBR_" + name)
height, albedo, roughness, metallic, ao = builder(nodes, links)
albedo_path = OUT / (name + "-albedo-2k.png")
height_path = OUT / (name + "-height-2k.png")
rough_path = OUT / (name + "-roughness-2k.png")
metal_path = OUT / (name + "-metallic-2k.png")
ao_path = OUT / (name + "-ao-2k.png")
render(scene, emit, albedo, albedo_path)
render(scene, emit, height, height_path)
render(scene, emit, roughness, rough_path)
render(scene, emit, metallic if not isinstance(metallic, (float, int)) else float(metallic), metal_path)
render(scene, emit, ao if not isinstance(ao, (float, int)) else float(ao), ao_path)
height_to_normal(height_path, OUT / (name + "-normal-2k.png"))
pack_orm(ao_path, rough_path, metal_path, OUT / (name + "-orm-2k.png"))
height_path.unlink()
rough_path.unlink()
metal_path.unlink()
ao_path.unlink()
print("PBR_BAKE_PASS", name)
# ---------------------------------------------------------------- sets
def build_limestone(nodes, links):
co, w = torus_uv(nodes, links, *isotropic(9.0, 0.8))
broad = noise(nodes, links, co, w, 2.2, 6, 0.7)
grain = noise(nodes, links, co, w, 11.0, 5, 0.62)
cracks = noise(nodes, links, co, w, 5.5, 8, 0.8)
height = math_node(nodes, links, "ADD",
math_node(nodes, links, "MULTIPLY", broad, 0.55),
math_node(nodes, links, "MULTIPLY", grain, 0.35))
height = math_node(nodes, links, "SUBTRACT", height, math_node(nodes, links, "MULTIPLY", cracks, 0.2))
albedo = ramp(nodes, links, height, [
(0.22, "6E675C"), (0.4, "8C8476"), (0.55, "A39A88"), (0.72, "BDB3A0"), (0.88, "D2C8B4")])
roughness = ramp(nodes, links, height, [(0.2, "F0F0F0"), (0.5, "D0D0D0"), (0.8, "B8B8B8")])
ao = ramp(nodes, links, height, [(0.15, "707070"), (0.45, "D0D0D0"), (0.8, "FFFFFF")])
return height, albedo, roughness, 0.0, ao
def build_soil(nodes, links):
co, w = torus_uv(nodes, links, *isotropic(7.0, 0.7))
broad = noise(nodes, links, co, w, 1.6, 5, 0.7)
grain = noise(nodes, links, co, w, 14.0, 4, 0.55)
pebbles = noise(nodes, links, co, w, 6.5, 6, 0.75)
height = math_node(nodes, links, "ADD",
math_node(nodes, links, "MULTIPLY", broad, 0.5),
math_node(nodes, links, "MULTIPLY", grain, 0.3))
height = math_node(nodes, links, "ADD", height, math_node(nodes, links, "MULTIPLY", pebbles, 0.2))
albedo = ramp(nodes, links, height, [
(0.2, "2A241C"), (0.4, "3C3428"), (0.58, "4A4030"), (0.75, "5C503C"), (0.9, "6E6048")])
roughness = ramp(nodes, links, height, [(0.2, "FAFAFA"), (0.6, "E8E8E8"), (0.9, "D8D8D8")])
ao = ramp(nodes, links, height, [(0.15, "808080"), (0.5, "D8D8D8"), (0.85, "FFFFFF")])
return height, albedo, roughness, 0.0, ao
def build_bark(nodes, links):
co, w = torus_uv(nodes, links, 10.0, 0.7)
grain = noise(nodes, links, co, w, 2.6, 7, 0.73)
ridges = noise(nodes, links, co, w, 7.5, 5, 0.8)
height = ramp(nodes, links, grain, [
(0.27, "121212"), (0.43, "242424"), (0.51, "8C8C8C"), (0.67, "CCCCCC")])
bump = math_node(nodes, links, "ADD", height, math_node(nodes, links, "MULTIPLY", ridges, 0.25))
albedo = ramp(nodes, links, height, [
(0.0, "302B23"), (0.18, "4B3C2A"), (0.51, "766041"), (0.74, "94855D"), (1.0, "B09A6D")])
roughness = ramp(nodes, links, ridges, [(0.2, "F5F5F5"), (0.7, "E0E0E0")])
ao = ramp(nodes, links, height, [(0.1, "606060"), (0.5, "C8C8C8"), (0.85, "FFFFFF")])
return bump, albedo, roughness, 0.0, ao
def build_leaf(nodes, links):
co, w = torus_uv(nodes, links, *isotropic(8.0, 0.8))
broad = noise(nodes, links, co, w, 1.8, 4, 0.65)
vein = noise(nodes, links, co, w, 9.0, 5, 0.7)
height = math_node(nodes, links, "ADD",
math_node(nodes, links, "MULTIPLY", broad, 0.6),
math_node(nodes, links, "MULTIPLY", vein, 0.4))
albedo = ramp(nodes, links, height, [
(0.2, "1E3224"), (0.4, "2F4A32"), (0.58, "456640"), (0.75, "5F8554"), (0.9, "7FA66A")])
roughness = ramp(nodes, links, height, [(0.2, "D8D8D8"), (0.7, "C0C0C0")])
ao = ramp(nodes, links, height, [(0.15, "707070"), (0.55, "D0D0D0"), (0.85, "FFFFFF")])
return height, albedo, roughness, 0.0, ao
def build_rock(nodes, links):
co, w = torus_uv(nodes, links, 6.0, 0.6)
strata = noise(nodes, links, co, w, 3.5, 7, 0.78)
grain = noise(nodes, links, co, w, 16.0, 3, 0.5)
height = math_node(nodes, links, "ADD",
math_node(nodes, links, "MULTIPLY", strata, 0.7),
math_node(nodes, links, "MULTIPLY", grain, 0.3))
albedo = ramp(nodes, links, height, [
(0.2, "3A3C40"), (0.4, "4A4C50"), (0.58, "5A5C60"), (0.75, "6A6C70"), (0.9, "7A7C80")])
roughness = ramp(nodes, links, height, [(0.2, "FAFAFA"), (0.7, "ECECEC")])
ao = ramp(nodes, links, height, [(0.12, "606060"), (0.5, "D0D0D0"), (0.85, "FFFFFF")])
return height, albedo, roughness, 0.0, ao
def build_water(nodes, links):
co, w = torus_uv(nodes, links, *isotropic(5.0, 0.5))
waves = noise(nodes, links, co, w, 3.2, 6, 0.62)
chop = noise(nodes, links, co, w, 9.5, 4, 0.55)
height = math_node(nodes, links, "ADD",
math_node(nodes, links, "MULTIPLY", waves, 0.65),
math_node(nodes, links, "MULTIPLY", chop, 0.35))
albedo = ramp(nodes, links, height, [(0.25, "0A2830"), (0.55, "123A42"), (0.8, "1A4A52")])
roughness = ramp(nodes, links, height, [(0.2, "3A3A3A"), (0.7, "555555")])
metallic = ramp(nodes, links, height, [(0.2, "202020"), (0.7, "303030")])
return height, albedo, roughness, metallic, 1.0
# ---------------------------------------------------------------- unit and armour families (U5.3)
def build_metal(nodes, links):
"""Hammered plate and mail: fine dents, a warm-to-cool steel grain, worn edges."""
co, w = torus_uv(nodes, links, *isotropic(8.0, 0.7))
dents = noise(nodes, links, co, w, 5.5, 6, 0.7)
grain = noise(nodes, links, co, w, 18.0, 3, 0.5)
scratches = noise(nodes, links, co, w, 11.0, 5, 0.8)
height = math_node(nodes, links, "ADD",
math_node(nodes, links, "MULTIPLY", dents, 0.6),
math_node(nodes, links, "MULTIPLY", grain, 0.25))
height = math_node(nodes, links, "SUBTRACT", height, math_node(nodes, links, "MULTIPLY", scratches, 0.15))
albedo = ramp(nodes, links, height, [
(0.2, "4A4E55"), (0.4, "6A6F78"), (0.58, "8A909A"), (0.75, "AAB0BA"), (0.9, "C8CDD4")])
roughness = ramp(nodes, links, height, [(0.2, "6A6A6A"), (0.5, "4A4A4A"), (0.8, "303030")])
ao = ramp(nodes, links, height, [(0.12, "707070"), (0.5, "D0D0D0"), (0.85, "FFFFFF")])
metallic = 0.95
return height, albedo, roughness, metallic, ao
def build_cloth(nodes, links):
"""Woven wool with a diagonal twill and soft fibre noise."""
co, w = torus_uv(nodes, links, *isotropic(12.0, 0.8))
twill = noise(nodes, links, co, w, 9.0, 4, 0.55)
fibre = noise(nodes, links, co, w, 22.0, 3, 0.5)
height = math_node(nodes, links, "ADD",
math_node(nodes, links, "MULTIPLY", twill, 0.65),
math_node(nodes, links, "MULTIPLY", fibre, 0.35))
albedo = ramp(nodes, links, height, [
(0.2, "3A342C"), (0.4, "524A3E"), (0.58, "6A6050"), (0.75, "827662"), (0.9, "9A8C76")])
roughness = ramp(nodes, links, height, [(0.2, "F5F5F5"), (0.7, "E2E2E2")])
ao = ramp(nodes, links, height, [(0.15, "787878"), (0.55, "D4D4D4"), (0.85, "FFFFFF")])
metallic = 0.0
return height, albedo, roughness, metallic, ao
def build_hide(nodes, links):
"""Thick beast hide: pebbled grain, scars, slightly waxy sheen."""
co, w = torus_uv(nodes, links, *isotropic(6.5, 0.6))
pebble = noise(nodes, links, co, w, 7.5, 6, 0.72)
scars = noise(nodes, links, co, w, 3.0, 5, 0.8)
grain = noise(nodes, links, co, w, 15.0, 3, 0.5)
height = math_node(nodes, links, "ADD",
math_node(nodes, links, "MULTIPLY", pebble, 0.55),
math_node(nodes, links, "MULTIPLY", grain, 0.25))
height = math_node(nodes, links, "SUBTRACT", height, math_node(nodes, links, "MULTIPLY", scars, 0.2))
albedo = ramp(nodes, links, height, [
(0.2, "1E2026"), (0.4, "2C2F36"), (0.58, "3A3E48"), (0.75, "4A4E58"), (0.9, "5A5E68")])
roughness = ramp(nodes, links, height, [(0.2, "E8E8E8"), (0.7, "D0D0D0")])
ao = ramp(nodes, links, height, [(0.12, "686868"), (0.5, "D0D0D0"), (0.85, "FFFFFF")])
metallic = 0.0
return height, albedo, roughness, metallic, ao
def build_bone(nodes, links):
"""Carved bone and ivory: porous grain, warm polish on the ridges."""
co, w = torus_uv(nodes, links, *isotropic(7.0, 0.65))
pores = noise(nodes, links, co, w, 10.0, 6, 0.7)
grain = noise(nodes, links, co, w, 3.5, 5, 0.6)
height = math_node(nodes, links, "ADD",
math_node(nodes, links, "MULTIPLY", pores, 0.45),
math_node(nodes, links, "MULTIPLY", grain, 0.55))
albedo = ramp(nodes, links, height, [
(0.2, "8A8474"), (0.4, "A39C88"), (0.58, "BDB49C"), (0.75, "D2C8AE"), (0.9, "E6DCC2")])
roughness = ramp(nodes, links, height, [(0.2, "B0B0B0"), (0.7, "D8D8D8")])
ao = ramp(nodes, links, height, [(0.15, "787878"), (0.55, "D8D8D8"), (0.85, "FFFFFF")])
metallic = 0.0
return height, albedo, roughness, metallic, ao
def build_wood(nodes, links):
"""Tool-haft and shield plank: tight rings, a worn varnish on the high spots."""
co, w = torus_uv(nodes, links, 9.5, 0.55)
rings = noise(nodes, links, co, w, 4.5, 7, 0.78)
grain = noise(nodes, links, co, w, 16.0, 3, 0.5)
height = math_node(nodes, links, "ADD",
math_node(nodes, links, "MULTIPLY", rings, 0.7),
math_node(nodes, links, "MULTIPLY", grain, 0.3))
albedo = ramp(nodes, links, height, [
(0.2, "3A2A1C"), (0.4, "52402C"), (0.58, "6A563C"), (0.75, "826C4C"), (0.9, "9A825C")])
roughness = ramp(nodes, links, height, [(0.2, "E0E0E0"), (0.7, "C8C8C8")])
ao = ramp(nodes, links, height, [(0.12, "606060"), (0.5, "D0D0D0"), (0.85, "FFFFFF")])
metallic = 0.0
return height, albedo, roughness, metallic, ao
SETS = {
"limestone": build_limestone,
"soil": build_soil,
"bark": build_bark,
"leaf": build_leaf,
"rock": build_rock,
"water": build_water,
"metal": build_metal,
"cloth": build_cloth,
"hide": build_hide,
"bone": build_bone,
"wood": build_wood,
}
def main():
options = args()
wanted = list(SETS) if options["sets"] == "all" else [s.strip() for s in options["sets"].split(",") if s.strip()]
OUT.mkdir(parents=True, exist_ok=True)
failed = []
for name in wanted:
if name not in SETS:
failed.append(name + ": unknown set")
continue
try:
save_set(name, SETS[name])
except Exception as error:
failed.append(name + ": " + repr(error))
if failed:
for line in failed:
print("PBR_BAKE_FAIL", line)
raise SystemExit(1)
print("PBR_BAKE_PASS all", ",".join(wanted))
if __name__ == "__main__":
main()