@willcgage/module-schematic 0.60.0 → 0.62.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
@@ -117,6 +117,15 @@ function samplePath(pts, segsPerArc = 20) {
117
117
  out.push({ x: pts[n - 1].x, y: pts[n - 1].y });
118
118
  return out;
119
119
  }
120
+ function pathLengthInches(path) {
121
+ const pts = trackPath(path);
122
+ if (!pts) return 0;
123
+ const poly = samplePath(pts);
124
+ let total = 0;
125
+ for (let i = 1; i < poly.length; i++)
126
+ total += Math.hypot(poly[i].x - poly[i - 1].x, poly[i].y - poly[i - 1].y);
127
+ return total;
128
+ }
120
129
  function trackPath(path) {
121
130
  const pts = (path ?? []).filter((p) => p && Number.isFinite(p.x) && Number.isFinite(p.y)).map((p) => ({
122
131
  x: p.x,
@@ -660,7 +669,7 @@ function buildTransition(state) {
660
669
  }
661
670
  function withPoses(endplates, overrides) {
662
671
  return endplates.map(
663
- (e) => overrides[e.id] ? { ...e, pose: overrides[e.id] } : e
672
+ (e) => overrides[e.id] ? { ...e, pose: overrides[e.id], poseAuthored: true } : e
664
673
  );
665
674
  }
666
675
  function withWidths(endplates, widths, offsets = {}) {
@@ -1682,15 +1691,43 @@ function moduleFeatures(doc) {
1682
1691
  laneA: trackLane.get(x.tracks?.[0] ?? "") ?? 0,
1683
1692
  laneB: trackLane.get(x.tracks?.[1] ?? "") ?? 1
1684
1693
  }));
1694
+ const main2Lane = trackLane.has(MAIN2_TRACK_ID) ? trackLane.get(MAIN2_TRACK_ID) : null;
1695
+ const baseLanes = [
1696
+ 0,
1697
+ main2Lane ?? (doubleMain ? 1 : 0),
1698
+ ...extraTracks.map((t) => t.lane),
1699
+ ...signals.map((s) => s.lane),
1700
+ ...crossings.flatMap((x) => [x.laneA, x.laneB]),
1701
+ ...crossovers.flatMap((x) => [x.fromLane, x.toLane])
1702
+ ];
1703
+ let upLane = Math.max(...baseLanes, 0);
1704
+ let downLane = Math.min(...baseLanes, 0);
1685
1705
  const branchConnectors = doc.endplates.filter(
1686
1706
  (e) => e.id !== "A" && e.id !== "B" && e.at && !!e.trackId && (doc.tracks ?? []).some((t) => t.id === e.trackId)
1687
1707
  ).map((e) => {
1708
+ const trk = (doc.tracks ?? []).find((t) => t.id === e.trackId);
1688
1709
  const sw = (doc.turnouts ?? []).find((t) => t.divergeTrack === e.trackId);
1710
+ const startPos = sw ? sw.pos : e.at.pos;
1711
+ const side = e.at.side === "down" ? "down" : "up";
1712
+ const lane = side === "down" ? --downLane : ++upLane;
1713
+ const runInches = pathLengthInches(trk.path) || Math.abs(e.at.pos - startPos) || FREEMO_ENDPLATE_WIDTH_MIN_INCHES;
1714
+ const toB = e.at.pos >= startPos;
1715
+ const fits = (p) => p >= 0 && p <= len;
1716
+ const ahead = startPos + runInches;
1717
+ const behind = startPos - runInches;
1718
+ const endPos = toB && fits(ahead) ? ahead : !toB && fits(behind) ? behind : fits(toB ? behind : ahead) ? toB ? behind : ahead : ahead;
1689
1719
  return {
1690
1720
  id: e.id,
1691
1721
  label: e.label ?? e.id,
1692
- posFrac: clampFrac(sw ? sw.pos : e.at.pos),
1693
- side: e.at.side === "down" ? "down" : "up"
1722
+ name: trk.trackName ?? "",
1723
+ trackId: trk.id,
1724
+ kind: e.kind === "main" ? "main" : "branch",
1725
+ posFrac: clampFrac(startPos),
1726
+ fromLane: sw ? trackLane.get(sw.onTrack) ?? 0 : 0,
1727
+ side,
1728
+ lane,
1729
+ endFrac: clampFrac(endPos),
1730
+ lengthInches: runInches
1694
1731
  };
1695
1732
  });
1696
1733
  const industries = (doc.industries ?? []).flatMap((ind) => {
@@ -1715,15 +1752,7 @@ function moduleFeatures(doc) {
1715
1752
  };
1716
1753
  });
1717
1754
  });
1718
- const main2Lane = trackLane.has(MAIN2_TRACK_ID) ? trackLane.get(MAIN2_TRACK_ID) : null;
1719
- const allLanes = [
1720
- 0,
1721
- main2Lane ?? (doubleMain ? 1 : 0),
1722
- ...extraTracks.map((t) => t.lane),
1723
- ...signals.map((s) => s.lane),
1724
- ...crossings.flatMap((x) => [x.laneA, x.laneB]),
1725
- ...crossovers.flatMap((x) => [x.fromLane, x.toLane])
1726
- ];
1755
+ const allLanes = [...baseLanes, ...branchConnectors.map((b) => b.lane)];
1727
1756
  const loop = isLoopDoc(doc);
1728
1757
  const main2 = doc.tracks.find((t) => t.id === MAIN2_TRACK_ID);
1729
1758
  const main2Positioned = !!main2 && (main2.fromPos != null || main2.toPos != null) && !loop;
@@ -2004,10 +2033,26 @@ function trackMeetsEndplateIssues(path, pose, opts) {
2004
2033
  function poseNeedsManual(geometryType) {
2005
2034
  return geometryType === "wye" || geometryType === "other";
2006
2035
  }
2036
+ function isAxialPose(p) {
2037
+ const h = norm360(p.heading);
2038
+ return Math.abs(p.y) < 1e-6 && (Math.abs(h) < 1e-6 || Math.abs(h - 180) < 1e-6);
2039
+ }
2007
2040
  function poseOverridesFromDoc(doc) {
2008
2041
  const out = {};
2009
2042
  for (const e of doc.endplates ?? []) {
2010
- if (e.id && e.pose && typeof e.pose.x === "number" && typeof e.pose.y === "number" && typeof e.pose.heading === "number") {
2043
+ if (e.id && e.pose && typeof e.pose.x === "number" && typeof e.pose.y === "number" && typeof e.pose.heading === "number" && // Only an AUTHORED pose overrides derivation (#182) — but docs written
2044
+ // before the flag existed have to keep working, so authorship is inferred
2045
+ // for the two cases that can only BE authored:
2046
+ // · a placed branch endplate (`at`) — dropping it on the board is the
2047
+ // gesture, and it has no derivable position at all;
2048
+ // · an OFF-AXIS pose on A/B — a hand-placed plate is off-axis by
2049
+ // definition (that's why it needed hand placing, e.g. a wye's B).
2050
+ // What's left is an axial A/B pose, which is exactly the shape plain
2051
+ // derivation produces — residue. Honouring it silently pins the plate so
2052
+ // it stops following the module and goes stale (FMN-0068's B sat at 48 on
2053
+ // a 47.9″ board). Anything saved from now on carries the flag, so this
2054
+ // guesswork only ever applies to old docs.
2055
+ (e.poseAuthored === true || !!e.at || !isAxialPose(e.pose))) {
2011
2056
  out[e.id] = { x: e.pose.x, y: e.pose.y, heading: e.pose.heading };
2012
2057
  }
2013
2058
  }
@@ -2065,6 +2110,7 @@ exports.moduleSections = moduleSections;
2065
2110
  exports.nextId = nextId;
2066
2111
  exports.parseXtpLibrary = parseXtpLibrary;
2067
2112
  exports.partOutlineAtFrog = partOutlineAtFrog;
2113
+ exports.pathLengthInches = pathLengthInches;
2068
2114
  exports.poseNeedsManual = poseNeedsManual;
2069
2115
  exports.poseOverridesFromDoc = poseOverridesFromDoc;
2070
2116
  exports.remapPos = remapPos;