@tscircuit/copper-pour-solver 0.0.25 → 0.0.26

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.
@@ -1,194 +0,0 @@
1
- import { expect, test } from "bun:test"
2
- import type { BRepShape } from "circuit-json"
3
- import { CopperPourPipelineSolver } from "lib/index"
4
- import {
5
- composeCrossSections,
6
- crossSectionFromPolygon,
7
- crossSectionToCopperPourIslands,
8
- removeTinyIslands,
9
- } from "lib/solvers/copper-pour/manifold-geometry-adapter"
10
- import type { InputPad, InputProblem } from "lib/types"
11
-
12
- const baseProblem = (pads: InputPad[]): InputProblem => ({
13
- regionsForPour: [
14
- {
15
- shape: "rect",
16
- layer: "top",
17
- bounds: { minX: 0, minY: 0, maxX: 10, maxY: 10 },
18
- connectivityKey: "net:GND",
19
- padMargin: 0.2,
20
- traceMargin: 0.2,
21
- },
22
- ],
23
- pads,
24
- })
25
-
26
- const solve = (pads: InputPad[]) =>
27
- new CopperPourPipelineSolver(baseProblem(pads)).getOutput().brep_shapes
28
-
29
- const ringArea = (ring: { vertices: { x: number; y: number }[] }) => {
30
- let area = 0
31
- for (let i = 0; i < ring.vertices.length; i++) {
32
- const current = ring.vertices[i]!
33
- const next = ring.vertices[(i + 1) % ring.vertices.length]!
34
- area += current.x * next.y - next.x * current.y
35
- }
36
- return area / 2
37
- }
38
-
39
- const brepArea = (shape: BRepShape) =>
40
- Math.abs(ringArea(shape.outer_ring)) -
41
- shape.inner_rings.reduce((sum, ring) => sum + Math.abs(ringArea(ring)), 0)
42
-
43
- const totalArea = (shapes: BRepShape[]) =>
44
- shapes.reduce((sum, shape) => sum + brepArea(shape), 0)
45
-
46
- const assertValidRings = (shapes: BRepShape[]) => {
47
- for (const shape of shapes) {
48
- expect(shape.outer_ring.vertices.length).toBeGreaterThanOrEqual(3)
49
- for (const point of shape.outer_ring.vertices) {
50
- expect(Number.isFinite(point.x)).toBe(true)
51
- expect(Number.isFinite(point.y)).toBe(true)
52
- }
53
- for (const ring of shape.inner_rings) {
54
- expect(ring.vertices.length).toBeGreaterThanOrEqual(3)
55
- for (const point of ring.vertices) {
56
- expect(Number.isFinite(point.x)).toBe(true)
57
- expect(Number.isFinite(point.y)).toBe(true)
58
- }
59
- }
60
- }
61
- }
62
-
63
- test("trace clearance subtracts from rectangular pour", () => {
64
- const shapes = solve([
65
- {
66
- padId: "trace-1",
67
- shape: "trace",
68
- layer: "top",
69
- connectivityKey: "net:OTHER",
70
- width: 0.4,
71
- segments: [
72
- { x: 2, y: 5 },
73
- { x: 8, y: 5 },
74
- ],
75
- },
76
- ])
77
-
78
- expect(shapes).toHaveLength(1)
79
- expect(shapes[0]!.inner_rings.length).toBe(1)
80
- expect(totalArea(shapes)).toBeLessThan(100)
81
- assertValidRings(shapes)
82
- })
83
-
84
- test("overlapping near-touching trace clearances compose before subtraction", () => {
85
- const shapes = solve([
86
- {
87
- padId: "trace-1",
88
- shape: "trace",
89
- layer: "top",
90
- connectivityKey: "net:OTHER",
91
- width: 0.4,
92
- segments: [
93
- { x: 2, y: 4.7 },
94
- { x: 8, y: 4.7 },
95
- ],
96
- },
97
- {
98
- padId: "trace-2",
99
- shape: "trace",
100
- layer: "top",
101
- connectivityKey: "net:OTHER",
102
- width: 0.4,
103
- segments: [
104
- { x: 2, y: 5.25 },
105
- { x: 8, y: 5.25 },
106
- ],
107
- },
108
- ])
109
-
110
- expect(shapes).toHaveLength(1)
111
- expect(shapes[0]!.inner_rings.length).toBe(1)
112
- expect(totalArea(shapes)).toBeGreaterThan(90)
113
- assertValidRings(shapes)
114
- })
115
-
116
- test("pads and vias inside pour become holes", () => {
117
- const shapes = solve([
118
- {
119
- padId: "pad-1",
120
- shape: "rect",
121
- layer: "top",
122
- connectivityKey: "net:OTHER",
123
- bounds: { minX: 2, minY: 2, maxX: 3, maxY: 3 },
124
- },
125
- {
126
- padId: "via-1",
127
- shape: "circle",
128
- layer: "top",
129
- connectivityKey: "net:OTHER",
130
- x: 7,
131
- y: 7,
132
- radius: 0.35,
133
- },
134
- ])
135
-
136
- expect(shapes).toHaveLength(1)
137
- expect(shapes[0]!.inner_rings.length).toBe(2)
138
- assertValidRings(shapes)
139
- })
140
-
141
- test("blocker covering pour returns empty result", () => {
142
- const shapes = solve([
143
- {
144
- padId: "pad-1",
145
- shape: "rect",
146
- layer: "top",
147
- connectivityKey: "net:OTHER",
148
- bounds: { minX: -1, minY: -1, maxX: 11, maxY: 11 },
149
- },
150
- ])
151
-
152
- expect(shapes).toHaveLength(0)
153
- })
154
-
155
- test("clearance can split pour into multiple disconnected islands", () => {
156
- const shapes = solve([
157
- {
158
- padId: "trace-1",
159
- shape: "trace",
160
- layer: "top",
161
- connectivityKey: "net:OTHER",
162
- width: 0.6,
163
- segments: [
164
- { x: 5, y: -1 },
165
- { x: 5, y: 11 },
166
- ],
167
- },
168
- ])
169
-
170
- expect(shapes).toHaveLength(2)
171
- assertValidRings(shapes)
172
- })
173
-
174
- test("tiny copper islands are removed below threshold", () => {
175
- const normalIsland = crossSectionFromPolygon([
176
- { x: 0, y: 0 },
177
- { x: 1, y: 0 },
178
- { x: 1, y: 1 },
179
- { x: 0, y: 1 },
180
- ])
181
- const tinyIsland = crossSectionFromPolygon([
182
- { x: 2, y: 0 },
183
- { x: 2.00001, y: 0 },
184
- { x: 2.00001, y: 0.00001 },
185
- { x: 2, y: 0.00001 },
186
- ])
187
-
188
- const cleaned = removeTinyIslands(
189
- composeCrossSections([normalIsland, tinyIsland]),
190
- 1e-8,
191
- )
192
-
193
- expect(crossSectionToCopperPourIslands(cleaned)).toHaveLength(1)
194
- })