@willcgage/module-schematic 0.69.0 → 0.71.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
@@ -471,8 +471,12 @@ function moduleFootprint(input) {
471
471
  const widthA = endplateWidthFor(input.endplateWidths, "A");
472
472
  const widthB = endplateWidthFor(input.endplateWidths, "B");
473
473
  const authored = benchworkOutline(input);
474
- const offA = input.endplateTrackOffsets?.["A"] ?? 0;
475
- const offB = input.endplateTrackOffsets?.["B"] ?? 0;
474
+ const offOf = (i, id) => input.endplateTrackOffsets?.[id] ?? endplateCentreOffsetInches({
475
+ config: input.endplateConfigs?.[i],
476
+ main2Below: input.mainsSwapped === true
477
+ });
478
+ const offA = offOf(0, "A");
479
+ const offB = offOf(1, "B");
476
480
  const secs = input.sections ?? [];
477
481
  const sectionsOwnShape = secs.length > 1 || secs.some((s) => (s.outline?.length ?? 0) >= 3);
478
482
  const sectionOutlines = sectionsOwnShape ? sectionFootprints(input, {
@@ -485,9 +489,10 @@ function moduleFootprint(input) {
485
489
  return {
486
490
  centerline,
487
491
  band: benchworkBand(centerline, widthA, widthB, offA, offB),
488
- // A loop's centre-line ends at the throat, not a far endplate — keep only
489
- // endplate A's face (the far face would be a spurious plate at the throat).
490
- endplateFaces: input.loop ? endplateFaceSegments(centerline, widthA, widthB, offA, offB).slice(0, 1) : endplateFaceSegments(centerline, widthA, widthB, offA, offB),
492
+ // Only emit a face where the module actually presents one. A loop's
493
+ // centre-line ends at the THROAT, and an end of the line / pocket simply
494
+ // stops a far face there is a plate the module hasn't got (#191).
495
+ endplateFaces: hasNoFarEndplate(input) ? endplateFaceSegments(centerline, widthA, widthB, offA, offB).slice(0, 1) : endplateFaceSegments(centerline, widthA, widthB, offA, offB),
491
496
  outline: sectionOutlines.length || !authored ? null : sampleBenchworkOutline(authored),
492
497
  // The donut hole, arc-sampled — only when there's a solid outline to punch it
493
498
  // out of (a sectioned module isn't a donut). Renderers cut it from `outline`.
@@ -495,13 +500,19 @@ function moduleFootprint(input) {
495
500
  sectionOutlines
496
501
  };
497
502
  }
498
- function endplateTrackOffsetFor(config, authoredTrackOffset) {
499
- const v = -endplateTrackOffsetInches(authoredTrackOffset, config);
503
+ function endplateCentreOffsetInches(input) {
504
+ const v = -endplateTrackOffsetInches(
505
+ input.authoredTrackOffsetInches,
506
+ input.config ?? void 0,
507
+ input.main2Below
508
+ );
500
509
  return v === 0 ? 0 : v;
501
510
  }
502
- function endplateTrackOffsetInches(authored, config) {
511
+ function endplateTrackOffsetInches(authored, config, main2Below = false) {
503
512
  if (typeof authored === "number" && Number.isFinite(authored)) return authored;
504
- return config === "double" ? -FREEMO_TRACK_SPACING_INCHES / 2 : 0;
513
+ if (config !== "double") return 0;
514
+ const half = FREEMO_TRACK_SPACING_INCHES / 2;
515
+ return main2Below ? half : -half;
505
516
  }
506
517
  function checkEndplateWidth(input) {
507
518
  const width = endplateWidthInches(input);
@@ -513,8 +524,13 @@ function checkEndplateWidth(input) {
513
524
  requiredInches: FREEMO_ENDPLATE_WIDTH_MIN_INCHES
514
525
  });
515
526
  }
516
- const off = endplateTrackOffsetInches(input.trackOffsetInches, input.config ?? void 0);
517
- const centres = input.config === "double" ? [off, off + FREEMO_TRACK_SPACING_INCHES] : [off];
527
+ const off = endplateTrackOffsetInches(
528
+ input.trackOffsetInches,
529
+ input.config ?? void 0,
530
+ input.main2Below
531
+ );
532
+ const second = off + (input.main2Below ? -1 : 1) * FREEMO_TRACK_SPACING_INCHES;
533
+ const centres = input.config === "double" ? [off, second] : [off];
518
534
  const worst = Math.max(...centres.map((c) => Math.abs(c)));
519
535
  const clearance = width / 2 - worst;
520
536
  if (clearance < FREEMO_ENDPLATE_TRACK_FASCIA_CLEARANCE_INCHES) {
@@ -525,6 +541,18 @@ function checkEndplateWidth(input) {
525
541
  requiredInches: required
526
542
  });
527
543
  }
544
+ if (input.config === "double") {
545
+ const mid = (off + second) / 2;
546
+ if (Math.abs(mid) > 0.01) {
547
+ issues.push({
548
+ code: "offcentre",
549
+ message: `The two tracks sit ${round2(Math.abs(mid))}\u2033 off the centre of this endplate. The standard recommends they straddle it \u2014 Main 1 at ${round2((input.main2Below ? 1 : -1) * (FREEMO_TRACK_SPACING_INCHES / 2))}\u2033. Clear the offset to use that.`,
550
+ // Not a width problem — no wider plate fixes it — so hand back the
551
+ // width unchanged rather than imply one would.
552
+ requiredInches: width
553
+ });
554
+ }
555
+ }
528
556
  return issues;
529
557
  }
530
558
  var round2 = (n) => Math.round(n * 100) / 100;
@@ -533,6 +561,9 @@ function endplateWidthFor(widths, id) {
533
561
  const w = widths?.[id];
534
562
  return typeof w === "number" && w > 0 ? w : FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES;
535
563
  }
564
+ function hasNoFarEndplate(input) {
565
+ return input.geometryType === "dead_end" || input.loop === true || input.endplateConfigs?.[1] === "none";
566
+ }
536
567
  function isLoopDoc(doc) {
537
568
  return doc.loop === true;
538
569
  }
@@ -1993,7 +2024,7 @@ function deriveEndplatePoses(geo) {
1993
2024
  trackOffsets: offsetsFor(cfg(0), half)
1994
2025
  })
1995
2026
  );
1996
- const noB = geo.geometryType === "dead_end" || geo.loop === true || geo.endplateConfigs?.[1] === "none";
2027
+ const noB = hasNoFarEndplate(geo);
1997
2028
  const end = sectionedEndPose({ sections: geo.sections });
1998
2029
  if (!noB && end) {
1999
2030
  poses.push(
@@ -2173,15 +2204,16 @@ exports.deriveEndplatePoses = deriveEndplatePoses;
2173
2204
  exports.divergeSideForHand = divergeSideForHand;
2174
2205
  exports.docToState = docToState;
2175
2206
  exports.emptyEditorState = emptyEditorState;
2207
+ exports.endplateCentreOffsetInches = endplateCentreOffsetInches;
2176
2208
  exports.endplateFaceSegments = endplateFaceSegments;
2177
2209
  exports.endplateLead = endplateLead;
2178
- exports.endplateTrackOffsetFor = endplateTrackOffsetFor;
2179
2210
  exports.endplateTrackOffsetInches = endplateTrackOffsetInches;
2180
2211
  exports.endplateWidthInches = endplateWidthInches;
2181
2212
  exports.frogCasting = frogCasting;
2182
2213
  exports.frogNumberFromName = frogNumberFromName;
2183
2214
  exports.fromSectionRelative = fromSectionRelative;
2184
2215
  exports.geometryTurnDegrees = geometryTurnDegrees;
2216
+ exports.hasNoFarEndplate = hasNoFarEndplate;
2185
2217
  exports.importedPartToTrackPart = importedPartToTrackPart;
2186
2218
  exports.inchesToScaleFeet = inchesToScaleFeet;
2187
2219
  exports.isLoopDoc = isLoopDoc;