@willcgage/module-schematic 0.48.0 → 0.49.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.cjs CHANGED
@@ -478,6 +478,9 @@ function moduleFootprint(input) {
478
478
  // endplate A's face (the far face would be a spurious plate at the throat).
479
479
  endplateFaces: input.loop ? endplateFaceSegments(centerline, widthA, widthB, offA, offB).slice(0, 1) : endplateFaceSegments(centerline, widthA, widthB, offA, offB),
480
480
  outline: sectionOutlines.length || !authored ? null : sampleBenchworkOutline(authored),
481
+ // The donut hole, arc-sampled — only when there's a solid outline to punch it
482
+ // out of (a sectioned module isn't a donut). Renderers cut it from `outline`.
483
+ outlineInner: sectionOutlines.length || !authored || !input.outlineInner || input.outlineInner.length < 3 ? null : sampleBenchworkOutline(input.outlineInner),
481
484
  sectionOutlines
482
485
  };
483
486
  }
@@ -559,6 +562,7 @@ function emptyEditorState(lengthInches) {
559
562
  endplateWidths: {},
560
563
  endplateTrackOffsets: {},
561
564
  outline: [],
565
+ outlineInner: [],
562
566
  sectionBreaks: [],
563
567
  sections: [],
564
568
  controlPoints: [],
@@ -795,6 +799,8 @@ function stateToDoc(state, recordNumber) {
795
799
  // Benchwork footprint outline (module-local inches); only when it's a real
796
800
  // ring (≥ 3 vertices).
797
801
  ...state.outline.length >= 3 ? { outline: state.outline } : {},
802
+ // Benchwork hole (donut inner boundary), when it's a real ring.
803
+ ...state.outlineInner.length >= 3 ? { outlineInner: state.outlineInner } : {},
798
804
  // Internal section joints (inches from A), when the module has more than one.
799
805
  ...state.sectionBreaks.length ? { sectionBreaks: state.sectionBreaks } : {},
800
806
  // Sections as objects — emitted only once the owner has some, so a module
@@ -886,6 +892,11 @@ function docToState(doc, fallbackLength, moduleTracks = []) {
886
892
  y: p.y,
887
893
  ...Number.isFinite(p.bulge) && p.bulge ? { bulge: p.bulge } : {}
888
894
  }));
895
+ const outlineInner = (d.outlineInner ?? []).filter((p) => p && Number.isFinite(p.x) && Number.isFinite(p.y)).map((p) => ({
896
+ x: p.x,
897
+ y: p.y,
898
+ ...Number.isFinite(p.bulge) && p.bulge ? { bulge: p.bulge } : {}
899
+ }));
889
900
  const mainPath = trackPath(d.mainPath) ?? [];
890
901
  const main2Track_ = (d.tracks ?? []).find((t) => t.id === MAIN2_TRACK_ID);
891
902
  const main2Path = trackPath(d.main2Path ?? main2Track_?.path) ?? [];
@@ -909,6 +920,7 @@ function docToState(doc, fallbackLength, moduleTracks = []) {
909
920
  endplateWidths,
910
921
  endplateTrackOffsets,
911
922
  outline,
923
+ outlineInner,
912
924
  sectionBreaks: (d.sectionBreaks ?? []).filter((n) => Number.isFinite(n)).map((n) => sc(n)),
913
925
  sections: moduleSections(d),
914
926
  mainPath,
@@ -1334,36 +1346,7 @@ function returnLoop(shape, opts) {
1334
1346
  for (let i = 0; i <= steps; i++) out.push({ x: cx + r * Math.cos(a0 + (a12 - a0) * i / steps), y: cy + r * Math.sin(a0 + (a12 - a0) * i / steps) });
1335
1347
  return out;
1336
1348
  };
1337
- if (shape === "square") {
1338
- const s = 2 * R;
1339
- const loop2 = [
1340
- { x: L, y: 0 },
1341
- { x: L, y: R },
1342
- { x: L + s, y: R },
1343
- { x: L + s, y: -R },
1344
- { x: L, y: -R },
1345
- { x: L, y: 0 }
1346
- ];
1347
- return {
1348
- throat: T,
1349
- loop: rd(loop2),
1350
- wyeLegs: [rd([T, { x: L, y: R }]), rd([T, { x: L, y: -R }])],
1351
- wyeHalfAngleDeg: 90,
1352
- outlineOuter: rd([
1353
- { x: 0, y: hw },
1354
- { x: L + s + hw, y: hw },
1355
- { x: L + s + hw, y: -R - hw },
1356
- { x: 0, y: -R - hw }
1357
- ]),
1358
- outlineInner: rd([
1359
- { x: L + hw, y: R - hw },
1360
- { x: L + s - hw, y: R - hw },
1361
- { x: L + s - hw, y: -R + hw },
1362
- { x: L + hw, y: -R + hw }
1363
- ])
1364
- };
1365
- }
1366
- const D = shape === "circle" ? R * 1.15 : R * 1.6;
1349
+ const D = shape === "circle" || shape === "square" ? R * 1.15 : R * 1.6;
1367
1350
  const offY = shape === "offset-teardrop" ? R * 0.7 : 0;
1368
1351
  const C = { x: L + D, y: offY };
1369
1352
  const dist = Math.hypot(C.x - T.x, C.y - T.y);
@@ -1390,6 +1373,36 @@ function returnLoop(shape, opts) {
1390
1373
  ];
1391
1374
  };
1392
1375
  const Ri = R - hw;
1376
+ if (shape === "square") {
1377
+ const xL = C.x - R - hw;
1378
+ const xR = C.x + R + hw;
1379
+ const yT = R + hw;
1380
+ const outerSquare = [
1381
+ { x: 0, y: hw },
1382
+ { x: xL, y: hw },
1383
+ { x: xL, y: yT },
1384
+ { x: xR, y: yT },
1385
+ { x: xR, y: -yT },
1386
+ { x: xL, y: -yT },
1387
+ { x: xL, y: -hw },
1388
+ { x: 0, y: -hw }
1389
+ ];
1390
+ const iHalf = (R - hw) / Math.SQRT2;
1391
+ const holeSquare = iHalf > 2 ? [
1392
+ { x: C.x - iHalf, y: iHalf },
1393
+ { x: C.x + iHalf, y: iHalf },
1394
+ { x: C.x + iHalf, y: -iHalf },
1395
+ { x: C.x - iHalf, y: -iHalf }
1396
+ ] : [];
1397
+ return {
1398
+ throat: T,
1399
+ loop: rd(loop),
1400
+ wyeLegs: [rd([T, P1]), rd([T, P2])],
1401
+ wyeHalfAngleDeg: r2(half * 180 / Math.PI),
1402
+ outlineOuter: rd(outerSquare),
1403
+ outlineInner: rd(holeSquare)
1404
+ };
1405
+ }
1393
1406
  return {
1394
1407
  throat: T,
1395
1408
  loop: rd(loop),