@willcgage/module-schematic 0.32.0 → 0.34.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
@@ -211,6 +211,24 @@ function sectionBreaksFromSections(doc) {
211
211
  const spans = sectionSpans(doc);
212
212
  return spans.slice(0, -1).map((sp) => sp.toPos);
213
213
  }
214
+ function sectionedEndPose(doc) {
215
+ const secs = moduleSections(doc).filter(
216
+ (sec) => typeof sec.lengthInches === "number" && sec.lengthInches > 0
217
+ );
218
+ if (!secs.length) return null;
219
+ let ox = 0;
220
+ let oy = 0;
221
+ let heading = 0;
222
+ for (const sec of secs) {
223
+ const local = sectionCenterlineLocal(sec);
224
+ const c = Math.cos(heading * DEG_FP);
225
+ const sn = Math.sin(heading * DEG_FP);
226
+ ox += local.endX * c - local.endY * sn;
227
+ oy += local.endX * sn + local.endY * c;
228
+ heading += local.endHeadingDeg;
229
+ }
230
+ return { x: ox, y: oy, heading };
231
+ }
214
232
  function sectionedCenterline(doc) {
215
233
  const secs = moduleSections(doc).filter(
216
234
  (sec) => typeof sec.lengthInches === "number" && sec.lengthInches > 0
@@ -537,18 +555,22 @@ function main1Track(state) {
537
555
  }
538
556
  function main2Track(state) {
539
557
  const bothDouble = state.configA === "double" && state.configB === "double";
540
- const sw = state.turnouts.find(isTransitionTurnout);
558
+ const sws = state.turnouts.filter((t) => isTransitionTurnout(t) && t.divergeTrack === MAIN2_TRACK_ID).sort((a, b) => a.pos - b.pos);
541
559
  const lane = state.mainsSwapped ? 0 : 1;
542
- if (bothDouble || !sw || sw.divergeTrack !== MAIN2_TRACK_ID) {
560
+ if (bothDouble || !sws.length) {
543
561
  return { id: MAIN2_TRACK_ID, role: "main", lane, from: "A", to: "B" };
544
562
  }
545
- return state.configA === "double" ? (
546
- // Double at A: Main 2 runs from A and ends at the turnout.
547
- { id: MAIN2_TRACK_ID, role: "main", lane, fromPos: 0, toPos: sw.pos }
548
- ) : (
549
- // Double at B: Main 2 begins at the turnout and runs to B.
550
- { id: MAIN2_TRACK_ID, role: "main", lane, fromPos: sw.pos, toPos: state.lengthInches }
551
- );
563
+ const track = (fromPos, toPos) => ({
564
+ id: MAIN2_TRACK_ID,
565
+ role: "main",
566
+ lane,
567
+ fromPos,
568
+ toPos
569
+ });
570
+ if (state.configA === "double") return track(0, sws[0].pos);
571
+ if (state.configB === "double") return track(sws[0].pos, state.lengthInches);
572
+ if (sws.length >= 2) return track(sws[0].pos, sws[sws.length - 1].pos);
573
+ return track(sws[0].pos, state.lengthInches);
552
574
  }
553
575
  function buildTransition(state) {
554
576
  const aDouble = state.configA === "double";
@@ -666,7 +688,12 @@ function stateToDoc(state, recordNumber) {
666
688
  // double) Main 2 only runs from the mainline turnout to the double end —
667
689
  // the turnout that diverges to main2 is the single source of truth for
668
690
  // where the transition sits (fd#175 / FMN-0038).
669
- ...!state.loop && (state.configA === "double" || state.configB === "double") ? [main2Track(state)] : [],
691
+ // …and on a module that's SINGLE at both ends but goes double in the
692
+ // middle to form a siding: the turnouts are what make Main 2 exist, so a
693
+ // pair of them is reason enough to emit it (#118). Without this the
694
+ // second main simply wasn't in the doc, and neither the board nor the
695
+ // dispatcher panel could draw it.
696
+ ...!state.loop && (state.configA === "double" || state.configB === "double" || state.turnouts.some((t) => isTransitionTurnout(t) && t.divergeTrack === MAIN2_TRACK_ID)) ? [main2Track(state)] : [],
670
697
  ...state.loop && state.loopReturn === "main2" ? [{ id: MAIN2_TRACK_ID, role: "main", lane: 1, fromPos: 0, toPos: state.lengthInches }] : [],
671
698
  ...state.extraTracks.map((t) => ({
672
699
  id: t.id,
@@ -1283,7 +1310,19 @@ function deriveEndplatePoses(geo) {
1283
1310
  })
1284
1311
  );
1285
1312
  const noB = geo.geometryType === "dead_end";
1286
- if (!noB) {
1313
+ const end = sectionedEndPose({ sections: geo.sections });
1314
+ if (!noB && end) {
1315
+ poses.push(
1316
+ withOverride({
1317
+ id: "B",
1318
+ x: end.x,
1319
+ y: end.y,
1320
+ heading: norm360(end.heading),
1321
+ trackConfig: cfg(1),
1322
+ trackOffsets: offsetsFor(cfg(1), half)
1323
+ })
1324
+ );
1325
+ } else if (!noB) {
1287
1326
  const turn = geometryTurnDegrees(geo.geometryType, geo.geometryDegrees);
1288
1327
  let bx;
1289
1328
  let by;
@@ -1391,6 +1430,7 @@ exports.sectionFootprints = sectionFootprints;
1391
1430
  exports.sectionNeighbours = sectionNeighbours;
1392
1431
  exports.sectionSpans = sectionSpans;
1393
1432
  exports.sectionedCenterline = sectionedCenterline;
1433
+ exports.sectionedEndPose = sectionedEndPose;
1394
1434
  exports.sliceCenterline = sliceCenterline;
1395
1435
  exports.stateToDoc = stateToDoc;
1396
1436
  exports.trackPath = trackPath;