@willcgage/module-schematic 0.48.0 → 0.49.1

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);
@@ -1381,15 +1364,47 @@ function returnLoop(shape, opts) {
1381
1364
  const xBot = C.x - Math.sqrt(Math.max(0, rr * rr - (-hw - C.y) ** 2));
1382
1365
  const tTop = Math.atan2(hw - C.y, xTop - C.x);
1383
1366
  const tBot = Math.atan2(-hw - C.y, xBot - C.x);
1367
+ let end = tBot;
1368
+ while (end >= tTop) end -= 2 * Math.PI;
1384
1369
  return [
1385
1370
  { x: 0, y: hw },
1386
1371
  { x: xTop, y: hw },
1387
- ...arc(C.x, C.y, rr, tTop, tBot - 2 * Math.PI, 48).slice(1, -1),
1372
+ ...arc(C.x, C.y, rr, tTop, end, 48).slice(1, -1),
1388
1373
  { x: xBot, y: -hw },
1389
1374
  { x: 0, y: -hw }
1390
1375
  ];
1391
1376
  };
1392
1377
  const Ri = R - hw;
1378
+ if (shape === "square") {
1379
+ const xL = C.x - R - hw;
1380
+ const xR = C.x + R + hw;
1381
+ const yT = R + hw;
1382
+ const outerSquare = [
1383
+ { x: 0, y: hw },
1384
+ { x: xL, y: hw },
1385
+ { x: xL, y: yT },
1386
+ { x: xR, y: yT },
1387
+ { x: xR, y: -yT },
1388
+ { x: xL, y: -yT },
1389
+ { x: xL, y: -hw },
1390
+ { x: 0, y: -hw }
1391
+ ];
1392
+ const iHalf = (R - hw) / Math.SQRT2;
1393
+ const holeSquare = iHalf > 2 ? [
1394
+ { x: C.x - iHalf, y: iHalf },
1395
+ { x: C.x + iHalf, y: iHalf },
1396
+ { x: C.x + iHalf, y: -iHalf },
1397
+ { x: C.x - iHalf, y: -iHalf }
1398
+ ] : [];
1399
+ return {
1400
+ throat: T,
1401
+ loop: rd(loop),
1402
+ wyeLegs: [rd([T, P1]), rd([T, P2])],
1403
+ wyeHalfAngleDeg: r2(half * 180 / Math.PI),
1404
+ outlineOuter: rd(outerSquare),
1405
+ outlineInner: rd(holeSquare)
1406
+ };
1407
+ }
1393
1408
  return {
1394
1409
  throat: T,
1395
1410
  loop: rd(loop),