@willcgage/module-schematic 0.71.0 → 0.72.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +167 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +180 -2
- package/dist/index.d.ts +180 -2
- package/dist/index.js +158 -4
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -123,6 +123,14 @@ interface SchematicTrack {
|
|
|
123
123
|
* the main centre-line + lane, as before. Physical view only; the operations
|
|
124
124
|
* view stays positional (#2d-track). */
|
|
125
125
|
path?: BenchworkPoint[] | null;
|
|
126
|
+
/** The flex product this run is laid with — a slug from the parts library
|
|
127
|
+
* (#193). Absent = the default. Per TRACK, so a module can have its mains in
|
|
128
|
+
* one product and a siding in another. */
|
|
129
|
+
flexPartId?: string | null;
|
|
130
|
+
/** Authored rail joints, inches along this run. Absent = derived from the
|
|
131
|
+
* product's maximum piece length. Present = these are the ONLY joints, so an
|
|
132
|
+
* owner's deliberate cut survives a change elsewhere on the module. */
|
|
133
|
+
flexCuts?: number[] | null;
|
|
126
134
|
}
|
|
127
135
|
interface SchematicTurnout {
|
|
128
136
|
id: string;
|
|
@@ -897,6 +905,19 @@ interface EditorState {
|
|
|
897
905
|
y: number;
|
|
898
906
|
heading: number;
|
|
899
907
|
}>;
|
|
908
|
+
/**
|
|
909
|
+
* Flex track settings by TRACK id (#193) — which product a run is laid with,
|
|
910
|
+
* and where the owner has decided its rail joints fall.
|
|
911
|
+
*
|
|
912
|
+
* One map rather than fields, because the MAINS aren't `extraTracks` (the main
|
|
913
|
+
* IS the centre-line), so per-track settings would otherwise need a separate
|
|
914
|
+
* pair of fields for Main 1 and Main 2 — four ways to say one thing. Round-trips
|
|
915
|
+
* onto each track in the doc, where it's self-describing for Free-Dispatcher.
|
|
916
|
+
*/
|
|
917
|
+
flexByTrack: Record<string, {
|
|
918
|
+
partId?: string;
|
|
919
|
+
cuts?: number[];
|
|
920
|
+
}>;
|
|
900
921
|
/** Authored endplate face widths by endplate id, inches (Free-moN 12″ min,
|
|
901
922
|
* 24″ recommended). Absent id = the recommended default. */
|
|
902
923
|
endplateWidths: Record<string, number>;
|
|
@@ -1242,7 +1263,10 @@ interface TrackPart {
|
|
|
1242
1263
|
line: string;
|
|
1243
1264
|
scale: "N";
|
|
1244
1265
|
name: string;
|
|
1245
|
-
|
|
1266
|
+
/** `flex` is track sold by the length rather than as a fixed geometry — its
|
|
1267
|
+
* {@link overallLength} is the LONGEST piece you can lay from it, not a shape
|
|
1268
|
+
* (#193). */
|
|
1269
|
+
kind: "turnout" | "wye" | "curved-turnout" | "crossing" | "flex";
|
|
1246
1270
|
/** Manufacturer part numbers by hand, where the part has a hand. */
|
|
1247
1271
|
partNumbers?: {
|
|
1248
1272
|
left?: string;
|
|
@@ -1350,10 +1374,164 @@ declare const CODE55_RAIL_HEIGHT_INCHES = 0.055;
|
|
|
1350
1374
|
* everything else is a tooling decision. **Measure the part. Do not model it.**
|
|
1351
1375
|
*/
|
|
1352
1376
|
declare const ATLAS_CODE55_N: TrackPart[];
|
|
1377
|
+
/**
|
|
1378
|
+
* Flex track — the stuff every run that isn't a turnout or a crossing is made
|
|
1379
|
+
* of (#193). A length of flex has no fixed geometry, so the only dimension it
|
|
1380
|
+
* carries is **how long a piece you can lay from one**: past that you have
|
|
1381
|
+
* another length, with a rail joint between them.
|
|
1382
|
+
*
|
|
1383
|
+
* ⚠️ These lengths are the NOMINAL PRODUCT LENGTHS as reported by Will Gage
|
|
1384
|
+
* (2026-07-26), not values read off a spec sheet or measured. They are what the
|
|
1385
|
+
* product is sold as — "30 inch flex track" — which is exactly the number a
|
|
1386
|
+
* builder plans cuts against, so they're recorded as `manufacturer` with the
|
|
1387
|
+
* source named. Correct them if a listing says otherwise.
|
|
1388
|
+
*
|
|
1389
|
+
* No part numbers here: I don't have them confirmed, and a wrong part number is
|
|
1390
|
+
* worse than none — someone would order against it.
|
|
1391
|
+
*/
|
|
1392
|
+
declare const FLEX_TRACK_PARTS: TrackPart[];
|
|
1393
|
+
/** What a track is laid with when nobody has said — the commonest N-scale flex. */
|
|
1394
|
+
declare const DEFAULT_FLEX_PART_ID = "atlas-c55-n-flex";
|
|
1353
1395
|
/** Every built-in part, across manufacturers. */
|
|
1354
1396
|
declare const BUILT_IN_TRACK_PARTS: TrackPart[];
|
|
1397
|
+
/** Every flex product a track can be laid with. */
|
|
1398
|
+
declare function flexParts(library?: TrackPart[]): TrackPart[];
|
|
1399
|
+
/**
|
|
1400
|
+
* The flex product a track is laid with, falling back to the default rather
|
|
1401
|
+
* than to nothing: every non-turnout inch of track IS some product, and a run
|
|
1402
|
+
* whose owner hasn't chosen still gets cut into buyable lengths.
|
|
1403
|
+
*/
|
|
1404
|
+
declare function flexPartFor(id: string | null | undefined, library?: TrackPart[]): TrackPart | null;
|
|
1405
|
+
/** How long a single piece of this product can be, inches. */
|
|
1406
|
+
declare function maxFlexPieceInches(id: string | null | undefined, library?: TrackPart[]): number;
|
|
1355
1407
|
/** Look a part up by its slug. */
|
|
1356
1408
|
declare function trackPart(id: string, library?: TrackPart[]): TrackPart | null;
|
|
1409
|
+
/** One length of flex track in a run — a real object you could pick up (#193). */
|
|
1410
|
+
interface FlexPiece {
|
|
1411
|
+
/** Position in the run, west to east. Stable enough to select and label by. */
|
|
1412
|
+
index: number;
|
|
1413
|
+
/** Inches along the RUN, not the module — the same coordinate turnouts,
|
|
1414
|
+
* industries and signals use, so a piece can be placed without a second
|
|
1415
|
+
* geometry to keep in step. */
|
|
1416
|
+
fromPos: number;
|
|
1417
|
+
toPos: number;
|
|
1418
|
+
lengthInches: number;
|
|
1419
|
+
/** Longer than the product allows. Only reachable from AUTHORED cuts, or from
|
|
1420
|
+
* a run that grew after they were authored — which is precisely when someone
|
|
1421
|
+
* needs telling. */
|
|
1422
|
+
overlong: boolean;
|
|
1423
|
+
/** What this piece butts against at each end: another piece, a part (turnout
|
|
1424
|
+
* or crossing), or the end of the run. */
|
|
1425
|
+
fromEnd: FlexPieceEnd;
|
|
1426
|
+
toEnd: FlexPieceEnd;
|
|
1427
|
+
}
|
|
1428
|
+
/** What a flex piece meets at one of its ends. */
|
|
1429
|
+
type FlexPieceEnd = "piece" | "part" | "runEnd";
|
|
1430
|
+
/**
|
|
1431
|
+
* A stretch of a run that is NOT flex — a turnout's or a crossing's own body.
|
|
1432
|
+
*
|
|
1433
|
+
* A **zero-length** span is meaningful: it's a break in the run at a point whose
|
|
1434
|
+
* own length we don't know. That's a crossing today — the run stops and starts
|
|
1435
|
+
* there, giving a rail joint, but we've measured no crossing part so claiming an
|
|
1436
|
+
* extent for it would be inventing one.
|
|
1437
|
+
*/
|
|
1438
|
+
interface OccupiedSpan {
|
|
1439
|
+
fromPos: number;
|
|
1440
|
+
toPos: number;
|
|
1441
|
+
}
|
|
1442
|
+
/**
|
|
1443
|
+
* Cut a run into lengths of flex track.
|
|
1444
|
+
*
|
|
1445
|
+
* The model (#193): everything that isn't a turnout or a crossing is flex, flex
|
|
1446
|
+
* comes in pieces of a maximum length, and where two pieces meet is a rail
|
|
1447
|
+
* joint — the same joint a turnout makes with the track past it (#189). A 96″
|
|
1448
|
+
* main isn't one piece of track; it's four lengths of Atlas flex with three
|
|
1449
|
+
* joints in it, which is what someone actually buys and lays.
|
|
1450
|
+
*
|
|
1451
|
+
* Pieces are **spans of the run**, not free-floating geometry. That's deliberate:
|
|
1452
|
+
* `pos` — inches along the run — is what every turnout, industry and signal is
|
|
1453
|
+
* placed in, so a piece that carried its own shape would be a second geometry to
|
|
1454
|
+
* keep in step with the first. As spans they get identity, a length you can
|
|
1455
|
+
* change, and ends that meet their neighbours by construction.
|
|
1456
|
+
*
|
|
1457
|
+
* `cuts` is the owner's authoring and is **complete when present**: those are
|
|
1458
|
+
* the only joints, and a piece that ends up too long is flagged rather than
|
|
1459
|
+
* silently re-cut. Absent, the cuts are derived — full lengths from the start of
|
|
1460
|
+
* each gap, remainder at the end, which is how you lay it. That's what lets
|
|
1461
|
+
* every existing module arrive already cut up without anyone touching it.
|
|
1462
|
+
*/
|
|
1463
|
+
declare function flexPieces(input: {
|
|
1464
|
+
fromPos: number;
|
|
1465
|
+
toPos: number;
|
|
1466
|
+
/** Longest single piece of the product this run is laid with. */
|
|
1467
|
+
maxPieceInches: number;
|
|
1468
|
+
/** Stretches the run gives up to parts — turnout bodies, crossings. */
|
|
1469
|
+
occupied?: OccupiedSpan[] | null;
|
|
1470
|
+
/** Authored joint positions, inches along the run. Absent = derive. */
|
|
1471
|
+
cuts?: number[] | null;
|
|
1472
|
+
}): FlexPiece[];
|
|
1473
|
+
/**
|
|
1474
|
+
* Which way a turnout faces along its host track: `+1` = its points look toward
|
|
1475
|
+
* increasing pos, `-1` = the other way.
|
|
1476
|
+
*
|
|
1477
|
+
* ⭐ ONE definition. The canvas had this inline for drawing the leg, and the
|
|
1478
|
+
* flex solver needs the same answer to know which side of `pos` the moulding
|
|
1479
|
+
* extends — a turnout's body is NOT symmetric about its points (an Atlas #7 is
|
|
1480
|
+
* about 0.9″ behind them and 5.1″ ahead), so getting the facing wrong moves a
|
|
1481
|
+
* rail joint four inches (#193).
|
|
1482
|
+
*
|
|
1483
|
+
* The rule: the turnout faces the way its diverging track LEAVES, because that's
|
|
1484
|
+
* where the frog is. `flipped` is the owner's override — rotating the part 180°
|
|
1485
|
+
* faces the points the other way, which is the only thing that can be right for
|
|
1486
|
+
* a siding pinned at a module end where the geometry reads ambiguously.
|
|
1487
|
+
*
|
|
1488
|
+
* `divergeFarPos` is whatever the caller can measure best: the canvas projects
|
|
1489
|
+
* the diverging track's real far end back onto the main, the editor uses its
|
|
1490
|
+
* authored extent. Same rule either way; only the precision of the input differs.
|
|
1491
|
+
*/
|
|
1492
|
+
declare function turnoutFacing(input: {
|
|
1493
|
+
pos: number;
|
|
1494
|
+
/** Where the diverging track ends up, in the same coordinate as `pos`. */
|
|
1495
|
+
divergeFarPos?: number | null;
|
|
1496
|
+
flipped?: boolean;
|
|
1497
|
+
}): 1 | -1;
|
|
1498
|
+
/**
|
|
1499
|
+
* The stretch of its host run a turnout's moulding occupies (#193) — what flex
|
|
1500
|
+
* track has to stop short of on each side.
|
|
1501
|
+
*
|
|
1502
|
+
* Null when the part hasn't been measured, exactly as {@link partExtent} is:
|
|
1503
|
+
* without a real length we don't know where the moulding stops, and a guessed
|
|
1504
|
+
* body would put a rail joint on track nobody has checked.
|
|
1505
|
+
*/
|
|
1506
|
+
declare function turnoutOccupiedSpan(input: {
|
|
1507
|
+
pos: number;
|
|
1508
|
+
extent: PartExtent | null | undefined;
|
|
1509
|
+
facing: 1 | -1;
|
|
1510
|
+
}): OccupiedSpan | null;
|
|
1511
|
+
/**
|
|
1512
|
+
* Retype one piece's length: move the rail joint at its far end, and let its
|
|
1513
|
+
* NEIGHBOUR take up the difference (#193).
|
|
1514
|
+
*
|
|
1515
|
+
* That's what cutting one piece longer actually does — the run doesn't grow, so
|
|
1516
|
+
* the next piece gets shorter by the same amount. The pair's total is fixed,
|
|
1517
|
+
* which is why the value is clamped to it: without that, asking for a length
|
|
1518
|
+
* past the next joint silently reorders the cuts and you get back something you
|
|
1519
|
+
* didn't ask for.
|
|
1520
|
+
*
|
|
1521
|
+
* Returns the run's COMPLETE new cut list (`flexCuts`), or null when the piece
|
|
1522
|
+
* has no neighbour to trade with — the last piece in a gap butts a turnout or
|
|
1523
|
+
* the endplate, and its length is set by what it meets, not by preference.
|
|
1524
|
+
*/
|
|
1525
|
+
declare function resizeFlexPiece(pieces: FlexPiece[], index: number, nextLengthInches: number): number[] | null;
|
|
1526
|
+
/** What a run costs in flex, for someone about to order it (#193). */
|
|
1527
|
+
declare function flexUsage(pieces: FlexPiece[]): {
|
|
1528
|
+
/** How many separate lengths have to be cut. */
|
|
1529
|
+
pieces: number;
|
|
1530
|
+
/** Total flex laid, inches — the run minus whatever the parts occupy. */
|
|
1531
|
+
totalInches: number;
|
|
1532
|
+
/** Pieces the product can't actually supply in one length. */
|
|
1533
|
+
overlong: number;
|
|
1534
|
+
};
|
|
1357
1535
|
/** Half an N-scale tie, inches — a 8′6″ tie is 102″ prototype, /160 ≈ 0.638″.
|
|
1358
1536
|
* The half-width a tie strip extends either side of the rail it carries, which
|
|
1359
1537
|
* is what gives a drawn turnout body its width. */
|
|
@@ -1844,4 +2022,4 @@ declare function poseOverridesFromDoc(doc: ModuleSchematicDoc): Record<string, {
|
|
|
1844
2022
|
heading: number;
|
|
1845
2023
|
}>;
|
|
1846
2024
|
|
|
1847
|
-
export { ATLAS_CODE55_N, BUILT_IN_TRACK_PARTS, type BenchworkPoint, type BranchConnector, CODE55_RAIL_HEIGHT_INCHES, type DimensionSource, type DrawCrossing, type DrawCrossover, type DrawIndustry, type DrawSignal, type DrawTrack, type DrawTurnout, ENDPLATE_FASCIA_CLEAR_INCHES, ENDPLATE_LEAD_INCHES, type EditorBranch, type EditorControlPoint, type EditorCpSignal, type EditorCrossing, type EditorIndustry, type EditorState, type EditorTrack, type EditorTurnout, type EndplateBConfig, type EndplatePose, type EndplateTrackIssue, type EndplateWidthIssue, FREEMO_ENDPLATE_TRACK_FASCIA_CLEARANCE_INCHES, FREEMO_ENDPLATE_WIDTH_MIN_INCHES, FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES, FREEMO_TRACK_SPACING_INCHES, type FrogCasting, type GeometryType, type ImportedPart, type IndustryLabelMode, type IndustrySpot, MAIN2_TRACK_ID, MAIN_TRACK_ID, type ModuleFeatures, type ModuleFootprint, type ModuleFootprintInput, type ModuleGeometryInput, type ModuleSchematicDoc, type ModuleTrackRow, N_CAR_LENGTH_INCHES, N_SCALE_RATIO, type OutlineFace, type PartAngle, type PartDimension, type PartEnd, type PartExtent, type PartSegment, RAIL_GAUGE_INCHES, type ReturnLoopGeometry, type ReturnLoopShape, type SchematicBlock, type SchematicControlPoint, type SchematicCrossing, type SchematicEndplate, type SchematicEndplateTrack, type SchematicIndustry, type SchematicSection, type SchematicSignal, type SchematicTrack, type SchematicTurnout, type SectionAdjacency, type SectionFootprint, type SectionRelativePos, type SignalFacing, type SignalSide, type StoredTrackPart, TIE_HALF_LENGTH_INCHES, TURNOUT_LEAD_INCHES_PER_FROG, type TrackConfig, type TrackPart, type TrackRole, type TurnoutClosure, type TurnoutKind, WHOLE_MODULE_SECTION_ID, asModuleSchematic, benchworkBand, benchworkOutline, buildCrossover, buildPassingSiding, buildTransition, carCapacity, checkEndplateWidth, deriveEndplatePoses, divergeSideForHand, docToState, emptyEditorState, endplateCentreOffsetInches, endplateFaceSegments, endplateLead, endplateTrackOffsetInches, endplateWidthInches, frogCasting, frogNumberFromName, fromSectionRelative, geometryTurnDegrees, hasNoFarEndplate, importedPartToTrackPart, inchesToScaleFeet, isLoopDoc, isTransitionTurnout, leadInchesForSize, mergeImportedParts, mergeStoredParts, moduleCenterline, moduleFeatures, moduleFootprint, moduleLengthFromSections, moduleSections, nextId, parseXtpLibrary, partExtent, partExtentForSize, partOutlineAtFrog, pastFrogInchesForSize, pathLengthInches, poseNeedsManual, poseOverridesFromDoc, remapPos, returnLoop, sampleBenchworkOutline, samplePartSegments, samplePath, scaleFeetToInches, sectionAdjacency, sectionBand, sectionBreaksFromSections, sectionComponents, sectionFootprints, sectionNeighbours, sectionSpans, sectionSpansOrWhole, sectionedCenterline, sectionedEndPose, sliceCenterline, stateToDoc, storedPartToTrackPart, toSectionRelative, trackMeetsEndplateIssues, trackPart, trackPath, turnoutClosure, turnoutPartForSize };
|
|
2025
|
+
export { ATLAS_CODE55_N, BUILT_IN_TRACK_PARTS, type BenchworkPoint, type BranchConnector, 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 StoredTrackPart, TIE_HALF_LENGTH_INCHES, TURNOUT_LEAD_INCHES_PER_FROG, type TrackConfig, type TrackPart, type TrackRole, type TurnoutClosure, type TurnoutKind, WHOLE_MODULE_SECTION_ID, asModuleSchematic, benchworkBand, benchworkOutline, buildCrossover, buildPassingSiding, buildTransition, carCapacity, checkEndplateWidth, deriveEndplatePoses, divergeSideForHand, docToState, emptyEditorState, endplateCentreOffsetInches, endplateFaceSegments, endplateLead, endplateTrackOffsetInches, endplateWidthInches, 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, stateToDoc, storedPartToTrackPart, toSectionRelative, trackMeetsEndplateIssues, trackPart, trackPath, turnoutClosure, turnoutFacing, turnoutOccupiedSpan, turnoutPartForSize };
|
package/dist/index.d.ts
CHANGED
|
@@ -123,6 +123,14 @@ interface SchematicTrack {
|
|
|
123
123
|
* the main centre-line + lane, as before. Physical view only; the operations
|
|
124
124
|
* view stays positional (#2d-track). */
|
|
125
125
|
path?: BenchworkPoint[] | null;
|
|
126
|
+
/** The flex product this run is laid with — a slug from the parts library
|
|
127
|
+
* (#193). Absent = the default. Per TRACK, so a module can have its mains in
|
|
128
|
+
* one product and a siding in another. */
|
|
129
|
+
flexPartId?: string | null;
|
|
130
|
+
/** Authored rail joints, inches along this run. Absent = derived from the
|
|
131
|
+
* product's maximum piece length. Present = these are the ONLY joints, so an
|
|
132
|
+
* owner's deliberate cut survives a change elsewhere on the module. */
|
|
133
|
+
flexCuts?: number[] | null;
|
|
126
134
|
}
|
|
127
135
|
interface SchematicTurnout {
|
|
128
136
|
id: string;
|
|
@@ -897,6 +905,19 @@ interface EditorState {
|
|
|
897
905
|
y: number;
|
|
898
906
|
heading: number;
|
|
899
907
|
}>;
|
|
908
|
+
/**
|
|
909
|
+
* Flex track settings by TRACK id (#193) — which product a run is laid with,
|
|
910
|
+
* and where the owner has decided its rail joints fall.
|
|
911
|
+
*
|
|
912
|
+
* One map rather than fields, because the MAINS aren't `extraTracks` (the main
|
|
913
|
+
* IS the centre-line), so per-track settings would otherwise need a separate
|
|
914
|
+
* pair of fields for Main 1 and Main 2 — four ways to say one thing. Round-trips
|
|
915
|
+
* onto each track in the doc, where it's self-describing for Free-Dispatcher.
|
|
916
|
+
*/
|
|
917
|
+
flexByTrack: Record<string, {
|
|
918
|
+
partId?: string;
|
|
919
|
+
cuts?: number[];
|
|
920
|
+
}>;
|
|
900
921
|
/** Authored endplate face widths by endplate id, inches (Free-moN 12″ min,
|
|
901
922
|
* 24″ recommended). Absent id = the recommended default. */
|
|
902
923
|
endplateWidths: Record<string, number>;
|
|
@@ -1242,7 +1263,10 @@ interface TrackPart {
|
|
|
1242
1263
|
line: string;
|
|
1243
1264
|
scale: "N";
|
|
1244
1265
|
name: string;
|
|
1245
|
-
|
|
1266
|
+
/** `flex` is track sold by the length rather than as a fixed geometry — its
|
|
1267
|
+
* {@link overallLength} is the LONGEST piece you can lay from it, not a shape
|
|
1268
|
+
* (#193). */
|
|
1269
|
+
kind: "turnout" | "wye" | "curved-turnout" | "crossing" | "flex";
|
|
1246
1270
|
/** Manufacturer part numbers by hand, where the part has a hand. */
|
|
1247
1271
|
partNumbers?: {
|
|
1248
1272
|
left?: string;
|
|
@@ -1350,10 +1374,164 @@ declare const CODE55_RAIL_HEIGHT_INCHES = 0.055;
|
|
|
1350
1374
|
* everything else is a tooling decision. **Measure the part. Do not model it.**
|
|
1351
1375
|
*/
|
|
1352
1376
|
declare const ATLAS_CODE55_N: TrackPart[];
|
|
1377
|
+
/**
|
|
1378
|
+
* Flex track — the stuff every run that isn't a turnout or a crossing is made
|
|
1379
|
+
* of (#193). A length of flex has no fixed geometry, so the only dimension it
|
|
1380
|
+
* carries is **how long a piece you can lay from one**: past that you have
|
|
1381
|
+
* another length, with a rail joint between them.
|
|
1382
|
+
*
|
|
1383
|
+
* ⚠️ These lengths are the NOMINAL PRODUCT LENGTHS as reported by Will Gage
|
|
1384
|
+
* (2026-07-26), not values read off a spec sheet or measured. They are what the
|
|
1385
|
+
* product is sold as — "30 inch flex track" — which is exactly the number a
|
|
1386
|
+
* builder plans cuts against, so they're recorded as `manufacturer` with the
|
|
1387
|
+
* source named. Correct them if a listing says otherwise.
|
|
1388
|
+
*
|
|
1389
|
+
* No part numbers here: I don't have them confirmed, and a wrong part number is
|
|
1390
|
+
* worse than none — someone would order against it.
|
|
1391
|
+
*/
|
|
1392
|
+
declare const FLEX_TRACK_PARTS: TrackPart[];
|
|
1393
|
+
/** What a track is laid with when nobody has said — the commonest N-scale flex. */
|
|
1394
|
+
declare const DEFAULT_FLEX_PART_ID = "atlas-c55-n-flex";
|
|
1353
1395
|
/** Every built-in part, across manufacturers. */
|
|
1354
1396
|
declare const BUILT_IN_TRACK_PARTS: TrackPart[];
|
|
1397
|
+
/** Every flex product a track can be laid with. */
|
|
1398
|
+
declare function flexParts(library?: TrackPart[]): TrackPart[];
|
|
1399
|
+
/**
|
|
1400
|
+
* The flex product a track is laid with, falling back to the default rather
|
|
1401
|
+
* than to nothing: every non-turnout inch of track IS some product, and a run
|
|
1402
|
+
* whose owner hasn't chosen still gets cut into buyable lengths.
|
|
1403
|
+
*/
|
|
1404
|
+
declare function flexPartFor(id: string | null | undefined, library?: TrackPart[]): TrackPart | null;
|
|
1405
|
+
/** How long a single piece of this product can be, inches. */
|
|
1406
|
+
declare function maxFlexPieceInches(id: string | null | undefined, library?: TrackPart[]): number;
|
|
1355
1407
|
/** Look a part up by its slug. */
|
|
1356
1408
|
declare function trackPart(id: string, library?: TrackPart[]): TrackPart | null;
|
|
1409
|
+
/** One length of flex track in a run — a real object you could pick up (#193). */
|
|
1410
|
+
interface FlexPiece {
|
|
1411
|
+
/** Position in the run, west to east. Stable enough to select and label by. */
|
|
1412
|
+
index: number;
|
|
1413
|
+
/** Inches along the RUN, not the module — the same coordinate turnouts,
|
|
1414
|
+
* industries and signals use, so a piece can be placed without a second
|
|
1415
|
+
* geometry to keep in step. */
|
|
1416
|
+
fromPos: number;
|
|
1417
|
+
toPos: number;
|
|
1418
|
+
lengthInches: number;
|
|
1419
|
+
/** Longer than the product allows. Only reachable from AUTHORED cuts, or from
|
|
1420
|
+
* a run that grew after they were authored — which is precisely when someone
|
|
1421
|
+
* needs telling. */
|
|
1422
|
+
overlong: boolean;
|
|
1423
|
+
/** What this piece butts against at each end: another piece, a part (turnout
|
|
1424
|
+
* or crossing), or the end of the run. */
|
|
1425
|
+
fromEnd: FlexPieceEnd;
|
|
1426
|
+
toEnd: FlexPieceEnd;
|
|
1427
|
+
}
|
|
1428
|
+
/** What a flex piece meets at one of its ends. */
|
|
1429
|
+
type FlexPieceEnd = "piece" | "part" | "runEnd";
|
|
1430
|
+
/**
|
|
1431
|
+
* A stretch of a run that is NOT flex — a turnout's or a crossing's own body.
|
|
1432
|
+
*
|
|
1433
|
+
* A **zero-length** span is meaningful: it's a break in the run at a point whose
|
|
1434
|
+
* own length we don't know. That's a crossing today — the run stops and starts
|
|
1435
|
+
* there, giving a rail joint, but we've measured no crossing part so claiming an
|
|
1436
|
+
* extent for it would be inventing one.
|
|
1437
|
+
*/
|
|
1438
|
+
interface OccupiedSpan {
|
|
1439
|
+
fromPos: number;
|
|
1440
|
+
toPos: number;
|
|
1441
|
+
}
|
|
1442
|
+
/**
|
|
1443
|
+
* Cut a run into lengths of flex track.
|
|
1444
|
+
*
|
|
1445
|
+
* The model (#193): everything that isn't a turnout or a crossing is flex, flex
|
|
1446
|
+
* comes in pieces of a maximum length, and where two pieces meet is a rail
|
|
1447
|
+
* joint — the same joint a turnout makes with the track past it (#189). A 96″
|
|
1448
|
+
* main isn't one piece of track; it's four lengths of Atlas flex with three
|
|
1449
|
+
* joints in it, which is what someone actually buys and lays.
|
|
1450
|
+
*
|
|
1451
|
+
* Pieces are **spans of the run**, not free-floating geometry. That's deliberate:
|
|
1452
|
+
* `pos` — inches along the run — is what every turnout, industry and signal is
|
|
1453
|
+
* placed in, so a piece that carried its own shape would be a second geometry to
|
|
1454
|
+
* keep in step with the first. As spans they get identity, a length you can
|
|
1455
|
+
* change, and ends that meet their neighbours by construction.
|
|
1456
|
+
*
|
|
1457
|
+
* `cuts` is the owner's authoring and is **complete when present**: those are
|
|
1458
|
+
* the only joints, and a piece that ends up too long is flagged rather than
|
|
1459
|
+
* silently re-cut. Absent, the cuts are derived — full lengths from the start of
|
|
1460
|
+
* each gap, remainder at the end, which is how you lay it. That's what lets
|
|
1461
|
+
* every existing module arrive already cut up without anyone touching it.
|
|
1462
|
+
*/
|
|
1463
|
+
declare function flexPieces(input: {
|
|
1464
|
+
fromPos: number;
|
|
1465
|
+
toPos: number;
|
|
1466
|
+
/** Longest single piece of the product this run is laid with. */
|
|
1467
|
+
maxPieceInches: number;
|
|
1468
|
+
/** Stretches the run gives up to parts — turnout bodies, crossings. */
|
|
1469
|
+
occupied?: OccupiedSpan[] | null;
|
|
1470
|
+
/** Authored joint positions, inches along the run. Absent = derive. */
|
|
1471
|
+
cuts?: number[] | null;
|
|
1472
|
+
}): FlexPiece[];
|
|
1473
|
+
/**
|
|
1474
|
+
* Which way a turnout faces along its host track: `+1` = its points look toward
|
|
1475
|
+
* increasing pos, `-1` = the other way.
|
|
1476
|
+
*
|
|
1477
|
+
* ⭐ ONE definition. The canvas had this inline for drawing the leg, and the
|
|
1478
|
+
* flex solver needs the same answer to know which side of `pos` the moulding
|
|
1479
|
+
* extends — a turnout's body is NOT symmetric about its points (an Atlas #7 is
|
|
1480
|
+
* about 0.9″ behind them and 5.1″ ahead), so getting the facing wrong moves a
|
|
1481
|
+
* rail joint four inches (#193).
|
|
1482
|
+
*
|
|
1483
|
+
* The rule: the turnout faces the way its diverging track LEAVES, because that's
|
|
1484
|
+
* where the frog is. `flipped` is the owner's override — rotating the part 180°
|
|
1485
|
+
* faces the points the other way, which is the only thing that can be right for
|
|
1486
|
+
* a siding pinned at a module end where the geometry reads ambiguously.
|
|
1487
|
+
*
|
|
1488
|
+
* `divergeFarPos` is whatever the caller can measure best: the canvas projects
|
|
1489
|
+
* the diverging track's real far end back onto the main, the editor uses its
|
|
1490
|
+
* authored extent. Same rule either way; only the precision of the input differs.
|
|
1491
|
+
*/
|
|
1492
|
+
declare function turnoutFacing(input: {
|
|
1493
|
+
pos: number;
|
|
1494
|
+
/** Where the diverging track ends up, in the same coordinate as `pos`. */
|
|
1495
|
+
divergeFarPos?: number | null;
|
|
1496
|
+
flipped?: boolean;
|
|
1497
|
+
}): 1 | -1;
|
|
1498
|
+
/**
|
|
1499
|
+
* The stretch of its host run a turnout's moulding occupies (#193) — what flex
|
|
1500
|
+
* track has to stop short of on each side.
|
|
1501
|
+
*
|
|
1502
|
+
* Null when the part hasn't been measured, exactly as {@link partExtent} is:
|
|
1503
|
+
* without a real length we don't know where the moulding stops, and a guessed
|
|
1504
|
+
* body would put a rail joint on track nobody has checked.
|
|
1505
|
+
*/
|
|
1506
|
+
declare function turnoutOccupiedSpan(input: {
|
|
1507
|
+
pos: number;
|
|
1508
|
+
extent: PartExtent | null | undefined;
|
|
1509
|
+
facing: 1 | -1;
|
|
1510
|
+
}): OccupiedSpan | null;
|
|
1511
|
+
/**
|
|
1512
|
+
* Retype one piece's length: move the rail joint at its far end, and let its
|
|
1513
|
+
* NEIGHBOUR take up the difference (#193).
|
|
1514
|
+
*
|
|
1515
|
+
* That's what cutting one piece longer actually does — the run doesn't grow, so
|
|
1516
|
+
* the next piece gets shorter by the same amount. The pair's total is fixed,
|
|
1517
|
+
* which is why the value is clamped to it: without that, asking for a length
|
|
1518
|
+
* past the next joint silently reorders the cuts and you get back something you
|
|
1519
|
+
* didn't ask for.
|
|
1520
|
+
*
|
|
1521
|
+
* Returns the run's COMPLETE new cut list (`flexCuts`), or null when the piece
|
|
1522
|
+
* has no neighbour to trade with — the last piece in a gap butts a turnout or
|
|
1523
|
+
* the endplate, and its length is set by what it meets, not by preference.
|
|
1524
|
+
*/
|
|
1525
|
+
declare function resizeFlexPiece(pieces: FlexPiece[], index: number, nextLengthInches: number): number[] | null;
|
|
1526
|
+
/** What a run costs in flex, for someone about to order it (#193). */
|
|
1527
|
+
declare function flexUsage(pieces: FlexPiece[]): {
|
|
1528
|
+
/** How many separate lengths have to be cut. */
|
|
1529
|
+
pieces: number;
|
|
1530
|
+
/** Total flex laid, inches — the run minus whatever the parts occupy. */
|
|
1531
|
+
totalInches: number;
|
|
1532
|
+
/** Pieces the product can't actually supply in one length. */
|
|
1533
|
+
overlong: number;
|
|
1534
|
+
};
|
|
1357
1535
|
/** Half an N-scale tie, inches — a 8′6″ tie is 102″ prototype, /160 ≈ 0.638″.
|
|
1358
1536
|
* The half-width a tie strip extends either side of the rail it carries, which
|
|
1359
1537
|
* is what gives a drawn turnout body its width. */
|
|
@@ -1844,4 +2022,4 @@ declare function poseOverridesFromDoc(doc: ModuleSchematicDoc): Record<string, {
|
|
|
1844
2022
|
heading: number;
|
|
1845
2023
|
}>;
|
|
1846
2024
|
|
|
1847
|
-
export { ATLAS_CODE55_N, BUILT_IN_TRACK_PARTS, type BenchworkPoint, type BranchConnector, CODE55_RAIL_HEIGHT_INCHES, type DimensionSource, type DrawCrossing, type DrawCrossover, type DrawIndustry, type DrawSignal, type DrawTrack, type DrawTurnout, ENDPLATE_FASCIA_CLEAR_INCHES, ENDPLATE_LEAD_INCHES, type EditorBranch, type EditorControlPoint, type EditorCpSignal, type EditorCrossing, type EditorIndustry, type EditorState, type EditorTrack, type EditorTurnout, type EndplateBConfig, type EndplatePose, type EndplateTrackIssue, type EndplateWidthIssue, FREEMO_ENDPLATE_TRACK_FASCIA_CLEARANCE_INCHES, FREEMO_ENDPLATE_WIDTH_MIN_INCHES, FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES, FREEMO_TRACK_SPACING_INCHES, type FrogCasting, type GeometryType, type ImportedPart, type IndustryLabelMode, type IndustrySpot, MAIN2_TRACK_ID, MAIN_TRACK_ID, type ModuleFeatures, type ModuleFootprint, type ModuleFootprintInput, type ModuleGeometryInput, type ModuleSchematicDoc, type ModuleTrackRow, N_CAR_LENGTH_INCHES, N_SCALE_RATIO, type OutlineFace, type PartAngle, type PartDimension, type PartEnd, type PartExtent, type PartSegment, RAIL_GAUGE_INCHES, type ReturnLoopGeometry, type ReturnLoopShape, type SchematicBlock, type SchematicControlPoint, type SchematicCrossing, type SchematicEndplate, type SchematicEndplateTrack, type SchematicIndustry, type SchematicSection, type SchematicSignal, type SchematicTrack, type SchematicTurnout, type SectionAdjacency, type SectionFootprint, type SectionRelativePos, type SignalFacing, type SignalSide, type StoredTrackPart, TIE_HALF_LENGTH_INCHES, TURNOUT_LEAD_INCHES_PER_FROG, type TrackConfig, type TrackPart, type TrackRole, type TurnoutClosure, type TurnoutKind, WHOLE_MODULE_SECTION_ID, asModuleSchematic, benchworkBand, benchworkOutline, buildCrossover, buildPassingSiding, buildTransition, carCapacity, checkEndplateWidth, deriveEndplatePoses, divergeSideForHand, docToState, emptyEditorState, endplateCentreOffsetInches, endplateFaceSegments, endplateLead, endplateTrackOffsetInches, endplateWidthInches, frogCasting, frogNumberFromName, fromSectionRelative, geometryTurnDegrees, hasNoFarEndplate, importedPartToTrackPart, inchesToScaleFeet, isLoopDoc, isTransitionTurnout, leadInchesForSize, mergeImportedParts, mergeStoredParts, moduleCenterline, moduleFeatures, moduleFootprint, moduleLengthFromSections, moduleSections, nextId, parseXtpLibrary, partExtent, partExtentForSize, partOutlineAtFrog, pastFrogInchesForSize, pathLengthInches, poseNeedsManual, poseOverridesFromDoc, remapPos, returnLoop, sampleBenchworkOutline, samplePartSegments, samplePath, scaleFeetToInches, sectionAdjacency, sectionBand, sectionBreaksFromSections, sectionComponents, sectionFootprints, sectionNeighbours, sectionSpans, sectionSpansOrWhole, sectionedCenterline, sectionedEndPose, sliceCenterline, stateToDoc, storedPartToTrackPart, toSectionRelative, trackMeetsEndplateIssues, trackPart, trackPath, turnoutClosure, turnoutPartForSize };
|
|
2025
|
+
export { ATLAS_CODE55_N, BUILT_IN_TRACK_PARTS, type BenchworkPoint, type BranchConnector, 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 StoredTrackPart, TIE_HALF_LENGTH_INCHES, TURNOUT_LEAD_INCHES_PER_FROG, type TrackConfig, type TrackPart, type TrackRole, type TurnoutClosure, type TurnoutKind, WHOLE_MODULE_SECTION_ID, asModuleSchematic, benchworkBand, benchworkOutline, buildCrossover, buildPassingSiding, buildTransition, carCapacity, checkEndplateWidth, deriveEndplatePoses, divergeSideForHand, docToState, emptyEditorState, endplateCentreOffsetInches, endplateFaceSegments, endplateLead, endplateTrackOffsetInches, endplateWidthInches, 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, stateToDoc, storedPartToTrackPart, toSectionRelative, trackMeetsEndplateIssues, trackPart, trackPath, turnoutClosure, turnoutFacing, turnoutOccupiedSpan, turnoutPartForSize };
|