@willcgage/module-schematic 0.92.0 → 0.93.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,8 @@ 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 FREEMO_MAIN_MIN_RADIUS_INCHES = 22;
9
+ var FREEMO_REVERSE_CURVE_STRAIGHT_INCHES = 6;
8
10
  var PINCH_EASE_INCHES = 3;
9
11
  function laneOffsetAt(lane, pos, pinches, spacingInches = FREEMO_TRACK_SPACING_INCHES) {
10
12
  const base = (lane ?? 0) * spacingInches;
@@ -2441,6 +2443,7 @@ function snapPiece(moving, others, library = BUILT_IN_TRACK_PARTS, withinInches
2441
2443
  };
2442
2444
  }
2443
2445
  var danglingDivergeWarning = (id) => `the route diverging at ${id} is not connected to anything`;
2446
+ var unreachedWarning = (id) => `${id} is not reachable from the endplate \u2014 nothing connects it`;
2444
2447
  function walkTrackGraph(graph, pieces, startAt, library = BUILT_IN_TRACK_PARTS) {
2445
2448
  const byKey = new Map(graph.joints.map((j) => [j.key, j]));
2446
2449
  const pieceById = new Map(pieces.map((p) => [p.id, p]));
@@ -2578,11 +2581,14 @@ function walkTrackGraph(graph, pieces, startAt, library = BUILT_IN_TRACK_PARTS)
2578
2581
  if (sw && !sw.divergeRoute) sw.divergeRoute = r.id;
2579
2582
  }
2580
2583
  const reached = new Set(routes.flatMap((r) => r.pieces));
2584
+ const unreached = [];
2581
2585
  for (const p of pieces)
2582
- if (!reached.has(p.id) && !graph.unplaceable.some((u) => u.piece === p.id))
2583
- warnings.push(`${p.id} is not reachable from the endplate \u2014 nothing connects it`);
2586
+ if (!reached.has(p.id) && !graph.unplaceable.some((u) => u.piece === p.id)) {
2587
+ warnings.push(unreachedWarning(p.id));
2588
+ unreached.push(p.id);
2589
+ }
2584
2590
  for (const c of graph.conflicts) warnings.push(c.reason);
2585
- return { routes, turnouts, warnings, danglingDiverges };
2591
+ return { routes, turnouts, warnings, danglingDiverges, unreached };
2586
2592
  }
2587
2593
  function resolveGraphAnchor(anchor, walk, pieces, library = BUILT_IN_TRACK_PARTS) {
2588
2594
  const piece = pieces.find((p) => p.id === anchor.piece);
@@ -2611,14 +2617,29 @@ function graphToDoc(pieces, input) {
2611
2617
  const epA = endplates[0];
2612
2618
  const epB = endplates[1];
2613
2619
  const main = walk.routes.find((r) => r.id === "main");
2614
- const lengthInches = round(main.toPos);
2615
- const branches = walk.routes.filter((r) => r.id !== "main" && r.pieces.length);
2620
+ const walk2 = input.start2 ? walkTrackGraph(graph, pieces, input.start2, library) : null;
2621
+ const main2 = walk2?.routes.find((r) => r.id === "main") ?? null;
2622
+ const claimed = new Set(walk.routes.flatMap((r) => r.pieces));
2623
+ if (main2 && main2.pieces.some((p) => claimed.has(p))) {
2624
+ warnings.push(
2625
+ "Main 2 starts on track that Main 1 already runs along \u2014 they are one run, not two"
2626
+ );
2627
+ }
2628
+ const twoMains = !!main2 && !main2.pieces.some((p) => claimed.has(p));
2629
+ const main2Lane = twoMains ? (graph.joints.find((j) => j.key === `${input.start2.piece}.${input.start2.joint}`)?.y ?? 1) >= 0 ? 1 : -1 : 0;
2630
+ const lengthInches = round(Math.max(main.toPos, twoMains ? main2.toPos : 0));
2631
+ const branches = [
2632
+ ...walk.routes.filter((r) => r.id !== "main" && r.pieces.length),
2633
+ ...twoMains ? walk2.routes.filter((r) => r.id !== "main" && r.pieces.length).map((r) => ({ ...r, id: `main2:${r.id}`, bornAt: r.bornAt })) : []
2634
+ ];
2616
2635
  const trackIdOf = /* @__PURE__ */ new Map([["main", MAIN_TRACK_ID]]);
2636
+ if (twoMains) trackIdOf.set("main2:main", MAIN2_TRACK_ID);
2617
2637
  for (const r of branches) trackIdOf.set(r.id, r.pieces[0]);
2618
2638
  const laneOf = (r) => {
2619
2639
  const side = Math.sign(r.lateral) || 1;
2620
2640
  const sameSide = branches.filter((x) => (Math.sign(x.lateral) || 1) === side).sort((a, b) => Math.abs(a.lateral) - Math.abs(b.lateral));
2621
- return side * (sameSide.indexOf(r) + 1);
2641
+ const taken = side === main2Lane ? 1 : 0;
2642
+ return side * (sameSide.indexOf(r) + 1 + taken);
2622
2643
  };
2623
2644
  const tracks = [
2624
2645
  {
@@ -2627,7 +2648,20 @@ function graphToDoc(pieces, input) {
2627
2648
  lane: 0,
2628
2649
  from: epA?.id ?? "A",
2629
2650
  ...epB ? { to: epB.id } : {}
2630
- }
2651
+ },
2652
+ ...twoMains ? [
2653
+ {
2654
+ id: MAIN2_TRACK_ID,
2655
+ role: "main",
2656
+ lane: main2Lane,
2657
+ from: epA?.id ?? "A",
2658
+ ...epB ? { to: epB.id } : {},
2659
+ // ⚠️ Its own extent, because a second main need not run the whole
2660
+ // module: on a transition module it starts or stops partway.
2661
+ fromPos: round(Math.min(main2.fromPos, main2.toPos)),
2662
+ toPos: round(Math.max(main2.fromPos, main2.toPos))
2663
+ }
2664
+ ] : []
2631
2665
  ];
2632
2666
  for (const r of branches) {
2633
2667
  const id = trackIdOf.get(r.id);
@@ -2645,9 +2679,29 @@ function graphToDoc(pieces, input) {
2645
2679
  ...meta.moduleTrackId != null ? { moduleTrackId: meta.moduleTrackId } : {}
2646
2680
  });
2647
2681
  }
2682
+ if (walk2) {
2683
+ const seen = /* @__PURE__ */ new Set([
2684
+ ...walk.routes.flatMap((r) => r.pieces),
2685
+ ...walk2.routes.flatMap((r) => r.pieces)
2686
+ ]);
2687
+ const covered = new Set([...walk.unreached, ...walk2.unreached].filter((id) => seen.has(id)));
2688
+ const drop = new Set([...covered].map(unreachedWarning));
2689
+ const d2 = new Set(walk2.danglingDiverges.map(danglingDivergeWarning));
2690
+ for (let i = warnings.length - 1; i >= 0; i--) if (drop.has(warnings[i])) warnings.splice(i, 1);
2691
+ for (const w of walk2.warnings)
2692
+ if (!d2.has(w) && !drop.has(w) && !warnings.includes(w)) warnings.push(w);
2693
+ }
2648
2694
  const pieceById = new Map(pieces.map((p) => [p.id, p]));
2649
2695
  const turnouts = [];
2650
- for (const t of walk.turnouts) {
2696
+ const allTurnouts = [
2697
+ ...walk.turnouts,
2698
+ ...twoMains ? walk2.turnouts.map((t) => ({
2699
+ ...t,
2700
+ onRoute: t.onRoute === "main" ? "main2:main" : `main2:${t.onRoute}`,
2701
+ divergeRoute: t.divergeRoute ? `main2:${t.divergeRoute}` : null
2702
+ })) : []
2703
+ ];
2704
+ for (const t of allTurnouts) {
2651
2705
  const diverge = t.divergeRoute ? trackIdOf.get(t.divergeRoute) : void 0;
2652
2706
  if (!diverge) {
2653
2707
  warnings.push(
@@ -2729,7 +2783,13 @@ function deriveGraphDoc(doc, library = BUILT_IN_TRACK_PARTS) {
2729
2783
  ...t.moduleTrackId != null ? { moduleTrackId: t.moduleTrackId } : {}
2730
2784
  };
2731
2785
  }
2732
- const out = graphToDoc(g.pieces, { startAt: g.startAt, base: doc, meta, library });
2786
+ const out = graphToDoc(g.pieces, {
2787
+ startAt: g.startAt,
2788
+ start2: g.start2 ?? null,
2789
+ base: doc,
2790
+ meta,
2791
+ library
2792
+ });
2733
2793
  return { doc: out.doc, warnings: out.warnings };
2734
2794
  }
2735
2795
  function frogCasting(cl, opts = {}) {
@@ -3378,6 +3438,8 @@ exports.FLEX_TRACK_PARTS = FLEX_TRACK_PARTS;
3378
3438
  exports.FREEMO_ENDPLATE_TRACK_FASCIA_CLEARANCE_INCHES = FREEMO_ENDPLATE_TRACK_FASCIA_CLEARANCE_INCHES;
3379
3439
  exports.FREEMO_ENDPLATE_WIDTH_MIN_INCHES = FREEMO_ENDPLATE_WIDTH_MIN_INCHES;
3380
3440
  exports.FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES = FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES;
3441
+ exports.FREEMO_MAIN_MIN_RADIUS_INCHES = FREEMO_MAIN_MIN_RADIUS_INCHES;
3442
+ exports.FREEMO_REVERSE_CURVE_STRAIGHT_INCHES = FREEMO_REVERSE_CURVE_STRAIGHT_INCHES;
3381
3443
  exports.FREEMO_TRACK_SPACING_INCHES = FREEMO_TRACK_SPACING_INCHES;
3382
3444
  exports.JOINT_SNAP_INCHES = JOINT_SNAP_INCHES;
3383
3445
  exports.MAIN2_TRACK_ID = MAIN2_TRACK_ID;