circuit-json-to-kicad 0.0.1 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +174 -0
- package/dist/index.d.ts +74 -0
- package/dist/index.js +1341 -0
- package/package.json +7 -4
package/dist/index.js
ADDED
|
@@ -0,0 +1,1341 @@
|
|
|
1
|
+
// lib/types.ts
|
|
2
|
+
var ConverterStage = class {
|
|
3
|
+
MAX_ITERATIONS = 1e3;
|
|
4
|
+
iteration = 0;
|
|
5
|
+
finished = false;
|
|
6
|
+
input;
|
|
7
|
+
ctx;
|
|
8
|
+
constructor(input, ctx) {
|
|
9
|
+
this.input = input;
|
|
10
|
+
this.ctx = ctx;
|
|
11
|
+
}
|
|
12
|
+
step() {
|
|
13
|
+
this.iteration++;
|
|
14
|
+
if (this.iteration > this.MAX_ITERATIONS) {
|
|
15
|
+
throw new Error("Max iterations reached");
|
|
16
|
+
}
|
|
17
|
+
this._step();
|
|
18
|
+
}
|
|
19
|
+
_step() {
|
|
20
|
+
throw new Error("Not implemented");
|
|
21
|
+
}
|
|
22
|
+
runUntilFinished() {
|
|
23
|
+
while (!this.finished) {
|
|
24
|
+
this.step();
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
getOutput() {
|
|
28
|
+
throw new Error("Not implemented");
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
// lib/schematic/CircuitJsonToKicadSchConverter.ts
|
|
33
|
+
import { KicadSch } from "kicadts";
|
|
34
|
+
import { cju } from "@tscircuit/circuit-json-util";
|
|
35
|
+
import { compose, translate, scale } from "transformation-matrix";
|
|
36
|
+
|
|
37
|
+
// lib/schematic/stages/InitializeSchematicStage.ts
|
|
38
|
+
import { Paper, Uuid } from "kicadts";
|
|
39
|
+
var InitializeSchematicStage = class extends ConverterStage {
|
|
40
|
+
_step() {
|
|
41
|
+
const { kicadSch } = this.ctx;
|
|
42
|
+
if (!kicadSch) {
|
|
43
|
+
throw new Error("KicadSch instance not initialized in context");
|
|
44
|
+
}
|
|
45
|
+
kicadSch.version = 20250114;
|
|
46
|
+
const paper = new Paper();
|
|
47
|
+
paper.size = "A4";
|
|
48
|
+
kicadSch.paper = paper;
|
|
49
|
+
kicadSch.uuid = new Uuid(crypto.randomUUID());
|
|
50
|
+
this.finished = true;
|
|
51
|
+
}
|
|
52
|
+
getOutput() {
|
|
53
|
+
return this.ctx.kicadSch;
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
// lib/schematic/stages/AddLibrarySymbolsStage.ts
|
|
58
|
+
import {
|
|
59
|
+
EmbeddedFonts,
|
|
60
|
+
LibSymbols,
|
|
61
|
+
Pts,
|
|
62
|
+
SchematicSymbol,
|
|
63
|
+
Stroke,
|
|
64
|
+
SymbolPin,
|
|
65
|
+
SymbolPinName,
|
|
66
|
+
SymbolPinNames,
|
|
67
|
+
SymbolPinNumber,
|
|
68
|
+
SymbolPinNumbers,
|
|
69
|
+
SymbolPolyline,
|
|
70
|
+
SymbolPolylineFill,
|
|
71
|
+
SymbolProperty,
|
|
72
|
+
TextEffects,
|
|
73
|
+
TextEffectsFont,
|
|
74
|
+
Xy
|
|
75
|
+
} from "kicadts";
|
|
76
|
+
import { symbols } from "schematic-symbols";
|
|
77
|
+
var AddLibrarySymbolsStage = class extends ConverterStage {
|
|
78
|
+
_step() {
|
|
79
|
+
const { kicadSch, db } = this.ctx;
|
|
80
|
+
const schematicComponents = db.schematic_component.list();
|
|
81
|
+
if (schematicComponents.length === 0) {
|
|
82
|
+
this.finished = true;
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
const libSymbols = new LibSymbols();
|
|
86
|
+
const symbolsToCreate = /* @__PURE__ */ new Set();
|
|
87
|
+
for (const comp of schematicComponents) {
|
|
88
|
+
if (comp.symbol_name) {
|
|
89
|
+
symbolsToCreate.add(comp.symbol_name);
|
|
90
|
+
} else {
|
|
91
|
+
const sourceComp = comp.source_component_id ? db.source_component.get(comp.source_component_id) : null;
|
|
92
|
+
if (sourceComp?.ftype === "simple_chip") {
|
|
93
|
+
symbolsToCreate.add(`generic_chip_${comp.source_component_id}`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
const librarySymbols = [];
|
|
98
|
+
for (const symbolName of symbolsToCreate) {
|
|
99
|
+
let symbolData = symbols[symbolName];
|
|
100
|
+
let exampleComp;
|
|
101
|
+
let sourceComp;
|
|
102
|
+
if (symbolName.startsWith("generic_chip_")) {
|
|
103
|
+
const sourceCompId = symbolName.replace("generic_chip_", "");
|
|
104
|
+
sourceComp = db.source_component.get(sourceCompId);
|
|
105
|
+
exampleComp = schematicComponents.find(
|
|
106
|
+
(c) => c.source_component_id === sourceCompId
|
|
107
|
+
);
|
|
108
|
+
if (exampleComp) {
|
|
109
|
+
symbolData = this.createGenericChipSymbolData(exampleComp, db);
|
|
110
|
+
}
|
|
111
|
+
} else {
|
|
112
|
+
symbolData = symbols[symbolName];
|
|
113
|
+
if (!symbolData) {
|
|
114
|
+
console.warn(`Symbol ${symbolName} not found in schematic-symbols`);
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
exampleComp = schematicComponents.find(
|
|
118
|
+
(c) => c.symbol_name === symbolName
|
|
119
|
+
);
|
|
120
|
+
sourceComp = exampleComp && exampleComp.source_component_id ? db.source_component.get(exampleComp.source_component_id) : null;
|
|
121
|
+
}
|
|
122
|
+
const libSymbol = this.createLibrarySymbolFromSchematicSymbol(
|
|
123
|
+
symbolName,
|
|
124
|
+
symbolData,
|
|
125
|
+
sourceComp
|
|
126
|
+
);
|
|
127
|
+
librarySymbols.push(libSymbol);
|
|
128
|
+
}
|
|
129
|
+
libSymbols.symbols = librarySymbols;
|
|
130
|
+
if (kicadSch) {
|
|
131
|
+
kicadSch.libSymbols = libSymbols;
|
|
132
|
+
}
|
|
133
|
+
this.finished = true;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Create generic chip symbol data for chips without a symbol_name
|
|
137
|
+
*/
|
|
138
|
+
createGenericChipSymbolData(schematicComp, db) {
|
|
139
|
+
const schematicPorts = db.schematic_port.list().filter(
|
|
140
|
+
(p) => p.schematic_component_id === schematicComp.schematic_component_id
|
|
141
|
+
).sort((a, b) => (a.pin_number || 0) - (b.pin_number || 0));
|
|
142
|
+
const width = schematicComp.size?.width || 1.5;
|
|
143
|
+
const height = schematicComp.size?.height || 1;
|
|
144
|
+
const boxPath = {
|
|
145
|
+
type: "path",
|
|
146
|
+
points: [
|
|
147
|
+
{ x: -width / 2, y: -height / 2 },
|
|
148
|
+
{ x: width / 2, y: -height / 2 },
|
|
149
|
+
{ x: width / 2, y: height / 2 },
|
|
150
|
+
{ x: -width / 2, y: height / 2 },
|
|
151
|
+
{ x: -width / 2, y: -height / 2 }
|
|
152
|
+
]
|
|
153
|
+
};
|
|
154
|
+
const ports = schematicPorts.map((port) => {
|
|
155
|
+
const portX = port.center.x - schematicComp.center.x;
|
|
156
|
+
const portY = port.center.y - schematicComp.center.y;
|
|
157
|
+
return {
|
|
158
|
+
x: portX,
|
|
159
|
+
y: portY,
|
|
160
|
+
labels: [port.display_pin_label || `${port.pin_number || 1}`],
|
|
161
|
+
pinNumber: port.pin_number || 1
|
|
162
|
+
};
|
|
163
|
+
});
|
|
164
|
+
return {
|
|
165
|
+
center: { x: 0, y: 0 },
|
|
166
|
+
primitives: [boxPath],
|
|
167
|
+
ports,
|
|
168
|
+
size: { width, height }
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Convert schematic-symbols data to KiCad library symbol
|
|
173
|
+
*/
|
|
174
|
+
createLibrarySymbolFromSchematicSymbol(symbolName, symbolData, sourceComp) {
|
|
175
|
+
const libId = this.getLibraryId(symbolName, sourceComp);
|
|
176
|
+
const symbol = new SchematicSymbol({
|
|
177
|
+
libraryId: libId,
|
|
178
|
+
excludeFromSim: false,
|
|
179
|
+
inBom: true,
|
|
180
|
+
onBoard: true
|
|
181
|
+
});
|
|
182
|
+
const pinNumbers = new SymbolPinNumbers();
|
|
183
|
+
pinNumbers.hide = sourceComp?.ftype !== "simple_chip";
|
|
184
|
+
symbol._sxPinNumbers = pinNumbers;
|
|
185
|
+
const pinNames = new SymbolPinNames();
|
|
186
|
+
pinNames.offset = sourceComp?.ftype === "simple_chip" ? 1.27 : 0;
|
|
187
|
+
symbol._sxPinNames = pinNames;
|
|
188
|
+
this.addSymbolProperties(symbol, libId, sourceComp);
|
|
189
|
+
const isChip = sourceComp?.ftype === "simple_chip";
|
|
190
|
+
const drawingSymbol = this.createDrawingSubsymbol(libId, symbolData, isChip);
|
|
191
|
+
symbol.subSymbols.push(drawingSymbol);
|
|
192
|
+
const pinSymbol = this.createPinSubsymbol(libId, symbolData, isChip);
|
|
193
|
+
symbol.subSymbols.push(pinSymbol);
|
|
194
|
+
symbol._sxEmbeddedFonts = new EmbeddedFonts(false);
|
|
195
|
+
return symbol;
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Get KiCad library ID for a symbol
|
|
199
|
+
*/
|
|
200
|
+
getLibraryId(symbolName, sourceComp) {
|
|
201
|
+
if (sourceComp?.ftype === "simple_resistor") {
|
|
202
|
+
return "Device:R";
|
|
203
|
+
}
|
|
204
|
+
if (sourceComp?.ftype === "simple_capacitor") {
|
|
205
|
+
return "Device:C";
|
|
206
|
+
}
|
|
207
|
+
if (sourceComp?.ftype === "simple_chip") {
|
|
208
|
+
return "Device:U";
|
|
209
|
+
}
|
|
210
|
+
return `Custom:${symbolName}`;
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Add properties to the library symbol
|
|
214
|
+
*/
|
|
215
|
+
addSymbolProperties(symbol, libId, sourceComp) {
|
|
216
|
+
const refPrefix = libId.split(":")[1]?.[0] || "U";
|
|
217
|
+
const properties = [
|
|
218
|
+
{
|
|
219
|
+
key: "Reference",
|
|
220
|
+
value: refPrefix,
|
|
221
|
+
id: 0,
|
|
222
|
+
at: [2.032, 0, 90],
|
|
223
|
+
hide: false
|
|
224
|
+
},
|
|
225
|
+
{ key: "Value", value: refPrefix, id: 1, at: [0, 0, 90], hide: false },
|
|
226
|
+
{
|
|
227
|
+
key: "Footprint",
|
|
228
|
+
value: "",
|
|
229
|
+
id: 2,
|
|
230
|
+
at: [-1.778, 0, 90],
|
|
231
|
+
hide: true
|
|
232
|
+
},
|
|
233
|
+
{
|
|
234
|
+
key: "Datasheet",
|
|
235
|
+
value: "~",
|
|
236
|
+
id: 3,
|
|
237
|
+
at: [0, 0, 0],
|
|
238
|
+
hide: true
|
|
239
|
+
},
|
|
240
|
+
{
|
|
241
|
+
key: "Description",
|
|
242
|
+
value: this.getDescription(sourceComp),
|
|
243
|
+
id: 4,
|
|
244
|
+
at: [0, 0, 0],
|
|
245
|
+
hide: true
|
|
246
|
+
},
|
|
247
|
+
{
|
|
248
|
+
key: "ki_keywords",
|
|
249
|
+
value: this.getKeywords(sourceComp),
|
|
250
|
+
id: 5,
|
|
251
|
+
at: [0, 0, 0],
|
|
252
|
+
hide: true
|
|
253
|
+
},
|
|
254
|
+
{
|
|
255
|
+
key: "ki_fp_filters",
|
|
256
|
+
value: this.getFpFilters(sourceComp),
|
|
257
|
+
id: 6,
|
|
258
|
+
at: [0, 0, 0],
|
|
259
|
+
hide: true
|
|
260
|
+
}
|
|
261
|
+
];
|
|
262
|
+
for (const prop of properties) {
|
|
263
|
+
symbol.properties.push(
|
|
264
|
+
new SymbolProperty({
|
|
265
|
+
key: prop.key,
|
|
266
|
+
value: prop.value,
|
|
267
|
+
id: prop.id,
|
|
268
|
+
at: prop.at,
|
|
269
|
+
effects: this.createTextEffects(1.27, prop.hide)
|
|
270
|
+
})
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
getDescription(sourceComp) {
|
|
275
|
+
if (sourceComp?.ftype === "simple_resistor") return "Resistor";
|
|
276
|
+
if (sourceComp?.ftype === "simple_capacitor") return "Capacitor";
|
|
277
|
+
if (sourceComp?.ftype === "simple_chip") return "Integrated Circuit";
|
|
278
|
+
return "Component";
|
|
279
|
+
}
|
|
280
|
+
getKeywords(sourceComp) {
|
|
281
|
+
if (sourceComp?.ftype === "simple_resistor") return "R res resistor";
|
|
282
|
+
if (sourceComp?.ftype === "simple_capacitor") return "C cap capacitor";
|
|
283
|
+
if (sourceComp?.ftype === "simple_chip") return "U IC chip";
|
|
284
|
+
return "";
|
|
285
|
+
}
|
|
286
|
+
getFpFilters(sourceComp) {
|
|
287
|
+
if (sourceComp?.ftype === "simple_resistor") return "R_*";
|
|
288
|
+
if (sourceComp?.ftype === "simple_capacitor") return "C_*";
|
|
289
|
+
if (sourceComp?.ftype === "simple_chip") return "*";
|
|
290
|
+
return "*";
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Create the drawing subsymbol (primitives, no pins)
|
|
294
|
+
* Converts schematic-symbols primitives to KiCad drawing elements
|
|
295
|
+
*/
|
|
296
|
+
createDrawingSubsymbol(libId, symbolData, isChip = false) {
|
|
297
|
+
const drawingSymbol = new SchematicSymbol({
|
|
298
|
+
libraryId: `${libId.split(":")[1]}_0_1`
|
|
299
|
+
});
|
|
300
|
+
const symbolScale = this.ctx.c2kMatSch?.a || 15;
|
|
301
|
+
for (const primitive of symbolData.primitives || []) {
|
|
302
|
+
if (primitive.type === "path" && primitive.points) {
|
|
303
|
+
const fillType = isChip ? "background" : "none";
|
|
304
|
+
const polyline = this.createPolylineFromPoints(
|
|
305
|
+
primitive.points,
|
|
306
|
+
symbolScale,
|
|
307
|
+
fillType
|
|
308
|
+
);
|
|
309
|
+
drawingSymbol.polylines.push(polyline);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
return drawingSymbol;
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Create a KiCad polyline from points
|
|
316
|
+
*/
|
|
317
|
+
createPolylineFromPoints(points, scale3, fillType = "none") {
|
|
318
|
+
const polyline = new SymbolPolyline();
|
|
319
|
+
const xyPoints = points.map((p) => new Xy(p.x * scale3, p.y * scale3));
|
|
320
|
+
const pts = new Pts(xyPoints);
|
|
321
|
+
polyline.points = pts;
|
|
322
|
+
const stroke = new Stroke();
|
|
323
|
+
stroke.width = 0.254;
|
|
324
|
+
stroke.type = "default";
|
|
325
|
+
polyline.stroke = stroke;
|
|
326
|
+
const fill = new SymbolPolylineFill();
|
|
327
|
+
fill.type = fillType;
|
|
328
|
+
polyline.fill = fill;
|
|
329
|
+
return polyline;
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* Create the pin subsymbol
|
|
333
|
+
*/
|
|
334
|
+
createPinSubsymbol(libId, symbolData, isChip = false) {
|
|
335
|
+
const pinSymbol = new SchematicSymbol({
|
|
336
|
+
libraryId: `${libId.split(":")[1]}_1_1`
|
|
337
|
+
});
|
|
338
|
+
for (let i = 0; i < (symbolData.ports?.length || 0); i++) {
|
|
339
|
+
const port = symbolData.ports[i];
|
|
340
|
+
const pin = new SymbolPin();
|
|
341
|
+
pin.pinElectricalType = "passive";
|
|
342
|
+
pin.pinGraphicStyle = "line";
|
|
343
|
+
const { x, y, angle } = this.calculatePinPosition(
|
|
344
|
+
port,
|
|
345
|
+
symbolData.center,
|
|
346
|
+
symbolData.size,
|
|
347
|
+
isChip
|
|
348
|
+
);
|
|
349
|
+
pin.at = [x, y, angle];
|
|
350
|
+
pin.length = isChip ? 2.54 : 1.27;
|
|
351
|
+
const nameFont = new TextEffectsFont();
|
|
352
|
+
nameFont.size = { height: 1.27, width: 1.27 };
|
|
353
|
+
const nameEffects = new TextEffects({ font: nameFont });
|
|
354
|
+
const pinName = port.labels?.[0] || "~";
|
|
355
|
+
pin._sxName = new SymbolPinName({ value: pinName, effects: nameEffects });
|
|
356
|
+
const numFont = new TextEffectsFont();
|
|
357
|
+
numFont.size = { height: 1.27, width: 1.27 };
|
|
358
|
+
const numEffects = new TextEffects({ font: numFont });
|
|
359
|
+
const pinNum = port.pinNumber?.toString() || `${i + 1}`;
|
|
360
|
+
pin._sxNumber = new SymbolPinNumber({
|
|
361
|
+
value: pinNum,
|
|
362
|
+
effects: numEffects
|
|
363
|
+
});
|
|
364
|
+
pinSymbol.pins.push(pin);
|
|
365
|
+
}
|
|
366
|
+
return pinSymbol;
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* Calculate KiCad pin position and rotation from schematic-symbols port
|
|
370
|
+
* Scale pins to match the c2kMatSch transformation scale
|
|
371
|
+
*/
|
|
372
|
+
calculatePinPosition(port, center, size, isChip) {
|
|
373
|
+
const symbolScale = this.ctx.c2kMatSch?.a || 15;
|
|
374
|
+
const dx = port.x - center.x;
|
|
375
|
+
const dy = port.y - center.y;
|
|
376
|
+
let x = port.x * symbolScale;
|
|
377
|
+
let y = port.y * symbolScale;
|
|
378
|
+
const chipPinLength = 2.54;
|
|
379
|
+
if (isChip && size) {
|
|
380
|
+
const halfWidth = size.width / 2 * symbolScale;
|
|
381
|
+
const halfHeight = size.height / 2 * symbolScale;
|
|
382
|
+
if (Math.abs(dx) > Math.abs(dy)) {
|
|
383
|
+
x = dx > 0 ? halfWidth : -halfWidth;
|
|
384
|
+
y = dy * symbolScale;
|
|
385
|
+
} else {
|
|
386
|
+
x = dx * symbolScale;
|
|
387
|
+
y = dy > 0 ? halfHeight : -halfHeight;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
let angle = 0;
|
|
391
|
+
if (Math.abs(dx) > Math.abs(dy)) {
|
|
392
|
+
if (dx > 0) {
|
|
393
|
+
if (isChip) {
|
|
394
|
+
angle = 180;
|
|
395
|
+
x = x + chipPinLength;
|
|
396
|
+
} else {
|
|
397
|
+
angle = 0;
|
|
398
|
+
}
|
|
399
|
+
} else {
|
|
400
|
+
if (isChip) {
|
|
401
|
+
angle = 0;
|
|
402
|
+
x = x - chipPinLength;
|
|
403
|
+
} else {
|
|
404
|
+
angle = 180;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
} else {
|
|
408
|
+
if (dy > 0) {
|
|
409
|
+
if (isChip) {
|
|
410
|
+
angle = 270;
|
|
411
|
+
y = y + chipPinLength;
|
|
412
|
+
} else {
|
|
413
|
+
angle = 90;
|
|
414
|
+
}
|
|
415
|
+
} else {
|
|
416
|
+
if (isChip) {
|
|
417
|
+
angle = 90;
|
|
418
|
+
y = y - chipPinLength;
|
|
419
|
+
} else {
|
|
420
|
+
angle = 270;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
return { x, y, angle };
|
|
425
|
+
}
|
|
426
|
+
/**
|
|
427
|
+
* Creates text effects for properties
|
|
428
|
+
*/
|
|
429
|
+
createTextEffects(size, hide) {
|
|
430
|
+
const font = new TextEffectsFont();
|
|
431
|
+
font.size = { height: size, width: size };
|
|
432
|
+
return new TextEffects({
|
|
433
|
+
font,
|
|
434
|
+
hiddenText: hide
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
getOutput() {
|
|
438
|
+
if (!this.ctx.kicadSch) {
|
|
439
|
+
throw new Error("kicadSch is not initialized");
|
|
440
|
+
}
|
|
441
|
+
return this.ctx.kicadSch;
|
|
442
|
+
}
|
|
443
|
+
};
|
|
444
|
+
|
|
445
|
+
// lib/schematic/stages/AddSchematicSymbolsStage.ts
|
|
446
|
+
import {
|
|
447
|
+
SchematicSymbol as SchematicSymbol2,
|
|
448
|
+
SymbolLibId,
|
|
449
|
+
SymbolProperty as SymbolProperty2,
|
|
450
|
+
SymbolPin as SymbolPin2,
|
|
451
|
+
SymbolInstances,
|
|
452
|
+
SymbolInstancesProject,
|
|
453
|
+
SymbolInstancePath,
|
|
454
|
+
TextEffects as TextEffects2,
|
|
455
|
+
TextEffectsFont as TextEffectsFont2,
|
|
456
|
+
TextEffectsJustify
|
|
457
|
+
} from "kicadts";
|
|
458
|
+
import { applyToPoint } from "transformation-matrix";
|
|
459
|
+
import { symbols as symbols2 } from "schematic-symbols";
|
|
460
|
+
var AddSchematicSymbolsStage = class extends ConverterStage {
|
|
461
|
+
_step() {
|
|
462
|
+
const { kicadSch, db } = this.ctx;
|
|
463
|
+
const schematicComponents = db.schematic_component.list();
|
|
464
|
+
if (schematicComponents.length === 0) {
|
|
465
|
+
this.finished = true;
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
const symbols3 = [];
|
|
469
|
+
for (const schematicComponent of schematicComponents) {
|
|
470
|
+
const sourceComponent = schematicComponent.source_component_id ? db.source_component.get(schematicComponent.source_component_id) : null;
|
|
471
|
+
if (!sourceComponent) continue;
|
|
472
|
+
if (!this.ctx.c2kMatSch) continue;
|
|
473
|
+
const { x, y } = applyToPoint(this.ctx.c2kMatSch, {
|
|
474
|
+
x: schematicComponent.center.x,
|
|
475
|
+
y: schematicComponent.center.y
|
|
476
|
+
});
|
|
477
|
+
const uuid = crypto.randomUUID();
|
|
478
|
+
const symbol = new SchematicSymbol2({
|
|
479
|
+
at: [x, y, 0],
|
|
480
|
+
unit: 1,
|
|
481
|
+
excludeFromSim: false,
|
|
482
|
+
inBom: true,
|
|
483
|
+
onBoard: true,
|
|
484
|
+
dnp: false,
|
|
485
|
+
uuid,
|
|
486
|
+
fieldsAutoplaced: true
|
|
487
|
+
});
|
|
488
|
+
const libId = this.getLibraryId(sourceComponent);
|
|
489
|
+
const symLibId = new SymbolLibId(libId);
|
|
490
|
+
symbol._sxLibId = symLibId;
|
|
491
|
+
const { reference, value, description } = this.getComponentMetadata(sourceComponent);
|
|
492
|
+
const { refTextPos, valTextPos } = this.getTextPositions(
|
|
493
|
+
schematicComponent,
|
|
494
|
+
{ x, y }
|
|
495
|
+
);
|
|
496
|
+
const referenceProperty = new SymbolProperty2({
|
|
497
|
+
key: "Reference",
|
|
498
|
+
value: reference,
|
|
499
|
+
id: 0,
|
|
500
|
+
at: [refTextPos.x, refTextPos.y, 0],
|
|
501
|
+
effects: this.createTextEffects(1.27, false)
|
|
502
|
+
});
|
|
503
|
+
const hideValue = sourceComponent.ftype === "simple_chip";
|
|
504
|
+
const valueProperty = new SymbolProperty2({
|
|
505
|
+
key: "Value",
|
|
506
|
+
value,
|
|
507
|
+
id: 1,
|
|
508
|
+
at: [valTextPos.x, valTextPos.y, 0],
|
|
509
|
+
effects: this.createTextEffects(1.27, hideValue)
|
|
510
|
+
});
|
|
511
|
+
const footprintProperty = new SymbolProperty2({
|
|
512
|
+
key: "Footprint",
|
|
513
|
+
value: "",
|
|
514
|
+
id: 2,
|
|
515
|
+
at: [x - 1.778, y, 90],
|
|
516
|
+
effects: this.createTextEffects(1.27, true)
|
|
517
|
+
});
|
|
518
|
+
const datasheetProperty = new SymbolProperty2({
|
|
519
|
+
key: "Datasheet",
|
|
520
|
+
value: "~",
|
|
521
|
+
id: 3,
|
|
522
|
+
at: [x, y, 0],
|
|
523
|
+
effects: this.createTextEffects(1.27, true)
|
|
524
|
+
});
|
|
525
|
+
const descriptionProperty = new SymbolProperty2({
|
|
526
|
+
key: "Description",
|
|
527
|
+
value: description,
|
|
528
|
+
id: 4,
|
|
529
|
+
at: [x, y, 0],
|
|
530
|
+
effects: this.createTextEffects(1.27, true)
|
|
531
|
+
});
|
|
532
|
+
symbol.properties.push(
|
|
533
|
+
referenceProperty,
|
|
534
|
+
valueProperty,
|
|
535
|
+
footprintProperty,
|
|
536
|
+
datasheetProperty,
|
|
537
|
+
descriptionProperty
|
|
538
|
+
);
|
|
539
|
+
const schematicPorts = db.schematic_port.list().filter(
|
|
540
|
+
(p) => p.schematic_component_id === schematicComponent.schematic_component_id
|
|
541
|
+
).sort((a, b) => (a.pin_number || 0) - (b.pin_number || 0));
|
|
542
|
+
for (const port of schematicPorts) {
|
|
543
|
+
const pin = new SymbolPin2();
|
|
544
|
+
pin.numberString = `${port.pin_number || 1}`;
|
|
545
|
+
pin.uuid = crypto.randomUUID();
|
|
546
|
+
symbol.pins.push(pin);
|
|
547
|
+
}
|
|
548
|
+
const instances = new SymbolInstances();
|
|
549
|
+
const project = new SymbolInstancesProject("");
|
|
550
|
+
const path = new SymbolInstancePath(`/${kicadSch?.uuid?.value || ""}`);
|
|
551
|
+
path.reference = reference;
|
|
552
|
+
path.unit = 1;
|
|
553
|
+
project.paths.push(path);
|
|
554
|
+
instances.projects.push(project);
|
|
555
|
+
symbol._sxInstances = instances;
|
|
556
|
+
symbols3.push(symbol);
|
|
557
|
+
}
|
|
558
|
+
if (kicadSch) {
|
|
559
|
+
kicadSch.symbols = symbols3;
|
|
560
|
+
}
|
|
561
|
+
this.finished = true;
|
|
562
|
+
}
|
|
563
|
+
/**
|
|
564
|
+
* Get text positions from schematic symbol definition or schematic_text elements
|
|
565
|
+
*/
|
|
566
|
+
getTextPositions(schematicComponent, symbolKicadPos) {
|
|
567
|
+
const schematicTexts = this.ctx.db.schematic_text?.list?.()?.filter(
|
|
568
|
+
(t) => t.schematic_component_id === schematicComponent.schematic_component_id
|
|
569
|
+
) || [];
|
|
570
|
+
const refText = schematicTexts.find((t) => t.text && t.text.length > 0);
|
|
571
|
+
if (refText && this.ctx.c2kMatSch) {
|
|
572
|
+
const refTextPos2 = applyToPoint(this.ctx.c2kMatSch, {
|
|
573
|
+
x: refText.position.x,
|
|
574
|
+
y: refText.position.y
|
|
575
|
+
});
|
|
576
|
+
const valTextPos2 = { x: symbolKicadPos.x, y: symbolKicadPos.y + 6 };
|
|
577
|
+
return { refTextPos: refTextPos2, valTextPos: valTextPos2 };
|
|
578
|
+
}
|
|
579
|
+
const symbolName = schematicComponent.symbol_name;
|
|
580
|
+
const symbol = symbols2[symbolName];
|
|
581
|
+
if (!symbol) {
|
|
582
|
+
return {
|
|
583
|
+
refTextPos: { x: symbolKicadPos.x, y: symbolKicadPos.y - 6 },
|
|
584
|
+
valTextPos: { x: symbolKicadPos.x, y: symbolKicadPos.y + 6 }
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
let refTextPrimitive = null;
|
|
588
|
+
let valTextPrimitive = null;
|
|
589
|
+
for (const primitive of symbol.primitives) {
|
|
590
|
+
if (primitive.type === "text") {
|
|
591
|
+
if (primitive.text === "{REF}") {
|
|
592
|
+
refTextPrimitive = primitive;
|
|
593
|
+
} else if (primitive.text === "{VAL}") {
|
|
594
|
+
valTextPrimitive = primitive;
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
const refTextPos = refTextPrimitive && this.ctx.c2kMatSch ? applyToPoint(this.ctx.c2kMatSch, {
|
|
599
|
+
x: schematicComponent.center.x + refTextPrimitive.x,
|
|
600
|
+
y: schematicComponent.center.y + refTextPrimitive.y
|
|
601
|
+
}) : { x: symbolKicadPos.x, y: symbolKicadPos.y - 6 };
|
|
602
|
+
const valTextPos = valTextPrimitive && this.ctx.c2kMatSch ? applyToPoint(this.ctx.c2kMatSch, {
|
|
603
|
+
x: schematicComponent.center.x + valTextPrimitive.x,
|
|
604
|
+
y: schematicComponent.center.y + valTextPrimitive.y
|
|
605
|
+
}) : { x: symbolKicadPos.x, y: symbolKicadPos.y + 6 };
|
|
606
|
+
return { refTextPos, valTextPos };
|
|
607
|
+
}
|
|
608
|
+
/**
|
|
609
|
+
* Get KiCad library ID for a component
|
|
610
|
+
*/
|
|
611
|
+
getLibraryId(sourceComp) {
|
|
612
|
+
if (sourceComp.ftype === "simple_resistor") {
|
|
613
|
+
return "Device:R";
|
|
614
|
+
}
|
|
615
|
+
if (sourceComp.ftype === "simple_capacitor") {
|
|
616
|
+
return "Device:C";
|
|
617
|
+
}
|
|
618
|
+
if (sourceComp.ftype === "simple_inductor") {
|
|
619
|
+
return "Device:L";
|
|
620
|
+
}
|
|
621
|
+
if (sourceComp.ftype === "simple_diode") {
|
|
622
|
+
return "Device:D";
|
|
623
|
+
}
|
|
624
|
+
if (sourceComp.ftype === "simple_chip") {
|
|
625
|
+
return "Device:U";
|
|
626
|
+
}
|
|
627
|
+
return "Device:Component";
|
|
628
|
+
}
|
|
629
|
+
/**
|
|
630
|
+
* Get component metadata (reference, value, description)
|
|
631
|
+
*/
|
|
632
|
+
getComponentMetadata(sourceComp) {
|
|
633
|
+
const name = sourceComp.name || "?";
|
|
634
|
+
if (sourceComp.ftype === "simple_resistor") {
|
|
635
|
+
return {
|
|
636
|
+
reference: name,
|
|
637
|
+
value: sourceComp.display_resistance || "R",
|
|
638
|
+
description: "Resistor"
|
|
639
|
+
};
|
|
640
|
+
}
|
|
641
|
+
if (sourceComp.ftype === "simple_capacitor") {
|
|
642
|
+
return {
|
|
643
|
+
reference: name,
|
|
644
|
+
value: sourceComp.display_capacitance || "C",
|
|
645
|
+
description: "Capacitor"
|
|
646
|
+
};
|
|
647
|
+
}
|
|
648
|
+
if (sourceComp.ftype === "simple_inductor") {
|
|
649
|
+
return {
|
|
650
|
+
reference: name,
|
|
651
|
+
value: sourceComp.display_inductance || "L",
|
|
652
|
+
description: "Inductor"
|
|
653
|
+
};
|
|
654
|
+
}
|
|
655
|
+
if (sourceComp.ftype === "simple_diode") {
|
|
656
|
+
return {
|
|
657
|
+
reference: name,
|
|
658
|
+
value: "D",
|
|
659
|
+
description: "Diode"
|
|
660
|
+
};
|
|
661
|
+
}
|
|
662
|
+
if (sourceComp.ftype === "simple_chip") {
|
|
663
|
+
return {
|
|
664
|
+
reference: name,
|
|
665
|
+
value: name,
|
|
666
|
+
description: "Integrated Circuit"
|
|
667
|
+
};
|
|
668
|
+
}
|
|
669
|
+
return {
|
|
670
|
+
reference: name,
|
|
671
|
+
value: name,
|
|
672
|
+
description: "Component"
|
|
673
|
+
};
|
|
674
|
+
}
|
|
675
|
+
/**
|
|
676
|
+
* Creates text effects for properties
|
|
677
|
+
*/
|
|
678
|
+
createTextEffects(size, hide = false, justify) {
|
|
679
|
+
const font = new TextEffectsFont2();
|
|
680
|
+
font.size = { height: size, width: size };
|
|
681
|
+
const justifyObj = justify ? new TextEffectsJustify({ horizontal: justify }) : void 0;
|
|
682
|
+
const effects = new TextEffects2({
|
|
683
|
+
font,
|
|
684
|
+
hiddenText: hide,
|
|
685
|
+
justify: justifyObj
|
|
686
|
+
});
|
|
687
|
+
return effects;
|
|
688
|
+
}
|
|
689
|
+
getOutput() {
|
|
690
|
+
if (!this.ctx.kicadSch) {
|
|
691
|
+
throw new Error("kicadSch is not initialized");
|
|
692
|
+
}
|
|
693
|
+
return this.ctx.kicadSch;
|
|
694
|
+
}
|
|
695
|
+
};
|
|
696
|
+
|
|
697
|
+
// lib/schematic/stages/AddSchematicTracesStage.ts
|
|
698
|
+
import { Wire, Pts as Pts2, Xy as Xy2, Stroke as Stroke2, Junction } from "kicadts";
|
|
699
|
+
import { applyToPoint as applyToPoint2 } from "transformation-matrix";
|
|
700
|
+
var AddSchematicTracesStage = class extends ConverterStage {
|
|
701
|
+
_step() {
|
|
702
|
+
const { kicadSch, db } = this.ctx;
|
|
703
|
+
if (!kicadSch) {
|
|
704
|
+
throw new Error("KicadSch instance not initialized in context");
|
|
705
|
+
}
|
|
706
|
+
const schematicTraces = db.schematic_trace.list();
|
|
707
|
+
if (schematicTraces.length === 0) {
|
|
708
|
+
this.finished = true;
|
|
709
|
+
return;
|
|
710
|
+
}
|
|
711
|
+
const wires = [];
|
|
712
|
+
const junctions = [];
|
|
713
|
+
for (const schematicTrace of schematicTraces) {
|
|
714
|
+
for (const edge of schematicTrace.edges) {
|
|
715
|
+
const wire = this.createWireFromEdge(edge);
|
|
716
|
+
wires.push(wire);
|
|
717
|
+
}
|
|
718
|
+
for (const junction of schematicTrace.junctions) {
|
|
719
|
+
const kicadJunction = this.createJunction(junction);
|
|
720
|
+
junctions.push(kicadJunction);
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
kicadSch.wires = wires;
|
|
724
|
+
kicadSch.junctions = junctions;
|
|
725
|
+
this.finished = true;
|
|
726
|
+
}
|
|
727
|
+
/**
|
|
728
|
+
* Create a KiCad wire from a schematic trace edge
|
|
729
|
+
*/
|
|
730
|
+
createWireFromEdge(edge) {
|
|
731
|
+
const wire = new Wire();
|
|
732
|
+
if (!this.ctx.c2kMatSch) {
|
|
733
|
+
throw new Error(
|
|
734
|
+
"Schematic transformation matrix not initialized in context"
|
|
735
|
+
);
|
|
736
|
+
}
|
|
737
|
+
const from = applyToPoint2(this.ctx.c2kMatSch, {
|
|
738
|
+
x: edge.from.x,
|
|
739
|
+
y: edge.from.y
|
|
740
|
+
});
|
|
741
|
+
const to = applyToPoint2(this.ctx.c2kMatSch, {
|
|
742
|
+
x: edge.to.x,
|
|
743
|
+
y: edge.to.y
|
|
744
|
+
});
|
|
745
|
+
const x1 = from.x;
|
|
746
|
+
const y1 = from.y;
|
|
747
|
+
const x2 = to.x;
|
|
748
|
+
const y2 = to.y;
|
|
749
|
+
const pts = new Pts2([new Xy2(x1, y1), new Xy2(x2, y2)]);
|
|
750
|
+
wire.points = pts;
|
|
751
|
+
const stroke = new Stroke2();
|
|
752
|
+
stroke.width = 0;
|
|
753
|
+
stroke.type = "default";
|
|
754
|
+
wire.stroke = stroke;
|
|
755
|
+
wire.uuid = crypto.randomUUID();
|
|
756
|
+
return wire;
|
|
757
|
+
}
|
|
758
|
+
/**
|
|
759
|
+
* Create a KiCad junction from a circuit-json junction point
|
|
760
|
+
*/
|
|
761
|
+
createJunction(junction) {
|
|
762
|
+
if (!this.ctx.c2kMatSch) {
|
|
763
|
+
throw new Error(
|
|
764
|
+
"Schematic transformation matrix not initialized in context"
|
|
765
|
+
);
|
|
766
|
+
}
|
|
767
|
+
const { x, y } = applyToPoint2(this.ctx.c2kMatSch, {
|
|
768
|
+
x: junction.x,
|
|
769
|
+
y: junction.y
|
|
770
|
+
});
|
|
771
|
+
const kicadJunction = new Junction({
|
|
772
|
+
at: [x, y, 0],
|
|
773
|
+
diameter: 0
|
|
774
|
+
// 0 means use default diameter
|
|
775
|
+
});
|
|
776
|
+
kicadJunction.uuid = crypto.randomUUID();
|
|
777
|
+
return kicadJunction;
|
|
778
|
+
}
|
|
779
|
+
getOutput() {
|
|
780
|
+
return this.ctx.kicadSch;
|
|
781
|
+
}
|
|
782
|
+
};
|
|
783
|
+
|
|
784
|
+
// lib/schematic/stages/AddSheetInstancesStage.ts
|
|
785
|
+
import {
|
|
786
|
+
SheetInstances,
|
|
787
|
+
SheetInstancesRootPath,
|
|
788
|
+
SheetInstancesRootPage,
|
|
789
|
+
EmbeddedFonts as EmbeddedFonts3
|
|
790
|
+
} from "kicadts";
|
|
791
|
+
var AddSheetInstancesStage = class extends ConverterStage {
|
|
792
|
+
_step() {
|
|
793
|
+
const { kicadSch } = this.ctx;
|
|
794
|
+
if (!kicadSch) {
|
|
795
|
+
throw new Error("KicadSch instance not initialized in context");
|
|
796
|
+
}
|
|
797
|
+
const sheetInstances = new SheetInstances();
|
|
798
|
+
const path = new SheetInstancesRootPath();
|
|
799
|
+
path.value = "/";
|
|
800
|
+
const page = new SheetInstancesRootPage("1");
|
|
801
|
+
path.pages = [page];
|
|
802
|
+
sheetInstances.paths = [path];
|
|
803
|
+
kicadSch.sheetInstances = sheetInstances;
|
|
804
|
+
kicadSch.embeddedFonts = new EmbeddedFonts3(false);
|
|
805
|
+
this.finished = true;
|
|
806
|
+
}
|
|
807
|
+
getOutput() {
|
|
808
|
+
return this.ctx.kicadSch;
|
|
809
|
+
}
|
|
810
|
+
};
|
|
811
|
+
|
|
812
|
+
// lib/schematic/CircuitJsonToKicadSchConverter.ts
|
|
813
|
+
var CircuitJsonToKicadSchConverter = class {
|
|
814
|
+
ctx;
|
|
815
|
+
pipeline;
|
|
816
|
+
currentStageIndex = 0;
|
|
817
|
+
finished = false;
|
|
818
|
+
get currentStage() {
|
|
819
|
+
return this.pipeline[this.currentStageIndex];
|
|
820
|
+
}
|
|
821
|
+
constructor(circuitJson) {
|
|
822
|
+
const CIRCUIT_JSON_SCALE_FACTOR = 15;
|
|
823
|
+
const KICAD_CENTER_X = 95.25;
|
|
824
|
+
const KICAD_CENTER_Y = 73.66;
|
|
825
|
+
this.ctx = {
|
|
826
|
+
db: cju(circuitJson),
|
|
827
|
+
circuitJson,
|
|
828
|
+
kicadSch: new KicadSch({
|
|
829
|
+
generator: "circuit-json-to-kicad",
|
|
830
|
+
generatorVersion: "0.0.1"
|
|
831
|
+
}),
|
|
832
|
+
c2kMatSch: compose(
|
|
833
|
+
translate(KICAD_CENTER_X, KICAD_CENTER_Y),
|
|
834
|
+
scale(CIRCUIT_JSON_SCALE_FACTOR, -CIRCUIT_JSON_SCALE_FACTOR)
|
|
835
|
+
)
|
|
836
|
+
};
|
|
837
|
+
this.pipeline = [
|
|
838
|
+
new InitializeSchematicStage(circuitJson, this.ctx),
|
|
839
|
+
new AddLibrarySymbolsStage(circuitJson, this.ctx),
|
|
840
|
+
new AddSchematicSymbolsStage(circuitJson, this.ctx),
|
|
841
|
+
new AddSchematicTracesStage(circuitJson, this.ctx),
|
|
842
|
+
new AddSheetInstancesStage(circuitJson, this.ctx)
|
|
843
|
+
];
|
|
844
|
+
}
|
|
845
|
+
step() {
|
|
846
|
+
if (!this.currentStage) {
|
|
847
|
+
this.finished = true;
|
|
848
|
+
return;
|
|
849
|
+
}
|
|
850
|
+
this.currentStage.step();
|
|
851
|
+
if (this.currentStage.finished) {
|
|
852
|
+
this.currentStageIndex++;
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
runUntilFinished() {
|
|
856
|
+
while (!this.finished) {
|
|
857
|
+
this.step();
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
getOutput() {
|
|
861
|
+
return this.ctx.kicadSch;
|
|
862
|
+
}
|
|
863
|
+
/**
|
|
864
|
+
* Get the output as a string
|
|
865
|
+
*/
|
|
866
|
+
getOutputString() {
|
|
867
|
+
return this.ctx.kicadSch.getString();
|
|
868
|
+
}
|
|
869
|
+
};
|
|
870
|
+
|
|
871
|
+
// lib/pcb/CircuitJsonToKicadPcbConverter.ts
|
|
872
|
+
import { KicadPcb } from "kicadts";
|
|
873
|
+
import { cju as cju2 } from "@tscircuit/circuit-json-util";
|
|
874
|
+
import { compose as compose2, translate as translate2, scale as scale2 } from "transformation-matrix";
|
|
875
|
+
|
|
876
|
+
// lib/pcb/stages/InitializePcbStage.ts
|
|
877
|
+
import {
|
|
878
|
+
Paper as Paper2,
|
|
879
|
+
PcbLayers,
|
|
880
|
+
PcbLayerDefinition,
|
|
881
|
+
PcbGeneral,
|
|
882
|
+
Setup
|
|
883
|
+
} from "kicadts";
|
|
884
|
+
var InitializePcbStage = class extends ConverterStage {
|
|
885
|
+
_step() {
|
|
886
|
+
const { kicadPcb } = this.ctx;
|
|
887
|
+
if (!kicadPcb) {
|
|
888
|
+
throw new Error("KicadPcb instance not initialized in context");
|
|
889
|
+
}
|
|
890
|
+
kicadPcb.version = 20241229;
|
|
891
|
+
const paper = new Paper2();
|
|
892
|
+
paper.size = "A4";
|
|
893
|
+
kicadPcb.paper = paper;
|
|
894
|
+
const general = new PcbGeneral();
|
|
895
|
+
general.thickness = 1.6;
|
|
896
|
+
kicadPcb.general = general;
|
|
897
|
+
const setup = new Setup();
|
|
898
|
+
setup.padToMaskClearance = 0;
|
|
899
|
+
kicadPcb.setup = setup;
|
|
900
|
+
const layers = new PcbLayers();
|
|
901
|
+
const layerDefinitions = [
|
|
902
|
+
// Copper layers (standard KiCad indices)
|
|
903
|
+
new PcbLayerDefinition({ index: 0, name: "F.Cu", type: "signal" }),
|
|
904
|
+
new PcbLayerDefinition({ index: 2, name: "B.Cu", type: "signal" }),
|
|
905
|
+
// Technical layers
|
|
906
|
+
new PcbLayerDefinition({ index: 9, name: "F.Adhes", type: "user" }),
|
|
907
|
+
new PcbLayerDefinition({ index: 11, name: "B.Adhes", type: "user" }),
|
|
908
|
+
new PcbLayerDefinition({ index: 13, name: "F.Paste", type: "user" }),
|
|
909
|
+
new PcbLayerDefinition({ index: 15, name: "B.Paste", type: "user" }),
|
|
910
|
+
new PcbLayerDefinition({ index: 5, name: "F.SilkS", type: "user" }),
|
|
911
|
+
new PcbLayerDefinition({ index: 7, name: "B.SilkS", type: "user" }),
|
|
912
|
+
new PcbLayerDefinition({ index: 1, name: "F.Mask", type: "user" }),
|
|
913
|
+
new PcbLayerDefinition({ index: 3, name: "B.Mask", type: "user" }),
|
|
914
|
+
// Drawing layers
|
|
915
|
+
new PcbLayerDefinition({ index: 20, name: "Dwgs.User", type: "user" }),
|
|
916
|
+
new PcbLayerDefinition({ index: 21, name: "Cmts.User", type: "user" }),
|
|
917
|
+
new PcbLayerDefinition({ index: 22, name: "Eco1.User", type: "user" }),
|
|
918
|
+
new PcbLayerDefinition({ index: 23, name: "Eco2.User", type: "user" }),
|
|
919
|
+
new PcbLayerDefinition({ index: 24, name: "Edge.Cuts", type: "user" }),
|
|
920
|
+
new PcbLayerDefinition({ index: 25, name: "Margin", type: "user" }),
|
|
921
|
+
// Fabrication layers
|
|
922
|
+
new PcbLayerDefinition({ index: 17, name: "B.CrtYd", type: "user" }),
|
|
923
|
+
new PcbLayerDefinition({ index: 16, name: "F.CrtYd", type: "user" }),
|
|
924
|
+
new PcbLayerDefinition({ index: 19, name: "B.Fab", type: "user" }),
|
|
925
|
+
new PcbLayerDefinition({ index: 18, name: "F.Fab", type: "user" })
|
|
926
|
+
];
|
|
927
|
+
layers.definitions = layerDefinitions;
|
|
928
|
+
kicadPcb.layers = layers;
|
|
929
|
+
this.finished = true;
|
|
930
|
+
}
|
|
931
|
+
getOutput() {
|
|
932
|
+
return this.ctx.kicadPcb;
|
|
933
|
+
}
|
|
934
|
+
};
|
|
935
|
+
|
|
936
|
+
// lib/pcb/stages/AddNetsStage.ts
|
|
937
|
+
import { PcbNet } from "kicadts";
|
|
938
|
+
var AddNetsStage = class extends ConverterStage {
|
|
939
|
+
_step() {
|
|
940
|
+
const { kicadPcb } = this.ctx;
|
|
941
|
+
if (!kicadPcb) {
|
|
942
|
+
throw new Error("KicadPcb instance not initialized in context");
|
|
943
|
+
}
|
|
944
|
+
if (!this.ctx.pcbNetMap) {
|
|
945
|
+
this.ctx.pcbNetMap = /* @__PURE__ */ new Map();
|
|
946
|
+
}
|
|
947
|
+
const pcbTraces = this.ctx.db.pcb_trace.list();
|
|
948
|
+
const netNames = /* @__PURE__ */ new Set();
|
|
949
|
+
netNames.add("GND");
|
|
950
|
+
for (const trace of pcbTraces) {
|
|
951
|
+
if (trace.route && trace.route.length > 0) {
|
|
952
|
+
const netName = `Net-${trace.pcb_trace_id}`;
|
|
953
|
+
netNames.add(netName);
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
let netNumber = 0;
|
|
957
|
+
for (const netName of Array.from(netNames).sort()) {
|
|
958
|
+
const net = new PcbNet(netNumber, netName);
|
|
959
|
+
const nets = kicadPcb.nets;
|
|
960
|
+
nets.push(net);
|
|
961
|
+
kicadPcb.nets = nets;
|
|
962
|
+
this.ctx.pcbNetMap.set(netName, netNumber);
|
|
963
|
+
netNumber++;
|
|
964
|
+
}
|
|
965
|
+
this.finished = true;
|
|
966
|
+
}
|
|
967
|
+
getOutput() {
|
|
968
|
+
return this.ctx.kicadPcb;
|
|
969
|
+
}
|
|
970
|
+
};
|
|
971
|
+
|
|
972
|
+
// lib/pcb/stages/AddFootprintsStage.ts
|
|
973
|
+
import { Footprint, FpText, FootprintPad } from "kicadts";
|
|
974
|
+
import { applyToPoint as applyToPoint3 } from "transformation-matrix";
|
|
975
|
+
var AddFootprintsStage = class extends ConverterStage {
|
|
976
|
+
componentsProcessed = 0;
|
|
977
|
+
pcbComponents = [];
|
|
978
|
+
constructor(input, ctx) {
|
|
979
|
+
super(input, ctx);
|
|
980
|
+
this.pcbComponents = this.ctx.db.pcb_component.list();
|
|
981
|
+
}
|
|
982
|
+
_step() {
|
|
983
|
+
const { kicadPcb, c2kMatPcb } = this.ctx;
|
|
984
|
+
if (!kicadPcb) {
|
|
985
|
+
throw new Error("KicadPcb instance not initialized in context");
|
|
986
|
+
}
|
|
987
|
+
if (!c2kMatPcb) {
|
|
988
|
+
throw new Error("PCB transformation matrix not initialized in context");
|
|
989
|
+
}
|
|
990
|
+
if (this.componentsProcessed >= this.pcbComponents.length) {
|
|
991
|
+
this.finished = true;
|
|
992
|
+
return;
|
|
993
|
+
}
|
|
994
|
+
const component = this.pcbComponents[this.componentsProcessed];
|
|
995
|
+
const sourceComponent = component.source_component_id ? this.ctx.db.source_component.get(component.source_component_id) : null;
|
|
996
|
+
const footprintName = sourceComponent?.ftype || "Unknown";
|
|
997
|
+
const componentName = sourceComponent?.name || `Component_${this.componentsProcessed}`;
|
|
998
|
+
const transformedPos = applyToPoint3(c2kMatPcb, {
|
|
999
|
+
x: component.center.x,
|
|
1000
|
+
y: component.center.y
|
|
1001
|
+
});
|
|
1002
|
+
const footprint = new Footprint({
|
|
1003
|
+
libraryLink: `tscircuit:${footprintName}`,
|
|
1004
|
+
layer: "F.Cu",
|
|
1005
|
+
at: [transformedPos.x, transformedPos.y, component.rotation || 0],
|
|
1006
|
+
uuid: crypto.randomUUID()
|
|
1007
|
+
});
|
|
1008
|
+
const refText = new FpText({
|
|
1009
|
+
type: "reference",
|
|
1010
|
+
text: componentName,
|
|
1011
|
+
position: [0, -1.5, 0],
|
|
1012
|
+
layer: "F.SilkS",
|
|
1013
|
+
uuid: crypto.randomUUID()
|
|
1014
|
+
});
|
|
1015
|
+
const valueText = new FpText({
|
|
1016
|
+
type: "value",
|
|
1017
|
+
text: footprintName,
|
|
1018
|
+
position: [0, 1.5, 0],
|
|
1019
|
+
layer: "F.Fab",
|
|
1020
|
+
uuid: crypto.randomUUID()
|
|
1021
|
+
});
|
|
1022
|
+
const fpTexts = footprint.fpTexts;
|
|
1023
|
+
fpTexts.push(refText);
|
|
1024
|
+
fpTexts.push(valueText);
|
|
1025
|
+
footprint.fpTexts = fpTexts;
|
|
1026
|
+
const pcbPads = this.ctx.db.pcb_smtpad?.list().filter(
|
|
1027
|
+
(pad) => pad.pcb_component_id === component.pcb_component_id
|
|
1028
|
+
) || [];
|
|
1029
|
+
const fpPads = footprint.fpPads;
|
|
1030
|
+
let padNumber = 1;
|
|
1031
|
+
for (const pcbPad of pcbPads) {
|
|
1032
|
+
if (!("x" in pcbPad && "y" in pcbPad)) {
|
|
1033
|
+
throw new Error("no support for polygon pads (or any pads w/o X/Y) yet");
|
|
1034
|
+
}
|
|
1035
|
+
const relativeX = pcbPad.x - component.center.x;
|
|
1036
|
+
const relativeY = pcbPad.y - component.center.y;
|
|
1037
|
+
const layerMap = {
|
|
1038
|
+
top: "F.Cu",
|
|
1039
|
+
bottom: "B.Cu"
|
|
1040
|
+
};
|
|
1041
|
+
const padLayer = layerMap[pcbPad.layer] || "F.Cu";
|
|
1042
|
+
const padShape = pcbPad.shape === "circle" ? "circle" : "rect";
|
|
1043
|
+
const padSize = pcbPad.shape === "circle" ? [
|
|
1044
|
+
"radius" in pcbPad ? pcbPad.radius * 2 : 0.5,
|
|
1045
|
+
"radius" in pcbPad ? pcbPad.radius * 2 : 0.5
|
|
1046
|
+
] : [
|
|
1047
|
+
"width" in pcbPad ? pcbPad.width : 0.5,
|
|
1048
|
+
"height" in pcbPad ? pcbPad.height : 0.5
|
|
1049
|
+
];
|
|
1050
|
+
const pad = new FootprintPad({
|
|
1051
|
+
number: String(padNumber),
|
|
1052
|
+
padType: "smd",
|
|
1053
|
+
shape: padShape,
|
|
1054
|
+
at: [relativeX, relativeY, 0],
|
|
1055
|
+
size: padSize,
|
|
1056
|
+
layers: [
|
|
1057
|
+
`${padLayer}`,
|
|
1058
|
+
`${padLayer === "F.Cu" ? "F" : "B"}.Paste`,
|
|
1059
|
+
`${padLayer === "F.Cu" ? "F" : "B"}.Mask`
|
|
1060
|
+
],
|
|
1061
|
+
uuid: crypto.randomUUID()
|
|
1062
|
+
});
|
|
1063
|
+
fpPads.push(pad);
|
|
1064
|
+
padNumber++;
|
|
1065
|
+
}
|
|
1066
|
+
footprint.fpPads = fpPads;
|
|
1067
|
+
const footprints = kicadPcb.footprints;
|
|
1068
|
+
footprints.push(footprint);
|
|
1069
|
+
kicadPcb.footprints = footprints;
|
|
1070
|
+
this.componentsProcessed++;
|
|
1071
|
+
}
|
|
1072
|
+
getOutput() {
|
|
1073
|
+
return this.ctx.kicadPcb;
|
|
1074
|
+
}
|
|
1075
|
+
};
|
|
1076
|
+
|
|
1077
|
+
// lib/pcb/stages/AddTracesStage.ts
|
|
1078
|
+
import { Segment, SegmentNet } from "kicadts";
|
|
1079
|
+
import { applyToPoint as applyToPoint4 } from "transformation-matrix";
|
|
1080
|
+
var AddTracesStage = class extends ConverterStage {
|
|
1081
|
+
tracesProcessed = 0;
|
|
1082
|
+
pcbTraces = [];
|
|
1083
|
+
constructor(input, ctx) {
|
|
1084
|
+
super(input, ctx);
|
|
1085
|
+
this.pcbTraces = this.ctx.db.pcb_trace.list();
|
|
1086
|
+
}
|
|
1087
|
+
_step() {
|
|
1088
|
+
const { kicadPcb, c2kMatPcb, pcbNetMap } = this.ctx;
|
|
1089
|
+
if (!kicadPcb) {
|
|
1090
|
+
throw new Error("KicadPcb instance not initialized in context");
|
|
1091
|
+
}
|
|
1092
|
+
if (!c2kMatPcb) {
|
|
1093
|
+
throw new Error("PCB transformation matrix not initialized in context");
|
|
1094
|
+
}
|
|
1095
|
+
if (this.tracesProcessed >= this.pcbTraces.length) {
|
|
1096
|
+
this.finished = true;
|
|
1097
|
+
return;
|
|
1098
|
+
}
|
|
1099
|
+
const trace = this.pcbTraces[this.tracesProcessed];
|
|
1100
|
+
if (!trace.route || trace.route.length < 2) {
|
|
1101
|
+
this.tracesProcessed++;
|
|
1102
|
+
return;
|
|
1103
|
+
}
|
|
1104
|
+
for (let i = 0; i < trace.route.length - 1; i++) {
|
|
1105
|
+
const startPoint = trace.route[i];
|
|
1106
|
+
const endPoint = trace.route[i + 1];
|
|
1107
|
+
const transformedStart = applyToPoint4(c2kMatPcb, {
|
|
1108
|
+
x: startPoint.x,
|
|
1109
|
+
y: startPoint.y
|
|
1110
|
+
});
|
|
1111
|
+
const transformedEnd = applyToPoint4(c2kMatPcb, {
|
|
1112
|
+
x: endPoint.x,
|
|
1113
|
+
y: endPoint.y
|
|
1114
|
+
});
|
|
1115
|
+
let netNumber = 0;
|
|
1116
|
+
if (pcbNetMap) {
|
|
1117
|
+
const netName = `Net-${trace.pcb_trace_id}`;
|
|
1118
|
+
netNumber = pcbNetMap.get(netName) ?? 0;
|
|
1119
|
+
}
|
|
1120
|
+
const layerMap = {
|
|
1121
|
+
top: "F.Cu",
|
|
1122
|
+
bottom: "B.Cu"
|
|
1123
|
+
};
|
|
1124
|
+
const kicadLayer = layerMap[startPoint.layer] || startPoint.layer || "F.Cu";
|
|
1125
|
+
const segment = new Segment({
|
|
1126
|
+
start: { x: transformedStart.x, y: transformedStart.y },
|
|
1127
|
+
end: { x: transformedEnd.x, y: transformedEnd.y },
|
|
1128
|
+
layer: kicadLayer,
|
|
1129
|
+
width: trace.width || 0.25,
|
|
1130
|
+
net: new SegmentNet(netNumber)
|
|
1131
|
+
});
|
|
1132
|
+
const segments = kicadPcb.segments;
|
|
1133
|
+
segments.push(segment);
|
|
1134
|
+
kicadPcb.segments = segments;
|
|
1135
|
+
}
|
|
1136
|
+
this.tracesProcessed++;
|
|
1137
|
+
}
|
|
1138
|
+
getOutput() {
|
|
1139
|
+
return this.ctx.kicadPcb;
|
|
1140
|
+
}
|
|
1141
|
+
};
|
|
1142
|
+
|
|
1143
|
+
// lib/pcb/stages/AddViasStage.ts
|
|
1144
|
+
import { Via, ViaNet } from "kicadts";
|
|
1145
|
+
import { applyToPoint as applyToPoint5 } from "transformation-matrix";
|
|
1146
|
+
var AddViasStage = class extends ConverterStage {
|
|
1147
|
+
viasProcessed = 0;
|
|
1148
|
+
pcbVias = [];
|
|
1149
|
+
constructor(input, ctx) {
|
|
1150
|
+
super(input, ctx);
|
|
1151
|
+
this.pcbVias = this.ctx.db.pcb_via?.list() || [];
|
|
1152
|
+
}
|
|
1153
|
+
_step() {
|
|
1154
|
+
const { kicadPcb, c2kMatPcb, pcbNetMap } = this.ctx;
|
|
1155
|
+
if (!kicadPcb) {
|
|
1156
|
+
throw new Error("KicadPcb instance not initialized in context");
|
|
1157
|
+
}
|
|
1158
|
+
if (!c2kMatPcb) {
|
|
1159
|
+
throw new Error("PCB transformation matrix not initialized in context");
|
|
1160
|
+
}
|
|
1161
|
+
if (this.viasProcessed >= this.pcbVias.length) {
|
|
1162
|
+
this.finished = true;
|
|
1163
|
+
return;
|
|
1164
|
+
}
|
|
1165
|
+
const via = this.pcbVias[this.viasProcessed];
|
|
1166
|
+
const transformedPos = applyToPoint5(c2kMatPcb, {
|
|
1167
|
+
x: via.x,
|
|
1168
|
+
y: via.y
|
|
1169
|
+
});
|
|
1170
|
+
let netNumber = 0;
|
|
1171
|
+
if (pcbNetMap && via.net_name) {
|
|
1172
|
+
netNumber = pcbNetMap.get(via.net_name) ?? 0;
|
|
1173
|
+
}
|
|
1174
|
+
const kicadVia = new Via({
|
|
1175
|
+
at: [transformedPos.x, transformedPos.y],
|
|
1176
|
+
size: via.outer_diameter || 0.8,
|
|
1177
|
+
drill: via.hole_diameter || 0.4,
|
|
1178
|
+
layers: ["F.Cu", "B.Cu"],
|
|
1179
|
+
net: new ViaNet(netNumber)
|
|
1180
|
+
});
|
|
1181
|
+
const vias = kicadPcb.vias;
|
|
1182
|
+
vias.push(kicadVia);
|
|
1183
|
+
kicadPcb.vias = vias;
|
|
1184
|
+
this.viasProcessed++;
|
|
1185
|
+
}
|
|
1186
|
+
getOutput() {
|
|
1187
|
+
return this.ctx.kicadPcb;
|
|
1188
|
+
}
|
|
1189
|
+
};
|
|
1190
|
+
|
|
1191
|
+
// lib/pcb/stages/AddGraphicsStage.ts
|
|
1192
|
+
import { GrLine } from "kicadts";
|
|
1193
|
+
import { applyToPoint as applyToPoint6 } from "transformation-matrix";
|
|
1194
|
+
var AddGraphicsStage = class extends ConverterStage {
|
|
1195
|
+
_step() {
|
|
1196
|
+
const { kicadPcb, c2kMatPcb } = this.ctx;
|
|
1197
|
+
if (!kicadPcb) {
|
|
1198
|
+
throw new Error("KicadPcb instance not initialized in context");
|
|
1199
|
+
}
|
|
1200
|
+
if (!c2kMatPcb) {
|
|
1201
|
+
throw new Error("PCB transformation matrix not initialized in context");
|
|
1202
|
+
}
|
|
1203
|
+
const pcbSilkscreenPaths = this.ctx.db.pcb_silkscreen_path?.list() || [];
|
|
1204
|
+
for (const path of pcbSilkscreenPaths) {
|
|
1205
|
+
if (!path.route || path.route.length < 2) continue;
|
|
1206
|
+
for (let i = 0; i < path.route.length - 1; i++) {
|
|
1207
|
+
const startPoint = path.route[i];
|
|
1208
|
+
const endPoint = path.route[i + 1];
|
|
1209
|
+
if (!startPoint || !endPoint) continue;
|
|
1210
|
+
const transformedStart = applyToPoint6(c2kMatPcb, {
|
|
1211
|
+
x: startPoint.x,
|
|
1212
|
+
y: startPoint.y
|
|
1213
|
+
});
|
|
1214
|
+
const transformedEnd = applyToPoint6(c2kMatPcb, {
|
|
1215
|
+
x: endPoint.x,
|
|
1216
|
+
y: endPoint.y
|
|
1217
|
+
});
|
|
1218
|
+
const layerMap = {
|
|
1219
|
+
top: "F.SilkS",
|
|
1220
|
+
bottom: "B.SilkS"
|
|
1221
|
+
};
|
|
1222
|
+
const kicadLayer = layerMap[path.layer] || path.layer || "F.SilkS";
|
|
1223
|
+
const grLine = new GrLine({
|
|
1224
|
+
start: { x: transformedStart.x, y: transformedStart.y },
|
|
1225
|
+
end: { x: transformedEnd.x, y: transformedEnd.y },
|
|
1226
|
+
layer: kicadLayer,
|
|
1227
|
+
width: path.stroke_width || 0.15
|
|
1228
|
+
});
|
|
1229
|
+
const graphicLines = kicadPcb.graphicLines;
|
|
1230
|
+
graphicLines.push(grLine);
|
|
1231
|
+
kicadPcb.graphicLines = graphicLines;
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
const pcbBoards = this.ctx.db.pcb_board?.list() || [];
|
|
1235
|
+
if (pcbBoards.length > 0) {
|
|
1236
|
+
const board = pcbBoards[0];
|
|
1237
|
+
if (!board) {
|
|
1238
|
+
this.finished = true;
|
|
1239
|
+
return;
|
|
1240
|
+
}
|
|
1241
|
+
let corners;
|
|
1242
|
+
if (board.outline && board.outline.length > 0) {
|
|
1243
|
+
corners = board.outline;
|
|
1244
|
+
} else {
|
|
1245
|
+
const halfWidth = board.width / 2;
|
|
1246
|
+
const halfHeight = board.height / 2;
|
|
1247
|
+
corners = [
|
|
1248
|
+
{ x: board.center.x - halfWidth, y: board.center.y - halfHeight },
|
|
1249
|
+
{ x: board.center.x + halfWidth, y: board.center.y - halfHeight },
|
|
1250
|
+
{ x: board.center.x + halfWidth, y: board.center.y + halfHeight },
|
|
1251
|
+
{ x: board.center.x - halfWidth, y: board.center.y + halfHeight }
|
|
1252
|
+
];
|
|
1253
|
+
}
|
|
1254
|
+
const transformedCorners = corners.map(
|
|
1255
|
+
(corner) => applyToPoint6(c2kMatPcb, corner)
|
|
1256
|
+
);
|
|
1257
|
+
for (let i = 0; i < transformedCorners.length; i++) {
|
|
1258
|
+
const start = transformedCorners[i];
|
|
1259
|
+
const end = transformedCorners[(i + 1) % transformedCorners.length];
|
|
1260
|
+
if (!start || !end) continue;
|
|
1261
|
+
const edgeLine = new GrLine({
|
|
1262
|
+
start: { x: start.x, y: start.y },
|
|
1263
|
+
end: { x: end.x, y: end.y },
|
|
1264
|
+
layer: "Edge.Cuts",
|
|
1265
|
+
width: 0.1
|
|
1266
|
+
});
|
|
1267
|
+
const graphicLines = kicadPcb.graphicLines;
|
|
1268
|
+
graphicLines.push(edgeLine);
|
|
1269
|
+
kicadPcb.graphicLines = graphicLines;
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
this.finished = true;
|
|
1273
|
+
}
|
|
1274
|
+
getOutput() {
|
|
1275
|
+
return this.ctx.kicadPcb;
|
|
1276
|
+
}
|
|
1277
|
+
};
|
|
1278
|
+
|
|
1279
|
+
// lib/pcb/CircuitJsonToKicadPcbConverter.ts
|
|
1280
|
+
var CircuitJsonToKicadPcbConverter = class {
|
|
1281
|
+
ctx;
|
|
1282
|
+
pipeline;
|
|
1283
|
+
currentStageIndex = 0;
|
|
1284
|
+
finished = false;
|
|
1285
|
+
get currentStage() {
|
|
1286
|
+
return this.pipeline[this.currentStageIndex];
|
|
1287
|
+
}
|
|
1288
|
+
constructor(circuitJson) {
|
|
1289
|
+
const CIRCUIT_JSON_TO_MM_SCALE = 1;
|
|
1290
|
+
const KICAD_PCB_CENTER_X = 100;
|
|
1291
|
+
const KICAD_PCB_CENTER_Y = 100;
|
|
1292
|
+
this.ctx = {
|
|
1293
|
+
db: cju2(circuitJson),
|
|
1294
|
+
circuitJson,
|
|
1295
|
+
kicadPcb: new KicadPcb({
|
|
1296
|
+
generator: "circuit-json-to-kicad",
|
|
1297
|
+
generatorVersion: "0.0.1"
|
|
1298
|
+
}),
|
|
1299
|
+
c2kMatPcb: compose2(
|
|
1300
|
+
translate2(KICAD_PCB_CENTER_X, KICAD_PCB_CENTER_Y),
|
|
1301
|
+
scale2(CIRCUIT_JSON_TO_MM_SCALE, -CIRCUIT_JSON_TO_MM_SCALE)
|
|
1302
|
+
)
|
|
1303
|
+
};
|
|
1304
|
+
this.pipeline = [
|
|
1305
|
+
new InitializePcbStage(circuitJson, this.ctx),
|
|
1306
|
+
new AddNetsStage(circuitJson, this.ctx),
|
|
1307
|
+
new AddFootprintsStage(circuitJson, this.ctx),
|
|
1308
|
+
new AddTracesStage(circuitJson, this.ctx),
|
|
1309
|
+
new AddViasStage(circuitJson, this.ctx),
|
|
1310
|
+
new AddGraphicsStage(circuitJson, this.ctx)
|
|
1311
|
+
];
|
|
1312
|
+
}
|
|
1313
|
+
step() {
|
|
1314
|
+
if (!this.currentStage) {
|
|
1315
|
+
this.finished = true;
|
|
1316
|
+
return;
|
|
1317
|
+
}
|
|
1318
|
+
this.currentStage.step();
|
|
1319
|
+
if (this.currentStage.finished) {
|
|
1320
|
+
this.currentStageIndex++;
|
|
1321
|
+
}
|
|
1322
|
+
}
|
|
1323
|
+
runUntilFinished() {
|
|
1324
|
+
while (!this.finished) {
|
|
1325
|
+
this.step();
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
getOutput() {
|
|
1329
|
+
return this.ctx.kicadPcb;
|
|
1330
|
+
}
|
|
1331
|
+
/**
|
|
1332
|
+
* Get the output as a string
|
|
1333
|
+
*/
|
|
1334
|
+
getOutputString() {
|
|
1335
|
+
return this.ctx.kicadPcb.getString();
|
|
1336
|
+
}
|
|
1337
|
+
};
|
|
1338
|
+
export {
|
|
1339
|
+
CircuitJsonToKicadPcbConverter,
|
|
1340
|
+
CircuitJsonToKicadSchConverter
|
|
1341
|
+
};
|