circuit-json-to-tscircuit 0.0.57 → 0.0.59
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/{chunk-U24G34W6.js → chunk-QMIS5SJZ.js} +388 -55
- package/dist/cli/main.js +2 -2
- package/dist/lib/index.js +1 -1
- package/lib/flatten-brep-ring.ts +46 -0
- package/lib/generate-copper-pours-tsx.ts +62 -0
- package/lib/generate-footprint-tsx/convert-silkscreen-graphics.ts +18 -0
- package/lib/generate-footprint-tsx/create-silkscreen-graphic-image.ts +91 -0
- package/lib/generate-footprint-tsx/get-brep-rings-bounds.ts +77 -0
- package/lib/generate-footprint-tsx.tsx +2 -0
- package/lib/get-board-using-template.ts +8 -1
- package/lib/get-brep-copper-pour-outline.ts +120 -0
- package/lib/get-bulge-arc.ts +41 -0
- package/lib/get-copper-pour-outline.ts +25 -0
- package/package.json +1 -1
- package/tests/test25-support-copper-pours.test.tsx +105 -0
- package/tests/test26-support-silkscreen-graphics.test.tsx +91 -0
- package/tests/test27-support-netless-copper-pours.test.tsx +80 -0
- package/tests/test28-support-curved-copper-pours.test.tsx +77 -0
- package/tests/ti-evms/__snapshots__/ti-drv8307evm-roundtrip-pcb-comparison.snap.svg +1 -1
- package/tests/ti-evms/__snapshots__/ti-lm251772evm-pd-roundtrip-pcb-comparison.snap.svg +1 -1
- package/tests/ti-evms/__snapshots__/ti-lm5155evm-fly-roundtrip-pcb-comparison.snap.svg +1 -1
- package/tests/ti-evms/__snapshots__/ti-lmg342x-bb-evm-roundtrip-pcb-comparison.snap.svg +1 -1
- package/tests/ti-evms/ti-drv8307evm-roundtrip.test.tsx +73 -0
- package/tests/ti-evms/ti-lm251772evm-pd-roundtrip.test.tsx +102 -0
- package/tests/ti-evms/ti-lm5155evm-fly-roundtrip.test.tsx +197 -0
- package/tests/ti-evms/ti-lmg342x-bb-evm-roundtrip.test.tsx +139 -0
|
@@ -1,16 +1,224 @@
|
|
|
1
1
|
// lib/get-board-using-template.ts
|
|
2
2
|
import { mmStr as mmStr8 } from "@tscircuit/mm";
|
|
3
|
-
import { su as
|
|
3
|
+
import { su as su16 } from "@tscircuit/soup-util";
|
|
4
4
|
|
|
5
|
-
// lib/generate-
|
|
5
|
+
// lib/generate-copper-pours-tsx.ts
|
|
6
6
|
import { su } from "@tscircuit/soup-util";
|
|
7
7
|
|
|
8
|
+
// lib/get-bulge-arc.ts
|
|
9
|
+
var getBulgeArc = ({
|
|
10
|
+
end,
|
|
11
|
+
start
|
|
12
|
+
}) => {
|
|
13
|
+
const bulge = start.bulge ?? 0;
|
|
14
|
+
const deltaX = end.x - start.x;
|
|
15
|
+
const deltaY = end.y - start.y;
|
|
16
|
+
const chordLength = Math.hypot(deltaX, deltaY);
|
|
17
|
+
if (Math.abs(bulge) < 1e-12 || chordLength < 1e-12) return null;
|
|
18
|
+
const centerOffset = chordLength * (1 - bulge * bulge) / (4 * bulge);
|
|
19
|
+
const midpoint = {
|
|
20
|
+
x: (start.x + end.x) / 2,
|
|
21
|
+
y: (start.y + end.y) / 2
|
|
22
|
+
};
|
|
23
|
+
return {
|
|
24
|
+
center: {
|
|
25
|
+
x: midpoint.x - deltaY / chordLength * centerOffset,
|
|
26
|
+
y: midpoint.y + deltaX / chordLength * centerOffset
|
|
27
|
+
},
|
|
28
|
+
radius: Math.abs(chordLength * (1 + bulge * bulge) / (4 * bulge)),
|
|
29
|
+
sweepAngle: 4 * Math.atan(bulge)
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
// lib/flatten-brep-ring.ts
|
|
34
|
+
var MAX_ANGLE_STEP = Math.PI / 18;
|
|
35
|
+
var flattenBrepRing = (vertices) => {
|
|
36
|
+
if (vertices.length < 2) return vertices.map(toPoint);
|
|
37
|
+
const points = [toPoint(vertices[0])];
|
|
38
|
+
for (let index = 0; index < vertices.length; index++) {
|
|
39
|
+
const start = vertices[index];
|
|
40
|
+
const end = vertices[(index + 1) % vertices.length];
|
|
41
|
+
const isClosingSegment = index === vertices.length - 1;
|
|
42
|
+
const arc = getBulgeArc({ end, start });
|
|
43
|
+
if (!arc) {
|
|
44
|
+
if (!isClosingSegment) points.push(toPoint(end));
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
const startAngle = Math.atan2(
|
|
48
|
+
start.y - arc.center.y,
|
|
49
|
+
start.x - arc.center.x
|
|
50
|
+
);
|
|
51
|
+
const stepCount = Math.max(
|
|
52
|
+
2,
|
|
53
|
+
Math.ceil(Math.abs(arc.sweepAngle) / MAX_ANGLE_STEP)
|
|
54
|
+
);
|
|
55
|
+
for (let step = 1; step <= stepCount; step++) {
|
|
56
|
+
if (isClosingSegment && step === stepCount) continue;
|
|
57
|
+
if (step === stepCount) {
|
|
58
|
+
points.push(toPoint(end));
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
const angle = startAngle + arc.sweepAngle * step / stepCount;
|
|
62
|
+
points.push({
|
|
63
|
+
x: arc.center.x + arc.radius * Math.cos(angle),
|
|
64
|
+
y: arc.center.y + arc.radius * Math.sin(angle)
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return points;
|
|
69
|
+
};
|
|
70
|
+
var toPoint = ({ x, y }) => ({ x, y });
|
|
71
|
+
|
|
72
|
+
// lib/get-brep-copper-pour-outline.ts
|
|
73
|
+
var getBrepCopperPourOutline = (brepShape) => {
|
|
74
|
+
let outline = withWinding(
|
|
75
|
+
flattenBrepRing(brepShape.outer_ring.vertices),
|
|
76
|
+
"ccw"
|
|
77
|
+
);
|
|
78
|
+
const holes = (brepShape.inner_rings ?? []).map((ring) => withWinding(flattenBrepRing(ring.vertices), "cw")).filter((ring) => ring.length >= 3).sort((first, second) => getLeftmostX(first) - getLeftmostX(second));
|
|
79
|
+
for (const hole of holes) {
|
|
80
|
+
const bridgedOutline = bridgeHoleIntoOutline({ hole, outline });
|
|
81
|
+
if (!bridgedOutline) return void 0;
|
|
82
|
+
outline = bridgedOutline;
|
|
83
|
+
}
|
|
84
|
+
return outline;
|
|
85
|
+
};
|
|
86
|
+
var bridgeHoleIntoOutline = ({
|
|
87
|
+
hole,
|
|
88
|
+
outline
|
|
89
|
+
}) => {
|
|
90
|
+
const holeStartIndex = getLeftmostPointIndex(hole);
|
|
91
|
+
const holeStart = hole[holeStartIndex];
|
|
92
|
+
const bridge = findLeftwardBridge({ holeStart, outline });
|
|
93
|
+
if (!bridge) return void 0;
|
|
94
|
+
const outlineWithBridge = [...outline];
|
|
95
|
+
let bridgeIndex = bridge.edgeStartIndex;
|
|
96
|
+
const edgeEndIndex = (bridge.edgeStartIndex + 1) % outline.length;
|
|
97
|
+
if (pointsEqual(bridge.point, outline[edgeEndIndex])) {
|
|
98
|
+
bridgeIndex = edgeEndIndex;
|
|
99
|
+
} else if (!pointsEqual(bridge.point, outline[bridge.edgeStartIndex])) {
|
|
100
|
+
bridgeIndex = bridge.edgeStartIndex + 1;
|
|
101
|
+
outlineWithBridge.splice(bridgeIndex, 0, bridge.point);
|
|
102
|
+
}
|
|
103
|
+
const orderedHole = [
|
|
104
|
+
...hole.slice(holeStartIndex),
|
|
105
|
+
...hole.slice(0, holeStartIndex)
|
|
106
|
+
];
|
|
107
|
+
return [
|
|
108
|
+
...outlineWithBridge.slice(0, bridgeIndex + 1),
|
|
109
|
+
...orderedHole,
|
|
110
|
+
holeStart,
|
|
111
|
+
bridge.point,
|
|
112
|
+
...outlineWithBridge.slice(bridgeIndex + 1)
|
|
113
|
+
];
|
|
114
|
+
};
|
|
115
|
+
var findLeftwardBridge = ({
|
|
116
|
+
holeStart,
|
|
117
|
+
outline
|
|
118
|
+
}) => {
|
|
119
|
+
let closestBridge;
|
|
120
|
+
for (let index = 0; index < outline.length; index++) {
|
|
121
|
+
const start = outline[index];
|
|
122
|
+
const end = outline[(index + 1) % outline.length];
|
|
123
|
+
if (start.y > holeStart.y === end.y > holeStart.y) continue;
|
|
124
|
+
const x = start.x + (holeStart.y - start.y) * (end.x - start.x) / (end.y - start.y);
|
|
125
|
+
if (x > holeStart.x) continue;
|
|
126
|
+
if (closestBridge && x <= closestBridge.point.x) continue;
|
|
127
|
+
closestBridge = {
|
|
128
|
+
edgeStartIndex: index,
|
|
129
|
+
point: { x, y: holeStart.y }
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
return closestBridge;
|
|
133
|
+
};
|
|
134
|
+
var withWinding = (ring, winding) => {
|
|
135
|
+
const isCounterclockwise = getSignedArea(ring) > 0;
|
|
136
|
+
const shouldReverse = winding === "ccw" && !isCounterclockwise || winding === "cw" && isCounterclockwise;
|
|
137
|
+
return shouldReverse ? [...ring].reverse() : [...ring];
|
|
138
|
+
};
|
|
139
|
+
var getSignedArea = (ring) => ring.reduce((area, point, index) => {
|
|
140
|
+
const nextPoint = ring[(index + 1) % ring.length];
|
|
141
|
+
return area + point.x * nextPoint.y - nextPoint.x * point.y;
|
|
142
|
+
}, 0) / 2;
|
|
143
|
+
var getLeftmostPointIndex = (ring) => ring.reduce(
|
|
144
|
+
(leftmostIndex, point, index) => point.x < ring[leftmostIndex].x ? index : leftmostIndex,
|
|
145
|
+
0
|
|
146
|
+
);
|
|
147
|
+
var getLeftmostX = (ring) => ring[getLeftmostPointIndex(ring)].x;
|
|
148
|
+
var pointsEqual = (first, second) => first.x === second.x && first.y === second.y;
|
|
149
|
+
|
|
150
|
+
// lib/get-copper-pour-outline.ts
|
|
151
|
+
function getCopperPourOutline(pour) {
|
|
152
|
+
if (pour.shape === "polygon") return pour.points;
|
|
153
|
+
if (pour.shape === "brep") {
|
|
154
|
+
return getBrepCopperPourOutline(pour.brep_shape);
|
|
155
|
+
}
|
|
156
|
+
const halfWidth = pour.width / 2;
|
|
157
|
+
const halfHeight = pour.height / 2;
|
|
158
|
+
const ccwRotationRadians = (pour.rotation ?? 0) * Math.PI / 180;
|
|
159
|
+
const cosine = Math.cos(ccwRotationRadians);
|
|
160
|
+
const sine = Math.sin(ccwRotationRadians);
|
|
161
|
+
return [
|
|
162
|
+
{ x: -halfWidth, y: -halfHeight },
|
|
163
|
+
{ x: halfWidth, y: -halfHeight },
|
|
164
|
+
{ x: halfWidth, y: halfHeight },
|
|
165
|
+
{ x: -halfWidth, y: halfHeight }
|
|
166
|
+
].map(({ x, y }) => ({
|
|
167
|
+
x: pour.center.x + x * cosine - y * sine,
|
|
168
|
+
y: pour.center.y + x * sine + y * cosine
|
|
169
|
+
}));
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// lib/generate-copper-pours-tsx.ts
|
|
173
|
+
function generateCopperPoursTsx(circuitJson) {
|
|
174
|
+
const sourceNetNameById = new Map(
|
|
175
|
+
su(circuitJson).source_net.list().map((sourceNet) => [sourceNet.source_net_id, sourceNet.name])
|
|
176
|
+
);
|
|
177
|
+
const reservedNetNames = new Set(sourceNetNameById.values());
|
|
178
|
+
const usedNetNames = /* @__PURE__ */ new Set();
|
|
179
|
+
const copperPours = su(circuitJson).pcb_copper_pour.list().flatMap((pour) => {
|
|
180
|
+
const resolvedSourceNetName = pour.source_net_id ? sourceNetNameById.get(pour.source_net_id) : void 0;
|
|
181
|
+
const sourceNetName = resolvedSourceNetName ?? getUnassignedCopperPourNetName({
|
|
182
|
+
pcbCopperPourId: pour.pcb_copper_pour_id,
|
|
183
|
+
reservedNetNames
|
|
184
|
+
});
|
|
185
|
+
const outline = getCopperPourOutline(pour);
|
|
186
|
+
if (!outline || outline.length < 3) return [];
|
|
187
|
+
usedNetNames.add(sourceNetName);
|
|
188
|
+
const netSelector = `net[name=${JSON.stringify(sourceNetName)}]`;
|
|
189
|
+
return [
|
|
190
|
+
`<copperpour layer={${JSON.stringify(pour.layer)}} connectsTo={${JSON.stringify(netSelector)}} outline={${JSON.stringify(outline)}} padMargin={0} traceMargin={0} clearance={0} boardEdgeMargin={0} cutoutMargin={0} useThermalReliefs={false} coveredWithSolderMask={${pour.covered_with_solder_mask ?? true}} />`
|
|
191
|
+
];
|
|
192
|
+
});
|
|
193
|
+
const nets = [...usedNetNames].map(
|
|
194
|
+
(sourceNetName) => `<net name={${JSON.stringify(sourceNetName)}} />`
|
|
195
|
+
);
|
|
196
|
+
return [...nets, ...copperPours];
|
|
197
|
+
}
|
|
198
|
+
var getUnassignedCopperPourNetName = ({
|
|
199
|
+
pcbCopperPourId,
|
|
200
|
+
reservedNetNames
|
|
201
|
+
}) => {
|
|
202
|
+
const baseName = `__circuit_json_unassigned_${pcbCopperPourId}`;
|
|
203
|
+
let name = baseName;
|
|
204
|
+
let suffix = 2;
|
|
205
|
+
while (reservedNetNames.has(name)) {
|
|
206
|
+
name = `${baseName}_${suffix}`;
|
|
207
|
+
suffix += 1;
|
|
208
|
+
}
|
|
209
|
+
reservedNetNames.add(name);
|
|
210
|
+
return name;
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
// lib/generate-footprint-tsx/convert-copper-text.ts
|
|
214
|
+
import { su as su2 } from "@tscircuit/soup-util";
|
|
215
|
+
|
|
8
216
|
// lib/generate-footprint-tsx/footprint-tsx-attribute-formatters/escape-jsx-text.ts
|
|
9
217
|
var escapeJsxText = (value) => String(value ?? "").replace(/"/g, '\\"');
|
|
10
218
|
|
|
11
219
|
// lib/generate-footprint-tsx/convert-copper-text.ts
|
|
12
220
|
var convertCopperText = (circuitJson) => {
|
|
13
|
-
const copperTexts =
|
|
221
|
+
const copperTexts = su2(circuitJson).pcb_copper_text.list();
|
|
14
222
|
const elementStrings = [];
|
|
15
223
|
for (const copperText of copperTexts) {
|
|
16
224
|
const anchorPosition = copperText.anchor_position ?? { x: 0, y: 0 };
|
|
@@ -44,7 +252,7 @@ var convertCopperText = (circuitJson) => {
|
|
|
44
252
|
};
|
|
45
253
|
|
|
46
254
|
// lib/generate-footprint-tsx/convert-courtyard.ts
|
|
47
|
-
import { su as
|
|
255
|
+
import { su as su3 } from "@tscircuit/soup-util";
|
|
48
256
|
|
|
49
257
|
// lib/generate-footprint-tsx/footprint-tsx-attribute-formatters/format-pcb-rotation-attr.ts
|
|
50
258
|
var formatPcbRotationAttr = (rotation, attrName = "pcbRotation") => {
|
|
@@ -55,9 +263,9 @@ var formatPcbRotationAttr = (rotation, attrName = "pcbRotation") => {
|
|
|
55
263
|
|
|
56
264
|
// lib/generate-footprint-tsx/convert-courtyard.ts
|
|
57
265
|
var convertCourtyard = (circuitJson) => {
|
|
58
|
-
const courtyardOutlines =
|
|
59
|
-
const courtyardRects =
|
|
60
|
-
const courtyardCircles =
|
|
266
|
+
const courtyardOutlines = su3(circuitJson).pcb_courtyard_outline.list();
|
|
267
|
+
const courtyardRects = su3(circuitJson).pcb_courtyard_rect.list();
|
|
268
|
+
const courtyardCircles = su3(circuitJson).pcb_courtyard_circle.list();
|
|
61
269
|
const elementStrings = [];
|
|
62
270
|
for (const courtyardOutline of courtyardOutlines) {
|
|
63
271
|
const attrs = [
|
|
@@ -98,7 +306,7 @@ var convertCourtyard = (circuitJson) => {
|
|
|
98
306
|
};
|
|
99
307
|
|
|
100
308
|
// lib/generate-footprint-tsx/convert-cutouts.ts
|
|
101
|
-
import { su as
|
|
309
|
+
import { su as su4 } from "@tscircuit/soup-util";
|
|
102
310
|
|
|
103
311
|
// lib/generate-footprint-tsx/footprint-tsx-attribute-formatters/format-mm.ts
|
|
104
312
|
import { mmStr } from "@tscircuit/mm";
|
|
@@ -106,7 +314,7 @@ var formatMm = (value) => mmStr(value ?? 0);
|
|
|
106
314
|
|
|
107
315
|
// lib/generate-footprint-tsx/convert-cutouts.ts
|
|
108
316
|
var convertCutouts = (circuitJson) => {
|
|
109
|
-
const pcbCutouts =
|
|
317
|
+
const pcbCutouts = su4(circuitJson).pcb_cutout.list();
|
|
110
318
|
const elementStrings = [];
|
|
111
319
|
for (const cutout of pcbCutouts) {
|
|
112
320
|
if (cutout.shape === "rect") {
|
|
@@ -130,12 +338,12 @@ var convertCutouts = (circuitJson) => {
|
|
|
130
338
|
};
|
|
131
339
|
|
|
132
340
|
// lib/generate-footprint-tsx/convert-fabrication.ts
|
|
133
|
-
import { su as
|
|
341
|
+
import { su as su5 } from "@tscircuit/soup-util";
|
|
134
342
|
var convertFabrication = (circuitJson) => {
|
|
135
|
-
const fabricationNotePaths =
|
|
136
|
-
const fabricationNoteTexts =
|
|
137
|
-
const fabricationNoteRects =
|
|
138
|
-
const fabricationNoteDimensions =
|
|
343
|
+
const fabricationNotePaths = su5(circuitJson).pcb_fabrication_note_path.list();
|
|
344
|
+
const fabricationNoteTexts = su5(circuitJson).pcb_fabrication_note_text.list();
|
|
345
|
+
const fabricationNoteRects = su5(circuitJson).pcb_fabrication_note_rect.list();
|
|
346
|
+
const fabricationNoteDimensions = su5(circuitJson).pcb_fabrication_note_dimension.list();
|
|
139
347
|
const elementStrings = [];
|
|
140
348
|
for (const fabPath of fabricationNotePaths) {
|
|
141
349
|
const attrs = [`route={${JSON.stringify(fabPath.route)}}`];
|
|
@@ -207,10 +415,10 @@ var convertFabrication = (circuitJson) => {
|
|
|
207
415
|
}
|
|
208
416
|
for (const fabDimension of fabricationNoteDimensions) {
|
|
209
417
|
const fromPoint = fabDimension.from ?? { x: 0, y: 0 };
|
|
210
|
-
const
|
|
418
|
+
const toPoint2 = fabDimension.to ?? { x: 0, y: 0 };
|
|
211
419
|
const attrs = [
|
|
212
420
|
`from={{ x: ${fromPoint.x}, y: ${fromPoint.y} }}`,
|
|
213
|
-
`to={{ x: ${
|
|
421
|
+
`to={{ x: ${toPoint2.x}, y: ${toPoint2.y} }}`
|
|
214
422
|
];
|
|
215
423
|
if (fabDimension.text !== void 0) {
|
|
216
424
|
attrs.push(`text="${escapeJsxText(fabDimension.text)}"`);
|
|
@@ -241,10 +449,10 @@ var convertFabrication = (circuitJson) => {
|
|
|
241
449
|
};
|
|
242
450
|
|
|
243
451
|
// lib/generate-footprint-tsx/convert-holes.ts
|
|
244
|
-
import { su as
|
|
452
|
+
import { su as su6 } from "@tscircuit/soup-util";
|
|
245
453
|
import { mmStr as mmStr2 } from "@tscircuit/mm";
|
|
246
454
|
var convertHoles = (circuitJson) => {
|
|
247
|
-
const holes =
|
|
455
|
+
const holes = su6(circuitJson).pcb_hole.list();
|
|
248
456
|
const elementStrings = [];
|
|
249
457
|
for (const hole of holes) {
|
|
250
458
|
if (hole.hole_shape === "circle") {
|
|
@@ -269,9 +477,9 @@ var convertHoles = (circuitJson) => {
|
|
|
269
477
|
};
|
|
270
478
|
|
|
271
479
|
// lib/generate-footprint-tsx/convert-keepouts.ts
|
|
272
|
-
import { su as
|
|
480
|
+
import { su as su7 } from "@tscircuit/soup-util";
|
|
273
481
|
var convertKeepouts = (circuitJson) => {
|
|
274
|
-
const pcbKeepouts =
|
|
482
|
+
const pcbKeepouts = su7(circuitJson).pcb_keepout.list();
|
|
275
483
|
const elementStrings = [];
|
|
276
484
|
for (const keepout of pcbKeepouts) {
|
|
277
485
|
if (keepout.shape === "rect") {
|
|
@@ -290,13 +498,13 @@ var convertKeepouts = (circuitJson) => {
|
|
|
290
498
|
};
|
|
291
499
|
|
|
292
500
|
// lib/generate-footprint-tsx/convert-notes.ts
|
|
293
|
-
import { su as
|
|
501
|
+
import { su as su8 } from "@tscircuit/soup-util";
|
|
294
502
|
var convertNotes = (circuitJson) => {
|
|
295
|
-
const noteTexts =
|
|
296
|
-
const noteRects =
|
|
297
|
-
const notePaths =
|
|
298
|
-
const noteLines =
|
|
299
|
-
const noteDimensions =
|
|
503
|
+
const noteTexts = su8(circuitJson).pcb_note_text.list();
|
|
504
|
+
const noteRects = su8(circuitJson).pcb_note_rect.list();
|
|
505
|
+
const notePaths = su8(circuitJson).pcb_note_path.list();
|
|
506
|
+
const noteLines = su8(circuitJson).pcb_note_line.list();
|
|
507
|
+
const noteDimensions = su8(circuitJson).pcb_note_dimension.list();
|
|
300
508
|
const elementStrings = [];
|
|
301
509
|
for (const noteText of noteTexts) {
|
|
302
510
|
const anchorPosition = noteText.anchor_position ?? { x: 0, y: 0 };
|
|
@@ -358,10 +566,10 @@ var convertNotes = (circuitJson) => {
|
|
|
358
566
|
}
|
|
359
567
|
for (const noteDimension of noteDimensions) {
|
|
360
568
|
const fromPoint = noteDimension.from ?? { x: 0, y: 0 };
|
|
361
|
-
const
|
|
569
|
+
const toPoint2 = noteDimension.to ?? { x: 0, y: 0 };
|
|
362
570
|
const attrs = [
|
|
363
571
|
`from={{ x: ${fromPoint.x}, y: ${fromPoint.y} }}`,
|
|
364
|
-
`to={{ x: ${
|
|
572
|
+
`to={{ x: ${toPoint2.x}, y: ${toPoint2.y} }}`,
|
|
365
573
|
`font="${noteDimension.font ?? "tscircuit2024"}"`,
|
|
366
574
|
`fontSize={${noteDimension.font_size ?? 0}}`
|
|
367
575
|
];
|
|
@@ -386,11 +594,11 @@ var convertNotes = (circuitJson) => {
|
|
|
386
594
|
};
|
|
387
595
|
|
|
388
596
|
// lib/generate-footprint-tsx/convert-pcb-traces.ts
|
|
389
|
-
import { su as
|
|
390
|
-
var convertPcbTraces = (circuitJson) =>
|
|
597
|
+
import { su as su9 } from "@tscircuit/soup-util";
|
|
598
|
+
var convertPcbTraces = (circuitJson) => su9(circuitJson).pcb_trace.list().map((trace) => `<pcbtrace route={${JSON.stringify(trace.route)}} />`);
|
|
391
599
|
|
|
392
600
|
// lib/generate-footprint-tsx/convert-plated-holes.ts
|
|
393
|
-
import { su as
|
|
601
|
+
import { su as su10 } from "@tscircuit/soup-util";
|
|
394
602
|
import { mmStr as mmStr4 } from "@tscircuit/mm";
|
|
395
603
|
|
|
396
604
|
// lib/generate-footprint-tsx/footprint-tsx-attribute-formatters/format-optional-mm-attr.ts
|
|
@@ -411,7 +619,7 @@ var formatSolderMaskAttrs = (platedHole) => {
|
|
|
411
619
|
|
|
412
620
|
// lib/generate-footprint-tsx/convert-plated-holes.ts
|
|
413
621
|
var convertPlatedHoles = (circuitJson) => {
|
|
414
|
-
const platedHoles =
|
|
622
|
+
const platedHoles = su10(circuitJson).pcb_plated_hole.list();
|
|
415
623
|
const elementStrings = [];
|
|
416
624
|
for (const platedHole of platedHoles) {
|
|
417
625
|
if (platedHole.shape === "oval" || platedHole.shape === "pill") {
|
|
@@ -443,12 +651,12 @@ var convertPlatedHoles = (circuitJson) => {
|
|
|
443
651
|
};
|
|
444
652
|
|
|
445
653
|
// lib/generate-footprint-tsx/convert-silkscreen.ts
|
|
446
|
-
import { su as
|
|
654
|
+
import { su as su11 } from "@tscircuit/soup-util";
|
|
447
655
|
var convertSilkscreen = (circuitJson) => {
|
|
448
|
-
const silkscreenLines =
|
|
449
|
-
const silkscreenPaths =
|
|
450
|
-
const silkscreenRects =
|
|
451
|
-
const silkscreenCircles =
|
|
656
|
+
const silkscreenLines = su11(circuitJson).pcb_silkscreen_line.list();
|
|
657
|
+
const silkscreenPaths = su11(circuitJson).pcb_silkscreen_path.list();
|
|
658
|
+
const silkscreenRects = su11(circuitJson).pcb_silkscreen_rect.list();
|
|
659
|
+
const silkscreenCircles = su11(circuitJson).pcb_silkscreen_circle.list();
|
|
452
660
|
const elementStrings = [];
|
|
453
661
|
for (const silkscreenPath of silkscreenPaths) {
|
|
454
662
|
const attrs = [`route={${JSON.stringify(silkscreenPath.route)}}`];
|
|
@@ -518,11 +726,131 @@ var convertSilkscreen = (circuitJson) => {
|
|
|
518
726
|
return elementStrings;
|
|
519
727
|
};
|
|
520
728
|
|
|
729
|
+
// lib/generate-footprint-tsx/convert-silkscreen-graphics.ts
|
|
730
|
+
import { su as su12 } from "@tscircuit/soup-util";
|
|
731
|
+
|
|
732
|
+
// lib/generate-footprint-tsx/get-brep-rings-bounds.ts
|
|
733
|
+
var getBrepRingsBounds = (rings) => {
|
|
734
|
+
const boundaryPoints = rings.flatMap(getRingBoundaryPoints);
|
|
735
|
+
if (boundaryPoints.length === 0) return null;
|
|
736
|
+
const xs = boundaryPoints.map((point) => point.x);
|
|
737
|
+
const ys = boundaryPoints.map((point) => point.y);
|
|
738
|
+
return {
|
|
739
|
+
maxX: Math.max(...xs),
|
|
740
|
+
maxY: Math.max(...ys),
|
|
741
|
+
minX: Math.min(...xs),
|
|
742
|
+
minY: Math.min(...ys)
|
|
743
|
+
};
|
|
744
|
+
};
|
|
745
|
+
var getRingBoundaryPoints = (ring) => {
|
|
746
|
+
const boundaryPoints = [...ring.vertices];
|
|
747
|
+
for (let index = 0; index < ring.vertices.length; index++) {
|
|
748
|
+
const start = ring.vertices[index];
|
|
749
|
+
const end = ring.vertices[(index + 1) % ring.vertices.length];
|
|
750
|
+
const arc = getBulgeArc({ end, start });
|
|
751
|
+
if (!arc) continue;
|
|
752
|
+
const startAngle = Math.atan2(
|
|
753
|
+
start.y - arc.center.y,
|
|
754
|
+
start.x - arc.center.x
|
|
755
|
+
);
|
|
756
|
+
for (const angle of [0, Math.PI / 2, Math.PI, 3 * Math.PI / 2]) {
|
|
757
|
+
if (!isAngleOnArc({ angle, startAngle, sweepAngle: arc.sweepAngle })) {
|
|
758
|
+
continue;
|
|
759
|
+
}
|
|
760
|
+
boundaryPoints.push({
|
|
761
|
+
x: arc.center.x + arc.radius * Math.cos(angle),
|
|
762
|
+
y: arc.center.y + arc.radius * Math.sin(angle)
|
|
763
|
+
});
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
return boundaryPoints;
|
|
767
|
+
};
|
|
768
|
+
var isAngleOnArc = ({
|
|
769
|
+
angle,
|
|
770
|
+
startAngle,
|
|
771
|
+
sweepAngle
|
|
772
|
+
}) => {
|
|
773
|
+
const angularDistance = sweepAngle >= 0 ? normalizeAngle(angle - startAngle) : normalizeAngle(startAngle - angle);
|
|
774
|
+
return angularDistance <= Math.abs(sweepAngle) + 1e-12;
|
|
775
|
+
};
|
|
776
|
+
var normalizeAngle = (angle) => {
|
|
777
|
+
const fullTurn = 2 * Math.PI;
|
|
778
|
+
return (angle % fullTurn + fullTurn) % fullTurn;
|
|
779
|
+
};
|
|
780
|
+
|
|
781
|
+
// lib/generate-footprint-tsx/create-silkscreen-graphic-image.ts
|
|
782
|
+
var createSilkscreenGraphicImage = (graphic) => {
|
|
783
|
+
const rings = [
|
|
784
|
+
graphic.brep_shape.outer_ring,
|
|
785
|
+
...(graphic.brep_shape.inner_rings ?? []).filter(isRenderableRing)
|
|
786
|
+
];
|
|
787
|
+
if (!isRenderableRing(graphic.brep_shape.outer_ring)) return null;
|
|
788
|
+
const bounds = getBrepRingsBounds(rings);
|
|
789
|
+
if (!bounds) return null;
|
|
790
|
+
const { maxX, maxY, minX, minY } = bounds;
|
|
791
|
+
const width = maxX - minX;
|
|
792
|
+
const height = maxY - minY;
|
|
793
|
+
if (width <= 0 || height <= 0) return null;
|
|
794
|
+
const pathData = rings.map((ring) => createRingPath({ maxY, minX, ring })).join(" ");
|
|
795
|
+
const svg = [
|
|
796
|
+
`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${width} ${height}">`,
|
|
797
|
+
`<path d="${pathData}" fill="black" fill-rule="evenodd"/>`,
|
|
798
|
+
"</svg>"
|
|
799
|
+
].join("");
|
|
800
|
+
return {
|
|
801
|
+
center: { x: (minX + maxX) / 2, y: (minY + maxY) / 2 },
|
|
802
|
+
dataUrl: `data:image/svg+xml;base64,${btoa(svg)}`,
|
|
803
|
+
height,
|
|
804
|
+
width
|
|
805
|
+
};
|
|
806
|
+
};
|
|
807
|
+
var createRingPath = ({
|
|
808
|
+
maxY,
|
|
809
|
+
minX,
|
|
810
|
+
ring
|
|
811
|
+
}) => {
|
|
812
|
+
const firstVertex = ring.vertices[0];
|
|
813
|
+
const commands = [
|
|
814
|
+
`M${formatCoordinate(firstVertex.x - minX)} ${formatCoordinate(maxY - firstVertex.y)}`
|
|
815
|
+
];
|
|
816
|
+
for (let index = 0; index < ring.vertices.length; index++) {
|
|
817
|
+
const start = ring.vertices[index];
|
|
818
|
+
const end = ring.vertices[(index + 1) % ring.vertices.length];
|
|
819
|
+
const arc = getBulgeArc({ end, start });
|
|
820
|
+
const x = formatCoordinate(end.x - minX);
|
|
821
|
+
const y = formatCoordinate(maxY - end.y);
|
|
822
|
+
if (arc) {
|
|
823
|
+
const radius = formatCoordinate(arc.radius);
|
|
824
|
+
const largeArcFlag = Math.abs(start.bulge ?? 0) > 1 ? 1 : 0;
|
|
825
|
+
const sweepFlag = (start.bulge ?? 0) < 0 ? 1 : 0;
|
|
826
|
+
commands.push(
|
|
827
|
+
`A${radius} ${radius} 0 ${largeArcFlag} ${sweepFlag} ${x} ${y}`
|
|
828
|
+
);
|
|
829
|
+
} else if (index < ring.vertices.length - 1) {
|
|
830
|
+
commands.push(`L${x} ${y}`);
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
return commands.concat("Z").join(" ");
|
|
834
|
+
};
|
|
835
|
+
var isRenderableRing = (ring) => ring.vertices.length >= 3 || ring.vertices.length >= 2 && ring.vertices.some((vertex) => Math.abs(vertex.bulge ?? 0) > 1e-12);
|
|
836
|
+
var formatCoordinate = (value) => Number(value.toFixed(6)).toString();
|
|
837
|
+
|
|
838
|
+
// lib/generate-footprint-tsx/convert-silkscreen-graphics.ts
|
|
839
|
+
var convertSilkscreenGraphics = (circuitJson) => {
|
|
840
|
+
return su12(circuitJson).pcb_silkscreen_graphic.list().flatMap((graphic) => {
|
|
841
|
+
const image = createSilkscreenGraphicImage(graphic);
|
|
842
|
+
if (!image) return [];
|
|
843
|
+
return [
|
|
844
|
+
`<silkscreengraphic imageUrl=${JSON.stringify(image.dataUrl)} pcbX={${image.center.x}} pcbY={${image.center.y}} width={${image.width}} height={${image.height}} layer=${JSON.stringify(graphic.layer)} />`
|
|
845
|
+
];
|
|
846
|
+
});
|
|
847
|
+
};
|
|
848
|
+
|
|
521
849
|
// lib/generate-footprint-tsx/convert-silkscreen-text.ts
|
|
522
|
-
import { su as
|
|
850
|
+
import { su as su13 } from "@tscircuit/soup-util";
|
|
523
851
|
import { mmStr as mmStr5 } from "@tscircuit/mm";
|
|
524
852
|
var convertSilkscreenText = (circuitJson) => {
|
|
525
|
-
const silkscreenTexts =
|
|
853
|
+
const silkscreenTexts = su13(circuitJson).pcb_silkscreen_text.list();
|
|
526
854
|
const elementStrings = [];
|
|
527
855
|
for (const silkscreenText of silkscreenTexts) {
|
|
528
856
|
const pcbX = silkscreenText.anchor_position?.x ?? 0;
|
|
@@ -537,10 +865,10 @@ var convertSilkscreenText = (circuitJson) => {
|
|
|
537
865
|
};
|
|
538
866
|
|
|
539
867
|
// lib/generate-footprint-tsx/convert-smt-pads.ts
|
|
540
|
-
import { su as
|
|
868
|
+
import { su as su14 } from "@tscircuit/soup-util";
|
|
541
869
|
import { mmStr as mmStr6 } from "@tscircuit/mm";
|
|
542
870
|
var convertSmtPads = (circuitJson) => {
|
|
543
|
-
const smtPads =
|
|
871
|
+
const smtPads = su14(circuitJson).pcb_smtpad.list();
|
|
544
872
|
const elementStrings = [];
|
|
545
873
|
for (const smtPad of smtPads) {
|
|
546
874
|
const commonAttrs = [];
|
|
@@ -589,9 +917,9 @@ var convertSmtPads = (circuitJson) => {
|
|
|
589
917
|
|
|
590
918
|
// lib/generate-footprint-tsx/convert-vias.ts
|
|
591
919
|
import { mmStr as mmStr7 } from "@tscircuit/mm";
|
|
592
|
-
import { su as
|
|
920
|
+
import { su as su15 } from "@tscircuit/soup-util";
|
|
593
921
|
var convertVias = (circuitJson) => {
|
|
594
|
-
return
|
|
922
|
+
return su15(circuitJson).pcb_via.list().map((via) => {
|
|
595
923
|
const fromLayer = via.from_layer ?? via.layers[0];
|
|
596
924
|
const toLayer = via.to_layer ?? via.layers[via.layers.length - 1];
|
|
597
925
|
if (!fromLayer || !toLayer) {
|
|
@@ -615,6 +943,7 @@ var generateFootprintTsx = (circuitJson) => {
|
|
|
615
943
|
convertSmtPads,
|
|
616
944
|
convertPcbTraces,
|
|
617
945
|
convertSilkscreen,
|
|
946
|
+
convertSilkscreenGraphics,
|
|
618
947
|
convertFabrication,
|
|
619
948
|
convertSilkscreenText,
|
|
620
949
|
convertCopperText,
|
|
@@ -639,7 +968,7 @@ var getBoardUsingTemplate = ({
|
|
|
639
968
|
circuitJson,
|
|
640
969
|
componentName
|
|
641
970
|
}) => {
|
|
642
|
-
const pcbBoard =
|
|
971
|
+
const pcbBoard = su16(circuitJson).pcb_board.list()[0];
|
|
643
972
|
const boardProps = [];
|
|
644
973
|
if (pcbBoard) {
|
|
645
974
|
if (pcbBoard.width !== void 0) {
|
|
@@ -679,7 +1008,11 @@ var getBoardUsingTemplate = ({
|
|
|
679
1008
|
}
|
|
680
1009
|
const boardPropsStr = boardProps.join(" ");
|
|
681
1010
|
const footprintTsx = generateFootprintTsx(circuitJson);
|
|
682
|
-
const
|
|
1011
|
+
const copperPoursTsx = generateCopperPoursTsx(circuitJson);
|
|
1012
|
+
const children = [
|
|
1013
|
+
footprintTsx ? `<chip footprint={${footprintTsx}} />` : "",
|
|
1014
|
+
...copperPoursTsx
|
|
1015
|
+
].filter(Boolean).join("\n");
|
|
683
1016
|
return `
|
|
684
1017
|
${componentName ? `export const ${componentName} =` : "export default"} () => (
|
|
685
1018
|
<board${boardPropsStr ? ` ${boardPropsStr}` : ""}>
|
|
@@ -691,18 +1024,18 @@ ${componentName ? `export default ${componentName}` : ""}
|
|
|
691
1024
|
};
|
|
692
1025
|
|
|
693
1026
|
// lib/generate-symbol-tsx.ts
|
|
694
|
-
import { su as
|
|
1027
|
+
import { su as su17 } from "@tscircuit/soup-util";
|
|
695
1028
|
var escapeJsxText2 = (text) => text.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
696
1029
|
var generateSymbolTsx = (circuitJson) => {
|
|
697
|
-
const schematicArcs =
|
|
698
|
-
const schematicLines =
|
|
699
|
-
const schematicPaths =
|
|
700
|
-
const schematicTexts =
|
|
701
|
-
const schematicCircles =
|
|
702
|
-
const schematicBoxes =
|
|
703
|
-
const schematicRects =
|
|
704
|
-
const schematicTables =
|
|
705
|
-
const schematicTableCells =
|
|
1030
|
+
const schematicArcs = su17(circuitJson).schematic_arc.list();
|
|
1031
|
+
const schematicLines = su17(circuitJson).schematic_line.list();
|
|
1032
|
+
const schematicPaths = su17(circuitJson).schematic_path.list();
|
|
1033
|
+
const schematicTexts = su17(circuitJson).schematic_text.list();
|
|
1034
|
+
const schematicCircles = su17(circuitJson).schematic_circle.list();
|
|
1035
|
+
const schematicBoxes = su17(circuitJson).schematic_box.list();
|
|
1036
|
+
const schematicRects = su17(circuitJson).schematic_rect.list();
|
|
1037
|
+
const schematicTables = su17(circuitJson).schematic_table.list();
|
|
1038
|
+
const schematicTableCells = su17(circuitJson).schematic_table_cell.list();
|
|
706
1039
|
const elementStrings = [];
|
|
707
1040
|
for (const arc of schematicArcs) {
|
|
708
1041
|
const center = arc.center ?? { x: 0, y: 0 };
|
package/dist/cli/main.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
convertCircuitJsonToTscircuit
|
|
4
|
-
} from "../chunk-
|
|
4
|
+
} from "../chunk-QMIS5SJZ.js";
|
|
5
5
|
|
|
6
6
|
// cli/main.ts
|
|
7
7
|
import { Command } from "commander";
|
|
@@ -16,7 +16,7 @@ var package_default = {
|
|
|
16
16
|
"./cli": "./dist/cli/main.js"
|
|
17
17
|
},
|
|
18
18
|
type: "module",
|
|
19
|
-
version: "0.0.
|
|
19
|
+
version: "0.0.58",
|
|
20
20
|
repository: {
|
|
21
21
|
type: "git",
|
|
22
22
|
url: "https://github.com/tscircuit/circuit-json-to-tscircuit.git"
|
package/dist/lib/index.js
CHANGED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { Point } from "circuit-json"
|
|
2
|
+
import { getBulgeArc, type BulgePoint } from "./get-bulge-arc"
|
|
3
|
+
|
|
4
|
+
const MAX_ANGLE_STEP = Math.PI / 18
|
|
5
|
+
|
|
6
|
+
export const flattenBrepRing = (vertices: BulgePoint[]): Point[] => {
|
|
7
|
+
if (vertices.length < 2) return vertices.map(toPoint)
|
|
8
|
+
|
|
9
|
+
const points: Point[] = [toPoint(vertices[0]!)]
|
|
10
|
+
for (let index = 0; index < vertices.length; index++) {
|
|
11
|
+
const start = vertices[index]!
|
|
12
|
+
const end = vertices[(index + 1) % vertices.length]!
|
|
13
|
+
const isClosingSegment = index === vertices.length - 1
|
|
14
|
+
const arc = getBulgeArc({ end, start })
|
|
15
|
+
|
|
16
|
+
if (!arc) {
|
|
17
|
+
if (!isClosingSegment) points.push(toPoint(end))
|
|
18
|
+
continue
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const startAngle = Math.atan2(
|
|
22
|
+
start.y - arc.center.y,
|
|
23
|
+
start.x - arc.center.x,
|
|
24
|
+
)
|
|
25
|
+
const stepCount = Math.max(
|
|
26
|
+
2,
|
|
27
|
+
Math.ceil(Math.abs(arc.sweepAngle) / MAX_ANGLE_STEP),
|
|
28
|
+
)
|
|
29
|
+
for (let step = 1; step <= stepCount; step++) {
|
|
30
|
+
if (isClosingSegment && step === stepCount) continue
|
|
31
|
+
if (step === stepCount) {
|
|
32
|
+
points.push(toPoint(end))
|
|
33
|
+
continue
|
|
34
|
+
}
|
|
35
|
+
const angle = startAngle + (arc.sweepAngle * step) / stepCount
|
|
36
|
+
points.push({
|
|
37
|
+
x: arc.center.x + arc.radius * Math.cos(angle),
|
|
38
|
+
y: arc.center.y + arc.radius * Math.sin(angle),
|
|
39
|
+
})
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return points
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const toPoint = ({ x, y }: BulgePoint): Point => ({ x, y })
|