circuit-json-to-lbrn 0.0.1 → 0.0.2
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/.github/workflows/bun-formatcheck.yml +26 -0
- package/.github/workflows/bun-pver-release.yml +71 -0
- package/.github/workflows/bun-test.yml +31 -0
- package/.github/workflows/bun-typecheck.yml +26 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +162 -162
- package/lib/ConvertContext.ts +10 -0
- package/lib/element-handlers/addPcbTrace/circle-to-polygon.ts +15 -0
- package/lib/element-handlers/addPcbTrace/index.ts +90 -0
- package/lib/element-handlers/addSmtPad/addRectSmtPad.ts +15 -26
- package/lib/index.ts +91 -4
- package/lib/polygon-to-shape-path.ts +49 -0
- package/lib/writeDebugSvg.ts +43 -0
- package/package.json +4 -2
- package/site/index.html +0 -0
- package/site/main.tsx +0 -0
- package/tests/assets/lga-interconnect.circuit.json +1681 -1321
- package/tests/examples/__snapshots__/lga-interconnect.snap.svg +2 -2
- package/tests/examples/__snapshots__/single-trace.snap.svg +8 -0
- package/tests/examples/lga-interconnect.test.ts +4 -0
- package/tests/examples/single-trace.test.ts +67 -0
- package/tests/fixtures/preload.ts +1 -1
- package/tests/svg.test.ts +10 -11
- package/.claude/settings.local.json +0 -9
- package/tests/plated-hole.test.ts +0 -70
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Created using @tscircuit/plop (npm install -g @tscircuit/plop)
|
|
2
|
+
name: Format Check
|
|
3
|
+
|
|
4
|
+
on:
|
|
5
|
+
push:
|
|
6
|
+
branches: [main]
|
|
7
|
+
pull_request:
|
|
8
|
+
branches: [main]
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
format-check:
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v4
|
|
16
|
+
|
|
17
|
+
- name: Setup bun
|
|
18
|
+
uses: oven-sh/setup-bun@v2
|
|
19
|
+
with:
|
|
20
|
+
bun-version: latest
|
|
21
|
+
|
|
22
|
+
- name: Install dependencies
|
|
23
|
+
run: bun install
|
|
24
|
+
|
|
25
|
+
- name: Run format check
|
|
26
|
+
run: bun run format:check
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# Created using @tscircuit/plop (npm install -g @tscircuit/plop)
|
|
2
|
+
name: Publish to npm
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- main
|
|
7
|
+
workflow_dispatch:
|
|
8
|
+
|
|
9
|
+
env:
|
|
10
|
+
UPSTREAM_REPOS: "" # comma-separated list, e.g. "eval,tscircuit,docs"
|
|
11
|
+
UPSTREAM_PACKAGES_TO_UPDATE: "" # comma-separated list, e.g. "@tscircuit/core,@tscircuit/protos"
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
publish:
|
|
15
|
+
runs-on: ubuntu-latest
|
|
16
|
+
if: ${{ !(github.event_name == 'push' && startsWith(github.event.head_commit.message, 'v')) }}
|
|
17
|
+
steps:
|
|
18
|
+
- uses: actions/checkout@v4
|
|
19
|
+
with:
|
|
20
|
+
token: ${{ secrets.TSCIRCUIT_BOT_GITHUB_TOKEN }}
|
|
21
|
+
- name: Setup bun
|
|
22
|
+
uses: oven-sh/setup-bun@v2
|
|
23
|
+
with:
|
|
24
|
+
bun-version: latest
|
|
25
|
+
- uses: actions/setup-node@v3
|
|
26
|
+
with:
|
|
27
|
+
node-version: 20
|
|
28
|
+
registry-url: https://registry.npmjs.org/
|
|
29
|
+
- run: npm install -g pver
|
|
30
|
+
- run: bun install --frozen-lockfile
|
|
31
|
+
- run: bun run build
|
|
32
|
+
- run: pver release --no-push-main
|
|
33
|
+
env:
|
|
34
|
+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
35
|
+
GITHUB_TOKEN: ${{ secrets.TSCIRCUIT_BOT_GITHUB_TOKEN }}
|
|
36
|
+
|
|
37
|
+
- name: Create Pull Request
|
|
38
|
+
id: create-pr
|
|
39
|
+
uses: peter-evans/create-pull-request@v5
|
|
40
|
+
with:
|
|
41
|
+
commit-message: "chore: bump version"
|
|
42
|
+
title: "chore: bump version"
|
|
43
|
+
body: "Automated package update"
|
|
44
|
+
branch: bump-version-${{ github.run_number }}
|
|
45
|
+
base: main
|
|
46
|
+
token: ${{ secrets.TSCIRCUIT_BOT_GITHUB_TOKEN }}
|
|
47
|
+
committer: tscircuitbot <githubbot@tscircuit.com>
|
|
48
|
+
author: tscircuitbot <githubbot@tscircuit.com>
|
|
49
|
+
|
|
50
|
+
- name: Enable auto-merge
|
|
51
|
+
if: steps.create-pr.outputs.pull-request-number != ''
|
|
52
|
+
run: |
|
|
53
|
+
gh pr merge ${{ steps.create-pr.outputs.pull-request-number }} --auto --squash --delete-branch
|
|
54
|
+
env:
|
|
55
|
+
GH_TOKEN: ${{ secrets.TSCIRCUIT_BOT_GITHUB_TOKEN }}
|
|
56
|
+
|
|
57
|
+
# - name: Trigger upstream repo updates
|
|
58
|
+
# if: env.UPSTREAM_REPOS && env.UPSTREAM_PACKAGES_TO_UPDATE
|
|
59
|
+
# run: |
|
|
60
|
+
# IFS=',' read -ra REPOS <<< "${{ env.UPSTREAM_REPOS }}"
|
|
61
|
+
# for repo in "${REPOS[@]}"; do
|
|
62
|
+
# if [[ -n "$repo" ]]; then
|
|
63
|
+
# echo "Triggering update for repo: $repo"
|
|
64
|
+
# curl -X POST \
|
|
65
|
+
# -H "Accept: application/vnd.github.v3+json" \
|
|
66
|
+
# -H "Authorization: token ${{ secrets.TSCIRCUIT_BOT_GITHUB_TOKEN }}" \
|
|
67
|
+
# -H "Content-Type: application/json" \
|
|
68
|
+
# "https://api.github.com/repos/tscircuit/$repo/actions/workflows/update-package.yml/dispatches" \
|
|
69
|
+
# -d "{\"ref\":\"main\",\"inputs\":{\"package_names\":\"${{ env.UPSTREAM_PACKAGES_TO_UPDATE }}\"}}"
|
|
70
|
+
# fi
|
|
71
|
+
# done
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Created using @tscircuit/plop (npm install -g @tscircuit/plop)
|
|
2
|
+
name: Bun Test
|
|
3
|
+
|
|
4
|
+
on:
|
|
5
|
+
pull_request:
|
|
6
|
+
push:
|
|
7
|
+
branches:
|
|
8
|
+
- main
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
test:
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
timeout-minutes: 5
|
|
14
|
+
|
|
15
|
+
# Skip test for PRs that not chore: bump version
|
|
16
|
+
if: "${{ github.event_name != 'pull_request' || github.event.pull_request.title != 'chore: bump version' }}"
|
|
17
|
+
|
|
18
|
+
steps:
|
|
19
|
+
- name: Checkout code
|
|
20
|
+
uses: actions/checkout@v4
|
|
21
|
+
|
|
22
|
+
- name: Setup bun
|
|
23
|
+
uses: oven-sh/setup-bun@v2
|
|
24
|
+
with:
|
|
25
|
+
bun-version: 1.3.1
|
|
26
|
+
|
|
27
|
+
- name: Install dependencies
|
|
28
|
+
run: bun install
|
|
29
|
+
|
|
30
|
+
- name: Run tests
|
|
31
|
+
run: bun test
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Created using @tscircuit/plop (npm install -g @tscircuit/plop)
|
|
2
|
+
name: Type Check
|
|
3
|
+
|
|
4
|
+
on:
|
|
5
|
+
push:
|
|
6
|
+
branches: [main]
|
|
7
|
+
pull_request:
|
|
8
|
+
branches: [main]
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
type-check:
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v4
|
|
16
|
+
|
|
17
|
+
- name: Setup bun
|
|
18
|
+
uses: oven-sh/setup-bun@v2
|
|
19
|
+
with:
|
|
20
|
+
bun-version: latest
|
|
21
|
+
|
|
22
|
+
- name: Install dependencies
|
|
23
|
+
run: bun i
|
|
24
|
+
|
|
25
|
+
- name: Run type check
|
|
26
|
+
run: bunx tsc --noEmit
|
package/dist/index.d.ts
CHANGED
|
@@ -3,6 +3,10 @@ import { LightBurnProject } from 'lbrnts';
|
|
|
3
3
|
|
|
4
4
|
declare const convertCircuitJsonToLbrn: (circuitJson: CircuitJson, options?: {
|
|
5
5
|
includeSilkscreen?: boolean;
|
|
6
|
+
origin?: {
|
|
7
|
+
x: number;
|
|
8
|
+
y: number;
|
|
9
|
+
};
|
|
6
10
|
}) => LightBurnProject;
|
|
7
11
|
|
|
8
12
|
export { convertCircuitJsonToLbrn };
|
package/dist/index.js
CHANGED
|
@@ -1,172 +1,27 @@
|
|
|
1
1
|
// lib/index.ts
|
|
2
|
-
import { LightBurnProject, CutSetting } from "lbrnts";
|
|
2
|
+
import { LightBurnProject, CutSetting, ShapePath as ShapePath3 } from "lbrnts";
|
|
3
3
|
import { cju } from "@tscircuit/circuit-json-util";
|
|
4
4
|
|
|
5
5
|
// lib/element-handlers/addPlatedHole/addCirclePlatedHole.ts
|
|
6
6
|
import { ShapePath } from "lbrnts";
|
|
7
|
-
var addCirclePlatedHole = (platedHole, ctx) => {
|
|
8
|
-
const { project, copperCutSetting } = ctx;
|
|
9
|
-
const radius = platedHole.outer_diameter / 2;
|
|
10
|
-
const centerX = platedHole.x;
|
|
11
|
-
const centerY = platedHole.y;
|
|
12
|
-
const kappa = 0.5522847498;
|
|
13
|
-
const controlOffset = radius * kappa;
|
|
14
|
-
const verts = [
|
|
15
|
-
// Right point (3 o'clock)
|
|
16
|
-
{ x: centerX + radius, y: centerY },
|
|
17
|
-
// Bottom point (6 o'clock) with control points
|
|
18
|
-
{
|
|
19
|
-
x: centerX,
|
|
20
|
-
y: centerY + radius,
|
|
21
|
-
c: 1,
|
|
22
|
-
c0x: radius,
|
|
23
|
-
c0y: controlOffset,
|
|
24
|
-
c1x: controlOffset,
|
|
25
|
-
c1y: radius
|
|
26
|
-
},
|
|
27
|
-
// Left point (9 o'clock) with control points
|
|
28
|
-
{
|
|
29
|
-
x: centerX - radius,
|
|
30
|
-
y: centerY,
|
|
31
|
-
c: 1,
|
|
32
|
-
c0x: -controlOffset,
|
|
33
|
-
c0y: radius,
|
|
34
|
-
c1x: -radius,
|
|
35
|
-
c1y: controlOffset
|
|
36
|
-
},
|
|
37
|
-
// Top point (12 o'clock) with control points
|
|
38
|
-
{
|
|
39
|
-
x: centerX,
|
|
40
|
-
y: centerY - radius,
|
|
41
|
-
c: 1,
|
|
42
|
-
c0x: -radius,
|
|
43
|
-
c0y: -controlOffset,
|
|
44
|
-
c1x: -controlOffset,
|
|
45
|
-
c1y: -radius
|
|
46
|
-
},
|
|
47
|
-
// Back to right point (close path) with control points
|
|
48
|
-
{
|
|
49
|
-
x: centerX + radius,
|
|
50
|
-
y: centerY,
|
|
51
|
-
c: 1,
|
|
52
|
-
c0x: controlOffset,
|
|
53
|
-
c0y: -radius,
|
|
54
|
-
c1x: radius,
|
|
55
|
-
c1y: -controlOffset
|
|
56
|
-
}
|
|
57
|
-
];
|
|
58
|
-
const prims = [
|
|
59
|
-
{ type: 1 },
|
|
60
|
-
// BezierTo for bottom
|
|
61
|
-
{ type: 1 },
|
|
62
|
-
// BezierTo for left
|
|
63
|
-
{ type: 1 },
|
|
64
|
-
// BezierTo for top
|
|
65
|
-
{ type: 1 }
|
|
66
|
-
// BezierTo for right (closing)
|
|
67
|
-
];
|
|
68
|
-
project.children.push(
|
|
69
|
-
new ShapePath({
|
|
70
|
-
cutIndex: copperCutSetting.index,
|
|
71
|
-
verts,
|
|
72
|
-
prims,
|
|
73
|
-
isClosed: true
|
|
74
|
-
})
|
|
75
|
-
);
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
// lib/element-handlers/addPlatedHole/addOvalPlatedHole.ts
|
|
79
|
-
var addOvalPlatedHole = (platedHole, ctx) => {
|
|
80
|
-
console.warn(
|
|
81
|
-
`Oval/pill plated hole not yet implemented: ${platedHole.pcb_plated_hole_id}`
|
|
82
|
-
);
|
|
83
|
-
};
|
|
84
|
-
|
|
85
|
-
// lib/element-handlers/addPlatedHole/addCircularHoleWithRectPad.ts
|
|
86
|
-
var addCircularHoleWithRectPad = (platedHole, ctx) => {
|
|
87
|
-
console.warn(
|
|
88
|
-
`Circular hole with rect pad not yet implemented: ${platedHole.pcb_plated_hole_id}`
|
|
89
|
-
);
|
|
90
|
-
};
|
|
91
|
-
|
|
92
|
-
// lib/element-handlers/addPlatedHole/addPillHoleWithRectPad.ts
|
|
93
|
-
var addPillHoleWithRectPad = (platedHole, ctx) => {
|
|
94
|
-
console.warn(
|
|
95
|
-
`Pill hole with rect pad not yet implemented: ${platedHole.pcb_plated_hole_id}`
|
|
96
|
-
);
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
// lib/element-handlers/addPlatedHole/addRotatedPillHoleWithRectPad.ts
|
|
100
|
-
var addRotatedPillHoleWithRectPad = (platedHole, ctx) => {
|
|
101
|
-
console.warn(
|
|
102
|
-
`Rotated pill hole with rect pad not yet implemented: ${platedHole.pcb_plated_hole_id}`
|
|
103
|
-
);
|
|
104
|
-
};
|
|
105
|
-
|
|
106
|
-
// lib/element-handlers/addPlatedHole/addHoleWithPolygonPad.ts
|
|
107
|
-
var addHoleWithPolygonPad = (platedHole, ctx) => {
|
|
108
|
-
console.warn(
|
|
109
|
-
`Hole with polygon pad not yet implemented: ${platedHole.pcb_plated_hole_id}`
|
|
110
|
-
);
|
|
111
|
-
};
|
|
112
|
-
|
|
113
|
-
// lib/element-handlers/addPlatedHole/index.ts
|
|
114
|
-
var addPlatedHole = (platedHole, ctx) => {
|
|
115
|
-
switch (platedHole.shape) {
|
|
116
|
-
case "circle":
|
|
117
|
-
return addCirclePlatedHole(platedHole, ctx);
|
|
118
|
-
case "oval":
|
|
119
|
-
case "pill":
|
|
120
|
-
return addOvalPlatedHole(platedHole, ctx);
|
|
121
|
-
case "circular_hole_with_rect_pad":
|
|
122
|
-
return addCircularHoleWithRectPad(platedHole, ctx);
|
|
123
|
-
case "pill_hole_with_rect_pad":
|
|
124
|
-
return addPillHoleWithRectPad(platedHole, ctx);
|
|
125
|
-
case "rotated_pill_hole_with_rect_pad":
|
|
126
|
-
return addRotatedPillHoleWithRectPad(platedHole, ctx);
|
|
127
|
-
case "hole_with_polygon_pad":
|
|
128
|
-
return addHoleWithPolygonPad(platedHole, ctx);
|
|
129
|
-
default:
|
|
130
|
-
const _exhaustive = platedHole;
|
|
131
|
-
console.warn(`Unknown plated hole shape: ${platedHole.shape}`);
|
|
132
|
-
}
|
|
133
|
-
};
|
|
134
7
|
|
|
135
8
|
// lib/element-handlers/addSmtPad/addRectSmtPad.ts
|
|
136
|
-
import {
|
|
9
|
+
import { Box } from "@flatten-js/core";
|
|
10
|
+
import "lbrnts";
|
|
137
11
|
var addRectSmtPad = (smtPad, ctx) => {
|
|
138
|
-
const { project, copperCutSetting } = ctx;
|
|
139
|
-
const centerX = smtPad.x;
|
|
140
|
-
const centerY = smtPad.y;
|
|
12
|
+
const { project, copperCutSetting, connMap, netGeoms, origin } = ctx;
|
|
13
|
+
const centerX = smtPad.x + origin.x;
|
|
14
|
+
const centerY = smtPad.y + origin.y;
|
|
141
15
|
const halfWidth = smtPad.width / 2;
|
|
142
16
|
const halfHeight = smtPad.height / 2;
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
// Bottom-left
|
|
152
|
-
];
|
|
153
|
-
const prims = [
|
|
154
|
-
{ type: 0 },
|
|
155
|
-
// LineTo for top-right
|
|
156
|
-
{ type: 0 },
|
|
157
|
-
// LineTo for bottom-right
|
|
158
|
-
{ type: 0 },
|
|
159
|
-
// LineTo for bottom-left
|
|
160
|
-
{ type: 0 }
|
|
161
|
-
// LineTo back to top-left (closing)
|
|
162
|
-
];
|
|
163
|
-
project.children.push(
|
|
164
|
-
new ShapePath2({
|
|
165
|
-
cutIndex: copperCutSetting.index,
|
|
166
|
-
verts,
|
|
167
|
-
prims,
|
|
168
|
-
isClosed: true
|
|
169
|
-
})
|
|
17
|
+
const netId = connMap.getNetConnectedToId(smtPad.pcb_smtpad_id);
|
|
18
|
+
ctx.netGeoms.get(netId)?.push(
|
|
19
|
+
new Box(
|
|
20
|
+
centerX - halfWidth,
|
|
21
|
+
centerY - halfHeight,
|
|
22
|
+
centerX + halfWidth,
|
|
23
|
+
centerY + halfHeight
|
|
24
|
+
)
|
|
170
25
|
);
|
|
171
26
|
};
|
|
172
27
|
|
|
@@ -183,6 +38,116 @@ var addSmtPad = (smtPad, ctx) => {
|
|
|
183
38
|
}
|
|
184
39
|
};
|
|
185
40
|
|
|
41
|
+
// lib/element-handlers/addPcbTrace/index.ts
|
|
42
|
+
import Flatten2, { BooleanOperations } from "@flatten-js/core";
|
|
43
|
+
|
|
44
|
+
// lib/element-handlers/addPcbTrace/circle-to-polygon.ts
|
|
45
|
+
import Flatten from "@flatten-js/core";
|
|
46
|
+
var circleToPolygon = (circle, segments = 32) => {
|
|
47
|
+
const points = [];
|
|
48
|
+
for (let i = 0; i < segments; i++) {
|
|
49
|
+
const angle = i * 2 * Math.PI / segments;
|
|
50
|
+
const x = circle.center.x + circle.r * Math.cos(angle);
|
|
51
|
+
const y = circle.center.y + circle.r * Math.sin(angle);
|
|
52
|
+
points.push(Flatten.point(x, y));
|
|
53
|
+
}
|
|
54
|
+
return new Flatten.Polygon(points);
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
// lib/element-handlers/addPcbTrace/index.ts
|
|
58
|
+
var addPcbTrace = (trace, ctx) => {
|
|
59
|
+
const { netGeoms, connMap, origin } = ctx;
|
|
60
|
+
const netId = connMap.getNetConnectedToId(
|
|
61
|
+
trace.source_trace_id ?? trace.pcb_trace_id
|
|
62
|
+
);
|
|
63
|
+
if (!netId) {
|
|
64
|
+
console.warn(`Trace ${trace.pcb_trace_id} is not connected to any net`);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
if (!trace.route || trace.route.length < 2) {
|
|
68
|
+
console.warn(`Trace ${trace.pcb_trace_id} has insufficient route points`);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
const { route } = trace;
|
|
72
|
+
const traceWidth = route.find((point) => point.route_type === "wire")?.width ?? 0.15;
|
|
73
|
+
const polygons = [];
|
|
74
|
+
for (const routePoint of route) {
|
|
75
|
+
const circle = new Flatten2.Circle(
|
|
76
|
+
new Flatten2.Point(routePoint.x + origin.x, routePoint.y + origin.y),
|
|
77
|
+
traceWidth / 2
|
|
78
|
+
);
|
|
79
|
+
polygons.push(circleToPolygon(circle));
|
|
80
|
+
}
|
|
81
|
+
for (let i = 0; i < route.length - 1; i++) {
|
|
82
|
+
const p1 = route[i];
|
|
83
|
+
const p2 = route[i + 1];
|
|
84
|
+
if (!p1 || !p2) continue;
|
|
85
|
+
const segmentLength = Math.hypot(p1.x - p2.x, p1.y - p2.y);
|
|
86
|
+
if (segmentLength === 0) continue;
|
|
87
|
+
const centerX = (p1.x + p2.x) / 2 + origin.x;
|
|
88
|
+
const centerY = (p1.y + p2.y) / 2 + origin.y;
|
|
89
|
+
const rotationDeg = Math.atan2(p2.y - p1.y, p2.x - p1.x) * 180 / Math.PI;
|
|
90
|
+
const w2 = segmentLength / 2;
|
|
91
|
+
const h2 = traceWidth / 2;
|
|
92
|
+
const angleRad = rotationDeg * Math.PI / 180;
|
|
93
|
+
const cosAngle = Math.cos(angleRad);
|
|
94
|
+
const sinAngle = Math.sin(angleRad);
|
|
95
|
+
const corners = [
|
|
96
|
+
{ x: -w2, y: -h2 },
|
|
97
|
+
{ x: w2, y: -h2 },
|
|
98
|
+
{ x: w2, y: h2 },
|
|
99
|
+
{ x: -w2, y: h2 }
|
|
100
|
+
];
|
|
101
|
+
const rotatedCorners = corners.map((p) => ({
|
|
102
|
+
x: centerX + p.x * cosAngle - p.y * sinAngle,
|
|
103
|
+
y: centerY + p.x * sinAngle + p.y * cosAngle
|
|
104
|
+
}));
|
|
105
|
+
polygons.push(
|
|
106
|
+
new Flatten2.Polygon(rotatedCorners.map((p) => Flatten2.point(p.x, p.y)))
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
if (polygons.length === 0) return;
|
|
110
|
+
let tracePolygon = polygons[0];
|
|
111
|
+
for (let i = 1; i < polygons.length; i++) {
|
|
112
|
+
const poly = polygons[i];
|
|
113
|
+
if (poly) {
|
|
114
|
+
tracePolygon = BooleanOperations.unify(tracePolygon, poly);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
netGeoms.get(netId)?.push(tracePolygon);
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
// lib/index.ts
|
|
121
|
+
import { getFullConnectivityMapFromCircuitJson } from "circuit-json-to-connectivity-map";
|
|
122
|
+
import { Polygon, Box as Box2, BooleanOperations as BooleanOperations2 } from "@flatten-js/core";
|
|
123
|
+
|
|
124
|
+
// lib/polygon-to-shape-path.ts
|
|
125
|
+
function polygonToShapePathData(polygon) {
|
|
126
|
+
const verts = [];
|
|
127
|
+
const prims = [];
|
|
128
|
+
const svgString = polygon.svg();
|
|
129
|
+
const dAttributeMatch = svgString.match(/\bd="([^"]+)"/);
|
|
130
|
+
if (!dAttributeMatch || !dAttributeMatch[1]) {
|
|
131
|
+
return { verts, prims };
|
|
132
|
+
}
|
|
133
|
+
const pathData = dAttributeMatch[1].trim();
|
|
134
|
+
const commands = pathData.match(/[MLz][^MLz]*/g) || [];
|
|
135
|
+
for (const command of commands) {
|
|
136
|
+
const type = command[0];
|
|
137
|
+
const coords = command.slice(1).trim();
|
|
138
|
+
if (type === "M" || type === "L") {
|
|
139
|
+
const parts = coords.split(",");
|
|
140
|
+
const x = Number(parts[0]);
|
|
141
|
+
const y = Number(parts[1]);
|
|
142
|
+
if (!Number.isNaN(x) && !Number.isNaN(y)) {
|
|
143
|
+
verts.push({ x, y });
|
|
144
|
+
prims.push({ type: 0 });
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return { verts, prims };
|
|
149
|
+
}
|
|
150
|
+
|
|
186
151
|
// lib/index.ts
|
|
187
152
|
var convertCircuitJsonToLbrn = (circuitJson, options = {}) => {
|
|
188
153
|
const db = cju(circuitJson);
|
|
@@ -197,16 +162,51 @@ var convertCircuitJsonToLbrn = (circuitJson, options = {}) => {
|
|
|
197
162
|
speed: 100
|
|
198
163
|
});
|
|
199
164
|
project.children.push(copperCutSetting);
|
|
165
|
+
const connMap = getFullConnectivityMapFromCircuitJson(circuitJson);
|
|
200
166
|
const ctx = {
|
|
201
167
|
db,
|
|
202
168
|
project,
|
|
203
|
-
copperCutSetting
|
|
169
|
+
copperCutSetting,
|
|
170
|
+
connMap,
|
|
171
|
+
netGeoms: /* @__PURE__ */ new Map(),
|
|
172
|
+
origin: options.origin ?? { x: 0, y: 0 }
|
|
204
173
|
};
|
|
174
|
+
for (const net of Object.keys(connMap.netMap)) {
|
|
175
|
+
ctx.netGeoms.set(net, []);
|
|
176
|
+
}
|
|
205
177
|
for (const smtpad of db.pcb_smtpad.list()) {
|
|
206
178
|
addSmtPad(smtpad, ctx);
|
|
207
179
|
}
|
|
208
|
-
for (const
|
|
209
|
-
|
|
180
|
+
for (const trace of db.pcb_trace.list()) {
|
|
181
|
+
addPcbTrace(trace, ctx);
|
|
182
|
+
}
|
|
183
|
+
for (const net of Object.keys(connMap.netMap)) {
|
|
184
|
+
const netGeoms = ctx.netGeoms.get(net);
|
|
185
|
+
if (netGeoms.length === 0) {
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
let union = netGeoms[0];
|
|
189
|
+
if (union instanceof Box2) {
|
|
190
|
+
union = new Polygon(union);
|
|
191
|
+
}
|
|
192
|
+
for (const geom of netGeoms.slice(1)) {
|
|
193
|
+
if (geom instanceof Polygon) {
|
|
194
|
+
union = BooleanOperations2.unify(union, geom);
|
|
195
|
+
} else if (geom instanceof Box2) {
|
|
196
|
+
union = BooleanOperations2.unify(union, new Polygon(geom));
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
for (const island of union.splitToIslands()) {
|
|
200
|
+
const { verts, prims } = polygonToShapePathData(island);
|
|
201
|
+
project.children.push(
|
|
202
|
+
new ShapePath3({
|
|
203
|
+
cutIndex: copperCutSetting.index,
|
|
204
|
+
verts,
|
|
205
|
+
prims,
|
|
206
|
+
isClosed: false
|
|
207
|
+
})
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
210
|
}
|
|
211
211
|
return project;
|
|
212
212
|
};
|
package/lib/ConvertContext.ts
CHANGED
|
@@ -1,9 +1,19 @@
|
|
|
1
1
|
import type { CircuitJsonUtilObjects } from "@tscircuit/circuit-json-util"
|
|
2
2
|
import type { CutSetting, LightBurnProject } from "lbrnts"
|
|
3
|
+
import type { AnyShape, Box, Polygon } from "@flatten-js/core"
|
|
4
|
+
import type { ConnectivityMap } from "circuit-json-to-connectivity-map"
|
|
5
|
+
|
|
6
|
+
export type ConnectivityMapKey = string
|
|
3
7
|
|
|
4
8
|
export interface ConvertContext {
|
|
5
9
|
db: CircuitJsonUtilObjects
|
|
6
10
|
project: LightBurnProject
|
|
7
11
|
|
|
8
12
|
copperCutSetting: CutSetting
|
|
13
|
+
|
|
14
|
+
connMap: ConnectivityMap
|
|
15
|
+
|
|
16
|
+
netGeoms: Map<ConnectivityMapKey, Array<Polygon | Box>>
|
|
17
|
+
|
|
18
|
+
origin: { x: number; y: number }
|
|
9
19
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import Flatten from "@flatten-js/core"
|
|
2
|
+
|
|
3
|
+
export const circleToPolygon = (
|
|
4
|
+
circle: Flatten.Circle,
|
|
5
|
+
segments = 32,
|
|
6
|
+
): Flatten.Polygon => {
|
|
7
|
+
const points: Flatten.Point[] = []
|
|
8
|
+
for (let i = 0; i < segments; i++) {
|
|
9
|
+
const angle = (i * 2 * Math.PI) / segments
|
|
10
|
+
const x = circle.center.x + circle.r * Math.cos(angle)
|
|
11
|
+
const y = circle.center.y + circle.r * Math.sin(angle)
|
|
12
|
+
points.push(Flatten.point(x, y))
|
|
13
|
+
}
|
|
14
|
+
return new Flatten.Polygon(points)
|
|
15
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import type { PcbTrace } from "circuit-json"
|
|
2
|
+
import type { ConvertContext } from "../../ConvertContext"
|
|
3
|
+
import Flatten, { BooleanOperations } from "@flatten-js/core"
|
|
4
|
+
import { circleToPolygon } from "./circle-to-polygon"
|
|
5
|
+
|
|
6
|
+
export const addPcbTrace = (trace: PcbTrace, ctx: ConvertContext) => {
|
|
7
|
+
const { netGeoms, connMap, origin } = ctx
|
|
8
|
+
|
|
9
|
+
const netId = connMap.getNetConnectedToId(
|
|
10
|
+
trace.source_trace_id ?? trace.pcb_trace_id,
|
|
11
|
+
)!
|
|
12
|
+
|
|
13
|
+
if (!netId) {
|
|
14
|
+
console.warn(`Trace ${trace.pcb_trace_id} is not connected to any net`)
|
|
15
|
+
return
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (!trace.route || trace.route.length < 2) {
|
|
19
|
+
console.warn(`Trace ${trace.pcb_trace_id} has insufficient route points`)
|
|
20
|
+
return
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const { route } = trace
|
|
24
|
+
|
|
25
|
+
const traceWidth =
|
|
26
|
+
route.find((point) => point.route_type === "wire")?.width ?? 0.15
|
|
27
|
+
|
|
28
|
+
const polygons: Flatten.Polygon[] = []
|
|
29
|
+
|
|
30
|
+
// Add circles for each vertex
|
|
31
|
+
for (const routePoint of route) {
|
|
32
|
+
const circle = new Flatten.Circle(
|
|
33
|
+
new Flatten.Point(routePoint.x + origin.x, routePoint.y + origin.y),
|
|
34
|
+
traceWidth / 2,
|
|
35
|
+
)
|
|
36
|
+
polygons.push(circleToPolygon(circle))
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Add rectangles for each segment
|
|
40
|
+
for (let i = 0; i < route.length - 1; i++) {
|
|
41
|
+
const p1 = route[i]
|
|
42
|
+
const p2 = route[i + 1]
|
|
43
|
+
|
|
44
|
+
if (!p1 || !p2) continue
|
|
45
|
+
|
|
46
|
+
const segmentLength = Math.hypot(p1.x - p2.x, p1.y - p2.y)
|
|
47
|
+
if (segmentLength === 0) continue
|
|
48
|
+
|
|
49
|
+
const centerX = (p1.x + p2.x) / 2 + origin.x
|
|
50
|
+
const centerY = (p1.y + p2.y) / 2 + origin.y
|
|
51
|
+
const rotationDeg = (Math.atan2(p2.y - p1.y, p2.x - p1.x) * 180) / Math.PI
|
|
52
|
+
|
|
53
|
+
const w2 = segmentLength / 2
|
|
54
|
+
const h2 = traceWidth / 2
|
|
55
|
+
|
|
56
|
+
const angleRad = (rotationDeg * Math.PI) / 180
|
|
57
|
+
const cosAngle = Math.cos(angleRad)
|
|
58
|
+
const sinAngle = Math.sin(angleRad)
|
|
59
|
+
|
|
60
|
+
const corners = [
|
|
61
|
+
{ x: -w2, y: -h2 },
|
|
62
|
+
{ x: w2, y: -h2 },
|
|
63
|
+
{ x: w2, y: h2 },
|
|
64
|
+
{ x: -w2, y: h2 },
|
|
65
|
+
]
|
|
66
|
+
|
|
67
|
+
const rotatedCorners = corners.map((p) => ({
|
|
68
|
+
x: centerX + p.x * cosAngle - p.y * sinAngle,
|
|
69
|
+
y: centerY + p.x * sinAngle + p.y * cosAngle,
|
|
70
|
+
}))
|
|
71
|
+
|
|
72
|
+
polygons.push(
|
|
73
|
+
new Flatten.Polygon(rotatedCorners.map((p) => Flatten.point(p.x, p.y))),
|
|
74
|
+
)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Union all polygons together to create the final trace polygon
|
|
78
|
+
if (polygons.length === 0) return
|
|
79
|
+
|
|
80
|
+
let tracePolygon = polygons[0]!
|
|
81
|
+
|
|
82
|
+
for (let i = 1; i < polygons.length; i++) {
|
|
83
|
+
const poly = polygons[i]
|
|
84
|
+
if (poly) {
|
|
85
|
+
tracePolygon = BooleanOperations.unify(tracePolygon, poly)
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
netGeoms.get(netId)?.push(tracePolygon)
|
|
90
|
+
}
|