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,188 @@
|
||||
# Creates the Enhanced Input assets the first-person character loads:
|
||||
# /Game/Input/IA_Move Axis2D, WASD + left stick
|
||||
# /Game/Input/IA_Look Axis2D, mouse XY + right stick
|
||||
# /Game/Input/IA_Jump axis1d, Space / gamepad face button
|
||||
# /Game/Input/IMC_Default InputMappingContext holding the three mappings
|
||||
#
|
||||
# Run headless (imports/dirty packages are saved before exit):
|
||||
# <engine>/Engine/Binaries/Linux/UnrealEditor-Cmd <uproject> -run=pythonscript \
|
||||
# -script=<repo>/Tools/unreal/ue_create_input_assets.py -nullrhi -unattended -nopause \
|
||||
# -stdout -FullStdOutLogOutput
|
||||
#
|
||||
# The result goes to /tmp/gigline/input-assets.txt because 5.8's pythonscript
|
||||
# commandlet swallows print() output with -stdout.
|
||||
#
|
||||
# Mapping choices:
|
||||
# - Look stays unit-free (no scalar modifier): the character applies its own
|
||||
# degrees-per-second, and a modifier here would double-scale.
|
||||
# - Move is a 2D axis: W = +Y, S = -Y, D = +X, A = -X via Negate on the Y axis.
|
||||
# For keyboard keys the Negate modifier's Y axis maps to the axis's Y.
|
||||
|
||||
import unreal
|
||||
|
||||
ASSET_DIR = "/Game/Input"
|
||||
_KEEP_ALIVE = []
|
||||
|
||||
|
||||
def negate_y_modifier():
|
||||
"""A negate-by-Y modifier, built with its Y flag set and X/Z cleared.
|
||||
|
||||
Returns the object plus its falsy reference; constructing the default
|
||||
modifier gives all axes true, so each flag is set explicitly. The caller owns
|
||||
keep-alive.
|
||||
"""
|
||||
neg = unreal.InputModifierNegate()
|
||||
neg.set_editor_property("x", False)
|
||||
neg.set_editor_property("y", True)
|
||||
neg.set_editor_property("z", False)
|
||||
return neg
|
||||
RESULT = "/tmp/gigline/input-assets.txt"
|
||||
lines = []
|
||||
|
||||
|
||||
def log(msg):
|
||||
lines.append(str(msg))
|
||||
unreal.log(str(msg))
|
||||
|
||||
|
||||
def fresh(name, cls, factory):
|
||||
"""Load the asset if it exists, otherwise create it.
|
||||
|
||||
Deleting then recreating in the same commandlet run fails: create_asset
|
||||
returns None for a path deleted earlier in the process. Overwriting in place
|
||||
is also what keeps the assets' identities stable across reruns.
|
||||
"""
|
||||
path = f"{ASSET_DIR}/{name}"
|
||||
existing = unreal.EditorAssetLibrary.load_asset(path)
|
||||
if existing is not None:
|
||||
return existing
|
||||
return unreal.AssetToolsHelpers.get_asset_tools().create_asset(
|
||||
name, ASSET_DIR, cls, factory())
|
||||
|
||||
|
||||
def save(asset):
|
||||
unreal.EditorAssetLibrary.save_loaded_asset(asset, only_if_is_dirty=False)
|
||||
|
||||
|
||||
def make_key(name):
|
||||
"""Build an FKey by name.
|
||||
|
||||
`unreal.InputCoreTypes` is not exposed to Python in 5.8 and `unreal.Key` does
|
||||
not accept a constructor argument, but the struct's `import_text` parses the
|
||||
key's export text, which is just its name.
|
||||
"""
|
||||
key = unreal.Key()
|
||||
key.import_text(name)
|
||||
return key
|
||||
|
||||
|
||||
def build():
|
||||
if not unreal.EditorAssetLibrary.does_directory_exist(ASSET_DIR):
|
||||
unreal.EditorAssetLibrary.make_directory(ASSET_DIR)
|
||||
|
||||
# --- actions ---------------------------------------------------------
|
||||
# Move keys are one-dimensional: W/S carry forward/back on the value's single
|
||||
# component and D/A carry strafe, combined by the character. A 2D value would
|
||||
# need per-key axis modifiers, and modifiers do not survive the save path here.
|
||||
ia_move = fresh("IA_Move", unreal.InputAction, unreal.InputAction_Factory)
|
||||
# Boolean (pressed = 1), not AXIS1D: the C++ handler reads Get<bool>() and
|
||||
# moves at full walk speed. An axis action from a keyboard key can deliver 0
|
||||
# depending on the trigger, which showed up as "character stands, W does not
|
||||
# move" in QA. WASD are four separate per-direction actions below.
|
||||
ia_move.set_editor_property("value_type", unreal.InputActionValueType.BOOLEAN)
|
||||
save(ia_move)
|
||||
|
||||
ia_look = fresh("IA_Look", unreal.InputAction, unreal.InputAction_Factory)
|
||||
ia_look.set_editor_property("value_type", unreal.InputActionValueType.AXIS2D)
|
||||
save(ia_look)
|
||||
|
||||
ia_jump = fresh("IA_Jump", unreal.InputAction, unreal.InputAction_Factory)
|
||||
ia_jump.set_editor_property("value_type", unreal.InputActionValueType.BOOLEAN)
|
||||
save(ia_jump)
|
||||
|
||||
# --- mapping context -------------------------------------------------
|
||||
imc = fresh("IMC_Default", unreal.InputMappingContext, unreal.InputMappingContext_Factory)
|
||||
|
||||
# Nine entries, one per key. No modifiers: the character negates S and A in
|
||||
# C++ instead, because a modifier object attached to a mapping struct
|
||||
# serialises as None on save in this build (the struct's object array does not
|
||||
# keep the modifier alive across the save path). One less moving part, and the
|
||||
# negation is visible where it is used.
|
||||
# Two actions feed the one-dimensional move: IA_Move for forward/back, and a
|
||||
# second action for strafe, both reporting on the value's single component.
|
||||
ia_strafe = fresh("IA_Strafe", unreal.InputAction, unreal.InputAction_Factory)
|
||||
ia_strafe.set_editor_property("value_type", unreal.InputActionValueType.BOOLEAN)
|
||||
save(ia_strafe)
|
||||
|
||||
ia_back = fresh("IA_MoveBack", unreal.InputAction, unreal.InputAction_Factory)
|
||||
ia_back.set_editor_property("value_type", unreal.InputActionValueType.BOOLEAN)
|
||||
save(ia_back)
|
||||
|
||||
ia_left = fresh("IA_StrafeLeft", unreal.InputAction, unreal.InputAction_Factory)
|
||||
ia_left.set_editor_property("value_type", unreal.InputActionValueType.BOOLEAN)
|
||||
save(ia_left)
|
||||
|
||||
# Reset and pause are boolean: one press, no accumulator.
|
||||
ia_reset = fresh("IA_Reset", unreal.InputAction, unreal.InputAction_Factory)
|
||||
ia_reset.set_editor_property("value_type", unreal.InputActionValueType.BOOLEAN)
|
||||
save(ia_reset)
|
||||
|
||||
ia_debug = fresh("IA_ToggleDebug", unreal.InputAction, unreal.InputAction_Factory)
|
||||
ia_debug.set_editor_property("value_type", unreal.InputActionValueType.BOOLEAN)
|
||||
save(ia_debug)
|
||||
|
||||
ia_cam = fresh("IA_ToggleCamera", unreal.InputAction, unreal.InputAction_Factory)
|
||||
ia_cam.set_editor_property("value_type", unreal.InputActionValueType.BOOLEAN)
|
||||
save(ia_cam)
|
||||
|
||||
ia_pause = fresh("IA_Pause", unreal.InputAction, unreal.InputAction_Factory)
|
||||
ia_pause.set_editor_property("value_type", unreal.InputActionValueType.BOOLEAN)
|
||||
save(ia_pause)
|
||||
|
||||
mapping_table = [
|
||||
(ia_move, "W"), (ia_back, "S"), (ia_strafe, "D"), (ia_left, "A"),
|
||||
(ia_move, "Gamepad_LeftY"), (ia_strafe, "Gamepad_LeftX"),
|
||||
(ia_look, "MouseX"), (ia_look, "MouseY"), (ia_look, "Gamepad_Right2D"),
|
||||
(ia_jump, "SpaceBar"), (ia_jump, "Gamepad_FaceButton_Bottom"),
|
||||
(ia_reset, "R"), (ia_pause, "Escape"), (ia_cam, "V"), (ia_debug, "F3"),
|
||||
]
|
||||
|
||||
mappings = []
|
||||
for action, key_name in mapping_table:
|
||||
try:
|
||||
entry = unreal.EnhancedActionKeyMapping()
|
||||
entry.set_editor_property("action", action)
|
||||
entry.set_editor_property("key", make_key(key_name))
|
||||
mappings.append(entry)
|
||||
except Exception as exc: # noqa: BLE001
|
||||
log(f"skip {action.get_name()}/{key_name}: {exc}")
|
||||
|
||||
# UE 5.8: the runtime reads DefaultKeyMappings.Mappings (FInputMappingContextMappingData),
|
||||
# NOT the deprecated 'mappings' array on the context. The deprecated array is
|
||||
# what editor UI shows and what the old script wrote, so the cooked game got 0
|
||||
# runtime mappings (key->action lookup failed -> no input). Populate the new
|
||||
# struct field that the runtime actually consumes.
|
||||
default_mapping_data = imc.get_editor_property("default_key_mappings")
|
||||
if default_mapping_data is None:
|
||||
default_mapping_data = unreal.InputMappingContextMappingData()
|
||||
default_mapping_data.set_editor_property("mappings", mappings)
|
||||
# Mirror into the deprecated array so the editor UI and any legacy reader agree.
|
||||
imc.set_editor_property("mappings", mappings)
|
||||
imc.set_editor_property("default_key_mappings", default_mapping_data)
|
||||
save(imc)
|
||||
|
||||
log(f"IA_Move={ia_move.get_path_name()} IA_Look={ia_look.get_path_name()} "
|
||||
f"IA_Jump={ia_jump.get_path_name()}")
|
||||
log(f"IMC_Default={imc.get_path_name()} mappings={len(mappings)}")
|
||||
log("assets=" + ",".join(unreal.EditorAssetLibrary.list_assets(ASSET_DIR)))
|
||||
|
||||
|
||||
try:
|
||||
build()
|
||||
except Exception as exc: # noqa: BLE001 - surface the failure in the result file
|
||||
import traceback
|
||||
log(f"FAILED: {type(exc).__name__}: {exc}")
|
||||
log(traceback.format_exc())
|
||||
|
||||
with open(RESULT, "w") as handle:
|
||||
handle.write("\n".join(lines))
|
||||
Reference in new issue
Block a user