@willcgage/module-schematic 0.86.0 → 0.89.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 +182 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +165 -1
- package/dist/index.d.ts +165 -1
- package/dist/index.js +180 -4
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1044,6 +1044,10 @@ function stateToDoc(state, recordNumber) {
|
|
|
1044
1044
|
track: ind.track,
|
|
1045
1045
|
fromPos: ind.fromPos,
|
|
1046
1046
|
toPos: ind.toPos,
|
|
1047
|
+
// Carried, not interpreted. An anchor is what HOLDS the span (ADR
|
|
1048
|
+
// 0001); dropping it here would quietly convert an anchored
|
|
1049
|
+
// industry back into a typed number the next time anyone saved.
|
|
1050
|
+
...ind.anchor ? { anchor: ind.anchor } : {},
|
|
1047
1051
|
...ind.spots?.length ? { spots: ind.spots } : {},
|
|
1048
1052
|
side: ind.side,
|
|
1049
1053
|
...ind.labelMode && ind.labelMode !== "none" ? { labelMode: ind.labelMode } : {},
|
|
@@ -1063,7 +1067,11 @@ function stateToDoc(state, recordNumber) {
|
|
|
1063
1067
|
...state.sections.length ? { sections: moduleSections({ sections: state.sections }) } : {},
|
|
1064
1068
|
// Authored mainline path (module-local inches); only when it's a real path.
|
|
1065
1069
|
...state.mainPath.length >= 2 ? { mainPath: state.mainPath } : {},
|
|
1066
|
-
...state.main2Path.length >= 2 ? { main2Path: state.main2Path } : {}
|
|
1070
|
+
...state.main2Path.length >= 2 ? { main2Path: state.main2Path } : {},
|
|
1071
|
+
// The pieces the owner drew, carried verbatim (ADR 0001). This function
|
|
1072
|
+
// stays dumb about them on purpose: deriving here would put the derivation
|
|
1073
|
+
// in every save path in both apps. `deriveGraphDoc` is the one place.
|
|
1074
|
+
...state.graph?.pieces?.length ? { graph: state.graph } : {}
|
|
1067
1075
|
};
|
|
1068
1076
|
}
|
|
1069
1077
|
function docToState(doc, fallbackLength, moduleTracks = []) {
|
|
@@ -1208,6 +1216,10 @@ function docToState(doc, fallbackLength, moduleTracks = []) {
|
|
|
1208
1216
|
sections: moduleSections(d),
|
|
1209
1217
|
mainPath,
|
|
1210
1218
|
main2Path,
|
|
1219
|
+
// ⚠️ NOT scaled by `sc()` like every position above it. A piece is placed in
|
|
1220
|
+
// real inches on the board; stretching the module's length does not move it,
|
|
1221
|
+
// and the length is DERIVED from the pieces anyway when a graph is present.
|
|
1222
|
+
...d.graph?.pieces?.length ? { graph: d.graph } : {},
|
|
1211
1223
|
crossings: (d.crossings ?? []).map((x) => ({
|
|
1212
1224
|
id: x.id,
|
|
1213
1225
|
name: x.name ?? "",
|
|
@@ -1242,12 +1254,14 @@ function docToState(doc, fallbackLength, moduleTracks = []) {
|
|
|
1242
1254
|
track: s.track,
|
|
1243
1255
|
fromPos: sc(s.fromPos ?? 0),
|
|
1244
1256
|
toPos: s.toPos != null ? sc(s.toPos) : len,
|
|
1245
|
-
...s.side ? { side: s.side } : {}
|
|
1257
|
+
...s.side ? { side: s.side } : {},
|
|
1258
|
+
...s.anchor ? { anchor: s.anchor } : {}
|
|
1246
1259
|
})),
|
|
1247
1260
|
side: ind.side ?? "above",
|
|
1248
1261
|
labelMode: ind.labelMode ?? "none",
|
|
1249
1262
|
carTypes: Array.isArray(ind.carTypes) ? ind.carTypes : [],
|
|
1250
|
-
moduleIndustryId: ind.moduleIndustryId ?? null
|
|
1263
|
+
moduleIndustryId: ind.moduleIndustryId ?? null,
|
|
1264
|
+
...ind.anchor ? { anchor: ind.anchor } : {}
|
|
1251
1265
|
}))
|
|
1252
1266
|
};
|
|
1253
1267
|
}
|
|
@@ -2346,6 +2360,7 @@ function walkTrackGraph(graph, pieces, startAt, library = BUILT_IN_TRACK_PARTS)
|
|
|
2346
2360
|
bornAt,
|
|
2347
2361
|
endsAt: null,
|
|
2348
2362
|
pieces: [],
|
|
2363
|
+
spans: [],
|
|
2349
2364
|
lateral: 0
|
|
2350
2365
|
};
|
|
2351
2366
|
let cur = startKey;
|
|
@@ -2403,7 +2418,15 @@ function walkTrackGraph(graph, pieces, startAt, library = BUILT_IN_TRACK_PARTS)
|
|
|
2403
2418
|
for (const j of graph.joints)
|
|
2404
2419
|
if (j.piece === here.piece && Math.abs(j.y) > Math.abs(route.lateral))
|
|
2405
2420
|
route.lateral = j.y;
|
|
2421
|
+
const entered = pos;
|
|
2406
2422
|
pos += gap(here, exit);
|
|
2423
|
+
route.spans.push({
|
|
2424
|
+
piece: here.piece,
|
|
2425
|
+
entryJoint: here.joint,
|
|
2426
|
+
exitJoint,
|
|
2427
|
+
fromPos: entered,
|
|
2428
|
+
toPos: pos
|
|
2429
|
+
});
|
|
2407
2430
|
route.toPos = pos;
|
|
2408
2431
|
const next = exit ? link.get(exit.key) : void 0;
|
|
2409
2432
|
if (!next) break;
|
|
@@ -2420,6 +2443,12 @@ function walkTrackGraph(graph, pieces, startAt, library = BUILT_IN_TRACK_PARTS)
|
|
|
2420
2443
|
warnings.push(`the route diverging at ${b.from} is not connected to anything`);
|
|
2421
2444
|
continue;
|
|
2422
2445
|
}
|
|
2446
|
+
const already = routes.find((r2) => r2.pieces.includes(byKey.get(start).piece));
|
|
2447
|
+
if (already) {
|
|
2448
|
+
const swFar = turnouts.find((t) => t.id === b.from);
|
|
2449
|
+
if (swFar && !swFar.divergeRoute) swFar.divergeRoute = already.id;
|
|
2450
|
+
continue;
|
|
2451
|
+
}
|
|
2423
2452
|
n += 1;
|
|
2424
2453
|
const r = walk(start, b.at + b.skew, `route${n}`, b.from);
|
|
2425
2454
|
r.fromPos = b.at;
|
|
@@ -2434,6 +2463,153 @@ function walkTrackGraph(graph, pieces, startAt, library = BUILT_IN_TRACK_PARTS)
|
|
|
2434
2463
|
for (const c of graph.conflicts) warnings.push(c.reason);
|
|
2435
2464
|
return { routes, turnouts, warnings };
|
|
2436
2465
|
}
|
|
2466
|
+
function resolveGraphAnchor(anchor, walk, pieces, library = BUILT_IN_TRACK_PARTS) {
|
|
2467
|
+
const piece = pieces.find((p) => p.id === anchor.piece);
|
|
2468
|
+
const part = piece ? library.find((x) => x.id === piece.partId) : void 0;
|
|
2469
|
+
const origin = part?.kind === "flex" ? "a" : "throat";
|
|
2470
|
+
for (const r of walk.routes) {
|
|
2471
|
+
const span = r.spans.find((s) => s.piece === anchor.piece);
|
|
2472
|
+
if (!span) continue;
|
|
2473
|
+
if (span.entryJoint === origin)
|
|
2474
|
+
return { pos: span.fromPos + anchor.atInches, routeId: r.id, reversed: false };
|
|
2475
|
+
if (span.exitJoint === origin)
|
|
2476
|
+
return { pos: span.toPos - anchor.atInches, routeId: r.id, reversed: true };
|
|
2477
|
+
return null;
|
|
2478
|
+
}
|
|
2479
|
+
return null;
|
|
2480
|
+
}
|
|
2481
|
+
function graphToDoc(pieces, input) {
|
|
2482
|
+
const library = input.library ?? BUILT_IN_TRACK_PARTS;
|
|
2483
|
+
const graph = buildTrackGraph(pieces, library);
|
|
2484
|
+
const walk = walkTrackGraph(graph, pieces, input.startAt, library);
|
|
2485
|
+
const warnings = [...walk.warnings];
|
|
2486
|
+
const round = (n) => Math.round(n * 100) / 100;
|
|
2487
|
+
const base = input.base ?? {};
|
|
2488
|
+
const endplates = base.endplates && base.endplates.length ? base.endplates : [{ id: "A", label: "West" }, { id: "B", label: "East" }];
|
|
2489
|
+
const epA = endplates[0];
|
|
2490
|
+
const epB = endplates[1];
|
|
2491
|
+
const main = walk.routes.find((r) => r.id === "main");
|
|
2492
|
+
const lengthInches = round(main.toPos);
|
|
2493
|
+
const branches = walk.routes.filter((r) => r.id !== "main" && r.pieces.length);
|
|
2494
|
+
const trackIdOf = /* @__PURE__ */ new Map([["main", MAIN_TRACK_ID]]);
|
|
2495
|
+
for (const r of branches) trackIdOf.set(r.id, r.pieces[0]);
|
|
2496
|
+
const laneOf = (r) => {
|
|
2497
|
+
const side = Math.sign(r.lateral) || 1;
|
|
2498
|
+
const sameSide = branches.filter((x) => (Math.sign(x.lateral) || 1) === side).sort((a, b) => Math.abs(a.lateral) - Math.abs(b.lateral));
|
|
2499
|
+
return side * (sameSide.indexOf(r) + 1);
|
|
2500
|
+
};
|
|
2501
|
+
const tracks = [
|
|
2502
|
+
{
|
|
2503
|
+
id: MAIN_TRACK_ID,
|
|
2504
|
+
role: "main",
|
|
2505
|
+
lane: 0,
|
|
2506
|
+
from: epA?.id ?? "A",
|
|
2507
|
+
...epB ? { to: epB.id } : {}
|
|
2508
|
+
}
|
|
2509
|
+
];
|
|
2510
|
+
for (const r of branches) {
|
|
2511
|
+
const id = trackIdOf.get(r.id);
|
|
2512
|
+
const meta = input.meta?.[id] ?? {};
|
|
2513
|
+
tracks.push({
|
|
2514
|
+
id,
|
|
2515
|
+
// It runs back into a second turnout, or it doesn't. That is the whole
|
|
2516
|
+
// difference between a siding and a spur, and it is read, not declared.
|
|
2517
|
+
role: r.endsAt ? "siding" : "spur",
|
|
2518
|
+
lane: laneOf(r),
|
|
2519
|
+
fromPos: round(Math.min(r.fromPos, r.toPos)),
|
|
2520
|
+
toPos: round(Math.max(r.fromPos, r.toPos)),
|
|
2521
|
+
...meta.trackName ? { trackName: meta.trackName } : {},
|
|
2522
|
+
...meta.capacityFeet != null ? { capacityFeet: meta.capacityFeet } : {},
|
|
2523
|
+
...meta.moduleTrackId != null ? { moduleTrackId: meta.moduleTrackId } : {}
|
|
2524
|
+
});
|
|
2525
|
+
}
|
|
2526
|
+
const pieceById = new Map(pieces.map((p) => [p.id, p]));
|
|
2527
|
+
const turnouts = [];
|
|
2528
|
+
for (const t of walk.turnouts) {
|
|
2529
|
+
const diverge = t.divergeRoute ? trackIdOf.get(t.divergeRoute) : void 0;
|
|
2530
|
+
if (!diverge) {
|
|
2531
|
+
warnings.push(
|
|
2532
|
+
`${t.id} is placed but its diverging route goes nowhere, so it is not in the operations view`
|
|
2533
|
+
);
|
|
2534
|
+
continue;
|
|
2535
|
+
}
|
|
2536
|
+
const piece = pieceById.get(t.id);
|
|
2537
|
+
const part = piece ? library.find((p) => p.id === piece.partId) : void 0;
|
|
2538
|
+
turnouts.push({
|
|
2539
|
+
id: t.id,
|
|
2540
|
+
pos: round(t.pos),
|
|
2541
|
+
onTrack: trackIdOf.get(t.onRoute) ?? MAIN_TRACK_ID,
|
|
2542
|
+
divergeTrack: diverge,
|
|
2543
|
+
...piece?.name ? { name: piece.name } : {},
|
|
2544
|
+
...part?.frogNumber != null ? { size: part.frogNumber } : {},
|
|
2545
|
+
...part ? { partId: part.id } : {}
|
|
2546
|
+
});
|
|
2547
|
+
}
|
|
2548
|
+
turnouts.sort((a, b) => a.pos - b.pos);
|
|
2549
|
+
const place = (a, what) => {
|
|
2550
|
+
if (!a) return null;
|
|
2551
|
+
const hit = resolveGraphAnchor(a, walk, pieces, library);
|
|
2552
|
+
if (!hit) {
|
|
2553
|
+
warnings.push(
|
|
2554
|
+
`${what} is anchored to ${a.piece}, which is not on any route \u2014 its authored position stands`
|
|
2555
|
+
);
|
|
2556
|
+
return null;
|
|
2557
|
+
}
|
|
2558
|
+
return hit;
|
|
2559
|
+
};
|
|
2560
|
+
const spanAt = (hit, from, to) => {
|
|
2561
|
+
const len = Math.abs(to - from);
|
|
2562
|
+
return hit.reversed ? [round(hit.pos - len), round(hit.pos)] : [round(hit.pos), round(hit.pos + len)];
|
|
2563
|
+
};
|
|
2564
|
+
const industries = base.industries?.map((ind) => {
|
|
2565
|
+
const hit = place(ind.anchor, `industry "${ind.name}"`);
|
|
2566
|
+
const spots = ind.spots?.map((s) => {
|
|
2567
|
+
const sh = place(s.anchor, `a spot of industry "${ind.name}"`);
|
|
2568
|
+
if (!sh) return s;
|
|
2569
|
+
const [from, to] = spanAt(sh, s.fromPos, s.toPos);
|
|
2570
|
+
return { ...s, track: trackIdOf.get(sh.routeId) ?? s.track, fromPos: from, toPos: to };
|
|
2571
|
+
});
|
|
2572
|
+
if (!hit) return spots ? { ...ind, spots } : ind;
|
|
2573
|
+
const [fromPos, toPos] = spanAt(hit, ind.fromPos, ind.toPos);
|
|
2574
|
+
return {
|
|
2575
|
+
...ind,
|
|
2576
|
+
track: trackIdOf.get(hit.routeId) ?? ind.track,
|
|
2577
|
+
fromPos,
|
|
2578
|
+
toPos,
|
|
2579
|
+
...spots ? { spots } : {}
|
|
2580
|
+
};
|
|
2581
|
+
});
|
|
2582
|
+
const signals = base.signals?.map((sig) => {
|
|
2583
|
+
const hit = place(sig.anchor, `signal "${sig.name ?? sig.id}"`);
|
|
2584
|
+
return hit ? { ...sig, pos: round(hit.pos), track: trackIdOf.get(hit.routeId) ?? sig.track } : sig;
|
|
2585
|
+
});
|
|
2586
|
+
const doc = {
|
|
2587
|
+
version: 1,
|
|
2588
|
+
...base,
|
|
2589
|
+
lengthInches,
|
|
2590
|
+
endplates,
|
|
2591
|
+
tracks,
|
|
2592
|
+
turnouts,
|
|
2593
|
+
...industries ? { industries } : {},
|
|
2594
|
+
...signals ? { signals } : {}
|
|
2595
|
+
};
|
|
2596
|
+
return { doc, graph, walk, warnings };
|
|
2597
|
+
}
|
|
2598
|
+
function deriveGraphDoc(doc, library = BUILT_IN_TRACK_PARTS) {
|
|
2599
|
+
const g = doc.graph;
|
|
2600
|
+
if (!g || !g.pieces?.length || !g.startAt) return { doc, warnings: [] };
|
|
2601
|
+
const meta = {};
|
|
2602
|
+
for (const t of doc.tracks ?? []) {
|
|
2603
|
+
if (t.role === "main") continue;
|
|
2604
|
+
meta[t.id] = {
|
|
2605
|
+
...t.trackName ? { trackName: t.trackName } : {},
|
|
2606
|
+
...t.capacityFeet != null ? { capacityFeet: t.capacityFeet } : {},
|
|
2607
|
+
...t.moduleTrackId != null ? { moduleTrackId: t.moduleTrackId } : {}
|
|
2608
|
+
};
|
|
2609
|
+
}
|
|
2610
|
+
const out = graphToDoc(g.pieces, { startAt: g.startAt, base: doc, meta, library });
|
|
2611
|
+
return { doc: out.doc, warnings: out.warnings };
|
|
2612
|
+
}
|
|
2437
2613
|
function frogCasting(cl, opts = {}) {
|
|
2438
2614
|
const g = opts.gaugeInches ?? RAIL_GAUGE_INCHES;
|
|
2439
2615
|
const w = opts.reachInches ?? g * 1.5;
|
|
@@ -3067,6 +3243,6 @@ function poseOverridesFromDoc(doc) {
|
|
|
3067
3243
|
return out;
|
|
3068
3244
|
}
|
|
3069
3245
|
|
|
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 };
|
|
3246
|
+
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, deriveGraphDoc, divergeSideForHand, docToState, emptyEditorState, endplateCentreOffsetInches, endplateEdgePose, endplateFaceSegments, endplateLead, endplateTrackOffsetInches, endplateWidthInches, flexPartFor, flexParts, flexPieces, flexUsage, frogCasting, frogNumberFromName, fromSectionRelative, geometryTurnDegrees, graphToDoc, 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, resolveGraphAnchor, 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 };
|
|
3071
3247
|
//# sourceMappingURL=index.js.map
|
|
3072
3248
|
//# sourceMappingURL=index.js.map
|