@willcgage/module-schematic 0.12.0 → 0.13.1

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.d.cts CHANGED
@@ -280,6 +280,16 @@ declare function buildPassingSiding(state: EditorState): {
280
280
  turnouts: EditorTurnout[];
281
281
  controlPoints: EditorControlPoint[];
282
282
  };
283
+ /**
284
+ * Build a crossover as one unit: a short connector track between Main 1 and
285
+ * Main 2, with a turnout on each main. The two turnouts sit on different lanes,
286
+ * so the feature resolver draws it as a diagonal (not a lane-paralleling siding)
287
+ * — this is what connects the two dots (#bug2). Needs a double-track module.
288
+ */
289
+ declare function buildCrossover(state: EditorState): {
290
+ track: EditorTrack;
291
+ turnouts: EditorTurnout[];
292
+ } | null;
283
293
  interface DrawTrack {
284
294
  id: string;
285
295
  role: TrackRole;
@@ -291,6 +301,13 @@ interface DrawTrack {
291
301
  * of the diverge diagonal. A spur off Main 2 starts at lane 1, not lane 0;
292
302
  * without this, renderers draw what looks like a crossover. */
293
303
  divergesFromLane: number;
304
+ /** The end that meets the main at its turnout (throat) and the far stub end,
305
+ * as fractions of length. Direction-preserving: unlike fromFrac/toFrac (always
306
+ * sorted West→East), these keep WHICH end joins, so an east-facing spur draws
307
+ * its throat on the east. A siding meets the main at both ends — there
308
+ * throat=fromFrac, stub=toFrac (the fields are only meaningful for spurs). */
309
+ throatFrac: number;
310
+ stubFrac: number;
294
311
  /** Inside the balloon of a loop module (#165). */
295
312
  inLoop: boolean;
296
313
  }
@@ -324,6 +341,18 @@ interface DrawCrossing {
324
341
  laneA: number;
325
342
  laneB: number;
326
343
  }
344
+ /** A crossover — a connector track joining two parallel mains through a turnout
345
+ * on each. Draw a straight diagonal between the two turnout points (unlike a
346
+ * siding, which parallels a lane between its turnouts). Detected structurally:
347
+ * a track whose turnouts sit on two different lanes. */
348
+ interface DrawCrossover {
349
+ id: string;
350
+ name: string;
351
+ fromPosFrac: number;
352
+ fromLane: number;
353
+ toPosFrac: number;
354
+ toLane: number;
355
+ }
327
356
  /** A branch endplate — draw a connector stub + arrow + label off the given
328
357
  * side (the CATS/US&S off-band idiom, #170). */
329
358
  interface BranchConnector {
@@ -352,6 +381,8 @@ interface ModuleFeatures {
352
381
  signals: DrawSignal[];
353
382
  /** Grade crossings / diamonds (#170). */
354
383
  crossings: DrawCrossing[];
384
+ /** Crossovers — connector tracks between two mains (drawn as a diagonal). */
385
+ crossovers: DrawCrossover[];
355
386
  /** Branch endplates — junction connectors off the module (#170). */
356
387
  branchConnectors: BranchConnector[];
357
388
  /** Main 2's extent when it doesn't run the full module — a single↔double
@@ -366,6 +397,17 @@ interface ModuleFeatures {
366
397
  laneMin: number;
367
398
  laneMax: number;
368
399
  }
400
+ /**
401
+ * Which side of the main a diverging track draws on, given the turnout's HAND
402
+ * and the direction the track body runs along the main from the turnout
403
+ * (`stubDir`: +1 = the body extends east / toward B, −1 = west / toward A).
404
+ * Returns +1 (above the main) or −1 (below); 0 for a wye or unset hand (keep the
405
+ * authored side). A left-hand turnout throws its route to the same side its body
406
+ * runs (facing the frog, left is the inside of a body running that way); a
407
+ * right-hand throws to the opposite side. `kind` is the source of truth for the
408
+ * drawn side (#bug1) — the stored lane's sign is reconciled to match it.
409
+ */
410
+ declare function divergeSideForHand(kind: TurnoutKind | undefined, stubDir: number): -1 | 0 | 1;
369
411
  /**
370
412
  * Resolve a schematic doc into positioned drawables. `pos` (inches) becomes a
371
413
  * fraction of the module length; endplate A = 0, B = length; turnouts sit at
@@ -437,4 +479,4 @@ declare function poseOverridesFromDoc(doc: ModuleSchematicDoc): Record<string, {
437
479
  heading: number;
438
480
  }>;
439
481
 
440
- export { type BranchConnector, type DrawCrossing, type DrawSignal, type DrawTrack, type DrawTurnout, type EditorBranch, type EditorControlPoint, type EditorCpSignal, type EditorCrossing, type EditorState, type EditorTrack, type EditorTurnout, type EndplateBConfig, type EndplatePose, type GeometryType, MAIN2_TRACK_ID, MAIN_TRACK_ID, type ModuleFeatures, type ModuleGeometryInput, type ModuleSchematicDoc, type ModuleTrackRow, N_SCALE_RATIO, type SchematicBlock, type SchematicControlPoint, type SchematicCrossing, type SchematicEndplate, type SchematicEndplateTrack, type SchematicSignal, type SchematicTrack, type SchematicTurnout, type SignalFacing, type SignalSide, type TrackConfig, type TrackRole, type TurnoutKind, asModuleSchematic, buildPassingSiding, buildTransition, deriveEndplatePoses, docToState, emptyEditorState, geometryTurnDegrees, inchesToScaleFeet, isLoopDoc, moduleFeatures, nextId, poseNeedsManual, poseOverridesFromDoc, scaleFeetToInches, stateToDoc };
482
+ export { type BranchConnector, type DrawCrossing, type DrawCrossover, type DrawSignal, type DrawTrack, type DrawTurnout, type EditorBranch, type EditorControlPoint, type EditorCpSignal, type EditorCrossing, type EditorState, type EditorTrack, type EditorTurnout, type EndplateBConfig, type EndplatePose, type GeometryType, MAIN2_TRACK_ID, MAIN_TRACK_ID, type ModuleFeatures, type ModuleGeometryInput, type ModuleSchematicDoc, type ModuleTrackRow, N_SCALE_RATIO, type SchematicBlock, type SchematicControlPoint, type SchematicCrossing, type SchematicEndplate, type SchematicEndplateTrack, type SchematicSignal, type SchematicTrack, type SchematicTurnout, type SignalFacing, type SignalSide, type TrackConfig, type TrackRole, type TurnoutKind, asModuleSchematic, buildCrossover, buildPassingSiding, buildTransition, deriveEndplatePoses, divergeSideForHand, docToState, emptyEditorState, geometryTurnDegrees, inchesToScaleFeet, isLoopDoc, moduleFeatures, nextId, poseNeedsManual, poseOverridesFromDoc, scaleFeetToInches, stateToDoc };
package/dist/index.d.ts CHANGED
@@ -280,6 +280,16 @@ declare function buildPassingSiding(state: EditorState): {
280
280
  turnouts: EditorTurnout[];
281
281
  controlPoints: EditorControlPoint[];
282
282
  };
283
+ /**
284
+ * Build a crossover as one unit: a short connector track between Main 1 and
285
+ * Main 2, with a turnout on each main. The two turnouts sit on different lanes,
286
+ * so the feature resolver draws it as a diagonal (not a lane-paralleling siding)
287
+ * — this is what connects the two dots (#bug2). Needs a double-track module.
288
+ */
289
+ declare function buildCrossover(state: EditorState): {
290
+ track: EditorTrack;
291
+ turnouts: EditorTurnout[];
292
+ } | null;
283
293
  interface DrawTrack {
284
294
  id: string;
285
295
  role: TrackRole;
@@ -291,6 +301,13 @@ interface DrawTrack {
291
301
  * of the diverge diagonal. A spur off Main 2 starts at lane 1, not lane 0;
292
302
  * without this, renderers draw what looks like a crossover. */
293
303
  divergesFromLane: number;
304
+ /** The end that meets the main at its turnout (throat) and the far stub end,
305
+ * as fractions of length. Direction-preserving: unlike fromFrac/toFrac (always
306
+ * sorted West→East), these keep WHICH end joins, so an east-facing spur draws
307
+ * its throat on the east. A siding meets the main at both ends — there
308
+ * throat=fromFrac, stub=toFrac (the fields are only meaningful for spurs). */
309
+ throatFrac: number;
310
+ stubFrac: number;
294
311
  /** Inside the balloon of a loop module (#165). */
295
312
  inLoop: boolean;
296
313
  }
@@ -324,6 +341,18 @@ interface DrawCrossing {
324
341
  laneA: number;
325
342
  laneB: number;
326
343
  }
344
+ /** A crossover — a connector track joining two parallel mains through a turnout
345
+ * on each. Draw a straight diagonal between the two turnout points (unlike a
346
+ * siding, which parallels a lane between its turnouts). Detected structurally:
347
+ * a track whose turnouts sit on two different lanes. */
348
+ interface DrawCrossover {
349
+ id: string;
350
+ name: string;
351
+ fromPosFrac: number;
352
+ fromLane: number;
353
+ toPosFrac: number;
354
+ toLane: number;
355
+ }
327
356
  /** A branch endplate — draw a connector stub + arrow + label off the given
328
357
  * side (the CATS/US&S off-band idiom, #170). */
329
358
  interface BranchConnector {
@@ -352,6 +381,8 @@ interface ModuleFeatures {
352
381
  signals: DrawSignal[];
353
382
  /** Grade crossings / diamonds (#170). */
354
383
  crossings: DrawCrossing[];
384
+ /** Crossovers — connector tracks between two mains (drawn as a diagonal). */
385
+ crossovers: DrawCrossover[];
355
386
  /** Branch endplates — junction connectors off the module (#170). */
356
387
  branchConnectors: BranchConnector[];
357
388
  /** Main 2's extent when it doesn't run the full module — a single↔double
@@ -366,6 +397,17 @@ interface ModuleFeatures {
366
397
  laneMin: number;
367
398
  laneMax: number;
368
399
  }
400
+ /**
401
+ * Which side of the main a diverging track draws on, given the turnout's HAND
402
+ * and the direction the track body runs along the main from the turnout
403
+ * (`stubDir`: +1 = the body extends east / toward B, −1 = west / toward A).
404
+ * Returns +1 (above the main) or −1 (below); 0 for a wye or unset hand (keep the
405
+ * authored side). A left-hand turnout throws its route to the same side its body
406
+ * runs (facing the frog, left is the inside of a body running that way); a
407
+ * right-hand throws to the opposite side. `kind` is the source of truth for the
408
+ * drawn side (#bug1) — the stored lane's sign is reconciled to match it.
409
+ */
410
+ declare function divergeSideForHand(kind: TurnoutKind | undefined, stubDir: number): -1 | 0 | 1;
369
411
  /**
370
412
  * Resolve a schematic doc into positioned drawables. `pos` (inches) becomes a
371
413
  * fraction of the module length; endplate A = 0, B = length; turnouts sit at
@@ -437,4 +479,4 @@ declare function poseOverridesFromDoc(doc: ModuleSchematicDoc): Record<string, {
437
479
  heading: number;
438
480
  }>;
439
481
 
440
- export { type BranchConnector, type DrawCrossing, type DrawSignal, type DrawTrack, type DrawTurnout, type EditorBranch, type EditorControlPoint, type EditorCpSignal, type EditorCrossing, type EditorState, type EditorTrack, type EditorTurnout, type EndplateBConfig, type EndplatePose, type GeometryType, MAIN2_TRACK_ID, MAIN_TRACK_ID, type ModuleFeatures, type ModuleGeometryInput, type ModuleSchematicDoc, type ModuleTrackRow, N_SCALE_RATIO, type SchematicBlock, type SchematicControlPoint, type SchematicCrossing, type SchematicEndplate, type SchematicEndplateTrack, type SchematicSignal, type SchematicTrack, type SchematicTurnout, type SignalFacing, type SignalSide, type TrackConfig, type TrackRole, type TurnoutKind, asModuleSchematic, buildPassingSiding, buildTransition, deriveEndplatePoses, docToState, emptyEditorState, geometryTurnDegrees, inchesToScaleFeet, isLoopDoc, moduleFeatures, nextId, poseNeedsManual, poseOverridesFromDoc, scaleFeetToInches, stateToDoc };
482
+ export { type BranchConnector, type DrawCrossing, type DrawCrossover, type DrawSignal, type DrawTrack, type DrawTurnout, type EditorBranch, type EditorControlPoint, type EditorCpSignal, type EditorCrossing, type EditorState, type EditorTrack, type EditorTurnout, type EndplateBConfig, type EndplatePose, type GeometryType, MAIN2_TRACK_ID, MAIN_TRACK_ID, type ModuleFeatures, type ModuleGeometryInput, type ModuleSchematicDoc, type ModuleTrackRow, N_SCALE_RATIO, type SchematicBlock, type SchematicControlPoint, type SchematicCrossing, type SchematicEndplate, type SchematicEndplateTrack, type SchematicSignal, type SchematicTrack, type SchematicTurnout, type SignalFacing, type SignalSide, type TrackConfig, type TrackRole, type TurnoutKind, asModuleSchematic, buildCrossover, buildPassingSiding, buildTransition, deriveEndplatePoses, divergeSideForHand, docToState, emptyEditorState, geometryTurnDegrees, inchesToScaleFeet, isLoopDoc, moduleFeatures, nextId, poseNeedsManual, poseOverridesFromDoc, scaleFeetToInches, stateToDoc };
package/dist/index.js CHANGED
@@ -61,7 +61,9 @@ function buildTransition(state) {
61
61
  pos,
62
62
  onTrack: MAIN_TRACK_ID,
63
63
  divergeTrack: MAIN2_TRACK_ID,
64
- kind: aDouble ? "left" : "right"
64
+ // Main 2 sits above (lane 1); the throw that runs toward the double end is
65
+ // left when that end is east (single at A), right when it's west (#bug1).
66
+ kind: aDouble ? "right" : "left"
65
67
  };
66
68
  const cpId = nextId("cp", state.controlPoints.map((c) => c.id));
67
69
  const sig = (facing) => ({
@@ -347,8 +349,8 @@ function buildPassingSiding(state) {
347
349
  const swW = nextId("sw", swIds);
348
350
  const swE = nextId("sw", [...swIds, swW]);
349
351
  const turnouts = [
350
- { id: swW, name: "West Siding", pos: fromPos, onTrack: MAIN_TRACK_ID, divergeTrack: sidId, kind: "right" },
351
- { id: swE, name: "East Siding", pos: toPos, onTrack: MAIN_TRACK_ID, divergeTrack: sidId, kind: "left" }
352
+ { id: swW, name: "West Siding", pos: fromPos, onTrack: MAIN_TRACK_ID, divergeTrack: sidId, kind: "left" },
353
+ { id: swE, name: "East Siding", pos: toPos, onTrack: MAIN_TRACK_ID, divergeTrack: sidId, kind: "right" }
352
354
  ];
353
355
  const cpIds = state.controlPoints.map((c) => c.id);
354
356
  const cpW = nextId("cp", cpIds);
@@ -367,6 +369,39 @@ function buildPassingSiding(state) {
367
369
  ];
368
370
  return { track, turnouts, controlPoints };
369
371
  }
372
+ function buildCrossover(state) {
373
+ const hasSecond = state.configA === "double" || state.configB === "double";
374
+ if (state.loop || !hasSecond) return null;
375
+ const len = state.lengthInches > 0 ? state.lengthInches : 24;
376
+ const mid = Math.round(len / 2);
377
+ const gap = Math.max(3, Math.round(len * 0.04));
378
+ const w = Math.round(mid - gap / 2);
379
+ const e = Math.round(mid + gap / 2);
380
+ const trackIds = [MAIN_TRACK_ID, MAIN2_TRACK_ID, ...state.extraTracks.map((t) => t.id)];
381
+ const xoId = nextId("xo", trackIds);
382
+ const track = {
383
+ id: xoId,
384
+ role: "crossover",
385
+ lane: 1,
386
+ fromPos: w,
387
+ toPos: e,
388
+ moduleTrackId: null,
389
+ trackName: "Crossover"
390
+ };
391
+ const swIds = state.turnouts.map((t) => t.id);
392
+ const sw1 = nextId("sw", swIds);
393
+ const sw2 = nextId("sw", [...swIds, sw1]);
394
+ const turnouts = [
395
+ { id: sw1, name: "Crossover", pos: w, onTrack: MAIN_TRACK_ID, divergeTrack: xoId, kind: "left" },
396
+ { id: sw2, name: "Crossover", pos: e, onTrack: MAIN2_TRACK_ID, divergeTrack: xoId, kind: "right" }
397
+ ];
398
+ return { track, turnouts };
399
+ }
400
+ function divergeSideForHand(kind, stubDir) {
401
+ if (kind !== "left" && kind !== "right") return 0;
402
+ const s = stubDir >= 0 ? 1 : -1;
403
+ return kind === "left" ? s : -s;
404
+ }
370
405
  function moduleFeatures(doc) {
371
406
  const len = doc.lengthInches && doc.lengthInches > 0 ? doc.lengthInches : Math.max(
372
407
  1,
@@ -393,23 +428,115 @@ function moduleFeatures(doc) {
393
428
  const sw = (doc.turnouts ?? []).find((t) => t.divergeTrack === trackId);
394
429
  return sw ? trackLane.get(sw.onTrack) ?? 0 : 0;
395
430
  };
431
+ const turnoutsByTrack = /* @__PURE__ */ new Map();
432
+ for (const sw of doc.turnouts ?? []) {
433
+ const arr = turnoutsByTrack.get(sw.divergeTrack) ?? [];
434
+ arr.push(sw);
435
+ turnoutsByTrack.set(sw.divergeTrack, arr);
436
+ }
437
+ const isCrossover = (trackId) => {
438
+ const sws = turnoutsByTrack.get(trackId) ?? [];
439
+ if (sws.length < 2) return false;
440
+ return new Set(sws.map((s) => trackLane.get(s.onTrack) ?? 0)).size >= 2;
441
+ };
442
+ const extentOf = (t) => {
443
+ const from = t.fromPos ?? posOf(t.from);
444
+ const to = t.toPos ?? posOf(t.to);
445
+ return from == null || to == null ? null : [from, to];
446
+ };
447
+ const trackById = new Map(doc.tracks.map((t) => [t.id, t]));
448
+ const resolvedLanes = /* @__PURE__ */ new Map();
449
+ const resolving = /* @__PURE__ */ new Set();
450
+ const resolveLane = (id) => {
451
+ const trk = trackById.get(id);
452
+ if (!trk) return 0;
453
+ if (trk.role === "main") return trk.lane;
454
+ if (resolvedLanes.has(id)) return resolvedLanes.get(id);
455
+ if (resolving.has(id)) return trk.lane;
456
+ resolving.add(id);
457
+ let lane = trk.lane;
458
+ const sw = turnoutsByTrack.get(id)?.[0];
459
+ const ext = extentOf(trk);
460
+ if (sw && (sw.kind === "left" || sw.kind === "right") && !isCrossover(id) && ext) {
461
+ const parentLane = resolveLane(sw.onTrack);
462
+ let sign;
463
+ if (parentLane === 0) {
464
+ const [from, to] = ext;
465
+ const far = Math.abs(to - sw.pos) >= Math.abs(from - sw.pos) ? to : from;
466
+ const s = divergeSideForHand(sw.kind, far - sw.pos);
467
+ sign = s !== 0 ? s : Math.sign(trk.lane) || 1;
468
+ } else {
469
+ sign = Math.sign(parentLane) || 1;
470
+ }
471
+ lane = sign * Math.abs(trk.lane);
472
+ }
473
+ resolving.delete(id);
474
+ resolvedLanes.set(id, lane);
475
+ return lane;
476
+ };
477
+ for (const t of doc.tracks) {
478
+ if (t.role === "main") continue;
479
+ trackLane.set(t.id, resolveLane(t.id));
480
+ }
396
481
  const extraTracks = [];
397
482
  for (const t of doc.tracks) {
398
483
  if (t.role === "main") continue;
399
- const from = t.fromPos ?? posOf(t.from);
400
- const to = t.toPos ?? posOf(t.to);
401
- if (from == null || to == null) continue;
484
+ if (isCrossover(t.id)) continue;
485
+ const ext = extentOf(t);
486
+ if (!ext) continue;
487
+ const [from, to] = ext;
488
+ const sw = turnoutsByTrack.get(t.id)?.[0];
489
+ const throatAtTo = sw != null && Math.abs(to - sw.pos) < Math.abs(from - sw.pos);
490
+ const throat = throatAtTo ? to : from;
491
+ const stub = throatAtTo ? from : to;
402
492
  extraTracks.push({
403
493
  id: t.id,
404
494
  role: t.role,
405
- lane: t.lane,
495
+ lane: trackLane.get(t.id) ?? t.lane,
406
496
  fromFrac: clampFrac(Math.min(from, to)),
407
497
  toFrac: clampFrac(Math.max(from, to)),
498
+ throatFrac: clampFrac(throat),
499
+ stubFrac: clampFrac(stub),
408
500
  capacityFeet: t.capacityFeet ?? null,
409
501
  divergesFromLane: divergeOrigin(t.id),
410
502
  inLoop: t.inLoop === true
411
503
  });
412
504
  }
505
+ const crossovers = [];
506
+ for (const t of doc.tracks) {
507
+ if (!isCrossover(t.id)) continue;
508
+ const [s1, s2] = turnoutsByTrack.get(t.id);
509
+ crossovers.push({
510
+ id: t.id,
511
+ name: t.trackName ?? "",
512
+ fromPosFrac: clampFrac(s1.pos),
513
+ fromLane: trackLane.get(s1.onTrack) ?? 0,
514
+ toPosFrac: clampFrac(s2.pos),
515
+ toLane: trackLane.get(s2.onTrack) ?? 1
516
+ });
517
+ }
518
+ const isMainId = (id) => !!id && trackById.get(id)?.role === "main";
519
+ const m2m = (doc.turnouts ?? []).filter(
520
+ (sw) => isMainId(sw.onTrack) && isMainId(sw.divergeTrack) && (trackLane.get(sw.onTrack) ?? 0) !== (trackLane.get(sw.divergeTrack) ?? 1)
521
+ );
522
+ const usedLegs = /* @__PURE__ */ new Set();
523
+ for (const t1 of m2m) {
524
+ if (usedLegs.has(t1.id)) continue;
525
+ usedLegs.add(t1.id);
526
+ const t2 = m2m.find(
527
+ (x) => !usedLegs.has(x.id) && x.onTrack === t1.divergeTrack && x.divergeTrack === t1.onTrack
528
+ );
529
+ if (t2) usedLegs.add(t2.id);
530
+ const end = t2 ?? t1;
531
+ crossovers.push({
532
+ id: t2 ? `${t1.id}-${t2.id}` : t1.id,
533
+ name: t1.name ?? t2?.name ?? "",
534
+ fromPosFrac: clampFrac(t1.pos),
535
+ fromLane: trackLane.get(t1.onTrack) ?? 0,
536
+ toPosFrac: clampFrac(end === t1 ? t1.pos : end.pos),
537
+ toLane: trackLane.get(t1.divergeTrack) ?? 1
538
+ });
539
+ }
413
540
  const turnouts = (doc.turnouts ?? []).map((t) => ({
414
541
  id: t.id,
415
542
  name: t.name ?? "",
@@ -455,7 +582,8 @@ function moduleFeatures(doc) {
455
582
  doubleMain ? 1 : 0,
456
583
  ...extraTracks.map((t) => t.lane),
457
584
  ...signals.map((s) => s.lane),
458
- ...crossings.flatMap((x) => [x.laneA, x.laneB])
585
+ ...crossings.flatMap((x) => [x.laneA, x.laneB]),
586
+ ...crossovers.flatMap((x) => [x.fromLane, x.toLane])
459
587
  ];
460
588
  const loop = isLoopDoc(doc);
461
589
  const main2 = doc.tracks.find((t) => t.id === MAIN2_TRACK_ID);
@@ -475,6 +603,7 @@ function moduleFeatures(doc) {
475
603
  turnouts,
476
604
  signals,
477
605
  crossings,
606
+ crossovers,
478
607
  branchConnectors,
479
608
  laneMin: Math.min(...allLanes),
480
609
  laneMax: Math.max(...allLanes)
@@ -578,6 +707,6 @@ function poseOverridesFromDoc(doc) {
578
707
  return out;
579
708
  }
580
709
 
581
- export { MAIN2_TRACK_ID, MAIN_TRACK_ID, N_SCALE_RATIO, asModuleSchematic, buildPassingSiding, buildTransition, deriveEndplatePoses, docToState, emptyEditorState, geometryTurnDegrees, inchesToScaleFeet, isLoopDoc, moduleFeatures, nextId, poseNeedsManual, poseOverridesFromDoc, scaleFeetToInches, stateToDoc };
710
+ export { MAIN2_TRACK_ID, MAIN_TRACK_ID, N_SCALE_RATIO, asModuleSchematic, buildCrossover, buildPassingSiding, buildTransition, deriveEndplatePoses, divergeSideForHand, docToState, emptyEditorState, geometryTurnDegrees, inchesToScaleFeet, isLoopDoc, moduleFeatures, nextId, poseNeedsManual, poseOverridesFromDoc, scaleFeetToInches, stateToDoc };
582
711
  //# sourceMappingURL=index.js.map
583
712
  //# sourceMappingURL=index.js.map