@willcgage/module-schematic 0.18.0 → 0.19.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.cjs CHANGED
@@ -45,6 +45,45 @@ function sampleBenchworkOutline(pts, segsPerArc = 20) {
45
45
  }
46
46
  return out;
47
47
  }
48
+ function samplePath(pts, segsPerArc = 20) {
49
+ const n = pts.length;
50
+ if (n < 2) return pts.map((p) => ({ x: p.x, y: p.y }));
51
+ const out = [];
52
+ for (let i = 0; i < n - 1; i++) {
53
+ const p0 = pts[i];
54
+ const p1 = pts[i + 1];
55
+ out.push({ x: p0.x, y: p0.y });
56
+ const bulge = p0.bulge ?? 0;
57
+ if (!bulge) continue;
58
+ const dx = p1.x - p0.x;
59
+ const dy = p1.y - p0.y;
60
+ const c = Math.hypot(dx, dy);
61
+ if (c < 1e-6) continue;
62
+ const nx = -dy / c;
63
+ const ny = dx / c;
64
+ const mid = { x: (p0.x + p1.x) / 2 + nx * bulge, y: (p0.y + p1.y) / 2 + ny * bulge };
65
+ const circ = circleThrough(p0, mid, p1);
66
+ if (!circ) continue;
67
+ const a0 = Math.atan2(p0.y - circ.cy, p0.x - circ.cx);
68
+ const am = Math.atan2(mid.y - circ.cy, mid.x - circ.cx);
69
+ const a1 = Math.atan2(p1.y - circ.cy, p1.x - circ.cx);
70
+ const sweep = arcSweep(a0, a1, am);
71
+ for (let s = 1; s < segsPerArc; s++) {
72
+ const a = a0 + sweep * s / segsPerArc;
73
+ out.push({ x: circ.cx + circ.r * Math.cos(a), y: circ.cy + circ.r * Math.sin(a) });
74
+ }
75
+ }
76
+ out.push({ x: pts[n - 1].x, y: pts[n - 1].y });
77
+ return out;
78
+ }
79
+ function trackPath(path) {
80
+ const pts = (path ?? []).filter((p) => p && Number.isFinite(p.x) && Number.isFinite(p.y)).map((p) => ({
81
+ x: p.x,
82
+ y: p.y,
83
+ ...Number.isFinite(p.bulge) && p.bulge ? { bulge: p.bulge } : {}
84
+ }));
85
+ return pts.length >= 2 ? pts : null;
86
+ }
48
87
  function circleThrough(a, b, c) {
49
88
  const d = 2 * (a.x * (b.y - c.y) + b.x * (c.y - a.y) + c.x * (a.y - b.y));
50
89
  if (Math.abs(d) < 1e-9) return null;
@@ -67,6 +106,8 @@ function arcSweep(a0, a1, am) {
67
106
  }
68
107
  var DEG_FP = Math.PI / 180;
69
108
  function moduleCenterline(input) {
109
+ const drawn = trackPath(input.mainPath);
110
+ if (drawn) return samplePath(drawn);
70
111
  const L = input.lengthInches > 0 ? input.lengthInches : 24;
71
112
  const gt = input.geometryType;
72
113
  if (gt === "dead_end") return [{ x: 0, y: 0 }];
@@ -176,7 +217,8 @@ function emptyEditorState(lengthInches) {
176
217
  endplateWidths: {},
177
218
  outline: [],
178
219
  controlPoints: [],
179
- industries: []
220
+ industries: [],
221
+ mainPath: []
180
222
  };
181
223
  }
182
224
  function isTransitionTurnout(t) {
@@ -313,7 +355,8 @@ function stateToDoc(state, recordNumber) {
313
355
  moduleTrackId: t.moduleTrackId,
314
356
  trackName: t.trackName || void 0,
315
357
  capacityFeet: Math.round(inchesToScaleFeet(Math.abs(t.toPos - t.fromPos))),
316
- ...state.loop && t.inLoop ? { inLoop: true } : {}
358
+ ...state.loop && t.inLoop ? { inLoop: true } : {},
359
+ ...t.path && t.path.length >= 2 ? { path: t.path } : {}
317
360
  }))
318
361
  ],
319
362
  turnouts: state.turnouts.map((t) => ({
@@ -363,7 +406,9 @@ function stateToDoc(state, recordNumber) {
363
406
  } : {},
364
407
  // Benchwork footprint outline (module-local inches); only when it's a real
365
408
  // ring (≥ 3 vertices).
366
- ...state.outline.length >= 3 ? { outline: state.outline } : {}
409
+ ...state.outline.length >= 3 ? { outline: state.outline } : {},
410
+ // Authored mainline path (module-local inches); only when it's a real path.
411
+ ...state.mainPath.length >= 2 ? { mainPath: state.mainPath } : {}
367
412
  };
368
413
  }
369
414
  function docToState(doc, fallbackLength, moduleTracks = []) {
@@ -393,7 +438,9 @@ function docToState(doc, fallbackLength, moduleTracks = []) {
393
438
  toPos: t.toPos != null ? sc(t.toPos) : len,
394
439
  moduleTrackId,
395
440
  trackName: t.trackName ?? nameOf(moduleTrackId),
396
- ...t.inLoop ? { inLoop: true } : {}
441
+ ...t.inLoop ? { inLoop: true } : {},
442
+ // Authored path kept as-drawn (a physical shape, not rescaled with length).
443
+ ...trackPath(t.path) ? { path: trackPath(t.path) } : {}
397
444
  });
398
445
  }
399
446
  }
@@ -442,6 +489,7 @@ function docToState(doc, fallbackLength, moduleTracks = []) {
442
489
  y: p.y,
443
490
  ...Number.isFinite(p.bulge) && p.bulge ? { bulge: p.bulge } : {}
444
491
  }));
492
+ const mainPath = trackPath(d.mainPath) ?? [];
445
493
  return {
446
494
  lengthInches: len,
447
495
  loop,
@@ -458,6 +506,7 @@ function docToState(doc, fallbackLength, moduleTracks = []) {
458
506
  poseOverrides,
459
507
  endplateWidths,
460
508
  outline,
509
+ mainPath,
461
510
  crossings: (d.crossings ?? []).map((x) => ({
462
511
  id: x.id,
463
512
  name: x.name ?? "",
@@ -974,7 +1023,9 @@ exports.nextId = nextId;
974
1023
  exports.poseNeedsManual = poseNeedsManual;
975
1024
  exports.poseOverridesFromDoc = poseOverridesFromDoc;
976
1025
  exports.sampleBenchworkOutline = sampleBenchworkOutline;
1026
+ exports.samplePath = samplePath;
977
1027
  exports.scaleFeetToInches = scaleFeetToInches;
978
1028
  exports.stateToDoc = stateToDoc;
1029
+ exports.trackPath = trackPath;
979
1030
  //# sourceMappingURL=index.cjs.map
980
1031
  //# sourceMappingURL=index.cjs.map