@willcgage/module-schematic 0.56.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 CHANGED
@@ -1320,6 +1320,85 @@ function samplePartSegments(segments, stepsPerCurve = 16) {
1320
1320
  return out;
1321
1321
  });
1322
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
+ }
1323
1402
  function turnoutClosure(size, opts = {}) {
1324
1403
  const N = size > 0 ? size : 6;
1325
1404
  const g = opts.gaugeInches ?? RAIL_GAUGE_INCHES;
@@ -1882,12 +1961,15 @@ exports.endplateLead = endplateLead;
1882
1961
  exports.endplateTrackOffsetFor = endplateTrackOffsetFor;
1883
1962
  exports.endplateTrackOffsetInches = endplateTrackOffsetInches;
1884
1963
  exports.endplateWidthInches = endplateWidthInches;
1964
+ exports.frogNumberFromName = frogNumberFromName;
1885
1965
  exports.fromSectionRelative = fromSectionRelative;
1886
1966
  exports.geometryTurnDegrees = geometryTurnDegrees;
1967
+ exports.importedPartToTrackPart = importedPartToTrackPart;
1887
1968
  exports.inchesToScaleFeet = inchesToScaleFeet;
1888
1969
  exports.isLoopDoc = isLoopDoc;
1889
1970
  exports.isTransitionTurnout = isTransitionTurnout;
1890
1971
  exports.leadInchesForSize = leadInchesForSize;
1972
+ exports.mergeImportedParts = mergeImportedParts;
1891
1973
  exports.moduleCenterline = moduleCenterline;
1892
1974
  exports.moduleFeatures = moduleFeatures;
1893
1975
  exports.moduleFootprint = moduleFootprint;