circuit-json-to-lbrn 0.0.3 → 0.0.5
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.d.ts +1 -0
- package/dist/index.js +64 -3
- package/lib/calculateBounds.ts +108 -0
- package/lib/index.ts +13 -1
- package/package.json +1 -1
- package/site/main.tsx +12 -4
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// lib/index.ts
|
|
2
2
|
import { LightBurnProject, CutSetting, ShapePath as ShapePath3 } from "lbrnts";
|
|
3
|
-
import { cju } from "@tscircuit/circuit-json-util";
|
|
3
|
+
import { cju as cju2 } from "@tscircuit/circuit-json-util";
|
|
4
4
|
|
|
5
5
|
// lib/element-handlers/addPlatedHole/addCirclePlatedHole.ts
|
|
6
6
|
import { ShapePath } from "lbrnts";
|
|
@@ -148,9 +148,65 @@ function polygonToShapePathData(polygon) {
|
|
|
148
148
|
return { verts, prims };
|
|
149
149
|
}
|
|
150
150
|
|
|
151
|
+
// lib/calculateBounds.ts
|
|
152
|
+
import { cju } from "@tscircuit/circuit-json-util";
|
|
153
|
+
var calculateCircuitBounds = (circuitJson) => {
|
|
154
|
+
const db = cju(circuitJson);
|
|
155
|
+
let minX = Infinity;
|
|
156
|
+
let minY = Infinity;
|
|
157
|
+
let maxX = -Infinity;
|
|
158
|
+
let maxY = -Infinity;
|
|
159
|
+
for (const smtpad of db.pcb_smtpad.list()) {
|
|
160
|
+
if (smtpad.shape === "rect") {
|
|
161
|
+
const halfWidth = smtpad.width / 2;
|
|
162
|
+
const halfHeight = smtpad.height / 2;
|
|
163
|
+
minX = Math.min(minX, smtpad.x - halfWidth);
|
|
164
|
+
minY = Math.min(minY, smtpad.y - halfHeight);
|
|
165
|
+
maxX = Math.max(maxX, smtpad.x + halfWidth);
|
|
166
|
+
maxY = Math.max(maxY, smtpad.y + halfHeight);
|
|
167
|
+
} else if (smtpad.shape === "circle") {
|
|
168
|
+
const radius = smtpad.radius;
|
|
169
|
+
minX = Math.min(minX, smtpad.x - radius);
|
|
170
|
+
minY = Math.min(minY, smtpad.y - radius);
|
|
171
|
+
maxX = Math.max(maxX, smtpad.x + radius);
|
|
172
|
+
maxY = Math.max(maxY, smtpad.y + radius);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
for (const trace of db.pcb_trace.list()) {
|
|
176
|
+
const isWidthPoint = (point) => "width" in point && typeof point.width === "number";
|
|
177
|
+
const halfWidth = trace.route_thickness_mode === "interpolated" ? 0 : (trace.route.find(isWidthPoint)?.width ?? 0) / 2;
|
|
178
|
+
for (const point of trace.route) {
|
|
179
|
+
const pointWidth = trace.route_thickness_mode === "interpolated" ? isWidthPoint(point) ? point.width / 2 : 0 : halfWidth;
|
|
180
|
+
minX = Math.min(minX, point.x - pointWidth);
|
|
181
|
+
minY = Math.min(minY, point.y - pointWidth);
|
|
182
|
+
maxX = Math.max(maxX, point.x + pointWidth);
|
|
183
|
+
maxY = Math.max(maxY, point.y + pointWidth);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
for (const hole of db.pcb_plated_hole.list()) {
|
|
187
|
+
if (hole.shape === "circle") {
|
|
188
|
+
const radius = hole.outer_diameter / 2;
|
|
189
|
+
minX = Math.min(minX, hole.x - radius);
|
|
190
|
+
minY = Math.min(minY, hole.y - radius);
|
|
191
|
+
maxX = Math.max(maxX, hole.x + radius);
|
|
192
|
+
maxY = Math.max(maxY, hole.y + radius);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
if (!isFinite(minX) || !isFinite(minY) || !isFinite(maxX) || !isFinite(maxY)) {
|
|
196
|
+
return { minX: 0, minY: 0, maxX: 0, maxY: 0 };
|
|
197
|
+
}
|
|
198
|
+
return { minX, minY, maxX, maxY };
|
|
199
|
+
};
|
|
200
|
+
var calculateOriginFromBounds = (bounds, margin) => {
|
|
201
|
+
const m = margin ?? 0.1;
|
|
202
|
+
const originX = bounds.minX < m ? -bounds.minX + m : 0;
|
|
203
|
+
const originY = bounds.minY < m ? -bounds.minY + m : 0;
|
|
204
|
+
return { x: originX, y: originY };
|
|
205
|
+
};
|
|
206
|
+
|
|
151
207
|
// lib/index.ts
|
|
152
208
|
var convertCircuitJsonToLbrn = (circuitJson, options = {}) => {
|
|
153
|
-
const db =
|
|
209
|
+
const db = cju2(circuitJson);
|
|
154
210
|
const project = new LightBurnProject({
|
|
155
211
|
appVersion: "1.7.03",
|
|
156
212
|
formatVersion: "1"
|
|
@@ -163,13 +219,18 @@ var convertCircuitJsonToLbrn = (circuitJson, options = {}) => {
|
|
|
163
219
|
});
|
|
164
220
|
project.children.push(copperCutSetting);
|
|
165
221
|
const connMap = getFullConnectivityMapFromCircuitJson(circuitJson);
|
|
222
|
+
let origin = options.origin;
|
|
223
|
+
if (!origin) {
|
|
224
|
+
const bounds = calculateCircuitBounds(circuitJson);
|
|
225
|
+
origin = calculateOriginFromBounds(bounds, options.margin);
|
|
226
|
+
}
|
|
166
227
|
const ctx = {
|
|
167
228
|
db,
|
|
168
229
|
project,
|
|
169
230
|
copperCutSetting,
|
|
170
231
|
connMap,
|
|
171
232
|
netGeoms: /* @__PURE__ */ new Map(),
|
|
172
|
-
origin
|
|
233
|
+
origin
|
|
173
234
|
};
|
|
174
235
|
for (const net of Object.keys(connMap.netMap)) {
|
|
175
236
|
ctx.netGeoms.set(net, []);
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import type { CircuitJson } from "circuit-json"
|
|
2
|
+
import { cju } from "@tscircuit/circuit-json-util"
|
|
3
|
+
|
|
4
|
+
export interface Bounds {
|
|
5
|
+
minX: number
|
|
6
|
+
minY: number
|
|
7
|
+
maxX: number
|
|
8
|
+
maxY: number
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Calculates the bounding box of all PCB elements in the circuit JSON
|
|
13
|
+
*/
|
|
14
|
+
export const calculateCircuitBounds = (circuitJson: CircuitJson): Bounds => {
|
|
15
|
+
const db = cju(circuitJson)
|
|
16
|
+
|
|
17
|
+
let minX = Infinity
|
|
18
|
+
let minY = Infinity
|
|
19
|
+
let maxX = -Infinity
|
|
20
|
+
let maxY = -Infinity
|
|
21
|
+
|
|
22
|
+
// Calculate bounds from SMT pads
|
|
23
|
+
for (const smtpad of db.pcb_smtpad.list()) {
|
|
24
|
+
if (smtpad.shape === "rect") {
|
|
25
|
+
const halfWidth = smtpad.width / 2
|
|
26
|
+
const halfHeight = smtpad.height / 2
|
|
27
|
+
|
|
28
|
+
minX = Math.min(minX, smtpad.x - halfWidth)
|
|
29
|
+
minY = Math.min(minY, smtpad.y - halfHeight)
|
|
30
|
+
maxX = Math.max(maxX, smtpad.x + halfWidth)
|
|
31
|
+
maxY = Math.max(maxY, smtpad.y + halfHeight)
|
|
32
|
+
} else if (smtpad.shape === "circle") {
|
|
33
|
+
const radius = smtpad.radius
|
|
34
|
+
|
|
35
|
+
minX = Math.min(minX, smtpad.x - radius)
|
|
36
|
+
minY = Math.min(minY, smtpad.y - radius)
|
|
37
|
+
maxX = Math.max(maxX, smtpad.x + radius)
|
|
38
|
+
maxY = Math.max(maxY, smtpad.y + radius)
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Calculate bounds from PCB traces
|
|
43
|
+
for (const trace of db.pcb_trace.list()) {
|
|
44
|
+
const isWidthPoint = (
|
|
45
|
+
point: (typeof trace.route)[number],
|
|
46
|
+
): point is (typeof trace.route)[number] & { width: number } =>
|
|
47
|
+
"width" in point && typeof point.width === "number"
|
|
48
|
+
|
|
49
|
+
const halfWidth =
|
|
50
|
+
trace.route_thickness_mode === "interpolated"
|
|
51
|
+
? 0
|
|
52
|
+
: (trace.route.find(isWidthPoint)?.width ?? 0) / 2
|
|
53
|
+
|
|
54
|
+
for (const point of trace.route) {
|
|
55
|
+
const pointWidth =
|
|
56
|
+
trace.route_thickness_mode === "interpolated"
|
|
57
|
+
? isWidthPoint(point)
|
|
58
|
+
? point.width / 2
|
|
59
|
+
: 0
|
|
60
|
+
: halfWidth
|
|
61
|
+
|
|
62
|
+
minX = Math.min(minX, point.x - pointWidth)
|
|
63
|
+
minY = Math.min(minY, point.y - pointWidth)
|
|
64
|
+
maxX = Math.max(maxX, point.x + pointWidth)
|
|
65
|
+
maxY = Math.max(maxY, point.y + pointWidth)
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Calculate bounds from plated holes
|
|
70
|
+
for (const hole of db.pcb_plated_hole.list()) {
|
|
71
|
+
if (hole.shape === "circle") {
|
|
72
|
+
const radius = hole.outer_diameter / 2
|
|
73
|
+
|
|
74
|
+
minX = Math.min(minX, hole.x - radius)
|
|
75
|
+
minY = Math.min(minY, hole.y - radius)
|
|
76
|
+
maxX = Math.max(maxX, hole.x + radius)
|
|
77
|
+
maxY = Math.max(maxY, hole.y + radius)
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// If no elements were found, return a default bounds
|
|
82
|
+
if (
|
|
83
|
+
!isFinite(minX) ||
|
|
84
|
+
!isFinite(minY) ||
|
|
85
|
+
!isFinite(maxX) ||
|
|
86
|
+
!isFinite(maxY)
|
|
87
|
+
) {
|
|
88
|
+
return { minX: 0, minY: 0, maxX: 0, maxY: 0 }
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return { minX, minY, maxX, maxY }
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Calculates the origin needed to shift all elements to the positive quadrant
|
|
96
|
+
* with a small margin
|
|
97
|
+
*/
|
|
98
|
+
export const calculateOriginFromBounds = (
|
|
99
|
+
bounds: Bounds,
|
|
100
|
+
margin?: number,
|
|
101
|
+
): { x: number; y: number } => {
|
|
102
|
+
const m = margin ?? 0.1
|
|
103
|
+
// If minimum coordinates are already positive, no shift needed (but add margin)
|
|
104
|
+
const originX = bounds.minX < m ? -bounds.minX + m : 0
|
|
105
|
+
const originY = bounds.minY < m ? -bounds.minY + m : 0
|
|
106
|
+
|
|
107
|
+
return { x: originX, y: originY }
|
|
108
|
+
}
|
package/lib/index.ts
CHANGED
|
@@ -8,6 +8,10 @@ import { addPcbTrace } from "./element-handlers/addPcbTrace"
|
|
|
8
8
|
import { getFullConnectivityMapFromCircuitJson } from "circuit-json-to-connectivity-map"
|
|
9
9
|
import { Polygon, Box, BooleanOperations } from "@flatten-js/core"
|
|
10
10
|
import { polygonToShapePathData } from "./polygon-to-shape-path"
|
|
11
|
+
import {
|
|
12
|
+
calculateCircuitBounds,
|
|
13
|
+
calculateOriginFromBounds,
|
|
14
|
+
} from "./calculateBounds"
|
|
11
15
|
// import { writeDebugSvg } from "./writeDebugSvg"
|
|
12
16
|
|
|
13
17
|
export const convertCircuitJsonToLbrn = (
|
|
@@ -15,6 +19,7 @@ export const convertCircuitJsonToLbrn = (
|
|
|
15
19
|
options: {
|
|
16
20
|
includeSilkscreen?: boolean
|
|
17
21
|
origin?: { x: number; y: number }
|
|
22
|
+
margin?: number
|
|
18
23
|
} = {},
|
|
19
24
|
): LightBurnProject => {
|
|
20
25
|
const db = cju(circuitJson)
|
|
@@ -33,13 +38,20 @@ export const convertCircuitJsonToLbrn = (
|
|
|
33
38
|
|
|
34
39
|
const connMap = getFullConnectivityMapFromCircuitJson(circuitJson)
|
|
35
40
|
|
|
41
|
+
// Auto-calculate origin if not provided to ensure all elements are in positive quadrant
|
|
42
|
+
let origin = options.origin
|
|
43
|
+
if (!origin) {
|
|
44
|
+
const bounds = calculateCircuitBounds(circuitJson)
|
|
45
|
+
origin = calculateOriginFromBounds(bounds, options.margin)
|
|
46
|
+
}
|
|
47
|
+
|
|
36
48
|
const ctx: ConvertContext = {
|
|
37
49
|
db,
|
|
38
50
|
project,
|
|
39
51
|
copperCutSetting,
|
|
40
52
|
connMap,
|
|
41
53
|
netGeoms: new Map(),
|
|
42
|
-
origin
|
|
54
|
+
origin,
|
|
43
55
|
}
|
|
44
56
|
|
|
45
57
|
for (const net of Object.keys(connMap.netMap)) {
|
package/package.json
CHANGED
package/site/main.tsx
CHANGED
|
@@ -7,6 +7,12 @@ import type { CircuitJson } from "circuit-json"
|
|
|
7
7
|
let currentLbrnProject: any = null
|
|
8
8
|
let currentCircuitJson: CircuitJson | null = null
|
|
9
9
|
|
|
10
|
+
function getErrorMessage(error: unknown): string {
|
|
11
|
+
if (error instanceof Error && error.message) return error.message
|
|
12
|
+
|
|
13
|
+
return "Unknown error"
|
|
14
|
+
}
|
|
15
|
+
|
|
10
16
|
// Get DOM elements
|
|
11
17
|
const dropArea = document.getElementById("dropArea") as HTMLDivElement
|
|
12
18
|
const fileInput = document.getElementById("fileInput") as HTMLInputElement
|
|
@@ -30,7 +36,9 @@ const originYInput = document.getElementById("originY") as HTMLInputElement
|
|
|
30
36
|
const includeSilkscreenInput = document.getElementById(
|
|
31
37
|
"includeSilkscreen",
|
|
32
38
|
) as HTMLInputElement
|
|
33
|
-
const reconvertBtn = document.getElementById(
|
|
39
|
+
const reconvertBtn = document.getElementById(
|
|
40
|
+
"reconvertBtn",
|
|
41
|
+
) as HTMLButtonElement
|
|
34
42
|
|
|
35
43
|
// Show error message
|
|
36
44
|
function showError(message: string) {
|
|
@@ -83,7 +91,7 @@ async function processFile(file: File) {
|
|
|
83
91
|
} catch (error) {
|
|
84
92
|
showLoading(false)
|
|
85
93
|
console.error("Error processing file:", error)
|
|
86
|
-
showError(`Error processing file: ${error
|
|
94
|
+
showError(`Error processing file: ${getErrorMessage(error)}`)
|
|
87
95
|
}
|
|
88
96
|
}
|
|
89
97
|
|
|
@@ -147,7 +155,7 @@ async function convertAndDisplay() {
|
|
|
147
155
|
} catch (error) {
|
|
148
156
|
showLoading(false)
|
|
149
157
|
console.error("Error converting:", error)
|
|
150
|
-
showError(`Error converting: ${error
|
|
158
|
+
showError(`Error converting: ${getErrorMessage(error)}`)
|
|
151
159
|
}
|
|
152
160
|
}
|
|
153
161
|
|
|
@@ -213,7 +221,7 @@ downloadBtn.addEventListener("click", () => {
|
|
|
213
221
|
URL.revokeObjectURL(url)
|
|
214
222
|
} catch (error) {
|
|
215
223
|
console.error("Error downloading file:", error)
|
|
216
|
-
showError(`Error downloading file: ${error
|
|
224
|
+
showError(`Error downloading file: ${getErrorMessage(error)}`)
|
|
217
225
|
}
|
|
218
226
|
})
|
|
219
227
|
|