Coverage for JLC2KiCadLib / symbol / symbol_handlers.py: 97%

156 statements  

« prev     ^ index     » next       coverage.py v7.13.1, created at 2026-01-04 22:31 +0100

1import logging 

2import math 

3import re 

4 

5RELATIVE_OFFSET = 0.254 

6ABSOLUTE_OFFSET_X = 101.6 

7ABSOLUTE_OFFSET_Y = -63.5 

8 

9__all__ = ["handlers", "h_R", "h_E", "h_P", "h_T", "h_PL", "h_PG", "h_PT", "h_A"] 

10 

11 

12def mil2mm(data): 

13 return float(data) / 3.937 

14 

15 

16def h_R(data, translation, kicad_symbol): 

17 """ 

18 Rectangle handler 

19 data = { 

20 0 : x1 

21 1 : y1 

22 2 : 

23 3 : 

24 4 : width 

25 5 : length 

26 6 : stroke color 

27 7 : ? 

28 8 : stroke style : 0 = solid, 1 = dashed, 2 = dotted 

29 9 : fill color 

30 10 : id 

31 11 : locked 

32 } 

33 """ 

34 

35 x1 = float(data[0]) 

36 y1 = float(data[1]) 

37 width = float(data[4]) 

38 length = float(data[5]) 

39 

40 x2 = x1 + width 

41 y2 = y1 + length 

42 

43 x1_mm = mil2mm(x1 - translation[0]) 

44 y1_mm = -mil2mm(y1 - translation[1]) 

45 x2_mm = mil2mm(x2 - translation[0]) 

46 y2_mm = -mil2mm(y2 - translation[1]) 

47 

48 if data[8] == 1: 

49 stroke_style = "dash" 

50 elif data[8] == 2: 

51 stroke_style = "dot" 

52 else: 

53 stroke_style = "default" 

54 

55 kicad_symbol.drawing += f""" 

56 (rectangle 

57 (start {x1_mm} {y1_mm}) 

58 (end {x2_mm} {y2_mm}) 

59 (stroke (width 0) (type {stroke_style}) (color 0 0 0 0)) 

60 (fill (type background)) 

61 )""" 

62 

63 

64def h_E(data, translation, kicad_symbol): 

65 """ 

66 Circle 

67 """ 

68 

69 x1 = mil2mm(float(data[0]) - translation[0]) 

70 y1 = -mil2mm(float(data[1]) - translation[1]) 

71 radius = mil2mm(float(data[2])) 

72 

73 kicad_symbol.drawing += f""" 

74 (circle 

75 (center {x1} {y1}) 

76 (radius {radius}) 

77 (stroke (width 0) (type default) (color 0 0 0 0)) 

78 (fill (type background)) 

79 )""" 

80 

81 

82def h_P(data, translation, kicad_symbol): 

83 """ 

84 Add Pin to the symbol 

85 data = [ 

86 0 : 

87 1 : electrical type 

88 2 : pin number 

89 3 : x1 

90 4 : y1 

91 5 : rotation 

92 6 : id 

93 7 : 

94 8 : 

95 9 : 

96 10 : 

97 11 : 

98 12 : 

99 13 : 

100 14 : 

101 15 : 

102 16 : 

103 17 : name size 

104 18 : 

105 19 : 

106 20 : 

107 21 : 

108 22 : 

109 23 : 

110 24 : number size 

111 25 : 

112 ] 

113 """ 

114 

115 if data[1] == "0": 

116 electrical_type = "unspecified" 

117 elif data[1] == "1": 

118 electrical_type = "input" 

119 elif data[1] == "2": 

120 electrical_type = "output" 

121 elif data[1] == "3": 

122 electrical_type = "bidirectional" 

123 elif data[1] == "4": 

124 electrical_type = "power_in" 

125 else: 

126 electrical_type = "unspecified" 

127 

128 pin_number = data[2] 

129 pin_name = data[13] 

130 

131 x1 = round(mil2mm(float(data[3]) - translation[0]), 3) 

132 y1 = round(-mil2mm(float(data[4]) - translation[1]), 3) 

133 

134 rotation = (int(data[5]) + 180) % 360 if data[5] else 180 

135 

136 if rotation == 0 or rotation == 180: 

137 length = round(mil2mm(abs(float(data[8].split("h")[-1]))), 3) 

138 elif rotation == 90 or rotation == 270: 

139 length = mil2mm(abs(float(data[8].split("v")[-1]))) 

140 else: 

141 length = 2.54 

142 logging.warning( 

143 f'symbol : pin number {pin_number} : "{pin_name}" failed to find length.' 

144 "Using Default length" 

145 ) 

146 

147 if data[9].split("^^")[1] != "0": 

148 kicad_symbol.pinNamesHide = "" 

149 if data[17].split("^^")[1] != "0": 

150 kicad_symbol.pinNumbersHide = "" 

151 

152 name_size = mil2mm(float(data[16].replace("pt", ""))) if data[16] else 1 

153 number_size = mil2mm(float(data[24].replace("pt", ""))) if data[24] else 1 

154 

155 kicad_symbol.drawing += f""" 

156 (pin {electrical_type} line 

157 (at {x1} {y1} {rotation}) 

158 (length {length}) 

159 (name "{pin_name}" (effects (font (size {name_size} {name_size})))) 

160 (number "{pin_number}" (effects (font (size {number_size} {number_size})))) 

161 )""" 

162 

163 

164def h_T(data, translation, kicad_symbol): 

165 """ 

166 Text handler 

167 data = [ 

168 0 : 

169 1 : x1 

170 2 : y1 

171 3 : rotation 

172 4 : color 

173 5 : font 

174 6 : font size 

175 7 : 

176 8 : 

177 9 : 

178 10 : 

179 11 : text 

180 12 : 

181 13 : anchor 

182 ] 

183 """ 

184 

185 x1 = mil2mm(float(data[1]) - translation[0]) 

186 y1 = -mil2mm(float(data[2]) - translation[1]) 

187 

188 # From https://dev-docs.kicad.org/en/file-formats/sexpr-intro/index.html#_position_identifier 

189 # Symbol text ANGLEs are stored in tenth’s of a degree. All other ANGLEs are stored 

190 # in degrees. 

191 rotation = ((int(data[3]) + 180) % 360) * 10 

192 

193 font_size = mil2mm(float(data[6].replace("pt", ""))) if data[6] else 15 

194 

195 text = data[11] 

196 

197 if data[13] == "middle": 

198 justify = "left" 

199 elif data[13] == "end": 

200 justify = "right" 

201 else: 

202 justify = "left" 

203 

204 kicad_symbol.drawing += f""" 

205 (text 

206 "{text}" 

207 (at {x1} {y1} {rotation}) 

208 (effects  

209 (font (size {font_size} {font_size})) 

210 (justify {justify} bottom) 

211 ) 

212 )""" 

213 

214 

215def h_PL(data, translation, kicad_symbol): 

216 """ 

217 Polygone handler 

218 """ 

219 

220 path_string = data[0].split(" ") 

221 polypts = [] 

222 for i, _ in enumerate(path_string[::2]): 

223 polypts.append( 

224 f"(xy {mil2mm(float(path_string[2 * i]) - translation[0])} " 

225 f"{-mil2mm(float(path_string[2 * i + 1]) - translation[-1])})" 

226 ) 

227 polystr = "\n ".join(polypts) 

228 

229 kicad_symbol.drawing += f""" 

230 (polyline 

231 (pts 

232 {polystr} 

233 ) 

234 (stroke (width 0) (type default) (color 0 0 0 0)) 

235 (fill (type none)) 

236 )""" 

237 

238 

239def h_PG(data, translation, kicad_symbol): 

240 """ 

241 Closed polygone handler 

242 """ 

243 

244 path_string = [i for i in data[0].split(" ") if i] 

245 polypts = [] 

246 for i, _ in enumerate(path_string[::2]): 

247 polypts.append( 

248 f"(xy {mil2mm(float(path_string[2 * i]) - translation[0])} " 

249 f"{-mil2mm(float(path_string[2 * i + 1]) - translation[1])})" 

250 ) 

251 polypts.append(polypts[0]) 

252 polystr = "\n ".join(polypts) 

253 

254 kicad_symbol.drawing += f""" 

255 (polyline 

256 (pts 

257 {polystr} 

258 ) 

259 (stroke (width 0) (type default) (color 0 0 0 0)) 

260 (fill (type background)) 

261 )""" 

262 

263 

264def h_PT(data, translation, kicad_symbol): 

265 """ 

266 Triangle handler 

267 """ 

268 

269 data[0] = ( 

270 data[0].replace("M", "").replace("L", "").replace("Z", "").replace("C", "") 

271 ) 

272 h_PG(data, translation, kicad_symbol) 

273 

274 

275def h_A(data, translation, kicad_symbol): 

276 """ 

277 Arc handler 

278 """ 

279 

280 # Parse SVG path: "M x1 y1 A rx ry rotation large-arc sweep x2 y2" 

281 path = data[0].strip() 

282 

283 # Split into M and A commands 

284 parts = re.split(r"[MA]", path) 

285 parts = [p.strip() for p in parts if p.strip()] 

286 

287 # Parse M command (start point) 

288 start_coords = re.split(r"[\s,]+", parts[0]) 

289 x1 = float(start_coords[0]) 

290 y1 = float(start_coords[1]) 

291 

292 # Parse A command (arc parameters) 

293 arc_params = re.split(r"[\s,]+", parts[1]) 

294 rx = float(arc_params[0]) 

295 ry = float(arc_params[1]) 

296 rotation = float(arc_params[2]) 

297 large_arc_flag = int(arc_params[3]) 

298 sweep_flag = int(arc_params[4]) 

299 x2 = float(arc_params[5]) 

300 y2 = float(arc_params[6]) 

301 

302 cos_rot = math.cos(math.radians(rotation)) 

303 sin_rot = math.sin(math.radians(rotation)) 

304 

305 # Step 1: Compute (x1', y1') 

306 dx = (x1 - x2) / 2 

307 dy = (y1 - y2) / 2 

308 x1_prime = cos_rot * dx + sin_rot * dy 

309 y1_prime = -sin_rot * dx + cos_rot * dy 

310 

311 # Step 2: Compute center (cx', cy') 

312 rx_sq = rx * rx 

313 ry_sq = ry * ry 

314 x1_prime_sq = x1_prime * x1_prime 

315 y1_prime_sq = y1_prime * y1_prime 

316 

317 # Correct radii if needed 

318 lambda_sq = x1_prime_sq / rx_sq + y1_prime_sq / ry_sq 

319 if lambda_sq > 1: 

320 rx *= math.sqrt(lambda_sq) 

321 ry *= math.sqrt(lambda_sq) 

322 rx_sq = rx * rx 

323 ry_sq = ry * ry 

324 

325 sign = -1 if large_arc_flag == sweep_flag else 1 

326 

327 if (rx_sq * y1_prime_sq + ry_sq * x1_prime_sq) == 0: 

328 return 

329 

330 sq = max( 

331 0, 

332 (rx_sq * ry_sq - rx_sq * y1_prime_sq - ry_sq * x1_prime_sq) 

333 / (rx_sq * y1_prime_sq + ry_sq * x1_prime_sq), 

334 ) 

335 coef = sign * math.sqrt(sq) 

336 

337 cx_prime = coef * rx * y1_prime / ry 

338 cy_prime = -coef * ry * x1_prime / rx 

339 

340 # Step 3: Compute center (cx, cy) 

341 cx = cos_rot * cx_prime - sin_rot * cy_prime + (x1 + x2) / 2 

342 cy = sin_rot * cx_prime + cos_rot * cy_prime + (y1 + y2) / 2 

343 

344 # Calculate angles for finding midpoint 

345 def angle_between(ux, uy, vx, vy): 

346 n = math.sqrt(ux * ux + uy * uy) * math.sqrt(vx * vx + vy * vy) 

347 c = (ux * vx + uy * vy) / n 

348 c = max(-1, min(1, c)) # Clamp to [-1, 1] 

349 angle = math.acos(c) 

350 if ux * vy - uy * vx < 0: 

351 angle = -angle 

352 return angle 

353 

354 theta1 = angle_between(1, 0, (x1_prime - cx_prime) / rx, (y1_prime - cy_prime) / ry) 

355 dtheta = angle_between( 

356 (x1_prime - cx_prime) / rx, 

357 (y1_prime - cy_prime) / ry, 

358 (-x1_prime - cx_prime) / rx, 

359 (-y1_prime - cy_prime) / ry, 

360 ) 

361 

362 if sweep_flag == 0 and dtheta > 0: 

363 dtheta -= 2 * math.pi 

364 elif sweep_flag == 1 and dtheta < 0: 

365 dtheta += 2 * math.pi 

366 

367 # Calculate midpoint angle 

368 mid_angle = theta1 + dtheta / 2 

369 

370 # Calculate midpoint coordinates 

371 x_mid = cx + rx * math.cos(mid_angle) * cos_rot - ry * math.sin(mid_angle) * sin_rot 

372 y_mid = cy + rx * math.cos(mid_angle) * sin_rot + ry * math.sin(mid_angle) * cos_rot 

373 

374 # Convert to KiCad coordinates (mil to mm and apply translation) 

375 x1_mm = mil2mm(x1 - translation[0]) 

376 y1_mm = -mil2mm(y1 - translation[1]) 

377 x2_mm = mil2mm(x2 - translation[0]) 

378 y2_mm = -mil2mm(y2 - translation[1]) 

379 x_mid_mm = mil2mm(x_mid - translation[0]) 

380 y_mid_mm = -mil2mm(y_mid - translation[1]) 

381 

382 kicad_symbol.drawing += f""" 

383 (arc 

384 (start {x1_mm} {y1_mm}) 

385 (mid {x_mid_mm} {y_mid_mm}) 

386 (end {x2_mm} {y2_mm}) 

387 (stroke (width 0) (type default) (color 0 0 0 0)) 

388 (fill (type none)) 

389 )""" 

390 

391 

392handlers = { 

393 "R": h_R, 

394 "E": h_E, 

395 "P": h_P, 

396 "T": h_T, 

397 "PL": h_PL, 

398 "PG": h_PG, 

399 "PT": h_PT, 

400 "A": h_A, 

401 # "J" : h_NotYetImplemented, 

402 # "N" : h_NotYetImplemented, 

403 # "BE" : h_NotYetImplemented, 

404 # "AR" : h_NotYetImplemented, 

405 # "O" : h_NotYetImplemented, 

406}