circuit-json-to-lbrn 0.0.43 → 0.0.44

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
@@ -1728,6 +1728,9 @@ var createCirclePolygon = (center, radius, numPoints = 16) => {
1728
1728
  }
1729
1729
  return new Flatten4.Polygon(points);
1730
1730
  };
1731
+ var positionKey = (x, y) => {
1732
+ return `${x.toFixed(6)},${y.toFixed(6)}`;
1733
+ };
1731
1734
  var addPcbTrace = (trace, ctx) => {
1732
1735
  const {
1733
1736
  topCutNetGeoms,
@@ -1738,7 +1741,9 @@ var addPcbTrace = (trace, ctx) => {
1738
1741
  origin,
1739
1742
  includeCopper,
1740
1743
  includeLayers,
1741
- traceMargin
1744
+ traceMargin,
1745
+ topTraceEndpoints,
1746
+ bottomTraceEndpoints
1742
1747
  } = ctx;
1743
1748
  if (!includeCopper) {
1744
1749
  return;
@@ -1779,22 +1784,32 @@ var addPcbTrace = (trace, ctx) => {
1779
1784
  cutNetGeoms.get(netId)?.push(result);
1780
1785
  }
1781
1786
  const segments = layerSegments.get(layer);
1787
+ const endpointSet = layer === "top" ? topTraceEndpoints : bottomTraceEndpoints;
1782
1788
  if (segments) {
1783
1789
  for (const segment of segments) {
1784
1790
  if (segment.length >= 2) {
1785
1791
  const firstPoint = segment[0];
1786
1792
  const lastPoint = segment[segment.length - 1];
1787
1793
  const radius = traceWidth / 2 * 1.1;
1788
- const startCircle = createCirclePolygon(
1789
- { x: firstPoint.x + origin.x, y: firstPoint.y + origin.y },
1790
- radius
1791
- );
1792
- cutNetGeoms.get(netId)?.push(startCircle);
1793
- const endCircle = createCirclePolygon(
1794
- { x: lastPoint.x + origin.x, y: lastPoint.y + origin.y },
1795
- radius
1796
- );
1797
- cutNetGeoms.get(netId)?.push(endCircle);
1794
+ const startX = firstPoint.x + origin.x;
1795
+ const startY = firstPoint.y + origin.y;
1796
+ const startKey = positionKey(startX, startY);
1797
+ if (!endpointSet.has(startKey)) {
1798
+ endpointSet.add(startKey);
1799
+ const startCircle = createCirclePolygon(
1800
+ { x: startX, y: startY },
1801
+ radius
1802
+ );
1803
+ cutNetGeoms.get(netId)?.push(startCircle);
1804
+ }
1805
+ const endX = lastPoint.x + origin.x;
1806
+ const endY = lastPoint.y + origin.y;
1807
+ const endKey = positionKey(endX, endY);
1808
+ if (!endpointSet.has(endKey)) {
1809
+ endpointSet.add(endKey);
1810
+ const endCircle = createCirclePolygon({ x: endX, y: endY }, radius);
1811
+ cutNetGeoms.get(netId)?.push(endCircle);
1812
+ }
1798
1813
  }
1799
1814
  }
1800
1815
  }
@@ -2484,19 +2499,38 @@ var createTraceClearanceAreasForLayer = ({
2484
2499
  // lib/createCopperCutFillForLayer.ts
2485
2500
  import { Polygon as Polygon5, Box as Box4, Point as Point2 } from "@flatten-js/core";
2486
2501
  import { ShapePath as ShapePath28, ShapeGroup } from "lbrnts";
2502
+
2503
+ // lib/getManifold.ts
2487
2504
  var manifoldInstance = null;
2488
2505
  var getManifold = async () => {
2489
2506
  if (!manifoldInstance) {
2490
- const moduleName = "manifold-3d";
2491
- const ManifoldModule = (await import(
2492
- /* @vite-ignore */
2493
- moduleName
2494
- )).default;
2507
+ let ManifoldModule;
2508
+ try {
2509
+ const moduleName = "manifold-3d";
2510
+ ManifoldModule = (await import(
2511
+ /* @vite-ignore */
2512
+ moduleName
2513
+ )).default;
2514
+ } catch {
2515
+ try {
2516
+ const cdnUrl = "https://cdn.jsdelivr.net/npm/manifold-3d@3.0.0/manifold.js";
2517
+ ManifoldModule = (await import(
2518
+ /* @vite-ignore */
2519
+ cdnUrl
2520
+ )).default;
2521
+ } catch (cdnError) {
2522
+ throw new Error(
2523
+ `Failed to load manifold-3d: not available in Node.js or via CDN. ${cdnError}`
2524
+ );
2525
+ }
2526
+ }
2495
2527
  manifoldInstance = await ManifoldModule();
2496
2528
  manifoldInstance.setup();
2497
2529
  }
2498
2530
  return manifoldInstance;
2499
2531
  };
2532
+
2533
+ // lib/createCopperCutFillForLayer.ts
2500
2534
  var polygonToContours = (polygon) => {
2501
2535
  const contours = [];
2502
2536
  if (polygon instanceof Box4) {
@@ -2791,7 +2825,9 @@ var convertCircuitJsonToLbrn = async (circuitJson, options = {}) => {
2791
2825
  solderMaskMarginPercent,
2792
2826
  topCopperCutFillCutSetting,
2793
2827
  bottomCopperCutFillCutSetting,
2794
- copperCutFillMargin
2828
+ copperCutFillMargin,
2829
+ topTraceEndpoints: /* @__PURE__ */ new Set(),
2830
+ bottomTraceEndpoints: /* @__PURE__ */ new Set()
2795
2831
  };
2796
2832
  for (const net of Object.keys(connMap.netMap)) {
2797
2833
  ctx.topCutNetGeoms.set(net, []);
@@ -53,4 +53,9 @@ export interface ConvertContext {
53
53
 
54
54
  // Copper cut fill margin (how far to expand the copper outline for the cut fill band)
55
55
  copperCutFillMargin: number
56
+
57
+ // Track trace endpoint positions to avoid duplicate circles
58
+ // Key is "x,y" rounded to 6 decimal places
59
+ topTraceEndpoints: Set<string>
60
+ bottomTraceEndpoints: Set<string>
56
61
  }
@@ -2,22 +2,7 @@ import { Polygon, Box, Point } from "@flatten-js/core"
2
2
  import type { ConvertContext } from "./ConvertContext"
3
3
  import { polygonToShapePathData } from "./polygon-to-shape-path"
4
4
  import { ShapePath, ShapeGroup } from "lbrnts"
5
-
6
- // Lazy-load the manifold WASM module
7
- // Use dynamic import with computed string to prevent bundler from analyzing the import
8
- // This allows the browser build to succeed - the feature will fail gracefully at runtime
9
- let manifoldInstance: any = null
10
-
11
- const getManifold = async () => {
12
- if (!manifoldInstance) {
13
- // Use computed module name to prevent bundler from tracing the import
14
- const moduleName = "manifold-3d"
15
- const ManifoldModule = (await import(/* @vite-ignore */ moduleName)).default
16
- manifoldInstance = await ManifoldModule()
17
- manifoldInstance.setup() // Initialize the JS-friendly API
18
- }
19
- return manifoldInstance
20
- }
5
+ import { getManifold } from "./getManifold"
21
6
 
22
7
  type Contour = Array<[number, number]>
23
8
 
@@ -27,6 +27,14 @@ const createCirclePolygon = (
27
27
  return new Flatten.Polygon(points)
28
28
  }
29
29
 
30
+ /**
31
+ * Creates a position key for deduplication.
32
+ * Rounds to 6 decimal places to handle floating point precision.
33
+ */
34
+ const positionKey = (x: number, y: number): string => {
35
+ return `${x.toFixed(6)},${y.toFixed(6)}`
36
+ }
37
+
30
38
  export const addPcbTrace = (trace: PcbTrace, ctx: ConvertContext) => {
31
39
  const {
32
40
  topCutNetGeoms,
@@ -38,6 +46,8 @@ export const addPcbTrace = (trace: PcbTrace, ctx: ConvertContext) => {
38
46
  includeCopper,
39
47
  includeLayers,
40
48
  traceMargin,
49
+ topTraceEndpoints,
50
+ bottomTraceEndpoints,
41
51
  } = ctx
42
52
 
43
53
  // Only include traces when including copper
@@ -95,7 +105,10 @@ export const addPcbTrace = (trace: PcbTrace, ctx: ConvertContext) => {
95
105
  // Add circular pads at trace endpoints to ensure overlap at junctions
96
106
  // This is crucial for boolean union to properly merge traces that share endpoints
97
107
  // Use a slightly larger radius (1.1x) to ensure proper overlap despite floating point issues
108
+ // Deduplicate endpoints to avoid identical polygons that cause boolean operation failures
98
109
  const segments = layerSegments.get(layer)
110
+ const endpointSet =
111
+ layer === "top" ? topTraceEndpoints : bottomTraceEndpoints
99
112
  if (segments) {
100
113
  for (const segment of segments) {
101
114
  if (segment.length >= 2) {
@@ -103,19 +116,28 @@ export const addPcbTrace = (trace: PcbTrace, ctx: ConvertContext) => {
103
116
  const lastPoint = segment[segment.length - 1]!
104
117
  const radius = (traceWidth / 2) * 1.1 // Slightly larger for reliable overlap
105
118
 
106
- // Add circle at start point (translated by origin)
107
- const startCircle = createCirclePolygon(
108
- { x: firstPoint.x + origin.x, y: firstPoint.y + origin.y },
109
- radius,
110
- )
111
- cutNetGeoms.get(netId)?.push(startCircle)
112
-
113
- // Add circle at end point (translated by origin)
114
- const endCircle = createCirclePolygon(
115
- { x: lastPoint.x + origin.x, y: lastPoint.y + origin.y },
116
- radius,
117
- )
118
- cutNetGeoms.get(netId)?.push(endCircle)
119
+ // Add circle at start point (translated by origin) if not already added
120
+ const startX = firstPoint.x + origin.x
121
+ const startY = firstPoint.y + origin.y
122
+ const startKey = positionKey(startX, startY)
123
+ if (!endpointSet.has(startKey)) {
124
+ endpointSet.add(startKey)
125
+ const startCircle = createCirclePolygon(
126
+ { x: startX, y: startY },
127
+ radius,
128
+ )
129
+ cutNetGeoms.get(netId)?.push(startCircle)
130
+ }
131
+
132
+ // Add circle at end point (translated by origin) if not already added
133
+ const endX = lastPoint.x + origin.x
134
+ const endY = lastPoint.y + origin.y
135
+ const endKey = positionKey(endX, endY)
136
+ if (!endpointSet.has(endKey)) {
137
+ endpointSet.add(endKey)
138
+ const endCircle = createCirclePolygon({ x: endX, y: endY }, radius)
139
+ cutNetGeoms.get(netId)?.push(endCircle)
140
+ }
119
141
  }
120
142
  }
121
143
  }
@@ -0,0 +1,31 @@
1
+ // Lazy-load the manifold WASM module
2
+ // Supports both Node.js and browser environments with CDN fallback
3
+
4
+ let manifoldInstance: any = null
5
+
6
+ export const getManifold = async () => {
7
+ if (!manifoldInstance) {
8
+ let ManifoldModule: any
9
+
10
+ // Try Node.js import first (for CLI/tests)
11
+ try {
12
+ const moduleName = "manifold-3d"
13
+ ManifoldModule = (await import(/* @vite-ignore */ moduleName)).default
14
+ } catch {
15
+ // Fallback to CDN for browser environments
16
+ try {
17
+ const cdnUrl =
18
+ "https://cdn.jsdelivr.net/npm/manifold-3d@3.0.0/manifold.js"
19
+ ManifoldModule = (await import(/* @vite-ignore */ cdnUrl)).default
20
+ } catch (cdnError) {
21
+ throw new Error(
22
+ `Failed to load manifold-3d: not available in Node.js or via CDN. ${cdnError}`,
23
+ )
24
+ }
25
+ }
26
+
27
+ manifoldInstance = await ManifoldModule()
28
+ manifoldInstance.setup() // Initialize the JS-friendly API
29
+ }
30
+ return manifoldInstance
31
+ }
package/lib/index.ts CHANGED
@@ -257,6 +257,8 @@ export const convertCircuitJsonToLbrn = async (
257
257
  topCopperCutFillCutSetting,
258
258
  bottomCopperCutFillCutSetting,
259
259
  copperCutFillMargin,
260
+ topTraceEndpoints: new Set(),
261
+ bottomTraceEndpoints: new Set(),
260
262
  }
261
263
 
262
264
  // Initialize net geometry maps
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "circuit-json-to-lbrn",
3
3
  "main": "dist/index.js",
4
- "version": "0.0.43",
4
+ "version": "0.0.44",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "start": "bun run site/index.html",