@willcgage/module-schematic 0.70.0 → 0.71.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
@@ -428,10 +428,18 @@ interface ModuleFootprintInput {
428
428
  /** Where each endplate's CENTRE sits relative to the main centre-line at that
429
429
  * end, inches (signed, along the +normal). Free-moN puts a **double**-track
430
430
  * plate's two tracks 9/16″ either side of its centre, so with Main 1 on the
431
- * centre-line the plate centre is half a track spacing up pass
432
- * `FREEMO_TRACK_SPACING_INCHES / 2`. Single track crosses at the centre ⇒ 0
433
- * (the default), and an off-centre track is a signed value. */
431
+ * centre-line the plate centre is half a track spacing toward Main 2. Single
432
+ * track crosses at the centre ⇒ 0, and an off-centre track is a signed value.
433
+ * {@link endplateCentreOffsetInches} computes it from what the owner authored.
434
+ *
435
+ * ⚠️ OMITTING an end no longer means 0 — it means "use §2.0", which is a
436
+ * straddle on a double end. Centring a double plate on Main 1 drew its pair
437
+ * wholly to one side, which is what the read-only and catalog views did by
438
+ * passing nothing at all (#190). Pass an explicit number to place a plate. */
434
439
  endplateTrackOffsets?: Record<string, number>;
440
+ /** Whether Main 2 runs BELOW Main 1 (`mainsSwapped`, #131). Only affects which
441
+ * way an unauthored double plate straddles. */
442
+ mainsSwapped?: boolean;
435
443
  /** Authored benchwork outline (module-local inches), or absent for the band. */
436
444
  outline?: BenchworkPoint[] | null;
437
445
  /** Authored benchwork HOLE — the inner boundary punched out of `outline` to
@@ -637,23 +645,45 @@ declare function endplateFaceSegments(center: BenchworkPoint[], widthA?: number,
637
645
  */
638
646
  declare function moduleFootprint(input: ModuleFootprintInput): ModuleFootprint;
639
647
  /**
640
- * The offset from the main centre-line to an endplate's CENTRE, inches — the
641
- * Free-moN geometry. A **double**-track end carries its two tracks 9/16″ either
642
- * side of the plate centre (§2.0 RP), and Main 1 sits on the centre-line, so the
643
- * plate centre is half a track spacing up. A single track crosses at the centre.
648
+ * Where an endplate's CENTRE sits relative to MAIN 1 — the renderer's framing,
649
+ * and the one number a drawing needs to place the plate.
650
+ *
651
+ * THE single definition. Two callers had their own: the builder computed it
652
+ * inline (swap-aware), and the read-only/catalog footprint **passed nothing at
653
+ * all**, so every plate there was centred on Main 1 — a double end drew its pair
654
+ * entirely to one side of the plate instead of straddling it (#190).
655
+ *
656
+ * §2.0 puts a double end's two tracks 0.5625″ either side of the plate centre,
657
+ * so the plate centre is half a track spacing away from Main 1 — **toward Main
658
+ * 2**, which is why the swap matters: with Main 2 below, the plate centre is
659
+ * below too. An authored offset always wins (an off-centre end is legal since
660
+ * the 20220628 revision), and is given in the standard's own framing — Main 1's
661
+ * distance from the plate centre — so it comes back negated here.
644
662
  */
645
- declare function endplateTrackOffsetFor(config: TrackConfig | "none" | undefined, authoredTrackOffset?: number | null): number;
663
+ declare function endplateCentreOffsetInches(input: {
664
+ config?: TrackConfig | "none" | null;
665
+ /** Main 1's signed distance from the plate centre, as authored. */
666
+ authoredTrackOffsetInches?: number | null;
667
+ /** Whether Main 2 runs below Main 1 (the mains are swapped). */
668
+ main2Below?: boolean;
669
+ }): number;
646
670
  /**
647
671
  * Where an endplate's PRIMARY track (Main 1) crosses, as a signed distance from
648
672
  * the plate's CENTRE — the standard's own framing. Authored value wins; absent
649
673
  * falls back to the §2.0 recommendations: a single track centred (0), a double
650
674
  * straddling so its two tracks land ∓ half the track spacing (Main 1 low).
651
675
  */
652
- declare function endplateTrackOffsetInches(authored: number | null | undefined, config: TrackConfig | "none" | undefined): number;
676
+ declare function endplateTrackOffsetInches(authored: number | null | undefined, config: TrackConfig | "none" | undefined,
677
+ /** Whether Main 2 runs BELOW Main 1 (the mains are swapped). The pair
678
+ * straddles the plate centre either way, so Main 1 sits on the side AWAY from
679
+ * Main 2 — low when Main 2 is high, high when it's low. Omitting this
680
+ * hard-coded "Main 2 is above", which is the assumption behind #190. */
681
+ main2Below?: boolean): number;
653
682
  /** A Free-moN conformance problem with an endplate's width/track placement. */
654
683
  interface EndplateWidthIssue {
655
- /** "narrow" = below the 12″ minimum; "clearance" = a track too near a fascia. */
656
- code: "narrow" | "clearance";
684
+ /** "narrow" = below the 12″ minimum; "clearance" = a track too near a fascia;
685
+ * "offcentre" = a double end whose pair doesn't straddle the plate centre. */
686
+ code: "narrow" | "clearance" | "offcentre";
657
687
  /** Plain-language problem, for the author. */
658
688
  message: string;
659
689
  /** The width that would satisfy this rule, inches. */
@@ -676,6 +706,10 @@ declare function checkEndplateWidth(input: {
676
706
  widthInches?: number | null;
677
707
  config?: TrackConfig | "none" | null;
678
708
  trackOffsetInches?: number | null;
709
+ /** Whether Main 2 runs BELOW Main 1 (the mains are swapped). Without it a
710
+ * double end's second track was assumed to be one spacing ABOVE Main 1, so a
711
+ * swapped pair was measured on the wrong side of the plate (#190). */
712
+ main2Below?: boolean;
679
713
  }): EndplateWidthIssue[];
680
714
  /**
681
715
  * Whether a module presents NO far endplate — one conforming face only.
@@ -1810,4 +1844,4 @@ declare function poseOverridesFromDoc(doc: ModuleSchematicDoc): Record<string, {
1810
1844
  heading: number;
1811
1845
  }>;
1812
1846
 
1813
- export { ATLAS_CODE55_N, BUILT_IN_TRACK_PARTS, type BenchworkPoint, type BranchConnector, CODE55_RAIL_HEIGHT_INCHES, 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, FREEMO_ENDPLATE_TRACK_FASCIA_CLEARANCE_INCHES, FREEMO_ENDPLATE_WIDTH_MIN_INCHES, FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES, FREEMO_TRACK_SPACING_INCHES, 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 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 SectionFootprint, type SectionRelativePos, type SignalFacing, type SignalSide, type StoredTrackPart, TIE_HALF_LENGTH_INCHES, TURNOUT_LEAD_INCHES_PER_FROG, type TrackConfig, type TrackPart, type TrackRole, type TurnoutClosure, type TurnoutKind, WHOLE_MODULE_SECTION_ID, asModuleSchematic, benchworkBand, benchworkOutline, buildCrossover, buildPassingSiding, buildTransition, carCapacity, checkEndplateWidth, deriveEndplatePoses, divergeSideForHand, docToState, emptyEditorState, endplateFaceSegments, endplateLead, endplateTrackOffsetFor, endplateTrackOffsetInches, endplateWidthInches, frogCasting, frogNumberFromName, fromSectionRelative, geometryTurnDegrees, hasNoFarEndplate, importedPartToTrackPart, inchesToScaleFeet, isLoopDoc, isTransitionTurnout, leadInchesForSize, mergeImportedParts, mergeStoredParts, moduleCenterline, moduleFeatures, moduleFootprint, moduleLengthFromSections, moduleSections, nextId, parseXtpLibrary, partExtent, partExtentForSize, partOutlineAtFrog, pastFrogInchesForSize, pathLengthInches, poseNeedsManual, poseOverridesFromDoc, remapPos, returnLoop, sampleBenchworkOutline, samplePartSegments, samplePath, scaleFeetToInches, sectionAdjacency, sectionBand, sectionBreaksFromSections, sectionComponents, sectionFootprints, sectionNeighbours, sectionSpans, sectionSpansOrWhole, sectionedCenterline, sectionedEndPose, sliceCenterline, stateToDoc, storedPartToTrackPart, toSectionRelative, trackMeetsEndplateIssues, trackPart, trackPath, turnoutClosure, turnoutPartForSize };
1847
+ export { ATLAS_CODE55_N, BUILT_IN_TRACK_PARTS, type BenchworkPoint, type BranchConnector, CODE55_RAIL_HEIGHT_INCHES, 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, FREEMO_ENDPLATE_TRACK_FASCIA_CLEARANCE_INCHES, FREEMO_ENDPLATE_WIDTH_MIN_INCHES, FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES, FREEMO_TRACK_SPACING_INCHES, 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 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 SectionFootprint, type SectionRelativePos, type SignalFacing, type SignalSide, type StoredTrackPart, TIE_HALF_LENGTH_INCHES, TURNOUT_LEAD_INCHES_PER_FROG, type TrackConfig, type TrackPart, type TrackRole, type TurnoutClosure, type TurnoutKind, WHOLE_MODULE_SECTION_ID, asModuleSchematic, benchworkBand, benchworkOutline, buildCrossover, buildPassingSiding, buildTransition, carCapacity, checkEndplateWidth, deriveEndplatePoses, divergeSideForHand, docToState, emptyEditorState, endplateCentreOffsetInches, endplateFaceSegments, endplateLead, endplateTrackOffsetInches, endplateWidthInches, frogCasting, frogNumberFromName, fromSectionRelative, geometryTurnDegrees, hasNoFarEndplate, importedPartToTrackPart, inchesToScaleFeet, isLoopDoc, isTransitionTurnout, leadInchesForSize, mergeImportedParts, mergeStoredParts, moduleCenterline, moduleFeatures, moduleFootprint, moduleLengthFromSections, moduleSections, nextId, parseXtpLibrary, partExtent, partExtentForSize, partOutlineAtFrog, pastFrogInchesForSize, pathLengthInches, poseNeedsManual, poseOverridesFromDoc, remapPos, returnLoop, sampleBenchworkOutline, samplePartSegments, samplePath, scaleFeetToInches, sectionAdjacency, sectionBand, sectionBreaksFromSections, sectionComponents, sectionFootprints, sectionNeighbours, sectionSpans, sectionSpansOrWhole, sectionedCenterline, sectionedEndPose, sliceCenterline, stateToDoc, storedPartToTrackPart, toSectionRelative, trackMeetsEndplateIssues, trackPart, trackPath, turnoutClosure, turnoutPartForSize };
package/dist/index.d.ts CHANGED
@@ -428,10 +428,18 @@ interface ModuleFootprintInput {
428
428
  /** Where each endplate's CENTRE sits relative to the main centre-line at that
429
429
  * end, inches (signed, along the +normal). Free-moN puts a **double**-track
430
430
  * plate's two tracks 9/16″ either side of its centre, so with Main 1 on the
431
- * centre-line the plate centre is half a track spacing up pass
432
- * `FREEMO_TRACK_SPACING_INCHES / 2`. Single track crosses at the centre ⇒ 0
433
- * (the default), and an off-centre track is a signed value. */
431
+ * centre-line the plate centre is half a track spacing toward Main 2. Single
432
+ * track crosses at the centre ⇒ 0, and an off-centre track is a signed value.
433
+ * {@link endplateCentreOffsetInches} computes it from what the owner authored.
434
+ *
435
+ * ⚠️ OMITTING an end no longer means 0 — it means "use §2.0", which is a
436
+ * straddle on a double end. Centring a double plate on Main 1 drew its pair
437
+ * wholly to one side, which is what the read-only and catalog views did by
438
+ * passing nothing at all (#190). Pass an explicit number to place a plate. */
434
439
  endplateTrackOffsets?: Record<string, number>;
440
+ /** Whether Main 2 runs BELOW Main 1 (`mainsSwapped`, #131). Only affects which
441
+ * way an unauthored double plate straddles. */
442
+ mainsSwapped?: boolean;
435
443
  /** Authored benchwork outline (module-local inches), or absent for the band. */
436
444
  outline?: BenchworkPoint[] | null;
437
445
  /** Authored benchwork HOLE — the inner boundary punched out of `outline` to
@@ -637,23 +645,45 @@ declare function endplateFaceSegments(center: BenchworkPoint[], widthA?: number,
637
645
  */
638
646
  declare function moduleFootprint(input: ModuleFootprintInput): ModuleFootprint;
639
647
  /**
640
- * The offset from the main centre-line to an endplate's CENTRE, inches — the
641
- * Free-moN geometry. A **double**-track end carries its two tracks 9/16″ either
642
- * side of the plate centre (§2.0 RP), and Main 1 sits on the centre-line, so the
643
- * plate centre is half a track spacing up. A single track crosses at the centre.
648
+ * Where an endplate's CENTRE sits relative to MAIN 1 — the renderer's framing,
649
+ * and the one number a drawing needs to place the plate.
650
+ *
651
+ * THE single definition. Two callers had their own: the builder computed it
652
+ * inline (swap-aware), and the read-only/catalog footprint **passed nothing at
653
+ * all**, so every plate there was centred on Main 1 — a double end drew its pair
654
+ * entirely to one side of the plate instead of straddling it (#190).
655
+ *
656
+ * §2.0 puts a double end's two tracks 0.5625″ either side of the plate centre,
657
+ * so the plate centre is half a track spacing away from Main 1 — **toward Main
658
+ * 2**, which is why the swap matters: with Main 2 below, the plate centre is
659
+ * below too. An authored offset always wins (an off-centre end is legal since
660
+ * the 20220628 revision), and is given in the standard's own framing — Main 1's
661
+ * distance from the plate centre — so it comes back negated here.
644
662
  */
645
- declare function endplateTrackOffsetFor(config: TrackConfig | "none" | undefined, authoredTrackOffset?: number | null): number;
663
+ declare function endplateCentreOffsetInches(input: {
664
+ config?: TrackConfig | "none" | null;
665
+ /** Main 1's signed distance from the plate centre, as authored. */
666
+ authoredTrackOffsetInches?: number | null;
667
+ /** Whether Main 2 runs below Main 1 (the mains are swapped). */
668
+ main2Below?: boolean;
669
+ }): number;
646
670
  /**
647
671
  * Where an endplate's PRIMARY track (Main 1) crosses, as a signed distance from
648
672
  * the plate's CENTRE — the standard's own framing. Authored value wins; absent
649
673
  * falls back to the §2.0 recommendations: a single track centred (0), a double
650
674
  * straddling so its two tracks land ∓ half the track spacing (Main 1 low).
651
675
  */
652
- declare function endplateTrackOffsetInches(authored: number | null | undefined, config: TrackConfig | "none" | undefined): number;
676
+ declare function endplateTrackOffsetInches(authored: number | null | undefined, config: TrackConfig | "none" | undefined,
677
+ /** Whether Main 2 runs BELOW Main 1 (the mains are swapped). The pair
678
+ * straddles the plate centre either way, so Main 1 sits on the side AWAY from
679
+ * Main 2 — low when Main 2 is high, high when it's low. Omitting this
680
+ * hard-coded "Main 2 is above", which is the assumption behind #190. */
681
+ main2Below?: boolean): number;
653
682
  /** A Free-moN conformance problem with an endplate's width/track placement. */
654
683
  interface EndplateWidthIssue {
655
- /** "narrow" = below the 12″ minimum; "clearance" = a track too near a fascia. */
656
- code: "narrow" | "clearance";
684
+ /** "narrow" = below the 12″ minimum; "clearance" = a track too near a fascia;
685
+ * "offcentre" = a double end whose pair doesn't straddle the plate centre. */
686
+ code: "narrow" | "clearance" | "offcentre";
657
687
  /** Plain-language problem, for the author. */
658
688
  message: string;
659
689
  /** The width that would satisfy this rule, inches. */
@@ -676,6 +706,10 @@ declare function checkEndplateWidth(input: {
676
706
  widthInches?: number | null;
677
707
  config?: TrackConfig | "none" | null;
678
708
  trackOffsetInches?: number | null;
709
+ /** Whether Main 2 runs BELOW Main 1 (the mains are swapped). Without it a
710
+ * double end's second track was assumed to be one spacing ABOVE Main 1, so a
711
+ * swapped pair was measured on the wrong side of the plate (#190). */
712
+ main2Below?: boolean;
679
713
  }): EndplateWidthIssue[];
680
714
  /**
681
715
  * Whether a module presents NO far endplate — one conforming face only.
@@ -1810,4 +1844,4 @@ declare function poseOverridesFromDoc(doc: ModuleSchematicDoc): Record<string, {
1810
1844
  heading: number;
1811
1845
  }>;
1812
1846
 
1813
- export { ATLAS_CODE55_N, BUILT_IN_TRACK_PARTS, type BenchworkPoint, type BranchConnector, CODE55_RAIL_HEIGHT_INCHES, 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, FREEMO_ENDPLATE_TRACK_FASCIA_CLEARANCE_INCHES, FREEMO_ENDPLATE_WIDTH_MIN_INCHES, FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES, FREEMO_TRACK_SPACING_INCHES, 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 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 SectionFootprint, type SectionRelativePos, type SignalFacing, type SignalSide, type StoredTrackPart, TIE_HALF_LENGTH_INCHES, TURNOUT_LEAD_INCHES_PER_FROG, type TrackConfig, type TrackPart, type TrackRole, type TurnoutClosure, type TurnoutKind, WHOLE_MODULE_SECTION_ID, asModuleSchematic, benchworkBand, benchworkOutline, buildCrossover, buildPassingSiding, buildTransition, carCapacity, checkEndplateWidth, deriveEndplatePoses, divergeSideForHand, docToState, emptyEditorState, endplateFaceSegments, endplateLead, endplateTrackOffsetFor, endplateTrackOffsetInches, endplateWidthInches, frogCasting, frogNumberFromName, fromSectionRelative, geometryTurnDegrees, hasNoFarEndplate, importedPartToTrackPart, inchesToScaleFeet, isLoopDoc, isTransitionTurnout, leadInchesForSize, mergeImportedParts, mergeStoredParts, moduleCenterline, moduleFeatures, moduleFootprint, moduleLengthFromSections, moduleSections, nextId, parseXtpLibrary, partExtent, partExtentForSize, partOutlineAtFrog, pastFrogInchesForSize, pathLengthInches, poseNeedsManual, poseOverridesFromDoc, remapPos, returnLoop, sampleBenchworkOutline, samplePartSegments, samplePath, scaleFeetToInches, sectionAdjacency, sectionBand, sectionBreaksFromSections, sectionComponents, sectionFootprints, sectionNeighbours, sectionSpans, sectionSpansOrWhole, sectionedCenterline, sectionedEndPose, sliceCenterline, stateToDoc, storedPartToTrackPart, toSectionRelative, trackMeetsEndplateIssues, trackPart, trackPath, turnoutClosure, turnoutPartForSize };
1847
+ export { ATLAS_CODE55_N, BUILT_IN_TRACK_PARTS, type BenchworkPoint, type BranchConnector, CODE55_RAIL_HEIGHT_INCHES, 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, FREEMO_ENDPLATE_TRACK_FASCIA_CLEARANCE_INCHES, FREEMO_ENDPLATE_WIDTH_MIN_INCHES, FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES, FREEMO_TRACK_SPACING_INCHES, 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 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 SectionFootprint, type SectionRelativePos, type SignalFacing, type SignalSide, type StoredTrackPart, TIE_HALF_LENGTH_INCHES, TURNOUT_LEAD_INCHES_PER_FROG, type TrackConfig, type TrackPart, type TrackRole, type TurnoutClosure, type TurnoutKind, WHOLE_MODULE_SECTION_ID, asModuleSchematic, benchworkBand, benchworkOutline, buildCrossover, buildPassingSiding, buildTransition, carCapacity, checkEndplateWidth, deriveEndplatePoses, divergeSideForHand, docToState, emptyEditorState, endplateCentreOffsetInches, endplateFaceSegments, endplateLead, endplateTrackOffsetInches, endplateWidthInches, frogCasting, frogNumberFromName, fromSectionRelative, geometryTurnDegrees, hasNoFarEndplate, importedPartToTrackPart, inchesToScaleFeet, isLoopDoc, isTransitionTurnout, leadInchesForSize, mergeImportedParts, mergeStoredParts, moduleCenterline, moduleFeatures, moduleFootprint, moduleLengthFromSections, moduleSections, nextId, parseXtpLibrary, partExtent, partExtentForSize, partOutlineAtFrog, pastFrogInchesForSize, pathLengthInches, poseNeedsManual, poseOverridesFromDoc, remapPos, returnLoop, sampleBenchworkOutline, samplePartSegments, samplePath, scaleFeetToInches, sectionAdjacency, sectionBand, sectionBreaksFromSections, sectionComponents, sectionFootprints, sectionNeighbours, sectionSpans, sectionSpansOrWhole, sectionedCenterline, sectionedEndPose, sliceCenterline, stateToDoc, storedPartToTrackPart, toSectionRelative, trackMeetsEndplateIssues, trackPart, trackPath, turnoutClosure, turnoutPartForSize };
package/dist/index.js CHANGED
@@ -469,8 +469,12 @@ function moduleFootprint(input) {
469
469
  const widthA = endplateWidthFor(input.endplateWidths, "A");
470
470
  const widthB = endplateWidthFor(input.endplateWidths, "B");
471
471
  const authored = benchworkOutline(input);
472
- const offA = input.endplateTrackOffsets?.["A"] ?? 0;
473
- const offB = input.endplateTrackOffsets?.["B"] ?? 0;
472
+ const offOf = (i, id) => input.endplateTrackOffsets?.[id] ?? endplateCentreOffsetInches({
473
+ config: input.endplateConfigs?.[i],
474
+ main2Below: input.mainsSwapped === true
475
+ });
476
+ const offA = offOf(0, "A");
477
+ const offB = offOf(1, "B");
474
478
  const secs = input.sections ?? [];
475
479
  const sectionsOwnShape = secs.length > 1 || secs.some((s) => (s.outline?.length ?? 0) >= 3);
476
480
  const sectionOutlines = sectionsOwnShape ? sectionFootprints(input, {
@@ -494,13 +498,19 @@ function moduleFootprint(input) {
494
498
  sectionOutlines
495
499
  };
496
500
  }
497
- function endplateTrackOffsetFor(config, authoredTrackOffset) {
498
- const v = -endplateTrackOffsetInches(authoredTrackOffset, config);
501
+ function endplateCentreOffsetInches(input) {
502
+ const v = -endplateTrackOffsetInches(
503
+ input.authoredTrackOffsetInches,
504
+ input.config ?? void 0,
505
+ input.main2Below
506
+ );
499
507
  return v === 0 ? 0 : v;
500
508
  }
501
- function endplateTrackOffsetInches(authored, config) {
509
+ function endplateTrackOffsetInches(authored, config, main2Below = false) {
502
510
  if (typeof authored === "number" && Number.isFinite(authored)) return authored;
503
- return config === "double" ? -FREEMO_TRACK_SPACING_INCHES / 2 : 0;
511
+ if (config !== "double") return 0;
512
+ const half = FREEMO_TRACK_SPACING_INCHES / 2;
513
+ return main2Below ? half : -half;
504
514
  }
505
515
  function checkEndplateWidth(input) {
506
516
  const width = endplateWidthInches(input);
@@ -512,8 +522,13 @@ function checkEndplateWidth(input) {
512
522
  requiredInches: FREEMO_ENDPLATE_WIDTH_MIN_INCHES
513
523
  });
514
524
  }
515
- const off = endplateTrackOffsetInches(input.trackOffsetInches, input.config ?? void 0);
516
- const centres = input.config === "double" ? [off, off + FREEMO_TRACK_SPACING_INCHES] : [off];
525
+ const off = endplateTrackOffsetInches(
526
+ input.trackOffsetInches,
527
+ input.config ?? void 0,
528
+ input.main2Below
529
+ );
530
+ const second = off + (input.main2Below ? -1 : 1) * FREEMO_TRACK_SPACING_INCHES;
531
+ const centres = input.config === "double" ? [off, second] : [off];
517
532
  const worst = Math.max(...centres.map((c) => Math.abs(c)));
518
533
  const clearance = width / 2 - worst;
519
534
  if (clearance < FREEMO_ENDPLATE_TRACK_FASCIA_CLEARANCE_INCHES) {
@@ -524,6 +539,18 @@ function checkEndplateWidth(input) {
524
539
  requiredInches: required
525
540
  });
526
541
  }
542
+ if (input.config === "double") {
543
+ const mid = (off + second) / 2;
544
+ if (Math.abs(mid) > 0.01) {
545
+ issues.push({
546
+ code: "offcentre",
547
+ message: `The two tracks sit ${round2(Math.abs(mid))}\u2033 off the centre of this endplate. The standard recommends they straddle it \u2014 Main 1 at ${round2((input.main2Below ? 1 : -1) * (FREEMO_TRACK_SPACING_INCHES / 2))}\u2033. Clear the offset to use that.`,
548
+ // Not a width problem — no wider plate fixes it — so hand back the
549
+ // width unchanged rather than imply one would.
550
+ requiredInches: width
551
+ });
552
+ }
553
+ }
527
554
  return issues;
528
555
  }
529
556
  var round2 = (n) => Math.round(n * 100) / 100;
@@ -2146,6 +2173,6 @@ function poseOverridesFromDoc(doc) {
2146
2173
  return out;
2147
2174
  }
2148
2175
 
2149
- export { ATLAS_CODE55_N, BUILT_IN_TRACK_PARTS, CODE55_RAIL_HEIGHT_INCHES, ENDPLATE_FASCIA_CLEAR_INCHES, ENDPLATE_LEAD_INCHES, 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, benchworkBand, benchworkOutline, buildCrossover, buildPassingSiding, buildTransition, carCapacity, checkEndplateWidth, deriveEndplatePoses, divergeSideForHand, docToState, emptyEditorState, endplateFaceSegments, endplateLead, endplateTrackOffsetFor, endplateTrackOffsetInches, endplateWidthInches, frogCasting, frogNumberFromName, fromSectionRelative, geometryTurnDegrees, hasNoFarEndplate, importedPartToTrackPart, inchesToScaleFeet, isLoopDoc, isTransitionTurnout, leadInchesForSize, mergeImportedParts, mergeStoredParts, moduleCenterline, moduleFeatures, moduleFootprint, moduleLengthFromSections, moduleSections, nextId, parseXtpLibrary, partExtent, partExtentForSize, partOutlineAtFrog, pastFrogInchesForSize, pathLengthInches, poseNeedsManual, poseOverridesFromDoc, remapPos, returnLoop, sampleBenchworkOutline, samplePartSegments, samplePath, scaleFeetToInches, sectionAdjacency, sectionBand, sectionBreaksFromSections, sectionComponents, sectionFootprints, sectionNeighbours, sectionSpans, sectionSpansOrWhole, sectionedCenterline, sectionedEndPose, sliceCenterline, stateToDoc, storedPartToTrackPart, toSectionRelative, trackMeetsEndplateIssues, trackPart, trackPath, turnoutClosure, turnoutPartForSize };
2176
+ export { ATLAS_CODE55_N, BUILT_IN_TRACK_PARTS, CODE55_RAIL_HEIGHT_INCHES, ENDPLATE_FASCIA_CLEAR_INCHES, ENDPLATE_LEAD_INCHES, 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, benchworkBand, benchworkOutline, buildCrossover, buildPassingSiding, buildTransition, carCapacity, checkEndplateWidth, deriveEndplatePoses, divergeSideForHand, docToState, emptyEditorState, endplateCentreOffsetInches, endplateFaceSegments, endplateLead, endplateTrackOffsetInches, endplateWidthInches, frogCasting, frogNumberFromName, fromSectionRelative, geometryTurnDegrees, hasNoFarEndplate, importedPartToTrackPart, inchesToScaleFeet, isLoopDoc, isTransitionTurnout, leadInchesForSize, mergeImportedParts, mergeStoredParts, moduleCenterline, moduleFeatures, moduleFootprint, moduleLengthFromSections, moduleSections, nextId, parseXtpLibrary, partExtent, partExtentForSize, partOutlineAtFrog, pastFrogInchesForSize, pathLengthInches, poseNeedsManual, poseOverridesFromDoc, remapPos, returnLoop, sampleBenchworkOutline, samplePartSegments, samplePath, scaleFeetToInches, sectionAdjacency, sectionBand, sectionBreaksFromSections, sectionComponents, sectionFootprints, sectionNeighbours, sectionSpans, sectionSpansOrWhole, sectionedCenterline, sectionedEndPose, sliceCenterline, stateToDoc, storedPartToTrackPart, toSectionRelative, trackMeetsEndplateIssues, trackPart, trackPath, turnoutClosure, turnoutPartForSize };
2150
2177
  //# sourceMappingURL=index.js.map
2151
2178
  //# sourceMappingURL=index.js.map