@willcgage/module-schematic 0.35.0 → 0.37.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
@@ -263,6 +263,11 @@ interface ModuleSchematicDoc {
263
263
  * Absent = the module keeps using its own `outline` exactly as before; this
264
264
  * is purely additive and nothing migrates on read. */
265
265
  sections?: SchematicSection[];
266
+ /** Which compass axis the module actually runs along. The dispatcher panel
267
+ * always draws LEFT→RIGHT — that's what makes a layout of modules read as one
268
+ * strip — but a module built north/south should still be labelled N and S.
269
+ * Absent = east/west. */
270
+ orientation?: ModuleOrientation;
266
271
  /** @deprecated pre-grouping flat signals; read for back-compat. */
267
272
  signals?: SchematicSignal[];
268
273
  /** Authored mainline centre-line (module-local inches, open path with arcs).
@@ -279,6 +284,20 @@ interface BenchworkPoint {
279
284
  y: number;
280
285
  bulge?: number;
281
286
  }
287
+ /** The compass axis a module runs along. Endplate A is the first label,
288
+ * endplate B the second, so A→B reads left→right in the dispatcher panel
289
+ * whichever axis it is. */
290
+ type ModuleOrientation = "east-west" | "north-south";
291
+ /** The compass letters for a module's two ends, A first. */
292
+ declare function endLabels(o: ModuleOrientation | null | undefined): {
293
+ a: string;
294
+ b: string;
295
+ };
296
+ /** …spelled out, for a caption like "derived, South → North". */
297
+ declare function endLabelsLong(o: ModuleOrientation | null | undefined): {
298
+ a: string;
299
+ b: string;
300
+ };
282
301
  /** One bench-work section of a module (#96 phase 2). */
283
302
  interface SectionFootprint {
284
303
  id: string;
@@ -435,6 +454,68 @@ declare function sectionSpans(doc: {
435
454
  fromPos: number;
436
455
  toPos: number;
437
456
  }[];
457
+ /** A position expressed against the board it sits on, rather than as inches
458
+ * from endplate A (#109). */
459
+ interface SectionRelativePos {
460
+ sectionId: string;
461
+ /** Inches from that section's own west end. */
462
+ offsetInches: number;
463
+ }
464
+ /**
465
+ * Section spans that ALWAYS cover the whole module. The owner's insight is
466
+ * what makes #109 tractable: every module has at least one section, even if
467
+ * that one section IS the whole module. So a module with no authored sections
468
+ * gets a single implicit span 0→length, every position falls inside exactly
469
+ * one span, and absolute ↔ relative becomes a total, lossless mapping with no
470
+ * un-convertible module and no orphan positions.
471
+ *
472
+ * The last span is also stretched to the module length when the sections come
473
+ * up short, so a position past the end still lands somewhere real.
474
+ */
475
+ declare function sectionSpansOrWhole(doc: {
476
+ sections?: SchematicSection[] | null;
477
+ } | null | undefined, lengthInches: number): {
478
+ id: string;
479
+ name?: string;
480
+ fromPos: number;
481
+ toPos: number;
482
+ }[];
483
+ /** The id a module with no authored sections uses for its single implicit one. */
484
+ declare const WHOLE_MODULE_SECTION_ID = "module";
485
+ /**
486
+ * Absolute inches → the board it sits on plus an offset along it (#109).
487
+ * Total: given spans from `sectionSpansOrWhole`, every position resolves.
488
+ *
489
+ * A position exactly ON a joint is assigned to the section that STARTS there,
490
+ * at offset 0 — a joint is the west end of the next board, and that keeps the
491
+ * mapping single-valued. The module's own east end is the exception: nothing
492
+ * starts there, so it belongs to the last board.
493
+ */
494
+ declare function toSectionRelative(pos: number, spans: {
495
+ id: string;
496
+ fromPos: number;
497
+ toPos: number;
498
+ }[]): SectionRelativePos | null;
499
+ /** …and back. Null when the section is gone — which is the caller's cue that
500
+ * the thing it positioned has lost its board (#96 phase 3). */
501
+ declare function fromSectionRelative(rel: SectionRelativePos, spans: {
502
+ id: string;
503
+ fromPos: number;
504
+ toPos: number;
505
+ }[]): number | null;
506
+ /** Re-derive an absolute position after the sections have moved: read it
507
+ * against the OLD spans, write it against the NEW ones. This is the whole
508
+ * point of #109 — reorder or resize a board and everything on it comes along
509
+ * instead of silently pointing at a different board. */
510
+ declare function remapPos(pos: number, before: {
511
+ id: string;
512
+ fromPos: number;
513
+ toPos: number;
514
+ }[], after: {
515
+ id: string;
516
+ fromPos: number;
517
+ toPos: number;
518
+ }[]): number | null;
438
519
  /** The joints implied by the sections — the interior boundaries, in inches from
439
520
  * endplate A. Replaces the authored `sectionBreaks` for a sectioned module. */
440
521
  declare function sectionBreaksFromSections(doc: {
@@ -709,6 +790,8 @@ interface EditorState {
709
790
  /** The module's sections as named objects, each optionally carrying its own
710
791
  * outline (#96 phase 2). Empty = fall back to `outline` + `sectionBreaks`. */
711
792
  sections: SchematicSection[];
793
+ /** Compass axis the module runs along; the panel still draws left→right. */
794
+ orientation: ModuleOrientation;
712
795
  controlPoints: EditorControlPoint[];
713
796
  /** Rail-served industries — car-spot spans on a track (#industries). */
714
797
  industries: EditorIndustry[];
@@ -1000,4 +1083,4 @@ declare function poseOverridesFromDoc(doc: ModuleSchematicDoc): Record<string, {
1000
1083
  heading: number;
1001
1084
  }>;
1002
1085
 
1003
- 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, type EndplateWidthIssue, FREEMO_ENDPLATE_TRACK_FASCIA_CLEARANCE_INCHES, FREEMO_ENDPLATE_WIDTH_MIN_INCHES, FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES, FREEMO_TRACK_SPACING_INCHES, type GeometryType, type IndustryLabelMode, type IndustrySpot, 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 SchematicSection, type SchematicSignal, type SchematicTrack, type SchematicTurnout, type SectionAdjacency, type SectionFootprint, type SignalFacing, type SignalSide, type TrackConfig, type TrackRole, type TurnoutKind, asModuleSchematic, benchworkBand, benchworkOutline, buildCrossover, buildPassingSiding, buildTransition, carCapacity, checkEndplateWidth, deriveEndplatePoses, divergeSideForHand, docToState, emptyEditorState, endplateFaceSegments, endplateTrackOffsetFor, endplateTrackOffsetInches, endplateWidthInches, geometryTurnDegrees, inchesToScaleFeet, isLoopDoc, isTransitionTurnout, moduleCenterline, moduleFeatures, moduleFootprint, moduleLengthFromSections, moduleSections, nextId, poseNeedsManual, poseOverridesFromDoc, sampleBenchworkOutline, samplePath, scaleFeetToInches, sectionAdjacency, sectionBand, sectionBreaksFromSections, sectionComponents, sectionFootprints, sectionNeighbours, sectionSpans, sectionedCenterline, sectionedEndPose, sliceCenterline, stateToDoc, trackPath };
1086
+ 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, type EndplateWidthIssue, FREEMO_ENDPLATE_TRACK_FASCIA_CLEARANCE_INCHES, FREEMO_ENDPLATE_WIDTH_MIN_INCHES, FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES, FREEMO_TRACK_SPACING_INCHES, type GeometryType, type IndustryLabelMode, type IndustrySpot, MAIN2_TRACK_ID, MAIN_TRACK_ID, type ModuleFeatures, type ModuleFootprint, type ModuleFootprintInput, type ModuleGeometryInput, type ModuleOrientation, 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 SchematicSection, type SchematicSignal, type SchematicTrack, type SchematicTurnout, type SectionAdjacency, type SectionFootprint, type SectionRelativePos, type SignalFacing, type SignalSide, type TrackConfig, type TrackRole, type TurnoutKind, WHOLE_MODULE_SECTION_ID, asModuleSchematic, benchworkBand, benchworkOutline, buildCrossover, buildPassingSiding, buildTransition, carCapacity, checkEndplateWidth, deriveEndplatePoses, divergeSideForHand, docToState, emptyEditorState, endLabels, endLabelsLong, endplateFaceSegments, endplateTrackOffsetFor, endplateTrackOffsetInches, endplateWidthInches, fromSectionRelative, geometryTurnDegrees, inchesToScaleFeet, isLoopDoc, isTransitionTurnout, moduleCenterline, moduleFeatures, moduleFootprint, moduleLengthFromSections, moduleSections, nextId, poseNeedsManual, poseOverridesFromDoc, remapPos, sampleBenchworkOutline, samplePath, scaleFeetToInches, sectionAdjacency, sectionBand, sectionBreaksFromSections, sectionComponents, sectionFootprints, sectionNeighbours, sectionSpans, sectionSpansOrWhole, sectionedCenterline, sectionedEndPose, sliceCenterline, stateToDoc, toSectionRelative, trackPath };
package/dist/index.d.ts CHANGED
@@ -263,6 +263,11 @@ interface ModuleSchematicDoc {
263
263
  * Absent = the module keeps using its own `outline` exactly as before; this
264
264
  * is purely additive and nothing migrates on read. */
265
265
  sections?: SchematicSection[];
266
+ /** Which compass axis the module actually runs along. The dispatcher panel
267
+ * always draws LEFT→RIGHT — that's what makes a layout of modules read as one
268
+ * strip — but a module built north/south should still be labelled N and S.
269
+ * Absent = east/west. */
270
+ orientation?: ModuleOrientation;
266
271
  /** @deprecated pre-grouping flat signals; read for back-compat. */
267
272
  signals?: SchematicSignal[];
268
273
  /** Authored mainline centre-line (module-local inches, open path with arcs).
@@ -279,6 +284,20 @@ interface BenchworkPoint {
279
284
  y: number;
280
285
  bulge?: number;
281
286
  }
287
+ /** The compass axis a module runs along. Endplate A is the first label,
288
+ * endplate B the second, so A→B reads left→right in the dispatcher panel
289
+ * whichever axis it is. */
290
+ type ModuleOrientation = "east-west" | "north-south";
291
+ /** The compass letters for a module's two ends, A first. */
292
+ declare function endLabels(o: ModuleOrientation | null | undefined): {
293
+ a: string;
294
+ b: string;
295
+ };
296
+ /** …spelled out, for a caption like "derived, South → North". */
297
+ declare function endLabelsLong(o: ModuleOrientation | null | undefined): {
298
+ a: string;
299
+ b: string;
300
+ };
282
301
  /** One bench-work section of a module (#96 phase 2). */
283
302
  interface SectionFootprint {
284
303
  id: string;
@@ -435,6 +454,68 @@ declare function sectionSpans(doc: {
435
454
  fromPos: number;
436
455
  toPos: number;
437
456
  }[];
457
+ /** A position expressed against the board it sits on, rather than as inches
458
+ * from endplate A (#109). */
459
+ interface SectionRelativePos {
460
+ sectionId: string;
461
+ /** Inches from that section's own west end. */
462
+ offsetInches: number;
463
+ }
464
+ /**
465
+ * Section spans that ALWAYS cover the whole module. The owner's insight is
466
+ * what makes #109 tractable: every module has at least one section, even if
467
+ * that one section IS the whole module. So a module with no authored sections
468
+ * gets a single implicit span 0→length, every position falls inside exactly
469
+ * one span, and absolute ↔ relative becomes a total, lossless mapping with no
470
+ * un-convertible module and no orphan positions.
471
+ *
472
+ * The last span is also stretched to the module length when the sections come
473
+ * up short, so a position past the end still lands somewhere real.
474
+ */
475
+ declare function sectionSpansOrWhole(doc: {
476
+ sections?: SchematicSection[] | null;
477
+ } | null | undefined, lengthInches: number): {
478
+ id: string;
479
+ name?: string;
480
+ fromPos: number;
481
+ toPos: number;
482
+ }[];
483
+ /** The id a module with no authored sections uses for its single implicit one. */
484
+ declare const WHOLE_MODULE_SECTION_ID = "module";
485
+ /**
486
+ * Absolute inches → the board it sits on plus an offset along it (#109).
487
+ * Total: given spans from `sectionSpansOrWhole`, every position resolves.
488
+ *
489
+ * A position exactly ON a joint is assigned to the section that STARTS there,
490
+ * at offset 0 — a joint is the west end of the next board, and that keeps the
491
+ * mapping single-valued. The module's own east end is the exception: nothing
492
+ * starts there, so it belongs to the last board.
493
+ */
494
+ declare function toSectionRelative(pos: number, spans: {
495
+ id: string;
496
+ fromPos: number;
497
+ toPos: number;
498
+ }[]): SectionRelativePos | null;
499
+ /** …and back. Null when the section is gone — which is the caller's cue that
500
+ * the thing it positioned has lost its board (#96 phase 3). */
501
+ declare function fromSectionRelative(rel: SectionRelativePos, spans: {
502
+ id: string;
503
+ fromPos: number;
504
+ toPos: number;
505
+ }[]): number | null;
506
+ /** Re-derive an absolute position after the sections have moved: read it
507
+ * against the OLD spans, write it against the NEW ones. This is the whole
508
+ * point of #109 — reorder or resize a board and everything on it comes along
509
+ * instead of silently pointing at a different board. */
510
+ declare function remapPos(pos: number, before: {
511
+ id: string;
512
+ fromPos: number;
513
+ toPos: number;
514
+ }[], after: {
515
+ id: string;
516
+ fromPos: number;
517
+ toPos: number;
518
+ }[]): number | null;
438
519
  /** The joints implied by the sections — the interior boundaries, in inches from
439
520
  * endplate A. Replaces the authored `sectionBreaks` for a sectioned module. */
440
521
  declare function sectionBreaksFromSections(doc: {
@@ -709,6 +790,8 @@ interface EditorState {
709
790
  /** The module's sections as named objects, each optionally carrying its own
710
791
  * outline (#96 phase 2). Empty = fall back to `outline` + `sectionBreaks`. */
711
792
  sections: SchematicSection[];
793
+ /** Compass axis the module runs along; the panel still draws left→right. */
794
+ orientation: ModuleOrientation;
712
795
  controlPoints: EditorControlPoint[];
713
796
  /** Rail-served industries — car-spot spans on a track (#industries). */
714
797
  industries: EditorIndustry[];
@@ -1000,4 +1083,4 @@ declare function poseOverridesFromDoc(doc: ModuleSchematicDoc): Record<string, {
1000
1083
  heading: number;
1001
1084
  }>;
1002
1085
 
1003
- 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, type EndplateWidthIssue, FREEMO_ENDPLATE_TRACK_FASCIA_CLEARANCE_INCHES, FREEMO_ENDPLATE_WIDTH_MIN_INCHES, FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES, FREEMO_TRACK_SPACING_INCHES, type GeometryType, type IndustryLabelMode, type IndustrySpot, 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 SchematicSection, type SchematicSignal, type SchematicTrack, type SchematicTurnout, type SectionAdjacency, type SectionFootprint, type SignalFacing, type SignalSide, type TrackConfig, type TrackRole, type TurnoutKind, asModuleSchematic, benchworkBand, benchworkOutline, buildCrossover, buildPassingSiding, buildTransition, carCapacity, checkEndplateWidth, deriveEndplatePoses, divergeSideForHand, docToState, emptyEditorState, endplateFaceSegments, endplateTrackOffsetFor, endplateTrackOffsetInches, endplateWidthInches, geometryTurnDegrees, inchesToScaleFeet, isLoopDoc, isTransitionTurnout, moduleCenterline, moduleFeatures, moduleFootprint, moduleLengthFromSections, moduleSections, nextId, poseNeedsManual, poseOverridesFromDoc, sampleBenchworkOutline, samplePath, scaleFeetToInches, sectionAdjacency, sectionBand, sectionBreaksFromSections, sectionComponents, sectionFootprints, sectionNeighbours, sectionSpans, sectionedCenterline, sectionedEndPose, sliceCenterline, stateToDoc, trackPath };
1086
+ 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, type EndplateWidthIssue, FREEMO_ENDPLATE_TRACK_FASCIA_CLEARANCE_INCHES, FREEMO_ENDPLATE_WIDTH_MIN_INCHES, FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES, FREEMO_TRACK_SPACING_INCHES, type GeometryType, type IndustryLabelMode, type IndustrySpot, MAIN2_TRACK_ID, MAIN_TRACK_ID, type ModuleFeatures, type ModuleFootprint, type ModuleFootprintInput, type ModuleGeometryInput, type ModuleOrientation, 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 SchematicSection, type SchematicSignal, type SchematicTrack, type SchematicTurnout, type SectionAdjacency, type SectionFootprint, type SectionRelativePos, type SignalFacing, type SignalSide, type TrackConfig, type TrackRole, type TurnoutKind, WHOLE_MODULE_SECTION_ID, asModuleSchematic, benchworkBand, benchworkOutline, buildCrossover, buildPassingSiding, buildTransition, carCapacity, checkEndplateWidth, deriveEndplatePoses, divergeSideForHand, docToState, emptyEditorState, endLabels, endLabelsLong, endplateFaceSegments, endplateTrackOffsetFor, endplateTrackOffsetInches, endplateWidthInches, fromSectionRelative, geometryTurnDegrees, inchesToScaleFeet, isLoopDoc, isTransitionTurnout, moduleCenterline, moduleFeatures, moduleFootprint, moduleLengthFromSections, moduleSections, nextId, poseNeedsManual, poseOverridesFromDoc, remapPos, sampleBenchworkOutline, samplePath, scaleFeetToInches, sectionAdjacency, sectionBand, sectionBreaksFromSections, sectionComponents, sectionFootprints, sectionNeighbours, sectionSpans, sectionSpansOrWhole, sectionedCenterline, sectionedEndPose, sliceCenterline, stateToDoc, toSectionRelative, trackPath };
package/dist/index.js CHANGED
@@ -7,6 +7,12 @@ function endplateWidthInches(ep) {
7
7
  const w = ep?.widthInches;
8
8
  return typeof w === "number" && w > 0 ? w : FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES;
9
9
  }
10
+ function endLabels(o) {
11
+ return o === "north-south" ? { a: "S", b: "N" } : { a: "W", b: "E" };
12
+ }
13
+ function endLabelsLong(o) {
14
+ return o === "north-south" ? { a: "South", b: "North" } : { a: "West", b: "East" };
15
+ }
10
16
  function benchworkOutline(doc) {
11
17
  const pts = (doc?.outline ?? []).filter((p) => p && Number.isFinite(p.x) && Number.isFinite(p.y)).map((p) => ({
12
18
  x: p.x,
@@ -205,6 +211,35 @@ function sectionSpans(doc) {
205
211
  }
206
212
  return out;
207
213
  }
214
+ function sectionSpansOrWhole(doc, lengthInches) {
215
+ const L = lengthInches > 0 ? lengthInches : 0;
216
+ const spans = sectionSpans(doc);
217
+ if (!spans.length) return [{ id: WHOLE_MODULE_SECTION_ID, fromPos: 0, toPos: L }];
218
+ const out = spans.map((sp) => ({ ...sp }));
219
+ const last = out[out.length - 1];
220
+ if (L > last.toPos) last.toPos = L;
221
+ return out;
222
+ }
223
+ var WHOLE_MODULE_SECTION_ID = "module";
224
+ function toSectionRelative(pos, spans) {
225
+ if (!spans.length) return null;
226
+ const p = Math.max(spans[0].fromPos, Math.min(spans[spans.length - 1].toPos, pos));
227
+ for (const sp of spans) {
228
+ if (p >= sp.fromPos && p < sp.toPos)
229
+ return { sectionId: sp.id, offsetInches: round3(p - sp.fromPos) };
230
+ }
231
+ const last = spans[spans.length - 1];
232
+ return { sectionId: last.id, offsetInches: round3(last.toPos - last.fromPos) };
233
+ }
234
+ function fromSectionRelative(rel, spans) {
235
+ const sp = spans.find((x) => x.id === rel.sectionId);
236
+ if (!sp) return null;
237
+ return round3(sp.fromPos + Math.max(0, Math.min(sp.toPos - sp.fromPos, rel.offsetInches)));
238
+ }
239
+ function remapPos(pos, before, after) {
240
+ const rel = toSectionRelative(pos, before);
241
+ return rel ? fromSectionRelative(rel, after) : null;
242
+ }
208
243
  function sectionBreaksFromSections(doc) {
209
244
  const spans = sectionSpans(doc);
210
245
  return spans.slice(0, -1).map((sp) => sp.toPos);
@@ -481,6 +516,7 @@ function checkEndplateWidth(input) {
481
516
  return issues;
482
517
  }
483
518
  var round2 = (n) => Math.round(n * 100) / 100;
519
+ var round3 = (n) => Math.round(n * 1e3) / 1e3;
484
520
  function endplateWidthFor(widths, id) {
485
521
  const w = widths?.[id];
486
522
  return typeof w === "number" && w > 0 ? w : FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES;
@@ -527,6 +563,7 @@ function emptyEditorState(lengthInches) {
527
563
  outline: [],
528
564
  sectionBreaks: [],
529
565
  sections: [],
566
+ orientation: "east-west",
530
567
  controlPoints: [],
531
568
  industries: [],
532
569
  mainPath: []
@@ -763,6 +800,9 @@ function stateToDoc(state, recordNumber) {
763
800
  // Sections as objects — emitted only once the owner has some, so a module
764
801
  // that never used them keeps exactly the doc it had before (#96 phase 2).
765
802
  ...state.sections.length ? { sections: moduleSections({ sections: state.sections }) } : {},
803
+ // Emitted only when it isn't the east/west default, so existing docs are
804
+ // byte-identical.
805
+ ...state.orientation === "north-south" ? { orientation: state.orientation } : {},
766
806
  // Authored mainline path (module-local inches); only when it's a real path.
767
807
  ...state.mainPath.length >= 2 ? { mainPath: state.mainPath } : {}
768
808
  };
@@ -869,6 +909,7 @@ function docToState(doc, fallbackLength, moduleTracks = []) {
869
909
  outline,
870
910
  sectionBreaks: (d.sectionBreaks ?? []).filter((n) => Number.isFinite(n)).map((n) => sc(n)),
871
911
  sections: moduleSections(d),
912
+ orientation: d.orientation === "north-south" ? "north-south" : "east-west",
872
913
  mainPath,
873
914
  crossings: (d.crossings ?? []).map((x) => ({
874
915
  id: x.id,
@@ -1383,6 +1424,6 @@ function poseOverridesFromDoc(doc) {
1383
1424
  return out;
1384
1425
  }
1385
1426
 
1386
- export { 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, asModuleSchematic, benchworkBand, benchworkOutline, buildCrossover, buildPassingSiding, buildTransition, carCapacity, checkEndplateWidth, deriveEndplatePoses, divergeSideForHand, docToState, emptyEditorState, endplateFaceSegments, endplateTrackOffsetFor, endplateTrackOffsetInches, endplateWidthInches, geometryTurnDegrees, inchesToScaleFeet, isLoopDoc, isTransitionTurnout, moduleCenterline, moduleFeatures, moduleFootprint, moduleLengthFromSections, moduleSections, nextId, poseNeedsManual, poseOverridesFromDoc, sampleBenchworkOutline, samplePath, scaleFeetToInches, sectionAdjacency, sectionBand, sectionBreaksFromSections, sectionComponents, sectionFootprints, sectionNeighbours, sectionSpans, sectionedCenterline, sectionedEndPose, sliceCenterline, stateToDoc, trackPath };
1427
+ export { 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, WHOLE_MODULE_SECTION_ID, asModuleSchematic, benchworkBand, benchworkOutline, buildCrossover, buildPassingSiding, buildTransition, carCapacity, checkEndplateWidth, deriveEndplatePoses, divergeSideForHand, docToState, emptyEditorState, endLabels, endLabelsLong, endplateFaceSegments, endplateTrackOffsetFor, endplateTrackOffsetInches, endplateWidthInches, fromSectionRelative, geometryTurnDegrees, inchesToScaleFeet, isLoopDoc, isTransitionTurnout, moduleCenterline, moduleFeatures, moduleFootprint, moduleLengthFromSections, moduleSections, nextId, poseNeedsManual, poseOverridesFromDoc, remapPos, sampleBenchworkOutline, samplePath, scaleFeetToInches, sectionAdjacency, sectionBand, sectionBreaksFromSections, sectionComponents, sectionFootprints, sectionNeighbours, sectionSpans, sectionSpansOrWhole, sectionedCenterline, sectionedEndPose, sliceCenterline, stateToDoc, toSectionRelative, trackPath };
1387
1428
  //# sourceMappingURL=index.js.map
1388
1429
  //# sourceMappingURL=index.js.map