@willcgage/module-schematic 0.55.0 → 0.57.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 +106 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +90 -26
- package/dist/index.d.ts +90 -26
- package/dist/index.js +104 -6
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1139,14 +1139,19 @@ var ATLAS_CODE55_N = [
|
|
|
1139
1139
|
partNumbers: { left: "2052", right: "2053" },
|
|
1140
1140
|
frogNumber: 7,
|
|
1141
1141
|
lead: {
|
|
1142
|
-
inches: 3.
|
|
1142
|
+
inches: 3.59375,
|
|
1143
1143
|
source: "measured",
|
|
1144
|
-
note: "
|
|
1144
|
+
note: "Will Gage, physical Atlas 2052 (#7), 3\xB9\u2079\u2044\u2083\u2082\u2033 points\u2192frog as a single span \u2014 and it matches his two positions exactly (4\u2077\u2044\u2083\u2082 \u2212 \u2075\u2044\u2088), so the reading is internally consistent. SUPERSEDES Steve Branton's 3\u215C\u2033 (#173), which was \u2077\u2044\u2083\u2082\u2033 short and was the library's founding measurement. \u26A0\uFE0F Steve's number was also the SOLE basis of TURNOUT_LEAD_INCHES_PER_FROG (3.375/7 = 0.482) \u2014 that constant is now baseless as well as refuted."
|
|
1145
|
+
},
|
|
1146
|
+
pointsOffset: {
|
|
1147
|
+
inches: 0.625,
|
|
1148
|
+
source: "measured",
|
|
1149
|
+
note: "Will Gage, physical Atlas 2052 (#7) \u2014 \xB9\u2070\u2044\u2081\u2086\u2033 tie end to point tips"
|
|
1145
1150
|
},
|
|
1146
1151
|
frogOffset: {
|
|
1147
|
-
inches: 4.
|
|
1152
|
+
inches: 4.21875,
|
|
1148
1153
|
source: "measured",
|
|
1149
|
-
note: "Will Gage, physical Atlas 2052 (#7) \u2014 tie end to the apex of the V"
|
|
1154
|
+
note: "Will Gage, physical Atlas 2052 (#7) \u2014 4\u2077\u2044\u2083\u2082\u2033, tie end to the apex of the V. Supersedes an initial 4\xB3\u2044\u2081\u2086\u2033. Worth noting for provenance: he was told beforehand that the clearance hypothesis predicted 4\xBC\u2033 and did NOT read 4\xBC\u2033 \u2014 so this number is not a confirmation artefact, and it sits \xB9\u2044\u2083\u2082\u2033 (one tape division) from the predicted value."
|
|
1150
1155
|
},
|
|
1151
1156
|
overallLength: {
|
|
1152
1157
|
inches: 6,
|
|
@@ -1232,10 +1237,24 @@ function turnoutPartForSize(size, library = BUILT_IN_TRACK_PARTS) {
|
|
|
1232
1237
|
(best, p) => Math.abs(p.frogNumber - size) < Math.abs(best.frogNumber - size) ? p : best
|
|
1233
1238
|
);
|
|
1234
1239
|
}
|
|
1240
|
+
function measuredLeadPoints(library) {
|
|
1241
|
+
return library.filter(
|
|
1242
|
+
(p) => p.kind === "turnout" && p.frogNumber != null && p.lead?.source === "measured"
|
|
1243
|
+
).map((p) => ({ n: p.frogNumber, lead: p.lead.inches })).sort((a, b) => a.n - b.n);
|
|
1244
|
+
}
|
|
1235
1245
|
function leadInchesForSize(size, library = BUILT_IN_TRACK_PARTS) {
|
|
1236
1246
|
const part = turnoutPartForSize(size, library);
|
|
1237
1247
|
if (part?.lead && part.frogNumber === size) return part.lead.inches;
|
|
1238
|
-
|
|
1248
|
+
const pts = measuredLeadPoints(library);
|
|
1249
|
+
if (!pts.length) return size * TURNOUT_LEAD_INCHES_PER_FROG;
|
|
1250
|
+
if (pts.length === 1) return size / pts[0].n * pts[0].lead;
|
|
1251
|
+
let i;
|
|
1252
|
+
if (size >= pts[pts.length - 1].n) i = pts.length - 2;
|
|
1253
|
+
else i = Math.max(0, pts.findIndex((p) => p.n >= size) - 1);
|
|
1254
|
+
const lo = pts[i];
|
|
1255
|
+
const hi = pts[i + 1];
|
|
1256
|
+
const t = (size - lo.n) / (hi.n - lo.n);
|
|
1257
|
+
return lo.lead + t * (hi.lead - lo.lead);
|
|
1239
1258
|
}
|
|
1240
1259
|
function parseXtpLibrary(text) {
|
|
1241
1260
|
const parts = [];
|
|
@@ -1301,6 +1320,85 @@ function samplePartSegments(segments, stepsPerCurve = 16) {
|
|
|
1301
1320
|
return out;
|
|
1302
1321
|
});
|
|
1303
1322
|
}
|
|
1323
|
+
function angleDeltaDeg(a, b) {
|
|
1324
|
+
let d = ((a - b) % 360 + 540) % 360 - 180;
|
|
1325
|
+
if (d === -180) d = 180;
|
|
1326
|
+
return d;
|
|
1327
|
+
}
|
|
1328
|
+
function frogNumberFromName(name) {
|
|
1329
|
+
if (!name) return void 0;
|
|
1330
|
+
const m = name.match(/(?:#|no\.?\s*|number\s+)(\d+(?:\.\d+)?)/i);
|
|
1331
|
+
if (!m) return void 0;
|
|
1332
|
+
const n = Number(m[1]);
|
|
1333
|
+
return Number.isFinite(n) && n > 0 ? n : void 0;
|
|
1334
|
+
}
|
|
1335
|
+
function importedKind(name) {
|
|
1336
|
+
const s = (name ?? "").toLowerCase();
|
|
1337
|
+
if (s.includes("wye")) return "wye";
|
|
1338
|
+
if (s.includes("crossing")) return "crossing";
|
|
1339
|
+
if (s.includes("curved")) return "curved-turnout";
|
|
1340
|
+
return "turnout";
|
|
1341
|
+
}
|
|
1342
|
+
var slug = (s) => s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
1343
|
+
function importedPartToTrackPart(part, sourceName = "imported .xtp") {
|
|
1344
|
+
const name = part.name ?? part.title ?? "Imported part";
|
|
1345
|
+
const manufacturer = part.manufacturer ?? "Unknown";
|
|
1346
|
+
const note = `from ${sourceName} \u2014 community CAD data, not a measurement`;
|
|
1347
|
+
const out = {
|
|
1348
|
+
id: `xtp-${slug(manufacturer)}-${slug(part.partNumber || name)}`,
|
|
1349
|
+
manufacturer,
|
|
1350
|
+
name,
|
|
1351
|
+
line: "",
|
|
1352
|
+
scale: "N",
|
|
1353
|
+
kind: importedKind(name),
|
|
1354
|
+
frogNumber: frogNumberFromName(name),
|
|
1355
|
+
segments: part.segments,
|
|
1356
|
+
ends: part.ends,
|
|
1357
|
+
importedFrom: sourceName
|
|
1358
|
+
};
|
|
1359
|
+
if (part.partNumber) out.partNumbers = { single: part.partNumber };
|
|
1360
|
+
const ends = part.ends;
|
|
1361
|
+
if (ends.length >= 2) {
|
|
1362
|
+
const a = ends[0].angleDeg * Math.PI / 180;
|
|
1363
|
+
const ux = Math.sin(a);
|
|
1364
|
+
const uy = Math.cos(a);
|
|
1365
|
+
const proj = ends.map((e) => (e.x - ends[0].x) * ux + (e.y - ends[0].y) * uy);
|
|
1366
|
+
const span = Math.max(...proj) - Math.min(...proj);
|
|
1367
|
+
if (span > 0) out.overallLength = { inches: span, source: "unverified", note };
|
|
1368
|
+
}
|
|
1369
|
+
if (ends.length >= 3) {
|
|
1370
|
+
const rest = ends.slice(1);
|
|
1371
|
+
const opposite = rest.map((e) => ({ e, off: Math.abs(angleDeltaDeg(e.angleDeg, ends[0].angleDeg + 180)) })).sort((p, q) => p.off - q.off);
|
|
1372
|
+
const through = opposite[0].e;
|
|
1373
|
+
const diverging = opposite[opposite.length - 1].e;
|
|
1374
|
+
const deg = Math.abs(angleDeltaDeg(diverging.angleDeg, through.angleDeg));
|
|
1375
|
+
if (deg > 0.01) out.actualAngle = { deg, source: "unverified", note };
|
|
1376
|
+
}
|
|
1377
|
+
return out;
|
|
1378
|
+
}
|
|
1379
|
+
function mergeImportedParts(imported, library = BUILT_IN_TRACK_PARTS, sourceName = "imported .xtp") {
|
|
1380
|
+
const out = library.map((p) => ({ ...p }));
|
|
1381
|
+
const numbersOf = (p) => [p.partNumbers?.left, p.partNumbers?.right, p.partNumbers?.single].filter(Boolean).map((s) => s.trim().toLowerCase());
|
|
1382
|
+
for (const raw of imported) {
|
|
1383
|
+
const conv = importedPartToTrackPart(raw, sourceName);
|
|
1384
|
+
const pn = raw.partNumber?.trim().toLowerCase();
|
|
1385
|
+
const match = (pn ? out.find((p) => numbersOf(p).includes(pn)) : void 0) ?? (conv.frogNumber != null ? out.find(
|
|
1386
|
+
(p) => p.frogNumber === conv.frogNumber && p.kind === conv.kind && p.manufacturer.toLowerCase() === conv.manufacturer.toLowerCase()
|
|
1387
|
+
) : void 0);
|
|
1388
|
+
if (!match) {
|
|
1389
|
+
out.push(conv);
|
|
1390
|
+
continue;
|
|
1391
|
+
}
|
|
1392
|
+
if (!match.segments?.length && conv.segments?.length) match.segments = conv.segments;
|
|
1393
|
+
if (!match.ends?.length && conv.ends?.length) match.ends = conv.ends;
|
|
1394
|
+
if (!match.importedFrom && (conv.segments?.length || conv.ends?.length)) {
|
|
1395
|
+
match.importedFrom = sourceName;
|
|
1396
|
+
}
|
|
1397
|
+
if (!match.overallLength && conv.overallLength) match.overallLength = conv.overallLength;
|
|
1398
|
+
if (!match.actualAngle && conv.actualAngle) match.actualAngle = conv.actualAngle;
|
|
1399
|
+
}
|
|
1400
|
+
return out;
|
|
1401
|
+
}
|
|
1304
1402
|
function turnoutClosure(size, opts = {}) {
|
|
1305
1403
|
const N = size > 0 ? size : 6;
|
|
1306
1404
|
const g = opts.gaugeInches ?? RAIL_GAUGE_INCHES;
|
|
@@ -1863,12 +1961,15 @@ exports.endplateLead = endplateLead;
|
|
|
1863
1961
|
exports.endplateTrackOffsetFor = endplateTrackOffsetFor;
|
|
1864
1962
|
exports.endplateTrackOffsetInches = endplateTrackOffsetInches;
|
|
1865
1963
|
exports.endplateWidthInches = endplateWidthInches;
|
|
1964
|
+
exports.frogNumberFromName = frogNumberFromName;
|
|
1866
1965
|
exports.fromSectionRelative = fromSectionRelative;
|
|
1867
1966
|
exports.geometryTurnDegrees = geometryTurnDegrees;
|
|
1967
|
+
exports.importedPartToTrackPart = importedPartToTrackPart;
|
|
1868
1968
|
exports.inchesToScaleFeet = inchesToScaleFeet;
|
|
1869
1969
|
exports.isLoopDoc = isLoopDoc;
|
|
1870
1970
|
exports.isTransitionTurnout = isTransitionTurnout;
|
|
1871
1971
|
exports.leadInchesForSize = leadInchesForSize;
|
|
1972
|
+
exports.mergeImportedParts = mergeImportedParts;
|
|
1872
1973
|
exports.moduleCenterline = moduleCenterline;
|
|
1873
1974
|
exports.moduleFeatures = moduleFeatures;
|
|
1874
1975
|
exports.moduleFootprint = moduleFootprint;
|