@willcgage/module-schematic 0.85.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 +377 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +284 -1
- package/dist/index.d.ts +284 -1
- package/dist/index.js +371 -4
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1046,6 +1046,10 @@ function stateToDoc(state, recordNumber) {
|
|
|
1046
1046
|
track: ind.track,
|
|
1047
1047
|
fromPos: ind.fromPos,
|
|
1048
1048
|
toPos: ind.toPos,
|
|
1049
|
+
// Carried, not interpreted. An anchor is what HOLDS the span (ADR
|
|
1050
|
+
// 0001); dropping it here would quietly convert an anchored
|
|
1051
|
+
// industry back into a typed number the next time anyone saved.
|
|
1052
|
+
...ind.anchor ? { anchor: ind.anchor } : {},
|
|
1049
1053
|
...ind.spots?.length ? { spots: ind.spots } : {},
|
|
1050
1054
|
side: ind.side,
|
|
1051
1055
|
...ind.labelMode && ind.labelMode !== "none" ? { labelMode: ind.labelMode } : {},
|
|
@@ -1065,7 +1069,11 @@ function stateToDoc(state, recordNumber) {
|
|
|
1065
1069
|
...state.sections.length ? { sections: moduleSections({ sections: state.sections }) } : {},
|
|
1066
1070
|
// Authored mainline path (module-local inches); only when it's a real path.
|
|
1067
1071
|
...state.mainPath.length >= 2 ? { mainPath: state.mainPath } : {},
|
|
1068
|
-
...state.main2Path.length >= 2 ? { main2Path: state.main2Path } : {}
|
|
1072
|
+
...state.main2Path.length >= 2 ? { main2Path: state.main2Path } : {},
|
|
1073
|
+
// The pieces the owner drew, carried verbatim (ADR 0001). This function
|
|
1074
|
+
// stays dumb about them on purpose: deriving here would put the derivation
|
|
1075
|
+
// in every save path in both apps. `deriveGraphDoc` is the one place.
|
|
1076
|
+
...state.graph?.pieces?.length ? { graph: state.graph } : {}
|
|
1069
1077
|
};
|
|
1070
1078
|
}
|
|
1071
1079
|
function docToState(doc, fallbackLength, moduleTracks = []) {
|
|
@@ -1210,6 +1218,10 @@ function docToState(doc, fallbackLength, moduleTracks = []) {
|
|
|
1210
1218
|
sections: moduleSections(d),
|
|
1211
1219
|
mainPath,
|
|
1212
1220
|
main2Path,
|
|
1221
|
+
// ⚠️ NOT scaled by `sc()` like every position above it. A piece is placed in
|
|
1222
|
+
// real inches on the board; stretching the module's length does not move it,
|
|
1223
|
+
// and the length is DERIVED from the pieces anyway when a graph is present.
|
|
1224
|
+
...d.graph?.pieces?.length ? { graph: d.graph } : {},
|
|
1213
1225
|
crossings: (d.crossings ?? []).map((x) => ({
|
|
1214
1226
|
id: x.id,
|
|
1215
1227
|
name: x.name ?? "",
|
|
@@ -1244,12 +1256,14 @@ function docToState(doc, fallbackLength, moduleTracks = []) {
|
|
|
1244
1256
|
track: s.track,
|
|
1245
1257
|
fromPos: sc(s.fromPos ?? 0),
|
|
1246
1258
|
toPos: s.toPos != null ? sc(s.toPos) : len,
|
|
1247
|
-
...s.side ? { side: s.side } : {}
|
|
1259
|
+
...s.side ? { side: s.side } : {},
|
|
1260
|
+
...s.anchor ? { anchor: s.anchor } : {}
|
|
1248
1261
|
})),
|
|
1249
1262
|
side: ind.side ?? "above",
|
|
1250
1263
|
labelMode: ind.labelMode ?? "none",
|
|
1251
1264
|
carTypes: Array.isArray(ind.carTypes) ? ind.carTypes : [],
|
|
1252
|
-
moduleIndustryId: ind.moduleIndustryId ?? null
|
|
1265
|
+
moduleIndustryId: ind.moduleIndustryId ?? null,
|
|
1266
|
+
...ind.anchor ? { anchor: ind.anchor } : {}
|
|
1253
1267
|
}))
|
|
1254
1268
|
};
|
|
1255
1269
|
}
|
|
@@ -2245,6 +2259,359 @@ function partsPlaceable(library = BUILT_IN_TRACK_PARTS) {
|
|
|
2245
2259
|
}
|
|
2246
2260
|
return { placeable, blocked };
|
|
2247
2261
|
}
|
|
2262
|
+
var JOINT_SNAP_INCHES = 0.01;
|
|
2263
|
+
function placedJoints(pieces, library = BUILT_IN_TRACK_PARTS) {
|
|
2264
|
+
const out = [];
|
|
2265
|
+
const RAD = Math.PI / 180;
|
|
2266
|
+
for (const p of pieces) {
|
|
2267
|
+
const part = library.find((x) => x.id === p.partId);
|
|
2268
|
+
if (!part) continue;
|
|
2269
|
+
const geo = partGeometry(part, library);
|
|
2270
|
+
if (!geo) continue;
|
|
2271
|
+
const c = Math.cos(p.rotationDeg * RAD);
|
|
2272
|
+
const s = Math.sin(p.rotationDeg * RAD);
|
|
2273
|
+
for (const j of geo.joints) {
|
|
2274
|
+
const lx = part.kind === "flex" && j.id === "b" ? p.lengthInches ?? 0 : j.x;
|
|
2275
|
+
const ly = p.flipped ? -j.y : j.y;
|
|
2276
|
+
const h = (p.flipped ? -j.angleDeg : j.angleDeg) + p.rotationDeg;
|
|
2277
|
+
out.push({
|
|
2278
|
+
key: `${p.id}.${j.id}`,
|
|
2279
|
+
piece: p.id,
|
|
2280
|
+
joint: j.id,
|
|
2281
|
+
role: j.role,
|
|
2282
|
+
x: p.x + lx * c - ly * s,
|
|
2283
|
+
y: p.y + lx * s + ly * c,
|
|
2284
|
+
headingDeg: norm360(h)
|
|
2285
|
+
});
|
|
2286
|
+
}
|
|
2287
|
+
}
|
|
2288
|
+
return out;
|
|
2289
|
+
}
|
|
2290
|
+
function buildTrackGraph(pieces, library = BUILT_IN_TRACK_PARTS, snapInches = JOINT_SNAP_INCHES) {
|
|
2291
|
+
const joints = placedJoints(pieces, library);
|
|
2292
|
+
const unplaceable = [];
|
|
2293
|
+
for (const p of pieces) {
|
|
2294
|
+
const part = library.find((x) => x.id === p.partId);
|
|
2295
|
+
if (!part) {
|
|
2296
|
+
unplaceable.push({ piece: p.id, partId: p.partId, why: "no such part in the library" });
|
|
2297
|
+
continue;
|
|
2298
|
+
}
|
|
2299
|
+
const why = partGeometryGap(part);
|
|
2300
|
+
if (why) unplaceable.push({ piece: p.id, partId: p.partId, why });
|
|
2301
|
+
}
|
|
2302
|
+
const groups = [];
|
|
2303
|
+
const taken = /* @__PURE__ */ new Set();
|
|
2304
|
+
for (const j of joints) {
|
|
2305
|
+
if (taken.has(j.key)) continue;
|
|
2306
|
+
const g = [j];
|
|
2307
|
+
taken.add(j.key);
|
|
2308
|
+
for (const k of joints) {
|
|
2309
|
+
if (taken.has(k.key) || k.piece === j.piece) continue;
|
|
2310
|
+
if (Math.hypot(k.x - j.x, k.y - j.y) <= snapInches) {
|
|
2311
|
+
g.push(k);
|
|
2312
|
+
taken.add(k.key);
|
|
2313
|
+
}
|
|
2314
|
+
}
|
|
2315
|
+
groups.push(g);
|
|
2316
|
+
}
|
|
2317
|
+
const connections = [];
|
|
2318
|
+
const open = [];
|
|
2319
|
+
const conflicts = [];
|
|
2320
|
+
for (const g of groups) {
|
|
2321
|
+
if (g.length === 1) open.push(g[0].key);
|
|
2322
|
+
else if (g.length === 2) connections.push({ a: g[0].key, b: g[1].key });
|
|
2323
|
+
else {
|
|
2324
|
+
conflicts.push({
|
|
2325
|
+
x: g[0].x,
|
|
2326
|
+
y: g[0].y,
|
|
2327
|
+
joints: g.map((j) => j.key),
|
|
2328
|
+
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.`
|
|
2329
|
+
});
|
|
2330
|
+
for (const j of g) open.push(j.key);
|
|
2331
|
+
}
|
|
2332
|
+
}
|
|
2333
|
+
return { joints, connections, open, conflicts, unplaceable };
|
|
2334
|
+
}
|
|
2335
|
+
function walkTrackGraph(graph, pieces, startAt, library = BUILT_IN_TRACK_PARTS) {
|
|
2336
|
+
const byKey = new Map(graph.joints.map((j) => [j.key, j]));
|
|
2337
|
+
const pieceById = new Map(pieces.map((p) => [p.id, p]));
|
|
2338
|
+
const link = /* @__PURE__ */ new Map();
|
|
2339
|
+
for (const c of graph.connections) {
|
|
2340
|
+
link.set(c.a, c.b);
|
|
2341
|
+
link.set(c.b, c.a);
|
|
2342
|
+
}
|
|
2343
|
+
const partOf = (pid) => {
|
|
2344
|
+
const p = pieceById.get(pid);
|
|
2345
|
+
return p ? library.find((x) => x.id === p.partId) : void 0;
|
|
2346
|
+
};
|
|
2347
|
+
const geoOf = (pid) => {
|
|
2348
|
+
const part = partOf(pid);
|
|
2349
|
+
return part ? partGeometry(part, library) : null;
|
|
2350
|
+
};
|
|
2351
|
+
const gap = (a, b) => a && b ? Math.hypot(a.x - b.x, a.y - b.y) : 0;
|
|
2352
|
+
const routes = [];
|
|
2353
|
+
const turnouts = [];
|
|
2354
|
+
const warnings = [];
|
|
2355
|
+
const queued = /* @__PURE__ */ new Set();
|
|
2356
|
+
const pending = [];
|
|
2357
|
+
const walk = (startKey, startPos, id, bornAt) => {
|
|
2358
|
+
const route = {
|
|
2359
|
+
id,
|
|
2360
|
+
fromPos: startPos,
|
|
2361
|
+
toPos: startPos,
|
|
2362
|
+
bornAt,
|
|
2363
|
+
endsAt: null,
|
|
2364
|
+
pieces: [],
|
|
2365
|
+
spans: [],
|
|
2366
|
+
lateral: 0
|
|
2367
|
+
};
|
|
2368
|
+
let cur = startKey;
|
|
2369
|
+
let pos = startPos;
|
|
2370
|
+
const guard = /* @__PURE__ */ new Set();
|
|
2371
|
+
while (cur && !guard.has(cur)) {
|
|
2372
|
+
guard.add(cur);
|
|
2373
|
+
const here = byKey.get(cur);
|
|
2374
|
+
if (!here) break;
|
|
2375
|
+
const geo = geoOf(here.piece);
|
|
2376
|
+
if (!geo) break;
|
|
2377
|
+
const opts = geo.routes.filter((r) => r.includes(here.joint));
|
|
2378
|
+
if (!opts.length) break;
|
|
2379
|
+
const pick = opts.find((r) => r.includes("through")) ?? opts[0];
|
|
2380
|
+
const exitJoint = pick[0] === here.joint ? pick[1] : pick[0];
|
|
2381
|
+
const exit = byKey.get(`${here.piece}.${exitJoint}`);
|
|
2382
|
+
const part = partOf(here.piece);
|
|
2383
|
+
if (part && (part.kind === "turnout" || part.kind === "wye")) {
|
|
2384
|
+
const throat = byKey.get(`${here.piece}.throat`);
|
|
2385
|
+
const through = byKey.get(`${here.piece}.through`) ?? byKey.get(`${here.piece}.legA`);
|
|
2386
|
+
const body = gap(throat, through);
|
|
2387
|
+
const lead = part.lead?.inches ?? body / 2;
|
|
2388
|
+
const dvj = byKey.get(`${here.piece}.diverge`) ?? byKey.get(`${here.piece}.legB`);
|
|
2389
|
+
const frogPt = throat && body > 0 && dvj ? {
|
|
2390
|
+
x: throat.x + (dvj.x - throat.x) * lead / body,
|
|
2391
|
+
y: throat.y + (dvj.y - throat.y) * lead / body
|
|
2392
|
+
} : null;
|
|
2393
|
+
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);
|
|
2394
|
+
const frogPos = pos + toFrog;
|
|
2395
|
+
if (here.joint === "diverge" || here.joint === "legB") {
|
|
2396
|
+
route.endsAt = here.piece;
|
|
2397
|
+
const known = turnouts.find((t) => t.id === here.piece);
|
|
2398
|
+
route.toPos = known ? known.pos : frogPos;
|
|
2399
|
+
break;
|
|
2400
|
+
}
|
|
2401
|
+
turnouts.push({ id: here.piece, pos: frogPos, onRoute: id, divergeRoute: null });
|
|
2402
|
+
for (const dj of ["diverge", "legB"]) {
|
|
2403
|
+
const dk = `${here.piece}.${dj}`;
|
|
2404
|
+
const d = byKey.get(dk);
|
|
2405
|
+
if (!d || queued.has(dk)) continue;
|
|
2406
|
+
queued.add(dk);
|
|
2407
|
+
const fp = throat && body > 0 ? {
|
|
2408
|
+
x: throat.x + (d.x - throat.x) * lead / body,
|
|
2409
|
+
y: throat.y + (d.y - throat.y) * lead / body
|
|
2410
|
+
} : null;
|
|
2411
|
+
pending.push({
|
|
2412
|
+
from: here.piece,
|
|
2413
|
+
at: frogPos,
|
|
2414
|
+
joint: dk,
|
|
2415
|
+
skew: fp ? Math.hypot(d.x - fp.x, d.y - fp.y) : 0
|
|
2416
|
+
});
|
|
2417
|
+
}
|
|
2418
|
+
}
|
|
2419
|
+
route.pieces.push(here.piece);
|
|
2420
|
+
for (const j of graph.joints)
|
|
2421
|
+
if (j.piece === here.piece && Math.abs(j.y) > Math.abs(route.lateral))
|
|
2422
|
+
route.lateral = j.y;
|
|
2423
|
+
const entered = pos;
|
|
2424
|
+
pos += gap(here, exit);
|
|
2425
|
+
route.spans.push({
|
|
2426
|
+
piece: here.piece,
|
|
2427
|
+
entryJoint: here.joint,
|
|
2428
|
+
exitJoint,
|
|
2429
|
+
fromPos: entered,
|
|
2430
|
+
toPos: pos
|
|
2431
|
+
});
|
|
2432
|
+
route.toPos = pos;
|
|
2433
|
+
const next = exit ? link.get(exit.key) : void 0;
|
|
2434
|
+
if (!next) break;
|
|
2435
|
+
cur = next;
|
|
2436
|
+
}
|
|
2437
|
+
return route;
|
|
2438
|
+
};
|
|
2439
|
+
routes.push(walk(`${startAt.piece}.${startAt.joint}`, 0, "main", null));
|
|
2440
|
+
let n = 0;
|
|
2441
|
+
while (pending.length) {
|
|
2442
|
+
const b = pending.shift();
|
|
2443
|
+
const start = link.get(b.joint);
|
|
2444
|
+
if (!start) {
|
|
2445
|
+
warnings.push(`the route diverging at ${b.from} is not connected to anything`);
|
|
2446
|
+
continue;
|
|
2447
|
+
}
|
|
2448
|
+
const already = routes.find((r2) => r2.pieces.includes(byKey.get(start).piece));
|
|
2449
|
+
if (already) {
|
|
2450
|
+
const swFar = turnouts.find((t) => t.id === b.from);
|
|
2451
|
+
if (swFar && !swFar.divergeRoute) swFar.divergeRoute = already.id;
|
|
2452
|
+
continue;
|
|
2453
|
+
}
|
|
2454
|
+
n += 1;
|
|
2455
|
+
const r = walk(start, b.at + b.skew, `route${n}`, b.from);
|
|
2456
|
+
r.fromPos = b.at;
|
|
2457
|
+
routes.push(r);
|
|
2458
|
+
const sw = turnouts.find((t) => t.id === b.from);
|
|
2459
|
+
if (sw && !sw.divergeRoute) sw.divergeRoute = r.id;
|
|
2460
|
+
}
|
|
2461
|
+
const reached = new Set(routes.flatMap((r) => r.pieces));
|
|
2462
|
+
for (const p of pieces)
|
|
2463
|
+
if (!reached.has(p.id) && !graph.unplaceable.some((u) => u.piece === p.id))
|
|
2464
|
+
warnings.push(`${p.id} is not reachable from the endplate \u2014 nothing connects it`);
|
|
2465
|
+
for (const c of graph.conflicts) warnings.push(c.reason);
|
|
2466
|
+
return { routes, turnouts, warnings };
|
|
2467
|
+
}
|
|
2468
|
+
function resolveGraphAnchor(anchor, walk, pieces, library = BUILT_IN_TRACK_PARTS) {
|
|
2469
|
+
const piece = pieces.find((p) => p.id === anchor.piece);
|
|
2470
|
+
const part = piece ? library.find((x) => x.id === piece.partId) : void 0;
|
|
2471
|
+
const origin = part?.kind === "flex" ? "a" : "throat";
|
|
2472
|
+
for (const r of walk.routes) {
|
|
2473
|
+
const span = r.spans.find((s) => s.piece === anchor.piece);
|
|
2474
|
+
if (!span) continue;
|
|
2475
|
+
if (span.entryJoint === origin)
|
|
2476
|
+
return { pos: span.fromPos + anchor.atInches, routeId: r.id, reversed: false };
|
|
2477
|
+
if (span.exitJoint === origin)
|
|
2478
|
+
return { pos: span.toPos - anchor.atInches, routeId: r.id, reversed: true };
|
|
2479
|
+
return null;
|
|
2480
|
+
}
|
|
2481
|
+
return null;
|
|
2482
|
+
}
|
|
2483
|
+
function graphToDoc(pieces, input) {
|
|
2484
|
+
const library = input.library ?? BUILT_IN_TRACK_PARTS;
|
|
2485
|
+
const graph = buildTrackGraph(pieces, library);
|
|
2486
|
+
const walk = walkTrackGraph(graph, pieces, input.startAt, library);
|
|
2487
|
+
const warnings = [...walk.warnings];
|
|
2488
|
+
const round = (n) => Math.round(n * 100) / 100;
|
|
2489
|
+
const base = input.base ?? {};
|
|
2490
|
+
const endplates = base.endplates && base.endplates.length ? base.endplates : [{ id: "A", label: "West" }, { id: "B", label: "East" }];
|
|
2491
|
+
const epA = endplates[0];
|
|
2492
|
+
const epB = endplates[1];
|
|
2493
|
+
const main = walk.routes.find((r) => r.id === "main");
|
|
2494
|
+
const lengthInches = round(main.toPos);
|
|
2495
|
+
const branches = walk.routes.filter((r) => r.id !== "main" && r.pieces.length);
|
|
2496
|
+
const trackIdOf = /* @__PURE__ */ new Map([["main", MAIN_TRACK_ID]]);
|
|
2497
|
+
for (const r of branches) trackIdOf.set(r.id, r.pieces[0]);
|
|
2498
|
+
const laneOf = (r) => {
|
|
2499
|
+
const side = Math.sign(r.lateral) || 1;
|
|
2500
|
+
const sameSide = branches.filter((x) => (Math.sign(x.lateral) || 1) === side).sort((a, b) => Math.abs(a.lateral) - Math.abs(b.lateral));
|
|
2501
|
+
return side * (sameSide.indexOf(r) + 1);
|
|
2502
|
+
};
|
|
2503
|
+
const tracks = [
|
|
2504
|
+
{
|
|
2505
|
+
id: MAIN_TRACK_ID,
|
|
2506
|
+
role: "main",
|
|
2507
|
+
lane: 0,
|
|
2508
|
+
from: epA?.id ?? "A",
|
|
2509
|
+
...epB ? { to: epB.id } : {}
|
|
2510
|
+
}
|
|
2511
|
+
];
|
|
2512
|
+
for (const r of branches) {
|
|
2513
|
+
const id = trackIdOf.get(r.id);
|
|
2514
|
+
const meta = input.meta?.[id] ?? {};
|
|
2515
|
+
tracks.push({
|
|
2516
|
+
id,
|
|
2517
|
+
// It runs back into a second turnout, or it doesn't. That is the whole
|
|
2518
|
+
// difference between a siding and a spur, and it is read, not declared.
|
|
2519
|
+
role: r.endsAt ? "siding" : "spur",
|
|
2520
|
+
lane: laneOf(r),
|
|
2521
|
+
fromPos: round(Math.min(r.fromPos, r.toPos)),
|
|
2522
|
+
toPos: round(Math.max(r.fromPos, r.toPos)),
|
|
2523
|
+
...meta.trackName ? { trackName: meta.trackName } : {},
|
|
2524
|
+
...meta.capacityFeet != null ? { capacityFeet: meta.capacityFeet } : {},
|
|
2525
|
+
...meta.moduleTrackId != null ? { moduleTrackId: meta.moduleTrackId } : {}
|
|
2526
|
+
});
|
|
2527
|
+
}
|
|
2528
|
+
const pieceById = new Map(pieces.map((p) => [p.id, p]));
|
|
2529
|
+
const turnouts = [];
|
|
2530
|
+
for (const t of walk.turnouts) {
|
|
2531
|
+
const diverge = t.divergeRoute ? trackIdOf.get(t.divergeRoute) : void 0;
|
|
2532
|
+
if (!diverge) {
|
|
2533
|
+
warnings.push(
|
|
2534
|
+
`${t.id} is placed but its diverging route goes nowhere, so it is not in the operations view`
|
|
2535
|
+
);
|
|
2536
|
+
continue;
|
|
2537
|
+
}
|
|
2538
|
+
const piece = pieceById.get(t.id);
|
|
2539
|
+
const part = piece ? library.find((p) => p.id === piece.partId) : void 0;
|
|
2540
|
+
turnouts.push({
|
|
2541
|
+
id: t.id,
|
|
2542
|
+
pos: round(t.pos),
|
|
2543
|
+
onTrack: trackIdOf.get(t.onRoute) ?? MAIN_TRACK_ID,
|
|
2544
|
+
divergeTrack: diverge,
|
|
2545
|
+
...piece?.name ? { name: piece.name } : {},
|
|
2546
|
+
...part?.frogNumber != null ? { size: part.frogNumber } : {},
|
|
2547
|
+
...part ? { partId: part.id } : {}
|
|
2548
|
+
});
|
|
2549
|
+
}
|
|
2550
|
+
turnouts.sort((a, b) => a.pos - b.pos);
|
|
2551
|
+
const place = (a, what) => {
|
|
2552
|
+
if (!a) return null;
|
|
2553
|
+
const hit = resolveGraphAnchor(a, walk, pieces, library);
|
|
2554
|
+
if (!hit) {
|
|
2555
|
+
warnings.push(
|
|
2556
|
+
`${what} is anchored to ${a.piece}, which is not on any route \u2014 its authored position stands`
|
|
2557
|
+
);
|
|
2558
|
+
return null;
|
|
2559
|
+
}
|
|
2560
|
+
return hit;
|
|
2561
|
+
};
|
|
2562
|
+
const spanAt = (hit, from, to) => {
|
|
2563
|
+
const len = Math.abs(to - from);
|
|
2564
|
+
return hit.reversed ? [round(hit.pos - len), round(hit.pos)] : [round(hit.pos), round(hit.pos + len)];
|
|
2565
|
+
};
|
|
2566
|
+
const industries = base.industries?.map((ind) => {
|
|
2567
|
+
const hit = place(ind.anchor, `industry "${ind.name}"`);
|
|
2568
|
+
const spots = ind.spots?.map((s) => {
|
|
2569
|
+
const sh = place(s.anchor, `a spot of industry "${ind.name}"`);
|
|
2570
|
+
if (!sh) return s;
|
|
2571
|
+
const [from, to] = spanAt(sh, s.fromPos, s.toPos);
|
|
2572
|
+
return { ...s, track: trackIdOf.get(sh.routeId) ?? s.track, fromPos: from, toPos: to };
|
|
2573
|
+
});
|
|
2574
|
+
if (!hit) return spots ? { ...ind, spots } : ind;
|
|
2575
|
+
const [fromPos, toPos] = spanAt(hit, ind.fromPos, ind.toPos);
|
|
2576
|
+
return {
|
|
2577
|
+
...ind,
|
|
2578
|
+
track: trackIdOf.get(hit.routeId) ?? ind.track,
|
|
2579
|
+
fromPos,
|
|
2580
|
+
toPos,
|
|
2581
|
+
...spots ? { spots } : {}
|
|
2582
|
+
};
|
|
2583
|
+
});
|
|
2584
|
+
const signals = base.signals?.map((sig) => {
|
|
2585
|
+
const hit = place(sig.anchor, `signal "${sig.name ?? sig.id}"`);
|
|
2586
|
+
return hit ? { ...sig, pos: round(hit.pos), track: trackIdOf.get(hit.routeId) ?? sig.track } : sig;
|
|
2587
|
+
});
|
|
2588
|
+
const doc = {
|
|
2589
|
+
version: 1,
|
|
2590
|
+
...base,
|
|
2591
|
+
lengthInches,
|
|
2592
|
+
endplates,
|
|
2593
|
+
tracks,
|
|
2594
|
+
turnouts,
|
|
2595
|
+
...industries ? { industries } : {},
|
|
2596
|
+
...signals ? { signals } : {}
|
|
2597
|
+
};
|
|
2598
|
+
return { doc, graph, walk, warnings };
|
|
2599
|
+
}
|
|
2600
|
+
function deriveGraphDoc(doc, library = BUILT_IN_TRACK_PARTS) {
|
|
2601
|
+
const g = doc.graph;
|
|
2602
|
+
if (!g || !g.pieces?.length || !g.startAt) return { doc, warnings: [] };
|
|
2603
|
+
const meta = {};
|
|
2604
|
+
for (const t of doc.tracks ?? []) {
|
|
2605
|
+
if (t.role === "main") continue;
|
|
2606
|
+
meta[t.id] = {
|
|
2607
|
+
...t.trackName ? { trackName: t.trackName } : {},
|
|
2608
|
+
...t.capacityFeet != null ? { capacityFeet: t.capacityFeet } : {},
|
|
2609
|
+
...t.moduleTrackId != null ? { moduleTrackId: t.moduleTrackId } : {}
|
|
2610
|
+
};
|
|
2611
|
+
}
|
|
2612
|
+
const out = graphToDoc(g.pieces, { startAt: g.startAt, base: doc, meta, library });
|
|
2613
|
+
return { doc: out.doc, warnings: out.warnings };
|
|
2614
|
+
}
|
|
2248
2615
|
function frogCasting(cl, opts = {}) {
|
|
2249
2616
|
const g = opts.gaugeInches ?? RAIL_GAUGE_INCHES;
|
|
2250
2617
|
const w = opts.reachInches ?? g * 1.5;
|
|
@@ -2892,6 +3259,7 @@ exports.FREEMO_ENDPLATE_TRACK_FASCIA_CLEARANCE_INCHES = FREEMO_ENDPLATE_TRACK_FA
|
|
|
2892
3259
|
exports.FREEMO_ENDPLATE_WIDTH_MIN_INCHES = FREEMO_ENDPLATE_WIDTH_MIN_INCHES;
|
|
2893
3260
|
exports.FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES = FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES;
|
|
2894
3261
|
exports.FREEMO_TRACK_SPACING_INCHES = FREEMO_TRACK_SPACING_INCHES;
|
|
3262
|
+
exports.JOINT_SNAP_INCHES = JOINT_SNAP_INCHES;
|
|
2895
3263
|
exports.MAIN2_TRACK_ID = MAIN2_TRACK_ID;
|
|
2896
3264
|
exports.MAIN_TRACK_ID = MAIN_TRACK_ID;
|
|
2897
3265
|
exports.N_CAR_LENGTH_INCHES = N_CAR_LENGTH_INCHES;
|
|
@@ -2908,12 +3276,14 @@ exports.benchworkBand = benchworkBand;
|
|
|
2908
3276
|
exports.benchworkOutline = benchworkOutline;
|
|
2909
3277
|
exports.buildCrossover = buildCrossover;
|
|
2910
3278
|
exports.buildPassingSiding = buildPassingSiding;
|
|
3279
|
+
exports.buildTrackGraph = buildTrackGraph;
|
|
2911
3280
|
exports.buildTransition = buildTransition;
|
|
2912
3281
|
exports.carCapacity = carCapacity;
|
|
2913
3282
|
exports.checkEndplateWidth = checkEndplateWidth;
|
|
2914
3283
|
exports.clearancePointPastFrogInches = clearancePointPastFrogInches;
|
|
2915
3284
|
exports.crossoverPinches = crossoverPinches;
|
|
2916
3285
|
exports.deriveEndplatePoses = deriveEndplatePoses;
|
|
3286
|
+
exports.deriveGraphDoc = deriveGraphDoc;
|
|
2917
3287
|
exports.divergeSideForHand = divergeSideForHand;
|
|
2918
3288
|
exports.docToState = docToState;
|
|
2919
3289
|
exports.emptyEditorState = emptyEditorState;
|
|
@@ -2931,6 +3301,7 @@ exports.frogCasting = frogCasting;
|
|
|
2931
3301
|
exports.frogNumberFromName = frogNumberFromName;
|
|
2932
3302
|
exports.fromSectionRelative = fromSectionRelative;
|
|
2933
3303
|
exports.geometryTurnDegrees = geometryTurnDegrees;
|
|
3304
|
+
exports.graphToDoc = graphToDoc;
|
|
2934
3305
|
exports.hasNoFarEndplate = hasNoFarEndplate;
|
|
2935
3306
|
exports.importedPartToTrackPart = importedPartToTrackPart;
|
|
2936
3307
|
exports.inchesToScaleFeet = inchesToScaleFeet;
|
|
@@ -2956,10 +3327,12 @@ exports.partOutlineAtFrog = partOutlineAtFrog;
|
|
|
2956
3327
|
exports.partsPlaceable = partsPlaceable;
|
|
2957
3328
|
exports.pastFrogInchesForSize = pastFrogInchesForSize;
|
|
2958
3329
|
exports.pathLengthInches = pathLengthInches;
|
|
3330
|
+
exports.placedJoints = placedJoints;
|
|
2959
3331
|
exports.poseNeedsManual = poseNeedsManual;
|
|
2960
3332
|
exports.poseOverridesFromDoc = poseOverridesFromDoc;
|
|
2961
3333
|
exports.remapPos = remapPos;
|
|
2962
3334
|
exports.resizeFlexPiece = resizeFlexPiece;
|
|
3335
|
+
exports.resolveGraphAnchor = resolveGraphAnchor;
|
|
2963
3336
|
exports.returnLoop = returnLoop;
|
|
2964
3337
|
exports.sampleBenchworkOutline = sampleBenchworkOutline;
|
|
2965
3338
|
exports.samplePartSegments = samplePartSegments;
|
|
@@ -2988,5 +3361,6 @@ exports.turnoutFacing = turnoutFacing;
|
|
|
2988
3361
|
exports.turnoutOccupiedSpan = turnoutOccupiedSpan;
|
|
2989
3362
|
exports.turnoutPartForSize = turnoutPartForSize;
|
|
2990
3363
|
exports.usableCapacity = usableCapacity;
|
|
3364
|
+
exports.walkTrackGraph = walkTrackGraph;
|
|
2991
3365
|
//# sourceMappingURL=index.cjs.map
|
|
2992
3366
|
//# sourceMappingURL=index.cjs.map
|