@willcgage/module-schematic 0.31.0 → 0.33.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
@@ -211,6 +211,24 @@ function sectionBreaksFromSections(doc) {
211
211
  const spans = sectionSpans(doc);
212
212
  return spans.slice(0, -1).map((sp) => sp.toPos);
213
213
  }
214
+ function sectionedEndPose(doc) {
215
+ const secs = moduleSections(doc).filter(
216
+ (sec) => typeof sec.lengthInches === "number" && sec.lengthInches > 0
217
+ );
218
+ if (!secs.length) return null;
219
+ let ox = 0;
220
+ let oy = 0;
221
+ let heading = 0;
222
+ for (const sec of secs) {
223
+ const local = sectionCenterlineLocal(sec);
224
+ const c = Math.cos(heading * DEG_FP);
225
+ const sn = Math.sin(heading * DEG_FP);
226
+ ox += local.endX * c - local.endY * sn;
227
+ oy += local.endX * sn + local.endY * c;
228
+ heading += local.endHeadingDeg;
229
+ }
230
+ return { x: ox, y: oy, heading };
231
+ }
214
232
  function sectionedCenterline(doc) {
215
233
  const secs = moduleSections(doc).filter(
216
234
  (sec) => typeof sec.lengthInches === "number" && sec.lengthInches > 0
@@ -327,6 +345,79 @@ function sectionBand(center, fromPos, toPos, widthA = FREEMO_ENDPLATE_WIDTH_RECO
327
345
  }));
328
346
  return [...left, ...right.reverse()];
329
347
  }
348
+ function ringEdges(pts) {
349
+ const out = [];
350
+ for (let i = 0; i < pts.length; i++) out.push([pts[i], pts[(i + 1) % pts.length]]);
351
+ return out;
352
+ }
353
+ function sharedEdgeLength(e1, e2, gap, angleDeg) {
354
+ const ux = e1[1].x - e1[0].x;
355
+ const uy = e1[1].y - e1[0].y;
356
+ const ul = Math.hypot(ux, uy);
357
+ const vx = e2[1].x - e2[0].x;
358
+ const vy = e2[1].y - e2[0].y;
359
+ const vl = Math.hypot(vx, vy);
360
+ if (ul < 1e-6 || vl < 1e-6) return 0;
361
+ const dx = ux / ul;
362
+ const dy = uy / ul;
363
+ const cross = Math.abs((dx * vy - dy * vx) / vl);
364
+ if (cross > Math.sin(angleDeg * Math.PI / 180)) return 0;
365
+ const perp = (q) => Math.abs((q.x - e1[0].x) * -dy + (q.y - e1[0].y) * dx);
366
+ if (perp(e2[0]) > gap || perp(e2[1]) > gap) return 0;
367
+ const proj = (q) => (q.x - e1[0].x) * dx + (q.y - e1[0].y) * dy;
368
+ const t1 = proj(e2[0]);
369
+ const t2 = proj(e2[1]);
370
+ return Math.max(0, Math.min(ul, Math.max(t1, t2)) - Math.max(0, Math.min(t1, t2)));
371
+ }
372
+ function sectionAdjacency(footprints, opts) {
373
+ const gap = opts?.gapInches ?? 0.5;
374
+ const angle = opts?.angleDegrees ?? 3;
375
+ const min = opts?.minOverlapInches ?? 1;
376
+ const edges = footprints.map((f) => ringEdges(f.outline));
377
+ const out = [];
378
+ for (let i = 0; i < footprints.length; i++) {
379
+ for (let j = i + 1; j < footprints.length; j++) {
380
+ let total = 0;
381
+ for (const e1 of edges[i])
382
+ for (const e2 of edges[j]) total += sharedEdgeLength(e1, e2, gap, angle);
383
+ if (total >= min)
384
+ out.push({
385
+ a: footprints[i].id,
386
+ b: footprints[j].id,
387
+ lengthInches: Math.round(total * 1e3) / 1e3
388
+ });
389
+ }
390
+ }
391
+ return out;
392
+ }
393
+ function sectionNeighbours(id, adj) {
394
+ return adj.filter((x) => x.a === id || x.b === id).map((x) => x.a === id ? x.b : x.a);
395
+ }
396
+ function sectionComponents(ids, adj) {
397
+ const parent = new Map(ids.map((id) => [id, id]));
398
+ const find = (x) => {
399
+ let r = x;
400
+ while (parent.get(r) !== r) r = parent.get(r);
401
+ while (parent.get(x) !== r) {
402
+ const nx = parent.get(x);
403
+ parent.set(x, r);
404
+ x = nx;
405
+ }
406
+ return r;
407
+ };
408
+ for (const { a, b } of adj) {
409
+ if (!parent.has(a) || !parent.has(b)) continue;
410
+ const ra = find(a);
411
+ const rb = find(b);
412
+ if (ra !== rb) parent.set(ra, rb);
413
+ }
414
+ const groups = /* @__PURE__ */ new Map();
415
+ for (const id of ids) {
416
+ const r = find(id);
417
+ groups.set(r, [...groups.get(r) ?? [], id]);
418
+ }
419
+ return [...groups.values()];
420
+ }
330
421
  function endplateFaceSegments(center, widthA = FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES, widthB = FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES, offsetA = 0, offsetB = 0) {
331
422
  if (center.length < 2) return [];
332
423
  const n = centerlineNormals(center);
@@ -1210,7 +1301,19 @@ function deriveEndplatePoses(geo) {
1210
1301
  })
1211
1302
  );
1212
1303
  const noB = geo.geometryType === "dead_end";
1213
- if (!noB) {
1304
+ const end = sectionedEndPose({ sections: geo.sections });
1305
+ if (!noB && end) {
1306
+ poses.push(
1307
+ withOverride({
1308
+ id: "B",
1309
+ x: end.x,
1310
+ y: end.y,
1311
+ heading: norm360(end.heading),
1312
+ trackConfig: cfg(1),
1313
+ trackOffsets: offsetsFor(cfg(1), half)
1314
+ })
1315
+ );
1316
+ } else if (!noB) {
1214
1317
  const turn = geometryTurnDegrees(geo.geometryType, geo.geometryDegrees);
1215
1318
  let bx;
1216
1319
  let by;
@@ -1310,11 +1413,15 @@ exports.poseOverridesFromDoc = poseOverridesFromDoc;
1310
1413
  exports.sampleBenchworkOutline = sampleBenchworkOutline;
1311
1414
  exports.samplePath = samplePath;
1312
1415
  exports.scaleFeetToInches = scaleFeetToInches;
1416
+ exports.sectionAdjacency = sectionAdjacency;
1313
1417
  exports.sectionBand = sectionBand;
1314
1418
  exports.sectionBreaksFromSections = sectionBreaksFromSections;
1419
+ exports.sectionComponents = sectionComponents;
1315
1420
  exports.sectionFootprints = sectionFootprints;
1421
+ exports.sectionNeighbours = sectionNeighbours;
1316
1422
  exports.sectionSpans = sectionSpans;
1317
1423
  exports.sectionedCenterline = sectionedCenterline;
1424
+ exports.sectionedEndPose = sectionedEndPose;
1318
1425
  exports.sliceCenterline = sliceCenterline;
1319
1426
  exports.stateToDoc = stateToDoc;
1320
1427
  exports.trackPath = trackPath;