@willcgage/module-schematic 0.26.0 → 0.28.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
@@ -3,6 +3,8 @@
3
3
  // src/index.ts
4
4
  var FREEMO_ENDPLATE_WIDTH_MIN_INCHES = 12;
5
5
  var FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES = 24;
6
+ var FREEMO_TRACK_SPACING_INCHES = 1.125;
7
+ var FREEMO_ENDPLATE_TRACK_FASCIA_CLEARANCE_INCHES = 4;
6
8
  function endplateWidthInches(ep) {
7
9
  const w = ep?.widthInches;
8
10
  return typeof w === "number" && w > 0 ? w : FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES;
@@ -144,37 +146,79 @@ function centerlineFractions(center) {
144
146
  const total = cum[cum.length - 1] || 1;
145
147
  return cum.map((d) => d / total);
146
148
  }
147
- function benchworkBand(center, widthA = FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES, widthB = FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES) {
149
+ function benchworkBand(center, widthA = FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES, widthB = FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES, offsetA = 0, offsetB = 0) {
148
150
  if (center.length < 2) return [];
149
151
  const n = centerlineNormals(center);
150
152
  const f = centerlineFractions(center);
151
153
  const half = (i) => (widthA * (1 - f[i]) + widthB * f[i]) / 2;
152
- const left = center.map((p, i) => ({ x: p.x + n[i].x * half(i), y: p.y + n[i].y * half(i) }));
153
- const right = center.map((p, i) => ({ x: p.x - n[i].x * half(i), y: p.y - n[i].y * half(i) }));
154
+ const off = (i) => offsetA * (1 - f[i]) + offsetB * f[i];
155
+ const left = center.map((p, i) => ({
156
+ x: p.x + n[i].x * (off(i) + half(i)),
157
+ y: p.y + n[i].y * (off(i) + half(i))
158
+ }));
159
+ const right = center.map((p, i) => ({
160
+ x: p.x + n[i].x * (off(i) - half(i)),
161
+ y: p.y + n[i].y * (off(i) - half(i))
162
+ }));
154
163
  return [...left, ...right.reverse()];
155
164
  }
156
- function endplateFaceSegments(center, widthA = FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES, widthB = FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES) {
165
+ function endplateFaceSegments(center, widthA = FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES, widthB = FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES, offsetA = 0, offsetB = 0) {
157
166
  if (center.length < 2) return [];
158
167
  const n = centerlineNormals(center);
159
- const face = (i, w) => ({
160
- p1: { x: center[i].x + n[i].x * (w / 2), y: center[i].y + n[i].y * (w / 2) },
161
- p2: { x: center[i].x - n[i].x * (w / 2), y: center[i].y - n[i].y * (w / 2) },
168
+ const face = (i, w, o) => ({
169
+ p1: { x: center[i].x + n[i].x * (o + w / 2), y: center[i].y + n[i].y * (o + w / 2) },
170
+ p2: { x: center[i].x + n[i].x * (o - w / 2), y: center[i].y + n[i].y * (o - w / 2) },
162
171
  mid: { x: center[i].x, y: center[i].y }
163
172
  });
164
- return [face(0, widthA), face(center.length - 1, widthB)];
173
+ return [face(0, widthA, offsetA), face(center.length - 1, widthB, offsetB)];
165
174
  }
166
175
  function moduleFootprint(input) {
167
176
  const centerline = moduleCenterline(input);
168
177
  const widthA = endplateWidthFor(input.endplateWidths, "A");
169
178
  const widthB = endplateWidthFor(input.endplateWidths, "B");
170
179
  const authored = benchworkOutline(input);
180
+ const offA = input.endplateTrackOffsets?.["A"] ?? 0;
181
+ const offB = input.endplateTrackOffsets?.["B"] ?? 0;
171
182
  return {
172
183
  centerline,
173
- band: benchworkBand(centerline, widthA, widthB),
174
- endplateFaces: endplateFaceSegments(centerline, widthA, widthB),
184
+ band: benchworkBand(centerline, widthA, widthB, offA, offB),
185
+ endplateFaces: endplateFaceSegments(centerline, widthA, widthB, offA, offB),
175
186
  outline: authored ? sampleBenchworkOutline(authored) : null
176
187
  };
177
188
  }
189
+ function endplateTrackOffsetFor(config, authoredTrackOffset) {
190
+ const v = -endplateTrackOffsetInches(authoredTrackOffset, config);
191
+ return v === 0 ? 0 : v;
192
+ }
193
+ function endplateTrackOffsetInches(authored, config) {
194
+ if (typeof authored === "number" && Number.isFinite(authored)) return authored;
195
+ return config === "double" ? -FREEMO_TRACK_SPACING_INCHES / 2 : 0;
196
+ }
197
+ function checkEndplateWidth(input) {
198
+ const width = endplateWidthInches(input);
199
+ const issues = [];
200
+ if (width < FREEMO_ENDPLATE_WIDTH_MIN_INCHES) {
201
+ issues.push({
202
+ code: "narrow",
203
+ message: `Endplate is ${round2(width)}\u2033 wide \u2014 the standard requires at least ${FREEMO_ENDPLATE_WIDTH_MIN_INCHES}\u2033.`,
204
+ requiredInches: FREEMO_ENDPLATE_WIDTH_MIN_INCHES
205
+ });
206
+ }
207
+ const off = input.trackOffsetInches ?? 0;
208
+ const centres = input.config === "double" ? [off - FREEMO_TRACK_SPACING_INCHES / 2, off + FREEMO_TRACK_SPACING_INCHES / 2] : [off];
209
+ const worst = Math.max(...centres.map((c) => Math.abs(c)));
210
+ const clearance = width / 2 - worst;
211
+ if (clearance < FREEMO_ENDPLATE_TRACK_FASCIA_CLEARANCE_INCHES) {
212
+ const required = 2 * (worst + FREEMO_ENDPLATE_TRACK_FASCIA_CLEARANCE_INCHES);
213
+ issues.push({
214
+ code: "clearance",
215
+ 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." : "."),
216
+ requiredInches: required
217
+ });
218
+ }
219
+ return issues;
220
+ }
221
+ var round2 = (n) => Math.round(n * 100) / 100;
178
222
  function endplateWidthFor(widths, id) {
179
223
  const w = widths?.[id];
180
224
  return typeof w === "number" && w > 0 ? w : FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES;
@@ -217,6 +261,7 @@ function emptyEditorState(lengthInches) {
217
261
  branches: [],
218
262
  poseOverrides: {},
219
263
  endplateWidths: {},
264
+ endplateTrackOffsets: {},
220
265
  outline: [],
221
266
  sectionBreaks: [],
222
267
  controlPoints: [],
@@ -299,10 +344,14 @@ function withPoses(endplates, overrides) {
299
344
  (e) => overrides[e.id] ? { ...e, pose: overrides[e.id] } : e
300
345
  );
301
346
  }
302
- function withWidths(endplates, widths) {
347
+ function withWidths(endplates, widths, offsets = {}) {
303
348
  return endplates.map((e) => {
304
349
  const w = widths[e.id];
305
- return typeof w === "number" && w > 0 ? { ...e, widthInches: w } : e;
350
+ const o = offsets[e.id];
351
+ let out = e;
352
+ if (typeof w === "number" && w > 0) out = { ...out, widthInches: w };
353
+ if (typeof o === "number" && Number.isFinite(o)) out = { ...out, trackOffsetInches: o };
354
+ return out;
306
355
  });
307
356
  }
308
357
  function stateToDoc(state, recordNumber) {
@@ -351,7 +400,8 @@ function stateToDoc(state, recordNumber) {
351
400
  ],
352
401
  state.poseOverrides
353
402
  ),
354
- state.endplateWidths
403
+ state.endplateWidths,
404
+ state.endplateTrackOffsets
355
405
  ),
356
406
  tracks: [
357
407
  state.loop ? (
@@ -510,9 +560,12 @@ function docToState(doc, fallbackLength, moduleTracks = []) {
510
560
  );
511
561
  const poseOverrides = poseOverridesFromDoc(d);
512
562
  const endplateWidths = {};
563
+ const endplateTrackOffsets = {};
513
564
  for (const e of d.endplates ?? []) {
514
565
  if (typeof e.widthInches === "number" && e.widthInches > 0)
515
566
  endplateWidths[e.id] = e.widthInches;
567
+ if (typeof e.trackOffsetInches === "number" && Number.isFinite(e.trackOffsetInches))
568
+ endplateTrackOffsets[e.id] = e.trackOffsetInches;
516
569
  }
517
570
  const outline = (d.outline ?? []).filter((p) => p && Number.isFinite(p.x) && Number.isFinite(p.y)).map((p) => ({
518
571
  x: p.x,
@@ -536,6 +589,7 @@ function docToState(doc, fallbackLength, moduleTracks = []) {
536
589
  })),
537
590
  poseOverrides,
538
591
  endplateWidths,
592
+ endplateTrackOffsets,
539
593
  outline,
540
594
  sectionBreaks: (d.sectionBreaks ?? []).filter((n) => Number.isFinite(n)).map((n) => sc(n)),
541
595
  mainPath,
@@ -1039,8 +1093,10 @@ function poseOverridesFromDoc(doc) {
1039
1093
  return out;
1040
1094
  }
1041
1095
 
1096
+ exports.FREEMO_ENDPLATE_TRACK_FASCIA_CLEARANCE_INCHES = FREEMO_ENDPLATE_TRACK_FASCIA_CLEARANCE_INCHES;
1042
1097
  exports.FREEMO_ENDPLATE_WIDTH_MIN_INCHES = FREEMO_ENDPLATE_WIDTH_MIN_INCHES;
1043
1098
  exports.FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES = FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES;
1099
+ exports.FREEMO_TRACK_SPACING_INCHES = FREEMO_TRACK_SPACING_INCHES;
1044
1100
  exports.MAIN2_TRACK_ID = MAIN2_TRACK_ID;
1045
1101
  exports.MAIN_TRACK_ID = MAIN_TRACK_ID;
1046
1102
  exports.N_CAR_LENGTH_INCHES = N_CAR_LENGTH_INCHES;
@@ -1052,11 +1108,14 @@ exports.buildCrossover = buildCrossover;
1052
1108
  exports.buildPassingSiding = buildPassingSiding;
1053
1109
  exports.buildTransition = buildTransition;
1054
1110
  exports.carCapacity = carCapacity;
1111
+ exports.checkEndplateWidth = checkEndplateWidth;
1055
1112
  exports.deriveEndplatePoses = deriveEndplatePoses;
1056
1113
  exports.divergeSideForHand = divergeSideForHand;
1057
1114
  exports.docToState = docToState;
1058
1115
  exports.emptyEditorState = emptyEditorState;
1059
1116
  exports.endplateFaceSegments = endplateFaceSegments;
1117
+ exports.endplateTrackOffsetFor = endplateTrackOffsetFor;
1118
+ exports.endplateTrackOffsetInches = endplateTrackOffsetInches;
1060
1119
  exports.endplateWidthInches = endplateWidthInches;
1061
1120
  exports.geometryTurnDegrees = geometryTurnDegrees;
1062
1121
  exports.inchesToScaleFeet = inchesToScaleFeet;