@tscircuit/checks 0.0.188 → 0.0.189

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/README.md CHANGED
@@ -44,6 +44,26 @@ and output an array of arrays for any issues found.
44
44
  | [`runAllRoutingChecks`](./lib/run-all-checks.ts) | Runs all routing checks currently enabled (`checkEachPcbPortConnectedToPcbTraces`, `checkSourceTracesHavePcbTraces`, `checkEachPcbTraceNonOverlapping`, `checkPadTraceClearance`, `checkViaTraceClearance`, same/different net via spacing, and `checkPcbTracesOutOfBoard`). Trace-obstacle pairs are classified before aggregation, so each pair produces one overlap or clearance diagnostic, never both. |
45
45
  | [`runAllChecks`](./lib/run-all-checks.ts) | Runs placement, schematic, netlist, pin specification, and routing checks and returns a combined list of issues. |
46
46
 
47
+ ## Consolidated placement overlaps
48
+
49
+ `runAllPlacementChecks` and `runAllChecks` report one placement conflict per
50
+ component pair when footprint overlap causes multiple footprint, pad clearance,
51
+ and courtyard diagnostics. The message names the components, counts the conflicts,
52
+ and suggests moving them apart. Separate component pairs, clearance-only issues,
53
+ standalone elements, and unrelated routing diagnostics remain separate.
54
+
55
+ The result uses the existing `pcb_footprint_overlap_error` type and retains the
56
+ union of affected pad and hole IDs for rendering. The exported
57
+ `PcbComponentOverlapError` interface adds `pcb_component_ids` and `related_errors`
58
+ with the original diagnostics, including measured clearances. These extra context
59
+ fields are provided by checks; older Circuit JSON schema parsers may strip them.
60
+
61
+ Individual checks still return detailed diagnostics. Use
62
+ `runAllPlacementChecks(circuitJson, { consolidateOverlaps: false })` to obtain raw
63
+ aggregate results, for example to apply exclusions before calling
64
+ `consolidatePcbOverlapErrors(circuitJson, errors)`. Consolidation does not mutate
65
+ its inputs and can be applied again when combining runners.
66
+
47
67
  ## Implementation Details
48
68
 
49
69
  > [!NOTE]
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as circuit_json from 'circuit-json';
2
- import { AnyCircuitElement, PcbPortNotConnectedError, PcbTraceError, PcbPlacementError, PcbComponentOutsideBoardError, PcbViaClearanceError, PcbTraceWarning, PcbTraceMissingError, PcbFootprintOverlapError, PcbComponentMissingCourtyardWarning, PcbTraceTooLongWarning, PcbPadPadClearanceError, PcbPadTraceClearanceError, PcbViaTraceClearanceError, SourceComponentMisconfiguredError, SourceComponentPinsUnderspecifiedWarning, SourceNoPowerPinDefinedWarning, SourceNoGroundPinDefinedWarning, SchematicComponentStylingWarning, PcbConnectorNotInAccessibleOrientationWarning, SourceConfusingNetNameWarning } from 'circuit-json';
2
+ import { AnyCircuitElement, PcbPortNotConnectedError, PcbTraceError, PcbPlacementError, PcbComponentOutsideBoardError, PcbViaClearanceError, PcbTraceWarning, PcbTraceMissingError, PcbFootprintOverlapError, PcbPadPadClearanceError, PcbCourtyardOverlapError, PcbComponentMissingCourtyardWarning, PcbTraceTooLongWarning, PcbPadTraceClearanceError, PcbViaTraceClearanceError, SourceComponentMisconfiguredError, SourceComponentPinsUnderspecifiedWarning, SourceNoPowerPinDefinedWarning, SourceNoGroundPinDefinedWarning, SchematicComponentStylingWarning, PcbConnectorNotInAccessibleOrientationWarning, SourceConfusingNetNameWarning } from 'circuit-json';
3
3
  import { ConnectivityMap } from 'circuit-json-to-connectivity-map';
4
4
 
5
5
  declare function checkEachPcbPortConnectedToPcbTraces(circuitJson: AnyCircuitElement[]): PcbPortNotConnectedError[];
@@ -77,11 +77,27 @@ interface TraceBoardCheckConfig {
77
77
  */
78
78
  declare function checkPcbTracesOutOfBoard(circuitJson: AnyCircuitElement[], config?: TraceBoardCheckConfig): PcbTraceError[];
79
79
 
80
+ /** Additional diagnostic context supplied by checks; the base error remains
81
+ * compatible with existing Circuit JSON renderers. */
82
+ interface PcbComponentOverlapError extends PcbFootprintOverlapError {
83
+ pcb_component_ids?: string[];
84
+ related_errors?: PlacementOverlapError[];
85
+ }
86
+ type PlacementOverlapError = PcbComponentOverlapError | PcbPadPadClearanceError | PcbCourtyardOverlapError;
87
+ /**
88
+ * Consolidate placement conflicts for the same exact pair of components, only
89
+ * when a footprint overlap was detected. Keep raw details and all affected
90
+ * pad/hole IDs on one renderer-compatible error. Unrelated checks, standalone
91
+ * elements, same-component clearance errors and clearance-only pairs survive.
92
+ * Does not mutate inputs; safe to apply again when combining check runners.
93
+ */
94
+ declare function consolidatePcbOverlapErrors<T extends AnyCircuitElement>(circuitJson: AnyCircuitElement[], errors: T[]): (T | PcbComponentOverlapError)[];
95
+
80
96
  /**
81
97
  * Check for overlapping PCB components
82
98
  * Returns errors for components that overlap inappropriately
83
99
  */
84
- declare function checkPcbComponentOverlap(circuitJson: AnyCircuitElement[]): PcbFootprintOverlapError[];
100
+ declare function checkPcbComponentOverlap(circuitJson: AnyCircuitElement[]): PcbComponentOverlapError[];
85
101
 
86
102
  /** Returns a warning for every PCB component without a courtyard. */
87
103
  declare function checkPcbComponentsMissingCourtyard(circuitJson: AnyCircuitElement[]): PcbComponentMissingCourtyardWarning[];
@@ -183,7 +199,9 @@ declare function checkSchematicComponentMissingReferenceDesignatorText(circuitJs
183
199
  */
184
200
  declare function checkSchematicComponentPortsOutsideBody(circuitJson: AnyCircuitElement[]): SchematicComponentStylingWarning[];
185
201
 
186
- declare function runAllPlacementChecks(circuitJson: AnyCircuitElement[]): Promise<(circuit_json.PcbPlacementError | circuit_json.PcbComponentOutsideBoardError | circuit_json.PcbFootprintOverlapError | circuit_json.PcbComponentMissingCourtyardWarning | circuit_json.PcbPadPadClearanceError | circuit_json.PcbConnectorNotInAccessibleOrientationWarning | circuit_json.PcbCourtyardOverlapError)[]>;
202
+ declare function runAllPlacementChecks(circuitJson: AnyCircuitElement[], { consolidateOverlaps }?: {
203
+ consolidateOverlaps?: boolean | undefined;
204
+ }): Promise<(circuit_json.PcbPlacementError | circuit_json.PcbComponentOutsideBoardError | PcbComponentOverlapError | circuit_json.PcbPadPadClearanceError | circuit_json.PcbCourtyardOverlapError | circuit_json.PcbComponentMissingCourtyardWarning | circuit_json.PcbConnectorNotInAccessibleOrientationWarning)[]>;
187
205
  declare function runAllNetlistChecks(circuitJson: AnyCircuitElement[]): Promise<({
188
206
  type: "source_pin_must_be_connected_error";
189
207
  source_pin_must_be_connected_error_id: string;
@@ -195,8 +213,8 @@ declare function runAllNetlistChecks(circuitJson: AnyCircuitElement[]): Promise<
195
213
  } | circuit_json.SourceComponentMisconfiguredError | circuit_json.SourceConfusingNetNameWarning)[]>;
196
214
  declare function runAllSchematicChecks(circuitJson: AnyCircuitElement[]): Promise<circuit_json.SchematicComponentStylingWarning[]>;
197
215
  declare function runAllPinSpecificationChecks(circuitJson: AnyCircuitElement[]): Promise<(circuit_json.SourceComponentPinsUnderspecifiedWarning | circuit_json.SourceNoPowerPinDefinedWarning | circuit_json.SourceNoGroundPinDefinedWarning)[]>;
198
- declare function runAllRoutingChecks(circuitJson: AnyCircuitElement[]): Promise<(circuit_json.PcbPortNotConnectedError | circuit_json.PcbTraceError | circuit_json.PcbViaClearanceError | circuit_json.PcbTraceMissingError | circuit_json.PcbTraceTooLongWarning | circuit_json.PcbPadPadClearanceError | circuit_json.PcbPadTraceClearanceError | circuit_json.PcbViaTraceClearanceError)[]>;
199
- declare function runAllChecks(circuitJson: AnyCircuitElement[]): Promise<(circuit_json.PcbPortNotConnectedError | circuit_json.PcbTraceError | circuit_json.PcbPlacementError | circuit_json.PcbComponentOutsideBoardError | circuit_json.PcbViaClearanceError | circuit_json.PcbTraceMissingError | circuit_json.PcbFootprintOverlapError | circuit_json.PcbComponentMissingCourtyardWarning | circuit_json.PcbTraceTooLongWarning | circuit_json.PcbPadPadClearanceError | circuit_json.PcbPadTraceClearanceError | circuit_json.PcbViaTraceClearanceError | {
216
+ declare function runAllRoutingChecks(circuitJson: AnyCircuitElement[]): Promise<(circuit_json.PcbPortNotConnectedError | circuit_json.PcbTraceError | circuit_json.PcbViaClearanceError | circuit_json.PcbTraceMissingError | circuit_json.PcbPadPadClearanceError | circuit_json.PcbTraceTooLongWarning | circuit_json.PcbPadTraceClearanceError | circuit_json.PcbViaTraceClearanceError)[]>;
217
+ declare function runAllChecks(circuitJson: AnyCircuitElement[]): Promise<(circuit_json.PcbPortNotConnectedError | circuit_json.PcbTraceError | circuit_json.PcbPlacementError | circuit_json.PcbComponentOutsideBoardError | circuit_json.PcbViaClearanceError | circuit_json.PcbTraceMissingError | PcbComponentOverlapError | circuit_json.PcbPadPadClearanceError | circuit_json.PcbCourtyardOverlapError | circuit_json.PcbComponentMissingCourtyardWarning | circuit_json.PcbTraceTooLongWarning | circuit_json.PcbPadTraceClearanceError | circuit_json.PcbViaTraceClearanceError | {
200
218
  type: "source_pin_must_be_connected_error";
201
219
  source_pin_must_be_connected_error_id: string;
202
220
  error_type: "source_pin_must_be_connected_error";
@@ -204,7 +222,7 @@ declare function runAllChecks(circuitJson: AnyCircuitElement[]): Promise<(circui
204
222
  source_component_id: string;
205
223
  source_port_id: string;
206
224
  subcircuit_id?: string;
207
- } | circuit_json.SourceComponentMisconfiguredError | circuit_json.SourceComponentPinsUnderspecifiedWarning | circuit_json.SourceNoPowerPinDefinedWarning | circuit_json.SourceNoGroundPinDefinedWarning | circuit_json.SchematicComponentStylingWarning | circuit_json.SourceConfusingNetNameWarning | circuit_json.PcbConnectorNotInAccessibleOrientationWarning | circuit_json.PcbCourtyardOverlapError)[]>;
225
+ } | circuit_json.SourceComponentMisconfiguredError | circuit_json.SourceComponentPinsUnderspecifiedWarning | circuit_json.SourceNoPowerPinDefinedWarning | circuit_json.SourceNoGroundPinDefinedWarning | circuit_json.SchematicComponentStylingWarning | circuit_json.SourceConfusingNetNameWarning | circuit_json.PcbConnectorNotInAccessibleOrientationWarning)[]>;
208
226
 
209
227
  declare function checkConnectorAccessibleOrientation(circuitJson: AnyCircuitElement[]): PcbConnectorNotInAccessibleOrientationWarning[];
210
228
 
@@ -218,4 +236,4 @@ declare function checkTestPointAccessibility(circuitJson: AnyCircuitElement[]):
218
236
  /** Warn once per name when its source nets belong to multiple electrical islands. */
219
237
  declare function checkSameNameNetsAreConnected(circuitJson: AnyCircuitElement[]): SourceConfusingNetNameWarning[];
220
238
 
221
- export { NetManager, checkAllPinsInComponentAreUnderspecified, checkConnectorAccessibleOrientation, checkCopperToBoardEdgeClearance, checkDifferentNetViaSpacing, checkEachPcbPortConnectedToPcbTraces, checkEachPcbTraceNonOverlapping, checkNoGroundPinDefined, checkNoPowerPinDefined, checkPadPadClearance, checkPadTraceClearance, checkPcbComponentOverCutout, checkPcbComponentOverlap, checkPcbComponentsMissingCourtyard, checkPcbComponentsOutOfBoard, checkPcbCopperOverKeepout, checkPcbTraceLengths, checkPcbTraceViaCounts, checkPcbTracesOutOfBoard, checkPinMustBeConnected, checkSameNameNetsAreConnected, checkSameNetViaSpacing, checkSchematicComponentExcessiveVerticalPadding, checkSchematicComponentMissingReferenceDesignatorText, checkSchematicComponentPortsOutsideBody, checkSourceTracesHavePcbTraces, checkSourceTracesMatchPcbTraceThickness, checkTestPointAccessibility, checkTracesAreContiguous, checkTwoTerminalSwitchContactsOnDifferentNets, checkViaPadClearance, checkViaTraceClearance, checkViasInPads, checkViasOffBoard, dedupePcbDrcErrors, runAllChecks, runAllNetlistChecks, runAllPinSpecificationChecks, runAllPlacementChecks, runAllRoutingChecks, runAllSchematicChecks };
239
+ export { NetManager, type PcbComponentOverlapError, checkAllPinsInComponentAreUnderspecified, checkConnectorAccessibleOrientation, checkCopperToBoardEdgeClearance, checkDifferentNetViaSpacing, checkEachPcbPortConnectedToPcbTraces, checkEachPcbTraceNonOverlapping, checkNoGroundPinDefined, checkNoPowerPinDefined, checkPadPadClearance, checkPadTraceClearance, checkPcbComponentOverCutout, checkPcbComponentOverlap, checkPcbComponentsMissingCourtyard, checkPcbComponentsOutOfBoard, checkPcbCopperOverKeepout, checkPcbTraceLengths, checkPcbTraceViaCounts, checkPcbTracesOutOfBoard, checkPinMustBeConnected, checkSameNameNetsAreConnected, checkSameNetViaSpacing, checkSchematicComponentExcessiveVerticalPadding, checkSchematicComponentMissingReferenceDesignatorText, checkSchematicComponentPortsOutsideBody, checkSourceTracesHavePcbTraces, checkSourceTracesMatchPcbTraceThickness, checkTestPointAccessibility, checkTracesAreContiguous, checkTwoTerminalSwitchContactsOnDifferentNets, checkViaPadClearance, checkViaTraceClearance, checkViasInPads, checkViasOffBoard, consolidatePcbOverlapErrors, dedupePcbDrcErrors, runAllChecks, runAllNetlistChecks, runAllPinSpecificationChecks, runAllPlacementChecks, runAllRoutingChecks, runAllSchematicChecks };
package/dist/index.js CHANGED
@@ -3510,7 +3510,11 @@ function checkPcbComponentOverlap(circuitJson) {
3510
3510
  type: "pcb_footprint_overlap_error",
3511
3511
  pcb_error_id: `pcb_footprint_overlap_${id1}_${id2}`,
3512
3512
  error_type: "pcb_footprint_overlap_error",
3513
- message: `${elem1.type} ${elem1Description} overlaps with ${elem2.type} ${elem2Description}`
3513
+ message: `${elem1.type} ${elem1Description} overlaps with ${elem2.type} ${elem2Description}`,
3514
+ pcb_component_ids: [
3515
+ elem1.pcb_component_id,
3516
+ elem2.pcb_component_id
3517
+ ].filter((id) => Boolean(id))
3514
3518
  };
3515
3519
  if (elem1.type === "pcb_smtpad" || elem2.type === "pcb_smtpad") {
3516
3520
  error.pcb_smtpad_ids = [];
@@ -4553,6 +4557,118 @@ function checkSchematicComponentPortsOutsideBody(circuitJson) {
4553
4557
  return warnings;
4554
4558
  }
4555
4559
 
4560
+ // lib/consolidate-pcb-overlap-errors.ts
4561
+ import { getPrimaryId as getPrimaryId9 } from "@tscircuit/circuit-json-util";
4562
+ function consolidatePcbOverlapErrors(circuitJson, errors) {
4563
+ const ownerByElementId = /* @__PURE__ */ new Map();
4564
+ const elementById = /* @__PURE__ */ new Map();
4565
+ for (const element of circuitJson) {
4566
+ const id = getPrimaryId9(element);
4567
+ elementById.set(id, element);
4568
+ if ("pcb_component_id" in element && element.pcb_component_id) {
4569
+ ownerByElementId.set(id, element.pcb_component_id);
4570
+ }
4571
+ }
4572
+ const rawErrors = errors.flatMap(
4573
+ (error) => error.type === "pcb_footprint_overlap_error" ? error.related_errors ?? [error] : [error]
4574
+ );
4575
+ const groups = /* @__PURE__ */ new Map();
4576
+ const keyByError = /* @__PURE__ */ new Map();
4577
+ for (const error of rawErrors) {
4578
+ let componentIds;
4579
+ if (error.type === "pcb_footprint_overlap_error") {
4580
+ if (error.pcb_keepout_ids?.length) continue;
4581
+ const overlap = error;
4582
+ const elementIds = [
4583
+ ...error.pcb_smtpad_ids ?? [],
4584
+ ...error.pcb_plated_hole_ids ?? [],
4585
+ ...error.pcb_hole_ids ?? []
4586
+ ];
4587
+ if (elementIds.some((id) => !ownerByElementId.has(id))) continue;
4588
+ componentIds = overlap.pcb_component_ids ?? elementIds.map((id) => ownerByElementId.get(id));
4589
+ } else if (error.type === "pcb_pad_pad_clearance_error") {
4590
+ if (error.pcb_pad_ids.some((id) => !ownerByElementId.has(id))) continue;
4591
+ componentIds = error.pcb_pad_ids.map((id) => ownerByElementId.get(id));
4592
+ } else if (error.type === "pcb_courtyard_overlap_error") {
4593
+ componentIds = error.pcb_component_ids;
4594
+ } else {
4595
+ continue;
4596
+ }
4597
+ componentIds = [...new Set(componentIds)].sort();
4598
+ if (componentIds.length !== 2) continue;
4599
+ const key = JSON.stringify(componentIds);
4600
+ keyByError.set(error, key);
4601
+ const group = groups.get(key) ?? [];
4602
+ group.push(error);
4603
+ groups.set(key, group);
4604
+ }
4605
+ const summaries = /* @__PURE__ */ new Map();
4606
+ for (const [key, group] of groups) {
4607
+ const overlaps = group.filter(
4608
+ (e) => e.type === "pcb_footprint_overlap_error"
4609
+ );
4610
+ if (group.length < 2 || overlaps.length === 0) continue;
4611
+ const componentIds = JSON.parse(key);
4612
+ const names = componentIds.map((id) => {
4613
+ const component = elementById.get(id);
4614
+ const source = component?.type === "pcb_component" ? elementById.get(component.source_component_id) : void 0;
4615
+ return source?.type === "source_component" ? source.name : id;
4616
+ });
4617
+ const counts = [
4618
+ [overlaps.length, "footprint overlap"],
4619
+ [
4620
+ group.filter((e) => e.type === "pcb_pad_pad_clearance_error").length,
4621
+ "pad clearance violation"
4622
+ ],
4623
+ [
4624
+ group.filter((e) => e.type === "pcb_courtyard_overlap_error").length,
4625
+ "courtyard conflict"
4626
+ ]
4627
+ ];
4628
+ const details = counts.filter(([count]) => count > 0).map(([count, label]) => `${count} ${label}${count === 1 ? "" : "s"}`);
4629
+ const summary = {
4630
+ type: "pcb_footprint_overlap_error",
4631
+ error_type: "pcb_footprint_overlap_error",
4632
+ pcb_error_id: `pcb_component_overlap_${componentIds.join("_")}`,
4633
+ pcb_component_ids: componentIds,
4634
+ message: `${names.join(" overlaps ")}: ${details.join(", ")}. Move the components apart.`,
4635
+ related_errors: group
4636
+ };
4637
+ if (group.some((error) => error.is_fatal)) summary.is_fatal = true;
4638
+ const affectedIds = /* @__PURE__ */ new Set();
4639
+ for (const error of group) {
4640
+ if (error.type === "pcb_footprint_overlap_error") {
4641
+ for (const id of [
4642
+ ...error.pcb_smtpad_ids ?? [],
4643
+ ...error.pcb_plated_hole_ids ?? [],
4644
+ ...error.pcb_hole_ids ?? []
4645
+ ])
4646
+ affectedIds.add(id);
4647
+ } else if (error.type === "pcb_pad_pad_clearance_error") {
4648
+ for (const id of error.pcb_pad_ids) affectedIds.add(id);
4649
+ }
4650
+ }
4651
+ for (const [field, type] of [
4652
+ ["pcb_smtpad_ids", "pcb_smtpad"],
4653
+ ["pcb_plated_hole_ids", "pcb_plated_hole"],
4654
+ ["pcb_hole_ids", "pcb_hole"]
4655
+ ]) {
4656
+ const ids = [...affectedIds].filter((id) => elementById.get(id)?.type === type).sort();
4657
+ if (ids.length) summary[field] = ids;
4658
+ }
4659
+ summaries.set(key, summary);
4660
+ }
4661
+ const emitted = /* @__PURE__ */ new Set();
4662
+ return rawErrors.flatMap((error) => {
4663
+ const key = keyByError.get(error);
4664
+ const summary = key === void 0 ? void 0 : summaries.get(key);
4665
+ if (!summary || key === void 0) return [error];
4666
+ if (emitted.has(key)) return [];
4667
+ emitted.add(key);
4668
+ return [summary];
4669
+ });
4670
+ }
4671
+
4556
4672
  // lib/check-same-name-nets-are-connected.ts
4557
4673
  function checkSameNameNetsAreConnected(circuitJson) {
4558
4674
  const parents = /* @__PURE__ */ new Map();
@@ -4881,8 +4997,8 @@ function checkTestPointAccessibility(circuitJson) {
4881
4997
  }
4882
4998
 
4883
4999
  // lib/run-all-checks.ts
4884
- async function runAllPlacementChecks(circuitJson) {
4885
- return [
5000
+ async function runAllPlacementChecks(circuitJson, { consolidateOverlaps = true } = {}) {
5001
+ const errors = [
4886
5002
  ...checkCopperToBoardEdgeClearance(circuitJson),
4887
5003
  ...checkViasInPads(circuitJson),
4888
5004
  ...checkPcbComponentsOutOfBoard(circuitJson),
@@ -4895,6 +5011,7 @@ async function runAllPlacementChecks(circuitJson) {
4895
5011
  ...checkConnectorAccessibleOrientation(circuitJson),
4896
5012
  ...checkTestPointAccessibility(circuitJson)
4897
5013
  ];
5014
+ return consolidateOverlaps ? consolidatePcbOverlapErrors(circuitJson, errors) : errors;
4898
5015
  }
4899
5016
  async function runAllNetlistChecks(circuitJson) {
4900
5017
  return [
@@ -4977,6 +5094,7 @@ export {
4977
5094
  checkViaTraceClearance,
4978
5095
  checkViasInPads,
4979
5096
  checkViasOffBoard,
5097
+ consolidatePcbOverlapErrors,
4980
5098
  dedupePcbDrcErrors,
4981
5099
  runAllChecks,
4982
5100
  runAllNetlistChecks,