@willcgage/module-schematic 0.14.0 → 0.16.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
@@ -156,9 +156,41 @@ interface ModuleSchematicDoc {
156
156
  /** Grade crossings / diamonds (#170). */
157
157
  crossings?: SchematicCrossing[];
158
158
  controlPoints?: SchematicControlPoint[];
159
+ /** Benchwork FOOTPRINT outline — the module's physical board shape as a
160
+ * polygon in module-local inches, in the same frame as the endplate poses
161
+ * (endplate A's track point at the origin, the mainline along +x, perpendicular
162
+ * +y up). Stored as an open ring; renderers close it. Absent = derive an
163
+ * approximate band from the endplate widths. */
164
+ outline?: BenchworkPoint[];
159
165
  /** @deprecated pre-grouping flat signals; read for back-compat. */
160
166
  signals?: SchematicSignal[];
161
167
  }
168
+ /** A benchwork-outline vertex, module-local inches. The edge from this vertex
169
+ * to the NEXT one is a straight line, unless `bulge` is set — then it's a
170
+ * circular arc whose midpoint is offset `bulge` inches (signed: + bows to the
171
+ * left of the P→next direction) perpendicular from the chord. */
172
+ interface BenchworkPoint {
173
+ x: number;
174
+ y: number;
175
+ bulge?: number;
176
+ }
177
+ /** The authored benchwork outline, or null when a module hasn't drawn one
178
+ * (renderers then fall back to a band derived from the endplate widths). A
179
+ * valid outline needs at least 3 points. Normalises each vertex to {x, y, bulge?}. */
180
+ declare function benchworkOutline(doc: {
181
+ outline?: BenchworkPoint[] | null;
182
+ } | null | undefined): BenchworkPoint[] | null;
183
+ /**
184
+ * Expand a benchwork outline (whose edges may be arcs) into a dense closed
185
+ * polyline for rendering — the SAME sampling both the Repository preview and
186
+ * Free-Dispatcher use, so a curve looks identical in both. Straight edges emit
187
+ * just their start vertex; a bulged edge emits `segsPerArc` points along the
188
+ * circular arc through the two endpoints and the bulged midpoint.
189
+ */
190
+ declare function sampleBenchworkOutline(pts: BenchworkPoint[], segsPerArc?: number): {
191
+ x: number;
192
+ y: number;
193
+ }[];
162
194
  /** Whether a doc is a single-endplate turnback (explicit flag or one endplate). */
163
195
  declare function isLoopDoc(doc: ModuleSchematicDoc): boolean;
164
196
  declare const MAIN_TRACK_ID = "main";
@@ -263,6 +295,10 @@ interface EditorState {
263
295
  /** Authored endplate face widths by endplate id, inches (Free-moN 12″ min,
264
296
  * 24″ recommended). Absent id = the recommended default. */
265
297
  endplateWidths: Record<string, number>;
298
+ /** Benchwork footprint outline — polygon vertices in module-local inches
299
+ * (endplate A's track point at the origin, mainline +x, perpendicular +y up).
300
+ * Empty = no authored outline (fall back to the endplate-width band). */
301
+ outline: BenchworkPoint[];
266
302
  controlPoints: EditorControlPoint[];
267
303
  }
268
304
  /** Build the empty editor state for a module of the given length. */
@@ -523,4 +559,4 @@ declare function poseOverridesFromDoc(doc: ModuleSchematicDoc): Record<string, {
523
559
  heading: number;
524
560
  }>;
525
561
 
526
- export { type BranchConnector, type DrawCrossing, type DrawCrossover, type DrawSignal, type DrawTrack, type DrawTurnout, type EditorBranch, type EditorControlPoint, type EditorCpSignal, type EditorCrossing, type EditorState, type EditorTrack, type EditorTurnout, type EndplateBConfig, type EndplatePose, FREEMO_ENDPLATE_WIDTH_MIN_INCHES, FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES, type GeometryType, MAIN2_TRACK_ID, MAIN_TRACK_ID, type ModuleFeatures, type ModuleGeometryInput, type ModuleSchematicDoc, type ModuleTrackRow, N_SCALE_RATIO, type SchematicBlock, type SchematicControlPoint, type SchematicCrossing, type SchematicEndplate, type SchematicEndplateTrack, type SchematicSignal, type SchematicTrack, type SchematicTurnout, type SignalFacing, type SignalSide, type TrackConfig, type TrackRole, type TurnoutKind, asModuleSchematic, buildCrossover, buildPassingSiding, buildTransition, deriveEndplatePoses, divergeSideForHand, docToState, emptyEditorState, endplateWidthInches, geometryTurnDegrees, inchesToScaleFeet, isLoopDoc, isTransitionTurnout, moduleFeatures, nextId, poseNeedsManual, poseOverridesFromDoc, scaleFeetToInches, stateToDoc };
562
+ export { type BenchworkPoint, type BranchConnector, type DrawCrossing, type DrawCrossover, type DrawSignal, type DrawTrack, type DrawTurnout, type EditorBranch, type EditorControlPoint, type EditorCpSignal, type EditorCrossing, type EditorState, type EditorTrack, type EditorTurnout, type EndplateBConfig, type EndplatePose, FREEMO_ENDPLATE_WIDTH_MIN_INCHES, FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES, type GeometryType, MAIN2_TRACK_ID, MAIN_TRACK_ID, type ModuleFeatures, type ModuleGeometryInput, type ModuleSchematicDoc, type ModuleTrackRow, N_SCALE_RATIO, type SchematicBlock, type SchematicControlPoint, type SchematicCrossing, type SchematicEndplate, type SchematicEndplateTrack, type SchematicSignal, type SchematicTrack, type SchematicTurnout, type SignalFacing, type SignalSide, type TrackConfig, type TrackRole, type TurnoutKind, asModuleSchematic, benchworkOutline, buildCrossover, buildPassingSiding, buildTransition, deriveEndplatePoses, divergeSideForHand, docToState, emptyEditorState, endplateWidthInches, geometryTurnDegrees, inchesToScaleFeet, isLoopDoc, isTransitionTurnout, moduleFeatures, nextId, poseNeedsManual, poseOverridesFromDoc, sampleBenchworkOutline, scaleFeetToInches, stateToDoc };
package/dist/index.d.ts CHANGED
@@ -156,9 +156,41 @@ interface ModuleSchematicDoc {
156
156
  /** Grade crossings / diamonds (#170). */
157
157
  crossings?: SchematicCrossing[];
158
158
  controlPoints?: SchematicControlPoint[];
159
+ /** Benchwork FOOTPRINT outline — the module's physical board shape as a
160
+ * polygon in module-local inches, in the same frame as the endplate poses
161
+ * (endplate A's track point at the origin, the mainline along +x, perpendicular
162
+ * +y up). Stored as an open ring; renderers close it. Absent = derive an
163
+ * approximate band from the endplate widths. */
164
+ outline?: BenchworkPoint[];
159
165
  /** @deprecated pre-grouping flat signals; read for back-compat. */
160
166
  signals?: SchematicSignal[];
161
167
  }
168
+ /** A benchwork-outline vertex, module-local inches. The edge from this vertex
169
+ * to the NEXT one is a straight line, unless `bulge` is set — then it's a
170
+ * circular arc whose midpoint is offset `bulge` inches (signed: + bows to the
171
+ * left of the P→next direction) perpendicular from the chord. */
172
+ interface BenchworkPoint {
173
+ x: number;
174
+ y: number;
175
+ bulge?: number;
176
+ }
177
+ /** The authored benchwork outline, or null when a module hasn't drawn one
178
+ * (renderers then fall back to a band derived from the endplate widths). A
179
+ * valid outline needs at least 3 points. Normalises each vertex to {x, y, bulge?}. */
180
+ declare function benchworkOutline(doc: {
181
+ outline?: BenchworkPoint[] | null;
182
+ } | null | undefined): BenchworkPoint[] | null;
183
+ /**
184
+ * Expand a benchwork outline (whose edges may be arcs) into a dense closed
185
+ * polyline for rendering — the SAME sampling both the Repository preview and
186
+ * Free-Dispatcher use, so a curve looks identical in both. Straight edges emit
187
+ * just their start vertex; a bulged edge emits `segsPerArc` points along the
188
+ * circular arc through the two endpoints and the bulged midpoint.
189
+ */
190
+ declare function sampleBenchworkOutline(pts: BenchworkPoint[], segsPerArc?: number): {
191
+ x: number;
192
+ y: number;
193
+ }[];
162
194
  /** Whether a doc is a single-endplate turnback (explicit flag or one endplate). */
163
195
  declare function isLoopDoc(doc: ModuleSchematicDoc): boolean;
164
196
  declare const MAIN_TRACK_ID = "main";
@@ -263,6 +295,10 @@ interface EditorState {
263
295
  /** Authored endplate face widths by endplate id, inches (Free-moN 12″ min,
264
296
  * 24″ recommended). Absent id = the recommended default. */
265
297
  endplateWidths: Record<string, number>;
298
+ /** Benchwork footprint outline — polygon vertices in module-local inches
299
+ * (endplate A's track point at the origin, mainline +x, perpendicular +y up).
300
+ * Empty = no authored outline (fall back to the endplate-width band). */
301
+ outline: BenchworkPoint[];
266
302
  controlPoints: EditorControlPoint[];
267
303
  }
268
304
  /** Build the empty editor state for a module of the given length. */
@@ -523,4 +559,4 @@ declare function poseOverridesFromDoc(doc: ModuleSchematicDoc): Record<string, {
523
559
  heading: number;
524
560
  }>;
525
561
 
526
- export { type BranchConnector, type DrawCrossing, type DrawCrossover, type DrawSignal, type DrawTrack, type DrawTurnout, type EditorBranch, type EditorControlPoint, type EditorCpSignal, type EditorCrossing, type EditorState, type EditorTrack, type EditorTurnout, type EndplateBConfig, type EndplatePose, FREEMO_ENDPLATE_WIDTH_MIN_INCHES, FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES, type GeometryType, MAIN2_TRACK_ID, MAIN_TRACK_ID, type ModuleFeatures, type ModuleGeometryInput, type ModuleSchematicDoc, type ModuleTrackRow, N_SCALE_RATIO, type SchematicBlock, type SchematicControlPoint, type SchematicCrossing, type SchematicEndplate, type SchematicEndplateTrack, type SchematicSignal, type SchematicTrack, type SchematicTurnout, type SignalFacing, type SignalSide, type TrackConfig, type TrackRole, type TurnoutKind, asModuleSchematic, buildCrossover, buildPassingSiding, buildTransition, deriveEndplatePoses, divergeSideForHand, docToState, emptyEditorState, endplateWidthInches, geometryTurnDegrees, inchesToScaleFeet, isLoopDoc, isTransitionTurnout, moduleFeatures, nextId, poseNeedsManual, poseOverridesFromDoc, scaleFeetToInches, stateToDoc };
562
+ export { type BenchworkPoint, type BranchConnector, type DrawCrossing, type DrawCrossover, type DrawSignal, type DrawTrack, type DrawTurnout, type EditorBranch, type EditorControlPoint, type EditorCpSignal, type EditorCrossing, type EditorState, type EditorTrack, type EditorTurnout, type EndplateBConfig, type EndplatePose, FREEMO_ENDPLATE_WIDTH_MIN_INCHES, FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES, type GeometryType, MAIN2_TRACK_ID, MAIN_TRACK_ID, type ModuleFeatures, type ModuleGeometryInput, type ModuleSchematicDoc, type ModuleTrackRow, N_SCALE_RATIO, type SchematicBlock, type SchematicControlPoint, type SchematicCrossing, type SchematicEndplate, type SchematicEndplateTrack, type SchematicSignal, type SchematicTrack, type SchematicTurnout, type SignalFacing, type SignalSide, type TrackConfig, type TrackRole, type TurnoutKind, asModuleSchematic, benchworkOutline, buildCrossover, buildPassingSiding, buildTransition, deriveEndplatePoses, divergeSideForHand, docToState, emptyEditorState, endplateWidthInches, geometryTurnDegrees, inchesToScaleFeet, isLoopDoc, isTransitionTurnout, moduleFeatures, nextId, poseNeedsManual, poseOverridesFromDoc, sampleBenchworkOutline, scaleFeetToInches, stateToDoc };
package/dist/index.js CHANGED
@@ -5,6 +5,64 @@ function endplateWidthInches(ep) {
5
5
  const w = ep?.widthInches;
6
6
  return typeof w === "number" && w > 0 ? w : FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES;
7
7
  }
8
+ function benchworkOutline(doc) {
9
+ const pts = (doc?.outline ?? []).filter((p) => p && Number.isFinite(p.x) && Number.isFinite(p.y)).map((p) => ({
10
+ x: p.x,
11
+ y: p.y,
12
+ ...Number.isFinite(p.bulge) && p.bulge ? { bulge: p.bulge } : {}
13
+ }));
14
+ return pts.length >= 3 ? pts : null;
15
+ }
16
+ function sampleBenchworkOutline(pts, segsPerArc = 20) {
17
+ const n = pts.length;
18
+ if (n < 2) return pts.map((p) => ({ x: p.x, y: p.y }));
19
+ const out = [];
20
+ for (let i = 0; i < n; i++) {
21
+ const p0 = pts[i];
22
+ const p1 = pts[(i + 1) % n];
23
+ out.push({ x: p0.x, y: p0.y });
24
+ const bulge = p0.bulge ?? 0;
25
+ if (!bulge) continue;
26
+ const dx = p1.x - p0.x;
27
+ const dy = p1.y - p0.y;
28
+ const c = Math.hypot(dx, dy);
29
+ if (c < 1e-6) continue;
30
+ const nx = -dy / c;
31
+ const ny = dx / c;
32
+ const mid = { x: (p0.x + p1.x) / 2 + nx * bulge, y: (p0.y + p1.y) / 2 + ny * bulge };
33
+ const circ = circleThrough(p0, mid, p1);
34
+ if (!circ) continue;
35
+ const a0 = Math.atan2(p0.y - circ.cy, p0.x - circ.cx);
36
+ const am = Math.atan2(mid.y - circ.cy, mid.x - circ.cx);
37
+ const a1 = Math.atan2(p1.y - circ.cy, p1.x - circ.cx);
38
+ const sweep = arcSweep(a0, a1, am);
39
+ for (let s = 1; s < segsPerArc; s++) {
40
+ const a = a0 + sweep * s / segsPerArc;
41
+ out.push({ x: circ.cx + circ.r * Math.cos(a), y: circ.cy + circ.r * Math.sin(a) });
42
+ }
43
+ }
44
+ return out;
45
+ }
46
+ function circleThrough(a, b, c) {
47
+ const d = 2 * (a.x * (b.y - c.y) + b.x * (c.y - a.y) + c.x * (a.y - b.y));
48
+ if (Math.abs(d) < 1e-9) return null;
49
+ const a2 = a.x * a.x + a.y * a.y;
50
+ const b2 = b.x * b.x + b.y * b.y;
51
+ const c2 = c.x * c.x + c.y * c.y;
52
+ const cx = (a2 * (b.y - c.y) + b2 * (c.y - a.y) + c2 * (a.y - b.y)) / d;
53
+ const cy = (a2 * (c.x - b.x) + b2 * (a.x - c.x) + c2 * (b.x - a.x)) / d;
54
+ return { cx, cy, r: Math.hypot(a.x - cx, a.y - cy) };
55
+ }
56
+ function arcSweep(a0, a1, am) {
57
+ const norm = (x) => {
58
+ let v = (x - a0) % (2 * Math.PI);
59
+ if (v < 0) v += 2 * Math.PI;
60
+ return v;
61
+ };
62
+ const m = norm(am);
63
+ const one = norm(a1);
64
+ return m <= one ? one : one - 2 * Math.PI;
65
+ }
8
66
  function isLoopDoc(doc) {
9
67
  return doc.loop === true || doc.endplates.length === 1;
10
68
  }
@@ -37,6 +95,7 @@ function emptyEditorState(lengthInches) {
37
95
  branches: [],
38
96
  poseOverrides: {},
39
97
  endplateWidths: {},
98
+ outline: [],
40
99
  controlPoints: []
41
100
  };
42
101
  }
@@ -206,7 +265,10 @@ function stateToDoc(state, recordNumber) {
206
265
  kind: "mast",
207
266
  side: s.side
208
267
  }))
209
- }))
268
+ })),
269
+ // Benchwork footprint outline (module-local inches); only when it's a real
270
+ // ring (≥ 3 vertices).
271
+ ...state.outline.length >= 3 ? { outline: state.outline } : {}
210
272
  };
211
273
  }
212
274
  function docToState(doc, fallbackLength, moduleTracks = []) {
@@ -280,6 +342,11 @@ function docToState(doc, fallbackLength, moduleTracks = []) {
280
342
  if (typeof e.widthInches === "number" && e.widthInches > 0)
281
343
  endplateWidths[e.id] = e.widthInches;
282
344
  }
345
+ const outline = (d.outline ?? []).filter((p) => p && Number.isFinite(p.x) && Number.isFinite(p.y)).map((p) => ({
346
+ x: p.x,
347
+ y: p.y,
348
+ ...Number.isFinite(p.bulge) && p.bulge ? { bulge: p.bulge } : {}
349
+ }));
283
350
  return {
284
351
  lengthInches: len,
285
352
  loop,
@@ -295,6 +362,7 @@ function docToState(doc, fallbackLength, moduleTracks = []) {
295
362
  })),
296
363
  poseOverrides,
297
364
  endplateWidths,
365
+ outline,
298
366
  crossings: (d.crossings ?? []).map((x) => ({
299
367
  id: x.id,
300
368
  name: x.name ?? "",
@@ -752,6 +820,6 @@ function poseOverridesFromDoc(doc) {
752
820
  return out;
753
821
  }
754
822
 
755
- export { FREEMO_ENDPLATE_WIDTH_MIN_INCHES, FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES, MAIN2_TRACK_ID, MAIN_TRACK_ID, N_SCALE_RATIO, asModuleSchematic, buildCrossover, buildPassingSiding, buildTransition, deriveEndplatePoses, divergeSideForHand, docToState, emptyEditorState, endplateWidthInches, geometryTurnDegrees, inchesToScaleFeet, isLoopDoc, isTransitionTurnout, moduleFeatures, nextId, poseNeedsManual, poseOverridesFromDoc, scaleFeetToInches, stateToDoc };
823
+ export { FREEMO_ENDPLATE_WIDTH_MIN_INCHES, FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES, MAIN2_TRACK_ID, MAIN_TRACK_ID, N_SCALE_RATIO, asModuleSchematic, benchworkOutline, buildCrossover, buildPassingSiding, buildTransition, deriveEndplatePoses, divergeSideForHand, docToState, emptyEditorState, endplateWidthInches, geometryTurnDegrees, inchesToScaleFeet, isLoopDoc, isTransitionTurnout, moduleFeatures, nextId, poseNeedsManual, poseOverridesFromDoc, sampleBenchworkOutline, scaleFeetToInches, stateToDoc };
756
824
  //# sourceMappingURL=index.js.map
757
825
  //# sourceMappingURL=index.js.map