@willcgage/module-schematic 0.20.0 → 0.22.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
@@ -149,16 +149,27 @@ type IndustryLabelMode = "none" | "cars" | "inches";
149
149
  * offset to `side`. The span length gives its car capacity; the dispatcher and
150
150
  * crews read where cars set out. Mirrors a `freemon_industries` row.
151
151
  */
152
+ /** One car-spot span of an industry on a track — an industry may have several
153
+ * (a house track serving one customer across multiple spot tracks, #54). */
154
+ interface IndustrySpot {
155
+ track: string;
156
+ fromPos: number;
157
+ toPos: number;
158
+ side?: SignalSide;
159
+ }
152
160
  interface SchematicIndustry {
153
161
  id: string;
154
162
  name: string;
155
163
  /** Industry type value from the lookup (e.g. "team_track", "grain"). */
156
164
  type?: string | null;
157
- /** The track this industry spots cars on (a spur/siding id, or the main). */
165
+ /** The primary track this industry spots cars on (a spur/siding id or main).
166
+ * Additional spots (other tracks) live in `spots`. */
158
167
  track: string;
159
- /** The car-spot span along that track, inches from endplate A. */
168
+ /** The primary car-spot span along `track`, inches from endplate A. */
160
169
  fromPos: number;
161
170
  toPos: number;
171
+ /** Extra car-spot spans on other tracks — the industry's house-track spots. */
172
+ spots?: IndustrySpot[];
162
173
  /** Which side of the track the building + label sit on. */
163
174
  side?: SignalSide;
164
175
  /** Secondary readout at the label — a car count, a length, or none. */
@@ -200,6 +211,10 @@ interface ModuleSchematicDoc {
200
211
  * +y up). Stored as an open ring; renderers close it. Absent = derive an
201
212
  * approximate band from the endplate widths. */
202
213
  outline?: BenchworkPoint[];
214
+ /** Internal section joints — inches from endplate A where the module's boards
215
+ * split into sections. Operationally one unit; these mark construction/transport
216
+ * seams (exempt from the end-interface standards). Empty/absent = one section. */
217
+ sectionBreaks?: number[];
203
218
  /** @deprecated pre-grouping flat signals; read for back-compat. */
204
219
  signals?: SchematicSignal[];
205
220
  /** Authored mainline centre-line (module-local inches, open path with arcs).
@@ -375,6 +390,8 @@ interface EditorIndustry {
375
390
  track: string;
376
391
  fromPos: number;
377
392
  toPos: number;
393
+ /** Extra car-spot spans on other tracks (house-track spots, #54). */
394
+ spots: IndustrySpot[];
378
395
  side: SignalSide;
379
396
  labelMode: IndustryLabelMode;
380
397
  carTypes: string[];
@@ -425,6 +442,9 @@ interface EditorState {
425
442
  * (endplate A's track point at the origin, mainline +x, perpendicular +y up).
426
443
  * Empty = no authored outline (fall back to the endplate-width band). */
427
444
  outline: BenchworkPoint[];
445
+ /** Internal section joints — inches from endplate A where the boards split
446
+ * into sections. Empty = a single section (#48). */
447
+ sectionBreaks: number[];
428
448
  controlPoints: EditorControlPoint[];
429
449
  /** Rail-served industries — car-spot spans on a track (#industries). */
430
450
  industries: EditorIndustry[];
@@ -709,4 +729,4 @@ declare function poseOverridesFromDoc(doc: ModuleSchematicDoc): Record<string, {
709
729
  heading: number;
710
730
  }>;
711
731
 
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 };
732
+ 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, 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 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
@@ -149,16 +149,27 @@ type IndustryLabelMode = "none" | "cars" | "inches";
149
149
  * offset to `side`. The span length gives its car capacity; the dispatcher and
150
150
  * crews read where cars set out. Mirrors a `freemon_industries` row.
151
151
  */
152
+ /** One car-spot span of an industry on a track — an industry may have several
153
+ * (a house track serving one customer across multiple spot tracks, #54). */
154
+ interface IndustrySpot {
155
+ track: string;
156
+ fromPos: number;
157
+ toPos: number;
158
+ side?: SignalSide;
159
+ }
152
160
  interface SchematicIndustry {
153
161
  id: string;
154
162
  name: string;
155
163
  /** Industry type value from the lookup (e.g. "team_track", "grain"). */
156
164
  type?: string | null;
157
- /** The track this industry spots cars on (a spur/siding id, or the main). */
165
+ /** The primary track this industry spots cars on (a spur/siding id or main).
166
+ * Additional spots (other tracks) live in `spots`. */
158
167
  track: string;
159
- /** The car-spot span along that track, inches from endplate A. */
168
+ /** The primary car-spot span along `track`, inches from endplate A. */
160
169
  fromPos: number;
161
170
  toPos: number;
171
+ /** Extra car-spot spans on other tracks — the industry's house-track spots. */
172
+ spots?: IndustrySpot[];
162
173
  /** Which side of the track the building + label sit on. */
163
174
  side?: SignalSide;
164
175
  /** Secondary readout at the label — a car count, a length, or none. */
@@ -200,6 +211,10 @@ interface ModuleSchematicDoc {
200
211
  * +y up). Stored as an open ring; renderers close it. Absent = derive an
201
212
  * approximate band from the endplate widths. */
202
213
  outline?: BenchworkPoint[];
214
+ /** Internal section joints — inches from endplate A where the module's boards
215
+ * split into sections. Operationally one unit; these mark construction/transport
216
+ * seams (exempt from the end-interface standards). Empty/absent = one section. */
217
+ sectionBreaks?: number[];
203
218
  /** @deprecated pre-grouping flat signals; read for back-compat. */
204
219
  signals?: SchematicSignal[];
205
220
  /** Authored mainline centre-line (module-local inches, open path with arcs).
@@ -375,6 +390,8 @@ interface EditorIndustry {
375
390
  track: string;
376
391
  fromPos: number;
377
392
  toPos: number;
393
+ /** Extra car-spot spans on other tracks (house-track spots, #54). */
394
+ spots: IndustrySpot[];
378
395
  side: SignalSide;
379
396
  labelMode: IndustryLabelMode;
380
397
  carTypes: string[];
@@ -425,6 +442,9 @@ interface EditorState {
425
442
  * (endplate A's track point at the origin, mainline +x, perpendicular +y up).
426
443
  * Empty = no authored outline (fall back to the endplate-width band). */
427
444
  outline: BenchworkPoint[];
445
+ /** Internal section joints — inches from endplate A where the boards split
446
+ * into sections. Empty = a single section (#48). */
447
+ sectionBreaks: number[];
428
448
  controlPoints: EditorControlPoint[];
429
449
  /** Rail-served industries — car-spot spans on a track (#industries). */
430
450
  industries: EditorIndustry[];
@@ -709,4 +729,4 @@ declare function poseOverridesFromDoc(doc: ModuleSchematicDoc): Record<string, {
709
729
  heading: number;
710
730
  }>;
711
731
 
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 };
732
+ 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, 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 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
@@ -214,6 +214,7 @@ function emptyEditorState(lengthInches) {
214
214
  poseOverrides: {},
215
215
  endplateWidths: {},
216
216
  outline: [],
217
+ sectionBreaks: [],
217
218
  controlPoints: [],
218
219
  industries: [],
219
220
  mainPath: []
@@ -397,6 +398,7 @@ function stateToDoc(state, recordNumber) {
397
398
  track: ind.track,
398
399
  fromPos: ind.fromPos,
399
400
  toPos: ind.toPos,
401
+ ...ind.spots?.length ? { spots: ind.spots } : {},
400
402
  side: ind.side,
401
403
  ...ind.labelMode && ind.labelMode !== "none" ? { labelMode: ind.labelMode } : {},
402
404
  ...ind.carTypes.length ? { carTypes: ind.carTypes } : {},
@@ -406,6 +408,8 @@ function stateToDoc(state, recordNumber) {
406
408
  // Benchwork footprint outline (module-local inches); only when it's a real
407
409
  // ring (≥ 3 vertices).
408
410
  ...state.outline.length >= 3 ? { outline: state.outline } : {},
411
+ // Internal section joints (inches from A), when the module has more than one.
412
+ ...state.sectionBreaks.length ? { sectionBreaks: state.sectionBreaks } : {},
409
413
  // Authored mainline path (module-local inches); only when it's a real path.
410
414
  ...state.mainPath.length >= 2 ? { mainPath: state.mainPath } : {}
411
415
  };
@@ -505,6 +509,7 @@ function docToState(doc, fallbackLength, moduleTracks = []) {
505
509
  poseOverrides,
506
510
  endplateWidths,
507
511
  outline,
512
+ sectionBreaks: (d.sectionBreaks ?? []).filter((n) => Number.isFinite(n)).map((n) => sc(n)),
508
513
  mainPath,
509
514
  crossings: (d.crossings ?? []).map((x) => ({
510
515
  id: x.id,
@@ -531,6 +536,12 @@ function docToState(doc, fallbackLength, moduleTracks = []) {
531
536
  track: ind.track,
532
537
  fromPos: sc(ind.fromPos ?? 0),
533
538
  toPos: ind.toPos != null ? sc(ind.toPos) : len,
539
+ spots: (ind.spots ?? []).map((s) => ({
540
+ track: s.track,
541
+ fromPos: sc(s.fromPos ?? 0),
542
+ toPos: s.toPos != null ? sc(s.toPos) : len,
543
+ ...s.side ? { side: s.side } : {}
544
+ })),
534
545
  side: ind.side ?? "above",
535
546
  labelMode: ind.labelMode ?? "none",
536
547
  carTypes: Array.isArray(ind.carTypes) ? ind.carTypes : [],
@@ -834,21 +845,27 @@ function moduleFeatures(doc) {
834
845
  posFrac: clampFrac(e.at.pos),
835
846
  side: e.at.side === "down" ? "down" : "up"
836
847
  }));
837
- const industries = (doc.industries ?? []).map((ind) => {
838
- const from = ind.fromPos ?? 0;
839
- const to = ind.toPos ?? len;
840
- return {
841
- id: ind.id,
842
- name: ind.name ?? "",
843
- type: ind.type ?? null,
844
- fromFrac: clampFrac(Math.min(from, to)),
845
- toFrac: clampFrac(Math.max(from, to)),
846
- lane: trackLane.get(ind.track) ?? 0,
847
- side: ind.side ?? "above",
848
- labelMode: ind.labelMode ?? "none",
849
- cars: carCapacity(from, to),
850
- carTypes: Array.isArray(ind.carTypes) ? ind.carTypes : []
851
- };
848
+ const industries = (doc.industries ?? []).flatMap((ind) => {
849
+ const spots = [
850
+ { track: ind.track, fromPos: ind.fromPos, toPos: ind.toPos, side: ind.side },
851
+ ...ind.spots ?? []
852
+ ];
853
+ return spots.map((sp, i) => {
854
+ const from = sp.fromPos ?? 0;
855
+ const to = sp.toPos ?? len;
856
+ return {
857
+ id: i === 0 ? ind.id : `${ind.id}-s${i}`,
858
+ name: ind.name ?? "",
859
+ type: ind.type ?? null,
860
+ fromFrac: clampFrac(Math.min(from, to)),
861
+ toFrac: clampFrac(Math.max(from, to)),
862
+ lane: trackLane.get(sp.track) ?? 0,
863
+ side: sp.side ?? "above",
864
+ labelMode: ind.labelMode ?? "none",
865
+ cars: carCapacity(from, to),
866
+ carTypes: Array.isArray(ind.carTypes) ? ind.carTypes : []
867
+ };
868
+ });
852
869
  });
853
870
  const allLanes = [
854
871
  0,