BlueGit

golden-age paperdoll-renderer

Files

paperdoll-renderer / scripts / make_fbx_equipment_fixture.py

make_fbx_equipment_fixture.py

make_fbx_equipment_fixture.py 3.2 KiB Download Raw
1#!/usr/bin/env python3
2"""Generate the CC0 rigid FBX equipment fixture with Blender.
3
4Writes tests/fixtures/equipment/sword-fbx.fbx: a blade with the CC0 checker
5texture embedded in the FBX and a plain-colour guard, and
6sword-fbx-external.fbx: the same model referencing checker.png next to it
7instead of embedding it. The files are exported
8Z-up in centimetres so the importer has to perform the axis/unit
9normalisation itself. Re-run when needed:
10
11 blender --background --factory-startup --python scripts/make_fbx_equipment_fixture.py
12
13Blender is only a fixture tool; the application never calls Blender.
14"""
15
16from pathlib import Path
17
18import bpy
19
20ROOT = Path(__file__).resolve().parents[1]
21FIXTURES = ROOT / "tests" / "fixtures" / "equipment"
22TARGET = FIXTURES / "sword-fbx.fbx"
23EXTERNAL = FIXTURES / "sword-fbx-external.fbx"
24
25
26def box(name, x0, x1, y0, y1, z0, z1, material):
27 verts = [
28 (x0, y0, z0), (x1, y0, z0), (x1, y1, z0), (x0, y1, z0),
29 (x0, y0, z1), (x1, y0, z1), (x1, y1, z1), (x0, y1, z1),
30 ]
31 faces = [
32 (0, 3, 2, 1), (4, 5, 6, 7), (0, 1, 5, 4),
33 (1, 2, 6, 5), (2, 3, 7, 6), (3, 0, 4, 7),
34 ]
35 mesh = bpy.data.meshes.new(name)
36 mesh.from_pydata(verts, [], faces)
37 uv = mesh.uv_layers.new(name="UVMap")
38 corners = [(0, 0), (1, 0), (1, 1), (0, 1)]
39 for poly in mesh.polygons:
40 for k, loop in enumerate(poly.loop_indices):
41 uv.data[loop].uv = corners[k]
42 mesh.materials.append(material)
43 mesh.update()
44 obj = bpy.data.objects.new(name, mesh)
45 bpy.context.scene.collection.objects.link(obj)
46 return obj
47
48
49def main() -> None:
50 bpy.ops.wm.read_factory_settings(use_empty=True)
51 scene = bpy.context.scene
52 scene.unit_settings.system = "METRIC"
53 scene.unit_settings.scale_length = 1.0
54
55 image = bpy.data.images.load(str(FIXTURES / "checker.png"))
56 image.pack()
57 blade_mat = bpy.data.materials.new("blade_checker")
58 blade_mat.use_nodes = True
59 bsdf = blade_mat.node_tree.nodes["Principled BSDF"]
60 tex = blade_mat.node_tree.nodes.new("ShaderNodeTexImage")
61 tex.image = image
62 blade_mat.node_tree.links.new(tex.outputs["Color"], bsdf.inputs["Base Color"])
63
64 guard_mat = bpy.data.materials.new("guard_brass")
65 guard_mat.use_nodes = True
66 guard_mat.node_tree.nodes["Principled BSDF"].inputs["Base Color"].default_value = (
67 0.75, 0.55, 0.2, 1.0,
68 )
69
70 # Blender space: Z up. Blade 0.58 m long, guard at the bottom.
71 box("blade", -0.03, 0.03, -0.01, 0.01, 0.03, 0.58, blade_mat)
72 box("guard", -0.09, 0.09, -0.015, 0.015, 0.0, 0.03, guard_mat)
73
74 common = dict(
75 use_selection=False,
76 object_types={"MESH"},
77 axis_forward="Y",
78 axis_up="Z",
79 global_scale=1.0,
80 apply_unit_scale=True,
81 apply_scale_options="FBX_SCALE_NONE",
82 bake_anim=False,
83 add_leaf_bones=False,
84 )
85 bpy.ops.export_scene.fbx(
86 filepath=str(TARGET), path_mode="COPY", embed_textures=True, **common
87 )
88 image.unpack(method="REMOVE")
89 image.filepath = str(FIXTURES / "checker.png")
90 bpy.ops.export_scene.fbx(
91 filepath=str(EXTERNAL), path_mode="RELATIVE", embed_textures=False, **common
92 )
93 print("wrote", EXTERNAL)
94 print("wrote", TARGET)
95
96
97if __name__ == "__main__":
98 main()