refactor(tools): one FBX export preset instead of six copies
The bpy.ops.export_scene.fbx argument block was duplicated in six scripts (hero, block frontage, north buildings, site, scale test, MPFB character) and all six carried mesh_smooth_type="OFF", which drops smoothing groups and made every Unreal import warn and every round part faceted. Tools/fbx_export_preset.py now owns the presets: - preset_kwargs(preset, selection, **overrides) is bpy-free and unit-tested; - export_static()/export_character() are thin wrappers that import bpy lazily; - ue_metric_v1 and ue_character_v1 keep the recorded scale contract (FBX_SCALE_NONE, apply_unit_scale, -Y forward / Z up) and use FACE smoothing. Callers switched: hero_facade_build, export_block_frontage, export_north_buildings, scale_test_export, mpfb_character_export, Art/blender/site/export_site_fbx. Verified by re-exporting every asset through the preset and re-importing all of them: 0 smoothing warnings on site, frontage and north buildings; packaging and -qaselftest all PASS (walk, transect 0 holes, wall probe 0/35, facade probe 0/9). 49 tool tests pass (9 new for the preset contract).
This commit is contained in:
1 parent
160e9144f9
commit
286d9f6479
49 files changed
+283
-158
No files matched your search
@@ -0,0 +1,94 @@
|
||||
#!/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.
|
||||
_CHARACTER = dict(
|
||||
_METRIC,
|
||||
object_types={"ARMATURE", "MESH"},
|
||||
add_leaf_bones=False,
|
||||
path_mode="COPY",
|
||||
embed_textures=False,
|
||||
)
|
||||
|
||||
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)
|
||||
Reference in new issue
Block a user