circuit-json-to-lbrn 0.0.23 → 0.0.25

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/README.md CHANGED
@@ -38,6 +38,7 @@ const defaultLbrn = convertCircuitJsonToLbrn(circuitJson)
38
38
  - `margin?: number` - Set the margin around the PCB
39
39
  - `traceMargin?: number` - Clearance margin around traces in mm (requires `includeCopper: true`)
40
40
  - `laserSpotSize?: number` - Laser spot size in mm for crosshatch spacing (default: `0.005`)
41
+ - `laserProfile?: { copper?: { speed?: number; numPasses?: number; frequency?: number; pulseWidth?: number }; board?: { speed?: number; numPasses?: number; frequency?: number; pulseWidth?: number } }` - Custom laser cut settings for copper and board operations. Defaults from GitHub issue: copper (speed: 300 mm/s, numPasses: 100, frequency: 20 kHz, pulseWidth: 1 ns), board (speed: 20 mm/s, numPasses: 100, frequency: 20 kHz, pulseWidth: 1 ns). Allows per-user customization for different lasers/lenses.
41
42
  - `includeLayers?: Array<"top" | "bottom">` - Specify which layers to include (default: `["top", "bottom"]`)
42
43
 
43
44
  ## Soldermask Support
package/dist/index.d.ts CHANGED
@@ -14,6 +14,20 @@ declare const convertCircuitJsonToLbrn: (circuitJson: CircuitJson, options?: {
14
14
  includeLayers?: Array<"top" | "bottom">;
15
15
  traceMargin?: number;
16
16
  laserSpotSize?: number;
17
+ laserProfile?: {
18
+ copper?: {
19
+ speed?: number;
20
+ numPasses?: number;
21
+ frequency?: number;
22
+ pulseWidth?: number;
23
+ };
24
+ board?: {
25
+ speed?: number;
26
+ numPasses?: number;
27
+ frequency?: number;
28
+ pulseWidth?: number;
29
+ };
30
+ };
17
31
  }) => LightBurnProject;
18
32
 
19
33
  export { convertCircuitJsonToLbrn };
package/dist/index.js CHANGED
@@ -2548,6 +2548,21 @@ var convertCircuitJsonToLbrn = (circuitJson, options = {}) => {
2548
2548
  const includeCopper = options.includeCopper ?? true;
2549
2549
  const includeSoldermask = options.includeSoldermask ?? false;
2550
2550
  const soldermaskMargin = options.soldermaskMargin ?? 0;
2551
+ const laserProfile = options.laserProfile;
2552
+ const defaultCopperSettings = {
2553
+ speed: 300,
2554
+ numPasses: 100,
2555
+ frequency: 2e4,
2556
+ pulseWidth: 1e-9
2557
+ };
2558
+ const defaultBoardSettings = {
2559
+ speed: 20,
2560
+ numPasses: 100,
2561
+ frequency: 2e4,
2562
+ pulseWidth: 1e-9
2563
+ };
2564
+ const copperSettings = { ...defaultCopperSettings, ...laserProfile?.copper };
2565
+ const boardSettings = { ...defaultBoardSettings, ...laserProfile?.board };
2551
2566
  const shouldGenerateTraceClearanceZones = traceMargin > 0 && includeCopper;
2552
2567
  if (traceMargin > 0 && !includeCopper) {
2553
2568
  throw new Error("traceMargin requires includeCopper to be true");
@@ -2555,22 +2570,28 @@ var convertCircuitJsonToLbrn = (circuitJson, options = {}) => {
2555
2570
  const topCopperCutSetting = new CutSetting({
2556
2571
  index: 0,
2557
2572
  name: "Cut Top Copper",
2558
- numPasses: 12,
2559
- speed: 100
2573
+ numPasses: copperSettings.numPasses,
2574
+ speed: copperSettings.speed,
2575
+ frequency: copperSettings.frequency,
2576
+ pulseWidth: copperSettings.pulseWidth
2560
2577
  });
2561
2578
  project.children.push(topCopperCutSetting);
2562
2579
  const bottomCopperCutSetting = new CutSetting({
2563
2580
  index: 1,
2564
2581
  name: "Cut Bottom Copper",
2565
- numPasses: 12,
2566
- speed: 100
2582
+ numPasses: copperSettings.numPasses,
2583
+ speed: copperSettings.speed,
2584
+ frequency: copperSettings.frequency,
2585
+ pulseWidth: copperSettings.pulseWidth
2567
2586
  });
2568
2587
  project.children.push(bottomCopperCutSetting);
2569
2588
  const throughBoardCutSetting = new CutSetting({
2570
2589
  index: 2,
2571
2590
  name: "Cut Through Board",
2572
- numPasses: 3,
2573
- speed: 50
2591
+ numPasses: boardSettings.numPasses,
2592
+ speed: boardSettings.speed,
2593
+ frequency: boardSettings.frequency,
2594
+ pulseWidth: boardSettings.pulseWidth
2574
2595
  });
2575
2596
  project.children.push(throughBoardCutSetting);
2576
2597
  const soldermaskCutSetting = new CutSetting({
package/lib/index.ts CHANGED
@@ -29,6 +29,20 @@ export const convertCircuitJsonToLbrn = (
29
29
  includeLayers?: Array<"top" | "bottom">
30
30
  traceMargin?: number
31
31
  laserSpotSize?: number
32
+ laserProfile?: {
33
+ copper?: {
34
+ speed?: number
35
+ numPasses?: number
36
+ frequency?: number
37
+ pulseWidth?: number
38
+ }
39
+ board?: {
40
+ speed?: number
41
+ numPasses?: number
42
+ frequency?: number
43
+ pulseWidth?: number
44
+ }
45
+ }
32
46
  } = {},
33
47
  ): LightBurnProject => {
34
48
  const db = cju(circuitJson)
@@ -44,6 +58,25 @@ export const convertCircuitJsonToLbrn = (
44
58
  const includeCopper = options.includeCopper ?? true
45
59
  const includeSoldermask = options.includeSoldermask ?? false
46
60
  const soldermaskMargin = options.soldermaskMargin ?? 0
61
+ const laserProfile = options.laserProfile
62
+
63
+ // Default laser settings from GitHub issue
64
+ const defaultCopperSettings = {
65
+ speed: 300,
66
+ numPasses: 100,
67
+ frequency: 20000,
68
+ pulseWidth: 1e-9,
69
+ }
70
+ const defaultBoardSettings = {
71
+ speed: 20,
72
+ numPasses: 100,
73
+ frequency: 20000,
74
+ pulseWidth: 1e-9,
75
+ }
76
+
77
+ // Merge user settings with defaults
78
+ const copperSettings = { ...defaultCopperSettings, ...laserProfile?.copper }
79
+ const boardSettings = { ...defaultBoardSettings, ...laserProfile?.board }
47
80
 
48
81
  // Determine if we should generate trace clearance zones
49
82
  const shouldGenerateTraceClearanceZones = traceMargin > 0 && includeCopper
@@ -57,24 +90,30 @@ export const convertCircuitJsonToLbrn = (
57
90
  const topCopperCutSetting = new CutSetting({
58
91
  index: 0,
59
92
  name: "Cut Top Copper",
60
- numPasses: 12,
61
- speed: 100,
93
+ numPasses: copperSettings.numPasses,
94
+ speed: copperSettings.speed,
95
+ frequency: copperSettings.frequency,
96
+ pulseWidth: copperSettings.pulseWidth,
62
97
  })
63
98
  project.children.push(topCopperCutSetting)
64
99
 
65
100
  const bottomCopperCutSetting = new CutSetting({
66
101
  index: 1,
67
102
  name: "Cut Bottom Copper",
68
- numPasses: 12,
69
- speed: 100,
103
+ numPasses: copperSettings.numPasses,
104
+ speed: copperSettings.speed,
105
+ frequency: copperSettings.frequency,
106
+ pulseWidth: copperSettings.pulseWidth,
70
107
  })
71
108
  project.children.push(bottomCopperCutSetting)
72
109
 
73
110
  const throughBoardCutSetting = new CutSetting({
74
111
  index: 2,
75
112
  name: "Cut Through Board",
76
- numPasses: 3,
77
- speed: 50,
113
+ numPasses: boardSettings.numPasses,
114
+ speed: boardSettings.speed,
115
+ frequency: boardSettings.frequency,
116
+ pulseWidth: boardSettings.pulseWidth,
78
117
  })
79
118
  project.children.push(throughBoardCutSetting)
80
119
 
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.23",
4
+ "version": "0.0.25",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "start": "bun run site/index.html",
@@ -28,6 +28,6 @@
28
28
  "typescript": "^5"
29
29
  },
30
30
  "dependencies": {
31
- "lbrnts": "^0.0.8"
31
+ "lbrnts": "^0.0.9"
32
32
  }
33
33
  }
package/site/index.html CHANGED
@@ -61,44 +61,245 @@
61
61
 
62
62
  <div id="optionsContainer" class="hidden bg-gray-800 rounded-lg p-6 mb-6">
63
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">
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>
91
69
  <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"
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"
95
75
  />
96
- <span class="ml-3 text-sm font-medium text-gray-300">
97
- Include Silkscreen Layer
98
- </span>
99
- </label>
100
- </div>
101
- </div>
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>
90
+ <label class="block text-sm font-medium text-gray-300 mb-2">
91
+ Trace Margin (mm)
92
+ </label>
93
+ <input
94
+ type="number"
95
+ id="traceMargin"
96
+ value="0"
97
+ step="0.001"
98
+ 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"
99
+ />
100
+ </div>
101
+ <div>
102
+ <label class="block text-sm font-medium text-gray-300 mb-2">
103
+ Soldermask Margin (mm)
104
+ </label>
105
+ <input
106
+ type="number"
107
+ id="soldermaskMargin"
108
+ value="0"
109
+ step="0.001"
110
+ 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"
111
+ />
112
+ </div>
113
+ <div>
114
+ <label class="block text-sm font-medium text-gray-300 mb-2">
115
+ Laser Spot Size (mm)
116
+ </label>
117
+ <input
118
+ type="number"
119
+ id="laserSpotSize"
120
+ value="0.005"
121
+ step="0.001"
122
+ 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"
123
+ />
124
+ </div>
125
+ <div class="md:col-span-2">
126
+ <details class="group">
127
+ <summary class="w-full text-md font-medium text-gray-300 mb-3 flex items-center justify-between bg-blue-500 hover:bg-blue-700 border border-gray-600 rounded-lg px-4 py-3 transition-colors cursor-pointer">
128
+ <span>Laser Cut Settings</span>
129
+ <span class="text-sm text-gray-100 group-open:rotate-90 transition-transform duration-200">▶</span>
130
+ </summary>
131
+ <div class="space-y-4 border border-gray-600 rounded-lg p-4 bg-gray-750">
132
+ <div class="flex items-center justify-between p-3 bg-gray-750 rounded-lg">
133
+ <div class="col-span-2">
134
+ <label class="block text-sm font-medium text-gray-300 mb-2">Preset</label>
135
+ <select id="presetSelect" class="w-full px-3 py-2 bg-gray-700 border border-gray-600 rounded-lg text-white focus:outline-none focus:ring-2 focus:ring-blue-500 text-sm">
136
+ <option value="default">Default</option>
137
+ <option value="omni-x-6w">Omni X 6W 150x150 - Continuous cutting with conductivity checks</option>
138
+ <!-- Add more options here -->
139
+ </select>
140
+ </div>
141
+ </div>
142
+ <table class="w-full text-sm text-gray-300">
143
+ <thead>
144
+ <tr class="border-b border-gray-600">
145
+ <th class="text-left py-2">Layer</th>
146
+ <th class="text-left py-2">Speed (mm/s)</th>
147
+ <th class="text-left py-2">Passes</th>
148
+ <th class="text-left py-2">Frequency (kHz)</th>
149
+ <th class="text-left py-2">Pulse Width (ns)</th>
150
+ </tr>
151
+ </thead>
152
+ <tbody>
153
+ <tr class="border-b border-gray-700">
154
+ <td class="py-2 font-medium">Copper Cut Settings</td>
155
+ <td class="py-2">
156
+ <input
157
+ type="number"
158
+ id="copperSpeed"
159
+ value="300"
160
+ step="1"
161
+ title="Cutting speed for copper layers"
162
+ class="w-full px-3 py-2 bg-gray-700 border border-gray-600 text-white focus:outline-none focus:ring-2 focus:ring-blue-500 text-sm"
163
+ />
164
+ </td>
165
+ <td class="py-2">
166
+ <input
167
+ type="number"
168
+ id="copperNumPasses"
169
+ value="1"
170
+ step="1"
171
+ title="Number of passes for copper ablation"
172
+ class="w-full px-3 py-2 bg-gray-700 border border-gray-600 text-white focus:outline-none focus:ring-2 focus:ring-blue-500 text-sm"
173
+ />
174
+ </td>
175
+ <td class="py-2">
176
+ <input
177
+ type="number"
178
+ id="copperFrequency"
179
+ value="20"
180
+ step="1"
181
+ title="Laser pulse frequency"
182
+ class="w-full px-3 py-2 bg-gray-700 border border-gray-600 text-white focus:outline-none focus:ring-2 focus:ring-blue-500 text-sm"
183
+ />
184
+ </td>
185
+ <td class="py-2">
186
+ <input
187
+ type="number"
188
+ id="copperPulseWidth"
189
+ value="1"
190
+ step="0.1"
191
+ title="Laser pulse width"
192
+ class="w-full px-3 py-2 bg-gray-700 border border-gray-600 text-white focus:outline-none focus:ring-2 focus:ring-blue-500 text-sm"
193
+ />
194
+ </td>
195
+ </tr>
196
+ <tr>
197
+ <td class="py-2 font-medium">Board Laser Settings</td>
198
+ <td class="py-2">
199
+ <input
200
+ type="number"
201
+ id="boardSpeed"
202
+ value="20"
203
+ step="1"
204
+ class="w-full px-3 py-2 bg-gray-700 border border-gray-600 text-white focus:outline-none focus:ring-2 focus:ring-blue-500 text-sm"
205
+ />
206
+ </td>
207
+ <td class="py-2">
208
+ <input
209
+ type="number"
210
+ id="boardNumPasses"
211
+ value="1"
212
+ step="1"
213
+ class="w-full px-3 py-2 bg-gray-700 border border-gray-600 text-white focus:outline-none focus:ring-2 focus:ring-blue-500 text-sm"
214
+ />
215
+ </td>
216
+ <td class="py-2">
217
+ <input
218
+ type="number"
219
+ id="boardFrequency"
220
+ value="20"
221
+ step="1"
222
+ class="w-full px-3 py-2 bg-gray-700 border border-gray-600 text-white focus:outline-none focus:ring-2 focus:ring-blue-500 text-sm"
223
+ />
224
+ </td>
225
+ <td class="py-2">
226
+ <input
227
+ type="number"
228
+ id="boardPulseWidth"
229
+ value="1"
230
+ step="0.1"
231
+ class="w-full px-3 py-2 bg-gray-700 border border-gray-600 text-white focus:outline-none focus:ring-2 focus:ring-blue-500 text-sm"
232
+ />
233
+ </td>
234
+ </tr>
235
+ </tbody>
236
+ </table>
237
+ </div>
238
+ </details>
239
+ </div>
240
+ <div class="md:col-span-2 grid grid-cols-1 md:grid-cols-3 gap-4">
241
+ <label class="flex items-center cursor-pointer">
242
+ <input
243
+ type="checkbox"
244
+ id="includeCopper"
245
+ checked
246
+ class="w-5 h-5 text-blue-500 bg-gray-700 border-gray-600 rounded focus:ring-blue-500 focus:ring-2"
247
+ />
248
+ <span class="ml-3 text-sm font-medium text-gray-300">
249
+ Include Copper
250
+ </span>
251
+ </label>
252
+ <label class="flex items-center cursor-pointer">
253
+ <input
254
+ type="checkbox"
255
+ id="includeSoldermask"
256
+ class="w-5 h-5 text-blue-500 bg-gray-700 border-gray-600 rounded focus:ring-blue-500 focus:ring-2"
257
+ />
258
+ <span class="ml-3 text-sm font-medium text-gray-300">
259
+ Include Soldermask
260
+ </span>
261
+ </label>
262
+ <label class="flex items-center cursor-pointer">
263
+ <input
264
+ type="checkbox"
265
+ id="includeSilkscreen"
266
+ class="w-5 h-5 text-blue-500 bg-gray-700 border-gray-600 rounded focus:ring-blue-500 focus:ring-2"
267
+ />
268
+ <span class="ml-3 text-sm font-medium text-gray-300">
269
+ Include Silkscreen Layer
270
+ </span>
271
+ </label>
272
+ </div>
273
+ <div class="md:col-span-2">
274
+ <label class="block text-sm font-medium text-gray-300 mb-2">
275
+ Include Layers
276
+ </label>
277
+ <div class="flex gap-4">
278
+ <label class="flex items-center cursor-pointer">
279
+ <input
280
+ type="checkbox"
281
+ id="includeTopLayer"
282
+ checked
283
+ class="w-5 h-5 text-blue-500 bg-gray-700 border-gray-600 rounded focus:ring-blue-500 focus:ring-2"
284
+ />
285
+ <span class="ml-3 text-sm font-medium text-gray-300">
286
+ Top
287
+ </span>
288
+ </label>
289
+ <label class="flex items-center cursor-pointer">
290
+ <input
291
+ type="checkbox"
292
+ id="includeBottomLayer"
293
+ checked
294
+ class="w-5 h-5 text-blue-500 bg-gray-700 border-gray-600 rounded focus:ring-blue-500 focus:ring-2"
295
+ />
296
+ <span class="ml-3 text-sm font-medium text-gray-300">
297
+ Bottom
298
+ </span>
299
+ </label>
300
+ </div>
301
+ </div>
302
+ </div>
102
303
  <button
103
304
  id="reconvertBtn"
104
305
  class="mt-4 bg-blue-600 hover:bg-blue-700 text-white px-6 py-2 rounded-lg transition-colors"
package/site/main.tsx CHANGED
@@ -33,13 +33,105 @@ const optionsContainer = document.getElementById(
33
33
  ) as HTMLDivElement
34
34
  const originXInput = document.getElementById("originX") as HTMLInputElement
35
35
  const originYInput = document.getElementById("originY") as HTMLInputElement
36
+ const traceMarginInput = document.getElementById(
37
+ "traceMargin",
38
+ ) as HTMLInputElement
39
+ const soldermaskMarginInput = document.getElementById(
40
+ "soldermaskMargin",
41
+ ) as HTMLInputElement
42
+ const laserSpotSizeInput = document.getElementById(
43
+ "laserSpotSize",
44
+ ) as HTMLInputElement
45
+ const includeCopperInput = document.getElementById(
46
+ "includeCopper",
47
+ ) as HTMLInputElement
48
+ const includeSoldermaskInput = document.getElementById(
49
+ "includeSoldermask",
50
+ ) as HTMLInputElement
36
51
  const includeSilkscreenInput = document.getElementById(
37
52
  "includeSilkscreen",
38
53
  ) as HTMLInputElement
54
+ const includeTopLayerInput = document.getElementById(
55
+ "includeTopLayer",
56
+ ) as HTMLInputElement
57
+ const includeBottomLayerInput = document.getElementById(
58
+ "includeBottomLayer",
59
+ ) as HTMLInputElement
60
+ const copperSpeedInput = document.getElementById(
61
+ "copperSpeed",
62
+ ) as HTMLInputElement
63
+ const copperNumPassesInput = document.getElementById(
64
+ "copperNumPasses",
65
+ ) as HTMLInputElement
66
+ const copperFrequencyInput = document.getElementById(
67
+ "copperFrequency",
68
+ ) as HTMLInputElement
69
+ const copperPulseWidthInput = document.getElementById(
70
+ "copperPulseWidth",
71
+ ) as HTMLInputElement
72
+ const boardSpeedInput = document.getElementById(
73
+ "boardSpeed",
74
+ ) as HTMLInputElement
75
+ const boardNumPassesInput = document.getElementById(
76
+ "boardNumPasses",
77
+ ) as HTMLInputElement
78
+ const boardFrequencyInput = document.getElementById(
79
+ "boardFrequency",
80
+ ) as HTMLInputElement
81
+ const boardPulseWidthInput = document.getElementById(
82
+ "boardPulseWidth",
83
+ ) as HTMLInputElement
84
+ const presetSelect = document.getElementById(
85
+ "presetSelect",
86
+ ) as HTMLSelectElement
39
87
  const reconvertBtn = document.getElementById(
40
88
  "reconvertBtn",
41
89
  ) as HTMLButtonElement
42
90
 
91
+ // Convert frequency from kHz to Hz
92
+ function kHzToHz(khz: number): number {
93
+ return khz * 1000
94
+ }
95
+
96
+ // Convert pulse width from ns to seconds
97
+ function nsToSeconds(ns: number): number {
98
+ return ns * 1e-9
99
+ }
100
+
101
+ // Load Omni X 6W 150x150 preset
102
+ function loadOmniX6W150x150Preset() {
103
+ copperSpeedInput.value = "300"
104
+ copperNumPassesInput.value = "1"
105
+ copperFrequencyInput.value = "20"
106
+ copperPulseWidthInput.value = "1"
107
+
108
+ boardSpeedInput.value = "20"
109
+ boardNumPassesInput.value = "1"
110
+ boardFrequencyInput.value = "20"
111
+ boardPulseWidthInput.value = "1"
112
+ }
113
+
114
+ // Reset to defaults
115
+ function resetToDefaults() {
116
+ copperSpeedInput.value = "300"
117
+ copperNumPassesInput.value = "1"
118
+ copperFrequencyInput.value = "20"
119
+ copperPulseWidthInput.value = "1"
120
+
121
+ boardSpeedInput.value = "20"
122
+ boardNumPassesInput.value = "1"
123
+ boardFrequencyInput.value = "20"
124
+ boardPulseWidthInput.value = "1"
125
+ }
126
+
127
+ // Initialize display values on load
128
+ function initializeDisplayValues() {
129
+ copperFrequencyInput.value = "20"
130
+ copperPulseWidthInput.value = "1"
131
+ boardFrequencyInput.value = "20"
132
+ boardPulseWidthInput.value = "1"
133
+ }
134
+
43
135
  // Show error message
44
136
  function showError(message: string) {
45
137
  errorMessage.textContent = message
@@ -68,6 +160,29 @@ function getConversionOptions() {
68
160
  x: parseFloat(originXInput.value) || 0,
69
161
  y: parseFloat(originYInput.value) || 0,
70
162
  },
163
+ includeCopper: includeCopperInput.checked,
164
+ includeSoldermask: includeSoldermaskInput.checked,
165
+ soldermaskMargin: parseFloat(soldermaskMarginInput.value) || 0,
166
+ includeLayers: [
167
+ ...(includeTopLayerInput.checked ? ["top" as const] : []),
168
+ ...(includeBottomLayerInput.checked ? ["bottom" as const] : []),
169
+ ],
170
+ traceMargin: parseFloat(traceMarginInput.value) || 0,
171
+ laserSpotSize: parseFloat(laserSpotSizeInput.value) || 0.005,
172
+ laserProfile: {
173
+ copper: {
174
+ speed: parseFloat(copperSpeedInput.value) || 300,
175
+ numPasses: parseInt(copperNumPassesInput.value) || 100,
176
+ frequency: kHzToHz(parseFloat(copperFrequencyInput.value) || 20),
177
+ pulseWidth: nsToSeconds(parseFloat(copperPulseWidthInput.value) || 1),
178
+ },
179
+ board: {
180
+ speed: parseFloat(boardSpeedInput.value) || 20,
181
+ numPasses: parseInt(boardNumPassesInput.value) || 100,
182
+ frequency: kHzToHz(parseFloat(boardFrequencyInput.value) || 20),
183
+ pulseWidth: nsToSeconds(parseFloat(boardPulseWidthInput.value) || 1),
184
+ },
185
+ },
71
186
  }
72
187
  }
73
188
 
@@ -85,6 +200,7 @@ async function processFile(file: File) {
85
200
 
86
201
  // Show options container
87
202
  optionsContainer.classList.remove("hidden")
203
+ initializeDisplayValues()
88
204
 
89
205
  // Convert to LBRN with options
90
206
  await convertAndDisplay()
@@ -131,9 +247,11 @@ async function convertAndDisplay() {
131
247
 
132
248
  // Convert to LBRN
133
249
  console.log("Converting to LBRN with options:", options)
134
- currentLbrnProject = convertCircuitJsonToLbrn(processedCircuitJson, {
135
- includeSilkscreen: options.includeSilkscreen,
136
- })
250
+ const { origin: _, ...conversionOptions } = options
251
+ currentLbrnProject = convertCircuitJsonToLbrn(
252
+ processedCircuitJson,
253
+ conversionOptions,
254
+ )
137
255
 
138
256
  // Generate SVGs
139
257
  console.log("Generating Circuit JSON SVG...")
@@ -201,6 +319,17 @@ reconvertBtn.addEventListener("click", () => {
201
319
  convertAndDisplay()
202
320
  })
203
321
 
322
+ // Handle preset select
323
+ presetSelect.addEventListener("change", (e: Event) => {
324
+ const target = e.target as HTMLSelectElement
325
+ const value = target.value
326
+ if (value === "default") {
327
+ resetToDefaults()
328
+ } else if (value === "omni-x-6w") {
329
+ loadOmniX6W150x150Preset()
330
+ }
331
+ })
332
+
204
333
  // Handle download button
205
334
  downloadBtn.addEventListener("click", () => {
206
335
  if (!currentLbrnProject) {
@@ -0,0 +1,91 @@
1
+ import { test, expect } from "bun:test"
2
+ import { convertCircuitJsonToLbrn } from "../../lib"
3
+ import { CutSetting } from "lbrnts"
4
+ import type { CircuitJson } from "circuit-json"
5
+
6
+ const circuitJson: CircuitJson = [
7
+ {
8
+ type: "pcb_board",
9
+ pcb_board_id: "board1",
10
+ center: { x: 5, y: 5 },
11
+ thickness: 1.6,
12
+ num_layers: 2,
13
+ material: "fr4",
14
+ outline: [
15
+ { x: 0, y: 0 },
16
+ { x: 10, y: 0 },
17
+ { x: 10, y: 10 },
18
+ { x: 0, y: 10 },
19
+ ],
20
+ },
21
+ ]
22
+
23
+ test("applies custom laserProfile settings", () => {
24
+ const project = convertCircuitJsonToLbrn(circuitJson, {
25
+ laserProfile: {
26
+ copper: {
27
+ speed: 350,
28
+ numPasses: 150,
29
+ frequency: 25000,
30
+ pulseWidth: 2e-9,
31
+ },
32
+ board: {
33
+ speed: 25,
34
+ numPasses: 120,
35
+ frequency: 21000,
36
+ pulseWidth: 1.5e-9,
37
+ },
38
+ },
39
+ })
40
+
41
+ expect(project).toBeDefined()
42
+ const cutSettings = project.children.filter(
43
+ (child) => child.constructor.name === "_CutSetting",
44
+ ) as CutSetting[]
45
+ expect(cutSettings.length).toBe(4) // top copper, bottom copper, through board, soldermask
46
+
47
+ // Verify top copper settings
48
+ const topCopper = cutSettings[0]!
49
+ expect(topCopper.speed).toBe(350)
50
+ expect(topCopper.numPasses).toBe(150)
51
+ expect(topCopper.frequency).toBe(25000)
52
+ expect(topCopper.pulseWidth).toBe(2e-9)
53
+
54
+ // Verify bottom copper settings (same as top)
55
+ const bottomCopper = cutSettings[1]!
56
+ expect(bottomCopper.speed).toBe(350)
57
+ expect(bottomCopper.numPasses).toBe(150)
58
+ expect(bottomCopper.frequency).toBe(25000)
59
+ expect(bottomCopper.pulseWidth).toBe(2e-9)
60
+
61
+ // Verify through board settings
62
+ const throughBoard = cutSettings[2]!
63
+ expect(throughBoard.speed).toBe(25)
64
+ expect(throughBoard.numPasses).toBe(120)
65
+ expect(throughBoard.frequency).toBe(21000)
66
+ expect(throughBoard.pulseWidth).toBe(1.5e-9)
67
+ })
68
+
69
+ test("uses default laserProfile settings when not provided", () => {
70
+ const project = convertCircuitJsonToLbrn(circuitJson)
71
+
72
+ expect(project).toBeDefined()
73
+ const cutSettings = project.children.filter(
74
+ (child) => child.constructor.name === "_CutSetting",
75
+ ) as CutSetting[]
76
+ expect(cutSettings.length).toBe(4)
77
+
78
+ // Verify top copper defaults
79
+ const topCopper = cutSettings[0]!
80
+ expect(topCopper.speed).toBe(300)
81
+ expect(topCopper.numPasses).toBe(100)
82
+ expect(topCopper.frequency).toBe(20000)
83
+ expect(topCopper.pulseWidth).toBe(1e-9)
84
+
85
+ // Verify through board defaults
86
+ const throughBoard = cutSettings[2]!
87
+ expect(throughBoard.speed).toBe(20)
88
+ expect(throughBoard.numPasses).toBe(100)
89
+ expect(throughBoard.frequency).toBe(20000)
90
+ expect(throughBoard.pulseWidth).toBe(1e-9)
91
+ })