@willcgage/module-schematic 0.75.0 → 0.77.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
@@ -31,10 +31,28 @@ function moduleSections(doc) {
31
31
  ...typeof len === "number" && Number.isFinite(len) && len > 0 ? { lengthInches: len } : {},
32
32
  ...sec.geometryType ? { geometryType: sec.geometryType } : {},
33
33
  ...typeof deg === "number" && Number.isFinite(deg) ? { geometryDegrees: deg } : {},
34
- ...typeof off === "number" && Number.isFinite(off) ? { geometryOffsetInches: off } : {}
34
+ ...typeof off === "number" && Number.isFinite(off) ? { geometryOffsetInches: off } : {},
35
+ // The board's two ends (#130). Written only when actually described —
36
+ // an absent end is an ordinary internal joint, and an empty object
37
+ // would claim the owner had said something about it.
38
+ ...sectionEnd(sec.endA) ? { endA: sectionEnd(sec.endA) } : {},
39
+ ...sectionEnd(sec.endB) ? { endB: sectionEnd(sec.endB) } : {}
35
40
  };
36
41
  });
37
42
  }
43
+ function sectionEnd(end) {
44
+ if (!end) return null;
45
+ const config = end.config === "single" || end.config === "double" || end.config === "none" ? end.config : null;
46
+ const w = end.widthInches;
47
+ const o = end.trackOffsetInches;
48
+ const out = {
49
+ ...config ? { config } : {},
50
+ ...typeof w === "number" && Number.isFinite(w) && w > 0 ? { widthInches: w } : {},
51
+ // Signed, and 0 is meaningful ("explicitly centred", #93) — keep any finite.
52
+ ...typeof o === "number" && Number.isFinite(o) ? { trackOffsetInches: o } : {}
53
+ };
54
+ return Object.keys(out).length ? out : null;
55
+ }
38
56
  function sectionFootprints(doc, derive) {
39
57
  const spans = derive ? sectionSpans(doc) : [];
40
58
  const spanOf = new Map(spans.map((sp) => [sp.id, sp]));
@@ -627,6 +645,42 @@ function usableCapacity(input) {
627
645
  scaleFeet: Math.round(inchesToScaleFeet(usable))
628
646
  };
629
647
  }
648
+ function assessSectionEnd(end, opts = {}) {
649
+ const config = end?.config ?? "none";
650
+ const widthInches = endplateWidthInches({ widthInches: end?.widthInches });
651
+ const trackOffsetInches = endplateTrackOffsetInches(
652
+ end?.trackOffsetInches,
653
+ config,
654
+ opts.main2Below
655
+ );
656
+ const described = config === "single" || config === "double";
657
+ if (!described) {
658
+ return { described: false, conforming: false, issues: [], config, widthInches, trackOffsetInches };
659
+ }
660
+ const issues = checkEndplateWidth({
661
+ widthInches: end?.widthInches,
662
+ config,
663
+ trackOffsetInches: end?.trackOffsetInches,
664
+ main2Below: opts.main2Below
665
+ });
666
+ return { described: true, conforming: issues.length === 0, issues, config, widthInches, trackOffsetInches };
667
+ }
668
+ function assessSectionJoint(west, east, opts = {}) {
669
+ const w = assessSectionEnd(west, opts);
670
+ const e = assessSectionEnd(east, opts);
671
+ let reason = null;
672
+ if (!w.described || !e.described) {
673
+ reason = "an internal joint \u2014 neither end has been described as an interface";
674
+ if (w.described !== e.described)
675
+ reason = `only the ${w.described ? "west" : "east"} side is described as an endplate`;
676
+ } else if (!w.conforming || !e.conforming) {
677
+ const bad = !w.conforming ? w : e;
678
+ reason = bad.issues[0]?.message ?? "an end doesn't meet the standard";
679
+ } else if (w.config !== e.config) {
680
+ reason = `${w.config} meets ${e.config} \u2014 the track counts differ`;
681
+ }
682
+ return { west: w, east: e, standardInterface: reason === null, reason };
683
+ }
630
684
  function spanOverhang(input) {
631
685
  const lo = Math.min(input.fromPos, input.toPos);
632
686
  const hi = Math.max(input.fromPos, input.toPos);
@@ -2318,15 +2372,21 @@ function deriveEndplatePoses(geo) {
2318
2372
  })
2319
2373
  );
2320
2374
  }
2375
+ const widthAt = (frac) => {
2376
+ const wa = endplateWidthFor(geo.endplateWidths, "A");
2377
+ const wb = endplateWidthFor(geo.endplateWidths, "B");
2378
+ return (wa * (1 - frac) + wb * frac) / 2;
2379
+ };
2321
2380
  for (const b of geo.branches ?? []) {
2322
2381
  const frac = L > 0 ? Math.min(1, Math.max(0, b.atPos / L)) : 0;
2323
2382
  const px = frac * L;
2324
2383
  const config = b.config === "double" ? "double" : "single";
2384
+ const depth = widthAt(frac);
2325
2385
  poses.push(
2326
2386
  withOverride({
2327
2387
  id: b.id,
2328
2388
  x: px,
2329
- y: 0,
2389
+ y: b.side === "down" ? -depth : depth,
2330
2390
  heading: b.side === "down" ? 270 : 90,
2331
2391
  trackConfig: config,
2332
2392
  trackOffsets: offsetsFor(config, half)
@@ -2446,6 +2506,8 @@ exports.TIE_HALF_LENGTH_INCHES = TIE_HALF_LENGTH_INCHES;
2446
2506
  exports.TURNOUT_LEAD_INCHES_PER_FROG = TURNOUT_LEAD_INCHES_PER_FROG;
2447
2507
  exports.WHOLE_MODULE_SECTION_ID = WHOLE_MODULE_SECTION_ID;
2448
2508
  exports.asModuleSchematic = asModuleSchematic;
2509
+ exports.assessSectionEnd = assessSectionEnd;
2510
+ exports.assessSectionJoint = assessSectionJoint;
2449
2511
  exports.benchworkBand = benchworkBand;
2450
2512
  exports.benchworkOutline = benchworkOutline;
2451
2513
  exports.buildCrossover = buildCrossover;