@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.
package/dist/index.js CHANGED
@@ -1,255 +1,18 @@
1
1
  // lib/solvers/CopperPourPipelineSolver.ts
2
2
  import { BasePipelineSolver } from "@tscircuit/solver-utils";
3
3
 
4
- // lib/solvers/copper-pour/generate-brep.ts
5
- var signedArea = (ring) => {
6
- let area = 0;
7
- for (let i = 0; i < ring.length; i++) {
8
- const current = ring[i];
9
- const next = ring[(i + 1) % ring.length];
10
- area += current.x * next.y - next.x * current.y;
11
- }
12
- return area / 2;
13
- };
14
- var ensureAreaSign = (ring, desiredSign) => {
15
- const area = signedArea(ring);
16
- const shouldReverse = desiredSign === "positive" && area < 0 || desiredSign === "negative" && area > 0;
17
- return shouldReverse ? [...ring].reverse() : ring;
18
- };
19
- var ringToVertices = (ring) => ring.map((point) => ({
20
- x: point.x,
21
- y: point.y
22
- }));
23
- var generateBRep = (pourIslands) => {
24
- const brep_shapes = [];
25
- for (const island of pourIslands) {
26
- if (island.outerRing.length < 3) continue;
27
- const outerRing = ensureAreaSign(island.outerRing, "negative");
28
- const innerRings = island.innerRings.filter((ring) => ring.length >= 3).map((ring) => ensureAreaSign(ring, "positive"));
29
- brep_shapes.push({
30
- outer_ring: { vertices: ringToVertices(outerRing) },
31
- inner_rings: innerRings.map((ring) => ({
32
- vertices: ringToVertices(ring)
33
- }))
34
- });
35
- }
36
- return brep_shapes;
37
- };
38
-
39
- // lib/solvers/copper-pour/manifold-geometry-adapter.ts
40
- var manifoldModule = await import("manifold-3d");
41
- var manifoldFactory = manifoldModule.default;
42
- var manifold = await manifoldFactory();
43
- manifold.setup();
44
- var { CrossSection } = manifold;
45
- var MANIFOLD_GEOMETRY_SCALE = 1e6;
46
- var DEFAULT_MIN_ISLAND_AREA = 1e-8;
47
- var describePolygons = (polygons) => {
48
- let pointCount = 0;
49
- const bbox = {
50
- minX: Number.POSITIVE_INFINITY,
51
- minY: Number.POSITIVE_INFINITY,
52
- maxX: Number.NEGATIVE_INFINITY,
53
- maxY: Number.NEGATIVE_INFINITY
54
- };
55
- for (const polygon of polygons) {
56
- pointCount += polygon.length;
57
- for (const [x, y] of polygon) {
58
- bbox.minX = Math.min(bbox.minX, x);
59
- bbox.minY = Math.min(bbox.minY, y);
60
- bbox.maxX = Math.max(bbox.maxX, x);
61
- bbox.maxY = Math.max(bbox.maxY, y);
62
- }
63
- }
64
- return {
65
- polygonCount: polygons.length,
66
- pointCount,
67
- bbox: pointCount > 0 ? bbox : null,
68
- scale: MANIFOLD_GEOMETRY_SCALE
69
- };
70
- };
71
- var assertFinitePoint = (point, operation) => {
72
- if (!Number.isFinite(point.x) || !Number.isFinite(point.y)) {
73
- throw new Error(
74
- `${operation} received non-finite point (${point.x}, ${point.y})`
75
- );
76
- }
77
- };
78
- var pointsEqual = (a, b) => a.x === b.x && a.y === b.y;
79
- var signedArea2 = (ring) => {
80
- let area = 0;
81
- for (let i = 0; i < ring.length; i++) {
82
- const current = ring[i];
83
- const next = ring[(i + 1) % ring.length];
84
- area += current.x * next.y - next.x * current.y;
85
- }
86
- return area / 2;
87
- };
88
- var normalizeRing = (ring, operation = "normalizeRing") => {
89
- const normalized = [];
90
- for (const point of ring) {
91
- assertFinitePoint(point, operation);
92
- const roundedPoint = {
93
- x: Math.round(point.x * MANIFOLD_GEOMETRY_SCALE) / MANIFOLD_GEOMETRY_SCALE,
94
- y: Math.round(point.y * MANIFOLD_GEOMETRY_SCALE) / MANIFOLD_GEOMETRY_SCALE
95
- };
96
- const previous = normalized[normalized.length - 1];
97
- if (!previous || !pointsEqual(previous, roundedPoint)) {
98
- normalized.push(roundedPoint);
99
- }
100
- }
101
- if (normalized.length > 1 && pointsEqual(normalized[0], normalized[normalized.length - 1])) {
102
- normalized.pop();
103
- }
104
- const uniquePoints = new Set(normalized.map((p) => `${p.x},${p.y}`));
105
- if (uniquePoints.size < 3 || Math.abs(signedArea2(normalized)) < 1e-18) {
106
- return [];
107
- }
108
- return normalized;
109
- };
110
- var toScaledManifoldPolygons = (polygons, operation = "toScaledManifoldPolygons") => {
111
- const scaledPolygons = [];
112
- for (const polygon of polygons) {
113
- const normalized = normalizeRing(polygon, operation);
114
- if (normalized.length < 3) continue;
115
- const positiveRing = signedArea2(normalized) < 0 ? [...normalized].reverse() : normalized;
116
- scaledPolygons.push(
117
- positiveRing.map((p) => [
118
- Math.round(p.x * MANIFOLD_GEOMETRY_SCALE),
119
- Math.round(p.y * MANIFOLD_GEOMETRY_SCALE)
120
- ])
121
- );
122
- }
123
- return scaledPolygons;
124
- };
125
- var fromScaledManifoldPolygons = (polygons) => polygons.map(
126
- (polygon) => normalizeRing(
127
- polygon.map(([x, y]) => ({
128
- x: x / MANIFOLD_GEOMETRY_SCALE,
129
- y: y / MANIFOLD_GEOMETRY_SCALE
130
- })),
131
- "fromScaledManifoldPolygons"
132
- )
133
- ).filter((polygon) => polygon.length >= 3);
134
- var runManifoldOperation = (operation, polygons, callback) => {
135
- try {
136
- return callback();
137
- } catch (error) {
138
- const details = describePolygons(polygons);
139
- const message = error instanceof Error ? error.message : String(error);
140
- throw new Error(
141
- `${operation} failed: ${message}; details=${JSON.stringify(details)}`
142
- );
143
- }
144
- };
145
- var crossSectionFromPolygon = (polygon, fillRule = "Positive") => {
146
- const scaledPolygons = toScaledManifoldPolygons(
147
- [polygon],
148
- "crossSectionFromPolygon"
149
- );
150
- if (scaledPolygons.length === 0) {
151
- return CrossSection.ofPolygons([]);
152
- }
153
- return runManifoldOperation(
154
- "crossSectionFromPolygon",
155
- scaledPolygons,
156
- () => CrossSection.ofPolygons(scaledPolygons, fillRule)
157
- );
158
- };
159
- var crossSectionFromPolygons = (polygons, fillRule = "Positive") => {
160
- const scaledPolygons = toScaledManifoldPolygons(
161
- polygons,
162
- "crossSectionFromPolygons"
163
- );
164
- if (scaledPolygons.length === 0) {
165
- return CrossSection.ofPolygons([]);
166
- }
167
- return runManifoldOperation(
168
- "crossSectionFromPolygons",
169
- scaledPolygons,
170
- () => CrossSection.ofPolygons(scaledPolygons, fillRule)
171
- );
172
- };
173
- var composeCrossSections = (sections) => {
174
- const nonEmptySections = sections.filter((section) => !section.isEmpty());
175
- if (nonEmptySections.length === 0) {
176
- return CrossSection.ofPolygons([]);
177
- }
178
- return runManifoldOperation(
179
- "composeCrossSections",
180
- [],
181
- () => CrossSection.compose(nonEmptySections)
182
- );
183
- };
184
- var offsetPolygon = (polygon, margin, joinType = "Miter") => {
185
- const scaledPolygons = toScaledManifoldPolygons([polygon], "offsetPolygon");
186
- if (scaledPolygons.length === 0 || margin <= 0) {
187
- return scaledPolygons.length === 0 ? [] : [normalizeRing(polygon)];
188
- }
189
- const scaledMargin = margin * MANIFOLD_GEOMETRY_SCALE;
190
- const section = runManifoldOperation(
191
- "offsetPolygon.input",
192
- scaledPolygons,
193
- () => CrossSection.ofPolygons(scaledPolygons, "Positive")
194
- );
195
- const offset = runManifoldOperation(
196
- "offsetPolygon.offset",
197
- scaledPolygons,
198
- () => section.offset(scaledMargin, joinType, 2, 32)
199
- );
200
- return fromScaledManifoldPolygons(offset.toPolygons());
201
- };
202
- var subtractBlockersFromPour = (pourPolygon, blockerPolygons) => {
203
- const pourSection = crossSectionFromPolygon(pourPolygon);
204
- const blockerSection = crossSectionFromPolygons(blockerPolygons);
205
- if (pourSection.isEmpty() || blockerSection.isEmpty()) {
206
- return pourSection;
207
- }
208
- const operationPolygons = [
209
- ...toScaledManifoldPolygons([pourPolygon], "subtractBlockersFromPour.pour"),
210
- ...toScaledManifoldPolygons(
211
- blockerPolygons,
212
- "subtractBlockersFromPour.blockers"
213
- )
214
- ];
215
- return runManifoldOperation(
216
- "subtractBlockersFromPour",
217
- operationPolygons,
218
- () => pourSection.subtract(blockerSection)
219
- );
220
- };
221
- var removeTinyIslands = (section, minArea = DEFAULT_MIN_ISLAND_AREA) => {
222
- if (section.isEmpty()) return section;
223
- const minScaledArea = minArea * MANIFOLD_GEOMETRY_SCALE * MANIFOLD_GEOMETRY_SCALE;
224
- const islands = section.decompose().filter((island) => Math.abs(island.area()) >= minScaledArea);
225
- return composeCrossSections(islands);
226
- };
227
- var crossSectionToCopperPourIslands = (section) => {
228
- const islands = [];
229
- for (const island of section.decompose()) {
230
- const rings = fromScaledManifoldPolygons(island.toPolygons());
231
- if (rings.length === 0) continue;
232
- const outerRing = rings.reduce(
233
- (largest, ring) => Math.abs(signedArea2(ring)) > Math.abs(signedArea2(largest)) ? ring : largest
234
- );
235
- const innerRings = rings.filter((ring) => ring !== outerRing);
236
- islands.push({
237
- outerRing,
238
- innerRings
239
- });
240
- }
241
- return islands;
242
- };
243
- var geometryDebugSummary = (label, polygons) => ({
244
- label,
245
- ...describePolygons(toScaledManifoldPolygons(polygons, label))
246
- });
247
-
248
4
  // lib/solvers/copper-pour/get-board-polygon.ts
5
+ import Flatten from "@flatten-js/core";
249
6
  var getBoardPolygon = (region) => {
250
7
  const board_edge_margin = region.board_edge_margin ?? 0;
251
8
  if (region.outline && region.outline.length > 0) {
252
- return normalizeRing(region.outline, "getBoardPolygon.outline");
9
+ const polygon = new Flatten.Polygon(
10
+ region.outline.map((p) => Flatten.point(p.x, p.y))
11
+ );
12
+ if (polygon.orientation() === Flatten.ORIENTATION.CW) {
13
+ polygon.reverse();
14
+ }
15
+ return polygon;
253
16
  }
254
17
  const { bounds } = region;
255
18
  const newBounds = {
@@ -259,64 +22,74 @@ var getBoardPolygon = (region) => {
259
22
  maxY: bounds.maxY - board_edge_margin
260
23
  };
261
24
  if (newBounds.minX >= newBounds.maxX || newBounds.minY >= newBounds.maxY) {
262
- return [];
25
+ return new Flatten.Polygon();
263
26
  }
264
- return [
265
- { x: newBounds.minX, y: newBounds.minY },
266
- { x: newBounds.maxX, y: newBounds.minY },
267
- { x: newBounds.maxX, y: newBounds.maxY },
268
- { x: newBounds.minX, y: newBounds.maxY }
269
- ];
27
+ return new Flatten.Polygon(
28
+ new Flatten.Box(
29
+ newBounds.minX,
30
+ newBounds.minY,
31
+ newBounds.maxX,
32
+ newBounds.maxY
33
+ ).toPoints()
34
+ );
270
35
  };
271
36
 
37
+ // lib/solvers/CopperPourPipelineSolver.ts
38
+ import Flatten5 from "@flatten-js/core";
39
+
272
40
  // lib/solvers/copper-pour/process-obstacles.ts
273
- var isRectPad = (pad) => pad.shape === "rect";
274
- var isTracePad = (pad) => pad.shape === "trace";
275
- var isCircularPad = (pad) => pad.shape === "circle";
276
- var isPolygonPad = (pad) => pad.shape === "polygon";
277
- var circleToPolygon = (center, radius, numSegments = 32) => {
41
+ import Flatten3 from "@flatten-js/core";
42
+
43
+ // lib/solvers/copper-pour/circle-to-polygon.ts
44
+ import Flatten2 from "@flatten-js/core";
45
+ var circleToPolygon = (circle, numSegments = 32) => {
278
46
  const points = [];
279
47
  for (let i = 0; i < numSegments; i++) {
280
48
  const angle = i / numSegments * 2 * Math.PI;
281
- points.push({
282
- x: center.x + radius * Math.cos(angle),
283
- y: center.y + radius * Math.sin(angle)
284
- });
49
+ points.push(
50
+ new Flatten2.Point(
51
+ circle.center.x + circle.r * Math.cos(angle),
52
+ circle.center.y + circle.r * Math.sin(angle)
53
+ )
54
+ );
285
55
  }
286
- return points;
56
+ return new Flatten2.Polygon(points);
287
57
  };
288
- var boxToPolygon = (minX, minY, maxX, maxY) => [
289
- { x: minX, y: minY },
290
- { x: maxX, y: minY },
291
- { x: maxX, y: maxY },
292
- { x: minX, y: maxY }
293
- ];
58
+
59
+ // lib/solvers/copper-pour/process-obstacles.ts
60
+ var isRectPad = (pad) => pad.shape === "rect";
61
+ var isTracePad = (pad) => pad.shape === "trace";
62
+ var isCircularPad = (pad) => pad.shape === "circle";
63
+ var isPolygonPad = (pad) => pad.shape === "polygon";
294
64
  var processObstaclesForPour = (pads, pourConnectivityKey, margins, boardOutline) => {
295
65
  const polygonsToSubtract = [];
296
66
  const { padMargin, traceMargin, board_edge_margin, cutoutMargin } = margins;
297
67
  if (boardOutline && boardOutline.length > 0 && board_edge_margin && board_edge_margin > 0) {
298
- const vertices = normalizeRing(
299
- boardOutline,
300
- "processObstacles.boardOutline"
68
+ const boardPoly = new Flatten3.Polygon(
69
+ boardOutline.map((p) => Flatten3.point(p.x, p.y))
301
70
  );
71
+ if (boardPoly.area() < 0) {
72
+ boardPoly.reverse();
73
+ }
74
+ const vertices = boardPoly.vertices;
302
75
  for (let i = 0; i < vertices.length; i++) {
303
76
  const p1 = vertices[i === 0 ? vertices.length - 1 : i - 1];
304
77
  const p2 = vertices[i];
305
78
  const p3 = vertices[(i + 1) % vertices.length];
306
79
  if (!p1 || !p2 || !p3) continue;
307
- const v1 = { x: p2.x - p1.x, y: p2.y - p1.y };
308
- const v2 = { x: p3.x - p2.x, y: p3.y - p2.y };
309
- const crossProduct = v1.x * v2.y - v1.y * v2.x;
310
- polygonsToSubtract.push(circleToPolygon(p2, board_edge_margin));
80
+ const v1 = new Flatten3.Vector(p1, p2);
81
+ const v2 = new Flatten3.Vector(p2, p3);
82
+ const crossProduct = v1.cross(v2);
83
+ const circle = new Flatten3.Circle(p2, board_edge_margin);
84
+ polygonsToSubtract.push(circleToPolygon(circle));
311
85
  if (crossProduct < 0) {
312
- polygonsToSubtract.push(
313
- boxToPolygon(
314
- p2.x - board_edge_margin,
315
- p2.y - board_edge_margin,
316
- p2.x + board_edge_margin,
317
- p2.y + board_edge_margin
318
- )
86
+ const box = new Flatten3.Box(
87
+ p2.x - board_edge_margin,
88
+ p2.y - board_edge_margin,
89
+ p2.x + board_edge_margin,
90
+ p2.y + board_edge_margin
319
91
  );
92
+ polygonsToSubtract.push(new Flatten3.Polygon(box.toPoints()));
320
93
  }
321
94
  }
322
95
  for (let i = 0; i < vertices.length; i++) {
@@ -344,7 +117,9 @@ var processObstaclesForPour = (pads, pourConnectivityKey, margins, boardOutline)
344
117
  x: centerX + p.x * cosAngle - p.y * sinAngle,
345
118
  y: centerY + p.x * sinAngle + p.y * cosAngle
346
119
  }));
347
- polygonsToSubtract.push(rotatedCorners);
120
+ polygonsToSubtract.push(
121
+ new Flatten3.Polygon(rotatedCorners.map((p) => Flatten3.point(p.x, p.y)))
122
+ );
348
123
  }
349
124
  }
350
125
  for (const pad of pads) {
@@ -355,22 +130,23 @@ var processObstaclesForPour = (pads, pourConnectivityKey, margins, boardOutline)
355
130
  const isHoleOrCutout = pad.connectivityKey.startsWith("hole:") || pad.connectivityKey.startsWith("cutout:");
356
131
  if (isCircularPad(pad)) {
357
132
  const margin = isHoleOrCutout ? cutoutMargin ?? 0 : padMargin;
358
- polygonsToSubtract.push(
359
- circleToPolygon({ x: pad.x, y: pad.y }, pad.radius + margin)
133
+ const circle = new Flatten3.Circle(
134
+ new Flatten3.Point(pad.x, pad.y),
135
+ pad.radius + margin
360
136
  );
137
+ polygonsToSubtract.push(circleToPolygon(circle));
361
138
  continue;
362
139
  }
363
140
  if (isRectPad(pad)) {
364
141
  const margin = isHoleOrCutout ? cutoutMargin ?? 0 : padMargin;
365
142
  const { bounds } = pad;
366
- polygonsToSubtract.push(
367
- boxToPolygon(
368
- bounds.minX - margin,
369
- bounds.minY - margin,
370
- bounds.maxX + margin,
371
- bounds.maxY + margin
372
- )
143
+ const b = new Flatten3.Box(
144
+ bounds.minX - margin,
145
+ bounds.minY - margin,
146
+ bounds.maxX + margin,
147
+ bounds.maxY + margin
373
148
  );
149
+ polygonsToSubtract.push(new Flatten3.Polygon(b.toPoints()));
374
150
  continue;
375
151
  }
376
152
  if (isPolygonPad(pad)) {
@@ -385,20 +161,50 @@ var processObstaclesForPour = (pads, pourConnectivityKey, margins, boardOutline)
385
161
  return true;
386
162
  });
387
163
  if (uniquePoints.length < 3) continue;
388
- const polygon = normalizeRing(uniquePoints, "processObstacles.polygonPad");
389
- if (polygon.length < 3) continue;
164
+ const polygon = new Flatten3.Polygon(
165
+ uniquePoints.map((p) => Flatten3.point(p.x, p.y))
166
+ );
167
+ if (Math.abs(polygon.area()) < 1e-9) continue;
390
168
  if (margin <= 0) {
391
169
  polygonsToSubtract.push(polygon);
392
170
  continue;
393
171
  }
394
- polygonsToSubtract.push(...offsetPolygon(polygon, margin));
172
+ if (polygon.area() > 0) {
173
+ polygon.reverse();
174
+ }
175
+ const offsetLines = [];
176
+ const polygonVertices = polygon.vertices;
177
+ for (let i = 0; i < polygonVertices.length; i++) {
178
+ const p1 = polygonVertices[i];
179
+ const p2 = polygonVertices[(i + 1) % polygonVertices.length];
180
+ const segment = Flatten3.segment(p1, p2);
181
+ if (segment.length === 0) continue;
182
+ const line = Flatten3.line(segment.start, segment.end);
183
+ const norm = line.norm;
184
+ const offsetLine = line.translate(norm.multiply(-margin));
185
+ offsetLines.push(offsetLine);
186
+ }
187
+ const newPolygonPoints = [];
188
+ for (let i = 0; i < offsetLines.length; i++) {
189
+ const line1 = offsetLines[i];
190
+ const line2 = offsetLines[(i + 1) % offsetLines.length];
191
+ const ip = line1.intersect(line2);
192
+ if (ip.length > 0) {
193
+ newPolygonPoints.push(ip[0]);
194
+ }
195
+ }
196
+ if (newPolygonPoints.length >= 3) {
197
+ polygonsToSubtract.push(new Flatten3.Polygon(newPolygonPoints));
198
+ }
395
199
  continue;
396
200
  }
397
201
  if (isTracePad(pad)) {
398
202
  for (const segment of pad.segments) {
399
- polygonsToSubtract.push(
400
- circleToPolygon(segment, pad.width / 2 + traceMargin)
203
+ const circle = new Flatten3.Circle(
204
+ new Flatten3.Point(segment.x, segment.y),
205
+ pad.width / 2 + traceMargin
401
206
  );
207
+ polygonsToSubtract.push(circleToPolygon(circle));
402
208
  }
403
209
  for (let i = 0; i < pad.segments.length - 1; i++) {
404
210
  const p1 = pad.segments[i];
@@ -425,13 +231,62 @@ var processObstaclesForPour = (pads, pourConnectivityKey, margins, boardOutline)
425
231
  x: centerX + p.x * cosAngle - p.y * sinAngle,
426
232
  y: centerY + p.x * sinAngle + p.y * cosAngle
427
233
  }));
428
- polygonsToSubtract.push(rotatedCorners);
234
+ polygonsToSubtract.push(
235
+ new Flatten3.Polygon(
236
+ rotatedCorners.map((p) => Flatten3.point(p.x, p.y))
237
+ )
238
+ );
429
239
  }
430
240
  }
431
241
  }
432
242
  return { polygonsToSubtract };
433
243
  };
434
244
 
245
+ // lib/solvers/copper-pour/generate-brep.ts
246
+ import Flatten4 from "@flatten-js/core";
247
+ var faceToVertices = (face) => face.edges.map((e) => {
248
+ const pt = {
249
+ x: e.start.x,
250
+ y: e.start.y
251
+ };
252
+ if (e.isArc) {
253
+ const bulge = Math.tan(e.shape.sweep / 4);
254
+ if (Math.abs(bulge) > 1e-9) {
255
+ pt.bulge = bulge;
256
+ }
257
+ }
258
+ return pt;
259
+ });
260
+ var generateBRep = (pourPolygons) => {
261
+ const brep_shapes = [];
262
+ const polygons = Array.isArray(pourPolygons) ? pourPolygons : [pourPolygons];
263
+ for (const p of polygons) {
264
+ const islands = p.splitToIslands();
265
+ for (const island of islands) {
266
+ if (island.isEmpty()) continue;
267
+ const faces = [...island.faces];
268
+ const outer_face_ccw = faces.find(
269
+ (f) => f.orientation() === Flatten4.ORIENTATION.CCW
270
+ );
271
+ const inner_faces_cw = faces.filter(
272
+ (f) => f.orientation() === Flatten4.ORIENTATION.CW
273
+ );
274
+ if (!outer_face_ccw) continue;
275
+ outer_face_ccw.reverse();
276
+ const outer_ring_vertices = faceToVertices(outer_face_ccw);
277
+ const inner_rings = inner_faces_cw.map((f) => {
278
+ f.reverse();
279
+ return { vertices: faceToVertices(f) };
280
+ });
281
+ brep_shapes.push({
282
+ outer_ring: { vertices: outer_ring_vertices },
283
+ inner_rings
284
+ });
285
+ }
286
+ }
287
+ return brep_shapes;
288
+ };
289
+
435
290
  // lib/solvers/CopperPourPipelineSolver.ts
436
291
  var CopperPourPipelineSolver = class extends BasePipelineSolver {
437
292
  constructor(input) {
@@ -461,32 +316,23 @@ var CopperPourPipelineSolver = class extends BasePipelineSolver {
461
316
  },
462
317
  region.outline
463
318
  );
464
- if (process.env.COPPER_POUR_DEBUG_GEOMETRY) {
465
- console.info(
466
- JSON.stringify([
467
- geometryDebugSummary("input pour polygon", [boardPolygon]),
468
- geometryDebugSummary("unioned blockers input", polygonsToSubtract)
469
- ])
470
- );
471
- }
472
- const finalPour = removeTinyIslands(
473
- subtractBlockersFromPour(boardPolygon, polygonsToSubtract)
474
- );
475
- const pourIslands = crossSectionToCopperPourIslands(finalPour);
476
- if (process.env.COPPER_POUR_DEBUG_GEOMETRY) {
477
- console.info(
478
- JSON.stringify(
479
- geometryDebugSummary(
480
- "final copper polygons",
481
- pourIslands.flatMap((island) => [
482
- island.outerRing,
483
- ...island.innerRings
484
- ])
485
- )
486
- )
487
- );
319
+ let pourPolygons = boardPolygon;
320
+ for (const poly of polygonsToSubtract) {
321
+ const currentPolys = Array.isArray(pourPolygons) ? pourPolygons : [pourPolygons];
322
+ const nextPolys = [];
323
+ for (const p of currentPolys) {
324
+ const result = Flatten5.BooleanOperations.subtract(p, poly);
325
+ if (result) {
326
+ if (Array.isArray(result)) {
327
+ nextPolys.push(...result.filter((r) => !r.isEmpty()));
328
+ } else {
329
+ if (!result.isEmpty()) nextPolys.push(result);
330
+ }
331
+ }
332
+ }
333
+ pourPolygons = nextPolys;
488
334
  }
489
- const new_breps = generateBRep(pourIslands);
335
+ const new_breps = generateBRep(pourPolygons);
490
336
  brep_shapes.push(...new_breps);
491
337
  }
492
338
  return {
@@ -1,15 +1,10 @@
1
1
  import { BasePipelineSolver } from "@tscircuit/solver-utils"
2
- import type { BRepShape } from "circuit-json"
3
- import type { InputProblem, PipelineOutput } from "lib/types"
4
- import { generateBRep } from "./copper-pour/generate-brep"
5
2
  import { getBoardPolygon } from "./copper-pour/get-board-polygon"
6
- import {
7
- crossSectionToCopperPourIslands,
8
- geometryDebugSummary,
9
- removeTinyIslands,
10
- subtractBlockersFromPour,
11
- } from "./copper-pour/manifold-geometry-adapter"
3
+ import Flatten from "@flatten-js/core"
12
4
  import { processObstaclesForPour } from "./copper-pour/process-obstacles"
5
+ import { generateBRep } from "./copper-pour/generate-brep"
6
+ import type { BRepShape } from "circuit-json"
7
+ import type { InputProblem, PipelineOutput } from "lib/types"
13
8
 
14
9
  export class CopperPourPipelineSolver extends BasePipelineSolver<InputProblem> {
15
10
  pipelineDef = []
@@ -43,35 +38,27 @@ export class CopperPourPipelineSolver extends BasePipelineSolver<InputProblem> {
43
38
  region.outline,
44
39
  )
45
40
 
46
- if (process.env.COPPER_POUR_DEBUG_GEOMETRY) {
47
- console.info(
48
- JSON.stringify([
49
- geometryDebugSummary("input pour polygon", [boardPolygon]),
50
- geometryDebugSummary("unioned blockers input", polygonsToSubtract),
51
- ]),
52
- )
53
- }
54
-
55
- const finalPour = removeTinyIslands(
56
- subtractBlockersFromPour(boardPolygon, polygonsToSubtract),
57
- )
58
- const pourIslands = crossSectionToCopperPourIslands(finalPour)
41
+ let pourPolygons: Flatten.Polygon | Flatten.Polygon[] = boardPolygon
59
42
 
60
- if (process.env.COPPER_POUR_DEBUG_GEOMETRY) {
61
- console.info(
62
- JSON.stringify(
63
- geometryDebugSummary(
64
- "final copper polygons",
65
- pourIslands.flatMap((island) => [
66
- island.outerRing,
67
- ...island.innerRings,
68
- ]),
69
- ),
70
- ),
71
- )
43
+ for (const poly of polygonsToSubtract) {
44
+ const currentPolys = Array.isArray(pourPolygons)
45
+ ? pourPolygons
46
+ : [pourPolygons]
47
+ const nextPolys: Flatten.Polygon[] = []
48
+ for (const p of currentPolys) {
49
+ const result = Flatten.BooleanOperations.subtract(p, poly)
50
+ if (result) {
51
+ if (Array.isArray(result)) {
52
+ nextPolys.push(...result.filter((r) => !r.isEmpty()))
53
+ } else {
54
+ if (!result.isEmpty()) nextPolys.push(result)
55
+ }
56
+ }
57
+ }
58
+ pourPolygons = nextPolys
72
59
  }
73
60
 
74
- const new_breps = generateBRep(pourIslands)
61
+ const new_breps = generateBRep(pourPolygons)
75
62
  brep_shapes.push(...new_breps)
76
63
  }
77
64
 
@@ -0,0 +1,15 @@
1
+ import Flatten from "@flatten-js/core"
2
+
3
+ export const circleToPolygon = (circle: Flatten.Circle, numSegments = 32) => {
4
+ const points: Flatten.Point[] = []
5
+ for (let i = 0; i < numSegments; i++) {
6
+ const angle = (i / numSegments) * 2 * Math.PI
7
+ points.push(
8
+ new Flatten.Point(
9
+ circle.center.x + circle.r * Math.cos(angle),
10
+ circle.center.y + circle.r * Math.sin(angle),
11
+ ),
12
+ )
13
+ }
14
+ return new Flatten.Polygon(points)
15
+ }