#!/usr/bin/env python3 """Generate the CC0 rigid FBX equipment fixture with Blender. Writes tests/fixtures/equipment/sword-fbx.fbx: a blade with the CC0 checker texture embedded in the FBX and a plain-colour guard, and sword-fbx-external.fbx: the same model referencing checker.png next to it instead of embedding it. The files are exported Z-up in centimetres so the importer has to perform the axis/unit normalisation itself. Re-run when needed: blender --background --factory-startup --python scripts/make_fbx_equipment_fixture.py Blender is only a fixture tool; the application never calls Blender. """ from pathlib import Path import bpy ROOT = Path(__file__).resolve().parents[1] FIXTURES = ROOT / "tests" / "fixtures" / "equipment" TARGET = FIXTURES / "sword-fbx.fbx" EXTERNAL = FIXTURES / "sword-fbx-external.fbx" def box(name, x0, x1, y0, y1, z0, z1, material): verts = [ (x0, y0, z0), (x1, y0, z0), (x1, y1, z0), (x0, y1, z0), (x0, y0, z1), (x1, y0, z1), (x1, y1, z1), (x0, y1, z1), ] faces = [ (0, 3, 2, 1), (4, 5, 6, 7), (0, 1, 5, 4), (1, 2, 6, 5), (2, 3, 7, 6), (3, 0, 4, 7), ] mesh = bpy.data.meshes.new(name) mesh.from_pydata(verts, [], faces) uv = mesh.uv_layers.new(name="UVMap") corners = [(0, 0), (1, 0), (1, 1), (0, 1)] for poly in mesh.polygons: for k, loop in enumerate(poly.loop_indices): uv.data[loop].uv = corners[k] mesh.materials.append(material) mesh.update() obj = bpy.data.objects.new(name, mesh) bpy.context.scene.collection.objects.link(obj) return obj def main() -> None: bpy.ops.wm.read_factory_settings(use_empty=True) scene = bpy.context.scene scene.unit_settings.system = "METRIC" scene.unit_settings.scale_length = 1.0 image = bpy.data.images.load(str(FIXTURES / "checker.png")) image.pack() blade_mat = bpy.data.materials.new("blade_checker") blade_mat.use_nodes = True bsdf = blade_mat.node_tree.nodes["Principled BSDF"] tex = blade_mat.node_tree.nodes.new("ShaderNodeTexImage") tex.image = image blade_mat.node_tree.links.new(tex.outputs["Color"], bsdf.inputs["Base Color"]) guard_mat = bpy.data.materials.new("guard_brass") guard_mat.use_nodes = True guard_mat.node_tree.nodes["Principled BSDF"].inputs["Base Color"].default_value = ( 0.75, 0.55, 0.2, 1.0, ) # Blender space: Z up. Blade 0.58 m long, guard at the bottom. box("blade", -0.03, 0.03, -0.01, 0.01, 0.03, 0.58, blade_mat) box("guard", -0.09, 0.09, -0.015, 0.015, 0.0, 0.03, guard_mat) common = dict( use_selection=False, object_types={"MESH"}, axis_forward="Y", axis_up="Z", global_scale=1.0, apply_unit_scale=True, apply_scale_options="FBX_SCALE_NONE", bake_anim=False, add_leaf_bones=False, ) bpy.ops.export_scene.fbx( filepath=str(TARGET), path_mode="COPY", embed_textures=True, **common ) image.unpack(method="REMOVE") image.filepath = str(FIXTURES / "checker.png") bpy.ops.export_scene.fbx( filepath=str(EXTERNAL), path_mode="RELATIVE", embed_textures=False, **common ) print("wrote", EXTERNAL) print("wrote", TARGET) if __name__ == "__main__": main()