refactor(tools): group the sixty scripts by what they talk to
One directory with sixty scripts stopped being navigable, and the grouping is also the sandbox boundary:
Tools/unreal/ commandlets and the two map builders, with ue_common.py and ue_placement.py as the
shared imports (they need the engine)
Tools/blender/ builders, exporters, the FBX preset, the MPFB character pipeline, blender_screenshots
(they need the Blender Flatpak)
Tools/review/ the visual loop: render_screenshots.sh, judge_screenshot.py, hero_compare.py,
build_comparison_md.py, e2e_asset_check.py, hero_iterate.sh, visual_loop.py,
visual_targets.json
Tools/gen/ image and texture generation: the ComfyUI driver, deterministic texture writers,
reference fetching
Tools/input/ OS-level input injection
root _common.py, check-lfs.sh, addon_compat_probe.py, sibling_asset_audit.py, tests/
Every moved script now resolves the repository root as ../.. and puts the tools root plus its siblings on
sys.path, so 'import ue_common' still works from unreal/. The test suite bootstrap does the same for the five
folders. render_screenshots.sh takes the folder as a third argument to run_cmdlet, and the e2e driver,
hero_iterate.sh and the comparison generator were repointed.
51 README rows now link to their new location, the README gains a Layout section naming each folder's
sandbox, and the architecture notes and Blender README follow the moves.
Verified: 108 tests pass, prettier is clean, judge_screenshot.py and hero_compare.py run from their new
homes, and the e2e run was aborted mid-way (it had been rebuilding an empty DemoMap for 15 minutes) and is
being rerun against the new layout.
This commit is contained in:
1 parent
f681d83736
commit
4d27109668
168 files changed
+799
-407
No files matched your search
@@ -0,0 +1,107 @@
|
||||
#!/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)
|
||||
Reference in new issue
Block a user