@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.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
  }
@@ -2243,6 +2257,359 @@ function partsPlaceable(library = BUILT_IN_TRACK_PARTS) {
2243
2257
  }
2244
2258
  return { placeable, blocked };
2245
2259
  }
2260
+ var JOINT_SNAP_INCHES = 0.01;
2261
+ function placedJoints(pieces, library = BUILT_IN_TRACK_PARTS) {
2262
+ const out = [];
2263
+ const RAD = Math.PI / 180;
2264
+ for (const p of pieces) {
2265
+ const part = library.find((x) => x.id === p.partId);
2266
+ if (!part) continue;
2267
+ const geo = partGeometry(part, library);
2268
+ if (!geo) continue;
2269
+ const c = Math.cos(p.rotationDeg * RAD);
2270
+ const s = Math.sin(p.rotationDeg * RAD);
2271
+ for (const j of geo.joints) {
2272
+ const lx = part.kind === "flex" && j.id === "b" ? p.lengthInches ?? 0 : j.x;
2273
+ const ly = p.flipped ? -j.y : j.y;
2274
+ const h = (p.flipped ? -j.angleDeg : j.angleDeg) + p.rotationDeg;
2275
+ out.push({
2276
+ key: `${p.id}.${j.id}`,
2277
+ piece: p.id,
2278
+ joint: j.id,
2279
+ role: j.role,
2280
+ x: p.x + lx * c - ly * s,
2281
+ y: p.y + lx * s + ly * c,
2282
+ headingDeg: norm360(h)
2283
+ });
2284
+ }
2285
+ }
2286
+ return out;
2287
+ }
2288
+ function buildTrackGraph(pieces, library = BUILT_IN_TRACK_PARTS, snapInches = JOINT_SNAP_INCHES) {
2289
+ const joints = placedJoints(pieces, library);
2290
+ const unplaceable = [];
2291
+ for (const p of pieces) {
2292
+ const part = library.find((x) => x.id === p.partId);
2293
+ if (!part) {
2294
+ unplaceable.push({ piece: p.id, partId: p.partId, why: "no such part in the library" });
2295
+ continue;
2296
+ }
2297
+ const why = partGeometryGap(part);
2298
+ if (why) unplaceable.push({ piece: p.id, partId: p.partId, why });
2299
+ }
2300
+ const groups = [];
2301
+ const taken = /* @__PURE__ */ new Set();
2302
+ for (const j of joints) {
2303
+ if (taken.has(j.key)) continue;
2304
+ const g = [j];
2305
+ taken.add(j.key);
2306
+ for (const k of joints) {
2307
+ if (taken.has(k.key) || k.piece === j.piece) continue;
2308
+ if (Math.hypot(k.x - j.x, k.y - j.y) <= snapInches) {
2309
+ g.push(k);
2310
+ taken.add(k.key);
2311
+ }
2312
+ }
2313
+ groups.push(g);
2314
+ }
2315
+ const connections = [];
2316
+ const open = [];
2317
+ const conflicts = [];
2318
+ for (const g of groups) {
2319
+ if (g.length === 1) open.push(g[0].key);
2320
+ else if (g.length === 2) connections.push({ a: g[0].key, b: g[1].key });
2321
+ else {
2322
+ conflicts.push({
2323
+ x: g[0].x,
2324
+ y: g[0].y,
2325
+ joints: g.map((j) => j.key),
2326
+ 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.`
2327
+ });
2328
+ for (const j of g) open.push(j.key);
2329
+ }
2330
+ }
2331
+ return { joints, connections, open, conflicts, unplaceable };
2332
+ }
2333
+ function walkTrackGraph(graph, pieces, startAt, library = BUILT_IN_TRACK_PARTS) {
2334
+ const byKey = new Map(graph.joints.map((j) => [j.key, j]));
2335
+ const pieceById = new Map(pieces.map((p) => [p.id, p]));
2336
+ const link = /* @__PURE__ */ new Map();
2337
+ for (const c of graph.connections) {
2338
+ link.set(c.a, c.b);
2339
+ link.set(c.b, c.a);
2340
+ }
2341
+ const partOf = (pid) => {
2342
+ const p = pieceById.get(pid);
2343
+ return p ? library.find((x) => x.id === p.partId) : void 0;
2344
+ };
2345
+ const geoOf = (pid) => {
2346
+ const part = partOf(pid);
2347
+ return part ? partGeometry(part, library) : null;
2348
+ };
2349
+ const gap = (a, b) => a && b ? Math.hypot(a.x - b.x, a.y - b.y) : 0;
2350
+ const routes = [];
2351
+ const turnouts = [];
2352
+ const warnings = [];
2353
+ const queued = /* @__PURE__ */ new Set();
2354
+ const pending = [];
2355
+ const walk = (startKey, startPos, id, bornAt) => {
2356
+ const route = {
2357
+ id,
2358
+ fromPos: startPos,
2359
+ toPos: startPos,
2360
+ bornAt,
2361
+ endsAt: null,
2362
+ pieces: [],
2363
+ spans: [],
2364
+ lateral: 0
2365
+ };
2366
+ let cur = startKey;
2367
+ let pos = startPos;
2368
+ const guard = /* @__PURE__ */ new Set();
2369
+ while (cur && !guard.has(cur)) {
2370
+ guard.add(cur);
2371
+ const here = byKey.get(cur);
2372
+ if (!here) break;
2373
+ const geo = geoOf(here.piece);
2374
+ if (!geo) break;
2375
+ const opts = geo.routes.filter((r) => r.includes(here.joint));
2376
+ if (!opts.length) break;
2377
+ const pick = opts.find((r) => r.includes("through")) ?? opts[0];
2378
+ const exitJoint = pick[0] === here.joint ? pick[1] : pick[0];
2379
+ const exit = byKey.get(`${here.piece}.${exitJoint}`);
2380
+ const part = partOf(here.piece);
2381
+ if (part && (part.kind === "turnout" || part.kind === "wye")) {
2382
+ const throat = byKey.get(`${here.piece}.throat`);
2383
+ const through = byKey.get(`${here.piece}.through`) ?? byKey.get(`${here.piece}.legA`);
2384
+ const body = gap(throat, through);
2385
+ const lead = part.lead?.inches ?? body / 2;
2386
+ const dvj = byKey.get(`${here.piece}.diverge`) ?? byKey.get(`${here.piece}.legB`);
2387
+ const frogPt = throat && body > 0 && dvj ? {
2388
+ x: throat.x + (dvj.x - throat.x) * lead / body,
2389
+ y: throat.y + (dvj.y - throat.y) * lead / body
2390
+ } : null;
2391
+ 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);
2392
+ const frogPos = pos + toFrog;
2393
+ if (here.joint === "diverge" || here.joint === "legB") {
2394
+ route.endsAt = here.piece;
2395
+ const known = turnouts.find((t) => t.id === here.piece);
2396
+ route.toPos = known ? known.pos : frogPos;
2397
+ break;
2398
+ }
2399
+ turnouts.push({ id: here.piece, pos: frogPos, onRoute: id, divergeRoute: null });
2400
+ for (const dj of ["diverge", "legB"]) {
2401
+ const dk = `${here.piece}.${dj}`;
2402
+ const d = byKey.get(dk);
2403
+ if (!d || queued.has(dk)) continue;
2404
+ queued.add(dk);
2405
+ const fp = throat && body > 0 ? {
2406
+ x: throat.x + (d.x - throat.x) * lead / body,
2407
+ y: throat.y + (d.y - throat.y) * lead / body
2408
+ } : null;
2409
+ pending.push({
2410
+ from: here.piece,
2411
+ at: frogPos,
2412
+ joint: dk,
2413
+ skew: fp ? Math.hypot(d.x - fp.x, d.y - fp.y) : 0
2414
+ });
2415
+ }
2416
+ }
2417
+ route.pieces.push(here.piece);
2418
+ for (const j of graph.joints)
2419
+ if (j.piece === here.piece && Math.abs(j.y) > Math.abs(route.lateral))
2420
+ route.lateral = j.y;
2421
+ const entered = pos;
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
+ });
2430
+ route.toPos = pos;
2431
+ const next = exit ? link.get(exit.key) : void 0;
2432
+ if (!next) break;
2433
+ cur = next;
2434
+ }
2435
+ return route;
2436
+ };
2437
+ routes.push(walk(`${startAt.piece}.${startAt.joint}`, 0, "main", null));
2438
+ let n = 0;
2439
+ while (pending.length) {
2440
+ const b = pending.shift();
2441
+ const start = link.get(b.joint);
2442
+ if (!start) {
2443
+ warnings.push(`the route diverging at ${b.from} is not connected to anything`);
2444
+ continue;
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
+ }
2452
+ n += 1;
2453
+ const r = walk(start, b.at + b.skew, `route${n}`, b.from);
2454
+ r.fromPos = b.at;
2455
+ routes.push(r);
2456
+ const sw = turnouts.find((t) => t.id === b.from);
2457
+ if (sw && !sw.divergeRoute) sw.divergeRoute = r.id;
2458
+ }
2459
+ const reached = new Set(routes.flatMap((r) => r.pieces));
2460
+ for (const p of pieces)
2461
+ if (!reached.has(p.id) && !graph.unplaceable.some((u) => u.piece === p.id))
2462
+ warnings.push(`${p.id} is not reachable from the endplate \u2014 nothing connects it`);
2463
+ for (const c of graph.conflicts) warnings.push(c.reason);
2464
+ return { routes, turnouts, warnings };
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
+ }
2246
2613
  function frogCasting(cl, opts = {}) {
2247
2614
  const g = opts.gaugeInches ?? RAIL_GAUGE_INCHES;
2248
2615
  const w = opts.reachInches ?? g * 1.5;
@@ -2876,6 +3243,6 @@ function poseOverridesFromDoc(doc) {
2876
3243
  return out;
2877
3244
  }
2878
3245
 
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 };
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 };
2880
3247
  //# sourceMappingURL=index.js.map
2881
3248
  //# sourceMappingURL=index.js.map