circuit-json-to-lbrn 0.0.53 → 0.0.55

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,7 +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
+ - `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). Pulse width is specified in ns. Allows per-user customization for different lasers/lenses.
42
42
  - `includeLayers?: Array<"top" | "bottom">` - Specify which layers to include (default: `["top", "bottom"]`)
43
43
 
44
44
  ## Soldermask Support
package/dist/index.d.ts CHANGED
@@ -10,6 +10,7 @@ interface ConvertCircuitJsonToLbrnOptions {
10
10
  margin?: number;
11
11
  includeCopper?: boolean;
12
12
  includeSoldermask?: boolean;
13
+ includeSoldermaskCure?: boolean;
13
14
  globalCopperSoldermaskMarginAdjustment?: number;
14
15
  solderMaskMarginPercent?: number;
15
16
  includeLayers?: Array<"top" | "bottom">;
package/dist/index.js CHANGED
@@ -2749,7 +2749,132 @@ var LAYER_INDEXES = {
2749
2749
  topCopperCutFill: 6,
2750
2750
  bottomCopperCutFill: 7,
2751
2751
  topOxidationCleaning: 8,
2752
- bottomOxidationCleaning: 9
2752
+ bottomOxidationCleaning: 9,
2753
+ soldermaskCure: 10
2754
+ };
2755
+
2756
+ // lib/createSoldermaskCureLayer.ts
2757
+ import { Point as Point4, Polygon as Polygon7 } from "@flatten-js/core";
2758
+ import { ShapeGroup as ShapeGroup3, ShapePath as ShapePath30 } from "lbrnts";
2759
+ var contourToPolygon3 = (contour) => {
2760
+ if (contour.length < 3) return null;
2761
+ const points = contour.map(([x, y]) => new Point4(x, y));
2762
+ try {
2763
+ const polygon = new Polygon7(points);
2764
+ if (polygon.faces.size > 0) {
2765
+ return polygon;
2766
+ }
2767
+ } catch {
2768
+ }
2769
+ return null;
2770
+ };
2771
+ var shapePathToContour = (shapePath) => {
2772
+ if (!shapePath.verts || shapePath.verts.length < 3) return null;
2773
+ const contour = shapePath.verts.map(
2774
+ (vert) => [vert.x, vert.y]
2775
+ );
2776
+ if (contour.length > 3) {
2777
+ const first = contour[0];
2778
+ const last = contour[contour.length - 1];
2779
+ if (first && last && first[0] === last[0] && first[1] === last[1]) {
2780
+ contour.pop();
2781
+ }
2782
+ }
2783
+ return contour.length >= 3 ? contour : null;
2784
+ };
2785
+ var collectSoldermaskContours = (project, soldermaskCutIndex) => {
2786
+ const contours = [];
2787
+ for (const child of project.children) {
2788
+ if (child instanceof ShapePath30) {
2789
+ if (child.cutIndex !== soldermaskCutIndex) {
2790
+ continue;
2791
+ }
2792
+ const contour = shapePathToContour(child);
2793
+ if (contour) {
2794
+ contours.push(contour);
2795
+ }
2796
+ continue;
2797
+ }
2798
+ if (child instanceof ShapeGroup3) {
2799
+ for (const groupChild of child.children) {
2800
+ if (groupChild instanceof ShapePath30) {
2801
+ if (groupChild.cutIndex !== soldermaskCutIndex) {
2802
+ continue;
2803
+ }
2804
+ const contour = shapePathToContour(groupChild);
2805
+ if (contour) {
2806
+ contours.push(contour);
2807
+ }
2808
+ }
2809
+ }
2810
+ }
2811
+ }
2812
+ return contours;
2813
+ };
2814
+ var createSoldermaskCureLayer = async ({
2815
+ ctx
2816
+ }) => {
2817
+ const { project, boardOutlineContour, soldermaskCureCutSetting } = ctx;
2818
+ if (!soldermaskCureCutSetting) {
2819
+ return;
2820
+ }
2821
+ if (ctx.soldermaskCutSetting.index === void 0) {
2822
+ return;
2823
+ }
2824
+ const soldermaskCutIndex = ctx.soldermaskCutSetting.index;
2825
+ if (!boardOutlineContour || boardOutlineContour.length < 3) {
2826
+ console.warn(
2827
+ "Cannot create soldermask cure layer: no board outline available"
2828
+ );
2829
+ return;
2830
+ }
2831
+ try {
2832
+ const manifold = await getManifold();
2833
+ const { CrossSection } = manifold;
2834
+ const allContours = collectSoldermaskContours(project, soldermaskCutIndex);
2835
+ if (allContours.length === 0) {
2836
+ return;
2837
+ }
2838
+ const boardOutline = new CrossSection([boardOutlineContour], "Positive");
2839
+ const soldermaskOpenings = new CrossSection(allContours, "Positive");
2840
+ const cureArea = boardOutline.subtract(soldermaskOpenings);
2841
+ const simplifiedArea = cureArea.simplify(1e-3);
2842
+ const resultContours = simplifiedArea.toPolygons();
2843
+ if (resultContours.length === 0) {
2844
+ boardOutline.delete();
2845
+ soldermaskOpenings.delete();
2846
+ cureArea.delete();
2847
+ simplifiedArea.delete();
2848
+ return;
2849
+ }
2850
+ const shapeGroup = new ShapeGroup3();
2851
+ for (const contour of resultContours) {
2852
+ const polygon = contourToPolygon3(contour);
2853
+ if (!polygon) continue;
2854
+ for (const island of polygon.splitToIslands()) {
2855
+ const { verts, prims } = polygonToShapePathData(island);
2856
+ if (verts.length > 0) {
2857
+ shapeGroup.children.push(
2858
+ new ShapePath30({
2859
+ cutIndex: soldermaskCureCutSetting.index,
2860
+ verts,
2861
+ prims,
2862
+ isClosed: true
2863
+ })
2864
+ );
2865
+ }
2866
+ }
2867
+ }
2868
+ if (shapeGroup.children.length > 0) {
2869
+ project.children.push(shapeGroup);
2870
+ }
2871
+ boardOutline.delete();
2872
+ soldermaskOpenings.delete();
2873
+ cureArea.delete();
2874
+ simplifiedArea.delete();
2875
+ } catch (error) {
2876
+ console.warn("Failed to create soldermask cure layer:", error);
2877
+ }
2753
2878
  };
2754
2879
 
2755
2880
  // lib/index.ts
@@ -2764,6 +2889,8 @@ var convertCircuitJsonToLbrn = async (circuitJson, options = {}) => {
2764
2889
  const laserSpotSize = options.laserSpotSize ?? 5e-3;
2765
2890
  const includeCopper = options.includeCopper ?? true;
2766
2891
  const includeSoldermask = options.includeSoldermask ?? false;
2892
+ const includeSoldermaskCure = options.includeSoldermaskCure ?? false;
2893
+ const shouldIncludeSoldermaskCure = includeSoldermask && includeSoldermaskCure;
2767
2894
  const globalCopperSoldermaskMarginAdjustment = options.globalCopperSoldermaskMarginAdjustment ?? 0;
2768
2895
  const solderMaskMarginPercent = options.solderMaskMarginPercent ?? 0;
2769
2896
  const laserProfile = options.laserProfile;
@@ -2774,13 +2901,13 @@ var convertCircuitJsonToLbrn = async (circuitJson, options = {}) => {
2774
2901
  speed: 300,
2775
2902
  numPasses: 100,
2776
2903
  frequency: 2e4,
2777
- pulseWidth: 1e-9
2904
+ pulseWidth: 1
2778
2905
  };
2779
2906
  const defaultBoardSettings = {
2780
2907
  speed: 20,
2781
2908
  numPasses: 100,
2782
2909
  frequency: 2e4,
2783
- pulseWidth: 1e-9
2910
+ pulseWidth: 1
2784
2911
  };
2785
2912
  const copperSettings = { ...defaultCopperSettings, ...laserProfile?.copper };
2786
2913
  const boardSettings = { ...defaultBoardSettings, ...laserProfile?.board };
@@ -2794,7 +2921,7 @@ var convertCircuitJsonToLbrn = async (circuitJson, options = {}) => {
2794
2921
  numPasses: copperSettings.numPasses,
2795
2922
  speed: copperSettings.speed,
2796
2923
  frequency: copperSettings.frequency,
2797
- pulseWidth: copperSettings.pulseWidth
2924
+ qPulseWidth: copperSettings.pulseWidth
2798
2925
  });
2799
2926
  project.children.push(topCopperCutSetting);
2800
2927
  const bottomCopperCutSetting = new CutSetting({
@@ -2803,7 +2930,7 @@ var convertCircuitJsonToLbrn = async (circuitJson, options = {}) => {
2803
2930
  numPasses: copperSettings.numPasses,
2804
2931
  speed: copperSettings.speed,
2805
2932
  frequency: copperSettings.frequency,
2806
- pulseWidth: copperSettings.pulseWidth
2933
+ qPulseWidth: copperSettings.pulseWidth
2807
2934
  });
2808
2935
  project.children.push(bottomCopperCutSetting);
2809
2936
  const throughBoardCutSetting = new CutSetting({
@@ -2812,7 +2939,7 @@ var convertCircuitJsonToLbrn = async (circuitJson, options = {}) => {
2812
2939
  numPasses: boardSettings.numPasses,
2813
2940
  speed: boardSettings.speed,
2814
2941
  frequency: boardSettings.frequency,
2815
- pulseWidth: boardSettings.pulseWidth
2942
+ qPulseWidth: boardSettings.pulseWidth
2816
2943
  });
2817
2944
  project.children.push(throughBoardCutSetting);
2818
2945
  const soldermaskCutSetting = new CutSetting({
@@ -2827,6 +2954,21 @@ var convertCircuitJsonToLbrn = async (circuitJson, options = {}) => {
2827
2954
  crossHatch: true
2828
2955
  });
2829
2956
  project.children.push(soldermaskCutSetting);
2957
+ let soldermaskCureCutSetting;
2958
+ if (shouldIncludeSoldermaskCure) {
2959
+ soldermaskCureCutSetting = new CutSetting({
2960
+ type: "Scan",
2961
+ index: LAYER_INDEXES.soldermaskCure,
2962
+ name: "Cut Soldermask Cure",
2963
+ numPasses: 1,
2964
+ speed: 150,
2965
+ scanOpt: "individual",
2966
+ interval: 0.18,
2967
+ angle: 45,
2968
+ crossHatch: true
2969
+ });
2970
+ project.children.push(soldermaskCureCutSetting);
2971
+ }
2830
2972
  let topTraceClearanceAreaCutSetting;
2831
2973
  let bottomTraceClearanceAreaCutSetting;
2832
2974
  if (shouldGenerateTraceClearanceZones) {
@@ -2936,6 +3078,7 @@ var convertCircuitJsonToLbrn = async (circuitJson, options = {}) => {
2936
3078
  bottomCopperCutSetting,
2937
3079
  throughBoardCutSetting,
2938
3080
  soldermaskCutSetting,
3081
+ soldermaskCureCutSetting,
2939
3082
  connMap,
2940
3083
  topCutNetGeoms: /* @__PURE__ */ new Map(),
2941
3084
  bottomCutNetGeoms: /* @__PURE__ */ new Map(),
@@ -2944,6 +3087,7 @@ var convertCircuitJsonToLbrn = async (circuitJson, options = {}) => {
2944
3087
  origin,
2945
3088
  includeCopper,
2946
3089
  includeSoldermask,
3090
+ includeSoldermaskCure,
2947
3091
  globalCopperSoldermaskMarginAdjustment,
2948
3092
  includeLayers,
2949
3093
  traceMargin,
@@ -3040,6 +3184,9 @@ var convertCircuitJsonToLbrn = async (circuitJson, options = {}) => {
3040
3184
  await createOxidationCleaningLayerForLayer({ layer: "bottom", ctx });
3041
3185
  }
3042
3186
  }
3187
+ if (shouldIncludeSoldermaskCure) {
3188
+ await createSoldermaskCureLayer({ ctx });
3189
+ }
3043
3190
  return project;
3044
3191
  };
3045
3192
  export {
@@ -15,6 +15,7 @@ export interface ConvertContext {
15
15
  bottomCopperCutSetting: CutSetting
16
16
  throughBoardCutSetting: CutSetting
17
17
  soldermaskCutSetting: CutSetting
18
+ soldermaskCureCutSetting?: CutSetting
18
19
 
19
20
  connMap: ConnectivityMap
20
21
 
@@ -33,6 +34,7 @@ export interface ConvertContext {
33
34
  // Include flags
34
35
  includeCopper: boolean
35
36
  includeSoldermask: boolean
37
+ includeSoldermaskCure: boolean
36
38
  includeLayers: Array<"top" | "bottom">
37
39
 
38
40
  // Global copper soldermask margin adjustment (can be negative)
@@ -0,0 +1,160 @@
1
+ import { Point, Polygon } from "@flatten-js/core"
2
+ import { ShapeGroup, ShapePath } from "lbrnts"
3
+ import type { ConvertContext } from "./ConvertContext"
4
+ import { getManifold } from "./getManifold"
5
+ import { polygonToShapePathData } from "./polygon-to-shape-path"
6
+
7
+ type Contour = Array<[number, number]>
8
+
9
+ /**
10
+ * Converts a single contour to a flatten-js Polygon
11
+ */
12
+ const contourToPolygon = (contour: Contour): Polygon | null => {
13
+ if (contour.length < 3) return null
14
+
15
+ const points = contour.map(([x, y]) => new Point(x, y))
16
+ try {
17
+ const polygon = new Polygon(points)
18
+ if (polygon.faces.size > 0) {
19
+ return polygon
20
+ }
21
+ } catch {
22
+ // Skip invalid polygons
23
+ }
24
+ return null
25
+ }
26
+
27
+ const shapePathToContour = (shapePath: ShapePath): Contour | null => {
28
+ if (!shapePath.verts || shapePath.verts.length < 3) return null
29
+ const contour = shapePath.verts.map(
30
+ (vert) => [vert.x, vert.y] as [number, number],
31
+ )
32
+ if (contour.length > 3) {
33
+ const first = contour[0]
34
+ const last = contour[contour.length - 1]
35
+ if (first && last && first[0] === last[0] && first[1] === last[1]) {
36
+ contour.pop()
37
+ }
38
+ }
39
+ return contour.length >= 3 ? contour : null
40
+ }
41
+
42
+ const collectSoldermaskContours = (
43
+ project: ConvertContext["project"],
44
+ soldermaskCutIndex: number,
45
+ ) => {
46
+ const contours: Contour[] = []
47
+
48
+ for (const child of project.children) {
49
+ if (child instanceof ShapePath) {
50
+ if (child.cutIndex !== soldermaskCutIndex) {
51
+ continue
52
+ }
53
+ const contour = shapePathToContour(child)
54
+ if (contour) {
55
+ contours.push(contour)
56
+ }
57
+ continue
58
+ }
59
+
60
+ if (child instanceof ShapeGroup) {
61
+ for (const groupChild of child.children) {
62
+ if (groupChild instanceof ShapePath) {
63
+ if (groupChild.cutIndex !== soldermaskCutIndex) {
64
+ continue
65
+ }
66
+ const contour = shapePathToContour(groupChild)
67
+ if (contour) {
68
+ contours.push(contour)
69
+ }
70
+ }
71
+ }
72
+ }
73
+ }
74
+
75
+ return contours
76
+ }
77
+
78
+ /**
79
+ * Creates a soldermask cure layer by subtracting soldermask openings from the board outline.
80
+ */
81
+ export const createSoldermaskCureLayer = async ({
82
+ ctx,
83
+ }: {
84
+ ctx: ConvertContext
85
+ }): Promise<void> => {
86
+ const { project, boardOutlineContour, soldermaskCureCutSetting } = ctx
87
+
88
+ if (!soldermaskCureCutSetting) {
89
+ return
90
+ }
91
+
92
+ if (ctx.soldermaskCutSetting.index === undefined) {
93
+ return
94
+ }
95
+ const soldermaskCutIndex = ctx.soldermaskCutSetting.index
96
+
97
+ if (!boardOutlineContour || boardOutlineContour.length < 3) {
98
+ console.warn(
99
+ "Cannot create soldermask cure layer: no board outline available",
100
+ )
101
+ return
102
+ }
103
+
104
+ try {
105
+ const manifold = await getManifold()
106
+ const { CrossSection } = manifold
107
+
108
+ const allContours = collectSoldermaskContours(project, soldermaskCutIndex)
109
+ if (allContours.length === 0) {
110
+ return
111
+ }
112
+
113
+ const boardOutline = new CrossSection([boardOutlineContour], "Positive")
114
+ const soldermaskOpenings = new CrossSection(allContours, "Positive")
115
+ const cureArea = boardOutline.subtract(soldermaskOpenings)
116
+ const simplifiedArea = cureArea.simplify(0.001)
117
+ const resultContours: Contour[] = simplifiedArea.toPolygons()
118
+
119
+ if (resultContours.length === 0) {
120
+ boardOutline.delete()
121
+ soldermaskOpenings.delete()
122
+ cureArea.delete()
123
+ simplifiedArea.delete()
124
+ return
125
+ }
126
+
127
+ const shapeGroup = new ShapeGroup()
128
+
129
+ for (const contour of resultContours) {
130
+ const polygon = contourToPolygon(contour)
131
+ if (!polygon) continue
132
+
133
+ for (const island of polygon.splitToIslands()) {
134
+ const { verts, prims } = polygonToShapePathData(island)
135
+
136
+ if (verts.length > 0) {
137
+ shapeGroup.children.push(
138
+ new ShapePath({
139
+ cutIndex: soldermaskCureCutSetting.index,
140
+ verts,
141
+ prims,
142
+ isClosed: true,
143
+ }),
144
+ )
145
+ }
146
+ }
147
+ }
148
+
149
+ if (shapeGroup.children.length > 0) {
150
+ project.children.push(shapeGroup)
151
+ }
152
+
153
+ boardOutline.delete()
154
+ soldermaskOpenings.delete()
155
+ cureArea.delete()
156
+ simplifiedArea.delete()
157
+ } catch (error) {
158
+ console.warn("Failed to create soldermask cure layer:", error)
159
+ }
160
+ }
package/lib/index.ts CHANGED
@@ -19,6 +19,7 @@ import { createTraceClearanceAreasForLayer } from "./createTraceClearanceAreasFo
19
19
  import { createCopperCutFillForLayer } from "./createCopperCutFillForLayer"
20
20
  import { createOxidationCleaningLayerForLayer } from "./createOxidationCleaningLayerForLayer"
21
21
  import { LAYER_INDEXES } from "./layer-indexes"
22
+ import { createSoldermaskCureLayer } from "./createSoldermaskCureLayer"
22
23
 
23
24
  export interface ConvertCircuitJsonToLbrnOptions {
24
25
  includeSilkscreen?: boolean
@@ -26,6 +27,7 @@ export interface ConvertCircuitJsonToLbrnOptions {
26
27
  margin?: number
27
28
  includeCopper?: boolean
28
29
  includeSoldermask?: boolean
30
+ includeSoldermaskCure?: boolean
29
31
  globalCopperSoldermaskMarginAdjustment?: number
30
32
  solderMaskMarginPercent?: number
31
33
  includeLayers?: Array<"top" | "bottom">
@@ -79,6 +81,8 @@ export const convertCircuitJsonToLbrn = async (
79
81
  const laserSpotSize = options.laserSpotSize ?? 0.005
80
82
  const includeCopper = options.includeCopper ?? true
81
83
  const includeSoldermask = options.includeSoldermask ?? false
84
+ const includeSoldermaskCure = options.includeSoldermaskCure ?? false
85
+ const shouldIncludeSoldermaskCure = includeSoldermask && includeSoldermaskCure
82
86
  const globalCopperSoldermaskMarginAdjustment =
83
87
  options.globalCopperSoldermaskMarginAdjustment ?? 0
84
88
  const solderMaskMarginPercent = options.solderMaskMarginPercent ?? 0
@@ -93,13 +97,13 @@ export const convertCircuitJsonToLbrn = async (
93
97
  speed: 300,
94
98
  numPasses: 100,
95
99
  frequency: 20000,
96
- pulseWidth: 1e-9,
100
+ pulseWidth: 1,
97
101
  }
98
102
  const defaultBoardSettings = {
99
103
  speed: 20,
100
104
  numPasses: 100,
101
105
  frequency: 20000,
102
- pulseWidth: 1e-9,
106
+ pulseWidth: 1,
103
107
  }
104
108
 
105
109
  // Merge user settings with defaults
@@ -121,7 +125,7 @@ export const convertCircuitJsonToLbrn = async (
121
125
  numPasses: copperSettings.numPasses,
122
126
  speed: copperSettings.speed,
123
127
  frequency: copperSettings.frequency,
124
- pulseWidth: copperSettings.pulseWidth,
128
+ qPulseWidth: copperSettings.pulseWidth,
125
129
  })
126
130
  project.children.push(topCopperCutSetting)
127
131
 
@@ -131,7 +135,7 @@ export const convertCircuitJsonToLbrn = async (
131
135
  numPasses: copperSettings.numPasses,
132
136
  speed: copperSettings.speed,
133
137
  frequency: copperSettings.frequency,
134
- pulseWidth: copperSettings.pulseWidth,
138
+ qPulseWidth: copperSettings.pulseWidth,
135
139
  })
136
140
  project.children.push(bottomCopperCutSetting)
137
141
 
@@ -141,7 +145,7 @@ export const convertCircuitJsonToLbrn = async (
141
145
  numPasses: boardSettings.numPasses,
142
146
  speed: boardSettings.speed,
143
147
  frequency: boardSettings.frequency,
144
- pulseWidth: boardSettings.pulseWidth,
148
+ qPulseWidth: boardSettings.pulseWidth,
145
149
  })
146
150
  project.children.push(throughBoardCutSetting)
147
151
 
@@ -158,6 +162,22 @@ export const convertCircuitJsonToLbrn = async (
158
162
  })
159
163
  project.children.push(soldermaskCutSetting)
160
164
 
165
+ let soldermaskCureCutSetting: CutSetting | undefined
166
+ if (shouldIncludeSoldermaskCure) {
167
+ soldermaskCureCutSetting = new CutSetting({
168
+ type: "Scan",
169
+ index: LAYER_INDEXES.soldermaskCure,
170
+ name: "Cut Soldermask Cure",
171
+ numPasses: 1,
172
+ speed: 150,
173
+ scanOpt: "individual",
174
+ interval: 0.18,
175
+ angle: 45,
176
+ crossHatch: true,
177
+ })
178
+ project.children.push(soldermaskCureCutSetting)
179
+ }
180
+
161
181
  // Create trace clearance cut settings if needed
162
182
  let topTraceClearanceAreaCutSetting: CutSetting | undefined
163
183
  let bottomTraceClearanceAreaCutSetting: CutSetting | undefined
@@ -282,6 +302,7 @@ export const convertCircuitJsonToLbrn = async (
282
302
  bottomCopperCutSetting,
283
303
  throughBoardCutSetting,
284
304
  soldermaskCutSetting,
305
+ soldermaskCureCutSetting,
285
306
  connMap,
286
307
  topCutNetGeoms: new Map(),
287
308
  bottomCutNetGeoms: new Map(),
@@ -290,6 +311,7 @@ export const convertCircuitJsonToLbrn = async (
290
311
  origin,
291
312
  includeCopper,
292
313
  includeSoldermask,
314
+ includeSoldermaskCure,
293
315
  globalCopperSoldermaskMarginAdjustment,
294
316
  includeLayers,
295
317
  traceMargin,
@@ -411,5 +433,9 @@ export const convertCircuitJsonToLbrn = async (
411
433
  }
412
434
  }
413
435
 
436
+ if (shouldIncludeSoldermaskCure) {
437
+ await createSoldermaskCureLayer({ ctx })
438
+ }
439
+
414
440
  return project
415
441
  }
@@ -17,6 +17,7 @@ export const LAYER_INDEXES = {
17
17
  bottomCopperCutFill: 7,
18
18
  topOxidationCleaning: 8,
19
19
  bottomOxidationCleaning: 9,
20
+ soldermaskCure: 10,
20
21
  } as const
21
22
 
22
23
  export type LayerIndex = (typeof LAYER_INDEXES)[keyof typeof LAYER_INDEXES]
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.53",
4
+ "version": "0.0.55",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "start": "bun run site/index.html",
@@ -28,7 +28,7 @@
28
28
  "typescript": "^5"
29
29
  },
30
30
  "dependencies": {
31
- "lbrnts": "^0.0.16",
31
+ "lbrnts": "^0.0.17",
32
32
  "manifold-3d": "^3.3.2"
33
33
  }
34
34
  }
package/site/main.tsx CHANGED
@@ -102,11 +102,6 @@ function kHzToHz(khz: number): number {
102
102
  return khz * 1000
103
103
  }
104
104
 
105
- // Convert pulse width from ns to seconds
106
- function nsToSeconds(ns: number): number {
107
- return ns * 1e-9
108
- }
109
-
110
105
  // Load Omni X 6W 150x150 preset
111
106
  function loadOmniX6W150x150Preset() {
112
107
  copperSpeedInput.value = "300"
@@ -188,13 +183,13 @@ function getConversionOptions() {
188
183
  speed: parseFloat(copperSpeedInput.value) || 300,
189
184
  numPasses: parseInt(copperNumPassesInput.value) || 100,
190
185
  frequency: kHzToHz(parseFloat(copperFrequencyInput.value) || 20),
191
- pulseWidth: nsToSeconds(parseFloat(copperPulseWidthInput.value) || 1),
186
+ pulseWidth: parseFloat(copperPulseWidthInput.value) || 1,
192
187
  },
193
188
  board: {
194
189
  speed: parseFloat(boardSpeedInput.value) || 20,
195
190
  numPasses: parseInt(boardNumPassesInput.value) || 100,
196
191
  frequency: kHzToHz(parseFloat(boardFrequencyInput.value) || 20),
197
- pulseWidth: nsToSeconds(parseFloat(boardPulseWidthInput.value) || 1),
192
+ pulseWidth: parseFloat(boardPulseWidthInput.value) || 1,
198
193
  },
199
194
  },
200
195
  }
@@ -27,13 +27,13 @@ test("applies custom laserProfile settings", async () => {
27
27
  speed: 350,
28
28
  numPasses: 150,
29
29
  frequency: 25000,
30
- pulseWidth: 2e-9,
30
+ pulseWidth: 2,
31
31
  },
32
32
  board: {
33
33
  speed: 25,
34
34
  numPasses: 120,
35
35
  frequency: 21000,
36
- pulseWidth: 1.5e-9,
36
+ pulseWidth: 1.5,
37
37
  },
38
38
  },
39
39
  })
@@ -49,21 +49,21 @@ test("applies custom laserProfile settings", async () => {
49
49
  expect(topCopper.speed).toBe(350)
50
50
  expect(topCopper.numPasses).toBe(150)
51
51
  expect(topCopper.frequency).toBe(25000)
52
- expect(topCopper.pulseWidth).toBe(2e-9)
52
+ expect(topCopper.qPulseWidth).toBe(2)
53
53
 
54
54
  // Verify bottom copper settings (same as top)
55
55
  const bottomCopper = cutSettings[1]!
56
56
  expect(bottomCopper.speed).toBe(350)
57
57
  expect(bottomCopper.numPasses).toBe(150)
58
58
  expect(bottomCopper.frequency).toBe(25000)
59
- expect(bottomCopper.pulseWidth).toBe(2e-9)
59
+ expect(bottomCopper.qPulseWidth).toBe(2)
60
60
 
61
61
  // Verify through board settings
62
62
  const throughBoard = cutSettings[2]!
63
63
  expect(throughBoard.speed).toBe(25)
64
64
  expect(throughBoard.numPasses).toBe(120)
65
65
  expect(throughBoard.frequency).toBe(21000)
66
- expect(throughBoard.pulseWidth).toBe(1.5e-9)
66
+ expect(throughBoard.qPulseWidth).toBe(1.5)
67
67
  })
68
68
 
69
69
  test("uses default laserProfile settings when not provided", async () => {
@@ -80,12 +80,12 @@ test("uses default laserProfile settings when not provided", async () => {
80
80
  expect(topCopper.speed).toBe(300)
81
81
  expect(topCopper.numPasses).toBe(100)
82
82
  expect(topCopper.frequency).toBe(20000)
83
- expect(topCopper.pulseWidth).toBe(1e-9)
83
+ expect(topCopper.qPulseWidth).toBe(1)
84
84
 
85
85
  // Verify through board defaults
86
86
  const throughBoard = cutSettings[2]!
87
87
  expect(throughBoard.speed).toBe(20)
88
88
  expect(throughBoard.numPasses).toBe(100)
89
89
  expect(throughBoard.frequency).toBe(20000)
90
- expect(throughBoard.pulseWidth).toBe(1e-9)
90
+ expect(throughBoard.qPulseWidth).toBe(1)
91
91
  })