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 +53 -17
- package/lib/ConvertContext.ts +5 -0
- package/lib/createCopperCutFillForLayer.ts +1 -16
- package/lib/element-handlers/addPcbTrace/index.ts +35 -13
- package/lib/getManifold.ts +31 -0
- package/lib/index.ts +2 -0
- package/package.json +1 -1
- package/tests/examples/example05/__snapshots__/example05.snap.svg +1 -0
- package/tests/examples/example05/example05.circuit.json +9562 -0
- package/tests/examples/example05/example05.test.ts +31 -0
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
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
)
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
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
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
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, []);
|
package/lib/ConvertContext.ts
CHANGED
|
@@ -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
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
)
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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
|