@willcgage/module-schematic 0.59.0 → 0.61.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
@@ -394,6 +394,13 @@ declare function samplePath(pts: BenchworkPoint[], segsPerArc?: number): {
394
394
  x: number;
395
395
  y: number;
396
396
  }[];
397
+ /**
398
+ * Arc length of an authored path, inches — the path's OWN length, independent of
399
+ * where it sits or which way it runs. A branch route to a third endplate (#181)
400
+ * has no meaningful extent along the module axis (a square 90° exit projects to
401
+ * zero), so its own length is the only honest measure of it.
402
+ */
403
+ declare function pathLengthInches(path: BenchworkPoint[] | null | undefined): number;
397
404
  /** Normalise an authored track path from a doc, or null if it isn't a real path
398
405
  * (needs ≥ 2 valid points). Keeps per-vertex bulge. */
399
406
  declare function trackPath(path: BenchworkPoint[] | null | undefined): BenchworkPoint[] | null;
@@ -951,13 +958,47 @@ interface DrawCrossover {
951
958
  toPosFrac: number;
952
959
  toLane: number;
953
960
  }
954
- /** A branch endplate — draw a connector stub + arrow + label off the given
955
- * side (the CATS/US&S off-band idiom, #170). */
961
+ /**
962
+ * A route leaving the module at a third endplate (#170) — Main 3, whatever the
963
+ * owner labels it (#181). Drawn as an ELBOW: it diverges from its host main at
964
+ * `posFrac`, drops to its own `lane`, then runs to `endFrac` and ends at an
965
+ * endplate face.
966
+ *
967
+ * The run is the branch's OWN arc length laid along the strip, not its
968
+ * projection onto the module axis — a square 90° exit projects to zero, which
969
+ * is exactly the case that made branches invisible here. Straightening the
970
+ * route out at its true length is the same idiom this whole view applies to the
971
+ * main; `posFrac → endFrac` is therefore a real axis, and `lengthInches` maps
972
+ * onto it, so anything positioned along the branch has somewhere to draw.
973
+ */
956
974
  interface BranchConnector {
975
+ /** Endplate id — "C", "D", … */
957
976
  id: string;
977
+ /** The endplate's label. MR draws no text (the destination depends on what's
978
+ * physically attached, so FD derives the panel label at runtime) — this is
979
+ * the owner's local name, for tooltips and FD's fallback. */
958
980
  label: string;
981
+ /** The route's own name, from the track. */
982
+ name: string;
983
+ /** The branch track's id. */
984
+ trackId: string;
985
+ /** Whether the route IS a main (a diverging/split main) or a secondary
986
+ * branch line. Drives drawn weight, not geometry. */
987
+ kind: "branch" | "main";
988
+ /** Where it leaves its host main. */
959
989
  posFrac: number;
990
+ /** The host track's lane — a branch need not leave Main 1. Renderers must
991
+ * start the diverge HERE, not at a hard-coded lane 0. */
992
+ fromLane: number;
993
+ /** Which side of the module it exits. */
960
994
  side: "up" | "down";
995
+ /** The branch's own lane, signed by `side` and placed clear of every other
996
+ * lane (so it never collides) — already folded into laneMin/laneMax. */
997
+ lane: number;
998
+ /** Where the run ends, and the endplate face is drawn. */
999
+ endFrac: number;
1000
+ /** The route's own arc length, inches. */
1001
+ lengthInches: number;
961
1002
  }
962
1003
  /** An industry — draw a car-spot span beside its track's lane, on `side`, with
963
1004
  * a name label + an optional car/length readout (#industries). */
@@ -1383,6 +1424,48 @@ declare function turnoutClosure(size: number, opts?: {
1383
1424
  * around 25″ on a #7 — clear of the Free-moN 22″ minimum. */
1384
1425
  easeInches?: number;
1385
1426
  }): TurnoutClosure;
1427
+ /** A frog casting's parts, in TURNOUT-LOCAL inches: `x` = distance past the
1428
+ * points, `y` = lateral offset from the through route. */
1429
+ interface FrogCasting {
1430
+ /** The point rail — the acute V, apex at the crossing, opening toward the
1431
+ * diverging end. Three points: through leg, apex, diverging leg. */
1432
+ point: Array<{
1433
+ x: number;
1434
+ y: number;
1435
+ }>;
1436
+ /** The two wing rails flanking the point, each a short polyline. */
1437
+ wings: Array<Array<{
1438
+ x: number;
1439
+ y: number;
1440
+ }>>;
1441
+ /** The apex — where the two inner rails actually cross. */
1442
+ apex: {
1443
+ x: number;
1444
+ y: number;
1445
+ };
1446
+ }
1447
+ /**
1448
+ * The frog casting at a turnout's crossing.
1449
+ *
1450
+ * The apex is where the two INNER rails cross, which is HALF a gauge off the
1451
+ * through centre-line (the through inner rail sits at +g/2, the diverging inner
1452
+ * rail at d−g/2, and d = g at the frog). Not one full gauge — that is the
1453
+ * diverging CENTRE-line, and putting the casting there floats it clear of the
1454
+ * rails it is made of.
1455
+ *
1456
+ * ⚠️ **The flangeway is EXAGGERATED and that is deliberate.** True scale in N is
1457
+ * about 0.011″ (prototype 1¾″ ÷ 160) — one pixel in a close-up render and
1458
+ * invisible at module zoom. Drawn to scale the wing rails would sit exactly on
1459
+ * the running rails and add nothing. `flangewayInches` defaults to a readable
1460
+ * fraction of the gauge instead; pass the true figure if you ever need it.
1461
+ */
1462
+ declare function frogCasting(cl: TurnoutClosure, opts?: {
1463
+ gaugeInches?: number;
1464
+ /** How far the casting runs either side of the apex. */
1465
+ reachInches?: number;
1466
+ /** See the warning above — exaggerated for legibility by default. */
1467
+ flangewayInches?: number;
1468
+ }): FrogCasting;
1386
1469
  declare function divergeSideForHand(kind: TurnoutKind | undefined, stubDir: number,
1387
1470
  /** The turnout is installed the other way round — the points face the far
1388
1471
  * direction, which swaps the side the diverging route leaves on. */
@@ -1541,4 +1624,4 @@ declare function poseOverridesFromDoc(doc: ModuleSchematicDoc): Record<string, {
1541
1624
  heading: number;
1542
1625
  }>;
1543
1626
 
1544
- 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 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 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, 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, frogNumberFromName, fromSectionRelative, geometryTurnDegrees, importedPartToTrackPart, inchesToScaleFeet, isLoopDoc, isTransitionTurnout, leadInchesForSize, mergeImportedParts, moduleCenterline, moduleFeatures, moduleFootprint, moduleLengthFromSections, moduleSections, nextId, parseXtpLibrary, partOutlineAtFrog, poseNeedsManual, poseOverridesFromDoc, remapPos, returnLoop, sampleBenchworkOutline, samplePartSegments, samplePath, scaleFeetToInches, sectionAdjacency, sectionBand, sectionBreaksFromSections, sectionComponents, sectionFootprints, sectionNeighbours, sectionSpans, sectionSpansOrWhole, sectionedCenterline, sectionedEndPose, sliceCenterline, stateToDoc, toSectionRelative, trackMeetsEndplateIssues, trackPart, trackPath, turnoutClosure, turnoutPartForSize };
1627
+ 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 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, 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, importedPartToTrackPart, inchesToScaleFeet, isLoopDoc, isTransitionTurnout, leadInchesForSize, mergeImportedParts, moduleCenterline, moduleFeatures, moduleFootprint, moduleLengthFromSections, moduleSections, nextId, parseXtpLibrary, partOutlineAtFrog, pathLengthInches, poseNeedsManual, poseOverridesFromDoc, remapPos, returnLoop, sampleBenchworkOutline, samplePartSegments, samplePath, scaleFeetToInches, sectionAdjacency, sectionBand, sectionBreaksFromSections, sectionComponents, sectionFootprints, sectionNeighbours, sectionSpans, sectionSpansOrWhole, sectionedCenterline, sectionedEndPose, sliceCenterline, stateToDoc, toSectionRelative, trackMeetsEndplateIssues, trackPart, trackPath, turnoutClosure, turnoutPartForSize };
package/dist/index.d.ts CHANGED
@@ -394,6 +394,13 @@ declare function samplePath(pts: BenchworkPoint[], segsPerArc?: number): {
394
394
  x: number;
395
395
  y: number;
396
396
  }[];
397
+ /**
398
+ * Arc length of an authored path, inches — the path's OWN length, independent of
399
+ * where it sits or which way it runs. A branch route to a third endplate (#181)
400
+ * has no meaningful extent along the module axis (a square 90° exit projects to
401
+ * zero), so its own length is the only honest measure of it.
402
+ */
403
+ declare function pathLengthInches(path: BenchworkPoint[] | null | undefined): number;
397
404
  /** Normalise an authored track path from a doc, or null if it isn't a real path
398
405
  * (needs ≥ 2 valid points). Keeps per-vertex bulge. */
399
406
  declare function trackPath(path: BenchworkPoint[] | null | undefined): BenchworkPoint[] | null;
@@ -951,13 +958,47 @@ interface DrawCrossover {
951
958
  toPosFrac: number;
952
959
  toLane: number;
953
960
  }
954
- /** A branch endplate — draw a connector stub + arrow + label off the given
955
- * side (the CATS/US&S off-band idiom, #170). */
961
+ /**
962
+ * A route leaving the module at a third endplate (#170) — Main 3, whatever the
963
+ * owner labels it (#181). Drawn as an ELBOW: it diverges from its host main at
964
+ * `posFrac`, drops to its own `lane`, then runs to `endFrac` and ends at an
965
+ * endplate face.
966
+ *
967
+ * The run is the branch's OWN arc length laid along the strip, not its
968
+ * projection onto the module axis — a square 90° exit projects to zero, which
969
+ * is exactly the case that made branches invisible here. Straightening the
970
+ * route out at its true length is the same idiom this whole view applies to the
971
+ * main; `posFrac → endFrac` is therefore a real axis, and `lengthInches` maps
972
+ * onto it, so anything positioned along the branch has somewhere to draw.
973
+ */
956
974
  interface BranchConnector {
975
+ /** Endplate id — "C", "D", … */
957
976
  id: string;
977
+ /** The endplate's label. MR draws no text (the destination depends on what's
978
+ * physically attached, so FD derives the panel label at runtime) — this is
979
+ * the owner's local name, for tooltips and FD's fallback. */
958
980
  label: string;
981
+ /** The route's own name, from the track. */
982
+ name: string;
983
+ /** The branch track's id. */
984
+ trackId: string;
985
+ /** Whether the route IS a main (a diverging/split main) or a secondary
986
+ * branch line. Drives drawn weight, not geometry. */
987
+ kind: "branch" | "main";
988
+ /** Where it leaves its host main. */
959
989
  posFrac: number;
990
+ /** The host track's lane — a branch need not leave Main 1. Renderers must
991
+ * start the diverge HERE, not at a hard-coded lane 0. */
992
+ fromLane: number;
993
+ /** Which side of the module it exits. */
960
994
  side: "up" | "down";
995
+ /** The branch's own lane, signed by `side` and placed clear of every other
996
+ * lane (so it never collides) — already folded into laneMin/laneMax. */
997
+ lane: number;
998
+ /** Where the run ends, and the endplate face is drawn. */
999
+ endFrac: number;
1000
+ /** The route's own arc length, inches. */
1001
+ lengthInches: number;
961
1002
  }
962
1003
  /** An industry — draw a car-spot span beside its track's lane, on `side`, with
963
1004
  * a name label + an optional car/length readout (#industries). */
@@ -1383,6 +1424,48 @@ declare function turnoutClosure(size: number, opts?: {
1383
1424
  * around 25″ on a #7 — clear of the Free-moN 22″ minimum. */
1384
1425
  easeInches?: number;
1385
1426
  }): TurnoutClosure;
1427
+ /** A frog casting's parts, in TURNOUT-LOCAL inches: `x` = distance past the
1428
+ * points, `y` = lateral offset from the through route. */
1429
+ interface FrogCasting {
1430
+ /** The point rail — the acute V, apex at the crossing, opening toward the
1431
+ * diverging end. Three points: through leg, apex, diverging leg. */
1432
+ point: Array<{
1433
+ x: number;
1434
+ y: number;
1435
+ }>;
1436
+ /** The two wing rails flanking the point, each a short polyline. */
1437
+ wings: Array<Array<{
1438
+ x: number;
1439
+ y: number;
1440
+ }>>;
1441
+ /** The apex — where the two inner rails actually cross. */
1442
+ apex: {
1443
+ x: number;
1444
+ y: number;
1445
+ };
1446
+ }
1447
+ /**
1448
+ * The frog casting at a turnout's crossing.
1449
+ *
1450
+ * The apex is where the two INNER rails cross, which is HALF a gauge off the
1451
+ * through centre-line (the through inner rail sits at +g/2, the diverging inner
1452
+ * rail at d−g/2, and d = g at the frog). Not one full gauge — that is the
1453
+ * diverging CENTRE-line, and putting the casting there floats it clear of the
1454
+ * rails it is made of.
1455
+ *
1456
+ * ⚠️ **The flangeway is EXAGGERATED and that is deliberate.** True scale in N is
1457
+ * about 0.011″ (prototype 1¾″ ÷ 160) — one pixel in a close-up render and
1458
+ * invisible at module zoom. Drawn to scale the wing rails would sit exactly on
1459
+ * the running rails and add nothing. `flangewayInches` defaults to a readable
1460
+ * fraction of the gauge instead; pass the true figure if you ever need it.
1461
+ */
1462
+ declare function frogCasting(cl: TurnoutClosure, opts?: {
1463
+ gaugeInches?: number;
1464
+ /** How far the casting runs either side of the apex. */
1465
+ reachInches?: number;
1466
+ /** See the warning above — exaggerated for legibility by default. */
1467
+ flangewayInches?: number;
1468
+ }): FrogCasting;
1386
1469
  declare function divergeSideForHand(kind: TurnoutKind | undefined, stubDir: number,
1387
1470
  /** The turnout is installed the other way round — the points face the far
1388
1471
  * direction, which swaps the side the diverging route leaves on. */
@@ -1541,4 +1624,4 @@ declare function poseOverridesFromDoc(doc: ModuleSchematicDoc): Record<string, {
1541
1624
  heading: number;
1542
1625
  }>;
1543
1626
 
1544
- 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 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 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, 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, frogNumberFromName, fromSectionRelative, geometryTurnDegrees, importedPartToTrackPart, inchesToScaleFeet, isLoopDoc, isTransitionTurnout, leadInchesForSize, mergeImportedParts, moduleCenterline, moduleFeatures, moduleFootprint, moduleLengthFromSections, moduleSections, nextId, parseXtpLibrary, partOutlineAtFrog, poseNeedsManual, poseOverridesFromDoc, remapPos, returnLoop, sampleBenchworkOutline, samplePartSegments, samplePath, scaleFeetToInches, sectionAdjacency, sectionBand, sectionBreaksFromSections, sectionComponents, sectionFootprints, sectionNeighbours, sectionSpans, sectionSpansOrWhole, sectionedCenterline, sectionedEndPose, sliceCenterline, stateToDoc, toSectionRelative, trackMeetsEndplateIssues, trackPart, trackPath, turnoutClosure, turnoutPartForSize };
1627
+ 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 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, 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, importedPartToTrackPart, inchesToScaleFeet, isLoopDoc, isTransitionTurnout, leadInchesForSize, mergeImportedParts, moduleCenterline, moduleFeatures, moduleFootprint, moduleLengthFromSections, moduleSections, nextId, parseXtpLibrary, partOutlineAtFrog, pathLengthInches, poseNeedsManual, poseOverridesFromDoc, remapPos, returnLoop, sampleBenchworkOutline, samplePartSegments, samplePath, scaleFeetToInches, sectionAdjacency, sectionBand, sectionBreaksFromSections, sectionComponents, sectionFootprints, sectionNeighbours, sectionSpans, sectionSpansOrWhole, sectionedCenterline, sectionedEndPose, sliceCenterline, stateToDoc, toSectionRelative, trackMeetsEndplateIssues, trackPart, trackPath, turnoutClosure, turnoutPartForSize };
package/dist/index.js CHANGED
@@ -115,6 +115,15 @@ function samplePath(pts, segsPerArc = 20) {
115
115
  out.push({ x: pts[n - 1].x, y: pts[n - 1].y });
116
116
  return out;
117
117
  }
118
+ function pathLengthInches(path) {
119
+ const pts = trackPath(path);
120
+ if (!pts) return 0;
121
+ const poly = samplePath(pts);
122
+ let total = 0;
123
+ for (let i = 1; i < poly.length; i++)
124
+ total += Math.hypot(poly[i].x - poly[i - 1].x, poly[i].y - poly[i - 1].y);
125
+ return total;
126
+ }
118
127
  function trackPath(path) {
119
128
  const pts = (path ?? []).filter((p) => p && Number.isFinite(p.x) && Number.isFinite(p.y)).map((p) => ({
120
129
  x: p.x,
@@ -1473,6 +1482,32 @@ function turnoutClosure(size, opts = {}) {
1473
1482
  }
1474
1483
  };
1475
1484
  }
1485
+ function frogCasting(cl, opts = {}) {
1486
+ const g = opts.gaugeInches ?? RAIL_GAUGE_INCHES;
1487
+ const w = opts.reachInches ?? g * 1.5;
1488
+ const fw = opts.flangewayInches ?? g * 0.12;
1489
+ const m = cl.frogSlope;
1490
+ const lead = cl.lead;
1491
+ const apex = { x: lead, y: g / 2 };
1492
+ const point = [
1493
+ { x: lead + w, y: g / 2 },
1494
+ apex,
1495
+ { x: lead + w, y: g / 2 + m * w }
1496
+ ];
1497
+ const wings = [
1498
+ [
1499
+ { x: lead - w, y: g / 2 - fw },
1500
+ { x: lead - w * 0.35, y: g / 2 - fw },
1501
+ { x: lead + w * 0.35, y: g / 2 - fw * 0.5 }
1502
+ ],
1503
+ [
1504
+ { x: lead - w, y: g / 2 + m * -w + fw },
1505
+ { x: lead - w * 0.35, y: g / 2 + m * -w * 0.35 + fw },
1506
+ { x: lead + w * 0.35, y: g / 2 + m * w * 0.35 + fw * 0.5 }
1507
+ ]
1508
+ ];
1509
+ return { point, wings, apex };
1510
+ }
1476
1511
  function divergeSideForHand(kind, stubDir, flipped) {
1477
1512
  if (kind !== "left" && kind !== "right") return 0;
1478
1513
  const s = (stubDir >= 0 ? 1 : -1) * (flipped ? -1 : 1);
@@ -1539,7 +1574,7 @@ function moduleFeatures(doc) {
1539
1574
  if (parentLane === 0) {
1540
1575
  const [from, to] = ext;
1541
1576
  const far = Math.abs(to - sw.pos) >= Math.abs(from - sw.pos) ? to : from;
1542
- const s = divergeSideForHand(sw.kind, far - sw.pos);
1577
+ const s = divergeSideForHand(sw.kind, far - sw.pos, sw.flipped);
1543
1578
  sign = s !== 0 ? s : Math.sign(trk.lane) || 1;
1544
1579
  } else {
1545
1580
  sign = Math.sign(parentLane) || 1;
@@ -1654,15 +1689,43 @@ function moduleFeatures(doc) {
1654
1689
  laneA: trackLane.get(x.tracks?.[0] ?? "") ?? 0,
1655
1690
  laneB: trackLane.get(x.tracks?.[1] ?? "") ?? 1
1656
1691
  }));
1692
+ const main2Lane = trackLane.has(MAIN2_TRACK_ID) ? trackLane.get(MAIN2_TRACK_ID) : null;
1693
+ const baseLanes = [
1694
+ 0,
1695
+ main2Lane ?? (doubleMain ? 1 : 0),
1696
+ ...extraTracks.map((t) => t.lane),
1697
+ ...signals.map((s) => s.lane),
1698
+ ...crossings.flatMap((x) => [x.laneA, x.laneB]),
1699
+ ...crossovers.flatMap((x) => [x.fromLane, x.toLane])
1700
+ ];
1701
+ let upLane = Math.max(...baseLanes, 0);
1702
+ let downLane = Math.min(...baseLanes, 0);
1657
1703
  const branchConnectors = doc.endplates.filter(
1658
1704
  (e) => e.id !== "A" && e.id !== "B" && e.at && !!e.trackId && (doc.tracks ?? []).some((t) => t.id === e.trackId)
1659
1705
  ).map((e) => {
1706
+ const trk = (doc.tracks ?? []).find((t) => t.id === e.trackId);
1660
1707
  const sw = (doc.turnouts ?? []).find((t) => t.divergeTrack === e.trackId);
1708
+ const startPos = sw ? sw.pos : e.at.pos;
1709
+ const side = e.at.side === "down" ? "down" : "up";
1710
+ const lane = side === "down" ? --downLane : ++upLane;
1711
+ const runInches = pathLengthInches(trk.path) || Math.abs(e.at.pos - startPos) || FREEMO_ENDPLATE_WIDTH_MIN_INCHES;
1712
+ const toB = e.at.pos >= startPos;
1713
+ const fits = (p) => p >= 0 && p <= len;
1714
+ const ahead = startPos + runInches;
1715
+ const behind = startPos - runInches;
1716
+ const endPos = toB && fits(ahead) ? ahead : !toB && fits(behind) ? behind : fits(toB ? behind : ahead) ? toB ? behind : ahead : ahead;
1661
1717
  return {
1662
1718
  id: e.id,
1663
1719
  label: e.label ?? e.id,
1664
- posFrac: clampFrac(sw ? sw.pos : e.at.pos),
1665
- side: e.at.side === "down" ? "down" : "up"
1720
+ name: trk.trackName ?? "",
1721
+ trackId: trk.id,
1722
+ kind: e.kind === "main" ? "main" : "branch",
1723
+ posFrac: clampFrac(startPos),
1724
+ fromLane: sw ? trackLane.get(sw.onTrack) ?? 0 : 0,
1725
+ side,
1726
+ lane,
1727
+ endFrac: clampFrac(endPos),
1728
+ lengthInches: runInches
1666
1729
  };
1667
1730
  });
1668
1731
  const industries = (doc.industries ?? []).flatMap((ind) => {
@@ -1687,15 +1750,7 @@ function moduleFeatures(doc) {
1687
1750
  };
1688
1751
  });
1689
1752
  });
1690
- const main2Lane = trackLane.has(MAIN2_TRACK_ID) ? trackLane.get(MAIN2_TRACK_ID) : null;
1691
- const allLanes = [
1692
- 0,
1693
- main2Lane ?? (doubleMain ? 1 : 0),
1694
- ...extraTracks.map((t) => t.lane),
1695
- ...signals.map((s) => s.lane),
1696
- ...crossings.flatMap((x) => [x.laneA, x.laneB]),
1697
- ...crossovers.flatMap((x) => [x.fromLane, x.toLane])
1698
- ];
1753
+ const allLanes = [...baseLanes, ...branchConnectors.map((b) => b.lane)];
1699
1754
  const loop = isLoopDoc(doc);
1700
1755
  const main2 = doc.tracks.find((t) => t.id === MAIN2_TRACK_ID);
1701
1756
  const main2Positioned = !!main2 && (main2.fromPos != null || main2.toPos != null) && !loop;
@@ -1986,6 +2041,6 @@ function poseOverridesFromDoc(doc) {
1986
2041
  return out;
1987
2042
  }
1988
2043
 
1989
- 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, 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, frogNumberFromName, fromSectionRelative, geometryTurnDegrees, importedPartToTrackPart, inchesToScaleFeet, isLoopDoc, isTransitionTurnout, leadInchesForSize, mergeImportedParts, moduleCenterline, moduleFeatures, moduleFootprint, moduleLengthFromSections, moduleSections, nextId, parseXtpLibrary, partOutlineAtFrog, poseNeedsManual, poseOverridesFromDoc, remapPos, returnLoop, sampleBenchworkOutline, samplePartSegments, samplePath, scaleFeetToInches, sectionAdjacency, sectionBand, sectionBreaksFromSections, sectionComponents, sectionFootprints, sectionNeighbours, sectionSpans, sectionSpansOrWhole, sectionedCenterline, sectionedEndPose, sliceCenterline, stateToDoc, toSectionRelative, trackMeetsEndplateIssues, trackPart, trackPath, turnoutClosure, turnoutPartForSize };
2044
+ 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, 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, importedPartToTrackPart, inchesToScaleFeet, isLoopDoc, isTransitionTurnout, leadInchesForSize, mergeImportedParts, moduleCenterline, moduleFeatures, moduleFootprint, moduleLengthFromSections, moduleSections, nextId, parseXtpLibrary, partOutlineAtFrog, pathLengthInches, poseNeedsManual, poseOverridesFromDoc, remapPos, returnLoop, sampleBenchworkOutline, samplePartSegments, samplePath, scaleFeetToInches, sectionAdjacency, sectionBand, sectionBreaksFromSections, sectionComponents, sectionFootprints, sectionNeighbours, sectionSpans, sectionSpansOrWhole, sectionedCenterline, sectionedEndPose, sliceCenterline, stateToDoc, toSectionRelative, trackMeetsEndplateIssues, trackPart, trackPath, turnoutClosure, turnoutPartForSize };
1990
2045
  //# sourceMappingURL=index.js.map
1991
2046
  //# sourceMappingURL=index.js.map