circuit-json-to-lbrn 0.0.2 → 0.0.3

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/package.json CHANGED
@@ -1,9 +1,11 @@
1
1
  {
2
2
  "name": "circuit-json-to-lbrn",
3
3
  "main": "dist/index.js",
4
- "version": "0.0.2",
4
+ "version": "0.0.3",
5
5
  "type": "module",
6
6
  "scripts": {
7
+ "start": "bun run site/index.html",
8
+ "build:site": "bun build site/index.html --outdir site-dist",
7
9
  "build": "tsup-node ./lib/index.ts --dts --format esm",
8
10
  "format": "biome format --write .",
9
11
  "format:check": "biome format ."
package/site/index.html CHANGED
@@ -0,0 +1,147 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Circuit JSON to LBRN Converter</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <style>
9
+ @keyframes spin {
10
+ 0% {
11
+ transform: rotate(0deg);
12
+ }
13
+ 100% {
14
+ transform: rotate(360deg);
15
+ }
16
+ }
17
+ .spinner {
18
+ animation: spin 1s linear infinite;
19
+ }
20
+ .svg-container svg {
21
+ width: 100%;
22
+ height: 100%;
23
+ object-fit: contain;
24
+ }
25
+ </style>
26
+ </head>
27
+ <body class="bg-gray-900 text-white min-h-screen p-8">
28
+ <div class="max-w-7xl mx-auto">
29
+ <h1 class="text-4xl font-bold mb-2 text-blue-400">
30
+ Circuit JSON to LBRN Converter
31
+ </h1>
32
+ <p class="text-gray-400 mb-8 text-lg">
33
+ Convert your circuit designs to LightBurn format
34
+ </p>
35
+
36
+ <div id="errorMessage" class="hidden bg-red-500 text-white p-4 rounded-lg mb-4"></div>
37
+
38
+ <div
39
+ id="dropArea"
40
+ class="border-3 border-dashed border-blue-500 rounded-xl p-12 text-center cursor-pointer transition-all bg-gray-800 hover:bg-gray-750 hover:border-blue-400 hover:scale-[1.01] mb-6"
41
+ >
42
+ <div class="text-5xl mb-4">📁</div>
43
+ <div class="text-xl text-gray-300 mb-2">
44
+ Drop your Circuit JSON file here or click to browse
45
+ </div>
46
+ <div class="text-sm text-gray-500">Supports .json files</div>
47
+ <input
48
+ type="file"
49
+ id="fileInput"
50
+ class="hidden"
51
+ accept=".json,application/json"
52
+ />
53
+ </div>
54
+
55
+ <div id="loading" class="hidden text-center py-8">
56
+ <div
57
+ class="spinner border-4 border-gray-700 border-t-blue-500 rounded-full w-10 h-10 mx-auto mb-4"
58
+ ></div>
59
+ <div>Processing your file...</div>
60
+ </div>
61
+
62
+ <div id="optionsContainer" class="hidden bg-gray-800 rounded-lg p-6 mb-6">
63
+ <h3 class="text-lg font-semibold mb-4 text-blue-400">Conversion Options</h3>
64
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
65
+ <div>
66
+ <label class="block text-sm font-medium text-gray-300 mb-2">
67
+ Origin X (mm)
68
+ </label>
69
+ <input
70
+ type="number"
71
+ id="originX"
72
+ value="0"
73
+ step="0.1"
74
+ class="w-full px-4 py-2 bg-gray-700 border border-gray-600 rounded-lg text-white focus:outline-none focus:ring-2 focus:ring-blue-500"
75
+ />
76
+ </div>
77
+ <div>
78
+ <label class="block text-sm font-medium text-gray-300 mb-2">
79
+ Origin Y (mm)
80
+ </label>
81
+ <input
82
+ type="number"
83
+ id="originY"
84
+ value="0"
85
+ step="0.1"
86
+ class="w-full px-4 py-2 bg-gray-700 border border-gray-600 rounded-lg text-white focus:outline-none focus:ring-2 focus:ring-blue-500"
87
+ />
88
+ </div>
89
+ <div class="md:col-span-2">
90
+ <label class="flex items-center cursor-pointer">
91
+ <input
92
+ type="checkbox"
93
+ id="includeSilkscreen"
94
+ class="w-5 h-5 text-blue-500 bg-gray-700 border-gray-600 rounded focus:ring-blue-500 focus:ring-2"
95
+ />
96
+ <span class="ml-3 text-sm font-medium text-gray-300">
97
+ Include Silkscreen Layer
98
+ </span>
99
+ </label>
100
+ </div>
101
+ </div>
102
+ <button
103
+ id="reconvertBtn"
104
+ class="mt-4 bg-blue-600 hover:bg-blue-700 text-white px-6 py-2 rounded-lg transition-colors"
105
+ >
106
+ Apply Changes
107
+ </button>
108
+ </div>
109
+
110
+ <div class="flex gap-4 items-center mb-6">
111
+ <button
112
+ id="downloadBtn"
113
+ disabled
114
+ class="bg-blue-600 hover:bg-blue-700 disabled:bg-gray-700 disabled:cursor-not-allowed text-white px-6 py-3 rounded-lg font-semibold transition-all disabled:opacity-50 flex items-center gap-2"
115
+ >
116
+ <span>⬇</span>
117
+ <span>Download LBRN File</span>
118
+ </button>
119
+ </div>
120
+
121
+ <div id="previewContainer" class="hidden grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">
122
+ <div class="bg-gray-800 rounded-xl p-6 border border-gray-700">
123
+ <h3 class="text-lg font-semibold mb-4 text-blue-400">
124
+ Circuit JSON Preview
125
+ </h3>
126
+ <div
127
+ id="circuitSvgContainer"
128
+ class="svg-container bg-white rounded-lg p-4 h-[500px] flex items-center justify-center overflow-auto"
129
+ >
130
+ <div class="text-gray-400">No file loaded</div>
131
+ </div>
132
+ </div>
133
+ <div class="bg-gray-800 rounded-xl p-6 border border-gray-700">
134
+ <h3 class="text-lg font-semibold mb-4 text-blue-400">LBRN Preview</h3>
135
+ <div
136
+ id="lbrnSvgContainer"
137
+ class="svg-container bg-white rounded-lg p-4 h-[500px] flex items-center justify-center overflow-auto"
138
+ >
139
+ <div class="text-gray-400">No file loaded</div>
140
+ </div>
141
+ </div>
142
+ </div>
143
+ </div>
144
+
145
+ <script type="module" src="./main.tsx"></script>
146
+ </body>
147
+ </html>
package/site/main.tsx CHANGED
@@ -0,0 +1,220 @@
1
+ import { convertCircuitJsonToLbrn } from "../lib/index.ts"
2
+ import { convertCircuitJsonToPcbSvg } from "circuit-to-svg"
3
+ import { generateLightBurnSvg } from "lbrnts"
4
+ import type { CircuitJson } from "circuit-json"
5
+
6
+ // Global state
7
+ let currentLbrnProject: any = null
8
+ let currentCircuitJson: CircuitJson | null = null
9
+
10
+ // Get DOM elements
11
+ const dropArea = document.getElementById("dropArea") as HTMLDivElement
12
+ const fileInput = document.getElementById("fileInput") as HTMLInputElement
13
+ const downloadBtn = document.getElementById("downloadBtn") as HTMLButtonElement
14
+ const previewContainer = document.getElementById(
15
+ "previewContainer",
16
+ ) as HTMLDivElement
17
+ const circuitSvgContainer = document.getElementById(
18
+ "circuitSvgContainer",
19
+ ) as HTMLDivElement
20
+ const lbrnSvgContainer = document.getElementById(
21
+ "lbrnSvgContainer",
22
+ ) as HTMLDivElement
23
+ const errorMessage = document.getElementById("errorMessage") as HTMLDivElement
24
+ const loading = document.getElementById("loading") as HTMLDivElement
25
+ const optionsContainer = document.getElementById(
26
+ "optionsContainer",
27
+ ) as HTMLDivElement
28
+ const originXInput = document.getElementById("originX") as HTMLInputElement
29
+ const originYInput = document.getElementById("originY") as HTMLInputElement
30
+ const includeSilkscreenInput = document.getElementById(
31
+ "includeSilkscreen",
32
+ ) as HTMLInputElement
33
+ const reconvertBtn = document.getElementById("reconvertBtn") as HTMLButtonElement
34
+
35
+ // Show error message
36
+ function showError(message: string) {
37
+ errorMessage.textContent = message
38
+ errorMessage.classList.remove("hidden")
39
+ setTimeout(() => {
40
+ errorMessage.classList.add("hidden")
41
+ }, 5000)
42
+ }
43
+
44
+ // Show loading state
45
+ function showLoading(show: boolean) {
46
+ if (show) {
47
+ loading.classList.remove("hidden")
48
+ previewContainer.classList.add("hidden")
49
+ downloadBtn.disabled = true
50
+ } else {
51
+ loading.classList.add("hidden")
52
+ }
53
+ }
54
+
55
+ // Get conversion options from UI
56
+ function getConversionOptions() {
57
+ return {
58
+ includeSilkscreen: includeSilkscreenInput.checked,
59
+ origin: {
60
+ x: parseFloat(originXInput.value) || 0,
61
+ y: parseFloat(originYInput.value) || 0,
62
+ },
63
+ }
64
+ }
65
+
66
+ // Process the uploaded file
67
+ async function processFile(file: File) {
68
+ try {
69
+ showLoading(true)
70
+ errorMessage.classList.add("hidden")
71
+
72
+ // Read the file
73
+ const fileContent = await file.text()
74
+ const circuitJson: CircuitJson = JSON.parse(fileContent)
75
+
76
+ currentCircuitJson = circuitJson
77
+
78
+ // Show options container
79
+ optionsContainer.classList.remove("hidden")
80
+
81
+ // Convert to LBRN with options
82
+ await convertAndDisplay()
83
+ } catch (error) {
84
+ showLoading(false)
85
+ console.error("Error processing file:", error)
86
+ showError(`Error processing file: ${error.message || "Unknown error"}`)
87
+ }
88
+ }
89
+
90
+ // Convert circuit JSON to LBRN and display previews
91
+ async function convertAndDisplay() {
92
+ if (!currentCircuitJson) return
93
+
94
+ try {
95
+ showLoading(true)
96
+
97
+ const options = getConversionOptions()
98
+
99
+ // Apply origin offset to circuit JSON if needed
100
+ let processedCircuitJson = currentCircuitJson
101
+ if (options.origin.x !== 0 || options.origin.y !== 0) {
102
+ processedCircuitJson = JSON.parse(JSON.stringify(currentCircuitJson))
103
+ // Apply offset to all elements with x/y coordinates
104
+ for (const element of processedCircuitJson as any[]) {
105
+ if (element.center) {
106
+ element.center.x += options.origin.x
107
+ element.center.y += options.origin.y
108
+ }
109
+ if (element.x !== undefined) {
110
+ element.x += options.origin.x
111
+ }
112
+ if (element.y !== undefined) {
113
+ element.y += options.origin.y
114
+ }
115
+ if (element.route) {
116
+ for (const point of element.route) {
117
+ if (point.x !== undefined) point.x += options.origin.x
118
+ if (point.y !== undefined) point.y += options.origin.y
119
+ }
120
+ }
121
+ }
122
+ }
123
+
124
+ // Convert to LBRN
125
+ console.log("Converting to LBRN with options:", options)
126
+ currentLbrnProject = convertCircuitJsonToLbrn(processedCircuitJson, {
127
+ includeSilkscreen: options.includeSilkscreen,
128
+ })
129
+
130
+ // Generate SVGs
131
+ console.log("Generating Circuit JSON SVG...")
132
+ const circuitSvg = await convertCircuitJsonToPcbSvg(processedCircuitJson)
133
+
134
+ console.log("Generating LBRN SVG...")
135
+ const lbrnSvg = await generateLightBurnSvg(currentLbrnProject)
136
+
137
+ // Display SVGs
138
+ circuitSvgContainer.innerHTML = circuitSvg
139
+ lbrnSvgContainer.innerHTML = lbrnSvg
140
+
141
+ // Show preview and enable download
142
+ previewContainer.classList.remove("hidden")
143
+ downloadBtn.disabled = false
144
+ showLoading(false)
145
+
146
+ console.log("Conversion complete!")
147
+ } catch (error) {
148
+ showLoading(false)
149
+ console.error("Error converting:", error)
150
+ showError(`Error converting: ${error.message || "Unknown error"}`)
151
+ }
152
+ }
153
+
154
+ // Handle file selection
155
+ fileInput.addEventListener("change", (e) => {
156
+ const file = (e.target as HTMLInputElement).files?.[0]
157
+ if (file) {
158
+ processFile(file)
159
+ }
160
+ })
161
+
162
+ // Handle drop area click
163
+ dropArea.addEventListener("click", () => {
164
+ fileInput.click()
165
+ })
166
+
167
+ // Handle drag and drop
168
+ dropArea.addEventListener("dragover", (e) => {
169
+ e.preventDefault()
170
+ dropArea.classList.add("border-blue-400")
171
+ })
172
+
173
+ dropArea.addEventListener("dragleave", () => {
174
+ dropArea.classList.remove("border-blue-400")
175
+ })
176
+
177
+ dropArea.addEventListener("drop", (e) => {
178
+ e.preventDefault()
179
+ dropArea.classList.remove("border-blue-400")
180
+
181
+ const file = e.dataTransfer?.files?.[0]
182
+ if (file) {
183
+ if (file.type === "application/json" || file.name.endsWith(".json")) {
184
+ processFile(file)
185
+ } else {
186
+ showError("Please upload a JSON file")
187
+ }
188
+ }
189
+ })
190
+
191
+ // Handle reconvert button
192
+ reconvertBtn.addEventListener("click", () => {
193
+ convertAndDisplay()
194
+ })
195
+
196
+ // Handle download button
197
+ downloadBtn.addEventListener("click", () => {
198
+ if (!currentLbrnProject) {
199
+ showError("No LBRN project to download")
200
+ return
201
+ }
202
+
203
+ try {
204
+ const lbrnString = currentLbrnProject.getString()
205
+ const blob = new Blob([lbrnString], { type: "application/xml" })
206
+ const url = URL.createObjectURL(blob)
207
+ const a = document.createElement("a")
208
+ a.href = url
209
+ a.download = "circuit.lbrn2"
210
+ document.body.appendChild(a)
211
+ a.click()
212
+ document.body.removeChild(a)
213
+ URL.revokeObjectURL(url)
214
+ } catch (error) {
215
+ console.error("Error downloading file:", error)
216
+ showError(`Error downloading file: ${error.message || "Unknown error"}`)
217
+ }
218
+ })
219
+
220
+ console.log("Circuit JSON to LBRN Converter loaded!")
package/tsconfig.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "compilerOptions": {
3
3
  // Environment setup & latest features
4
- "lib": ["ESNext"],
4
+ "lib": ["ESNext", "DOM"],
5
5
  "target": "ESNext",
6
6
  "module": "Preserve",
7
7
  "moduleDetection": "force",