ENCReload

glTF Import Pitfalls — the Dug-out Canoe Findings (2026-07-30)

The Era-1 Dug-out Canoe (Sketchfab, Matt LeMoine, CC-BY-NC-SA-4.0) is the most pathological asset the Factory has eaten. It forced six real pipeline defects into the open. This guide records what each one looks like from the outside, so the next node-animated Sketchfab model doesn’t cost a full day.

The asset’s pathology (why everything below happened)

Symptom → cause table

Symptom Cause Fix / knob
Whole model wears material 0, rest grey glbconv crashed on byteStride → single-atlas fallback repair the GLB (above)
Multi-material atlas missing one material after config changes extraction cache was mtime-only; a deploy-converted (part-stripped) extraction looked “fresh” for the raw file fixed: <name>.mtl.src source stamp
A rigid decor part (sail) dragged off its mount after bind bone-parent→skin bound it to bones whose animated frame ≠ static layout Static parts (no bone-bind) in the Animation Lab (staticParts) — keeps listed mesh/material substrings weightless at their authored position
deploy_convert silently deletes half the model its default strip list contains "polysurface" (howitzer-era default) put anything in Deploy strip parts (e.g. camera) — a non-empty value replaces the default list
Exported clip is a statue though the source animates multi-slot action: a glTF import packs every object’s animation into ONE action with a slot per object; slots[0] is not the armature’s slot fixed in assign_actiongated to localNodeAnim, see below
Sail/parts collapse to a clump after “fixing” the slot the legacy static path’s captures (flatten, bone-parent) depend on the frozen rig — un-freezing it relocated every capture the slot fix + REST-hold only run under localNodeAnim; the static path is byte-faithful to every proven bake

Current state of node-animation support

The resolution: strip + synthesize instead of transplant (2026-07-31)

Rather than keep fighting the source’s take, the Vehicle Lab now covers this whole class of model:

The wave rock, as shipped

Two INDEPENDENT sine waves on the RootHull bone — no ratios or multipliers, each stated in its own terms, because coupling them made every adjustment move something unintended:

Clip length (frames)                  120  = 5.0s at 24fps
Roll  — amount (deg) / swings per clip  6 / 1     side to side, about the hull's length
Pitch — amount (deg) / swings per clip  2.4 / 1   bow up and down, across the hull
      — offset (deg)                    90
Axis  — hull length / heading          Auto / 0

Cycle counts are integers so the loop always closes without a pop. The offset is what keeps an equal-speed rock two-dimensional: at 0 the two swings stay in lockstep and the hull tilts along one fixed diagonal (reads as a single axis); at 90 it traces an ellipse, the hull circling as it bobs. Frame 0 is then a slightly heeled pose — the loop still closes exactly and the bake’s rest-normalize adopts it, so bind == frame 0 holds. Roll 0 with a pitch value is a valid pitch-only rock.

Also in the Lab: Orientation (X/Y/Z straighten, applied to the DATA before anything measures the model, so axle/tread/hull-length inference all read the corrected pose), a waterline grid in the preview to judge it against, New model to clear a session, and a window scrollbar.

VERIFIED IN-GAME 2026-07-31 — the canoe floats and rocks. Registry: modelFile = the generated _Wave.glb, animClip = Spin, staticParts empty, localNodeAnim off.

Flow for a floating unit: Vehicle Lab → New model → Browse + Probe parts → mark unwanted parts Ignore → set the rock → Generate rig → point the Model Factory entry’s Model file at the generated GLB with clip Spin, animated + Convert-raw-rig → Bake. This bypasses a broken source animation entirely: the motion is authored, not transplanted.

Trap — a stale generated GLB. The rig step is a separate button from the bake, so the GLB can lag the Lab’s settings silently. Spin’s frame range should equal your Rock cycle exactly (120 → Spin (0,120)); if it reads anything else, the GLB predates the current settings — re-run Generate rig. Older files also carry the source’s own clip (e.g. Take 001) beside Spin: picking it bakes a unit that never moves, with nothing in the logs to explain why. Newly generated rigs purge it.

Debugging method that finally worked

Iterate headless, without Unity, and look at the result:

  1. Run the converter directly (empty argv strings vanish in PowerShell 5.1 — drive Blender from bash): blender -b -P Tools/rig_anim.py -- <glb> <out.fbx> 24000 "" "<clip>" "" 1 "0,180,0" 1 "" 0 "" 1 "<staticParts>" <localNodeAnim 0|1>
  2. Measure placement numerically (cloth/part verts as % of model bbox) and motion (max per-vert displacement across frames) by re-importing the FBX in a second Blender run.
  3. Render the baked FBX to PNG (Workbench engine, camera auto-framed from evaluated bounds) and look at it. The Lab preview shows the bind pose only — a clip-playing bake can look wrong in the preview and right in-game, and vice versa. The render at a mid-clip frame is the truth.