@willcgage/module-schematic 0.85.0 → 0.86.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
@@ -2369,6 +2369,125 @@ declare function partsPlaceable(library?: TrackPart[]): {
2369
2369
  why: string;
2370
2370
  }[];
2371
2371
  };
2372
+ /** A part placed on the benchwork. */
2373
+ interface TrackPiece {
2374
+ id: string;
2375
+ /** A slug from the parts library. */
2376
+ partId: string;
2377
+ /** Where the part's own origin sits, in module-local inches. */
2378
+ x: number;
2379
+ y: number;
2380
+ /** Rotation about that origin, degrees. */
2381
+ rotationDeg: number;
2382
+ /** Mirrored across its own through route — a left-hand turnout from a right. */
2383
+ flipped?: boolean;
2384
+ /** FLEX ONLY: how long this run is. The one piece a builder cuts (ADR 0001). */
2385
+ lengthInches?: number;
2386
+ /** Owner's label, carried onto whatever route this piece ends up in. */
2387
+ name?: string;
2388
+ }
2389
+ /** A piece's joint, in MODULE coordinates. */
2390
+ interface PlacedJoint {
2391
+ /** `"<pieceId>.<jointId>"` — stable, and what connections refer to. */
2392
+ key: string;
2393
+ piece: string;
2394
+ joint: string;
2395
+ role: PartJointRole;
2396
+ x: number;
2397
+ y: number;
2398
+ headingDeg: number;
2399
+ }
2400
+ /** Two joints in the same place. That is the entire connection rule. */
2401
+ interface GraphConnection {
2402
+ a: string;
2403
+ b: string;
2404
+ }
2405
+ /**
2406
+ * More than two joints in one place — REFUSED, not resolved.
2407
+ *
2408
+ * ⚠️ THIS IS THE GAP THE SPIKE FOUND, and the one that would bite owners. With
2409
+ * three ends stacked on a point, picking a pair silently drops a piece out of
2410
+ * the layout: the walk never reaches it, and nothing says so. So none of them
2411
+ * connect and the ambiguity is reported. Refusing is the only honest answer —
2412
+ * the model cannot know which two the owner meant.
2413
+ */
2414
+ interface GraphConflict {
2415
+ x: number;
2416
+ y: number;
2417
+ joints: string[];
2418
+ reason: string;
2419
+ }
2420
+ interface TrackGraph {
2421
+ joints: PlacedJoint[];
2422
+ connections: GraphConnection[];
2423
+ /** Joints connected to nothing — an unfinished layout, which is a real thing
2424
+ * to show an owner rather than quietly draw as if it were joined. */
2425
+ open: string[];
2426
+ conflicts: GraphConflict[];
2427
+ /** Pieces whose part has no derivable geometry (see {@link partGeometryGap}). */
2428
+ unplaceable: {
2429
+ piece: string;
2430
+ partId: string;
2431
+ why: string;
2432
+ }[];
2433
+ }
2434
+ /** How close two joints must be to count as connected. A hundredth of an inch:
2435
+ * tight enough that nothing joins by accident, loose enough to absorb the
2436
+ * rounding of a drag. */
2437
+ declare const JOINT_SNAP_INCHES = 0.01;
2438
+ /** Every piece's joints, transformed onto the board. */
2439
+ declare function placedJoints(pieces: TrackPiece[], library?: TrackPart[]): PlacedJoint[];
2440
+ /**
2441
+ * Build the graph: which joints are connected, which are open, and where the
2442
+ * layout is ambiguous.
2443
+ *
2444
+ * ⭐ A JOINT HOLDS AT MOST ONE CONNECTION. Rail has two ends; a place where
2445
+ * three meet is a mistake, not a junction — a junction is a TURNOUT, which is a
2446
+ * part carrying three joints of its own.
2447
+ */
2448
+ declare function buildTrackGraph(pieces: TrackPiece[], library?: TrackPart[], snapInches?: number): TrackGraph;
2449
+ /** A route the walk found: a continuous run of pieces a train can travel. */
2450
+ interface GraphRoute {
2451
+ id: string;
2452
+ /** Inches from endplate A, ALONG THE RAIL, to where this route begins. */
2453
+ fromPos: number;
2454
+ toPos: number;
2455
+ /** The turnout this route branched from; null for the main. */
2456
+ bornAt: string | null;
2457
+ /** The turnout it runs back into. Set = a SIDING; null = it dead-ends. */
2458
+ endsAt: string | null;
2459
+ pieces: string[];
2460
+ /** Furthest lateral offset reached — what gives a lane its side. */
2461
+ lateral: number;
2462
+ }
2463
+ interface GraphTurnout {
2464
+ id: string;
2465
+ /** ⚠️ The FROG's distance from endplate A (#132), measured along the rail. */
2466
+ pos: number;
2467
+ onRoute: string;
2468
+ divergeRoute: string | null;
2469
+ }
2470
+ interface GraphWalk {
2471
+ routes: GraphRoute[];
2472
+ turnouts: GraphTurnout[];
2473
+ /** Every reason this walk is not the whole layout. */
2474
+ warnings: string[];
2475
+ }
2476
+ /**
2477
+ * Walk the graph from an endplate and read the topology off it.
2478
+ *
2479
+ * ⚠️ POSITIONS ARE ARC LENGTH ALONG THE RAIL, never x. On a curved module the
2480
+ * two differ by inches — a 90°/R30 corner runs 47.1″ of rail across a 42.4″
2481
+ * chord — and it is the rail a train travels.
2482
+ *
2483
+ * Nesting falls out for free: a turnout found on a branch queues the branch IT
2484
+ * opens, so a yard ladder resolves to whatever depth it actually has. That is
2485
+ * the case the 1-D model can only approximate.
2486
+ */
2487
+ declare function walkTrackGraph(graph: TrackGraph, pieces: TrackPiece[], startAt: {
2488
+ piece: string;
2489
+ joint: string;
2490
+ }, library?: TrackPart[]): GraphWalk;
2372
2491
  /** A frog casting's parts, in TURNOUT-LOCAL inches: `x` = distance past the
2373
2492
  * points, `y` = lateral offset from the through route. */
2374
2493
  interface FrogCasting {
@@ -2650,4 +2769,4 @@ declare function poseOverridesFromDoc(doc: ModuleSchematicDoc): Record<string, {
2650
2769
  heading: number;
2651
2770
  }>;
2652
2771
 
2653
- 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 EndplateEdge, type EndplateEdgePose, type EndplatePose, type EndplateTrackIssue, type EndplateWidthIssue, FAST_TRACKS_N_ME55, FAST_TRACKS_N_ME55_CROSSOVERS, FLEX_TRACK_PARTS, FREEMO_ENDPLATE_TRACK_FASCIA_CLEARANCE_INCHES, FREEMO_ENDPLATE_WIDTH_MIN_INCHES, FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES, FREEMO_TRACK_SPACING_INCHES, type FlexPiece, type FlexPieceEnd, type FrogCasting, type GeometryType, type ImportedPart, type IndustryLabelMode, type IndustrySpot, type LanePinch, MAIN2_TRACK_ID, MAIN_TRACK_ID, type ModuleFeatures, type ModuleFootprint, type ModuleFootprintInput, type ModuleGeometryInput, type ModuleSchematicDoc, type ModuleTrackRow, N_CAR_LENGTH_INCHES, N_SCALE_RATIO, type OccupiedSpan, type OutlineFace, PINCH_EASE_INCHES, type PartAngle, type PartDimension, type PartEnd, type PartExtent, type PartGeometry, type PartJoint, type PartJointRole, type PartSegment, RAIL_GAUGE_INCHES, type ReturnLoopGeometry, type ReturnLoopShape, type SchematicBlock, type SchematicControlPoint, type SchematicCrossing, type SchematicEndplate, type SchematicEndplateTrack, type SchematicIndustry, type SchematicSection, type SchematicSignal, type SchematicTrack, type SchematicTurnout, type SectionAdjacency, type SectionEnd, type SectionEndAssessment, type SectionFootprint, type SectionJointAssessment, type SectionRelativePos, type SignalFacing, type SignalSide, type SpanOverhang, type StoredTrackPart, TIE_HALF_LENGTH_INCHES, TURNOUT_LEAD_INCHES_PER_FROG, type TrackConfig, type TrackPart, type TrackRole, type TurnoutClosure, type TurnoutKind, type UsableCapacity, WHOLE_MODULE_SECTION_ID, asModuleSchematic, assessSectionEnd, assessSectionJoint, benchworkBand, benchworkOutline, buildCrossover, buildPassingSiding, buildTransition, carCapacity, checkEndplateWidth, clearancePointPastFrogInches, crossoverPinches, deriveEndplatePoses, divergeSideForHand, docToState, emptyEditorState, endplateCentreOffsetInches, endplateEdgePose, endplateFaceSegments, endplateLead, endplateTrackOffsetInches, endplateWidthInches, flexPartFor, flexParts, flexPieces, flexUsage, frogCasting, frogNumberFromName, fromSectionRelative, geometryTurnDegrees, hasNoFarEndplate, importedPartToTrackPart, inchesToScaleFeet, isLoopDoc, isTransitionTurnout, laneOffsetAt, leadInchesForSize, maxFlexPieceInches, mergeImportedParts, mergeStoredParts, moduleCenterline, moduleFeatures, moduleFootprint, moduleLengthFromSections, moduleSections, nextId, parseXtpLibrary, partExtent, partExtentForSize, partGeometry, partGeometryGap, partOutlineAtFrog, partsPlaceable, 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 };
2772
+ 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 EndplateEdge, type EndplateEdgePose, type EndplatePose, type EndplateTrackIssue, type EndplateWidthIssue, FAST_TRACKS_N_ME55, FAST_TRACKS_N_ME55_CROSSOVERS, FLEX_TRACK_PARTS, FREEMO_ENDPLATE_TRACK_FASCIA_CLEARANCE_INCHES, FREEMO_ENDPLATE_WIDTH_MIN_INCHES, FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES, FREEMO_TRACK_SPACING_INCHES, type FlexPiece, type FlexPieceEnd, type FrogCasting, type GeometryType, type GraphConflict, type GraphConnection, type GraphRoute, type GraphTurnout, type GraphWalk, type ImportedPart, type IndustryLabelMode, type IndustrySpot, JOINT_SNAP_INCHES, type LanePinch, MAIN2_TRACK_ID, MAIN_TRACK_ID, type ModuleFeatures, type ModuleFootprint, type ModuleFootprintInput, type ModuleGeometryInput, type ModuleSchematicDoc, type ModuleTrackRow, N_CAR_LENGTH_INCHES, N_SCALE_RATIO, type OccupiedSpan, type OutlineFace, PINCH_EASE_INCHES, type PartAngle, type PartDimension, type PartEnd, type PartExtent, type PartGeometry, type PartJoint, type PartJointRole, type PartSegment, type PlacedJoint, 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 TrackGraph, type TrackPart, type TrackPiece, type TrackRole, type TurnoutClosure, type TurnoutKind, type UsableCapacity, WHOLE_MODULE_SECTION_ID, asModuleSchematic, assessSectionEnd, assessSectionJoint, benchworkBand, benchworkOutline, buildCrossover, buildPassingSiding, buildTrackGraph, buildTransition, carCapacity, checkEndplateWidth, clearancePointPastFrogInches, crossoverPinches, deriveEndplatePoses, divergeSideForHand, docToState, emptyEditorState, endplateCentreOffsetInches, endplateEdgePose, endplateFaceSegments, endplateLead, endplateTrackOffsetInches, endplateWidthInches, flexPartFor, flexParts, flexPieces, flexUsage, frogCasting, frogNumberFromName, fromSectionRelative, geometryTurnDegrees, hasNoFarEndplate, importedPartToTrackPart, inchesToScaleFeet, isLoopDoc, isTransitionTurnout, laneOffsetAt, leadInchesForSize, maxFlexPieceInches, mergeImportedParts, mergeStoredParts, moduleCenterline, moduleFeatures, moduleFootprint, moduleLengthFromSections, moduleSections, nextId, parseXtpLibrary, partExtent, partExtentForSize, partGeometry, partGeometryGap, partOutlineAtFrog, partsPlaceable, pastFrogInchesForSize, pathLengthInches, placedJoints, 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, walkTrackGraph };
package/dist/index.d.ts CHANGED
@@ -2369,6 +2369,125 @@ declare function partsPlaceable(library?: TrackPart[]): {
2369
2369
  why: string;
2370
2370
  }[];
2371
2371
  };
2372
+ /** A part placed on the benchwork. */
2373
+ interface TrackPiece {
2374
+ id: string;
2375
+ /** A slug from the parts library. */
2376
+ partId: string;
2377
+ /** Where the part's own origin sits, in module-local inches. */
2378
+ x: number;
2379
+ y: number;
2380
+ /** Rotation about that origin, degrees. */
2381
+ rotationDeg: number;
2382
+ /** Mirrored across its own through route — a left-hand turnout from a right. */
2383
+ flipped?: boolean;
2384
+ /** FLEX ONLY: how long this run is. The one piece a builder cuts (ADR 0001). */
2385
+ lengthInches?: number;
2386
+ /** Owner's label, carried onto whatever route this piece ends up in. */
2387
+ name?: string;
2388
+ }
2389
+ /** A piece's joint, in MODULE coordinates. */
2390
+ interface PlacedJoint {
2391
+ /** `"<pieceId>.<jointId>"` — stable, and what connections refer to. */
2392
+ key: string;
2393
+ piece: string;
2394
+ joint: string;
2395
+ role: PartJointRole;
2396
+ x: number;
2397
+ y: number;
2398
+ headingDeg: number;
2399
+ }
2400
+ /** Two joints in the same place. That is the entire connection rule. */
2401
+ interface GraphConnection {
2402
+ a: string;
2403
+ b: string;
2404
+ }
2405
+ /**
2406
+ * More than two joints in one place — REFUSED, not resolved.
2407
+ *
2408
+ * ⚠️ THIS IS THE GAP THE SPIKE FOUND, and the one that would bite owners. With
2409
+ * three ends stacked on a point, picking a pair silently drops a piece out of
2410
+ * the layout: the walk never reaches it, and nothing says so. So none of them
2411
+ * connect and the ambiguity is reported. Refusing is the only honest answer —
2412
+ * the model cannot know which two the owner meant.
2413
+ */
2414
+ interface GraphConflict {
2415
+ x: number;
2416
+ y: number;
2417
+ joints: string[];
2418
+ reason: string;
2419
+ }
2420
+ interface TrackGraph {
2421
+ joints: PlacedJoint[];
2422
+ connections: GraphConnection[];
2423
+ /** Joints connected to nothing — an unfinished layout, which is a real thing
2424
+ * to show an owner rather than quietly draw as if it were joined. */
2425
+ open: string[];
2426
+ conflicts: GraphConflict[];
2427
+ /** Pieces whose part has no derivable geometry (see {@link partGeometryGap}). */
2428
+ unplaceable: {
2429
+ piece: string;
2430
+ partId: string;
2431
+ why: string;
2432
+ }[];
2433
+ }
2434
+ /** How close two joints must be to count as connected. A hundredth of an inch:
2435
+ * tight enough that nothing joins by accident, loose enough to absorb the
2436
+ * rounding of a drag. */
2437
+ declare const JOINT_SNAP_INCHES = 0.01;
2438
+ /** Every piece's joints, transformed onto the board. */
2439
+ declare function placedJoints(pieces: TrackPiece[], library?: TrackPart[]): PlacedJoint[];
2440
+ /**
2441
+ * Build the graph: which joints are connected, which are open, and where the
2442
+ * layout is ambiguous.
2443
+ *
2444
+ * ⭐ A JOINT HOLDS AT MOST ONE CONNECTION. Rail has two ends; a place where
2445
+ * three meet is a mistake, not a junction — a junction is a TURNOUT, which is a
2446
+ * part carrying three joints of its own.
2447
+ */
2448
+ declare function buildTrackGraph(pieces: TrackPiece[], library?: TrackPart[], snapInches?: number): TrackGraph;
2449
+ /** A route the walk found: a continuous run of pieces a train can travel. */
2450
+ interface GraphRoute {
2451
+ id: string;
2452
+ /** Inches from endplate A, ALONG THE RAIL, to where this route begins. */
2453
+ fromPos: number;
2454
+ toPos: number;
2455
+ /** The turnout this route branched from; null for the main. */
2456
+ bornAt: string | null;
2457
+ /** The turnout it runs back into. Set = a SIDING; null = it dead-ends. */
2458
+ endsAt: string | null;
2459
+ pieces: string[];
2460
+ /** Furthest lateral offset reached — what gives a lane its side. */
2461
+ lateral: number;
2462
+ }
2463
+ interface GraphTurnout {
2464
+ id: string;
2465
+ /** ⚠️ The FROG's distance from endplate A (#132), measured along the rail. */
2466
+ pos: number;
2467
+ onRoute: string;
2468
+ divergeRoute: string | null;
2469
+ }
2470
+ interface GraphWalk {
2471
+ routes: GraphRoute[];
2472
+ turnouts: GraphTurnout[];
2473
+ /** Every reason this walk is not the whole layout. */
2474
+ warnings: string[];
2475
+ }
2476
+ /**
2477
+ * Walk the graph from an endplate and read the topology off it.
2478
+ *
2479
+ * ⚠️ POSITIONS ARE ARC LENGTH ALONG THE RAIL, never x. On a curved module the
2480
+ * two differ by inches — a 90°/R30 corner runs 47.1″ of rail across a 42.4″
2481
+ * chord — and it is the rail a train travels.
2482
+ *
2483
+ * Nesting falls out for free: a turnout found on a branch queues the branch IT
2484
+ * opens, so a yard ladder resolves to whatever depth it actually has. That is
2485
+ * the case the 1-D model can only approximate.
2486
+ */
2487
+ declare function walkTrackGraph(graph: TrackGraph, pieces: TrackPiece[], startAt: {
2488
+ piece: string;
2489
+ joint: string;
2490
+ }, library?: TrackPart[]): GraphWalk;
2372
2491
  /** A frog casting's parts, in TURNOUT-LOCAL inches: `x` = distance past the
2373
2492
  * points, `y` = lateral offset from the through route. */
2374
2493
  interface FrogCasting {
@@ -2650,4 +2769,4 @@ declare function poseOverridesFromDoc(doc: ModuleSchematicDoc): Record<string, {
2650
2769
  heading: number;
2651
2770
  }>;
2652
2771
 
2653
- 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 EndplateEdge, type EndplateEdgePose, type EndplatePose, type EndplateTrackIssue, type EndplateWidthIssue, FAST_TRACKS_N_ME55, FAST_TRACKS_N_ME55_CROSSOVERS, FLEX_TRACK_PARTS, FREEMO_ENDPLATE_TRACK_FASCIA_CLEARANCE_INCHES, FREEMO_ENDPLATE_WIDTH_MIN_INCHES, FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES, FREEMO_TRACK_SPACING_INCHES, type FlexPiece, type FlexPieceEnd, type FrogCasting, type GeometryType, type ImportedPart, type IndustryLabelMode, type IndustrySpot, type LanePinch, MAIN2_TRACK_ID, MAIN_TRACK_ID, type ModuleFeatures, type ModuleFootprint, type ModuleFootprintInput, type ModuleGeometryInput, type ModuleSchematicDoc, type ModuleTrackRow, N_CAR_LENGTH_INCHES, N_SCALE_RATIO, type OccupiedSpan, type OutlineFace, PINCH_EASE_INCHES, type PartAngle, type PartDimension, type PartEnd, type PartExtent, type PartGeometry, type PartJoint, type PartJointRole, type PartSegment, RAIL_GAUGE_INCHES, type ReturnLoopGeometry, type ReturnLoopShape, type SchematicBlock, type SchematicControlPoint, type SchematicCrossing, type SchematicEndplate, type SchematicEndplateTrack, type SchematicIndustry, type SchematicSection, type SchematicSignal, type SchematicTrack, type SchematicTurnout, type SectionAdjacency, type SectionEnd, type SectionEndAssessment, type SectionFootprint, type SectionJointAssessment, type SectionRelativePos, type SignalFacing, type SignalSide, type SpanOverhang, type StoredTrackPart, TIE_HALF_LENGTH_INCHES, TURNOUT_LEAD_INCHES_PER_FROG, type TrackConfig, type TrackPart, type TrackRole, type TurnoutClosure, type TurnoutKind, type UsableCapacity, WHOLE_MODULE_SECTION_ID, asModuleSchematic, assessSectionEnd, assessSectionJoint, benchworkBand, benchworkOutline, buildCrossover, buildPassingSiding, buildTransition, carCapacity, checkEndplateWidth, clearancePointPastFrogInches, crossoverPinches, deriveEndplatePoses, divergeSideForHand, docToState, emptyEditorState, endplateCentreOffsetInches, endplateEdgePose, endplateFaceSegments, endplateLead, endplateTrackOffsetInches, endplateWidthInches, flexPartFor, flexParts, flexPieces, flexUsage, frogCasting, frogNumberFromName, fromSectionRelative, geometryTurnDegrees, hasNoFarEndplate, importedPartToTrackPart, inchesToScaleFeet, isLoopDoc, isTransitionTurnout, laneOffsetAt, leadInchesForSize, maxFlexPieceInches, mergeImportedParts, mergeStoredParts, moduleCenterline, moduleFeatures, moduleFootprint, moduleLengthFromSections, moduleSections, nextId, parseXtpLibrary, partExtent, partExtentForSize, partGeometry, partGeometryGap, partOutlineAtFrog, partsPlaceable, 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 };
2772
+ 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 EndplateEdge, type EndplateEdgePose, type EndplatePose, type EndplateTrackIssue, type EndplateWidthIssue, FAST_TRACKS_N_ME55, FAST_TRACKS_N_ME55_CROSSOVERS, FLEX_TRACK_PARTS, FREEMO_ENDPLATE_TRACK_FASCIA_CLEARANCE_INCHES, FREEMO_ENDPLATE_WIDTH_MIN_INCHES, FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES, FREEMO_TRACK_SPACING_INCHES, type FlexPiece, type FlexPieceEnd, type FrogCasting, type GeometryType, type GraphConflict, type GraphConnection, type GraphRoute, type GraphTurnout, type GraphWalk, type ImportedPart, type IndustryLabelMode, type IndustrySpot, JOINT_SNAP_INCHES, type LanePinch, MAIN2_TRACK_ID, MAIN_TRACK_ID, type ModuleFeatures, type ModuleFootprint, type ModuleFootprintInput, type ModuleGeometryInput, type ModuleSchematicDoc, type ModuleTrackRow, N_CAR_LENGTH_INCHES, N_SCALE_RATIO, type OccupiedSpan, type OutlineFace, PINCH_EASE_INCHES, type PartAngle, type PartDimension, type PartEnd, type PartExtent, type PartGeometry, type PartJoint, type PartJointRole, type PartSegment, type PlacedJoint, 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 TrackGraph, type TrackPart, type TrackPiece, type TrackRole, type TurnoutClosure, type TurnoutKind, type UsableCapacity, WHOLE_MODULE_SECTION_ID, asModuleSchematic, assessSectionEnd, assessSectionJoint, benchworkBand, benchworkOutline, buildCrossover, buildPassingSiding, buildTrackGraph, buildTransition, carCapacity, checkEndplateWidth, clearancePointPastFrogInches, crossoverPinches, deriveEndplatePoses, divergeSideForHand, docToState, emptyEditorState, endplateCentreOffsetInches, endplateEdgePose, endplateFaceSegments, endplateLead, endplateTrackOffsetInches, endplateWidthInches, flexPartFor, flexParts, flexPieces, flexUsage, frogCasting, frogNumberFromName, fromSectionRelative, geometryTurnDegrees, hasNoFarEndplate, importedPartToTrackPart, inchesToScaleFeet, isLoopDoc, isTransitionTurnout, laneOffsetAt, leadInchesForSize, maxFlexPieceInches, mergeImportedParts, mergeStoredParts, moduleCenterline, moduleFeatures, moduleFootprint, moduleLengthFromSections, moduleSections, nextId, parseXtpLibrary, partExtent, partExtentForSize, partGeometry, partGeometryGap, partOutlineAtFrog, partsPlaceable, pastFrogInchesForSize, pathLengthInches, placedJoints, 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, walkTrackGraph };
package/dist/index.js CHANGED
@@ -2243,6 +2243,197 @@ function partsPlaceable(library = BUILT_IN_TRACK_PARTS) {
2243
2243
  }
2244
2244
  return { placeable, blocked };
2245
2245
  }
2246
+ var JOINT_SNAP_INCHES = 0.01;
2247
+ function placedJoints(pieces, library = BUILT_IN_TRACK_PARTS) {
2248
+ const out = [];
2249
+ const RAD = Math.PI / 180;
2250
+ for (const p of pieces) {
2251
+ const part = library.find((x) => x.id === p.partId);
2252
+ if (!part) continue;
2253
+ const geo = partGeometry(part, library);
2254
+ if (!geo) continue;
2255
+ const c = Math.cos(p.rotationDeg * RAD);
2256
+ const s = Math.sin(p.rotationDeg * RAD);
2257
+ for (const j of geo.joints) {
2258
+ const lx = part.kind === "flex" && j.id === "b" ? p.lengthInches ?? 0 : j.x;
2259
+ const ly = p.flipped ? -j.y : j.y;
2260
+ const h = (p.flipped ? -j.angleDeg : j.angleDeg) + p.rotationDeg;
2261
+ out.push({
2262
+ key: `${p.id}.${j.id}`,
2263
+ piece: p.id,
2264
+ joint: j.id,
2265
+ role: j.role,
2266
+ x: p.x + lx * c - ly * s,
2267
+ y: p.y + lx * s + ly * c,
2268
+ headingDeg: norm360(h)
2269
+ });
2270
+ }
2271
+ }
2272
+ return out;
2273
+ }
2274
+ function buildTrackGraph(pieces, library = BUILT_IN_TRACK_PARTS, snapInches = JOINT_SNAP_INCHES) {
2275
+ const joints = placedJoints(pieces, library);
2276
+ const unplaceable = [];
2277
+ for (const p of pieces) {
2278
+ const part = library.find((x) => x.id === p.partId);
2279
+ if (!part) {
2280
+ unplaceable.push({ piece: p.id, partId: p.partId, why: "no such part in the library" });
2281
+ continue;
2282
+ }
2283
+ const why = partGeometryGap(part);
2284
+ if (why) unplaceable.push({ piece: p.id, partId: p.partId, why });
2285
+ }
2286
+ const groups = [];
2287
+ const taken = /* @__PURE__ */ new Set();
2288
+ for (const j of joints) {
2289
+ if (taken.has(j.key)) continue;
2290
+ const g = [j];
2291
+ taken.add(j.key);
2292
+ for (const k of joints) {
2293
+ if (taken.has(k.key) || k.piece === j.piece) continue;
2294
+ if (Math.hypot(k.x - j.x, k.y - j.y) <= snapInches) {
2295
+ g.push(k);
2296
+ taken.add(k.key);
2297
+ }
2298
+ }
2299
+ groups.push(g);
2300
+ }
2301
+ const connections = [];
2302
+ const open = [];
2303
+ const conflicts = [];
2304
+ for (const g of groups) {
2305
+ if (g.length === 1) open.push(g[0].key);
2306
+ else if (g.length === 2) connections.push({ a: g[0].key, b: g[1].key });
2307
+ else {
2308
+ conflicts.push({
2309
+ x: g[0].x,
2310
+ y: g[0].y,
2311
+ joints: g.map((j) => j.key),
2312
+ reason: `${g.length} track ends are stacked in one place. Rail has two ends, so a junction of three is a turnout, not a joint \u2014 none of these are joined until one is moved.`
2313
+ });
2314
+ for (const j of g) open.push(j.key);
2315
+ }
2316
+ }
2317
+ return { joints, connections, open, conflicts, unplaceable };
2318
+ }
2319
+ function walkTrackGraph(graph, pieces, startAt, library = BUILT_IN_TRACK_PARTS) {
2320
+ const byKey = new Map(graph.joints.map((j) => [j.key, j]));
2321
+ const pieceById = new Map(pieces.map((p) => [p.id, p]));
2322
+ const link = /* @__PURE__ */ new Map();
2323
+ for (const c of graph.connections) {
2324
+ link.set(c.a, c.b);
2325
+ link.set(c.b, c.a);
2326
+ }
2327
+ const partOf = (pid) => {
2328
+ const p = pieceById.get(pid);
2329
+ return p ? library.find((x) => x.id === p.partId) : void 0;
2330
+ };
2331
+ const geoOf = (pid) => {
2332
+ const part = partOf(pid);
2333
+ return part ? partGeometry(part, library) : null;
2334
+ };
2335
+ const gap = (a, b) => a && b ? Math.hypot(a.x - b.x, a.y - b.y) : 0;
2336
+ const routes = [];
2337
+ const turnouts = [];
2338
+ const warnings = [];
2339
+ const queued = /* @__PURE__ */ new Set();
2340
+ const pending = [];
2341
+ const walk = (startKey, startPos, id, bornAt) => {
2342
+ const route = {
2343
+ id,
2344
+ fromPos: startPos,
2345
+ toPos: startPos,
2346
+ bornAt,
2347
+ endsAt: null,
2348
+ pieces: [],
2349
+ lateral: 0
2350
+ };
2351
+ let cur = startKey;
2352
+ let pos = startPos;
2353
+ const guard = /* @__PURE__ */ new Set();
2354
+ while (cur && !guard.has(cur)) {
2355
+ guard.add(cur);
2356
+ const here = byKey.get(cur);
2357
+ if (!here) break;
2358
+ const geo = geoOf(here.piece);
2359
+ if (!geo) break;
2360
+ const opts = geo.routes.filter((r) => r.includes(here.joint));
2361
+ if (!opts.length) break;
2362
+ const pick = opts.find((r) => r.includes("through")) ?? opts[0];
2363
+ const exitJoint = pick[0] === here.joint ? pick[1] : pick[0];
2364
+ const exit = byKey.get(`${here.piece}.${exitJoint}`);
2365
+ const part = partOf(here.piece);
2366
+ if (part && (part.kind === "turnout" || part.kind === "wye")) {
2367
+ const throat = byKey.get(`${here.piece}.throat`);
2368
+ const through = byKey.get(`${here.piece}.through`) ?? byKey.get(`${here.piece}.legA`);
2369
+ const body = gap(throat, through);
2370
+ const lead = part.lead?.inches ?? body / 2;
2371
+ const dvj = byKey.get(`${here.piece}.diverge`) ?? byKey.get(`${here.piece}.legB`);
2372
+ const frogPt = throat && body > 0 && dvj ? {
2373
+ x: throat.x + (dvj.x - throat.x) * lead / body,
2374
+ y: throat.y + (dvj.y - throat.y) * lead / body
2375
+ } : null;
2376
+ const toFrog = here.joint === "throat" ? lead : here.joint === "diverge" || here.joint === "legB" ? frogPt && dvj ? Math.hypot(dvj.x - frogPt.x, dvj.y - frogPt.y) : Math.max(0, body - lead) : Math.max(0, body - lead);
2377
+ const frogPos = pos + toFrog;
2378
+ if (here.joint === "diverge" || here.joint === "legB") {
2379
+ route.endsAt = here.piece;
2380
+ const known = turnouts.find((t) => t.id === here.piece);
2381
+ route.toPos = known ? known.pos : frogPos;
2382
+ break;
2383
+ }
2384
+ turnouts.push({ id: here.piece, pos: frogPos, onRoute: id, divergeRoute: null });
2385
+ for (const dj of ["diverge", "legB"]) {
2386
+ const dk = `${here.piece}.${dj}`;
2387
+ const d = byKey.get(dk);
2388
+ if (!d || queued.has(dk)) continue;
2389
+ queued.add(dk);
2390
+ const fp = throat && body > 0 ? {
2391
+ x: throat.x + (d.x - throat.x) * lead / body,
2392
+ y: throat.y + (d.y - throat.y) * lead / body
2393
+ } : null;
2394
+ pending.push({
2395
+ from: here.piece,
2396
+ at: frogPos,
2397
+ joint: dk,
2398
+ skew: fp ? Math.hypot(d.x - fp.x, d.y - fp.y) : 0
2399
+ });
2400
+ }
2401
+ }
2402
+ route.pieces.push(here.piece);
2403
+ for (const j of graph.joints)
2404
+ if (j.piece === here.piece && Math.abs(j.y) > Math.abs(route.lateral))
2405
+ route.lateral = j.y;
2406
+ pos += gap(here, exit);
2407
+ route.toPos = pos;
2408
+ const next = exit ? link.get(exit.key) : void 0;
2409
+ if (!next) break;
2410
+ cur = next;
2411
+ }
2412
+ return route;
2413
+ };
2414
+ routes.push(walk(`${startAt.piece}.${startAt.joint}`, 0, "main", null));
2415
+ let n = 0;
2416
+ while (pending.length) {
2417
+ const b = pending.shift();
2418
+ const start = link.get(b.joint);
2419
+ if (!start) {
2420
+ warnings.push(`the route diverging at ${b.from} is not connected to anything`);
2421
+ continue;
2422
+ }
2423
+ n += 1;
2424
+ const r = walk(start, b.at + b.skew, `route${n}`, b.from);
2425
+ r.fromPos = b.at;
2426
+ routes.push(r);
2427
+ const sw = turnouts.find((t) => t.id === b.from);
2428
+ if (sw && !sw.divergeRoute) sw.divergeRoute = r.id;
2429
+ }
2430
+ const reached = new Set(routes.flatMap((r) => r.pieces));
2431
+ for (const p of pieces)
2432
+ if (!reached.has(p.id) && !graph.unplaceable.some((u) => u.piece === p.id))
2433
+ warnings.push(`${p.id} is not reachable from the endplate \u2014 nothing connects it`);
2434
+ for (const c of graph.conflicts) warnings.push(c.reason);
2435
+ return { routes, turnouts, warnings };
2436
+ }
2246
2437
  function frogCasting(cl, opts = {}) {
2247
2438
  const g = opts.gaugeInches ?? RAIL_GAUGE_INCHES;
2248
2439
  const w = opts.reachInches ?? g * 1.5;
@@ -2876,6 +3067,6 @@ function poseOverridesFromDoc(doc) {
2876
3067
  return out;
2877
3068
  }
2878
3069
 
2879
- export { ATLAS_CODE55_N, BUILT_IN_TRACK_PARTS, CLEARANCE_SPACING_INCHES, CODE55_RAIL_HEIGHT_INCHES, DEFAULT_FLEX_PART_ID, ENDPLATE_FASCIA_CLEAR_INCHES, ENDPLATE_LEAD_INCHES, FAST_TRACKS_N_ME55, FAST_TRACKS_N_ME55_CROSSOVERS, FLEX_TRACK_PARTS, FREEMO_ENDPLATE_TRACK_FASCIA_CLEARANCE_INCHES, FREEMO_ENDPLATE_WIDTH_MIN_INCHES, FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES, FREEMO_TRACK_SPACING_INCHES, MAIN2_TRACK_ID, MAIN_TRACK_ID, N_CAR_LENGTH_INCHES, N_SCALE_RATIO, PINCH_EASE_INCHES, RAIL_GAUGE_INCHES, TIE_HALF_LENGTH_INCHES, TURNOUT_LEAD_INCHES_PER_FROG, WHOLE_MODULE_SECTION_ID, asModuleSchematic, assessSectionEnd, assessSectionJoint, benchworkBand, benchworkOutline, buildCrossover, buildPassingSiding, buildTransition, carCapacity, checkEndplateWidth, clearancePointPastFrogInches, crossoverPinches, deriveEndplatePoses, divergeSideForHand, docToState, emptyEditorState, endplateCentreOffsetInches, endplateEdgePose, endplateFaceSegments, endplateLead, endplateTrackOffsetInches, endplateWidthInches, flexPartFor, flexParts, flexPieces, flexUsage, frogCasting, frogNumberFromName, fromSectionRelative, geometryTurnDegrees, hasNoFarEndplate, importedPartToTrackPart, inchesToScaleFeet, isLoopDoc, isTransitionTurnout, laneOffsetAt, leadInchesForSize, maxFlexPieceInches, mergeImportedParts, mergeStoredParts, moduleCenterline, moduleFeatures, moduleFootprint, moduleLengthFromSections, moduleSections, nextId, parseXtpLibrary, partExtent, partExtentForSize, partGeometry, partGeometryGap, partOutlineAtFrog, partsPlaceable, 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 };
3070
+ export { ATLAS_CODE55_N, BUILT_IN_TRACK_PARTS, CLEARANCE_SPACING_INCHES, CODE55_RAIL_HEIGHT_INCHES, DEFAULT_FLEX_PART_ID, ENDPLATE_FASCIA_CLEAR_INCHES, ENDPLATE_LEAD_INCHES, FAST_TRACKS_N_ME55, FAST_TRACKS_N_ME55_CROSSOVERS, FLEX_TRACK_PARTS, FREEMO_ENDPLATE_TRACK_FASCIA_CLEARANCE_INCHES, FREEMO_ENDPLATE_WIDTH_MIN_INCHES, FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES, FREEMO_TRACK_SPACING_INCHES, JOINT_SNAP_INCHES, MAIN2_TRACK_ID, MAIN_TRACK_ID, N_CAR_LENGTH_INCHES, N_SCALE_RATIO, PINCH_EASE_INCHES, RAIL_GAUGE_INCHES, TIE_HALF_LENGTH_INCHES, TURNOUT_LEAD_INCHES_PER_FROG, WHOLE_MODULE_SECTION_ID, asModuleSchematic, assessSectionEnd, assessSectionJoint, benchworkBand, benchworkOutline, buildCrossover, buildPassingSiding, buildTrackGraph, buildTransition, carCapacity, checkEndplateWidth, clearancePointPastFrogInches, crossoverPinches, deriveEndplatePoses, divergeSideForHand, docToState, emptyEditorState, endplateCentreOffsetInches, endplateEdgePose, endplateFaceSegments, endplateLead, endplateTrackOffsetInches, endplateWidthInches, flexPartFor, flexParts, flexPieces, flexUsage, frogCasting, frogNumberFromName, fromSectionRelative, geometryTurnDegrees, hasNoFarEndplate, importedPartToTrackPart, inchesToScaleFeet, isLoopDoc, isTransitionTurnout, laneOffsetAt, leadInchesForSize, maxFlexPieceInches, mergeImportedParts, mergeStoredParts, moduleCenterline, moduleFeatures, moduleFootprint, moduleLengthFromSections, moduleSections, nextId, parseXtpLibrary, partExtent, partExtentForSize, partGeometry, partGeometryGap, partOutlineAtFrog, partsPlaceable, pastFrogInchesForSize, pathLengthInches, placedJoints, 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, walkTrackGraph };
2880
3071
  //# sourceMappingURL=index.js.map
2881
3072
  //# sourceMappingURL=index.js.map