@tscircuit/checks 0.0.196 → 0.0.197

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 CHANGED
@@ -1,3 +1,711 @@
1
+ // lib/check-copper-pour-shorts.ts
2
+ import Flatbush from "flatbush";
3
+ import "@flatten-js/core";
4
+ import { getReadableNameForElement } from "@tscircuit/circuit-json-util";
5
+
6
+ // node_modules/@tscircuit/circuit-json-to-flattenjs/dist/index.js
7
+ import * as F from "@flatten-js/core";
8
+ import { Vector as Vector2 } from "@flatten-js/core";
9
+ import { BooleanOperations } from "@flatten-js/core";
10
+ import { Arc as Arc2 } from "@flatten-js/core";
11
+ var EPS = 1e-9;
12
+ var point = (p) => new F.Point(p.x, p.y);
13
+ function positive(value, name) {
14
+ if (!Number.isFinite(value) || value <= 0)
15
+ throw new Error(`${name} must be finite and positive`);
16
+ return value;
17
+ }
18
+ function circle(center, radius) {
19
+ positive(radius, "radius");
20
+ return new F.Polygon(new F.Circle(point(center), radius));
21
+ }
22
+ function ring(vertices) {
23
+ const clean = vertices.filter(
24
+ (p, i) => i === 0 || point(p).distanceTo(point(vertices[i - 1]))[0] > EPS
25
+ );
26
+ if (clean.length > 1 && point(clean[0]).distanceTo(point(clean.at(-1)))[0] <= EPS)
27
+ clean.pop();
28
+ if (clean.length < 2)
29
+ throw new Error("A ring needs at least two distinct vertices");
30
+ const edges = clean.map((a, i) => {
31
+ const b = clean[(i + 1) % clean.length];
32
+ if (![a.x, a.y, a.bulge ?? 0].every(Number.isFinite))
33
+ throw new Error("Non-finite ring vertex");
34
+ const bulge = a.bulge ?? 0;
35
+ if (Math.abs(bulge) <= EPS) return new F.Segment(point(a), point(b));
36
+ const dx = b.x - a.x, dy = b.y - a.y;
37
+ const length = Math.hypot(dx, dy);
38
+ const offset = length * (1 - bulge * bulge) / (4 * bulge);
39
+ const center = new F.Point(
40
+ (a.x + b.x) / 2 - dy / length * offset,
41
+ (a.y + b.y) / 2 + dx / length * offset
42
+ );
43
+ return new F.Arc(
44
+ center,
45
+ length * (1 + bulge * bulge) / (4 * Math.abs(bulge)),
46
+ Math.atan2(a.y - center.y, a.x - center.x),
47
+ Math.atan2(b.y - center.y, b.x - center.x),
48
+ bulge > 0
49
+ );
50
+ });
51
+ const polygon = new F.Polygon();
52
+ polygon.addFace(edges);
53
+ if (polygon.area() <= EPS)
54
+ throw new Error("A ring must enclose a non-zero area");
55
+ return polygon;
56
+ }
57
+ function rectangle(center, width, height, rotation = 0, radius = 0) {
58
+ positive(width, "width");
59
+ positive(height, "height");
60
+ if (!Number.isFinite(rotation) || !Number.isFinite(radius) || radius < 0)
61
+ throw new Error("Invalid rectangle rotation or corner radius");
62
+ const r = Math.min(radius, width / 2, height / 2);
63
+ const x = width / 2, y = height / 2;
64
+ let polygon;
65
+ if (r <= EPS)
66
+ polygon = ring([
67
+ { x: -x, y: -y },
68
+ { x, y: -y },
69
+ { x, y },
70
+ { x: -x, y }
71
+ ]);
72
+ else {
73
+ const corners = [
74
+ new F.Point(x - r, -y + r),
75
+ new F.Point(x - r, y - r),
76
+ new F.Point(-x + r, y - r),
77
+ new F.Point(-x + r, -y + r)
78
+ ];
79
+ const edges = [];
80
+ for (let i = 0; i < 4; i++) {
81
+ const angle = -Math.PI / 2 + i * Math.PI / 2;
82
+ const arc = new F.Arc(corners[i], r, angle, angle + Math.PI / 2, true);
83
+ const next = corners[(i + 1) % 4];
84
+ const nextStart = new F.Point(
85
+ next.x + r * Math.cos(angle + Math.PI / 2),
86
+ next.y + r * Math.sin(angle + Math.PI / 2)
87
+ );
88
+ edges.push(arc);
89
+ if (arc.end.distanceTo(nextStart)[0] > EPS)
90
+ edges.push(new F.Segment(arc.end, nextStart));
91
+ }
92
+ polygon = new F.Polygon();
93
+ polygon.addFace(edges);
94
+ }
95
+ return polygon.rotate(rotation * Math.PI / 180).translate(new F.Vector(center.x, center.y));
96
+ }
97
+ function stroke(a, b, widthA, widthB = widthA) {
98
+ const r1 = positive(widthA, "trace width") / 2, r2 = positive(widthB, "trace width") / 2;
99
+ const d = point(a).distanceTo(point(b))[0];
100
+ if (d <= Math.abs(r1 - r2) + EPS)
101
+ return circle(r1 >= r2 ? a : b, Math.max(r1, r2));
102
+ const theta = Math.atan2(b.y - a.y, b.x - a.x);
103
+ const alpha = Math.acos((r1 - r2) / d);
104
+ const low = theta - alpha, high = theta + alpha;
105
+ const p = (c, r, angle) => new F.Point(c.x + r * Math.cos(angle), c.y + r * Math.sin(angle));
106
+ const polygon = new F.Polygon();
107
+ polygon.addFace([
108
+ new F.Segment(p(a, r1, low), p(b, r2, low)),
109
+ new F.Arc(point(b), r2, low, high, true),
110
+ new F.Segment(p(b, r2, high), p(a, r1, high)),
111
+ new F.Arc(point(a), r1, high, low, true)
112
+ ]);
113
+ return polygon;
114
+ }
115
+ function withHoles(outer, holes) {
116
+ const result = outer.clone();
117
+ const orientation = [...result.faces][0].orientation();
118
+ for (const hole of holes) {
119
+ const copy = hole.clone();
120
+ if ([...copy.faces][0].orientation() === orientation) copy.reverse();
121
+ for (const face of copy.faces) result.addFace(face.shapes);
122
+ }
123
+ return result;
124
+ }
125
+ function ellipse(center, width, height, rotation = 0, tolerance = 1e-3) {
126
+ positive(width, "ellipse width");
127
+ positive(height, "ellipse height");
128
+ positive(tolerance, "curveTolerance");
129
+ if (Math.abs(width - height) < EPS) return circle(center, width / 2);
130
+ const radius = Math.max(width, height) / 2;
131
+ const count = Math.max(
132
+ 16,
133
+ Math.ceil(Math.PI / Math.acos(Math.max(-1, 1 - tolerance / radius)))
134
+ );
135
+ if (count > 1e5)
136
+ throw new Error("curveTolerance requires more than 100000 ellipse segments");
137
+ return ring(
138
+ Array.from({ length: count }, (_, i) => ({
139
+ x: width / 2 * Math.cos(i * 2 * Math.PI / count),
140
+ y: height / 2 * Math.sin(i * 2 * Math.PI / count)
141
+ }))
142
+ ).rotate(rotation * Math.PI / 180).translate(new F.Vector(center.x, center.y));
143
+ }
144
+ function getCopperLayers(json, options) {
145
+ if (options.copperLayers) {
146
+ if (!options.copperLayers.length || new Set(options.copperLayers).size !== options.copperLayers.length)
147
+ throw new Error(
148
+ "copperLayers must be a non-empty list without duplicates"
149
+ );
150
+ return [...options.copperLayers];
151
+ }
152
+ const board = json.find((e) => e.type === "pcb_board");
153
+ let count = board?.num_layers ?? 2;
154
+ for (const element of json) {
155
+ const layers = "layers" in element ? element.layers : "layer" in element ? [element.layer] : [];
156
+ if (Array.isArray(layers))
157
+ for (const layer of layers) {
158
+ const match = typeof layer === "string" && /^inner(\d+)$/.exec(layer);
159
+ if (match) count = Math.max(count, Number(match[1]) + 2);
160
+ }
161
+ if (element.type === "pcb_trace")
162
+ for (const p of element.route) {
163
+ for (const layer of p.route_type === "wire" ? [p.layer] : p.route_type === "via" ? [p.from_layer, p.to_layer] : [p.start_layer, p.end_layer]) {
164
+ const match = /^inner(\d+)$/.exec(layer);
165
+ if (match) count = Math.max(count, Number(match[1]) + 2);
166
+ }
167
+ }
168
+ }
169
+ if (!Number.isInteger(count) || count < 1 || count > 10)
170
+ throw new Error("Supported copper layer count is 1\u201310");
171
+ return count === 1 ? ["top"] : [
172
+ "top",
173
+ ...Array.from(
174
+ { length: count - 2 },
175
+ (_, i) => `inner${i + 1}`
176
+ ),
177
+ "bottom"
178
+ ];
179
+ }
180
+ function spanLayers(layers, stack) {
181
+ if (!layers.length) return [];
182
+ const indices = layers.map((layer) => stack.indexOf(layer));
183
+ if (indices.some((i) => i < 0))
184
+ throw new Error(`Layer is missing from copperLayers: ${layers.join(", ")}`);
185
+ return stack.slice(Math.min(...indices), Math.max(...indices) + 1);
186
+ }
187
+ function smtPad(pad) {
188
+ switch (pad.shape) {
189
+ case "circle":
190
+ return circle(pad, pad.radius);
191
+ case "polygon":
192
+ return ring(pad.points);
193
+ case "rect":
194
+ case "rotated_rect":
195
+ return rectangle(
196
+ pad,
197
+ pad.width,
198
+ pad.height,
199
+ "ccw_rotation" in pad ? pad.ccw_rotation : 0,
200
+ pad.rect_border_radius ?? pad.corner_radius ?? 0
201
+ );
202
+ case "pill":
203
+ case "rotated_pill":
204
+ return rectangle(
205
+ pad,
206
+ pad.width,
207
+ pad.height,
208
+ "ccw_rotation" in pad ? pad.ccw_rotation : 0,
209
+ pad.radius
210
+ );
211
+ }
212
+ }
213
+ function nonPlatedHole(hole, tolerance) {
214
+ switch (hole.hole_shape) {
215
+ case "circle":
216
+ return circle(hole, hole.hole_diameter / 2);
217
+ case "square":
218
+ return rectangle(hole, hole.hole_diameter, hole.hole_diameter);
219
+ case "rect":
220
+ return rectangle(hole, hole.hole_width, hole.hole_height);
221
+ case "oval":
222
+ return ellipse(hole, hole.hole_width, hole.hole_height, 0, tolerance);
223
+ case "pill":
224
+ case "rotated_pill":
225
+ return rectangle(
226
+ hole,
227
+ hole.hole_width,
228
+ hole.hole_height,
229
+ "ccw_rotation" in hole ? hole.ccw_rotation : 0,
230
+ Math.min(hole.hole_width, hole.hole_height) / 2
231
+ );
232
+ }
233
+ }
234
+ function platedHole(hole, componentRotation, includeDrill, tolerance) {
235
+ let outer;
236
+ let drill;
237
+ const drillCenter = {
238
+ x: hole.x + ("hole_offset_x" in hole ? hole.hole_offset_x ?? 0 : 0),
239
+ y: hole.y + ("hole_offset_y" in hole ? hole.hole_offset_y ?? 0 : 0)
240
+ };
241
+ switch (hole.shape) {
242
+ case "circle":
243
+ outer = circle(hole, hole.outer_diameter / 2);
244
+ drill = circle(hole, hole.hole_diameter / 2);
245
+ break;
246
+ case "oval":
247
+ outer = ellipse(
248
+ hole,
249
+ hole.outer_width,
250
+ hole.outer_height,
251
+ hole.ccw_rotation,
252
+ tolerance
253
+ );
254
+ drill = ellipse(
255
+ hole,
256
+ hole.hole_width,
257
+ hole.hole_height,
258
+ hole.ccw_rotation,
259
+ tolerance
260
+ );
261
+ break;
262
+ case "pill":
263
+ outer = rectangle(
264
+ hole,
265
+ hole.outer_width,
266
+ hole.outer_height,
267
+ hole.ccw_rotation,
268
+ Math.min(hole.outer_width, hole.outer_height) / 2
269
+ );
270
+ drill = rectangle(
271
+ hole,
272
+ hole.hole_width,
273
+ hole.hole_height,
274
+ hole.ccw_rotation,
275
+ Math.min(hole.hole_width, hole.hole_height) / 2
276
+ );
277
+ break;
278
+ case "circular_hole_with_rect_pad":
279
+ case "pill_hole_with_rect_pad":
280
+ case "rotated_pill_hole_with_rect_pad":
281
+ outer = rectangle(
282
+ hole,
283
+ hole.rect_pad_width,
284
+ hole.rect_pad_height,
285
+ "rect_ccw_rotation" in hole ? hole.rect_ccw_rotation : 0,
286
+ hole.rect_border_radius ?? 0
287
+ );
288
+ drill = hole.shape === "circular_hole_with_rect_pad" ? circle(drillCenter, hole.hole_diameter / 2) : rectangle(
289
+ drillCenter,
290
+ hole.hole_width,
291
+ hole.hole_height,
292
+ "hole_ccw_rotation" in hole ? hole.hole_ccw_rotation : 0,
293
+ Math.min(hole.hole_width, hole.hole_height) / 2
294
+ );
295
+ break;
296
+ case "hole_with_polygon_pad": {
297
+ const rotation = hole.ccw_rotation ?? componentRotation;
298
+ outer = ring(hole.pad_outline).rotate(rotation * Math.PI / 180).translate(new Vector2(hole.x, hole.y));
299
+ drill = hole.hole_shape === "circle" ? circle(drillCenter, hole.hole_diameter / 2) : hole.hole_shape === "oval" ? ellipse(
300
+ drillCenter,
301
+ hole.hole_width,
302
+ hole.hole_height,
303
+ rotation,
304
+ tolerance
305
+ ) : rectangle(
306
+ drillCenter,
307
+ hole.hole_width,
308
+ hole.hole_height,
309
+ rotation,
310
+ Math.min(hole.hole_width, hole.hole_height) / 2
311
+ );
312
+ break;
313
+ }
314
+ }
315
+ return includeDrill ? withHoles(outer, [drill]) : outer;
316
+ }
317
+ var samePoint = (a, b) => a.x === b.x && a.y === b.y;
318
+ var unit = (x, y) => {
319
+ const length = Math.hypot(x, y);
320
+ return length ? { x: x / length, y: y / length } : { x: 1, y: 0 };
321
+ };
322
+ function interpolatedPolygon(segments) {
323
+ const points = [
324
+ { ...segments[0].start, width: segments[0].startWidth },
325
+ ...segments.map((s) => ({ ...s.end, width: s.endWidth }))
326
+ ];
327
+ const directions = segments.map(
328
+ (s) => unit(s.end.x - s.start.x, s.end.y - s.start.y)
329
+ );
330
+ const left = [], right = [];
331
+ for (let i = 0; i < points.length; i++) {
332
+ const p = points[i];
333
+ positive(p.width, "interpolated trace width");
334
+ const before = directions[Math.max(0, i - 1)], after = directions[Math.min(i, directions.length - 1)];
335
+ const d = unit(before.x + after.x, before.y + after.y);
336
+ const nx = -d.y * p.width / 2, ny = d.x * p.width / 2;
337
+ left.push({ x: p.x + nx, y: p.y + ny });
338
+ right.push({ x: p.x - nx, y: p.y - ny });
339
+ }
340
+ return ring([...left, ...right.reverse()]);
341
+ }
342
+ function traceGeometry(trace, stack, includeDrill) {
343
+ const result = /* @__PURE__ */ new Map();
344
+ const drills = /* @__PURE__ */ new Map();
345
+ const segments = [];
346
+ const add = (layer, shape) => result.set(layer, [...result.get(layer) ?? [], shape]);
347
+ for (const p of trace.route) {
348
+ if (p.route_type === "via") {
349
+ const legacy = p;
350
+ const outerDiameter = p.outer_diameter ?? legacy.via_diameter;
351
+ if (outerDiameter === void 0) continue;
352
+ const holeDiameter = p.hole_diameter ?? legacy.via_hole_diameter;
353
+ const shape = circle(p, outerDiameter / 2);
354
+ const drill = includeDrill && holeDiameter ? circle(p, holeDiameter / 2) : void 0;
355
+ for (const layer of spanLayers([p.from_layer, p.to_layer], stack)) {
356
+ add(layer, drill ? withHoles(shape, [drill]) : shape);
357
+ if (drill) drills.set(layer, [...drills.get(layer) ?? [], drill]);
358
+ }
359
+ }
360
+ if (p.route_type === "through_pad") {
361
+ for (const layer of /* @__PURE__ */ new Set([p.start_layer, p.end_layer]))
362
+ add(layer, stroke(p.start, p.end, p.width));
363
+ }
364
+ }
365
+ for (let i = 0; i < trace.route.length - 1; i++) {
366
+ const a = trace.route[i], b = trace.route[i + 1];
367
+ const start = a.route_type === "through_pad" ? a.end : a;
368
+ const end = b.route_type === "through_pad" ? b.start : b;
369
+ if (samePoint(start, end)) continue;
370
+ const startLayers = a.route_type === "wire" ? [a.layer] : a.route_type === "via" ? spanLayers([a.from_layer, a.to_layer], stack) : [a.end_layer];
371
+ const endLayers = b.route_type === "wire" ? [b.layer] : b.route_type === "via" ? spanLayers([b.from_layer, b.to_layer], stack) : [b.start_layer];
372
+ const sharedLayers = startLayers.filter(
373
+ (layer) => endLayers.includes(layer)
374
+ );
375
+ if (sharedLayers.length !== 1)
376
+ throw new Error(
377
+ `Cannot determine one wire layer between route points ${i} and ${i + 1}`
378
+ );
379
+ const startLayer = sharedLayers[0];
380
+ const width = "width" in a ? a.width : "width" in b ? b.width : void 0;
381
+ if (width === void 0 || samePoint(start, end)) continue;
382
+ segments.push({
383
+ start,
384
+ end,
385
+ layer: startLayer,
386
+ startWidth: width,
387
+ endWidth: "width" in b ? b.width : width
388
+ });
389
+ }
390
+ if (trace.route_thickness_mode === "interpolated") {
391
+ let group = [];
392
+ const flush = () => {
393
+ if (group.length) {
394
+ add(group[0].layer, interpolatedPolygon(group));
395
+ group = [];
396
+ }
397
+ };
398
+ for (const segment of segments) {
399
+ const last = group.at(-1);
400
+ if (last && (last.layer !== segment.layer || !samePoint(last.end, segment.start) || last.endWidth !== segment.startWidth))
401
+ flush();
402
+ group.push(segment);
403
+ }
404
+ flush();
405
+ } else
406
+ for (const s of segments) add(s.layer, stroke(s.start, s.end, s.startWidth));
407
+ for (const [layer, holes] of drills) {
408
+ result.set(
409
+ layer,
410
+ result.get(layer).map(
411
+ (shape) => holes.reduce(
412
+ (shape2, hole) => BooleanOperations.subtract(shape2, hole),
413
+ shape
414
+ )
415
+ ).filter((shape) => shape.faces.size > 0)
416
+ );
417
+ }
418
+ return result;
419
+ }
420
+ var supportedElementTypes = [
421
+ "pcb_board",
422
+ "pcb_smtpad",
423
+ "pcb_plated_hole",
424
+ "pcb_hole",
425
+ "pcb_via",
426
+ "pcb_trace",
427
+ "pcb_copper_pour",
428
+ "pcb_cutout",
429
+ "pcb_keepout",
430
+ "pcb_courtyard_rect",
431
+ "pcb_courtyard_circle",
432
+ "pcb_courtyard_pill",
433
+ "pcb_courtyard_polygon",
434
+ "pcb_courtyard_outline"
435
+ ];
436
+ function cutoutGeometry(cutout) {
437
+ switch (cutout.shape) {
438
+ case "circle":
439
+ return [circle(cutout.center, cutout.radius)];
440
+ case "rect":
441
+ return [
442
+ rectangle(
443
+ cutout.center,
444
+ cutout.width,
445
+ cutout.height,
446
+ cutout.rotation,
447
+ cutout.corner_radius
448
+ )
449
+ ];
450
+ case "polygon":
451
+ return [ring(cutout.points)];
452
+ case "path": {
453
+ if (cutout.slot_length !== void 0 || cutout.space_between_slots !== void 0 || cutout.slot_corner_radius !== void 0) {
454
+ throw new Error(
455
+ "Patterned/custom-corner path cutouts are not supported; use explicit rect/polygon cutouts"
456
+ );
457
+ }
458
+ return cutout.route.slice(1).map((p, i) => stroke(cutout.route[i], p, cutout.slot_width));
459
+ }
460
+ }
461
+ }
462
+ function convertCircuitJsonToFlattenJs(json, options = {}) {
463
+ if (options.layer && options.layers)
464
+ throw new Error("Specify layer or layers, not both");
465
+ const selectedLayers = options.layer ? [options.layer] : options.layers;
466
+ const stack = getCopperLayers(json, options);
467
+ const result = {
468
+ elements: [],
469
+ warnings: [],
470
+ bounds: void 0,
471
+ copperLayers: stack
472
+ };
473
+ const rotations = new Map(
474
+ json.filter((e) => e.type === "pcb_component").map((e) => [e.pcb_component_id, e.rotation])
475
+ );
476
+ const tolerance = options.curveTolerance ?? 1e-3;
477
+ if (!Number.isFinite(tolerance) || tolerance <= 0)
478
+ throw new Error("curveTolerance must be finite and positive");
479
+ const includeDrill = options.includeDrillHoles !== false;
480
+ for (const element of json) {
481
+ if (!supportedElementTypes.includes(element.type))
482
+ continue;
483
+ const elementId = element[`${element.type}_id`];
484
+ if (options.elementTypes && !options.elementTypes.includes(element.type))
485
+ continue;
486
+ if (options.elementIds && !options.elementIds.includes(elementId)) continue;
487
+ let role = "copper";
488
+ let layers = [null];
489
+ let shapes = [];
490
+ let byLayer;
491
+ try {
492
+ switch (element.type) {
493
+ case "pcb_smtpad":
494
+ layers = [element.layer];
495
+ shapes = [smtPad(element)];
496
+ break;
497
+ case "pcb_plated_hole":
498
+ layers = spanLayers(element.layers, stack);
499
+ shapes = [
500
+ platedHole(
501
+ element,
502
+ rotations.get(element.pcb_component_id ?? "") ?? 0,
503
+ includeDrill,
504
+ tolerance
505
+ )
506
+ ];
507
+ break;
508
+ case "pcb_via": {
509
+ layers = spanLayers(element.layers, stack);
510
+ const outer = circle(element, element.outer_diameter / 2);
511
+ shapes = [
512
+ includeDrill ? withHoles(outer, [circle(element, element.hole_diameter / 2)]) : outer
513
+ ];
514
+ break;
515
+ }
516
+ case "pcb_trace":
517
+ byLayer = traceGeometry(element, stack, includeDrill);
518
+ layers = [...byLayer.keys()];
519
+ break;
520
+ case "pcb_copper_pour":
521
+ layers = [element.layer];
522
+ shapes = [
523
+ element.shape === "rect" ? rectangle(
524
+ element.center,
525
+ element.width,
526
+ element.height,
527
+ element.rotation
528
+ ) : element.shape === "polygon" ? ring(element.points) : withHoles(
529
+ ring(element.brep_shape.outer_ring.vertices),
530
+ element.brep_shape.inner_rings.map(
531
+ (h) => ring(h.vertices)
532
+ )
533
+ )
534
+ ];
535
+ break;
536
+ case "pcb_board":
537
+ role = "board";
538
+ shapes = [
539
+ element.outline?.length ? ring(element.outline) : rectangle(element.center, element.width, element.height)
540
+ ];
541
+ break;
542
+ case "pcb_hole":
543
+ role = "drill";
544
+ shapes = [nonPlatedHole(element, tolerance)];
545
+ break;
546
+ case "pcb_cutout":
547
+ role = "cutout";
548
+ shapes = cutoutGeometry(element);
549
+ break;
550
+ case "pcb_keepout":
551
+ role = "keepout";
552
+ layers = element.layers;
553
+ shapes = [
554
+ element.shape === "circle" ? circle(element.center, element.radius) : element.shape === "rect" ? rectangle(element.center, element.width, element.height) : ring(element.outline)
555
+ ];
556
+ break;
557
+ case "pcb_courtyard_rect":
558
+ role = "courtyard";
559
+ layers = [element.layer];
560
+ shapes = [
561
+ rectangle(
562
+ element.center,
563
+ element.width,
564
+ element.height,
565
+ element.ccw_rotation
566
+ )
567
+ ];
568
+ break;
569
+ case "pcb_courtyard_circle":
570
+ role = "courtyard";
571
+ layers = [element.layer];
572
+ shapes = [circle(element.center, element.radius)];
573
+ break;
574
+ case "pcb_courtyard_pill":
575
+ role = "courtyard";
576
+ layers = [element.layer];
577
+ shapes = [
578
+ rectangle(
579
+ element.center,
580
+ element.width,
581
+ element.height,
582
+ 0,
583
+ element.radius
584
+ )
585
+ ];
586
+ break;
587
+ case "pcb_courtyard_polygon":
588
+ role = "courtyard";
589
+ layers = [element.layer];
590
+ shapes = [ring(element.points)];
591
+ break;
592
+ case "pcb_courtyard_outline":
593
+ role = "courtyard";
594
+ layers = [element.layer];
595
+ shapes = [ring(element.outline)];
596
+ break;
597
+ }
598
+ if (options.roles && !options.roles.includes(role)) continue;
599
+ const convertedElements = [];
600
+ for (const layer of new Set(layers)) {
601
+ if (layer === null ? options.includeLayerless === false : selectedLayers && !selectedLayers.includes(layer))
602
+ continue;
603
+ const layerShapes = byLayer?.get(layer) ?? shapes;
604
+ if (!layerShapes.length) continue;
605
+ const bounds = layerShapes.reduce(
606
+ (box, shape) => box.merge(shape.box),
607
+ layerShapes[0].box
608
+ );
609
+ if (![bounds.xmin, bounds.ymin, bounds.xmax, bounds.ymax].every(
610
+ Number.isFinite
611
+ ))
612
+ throw new Error("Non-finite geometry bounds");
613
+ const converted = {
614
+ elementId,
615
+ elementType: element.type,
616
+ sourceElement: element,
617
+ role,
618
+ layer,
619
+ shapes: layerShapes,
620
+ bounds
621
+ };
622
+ convertedElements.push(converted);
623
+ }
624
+ for (const converted of convertedElements) {
625
+ result.elements.push(converted);
626
+ result.bounds = result.bounds ? result.bounds.merge(converted.bounds) : converted.bounds;
627
+ }
628
+ } catch (error) {
629
+ const message = error instanceof Error ? error.message : String(error);
630
+ if (options.strict) throw new Error(`${elementId}: ${message}`);
631
+ result.warnings.push({ elementId, elementType: element.type, message });
632
+ }
633
+ }
634
+ return result;
635
+ }
636
+
637
+ // lib/check-copper-pour-shorts.ts
638
+ import { getFullConnectivityMapFromCircuitJson } from "circuit-json-to-connectivity-map";
639
+ function touchesPour(pour, geometry) {
640
+ if (!pour.box.intersect(geometry.box)) return false;
641
+ return pour.intersect(geometry).length > 0 || [...geometry.faces].some((face) => pour.contains(face.first.start)) || [...pour.faces].some((face) => geometry.contains(face.first.start));
642
+ }
643
+ function checkCopperPourShorts(circuitJson) {
644
+ if (!circuitJson.some((e) => e.type === "pcb_copper_pour")) return [];
645
+ const connMap = getFullConnectivityMapFromCircuitJson(circuitJson);
646
+ const { elements: copper } = convertCircuitJsonToFlattenJs(circuitJson, {
647
+ elementTypes: [
648
+ "pcb_smtpad",
649
+ "pcb_plated_hole",
650
+ "pcb_via",
651
+ "pcb_trace",
652
+ "pcb_copper_pour"
653
+ ],
654
+ strict: true
655
+ });
656
+ const layers = /* @__PURE__ */ new Map();
657
+ for (const element of copper) {
658
+ const entries = layers.get(element.layer) ?? [];
659
+ for (const shape of element.shapes) entries.push({ element, shape });
660
+ layers.set(element.layer, entries);
661
+ }
662
+ const indexes = new Map(
663
+ [...layers].map(([layer, entries]) => {
664
+ const index = new Flatbush(entries.length);
665
+ for (const { shape } of entries) {
666
+ const b = shape.box;
667
+ index.add(b.xmin, b.ymin, b.xmax, b.ymax);
668
+ }
669
+ index.finish();
670
+ return [layer, { index, entries }];
671
+ })
672
+ );
673
+ const netNames = new Map(
674
+ circuitJson.filter((e) => e.type === "source_net").map((e) => [e.source_net_id, e.name])
675
+ );
676
+ const errors = /* @__PURE__ */ new Map();
677
+ for (const pour of copper) {
678
+ const element = pour.sourceElement;
679
+ if (element.type !== "pcb_copper_pour") continue;
680
+ const netId = element.source_net_id;
681
+ const { index, entries } = indexes.get(pour.layer);
682
+ const candidates = /* @__PURE__ */ new Set();
683
+ for (const shape of pour.shapes) {
684
+ const b = shape.box;
685
+ for (const i of index.search(b.xmin, b.ymin, b.xmax, b.ymax))
686
+ candidates.add(i);
687
+ }
688
+ for (const i of [...candidates].sort((a, b) => a - b)) {
689
+ const { element: other, shape: otherShape } = entries[i];
690
+ if (pour.elementId === other.elementId) continue;
691
+ const otherNetId = other.sourceElement.type === "pcb_copper_pour" ? other.sourceElement.source_net_id : other.elementId;
692
+ if (netId && otherNetId && (netId === otherNetId || connMap.areIdsConnected(netId, otherNetId)))
693
+ continue;
694
+ const id = `copper_pour_short_${[pour.elementId, other.elementId].sort().join("_")}`;
695
+ if (errors.has(id) || !pour.shapes.some((shape) => touchesPour(shape, otherShape)))
696
+ continue;
697
+ errors.set(id, {
698
+ type: "pcb_placement_error",
699
+ pcb_placement_error_id: id,
700
+ error_type: "pcb_placement_error",
701
+ message: `Copper pour ${pour.elementId} (${netNames.get(netId) ?? netId ?? "unassigned net"}) shorts to ${getReadableNameForElement(circuitJson, other.elementId)} (${other.elementId}) on ${pour.layer} (accidental copper contact)`,
702
+ subcircuit_id: element.subcircuit_id
703
+ });
704
+ }
705
+ }
706
+ return [...errors.values()];
707
+ }
708
+
1
709
  // lib/check-traces-are-contiguous/is-point-in-pad.ts
2
710
  import { pointToSegmentDistance } from "@tscircuit/math-utils";
3
711
 
@@ -9,11 +717,11 @@ import {
9
717
  pointToSegmentClosestPoint,
10
718
  segmentToSegmentMinDistance
11
719
  } from "@tscircuit/math-utils";
12
- var rotatePoint = (point, angleDegrees) => {
720
+ var rotatePoint = (point2, angleDegrees) => {
13
721
  const angle = angleDegrees * Math.PI / 180;
14
722
  return {
15
- x: point.x * Math.cos(angle) - point.y * Math.sin(angle),
16
- y: point.x * Math.sin(angle) + point.y * Math.cos(angle)
723
+ x: point2.x * Math.cos(angle) - point2.y * Math.sin(angle),
724
+ y: point2.x * Math.sin(angle) + point2.y * Math.cos(angle)
17
725
  };
18
726
  };
19
727
  var getRotatedRectPoints = ({
@@ -30,8 +738,8 @@ var getRotatedRectPoints = ({
30
738
  { x: halfWidth, y: -halfHeight },
31
739
  { x: halfWidth, y: halfHeight },
32
740
  { x: -halfWidth, y: halfHeight }
33
- ].map((point) => {
34
- const rotated = rotatePoint(point, ccwRotation);
741
+ ].map((point2) => {
742
+ const rotated = rotatePoint(point2, ccwRotation);
35
743
  return { x: x + rotated.x, y: y + rotated.y };
36
744
  });
37
745
  };
@@ -76,7 +784,7 @@ var getPolygonPointsForPad = (pad) => {
76
784
  );
77
785
  };
78
786
  var getPolygonEdges = (points) => points.map(
79
- (point, index) => [point, points[(index + 1) % points.length]]
787
+ (point2, index) => [point2, points[(index + 1) % points.length]]
80
788
  );
81
789
  var getClosestPointsBetweenSegments = (a1, a2, b1, b2) => {
82
790
  const intersection = getSegmentIntersection(a1, a2, b1, b2);
@@ -127,7 +835,7 @@ var getSegmentToPolygonClearanceFromPoints = (start, end, polygon) => {
127
835
  }
128
836
  const intersections = getPolygonEdges(polygon).map(
129
837
  ([edgeStart, edgeEnd]) => getSegmentIntersection(start, end, edgeStart, edgeEnd)
130
- ).filter((point) => point !== null);
838
+ ).filter((point2) => point2 !== null);
131
839
  if (intersections.length > 0) {
132
840
  const dx = end.x - start.x;
133
841
  const dy = end.y - start.y;
@@ -179,17 +887,17 @@ var getSegmentToPolygonClearanceFromPoints = (start, end, polygon) => {
179
887
  };
180
888
  };
181
889
  var getSegmentToPillClearance = (segment, pad) => {
182
- const pill2 = getPillCenterLineForPad(pad);
890
+ const pill = getPillCenterLineForPad(pad);
183
891
  const closest = getClosestPointsBetweenSegments(
184
892
  { x: segment.x1, y: segment.y1 },
185
893
  { x: segment.x2, y: segment.y2 },
186
- pill2.start,
187
- pill2.end
894
+ pill.start,
895
+ pill.end
188
896
  );
189
897
  return {
190
898
  distance: closest.distance,
191
899
  center: closest.center,
192
- radius: pill2.radius,
900
+ radius: pill.radius,
193
901
  tracePoint: closest.pointOnA,
194
902
  obstaclePoint: closest.pointOnB
195
903
  };
@@ -201,73 +909,73 @@ function getDistanceBetweenPoints(pointA, pointB) {
201
909
  }
202
910
  var POINT_ON_SEGMENT_TOLERANCE_MM = 1e-9;
203
911
  var POINT_IN_PAD_TOLERANCE_MM = 1e-9;
204
- function isPointOnSegment(point, segment) {
205
- const crossProduct = (point.y - segment.start.y) * (segment.end.x - segment.start.x) - (point.x - segment.start.x) * (segment.end.y - segment.start.y);
912
+ function isPointOnSegment(point2, segment) {
913
+ const crossProduct = (point2.y - segment.start.y) * (segment.end.x - segment.start.x) - (point2.x - segment.start.x) * (segment.end.y - segment.start.y);
206
914
  if (Math.abs(crossProduct) > POINT_ON_SEGMENT_TOLERANCE_MM) return false;
207
- const dotProduct = (point.x - segment.start.x) * (segment.end.x - segment.start.x) + (point.y - segment.start.y) * (segment.end.y - segment.start.y);
915
+ const dotProduct = (point2.x - segment.start.x) * (segment.end.x - segment.start.x) + (point2.y - segment.start.y) * (segment.end.y - segment.start.y);
208
916
  if (dotProduct < -POINT_ON_SEGMENT_TOLERANCE_MM) return false;
209
917
  const squaredLength = (segment.end.x - segment.start.x) ** 2 + (segment.end.y - segment.start.y) ** 2;
210
918
  return dotProduct <= squaredLength + POINT_ON_SEGMENT_TOLERANCE_MM;
211
919
  }
212
- function isPointInPolygon(point, polygon) {
920
+ function isPointInPolygon(point2, polygon) {
213
921
  let inside = false;
214
922
  for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
215
923
  const pi = polygon[i];
216
924
  const pj = polygon[j];
217
- if (isPointOnSegment(point, { start: pi, end: pj })) return true;
218
- const intersects = pi.y > point.y !== pj.y > point.y && point.x < (pj.x - pi.x) * (point.y - pi.y) / (pj.y - pi.y) + pi.x;
925
+ if (isPointOnSegment(point2, { start: pi, end: pj })) return true;
926
+ const intersects = pi.y > point2.y !== pj.y > point2.y && point2.x < (pj.x - pi.x) * (point2.y - pi.y) / (pj.y - pi.y) + pi.x;
219
927
  if (intersects) inside = !inside;
220
928
  }
221
929
  return inside;
222
930
  }
223
- function isPointInPad(point, pad) {
931
+ function isPointInPad(point2, pad) {
224
932
  if (pad.type === "pcb_smtpad") {
225
933
  if (pad.shape === "circle") {
226
- return getDistanceBetweenPoints(point, pad) <= pad.radius + POINT_IN_PAD_TOLERANCE_MM;
934
+ return getDistanceBetweenPoints(point2, pad) <= pad.radius + POINT_IN_PAD_TOLERANCE_MM;
227
935
  }
228
936
  if (pad.shape === "rect") {
229
937
  const halfWidth = pad.width / 2;
230
938
  const halfHeight = pad.height / 2;
231
- return Math.abs(point.x - pad.x) <= halfWidth + POINT_IN_PAD_TOLERANCE_MM && Math.abs(point.y - pad.y) <= halfHeight + POINT_IN_PAD_TOLERANCE_MM;
939
+ return Math.abs(point2.x - pad.x) <= halfWidth + POINT_IN_PAD_TOLERANCE_MM && Math.abs(point2.y - pad.y) <= halfHeight + POINT_IN_PAD_TOLERANCE_MM;
232
940
  }
233
941
  if (pad.shape === "rotated_rect") {
234
- return isPointInPolygon(point, getPolygonPointsForPad(pad));
942
+ return isPointInPolygon(point2, getPolygonPointsForPad(pad));
235
943
  }
236
944
  if (pad.shape === "pill" || pad.shape === "rotated_pill") {
237
945
  if (pad.shape === "rotated_pill") {
238
- const pill2 = getPillCenterLineForPad(pad);
239
- return pointToSegmentDistance(point, pill2.start, pill2.end) <= pill2.radius + POINT_IN_PAD_TOLERANCE_MM;
946
+ const pill = getPillCenterLineForPad(pad);
947
+ return pointToSegmentDistance(point2, pill.start, pill.end) <= pill.radius + POINT_IN_PAD_TOLERANCE_MM;
240
948
  }
241
949
  const halfWidth = pad.width / 2;
242
950
  const halfHeight = pad.height / 2;
243
951
  const radius = pad.radius;
244
- if (Math.abs(point.x - pad.x) <= halfWidth - radius + POINT_IN_PAD_TOLERANCE_MM && Math.abs(point.y - pad.y) <= halfHeight + POINT_IN_PAD_TOLERANCE_MM) {
952
+ if (Math.abs(point2.x - pad.x) <= halfWidth - radius + POINT_IN_PAD_TOLERANCE_MM && Math.abs(point2.y - pad.y) <= halfHeight + POINT_IN_PAD_TOLERANCE_MM) {
245
953
  return true;
246
954
  }
247
955
  const cornerX = Math.max(
248
- Math.abs(point.x - pad.x) - (halfWidth - radius),
956
+ Math.abs(point2.x - pad.x) - (halfWidth - radius),
249
957
  0
250
958
  );
251
959
  const cornerY = Math.max(
252
- Math.abs(point.y - pad.y) - (halfHeight - radius),
960
+ Math.abs(point2.y - pad.y) - (halfHeight - radius),
253
961
  0
254
962
  );
255
963
  const radiusWithTolerance = radius + POINT_IN_PAD_TOLERANCE_MM;
256
964
  return cornerX * cornerX + cornerY * cornerY <= radiusWithTolerance * radiusWithTolerance;
257
965
  }
258
966
  if (pad.shape === "polygon") {
259
- return isPointInPolygon(point, pad.points);
967
+ return isPointInPolygon(point2, pad.points);
260
968
  }
261
969
  }
262
970
  if (pad.type === "pcb_plated_hole") {
263
971
  if (pad.shape === "circle") {
264
- return getDistanceBetweenPoints(point, pad) <= pad.outer_diameter / 2 + POINT_IN_PAD_TOLERANCE_MM;
972
+ return getDistanceBetweenPoints(point2, pad) <= pad.outer_diameter / 2 + POINT_IN_PAD_TOLERANCE_MM;
265
973
  }
266
974
  if ("rect_pad_width" in pad && "rect_pad_height" in pad) {
267
- return isPointInPolygon(point, getPolygonPointsForPad(pad));
975
+ return isPointInPolygon(point2, getPolygonPointsForPad(pad));
268
976
  }
269
977
  if (pad.shape === "oval" || pad.shape === "pill") {
270
- return Math.abs(point.x - pad.x) <= pad.outer_width / 2 + POINT_IN_PAD_TOLERANCE_MM && Math.abs(point.y - pad.y) <= pad.outer_height / 2 + POINT_IN_PAD_TOLERANCE_MM;
978
+ return Math.abs(point2.x - pad.x) <= pad.outer_width / 2 + POINT_IN_PAD_TOLERANCE_MM && Math.abs(point2.y - pad.y) <= pad.outer_height / 2 + POINT_IN_PAD_TOLERANCE_MM;
271
979
  }
272
980
  }
273
981
  return false;
@@ -283,23 +991,23 @@ var addStartAndEndPortIdsIfMissing = (soup) => {
283
991
  (item) => item.type === "pcb_smtpad"
284
992
  );
285
993
  const pcbTraces = soup.filter((item) => item.type === "pcb_trace");
286
- function findPortIdOverlappingPoint(point, options = {}) {
994
+ function findPortIdOverlappingPoint(point2, options = {}) {
287
995
  const traceWidth = options.traceWidth || 0;
288
996
  const directPort = pcbPorts.find(
289
- (port) => distance(port.x, port.y, point.x, point.y) < 0.01
997
+ (port) => distance(port.x, port.y, point2.x, point2.y) < 0.01
290
998
  );
291
999
  if (directPort) return directPort.pcb_port_id;
292
1000
  if (options.isFirstOrLastPoint) {
293
- const smtPad = pcbSmtPads.find((pad) => {
1001
+ const smtPad2 = pcbSmtPads.find((pad) => {
294
1002
  if (pad.shape === "rect") {
295
- return Math.abs(point.x - pad.x) < pad.width / 2 + traceWidth / 2 && Math.abs(point.y - pad.y) < pad.height / 2 + traceWidth / 2;
1003
+ return Math.abs(point2.x - pad.x) < pad.width / 2 + traceWidth / 2 && Math.abs(point2.y - pad.y) < pad.height / 2 + traceWidth / 2;
296
1004
  } else if (pad.shape === "circle") {
297
- return distance(point.x, point.y, pad.x, pad.y) < pad.radius;
1005
+ return distance(point2.x, point2.y, pad.x, pad.y) < pad.radius;
298
1006
  } else if (pad.shape === "pill" || pad.shape === "rotated_pill") {
299
- return isPointInPad(point, pad);
1007
+ return isPointInPad(point2, pad);
300
1008
  }
301
1009
  });
302
- if (smtPad) return smtPad.pcb_port_id ?? null;
1010
+ if (smtPad2) return smtPad2.pcb_port_id ?? null;
303
1011
  }
304
1012
  return null;
305
1013
  }
@@ -333,13 +1041,13 @@ var addStartAndEndPortIdsIfMissing = (soup) => {
333
1041
 
334
1042
  // lib/check-each-pcb-port-connected-to-pcb-trace.ts
335
1043
  import {
336
- getFullConnectivityMapFromCircuitJson,
1044
+ getFullConnectivityMapFromCircuitJson as getFullConnectivityMapFromCircuitJson2,
337
1045
  PcbConnectivityMap
338
1046
  } from "circuit-json-to-connectivity-map";
339
1047
 
340
1048
  // lib/util/get-readable-names.ts
341
1049
  import {
342
- getReadableNameForElement,
1050
+ getReadableNameForElement as getReadableNameForElement2,
343
1051
  getReadableNameForPcbPort,
344
1052
  getBoundsOfPcbElements
345
1053
  } from "@tscircuit/circuit-json-util";
@@ -358,7 +1066,7 @@ var firstReadableName = (candidates, id) => {
358
1066
  return "";
359
1067
  };
360
1068
  var getReadableNameForComponent = (circuitJson, pcbComponentId) => sanitizeReadableName(
361
- getReadableNameForElement(circuitJson, pcbComponentId),
1069
+ getReadableNameForElement2(circuitJson, pcbComponentId),
362
1070
  pcbComponentId,
363
1071
  "component"
364
1072
  );
@@ -395,7 +1103,7 @@ var getReadableNameForPort = (circuitJson, pcbPortId) => {
395
1103
  }
396
1104
  }
397
1105
  return sanitizeReadableName(
398
- getReadableNameForPcbPort(circuitJson, pcbPortId) ?? getReadableNameForElement(circuitJson, pcbPortId),
1106
+ getReadableNameForPcbPort(circuitJson, pcbPortId) ?? getReadableNameForElement2(circuitJson, pcbPortId),
399
1107
  pcbPortId,
400
1108
  "port"
401
1109
  );
@@ -448,7 +1156,7 @@ var getReadableNameForSourceTrace = (circuitJson, sourceTrace) => {
448
1156
  return `trace ${sourceTrace.source_trace_id}`;
449
1157
  };
450
1158
  var getReadableNameForElementId = (circuitJson, elementId) => sanitizeReadableName(
451
- getReadableNameForElement(circuitJson, elementId),
1159
+ getReadableNameForElement2(circuitJson, elementId),
452
1160
  elementId,
453
1161
  "element"
454
1162
  );
@@ -640,7 +1348,7 @@ function checkEachPcbPortConnectedToPcbTraces(circuitJson) {
640
1348
  (item) => item.type === "source_net"
641
1349
  );
642
1350
  const errors = [];
643
- const connectivityMap = getFullConnectivityMapFromCircuitJson(circuitJson);
1351
+ const connectivityMap = getFullConnectivityMapFromCircuitJson2(circuitJson);
644
1352
  const pcbConnectivityMap = new PcbConnectivityMap(circuitJson);
645
1353
  let pourConnectivity;
646
1354
  const getPourConnectivity = () => pourConnectivity ??= getCopperPourConnectivity(
@@ -727,7 +1435,7 @@ function checkEachPcbPortConnectedToPcbTraces(circuitJson) {
727
1435
  }
728
1436
 
729
1437
  // lib/check-each-pcb-trace-non-overlapping/check-each-pcb-trace-non-overlapping.ts
730
- import { cju as cju2, getReadableNameForElement as getReadableNameForElement2 } from "@tscircuit/circuit-json-util";
1438
+ import { cju as cju2, getReadableNameForElement as getReadableNameForElement3 } from "@tscircuit/circuit-json-util";
731
1439
  import { getPrimaryId as getPrimaryId2 } from "@tscircuit/circuit-json-util";
732
1440
  import {
733
1441
  segmentToBoundsMinDistance,
@@ -735,7 +1443,7 @@ import {
735
1443
  } from "@tscircuit/math-utils";
736
1444
  import { segmentToSegmentMinDistance as segmentToSegmentMinDistance3 } from "@tscircuit/math-utils";
737
1445
  import {
738
- getFullConnectivityMapFromCircuitJson as getFullConnectivityMapFromCircuitJson2
1446
+ getFullConnectivityMapFromCircuitJson as getFullConnectivityMapFromCircuitJson3
739
1447
  } from "circuit-json-to-connectivity-map";
740
1448
 
741
1449
  // lib/check-pad-clearance/common.ts
@@ -782,10 +1490,10 @@ var getPadBounds = (pad) => {
782
1490
  if (pad.type === "pcb_keepout") {
783
1491
  if (pad.shape === "outline") {
784
1492
  return {
785
- minX: Math.min(...pad.outline.map((point) => point.x)),
786
- minY: Math.min(...pad.outline.map((point) => point.y)),
787
- maxX: Math.max(...pad.outline.map((point) => point.x)),
788
- maxY: Math.max(...pad.outline.map((point) => point.y))
1493
+ minX: Math.min(...pad.outline.map((point2) => point2.x)),
1494
+ minY: Math.min(...pad.outline.map((point2) => point2.y)),
1495
+ maxX: Math.max(...pad.outline.map((point2) => point2.x)),
1496
+ maxY: Math.max(...pad.outline.map((point2) => point2.y))
789
1497
  };
790
1498
  }
791
1499
  if (pad.shape === "circle") {
@@ -897,28 +1605,28 @@ var getPadToPadGap = (padA, padB) => {
897
1605
  ) - pillA.radius - pillB.radius;
898
1606
  }
899
1607
  if (isPillPad(padA) && isCircularPad(padB)) {
900
- const pill2 = getPillCenterLineForPad(padA);
901
- return segmentToCircleMinDistance(pill2.start, pill2.end, getCircleShape(padB)) - pill2.radius;
1608
+ const pill = getPillCenterLineForPad(padA);
1609
+ return segmentToCircleMinDistance(pill.start, pill.end, getCircleShape(padB)) - pill.radius;
902
1610
  }
903
1611
  if (isCircularPad(padA) && isPillPad(padB)) {
904
- const pill2 = getPillCenterLineForPad(padB);
905
- return segmentToCircleMinDistance(pill2.start, pill2.end, getCircleShape(padA)) - pill2.radius;
1612
+ const pill = getPillCenterLineForPad(padB);
1613
+ return segmentToCircleMinDistance(pill.start, pill.end, getCircleShape(padA)) - pill.radius;
906
1614
  }
907
1615
  if (isPillPad(padA)) {
908
- const pill2 = getPillCenterLineForPad(padA);
1616
+ const pill = getPillCenterLineForPad(padA);
909
1617
  return getSegmentToPolygonClearanceFromPoints(
910
- pill2.start,
911
- pill2.end,
1618
+ pill.start,
1619
+ pill.end,
912
1620
  getPolygonShape(padB).points
913
- ).distance - pill2.radius;
1621
+ ).distance - pill.radius;
914
1622
  }
915
1623
  if (isPillPad(padB)) {
916
- const pill2 = getPillCenterLineForPad(padB);
1624
+ const pill = getPillCenterLineForPad(padB);
917
1625
  return getSegmentToPolygonClearanceFromPoints(
918
- pill2.start,
919
- pill2.end,
1626
+ pill.start,
1627
+ pill.end,
920
1628
  getPolygonShape(padA).points
921
- ).distance - pill2.radius;
1629
+ ).distance - pill.radius;
922
1630
  }
923
1631
  if (isCircularPad(padA) && isCircularPad(padB)) {
924
1632
  return distanceBetweenCircleAndCircle(
@@ -1008,19 +1716,19 @@ var getTraceObstacleClearance = (segment, obstacle) => {
1008
1716
  const end = { x: segment.x2, y: segment.y2 };
1009
1717
  const traceRadius = segment.thickness / 2;
1010
1718
  if (obstacle.type === "pcb_via" || isCircularPad(obstacle)) {
1011
- const circle = obstacle.type === "pcb_via" ? {
1719
+ const circle2 = obstacle.type === "pcb_via" ? {
1012
1720
  x: obstacle.x,
1013
1721
  y: obstacle.y,
1014
1722
  radius: obstacle.outer_diameter / 2
1015
1723
  } : getCircleShape(obstacle);
1016
- const closestPoint = pointToSegmentClosestPoint2(circle, start, end);
1724
+ const closestPoint = pointToSegmentClosestPoint2(circle2, start, end);
1017
1725
  return {
1018
- gap: segmentToCircleMinDistance(start, end, circle) - traceRadius,
1726
+ gap: segmentToCircleMinDistance(start, end, circle2) - traceRadius,
1019
1727
  center: getCenterBetweenCopperEdges({
1020
1728
  tracePoint: closestPoint,
1021
- obstaclePoint: circle,
1729
+ obstaclePoint: circle2,
1022
1730
  traceRadius,
1023
- obstacleRadius: circle.radius
1731
+ obstacleRadius: circle2.radius
1024
1732
  })
1025
1733
  };
1026
1734
  }
@@ -1440,19 +2148,19 @@ var getCollidableBounds = (collidable) => {
1440
2148
  if (isPolygon) {
1441
2149
  const polygonPoints = getPolygonPointsForPad(collidable);
1442
2150
  return {
1443
- minX: Math.min(...polygonPoints.map((point) => point.x)),
1444
- minY: Math.min(...polygonPoints.map((point) => point.y)),
1445
- maxX: Math.max(...polygonPoints.map((point) => point.x)),
1446
- maxY: Math.max(...polygonPoints.map((point) => point.y))
2151
+ minX: Math.min(...polygonPoints.map((point2) => point2.x)),
2152
+ minY: Math.min(...polygonPoints.map((point2) => point2.y)),
2153
+ maxX: Math.max(...polygonPoints.map((point2) => point2.x)),
2154
+ maxY: Math.max(...polygonPoints.map((point2) => point2.y))
1447
2155
  };
1448
2156
  }
1449
2157
  if (collidable.type === "pcb_smtpad" && collidable.shape === "rotated_pill") {
1450
- const pill2 = getPillCenterLineForPad(collidable);
2158
+ const pill = getPillCenterLineForPad(collidable);
1451
2159
  return {
1452
- minX: Math.min(pill2.start.x, pill2.end.x) - pill2.radius,
1453
- minY: Math.min(pill2.start.y, pill2.end.y) - pill2.radius,
1454
- maxX: Math.max(pill2.start.x, pill2.end.x) + pill2.radius,
1455
- maxY: Math.max(pill2.start.y, pill2.end.y) + pill2.radius
2160
+ minX: Math.min(pill.start.x, pill.end.x) - pill.radius,
2161
+ minY: Math.min(pill.start.y, pill.end.y) - pill.radius,
2162
+ maxX: Math.max(pill.start.x, pill.end.x) + pill.radius,
2163
+ maxY: Math.max(pill.start.y, pill.end.y) + pill.radius
1456
2164
  };
1457
2165
  }
1458
2166
  }
@@ -1516,7 +2224,7 @@ function checkEachPcbTraceNonOverlapping(circuitJson, {
1516
2224
  } = {}) {
1517
2225
  const errors = [];
1518
2226
  addStartAndEndPortIdsIfMissing(circuitJson);
1519
- connMap ??= getFullConnectivityMapFromCircuitJson2(circuitJson);
2227
+ connMap ??= getFullConnectivityMapFromCircuitJson3(circuitJson);
1520
2228
  const board = getPcbBoard(circuitJson);
1521
2229
  minClearance ??= getBoardDrcValue(board, "min_trace_to_pad_edge_clearance") ?? DEFAULT_TRACE_MARGIN;
1522
2230
  const pcbTraces = cju2(circuitJson).pcb_trace.list();
@@ -1578,7 +2286,7 @@ function checkEachPcbTraceNonOverlapping(circuitJson, {
1578
2286
  objects: allObjects,
1579
2287
  getBounds: getCollidableBounds
1580
2288
  });
1581
- const getReadableName = (id) => getReadableNameForElement2(circuitJson, id);
2289
+ const getReadableName = (id) => getReadableNameForElement3(circuitJson, id);
1582
2290
  const constructErrorMessage = (traceName, otherName, gap) => {
1583
2291
  if (isTraceObstacleOverlap(gap)) {
1584
2292
  return `PCB trace ${traceName} overlaps with ${otherName} (accidental contact)`;
@@ -1792,400 +2500,60 @@ var NetManager = class {
1792
2500
  };
1793
2501
 
1794
2502
  // lib/check-pcb-components-out-of-board/checkViasOffBoard.ts
1795
- import { getReadableNameForElement as getReadableNameForElement3 } from "@tscircuit/circuit-json-util";
2503
+ import { getReadableNameForElement as getReadableNameForElement4 } from "@tscircuit/circuit-json-util";
1796
2504
 
1797
2505
  // lib/check-copper-to-board-edge-clearance.ts
1798
- import * as Flatten from "@flatten-js/core";
1799
- import { applyToPoint, rotateDEG } from "transformation-matrix";
1800
- var toPcbComponentId = (id) => id;
1801
2506
  var GEOMETRY_EPSILON = 1e-9;
1802
- var pointsToPolygon = (points) => {
1803
- if (points.length < 3) return null;
1804
- return new Flatten.Polygon(points.map(({ x, y }) => new Flatten.Point(x, y)));
1805
- };
1806
- var brepRingToPolygon = (vertices) => {
1807
- const ring = vertices.filter((vertex, index) => {
1808
- const previous = vertices[index - 1];
1809
- return !previous || Math.abs(previous.x - vertex.x) > GEOMETRY_EPSILON || Math.abs(previous.y - vertex.y) > GEOMETRY_EPSILON;
1810
- });
1811
- if (ring.length > 1 && Math.abs(ring[0].x - ring.at(-1).x) <= GEOMETRY_EPSILON && Math.abs(ring[0].y - ring.at(-1).y) <= GEOMETRY_EPSILON) {
1812
- ring.pop();
1813
- }
1814
- if (ring.length < 3) return null;
1815
- const edges = [];
1816
- for (let index = 0; index < ring.length; index++) {
1817
- const start = ring[index];
1818
- const end = ring[(index + 1) % ring.length];
1819
- const startPoint = new Flatten.Point(start.x, start.y);
1820
- const endPoint = new Flatten.Point(end.x, end.y);
1821
- const bulge = start.bulge ?? 0;
1822
- if (Math.abs(bulge) <= GEOMETRY_EPSILON) {
1823
- edges.push(new Flatten.Segment(startPoint, endPoint));
1824
- continue;
1825
- }
1826
- const chordLength = startPoint.distanceTo(endPoint)[0];
1827
- if (chordLength <= GEOMETRY_EPSILON) continue;
1828
- const midpoint2 = {
1829
- x: (start.x + end.x) / 2,
1830
- y: (start.y + end.y) / 2
1831
- };
1832
- const leftNormal = {
1833
- x: -(end.y - start.y) / chordLength,
1834
- y: (end.x - start.x) / chordLength
1835
- };
1836
- const centerOffset = chordLength * (1 - bulge * bulge) / (4 * bulge);
1837
- const center = new Flatten.Point(
1838
- midpoint2.x + leftNormal.x * centerOffset,
1839
- midpoint2.y + leftNormal.y * centerOffset
1840
- );
1841
- const radius = chordLength * (1 + bulge * bulge) / (4 * Math.abs(bulge));
1842
- edges.push(
1843
- new Flatten.Arc(
1844
- center,
1845
- radius,
1846
- Math.atan2(start.y - center.y, start.x - center.x),
1847
- Math.atan2(end.y - center.y, end.x - center.x),
1848
- bulge > 0
1849
- )
1850
- );
1851
- }
1852
- if (edges.length < 3) return null;
1853
- const polygon = new Flatten.Polygon();
1854
- polygon.addFace(edges);
1855
- return polygon;
1856
- };
1857
- var boardToPolygon = (board) => {
1858
- if (board.outline && board.outline.length >= 3) {
1859
- return pointsToPolygon(board.outline);
1860
- }
1861
- if (!board.center || typeof board.width !== "number" || typeof board.height !== "number") {
1862
- return null;
1863
- }
1864
- const halfWidth = board.width / 2;
1865
- const halfHeight = board.height / 2;
1866
- return pointsToPolygon([
1867
- { x: board.center.x - halfWidth, y: board.center.y - halfHeight },
1868
- { x: board.center.x + halfWidth, y: board.center.y - halfHeight },
1869
- { x: board.center.x + halfWidth, y: board.center.y + halfHeight },
1870
- { x: board.center.x - halfWidth, y: board.center.y + halfHeight }
1871
- ]);
1872
- };
1873
- var getRectanglePolygon = ({
1874
- x,
1875
- y,
1876
- width,
1877
- height,
1878
- ccwRotationDegrees = 0
1879
- }) => {
1880
- const halfWidth = width / 2;
1881
- const halfHeight = height / 2;
1882
- const rotationMatrix = rotateDEG(ccwRotationDegrees, x, y);
1883
- return pointsToPolygon(
1884
- [
1885
- { x: x - halfWidth, y: y - halfHeight },
1886
- { x: x + halfWidth, y: y - halfHeight },
1887
- { x: x + halfWidth, y: y + halfHeight },
1888
- { x: x - halfWidth, y: y + halfHeight }
1889
- ].map((point) => applyToPoint(rotationMatrix, point))
1890
- );
1891
- };
1892
- var roundedRect = ({
1893
- x,
1894
- y,
1895
- width,
1896
- height,
1897
- cornerRadius,
1898
- ccwRotationDegrees = 0
1899
- }) => {
1900
- const radius = Math.max(0, Math.min(cornerRadius, width / 2, height / 2));
1901
- if (radius <= GEOMETRY_EPSILON) {
1902
- const polygon = getRectanglePolygon({
1903
- x,
1904
- y,
1905
- width,
1906
- height,
1907
- ccwRotationDegrees
1908
- });
1909
- return polygon ? { kind: "shapes", shapes: [polygon] } : null;
1910
- }
1911
- const shapes = [];
1912
- const innerWidth = width - 2 * radius;
1913
- const innerHeight = height - 2 * radius;
1914
- if (innerWidth > GEOMETRY_EPSILON) {
1915
- const verticalBand = getRectanglePolygon({
1916
- x,
1917
- y,
1918
- width: innerWidth,
1919
- height,
1920
- ccwRotationDegrees
1921
- });
1922
- if (verticalBand) shapes.push(verticalBand);
1923
- }
1924
- if (innerHeight > GEOMETRY_EPSILON) {
1925
- const horizontalBand = getRectanglePolygon({
1926
- x,
1927
- y,
1928
- width,
1929
- height: innerHeight,
1930
- ccwRotationDegrees
1931
- });
1932
- if (horizontalBand) shapes.push(horizontalBand);
1933
- }
1934
- const halfInnerWidth = innerWidth / 2;
1935
- const halfInnerHeight = innerHeight / 2;
1936
- const rotationMatrix = rotateDEG(ccwRotationDegrees, x, y);
1937
- const cornerCenters = [
1938
- { x: x - halfInnerWidth, y: y - halfInnerHeight },
1939
- { x: x + halfInnerWidth, y: y - halfInnerHeight },
1940
- { x: x + halfInnerWidth, y: y + halfInnerHeight },
1941
- { x: x - halfInnerWidth, y: y + halfInnerHeight }
1942
- ].map((point) => applyToPoint(rotationMatrix, point)).filter(
1943
- (point, index, points) => points.findIndex(
1944
- (candidate) => Math.abs(candidate.x - point.x) <= GEOMETRY_EPSILON && Math.abs(candidate.y - point.y) <= GEOMETRY_EPSILON
1945
- ) === index
1946
- );
1947
- shapes.push(
1948
- ...cornerCenters.map(
1949
- (center) => new Flatten.Circle(new Flatten.Point(center.x, center.y), radius)
1950
- )
1951
- );
1952
- return shapes.length > 0 ? { kind: "shapes", shapes } : null;
1953
- };
1954
- var pill = ({
1955
- x,
1956
- y,
1957
- width,
1958
- height,
1959
- radius,
1960
- ccwRotationDegrees = 0
1961
- }) => {
1962
- const halfLineLength = Math.max(Math.max(width, height) / 2 - radius, 0);
1963
- const localAxis = width >= height ? { x: halfLineLength, y: 0 } : { x: 0, y: halfLineLength };
1964
- const axis = applyToPoint(rotateDEG(ccwRotationDegrees), localAxis);
1965
- if (halfLineLength <= GEOMETRY_EPSILON) {
1966
- return {
1967
- kind: "shapes",
1968
- shapes: [new Flatten.Circle(new Flatten.Point(x, y), radius)]
1969
- };
1970
- }
1971
- return {
1972
- kind: "pill",
1973
- centerLine: new Flatten.Segment(
1974
- new Flatten.Point(x - axis.x, y - axis.y),
1975
- new Flatten.Point(x + axis.x, y + axis.y)
1976
- ),
1977
- radius
1978
- };
1979
- };
1980
- var getSmtPadGeometry = (pad) => {
1981
- switch (pad.shape) {
1982
- case "circle":
1983
- return {
1984
- kind: "shapes",
1985
- shapes: [
1986
- new Flatten.Circle(new Flatten.Point(pad.x, pad.y), pad.radius)
1987
- ]
1988
- };
1989
- case "rect":
1990
- return roundedRect({
1991
- x: pad.x,
1992
- y: pad.y,
1993
- width: pad.width,
1994
- height: pad.height,
1995
- cornerRadius: pad.rect_border_radius ?? pad.corner_radius ?? 0
1996
- });
1997
- case "rotated_rect":
1998
- return roundedRect({
1999
- x: pad.x,
2000
- y: pad.y,
2001
- width: pad.width,
2002
- height: pad.height,
2003
- cornerRadius: pad.rect_border_radius ?? pad.corner_radius ?? 0,
2004
- ccwRotationDegrees: pad.ccw_rotation
2005
- });
2006
- case "pill":
2007
- return pill({
2008
- x: pad.x,
2009
- y: pad.y,
2010
- width: pad.width,
2011
- height: pad.height,
2012
- radius: pad.radius
2013
- });
2014
- case "rotated_pill":
2015
- return pill({
2016
- x: pad.x,
2017
- y: pad.y,
2018
- width: pad.width,
2019
- height: pad.height,
2020
- radius: pad.radius,
2021
- ccwRotationDegrees: pad.ccw_rotation
2022
- });
2023
- case "polygon": {
2024
- const polygon = pointsToPolygon(pad.points);
2025
- return polygon ? { kind: "shapes", shapes: [polygon] } : null;
2026
- }
2027
- }
2028
- };
2029
- var getPlatedHoleGeometry = (platedHole, componentCcwRotationDegrees) => {
2030
- switch (platedHole.shape) {
2031
- case "circle":
2032
- return {
2033
- kind: "shapes",
2034
- shapes: [
2035
- new Flatten.Circle(
2036
- new Flatten.Point(platedHole.x, platedHole.y),
2037
- platedHole.outer_diameter / 2
2038
- )
2039
- ]
2040
- };
2041
- case "oval":
2042
- case "pill":
2043
- return pill({
2044
- x: platedHole.x,
2045
- y: platedHole.y,
2046
- width: platedHole.outer_width,
2047
- height: platedHole.outer_height,
2048
- radius: Math.min(
2049
- platedHole.outer_width / 2,
2050
- platedHole.outer_height / 2
2051
- ),
2052
- ccwRotationDegrees: platedHole.ccw_rotation
2053
- });
2054
- case "circular_hole_with_rect_pad":
2055
- case "pill_hole_with_rect_pad":
2056
- case "rotated_pill_hole_with_rect_pad":
2057
- return roundedRect({
2058
- x: platedHole.x,
2059
- y: platedHole.y,
2060
- width: platedHole.rect_pad_width,
2061
- height: platedHole.rect_pad_height,
2062
- cornerRadius: platedHole.rect_border_radius ?? 0,
2063
- ccwRotationDegrees: "rect_ccw_rotation" in platedHole ? platedHole.rect_ccw_rotation ?? 0 : 0
2064
- });
2065
- case "hole_with_polygon_pad": {
2066
- const ccwRotationDegrees = platedHole.ccw_rotation ?? componentCcwRotationDegrees;
2067
- const rotationMatrix = rotateDEG(ccwRotationDegrees);
2068
- const polygon = pointsToPolygon(
2069
- platedHole.pad_outline.map((point) => {
2070
- const rotatedPoint = applyToPoint(rotationMatrix, point);
2071
- return {
2072
- x: platedHole.x + rotatedPoint.x,
2073
- y: platedHole.y + rotatedPoint.y
2074
- };
2075
- })
2076
- );
2077
- return polygon ? { kind: "shapes", shapes: [polygon] } : null;
2078
- }
2079
- }
2080
- };
2081
- var getCopperGeometry = (element, componentCcwRotationsById) => {
2082
- if (element.type === "pcb_via") {
2083
- return {
2084
- kind: "shapes",
2085
- shapes: [
2086
- new Flatten.Circle(
2087
- new Flatten.Point(element.x, element.y),
2088
- element.outer_diameter / 2
2089
- )
2090
- ]
2091
- };
2092
- }
2093
- if (element.type === "pcb_smtpad") return getSmtPadGeometry(element);
2094
- if (element.type === "pcb_plated_hole") {
2095
- return getPlatedHoleGeometry(
2096
- element,
2097
- element.pcb_component_id ? componentCcwRotationsById.get(
2098
- toPcbComponentId(element.pcb_component_id)
2099
- ) ?? 0 : 0
2100
- );
2101
- }
2102
- let polygon;
2103
- switch (element.shape) {
2104
- case "rect":
2105
- polygon = getRectanglePolygon({
2106
- x: element.center.x,
2107
- y: element.center.y,
2108
- width: element.width,
2109
- height: element.height,
2110
- ccwRotationDegrees: element.rotation ?? 0
2111
- });
2112
- break;
2113
- case "polygon":
2114
- polygon = pointsToPolygon(element.points);
2115
- break;
2116
- case "brep":
2117
- polygon = brepRingToPolygon(element.brep_shape.outer_ring.vertices);
2118
- break;
2119
- }
2120
- return polygon ? { kind: "shapes", shapes: [polygon] } : null;
2121
- };
2122
- var getCopperElementId = (element) => {
2123
- if (element.type === "pcb_via") return element.pcb_via_id;
2124
- if (element.type === "pcb_smtpad") return element.pcb_smtpad_id;
2125
- if (element.type === "pcb_plated_hole") return element.pcb_plated_hole_id;
2126
- return element.pcb_copper_pour_id;
2127
- };
2128
2507
  var getCopperElementLabel = (element) => {
2129
2508
  if (element.type === "pcb_via") return "Via";
2130
2509
  if (element.type === "pcb_smtpad") return "SMT pad";
2131
2510
  if (element.type === "pcb_plated_hole") return "Plated hole";
2132
2511
  return "Copper pour";
2133
2512
  };
2134
- var measureClearance = (board, geometry) => {
2135
- if (geometry.kind === "shapes") {
2136
- const isInside2 = geometry.shapes.every((shape) => board.contains(shape));
2137
- return {
2138
- isInside: isInside2,
2139
- clearance: isInside2 ? Math.min(
2140
- ...geometry.shapes.map((shape) => board.distanceTo(shape)[0])
2141
- ) : 0
2142
- };
2143
- }
2144
- const centerLineClearance = board.distanceTo(geometry.centerLine)[0];
2145
- const clearance = centerLineClearance - geometry.radius;
2146
- const isInside = board.contains(geometry.centerLine) && clearance >= -GEOMETRY_EPSILON;
2147
- return {
2148
- isInside,
2149
- clearance: isInside ? Math.max(0, clearance) : 0
2150
- };
2151
- };
2152
2513
  function checkCopperToBoardEdgeClearance(circuitJson) {
2153
2514
  const board = getPcbBoard(circuitJson);
2154
2515
  if (!board) return [];
2155
- const boardPolygon = boardToPolygon(board);
2516
+ const boardPolygon = convertCircuitJsonToFlattenJs([board], { strict: true }).elements[0]?.shapes[0];
2156
2517
  if (!boardPolygon) return [];
2157
2518
  const requiredClearance = getBoardDrcValue(board, "min_board_edge_clearance") ?? jlcMinTolerances.min_board_edge_clearance;
2158
2519
  if (requiredClearance === void 0) return [];
2159
2520
  const allowedOffBoardComponentIds = new Set(
2160
2521
  circuitJson.filter(
2161
2522
  (element) => element.type === "pcb_component"
2162
- ).filter((component) => component.is_allowed_to_be_off_board).map((component) => toPcbComponentId(component.pcb_component_id))
2163
- );
2164
- const copperElements = circuitJson.filter(
2165
- (element) => element.type === "pcb_via" || element.type === "pcb_smtpad" || element.type === "pcb_plated_hole" || element.type === "pcb_copper_pour"
2523
+ ).filter((component) => component.is_allowed_to_be_off_board).map((component) => component.pcb_component_id)
2166
2524
  );
2167
- const componentCcwRotationsById = new Map(
2168
- circuitJson.filter(
2169
- (element) => element.type === "pcb_component"
2170
- ).map((component) => [
2171
- toPcbComponentId(component.pcb_component_id),
2172
- component.rotation
2173
- ])
2525
+ const { elements: copperElements } = convertCircuitJsonToFlattenJs(
2526
+ circuitJson,
2527
+ {
2528
+ elementTypes: [
2529
+ "pcb_via",
2530
+ "pcb_smtpad",
2531
+ "pcb_plated_hole",
2532
+ "pcb_copper_pour"
2533
+ ],
2534
+ includeDrillHoles: false,
2535
+ strict: true
2536
+ }
2174
2537
  );
2175
2538
  const errors = [];
2176
- for (const element of copperElements) {
2177
- if ((element.type === "pcb_smtpad" || element.type === "pcb_plated_hole") && element.pcb_component_id && allowedOffBoardComponentIds.has(
2178
- toPcbComponentId(element.pcb_component_id)
2179
- )) {
2539
+ const seen = /* @__PURE__ */ new Set();
2540
+ for (const geometry of copperElements) {
2541
+ if (seen.has(geometry.elementId)) continue;
2542
+ seen.add(geometry.elementId);
2543
+ const element = geometry.sourceElement;
2544
+ if ((element.type === "pcb_smtpad" || element.type === "pcb_plated_hole") && element.pcb_component_id && allowedOffBoardComponentIds.has(element.pcb_component_id)) {
2180
2545
  continue;
2181
2546
  }
2182
- const geometry = getCopperGeometry(element, componentCcwRotationsById);
2183
- if (!geometry) continue;
2184
- const { isInside, clearance } = measureClearance(boardPolygon, geometry);
2547
+ const isInside = geometry.shapes.every(
2548
+ (shape) => boardPolygon.contains(shape)
2549
+ );
2550
+ const clearance = isInside ? Math.min(
2551
+ ...geometry.shapes.map((shape) => boardPolygon.distanceTo(shape)[0])
2552
+ ) : 0;
2185
2553
  if (isInside && clearance + GEOMETRY_EPSILON >= requiredClearance) {
2186
2554
  continue;
2187
2555
  }
2188
- const id = getCopperElementId(element);
2556
+ const id = geometry.elementId;
2189
2557
  const label = getCopperElementLabel(element);
2190
2558
  errors.push({
2191
2559
  type: "pcb_placement_error",
@@ -2216,7 +2584,7 @@ function checkViasOffBoard(circuitJson) {
2216
2584
  return vias.flatMap((via) => {
2217
2585
  const violation = violationsById.get(via.pcb_via_id);
2218
2586
  if (!violation) return [];
2219
- const viaName = getReadableNameForElement3(circuitJson, via.pcb_via_id);
2587
+ const viaName = getReadableNameForElement4(circuitJson, via.pcb_via_id);
2220
2588
  return [
2221
2589
  {
2222
2590
  ...violation,
@@ -2229,7 +2597,7 @@ function checkViasOffBoard(circuitJson) {
2229
2597
 
2230
2598
  // lib/check-pcb-components-out-of-board/checkPcbComponentsOutOfBoard.ts
2231
2599
  import * as Flatten2 from "@flatten-js/core";
2232
- import { rotateDEG as rotateDEG2, applyToPoint as applyToPoint2 } from "transformation-matrix";
2600
+ import { rotateDEG, applyToPoint } from "transformation-matrix";
2233
2601
  function isPolygonCCW(poly) {
2234
2602
  return poly.area() >= 0;
2235
2603
  }
@@ -2250,9 +2618,9 @@ function rectanglePolygon({
2250
2618
  ];
2251
2619
  let poly = new Flatten2.Polygon(corners);
2252
2620
  if (rotationDeg) {
2253
- const matrix = rotateDEG2(rotationDeg, cx, cy);
2621
+ const matrix = rotateDEG(rotationDeg, cx, cy);
2254
2622
  const rotatedCorners = corners.map((pt) => {
2255
- const p = applyToPoint2(matrix, { x: pt.x, y: pt.y });
2623
+ const p = applyToPoint(matrix, { x: pt.x, y: pt.y });
2256
2624
  return new Flatten2.Point(p.x, p.y);
2257
2625
  });
2258
2626
  poly = new Flatten2.Polygon(rotatedCorners);
@@ -2260,7 +2628,7 @@ function rectanglePolygon({
2260
2628
  if (!isPolygonCCW(poly)) poly.reverse();
2261
2629
  return poly;
2262
2630
  }
2263
- function boardToPolygon2({
2631
+ function boardToPolygon({
2264
2632
  board
2265
2633
  }) {
2266
2634
  if (board.outline && board.outline.length > 0) {
@@ -2316,9 +2684,9 @@ function computeOverlapDistance(compPoly, boardPoly, componentCenter, componentW
2316
2684
  y: (corners[i].y + corners[next].y) / 2
2317
2685
  });
2318
2686
  }
2319
- const matrix = rotateDEG2(rotationDeg, componentCenter.x, componentCenter.y);
2687
+ const matrix = rotateDEG(rotationDeg, componentCenter.x, componentCenter.y);
2320
2688
  const rotatePoint2 = (pt) => {
2321
- const p = applyToPoint2(matrix, pt);
2689
+ const p = applyToPoint(matrix, pt);
2322
2690
  return new Flatten2.Point(p.x, p.y);
2323
2691
  };
2324
2692
  const rotatedPoints = corners.concat(midpoints).map(rotatePoint2);
@@ -2404,7 +2772,7 @@ function checkPcbComponentsOutOfBoard(circuitJson) {
2404
2772
  (el) => el.type === "pcb_board"
2405
2773
  );
2406
2774
  if (!board) return [];
2407
- const boardPoly = boardToPolygon2({ board });
2775
+ const boardPoly = boardToPolygon({ board });
2408
2776
  if (!boardPoly) return [];
2409
2777
  const components = circuitJson.filter(
2410
2778
  (el) => el.type === "pcb_component"
@@ -2462,7 +2830,7 @@ function checkPcbComponentsOutOfBoard(circuitJson) {
2462
2830
  // lib/check-pcb-component-over-cutout.ts
2463
2831
  import { doBoundsOverlap } from "@tscircuit/math-utils";
2464
2832
  import * as Flatten3 from "@flatten-js/core";
2465
- import { applyToPoint as applyToPoint3, rotateDEG as rotateDEG3 } from "transformation-matrix";
2833
+ import { applyToPoint as applyToPoint2, rotateDEG as rotateDEG2 } from "transformation-matrix";
2466
2834
  var CUTOUT_CIRCLE_SEGMENTS = 32;
2467
2835
  function rectanglePolygon2({
2468
2836
  center,
@@ -2478,10 +2846,10 @@ function rectanglePolygon2({
2478
2846
  { x: center.x + halfWidth, y: center.y + halfHeight },
2479
2847
  { x: center.x - halfWidth, y: center.y + halfHeight }
2480
2848
  ];
2481
- const matrix = rotateDEG3(rotation, center.x, center.y);
2849
+ const matrix = rotateDEG2(rotation, center.x, center.y);
2482
2850
  return new Flatten3.Polygon(
2483
2851
  corners.map((corner) => {
2484
- const rotated = rotation ? applyToPoint3(matrix, corner) : corner;
2852
+ const rotated = rotation ? applyToPoint2(matrix, corner) : corner;
2485
2853
  return new Flatten3.Point(rotated.x, rotated.y);
2486
2854
  })
2487
2855
  );
@@ -2514,7 +2882,7 @@ function cutoutToPolygon(cutout) {
2514
2882
  }
2515
2883
  if (cutout.shape === "polygon") {
2516
2884
  return new Flatten3.Polygon(
2517
- cutout.points.map((point) => new Flatten3.Point(point.x, point.y))
2885
+ cutout.points.map((point2) => new Flatten3.Point(point2.x, point2.y))
2518
2886
  );
2519
2887
  }
2520
2888
  return null;
@@ -2643,9 +3011,9 @@ function checkPcbCopperOverKeepout(circuitJson) {
2643
3011
  }
2644
3012
 
2645
3013
  // lib/check-same-net-via-spacing.ts
2646
- import { getReadableNameForElement as getReadableNameForElement4 } from "@tscircuit/circuit-json-util";
3014
+ import { getReadableNameForElement as getReadableNameForElement5 } from "@tscircuit/circuit-json-util";
2647
3015
  import {
2648
- getFullConnectivityMapFromCircuitJson as getFullConnectivityMapFromCircuitJson3
3016
+ getFullConnectivityMapFromCircuitJson as getFullConnectivityMapFromCircuitJson4
2649
3017
  } from "circuit-json-to-connectivity-map";
2650
3018
 
2651
3019
  // lib/util/distance.ts
@@ -2667,7 +3035,7 @@ function checkSameNetViaSpacing(circuitJson, {
2667
3035
  if (vias.length < 2) return [];
2668
3036
  const board = getPcbBoard(circuitJson);
2669
3037
  minClearance ??= getBoardDrcValue(board, "min_via_hole_edge_to_via_hole_edge_clearance") ?? jlcMinTolerances.min_via_hole_edge_to_via_hole_edge_clearance;
2670
- connMap ??= getFullConnectivityMapFromCircuitJson3(circuitJson);
3038
+ connMap ??= getFullConnectivityMapFromCircuitJson4(circuitJson);
2671
3039
  const errors = [];
2672
3040
  const reported = /* @__PURE__ */ new Set();
2673
3041
  for (let i = 0; i < vias.length; i++) {
@@ -2684,10 +3052,10 @@ function checkSameNetViaSpacing(circuitJson, {
2684
3052
  errors.push({
2685
3053
  type: "pcb_via_clearance_error",
2686
3054
  pcb_error_id: `same_net_vias_close_${pairId}`,
2687
- message: `Vias ${getReadableNameForElement4(
3055
+ message: `Vias ${getReadableNameForElement5(
2688
3056
  circuitJson,
2689
3057
  viaA.pcb_via_id
2690
- )} and ${getReadableNameForElement4(
3058
+ )} and ${getReadableNameForElement5(
2691
3059
  circuitJson,
2692
3060
  viaB.pcb_via_id
2693
3061
  )} are too close together (gap: ${gap.toFixed(3)}mm)`,
@@ -2706,9 +3074,9 @@ function checkSameNetViaSpacing(circuitJson, {
2706
3074
  }
2707
3075
 
2708
3076
  // lib/check-different-net-via-spacing.ts
2709
- import { getReadableNameForElement as getReadableNameForElement5 } from "@tscircuit/circuit-json-util";
3077
+ import { getReadableNameForElement as getReadableNameForElement6 } from "@tscircuit/circuit-json-util";
2710
3078
  import {
2711
- getFullConnectivityMapFromCircuitJson as getFullConnectivityMapFromCircuitJson4
3079
+ getFullConnectivityMapFromCircuitJson as getFullConnectivityMapFromCircuitJson5
2712
3080
  } from "circuit-json-to-connectivity-map";
2713
3081
  function checkDifferentNetViaSpacing(circuitJson, {
2714
3082
  connMap,
@@ -2718,7 +3086,7 @@ function checkDifferentNetViaSpacing(circuitJson, {
2718
3086
  if (vias.length < 2) return [];
2719
3087
  const board = getPcbBoard(circuitJson);
2720
3088
  minClearance ??= getBoardDrcValue(board, "min_via_hole_edge_to_via_hole_edge_clearance") ?? jlcMinTolerances.min_via_hole_edge_to_via_hole_edge_clearance;
2721
- connMap ??= getFullConnectivityMapFromCircuitJson4(circuitJson);
3089
+ connMap ??= getFullConnectivityMapFromCircuitJson5(circuitJson);
2722
3090
  const errors = [];
2723
3091
  const reported = /* @__PURE__ */ new Set();
2724
3092
  for (let i = 0; i < vias.length; i++) {
@@ -2735,10 +3103,10 @@ function checkDifferentNetViaSpacing(circuitJson, {
2735
3103
  errors.push({
2736
3104
  type: "pcb_via_clearance_error",
2737
3105
  pcb_error_id: `different_net_vias_close_${pairId}`,
2738
- message: `Vias ${getReadableNameForElement5(
3106
+ message: `Vias ${getReadableNameForElement6(
2739
3107
  circuitJson,
2740
3108
  viaA.pcb_via_id
2741
- )} and ${getReadableNameForElement5(
3109
+ )} and ${getReadableNameForElement6(
2742
3110
  circuitJson,
2743
3111
  viaB.pcb_via_id
2744
3112
  )} from different nets are too close together (gap: ${gap.toFixed(
@@ -2760,14 +3128,14 @@ function checkDifferentNetViaSpacing(circuitJson, {
2760
3128
 
2761
3129
  // lib/check-source-traces-match-pcb-trace-thickness.ts
2762
3130
  import { cju as cju4 } from "@tscircuit/circuit-json-util";
2763
- import { getFullConnectivityMapFromCircuitJson as getFullConnectivityMapFromCircuitJson5 } from "circuit-json-to-connectivity-map";
3131
+ import { getFullConnectivityMapFromCircuitJson as getFullConnectivityMapFromCircuitJson6 } from "circuit-json-to-connectivity-map";
2764
3132
  function checkSourceTracesMatchPcbTraceThickness(circuitJson) {
2765
3133
  const warnings = [];
2766
3134
  const db = cju4(circuitJson);
2767
3135
  const sourceTraces = db.source_trace.list();
2768
3136
  const pcbTraces = db.pcb_trace.list();
2769
3137
  const pcbPorts = db.pcb_port.list();
2770
- const connectivityMap = getFullConnectivityMapFromCircuitJson5(circuitJson);
3138
+ const connectivityMap = getFullConnectivityMapFromCircuitJson6(circuitJson);
2771
3139
  for (const sourceTrace of sourceTraces) {
2772
3140
  const requestedThickness = sourceTrace.min_trace_thickness;
2773
3141
  if (requestedThickness === void 0) continue;
@@ -2785,7 +3153,7 @@ function checkSourceTracesMatchPcbTraceThickness(circuitJson) {
2785
3153
  );
2786
3154
  if (relatedPcbTraces.length === 0) continue;
2787
3155
  const actualWireWidths = relatedPcbTraces.flatMap(
2788
- (pcbTrace) => pcbTrace.route.filter((point) => point.route_type === "wire").map((point) => point.width)
3156
+ (pcbTrace) => pcbTrace.route.filter((point2) => point2.route_type === "wire").map((point2) => point2.width)
2789
3157
  );
2790
3158
  if (actualWireWidths.length === 0) continue;
2791
3159
  const actualThickness = Math.min(...actualWireWidths);
@@ -2793,18 +3161,18 @@ function checkSourceTracesMatchPcbTraceThickness(circuitJson) {
2793
3161
  let undersizedSegment;
2794
3162
  for (const relatedPcbTrace of relatedPcbTraces) {
2795
3163
  for (let i = 0; i < relatedPcbTrace.route.length - 1; i++) {
2796
- const point = relatedPcbTrace.route[i];
3164
+ const point2 = relatedPcbTrace.route[i];
2797
3165
  const nextPoint = relatedPcbTrace.route[i + 1];
2798
- if (!point || !nextPoint) continue;
2799
- if (point.route_type !== "wire" || nextPoint.route_type !== "wire") {
3166
+ if (!point2 || !nextPoint) continue;
3167
+ if (point2.route_type !== "wire" || nextPoint.route_type !== "wire") {
2800
3168
  continue;
2801
3169
  }
2802
- if (point.width !== actualThickness) continue;
3170
+ if (point2.width !== actualThickness) continue;
2803
3171
  undersizedSegment = {
2804
3172
  pcb_trace_id: relatedPcbTrace.pcb_trace_id,
2805
3173
  center: {
2806
- x: (point.x + nextPoint.x) / 2,
2807
- y: (point.y + nextPoint.y) / 2
3174
+ x: (point2.x + nextPoint.x) / 2,
3175
+ y: (point2.y + nextPoint.y) / 2
2808
3176
  }
2809
3177
  };
2810
3178
  break;
@@ -2832,7 +3200,7 @@ function checkSourceTracesMatchPcbTraceThickness(circuitJson) {
2832
3200
  }
2833
3201
 
2834
3202
  // lib/check-source-traces-have-pcb-traces.ts
2835
- import { getFullConnectivityMapFromCircuitJson as getFullConnectivityMapFromCircuitJson6 } from "circuit-json-to-connectivity-map";
3203
+ import { getFullConnectivityMapFromCircuitJson as getFullConnectivityMapFromCircuitJson7 } from "circuit-json-to-connectivity-map";
2836
3204
  function checkSourceTracesHavePcbTraces(circuitJson) {
2837
3205
  const errors = [];
2838
3206
  const sourceTraces = circuitJson.filter(
@@ -2847,7 +3215,7 @@ function checkSourceTracesHavePcbTraces(circuitJson) {
2847
3215
  const sourcePortToPcbPort = new Map(
2848
3216
  pcbPorts.map((pcbPort) => [pcbPort.source_port_id, pcbPort])
2849
3217
  );
2850
- const connectivityMap = getFullConnectivityMapFromCircuitJson6(circuitJson);
3218
+ const connectivityMap = getFullConnectivityMapFromCircuitJson7(circuitJson);
2851
3219
  let pourConnectivity;
2852
3220
  const getPourConnectivity = () => pourConnectivity ??= getCopperPourConnectivity(
2853
3221
  circuitJson,
@@ -2895,7 +3263,7 @@ import {
2895
3263
  getReadableNameForPcbTrace
2896
3264
  } from "@tscircuit/circuit-json-util";
2897
3265
  import {
2898
- getFullConnectivityMapFromCircuitJson as getFullConnectivityMapFromCircuitJson7,
3266
+ getFullConnectivityMapFromCircuitJson as getFullConnectivityMapFromCircuitJson8,
2899
3267
  PcbConnectivityMap as PcbConnectivityMap2
2900
3268
  } from "circuit-json-to-connectivity-map";
2901
3269
 
@@ -2977,27 +3345,27 @@ function getViaContactIndex(circuitJson, connectivity) {
2977
3345
  }
2978
3346
  for (const trace of circuitJson) {
2979
3347
  if (trace.type !== "pcb_trace") continue;
2980
- for (const point of trace.route) {
2981
- if (point.route_type !== "via") continue;
3348
+ for (const point2 of trace.route) {
3349
+ if (point2.route_type !== "via") continue;
2982
3350
  if (vias.some(
2983
- (via) => Math.hypot(via.x - point.x, via.y - point.y) <= CONTACT_EPSILON && (via.pcb_trace_id === trace.pcb_trace_id || !via.pcb_trace_id && connectivity.areIdsConnected(
3351
+ (via) => Math.hypot(via.x - point2.x, via.y - point2.y) <= CONTACT_EPSILON && (via.pcb_trace_id === trace.pcb_trace_id || !via.pcb_trace_id && connectivity.areIdsConnected(
2984
3352
  via.pcb_via_id,
2985
3353
  trace.pcb_trace_id
2986
3354
  ))
2987
3355
  ))
2988
3356
  continue;
2989
- const from = stack.indexOf(point.from_layer);
2990
- const to = stack.indexOf(point.to_layer);
3357
+ const from = stack.indexOf(point2.from_layer);
3358
+ const to = stack.indexOf(point2.to_layer);
2991
3359
  if (from < 0 || to < 0) continue;
2992
- const diameter = point.outer_diameter;
3360
+ const diameter = point2.outer_diameter;
2993
3361
  if (diameter !== void 0 && (!Number.isFinite(diameter) || diameter <= 0))
2994
3362
  continue;
2995
3363
  add(
2996
3364
  trace.pcb_trace_id,
2997
3365
  stack.slice(Math.min(from, to), Math.max(from, to) + 1),
2998
3366
  {
2999
- x: point.x,
3000
- y: point.y,
3367
+ x: point2.x,
3368
+ y: point2.y,
3001
3369
  ownerTraceId: trace.pcb_trace_id,
3002
3370
  radius: diameter === void 0 ? void 0 : diameter / 2
3003
3371
  }
@@ -3007,30 +3375,30 @@ function getViaContactIndex(circuitJson, connectivity) {
3007
3375
  return index;
3008
3376
  }
3009
3377
  function endpointTouchesVia({
3010
- point,
3378
+ point: point2,
3011
3379
  width,
3012
3380
  ownerTrace,
3013
3381
  index,
3014
3382
  connectivity
3015
3383
  }) {
3016
- if (point.route_type !== "wire" || !Number.isFinite(width) || width <= 0)
3384
+ if (point2.route_type !== "wire" || !Number.isFinite(width) || width <= 0)
3017
3385
  return false;
3018
3386
  const net = connectivity.getNetConnectedToId(ownerTrace.pcb_trace_id);
3019
3387
  if (!net) return false;
3020
- return (index.get(net)?.get(point.layer) ?? []).some((via) => {
3388
+ return (index.get(net)?.get(point2.layer) ?? []).some((via) => {
3021
3389
  if (via.ownerTraceId === ownerTrace.pcb_trace_id) return false;
3022
3390
  if (!via.touchesPad && ![...via.touchingTraceIds].some((id) => id !== ownerTrace.pcb_trace_id))
3023
3391
  return false;
3024
3392
  const contactDistance = via.radius === void 0 ? 0 : via.radius + width / 2;
3025
- return Math.hypot(point.x - via.x, point.y - via.y) <= contactDistance + CONTACT_EPSILON;
3393
+ return Math.hypot(point2.x - via.x, point2.y - via.y) <= contactDistance + CONTACT_EPSILON;
3026
3394
  });
3027
3395
  }
3028
3396
 
3029
3397
  // lib/check-traces-are-contiguous/check-traces-are-contiguous.ts
3030
3398
  var ENDPOINT_CONTACT_EPSILON = 1e-9;
3031
3399
  var TRACE_SEGMENT_GEOMETRY_EPSILON = 1e-9;
3032
- function routePointTouchesPad(point, pad) {
3033
- return point.route_type === "wire" && getLayersOfPcbElement(pad).includes(point.layer) && isPointInPad(point, pad);
3400
+ function routePointTouchesPad(point2, pad) {
3401
+ return point2.route_type === "wire" && getLayersOfPcbElement(pad).includes(point2.layer) && isPointInPad(point2, pad);
3034
3402
  }
3035
3403
  function getTraceWireSegmentsByNetAndLayer(pcbTraces, fullConnectivityMap) {
3036
3404
  const segmentsByNetAndLayer = /* @__PURE__ */ new Map();
@@ -3073,43 +3441,43 @@ function getEndpointTraceCopperWidth(trace, endpoint) {
3073
3441
  return void 0;
3074
3442
  }
3075
3443
  function routePointTouchesLogicallyConnectedTraceCopper({
3076
- point,
3444
+ point: point2,
3077
3445
  endpointTraceCopperWidth,
3078
3446
  ownerTrace,
3079
3447
  traceWireSegmentsByNetAndLayer,
3080
3448
  fullConnectivityMap
3081
3449
  }) {
3082
- if (point.route_type !== "wire") return false;
3450
+ if (point2.route_type !== "wire") return false;
3083
3451
  const ownerNetId = fullConnectivityMap.getNetConnectedToId(
3084
3452
  ownerTrace.pcb_trace_id
3085
3453
  );
3086
3454
  if (!ownerNetId) return false;
3087
- const candidateSegments = traceWireSegmentsByNetAndLayer.get(ownerNetId)?.get(point.layer) ?? [];
3455
+ const candidateSegments = traceWireSegmentsByNetAndLayer.get(ownerNetId)?.get(point2.layer) ?? [];
3088
3456
  for (const segment of candidateSegments) {
3089
3457
  if (segment.trace.pcb_trace_id === ownerTrace.pcb_trace_id) continue;
3090
3458
  const maximumContactDistance = endpointTraceCopperWidth / 2 + segment.start.width / 2 + ENDPOINT_CONTACT_EPSILON;
3091
- if (pointToSegmentDistance3(point, segment.start, segment.end) <= maximumContactDistance) {
3459
+ if (pointToSegmentDistance3(point2, segment.start, segment.end) <= maximumContactDistance) {
3092
3460
  return true;
3093
3461
  }
3094
3462
  }
3095
3463
  return false;
3096
3464
  }
3097
- function getRoutePointCenter(point) {
3098
- if (point.route_type === "through_pad") {
3465
+ function getRoutePointCenter(point2) {
3466
+ if (point2.route_type === "through_pad") {
3099
3467
  return {
3100
- x: (point.start.x + point.end.x) / 2,
3101
- y: (point.start.y + point.end.y) / 2
3468
+ x: (point2.start.x + point2.end.x) / 2,
3469
+ y: (point2.start.y + point2.end.y) / 2
3102
3470
  };
3103
3471
  }
3104
- return { x: point.x, y: point.y };
3472
+ return { x: point2.x, y: point2.y };
3105
3473
  }
3106
- function routePointConnectsToAnotherExpectedPort(point, expectedPorts, missingPcbPortId, padMap) {
3474
+ function routePointConnectsToAnotherExpectedPort(point2, expectedPorts, missingPcbPortId, padMap) {
3107
3475
  return expectedPorts.some((expectedPort) => {
3108
3476
  if (!expectedPort.pcb_port_id || expectedPort.pcb_port_id === missingPcbPortId) {
3109
3477
  return false;
3110
3478
  }
3111
3479
  const expectedPads = padMap.get(expectedPort.pcb_port_id);
3112
- return expectedPads?.some((pad) => routePointTouchesPad(point, pad)) ?? false;
3480
+ return expectedPads?.some((pad) => routePointTouchesPad(point2, pad)) ?? false;
3113
3481
  });
3114
3482
  }
3115
3483
  function getMissingConnectionErrorCenter({
@@ -3182,7 +3550,7 @@ function checkTracesAreContiguous(circuitJson) {
3182
3550
  let fullConnectivityMap;
3183
3551
  let traceWireSegmentsByNetAndLayer;
3184
3552
  const getFullConnectivityMap = () => {
3185
- fullConnectivityMap ??= getFullConnectivityMapFromCircuitJson7(circuitJson);
3553
+ fullConnectivityMap ??= getFullConnectivityMapFromCircuitJson8(circuitJson);
3186
3554
  return fullConnectivityMap;
3187
3555
  };
3188
3556
  const getTraceWireSegmentIndex = () => {
@@ -3220,10 +3588,10 @@ function checkTracesAreContiguous(circuitJson) {
3220
3588
  const touchedPortIds = /* @__PURE__ */ new Set();
3221
3589
  const firstPoint = trace.route[0];
3222
3590
  const lastPoint = trace.route.at(-1);
3223
- for (const point of [firstPoint, lastPoint]) {
3224
- if (!point) continue;
3591
+ for (const point2 of [firstPoint, lastPoint]) {
3592
+ if (!point2) continue;
3225
3593
  for (const [pcbPortId, pads] of padMap) {
3226
- if (pads.some((pad) => routePointTouchesPad(point, pad))) {
3594
+ if (pads.some((pad) => routePointTouchesPad(point2, pad))) {
3227
3595
  touchedPortIds.add(pcbPortId);
3228
3596
  }
3229
3597
  }
@@ -3521,7 +3889,7 @@ import {
3521
3889
  getPrimaryId as getPrimaryId5
3522
3890
  } from "@tscircuit/circuit-json-util";
3523
3891
  import { doBoundsOverlap as doBoundsOverlap3 } from "@tscircuit/math-utils";
3524
- import { getFullConnectivityMapFromCircuitJson as getFullConnectivityMapFromCircuitJson8 } from "circuit-json-to-connectivity-map";
3892
+ import { getFullConnectivityMapFromCircuitJson as getFullConnectivityMapFromCircuitJson9 } from "circuit-json-to-connectivity-map";
3525
3893
 
3526
3894
  // lib/check-pcb-components-overlap/doPcbElementsOverlap.ts
3527
3895
  import { getBoundsOfPcbElements as getBoundsOfPcbElements4 } from "@tscircuit/circuit-json-util";
@@ -3608,7 +3976,7 @@ var formatOverlapElementDescription = (circuitJson, element) => {
3608
3976
  };
3609
3977
  function checkPcbComponentOverlap(circuitJson) {
3610
3978
  const errors = [];
3611
- const connMap = getFullConnectivityMapFromCircuitJson8(circuitJson);
3979
+ const connMap = getFullConnectivityMapFromCircuitJson9(circuitJson);
3612
3980
  const smtPads = cju6(circuitJson).pcb_smtpad.list();
3613
3981
  const platedHoles = cju6(circuitJson).pcb_plated_hole.list();
3614
3982
  const holes = cju6(circuitJson).pcb_hole.list();
@@ -3933,11 +4301,11 @@ var checkPcbTraceViaCounts = (circuitJson) => {
3933
4301
  // lib/check-pad-pad-clearance.ts
3934
4302
  import {
3935
4303
  getPrimaryId as getPrimaryId6,
3936
- getReadableNameForElement as getReadableNameForElement6
4304
+ getReadableNameForElement as getReadableNameForElement7
3937
4305
  } from "@tscircuit/circuit-json-util";
3938
4306
  import { formatMm } from "format-si-unit";
3939
4307
  import {
3940
- getFullConnectivityMapFromCircuitJson as getFullConnectivityMapFromCircuitJson9
4308
+ getFullConnectivityMapFromCircuitJson as getFullConnectivityMapFromCircuitJson10
3941
4309
  } from "circuit-json-to-connectivity-map";
3942
4310
  function checkPadPadClearance(circuitJson, {
3943
4311
  connMap,
@@ -3947,7 +4315,7 @@ function checkPadPadClearance(circuitJson, {
3947
4315
  if (pads.length < 2) return [];
3948
4316
  const board = getPcbBoard(circuitJson);
3949
4317
  minClearance ??= getBoardDrcValue(board, "min_pad_edge_to_pad_edge_clearance") ?? jlcMinTolerances.min_pad_edge_to_pad_edge_clearance;
3950
- connMap ??= getFullConnectivityMapFromCircuitJson9(circuitJson);
4318
+ connMap ??= getFullConnectivityMapFromCircuitJson10(circuitJson);
3951
4319
  const spatialIndex = new SpatialObjectIndex({
3952
4320
  objects: pads,
3953
4321
  getBounds: getPadBounds,
@@ -3978,7 +4346,7 @@ function checkPadPadClearance(circuitJson, {
3978
4346
  type: "pcb_pad_pad_clearance_error",
3979
4347
  pcb_pad_pad_clearance_error_id: `pad_pad_clearance_${pairId}`,
3980
4348
  error_type: "pcb_pad_pad_clearance_error",
3981
- message: `Pads ${getReadableNameForElement6(circuitJson, padAId)} and ${getReadableNameForElement6(circuitJson, padBId)} are too close (clearance: ${formatMm(gap)}, minimum: ${formatMm(minClearance)})`,
4349
+ message: `Pads ${getReadableNameForElement7(circuitJson, padAId)} and ${getReadableNameForElement7(circuitJson, padBId)} are too close (clearance: ${formatMm(gap)}, minimum: ${formatMm(minClearance)})`,
3982
4350
  pcb_pad_ids: [padAId, padBId],
3983
4351
  minimum_clearance: minClearance,
3984
4352
  actual_clearance: gap,
@@ -3998,11 +4366,11 @@ function checkPadPadClearance(circuitJson, {
3998
4366
  // lib/check-pad-trace-clearance.ts
3999
4367
  import {
4000
4368
  getPrimaryId as getPrimaryId7,
4001
- getReadableNameForElement as getReadableNameForElement7
4369
+ getReadableNameForElement as getReadableNameForElement8
4002
4370
  } from "@tscircuit/circuit-json-util";
4003
4371
  import { formatMm as formatMm2 } from "format-si-unit";
4004
4372
  import {
4005
- getFullConnectivityMapFromCircuitJson as getFullConnectivityMapFromCircuitJson10
4373
+ getFullConnectivityMapFromCircuitJson as getFullConnectivityMapFromCircuitJson11
4006
4374
  } from "circuit-json-to-connectivity-map";
4007
4375
  function checkPadTraceClearance(circuitJson, {
4008
4376
  connMap,
@@ -4013,7 +4381,7 @@ function checkPadTraceClearance(circuitJson, {
4013
4381
  if (pads.length === 0 || segments.length === 0) return [];
4014
4382
  const board = getPcbBoard(circuitJson);
4015
4383
  minClearance ??= getBoardDrcValue(board, "min_trace_to_pad_edge_clearance") ?? jlcMinTolerances.min_trace_to_pad_edge_clearance;
4016
- connMap ??= getFullConnectivityMapFromCircuitJson10(circuitJson);
4384
+ connMap ??= getFullConnectivityMapFromCircuitJson11(circuitJson);
4017
4385
  const spatialIndex = new SpatialObjectIndex({
4018
4386
  objects: pads,
4019
4387
  getBounds: getPadBounds,
@@ -4043,7 +4411,7 @@ function checkPadTraceClearance(circuitJson, {
4043
4411
  type: "pcb_pad_trace_clearance_error",
4044
4412
  pcb_pad_trace_clearance_error_id: `pad_trace_clearance_${pairId}`,
4045
4413
  error_type: "pcb_pad_trace_clearance_error",
4046
- message: `Pad ${getReadableNameForElement7(circuitJson, padId)} and trace ${getReadableNameForElement7(circuitJson, segment.pcb_trace_id)} are too close (clearance: ${formatMm2(gap)}, minimum: ${formatMm2(minClearance)})`,
4414
+ message: `Pad ${getReadableNameForElement8(circuitJson, padId)} and trace ${getReadableNameForElement8(circuitJson, segment.pcb_trace_id)} are too close (clearance: ${formatMm2(gap)}, minimum: ${formatMm2(minClearance)})`,
4047
4415
  pcb_pad_id: padId,
4048
4416
  pcb_trace_id: segment.pcb_trace_id,
4049
4417
  minimum_clearance: minClearance,
@@ -4060,10 +4428,10 @@ function checkPadTraceClearance(circuitJson, {
4060
4428
  }
4061
4429
 
4062
4430
  // lib/check-via-trace-clearance.ts
4063
- import { getReadableNameForElement as getReadableNameForElement8 } from "@tscircuit/circuit-json-util";
4431
+ import { getReadableNameForElement as getReadableNameForElement9 } from "@tscircuit/circuit-json-util";
4064
4432
  import { formatMm as formatMm3 } from "format-si-unit";
4065
4433
  import {
4066
- getFullConnectivityMapFromCircuitJson as getFullConnectivityMapFromCircuitJson11
4434
+ getFullConnectivityMapFromCircuitJson as getFullConnectivityMapFromCircuitJson12
4067
4435
  } from "circuit-json-to-connectivity-map";
4068
4436
  function checkViaTraceClearance(circuitJson, {
4069
4437
  connMap,
@@ -4074,7 +4442,7 @@ function checkViaTraceClearance(circuitJson, {
4074
4442
  if (vias.length === 0 || segments.length === 0) return [];
4075
4443
  const board = getPcbBoard(circuitJson);
4076
4444
  minClearance ??= getBoardDrcValue(board, "min_trace_to_pad_edge_clearance") ?? jlcMinTolerances.min_trace_to_pad_edge_clearance;
4077
- connMap ??= getFullConnectivityMapFromCircuitJson11(circuitJson);
4445
+ connMap ??= getFullConnectivityMapFromCircuitJson12(circuitJson);
4078
4446
  const errors = /* @__PURE__ */ new Map();
4079
4447
  const overlappingPairIds = /* @__PURE__ */ new Set();
4080
4448
  for (const via of vias) {
@@ -4095,7 +4463,7 @@ function checkViaTraceClearance(circuitJson, {
4095
4463
  type: "pcb_via_trace_clearance_error",
4096
4464
  pcb_via_trace_clearance_error_id: `via_trace_clearance_${pairId}`,
4097
4465
  error_type: "pcb_via_trace_clearance_error",
4098
- message: `Via ${getReadableNameForElement8(circuitJson, via.pcb_via_id)} and trace ${getReadableNameForElement8(circuitJson, segment.pcb_trace_id)} are too close (clearance: ${formatMm3(gap)}, minimum: ${formatMm3(minClearance)})`,
4466
+ message: `Via ${getReadableNameForElement9(circuitJson, via.pcb_via_id)} and trace ${getReadableNameForElement9(circuitJson, segment.pcb_trace_id)} are too close (clearance: ${formatMm3(gap)}, minimum: ${formatMm3(minClearance)})`,
4099
4467
  pcb_via_id: via.pcb_via_id,
4100
4468
  pcb_trace_id: segment.pcb_trace_id,
4101
4469
  minimum_clearance: minClearance,
@@ -4114,11 +4482,11 @@ function checkViaTraceClearance(circuitJson, {
4114
4482
  // lib/check-via-pad-clearance.ts
4115
4483
  import {
4116
4484
  getPrimaryId as getPrimaryId8,
4117
- getReadableNameForElement as getReadableNameForElement9
4485
+ getReadableNameForElement as getReadableNameForElement10
4118
4486
  } from "@tscircuit/circuit-json-util";
4119
4487
  import { formatMm as formatMm4 } from "format-si-unit";
4120
4488
  import {
4121
- getFullConnectivityMapFromCircuitJson as getFullConnectivityMapFromCircuitJson12
4489
+ getFullConnectivityMapFromCircuitJson as getFullConnectivityMapFromCircuitJson13
4122
4490
  } from "circuit-json-to-connectivity-map";
4123
4491
  function checkViaPadClearance(circuitJson, {
4124
4492
  connMap,
@@ -4131,7 +4499,7 @@ function checkViaPadClearance(circuitJson, {
4131
4499
  if (vias.length === 0 || pads.length === 0) return [];
4132
4500
  const board = getPcbBoard(circuitJson);
4133
4501
  const requiredClearance = minClearance ?? getBoardDrcValue(board, "min_pad_edge_to_pad_edge_clearance") ?? jlcMinTolerances.min_pad_edge_to_pad_edge_clearance;
4134
- connMap ??= getFullConnectivityMapFromCircuitJson12(circuitJson);
4502
+ connMap ??= getFullConnectivityMapFromCircuitJson13(circuitJson);
4135
4503
  const padIndex = new SpatialObjectIndex({
4136
4504
  objects: pads,
4137
4505
  getBounds: getPadBounds,
@@ -4157,7 +4525,7 @@ function checkViaPadClearance(circuitJson, {
4157
4525
  type: "pcb_pad_pad_clearance_error",
4158
4526
  pcb_pad_pad_clearance_error_id: `via_pad_clearance_${via.pcb_via_id}_${padId}`,
4159
4527
  error_type: "pcb_pad_pad_clearance_error",
4160
- message: `Via ${getReadableNameForElement9(circuitJson, via.pcb_via_id)} and pad ${getReadableNameForElement9(circuitJson, padId)} are too close (clearance: ${formatMm4(gap)}, minimum: ${formatMm4(requiredClearance)})`,
4528
+ message: `Via ${getReadableNameForElement10(circuitJson, via.pcb_via_id)} and pad ${getReadableNameForElement10(circuitJson, padId)} are too close (clearance: ${formatMm4(gap)}, minimum: ${formatMm4(requiredClearance)})`,
4161
4529
  pcb_pad_ids: [via.pcb_via_id, padId],
4162
4530
  minimum_clearance: requiredClearance,
4163
4531
  actual_clearance: gap,
@@ -4170,7 +4538,7 @@ function checkViaPadClearance(circuitJson, {
4170
4538
 
4171
4539
  // lib/check-vias-in-pads.ts
4172
4540
  import { getPrimaryId as getPrimaryId9 } from "@tscircuit/circuit-json-util";
4173
- import { getFullConnectivityMapFromCircuitJson as getFullConnectivityMapFromCircuitJson13 } from "circuit-json-to-connectivity-map";
4541
+ import { getFullConnectivityMapFromCircuitJson as getFullConnectivityMapFromCircuitJson14 } from "circuit-json-to-connectivity-map";
4174
4542
  function checkViasInPads(circuitJson) {
4175
4543
  const board = getPcbBoard(circuitJson);
4176
4544
  if (board && "is_via_in_pad_allowed" in board && board.is_via_in_pad_allowed === true) {
@@ -4181,7 +4549,7 @@ function checkViasInPads(circuitJson) {
4181
4549
  );
4182
4550
  const pads = getPads(circuitJson);
4183
4551
  if (vias.length === 0 || pads.length === 0) return [];
4184
- const connMap = getFullConnectivityMapFromCircuitJson13(circuitJson);
4552
+ const connMap = getFullConnectivityMapFromCircuitJson14(circuitJson);
4185
4553
  const padOrdinals = new Map(
4186
4554
  pads.map((pad, index) => [getPrimaryId9(pad), index])
4187
4555
  );
@@ -5119,22 +5487,22 @@ function checkCourtyardOverlap(circuitJson) {
5119
5487
  // lib/check-testpoint-accessibility.ts
5120
5488
  import { isPointInsidePolygon as isPointInsidePolygon3 } from "@tscircuit/math-utils";
5121
5489
  var isCourtyardElement3 = (element) => element.type === "pcb_courtyard_circle" || element.type === "pcb_courtyard_outline" || element.type === "pcb_courtyard_polygon" || element.type === "pcb_courtyard_rect";
5122
- var isPointInsideCourtyard = (point, courtyard) => {
5490
+ var isPointInsideCourtyard = (point2, courtyard) => {
5123
5491
  if (courtyard.type === "pcb_courtyard_circle") {
5124
- const dx = point.x - courtyard.center.x;
5125
- const dy = point.y - courtyard.center.y;
5492
+ const dx = point2.x - courtyard.center.x;
5493
+ const dy = point2.y - courtyard.center.y;
5126
5494
  return dx * dx + dy * dy <= courtyard.radius * courtyard.radius;
5127
5495
  }
5128
5496
  if (courtyard.type === "pcb_courtyard_rect") {
5129
5497
  const angle = -1 * (courtyard.ccw_rotation ?? 0) * Math.PI / 180;
5130
- const dx = point.x - courtyard.center.x;
5131
- const dy = point.y - courtyard.center.y;
5498
+ const dx = point2.x - courtyard.center.x;
5499
+ const dy = point2.y - courtyard.center.y;
5132
5500
  const localX = dx * Math.cos(angle) - dy * Math.sin(angle);
5133
5501
  const localY = dx * Math.sin(angle) + dy * Math.cos(angle);
5134
5502
  return Math.abs(localX) <= courtyard.width / 2 && Math.abs(localY) <= courtyard.height / 2;
5135
5503
  }
5136
5504
  const polygon = courtyard.type === "pcb_courtyard_polygon" ? courtyard.points : courtyard.outline;
5137
- return isPointInsidePolygon3(point, polygon);
5505
+ return isPointInsidePolygon3(point2, polygon);
5138
5506
  };
5139
5507
  var getPcbComponentName = (circuitJson, pcbComponentId) => {
5140
5508
  const pcbComponent = circuitJson.find(
@@ -5234,6 +5602,7 @@ async function runAllRoutingChecks(circuitJson) {
5234
5602
  ...checkPcbTraceLengths(circuitJson),
5235
5603
  ...checkPcbTraceViaCounts(circuitJson),
5236
5604
  ...checkEachPcbTraceNonOverlapping(circuitJson),
5605
+ ...checkCopperPourShorts(circuitJson),
5237
5606
  ...checkPadTraceClearance(circuitJson),
5238
5607
  ...checkViaTraceClearance(circuitJson),
5239
5608
  ...checkViaPadClearance(circuitJson),
@@ -5256,6 +5625,7 @@ export {
5256
5625
  NetManager,
5257
5626
  checkAllPinsInComponentAreUnderspecified,
5258
5627
  checkConnectorAccessibleOrientation,
5628
+ checkCopperPourShorts,
5259
5629
  checkCopperToBoardEdgeClearance,
5260
5630
  checkDifferentNetViaSpacing,
5261
5631
  checkEachPcbPortConnectedToPcbTraces,