@willcgage/module-schematic 0.81.0 → 0.83.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
@@ -5,6 +5,46 @@ var FREEMO_ENDPLATE_WIDTH_MIN_INCHES = 12;
5
5
  var FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES = 24;
6
6
  var FREEMO_TRACK_SPACING_INCHES = 1.125;
7
7
  var FREEMO_ENDPLATE_TRACK_FASCIA_CLEARANCE_INCHES = 4;
8
+ var PINCH_EASE_INCHES = 3;
9
+ function laneOffsetAt(lane, pos, pinches, spacingInches = FREEMO_TRACK_SPACING_INCHES) {
10
+ const base = (lane ?? 0) * spacingInches;
11
+ if (!lane || !pinches?.length) return base;
12
+ const sign = Math.sign(lane);
13
+ const smooth = (t) => t * t * (3 - 2 * t);
14
+ let off = base;
15
+ for (const p of pinches) {
16
+ if (p.lane !== lane) continue;
17
+ const a = Math.min(p.fromPos, p.toPos);
18
+ const b = Math.max(p.fromPos, p.toPos);
19
+ const target = base - sign * (spacingInches * Math.abs(lane) - p.spacingInches);
20
+ const e = PINCH_EASE_INCHES;
21
+ let t = null;
22
+ if (pos >= a && pos <= b) t = 1;
23
+ else if (pos > a - e && pos < a) t = smooth((pos - (a - e)) / e);
24
+ else if (pos > b && pos < b + e) t = smooth(1 - (pos - b) / e);
25
+ if (t == null) continue;
26
+ const candidate = base + (target - base) * t;
27
+ if (Math.abs(candidate - base) > Math.abs(off - base)) off = candidate;
28
+ }
29
+ return off;
30
+ }
31
+ function crossoverPinches(tracks, library = BUILT_IN_TRACK_PARTS, spacingInches = FREEMO_TRACK_SPACING_INCHES) {
32
+ const out = [];
33
+ for (const t of tracks) {
34
+ if (t.role !== "crossover" || !t.crossoverPartId) continue;
35
+ const part = library.find((p) => p.id === t.crossoverPartId);
36
+ const s = part?.trackSpacing?.inches;
37
+ if (typeof s !== "number" || !Number.isFinite(s) || s <= 0) continue;
38
+ if (Math.abs(s - spacingInches) < 1e-9) continue;
39
+ const lane = t.lane ?? 1;
40
+ if (!lane) continue;
41
+ const a = t.fromPos ?? 0;
42
+ const b = t.toPos ?? 0;
43
+ if (!Number.isFinite(a) || !Number.isFinite(b) || Math.abs(b - a) < 1e-9) continue;
44
+ out.push({ lane, fromPos: Math.min(a, b), toPos: Math.max(a, b), spacingInches: s });
45
+ }
46
+ return out;
47
+ }
8
48
  function endplateWidthInches(ep) {
9
49
  const w = ep?.widthInches;
10
50
  return typeof w === "number" && w > 0 ? w : FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES;
@@ -943,6 +983,7 @@ function stateToDoc(state, recordNumber) {
943
983
  }).scaleFeet,
944
984
  ...typeof t.measuredUsableInches === "number" && t.measuredUsableInches >= 0 ? { measuredUsableInches: t.measuredUsableInches } : {},
945
985
  ...state.loop && t.inLoop ? { inLoop: true } : {},
986
+ ...t.crossoverPartId ? { crossoverPartId: t.crossoverPartId } : {},
946
987
  ...t.path && t.path.length >= 2 ? { path: t.path } : {}
947
988
  }))
948
989
  ]),
@@ -1039,6 +1080,9 @@ function docToState(doc, fallbackLength, moduleTracks = []) {
1039
1080
  moduleTrackId,
1040
1081
  trackName: t.trackName ?? nameOf(moduleTrackId),
1041
1082
  ...t.inLoop ? { inLoop: true } : {},
1083
+ // The crossover product this connector was built from — what makes the
1084
+ // physical view draw the pair at the spacing it was really built to.
1085
+ ...typeof t.crossoverPartId === "string" && t.crossoverPartId ? { crossoverPartId: t.crossoverPartId } : {},
1042
1086
  // Authored path kept as-drawn (a physical shape, not rescaled with length).
1043
1087
  ...trackPath(t.path) ? { path: trackPath(t.path) } : {},
1044
1088
  // A MEASURED length is a real-world fact about the physical track, so it
@@ -1588,11 +1632,12 @@ var FAST_TRACKS_N_ME55_CROSSOVERS = [
1588
1632
  manufacturer: "Fast Tracks",
1589
1633
  line: "Code 55",
1590
1634
  scale: "N",
1591
- name: `#${n} Crossover`,
1635
+ name: `#${n} Double Crossover`,
1592
1636
  kind: "crossover",
1593
1637
  partNumbers: { single: `AF-N-C-${n}-ME55` },
1594
1638
  frogNumber: n,
1595
1639
  buildable: true,
1640
+ piecesPerAssembly: 2,
1596
1641
  actualAngle: { deg, source: manufacturer, note: `${spec}. atan(1/${n}) exactly.` },
1597
1642
  secondaryFrogAngle: {
1598
1643
  deg: second,
@@ -1602,9 +1647,13 @@ var FAST_TRACKS_N_ME55_CROSSOVERS = [
1602
1647
  overallLength: {
1603
1648
  inches: dflt,
1604
1649
  source: manufacturer,
1605
- note: `${spec}. The DEFAULT length of the whole assembly \u2014 a fixture, so the builder chooses.`
1650
+ note: `${spec}. \u26A0\uFE0F ONE HALF, NOT THE FINISHED CROSSOVER \u2014 the fixture builds a symmetrical half, which you build twice and butt together after turning the second 180\xB0. Fast Tracks gloss it as "the length of the turnout on the QuickSticks", i.e. the piece in the jig. The DEFAULT for that piece; it is a fixture, so the builder chooses.`
1651
+ },
1652
+ minimumLength: {
1653
+ inches: min,
1654
+ source: manufacturer,
1655
+ note: `${spec}. The shortest ONE HALF can be built \u2014 see the overall length's note.`
1606
1656
  },
1607
- minimumLength: { inches: min, source: manufacturer, note: spec },
1608
1657
  trackSpacing: {
1609
1658
  inches: spacing,
1610
1659
  source: manufacturer,
@@ -1953,6 +2002,8 @@ function storedPartToTrackPart(row) {
1953
2002
  ...note ? { note } : {}
1954
2003
  };
1955
2004
  if (row.buildable) part.buildable = true;
2005
+ if (typeof row.piecesPerAssembly === "number" && row.piecesPerAssembly > 1)
2006
+ part.piecesPerAssembly = row.piecesPerAssembly;
1956
2007
  if (lead) part.lead = lead;
1957
2008
  const outer = dim(row.outerRadiusInches, row.radiusSource);
1958
2009
  const inner = dim(row.innerRadiusInches, row.radiusSource);
@@ -2665,6 +2716,7 @@ exports.MAIN2_TRACK_ID = MAIN2_TRACK_ID;
2665
2716
  exports.MAIN_TRACK_ID = MAIN_TRACK_ID;
2666
2717
  exports.N_CAR_LENGTH_INCHES = N_CAR_LENGTH_INCHES;
2667
2718
  exports.N_SCALE_RATIO = N_SCALE_RATIO;
2719
+ exports.PINCH_EASE_INCHES = PINCH_EASE_INCHES;
2668
2720
  exports.RAIL_GAUGE_INCHES = RAIL_GAUGE_INCHES;
2669
2721
  exports.TIE_HALF_LENGTH_INCHES = TIE_HALF_LENGTH_INCHES;
2670
2722
  exports.TURNOUT_LEAD_INCHES_PER_FROG = TURNOUT_LEAD_INCHES_PER_FROG;
@@ -2680,6 +2732,7 @@ exports.buildTransition = buildTransition;
2680
2732
  exports.carCapacity = carCapacity;
2681
2733
  exports.checkEndplateWidth = checkEndplateWidth;
2682
2734
  exports.clearancePointPastFrogInches = clearancePointPastFrogInches;
2735
+ exports.crossoverPinches = crossoverPinches;
2683
2736
  exports.deriveEndplatePoses = deriveEndplatePoses;
2684
2737
  exports.divergeSideForHand = divergeSideForHand;
2685
2738
  exports.docToState = docToState;
@@ -2702,6 +2755,7 @@ exports.importedPartToTrackPart = importedPartToTrackPart;
2702
2755
  exports.inchesToScaleFeet = inchesToScaleFeet;
2703
2756
  exports.isLoopDoc = isLoopDoc;
2704
2757
  exports.isTransitionTurnout = isTransitionTurnout;
2758
+ exports.laneOffsetAt = laneOffsetAt;
2705
2759
  exports.leadInchesForSize = leadInchesForSize;
2706
2760
  exports.maxFlexPieceInches = maxFlexPieceInches;
2707
2761
  exports.mergeImportedParts = mergeImportedParts;