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:
1 parent
01939de9a0
commit
1c6b956435
21 files changed
+614
-33
No files matched your search
@@ -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
@@ -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
@@ -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
@@ -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.
Binary file not shown.
Binary file not shown.
@@ -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
@@ -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.
|
||||
|
||||
@@ -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))
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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")
|
||||
@@ -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()
|
||||
Reference in new issue
Block a user