@willcgage/module-schematic 0.29.0 → 0.31.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 +167 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +81 -18
- package/dist/index.d.ts +81 -18
- package/dist/index.js +162 -9
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -21,19 +21,40 @@ function moduleSections(doc) {
|
|
|
21
21
|
return (doc?.sections ?? []).filter((sec) => sec && typeof sec.id === "string" && sec.id !== "").map((sec) => {
|
|
22
22
|
const outline = benchworkOutline({ outline: sec.outline });
|
|
23
23
|
const name = typeof sec.name === "string" ? sec.name.trim() : "";
|
|
24
|
+
const len = sec.lengthInches;
|
|
25
|
+
const deg = sec.geometryDegrees;
|
|
26
|
+
const off = sec.geometryOffsetInches;
|
|
24
27
|
return {
|
|
25
28
|
id: sec.id,
|
|
26
29
|
...name ? { name } : {},
|
|
27
|
-
...outline ? { outline } : {}
|
|
30
|
+
...outline ? { outline } : {},
|
|
31
|
+
...typeof len === "number" && Number.isFinite(len) && len > 0 ? { lengthInches: len } : {},
|
|
32
|
+
...sec.geometryType ? { geometryType: sec.geometryType } : {},
|
|
33
|
+
...typeof deg === "number" && Number.isFinite(deg) ? { geometryDegrees: deg } : {},
|
|
34
|
+
...typeof off === "number" && Number.isFinite(off) ? { geometryOffsetInches: off } : {}
|
|
28
35
|
};
|
|
29
36
|
});
|
|
30
37
|
}
|
|
31
|
-
function sectionFootprints(doc) {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
38
|
+
function sectionFootprints(doc, derive) {
|
|
39
|
+
const spans = derive ? sectionSpans(doc) : [];
|
|
40
|
+
const spanOf = new Map(spans.map((sp) => [sp.id, sp]));
|
|
41
|
+
return moduleSections(doc).map((sec) => {
|
|
42
|
+
const name = sec.name ? { name: sec.name } : {};
|
|
43
|
+
if (sec.outline)
|
|
44
|
+
return { id: sec.id, ...name, outline: sampleBenchworkOutline(sec.outline), derived: false };
|
|
45
|
+
const sp = spanOf.get(sec.id);
|
|
46
|
+
if (!sp || !derive) return null;
|
|
47
|
+
const band = sectionBand(
|
|
48
|
+
derive.centerline,
|
|
49
|
+
sp.fromPos,
|
|
50
|
+
sp.toPos,
|
|
51
|
+
derive.widthA,
|
|
52
|
+
derive.widthB,
|
|
53
|
+
derive.offsetA,
|
|
54
|
+
derive.offsetB
|
|
55
|
+
);
|
|
56
|
+
return band.length >= 3 ? { id: sec.id, ...name, outline: band, derived: true } : null;
|
|
57
|
+
}).filter((x) => x !== null);
|
|
37
58
|
}
|
|
38
59
|
function sampleBenchworkOutline(pts, segsPerArc = 20) {
|
|
39
60
|
const n = pts.length;
|
|
@@ -128,6 +149,8 @@ var DEG_FP = Math.PI / 180;
|
|
|
128
149
|
function moduleCenterline(input) {
|
|
129
150
|
const drawn = trackPath(input.mainPath);
|
|
130
151
|
if (drawn) return samplePath(drawn);
|
|
152
|
+
const chained = sectionedCenterline(input);
|
|
153
|
+
if (chained.length >= 2) return chained;
|
|
131
154
|
if (!input.geometryType) return [];
|
|
132
155
|
const L = input.lengthInches > 0 ? input.lengthInches : 24;
|
|
133
156
|
const gt = input.geometryType;
|
|
@@ -145,6 +168,73 @@ function moduleCenterline(input) {
|
|
|
145
168
|
}
|
|
146
169
|
return pts;
|
|
147
170
|
}
|
|
171
|
+
function sectionCenterlineLocal(sec) {
|
|
172
|
+
const L = typeof sec.lengthInches === "number" && sec.lengthInches > 0 ? sec.lengthInches : 0;
|
|
173
|
+
const gt = sec.geometryType || "straight";
|
|
174
|
+
if (L <= 0) return { points: [{ x: 0, y: 0 }], endX: 0, endY: 0, endHeadingDeg: 0 };
|
|
175
|
+
if (gt === "offset") {
|
|
176
|
+
const dy = sec.geometryOffsetInches ?? 0;
|
|
177
|
+
return { points: [{ x: 0, y: 0 }, { x: L, y: dy }], endX: L, endY: dy, endHeadingDeg: 0 };
|
|
178
|
+
}
|
|
179
|
+
const turn = gt === "corner_45" ? 45 : gt === "corner_90" ? 90 : gt === "curve" ? sec.geometryDegrees ?? 0 : 0;
|
|
180
|
+
if (turn === 0) return { points: [{ x: 0, y: 0 }, { x: L, y: 0 }], endX: L, endY: 0, endHeadingDeg: 0 };
|
|
181
|
+
const t = turn * DEG_FP;
|
|
182
|
+
const r = L / t;
|
|
183
|
+
const steps = 12;
|
|
184
|
+
const points = [];
|
|
185
|
+
for (let i = 0; i <= steps; i++) {
|
|
186
|
+
const a = t * i / steps;
|
|
187
|
+
points.push({ x: r * Math.sin(a), y: r * (1 - Math.cos(a)) });
|
|
188
|
+
}
|
|
189
|
+
const last = points[points.length - 1];
|
|
190
|
+
return { points, endX: last.x, endY: last.y, endHeadingDeg: turn };
|
|
191
|
+
}
|
|
192
|
+
function moduleLengthFromSections(doc) {
|
|
193
|
+
const secs = moduleSections(doc).filter(
|
|
194
|
+
(sec) => typeof sec.lengthInches === "number" && sec.lengthInches > 0
|
|
195
|
+
);
|
|
196
|
+
if (!secs.length) return null;
|
|
197
|
+
return secs.reduce((a, sec) => a + sec.lengthInches, 0);
|
|
198
|
+
}
|
|
199
|
+
function sectionSpans(doc) {
|
|
200
|
+
let acc = 0;
|
|
201
|
+
const out = [];
|
|
202
|
+
for (const sec of moduleSections(doc)) {
|
|
203
|
+
const L = typeof sec.lengthInches === "number" && sec.lengthInches > 0 ? sec.lengthInches : 0;
|
|
204
|
+
if (L <= 0) continue;
|
|
205
|
+
out.push({ id: sec.id, ...sec.name ? { name: sec.name } : {}, fromPos: acc, toPos: acc + L });
|
|
206
|
+
acc += L;
|
|
207
|
+
}
|
|
208
|
+
return out;
|
|
209
|
+
}
|
|
210
|
+
function sectionBreaksFromSections(doc) {
|
|
211
|
+
const spans = sectionSpans(doc);
|
|
212
|
+
return spans.slice(0, -1).map((sp) => sp.toPos);
|
|
213
|
+
}
|
|
214
|
+
function sectionedCenterline(doc) {
|
|
215
|
+
const secs = moduleSections(doc).filter(
|
|
216
|
+
(sec) => typeof sec.lengthInches === "number" && sec.lengthInches > 0
|
|
217
|
+
);
|
|
218
|
+
if (!secs.length) return [];
|
|
219
|
+
const out = [];
|
|
220
|
+
let ox = 0;
|
|
221
|
+
let oy = 0;
|
|
222
|
+
let heading = 0;
|
|
223
|
+
for (const sec of secs) {
|
|
224
|
+
const local = sectionCenterlineLocal(sec);
|
|
225
|
+
const c = Math.cos(heading * DEG_FP);
|
|
226
|
+
const sn = Math.sin(heading * DEG_FP);
|
|
227
|
+
for (let i = 0; i < local.points.length; i++) {
|
|
228
|
+
if (i === 0 && out.length) continue;
|
|
229
|
+
const p = local.points[i];
|
|
230
|
+
out.push({ x: ox + p.x * c - p.y * sn, y: oy + p.x * sn + p.y * c });
|
|
231
|
+
}
|
|
232
|
+
ox += local.endX * c - local.endY * sn;
|
|
233
|
+
oy += local.endX * sn + local.endY * c;
|
|
234
|
+
heading += local.endHeadingDeg;
|
|
235
|
+
}
|
|
236
|
+
return out;
|
|
237
|
+
}
|
|
148
238
|
function centerlineNormals(center) {
|
|
149
239
|
return center.map((_, i) => {
|
|
150
240
|
const a = center[Math.max(0, i - 1)];
|
|
@@ -180,6 +270,63 @@ function benchworkBand(center, widthA = FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES
|
|
|
180
270
|
}));
|
|
181
271
|
return [...left, ...right.reverse()];
|
|
182
272
|
}
|
|
273
|
+
function sliceCenterline(center, fromPos, toPos) {
|
|
274
|
+
if (center.length < 2) return [];
|
|
275
|
+
const cum = [0];
|
|
276
|
+
for (let i = 1; i < center.length; i++)
|
|
277
|
+
cum.push(cum[i - 1] + Math.hypot(center[i].x - center[i - 1].x, center[i].y - center[i - 1].y));
|
|
278
|
+
const total = cum[cum.length - 1];
|
|
279
|
+
const a = Math.max(0, Math.min(total, Math.min(fromPos, toPos)));
|
|
280
|
+
const b = Math.max(0, Math.min(total, Math.max(fromPos, toPos)));
|
|
281
|
+
if (b - a <= 0) return [];
|
|
282
|
+
const at = (d) => {
|
|
283
|
+
for (let i = 1; i < center.length; i++) {
|
|
284
|
+
if (d <= cum[i] || i === center.length - 1) {
|
|
285
|
+
const seg = cum[i] - cum[i - 1] || 1;
|
|
286
|
+
const t = Math.max(0, Math.min(1, (d - cum[i - 1]) / seg));
|
|
287
|
+
return {
|
|
288
|
+
x: center[i - 1].x + (center[i].x - center[i - 1].x) * t,
|
|
289
|
+
y: center[i - 1].y + (center[i].y - center[i - 1].y) * t
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
return center[center.length - 1];
|
|
294
|
+
};
|
|
295
|
+
const out = [at(a)];
|
|
296
|
+
for (let i = 0; i < center.length; i++) {
|
|
297
|
+
if (cum[i] > a && cum[i] < b) out.push({ x: center[i].x, y: center[i].y });
|
|
298
|
+
}
|
|
299
|
+
out.push(at(b));
|
|
300
|
+
return out;
|
|
301
|
+
}
|
|
302
|
+
function sectionBand(center, fromPos, toPos, widthA = FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES, widthB = FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES, offsetA = 0, offsetB = 0) {
|
|
303
|
+
const slice = sliceCenterline(center, fromPos, toPos);
|
|
304
|
+
if (slice.length < 2) return [];
|
|
305
|
+
let total = 0;
|
|
306
|
+
for (let i = 1; i < center.length; i++)
|
|
307
|
+
total += Math.hypot(center[i].x - center[i - 1].x, center[i].y - center[i - 1].y);
|
|
308
|
+
total = total || 1;
|
|
309
|
+
const lo = Math.min(fromPos, toPos);
|
|
310
|
+
let acc = 0;
|
|
311
|
+
const fr = [0];
|
|
312
|
+
for (let i = 1; i < slice.length; i++) {
|
|
313
|
+
acc += Math.hypot(slice[i].x - slice[i - 1].x, slice[i].y - slice[i - 1].y);
|
|
314
|
+
fr.push(acc);
|
|
315
|
+
}
|
|
316
|
+
const f = fr.map((d) => Math.max(0, Math.min(1, (lo + d) / total)));
|
|
317
|
+
const n = centerlineNormals(slice);
|
|
318
|
+
const half = (i) => (widthA * (1 - f[i]) + widthB * f[i]) / 2;
|
|
319
|
+
const off = (i) => offsetA * (1 - f[i]) + offsetB * f[i];
|
|
320
|
+
const left = slice.map((p, i) => ({
|
|
321
|
+
x: p.x + n[i].x * (off(i) + half(i)),
|
|
322
|
+
y: p.y + n[i].y * (off(i) + half(i))
|
|
323
|
+
}));
|
|
324
|
+
const right = slice.map((p, i) => ({
|
|
325
|
+
x: p.x + n[i].x * (off(i) - half(i)),
|
|
326
|
+
y: p.y + n[i].y * (off(i) - half(i))
|
|
327
|
+
}));
|
|
328
|
+
return [...left, ...right.reverse()];
|
|
329
|
+
}
|
|
183
330
|
function endplateFaceSegments(center, widthA = FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES, widthB = FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES, offsetA = 0, offsetB = 0) {
|
|
184
331
|
if (center.length < 2) return [];
|
|
185
332
|
const n = centerlineNormals(center);
|
|
@@ -197,7 +344,13 @@ function moduleFootprint(input) {
|
|
|
197
344
|
const authored = benchworkOutline(input);
|
|
198
345
|
const offA = input.endplateTrackOffsets?.["A"] ?? 0;
|
|
199
346
|
const offB = input.endplateTrackOffsets?.["B"] ?? 0;
|
|
200
|
-
const sectionOutlines = sectionFootprints(input
|
|
347
|
+
const sectionOutlines = sectionFootprints(input, {
|
|
348
|
+
centerline,
|
|
349
|
+
widthA,
|
|
350
|
+
widthB,
|
|
351
|
+
offsetA: offA,
|
|
352
|
+
offsetB: offB
|
|
353
|
+
});
|
|
201
354
|
return {
|
|
202
355
|
centerline,
|
|
203
356
|
band: benchworkBand(centerline, widthA, widthB, offA, offB),
|
|
@@ -1149,6 +1302,7 @@ exports.isTransitionTurnout = isTransitionTurnout;
|
|
|
1149
1302
|
exports.moduleCenterline = moduleCenterline;
|
|
1150
1303
|
exports.moduleFeatures = moduleFeatures;
|
|
1151
1304
|
exports.moduleFootprint = moduleFootprint;
|
|
1305
|
+
exports.moduleLengthFromSections = moduleLengthFromSections;
|
|
1152
1306
|
exports.moduleSections = moduleSections;
|
|
1153
1307
|
exports.nextId = nextId;
|
|
1154
1308
|
exports.poseNeedsManual = poseNeedsManual;
|
|
@@ -1156,7 +1310,12 @@ exports.poseOverridesFromDoc = poseOverridesFromDoc;
|
|
|
1156
1310
|
exports.sampleBenchworkOutline = sampleBenchworkOutline;
|
|
1157
1311
|
exports.samplePath = samplePath;
|
|
1158
1312
|
exports.scaleFeetToInches = scaleFeetToInches;
|
|
1313
|
+
exports.sectionBand = sectionBand;
|
|
1314
|
+
exports.sectionBreaksFromSections = sectionBreaksFromSections;
|
|
1159
1315
|
exports.sectionFootprints = sectionFootprints;
|
|
1316
|
+
exports.sectionSpans = sectionSpans;
|
|
1317
|
+
exports.sectionedCenterline = sectionedCenterline;
|
|
1318
|
+
exports.sliceCenterline = sliceCenterline;
|
|
1160
1319
|
exports.stateToDoc = stateToDoc;
|
|
1161
1320
|
exports.trackPath = trackPath;
|
|
1162
1321
|
//# sourceMappingURL=index.cjs.map
|