@willcgage/module-schematic 0.84.0 → 0.86.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
@@ -869,6 +869,10 @@ function withPoses(endplates, overrides) {
869
869
  (e) => overrides[e.id] ? { ...e, pose: overrides[e.id], poseAuthored: true } : e
870
870
  );
871
871
  }
872
+ function withEdges(endplates, edges) {
873
+ if (!edges) return endplates;
874
+ return endplates.map((e) => edges[e.id] ? { ...e, edge: edges[e.id] } : e);
875
+ }
872
876
  function withWidths(endplates, widths, offsets = {}) {
873
877
  return endplates.map((e) => {
874
878
  const w = widths[e.id];
@@ -900,46 +904,49 @@ function stateToDoc(state, recordNumber) {
900
904
  ...state.loop && state.loopReturn === "main2" ? { loopReturn: "main2" } : {},
901
905
  ...state.mainsSwapped ? { mainsSwapped: true } : {},
902
906
  endplates: withWidths(
903
- withPoses(
904
- [
905
- ...state.loop ? (
906
- // Balloon loop: A is the entry. A standard endplate B on the balloon
907
- // makes it an INTERCHANGE (second route connects at the loop, e.g.
908
- // Seaford); configB "none" makes it a pure turnback.
909
- [
910
- { id: "A", label: "Entry", tracks: [{ trackId: MAIN_TRACK_ID, lane: 0, config: state.configA }] },
911
- ...state.configB !== "none" ? [{ id: "B", label: "Interchange", tracks: [{ trackId: MAIN_TRACK_ID, lane: 0, config: state.configB }] }] : []
912
- ]
913
- ) : [
914
- { id: "A", label: "West", tracks: [{ trackId: MAIN_TRACK_ID, lane: 0, config: state.configA }] },
915
- // ⚠️ `configB: "none"` means the module HAS NO FAR ENDPLATE, and
916
- // that is not loop-only (#184). An *end of the line* or a *pocket*
917
- // presents one conforming face and the track simply stops. The
918
- // standard governs the ends a module offers for joining; it never
919
- // required a module to offer two. This used to coerce "none" to
920
- // "single" and emit a B regardless, so a single-ended module was
921
- // impossible to author.
922
- ...state.configB !== "none" ? [
923
- {
924
- id: "B",
925
- label: "East",
926
- tracks: [{ trackId: MAIN_TRACK_ID, lane: 0, config: state.configB }]
927
- }
928
- ] : []
907
+ withEdges(
908
+ withPoses(
909
+ [
910
+ ...state.loop ? (
911
+ // Balloon loop: A is the entry. A standard endplate B on the balloon
912
+ // makes it an INTERCHANGE (second route connects at the loop, e.g.
913
+ // Seaford); configB "none" makes it a pure turnback.
914
+ [
915
+ { id: "A", label: "Entry", tracks: [{ trackId: MAIN_TRACK_ID, lane: 0, config: state.configA }] },
916
+ ...state.configB !== "none" ? [{ id: "B", label: "Interchange", tracks: [{ trackId: MAIN_TRACK_ID, lane: 0, config: state.configB }] }] : []
917
+ ]
918
+ ) : [
919
+ { id: "A", label: "West", tracks: [{ trackId: MAIN_TRACK_ID, lane: 0, config: state.configA }] },
920
+ // ⚠️ `configB: "none"` means the module HAS NO FAR ENDPLATE, and
921
+ // that is not loop-only (#184). An *end of the line* or a *pocket*
922
+ // presents one conforming face and the track simply stops. The
923
+ // standard governs the ends a module offers for joining; it never
924
+ // required a module to offer two. This used to coerce "none" to
925
+ // "single" and emit a B regardless, so a single-ended module was
926
+ // impossible to author.
927
+ ...state.configB !== "none" ? [
928
+ {
929
+ id: "B",
930
+ label: "East",
931
+ tracks: [{ trackId: MAIN_TRACK_ID, lane: 0, config: state.configB }]
932
+ }
933
+ ] : []
934
+ ],
935
+ // Branch endplates C, D, … — junction connections at pos, off one side
936
+ // (#170). A set can carry several (e.g. a second railroad through).
937
+ ...state.branches.map((b, i) => ({
938
+ id: String.fromCharCode(67 + i),
939
+ // C, D, E…
940
+ label: b.label || `Branch ${i + 1}`,
941
+ tracks: [{ trackId: MAIN_TRACK_ID, lane: 0, config: b.config }],
942
+ at: { pos: b.pos, side: b.side },
943
+ kind: b.kind ?? "branch",
944
+ ...b.trackId ? { trackId: b.trackId } : {}
945
+ }))
929
946
  ],
930
- // Branch endplates C, D, … — junction connections at pos, off one side
931
- // (#170). A set can carry several (e.g. a second railroad through).
932
- ...state.branches.map((b, i) => ({
933
- id: String.fromCharCode(67 + i),
934
- // C, D, E…
935
- label: b.label || `Branch ${i + 1}`,
936
- tracks: [{ trackId: MAIN_TRACK_ID, lane: 0, config: b.config }],
937
- at: { pos: b.pos, side: b.side },
938
- kind: b.kind ?? "branch",
939
- ...b.trackId ? { trackId: b.trackId } : {}
940
- }))
941
- ],
942
- state.poseOverrides
947
+ state.poseOverrides
948
+ ),
949
+ state.endplateEdges
943
950
  ),
944
951
  state.endplateWidths,
945
952
  state.endplateTrackOffsets
@@ -1135,7 +1142,15 @@ function docToState(doc, fallbackLength, moduleTracks = []) {
1135
1142
  const poseOverrides = poseOverridesFromDoc(d);
1136
1143
  const endplateWidths = {};
1137
1144
  const endplateTrackOffsets = {};
1145
+ const endplateEdges = {};
1138
1146
  for (const e of d.endplates ?? []) {
1147
+ if (e.edge && Number.isFinite(e.edge.index) && e.edge.index >= 0)
1148
+ endplateEdges[e.id] = {
1149
+ index: Math.trunc(e.edge.index),
1150
+ ...e.edge.section ? { section: e.edge.section } : {},
1151
+ ...Number.isFinite(e.edge.fromT) ? { fromT: e.edge.fromT } : {},
1152
+ ...Number.isFinite(e.edge.toT) ? { toT: e.edge.toT } : {}
1153
+ };
1139
1154
  if (typeof e.widthInches === "number" && e.widthInches > 0)
1140
1155
  endplateWidths[e.id] = e.widthInches;
1141
1156
  if (typeof e.trackOffsetInches === "number" && Number.isFinite(e.trackOffsetInches))
@@ -1186,6 +1201,7 @@ function docToState(doc, fallbackLength, moduleTracks = []) {
1186
1201
  flexByTrack,
1187
1202
  endplateWidths,
1188
1203
  endplateTrackOffsets,
1204
+ endplateEdges,
1189
1205
  outline,
1190
1206
  outlineInner,
1191
1207
  sectionBreaks: (d.sectionBreaks ?? []).filter((n) => Number.isFinite(n)).map((n) => sc(n)),
@@ -2227,6 +2243,197 @@ function partsPlaceable(library = BUILT_IN_TRACK_PARTS) {
2227
2243
  }
2228
2244
  return { placeable, blocked };
2229
2245
  }
2246
+ var JOINT_SNAP_INCHES = 0.01;
2247
+ function placedJoints(pieces, library = BUILT_IN_TRACK_PARTS) {
2248
+ const out = [];
2249
+ const RAD = Math.PI / 180;
2250
+ for (const p of pieces) {
2251
+ const part = library.find((x) => x.id === p.partId);
2252
+ if (!part) continue;
2253
+ const geo = partGeometry(part, library);
2254
+ if (!geo) continue;
2255
+ const c = Math.cos(p.rotationDeg * RAD);
2256
+ const s = Math.sin(p.rotationDeg * RAD);
2257
+ for (const j of geo.joints) {
2258
+ const lx = part.kind === "flex" && j.id === "b" ? p.lengthInches ?? 0 : j.x;
2259
+ const ly = p.flipped ? -j.y : j.y;
2260
+ const h = (p.flipped ? -j.angleDeg : j.angleDeg) + p.rotationDeg;
2261
+ out.push({
2262
+ key: `${p.id}.${j.id}`,
2263
+ piece: p.id,
2264
+ joint: j.id,
2265
+ role: j.role,
2266
+ x: p.x + lx * c - ly * s,
2267
+ y: p.y + lx * s + ly * c,
2268
+ headingDeg: norm360(h)
2269
+ });
2270
+ }
2271
+ }
2272
+ return out;
2273
+ }
2274
+ function buildTrackGraph(pieces, library = BUILT_IN_TRACK_PARTS, snapInches = JOINT_SNAP_INCHES) {
2275
+ const joints = placedJoints(pieces, library);
2276
+ const unplaceable = [];
2277
+ for (const p of pieces) {
2278
+ const part = library.find((x) => x.id === p.partId);
2279
+ if (!part) {
2280
+ unplaceable.push({ piece: p.id, partId: p.partId, why: "no such part in the library" });
2281
+ continue;
2282
+ }
2283
+ const why = partGeometryGap(part);
2284
+ if (why) unplaceable.push({ piece: p.id, partId: p.partId, why });
2285
+ }
2286
+ const groups = [];
2287
+ const taken = /* @__PURE__ */ new Set();
2288
+ for (const j of joints) {
2289
+ if (taken.has(j.key)) continue;
2290
+ const g = [j];
2291
+ taken.add(j.key);
2292
+ for (const k of joints) {
2293
+ if (taken.has(k.key) || k.piece === j.piece) continue;
2294
+ if (Math.hypot(k.x - j.x, k.y - j.y) <= snapInches) {
2295
+ g.push(k);
2296
+ taken.add(k.key);
2297
+ }
2298
+ }
2299
+ groups.push(g);
2300
+ }
2301
+ const connections = [];
2302
+ const open = [];
2303
+ const conflicts = [];
2304
+ for (const g of groups) {
2305
+ if (g.length === 1) open.push(g[0].key);
2306
+ else if (g.length === 2) connections.push({ a: g[0].key, b: g[1].key });
2307
+ else {
2308
+ conflicts.push({
2309
+ x: g[0].x,
2310
+ y: g[0].y,
2311
+ joints: g.map((j) => j.key),
2312
+ 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.`
2313
+ });
2314
+ for (const j of g) open.push(j.key);
2315
+ }
2316
+ }
2317
+ return { joints, connections, open, conflicts, unplaceable };
2318
+ }
2319
+ function walkTrackGraph(graph, pieces, startAt, library = BUILT_IN_TRACK_PARTS) {
2320
+ const byKey = new Map(graph.joints.map((j) => [j.key, j]));
2321
+ const pieceById = new Map(pieces.map((p) => [p.id, p]));
2322
+ const link = /* @__PURE__ */ new Map();
2323
+ for (const c of graph.connections) {
2324
+ link.set(c.a, c.b);
2325
+ link.set(c.b, c.a);
2326
+ }
2327
+ const partOf = (pid) => {
2328
+ const p = pieceById.get(pid);
2329
+ return p ? library.find((x) => x.id === p.partId) : void 0;
2330
+ };
2331
+ const geoOf = (pid) => {
2332
+ const part = partOf(pid);
2333
+ return part ? partGeometry(part, library) : null;
2334
+ };
2335
+ const gap = (a, b) => a && b ? Math.hypot(a.x - b.x, a.y - b.y) : 0;
2336
+ const routes = [];
2337
+ const turnouts = [];
2338
+ const warnings = [];
2339
+ const queued = /* @__PURE__ */ new Set();
2340
+ const pending = [];
2341
+ const walk = (startKey, startPos, id, bornAt) => {
2342
+ const route = {
2343
+ id,
2344
+ fromPos: startPos,
2345
+ toPos: startPos,
2346
+ bornAt,
2347
+ endsAt: null,
2348
+ pieces: [],
2349
+ lateral: 0
2350
+ };
2351
+ let cur = startKey;
2352
+ let pos = startPos;
2353
+ const guard = /* @__PURE__ */ new Set();
2354
+ while (cur && !guard.has(cur)) {
2355
+ guard.add(cur);
2356
+ const here = byKey.get(cur);
2357
+ if (!here) break;
2358
+ const geo = geoOf(here.piece);
2359
+ if (!geo) break;
2360
+ const opts = geo.routes.filter((r) => r.includes(here.joint));
2361
+ if (!opts.length) break;
2362
+ const pick = opts.find((r) => r.includes("through")) ?? opts[0];
2363
+ const exitJoint = pick[0] === here.joint ? pick[1] : pick[0];
2364
+ const exit = byKey.get(`${here.piece}.${exitJoint}`);
2365
+ const part = partOf(here.piece);
2366
+ if (part && (part.kind === "turnout" || part.kind === "wye")) {
2367
+ const throat = byKey.get(`${here.piece}.throat`);
2368
+ const through = byKey.get(`${here.piece}.through`) ?? byKey.get(`${here.piece}.legA`);
2369
+ const body = gap(throat, through);
2370
+ const lead = part.lead?.inches ?? body / 2;
2371
+ const dvj = byKey.get(`${here.piece}.diverge`) ?? byKey.get(`${here.piece}.legB`);
2372
+ const frogPt = throat && body > 0 && dvj ? {
2373
+ x: throat.x + (dvj.x - throat.x) * lead / body,
2374
+ y: throat.y + (dvj.y - throat.y) * lead / body
2375
+ } : null;
2376
+ 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);
2377
+ const frogPos = pos + toFrog;
2378
+ if (here.joint === "diverge" || here.joint === "legB") {
2379
+ route.endsAt = here.piece;
2380
+ const known = turnouts.find((t) => t.id === here.piece);
2381
+ route.toPos = known ? known.pos : frogPos;
2382
+ break;
2383
+ }
2384
+ turnouts.push({ id: here.piece, pos: frogPos, onRoute: id, divergeRoute: null });
2385
+ for (const dj of ["diverge", "legB"]) {
2386
+ const dk = `${here.piece}.${dj}`;
2387
+ const d = byKey.get(dk);
2388
+ if (!d || queued.has(dk)) continue;
2389
+ queued.add(dk);
2390
+ const fp = throat && body > 0 ? {
2391
+ x: throat.x + (d.x - throat.x) * lead / body,
2392
+ y: throat.y + (d.y - throat.y) * lead / body
2393
+ } : null;
2394
+ pending.push({
2395
+ from: here.piece,
2396
+ at: frogPos,
2397
+ joint: dk,
2398
+ skew: fp ? Math.hypot(d.x - fp.x, d.y - fp.y) : 0
2399
+ });
2400
+ }
2401
+ }
2402
+ route.pieces.push(here.piece);
2403
+ for (const j of graph.joints)
2404
+ if (j.piece === here.piece && Math.abs(j.y) > Math.abs(route.lateral))
2405
+ route.lateral = j.y;
2406
+ pos += gap(here, exit);
2407
+ route.toPos = pos;
2408
+ const next = exit ? link.get(exit.key) : void 0;
2409
+ if (!next) break;
2410
+ cur = next;
2411
+ }
2412
+ return route;
2413
+ };
2414
+ routes.push(walk(`${startAt.piece}.${startAt.joint}`, 0, "main", null));
2415
+ let n = 0;
2416
+ while (pending.length) {
2417
+ const b = pending.shift();
2418
+ const start = link.get(b.joint);
2419
+ if (!start) {
2420
+ warnings.push(`the route diverging at ${b.from} is not connected to anything`);
2421
+ continue;
2422
+ }
2423
+ n += 1;
2424
+ const r = walk(start, b.at + b.skew, `route${n}`, b.from);
2425
+ r.fromPos = b.at;
2426
+ routes.push(r);
2427
+ const sw = turnouts.find((t) => t.id === b.from);
2428
+ if (sw && !sw.divergeRoute) sw.divergeRoute = r.id;
2429
+ }
2430
+ const reached = new Set(routes.flatMap((r) => r.pieces));
2431
+ for (const p of pieces)
2432
+ if (!reached.has(p.id) && !graph.unplaceable.some((u) => u.piece === p.id))
2433
+ warnings.push(`${p.id} is not reachable from the endplate \u2014 nothing connects it`);
2434
+ for (const c of graph.conflicts) warnings.push(c.reason);
2435
+ return { routes, turnouts, warnings };
2436
+ }
2230
2437
  function frogCasting(cl, opts = {}) {
2231
2438
  const g = opts.gaugeInches ?? RAIL_GAUGE_INCHES;
2232
2439
  const w = opts.reachInches ?? g * 1.5;
@@ -2532,6 +2739,45 @@ function moduleFeatures(doc) {
2532
2739
  laneMax: Math.max(...allLanes)
2533
2740
  };
2534
2741
  }
2742
+ function endplateEdgePose(outline, edge) {
2743
+ if (!outline || outline.length < 3) return null;
2744
+ const n = outline.length;
2745
+ const i = Math.trunc(edge.index);
2746
+ if (!Number.isFinite(i) || i < 0 || i >= n) return null;
2747
+ const p0 = outline[i];
2748
+ const p1 = outline[(i + 1) % n];
2749
+ if (p0.bulge) return null;
2750
+ const t0 = Math.max(0, Math.min(1, edge.fromT ?? 0));
2751
+ const t1 = Math.max(0, Math.min(1, edge.toT ?? 1));
2752
+ const a = { x: p0.x + (p1.x - p0.x) * Math.min(t0, t1), y: p0.y + (p1.y - p0.y) * Math.min(t0, t1) };
2753
+ const b = { x: p0.x + (p1.x - p0.x) * Math.max(t0, t1), y: p0.y + (p1.y - p0.y) * Math.max(t0, t1) };
2754
+ const w = Math.hypot(b.x - a.x, b.y - a.y);
2755
+ if (!(w > 0)) return null;
2756
+ let cx = 0;
2757
+ let cy = 0;
2758
+ for (const v of outline) {
2759
+ cx += v.x;
2760
+ cy += v.y;
2761
+ }
2762
+ cx /= n;
2763
+ cy /= n;
2764
+ const mid = { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 };
2765
+ const ex = (b.x - a.x) / w;
2766
+ const ey = (b.y - a.y) / w;
2767
+ let nx = ey;
2768
+ let ny = -ex;
2769
+ if ((mid.x - cx) * nx + (mid.y - cy) * ny < 0) {
2770
+ nx = -nx;
2771
+ ny = -ny;
2772
+ }
2773
+ return {
2774
+ x: mid.x,
2775
+ y: mid.y,
2776
+ heading: norm360(Math.atan2(ny, nx) * 180 / Math.PI),
2777
+ widthInches: w,
2778
+ face: [a, b]
2779
+ };
2780
+ }
2535
2781
  function returnLoop(shape, opts) {
2536
2782
  const L = Math.max(1, opts.leadInches);
2537
2783
  const R = Math.max(1, opts.radius);
@@ -2635,6 +2881,21 @@ function deriveEndplatePoses(geo) {
2635
2881
  const half = geo.trackHalfSpacingInches ?? 1;
2636
2882
  const cfg = (i) => geo.endplateConfigs?.[i] === "double" ? "double" : "single";
2637
2883
  const withOverride = (p) => {
2884
+ const bound = geo.endplateEdges?.[p.id];
2885
+ if (bound) {
2886
+ const outline = (bound.section ? geo.sections?.find((s) => s.id === bound.section)?.outline : geo.outline) ?? geo.outline;
2887
+ const e = endplateEdgePose(outline, bound);
2888
+ if (e)
2889
+ return {
2890
+ ...p,
2891
+ x: e.x,
2892
+ y: e.y,
2893
+ heading: e.heading,
2894
+ widthInches: e.widthInches,
2895
+ face: e.face,
2896
+ boundToEdge: true
2897
+ };
2898
+ }
2638
2899
  const o = geo.poseOverrides?.[p.id];
2639
2900
  return o ? { ...p, x: o.x, y: o.y, heading: norm360(o.heading), manual: true } : p;
2640
2901
  };
@@ -2806,6 +3067,6 @@ function poseOverridesFromDoc(doc) {
2806
3067
  return out;
2807
3068
  }
2808
3069
 
2809
- 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, 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 };
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 };
2810
3071
  //# sourceMappingURL=index.js.map
2811
3072
  //# sourceMappingURL=index.js.map