@willcgage/module-schematic 0.13.2 → 0.14.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
@@ -1,6 +1,12 @@
1
1
  'use strict';
2
2
 
3
3
  // src/index.ts
4
+ var FREEMO_ENDPLATE_WIDTH_MIN_INCHES = 12;
5
+ var FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES = 24;
6
+ function endplateWidthInches(ep) {
7
+ const w = ep?.widthInches;
8
+ return typeof w === "number" && w > 0 ? w : FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES;
9
+ }
4
10
  function isLoopDoc(doc) {
5
11
  return doc.loop === true || doc.endplates.length === 1;
6
12
  }
@@ -32,6 +38,7 @@ function emptyEditorState(lengthInches) {
32
38
  crossings: [],
33
39
  branches: [],
34
40
  poseOverrides: {},
41
+ endplateWidths: {},
35
42
  controlPoints: []
36
43
  };
37
44
  }
@@ -41,7 +48,7 @@ function isTransitionTurnout(t) {
41
48
  function main2Track(state) {
42
49
  const bothDouble = state.configA === "double" && state.configB === "double";
43
50
  const sw = state.turnouts.find(isTransitionTurnout);
44
- if (bothDouble || !sw) {
51
+ if (bothDouble || !sw || sw.divergeTrack !== MAIN2_TRACK_ID) {
45
52
  return { id: MAIN2_TRACK_ID, role: "main", lane: 1, from: "A", to: "B" };
46
53
  }
47
54
  return state.configA === "double" ? (
@@ -93,6 +100,12 @@ function withPoses(endplates, overrides) {
93
100
  (e) => overrides[e.id] ? { ...e, pose: overrides[e.id] } : e
94
101
  );
95
102
  }
103
+ function withWidths(endplates, widths) {
104
+ return endplates.map((e) => {
105
+ const w = widths[e.id];
106
+ return typeof w === "number" && w > 0 ? { ...e, widthInches: w } : e;
107
+ });
108
+ }
96
109
  function stateToDoc(state, recordNumber) {
97
110
  return {
98
111
  version: 1,
@@ -100,42 +113,45 @@ function stateToDoc(state, recordNumber) {
100
113
  lengthInches: state.lengthInches,
101
114
  ...state.loop ? { loop: true } : {},
102
115
  ...state.loop && state.loopReturn === "main2" ? { loopReturn: "main2" } : {},
103
- endplates: withPoses(
104
- [
105
- ...state.loop ? (
106
- // Balloon loop: A is the entry. A standard endplate B on the balloon
107
- // makes it an INTERCHANGE (second route connects at the loop, e.g.
108
- // Seaford); configB "none" makes it a pure turnback.
109
- [
110
- { id: "A", label: "Entry", tracks: [{ trackId: MAIN_TRACK_ID, lane: 0, config: state.configA }] },
111
- ...state.configB !== "none" ? [{ id: "B", label: "Interchange", tracks: [{ trackId: MAIN_TRACK_ID, lane: 0, config: state.configB }] }] : []
112
- ]
113
- ) : [
114
- { id: "A", label: "West", tracks: [{ trackId: MAIN_TRACK_ID, lane: 0, config: state.configA }] },
115
- // Non-loop modules always have a real B ("none" never applies).
116
- {
117
- id: "B",
118
- label: "East",
119
- tracks: [
120
- {
121
- trackId: MAIN_TRACK_ID,
122
- lane: 0,
123
- config: state.configB === "none" ? "single" : state.configB
124
- }
116
+ endplates: withWidths(
117
+ withPoses(
118
+ [
119
+ ...state.loop ? (
120
+ // Balloon loop: A is the entry. A standard endplate B on the balloon
121
+ // makes it an INTERCHANGE (second route connects at the loop, e.g.
122
+ // Seaford); configB "none" makes it a pure turnback.
123
+ [
124
+ { id: "A", label: "Entry", tracks: [{ trackId: MAIN_TRACK_ID, lane: 0, config: state.configA }] },
125
+ ...state.configB !== "none" ? [{ id: "B", label: "Interchange", tracks: [{ trackId: MAIN_TRACK_ID, lane: 0, config: state.configB }] }] : []
125
126
  ]
126
- }
127
+ ) : [
128
+ { id: "A", label: "West", tracks: [{ trackId: MAIN_TRACK_ID, lane: 0, config: state.configA }] },
129
+ // Non-loop modules always have a real B ("none" never applies).
130
+ {
131
+ id: "B",
132
+ label: "East",
133
+ tracks: [
134
+ {
135
+ trackId: MAIN_TRACK_ID,
136
+ lane: 0,
137
+ config: state.configB === "none" ? "single" : state.configB
138
+ }
139
+ ]
140
+ }
141
+ ],
142
+ // Branch endplates C, D, … — junction connections at pos, off one side
143
+ // (#170). A set can carry several (e.g. a second railroad through).
144
+ ...state.branches.map((b, i) => ({
145
+ id: String.fromCharCode(67 + i),
146
+ // C, D, E…
147
+ label: b.label || `Branch ${i + 1}`,
148
+ tracks: [{ trackId: MAIN_TRACK_ID, lane: 0, config: b.config }],
149
+ at: { pos: b.pos, side: b.side }
150
+ }))
127
151
  ],
128
- // Branch endplates C, D, … — junction connections at pos, off one side
129
- // (#170). A set can carry several (e.g. a second railroad through).
130
- ...state.branches.map((b, i) => ({
131
- id: String.fromCharCode(67 + i),
132
- // C, D, E…
133
- label: b.label || `Branch ${i + 1}`,
134
- tracks: [{ trackId: MAIN_TRACK_ID, lane: 0, config: b.config }],
135
- at: { pos: b.pos, side: b.side }
136
- }))
137
- ],
138
- state.poseOverrides
152
+ state.poseOverrides
153
+ ),
154
+ state.endplateWidths
139
155
  ),
140
156
  tracks: [
141
157
  state.loop ? (
@@ -261,6 +277,11 @@ function docToState(doc, fallbackLength, moduleTracks = []) {
261
277
  (e) => e.id !== "A" && e.id !== "B" && e.at
262
278
  );
263
279
  const poseOverrides = poseOverridesFromDoc(d);
280
+ const endplateWidths = {};
281
+ for (const e of d.endplates ?? []) {
282
+ if (typeof e.widthInches === "number" && e.widthInches > 0)
283
+ endplateWidths[e.id] = e.widthInches;
284
+ }
264
285
  return {
265
286
  lengthInches: len,
266
287
  loop,
@@ -275,6 +296,7 @@ function docToState(doc, fallbackLength, moduleTracks = []) {
275
296
  config: ep.tracks?.[0]?.config === "double" ? "double" : "single"
276
297
  })),
277
298
  poseOverrides,
299
+ endplateWidths,
278
300
  crossings: (d.crossings ?? []).map((x) => ({
279
301
  id: x.id,
280
302
  name: x.name ?? "",
@@ -607,13 +629,20 @@ function moduleFeatures(doc) {
607
629
  fromFrac: clampFrac(main2.fromPos ?? 0),
608
630
  toFrac: clampFrac(main2.toPos ?? len)
609
631
  };
610
- } else if (main2 && transitionSw && !loop) {
632
+ } else if (main2 && transitionSw && transitionSw.divergeTrack === MAIN2_TRACK_ID && !loop) {
611
633
  main2Extent = aDbl ? { fromFrac: 0, toFrac: clampFrac(transitionSw.pos) } : { fromFrac: clampFrac(transitionSw.pos), toFrac: 1 };
612
634
  }
635
+ const transition = transitionSw && aDbl !== bDbl && !loop ? {
636
+ throughLane: trackLane.get(transitionSw.onTrack) ?? 0,
637
+ branchLane: trackLane.get(transitionSw.divergeTrack) ?? 1,
638
+ atFrac: clampFrac(transitionSw.pos),
639
+ doubleSide: aDbl ? "west" : "east"
640
+ } : null;
613
641
  return {
614
642
  doubleMain,
615
643
  loop,
616
644
  main2Extent,
645
+ transition,
617
646
  loopInterchange: loop && doc.endplates.filter((e) => !e.at).length >= 2,
618
647
  loopReturn: loop && doc.loopReturn === "main2" ? "main2" : "same",
619
648
  loopRender: loop ? doc.loopRender ?? null : null,
@@ -725,6 +754,8 @@ function poseOverridesFromDoc(doc) {
725
754
  return out;
726
755
  }
727
756
 
757
+ exports.FREEMO_ENDPLATE_WIDTH_MIN_INCHES = FREEMO_ENDPLATE_WIDTH_MIN_INCHES;
758
+ exports.FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES = FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES;
728
759
  exports.MAIN2_TRACK_ID = MAIN2_TRACK_ID;
729
760
  exports.MAIN_TRACK_ID = MAIN_TRACK_ID;
730
761
  exports.N_SCALE_RATIO = N_SCALE_RATIO;
@@ -736,6 +767,7 @@ exports.deriveEndplatePoses = deriveEndplatePoses;
736
767
  exports.divergeSideForHand = divergeSideForHand;
737
768
  exports.docToState = docToState;
738
769
  exports.emptyEditorState = emptyEditorState;
770
+ exports.endplateWidthInches = endplateWidthInches;
739
771
  exports.geometryTurnDegrees = geometryTurnDegrees;
740
772
  exports.inchesToScaleFeet = inchesToScaleFeet;
741
773
  exports.isLoopDoc = isLoopDoc;