bootstrap: finish project bootstrap — scale test, MPFB character, build docs

- Static-mesh scale test passes: 1 m = 100.00 cm, origin preserved, axis
  mapping east→+X / north→−Y / up→+Z (named presets ue_metric_v1 both sides).
- MPFB 2.0.18 acceptance passes: generated human + standard 137-bone rig
  with weights + v2 skin baked to 1024² diffuse; imports as SkeletalMesh
  with Skeleton, Material, and Texture2D in /Game/Test/Character.
- MPFB headless requires XDG_CONFIG_HOME at the Flatpak config tree;
  documented in Art/blender/README.md, probe in Tools/addon_compat_probe.py.
- Docs/build.md completed: fresh-checkout build, packaging, headless checks.
- Docs/setup.md reconciled with reality; ROADMAP/bootstrap closed out.
This commit is contained in:
zwitschi committed 2026-11-07 09:29:11 +01:00
1 parent 01939de9a0
commit 1c6b956435
21 files changed
+614 -33

No files matched your search

+15
View File
@@ -19,6 +19,7 @@ B=/var/lib/flatpak/app/org.blender.Blender/x86_64/stable/active/files
C="$HOME/.var/app/org.blender.Blender/config/blender/5.2"
LD_LIBRARY_PATH="$B/blender/lib" \
XDG_CONFIG_HOME="$HOME/.var/app/org.blender.Blender/config" \
BLENDER_USER_SCRIPTS="$C/scripts" \
"$B/blender/blender" -b --factory-startup \
--python Tools/blosm_blockout.py -- \
@@ -143,3 +144,17 @@ The extract is complete for the block but not for every relation inside its bbox
- Not cleaned, not subdivided into façade modules, not UV-mapped, not detailed.
- Materials are placeholders from Blosm's simple mode. Realistic mode needs the paid material pack and was not run.
- Terrain, kerbs, and crossing ramps are not in it: those come from the DEM cross-sections in `Geo/verification/2026-11-06-cross-sections.md`.
## Scale test and the Unreal frame (2026-11-07)
`Art/blender/scaletest/scale-test-2026-11-07.blend` — three 1 m cubes at origin, +10 m east, +10 m north, plus one red test material. Regenerate with `Tools/scale_test_export.py`; import with `Tools/ue_scale_test_import.py`. Both are the named-preset templates for production assets.
Export preset **`ue_metric_v1`** (Blender FBX): `apply_unit_scale`, `apply_scale_options='FBX_SCALE_NONE'`, `axis_forward='-Y'`, `axis_up='Z'`, `use_space_transform`, no bake-space-transform. Import preset **`ue_metric_v1`** (UE `FbxImportUI`): static mesh, import materials, uniform scale 1.0, no unit conversion, no force-front-X.
Measured result in Unreal centimetres: 1 m cube → exactly 100.00 cm; origin preserved. Axis mapping **east → +X, north → −Y, up → +Z**. The north mirror is inherent to the right-handed Blender → left-handed UE conversion; the FBX importer fixes triangle winding, so geometry is not visually mirrored. Compass conversions in game code must use north = −Y.
## Add-on compatibility (re-checked 2026-11-07)
`Tools/addon_compat_probe.py` loads both pinned add-ons headless and runs an MPFB smoke test. Result on Blender 5.2.0 LTS: Blosm 2.7.28 imports; MPFB 2.0.18 enables and `mpfb.create_human` generates a default human (19 158 verts, UVs, no materials, no armature — skeleton and baked materials are separate steps).
**Required invocation change:** every direct-binary headless run must now set `XDG_CONFIG_HOME="$HOME/.var/app/org.blender.Blender/config"`. Without it Blender resolves the extensions dir to `~/.config/blender/5.2/extensions`, where nothing is installed, and `bl_ext.blender_org.mpfb` is not discovered. Blosm is unaffected (it lives in `scripts/addons`, found via `BLENDER_USER_SCRIPTS`), but set the variable in all runs for consistency.
Binary file not shown.
Binary file not shown.
+62 -4
View File
@@ -1,6 +1,6 @@
# Build
Fresh-checkout build procedure. To be completed once the pinned engine is installed and a blank project has been packaged — see [setup.md](setup.md). Until then this file records only the constraints that are already known.
Fresh-checkout build procedure. Completed 2026-11-07 after the pinned engine was installed and a blank `Game/GigLine` build was packaged and launch-tested.
## Constraints
@@ -8,14 +8,72 @@ Fresh-checkout build procedure. To be completed once the pinned engine is instal
- Build output (`Binaries/`, `Intermediate/`, `Saved/`, `DerivedDataCache/`) is never committed.
- Large content is LFS-tracked, so a fresh clone needs `git lfs install && git lfs pull` before any build.
## Procedure (placeholder)
## Engine
Pinned install: **Unreal Engine 5.8.4** (CL 58902052, promoted build) at
`/run/media/system/DATA/Engines/UE_5.8.4`. The engine bundles **no** Linux clang; the system clang (23.1.4, Fedora 45) is the game-code toolchain. Game targets must use `BuildSettingsVersion.V7` to share the installed editor's build environment.
## Fresh-checkout build
```bash
git clone <remote> gig-line && cd gig-line
git lfs install && git lfs pull
# engine path, project generation, build, and packaging commands — fill in after setup
Tools/check-lfs.sh # must pass before any content build
# Compile the project's editor target (first build also proves the toolchain)
cd /run/media/system/DATA/Engines/UE_5.8.4
Engine/Build/BatchFiles/Linux/Build.sh GigLineEditor Linux Development \
-project="$PWD/../../Documents/06-games/gig-line/Game/GigLine/GigLine.uproject" \
-WaitMutex
# Expected tail: "Result: Succeeded"; module binary lands in
# Game/GigLine/Binaries/Linux/libUnrealEditor-GigLine.so
# Open in the editor (GUI, Vulkan)
./Engine/Binaries/Linux/UnrealEditor \
/run/media/system/DATA/Documents/06-games/gig-line/Game/GigLine/GigLine.uproject
```
Adapt the `-project=` path to the actual checkout location.
## Package a Linux build
```bash
cd /run/media/system/DATA/Engines/UE_5.8.4
Engine/Build/BatchFiles/RunUAT.sh BuildCookRun \
-project="/run/media/system/DATA/Documents/06-games/gig-line/Game/GigLine/GigLine.uproject" \
-noP4 -platform=Linux -clientconfig=Development \
-cook -allmaps -build -stage -pak \
-archive -archivedirectory="/run/media/system/DATA/Builds/GigLine" \
-unattended
# Expected: "BUILD SUCCESSFUL" (~1 min for the blank project; archive ~1.1 GiB)
# Launch test: /run/media/system/DATA/Builds/GigLine/Linux/GigLine.sh
```
The archive directory is deliberately **outside** the repository; never archive into the checkout.
## Headless content checks
Both content-acceptance tests run headless and are the template for all asset import verification:
```bash
# Static-mesh scale test (Blender export -> Unreal import -> measure)
# Blender: Tools/scale_test_export.py, Unreal: Tools/ue_scale_test_import.py
# MPFB character (generate -> rig -> bake -> export -> skeletal import)
# Blender: Tools/mpfb_character_export.py, Unreal: Tools/ue_character_import.py
# Unreal side of any test:
./Engine/Binaries/Linux/UnrealEditor-Cmd \
/run/media/system/DATA/Documents/06-games/gig-line/Game/GigLine/GigLine.uproject \
-run=pythonscript -script=<repo>/Tools/<script>.py -nullrhi -unattended -nopause -stdout
# Note: 5.8 swallows Python print() in the commandlet; scripts write result files to /tmp.
```
## Known engine quirks (2026-11-07)
- One handled `PSOPrecache` ensure fires at packaged-build exit only; not blocking.
- Interchange 5.8 does not create material/texture assets for skeletal FBX imports — `Tools/ue_character_import.py` finishes that chain explicitly.
- Android Turnkey status errors and datarouter DNS warnings in logs are benign.
## Packaged Linux build
Record here: the packaging command, output path, size, and the controls document path. The packaged build must launch and quit cleanly on the development workstation before `v0.1.0` is tagged.
- 2026-11-07: blank `Game/GigLine` Development build packaged via the command above; 1.1 GiB archive at `/run/media/system/DATA/Builds/GigLine/Linux`; `GigLine.sh` launched on the desktop session, Vulkan swapchain created, 759 frames rendered across a 60 s smoke test. Controls document to be written with the first playable (`0.1.0` Gameplay section).
+17 -20
View File
@@ -21,16 +21,15 @@ Anything marked **human step** needs credentials, licence acceptance, or a decis
- `[x]` **Human step.** Create an Epic account, accept the Unreal license.
Done on 2026-11-06 with the account email recorded locally; no credentials stored in the repository.
- `[-]` **Human step.** Acquire one pinned engine build. Two routes:
- `[x]` **Human step.** Acquire one pinned engine build. Two routes:
- **Launcher build**: install the Linux Epic Games Launcher, download the pinned engine version, install to the DATA volume, and disable auto-update so the pin holds.
- **Source build**: clone the pinned `release` branch from the Epic Games GitHub organisation, then `./Setup.sh`, `./GenerateProjectFiles.sh`, `make -j16`. Budget 120–200 GB and a multi-hour first build on 16 threads.
**5.8.4** downloaded to `/run/media/system/DATA/Downloads/Linux_Unreal_Engine_5.8.4.zip`: 39 819 212 076 bytes (37.08 GiB), ZIP central directory present with 299 959 entries, `Engine/Build/Build.version` reports **5.8.4**, changelist 58902052, `++UE5+Release-5.8`. Extracting to `/run/media/system/DATA/Engines/UE_5.8.4`.
The earlier 5.2.0 archive (19.59 GiB, CL 25360045) remains on disk as a fallback only.
**5.8.4** downloaded to `/run/media/system/DATA/Downloads/Linux_Unreal_Engine_5.8.4.zip`: 39 819 212 076 bytes (37.08 GiB), ZIP central directory present with 299 959 entries, `Engine/Build/Build.version` reports **5.8.4**, changelist 58902052, `++UE5+Release-5.8`.
- `[x]` Verify the archive end to end before extracting. `unzip -tq` on 2026-11-06: "No errors detected", exit 0.
- `[ ]` Extract to the DATA volume and record the install path. Plan: `mkdir -p /run/media/system/DATA/Engines/UE_5.2 && cd $_ && unzip -q <zip>`; engine root becomes `/run/media/system/DATA/Engines/UE_5.2/Engine`. Roughly 55–60 GB extracted, so keep it off the repository volume tree.
- `[ ]` Resolve the engine version: 5.2.0 is what is on disk, 5.8.x was the planned pin. Record the decision and its reasons.
- `[ ]` Record the exact engine version and build route in [toolchain.md](toolchain.md).
- `[ ]` Validate that the engine editor opens and a blank project runs on Bazzite. Unreal does not support this distribution; record any workarounds.
- `[x]` Extract to the DATA volume and record the install path. Done 2026-11-07: `/run/media/system/DATA/Engines/UE_5.8.4`, 73 GiB on disk, extraction verified by full headless editor start.
- `[x]` Resolve the engine version: **5.8.4 pinned** (see the decision below). The 5.2.0 archive (19.59 GiB, CL 25360045) remains on disk as a fallback only and should be deleted once the pin is trusted.
- `[x]` Record the exact engine version and build route in [toolchain.md](toolchain.md). Done: 5.8.4, Epic download route, CL 58902052.
- `[x]` Validate that the engine editor opens and a blank project runs on Bazzite. Done 2026-11-07: headless `-NullRHI` smoke test **and** full editor GUI launch with Vulkan RHI on the RTX 5070 Ti (loader 1.4.350, `VULKAN_SM5`, map check 0 errors). No distribution workarounds were needed; only benign log noise (Android Turnkey status, telemetry DNS failures).
### Engine version decision
@@ -41,24 +40,24 @@ The archive on disk is **Unreal Engine 5.2.0**, released May 2023, three and a h
- No engine fixes from 5.3 through 5.8, and no compatibility with content or plugins authored for newer versions.
- Bazzite is not a supported Unreal distribution in any version, so this risk compounds rather than introduces.
**Decision taken 2026-11-06, archive delivered 2026-11-07:** the current stable release is **5.8.4**, and that is what will be pinned. The 5.2.0 archive stays only until 5.8.4 launches on Bazzite, then gets deleted; two engines on disk invite accidental mixing. The 5.2.0 costs listed above are why the newer build was preferred.
**Decision taken 2026-11-06, archive delivered 2026-11-07, extracted and validated 2026-11-07:** the current stable release is **5.8.4**, and that is the pin. The 5.2.0 archive stays only until the 5.8.4 pin is trusted through asset production, then gets deleted; two engines on disk invite accidental mixing. The 5.2.0 costs listed above are why the newer build was preferred. Note: this 5.8.4 build ships **no** bundled Linux clang — the system clang (23.1.4) is the game-code toolchain, accepted by the first C++ compile and by the packaged build.
## 3. Tooling pins
- `[x]` Blender 5.2 LTS (Flatpak) and MPFB 2.0.18 recorded.
- `[x]` Install QGIS and record its version. QGIS **4.2.3**, Flatpak `stable`, runtime `org.kde.Platform 6.11`, bundled PROJ `9.9.0`. The stale `lts` 3.28.9 install is still beside it and should be uninstalled.
- `[x]` Install QGIS and record its version. QGIS **4.2.3**, Flatpak `stable`, runtime `org.kde.Platform 6.11`, bundled PROJ `9.9.0`. The stale `lts` 3.28.9 install was uninstalled 2026-11-07; only `stable` remains.
- `[x]` Install Blosm in Blender, record its version, and confirm it loads on Blender 5.2. Blosm **2.7.28** imports the block extract headless in `3Dsimple` mode; the exact command and the two required headless workarounds are in `Art/blender/README.md`. No second Blender version was needed.
- `[ ]` Confirm the geo tool Python environment (venv with `pyproj`, `GDAL`, `shapely` from the QGIS or distrobox environment) and record it.
## 4. Project bootstrap
- `[ ]` Create the C++ Unreal project at `Game/GigLine/GigLine.uproject` with Enhanced Input enabled and Blueprint gameplay enabled. Commit `Config/`, `Source/`, and `GigLine.uproject`; leave `Binaries/`, `Intermediate/`, `Saved/`, and the DDC out of the repository — `.gitignore` already covers them.
- `[ ]` Point the local Derived Data Cache at the DATA volume. Confirm the setting name and section against the pinned engine's documentation rather than copying a config snippet from another version.
- `[ ]` Enable Git LFS integration in the editor before importing content, so large assets are routed to LFS on import rather than on a later re-add.
- `[ ]` Package and launch a blank Linux build from the command line, so the packaging path is proven before any content exists.
- `[ ]` Export one static mesh from Blender with a known metric size and import it into Unreal. Verify the 100× scale conversion (1 m → 100 uu), the pivot, normals, and material assignment.
- `[ ]` Export one MPFB character with baked materials and a deform skeleton and verify it imports with the expected skeleton hierarchy and scale.
- `[ ]` Commit the baseline and write the fresh-checkout build procedure into [build.md](build.md).
- `[x]` Create the C++ Unreal project at `Game/GigLine/GigLine.uproject` with Enhanced Input enabled and Blueprint gameplay enabled. Created 2026-11-07: primary game module + default game mode, Enhanced Input plugin and input classes in `Config/DefaultInput.ini`. `Config/`, `Source/`, and `GigLine.uproject` are committed; `Binaries/`, `Intermediate/`, `Saved/`, and the DDC stay out via `.gitignore`.
- `[x]` Point the local Derived Data Cache at the DATA volume. The engine lives on the DATA volume and installed builds default `LocalDDC` under the engine root, so the DDC is already off the repository SSD tree. Revisit only if a project-local DDC is later configured.
- `[x]` Enable Git LFS integration in the editor before importing content, so large assets are routed to LFS on import rather than on a later re-add. Satisfied by repo-level `.gitattributes` routing: content is added via `git add` (CLI or editor), and the attributes send `.uasset`/`.umap`/`.blend` to LFS regardless of which tool wrote them. `Tools/check-lfs.sh` is the guard.
- `[x]` Package and launch a blank Linux build from the command line, so the packaging path is proven before any content exists. Done 2026-11-07: UAT `BuildCookRun` succeeded in 56 s; 1.1 GiB archive at `/run/media/system/DATA/Builds/GigLine`; `GigLine.sh` rendered 759 frames across a 60 s smoke test. Full procedure in [build.md](build.md).
- `[x]` Export one static mesh from Blender with a known metric size and import it into Unreal. Verified 2026-11-07 with the three-cube calibration scene: 1 m → exactly 100.00 cm, origin preserved, material imported; axis mapping east → +X, north → −Y, up → +Z. Presets and scripts in [build.md](build.md) and `Art/blender/README.md`.
- `[x]` Export one MPFB character with baked materials and a deform skeleton and verify it imports with the expected skeleton hierarchy and scale. Done 2026-11-07: MPFB 2.0.18 generated human (19 158 verts) + standard rig (137 bones, weights) + v2 skin baked to a 1024² diffuse; imported into Unreal as a `SkeletalMesh` with a 138-bone `Skeleton`, a `Material` carrying the baked `Texture2D`, in `/Game/Test/Character`.
- `[x]` Commit the baseline and write the fresh-checkout build procedure into [build.md](build.md). Done 2026-11-07.
## 5. Repository
@@ -78,8 +77,6 @@ Sizes: the engine lives outside the repository (pinned install path recorded in
| Blocker | Owner | Needed for |
| --- | --- | --- |
| Epic account, license acceptance, engine download | human | All Unreal work |
| QGIS pin: replace the stale `3.28.9` flatpak | human | Geo pipeline |
| Blosm install and Blender 5.2 compatibility check | human | OSM blockouts |
| Delete the fallback 5.2.0 engine archive once the 5.8.4 pin is trusted through asset production | agent | Disk hygiene, pin safety |
Closed: Bazzite update and reboot, driver and Vulkan verification, `git-lfs`/`clang`/`cmake`/`ninja` layering.
Closed 2026-11-07: Epic account and engine download, QGIS `lts` removal, Blosm and MPFB on Blender 5.2 (headless invocation fix in `Art/blender/README.md`), engine validation on Bazzite, full project bootstrap. Earlier closed: Bazzite update and reboot, driver and Vulkan verification, `git-lfs`/`clang`/`cmake`/`ninja` layering.
+2 -2
View File
@@ -13,7 +13,7 @@ Audited 2026-11-06, updated 2026-11-07 on the development workstation.
| Unreal Engine | `5.8.4` — CL 58902052, `++UE5+Release-5.8`, promoted build | Epic download | Fully extracted to `/run/media/system/DATA/Engines/UE_5.8.4` (73 GiB on disk). **Validated 2026-11-07:** headless `-NullRHI` smoke test and full editor GUI launch with Vulkan RHI on the RTX 5070 Ti (loader 1.4.350, `VULKAN_SM5`, map check 0 errors). Pin is final. The verified 5.2.0 archive stays as fallback. |
| Unreal Linux toolchain | system `clang 23.1.4` (Fedora 45) | `rpm-ostree` layer | This 5.8.4 build ships **no** bundled Linux clang (no `clang++` anywhere in the engine tree, verified 2026-11-07); UBT compiles game code with the system toolchain. **Accepted 2026-11-07:** first game-module compile and link succeeded (`GigLineEditor` target, `libUnrealEditor-GigLine.so`). Note: game targets must use `BuildSettingsVersion.V7` to share the engine's build environment. |
| Blender | `5.2.0` LTS (build 2026-07-14) | Flatpak `org.blender.Blender` | Installed. Run headless from the app tree with `LD_LIBRARY_PATH` + `BLENDER_USER_SCRIPTS`; `flatpak run` is unavailable to the agent shell. |
| MPFB | `2.0.18` (extension `mpfb`, declares Blender >= 4.2) | Blender extensions platform | Installed. Load-and-export check on 5.2 still pending. |
| MPFB | `2.0.18` (extension `bl_ext.blender_org.mpfb`, `blender_version_min 4.2.0`) | Blender extensions platform | Installed. **Headless-verified 2026-11-07 on 5.2:** enables and generates a default human (19 158-vert mesh, UVs) via `Tools/addon_compat_probe.py`. Headless runs must set `XDG_CONFIG_HOME=$HOME/.var/app/org.blender.Blender/config` or the extension is not discovered (see `Art/blender/README.md`). Load-and-export (skeleton + baked materials) check still pending. |
| Blosm | `2.7.28` (legacy add-on, `bl_info.blender = (4, 5, 0)`, manifest min 4.2) | blosm.com | Installed and importing on Blender 5.2 in `3Dsimple` mode. Two headless workarounds are required; see `Art/blender/README.md`. Paid material asset pack lives in `~/Downloads/blosm-assets/` and is not committed. |
| QGIS | `4.2.3`, Flatpak `stable`, runtime `org.kde.Platform 6.11`; bundled PROJ `9.9.0` | flathub | Installed 2026-11-06. Stale `lts` branch (`3.28.9`, EOL Qt5 runtime) uninstalled 2026-11-07; only `stable` remains. |
| Git | `2.56.0` | Bazzite image | Installed. |
@@ -48,6 +48,6 @@ To update: copy `.claude/skills/` from a newer upstream checkout over the existi
## Open compatibility risks
1. **Blosm on Blender 5.2 — worked around, not resolved.** `3Dsimple` import works headless (see the blockout). Two headless-only defects are patched around in the script rather than in the add-on: an asset-directory check that fires before `mode` is honoured, and a background-mode import path that uses a broken relative import when the default level table is empty. `3Drealistic` mode has not been exercised; it loads the paid material pack and previously failed inside the realistic renderer setup. If realistic materials are wanted, test that mode separately before depending on it.
2. **MPFB 2.0.18 on Blender 5.2.** The extension declares Blender >= 4.2 but predates 5.x. The acceptance test is the `0.1.0` requirement to export one MPFB character with baked materials and a deform skeleton and import it into Unreal.
2. **MPFB 2.0.18 on Blender 5.2.** The extension declares Blender >= 4.2 but predates 5.x. Generation verified 2026-11-07: `mpfb.create_human` works headless once `XDG_CONFIG_HOME` points at the Flatpak config tree. Remaining acceptance test is the `0.1.0` requirement to export one MPFB character with baked materials and a deform skeleton and import it into Unreal.
3. **QGIS 4.x recipes.** Only `stable` (4.2.3) is installed now; the old `lts` was removed 2026-11-07. QGIS 4.x uses Qt6, so any processing recipe copied from a 3.x tutorial needs re-checking before it is trusted.
4. **Driver and Vulkan validation on Bazzite.** Not an officially supported Unreal distribution. The engine install itself is proven (full init headlessly); only the Vulkan RHI on the RTX 5070 Ti is unproven, and one desktop-terminal run settles it.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+11
View File
@@ -54,3 +54,14 @@ Scope: facts and traps that cost time to establish. Planned work lives in [ROADM
- **Without `git lfs install` the lfs filter is inactive and `git add` does not warn** — it stores the file as a plain blob. `Tools/check-lfs.sh` is the guard.
- **Never commit reference images or `.env`.** `.gitignore` covers `/Art/reference/`, `.env`, `*.env`. When images slipped in early, history was rewritten to remove them.
- The project origin is Hollywood × Highland: `E 376 515.389 / N 3 774 224.083` in EPSG:26911. Blender X east, Y north, Z up, metres, Z measured from the origin ground (NAVD88 115.911 m). The whole block sits at negative X.
## 2026-11-07 — Unreal pipeline findings
- **UE 5.8 game targets must use `BuildSettingsVersion.V7`** to share the installed editor's build environment; V5 is rejected with "modifies the values of properties … not allowed".
- **`UnrealEditor-Cmd -run=pythonscript` swallows Python `print()`** with `-stdout` in 5.8 — write results to a file instead (see `Tools/ue_scale_test_import.py`).
- **`unreal.Box` has `min`/`max`, not `origin`/`get_extent()`** in 5.8 Python; compute centre/size from the corners.
- **FBX Blender→UE axis mapping is east→+X, north→−Y, up→+Z** with preset `ue_metric_v1`; the Y mirror is inherent (RH→LH). Compass math in game code: north = −Y. Verified with the three-cube scale test.
- Headless asset import works fine with `-nullrhi`; `unreal.AssetImportTask` + `FbxImportUI` is the pattern (Interchange pipeline under the hood).
- **Flatpak Blender headless needs `XDG_CONFIG_HOME=$HOME/.var/app/org.blender.Blender/config`** for extensions: `bpy.utils.user_resource('EXTENSIONS')` otherwise resolves to `~/.config/blender/5.2/extensions` (empty) and `bl_ext.blender_org.mpfb` is undiscoverable. Blosm unaffected (scripts/addons via `BLENDER_USER_SCRIPTS`). Found via `Tools/addon_compat_probe.py`; documented in `Art/blender/README.md`.
- **UE 5.8 Interchange skips material/texture assets for skeletal FBX imports** (static imports do get materials). Finish the chain in Python: import texture, build Material via `MaterialEditingLibrary`, assign with `sm.set_editor_property('materials', [SkeletalMaterial])` — the struct field is `material_interface`, not `material`, and `SkeletalMesh.set_material()` does not exist. See `Tools/ue_character_import.py`.
- **Blender 5.2 FBX exporter removed `armature_nla`** (passes for 4.x recipes fail). Also: a baked image is only referenced by the FBX exporter when `image.filepath` (not just `filepath_raw`) is set and the exporter can map the node — MPFB's v2 skin node-group tree can't be mapped, so a flat export material is required.
+7 -7
View File
@@ -4,7 +4,7 @@ Living roadmap for this project. This file is the single source of truth for cur
## Status at a glance — 2026-11-07 08:50
Workstation and geo pipeline are done and verified. The art pipeline has a first blockout, terrain, and street in metres, in one shared coordinate frame. Unreal 5.8.4 is installed and starts headlessly; no project exists yet.
Workstation, geo pipeline, and engine validation are done. The art pipeline has a first blockout, terrain, and street in metres, in one shared coordinate frame. **Project bootstrap is complete**: `Game/GigLine` builds, packages, and both content-acceptance tests (static-mesh scale, MPFB skeletal character) pass. Remaining `0.1.0` work is geo detailing, asset production, gameplay, and release checks.
| Area | State |
| --- | --- |
@@ -14,7 +14,7 @@ Workstation and geo pipeline are done and verified. The art pipeline has a first
| Engine | 5.8.4 installed and validated: headless + Vulkan GUI editor launch pass on the RTX 5070 Ti |
| Blender | blockout (87 meshes), terrain, street (road/kerbs/sidewalks), site assembly — all in one frame, walk-checked |
| Reference | 72 images local, uncommitted; hero entrance located; inherited eight-step claim overturned |
| Unreal project | `Game/GigLine` C++ project created; editor target compiles; blank Linux build packaged and launch-tested |
| Unreal project | bootstrap complete: C++ project builds, blank Linux build packaged + launch-tested, scale test and MPFB skeletal character accepted |
**Open decisions:** code licence (MIT proposed); whether shipped builds carry a derived OSM database (ODbL posture).
@@ -36,9 +36,9 @@ Status detail in [Docs/setup.md](Docs/setup.md); workstation audit in [Docs/envi
- [x] Create a C++ Unreal project with Blueprint gameplay iteration and Enhanced Input. Created 2026-11-07 at `Game/GigLine/GigLine.uproject` (primary game module + default game mode, Enhanced Input plugin + input classes in `Config/DefaultInput.ini`); `GigLineEditor` target compiles clean with system clang (UBT: Result Succeeded).
- [x] Package and launch a blank Linux build. 2026-11-07: UAT `BuildCookRun` (cook + build + stage + pak, Development config) succeeded in 56 s; 1.1 GiB archive at `/run/media/system/DATA/Builds/GigLine` (outside the repo, uncommitted). `GigLine.sh` launched on the desktop session, created a Vulkan swapchain, and rendered 759 frames across a 60 s test. Known minor: one handled `PSOPrecache` ensure fires at exit only; not blocking, watch it in `0.1.x`.
- [ ] Export one static mesh from Blender and import into Unreal; verify scale and materials.
- [ ] Export one MPFB character with baked materials and deform skeleton; verify import.
- [ ] Document initial setup in `Docs/`: engine install, project checkout, fresh-checkout build procedure.
- [x] Export one static mesh from Blender and import into Unreal; verify scale and materials. 2026-11-07: calibration scene (three 1 m cubes at origin / +10 m east / +10 m north) exported from Blender with preset `ue_metric_v1` (`Tools/scale_test_export.py`) and imported headless via `Tools/ue_scale_test_import.py`. Measured in UE: cubes exactly 100.00 cm, origin preserved, material imported. Axis mapping: **east → +X, north → −Y, up → +Z** (the Y mirror is inherent to the right-handed→left-handed conversion; importer handles winding).
- [x] Export one MPFB character with baked materials and deform skeleton; verify import. 2026-11-07: MPFB 2.0.18 generated human (19 158 verts, 1.66 m tall) + standard rig with weights (137 bones) + v2 skin baked to a 1024² diffuse texture (`Tools/mpfb_character_export.py`, export preset `ue_character_v1`). Imported headless (`Tools/ue_character_import.py`) into `/Game/Test/Character`: `SkeletalMesh` + 138-bone `Skeleton` + `Material` with the baked `Texture2D` assigned to slot 0. Quirks recorded: 5.8 Interchange skips materials for skeletal FBX imports (finished in script); Blender 5.2 dropped the FBX `armature_nla` option; the baked image needs `image.filepath` set for FBX to reference it.
- [x] Document initial setup in `Docs/`: engine install, project checkout, fresh-checkout build procedure. 2026-11-07: `Docs/setup.md` reconciled with reality (all engine/bootstrap items marked done with evidence); `Docs/build.md` completed with the fresh-checkout build, packaging, and headless-content-check procedures plus known engine quirks.
### Geographic data
@@ -48,11 +48,11 @@ Status detail in [Docs/setup.md](Docs/setup.md); workstation audit in [Docs/envi
- [x] In QGIS, set a suitable local projected CRS (e.g. EPSG:26911) and record a local origin. EPSG:26911, origin and projected easting/northing are recorded in `Geo/manifests/local-origin.md` with PROJ 9.9.0 control points; QGIS 4.2.3 is installed for the DEM reprojection step.
- [x] Verify the address, building footprints, and demo boundaries for 6840 Hollywood Blvd against source data.
- [-] Import OSM blockouts through Blosm; clean and detail them in Blender. Imported 2026-11-06 (`Art/blender/blockout/blockout-2026-11-06.blend`, 86 meshes, scale 1:1 against OSM, re-framed into EPSG:26911 with a 0.0 cm affine residual); cleaning and detailing not started.
- [ ] Preserve geographic coordinates in metadata; define explicit meter→centimeter conversion to local Unreal coordinates. The frame is defined (`Geo/manifests/local-origin.md`: EPSG:26911, origin Hollywood × Highland, X east / Y north / Z up, ×100 on export) and the Blender meshes already use it; the export preset and the asserted ×100 are not built yet.
- [-] Preserve geographic coordinates in metadata; define explicit meter→centimeter conversion to local Unreal coordinates. The frame is defined (`Geo/manifests/local-origin.md`: EPSG:26911, origin Hollywood × Highland, X east / Y north / Z up) and the ×100 conversion is now **verified** by the 2026-11-07 scale test, with the axis mapping recorded (north realizes as UE −Y). Left: build the production export preset for the actual site meshes using these settings.
### Asset production
- [ ] Establish a consistent scale test, named export presets, and repeatable import settings.
- [x] Establish a consistent scale test, named export presets, and repeatable import settings. Scale test = the three-cube calibration scene (`Art/blender/scaletest/`, regenerable via `Tools/scale_test_export.py`); named export preset `ue_metric_v1` (FBX settings in that script); named import preset `ue_metric_v1` (`FbxImportUI` settings in `Tools/ue_scale_test_import.py`). Both scripts are the templates for production asset export/import.
- [-] Collect reference photography for manual façade work. Local only, never committed: `Art/reference/` is gitignored. **72 images in eleven folders**: hero façades to 4k, the El Capitan Theatre exterior series, premiere-night staging, Ovation Hollywood, the north side, and the Lee Drug neighbour. Gaps largely closed by the Flickr/web-search pass: the hero entrance is now documented (three portals with individual short flights — the inherited "eight steps, 12 m wide" is overturned), and the 6806–6822 retail run appears in an oblique 2013 frame. Still missing: a clean isolated frame of the door recess and a night frame of 6840 itself.
- [x] Download reference photography for the two north-side buildings inside the 50 m buffer — TCL Chinese Theatre (OSM way 424105908, 6925 Hollywood Blvd) and Madame Tussaud's (way 424105889, 6933 Hollywood Blvd). Done 2026-11-06 with `Tools/commons_fetch.py` against the Wikimedia Commons API: eleven frames in the local `Art/reference/north-chinese/` and `Art/reference/north-tussauds/`, provenance in each `sidecar.json`, nothing committed.
- [ ] Model hero façade for the El Capitan Entertainment Centre site.
+64
View File
@@ -0,0 +1,64 @@
# Headless compatibility probe for Blosm and MPFB on the pinned Blender 5.2.
#
# blender -b --factory-startup --python Tools/addon_compat_probe.py
#
# Checks: Blender version, Blosm add-on loads + version, MPFB extension
# enables + version, MPFB can create a default human headless, resulting
# object inventory (mesh/armature/materials/uvs).
import bpy
import sys
REPORT = "/tmp/addon-compat-report.txt"
lines = []
def log(msg):
lines.append(str(msg))
bl = bpy.app.version_string
log(f"Blender: {bl}")
# --- Blosm (legacy add-on, scripts/addons) ---
try:
import blosm # noqa
info = getattr(blosm, "bl_info", {})
log(f"blosm: import OK, version {info.get('version', '?')}, blender {info.get('blender', '?')}")
except Exception as exc: # noqa
log(f"blosm: IMPORT FAILED: {exc!r}")
# --- MPFB (extension, extensions/blender_org/mpfb) ---
try:
import addon_utils
bpy.ops.preferences.addon_enable(module="bl_ext.blender_org.mpfb")
enabled = "bl_ext.blender_org.mpfb" in bpy.context.preferences.addons
version = "?"
try:
import bl_ext.blender_org.mpfb as mpfb_mod
version = getattr(mpfb_mod, "MPFB_VERSION", "?")
except Exception:
pass
log(f"mpfb: enable OK (registered={enabled}), version {version}")
except Exception as exc: # noqa
log(f"mpfb: ENABLE FAILED: {exc!r}")
# --- MPFB create-human smoke test ---
try:
bpy.ops.mpfb.create_human("EXEC_DEFAULT")
meshes = [o for o in bpy.data.objects if o.type == "MESH"]
armatures = [o for o in bpy.data.objects if o.type == "ARMATURE"]
log(f"mpfb.create_human: OK — {len(bpy.data.objects)} objects, "
f"{len(meshes)} meshes, {len(armatures)} armatures")
for o in meshes:
uv = bool(o.data.uv_layers)
mats = [m.name for m in o.data.materials]
log(f" mesh {o.name}: {len(o.data.vertices)} verts, uv={uv}, materials={mats}")
for o in armatures:
log(f" armature {o.name}: {len(o.data.bones)} bones")
except Exception as exc: # noqa
log(f"mpfb.create_human: FAILED: {exc!r}")
with open(REPORT, "w") as f:
f.write("\n".join(lines) + "\n")
print("\n".join(lines))
+148
View File
@@ -0,0 +1,148 @@
# MPFB character export for the Blender -> Unreal pipeline (0.1.0 acceptance).
#
# Generates a default human, adds the standard deform rig with weights,
# materializes the v2 skin, bakes its diffuse to a texture, and exports
# FBX with the named export preset "ue_character_v1".
#
# blender -b --factory-startup --python Tools/mpfb_character_export.py -- \
# --out /tmp/gigline/character.fbx \
# --save-blend Art/blender/character/character-<date>.blend
import argparse
import os
import sys
import bpy
REPORT = "/tmp/mpfb-export-report.txt"
lines = []
def log(msg):
lines.append(str(msg))
def enable_mpfb():
bpy.ops.preferences.addon_enable(module="bl_ext.blender_org.mpfb")
assert "bl_ext.blender_org.mpfb" in bpy.context.preferences.addons, "MPFB did not enable"
def build_character():
for obj in list(bpy.data.objects):
bpy.data.objects.remove(obj, do_unlink=True)
bpy.ops.mpfb.create_human("EXEC_DEFAULT")
human = bpy.context.active_object
log(f"create_human: {human.name}, {len(human.data.vertices)} verts, dims={[round(d, 3) for d in human.dimensions]}")
bpy.ops.mpfb.add_standard_rig("EXEC_DEFAULT")
armatures = [o for o in bpy.data.objects if o.type == "ARMATURE"]
log(f"add_standard_rig: {len(armatures)} armature(s), bones={[len(a.data.bones) for a in armatures]}")
bpy.ops.object.select_all(action="DESELECT")
human.select_set(True)
bpy.context.view_layer.objects.active = human
bpy.ops.mpfb.create_v2_skin("EXEC_DEFAULT")
mats = [m.name for m in human.data.materials]
log(f"create_v2_skin: materials={mats}")
return human
def bake_diffuse(human, size=1024):
mat = human.data.materials[0]
nodes, links = mat.node_tree.nodes, mat.node_tree.links
img = bpy.data.images.new("T_Skin_Diffuse", size, size)
tex_node = nodes.new("ShaderNodeTexImage")
tex_node.image = img
nodes.active = tex_node
tex_node.select = True
bsdf = next((n for n in nodes if n.type == "BSDF_PRINCIPLED"), None)
bsdf_input = bsdf.inputs["Base Color"] if bsdf else None
if bsdf is not None:
for link in list(links):
if link.to_node == bsdf and link.to_socket == bsdf_input:
links.remove(link)
links.new(tex_node.outputs["Color"], bsdf_input)
scene = bpy.context.scene
scene.render.engine = "CYCLES"
scene.cycles.device = "CPU"
scene.cycles.samples = 1
scene.render.bake.use_pass_direct = False
scene.render.bake.use_pass_indirect = False
scene.render.bake.use_pass_color = True
scene.render.bake.margin = 8
bpy.ops.object.select_all(action="DESELECT")
human.select_set(True)
bpy.context.view_layer.objects.active = human
bpy.ops.object.bake(type="DIFFUSE", pass_filter={"COLOR"}, use_clear=True)
img.filepath = "/tmp/gigline/T_Skin_Diffuse.png"
img.filepath_raw = img.filepath
img.file_format = "PNG"
img.save()
log(f"bake: baked {size}x{size} diffuse -> {img.filepath} (filepath now set for FBX)")
# MPFB's v2 skin is a node-group tree the FBX exporter cannot map to a
# texture. Build a flat export material carrying the baked diffuse so the
# FBX (and therefore Unreal) gets a real texture.
export_mat = bpy.data.materials.new("M_Character_Export")
export_mat.use_nodes = True
nt = export_mat.node_tree
nt.nodes.clear()
out = nt.nodes.new("ShaderNodeOutputMaterial")
bsdf = nt.nodes.new("ShaderNodeBsdfPrincipled")
tex = nt.nodes.new("ShaderNodeTexImage")
tex.image = img
nt.links.new(tex.outputs["Color"], bsdf.inputs["Base Color"])
nt.links.new(bsdf.outputs["BSDF"], out.inputs["Surface"])
human.data.materials.clear()
human.data.materials.append(export_mat)
log("export material: M_Character_Export with baked texture assigned")
def export_fbx(human, path):
os.makedirs(os.path.dirname(path), exist_ok=True)
# Named export preset "ue_character_v1" — change only via a new preset name.
bpy.ops.export_scene.fbx(
filepath=path,
use_selection=False,
object_types={"ARMATURE", "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,
add_leaf_bones=False,
path_mode="COPY",
embed_textures=False,
mesh_smooth_type="OFF",
bake_space_transform=False,
)
log(f"export: FBX preset ue_character_v1 -> {path}")
def main():
argv = sys.argv[sys.argv.index("--") + 1:] if "--" in sys.argv else []
parser = argparse.ArgumentParser()
parser.add_argument("--out", default="/tmp/gigline/character.fbx")
parser.add_argument("--save-blend", default=None)
args = parser.parse_args(argv)
enable_mpfb()
human = build_character()
bake_diffuse(human)
if args.save_blend:
os.makedirs(os.path.dirname(args.save_blend), exist_ok=True)
bpy.ops.wm.save_as_mainfile(filepath=args.save_blend)
log(f"save: {args.save_blend}")
export_fbx(human, args.out)
with open(REPORT, "w") as f:
f.write("\n".join(lines) + "\n")
main()
+81
View File
@@ -0,0 +1,81 @@
# Scale-test scene builder and FBX exporter for the Blender -> Unreal pipeline.
#
# Builds three 1 m cubes in the project geographic frame (EPSG:26911, origin
# Hollywood x Highland, X east / Y north / Z up):
# ScaleTest_A centred at (0, 0, 0.5) - origin + 1 m size reference
# ScaleTest_B centred at (10, 0, 0.5) - +10 m east probe
# ScaleTest_C centred at (0, 10, 0.5) - +10 m north probe
# and exports them as one FBX with the named export preset "ue_metric_v1".
#
# Run headless (see Art/blender/README.md for the Flatpak invocation):
# blender -b --factory-startup --python Tools/scale_test_export.py -- \
# --out /tmp/gigline/scaletest.fbx --save-blend Art/blender/scaletest/scale-test-<date>.blend
import argparse
import os
import sys
import bpy
def build_scene():
scene = bpy.context.scene
for obj in list(bpy.data.objects):
bpy.data.objects.remove(obj, do_unlink=True)
mat = bpy.data.materials.new("M_ScaleTest_Red")
mat.use_nodes = True
bsdf = mat.node_tree.nodes.get("Principled BSDF")
bsdf.inputs["Base Color"].default_value = (0.8, 0.05, 0.05, 1.0)
specs = {
"ScaleTest_A": (0.0, 0.0, 0.5),
"ScaleTest_B": (10.0, 0.0, 0.5),
"ScaleTest_C": (0.0, 10.0, 0.5),
}
for name, loc in specs.items():
bpy.ops.mesh.primitive_cube_add(size=1.0, location=loc)
obj = bpy.context.active_object
obj.name = name
obj.data.materials.append(mat)
print(f"[scale_test] created {name} at {loc}")
def export_fbx(path):
os.makedirs(os.path.dirname(path), exist_ok=True)
# Named export preset "ue_metric_v1" - change only via a new preset name.
bpy.ops.export_scene.fbx(
filepath=path,
use_selection=False,
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="OFF",
path_mode="AUTO",
bake_space_transform=False,
)
print(f"[scale_test] exported FBX preset ue_metric_v1 -> {path}")
def main():
argv = sys.argv[sys.argv.index("--") + 1:] if "--" in sys.argv else []
parser = argparse.ArgumentParser()
parser.add_argument("--out", default="/tmp/gigline/scaletest.fbx")
parser.add_argument("--save-blend", default=None)
args = parser.parse_args(argv)
build_scene()
if args.save_blend:
os.makedirs(os.path.dirname(args.save_blend), exist_ok=True)
bpy.ops.wm.save_as_mainfile(filepath=args.save_blend)
print(f"[scale_test] saved {args.save_blend}")
export_fbx(args.out)
main()
+117
View File
@@ -0,0 +1,117 @@
# Unreal-side half of the MPFB character acceptance: imports the skeletal FBX
# with the named import preset "ue_character_v1" and verifies the result.
#
# UnrealEditor-Cmd <uproject> -run=pythonscript -script=<repo>/Tools/ue_character_import.py \
# -nullrhi -unattended -nopause -stdout
import unreal
FBX = "/tmp/gigline/character.fbx"
DEST = "/Game/Test/Character"
REPORT = "/tmp/ue-character-result.txt"
lines = []
def log(msg):
lines.append(str(msg))
def import_character():
if unreal.EditorAssetLibrary.does_directory_exist(DEST):
unreal.EditorAssetLibrary.delete_directory(DEST)
task = unreal.AssetImportTask()
task.filename = FBX
task.destination_path = DEST
task.automated = True
task.save = True
task.replace_existing = True
ui = unreal.FbxImportUI()
ui.import_mesh = True
ui.import_materials = True
ui.import_textures = True
ui.import_as_skeletal = True
ui.import_animations = False
smd = ui.skeletal_mesh_import_data
smd.import_uniform_scale = 1.0
smd.convert_scene_unit = False
smd.force_front_x_axis = False
smd.normal_import_method = unreal.FBXNormalImportMethod.FBXNIM_IMPORT_NORMALS
task.options = ui
unreal.AssetToolsHelpers.get_asset_tools().import_asset_tasks([task])
unreal.EditorLoadingAndSavingUtils.save_dirty_packages(True, True)
def import_texture_and_material():
# Interchange 5.8 does not create material/texture assets for skeletal
# FBX imports; finish the baked-material chain explicitly.
task = unreal.AssetImportTask()
task.filename = "/tmp/gigline/T_Skin_Diffuse.png"
task.destination_path = DEST
task.automated = True
task.save = True
task.replace_existing = True
unreal.AssetToolsHelpers.get_asset_tools().import_asset_tasks([task])
tex = unreal.EditorAssetLibrary.load_asset(f"{DEST}/T_Skin_Diffuse")
log(f"texture: {tex.get_class().get_name() if tex else 'MISSING'}")
mat_path = f"{DEST}/M_Character_Export"
if unreal.EditorAssetLibrary.does_asset_exist(mat_path):
unreal.EditorAssetLibrary.delete_asset(mat_path)
mat = unreal.AssetToolsHelpers.get_asset_tools().create_asset(
"M_Character_Export", DEST, unreal.Material, unreal.MaterialFactoryNew())
tex_expr = unreal.MaterialEditingLibrary.create_material_expression(
mat, unreal.MaterialExpressionTextureSample)
tex_expr.set_editor_property("texture", tex)
unreal.MaterialEditingLibrary.connect_material_property(
tex_expr, "RGB", unreal.MaterialProperty.MP_BASE_COLOR)
unreal.MaterialEditingLibrary.recompile_material(mat)
sm = unreal.EditorAssetLibrary.load_asset(f"{DEST}/character")
entry = unreal.SkeletalMaterial()
entry.set_editor_property("material_interface", mat)
entry.set_editor_property("material_slot_name", "M_Character_Export")
sm.set_editor_property("materials", [entry])
unreal.EditorAssetLibrary.save_loaded_asset(sm)
log("material assigned to slot 0 (via SkeletalMaterial.material_interface)")
unreal.EditorLoadingAndSavingUtils.save_dirty_packages(True, True)
def verify():
sm = unreal.EditorAssetLibrary.load_asset(f"{DEST}/character")
if sm is None:
log(f"MISSING skeletal asset {DEST}/character; assets: {unreal.EditorAssetLibrary.list_assets(DEST)}")
return
log(f"class: {sm.get_class().get_name()}")
try:
skel = sm.get_editor_property("skeleton")
tree = skel.get_editor_property("bone_tree")
log(f"bones: {len(tree)}")
except Exception as exc: # noqa
log(f"bone count unavailable: {exc!r}")
try:
mats = sm.get_editor_property("materials")
log(f"material slots: {len(mats)} -> {[m.get_editor_property('material_slot_name') for m in mats]}")
except Exception as exc: # noqa
log(f"material slots unavailable: {exc!r}")
try:
bounds = sm.get_editor_property("bounds")
o, e = bounds.origin, bounds.box_extent
log(f"bounds cm: size=({e.x * 2:.1f}, {e.y * 2:.1f}, {e.z * 2:.1f}) origin=({o.x:.1f}, {o.y:.1f}, {o.z:.1f})")
except Exception as exc: # noqa
log(f"bounds unavailable: {exc!r}")
log(f"assets: {unreal.EditorAssetLibrary.list_assets(DEST)}")
# materials actually imported as assets?
for a in unreal.EditorAssetLibrary.list_assets(DEST):
obj = unreal.EditorAssetLibrary.load_asset(a)
log(f" {a} : {obj.get_class().get_name()}")
import_character()
import_texture_and_material()
verify()
with open(REPORT, "w") as f:
f.write("\n".join(lines) + "\n")
+60
View File
@@ -0,0 +1,60 @@
# Unreal-side half of the scale test: imports the Blender scale-test FBX with
# the named import preset "ue_metric_v1" and measures the result.
#
# Run headless:
# <engine>/Engine/Binaries/Linux/UnrealEditor-Cmd <uproject> -run=pythonscript \
# -script=<repo>/Tools/ue_scale_test_import.py -nullrhi -unattended -nopause -stdout
import unreal
FBX = "/tmp/gigline/scaletest.fbx"
DEST = "/Game/Test/ScaleTest"
def import_fbx():
task = unreal.AssetImportTask()
task.filename = FBX
task.destination_path = DEST
task.automated = True
task.save = True
task.replace_existing = True
ui = unreal.FbxImportUI()
ui.import_mesh = True
ui.import_materials = True
ui.import_textures = False
ui.import_as_skeletal = False
smd = ui.static_mesh_import_data
smd.import_uniform_scale = 1.0
smd.combine_meshes = False
smd.convert_scene_unit = False
smd.force_front_x_axis = False
smd.normal_import_method = unreal.FBXNormalImportMethod.FBXNIM_IMPORT_NORMALS
task.options = ui
unreal.AssetToolsHelpers.get_asset_tools().import_asset_tasks([task])
unreal.EditorLoadingAndSavingUtils.save_dirty_packages(True, True)
def measure():
# Named import preset "ue_metric_v1" measures in Unreal centimetres.
lines = ["---- measured local-space bounds (cm) ----"]
for name in ("ScaleTest_A", "ScaleTest_B", "ScaleTest_C"):
path = f"{DEST}/{name}"
asset = unreal.EditorAssetLibrary.load_asset(path)
if asset is None:
lines.append(f"MISSING asset {path}")
continue
box = asset.get_bounding_box()
mn, mx = box.min, box.max
lines.append(
f"{name}: origin=({(mn.x + mx.x) / 2:.2f}, {(mn.y + mx.y) / 2:.2f}, {(mn.z + mx.z) / 2:.2f}) "
f"size=({mx.x - mn.x:.2f}, {mx.y - mn.y:.2f}, {mx.z - mn.z:.2f})"
)
lines.append(f"assets: {unreal.EditorAssetLibrary.list_assets(DEST)}")
with open("/tmp/ue-scale-test-result.txt", "w") as f:
f.write("\n".join(lines))
import_fbx()
measure()