@willcgage/module-schematic 0.82.0 → 0.83.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
@@ -5,6 +5,55 @@ 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
+ const pinch = {
45
+ lane,
46
+ fromPos: Math.min(a, b),
47
+ toPos: Math.max(a, b),
48
+ spacingInches: s
49
+ };
50
+ const same = out.some(
51
+ (p) => p.lane === pinch.lane && Math.abs(p.fromPos - pinch.fromPos) < 1e-9 && Math.abs(p.toPos - pinch.toPos) < 1e-9 && Math.abs(p.spacingInches - pinch.spacingInches) < 1e-9
52
+ );
53
+ if (!same) out.push(pinch);
54
+ }
55
+ return out;
56
+ }
8
57
  function endplateWidthInches(ep) {
9
58
  const w = ep?.widthInches;
10
59
  return typeof w === "number" && w > 0 ? w : FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES;
@@ -943,6 +992,7 @@ function stateToDoc(state, recordNumber) {
943
992
  }).scaleFeet,
944
993
  ...typeof t.measuredUsableInches === "number" && t.measuredUsableInches >= 0 ? { measuredUsableInches: t.measuredUsableInches } : {},
945
994
  ...state.loop && t.inLoop ? { inLoop: true } : {},
995
+ ...t.crossoverPartId ? { crossoverPartId: t.crossoverPartId } : {},
946
996
  ...t.path && t.path.length >= 2 ? { path: t.path } : {}
947
997
  }))
948
998
  ]),
@@ -1039,6 +1089,9 @@ function docToState(doc, fallbackLength, moduleTracks = []) {
1039
1089
  moduleTrackId,
1040
1090
  trackName: t.trackName ?? nameOf(moduleTrackId),
1041
1091
  ...t.inLoop ? { inLoop: true } : {},
1092
+ // The crossover product this connector was built from — what makes the
1093
+ // physical view draw the pair at the spacing it was really built to.
1094
+ ...typeof t.crossoverPartId === "string" && t.crossoverPartId ? { crossoverPartId: t.crossoverPartId } : {},
1042
1095
  // Authored path kept as-drawn (a physical shape, not rescaled with length).
1043
1096
  ...trackPath(t.path) ? { path: trackPath(t.path) } : {},
1044
1097
  // A MEASURED length is a real-world fact about the physical track, so it
@@ -2672,6 +2725,7 @@ exports.MAIN2_TRACK_ID = MAIN2_TRACK_ID;
2672
2725
  exports.MAIN_TRACK_ID = MAIN_TRACK_ID;
2673
2726
  exports.N_CAR_LENGTH_INCHES = N_CAR_LENGTH_INCHES;
2674
2727
  exports.N_SCALE_RATIO = N_SCALE_RATIO;
2728
+ exports.PINCH_EASE_INCHES = PINCH_EASE_INCHES;
2675
2729
  exports.RAIL_GAUGE_INCHES = RAIL_GAUGE_INCHES;
2676
2730
  exports.TIE_HALF_LENGTH_INCHES = TIE_HALF_LENGTH_INCHES;
2677
2731
  exports.TURNOUT_LEAD_INCHES_PER_FROG = TURNOUT_LEAD_INCHES_PER_FROG;
@@ -2687,6 +2741,7 @@ exports.buildTransition = buildTransition;
2687
2741
  exports.carCapacity = carCapacity;
2688
2742
  exports.checkEndplateWidth = checkEndplateWidth;
2689
2743
  exports.clearancePointPastFrogInches = clearancePointPastFrogInches;
2744
+ exports.crossoverPinches = crossoverPinches;
2690
2745
  exports.deriveEndplatePoses = deriveEndplatePoses;
2691
2746
  exports.divergeSideForHand = divergeSideForHand;
2692
2747
  exports.docToState = docToState;
@@ -2709,6 +2764,7 @@ exports.importedPartToTrackPart = importedPartToTrackPart;
2709
2764
  exports.inchesToScaleFeet = inchesToScaleFeet;
2710
2765
  exports.isLoopDoc = isLoopDoc;
2711
2766
  exports.isTransitionTurnout = isTransitionTurnout;
2767
+ exports.laneOffsetAt = laneOffsetAt;
2712
2768
  exports.leadInchesForSize = leadInchesForSize;
2713
2769
  exports.maxFlexPieceInches = maxFlexPieceInches;
2714
2770
  exports.mergeImportedParts = mergeImportedParts;