kicad-to-circuit-json 0.0.78 → 0.0.80

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,11 +1,92 @@
1
+ import { AnyCircuitElement } from 'circuit-json';
1
2
  import { CircuitJsonUtilObjects } from '@tscircuit/circuit-json-util';
2
3
  import { KicadPcb, KicadSch } from 'kicadts';
3
4
  import { Matrix } from 'transformation-matrix';
4
5
 
6
+ interface KicadSymbolLibPinAlternate {
7
+ name: string;
8
+ electricalType?: string;
9
+ graphicStyle?: string;
10
+ }
11
+ interface KicadSymbolLibPin {
12
+ name: string;
13
+ number: string;
14
+ electricalType?: string;
15
+ graphicStyle?: string;
16
+ at?: {
17
+ x: number;
18
+ y: number;
19
+ angle: number;
20
+ };
21
+ length?: number;
22
+ hidden?: boolean;
23
+ alternates?: KicadSymbolLibPinAlternate[];
24
+ }
25
+ interface KicadSymbolLibPoint {
26
+ x: number;
27
+ y: number;
28
+ }
29
+ interface KicadSymbolLibStroke {
30
+ width?: number;
31
+ type?: string;
32
+ }
33
+ interface KicadSymbolLibFill {
34
+ type?: string;
35
+ }
36
+ interface KicadSymbolLibPolyline {
37
+ points: KicadSymbolLibPoint[];
38
+ stroke?: KicadSymbolLibStroke;
39
+ fill?: KicadSymbolLibFill;
40
+ }
41
+ interface KicadSymbolLibRectangle {
42
+ start: KicadSymbolLibPoint;
43
+ end: KicadSymbolLibPoint;
44
+ stroke?: KicadSymbolLibStroke;
45
+ fill?: KicadSymbolLibFill;
46
+ }
47
+ interface KicadSymbolLibCircle {
48
+ center: KicadSymbolLibPoint;
49
+ radius: number;
50
+ stroke?: KicadSymbolLibStroke;
51
+ fill?: KicadSymbolLibFill;
52
+ }
53
+ interface KicadSymbolLibArc {
54
+ start: KicadSymbolLibPoint;
55
+ mid: KicadSymbolLibPoint;
56
+ end: KicadSymbolLibPoint;
57
+ stroke?: KicadSymbolLibStroke;
58
+ }
59
+ interface KicadSymbolLibText {
60
+ text: string;
61
+ at: {
62
+ x: number;
63
+ y: number;
64
+ angle: number;
65
+ };
66
+ fontSize?: number;
67
+ }
68
+ interface KicadSymbolLibSymbol {
69
+ name: string;
70
+ properties: Record<string, string>;
71
+ pins: KicadSymbolLibPin[];
72
+ polylines: KicadSymbolLibPolyline[];
73
+ rectangles: KicadSymbolLibRectangle[];
74
+ circles: KicadSymbolLibCircle[];
75
+ arcs: KicadSymbolLibArc[];
76
+ texts: KicadSymbolLibText[];
77
+ subSymbols: KicadSymbolLibSymbol[];
78
+ }
79
+ interface KicadSymbolLib {
80
+ version?: string;
81
+ generator?: string;
82
+ generatorVersion?: string;
83
+ symbols: KicadSymbolLibSymbol[];
84
+ }
5
85
  interface ConverterContext {
6
86
  db: CircuitJsonUtilObjects;
7
87
  kicadPcb?: KicadPcb;
8
88
  kicadSch?: KicadSch;
89
+ kicadSymbolLib?: KicadSymbolLib;
9
90
  k2cMatSch?: Matrix;
10
91
  k2cMatPcb?: Matrix;
11
92
  netNumToName?: Map<number, string>;
@@ -56,7 +137,7 @@ declare class KicadToCircuitJsonConverter {
56
137
  initializePipeline(): void;
57
138
  step(): boolean;
58
139
  runUntilFinished(): void;
59
- getOutput(): any[];
140
+ getOutput(): AnyCircuitElement[];
60
141
  getOutputString(): string;
61
142
  getWarnings(): string[];
62
143
  getStats(): {
@@ -265,4 +346,51 @@ declare class CollectSourceTracesStage extends ConverterStage {
265
346
  private createSourceNet;
266
347
  }
267
348
 
268
- export { CollectFootprintsStage, CollectGraphicsStage, CollectLibrarySymbolsStage, CollectNetsStage, CollectSchematicTracesStage, CollectSourceTracesStage, CollectTracesStage, CollectViasStage, CollectZonesStage, type ConverterContext, ConverterStage, InitializePcbContextStage, InitializeSchematicContextStage, KicadToCircuitJsonConverter };
349
+ /**
350
+ * InitializeSymbolLibraryContextStage prepares shared state for .kicad_sym
351
+ * conversion. Symbol libraries have no schematic placements, so this branch
352
+ * emits source-level Circuit JSON only.
353
+ */
354
+ declare class InitializeSymbolLibraryContextStage extends ConverterStage {
355
+ step(): boolean;
356
+ }
357
+
358
+ /**
359
+ * CollectSymbolLibrarySymbolsStage converts KiCad symbol-library definitions
360
+ * into source-level Circuit JSON:
361
+ * - source_component for each top-level library symbol
362
+ * - source_port for each physical pin in the symbol definition
363
+ * - schematic_component and schematic_port preview geometry for snapshots
364
+ */
365
+ declare class CollectSymbolLibrarySymbolsStage extends ConverterStage {
366
+ private processedSymbols;
367
+ private previewIndex;
368
+ step(): boolean;
369
+ private processSymbol;
370
+ private getKicadSymbolExportFileName;
371
+ private collectPins;
372
+ private collectPolylines;
373
+ private collectRectangles;
374
+ private collectCircles;
375
+ private collectArcs;
376
+ private collectTexts;
377
+ private createSchematicPreview;
378
+ private getPreviewCenter;
379
+ private getPinBounds;
380
+ private getPreviewScale;
381
+ private createSchematicPrimitives;
382
+ private createPolylinePrimitives;
383
+ private toSchematicPoint;
384
+ private toStrokeWidth;
385
+ private isFilled;
386
+ private getArcGeometry;
387
+ private getAngleDegrees;
388
+ private getManufacturerPartNumber;
389
+ private createSourceComponentData;
390
+ private inferFtype;
391
+ private getPortName;
392
+ private getSourcePortPinMetadata;
393
+ private getSchematicPortPinMetadata;
394
+ }
395
+
396
+ export { CollectFootprintsStage, CollectGraphicsStage, CollectLibrarySymbolsStage, CollectNetsStage, CollectSchematicTracesStage, CollectSourceTracesStage, CollectSymbolLibrarySymbolsStage, CollectTracesStage, CollectViasStage, CollectZonesStage, type ConverterContext, ConverterStage, InitializePcbContextStage, InitializeSchematicContextStage, InitializeSymbolLibraryContextStage, KicadToCircuitJsonConverter };
package/dist/index.js CHANGED
@@ -2,6 +2,255 @@
2
2
  import { cju } from "@tscircuit/circuit-json-util";
3
3
  import { parseKicadPcb, parseKicadSch } from "kicadts";
4
4
 
5
+ // lib/parseKicadSymbolLib.ts
6
+ function parseKicadSymbolLib(content) {
7
+ const expressions = parseSExpr(content);
8
+ const root = expressions.find(
9
+ (expr) => isList(expr) && expr[0] === "kicad_symbol_lib"
10
+ );
11
+ if (!root) {
12
+ throw new Error("Expected kicad_symbol_lib root in .kicad_sym file");
13
+ }
14
+ return {
15
+ version: getChildScalar(root, "version"),
16
+ generator: getChildScalar(root, "generator"),
17
+ generatorVersion: getChildScalar(root, "generator_version"),
18
+ symbols: getChildLists(root, "symbol").map(parseSymbol)
19
+ };
20
+ }
21
+ function parseSExpr(content) {
22
+ const tokens = tokenize(content);
23
+ const expressions = [];
24
+ let index = 0;
25
+ function parseList() {
26
+ const list = [];
27
+ index++;
28
+ while (index < tokens.length && tokens[index] !== ")") {
29
+ if (tokens[index] === "(") {
30
+ list.push(parseList());
31
+ } else {
32
+ list.push(tokens[index]);
33
+ index++;
34
+ }
35
+ }
36
+ if (tokens[index] !== ")") {
37
+ throw new Error("Unterminated S-expression list");
38
+ }
39
+ index++;
40
+ return list;
41
+ }
42
+ while (index < tokens.length) {
43
+ const token = tokens[index];
44
+ if (token === "(") {
45
+ expressions.push(parseList());
46
+ } else if (token === ")") {
47
+ throw new Error("Unexpected ')' in S-expression");
48
+ } else if (token !== void 0) {
49
+ expressions.push(token);
50
+ index++;
51
+ }
52
+ }
53
+ return expressions;
54
+ }
55
+ function tokenize(content) {
56
+ const tokens = [];
57
+ let index = 0;
58
+ while (index < content.length) {
59
+ const char = content[index];
60
+ if (/\s/.test(char)) {
61
+ index++;
62
+ continue;
63
+ }
64
+ if (char === ";") {
65
+ while (index < content.length && content[index] !== "\n") index++;
66
+ continue;
67
+ }
68
+ if (char === "(" || char === ")") {
69
+ tokens.push(char);
70
+ index++;
71
+ continue;
72
+ }
73
+ if (char === '"') {
74
+ let value2 = "";
75
+ index++;
76
+ while (index < content.length) {
77
+ const current = content[index];
78
+ if (current === "\\") {
79
+ const next = content[index + 1];
80
+ if (next === void 0) {
81
+ throw new Error("Unterminated escape sequence in quoted string");
82
+ }
83
+ value2 += next;
84
+ index += 2;
85
+ continue;
86
+ }
87
+ if (current === '"') {
88
+ index++;
89
+ break;
90
+ }
91
+ value2 += current;
92
+ index++;
93
+ }
94
+ tokens.push(value2);
95
+ continue;
96
+ }
97
+ let value = "";
98
+ while (index < content.length && !/\s/.test(content[index]) && content[index] !== "(" && content[index] !== ")") {
99
+ value += content[index];
100
+ index++;
101
+ }
102
+ tokens.push(value);
103
+ }
104
+ return tokens;
105
+ }
106
+ function parseSymbol(expr) {
107
+ const name = getAtom(expr[1]) ?? "";
108
+ const subSymbols = getChildLists(expr, "symbol").map(parseSymbol);
109
+ const directPins = getChildLists(expr, "pin").map(parsePin);
110
+ return {
111
+ name,
112
+ properties: Object.fromEntries(
113
+ getChildLists(expr, "property").flatMap((property) => {
114
+ const key = getAtom(property[1]);
115
+ const value = getAtom(property[2]);
116
+ return key ? [[key, value ?? ""]] : [];
117
+ })
118
+ ),
119
+ pins: directPins,
120
+ polylines: getChildLists(expr, "polyline").map(parsePolyline),
121
+ rectangles: getChildLists(expr, "rectangle").map(parseRectangle),
122
+ circles: getChildLists(expr, "circle").map(parseCircle),
123
+ arcs: getChildLists(expr, "arc").map(parseArc),
124
+ texts: getChildLists(expr, "text").map(parseText),
125
+ subSymbols
126
+ };
127
+ }
128
+ function parsePin(expr) {
129
+ const at = getChildList(expr, "at");
130
+ const length = getChildScalar(expr, "length");
131
+ return {
132
+ electricalType: getAtom(expr[1]),
133
+ graphicStyle: getAtom(expr[2]),
134
+ at: at ? {
135
+ x: parseNumber(getAtom(at[1])),
136
+ y: parseNumber(getAtom(at[2])),
137
+ angle: parseNumber(getAtom(at[3]))
138
+ } : void 0,
139
+ length: length !== void 0 ? parseNumber(length) : void 0,
140
+ hidden: getChildScalar(expr, "hide") === "yes",
141
+ name: getChildScalar(expr, "name") ?? "",
142
+ number: getChildScalar(expr, "number") ?? "",
143
+ alternates: getChildLists(expr, "alternate").map(parseAlternate)
144
+ };
145
+ }
146
+ function parseAlternate(expr) {
147
+ return {
148
+ name: getAtom(expr[1]) ?? "",
149
+ electricalType: getAtom(expr[2]),
150
+ graphicStyle: getAtom(expr[3])
151
+ };
152
+ }
153
+ function parsePolyline(expr) {
154
+ const pts = getChildList(expr, "pts");
155
+ return {
156
+ points: pts ? getChildLists(pts, "xy").map(parseXy) : [],
157
+ stroke: parseStroke(getChildList(expr, "stroke")),
158
+ fill: parseFill(getChildList(expr, "fill"))
159
+ };
160
+ }
161
+ function parseRectangle(expr) {
162
+ return {
163
+ start: parsePoint(getChildList(expr, "start")),
164
+ end: parsePoint(getChildList(expr, "end")),
165
+ stroke: parseStroke(getChildList(expr, "stroke")),
166
+ fill: parseFill(getChildList(expr, "fill"))
167
+ };
168
+ }
169
+ function parseCircle(expr) {
170
+ return {
171
+ center: parsePoint(getChildList(expr, "center")),
172
+ radius: parseNumber(getChildScalar(expr, "radius")),
173
+ stroke: parseStroke(getChildList(expr, "stroke")),
174
+ fill: parseFill(getChildList(expr, "fill"))
175
+ };
176
+ }
177
+ function parseArc(expr) {
178
+ return {
179
+ start: parsePoint(getChildList(expr, "start")),
180
+ mid: parsePoint(getChildList(expr, "mid")),
181
+ end: parsePoint(getChildList(expr, "end")),
182
+ stroke: parseStroke(getChildList(expr, "stroke"))
183
+ };
184
+ }
185
+ function parseText(expr) {
186
+ const at = getChildList(expr, "at");
187
+ const effects = getChildList(expr, "effects");
188
+ const font = effects ? getChildList(effects, "font") : void 0;
189
+ const fontSize = font ? getChildList(font, "size") : void 0;
190
+ return {
191
+ text: getAtom(expr[1]) ?? "",
192
+ at: at ? {
193
+ x: parseNumber(getAtom(at[1])),
194
+ y: parseNumber(getAtom(at[2])),
195
+ angle: parseNumber(getAtom(at[3]))
196
+ } : { x: 0, y: 0, angle: 0 },
197
+ fontSize: fontSize ? Math.max(
198
+ parseNumber(getAtom(fontSize[1])),
199
+ parseNumber(getAtom(fontSize[2]))
200
+ ) : void 0
201
+ };
202
+ }
203
+ function parseStroke(expr) {
204
+ if (!expr) return {};
205
+ return {
206
+ width: parseNumber(getChildScalar(expr, "width")),
207
+ type: getChildScalar(expr, "type")
208
+ };
209
+ }
210
+ function parseFill(expr) {
211
+ if (!expr) return {};
212
+ return {
213
+ type: getChildScalar(expr, "type")
214
+ };
215
+ }
216
+ function parseXy(expr) {
217
+ return {
218
+ x: parseNumber(getAtom(expr[1])),
219
+ y: parseNumber(getAtom(expr[2]))
220
+ };
221
+ }
222
+ function parsePoint(expr) {
223
+ if (!expr) return { x: 0, y: 0 };
224
+ return {
225
+ x: parseNumber(getAtom(expr[1])),
226
+ y: parseNumber(getAtom(expr[2]))
227
+ };
228
+ }
229
+ function getChildScalar(expr, token) {
230
+ const child = getChildList(expr, token);
231
+ if (!child) return void 0;
232
+ return getAtom(child[1]);
233
+ }
234
+ function getChildList(expr, token) {
235
+ return getChildLists(expr, token)[0];
236
+ }
237
+ function getChildLists(expr, token) {
238
+ return expr.filter(
239
+ (child) => isList(child) && child[0] === token
240
+ );
241
+ }
242
+ function getAtom(expr) {
243
+ return typeof expr === "string" ? expr : void 0;
244
+ }
245
+ function isList(expr) {
246
+ return Array.isArray(expr);
247
+ }
248
+ function parseNumber(value) {
249
+ if (value === void 0) return 0;
250
+ const parsed = Number(value);
251
+ return Number.isFinite(parsed) ? parsed : 0;
252
+ }
253
+
5
254
  // lib/types.ts
6
255
  var ConverterStage = class {
7
256
  constructor(ctx) {
@@ -3671,6 +3920,411 @@ var InitializeSchematicContextStage = class extends ConverterStage {
3671
3920
  }
3672
3921
  };
3673
3922
 
3923
+ // lib/stages/symbol-library/CollectSymbolLibrarySymbolsStage.ts
3924
+ var MAX_KICAD_SYMBOL_UNIT_TO_CJ = 1;
3925
+ var PREVIEW_COLUMNS = 6;
3926
+ var PREVIEW_CELL_WIDTH = 10;
3927
+ var PREVIEW_CELL_HEIGHT = 9.5;
3928
+ var PREVIEW_CELL_FILL_RATIO = 0.95;
3929
+ var DEFAULT_STROKE_COLOR = "rgb(132, 0, 0)";
3930
+ var DEFAULT_FILL_COLOR = "rgb(255, 255, 194)";
3931
+ var CollectSymbolLibrarySymbolsStage = class extends ConverterStage {
3932
+ processedSymbols = /* @__PURE__ */ new Set();
3933
+ previewIndex = 0;
3934
+ step() {
3935
+ if (!this.ctx.kicadSymbolLib) {
3936
+ this.finished = true;
3937
+ return false;
3938
+ }
3939
+ const symbols = [...this.ctx.kicadSymbolLib.symbols].sort((a, b) => {
3940
+ const aFileName = this.getKicadSymbolExportFileName(a);
3941
+ const bFileName = this.getKicadSymbolExportFileName(b);
3942
+ return aFileName < bFileName ? -1 : aFileName > bFileName ? 1 : 0;
3943
+ });
3944
+ for (const symbol of symbols) {
3945
+ if (!symbol.name || this.processedSymbols.has(symbol.name)) continue;
3946
+ this.processSymbol(symbol);
3947
+ this.processedSymbols.add(symbol.name);
3948
+ }
3949
+ this.finished = true;
3950
+ return false;
3951
+ }
3952
+ processSymbol(symbol) {
3953
+ const sourceComponentData = this.createSourceComponentData(symbol);
3954
+ const sourceComponent = this.ctx.db.source_component.insert(sourceComponentData);
3955
+ const pins = this.collectPins(symbol);
3956
+ const seenPinNumbers = /* @__PURE__ */ new Set();
3957
+ let unnamedPinIndex = 0;
3958
+ const sourcePortIdByPinNumber = /* @__PURE__ */ new Map();
3959
+ for (const pin of pins) {
3960
+ const pinNumber = pin.number || `unnamed_${unnamedPinIndex++}`;
3961
+ if (seenPinNumbers.has(pinNumber)) continue;
3962
+ seenPinNumbers.add(pinNumber);
3963
+ const sourcePortData = {
3964
+ source_component_id: sourceComponent.source_component_id,
3965
+ name: this.getPortName(pin, pinNumber),
3966
+ ...this.getSourcePortPinMetadata(pinNumber)
3967
+ };
3968
+ const sourcePort = this.ctx.db.source_port.insert(sourcePortData);
3969
+ sourcePortIdByPinNumber.set(pinNumber, sourcePort.source_port_id);
3970
+ }
3971
+ this.createSchematicPreview({
3972
+ symbol,
3973
+ pins,
3974
+ sourceComponentId: sourceComponent.source_component_id,
3975
+ sourcePortIdByPinNumber
3976
+ });
3977
+ if (this.ctx.stats) {
3978
+ this.ctx.stats.components = (this.ctx.stats.components || 0) + 1;
3979
+ this.ctx.stats.pads = (this.ctx.stats.pads || 0) + seenPinNumbers.size;
3980
+ }
3981
+ }
3982
+ getKicadSymbolExportFileName(symbol) {
3983
+ return `${symbol.name}_unit1.svg`;
3984
+ }
3985
+ collectPins(symbol) {
3986
+ return [
3987
+ ...symbol.pins,
3988
+ ...symbol.subSymbols.flatMap((subSymbol) => this.collectPins(subSymbol))
3989
+ ];
3990
+ }
3991
+ collectPolylines(symbol) {
3992
+ return [
3993
+ ...symbol.polylines,
3994
+ ...symbol.subSymbols.flatMap(
3995
+ (subSymbol) => this.collectPolylines(subSymbol)
3996
+ )
3997
+ ];
3998
+ }
3999
+ collectRectangles(symbol) {
4000
+ return [
4001
+ ...symbol.rectangles,
4002
+ ...symbol.subSymbols.flatMap(
4003
+ (subSymbol) => this.collectRectangles(subSymbol)
4004
+ )
4005
+ ];
4006
+ }
4007
+ collectCircles(symbol) {
4008
+ return [
4009
+ ...symbol.circles,
4010
+ ...symbol.subSymbols.flatMap(
4011
+ (subSymbol) => this.collectCircles(subSymbol)
4012
+ )
4013
+ ];
4014
+ }
4015
+ collectArcs(symbol) {
4016
+ return [
4017
+ ...symbol.arcs,
4018
+ ...symbol.subSymbols.flatMap((subSymbol) => this.collectArcs(subSymbol))
4019
+ ];
4020
+ }
4021
+ collectTexts(symbol) {
4022
+ return [
4023
+ ...symbol.texts,
4024
+ ...symbol.subSymbols.flatMap((subSymbol) => this.collectTexts(subSymbol))
4025
+ ];
4026
+ }
4027
+ createSchematicPreview(params) {
4028
+ const { symbol, pins, sourceComponentId, sourcePortIdByPinNumber } = params;
4029
+ const bounds = this.getPinBounds(pins);
4030
+ const scale3 = this.getPreviewScale(bounds);
4031
+ const size = {
4032
+ width: Math.max(1, bounds.width * scale3),
4033
+ height: Math.max(1, bounds.height * scale3)
4034
+ };
4035
+ const center = this.getPreviewCenter();
4036
+ const schematicComponentData = {
4037
+ source_component_id: sourceComponentId,
4038
+ center,
4039
+ size,
4040
+ is_box_with_pins: false
4041
+ };
4042
+ const schematicComponent = this.ctx.db.schematic_component.insert(
4043
+ schematicComponentData
4044
+ );
4045
+ for (const pin of pins) {
4046
+ if (!pin.at) continue;
4047
+ const pinNumber = pin.number || "";
4048
+ const sourcePortId = sourcePortIdByPinNumber.get(pinNumber);
4049
+ if (!sourcePortId) continue;
4050
+ const schematicPortData = {
4051
+ schematic_component_id: schematicComponent.schematic_component_id,
4052
+ source_port_id: sourcePortId,
4053
+ center: this.toSchematicPoint(pin.at, center, scale3),
4054
+ facing_direction: rotationToDirection(pin.at.angle),
4055
+ ...this.getSchematicPortPinMetadata(pinNumber)
4056
+ };
4057
+ this.ctx.db.schematic_port.insert(schematicPortData);
4058
+ }
4059
+ this.createSchematicPrimitives({
4060
+ symbol,
4061
+ schematicComponentId: schematicComponent.schematic_component_id,
4062
+ origin: center,
4063
+ scale: scale3
4064
+ });
4065
+ }
4066
+ getPreviewCenter() {
4067
+ const index = this.previewIndex++;
4068
+ const column = index % PREVIEW_COLUMNS;
4069
+ const row = Math.floor(index / PREVIEW_COLUMNS);
4070
+ return {
4071
+ x: (column - (PREVIEW_COLUMNS - 1) / 2) * PREVIEW_CELL_WIDTH,
4072
+ y: -row * PREVIEW_CELL_HEIGHT
4073
+ };
4074
+ }
4075
+ getPinBounds(pins) {
4076
+ const pinsWithPositions = pins.filter((pin) => pin.at);
4077
+ if (pinsWithPositions.length === 0) {
4078
+ return { width: 15, height: 15 };
4079
+ }
4080
+ const xs = pinsWithPositions.map((pin) => pin.at.x);
4081
+ const ys = pinsWithPositions.map((pin) => pin.at.y);
4082
+ return {
4083
+ width: Math.max(...xs) - Math.min(...xs) + 7.5,
4084
+ height: Math.max(...ys) - Math.min(...ys) + 7.5
4085
+ };
4086
+ }
4087
+ getPreviewScale(bounds) {
4088
+ const scaleX = PREVIEW_CELL_WIDTH * PREVIEW_CELL_FILL_RATIO / Math.max(1, bounds.width);
4089
+ const scaleY = PREVIEW_CELL_HEIGHT * PREVIEW_CELL_FILL_RATIO / Math.max(1, bounds.height);
4090
+ return Math.min(MAX_KICAD_SYMBOL_UNIT_TO_CJ, scaleX, scaleY);
4091
+ }
4092
+ createSchematicPrimitives(params) {
4093
+ const { symbol, schematicComponentId, origin, scale: scale3 } = params;
4094
+ for (const polyline of this.collectPolylines(symbol)) {
4095
+ this.createPolylinePrimitives(
4096
+ polyline,
4097
+ schematicComponentId,
4098
+ origin,
4099
+ scale3
4100
+ );
4101
+ }
4102
+ for (const rectangle of this.collectRectangles(symbol)) {
4103
+ const start = this.toSchematicPoint(rectangle.start, origin, scale3);
4104
+ const end = this.toSchematicPoint(rectangle.end, origin, scale3);
4105
+ const rectData = {
4106
+ schematic_component_id: schematicComponentId,
4107
+ center: {
4108
+ x: (start.x + end.x) / 2,
4109
+ y: (start.y + end.y) / 2
4110
+ },
4111
+ width: Math.abs(end.x - start.x),
4112
+ height: Math.abs(end.y - start.y),
4113
+ rotation: 0,
4114
+ stroke_width: this.toStrokeWidth(rectangle.stroke?.width, scale3),
4115
+ color: DEFAULT_STROKE_COLOR,
4116
+ is_filled: this.isFilled(rectangle.fill?.type),
4117
+ fill_color: this.isFilled(rectangle.fill?.type) ? DEFAULT_FILL_COLOR : void 0,
4118
+ is_dashed: rectangle.stroke?.type === "dash"
4119
+ };
4120
+ this.ctx.db.schematic_rect.insert(rectData);
4121
+ }
4122
+ for (const circle of this.collectCircles(symbol)) {
4123
+ const circleData = {
4124
+ schematic_component_id: schematicComponentId,
4125
+ center: this.toSchematicPoint(circle.center, origin, scale3),
4126
+ radius: circle.radius * scale3,
4127
+ stroke_width: this.toStrokeWidth(circle.stroke?.width, scale3),
4128
+ color: DEFAULT_STROKE_COLOR,
4129
+ is_filled: this.isFilled(circle.fill?.type),
4130
+ fill_color: this.isFilled(circle.fill?.type) ? DEFAULT_FILL_COLOR : void 0,
4131
+ is_dashed: circle.stroke?.type === "dash"
4132
+ };
4133
+ this.ctx.db.schematic_circle.insert(circleData);
4134
+ }
4135
+ for (const arc of this.collectArcs(symbol)) {
4136
+ const arcGeometry = this.getArcGeometry(arc, origin, scale3);
4137
+ if (!arcGeometry) {
4138
+ const pathData = {
4139
+ schematic_component_id: schematicComponentId,
4140
+ points: [arc.start, arc.mid, arc.end].map(
4141
+ (point) => this.toSchematicPoint(point, origin, scale3)
4142
+ ),
4143
+ stroke_width: this.toStrokeWidth(arc.stroke?.width, scale3),
4144
+ stroke_color: DEFAULT_STROKE_COLOR
4145
+ };
4146
+ this.ctx.db.schematic_path.insert(pathData);
4147
+ continue;
4148
+ }
4149
+ const arcData = {
4150
+ schematic_component_id: schematicComponentId,
4151
+ ...arcGeometry,
4152
+ stroke_width: this.toStrokeWidth(arc.stroke?.width, scale3),
4153
+ color: DEFAULT_STROKE_COLOR,
4154
+ is_dashed: arc.stroke?.type === "dash"
4155
+ };
4156
+ this.ctx.db.schematic_arc.insert(arcData);
4157
+ }
4158
+ for (const text of this.collectTexts(symbol)) {
4159
+ if (!text.text) continue;
4160
+ const textData = {
4161
+ schematic_component_id: schematicComponentId,
4162
+ text: text.text,
4163
+ font_size: Math.max(0.1, (text.fontSize ?? 1.27) * scale3),
4164
+ position: this.toSchematicPoint(text.at, origin, scale3),
4165
+ rotation: -text.at.angle,
4166
+ anchor: "center",
4167
+ color: DEFAULT_STROKE_COLOR
4168
+ };
4169
+ this.ctx.db.schematic_text.insert(textData);
4170
+ }
4171
+ }
4172
+ createPolylinePrimitives(polyline, schematicComponentId, origin, scale3) {
4173
+ if (polyline.points.length < 2) return;
4174
+ if (this.isFilled(polyline.fill?.type) && polyline.points.length >= 3) {
4175
+ const pathData = {
4176
+ schematic_component_id: schematicComponentId,
4177
+ points: polyline.points.map(
4178
+ (point) => this.toSchematicPoint(point, origin, scale3)
4179
+ ),
4180
+ stroke_width: this.toStrokeWidth(polyline.stroke?.width, scale3),
4181
+ stroke_color: DEFAULT_STROKE_COLOR,
4182
+ is_filled: true,
4183
+ fill_color: DEFAULT_FILL_COLOR
4184
+ };
4185
+ this.ctx.db.schematic_path.insert(pathData);
4186
+ }
4187
+ for (let index = 1; index < polyline.points.length; index++) {
4188
+ const start = this.toSchematicPoint(
4189
+ polyline.points[index - 1],
4190
+ origin,
4191
+ scale3
4192
+ );
4193
+ const end = this.toSchematicPoint(polyline.points[index], origin, scale3);
4194
+ const lineData = {
4195
+ schematic_component_id: schematicComponentId,
4196
+ x1: start.x,
4197
+ y1: start.y,
4198
+ x2: end.x,
4199
+ y2: end.y,
4200
+ stroke_width: this.toStrokeWidth(polyline.stroke?.width, scale3),
4201
+ color: DEFAULT_STROKE_COLOR,
4202
+ is_dashed: polyline.stroke?.type === "dash"
4203
+ };
4204
+ this.ctx.db.schematic_line.insert(lineData);
4205
+ }
4206
+ }
4207
+ toSchematicPoint(point, origin = { x: 0, y: 0 }, scale3 = MAX_KICAD_SYMBOL_UNIT_TO_CJ) {
4208
+ return {
4209
+ x: origin.x + point.x * scale3,
4210
+ y: origin.y + point.y * scale3
4211
+ };
4212
+ }
4213
+ toStrokeWidth(width, scale3) {
4214
+ if (!width) return null;
4215
+ return Math.max(0.01, width * scale3);
4216
+ }
4217
+ isFilled(fillType) {
4218
+ return fillType !== void 0 && fillType !== "none";
4219
+ }
4220
+ getArcGeometry(arc, origin, scale3) {
4221
+ const start = this.toSchematicPoint(arc.start, origin, scale3);
4222
+ const mid = this.toSchematicPoint(arc.mid, origin, scale3);
4223
+ const end = this.toSchematicPoint(arc.end, origin, scale3);
4224
+ const denominator = 2 * (start.x * (mid.y - end.y) + mid.x * (end.y - start.y) + end.x * (start.y - mid.y));
4225
+ if (Math.abs(denominator) < 1e-9) return null;
4226
+ const startLen = start.x ** 2 + start.y ** 2;
4227
+ const midLen = mid.x ** 2 + mid.y ** 2;
4228
+ const endLen = end.x ** 2 + end.y ** 2;
4229
+ const center = {
4230
+ x: (startLen * (mid.y - end.y) + midLen * (end.y - start.y) + endLen * (start.y - mid.y)) / denominator,
4231
+ y: (startLen * (end.x - mid.x) + midLen * (start.x - end.x) + endLen * (mid.x - start.x)) / denominator
4232
+ };
4233
+ const radius = Math.hypot(start.x - center.x, start.y - center.y);
4234
+ const startAngleDegrees = this.getAngleDegrees(start, center);
4235
+ const endAngleDegrees = this.getAngleDegrees(end, center);
4236
+ const cross = (mid.x - start.x) * (end.y - mid.y) - (mid.y - start.y) * (end.x - mid.x);
4237
+ return {
4238
+ center,
4239
+ radius,
4240
+ start_angle_degrees: startAngleDegrees,
4241
+ end_angle_degrees: endAngleDegrees,
4242
+ direction: cross >= 0 ? "counterclockwise" : "clockwise"
4243
+ };
4244
+ }
4245
+ getAngleDegrees(point, center) {
4246
+ return Math.atan2(point.y - center.y, point.x - center.x) * 180 / Math.PI;
4247
+ }
4248
+ getManufacturerPartNumber(symbol) {
4249
+ return symbol.properties["Manufacturer Part Number"] || symbol.properties["MPN"] || symbol.properties["P/N"] || symbol.properties.Value || void 0;
4250
+ }
4251
+ createSourceComponentData(symbol) {
4252
+ const base = {
4253
+ name: symbol.name,
4254
+ manufacturer_part_number: this.getManufacturerPartNumber(symbol)
4255
+ };
4256
+ const ftype = this.inferFtype(symbol);
4257
+ switch (ftype) {
4258
+ case "simple_resistor":
4259
+ return { ...base, ftype, resistance: 0 };
4260
+ case "simple_capacitor":
4261
+ return { ...base, ftype, capacitance: 0 };
4262
+ case "simple_inductor":
4263
+ return { ...base, ftype, inductance: 0 };
4264
+ case "simple_transistor":
4265
+ return { ...base, ftype, transistor_type: "npn" };
4266
+ case "simple_led":
4267
+ case "simple_diode":
4268
+ case "simple_chip":
4269
+ return { ...base, ftype };
4270
+ }
4271
+ }
4272
+ inferFtype(symbol) {
4273
+ const name = symbol.name.toLowerCase();
4274
+ const reference = symbol.properties.Reference ?? "";
4275
+ if (name === "r" || name.startsWith("r_") || reference.startsWith("R")) {
4276
+ return "simple_resistor";
4277
+ }
4278
+ if (name === "c" || name.startsWith("c_") || reference.startsWith("C")) {
4279
+ return "simple_capacitor";
4280
+ }
4281
+ if (name === "l" || name.startsWith("l_") || reference.startsWith("L")) {
4282
+ return "simple_inductor";
4283
+ }
4284
+ if (name.includes("led") || reference.startsWith("LED")) {
4285
+ return "simple_led";
4286
+ }
4287
+ if (name.startsWith("d_") || reference.startsWith("D")) {
4288
+ return "simple_diode";
4289
+ }
4290
+ if (name.startsWith("q_") || reference.startsWith("Q")) {
4291
+ return "simple_transistor";
4292
+ }
4293
+ return "simple_chip";
4294
+ }
4295
+ getPortName(pin, pinNumber) {
4296
+ if (pin.name) return pin.name;
4297
+ if (/^\d+$/.test(pinNumber)) return `pin${Number(pinNumber)}`;
4298
+ return pinNumber;
4299
+ }
4300
+ getSourcePortPinMetadata(pinNumber) {
4301
+ if (/^\d+$/.test(pinNumber)) {
4302
+ return { pin_number: Number(pinNumber) };
4303
+ }
4304
+ return { port_hints: [pinNumber] };
4305
+ }
4306
+ getSchematicPortPinMetadata(pinNumber) {
4307
+ if (/^\d+$/.test(pinNumber)) {
4308
+ return { pin_number: Number(pinNumber) };
4309
+ }
4310
+ return { display_pin_label: pinNumber };
4311
+ }
4312
+ };
4313
+
4314
+ // lib/stages/symbol-library/InitializeSymbolLibraryContextStage.ts
4315
+ var InitializeSymbolLibraryContextStage = class extends ConverterStage {
4316
+ step() {
4317
+ if (!this.ctx.kicadSymbolLib) {
4318
+ this.finished = true;
4319
+ return false;
4320
+ }
4321
+ this.ctx.warnings = this.ctx.warnings || [];
4322
+ this.ctx.stats = this.ctx.stats || {};
4323
+ this.finished = true;
4324
+ return false;
4325
+ }
4326
+ };
4327
+
3674
4328
  // lib/KicadToCircuitJsonConverter.ts
3675
4329
  var KicadToCircuitJsonConverter = class {
3676
4330
  fsMap = {};
@@ -3697,14 +4351,22 @@ var KicadToCircuitJsonConverter = class {
3697
4351
  initializePipeline() {
3698
4352
  const pcbFile = this._findFileWithExtension(".kicad_pcb");
3699
4353
  const schFile = this._findFileWithExtension(".kicad_sch");
4354
+ const symbolLibFile = this._findFileWithExtension(".kicad_sym");
3700
4355
  this.ctx = {
3701
4356
  db: cju([]),
3702
4357
  kicadPcb: pcbFile ? parseKicadPcb(this.fsMap[pcbFile]) : void 0,
3703
4358
  kicadSch: schFile ? parseKicadSch(this.fsMap[schFile]) : void 0,
4359
+ kicadSymbolLib: symbolLibFile ? parseKicadSymbolLib(this.fsMap[symbolLibFile]) : void 0,
3704
4360
  warnings: [],
3705
4361
  stats: {}
3706
4362
  };
3707
4363
  this.pipeline = [];
4364
+ if (this.ctx.kicadSymbolLib) {
4365
+ this.pipeline.push(
4366
+ new InitializeSymbolLibraryContextStage(this.ctx),
4367
+ new CollectSymbolLibrarySymbolsStage(this.ctx)
4368
+ );
4369
+ }
3708
4370
  if (this.ctx.kicadSch) {
3709
4371
  this.pipeline.push(
3710
4372
  new InitializeSchematicContextStage(this.ctx),
@@ -3751,46 +4413,7 @@ var KicadToCircuitJsonConverter = class {
3751
4413
  this.initializePipeline();
3752
4414
  this.runUntilFinished();
3753
4415
  }
3754
- const elements = [];
3755
- const tableNames = [
3756
- "source_component",
3757
- "source_port",
3758
- "source_net",
3759
- "source_trace",
3760
- "schematic_component",
3761
- "schematic_port",
3762
- "schematic_trace",
3763
- "schematic_net_label",
3764
- "pcb_component",
3765
- "pcb_port",
3766
- "pcb_smtpad",
3767
- "pcb_plated_hole",
3768
- "pcb_hole",
3769
- "pcb_trace",
3770
- "pcb_via",
3771
- "pcb_copper_pour",
3772
- "pcb_board",
3773
- "pcb_cutout",
3774
- "pcb_copper_text",
3775
- "pcb_silkscreen_text",
3776
- "pcb_silkscreen_path",
3777
- "pcb_fabrication_note_text",
3778
- "pcb_fabrication_note_path",
3779
- "pcb_fabrication_note_rect",
3780
- "pcb_courtyard_rect",
3781
- "pcb_courtyard_outline",
3782
- "pcb_courtyard_circle"
3783
- ];
3784
- for (const tableName of tableNames) {
3785
- const table = this.ctx.db[tableName];
3786
- if (table && typeof table.list === "function") {
3787
- const items = table.list();
3788
- if (items && Array.isArray(items)) {
3789
- elements.push(...items);
3790
- }
3791
- }
3792
- }
3793
- return elements;
4416
+ return this.ctx.db.toArray();
3794
4417
  }
3795
4418
  getOutputString() {
3796
4419
  return JSON.stringify(this.getOutput(), null, 2);
@@ -3809,11 +4432,13 @@ export {
3809
4432
  CollectNetsStage,
3810
4433
  CollectSchematicTracesStage,
3811
4434
  CollectSourceTracesStage,
4435
+ CollectSymbolLibrarySymbolsStage,
3812
4436
  CollectTracesStage,
3813
4437
  CollectViasStage,
3814
4438
  CollectZonesStage,
3815
4439
  ConverterStage,
3816
4440
  InitializePcbContextStage,
3817
4441
  InitializeSchematicContextStage,
4442
+ InitializeSymbolLibraryContextStage,
3818
4443
  KicadToCircuitJsonConverter
3819
4444
  };
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "kicad-to-circuit-json",
3
3
  "main": "dist/index.js",
4
4
  "type": "module",
5
- "version": "0.0.78",
5
+ "version": "0.0.80",
6
6
  "files": [
7
7
  "dist"
8
8
  ],
@@ -35,7 +35,7 @@
35
35
  "kicadts": "^0.0.43",
36
36
  "react": "^19.2.0",
37
37
  "react-dom": "^19.2.5",
38
- "tscircuit": "^0.0.1792",
38
+ "tscircuit": "^0.0.1825",
39
39
  "tsup": "^8.5.0",
40
40
  "vercel": "^50.42.0",
41
41
  "vite": "^8.0.8"