@tscircuit/checks 0.0.1

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 ADDED
@@ -0,0 +1,121 @@
1
+ # @tscircuit/checks
2
+
3
+ ## `checkEachPcbPortConnected(soup: AnySoupElement[]) => PCBTraceError[]`
4
+
5
+ Returns `pcb_trace_error` if any `source_port` is not connected to a net or it's other
6
+ source ports.
7
+
8
+ ## `checkEachPcbTraceNonOverlapping(soup: AnySoupElement[]) => PCBTraceError[]`
9
+
10
+ Returns `pcb_trace_error` if any `pcb_trace` is overlapping with another `pcb_trace`
11
+ that is not connected to the same net.
12
+
13
+ ## Implementation Details
14
+
15
+ > [!NOTE]
16
+ > It can be helpful to look at an [example soup file](./tests/assets/unrouted-soup-example.json)
17
+
18
+ tscircuit soup JSON array containing elements. For checks involving source ports,
19
+ and pcb traces here are the relevant elements (the types are produced below)
20
+
21
+ > [!NOTE]
22
+ > For the most up-to-date types, check out [@tscircuit/soup](https://github.com/tscircuit/soup)
23
+
24
+ ```ts
25
+ // You can import these types from the @tscircuit/soup package e.g.
26
+ // import type { PCBPort, PCBTrace, AnySoupElement } from "@tscircuit/soup"
27
+
28
+ import { z } from "zod"
29
+ import { distance } from "../units"
30
+
31
+ export const pcb_trace = z.object({
32
+ type: z.literal("pcb_trace"),
33
+ source_trace_id: z.string().optional(),
34
+ pcb_component_id: z.string().optional(),
35
+ pcb_trace_id: z.string(),
36
+ route: z.array(
37
+ z.union([
38
+ z.object({
39
+ route_type: z.literal("wire"),
40
+ x: distance,
41
+ y: distance,
42
+ width: distance,
43
+ start_pcb_port_id: z.string().optional(),
44
+ end_pcb_port_id: z.string().optional(),
45
+ layer: z.string(),
46
+ }),
47
+ z.object({
48
+ route_type: z.literal("via"),
49
+ x: distance,
50
+ y: distance,
51
+ from_layer: z.string(),
52
+ to_layer: z.string(),
53
+ }),
54
+ ])
55
+ ),
56
+ })
57
+
58
+ export type PCBTraceInput = z.input<typeof pcb_trace>
59
+ export type PCBTrace = z.output<typeof pcb_trace>
60
+
61
+ import { distance } from "../units"
62
+ import { layer_ref } from "./properties/layer_ref"
63
+
64
+ export const pcb_port = z
65
+ .object({
66
+ type: z.literal("pcb_port"),
67
+ pcb_port_id: z.string(),
68
+ source_port_id: z.string(),
69
+ pcb_component_id: z.string(),
70
+ x: distance,
71
+ y: distance,
72
+ layers: z.array(layer_ref),
73
+ })
74
+ .describe("Defines a port on the PCB")
75
+
76
+ export type PCBPort = z.infer<typeof pcb_port>
77
+ export type PCBPortInput = z.input<typeof pcb_port>
78
+
79
+ export const source_port = z.object({
80
+ type: z.literal("source_port"),
81
+ pin_number: z.number().optional(),
82
+ port_hints: z.array(z.string()).optional(),
83
+ name: z.string(),
84
+ source_port_id: z.string(),
85
+ source_component_id: z.string(),
86
+ })
87
+
88
+ export type SourcePort = z.infer<typeof source_port>
89
+
90
+ export const source_net = z.object({
91
+ type: z.literal("source_net"),
92
+ source_net_id: z.string(),
93
+ name: z.string(),
94
+ member_source_group_ids: z.array(z.string()),
95
+ is_power: z.boolean().optional(),
96
+ is_ground: z.boolean().optional(),
97
+ is_digital_signal: z.boolean().optional(),
98
+ is_analog_signal: z.boolean().optional(),
99
+ })
100
+
101
+ export type SourceNet = z.infer<typeof source_net>
102
+ export type SourceNetInput = z.input<typeof source_net>
103
+
104
+ import { z } from "zod"
105
+
106
+ export const pcb_trace_error = z
107
+ .object({
108
+ pcb_error_id: z.string(),
109
+ type: z.literal("pcb_error"),
110
+ error_type: z.literal("pcb_trace_error"),
111
+ message: z.string(),
112
+ pcb_trace_id: z.string(),
113
+ source_trace_id: z.string(),
114
+ pcb_component_ids: z.array(z.string()),
115
+ pcb_port_ids: z.array(z.string()),
116
+ })
117
+ .describe("Defines a trace error on the PCB")
118
+
119
+ export type PCBTraceErrorInput = z.input<typeof pcb_trace_error>
120
+ export type PCBTraceError = z.infer<typeof pcb_trace_error>
121
+ ```
package/dist/index.cjs ADDED
@@ -0,0 +1,291 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // index.ts
21
+ var checks_exports = {};
22
+ __export(checks_exports, {
23
+ checkEachPcbPortConnected: () => checkEachPcbPortConnected,
24
+ checkEachPcbTraceNonOverlapping: () => checkEachPcbTraceNonOverlapping
25
+ });
26
+ module.exports = __toCommonJS(checks_exports);
27
+
28
+ // lib/check-each-pcb-port-connected.ts
29
+ function distance(x1, y1, x2, y2) {
30
+ return Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2));
31
+ }
32
+ function checkEachPcbPortConnected(soup) {
33
+ const pcbPorts = soup.filter((item) => item.type === "pcb_port");
34
+ const pcbTraces = soup.filter((item) => item.type === "pcb_trace");
35
+ const sourceTraces = soup.filter(
36
+ (item) => item.type === "source_trace"
37
+ );
38
+ const errors = [];
39
+ pcbTraces.forEach((trace) => {
40
+ trace.route.forEach((segment, index) => {
41
+ if (segment.route_type === "wire") {
42
+ if (!segment.start_pcb_port_id && index === 0) {
43
+ const startPort = pcbPorts.find(
44
+ (port) => distance(port.x, port.y, segment.x, segment.y) < 1e-3
45
+ );
46
+ if (startPort) {
47
+ segment.start_pcb_port_id = startPort.pcb_port_id;
48
+ }
49
+ }
50
+ if (!segment.end_pcb_port_id && index === trace.route.length - 1) {
51
+ const endPort = pcbPorts.find(
52
+ (port) => distance(port.x, port.y, segment.x, segment.y) < 1e-3
53
+ );
54
+ if (endPort) {
55
+ segment.end_pcb_port_id = endPort.pcb_port_id;
56
+ }
57
+ }
58
+ }
59
+ });
60
+ });
61
+ for (const port of pcbPorts) {
62
+ const connectedTraces = pcbTraces.filter(
63
+ (trace) => trace.route.some(
64
+ (segment) => segment.route_type === "wire" && (segment.start_pcb_port_id === port.pcb_port_id || segment.end_pcb_port_id === port.pcb_port_id)
65
+ )
66
+ );
67
+ if (connectedTraces.length === 0) {
68
+ const sourceTrace = sourceTraces.find(
69
+ (trace) => trace.connected_source_port_ids.includes(port.source_port_id)
70
+ );
71
+ errors.push({
72
+ type: "pcb_error",
73
+ message: `pcb_trace_error: PCB port ${port.pcb_port_id} is not connected by a PCB trace`,
74
+ source_trace_id: sourceTrace ? sourceTrace.source_trace_id : "",
75
+ error_type: "pcb_trace_error",
76
+ pcb_trace_id: "",
77
+ pcb_error_id: "",
78
+ // Add appropriate ID generation if necessary
79
+ pcb_component_ids: [],
80
+ pcb_port_ids: [port.pcb_port_id]
81
+ });
82
+ }
83
+ }
84
+ return errors;
85
+ }
86
+
87
+ // lib/net-manager.ts
88
+ var NetManager = class {
89
+ networks = /* @__PURE__ */ new Set();
90
+ setConnected(nodes) {
91
+ if (nodes.length < 2) return;
92
+ let targetNetwork = null;
93
+ for (const network of this.networks) {
94
+ for (const node of nodes) {
95
+ if (network.has(node)) {
96
+ if (targetNetwork === null) {
97
+ targetNetwork = network;
98
+ } else if (targetNetwork !== network) {
99
+ for (const mergeNode of network) {
100
+ targetNetwork.add(mergeNode);
101
+ }
102
+ this.networks.delete(network);
103
+ }
104
+ break;
105
+ }
106
+ }
107
+ if (targetNetwork !== null && targetNetwork !== network) break;
108
+ }
109
+ if (targetNetwork === null) {
110
+ targetNetwork = new Set(nodes);
111
+ this.networks.add(targetNetwork);
112
+ } else {
113
+ for (const node of nodes) {
114
+ targetNetwork.add(node);
115
+ }
116
+ }
117
+ }
118
+ isConnected(nodes) {
119
+ if (nodes.length < 2) return true;
120
+ for (const network of this.networks) {
121
+ if (nodes.every((node) => network.has(node))) {
122
+ return true;
123
+ }
124
+ }
125
+ return false;
126
+ }
127
+ };
128
+
129
+ // lib/check-each-pcb-trace-non-overlapping.ts
130
+ function lineIntersects(x1, y1, x2, y2, x3, y3, x4, y4) {
131
+ const denom = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1);
132
+ if (denom === 0) return false;
133
+ const ua = ((x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)) / denom;
134
+ const ub = ((x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3)) / denom;
135
+ return ua >= 0 && ua <= 1 && ub >= 0 && ub <= 1;
136
+ }
137
+ function tracesOverlap(trace1, trace2) {
138
+ for (let i = 0; i < trace1.route.length - 1; i++) {
139
+ for (let j = 0; j < trace2.route.length - 1; j++) {
140
+ const seg1 = trace1.route[i];
141
+ const seg2 = trace1.route[i + 1];
142
+ const seg3 = trace2.route[j];
143
+ const seg4 = trace2.route[j + 1];
144
+ if (seg1.route_type === "wire" && seg2.route_type === "wire" && seg3.route_type === "wire" && seg4.route_type === "wire" && seg1.layer === seg3.layer && lineIntersects(
145
+ seg1.x,
146
+ seg1.y,
147
+ seg2.x,
148
+ seg2.y,
149
+ seg3.x,
150
+ seg3.y,
151
+ seg4.x,
152
+ seg4.y
153
+ )) {
154
+ return true;
155
+ }
156
+ }
157
+ }
158
+ return false;
159
+ }
160
+ function traceOverlapsWithPad(trace, pad) {
161
+ for (let i = 0; i < trace.route.length - 1; i++) {
162
+ const seg1 = trace.route[i];
163
+ const seg2 = trace.route[i + 1];
164
+ if (seg1.route_type === "wire" && seg2.route_type === "wire" && seg1.layer === pad.layer && pad.shape === "rect") {
165
+ const padLeft = pad.x - pad.width / 2;
166
+ const padRight = pad.x + pad.width / 2;
167
+ const padTop = pad.y - pad.height / 2;
168
+ const padBottom = pad.y + pad.height / 2;
169
+ if (lineIntersects(
170
+ seg1.x,
171
+ seg1.y,
172
+ seg2.x,
173
+ seg2.y,
174
+ padLeft,
175
+ padTop,
176
+ padRight,
177
+ padTop
178
+ ) || lineIntersects(
179
+ seg1.x,
180
+ seg1.y,
181
+ seg2.x,
182
+ seg2.y,
183
+ padRight,
184
+ padTop,
185
+ padRight,
186
+ padBottom
187
+ ) || lineIntersects(
188
+ seg1.x,
189
+ seg1.y,
190
+ seg2.x,
191
+ seg2.y,
192
+ padRight,
193
+ padBottom,
194
+ padLeft,
195
+ padBottom
196
+ ) || lineIntersects(
197
+ seg1.x,
198
+ seg1.y,
199
+ seg2.x,
200
+ seg2.y,
201
+ padLeft,
202
+ padBottom,
203
+ padLeft,
204
+ padTop
205
+ )) {
206
+ return true;
207
+ }
208
+ }
209
+ }
210
+ return false;
211
+ }
212
+ function getPortIdsConnectedToTrace(trace) {
213
+ const connectedPorts = /* @__PURE__ */ new Set();
214
+ for (const segment of trace.route) {
215
+ if (segment.route_type === "wire") {
216
+ if (segment.start_pcb_port_id)
217
+ connectedPorts.add(segment.start_pcb_port_id);
218
+ if (segment.end_pcb_port_id) connectedPorts.add(segment.end_pcb_port_id);
219
+ }
220
+ }
221
+ return Array.from(connectedPorts);
222
+ }
223
+ function getPortIdsConnectedToTraces(...traces) {
224
+ const connectedPorts = /* @__PURE__ */ new Set();
225
+ for (const trace of traces) {
226
+ getPortIdsConnectedToTrace(trace).forEach(
227
+ (portId) => connectedPorts.add(portId)
228
+ );
229
+ }
230
+ return Array.from(connectedPorts);
231
+ }
232
+ function checkEachPcbTraceNonOverlapping(soup) {
233
+ const pcbTraces = soup.filter(
234
+ (item) => item.type === "pcb_trace"
235
+ );
236
+ const pcbSMTPads = soup.filter(
237
+ (item) => item.type === "pcb_smtpad"
238
+ );
239
+ const errors = [];
240
+ const netManager = new NetManager();
241
+ pcbTraces.forEach(
242
+ (trace) => netManager.setConnected(getPortIdsConnectedToTrace(trace))
243
+ );
244
+ for (let i = 0; i < pcbTraces.length; i++) {
245
+ for (let j = i + 1; j < pcbTraces.length; j++) {
246
+ if (netManager.isConnected(
247
+ getPortIdsConnectedToTraces(pcbTraces[i], pcbTraces[j])
248
+ )) {
249
+ continue;
250
+ }
251
+ if (tracesOverlap(pcbTraces[i], pcbTraces[j])) {
252
+ errors.push({
253
+ type: "pcb_error",
254
+ error_type: "pcb_trace_error",
255
+ message: `PCB trace ${pcbTraces[i].pcb_trace_id} overlaps with ${pcbTraces[j].pcb_trace_id}`,
256
+ pcb_trace_id: pcbTraces[i].pcb_trace_id,
257
+ source_trace_id: "",
258
+ pcb_error_id: `overlap_${pcbTraces[i].pcb_trace_id}_${pcbTraces[j].pcb_trace_id}`,
259
+ pcb_component_ids: [],
260
+ pcb_port_ids: getPortIdsConnectedToTraces(pcbTraces[i], pcbTraces[j])
261
+ });
262
+ }
263
+ }
264
+ for (const pad of pcbSMTPads) {
265
+ if (pad.pcb_port_id && netManager.isConnected(
266
+ getPortIdsConnectedToTrace(pcbTraces[i]).concat([pad.pcb_port_id])
267
+ )) {
268
+ continue;
269
+ }
270
+ if (traceOverlapsWithPad(pcbTraces[i], pad)) {
271
+ errors.push({
272
+ type: "pcb_error",
273
+ error_type: "pcb_trace_error",
274
+ message: `PCB trace ${pcbTraces[i].pcb_trace_id} overlaps with pcb_smtpad ${pad.pcb_smtpad_id}`,
275
+ pcb_trace_id: pcbTraces[i].pcb_trace_id,
276
+ source_trace_id: "",
277
+ pcb_error_id: `overlap_${pcbTraces[i].pcb_trace_id}_${pad.pcb_smtpad_id}`,
278
+ pcb_component_ids: [],
279
+ pcb_port_ids: getPortIdsConnectedToTrace(pcbTraces[i])
280
+ });
281
+ }
282
+ }
283
+ }
284
+ return errors;
285
+ }
286
+ // Annotate the CommonJS export names for ESM import in node:
287
+ 0 && (module.exports = {
288
+ checkEachPcbPortConnected,
289
+ checkEachPcbTraceNonOverlapping
290
+ });
291
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../index.ts","../lib/check-each-pcb-port-connected.ts","../lib/net-manager.ts","../lib/check-each-pcb-trace-non-overlapping.ts"],"sourcesContent":["export { checkEachPcbPortConnected } from \"./lib/check-each-pcb-port-connected\"\nexport { checkEachPcbTraceNonOverlapping } from \"./lib/check-each-pcb-trace-non-overlapping\"\n","import type {\n PCBPort,\n PCBTrace,\n SourceTrace,\n AnySoupElement,\n PCBTraceError,\n} from \"@tscircuit/soup\"\n\nfunction distance(x1: number, y1: number, x2: number, y2: number): number {\n return Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2))\n}\n\nfunction checkEachPcbPortConnected(soup: AnySoupElement[]): PCBTraceError[] {\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 // Add start_pcb_port_id and end_pcb_port_id if not present\n pcbTraces.forEach((trace) => {\n trace.route.forEach((segment, index) => {\n if (segment.route_type === \"wire\") {\n if (!segment.start_pcb_port_id && index === 0) {\n const startPort = pcbPorts.find(\n (port) =>\n distance(port.x, port.y, segment.x, segment.y) < 0.001\n )\n if (startPort) {\n segment.start_pcb_port_id = startPort.pcb_port_id\n }\n }\n if (!segment.end_pcb_port_id && index === trace.route.length - 1) {\n const endPort = pcbPorts.find(\n (port) =>\n distance(port.x, port.y, segment.x, segment.y) < 0.001\n )\n if (endPort) {\n segment.end_pcb_port_id = endPort.pcb_port_id\n }\n }\n }\n })\n })\n\n for (const port of pcbPorts) {\n const connectedTraces = pcbTraces.filter((trace) =>\n trace.route.some(\n (segment) =>\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 if (connectedTraces.length === 0) {\n const sourceTrace = sourceTraces.find((trace) =>\n trace.connected_source_port_ids.includes(port.source_port_id)\n )\n\n errors.push({\n type: \"pcb_error\",\n message: `pcb_trace_error: PCB port ${port.pcb_port_id} is not connected by a PCB trace`,\n source_trace_id: sourceTrace ? sourceTrace.source_trace_id : \"\",\n error_type: \"pcb_trace_error\",\n pcb_trace_id: \"\",\n pcb_error_id: \"\", // Add appropriate ID generation if necessary\n pcb_component_ids: [],\n pcb_port_ids: [port.pcb_port_id],\n })\n }\n }\n\n return errors\n}\n\nexport { checkEachPcbPortConnected }\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","import type {\n PCBTrace,\n PCBSMTPad,\n AnySoupElement,\n PCBTraceError,\n} from \"@tscircuit/soup\"\nimport { NetManager } from \"./net-manager\"\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(trace1: PCBTrace, trace2: PCBTrace): boolean {\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 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 ) {\n return true\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 getPortIdsConnectedToTrace(trace: PCBTrace) {\n const connectedPorts = new Set<string>()\n for (const segment of trace.route) {\n if (segment.route_type === \"wire\") {\n if (segment.start_pcb_port_id)\n connectedPorts.add(segment.start_pcb_port_id)\n if (segment.end_pcb_port_id) connectedPorts.add(segment.end_pcb_port_id)\n }\n }\n return Array.from(connectedPorts)\n}\n\nfunction getPortIdsConnectedToTraces(...traces: PCBTrace[]) {\n const connectedPorts = new Set<string>()\n for (const trace of traces) {\n getPortIdsConnectedToTrace(trace).forEach((portId) =>\n connectedPorts.add(portId)\n )\n }\n return Array.from(connectedPorts)\n}\n\nfunction checkEachPcbTraceNonOverlapping(\n soup: AnySoupElement[]\n): PCBTraceError[] {\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 const netManager = new NetManager()\n\n // TODO use source port ids instead of port ids, parse source ports for connections\n pcbTraces.forEach((trace) =>\n netManager.setConnected(getPortIdsConnectedToTrace(trace))\n )\n\n for (let i = 0; i < pcbTraces.length; i++) {\n for (let j = i + 1; j < pcbTraces.length; j++) {\n if (\n netManager.isConnected(\n getPortIdsConnectedToTraces(pcbTraces[i], pcbTraces[j])\n )\n ) {\n continue\n }\n if (tracesOverlap(pcbTraces[i], pcbTraces[j])) {\n errors.push({\n type: \"pcb_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_error_id: `overlap_${pcbTraces[i].pcb_trace_id}_${pcbTraces[j].pcb_trace_id}`,\n pcb_component_ids: [],\n pcb_port_ids: getPortIdsConnectedToTraces(pcbTraces[i], pcbTraces[j]),\n })\n }\n }\n\n for (const pad of pcbSMTPads) {\n if (\n pad.pcb_port_id &&\n netManager.isConnected(\n getPortIdsConnectedToTrace(pcbTraces[i]).concat([pad.pcb_port_id])\n )\n ) {\n continue\n }\n if (traceOverlapsWithPad(pcbTraces[i], pad)) {\n errors.push({\n type: \"pcb_error\",\n error_type: \"pcb_trace_error\",\n message: `PCB trace ${pcbTraces[i].pcb_trace_id} overlaps with pcb_smtpad ${pad.pcb_smtpad_id}`,\n pcb_trace_id: pcbTraces[i].pcb_trace_id,\n source_trace_id: \"\",\n pcb_error_id: `overlap_${pcbTraces[i].pcb_trace_id}_${pad.pcb_smtpad_id}`,\n pcb_component_ids: [],\n pcb_port_ids: getPortIdsConnectedToTrace(pcbTraces[i]),\n })\n }\n }\n }\n\n return errors\n}\n\nexport { checkEachPcbTraceNonOverlapping }\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACQA,SAAS,SAAS,IAAY,IAAY,IAAY,IAAoB;AACxE,SAAO,KAAK,KAAK,KAAK,IAAI,KAAK,IAAI,CAAC,IAAI,KAAK,IAAI,KAAK,IAAI,CAAC,CAAC;AAC9D;AAEA,SAAS,0BAA0B,MAAyC;AAC1E,QAAM,WAAsB,KAAK,OAAO,CAAC,SAAS,KAAK,SAAS,UAAU;AAC1E,QAAM,YAAwB,KAAK,OAAO,CAAC,SAAS,KAAK,SAAS,WAAW;AAC7E,QAAM,eAA8B,KAAK;AAAA,IACvC,CAAC,SAAS,KAAK,SAAS;AAAA,EAC1B;AACA,QAAM,SAA0B,CAAC;AAGjC,YAAU,QAAQ,CAAC,UAAU;AAC3B,UAAM,MAAM,QAAQ,CAAC,SAAS,UAAU;AACtC,UAAI,QAAQ,eAAe,QAAQ;AACjC,YAAI,CAAC,QAAQ,qBAAqB,UAAU,GAAG;AAC7C,gBAAM,YAAY,SAAS;AAAA,YACzB,CAAC,SACC,SAAS,KAAK,GAAG,KAAK,GAAG,QAAQ,GAAG,QAAQ,CAAC,IAAI;AAAA,UACrD;AACA,cAAI,WAAW;AACb,oBAAQ,oBAAoB,UAAU;AAAA,UACxC;AAAA,QACF;AACA,YAAI,CAAC,QAAQ,mBAAmB,UAAU,MAAM,MAAM,SAAS,GAAG;AAChE,gBAAM,UAAU,SAAS;AAAA,YACvB,CAAC,SACC,SAAS,KAAK,GAAG,KAAK,GAAG,QAAQ,GAAG,QAAQ,CAAC,IAAI;AAAA,UACrD;AACA,cAAI,SAAS;AACX,oBAAQ,kBAAkB,QAAQ;AAAA,UACpC;AAAA,QACF;AAAA,MACF;AAAA,IACF,CAAC;AAAA,EACH,CAAC;AAED,aAAW,QAAQ,UAAU;AAC3B,UAAM,kBAAkB,UAAU;AAAA,MAAO,CAAC,UACxC,MAAM,MAAM;AAAA,QACV,CAAC,YACC,QAAQ,eAAe,WACtB,QAAQ,sBAAsB,KAAK,eAClC,QAAQ,oBAAoB,KAAK;AAAA,MACvC;AAAA,IACF;AAEA,QAAI,gBAAgB,WAAW,GAAG;AAChC,YAAM,cAAc,aAAa;AAAA,QAAK,CAAC,UACrC,MAAM,0BAA0B,SAAS,KAAK,cAAc;AAAA,MAC9D;AAEA,aAAO,KAAK;AAAA,QACV,MAAM;AAAA,QACN,SAAS,6BAA6B,KAAK,WAAW;AAAA,QACtD,iBAAiB,cAAc,YAAY,kBAAkB;AAAA,QAC7D,YAAY;AAAA,QACZ,cAAc;AAAA,QACd,cAAc;AAAA;AAAA,QACd,mBAAmB,CAAC;AAAA,QACpB,cAAc,CAAC,KAAK,WAAW;AAAA,MACjC,CAAC;AAAA,IACH;AAAA,EACF;AAEA,SAAO;AACT;;;AC3EO,IAAM,aAAN,MAAiB;AAAA,EACd,WAA6B,oBAAI,IAAI;AAAA,EAE7C,aAAa,OAAuB;AAClC,QAAI,MAAM,SAAS,EAAG;AAEtB,QAAI,gBAAoC;AAGxC,eAAW,WAAW,KAAK,UAAU;AACnC,iBAAW,QAAQ,OAAO;AACxB,YAAI,QAAQ,IAAI,IAAI,GAAG;AACrB,cAAI,kBAAkB,MAAM;AAC1B,4BAAgB;AAAA,UAClB,WAAW,kBAAkB,SAAS;AAEpC,uBAAW,aAAa,SAAS;AAC/B,4BAAc,IAAI,SAAS;AAAA,YAC7B;AACA,iBAAK,SAAS,OAAO,OAAO;AAAA,UAC9B;AACA;AAAA,QACF;AAAA,MACF;AACA,UAAI,kBAAkB,QAAQ,kBAAkB,QAAS;AAAA,IAC3D;AAGA,QAAI,kBAAkB,MAAM;AAC1B,sBAAgB,IAAI,IAAI,KAAK;AAC7B,WAAK,SAAS,IAAI,aAAa;AAAA,IACjC,OAAO;AAEL,iBAAW,QAAQ,OAAO;AACxB,sBAAc,IAAI,IAAI;AAAA,MACxB;AAAA,IACF;AAAA,EACF;AAAA,EAEA,YAAY,OAA0B;AACpC,QAAI,MAAM,SAAS,EAAG,QAAO;AAE7B,eAAW,WAAW,KAAK,UAAU;AACnC,UAAI,MAAM,MAAM,CAAC,SAAS,QAAQ,IAAI,IAAI,CAAC,GAAG;AAC5C,eAAO;AAAA,MACT;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AACF;;;ACtCA,SAAS,eACP,IACA,IACA,IACA,IACA,IACA,IACA,IACA,IACS;AACT,QAAM,SAAS,KAAK,OAAO,KAAK,OAAO,KAAK,OAAO,KAAK;AACxD,MAAI,UAAU,EAAG,QAAO;AAExB,QAAM,OAAO,KAAK,OAAO,KAAK,OAAO,KAAK,OAAO,KAAK,OAAO;AAC7D,QAAM,OAAO,KAAK,OAAO,KAAK,OAAO,KAAK,OAAO,KAAK,OAAO;AAE7D,SAAO,MAAM,KAAK,MAAM,KAAK,MAAM,KAAK,MAAM;AAChD;AAEA,SAAS,cAAc,QAAkB,QAA2B;AAClE,WAAS,IAAI,GAAG,IAAI,OAAO,MAAM,SAAS,GAAG,KAAK;AAChD,aAAS,IAAI,GAAG,IAAI,OAAO,MAAM,SAAS,GAAG,KAAK;AAChD,YAAM,OAAO,OAAO,MAAM,CAAC;AAC3B,YAAM,OAAO,OAAO,MAAM,IAAI,CAAC;AAC/B,YAAM,OAAO,OAAO,MAAM,CAAC;AAC3B,YAAM,OAAO,OAAO,MAAM,IAAI,CAAC;AAE/B,UACE,KAAK,eAAe,UACpB,KAAK,eAAe,UACpB,KAAK,eAAe,UACpB,KAAK,eAAe,UACpB,KAAK,UAAU,KAAK,SACpB;AAAA,QACE,KAAK;AAAA,QACL,KAAK;AAAA,QACL,KAAK;AAAA,QACL,KAAK;AAAA,QACL,KAAK;AAAA,QACL,KAAK;AAAA,QACL,KAAK;AAAA,QACL,KAAK;AAAA,MACP,GACA;AACA,eAAO;AAAA,MACT;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,qBAAqB,OAAiB,KAAyB;AACtE,WAAS,IAAI,GAAG,IAAI,MAAM,MAAM,SAAS,GAAG,KAAK;AAC/C,UAAM,OAAO,MAAM,MAAM,CAAC;AAC1B,UAAM,OAAO,MAAM,MAAM,IAAI,CAAC;AAE9B,QACE,KAAK,eAAe,UACpB,KAAK,eAAe,UACpB,KAAK,UAAU,IAAI,SACnB,IAAI,UAAU,QACd;AACA,YAAM,UAAU,IAAI,IAAI,IAAI,QAAQ;AACpC,YAAM,WAAW,IAAI,IAAI,IAAI,QAAQ;AACrC,YAAM,SAAS,IAAI,IAAI,IAAI,SAAS;AACpC,YAAM,YAAY,IAAI,IAAI,IAAI,SAAS;AAEvC,UACE;AAAA,QACE,KAAK;AAAA,QACL,KAAK;AAAA,QACL,KAAK;AAAA,QACL,KAAK;AAAA,QACL;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF,KACA;AAAA,QACE,KAAK;AAAA,QACL,KAAK;AAAA,QACL,KAAK;AAAA,QACL,KAAK;AAAA,QACL;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF,KACA;AAAA,QACE,KAAK;AAAA,QACL,KAAK;AAAA,QACL,KAAK;AAAA,QACL,KAAK;AAAA,QACL;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF,KACA;AAAA,QACE,KAAK;AAAA,QACL,KAAK;AAAA,QACL,KAAK;AAAA,QACL,KAAK;AAAA,QACL;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF,GACA;AACA,eAAO;AAAA,MACT;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,2BAA2B,OAAiB;AACnD,QAAM,iBAAiB,oBAAI,IAAY;AACvC,aAAW,WAAW,MAAM,OAAO;AACjC,QAAI,QAAQ,eAAe,QAAQ;AACjC,UAAI,QAAQ;AACV,uBAAe,IAAI,QAAQ,iBAAiB;AAC9C,UAAI,QAAQ,gBAAiB,gBAAe,IAAI,QAAQ,eAAe;AAAA,IACzE;AAAA,EACF;AACA,SAAO,MAAM,KAAK,cAAc;AAClC;AAEA,SAAS,+BAA+B,QAAoB;AAC1D,QAAM,iBAAiB,oBAAI,IAAY;AACvC,aAAW,SAAS,QAAQ;AAC1B,+BAA2B,KAAK,EAAE;AAAA,MAAQ,CAAC,WACzC,eAAe,IAAI,MAAM;AAAA,IAC3B;AAAA,EACF;AACA,SAAO,MAAM,KAAK,cAAc;AAClC;AAEA,SAAS,gCACP,MACiB;AACjB,QAAM,YAAwB,KAAK;AAAA,IACjC,CAAC,SAA2B,KAAK,SAAS;AAAA,EAC5C;AACA,QAAM,aAA0B,KAAK;AAAA,IACnC,CAAC,SAA4B,KAAK,SAAS;AAAA,EAC7C;AACA,QAAM,SAA0B,CAAC;AACjC,QAAM,aAAa,IAAI,WAAW;AAGlC,YAAU;AAAA,IAAQ,CAAC,UACjB,WAAW,aAAa,2BAA2B,KAAK,CAAC;AAAA,EAC3D;AAEA,WAAS,IAAI,GAAG,IAAI,UAAU,QAAQ,KAAK;AACzC,aAAS,IAAI,IAAI,GAAG,IAAI,UAAU,QAAQ,KAAK;AAC7C,UACE,WAAW;AAAA,QACT,4BAA4B,UAAU,CAAC,GAAG,UAAU,CAAC,CAAC;AAAA,MACxD,GACA;AACA;AAAA,MACF;AACA,UAAI,cAAc,UAAU,CAAC,GAAG,UAAU,CAAC,CAAC,GAAG;AAC7C,eAAO,KAAK;AAAA,UACV,MAAM;AAAA,UACN,YAAY;AAAA,UACZ,SAAS,aAAa,UAAU,CAAC,EAAE,YAAY,kBAAkB,UAAU,CAAC,EAAE,YAAY;AAAA,UAC1F,cAAc,UAAU,CAAC,EAAE;AAAA,UAC3B,iBAAiB;AAAA,UACjB,cAAc,WAAW,UAAU,CAAC,EAAE,YAAY,IAAI,UAAU,CAAC,EAAE,YAAY;AAAA,UAC/E,mBAAmB,CAAC;AAAA,UACpB,cAAc,4BAA4B,UAAU,CAAC,GAAG,UAAU,CAAC,CAAC;AAAA,QACtE,CAAC;AAAA,MACH;AAAA,IACF;AAEA,eAAW,OAAO,YAAY;AAC5B,UACE,IAAI,eACJ,WAAW;AAAA,QACT,2BAA2B,UAAU,CAAC,CAAC,EAAE,OAAO,CAAC,IAAI,WAAW,CAAC;AAAA,MACnE,GACA;AACA;AAAA,MACF;AACA,UAAI,qBAAqB,UAAU,CAAC,GAAG,GAAG,GAAG;AAC3C,eAAO,KAAK;AAAA,UACV,MAAM;AAAA,UACN,YAAY;AAAA,UACZ,SAAS,aAAa,UAAU,CAAC,EAAE,YAAY,6BAA6B,IAAI,aAAa;AAAA,UAC7F,cAAc,UAAU,CAAC,EAAE;AAAA,UAC3B,iBAAiB;AAAA,UACjB,cAAc,WAAW,UAAU,CAAC,EAAE,YAAY,IAAI,IAAI,aAAa;AAAA,UACvE,mBAAmB,CAAC;AAAA,UACpB,cAAc,2BAA2B,UAAU,CAAC,CAAC;AAAA,QACvD,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;","names":[]}
@@ -0,0 +1,7 @@
1
+ import { AnySoupElement, PCBTraceError } from '@tscircuit/soup';
2
+
3
+ declare function checkEachPcbPortConnected(soup: AnySoupElement[]): PCBTraceError[];
4
+
5
+ declare function checkEachPcbTraceNonOverlapping(soup: AnySoupElement[]): PCBTraceError[];
6
+
7
+ export { checkEachPcbPortConnected, checkEachPcbTraceNonOverlapping };
package/package.json ADDED
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "@tscircuit/checks",
3
+ "module": "index.ts",
4
+ "type": "module",
5
+ "main": "./dist/index.cjs",
6
+ "version": "0.0.1",
7
+ "files": [
8
+ "dist"
9
+ ],
10
+ "scripts": {
11
+ "build": "tsup index.ts --dts --sourcemap"
12
+ },
13
+ "devDependencies": {
14
+ "@tscircuit/log-soup": "^1.0.2",
15
+ "@tscircuit/soup": "^0.0.40",
16
+ "@tscircuit/soup-util": "^0.0.13",
17
+ "@types/bun": "latest",
18
+ "tsup": "^8.2.1"
19
+ },
20
+ "peerDependencies": {
21
+ "typescript": "^5.5.3"
22
+ }
23
+ }