@ikijs/editor 0.30.1 → 0.32.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 +26 -14
- package/dist/index.d.mts +184 -12
- package/dist/index.d.ts +184 -12
- package/dist/index.js +1018 -39
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1015 -39
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
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,19 @@ 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`.
|
|
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`, and `occlusion`: roles whose parts hide it by an amount keyed on a
|
|
106
|
+
parameter), unrigged on `headDeformer` with a plain mesh and drawn after the
|
|
107
|
+
`after` role's last part; the rest of the model is as without them but for
|
|
108
|
+
the `order` of every part after an inserted one (a set rigged without
|
|
109
|
+
`customParts` is byte-identical), and a malformed entry throws
|
|
110
|
+
`CustomPartError`. `options.bakes` (`RigBakes`: control lattices per
|
|
111
|
+
parameter stop, `meshes` to re-mesh) writes warps on those parts that
|
|
112
|
+
land them where the lattice asks, solved in closed form through the
|
|
113
|
+
head's frame; a `rigid` custom part refuses a bend over 0.0025 face
|
|
114
|
+
widths (`rigidResidual`), a refused input throws `BakeError`, and
|
|
115
|
+
`options.onBakesApplied` gets what each part was asked for. The turn
|
|
104
116
|
is bounded by the art: the far eye stays inside the plate's outline and, given
|
|
105
117
|
`options.strandEdges`, the far iris may not slide under the bangs' side strand
|
|
106
118
|
any further than it is painted — best effort, never a refusal. The report's
|
package/dist/index.d.mts
CHANGED
|
@@ -251,10 +251,11 @@ declare class AddDeformer implements EditCommand {
|
|
|
251
251
|
* visually predictable.
|
|
252
252
|
*
|
|
253
253
|
* No `parseIkiModel` pre-check is needed: removing an element from an already-
|
|
254
|
-
* valid model cannot introduce a structural violation — EXCEPT for
|
|
255
|
-
* references
|
|
256
|
-
* delete a part still used as another part's `clip.masks`
|
|
257
|
-
* so it can never leave a dangling
|
|
254
|
+
* valid model cannot introduce a structural violation — EXCEPT for the part→part
|
|
255
|
+
* references in the contract: `clip.masks` and `occlusion.by`. `apply` refuses to
|
|
256
|
+
* delete a part still used as another part's `clip.masks` or `occlusion.by`
|
|
257
|
+
* entry (guards below), so it can never leave a dangling ref that would fail
|
|
258
|
+
* `toIkiModel()`.
|
|
258
259
|
*
|
|
259
260
|
* Texture-reference safety (package invariant): `apply` refuses to delete a
|
|
260
261
|
* part that still carries `part.texture`. Texture/atlas state is non-undoable
|
|
@@ -1052,6 +1053,145 @@ interface PatchReport {
|
|
|
1052
1053
|
declare class PatchError extends Error {
|
|
1053
1054
|
constructor(message: string);
|
|
1054
1055
|
}
|
|
1056
|
+
/**
|
|
1057
|
+
* A part the auto-rigger has no role for (a hairpin, a twin tail): drawn from
|
|
1058
|
+
* its own canvas-sized layer, hung on the head with plain warps.
|
|
1059
|
+
*/
|
|
1060
|
+
interface CustomPart {
|
|
1061
|
+
/** A plain identifier; never a role's name or a part id a role yields. */
|
|
1062
|
+
id: string;
|
|
1063
|
+
layer: {
|
|
1064
|
+
fileName: string;
|
|
1065
|
+
/** The layer's alpha box in canvas px, as `LayerInput.bbox`. */
|
|
1066
|
+
bbox: {
|
|
1067
|
+
x: number;
|
|
1068
|
+
y: number;
|
|
1069
|
+
w: number;
|
|
1070
|
+
h: number;
|
|
1071
|
+
};
|
|
1072
|
+
};
|
|
1073
|
+
/** The role whose layer the part is fixed against. */
|
|
1074
|
+
anchor: string;
|
|
1075
|
+
/** Where the part meets the anchor, canvas px: inside both layers' boxes. */
|
|
1076
|
+
tie: [number, number];
|
|
1077
|
+
/** A role of the set: the part is drawn right after that role's last part. */
|
|
1078
|
+
after: string;
|
|
1079
|
+
/** The matrix deformer it hangs on; only `headDeformer` (the default) is
|
|
1080
|
+
* accepted. */
|
|
1081
|
+
deformer?: string;
|
|
1082
|
+
/** The art does not bend (a hairpin, a ribbon): a bake that bends it more
|
|
1083
|
+
* than 0.0025 face widths at a stop is refused (`BakeError`). */
|
|
1084
|
+
rigid?: boolean;
|
|
1085
|
+
/** Hide the part where the parts of these roles cover it, by an amount keyed on a
|
|
1086
|
+
* parameter (`IkiPart.occlusion`). `by` lists roles of the set: the
|
|
1087
|
+
* generator writes the part ids those roles yield. The parameter is one
|
|
1088
|
+
* the model declares and not a physics output; the stops follow the
|
|
1089
|
+
* format's rules. */
|
|
1090
|
+
occlusion?: {
|
|
1091
|
+
by: string[];
|
|
1092
|
+
parameter: string;
|
|
1093
|
+
stops: {
|
|
1094
|
+
value: number;
|
|
1095
|
+
amount: number;
|
|
1096
|
+
}[];
|
|
1097
|
+
};
|
|
1098
|
+
}
|
|
1099
|
+
/** A `customParts` entry that is malformed or misses the layer set. Its
|
|
1100
|
+
* message starts `customParts[i].<field>` (`customParts` alone for a
|
|
1101
|
+
* non-array). */
|
|
1102
|
+
declare class CustomPartError extends Error {
|
|
1103
|
+
constructor(message: string);
|
|
1104
|
+
}
|
|
1105
|
+
/**
|
|
1106
|
+
* One lattice a bake spreads over the custom parts it names: `(cols + 1) x
|
|
1107
|
+
* (rows + 1)` control points laid evenly over `box`, listed row-major from the
|
|
1108
|
+
* top-left (the top row first, each row left to right). Every position and
|
|
1109
|
+
* displacement is in canvas px, origin top-left, y DOWN.
|
|
1110
|
+
*
|
|
1111
|
+
* A stop is exact where the bake writes it, and so is the end key a side's
|
|
1112
|
+
* outermost stop gets at the parameter's range end; between keys the motion
|
|
1113
|
+
* is interpolated, and under a parent that rotates with the parameter (the
|
|
1114
|
+
* head's roll) it bows off the straight line, so put a stop where the path
|
|
1115
|
+
* matters. A side with no stop is not moved by this target. Targets that name the same part and
|
|
1116
|
+
* parameter add.
|
|
1117
|
+
*/
|
|
1118
|
+
interface BakeTarget {
|
|
1119
|
+
/** A label for reports; the rig ignores it. */
|
|
1120
|
+
id?: string;
|
|
1121
|
+
/** A parameter the model declares. */
|
|
1122
|
+
param: string;
|
|
1123
|
+
/** Custom part ids only: a bake moves nothing else. */
|
|
1124
|
+
parts: string[];
|
|
1125
|
+
/** Canvas px; absent, the union of the parts' rest boxes grown by 2 px. */
|
|
1126
|
+
box?: {
|
|
1127
|
+
x: number;
|
|
1128
|
+
y: number;
|
|
1129
|
+
w: number;
|
|
1130
|
+
h: number;
|
|
1131
|
+
};
|
|
1132
|
+
/** Integers 1 to 32. */
|
|
1133
|
+
grid: {
|
|
1134
|
+
cols: number;
|
|
1135
|
+
rows: number;
|
|
1136
|
+
};
|
|
1137
|
+
/** Keyed by a value of `param` inside its range, never its default (rest is
|
|
1138
|
+
* implicit). A stop is the part's TOTAL landed displacement from rest when
|
|
1139
|
+
* `param` alone is at that value and every other parameter is at its
|
|
1140
|
+
* default: the head's own carry is included, so a neighbour's measured
|
|
1141
|
+
* displacement at that pose is written as it is. */
|
|
1142
|
+
stops: Record<string, [number, number][]>;
|
|
1143
|
+
/** Fill a missing stop from the one at the value mirrored about the rest
|
|
1144
|
+
* value: the lattice reflected about the box's vertical centre line, `dx`
|
|
1145
|
+
* negated. A written stop is never overwritten. */
|
|
1146
|
+
mirror?: boolean;
|
|
1147
|
+
/** Why the target exists, for whoever edits the bakes next; ignored. */
|
|
1148
|
+
why?: string;
|
|
1149
|
+
}
|
|
1150
|
+
/**
|
|
1151
|
+
* Lattice bakes for custom parts: what moves them when the head turns or the
|
|
1152
|
+
* hair sways, which an unrigged custom part otherwise does not. A bake moves
|
|
1153
|
+
* custom parts only.
|
|
1154
|
+
*/
|
|
1155
|
+
interface RigBakes {
|
|
1156
|
+
targets: BakeTarget[];
|
|
1157
|
+
/** Re-mesh a custom part before baking: `rows` (canvas-px y, strictly
|
|
1158
|
+
* increasing, first the part's top and last its bottom within 0.5 px) with
|
|
1159
|
+
* `cols`, or `cellPx` for a uniform grid. "The box" here is the part's own
|
|
1160
|
+
* rest box (its layer's bbox), not a target's `box`. */
|
|
1161
|
+
meshes?: Record<string, {
|
|
1162
|
+
rows: number[];
|
|
1163
|
+
cols: number;
|
|
1164
|
+
} | {
|
|
1165
|
+
cellPx: number;
|
|
1166
|
+
}>;
|
|
1167
|
+
}
|
|
1168
|
+
/** What a bake asked of one custom part on one parameter, for a caller that
|
|
1169
|
+
* checks the landing. */
|
|
1170
|
+
interface BakeReport {
|
|
1171
|
+
part: string;
|
|
1172
|
+
param: string;
|
|
1173
|
+
/** The stops written, ascending (rest, implicit, not among them). */
|
|
1174
|
+
stops: number[];
|
|
1175
|
+
vertexCount: number;
|
|
1176
|
+
/** Per stop, in `stops` order: each vertex's wanted displacement from rest,
|
|
1177
|
+
* `[dx0, dy0, dx1, dy1, ...]` in canvas px (y down), before the offsets
|
|
1178
|
+
* were rounded. */
|
|
1179
|
+
wanted: number[][];
|
|
1180
|
+
/** The end keys the bake wrote at the parameter's range ends, ascending,
|
|
1181
|
+
* each with the displacement it was solved for (`wanted` as above): its
|
|
1182
|
+
* side's outermost stop's. A side with no stop, or whose outermost stop is
|
|
1183
|
+
* at the end, has none. */
|
|
1184
|
+
ends: {
|
|
1185
|
+
value: number;
|
|
1186
|
+
wanted: number[];
|
|
1187
|
+
}[];
|
|
1188
|
+
}
|
|
1189
|
+
/** A `bakes` input that is malformed or misses the model. Its message starts
|
|
1190
|
+
* with the path of the field (`bakes.targets[2].stops["30"][7]`,
|
|
1191
|
+
* `bakes.meshes.tail_R.rows[0]`). */
|
|
1192
|
+
declare class BakeError extends Error {
|
|
1193
|
+
constructor(message: string);
|
|
1194
|
+
}
|
|
1055
1195
|
interface HeadEdges {
|
|
1056
1196
|
left: {
|
|
1057
1197
|
role: string;
|
|
@@ -1076,6 +1216,17 @@ interface GenerateOptions {
|
|
|
1076
1216
|
/** Displacements added to the generated rig's stops (`RigPatch`), replayed
|
|
1077
1217
|
* on every rig; a refused op throws `PatchError` and rigs nothing. */
|
|
1078
1218
|
patches?: RigPatch[];
|
|
1219
|
+
/** Parts the auto-rigger has no role for, put in the model unrigged: drawn
|
|
1220
|
+
* from their own layers, hung on the head. Every entry is checked first
|
|
1221
|
+
* (`CustomPartError`). */
|
|
1222
|
+
customParts?: CustomPart[];
|
|
1223
|
+
/** Lattices that move the custom parts when a parameter does (`RigBakes`):
|
|
1224
|
+
* written as warps on the parts, after the patches. A refused input throws
|
|
1225
|
+
* `BakeError` and rigs nothing. */
|
|
1226
|
+
bakes?: RigBakes;
|
|
1227
|
+
/** What `bakes` asked of each custom part and parameter — called only when
|
|
1228
|
+
* `bakes` is given, and only for a model that validates. */
|
|
1229
|
+
onBakesApplied?: (report: BakeReport[]) => void;
|
|
1079
1230
|
/** What each of `patches` moved, in order — called only when `patches` is
|
|
1080
1231
|
* given, and only for a model that validates. */
|
|
1081
1232
|
onPatchesApplied?: (report: PatchReport[]) => void;
|
|
@@ -1130,17 +1281,17 @@ interface GenerateOptions {
|
|
|
1130
1281
|
* the shoulder's +: it turns CW on the +x side, CCW on the −x side.
|
|
1131
1282
|
*/
|
|
1132
1283
|
|
|
1133
|
-
type Point = {
|
|
1284
|
+
type Point$1 = {
|
|
1134
1285
|
x: number;
|
|
1135
1286
|
y: number;
|
|
1136
1287
|
};
|
|
1137
1288
|
interface ArmGeometry {
|
|
1138
1289
|
/** The shoulder pivot, model space: the deltoid cap's centre. */
|
|
1139
|
-
shoulder: Point;
|
|
1290
|
+
shoulder: Point$1;
|
|
1140
1291
|
/** r_u: the upper arm's half-width under the cap, px. */
|
|
1141
1292
|
shoulderRadius: number;
|
|
1142
1293
|
/** The elbow pivot, model space. Its y is the seam the two meshes meet on. */
|
|
1143
|
-
elbow: Point;
|
|
1294
|
+
elbow: Point$1;
|
|
1144
1295
|
/** The elbow cap's largest radius, px. */
|
|
1145
1296
|
capRadius: number;
|
|
1146
1297
|
/** The cap's radius on each of its `CAP_SEGMENTS + 1` rays, from the elbow
|
|
@@ -1395,6 +1546,26 @@ declare function mouthFold(role: LipRole | LipInsideRole, opening: Opening): (co
|
|
|
1395
1546
|
* clamps differently, and the knot grid starts at the union's edge. */
|
|
1396
1547
|
declare function mouthRestShift(byRole: Map<string, LayerInput>): (role: "lip_upper" | "lip_lower", col: number, row: number) => number | undefined;
|
|
1397
1548
|
|
|
1549
|
+
/**
|
|
1550
|
+
* The `bakes` generator input, in two steps. `parseBakes` checks the whole
|
|
1551
|
+
* input before anything is applied, so a refusal changes nothing; each
|
|
1552
|
+
* message starts at the path of the field (`bakes.targets[2].stops["30"][7]`).
|
|
1553
|
+
* `applyBakes` then re-meshes the named parts and writes their warps, solved
|
|
1554
|
+
* in closed form through the head's chain.
|
|
1555
|
+
*/
|
|
1556
|
+
|
|
1557
|
+
type Point = [number, number];
|
|
1558
|
+
/**
|
|
1559
|
+
* The largest distance, over the point set, from where `moved` puts a point
|
|
1560
|
+
* to where the least-squares rigid fit of `rest` puts it: 0 for a rigid
|
|
1561
|
+
* motion, the bend of anything else. The fit is a rotation and a translation,
|
|
1562
|
+
* no scale and no reflection, and it minimises the summed squares, not the
|
|
1563
|
+
* largest distance (so the result can exceed the minimax one). Both lists
|
|
1564
|
+
* hold the same points in the same order, in one frame, so their lengths must
|
|
1565
|
+
* be equal.
|
|
1566
|
+
*/
|
|
1567
|
+
declare function rigidResidual(rest: readonly Point[], moved: readonly Point[]): number;
|
|
1568
|
+
|
|
1398
1569
|
/**
|
|
1399
1570
|
* `generateIkiFromLayerSet`: role layers in, a rigged `.iki` out.
|
|
1400
1571
|
*
|
|
@@ -1760,10 +1931,11 @@ declare function validateDeformerReparent(deformers: IkiDeformer[], deformerId:
|
|
|
1760
1931
|
* `toIkiModel()` refuses.
|
|
1761
1932
|
*
|
|
1762
1933
|
* Note: there is no validatePartDelete here, but NOT because parts are
|
|
1763
|
-
* unreferenced — `clip.masks`
|
|
1764
|
-
* closer to the edits that could break it: `DeletePart`
|
|
1765
|
-
* part still used as a mask
|
|
1766
|
-
* one (
|
|
1934
|
+
* unreferenced — `clip.masks` and `occlusion.by` name parts by id. That
|
|
1935
|
+
* invariant is enforced closer to the edits that could break it: `DeletePart`
|
|
1936
|
+
* refuses to remove a part still used as a mask or an occluder, and
|
|
1937
|
+
* `SetPartMesh` refuses to strip the mesh off one (both must be mesh parts).
|
|
1938
|
+
* No command creates or edits `clip` or `occlusion`.
|
|
1767
1939
|
*/
|
|
1768
1940
|
declare function validateDeformerDelete(deformers: IkiDeformer[], parts: IkiPart[], physicsChains: IkiPhysicsChain[], deformerId: string): void;
|
|
1769
1941
|
/**
|
|
@@ -1820,4 +1992,4 @@ declare function createGridMesh(cols: number, rows: number): IkiMesh;
|
|
|
1820
1992
|
*/
|
|
1821
1993
|
declare function createDefaultWarpDeformer(model: IkiModel): IkiWarpDeformer;
|
|
1822
1994
|
|
|
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 };
|
|
1995
|
+
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
|
@@ -251,10 +251,11 @@ declare class AddDeformer implements EditCommand {
|
|
|
251
251
|
* visually predictable.
|
|
252
252
|
*
|
|
253
253
|
* No `parseIkiModel` pre-check is needed: removing an element from an already-
|
|
254
|
-
* valid model cannot introduce a structural violation — EXCEPT for
|
|
255
|
-
* references
|
|
256
|
-
* delete a part still used as another part's `clip.masks`
|
|
257
|
-
* so it can never leave a dangling
|
|
254
|
+
* valid model cannot introduce a structural violation — EXCEPT for the part→part
|
|
255
|
+
* references in the contract: `clip.masks` and `occlusion.by`. `apply` refuses to
|
|
256
|
+
* delete a part still used as another part's `clip.masks` or `occlusion.by`
|
|
257
|
+
* entry (guards below), so it can never leave a dangling ref that would fail
|
|
258
|
+
* `toIkiModel()`.
|
|
258
259
|
*
|
|
259
260
|
* Texture-reference safety (package invariant): `apply` refuses to delete a
|
|
260
261
|
* part that still carries `part.texture`. Texture/atlas state is non-undoable
|
|
@@ -1052,6 +1053,145 @@ interface PatchReport {
|
|
|
1052
1053
|
declare class PatchError extends Error {
|
|
1053
1054
|
constructor(message: string);
|
|
1054
1055
|
}
|
|
1056
|
+
/**
|
|
1057
|
+
* A part the auto-rigger has no role for (a hairpin, a twin tail): drawn from
|
|
1058
|
+
* its own canvas-sized layer, hung on the head with plain warps.
|
|
1059
|
+
*/
|
|
1060
|
+
interface CustomPart {
|
|
1061
|
+
/** A plain identifier; never a role's name or a part id a role yields. */
|
|
1062
|
+
id: string;
|
|
1063
|
+
layer: {
|
|
1064
|
+
fileName: string;
|
|
1065
|
+
/** The layer's alpha box in canvas px, as `LayerInput.bbox`. */
|
|
1066
|
+
bbox: {
|
|
1067
|
+
x: number;
|
|
1068
|
+
y: number;
|
|
1069
|
+
w: number;
|
|
1070
|
+
h: number;
|
|
1071
|
+
};
|
|
1072
|
+
};
|
|
1073
|
+
/** The role whose layer the part is fixed against. */
|
|
1074
|
+
anchor: string;
|
|
1075
|
+
/** Where the part meets the anchor, canvas px: inside both layers' boxes. */
|
|
1076
|
+
tie: [number, number];
|
|
1077
|
+
/** A role of the set: the part is drawn right after that role's last part. */
|
|
1078
|
+
after: string;
|
|
1079
|
+
/** The matrix deformer it hangs on; only `headDeformer` (the default) is
|
|
1080
|
+
* accepted. */
|
|
1081
|
+
deformer?: string;
|
|
1082
|
+
/** The art does not bend (a hairpin, a ribbon): a bake that bends it more
|
|
1083
|
+
* than 0.0025 face widths at a stop is refused (`BakeError`). */
|
|
1084
|
+
rigid?: boolean;
|
|
1085
|
+
/** Hide the part where the parts of these roles cover it, by an amount keyed on a
|
|
1086
|
+
* parameter (`IkiPart.occlusion`). `by` lists roles of the set: the
|
|
1087
|
+
* generator writes the part ids those roles yield. The parameter is one
|
|
1088
|
+
* the model declares and not a physics output; the stops follow the
|
|
1089
|
+
* format's rules. */
|
|
1090
|
+
occlusion?: {
|
|
1091
|
+
by: string[];
|
|
1092
|
+
parameter: string;
|
|
1093
|
+
stops: {
|
|
1094
|
+
value: number;
|
|
1095
|
+
amount: number;
|
|
1096
|
+
}[];
|
|
1097
|
+
};
|
|
1098
|
+
}
|
|
1099
|
+
/** A `customParts` entry that is malformed or misses the layer set. Its
|
|
1100
|
+
* message starts `customParts[i].<field>` (`customParts` alone for a
|
|
1101
|
+
* non-array). */
|
|
1102
|
+
declare class CustomPartError extends Error {
|
|
1103
|
+
constructor(message: string);
|
|
1104
|
+
}
|
|
1105
|
+
/**
|
|
1106
|
+
* One lattice a bake spreads over the custom parts it names: `(cols + 1) x
|
|
1107
|
+
* (rows + 1)` control points laid evenly over `box`, listed row-major from the
|
|
1108
|
+
* top-left (the top row first, each row left to right). Every position and
|
|
1109
|
+
* displacement is in canvas px, origin top-left, y DOWN.
|
|
1110
|
+
*
|
|
1111
|
+
* A stop is exact where the bake writes it, and so is the end key a side's
|
|
1112
|
+
* outermost stop gets at the parameter's range end; between keys the motion
|
|
1113
|
+
* is interpolated, and under a parent that rotates with the parameter (the
|
|
1114
|
+
* head's roll) it bows off the straight line, so put a stop where the path
|
|
1115
|
+
* matters. A side with no stop is not moved by this target. Targets that name the same part and
|
|
1116
|
+
* parameter add.
|
|
1117
|
+
*/
|
|
1118
|
+
interface BakeTarget {
|
|
1119
|
+
/** A label for reports; the rig ignores it. */
|
|
1120
|
+
id?: string;
|
|
1121
|
+
/** A parameter the model declares. */
|
|
1122
|
+
param: string;
|
|
1123
|
+
/** Custom part ids only: a bake moves nothing else. */
|
|
1124
|
+
parts: string[];
|
|
1125
|
+
/** Canvas px; absent, the union of the parts' rest boxes grown by 2 px. */
|
|
1126
|
+
box?: {
|
|
1127
|
+
x: number;
|
|
1128
|
+
y: number;
|
|
1129
|
+
w: number;
|
|
1130
|
+
h: number;
|
|
1131
|
+
};
|
|
1132
|
+
/** Integers 1 to 32. */
|
|
1133
|
+
grid: {
|
|
1134
|
+
cols: number;
|
|
1135
|
+
rows: number;
|
|
1136
|
+
};
|
|
1137
|
+
/** Keyed by a value of `param` inside its range, never its default (rest is
|
|
1138
|
+
* implicit). A stop is the part's TOTAL landed displacement from rest when
|
|
1139
|
+
* `param` alone is at that value and every other parameter is at its
|
|
1140
|
+
* default: the head's own carry is included, so a neighbour's measured
|
|
1141
|
+
* displacement at that pose is written as it is. */
|
|
1142
|
+
stops: Record<string, [number, number][]>;
|
|
1143
|
+
/** Fill a missing stop from the one at the value mirrored about the rest
|
|
1144
|
+
* value: the lattice reflected about the box's vertical centre line, `dx`
|
|
1145
|
+
* negated. A written stop is never overwritten. */
|
|
1146
|
+
mirror?: boolean;
|
|
1147
|
+
/** Why the target exists, for whoever edits the bakes next; ignored. */
|
|
1148
|
+
why?: string;
|
|
1149
|
+
}
|
|
1150
|
+
/**
|
|
1151
|
+
* Lattice bakes for custom parts: what moves them when the head turns or the
|
|
1152
|
+
* hair sways, which an unrigged custom part otherwise does not. A bake moves
|
|
1153
|
+
* custom parts only.
|
|
1154
|
+
*/
|
|
1155
|
+
interface RigBakes {
|
|
1156
|
+
targets: BakeTarget[];
|
|
1157
|
+
/** Re-mesh a custom part before baking: `rows` (canvas-px y, strictly
|
|
1158
|
+
* increasing, first the part's top and last its bottom within 0.5 px) with
|
|
1159
|
+
* `cols`, or `cellPx` for a uniform grid. "The box" here is the part's own
|
|
1160
|
+
* rest box (its layer's bbox), not a target's `box`. */
|
|
1161
|
+
meshes?: Record<string, {
|
|
1162
|
+
rows: number[];
|
|
1163
|
+
cols: number;
|
|
1164
|
+
} | {
|
|
1165
|
+
cellPx: number;
|
|
1166
|
+
}>;
|
|
1167
|
+
}
|
|
1168
|
+
/** What a bake asked of one custom part on one parameter, for a caller that
|
|
1169
|
+
* checks the landing. */
|
|
1170
|
+
interface BakeReport {
|
|
1171
|
+
part: string;
|
|
1172
|
+
param: string;
|
|
1173
|
+
/** The stops written, ascending (rest, implicit, not among them). */
|
|
1174
|
+
stops: number[];
|
|
1175
|
+
vertexCount: number;
|
|
1176
|
+
/** Per stop, in `stops` order: each vertex's wanted displacement from rest,
|
|
1177
|
+
* `[dx0, dy0, dx1, dy1, ...]` in canvas px (y down), before the offsets
|
|
1178
|
+
* were rounded. */
|
|
1179
|
+
wanted: number[][];
|
|
1180
|
+
/** The end keys the bake wrote at the parameter's range ends, ascending,
|
|
1181
|
+
* each with the displacement it was solved for (`wanted` as above): its
|
|
1182
|
+
* side's outermost stop's. A side with no stop, or whose outermost stop is
|
|
1183
|
+
* at the end, has none. */
|
|
1184
|
+
ends: {
|
|
1185
|
+
value: number;
|
|
1186
|
+
wanted: number[];
|
|
1187
|
+
}[];
|
|
1188
|
+
}
|
|
1189
|
+
/** A `bakes` input that is malformed or misses the model. Its message starts
|
|
1190
|
+
* with the path of the field (`bakes.targets[2].stops["30"][7]`,
|
|
1191
|
+
* `bakes.meshes.tail_R.rows[0]`). */
|
|
1192
|
+
declare class BakeError extends Error {
|
|
1193
|
+
constructor(message: string);
|
|
1194
|
+
}
|
|
1055
1195
|
interface HeadEdges {
|
|
1056
1196
|
left: {
|
|
1057
1197
|
role: string;
|
|
@@ -1076,6 +1216,17 @@ interface GenerateOptions {
|
|
|
1076
1216
|
/** Displacements added to the generated rig's stops (`RigPatch`), replayed
|
|
1077
1217
|
* on every rig; a refused op throws `PatchError` and rigs nothing. */
|
|
1078
1218
|
patches?: RigPatch[];
|
|
1219
|
+
/** Parts the auto-rigger has no role for, put in the model unrigged: drawn
|
|
1220
|
+
* from their own layers, hung on the head. Every entry is checked first
|
|
1221
|
+
* (`CustomPartError`). */
|
|
1222
|
+
customParts?: CustomPart[];
|
|
1223
|
+
/** Lattices that move the custom parts when a parameter does (`RigBakes`):
|
|
1224
|
+
* written as warps on the parts, after the patches. A refused input throws
|
|
1225
|
+
* `BakeError` and rigs nothing. */
|
|
1226
|
+
bakes?: RigBakes;
|
|
1227
|
+
/** What `bakes` asked of each custom part and parameter — called only when
|
|
1228
|
+
* `bakes` is given, and only for a model that validates. */
|
|
1229
|
+
onBakesApplied?: (report: BakeReport[]) => void;
|
|
1079
1230
|
/** What each of `patches` moved, in order — called only when `patches` is
|
|
1080
1231
|
* given, and only for a model that validates. */
|
|
1081
1232
|
onPatchesApplied?: (report: PatchReport[]) => void;
|
|
@@ -1130,17 +1281,17 @@ interface GenerateOptions {
|
|
|
1130
1281
|
* the shoulder's +: it turns CW on the +x side, CCW on the −x side.
|
|
1131
1282
|
*/
|
|
1132
1283
|
|
|
1133
|
-
type Point = {
|
|
1284
|
+
type Point$1 = {
|
|
1134
1285
|
x: number;
|
|
1135
1286
|
y: number;
|
|
1136
1287
|
};
|
|
1137
1288
|
interface ArmGeometry {
|
|
1138
1289
|
/** The shoulder pivot, model space: the deltoid cap's centre. */
|
|
1139
|
-
shoulder: Point;
|
|
1290
|
+
shoulder: Point$1;
|
|
1140
1291
|
/** r_u: the upper arm's half-width under the cap, px. */
|
|
1141
1292
|
shoulderRadius: number;
|
|
1142
1293
|
/** The elbow pivot, model space. Its y is the seam the two meshes meet on. */
|
|
1143
|
-
elbow: Point;
|
|
1294
|
+
elbow: Point$1;
|
|
1144
1295
|
/** The elbow cap's largest radius, px. */
|
|
1145
1296
|
capRadius: number;
|
|
1146
1297
|
/** The cap's radius on each of its `CAP_SEGMENTS + 1` rays, from the elbow
|
|
@@ -1395,6 +1546,26 @@ declare function mouthFold(role: LipRole | LipInsideRole, opening: Opening): (co
|
|
|
1395
1546
|
* clamps differently, and the knot grid starts at the union's edge. */
|
|
1396
1547
|
declare function mouthRestShift(byRole: Map<string, LayerInput>): (role: "lip_upper" | "lip_lower", col: number, row: number) => number | undefined;
|
|
1397
1548
|
|
|
1549
|
+
/**
|
|
1550
|
+
* The `bakes` generator input, in two steps. `parseBakes` checks the whole
|
|
1551
|
+
* input before anything is applied, so a refusal changes nothing; each
|
|
1552
|
+
* message starts at the path of the field (`bakes.targets[2].stops["30"][7]`).
|
|
1553
|
+
* `applyBakes` then re-meshes the named parts and writes their warps, solved
|
|
1554
|
+
* in closed form through the head's chain.
|
|
1555
|
+
*/
|
|
1556
|
+
|
|
1557
|
+
type Point = [number, number];
|
|
1558
|
+
/**
|
|
1559
|
+
* The largest distance, over the point set, from where `moved` puts a point
|
|
1560
|
+
* to where the least-squares rigid fit of `rest` puts it: 0 for a rigid
|
|
1561
|
+
* motion, the bend of anything else. The fit is a rotation and a translation,
|
|
1562
|
+
* no scale and no reflection, and it minimises the summed squares, not the
|
|
1563
|
+
* largest distance (so the result can exceed the minimax one). Both lists
|
|
1564
|
+
* hold the same points in the same order, in one frame, so their lengths must
|
|
1565
|
+
* be equal.
|
|
1566
|
+
*/
|
|
1567
|
+
declare function rigidResidual(rest: readonly Point[], moved: readonly Point[]): number;
|
|
1568
|
+
|
|
1398
1569
|
/**
|
|
1399
1570
|
* `generateIkiFromLayerSet`: role layers in, a rigged `.iki` out.
|
|
1400
1571
|
*
|
|
@@ -1760,10 +1931,11 @@ declare function validateDeformerReparent(deformers: IkiDeformer[], deformerId:
|
|
|
1760
1931
|
* `toIkiModel()` refuses.
|
|
1761
1932
|
*
|
|
1762
1933
|
* Note: there is no validatePartDelete here, but NOT because parts are
|
|
1763
|
-
* unreferenced — `clip.masks`
|
|
1764
|
-
* closer to the edits that could break it: `DeletePart`
|
|
1765
|
-
* part still used as a mask
|
|
1766
|
-
* one (
|
|
1934
|
+
* unreferenced — `clip.masks` and `occlusion.by` name parts by id. That
|
|
1935
|
+
* invariant is enforced closer to the edits that could break it: `DeletePart`
|
|
1936
|
+
* refuses to remove a part still used as a mask or an occluder, and
|
|
1937
|
+
* `SetPartMesh` refuses to strip the mesh off one (both must be mesh parts).
|
|
1938
|
+
* No command creates or edits `clip` or `occlusion`.
|
|
1767
1939
|
*/
|
|
1768
1940
|
declare function validateDeformerDelete(deformers: IkiDeformer[], parts: IkiPart[], physicsChains: IkiPhysicsChain[], deformerId: string): void;
|
|
1769
1941
|
/**
|
|
@@ -1820,4 +1992,4 @@ declare function createGridMesh(cols: number, rows: number): IkiMesh;
|
|
|
1820
1992
|
*/
|
|
1821
1993
|
declare function createDefaultWarpDeformer(model: IkiModel): IkiWarpDeformer;
|
|
1822
1994
|
|
|
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 };
|
|
1995
|
+
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 };
|