rig-parts 2.8.0 → 2.9.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 CHANGED
@@ -276,7 +276,7 @@ rig-parts assemble --source painting.png --full layers/full --head layers/head -
276
276
  # -> read the `uncovered hole N:` lines and look at work/render/recomposite_error_rig.png:
277
277
  # red is painting that no part holds, and no later gate can see it
278
278
  # -> a large red hole neither run holds? add an assemble.patches entry (cut from the painting) and assemble again
279
- rig-parts propose --parts work/rig --source painting.png --out work
279
+ rig-parts propose --parts work/rig --bust-landmarks bust-landmarks.json --source painting.png --out work
280
280
  # -> proposal.json, basis.json (what each bone rests on) and render/landmarks.png; per-bone coverage lines;
281
281
  # correct it, copy bones/meshes/regions/limbs/motion/constraints/turn/motion_seams/proposed_rig_scale into config.json, each it wrote
282
282
  # (limbs only with --keypoints, constraints only with --cloth, turn only with --full/--head;
package/cli.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env bun
2
+ import { diffComponentsCommand } from './src/diffcomponents.ts';
2
3
  import { applyTakenLayers } from './src/takelayers.ts';
3
4
  import { applyDepth, prepareDepth } from './src/depthworkflow.ts';
4
5
  /**
@@ -33,7 +34,7 @@ import { makeInputs } from './src/inputs.ts';
33
34
  import { idleKeysOf, isIdleKeys } from './src/rig.ts';
34
35
  import { bodyCoreBox, bodyOpaqueBox, CORE_ALPHA, depthSkipLine, exportLayers, figuresPhrase, hiddenLayerLine, implausibleRules, layerCoreBox, layerFigures, layerOpaqueBox, type LayerSet, layerSetDiff, type OpaqueBox, OPAQUE_ALPHA_ABOVE, pct, readLayers, readWrapperLayers, ruleSummary, templateLayerNames, templatePsdBytes, times } from './src/layers.ts';
35
36
  import { checkImageSize, choosePerson, KEYPOINTS_SPEC, loadKeypoints, type RigJoints, toRigJoints } from './src/keypoints.ts';
36
- import { basisLines, branchSqueezeLine, checkProposal, compare, compareLines, DEFAULT_GRID_RULE, draftTurn, drawLandmarks, FIGURE_GRID_DIVISOR, FRINGE_STEP_PX, GRID_RULES, type GridRule, HIP_MIN_FRACTION, type DraftLintFinding, lint, lintLine, type PartSet, PROPOSAL_CONFIG_SECTIONS, proposalAsConfig, proposeWithBasis, readPartSet, serializeProposal, withTurn, WRIST_ON_PART_PX } from './src/propose.ts';
37
+ import { basisLines, branchSqueezeLine, checkProposal, compare, compareLines, DEFAULT_GRID_RULE, draftTurn, drawLandmarks, FIGURE_GRID_DIVISOR, FRINGE_STEP_PX, GRID_RULES, type GridRule, type BustDraft, HIP_MIN_FRACTION, type DraftLintFinding, lint, lintLine, type PartSet, PROPOSAL_CONFIG_SECTIONS, proposalAsConfig, proposeWithBasis, readBustLandmarks, readPartSet, serializeProposal, withTurn, WRIST_ON_PART_PX } from './src/propose.ts';
37
38
  import { BASIS_FILE, BASIS_SPEC, basisFile, basisLine, coverageLines, legChainChoice, serializeBasis } from './src/diagnostics.ts';
38
39
  import { BEND_DEGREES, BEND_WORST_SHARE, DUST_MAX_PX, DUST_MAX_SHARE, IDLE_BOUND_PX, MESH_DRAFT_REPORT, MESH_DRAFT_SPEC, meshDraftStage, MORE_TRIANGLES_FLAG, relativeConfigPaths, TOLERANCE_FIGURE_DIVISOR } from './src/meshdraft.ts';
39
40
  import { meshStatsStage } from './src/meshstats.ts';
@@ -68,6 +69,11 @@ const LATER: ReadonlyArray<[string, string]> = [];
68
69
  const HELP = `rig-parts ${version()} — Spine-ready parts from one painting and its See-through layers
69
70
 
70
71
  usage:
72
+ rig-parts diff-components --stack <png> --painting <png> --parts <parts.json> --floor <px>
73
+ [--threshold <channel>] [--figure <binary-png>] (--out <json> | --verify <json>)
74
+ Exhaustive native difference inventory; default max RGB channel > 40.
75
+
76
+
71
77
  rig-parts depth-prepare --parts <assembled-dir> --config <depth-recipe.json> --out <empty-dir>
72
78
  Write per-part inference PNGs on neutral gray, upscaled and padded as
73
79
  declared, plus a source-hashed manifest. No model is bundled or called.
@@ -177,6 +183,10 @@ usage:
177
183
  bone differs (origin, tip, parent; how far, in px). No confidence is
178
184
  computed. It prints "wrote ${BASIS_FILE} (…)" after the coverage lines.
179
185
 
186
+ rig-parts propose … (--bust-landmarks <landmarks.json> | --no-bust <reason>)
187
+ Draft the chest-holding topwear as contour. Landmark JSON declares apex_r,
188
+ apex_l and optional cleavage in rig pixels. Without cleavage, intersect
189
+ their segment with the neck-to-hip midline. --no-bust needs a stated reason.
180
190
  rig-parts propose … [--keypoints <keypoints.json> [--person <id>] [--wrist-snap <px>]]
181
191
  Read the figure's pose from one explicit file (spec "${KEYPOINTS_SPEC}"):
182
192
  its space (painting-px, origin top-left, y down — another is refused),
@@ -1016,6 +1026,14 @@ function usage(message: string): number {
1016
1026
  return EXIT_USAGE;
1017
1027
  }
1018
1028
 
1029
+ function cmdDiffComponents(args: string[]): number {
1030
+ for (let i = 0; i < args.length; i += 2) {
1031
+ if (!takesValued('diff-components', args[i])) return usage(notTaken('diff-components', args[i]));
1032
+ if (args[i + 1] === undefined || args[i + 1].startsWith('--')) return usage(`diff-components ${args[i]} requires a value`);
1033
+ }
1034
+ try { diffComponentsCommand(args); return EXIT_OK; } catch (err) { return printRefusal(err); }
1035
+ }
1036
+
1019
1037
  /** `guide`: one argument, a rule name or a section; the slice printed, or the refusal. */
1020
1038
  function cmdGuide(args: string[]): number {
1021
1039
  if (args.length !== 1) return usage(`guide takes one rule name or one §<n> / §<n>.<m> / §<n>.<m>.<k>; got ${args.length} argument(s)`);
@@ -1104,12 +1122,13 @@ function printLayerTable(set: LayerSet): void {
1104
1122
  * no row; `layers` takes one positional path beside its flags (issue #359).
1105
1123
  */
1106
1124
  const TAKES = {
1125
+ 'diff-components': { valued: ['--stack', '--painting', '--parts', '--figure', '--floor', '--threshold', '--out', '--verify'], switches: [] },
1107
1126
  'depth-prepare': { valued: ['--parts', '--config', '--out'], switches: [] },
1108
1127
  'depth-apply': { valued: ['--parts', '--manifest', '--config', '--rig-config', '--out'], switches: [] },
1109
1128
  layers: { valued: ['--export', '--template', '--canvas', '--full', '--source', '--config'], switches: [] },
1110
1129
  sheet: { valued: ['--source', '--layers', '--out', '--cell', '--cols'], switches: [] },
1111
1130
  rig: { valued: ['--config', '--parts', '--out', '--idle-keys', '--page-edges', '--pack-shape'], switches: [] },
1112
- propose: { valued: ['--parts', '--source', '--out', '--from-config', '--compare', '--full', '--head', '--canvas', '--keypoints', '--person', '--wrist-snap', '--grid-rule', '--cloth', '--write-config', '--from'], switches: ['--head-box', '--no-back-hair', '--only-missing'] },
1131
+ propose: { valued: ['--parts', '--source', '--out', '--from-config', '--compare', '--full', '--head', '--canvas', '--keypoints', '--person', '--wrist-snap', '--grid-rule', '--cloth', '--write-config', '--from', '--bust-landmarks', '--no-bust'], switches: ['--head-box', '--no-back-hair', '--only-missing'] },
1113
1132
  compare: { valued: ['--left', '--right', '--map'], switches: [] },
1114
1133
  check: { valued: ['--rig', '--out', '--parts', '--source', '--page-edges', '--pack-shape', '--requirements', '--set', '--environment', '--wind-band'], switches: ['--seam-pairs', '--keep-stills', '--all-sets', '--no-extremes', '--json'] },
1115
1134
  loop: { valued: ['--frames', '--out'], switches: ['--palette'] },
@@ -1557,6 +1576,7 @@ function cmdPropose(args: string[]): number {
1557
1576
  return EXIT_OK;
1558
1577
  }
1559
1578
  }
1579
+ if (flags.has('--from-config') && (flags.has('--bust-landmarks') || flags.has('--no-bust'))) return usage('--bust-landmarks and --no-bust draft a torso. --from-config reads authored meshes');
1560
1580
  const P = readPartSet(partsDir);
1561
1581
  const painting = readPng(source);
1562
1582
  // Issue #75: the keypoint file, held to this painting's size and taken to rig px by the overlay's map.
@@ -1580,7 +1600,23 @@ function cmdPropose(args: string[]): number {
1580
1600
  }
1581
1601
  const cmpPath = flags.get('--compare');
1582
1602
  const cfg = cmpPath === undefined ? null : loadConfig(cmpPath);
1583
- const { proposal: drafted, basis, draftLint } = proposeWithBasis(P, joints, wristSnap, gridRule, cloth);
1603
+ const noBust = flags.get('--no-bust');
1604
+ const bustPath = flags.get('--bust-landmarks');
1605
+ if (noBust !== undefined && (noBust.trim().length === 0 || bustPath !== undefined)) return usage('--no-bust requires a non-blank reason and cannot accompany --bust-landmarks');
1606
+ const bust: BustDraft = {};
1607
+ if (noBust !== undefined) bust.noBust = noBust;
1608
+ if (bustPath !== undefined) {
1609
+ try {
1610
+ bust.landmarks = readBustLandmarks(JSON.parse(readFileSync(bustPath, 'utf8')) as unknown);
1611
+ } catch (error) {
1612
+ if (error instanceof PartsError) throw error;
1613
+ throw new PartsError([{
1614
+ code: 'PROPOSE_BUST_INPUT', object: '--bust-landmarks',
1615
+ detail: `cannot read the declared JSON file (${error instanceof Error ? error.message : String(error)}). Give an object with apex_r, apex_l and optional cleavage in rig pixels`,
1616
+ }]);
1617
+ }
1618
+ }
1619
+ const { proposal: drafted, basis, draftLint } = proposeWithBasis(P, joints, wristSnap, gridRule, cloth, true, bust);
1584
1620
  const fullRun = flags.get('--full');
1585
1621
  const headRun = flags.get('--head');
1586
1622
  const prop =
@@ -1590,7 +1626,7 @@ function cmdPropose(args: string[]): number {
1590
1626
  // Emit only after green: a proposal the config loader would refuse is not written, nor a basis record that does not hold to it.
1591
1627
  checkProposal(P, prop);
1592
1628
  // The same parts drafted without the recipe: what --write-config measures its edits against, and what issue #572's chain reading compares with.
1593
- const plain = cloth === null ? null : proposeWithBasis(P, joints, wristSnap, gridRule, null).proposal;
1629
+ const plain = cloth === null ? null : proposeWithBasis(P, joints, wristSnap, gridRule, null, true, bust).proposal;
1594
1630
  // Issue #537: the sections the recipe moves, measured against the same parts drafted without it, spliced before anything is written.
1595
1631
  const written =
1596
1632
  writeArg === undefined || clothArg === undefined || plain === null
@@ -1599,7 +1635,7 @@ function cmdPropose(args: string[]): number {
1599
1635
  const text = readFileSync(clothArg, 'utf8');
1600
1636
  // Issue #552: bones, constraints and motion.tracks entry by entry; a hand edit the recipe would drop is refused by name.
1601
1637
  // Issue #571: with --from, the same parts drafted a third time with the previous recipe.
1602
- const previous = previousCloth === null ? null : proposeWithBasis(P, joints, wristSnap, gridRule, previousCloth).proposal;
1638
+ const previous = previousCloth === null ? null : proposeWithBasis(P, joints, wristSnap, gridRule, previousCloth, true, bust).proposal;
1603
1639
  const edits = clothConfigEdits(drafted, plain, JSON.parse(text) as unknown, false, previous);
1604
1640
  return { at: writeArg, input: text, text: writtenClothConfig(clothArg, text, edits), paths: editPaths(edits), vouched: previousCount(edits), returned: plainCount(edits), relative: relativeConfigPaths(dirname(resolve(clothArg)), JSON.parse(text) as Record<string, unknown>) };
1605
1641
  })();
@@ -2257,6 +2293,7 @@ function main(argv: string[]): number | Promise<number> {
2257
2293
  console.log(version());
2258
2294
  return EXIT_OK;
2259
2295
  }
2296
+ if (command === 'diff-components') return cmdDiffComponents(rest);
2260
2297
  if (command === 'layers') return cmdLayers(rest);
2261
2298
  if (command === 'depth-prepare') return cmdDepthPrepare(rest);
2262
2299
  if (command === 'depth-apply') return cmdDepthApply(rest);