circuit-json-to-lbrn 0.0.52 → 0.0.53
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/CLAUDE.md +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +140 -9
- package/lib/ConvertContext.ts +4 -0
- package/lib/createOxidationCleaningLayerForLayer.ts +120 -0
- package/lib/index.ts +66 -11
- package/lib/layer-indexes.ts +22 -0
- package/package.json +1 -1
- package/tests/basics/copper-cut-fill/__snapshots__/copper-cut-fill-basic.snap.svg +1 -1
- package/tests/basics/copper-cut-fill/__snapshots__/copper-cut-fill-with-pads.snap.svg +1 -1
- package/tests/basics/oxidation-cleaning/__snapshots__/oxidation-cleaning-basic.snap.svg +8 -0
- package/tests/basics/oxidation-cleaning/oxidation-cleaning-basic.test.ts +42 -0
- package/tests/examples/example05/__snapshots__/example05.snap.svg +1 -1
package/CLAUDE.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
- Use `bun`/`bunx` (DO NOT USE `npm` or `npx`)
|
package/dist/index.d.ts
CHANGED
|
@@ -26,6 +26,12 @@ interface ConvertCircuitJsonToLbrnOptions {
|
|
|
26
26
|
* This determines how wide the band of copper removal will be around traces/pads.
|
|
27
27
|
*/
|
|
28
28
|
copperCutFillMargin?: number;
|
|
29
|
+
/**
|
|
30
|
+
* Whether to generate an oxidation cleaning layer.
|
|
31
|
+
* Creates a filled area covering the entire "inside" of the board outline
|
|
32
|
+
* for laser ablation to clean oxidation from the copper surface.
|
|
33
|
+
*/
|
|
34
|
+
includeOxidationCleaningLayer?: boolean;
|
|
29
35
|
laserProfile?: {
|
|
30
36
|
copper?: {
|
|
31
37
|
speed?: number;
|
package/dist/index.js
CHANGED
|
@@ -2663,6 +2663,95 @@ var createCopperCutFillForLayer = async ({
|
|
|
2663
2663
|
}
|
|
2664
2664
|
};
|
|
2665
2665
|
|
|
2666
|
+
// lib/createOxidationCleaningLayerForLayer.ts
|
|
2667
|
+
import { Polygon as Polygon6, Point as Point3 } from "@flatten-js/core";
|
|
2668
|
+
import { ShapePath as ShapePath29, ShapeGroup as ShapeGroup2 } from "lbrnts";
|
|
2669
|
+
var contourToPolygon2 = (contour) => {
|
|
2670
|
+
if (contour.length < 3) return null;
|
|
2671
|
+
const points = contour.map(([x, y]) => new Point3(x, y));
|
|
2672
|
+
try {
|
|
2673
|
+
const polygon = new Polygon6(points);
|
|
2674
|
+
if (polygon.faces.size > 0) {
|
|
2675
|
+
return polygon;
|
|
2676
|
+
}
|
|
2677
|
+
} catch {
|
|
2678
|
+
}
|
|
2679
|
+
return null;
|
|
2680
|
+
};
|
|
2681
|
+
var createOxidationCleaningLayerForLayer = async ({
|
|
2682
|
+
layer,
|
|
2683
|
+
ctx
|
|
2684
|
+
}) => {
|
|
2685
|
+
const { project, boardOutlineContour } = ctx;
|
|
2686
|
+
const cutSetting = layer === "top" ? ctx.topOxidationCleaningCutSetting : ctx.bottomOxidationCleaningCutSetting;
|
|
2687
|
+
if (!cutSetting) {
|
|
2688
|
+
return;
|
|
2689
|
+
}
|
|
2690
|
+
if (!boardOutlineContour || boardOutlineContour.length < 3) {
|
|
2691
|
+
console.warn(
|
|
2692
|
+
`Cannot create oxidation cleaning layer for ${layer}: no board outline available`
|
|
2693
|
+
);
|
|
2694
|
+
return;
|
|
2695
|
+
}
|
|
2696
|
+
try {
|
|
2697
|
+
const manifold = await getManifold();
|
|
2698
|
+
const { CrossSection } = manifold;
|
|
2699
|
+
const fillArea = new CrossSection([boardOutlineContour], "Positive");
|
|
2700
|
+
const simplifiedArea = fillArea.simplify(1e-3);
|
|
2701
|
+
const resultContours = simplifiedArea.toPolygons();
|
|
2702
|
+
if (resultContours.length === 0) {
|
|
2703
|
+
fillArea.delete();
|
|
2704
|
+
simplifiedArea.delete();
|
|
2705
|
+
return;
|
|
2706
|
+
}
|
|
2707
|
+
const shapeGroup = new ShapeGroup2();
|
|
2708
|
+
for (const contour of resultContours) {
|
|
2709
|
+
const polygon = contourToPolygon2(contour);
|
|
2710
|
+
if (!polygon) continue;
|
|
2711
|
+
for (const island of polygon.splitToIslands()) {
|
|
2712
|
+
const { verts, prims } = polygonToShapePathData(island);
|
|
2713
|
+
if (verts.length > 0) {
|
|
2714
|
+
shapeGroup.children.push(
|
|
2715
|
+
new ShapePath29({
|
|
2716
|
+
cutIndex: cutSetting.index,
|
|
2717
|
+
verts,
|
|
2718
|
+
prims,
|
|
2719
|
+
isClosed: true
|
|
2720
|
+
// Filled shapes should be closed
|
|
2721
|
+
})
|
|
2722
|
+
);
|
|
2723
|
+
}
|
|
2724
|
+
}
|
|
2725
|
+
}
|
|
2726
|
+
if (shapeGroup.children.length > 0) {
|
|
2727
|
+
project.children.push(shapeGroup);
|
|
2728
|
+
}
|
|
2729
|
+
fillArea.delete();
|
|
2730
|
+
simplifiedArea.delete();
|
|
2731
|
+
} catch (error) {
|
|
2732
|
+
console.warn(
|
|
2733
|
+
`Failed to create oxidation cleaning layer for ${layer} layer:`,
|
|
2734
|
+
error
|
|
2735
|
+
);
|
|
2736
|
+
}
|
|
2737
|
+
};
|
|
2738
|
+
|
|
2739
|
+
// lib/layer-indexes.ts
|
|
2740
|
+
var LAYER_INDEXES = {
|
|
2741
|
+
// Cut layers (vector/outline)
|
|
2742
|
+
topCopper: 0,
|
|
2743
|
+
bottomCopper: 1,
|
|
2744
|
+
throughBoard: 2,
|
|
2745
|
+
// Scan layers (raster/fill)
|
|
2746
|
+
soldermask: 3,
|
|
2747
|
+
topTraceClearance: 4,
|
|
2748
|
+
bottomTraceClearance: 5,
|
|
2749
|
+
topCopperCutFill: 6,
|
|
2750
|
+
bottomCopperCutFill: 7,
|
|
2751
|
+
topOxidationCleaning: 8,
|
|
2752
|
+
bottomOxidationCleaning: 9
|
|
2753
|
+
};
|
|
2754
|
+
|
|
2666
2755
|
// lib/index.ts
|
|
2667
2756
|
var convertCircuitJsonToLbrn = async (circuitJson, options = {}) => {
|
|
2668
2757
|
const db = cju2(circuitJson);
|
|
@@ -2680,6 +2769,7 @@ var convertCircuitJsonToLbrn = async (circuitJson, options = {}) => {
|
|
|
2680
2769
|
const laserProfile = options.laserProfile;
|
|
2681
2770
|
const includeCopperCutFill = options.includeCopperCutFill ?? false;
|
|
2682
2771
|
const copperCutFillMargin = options.copperCutFillMargin ?? 0.5;
|
|
2772
|
+
const includeOxidationCleaningLayer = options.includeOxidationCleaningLayer ?? false;
|
|
2683
2773
|
const defaultCopperSettings = {
|
|
2684
2774
|
speed: 300,
|
|
2685
2775
|
numPasses: 100,
|
|
@@ -2699,7 +2789,7 @@ var convertCircuitJsonToLbrn = async (circuitJson, options = {}) => {
|
|
|
2699
2789
|
throw new Error("traceMargin requires includeCopper to be true");
|
|
2700
2790
|
}
|
|
2701
2791
|
const topCopperCutSetting = new CutSetting({
|
|
2702
|
-
index:
|
|
2792
|
+
index: LAYER_INDEXES.topCopper,
|
|
2703
2793
|
name: "Cut Top Copper",
|
|
2704
2794
|
numPasses: copperSettings.numPasses,
|
|
2705
2795
|
speed: copperSettings.speed,
|
|
@@ -2708,7 +2798,7 @@ var convertCircuitJsonToLbrn = async (circuitJson, options = {}) => {
|
|
|
2708
2798
|
});
|
|
2709
2799
|
project.children.push(topCopperCutSetting);
|
|
2710
2800
|
const bottomCopperCutSetting = new CutSetting({
|
|
2711
|
-
index:
|
|
2801
|
+
index: LAYER_INDEXES.bottomCopper,
|
|
2712
2802
|
name: "Cut Bottom Copper",
|
|
2713
2803
|
numPasses: copperSettings.numPasses,
|
|
2714
2804
|
speed: copperSettings.speed,
|
|
@@ -2717,7 +2807,7 @@ var convertCircuitJsonToLbrn = async (circuitJson, options = {}) => {
|
|
|
2717
2807
|
});
|
|
2718
2808
|
project.children.push(bottomCopperCutSetting);
|
|
2719
2809
|
const throughBoardCutSetting = new CutSetting({
|
|
2720
|
-
index:
|
|
2810
|
+
index: LAYER_INDEXES.throughBoard,
|
|
2721
2811
|
name: "Cut Through Board",
|
|
2722
2812
|
numPasses: boardSettings.numPasses,
|
|
2723
2813
|
speed: boardSettings.speed,
|
|
@@ -2727,7 +2817,7 @@ var convertCircuitJsonToLbrn = async (circuitJson, options = {}) => {
|
|
|
2727
2817
|
project.children.push(throughBoardCutSetting);
|
|
2728
2818
|
const soldermaskCutSetting = new CutSetting({
|
|
2729
2819
|
type: "Scan",
|
|
2730
|
-
index:
|
|
2820
|
+
index: LAYER_INDEXES.soldermask,
|
|
2731
2821
|
name: "Cut Soldermask",
|
|
2732
2822
|
numPasses: 1,
|
|
2733
2823
|
speed: 150,
|
|
@@ -2737,14 +2827,13 @@ var convertCircuitJsonToLbrn = async (circuitJson, options = {}) => {
|
|
|
2737
2827
|
crossHatch: true
|
|
2738
2828
|
});
|
|
2739
2829
|
project.children.push(soldermaskCutSetting);
|
|
2740
|
-
let nextCutIndex = 4;
|
|
2741
2830
|
let topTraceClearanceAreaCutSetting;
|
|
2742
2831
|
let bottomTraceClearanceAreaCutSetting;
|
|
2743
2832
|
if (shouldGenerateTraceClearanceZones) {
|
|
2744
2833
|
if (includeLayers.includes("top")) {
|
|
2745
2834
|
topTraceClearanceAreaCutSetting = new CutSetting({
|
|
2746
2835
|
type: "Scan",
|
|
2747
|
-
index:
|
|
2836
|
+
index: LAYER_INDEXES.topTraceClearance,
|
|
2748
2837
|
name: "Clear Top Trace Clearance Areas",
|
|
2749
2838
|
numPasses: 12,
|
|
2750
2839
|
speed: 100,
|
|
@@ -2758,7 +2847,7 @@ var convertCircuitJsonToLbrn = async (circuitJson, options = {}) => {
|
|
|
2758
2847
|
if (includeLayers.includes("bottom")) {
|
|
2759
2848
|
bottomTraceClearanceAreaCutSetting = new CutSetting({
|
|
2760
2849
|
type: "Scan",
|
|
2761
|
-
index:
|
|
2850
|
+
index: LAYER_INDEXES.bottomTraceClearance,
|
|
2762
2851
|
name: "Clear Bottom Trace Clearance Areas",
|
|
2763
2852
|
numPasses: 12,
|
|
2764
2853
|
speed: 100,
|
|
@@ -2776,7 +2865,7 @@ var convertCircuitJsonToLbrn = async (circuitJson, options = {}) => {
|
|
|
2776
2865
|
if (includeLayers.includes("top")) {
|
|
2777
2866
|
topCopperCutFillCutSetting = new CutSetting({
|
|
2778
2867
|
type: "Scan",
|
|
2779
|
-
index:
|
|
2868
|
+
index: LAYER_INDEXES.topCopperCutFill,
|
|
2780
2869
|
name: "Top Copper Cut Fill",
|
|
2781
2870
|
numPasses: copperSettings.numPasses,
|
|
2782
2871
|
speed: copperSettings.speed,
|
|
@@ -2790,7 +2879,7 @@ var convertCircuitJsonToLbrn = async (circuitJson, options = {}) => {
|
|
|
2790
2879
|
if (includeLayers.includes("bottom")) {
|
|
2791
2880
|
bottomCopperCutFillCutSetting = new CutSetting({
|
|
2792
2881
|
type: "Scan",
|
|
2793
|
-
index:
|
|
2882
|
+
index: LAYER_INDEXES.bottomCopperCutFill,
|
|
2794
2883
|
name: "Bottom Copper Cut Fill",
|
|
2795
2884
|
numPasses: copperSettings.numPasses,
|
|
2796
2885
|
speed: copperSettings.speed,
|
|
@@ -2802,6 +2891,38 @@ var convertCircuitJsonToLbrn = async (circuitJson, options = {}) => {
|
|
|
2802
2891
|
project.children.push(bottomCopperCutFillCutSetting);
|
|
2803
2892
|
}
|
|
2804
2893
|
}
|
|
2894
|
+
let topOxidationCleaningCutSetting;
|
|
2895
|
+
let bottomOxidationCleaningCutSetting;
|
|
2896
|
+
if (includeOxidationCleaningLayer) {
|
|
2897
|
+
if (includeLayers.includes("top")) {
|
|
2898
|
+
topOxidationCleaningCutSetting = new CutSetting({
|
|
2899
|
+
type: "Scan",
|
|
2900
|
+
index: LAYER_INDEXES.topOxidationCleaning,
|
|
2901
|
+
name: "Top Oxidation Cleaning",
|
|
2902
|
+
numPasses: copperSettings.numPasses,
|
|
2903
|
+
speed: 500,
|
|
2904
|
+
scanOpt: "individual",
|
|
2905
|
+
interval: laserSpotSize,
|
|
2906
|
+
angle: 45,
|
|
2907
|
+
crossHatch: true
|
|
2908
|
+
});
|
|
2909
|
+
project.children.push(topOxidationCleaningCutSetting);
|
|
2910
|
+
}
|
|
2911
|
+
if (includeLayers.includes("bottom")) {
|
|
2912
|
+
bottomOxidationCleaningCutSetting = new CutSetting({
|
|
2913
|
+
type: "Scan",
|
|
2914
|
+
index: LAYER_INDEXES.bottomOxidationCleaning,
|
|
2915
|
+
name: "Bottom Oxidation Cleaning",
|
|
2916
|
+
numPasses: copperSettings.numPasses,
|
|
2917
|
+
speed: 500,
|
|
2918
|
+
scanOpt: "individual",
|
|
2919
|
+
interval: laserSpotSize,
|
|
2920
|
+
angle: 45,
|
|
2921
|
+
crossHatch: true
|
|
2922
|
+
});
|
|
2923
|
+
project.children.push(bottomOxidationCleaningCutSetting);
|
|
2924
|
+
}
|
|
2925
|
+
}
|
|
2805
2926
|
const connMap = getFullConnectivityMapFromCircuitJson(circuitJson);
|
|
2806
2927
|
let origin = options.origin;
|
|
2807
2928
|
if (!origin) {
|
|
@@ -2833,6 +2954,8 @@ var convertCircuitJsonToLbrn = async (circuitJson, options = {}) => {
|
|
|
2833
2954
|
topCopperCutFillCutSetting,
|
|
2834
2955
|
bottomCopperCutFillCutSetting,
|
|
2835
2956
|
copperCutFillMargin,
|
|
2957
|
+
topOxidationCleaningCutSetting,
|
|
2958
|
+
bottomOxidationCleaningCutSetting,
|
|
2836
2959
|
topTraceEndpoints: /* @__PURE__ */ new Set(),
|
|
2837
2960
|
bottomTraceEndpoints: /* @__PURE__ */ new Set()
|
|
2838
2961
|
};
|
|
@@ -2909,6 +3032,14 @@ var convertCircuitJsonToLbrn = async (circuitJson, options = {}) => {
|
|
|
2909
3032
|
await createCopperCutFillForLayer({ layer: "bottom", ctx });
|
|
2910
3033
|
}
|
|
2911
3034
|
}
|
|
3035
|
+
if (includeOxidationCleaningLayer) {
|
|
3036
|
+
if (includeLayers.includes("top")) {
|
|
3037
|
+
await createOxidationCleaningLayerForLayer({ layer: "top", ctx });
|
|
3038
|
+
}
|
|
3039
|
+
if (includeLayers.includes("bottom")) {
|
|
3040
|
+
await createOxidationCleaningLayerForLayer({ layer: "bottom", ctx });
|
|
3041
|
+
}
|
|
3042
|
+
}
|
|
2912
3043
|
return project;
|
|
2913
3044
|
};
|
|
2914
3045
|
export {
|
package/lib/ConvertContext.ts
CHANGED
|
@@ -50,6 +50,10 @@ export interface ConvertContext {
|
|
|
50
50
|
topCopperCutFillCutSetting?: CutSetting
|
|
51
51
|
bottomCopperCutFillCutSetting?: CutSetting
|
|
52
52
|
|
|
53
|
+
// Cut settings for oxidation cleaning layer
|
|
54
|
+
topOxidationCleaningCutSetting?: CutSetting
|
|
55
|
+
bottomOxidationCleaningCutSetting?: CutSetting
|
|
56
|
+
|
|
53
57
|
// Percent-based solder mask margin (scales with element size)
|
|
54
58
|
solderMaskMarginPercent: number
|
|
55
59
|
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { Polygon, Point } from "@flatten-js/core"
|
|
2
|
+
import type { ConvertContext } from "./ConvertContext"
|
|
3
|
+
import { polygonToShapePathData } from "./polygon-to-shape-path"
|
|
4
|
+
import { ShapePath, ShapeGroup } from "lbrnts"
|
|
5
|
+
import { getManifold } from "./getManifold"
|
|
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
|
+
/**
|
|
28
|
+
* Creates an oxidation cleaning layer for a given layer.
|
|
29
|
+
*
|
|
30
|
+
* This generates a filled area covering the entire "inside" of the board outline
|
|
31
|
+
* that can be used to laser clean oxidation from the copper surface.
|
|
32
|
+
*
|
|
33
|
+
* The algorithm:
|
|
34
|
+
* 1. Get the board outline contour
|
|
35
|
+
* 2. Optionally inset (shrink) the outline by the margin to leave a border
|
|
36
|
+
* 3. Create SCAN mode shapes that will fill the entire board area
|
|
37
|
+
*/
|
|
38
|
+
export const createOxidationCleaningLayerForLayer = async ({
|
|
39
|
+
layer,
|
|
40
|
+
ctx,
|
|
41
|
+
}: {
|
|
42
|
+
layer: "top" | "bottom"
|
|
43
|
+
ctx: ConvertContext
|
|
44
|
+
}): Promise<void> => {
|
|
45
|
+
const { project, boardOutlineContour } = ctx
|
|
46
|
+
|
|
47
|
+
// Get the appropriate cut setting for this layer
|
|
48
|
+
const cutSetting =
|
|
49
|
+
layer === "top"
|
|
50
|
+
? ctx.topOxidationCleaningCutSetting
|
|
51
|
+
: ctx.bottomOxidationCleaningCutSetting
|
|
52
|
+
|
|
53
|
+
if (!cutSetting) {
|
|
54
|
+
return
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (!boardOutlineContour || boardOutlineContour.length < 3) {
|
|
58
|
+
console.warn(
|
|
59
|
+
`Cannot create oxidation cleaning layer for ${layer}: no board outline available`,
|
|
60
|
+
)
|
|
61
|
+
return
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
const manifold = await getManifold()
|
|
66
|
+
const { CrossSection } = manifold
|
|
67
|
+
|
|
68
|
+
// Create board outline as CrossSection (the fill area)
|
|
69
|
+
const fillArea = new CrossSection([boardOutlineContour], "Positive")
|
|
70
|
+
|
|
71
|
+
// Simplify to clean up any spurious tiny segments
|
|
72
|
+
const simplifiedArea = fillArea.simplify(0.001)
|
|
73
|
+
|
|
74
|
+
// Get the resulting contours
|
|
75
|
+
const resultContours: Contour[] = simplifiedArea.toPolygons()
|
|
76
|
+
|
|
77
|
+
if (resultContours.length === 0) {
|
|
78
|
+
fillArea.delete()
|
|
79
|
+
simplifiedArea.delete()
|
|
80
|
+
return
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Create a ShapeGroup to hold all contours
|
|
84
|
+
const shapeGroup = new ShapeGroup()
|
|
85
|
+
|
|
86
|
+
for (const contour of resultContours) {
|
|
87
|
+
const polygon = contourToPolygon(contour)
|
|
88
|
+
if (!polygon) continue
|
|
89
|
+
|
|
90
|
+
for (const island of polygon.splitToIslands()) {
|
|
91
|
+
const { verts, prims } = polygonToShapePathData(island)
|
|
92
|
+
|
|
93
|
+
if (verts.length > 0) {
|
|
94
|
+
shapeGroup.children.push(
|
|
95
|
+
new ShapePath({
|
|
96
|
+
cutIndex: cutSetting.index,
|
|
97
|
+
verts,
|
|
98
|
+
prims,
|
|
99
|
+
isClosed: true, // Filled shapes should be closed
|
|
100
|
+
}),
|
|
101
|
+
)
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Add the group to the project if it has shapes
|
|
107
|
+
if (shapeGroup.children.length > 0) {
|
|
108
|
+
project.children.push(shapeGroup)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Clean up WASM memory
|
|
112
|
+
fillArea.delete()
|
|
113
|
+
simplifiedArea.delete()
|
|
114
|
+
} catch (error) {
|
|
115
|
+
console.warn(
|
|
116
|
+
`Failed to create oxidation cleaning layer for ${layer} layer:`,
|
|
117
|
+
error,
|
|
118
|
+
)
|
|
119
|
+
}
|
|
120
|
+
}
|
package/lib/index.ts
CHANGED
|
@@ -17,6 +17,8 @@ import { addPcbCutout } from "./element-handlers/addPcbCutout"
|
|
|
17
17
|
import { createCopperShapesForLayer } from "./createCopperShapesForLayer"
|
|
18
18
|
import { createTraceClearanceAreasForLayer } from "./createTraceClearanceAreasForLayer"
|
|
19
19
|
import { createCopperCutFillForLayer } from "./createCopperCutFillForLayer"
|
|
20
|
+
import { createOxidationCleaningLayerForLayer } from "./createOxidationCleaningLayerForLayer"
|
|
21
|
+
import { LAYER_INDEXES } from "./layer-indexes"
|
|
20
22
|
|
|
21
23
|
export interface ConvertCircuitJsonToLbrnOptions {
|
|
22
24
|
includeSilkscreen?: boolean
|
|
@@ -40,6 +42,12 @@ export interface ConvertCircuitJsonToLbrnOptions {
|
|
|
40
42
|
* This determines how wide the band of copper removal will be around traces/pads.
|
|
41
43
|
*/
|
|
42
44
|
copperCutFillMargin?: number
|
|
45
|
+
/**
|
|
46
|
+
* Whether to generate an oxidation cleaning layer.
|
|
47
|
+
* Creates a filled area covering the entire "inside" of the board outline
|
|
48
|
+
* for laser ablation to clean oxidation from the copper surface.
|
|
49
|
+
*/
|
|
50
|
+
includeOxidationCleaningLayer?: boolean
|
|
43
51
|
laserProfile?: {
|
|
44
52
|
copper?: {
|
|
45
53
|
speed?: number
|
|
@@ -77,6 +85,8 @@ export const convertCircuitJsonToLbrn = async (
|
|
|
77
85
|
const laserProfile = options.laserProfile
|
|
78
86
|
const includeCopperCutFill = options.includeCopperCutFill ?? false
|
|
79
87
|
const copperCutFillMargin = options.copperCutFillMargin ?? 0.5
|
|
88
|
+
const includeOxidationCleaningLayer =
|
|
89
|
+
options.includeOxidationCleaningLayer ?? false
|
|
80
90
|
|
|
81
91
|
// Default laser settings
|
|
82
92
|
const defaultCopperSettings = {
|
|
@@ -106,7 +116,7 @@ export const convertCircuitJsonToLbrn = async (
|
|
|
106
116
|
|
|
107
117
|
// Create cut settings
|
|
108
118
|
const topCopperCutSetting = new CutSetting({
|
|
109
|
-
index:
|
|
119
|
+
index: LAYER_INDEXES.topCopper,
|
|
110
120
|
name: "Cut Top Copper",
|
|
111
121
|
numPasses: copperSettings.numPasses,
|
|
112
122
|
speed: copperSettings.speed,
|
|
@@ -116,7 +126,7 @@ export const convertCircuitJsonToLbrn = async (
|
|
|
116
126
|
project.children.push(topCopperCutSetting)
|
|
117
127
|
|
|
118
128
|
const bottomCopperCutSetting = new CutSetting({
|
|
119
|
-
index:
|
|
129
|
+
index: LAYER_INDEXES.bottomCopper,
|
|
120
130
|
name: "Cut Bottom Copper",
|
|
121
131
|
numPasses: copperSettings.numPasses,
|
|
122
132
|
speed: copperSettings.speed,
|
|
@@ -126,7 +136,7 @@ export const convertCircuitJsonToLbrn = async (
|
|
|
126
136
|
project.children.push(bottomCopperCutSetting)
|
|
127
137
|
|
|
128
138
|
const throughBoardCutSetting = new CutSetting({
|
|
129
|
-
index:
|
|
139
|
+
index: LAYER_INDEXES.throughBoard,
|
|
130
140
|
name: "Cut Through Board",
|
|
131
141
|
numPasses: boardSettings.numPasses,
|
|
132
142
|
speed: boardSettings.speed,
|
|
@@ -137,7 +147,7 @@ export const convertCircuitJsonToLbrn = async (
|
|
|
137
147
|
|
|
138
148
|
const soldermaskCutSetting = new CutSetting({
|
|
139
149
|
type: "Scan",
|
|
140
|
-
index:
|
|
150
|
+
index: LAYER_INDEXES.soldermask,
|
|
141
151
|
name: "Cut Soldermask",
|
|
142
152
|
numPasses: 1,
|
|
143
153
|
speed: 150,
|
|
@@ -148,9 +158,6 @@ export const convertCircuitJsonToLbrn = async (
|
|
|
148
158
|
})
|
|
149
159
|
project.children.push(soldermaskCutSetting)
|
|
150
160
|
|
|
151
|
-
// Track the next available cut setting index
|
|
152
|
-
let nextCutIndex = 4
|
|
153
|
-
|
|
154
161
|
// Create trace clearance cut settings if needed
|
|
155
162
|
let topTraceClearanceAreaCutSetting: CutSetting | undefined
|
|
156
163
|
let bottomTraceClearanceAreaCutSetting: CutSetting | undefined
|
|
@@ -159,7 +166,7 @@ export const convertCircuitJsonToLbrn = async (
|
|
|
159
166
|
if (includeLayers.includes("top")) {
|
|
160
167
|
topTraceClearanceAreaCutSetting = new CutSetting({
|
|
161
168
|
type: "Scan",
|
|
162
|
-
index:
|
|
169
|
+
index: LAYER_INDEXES.topTraceClearance,
|
|
163
170
|
name: "Clear Top Trace Clearance Areas",
|
|
164
171
|
numPasses: 12,
|
|
165
172
|
speed: 100,
|
|
@@ -174,7 +181,7 @@ export const convertCircuitJsonToLbrn = async (
|
|
|
174
181
|
if (includeLayers.includes("bottom")) {
|
|
175
182
|
bottomTraceClearanceAreaCutSetting = new CutSetting({
|
|
176
183
|
type: "Scan",
|
|
177
|
-
index:
|
|
184
|
+
index: LAYER_INDEXES.bottomTraceClearance,
|
|
178
185
|
name: "Clear Bottom Trace Clearance Areas",
|
|
179
186
|
numPasses: 12,
|
|
180
187
|
speed: 100,
|
|
@@ -195,7 +202,7 @@ export const convertCircuitJsonToLbrn = async (
|
|
|
195
202
|
if (includeLayers.includes("top")) {
|
|
196
203
|
topCopperCutFillCutSetting = new CutSetting({
|
|
197
204
|
type: "Scan",
|
|
198
|
-
index:
|
|
205
|
+
index: LAYER_INDEXES.topCopperCutFill,
|
|
199
206
|
name: "Top Copper Cut Fill",
|
|
200
207
|
numPasses: copperSettings.numPasses,
|
|
201
208
|
speed: copperSettings.speed,
|
|
@@ -210,7 +217,7 @@ export const convertCircuitJsonToLbrn = async (
|
|
|
210
217
|
if (includeLayers.includes("bottom")) {
|
|
211
218
|
bottomCopperCutFillCutSetting = new CutSetting({
|
|
212
219
|
type: "Scan",
|
|
213
|
-
index:
|
|
220
|
+
index: LAYER_INDEXES.bottomCopperCutFill,
|
|
214
221
|
name: "Bottom Copper Cut Fill",
|
|
215
222
|
numPasses: copperSettings.numPasses,
|
|
216
223
|
speed: copperSettings.speed,
|
|
@@ -223,6 +230,42 @@ export const convertCircuitJsonToLbrn = async (
|
|
|
223
230
|
}
|
|
224
231
|
}
|
|
225
232
|
|
|
233
|
+
// Create oxidation cleaning layer cut settings if needed
|
|
234
|
+
let topOxidationCleaningCutSetting: CutSetting | undefined
|
|
235
|
+
let bottomOxidationCleaningCutSetting: CutSetting | undefined
|
|
236
|
+
|
|
237
|
+
if (includeOxidationCleaningLayer) {
|
|
238
|
+
if (includeLayers.includes("top")) {
|
|
239
|
+
topOxidationCleaningCutSetting = new CutSetting({
|
|
240
|
+
type: "Scan",
|
|
241
|
+
index: LAYER_INDEXES.topOxidationCleaning,
|
|
242
|
+
name: "Top Oxidation Cleaning",
|
|
243
|
+
numPasses: copperSettings.numPasses,
|
|
244
|
+
speed: 500,
|
|
245
|
+
scanOpt: "individual",
|
|
246
|
+
interval: laserSpotSize,
|
|
247
|
+
angle: 45,
|
|
248
|
+
crossHatch: true,
|
|
249
|
+
})
|
|
250
|
+
project.children.push(topOxidationCleaningCutSetting)
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (includeLayers.includes("bottom")) {
|
|
254
|
+
bottomOxidationCleaningCutSetting = new CutSetting({
|
|
255
|
+
type: "Scan",
|
|
256
|
+
index: LAYER_INDEXES.bottomOxidationCleaning,
|
|
257
|
+
name: "Bottom Oxidation Cleaning",
|
|
258
|
+
numPasses: copperSettings.numPasses,
|
|
259
|
+
speed: 500,
|
|
260
|
+
scanOpt: "individual",
|
|
261
|
+
interval: laserSpotSize,
|
|
262
|
+
angle: 45,
|
|
263
|
+
crossHatch: true,
|
|
264
|
+
})
|
|
265
|
+
project.children.push(bottomOxidationCleaningCutSetting)
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
226
269
|
// Build connectivity map and origin
|
|
227
270
|
const connMap = getFullConnectivityMapFromCircuitJson(circuitJson)
|
|
228
271
|
let origin = options.origin
|
|
@@ -257,6 +300,8 @@ export const convertCircuitJsonToLbrn = async (
|
|
|
257
300
|
topCopperCutFillCutSetting,
|
|
258
301
|
bottomCopperCutFillCutSetting,
|
|
259
302
|
copperCutFillMargin,
|
|
303
|
+
topOxidationCleaningCutSetting,
|
|
304
|
+
bottomOxidationCleaningCutSetting,
|
|
260
305
|
topTraceEndpoints: new Set(),
|
|
261
306
|
bottomTraceEndpoints: new Set(),
|
|
262
307
|
}
|
|
@@ -356,5 +401,15 @@ export const convertCircuitJsonToLbrn = async (
|
|
|
356
401
|
}
|
|
357
402
|
}
|
|
358
403
|
|
|
404
|
+
// Create oxidation cleaning layer for each layer
|
|
405
|
+
if (includeOxidationCleaningLayer) {
|
|
406
|
+
if (includeLayers.includes("top")) {
|
|
407
|
+
await createOxidationCleaningLayerForLayer({ layer: "top", ctx })
|
|
408
|
+
}
|
|
409
|
+
if (includeLayers.includes("bottom")) {
|
|
410
|
+
await createOxidationCleaningLayerForLayer({ layer: "bottom", ctx })
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
359
414
|
return project
|
|
360
415
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Consistent layer index assignments for LightBurn cut settings.
|
|
3
|
+
* Each layer type always gets the same index (and thus the same color)
|
|
4
|
+
* regardless of what other layers are enabled.
|
|
5
|
+
*/
|
|
6
|
+
export const LAYER_INDEXES = {
|
|
7
|
+
// Cut layers (vector/outline)
|
|
8
|
+
topCopper: 0,
|
|
9
|
+
bottomCopper: 1,
|
|
10
|
+
throughBoard: 2,
|
|
11
|
+
|
|
12
|
+
// Scan layers (raster/fill)
|
|
13
|
+
soldermask: 3,
|
|
14
|
+
topTraceClearance: 4,
|
|
15
|
+
bottomTraceClearance: 5,
|
|
16
|
+
topCopperCutFill: 6,
|
|
17
|
+
bottomCopperCutFill: 7,
|
|
18
|
+
topOxidationCleaning: 8,
|
|
19
|
+
bottomOxidationCleaning: 9,
|
|
20
|
+
} as const
|
|
21
|
+
|
|
22
|
+
export type LayerIndex = (typeof LAYER_INDEXES)[keyof typeof LAYER_INDEXES]
|
package/package.json
CHANGED
|
@@ -3,6 +3,6 @@
|
|
|
3
3
|
<style></style><rect class="boundary" x="0" y="0" fill="#000" width="800" height="600" data-type="pcb_background" data-pcb-layer="global"/><rect class="pcb-boundary" fill="none" stroke="#fff" stroke-width="0.3" x="127.27272727272725" y="27.272727272727252" width="545.4545454545455" height="545.4545454545455" data-type="pcb_boundary" data-pcb-layer="global"/><path class="pcb-board" d="M 127.27272727272725 572.7272727272727 L 672.7272727272727 572.7272727272727 L 672.7272727272727 27.272727272727252 L 127.27272727272725 27.272727272727252 Z" fill="none" stroke="rgba(255, 255, 255, 0.5)" stroke-width="2.7272727272727275" data-type="pcb_board" data-pcb-layer="board"/><path class="pcb-trace" stroke="rgb(200, 52, 52)" fill="none" d="M 263.6363636363636 300 L 536.3636363636364 300" stroke-width="13.636363636363637" stroke-linecap="round" stroke-linejoin="round" shape-rendering="crispEdges" data-type="pcb_trace" data-pcb-layer="top"/>
|
|
4
4
|
</g>
|
|
5
5
|
<g transform="translate(0, 600) scale(20, 20) translate(14.65, 19.65)">
|
|
6
|
-
<defs><pattern id="hatch-0.0050-45-true-
|
|
6
|
+
<defs><pattern id="hatch-0.0050-45-true-_2300FFFF-0.1000-0.80__stack1" patternUnits="userSpaceOnUse" width="0.005" height="0.005" patternTransform="rotate(45)"><path d="M -0.005 0 L 0.005 0 M 0 -0.005 L 0 0.005" stroke="#00FFFF" stroke-width="0.1" stroke-opacity="0.8"/></pattern></defs><rect x="-14.65" y="-19.65" width="40" height="40" fill="white"/><g transform="matrix(1 0 0 -1 0 0.7000000000000028)"><g transform="matrix(1,0,0,1,0,0)"><path d="M -4.65 -9.65 L 15.35 -9.65 L 15.35 10.35 L -4.65 10.35 L -4.65 -9.65 L -4.65 -9.65 Z" fill="none" stroke="#FF0000" stroke-width="0.1" stroke-linecap="round" stroke-linejoin="round"/></g><g transform="matrix(1,0,0,1,0,0)"><path d="M 10.23867555286557 0.09999999999999998 L 10.2447620560996 0.09593312855939617 L 10.35 0.07499999999999996 L 10.4552379439004 0.09593312855939612 L 10.5444543648263 0.15554563517369935 L 10.604066871440603 0.2447620560996001 L 10.625 0.35 L 10.604066871440603 0.4552379439003997 L 10.5444543648263 0.5444543648263005 L 10.4552379439004 0.6040668714406039 L 10.35 0.625 L 10.2447620560996 0.6040668714406039 L 10.238675552865573 0.6 L 0.46132444713442683 0.6 L 0.45523794390039934 0.6040668714406039 L 0.34999999999999964 0.625 L 0.24476205609959995 0.6040668714406039 L 0.15554563517369907 0.5444543648263006 L 0.09593312855939579 0.45523794390039973 L 0.07499999999999962 0.35000000000000003 L 0.09593312855939573 0.2447620560996003 L 0.15554563517369902 0.1555456351736994 L 0.24476205609959978 0.09593312855939617 L 0.3499999999999996 0.07499999999999996 L 0.4552379439003994 0.09593312855939612 L 0.46132444713442683 0.09999999999999998 L 10.23867555286557 0.09999999999999998 L 10.23867555286557 0.09999999999999998" fill="none" stroke="#000000" stroke-width="0.1" stroke-linecap="round" stroke-linejoin="round"/></g><g><path d="M 10.244762055575848 0.09593313187360764 L 10.238675557076931 0.10000000149011612 L 0.46132444590330124 0.10000000149011612 L 0.4552379474043846 0.09593313187360764 L 0.3500000014901161 0.07500000298023224 L 0.24476205557584763 0.09593313187360764 L 0.15554563701152802 0.15554563701152802 L 0.09593313187360764 0.24476205557584763 L 0.07500000298023224 0.3500000014901161 L 0.09593313187360764 0.4552379474043846 L 0.15554563701152802 0.5444543659687042 L 0.24476205557584763 0.6040668711066246 L 0.3500000014901161 0.625 L 0.4552379474043846 0.6040668711066246 L 0.46132444590330124 0.6000000014901161 L 10.238675557076931 0.6000000014901161 L 10.244762055575848 0.6040668711066246 L 10.350000001490116 0.625 L 10.455237947404385 0.6040668711066246 L 10.544454365968704 0.5444543659687042 L 10.604066871106625 0.4552379474043846 L 10.625 0.3500000014901161 L 10.604066871106625 0.24476205557584763 L 10.544454365968704 0.15554563701152802 L 10.455237947404385 0.09593313187360764 L 10.350000001490116 0.07500000298023224 L 10.244762055575848 0.09593313187360764 L 10.244762055575848 0.09593313187360764 Z M 10.447545163333416 -0.4153926372528076 L 10.552783109247684 -0.3944595083594322 L 10.646579667925835 -0.36600664258003235 L 10.733023069798946 -0.3198016807436943 L 10.822239503264427 -0.26018916070461273 L 10.898007772862911 -0.1980077400803566 L 10.960189193487167 -0.12223946303129196 L 11.019801691174507 -0.033023037016391754 L 11.066006653010845 0.05342037230730057 L 11.094459511339664 0.14721688628196716 L 11.11539264023304 0.25245483219623566 L 11.125 0.3499999865889549 L 11.11539264023304 0.447545163333416 L 11.094459511339664 0.5527831092476845 L 11.066006638109684 0.6465796679258347 L 11.019801691174507 0.7330230474472046 L 10.960189193487167 0.8222394734621048 L 10.898007780313492 0.8980077430605888 L 10.822239473462105 0.9601891934871674 L 10.733023047447205 1.0198016911745071 L 10.646579638123512 1.0660066530108452 L 10.552783109247684 1.0944595113396645 L 10.447545163333416 1.1153926402330399 L 10.350000001490116 1.125 L 10.252454832196236 1.1153926402330399 L 10.175070807337761 1.1000000014901161 L 0.5249291881918907 1.1000000014901161 L 0.447545163333416 1.1153926402330399 L 0.3500000014901161 1.125 L 0.25245483219623566 1.1153926402330399 L 0.14721688628196716 1.0944595113396645 L 0.053420327603816986 1.0660066306591034 L -0.033023037016391754 1.0198016911745071 L -0.12223946303129196 0.9601891934871674 L -0.198007732629776 0.8980077803134918 L -0.26018916070461273 0.8222395032644272 L -0.3198016732931137 0.733023077249527 L -0.36600662767887115 0.6465796828269958 L -0.3944595083594322 0.5527831315994263 L -0.4153926372528076 0.447545163333416 L -0.42499999701976776 0.3500000014901161 L -0.4153926372528076 0.25245483219623566 L -0.3944595083594322 0.14721688628196716 L -0.36600662767887115 0.053420327603816986 L -0.3198016732931137 -0.03302306681871414 L -0.26018916070461273 -0.12223949283361435 L -0.1980077400803566 -0.1980077624320984 L -0.12223949283361435 -0.26018916070461273 L -0.03302306681871414 -0.3198016732931137 L 0.053420327603816986 -0.36600662767887115 L 0.14721687883138657 -0.3944595083594322 L 0.25245483219623566 -0.4153926372528076 L 0.3499999865889549 -0.42499999701976776 L 0.447545163333416 -0.4153926372528076 L 0.5249291881918907 -0.3999999985098839 L 10.175070807337761 -0.3999999985098839 L 10.252454832196236 -0.4153926372528076 L 10.349999986588955 -0.42499999701976776 L 10.447545163333416 -0.4153926372528076 L 10.447545163333416 -0.4153926372528076 Z" fill="url(#hatch-0.0050-45-true-_2300FFFF-0.1000-0.80__stack1)" fill-rule="nonzero" stroke="none"/><path d="M 10.244762055575848 0.09593313187360764 L 10.238675557076931 0.10000000149011612 L 0.46132444590330124 0.10000000149011612 L 0.4552379474043846 0.09593313187360764 L 0.3500000014901161 0.07500000298023224 L 0.24476205557584763 0.09593313187360764 L 0.15554563701152802 0.15554563701152802 L 0.09593313187360764 0.24476205557584763 L 0.07500000298023224 0.3500000014901161 L 0.09593313187360764 0.4552379474043846 L 0.15554563701152802 0.5444543659687042 L 0.24476205557584763 0.6040668711066246 L 0.3500000014901161 0.625 L 0.4552379474043846 0.6040668711066246 L 0.46132444590330124 0.6000000014901161 L 10.238675557076931 0.6000000014901161 L 10.244762055575848 0.6040668711066246 L 10.350000001490116 0.625 L 10.455237947404385 0.6040668711066246 L 10.544454365968704 0.5444543659687042 L 10.604066871106625 0.4552379474043846 L 10.625 0.3500000014901161 L 10.604066871106625 0.24476205557584763 L 10.544454365968704 0.15554563701152802 L 10.455237947404385 0.09593313187360764 L 10.350000001490116 0.07500000298023224 L 10.244762055575848 0.09593313187360764 L 10.244762055575848 0.09593313187360764 Z M 10.447545163333416 -0.4153926372528076 L 10.552783109247684 -0.3944595083594322 L 10.646579667925835 -0.36600664258003235 L 10.733023069798946 -0.3198016807436943 L 10.822239503264427 -0.26018916070461273 L 10.898007772862911 -0.1980077400803566 L 10.960189193487167 -0.12223946303129196 L 11.019801691174507 -0.033023037016391754 L 11.066006653010845 0.05342037230730057 L 11.094459511339664 0.14721688628196716 L 11.11539264023304 0.25245483219623566 L 11.125 0.3499999865889549 L 11.11539264023304 0.447545163333416 L 11.094459511339664 0.5527831092476845 L 11.066006638109684 0.6465796679258347 L 11.019801691174507 0.7330230474472046 L 10.960189193487167 0.8222394734621048 L 10.898007780313492 0.8980077430605888 L 10.822239473462105 0.9601891934871674 L 10.733023047447205 1.0198016911745071 L 10.646579638123512 1.0660066530108452 L 10.552783109247684 1.0944595113396645 L 10.447545163333416 1.1153926402330399 L 10.350000001490116 1.125 L 10.252454832196236 1.1153926402330399 L 10.175070807337761 1.1000000014901161 L 0.5249291881918907 1.1000000014901161 L 0.447545163333416 1.1153926402330399 L 0.3500000014901161 1.125 L 0.25245483219623566 1.1153926402330399 L 0.14721688628196716 1.0944595113396645 L 0.053420327603816986 1.0660066306591034 L -0.033023037016391754 1.0198016911745071 L -0.12223946303129196 0.9601891934871674 L -0.198007732629776 0.8980077803134918 L -0.26018916070461273 0.8222395032644272 L -0.3198016732931137 0.733023077249527 L -0.36600662767887115 0.6465796828269958 L -0.3944595083594322 0.5527831315994263 L -0.4153926372528076 0.447545163333416 L -0.42499999701976776 0.3500000014901161 L -0.4153926372528076 0.25245483219623566 L -0.3944595083594322 0.14721688628196716 L -0.36600662767887115 0.053420327603816986 L -0.3198016732931137 -0.03302306681871414 L -0.26018916070461273 -0.12223949283361435 L -0.1980077400803566 -0.1980077624320984 L -0.12223949283361435 -0.26018916070461273 L -0.03302306681871414 -0.3198016732931137 L 0.053420327603816986 -0.36600662767887115 L 0.14721687883138657 -0.3944595083594322 L 0.25245483219623566 -0.4153926372528076 L 0.3499999865889549 -0.42499999701976776 L 0.447545163333416 -0.4153926372528076 L 0.5249291881918907 -0.3999999985098839 L 10.175070807337761 -0.3999999985098839 L 10.252454832196236 -0.4153926372528076 L 10.349999986588955 -0.42499999701976776 L 10.447545163333416 -0.4153926372528076 L 10.447545163333416 -0.4153926372528076 Z" fill="none" fill-rule="nonzero" stroke="#00FFFF" stroke-width="0.1"/></g></g>
|
|
7
7
|
</g>
|
|
8
8
|
</svg>
|