@ikijs/editor 0.1.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/LICENSE +21 -0
- package/README.md +73 -0
- package/dist/index.d.mts +916 -0
- package/dist/index.d.ts +916 -0
- package/dist/index.js +2293 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +2233 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +49 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,916 @@
|
|
|
1
|
+
import { IkiDeformer, IkiPart, IkiPhysics, IkiDeformerBinding, IkiMatrixDeformer, IkiBinding, IkiMesh, IkiModel, IkiWarpDeformer, IkiTexture, IkiUvRect, IkiTransform, IkiDeformerTransform, IkiTransformChannel, IkiGridKeyform, IkiPhysicsChain } from '@ikijs/format';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* One invertible edit. The document is passed IN at apply/invert time — a
|
|
5
|
+
* command is constructed from `(partId, value)` alone, before any document
|
|
6
|
+
* exists — so the same command object can be applied, inverted, and re-applied
|
|
7
|
+
* by the undo/redo stack.
|
|
8
|
+
*
|
|
9
|
+
* Prior-value capture happens exactly ONCE, on the first {@link apply}. `redo`
|
|
10
|
+
* (a second `apply`) reuses that captured value rather than re-reading the
|
|
11
|
+
* current field, so undo always restores the original target.
|
|
12
|
+
*/
|
|
13
|
+
interface EditCommand {
|
|
14
|
+
apply(doc: EditorDocument): void;
|
|
15
|
+
invert(doc: EditorDocument): void;
|
|
16
|
+
readonly label: string;
|
|
17
|
+
}
|
|
18
|
+
/** Channels of {@link IkiTransform} this editor can edit (object-field names,
|
|
19
|
+
* NOT the binding `IkiTransformChannel` vocabulary). */
|
|
20
|
+
type EditTransformChannel = "x" | "y" | "rotation" | "scaleX" | "scaleY" | "opacity";
|
|
21
|
+
/**
|
|
22
|
+
* Generic single-field command: reads/writes one field of the resolved part via
|
|
23
|
+
* a getter/setter closure, capturing the prior value on the first `apply` and
|
|
24
|
+
* restoring it on `invert`. `T` is the captured value's type; for cloned values
|
|
25
|
+
* (the color tuple) the getter/setter perform the copy.
|
|
26
|
+
*/
|
|
27
|
+
declare class FieldCommand<T> implements EditCommand {
|
|
28
|
+
private readonly partId;
|
|
29
|
+
private readonly newValue;
|
|
30
|
+
private readonly get;
|
|
31
|
+
private readonly set;
|
|
32
|
+
readonly label: string;
|
|
33
|
+
private captured;
|
|
34
|
+
private prevValue;
|
|
35
|
+
constructor(partId: string, newValue: T, label: string, get: (part: IkiPart) => T, set: (part: IkiPart, value: T) => void);
|
|
36
|
+
apply(doc: EditorDocument): void;
|
|
37
|
+
invert(doc: EditorDocument): void;
|
|
38
|
+
}
|
|
39
|
+
/** Edit a part's RGBA fill. The 4-tuple is mutable, so the command clones on
|
|
40
|
+
* construction (caller's array), on capture (part's current color), and on
|
|
41
|
+
* assign (writing to the part) — it never retains the caller's or model's
|
|
42
|
+
* array by reference. */
|
|
43
|
+
declare class SetPartColor extends FieldCommand<[
|
|
44
|
+
number,
|
|
45
|
+
number,
|
|
46
|
+
number,
|
|
47
|
+
number
|
|
48
|
+
]> {
|
|
49
|
+
constructor(partId: string, rgba: [number, number, number, number]);
|
|
50
|
+
}
|
|
51
|
+
/** Edit a part's width (model-space units). */
|
|
52
|
+
declare class SetPartWidth extends FieldCommand<number> {
|
|
53
|
+
constructor(partId: string, value: number);
|
|
54
|
+
}
|
|
55
|
+
/** Edit a part's height (model-space units). */
|
|
56
|
+
declare class SetPartHeight extends FieldCommand<number> {
|
|
57
|
+
constructor(partId: string, value: number);
|
|
58
|
+
}
|
|
59
|
+
/** Edit a part's paint order. */
|
|
60
|
+
declare class SetPartOrder extends FieldCommand<number> {
|
|
61
|
+
constructor(partId: string, value: number);
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Edit one channel of a part's base transform. `x`/`y` are required; the rest
|
|
65
|
+
* are optional and may be absent on the part. For an optional channel the
|
|
66
|
+
* command captures the raw current value INCLUDING `undefined`, and restoring
|
|
67
|
+
* `undefined` DELETES the key so undo returns the part to its original
|
|
68
|
+
* (possibly-omitted) shape. Engine defaults (rotation 0 / scale 1 / opacity 1)
|
|
69
|
+
* are NOT substituted here.
|
|
70
|
+
*/
|
|
71
|
+
declare class SetPartTransform extends FieldCommand<number | undefined> {
|
|
72
|
+
constructor(partId: string, channel: EditTransformChannel, value: number);
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Capture the warp deformer's grid as one keyform at the driving parameter
|
|
76
|
+
* `value`, upserting `offsets` into `warps[0].keyforms`. The 4-tuple-style
|
|
77
|
+
* mutable `offsets` array is cloned on construction so a later caller mutation
|
|
78
|
+
* cannot corrupt apply/redo (mirrors {@link SetPartColor}).
|
|
79
|
+
*
|
|
80
|
+
* `apply` validates BEFORE mutating: the deformer must have a grid warp, the
|
|
81
|
+
* offsets length must equal `grid.points.length`, and `value` must lie within
|
|
82
|
+
* the driving parameter's declared `[min,max]` (fail fast, before
|
|
83
|
+
* `parseIkiModel` would reject it). Prior keyforms are deep-copied once on the
|
|
84
|
+
* first `apply` (capture-once like {@link FieldCommand}); `invert` restores
|
|
85
|
+
* that deep copy so re-apply after undo never aliases.
|
|
86
|
+
*/
|
|
87
|
+
declare class CaptureGridKeyform implements EditCommand {
|
|
88
|
+
private readonly deformerId;
|
|
89
|
+
private readonly value;
|
|
90
|
+
readonly label = "Capture grid keyform";
|
|
91
|
+
private readonly offsets;
|
|
92
|
+
private captured;
|
|
93
|
+
private prevKeyforms;
|
|
94
|
+
constructor(deformerId: string, value: number, offsets: number[]);
|
|
95
|
+
apply(doc: EditorDocument): void;
|
|
96
|
+
invert(doc: EditorDocument): void;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Generic single-field command targeting a matrix deformer, mirroring
|
|
100
|
+
* {@link FieldCommand} but resolving via `doc.findMatrixDeformer` instead of
|
|
101
|
+
* `doc.findPart`. Capture-once on first `apply`; restore on `invert`.
|
|
102
|
+
*/
|
|
103
|
+
declare class DeformerFieldCommand<T> implements EditCommand {
|
|
104
|
+
private readonly deformerId;
|
|
105
|
+
private readonly newValue;
|
|
106
|
+
private readonly get;
|
|
107
|
+
private readonly set;
|
|
108
|
+
readonly label: string;
|
|
109
|
+
private captured;
|
|
110
|
+
private prevValue;
|
|
111
|
+
constructor(deformerId: string, newValue: T, label: string, get: (deformer: IkiMatrixDeformer) => T, set: (deformer: IkiMatrixDeformer, value: T) => void);
|
|
112
|
+
apply(doc: EditorDocument): void;
|
|
113
|
+
invert(doc: EditorDocument): void;
|
|
114
|
+
}
|
|
115
|
+
/** Edit a matrix deformer's pivot x. */
|
|
116
|
+
declare class SetDeformerPivotX extends DeformerFieldCommand<number> {
|
|
117
|
+
constructor(deformerId: string, value: number);
|
|
118
|
+
}
|
|
119
|
+
/** Edit a matrix deformer's pivot y. */
|
|
120
|
+
declare class SetDeformerPivotY extends DeformerFieldCommand<number> {
|
|
121
|
+
constructor(deformerId: string, value: number);
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Set a matrix deformer's pivot x and y atomically (one drag = one undo step).
|
|
125
|
+
* {@link SetDeformerPivotX} and {@link SetDeformerPivotY} remain for the
|
|
126
|
+
* Inspector's single-axis number inputs.
|
|
127
|
+
*/
|
|
128
|
+
declare class SetDeformerPivot implements EditCommand {
|
|
129
|
+
private readonly deformerId;
|
|
130
|
+
readonly label = "Set pivot";
|
|
131
|
+
private captured;
|
|
132
|
+
private prevPivot;
|
|
133
|
+
private readonly pivot;
|
|
134
|
+
constructor(deformerId: string, pivot: {
|
|
135
|
+
x: number;
|
|
136
|
+
y: number;
|
|
137
|
+
});
|
|
138
|
+
apply(doc: EditorDocument): void;
|
|
139
|
+
invert(doc: EditorDocument): void;
|
|
140
|
+
}
|
|
141
|
+
/** Channels of {@link IkiDeformerTransform} this editor can edit. */
|
|
142
|
+
type DeformerTransformChannel = "x" | "y" | "rotation" | "scaleX" | "scaleY";
|
|
143
|
+
/**
|
|
144
|
+
* Edit one channel of a matrix deformer's base transform. Because
|
|
145
|
+
* {@link IkiDeformerTransform} REQUIRES finite `x` and `y`, this command
|
|
146
|
+
* captures and restores the WHOLE prior `transform` object (present or absent)
|
|
147
|
+
* rather than a single channel, so undo can delete the transform when it did
|
|
148
|
+
* not previously exist, and redo never produces a partial object missing `x`/`y`.
|
|
149
|
+
*
|
|
150
|
+
* When no `transform` is present on the deformer, `apply` creates one from
|
|
151
|
+
* the identity base `{ x: 0, y: 0 }` — the minimal valid shape the validator
|
|
152
|
+
* accepts — then writes the edited channel. For example, editing `rotation`
|
|
153
|
+
* on a transform-less deformer yields `{ x: 0, y: 0, rotation: <value> }`.
|
|
154
|
+
*/
|
|
155
|
+
declare class SetDeformerTransform implements EditCommand {
|
|
156
|
+
private readonly deformerId;
|
|
157
|
+
private readonly channel;
|
|
158
|
+
private readonly value;
|
|
159
|
+
readonly label = "Set deformer transform";
|
|
160
|
+
private captured;
|
|
161
|
+
private prevTransform;
|
|
162
|
+
constructor(deformerId: string, channel: DeformerTransformChannel, value: number);
|
|
163
|
+
apply(doc: EditorDocument): void;
|
|
164
|
+
invert(doc: EditorDocument): void;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Replace a matrix deformer's `bindings` array wholesale. A single command
|
|
168
|
+
* covers add, edit, and remove (pass the desired final array; pass `[]` to
|
|
169
|
+
* remove all). Mirrors {@link CaptureGridKeyform}'s deep-copy discipline:
|
|
170
|
+
* clone-on-construction, capture-once, fresh deep copy on invert.
|
|
171
|
+
*
|
|
172
|
+
* Each {@link IkiDeformerBinding} is a flat object so a per-element spread
|
|
173
|
+
* `{ ...b }` is a sufficient deep copy.
|
|
174
|
+
*/
|
|
175
|
+
declare class SetDeformerBindings implements EditCommand {
|
|
176
|
+
private readonly deformerId;
|
|
177
|
+
readonly label = "Set deformer bindings";
|
|
178
|
+
private readonly bindings;
|
|
179
|
+
private captured;
|
|
180
|
+
private prevBindings;
|
|
181
|
+
constructor(deformerId: string, bindings: IkiDeformerBinding[]);
|
|
182
|
+
apply(doc: EditorDocument): void;
|
|
183
|
+
invert(doc: EditorDocument): void;
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Reparent a deformer (matrix or warp) under a new parent, or promote it to
|
|
187
|
+
* root (`newParentId === undefined`). Calls {@link validateDeformerReparent}
|
|
188
|
+
* FIRST so invalid reparents (cycles, warp parent, unknown id) throw before any
|
|
189
|
+
* capture or mutation — a throwing apply leaves the model and undo stack
|
|
190
|
+
* untouched.
|
|
191
|
+
*
|
|
192
|
+
* Absent-vs-present distinction: captures both the prior `parent` value AND
|
|
193
|
+
* whether the key was present on the object, so `invert` can delete the key
|
|
194
|
+
* (restore "absent") rather than blindly assigning `undefined`.
|
|
195
|
+
*/
|
|
196
|
+
declare class SetDeformerParent implements EditCommand {
|
|
197
|
+
private readonly deformerId;
|
|
198
|
+
private readonly newParentId;
|
|
199
|
+
readonly label = "Set deformer parent";
|
|
200
|
+
private captured;
|
|
201
|
+
private prevParent;
|
|
202
|
+
private prevHadParent;
|
|
203
|
+
constructor(deformerId: string, newParentId: string | undefined);
|
|
204
|
+
apply(doc: EditorDocument): void;
|
|
205
|
+
invert(doc: EditorDocument): void;
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Add a new part to the model. Validates the candidate model with
|
|
209
|
+
* {@link parseIkiModel} BEFORE mutating, so a structurally invalid part
|
|
210
|
+
* (bad color tuple, missing required fields, id collision with a deformer)
|
|
211
|
+
* throws an `IkiFormatError` and leaves the model untouched.
|
|
212
|
+
*
|
|
213
|
+
* Captures the prior base-UV entry for the part's id on the FIRST apply so
|
|
214
|
+
* invert can restore the exact pre-add state. This guards the id-reuse hazard:
|
|
215
|
+
* DeletePart(X) → AddPart(X′, different mesh) → undo(add) → undo(delete) —
|
|
216
|
+
* without restore, X's constructor-captured base would be gone, causing
|
|
217
|
+
* applyAtlas to fail when X is restored by the undo of the delete.
|
|
218
|
+
*/
|
|
219
|
+
declare class AddPart implements EditCommand {
|
|
220
|
+
readonly label = "Add part";
|
|
221
|
+
private readonly part;
|
|
222
|
+
private captured;
|
|
223
|
+
private prevBaseMeshUvs;
|
|
224
|
+
constructor(part: IkiPart);
|
|
225
|
+
apply(doc: EditorDocument): void;
|
|
226
|
+
invert(doc: EditorDocument): void;
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Add a new deformer (matrix or warp) to the model. Validates the candidate
|
|
230
|
+
* model with {@link parseIkiModel} BEFORE mutating — this enforces the warp
|
|
231
|
+
* rest-grid invariant, points length, pivot, parent, and bindings without
|
|
232
|
+
* hand-rolling partial checks. Mirrors {@link AddPart} but also tracks whether
|
|
233
|
+
* `model.deformers` was absent before the first apply, so `invert` can restore
|
|
234
|
+
* the exact key-absence state (mirrors {@link SetDeformerBindings}).
|
|
235
|
+
*/
|
|
236
|
+
declare class AddDeformer implements EditCommand {
|
|
237
|
+
readonly label = "Add deformer";
|
|
238
|
+
private readonly deformer;
|
|
239
|
+
private captured;
|
|
240
|
+
private prevDeformersAbsent;
|
|
241
|
+
constructor(deformer: IkiDeformer);
|
|
242
|
+
apply(doc: EditorDocument): void;
|
|
243
|
+
invert(doc: EditorDocument): void;
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Delete a part by id, preserving its original array slot so `invert` restores
|
|
247
|
+
* it at the same position. Slot position is cosmetic (the renderer uses the
|
|
248
|
+
* `order` field for paint ordering), but restoring the index keeps undo
|
|
249
|
+
* visually predictable.
|
|
250
|
+
*
|
|
251
|
+
* No `parseIkiModel` pre-check is needed: removing an element from an already-
|
|
252
|
+
* valid model cannot introduce a structural violation — EXCEPT for clip-mask
|
|
253
|
+
* references, the one part→part reference in the contract. `apply` refuses to
|
|
254
|
+
* delete a part still used as another part's `clip.masks` entry (guard below),
|
|
255
|
+
* so it can never leave a dangling mask ref that would fail `toIkiModel()`.
|
|
256
|
+
*
|
|
257
|
+
* Texture-reference safety (package invariant): `apply` refuses to delete a
|
|
258
|
+
* part that still carries `part.texture`. Texture/atlas state is non-undoable
|
|
259
|
+
* per the 5b boundary; clear the texture first via
|
|
260
|
+
* {@link EditorDocument.clearPartTextureRef} (model-committed) or
|
|
261
|
+
* {@link EditorDocument.applyAtlas} with no assignment (imported) — both are
|
|
262
|
+
* non-undoable. By the time a part is deletable it carries no texture, so
|
|
263
|
+
* `invert` can never restore a stale texture index that would render the wrong
|
|
264
|
+
* atlas region after a later atlas repack. No transactional atlas capture is
|
|
265
|
+
* needed in this command.
|
|
266
|
+
*/
|
|
267
|
+
declare class DeletePart implements EditCommand {
|
|
268
|
+
private readonly partId;
|
|
269
|
+
readonly label = "Delete part";
|
|
270
|
+
private captured;
|
|
271
|
+
private removed;
|
|
272
|
+
private index;
|
|
273
|
+
constructor(partId: string);
|
|
274
|
+
apply(doc: EditorDocument): void;
|
|
275
|
+
invert(doc: EditorDocument): void;
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Delete a deformer by id. Calls {@link validateDeformerDelete} FIRST so the
|
|
279
|
+
* delete is refused while anything still references it — a child deformer, an
|
|
280
|
+
* attached part, or a physics chain anchored to it — enforcing the same
|
|
281
|
+
* referential safety as {@link SetDeformerParent} and {@link SetPartDeformer}.
|
|
282
|
+
*
|
|
283
|
+
* `invert` re-inserts the deformer at its original index.
|
|
284
|
+
*/
|
|
285
|
+
declare class DeleteDeformer implements EditCommand {
|
|
286
|
+
private readonly deformerId;
|
|
287
|
+
readonly label = "Delete deformer";
|
|
288
|
+
private captured;
|
|
289
|
+
private removed;
|
|
290
|
+
private index;
|
|
291
|
+
constructor(deformerId: string);
|
|
292
|
+
apply(doc: EditorDocument): void;
|
|
293
|
+
invert(doc: EditorDocument): void;
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Replace a part's `bindings` array wholesale. A single command covers add,
|
|
297
|
+
* edit, and remove (pass the desired final array; pass `[]` to remove all).
|
|
298
|
+
* Mirrors {@link SetDeformerBindings}'s deep-copy and absent-vs-empty discipline:
|
|
299
|
+
* clone-on-construction, capture-once, fresh deep copy on every assign/invert.
|
|
300
|
+
*
|
|
301
|
+
* Each {@link IkiBinding} is a flat object, so a per-element spread `{ ...b }`
|
|
302
|
+
* is a sufficient deep copy.
|
|
303
|
+
*
|
|
304
|
+
* Validates the WRITTEN bindings against the declared parameters via a narrow
|
|
305
|
+
* synthetic {@link parseIkiModel} candidate (so unrelated in-flight invalid
|
|
306
|
+
* editor state — e.g. a NaN width on another part — cannot false-reject a
|
|
307
|
+
* binding edit). Validation runs BEFORE any mutation; on failure the model and
|
|
308
|
+
* undo stack are left untouched.
|
|
309
|
+
*
|
|
310
|
+
* Empty bindings → omit the `bindings` key on the candidate AND delete
|
|
311
|
+
* `part.bindings` on apply (keeps the model shape minimal; represents "no
|
|
312
|
+
* bindings" as an absent key rather than an empty array).
|
|
313
|
+
*/
|
|
314
|
+
declare class SetPartBindings implements EditCommand {
|
|
315
|
+
private readonly partId;
|
|
316
|
+
readonly label = "Set part bindings";
|
|
317
|
+
private readonly bindings;
|
|
318
|
+
private captured;
|
|
319
|
+
private prevBindings;
|
|
320
|
+
constructor(partId: string, bindings: IkiBinding[]);
|
|
321
|
+
apply(doc: EditorDocument): void;
|
|
322
|
+
invert(doc: EditorDocument): void;
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Attach a part to a deformer, or detach it (`newDeformerId === undefined`).
|
|
326
|
+
* Calls {@link validatePartAttach} FIRST so invalid attachments (warp without
|
|
327
|
+
* mesh, unknown ids) throw before any capture or mutation.
|
|
328
|
+
*
|
|
329
|
+
* Absent-vs-present distinction mirrors {@link SetDeformerParent}: captures
|
|
330
|
+
* both the prior `deformer` value and whether the key was present, so `invert`
|
|
331
|
+
* can delete the key rather than assigning `undefined`.
|
|
332
|
+
*/
|
|
333
|
+
declare class SetPartDeformer implements EditCommand {
|
|
334
|
+
private readonly partId;
|
|
335
|
+
private readonly newDeformerId;
|
|
336
|
+
readonly label = "Set part deformer";
|
|
337
|
+
private captured;
|
|
338
|
+
private prevDeformer;
|
|
339
|
+
private prevHadDeformer;
|
|
340
|
+
constructor(partId: string, newDeformerId: string | undefined);
|
|
341
|
+
apply(doc: EditorDocument): void;
|
|
342
|
+
invert(doc: EditorDocument): void;
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Add, regenerate, or remove the triangle mesh on a part.
|
|
346
|
+
*
|
|
347
|
+
* - `mesh !== undefined` → add or replace the mesh, registering the
|
|
348
|
+
* unit-square base UVs in the side-table so {@link EditorDocument.applyAtlas}
|
|
349
|
+
* can remap them later.
|
|
350
|
+
* - `mesh === undefined` → delete `part.mesh` and remove the side-table entry.
|
|
351
|
+
*
|
|
352
|
+
* Fails fast (BEFORE any mutation) on warp-topology violations:
|
|
353
|
+
* - REMOVE while `part.warps` is present (even empty) or the part is
|
|
354
|
+
* attached to a warp deformer — the format rejects any `warps` key once
|
|
355
|
+
* the mesh is gone.
|
|
356
|
+
* - ADD/REPLACE while `part.warps` has authored offsets (`length > 0`) —
|
|
357
|
+
* regenerating the mesh invalidates offset positions silently; the user
|
|
358
|
+
* must remove the warps first.
|
|
359
|
+
*
|
|
360
|
+
* The remove guard checks PRESENCE of `part.warps` (not length) while the
|
|
361
|
+
* add/replace guard checks LENGTH > 0. This asymmetry is intentional: the
|
|
362
|
+
* format allows `warps: []` only when a mesh exists, so any present key
|
|
363
|
+
* (even empty) would become invalid after mesh removal; but replacing a mesh
|
|
364
|
+
* under an empty `warps: []` is harmless because there are no authored offsets.
|
|
365
|
+
*/
|
|
366
|
+
declare class SetPartMesh implements EditCommand {
|
|
367
|
+
private readonly partId;
|
|
368
|
+
readonly label = "Set part mesh";
|
|
369
|
+
private readonly mesh;
|
|
370
|
+
private captured;
|
|
371
|
+
private prevHadMesh;
|
|
372
|
+
private prevMesh;
|
|
373
|
+
private prevBaseMeshUvs;
|
|
374
|
+
constructor(partId: string, mesh: IkiMesh | undefined);
|
|
375
|
+
apply(doc: EditorDocument): void;
|
|
376
|
+
invert(doc: EditorDocument): void;
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* Add a physics rig to `model.physics`. Mirrors {@link AddDeformer}: clone on
|
|
380
|
+
* construction, cheap friendly duplicate-id pre-check, full synthetic-candidate
|
|
381
|
+
* validation before mutating, capture the absent-vs-present `physics` state once,
|
|
382
|
+
* and on invert delete the key when the array empties back to its prior absence.
|
|
383
|
+
*/
|
|
384
|
+
declare class AddPhysicsRig implements EditCommand {
|
|
385
|
+
readonly label = "Add physics rig";
|
|
386
|
+
private readonly rig;
|
|
387
|
+
private captured;
|
|
388
|
+
private prevPhysicsAbsent;
|
|
389
|
+
constructor(rig: IkiPhysics);
|
|
390
|
+
apply(doc: EditorDocument): void;
|
|
391
|
+
invert(doc: EditorDocument): void;
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* Delete a physics rig by id. Mirrors {@link DeleteDeformer}: resolve/validate
|
|
395
|
+
* first, capture the removed rig + its index once, splice. Removing the LAST rig
|
|
396
|
+
* deletes the `physics` key to keep the exported shape minimal; invert re-inserts
|
|
397
|
+
* at the original index (recreating the array if it was deleted).
|
|
398
|
+
*/
|
|
399
|
+
declare class DeletePhysicsRig implements EditCommand {
|
|
400
|
+
private readonly rigId;
|
|
401
|
+
readonly label = "Delete physics rig";
|
|
402
|
+
private captured;
|
|
403
|
+
private removed;
|
|
404
|
+
private index;
|
|
405
|
+
constructor(rigId: string);
|
|
406
|
+
apply(doc: EditorDocument): void;
|
|
407
|
+
invert(doc: EditorDocument): void;
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* Replace a physics rig in place (tuning). Mirrors {@link SetDeformerBindings}
|
|
411
|
+
* but DEEP-clones the nested `input`/`output` (a shallow spread would alias them).
|
|
412
|
+
* Forbids rename (`rig.id` must equal the target `rigId`) — this command tunes a
|
|
413
|
+
* rig, never re-keys it. Validates the whole candidate (the edited rig swapped in
|
|
414
|
+
* at its index) so cross-rig rules still run, then captures the prior rig once.
|
|
415
|
+
*/
|
|
416
|
+
declare class SetPhysicsRig implements EditCommand {
|
|
417
|
+
private readonly rigId;
|
|
418
|
+
readonly label = "Set physics rig";
|
|
419
|
+
private readonly rig;
|
|
420
|
+
private captured;
|
|
421
|
+
private prevRig;
|
|
422
|
+
constructor(rigId: string, rig: IkiPhysics);
|
|
423
|
+
apply(doc: EditorDocument): void;
|
|
424
|
+
invert(doc: EditorDocument): void;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* One part mapped to an imported atlas source. `index` is always 0 because the
|
|
429
|
+
* atlas is a single page; only the `uv` sub-rectangle varies per part.
|
|
430
|
+
*/
|
|
431
|
+
interface AtlasAssignment {
|
|
432
|
+
partId: string;
|
|
433
|
+
uv: IkiUvRect;
|
|
434
|
+
}
|
|
435
|
+
/** Input to {@link EditorDocument.applyAtlas}: the new atlas table plus the
|
|
436
|
+
* per-part UV assignments into it. */
|
|
437
|
+
interface ApplyAtlasInput {
|
|
438
|
+
textures: IkiTexture[];
|
|
439
|
+
partTextureAssignments: AtlasAssignment[];
|
|
440
|
+
}
|
|
441
|
+
/**
|
|
442
|
+
* In-memory editing session over a single {@link IkiModel}. The model is held
|
|
443
|
+
* directly (no superset) and mutated in place by invertible {@link EditCommand}s
|
|
444
|
+
* pushed through {@link execute}; undo/redo invert/re-apply them.
|
|
445
|
+
*
|
|
446
|
+
* The constructor `structuredClone`s the input so the caller's model is never
|
|
447
|
+
* mutated. Parts are addressed by stable `id`, never by array index.
|
|
448
|
+
*/
|
|
449
|
+
declare class EditorDocument {
|
|
450
|
+
private readonly model;
|
|
451
|
+
private readonly undoStack;
|
|
452
|
+
private readonly redoStack;
|
|
453
|
+
/** Editor-only session state, never serialized; keyed by stable part id. The
|
|
454
|
+
* unmodified BASE local uvs of every mesh part, captured once at construction
|
|
455
|
+
* so atlas remaps always derive from the original (idempotent). */
|
|
456
|
+
private readonly baseMeshUvs;
|
|
457
|
+
constructor(model: IkiModel);
|
|
458
|
+
/** Live reference to the working model — for READ access. Mutate it only
|
|
459
|
+
* through {@link execute}/{@link undo}/{@link redo}. */
|
|
460
|
+
getModel(): IkiModel;
|
|
461
|
+
/** Resolve a part by stable id. Throws a plain `Error` (NOT an
|
|
462
|
+
* `IkiFormatError`) with a path-qualified message if the id is unknown. */
|
|
463
|
+
findPart(id: string): IkiPart;
|
|
464
|
+
/** Resolve a warp deformer by stable id. Throws a path-qualified plain
|
|
465
|
+
* `Error` if no deformer matches the id or the match is not a warp deformer.
|
|
466
|
+
* READ/mutate-through accessor, consistent with {@link findPart}. */
|
|
467
|
+
findWarpDeformer(id: string): IkiWarpDeformer;
|
|
468
|
+
/** Resolve a matrix deformer by stable id. Throws a path-qualified plain
|
|
469
|
+
* `Error` if no deformer matches the id or the match is a warp deformer
|
|
470
|
+
* (`kind === "warp"`). A `kind` of `"matrix"` or `undefined` is a matrix
|
|
471
|
+
* deformer. READ/mutate-through accessor, consistent with {@link findPart}. */
|
|
472
|
+
findMatrixDeformer(id: string): IkiMatrixDeformer;
|
|
473
|
+
/** Resolve any deformer (matrix or warp) by stable id. Throws a
|
|
474
|
+
* path-qualified plain `Error` if no deformer matches the id.
|
|
475
|
+
* READ/mutate-through accessor, consistent with {@link findPart}. */
|
|
476
|
+
findDeformer(id: string): IkiDeformer;
|
|
477
|
+
/** Resolve a physics rig by stable id. Throws a path-qualified plain `Error`
|
|
478
|
+
* if no rig matches the id. The `physics` array is optional, so guard it.
|
|
479
|
+
* READ/mutate-through accessor, consistent with {@link findDeformer}. */
|
|
480
|
+
findPhysicsRig(id: string): IkiPhysics;
|
|
481
|
+
/**
|
|
482
|
+
* Record the base mesh UVs for a part inserted AFTER construction (e.g. by
|
|
483
|
+
* {@link AddPart}). Returns the PRIOR entry for that id (or `undefined` if
|
|
484
|
+
* none existed) so the caller can restore it on undo. No-op for meshless
|
|
485
|
+
* parts (returns `undefined`). Must be called by any command that pushes a
|
|
486
|
+
* mesh part into the model so the part joins the constructor-captured base-UV
|
|
487
|
+
* side state required by {@link applyAtlas}.
|
|
488
|
+
*
|
|
489
|
+
* The returned prior value is what {@link restoreBaseMeshUvs} expects on
|
|
490
|
+
* undo — pass it verbatim. Hazard guarded: DeletePart(X) → AddPart(X′) →
|
|
491
|
+
* undo(add) → undo(delete); without restore, X's constructor-captured entry
|
|
492
|
+
* would be permanently gone after undo of the add, causing applyAtlas to
|
|
493
|
+
* fail when X is restored.
|
|
494
|
+
*/
|
|
495
|
+
captureBaseMeshUvs(partId: string): number[] | undefined;
|
|
496
|
+
/**
|
|
497
|
+
* Restore the base-UV side state to exactly what it was before a
|
|
498
|
+
* {@link captureBaseMeshUvs} call. Called from {@link AddPart.invert}:
|
|
499
|
+
* pass the value returned by captureBaseMeshUvs on first apply.
|
|
500
|
+
* - `prev` is `number[]` → sets the entry (restores a prior mesh's base).
|
|
501
|
+
* - `prev` is `undefined` → deletes the entry (no entry existed before the
|
|
502
|
+
* add, so a later different-mesh part reusing the id must not inherit this
|
|
503
|
+
* one's base).
|
|
504
|
+
* DeletePart does NOT call this — the entry persists across delete/undo so
|
|
505
|
+
* the restored part still has its base available for applyAtlas.
|
|
506
|
+
*/
|
|
507
|
+
restoreBaseMeshUvs(partId: string, prev: number[] | undefined): void;
|
|
508
|
+
/** The construction-captured base UVs for a mesh part. Throws a path-qualified
|
|
509
|
+
* plain `Error` if absent. SINGLE accessor for both apply branches — never
|
|
510
|
+
* read `baseMeshUvs` with a bare `!` elsewhere. */
|
|
511
|
+
private requireBaseUvs;
|
|
512
|
+
/**
|
|
513
|
+
* Replace the atlas table and rewrite every part's texture reference in a
|
|
514
|
+
* single atomic step. For every part in `partTextureAssignments` set
|
|
515
|
+
* `texture = { index: 0, uv }`; CLEAR `texture` (delete the key) on every
|
|
516
|
+
* other part.
|
|
517
|
+
*
|
|
518
|
+
* Mesh parts are textured as a MATCHED PAIR: an assigned mesh part also has
|
|
519
|
+
* its per-vertex `mesh.uvs` remapped (from the construction-captured base)
|
|
520
|
+
* into the same `uv` rect; an unassigned mesh part has its `mesh.uvs` restored
|
|
521
|
+
* to that base. Quad parts carry `texture.uv` only and are untouched here.
|
|
522
|
+
*
|
|
523
|
+
* Deliberately NON-undoable: it does NOT push to or clear the undo/redo
|
|
524
|
+
* stacks (texture/atlas state is not undoable in 5b — the unified
|
|
525
|
+
* editor-state superset is deferred to 5d). `canUndo()`/`canRedo()` are
|
|
526
|
+
* unchanged after a call.
|
|
527
|
+
*
|
|
528
|
+
* Validate-all-then-apply: structural input validation, per-partId
|
|
529
|
+
* resolution, and a base-UV preflight over every mesh part all run BEFORE any
|
|
530
|
+
* mutation, so a bad input (wrong shape, duplicate partId, unknown partId, or
|
|
531
|
+
* a mesh part with no captured base) throws a plain `Error` and leaves the
|
|
532
|
+
* model exactly as it was — never a partial application.
|
|
533
|
+
*/
|
|
534
|
+
applyAtlas(input: ApplyAtlasInput): void;
|
|
535
|
+
/**
|
|
536
|
+
* Clear a model-committed texture reference from a single part. Deliberately
|
|
537
|
+
* NON-undoable, matching {@link applyAtlas} — texture/atlas state is not in
|
|
538
|
+
* the undo model (unified editor-state is deferred). Does NOT push to or
|
|
539
|
+
* clear the undo/redo stacks. Does NOT touch `mesh.uvs` — atlas-space UVs on
|
|
540
|
+
* an untextured mesh are inert for color rendering; a later atlas import
|
|
541
|
+
* remaps from the base UVs anyway.
|
|
542
|
+
*
|
|
543
|
+
* This is the single consistent texture/atlas undo boundary: by the time a
|
|
544
|
+
* part is deletable (no texture), its DeletePart snapshot carries no texture
|
|
545
|
+
* reference, so no stale index can resurface on undo after a later atlas
|
|
546
|
+
* repack.
|
|
547
|
+
*
|
|
548
|
+
* Throws a path-qualified plain `Error` (NOT `IkiFormatError`) if the part
|
|
549
|
+
* id is unknown — same contract as {@link findPart}.
|
|
550
|
+
*/
|
|
551
|
+
clearPartTextureRef(partId: string): void;
|
|
552
|
+
/**
|
|
553
|
+
* Validate the structural shape of an {@link ApplyAtlasInput} without
|
|
554
|
+
* touching the model, returning a known-good `{ texture?, assignmentsByPart }`
|
|
555
|
+
* for {@link applyAtlas} to resolve and apply.
|
|
556
|
+
*/
|
|
557
|
+
private normalizeAtlasInput;
|
|
558
|
+
/**
|
|
559
|
+
* Overwrite a part's whole transform with a fresh copy of `transform`.
|
|
560
|
+
* Replacing the whole object (rather than individual channels) preserves any
|
|
561
|
+
* optional keys already absent from the incoming value.
|
|
562
|
+
*
|
|
563
|
+
* Deliberately NON-undoable — used ONLY for an editor app's transient capture
|
|
564
|
+
* pose. Does NOT push to or clear undoStack/redoStack (sibling to
|
|
565
|
+
* {@link applyAtlas}'s non-undoable boundary). The caller is responsible for
|
|
566
|
+
* restoring the exact prior snapshot when the capture pose ends.
|
|
567
|
+
*
|
|
568
|
+
* `IkiTransform` is a flat number map, so a shallow spread is a sufficient
|
|
569
|
+
* deep copy — no aliasing of the caller's object remains.
|
|
570
|
+
*/
|
|
571
|
+
setPartTransformEphemeral(partId: string, transform: IkiTransform): void;
|
|
572
|
+
/**
|
|
573
|
+
* Overwrite a matrix deformer's optional transform with a fresh copy, or
|
|
574
|
+
* delete it when `transform` is `undefined`.
|
|
575
|
+
* `undefined` deletes the key, restoring the deformer to the same state as
|
|
576
|
+
* one that never had a transform (absent-vs-present matters for downstream
|
|
577
|
+
* renderers).
|
|
578
|
+
*
|
|
579
|
+
* Deliberately NON-undoable — used ONLY for an editor app's transient capture
|
|
580
|
+
* pose. Does NOT push to or clear undoStack/redoStack (sibling to
|
|
581
|
+
* {@link applyAtlas}'s non-undoable boundary). The caller is responsible for
|
|
582
|
+
* restoring the exact prior snapshot when the capture pose ends.
|
|
583
|
+
*
|
|
584
|
+
* `IkiDeformerTransform` is a flat number map, so a shallow spread is a
|
|
585
|
+
* sufficient deep copy — no aliasing of the caller's object remains.
|
|
586
|
+
*/
|
|
587
|
+
setDeformerTransformEphemeral(deformerId: string, transform: IkiDeformerTransform | undefined): void;
|
|
588
|
+
/**
|
|
589
|
+
* Overwrite a part's whole bindings array with a fresh deep copy, or delete
|
|
590
|
+
* the key when `bindings` is empty.
|
|
591
|
+
*
|
|
592
|
+
* Deliberately NON-undoable — used ONLY for an editor app's transient capture
|
|
593
|
+
* neutralization (zeroing the row being recaptured so the preview reflects
|
|
594
|
+
* base-only during posing). Does NOT push to or clear undoStack/redoStack.
|
|
595
|
+
* The caller is responsible for restoring the original bindings when the
|
|
596
|
+
* capture session ends.
|
|
597
|
+
*/
|
|
598
|
+
setPartBindingsEphemeral(partId: string, bindings: IkiBinding[]): void;
|
|
599
|
+
/**
|
|
600
|
+
* Overwrite a matrix deformer's whole bindings array with a fresh deep copy,
|
|
601
|
+
* or delete the key when `bindings` is empty.
|
|
602
|
+
*
|
|
603
|
+
* Deliberately NON-undoable — used ONLY for an editor app's transient capture
|
|
604
|
+
* neutralization (zeroing the row being recaptured so the preview reflects
|
|
605
|
+
* base-only during posing). Does NOT push to or clear undoStack/redoStack.
|
|
606
|
+
* The caller is responsible for restoring the original bindings when the
|
|
607
|
+
* capture session ends.
|
|
608
|
+
*/
|
|
609
|
+
setDeformerBindingsEphemeral(deformerId: string, bindings: IkiDeformerBinding[]): void;
|
|
610
|
+
/** Apply a command and record it as one undo step. Clears the redo stack. */
|
|
611
|
+
execute(cmd: EditCommand): void;
|
|
612
|
+
/** Invert the most recent command and move it onto the redo stack. */
|
|
613
|
+
undo(): void;
|
|
614
|
+
/** Re-apply the most recently undone command and move it back onto undo. */
|
|
615
|
+
redo(): void;
|
|
616
|
+
canUndo(): boolean;
|
|
617
|
+
canRedo(): boolean;
|
|
618
|
+
/**
|
|
619
|
+
* Validate and export the current working model by running it through
|
|
620
|
+
* {@link parseIkiModel}. Uses `structuredClone` so the validator's
|
|
621
|
+
* normalized output cannot alias the working model. Propagates
|
|
622
|
+
* `IkiFormatError` unchanged on failure — callers surface `.message`.
|
|
623
|
+
*/
|
|
624
|
+
toIkiModel(): IkiModel;
|
|
625
|
+
/**
|
|
626
|
+
* Pretty-print the validated model as a `.iki` JSON string. Always
|
|
627
|
+
* validates first — invalid documents never reach a file.
|
|
628
|
+
*/
|
|
629
|
+
serialize(): string;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
/**
|
|
633
|
+
* Alpha bounding-box scan shared by every auto-rig ingestion path.
|
|
634
|
+
*
|
|
635
|
+
* The scan itself is environment-free — it only needs indexable RGBA bytes — so
|
|
636
|
+
* a browser editor (canvas `ImageData`) and the Node MCP server (a `sharp`
|
|
637
|
+
* raw buffer) run the SAME code instead of two copies that have to be kept
|
|
638
|
+
* byte-identical by hand. Only decoding differs between them.
|
|
639
|
+
*/
|
|
640
|
+
/** Alpha at or above this counts as coverage; below it is treated as empty. */
|
|
641
|
+
declare const ALPHA_BBOX_THRESHOLD = 8;
|
|
642
|
+
/** Top-left origin, +y down — image space, not model space. */
|
|
643
|
+
interface AlphaBbox {
|
|
644
|
+
x: number;
|
|
645
|
+
y: number;
|
|
646
|
+
w: number;
|
|
647
|
+
h: number;
|
|
648
|
+
}
|
|
649
|
+
/**
|
|
650
|
+
* Tight bounding box of every pixel with alpha >= {@link ALPHA_BBOX_THRESHOLD},
|
|
651
|
+
* expanded 1px on each side (clamped to the image) for AA / extrude margin.
|
|
652
|
+
*
|
|
653
|
+
* Returns `null` when no pixel passes the threshold, leaving the "empty layer"
|
|
654
|
+
* error to the caller: each ingestion path reports it with its own error type
|
|
655
|
+
* and message.
|
|
656
|
+
*/
|
|
657
|
+
declare function detectAlphaBbox(rgba: ArrayLike<number>, width: number, height: number): AlphaBbox | null;
|
|
658
|
+
|
|
659
|
+
/**
|
|
660
|
+
* Pure, binding-value logic for computing an endpoint (rest-to-posed delta or
|
|
661
|
+
* ratio) when capturing a transform channel binding. No DOM, no @ikijs/engine —
|
|
662
|
+
* the single home of the additive-vs-multiplicative rule, reused by both the
|
|
663
|
+
* part and deformer capture paths.
|
|
664
|
+
*
|
|
665
|
+
* Additive channels (translateX, translateY, rotate, scaleX, scaleY) return the
|
|
666
|
+
* delta: `posedValue - restValue`. The binding will multiply that delta across
|
|
667
|
+
* the driven range.
|
|
668
|
+
*
|
|
669
|
+
* Opacity is multiplicative: returns the ratio `posedValue / restValue`. The
|
|
670
|
+
* binding will multiply by that ratio across the driven range. When `restValue`
|
|
671
|
+
* is 0 (a degenerate case: base opacity cannot be represented multiplicatively
|
|
672
|
+
* since 0 * x ≡ 0), this returns 0 as a documented fallback — it does NOT
|
|
673
|
+
* recover `posedValue` and is NOT unit-tested as an identity. The store layer
|
|
674
|
+
* additionally skips an opacity capture when rest opacity is 0, surfacing an
|
|
675
|
+
* editError.
|
|
676
|
+
*
|
|
677
|
+
* Deformer channels never include opacity (they use `IkiMatrixChannel`), so the
|
|
678
|
+
* opacity branch is reached only for parts.
|
|
679
|
+
*
|
|
680
|
+
* Finiteness of the captured value is NOT validated here; the store's
|
|
681
|
+
* `captureEndpoint` finite-value guard is the appropriate layer for that check.
|
|
682
|
+
*/
|
|
683
|
+
declare function captureBindingEndpoint(channel: IkiTransformChannel, restValue: number, posedValue: number): number;
|
|
684
|
+
|
|
685
|
+
/**
|
|
686
|
+
* Pure, grid-size-agnostic keyform/offset math for authoring a warp-deformer
|
|
687
|
+
* grid by dragging. No DOM, no `@ikijs/engine` — the load-bearing testable core.
|
|
688
|
+
* Constraints derive only from the input array lengths, never the sample grid.
|
|
689
|
+
*/
|
|
690
|
+
/**
|
|
691
|
+
* Interpolate the grid offsets at `value`, mirroring the engine's
|
|
692
|
+
* `accumulateKeyformOffsets` clamp+lerp semantics: clamp to the first/last
|
|
693
|
+
* keyform (NO extrapolation) and linearly interpolate the bracketing pair.
|
|
694
|
+
* Returns a NEW array of length `keyforms[0].offsets.length`. Throws on empty.
|
|
695
|
+
*/
|
|
696
|
+
declare function interpolateGridOffsets(keyforms: {
|
|
697
|
+
value: number;
|
|
698
|
+
offsets: number[];
|
|
699
|
+
}[], value: number): number[];
|
|
700
|
+
/**
|
|
701
|
+
* Per-control-point delta of the dragged grid from the rest grid: for each
|
|
702
|
+
* point `i`, `(draggedX_i - restX_i, draggedY_i - restY_i)`. The DOM layer
|
|
703
|
+
* assembles the full `restFrameDraggedPoints` (including untouched points), so
|
|
704
|
+
* this is a straight subtract — no prior-offset blending.
|
|
705
|
+
*
|
|
706
|
+
* Both arrays must have the SAME length and that length must be even (x,y
|
|
707
|
+
* pairs). Returns a NEW array of length `restPoints.length`.
|
|
708
|
+
*/
|
|
709
|
+
declare function computeGridOffsets(restPoints: number[], restFrameDraggedPoints: number[]): number[];
|
|
710
|
+
/**
|
|
711
|
+
* Insert or replace the keyform at `value`, returning a NEW array. If a keyform
|
|
712
|
+
* already exists with an exact-match `value`, REPLACE its offsets (with a copy);
|
|
713
|
+
* otherwise INSERT `{ value, offsets: [...offsets] }` at the position that keeps
|
|
714
|
+
* the array strictly ascending by value. The input array and its keyform objects
|
|
715
|
+
* are never mutated, and `offsets` is copied so the result never aliases the
|
|
716
|
+
* caller's array.
|
|
717
|
+
*
|
|
718
|
+
* Deliberately RANGE-FREE — a generic, reusable array op. Value-range
|
|
719
|
+
* enforcement is the command's job, not this helper's.
|
|
720
|
+
*/
|
|
721
|
+
declare function upsertGridKeyform(keyforms: IkiGridKeyform[], value: number, offsets: number[]): IkiGridKeyform[];
|
|
722
|
+
|
|
723
|
+
declare const ATLAS_PADDING = 2;
|
|
724
|
+
declare const UV_INSET_PX = 0.5;
|
|
725
|
+
/** Intrinsic pixel size of one decoded image; id is an editor-only stable key. */
|
|
726
|
+
interface AtlasSource {
|
|
727
|
+
id: string;
|
|
728
|
+
width: number;
|
|
729
|
+
height: number;
|
|
730
|
+
}
|
|
731
|
+
/** Sub-image pixel rect within the page, top-left origin, EXCLUDING the gutter. */
|
|
732
|
+
interface AtlasPlacement {
|
|
733
|
+
id: string;
|
|
734
|
+
x: number;
|
|
735
|
+
y: number;
|
|
736
|
+
width: number;
|
|
737
|
+
height: number;
|
|
738
|
+
}
|
|
739
|
+
interface AtlasLayout {
|
|
740
|
+
pageWidth: number;
|
|
741
|
+
pageHeight: number;
|
|
742
|
+
placements: AtlasPlacement[];
|
|
743
|
+
padding: number;
|
|
744
|
+
}
|
|
745
|
+
/**
|
|
746
|
+
* Deterministic shelf/row packer. Sources are sorted by id for stability so
|
|
747
|
+
* identical inputs always produce an identical layout.
|
|
748
|
+
*
|
|
749
|
+
* Padding is one-sided: each placement reserves `padding` px on its RIGHT and
|
|
750
|
+
* BOTTOM only. Page left/top edges need no gutter.
|
|
751
|
+
*
|
|
752
|
+
* pageWidth/pageHeight = tight bound (max x+width+padding, max y+height+padding).
|
|
753
|
+
* Empty sources → { pageWidth: 0, pageHeight: 0, placements: [], padding }.
|
|
754
|
+
* Throws a plain Error naming the offending source id on a non-finite or <= 0 dimension.
|
|
755
|
+
*/
|
|
756
|
+
declare function packAtlas(sources: AtlasSource[], padding?: number): AtlasLayout;
|
|
757
|
+
/**
|
|
758
|
+
* Convert a pixel placement into a UV rect, inset by `insetPx` on all four
|
|
759
|
+
* edges and clamped to [0, 1] so the validator's bounds check always passes.
|
|
760
|
+
*/
|
|
761
|
+
declare function uvRectFor(placement: AtlasPlacement, page: {
|
|
762
|
+
width: number;
|
|
763
|
+
height: number;
|
|
764
|
+
}, insetPx?: number): IkiUvRect;
|
|
765
|
+
|
|
766
|
+
/**
|
|
767
|
+
* Validate that reparenting `deformerId` under `newParentId` keeps the deformer
|
|
768
|
+
* hierarchy valid. Pass `newParentId === undefined` to move to root (always legal).
|
|
769
|
+
* Checks: existence, self-reference, undeclared parent, kind constraint (warp
|
|
770
|
+
* deformers cannot be parents), and cycle detection via the proposed edge.
|
|
771
|
+
*/
|
|
772
|
+
declare function validateDeformerReparent(deformers: IkiDeformer[], deformerId: string, newParentId: string | undefined): void;
|
|
773
|
+
/**
|
|
774
|
+
* Validate that deleting `deformerId` is safe. Throws when the deformer does
|
|
775
|
+
* not exist, when another deformer is parented to it (must be reparented or
|
|
776
|
+
* detached first), when a part is attached to it (must be detached first), or
|
|
777
|
+
* when a physics chain anchors to it (must be re-anchored or deleted first).
|
|
778
|
+
*
|
|
779
|
+
* Every id that can reference a deformer must be covered here: the format
|
|
780
|
+
* validator rejects a dangling reference at export, so a delete this function
|
|
781
|
+
* lets through does not fail now — it strands the document in a state
|
|
782
|
+
* `toIkiModel()` refuses.
|
|
783
|
+
*
|
|
784
|
+
* Note: there is no validatePartDelete here, but NOT because parts are
|
|
785
|
+
* unreferenced — `clip.masks` names parts by id. That invariant is enforced
|
|
786
|
+
* closer to the edits that could break it: `DeletePart` refuses to remove a
|
|
787
|
+
* part still used as a mask, and `SetPartMesh` refuses to strip the mesh off
|
|
788
|
+
* one (masks must be mesh parts). No command creates or edits `clip`.
|
|
789
|
+
*/
|
|
790
|
+
declare function validateDeformerDelete(deformers: IkiDeformer[], parts: IkiPart[], physicsChains: IkiPhysicsChain[], deformerId: string): void;
|
|
791
|
+
/**
|
|
792
|
+
* Validate that attaching part `partId` to deformer `newDeformerId` is valid.
|
|
793
|
+
* Pass `newDeformerId === undefined` to detach (always legal).
|
|
794
|
+
* Checks: part existence, undeclared deformer, and mesh-required-for-warp.
|
|
795
|
+
*/
|
|
796
|
+
declare function validatePartAttach(deformers: IkiDeformer[], partId: string, parts: IkiPart[], newDeformerId: string | undefined): void;
|
|
797
|
+
|
|
798
|
+
/**
|
|
799
|
+
* Create a minimal valid part with a collision-free id and a paint order one
|
|
800
|
+
* above the current top-most part so it is immediately visible in the viewport.
|
|
801
|
+
* Uses a distinct non-white blue tint so it is distinguishable from the canvas
|
|
802
|
+
* background without requiring a texture.
|
|
803
|
+
*
|
|
804
|
+
* No optional keys (`texture`, `mesh`, `deformer`, `bindings`, `warps`) are set
|
|
805
|
+
* — the format treats all of them as absent by default.
|
|
806
|
+
*/
|
|
807
|
+
declare function createDefaultPart(model: IkiModel): IkiPart;
|
|
808
|
+
/**
|
|
809
|
+
* Create a minimal valid matrix deformer rooted at the canvas origin.
|
|
810
|
+
* `kind` is omitted because the format treats its absence as "matrix" (the
|
|
811
|
+
* default), keeping the serialised model compact.
|
|
812
|
+
*/
|
|
813
|
+
declare function createDefaultMatrixDeformer(model: IkiModel): IkiMatrixDeformer;
|
|
814
|
+
/**
|
|
815
|
+
* Create a regular grid mesh in part LOCAL space (±0.5 unit frame).
|
|
816
|
+
*
|
|
817
|
+
* Vertices span x ∈ [-0.5, 0.5] and y ∈ [-0.5, 0.5] (+y up, engine convention).
|
|
818
|
+
* Row 0 is the TOP of the grid (y = +0.5); row index increases downward.
|
|
819
|
+
* UVs are unit-square base coordinates: u = col/cols (0..1 left→right),
|
|
820
|
+
* v = row/rows (0..1 top→bottom). The top row maps to v=0 because v and y
|
|
821
|
+
* run in opposite directions — keeps textures upright without a post-flip.
|
|
822
|
+
* The UV-to-texture remap (atlas rect) is applied later in SetPartMesh, not here.
|
|
823
|
+
*
|
|
824
|
+
* Index winding per cell: [BL, BR, TL] then [TL, BR, TR], matching the engine's
|
|
825
|
+
* implicit-quad convention (see examples/editor/src/mesh-generator.ts).
|
|
826
|
+
*
|
|
827
|
+
* Bounds are validated BEFORE any array allocation because this factory runs
|
|
828
|
+
* before SetPartMesh's parseIkiModel — an unbounded count would freeze the
|
|
829
|
+
* editor before the format-level 65536 limit is ever reached.
|
|
830
|
+
*/
|
|
831
|
+
declare function createGridMesh(cols: number, rows: number): IkiMesh;
|
|
832
|
+
/**
|
|
833
|
+
* Create a 4×4-cell warp deformer whose rest grid spans a quarter of the
|
|
834
|
+
* canvas in each direction (`±canvas.width/4` × `±canvas.height/4`). For the
|
|
835
|
+
* 1000-unit sample canvas this gives x,y ∈ [−250, 250], which is large enough
|
|
836
|
+
* to cover a typical face part without spilling to the edge.
|
|
837
|
+
*
|
|
838
|
+
* `warps` is omitted — the format treats its absence as "rest grid only", so
|
|
839
|
+
* the deformer is immediately usable without authored keyforms.
|
|
840
|
+
* `parent` is omitted — the deformer is placed at root; the caller may
|
|
841
|
+
* reparent it via `SetDeformerParent` after creation.
|
|
842
|
+
*/
|
|
843
|
+
declare function createDefaultWarpDeformer(model: IkiModel): IkiWarpDeformer;
|
|
844
|
+
|
|
845
|
+
/**
|
|
846
|
+
* Role table, role parsing, bbox→transform math, and model assembly for the
|
|
847
|
+
* AI auto-rig generator. All pure functions — no DOM, no canvas, no
|
|
848
|
+
* crypto.randomUUID.
|
|
849
|
+
*
|
|
850
|
+
* L/R = CHARACTER frame: *_L is the character's left = screen right.
|
|
851
|
+
*/
|
|
852
|
+
|
|
853
|
+
/**
|
|
854
|
+
* Input contract from the host app to this package's auto-rig functions.
|
|
855
|
+
* Passed in after the host has decoded PNGs, computed alpha bboxes, and
|
|
856
|
+
* mapped filenames to canonical roles.
|
|
857
|
+
*/
|
|
858
|
+
interface LayerInput {
|
|
859
|
+
/** Canonical role, e.g. "eye_L". */
|
|
860
|
+
role: string;
|
|
861
|
+
/** Original file name — used in error messages and as a stable id. */
|
|
862
|
+
fileName: string;
|
|
863
|
+
/** Shared canvas width (all layers have the same canvas size). */
|
|
864
|
+
canvasW: number;
|
|
865
|
+
/** Shared canvas height. */
|
|
866
|
+
canvasH: number;
|
|
867
|
+
/** Alpha-tight bounding box, top-left origin, +y down (image coords). */
|
|
868
|
+
bbox: {
|
|
869
|
+
x: number;
|
|
870
|
+
y: number;
|
|
871
|
+
w: number;
|
|
872
|
+
h: number;
|
|
873
|
+
};
|
|
874
|
+
/** Cropped image width = bbox.w. */
|
|
875
|
+
cropW: number;
|
|
876
|
+
/** Cropped image height = bbox.h. */
|
|
877
|
+
cropH: number;
|
|
878
|
+
}
|
|
879
|
+
/**
|
|
880
|
+
* Map an array of raw filenames to canonical `{ role, fileName }` pairs.
|
|
881
|
+
*
|
|
882
|
+
* Steps:
|
|
883
|
+
* 1. Normalize each filename → role (normalizeRole).
|
|
884
|
+
* 2. Eagerly check each role against ROLE_TABLE — unknown roles throw early
|
|
885
|
+
* with the offending fileName included in the message.
|
|
886
|
+
* 3. Call assertRoleSet to check duplicates + required roles.
|
|
887
|
+
*
|
|
888
|
+
* Throws a path-qualified Error on any contract violation.
|
|
889
|
+
*/
|
|
890
|
+
declare function parseLayerRoles(fileNames: string[]): {
|
|
891
|
+
role: string;
|
|
892
|
+
fileName: string;
|
|
893
|
+
}[];
|
|
894
|
+
/**
|
|
895
|
+
* Auto-rig: given decoded layer inputs and the shared canvas size, produce a
|
|
896
|
+
* valid IkiModel ready for parseIkiModel.
|
|
897
|
+
*
|
|
898
|
+
* - Validate all inputs before deriving anything.
|
|
899
|
+
* - Place parts at source-derived positions (bboxToTransform, unshifted).
|
|
900
|
+
* - Emit the standard parameters (same ids/ranges as sample-model.ts), plus a
|
|
901
|
+
* conditional HairSwayX descriptor + hair-sway physics rig when a hair_front
|
|
902
|
+
* layer is present.
|
|
903
|
+
* - Build headDeformer (matrix, neck pivot, AngleX+Breath bindings) and faceWarp
|
|
904
|
+
* (warp, 4×4, baked cylinder warp center-relative on faceCenterX).
|
|
905
|
+
* - Mesh parts (spec.mesh===true) → width:1, height:1, pixel grid mesh 4×4 + role bindings.
|
|
906
|
+
* - Static parts (spec.mesh===false) → width:cropW, height:cropH, no mesh.
|
|
907
|
+
* - Part ids equal the role string (deterministic, no crypto.randomUUID).
|
|
908
|
+
* - Return parseIkiModel(structuredClone(model)) — every caller gets a
|
|
909
|
+
* validated model; bad assembly fails loudly.
|
|
910
|
+
*/
|
|
911
|
+
declare function generateIkiFromLayerSet(layers: LayerInput[], canvas: {
|
|
912
|
+
width: number;
|
|
913
|
+
height: number;
|
|
914
|
+
}): IkiModel;
|
|
915
|
+
|
|
916
|
+
export { ALPHA_BBOX_THRESHOLD, ATLAS_PADDING, AddDeformer, AddPart, AddPhysicsRig, type AlphaBbox, type ApplyAtlasInput, type AtlasAssignment, type AtlasLayout, type AtlasPlacement, type AtlasSource, CaptureGridKeyform, type DeformerTransformChannel, DeleteDeformer, DeletePart, DeletePhysicsRig, type EditCommand, type EditTransformChannel, EditorDocument, type LayerInput, SetDeformerBindings, SetDeformerParent, SetDeformerPivot, SetDeformerPivotX, SetDeformerPivotY, SetDeformerTransform, SetPartBindings, SetPartColor, SetPartDeformer, SetPartHeight, SetPartMesh, SetPartOrder, SetPartTransform, SetPartWidth, SetPhysicsRig, UV_INSET_PX, captureBindingEndpoint, computeGridOffsets, createDefaultMatrixDeformer, createDefaultPart, createDefaultWarpDeformer, createGridMesh, detectAlphaBbox, generateIkiFromLayerSet, interpolateGridOffsets, packAtlas, parseLayerRoles, upsertGridKeyform, uvRectFor, validateDeformerDelete, validateDeformerReparent, validatePartAttach };
|