@willcgage/module-schematic 0.26.0 → 0.27.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
@@ -51,7 +51,17 @@ interface SchematicEndplate {
51
51
  }
52
52
  /** Free-moN endplate face width, inches — the connection interface size. */
53
53
  declare const FREEMO_ENDPLATE_WIDTH_MIN_INCHES = 12;
54
+ /** NB: our own default, NOT from the standard — §1.1 states only the 12″ minimum
55
+ * ("Endplates shall be 6 inches high and a minimum 12 inches wide"). 24″ is
56
+ * simply a common real-world width. Don't present it as required. */
54
57
  declare const FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES = 24;
58
+ /** Free-moN §2.0 **standard**: "Double track endplates must have a track spacing
59
+ * of 1.125 inches (1 1/8 inches). Track spacing shall be measured along the
60
+ * track center line." The one definition both apps read. */
61
+ declare const FREEMO_TRACK_SPACING_INCHES = 1.125;
62
+ /** Free-moN §2.0 **standard**: track crossing an endplate must be "not less than
63
+ * 4 inches from either fascia" (and perpendicular, straight and level for 4″). */
64
+ declare const FREEMO_ENDPLATE_TRACK_FASCIA_CLEARANCE_INCHES = 4;
55
65
  /**
56
66
  * The authored face width for an endplate, or the recommended default when a
57
67
  * module hasn't authored one. The single source of truth both apps read so a
@@ -277,6 +287,13 @@ interface ModuleFootprintInput {
277
287
  geometryOffsetInches?: number | null;
278
288
  /** Authored endplate face widths by id ("A"/"B"…), inches; default recommended. */
279
289
  endplateWidths?: Record<string, number>;
290
+ /** Where each endplate's CENTRE sits relative to the main centre-line at that
291
+ * end, inches (signed, along the +normal). Free-moN puts a **double**-track
292
+ * plate's two tracks 9/16″ either side of its centre, so with Main 1 on the
293
+ * centre-line the plate centre is half a track spacing up — pass
294
+ * `FREEMO_TRACK_SPACING_INCHES / 2`. Single track crosses at the centre ⇒ 0
295
+ * (the default), and an off-centre track is a signed value. */
296
+ endplateTrackOffsets?: Record<string, number>;
280
297
  /** Authored benchwork outline (module-local inches), or absent for the band. */
281
298
  outline?: BenchworkPoint[] | null;
282
299
  /** Authored mainline centre-line (module-local inches, open path with arcs).
@@ -305,15 +322,49 @@ interface ModuleFootprint {
305
322
  * centre-line is derived from the geometry fields (length + type/degrees/offset). */
306
323
  declare function moduleCenterline(input: ModuleFootprintInput): BenchworkPoint[];
307
324
  /** Benchwork band: the centre-line offset ±half-width, tapering widthA→widthB. */
308
- declare function benchworkBand(center: BenchworkPoint[], widthA?: number, widthB?: number): BenchworkPoint[];
325
+ declare function benchworkBand(center: BenchworkPoint[], widthA?: number, widthB?: number, offsetA?: number, offsetB?: number): BenchworkPoint[];
309
326
  /** The two endplate faces (the band's flat ends): [A end at widthA, B end at widthB]. */
310
- declare function endplateFaceSegments(center: BenchworkPoint[], widthA?: number, widthB?: number): OutlineFace[];
327
+ declare function endplateFaceSegments(center: BenchworkPoint[], widthA?: number, widthB?: number, offsetA?: number, offsetB?: number): OutlineFace[];
311
328
  /**
312
329
  * The full single-module physical footprint: centre-line + derived band +
313
330
  * endplate faces + the authored outline (arc-sampled), all in module-local
314
331
  * inches. Renderers draw `outline ?? band`.
315
332
  */
316
333
  declare function moduleFootprint(input: ModuleFootprintInput): ModuleFootprint;
334
+ /**
335
+ * The offset from the main centre-line to an endplate's CENTRE, inches — the
336
+ * Free-moN geometry. A **double**-track end carries its two tracks 9/16″ either
337
+ * side of the plate centre (§2.0 RP), and Main 1 sits on the centre-line, so the
338
+ * plate centre is half a track spacing up. A single track crosses at the centre.
339
+ */
340
+ declare function endplateTrackOffsetFor(config: TrackConfig | "none" | undefined): number;
341
+ /** A Free-moN conformance problem with an endplate's width/track placement. */
342
+ interface EndplateWidthIssue {
343
+ /** "narrow" = below the 12″ minimum; "clearance" = a track too near a fascia. */
344
+ code: "narrow" | "clearance";
345
+ /** Plain-language problem, for the author. */
346
+ message: string;
347
+ /** The width that would satisfy this rule, inches. */
348
+ requiredInches: number;
349
+ }
350
+ /**
351
+ * Check an endplate against the two Free-moN **standards** that bound its width:
352
+ *
353
+ * - §1.1 "Endplates shall be 6 inches high and a **minimum 12 inches wide**."
354
+ * - §2.0 "At the endplate, track shall cross near center on the width, **not less
355
+ * than 4 inches from either fascia**."
356
+ *
357
+ * With the tracks centred (the §2.0 recommendation) a double-track end needs
358
+ * 4 + 1.125 + 4 = 9.125″ for clearance alone, so the 12″ minimum governs — but an
359
+ * **off-centre** track can breach the 4″ rule on a plate that is otherwise wide
360
+ * enough, which is why both are checked. `trackOffsetInches` is the signed
361
+ * distance from the plate's centre to the main's crossing point (0 = centred).
362
+ */
363
+ declare function checkEndplateWidth(input: {
364
+ widthInches?: number | null;
365
+ config?: TrackConfig | "none" | null;
366
+ trackOffsetInches?: number | null;
367
+ }): EndplateWidthIssue[];
317
368
  /** Whether a doc is a single-endplate turnback (explicit flag or one endplate). */
318
369
  declare function isLoopDoc(doc: ModuleSchematicDoc): boolean;
319
370
  declare const MAIN_TRACK_ID = "main";
@@ -745,4 +796,4 @@ declare function poseOverridesFromDoc(doc: ModuleSchematicDoc): Record<string, {
745
796
  heading: number;
746
797
  }>;
747
798
 
748
- 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 };
799
+ 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 SchematicSignal, type SchematicTrack, type SchematicTurnout, type SignalFacing, type SignalSide, type TrackConfig, type TrackRole, type TurnoutKind, asModuleSchematic, benchworkBand, benchworkOutline, buildCrossover, buildPassingSiding, buildTransition, carCapacity, checkEndplateWidth, deriveEndplatePoses, divergeSideForHand, docToState, emptyEditorState, endplateFaceSegments, endplateTrackOffsetFor, endplateWidthInches, geometryTurnDegrees, inchesToScaleFeet, isLoopDoc, isTransitionTurnout, moduleCenterline, moduleFeatures, moduleFootprint, nextId, poseNeedsManual, poseOverridesFromDoc, sampleBenchworkOutline, samplePath, scaleFeetToInches, stateToDoc, trackPath };
package/dist/index.d.ts CHANGED
@@ -51,7 +51,17 @@ interface SchematicEndplate {
51
51
  }
52
52
  /** Free-moN endplate face width, inches — the connection interface size. */
53
53
  declare const FREEMO_ENDPLATE_WIDTH_MIN_INCHES = 12;
54
+ /** NB: our own default, NOT from the standard — §1.1 states only the 12″ minimum
55
+ * ("Endplates shall be 6 inches high and a minimum 12 inches wide"). 24″ is
56
+ * simply a common real-world width. Don't present it as required. */
54
57
  declare const FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES = 24;
58
+ /** Free-moN §2.0 **standard**: "Double track endplates must have a track spacing
59
+ * of 1.125 inches (1 1/8 inches). Track spacing shall be measured along the
60
+ * track center line." The one definition both apps read. */
61
+ declare const FREEMO_TRACK_SPACING_INCHES = 1.125;
62
+ /** Free-moN §2.0 **standard**: track crossing an endplate must be "not less than
63
+ * 4 inches from either fascia" (and perpendicular, straight and level for 4″). */
64
+ declare const FREEMO_ENDPLATE_TRACK_FASCIA_CLEARANCE_INCHES = 4;
55
65
  /**
56
66
  * The authored face width for an endplate, or the recommended default when a
57
67
  * module hasn't authored one. The single source of truth both apps read so a
@@ -277,6 +287,13 @@ interface ModuleFootprintInput {
277
287
  geometryOffsetInches?: number | null;
278
288
  /** Authored endplate face widths by id ("A"/"B"…), inches; default recommended. */
279
289
  endplateWidths?: Record<string, number>;
290
+ /** Where each endplate's CENTRE sits relative to the main centre-line at that
291
+ * end, inches (signed, along the +normal). Free-moN puts a **double**-track
292
+ * plate's two tracks 9/16″ either side of its centre, so with Main 1 on the
293
+ * centre-line the plate centre is half a track spacing up — pass
294
+ * `FREEMO_TRACK_SPACING_INCHES / 2`. Single track crosses at the centre ⇒ 0
295
+ * (the default), and an off-centre track is a signed value. */
296
+ endplateTrackOffsets?: Record<string, number>;
280
297
  /** Authored benchwork outline (module-local inches), or absent for the band. */
281
298
  outline?: BenchworkPoint[] | null;
282
299
  /** Authored mainline centre-line (module-local inches, open path with arcs).
@@ -305,15 +322,49 @@ interface ModuleFootprint {
305
322
  * centre-line is derived from the geometry fields (length + type/degrees/offset). */
306
323
  declare function moduleCenterline(input: ModuleFootprintInput): BenchworkPoint[];
307
324
  /** Benchwork band: the centre-line offset ±half-width, tapering widthA→widthB. */
308
- declare function benchworkBand(center: BenchworkPoint[], widthA?: number, widthB?: number): BenchworkPoint[];
325
+ declare function benchworkBand(center: BenchworkPoint[], widthA?: number, widthB?: number, offsetA?: number, offsetB?: number): BenchworkPoint[];
309
326
  /** The two endplate faces (the band's flat ends): [A end at widthA, B end at widthB]. */
310
- declare function endplateFaceSegments(center: BenchworkPoint[], widthA?: number, widthB?: number): OutlineFace[];
327
+ declare function endplateFaceSegments(center: BenchworkPoint[], widthA?: number, widthB?: number, offsetA?: number, offsetB?: number): OutlineFace[];
311
328
  /**
312
329
  * The full single-module physical footprint: centre-line + derived band +
313
330
  * endplate faces + the authored outline (arc-sampled), all in module-local
314
331
  * inches. Renderers draw `outline ?? band`.
315
332
  */
316
333
  declare function moduleFootprint(input: ModuleFootprintInput): ModuleFootprint;
334
+ /**
335
+ * The offset from the main centre-line to an endplate's CENTRE, inches — the
336
+ * Free-moN geometry. A **double**-track end carries its two tracks 9/16″ either
337
+ * side of the plate centre (§2.0 RP), and Main 1 sits on the centre-line, so the
338
+ * plate centre is half a track spacing up. A single track crosses at the centre.
339
+ */
340
+ declare function endplateTrackOffsetFor(config: TrackConfig | "none" | undefined): number;
341
+ /** A Free-moN conformance problem with an endplate's width/track placement. */
342
+ interface EndplateWidthIssue {
343
+ /** "narrow" = below the 12″ minimum; "clearance" = a track too near a fascia. */
344
+ code: "narrow" | "clearance";
345
+ /** Plain-language problem, for the author. */
346
+ message: string;
347
+ /** The width that would satisfy this rule, inches. */
348
+ requiredInches: number;
349
+ }
350
+ /**
351
+ * Check an endplate against the two Free-moN **standards** that bound its width:
352
+ *
353
+ * - §1.1 "Endplates shall be 6 inches high and a **minimum 12 inches wide**."
354
+ * - §2.0 "At the endplate, track shall cross near center on the width, **not less
355
+ * than 4 inches from either fascia**."
356
+ *
357
+ * With the tracks centred (the §2.0 recommendation) a double-track end needs
358
+ * 4 + 1.125 + 4 = 9.125″ for clearance alone, so the 12″ minimum governs — but an
359
+ * **off-centre** track can breach the 4″ rule on a plate that is otherwise wide
360
+ * enough, which is why both are checked. `trackOffsetInches` is the signed
361
+ * distance from the plate's centre to the main's crossing point (0 = centred).
362
+ */
363
+ declare function checkEndplateWidth(input: {
364
+ widthInches?: number | null;
365
+ config?: TrackConfig | "none" | null;
366
+ trackOffsetInches?: number | null;
367
+ }): EndplateWidthIssue[];
317
368
  /** Whether a doc is a single-endplate turnback (explicit flag or one endplate). */
318
369
  declare function isLoopDoc(doc: ModuleSchematicDoc): boolean;
319
370
  declare const MAIN_TRACK_ID = "main";
@@ -745,4 +796,4 @@ declare function poseOverridesFromDoc(doc: ModuleSchematicDoc): Record<string, {
745
796
  heading: number;
746
797
  }>;
747
798
 
748
- 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 };
799
+ 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 SchematicSignal, type SchematicTrack, type SchematicTurnout, type SignalFacing, type SignalSide, type TrackConfig, type TrackRole, type TurnoutKind, asModuleSchematic, benchworkBand, benchworkOutline, buildCrossover, buildPassingSiding, buildTransition, carCapacity, checkEndplateWidth, deriveEndplatePoses, divergeSideForHand, docToState, emptyEditorState, endplateFaceSegments, endplateTrackOffsetFor, endplateWidthInches, geometryTurnDegrees, inchesToScaleFeet, isLoopDoc, isTransitionTurnout, moduleCenterline, moduleFeatures, moduleFootprint, nextId, poseNeedsManual, poseOverridesFromDoc, sampleBenchworkOutline, samplePath, scaleFeetToInches, stateToDoc, trackPath };
package/dist/index.js CHANGED
@@ -1,6 +1,8 @@
1
1
  // src/index.ts
2
2
  var FREEMO_ENDPLATE_WIDTH_MIN_INCHES = 12;
3
3
  var FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES = 24;
4
+ var FREEMO_TRACK_SPACING_INCHES = 1.125;
5
+ var FREEMO_ENDPLATE_TRACK_FASCIA_CLEARANCE_INCHES = 4;
4
6
  function endplateWidthInches(ep) {
5
7
  const w = ep?.widthInches;
6
8
  return typeof w === "number" && w > 0 ? w : FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES;
@@ -142,37 +144,74 @@ function centerlineFractions(center) {
142
144
  const total = cum[cum.length - 1] || 1;
143
145
  return cum.map((d) => d / total);
144
146
  }
145
- function benchworkBand(center, widthA = FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES, widthB = FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES) {
147
+ function benchworkBand(center, widthA = FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES, widthB = FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES, offsetA = 0, offsetB = 0) {
146
148
  if (center.length < 2) return [];
147
149
  const n = centerlineNormals(center);
148
150
  const f = centerlineFractions(center);
149
151
  const half = (i) => (widthA * (1 - f[i]) + widthB * f[i]) / 2;
150
- const left = center.map((p, i) => ({ x: p.x + n[i].x * half(i), y: p.y + n[i].y * half(i) }));
151
- const right = center.map((p, i) => ({ x: p.x - n[i].x * half(i), y: p.y - n[i].y * half(i) }));
152
+ const off = (i) => offsetA * (1 - f[i]) + offsetB * f[i];
153
+ const left = center.map((p, i) => ({
154
+ x: p.x + n[i].x * (off(i) + half(i)),
155
+ y: p.y + n[i].y * (off(i) + half(i))
156
+ }));
157
+ const right = center.map((p, i) => ({
158
+ x: p.x + n[i].x * (off(i) - half(i)),
159
+ y: p.y + n[i].y * (off(i) - half(i))
160
+ }));
152
161
  return [...left, ...right.reverse()];
153
162
  }
154
- function endplateFaceSegments(center, widthA = FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES, widthB = FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES) {
163
+ function endplateFaceSegments(center, widthA = FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES, widthB = FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES, offsetA = 0, offsetB = 0) {
155
164
  if (center.length < 2) return [];
156
165
  const n = centerlineNormals(center);
157
- const face = (i, w) => ({
158
- p1: { x: center[i].x + n[i].x * (w / 2), y: center[i].y + n[i].y * (w / 2) },
159
- p2: { x: center[i].x - n[i].x * (w / 2), y: center[i].y - n[i].y * (w / 2) },
166
+ const face = (i, w, o) => ({
167
+ p1: { x: center[i].x + n[i].x * (o + w / 2), y: center[i].y + n[i].y * (o + w / 2) },
168
+ p2: { x: center[i].x + n[i].x * (o - w / 2), y: center[i].y + n[i].y * (o - w / 2) },
160
169
  mid: { x: center[i].x, y: center[i].y }
161
170
  });
162
- return [face(0, widthA), face(center.length - 1, widthB)];
171
+ return [face(0, widthA, offsetA), face(center.length - 1, widthB, offsetB)];
163
172
  }
164
173
  function moduleFootprint(input) {
165
174
  const centerline = moduleCenterline(input);
166
175
  const widthA = endplateWidthFor(input.endplateWidths, "A");
167
176
  const widthB = endplateWidthFor(input.endplateWidths, "B");
168
177
  const authored = benchworkOutline(input);
178
+ const offA = input.endplateTrackOffsets?.["A"] ?? 0;
179
+ const offB = input.endplateTrackOffsets?.["B"] ?? 0;
169
180
  return {
170
181
  centerline,
171
- band: benchworkBand(centerline, widthA, widthB),
172
- endplateFaces: endplateFaceSegments(centerline, widthA, widthB),
182
+ band: benchworkBand(centerline, widthA, widthB, offA, offB),
183
+ endplateFaces: endplateFaceSegments(centerline, widthA, widthB, offA, offB),
173
184
  outline: authored ? sampleBenchworkOutline(authored) : null
174
185
  };
175
186
  }
187
+ function endplateTrackOffsetFor(config) {
188
+ return config === "double" ? FREEMO_TRACK_SPACING_INCHES / 2 : 0;
189
+ }
190
+ function checkEndplateWidth(input) {
191
+ const width = endplateWidthInches(input);
192
+ const issues = [];
193
+ if (width < FREEMO_ENDPLATE_WIDTH_MIN_INCHES) {
194
+ issues.push({
195
+ code: "narrow",
196
+ message: `Endplate is ${round2(width)}\u2033 wide \u2014 the standard requires at least ${FREEMO_ENDPLATE_WIDTH_MIN_INCHES}\u2033.`,
197
+ requiredInches: FREEMO_ENDPLATE_WIDTH_MIN_INCHES
198
+ });
199
+ }
200
+ const off = input.trackOffsetInches ?? 0;
201
+ const centres = input.config === "double" ? [off - FREEMO_TRACK_SPACING_INCHES / 2, off + FREEMO_TRACK_SPACING_INCHES / 2] : [off];
202
+ const worst = Math.max(...centres.map((c) => Math.abs(c)));
203
+ const clearance = width / 2 - worst;
204
+ if (clearance < FREEMO_ENDPLATE_TRACK_FASCIA_CLEARANCE_INCHES) {
205
+ const required = 2 * (worst + FREEMO_ENDPLATE_TRACK_FASCIA_CLEARANCE_INCHES);
206
+ issues.push({
207
+ code: "clearance",
208
+ message: `Track sits ${round2(clearance)}\u2033 from the fascia \u2014 the standard requires at least ${FREEMO_ENDPLATE_TRACK_FASCIA_CLEARANCE_INCHES}\u2033. Widen this end to ${round2(required)}\u2033` + (off !== 0 ? " or move the track back toward the centre." : "."),
209
+ requiredInches: required
210
+ });
211
+ }
212
+ return issues;
213
+ }
214
+ var round2 = (n) => Math.round(n * 100) / 100;
176
215
  function endplateWidthFor(widths, id) {
177
216
  const w = widths?.[id];
178
217
  return typeof w === "number" && w > 0 ? w : FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES;
@@ -1037,6 +1076,6 @@ function poseOverridesFromDoc(doc) {
1037
1076
  return out;
1038
1077
  }
1039
1078
 
1040
- 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 };
1079
+ 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, endplateWidthInches, geometryTurnDegrees, inchesToScaleFeet, isLoopDoc, isTransitionTurnout, moduleCenterline, moduleFeatures, moduleFootprint, nextId, poseNeedsManual, poseOverridesFromDoc, sampleBenchworkOutline, samplePath, scaleFeetToInches, stateToDoc, trackPath };
1041
1080
  //# sourceMappingURL=index.js.map
1042
1081
  //# sourceMappingURL=index.js.map