@jlcpcb/core 0.1.0
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/CHANGELOG.md +10 -0
- package/README.md +474 -0
- package/package.json +48 -0
- package/src/api/easyeda-community.ts +259 -0
- package/src/api/easyeda.ts +153 -0
- package/src/api/index.ts +7 -0
- package/src/api/jlc.ts +185 -0
- package/src/constants/design-rules.ts +119 -0
- package/src/constants/footprints.ts +68 -0
- package/src/constants/index.ts +7 -0
- package/src/constants/kicad.ts +147 -0
- package/src/converter/category-router.ts +638 -0
- package/src/converter/footprint-mapper.ts +236 -0
- package/src/converter/footprint.ts +949 -0
- package/src/converter/global-lib-table.ts +394 -0
- package/src/converter/index.ts +46 -0
- package/src/converter/lib-table.ts +181 -0
- package/src/converter/svg-arc.ts +179 -0
- package/src/converter/symbol-templates.ts +214 -0
- package/src/converter/symbol.ts +1682 -0
- package/src/converter/value-normalizer.ts +262 -0
- package/src/index.ts +25 -0
- package/src/parsers/easyeda-shapes.ts +628 -0
- package/src/parsers/http-client.ts +96 -0
- package/src/parsers/index.ts +38 -0
- package/src/parsers/utils.ts +29 -0
- package/src/services/component-service.ts +100 -0
- package/src/services/fix-service.ts +50 -0
- package/src/services/index.ts +9 -0
- package/src/services/library-service.ts +696 -0
- package/src/types/component.ts +61 -0
- package/src/types/easyeda-community.ts +78 -0
- package/src/types/easyeda.ts +356 -0
- package/src/types/index.ts +12 -0
- package/src/types/jlc.ts +84 -0
- package/src/types/kicad.ts +136 -0
- package/src/types/mcp.ts +77 -0
- package/src/types/project.ts +60 -0
- package/src/utils/conversion.ts +104 -0
- package/src/utils/file-system.ts +143 -0
- package/src/utils/index.ts +8 -0
- package/src/utils/logger.ts +96 -0
- package/src/utils/validation.ts +110 -0
- package/tsconfig.json +9 -0
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SVG Arc to Center Parameterization
|
|
3
|
+
*
|
|
4
|
+
* Converts SVG arc endpoint parameters to center point format used by KiCad.
|
|
5
|
+
* Based on SVG specification: https://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export interface ArcEndpointParams {
|
|
9
|
+
x1: number; // Start point X
|
|
10
|
+
y1: number; // Start point Y
|
|
11
|
+
rx: number; // X radius
|
|
12
|
+
ry: number; // Y radius
|
|
13
|
+
phi: number; // X-axis rotation in degrees
|
|
14
|
+
largeArc: boolean; // Large arc flag
|
|
15
|
+
sweep: boolean; // Sweep flag (clockwise)
|
|
16
|
+
x2: number; // End point X
|
|
17
|
+
y2: number; // End point Y
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface ArcCenterParams {
|
|
21
|
+
cx: number; // Center X
|
|
22
|
+
cy: number; // Center Y
|
|
23
|
+
rx: number; // Corrected X radius
|
|
24
|
+
ry: number; // Corrected Y radius
|
|
25
|
+
startAngle: number; // Start angle in radians
|
|
26
|
+
endAngle: number; // End angle in radians
|
|
27
|
+
deltaAngle: number; // Arc sweep in radians (negative = clockwise)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Convert SVG arc endpoint parameterization to center parameterization
|
|
32
|
+
* Algorithm from SVG spec Appendix B.2.4
|
|
33
|
+
*/
|
|
34
|
+
export function svgArcToCenter(params: ArcEndpointParams): ArcCenterParams | null {
|
|
35
|
+
const { x1, y1, x2, y2, largeArc, sweep } = params;
|
|
36
|
+
let { rx, ry, phi } = params;
|
|
37
|
+
|
|
38
|
+
// Convert rotation to radians
|
|
39
|
+
const phiRad = (phi * Math.PI) / 180;
|
|
40
|
+
const cosPhi = Math.cos(phiRad);
|
|
41
|
+
const sinPhi = Math.sin(phiRad);
|
|
42
|
+
|
|
43
|
+
// Step 1: Compute (x1', y1') - midpoint in rotated coordinates
|
|
44
|
+
const dx = (x1 - x2) / 2;
|
|
45
|
+
const dy = (y1 - y2) / 2;
|
|
46
|
+
const x1p = cosPhi * dx + sinPhi * dy;
|
|
47
|
+
const y1p = -sinPhi * dx + cosPhi * dy;
|
|
48
|
+
|
|
49
|
+
// Ensure radii are positive
|
|
50
|
+
rx = Math.abs(rx);
|
|
51
|
+
ry = Math.abs(ry);
|
|
52
|
+
|
|
53
|
+
// Check for degenerate cases
|
|
54
|
+
if (rx === 0 || ry === 0) {
|
|
55
|
+
return null; // Line, not an arc
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Step 2: Correct out-of-range radii
|
|
59
|
+
// Ensure radii are large enough
|
|
60
|
+
const lambda = (x1p * x1p) / (rx * rx) + (y1p * y1p) / (ry * ry);
|
|
61
|
+
if (lambda > 1) {
|
|
62
|
+
const sqrtLambda = Math.sqrt(lambda);
|
|
63
|
+
rx = sqrtLambda * rx;
|
|
64
|
+
ry = sqrtLambda * ry;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Step 3: Compute (cx', cy') - center in rotated coordinates
|
|
68
|
+
const rx2 = rx * rx;
|
|
69
|
+
const ry2 = ry * ry;
|
|
70
|
+
const x1p2 = x1p * x1p;
|
|
71
|
+
const y1p2 = y1p * y1p;
|
|
72
|
+
|
|
73
|
+
let sq = (rx2 * ry2 - rx2 * y1p2 - ry2 * x1p2) / (rx2 * y1p2 + ry2 * x1p2);
|
|
74
|
+
if (sq < 0) sq = 0; // Numerical precision fix
|
|
75
|
+
|
|
76
|
+
let coef = Math.sqrt(sq);
|
|
77
|
+
if (largeArc === sweep) {
|
|
78
|
+
coef = -coef;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const cxp = coef * ((rx * y1p) / ry);
|
|
82
|
+
const cyp = coef * (-(ry * x1p) / rx);
|
|
83
|
+
|
|
84
|
+
// Step 4: Compute (cx, cy) from (cx', cy')
|
|
85
|
+
const cx = cosPhi * cxp - sinPhi * cyp + (x1 + x2) / 2;
|
|
86
|
+
const cy = sinPhi * cxp + cosPhi * cyp + (y1 + y2) / 2;
|
|
87
|
+
|
|
88
|
+
// Step 5: Compute start angle and delta angle
|
|
89
|
+
const ux = (x1p - cxp) / rx;
|
|
90
|
+
const uy = (y1p - cyp) / ry;
|
|
91
|
+
const vx = (-x1p - cxp) / rx;
|
|
92
|
+
const vy = (-y1p - cyp) / ry;
|
|
93
|
+
|
|
94
|
+
// Angle between two vectors
|
|
95
|
+
const vectorAngle = (ux: number, uy: number, vx: number, vy: number): number => {
|
|
96
|
+
const dot = ux * vx + uy * vy;
|
|
97
|
+
const len = Math.sqrt(ux * ux + uy * uy) * Math.sqrt(vx * vx + vy * vy);
|
|
98
|
+
let angle = Math.acos(Math.max(-1, Math.min(1, dot / len))); // Clamp for numerical stability
|
|
99
|
+
if (ux * vy - uy * vx < 0) {
|
|
100
|
+
angle = -angle;
|
|
101
|
+
}
|
|
102
|
+
return angle;
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
// Start angle (angle from positive x-axis to start point vector)
|
|
106
|
+
const startAngle = vectorAngle(1, 0, ux, uy);
|
|
107
|
+
|
|
108
|
+
// Delta angle (sweep)
|
|
109
|
+
let deltaAngle = vectorAngle(ux, uy, vx, vy);
|
|
110
|
+
|
|
111
|
+
// Adjust delta based on sweep flag
|
|
112
|
+
if (!sweep && deltaAngle > 0) {
|
|
113
|
+
deltaAngle -= 2 * Math.PI;
|
|
114
|
+
} else if (sweep && deltaAngle < 0) {
|
|
115
|
+
deltaAngle += 2 * Math.PI;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const endAngle = startAngle + deltaAngle;
|
|
119
|
+
|
|
120
|
+
return {
|
|
121
|
+
cx,
|
|
122
|
+
cy,
|
|
123
|
+
rx,
|
|
124
|
+
ry,
|
|
125
|
+
startAngle,
|
|
126
|
+
endAngle,
|
|
127
|
+
deltaAngle,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Parse SVG arc path and extract parameters
|
|
133
|
+
* Format: "M x1 y1 A rx ry rotation largeArc sweep x2 y2"
|
|
134
|
+
*/
|
|
135
|
+
export function parseSvgArcPath(path: string): ArcEndpointParams | null {
|
|
136
|
+
try {
|
|
137
|
+
// Normalize path - handle both space and comma separators
|
|
138
|
+
const normalized = path.replace(/,/g, ' ').replace(/\s+/g, ' ').trim();
|
|
139
|
+
|
|
140
|
+
// Match M x y A rx ry rotation largeArc sweep x y
|
|
141
|
+
const match = normalized.match(
|
|
142
|
+
/M\s*(-?[\d.]+)\s+(-?[\d.]+)\s*A\s*(-?[\d.]+)\s+(-?[\d.]+)\s+(-?[\d.]+)\s+([01])\s+([01])\s+(-?[\d.]+)\s+(-?[\d.]+)/i
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
if (!match) {
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return {
|
|
150
|
+
x1: parseFloat(match[1]),
|
|
151
|
+
y1: parseFloat(match[2]),
|
|
152
|
+
rx: parseFloat(match[3]),
|
|
153
|
+
ry: parseFloat(match[4]),
|
|
154
|
+
phi: parseFloat(match[5]),
|
|
155
|
+
largeArc: match[6] === '1',
|
|
156
|
+
sweep: match[7] === '1',
|
|
157
|
+
x2: parseFloat(match[8]),
|
|
158
|
+
y2: parseFloat(match[9]),
|
|
159
|
+
};
|
|
160
|
+
} catch {
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Convert radians to degrees
|
|
167
|
+
*/
|
|
168
|
+
export function radToDeg(rad: number): number {
|
|
169
|
+
return (rad * 180) / Math.PI;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Normalize angle to 0-360 range
|
|
174
|
+
*/
|
|
175
|
+
export function normalizeAngle(degrees: number): number {
|
|
176
|
+
while (degrees < 0) degrees += 360;
|
|
177
|
+
while (degrees >= 360) degrees -= 360;
|
|
178
|
+
return degrees;
|
|
179
|
+
}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Symbol Templates for Passive Components
|
|
3
|
+
* Fixed layouts that produce clean, consistent symbols like CDFER's JLCPCB-Kicad-Library
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export interface SymbolTemplate {
|
|
7
|
+
/** Pin length in mm */
|
|
8
|
+
pinLength: number;
|
|
9
|
+
/** Y-distance between pins for 2-pin components (total, not from center) */
|
|
10
|
+
pinSpacing: number;
|
|
11
|
+
/** KiCad S-expression for body graphics */
|
|
12
|
+
bodyGraphics: string;
|
|
13
|
+
/** Reference designator position */
|
|
14
|
+
refPosition: { x: number; y: number; angle: number };
|
|
15
|
+
/** Value property position */
|
|
16
|
+
valuePosition: { x: number; y: number; angle: number };
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Resistor template - Rectangular body
|
|
21
|
+
* Based on CDFER: Body 2.032mm × 5.08mm, pins at ±3.81mm
|
|
22
|
+
*/
|
|
23
|
+
const RESISTOR_TEMPLATE: SymbolTemplate = {
|
|
24
|
+
pinLength: 2.54,
|
|
25
|
+
pinSpacing: 7.62, // pins at y = ±3.81mm
|
|
26
|
+
bodyGraphics: `\t\t\t(rectangle
|
|
27
|
+
\t\t\t\t(start -1.016 2.54)
|
|
28
|
+
\t\t\t\t(end 1.016 -2.54)
|
|
29
|
+
\t\t\t\t(stroke
|
|
30
|
+
\t\t\t\t\t(width 0.254)
|
|
31
|
+
\t\t\t\t\t(type default)
|
|
32
|
+
\t\t\t\t)
|
|
33
|
+
\t\t\t\t(fill
|
|
34
|
+
\t\t\t\t\t(type background)
|
|
35
|
+
\t\t\t\t)
|
|
36
|
+
\t\t\t)`,
|
|
37
|
+
refPosition: { x: 2.54, y: 0, angle: 90 },
|
|
38
|
+
valuePosition: { x: -1.778, y: 0, angle: 90 },
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Capacitor template - Two parallel plates
|
|
43
|
+
* Based on CDFER: Plates at ±0.635mm, pins at ±2.54mm
|
|
44
|
+
*/
|
|
45
|
+
const CAPACITOR_TEMPLATE: SymbolTemplate = {
|
|
46
|
+
pinLength: 2.54,
|
|
47
|
+
pinSpacing: 5.08, // pins at y = ±2.54mm
|
|
48
|
+
bodyGraphics: `\t\t\t(polyline
|
|
49
|
+
\t\t\t\t(pts
|
|
50
|
+
\t\t\t\t\t(xy -1.27 0.635)
|
|
51
|
+
\t\t\t\t\t(xy 1.27 0.635)
|
|
52
|
+
\t\t\t\t)
|
|
53
|
+
\t\t\t\t(stroke
|
|
54
|
+
\t\t\t\t\t(width 0.254)
|
|
55
|
+
\t\t\t\t\t(type default)
|
|
56
|
+
\t\t\t\t)
|
|
57
|
+
\t\t\t\t(fill
|
|
58
|
+
\t\t\t\t\t(type none)
|
|
59
|
+
\t\t\t\t)
|
|
60
|
+
\t\t\t)
|
|
61
|
+
\t\t\t(polyline
|
|
62
|
+
\t\t\t\t(pts
|
|
63
|
+
\t\t\t\t\t(xy -1.27 -0.635)
|
|
64
|
+
\t\t\t\t\t(xy 1.27 -0.635)
|
|
65
|
+
\t\t\t\t)
|
|
66
|
+
\t\t\t\t(stroke
|
|
67
|
+
\t\t\t\t\t(width 0.254)
|
|
68
|
+
\t\t\t\t\t(type default)
|
|
69
|
+
\t\t\t\t)
|
|
70
|
+
\t\t\t\t(fill
|
|
71
|
+
\t\t\t\t\t(type none)
|
|
72
|
+
\t\t\t\t)
|
|
73
|
+
\t\t\t)`,
|
|
74
|
+
refPosition: { x: 2.54, y: 0, angle: 0 },
|
|
75
|
+
valuePosition: { x: -2.54, y: 0, angle: 0 },
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Inductor template - 4 semicircular arcs (standard coil symbol)
|
|
80
|
+
* Arcs arranged vertically, bulging to the right
|
|
81
|
+
*/
|
|
82
|
+
const INDUCTOR_TEMPLATE: SymbolTemplate = {
|
|
83
|
+
pinLength: 2.54,
|
|
84
|
+
pinSpacing: 7.62, // pins at y = ±3.81mm
|
|
85
|
+
bodyGraphics: `\t\t\t(arc
|
|
86
|
+
\t\t\t\t(start 0 2.54)
|
|
87
|
+
\t\t\t\t(mid 0.635 1.905)
|
|
88
|
+
\t\t\t\t(end 0 1.27)
|
|
89
|
+
\t\t\t\t(stroke
|
|
90
|
+
\t\t\t\t\t(width 0.254)
|
|
91
|
+
\t\t\t\t\t(type default)
|
|
92
|
+
\t\t\t\t)
|
|
93
|
+
\t\t\t\t(fill
|
|
94
|
+
\t\t\t\t\t(type none)
|
|
95
|
+
\t\t\t\t)
|
|
96
|
+
\t\t\t)
|
|
97
|
+
\t\t\t(arc
|
|
98
|
+
\t\t\t\t(start 0 1.27)
|
|
99
|
+
\t\t\t\t(mid 0.635 0.635)
|
|
100
|
+
\t\t\t\t(end 0 0)
|
|
101
|
+
\t\t\t\t(stroke
|
|
102
|
+
\t\t\t\t\t(width 0.254)
|
|
103
|
+
\t\t\t\t\t(type default)
|
|
104
|
+
\t\t\t\t)
|
|
105
|
+
\t\t\t\t(fill
|
|
106
|
+
\t\t\t\t\t(type none)
|
|
107
|
+
\t\t\t\t)
|
|
108
|
+
\t\t\t)
|
|
109
|
+
\t\t\t(arc
|
|
110
|
+
\t\t\t\t(start 0 0)
|
|
111
|
+
\t\t\t\t(mid 0.635 -0.635)
|
|
112
|
+
\t\t\t\t(end 0 -1.27)
|
|
113
|
+
\t\t\t\t(stroke
|
|
114
|
+
\t\t\t\t\t(width 0.254)
|
|
115
|
+
\t\t\t\t\t(type default)
|
|
116
|
+
\t\t\t\t)
|
|
117
|
+
\t\t\t\t(fill
|
|
118
|
+
\t\t\t\t\t(type none)
|
|
119
|
+
\t\t\t\t)
|
|
120
|
+
\t\t\t)
|
|
121
|
+
\t\t\t(arc
|
|
122
|
+
\t\t\t\t(start 0 -1.27)
|
|
123
|
+
\t\t\t\t(mid 0.635 -1.905)
|
|
124
|
+
\t\t\t\t(end 0 -2.54)
|
|
125
|
+
\t\t\t\t(stroke
|
|
126
|
+
\t\t\t\t\t(width 0.254)
|
|
127
|
+
\t\t\t\t\t(type default)
|
|
128
|
+
\t\t\t\t)
|
|
129
|
+
\t\t\t\t(fill
|
|
130
|
+
\t\t\t\t\t(type none)
|
|
131
|
+
\t\t\t\t)
|
|
132
|
+
\t\t\t)`,
|
|
133
|
+
refPosition: { x: 2.54, y: 0, angle: 90 },
|
|
134
|
+
valuePosition: { x: -1.778, y: 0, angle: 90 },
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Diode template - Triangle with cathode bar
|
|
139
|
+
* Based on CDFER: Triangle 2.54mm wide, pins at ±5.08mm
|
|
140
|
+
*/
|
|
141
|
+
const DIODE_TEMPLATE: SymbolTemplate = {
|
|
142
|
+
pinLength: 3.81,
|
|
143
|
+
pinSpacing: 10.16, // pins at y = ±5.08mm
|
|
144
|
+
bodyGraphics: `\t\t\t(polyline
|
|
145
|
+
\t\t\t\t(pts
|
|
146
|
+
\t\t\t\t\t(xy -1.27 1.27)
|
|
147
|
+
\t\t\t\t\t(xy 0 -1.27)
|
|
148
|
+
\t\t\t\t\t(xy 1.27 1.27)
|
|
149
|
+
\t\t\t\t\t(xy -1.27 1.27)
|
|
150
|
+
\t\t\t\t)
|
|
151
|
+
\t\t\t\t(stroke
|
|
152
|
+
\t\t\t\t\t(width 0.254)
|
|
153
|
+
\t\t\t\t\t(type default)
|
|
154
|
+
\t\t\t\t)
|
|
155
|
+
\t\t\t\t(fill
|
|
156
|
+
\t\t\t\t\t(type none)
|
|
157
|
+
\t\t\t\t)
|
|
158
|
+
\t\t\t)
|
|
159
|
+
\t\t\t(polyline
|
|
160
|
+
\t\t\t\t(pts
|
|
161
|
+
\t\t\t\t\t(xy -1.27 -1.27)
|
|
162
|
+
\t\t\t\t\t(xy 1.27 -1.27)
|
|
163
|
+
\t\t\t\t)
|
|
164
|
+
\t\t\t\t(stroke
|
|
165
|
+
\t\t\t\t\t(width 0.254)
|
|
166
|
+
\t\t\t\t\t(type default)
|
|
167
|
+
\t\t\t\t)
|
|
168
|
+
\t\t\t\t(fill
|
|
169
|
+
\t\t\t\t\t(type none)
|
|
170
|
+
\t\t\t\t)
|
|
171
|
+
\t\t\t)`,
|
|
172
|
+
refPosition: { x: 2.54, y: 0, angle: 0 },
|
|
173
|
+
valuePosition: { x: -2.54, y: 0, angle: 0 },
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* LED template - Triangle with light rays
|
|
178
|
+
*/
|
|
179
|
+
const LED_TEMPLATE: SymbolTemplate = {
|
|
180
|
+
...DIODE_TEMPLATE,
|
|
181
|
+
// Same as diode but could add light rays in future
|
|
182
|
+
bodyGraphics: DIODE_TEMPLATE.bodyGraphics,
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Get symbol template for a component prefix
|
|
187
|
+
* Returns null for components that should use EasyEDA-derived layout
|
|
188
|
+
*/
|
|
189
|
+
export function getSymbolTemplate(prefix: string): SymbolTemplate | null {
|
|
190
|
+
const p = prefix.toUpperCase();
|
|
191
|
+
|
|
192
|
+
switch (p) {
|
|
193
|
+
case 'R':
|
|
194
|
+
return RESISTOR_TEMPLATE;
|
|
195
|
+
case 'C':
|
|
196
|
+
return CAPACITOR_TEMPLATE;
|
|
197
|
+
case 'L':
|
|
198
|
+
return INDUCTOR_TEMPLATE;
|
|
199
|
+
case 'D':
|
|
200
|
+
return DIODE_TEMPLATE;
|
|
201
|
+
case 'LED':
|
|
202
|
+
return LED_TEMPLATE;
|
|
203
|
+
default:
|
|
204
|
+
// No template - use EasyEDA-derived layout for ICs, transistors, etc.
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Check if a component should use a fixed template
|
|
211
|
+
*/
|
|
212
|
+
export function hasFixedTemplate(prefix: string): boolean {
|
|
213
|
+
return getSymbolTemplate(prefix) !== null;
|
|
214
|
+
}
|