Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ VERSION - Source of truth for the repo version
| addon-scaffolding | Extensions Platform manifest, file layout, register/unregister symmetry |
| ai-mesh-cleanup | Ordered cleanup for imported generated meshes: units, transform apply, origin, normals, budget, collider |
| bake-high-to-low | Cycles cage-bake of high-poly detail onto a low-poly target as a tangent-space normal map |
| engine-export-presets | Unity Y-up, Godot Z-up, and Unreal centimeter glTF/FBX presets; glTF uses export_yup, FBX uses axis_forward/axis_up |
| engine-export-presets | Unity, Godot and Unreal presets: glTF is +Y up and meters for every engine (export_yup=True; Unreal converts to cm on import), FBX uses axis_forward/axis_up plus centimeter scale |
| operators | `bpy.types.Operator` lifecycle, `bl_idname`, redo, defensive context handling |
| ui-panels | `bpy.types.Panel` declarative `draw()`, layout primitives, conditional UI |
| custom-properties | `bpy.props` annotations, PropertyGroup, PointerProperty, storage tradeoffs |
Expand Down
2 changes: 1 addition & 1 deletion ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ Provider-agnostic GLB-in / engine-ready-out. This repo does not generate meshes.

- **Post-generation cleanup skills.** Import and unit-scale normalization, transform apply and origin, poly-budget decimate, LOD chain, collision mesh. Phase 1 shipped in v0.54.0 as `ai-mesh-cleanup`, four snippets, two rules.
- **High-to-low normal bake.** **Delivered.** Skill `bake-high-to-low`, snippets `setup_bake_target_image.py` / `bake_normal_high_to_low.py` / `save_baked_image.py`, witness `examples/bake-normal-high-to-low/`. Cycles CPU cage bake; statistical gates, not byte-identity. UV transfer and atlas packing remain a later phase. Template integration remains a later phase.
- **Engine export presets.** **Delivered.** Unity (Y-up glTF), Godot (Z-up glTF, meters), Unreal (centimeter glTF bake and FBX `global_scale`). Skill `engine-export-presets`, three snippets, rule `use-correct-axis-rna-per-exporter`, witness `examples/export-preset-axis/`. Draco remains opt-in via `gltf_draco_export.py`.
- **Engine export presets.** **Delivered.** Unity, Godot and Unreal glTF are one spec-compliant export (+Y up, meters; Unreal converts to cm on import, corrected in #346), and Unreal FBX carries `axis_forward`/`axis_up` plus `global_scale`. Skill `engine-export-presets`, three snippets, rule `use-correct-axis-rna-per-exporter`, witness `examples/export-preset-axis/`. Draco remains opt-in via `gltf_draco_export.py`.
- **`ai-asset-pipeline-template/`.** **Delivered.** Third template. Headless: GLB path in; LOD set, convex or box collider, engine-preset export; explicit CI exit codes. Pattern: `templates/headless-batch-script-template/`.
- **Live-session agent bridge.** Research spike, not a committed deliverable. MCP server or socket listener so an agent can execute against a running Blender instance instead of blind `--background` scripts. Built on `templates/extension-addon-template/`. Needs its own design pass. Unpinned.

Expand Down
32 changes: 16 additions & 16 deletions docs/gallery/export-preset-axis/index.html

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions docs/gallery/gltf-export-roundtrip/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -115,11 +115,11 @@ <h3><a class="stretch" href="../uv-layer-grid/">UV Layer Grid</a></h3>
</article>
<article class="card mini">
<div class="card-media">
<img src="../assets/export-preset-axis-hero-640.webp" srcset="../assets/export-preset-axis-hero-640.webp 640w, ../assets/export-preset-axis-hero.webp 1280w" sizes="(min-width: 560px) 340px, 100vw" alt="Two re-imports of a red-and-white radio mast with axis gizmos: the Unity copy stands with blue Z up, the Godot copy lies on the floor with green Y up and blue Z along the mast." width="1280" height="720" loading="lazy" decoding="async" />
<img src="../assets/export-preset-axis-hero-640.webp" srcset="../assets/export-preset-axis-hero-640.webp 640w, ../assets/export-preset-axis-hero.webp 1280w" sizes="(min-width: 560px) 340px, 100vw" alt="Two re-imports of a red-and-white radio mast with axis gizmos: the engine-preset copy stands with blue Z up, the naive Z-up copy lies on the floor with green Y up and blue Z along the mast." width="1280" height="720" loading="lazy" decoding="async" />
</div>
<div class="card-body">
<h3><a class="stretch" href="../export-preset-axis/">Export Preset Axis</a></h3>
<p>A radio mast exported under Unity and Godot glTF presets and re-imported, proving the two files have different vertex orientation</p>
<p>A radio mast exported once with the engine glTF preset (export_yup=True, shared by Unity, Godot and Unreal) and once naively Z-up, then re-imported: the Z-up file lies on its back</p>
</div>
</article>
</div>
Expand Down
8 changes: 4 additions & 4 deletions docs/gallery/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -449,15 +449,15 @@ <h2><a class="stretch" href="gltf-export-roundtrip/">glTF Export Roundtrip</a></
<span class="card-link" aria-hidden="true">View example &rarr;</span>
</div>
</article>
<article class="card" data-tags="export" data-kind="examples" data-name="export-preset-axis" data-search="export preset axis export-preset-axis a radio mast exported under unity and godot gltf presets and re-imported, proving the two files have different vertex orientation export ">
<article class="card" data-tags="export" data-kind="examples" data-name="export-preset-axis" data-search="export preset axis export-preset-axis a radio mast exported once with the engine gltf preset (export_yup=true, shared by unity, godot and unreal) and once naively z-up, then re-imported: the z-up file lies on its back export ">
<div class="card-media">
<img src="assets/export-preset-axis-hero-640.webp" srcset="assets/export-preset-axis-hero-640.webp 640w, assets/export-preset-axis-hero.webp 1280w" sizes="(min-width: 900px) 340px, (min-width: 560px) 50vw, 100vw" alt="Two re-imports of a red-and-white radio mast with axis gizmos: the Unity copy stands with blue Z up, the Godot copy lies on the floor with green Y up and blue Z along the mast." width="1280" height="720" loading="lazy" decoding="async" />
<img src="assets/export-preset-axis-hero-640.webp" srcset="assets/export-preset-axis-hero-640.webp 640w, assets/export-preset-axis-hero.webp 1280w" sizes="(min-width: 900px) 340px, (min-width: 560px) 50vw, 100vw" alt="Two re-imports of a red-and-white radio mast with axis gizmos: the engine-preset copy stands with blue Z up, the naive Z-up copy lies on the floor with green Y up and blue Z along the mast." width="1280" height="720" loading="lazy" decoding="async" />
</div>
<div class="card-body">
<h2><a class="stretch" href="export-preset-axis/">Export Preset Axis</a></h2>
<p class="slug">export-preset-axis</p>
<p class="teaches">A radio mast exported under Unity and Godot glTF presets and re-imported, proving the two files have different vertex orientation</p>
<p class="witnesses"><span class="tag">witnesses</span> Unity export_yup=True round-trips standing (x,y,z); Godot export_yup=False reimports lying along -Y as (x,-z,y); --same-axis exits 9; exporter RNA is guarded so a future kwarg rename fails loudly</p>
<p class="teaches">A radio mast exported once with the engine glTF preset (export_yup=True, shared by Unity, Godot and Unreal) and once naively Z-up, then re-imported: the Z-up file lies on its back</p>
<p class="witnesses"><span class="tag">witnesses</span> The engine preset (export_yup=True) round-trips standing (x,y,z); a naive export_yup=False file reimports lying along -Y as (x,-z,y), as it would in any glTF engine; --same-axis exits 9; exporter RNA is guarded so a future kwarg rename fails loudly</p>
<span class="card-link" aria-hidden="true">View example &rarr;</span>
</div>
</article>
Expand Down
66 changes: 36 additions & 30 deletions examples/export-preset-axis/README.md
Original file line number Diff line number Diff line change
@@ -1,30 +1,37 @@
# Export Preset Axis

A runnable example that exports the same radio-mast mesh under the Unity
and Godot glTF presets from
[`engine-export-presets`](../../skills/engine-export-presets/SKILL.md) and
re-imports both files. The check is on coordinates, not a screenshot: Unity
(`export_yup=True`) stands; Godot (`export_yup=False`) lies.
A runnable example that exports the same radio-mast mesh twice: once with
the engine glTF preset from
[`engine-export-presets`](../../skills/engine-export-presets/SKILL.md)
(`export_yup=True`, the call Unity, Godot and Unreal all share), and once
naively with `export_yup=False`. It re-imports both files. The check is on
coordinates, not a screenshot: the preset copy stands; the Z-up copy lies.

**What it witnesses:** named engine presets are not comments on the same
kwargs. glTF axis RNA is `export_yup`, not FBX `axis_forward` / `axis_up`.
**What it witnesses:** glTF is +Y up by spec, so `export_yup=False` is not
an engine preset, it is a bug every engine shows the same way. glTF axis RNA
is `export_yup`, not FBX `axis_forward` / `axis_up`.

- **Disk POSITION follows the closed form.** Unity bakes
`(x, y, z) -> (x, z, -y)` with no node rotation. Godot writes raw Z-up
`(x, y, z)`. The check reads accessor min/max from the `.gltf` JSON.
- **Re-import proves the conversion.** Blender's importer always treats the
file as Y-up: `blender = (gltf.x, -gltf.z, gltf.y)`. Unity restores the
source. Godot permutes again, so the mast lies along `-Y`.
- **The two reimports differ.** Unity `z_span` matches source height; Godot
`y_span` matches that height. `--same-axis` exports both with
`export_yup=True`; both stand and the differ check exits 9.
- **Disk POSITION follows the closed form.** The preset bakes
`(x, y, z) -> (x, z, -y)` with no node rotation. The naive export writes
raw Z-up `(x, y, z)`. The check reads accessor min/max from the `.gltf`
JSON.
- **Re-import proves the conversion.** Blender's importer, like Unity's,
Godot's and Unreal's, treats the file as Y-up:
`blender = (gltf.x, -gltf.z, gltf.y)`. The preset restores the source. The
Z-up file permutes again, so the mast lies along `-Y`.
- **The two reimports differ.** The preset's `z_span` matches source
height; the Z-up file's `y_span` matches that height. `--same-axis`
exports both with `export_yup=True`; both stand and the differ check
exits 9.
- **Exporter RNA is guarded.** Every kwarg passed must still exist on
`bpy.ops.export_scene.gltf`.

Neighbor of [`gltf-export-roundtrip`](../gltf-export-roundtrip/) (Y-up bake
vs Z-up on disk for one file) and [`unapplied-scale-gltf`](../unapplied-scale-gltf/)
(`export_apply` is modifiers, not object scale). This example names the
Unity vs Godot presets and asserts the re-imported orientations diverge.
(`export_apply` is modifiers, not object scale). Until 0.143.x this example
called the Z-up export the "Godot preset". That was wrong: Godot is Y-up and
imports glTF per the spec, so the lying mast is what a Godot user would have
seen (#346).

The subject is a radio mast chosen because "up" is unmistakable on it: a
stepped concrete footing, a bolted base flange, a tapered mast in red and
Expand All @@ -33,26 +40,25 @@ shrouded microwave dish on a raked arm, an equipment cabinet, and a red
obstruction lamp under a lightning rod. The rod's point is the witnessed tip
vertex.

The still stages the two re-imports side by side: the Unity copy standing on
the left, the Godot copy lying along the floor on the right. Beside each sits
The still stages the two re-imports side by side: the preset copy standing on
the left, the Z-up copy lying along the floor on the right. Beside each sits
a modelled X/Y/Z axis gizmo (red, green, blue) showing that re-import's
frame. The gizmo is measured, not placed: the render path tries all 24
axis-aligned rotations and keeps the one that maps the source vertices onto
the re-imported vertices (worst nearest-vertex distance, printed as
`fit_err`; exit 12 if it exceeds 1 mm). The Unity gizmo comes out as the
identity, blue Z up. The Godot gizmo comes out as `Y -> +Z, Z -> -Y`: green
`fit_err`; exit 12 if it exceeds 1 mm). The preset gizmo comes out as the
identity, blue Z up. The Z-up gizmo comes out as `Y -> +Z, Z -> -Y`: green
Y points up and blue Z runs along the lying mast. The mast didn't fall over.
The Godot file stores Z-up coordinates, and the importer reads them as
Y-up. If `export_yup` were the same on both, the two copies and both gizmos
would match.
The Z-up file stores Z-up coordinates, and the importer reads them as
Y-up, as every glTF consumer does.

## Run

```bash
# Cheap correctness check (no render) - the CI check:
blender --background --python export_preset_axis.py --

# Falsifier: both presets Y-up. Must exit non-zero.
# Falsifier: both exports Y-up. Must exit non-zero.
blender --background --python export_preset_axis.py -- --same-axis

# Also render a still (EEVEE on a GPU host; use --engine cycles on GPU-less hosts):
Expand All @@ -73,10 +79,10 @@ the shared asset-quality helper.
| 2 | argparse / usage; also exporter RNA missing expected glTF kwargs |
| 3 | Source mast is not Z-dominant, or tip drifted |
| 4 | glTF reimport produced no mesh |
| 5 | Unity disk POSITION is not `(x, z, -y)`, or Unity node has rotation |
| 6 | Godot disk POSITION is not raw Z-up; also `--output` produced no file |
| 7 | Unity reimport is not standing |
| 8 | Godot reimport is not lying along Y, or reimported tip mismatch |
| 5 | Preset disk POSITION is not `(x, z, -y)`, or preset node has rotation |
| 6 | Z-up disk POSITION is not raw Z-up; also `--output` produced no file |
| 7 | Preset reimport is not standing |
| 8 | Z-up reimport is not lying along Y, or reimported tip mismatch |
| 9 | Reimported orientations did not differ (`--same-axis` lands here) |
| 10 | Gallery framing violation |
| 11 | `--same-axis` did not collapse the axis difference; also asset-quality floor violation (render path) |
Expand Down
Loading
Loading