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 CHANGED
@@ -7,6 +7,7 @@ declare const convertCircuitJsonToLbrn: (circuitJson: CircuitJson, options?: {
7
7
  x: number;
8
8
  y: number;
9
9
  };
10
+ margin?: number;
10
11
  }) => LightBurnProject;
11
12
 
12
13
  export { convertCircuitJsonToLbrn };
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 = cju(circuitJson);
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: options.origin ?? { x: 0, y: 0 }
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: options.origin ?? { x: 0, y: 0 },
54
+ origin,
43
55
  }
44
56
 
45
57
  for (const net of Object.keys(connMap.netMap)) {
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.3",
4
+ "version": "0.0.5",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "start": "bun run site/index.html",
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("reconvertBtn") as HTMLButtonElement
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.message || "Unknown 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.message || "Unknown 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.message || "Unknown error"}`)
224
+ showError(`Error downloading file: ${getErrorMessage(error)}`)
217
225
  }
218
226
  })
219
227