rig-parts 1.72.0 → 1.72.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/docs/AUTHORING.md CHANGED
@@ -4518,7 +4518,7 @@ and stay absent).
4518
4518
  | <a id="CONFIG_TURN_HEAD_BONE"></a>`CONFIG_TURN_HEAD_BONE` | `config.turn.roles` is declared without a single bone `"head"` in `config.bones`, or `config.turn.body` without a single bone `"chest"` / `"hip"` (or its declared `bone`) | declare `"head"` (or the body turn root bone) as a single bone in `config.bones` |
4519
4519
  | <a id="CONFIG_TURN_SLIDER_NAME_FREE"></a>`CONFIG_TURN_SLIDER_NAME_FREE` | `config.turn.body.slider` (or the default `"body_yaw_dial"`) collides with a head turn dial (`"yaw"`, `"pitch"`, `"yaw_dial"`, `"pitch_dial"`) or an existing slider constraint in `config.constraints` (#787) | choose a distinct slider name in `config.turn.body.slider` |
4520
4520
  | <a id="RIG_TURN_SURFACE_MESH_GROUPS"></a>`RIG_TURN_SURFACE_MESH_GROUPS` | a surface part contains a blended attachment but lacks one 2.5D group, includes an ungrouped positive influence in any attachment, or also declares a unit/rear role | declare both `turn.roles` and `turn.body`, a body root outside head, surface in one or both groups, and bindings wholly inside those two subtrees |
4521
- | <a id="RIG_TURN_SURFACE_MESH_BINDINGS"></a>`RIG_TURN_SURFACE_MESH_BINDINGS` | an authored surface mesh has unresolved/nonclosing weights or shares a frame moved by a unit/rear turn carrier | provide weighted bindings closing at 1 in the authored frame; use independent chains for parts with different roles |
4521
+ | <a id="RIG_TURN_SURFACE_MESH_BINDINGS"></a>`RIG_TURN_SURFACE_MESH_BINDINGS` | an authored surface mesh has unresolved/nonclosing weights or shares a frame moved by a unit/rear turn carrier; declared stop draw aliases resolve to their links for setup sampling | provide weighted bindings closing at 1 in the authored frame; `<link>_d` resolves only for links declared in `config.stops`; use independent chains for parts with different roles |
4522
4522
  | <a id="RIG_TURN_SURFACE_MESH_ATTACHMENT"></a>`RIG_TURN_SURFACE_MESH_ATTACHMENT` | a surface mesh has an alternate drawing without its own registered depth sampling frame | use the base attachment; explicit islands retain their own slot, attachment and bindings |
4523
4523
  | <a id="RIG_TURN_SURFACE_DEPTH_REQUIRED"></a>`RIG_TURN_SURFACE_DEPTH_REQUIRED` | a part with role `"surface"` in `config.turn.roles` or `config.turn.body.roles` carries no depth sheet in `parts.json` | run `assemble` with `config.depth` or provide `<part>_depth.png` in `parts.json` |
4524
4524
  | <a id="RIG_TURN_GRID_BUDGET"></a>`RIG_TURN_GRID_BUDGET` | a `"surface"` grid exceeds 2,400 triangles on one slot or 4,000 triangles across all `"surface"` slots | lower `config.turn.grid` / `config.turn.body.grid` `[cols, rows]` within the per-slot and total budgets |
@@ -5575,6 +5575,13 @@ dial; see [`CONFIG_TURN_ROLE_UNKNOWN`](#CONFIG_TURN_ROLE_UNKNOWN),
5575
5575
  [`RIG_TURN_SURFACE_MESH_BINDINGS`](#RIG_TURN_SURFACE_MESH_BINDINGS),
5576
5576
  [`CONFIG_TURN_UNIT_MESH_BONES`](#CONFIG_TURN_UNIT_MESH_BONES) and
5577
5577
  [`CONFIG_TURN_SLIDER_NAME_FREE`](#CONFIG_TURN_SLIDER_NAME_FREE).
5578
+ Weighted surfaces may bind chains declared in `config.stops` (#800). The
5579
+ sampler resolves each declared `<link>_d` draw alias and any child parent using
5580
+ it to the original link's setup frame, as `flattenRig` does. Weights, child
5581
+ parents and stops keep their emitted names; Core creates the draw bones later.
5582
+ A name ending in `_d` alone is not an alias: a missing non-stop bone still
5583
+ raises `RIG_TURN_SURFACE_MESH_BINDINGS`. No new config field is required.
5584
+
5578
5585
  A weighted surface spanning the head and body subtrees (#796) takes both
5579
5586
  fields. Declare it as `"surface"` in `turn.roles` or `turn.body.roles` (both is
5580
5587
  also accepted), with both 2.5D groups present. Head descendants belong to the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rig-parts",
3
- "version": "1.72.0",
3
+ "version": "1.72.1",
4
4
  "description": "AI-authored 2D character parts from one anime painting and its See-through layer decomposition: measured parts, weighted-mesh rig specs, verified through rig-c before anything is written. A CLI for agents that cannot see the image.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/turn.ts CHANGED
@@ -1512,12 +1512,25 @@ interface Depth25DGroupSpec {
1512
1512
  }>;
1513
1513
  }
1514
1514
 
1515
+ interface Depth25DTurnRig {
1516
+ bones: Array<{ name: string; parent?: string; x: number; y: number; rotation?: number; length?: number }>;
1517
+ slots: Array<{ name: string; bone: string; attachment: string | null }>;
1518
+ constraints?: Array<Record<string, unknown>>;
1519
+ stops?: ReadonlyArray<{ links: readonly string[] }>;
1520
+ }
1521
+
1522
+ /** Stop draw bones share their link's setup frame; Core emits them later. */
1523
+ function surfaceSetupGraph(rig: Depth25DTurnRig) {
1524
+ const aliases = new Map<string, string>();
1525
+ for (const stop of rig.stops ?? []) for (const link of stop.links) aliases.set(`${link}_d`, link);
1526
+ const resolve = (name: string): string => aliases.get(name) ?? name;
1527
+ // Like flattenRig: resolve parents as well as weights, without changing the rig.
1528
+ const bones = rig.bones.map(b => b.parent === undefined ? b : { ...b, parent: resolve(b.parent) });
1529
+ return { bones, resolve };
1530
+ }
1531
+
1515
1532
  function buildDepth25DTurnGroup(
1516
- rig: {
1517
- bones: Array<{ name: string; parent?: string; x: number; y: number; rotation?: number; length?: number }>;
1518
- slots: Array<{ name: string; bone: string; attachment: string | null }>;
1519
- constraints?: Array<Record<string, unknown>>;
1520
- },
1533
+ rig: Depth25DTurnRig,
1521
1534
  motion: {
1522
1535
  groups?: Record<string, string[]>;
1523
1536
  animations: Record<string, unknown>;
@@ -1814,7 +1827,8 @@ function buildDepth25DTurnGroup(
1814
1827
  for (const [part, { rec, ext, zRef }] of surfaceSheets) {
1815
1828
  const authored = opts.surfaceMeshes?.filter((m) => m.part === part) ?? [];
1816
1829
  if (authored.length > 0) {
1817
- const world = computeWorldTransforms(rig.bones);
1830
+ const setup = surfaceSetupGraph(rig);
1831
+ const world = computeWorldTransforms(setup.bones);
1818
1832
  for (const mesh of authored) {
1819
1833
  // An all-zero timeline can overwrite another slider's deform (Core A40).
1820
1834
  // A sibling with no positive binding in this group owns no timeline here.
@@ -1822,19 +1836,19 @@ function buildDepth25DTurnGroup(
1822
1836
  const vertices = mesh.weights.map((row, index) => {
1823
1837
  let wx = 0, wy = 0, total = 0;
1824
1838
  for (const w of row) {
1825
- const frame = world.get(w.bone);
1839
+ const frame = world.get(setup.resolve(w.bone));
1826
1840
  if (frame === undefined) {
1827
1841
  problems.push({ code: 'RIG_TURN_SURFACE_MESH_BINDINGS', object: `surface mesh "${mesh.slot}/${mesh.attachment}" vertex ${index}`, detail: `names missing bone "${w.bone}"; every influence must resolve in the final setup` });
1828
1842
  continue;
1829
1843
  }
1830
1844
  // A unit/rear mesh sharing this root would move our bindings too.
1831
- let ancestor = rig.bones.find((b) => b.name === w.bone);
1845
+ let ancestor = setup.bones.find((b) => b.name === setup.resolve(w.bone));
1832
1846
  while (ancestor !== undefined) {
1833
1847
  if ([FACESHIFT_BONE, REAR_SHIFT_BONE, BODYSHIFT_BONE, BODY_REAR_SHIFT_BONE].includes(ancestor.name)) {
1834
1848
  problems.push({ code: 'RIG_TURN_SURFACE_MESH_BINDINGS', object: `${group.fieldPrefix}.roles.${part}`, detail: `binds "${w.bone}" through turn carrier "${ancestor.name}" shared with another role; surface mesh bindings must retain their authored frame` });
1835
1849
  break;
1836
1850
  }
1837
- ancestor = rig.bones.find((b) => b.name === ancestor?.parent);
1851
+ ancestor = setup.bones.find((b) => b.name === ancestor?.parent);
1838
1852
  }
1839
1853
  const [x, y] = toWorld(frame, w.x, w.y);
1840
1854
  wx += x * w.weight; wy += y * w.weight; total += w.weight;
@@ -1858,7 +1872,7 @@ function buildDepth25DTurnGroup(
1858
1872
  const d = -depth * Math.sin(deg * Math.PI / 180);
1859
1873
  return row.flatMap((w) => {
1860
1874
  if (mesh.groups !== undefined && (w.weight === 0 || mesh.groups.get(w.bone) !== (group.fieldPrefix === 'config.turn' ? 'head' : 'body'))) return [0, 0];
1861
- const frame = world.get(w.bone)!; // every influence resolved before emitting keys
1875
+ const frame = world.get(setup.resolve(w.bone))!; // every influence resolved before emitting keys
1862
1876
  const [x, y] = toBoneLocalVector(frame, kind === 'yaw' ? d : 0, kind === 'pitch' ? d : 0);
1863
1877
  return [pyRound(x, 6), pyRound(y, 6)];
1864
1878
  });
@@ -1947,11 +1961,7 @@ function buildDepth25DTurnGroup(
1947
1961
  * `derive` and `deform` `transform` (`depth: true, narrowing: false`).
1948
1962
  */
1949
1963
  export function buildDepth25DTurn(
1950
- rig: {
1951
- bones: Array<{ name: string; parent?: string; x: number; y: number; rotation?: number; length?: number }>;
1952
- slots: Array<{ name: string; bone: string; attachment: string | null }>;
1953
- constraints?: Array<Record<string, unknown>>;
1954
- },
1964
+ rig: Depth25DTurnRig,
1955
1965
  motion: {
1956
1966
  groups?: Record<string, string[]>;
1957
1967
  animations: Record<string, unknown>;
@@ -1963,11 +1973,12 @@ export function buildDepth25DTurn(
1963
1973
 
1964
1974
  // Only a surface opt-in spanning both authored subtrees changes paths (#796).
1965
1975
  // Single-group meshes keep #795's role and bytes, including explicit islands.
1966
- const parents = new Map(rig.bones.map((b) => [b.name, b.parent]));
1976
+ const setup = surfaceSetupGraph(rig);
1977
+ const parents = new Map(setup.bones.map((b) => [b.name, b.parent]));
1967
1978
  const bodyRoot = opts.turn.body?.bone ?? (parents.has('chest') ? 'chest' : 'hip');
1968
1979
  const descends = (bone: string, root: string): boolean => {
1969
1980
  const seen = new Set<string>();
1970
- for (let cur: string | undefined = bone; cur !== undefined && !seen.has(cur); cur = parents.get(cur)) {
1981
+ for (let cur: string | undefined = setup.resolve(bone); cur !== undefined && !seen.has(cur); cur = parents.get(cur)) {
1971
1982
  if (cur === root) return true;
1972
1983
  seen.add(cur);
1973
1984
  }