@willcgage/module-schematic 0.95.1 → 0.97.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 +87 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +72 -2
- package/dist/index.d.ts +72 -2
- package/dist/index.js +84 -6
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1703,11 +1703,22 @@ var FAST_TRACKS_N_ME55_CROSSOVERS = [
|
|
|
1703
1703
|
};
|
|
1704
1704
|
});
|
|
1705
1705
|
var DEFAULT_FLEX_PART_ID = "atlas-c55-n-flex";
|
|
1706
|
+
var GENERIC_END_OF_TRACK = [
|
|
1707
|
+
{
|
|
1708
|
+
id: "generic-bumper",
|
|
1709
|
+
manufacturer: "Generic",
|
|
1710
|
+
line: "N scale",
|
|
1711
|
+
scale: "N",
|
|
1712
|
+
name: "Bumper",
|
|
1713
|
+
kind: "bumper"
|
|
1714
|
+
}
|
|
1715
|
+
];
|
|
1706
1716
|
var BUILT_IN_TRACK_PARTS = [
|
|
1707
1717
|
...ATLAS_CODE55_N,
|
|
1708
1718
|
...FAST_TRACKS_N_ME55,
|
|
1709
1719
|
...FAST_TRACKS_N_ME55_CROSSOVERS,
|
|
1710
|
-
...FLEX_TRACK_PARTS
|
|
1720
|
+
...FLEX_TRACK_PARTS,
|
|
1721
|
+
...GENERIC_END_OF_TRACK
|
|
1711
1722
|
];
|
|
1712
1723
|
function flexParts(library = BUILT_IN_TRACK_PARTS) {
|
|
1713
1724
|
return library.filter((p) => p.kind === "flex");
|
|
@@ -2008,14 +2019,14 @@ function storedPartToTrackPart(row) {
|
|
|
2008
2019
|
source: points.source === "measured" && frog.source === "measured" ? "measured" : "derived",
|
|
2009
2020
|
...note ? { note } : {}
|
|
2010
2021
|
} : dim(row.leadInches, row.leadSource);
|
|
2011
|
-
const kind = row.kind;
|
|
2022
|
+
const kind = TRACK_PART_KINDS.includes(row.kind) ? row.kind : "turnout";
|
|
2012
2023
|
const part = {
|
|
2013
2024
|
id: row.slug,
|
|
2014
2025
|
manufacturer: row.manufacturer,
|
|
2015
2026
|
line: row.line,
|
|
2016
2027
|
scale: "N",
|
|
2017
2028
|
name: row.name,
|
|
2018
|
-
kind
|
|
2029
|
+
kind
|
|
2019
2030
|
};
|
|
2020
2031
|
const numbers = {
|
|
2021
2032
|
...row.partNumberLeft ? { left: row.partNumberLeft } : {},
|
|
@@ -2050,6 +2061,9 @@ function storedPartToTrackPart(row) {
|
|
|
2050
2061
|
const inner = dim(row.innerRadiusInches, row.radiusSource);
|
|
2051
2062
|
if (outer) part.outerRadius = outer;
|
|
2052
2063
|
if (inner) part.innerRadius = inner;
|
|
2064
|
+
const radius = dim(row.radiusInches, row.radiusSource);
|
|
2065
|
+
if (radius) part.radius = radius;
|
|
2066
|
+
if (typeof row.arcDegrees === "number") part.arcDegrees = row.arcDegrees;
|
|
2053
2067
|
if (typeof row.actualAngleDeg === "number")
|
|
2054
2068
|
part.actualAngle = {
|
|
2055
2069
|
deg: row.actualAngleDeg,
|
|
@@ -2162,6 +2176,14 @@ function turnoutClosure(size, opts = {}) {
|
|
|
2162
2176
|
}
|
|
2163
2177
|
function partGeometryGap(part) {
|
|
2164
2178
|
if (part.kind === "flex") return null;
|
|
2179
|
+
if (part.kind === "bumper") return null;
|
|
2180
|
+
if (part.kind === "straight")
|
|
2181
|
+
return part.overallLength ? null : "no overall length \u2014 a sectional straight IS its length";
|
|
2182
|
+
if (part.kind === "curve") {
|
|
2183
|
+
if (!part.radius) return "no radius \u2014 a sectional curve is a radius and an arc";
|
|
2184
|
+
if (part.arcDegrees == null) return "no arc \u2014 the radius alone does not say how far it turns";
|
|
2185
|
+
return null;
|
|
2186
|
+
}
|
|
2165
2187
|
if (part.kind === "crossover")
|
|
2166
2188
|
return "a crossover fixture builds one HALF of the assembly (piecesPerAssembly), so its published lengths describe a piece, not the finished part \u2014 the geometry of the whole crossover is not yet derivable from them";
|
|
2167
2189
|
if (part.kind === "crossing") return "crossing geometry is not modelled yet";
|
|
@@ -2175,6 +2197,26 @@ function partGeometryGap(part) {
|
|
|
2175
2197
|
}
|
|
2176
2198
|
function partGeometry(part, library = BUILT_IN_TRACK_PARTS) {
|
|
2177
2199
|
if (partGeometryGap(part)) return null;
|
|
2200
|
+
if (part.kind === "straight" || part.kind === "curve") {
|
|
2201
|
+
const end = part.kind === "curve" ? flexRunEnd(sectionalArcInches(part), part.radius.inches) : { x: part.overallLength.inches, y: 0, headingDeg: 0 };
|
|
2202
|
+
return {
|
|
2203
|
+
joints: [
|
|
2204
|
+
{ id: "a", role: "throat", x: 0, y: 0, angleDeg: 180 },
|
|
2205
|
+
{ id: "b", role: "through", x: end.x, y: end.y, angleDeg: end.headingDeg }
|
|
2206
|
+
],
|
|
2207
|
+
routes: [["a", "b"]],
|
|
2208
|
+
source: (part.kind === "curve" ? part.radius : part.overallLength)?.source ?? "unverified",
|
|
2209
|
+
divergingEndMeasured: false
|
|
2210
|
+
};
|
|
2211
|
+
}
|
|
2212
|
+
if (part.kind === "bumper") {
|
|
2213
|
+
return {
|
|
2214
|
+
joints: [{ id: "a", role: "throat", x: 0, y: 0, angleDeg: 180 }],
|
|
2215
|
+
routes: [],
|
|
2216
|
+
source: part.overallLength?.source ?? "unverified",
|
|
2217
|
+
divergingEndMeasured: false
|
|
2218
|
+
};
|
|
2219
|
+
}
|
|
2178
2220
|
if (part.kind === "flex") {
|
|
2179
2221
|
return {
|
|
2180
2222
|
joints: [
|
|
@@ -2265,6 +2307,12 @@ function pieceRoutePaths(piece, library = BUILT_IN_TRACK_PARTS) {
|
|
|
2265
2307
|
return { x: piece.x + x * c - ly * s, y: piece.y + x * s + ly * c };
|
|
2266
2308
|
};
|
|
2267
2309
|
const out = [];
|
|
2310
|
+
if (part.kind === "bumper") {
|
|
2311
|
+
const at2 = joints[0];
|
|
2312
|
+
if (!at2) return out;
|
|
2313
|
+
const len = part.overallLength?.inches ?? BUMPER_DRAWN_INCHES;
|
|
2314
|
+
return [{ route: ["a", "a"], points: [{ x: at2.x, y: at2.y }, place(len, 0)] }];
|
|
2315
|
+
}
|
|
2268
2316
|
for (const route of geo.routes) {
|
|
2269
2317
|
const a = at(route[0]);
|
|
2270
2318
|
const b = at(route[1]);
|
|
@@ -2328,6 +2376,22 @@ function partsPlaceable(library = BUILT_IN_TRACK_PARTS) {
|
|
|
2328
2376
|
return { placeable, blocked };
|
|
2329
2377
|
}
|
|
2330
2378
|
var JOINT_SNAP_INCHES = 0.01;
|
|
2379
|
+
var TRACK_PART_KINDS = [
|
|
2380
|
+
"turnout",
|
|
2381
|
+
"wye",
|
|
2382
|
+
"curved-turnout",
|
|
2383
|
+
"crossover",
|
|
2384
|
+
"crossing",
|
|
2385
|
+
"flex",
|
|
2386
|
+
"straight",
|
|
2387
|
+
"curve",
|
|
2388
|
+
"bumper"
|
|
2389
|
+
];
|
|
2390
|
+
var BUMPER_DRAWN_INCHES = 0.75;
|
|
2391
|
+
function sectionalArcInches(part) {
|
|
2392
|
+
if (part.kind !== "curve" || !part.radius || part.arcDegrees == null) return 0;
|
|
2393
|
+
return part.radius.inches * Math.abs(part.arcDegrees) * Math.PI / 180;
|
|
2394
|
+
}
|
|
2331
2395
|
function flexRunEnd(lengthInches, radiusInches) {
|
|
2332
2396
|
const L = lengthInches;
|
|
2333
2397
|
const R = radiusInches;
|
|
@@ -2503,6 +2567,7 @@ function walkTrackGraph(graph, pieces, startAt, library = BUILT_IN_TRACK_PARTS)
|
|
|
2503
2567
|
const piece = pieceById.get(a.piece);
|
|
2504
2568
|
const part = piece ? partOf(a.piece) : void 0;
|
|
2505
2569
|
if (piece && part?.kind === "flex") return piece.lengthInches ?? 0;
|
|
2570
|
+
if (part?.kind === "curve") return sectionalArcInches(part);
|
|
2506
2571
|
}
|
|
2507
2572
|
return Math.hypot(a.x - b.x, a.y - b.y);
|
|
2508
2573
|
};
|
|
@@ -2519,6 +2584,7 @@ function walkTrackGraph(graph, pieces, startAt, library = BUILT_IN_TRACK_PARTS)
|
|
|
2519
2584
|
toPos: startPos,
|
|
2520
2585
|
bornAt,
|
|
2521
2586
|
endsAt: null,
|
|
2587
|
+
closedBy: null,
|
|
2522
2588
|
pieces: [],
|
|
2523
2589
|
spans: [],
|
|
2524
2590
|
lateral: 0
|
|
@@ -2533,7 +2599,11 @@ function walkTrackGraph(graph, pieces, startAt, library = BUILT_IN_TRACK_PARTS)
|
|
|
2533
2599
|
const geo = geoOf(here.piece);
|
|
2534
2600
|
if (!geo) break;
|
|
2535
2601
|
const opts = geo.routes.filter((r) => r.includes(here.joint));
|
|
2536
|
-
if (!opts.length)
|
|
2602
|
+
if (!opts.length) {
|
|
2603
|
+
route.pieces.push(here.piece);
|
|
2604
|
+
route.closedBy = here.piece;
|
|
2605
|
+
break;
|
|
2606
|
+
}
|
|
2537
2607
|
const pick = opts.find((r) => r.includes("through")) ?? opts[0];
|
|
2538
2608
|
const exitJoint = pick[0] === here.joint ? pick[1] : pick[0];
|
|
2539
2609
|
const exit = byKey.get(`${here.piece}.${exitJoint}`);
|
|
@@ -2691,7 +2761,10 @@ function graphToDoc(pieces, input) {
|
|
|
2691
2761
|
role: "main",
|
|
2692
2762
|
lane: 0,
|
|
2693
2763
|
from: epA?.id ?? "A",
|
|
2694
|
-
...epB ? { to: epB.id } : {}
|
|
2764
|
+
...epB ? { to: epB.id } : {},
|
|
2765
|
+
// A main can be closed too — that is a pocket module, where the track
|
|
2766
|
+
// runs in and stops rather than crossing to a second endplate.
|
|
2767
|
+
...main.closedBy ? { bumperAt: "to" } : {}
|
|
2695
2768
|
},
|
|
2696
2769
|
...twoMains ? [
|
|
2697
2770
|
{
|
|
@@ -2718,6 +2791,11 @@ function graphToDoc(pieces, input) {
|
|
|
2718
2791
|
lane: laneOf(r, uniqueBranches),
|
|
2719
2792
|
fromPos: round(Math.min(r.fromPos, r.toPos)),
|
|
2720
2793
|
toPos: round(Math.max(r.fromPos, r.toPos)),
|
|
2794
|
+
// ⚠️ ALWAYS THE `to` END, and not because of an assumption: a branch's
|
|
2795
|
+
// positions are ARC LENGTH FROM ITS THROAT, so they only ever grow — a
|
|
2796
|
+
// spur running physically back toward endplate A still ends at the larger
|
|
2797
|
+
// number. The closed end is the far one from the turnout, by definition.
|
|
2798
|
+
...r.closedBy ? { bumperAt: "to" } : {},
|
|
2721
2799
|
...meta.trackName ? { trackName: meta.trackName } : {},
|
|
2722
2800
|
...meta.capacityFeet != null ? { capacityFeet: meta.capacityFeet } : {},
|
|
2723
2801
|
...meta.moduleTrackId != null ? { moduleTrackId: meta.moduleTrackId } : {}
|
|
@@ -3477,6 +3555,7 @@ function poseOverridesFromDoc(doc) {
|
|
|
3477
3555
|
|
|
3478
3556
|
exports.ATLAS_CODE55_N = ATLAS_CODE55_N;
|
|
3479
3557
|
exports.BUILT_IN_TRACK_PARTS = BUILT_IN_TRACK_PARTS;
|
|
3558
|
+
exports.BUMPER_DRAWN_INCHES = BUMPER_DRAWN_INCHES;
|
|
3480
3559
|
exports.CLEARANCE_SPACING_INCHES = CLEARANCE_SPACING_INCHES;
|
|
3481
3560
|
exports.CODE55_RAIL_HEIGHT_INCHES = CODE55_RAIL_HEIGHT_INCHES;
|
|
3482
3561
|
exports.DEFAULT_FLEX_PART_ID = DEFAULT_FLEX_PART_ID;
|
|
@@ -3491,6 +3570,7 @@ exports.FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES = FREEMO_ENDPLATE_WIDTH_RECOMME
|
|
|
3491
3570
|
exports.FREEMO_MAIN_MIN_RADIUS_INCHES = FREEMO_MAIN_MIN_RADIUS_INCHES;
|
|
3492
3571
|
exports.FREEMO_REVERSE_CURVE_STRAIGHT_INCHES = FREEMO_REVERSE_CURVE_STRAIGHT_INCHES;
|
|
3493
3572
|
exports.FREEMO_TRACK_SPACING_INCHES = FREEMO_TRACK_SPACING_INCHES;
|
|
3573
|
+
exports.GENERIC_END_OF_TRACK = GENERIC_END_OF_TRACK;
|
|
3494
3574
|
exports.JOINT_SNAP_INCHES = JOINT_SNAP_INCHES;
|
|
3495
3575
|
exports.MAIN2_TRACK_ID = MAIN2_TRACK_ID;
|
|
3496
3576
|
exports.MAIN_TRACK_ID = MAIN_TRACK_ID;
|
|
@@ -3499,6 +3579,7 @@ exports.N_SCALE_RATIO = N_SCALE_RATIO;
|
|
|
3499
3579
|
exports.PINCH_EASE_INCHES = PINCH_EASE_INCHES;
|
|
3500
3580
|
exports.RAIL_GAUGE_INCHES = RAIL_GAUGE_INCHES;
|
|
3501
3581
|
exports.TIE_HALF_LENGTH_INCHES = TIE_HALF_LENGTH_INCHES;
|
|
3582
|
+
exports.TRACK_PART_KINDS = TRACK_PART_KINDS;
|
|
3502
3583
|
exports.TURNOUT_LEAD_INCHES_PER_FROG = TURNOUT_LEAD_INCHES_PER_FROG;
|
|
3503
3584
|
exports.WHOLE_MODULE_SECTION_ID = WHOLE_MODULE_SECTION_ID;
|
|
3504
3585
|
exports.asModuleSchematic = asModuleSchematic;
|
|
@@ -3583,6 +3664,7 @@ exports.sectionFootprints = sectionFootprints;
|
|
|
3583
3664
|
exports.sectionNeighbours = sectionNeighbours;
|
|
3584
3665
|
exports.sectionSpans = sectionSpans;
|
|
3585
3666
|
exports.sectionSpansOrWhole = sectionSpansOrWhole;
|
|
3667
|
+
exports.sectionalArcInches = sectionalArcInches;
|
|
3586
3668
|
exports.sectionedCenterline = sectionedCenterline;
|
|
3587
3669
|
exports.sectionedEndPose = sectionedEndPose;
|
|
3588
3670
|
exports.sliceCenterline = sliceCenterline;
|