@willcgage/module-schematic 0.31.0 → 0.32.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.d.cts CHANGED
@@ -455,6 +455,36 @@ declare function sliceCenterline(center: BenchworkPoint[], fromPos: number, toPo
455
455
  * authored polygon gets one derived from its span, so resizing the board
456
456
  * reshapes it instead of leaving a hand-drawn outline stranded. */
457
457
  declare function sectionBand(center: BenchworkPoint[], fromPos: number, toPos: number, widthA?: number, widthB?: number, offsetA?: number, offsetB?: number): BenchworkPoint[];
458
+ /** Two sections that physically meet, and how much edge they share. */
459
+ interface SectionAdjacency {
460
+ a: string;
461
+ b: string;
462
+ /** Inches of shared edge — a butt joint across a 24″ board reads ~24. */
463
+ lengthInches: number;
464
+ }
465
+ /**
466
+ * Which sections physically MEET, derived from shared polygon edges rather
467
+ * than list order (#96 phase 2c).
468
+ *
469
+ * Order is the wrong model as soon as a module stops being a row of boards: a
470
+ * peninsula hangs off the BACK of a shallow band over part of its length, so
471
+ * it neighbours a board it isn't next to in any list. Geometry is the only
472
+ * thing that knows.
473
+ */
474
+ declare function sectionAdjacency(footprints: SectionFootprint[], opts?: {
475
+ gapInches?: number;
476
+ angleDegrees?: number;
477
+ minOverlapInches?: number;
478
+ }): SectionAdjacency[];
479
+ /** The sections each one touches. */
480
+ declare function sectionNeighbours(id: string, adj: SectionAdjacency[]): string[];
481
+ /**
482
+ * Groups of sections that hang together, as connected components. One group
483
+ * means the module is a single piece of bench work; more than one means some
484
+ * board is floating free — an authoring mistake now, and the test #96 phase 3
485
+ * needs before it can say whether dropping a section leaves the rest intact.
486
+ */
487
+ declare function sectionComponents(ids: string[], adj: SectionAdjacency[]): string[][];
458
488
  /** The two endplate faces (the band's flat ends): [A end at widthA, B end at widthB]. */
459
489
  declare function endplateFaceSegments(center: BenchworkPoint[], widthA?: number, widthB?: number, offsetA?: number, offsetB?: number): OutlineFace[];
460
490
  /**
@@ -941,4 +971,4 @@ declare function poseOverridesFromDoc(doc: ModuleSchematicDoc): Record<string, {
941
971
  heading: number;
942
972
  }>;
943
973
 
944
- export { type BenchworkPoint, type BranchConnector, type DrawCrossing, type DrawCrossover, type DrawIndustry, type DrawSignal, type DrawTrack, type DrawTurnout, type EditorBranch, type EditorControlPoint, type EditorCpSignal, type EditorCrossing, type EditorIndustry, type EditorState, type EditorTrack, type EditorTurnout, type EndplateBConfig, type EndplatePose, type EndplateWidthIssue, FREEMO_ENDPLATE_TRACK_FASCIA_CLEARANCE_INCHES, FREEMO_ENDPLATE_WIDTH_MIN_INCHES, FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES, FREEMO_TRACK_SPACING_INCHES, type GeometryType, type IndustryLabelMode, type IndustrySpot, MAIN2_TRACK_ID, MAIN_TRACK_ID, type ModuleFeatures, type ModuleFootprint, type ModuleFootprintInput, type ModuleGeometryInput, type ModuleSchematicDoc, type ModuleTrackRow, N_CAR_LENGTH_INCHES, N_SCALE_RATIO, type OutlineFace, type SchematicBlock, type SchematicControlPoint, type SchematicCrossing, type SchematicEndplate, type SchematicEndplateTrack, type SchematicIndustry, type SchematicSection, type SchematicSignal, type SchematicTrack, type SchematicTurnout, type SectionFootprint, type SignalFacing, type SignalSide, type TrackConfig, type TrackRole, type TurnoutKind, asModuleSchematic, benchworkBand, benchworkOutline, buildCrossover, buildPassingSiding, buildTransition, carCapacity, checkEndplateWidth, deriveEndplatePoses, divergeSideForHand, docToState, emptyEditorState, endplateFaceSegments, endplateTrackOffsetFor, endplateTrackOffsetInches, endplateWidthInches, geometryTurnDegrees, inchesToScaleFeet, isLoopDoc, isTransitionTurnout, moduleCenterline, moduleFeatures, moduleFootprint, moduleLengthFromSections, moduleSections, nextId, poseNeedsManual, poseOverridesFromDoc, sampleBenchworkOutline, samplePath, scaleFeetToInches, sectionBand, sectionBreaksFromSections, sectionFootprints, sectionSpans, sectionedCenterline, sliceCenterline, stateToDoc, trackPath };
974
+ export { type BenchworkPoint, type BranchConnector, type DrawCrossing, type DrawCrossover, type DrawIndustry, type DrawSignal, type DrawTrack, type DrawTurnout, type EditorBranch, type EditorControlPoint, type EditorCpSignal, type EditorCrossing, type EditorIndustry, type EditorState, type EditorTrack, type EditorTurnout, type EndplateBConfig, type EndplatePose, type EndplateWidthIssue, FREEMO_ENDPLATE_TRACK_FASCIA_CLEARANCE_INCHES, FREEMO_ENDPLATE_WIDTH_MIN_INCHES, FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES, FREEMO_TRACK_SPACING_INCHES, type GeometryType, type IndustryLabelMode, type IndustrySpot, MAIN2_TRACK_ID, MAIN_TRACK_ID, type ModuleFeatures, type ModuleFootprint, type ModuleFootprintInput, type ModuleGeometryInput, type ModuleSchematicDoc, type ModuleTrackRow, N_CAR_LENGTH_INCHES, N_SCALE_RATIO, type OutlineFace, type SchematicBlock, type SchematicControlPoint, type SchematicCrossing, type SchematicEndplate, type SchematicEndplateTrack, type SchematicIndustry, type SchematicSection, type SchematicSignal, type SchematicTrack, type SchematicTurnout, type SectionAdjacency, type SectionFootprint, type SignalFacing, type SignalSide, type TrackConfig, type TrackRole, type TurnoutKind, asModuleSchematic, benchworkBand, benchworkOutline, buildCrossover, buildPassingSiding, buildTransition, carCapacity, checkEndplateWidth, deriveEndplatePoses, divergeSideForHand, docToState, emptyEditorState, endplateFaceSegments, endplateTrackOffsetFor, endplateTrackOffsetInches, endplateWidthInches, geometryTurnDegrees, inchesToScaleFeet, isLoopDoc, isTransitionTurnout, moduleCenterline, moduleFeatures, moduleFootprint, moduleLengthFromSections, moduleSections, nextId, poseNeedsManual, poseOverridesFromDoc, sampleBenchworkOutline, samplePath, scaleFeetToInches, sectionAdjacency, sectionBand, sectionBreaksFromSections, sectionComponents, sectionFootprints, sectionNeighbours, sectionSpans, sectionedCenterline, sliceCenterline, stateToDoc, trackPath };
package/dist/index.d.ts CHANGED
@@ -455,6 +455,36 @@ declare function sliceCenterline(center: BenchworkPoint[], fromPos: number, toPo
455
455
  * authored polygon gets one derived from its span, so resizing the board
456
456
  * reshapes it instead of leaving a hand-drawn outline stranded. */
457
457
  declare function sectionBand(center: BenchworkPoint[], fromPos: number, toPos: number, widthA?: number, widthB?: number, offsetA?: number, offsetB?: number): BenchworkPoint[];
458
+ /** Two sections that physically meet, and how much edge they share. */
459
+ interface SectionAdjacency {
460
+ a: string;
461
+ b: string;
462
+ /** Inches of shared edge — a butt joint across a 24″ board reads ~24. */
463
+ lengthInches: number;
464
+ }
465
+ /**
466
+ * Which sections physically MEET, derived from shared polygon edges rather
467
+ * than list order (#96 phase 2c).
468
+ *
469
+ * Order is the wrong model as soon as a module stops being a row of boards: a
470
+ * peninsula hangs off the BACK of a shallow band over part of its length, so
471
+ * it neighbours a board it isn't next to in any list. Geometry is the only
472
+ * thing that knows.
473
+ */
474
+ declare function sectionAdjacency(footprints: SectionFootprint[], opts?: {
475
+ gapInches?: number;
476
+ angleDegrees?: number;
477
+ minOverlapInches?: number;
478
+ }): SectionAdjacency[];
479
+ /** The sections each one touches. */
480
+ declare function sectionNeighbours(id: string, adj: SectionAdjacency[]): string[];
481
+ /**
482
+ * Groups of sections that hang together, as connected components. One group
483
+ * means the module is a single piece of bench work; more than one means some
484
+ * board is floating free — an authoring mistake now, and the test #96 phase 3
485
+ * needs before it can say whether dropping a section leaves the rest intact.
486
+ */
487
+ declare function sectionComponents(ids: string[], adj: SectionAdjacency[]): string[][];
458
488
  /** The two endplate faces (the band's flat ends): [A end at widthA, B end at widthB]. */
459
489
  declare function endplateFaceSegments(center: BenchworkPoint[], widthA?: number, widthB?: number, offsetA?: number, offsetB?: number): OutlineFace[];
460
490
  /**
@@ -941,4 +971,4 @@ declare function poseOverridesFromDoc(doc: ModuleSchematicDoc): Record<string, {
941
971
  heading: number;
942
972
  }>;
943
973
 
944
- export { type BenchworkPoint, type BranchConnector, type DrawCrossing, type DrawCrossover, type DrawIndustry, type DrawSignal, type DrawTrack, type DrawTurnout, type EditorBranch, type EditorControlPoint, type EditorCpSignal, type EditorCrossing, type EditorIndustry, type EditorState, type EditorTrack, type EditorTurnout, type EndplateBConfig, type EndplatePose, type EndplateWidthIssue, FREEMO_ENDPLATE_TRACK_FASCIA_CLEARANCE_INCHES, FREEMO_ENDPLATE_WIDTH_MIN_INCHES, FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES, FREEMO_TRACK_SPACING_INCHES, type GeometryType, type IndustryLabelMode, type IndustrySpot, MAIN2_TRACK_ID, MAIN_TRACK_ID, type ModuleFeatures, type ModuleFootprint, type ModuleFootprintInput, type ModuleGeometryInput, type ModuleSchematicDoc, type ModuleTrackRow, N_CAR_LENGTH_INCHES, N_SCALE_RATIO, type OutlineFace, type SchematicBlock, type SchematicControlPoint, type SchematicCrossing, type SchematicEndplate, type SchematicEndplateTrack, type SchematicIndustry, type SchematicSection, type SchematicSignal, type SchematicTrack, type SchematicTurnout, type SectionFootprint, type SignalFacing, type SignalSide, type TrackConfig, type TrackRole, type TurnoutKind, asModuleSchematic, benchworkBand, benchworkOutline, buildCrossover, buildPassingSiding, buildTransition, carCapacity, checkEndplateWidth, deriveEndplatePoses, divergeSideForHand, docToState, emptyEditorState, endplateFaceSegments, endplateTrackOffsetFor, endplateTrackOffsetInches, endplateWidthInches, geometryTurnDegrees, inchesToScaleFeet, isLoopDoc, isTransitionTurnout, moduleCenterline, moduleFeatures, moduleFootprint, moduleLengthFromSections, moduleSections, nextId, poseNeedsManual, poseOverridesFromDoc, sampleBenchworkOutline, samplePath, scaleFeetToInches, sectionBand, sectionBreaksFromSections, sectionFootprints, sectionSpans, sectionedCenterline, sliceCenterline, stateToDoc, trackPath };
974
+ export { type BenchworkPoint, type BranchConnector, type DrawCrossing, type DrawCrossover, type DrawIndustry, type DrawSignal, type DrawTrack, type DrawTurnout, type EditorBranch, type EditorControlPoint, type EditorCpSignal, type EditorCrossing, type EditorIndustry, type EditorState, type EditorTrack, type EditorTurnout, type EndplateBConfig, type EndplatePose, type EndplateWidthIssue, FREEMO_ENDPLATE_TRACK_FASCIA_CLEARANCE_INCHES, FREEMO_ENDPLATE_WIDTH_MIN_INCHES, FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES, FREEMO_TRACK_SPACING_INCHES, type GeometryType, type IndustryLabelMode, type IndustrySpot, MAIN2_TRACK_ID, MAIN_TRACK_ID, type ModuleFeatures, type ModuleFootprint, type ModuleFootprintInput, type ModuleGeometryInput, type ModuleSchematicDoc, type ModuleTrackRow, N_CAR_LENGTH_INCHES, N_SCALE_RATIO, type OutlineFace, type SchematicBlock, type SchematicControlPoint, type SchematicCrossing, type SchematicEndplate, type SchematicEndplateTrack, type SchematicIndustry, type SchematicSection, type SchematicSignal, type SchematicTrack, type SchematicTurnout, type SectionAdjacency, type SectionFootprint, type SignalFacing, type SignalSide, type TrackConfig, type TrackRole, type TurnoutKind, asModuleSchematic, benchworkBand, benchworkOutline, buildCrossover, buildPassingSiding, buildTransition, carCapacity, checkEndplateWidth, deriveEndplatePoses, divergeSideForHand, docToState, emptyEditorState, endplateFaceSegments, endplateTrackOffsetFor, endplateTrackOffsetInches, endplateWidthInches, geometryTurnDegrees, inchesToScaleFeet, isLoopDoc, isTransitionTurnout, moduleCenterline, moduleFeatures, moduleFootprint, moduleLengthFromSections, moduleSections, nextId, poseNeedsManual, poseOverridesFromDoc, sampleBenchworkOutline, samplePath, scaleFeetToInches, sectionAdjacency, sectionBand, sectionBreaksFromSections, sectionComponents, sectionFootprints, sectionNeighbours, sectionSpans, sectionedCenterline, sliceCenterline, stateToDoc, trackPath };
package/dist/index.js CHANGED
@@ -325,6 +325,79 @@ function sectionBand(center, fromPos, toPos, widthA = FREEMO_ENDPLATE_WIDTH_RECO
325
325
  }));
326
326
  return [...left, ...right.reverse()];
327
327
  }
328
+ function ringEdges(pts) {
329
+ const out = [];
330
+ for (let i = 0; i < pts.length; i++) out.push([pts[i], pts[(i + 1) % pts.length]]);
331
+ return out;
332
+ }
333
+ function sharedEdgeLength(e1, e2, gap, angleDeg) {
334
+ const ux = e1[1].x - e1[0].x;
335
+ const uy = e1[1].y - e1[0].y;
336
+ const ul = Math.hypot(ux, uy);
337
+ const vx = e2[1].x - e2[0].x;
338
+ const vy = e2[1].y - e2[0].y;
339
+ const vl = Math.hypot(vx, vy);
340
+ if (ul < 1e-6 || vl < 1e-6) return 0;
341
+ const dx = ux / ul;
342
+ const dy = uy / ul;
343
+ const cross = Math.abs((dx * vy - dy * vx) / vl);
344
+ if (cross > Math.sin(angleDeg * Math.PI / 180)) return 0;
345
+ const perp = (q) => Math.abs((q.x - e1[0].x) * -dy + (q.y - e1[0].y) * dx);
346
+ if (perp(e2[0]) > gap || perp(e2[1]) > gap) return 0;
347
+ const proj = (q) => (q.x - e1[0].x) * dx + (q.y - e1[0].y) * dy;
348
+ const t1 = proj(e2[0]);
349
+ const t2 = proj(e2[1]);
350
+ return Math.max(0, Math.min(ul, Math.max(t1, t2)) - Math.max(0, Math.min(t1, t2)));
351
+ }
352
+ function sectionAdjacency(footprints, opts) {
353
+ const gap = opts?.gapInches ?? 0.5;
354
+ const angle = opts?.angleDegrees ?? 3;
355
+ const min = opts?.minOverlapInches ?? 1;
356
+ const edges = footprints.map((f) => ringEdges(f.outline));
357
+ const out = [];
358
+ for (let i = 0; i < footprints.length; i++) {
359
+ for (let j = i + 1; j < footprints.length; j++) {
360
+ let total = 0;
361
+ for (const e1 of edges[i])
362
+ for (const e2 of edges[j]) total += sharedEdgeLength(e1, e2, gap, angle);
363
+ if (total >= min)
364
+ out.push({
365
+ a: footprints[i].id,
366
+ b: footprints[j].id,
367
+ lengthInches: Math.round(total * 1e3) / 1e3
368
+ });
369
+ }
370
+ }
371
+ return out;
372
+ }
373
+ function sectionNeighbours(id, adj) {
374
+ return adj.filter((x) => x.a === id || x.b === id).map((x) => x.a === id ? x.b : x.a);
375
+ }
376
+ function sectionComponents(ids, adj) {
377
+ const parent = new Map(ids.map((id) => [id, id]));
378
+ const find = (x) => {
379
+ let r = x;
380
+ while (parent.get(r) !== r) r = parent.get(r);
381
+ while (parent.get(x) !== r) {
382
+ const nx = parent.get(x);
383
+ parent.set(x, r);
384
+ x = nx;
385
+ }
386
+ return r;
387
+ };
388
+ for (const { a, b } of adj) {
389
+ if (!parent.has(a) || !parent.has(b)) continue;
390
+ const ra = find(a);
391
+ const rb = find(b);
392
+ if (ra !== rb) parent.set(ra, rb);
393
+ }
394
+ const groups = /* @__PURE__ */ new Map();
395
+ for (const id of ids) {
396
+ const r = find(id);
397
+ groups.set(r, [...groups.get(r) ?? [], id]);
398
+ }
399
+ return [...groups.values()];
400
+ }
328
401
  function endplateFaceSegments(center, widthA = FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES, widthB = FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES, offsetA = 0, offsetB = 0) {
329
402
  if (center.length < 2) return [];
330
403
  const n = centerlineNormals(center);
@@ -1269,6 +1342,6 @@ function poseOverridesFromDoc(doc) {
1269
1342
  return out;
1270
1343
  }
1271
1344
 
1272
- export { FREEMO_ENDPLATE_TRACK_FASCIA_CLEARANCE_INCHES, FREEMO_ENDPLATE_WIDTH_MIN_INCHES, FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES, FREEMO_TRACK_SPACING_INCHES, MAIN2_TRACK_ID, MAIN_TRACK_ID, N_CAR_LENGTH_INCHES, N_SCALE_RATIO, asModuleSchematic, benchworkBand, benchworkOutline, buildCrossover, buildPassingSiding, buildTransition, carCapacity, checkEndplateWidth, deriveEndplatePoses, divergeSideForHand, docToState, emptyEditorState, endplateFaceSegments, endplateTrackOffsetFor, endplateTrackOffsetInches, endplateWidthInches, geometryTurnDegrees, inchesToScaleFeet, isLoopDoc, isTransitionTurnout, moduleCenterline, moduleFeatures, moduleFootprint, moduleLengthFromSections, moduleSections, nextId, poseNeedsManual, poseOverridesFromDoc, sampleBenchworkOutline, samplePath, scaleFeetToInches, sectionBand, sectionBreaksFromSections, sectionFootprints, sectionSpans, sectionedCenterline, sliceCenterline, stateToDoc, trackPath };
1345
+ export { FREEMO_ENDPLATE_TRACK_FASCIA_CLEARANCE_INCHES, FREEMO_ENDPLATE_WIDTH_MIN_INCHES, FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES, FREEMO_TRACK_SPACING_INCHES, MAIN2_TRACK_ID, MAIN_TRACK_ID, N_CAR_LENGTH_INCHES, N_SCALE_RATIO, asModuleSchematic, benchworkBand, benchworkOutline, buildCrossover, buildPassingSiding, buildTransition, carCapacity, checkEndplateWidth, deriveEndplatePoses, divergeSideForHand, docToState, emptyEditorState, endplateFaceSegments, endplateTrackOffsetFor, endplateTrackOffsetInches, endplateWidthInches, geometryTurnDegrees, inchesToScaleFeet, isLoopDoc, isTransitionTurnout, moduleCenterline, moduleFeatures, moduleFootprint, moduleLengthFromSections, moduleSections, nextId, poseNeedsManual, poseOverridesFromDoc, sampleBenchworkOutline, samplePath, scaleFeetToInches, sectionAdjacency, sectionBand, sectionBreaksFromSections, sectionComponents, sectionFootprints, sectionNeighbours, sectionSpans, sectionedCenterline, sliceCenterline, stateToDoc, trackPath };
1273
1346
  //# sourceMappingURL=index.js.map
1274
1347
  //# sourceMappingURL=index.js.map