@willcgage/module-schematic 0.18.0 → 0.20.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.d.cts CHANGED
@@ -78,6 +78,11 @@ interface SchematicTrack {
78
78
  * endplate A (past the throat = in the loop), lane is the ladder/arc index —
79
79
  * one record drives both the unrolled fan and a geometric render. */
80
80
  inLoop?: boolean;
81
+ /** Authored 2-D path for this track (module-local inches, open path with
82
+ * arcs) — the PHYSICAL shape a bent/rotated spur draws. Absent = derive from
83
+ * the main centre-line + lane, as before. Physical view only; the operations
84
+ * view stays positional (#2d-track). */
85
+ path?: BenchworkPoint[] | null;
81
86
  }
82
87
  interface SchematicTurnout {
83
88
  id: string;
@@ -87,6 +92,8 @@ interface SchematicTurnout {
87
92
  kind?: TurnoutKind;
88
93
  name?: string | null;
89
94
  address?: string | null;
95
+ /** Frog number ("size") — #4, #6, #8, etc. Governs the diverging angle. */
96
+ size?: number | null;
90
97
  }
91
98
  interface SchematicSignal {
92
99
  id: string;
@@ -195,6 +202,10 @@ interface ModuleSchematicDoc {
195
202
  outline?: BenchworkPoint[];
196
203
  /** @deprecated pre-grouping flat signals; read for back-compat. */
197
204
  signals?: SchematicSignal[];
205
+ /** Authored mainline centre-line (module-local inches, open path with arcs).
206
+ * Present = the owner drew the real shape; absent = derive from geometry.
207
+ * Physical view only — the operations view stays derived (#2d-track). */
208
+ mainPath?: BenchworkPoint[] | null;
198
209
  }
199
210
  /** A benchwork-outline vertex, module-local inches. The edge from this vertex
200
211
  * to the NEXT one is a straight line, unless `bulge` is set — then it's a
@@ -222,6 +233,19 @@ declare function sampleBenchworkOutline(pts: BenchworkPoint[], segsPerArc?: numb
222
233
  x: number;
223
234
  y: number;
224
235
  }[];
236
+ /**
237
+ * Expand an OPEN track path (whose edges may be arcs) into a dense polyline —
238
+ * the open-ended sibling of sampleBenchworkOutline (which closes the ring).
239
+ * Used for authored track centre-lines (a drawn mainline or spur). The final
240
+ * vertex is always emitted so the path reaches its end.
241
+ */
242
+ declare function samplePath(pts: BenchworkPoint[], segsPerArc?: number): {
243
+ x: number;
244
+ y: number;
245
+ }[];
246
+ /** Normalise an authored track path from a doc, or null if it isn't a real path
247
+ * (needs ≥ 2 valid points). Keeps per-vertex bulge. */
248
+ declare function trackPath(path: BenchworkPoint[] | null | undefined): BenchworkPoint[] | null;
225
249
  interface ModuleFootprintInput {
226
250
  /** Mainline length (falls back to footprint length), inches. */
227
251
  lengthInches: number;
@@ -232,6 +256,10 @@ interface ModuleFootprintInput {
232
256
  endplateWidths?: Record<string, number>;
233
257
  /** Authored benchwork outline (module-local inches), or absent for the band. */
234
258
  outline?: BenchworkPoint[] | null;
259
+ /** Authored mainline centre-line (module-local inches, open path with arcs).
260
+ * When present it wins over the geometry-derived centre-line — the owner drew
261
+ * the real shape (#2d-track, physical view only). */
262
+ mainPath?: BenchworkPoint[] | null;
235
263
  }
236
264
  interface OutlineFace {
237
265
  /** The endplate face's two corners + midpoint (the track point). */
@@ -249,7 +277,9 @@ interface ModuleFootprint {
249
277
  /** Authored outline (arc-sampled closed ring) or null → render the band. */
250
278
  outline: BenchworkPoint[] | null;
251
279
  }
252
- /** Module-local main track centre-line (A→B), sampling arcs for curves/corners. */
280
+ /** Module-local main track centre-line (A→B), sampling arcs for curves/corners.
281
+ * An authored `mainPath` wins — the owner drew the real shape; otherwise the
282
+ * centre-line is derived from the geometry fields (length + type/degrees/offset). */
253
283
  declare function moduleCenterline(input: ModuleFootprintInput): BenchworkPoint[];
254
284
  /** Benchwork band: the centre-line offset ±half-width, tapering widthA→widthB. */
255
285
  declare function benchworkBand(center: BenchworkPoint[], widthA?: number, widthB?: number): BenchworkPoint[];
@@ -295,6 +325,9 @@ interface EditorTrack {
295
325
  trackName: string;
296
326
  /** Inside the balloon of a loop module (#165). */
297
327
  inLoop?: boolean;
328
+ /** Authored 2-D path (module-local inches) — a bent/rotated spur's real
329
+ * shape. Absent = derive from the main + lane (#2d-track). */
330
+ path?: BenchworkPoint[];
298
331
  }
299
332
  /** A module_tracks row as loaded for the editor. */
300
333
  interface ModuleTrackRow {
@@ -309,6 +342,8 @@ interface EditorTurnout {
309
342
  onTrack: string;
310
343
  divergeTrack: string;
311
344
  kind: TurnoutKind;
345
+ /** Frog number ("size") — #4, #6, #8, etc. Governs the diverging angle. */
346
+ size?: number;
312
347
  }
313
348
  interface EditorCpSignal {
314
349
  id: string;
@@ -393,6 +428,9 @@ interface EditorState {
393
428
  controlPoints: EditorControlPoint[];
394
429
  /** Rail-served industries — car-spot spans on a track (#industries). */
395
430
  industries: EditorIndustry[];
431
+ /** Authored mainline centre-line (module-local inches) — empty = derive from
432
+ * geometry. The owner-drawn real shape (#2d-track, physical view only). */
433
+ mainPath: BenchworkPoint[];
396
434
  }
397
435
  /** Build the empty editor state for a module of the given length. */
398
436
  declare function emptyEditorState(lengthInches: number): EditorState;
@@ -671,4 +709,4 @@ declare function poseOverridesFromDoc(doc: ModuleSchematicDoc): Record<string, {
671
709
  heading: number;
672
710
  }>;
673
711
 
674
- export { type BenchworkPoint, type BranchConnector, type DrawCrossing, type DrawCrossover, type DrawIndustry, type DrawSignal, type DrawTrack, type DrawTurnout, type EditorBranch, type EditorControlPoint, type EditorCpSignal, type EditorCrossing, type EditorIndustry, type EditorState, type EditorTrack, type EditorTurnout, type EndplateBConfig, type EndplatePose, FREEMO_ENDPLATE_WIDTH_MIN_INCHES, FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES, type GeometryType, type IndustryLabelMode, MAIN2_TRACK_ID, MAIN_TRACK_ID, type ModuleFeatures, type ModuleFootprint, type ModuleFootprintInput, type ModuleGeometryInput, type ModuleSchematicDoc, type ModuleTrackRow, N_CAR_LENGTH_INCHES, N_SCALE_RATIO, type OutlineFace, type SchematicBlock, type SchematicControlPoint, type SchematicCrossing, type SchematicEndplate, type SchematicEndplateTrack, type SchematicIndustry, type SchematicSignal, type SchematicTrack, type SchematicTurnout, type SignalFacing, type SignalSide, type TrackConfig, type TrackRole, type TurnoutKind, asModuleSchematic, benchworkBand, benchworkOutline, buildCrossover, buildPassingSiding, buildTransition, carCapacity, deriveEndplatePoses, divergeSideForHand, docToState, emptyEditorState, endplateFaceSegments, endplateWidthInches, geometryTurnDegrees, inchesToScaleFeet, isLoopDoc, isTransitionTurnout, moduleCenterline, moduleFeatures, moduleFootprint, nextId, poseNeedsManual, poseOverridesFromDoc, sampleBenchworkOutline, scaleFeetToInches, stateToDoc };
712
+ export { type BenchworkPoint, type BranchConnector, type DrawCrossing, type DrawCrossover, type DrawIndustry, type DrawSignal, type DrawTrack, type DrawTurnout, type EditorBranch, type EditorControlPoint, type EditorCpSignal, type EditorCrossing, type EditorIndustry, type EditorState, type EditorTrack, type EditorTurnout, type EndplateBConfig, type EndplatePose, FREEMO_ENDPLATE_WIDTH_MIN_INCHES, FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES, type GeometryType, type IndustryLabelMode, MAIN2_TRACK_ID, MAIN_TRACK_ID, type ModuleFeatures, type ModuleFootprint, type ModuleFootprintInput, type ModuleGeometryInput, type ModuleSchematicDoc, type ModuleTrackRow, N_CAR_LENGTH_INCHES, N_SCALE_RATIO, type OutlineFace, type SchematicBlock, type SchematicControlPoint, type SchematicCrossing, type SchematicEndplate, type SchematicEndplateTrack, type SchematicIndustry, type SchematicSignal, type SchematicTrack, type SchematicTurnout, type SignalFacing, type SignalSide, type TrackConfig, type TrackRole, type TurnoutKind, asModuleSchematic, benchworkBand, benchworkOutline, buildCrossover, buildPassingSiding, buildTransition, carCapacity, deriveEndplatePoses, divergeSideForHand, docToState, emptyEditorState, endplateFaceSegments, endplateWidthInches, geometryTurnDegrees, inchesToScaleFeet, isLoopDoc, isTransitionTurnout, moduleCenterline, moduleFeatures, moduleFootprint, nextId, poseNeedsManual, poseOverridesFromDoc, sampleBenchworkOutline, samplePath, scaleFeetToInches, stateToDoc, trackPath };
package/dist/index.d.ts CHANGED
@@ -78,6 +78,11 @@ interface SchematicTrack {
78
78
  * endplate A (past the throat = in the loop), lane is the ladder/arc index —
79
79
  * one record drives both the unrolled fan and a geometric render. */
80
80
  inLoop?: boolean;
81
+ /** Authored 2-D path for this track (module-local inches, open path with
82
+ * arcs) — the PHYSICAL shape a bent/rotated spur draws. Absent = derive from
83
+ * the main centre-line + lane, as before. Physical view only; the operations
84
+ * view stays positional (#2d-track). */
85
+ path?: BenchworkPoint[] | null;
81
86
  }
82
87
  interface SchematicTurnout {
83
88
  id: string;
@@ -87,6 +92,8 @@ interface SchematicTurnout {
87
92
  kind?: TurnoutKind;
88
93
  name?: string | null;
89
94
  address?: string | null;
95
+ /** Frog number ("size") — #4, #6, #8, etc. Governs the diverging angle. */
96
+ size?: number | null;
90
97
  }
91
98
  interface SchematicSignal {
92
99
  id: string;
@@ -195,6 +202,10 @@ interface ModuleSchematicDoc {
195
202
  outline?: BenchworkPoint[];
196
203
  /** @deprecated pre-grouping flat signals; read for back-compat. */
197
204
  signals?: SchematicSignal[];
205
+ /** Authored mainline centre-line (module-local inches, open path with arcs).
206
+ * Present = the owner drew the real shape; absent = derive from geometry.
207
+ * Physical view only — the operations view stays derived (#2d-track). */
208
+ mainPath?: BenchworkPoint[] | null;
198
209
  }
199
210
  /** A benchwork-outline vertex, module-local inches. The edge from this vertex
200
211
  * to the NEXT one is a straight line, unless `bulge` is set — then it's a
@@ -222,6 +233,19 @@ declare function sampleBenchworkOutline(pts: BenchworkPoint[], segsPerArc?: numb
222
233
  x: number;
223
234
  y: number;
224
235
  }[];
236
+ /**
237
+ * Expand an OPEN track path (whose edges may be arcs) into a dense polyline —
238
+ * the open-ended sibling of sampleBenchworkOutline (which closes the ring).
239
+ * Used for authored track centre-lines (a drawn mainline or spur). The final
240
+ * vertex is always emitted so the path reaches its end.
241
+ */
242
+ declare function samplePath(pts: BenchworkPoint[], segsPerArc?: number): {
243
+ x: number;
244
+ y: number;
245
+ }[];
246
+ /** Normalise an authored track path from a doc, or null if it isn't a real path
247
+ * (needs ≥ 2 valid points). Keeps per-vertex bulge. */
248
+ declare function trackPath(path: BenchworkPoint[] | null | undefined): BenchworkPoint[] | null;
225
249
  interface ModuleFootprintInput {
226
250
  /** Mainline length (falls back to footprint length), inches. */
227
251
  lengthInches: number;
@@ -232,6 +256,10 @@ interface ModuleFootprintInput {
232
256
  endplateWidths?: Record<string, number>;
233
257
  /** Authored benchwork outline (module-local inches), or absent for the band. */
234
258
  outline?: BenchworkPoint[] | null;
259
+ /** Authored mainline centre-line (module-local inches, open path with arcs).
260
+ * When present it wins over the geometry-derived centre-line — the owner drew
261
+ * the real shape (#2d-track, physical view only). */
262
+ mainPath?: BenchworkPoint[] | null;
235
263
  }
236
264
  interface OutlineFace {
237
265
  /** The endplate face's two corners + midpoint (the track point). */
@@ -249,7 +277,9 @@ interface ModuleFootprint {
249
277
  /** Authored outline (arc-sampled closed ring) or null → render the band. */
250
278
  outline: BenchworkPoint[] | null;
251
279
  }
252
- /** Module-local main track centre-line (A→B), sampling arcs for curves/corners. */
280
+ /** Module-local main track centre-line (A→B), sampling arcs for curves/corners.
281
+ * An authored `mainPath` wins — the owner drew the real shape; otherwise the
282
+ * centre-line is derived from the geometry fields (length + type/degrees/offset). */
253
283
  declare function moduleCenterline(input: ModuleFootprintInput): BenchworkPoint[];
254
284
  /** Benchwork band: the centre-line offset ±half-width, tapering widthA→widthB. */
255
285
  declare function benchworkBand(center: BenchworkPoint[], widthA?: number, widthB?: number): BenchworkPoint[];
@@ -295,6 +325,9 @@ interface EditorTrack {
295
325
  trackName: string;
296
326
  /** Inside the balloon of a loop module (#165). */
297
327
  inLoop?: boolean;
328
+ /** Authored 2-D path (module-local inches) — a bent/rotated spur's real
329
+ * shape. Absent = derive from the main + lane (#2d-track). */
330
+ path?: BenchworkPoint[];
298
331
  }
299
332
  /** A module_tracks row as loaded for the editor. */
300
333
  interface ModuleTrackRow {
@@ -309,6 +342,8 @@ interface EditorTurnout {
309
342
  onTrack: string;
310
343
  divergeTrack: string;
311
344
  kind: TurnoutKind;
345
+ /** Frog number ("size") — #4, #6, #8, etc. Governs the diverging angle. */
346
+ size?: number;
312
347
  }
313
348
  interface EditorCpSignal {
314
349
  id: string;
@@ -393,6 +428,9 @@ interface EditorState {
393
428
  controlPoints: EditorControlPoint[];
394
429
  /** Rail-served industries — car-spot spans on a track (#industries). */
395
430
  industries: EditorIndustry[];
431
+ /** Authored mainline centre-line (module-local inches) — empty = derive from
432
+ * geometry. The owner-drawn real shape (#2d-track, physical view only). */
433
+ mainPath: BenchworkPoint[];
396
434
  }
397
435
  /** Build the empty editor state for a module of the given length. */
398
436
  declare function emptyEditorState(lengthInches: number): EditorState;
@@ -671,4 +709,4 @@ declare function poseOverridesFromDoc(doc: ModuleSchematicDoc): Record<string, {
671
709
  heading: number;
672
710
  }>;
673
711
 
674
- export { type BenchworkPoint, type BranchConnector, type DrawCrossing, type DrawCrossover, type DrawIndustry, type DrawSignal, type DrawTrack, type DrawTurnout, type EditorBranch, type EditorControlPoint, type EditorCpSignal, type EditorCrossing, type EditorIndustry, type EditorState, type EditorTrack, type EditorTurnout, type EndplateBConfig, type EndplatePose, FREEMO_ENDPLATE_WIDTH_MIN_INCHES, FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES, type GeometryType, type IndustryLabelMode, MAIN2_TRACK_ID, MAIN_TRACK_ID, type ModuleFeatures, type ModuleFootprint, type ModuleFootprintInput, type ModuleGeometryInput, type ModuleSchematicDoc, type ModuleTrackRow, N_CAR_LENGTH_INCHES, N_SCALE_RATIO, type OutlineFace, type SchematicBlock, type SchematicControlPoint, type SchematicCrossing, type SchematicEndplate, type SchematicEndplateTrack, type SchematicIndustry, type SchematicSignal, type SchematicTrack, type SchematicTurnout, type SignalFacing, type SignalSide, type TrackConfig, type TrackRole, type TurnoutKind, asModuleSchematic, benchworkBand, benchworkOutline, buildCrossover, buildPassingSiding, buildTransition, carCapacity, deriveEndplatePoses, divergeSideForHand, docToState, emptyEditorState, endplateFaceSegments, endplateWidthInches, geometryTurnDegrees, inchesToScaleFeet, isLoopDoc, isTransitionTurnout, moduleCenterline, moduleFeatures, moduleFootprint, nextId, poseNeedsManual, poseOverridesFromDoc, sampleBenchworkOutline, scaleFeetToInches, stateToDoc };
712
+ export { type BenchworkPoint, type BranchConnector, type DrawCrossing, type DrawCrossover, type DrawIndustry, type DrawSignal, type DrawTrack, type DrawTurnout, type EditorBranch, type EditorControlPoint, type EditorCpSignal, type EditorCrossing, type EditorIndustry, type EditorState, type EditorTrack, type EditorTurnout, type EndplateBConfig, type EndplatePose, FREEMO_ENDPLATE_WIDTH_MIN_INCHES, FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES, type GeometryType, type IndustryLabelMode, MAIN2_TRACK_ID, MAIN_TRACK_ID, type ModuleFeatures, type ModuleFootprint, type ModuleFootprintInput, type ModuleGeometryInput, type ModuleSchematicDoc, type ModuleTrackRow, N_CAR_LENGTH_INCHES, N_SCALE_RATIO, type OutlineFace, type SchematicBlock, type SchematicControlPoint, type SchematicCrossing, type SchematicEndplate, type SchematicEndplateTrack, type SchematicIndustry, type SchematicSignal, type SchematicTrack, type SchematicTurnout, type SignalFacing, type SignalSide, type TrackConfig, type TrackRole, type TurnoutKind, asModuleSchematic, benchworkBand, benchworkOutline, buildCrossover, buildPassingSiding, buildTransition, carCapacity, deriveEndplatePoses, divergeSideForHand, docToState, emptyEditorState, endplateFaceSegments, endplateWidthInches, geometryTurnDegrees, inchesToScaleFeet, isLoopDoc, isTransitionTurnout, moduleCenterline, moduleFeatures, moduleFootprint, nextId, poseNeedsManual, poseOverridesFromDoc, sampleBenchworkOutline, samplePath, scaleFeetToInches, stateToDoc, trackPath };
package/dist/index.js CHANGED
@@ -43,6 +43,45 @@ function sampleBenchworkOutline(pts, segsPerArc = 20) {
43
43
  }
44
44
  return out;
45
45
  }
46
+ function samplePath(pts, segsPerArc = 20) {
47
+ const n = pts.length;
48
+ if (n < 2) return pts.map((p) => ({ x: p.x, y: p.y }));
49
+ const out = [];
50
+ for (let i = 0; i < n - 1; i++) {
51
+ const p0 = pts[i];
52
+ const p1 = pts[i + 1];
53
+ out.push({ x: p0.x, y: p0.y });
54
+ const bulge = p0.bulge ?? 0;
55
+ if (!bulge) continue;
56
+ const dx = p1.x - p0.x;
57
+ const dy = p1.y - p0.y;
58
+ const c = Math.hypot(dx, dy);
59
+ if (c < 1e-6) continue;
60
+ const nx = -dy / c;
61
+ const ny = dx / c;
62
+ const mid = { x: (p0.x + p1.x) / 2 + nx * bulge, y: (p0.y + p1.y) / 2 + ny * bulge };
63
+ const circ = circleThrough(p0, mid, p1);
64
+ if (!circ) continue;
65
+ const a0 = Math.atan2(p0.y - circ.cy, p0.x - circ.cx);
66
+ const am = Math.atan2(mid.y - circ.cy, mid.x - circ.cx);
67
+ const a1 = Math.atan2(p1.y - circ.cy, p1.x - circ.cx);
68
+ const sweep = arcSweep(a0, a1, am);
69
+ for (let s = 1; s < segsPerArc; s++) {
70
+ const a = a0 + sweep * s / segsPerArc;
71
+ out.push({ x: circ.cx + circ.r * Math.cos(a), y: circ.cy + circ.r * Math.sin(a) });
72
+ }
73
+ }
74
+ out.push({ x: pts[n - 1].x, y: pts[n - 1].y });
75
+ return out;
76
+ }
77
+ function trackPath(path) {
78
+ const pts = (path ?? []).filter((p) => p && Number.isFinite(p.x) && Number.isFinite(p.y)).map((p) => ({
79
+ x: p.x,
80
+ y: p.y,
81
+ ...Number.isFinite(p.bulge) && p.bulge ? { bulge: p.bulge } : {}
82
+ }));
83
+ return pts.length >= 2 ? pts : null;
84
+ }
46
85
  function circleThrough(a, b, c) {
47
86
  const d = 2 * (a.x * (b.y - c.y) + b.x * (c.y - a.y) + c.x * (a.y - b.y));
48
87
  if (Math.abs(d) < 1e-9) return null;
@@ -65,6 +104,8 @@ function arcSweep(a0, a1, am) {
65
104
  }
66
105
  var DEG_FP = Math.PI / 180;
67
106
  function moduleCenterline(input) {
107
+ const drawn = trackPath(input.mainPath);
108
+ if (drawn) return samplePath(drawn);
68
109
  const L = input.lengthInches > 0 ? input.lengthInches : 24;
69
110
  const gt = input.geometryType;
70
111
  if (gt === "dead_end") return [{ x: 0, y: 0 }];
@@ -174,7 +215,8 @@ function emptyEditorState(lengthInches) {
174
215
  endplateWidths: {},
175
216
  outline: [],
176
217
  controlPoints: [],
177
- industries: []
218
+ industries: [],
219
+ mainPath: []
178
220
  };
179
221
  }
180
222
  function isTransitionTurnout(t) {
@@ -311,7 +353,8 @@ function stateToDoc(state, recordNumber) {
311
353
  moduleTrackId: t.moduleTrackId,
312
354
  trackName: t.trackName || void 0,
313
355
  capacityFeet: Math.round(inchesToScaleFeet(Math.abs(t.toPos - t.fromPos))),
314
- ...state.loop && t.inLoop ? { inLoop: true } : {}
356
+ ...state.loop && t.inLoop ? { inLoop: true } : {},
357
+ ...t.path && t.path.length >= 2 ? { path: t.path } : {}
315
358
  }))
316
359
  ],
317
360
  turnouts: state.turnouts.map((t) => ({
@@ -320,7 +363,8 @@ function stateToDoc(state, recordNumber) {
320
363
  onTrack: t.onTrack,
321
364
  divergeTrack: t.divergeTrack,
322
365
  kind: t.kind,
323
- name: t.name || void 0
366
+ name: t.name || void 0,
367
+ ...t.size ? { size: t.size } : {}
324
368
  })),
325
369
  ...state.crossings.length > 0 ? {
326
370
  crossings: state.crossings.map((x) => ({
@@ -361,7 +405,9 @@ function stateToDoc(state, recordNumber) {
361
405
  } : {},
362
406
  // Benchwork footprint outline (module-local inches); only when it's a real
363
407
  // ring (≥ 3 vertices).
364
- ...state.outline.length >= 3 ? { outline: state.outline } : {}
408
+ ...state.outline.length >= 3 ? { outline: state.outline } : {},
409
+ // Authored mainline path (module-local inches); only when it's a real path.
410
+ ...state.mainPath.length >= 2 ? { mainPath: state.mainPath } : {}
365
411
  };
366
412
  }
367
413
  function docToState(doc, fallbackLength, moduleTracks = []) {
@@ -391,7 +437,9 @@ function docToState(doc, fallbackLength, moduleTracks = []) {
391
437
  toPos: t.toPos != null ? sc(t.toPos) : len,
392
438
  moduleTrackId,
393
439
  trackName: t.trackName ?? nameOf(moduleTrackId),
394
- ...t.inLoop ? { inLoop: true } : {}
440
+ ...t.inLoop ? { inLoop: true } : {},
441
+ // Authored path kept as-drawn (a physical shape, not rescaled with length).
442
+ ...trackPath(t.path) ? { path: trackPath(t.path) } : {}
395
443
  });
396
444
  }
397
445
  }
@@ -440,6 +488,7 @@ function docToState(doc, fallbackLength, moduleTracks = []) {
440
488
  y: p.y,
441
489
  ...Number.isFinite(p.bulge) && p.bulge ? { bulge: p.bulge } : {}
442
490
  }));
491
+ const mainPath = trackPath(d.mainPath) ?? [];
443
492
  return {
444
493
  lengthInches: len,
445
494
  loop,
@@ -456,6 +505,7 @@ function docToState(doc, fallbackLength, moduleTracks = []) {
456
505
  poseOverrides,
457
506
  endplateWidths,
458
507
  outline,
508
+ mainPath,
459
509
  crossings: (d.crossings ?? []).map((x) => ({
460
510
  id: x.id,
461
511
  name: x.name ?? "",
@@ -470,7 +520,8 @@ function docToState(doc, fallbackLength, moduleTracks = []) {
470
520
  pos: sc(t.pos),
471
521
  onTrack: t.onTrack,
472
522
  divergeTrack: t.divergeTrack,
473
- kind: t.kind ?? "right"
523
+ kind: t.kind ?? "right",
524
+ ...t.size ? { size: t.size } : {}
474
525
  })),
475
526
  controlPoints: readControlPoints(d, sc),
476
527
  industries: (d.industries ?? []).map((ind) => ({
@@ -942,6 +993,6 @@ function poseOverridesFromDoc(doc) {
942
993
  return out;
943
994
  }
944
995
 
945
- export { FREEMO_ENDPLATE_WIDTH_MIN_INCHES, FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES, MAIN2_TRACK_ID, MAIN_TRACK_ID, N_CAR_LENGTH_INCHES, N_SCALE_RATIO, asModuleSchematic, benchworkBand, benchworkOutline, buildCrossover, buildPassingSiding, buildTransition, carCapacity, deriveEndplatePoses, divergeSideForHand, docToState, emptyEditorState, endplateFaceSegments, endplateWidthInches, geometryTurnDegrees, inchesToScaleFeet, isLoopDoc, isTransitionTurnout, moduleCenterline, moduleFeatures, moduleFootprint, nextId, poseNeedsManual, poseOverridesFromDoc, sampleBenchworkOutline, scaleFeetToInches, stateToDoc };
996
+ export { FREEMO_ENDPLATE_WIDTH_MIN_INCHES, FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES, MAIN2_TRACK_ID, MAIN_TRACK_ID, N_CAR_LENGTH_INCHES, N_SCALE_RATIO, asModuleSchematic, benchworkBand, benchworkOutline, buildCrossover, buildPassingSiding, buildTransition, carCapacity, deriveEndplatePoses, divergeSideForHand, docToState, emptyEditorState, endplateFaceSegments, endplateWidthInches, geometryTurnDegrees, inchesToScaleFeet, isLoopDoc, isTransitionTurnout, moduleCenterline, moduleFeatures, moduleFootprint, nextId, poseNeedsManual, poseOverridesFromDoc, sampleBenchworkOutline, samplePath, scaleFeetToInches, stateToDoc, trackPath };
946
997
  //# sourceMappingURL=index.js.map
947
998
  //# sourceMappingURL=index.js.map