@tscircuit/copper-pour-solver 0.0.24 → 0.0.25

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,18 +1,255 @@
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
+
4
248
  // lib/solvers/copper-pour/get-board-polygon.ts
5
- import Flatten from "@flatten-js/core";
6
249
  var getBoardPolygon = (region) => {
7
250
  const board_edge_margin = region.board_edge_margin ?? 0;
8
251
  if (region.outline && region.outline.length > 0) {
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;
252
+ return normalizeRing(region.outline, "getBoardPolygon.outline");
16
253
  }
17
254
  const { bounds } = region;
18
255
  const newBounds = {
@@ -22,74 +259,64 @@ var getBoardPolygon = (region) => {
22
259
  maxY: bounds.maxY - board_edge_margin
23
260
  };
24
261
  if (newBounds.minX >= newBounds.maxX || newBounds.minY >= newBounds.maxY) {
25
- return new Flatten.Polygon();
262
+ return [];
26
263
  }
27
- return new Flatten.Polygon(
28
- new Flatten.Box(
29
- newBounds.minX,
30
- newBounds.minY,
31
- newBounds.maxX,
32
- newBounds.maxY
33
- ).toPoints()
34
- );
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
+ ];
35
270
  };
36
271
 
37
- // lib/solvers/CopperPourPipelineSolver.ts
38
- import Flatten5 from "@flatten-js/core";
39
-
40
272
  // lib/solvers/copper-pour/process-obstacles.ts
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) => {
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) => {
46
278
  const points = [];
47
279
  for (let i = 0; i < numSegments; i++) {
48
280
  const angle = i / numSegments * 2 * Math.PI;
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
- );
281
+ points.push({
282
+ x: center.x + radius * Math.cos(angle),
283
+ y: center.y + radius * Math.sin(angle)
284
+ });
55
285
  }
56
- return new Flatten2.Polygon(points);
286
+ return points;
57
287
  };
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";
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
+ ];
64
294
  var processObstaclesForPour = (pads, pourConnectivityKey, margins, boardOutline) => {
65
295
  const polygonsToSubtract = [];
66
296
  const { padMargin, traceMargin, board_edge_margin, cutoutMargin } = margins;
67
297
  if (boardOutline && boardOutline.length > 0 && board_edge_margin && board_edge_margin > 0) {
68
- const boardPoly = new Flatten3.Polygon(
69
- boardOutline.map((p) => Flatten3.point(p.x, p.y))
298
+ const vertices = normalizeRing(
299
+ boardOutline,
300
+ "processObstacles.boardOutline"
70
301
  );
71
- if (boardPoly.area() < 0) {
72
- boardPoly.reverse();
73
- }
74
- const vertices = boardPoly.vertices;
75
302
  for (let i = 0; i < vertices.length; i++) {
76
303
  const p1 = vertices[i === 0 ? vertices.length - 1 : i - 1];
77
304
  const p2 = vertices[i];
78
305
  const p3 = vertices[(i + 1) % vertices.length];
79
306
  if (!p1 || !p2 || !p3) continue;
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));
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));
85
311
  if (crossProduct < 0) {
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
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
+ )
91
319
  );
92
- polygonsToSubtract.push(new Flatten3.Polygon(box.toPoints()));
93
320
  }
94
321
  }
95
322
  for (let i = 0; i < vertices.length; i++) {
@@ -117,9 +344,7 @@ var processObstaclesForPour = (pads, pourConnectivityKey, margins, boardOutline)
117
344
  x: centerX + p.x * cosAngle - p.y * sinAngle,
118
345
  y: centerY + p.x * sinAngle + p.y * cosAngle
119
346
  }));
120
- polygonsToSubtract.push(
121
- new Flatten3.Polygon(rotatedCorners.map((p) => Flatten3.point(p.x, p.y)))
122
- );
347
+ polygonsToSubtract.push(rotatedCorners);
123
348
  }
124
349
  }
125
350
  for (const pad of pads) {
@@ -130,23 +355,22 @@ var processObstaclesForPour = (pads, pourConnectivityKey, margins, boardOutline)
130
355
  const isHoleOrCutout = pad.connectivityKey.startsWith("hole:") || pad.connectivityKey.startsWith("cutout:");
131
356
  if (isCircularPad(pad)) {
132
357
  const margin = isHoleOrCutout ? cutoutMargin ?? 0 : padMargin;
133
- const circle = new Flatten3.Circle(
134
- new Flatten3.Point(pad.x, pad.y),
135
- pad.radius + margin
358
+ polygonsToSubtract.push(
359
+ circleToPolygon({ x: pad.x, y: pad.y }, pad.radius + margin)
136
360
  );
137
- polygonsToSubtract.push(circleToPolygon(circle));
138
361
  continue;
139
362
  }
140
363
  if (isRectPad(pad)) {
141
364
  const margin = isHoleOrCutout ? cutoutMargin ?? 0 : padMargin;
142
365
  const { bounds } = pad;
143
- const b = new Flatten3.Box(
144
- bounds.minX - margin,
145
- bounds.minY - margin,
146
- bounds.maxX + margin,
147
- bounds.maxY + margin
366
+ polygonsToSubtract.push(
367
+ boxToPolygon(
368
+ bounds.minX - margin,
369
+ bounds.minY - margin,
370
+ bounds.maxX + margin,
371
+ bounds.maxY + margin
372
+ )
148
373
  );
149
- polygonsToSubtract.push(new Flatten3.Polygon(b.toPoints()));
150
374
  continue;
151
375
  }
152
376
  if (isPolygonPad(pad)) {
@@ -161,50 +385,20 @@ var processObstaclesForPour = (pads, pourConnectivityKey, margins, boardOutline)
161
385
  return true;
162
386
  });
163
387
  if (uniquePoints.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;
388
+ const polygon = normalizeRing(uniquePoints, "processObstacles.polygonPad");
389
+ if (polygon.length < 3) continue;
168
390
  if (margin <= 0) {
169
391
  polygonsToSubtract.push(polygon);
170
392
  continue;
171
393
  }
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
- }
394
+ polygonsToSubtract.push(...offsetPolygon(polygon, margin));
199
395
  continue;
200
396
  }
201
397
  if (isTracePad(pad)) {
202
398
  for (const segment of pad.segments) {
203
- const circle = new Flatten3.Circle(
204
- new Flatten3.Point(segment.x, segment.y),
205
- pad.width / 2 + traceMargin
399
+ polygonsToSubtract.push(
400
+ circleToPolygon(segment, pad.width / 2 + traceMargin)
206
401
  );
207
- polygonsToSubtract.push(circleToPolygon(circle));
208
402
  }
209
403
  for (let i = 0; i < pad.segments.length - 1; i++) {
210
404
  const p1 = pad.segments[i];
@@ -231,62 +425,13 @@ var processObstaclesForPour = (pads, pourConnectivityKey, margins, boardOutline)
231
425
  x: centerX + p.x * cosAngle - p.y * sinAngle,
232
426
  y: centerY + p.x * sinAngle + p.y * cosAngle
233
427
  }));
234
- polygonsToSubtract.push(
235
- new Flatten3.Polygon(
236
- rotatedCorners.map((p) => Flatten3.point(p.x, p.y))
237
- )
238
- );
428
+ polygonsToSubtract.push(rotatedCorners);
239
429
  }
240
430
  }
241
431
  }
242
432
  return { polygonsToSubtract };
243
433
  };
244
434
 
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
-
290
435
  // lib/solvers/CopperPourPipelineSolver.ts
291
436
  var CopperPourPipelineSolver = class extends BasePipelineSolver {
292
437
  constructor(input) {
@@ -316,23 +461,32 @@ var CopperPourPipelineSolver = class extends BasePipelineSolver {
316
461
  },
317
462
  region.outline
318
463
  );
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;
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
+ );
334
488
  }
335
- const new_breps = generateBRep(pourPolygons);
489
+ const new_breps = generateBRep(pourIslands);
336
490
  brep_shapes.push(...new_breps);
337
491
  }
338
492
  return {
@@ -1,10 +1,15 @@
1
1
  import { BasePipelineSolver } from "@tscircuit/solver-utils"
2
- import { getBoardPolygon } from "./copper-pour/get-board-polygon"
3
- import Flatten from "@flatten-js/core"
4
- import { processObstaclesForPour } from "./copper-pour/process-obstacles"
5
- import { generateBRep } from "./copper-pour/generate-brep"
6
2
  import type { BRepShape } from "circuit-json"
7
3
  import type { InputProblem, PipelineOutput } from "lib/types"
4
+ import { generateBRep } from "./copper-pour/generate-brep"
5
+ 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"
12
+ import { processObstaclesForPour } from "./copper-pour/process-obstacles"
8
13
 
9
14
  export class CopperPourPipelineSolver extends BasePipelineSolver<InputProblem> {
10
15
  pipelineDef = []
@@ -38,27 +43,35 @@ export class CopperPourPipelineSolver extends BasePipelineSolver<InputProblem> {
38
43
  region.outline,
39
44
  )
40
45
 
41
- let pourPolygons: Flatten.Polygon | Flatten.Polygon[] = boardPolygon
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)
42
59
 
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
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
+ )
59
72
  }
60
73
 
61
- const new_breps = generateBRep(pourPolygons)
74
+ const new_breps = generateBRep(pourIslands)
62
75
  brep_shapes.push(...new_breps)
63
76
  }
64
77