@willcgage/module-schematic 0.81.0 → 0.83.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.d.cts CHANGED
@@ -92,6 +92,64 @@ declare const FREEMO_TRACK_SPACING_INCHES = 1.125;
92
92
  /** Free-moN §2.0 **standard**: track crossing an endplate must be "not less than
93
93
  * 4 inches from either fascia" (and perpendicular, straight and level for 4″). */
94
94
  declare const FREEMO_ENDPLATE_TRACK_FASCIA_CLEARANCE_INCHES = 4;
95
+ /**
96
+ * A stretch where a pair of parallel tracks runs at something other than the
97
+ * standard spacing, because a rigid assembly built to another spacing sits in
98
+ * it (#180).
99
+ *
100
+ * The case this exists for: a crossover fixture is machined for ONE track
101
+ * spacing and cannot be built to another. The Fast Tracks N crossovers are
102
+ * 1.09″ where Free-moN §2.0 requires 1.125″, so a module with one genuinely has
103
+ * its two mains 0.035″ closer together across the crossover, opening back to
104
+ * 1.125″ at the endplates — which the standard fixes. That pinch is real, and
105
+ * drawing it is more honest than drawing a straight pair.
106
+ */
107
+ interface LanePinch {
108
+ /** The lane pulled in. Lane 0 is the reference and never moves. */
109
+ lane: number;
110
+ /** Inches from endplate A where the rigid section begins and ends. */
111
+ fromPos: number;
112
+ toPos: number;
113
+ /** Centre-to-centre spacing INSIDE that span, inches. */
114
+ spacingInches: number;
115
+ }
116
+ /**
117
+ * How far either side of a rigid section the pair takes to reach it.
118
+ *
119
+ * ⚠️ A DRAWING CONVENTION, NOT A MEASUREMENT. Nobody publishes this and it is
120
+ * not a property of any part — in the real world the builder eases the flex over
121
+ * whatever room they have. It exists so the pinch renders as a smooth deviation
122
+ * rather than a kink, and it is deliberately a round number so it never reads as
123
+ * something that was measured.
124
+ */
125
+ declare const PINCH_EASE_INCHES = 3;
126
+ /**
127
+ * The lane offset at a point, honouring any {@link LanePinch} covering it.
128
+ *
129
+ * Eases with a smoothstep, whose slope is zero at both ends, so the deviation
130
+ * leaves and rejoins the straight run tangentially — the way bent flex actually
131
+ * behaves. A linear ramp would put a visible corner at each end.
132
+ *
133
+ * ⚠️ ONLY THE PINCH'S OWN LANE MOVES. The main is the reference every Free-moN
134
+ * measurement is taken from, so it stays put and the parallel track deviates,
135
+ * which is also what a builder does.
136
+ */
137
+ declare function laneOffsetAt(lane: number, pos: number, pinches?: LanePinch[] | null, spacingInches?: number): number;
138
+ /**
139
+ * The pinches a document's crossovers impose — one per crossover connector that
140
+ * names a part whose {@link TrackPart.trackSpacing} differs from the standard.
141
+ *
142
+ * A crossover with no part named, or one built to the standard spacing, imposes
143
+ * nothing: an owner who hasn't said what they built gets the straight pair they
144
+ * had before, which is the only honest default.
145
+ */
146
+ declare function crossoverPinches(tracks: Array<{
147
+ role?: string;
148
+ lane?: number;
149
+ fromPos?: number | null;
150
+ toPos?: number | null;
151
+ crossoverPartId?: string | null;
152
+ }>, library?: TrackPart[], spacingInches?: number): LanePinch[];
95
153
  /**
96
154
  * The authored face width for an endplate, or the recommended default when a
97
155
  * module hasn't authored one. The single source of truth both apps read so a
@@ -131,6 +189,16 @@ interface SchematicTrack {
131
189
  * product's maximum piece length. Present = these are the ONLY joints, so an
132
190
  * owner's deliberate cut survives a change elsewhere on the module. */
133
191
  flexCuts?: number[] | null;
192
+ /** `role: "crossover"` only — the crossover product this connector was built
193
+ * from, a slug from the parts library.
194
+ *
195
+ * It sits on the CONNECTOR rather than on the turnouts at its ends because a
196
+ * crossover is one assembly: the fixture that set its angle also set its
197
+ * {@link TrackPart.trackSpacing}, and that spacing belongs to the pair of
198
+ * tracks, not to either turnout. Naming it is what lets the physical view draw
199
+ * the crossover at the spacing it was actually built to — the Fast Tracks N
200
+ * fixtures are 1.09″ against Free-moN's 1.125″, and the difference is real. */
201
+ crossoverPartId?: string | null;
134
202
  /** The owner's MEASURED usable length, real inches (#20) — for what the
135
203
  * drawing can't know: a bumper post short of the drawn end, a structure
136
204
  * fouling the track. Absent = derive it from the clearance points (#19). */
@@ -981,6 +1049,10 @@ interface EditorTrack {
981
1049
  path?: BenchworkPoint[];
982
1050
  /** Measured usable length, real inches (#20). Absent = derived (#19). */
983
1051
  measuredUsableInches?: number;
1052
+ /** `role: "crossover"` only — the crossover product this connector was built
1053
+ * from. Drives the spacing the pair is DRAWN at (see
1054
+ * {@link SchematicTrack.crossoverPartId}). */
1055
+ crossoverPartId?: string;
984
1056
  }
985
1057
  /** A module_tracks row as loaded for the editor. */
986
1058
  interface ModuleTrackRow {
@@ -1454,10 +1526,14 @@ interface TrackPart {
1454
1526
  /** `flex` is track sold by the length rather than as a fixed geometry — its
1455
1527
  * {@link overallLength} is the LONGEST piece you can lay from it, not a shape
1456
1528
  * (#193). */
1457
- /** ⚠️ `crossover` is an ASSEMBLY, not a single turnout two turnouts plus the
1458
- * diagonal between two parallel tracks, sold as one fixture. It therefore
1459
- * carries {@link trackSpacing}, which no single turnout has, and its
1460
- * `frogNumber` describes the two turnouts in it. */
1529
+ /** ⚠️ `crossover` is an ASSEMBLY, not a single turnout. A Fast Tracks
1530
+ * crossover fixture builds ONE SYMMETRICAL HALF; you build a second, rotate it
1531
+ * 180° and butt the two together at the through routes and the diamond to get
1532
+ * a complete DOUBLE (scissors) crossover — four turnouts, two diagonals and
1533
+ * the X where they cross. So it carries {@link trackSpacing} and
1534
+ * {@link secondaryFrogAngle}, which no single turnout has, and
1535
+ * {@link piecesPerAssembly} — because its lengths describe the HALF, not the
1536
+ * finished crossover. */
1461
1537
  kind: "turnout" | "wye" | "curved-turnout" | "crossover" | "crossing" | "flex";
1462
1538
  /** Manufacturer part numbers by hand, where the part has a hand. */
1463
1539
  partNumbers?: {
@@ -1503,6 +1579,14 @@ interface TrackPart {
1503
1579
  buildable?: boolean;
1504
1580
  /** The shortest the part can be built. Buildable parts only. */
1505
1581
  minimumLength?: PartDimension;
1582
+ /** How many identical pieces built on this fixture make ONE finished part.
1583
+ *
1584
+ * ⚠️ WHEN THIS IS SET, `overallLength` AND `minimumLength` DESCRIBE ONE PIECE,
1585
+ * NOT THE FINISHED ITEM. A Fast Tracks crossover fixture builds one half of a
1586
+ * double crossover: you build it twice, rotate the second 180°, and butt them
1587
+ * together. Its 10.07″ is the half. Absent or 1 means the fixture builds the
1588
+ * whole part in one go, which is the case for every turnout and wye here. */
1589
+ piecesPerAssembly?: number;
1506
1590
  /** The radius of plain curve this turnout can stand in for, as a
1507
1591
  * layout-planning figure. Fast Tracks publish it; Atlas do not. Nothing draws
1508
1592
  * with it — it is here because it is a real published dimension and dropping
@@ -1684,9 +1768,32 @@ declare const FAST_TRACKS_N_ME55: TrackPart[];
1684
1768
  /**
1685
1769
  * Fast Tracks N-scale CROSSOVER fixtures, ME Code 55.
1686
1770
  *
1687
- * crossover angle 2nd frog default minimum track spacing
1688
- * #6 9.46° 19° 10.07″ 9.31″ 1.09″
1689
- * #8 7.13° 14.3° 13.61″ 13.07″ 1.09″
1771
+ * crossover angle 2nd frog HALF: default minimum track spacing
1772
+ * #6 9.46° 19° 10.07″ 9.31″ 1.09″
1773
+ * #8 7.13° 14.3° 13.61″ 13.07″ 1.09″
1774
+ *
1775
+ * ⚠️⚠️ **THE LENGTHS ABOVE ARE ONE HALF, NOT THE FINISHED CROSSOVER.** Will
1776
+ * Gage, 2026-07-26: *"crossovers are two pieces. the pdf shows half, then you
1777
+ * would duplicate this same piece and flip it 180 and butt it up to the through
1778
+ * and X."* Fast Tracks say the same — *"Crossovers are constructed by building
1779
+ * two symmetrical halves of a crossover in the Assembly Fixture and then joining
1780
+ * them to form a complete double crossover"* — and their own gloss on the
1781
+ * length is "the length of the turnout on the QuickSticks", i.e. the piece the
1782
+ * fixture holds. `piecesPerAssembly: 2` records this so the number cannot be
1783
+ * read as the assembly's.
1784
+ *
1785
+ * ⚠️ THE FINISHED LENGTH IS NOT PUBLISHED, and is deliberately not stored. The
1786
+ * two halves are related by a 180° rotation about the diamond, so they cover the
1787
+ * same longitudinal span and the finished crossover is plausibly also ~10.07″ —
1788
+ * but that is an inference from the symmetry, not a reading, and this library
1789
+ * has been burned four times by exactly that kind of plausible reconstruction.
1790
+ * If it matters, measure a built one.
1791
+ *
1792
+ * ⚠️ THESE MAKE A DOUBLE (SCISSORS) CROSSOVER — four turnouts, two diagonals,
1793
+ * and the X where the diagonals cross. A half carries one full 9.46° frog and
1794
+ * HALF of the 19° diamond, which is why the diamond only exists once the second
1795
+ * piece is butted up. Not a single crossover: half a diamond is not usable on
1796
+ * its own.
1690
1797
  *
1691
1798
  * ⚠️⚠️ **THE TRACK SPACING IS 1.09″, AND FREE-moN §2.0 REQUIRES 1.125″.**
1692
1799
  * A crossover fixture is machined for ONE spacing; it is not adjustable. So a
@@ -1703,9 +1810,11 @@ declare const FAST_TRACKS_N_ME55: TrackPart[];
1703
1810
  *
1704
1811
  * ⭐ THE SECOND FROG IS EXACTLY TWICE THE FIRST — 19° against 2 × 9.46 = 18.92,
1705
1812
  * and 14.3° against 2 × 7.13 = 14.26, both inside the published rounding. That
1706
- * is the diamond in a scissors crossover, where two diagonals each leaving at
1707
- * the frog angle meet each other. It is a free cross-check on the pair, and it
1708
- * passes.
1813
+ * is the diamond, where the two halves' diagonals each leaving its main at the
1814
+ * frog angle, and pointing opposite ways because the second piece is turned
1815
+ * 180° — cross each other. It is a free cross-check on the pair, it passes, and
1816
+ * it independently corroborates the two-piece build: a part with one frog would
1817
+ * have no second angle to publish.
1709
1818
  *
1710
1819
  * ⚠️ A CROSSOVER IS AN ASSEMBLY, so `partExtent` means nothing for it and the
1711
1820
  * turnout size lookups must never see it: `kind` is `"crossover"`, and every
@@ -2046,6 +2155,7 @@ interface StoredTrackPart {
2046
2155
  secondaryFrogAngleDeg?: number | null;
2047
2156
  secondaryFrogAngleSource?: string | null;
2048
2157
  buildable?: boolean | null;
2158
+ piecesPerAssembly?: number | null;
2049
2159
  leadInches?: number | null;
2050
2160
  leadSource?: string | null;
2051
2161
  outerRadiusInches?: number | null;
@@ -2388,4 +2498,4 @@ declare function poseOverridesFromDoc(doc: ModuleSchematicDoc): Record<string, {
2388
2498
  heading: number;
2389
2499
  }>;
2390
2500
 
2391
- export { ATLAS_CODE55_N, BUILT_IN_TRACK_PARTS, type BenchworkPoint, type BranchConnector, CLEARANCE_SPACING_INCHES, CODE55_RAIL_HEIGHT_INCHES, DEFAULT_FLEX_PART_ID, type DimensionSource, type DrawCrossing, type DrawCrossover, type DrawIndustry, type DrawSignal, type DrawTrack, type DrawTurnout, ENDPLATE_FASCIA_CLEAR_INCHES, ENDPLATE_LEAD_INCHES, type EditorBranch, type EditorControlPoint, type EditorCpSignal, type EditorCrossing, type EditorIndustry, type EditorState, type EditorTrack, type EditorTurnout, type EndplateBConfig, type EndplatePose, type EndplateTrackIssue, type EndplateWidthIssue, FAST_TRACKS_N_ME55, FAST_TRACKS_N_ME55_CROSSOVERS, FLEX_TRACK_PARTS, FREEMO_ENDPLATE_TRACK_FASCIA_CLEARANCE_INCHES, FREEMO_ENDPLATE_WIDTH_MIN_INCHES, FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES, FREEMO_TRACK_SPACING_INCHES, type FlexPiece, type FlexPieceEnd, type FrogCasting, type GeometryType, type ImportedPart, type IndustryLabelMode, type IndustrySpot, MAIN2_TRACK_ID, MAIN_TRACK_ID, type ModuleFeatures, type ModuleFootprint, type ModuleFootprintInput, type ModuleGeometryInput, type ModuleSchematicDoc, type ModuleTrackRow, N_CAR_LENGTH_INCHES, N_SCALE_RATIO, type OccupiedSpan, type OutlineFace, type PartAngle, type PartDimension, type PartEnd, type PartExtent, type PartSegment, RAIL_GAUGE_INCHES, type ReturnLoopGeometry, type ReturnLoopShape, type SchematicBlock, type SchematicControlPoint, type SchematicCrossing, type SchematicEndplate, type SchematicEndplateTrack, type SchematicIndustry, type SchematicSection, type SchematicSignal, type SchematicTrack, type SchematicTurnout, type SectionAdjacency, type SectionEnd, type SectionEndAssessment, type SectionFootprint, type SectionJointAssessment, type SectionRelativePos, type SignalFacing, type SignalSide, type SpanOverhang, type StoredTrackPart, TIE_HALF_LENGTH_INCHES, TURNOUT_LEAD_INCHES_PER_FROG, type TrackConfig, type TrackPart, type TrackRole, type TurnoutClosure, type TurnoutKind, type UsableCapacity, WHOLE_MODULE_SECTION_ID, asModuleSchematic, assessSectionEnd, assessSectionJoint, benchworkBand, benchworkOutline, buildCrossover, buildPassingSiding, buildTransition, carCapacity, checkEndplateWidth, clearancePointPastFrogInches, deriveEndplatePoses, divergeSideForHand, docToState, emptyEditorState, endplateCentreOffsetInches, endplateFaceSegments, endplateLead, endplateTrackOffsetInches, endplateWidthInches, flexPartFor, flexParts, flexPieces, flexUsage, frogCasting, frogNumberFromName, fromSectionRelative, geometryTurnDegrees, hasNoFarEndplate, importedPartToTrackPart, inchesToScaleFeet, isLoopDoc, isTransitionTurnout, leadInchesForSize, maxFlexPieceInches, mergeImportedParts, mergeStoredParts, moduleCenterline, moduleFeatures, moduleFootprint, moduleLengthFromSections, moduleSections, nextId, parseXtpLibrary, partExtent, partExtentForSize, partOutlineAtFrog, pastFrogInchesForSize, pathLengthInches, poseNeedsManual, poseOverridesFromDoc, remapPos, resizeFlexPiece, returnLoop, sampleBenchworkOutline, samplePartSegments, samplePath, scaleFeetToInches, sectionAdjacency, sectionBand, sectionBreaksFromSections, sectionComponents, sectionFootprints, sectionNeighbours, sectionSpans, sectionSpansOrWhole, sectionedCenterline, sectionedEndPose, sliceCenterline, spanOverhang, stateToDoc, storedPartToTrackPart, toSectionRelative, trackMeetsEndplateIssues, trackPart, trackPath, turnoutClosure, turnoutFacing, turnoutOccupiedSpan, turnoutPartForSize, usableCapacity };
2501
+ export { ATLAS_CODE55_N, BUILT_IN_TRACK_PARTS, type BenchworkPoint, type BranchConnector, CLEARANCE_SPACING_INCHES, CODE55_RAIL_HEIGHT_INCHES, DEFAULT_FLEX_PART_ID, type DimensionSource, type DrawCrossing, type DrawCrossover, type DrawIndustry, type DrawSignal, type DrawTrack, type DrawTurnout, ENDPLATE_FASCIA_CLEAR_INCHES, ENDPLATE_LEAD_INCHES, type EditorBranch, type EditorControlPoint, type EditorCpSignal, type EditorCrossing, type EditorIndustry, type EditorState, type EditorTrack, type EditorTurnout, type EndplateBConfig, type EndplatePose, type EndplateTrackIssue, type EndplateWidthIssue, FAST_TRACKS_N_ME55, FAST_TRACKS_N_ME55_CROSSOVERS, FLEX_TRACK_PARTS, FREEMO_ENDPLATE_TRACK_FASCIA_CLEARANCE_INCHES, FREEMO_ENDPLATE_WIDTH_MIN_INCHES, FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES, FREEMO_TRACK_SPACING_INCHES, type FlexPiece, type FlexPieceEnd, type FrogCasting, type GeometryType, type ImportedPart, type IndustryLabelMode, type IndustrySpot, type LanePinch, MAIN2_TRACK_ID, MAIN_TRACK_ID, type ModuleFeatures, type ModuleFootprint, type ModuleFootprintInput, type ModuleGeometryInput, type ModuleSchematicDoc, type ModuleTrackRow, N_CAR_LENGTH_INCHES, N_SCALE_RATIO, type OccupiedSpan, type OutlineFace, PINCH_EASE_INCHES, type PartAngle, type PartDimension, type PartEnd, type PartExtent, type PartSegment, RAIL_GAUGE_INCHES, type ReturnLoopGeometry, type ReturnLoopShape, type SchematicBlock, type SchematicControlPoint, type SchematicCrossing, type SchematicEndplate, type SchematicEndplateTrack, type SchematicIndustry, type SchematicSection, type SchematicSignal, type SchematicTrack, type SchematicTurnout, type SectionAdjacency, type SectionEnd, type SectionEndAssessment, type SectionFootprint, type SectionJointAssessment, type SectionRelativePos, type SignalFacing, type SignalSide, type SpanOverhang, type StoredTrackPart, TIE_HALF_LENGTH_INCHES, TURNOUT_LEAD_INCHES_PER_FROG, type TrackConfig, type TrackPart, type TrackRole, type TurnoutClosure, type TurnoutKind, type UsableCapacity, WHOLE_MODULE_SECTION_ID, asModuleSchematic, assessSectionEnd, assessSectionJoint, benchworkBand, benchworkOutline, buildCrossover, buildPassingSiding, buildTransition, carCapacity, checkEndplateWidth, clearancePointPastFrogInches, crossoverPinches, deriveEndplatePoses, divergeSideForHand, docToState, emptyEditorState, endplateCentreOffsetInches, endplateFaceSegments, endplateLead, endplateTrackOffsetInches, endplateWidthInches, flexPartFor, flexParts, flexPieces, flexUsage, frogCasting, frogNumberFromName, fromSectionRelative, geometryTurnDegrees, hasNoFarEndplate, importedPartToTrackPart, inchesToScaleFeet, isLoopDoc, isTransitionTurnout, laneOffsetAt, leadInchesForSize, maxFlexPieceInches, mergeImportedParts, mergeStoredParts, moduleCenterline, moduleFeatures, moduleFootprint, moduleLengthFromSections, moduleSections, nextId, parseXtpLibrary, partExtent, partExtentForSize, partOutlineAtFrog, pastFrogInchesForSize, pathLengthInches, poseNeedsManual, poseOverridesFromDoc, remapPos, resizeFlexPiece, returnLoop, sampleBenchworkOutline, samplePartSegments, samplePath, scaleFeetToInches, sectionAdjacency, sectionBand, sectionBreaksFromSections, sectionComponents, sectionFootprints, sectionNeighbours, sectionSpans, sectionSpansOrWhole, sectionedCenterline, sectionedEndPose, sliceCenterline, spanOverhang, stateToDoc, storedPartToTrackPart, toSectionRelative, trackMeetsEndplateIssues, trackPart, trackPath, turnoutClosure, turnoutFacing, turnoutOccupiedSpan, turnoutPartForSize, usableCapacity };
package/dist/index.d.ts CHANGED
@@ -92,6 +92,64 @@ declare const FREEMO_TRACK_SPACING_INCHES = 1.125;
92
92
  /** Free-moN §2.0 **standard**: track crossing an endplate must be "not less than
93
93
  * 4 inches from either fascia" (and perpendicular, straight and level for 4″). */
94
94
  declare const FREEMO_ENDPLATE_TRACK_FASCIA_CLEARANCE_INCHES = 4;
95
+ /**
96
+ * A stretch where a pair of parallel tracks runs at something other than the
97
+ * standard spacing, because a rigid assembly built to another spacing sits in
98
+ * it (#180).
99
+ *
100
+ * The case this exists for: a crossover fixture is machined for ONE track
101
+ * spacing and cannot be built to another. The Fast Tracks N crossovers are
102
+ * 1.09″ where Free-moN §2.0 requires 1.125″, so a module with one genuinely has
103
+ * its two mains 0.035″ closer together across the crossover, opening back to
104
+ * 1.125″ at the endplates — which the standard fixes. That pinch is real, and
105
+ * drawing it is more honest than drawing a straight pair.
106
+ */
107
+ interface LanePinch {
108
+ /** The lane pulled in. Lane 0 is the reference and never moves. */
109
+ lane: number;
110
+ /** Inches from endplate A where the rigid section begins and ends. */
111
+ fromPos: number;
112
+ toPos: number;
113
+ /** Centre-to-centre spacing INSIDE that span, inches. */
114
+ spacingInches: number;
115
+ }
116
+ /**
117
+ * How far either side of a rigid section the pair takes to reach it.
118
+ *
119
+ * ⚠️ A DRAWING CONVENTION, NOT A MEASUREMENT. Nobody publishes this and it is
120
+ * not a property of any part — in the real world the builder eases the flex over
121
+ * whatever room they have. It exists so the pinch renders as a smooth deviation
122
+ * rather than a kink, and it is deliberately a round number so it never reads as
123
+ * something that was measured.
124
+ */
125
+ declare const PINCH_EASE_INCHES = 3;
126
+ /**
127
+ * The lane offset at a point, honouring any {@link LanePinch} covering it.
128
+ *
129
+ * Eases with a smoothstep, whose slope is zero at both ends, so the deviation
130
+ * leaves and rejoins the straight run tangentially — the way bent flex actually
131
+ * behaves. A linear ramp would put a visible corner at each end.
132
+ *
133
+ * ⚠️ ONLY THE PINCH'S OWN LANE MOVES. The main is the reference every Free-moN
134
+ * measurement is taken from, so it stays put and the parallel track deviates,
135
+ * which is also what a builder does.
136
+ */
137
+ declare function laneOffsetAt(lane: number, pos: number, pinches?: LanePinch[] | null, spacingInches?: number): number;
138
+ /**
139
+ * The pinches a document's crossovers impose — one per crossover connector that
140
+ * names a part whose {@link TrackPart.trackSpacing} differs from the standard.
141
+ *
142
+ * A crossover with no part named, or one built to the standard spacing, imposes
143
+ * nothing: an owner who hasn't said what they built gets the straight pair they
144
+ * had before, which is the only honest default.
145
+ */
146
+ declare function crossoverPinches(tracks: Array<{
147
+ role?: string;
148
+ lane?: number;
149
+ fromPos?: number | null;
150
+ toPos?: number | null;
151
+ crossoverPartId?: string | null;
152
+ }>, library?: TrackPart[], spacingInches?: number): LanePinch[];
95
153
  /**
96
154
  * The authored face width for an endplate, or the recommended default when a
97
155
  * module hasn't authored one. The single source of truth both apps read so a
@@ -131,6 +189,16 @@ interface SchematicTrack {
131
189
  * product's maximum piece length. Present = these are the ONLY joints, so an
132
190
  * owner's deliberate cut survives a change elsewhere on the module. */
133
191
  flexCuts?: number[] | null;
192
+ /** `role: "crossover"` only — the crossover product this connector was built
193
+ * from, a slug from the parts library.
194
+ *
195
+ * It sits on the CONNECTOR rather than on the turnouts at its ends because a
196
+ * crossover is one assembly: the fixture that set its angle also set its
197
+ * {@link TrackPart.trackSpacing}, and that spacing belongs to the pair of
198
+ * tracks, not to either turnout. Naming it is what lets the physical view draw
199
+ * the crossover at the spacing it was actually built to — the Fast Tracks N
200
+ * fixtures are 1.09″ against Free-moN's 1.125″, and the difference is real. */
201
+ crossoverPartId?: string | null;
134
202
  /** The owner's MEASURED usable length, real inches (#20) — for what the
135
203
  * drawing can't know: a bumper post short of the drawn end, a structure
136
204
  * fouling the track. Absent = derive it from the clearance points (#19). */
@@ -981,6 +1049,10 @@ interface EditorTrack {
981
1049
  path?: BenchworkPoint[];
982
1050
  /** Measured usable length, real inches (#20). Absent = derived (#19). */
983
1051
  measuredUsableInches?: number;
1052
+ /** `role: "crossover"` only — the crossover product this connector was built
1053
+ * from. Drives the spacing the pair is DRAWN at (see
1054
+ * {@link SchematicTrack.crossoverPartId}). */
1055
+ crossoverPartId?: string;
984
1056
  }
985
1057
  /** A module_tracks row as loaded for the editor. */
986
1058
  interface ModuleTrackRow {
@@ -1454,10 +1526,14 @@ interface TrackPart {
1454
1526
  /** `flex` is track sold by the length rather than as a fixed geometry — its
1455
1527
  * {@link overallLength} is the LONGEST piece you can lay from it, not a shape
1456
1528
  * (#193). */
1457
- /** ⚠️ `crossover` is an ASSEMBLY, not a single turnout two turnouts plus the
1458
- * diagonal between two parallel tracks, sold as one fixture. It therefore
1459
- * carries {@link trackSpacing}, which no single turnout has, and its
1460
- * `frogNumber` describes the two turnouts in it. */
1529
+ /** ⚠️ `crossover` is an ASSEMBLY, not a single turnout. A Fast Tracks
1530
+ * crossover fixture builds ONE SYMMETRICAL HALF; you build a second, rotate it
1531
+ * 180° and butt the two together at the through routes and the diamond to get
1532
+ * a complete DOUBLE (scissors) crossover — four turnouts, two diagonals and
1533
+ * the X where they cross. So it carries {@link trackSpacing} and
1534
+ * {@link secondaryFrogAngle}, which no single turnout has, and
1535
+ * {@link piecesPerAssembly} — because its lengths describe the HALF, not the
1536
+ * finished crossover. */
1461
1537
  kind: "turnout" | "wye" | "curved-turnout" | "crossover" | "crossing" | "flex";
1462
1538
  /** Manufacturer part numbers by hand, where the part has a hand. */
1463
1539
  partNumbers?: {
@@ -1503,6 +1579,14 @@ interface TrackPart {
1503
1579
  buildable?: boolean;
1504
1580
  /** The shortest the part can be built. Buildable parts only. */
1505
1581
  minimumLength?: PartDimension;
1582
+ /** How many identical pieces built on this fixture make ONE finished part.
1583
+ *
1584
+ * ⚠️ WHEN THIS IS SET, `overallLength` AND `minimumLength` DESCRIBE ONE PIECE,
1585
+ * NOT THE FINISHED ITEM. A Fast Tracks crossover fixture builds one half of a
1586
+ * double crossover: you build it twice, rotate the second 180°, and butt them
1587
+ * together. Its 10.07″ is the half. Absent or 1 means the fixture builds the
1588
+ * whole part in one go, which is the case for every turnout and wye here. */
1589
+ piecesPerAssembly?: number;
1506
1590
  /** The radius of plain curve this turnout can stand in for, as a
1507
1591
  * layout-planning figure. Fast Tracks publish it; Atlas do not. Nothing draws
1508
1592
  * with it — it is here because it is a real published dimension and dropping
@@ -1684,9 +1768,32 @@ declare const FAST_TRACKS_N_ME55: TrackPart[];
1684
1768
  /**
1685
1769
  * Fast Tracks N-scale CROSSOVER fixtures, ME Code 55.
1686
1770
  *
1687
- * crossover angle 2nd frog default minimum track spacing
1688
- * #6 9.46° 19° 10.07″ 9.31″ 1.09″
1689
- * #8 7.13° 14.3° 13.61″ 13.07″ 1.09″
1771
+ * crossover angle 2nd frog HALF: default minimum track spacing
1772
+ * #6 9.46° 19° 10.07″ 9.31″ 1.09″
1773
+ * #8 7.13° 14.3° 13.61″ 13.07″ 1.09″
1774
+ *
1775
+ * ⚠️⚠️ **THE LENGTHS ABOVE ARE ONE HALF, NOT THE FINISHED CROSSOVER.** Will
1776
+ * Gage, 2026-07-26: *"crossovers are two pieces. the pdf shows half, then you
1777
+ * would duplicate this same piece and flip it 180 and butt it up to the through
1778
+ * and X."* Fast Tracks say the same — *"Crossovers are constructed by building
1779
+ * two symmetrical halves of a crossover in the Assembly Fixture and then joining
1780
+ * them to form a complete double crossover"* — and their own gloss on the
1781
+ * length is "the length of the turnout on the QuickSticks", i.e. the piece the
1782
+ * fixture holds. `piecesPerAssembly: 2` records this so the number cannot be
1783
+ * read as the assembly's.
1784
+ *
1785
+ * ⚠️ THE FINISHED LENGTH IS NOT PUBLISHED, and is deliberately not stored. The
1786
+ * two halves are related by a 180° rotation about the diamond, so they cover the
1787
+ * same longitudinal span and the finished crossover is plausibly also ~10.07″ —
1788
+ * but that is an inference from the symmetry, not a reading, and this library
1789
+ * has been burned four times by exactly that kind of plausible reconstruction.
1790
+ * If it matters, measure a built one.
1791
+ *
1792
+ * ⚠️ THESE MAKE A DOUBLE (SCISSORS) CROSSOVER — four turnouts, two diagonals,
1793
+ * and the X where the diagonals cross. A half carries one full 9.46° frog and
1794
+ * HALF of the 19° diamond, which is why the diamond only exists once the second
1795
+ * piece is butted up. Not a single crossover: half a diamond is not usable on
1796
+ * its own.
1690
1797
  *
1691
1798
  * ⚠️⚠️ **THE TRACK SPACING IS 1.09″, AND FREE-moN §2.0 REQUIRES 1.125″.**
1692
1799
  * A crossover fixture is machined for ONE spacing; it is not adjustable. So a
@@ -1703,9 +1810,11 @@ declare const FAST_TRACKS_N_ME55: TrackPart[];
1703
1810
  *
1704
1811
  * ⭐ THE SECOND FROG IS EXACTLY TWICE THE FIRST — 19° against 2 × 9.46 = 18.92,
1705
1812
  * and 14.3° against 2 × 7.13 = 14.26, both inside the published rounding. That
1706
- * is the diamond in a scissors crossover, where two diagonals each leaving at
1707
- * the frog angle meet each other. It is a free cross-check on the pair, and it
1708
- * passes.
1813
+ * is the diamond, where the two halves' diagonals each leaving its main at the
1814
+ * frog angle, and pointing opposite ways because the second piece is turned
1815
+ * 180° — cross each other. It is a free cross-check on the pair, it passes, and
1816
+ * it independently corroborates the two-piece build: a part with one frog would
1817
+ * have no second angle to publish.
1709
1818
  *
1710
1819
  * ⚠️ A CROSSOVER IS AN ASSEMBLY, so `partExtent` means nothing for it and the
1711
1820
  * turnout size lookups must never see it: `kind` is `"crossover"`, and every
@@ -2046,6 +2155,7 @@ interface StoredTrackPart {
2046
2155
  secondaryFrogAngleDeg?: number | null;
2047
2156
  secondaryFrogAngleSource?: string | null;
2048
2157
  buildable?: boolean | null;
2158
+ piecesPerAssembly?: number | null;
2049
2159
  leadInches?: number | null;
2050
2160
  leadSource?: string | null;
2051
2161
  outerRadiusInches?: number | null;
@@ -2388,4 +2498,4 @@ declare function poseOverridesFromDoc(doc: ModuleSchematicDoc): Record<string, {
2388
2498
  heading: number;
2389
2499
  }>;
2390
2500
 
2391
- export { ATLAS_CODE55_N, BUILT_IN_TRACK_PARTS, type BenchworkPoint, type BranchConnector, CLEARANCE_SPACING_INCHES, CODE55_RAIL_HEIGHT_INCHES, DEFAULT_FLEX_PART_ID, type DimensionSource, type DrawCrossing, type DrawCrossover, type DrawIndustry, type DrawSignal, type DrawTrack, type DrawTurnout, ENDPLATE_FASCIA_CLEAR_INCHES, ENDPLATE_LEAD_INCHES, type EditorBranch, type EditorControlPoint, type EditorCpSignal, type EditorCrossing, type EditorIndustry, type EditorState, type EditorTrack, type EditorTurnout, type EndplateBConfig, type EndplatePose, type EndplateTrackIssue, type EndplateWidthIssue, FAST_TRACKS_N_ME55, FAST_TRACKS_N_ME55_CROSSOVERS, FLEX_TRACK_PARTS, FREEMO_ENDPLATE_TRACK_FASCIA_CLEARANCE_INCHES, FREEMO_ENDPLATE_WIDTH_MIN_INCHES, FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES, FREEMO_TRACK_SPACING_INCHES, type FlexPiece, type FlexPieceEnd, type FrogCasting, type GeometryType, type ImportedPart, type IndustryLabelMode, type IndustrySpot, MAIN2_TRACK_ID, MAIN_TRACK_ID, type ModuleFeatures, type ModuleFootprint, type ModuleFootprintInput, type ModuleGeometryInput, type ModuleSchematicDoc, type ModuleTrackRow, N_CAR_LENGTH_INCHES, N_SCALE_RATIO, type OccupiedSpan, type OutlineFace, type PartAngle, type PartDimension, type PartEnd, type PartExtent, type PartSegment, RAIL_GAUGE_INCHES, type ReturnLoopGeometry, type ReturnLoopShape, type SchematicBlock, type SchematicControlPoint, type SchematicCrossing, type SchematicEndplate, type SchematicEndplateTrack, type SchematicIndustry, type SchematicSection, type SchematicSignal, type SchematicTrack, type SchematicTurnout, type SectionAdjacency, type SectionEnd, type SectionEndAssessment, type SectionFootprint, type SectionJointAssessment, type SectionRelativePos, type SignalFacing, type SignalSide, type SpanOverhang, type StoredTrackPart, TIE_HALF_LENGTH_INCHES, TURNOUT_LEAD_INCHES_PER_FROG, type TrackConfig, type TrackPart, type TrackRole, type TurnoutClosure, type TurnoutKind, type UsableCapacity, WHOLE_MODULE_SECTION_ID, asModuleSchematic, assessSectionEnd, assessSectionJoint, benchworkBand, benchworkOutline, buildCrossover, buildPassingSiding, buildTransition, carCapacity, checkEndplateWidth, clearancePointPastFrogInches, deriveEndplatePoses, divergeSideForHand, docToState, emptyEditorState, endplateCentreOffsetInches, endplateFaceSegments, endplateLead, endplateTrackOffsetInches, endplateWidthInches, flexPartFor, flexParts, flexPieces, flexUsage, frogCasting, frogNumberFromName, fromSectionRelative, geometryTurnDegrees, hasNoFarEndplate, importedPartToTrackPart, inchesToScaleFeet, isLoopDoc, isTransitionTurnout, leadInchesForSize, maxFlexPieceInches, mergeImportedParts, mergeStoredParts, moduleCenterline, moduleFeatures, moduleFootprint, moduleLengthFromSections, moduleSections, nextId, parseXtpLibrary, partExtent, partExtentForSize, partOutlineAtFrog, pastFrogInchesForSize, pathLengthInches, poseNeedsManual, poseOverridesFromDoc, remapPos, resizeFlexPiece, returnLoop, sampleBenchworkOutline, samplePartSegments, samplePath, scaleFeetToInches, sectionAdjacency, sectionBand, sectionBreaksFromSections, sectionComponents, sectionFootprints, sectionNeighbours, sectionSpans, sectionSpansOrWhole, sectionedCenterline, sectionedEndPose, sliceCenterline, spanOverhang, stateToDoc, storedPartToTrackPart, toSectionRelative, trackMeetsEndplateIssues, trackPart, trackPath, turnoutClosure, turnoutFacing, turnoutOccupiedSpan, turnoutPartForSize, usableCapacity };
2501
+ export { ATLAS_CODE55_N, BUILT_IN_TRACK_PARTS, type BenchworkPoint, type BranchConnector, CLEARANCE_SPACING_INCHES, CODE55_RAIL_HEIGHT_INCHES, DEFAULT_FLEX_PART_ID, type DimensionSource, type DrawCrossing, type DrawCrossover, type DrawIndustry, type DrawSignal, type DrawTrack, type DrawTurnout, ENDPLATE_FASCIA_CLEAR_INCHES, ENDPLATE_LEAD_INCHES, type EditorBranch, type EditorControlPoint, type EditorCpSignal, type EditorCrossing, type EditorIndustry, type EditorState, type EditorTrack, type EditorTurnout, type EndplateBConfig, type EndplatePose, type EndplateTrackIssue, type EndplateWidthIssue, FAST_TRACKS_N_ME55, FAST_TRACKS_N_ME55_CROSSOVERS, FLEX_TRACK_PARTS, FREEMO_ENDPLATE_TRACK_FASCIA_CLEARANCE_INCHES, FREEMO_ENDPLATE_WIDTH_MIN_INCHES, FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES, FREEMO_TRACK_SPACING_INCHES, type FlexPiece, type FlexPieceEnd, type FrogCasting, type GeometryType, type ImportedPart, type IndustryLabelMode, type IndustrySpot, type LanePinch, MAIN2_TRACK_ID, MAIN_TRACK_ID, type ModuleFeatures, type ModuleFootprint, type ModuleFootprintInput, type ModuleGeometryInput, type ModuleSchematicDoc, type ModuleTrackRow, N_CAR_LENGTH_INCHES, N_SCALE_RATIO, type OccupiedSpan, type OutlineFace, PINCH_EASE_INCHES, type PartAngle, type PartDimension, type PartEnd, type PartExtent, type PartSegment, RAIL_GAUGE_INCHES, type ReturnLoopGeometry, type ReturnLoopShape, type SchematicBlock, type SchematicControlPoint, type SchematicCrossing, type SchematicEndplate, type SchematicEndplateTrack, type SchematicIndustry, type SchematicSection, type SchematicSignal, type SchematicTrack, type SchematicTurnout, type SectionAdjacency, type SectionEnd, type SectionEndAssessment, type SectionFootprint, type SectionJointAssessment, type SectionRelativePos, type SignalFacing, type SignalSide, type SpanOverhang, type StoredTrackPart, TIE_HALF_LENGTH_INCHES, TURNOUT_LEAD_INCHES_PER_FROG, type TrackConfig, type TrackPart, type TrackRole, type TurnoutClosure, type TurnoutKind, type UsableCapacity, WHOLE_MODULE_SECTION_ID, asModuleSchematic, assessSectionEnd, assessSectionJoint, benchworkBand, benchworkOutline, buildCrossover, buildPassingSiding, buildTransition, carCapacity, checkEndplateWidth, clearancePointPastFrogInches, crossoverPinches, deriveEndplatePoses, divergeSideForHand, docToState, emptyEditorState, endplateCentreOffsetInches, endplateFaceSegments, endplateLead, endplateTrackOffsetInches, endplateWidthInches, flexPartFor, flexParts, flexPieces, flexUsage, frogCasting, frogNumberFromName, fromSectionRelative, geometryTurnDegrees, hasNoFarEndplate, importedPartToTrackPart, inchesToScaleFeet, isLoopDoc, isTransitionTurnout, laneOffsetAt, leadInchesForSize, maxFlexPieceInches, mergeImportedParts, mergeStoredParts, moduleCenterline, moduleFeatures, moduleFootprint, moduleLengthFromSections, moduleSections, nextId, parseXtpLibrary, partExtent, partExtentForSize, partOutlineAtFrog, pastFrogInchesForSize, pathLengthInches, poseNeedsManual, poseOverridesFromDoc, remapPos, resizeFlexPiece, returnLoop, sampleBenchworkOutline, samplePartSegments, samplePath, scaleFeetToInches, sectionAdjacency, sectionBand, sectionBreaksFromSections, sectionComponents, sectionFootprints, sectionNeighbours, sectionSpans, sectionSpansOrWhole, sectionedCenterline, sectionedEndPose, sliceCenterline, spanOverhang, stateToDoc, storedPartToTrackPart, toSectionRelative, trackMeetsEndplateIssues, trackPart, trackPath, turnoutClosure, turnoutFacing, turnoutOccupiedSpan, turnoutPartForSize, usableCapacity };
package/dist/index.js CHANGED
@@ -3,6 +3,46 @@ var FREEMO_ENDPLATE_WIDTH_MIN_INCHES = 12;
3
3
  var FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES = 24;
4
4
  var FREEMO_TRACK_SPACING_INCHES = 1.125;
5
5
  var FREEMO_ENDPLATE_TRACK_FASCIA_CLEARANCE_INCHES = 4;
6
+ var PINCH_EASE_INCHES = 3;
7
+ function laneOffsetAt(lane, pos, pinches, spacingInches = FREEMO_TRACK_SPACING_INCHES) {
8
+ const base = (lane ?? 0) * spacingInches;
9
+ if (!lane || !pinches?.length) return base;
10
+ const sign = Math.sign(lane);
11
+ const smooth = (t) => t * t * (3 - 2 * t);
12
+ let off = base;
13
+ for (const p of pinches) {
14
+ if (p.lane !== lane) continue;
15
+ const a = Math.min(p.fromPos, p.toPos);
16
+ const b = Math.max(p.fromPos, p.toPos);
17
+ const target = base - sign * (spacingInches * Math.abs(lane) - p.spacingInches);
18
+ const e = PINCH_EASE_INCHES;
19
+ let t = null;
20
+ if (pos >= a && pos <= b) t = 1;
21
+ else if (pos > a - e && pos < a) t = smooth((pos - (a - e)) / e);
22
+ else if (pos > b && pos < b + e) t = smooth(1 - (pos - b) / e);
23
+ if (t == null) continue;
24
+ const candidate = base + (target - base) * t;
25
+ if (Math.abs(candidate - base) > Math.abs(off - base)) off = candidate;
26
+ }
27
+ return off;
28
+ }
29
+ function crossoverPinches(tracks, library = BUILT_IN_TRACK_PARTS, spacingInches = FREEMO_TRACK_SPACING_INCHES) {
30
+ const out = [];
31
+ for (const t of tracks) {
32
+ if (t.role !== "crossover" || !t.crossoverPartId) continue;
33
+ const part = library.find((p) => p.id === t.crossoverPartId);
34
+ const s = part?.trackSpacing?.inches;
35
+ if (typeof s !== "number" || !Number.isFinite(s) || s <= 0) continue;
36
+ if (Math.abs(s - spacingInches) < 1e-9) continue;
37
+ const lane = t.lane ?? 1;
38
+ if (!lane) continue;
39
+ const a = t.fromPos ?? 0;
40
+ const b = t.toPos ?? 0;
41
+ if (!Number.isFinite(a) || !Number.isFinite(b) || Math.abs(b - a) < 1e-9) continue;
42
+ out.push({ lane, fromPos: Math.min(a, b), toPos: Math.max(a, b), spacingInches: s });
43
+ }
44
+ return out;
45
+ }
6
46
  function endplateWidthInches(ep) {
7
47
  const w = ep?.widthInches;
8
48
  return typeof w === "number" && w > 0 ? w : FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES;
@@ -941,6 +981,7 @@ function stateToDoc(state, recordNumber) {
941
981
  }).scaleFeet,
942
982
  ...typeof t.measuredUsableInches === "number" && t.measuredUsableInches >= 0 ? { measuredUsableInches: t.measuredUsableInches } : {},
943
983
  ...state.loop && t.inLoop ? { inLoop: true } : {},
984
+ ...t.crossoverPartId ? { crossoverPartId: t.crossoverPartId } : {},
944
985
  ...t.path && t.path.length >= 2 ? { path: t.path } : {}
945
986
  }))
946
987
  ]),
@@ -1037,6 +1078,9 @@ function docToState(doc, fallbackLength, moduleTracks = []) {
1037
1078
  moduleTrackId,
1038
1079
  trackName: t.trackName ?? nameOf(moduleTrackId),
1039
1080
  ...t.inLoop ? { inLoop: true } : {},
1081
+ // The crossover product this connector was built from — what makes the
1082
+ // physical view draw the pair at the spacing it was really built to.
1083
+ ...typeof t.crossoverPartId === "string" && t.crossoverPartId ? { crossoverPartId: t.crossoverPartId } : {},
1040
1084
  // Authored path kept as-drawn (a physical shape, not rescaled with length).
1041
1085
  ...trackPath(t.path) ? { path: trackPath(t.path) } : {},
1042
1086
  // A MEASURED length is a real-world fact about the physical track, so it
@@ -1586,11 +1630,12 @@ var FAST_TRACKS_N_ME55_CROSSOVERS = [
1586
1630
  manufacturer: "Fast Tracks",
1587
1631
  line: "Code 55",
1588
1632
  scale: "N",
1589
- name: `#${n} Crossover`,
1633
+ name: `#${n} Double Crossover`,
1590
1634
  kind: "crossover",
1591
1635
  partNumbers: { single: `AF-N-C-${n}-ME55` },
1592
1636
  frogNumber: n,
1593
1637
  buildable: true,
1638
+ piecesPerAssembly: 2,
1594
1639
  actualAngle: { deg, source: manufacturer, note: `${spec}. atan(1/${n}) exactly.` },
1595
1640
  secondaryFrogAngle: {
1596
1641
  deg: second,
@@ -1600,9 +1645,13 @@ var FAST_TRACKS_N_ME55_CROSSOVERS = [
1600
1645
  overallLength: {
1601
1646
  inches: dflt,
1602
1647
  source: manufacturer,
1603
- note: `${spec}. The DEFAULT length of the whole assembly \u2014 a fixture, so the builder chooses.`
1648
+ note: `${spec}. \u26A0\uFE0F ONE HALF, NOT THE FINISHED CROSSOVER \u2014 the fixture builds a symmetrical half, which you build twice and butt together after turning the second 180\xB0. Fast Tracks gloss it as "the length of the turnout on the QuickSticks", i.e. the piece in the jig. The DEFAULT for that piece; it is a fixture, so the builder chooses.`
1649
+ },
1650
+ minimumLength: {
1651
+ inches: min,
1652
+ source: manufacturer,
1653
+ note: `${spec}. The shortest ONE HALF can be built \u2014 see the overall length's note.`
1604
1654
  },
1605
- minimumLength: { inches: min, source: manufacturer, note: spec },
1606
1655
  trackSpacing: {
1607
1656
  inches: spacing,
1608
1657
  source: manufacturer,
@@ -1951,6 +2000,8 @@ function storedPartToTrackPart(row) {
1951
2000
  ...note ? { note } : {}
1952
2001
  };
1953
2002
  if (row.buildable) part.buildable = true;
2003
+ if (typeof row.piecesPerAssembly === "number" && row.piecesPerAssembly > 1)
2004
+ part.piecesPerAssembly = row.piecesPerAssembly;
1954
2005
  if (lead) part.lead = lead;
1955
2006
  const outer = dim(row.outerRadiusInches, row.radiusSource);
1956
2007
  const inner = dim(row.innerRadiusInches, row.radiusSource);
@@ -2645,6 +2696,6 @@ function poseOverridesFromDoc(doc) {
2645
2696
  return out;
2646
2697
  }
2647
2698
 
2648
- export { ATLAS_CODE55_N, BUILT_IN_TRACK_PARTS, CLEARANCE_SPACING_INCHES, CODE55_RAIL_HEIGHT_INCHES, DEFAULT_FLEX_PART_ID, ENDPLATE_FASCIA_CLEAR_INCHES, ENDPLATE_LEAD_INCHES, FAST_TRACKS_N_ME55, FAST_TRACKS_N_ME55_CROSSOVERS, FLEX_TRACK_PARTS, FREEMO_ENDPLATE_TRACK_FASCIA_CLEARANCE_INCHES, FREEMO_ENDPLATE_WIDTH_MIN_INCHES, FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES, FREEMO_TRACK_SPACING_INCHES, MAIN2_TRACK_ID, MAIN_TRACK_ID, N_CAR_LENGTH_INCHES, N_SCALE_RATIO, RAIL_GAUGE_INCHES, TIE_HALF_LENGTH_INCHES, TURNOUT_LEAD_INCHES_PER_FROG, WHOLE_MODULE_SECTION_ID, asModuleSchematic, assessSectionEnd, assessSectionJoint, benchworkBand, benchworkOutline, buildCrossover, buildPassingSiding, buildTransition, carCapacity, checkEndplateWidth, clearancePointPastFrogInches, deriveEndplatePoses, divergeSideForHand, docToState, emptyEditorState, endplateCentreOffsetInches, endplateFaceSegments, endplateLead, endplateTrackOffsetInches, endplateWidthInches, flexPartFor, flexParts, flexPieces, flexUsage, frogCasting, frogNumberFromName, fromSectionRelative, geometryTurnDegrees, hasNoFarEndplate, importedPartToTrackPart, inchesToScaleFeet, isLoopDoc, isTransitionTurnout, leadInchesForSize, maxFlexPieceInches, mergeImportedParts, mergeStoredParts, moduleCenterline, moduleFeatures, moduleFootprint, moduleLengthFromSections, moduleSections, nextId, parseXtpLibrary, partExtent, partExtentForSize, partOutlineAtFrog, pastFrogInchesForSize, pathLengthInches, poseNeedsManual, poseOverridesFromDoc, remapPos, resizeFlexPiece, returnLoop, sampleBenchworkOutline, samplePartSegments, samplePath, scaleFeetToInches, sectionAdjacency, sectionBand, sectionBreaksFromSections, sectionComponents, sectionFootprints, sectionNeighbours, sectionSpans, sectionSpansOrWhole, sectionedCenterline, sectionedEndPose, sliceCenterline, spanOverhang, stateToDoc, storedPartToTrackPart, toSectionRelative, trackMeetsEndplateIssues, trackPart, trackPath, turnoutClosure, turnoutFacing, turnoutOccupiedSpan, turnoutPartForSize, usableCapacity };
2699
+ export { ATLAS_CODE55_N, BUILT_IN_TRACK_PARTS, CLEARANCE_SPACING_INCHES, CODE55_RAIL_HEIGHT_INCHES, DEFAULT_FLEX_PART_ID, ENDPLATE_FASCIA_CLEAR_INCHES, ENDPLATE_LEAD_INCHES, FAST_TRACKS_N_ME55, FAST_TRACKS_N_ME55_CROSSOVERS, FLEX_TRACK_PARTS, FREEMO_ENDPLATE_TRACK_FASCIA_CLEARANCE_INCHES, FREEMO_ENDPLATE_WIDTH_MIN_INCHES, FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES, FREEMO_TRACK_SPACING_INCHES, MAIN2_TRACK_ID, MAIN_TRACK_ID, N_CAR_LENGTH_INCHES, N_SCALE_RATIO, PINCH_EASE_INCHES, RAIL_GAUGE_INCHES, TIE_HALF_LENGTH_INCHES, TURNOUT_LEAD_INCHES_PER_FROG, WHOLE_MODULE_SECTION_ID, asModuleSchematic, assessSectionEnd, assessSectionJoint, benchworkBand, benchworkOutline, buildCrossover, buildPassingSiding, buildTransition, carCapacity, checkEndplateWidth, clearancePointPastFrogInches, crossoverPinches, deriveEndplatePoses, divergeSideForHand, docToState, emptyEditorState, endplateCentreOffsetInches, endplateFaceSegments, endplateLead, endplateTrackOffsetInches, endplateWidthInches, flexPartFor, flexParts, flexPieces, flexUsage, frogCasting, frogNumberFromName, fromSectionRelative, geometryTurnDegrees, hasNoFarEndplate, importedPartToTrackPart, inchesToScaleFeet, isLoopDoc, isTransitionTurnout, laneOffsetAt, leadInchesForSize, maxFlexPieceInches, mergeImportedParts, mergeStoredParts, moduleCenterline, moduleFeatures, moduleFootprint, moduleLengthFromSections, moduleSections, nextId, parseXtpLibrary, partExtent, partExtentForSize, partOutlineAtFrog, pastFrogInchesForSize, pathLengthInches, poseNeedsManual, poseOverridesFromDoc, remapPos, resizeFlexPiece, returnLoop, sampleBenchworkOutline, samplePartSegments, samplePath, scaleFeetToInches, sectionAdjacency, sectionBand, sectionBreaksFromSections, sectionComponents, sectionFootprints, sectionNeighbours, sectionSpans, sectionSpansOrWhole, sectionedCenterline, sectionedEndPose, sliceCenterline, spanOverhang, stateToDoc, storedPartToTrackPart, toSectionRelative, trackMeetsEndplateIssues, trackPart, trackPath, turnoutClosure, turnoutFacing, turnoutOccupiedSpan, turnoutPartForSize, usableCapacity };
2649
2700
  //# sourceMappingURL=index.js.map
2650
2701
  //# sourceMappingURL=index.js.map