@tscircuit/checks 0.0.27 → 0.0.28

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.js ADDED
@@ -0,0 +1,326 @@
1
+ // lib/add-start-and-end-port-ids-if-missing.ts
2
+ function distance(x1, y1, x2, y2) {
3
+ return Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2);
4
+ }
5
+ var addStartAndEndPortIdsIfMissing = (soup) => {
6
+ const pcbPorts = soup.filter((item) => item.type === "pcb_port");
7
+ const pcbSmtPads = soup.filter(
8
+ (item) => item.type === "pcb_smtpad"
9
+ );
10
+ const pcbTraces = soup.filter((item) => item.type === "pcb_trace");
11
+ function findPortIdOverlappingPoint(point, options = {}) {
12
+ const traceWidth = options.traceWidth || 0;
13
+ const directPort = pcbPorts.find(
14
+ (port) => distance(port.x, port.y, point.x, point.y) < 0.01
15
+ );
16
+ if (directPort) return directPort.pcb_port_id;
17
+ if (options.isFirstOrLastPoint) {
18
+ const smtPad = pcbSmtPads.find((pad) => {
19
+ if (pad.shape === "rect") {
20
+ return Math.abs(point.x - pad.x) < pad.width / 2 + traceWidth / 2 && Math.abs(point.y - pad.y) < pad.height / 2 + traceWidth / 2;
21
+ } else if (pad.shape === "circle") {
22
+ return distance(point.x, point.y, pad.x, pad.y) < pad.radius;
23
+ }
24
+ });
25
+ if (smtPad) return smtPad.pcb_port_id ?? null;
26
+ }
27
+ return null;
28
+ }
29
+ for (const trace of pcbTraces) {
30
+ for (let index = 0; index < trace.route.length; index++) {
31
+ const segment = trace.route[index];
32
+ const isFirstOrLastPoint = index === 0 || index === trace.route.length - 1;
33
+ if (segment.route_type === "wire") {
34
+ if (!segment.start_pcb_port_id && index === 0) {
35
+ const startPortId = findPortIdOverlappingPoint(segment, {
36
+ isFirstOrLastPoint,
37
+ traceWidth: segment.width
38
+ });
39
+ if (startPortId) {
40
+ segment.start_pcb_port_id = startPortId;
41
+ }
42
+ }
43
+ if (!segment.end_pcb_port_id && index === trace.route.length - 1) {
44
+ const endPortId = findPortIdOverlappingPoint(segment, {
45
+ isFirstOrLastPoint,
46
+ traceWidth: segment.width
47
+ });
48
+ if (endPortId) {
49
+ segment.end_pcb_port_id = endPortId;
50
+ }
51
+ }
52
+ }
53
+ }
54
+ }
55
+ };
56
+
57
+ // lib/check-each-pcb-port-connected.ts
58
+ import { getReadableNameForPcbPort } from "@tscircuit/soup-util";
59
+ function checkEachPcbPortConnected(soup) {
60
+ addStartAndEndPortIdsIfMissing(soup);
61
+ const pcbPorts = soup.filter((item) => item.type === "pcb_port");
62
+ const pcbTraces = soup.filter((item) => item.type === "pcb_trace");
63
+ const sourceTraces = soup.filter(
64
+ (item) => item.type === "source_trace"
65
+ );
66
+ const errors = [];
67
+ for (const port of pcbPorts) {
68
+ const connectedTraces = pcbTraces.filter(
69
+ (trace) => trace.route.some(
70
+ (segment) => segment.route_type === "wire" && (segment.start_pcb_port_id === port.pcb_port_id || segment.end_pcb_port_id === port.pcb_port_id)
71
+ )
72
+ );
73
+ const sourceTrace = sourceTraces.find(
74
+ (trace) => trace.connected_source_port_ids?.includes(port.source_port_id)
75
+ );
76
+ const hasSourceTraceWithConnections = sourceTrace && sourceTrace.connected_source_port_ids?.length > 0;
77
+ if (connectedTraces.length === 0 && hasSourceTraceWithConnections) {
78
+ errors.push({
79
+ type: "pcb_trace_error",
80
+ message: `pcb_trace_error: PCB port ${getReadableNameForPcbPort(soup, port.pcb_port_id)} is not connected by a PCB trace`,
81
+ source_trace_id: sourceTrace.source_trace_id,
82
+ error_type: "pcb_trace_error",
83
+ pcb_trace_id: "",
84
+ pcb_trace_error_id: "",
85
+ pcb_component_ids: [],
86
+ pcb_port_ids: [port.pcb_port_id]
87
+ });
88
+ }
89
+ }
90
+ return errors;
91
+ }
92
+
93
+ // lib/check-each-pcb-trace-non-overlapping.ts
94
+ import Debug from "debug";
95
+ import { getReadableNameForElement } from "@tscircuit/soup-util";
96
+ import { getFullConnectivityMapFromCircuitJson } from "circuit-json-to-connectivity-map";
97
+ var debug = Debug("tscircuit:checks:check-each-pcb-trace-non-overlapping");
98
+ function lineIntersects(x1, y1, x2, y2, x3, y3, x4, y4) {
99
+ const denom = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1);
100
+ if (denom === 0) return false;
101
+ const ua = ((x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)) / denom;
102
+ const ub = ((x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3)) / denom;
103
+ return ua >= 0 && ua <= 1 && ub >= 0 && ub <= 1;
104
+ }
105
+ function tracesOverlap(trace1, trace2) {
106
+ for (let i = 0; i < trace1.route.length - 1; i++) {
107
+ for (let j = 0; j < trace2.route.length - 1; j++) {
108
+ const seg1 = trace1.route[i];
109
+ const seg2 = trace1.route[i + 1];
110
+ const seg3 = trace2.route[j];
111
+ const seg4 = trace2.route[j + 1];
112
+ if (seg1.route_type === "wire" && seg2.route_type === "wire" && seg3.route_type === "wire" && seg4.route_type === "wire" && seg1.layer === seg3.layer) {
113
+ const areLinesIntersecting = lineIntersects(
114
+ seg1.x,
115
+ seg1.y,
116
+ seg2.x,
117
+ seg2.y,
118
+ seg3.x,
119
+ seg3.y,
120
+ seg4.x,
121
+ seg4.y
122
+ );
123
+ if (areLinesIntersecting)
124
+ return {
125
+ // return the intersection point
126
+ x: (seg1.x * seg2.y - seg2.x * seg1.y) / (seg2.y - seg1.y),
127
+ y: (seg1.x * seg2.y - seg2.x * seg1.y) / (seg2.y - seg1.y)
128
+ };
129
+ }
130
+ }
131
+ }
132
+ return false;
133
+ }
134
+ function traceOverlapsWithPad(trace, pad) {
135
+ for (let i = 0; i < trace.route.length - 1; i++) {
136
+ const seg1 = trace.route[i];
137
+ const seg2 = trace.route[i + 1];
138
+ if (seg1.route_type === "wire" && seg2.route_type === "wire" && seg1.layer === pad.layer && pad.shape === "rect") {
139
+ const padLeft = pad.x - pad.width / 2;
140
+ const padRight = pad.x + pad.width / 2;
141
+ const padTop = pad.y - pad.height / 2;
142
+ const padBottom = pad.y + pad.height / 2;
143
+ if (lineIntersects(
144
+ seg1.x,
145
+ seg1.y,
146
+ seg2.x,
147
+ seg2.y,
148
+ padLeft,
149
+ padTop,
150
+ padRight,
151
+ padTop
152
+ ) || lineIntersects(
153
+ seg1.x,
154
+ seg1.y,
155
+ seg2.x,
156
+ seg2.y,
157
+ padRight,
158
+ padTop,
159
+ padRight,
160
+ padBottom
161
+ ) || lineIntersects(
162
+ seg1.x,
163
+ seg1.y,
164
+ seg2.x,
165
+ seg2.y,
166
+ padRight,
167
+ padBottom,
168
+ padLeft,
169
+ padBottom
170
+ ) || lineIntersects(
171
+ seg1.x,
172
+ seg1.y,
173
+ seg2.x,
174
+ seg2.y,
175
+ padLeft,
176
+ padBottom,
177
+ padLeft,
178
+ padTop
179
+ )) {
180
+ return true;
181
+ }
182
+ }
183
+ }
184
+ return false;
185
+ }
186
+ function getPcbPortIdsConnectedToTrace(trace) {
187
+ const connectedPcbPorts = /* @__PURE__ */ new Set();
188
+ for (const segment of trace.route) {
189
+ if (segment.route_type === "wire") {
190
+ if (segment.start_pcb_port_id)
191
+ connectedPcbPorts.add(segment.start_pcb_port_id);
192
+ if (segment.end_pcb_port_id)
193
+ connectedPcbPorts.add(segment.end_pcb_port_id);
194
+ }
195
+ }
196
+ return Array.from(connectedPcbPorts);
197
+ }
198
+ function getPcbPortIdsConnectedToTraces(traces) {
199
+ const connectedPorts = /* @__PURE__ */ new Set();
200
+ for (const trace of traces) {
201
+ for (const portId of getPcbPortIdsConnectedToTrace(trace)) {
202
+ connectedPorts.add(portId);
203
+ }
204
+ }
205
+ return Array.from(connectedPorts);
206
+ }
207
+ function checkEachPcbTraceNonOverlapping(soup) {
208
+ addStartAndEndPortIdsIfMissing(soup);
209
+ const connMap = getFullConnectivityMapFromCircuitJson(soup);
210
+ const pcbTraces = soup.filter(
211
+ (item) => item.type === "pcb_trace"
212
+ );
213
+ const pcbSMTPads = soup.filter(
214
+ (item) => item.type === "pcb_smtpad"
215
+ );
216
+ const errors = [];
217
+ for (let i = 0; i < pcbTraces.length; i++) {
218
+ for (let j = i + 1; j < pcbTraces.length; j++) {
219
+ debug(
220
+ `Checking overlap for ${pcbTraces[i].pcb_trace_id} and ${pcbTraces[j].pcb_trace_id}`
221
+ );
222
+ const connectedPorts = getPcbPortIdsConnectedToTraces([
223
+ pcbTraces[i],
224
+ pcbTraces[j]
225
+ ]);
226
+ debug(`Connected ports: ${connectedPorts.join(",")}`);
227
+ if (connectedPorts.length === 0) {
228
+ debug("No ports connected to trace, skipping");
229
+ continue;
230
+ }
231
+ if (connectedPorts.length === 1) {
232
+ debug("Only one port connected, skipping");
233
+ continue;
234
+ }
235
+ if (connMap.areAllIdsConnected(connectedPorts)) {
236
+ continue;
237
+ }
238
+ const overlapPoint = tracesOverlap(pcbTraces[i], pcbTraces[j]);
239
+ if (overlapPoint) {
240
+ errors.push({
241
+ type: "pcb_trace_error",
242
+ error_type: "pcb_trace_error",
243
+ message: `PCB trace ${pcbTraces[i].pcb_trace_id} overlaps with ${pcbTraces[j].pcb_trace_id}`,
244
+ pcb_trace_id: pcbTraces[i].pcb_trace_id,
245
+ source_trace_id: "",
246
+ pcb_trace_error_id: `overlap_${pcbTraces[i].pcb_trace_id}_${pcbTraces[j].pcb_trace_id}`,
247
+ pcb_component_ids: [],
248
+ // @ts-ignore this is available in a future version of @tscircuit/soup
249
+ center: overlapPoint,
250
+ pcb_port_ids: getPcbPortIdsConnectedToTraces([
251
+ pcbTraces[i],
252
+ pcbTraces[j]
253
+ ])
254
+ });
255
+ }
256
+ }
257
+ for (const pad of pcbSMTPads) {
258
+ if (pad.pcb_port_id && connMap.areAllIdsConnected(
259
+ getPcbPortIdsConnectedToTrace(pcbTraces[i]).concat([pad.pcb_port_id])
260
+ )) {
261
+ continue;
262
+ }
263
+ if (traceOverlapsWithPad(pcbTraces[i], pad)) {
264
+ errors.push({
265
+ type: "pcb_trace_error",
266
+ error_type: "pcb_trace_error",
267
+ message: `PCB trace ${getReadableNameForElement(soup, pcbTraces[i].pcb_trace_id)} overlaps with ${getReadableNameForElement(soup, pad.pcb_smtpad_id)}`,
268
+ pcb_trace_id: pcbTraces[i].pcb_trace_id,
269
+ source_trace_id: "",
270
+ pcb_trace_error_id: `overlap_${pcbTraces[i].pcb_trace_id}_${pad.pcb_smtpad_id}`,
271
+ pcb_component_ids: [],
272
+ pcb_port_ids: getPcbPortIdsConnectedToTrace(pcbTraces[i])
273
+ });
274
+ }
275
+ }
276
+ }
277
+ return errors;
278
+ }
279
+
280
+ // lib/net-manager.ts
281
+ var NetManager = class {
282
+ networks = /* @__PURE__ */ new Set();
283
+ setConnected(nodes) {
284
+ if (nodes.length < 2) return;
285
+ let targetNetwork = null;
286
+ for (const network of this.networks) {
287
+ for (const node of nodes) {
288
+ if (network.has(node)) {
289
+ if (targetNetwork === null) {
290
+ targetNetwork = network;
291
+ } else if (targetNetwork !== network) {
292
+ for (const mergeNode of network) {
293
+ targetNetwork.add(mergeNode);
294
+ }
295
+ this.networks.delete(network);
296
+ }
297
+ break;
298
+ }
299
+ }
300
+ if (targetNetwork !== null && targetNetwork !== network) break;
301
+ }
302
+ if (targetNetwork === null) {
303
+ targetNetwork = new Set(nodes);
304
+ this.networks.add(targetNetwork);
305
+ } else {
306
+ for (const node of nodes) {
307
+ targetNetwork.add(node);
308
+ }
309
+ }
310
+ }
311
+ isConnected(nodes) {
312
+ if (nodes.length < 2) return true;
313
+ for (const network of this.networks) {
314
+ if (nodes.every((node) => network.has(node))) {
315
+ return true;
316
+ }
317
+ }
318
+ return false;
319
+ }
320
+ };
321
+ export {
322
+ NetManager,
323
+ checkEachPcbPortConnected,
324
+ checkEachPcbTraceNonOverlapping
325
+ };
326
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../lib/add-start-and-end-port-ids-if-missing.ts","../lib/check-each-pcb-port-connected.ts","../lib/check-each-pcb-trace-non-overlapping.ts","../lib/net-manager.ts"],"sourcesContent":["import type {\n PcbPort,\n PcbTrace,\n AnyCircuitElement,\n PcbSmtPad,\n} from \"circuit-json\"\n\nfunction distance(x1: number, y1: number, x2: number, y2: number): number {\n return Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2)\n}\n\n/**\n * HACK: this whole method and all usage of it is a hack because of this issue:\n * https://github.com/tscircuit/tscircuit/issues/291\n */\nexport const addStartAndEndPortIdsIfMissing = (\n soup: AnyCircuitElement[],\n): void => {\n const pcbPorts: PcbPort[] = soup.filter((item) => item.type === \"pcb_port\")\n const pcbSmtPads: PcbSmtPad[] = soup.filter(\n (item) => item.type === \"pcb_smtpad\",\n )\n const pcbTraces: PcbTrace[] = soup.filter((item) => item.type === \"pcb_trace\")\n\n function findPortIdOverlappingPoint(\n point: {\n x: number\n y: number\n },\n options: { isFirstOrLastPoint?: boolean; traceWidth?: number } = {},\n ): string | null {\n const traceWidth = options.traceWidth || 0\n const directPort = pcbPorts.find(\n (port) => distance(port.x, port.y, point.x, point.y) < 0.01,\n )\n if (directPort) return directPort.pcb_port_id\n\n // If it starts or ends inside an smtpad, we'll connect it to the por\n if (options.isFirstOrLastPoint) {\n const smtPad = pcbSmtPads.find((pad) => {\n if (pad.shape === \"rect\") {\n return (\n Math.abs(point.x - pad.x) < pad.width / 2 + traceWidth / 2 &&\n Math.abs(point.y - pad.y) < pad.height / 2 + traceWidth / 2\n )\n // biome-ignore lint/style/noUselessElse: <explanation>\n } else if (pad.shape === \"circle\") {\n return distance(point.x, point.y, pad.x, pad.y) < pad.radius\n }\n })\n if (smtPad) return smtPad.pcb_port_id ?? null\n }\n\n return null\n }\n\n // Add start_pcb_port_id and end_pcb_port_id if not present\n for (const trace of pcbTraces) {\n for (let index = 0; index < trace.route.length; index++) {\n const segment = trace.route[index]\n const isFirstOrLastPoint = index === 0 || index === trace.route.length - 1\n if (segment.route_type === \"wire\") {\n if (!segment.start_pcb_port_id && index === 0) {\n const startPortId = findPortIdOverlappingPoint(segment, {\n isFirstOrLastPoint,\n traceWidth: segment.width,\n })\n if (startPortId) {\n segment.start_pcb_port_id = startPortId\n }\n }\n if (!segment.end_pcb_port_id && index === trace.route.length - 1) {\n const endPortId = findPortIdOverlappingPoint(segment, {\n isFirstOrLastPoint,\n traceWidth: segment.width,\n })\n if (endPortId) {\n segment.end_pcb_port_id = endPortId\n }\n }\n }\n }\n }\n}\n","import type {\n PcbPort,\n PcbTrace,\n SourceTrace,\n AnyCircuitElement,\n PcbTraceError,\n} from \"circuit-json\"\nimport { addStartAndEndPortIdsIfMissing } from \"./add-start-and-end-port-ids-if-missing\"\nimport { getReadableNameForPcbPort } from \"@tscircuit/soup-util\"\n\nfunction checkEachPcbPortConnected(soup: AnyCircuitElement[]): PcbTraceError[] {\n addStartAndEndPortIdsIfMissing(soup)\n const pcbPorts: PcbPort[] = soup.filter((item) => item.type === \"pcb_port\")\n const pcbTraces: PcbTrace[] = soup.filter((item) => item.type === \"pcb_trace\")\n const sourceTraces: SourceTrace[] = soup.filter(\n (item) => item.type === \"source_trace\",\n )\n const errors: PcbTraceError[] = []\n\n for (const port of pcbPorts) {\n const connectedTraces = pcbTraces.filter((trace) =>\n trace.route.some(\n (segment: any) =>\n segment.route_type === \"wire\" &&\n (segment.start_pcb_port_id === port.pcb_port_id ||\n segment.end_pcb_port_id === port.pcb_port_id),\n ),\n )\n\n const sourceTrace = sourceTraces.find((trace) =>\n trace.connected_source_port_ids?.includes(port.source_port_id),\n )\n\n const hasSourceTraceWithConnections =\n sourceTrace && sourceTrace.connected_source_port_ids?.length > 0\n\n if (connectedTraces.length === 0 && hasSourceTraceWithConnections) {\n errors.push({\n type: \"pcb_trace_error\",\n message: `pcb_trace_error: PCB port ${getReadableNameForPcbPort(soup, port.pcb_port_id)} is not connected by a PCB trace`,\n source_trace_id: sourceTrace.source_trace_id,\n error_type: \"pcb_trace_error\",\n pcb_trace_id: \"\",\n pcb_trace_error_id: \"\",\n pcb_component_ids: [],\n pcb_port_ids: [port.pcb_port_id],\n })\n }\n }\n\n return errors\n}\n\nexport { checkEachPcbPortConnected }\n","import type {\n PcbTrace,\n PcbSmtPad,\n AnyCircuitElement,\n PcbTraceError,\n} from \"circuit-json\"\nimport { addStartAndEndPortIdsIfMissing } from \"./add-start-and-end-port-ids-if-missing\"\nimport Debug from \"debug\"\nimport { getReadableNameForElement } from \"@tscircuit/soup-util\"\nimport { getFullConnectivityMapFromCircuitJson } from \"circuit-json-to-connectivity-map\"\n\nconst debug = Debug(\"tscircuit:checks:check-each-pcb-trace-non-overlapping\")\n\n/**\n * Checks if lines given by (x1, y1) and (x2, y2) intersect with line\n * given by (x3, y3) and (x4, y4)\n */\nfunction lineIntersects(\n x1: number,\n y1: number,\n x2: number,\n y2: number,\n x3: number,\n y3: number,\n x4: number,\n y4: number,\n): boolean {\n const denom = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1)\n if (denom === 0) return false // parallel lines\n\n const ua = ((x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)) / denom\n const ub = ((x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3)) / denom\n\n return ua >= 0 && ua <= 1 && ub >= 0 && ub <= 1\n}\n\nfunction tracesOverlap(\n trace1: PcbTrace,\n trace2: PcbTrace,\n): { x: number; y: number } | false {\n for (let i = 0; i < trace1.route.length - 1; i++) {\n for (let j = 0; j < trace2.route.length - 1; j++) {\n const seg1 = trace1.route[i]\n const seg2 = trace1.route[i + 1]\n const seg3 = trace2.route[j]\n const seg4 = trace2.route[j + 1]\n\n if (\n seg1.route_type === \"wire\" &&\n seg2.route_type === \"wire\" &&\n seg3.route_type === \"wire\" &&\n seg4.route_type === \"wire\" &&\n seg1.layer === seg3.layer\n ) {\n const areLinesIntersecting = lineIntersects(\n seg1.x,\n seg1.y,\n seg2.x,\n seg2.y,\n seg3.x,\n seg3.y,\n seg4.x,\n seg4.y,\n )\n if (areLinesIntersecting)\n return {\n // return the intersection point\n x: (seg1.x * seg2.y - seg2.x * seg1.y) / (seg2.y - seg1.y),\n y: (seg1.x * seg2.y - seg2.x * seg1.y) / (seg2.y - seg1.y),\n }\n }\n }\n }\n return false\n}\n\nfunction traceOverlapsWithPad(trace: PcbTrace, pad: PcbSmtPad): boolean {\n for (let i = 0; i < trace.route.length - 1; i++) {\n const seg1 = trace.route[i]\n const seg2 = trace.route[i + 1]\n\n if (\n seg1.route_type === \"wire\" &&\n seg2.route_type === \"wire\" &&\n seg1.layer === pad.layer &&\n pad.shape === \"rect\"\n ) {\n const padLeft = pad.x - pad.width / 2\n const padRight = pad.x + pad.width / 2\n const padTop = pad.y - pad.height / 2\n const padBottom = pad.y + pad.height / 2\n\n if (\n lineIntersects(\n seg1.x,\n seg1.y,\n seg2.x,\n seg2.y,\n padLeft,\n padTop,\n padRight,\n padTop,\n ) ||\n lineIntersects(\n seg1.x,\n seg1.y,\n seg2.x,\n seg2.y,\n padRight,\n padTop,\n padRight,\n padBottom,\n ) ||\n lineIntersects(\n seg1.x,\n seg1.y,\n seg2.x,\n seg2.y,\n padRight,\n padBottom,\n padLeft,\n padBottom,\n ) ||\n lineIntersects(\n seg1.x,\n seg1.y,\n seg2.x,\n seg2.y,\n padLeft,\n padBottom,\n padLeft,\n padTop,\n )\n ) {\n return true\n }\n }\n }\n return false\n}\n\nfunction getPcbPortIdsConnectedToTrace(trace: PcbTrace) {\n const connectedPcbPorts = new Set<string>()\n for (const segment of trace.route) {\n if (segment.route_type === \"wire\") {\n if (segment.start_pcb_port_id)\n connectedPcbPorts.add(segment.start_pcb_port_id)\n if (segment.end_pcb_port_id)\n connectedPcbPorts.add(segment.end_pcb_port_id)\n }\n }\n\n return Array.from(connectedPcbPorts)\n}\n\nfunction getPcbPortIdsConnectedToTraces(traces: PcbTrace[]) {\n const connectedPorts = new Set<string>()\n for (const trace of traces) {\n for (const portId of getPcbPortIdsConnectedToTrace(trace)) {\n connectedPorts.add(portId)\n }\n }\n return Array.from(connectedPorts)\n}\n\nfunction checkEachPcbTraceNonOverlapping(\n soup: AnyCircuitElement[],\n): PcbTraceError[] {\n addStartAndEndPortIdsIfMissing(soup)\n const connMap = getFullConnectivityMapFromCircuitJson(soup)\n const pcbTraces: PcbTrace[] = soup.filter(\n (item): item is PcbTrace => item.type === \"pcb_trace\",\n )\n const pcbSMTPads: PcbSmtPad[] = soup.filter(\n (item): item is PcbSmtPad => item.type === \"pcb_smtpad\",\n )\n const errors: PcbTraceError[] = []\n\n for (let i = 0; i < pcbTraces.length; i++) {\n for (let j = i + 1; j < pcbTraces.length; j++) {\n debug(\n `Checking overlap for ${pcbTraces[i].pcb_trace_id} and ${pcbTraces[j].pcb_trace_id}`,\n )\n const connectedPorts = getPcbPortIdsConnectedToTraces([\n pcbTraces[i],\n pcbTraces[j],\n ])\n debug(`Connected ports: ${connectedPorts.join(\",\")}`)\n\n if (connectedPorts.length === 0) {\n debug(\"No ports connected to trace, skipping\")\n continue\n }\n\n if (connectedPorts.length === 1) {\n debug(\"Only one port connected, skipping\")\n continue\n }\n\n if (connMap.areAllIdsConnected(connectedPorts)) {\n continue\n }\n const overlapPoint = tracesOverlap(pcbTraces[i], pcbTraces[j])\n if (overlapPoint) {\n errors.push({\n type: \"pcb_trace_error\",\n error_type: \"pcb_trace_error\",\n message: `PCB trace ${pcbTraces[i].pcb_trace_id} overlaps with ${pcbTraces[j].pcb_trace_id}`,\n pcb_trace_id: pcbTraces[i].pcb_trace_id,\n source_trace_id: \"\",\n pcb_trace_error_id: `overlap_${pcbTraces[i].pcb_trace_id}_${pcbTraces[j].pcb_trace_id}`,\n pcb_component_ids: [],\n // @ts-ignore this is available in a future version of @tscircuit/soup\n center: overlapPoint,\n pcb_port_ids: getPcbPortIdsConnectedToTraces([\n pcbTraces[i],\n pcbTraces[j],\n ]),\n })\n }\n }\n\n for (const pad of pcbSMTPads) {\n if (\n pad.pcb_port_id &&\n connMap.areAllIdsConnected(\n getPcbPortIdsConnectedToTrace(pcbTraces[i]).concat([pad.pcb_port_id]),\n )\n ) {\n continue\n }\n if (traceOverlapsWithPad(pcbTraces[i], pad)) {\n errors.push({\n type: \"pcb_trace_error\",\n error_type: \"pcb_trace_error\",\n message: `PCB trace ${getReadableNameForElement(soup, pcbTraces[i].pcb_trace_id)} overlaps with ${getReadableNameForElement(soup, pad.pcb_smtpad_id)}`,\n pcb_trace_id: pcbTraces[i].pcb_trace_id,\n source_trace_id: \"\",\n pcb_trace_error_id: `overlap_${pcbTraces[i].pcb_trace_id}_${pad.pcb_smtpad_id}`,\n pcb_component_ids: [],\n pcb_port_ids: getPcbPortIdsConnectedToTrace(pcbTraces[i]),\n })\n }\n }\n }\n\n return errors\n}\n\nexport { checkEachPcbTraceNonOverlapping }\n","export class NetManager {\n private networks: Set<Set<string>> = new Set()\n\n setConnected(nodes: string[]): void {\n if (nodes.length < 2) return\n\n let targetNetwork: Set<string> | null = null\n\n // Check if any of the nodes are already in a network\n for (const network of this.networks) {\n for (const node of nodes) {\n if (network.has(node)) {\n if (targetNetwork === null) {\n targetNetwork = network\n } else if (targetNetwork !== network) {\n // Merge networks\n for (const mergeNode of network) {\n targetNetwork.add(mergeNode)\n }\n this.networks.delete(network)\n }\n break\n }\n }\n if (targetNetwork !== null && targetNetwork !== network) break\n }\n\n // If no existing network found, create a new one\n if (targetNetwork === null) {\n targetNetwork = new Set(nodes)\n this.networks.add(targetNetwork)\n } else {\n // Add all nodes to the target network\n for (const node of nodes) {\n targetNetwork.add(node)\n }\n }\n }\n\n isConnected(nodes: string[]): boolean {\n if (nodes.length < 2) return true\n\n for (const network of this.networks) {\n if (nodes.every((node) => network.has(node))) {\n return true\n }\n }\n\n return false\n }\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -2,13 +2,13 @@
2
2
  "name": "@tscircuit/checks",
3
3
  "module": "index.ts",
4
4
  "type": "module",
5
- "main": "./dist/index.cjs",
6
- "version": "0.0.27",
5
+ "main": "./dist/index.js",
6
+ "version": "0.0.28",
7
7
  "files": [
8
8
  "dist"
9
9
  ],
10
10
  "scripts": {
11
- "build": "tsup index.ts --dts --sourcemap",
11
+ "build": "tsup-node index.ts --format esm --dts --sourcemap",
12
12
  "format:check": "biome format .",
13
13
  "format": "biome format . --write"
14
14
  },
@@ -23,10 +23,10 @@
23
23
  },
24
24
  "peerDependencies": {
25
25
  "circuit-json": "*",
26
- "typescript": "^5.5.3"
26
+ "typescript": "^5.5.3",
27
+ "@tscircuit/soup-util": "*"
27
28
  },
28
29
  "dependencies": {
29
- "@tscircuit/soup-util": "0.0.36",
30
- "circuit-json-to-connectivity-map": "^0.0.17"
30
+ "circuit-json-to-connectivity-map": "^0.0.10"
31
31
  }
32
32
  }