Skip to content

Fit Lab

Moshu edited this page Oct 6, 2026 · 4 revisions

Fit Lab

Fit Lab shows equipment parts on the animated UO body through the UO camera, lets you tune each equipment slot's fit, hides body faces under clothes to stop poke-through, and renders the real Blender output beside the live preview.

Fit Lab tour: the walk turning through all 8 facings

▶ Watch the Fit Lab tour (MP4)

New look, facing ring and 2×2 view: the screenshots on this page show the DirectorDeck-styled Fit Lab from PR #19 (pending merge). Until it is merged, main has the previous styling, the old direction ring and the single 3D view; everything else on this page already applies.

Step by step: Guide: Fit a slot in Fit Lab. Source guide: tools/fit-lab/README.md.

Start

Fit Lab works on a prepared pack export: a body GLB, fitted item GLBs and a manifest, created once by Blender.

Pack Commands
Bundled CC0 starter launchers\editor\workbench.bat cc0-starter, or python tools/starter-assets/run.py --prepare-lab then python tools/fit-lab/run.py serve --pack cc0-starter
Your own mapped pack python <sidecar>/tools/<pack>/lab_items.py --per-slot 4, then python tools/fit-lab/run.py export --pack <pack>, then launchers\editor\fit-lab.bat <pack>

Open http://127.0.0.1:8774. You can set SPRITEMOTION_FIT_PACK instead of passing the pack name. Exports are reused; --force redoes them. Data lives in workspace/ultima-online/fit-lab/<pack>/. The launcher does not turn an arbitrary folder of raw models into a pack; see Asset Packs.

Layout

Fit Lab overview

Area Contents
Top Pack, save status, Undo, Redo, Save, Open Content Studio link
Left Slot dropdown and its items. Tick items to show them in 3D; click one (or its preview card) to select it. Load fitted GLBs from a folder.
Centre Live 3D (drag to orbit, 1 view / 2×2, UO camera resets, Hidden faces), the Blender render pane, the action dropdown, the facing ring, Play and the frame slider. Below: enlarged slot previews with their own Animate, Cycle directions, Size, Base, Poke pixels and Steady head controls.
Right Tabs: Fit, Corrections, Render, Measure, History

Facing ring and 2×2 view

The facing ring and transport bar

Each number on the ring sits where the character faces on screen under the UO camera: 0 faces the viewer, then each step turns 45° clockwise (1 down-left, 2 left, 3 up-left, 4 away, 5 up-right, 6 right, 7 down-right). The needle and the label beside the ring show the current facing. Click a number, or focus the ring and use the arrow keys to turn one step. Dashed 5–7 are mirrored from 3–1, as in the client.

Front and back views of a CC0 vest during the walk

1 view / 2×2 (remembered per browser) switches the 3D pane between the orbit view and four views: the orbit view plus fixed Front, Side and Back views that turn with the character, so Front always shows the face. Use it to check a garment from every side while you adjust one slider. The animation, hidden-face overlay and item visibility apply to all four.

2×2 view while fitting the vest

Fit tab

  • Slot fit: Offset X/Y/Z (cm), Rotate X/Y/Z (degrees), Scale, Front/back depth and Binding (skinned deforms with its bones; rigid follows one bone, for metal that must not stretch). This is the base for every animation and direction.
  • This item only: an extra offset for the selected item.
  • Left/Right X/Y/Z: separate offsets for the character's left and right side of one item (paired gloves and boots). Left/right refer to the character, not the screen; mirrored directions mirror the fitted result.
  • Hide body under clothes: on/off plus outward and inward distance. Body faces this close to the item are removed from the fitting body so they cannot poke through or cut holes in the sprite.

Front/back depth changes only the rest-pose front-to-back size (Blender Y), before rotation. Use it for a shallow torso shell instead of raising Scale and widening the shoulders.

Hidden body faces (red) under the sleeves, from all four sides

Corrections tab: scoped fixes

Corrections tab

A correction is a delta on top of the slot fit, applied to a target (pack, slot, named group or one item) and optionally only for one action (0–34) and/or one stored direction (0–4). Directions 5/6/7 share the corrections of 3/2/1.

  • Offsets and rotations add; scale and depth multiply.
  • Resolution order: pack, slot, groups, item; within each, all poses, then action, then direction, then action+direction.
  • Groups are created from the checked items in the current slot.
  • Body masking per correction: inherit, clothing (limbs/head only), body (also torso) or none. Back and quiver slots default to body.

The lab and the Blender build resolve corrections with the same rules (Python and JavaScript implementations are parity-tested). Every rendered pose starts from the untouched base geometry, so corrections never accumulate.

Render tab and the Blender render pane

Render tab

  • Build item renders the selected item with a snapshot of the saved fit at Current animation, Preview: 5 animations or All 35 animations coverage.
  • Rebuild changed blocks compares with that item's last successful lab build and re-renders only the action/direction blocks your changes affect, into a new validated revision. The original job is never changed.
  • The Blender render pane shows the newest finished render for the current animation, locked to the main frame and direction. Status: Matches the saved fit or Fit changed since this render. Auto re-render rebuilds once the saved fit has been still for 3 s.
  • Sprite sheet shows every frame of the 5 stored directions; click a cell to jump there. Download sheet and Import package download the results.

Sprite sheet view

Measure tab

Measure tab

Measure slot counts poke pixels (body pixels showing through the item) over every frame of the checked actions in 5 directions. The first run is the baseline; later runs show the change. Run A/B compares the experimental Steady head preview against the original motion on the first three items of every slot, with a downloadable report and contact sheet.

Poke counts are approximate and run high: they do not model the renderer's 1 cm holdout margin or 6 mm push-out. Compare them between runs; don't read them as absolutes. A lower count is not proof of a better match to UO art.

History tab, saving and recovery

History tab

Feature Behaviour
Autosave 800 ms after the last edit; Save saves immediately
Undo / redo Ctrl+Z; Ctrl+Y or Ctrl+Shift+Z (Command on macOS). A slider drag is one step.
History Jump to any of the last 100 steps. Editing after undo discards the redo branch.
Crash recovery Every edit is cached in the browser, scoped to this pack and server
Disk backups Saves atomically replace lab-adjustments.json and keep the previous three in lab-adjustments-backups/. Restore backup is itself an undoable edit.
Conflicts If another tab or tool changed the file, saving pauses: Use disk version or Keep my recovered edits.

Selection, playback and camera changes are not fit edits.

Previews

  • Base: the original UO sprite (extracted from the model's embedded original frames), the 3D body, or transparent content only.
  • Preview cards default to 2x native size with pixelated scaling (up to 4x). Animate and Cycle directions (every two seconds) work independently of the main view.
  • Magenta marks body pixels poking through.

These are live camera renders, not final Blender output. The Blender render is the final word, and an in-game test is the acceptance check.

Loading already-fitted GLBs

Load fitted GLBs from a folder accepts a local folder of self-contained GLBs that already carry skin weights with the canonical body's bone names. Select the destination slot first. Files are copied into an ignored import cache; imports are session-only. Limits: 100 GLBs, 50 MB each. Raw FBX and unrigged objects must go through the pack export workflow.

From the lab to builds

  1. A pack's mapping generator (in your sidecar) merges lab-adjustments.json slot fits into the mapping's part types (offset, rotate, scale, bind, hide_body).
  2. tools/uo-content/blender_build.py applies the mapped part as the default fit and, when hide_body is enabled, deletes the covered body faces before rendering.
  3. Lab builds, and Studio jobs with fit_item, also use item overrides and scoped corrections, snapshotted at job creation.

Local API

GUO and the standalone browser can share one Fit Lab process. Key routes: GET /api/service, GET /api/state, POST /api/adjustments ({adjustments, base_revision}; a stale revision returns HTTP 409), GET/POST /api/build, GET /api/renders?item=<id>, POST /api/assets. Contract: docs/fit-lab-service.md and Data Contracts.

Clone this wiki locally