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

1 import logging

2 import math

3 import re

4

5 RELATIVE_OFFSET = 0.254

6 ABSOLUTE_OFFSET_X = 101.6

7 ABSOLUTE_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

12 def mil2mm(data):

13 return float(data) / 3.937

14

15

16 def 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

64 def 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

82 def 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

164 def 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

215 def 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

239 def 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

264 def 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

275 def 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

392 handlers = {

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}