@ikijs/editor 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1420,6 +1420,242 @@ function detectAlphaBbox(rgba, width, height) {
1420
1420
  return { x, y, w: x2 - x + 1, h: y2 - y + 1 };
1421
1421
  }
1422
1422
 
1423
+ // src/layer-measure.ts
1424
+ var HEAD_BAND = 10;
1425
+ var ALPHA_OPAQUE = 128;
1426
+ function denseCoreOf(rgba, width, height) {
1427
+ let minX = width;
1428
+ let minY = height;
1429
+ let maxX = -1;
1430
+ let maxY = -1;
1431
+ for (let y = 0; y < height; y++) {
1432
+ for (let x = 0; x < width; x++) {
1433
+ if (rgba[(y * width + x) * 4 + 3] >= ALPHA_OPAQUE) {
1434
+ if (x < minX) minX = x;
1435
+ if (x > maxX) maxX = x;
1436
+ if (y < minY) minY = y;
1437
+ if (y > maxY) maxY = y;
1438
+ }
1439
+ }
1440
+ }
1441
+ if (maxX < 0) return null;
1442
+ return { x: minX, y: minY, w: maxX - minX + 1, h: maxY - minY + 1 };
1443
+ }
1444
+ var SPECK_CORE_FRACTION = 0.25;
1445
+ function isSpeckCore(core, part) {
1446
+ return core.w < SPECK_CORE_FRACTION * part.w || core.h < SPECK_CORE_FRACTION * part.h;
1447
+ }
1448
+ function foregroundSpan(mask, width, height, rowLo, rowHi) {
1449
+ let left = width;
1450
+ let right = -1;
1451
+ for (let y = Math.max(0, rowLo); y <= Math.min(height - 1, rowHi); y++) {
1452
+ for (let x = 0; x < width; x++) {
1453
+ if (!mask[y * width + x]) continue;
1454
+ if (x < left) left = x;
1455
+ if (x > right) right = x;
1456
+ }
1457
+ }
1458
+ return { left, right };
1459
+ }
1460
+ function headHalfOf(span) {
1461
+ return (span.right - span.left + 1) / 2;
1462
+ }
1463
+ function eyeBandRowOf(layers) {
1464
+ const centreY = (role) => {
1465
+ const layer = layers.find((l) => l.role === role);
1466
+ return layer && layer.bbox.y + layer.bbox.h / 2;
1467
+ };
1468
+ const pairRow = (left, right) => {
1469
+ const a = centreY(left);
1470
+ const b = centreY(right);
1471
+ return a === void 0 || b === void 0 ? void 0 : (a + b) / 2;
1472
+ };
1473
+ const row = pairRow("iris_L", "iris_R") ?? pairRow("eye_L", "eye_R");
1474
+ if (row === void 0) {
1475
+ throw new Error("auto-rig: no eye pair to measure the head width at");
1476
+ }
1477
+ return Math.round(row);
1478
+ }
1479
+ function foregroundSpanOfRows(rowLeft, rowRight, rowLo, rowHi) {
1480
+ let left = Infinity;
1481
+ let right = -Infinity;
1482
+ for (let y = Math.max(0, rowLo); y <= Math.min(rowLeft.length - 1, rowHi); y++) {
1483
+ if (rowLeft[y] < left) left = rowLeft[y];
1484
+ if (rowRight[y] > right) right = rowRight[y];
1485
+ }
1486
+ return { left, right };
1487
+ }
1488
+ function facePlateHalfOf(layers) {
1489
+ const face = layers.find((l) => l.role === "face");
1490
+ if (face === void 0) {
1491
+ throw new Error("auto-rig: no face layer to measure the plate against");
1492
+ }
1493
+ return face.cropW / 2;
1494
+ }
1495
+ var STRAND_MIN_RUN_FRACTION = 0.5;
1496
+ function createLayerSetMeasurer(canvas) {
1497
+ for (const field of ["width", "height"]) {
1498
+ const value = canvas[field];
1499
+ if (!Number.isSafeInteger(value) || value <= 0) {
1500
+ throw new Error(
1501
+ `auto-rig: createLayerSetMeasurer: canvas.${field} must be a positive safe integer, got ${value}`
1502
+ );
1503
+ }
1504
+ }
1505
+ const { width: canvasW, height: canvasH } = canvas;
1506
+ const rgbaLength = canvasW * canvasH * 4;
1507
+ if (!Number.isSafeInteger(rgbaLength)) {
1508
+ throw new Error(
1509
+ `auto-rig: createLayerSetMeasurer: canvas ${canvasW}x${canvasH} is too large: width \xD7 height \xD7 4 = ${rgbaLength} is not a safe integer`
1510
+ );
1511
+ }
1512
+ const opaque = new Uint8Array(canvasW * canvasH);
1513
+ const layerInputs = [];
1514
+ const rowSpansByRole = [];
1515
+ let hairFrontMask;
1516
+ function add({ role, fileName, rgba }) {
1517
+ if (rgba.length !== rgbaLength) {
1518
+ throw new Error(
1519
+ `auto-rig: layer "${fileName}": rgba length ${rgba.length} is not ${canvasW}x${canvasH}x4 = ${rgbaLength}`
1520
+ );
1521
+ }
1522
+ const bbox = detectAlphaBbox(rgba, canvasW, canvasH);
1523
+ if (bbox === null) return null;
1524
+ const rowLeft = new Int32Array(canvasH).fill(canvasW);
1525
+ const rowRight = new Int32Array(canvasH).fill(-1);
1526
+ const ownMask = role === "hair_front" ? new Uint8Array(canvasW * canvasH) : void 0;
1527
+ for (let y = 0; y < canvasH; y++) {
1528
+ for (let x = 0; x < canvasW; x++) {
1529
+ const p = y * canvasW + x;
1530
+ if (rgba[p * 4 + 3] < ALPHA_OPAQUE) continue;
1531
+ opaque[p] = 1;
1532
+ if (ownMask !== void 0) ownMask[p] = 1;
1533
+ if (x < rowLeft[y]) rowLeft[y] = x;
1534
+ if (x > rowRight[y]) rowRight[y] = x;
1535
+ }
1536
+ }
1537
+ rowSpansByRole.push({ role, rowLeft, rowRight });
1538
+ if (ownMask !== void 0) hairFrontMask = ownMask;
1539
+ const layer = {
1540
+ role,
1541
+ fileName,
1542
+ canvasW,
1543
+ canvasH,
1544
+ bbox,
1545
+ cropW: bbox.w,
1546
+ cropH: bbox.h
1547
+ };
1548
+ if (role === "face") {
1549
+ const rowHalfWidths = [];
1550
+ for (let y = bbox.y; y < bbox.y + bbox.h; y++) {
1551
+ rowHalfWidths.push(
1552
+ rowRight[y] >= rowLeft[y] ? headHalfOf({ left: rowLeft[y], right: rowRight[y] }) : 0
1553
+ );
1554
+ }
1555
+ layer.rowHalfWidths = rowHalfWidths;
1556
+ } else if (role === "nose") {
1557
+ const core = denseCoreOf(rgba, canvasW, canvasH);
1558
+ if (core !== null && !isSpeckCore(core, bbox)) layer.denseCore = core;
1559
+ }
1560
+ layerInputs.push(layer);
1561
+ return layer;
1562
+ }
1563
+ function finish() {
1564
+ const eyeRow = eyeBandRowOf(layerInputs);
1565
+ const span = foregroundSpan(
1566
+ opaque,
1567
+ canvasW,
1568
+ canvasH,
1569
+ eyeRow - HEAD_BAND,
1570
+ eyeRow + HEAD_BAND
1571
+ );
1572
+ const headHalfWidth = span.right > span.left ? headHalfOf(span) : void 0;
1573
+ const headHalfWidthApplied = headHalfWidth !== void 0 && headHalfWidth > facePlateHalfOf(layerInputs);
1574
+ let headEdges;
1575
+ if (headHalfWidthApplied) {
1576
+ const bandLo = eyeRow - HEAD_BAND;
1577
+ const bandHi = eyeRow + HEAD_BAND;
1578
+ const left = [];
1579
+ const right = [];
1580
+ for (const { role, rowLeft, rowRight } of rowSpansByRole) {
1581
+ const own = foregroundSpanOfRows(rowLeft, rowRight, bandLo, bandHi);
1582
+ if (own.right <= own.left) continue;
1583
+ left.push({ role, x: own.left - canvasW / 2 });
1584
+ right.push({ role, x: own.right - canvasW / 2 });
1585
+ }
1586
+ headEdges = { left, right };
1587
+ }
1588
+ let strandEdges;
1589
+ const irisLayers = layerInputs.filter((l) => l.role === "iris_L" || l.role === "iris_R").sort((a, b) => a.bbox.x + a.bbox.w / 2 - (b.bbox.x + b.bbox.w / 2));
1590
+ if (hairFrontMask !== void 0 && irisLayers.length === 2) {
1591
+ const mask = hairFrontMask;
1592
+ const half = canvasW / 2;
1593
+ const strandOn = (iris, other, side) => {
1594
+ const r = Math.floor(iris.bbox.y + iris.bbox.h / 2);
1595
+ const centre = iris.bbox.x + iris.bbox.w / 2;
1596
+ const otherX = other.bbox.x + other.bbox.w / 2 - half;
1597
+ const c = side < 0 ? Math.ceil(centre) - 1 : Math.floor(centre);
1598
+ const span2 = rowSpansByRole.find((s) => s.role === iris.role);
1599
+ if (span2.rowRight[r] < span2.rowLeft[r]) return void 0;
1600
+ const minRun = STRAND_MIN_RUN_FRACTION * (span2.rowRight[r] + 1 - span2.rowLeft[r]);
1601
+ const strand = new Uint8Array(canvasW);
1602
+ for (let a = 0; a < canvasW; ) {
1603
+ if (mask[r * canvasW + a] !== 1) {
1604
+ a++;
1605
+ continue;
1606
+ }
1607
+ let b = a;
1608
+ while (b < canvasW && mask[r * canvasW + b] === 1) b++;
1609
+ if (b - a >= minRun) strand.fill(1, a, b);
1610
+ a = b;
1611
+ }
1612
+ const strandAt = (x2) => x2 >= 0 && x2 < canvasW && strand[x2] === 1;
1613
+ const outerBoundary = (x2) => (side < 0 ? x2 : x2 + 1) - half;
1614
+ let x = c;
1615
+ let runFace;
1616
+ if (!strandAt(c)) {
1617
+ while (x >= 0 && x < canvasW && !strandAt(x)) x += side;
1618
+ if (x < 0 || x >= canvasW) return void 0;
1619
+ runFace = outerBoundary(x - side);
1620
+ } else {
1621
+ let f = c - side;
1622
+ while (strandAt(f)) f -= side;
1623
+ const face = outerBoundary(f);
1624
+ runFace = side * (face - otherX) > 0 ? face : null;
1625
+ }
1626
+ while (strandAt(x)) x += side;
1627
+ return {
1628
+ y: canvasH / 2 - (r + 0.5),
1629
+ irisOuter: (side < 0 ? span2.rowLeft[r] : span2.rowRight[r] + 1) - half,
1630
+ irisInner: (side < 0 ? span2.rowRight[r] + 1 : span2.rowLeft[r]) - half,
1631
+ runOuter: outerBoundary(x - side),
1632
+ runFace
1633
+ };
1634
+ };
1635
+ const [low, high] = irisLayers;
1636
+ const left = strandOn(low, high, -1);
1637
+ const right = strandOn(high, low, 1);
1638
+ if (left !== void 0 || right !== void 0) {
1639
+ strandEdges = {
1640
+ ...left === void 0 ? {} : { left },
1641
+ ...right === void 0 ? {} : { right }
1642
+ };
1643
+ }
1644
+ }
1645
+ return {
1646
+ layers: layerInputs,
1647
+ ...headHalfWidth === void 0 ? {} : { headHalfWidth },
1648
+ headHalfWidthApplied,
1649
+ turnOptions: {
1650
+ ...headHalfWidthApplied ? { turnTargets: { headHalfWidth } } : {},
1651
+ ...headEdges === void 0 ? {} : { headEdges },
1652
+ ...strandEdges === void 0 ? {} : { strandEdges }
1653
+ }
1654
+ };
1655
+ }
1656
+ return { add, finish };
1657
+ }
1658
+
1423
1659
  // src/binding-capture.ts
1424
1660
  function captureBindingEndpoint(channel, restValue, posedValue) {
1425
1661
  if (channel === "opacity") {
@@ -1991,6 +2227,7 @@ function meshCellsFor(w, h) {
1991
2227
  }
1992
2228
  var FACE_PLATE_CELLS = 10;
1993
2229
  var FEATURE_GRID_CELLS = 4;
2230
+ var MOUTH_GRID_CELL_PX = 32;
1994
2231
  var TURN_LATTICE_CELL_PX = 16;
1995
2232
  var GRID_MARGIN = 0.12;
1996
2233
  var HEAD_CYLINDER_RADIUS_FACTOR = 0.6 / 0.5;
@@ -2141,31 +2378,33 @@ function turnColumnMap(grid, faceCenterX, radiusX, angleX, travel) {
2141
2378
  x + boundedCylinderBend(x - faceCenterX, radiusX, theta) + slide
2142
2379
  );
2143
2380
  }
2144
- const cellFor = (v, edges) => {
2381
+ const cellFor = (x) => {
2145
2382
  let lo = 0;
2146
2383
  let hi = grid.cols - 1;
2147
2384
  while (lo < hi) {
2148
2385
  const mid = lo + hi >> 1;
2149
- if (v < edges[mid + 1]) hi = mid;
2386
+ if (x < restX[mid + 1]) hi = mid;
2150
2387
  else lo = mid + 1;
2151
2388
  }
2152
2389
  return lo;
2153
2390
  };
2154
- const sample = (edges, values, v) => {
2155
- const c = cellFor(v, edges);
2156
- const s = Math.max(
2157
- 0,
2158
- Math.min(1, (v - edges[c]) / (edges[c + 1] - edges[c]))
2159
- );
2160
- if (s === 0) return values[c];
2161
- if (s === 1) return values[c + 1];
2162
- return values[c] + (values[c + 1] - values[c]) * s;
2163
- };
2164
2391
  return {
2165
2392
  restX,
2166
2393
  warpedX,
2167
- mapX: (x) => sample(restX, warpedX, x),
2168
- invertX: (X) => sample(warpedX, restX, X)
2394
+ // Piecewise-linear read of `warpedX` over `restX`, the within-cell
2395
+ // fraction clamped to [0,1] as sampleWarpGrid clamps (s, t). A node reads
2396
+ // its own warped x: `warpedX[c] + Δ·1` is `warpedX[c + 1]` only to an
2397
+ // ulp, and the columns a bake reads are nodes.
2398
+ mapX: (x) => {
2399
+ const c = cellFor(x);
2400
+ const s = Math.max(
2401
+ 0,
2402
+ Math.min(1, (x - restX[c]) / (restX[c + 1] - restX[c]))
2403
+ );
2404
+ if (s === 0) return warpedX[c];
2405
+ if (s === 1) return warpedX[c + 1];
2406
+ return warpedX[c] + (warpedX[c + 1] - warpedX[c]) * s;
2407
+ }
2169
2408
  };
2170
2409
  }
2171
2410
  function renderedLandingX(part, vertexLanding, px, py) {
@@ -2271,7 +2510,7 @@ function plateLandingOn(plate, mapAt) {
2271
2510
  y
2272
2511
  );
2273
2512
  }
2274
- function plateGuardRowsFor(face, hairFront) {
2513
+ function plateGuardRowsFor(face, hairFront, widestY = -Infinity) {
2275
2514
  const rowsOf = (part, y) => {
2276
2515
  const { rows } = meshCellsFor(part.cropW, part.cropH);
2277
2516
  return Array.from(
@@ -2286,7 +2525,7 @@ function plateGuardRowsFor(face, hairFront) {
2286
2525
  return [
2287
2526
  ...faceRows,
2288
2527
  ...rowsOf(hairFront, hairFront.centerY).map(
2289
- (y) => Math.max(bottom, Math.min(top, y))
2528
+ (y) => Math.max(bottom, Math.min(top, Math.max(y, widestY)))
2290
2529
  )
2291
2530
  ];
2292
2531
  }
@@ -2298,7 +2537,7 @@ function plateGuardsOf(carriers, faceHalfWidth, profile, hairFront) {
2298
2537
  return {
2299
2538
  plate,
2300
2539
  edgeAt: profile === void 0 ? () => faceHalfWidth : profile.at,
2301
- plateGuardRows: plateGuardRowsFor(plate.part, hairFront)
2540
+ plateGuardRows: plateGuardRowsFor(plate.part, hairFront, profile?.widestY)
2302
2541
  };
2303
2542
  }
2304
2543
  var TurnTargetError = class extends Error {
@@ -2476,14 +2715,15 @@ var TURN_SWEEP_SAMPLES = 32;
2476
2715
  var HOLD_CLEARANCE = 1;
2477
2716
  var TURN_HOLD_SAMPLES = 16;
2478
2717
  var TURN_SETTLE_PASSES = 24;
2479
- function hairFrontHoldTarget(x, y, deg, faceCenterX, plateLandingAt, edgeAt, holdBase, holdEdge) {
2718
+ function hairFrontHoldTarget(x, y, deg, faceCenterX, plateLandingAt, edgeAt, holdBase, holdEdge, widestY = -Infinity) {
2480
2719
  const local = x - faceCenterX;
2481
2720
  const dist = Math.abs(local);
2482
2721
  const side = Math.sign(local);
2483
- const painted = edgeAt(y);
2484
- if (dist <= painted) return plateLandingAt(deg, x, y);
2722
+ const row = Math.max(y, widestY);
2723
+ const painted = edgeAt(row);
2724
+ if (dist <= painted) return plateLandingAt(deg, x, row);
2485
2725
  if (dist <= holdBase) {
2486
- const inner = plateLandingAt(deg, faceCenterX + side * painted, y);
2726
+ const inner = plateLandingAt(deg, faceCenterX + side * painted, row);
2487
2727
  const u = (dist - painted) / (holdBase - painted);
2488
2728
  return inner + (faceCenterX + side * holdEdge - inner) * u;
2489
2729
  }
@@ -2588,7 +2828,8 @@ function evaluateTurnCandidate(ctx, radius) {
2588
2828
  plateLandingAt,
2589
2829
  ctx.edgeAt,
2590
2830
  hold.holdBase,
2591
- hold.holdEdgeAt(deg)
2831
+ hold.holdEdgeAt(deg),
2832
+ ctx.profile?.widestY
2592
2833
  ),
2593
2834
  leadShift
2594
2835
  );
@@ -3563,7 +3804,7 @@ function plateReachAt(plateLandingAt, faceCenterX, edgeAt, plateGuardRows, deg)
3563
3804
  )
3564
3805
  );
3565
3806
  }
3566
- function bakeHairFrontSilhouetteWarp(mesh, partX, partY, faceCenterX, plateLandingAt, edgeAt, holdBase, holdEdgeAt, plateGuardRows) {
3807
+ function bakeHairFrontSilhouetteWarp(mesh, partX, partY, faceCenterX, plateLandingAt, edgeAt, holdBase, holdEdgeAt, plateGuardRows, widestY = -Infinity) {
3567
3808
  if (holdEdgeAt(0) !== holdBase) {
3568
3809
  throw new Error(
3569
3810
  `auto-rig: bakeHairFrontSilhouetteWarp: holdEdgeAt(0) is ${holdEdgeAt(0)}, not the hold edge's own rest distance ${holdBase}, so the bangs would move in the rest pose`
@@ -3601,7 +3842,8 @@ function bakeHairFrontSilhouetteWarp(mesh, partX, partY, faceCenterX, plateLandi
3601
3842
  plateLandingAt,
3602
3843
  edgeAt,
3603
3844
  holdBase,
3604
- holdEdge
3845
+ holdEdge,
3846
+ widestY
3605
3847
  ) - x,
3606
3848
  0
3607
3849
  );
@@ -3780,7 +4022,7 @@ function defaultPoseOf(bindings) {
3780
4022
  return binding.from + (binding.to - binding.from) * t;
3781
4023
  });
3782
4024
  }
3783
- function groupGridFor(members, cells, turnShiftPx, nodShiftPx) {
4025
+ function groupGridFor(members, cells, turnShiftPx, nodShiftPx, cellPx) {
3784
4026
  let minX = Infinity;
3785
4027
  let maxX = -Infinity;
3786
4028
  let minY = Infinity;
@@ -3815,24 +4057,50 @@ function groupGridFor(members, cells, turnShiftPx, nodShiftPx) {
3815
4057
  }
3816
4058
  }
3817
4059
  }
4060
+ const extentMinX = minX;
4061
+ const extentMaxX = maxX;
3818
4062
  minX -= turnShiftPx;
3819
4063
  maxX += turnShiftPx;
3820
4064
  minY -= nodShiftPx;
3821
4065
  maxY += nodShiftPx;
3822
4066
  const marginX = (maxX - minX) * GRID_MARGIN + 1;
3823
4067
  const marginY = (maxY - minY) * GRID_MARGIN + 1;
3824
- return {
3825
- cols: cells,
3826
- rows: cells,
3827
- points: generateGridPoints(
3828
- cells,
3829
- cells,
3830
- minX - marginX,
3831
- maxX + marginX,
3832
- minY - marginY,
3833
- maxY + marginY
3834
- )
3835
- };
4068
+ const points = generateGridPoints(
4069
+ cells,
4070
+ cells,
4071
+ minX - marginX,
4072
+ maxX + marginX,
4073
+ minY - marginY,
4074
+ maxY + marginY
4075
+ );
4076
+ if (cellPx === void 0) return { cols: cells, rows: cells, points };
4077
+ const stride = cells + 1;
4078
+ const uniformXs = Array.from({ length: stride }, (_, c) => points[c * 2]);
4079
+ const ys = Array.from(
4080
+ { length: stride },
4081
+ (_, r) => points[r * stride * 2 + 1]
4082
+ );
4083
+ const xs = [uniformXs[0]];
4084
+ for (let c = 0; c < cells; c++) {
4085
+ const left = uniformXs[c];
4086
+ const right = uniformXs[c + 1];
4087
+ const from = Math.max(left, extentMinX);
4088
+ const to = Math.min(right, extentMaxX);
4089
+ if (right - left > cellPx && from < to) {
4090
+ const steps = Math.ceil((to - from) / cellPx);
4091
+ if (from > left) xs.push(from);
4092
+ for (let k = 1; k < steps; k++) {
4093
+ xs.push(from + k * (to - from) / steps);
4094
+ }
4095
+ if (to < right) xs.push(to);
4096
+ }
4097
+ xs.push(right);
4098
+ }
4099
+ const merged = [];
4100
+ for (const y of ys) {
4101
+ for (const x of xs) merged.push(x, y);
4102
+ }
4103
+ return { cols: xs.length - 1, rows: cells, points: merged };
3836
4104
  }
3837
4105
  function eyelidFoldFor(layers, layer, mesh, t) {
3838
4106
  const side = ROLE_TABLE[layer.role].eyeSide;
@@ -3933,7 +4201,8 @@ function turnSetup(layers) {
3933
4201
  own,
3934
4202
  isPlate ? FACE_PLATE_CELLS : FEATURE_GRID_CELLS,
3935
4203
  isPlate ? 0 : turnShift[turnFamily(role)],
3936
- nodShiftPx
4204
+ nodShiftPx,
4205
+ group === "mouthWarp" ? MOUTH_GRID_CELL_PX : void 0
3937
4206
  )
3938
4207
  );
3939
4208
  }
@@ -4273,7 +4542,8 @@ function generateIkiFromLayerSet(layers, canvas, options = {}) {
4273
4542
  edgeAt,
4274
4543
  holdBase,
4275
4544
  turn?.holdEdgeAt ?? (() => holdBase),
4276
- plateGuardRows
4545
+ plateGuardRows,
4546
+ profile?.widestY
4277
4547
  )
4278
4548
  );
4279
4549
  ownWarps.push(
@@ -4362,6 +4632,7 @@ function generateIkiFromLayerSet(layers, canvas, options = {}) {
4362
4632
  }
4363
4633
  export {
4364
4634
  ALPHA_BBOX_THRESHOLD,
4635
+ ALPHA_OPAQUE,
4365
4636
  ATLAS_PADDING,
4366
4637
  AddDeformer,
4367
4638
  AddPart,
@@ -4372,6 +4643,8 @@ export {
4372
4643
  DeletePart,
4373
4644
  DeletePhysicsRig,
4374
4645
  EditorDocument,
4646
+ HEAD_BAND,
4647
+ SPECK_CORE_FRACTION,
4375
4648
  SetDeformerBindings,
4376
4649
  SetDeformerParent,
4377
4650
  SetDeformerPivot,
@@ -4395,9 +4668,14 @@ export {
4395
4668
  createDefaultPart,
4396
4669
  createDefaultWarpDeformer,
4397
4670
  createGridMesh,
4671
+ createLayerSetMeasurer,
4672
+ denseCoreOf,
4398
4673
  detectAlphaBbox,
4674
+ foregroundSpan,
4399
4675
  generateIkiFromLayerSet,
4676
+ headHalfOf,
4400
4677
  interpolateGridOffsets,
4678
+ isSpeckCore,
4401
4679
  packAtlas,
4402
4680
  parseLayerRoles,
4403
4681
  upsertGridKeyform,