@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.
Files changed (44) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +474 -0
  3. package/package.json +48 -0
  4. package/src/api/easyeda-community.ts +259 -0
  5. package/src/api/easyeda.ts +153 -0
  6. package/src/api/index.ts +7 -0
  7. package/src/api/jlc.ts +185 -0
  8. package/src/constants/design-rules.ts +119 -0
  9. package/src/constants/footprints.ts +68 -0
  10. package/src/constants/index.ts +7 -0
  11. package/src/constants/kicad.ts +147 -0
  12. package/src/converter/category-router.ts +638 -0
  13. package/src/converter/footprint-mapper.ts +236 -0
  14. package/src/converter/footprint.ts +949 -0
  15. package/src/converter/global-lib-table.ts +394 -0
  16. package/src/converter/index.ts +46 -0
  17. package/src/converter/lib-table.ts +181 -0
  18. package/src/converter/svg-arc.ts +179 -0
  19. package/src/converter/symbol-templates.ts +214 -0
  20. package/src/converter/symbol.ts +1682 -0
  21. package/src/converter/value-normalizer.ts +262 -0
  22. package/src/index.ts +25 -0
  23. package/src/parsers/easyeda-shapes.ts +628 -0
  24. package/src/parsers/http-client.ts +96 -0
  25. package/src/parsers/index.ts +38 -0
  26. package/src/parsers/utils.ts +29 -0
  27. package/src/services/component-service.ts +100 -0
  28. package/src/services/fix-service.ts +50 -0
  29. package/src/services/index.ts +9 -0
  30. package/src/services/library-service.ts +696 -0
  31. package/src/types/component.ts +61 -0
  32. package/src/types/easyeda-community.ts +78 -0
  33. package/src/types/easyeda.ts +356 -0
  34. package/src/types/index.ts +12 -0
  35. package/src/types/jlc.ts +84 -0
  36. package/src/types/kicad.ts +136 -0
  37. package/src/types/mcp.ts +77 -0
  38. package/src/types/project.ts +60 -0
  39. package/src/utils/conversion.ts +104 -0
  40. package/src/utils/file-system.ts +143 -0
  41. package/src/utils/index.ts +8 -0
  42. package/src/utils/logger.ts +96 -0
  43. package/src/utils/validation.ts +110 -0
  44. 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
+ }