@willcgage/module-schematic 0.103.0 → 0.105.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 +256 -18
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +47 -2
- package/dist/index.d.ts +47 -2
- package/dist/index.js +256 -19
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -2480,7 +2480,12 @@ declare function turnoutClosure(size: number, opts?: {
|
|
|
2480
2480
|
easeInches?: number;
|
|
2481
2481
|
}): TurnoutClosure;
|
|
2482
2482
|
/** Which end of a part this is. A graph connects joints; a walk uses `routes`. */
|
|
2483
|
-
type PartJointRole = "throat" | "through" | "diverge" | "divergeB"
|
|
2483
|
+
type PartJointRole = "throat" | "through" | "diverge" | "divergeB"
|
|
2484
|
+
/** An end of one of the two parallel tracks THROUGH a crossover assembly.
|
|
2485
|
+
* A double crossover has four, and none of them is a throat: every end is
|
|
2486
|
+
* both a straight route and a crossing route, which is exactly why it is one
|
|
2487
|
+
* moulding and not four turnouts. */
|
|
2488
|
+
| "crossoverEnd";
|
|
2484
2489
|
/** One end of a part, in part-local inches: `x` along the through route from
|
|
2485
2490
|
* the tie end, `y` lateral toward the diverging side, `angleDeg` the OUTWARD
|
|
2486
2491
|
* tangent (0 = +x). Named, unlike {@link PartEnd}, because a graph has to know
|
|
@@ -2502,6 +2507,46 @@ interface PartGeometry {
|
|
|
2502
2507
|
* leaves the moulding at the same place the through route does. */
|
|
2503
2508
|
divergingEndMeasured: boolean;
|
|
2504
2509
|
}
|
|
2510
|
+
/**
|
|
2511
|
+
* A double crossover's geometry, every number of it PUBLISHED rather than
|
|
2512
|
+
* guessed — or null when the part does not carry them.
|
|
2513
|
+
*
|
|
2514
|
+
* ⭐⭐ **A DOUBLE CROSSOVER IS ONE ASSEMBLY, NOT FOUR TURNOUTS** (Will,
|
|
2515
|
+
* 2026-07-28). Modelling it as four independent turnout mouldings is what made a
|
|
2516
|
+
* rebuild of FMN-0078 collapse: two Atlas #7s 2.5″ apart overlap by 3.5″, so the
|
|
2517
|
+
* pieces intersected and the walk emitted a module nobody has. The real thing is
|
|
2518
|
+
* a single moulding whose four point-sets sit closer together than four separate
|
|
2519
|
+
* turnouts ever could — which is precisely why it is sold as one fixture.
|
|
2520
|
+
*
|
|
2521
|
+
* ⚠️ **ITS SPACING IS NOT A DEPARTURE FROM THE STANDARD.** Free-moN §2.0 fixes
|
|
2522
|
+
* {@link FREEMO_TRACK_SPACING_INCHES} **at the endplate** — "double track
|
|
2523
|
+
* endplates must have a track spacing of 1.125 inches", perpendicular, straight
|
|
2524
|
+
* and level for 4″ from the outside face. What the mains do in between is the
|
|
2525
|
+
* builder's business, and every real double crossover pinches them closer.
|
|
2526
|
+
*
|
|
2527
|
+
* The derivation:
|
|
2528
|
+
* - **Length** is `overallLength × piecesPerAssembly`. Fast Tracks' figure is
|
|
2529
|
+
* ONE HALF — the fixture builds a symmetrical half you make twice and butt
|
|
2530
|
+
* together after turning the second 180°.
|
|
2531
|
+
* - **The crossing run** `W = spacing / tan θ` is how far along the track a route
|
|
2532
|
+
* takes to cross to the other one. The two point-sets on the SAME track are
|
|
2533
|
+
* therefore `W` apart — 6.54″ for a #6 at 1.09″, not the 2.5″ my own FMN-0078
|
|
2534
|
+
* fixture claimed.
|
|
2535
|
+
* - Both crossing routes are centred on the assembly, so they meet in the middle
|
|
2536
|
+
* at `2θ` — which is exactly the {@link TrackPart.secondaryFrogAngle} Fast
|
|
2537
|
+
* Tracks publish, an independent check that this reading is right.
|
|
2538
|
+
*/
|
|
2539
|
+
declare function crossoverAssembly(part: TrackPart): {
|
|
2540
|
+
/** End to end along the track, both halves. */
|
|
2541
|
+
lengthInches: number;
|
|
2542
|
+
spacingInches: number;
|
|
2543
|
+
/** Along-track distance a crossing route takes to reach the other track. */
|
|
2544
|
+
crossingRunInches: number;
|
|
2545
|
+
/** Where the four point-sets sit along the assembly. */
|
|
2546
|
+
pointsAtInches: [number, number];
|
|
2547
|
+
/** The X where the two crossing routes meet — a real diamond in the middle. */
|
|
2548
|
+
scissorsAtInches: number;
|
|
2549
|
+
} | null;
|
|
2505
2550
|
/**
|
|
2506
2551
|
* Why a part has no derivable geometry — null when it has.
|
|
2507
2552
|
*
|
|
@@ -3393,4 +3438,4 @@ declare function poseOverridesFromDoc(doc: ModuleSchematicDoc): Record<string, {
|
|
|
3393
3438
|
heading: number;
|
|
3394
3439
|
}>;
|
|
3395
3440
|
|
|
3396
|
-
export { ATLAS_CODE55_N, BUILT_IN_TRACK_PARTS, BUMPER_DRAWN_INCHES, type BenchworkPoint, type BranchConnector, CLEARANCE_SPACING_INCHES, CODE55_RAIL_HEIGHT_INCHES, type ConversionAnswers, type ConversionBlocker, DEFAULT_FLEX_PART_ID, type DimensionSource, type DocToGraphResult, 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 EndplateEdge, type EndplateEdgePose, 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_MAIN_MIN_RADIUS_INCHES, FREEMO_REVERSE_CURVE_STRAIGHT_INCHES, FREEMO_TRACK_SPACING_INCHES, type FlexPiece, type FlexPieceEnd, type FrogCasting, GENERIC_END_OF_TRACK, type GeometryType, type GraphAnchor, type GraphConflict, type GraphConnection, type GraphDocInput, type GraphDocResult, type GraphRoute, type GraphTurnout, type GraphWalk, type ImportedPart, type IndustryLabelMode, type IndustrySpot, JOINT_SNAP_INCHES, type LanePinch, MAIN2_TRACK_ID, MAIN_TRACK_ID, type ModuleConversionReport, type ModuleFeatures, type ModuleFootprint, type ModuleFootprintInput, type ModuleGeometryInput, type ModuleSchematicDoc, type ModuleTrackRow, N_CAR_LENGTH_INCHES, N_SCALE_RATIO, type OccupiedSpan, type OrphanTrack, type OutlineFace, PINCH_EASE_INCHES, type PartAngle, type PartDimension, type PartEnd, type PartExtent, type PartGeometry, type PartJoint, type PartJointRole, type PartSegment, type PieceSnap, type PlacedJoint, RAIL_GAUGE_INCHES, type ResolvedAnchor, type ReturnLoopGeometry, type ReturnLoopShape, type RouteSpan, type RunInsertion, 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, TRACK_PART_KINDS, TURNOUT_LEAD_INCHES_PER_FROG, type TrackConfig, type TrackGraph, type TrackPart, type TrackPiece, type TrackRole, type TurnoutClosure, type TurnoutIdentity, type TurnoutKind, type UsableCapacity, WHOLE_MODULE_SECTION_ID, asModuleSchematic, assessSectionEnd, assessSectionJoint, benchworkBand, benchworkOutline, buildCrossover, buildPassingSiding, buildTrackGraph, buildTransition, carCapacity, checkEndplateWidth, clearancePointPastFrogInches, crossoverPinches, deriveEndplatePoses, deriveGraphDoc, divergeSideForHand, docToGraph, docToState, emptyEditorState, endplateCentreOffsetInches, endplateEdgePose, endplateFaceSegments, endplateLead, endplateTrackOffsetInches, endplateWidthInches, fitFlexBetween, flexPartFor, flexParts, flexPieces, flexRunEnd, flexUsage, frogCasting, frogNumberFromName, fromSectionRelative, geometryTurnDegrees, graphToDoc, hasNoFarEndplate, importedPartToTrackPart, inchesToScaleFeet, insertIntoRun, isLoopDoc, isTransitionTurnout, laneOffsetAt, leadInchesForSize, maxFlexPieceInches, mergeImportedParts, mergeStoredParts, moduleCenterline, moduleConversionReport, moduleFeatures, moduleFootprint, moduleLengthFromSections, moduleSections, nextId, parseXtpLibrary, partExtent, partExtentForSize, partGeometry, partGeometryGap, partOutlineAtFrog, partsPlaceable, pastFrogInchesForSize, pathLengthInches, pieceHand, piecePartNumber, pieceRoutePaths, placedJoints, poseNeedsManual, poseOverridesFromDoc, remapPos, resizeFlexPiece, resolveGraphAnchor, returnLoop, sampleBenchworkOutline, samplePartSegments, samplePath, scaleFeetToInches, sectionAdjacency, sectionBand, sectionBreaksFromSections, sectionComponents, sectionFootprints, sectionNeighbours, sectionSpans, sectionSpansOrWhole, sectionalArcInches, sectionedCenterline, sectionedEndPose, sliceCenterline, snapPiece, spanOverhang, stateToDoc, storedPartToTrackPart, toSectionRelative, trackMeetsEndplateIssues, trackPart, trackPath, turnoutClosure, turnoutFacing, turnoutOccupiedSpan, turnoutPartForSize, usableCapacity, walkTrackGraph };
|
|
3441
|
+
export { ATLAS_CODE55_N, BUILT_IN_TRACK_PARTS, BUMPER_DRAWN_INCHES, type BenchworkPoint, type BranchConnector, CLEARANCE_SPACING_INCHES, CODE55_RAIL_HEIGHT_INCHES, type ConversionAnswers, type ConversionBlocker, DEFAULT_FLEX_PART_ID, type DimensionSource, type DocToGraphResult, 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 EndplateEdge, type EndplateEdgePose, 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_MAIN_MIN_RADIUS_INCHES, FREEMO_REVERSE_CURVE_STRAIGHT_INCHES, FREEMO_TRACK_SPACING_INCHES, type FlexPiece, type FlexPieceEnd, type FrogCasting, GENERIC_END_OF_TRACK, type GeometryType, type GraphAnchor, type GraphConflict, type GraphConnection, type GraphDocInput, type GraphDocResult, type GraphRoute, type GraphTurnout, type GraphWalk, type ImportedPart, type IndustryLabelMode, type IndustrySpot, JOINT_SNAP_INCHES, type LanePinch, MAIN2_TRACK_ID, MAIN_TRACK_ID, type ModuleConversionReport, type ModuleFeatures, type ModuleFootprint, type ModuleFootprintInput, type ModuleGeometryInput, type ModuleSchematicDoc, type ModuleTrackRow, N_CAR_LENGTH_INCHES, N_SCALE_RATIO, type OccupiedSpan, type OrphanTrack, type OutlineFace, PINCH_EASE_INCHES, type PartAngle, type PartDimension, type PartEnd, type PartExtent, type PartGeometry, type PartJoint, type PartJointRole, type PartSegment, type PieceSnap, type PlacedJoint, RAIL_GAUGE_INCHES, type ResolvedAnchor, type ReturnLoopGeometry, type ReturnLoopShape, type RouteSpan, type RunInsertion, 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, TRACK_PART_KINDS, TURNOUT_LEAD_INCHES_PER_FROG, type TrackConfig, type TrackGraph, type TrackPart, type TrackPiece, type TrackRole, type TurnoutClosure, type TurnoutIdentity, type TurnoutKind, type UsableCapacity, WHOLE_MODULE_SECTION_ID, asModuleSchematic, assessSectionEnd, assessSectionJoint, benchworkBand, benchworkOutline, buildCrossover, buildPassingSiding, buildTrackGraph, buildTransition, carCapacity, checkEndplateWidth, clearancePointPastFrogInches, crossoverAssembly, crossoverPinches, deriveEndplatePoses, deriveGraphDoc, divergeSideForHand, docToGraph, docToState, emptyEditorState, endplateCentreOffsetInches, endplateEdgePose, endplateFaceSegments, endplateLead, endplateTrackOffsetInches, endplateWidthInches, fitFlexBetween, flexPartFor, flexParts, flexPieces, flexRunEnd, flexUsage, frogCasting, frogNumberFromName, fromSectionRelative, geometryTurnDegrees, graphToDoc, hasNoFarEndplate, importedPartToTrackPart, inchesToScaleFeet, insertIntoRun, isLoopDoc, isTransitionTurnout, laneOffsetAt, leadInchesForSize, maxFlexPieceInches, mergeImportedParts, mergeStoredParts, moduleCenterline, moduleConversionReport, moduleFeatures, moduleFootprint, moduleLengthFromSections, moduleSections, nextId, parseXtpLibrary, partExtent, partExtentForSize, partGeometry, partGeometryGap, partOutlineAtFrog, partsPlaceable, pastFrogInchesForSize, pathLengthInches, pieceHand, piecePartNumber, pieceRoutePaths, placedJoints, poseNeedsManual, poseOverridesFromDoc, remapPos, resizeFlexPiece, resolveGraphAnchor, returnLoop, sampleBenchworkOutline, samplePartSegments, samplePath, scaleFeetToInches, sectionAdjacency, sectionBand, sectionBreaksFromSections, sectionComponents, sectionFootprints, sectionNeighbours, sectionSpans, sectionSpansOrWhole, sectionalArcInches, sectionedCenterline, sectionedEndPose, sliceCenterline, snapPiece, spanOverhang, stateToDoc, storedPartToTrackPart, toSectionRelative, trackMeetsEndplateIssues, trackPart, trackPath, turnoutClosure, turnoutFacing, turnoutOccupiedSpan, turnoutPartForSize, usableCapacity, walkTrackGraph };
|
package/dist/index.d.ts
CHANGED
|
@@ -2480,7 +2480,12 @@ declare function turnoutClosure(size: number, opts?: {
|
|
|
2480
2480
|
easeInches?: number;
|
|
2481
2481
|
}): TurnoutClosure;
|
|
2482
2482
|
/** Which end of a part this is. A graph connects joints; a walk uses `routes`. */
|
|
2483
|
-
type PartJointRole = "throat" | "through" | "diverge" | "divergeB"
|
|
2483
|
+
type PartJointRole = "throat" | "through" | "diverge" | "divergeB"
|
|
2484
|
+
/** An end of one of the two parallel tracks THROUGH a crossover assembly.
|
|
2485
|
+
* A double crossover has four, and none of them is a throat: every end is
|
|
2486
|
+
* both a straight route and a crossing route, which is exactly why it is one
|
|
2487
|
+
* moulding and not four turnouts. */
|
|
2488
|
+
| "crossoverEnd";
|
|
2484
2489
|
/** One end of a part, in part-local inches: `x` along the through route from
|
|
2485
2490
|
* the tie end, `y` lateral toward the diverging side, `angleDeg` the OUTWARD
|
|
2486
2491
|
* tangent (0 = +x). Named, unlike {@link PartEnd}, because a graph has to know
|
|
@@ -2502,6 +2507,46 @@ interface PartGeometry {
|
|
|
2502
2507
|
* leaves the moulding at the same place the through route does. */
|
|
2503
2508
|
divergingEndMeasured: boolean;
|
|
2504
2509
|
}
|
|
2510
|
+
/**
|
|
2511
|
+
* A double crossover's geometry, every number of it PUBLISHED rather than
|
|
2512
|
+
* guessed — or null when the part does not carry them.
|
|
2513
|
+
*
|
|
2514
|
+
* ⭐⭐ **A DOUBLE CROSSOVER IS ONE ASSEMBLY, NOT FOUR TURNOUTS** (Will,
|
|
2515
|
+
* 2026-07-28). Modelling it as four independent turnout mouldings is what made a
|
|
2516
|
+
* rebuild of FMN-0078 collapse: two Atlas #7s 2.5″ apart overlap by 3.5″, so the
|
|
2517
|
+
* pieces intersected and the walk emitted a module nobody has. The real thing is
|
|
2518
|
+
* a single moulding whose four point-sets sit closer together than four separate
|
|
2519
|
+
* turnouts ever could — which is precisely why it is sold as one fixture.
|
|
2520
|
+
*
|
|
2521
|
+
* ⚠️ **ITS SPACING IS NOT A DEPARTURE FROM THE STANDARD.** Free-moN §2.0 fixes
|
|
2522
|
+
* {@link FREEMO_TRACK_SPACING_INCHES} **at the endplate** — "double track
|
|
2523
|
+
* endplates must have a track spacing of 1.125 inches", perpendicular, straight
|
|
2524
|
+
* and level for 4″ from the outside face. What the mains do in between is the
|
|
2525
|
+
* builder's business, and every real double crossover pinches them closer.
|
|
2526
|
+
*
|
|
2527
|
+
* The derivation:
|
|
2528
|
+
* - **Length** is `overallLength × piecesPerAssembly`. Fast Tracks' figure is
|
|
2529
|
+
* ONE HALF — the fixture builds a symmetrical half you make twice and butt
|
|
2530
|
+
* together after turning the second 180°.
|
|
2531
|
+
* - **The crossing run** `W = spacing / tan θ` is how far along the track a route
|
|
2532
|
+
* takes to cross to the other one. The two point-sets on the SAME track are
|
|
2533
|
+
* therefore `W` apart — 6.54″ for a #6 at 1.09″, not the 2.5″ my own FMN-0078
|
|
2534
|
+
* fixture claimed.
|
|
2535
|
+
* - Both crossing routes are centred on the assembly, so they meet in the middle
|
|
2536
|
+
* at `2θ` — which is exactly the {@link TrackPart.secondaryFrogAngle} Fast
|
|
2537
|
+
* Tracks publish, an independent check that this reading is right.
|
|
2538
|
+
*/
|
|
2539
|
+
declare function crossoverAssembly(part: TrackPart): {
|
|
2540
|
+
/** End to end along the track, both halves. */
|
|
2541
|
+
lengthInches: number;
|
|
2542
|
+
spacingInches: number;
|
|
2543
|
+
/** Along-track distance a crossing route takes to reach the other track. */
|
|
2544
|
+
crossingRunInches: number;
|
|
2545
|
+
/** Where the four point-sets sit along the assembly. */
|
|
2546
|
+
pointsAtInches: [number, number];
|
|
2547
|
+
/** The X where the two crossing routes meet — a real diamond in the middle. */
|
|
2548
|
+
scissorsAtInches: number;
|
|
2549
|
+
} | null;
|
|
2505
2550
|
/**
|
|
2506
2551
|
* Why a part has no derivable geometry — null when it has.
|
|
2507
2552
|
*
|
|
@@ -3393,4 +3438,4 @@ declare function poseOverridesFromDoc(doc: ModuleSchematicDoc): Record<string, {
|
|
|
3393
3438
|
heading: number;
|
|
3394
3439
|
}>;
|
|
3395
3440
|
|
|
3396
|
-
export { ATLAS_CODE55_N, BUILT_IN_TRACK_PARTS, BUMPER_DRAWN_INCHES, type BenchworkPoint, type BranchConnector, CLEARANCE_SPACING_INCHES, CODE55_RAIL_HEIGHT_INCHES, type ConversionAnswers, type ConversionBlocker, DEFAULT_FLEX_PART_ID, type DimensionSource, type DocToGraphResult, 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 EndplateEdge, type EndplateEdgePose, 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_MAIN_MIN_RADIUS_INCHES, FREEMO_REVERSE_CURVE_STRAIGHT_INCHES, FREEMO_TRACK_SPACING_INCHES, type FlexPiece, type FlexPieceEnd, type FrogCasting, GENERIC_END_OF_TRACK, type GeometryType, type GraphAnchor, type GraphConflict, type GraphConnection, type GraphDocInput, type GraphDocResult, type GraphRoute, type GraphTurnout, type GraphWalk, type ImportedPart, type IndustryLabelMode, type IndustrySpot, JOINT_SNAP_INCHES, type LanePinch, MAIN2_TRACK_ID, MAIN_TRACK_ID, type ModuleConversionReport, type ModuleFeatures, type ModuleFootprint, type ModuleFootprintInput, type ModuleGeometryInput, type ModuleSchematicDoc, type ModuleTrackRow, N_CAR_LENGTH_INCHES, N_SCALE_RATIO, type OccupiedSpan, type OrphanTrack, type OutlineFace, PINCH_EASE_INCHES, type PartAngle, type PartDimension, type PartEnd, type PartExtent, type PartGeometry, type PartJoint, type PartJointRole, type PartSegment, type PieceSnap, type PlacedJoint, RAIL_GAUGE_INCHES, type ResolvedAnchor, type ReturnLoopGeometry, type ReturnLoopShape, type RouteSpan, type RunInsertion, 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, TRACK_PART_KINDS, TURNOUT_LEAD_INCHES_PER_FROG, type TrackConfig, type TrackGraph, type TrackPart, type TrackPiece, type TrackRole, type TurnoutClosure, type TurnoutIdentity, type TurnoutKind, type UsableCapacity, WHOLE_MODULE_SECTION_ID, asModuleSchematic, assessSectionEnd, assessSectionJoint, benchworkBand, benchworkOutline, buildCrossover, buildPassingSiding, buildTrackGraph, buildTransition, carCapacity, checkEndplateWidth, clearancePointPastFrogInches, crossoverPinches, deriveEndplatePoses, deriveGraphDoc, divergeSideForHand, docToGraph, docToState, emptyEditorState, endplateCentreOffsetInches, endplateEdgePose, endplateFaceSegments, endplateLead, endplateTrackOffsetInches, endplateWidthInches, fitFlexBetween, flexPartFor, flexParts, flexPieces, flexRunEnd, flexUsage, frogCasting, frogNumberFromName, fromSectionRelative, geometryTurnDegrees, graphToDoc, hasNoFarEndplate, importedPartToTrackPart, inchesToScaleFeet, insertIntoRun, isLoopDoc, isTransitionTurnout, laneOffsetAt, leadInchesForSize, maxFlexPieceInches, mergeImportedParts, mergeStoredParts, moduleCenterline, moduleConversionReport, moduleFeatures, moduleFootprint, moduleLengthFromSections, moduleSections, nextId, parseXtpLibrary, partExtent, partExtentForSize, partGeometry, partGeometryGap, partOutlineAtFrog, partsPlaceable, pastFrogInchesForSize, pathLengthInches, pieceHand, piecePartNumber, pieceRoutePaths, placedJoints, poseNeedsManual, poseOverridesFromDoc, remapPos, resizeFlexPiece, resolveGraphAnchor, returnLoop, sampleBenchworkOutline, samplePartSegments, samplePath, scaleFeetToInches, sectionAdjacency, sectionBand, sectionBreaksFromSections, sectionComponents, sectionFootprints, sectionNeighbours, sectionSpans, sectionSpansOrWhole, sectionalArcInches, sectionedCenterline, sectionedEndPose, sliceCenterline, snapPiece, spanOverhang, stateToDoc, storedPartToTrackPart, toSectionRelative, trackMeetsEndplateIssues, trackPart, trackPath, turnoutClosure, turnoutFacing, turnoutOccupiedSpan, turnoutPartForSize, usableCapacity, walkTrackGraph };
|
|
3441
|
+
export { ATLAS_CODE55_N, BUILT_IN_TRACK_PARTS, BUMPER_DRAWN_INCHES, type BenchworkPoint, type BranchConnector, CLEARANCE_SPACING_INCHES, CODE55_RAIL_HEIGHT_INCHES, type ConversionAnswers, type ConversionBlocker, DEFAULT_FLEX_PART_ID, type DimensionSource, type DocToGraphResult, 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 EndplateEdge, type EndplateEdgePose, 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_MAIN_MIN_RADIUS_INCHES, FREEMO_REVERSE_CURVE_STRAIGHT_INCHES, FREEMO_TRACK_SPACING_INCHES, type FlexPiece, type FlexPieceEnd, type FrogCasting, GENERIC_END_OF_TRACK, type GeometryType, type GraphAnchor, type GraphConflict, type GraphConnection, type GraphDocInput, type GraphDocResult, type GraphRoute, type GraphTurnout, type GraphWalk, type ImportedPart, type IndustryLabelMode, type IndustrySpot, JOINT_SNAP_INCHES, type LanePinch, MAIN2_TRACK_ID, MAIN_TRACK_ID, type ModuleConversionReport, type ModuleFeatures, type ModuleFootprint, type ModuleFootprintInput, type ModuleGeometryInput, type ModuleSchematicDoc, type ModuleTrackRow, N_CAR_LENGTH_INCHES, N_SCALE_RATIO, type OccupiedSpan, type OrphanTrack, type OutlineFace, PINCH_EASE_INCHES, type PartAngle, type PartDimension, type PartEnd, type PartExtent, type PartGeometry, type PartJoint, type PartJointRole, type PartSegment, type PieceSnap, type PlacedJoint, RAIL_GAUGE_INCHES, type ResolvedAnchor, type ReturnLoopGeometry, type ReturnLoopShape, type RouteSpan, type RunInsertion, 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, TRACK_PART_KINDS, TURNOUT_LEAD_INCHES_PER_FROG, type TrackConfig, type TrackGraph, type TrackPart, type TrackPiece, type TrackRole, type TurnoutClosure, type TurnoutIdentity, type TurnoutKind, type UsableCapacity, WHOLE_MODULE_SECTION_ID, asModuleSchematic, assessSectionEnd, assessSectionJoint, benchworkBand, benchworkOutline, buildCrossover, buildPassingSiding, buildTrackGraph, buildTransition, carCapacity, checkEndplateWidth, clearancePointPastFrogInches, crossoverAssembly, crossoverPinches, deriveEndplatePoses, deriveGraphDoc, divergeSideForHand, docToGraph, docToState, emptyEditorState, endplateCentreOffsetInches, endplateEdgePose, endplateFaceSegments, endplateLead, endplateTrackOffsetInches, endplateWidthInches, fitFlexBetween, flexPartFor, flexParts, flexPieces, flexRunEnd, flexUsage, frogCasting, frogNumberFromName, fromSectionRelative, geometryTurnDegrees, graphToDoc, hasNoFarEndplate, importedPartToTrackPart, inchesToScaleFeet, insertIntoRun, isLoopDoc, isTransitionTurnout, laneOffsetAt, leadInchesForSize, maxFlexPieceInches, mergeImportedParts, mergeStoredParts, moduleCenterline, moduleConversionReport, moduleFeatures, moduleFootprint, moduleLengthFromSections, moduleSections, nextId, parseXtpLibrary, partExtent, partExtentForSize, partGeometry, partGeometryGap, partOutlineAtFrog, partsPlaceable, pastFrogInchesForSize, pathLengthInches, pieceHand, piecePartNumber, pieceRoutePaths, placedJoints, poseNeedsManual, poseOverridesFromDoc, remapPos, resizeFlexPiece, resolveGraphAnchor, returnLoop, sampleBenchworkOutline, samplePartSegments, samplePath, scaleFeetToInches, sectionAdjacency, sectionBand, sectionBreaksFromSections, sectionComponents, sectionFootprints, sectionNeighbours, sectionSpans, sectionSpansOrWhole, sectionalArcInches, sectionedCenterline, sectionedEndPose, sliceCenterline, snapPiece, spanOverhang, stateToDoc, storedPartToTrackPart, toSectionRelative, trackMeetsEndplateIssues, trackPart, trackPath, turnoutClosure, turnoutFacing, turnoutOccupiedSpan, turnoutPartForSize, usableCapacity, walkTrackGraph };
|
package/dist/index.js
CHANGED
|
@@ -1696,7 +1696,7 @@ var FAST_TRACKS_N_ME55_CROSSOVERS = [
|
|
|
1696
1696
|
trackSpacing: {
|
|
1697
1697
|
inches: spacing,
|
|
1698
1698
|
source: manufacturer,
|
|
1699
|
-
note: `${spec}. \u26A0\uFE0F Free-moN \xA72.0
|
|
1699
|
+
note: `${spec}. ${spacing}\u2033, and the fixture cannot be built to another spacing. \u26A0\uFE0F THIS IS NOT A DEPARTURE FROM THE STANDARD. Free-moN \xA72.0 fixes ${FREEMO_TRACK_SPACING_INCHES}\u2033 at the ENDPLATE \u2014 "double track endplates must have a track spacing of 1.125 inches", with track perpendicular, straight and level for 4\u2033 from the outside face. What the two mains do in between is the module builder's business, and every real double crossover pinches them closer. Earlier wording here called the fixture "tighter than the standard", which reads as non-conformance and is wrong.`
|
|
1700
1700
|
}
|
|
1701
1701
|
};
|
|
1702
1702
|
});
|
|
@@ -2174,6 +2174,31 @@ function turnoutClosure(size, opts = {}) {
|
|
|
2174
2174
|
}
|
|
2175
2175
|
};
|
|
2176
2176
|
}
|
|
2177
|
+
function crossoverAssembly(part) {
|
|
2178
|
+
const overall = part.overallLength?.inches;
|
|
2179
|
+
const spacing = part.trackSpacing?.inches;
|
|
2180
|
+
const n = part.frogNumber;
|
|
2181
|
+
if (!overall || !spacing || !n) return null;
|
|
2182
|
+
const lengthInches = overall * (part.piecesPerAssembly ?? 1);
|
|
2183
|
+
const tan = part.actualAngle ? Math.tan(part.actualAngle.deg * Math.PI / 180) : 1 / n;
|
|
2184
|
+
if (!(tan > 0)) return null;
|
|
2185
|
+
const crossingRunInches = spacing / tan;
|
|
2186
|
+
const mid = lengthInches / 2;
|
|
2187
|
+
const half = crossingRunInches / 2;
|
|
2188
|
+
return {
|
|
2189
|
+
lengthInches,
|
|
2190
|
+
spacingInches: spacing,
|
|
2191
|
+
crossingRunInches,
|
|
2192
|
+
pointsAtInches: [mid - half, mid + half],
|
|
2193
|
+
scissorsAtInches: mid
|
|
2194
|
+
};
|
|
2195
|
+
}
|
|
2196
|
+
function weakestOf(...ds) {
|
|
2197
|
+
const rank = ["measured", "manufacturer", "derived", "unverified"];
|
|
2198
|
+
let worst = 0;
|
|
2199
|
+
for (const d of ds) if (d) worst = Math.max(worst, rank.indexOf(d.source));
|
|
2200
|
+
return rank[worst];
|
|
2201
|
+
}
|
|
2177
2202
|
function partGeometryGap(part) {
|
|
2178
2203
|
if (part.kind === "flex") return null;
|
|
2179
2204
|
if (part.kind === "bumper") return null;
|
|
@@ -2184,8 +2209,13 @@ function partGeometryGap(part) {
|
|
|
2184
2209
|
if (part.arcDegrees == null) return "no arc \u2014 the radius alone does not say how far it turns";
|
|
2185
2210
|
return null;
|
|
2186
2211
|
}
|
|
2187
|
-
if (part.kind === "crossover")
|
|
2188
|
-
|
|
2212
|
+
if (part.kind === "crossover") {
|
|
2213
|
+
if (!part.overallLength) return "no overall length \u2014 nothing says how long the assembly is";
|
|
2214
|
+
if (!part.trackSpacing)
|
|
2215
|
+
return "no track spacing \u2014 a crossover is defined by how far apart the two tracks it joins are";
|
|
2216
|
+
if (part.frogNumber == null) return "no frog number \u2014 the crossing angle is unknown";
|
|
2217
|
+
return null;
|
|
2218
|
+
}
|
|
2189
2219
|
if (part.kind === "crossing") return "crossing geometry is not modelled yet";
|
|
2190
2220
|
if (part.kind === "curved-turnout")
|
|
2191
2221
|
return "a curved turnout needs both radii AND its points/frog landmarks; only the radii are published";
|
|
@@ -2228,6 +2258,30 @@ function partGeometry(part, library = BUILT_IN_TRACK_PARTS) {
|
|
|
2228
2258
|
divergingEndMeasured: false
|
|
2229
2259
|
};
|
|
2230
2260
|
}
|
|
2261
|
+
if (part.kind === "crossover") {
|
|
2262
|
+
const g = crossoverAssembly(part);
|
|
2263
|
+
if (!g) return null;
|
|
2264
|
+
return {
|
|
2265
|
+
joints: [
|
|
2266
|
+
{ id: "a1", role: "crossoverEnd", x: 0, y: 0, angleDeg: 180 },
|
|
2267
|
+
{ id: "b1", role: "crossoverEnd", x: g.lengthInches, y: 0, angleDeg: 0 },
|
|
2268
|
+
{ id: "a2", role: "crossoverEnd", x: 0, y: g.spacingInches, angleDeg: 180 },
|
|
2269
|
+
{ id: "b2", role: "crossoverEnd", x: g.lengthInches, y: g.spacingInches, angleDeg: 0 }
|
|
2270
|
+
],
|
|
2271
|
+
// ⭐ FOUR ROUTES, and that is the whole point. From either end of either
|
|
2272
|
+
// track a train can run straight on or cross to the other — which is what
|
|
2273
|
+
// makes this ONE assembly rather than four turnouts that happen to be near
|
|
2274
|
+
// each other.
|
|
2275
|
+
routes: [
|
|
2276
|
+
["a1", "b1"],
|
|
2277
|
+
["a2", "b2"],
|
|
2278
|
+
["a1", "b2"],
|
|
2279
|
+
["a2", "b1"]
|
|
2280
|
+
],
|
|
2281
|
+
source: weakestOf(part.overallLength, part.trackSpacing),
|
|
2282
|
+
divergingEndMeasured: false
|
|
2283
|
+
};
|
|
2284
|
+
}
|
|
2231
2285
|
const N = part.frogNumber;
|
|
2232
2286
|
const points = part.pointsOffset;
|
|
2233
2287
|
const overall = part.overallLength;
|
|
@@ -2323,6 +2377,30 @@ function pieceRoutePaths(piece, library = BUILT_IN_TRACK_PARTS) {
|
|
|
2323
2377
|
pts[pts.length - 1] = { x: b.x, y: b.y };
|
|
2324
2378
|
return [{ route: ["a", "b"], points: pts }];
|
|
2325
2379
|
}
|
|
2380
|
+
if (part.kind === "crossover") {
|
|
2381
|
+
const g = crossoverAssembly(part);
|
|
2382
|
+
if (g) {
|
|
2383
|
+
const [p1, p2] = g.pointsAtInches;
|
|
2384
|
+
const S = g.spacingInches;
|
|
2385
|
+
const L = g.lengthInches;
|
|
2386
|
+
const yOf = (id) => id.endsWith("2") ? S : 0;
|
|
2387
|
+
return geo.routes.map((route) => {
|
|
2388
|
+
const [from, to] = route;
|
|
2389
|
+
const y0 = yOf(from);
|
|
2390
|
+
const y1 = yOf(to);
|
|
2391
|
+
const x0 = from.startsWith("a") ? 0 : L;
|
|
2392
|
+
const x1 = to.startsWith("a") ? 0 : L;
|
|
2393
|
+
if (y0 === y1)
|
|
2394
|
+
return { route, points: [place(x0, y0), place(x1, y1)] };
|
|
2395
|
+
const west = Math.min(x0, x1) === 0;
|
|
2396
|
+
const [xa, xb] = west ? [p1, p2] : [p2, p1];
|
|
2397
|
+
return {
|
|
2398
|
+
route,
|
|
2399
|
+
points: [place(x0, y0), place(xa, y0), place(xb, y1), place(x1, y1)]
|
|
2400
|
+
};
|
|
2401
|
+
});
|
|
2402
|
+
}
|
|
2403
|
+
}
|
|
2326
2404
|
for (const route of geo.routes) {
|
|
2327
2405
|
const a = at(route[0]);
|
|
2328
2406
|
const b = at(route[1]);
|
|
@@ -2691,7 +2769,11 @@ function walkTrackGraph(graph, pieces, startAt, library = BUILT_IN_TRACK_PARTS)
|
|
|
2691
2769
|
route.closedBy = here.piece;
|
|
2692
2770
|
break;
|
|
2693
2771
|
}
|
|
2694
|
-
const
|
|
2772
|
+
const sameTrack = (r) => {
|
|
2773
|
+
const other = r[0] === here.joint ? r[1] : r[0];
|
|
2774
|
+
return other.slice(-1) === here.joint.slice(-1);
|
|
2775
|
+
};
|
|
2776
|
+
const pick = (partOf(here.piece)?.kind === "crossover" ? opts.find(sameTrack) : void 0) ?? opts.find((r) => r.includes("through")) ?? opts[0];
|
|
2695
2777
|
const exitJoint = pick[0] === here.joint ? pick[1] : pick[0];
|
|
2696
2778
|
const exit = byKey.get(`${here.piece}.${exitJoint}`);
|
|
2697
2779
|
const part = partOf(here.piece);
|
|
@@ -2814,7 +2896,11 @@ function graphToDoc(pieces, input) {
|
|
|
2814
2896
|
const main = walk.routes.find((r) => r.id === "main");
|
|
2815
2897
|
const walk2 = input.start2 ? walkTrackGraph(graph, pieces, input.start2, library) : null;
|
|
2816
2898
|
const main2 = walk2?.routes.find((r) => r.id === "main") ?? null;
|
|
2817
|
-
const
|
|
2899
|
+
const sharedByDesign = (id) => {
|
|
2900
|
+
const piece = pieces.find((p) => p.id === id);
|
|
2901
|
+
return library.find((p) => p.id === piece?.partId)?.kind === "crossover";
|
|
2902
|
+
};
|
|
2903
|
+
const claimed = new Set(walk.routes.flatMap((r) => r.pieces).filter((p) => !sharedByDesign(p)));
|
|
2818
2904
|
if (main2 && main2.pieces.some((p) => claimed.has(p))) {
|
|
2819
2905
|
warnings.push(
|
|
2820
2906
|
"Main 2 starts on track that Main 1 already runs along \u2014 they are one run, not two"
|
|
@@ -2931,6 +3017,74 @@ function graphToDoc(pieces, input) {
|
|
|
2931
3017
|
...part ? { partId: part.id } : {}
|
|
2932
3018
|
});
|
|
2933
3019
|
}
|
|
3020
|
+
const crossoverSpans = (() => {
|
|
3021
|
+
const found = [];
|
|
3022
|
+
const routesAll = [
|
|
3023
|
+
...walk.routes.map((r) => ({ r, prefix: "" })),
|
|
3024
|
+
...twoMains ? walk2.routes.map((r) => ({ r, prefix: "main2:" })) : []
|
|
3025
|
+
];
|
|
3026
|
+
const seen = /* @__PURE__ */ new Map();
|
|
3027
|
+
for (const { r, prefix } of routesAll) {
|
|
3028
|
+
for (const sp of r.spans) {
|
|
3029
|
+
const piece = pieceById.get(sp.piece);
|
|
3030
|
+
const part = piece ? library.find((p) => p.id === piece.partId) : void 0;
|
|
3031
|
+
if (!part || part.kind !== "crossover") continue;
|
|
3032
|
+
const g = crossoverAssembly(part);
|
|
3033
|
+
if (!g) continue;
|
|
3034
|
+
const [p1, p2] = g.pointsAtInches;
|
|
3035
|
+
const west = sp.entryJoint.startsWith("a");
|
|
3036
|
+
const at = west ? [sp.fromPos + p1, sp.fromPos + p2] : [sp.fromPos + (g.lengthInches - p2), sp.fromPos + (g.lengthInches - p1)];
|
|
3037
|
+
const track = sp.entryJoint.endsWith("2") ? 2 : 1;
|
|
3038
|
+
const routeId = r.id === "main" && prefix ? "main2:main" : `${prefix}${r.id}`;
|
|
3039
|
+
const rec = seen.get(sp.piece) ?? { piece: sp.piece, part, byTrack: /* @__PURE__ */ new Map() };
|
|
3040
|
+
rec.byTrack.set(track, { routeId, at });
|
|
3041
|
+
seen.set(sp.piece, rec);
|
|
3042
|
+
}
|
|
3043
|
+
}
|
|
3044
|
+
for (const rec of seen.values()) if (rec.byTrack.size === 2) found.push(rec);
|
|
3045
|
+
return found;
|
|
3046
|
+
})();
|
|
3047
|
+
for (const xo of crossoverSpans) {
|
|
3048
|
+
const one = xo.byTrack.get(1);
|
|
3049
|
+
const two = xo.byTrack.get(2);
|
|
3050
|
+
const onA = trackIdOf.get(one.routeId) ?? MAIN_TRACK_ID;
|
|
3051
|
+
const onB = trackIdOf.get(two.routeId) ?? MAIN2_TRACK_ID;
|
|
3052
|
+
const legs = [
|
|
3053
|
+
{ id: `${xo.piece}-a`, from: [onA, one.at[0]], to: [onB, two.at[1]] },
|
|
3054
|
+
{ id: `${xo.piece}-b`, from: [onB, two.at[0]], to: [onA, one.at[1]] }
|
|
3055
|
+
];
|
|
3056
|
+
for (const leg of legs) {
|
|
3057
|
+
tracks.push({
|
|
3058
|
+
id: leg.id,
|
|
3059
|
+
role: "crossover",
|
|
3060
|
+
// The connector lives between the mains, so it takes Main 2's lane —
|
|
3061
|
+
// the same lane the 1-D model has always drawn a crossover in.
|
|
3062
|
+
lane: main2Lane ?? 1,
|
|
3063
|
+
fromPos: round(Math.min(leg.from[1], leg.to[1])),
|
|
3064
|
+
toPos: round(Math.max(leg.from[1], leg.to[1])),
|
|
3065
|
+
trackName: xo.part.name ?? "Crossover",
|
|
3066
|
+
crossoverPartId: xo.part.id
|
|
3067
|
+
});
|
|
3068
|
+
turnouts.push(
|
|
3069
|
+
{
|
|
3070
|
+
id: `${leg.id}-1`,
|
|
3071
|
+
pos: round(leg.from[1]),
|
|
3072
|
+
onTrack: leg.from[0],
|
|
3073
|
+
divergeTrack: leg.id,
|
|
3074
|
+
name: xo.part.name ?? "Crossover",
|
|
3075
|
+
...xo.part.frogNumber != null ? { size: xo.part.frogNumber } : {}
|
|
3076
|
+
},
|
|
3077
|
+
{
|
|
3078
|
+
id: `${leg.id}-2`,
|
|
3079
|
+
pos: round(leg.to[1]),
|
|
3080
|
+
onTrack: leg.to[0],
|
|
3081
|
+
divergeTrack: leg.id,
|
|
3082
|
+
name: xo.part.name ?? "Crossover",
|
|
3083
|
+
...xo.part.frogNumber != null ? { size: xo.part.frogNumber } : {}
|
|
3084
|
+
}
|
|
3085
|
+
);
|
|
3086
|
+
}
|
|
3087
|
+
}
|
|
2934
3088
|
turnouts.sort((a, b) => a.pos - b.pos);
|
|
2935
3089
|
for (const t of tracks) {
|
|
2936
3090
|
if (t.role === "main") continue;
|
|
@@ -3113,9 +3267,17 @@ function docToGraph(doc, answers = {}, library = BUILT_IN_TRACK_PARTS) {
|
|
|
3113
3267
|
"this module's mainline is drawn with bends, and laying it as pieces would need each bend's radius \u2014 convert it once curved runs are supported"
|
|
3114
3268
|
);
|
|
3115
3269
|
const partById = new Map(library.map((p) => [p.id, p]));
|
|
3270
|
+
const inAssembly = /* @__PURE__ */ new Set();
|
|
3271
|
+
for (const t of doc.tracks ?? []) {
|
|
3272
|
+
if (t.role !== "crossover" || !t.crossoverPartId) continue;
|
|
3273
|
+
const part = partById.get(t.crossoverPartId);
|
|
3274
|
+
if (!part || part.kind !== "crossover" || partGeometryGap(part)) continue;
|
|
3275
|
+
for (const sw of doc.turnouts ?? []) if (sw.divergeTrack === t.id) inAssembly.add(sw.id);
|
|
3276
|
+
}
|
|
3116
3277
|
const chosen = /* @__PURE__ */ new Map();
|
|
3117
3278
|
const unknown = [];
|
|
3118
3279
|
for (const t of report.turnouts) {
|
|
3280
|
+
if (inAssembly.has(t.id)) continue;
|
|
3119
3281
|
const id = answers.overrides?.[t.id] ?? (t.partId ? t.partId : answers.turnoutPartId) ?? null;
|
|
3120
3282
|
const part = id ? partById.get(id) : void 0;
|
|
3121
3283
|
if (!part || partGeometryGap(part)) unknown.push(t.id);
|
|
@@ -3198,19 +3360,26 @@ function docToGraph(doc, answers = {}, library = BUILT_IN_TRACK_PARTS) {
|
|
|
3198
3360
|
const divergeId = geom.joints.find((j) => j.role === "diverge")?.id ?? "diverge";
|
|
3199
3361
|
return { t, piece, span, divergeId };
|
|
3200
3362
|
};
|
|
3363
|
+
const aimEndAt = (piece, to) => {
|
|
3364
|
+
const dx = to.x - piece.x;
|
|
3365
|
+
const dy = to.y - piece.y;
|
|
3366
|
+
const len = Math.hypot(dx, dy);
|
|
3367
|
+
if (!(len > 0)) return;
|
|
3368
|
+
piece.rotationDeg = Math.atan2(dy, dx) * 180 / Math.PI;
|
|
3369
|
+
piece.lengthInches = len;
|
|
3370
|
+
};
|
|
3201
3371
|
const layFlex = (label, hostY, fromPos, toPos, occupied, cuts) => {
|
|
3202
3372
|
const out = flexPieces({ fromPos, toPos, maxPieceInches: maxPiece, occupied, cuts: cuts ?? null });
|
|
3203
|
-
out.
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
return out;
|
|
3373
|
+
const placed = out.map((f, i) => ({
|
|
3374
|
+
id: `f-${label}-${i}`,
|
|
3375
|
+
partId: flexId,
|
|
3376
|
+
x: f.fromPos,
|
|
3377
|
+
y: hostY,
|
|
3378
|
+
rotationDeg: 0,
|
|
3379
|
+
lengthInches: r3(f.lengthInches)
|
|
3380
|
+
}));
|
|
3381
|
+
pieces.push(...placed);
|
|
3382
|
+
return placed;
|
|
3214
3383
|
};
|
|
3215
3384
|
const turnoutsOn = (trackId, hostY) => {
|
|
3216
3385
|
const laid = turnouts.filter((t) => t.onTrack === trackId && chosen.has(t.id)).map((t) => layTurnout(t, hostY)).sort((a, b) => a.span.fromPos - b.span.fromPos);
|
|
@@ -3223,19 +3392,64 @@ function docToGraph(doc, answers = {}, library = BUILT_IN_TRACK_PARTS) {
|
|
|
3223
3392
|
clashed.add(cur.t.id);
|
|
3224
3393
|
const gap = Math.abs(cur.t.pos - prev.t.pos);
|
|
3225
3394
|
const body = prev.span.toPos - prev.span.fromPos;
|
|
3395
|
+
const asm = trackById.get(cur.t.divergeTrack)?.role === "crossover";
|
|
3226
3396
|
notLaid.push({
|
|
3227
3397
|
id: cur.t.divergeTrack,
|
|
3228
|
-
why: `${prev.t.name || prev.t.id} and ${cur.t.name || cur.t.id} are ${gap.toFixed(1)}\u2033 apart on the same track, but the turnout chosen is ${body.toFixed(1)}\u2033 long \u2014 their mouldings would overlap,
|
|
3398
|
+
why: asm ? `this is a crossover \u2014 one assembly, not ${gap.toFixed(1)}\u2033 between two separate turnouts. Laying it needs the crossover product itself; pick one on the track and it can be placed as the single piece it is` : `${prev.t.name || prev.t.id} and ${cur.t.name || cur.t.id} are ${gap.toFixed(1)}\u2033 apart on the same track, but the turnout chosen is ${body.toFixed(1)}\u2033 long \u2014 their mouldings would overlap, so they cannot both be where the document puts them`
|
|
3229
3399
|
});
|
|
3230
3400
|
}
|
|
3231
3401
|
}
|
|
3232
3402
|
return laid.filter((l) => !clashed.has(l.t.id));
|
|
3233
3403
|
};
|
|
3404
|
+
const assemblies = (() => {
|
|
3405
|
+
const out = [];
|
|
3406
|
+
const byPart = /* @__PURE__ */ new Map();
|
|
3407
|
+
for (const t of tracks) {
|
|
3408
|
+
if (t.role !== "crossover" || !t.crossoverPartId) continue;
|
|
3409
|
+
const list = byPart.get(t.crossoverPartId) ?? [];
|
|
3410
|
+
list.push(t);
|
|
3411
|
+
byPart.set(t.crossoverPartId, list);
|
|
3412
|
+
}
|
|
3413
|
+
for (const [partId, connectors] of byPart) {
|
|
3414
|
+
const part = partById.get(partId);
|
|
3415
|
+
if (!part || part.kind !== "crossover" || partGeometryGap(part)) continue;
|
|
3416
|
+
const geom = crossoverAssembly(part);
|
|
3417
|
+
if (!geom) continue;
|
|
3418
|
+
const trackIds = new Set(connectors.map((c) => c.id));
|
|
3419
|
+
const mine = turnouts.filter((t) => trackIds.has(t.divergeTrack));
|
|
3420
|
+
if (!mine.length) continue;
|
|
3421
|
+
const centre = mine.reduce((a, t) => a + t.pos, 0) / mine.length;
|
|
3422
|
+
const x0 = centre - geom.lengthInches / 2;
|
|
3423
|
+
const spread = Math.max(...mine.map((t) => t.pos)) - Math.min(...mine.map((t) => t.pos));
|
|
3424
|
+
if (Math.abs(spread - geom.crossingRunInches) > 0.05)
|
|
3425
|
+
warnings.push(
|
|
3426
|
+
`this document puts the crossover's point-sets ${spread.toFixed(2)}\u2033 apart, but a ${part.name ?? partId} is ${geom.crossingRunInches.toFixed(2)}\u2033 \u2014 the assembly is laid to its own dimensions, centred where the document has it`
|
|
3427
|
+
);
|
|
3428
|
+
out.push({ part, geom, x0, trackIds, turnoutIds: new Set(mine.map((t) => t.id)) });
|
|
3429
|
+
}
|
|
3430
|
+
return out;
|
|
3431
|
+
})();
|
|
3432
|
+
const assemblyTracks = new Set(assemblies.flatMap((a) => [...a.trackIds]));
|
|
3234
3433
|
const main = trackById.get(MAIN_TRACK_ID) ?? tracks.find((t) => t.role === "main");
|
|
3235
3434
|
if (!main) return refuse("this module's document has no mainline to convert");
|
|
3236
3435
|
const mains = [main, ...tracks.filter((t) => t.role === "main" && t.id !== main.id)];
|
|
3237
3436
|
const laidTurnouts = /* @__PURE__ */ new Map();
|
|
3238
3437
|
const done = /* @__PURE__ */ new Set();
|
|
3438
|
+
const mainY = laneOffsetAt(mains[0]?.lane ?? 0, 0);
|
|
3439
|
+
for (const [i, a] of assemblies.entries()) {
|
|
3440
|
+
pieces.push({
|
|
3441
|
+
id: `xo-${i}`,
|
|
3442
|
+
partId: a.part.id,
|
|
3443
|
+
x: r3(a.x0),
|
|
3444
|
+
y: mainY,
|
|
3445
|
+
rotationDeg: 0,
|
|
3446
|
+
...a.part.name ? { name: a.part.name } : {}
|
|
3447
|
+
});
|
|
3448
|
+
}
|
|
3449
|
+
const assemblySpans = assemblies.map((a) => ({
|
|
3450
|
+
fromPos: a.x0,
|
|
3451
|
+
toPos: a.x0 + a.geom.lengthInches
|
|
3452
|
+
}));
|
|
3239
3453
|
for (const m of mains) {
|
|
3240
3454
|
const y = laneOffsetAt(m.lane ?? 0, 0);
|
|
3241
3455
|
const on = turnoutsOn(m.id, y);
|
|
@@ -3243,9 +3457,32 @@ function docToGraph(doc, answers = {}, library = BUILT_IN_TRACK_PARTS) {
|
|
|
3243
3457
|
pieces.push(l.piece);
|
|
3244
3458
|
laidTurnouts.set(l.t.id, l);
|
|
3245
3459
|
}
|
|
3246
|
-
|
|
3460
|
+
const laidFlex = layFlex(
|
|
3461
|
+
m.id,
|
|
3462
|
+
y,
|
|
3463
|
+
m.fromPos ?? 0,
|
|
3464
|
+
m.toPos ?? mainLen,
|
|
3465
|
+
[...on.map((l) => l.span), ...assemblySpans],
|
|
3466
|
+
m.flexCuts
|
|
3467
|
+
);
|
|
3468
|
+
if (m !== mains[0]) {
|
|
3469
|
+
for (const a of assemblies) {
|
|
3470
|
+
const west = a.x0;
|
|
3471
|
+
const east = a.x0 + a.geom.lengthInches;
|
|
3472
|
+
const target = mainY + a.geom.spacingInches;
|
|
3473
|
+
for (const f of laidFlex) {
|
|
3474
|
+
const end = f.x + (f.lengthInches ?? 0);
|
|
3475
|
+
if (Math.abs(end - west) < 1e-6) aimEndAt(f, { x: west, y: target });
|
|
3476
|
+
else if (Math.abs(f.x - east) < 1e-6) {
|
|
3477
|
+
f.y = target;
|
|
3478
|
+
aimEndAt(f, { x: end, y });
|
|
3479
|
+
}
|
|
3480
|
+
}
|
|
3481
|
+
}
|
|
3482
|
+
}
|
|
3247
3483
|
done.add(m.id);
|
|
3248
3484
|
}
|
|
3485
|
+
for (const id of assemblyTracks) done.add(id);
|
|
3249
3486
|
let progressed = true;
|
|
3250
3487
|
while (progressed) {
|
|
3251
3488
|
progressed = false;
|
|
@@ -3999,6 +4236,6 @@ function poseOverridesFromDoc(doc) {
|
|
|
3999
4236
|
return out;
|
|
4000
4237
|
}
|
|
4001
4238
|
|
|
4002
|
-
export { ATLAS_CODE55_N, BUILT_IN_TRACK_PARTS, BUMPER_DRAWN_INCHES, 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_MAIN_MIN_RADIUS_INCHES, FREEMO_REVERSE_CURVE_STRAIGHT_INCHES, FREEMO_TRACK_SPACING_INCHES, GENERIC_END_OF_TRACK, JOINT_SNAP_INCHES, MAIN2_TRACK_ID, MAIN_TRACK_ID, N_CAR_LENGTH_INCHES, N_SCALE_RATIO, PINCH_EASE_INCHES, RAIL_GAUGE_INCHES, TIE_HALF_LENGTH_INCHES, TRACK_PART_KINDS, TURNOUT_LEAD_INCHES_PER_FROG, WHOLE_MODULE_SECTION_ID, asModuleSchematic, assessSectionEnd, assessSectionJoint, benchworkBand, benchworkOutline, buildCrossover, buildPassingSiding, buildTrackGraph, buildTransition, carCapacity, checkEndplateWidth, clearancePointPastFrogInches, crossoverPinches, deriveEndplatePoses, deriveGraphDoc, divergeSideForHand, docToGraph, docToState, emptyEditorState, endplateCentreOffsetInches, endplateEdgePose, endplateFaceSegments, endplateLead, endplateTrackOffsetInches, endplateWidthInches, fitFlexBetween, flexPartFor, flexParts, flexPieces, flexRunEnd, flexUsage, frogCasting, frogNumberFromName, fromSectionRelative, geometryTurnDegrees, graphToDoc, hasNoFarEndplate, importedPartToTrackPart, inchesToScaleFeet, insertIntoRun, isLoopDoc, isTransitionTurnout, laneOffsetAt, leadInchesForSize, maxFlexPieceInches, mergeImportedParts, mergeStoredParts, moduleCenterline, moduleConversionReport, moduleFeatures, moduleFootprint, moduleLengthFromSections, moduleSections, nextId, parseXtpLibrary, partExtent, partExtentForSize, partGeometry, partGeometryGap, partOutlineAtFrog, partsPlaceable, pastFrogInchesForSize, pathLengthInches, pieceHand, piecePartNumber, pieceRoutePaths, placedJoints, poseNeedsManual, poseOverridesFromDoc, remapPos, resizeFlexPiece, resolveGraphAnchor, returnLoop, sampleBenchworkOutline, samplePartSegments, samplePath, scaleFeetToInches, sectionAdjacency, sectionBand, sectionBreaksFromSections, sectionComponents, sectionFootprints, sectionNeighbours, sectionSpans, sectionSpansOrWhole, sectionalArcInches, sectionedCenterline, sectionedEndPose, sliceCenterline, snapPiece, spanOverhang, stateToDoc, storedPartToTrackPart, toSectionRelative, trackMeetsEndplateIssues, trackPart, trackPath, turnoutClosure, turnoutFacing, turnoutOccupiedSpan, turnoutPartForSize, usableCapacity, walkTrackGraph };
|
|
4239
|
+
export { ATLAS_CODE55_N, BUILT_IN_TRACK_PARTS, BUMPER_DRAWN_INCHES, 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_MAIN_MIN_RADIUS_INCHES, FREEMO_REVERSE_CURVE_STRAIGHT_INCHES, FREEMO_TRACK_SPACING_INCHES, GENERIC_END_OF_TRACK, JOINT_SNAP_INCHES, MAIN2_TRACK_ID, MAIN_TRACK_ID, N_CAR_LENGTH_INCHES, N_SCALE_RATIO, PINCH_EASE_INCHES, RAIL_GAUGE_INCHES, TIE_HALF_LENGTH_INCHES, TRACK_PART_KINDS, TURNOUT_LEAD_INCHES_PER_FROG, WHOLE_MODULE_SECTION_ID, asModuleSchematic, assessSectionEnd, assessSectionJoint, benchworkBand, benchworkOutline, buildCrossover, buildPassingSiding, buildTrackGraph, buildTransition, carCapacity, checkEndplateWidth, clearancePointPastFrogInches, crossoverAssembly, crossoverPinches, deriveEndplatePoses, deriveGraphDoc, divergeSideForHand, docToGraph, docToState, emptyEditorState, endplateCentreOffsetInches, endplateEdgePose, endplateFaceSegments, endplateLead, endplateTrackOffsetInches, endplateWidthInches, fitFlexBetween, flexPartFor, flexParts, flexPieces, flexRunEnd, flexUsage, frogCasting, frogNumberFromName, fromSectionRelative, geometryTurnDegrees, graphToDoc, hasNoFarEndplate, importedPartToTrackPart, inchesToScaleFeet, insertIntoRun, isLoopDoc, isTransitionTurnout, laneOffsetAt, leadInchesForSize, maxFlexPieceInches, mergeImportedParts, mergeStoredParts, moduleCenterline, moduleConversionReport, moduleFeatures, moduleFootprint, moduleLengthFromSections, moduleSections, nextId, parseXtpLibrary, partExtent, partExtentForSize, partGeometry, partGeometryGap, partOutlineAtFrog, partsPlaceable, pastFrogInchesForSize, pathLengthInches, pieceHand, piecePartNumber, pieceRoutePaths, placedJoints, poseNeedsManual, poseOverridesFromDoc, remapPos, resizeFlexPiece, resolveGraphAnchor, returnLoop, sampleBenchworkOutline, samplePartSegments, samplePath, scaleFeetToInches, sectionAdjacency, sectionBand, sectionBreaksFromSections, sectionComponents, sectionFootprints, sectionNeighbours, sectionSpans, sectionSpansOrWhole, sectionalArcInches, sectionedCenterline, sectionedEndPose, sliceCenterline, snapPiece, spanOverhang, stateToDoc, storedPartToTrackPart, toSectionRelative, trackMeetsEndplateIssues, trackPart, trackPath, turnoutClosure, turnoutFacing, turnoutOccupiedSpan, turnoutPartForSize, usableCapacity, walkTrackGraph };
|
|
4003
4240
|
//# sourceMappingURL=index.js.map
|
|
4004
4241
|
//# sourceMappingURL=index.js.map
|