|
| 1 | +<?xml version="1.0" encoding="utf-8"?> |
| 2 | +<object type="traktor.mcp.SkillAsset"> |
| 3 | + <name>edit-skinned-rig</name> |
| 4 | + <description>Add or modify joints, geometry, skin weights and animation-take poses on an existing skinned character in place, without regenerating it.</description> |
| 5 | + <whenToUse><![CDATA[Asked to extend or edit an existing skinned/animated character or creature — add joints (e.g. forearms, hands, feet), re-skin part of the mesh, or add joints to existing animation takes — while preserving the current geometry and animation clips. For building a character from scratch, use create-animated-character instead.]]></whenToUse> |
| 6 | + <body><![CDATA[# Edit an existing skinned rig in place |
| 7 | +
|
| 8 | +Extend or modify the skinned character "{{mesh}}" — add joints, re-skin geometry, and/or add joints to its animation takes — **without regenerating it**, so existing geometry and clips are preserved. This is the in-place counterpart to *create-animated-character* (use that to build one from scratch); it relies on the `model_*` tools, which read/write an existing `.tmd` directly. |
| 9 | +
|
| 10 | +## Golden rule |
| 11 | +Edit the model **in place** (open → mutate → save `overwriteSource`). Do NOT rebuild via `create_mesh_from_geometry`: that path cannot read back existing keyframe poses, so it silently destroys hand-made animation. Only the `model_*` tools can extend a rig non-destructively. |
| 12 | +
|
| 13 | +## Pipeline |
| 14 | +`model_open {meshAsset}` → `model_get_elements` (read) / `model_edit` (mutate) / `model_apply_operation` → `model_save {overwriteSource}` → `build_asset` the mesh + SkeletonAsset + every AnimationAsset. |
| 15 | +
|
| 16 | +## 1. Open and map the existing rig |
| 17 | +- `model_open { "meshAsset": "{{mesh}}" }` → `handle`. `model_inspect` for counts + joint/animation lists. |
| 18 | +- `model_get_elements` kinds `joints`, `positions`, `vertices`, `polygons`, and `pose` (needs `animation`+`keyFrame`). A box-man is **one rigid box per joint**: 24 vertices (6 faces × 4) + 6 quads, each vertex weight 1.0 to a single joint. Box *k* usually occupies positions/vertices `24k..24k+23` and polygons `12k..12k+11` (verify). |
| 19 | +
|
| 20 | +## 2. Add joints (`model_edit` `addJoint`) |
| 21 | +- Joints are **appended** at the end (indices keep growing); existing pose/vertex data indexed 0..n-1 stays valid. |
| 22 | +- `translation` is **LOCAL** = `worldChild − worldParent`; `rotation` `[0,0,0,1]`; `parent` = an existing joint index. |
| 23 | +- Make new joints **children of the joints already animated** (forearm→upper-arm, foot→shin) so existing takes still carry them along — *after* you key them (step 4). |
| 24 | +
|
| 25 | +## 3. Re-skin so new joints deform the mesh (optional) |
| 26 | +Rigid box-man workflow, all additive: |
| 27 | +- `removePolygons {indices}` for the box(es) you're replacing. Orphaned positions/vertices are harmless (unreferenced = not drawn). |
| 28 | +- Append new boxes: `addPosition` (8 corners), `addNormal` (reuse 6 face normals ±X/±Y/±Z), `addVertex` (24: `position`,`normal`,`jointInfluences:[{joint,weight:1.0}]`), `addPolygon` (6 quads, `material:0`, `normal:-1`). |
| 29 | +- **Winding**: Traktor's front face is **clockwise seen from outside** — the triangle's right-hand normal must point *inward*. Auto-orient each face: if `dot(cross(p1−p0, p2−p0), outwardNormal) > 0`, reverse the vertex order. |
| 30 | +- Polygons store `normal:-1`; lighting uses per-vertex normals, so give each vertex the true **outward** normal. |
| 31 | +- `model_apply_operation` `Triangulate` after adding quads. |
| 32 | +- Indices are deterministic (appends start at the current counts). Add in batches positions→normals→vertices→polygons and check the returned counts between batches; generate the arrays with a script to avoid index/winding mistakes. |
| 33 | +
|
| 34 | +## 4. Add joints to the animation takes — REQUIRED if you added joints |
| 35 | +**Every keyframe must carry an entry for every joint.** A joint absent from a keyframe is baked as **identity**, not bind — verified in engine source (`Model/Pose.cpp` `getJointTransform` returns `Transform::identity()`; `AnimationPipeline.cpp` bakes `pose.getJointTransform(k)` per skeleton joint). Identity local = zero offset from parent, so the limb **collapses onto its parent's origin** at runtime. |
| 36 | +- For each take and each keyframe, `model_edit` `setPoseJoint {animation, keyFrame, joint, translation, rotation}` for each new joint, in **ascending joint order** (the pose is a dense array indexed by joint id; ascending appends fill it with no gaps). |
| 37 | +- For a new joint that shouldn't move on its own, pass its **bind local** transform (the joint's `translation`, rotation `[0,0,0,1]`). Motion then comes entirely from the animated parent. To actually articulate it, key real rotations instead. |
| 38 | +- `setPoseJoint` only edits the named joint, so joints 0..n-1 keep their exact curves. Read a pose back with `model_get_elements kind=pose` to confirm (`total` should reach the skeleton joint count). |
| 39 | +
|
| 40 | +## 5. Verify, then commit |
| 41 | +- Save to a **throwaway** asset first: `model_save { meshAsset: { path:"Models/X_check", fileName:"Models/X_check.tmd", meshType:"skinned" } }` → `build_asset` → `render_mesh_preview` to check shape/winding (this renders the **bind pose** only). |
| 42 | +- When good, `model_save { overwriteSource: true }` to write the real `.tmd`. Delete the temp instance (`delete_instance`) and its file. |
| 43 | +
|
| 44 | +## 6. Rebuild |
| 45 | +`build_asset` (rebuild) the mesh, the `SkeletonAsset`, and **each** `AnimationAsset` — they all import from the `.tmd`, so new joints and pose entries only reach runtime after rebuilding. |
| 46 | +
|
| 47 | +## Gotchas (hard-won) |
| 48 | +- **Absent pose joint → identity → limb snaps to origin** (see step 4). This is the #1 trap; render preview won't catch it because it only shows bind pose. |
| 49 | +- **Skeleton↔take matching is by joint NAME** in the pipeline (`findJointIndex`); a rename or typo logs `No such joint` and drops it. Keep names stable and consistent between skeleton and takes. |
| 50 | +- **No `removeAnimation`/`removeKeyFrame`** in the Model API — you can only add/edit poses and keyframes. |
| 51 | +- **Winding/weights** as in step 3 — same rules as *create-animated-character*. |
| 52 | +- `render_mesh_preview` needs a MeshAsset and shows only the static bind pose; there is no MCP tool to render an animated frame, and `Traktor.Run.App` does not link the animation/database classes — so runtime animation must be checked by launching the game from the editor. |
| 53 | +]]></body> |
| 54 | + <engineVersion/> |
| 55 | + <published>true</published> |
| 56 | + <parameters> |
| 57 | + <item type="traktor.mcp.SkillParameter"> |
| 58 | + <name>mesh</name> |
| 59 | + <description>MeshAsset path or guid of the character to edit</description> |
| 60 | + <defaultValue>Models/Humanoid</defaultValue> |
| 61 | + </item> |
| 62 | + </parameters> |
| 63 | +</object> |
0 commit comments