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 +3 -1
- package/site/index.html +147 -0
- package/site/main.tsx +220 -0
- package/tsconfig.json +1 -1
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.
|
|
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!")
|