@rig-c/parts 1.13.0 → 1.14.0
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 +1 -1
- package/cli.ts +7 -2
- package/docs/AUTHORING.md +134 -30
- package/docs/STABILITY.md +4 -4
- package/package.json +1 -1
- package/src/build.ts +68 -11
- package/src/check.ts +417 -77
- package/src/config.ts +7 -1
- package/src/contour.ts +350 -14
- package/src/instruments.ts +6 -5
- package/src/meshdraft.ts +107 -8
- package/src/requirements.ts +67 -5
- package/src/rig.ts +3 -3
- package/src/scene.ts +2 -2
package/src/build.ts
CHANGED
|
@@ -70,7 +70,7 @@ import {
|
|
|
70
70
|
splitSchedule,
|
|
71
71
|
untestedIntervals,
|
|
72
72
|
} from './autoreplay.ts';
|
|
73
|
-
import {
|
|
73
|
+
import { besideIdleLine, FOLD_LINE, type FoldsLine, buildGateLines, causeLines, IDLE_FPS, IDLE_MAX_PX, maxRgbDiff, REQUIREMENTS_DIR, type CheckReport, type PackLine, DEFAULT_PACK_SHAPE, DEFAULT_PAGE_EDGES, JUDGEMENT_LINES, type JudgementLine, packedBuildArgs, packedBuildLabel, type PackMode, type PackShape, type PageEdges, readFrameSet, rigcFailed, REPORTED_LINES, type ReportedLine, type RigcRunner, runCheck, SEAM_DIR, SEAM_MEAN_BAR, seamPairText, seamRigPxText, SOURCE_LINE, SEAM_PX_BAR, SEAM_PX_LEVEL, SEAM_PX_LEVEL_HIGH, SPINEBOY_YARDSTICK, STILL_DIR } from './check.ts';
|
|
74
74
|
import { type Cut, loadConfig, loadConfigAndAnimations, loadEarlyConfig } from './config.ts';
|
|
75
75
|
import { PartsError, type Problem, problemLine, refuseIfAny } from './errors.ts';
|
|
76
76
|
import type { MeshQualityReport, MotionSchedule } from 'rig-c/mesh';
|
|
@@ -972,13 +972,15 @@ function showFigure(v: unknown): string {
|
|
|
972
972
|
}
|
|
973
973
|
|
|
974
974
|
/**
|
|
975
|
-
* How many of the
|
|
975
|
+
* How many of the bars measured and how many said SKIP, by name — the
|
|
976
976
|
* summary's second half (issue #77): `PASS` reads only the bars that measured,
|
|
977
|
-
* so a run that measured one bar says so on the line that says PASS.
|
|
977
|
+
* so a run that measured one bar says so on the line that says PASS. The
|
|
978
|
+
* count is every bar `check` tallied: the `BARS` of `src/check.ts`, and
|
|
979
|
+
* `ANIMATIONS_DO_NOT_FOLD` where it is written (issue #320).
|
|
978
980
|
*/
|
|
979
981
|
export function barsSummary(r: CheckReport): string {
|
|
980
982
|
const { measured, skipped } = r.bars;
|
|
981
|
-
return `${measured.length} of ${
|
|
983
|
+
return `${measured.length} of ${measured.length + skipped.length} bar(s) measured, ${skipped.length} skipped${skipped.length === 0 ? '' : ` (${skipped.map((s) => s.bar).join(', ')})`}`;
|
|
982
984
|
}
|
|
983
985
|
|
|
984
986
|
/**
|
|
@@ -1028,6 +1030,20 @@ export function judgementLine(name: string, line: JudgementLine | ReportedLine):
|
|
|
1028
1030
|
.join('; ')}`;
|
|
1029
1031
|
}
|
|
1030
1032
|
|
|
1033
|
+
/**
|
|
1034
|
+
* The fold line (issue #320) as the console prints it: the status, what was
|
|
1035
|
+
* read and at what rate, and then each folding mesh on a line of its own,
|
|
1036
|
+
* rigc's own words, with the declaration's why where `may_fold` takes it.
|
|
1037
|
+
*/
|
|
1038
|
+
export function foldsLines(line: FoldsLine): string[] {
|
|
1039
|
+
const physics = line.physics_left_out === 0 ? '' : `, its ${line.physics_left_out} physics constraint(s) left out`;
|
|
1040
|
+
const head =
|
|
1041
|
+
`${FOLD_LINE}: ${line.status} — ${line.animations.length} animation(s) beside the idle read by rigc render --folds at ${line.fps} fps${physics} (${line.animations.join(', ')}): ` +
|
|
1042
|
+
`${line.folding.length} folding mesh(es), ${line.folding_undeclared} not declared in may_fold (<= ${line.bar})` +
|
|
1043
|
+
(line.declared_not_folding.length === 0 ? '' : `; may_fold declares ${line.declared_not_folding.map((d) => `slot "${d.slot}" in "${d.animation}"`).join(', ')}, which do(es) not fold`);
|
|
1044
|
+
return [head, ...line.folding.map((r) => ` folds ${r.line}${r.declared === null ? '' : ` — declared in may_fold: ${r.declared}`}`)];
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1031
1047
|
/** A requirement's line as the console prints it: its name and status, the reason first when NOT MEASURABLE, then its figures as check.json holds them. */
|
|
1032
1048
|
export function requirementText(name: string, line: RequirementLine): string {
|
|
1033
1049
|
const rest = Object.entries(line).filter(([k]) => k !== 'status' && k !== 'reason');
|
|
@@ -1052,7 +1068,7 @@ export function checkStage(input: CheckStageInput, rigc: RigcRunner, bin: string
|
|
|
1052
1068
|
for (const l of packLines(r)) log(` ${l}`);
|
|
1053
1069
|
const fig = r.figures;
|
|
1054
1070
|
if (r.idle === null) log(` loop: SKIP — ${fig.skipped?.loop ?? ''}`);
|
|
1055
|
-
else log(`
|
|
1071
|
+
else log(` ${loopCheckLine(r.idle, fig.loop_max_diff, fig.loop_physics?.length ?? 0)}`);
|
|
1056
1072
|
if (r.loopPhysics !== null) log(` ${r.loopPhysics}`);
|
|
1057
1073
|
const beside = besideIdleLine(r.besideIdle);
|
|
1058
1074
|
if (beside !== null) log(` ${beside}`);
|
|
@@ -1066,6 +1082,8 @@ export function checkStage(input: CheckStageInput, rigc: RigcRunner, bin: string
|
|
|
1066
1082
|
pairs.forEach((row, i) => log(` seam pair ${i + 1} of ${pairs.length}: ${seamPairText(row)}`));
|
|
1067
1083
|
}
|
|
1068
1084
|
for (const name of JUDGEMENT_LINES) log(` ${judgementLine(name, fig[name])}`);
|
|
1085
|
+
const folds = fig[FOLD_LINE];
|
|
1086
|
+
if (folds !== undefined) for (const l of foldsLines(folds)) log(` ${l}`);
|
|
1069
1087
|
for (const name of REPORTED_LINES) log(` ${judgementLine(name, fig[name])}`);
|
|
1070
1088
|
const vsSource = fig[SOURCE_LINE];
|
|
1071
1089
|
if (vsSource !== undefined) log(` ${judgementLine(SOURCE_LINE, vsSource)}`);
|
|
@@ -1216,12 +1234,49 @@ function pixelsOver(a: Raster, b: Raster, level: number): number {
|
|
|
1216
1234
|
return n;
|
|
1217
1235
|
}
|
|
1218
1236
|
|
|
1237
|
+
/**
|
|
1238
|
+
* `check`'s `loop:` line (issue #325). With physics constraints in the rig the
|
|
1239
|
+
* comparison it reports was made with them left out (issue #183), and the line
|
|
1240
|
+
* says so itself rather than leaving it to the `loop physics:` line below: the
|
|
1241
|
+
* loop stage compares the frames rendered with them, and on a rig with physics
|
|
1242
|
+
* those ends differ although this line reads max |d| 0.
|
|
1243
|
+
*/
|
|
1244
|
+
export function loopCheckLine(idle: { frames: number; fps: number; duration: number; lastIndex: number }, max: number | null, physics: number): string {
|
|
1245
|
+
const left = physics === 0 ? '' : ` with the rig's ${physics} physics constraint(s) left out (named on the \`loop physics:\` line; rendered with them, the ends need not meet — \`build --loop --lead-in <n>\`)`;
|
|
1246
|
+
return `loop: idle ${idle.frames} frame(s) at ${idle.fps} fps, f0000 vs f${String(idle.lastIndex).padStart(4, '0')} (t = ${idle.duration}s)${left}: max |d| ${String(max)} (0 required)`;
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
/**
|
|
1250
|
+
* The loop stage's line when the end frame is not frame 0 (issue #325): the
|
|
1251
|
+
* jump as measured (max |d| over R, G and B, and pixels over 40 — the lead-in
|
|
1252
|
+
* line's figures), then why. `physics` is how many physics constraints the rig
|
|
1253
|
+
* behind the frames holds; `build --loop` knows it from `check`, while
|
|
1254
|
+
* `loop --frames` reads frames alone and does not, so its line names both
|
|
1255
|
+
* causes. With physics, rigc's render starts every constraint at rest at frame
|
|
1256
|
+
* 0, so the idle does not end where it starts, and `--lead-in` is the remedy;
|
|
1257
|
+
* with none, the idle itself does not close, which `check`'s `loop:` line
|
|
1258
|
+
* measures — under `build`, which stops at that bar, the branch is reached
|
|
1259
|
+
* only by a difference the bar does not read.
|
|
1260
|
+
*/
|
|
1261
|
+
export function loopJumpLine(lastName: string, max: number, over40: number, physics: number | undefined): string {
|
|
1262
|
+
const jump = `${lastName} differs from f0000.png by max |d| ${max}, ${over40} px over 40, so every frame is kept and the loop will jump at the wrap`;
|
|
1263
|
+
const remedy = '`build --loop --lead-in <n>` plays the idle n periods ahead and keeps the last (docs/AUTHORING.md §5, *build --loop --lead-in*)';
|
|
1264
|
+
if (physics === undefined) {
|
|
1265
|
+
return `${jump}; the frames alone do not say why — if their rig holds a physics constraint, rigc's render starts it at rest at frame 0 and ${remedy}; if it holds none, the idle itself does not close, which \`check\`'s \`loop:\` line (CHECK_LOOP_CLOSES) measures`;
|
|
1266
|
+
}
|
|
1267
|
+
if (physics === 0) {
|
|
1268
|
+
return `${jump}: the rig holds no physics constraint, so the idle itself does not close — \`check\`'s \`loop:\` line (CHECK_LOOP_CLOSES) measures the same two frames, and \`--lead-in\`, which settles physics, is not the remedy`;
|
|
1269
|
+
}
|
|
1270
|
+
return `${jump}: the rig holds ${physics} physics constraint(s), and rigc's render starts every one at rest at frame 0, so the idle does not end where it starts — \`check\`'s \`loop:\` line read its ends with them left out; ${remedy}`;
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1219
1273
|
/**
|
|
1220
1274
|
* Encode a rigc frame set as a looping APNG, indexed APNG or GIF and write it.
|
|
1221
1275
|
* An encoder refusal is a PartsError coded `LOOP_ENCODE`, so it prints like
|
|
1222
|
-
* every other.
|
|
1276
|
+
* every other. `physics` is how many physics constraints the frames' rig holds,
|
|
1277
|
+
* when the caller knows it ({@link loopJumpLine}).
|
|
1223
1278
|
*/
|
|
1224
|
-
export function loopStage(frames: string, out: string, format: LoopFormat, log: Log, window?: LoopWindow): LoopResult {
|
|
1279
|
+
export function loopStage(frames: string, out: string, format: LoopFormat, log: Log, window?: LoopWindow, physics?: number): LoopResult {
|
|
1225
1280
|
const set = readFrameSet(frames);
|
|
1226
1281
|
const images = set.frames.map((fr) => fr.image);
|
|
1227
1282
|
log(`rig-parts loop: ${set.dir} -> ${out}`);
|
|
@@ -1243,7 +1298,8 @@ export function loopStage(frames: string, out: string, format: LoopFormat, log:
|
|
|
1243
1298
|
used = images.slice(0, -1);
|
|
1244
1299
|
log(` ${lastName} equals f0000.png byte for byte, so it is dropped: the loop wraps onto frame 0, and showing it twice would hold that pose for two ticks`);
|
|
1245
1300
|
} else if (images.length > 1) {
|
|
1246
|
-
|
|
1301
|
+
const last = images[images.length - 1];
|
|
1302
|
+
log(` ${loopJumpLine(lastName, maxRgbDiff(images[0], last).max, pixelsOver(images[0], last, 40), physics)}`);
|
|
1247
1303
|
}
|
|
1248
1304
|
const anim: AnimFrame[] = used.map((image) => ({ image, ticks: 1 }));
|
|
1249
1305
|
log(` ${anim.length} frame(s) encoded, ${(anim.length / set.fps).toFixed(3)}s per loop, looping forever`);
|
|
@@ -1462,9 +1518,10 @@ export function build(input: BuildInput, run: BuildRunners, log: Log): BuildResu
|
|
|
1462
1518
|
const window = input.leadIn === undefined ? undefined : { periods: input.leadIn };
|
|
1463
1519
|
if (window !== undefined) rmSync(run.scratch, { recursive: true, force: true });
|
|
1464
1520
|
const frames = window === undefined ? join(out, 'check', 'idle_frames') : leadInFrames(join(out, 'rig'), run.scratch, window.periods, run.check, { pageEdges: input.pageEdges, packShape: input.packShape ?? DEFAULT_PACK_SHAPE });
|
|
1465
|
-
const
|
|
1466
|
-
const
|
|
1467
|
-
const
|
|
1521
|
+
const physics = report.figures.loop_physics?.length ?? 0;
|
|
1522
|
+
const lossless = loopStage(frames, join(out, 'idle.png'), 'apng', say, window, physics);
|
|
1523
|
+
const indexed = loopStage(frames, join(out, 'idle-indexed.png'), 'indexed', say, window, physics);
|
|
1524
|
+
const gif = loopStage(frames, join(out, 'idle.gif'), 'gif', say, window, physics);
|
|
1468
1525
|
if (window !== undefined) rmSync(run.scratch, { recursive: true, force: true });
|
|
1469
1526
|
const fig = (e: PaletteError | null): string => (e === null ? 'lossless' : `max ${e.max}, mean ${e.mean.toFixed(3)}`);
|
|
1470
1527
|
say(`loop: idle.png ${lossless.bytes} B (${fig(lossless.error)}); idle-indexed.png ${indexed.bytes} B (${fig(indexed.error)}); idle.gif ${gif.bytes} B (${fig(gif.error)}) — palette error per channel over all frames`);
|