circuit-json-to-lbrn 0.0.1 → 0.0.2
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/.github/workflows/bun-formatcheck.yml +26 -0
- package/.github/workflows/bun-pver-release.yml +71 -0
- package/.github/workflows/bun-test.yml +31 -0
- package/.github/workflows/bun-typecheck.yml +26 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +162 -162
- package/lib/ConvertContext.ts +10 -0
- package/lib/element-handlers/addPcbTrace/circle-to-polygon.ts +15 -0
- package/lib/element-handlers/addPcbTrace/index.ts +90 -0
- package/lib/element-handlers/addSmtPad/addRectSmtPad.ts +15 -26
- package/lib/index.ts +91 -4
- package/lib/polygon-to-shape-path.ts +49 -0
- package/lib/writeDebugSvg.ts +43 -0
- package/package.json +4 -2
- package/site/index.html +0 -0
- package/site/main.tsx +0 -0
- package/tests/assets/lga-interconnect.circuit.json +1681 -1321
- package/tests/examples/__snapshots__/lga-interconnect.snap.svg +2 -2
- package/tests/examples/__snapshots__/single-trace.snap.svg +8 -0
- package/tests/examples/lga-interconnect.test.ts +4 -0
- package/tests/examples/single-trace.test.ts +67 -0
- package/tests/fixtures/preload.ts +1 -1
- package/tests/svg.test.ts +10 -11
- package/.claude/settings.local.json +0 -9
- package/tests/plated-hole.test.ts +0 -70
|
@@ -1,37 +1,26 @@
|
|
|
1
|
+
import { Box } from "@flatten-js/core"
|
|
1
2
|
import type { ConvertContext } from "../../ConvertContext"
|
|
2
3
|
import type { PcbSmtPadRect } from "circuit-json"
|
|
3
4
|
import { ShapePath } from "lbrnts"
|
|
4
5
|
|
|
5
6
|
export const addRectSmtPad = (smtPad: PcbSmtPadRect, ctx: ConvertContext) => {
|
|
6
|
-
const { project, copperCutSetting } = ctx
|
|
7
|
+
const { project, copperCutSetting, connMap, netGeoms, origin } = ctx
|
|
7
8
|
|
|
8
|
-
const centerX = smtPad.x
|
|
9
|
-
const centerY = smtPad.y
|
|
9
|
+
const centerX = smtPad.x + origin.x
|
|
10
|
+
const centerY = smtPad.y + origin.y
|
|
10
11
|
const halfWidth = smtPad.width / 2
|
|
11
12
|
const halfHeight = smtPad.height / 2
|
|
12
13
|
|
|
13
|
-
|
|
14
|
-
const verts = [
|
|
15
|
-
{ x: centerX - halfWidth, y: centerY - halfHeight }, // Top-left
|
|
16
|
-
{ x: centerX + halfWidth, y: centerY - halfHeight }, // Top-right
|
|
17
|
-
{ x: centerX + halfWidth, y: centerY + halfHeight }, // Bottom-right
|
|
18
|
-
{ x: centerX - halfWidth, y: centerY + halfHeight }, // Bottom-left
|
|
19
|
-
]
|
|
14
|
+
const netId = connMap.getNetConnectedToId(smtPad.pcb_smtpad_id)!
|
|
20
15
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
cutIndex: copperCutSetting.index,
|
|
32
|
-
verts,
|
|
33
|
-
prims,
|
|
34
|
-
isClosed: true,
|
|
35
|
-
}),
|
|
36
|
-
)
|
|
16
|
+
ctx.netGeoms
|
|
17
|
+
.get(netId)
|
|
18
|
+
?.push(
|
|
19
|
+
new Box(
|
|
20
|
+
centerX - halfWidth,
|
|
21
|
+
centerY - halfHeight,
|
|
22
|
+
centerX + halfWidth,
|
|
23
|
+
centerY + halfHeight,
|
|
24
|
+
),
|
|
25
|
+
)
|
|
37
26
|
}
|
package/lib/index.ts
CHANGED
|
@@ -1,13 +1,21 @@
|
|
|
1
1
|
import type { CircuitJson } from "circuit-json"
|
|
2
|
-
import { LightBurnProject, CutSetting } from "lbrnts"
|
|
2
|
+
import { LightBurnProject, CutSetting, ShapePath } from "lbrnts"
|
|
3
3
|
import { cju } from "@tscircuit/circuit-json-util"
|
|
4
4
|
import type { ConvertContext } from "./ConvertContext"
|
|
5
5
|
import { addPlatedHole } from "./element-handlers/addPlatedHole"
|
|
6
6
|
import { addSmtPad } from "./element-handlers/addSmtPad"
|
|
7
|
+
import { addPcbTrace } from "./element-handlers/addPcbTrace"
|
|
8
|
+
import { getFullConnectivityMapFromCircuitJson } from "circuit-json-to-connectivity-map"
|
|
9
|
+
import { Polygon, Box, BooleanOperations } from "@flatten-js/core"
|
|
10
|
+
import { polygonToShapePathData } from "./polygon-to-shape-path"
|
|
11
|
+
// import { writeDebugSvg } from "./writeDebugSvg"
|
|
7
12
|
|
|
8
13
|
export const convertCircuitJsonToLbrn = (
|
|
9
14
|
circuitJson: CircuitJson,
|
|
10
|
-
options: {
|
|
15
|
+
options: {
|
|
16
|
+
includeSilkscreen?: boolean
|
|
17
|
+
origin?: { x: number; y: number }
|
|
18
|
+
} = {},
|
|
11
19
|
): LightBurnProject => {
|
|
12
20
|
const db = cju(circuitJson)
|
|
13
21
|
const project = new LightBurnProject({
|
|
@@ -23,19 +31,98 @@ export const convertCircuitJsonToLbrn = (
|
|
|
23
31
|
})
|
|
24
32
|
project.children.push(copperCutSetting)
|
|
25
33
|
|
|
34
|
+
const connMap = getFullConnectivityMapFromCircuitJson(circuitJson)
|
|
35
|
+
|
|
26
36
|
const ctx: ConvertContext = {
|
|
27
37
|
db,
|
|
28
38
|
project,
|
|
29
39
|
copperCutSetting,
|
|
40
|
+
connMap,
|
|
41
|
+
netGeoms: new Map(),
|
|
42
|
+
origin: options.origin ?? { x: 0, y: 0 },
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
for (const net of Object.keys(connMap.netMap)) {
|
|
46
|
+
ctx.netGeoms.set(net, [])
|
|
30
47
|
}
|
|
31
48
|
|
|
32
49
|
for (const smtpad of db.pcb_smtpad.list()) {
|
|
33
50
|
addSmtPad(smtpad, ctx)
|
|
34
51
|
}
|
|
35
52
|
|
|
36
|
-
for (const platedHole of db.pcb_plated_hole.list()) {
|
|
37
|
-
|
|
53
|
+
// for (const platedHole of db.pcb_plated_hole.list()) {
|
|
54
|
+
// addPlatedHole(platedHole, ctx)
|
|
55
|
+
// }
|
|
56
|
+
|
|
57
|
+
for (const trace of db.pcb_trace.list()) {
|
|
58
|
+
addPcbTrace(trace, ctx)
|
|
38
59
|
}
|
|
39
60
|
|
|
61
|
+
// Draw each individual shape geometry as a ShapePath
|
|
62
|
+
// FOR DEBUGGING!!!
|
|
63
|
+
// for (const net of Object.keys(connMap.netMap)) {
|
|
64
|
+
// const netGeoms = ctx.netGeoms.get(net)!
|
|
65
|
+
|
|
66
|
+
// if (netGeoms.length === 0) {
|
|
67
|
+
// continue
|
|
68
|
+
// }
|
|
69
|
+
|
|
70
|
+
// for (const geom of netGeoms) {
|
|
71
|
+
// // Convert Box to Polygon if needed
|
|
72
|
+
// const polygon = geom instanceof Box ? new Polygon(geom) : geom
|
|
73
|
+
|
|
74
|
+
// // Convert the polygon to verts and prims
|
|
75
|
+
// const { verts, prims } = polygonToShapePathData(polygon)
|
|
76
|
+
|
|
77
|
+
// project.children.push(
|
|
78
|
+
// new ShapePath({
|
|
79
|
+
// cutIndex: copperCutSetting.index,
|
|
80
|
+
// verts,
|
|
81
|
+
// prims,
|
|
82
|
+
// isClosed: false,
|
|
83
|
+
// }),
|
|
84
|
+
// )
|
|
85
|
+
// }
|
|
86
|
+
// }
|
|
87
|
+
|
|
88
|
+
// Create a union of all the net geoms, and add to project
|
|
89
|
+
for (const net of Object.keys(connMap.netMap)) {
|
|
90
|
+
const netGeoms = ctx.netGeoms.get(net)!
|
|
91
|
+
|
|
92
|
+
if (netGeoms.length === 0) {
|
|
93
|
+
continue
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
let union = netGeoms[0]!
|
|
97
|
+
if (union instanceof Box) {
|
|
98
|
+
union = new Polygon(union)
|
|
99
|
+
}
|
|
100
|
+
for (const geom of netGeoms.slice(1)) {
|
|
101
|
+
if (geom instanceof Polygon) {
|
|
102
|
+
union = BooleanOperations.unify(union, geom)
|
|
103
|
+
} else if (geom instanceof Box) {
|
|
104
|
+
union = BooleanOperations.unify(union, new Polygon(geom))
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// DEBUGGING ONLY!!!
|
|
109
|
+
// if (netGeoms.length > 1) {
|
|
110
|
+
// writeDebugSvg(net, union)
|
|
111
|
+
// }
|
|
112
|
+
|
|
113
|
+
for (const island of union.splitToIslands()) {
|
|
114
|
+
// Convert the polygon to verts and prims
|
|
115
|
+
const { verts, prims } = polygonToShapePathData(island)
|
|
116
|
+
|
|
117
|
+
project.children.push(
|
|
118
|
+
new ShapePath({
|
|
119
|
+
cutIndex: copperCutSetting.index,
|
|
120
|
+
verts,
|
|
121
|
+
prims,
|
|
122
|
+
isClosed: false,
|
|
123
|
+
}),
|
|
124
|
+
)
|
|
125
|
+
}
|
|
126
|
+
}
|
|
40
127
|
return project
|
|
41
128
|
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { Polygon } from "@flatten-js/core"
|
|
2
|
+
|
|
3
|
+
export interface ShapePathData {
|
|
4
|
+
verts: Array<{ x: number; y: number }>
|
|
5
|
+
prims: Array<{ type: number }>
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Converts a flattenjs Polygon to verts and prims arrays for use with ShapePath
|
|
10
|
+
* @param polygon The flattenjs Polygon to convert
|
|
11
|
+
* @returns Object containing verts and prims arrays
|
|
12
|
+
*/
|
|
13
|
+
export function polygonToShapePathData(polygon: Polygon): ShapePathData {
|
|
14
|
+
const verts: Array<{ x: number; y: number }> = []
|
|
15
|
+
const prims: Array<{ type: number }> = []
|
|
16
|
+
|
|
17
|
+
// Get SVG representation and extract path data
|
|
18
|
+
const svgString = polygon.svg()
|
|
19
|
+
const dAttributeMatch = svgString.match(/\bd="([^"]+)"/)
|
|
20
|
+
|
|
21
|
+
if (!dAttributeMatch || !dAttributeMatch[1]) {
|
|
22
|
+
return { verts, prims }
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const pathData = dAttributeMatch[1].trim()
|
|
26
|
+
|
|
27
|
+
// Parse SVG path commands (M, L, z)
|
|
28
|
+
const commands = pathData.match(/[MLz][^MLz]*/g) || []
|
|
29
|
+
|
|
30
|
+
for (const command of commands) {
|
|
31
|
+
const type = command[0]
|
|
32
|
+
const coords = command.slice(1).trim()
|
|
33
|
+
|
|
34
|
+
if (type === "M" || type === "L") {
|
|
35
|
+
// Parse coordinates (format: "x,y")
|
|
36
|
+
const parts = coords.split(",")
|
|
37
|
+
const x = Number(parts[0])
|
|
38
|
+
const y = Number(parts[1])
|
|
39
|
+
|
|
40
|
+
if (!Number.isNaN(x) && !Number.isNaN(y)) {
|
|
41
|
+
verts.push({ x, y })
|
|
42
|
+
prims.push({ type: 0 }) // 0 = LineTo
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
// 'z' closes the path, no action needed
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return { verts, prims }
|
|
49
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Polygon } from "@flatten-js/core"
|
|
2
|
+
|
|
3
|
+
export const writeDebugSvg = (netName: string, polygon: Polygon) => {
|
|
4
|
+
// Calculate bounds from all points in the polygon
|
|
5
|
+
let minX = Infinity
|
|
6
|
+
let minY = Infinity
|
|
7
|
+
let maxX = -Infinity
|
|
8
|
+
let maxY = -Infinity
|
|
9
|
+
|
|
10
|
+
for (const face of polygon.faces) {
|
|
11
|
+
for (const edge of face.edges) {
|
|
12
|
+
const start = edge.start
|
|
13
|
+
const end = edge.end
|
|
14
|
+
|
|
15
|
+
minX = Math.min(minX, start.x, end.x)
|
|
16
|
+
minY = Math.min(minY, start.y, end.y)
|
|
17
|
+
maxX = Math.max(maxX, start.x, end.x)
|
|
18
|
+
maxY = Math.max(maxY, start.y, end.y)
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Calculate bounds with 15% margin
|
|
23
|
+
const width = maxX - minX
|
|
24
|
+
const height = maxY - minY
|
|
25
|
+
const margin = 0.15
|
|
26
|
+
|
|
27
|
+
const viewBoxX = minX - width * margin
|
|
28
|
+
const viewBoxY = minY - height * margin
|
|
29
|
+
const viewBoxWidth = width * (1 + 2 * margin)
|
|
30
|
+
const viewBoxHeight = height * (1 + 2 * margin)
|
|
31
|
+
|
|
32
|
+
// Calculate stroke width as 0.5% of max viewBox dimension
|
|
33
|
+
const maxDimension = Math.max(viewBoxWidth, viewBoxHeight)
|
|
34
|
+
const strokeWidth = maxDimension * 0.001
|
|
35
|
+
|
|
36
|
+
const svgContent = `<svg width="800" height="800" style="background-color: #ffffff;" viewBox="${viewBoxX} ${viewBoxY} ${viewBoxWidth} ${viewBoxHeight}" xmlns="http://www.w3.org/2000/svg">
|
|
37
|
+
<g stroke-width="${strokeWidth}">
|
|
38
|
+
${polygon.svg()}
|
|
39
|
+
</g>
|
|
40
|
+
</svg>`
|
|
41
|
+
|
|
42
|
+
Bun.write(`debug-output/${netName}.svg`, svgContent)
|
|
43
|
+
}
|
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.
|
|
4
|
+
"version": "0.0.2",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"build": "tsup-node ./lib/index.ts --dts --format esm",
|
|
@@ -10,6 +10,8 @@
|
|
|
10
10
|
},
|
|
11
11
|
"devDependencies": {
|
|
12
12
|
"@biomejs/biome": "^2.3.6",
|
|
13
|
+
"@flatten-js/core": "^1.6.8",
|
|
14
|
+
"@flatten-js/polygon-offset": "^1.1.4",
|
|
13
15
|
"@tscircuit/circuit-json-util": "^0.0.72",
|
|
14
16
|
"@types/bun": "latest",
|
|
15
17
|
"bun-match-svg": "^0.0.14",
|
|
@@ -24,6 +26,6 @@
|
|
|
24
26
|
"typescript": "^5"
|
|
25
27
|
},
|
|
26
28
|
"dependencies": {
|
|
27
|
-
"lbrnts": "^0.0.
|
|
29
|
+
"lbrnts": "^0.0.6"
|
|
28
30
|
}
|
|
29
31
|
}
|
package/site/index.html
ADDED
|
File without changes
|
package/site/main.tsx
ADDED
|
File without changes
|