@willcgage/module-schematic 0.15.0 → 0.17.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 +137 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +57 -3
- package/dist/index.d.ts +57 -3
- package/dist/index.js +133 -7
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -8,11 +8,135 @@ function endplateWidthInches(ep) {
|
|
|
8
8
|
return typeof w === "number" && w > 0 ? w : FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES;
|
|
9
9
|
}
|
|
10
10
|
function benchworkOutline(doc) {
|
|
11
|
-
const pts = (doc?.outline ?? []).filter(
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
const pts = (doc?.outline ?? []).filter((p) => p && Number.isFinite(p.x) && Number.isFinite(p.y)).map((p) => ({
|
|
12
|
+
x: p.x,
|
|
13
|
+
y: p.y,
|
|
14
|
+
...Number.isFinite(p.bulge) && p.bulge ? { bulge: p.bulge } : {}
|
|
15
|
+
}));
|
|
14
16
|
return pts.length >= 3 ? pts : null;
|
|
15
17
|
}
|
|
18
|
+
function sampleBenchworkOutline(pts, segsPerArc = 20) {
|
|
19
|
+
const n = pts.length;
|
|
20
|
+
if (n < 2) return pts.map((p) => ({ x: p.x, y: p.y }));
|
|
21
|
+
const out = [];
|
|
22
|
+
for (let i = 0; i < n; i++) {
|
|
23
|
+
const p0 = pts[i];
|
|
24
|
+
const p1 = pts[(i + 1) % n];
|
|
25
|
+
out.push({ x: p0.x, y: p0.y });
|
|
26
|
+
const bulge = p0.bulge ?? 0;
|
|
27
|
+
if (!bulge) continue;
|
|
28
|
+
const dx = p1.x - p0.x;
|
|
29
|
+
const dy = p1.y - p0.y;
|
|
30
|
+
const c = Math.hypot(dx, dy);
|
|
31
|
+
if (c < 1e-6) continue;
|
|
32
|
+
const nx = -dy / c;
|
|
33
|
+
const ny = dx / c;
|
|
34
|
+
const mid = { x: (p0.x + p1.x) / 2 + nx * bulge, y: (p0.y + p1.y) / 2 + ny * bulge };
|
|
35
|
+
const circ = circleThrough(p0, mid, p1);
|
|
36
|
+
if (!circ) continue;
|
|
37
|
+
const a0 = Math.atan2(p0.y - circ.cy, p0.x - circ.cx);
|
|
38
|
+
const am = Math.atan2(mid.y - circ.cy, mid.x - circ.cx);
|
|
39
|
+
const a1 = Math.atan2(p1.y - circ.cy, p1.x - circ.cx);
|
|
40
|
+
const sweep = arcSweep(a0, a1, am);
|
|
41
|
+
for (let s = 1; s < segsPerArc; s++) {
|
|
42
|
+
const a = a0 + sweep * s / segsPerArc;
|
|
43
|
+
out.push({ x: circ.cx + circ.r * Math.cos(a), y: circ.cy + circ.r * Math.sin(a) });
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return out;
|
|
47
|
+
}
|
|
48
|
+
function circleThrough(a, b, c) {
|
|
49
|
+
const d = 2 * (a.x * (b.y - c.y) + b.x * (c.y - a.y) + c.x * (a.y - b.y));
|
|
50
|
+
if (Math.abs(d) < 1e-9) return null;
|
|
51
|
+
const a2 = a.x * a.x + a.y * a.y;
|
|
52
|
+
const b2 = b.x * b.x + b.y * b.y;
|
|
53
|
+
const c2 = c.x * c.x + c.y * c.y;
|
|
54
|
+
const cx = (a2 * (b.y - c.y) + b2 * (c.y - a.y) + c2 * (a.y - b.y)) / d;
|
|
55
|
+
const cy = (a2 * (c.x - b.x) + b2 * (a.x - c.x) + c2 * (b.x - a.x)) / d;
|
|
56
|
+
return { cx, cy, r: Math.hypot(a.x - cx, a.y - cy) };
|
|
57
|
+
}
|
|
58
|
+
function arcSweep(a0, a1, am) {
|
|
59
|
+
const norm = (x) => {
|
|
60
|
+
let v = (x - a0) % (2 * Math.PI);
|
|
61
|
+
if (v < 0) v += 2 * Math.PI;
|
|
62
|
+
return v;
|
|
63
|
+
};
|
|
64
|
+
const m = norm(am);
|
|
65
|
+
const one = norm(a1);
|
|
66
|
+
return m <= one ? one : one - 2 * Math.PI;
|
|
67
|
+
}
|
|
68
|
+
var DEG_FP = Math.PI / 180;
|
|
69
|
+
function moduleCenterline(input) {
|
|
70
|
+
const L = input.lengthInches > 0 ? input.lengthInches : 24;
|
|
71
|
+
const gt = input.geometryType;
|
|
72
|
+
if (gt === "dead_end") return [{ x: 0, y: 0 }];
|
|
73
|
+
if (gt === "offset") return [{ x: 0, y: 0 }, { x: L, y: input.geometryOffsetInches ?? 0 }];
|
|
74
|
+
const turn = gt === "corner_45" ? 45 : gt === "corner_90" ? 90 : gt === "curve" ? input.geometryDegrees ?? 0 : 0;
|
|
75
|
+
if (turn === 0) return [{ x: 0, y: 0 }, { x: L, y: 0 }];
|
|
76
|
+
const t = turn * DEG_FP;
|
|
77
|
+
const r = L / t;
|
|
78
|
+
const steps = 12;
|
|
79
|
+
const pts = [];
|
|
80
|
+
for (let i = 0; i <= steps; i++) {
|
|
81
|
+
const a = t * i / steps;
|
|
82
|
+
pts.push({ x: r * Math.sin(a), y: r * (1 - Math.cos(a)) });
|
|
83
|
+
}
|
|
84
|
+
return pts;
|
|
85
|
+
}
|
|
86
|
+
function centerlineNormals(center) {
|
|
87
|
+
return center.map((_, i) => {
|
|
88
|
+
const a = center[Math.max(0, i - 1)];
|
|
89
|
+
const b = center[Math.min(center.length - 1, i + 1)];
|
|
90
|
+
let dx = b.x - a.x;
|
|
91
|
+
let dy = b.y - a.y;
|
|
92
|
+
const len = Math.hypot(dx, dy) || 1;
|
|
93
|
+
dx /= len;
|
|
94
|
+
dy /= len;
|
|
95
|
+
return { x: -dy, y: dx };
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
function centerlineFractions(center) {
|
|
99
|
+
const cum = [0];
|
|
100
|
+
for (let i = 1; i < center.length; i++)
|
|
101
|
+
cum.push(cum[i - 1] + Math.hypot(center[i].x - center[i - 1].x, center[i].y - center[i - 1].y));
|
|
102
|
+
const total = cum[cum.length - 1] || 1;
|
|
103
|
+
return cum.map((d) => d / total);
|
|
104
|
+
}
|
|
105
|
+
function benchworkBand(center, widthA = FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES, widthB = FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES) {
|
|
106
|
+
if (center.length < 2) return [];
|
|
107
|
+
const n = centerlineNormals(center);
|
|
108
|
+
const f = centerlineFractions(center);
|
|
109
|
+
const half = (i) => (widthA * (1 - f[i]) + widthB * f[i]) / 2;
|
|
110
|
+
const left = center.map((p, i) => ({ x: p.x + n[i].x * half(i), y: p.y + n[i].y * half(i) }));
|
|
111
|
+
const right = center.map((p, i) => ({ x: p.x - n[i].x * half(i), y: p.y - n[i].y * half(i) }));
|
|
112
|
+
return [...left, ...right.reverse()];
|
|
113
|
+
}
|
|
114
|
+
function endplateFaceSegments(center, widthA = FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES, widthB = FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES) {
|
|
115
|
+
if (center.length < 2) return [];
|
|
116
|
+
const n = centerlineNormals(center);
|
|
117
|
+
const face = (i, w) => ({
|
|
118
|
+
p1: { x: center[i].x + n[i].x * (w / 2), y: center[i].y + n[i].y * (w / 2) },
|
|
119
|
+
p2: { x: center[i].x - n[i].x * (w / 2), y: center[i].y - n[i].y * (w / 2) },
|
|
120
|
+
mid: { x: center[i].x, y: center[i].y }
|
|
121
|
+
});
|
|
122
|
+
return [face(0, widthA), face(center.length - 1, widthB)];
|
|
123
|
+
}
|
|
124
|
+
function moduleFootprint(input) {
|
|
125
|
+
const centerline = moduleCenterline(input);
|
|
126
|
+
const widthA = endplateWidthFor(input.endplateWidths, "A");
|
|
127
|
+
const widthB = endplateWidthFor(input.endplateWidths, "B");
|
|
128
|
+
const authored = benchworkOutline(input);
|
|
129
|
+
return {
|
|
130
|
+
centerline,
|
|
131
|
+
band: benchworkBand(centerline, widthA, widthB),
|
|
132
|
+
endplateFaces: endplateFaceSegments(centerline, widthA, widthB),
|
|
133
|
+
outline: authored ? sampleBenchworkOutline(authored) : null
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
function endplateWidthFor(widths, id) {
|
|
137
|
+
const w = widths?.[id];
|
|
138
|
+
return typeof w === "number" && w > 0 ? w : FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES;
|
|
139
|
+
}
|
|
16
140
|
function isLoopDoc(doc) {
|
|
17
141
|
return doc.loop === true || doc.endplates.length === 1;
|
|
18
142
|
}
|
|
@@ -292,9 +416,11 @@ function docToState(doc, fallbackLength, moduleTracks = []) {
|
|
|
292
416
|
if (typeof e.widthInches === "number" && e.widthInches > 0)
|
|
293
417
|
endplateWidths[e.id] = e.widthInches;
|
|
294
418
|
}
|
|
295
|
-
const outline = (d.outline ?? []).filter(
|
|
296
|
-
|
|
297
|
-
|
|
419
|
+
const outline = (d.outline ?? []).filter((p) => p && Number.isFinite(p.x) && Number.isFinite(p.y)).map((p) => ({
|
|
420
|
+
x: p.x,
|
|
421
|
+
y: p.y,
|
|
422
|
+
...Number.isFinite(p.bulge) && p.bulge ? { bulge: p.bulge } : {}
|
|
423
|
+
}));
|
|
298
424
|
return {
|
|
299
425
|
lengthInches: len,
|
|
300
426
|
loop,
|
|
@@ -774,6 +900,7 @@ exports.MAIN2_TRACK_ID = MAIN2_TRACK_ID;
|
|
|
774
900
|
exports.MAIN_TRACK_ID = MAIN_TRACK_ID;
|
|
775
901
|
exports.N_SCALE_RATIO = N_SCALE_RATIO;
|
|
776
902
|
exports.asModuleSchematic = asModuleSchematic;
|
|
903
|
+
exports.benchworkBand = benchworkBand;
|
|
777
904
|
exports.benchworkOutline = benchworkOutline;
|
|
778
905
|
exports.buildCrossover = buildCrossover;
|
|
779
906
|
exports.buildPassingSiding = buildPassingSiding;
|
|
@@ -782,15 +909,19 @@ exports.deriveEndplatePoses = deriveEndplatePoses;
|
|
|
782
909
|
exports.divergeSideForHand = divergeSideForHand;
|
|
783
910
|
exports.docToState = docToState;
|
|
784
911
|
exports.emptyEditorState = emptyEditorState;
|
|
912
|
+
exports.endplateFaceSegments = endplateFaceSegments;
|
|
785
913
|
exports.endplateWidthInches = endplateWidthInches;
|
|
786
914
|
exports.geometryTurnDegrees = geometryTurnDegrees;
|
|
787
915
|
exports.inchesToScaleFeet = inchesToScaleFeet;
|
|
788
916
|
exports.isLoopDoc = isLoopDoc;
|
|
789
917
|
exports.isTransitionTurnout = isTransitionTurnout;
|
|
918
|
+
exports.moduleCenterline = moduleCenterline;
|
|
790
919
|
exports.moduleFeatures = moduleFeatures;
|
|
920
|
+
exports.moduleFootprint = moduleFootprint;
|
|
791
921
|
exports.nextId = nextId;
|
|
792
922
|
exports.poseNeedsManual = poseNeedsManual;
|
|
793
923
|
exports.poseOverridesFromDoc = poseOverridesFromDoc;
|
|
924
|
+
exports.sampleBenchworkOutline = sampleBenchworkOutline;
|
|
794
925
|
exports.scaleFeetToInches = scaleFeetToInches;
|
|
795
926
|
exports.stateToDoc = stateToDoc;
|
|
796
927
|
//# sourceMappingURL=index.cjs.map
|