@willcgage/module-schematic 0.15.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
@@ -165,17 +165,32 @@ interface ModuleSchematicDoc {
165
165
  /** @deprecated pre-grouping flat signals; read for back-compat. */
166
166
  signals?: SchematicSignal[];
167
167
  }
168
- /** A benchwork-outline vertex, module-local inches. */
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. */
169
172
  interface BenchworkPoint {
170
173
  x: number;
171
174
  y: number;
175
+ bulge?: number;
172
176
  }
173
177
  /** The authored benchwork outline, or null when a module hasn't drawn one
174
178
  * (renderers then fall back to a band derived from the endplate widths). A
175
- * valid outline needs at least 3 points. */
179
+ * valid outline needs at least 3 points. Normalises each vertex to {x, y, bulge?}. */
176
180
  declare function benchworkOutline(doc: {
177
181
  outline?: BenchworkPoint[] | null;
178
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
+ }[];
179
194
  /** Whether a doc is a single-endplate turnback (explicit flag or one endplate). */
180
195
  declare function isLoopDoc(doc: ModuleSchematicDoc): boolean;
181
196
  declare const MAIN_TRACK_ID = "main";
@@ -544,4 +559,4 @@ declare function poseOverridesFromDoc(doc: ModuleSchematicDoc): Record<string, {
544
559
  heading: number;
545
560
  }>;
546
561
 
547
- 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, 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
@@ -165,17 +165,32 @@ interface ModuleSchematicDoc {
165
165
  /** @deprecated pre-grouping flat signals; read for back-compat. */
166
166
  signals?: SchematicSignal[];
167
167
  }
168
- /** A benchwork-outline vertex, module-local inches. */
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. */
169
172
  interface BenchworkPoint {
170
173
  x: number;
171
174
  y: number;
175
+ bulge?: number;
172
176
  }
173
177
  /** The authored benchwork outline, or null when a module hasn't drawn one
174
178
  * (renderers then fall back to a band derived from the endplate widths). A
175
- * valid outline needs at least 3 points. */
179
+ * valid outline needs at least 3 points. Normalises each vertex to {x, y, bulge?}. */
176
180
  declare function benchworkOutline(doc: {
177
181
  outline?: BenchworkPoint[] | null;
178
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
+ }[];
179
194
  /** Whether a doc is a single-endplate turnback (explicit flag or one endplate). */
180
195
  declare function isLoopDoc(doc: ModuleSchematicDoc): boolean;
181
196
  declare const MAIN_TRACK_ID = "main";
@@ -544,4 +559,4 @@ declare function poseOverridesFromDoc(doc: ModuleSchematicDoc): Record<string, {
544
559
  heading: number;
545
560
  }>;
546
561
 
547
- 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, 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
@@ -6,11 +6,63 @@ function endplateWidthInches(ep) {
6
6
  return typeof w === "number" && w > 0 ? w : FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES;
7
7
  }
8
8
  function benchworkOutline(doc) {
9
- const pts = (doc?.outline ?? []).filter(
10
- (p) => p && Number.isFinite(p.x) && Number.isFinite(p.y)
11
- );
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
+ }));
12
14
  return pts.length >= 3 ? pts : null;
13
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
+ }
14
66
  function isLoopDoc(doc) {
15
67
  return doc.loop === true || doc.endplates.length === 1;
16
68
  }
@@ -290,9 +342,11 @@ function docToState(doc, fallbackLength, moduleTracks = []) {
290
342
  if (typeof e.widthInches === "number" && e.widthInches > 0)
291
343
  endplateWidths[e.id] = e.widthInches;
292
344
  }
293
- const outline = (d.outline ?? []).filter(
294
- (p) => p && Number.isFinite(p.x) && Number.isFinite(p.y)
295
- );
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
+ }));
296
350
  return {
297
351
  lengthInches: len,
298
352
  loop,
@@ -766,6 +820,6 @@ function poseOverridesFromDoc(doc) {
766
820
  return out;
767
821
  }
768
822
 
769
- 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, 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 };
770
824
  //# sourceMappingURL=index.js.map
771
825
  //# sourceMappingURL=index.js.map