Repository navigation
Asset Packs
Content Studio and Fit Lab can fit parts of third-party modular 3D packs (outfits, helmets, weapons) onto the UO
body and export them as UO equipment. Each pack is described by one asset-pack mapping: a JSON document
(schema: spritemotion.asset-pack) that says how the pack's skeleton fits the UO rig and which UO layer each part
becomes.
Source: docs/asset-packs.md.
Packs are handled 1:1. Nothing in the studio is specific to a pack: tools/uo-content/pack_fit.py reads the mapping,
and a job selects it with pack_mapping. A mapping holds two tables:
-
Bones → target rig (
uo-model3d-v13): how each pack bone is fitted onto the canonical UO rig. -
Parts → equipment slots: which UO layer each part type becomes, from
games/ultima-online/equipment/layers.json.
A fictional, validated example: examples/asset-pack/example-modular.json.
The CC0 starter has a real one: examples/cc0-starter/outfit-mapping.json.
Most commercial packs forbid redistributing their meshes, textures and data. Keep a commercial pack's mapping, its
batch scripts and every output outside the repository, in a local sidecar folder (SPRITEMOTION_SIDECAR,
default ../SpriteMotion-Sidecar) that is never pushed. The pack's own files are located through the environment
variable named in the mapping's source.env; they are never copied.
A mapping may go in games/<game>/asset-packs/ only if its pack's license allows it.
bones lists every bone of the pack's skeleton.
| Field | Meaning |
|---|---|
role |
root, deform, twist, finger, attach, face, dynamic, ik, prop
|
target |
Target-rig bone that receives this bone's weights |
end |
On aligned bones: the source bone at the far end of the chain |
keep_orientation |
Aligned bone that is moved only, not rotated or scaled (spine, head) |
target_end |
Optional target bone ending the aligned chain (e.g. a knuckle for wrist-to-knuckle chains) |
end_from_parent |
With end: null, extrapolate the previous segment for a terminal finger |
follows |
For a non-aligned bone: the aligned ancestor it moves with |
proposed_target |
A better rig target not yet used by the fitter (twist bones, fingers, shield bone) |
Fit per source mesh: each aligned chain is rotated onto its target bone and scaled to its length (clamped). Every
vertex follows the weighted blend of its bones; non-aligned bones use their nearest aligned ancestor.
dynamic_chains covers cloth/hair bones that ship inside part files; attach_points lists sockets.
One entry per part type.
| Field | Meaning |
|---|---|
status |
uo (becomes an item on uo_layer), merge, extra, body, skip
|
uo_layer |
Layer id from layers.json
|
alternatives |
Other layers that fit some pieces |
pair |
The mirrored part; left and right become one item (one UO layer = one item) |
studio_part |
Fit template: helm, chest, arms, gloves, legs, boots, robe, cloak, skirt, weapon, shield, bow, quiver
|
bind |
skinned or rigid
|
offset, rotate, scale, depth_scale
|
Rest-pose fit |
surface_clearance |
Push close-fitting shells out of the body surface before binding |
hide_body |
{enabled, outward, inward}: hide body faces under the part |
uncovered_layers lists animated UO layers the pack has nothing for. Tune offset, rotate, scale, bind and
hide_body per slot in Fit Lab.
| Id | Layer | Animated | Id | Layer | Animated |
|---|---|---|---|---|---|
| 1 | OneHanded | yes | 14 | Bracelet | paperdoll only |
| 2 | TwoHanded (shields, bows, 2H weapons) | yes | 15 | Face | yes |
| 3 | Shoes | yes | 16 | FacialHair | yes |
| 4 | Pants | yes | 17 | MiddleTorso | yes |
| 5 | Shirt | yes | 18 | Earrings | paperdoll only |
| 6 | Helm | yes | 19 | Arms | yes |
| 7 | Gloves | yes | 20 | Cloak (also quivers) | yes |
| 8 | Ring | paperdoll only | 21 | Backpack | yes |
| 9 | Talisman | paperdoll only | 22 | OuterTorso | yes |
| 10 | Neck | yes | 23 | OuterLegs | yes |
| 11 | Hair | yes | 24 | InnerLegs | yes |
| 12 | Waist | yes | 25 | Mount | internal |
| 13 | InnerTorso | yes |
- Dump the pack's skeleton and the bones each part type is weighted to (a Blender script importing the base model and a few part files per type).
- Write the mapping: align the main chains (pelvis, spine, neck, head, clavicles, arms, hands, legs, feet, toes), let everything else follow, map part types to layers, mark pairs, use rigid binding for hard pieces.
- Validate:
An empty list means valid.
python -c "from spritemotion.schemas import validate, read_json; print(validate(read_json('<file>'), 'spritemotion.asset-pack', required=True))"
- Build a preview job with
source_files,paletteandpack_mapping, and review the contact sheet before full builds. - Export it to Fit Lab (
python tools/fit-lab/run.py export --pack <pack>) and tune slots there.
After a mapping change, regenerate the GLBs and create new render jobs; existing jobs keep their frozen mapping.
SpriteMotion is MIT licensed · Repository · License · Third-party notices
Not affiliated with or endorsed by Electronic Arts or Broadsword. Ultima Online is a trademark of its owners. No game data is distributed with this project.
Start
Guides
- Fit a slot in Fit Lab
- Render an item end to end
- Clean up with masking
- Troubleshoot a failing step
- Take content into GUO
Tools
Reference
Project