Files
gig-line/Tools/blender/fbx_export_preset.py
T
zwitschi 911a7059ae docs(vehicle): revert the axis fix; the reverse is in the Chaos solver
The axis_forward:X / Blender-Z axle fix changed the reference pose but not the drive
(-2737 cm/s), and axis_forward:X also alters the shared character preset. Revert it.
The Chaos solver drives the wheel about the vehicle's Y (not the bone), and the
drive force is positive yet the car moves backward - the inversion is in the engine
torque / drive-force sign. Next: GetEngineTorque() sign or a SetupVehicle override.
2026-11-10 19:45:17 +01:00

108 lines
4.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""The one FBX export preset for this project ("ue_metric_v1"/"ue_character_v1").
Before this module the same `bpy.ops.export_scene.fbx(...)` argument block was
copied into six scripts (hero, frontage, north buildings, site, scale test, MPFB
character). They had already drifted only by accident, and every one of them
carried `mesh_smooth_type="OFF"` — which is why Unreal logged "No smoothing group
information was found for this mesh" on every import and round parts rendered
faceted. One preset, one place to fix that.
Use from inside Blender:
import sys, os
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "Tools"))
import fbx_export_preset as preset
preset.export_static(path) # whole scene, MESH only
preset.export_static(path, selection=True) # selected objects only
preset.export_character(path) # armature + mesh, character flags
`preset_kwargs()` is pure (no `bpy`), so the argument set can be unit-tested
without Blender — see Tools/tests/test_fbx_export_preset.py.
Why these values:
* `FBX_SCALE_NONE` + `apply_unit_scale` keeps Blender metres → Unreal centimetres
at exactly ×100, verified by the three-cube scale test (1 m → 100.00 uu).
* `axis_forward="-Y"`, `axis_up="Z"` is the project's recorded mapping: east → +X,
north → −Y, up → +Z.
* `mesh_smooth_type="FACE"` exports smoothing groups. `"OFF"` (the old value)
drops them, so Unreal rebuilds flat normals and every cylinder is faceted.
* `use_mesh_modifiers=True` bakes the bevel/modifier stack at export; the source
`.blend` stays non-destructive.
"""
from __future__ import annotations
# Shared by every mesh export. Values are the recorded ue_metric_v1 preset.
_METRIC = {
"object_types": {"MESH"},
"apply_unit_scale": True,
"apply_scale_options": "FBX_SCALE_NONE",
"axis_forward": "-Y",
"axis_up": "Z",
"use_space_transform": True,
"use_mesh_modifiers": True,
"mesh_smooth_type": "FACE",
"path_mode": "AUTO",
"bake_space_transform": False,
}
# Character exports differ only in the object set and the armature/path flags.
#
# Animation export (added for the 0.2.0 animation set): without `bake_anim` the
# exporter silently writes a static bind pose even when the armature carries
# actions, and Unreal then imports a character with no AnimSequence and no error.
# `bake_anim_use_all_actions` exports every action as its own clip; NLA strips are
# off because the project does not author NLA. `bake_anim_simplify_factor=0.0`
# keeps the sampled keys exactly as authored - the clips are short, so the size
# cost is irrelevant and a simplified curve would drift from the source poses.
_CHARACTER = dict(
_METRIC,
object_types={"ARMATURE", "MESH"},
add_leaf_bones=False,
path_mode="COPY",
embed_textures=False,
bake_anim=True,
bake_anim_use_all_actions=True,
bake_anim_use_nla_strips=False,
bake_anim_force_startend_keying=True,
bake_anim_simplify_factor=0.0,
)
PRESETS = {"ue_metric_v1": _METRIC, "ue_character_v1": _CHARACTER}
def preset_kwargs(preset: str = "ue_metric_v1", selection: bool = False, **overrides) -> dict:
"""Return the `bpy.ops.export_scene.fbx` argument dict (no `bpy` needed).
`preset` selects the base set, `selection` decides whether only selected
objects are exported, and `overrides` wins last for the rare special case.
"""
if preset not in PRESETS:
raise KeyError(f"unknown preset {preset!r}; known: {sorted(PRESETS)}")
kwargs = dict(PRESETS[preset])
kwargs["use_selection"] = bool(selection)
kwargs.update(overrides)
if "filepath" in kwargs:
raise ValueError("pass the path to export_static/export_character, not as an override")
return kwargs
def _export(path: str, preset: str, selection: bool, overrides: dict) -> str:
import bpy # imported here so `preset_kwargs` stays usable without Blender
kwargs = preset_kwargs(preset, selection=selection, **overrides)
bpy.ops.export_scene.fbx(filepath=path, **kwargs)
return path
def export_static(path: str, selection: bool = False, **overrides) -> str:
"""Export meshes with the ue_metric_v1 preset. Returns the path."""
return _export(path, "ue_metric_v1", selection, overrides)
def export_character(path: str, selection: bool = False, **overrides) -> str:
"""Export armature + mesh with the ue_character_v1 preset. Returns the path."""
return _export(path, "ue_character_v1", selection, overrides)