@ikijs/editor 0.30.1 → 0.31.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -26,7 +26,9 @@ __export(index_exports, {
26
26
  AddDeformer: () => AddDeformer,
27
27
  AddPart: () => AddPart,
28
28
  AddPhysicsRig: () => AddPhysicsRig,
29
+ BakeError: () => BakeError,
29
30
  CaptureGridKeyform: () => CaptureGridKeyform,
31
+ CustomPartError: () => CustomPartError,
30
32
  DEFAULT_TURN_TARGETS: () => DEFAULT_TURN_TARGETS,
31
33
  DeleteDeformer: () => DeleteDeformer,
32
34
  DeletePart: () => DeletePart,
@@ -82,6 +84,7 @@ __export(index_exports, {
82
84
  packAtlas: () => packAtlas,
83
85
  parseLayerRoles: () => parseLayerRoles,
84
86
  partIdsOfRole: () => partIdsOfRole,
87
+ rigidResidual: () => rigidResidual,
85
88
  styleRemovedMessage: () => styleRemovedMessage,
86
89
  upsertGridKeyform: () => upsertGridKeyform,
87
90
  uvRectFor: () => uvRectFor,
@@ -2203,6 +2206,18 @@ var PatchError = class extends Error {
2203
2206
  this.name = "PatchError";
2204
2207
  }
2205
2208
  };
2209
+ var CustomPartError = class extends Error {
2210
+ constructor(message) {
2211
+ super(message);
2212
+ this.name = "CustomPartError";
2213
+ }
2214
+ };
2215
+ var BakeError = class extends Error {
2216
+ constructor(message) {
2217
+ super(message);
2218
+ this.name = "BakeError";
2219
+ }
2220
+ };
2206
2221
 
2207
2222
  // src/auto-rig/layout.ts
2208
2223
  function boxOfLayer(layer) {
@@ -2607,10 +2622,10 @@ function checkSidesHaveFront(roles) {
2607
2622
  function checkMouth(roles) {
2608
2623
  const has = new Set(roles);
2609
2624
  const present = LIP_ROLES.filter((r) => has.has(r));
2610
- const inside = LIP_INSIDE_ROLES.filter((r) => has.has(r));
2611
- if (inside.length > 0 && present.length === 0) {
2625
+ const inside2 = LIP_INSIDE_ROLES.filter((r) => has.has(r));
2626
+ if (inside2.length > 0 && present.length === 0) {
2612
2627
  throw new Error(
2613
- `auto-rig: ${inside[0]} needs the lip set ${LIP_ROLES.join(", ")} (it is cut from the interior and clipped to it)`
2628
+ `auto-rig: ${inside2[0]} needs the lip set ${LIP_ROLES.join(", ")} (it is cut from the interior and clipped to it)`
2614
2629
  );
2615
2630
  }
2616
2631
  if (present.length === 0) {
@@ -2830,10 +2845,10 @@ function mouthFold(role, opening) {
2830
2845
  const stackThin = (c, y) => y > c.Tu ? c.Tu + UPPER_THIN * (y - c.Tu) : y;
2831
2846
  return (col, y, v) => {
2832
2847
  const k = 1 - v;
2833
- const inside = col >= x0 && col <= x1;
2848
+ const inside2 = col >= x0 && col <= x1;
2834
2849
  switch (role) {
2835
2850
  case "lip_lower": {
2836
- if (!inside) return [0, 0];
2851
+ if (!inside2) return [0, 0];
2837
2852
  const c = opening.at(col);
2838
2853
  return [0, (c.seam + c.overlap - c.Bl) * k];
2839
2854
  }
@@ -2857,9 +2872,9 @@ function mouthFold(role, opening) {
2857
2872
  line = opening.line(c += col < x0 ? 1 : -1);
2858
2873
  const thin = line.bottom + UPPER_THIN * (y - line.bottom);
2859
2874
  const mid = line.bottom + UPPER_THIN * (line.top - line.bottom) / 2;
2860
- const travel = inside ? opening.at(col).seam - line.bottom : 0;
2875
+ const travel = inside2 ? opening.at(col).seam - line.bottom : 0;
2861
2876
  const shut = mid + line.squash * (thin - mid) + travel;
2862
- const pull = inside ? 0 : (1 - FLICK_PULL) * ((col < x0 ? x0 : x1 + 1) - col);
2877
+ const pull = inside2 ? 0 : (1 - FLICK_PULL) * ((col < x0 ? x0 : x1 + 1) - col);
2863
2878
  return [pull * k, thin - y + (shut - thin) * k];
2864
2879
  }
2865
2880
  }
@@ -4192,14 +4207,14 @@ function cuesAt(ctx, land2, ax) {
4192
4207
  const l0 = land2(L.role, L.lo, L.y, ax);
4193
4208
  const l1 = land2(L.role, L.hi, L.y, ax);
4194
4209
  const r0 = land2(R.role, R.lo, R.y, ax);
4195
- const r1 = land2(R.role, R.hi, R.y, ax);
4210
+ const r12 = land2(R.role, R.hi, R.y, ax);
4196
4211
  const wl = l1 - l0;
4197
- const wr = r1 - r0;
4212
+ const wr = r12 - r0;
4198
4213
  const wl0 = L.hi - L.lo;
4199
4214
  const wr0 = R.hi - R.lo;
4200
4215
  const ratio = ax < 0 ? wl / wr / (wl0 / wr0) : wr / wl / (wr0 / wl0);
4201
4216
  const pc0 = (L.lo + L.hi + R.lo + R.hi) / 4;
4202
- const pc = (l0 + l1 + r0 + r1) / 4;
4217
+ const pc = (l0 + l1 + r0 + r12) / 4;
4203
4218
  const y = ctx.frame.eyeY;
4204
4219
  const hl0 = Math.min(...ctx.candidates.left.map((c) => c.x));
4205
4220
  const hr0 = Math.max(...ctx.candidates.right.map((c) => c.x));
@@ -4833,7 +4848,7 @@ function blendStops(values, offsets, v) {
4833
4848
  const t = (x - values[i]) / (values[i + 1] - values[i]);
4834
4849
  return offsets[i].map((o, k) => o + (offsets[i + 1][k] - o) * t);
4835
4850
  }
4836
- function land(l, offsets, x, y) {
4851
+ function bindToRest(l, x, y) {
4837
4852
  const { cols, rows, points } = l;
4838
4853
  const stride = cols + 1;
4839
4854
  let col = cols - 1;
@@ -4854,8 +4869,17 @@ function land(l, offsets, x, y) {
4854
4869
  const x1 = points[(col + 1) * 2];
4855
4870
  const yT = points[row * stride * 2 + 1];
4856
4871
  const yB = points[(row + 1) * stride * 2 + 1];
4857
- const s = clamp((x - x0) / (x1 - x0), 0, 1);
4858
- const t = clamp((yT - y) / (yT - yB), 0, 1);
4872
+ return {
4873
+ row,
4874
+ col,
4875
+ s: clamp((x - x0) / (x1 - x0), 0, 1),
4876
+ t: clamp((yT - y) / (yT - yB), 0, 1)
4877
+ };
4878
+ }
4879
+ function land(l, offsets, x, y) {
4880
+ const { cols, points } = l;
4881
+ const stride = cols + 1;
4882
+ const { row, col, s, t } = bindToRest(l, x, y);
4859
4883
  const at = (r, c) => {
4860
4884
  const k = (r * stride + c) * 2;
4861
4885
  return [points[k] + offsets[k], points[k + 1] + offsets[k + 1]];
@@ -5195,7 +5219,7 @@ function armGeometry(layer, bodyAxisX) {
5195
5219
  b.x1 - elbow.x,
5196
5220
  b.y1 - elbow.y
5197
5221
  );
5198
- const inside = (x, y) => {
5222
+ const inside2 = (x, y) => {
5199
5223
  const k = Math.floor(y) - bbox.y;
5200
5224
  if (k < 0 || k >= bbox.h) return false;
5201
5225
  const [rs, re] = span3(k);
@@ -5204,7 +5228,7 @@ function armGeometry(layer, bodyAxisX) {
5204
5228
  const capRays = Array.from({ length: CAP_SEGMENTS + 1 }, (_, i) => {
5205
5229
  const a = Math.PI * i / CAP_SEGMENTS;
5206
5230
  let t = 1;
5207
- while (t <= capRadius + CAP_INSET && inside(
5231
+ while (t <= capRadius + CAP_INSET && inside2(
5208
5232
  elbow.x + canvasW / 2 + t * Math.cos(a),
5209
5233
  canvasH / 2 - elbow.y - t * Math.sin(a)
5210
5234
  )) {
@@ -5404,6 +5428,675 @@ function forearmPoseDeformer(role, arm) {
5404
5428
  };
5405
5429
  }
5406
5430
 
5431
+ // src/auto-rig/chain.ts
5432
+ function multiply(a, b) {
5433
+ return [
5434
+ a[0] * b[0] + a[2] * b[1],
5435
+ a[1] * b[0] + a[3] * b[1],
5436
+ a[0] * b[2] + a[2] * b[3],
5437
+ a[1] * b[2] + a[3] * b[3],
5438
+ a[0] * b[4] + a[2] * b[5] + a[4],
5439
+ a[1] * b[4] + a[3] * b[5] + a[5]
5440
+ ];
5441
+ }
5442
+ function valueOf(parameters, values, id) {
5443
+ const p = parameters.find((q) => q.id === id);
5444
+ if (p === void 0) return 0;
5445
+ const v = values[id] ?? p.default;
5446
+ return Math.min(p.max, Math.max(p.min, v));
5447
+ }
5448
+ function ownMatrix(d, parameters, values) {
5449
+ let x = d.transform?.x ?? 0;
5450
+ let y = d.transform?.y ?? 0;
5451
+ let rotation = d.transform?.rotation ?? 0;
5452
+ let sx = d.transform?.scaleX ?? 1;
5453
+ let sy = d.transform?.scaleY ?? 1;
5454
+ for (const b of d.bindings ?? []) {
5455
+ const p = parameters.find((q) => q.id === b.parameter);
5456
+ const t = p === void 0 || p.max === p.min ? 0 : (valueOf(parameters, values, b.parameter) - p.min) / (p.max - p.min);
5457
+ const v = b.from + (b.to - b.from) * t;
5458
+ if (b.channel === "translateX") x += v;
5459
+ else if (b.channel === "translateY") y += v;
5460
+ else if (b.channel === "rotate") rotation += v;
5461
+ else if (b.channel === "scaleX") sx += v;
5462
+ else if (b.channel === "scaleY") sy += v;
5463
+ }
5464
+ const r = rotation * Math.PI / 180;
5465
+ const c = Math.cos(r);
5466
+ const s = Math.sin(r);
5467
+ const trs = [c * sx, s * sx, -s * sy, c * sy, x, y];
5468
+ return multiply(multiply([1, 0, 0, 1, d.pivot.x, d.pivot.y], trs), [
5469
+ 1,
5470
+ 0,
5471
+ 0,
5472
+ 1,
5473
+ -d.pivot.x,
5474
+ -d.pivot.y
5475
+ ]);
5476
+ }
5477
+ function localGrid(w, parameters, values) {
5478
+ const points = [...w.grid.points];
5479
+ for (const warp of w.warps ?? []) {
5480
+ const offsets = blendStops(
5481
+ warp.keyforms.map((k) => k.value),
5482
+ warp.keyforms.map((k) => k.offsets),
5483
+ valueOf(parameters, values, warp.parameter)
5484
+ );
5485
+ for (let i = 0; i < points.length; i++) points[i] += offsets[i];
5486
+ }
5487
+ return points;
5488
+ }
5489
+ function riddenFrame(pivot, w, q) {
5490
+ const { cols, points: rest } = w.grid;
5491
+ const stride = cols + 1;
5492
+ const { row, col, s, t } = bindToRest(w.grid, pivot.x, pivot.y);
5493
+ const xl = rest[col * 2];
5494
+ const xr = rest[(col + 1) * 2];
5495
+ const yt = rest[row * stride * 2 + 1];
5496
+ const yb = rest[(row + 1) * stride * 2 + 1];
5497
+ const i00 = (row * stride + col) * 2;
5498
+ const i10 = (row * stride + col + 1) * 2;
5499
+ const i01 = ((row + 1) * stride + col) * 2;
5500
+ const i11 = ((row + 1) * stride + col + 1) * 2;
5501
+ const lerp = (a, b, k) => a + (b - a) * k;
5502
+ const topX = lerp(q[i00], q[i10], s);
5503
+ const topY = lerp(q[i00 + 1], q[i10 + 1], s);
5504
+ const botX = lerp(q[i01], q[i11], s);
5505
+ const botY = lerp(q[i01 + 1], q[i11 + 1], s);
5506
+ const px = lerp(topX, botX, t);
5507
+ const py = lerp(topY, botY, t);
5508
+ const width = xr - xl;
5509
+ const height = yt - yb;
5510
+ const exX = lerp(q[i10] - q[i00], q[i11] - q[i01], t) / width;
5511
+ const exY = lerp(q[i10 + 1] - q[i00 + 1], q[i11 + 1] - q[i01 + 1], t) / width;
5512
+ const eyX = -lerp(q[i01] - q[i00], q[i11] - q[i10], s) / height;
5513
+ const eyY = -lerp(q[i01 + 1] - q[i00 + 1], q[i11 + 1] - q[i10 + 1], s) / height;
5514
+ const theta = Math.atan2(exY - eyX, exX + eyY);
5515
+ const c = Math.cos(theta);
5516
+ const sn = Math.sin(theta);
5517
+ return [
5518
+ c,
5519
+ sn,
5520
+ -sn,
5521
+ c,
5522
+ px - (c * pivot.x - sn * pivot.y),
5523
+ py - (sn * pivot.x + c * pivot.y)
5524
+ ];
5525
+ }
5526
+ function matrixWorldAt(deformers2, parameters, id, values = {}) {
5527
+ for (const key of Object.keys(values)) {
5528
+ if (!parameters.some((q) => q.id === key)) {
5529
+ throw new Error(`chain: unknown parameter "${key}"`);
5530
+ }
5531
+ }
5532
+ const d = deformers2.find((x) => x.id === id);
5533
+ if (d === void 0) throw new Error(`chain: no deformer "${id}"`);
5534
+ if (d.kind === "warp") {
5535
+ throw new Error(`chain: "${id}" is a warp deformer, not a matrix`);
5536
+ }
5537
+ const own = ownMatrix(d, parameters, values);
5538
+ if (d.parent === void 0) return own;
5539
+ const parent = deformers2.find((x) => x.id === d.parent);
5540
+ if (parent === void 0) {
5541
+ throw new Error(`chain: "${id}" has an unknown parent "${d.parent}"`);
5542
+ }
5543
+ if (parent.kind !== "warp") {
5544
+ throw new Error(
5545
+ `chain: "${id}" hangs from the matrix deformer "${parent.id}"; only a root warp is supported`
5546
+ );
5547
+ }
5548
+ if (parent.parent !== void 0) {
5549
+ throw new Error(
5550
+ `chain: "${id}" hangs from warp "${parent.id}", which has a parent "${parent.parent}"; only a root warp is supported`
5551
+ );
5552
+ }
5553
+ if (parent.warp2d !== void 0) {
5554
+ throw new Error(
5555
+ `chain: "${id}" hangs from warp "${parent.id}", which carries a warp2d; only 1D warps are supported`
5556
+ );
5557
+ }
5558
+ return multiply(
5559
+ riddenFrame(d.pivot, parent, localGrid(parent, parameters, values)),
5560
+ own
5561
+ );
5562
+ }
5563
+
5564
+ // src/auto-rig/bakes.ts
5565
+ var TARGET_KEYS = [
5566
+ "id",
5567
+ "param",
5568
+ "parts",
5569
+ "box",
5570
+ "grid",
5571
+ "stops",
5572
+ "mirror",
5573
+ "why"
5574
+ ];
5575
+ var BOX_KEYS = ["x", "y", "w", "h"];
5576
+ var MAX_VERTICES = 65536;
5577
+ var CELL_PX = [4, 400];
5578
+ var MAX_MESH_COLS = 64;
5579
+ var MAX_MESH_ROWS = 400;
5580
+ var DEFAULT_BOX_MARGIN = 2;
5581
+ var SAME_STOP = 1e-9;
5582
+ var PLAIN_DECIMAL = /^[+-]?(\d+\.?\d*|\.\d+)([eE][+-]?\d+)?$/;
5583
+ var finite = (v) => typeof v === "number" && Number.isFinite(v);
5584
+ var isObject = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
5585
+ var show = (v) => typeof v === "number" ? String(v) : JSON.stringify(v) ?? String(v);
5586
+ function mirrorLattice(lattice2, cols, rows) {
5587
+ const out = new Array(lattice2.length);
5588
+ for (let r = 0; r <= rows; r++) {
5589
+ for (let c = 0; c <= cols; c++) {
5590
+ const [dx, dy] = lattice2[r * (cols + 1) + (cols - c)];
5591
+ out[r * (cols + 1) + c] = [-dx, dy];
5592
+ }
5593
+ }
5594
+ return out;
5595
+ }
5596
+ var r1 = (v) => Math.round(v * 10) / 10;
5597
+ var findStop = (stops, v) => [...stops.keys()].find((k) => Math.abs(k - v) <= SAME_STOP);
5598
+ function checkGrid(at, grid) {
5599
+ if (!isObject(grid)) {
5600
+ throw new BakeError(`${at}.grid must be { cols, rows }, got ${show(grid)}`);
5601
+ }
5602
+ checkedKeys(`${at}.grid`, grid, ["cols", "rows"], BakeError);
5603
+ const out = { cols: 0, rows: 0 };
5604
+ for (const key of ["cols", "rows"]) {
5605
+ const v = checkedNumber(
5606
+ `${at}.grid.${key}`,
5607
+ grid[key],
5608
+ 1,
5609
+ 32,
5610
+ false,
5611
+ BakeError
5612
+ );
5613
+ if (!Number.isInteger(v)) {
5614
+ throw new BakeError(`${at}.grid.${key} must be an integer, got ${v}`);
5615
+ }
5616
+ out[key] = v;
5617
+ }
5618
+ return out;
5619
+ }
5620
+ function checkBox(at, box, partBoxes) {
5621
+ if (box === void 0) {
5622
+ const x0 = Math.min(...partBoxes.map((b) => b.x)) - DEFAULT_BOX_MARGIN;
5623
+ const y0 = Math.min(...partBoxes.map((b) => b.y)) - DEFAULT_BOX_MARGIN;
5624
+ const x1 = Math.max(...partBoxes.map((b) => b.x + b.w)) + DEFAULT_BOX_MARGIN;
5625
+ const y1 = Math.max(...partBoxes.map((b) => b.y + b.h)) + DEFAULT_BOX_MARGIN;
5626
+ return { x: x0, y: y0, w: x1 - x0, h: y1 - y0 };
5627
+ }
5628
+ if (!isObject(box)) {
5629
+ throw new BakeError(`${at}.box must be { x, y, w, h }, got ${show(box)}`);
5630
+ }
5631
+ checkedKeys(`${at}.box`, box, BOX_KEYS, BakeError);
5632
+ const [x, y] = ["x", "y"].map(
5633
+ (k) => checkedNumber(
5634
+ `${at}.box.${k}`,
5635
+ box[k],
5636
+ -Infinity,
5637
+ Infinity,
5638
+ false,
5639
+ BakeError
5640
+ )
5641
+ );
5642
+ const [w, h] = ["w", "h"].map(
5643
+ (k) => checkedNumber(`${at}.box.${k}`, box[k], 0, Infinity, true, BakeError)
5644
+ );
5645
+ return { x, y, w, h };
5646
+ }
5647
+ function checkStops(at, t, info, rest, cols, rows) {
5648
+ if (!isObject(t.stops)) {
5649
+ throw new BakeError(
5650
+ `${at}.stops must be an object keyed by a ${info.id} value, got ${show(t.stops)}`
5651
+ );
5652
+ }
5653
+ const n = (cols + 1) * (rows + 1);
5654
+ const stops = /* @__PURE__ */ new Map();
5655
+ for (const [key, list] of Object.entries(t.stops)) {
5656
+ const sat = `${at}.stops["${key}"]`;
5657
+ if (!PLAIN_DECIMAL.test(key)) {
5658
+ throw new BakeError(`${sat} must be keyed by a plain decimal number`);
5659
+ }
5660
+ const v = Number(key);
5661
+ if (!Number.isFinite(v) || v < info.min || v > info.max) {
5662
+ throw new BakeError(
5663
+ `${sat} is outside ${info.id}'s range ${info.min} to ${info.max}`
5664
+ );
5665
+ }
5666
+ if (Math.abs(v - rest) <= SAME_STOP) {
5667
+ throw new BakeError(
5668
+ `${sat} is ${info.id}'s rest value ${rest}: rest is implicit, leave it out`
5669
+ );
5670
+ }
5671
+ if (findStop(stops, v) !== void 0) {
5672
+ throw new BakeError(`${sat} repeats an earlier stop`);
5673
+ }
5674
+ if (!Array.isArray(list) || list.length !== n) {
5675
+ throw new BakeError(
5676
+ `${sat} must be ${n} points [dx, dy] (${cols + 1} x ${rows + 1}), got ${Array.isArray(list) ? `${list.length} points` : show(list)}`
5677
+ );
5678
+ }
5679
+ list.forEach((pt, k) => {
5680
+ if (!Array.isArray(pt) || pt.length !== 2) {
5681
+ throw new BakeError(`${sat}[${k}] must be [dx, dy], got ${show(pt)}`);
5682
+ }
5683
+ pt.forEach((c, j) => {
5684
+ if (!finite(c)) {
5685
+ throw new BakeError(
5686
+ `${sat}[${k}][${j}] must be a finite number, got ${String(c)}`
5687
+ );
5688
+ }
5689
+ });
5690
+ });
5691
+ stops.set(v, list);
5692
+ }
5693
+ if (stops.size === 0) {
5694
+ throw new BakeError(`${at}.stops must have at least one stop`);
5695
+ }
5696
+ if (t.mirror === true) {
5697
+ for (const [v, lattice2] of [...stops]) {
5698
+ const m = 2 * rest - v;
5699
+ if (m < info.min - SAME_STOP || m > info.max + SAME_STOP) continue;
5700
+ if (Math.abs(m - rest) <= SAME_STOP || findStop(stops, m) !== void 0) {
5701
+ continue;
5702
+ }
5703
+ stops.set(
5704
+ Math.min(info.max, Math.max(info.min, m)),
5705
+ mirrorLattice(lattice2, cols, rows)
5706
+ );
5707
+ }
5708
+ }
5709
+ return stops;
5710
+ }
5711
+ function checkTarget(t, i, customParts, paramById) {
5712
+ const at = `bakes.targets[${i}]`;
5713
+ if (!isObject(t)) {
5714
+ throw new BakeError(`${at} must be a plain object, got ${show(t)}`);
5715
+ }
5716
+ checkedKeys(at, t, TARGET_KEYS, BakeError);
5717
+ if (t.id !== void 0 && typeof t.id !== "string") {
5718
+ throw new BakeError(`${at}.id must be a string, got ${show(t.id)}`);
5719
+ }
5720
+ if (t.why !== void 0 && typeof t.why !== "string") {
5721
+ throw new BakeError(`${at}.why must be a string, got ${show(t.why)}`);
5722
+ }
5723
+ const info = typeof t.param === "string" ? paramById.get(t.param) : void 0;
5724
+ if (info === void 0) {
5725
+ throw new BakeError(
5726
+ `${at}.param must be a parameter the model declares, got ${show(t.param)}`
5727
+ );
5728
+ }
5729
+ const rest = Math.min(info.max, Math.max(info.min, info.default));
5730
+ const customIds = customParts.map((c) => c.id);
5731
+ if (!Array.isArray(t.parts) || t.parts.length === 0) {
5732
+ throw new BakeError(
5733
+ `${at}.parts must be a non-empty array of part ids, got ${show(t.parts)}`
5734
+ );
5735
+ }
5736
+ const parts = t.parts;
5737
+ parts.forEach((id, k) => {
5738
+ if (typeof id !== "string" || !customIds.includes(id)) {
5739
+ throw new BakeError(
5740
+ `${at}.parts[${k}] must be a custom part id (${customIds.join(", ")}): a bake moves custom parts only, got ${show(id)}`
5741
+ );
5742
+ }
5743
+ if (parts.indexOf(id) !== k) {
5744
+ throw new BakeError(`${at}.parts[${k}] repeats "${id}"`);
5745
+ }
5746
+ });
5747
+ const { cols, rows } = checkGrid(at, t.grid);
5748
+ const partBoxes = parts.map(
5749
+ (id) => customParts.find((c) => c.id === id).layer.bbox
5750
+ );
5751
+ const box = checkBox(at, t.box, partBoxes);
5752
+ if (t.mirror !== void 0 && typeof t.mirror !== "boolean") {
5753
+ throw new BakeError(
5754
+ `${at}.mirror must be true or false, got ${show(t.mirror)}`
5755
+ );
5756
+ }
5757
+ const stops = checkStops(at, t, info, rest, cols, rows);
5758
+ return {
5759
+ id: typeof t.id === "string" ? t.id : `target${i}`,
5760
+ param: info.id,
5761
+ rest,
5762
+ parts,
5763
+ box,
5764
+ cols,
5765
+ rows,
5766
+ stops
5767
+ };
5768
+ }
5769
+ function checkMesh(at, choice, box) {
5770
+ if (!isObject(choice)) {
5771
+ throw new BakeError(
5772
+ `${at} must be { rows, cols } or { cellPx }, got ${show(choice)}`
5773
+ );
5774
+ }
5775
+ checkedKeys(at, choice, ["rows", "cols", "cellPx"], BakeError);
5776
+ if (choice.cellPx !== void 0) {
5777
+ if (choice.rows !== void 0 || choice.cols !== void 0) {
5778
+ throw new BakeError(`${at} takes { cellPx } or { rows, cols }, not both`);
5779
+ }
5780
+ const cell = checkedNumber(
5781
+ `${at}.cellPx`,
5782
+ choice.cellPx,
5783
+ CELL_PX[0],
5784
+ CELL_PX[1],
5785
+ false,
5786
+ BakeError
5787
+ );
5788
+ const count = (Math.max(1, Math.round(box.w / cell)) + 1) * (Math.max(1, Math.round(box.h / cell)) + 1);
5789
+ if (count > MAX_VERTICES) {
5790
+ throw new BakeError(
5791
+ `${at}.cellPx ${cell} makes ${count} vertices, over the format's ${MAX_VERTICES} limit`
5792
+ );
5793
+ }
5794
+ return { cellPx: cell };
5795
+ }
5796
+ const cols = checkedNumber(
5797
+ `${at}.cols`,
5798
+ choice.cols,
5799
+ 1,
5800
+ MAX_MESH_COLS,
5801
+ false,
5802
+ BakeError
5803
+ );
5804
+ if (!Number.isInteger(cols)) {
5805
+ throw new BakeError(`${at}.cols must be an integer, got ${cols}`);
5806
+ }
5807
+ const ys = choice.rows;
5808
+ if (!Array.isArray(ys) || ys.length < 2 || ys.length > MAX_MESH_ROWS) {
5809
+ throw new BakeError(
5810
+ `${at}.rows must be an array of 2 to ${MAX_MESH_ROWS} canvas-px y values, got ${Array.isArray(ys) ? `${ys.length} values` : show(ys)}`
5811
+ );
5812
+ }
5813
+ ys.forEach((y, k) => {
5814
+ if (!finite(y)) {
5815
+ throw new BakeError(
5816
+ `${at}.rows[${k}] must be a finite number, got ${String(y)}`
5817
+ );
5818
+ }
5819
+ if (k > 0 && !(y > ys[k - 1])) {
5820
+ throw new BakeError(
5821
+ `${at}.rows[${k}] must be below the row before it (rows are strictly increasing), got ${y} after ${String(ys[k - 1])}`
5822
+ );
5823
+ }
5824
+ });
5825
+ const last = ys.length - 1;
5826
+ if (Math.abs(ys[0] - box.y) > 0.5) {
5827
+ throw new BakeError(
5828
+ `${at}.rows[0] must be the part's box top ${r1(box.y)} (within 0.5 px), got ${String(ys[0])}`
5829
+ );
5830
+ }
5831
+ if (Math.abs(ys[last] - (box.y + box.h)) > 0.5) {
5832
+ throw new BakeError(
5833
+ `${at}.rows[${last}] must be the part's box bottom ${r1(box.y + box.h)} (within 0.5 px), got ${String(ys[last])}`
5834
+ );
5835
+ }
5836
+ return { rows: ys, cols };
5837
+ }
5838
+ function parseBakes(bakes, customParts, parameters) {
5839
+ if (customParts === void 0 || customParts.length === 0) {
5840
+ throw new BakeError(
5841
+ "bakes needs customParts: a bake moves custom parts only, and none were given"
5842
+ );
5843
+ }
5844
+ if (!isObject(bakes)) {
5845
+ throw new BakeError(`bakes must be a plain object, got ${show(bakes)}`);
5846
+ }
5847
+ checkedKeys("bakes", bakes, ["targets", "meshes"], BakeError);
5848
+ const { targets, meshes } = bakes;
5849
+ if (!Array.isArray(targets) || targets.length === 0) {
5850
+ throw new BakeError("bakes.targets must be a non-empty array");
5851
+ }
5852
+ const paramById = new Map(parameters.map((p) => [p.id, p]));
5853
+ const parsedTargets = targets.map(
5854
+ (t, i) => checkTarget(t, i, customParts, paramById)
5855
+ );
5856
+ const parsedMeshes = /* @__PURE__ */ new Map();
5857
+ if (meshes !== void 0) {
5858
+ if (!isObject(meshes)) {
5859
+ throw new BakeError(
5860
+ `bakes.meshes must be an object keyed by part id, got ${show(meshes)}`
5861
+ );
5862
+ }
5863
+ for (const [id, choice] of Object.entries(meshes)) {
5864
+ const at = `bakes.meshes.${id}`;
5865
+ const part = customParts.find((c) => c.id === id);
5866
+ if (part === void 0) {
5867
+ throw new BakeError(
5868
+ `${at}: "${id}" is not a custom part (${customParts.map((c) => c.id).join(", ")}): only a custom part is re-meshed`
5869
+ );
5870
+ }
5871
+ parsedMeshes.set(id, checkMesh(at, choice, part.layer.bbox));
5872
+ }
5873
+ }
5874
+ return { targets: parsedTargets, meshes: parsedMeshes };
5875
+ }
5876
+ var OFFSET_STEP = 1e-6;
5877
+ var RIGID_BEND = 25e-4;
5878
+ function rigidResidual(rest, moved) {
5879
+ const n = rest.length;
5880
+ if (moved.length !== n) {
5881
+ throw new Error(
5882
+ `rigidResidual: ${n} rest points but ${moved.length} moved points`
5883
+ );
5884
+ }
5885
+ if (n === 0) return 0;
5886
+ let pcx = 0;
5887
+ let pcy = 0;
5888
+ let qcx = 0;
5889
+ let qcy = 0;
5890
+ for (let i = 0; i < n; i++) {
5891
+ pcx += rest[i][0];
5892
+ pcy += rest[i][1];
5893
+ qcx += moved[i][0];
5894
+ qcy += moved[i][1];
5895
+ }
5896
+ pcx /= n;
5897
+ pcy /= n;
5898
+ qcx /= n;
5899
+ qcy /= n;
5900
+ let dot = 0;
5901
+ let cross = 0;
5902
+ for (let i = 0; i < n; i++) {
5903
+ const px = rest[i][0] - pcx;
5904
+ const py = rest[i][1] - pcy;
5905
+ const qx = moved[i][0] - qcx;
5906
+ const qy = moved[i][1] - qcy;
5907
+ dot += px * qx + py * qy;
5908
+ cross += px * qy - py * qx;
5909
+ }
5910
+ const theta = Math.atan2(cross, dot);
5911
+ const c = Math.cos(theta);
5912
+ const s = Math.sin(theta);
5913
+ let worst = 0;
5914
+ for (let i = 0; i < n; i++) {
5915
+ const px = rest[i][0] - pcx;
5916
+ const py = rest[i][1] - pcy;
5917
+ const ex = qcx + c * px - s * py - moved[i][0];
5918
+ const ey = qcy + s * px + c * py - moved[i][1];
5919
+ worst = Math.max(worst, Math.hypot(ex, ey));
5920
+ }
5921
+ return worst;
5922
+ }
5923
+ function latticeAt(t, lattice2, x, y) {
5924
+ const { cols, rows, box } = t;
5925
+ const u = clamp((x - box.x) / box.w, 0, 1) * cols;
5926
+ const v = clamp((y - box.y) / box.h, 0, 1) * rows;
5927
+ const c = Math.min(Math.floor(u), cols - 1);
5928
+ const r = Math.min(Math.floor(v), rows - 1);
5929
+ const fx = u - c;
5930
+ const fy = v - r;
5931
+ const at = (rr, cc) => lattice2[rr * (cols + 1) + cc];
5932
+ const mix = (k) => {
5933
+ const top = at(r, c)[k] + (at(r, c + 1)[k] - at(r, c)[k]) * fx;
5934
+ const bot = at(r + 1, c)[k] + (at(r + 1, c + 1)[k] - at(r + 1, c)[k]) * fx;
5935
+ return top + (bot - top) * fy;
5936
+ };
5937
+ return [mix(0), mix(1)];
5938
+ }
5939
+ function targetField(t, v) {
5940
+ const side = Math.sign(v - t.rest);
5941
+ const knots = [...t.stops.keys()].filter((k) => Math.sign(k - t.rest) === side).sort((a2, b) => Math.abs(a2 - t.rest) - Math.abs(b - t.rest));
5942
+ const a = Math.abs(v - t.rest);
5943
+ return (x, y) => {
5944
+ if (knots.length === 0) return [0, 0];
5945
+ const at = (k) => latticeAt(t, t.stops.get(k), x, y);
5946
+ let lo = 0;
5947
+ let loVal = [0, 0];
5948
+ for (const k of knots) {
5949
+ const hi = Math.abs(k - t.rest);
5950
+ const hiVal = at(k);
5951
+ if (a <= hi + SAME_STOP) {
5952
+ const f = hi - lo > 0 ? Math.min(1, (a - lo) / (hi - lo)) : 1;
5953
+ return [
5954
+ loVal[0] + (hiVal[0] - loVal[0]) * f,
5955
+ loVal[1] + (hiVal[1] - loVal[1]) * f
5956
+ ];
5957
+ }
5958
+ lo = hi;
5959
+ loVal = hiVal;
5960
+ }
5961
+ return loVal;
5962
+ };
5963
+ }
5964
+ var apply = (m, x, y) => [
5965
+ m[0] * x + m[2] * y + m[4],
5966
+ m[1] * x + m[3] * y + m[5]
5967
+ ];
5968
+ function invert(m) {
5969
+ const det = m[0] * m[3] - m[1] * m[2];
5970
+ const a = m[3] / det;
5971
+ const b = -m[1] / det;
5972
+ const c = -m[2] / det;
5973
+ const d = m[0] / det;
5974
+ return [a, b, c, d, -(a * m[4] + c * m[5]), -(b * m[4] + d * m[5])];
5975
+ }
5976
+ function remesh(part, entry, choice, canvas) {
5977
+ const b = boxOfPixels(entry.layer.bbox, canvas.width, canvas.height);
5978
+ if ("cellPx" in choice) {
5979
+ part.mesh = gridMesh(
5980
+ Math.max(1, Math.round(bw(b) / choice.cellPx)),
5981
+ Math.max(1, Math.round(bh(b) / choice.cellPx))
5982
+ );
5983
+ return;
5984
+ }
5985
+ const xs = Array.from(
5986
+ { length: choice.cols + 1 },
5987
+ (_, c) => b.x0 + bw(b) * c / choice.cols
5988
+ );
5989
+ part.mesh = columnMesh(b, {
5990
+ xs,
5991
+ ys: choice.rows.map((y) => canvas.height / 2 - y)
5992
+ });
5993
+ }
5994
+ function applyBakes(parsed, parts, entries, ctx) {
5995
+ const { canvas, deformers: deformers2, parameters, faceWidth } = ctx;
5996
+ const partOf = (id) => parts.find((p) => p.id === id);
5997
+ const entryOf = (id) => entries.find((e) => e.id === id);
5998
+ for (const [id, choice] of parsed.meshes) {
5999
+ remesh(partOf(id), entryOf(id), choice, canvas);
6000
+ }
6001
+ const ids = [...new Set(parsed.targets.flatMap((t) => t.parts))];
6002
+ const reports = [];
6003
+ for (const id of ids) {
6004
+ const part = partOf(id);
6005
+ const n = part.mesh.vertices.length / 2;
6006
+ const own = parsed.targets.filter((t) => t.parts.includes(id));
6007
+ const w0 = matrixWorldAt(deformers2, parameters, part.deformer);
6008
+ const local = [];
6009
+ const restModel = [];
6010
+ const restCanvas = [];
6011
+ for (let i = 0; i < n; i++) {
6012
+ const p = [
6013
+ part.transform.x + part.mesh.vertices[i * 2] * part.width,
6014
+ part.transform.y + part.mesh.vertices[i * 2 + 1] * part.height
6015
+ ];
6016
+ const r = apply(w0, p[0], p[1]);
6017
+ local.push(p);
6018
+ restModel.push(r);
6019
+ restCanvas.push([r[0] + canvas.width / 2, canvas.height / 2 - r[1]]);
6020
+ }
6021
+ for (const param of [...new Set(own.map((t) => t.param))]) {
6022
+ const targets = own.filter((t) => t.param === param);
6023
+ const rest = targets[0].rest;
6024
+ const stops = [];
6025
+ for (const v of targets.flatMap((t) => [...t.stops.keys()]).sort((a, b) => a - b)) {
6026
+ if (stops.length === 0 || v - stops[stops.length - 1] > SAME_STOP) {
6027
+ stops.push(v);
6028
+ }
6029
+ }
6030
+ const range = parameters.find((p) => p.id === param);
6031
+ const ends = [];
6032
+ for (const [outer, end] of [
6033
+ [stops.filter((v) => v < rest).shift(), range.min],
6034
+ [stops.filter((v) => v > rest).pop(), range.max]
6035
+ ]) {
6036
+ if (outer !== void 0 && Math.abs(end - outer) > SAME_STOP) {
6037
+ ends.push(end);
6038
+ }
6039
+ }
6040
+ const keyforms = [{ value: rest, offsets: new Array(2 * n).fill(0) }];
6041
+ const wanted = [];
6042
+ const endWanted = [];
6043
+ for (const v of [...stops, ...ends]) {
6044
+ const disp = new Array(2 * n).fill(0);
6045
+ const fields2 = targets.map((t) => targetField(t, v));
6046
+ for (let i = 0; i < n; i++) {
6047
+ for (const field of fields2) {
6048
+ const [dx, dy] = field(restCanvas[i][0], restCanvas[i][1]);
6049
+ disp[i * 2] += dx;
6050
+ disp[i * 2 + 1] += dy;
6051
+ }
6052
+ }
6053
+ (ends.includes(v) ? endWanted : wanted).push(disp);
6054
+ if (entryOf(id).rigid === true) {
6055
+ const moved = restCanvas.map(
6056
+ (p, i) => [p[0] + disp[i * 2], p[1] + disp[i * 2 + 1]]
6057
+ );
6058
+ const bend = rigidResidual(restCanvas, moved);
6059
+ if (bend > RIGID_BEND * faceWidth) {
6060
+ const names = targets.map(
6061
+ (t) => `bakes.targets[${parsed.targets.indexOf(t)}] ("${t.id}")`
6062
+ ).join(", ");
6063
+ throw new BakeError(
6064
+ `${names}: "${id}" is rigid, but at ${param} ${v} these targets together bend it by ${bend.toFixed(2)} px, over the ${(RIGID_BEND * faceWidth).toFixed(2)} px a rigid part may bend (${RIGID_BEND} face widths): give every control point one displacement, or a rotation`
6065
+ );
6066
+ }
6067
+ }
6068
+ const inv = invert(
6069
+ matrixWorldAt(deformers2, parameters, part.deformer, { [param]: v })
6070
+ );
6071
+ const offsets = [];
6072
+ for (let i = 0; i < n; i++) {
6073
+ const [px, py] = apply(
6074
+ inv,
6075
+ restModel[i][0] + disp[i * 2],
6076
+ restModel[i][1] - disp[i * 2 + 1]
6077
+ );
6078
+ offsets.push(
6079
+ roundTo((px - local[i][0]) / part.width, OFFSET_STEP),
6080
+ roundTo((py - local[i][1]) / part.height, OFFSET_STEP)
6081
+ );
6082
+ }
6083
+ keyforms.push({ value: v, offsets });
6084
+ }
6085
+ keyforms.sort((a, b) => a.value - b.value);
6086
+ part.warps = [...part.warps ?? [], { parameter: param, keyforms }];
6087
+ reports.push({
6088
+ part: id,
6089
+ param,
6090
+ stops,
6091
+ vertexCount: n,
6092
+ wanted,
6093
+ ends: ends.map((value, i) => ({ value, wanted: endWanted[i] }))
6094
+ });
6095
+ }
6096
+ }
6097
+ return reports;
6098
+ }
6099
+
5407
6100
  // src/auto-rig/generate.ts
5408
6101
  var import_format12 = require("@ikijs/format");
5409
6102
 
@@ -5606,8 +6299,8 @@ function defaultMotions(declared) {
5606
6299
  }
5607
6300
 
5608
6301
  // src/auto-rig/checks.ts
5609
- var finite = (v) => typeof v === "number" && Number.isFinite(v);
5610
- var isObject = (v) => typeof v === "object" && v !== null;
6302
+ var finite2 = (v) => typeof v === "number" && Number.isFinite(v);
6303
+ var isObject2 = (v) => typeof v === "object" && v !== null;
5611
6304
  var MIN_FACE_PX = 24;
5612
6305
  function checkLayers(layers, canvas) {
5613
6306
  if (!Array.isArray(layers) || layers.length === 0) {
@@ -5615,10 +6308,10 @@ function checkLayers(layers, canvas) {
5615
6308
  "auto-rig: generateIkiFromLayerSet: layers must be a non-empty array"
5616
6309
  );
5617
6310
  }
5618
- if (!(finite(canvas?.width) && canvas.width > 0)) {
6311
+ if (!(finite2(canvas?.width) && canvas.width > 0)) {
5619
6312
  throw new Error("auto-rig: canvas.width must be a positive number");
5620
6313
  }
5621
- if (!(finite(canvas?.height) && canvas.height > 0)) {
6314
+ if (!(finite2(canvas?.height) && canvas.height > 0)) {
5622
6315
  throw new Error("auto-rig: canvas.height must be a positive number");
5623
6316
  }
5624
6317
  const seen = /* @__PURE__ */ new Set();
@@ -5630,7 +6323,7 @@ function checkLayers(layers, canvas) {
5630
6323
  seen.add(l.role);
5631
6324
  const at = `auto-rig: layer "${l.fileName}"`;
5632
6325
  const { x, y, w, h } = l.bbox ?? {};
5633
- if (![x, y, w, h].every(finite) || w <= 0 || h <= 0 || x < 0 || y < 0 || x + w > canvas.width || y + h > canvas.height) {
6326
+ if (![x, y, w, h].every(finite2) || w <= 0 || h <= 0 || x < 0 || y < 0 || x + w > canvas.width || y + h > canvas.height) {
5634
6327
  throw new Error(
5635
6328
  `${at} has a bbox outside its ${canvas.width}x${canvas.height} canvas`
5636
6329
  );
@@ -5658,7 +6351,7 @@ function checkLayers(layers, canvas) {
5658
6351
  );
5659
6352
  }
5660
6353
  rows.forEach((v, i) => {
5661
- if (!finite(v) || v < 0 || v > w / 2 + 0.5) {
6354
+ if (!finite2(v) || v < 0 || v > w / 2 + 0.5) {
5662
6355
  throw new Error(
5663
6356
  `${at}: rowHalfWidths[${i}] ${v} is not a half-width inside its crop`
5664
6357
  );
@@ -5699,7 +6392,7 @@ function checkLayers(layers, canvas) {
5699
6392
  }
5700
6393
  if (l.denseCore !== void 0) {
5701
6394
  const c = l.denseCore;
5702
- if (![c.x, c.y, c.w, c.h].every(finite) || c.w <= 0 || c.h <= 0 || c.x < x - 1 || c.y < y - 1 || c.x + c.w > x + w + 1 || c.y + c.h > y + h + 1) {
6395
+ if (![c.x, c.y, c.w, c.h].every(finite2) || c.w <= 0 || c.h <= 0 || c.x < x - 1 || c.y < y - 1 || c.x + c.w > x + w + 1 || c.y + c.h > y + h + 1) {
5703
6396
  throw new Error(`${at}: denseCore is not a box inside its crop`);
5704
6397
  }
5705
6398
  }
@@ -5722,7 +6415,7 @@ function checkLayers(layers, canvas) {
5722
6415
  }
5723
6416
  function checkHeadEdges(edges, byRole) {
5724
6417
  if (edges === void 0) return;
5725
- if (!isObject(edges))
6418
+ if (!isObject2(edges))
5726
6419
  throw new TurnTargetError("headEdges must be an object");
5727
6420
  for (const side of ["left", "right"]) {
5728
6421
  const list = edges[side];
@@ -5730,12 +6423,12 @@ function checkHeadEdges(edges, byRole) {
5730
6423
  throw new TurnTargetError(`headEdges.${side} must be an array`);
5731
6424
  }
5732
6425
  list.forEach((e, i) => {
5733
- if (!isObject(e) || typeof e.role !== "string" || !byRole.has(e.role)) {
6426
+ if (!isObject2(e) || typeof e.role !== "string" || !byRole.has(e.role)) {
5734
6427
  throw new TurnTargetError(
5735
6428
  `headEdges.${side}[${i}].role is not a layer in this set`
5736
6429
  );
5737
6430
  }
5738
- if (!finite(e.x)) {
6431
+ if (!finite2(e.x)) {
5739
6432
  throw new TurnTargetError(
5740
6433
  `headEdges.${side}[${i}].x must be a finite number`
5741
6434
  );
@@ -5755,25 +6448,25 @@ function checkHeadEdges(edges, byRole) {
5755
6448
  }
5756
6449
  function checkStrandEdges(strand, irises, hair) {
5757
6450
  if (strand === void 0) return;
5758
- if (!isObject(strand)) {
6451
+ if (!isObject2(strand)) {
5759
6452
  throw new TurnTargetError("strandEdges must be an object");
5760
6453
  }
5761
6454
  for (const side of ["left", "right"]) {
5762
6455
  const s = strand[side];
5763
6456
  if (s === void 0) continue;
5764
6457
  const at = `strandEdges.${side}`;
5765
- if (!isObject(s)) throw new TurnTargetError(`${at} must be an object`);
6458
+ if (!isObject2(s)) throw new TurnTargetError(`${at} must be an object`);
5766
6459
  if (hair === void 0) {
5767
6460
  throw new TurnTargetError(
5768
6461
  `${at} names a bangs' run, but this layer set has no hair_front`
5769
6462
  );
5770
6463
  }
5771
6464
  for (const k of ["y", "irisOuter", "irisInner", "runOuter"]) {
5772
- if (!finite(s[k])) {
6465
+ if (!finite2(s[k])) {
5773
6466
  throw new TurnTargetError(`${at}.${k} must be a finite number`);
5774
6467
  }
5775
6468
  }
5776
- if (s.runFace !== null && !finite(s.runFace)) {
6469
+ if (s.runFace !== null && !finite2(s.runFace)) {
5777
6470
  throw new TurnTargetError(
5778
6471
  `${at}.runFace must be a finite number or null`
5779
6472
  );
@@ -6242,9 +6935,9 @@ function replay(op, i, ctx, sums) {
6242
6935
  const x = span2(`${name}.region.x`, region.x);
6243
6936
  const y = span2(`${name}.region.y`, region.y);
6244
6937
  const by = fields(`${name}.by`, o.by, BY_KEYS);
6245
- const dx = finite2(`${name}.by.dx`, by.dx);
6246
- const dy = finite2(`${name}.by.dy`, by.dy);
6247
- const falloff = o.falloff === void 0 ? 0 : finite2(`${name}.falloff`, o.falloff, 0);
6938
+ const dx = finite3(`${name}.by.dx`, by.dx);
6939
+ const dy = finite3(`${name}.by.dy`, by.dy);
6940
+ const falloff = o.falloff === void 0 ? 0 : finite3(`${name}.falloff`, o.falloff, 0);
6248
6941
  if (o.why !== void 0) text(`${name}.why`, o.why);
6249
6942
  const box = ctx.boxOf(role);
6250
6943
  if (box === void 0) {
@@ -6412,10 +7105,10 @@ function fields(name, v, known) {
6412
7105
  function numbers(name, v) {
6413
7106
  const o = fields(name, v, Object.keys(v ?? {}));
6414
7107
  return Object.fromEntries(
6415
- Object.entries(o).map(([k, n]) => [k, finite2(`${name}.${k}`, n)])
7108
+ Object.entries(o).map(([k, n]) => [k, finite3(`${name}.${k}`, n)])
6416
7109
  );
6417
7110
  }
6418
- var finite2 = (name, v, lo = -Infinity) => asPatch(() => checkedNumber(name, v, lo, Infinity));
7111
+ var finite3 = (name, v, lo = -Infinity) => asPatch(() => checkedNumber(name, v, lo, Infinity));
6419
7112
  function text(name, v) {
6420
7113
  if (typeof v !== "string") {
6421
7114
  throw new PatchError(`${name} must be a string, got ${String(v)}`);
@@ -6428,8 +7121,8 @@ function span2(name, v) {
6428
7121
  `${name} must be [from, to], got ${JSON.stringify(v)}`
6429
7122
  );
6430
7123
  }
6431
- const lo = finite2(`${name}[0]`, v[0]);
6432
- const hi = finite2(`${name}[1]`, v[1]);
7124
+ const lo = finite3(`${name}[0]`, v[0]);
7125
+ const hi = finite3(`${name}[1]`, v[1]);
6433
7126
  if (lo > hi) {
6434
7127
  throw new PatchError(
6435
7128
  `${name} [${lo}, ${hi}] runs backwards: ${name}[0] must not exceed ${name}[1]`
@@ -6438,6 +7131,112 @@ function span2(name, v) {
6438
7131
  return [lo, hi];
6439
7132
  }
6440
7133
 
7134
+ // src/auto-rig/custom-parts.ts
7135
+ var KEYS = ["id", "layer", "anchor", "tie", "after", "deformer", "rigid"];
7136
+ var LAYER_KEYS = ["fileName", "bbox"];
7137
+ var finite4 = (v) => typeof v === "number" && Number.isFinite(v);
7138
+ var isObject3 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
7139
+ var inside = (b, [x, y]) => x >= b.x && x < b.x + b.w && y >= b.y && y < b.y + b.h;
7140
+ var RESERVED = new Set(
7141
+ ROLE_TABLE.flatMap((r) => [r.role, ...partIdsOfRole(r.role)])
7142
+ );
7143
+ function checkCustomParts(customParts, byRole, canvas) {
7144
+ if (!Array.isArray(customParts)) {
7145
+ throw new CustomPartError(
7146
+ `customParts must be an array, got ${customParts === null ? "null" : typeof customParts}`
7147
+ );
7148
+ }
7149
+ const present = [...byRole.keys()];
7150
+ const roleField = (at, v) => {
7151
+ const layer = typeof v === "string" ? byRole.get(v) : void 0;
7152
+ if (layer === void 0) {
7153
+ throw new CustomPartError(
7154
+ `${at} must be a role of the set, one of ${present.join(", ")}; got ${JSON.stringify(v)}`
7155
+ );
7156
+ }
7157
+ return layer;
7158
+ };
7159
+ const ids = /* @__PURE__ */ new Set();
7160
+ customParts.forEach((entry, i) => {
7161
+ const at = `customParts[${i}]`;
7162
+ if (!isObject3(entry)) {
7163
+ throw new CustomPartError(`${at} must be a plain object`);
7164
+ }
7165
+ checkedKeys(at, entry, KEYS, CustomPartError);
7166
+ const { id, layer, anchor, tie, after, deformer, rigid } = entry;
7167
+ if (typeof id !== "string" || !/^[A-Za-z_][A-Za-z0-9_]*$/.test(id)) {
7168
+ throw new CustomPartError(
7169
+ `${at}.id must be a plain identifier (letters, digits, underscore; not starting with a digit), got ${JSON.stringify(id)}`
7170
+ );
7171
+ }
7172
+ if (RESERVED.has(id)) {
7173
+ throw new CustomPartError(
7174
+ `${at}.id "${id}" is a role's name or a part id a role yields: those are reserved, also for roles added later, so pick a name no role would take`
7175
+ );
7176
+ }
7177
+ if (ids.has(id)) {
7178
+ throw new CustomPartError(
7179
+ `${at}.id "${id}" repeats an earlier entry's id`
7180
+ );
7181
+ }
7182
+ ids.add(id);
7183
+ if (!isObject3(layer)) {
7184
+ throw new CustomPartError(`${at}.layer must be a plain object`);
7185
+ }
7186
+ checkedKeys(`${at}.layer`, layer, LAYER_KEYS, CustomPartError);
7187
+ if (typeof layer.fileName !== "string" || layer.fileName === "") {
7188
+ throw new CustomPartError(
7189
+ `${at}.layer.fileName must be a non-empty string`
7190
+ );
7191
+ }
7192
+ const b = isObject3(layer.bbox) ? layer.bbox : {};
7193
+ const { x, y, w, h } = b;
7194
+ if (!(finite4(x) && finite4(y) && finite4(w) && finite4(h)) || w <= 0 || h <= 0 || x < 0 || y < 0 || x + w > canvas.width || y + h > canvas.height) {
7195
+ throw new CustomPartError(
7196
+ `${at}.layer.bbox must be a finite, non-empty box inside the ${canvas.width}x${canvas.height} canvas, got ${JSON.stringify(layer.bbox)}`
7197
+ );
7198
+ }
7199
+ const box = { x, y, w, h };
7200
+ const anchorLayer = roleField(`${at}.anchor`, anchor);
7201
+ roleField(`${at}.after`, after);
7202
+ if (!Array.isArray(tie) || tie.length !== 2 || !tie.every(finite4) || !inside(box, tie) || !inside(anchorLayer.bbox, tie)) {
7203
+ throw new CustomPartError(
7204
+ `${at}.tie must be [x, y], two finite canvas px inside both the part's layer box and the anchor "${String(anchor)}" layer box, got ${JSON.stringify(tie)}`
7205
+ );
7206
+ }
7207
+ if (deformer !== void 0 && deformer !== "headDeformer") {
7208
+ throw new CustomPartError(
7209
+ `${at}.deformer "${String(deformer)}" is not accepted: headDeformer is the only deformer a custom part hangs on`
7210
+ );
7211
+ }
7212
+ if (rigid !== void 0 && typeof rigid !== "boolean") {
7213
+ throw new CustomPartError(
7214
+ `${at}.rigid must be true or false, got ${JSON.stringify(rigid)}`
7215
+ );
7216
+ }
7217
+ });
7218
+ return customParts;
7219
+ }
7220
+ var CELL_PX2 = 20;
7221
+ function buildCustomParts(customParts, canvas) {
7222
+ return customParts.map(({ id, layer }) => {
7223
+ const b = boxOfPixels(layer.bbox, canvas.width, canvas.height);
7224
+ return {
7225
+ id,
7226
+ color: [1, 1, 1, 1],
7227
+ width: bw(b),
7228
+ height: bh(b),
7229
+ transform: { x: cx(b), y: cy(b) },
7230
+ order: 0,
7231
+ deformer: "headDeformer",
7232
+ mesh: gridMesh(
7233
+ Math.max(1, Math.round(bw(b) / CELL_PX2)),
7234
+ Math.max(1, Math.round(bh(b) / CELL_PX2))
7235
+ )
7236
+ };
7237
+ });
7238
+ }
7239
+
6441
7240
  // src/auto-rig/generate.ts
6442
7241
  var MESH_CELLS = {
6443
7242
  face: { px: 28, min: 4, max: 24 },
@@ -6479,6 +7278,9 @@ function generateIkiFromLayerSet(layers, canvas, options = {}) {
6479
7278
  const hasNose = has("nose");
6480
7279
  const plateHalf = byRole.get("face").cropW / 2;
6481
7280
  checkHeadEdges(options.headEdges, byRole);
7281
+ const entries = options.customParts !== void 0 && !(Array.isArray(options.customParts) && options.customParts.length === 0) ? checkCustomParts(options.customParts, byRole, canvas) : [];
7282
+ const parameters = declareParameters(new Set(byRole.keys()));
7283
+ const bakes = options.bakes === void 0 ? void 0 : parseBakes(options.bakes, entries, parameters);
6482
7284
  checkStrandEdges(
6483
7285
  options.strandEdges,
6484
7286
  sideIrises(byRole),
@@ -6628,7 +7430,6 @@ function generateIkiFromLayerSet(layers, canvas, options = {}) {
6628
7430
  ...part.warps ?? []
6629
7431
  ];
6630
7432
  }
6631
- const parameters = declareParameters(new Set(byRole.keys()));
6632
7433
  const reports = options.patches === void 0 ? void 0 : applyPatches(options.patches, {
6633
7434
  grids: new Map(
6634
7435
  [...grids].map(([family, g]) => [WARP_ID[family], g])
@@ -6638,19 +7439,50 @@ function generateIkiFromLayerSet(layers, canvas, options = {}) {
6638
7439
  faceWidth: byRole.get("face").cropW,
6639
7440
  parameters
6640
7441
  });
7442
+ const deformerList = deformers(
7443
+ frame,
7444
+ grids,
7445
+ bodyWarp?.deformer,
7446
+ arms,
7447
+ byRole
7448
+ );
7449
+ let bakeReports;
7450
+ if (entries.length > 0) {
7451
+ const built = buildCustomParts(entries, canvas);
7452
+ entries.forEach((entry, i) => {
7453
+ const group = new Set(partIdsOfRole(entry.after));
7454
+ for (let j = 0; j < i; j++) {
7455
+ if (entries[j].after === entry.after) group.add(entries[j].id);
7456
+ }
7457
+ const last = parts.map((p) => group.has(p.id)).lastIndexOf(true);
7458
+ parts.splice(last + 1, 0, built[i]);
7459
+ });
7460
+ parts.forEach((p, i) => {
7461
+ p.order = i;
7462
+ });
7463
+ if (bakes !== void 0) {
7464
+ bakeReports = applyBakes(bakes, built, entries, {
7465
+ canvas,
7466
+ deformers: deformerList,
7467
+ parameters,
7468
+ faceWidth: byRole.get("face").cropW
7469
+ });
7470
+ }
7471
+ }
6641
7472
  const model = {
6642
7473
  version: import_format12.IKI_FORMAT_VERSION,
6643
7474
  name: "auto-rigged",
6644
7475
  canvas: { width: canvas.width, height: canvas.height },
6645
7476
  parameters,
6646
7477
  parts,
6647
- deformers: deformers(frame, grids, bodyWarp?.deformer, arms, byRole),
7478
+ deformers: deformerList,
6648
7479
  ...has("hair_front") ? { physics: hairPhysics(has("hair_back")) } : {},
6649
7480
  expressions: defaultExpressions(new Set(parameters.map((p) => p.id))),
6650
7481
  motions: defaultMotions(new Set(parameters.map((p) => p.id)))
6651
7482
  };
6652
7483
  const valid = (0, import_format12.parseIkiModel)(model);
6653
7484
  if (reports !== void 0) options.onPatchesApplied?.(reports);
7485
+ if (bakeReports !== void 0) options.onBakesApplied?.(bakeReports);
6654
7486
  options.onRigPlanResolved?.(
6655
7487
  rigPlanReport(options.rigPlan ?? {}, plan, targets, fit)
6656
7488
  );
@@ -7133,7 +7965,9 @@ function declareParameters(roles) {
7133
7965
  AddDeformer,
7134
7966
  AddPart,
7135
7967
  AddPhysicsRig,
7968
+ BakeError,
7136
7969
  CaptureGridKeyform,
7970
+ CustomPartError,
7137
7971
  DEFAULT_TURN_TARGETS,
7138
7972
  DeleteDeformer,
7139
7973
  DeletePart,
@@ -7189,6 +8023,7 @@ function declareParameters(roles) {
7189
8023
  packAtlas,
7190
8024
  parseLayerRoles,
7191
8025
  partIdsOfRole,
8026
+ rigidResidual,
7192
8027
  styleRemovedMessage,
7193
8028
  upsertGridKeyform,
7194
8029
  uvRectFor,