@willcgage/module-schematic 0.82.0 → 0.83.1

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 {
@@ -2426,4 +2498,4 @@ declare function poseOverridesFromDoc(doc: ModuleSchematicDoc): Record<string, {
2426
2498
  heading: number;
2427
2499
  }>;
2428
2500
 
2429
- 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 {
@@ -2426,4 +2498,4 @@ declare function poseOverridesFromDoc(doc: ModuleSchematicDoc): Record<string, {
2426
2498
  heading: number;
2427
2499
  }>;
2428
2500
 
2429
- 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,55 @@ 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
+ const pinch = {
43
+ lane,
44
+ fromPos: Math.min(a, b),
45
+ toPos: Math.max(a, b),
46
+ spacingInches: s
47
+ };
48
+ const same = out.some(
49
+ (p) => p.lane === pinch.lane && Math.abs(p.fromPos - pinch.fromPos) < 1e-9 && Math.abs(p.toPos - pinch.toPos) < 1e-9 && Math.abs(p.spacingInches - pinch.spacingInches) < 1e-9
50
+ );
51
+ if (!same) out.push(pinch);
52
+ }
53
+ return out;
54
+ }
6
55
  function endplateWidthInches(ep) {
7
56
  const w = ep?.widthInches;
8
57
  return typeof w === "number" && w > 0 ? w : FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES;
@@ -941,6 +990,7 @@ function stateToDoc(state, recordNumber) {
941
990
  }).scaleFeet,
942
991
  ...typeof t.measuredUsableInches === "number" && t.measuredUsableInches >= 0 ? { measuredUsableInches: t.measuredUsableInches } : {},
943
992
  ...state.loop && t.inLoop ? { inLoop: true } : {},
993
+ ...t.crossoverPartId ? { crossoverPartId: t.crossoverPartId } : {},
944
994
  ...t.path && t.path.length >= 2 ? { path: t.path } : {}
945
995
  }))
946
996
  ]),
@@ -1037,6 +1087,9 @@ function docToState(doc, fallbackLength, moduleTracks = []) {
1037
1087
  moduleTrackId,
1038
1088
  trackName: t.trackName ?? nameOf(moduleTrackId),
1039
1089
  ...t.inLoop ? { inLoop: true } : {},
1090
+ // The crossover product this connector was built from — what makes the
1091
+ // physical view draw the pair at the spacing it was really built to.
1092
+ ...typeof t.crossoverPartId === "string" && t.crossoverPartId ? { crossoverPartId: t.crossoverPartId } : {},
1040
1093
  // Authored path kept as-drawn (a physical shape, not rescaled with length).
1041
1094
  ...trackPath(t.path) ? { path: trackPath(t.path) } : {},
1042
1095
  // A MEASURED length is a real-world fact about the physical track, so it
@@ -2652,6 +2705,6 @@ function poseOverridesFromDoc(doc) {
2652
2705
  return out;
2653
2706
  }
2654
2707
 
2655
- 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 };
2708
+ 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 };
2656
2709
  //# sourceMappingURL=index.js.map
2657
2710
  //# sourceMappingURL=index.js.map