circuit-json-to-lbrn 0.0.54 → 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/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;
@@ -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
@@ -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.54",
4
+ "version": "0.0.55",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "start": "bun run site/index.html",