@ikijs/editor 0.30.1 → 0.31.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
@@ -40,19 +40,19 @@ leaves the document untouched.
40
40
 
41
41
  ## API
42
42
 
43
- | Area | Exports |
44
- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
45
- | Document | `EditorDocument`, `EditCommand` |
46
- | Part edits | `AddPart`, `DeletePart`, `SetPartColor`, `SetPartWidth`, `SetPartHeight`, `SetPartOrder`, `SetPartTransform`, `SetPartBindings`, `SetPartMesh`, `SetPartDeformer` |
47
- | Deformer edits | `AddDeformer`, `DeleteDeformer`, `SetDeformerParent`, `SetDeformerTransform`, `SetDeformerBindings`, `SetDeformerPivot` (+ `X`/`Y`), `CaptureGridKeyform` |
48
- | Physics edits | `AddPhysicsRig`, `SetPhysicsRig`, `DeletePhysicsRig` |
49
- | Referential guards | `validateDeformerReparent`, `validateDeformerDelete`, `validatePartAttach` |
50
- | Atlas | `packAtlas`, `uvRectFor`, `ATLAS_PADDING`, `UV_INSET_PX` |
51
- | Grid keyforms | `computeGridOffsets`, `interpolateGridOffsets`, `upsertGridKeyform` |
52
- | Factories | `createDefaultPart`, `createDefaultMatrixDeformer`, `createDefaultWarpDeformer`, `createGridMesh` |
53
- | Pixels | `detectAlphaBbox`, `ALPHA_BBOX_THRESHOLD`, `AlphaBbox`, `ALPHA_OPAQUE`, `HEAD_BAND`, `SPECK_CORE_FRACTION`, `denseCoreOf`, `isSpeckCore`, `foregroundSpan`, `headHalfOf` |
54
- | Auto-rig | `generateIkiFromLayerSet`, `parseLayerRoles`, `partIdsOfRole`, `createLayerSetMeasurer`, `armGeometry`, `ArmGeometry`, `forearmPoseGeometry`, `ForearmPoseGeometry`, `mouthOpening`, `mouthFold`, `mouthRestShift`, `columnRuns`, `LIP_ROLES`, `LipRole`, `Opening`, `OpeningColumn`, `TurnTargets`, `DEFAULT_TURN_TARGETS`, `TurnSolveReport`, `HairSideRoom`, `RigPlan`, `RigPlanError`, `RIG_PLAN_FIELDS`, `RigPlanReport`, `RigPatch`, `PatchReport`, `PatchError`, `SHUT_BY`, `LayerGeometryError` |
55
- | Bindings | `captureBindingEndpoint` |
43
+ | Area | Exports |
44
+ | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
45
+ | Document | `EditorDocument`, `EditCommand` |
46
+ | Part edits | `AddPart`, `DeletePart`, `SetPartColor`, `SetPartWidth`, `SetPartHeight`, `SetPartOrder`, `SetPartTransform`, `SetPartBindings`, `SetPartMesh`, `SetPartDeformer` |
47
+ | Deformer edits | `AddDeformer`, `DeleteDeformer`, `SetDeformerParent`, `SetDeformerTransform`, `SetDeformerBindings`, `SetDeformerPivot` (+ `X`/`Y`), `CaptureGridKeyform` |
48
+ | Physics edits | `AddPhysicsRig`, `SetPhysicsRig`, `DeletePhysicsRig` |
49
+ | Referential guards | `validateDeformerReparent`, `validateDeformerDelete`, `validatePartAttach` |
50
+ | Atlas | `packAtlas`, `uvRectFor`, `ATLAS_PADDING`, `UV_INSET_PX` |
51
+ | Grid keyforms | `computeGridOffsets`, `interpolateGridOffsets`, `upsertGridKeyform` |
52
+ | Factories | `createDefaultPart`, `createDefaultMatrixDeformer`, `createDefaultWarpDeformer`, `createGridMesh` |
53
+ | Pixels | `detectAlphaBbox`, `ALPHA_BBOX_THRESHOLD`, `AlphaBbox`, `ALPHA_OPAQUE`, `HEAD_BAND`, `SPECK_CORE_FRACTION`, `denseCoreOf`, `isSpeckCore`, `foregroundSpan`, `headHalfOf` |
54
+ | Auto-rig | `generateIkiFromLayerSet`, `parseLayerRoles`, `partIdsOfRole`, `createLayerSetMeasurer`, `armGeometry`, `ArmGeometry`, `forearmPoseGeometry`, `ForearmPoseGeometry`, `mouthOpening`, `mouthFold`, `mouthRestShift`, `columnRuns`, `LIP_ROLES`, `LipRole`, `Opening`, `OpeningColumn`, `TurnTargets`, `DEFAULT_TURN_TARGETS`, `TurnSolveReport`, `HairSideRoom`, `RigPlan`, `RigPlanError`, `RIG_PLAN_FIELDS`, `RigPlanReport`, `RigPatch`, `PatchReport`, `PatchError`, `RigBakes`, `BakeTarget`, `BakeReport`, `BakeError`, `rigidResidual`, `SHUT_BY`, `LayerGeometryError` |
55
+ | Bindings | `captureBindingEndpoint` |
56
56
 
57
57
  ## Auto-rig
58
58
 
@@ -100,7 +100,18 @@ profile in three groups (`turn`, `hair`, `eyes`: the whole turn, the
100
100
  features' lead, the front hair's follow and its outline's, the blink and the
101
101
  hair sway), validated against `RIG_PLAN_FIELDS`. `options.patches` adds displacements to a generated rig's keyform
102
102
  stops (`RigPatch`), replayed on every rig and reported through
103
- `options.onPatchesApplied`; a refused op throws `PatchError`. The turn
103
+ `options.onPatchesApplied`; a refused op throws `PatchError`. `options.customParts` adds parts the
104
+ auto-rigger has no role for (`CustomPart`: `id`, `layer`, `anchor`, `tie`,
105
+ `after`), unrigged on `headDeformer` with a plain mesh and drawn after the
106
+ `after` role's last part; the rest of the model is as without them but for
107
+ the `order` of every part after an inserted one (a set rigged without
108
+ `customParts` is byte-identical), and a malformed entry throws
109
+ `CustomPartError`. `options.bakes` (`RigBakes`: control lattices per
110
+ parameter stop, `meshes` to re-mesh) writes warps on those parts that
111
+ land them where the lattice asks, solved in closed form through the
112
+ head's frame; a `rigid` custom part refuses a bend over 0.0025 face
113
+ widths (`rigidResidual`), a refused input throws `BakeError`, and
114
+ `options.onBakesApplied` gets what each part was asked for. The turn
104
115
  is bounded by the art: the far eye stays inside the plate's outline and, given
105
116
  `options.strandEdges`, the far iris may not slide under the bangs' side strand
106
117
  any further than it is painted — best effort, never a refusal. The report's
package/dist/index.d.mts CHANGED
@@ -1052,6 +1052,132 @@ interface PatchReport {
1052
1052
  declare class PatchError extends Error {
1053
1053
  constructor(message: string);
1054
1054
  }
1055
+ /**
1056
+ * A part the auto-rigger has no role for (a hairpin, a twin tail): drawn from
1057
+ * its own canvas-sized layer, hung on the head with plain warps.
1058
+ */
1059
+ interface CustomPart {
1060
+ /** A plain identifier; never a role's name or a part id a role yields. */
1061
+ id: string;
1062
+ layer: {
1063
+ fileName: string;
1064
+ /** The layer's alpha box in canvas px, as `LayerInput.bbox`. */
1065
+ bbox: {
1066
+ x: number;
1067
+ y: number;
1068
+ w: number;
1069
+ h: number;
1070
+ };
1071
+ };
1072
+ /** The role whose layer the part is fixed against. */
1073
+ anchor: string;
1074
+ /** Where the part meets the anchor, canvas px: inside both layers' boxes. */
1075
+ tie: [number, number];
1076
+ /** A role of the set: the part is drawn right after that role's last part. */
1077
+ after: string;
1078
+ /** The matrix deformer it hangs on; only `headDeformer` (the default) is
1079
+ * accepted. */
1080
+ deformer?: string;
1081
+ /** The art does not bend (a hairpin, a ribbon): a bake that bends it more
1082
+ * than 0.0025 face widths at a stop is refused (`BakeError`). */
1083
+ rigid?: boolean;
1084
+ }
1085
+ /** A `customParts` entry that is malformed or misses the layer set. Its
1086
+ * message starts `customParts[i].<field>` (`customParts` alone for a
1087
+ * non-array). */
1088
+ declare class CustomPartError extends Error {
1089
+ constructor(message: string);
1090
+ }
1091
+ /**
1092
+ * One lattice a bake spreads over the custom parts it names: `(cols + 1) x
1093
+ * (rows + 1)` control points laid evenly over `box`, listed row-major from the
1094
+ * top-left (the top row first, each row left to right). Every position and
1095
+ * displacement is in canvas px, origin top-left, y DOWN.
1096
+ *
1097
+ * A stop is exact where the bake writes it, and so is the end key a side's
1098
+ * outermost stop gets at the parameter's range end; between keys the motion
1099
+ * is interpolated, and under a parent that rotates with the parameter (the
1100
+ * head's roll) it bows off the straight line, so put a stop where the path
1101
+ * matters. A side with no stop is not moved by this target. Targets that name the same part and
1102
+ * parameter add.
1103
+ */
1104
+ interface BakeTarget {
1105
+ /** A label for reports; the rig ignores it. */
1106
+ id?: string;
1107
+ /** A parameter the model declares. */
1108
+ param: string;
1109
+ /** Custom part ids only: a bake moves nothing else. */
1110
+ parts: string[];
1111
+ /** Canvas px; absent, the union of the parts' rest boxes grown by 2 px. */
1112
+ box?: {
1113
+ x: number;
1114
+ y: number;
1115
+ w: number;
1116
+ h: number;
1117
+ };
1118
+ /** Integers 1 to 32. */
1119
+ grid: {
1120
+ cols: number;
1121
+ rows: number;
1122
+ };
1123
+ /** Keyed by a value of `param` inside its range, never its default (rest is
1124
+ * implicit). A stop is the part's TOTAL landed displacement from rest when
1125
+ * `param` alone is at that value and every other parameter is at its
1126
+ * default: the head's own carry is included, so a neighbour's measured
1127
+ * displacement at that pose is written as it is. */
1128
+ stops: Record<string, [number, number][]>;
1129
+ /** Fill a missing stop from the one at the value mirrored about the rest
1130
+ * value: the lattice reflected about the box's vertical centre line, `dx`
1131
+ * negated. A written stop is never overwritten. */
1132
+ mirror?: boolean;
1133
+ /** Why the target exists, for whoever edits the bakes next; ignored. */
1134
+ why?: string;
1135
+ }
1136
+ /**
1137
+ * Lattice bakes for custom parts: what moves them when the head turns or the
1138
+ * hair sways, which an unrigged custom part otherwise does not. A bake moves
1139
+ * custom parts only.
1140
+ */
1141
+ interface RigBakes {
1142
+ targets: BakeTarget[];
1143
+ /** Re-mesh a custom part before baking: `rows` (canvas-px y, strictly
1144
+ * increasing, first the part's top and last its bottom within 0.5 px) with
1145
+ * `cols`, or `cellPx` for a uniform grid. "The box" here is the part's own
1146
+ * rest box (its layer's bbox), not a target's `box`. */
1147
+ meshes?: Record<string, {
1148
+ rows: number[];
1149
+ cols: number;
1150
+ } | {
1151
+ cellPx: number;
1152
+ }>;
1153
+ }
1154
+ /** What a bake asked of one custom part on one parameter, for a caller that
1155
+ * checks the landing. */
1156
+ interface BakeReport {
1157
+ part: string;
1158
+ param: string;
1159
+ /** The stops written, ascending (rest, implicit, not among them). */
1160
+ stops: number[];
1161
+ vertexCount: number;
1162
+ /** Per stop, in `stops` order: each vertex's wanted displacement from rest,
1163
+ * `[dx0, dy0, dx1, dy1, ...]` in canvas px (y down), before the offsets
1164
+ * were rounded. */
1165
+ wanted: number[][];
1166
+ /** The end keys the bake wrote at the parameter's range ends, ascending,
1167
+ * each with the displacement it was solved for (`wanted` as above): its
1168
+ * side's outermost stop's. A side with no stop, or whose outermost stop is
1169
+ * at the end, has none. */
1170
+ ends: {
1171
+ value: number;
1172
+ wanted: number[];
1173
+ }[];
1174
+ }
1175
+ /** A `bakes` input that is malformed or misses the model. Its message starts
1176
+ * with the path of the field (`bakes.targets[2].stops["30"][7]`,
1177
+ * `bakes.meshes.tail_R.rows[0]`). */
1178
+ declare class BakeError extends Error {
1179
+ constructor(message: string);
1180
+ }
1055
1181
  interface HeadEdges {
1056
1182
  left: {
1057
1183
  role: string;
@@ -1076,6 +1202,17 @@ interface GenerateOptions {
1076
1202
  /** Displacements added to the generated rig's stops (`RigPatch`), replayed
1077
1203
  * on every rig; a refused op throws `PatchError` and rigs nothing. */
1078
1204
  patches?: RigPatch[];
1205
+ /** Parts the auto-rigger has no role for, put in the model unrigged: drawn
1206
+ * from their own layers, hung on the head. Every entry is checked first
1207
+ * (`CustomPartError`). */
1208
+ customParts?: CustomPart[];
1209
+ /** Lattices that move the custom parts when a parameter does (`RigBakes`):
1210
+ * written as warps on the parts, after the patches. A refused input throws
1211
+ * `BakeError` and rigs nothing. */
1212
+ bakes?: RigBakes;
1213
+ /** What `bakes` asked of each custom part and parameter — called only when
1214
+ * `bakes` is given, and only for a model that validates. */
1215
+ onBakesApplied?: (report: BakeReport[]) => void;
1079
1216
  /** What each of `patches` moved, in order — called only when `patches` is
1080
1217
  * given, and only for a model that validates. */
1081
1218
  onPatchesApplied?: (report: PatchReport[]) => void;
@@ -1130,17 +1267,17 @@ interface GenerateOptions {
1130
1267
  * the shoulder's +: it turns CW on the +x side, CCW on the −x side.
1131
1268
  */
1132
1269
 
1133
- type Point = {
1270
+ type Point$1 = {
1134
1271
  x: number;
1135
1272
  y: number;
1136
1273
  };
1137
1274
  interface ArmGeometry {
1138
1275
  /** The shoulder pivot, model space: the deltoid cap's centre. */
1139
- shoulder: Point;
1276
+ shoulder: Point$1;
1140
1277
  /** r_u: the upper arm's half-width under the cap, px. */
1141
1278
  shoulderRadius: number;
1142
1279
  /** The elbow pivot, model space. Its y is the seam the two meshes meet on. */
1143
- elbow: Point;
1280
+ elbow: Point$1;
1144
1281
  /** The elbow cap's largest radius, px. */
1145
1282
  capRadius: number;
1146
1283
  /** The cap's radius on each of its `CAP_SEGMENTS + 1` rays, from the elbow
@@ -1395,6 +1532,26 @@ declare function mouthFold(role: LipRole | LipInsideRole, opening: Opening): (co
1395
1532
  * clamps differently, and the knot grid starts at the union's edge. */
1396
1533
  declare function mouthRestShift(byRole: Map<string, LayerInput>): (role: "lip_upper" | "lip_lower", col: number, row: number) => number | undefined;
1397
1534
 
1535
+ /**
1536
+ * The `bakes` generator input, in two steps. `parseBakes` checks the whole
1537
+ * input before anything is applied, so a refusal changes nothing; each
1538
+ * message starts at the path of the field (`bakes.targets[2].stops["30"][7]`).
1539
+ * `applyBakes` then re-meshes the named parts and writes their warps, solved
1540
+ * in closed form through the head's chain.
1541
+ */
1542
+
1543
+ type Point = [number, number];
1544
+ /**
1545
+ * The largest distance, over the point set, from where `moved` puts a point
1546
+ * to where the least-squares rigid fit of `rest` puts it: 0 for a rigid
1547
+ * motion, the bend of anything else. The fit is a rotation and a translation,
1548
+ * no scale and no reflection, and it minimises the summed squares, not the
1549
+ * largest distance (so the result can exceed the minimax one). Both lists
1550
+ * hold the same points in the same order, in one frame, so their lengths must
1551
+ * be equal.
1552
+ */
1553
+ declare function rigidResidual(rest: readonly Point[], moved: readonly Point[]): number;
1554
+
1398
1555
  /**
1399
1556
  * `generateIkiFromLayerSet`: role layers in, a rigged `.iki` out.
1400
1557
  *
@@ -1820,4 +1977,4 @@ declare function createGridMesh(cols: number, rows: number): IkiMesh;
1820
1977
  */
1821
1978
  declare function createDefaultWarpDeformer(model: IkiModel): IkiWarpDeformer;
1822
1979
 
1823
- export { ALPHA_BBOX_THRESHOLD, ALPHA_OPAQUE, ATLAS_PADDING, AddDeformer, AddPart, AddPhysicsRig, type AlphaBbox, type ApplyAtlasInput, type ArmGeometry, type AtlasAssignment, type AtlasLayout, type AtlasPlacement, type AtlasSource, CaptureGridKeyform, DEFAULT_TURN_TARGETS, type DeformerTransformChannel, DeleteDeformer, DeletePart, DeletePhysicsRig, type EditCommand, type EditTransformChannel, EditorDocument, type ForearmPoseGeometry, HEAD_BAND, type HairSideRoom, type IrisStrand, LIP_INSIDE_ROLES, LIP_ROLES, LayerGeometryError, type LayerInput, type LayerSetMeasurement, type LayerSetMeasurer, type LipInsideRole, type LipRole, type Opening, type OpeningColumn, PatchError, type PatchReport, RIG_PLAN_FIELDS, type RgbaLayer, type RigPatch, type RigPlan, RigPlanError, type RigPlanField, type RigPlanReport, SHUT_BY, SPECK_CORE_FRACTION, SetDeformerBindings, SetDeformerParent, SetDeformerPivot, SetDeformerPivotX, SetDeformerPivotY, SetDeformerTransform, SetPartBindings, SetPartColor, SetPartDeformer, SetPartHeight, SetPartMesh, SetPartOrder, SetPartTransform, SetPartWidth, SetPhysicsRig, type StrandOverlap, type TurnDepths, type TurnSolveReport, TurnTargetError, type TurnTargets, UV_INSET_PX, armGeometry, captureBindingEndpoint, columnRuns, computeGridOffsets, createDefaultMatrixDeformer, createDefaultPart, createDefaultWarpDeformer, createGridMesh, createLayerSetMeasurer, denseCoreOf, detectAlphaBbox, forearmPoseGeometry, foregroundSpan, generateIkiFromLayerSet, headHalfOf, interpolateGridOffsets, isLipInsideRole, isSpeckCore, mouthFold, mouthOpening, mouthRestShift, packAtlas, parseLayerRoles, partIdsOfRole, styleRemovedMessage, upsertGridKeyform, uvRectFor, validateDeformerDelete, validateDeformerReparent, validatePartAttach };
1980
+ export { ALPHA_BBOX_THRESHOLD, ALPHA_OPAQUE, ATLAS_PADDING, AddDeformer, AddPart, AddPhysicsRig, type AlphaBbox, type ApplyAtlasInput, type ArmGeometry, type AtlasAssignment, type AtlasLayout, type AtlasPlacement, type AtlasSource, BakeError, type BakeReport, type BakeTarget, CaptureGridKeyform, type CustomPart, CustomPartError, DEFAULT_TURN_TARGETS, type DeformerTransformChannel, DeleteDeformer, DeletePart, DeletePhysicsRig, type EditCommand, type EditTransformChannel, EditorDocument, type ForearmPoseGeometry, HEAD_BAND, type HairSideRoom, type IrisStrand, LIP_INSIDE_ROLES, LIP_ROLES, LayerGeometryError, type LayerInput, type LayerSetMeasurement, type LayerSetMeasurer, type LipInsideRole, type LipRole, type Opening, type OpeningColumn, PatchError, type PatchReport, RIG_PLAN_FIELDS, type RgbaLayer, type RigBakes, type RigPatch, type RigPlan, RigPlanError, type RigPlanField, type RigPlanReport, SHUT_BY, SPECK_CORE_FRACTION, SetDeformerBindings, SetDeformerParent, SetDeformerPivot, SetDeformerPivotX, SetDeformerPivotY, SetDeformerTransform, SetPartBindings, SetPartColor, SetPartDeformer, SetPartHeight, SetPartMesh, SetPartOrder, SetPartTransform, SetPartWidth, SetPhysicsRig, type StrandOverlap, type TurnDepths, type TurnSolveReport, TurnTargetError, type TurnTargets, UV_INSET_PX, armGeometry, captureBindingEndpoint, columnRuns, computeGridOffsets, createDefaultMatrixDeformer, createDefaultPart, createDefaultWarpDeformer, createGridMesh, createLayerSetMeasurer, denseCoreOf, detectAlphaBbox, forearmPoseGeometry, foregroundSpan, generateIkiFromLayerSet, headHalfOf, interpolateGridOffsets, isLipInsideRole, isSpeckCore, mouthFold, mouthOpening, mouthRestShift, packAtlas, parseLayerRoles, partIdsOfRole, rigidResidual, styleRemovedMessage, upsertGridKeyform, uvRectFor, validateDeformerDelete, validateDeformerReparent, validatePartAttach };
package/dist/index.d.ts CHANGED
@@ -1052,6 +1052,132 @@ interface PatchReport {
1052
1052
  declare class PatchError extends Error {
1053
1053
  constructor(message: string);
1054
1054
  }
1055
+ /**
1056
+ * A part the auto-rigger has no role for (a hairpin, a twin tail): drawn from
1057
+ * its own canvas-sized layer, hung on the head with plain warps.
1058
+ */
1059
+ interface CustomPart {
1060
+ /** A plain identifier; never a role's name or a part id a role yields. */
1061
+ id: string;
1062
+ layer: {
1063
+ fileName: string;
1064
+ /** The layer's alpha box in canvas px, as `LayerInput.bbox`. */
1065
+ bbox: {
1066
+ x: number;
1067
+ y: number;
1068
+ w: number;
1069
+ h: number;
1070
+ };
1071
+ };
1072
+ /** The role whose layer the part is fixed against. */
1073
+ anchor: string;
1074
+ /** Where the part meets the anchor, canvas px: inside both layers' boxes. */
1075
+ tie: [number, number];
1076
+ /** A role of the set: the part is drawn right after that role's last part. */
1077
+ after: string;
1078
+ /** The matrix deformer it hangs on; only `headDeformer` (the default) is
1079
+ * accepted. */
1080
+ deformer?: string;
1081
+ /** The art does not bend (a hairpin, a ribbon): a bake that bends it more
1082
+ * than 0.0025 face widths at a stop is refused (`BakeError`). */
1083
+ rigid?: boolean;
1084
+ }
1085
+ /** A `customParts` entry that is malformed or misses the layer set. Its
1086
+ * message starts `customParts[i].<field>` (`customParts` alone for a
1087
+ * non-array). */
1088
+ declare class CustomPartError extends Error {
1089
+ constructor(message: string);
1090
+ }
1091
+ /**
1092
+ * One lattice a bake spreads over the custom parts it names: `(cols + 1) x
1093
+ * (rows + 1)` control points laid evenly over `box`, listed row-major from the
1094
+ * top-left (the top row first, each row left to right). Every position and
1095
+ * displacement is in canvas px, origin top-left, y DOWN.
1096
+ *
1097
+ * A stop is exact where the bake writes it, and so is the end key a side's
1098
+ * outermost stop gets at the parameter's range end; between keys the motion
1099
+ * is interpolated, and under a parent that rotates with the parameter (the
1100
+ * head's roll) it bows off the straight line, so put a stop where the path
1101
+ * matters. A side with no stop is not moved by this target. Targets that name the same part and
1102
+ * parameter add.
1103
+ */
1104
+ interface BakeTarget {
1105
+ /** A label for reports; the rig ignores it. */
1106
+ id?: string;
1107
+ /** A parameter the model declares. */
1108
+ param: string;
1109
+ /** Custom part ids only: a bake moves nothing else. */
1110
+ parts: string[];
1111
+ /** Canvas px; absent, the union of the parts' rest boxes grown by 2 px. */
1112
+ box?: {
1113
+ x: number;
1114
+ y: number;
1115
+ w: number;
1116
+ h: number;
1117
+ };
1118
+ /** Integers 1 to 32. */
1119
+ grid: {
1120
+ cols: number;
1121
+ rows: number;
1122
+ };
1123
+ /** Keyed by a value of `param` inside its range, never its default (rest is
1124
+ * implicit). A stop is the part's TOTAL landed displacement from rest when
1125
+ * `param` alone is at that value and every other parameter is at its
1126
+ * default: the head's own carry is included, so a neighbour's measured
1127
+ * displacement at that pose is written as it is. */
1128
+ stops: Record<string, [number, number][]>;
1129
+ /** Fill a missing stop from the one at the value mirrored about the rest
1130
+ * value: the lattice reflected about the box's vertical centre line, `dx`
1131
+ * negated. A written stop is never overwritten. */
1132
+ mirror?: boolean;
1133
+ /** Why the target exists, for whoever edits the bakes next; ignored. */
1134
+ why?: string;
1135
+ }
1136
+ /**
1137
+ * Lattice bakes for custom parts: what moves them when the head turns or the
1138
+ * hair sways, which an unrigged custom part otherwise does not. A bake moves
1139
+ * custom parts only.
1140
+ */
1141
+ interface RigBakes {
1142
+ targets: BakeTarget[];
1143
+ /** Re-mesh a custom part before baking: `rows` (canvas-px y, strictly
1144
+ * increasing, first the part's top and last its bottom within 0.5 px) with
1145
+ * `cols`, or `cellPx` for a uniform grid. "The box" here is the part's own
1146
+ * rest box (its layer's bbox), not a target's `box`. */
1147
+ meshes?: Record<string, {
1148
+ rows: number[];
1149
+ cols: number;
1150
+ } | {
1151
+ cellPx: number;
1152
+ }>;
1153
+ }
1154
+ /** What a bake asked of one custom part on one parameter, for a caller that
1155
+ * checks the landing. */
1156
+ interface BakeReport {
1157
+ part: string;
1158
+ param: string;
1159
+ /** The stops written, ascending (rest, implicit, not among them). */
1160
+ stops: number[];
1161
+ vertexCount: number;
1162
+ /** Per stop, in `stops` order: each vertex's wanted displacement from rest,
1163
+ * `[dx0, dy0, dx1, dy1, ...]` in canvas px (y down), before the offsets
1164
+ * were rounded. */
1165
+ wanted: number[][];
1166
+ /** The end keys the bake wrote at the parameter's range ends, ascending,
1167
+ * each with the displacement it was solved for (`wanted` as above): its
1168
+ * side's outermost stop's. A side with no stop, or whose outermost stop is
1169
+ * at the end, has none. */
1170
+ ends: {
1171
+ value: number;
1172
+ wanted: number[];
1173
+ }[];
1174
+ }
1175
+ /** A `bakes` input that is malformed or misses the model. Its message starts
1176
+ * with the path of the field (`bakes.targets[2].stops["30"][7]`,
1177
+ * `bakes.meshes.tail_R.rows[0]`). */
1178
+ declare class BakeError extends Error {
1179
+ constructor(message: string);
1180
+ }
1055
1181
  interface HeadEdges {
1056
1182
  left: {
1057
1183
  role: string;
@@ -1076,6 +1202,17 @@ interface GenerateOptions {
1076
1202
  /** Displacements added to the generated rig's stops (`RigPatch`), replayed
1077
1203
  * on every rig; a refused op throws `PatchError` and rigs nothing. */
1078
1204
  patches?: RigPatch[];
1205
+ /** Parts the auto-rigger has no role for, put in the model unrigged: drawn
1206
+ * from their own layers, hung on the head. Every entry is checked first
1207
+ * (`CustomPartError`). */
1208
+ customParts?: CustomPart[];
1209
+ /** Lattices that move the custom parts when a parameter does (`RigBakes`):
1210
+ * written as warps on the parts, after the patches. A refused input throws
1211
+ * `BakeError` and rigs nothing. */
1212
+ bakes?: RigBakes;
1213
+ /** What `bakes` asked of each custom part and parameter — called only when
1214
+ * `bakes` is given, and only for a model that validates. */
1215
+ onBakesApplied?: (report: BakeReport[]) => void;
1079
1216
  /** What each of `patches` moved, in order — called only when `patches` is
1080
1217
  * given, and only for a model that validates. */
1081
1218
  onPatchesApplied?: (report: PatchReport[]) => void;
@@ -1130,17 +1267,17 @@ interface GenerateOptions {
1130
1267
  * the shoulder's +: it turns CW on the +x side, CCW on the −x side.
1131
1268
  */
1132
1269
 
1133
- type Point = {
1270
+ type Point$1 = {
1134
1271
  x: number;
1135
1272
  y: number;
1136
1273
  };
1137
1274
  interface ArmGeometry {
1138
1275
  /** The shoulder pivot, model space: the deltoid cap's centre. */
1139
- shoulder: Point;
1276
+ shoulder: Point$1;
1140
1277
  /** r_u: the upper arm's half-width under the cap, px. */
1141
1278
  shoulderRadius: number;
1142
1279
  /** The elbow pivot, model space. Its y is the seam the two meshes meet on. */
1143
- elbow: Point;
1280
+ elbow: Point$1;
1144
1281
  /** The elbow cap's largest radius, px. */
1145
1282
  capRadius: number;
1146
1283
  /** The cap's radius on each of its `CAP_SEGMENTS + 1` rays, from the elbow
@@ -1395,6 +1532,26 @@ declare function mouthFold(role: LipRole | LipInsideRole, opening: Opening): (co
1395
1532
  * clamps differently, and the knot grid starts at the union's edge. */
1396
1533
  declare function mouthRestShift(byRole: Map<string, LayerInput>): (role: "lip_upper" | "lip_lower", col: number, row: number) => number | undefined;
1397
1534
 
1535
+ /**
1536
+ * The `bakes` generator input, in two steps. `parseBakes` checks the whole
1537
+ * input before anything is applied, so a refusal changes nothing; each
1538
+ * message starts at the path of the field (`bakes.targets[2].stops["30"][7]`).
1539
+ * `applyBakes` then re-meshes the named parts and writes their warps, solved
1540
+ * in closed form through the head's chain.
1541
+ */
1542
+
1543
+ type Point = [number, number];
1544
+ /**
1545
+ * The largest distance, over the point set, from where `moved` puts a point
1546
+ * to where the least-squares rigid fit of `rest` puts it: 0 for a rigid
1547
+ * motion, the bend of anything else. The fit is a rotation and a translation,
1548
+ * no scale and no reflection, and it minimises the summed squares, not the
1549
+ * largest distance (so the result can exceed the minimax one). Both lists
1550
+ * hold the same points in the same order, in one frame, so their lengths must
1551
+ * be equal.
1552
+ */
1553
+ declare function rigidResidual(rest: readonly Point[], moved: readonly Point[]): number;
1554
+
1398
1555
  /**
1399
1556
  * `generateIkiFromLayerSet`: role layers in, a rigged `.iki` out.
1400
1557
  *
@@ -1820,4 +1977,4 @@ declare function createGridMesh(cols: number, rows: number): IkiMesh;
1820
1977
  */
1821
1978
  declare function createDefaultWarpDeformer(model: IkiModel): IkiWarpDeformer;
1822
1979
 
1823
- export { ALPHA_BBOX_THRESHOLD, ALPHA_OPAQUE, ATLAS_PADDING, AddDeformer, AddPart, AddPhysicsRig, type AlphaBbox, type ApplyAtlasInput, type ArmGeometry, type AtlasAssignment, type AtlasLayout, type AtlasPlacement, type AtlasSource, CaptureGridKeyform, DEFAULT_TURN_TARGETS, type DeformerTransformChannel, DeleteDeformer, DeletePart, DeletePhysicsRig, type EditCommand, type EditTransformChannel, EditorDocument, type ForearmPoseGeometry, HEAD_BAND, type HairSideRoom, type IrisStrand, LIP_INSIDE_ROLES, LIP_ROLES, LayerGeometryError, type LayerInput, type LayerSetMeasurement, type LayerSetMeasurer, type LipInsideRole, type LipRole, type Opening, type OpeningColumn, PatchError, type PatchReport, RIG_PLAN_FIELDS, type RgbaLayer, type RigPatch, type RigPlan, RigPlanError, type RigPlanField, type RigPlanReport, SHUT_BY, SPECK_CORE_FRACTION, SetDeformerBindings, SetDeformerParent, SetDeformerPivot, SetDeformerPivotX, SetDeformerPivotY, SetDeformerTransform, SetPartBindings, SetPartColor, SetPartDeformer, SetPartHeight, SetPartMesh, SetPartOrder, SetPartTransform, SetPartWidth, SetPhysicsRig, type StrandOverlap, type TurnDepths, type TurnSolveReport, TurnTargetError, type TurnTargets, UV_INSET_PX, armGeometry, captureBindingEndpoint, columnRuns, computeGridOffsets, createDefaultMatrixDeformer, createDefaultPart, createDefaultWarpDeformer, createGridMesh, createLayerSetMeasurer, denseCoreOf, detectAlphaBbox, forearmPoseGeometry, foregroundSpan, generateIkiFromLayerSet, headHalfOf, interpolateGridOffsets, isLipInsideRole, isSpeckCore, mouthFold, mouthOpening, mouthRestShift, packAtlas, parseLayerRoles, partIdsOfRole, styleRemovedMessage, upsertGridKeyform, uvRectFor, validateDeformerDelete, validateDeformerReparent, validatePartAttach };
1980
+ export { ALPHA_BBOX_THRESHOLD, ALPHA_OPAQUE, ATLAS_PADDING, AddDeformer, AddPart, AddPhysicsRig, type AlphaBbox, type ApplyAtlasInput, type ArmGeometry, type AtlasAssignment, type AtlasLayout, type AtlasPlacement, type AtlasSource, BakeError, type BakeReport, type BakeTarget, CaptureGridKeyform, type CustomPart, CustomPartError, DEFAULT_TURN_TARGETS, type DeformerTransformChannel, DeleteDeformer, DeletePart, DeletePhysicsRig, type EditCommand, type EditTransformChannel, EditorDocument, type ForearmPoseGeometry, HEAD_BAND, type HairSideRoom, type IrisStrand, LIP_INSIDE_ROLES, LIP_ROLES, LayerGeometryError, type LayerInput, type LayerSetMeasurement, type LayerSetMeasurer, type LipInsideRole, type LipRole, type Opening, type OpeningColumn, PatchError, type PatchReport, RIG_PLAN_FIELDS, type RgbaLayer, type RigBakes, type RigPatch, type RigPlan, RigPlanError, type RigPlanField, type RigPlanReport, SHUT_BY, SPECK_CORE_FRACTION, SetDeformerBindings, SetDeformerParent, SetDeformerPivot, SetDeformerPivotX, SetDeformerPivotY, SetDeformerTransform, SetPartBindings, SetPartColor, SetPartDeformer, SetPartHeight, SetPartMesh, SetPartOrder, SetPartTransform, SetPartWidth, SetPhysicsRig, type StrandOverlap, type TurnDepths, type TurnSolveReport, TurnTargetError, type TurnTargets, UV_INSET_PX, armGeometry, captureBindingEndpoint, columnRuns, computeGridOffsets, createDefaultMatrixDeformer, createDefaultPart, createDefaultWarpDeformer, createGridMesh, createLayerSetMeasurer, denseCoreOf, detectAlphaBbox, forearmPoseGeometry, foregroundSpan, generateIkiFromLayerSet, headHalfOf, interpolateGridOffsets, isLipInsideRole, isSpeckCore, mouthFold, mouthOpening, mouthRestShift, packAtlas, parseLayerRoles, partIdsOfRole, rigidResidual, styleRemovedMessage, upsertGridKeyform, uvRectFor, validateDeformerDelete, validateDeformerReparent, validatePartAttach };