@willcgage/module-schematic 0.75.0 → 0.77.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
@@ -366,6 +366,35 @@ interface SchematicSection {
366
366
  geometryDegrees?: number | null;
367
367
  /** Lateral jog, for offset sections. */
368
368
  geometryOffsetInches?: number | null;
369
+ /** This board's WEST end (the A side) — see {@link SectionEnd}. */
370
+ endA?: SectionEnd | null;
371
+ /** This board's EAST end (the B side). */
372
+ endB?: SectionEnd | null;
373
+ }
374
+ /**
375
+ * One end of a board (#130).
376
+ *
377
+ * Owner's question was *"how would I update my section joints to be endplates
378
+ * from within MR?"* — and until now nothing could say it: a section had a
379
+ * length, a shape and an outline, but nothing about its ends.
380
+ *
381
+ * ⚠️ **There is deliberately no "this is an endplate" flag.** An owner could
382
+ * tick it wrongly and the registry would then tell Free-Dispatcher that two
383
+ * boards will physically mate when they won't. The geometry decides — describe
384
+ * the end and {@link assessSectionEnd} works out whether it conforms.
385
+ *
386
+ * Absent means **not described**, which is an ordinary internal joint, not a
387
+ * failing endplate: the standard exempts internal boundaries from the
388
+ * end-interface rules (#96).
389
+ */
390
+ interface SectionEnd {
391
+ /** What this end presents. `"none"` = a closed end (a bumper). */
392
+ config?: TrackConfig | "none" | null;
393
+ /** Face width, inches. Absent = the recommended default. */
394
+ widthInches?: number | null;
395
+ /** Main 1's signed distance from the face centre — the standard's own
396
+ * framing, same as an endplate's (see {@link endplateTrackOffsetInches}). */
397
+ trackOffsetInches?: number | null;
369
398
  }
370
399
  /** The authored benchwork outline, or null when a module hasn't drawn one
371
400
  * (renderers then fall back to a band derived from the endplate widths). A
@@ -843,6 +872,62 @@ declare function usableCapacity(input: {
843
872
  library?: TrackPart[];
844
873
  carLengthInches?: number;
845
874
  }): UsableCapacity;
875
+ /** What a board's end is, once the geometry has been asked (#130). */
876
+ interface SectionEndAssessment {
877
+ /** Has the owner described this end at all? Undescribed is an ordinary
878
+ * internal joint — exempt from the end-interface rules (#96) — not a failure. */
879
+ described: boolean;
880
+ /** Does it meet the standard's end-interface rules? Only a described end can. */
881
+ conforming: boolean;
882
+ /** Why it doesn't, when it doesn't. Empty for a conforming or undescribed end. */
883
+ issues: EndplateWidthIssue[];
884
+ /** The resolved values the assessment was made against. */
885
+ config: TrackConfig | "none";
886
+ widthInches: number;
887
+ trackOffsetInches: number;
888
+ }
889
+ /**
890
+ * Is this end of a board a standard endplate? (#130)
891
+ *
892
+ * ⭐ **Derived, never declared.** The issue is emphatic and the reason is good:
893
+ * a checkbox could be ticked wrongly, and the registry would then promise
894
+ * Free-Dispatcher a mating surface that doesn't exist. So the answer comes from
895
+ * the same rules {@link checkEndplateWidth} already applies to a module's own
896
+ * plates — §1.1's 12″ minimum, and §2.0's 4″ fascia clearance and 1.125″ pair.
897
+ *
898
+ * Two of the standard's conditions aren't checked because they can't be false
899
+ * here: track crosses the face **perpendicular** and a double end's two tracks
900
+ * sit **1.125″ apart** are both true by construction in this model. Inventing
901
+ * fields for them would be inventing ways to be wrong.
902
+ *
903
+ * An end with no config is **undescribed**, which is the ordinary case — most
904
+ * joints inside a module are just joints.
905
+ */
906
+ declare function assessSectionEnd(end: SectionEnd | null | undefined, opts?: {
907
+ main2Below?: boolean;
908
+ }): SectionEndAssessment;
909
+ /** Two board ends meeting at a joint, and whether that joint is a standard
910
+ * interface (#130). */
911
+ interface SectionJointAssessment {
912
+ west: SectionEndAssessment;
913
+ east: SectionEndAssessment;
914
+ /** Both ends conform AND present the same number of tracks — so these two
915
+ * boards could be separated and each used against any other module. */
916
+ standardInterface: boolean;
917
+ /** Plain-language reason it isn't one, or null when it is. */
918
+ reason: string | null;
919
+ }
920
+ /**
921
+ * Assess a joint — the two board ends that meet there (#130).
922
+ *
923
+ * ⚠️ Track COUNT is the only compatibility rule. Two conforming ends of
924
+ * differing face WIDTH still mate: the standard lets plates differ in width and
925
+ * be offset, so long as the track lines up. That's a deliberate non-check — see
926
+ * the note on endplate width authoring.
927
+ */
928
+ declare function assessSectionJoint(west: SectionEnd | null | undefined, east: SectionEnd | null | undefined, opts?: {
929
+ main2Below?: boolean;
930
+ }): SectionJointAssessment;
846
931
  /** How much of a car-spot span has no rail under it (#194). */
847
932
  interface SpanOverhang {
848
933
  /** Inches the span runs past the track's near end (0 = it starts on track). */
@@ -2047,6 +2132,10 @@ interface ModuleGeometryInput {
2047
2132
  y: number;
2048
2133
  heading: number;
2049
2134
  }>;
2135
+ /** Authored endplate FACE widths by id ("A"/"B"), inches — the board's depth.
2136
+ * Needed to place a branch endplate on the benchwork edge rather than on the
2137
+ * centre line; absent ends use the recommended default. */
2138
+ endplateWidths?: Record<string, number>;
2050
2139
  /** Half the spacing between the two tracks of a double endplate (Free-mo ≈ 1",
2051
2140
  * Free-moN ≈ 9/16"). */
2052
2141
  trackHalfSpacingInches?: number;
@@ -2125,4 +2214,4 @@ declare function poseOverridesFromDoc(doc: ModuleSchematicDoc): Record<string, {
2125
2214
  heading: number;
2126
2215
  }>;
2127
2216
 
2128
- 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, 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 SectionFootprint, 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, 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 };
2217
+ 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, 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 };
package/dist/index.d.ts CHANGED
@@ -366,6 +366,35 @@ interface SchematicSection {
366
366
  geometryDegrees?: number | null;
367
367
  /** Lateral jog, for offset sections. */
368
368
  geometryOffsetInches?: number | null;
369
+ /** This board's WEST end (the A side) — see {@link SectionEnd}. */
370
+ endA?: SectionEnd | null;
371
+ /** This board's EAST end (the B side). */
372
+ endB?: SectionEnd | null;
373
+ }
374
+ /**
375
+ * One end of a board (#130).
376
+ *
377
+ * Owner's question was *"how would I update my section joints to be endplates
378
+ * from within MR?"* — and until now nothing could say it: a section had a
379
+ * length, a shape and an outline, but nothing about its ends.
380
+ *
381
+ * ⚠️ **There is deliberately no "this is an endplate" flag.** An owner could
382
+ * tick it wrongly and the registry would then tell Free-Dispatcher that two
383
+ * boards will physically mate when they won't. The geometry decides — describe
384
+ * the end and {@link assessSectionEnd} works out whether it conforms.
385
+ *
386
+ * Absent means **not described**, which is an ordinary internal joint, not a
387
+ * failing endplate: the standard exempts internal boundaries from the
388
+ * end-interface rules (#96).
389
+ */
390
+ interface SectionEnd {
391
+ /** What this end presents. `"none"` = a closed end (a bumper). */
392
+ config?: TrackConfig | "none" | null;
393
+ /** Face width, inches. Absent = the recommended default. */
394
+ widthInches?: number | null;
395
+ /** Main 1's signed distance from the face centre — the standard's own
396
+ * framing, same as an endplate's (see {@link endplateTrackOffsetInches}). */
397
+ trackOffsetInches?: number | null;
369
398
  }
370
399
  /** The authored benchwork outline, or null when a module hasn't drawn one
371
400
  * (renderers then fall back to a band derived from the endplate widths). A
@@ -843,6 +872,62 @@ declare function usableCapacity(input: {
843
872
  library?: TrackPart[];
844
873
  carLengthInches?: number;
845
874
  }): UsableCapacity;
875
+ /** What a board's end is, once the geometry has been asked (#130). */
876
+ interface SectionEndAssessment {
877
+ /** Has the owner described this end at all? Undescribed is an ordinary
878
+ * internal joint — exempt from the end-interface rules (#96) — not a failure. */
879
+ described: boolean;
880
+ /** Does it meet the standard's end-interface rules? Only a described end can. */
881
+ conforming: boolean;
882
+ /** Why it doesn't, when it doesn't. Empty for a conforming or undescribed end. */
883
+ issues: EndplateWidthIssue[];
884
+ /** The resolved values the assessment was made against. */
885
+ config: TrackConfig | "none";
886
+ widthInches: number;
887
+ trackOffsetInches: number;
888
+ }
889
+ /**
890
+ * Is this end of a board a standard endplate? (#130)
891
+ *
892
+ * ⭐ **Derived, never declared.** The issue is emphatic and the reason is good:
893
+ * a checkbox could be ticked wrongly, and the registry would then promise
894
+ * Free-Dispatcher a mating surface that doesn't exist. So the answer comes from
895
+ * the same rules {@link checkEndplateWidth} already applies to a module's own
896
+ * plates — §1.1's 12″ minimum, and §2.0's 4″ fascia clearance and 1.125″ pair.
897
+ *
898
+ * Two of the standard's conditions aren't checked because they can't be false
899
+ * here: track crosses the face **perpendicular** and a double end's two tracks
900
+ * sit **1.125″ apart** are both true by construction in this model. Inventing
901
+ * fields for them would be inventing ways to be wrong.
902
+ *
903
+ * An end with no config is **undescribed**, which is the ordinary case — most
904
+ * joints inside a module are just joints.
905
+ */
906
+ declare function assessSectionEnd(end: SectionEnd | null | undefined, opts?: {
907
+ main2Below?: boolean;
908
+ }): SectionEndAssessment;
909
+ /** Two board ends meeting at a joint, and whether that joint is a standard
910
+ * interface (#130). */
911
+ interface SectionJointAssessment {
912
+ west: SectionEndAssessment;
913
+ east: SectionEndAssessment;
914
+ /** Both ends conform AND present the same number of tracks — so these two
915
+ * boards could be separated and each used against any other module. */
916
+ standardInterface: boolean;
917
+ /** Plain-language reason it isn't one, or null when it is. */
918
+ reason: string | null;
919
+ }
920
+ /**
921
+ * Assess a joint — the two board ends that meet there (#130).
922
+ *
923
+ * ⚠️ Track COUNT is the only compatibility rule. Two conforming ends of
924
+ * differing face WIDTH still mate: the standard lets plates differ in width and
925
+ * be offset, so long as the track lines up. That's a deliberate non-check — see
926
+ * the note on endplate width authoring.
927
+ */
928
+ declare function assessSectionJoint(west: SectionEnd | null | undefined, east: SectionEnd | null | undefined, opts?: {
929
+ main2Below?: boolean;
930
+ }): SectionJointAssessment;
846
931
  /** How much of a car-spot span has no rail under it (#194). */
847
932
  interface SpanOverhang {
848
933
  /** Inches the span runs past the track's near end (0 = it starts on track). */
@@ -2047,6 +2132,10 @@ interface ModuleGeometryInput {
2047
2132
  y: number;
2048
2133
  heading: number;
2049
2134
  }>;
2135
+ /** Authored endplate FACE widths by id ("A"/"B"), inches — the board's depth.
2136
+ * Needed to place a branch endplate on the benchwork edge rather than on the
2137
+ * centre line; absent ends use the recommended default. */
2138
+ endplateWidths?: Record<string, number>;
2050
2139
  /** Half the spacing between the two tracks of a double endplate (Free-mo ≈ 1",
2051
2140
  * Free-moN ≈ 9/16"). */
2052
2141
  trackHalfSpacingInches?: number;
@@ -2125,4 +2214,4 @@ declare function poseOverridesFromDoc(doc: ModuleSchematicDoc): Record<string, {
2125
2214
  heading: number;
2126
2215
  }>;
2127
2216
 
2128
- 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, 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 SectionFootprint, 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, 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 };
2217
+ 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, 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 };
package/dist/index.js CHANGED
@@ -29,10 +29,28 @@ function moduleSections(doc) {
29
29
  ...typeof len === "number" && Number.isFinite(len) && len > 0 ? { lengthInches: len } : {},
30
30
  ...sec.geometryType ? { geometryType: sec.geometryType } : {},
31
31
  ...typeof deg === "number" && Number.isFinite(deg) ? { geometryDegrees: deg } : {},
32
- ...typeof off === "number" && Number.isFinite(off) ? { geometryOffsetInches: off } : {}
32
+ ...typeof off === "number" && Number.isFinite(off) ? { geometryOffsetInches: off } : {},
33
+ // The board's two ends (#130). Written only when actually described —
34
+ // an absent end is an ordinary internal joint, and an empty object
35
+ // would claim the owner had said something about it.
36
+ ...sectionEnd(sec.endA) ? { endA: sectionEnd(sec.endA) } : {},
37
+ ...sectionEnd(sec.endB) ? { endB: sectionEnd(sec.endB) } : {}
33
38
  };
34
39
  });
35
40
  }
41
+ function sectionEnd(end) {
42
+ if (!end) return null;
43
+ const config = end.config === "single" || end.config === "double" || end.config === "none" ? end.config : null;
44
+ const w = end.widthInches;
45
+ const o = end.trackOffsetInches;
46
+ const out = {
47
+ ...config ? { config } : {},
48
+ ...typeof w === "number" && Number.isFinite(w) && w > 0 ? { widthInches: w } : {},
49
+ // Signed, and 0 is meaningful ("explicitly centred", #93) — keep any finite.
50
+ ...typeof o === "number" && Number.isFinite(o) ? { trackOffsetInches: o } : {}
51
+ };
52
+ return Object.keys(out).length ? out : null;
53
+ }
36
54
  function sectionFootprints(doc, derive) {
37
55
  const spans = derive ? sectionSpans(doc) : [];
38
56
  const spanOf = new Map(spans.map((sp) => [sp.id, sp]));
@@ -625,6 +643,42 @@ function usableCapacity(input) {
625
643
  scaleFeet: Math.round(inchesToScaleFeet(usable))
626
644
  };
627
645
  }
646
+ function assessSectionEnd(end, opts = {}) {
647
+ const config = end?.config ?? "none";
648
+ const widthInches = endplateWidthInches({ widthInches: end?.widthInches });
649
+ const trackOffsetInches = endplateTrackOffsetInches(
650
+ end?.trackOffsetInches,
651
+ config,
652
+ opts.main2Below
653
+ );
654
+ const described = config === "single" || config === "double";
655
+ if (!described) {
656
+ return { described: false, conforming: false, issues: [], config, widthInches, trackOffsetInches };
657
+ }
658
+ const issues = checkEndplateWidth({
659
+ widthInches: end?.widthInches,
660
+ config,
661
+ trackOffsetInches: end?.trackOffsetInches,
662
+ main2Below: opts.main2Below
663
+ });
664
+ return { described: true, conforming: issues.length === 0, issues, config, widthInches, trackOffsetInches };
665
+ }
666
+ function assessSectionJoint(west, east, opts = {}) {
667
+ const w = assessSectionEnd(west, opts);
668
+ const e = assessSectionEnd(east, opts);
669
+ let reason = null;
670
+ if (!w.described || !e.described) {
671
+ reason = "an internal joint \u2014 neither end has been described as an interface";
672
+ if (w.described !== e.described)
673
+ reason = `only the ${w.described ? "west" : "east"} side is described as an endplate`;
674
+ } else if (!w.conforming || !e.conforming) {
675
+ const bad = !w.conforming ? w : e;
676
+ reason = bad.issues[0]?.message ?? "an end doesn't meet the standard";
677
+ } else if (w.config !== e.config) {
678
+ reason = `${w.config} meets ${e.config} \u2014 the track counts differ`;
679
+ }
680
+ return { west: w, east: e, standardInterface: reason === null, reason };
681
+ }
628
682
  function spanOverhang(input) {
629
683
  const lo = Math.min(input.fromPos, input.toPos);
630
684
  const hi = Math.max(input.fromPos, input.toPos);
@@ -2316,15 +2370,21 @@ function deriveEndplatePoses(geo) {
2316
2370
  })
2317
2371
  );
2318
2372
  }
2373
+ const widthAt = (frac) => {
2374
+ const wa = endplateWidthFor(geo.endplateWidths, "A");
2375
+ const wb = endplateWidthFor(geo.endplateWidths, "B");
2376
+ return (wa * (1 - frac) + wb * frac) / 2;
2377
+ };
2319
2378
  for (const b of geo.branches ?? []) {
2320
2379
  const frac = L > 0 ? Math.min(1, Math.max(0, b.atPos / L)) : 0;
2321
2380
  const px = frac * L;
2322
2381
  const config = b.config === "double" ? "double" : "single";
2382
+ const depth = widthAt(frac);
2323
2383
  poses.push(
2324
2384
  withOverride({
2325
2385
  id: b.id,
2326
2386
  x: px,
2327
- y: 0,
2387
+ y: b.side === "down" ? -depth : depth,
2328
2388
  heading: b.side === "down" ? 270 : 90,
2329
2389
  trackConfig: config,
2330
2390
  trackOffsets: offsetsFor(config, half)
@@ -2423,6 +2483,6 @@ function poseOverridesFromDoc(doc) {
2423
2483
  return out;
2424
2484
  }
2425
2485
 
2426
- 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, 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, 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 };
2486
+ 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, 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 };
2427
2487
  //# sourceMappingURL=index.js.map
2428
2488
  //# sourceMappingURL=index.js.map