@rig-c/pose 0.2.0 → 0.2.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/README.md CHANGED
@@ -8,6 +8,16 @@ Part of the rig-c toolchain: [core](https://github.com/rig-c/core) compiles and
8
8
 
9
9
  <!-- demo -->
10
10
 
11
+ ## Try it
12
+
13
+ [`examples/wave`](examples/wave) runs with nothing else: a bones-only stick rig (no art), a 3-second wave made from joint angles (`make-pose.ts` writes it), and the map that joins them.
14
+
15
+ ```sh
16
+ rig-pose retarget --rig examples/wave/rig --map examples/wave/map.json --pose examples/wave/pose.json --out wave.motion.json --build wave-build
17
+ ```
18
+
19
+ It prints the gate's verdict and where every mapped bone landed, then writes `wave.motion.json` and the gated build. `rigc preview --candidate wave-build` plays it in the Spine Web Player. A rig with no art shows only its bones.
20
+
11
21
  ## What it does
12
22
 
13
23
  ```sh
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rig-c/pose",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "Puts motion from a body-18 pose sequence onto a rig built with rig-c: bone keys in rig-c's motion format, checked through rig-c's gate before anything is written. A CLI for agents that cannot see the rig.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/rigc.ts CHANGED
@@ -100,18 +100,27 @@ export function gate(rig: string, motion: Record<string, unknown>, dir: string,
100
100
  mkdirSync(dir, { recursive: true });
101
101
  const motionPath = join(dir, 'motion.json');
102
102
  writeFileSync(motionPath, `${JSON.stringify(motion, null, 2)}\n`);
103
- const build = join(dir, 'build');
104
- // --copy-images: a build's page paths are relative to its directory, and the
105
- // gated build is copied out of the scratch directory for `--build`, so its
106
- // pages travel with it (rigc refuses to draw a moved build otherwise).
107
- const run = runRigc(['build', '--rig', rig, '--motion', motionPath, '--out', build, '--profile', profile, '--copy-images', '--report', join(dir, 'build-report.json')]);
103
+ return buildInto(rig, motionPath, join(dir, 'build'), profile, join(dir, 'build-report.json'));
104
+ }
105
+
106
+ /**
107
+ * `rigc build` of `rig` with the motion spec at `motionPath` into `out`, under
108
+ * rigc's gate (it writes nothing when red). rigc records each source image's
109
+ * path relative to `out` (`images[].page` in the model document), so a build is
110
+ * only right where it was built: the run gates in a scratch directory, and the
111
+ * build `--build` keeps is built again, by rigc, where it will live (#18) —
112
+ * never copied out, which would carry a path climbing from the scratch
113
+ * directory to the filesystem root. --copy-images puts the pages beside it.
114
+ */
115
+ export function buildInto(rig: string, motionPath: string, out: string, profile: string, report?: string): Gate {
116
+ const run = runRigc(['build', '--rig', rig, '--motion', motionPath, '--out', out, '--profile', profile, '--copy-images', ...(report === undefined ? [] : ['--report', report])]);
108
117
  const lines = run.out.split('\n');
109
118
  return {
110
119
  run,
111
120
  green: run.status === 0,
112
121
  summary: lines.find((l) => /assertions: \d+ measured/.test(l))?.replace(/^[\s.]+/, '') ?? null,
113
122
  fails: lines.filter((l) => /^\s*FAIL\s/.test(l) || l.startsWith('rigc compile error')).map((l) => l.trim()),
114
- build,
123
+ build: out,
115
124
  };
116
125
  }
117
126
 
package/src/run.ts CHANGED
@@ -5,12 +5,12 @@
5
5
  */
6
6
  import { cpSync, existsSync, mkdtempSync, readFileSync, rmSync, statSync, writeFileSync } from 'node:fs';
7
7
  import { tmpdir } from 'node:os';
8
- import { dirname, join } from 'node:path';
8
+ import { dirname, join, resolve } from 'node:path';
9
9
  import { readMap, resolveMap } from './map.ts';
10
10
  import { convertCoco17, personTrack, readPoseSequence } from './pose.ts';
11
11
  import { lineOf, Problems, Refusal, type Problem } from './problems.ts';
12
12
  import { bendLine, flipLine, holdLine, retarget, type RetargetOptions } from './retarget.ts';
13
- import { baseMotionOf, folds, gate, readBuild, resolveRig, restOf } from './rigc.ts';
13
+ import { baseMotionOf, buildInto, folds, gate, readBuild, resolveRig, restOf } from './rigc.ts';
14
14
  import { landed, measureLanding, measurePosture, poseFrames, postureLines, ANGLE_TOLERANCE, ORIGIN_TOLERANCE } from './verify.ts';
15
15
 
16
16
  export interface RetargetArgs extends RetargetOptions {
@@ -109,17 +109,21 @@ export function runRetarget(a: RetargetArgs, log: (line: string) => void): RunRe
109
109
 
110
110
  const dts = r.times.slice(1).map((t, i) => t - r.times[i]!).sort((x, y) => x - y);
111
111
  const fps = dts.length === 0 ? 12 : Math.max(1, Math.min(60, Math.round(1 / dts[dts.length >> 1]!)));
112
- const f = folds(g1.build, a.name, join(scratch, 'folds'), fps);
113
- if (f.meshes === null) say(` FOLD rigc render --folds did not read (exit ${f.run.status}); folds not measured`);
112
+ // A rig that draws nothing (bones only) has nothing to fold, and rigc's render refuses it.
113
+ const f = doc.slots.length === 0 ? { run: { args: [], status: 0, out: '' }, meshes: [], frames: '' } : folds(g1.build, a.name, join(scratch, 'folds'), fps);
114
+ if (doc.slots.length === 0) say(' FOLD the rig has no slots, so it draws nothing to fold; not measured');
115
+ else if (f.meshes === null) say(` FOLD rigc render --folds did not read (exit ${f.run.status}); folds not measured`);
114
116
  else if (f.meshes.length === 0) say(` FOLD rigc render --folds at ${fps} fps: no mesh folds`);
115
117
  else for (const m of f.meshes) say(` FOLD mesh "${m.attachment}" (slot "${m.slot}") folds on ${m.times.length} frame(s) at ${fps} fps: t=${spans(m.times, 1 / fps)}`);
116
118
 
117
- writeFileSync(a.out, `${JSON.stringify(motion, null, 2)}\n`);
118
119
  if (a.build !== undefined) {
120
+ // Built again where it will live, not copied: rigc's page paths are relative to the build (#18).
119
121
  rmSync(a.build, { recursive: true, force: true });
120
- cpSync(g1.build, a.build, { recursive: true });
122
+ const kept = buildInto(input.rig, join(scratch, 'with', 'motion.json'), resolve(a.build), a.profile);
123
+ if (!kept.green) refuse([{ code: 'RIG_GATE', text: `rigc build into --build ${a.build} is red where the same build was green in scratch (exit ${kept.run.status}); nothing written` }, ...kept.fails.map((x) => ({ code: 'RIGC', text: x }))]);
121
124
  }
122
- if (a.render !== undefined && f.meshes !== null) {
125
+ writeFileSync(a.out, `${JSON.stringify(motion, null, 2)}\n`);
126
+ if (a.render !== undefined && f.meshes !== null && f.frames !== '') {
123
127
  rmSync(a.render, { recursive: true, force: true });
124
128
  cpSync(f.frames, a.render, { recursive: true });
125
129
  }