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.
98 lines
3.7 KiB
Python
98 lines
3.7 KiB
Python
"""Tests for Tools/fbx_export_preset.py.
|
|
|
|
The preset module keeps its argument set in plain dicts and builds it in
|
|
`preset_kwargs()`, so the export contract can be checked without starting
|
|
Blender. Only `export_static`/`export_character` import `bpy`.
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
|
|
_TOOLS = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
sys.path.insert(0, _TOOLS)
|
|
for _sub in ("review", "gen", "unreal", "blender", "input"):
|
|
sys.path.insert(0, os.path.join(_TOOLS, _sub))
|
|
|
|
|
|
import os
|
|
import sys
|
|
import unittest
|
|
|
|
ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", ".."))
|
|
sys.path.insert(0, os.path.join(ROOT, "Tools"))
|
|
|
|
import fbx_export_preset as preset # noqa: E402
|
|
|
|
|
|
class MetricPresetTest(unittest.TestCase):
|
|
def test_scale_and_axis_contract(self):
|
|
# These three are the project's recorded conversion contract: metres ->
|
|
# centimetres by a factor of exactly 100, east +X, north -Y, up +Z.
|
|
kwargs = preset.preset_kwargs()
|
|
self.assertTrue(kwargs["apply_unit_scale"])
|
|
self.assertEqual(kwargs["apply_scale_options"], "FBX_SCALE_NONE")
|
|
self.assertEqual(kwargs["axis_forward"], "-Y")
|
|
self.assertEqual(kwargs["axis_up"], "Z")
|
|
|
|
def test_smoothing_groups_are_exported(self):
|
|
# "OFF" (the pre-refactor value) drops smoothing groups and every Unreal
|
|
# import warns about it, with round parts faceted.
|
|
self.assertEqual(preset.preset_kwargs()["mesh_smooth_type"], "FACE")
|
|
|
|
def test_selection_flag(self):
|
|
self.assertFalse(preset.preset_kwargs()["use_selection"])
|
|
self.assertTrue(preset.preset_kwargs(selection=True)["use_selection"])
|
|
|
|
def test_object_types_are_meshes_only(self):
|
|
self.assertEqual(preset.preset_kwargs()["object_types"], {"MESH"})
|
|
|
|
|
|
class CharacterPresetTest(unittest.TestCase):
|
|
def test_character_exports_armature_and_mesh(self):
|
|
kwargs = preset.preset_kwargs("ue_character_v1")
|
|
self.assertEqual(kwargs["object_types"], {"ARMATURE", "MESH"})
|
|
self.assertFalse(kwargs["add_leaf_bones"])
|
|
self.assertEqual(kwargs["path_mode"], "COPY")
|
|
self.assertFalse(kwargs["embed_textures"])
|
|
|
|
def test_character_keeps_the_scale_contract(self):
|
|
kwargs = preset.preset_kwargs("ue_character_v1")
|
|
self.assertEqual(kwargs["apply_scale_options"], "FBX_SCALE_NONE")
|
|
self.assertEqual(kwargs["mesh_smooth_type"], "FACE")
|
|
|
|
|
|
class CharacterAnimationTest(unittest.TestCase):
|
|
"""The animation flags are the difference between a clip and a silent bind pose."""
|
|
|
|
def test_bakes_animation(self):
|
|
kwargs = preset.preset_kwargs("ue_character_v1")
|
|
self.assertTrue(kwargs["bake_anim"], "without bake_anim Unreal imports no AnimSequence")
|
|
self.assertTrue(kwargs["bake_anim_use_all_actions"])
|
|
self.assertFalse(kwargs["bake_anim_use_nla_strips"])
|
|
|
|
def test_does_not_simplify_authored_keys(self):
|
|
kwargs = preset.preset_kwargs("ue_character_v1")
|
|
self.assertEqual(kwargs["bake_anim_simplify_factor"], 0.0)
|
|
|
|
def test_static_preset_stays_animation_free(self):
|
|
kwargs = preset.preset_kwargs("ue_metric_v1")
|
|
self.assertNotIn("bake_anim", kwargs)
|
|
|
|
|
|
class OverrideTest(unittest.TestCase):
|
|
def test_overrides_win(self):
|
|
kwargs = preset.preset_kwargs(mesh_smooth_type="OFF")
|
|
self.assertEqual(kwargs["mesh_smooth_type"], "OFF")
|
|
|
|
def test_unknown_preset_raises(self):
|
|
with self.assertRaises(KeyError):
|
|
preset.preset_kwargs("ue_imperial_v9")
|
|
|
|
def test_filepath_must_not_be_an_override(self):
|
|
with self.assertRaises(ValueError):
|
|
preset.preset_kwargs(filepath="/tmp/x.fbx")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|