@tscircuit/copper-pour-solver 0.0.10 → 0.0.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -11,6 +11,7 @@ interface InputPourRegion {
11
11
  padMargin: number;
12
12
  traceMargin: number;
13
13
  board_edge_margin?: number;
14
+ cutout_margin?: number;
14
15
  }
15
16
  interface BaseInputPad {
16
17
  padId: string;
@@ -32,7 +33,11 @@ interface InputTracePad extends BaseInputPad {
32
33
  width: number;
33
34
  segments: Point[];
34
35
  }
35
- type InputPad = InputRectPad | InputCircularPad | InputTracePad;
36
+ interface InputPolygonPad extends BaseInputPad {
37
+ shape: "polygon";
38
+ points: Point[];
39
+ }
40
+ type InputPad = InputRectPad | InputCircularPad | InputTracePad | InputPolygonPad;
36
41
  interface InputProblem {
37
42
  regionsForPour: InputPourRegion[];
38
43
  pads: InputPad[];
@@ -54,6 +59,7 @@ declare const convertCircuitJsonToInputProblem: (circuitJson: AnyCircuitElement[
54
59
  pad_margin: number;
55
60
  trace_margin: number;
56
61
  board_edge_margin?: number;
62
+ cutout_margin?: number;
57
63
  }) => InputProblem;
58
64
 
59
- export { type BaseInputPad, CopperPourPipelineSolver, type InputCircularPad, type InputPad, type InputPourRegion, type InputProblem, type InputRectPad, type InputTracePad, type PipelineOutput, convertCircuitJsonToInputProblem };
65
+ export { type BaseInputPad, CopperPourPipelineSolver, type InputCircularPad, type InputPad, type InputPolygonPad, type InputPourRegion, type InputProblem, type InputRectPad, type InputTracePad, type PipelineOutput, convertCircuitJsonToInputProblem };
package/dist/index.js CHANGED
@@ -57,9 +57,10 @@ var circleToPolygon = (circle, numSegments = 32) => {
57
57
  var isRectPad = (pad) => pad.shape === "rect";
58
58
  var isTracePad = (pad) => pad.shape === "trace";
59
59
  var isCircularPad = (pad) => pad.shape === "circle";
60
+ var isPolygonPad = (pad) => pad.shape === "polygon";
60
61
  var processObstaclesForPour = (pads, pourConnectivityKey, margins, boardOutline) => {
61
62
  const polygonsToSubtract = [];
62
- const { padMargin, traceMargin, board_edge_margin } = margins;
63
+ const { padMargin, traceMargin, board_edge_margin, cutoutMargin } = margins;
63
64
  if (boardOutline && boardOutline.length > 0 && board_edge_margin && board_edge_margin > 0) {
64
65
  const boardPoly = new Flatten3.Polygon(
65
66
  boardOutline.map((p) => Flatten3.point(p.x, p.y))
@@ -123,9 +124,18 @@ var processObstaclesForPour = (pads, pourConnectivityKey, margins, boardOutline)
123
124
  if (isOnNet) {
124
125
  continue;
125
126
  }
127
+ const isHoleOrCutout = pad.connectivityKey.startsWith("hole:") || pad.connectivityKey.startsWith("cutout:");
126
128
  if (isCircularPad(pad)) {
127
- const isHole = pad.connectivityKey.startsWith("hole:");
128
- const margin = isHole ? 0 : padMargin;
129
+ const margin = isHoleOrCutout ? cutoutMargin ?? 0 : padMargin;
130
+ if (isHoleOrCutout) {
131
+ console.log(
132
+ `Applying cutout margin to circular pad/hole/cutout: ${pad.padId}, margin: ${margin}`
133
+ );
134
+ } else {
135
+ console.log(
136
+ `Applying pad margin to circular pad: ${pad.padId}, margin: ${margin}`
137
+ );
138
+ }
129
139
  const circle = new Flatten3.Circle(
130
140
  new Flatten3.Point(pad.x, pad.y),
131
141
  pad.radius + margin
@@ -134,16 +144,83 @@ var processObstaclesForPour = (pads, pourConnectivityKey, margins, boardOutline)
134
144
  continue;
135
145
  }
136
146
  if (isRectPad(pad)) {
147
+ const margin = isHoleOrCutout ? cutoutMargin ?? 0 : padMargin;
148
+ if (isHoleOrCutout) {
149
+ console.log(
150
+ `Applying cutout margin to rect pad/cutout: ${pad.padId}, margin: ${margin}`
151
+ );
152
+ } else {
153
+ console.log(
154
+ `Applying pad margin to rect pad: ${pad.padId}, margin: ${margin}`
155
+ );
156
+ }
137
157
  const { bounds } = pad;
138
158
  const b = new Flatten3.Box(
139
- bounds.minX - padMargin,
140
- bounds.minY - padMargin,
141
- bounds.maxX + padMargin,
142
- bounds.maxY + padMargin
159
+ bounds.minX - margin,
160
+ bounds.minY - margin,
161
+ bounds.maxX + margin,
162
+ bounds.maxY + margin
143
163
  );
144
164
  polygonsToSubtract.push(new Flatten3.Polygon(b.toPoints()));
145
165
  continue;
146
166
  }
167
+ if (isPolygonPad(pad)) {
168
+ const margin = isHoleOrCutout ? cutoutMargin ?? 0 : 0;
169
+ if (isHoleOrCutout) {
170
+ console.log(
171
+ `Applying cutout margin to polygon cutout: ${pad.padId}, margin: ${margin}`
172
+ );
173
+ }
174
+ const seen = /* @__PURE__ */ new Set();
175
+ const uniquePoints = pad.points.filter((p) => {
176
+ const key = `${p.x},${p.y}`;
177
+ if (seen.has(key)) {
178
+ console.log(
179
+ `Duplicate point detected and removed for ${pad.padId}: (${p.x}, ${p.y})`
180
+ );
181
+ return false;
182
+ }
183
+ seen.add(key);
184
+ return true;
185
+ });
186
+ if (uniquePoints.length < 3) continue;
187
+ const polygon = new Flatten3.Polygon(
188
+ uniquePoints.map((p) => Flatten3.point(p.x, p.y))
189
+ );
190
+ if (Math.abs(polygon.area()) < 1e-9) continue;
191
+ if (margin <= 0) {
192
+ polygonsToSubtract.push(polygon);
193
+ continue;
194
+ }
195
+ if (polygon.area() > 0) {
196
+ polygon.reverse();
197
+ }
198
+ const offsetLines = [];
199
+ const polygonVertices = polygon.vertices;
200
+ for (let i = 0; i < polygonVertices.length; i++) {
201
+ const p1 = polygonVertices[i];
202
+ const p2 = polygonVertices[(i + 1) % polygonVertices.length];
203
+ const segment = Flatten3.segment(p1, p2);
204
+ if (segment.length === 0) continue;
205
+ const line = Flatten3.line(segment.start, segment.end);
206
+ const norm = line.norm;
207
+ const offsetLine = line.translate(norm.multiply(-margin));
208
+ offsetLines.push(offsetLine);
209
+ }
210
+ const newPolygonPoints = [];
211
+ for (let i = 0; i < offsetLines.length; i++) {
212
+ const line1 = offsetLines[i];
213
+ const line2 = offsetLines[(i + 1) % offsetLines.length];
214
+ const ip = line1.intersect(line2);
215
+ if (ip.length > 0) {
216
+ newPolygonPoints.push(ip[0]);
217
+ }
218
+ }
219
+ if (newPolygonPoints.length >= 3) {
220
+ polygonsToSubtract.push(new Flatten3.Polygon(newPolygonPoints));
221
+ }
222
+ continue;
223
+ }
147
224
  if (isTracePad(pad)) {
148
225
  for (const segment of pad.segments) {
149
226
  const circle = new Flatten3.Circle(
@@ -254,7 +331,8 @@ var CopperPourPipelineSolver = class extends BasePipelineSolver {
254
331
  {
255
332
  padMargin: region.padMargin,
256
333
  traceMargin: region.traceMargin,
257
- board_edge_margin: region.board_edge_margin
334
+ board_edge_margin: region.board_edge_margin,
335
+ cutoutMargin: region.cutout_margin
258
336
  },
259
337
  region.outline
260
338
  );
@@ -410,6 +488,49 @@ var convertCircuitJsonToInputProblem = (circuitJson, options) => {
410
488
  y: hole.y,
411
489
  radius: hole.hole_diameter / 2
412
490
  });
491
+ } else if (elm.type === "pcb_cutout") {
492
+ const cutout = elm;
493
+ console.log(
494
+ `Processing cutout: ${cutout.pcb_cutout_id} (shape: ${cutout.shape})`
495
+ );
496
+ if (cutout.shape === "rect") {
497
+ pads.push({
498
+ shape: "rect",
499
+ padId: cutout.pcb_cutout_id,
500
+ layer: options.layer,
501
+ // through-all
502
+ connectivityKey: `cutout:${cutout.pcb_cutout_id}`,
503
+ bounds: {
504
+ minX: cutout.center.x - cutout.width / 2,
505
+ minY: cutout.center.y - cutout.height / 2,
506
+ maxX: cutout.center.x + cutout.width / 2,
507
+ maxY: cutout.center.y + cutout.height / 2
508
+ }
509
+ });
510
+ } else if (cutout.shape === "circle") {
511
+ pads.push({
512
+ shape: "circle",
513
+ padId: cutout.pcb_cutout_id,
514
+ layer: options.layer,
515
+ // through-all
516
+ connectivityKey: `cutout:${cutout.pcb_cutout_id}`,
517
+ x: cutout.center.x,
518
+ y: cutout.center.y,
519
+ radius: cutout.radius
520
+ });
521
+ } else if (cutout.shape === "polygon") {
522
+ console.log(
523
+ `Polygon cutout points for ${cutout.pcb_cutout_id}: ${JSON.stringify(cutout.points)}`
524
+ );
525
+ pads.push({
526
+ shape: "polygon",
527
+ padId: cutout.pcb_cutout_id,
528
+ layer: options.layer,
529
+ // through-all
530
+ connectivityKey: `cutout:${cutout.pcb_cutout_id}`,
531
+ points: cutout.points
532
+ });
533
+ }
413
534
  } else if (elm.type === "pcb_via") {
414
535
  const via = elm;
415
536
  if (!via.layers.includes(options.layer)) continue;
@@ -478,7 +599,8 @@ var convertCircuitJsonToInputProblem = (circuitJson, options) => {
478
599
  connectivityKey: options.pour_connectivity_key,
479
600
  padMargin: options.pad_margin,
480
601
  traceMargin: options.trace_margin,
481
- board_edge_margin: options.board_edge_margin ?? 0
602
+ board_edge_margin: options.board_edge_margin ?? 0,
603
+ cutout_margin: options.cutout_margin
482
604
  }
483
605
  ];
484
606
  return {
@@ -16,6 +16,7 @@ import type {
16
16
  import type {
17
17
  InputCircularPad,
18
18
  InputPad,
19
+ InputPolygonPad,
19
20
  InputProblem,
20
21
  InputRectPad,
21
22
  InputTracePad,
@@ -29,6 +30,7 @@ export const convertCircuitJsonToInputProblem = (
29
30
  pad_margin: number
30
31
  trace_margin: number
31
32
  board_edge_margin?: number
33
+ cutout_margin?: number
32
34
  },
33
35
  ): InputProblem => {
34
36
  const source_ports = circuitJson.filter(
@@ -180,6 +182,46 @@ export const convertCircuitJsonToInputProblem = (
180
182
  y: hole.y,
181
183
  radius: hole.hole_diameter / 2,
182
184
  } as InputCircularPad)
185
+ } else if (elm.type === "pcb_cutout") {
186
+ const cutout = elm as any
187
+ console.log(
188
+ `Processing cutout: ${cutout.pcb_cutout_id} (shape: ${cutout.shape})`,
189
+ )
190
+ if (cutout.shape === "rect") {
191
+ pads.push({
192
+ shape: "rect",
193
+ padId: cutout.pcb_cutout_id,
194
+ layer: options.layer, // through-all
195
+ connectivityKey: `cutout:${cutout.pcb_cutout_id}`,
196
+ bounds: {
197
+ minX: cutout.center.x - cutout.width / 2,
198
+ minY: cutout.center.y - cutout.height / 2,
199
+ maxX: cutout.center.x + cutout.width / 2,
200
+ maxY: cutout.center.y + cutout.height / 2,
201
+ },
202
+ } as InputRectPad)
203
+ } else if (cutout.shape === "circle") {
204
+ pads.push({
205
+ shape: "circle",
206
+ padId: cutout.pcb_cutout_id,
207
+ layer: options.layer, // through-all
208
+ connectivityKey: `cutout:${cutout.pcb_cutout_id}`,
209
+ x: cutout.center.x,
210
+ y: cutout.center.y,
211
+ radius: cutout.radius,
212
+ } as InputCircularPad)
213
+ } else if (cutout.shape === "polygon") {
214
+ console.log(
215
+ `Polygon cutout points for ${cutout.pcb_cutout_id}: ${JSON.stringify(cutout.points)}`,
216
+ )
217
+ pads.push({
218
+ shape: "polygon",
219
+ padId: cutout.pcb_cutout_id,
220
+ layer: options.layer, // through-all
221
+ connectivityKey: `cutout:${cutout.pcb_cutout_id}`,
222
+ points: cutout.points,
223
+ } as InputPolygonPad)
224
+ }
183
225
  } else if (elm.type === "pcb_via") {
184
226
  const via = elm as PcbVia
185
227
  if (!via.layers.includes(options.layer)) continue
@@ -257,6 +299,7 @@ export const convertCircuitJsonToInputProblem = (
257
299
  padMargin: options.pad_margin,
258
300
  traceMargin: options.trace_margin,
259
301
  board_edge_margin: options.board_edge_margin ?? 0,
302
+ cutout_margin: options.cutout_margin,
260
303
  },
261
304
  ]
262
305
 
@@ -29,6 +29,7 @@ export class CopperPourPipelineSolver extends BasePipelineSolver<InputProblem> {
29
29
  padMargin: region.padMargin,
30
30
  traceMargin: region.traceMargin,
31
31
  board_edge_margin: region.board_edge_margin,
32
+ cutoutMargin: region.cutout_margin,
32
33
  },
33
34
  region.outline,
34
35
  )
@@ -3,6 +3,7 @@ import type { Point } from "@tscircuit/math-utils"
3
3
  import type {
4
4
  InputCircularPad,
5
5
  InputPad,
6
+ InputPolygonPad,
6
7
  InputRectPad,
7
8
  InputTracePad,
8
9
  } from "lib/types"
@@ -17,6 +18,8 @@ const isTracePad = (pad: InputPad): pad is InputTracePad =>
17
18
  pad.shape === "trace"
18
19
  const isCircularPad = (pad: InputPad): pad is InputCircularPad =>
19
20
  pad.shape === "circle"
21
+ const isPolygonPad = (pad: InputPad): pad is InputPolygonPad =>
22
+ pad.shape === "polygon"
20
23
 
21
24
  export const processObstaclesForPour = (
22
25
  pads: InputPad[],
@@ -25,12 +28,13 @@ export const processObstaclesForPour = (
25
28
  padMargin: number
26
29
  traceMargin: number
27
30
  board_edge_margin?: number
31
+ cutoutMargin?: number
28
32
  },
29
33
  boardOutline?: Point[],
30
34
  ): ProcessedObstacles => {
31
35
  const polygonsToSubtract: Flatten.Polygon[] = []
32
36
 
33
- const { padMargin, traceMargin, board_edge_margin } = margins
37
+ const { padMargin, traceMargin, board_edge_margin, cutoutMargin } = margins
34
38
 
35
39
  if (
36
40
  boardOutline &&
@@ -120,9 +124,21 @@ export const processObstaclesForPour = (
120
124
  continue
121
125
  }
122
126
 
127
+ const isHoleOrCutout =
128
+ pad.connectivityKey.startsWith("hole:") ||
129
+ pad.connectivityKey.startsWith("cutout:")
130
+
123
131
  if (isCircularPad(pad)) {
124
- const isHole = pad.connectivityKey.startsWith("hole:")
125
- const margin = isHole ? 0 : padMargin
132
+ const margin = isHoleOrCutout ? (cutoutMargin ?? 0) : padMargin
133
+ if (isHoleOrCutout) {
134
+ console.log(
135
+ `Applying cutout margin to circular pad/hole/cutout: ${pad.padId}, margin: ${margin}`,
136
+ )
137
+ } else {
138
+ console.log(
139
+ `Applying pad margin to circular pad: ${pad.padId}, margin: ${margin}`,
140
+ )
141
+ }
126
142
  const circle = new Flatten.Circle(
127
143
  new Flatten.Point(pad.x, pad.y),
128
144
  pad.radius + margin,
@@ -132,17 +148,103 @@ export const processObstaclesForPour = (
132
148
  }
133
149
 
134
150
  if (isRectPad(pad)) {
151
+ const margin = isHoleOrCutout ? (cutoutMargin ?? 0) : padMargin
152
+ if (isHoleOrCutout) {
153
+ console.log(
154
+ `Applying cutout margin to rect pad/cutout: ${pad.padId}, margin: ${margin}`,
155
+ )
156
+ } else {
157
+ console.log(
158
+ `Applying pad margin to rect pad: ${pad.padId}, margin: ${margin}`,
159
+ )
160
+ }
135
161
  const { bounds } = pad
136
162
  const b = new Flatten.Box(
137
- bounds.minX - padMargin,
138
- bounds.minY - padMargin,
139
- bounds.maxX + padMargin,
140
- bounds.maxY + padMargin,
163
+ bounds.minX - margin,
164
+ bounds.minY - margin,
165
+ bounds.maxX + margin,
166
+ bounds.maxY + margin,
141
167
  )
142
168
  polygonsToSubtract.push(new Flatten.Polygon(b.toPoints()))
143
169
  continue
144
170
  }
145
171
 
172
+ if (isPolygonPad(pad)) {
173
+ const margin = isHoleOrCutout ? (cutoutMargin ?? 0) : 0
174
+ if (isHoleOrCutout) {
175
+ console.log(
176
+ `Applying cutout margin to polygon cutout: ${pad.padId}, margin: ${margin}`,
177
+ )
178
+ }
179
+
180
+ const seen = new Set<string>()
181
+ const uniquePoints = pad.points.filter((p) => {
182
+ const key = `${p.x},${p.y}`
183
+ if (seen.has(key)) {
184
+ console.log(
185
+ `Duplicate point detected and removed for ${pad.padId}: (${p.x}, ${p.y})`,
186
+ )
187
+ return false
188
+ }
189
+ seen.add(key)
190
+ return true
191
+ })
192
+
193
+ if (uniquePoints.length < 3) continue
194
+
195
+ const polygon = new Flatten.Polygon(
196
+ uniquePoints.map((p) => Flatten.point(p.x, p.y)),
197
+ )
198
+
199
+ if (Math.abs(polygon.area()) < 1e-9) continue
200
+
201
+ if (margin <= 0) {
202
+ polygonsToSubtract.push(polygon)
203
+ continue
204
+ }
205
+
206
+ // Ensure polygon is CCW for consistent normal direction.
207
+ // In flatten-js, CCW corresponds to a negative area.
208
+ if (polygon.area() > 0) {
209
+ polygon.reverse()
210
+ }
211
+
212
+ const offsetLines: Flatten.Line[] = []
213
+ const polygonVertices = polygon.vertices
214
+ for (let i = 0; i < polygonVertices.length; i++) {
215
+ const p1 = polygonVertices[i]!
216
+ const p2 = polygonVertices[(i + 1) % polygonVertices.length]!
217
+
218
+ const segment = Flatten.segment(p1, p2)
219
+
220
+ if (segment.length === 0) continue
221
+
222
+ const line = Flatten.line(segment.start, segment.end)
223
+
224
+ // For a CCW polygon, the normal (rotated +90deg, i.e. "left") points inward.
225
+ // We must translate outward, so we use a negative margin.
226
+ const norm = line.norm
227
+ const offsetLine = line.translate(norm.multiply(-margin))
228
+ offsetLines.push(offsetLine)
229
+ }
230
+
231
+ const newPolygonPoints: Flatten.Point[] = []
232
+ for (let i = 0; i < offsetLines.length; i++) {
233
+ const line1 = offsetLines[i]!
234
+ const line2 = offsetLines[(i + 1) % offsetLines.length]!
235
+
236
+ const ip = line1.intersect(line2)
237
+ if (ip.length > 0) {
238
+ newPolygonPoints.push(ip[0]!)
239
+ }
240
+ }
241
+
242
+ if (newPolygonPoints.length >= 3) {
243
+ polygonsToSubtract.push(new Flatten.Polygon(newPolygonPoints))
244
+ }
245
+ continue
246
+ }
247
+
146
248
  if (isTracePad(pad)) {
147
249
  // Add circles for each vertex
148
250
  for (const segment of pad.segments) {
package/lib/types.ts CHANGED
@@ -10,6 +10,7 @@ export interface InputPourRegion {
10
10
  padMargin: number
11
11
  traceMargin: number
12
12
  board_edge_margin?: number
13
+ cutout_margin?: number
13
14
  }
14
15
 
15
16
  export interface BaseInputPad {
@@ -36,7 +37,16 @@ export interface InputTracePad extends BaseInputPad {
36
37
  segments: Point[]
37
38
  }
38
39
 
39
- export type InputPad = InputRectPad | InputCircularPad | InputTracePad
40
+ export interface InputPolygonPad extends BaseInputPad {
41
+ shape: "polygon"
42
+ points: Point[]
43
+ }
44
+
45
+ export type InputPad =
46
+ | InputRectPad
47
+ | InputCircularPad
48
+ | InputTracePad
49
+ | InputPolygonPad
40
50
 
41
51
  export interface InputProblem {
42
52
  regionsForPour: InputPourRegion[]
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tscircuit/copper-pour-solver",
3
3
  "main": "dist/index.js",
4
- "version": "0.0.10",
4
+ "version": "0.0.11",
5
5
  "scripts": {
6
6
  "format": "biome format . --write",
7
7
  "format:check": "biome format .",
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="800" height="600" data-software-used-string="@tscircuit/core@0.0.849"><style></style><rect class="boundary" x="0" y="0" fill="#000" width="800" height="600" data-type="pcb_background" data-pcb-layer="global"/><rect class="pcb-boundary" fill="none" stroke="#fff" stroke-width="0.3" x="150" y="50" width="500" height="500" data-type="pcb_boundary" data-pcb-layer="global"/><rect class="pcb-pad" fill="rgb(77, 127, 196)" x="284" y="284" width="27" height="32" data-type="pcb_smtpad" data-pcb-layer="bottom"/><rect class="pcb-pad" fill="rgb(77, 127, 196)" x="233" y="284" width="27" height="32" data-type="pcb_smtpad" data-pcb-layer="bottom"/><path class="pcb-trace" stroke="rgb(77, 127, 196)" fill="none" d="M 297.5 300 L 297.5 378.95223661654137" stroke-width="7.5" stroke-linecap="round" stroke-linejoin="round" shape-rendering="crispEdges" data-type="pcb_trace" data-pcb-layer="bottom"/><path class="pcb-trace" stroke="rgb(77, 127, 196)" fill="none" d="M 297.5 378.95223661654137 L 303.5414925183353 384.99372913487673" stroke-width="7.5" stroke-linecap="round" stroke-linejoin="round" shape-rendering="crispEdges" data-type="pcb_trace" data-pcb-layer="bottom"/><path class="pcb-trace" stroke="rgb(77, 127, 196)" fill="none" d="M 303.5414925183353 384.99372913487673 L 306.25 393.75" stroke-width="7.5" stroke-linecap="round" stroke-linejoin="round" shape-rendering="crispEdges" data-type="pcb_trace" data-pcb-layer="bottom"/><path class="pcb-trace" stroke="rgb(77, 127, 196)" fill="none" d="M 306.25 393.75 L 306.25 393.75" stroke-width="7.5" stroke-linecap="round" stroke-linejoin="round" shape-rendering="crispEdges" data-type="pcb_trace" data-pcb-layer="bottom"/><path class="pcb-silkscreen pcb-silkscreen-bottom" d="M 246.5 264 L 321 264 L 321 336 L 246.5 336" fill="none" stroke="#5da9e9" stroke-width="5" stroke-linecap="round" stroke-linejoin="round" data-pcb-component-id="pcb_component_1" data-pcb-silkscreen-path-id="pcb_silkscreen_path_1" data-type="pcb_silkscreen_path" data-pcb-layer="bottom"/><text x="0" y="0" dx="0" dy="0" fill="#5da9e9" font-family="Arial, sans-serif" font-size="20" text-anchor="middle" dominant-baseline="central" transform="matrix(-1,0,0,1,272,239)" class="pcb-silkscreen-text pcb-silkscreen-bottom" data-pcb-silkscreen-text-id="pcb_component_1" stroke="none" data-type="pcb_silkscreen_text" data-pcb-layer="bottom">C1</text><path class="pcb-board" d="M 150 550 L 650 550 L 650 50 L 150 50 Z" fill="none" stroke="rgba(255, 255, 255, 0.5)" stroke-width="5" data-type="pcb_board" data-pcb-layer="board"/><rect class="pcb-pad" fill="rgb(200, 52, 52)" x="361" y="284" width="27" height="32" data-type="pcb_smtpad" data-pcb-layer="top"/><rect class="pcb-pad" fill="rgb(200, 52, 52)" x="412" y="284" width="27" height="32" data-type="pcb_smtpad" data-pcb-layer="top"/><path class="pcb-trace" stroke="rgb(200, 52, 52)" fill="none" d="M 306.25 393.75 L 306.25 393.75" stroke-width="7.5" stroke-linecap="round" stroke-linejoin="round" shape-rendering="crispEdges" data-type="pcb_trace" data-pcb-layer="top"/><path class="pcb-trace" stroke="rgb(200, 52, 52)" fill="none" d="M 306.25 393.75 L 306.25 368.25" stroke-width="7.5" stroke-linecap="round" stroke-linejoin="round" shape-rendering="crispEdges" data-type="pcb_trace" data-pcb-layer="top"/><path class="pcb-trace" stroke="rgb(200, 52, 52)" fill="none" d="M 306.25 368.25 L 374.5 300" stroke-width="7.5" stroke-linecap="round" stroke-linejoin="round" shape-rendering="crispEdges" data-type="pcb_trace" data-pcb-layer="top"/><path class="pcb-copper-pour pcb-copper-pour-brep" d="M 150 550 L 150 50 L 650 50 L 650 550 L 150 550 Z M 610 150 L 608.8471168241938 138.29458067903232 L 605.4327719506772 127.03899405809463 L 599.8881767381527 116.66578601882387 L 592.4264068711929 107.57359312880715 L 583.3342139811762 100.11182326184729 L 572.9610059419053 94.56722804932278 L 561.7054193209676 91.15288317580615 L 550 90 L 538.2945806790324 91.15288317580615 L 527.0389940580947 94.56722804932278 L 516.6657860188238 100.11182326184729 L 507.57359312880715 107.57359312880715 L 500.1118232618473 116.66578601882387 L 494.5672280493228 127.0389940580946 L 491.1528831758062 138.2945806790323 L 490 150 L 491.1528831758062 161.70541932096768 L 494.5672280493228 172.96100594190537 L 500.1118232618473 183.3342139811761 L 507.57359312880715 192.42640687119285 L 516.6657860188238 199.8881767381527 L 527.0389940580945 205.4327719506772 L 538.2945806790323 208.84711682419382 L 550 210 L 561.7054193209676 208.84711682419382 L 572.9610059419053 205.4327719506772 L 583.3342139811762 199.8881767381527 L 592.4264068711929 192.42640687119285 L 599.8881767381527 183.33421398117613 L 605.4327719506772 172.96100594190543 L 608.8471168241938 161.7054193209677 L 610 150 Z M 460 450 L 458.84711682419385 438.2945806790323 L 455.4327719506772 427.03899405809466 L 449.8881767381527 416.66578601882384 L 442.42640687119285 407.57359312880715 L 433.33421398117616 400.1118232618473 L 422.9610059419054 394.5672280493228 L 411.7054193209677 391.1528831758062 L 400 390 L 388.2945806790323 391.1528831758062 L 377.0389940580946 394.5672280493228 L 366.6657860188239 400.1118232618473 L 357.57359312880715 407.57359312880715 L 350.1118232618473 416.66578601882384 L 344.5672280493228 427.0389940580946 L 341.15288317580615 438.29458067903226 L 340 450 L 341.15288317580615 461.7054193209677 L 344.5672280493228 472.96100594190534 L 350.1118232618473 483.3342139811761 L 357.57359312880715 492.42640687119285 L 366.66578601882384 499.8881767381527 L 377.0389940580946 505.4327719506772 L 388.29458067903226 508.8471168241938 L 400 510 L 411.7054193209677 508.84711682419385 L 422.9610059419054 505.4327719506772 L 433.3342139811761 499.8881767381527 L 442.42640687119285 492.42640687119285 L 449.8881767381527 483.33421398117616 L 455.4327719506772 472.96100594190546 L 458.8471168241938 461.70541932096774 L 460 450 Z M 490 335 L 610 335 L 610 265 L 490 265 L 490 335 Z M 316.0805385563561 370.7938301144085 L 360.87436867076457 326 L 398 326 L 398 274 L 351 274 L 351 311.12563132923543 L 300.0628156646177 362.0628156646177 L 298.97464089235274 363.3887604610785 L 298.16605409052625 364.9015199668055 L 297.6681287964717 366.54295968235886 L 297.5 368.25 L 297.5 370.40515141679646 L 296.6829141908728 370.6530116872178 L 292.36074417450993 372.9632596924364 L 288.5723304703363 376.0723304703363 L 285.4632596924364 379.86074417450993 L 283.1530116872178 384.18291419087274 L 281.7303679899193 388.8727419495968 L 281.25 393.75 L 281.7303679899193 398.6272580504032 L 283.1530116872178 403.3170858091272 L 285.46325969243634 407.63925582549007 L 288.5723304703363 411.4276695296637 L 292.36074417450993 414.5367403075636 L 296.68291419087274 416.8469883127822 L 301.3727419495968 418.2696320100807 L 306.25 418.75 L 311.1272580504032 418.2696320100807 L 315.81708580912726 416.8469883127822 L 320.13925582549007 414.53674030756366 L 323.9276695296637 411.4276695296637 L 327.0367403075636 407.63925582549007 L 329.3469883127822 403.31708580912726 L 330.7696320100807 398.6272580504032 L 331.25 393.75 L 330.7696320100807 388.8727419495968 L 329.3469883127822 384.1829141908728 L 327.0367403075636 379.86074417450993 L 323.9276695296637 376.0723304703363 L 320.13925582549007 372.9632596924364 L 316.0805385563561 370.7938301144085 Z M 475.85786437626905 460 L 624.142135623731 460 L 550 385.85786437626905 L 475.85786437626905 460 Z" fill="rgb(200, 52, 52)" fill-rule="evenodd" fill-opacity="0.5" data-type="pcb_copper_pour" data-pcb-layer="top"/><path class="pcb-silkscreen pcb-silkscreen-top" d="M 425.5 264 L 351 264 L 351 336 L 425.5 336" fill="none" stroke="#f2eda1" stroke-width="5" stroke-linecap="round" stroke-linejoin="round" data-pcb-component-id="pcb_component_0" data-pcb-silkscreen-path-id="pcb_silkscreen_path_0" data-type="pcb_silkscreen_path" data-pcb-layer="top"/><text x="0" y="0" dx="0" dy="0" fill="#f2eda1" font-family="Arial, sans-serif" font-size="20" text-anchor="middle" dominant-baseline="central" transform="matrix(1,0,0,1,400,239)" class="pcb-silkscreen-text pcb-silkscreen-top" data-pcb-silkscreen-text-id="pcb_component_0" stroke="none" data-type="pcb_silkscreen_text" data-pcb-layer="top">R1</text><g data-type="pcb_via" data-pcb-layer="through"><circle class="pcb-hole-outer" fill="rgb(200, 52, 52)" cx="306.25" cy="393.75" r="15" data-type="pcb_via" data-pcb-layer="top"/><circle class="pcb-hole-inner" fill="#FF26E2" cx="306.25" cy="393.75" r="7.5" data-type="pcb_via" data-pcb-layer="drill"/></g><rect class="pcb-cutout pcb-cutout-rect" x="-50" y="-25" width="100" height="50" fill="#FF26E2" transform="matrix(1,0,0,1,550,300)" data-type="pcb_cutout" data-pcb-layer="drill"/><circle class="pcb-cutout pcb-cutout-circle" cx="550" cy="150" r="50" fill="#FF26E2" data-type="pcb_cutout" data-pcb-layer="drill"/><polygon class="pcb-cutout pcb-cutout-polygon" points="550,400 550,400 600,450 500,450" fill="#FF26E2" data-type="pcb_cutout" data-pcb-layer="drill"/><circle class="pcb-hole" cx="400" cy="450" r="50" fill="#FF26E2" data-type="pcb_hole" data-pcb-layer="drill"/></svg>
@@ -0,0 +1,772 @@
1
+ [
2
+ {
3
+ "type": "source_project_metadata",
4
+ "source_project_metadata_id": "source_project_metadata_0",
5
+ "software_used_string": "@tscircuit/core@0.0.849"
6
+ },
7
+ {
8
+ "type": "source_group",
9
+ "source_group_id": "source_group_0",
10
+ "is_subcircuit": true,
11
+ "was_automatically_named": true,
12
+ "subcircuit_id": "subcircuit_source_group_0"
13
+ },
14
+ {
15
+ "type": "source_net",
16
+ "source_net_id": "source_net_0",
17
+ "name": "GND",
18
+ "member_source_group_ids": [],
19
+ "is_ground": true,
20
+ "is_power": false,
21
+ "is_positive_voltage_source": false,
22
+ "subcircuit_id": "subcircuit_source_group_0",
23
+ "subcircuit_connectivity_map_key": "unnamedsubcircuit917_connectivity_net0"
24
+ },
25
+ {
26
+ "type": "source_port",
27
+ "source_port_id": "source_port_0",
28
+ "name": "pin1",
29
+ "pin_number": 1,
30
+ "port_hints": ["pin1", "anode", "pos", "left", "1"],
31
+ "source_component_id": "source_component_0",
32
+ "subcircuit_id": "subcircuit_source_group_0",
33
+ "subcircuit_connectivity_map_key": "unnamedsubcircuit917_connectivity_net1"
34
+ },
35
+ {
36
+ "type": "source_port",
37
+ "source_port_id": "source_port_1",
38
+ "name": "pin2",
39
+ "pin_number": 2,
40
+ "port_hints": ["pin2", "cathode", "neg", "right", "2"],
41
+ "source_component_id": "source_component_0",
42
+ "subcircuit_id": "subcircuit_source_group_0",
43
+ "subcircuit_connectivity_map_key": "unnamedsubcircuit917_connectivity_net0"
44
+ },
45
+ {
46
+ "type": "source_component",
47
+ "source_component_id": "source_component_0",
48
+ "ftype": "simple_resistor",
49
+ "name": "R1",
50
+ "supplier_part_numbers": {
51
+ "jlcpcb": ["C11702", "C25543", "C2906864"]
52
+ },
53
+ "resistance": 1000,
54
+ "display_resistance": "1kΩ",
55
+ "are_pins_interchangeable": true,
56
+ "source_group_id": "source_group_0"
57
+ },
58
+ {
59
+ "type": "source_port",
60
+ "source_port_id": "source_port_2",
61
+ "name": "pin1",
62
+ "pin_number": 1,
63
+ "port_hints": ["pin1", "pos", "anode", "1"],
64
+ "source_component_id": "source_component_1",
65
+ "subcircuit_id": "subcircuit_source_group_0",
66
+ "subcircuit_connectivity_map_key": "unnamedsubcircuit917_connectivity_net1"
67
+ },
68
+ {
69
+ "type": "source_port",
70
+ "source_port_id": "source_port_3",
71
+ "name": "pin2",
72
+ "pin_number": 2,
73
+ "port_hints": ["pin2", "neg", "cathode", "2"],
74
+ "source_component_id": "source_component_1",
75
+ "subcircuit_id": "subcircuit_source_group_0"
76
+ },
77
+ {
78
+ "type": "source_component",
79
+ "source_component_id": "source_component_1",
80
+ "ftype": "simple_capacitor",
81
+ "name": "C1",
82
+ "supplier_part_numbers": {
83
+ "jlcpcb": ["C1523", "C696907", "C5137470"]
84
+ },
85
+ "capacitance": 1e-9,
86
+ "display_capacitance": "1000pF",
87
+ "are_pins_interchangeable": true,
88
+ "source_group_id": "source_group_0"
89
+ },
90
+ {
91
+ "type": "source_net",
92
+ "source_net_id": "source_net_1",
93
+ "name": "VCC",
94
+ "member_source_group_ids": [],
95
+ "is_ground": false,
96
+ "is_power": true,
97
+ "is_positive_voltage_source": true,
98
+ "subcircuit_id": "subcircuit_source_group_0",
99
+ "subcircuit_connectivity_map_key": "unnamedsubcircuit917_connectivity_net1"
100
+ },
101
+ {
102
+ "type": "source_board",
103
+ "source_board_id": "source_board_0",
104
+ "source_group_id": "source_group_0"
105
+ },
106
+ {
107
+ "type": "source_trace",
108
+ "source_trace_id": "source_trace_0",
109
+ "connected_source_port_ids": ["source_port_1"],
110
+ "connected_source_net_ids": ["source_net_0"],
111
+ "subcircuit_id": "subcircuit_source_group_0",
112
+ "display_name": ".R1 > .pin2 to net.GND",
113
+ "subcircuit_connectivity_map_key": "unnamedsubcircuit917_connectivity_net0"
114
+ },
115
+ {
116
+ "type": "source_trace",
117
+ "source_trace_id": "source_trace_1",
118
+ "connected_source_port_ids": ["source_port_0"],
119
+ "connected_source_net_ids": ["source_net_1"],
120
+ "subcircuit_id": "subcircuit_source_group_0",
121
+ "display_name": ".R1 > .pin1 to net.VCC",
122
+ "subcircuit_connectivity_map_key": "unnamedsubcircuit917_connectivity_net1"
123
+ },
124
+ {
125
+ "type": "source_trace",
126
+ "source_trace_id": "source_trace_2",
127
+ "connected_source_port_ids": ["source_port_2"],
128
+ "connected_source_net_ids": ["source_net_1"],
129
+ "subcircuit_id": "subcircuit_source_group_0",
130
+ "max_length": null,
131
+ "display_name": ".C1 > .pin1 to net.VCC",
132
+ "subcircuit_connectivity_map_key": "unnamedsubcircuit917_connectivity_net1"
133
+ },
134
+ {
135
+ "type": "source_pin_missing_trace_warning",
136
+ "source_pin_missing_trace_warning_id": "source_pin_missing_trace_warning_0",
137
+ "message": "Port pin2 on C1 is missing a trace",
138
+ "source_component_id": "source_component_1",
139
+ "source_port_id": "source_port_3",
140
+ "subcircuit_id": "subcircuit_source_group_0",
141
+ "warning_type": "source_pin_missing_trace_warning"
142
+ },
143
+ {
144
+ "type": "schematic_component",
145
+ "schematic_component_id": "schematic_component_0",
146
+ "center": {
147
+ "x": 0,
148
+ "y": 0
149
+ },
150
+ "size": {
151
+ "width": 1.1,
152
+ "height": 0.388910699999999
153
+ },
154
+ "source_component_id": "source_component_0",
155
+ "is_box_with_pins": true,
156
+ "symbol_name": "boxresistor_right",
157
+ "symbol_display_value": "1kΩ",
158
+ "schematic_group_id": "schematic_group_0"
159
+ },
160
+ {
161
+ "type": "schematic_component",
162
+ "schematic_component_id": "schematic_component_1",
163
+ "center": {
164
+ "x": 0,
165
+ "y": -1.8144553499999994
166
+ },
167
+ "size": {
168
+ "width": 1.1,
169
+ "height": 0.84
170
+ },
171
+ "source_component_id": "source_component_1",
172
+ "is_box_with_pins": true,
173
+ "symbol_name": "capacitor_right",
174
+ "symbol_display_value": "1000pF",
175
+ "schematic_group_id": "schematic_group_0"
176
+ },
177
+ {
178
+ "type": "schematic_group",
179
+ "schematic_group_id": "schematic_group_0",
180
+ "is_subcircuit": true,
181
+ "subcircuit_id": "subcircuit_source_group_0",
182
+ "name": "unnamed_board1",
183
+ "center": {
184
+ "x": 0,
185
+ "y": 0
186
+ },
187
+ "width": 0,
188
+ "height": 0,
189
+ "schematic_component_ids": [],
190
+ "source_group_id": "source_group_0"
191
+ },
192
+ {
193
+ "type": "schematic_port",
194
+ "schematic_port_id": "schematic_port_0",
195
+ "schematic_component_id": "schematic_component_0",
196
+ "center": {
197
+ "x": -0.55,
198
+ "y": 0
199
+ },
200
+ "source_port_id": "source_port_0",
201
+ "facing_direction": "left",
202
+ "distance_from_component_edge": 0.4,
203
+ "pin_number": 1,
204
+ "display_pin_label": "anode",
205
+ "is_connected": true
206
+ },
207
+ {
208
+ "type": "schematic_port",
209
+ "schematic_port_id": "schematic_port_1",
210
+ "schematic_component_id": "schematic_component_0",
211
+ "center": {
212
+ "x": 0.55,
213
+ "y": 0
214
+ },
215
+ "source_port_id": "source_port_1",
216
+ "facing_direction": "right",
217
+ "distance_from_component_edge": 0.4,
218
+ "pin_number": 2,
219
+ "display_pin_label": "cathode",
220
+ "is_connected": false
221
+ },
222
+ {
223
+ "type": "schematic_port",
224
+ "schematic_port_id": "schematic_port_2",
225
+ "schematic_component_id": "schematic_component_1",
226
+ "center": {
227
+ "x": -0.55,
228
+ "y": -1.8144553499999994
229
+ },
230
+ "source_port_id": "source_port_2",
231
+ "facing_direction": "left",
232
+ "distance_from_component_edge": 0.4,
233
+ "pin_number": 1,
234
+ "display_pin_label": "pos",
235
+ "is_connected": true
236
+ },
237
+ {
238
+ "type": "schematic_port",
239
+ "schematic_port_id": "schematic_port_3",
240
+ "schematic_component_id": "schematic_component_1",
241
+ "center": {
242
+ "x": 0.55,
243
+ "y": -1.8144553499999994
244
+ },
245
+ "source_port_id": "source_port_3",
246
+ "facing_direction": "right",
247
+ "distance_from_component_edge": 0.4,
248
+ "pin_number": 2,
249
+ "display_pin_label": "neg",
250
+ "is_connected": false
251
+ },
252
+ {
253
+ "type": "schematic_trace",
254
+ "schematic_trace_id": "schematic_trace_0",
255
+ "source_trace_id": "solver_R1.1-C1.1",
256
+ "edges": [
257
+ {
258
+ "from": {
259
+ "x": -0.55,
260
+ "y": 0
261
+ },
262
+ "to": {
263
+ "x": -0.75,
264
+ "y": 0
265
+ }
266
+ },
267
+ {
268
+ "from": {
269
+ "x": -0.75,
270
+ "y": 0
271
+ },
272
+ "to": {
273
+ "x": -0.75,
274
+ "y": -1.8144553499999994
275
+ }
276
+ },
277
+ {
278
+ "from": {
279
+ "x": -0.75,
280
+ "y": -1.8144553499999994
281
+ },
282
+ "to": {
283
+ "x": -0.55,
284
+ "y": -1.8144553499999994
285
+ }
286
+ }
287
+ ],
288
+ "junctions": [],
289
+ "subcircuit_connectivity_map_key": "unnamedsubcircuit917_connectivity_net1"
290
+ },
291
+ {
292
+ "type": "schematic_net_label",
293
+ "schematic_net_label_id": "schematic_net_label_0",
294
+ "text": "VCC",
295
+ "anchor_position": {
296
+ "x": -0.75,
297
+ "y": 0
298
+ },
299
+ "center": {
300
+ "x": -0.75,
301
+ "y": 0.09
302
+ },
303
+ "anchor_side": "bottom",
304
+ "source_net_id": "source_net_1",
305
+ "symbol_name": "rail_up"
306
+ },
307
+ {
308
+ "type": "schematic_net_label",
309
+ "schematic_net_label_id": "schematic_net_label_1",
310
+ "text": "GND",
311
+ "anchor_position": {
312
+ "x": 0.55,
313
+ "y": 0
314
+ },
315
+ "center": {
316
+ "x": 0.7000000000000001,
317
+ "y": 0
318
+ },
319
+ "anchor_side": "left",
320
+ "source_net_id": "source_net_0"
321
+ },
322
+ {
323
+ "type": "pcb_component",
324
+ "pcb_component_id": "pcb_component_0",
325
+ "center": {
326
+ "x": 0,
327
+ "y": 0
328
+ },
329
+ "width": 1.56,
330
+ "height": 0.64,
331
+ "layer": "top",
332
+ "rotation": 0,
333
+ "source_component_id": "source_component_0",
334
+ "subcircuit_id": "subcircuit_source_group_0",
335
+ "do_not_place": false,
336
+ "obstructs_within_bounds": true
337
+ },
338
+ {
339
+ "type": "pcb_component",
340
+ "pcb_component_id": "pcb_component_1",
341
+ "center": {
342
+ "x": -2.5600000000000005,
343
+ "y": 0
344
+ },
345
+ "width": 1.56,
346
+ "height": 0.64,
347
+ "layer": "bottom",
348
+ "rotation": 0,
349
+ "source_component_id": "source_component_1",
350
+ "subcircuit_id": "subcircuit_source_group_0",
351
+ "do_not_place": false,
352
+ "obstructs_within_bounds": true
353
+ },
354
+ {
355
+ "type": "pcb_board",
356
+ "pcb_board_id": "pcb_board_0",
357
+ "center": {
358
+ "x": 0,
359
+ "y": 0
360
+ },
361
+ "thickness": 1.4,
362
+ "num_layers": 2,
363
+ "width": 10,
364
+ "height": 10,
365
+ "material": "fr4"
366
+ },
367
+ {
368
+ "type": "pcb_smtpad",
369
+ "pcb_smtpad_id": "pcb_smtpad_0",
370
+ "pcb_component_id": "pcb_component_0",
371
+ "pcb_port_id": "pcb_port_0",
372
+ "layer": "top",
373
+ "shape": "rect",
374
+ "width": 0.54,
375
+ "height": 0.64,
376
+ "port_hints": ["1", "left"],
377
+ "is_covered_with_solder_mask": false,
378
+ "x": -0.51,
379
+ "y": 0,
380
+ "subcircuit_id": "subcircuit_source_group_0"
381
+ },
382
+ {
383
+ "type": "pcb_solder_paste",
384
+ "pcb_solder_paste_id": "pcb_solder_paste_0",
385
+ "layer": "top",
386
+ "shape": "rect",
387
+ "width": 0.378,
388
+ "height": 0.44799999999999995,
389
+ "x": -0.51,
390
+ "y": 0,
391
+ "pcb_component_id": "pcb_component_0",
392
+ "pcb_smtpad_id": "pcb_smtpad_0",
393
+ "subcircuit_id": "subcircuit_source_group_0"
394
+ },
395
+ {
396
+ "type": "pcb_smtpad",
397
+ "pcb_smtpad_id": "pcb_smtpad_1",
398
+ "pcb_component_id": "pcb_component_0",
399
+ "pcb_port_id": "pcb_port_1",
400
+ "layer": "top",
401
+ "shape": "rect",
402
+ "width": 0.54,
403
+ "height": 0.64,
404
+ "port_hints": ["2", "right"],
405
+ "is_covered_with_solder_mask": false,
406
+ "x": 0.51,
407
+ "y": 0,
408
+ "subcircuit_id": "subcircuit_source_group_0"
409
+ },
410
+ {
411
+ "type": "pcb_solder_paste",
412
+ "pcb_solder_paste_id": "pcb_solder_paste_1",
413
+ "layer": "top",
414
+ "shape": "rect",
415
+ "width": 0.378,
416
+ "height": 0.44799999999999995,
417
+ "x": 0.51,
418
+ "y": 0,
419
+ "pcb_component_id": "pcb_component_0",
420
+ "pcb_smtpad_id": "pcb_smtpad_1",
421
+ "subcircuit_id": "subcircuit_source_group_0"
422
+ },
423
+ {
424
+ "type": "pcb_silkscreen_path",
425
+ "pcb_silkscreen_path_id": "pcb_silkscreen_path_0",
426
+ "pcb_component_id": "pcb_component_0",
427
+ "layer": "top",
428
+ "route": [
429
+ {
430
+ "x": 0.51,
431
+ "y": 0.72
432
+ },
433
+ {
434
+ "x": -0.98,
435
+ "y": 0.72
436
+ },
437
+ {
438
+ "x": -0.98,
439
+ "y": -0.72
440
+ },
441
+ {
442
+ "x": 0.51,
443
+ "y": -0.72
444
+ }
445
+ ],
446
+ "stroke_width": 0.1,
447
+ "subcircuit_id": "subcircuit_source_group_0"
448
+ },
449
+ {
450
+ "type": "pcb_silkscreen_text",
451
+ "pcb_silkscreen_text_id": "pcb_silkscreen_text_0",
452
+ "anchor_alignment": "center",
453
+ "anchor_position": {
454
+ "x": 0,
455
+ "y": 1.22
456
+ },
457
+ "font": "tscircuit2024",
458
+ "font_size": 0.4,
459
+ "layer": "top",
460
+ "text": "R1",
461
+ "ccw_rotation": 0,
462
+ "pcb_component_id": "pcb_component_0",
463
+ "subcircuit_id": "subcircuit_source_group_0"
464
+ },
465
+ {
466
+ "type": "pcb_smtpad",
467
+ "pcb_smtpad_id": "pcb_smtpad_2",
468
+ "pcb_component_id": "pcb_component_1",
469
+ "pcb_port_id": "pcb_port_2",
470
+ "layer": "bottom",
471
+ "shape": "rect",
472
+ "width": 0.54,
473
+ "height": 0.64,
474
+ "port_hints": ["1", "left"],
475
+ "is_covered_with_solder_mask": false,
476
+ "x": -2.0500000000000007,
477
+ "y": 0,
478
+ "subcircuit_id": "subcircuit_source_group_0"
479
+ },
480
+ {
481
+ "type": "pcb_solder_paste",
482
+ "pcb_solder_paste_id": "pcb_solder_paste_2",
483
+ "layer": "bottom",
484
+ "shape": "rect",
485
+ "width": 0.378,
486
+ "height": 0.44799999999999995,
487
+ "x": 0.51,
488
+ "y": 0,
489
+ "pcb_component_id": "pcb_component_1",
490
+ "pcb_smtpad_id": "pcb_smtpad_2",
491
+ "subcircuit_id": "subcircuit_source_group_0"
492
+ },
493
+ {
494
+ "type": "pcb_smtpad",
495
+ "pcb_smtpad_id": "pcb_smtpad_3",
496
+ "pcb_component_id": "pcb_component_1",
497
+ "pcb_port_id": "pcb_port_3",
498
+ "layer": "bottom",
499
+ "shape": "rect",
500
+ "width": 0.54,
501
+ "height": 0.64,
502
+ "port_hints": ["2", "right"],
503
+ "is_covered_with_solder_mask": false,
504
+ "x": -3.0700000000000003,
505
+ "y": 0,
506
+ "subcircuit_id": "subcircuit_source_group_0"
507
+ },
508
+ {
509
+ "type": "pcb_solder_paste",
510
+ "pcb_solder_paste_id": "pcb_solder_paste_3",
511
+ "layer": "bottom",
512
+ "shape": "rect",
513
+ "width": 0.378,
514
+ "height": 0.44799999999999995,
515
+ "x": -0.51,
516
+ "y": 0,
517
+ "pcb_component_id": "pcb_component_1",
518
+ "pcb_smtpad_id": "pcb_smtpad_3",
519
+ "subcircuit_id": "subcircuit_source_group_0"
520
+ },
521
+ {
522
+ "type": "pcb_silkscreen_path",
523
+ "pcb_silkscreen_path_id": "pcb_silkscreen_path_1",
524
+ "pcb_component_id": "pcb_component_1",
525
+ "layer": "bottom",
526
+ "route": [
527
+ {
528
+ "x": -3.0700000000000003,
529
+ "y": 0.72
530
+ },
531
+ {
532
+ "x": -1.5800000000000005,
533
+ "y": 0.72
534
+ },
535
+ {
536
+ "x": -1.5800000000000005,
537
+ "y": -0.72
538
+ },
539
+ {
540
+ "x": -3.0700000000000003,
541
+ "y": -0.72
542
+ }
543
+ ],
544
+ "stroke_width": 0.1,
545
+ "subcircuit_id": "subcircuit_source_group_0"
546
+ },
547
+ {
548
+ "type": "pcb_silkscreen_text",
549
+ "pcb_silkscreen_text_id": "pcb_silkscreen_text_1",
550
+ "anchor_alignment": "center",
551
+ "anchor_position": {
552
+ "x": -2.5600000000000005,
553
+ "y": 1.22
554
+ },
555
+ "font": "tscircuit2024",
556
+ "font_size": 0.4,
557
+ "layer": "bottom",
558
+ "text": "C1",
559
+ "ccw_rotation": 0,
560
+ "pcb_component_id": "pcb_component_1",
561
+ "subcircuit_id": "subcircuit_source_group_0"
562
+ },
563
+ {
564
+ "type": "pcb_hole",
565
+ "pcb_hole_id": "pcb_hole_0",
566
+ "hole_shape": "circle",
567
+ "hole_diameter": 2,
568
+ "x": 0,
569
+ "y": -3,
570
+ "subcircuit_id": "subcircuit_source_group_0"
571
+ },
572
+ {
573
+ "type": "pcb_cutout",
574
+ "pcb_cutout_id": "pcb_cutout_0",
575
+ "shape": "rect",
576
+ "center": {
577
+ "x": 3,
578
+ "y": 0
579
+ },
580
+ "width": 2,
581
+ "height": 1,
582
+ "subcircuit_id": "subcircuit_source_group_0"
583
+ },
584
+ {
585
+ "type": "pcb_cutout",
586
+ "pcb_cutout_id": "pcb_cutout_1",
587
+ "shape": "circle",
588
+ "center": {
589
+ "x": 3,
590
+ "y": 3
591
+ },
592
+ "radius": 1,
593
+ "subcircuit_id": "subcircuit_source_group_0"
594
+ },
595
+ {
596
+ "type": "pcb_cutout",
597
+ "pcb_cutout_id": "pcb_cutout_2",
598
+ "shape": "polygon",
599
+ "points": [
600
+ {
601
+ "x": 3,
602
+ "y": -2
603
+ },
604
+ {
605
+ "x": 3,
606
+ "y": -2
607
+ },
608
+ {
609
+ "x": 4,
610
+ "y": -3
611
+ },
612
+ {
613
+ "x": 2,
614
+ "y": -3
615
+ }
616
+ ],
617
+ "subcircuit_id": "subcircuit_source_group_0"
618
+ },
619
+ {
620
+ "type": "pcb_port",
621
+ "pcb_port_id": "pcb_port_0",
622
+ "pcb_component_id": "pcb_component_0",
623
+ "layers": ["top"],
624
+ "subcircuit_id": "subcircuit_source_group_0",
625
+ "x": -0.51,
626
+ "y": 0,
627
+ "source_port_id": "source_port_0"
628
+ },
629
+ {
630
+ "type": "pcb_port",
631
+ "pcb_port_id": "pcb_port_1",
632
+ "pcb_component_id": "pcb_component_0",
633
+ "layers": ["top"],
634
+ "subcircuit_id": "subcircuit_source_group_0",
635
+ "x": 0.51,
636
+ "y": 0,
637
+ "source_port_id": "source_port_1"
638
+ },
639
+ {
640
+ "type": "pcb_port",
641
+ "pcb_port_id": "pcb_port_2",
642
+ "pcb_component_id": "pcb_component_1",
643
+ "layers": ["bottom"],
644
+ "subcircuit_id": "subcircuit_source_group_0",
645
+ "x": -2.0500000000000007,
646
+ "y": 0,
647
+ "source_port_id": "source_port_2"
648
+ },
649
+ {
650
+ "type": "pcb_port",
651
+ "pcb_port_id": "pcb_port_3",
652
+ "pcb_component_id": "pcb_component_1",
653
+ "layers": ["bottom"],
654
+ "subcircuit_id": "subcircuit_source_group_0",
655
+ "x": -3.0700000000000003,
656
+ "y": 0,
657
+ "source_port_id": "source_port_3"
658
+ },
659
+ {
660
+ "type": "cad_component",
661
+ "cad_component_id": "cad_component_0",
662
+ "position": {
663
+ "x": 0,
664
+ "y": 0,
665
+ "z": 0.7
666
+ },
667
+ "rotation": {
668
+ "x": 0,
669
+ "y": 0,
670
+ "z": 0
671
+ },
672
+ "pcb_component_id": "pcb_component_0",
673
+ "source_component_id": "source_component_0",
674
+ "footprinter_string": "0402"
675
+ },
676
+ {
677
+ "type": "cad_component",
678
+ "cad_component_id": "cad_component_1",
679
+ "position": {
680
+ "x": -2.5600000000000005,
681
+ "y": 0,
682
+ "z": -0.7
683
+ },
684
+ "rotation": {
685
+ "x": 0,
686
+ "y": 180,
687
+ "z": 0
688
+ },
689
+ "pcb_component_id": "pcb_component_1",
690
+ "source_component_id": "source_component_1",
691
+ "footprinter_string": "0402"
692
+ },
693
+ {
694
+ "type": "pcb_trace",
695
+ "pcb_trace_id": "source_net_1_0",
696
+ "connection_name": "source_net_1",
697
+ "route": [
698
+ {
699
+ "route_type": "wire",
700
+ "x": -2.0500000000000007,
701
+ "y": 0,
702
+ "width": 0.15,
703
+ "layer": "bottom",
704
+ "start_pcb_port_id": "pcb_port_2"
705
+ },
706
+ {
707
+ "route_type": "wire",
708
+ "x": -2.0500000000000007,
709
+ "y": -1.5790447323308274,
710
+ "width": 0.15,
711
+ "layer": "bottom"
712
+ },
713
+ {
714
+ "route_type": "wire",
715
+ "x": -1.929170149633294,
716
+ "y": -1.699874582697534,
717
+ "width": 0.15,
718
+ "layer": "bottom"
719
+ },
720
+ {
721
+ "route_type": "wire",
722
+ "x": -1.875,
723
+ "y": -1.875,
724
+ "width": 0.15,
725
+ "layer": "bottom"
726
+ },
727
+ {
728
+ "route_type": "via",
729
+ "x": -1.875,
730
+ "y": -1.875,
731
+ "from_layer": "bottom",
732
+ "to_layer": "top"
733
+ },
734
+ {
735
+ "route_type": "wire",
736
+ "x": -1.875,
737
+ "y": -1.875,
738
+ "width": 0.15,
739
+ "layer": "top"
740
+ },
741
+ {
742
+ "route_type": "wire",
743
+ "x": -1.875,
744
+ "y": -1.365,
745
+ "width": 0.15,
746
+ "layer": "top"
747
+ },
748
+ {
749
+ "route_type": "wire",
750
+ "x": -0.51,
751
+ "y": 0,
752
+ "width": 0.15,
753
+ "layer": "top",
754
+ "end_pcb_port_id": "pcb_port_0"
755
+ }
756
+ ],
757
+ "subcircuit_id": "subcircuit_source_group_0",
758
+ "source_trace_id": "source_net_1"
759
+ },
760
+ {
761
+ "type": "pcb_via",
762
+ "pcb_via_id": "pcb_via_0",
763
+ "pcb_trace_id": "source_net_1_0",
764
+ "x": -1.875,
765
+ "y": -1.875,
766
+ "hole_diameter": 0.3,
767
+ "outer_diameter": 0.6,
768
+ "layers": ["bottom", "top"],
769
+ "from_layer": "bottom",
770
+ "to_layer": "top"
771
+ }
772
+ ]
@@ -0,0 +1,16 @@
1
+ import { expect, test } from "bun:test"
2
+ import circuitJson from "./assets/hole-and-cutouts.json"
3
+ import { runSolverAndRenderToSvg } from "./utils/run-solver-and-render-to-svg"
4
+ import type { AnyCircuitElement } from "circuit-json"
5
+
6
+ test("hole and cutouts", async () => {
7
+ const svg = runSolverAndRenderToSvg(circuitJson as AnyCircuitElement[], {
8
+ layer: "top",
9
+ net_name: "GND",
10
+ pad_margin: 0.2,
11
+ trace_margin: 0.1,
12
+ cutout_margin: 0.2,
13
+ })
14
+
15
+ await expect(svg).toMatchSvgSnapshot(import.meta.path, "hole-and-cutouts")
16
+ })
@@ -18,6 +18,7 @@ export const runSolverAndRenderToSvg = (
18
18
  pad_margin: number
19
19
  trace_margin: number
20
20
  board_edge_margin?: number
21
+ cutout_margin?: number
21
22
  },
22
23
  ) => {
23
24
  const source_net = circuitJson.find(