@willcgage/module-schematic 0.17.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 }];
@@ -149,6 +190,11 @@ function inchesToScaleFeet(inches, ratio = N_SCALE_RATIO) {
149
190
  function scaleFeetToInches(feet, ratio = N_SCALE_RATIO) {
150
191
  return feet * 12 / ratio;
151
192
  }
193
+ var N_CAR_LENGTH_INCHES = 3.3;
194
+ function carCapacity(fromPos, toPos, carLengthInches = N_CAR_LENGTH_INCHES) {
195
+ if (!(carLengthInches > 0)) return 0;
196
+ return Math.max(0, Math.floor(Math.abs(toPos - fromPos) / carLengthInches));
197
+ }
152
198
  function asModuleSchematic(x) {
153
199
  if (!x || typeof x !== "object") return null;
154
200
  const d = x;
@@ -170,7 +216,9 @@ function emptyEditorState(lengthInches) {
170
216
  poseOverrides: {},
171
217
  endplateWidths: {},
172
218
  outline: [],
173
- controlPoints: []
219
+ controlPoints: [],
220
+ industries: [],
221
+ mainPath: []
174
222
  };
175
223
  }
176
224
  function isTransitionTurnout(t) {
@@ -307,7 +355,8 @@ function stateToDoc(state, recordNumber) {
307
355
  moduleTrackId: t.moduleTrackId,
308
356
  trackName: t.trackName || void 0,
309
357
  capacityFeet: Math.round(inchesToScaleFeet(Math.abs(t.toPos - t.fromPos))),
310
- ...state.loop && t.inLoop ? { inLoop: true } : {}
358
+ ...state.loop && t.inLoop ? { inLoop: true } : {},
359
+ ...t.path && t.path.length >= 2 ? { path: t.path } : {}
311
360
  }))
312
361
  ],
313
362
  turnouts: state.turnouts.map((t) => ({
@@ -340,9 +389,26 @@ function stateToDoc(state, recordNumber) {
340
389
  side: s.side
341
390
  }))
342
391
  })),
392
+ // Industries — car-spot spans on a track; only when any are authored.
393
+ ...state.industries.length > 0 ? {
394
+ industries: state.industries.map((ind) => ({
395
+ id: ind.id,
396
+ name: ind.name,
397
+ ...ind.type ? { type: ind.type } : {},
398
+ track: ind.track,
399
+ fromPos: ind.fromPos,
400
+ toPos: ind.toPos,
401
+ side: ind.side,
402
+ ...ind.labelMode && ind.labelMode !== "none" ? { labelMode: ind.labelMode } : {},
403
+ ...ind.carTypes.length ? { carTypes: ind.carTypes } : {},
404
+ moduleIndustryId: ind.moduleIndustryId
405
+ }))
406
+ } : {},
343
407
  // Benchwork footprint outline (module-local inches); only when it's a real
344
408
  // ring (≥ 3 vertices).
345
- ...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 } : {}
346
412
  };
347
413
  }
348
414
  function docToState(doc, fallbackLength, moduleTracks = []) {
@@ -372,7 +438,9 @@ function docToState(doc, fallbackLength, moduleTracks = []) {
372
438
  toPos: t.toPos != null ? sc(t.toPos) : len,
373
439
  moduleTrackId,
374
440
  trackName: t.trackName ?? nameOf(moduleTrackId),
375
- ...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) } : {}
376
444
  });
377
445
  }
378
446
  }
@@ -421,6 +489,7 @@ function docToState(doc, fallbackLength, moduleTracks = []) {
421
489
  y: p.y,
422
490
  ...Number.isFinite(p.bulge) && p.bulge ? { bulge: p.bulge } : {}
423
491
  }));
492
+ const mainPath = trackPath(d.mainPath) ?? [];
424
493
  return {
425
494
  lengthInches: len,
426
495
  loop,
@@ -437,6 +506,7 @@ function docToState(doc, fallbackLength, moduleTracks = []) {
437
506
  poseOverrides,
438
507
  endplateWidths,
439
508
  outline,
509
+ mainPath,
440
510
  crossings: (d.crossings ?? []).map((x) => ({
441
511
  id: x.id,
442
512
  name: x.name ?? "",
@@ -453,7 +523,19 @@ function docToState(doc, fallbackLength, moduleTracks = []) {
453
523
  divergeTrack: t.divergeTrack,
454
524
  kind: t.kind ?? "right"
455
525
  })),
456
- controlPoints: readControlPoints(d, sc)
526
+ controlPoints: readControlPoints(d, sc),
527
+ industries: (d.industries ?? []).map((ind) => ({
528
+ id: ind.id,
529
+ name: ind.name ?? "",
530
+ type: ind.type ?? "",
531
+ track: ind.track,
532
+ fromPos: sc(ind.fromPos ?? 0),
533
+ toPos: ind.toPos != null ? sc(ind.toPos) : len,
534
+ side: ind.side ?? "above",
535
+ labelMode: ind.labelMode ?? "none",
536
+ carTypes: Array.isArray(ind.carTypes) ? ind.carTypes : [],
537
+ moduleIndustryId: ind.moduleIndustryId ?? null
538
+ }))
457
539
  };
458
540
  }
459
541
  function readControlPoints(d, sc = (p) => p) {
@@ -752,6 +834,22 @@ function moduleFeatures(doc) {
752
834
  posFrac: clampFrac(e.at.pos),
753
835
  side: e.at.side === "down" ? "down" : "up"
754
836
  }));
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
+ };
852
+ });
755
853
  const allLanes = [
756
854
  0,
757
855
  doubleMain ? 1 : 0,
@@ -792,6 +890,7 @@ function moduleFeatures(doc) {
792
890
  crossings,
793
891
  crossovers,
794
892
  branchConnectors,
893
+ industries,
795
894
  laneMin: Math.min(...allLanes),
796
895
  laneMax: Math.max(...allLanes)
797
896
  };
@@ -898,6 +997,7 @@ exports.FREEMO_ENDPLATE_WIDTH_MIN_INCHES = FREEMO_ENDPLATE_WIDTH_MIN_INCHES;
898
997
  exports.FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES = FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES;
899
998
  exports.MAIN2_TRACK_ID = MAIN2_TRACK_ID;
900
999
  exports.MAIN_TRACK_ID = MAIN_TRACK_ID;
1000
+ exports.N_CAR_LENGTH_INCHES = N_CAR_LENGTH_INCHES;
901
1001
  exports.N_SCALE_RATIO = N_SCALE_RATIO;
902
1002
  exports.asModuleSchematic = asModuleSchematic;
903
1003
  exports.benchworkBand = benchworkBand;
@@ -905,6 +1005,7 @@ exports.benchworkOutline = benchworkOutline;
905
1005
  exports.buildCrossover = buildCrossover;
906
1006
  exports.buildPassingSiding = buildPassingSiding;
907
1007
  exports.buildTransition = buildTransition;
1008
+ exports.carCapacity = carCapacity;
908
1009
  exports.deriveEndplatePoses = deriveEndplatePoses;
909
1010
  exports.divergeSideForHand = divergeSideForHand;
910
1011
  exports.docToState = docToState;
@@ -922,7 +1023,9 @@ exports.nextId = nextId;
922
1023
  exports.poseNeedsManual = poseNeedsManual;
923
1024
  exports.poseOverridesFromDoc = poseOverridesFromDoc;
924
1025
  exports.sampleBenchworkOutline = sampleBenchworkOutline;
1026
+ exports.samplePath = samplePath;
925
1027
  exports.scaleFeetToInches = scaleFeetToInches;
926
1028
  exports.stateToDoc = stateToDoc;
1029
+ exports.trackPath = trackPath;
927
1030
  //# sourceMappingURL=index.cjs.map
928
1031
  //# sourceMappingURL=index.cjs.map