@willcgage/module-schematic 0.16.0 → 0.18.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 +130 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +113 -1
- package/dist/index.d.ts +113 -1
- package/dist/index.js +125 -3
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -65,6 +65,78 @@ function arcSweep(a0, a1, am) {
|
|
|
65
65
|
const one = norm(a1);
|
|
66
66
|
return m <= one ? one : one - 2 * Math.PI;
|
|
67
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
|
+
}
|
|
68
140
|
function isLoopDoc(doc) {
|
|
69
141
|
return doc.loop === true || doc.endplates.length === 1;
|
|
70
142
|
}
|
|
@@ -77,6 +149,11 @@ function inchesToScaleFeet(inches, ratio = N_SCALE_RATIO) {
|
|
|
77
149
|
function scaleFeetToInches(feet, ratio = N_SCALE_RATIO) {
|
|
78
150
|
return feet * 12 / ratio;
|
|
79
151
|
}
|
|
152
|
+
var N_CAR_LENGTH_INCHES = 3.3;
|
|
153
|
+
function carCapacity(fromPos, toPos, carLengthInches = N_CAR_LENGTH_INCHES) {
|
|
154
|
+
if (!(carLengthInches > 0)) return 0;
|
|
155
|
+
return Math.max(0, Math.floor(Math.abs(toPos - fromPos) / carLengthInches));
|
|
156
|
+
}
|
|
80
157
|
function asModuleSchematic(x) {
|
|
81
158
|
if (!x || typeof x !== "object") return null;
|
|
82
159
|
const d = x;
|
|
@@ -98,7 +175,8 @@ function emptyEditorState(lengthInches) {
|
|
|
98
175
|
poseOverrides: {},
|
|
99
176
|
endplateWidths: {},
|
|
100
177
|
outline: [],
|
|
101
|
-
controlPoints: []
|
|
178
|
+
controlPoints: [],
|
|
179
|
+
industries: []
|
|
102
180
|
};
|
|
103
181
|
}
|
|
104
182
|
function isTransitionTurnout(t) {
|
|
@@ -268,6 +346,21 @@ function stateToDoc(state, recordNumber) {
|
|
|
268
346
|
side: s.side
|
|
269
347
|
}))
|
|
270
348
|
})),
|
|
349
|
+
// Industries — car-spot spans on a track; only when any are authored.
|
|
350
|
+
...state.industries.length > 0 ? {
|
|
351
|
+
industries: state.industries.map((ind) => ({
|
|
352
|
+
id: ind.id,
|
|
353
|
+
name: ind.name,
|
|
354
|
+
...ind.type ? { type: ind.type } : {},
|
|
355
|
+
track: ind.track,
|
|
356
|
+
fromPos: ind.fromPos,
|
|
357
|
+
toPos: ind.toPos,
|
|
358
|
+
side: ind.side,
|
|
359
|
+
...ind.labelMode && ind.labelMode !== "none" ? { labelMode: ind.labelMode } : {},
|
|
360
|
+
...ind.carTypes.length ? { carTypes: ind.carTypes } : {},
|
|
361
|
+
moduleIndustryId: ind.moduleIndustryId
|
|
362
|
+
}))
|
|
363
|
+
} : {},
|
|
271
364
|
// Benchwork footprint outline (module-local inches); only when it's a real
|
|
272
365
|
// ring (≥ 3 vertices).
|
|
273
366
|
...state.outline.length >= 3 ? { outline: state.outline } : {}
|
|
@@ -381,7 +474,19 @@ function docToState(doc, fallbackLength, moduleTracks = []) {
|
|
|
381
474
|
divergeTrack: t.divergeTrack,
|
|
382
475
|
kind: t.kind ?? "right"
|
|
383
476
|
})),
|
|
384
|
-
controlPoints: readControlPoints(d, sc)
|
|
477
|
+
controlPoints: readControlPoints(d, sc),
|
|
478
|
+
industries: (d.industries ?? []).map((ind) => ({
|
|
479
|
+
id: ind.id,
|
|
480
|
+
name: ind.name ?? "",
|
|
481
|
+
type: ind.type ?? "",
|
|
482
|
+
track: ind.track,
|
|
483
|
+
fromPos: sc(ind.fromPos ?? 0),
|
|
484
|
+
toPos: ind.toPos != null ? sc(ind.toPos) : len,
|
|
485
|
+
side: ind.side ?? "above",
|
|
486
|
+
labelMode: ind.labelMode ?? "none",
|
|
487
|
+
carTypes: Array.isArray(ind.carTypes) ? ind.carTypes : [],
|
|
488
|
+
moduleIndustryId: ind.moduleIndustryId ?? null
|
|
489
|
+
}))
|
|
385
490
|
};
|
|
386
491
|
}
|
|
387
492
|
function readControlPoints(d, sc = (p) => p) {
|
|
@@ -680,6 +785,22 @@ function moduleFeatures(doc) {
|
|
|
680
785
|
posFrac: clampFrac(e.at.pos),
|
|
681
786
|
side: e.at.side === "down" ? "down" : "up"
|
|
682
787
|
}));
|
|
788
|
+
const industries = (doc.industries ?? []).map((ind) => {
|
|
789
|
+
const from = ind.fromPos ?? 0;
|
|
790
|
+
const to = ind.toPos ?? len;
|
|
791
|
+
return {
|
|
792
|
+
id: ind.id,
|
|
793
|
+
name: ind.name ?? "",
|
|
794
|
+
type: ind.type ?? null,
|
|
795
|
+
fromFrac: clampFrac(Math.min(from, to)),
|
|
796
|
+
toFrac: clampFrac(Math.max(from, to)),
|
|
797
|
+
lane: trackLane.get(ind.track) ?? 0,
|
|
798
|
+
side: ind.side ?? "above",
|
|
799
|
+
labelMode: ind.labelMode ?? "none",
|
|
800
|
+
cars: carCapacity(from, to),
|
|
801
|
+
carTypes: Array.isArray(ind.carTypes) ? ind.carTypes : []
|
|
802
|
+
};
|
|
803
|
+
});
|
|
683
804
|
const allLanes = [
|
|
684
805
|
0,
|
|
685
806
|
doubleMain ? 1 : 0,
|
|
@@ -720,6 +841,7 @@ function moduleFeatures(doc) {
|
|
|
720
841
|
crossings,
|
|
721
842
|
crossovers,
|
|
722
843
|
branchConnectors,
|
|
844
|
+
industries,
|
|
723
845
|
laneMin: Math.min(...allLanes),
|
|
724
846
|
laneMax: Math.max(...allLanes)
|
|
725
847
|
};
|
|
@@ -826,22 +948,28 @@ exports.FREEMO_ENDPLATE_WIDTH_MIN_INCHES = FREEMO_ENDPLATE_WIDTH_MIN_INCHES;
|
|
|
826
948
|
exports.FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES = FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES;
|
|
827
949
|
exports.MAIN2_TRACK_ID = MAIN2_TRACK_ID;
|
|
828
950
|
exports.MAIN_TRACK_ID = MAIN_TRACK_ID;
|
|
951
|
+
exports.N_CAR_LENGTH_INCHES = N_CAR_LENGTH_INCHES;
|
|
829
952
|
exports.N_SCALE_RATIO = N_SCALE_RATIO;
|
|
830
953
|
exports.asModuleSchematic = asModuleSchematic;
|
|
954
|
+
exports.benchworkBand = benchworkBand;
|
|
831
955
|
exports.benchworkOutline = benchworkOutline;
|
|
832
956
|
exports.buildCrossover = buildCrossover;
|
|
833
957
|
exports.buildPassingSiding = buildPassingSiding;
|
|
834
958
|
exports.buildTransition = buildTransition;
|
|
959
|
+
exports.carCapacity = carCapacity;
|
|
835
960
|
exports.deriveEndplatePoses = deriveEndplatePoses;
|
|
836
961
|
exports.divergeSideForHand = divergeSideForHand;
|
|
837
962
|
exports.docToState = docToState;
|
|
838
963
|
exports.emptyEditorState = emptyEditorState;
|
|
964
|
+
exports.endplateFaceSegments = endplateFaceSegments;
|
|
839
965
|
exports.endplateWidthInches = endplateWidthInches;
|
|
840
966
|
exports.geometryTurnDegrees = geometryTurnDegrees;
|
|
841
967
|
exports.inchesToScaleFeet = inchesToScaleFeet;
|
|
842
968
|
exports.isLoopDoc = isLoopDoc;
|
|
843
969
|
exports.isTransitionTurnout = isTransitionTurnout;
|
|
970
|
+
exports.moduleCenterline = moduleCenterline;
|
|
844
971
|
exports.moduleFeatures = moduleFeatures;
|
|
972
|
+
exports.moduleFootprint = moduleFootprint;
|
|
845
973
|
exports.nextId = nextId;
|
|
846
974
|
exports.poseNeedsManual = poseNeedsManual;
|
|
847
975
|
exports.poseOverridesFromDoc = poseOverridesFromDoc;
|