KiCad PCB EDA Suite
Loading...
Searching...
No Matches
eagle_bin_parser.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Binary Eagle parsing logic and the eagle_script[] format table ported from
5 * pcb-rnd src_plugins/io_eagle (eagle_bin.c) by Tibor 'Igor2' Palinkas and
6 * Erich S. Heinzle.
7 *
8 * COPYRIGHT (pcb-rnd, eagle_bin.c / eagle_bin.h)
9 *
10 * pcb-rnd, interactive printed circuit board design
11 * Copyright (C) 2017 Tibor 'Igor2' Palinkas
12 * Copyright (C) 2017 Erich S. Heinzle
13 *
14 * Copyright (C) 2026 KiCad Developers, see AUTHORS.txt for contributors.
15 *
16 * This program is free software; you can redistribute it and/or
17 * modify it under the terms of the GNU General Public License
18 * as published by the Free Software Foundation; either version 2
19 * of the License, or (at your option) any later version.
20 *
21 * This program is distributed in the hope that it will be useful,
22 * but WITHOUT ANY WARRANTY; without even the implied warranty of
23 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24 * GNU General Public License for more details.
25 *
26 * You should have received a copy of the GNU General Public License
27 * along with this program. If not, see <https://www.gnu.org/licenses/>.
28 */
29
30#include "eagle_bin_parser.h"
31
32#include <cmath>
33#include <cstring>
34#include <algorithm>
35#include <functional>
36
37#include <wx/intl.h>
38#include <wx/stream.h>
39#include <wx/xml/xml.h>
40
41#include <ki_exception.h>
42#include <macros.h>
43#include <trace_helpers.h>
44#include <wx/log.h>
45
46// Section keyword ids; the high byte selects the record kind in the binary stream.
47enum EGKW
48{
67 EGKW_SECT_ARC = 0x2400,
72 EGKW_SECT_VIA = 0x2900,
73 EGKW_SECT_PAD = 0x2a00,
74 EGKW_SECT_SMD = 0x2b00,
75 EGKW_SECT_PIN = 0x2c00,
99
100 // Synthetic nodes created during post-processing.
103};
104
105namespace
106{
107enum ATTR_TYPE
108{
109 T_BMB, // bit-mask-bool: apply mask in len to byte at offs, result is a boolean
110 T_UBF, // unsigned bitfield, len is a BITFIELD() descriptor
111 T_INT, // signed little-endian integer
112 T_DBL, // 8-byte IEEE double
113 T_STR // fixed-length NUL-padded string
114};
115
116enum SS_TYPE
117{
118 SS_DIRECT, // number of direct children
119 SS_RECURSIVE, // number of all children, recursively
120 SS_RECURSIVE_MINUS_1 // same, but decrement the count first
121};
122
123// Describe a bitfield hosted in a field of the given width; first/last are
124// inclusive bit offsets counted from the LSB.
125constexpr uint32_t BITFIELD( uint32_t aWidth, uint32_t aFirst, uint32_t aLast )
126{
127 return ( aWidth << 16 ) | ( aFirst << 8 ) | aLast;
128}
129
130struct FMATCH
131{
132 int offs; // 0 terminates the list
133 unsigned len;
134 int val;
135};
136
137struct SUBSECT
138{
139 int offs; // 0 terminates the list
140 int len;
141 SS_TYPE ssType;
142 const char* treeName; // if set, wrap children in a synthetic subtree
143};
144
145struct ATTR
146{
147 const char* name; // nullptr terminates the list
148 ATTR_TYPE type;
149 int offs;
150 uint32_t len;
151};
152
153struct SCRIPT_ROW
154{
155 unsigned cmd, cmdMask; // matches when (block[0..1] & mask) == cmd
156 const char* name;
157 FMATCH fmatch[4];
158 SUBSECT subs[8];
159 ATTR attrs[32];
160};
161
162#define TERM_F \
163 { \
164 0, 0, 0 \
165 }
166#define TERM_S \
167 { \
168 0, 0, SS_DIRECT, nullptr \
169 }
170#define TERM_A \
171 { \
172 nullptr, T_INT, 0, 0 \
173 }
174
175// The format spec. Each row decodes one record kind; every offset is exact.
176const SCRIPT_ROW g_script[] = {
178 0xFF7F,
179 "drawing",
180 { TERM_F },
181 { { 4, 4, SS_RECURSIVE_MINUS_1, nullptr }, TERM_S },
182 { { "subsecs", T_INT, 2, 2 },
183 { "numsecs", T_INT, 4, 4 },
184 { "subsecsMSB", T_INT, 3, 1 },
185 { "subsecsLSB", T_INT, 2, 1 },
186 { "numsecsMSB2", T_INT, 7, 1 },
187 { "numsecsMSB1", T_INT, 6, 1 },
188 { "numsecsMSB0", T_INT, 5, 1 },
189 { "numsecsLSB", T_INT, 4, 1 },
190 { "v1", T_INT, 8, 1 },
191 { "v2", T_INT, 9, 1 },
192 TERM_A } },
193 { EGKW_SECT_UNKNOWN11, 0xFFFF, "unknown11", { TERM_F }, { TERM_S }, { TERM_A } },
195 0xFF7F,
196 "grid",
197 { TERM_F },
198 { TERM_S },
199 { { "display", T_BMB, 2, 0x01 },
200 { "visible", T_BMB, 2, 0x02 },
201 { "unit", T_UBF, 3, BITFIELD( 1, 0, 3 ) },
202 { "altunit", T_UBF, 3, BITFIELD( 1, 4, 7 ) },
203 { "multiple", T_INT, 4, 3 },
204 { "size", T_DBL, 8, 8 },
205 { "altsize", T_DBL, 16, 8 },
206 TERM_A } },
208 0xFF7F,
209 "layer",
210 { TERM_F },
211 { TERM_S },
212 { { "side", T_BMB, 2, 0x10 },
213 { "visible", T_UBF, 2, BITFIELD( 1, 2, 3 ) },
214 { "active", T_BMB, 2, 0x02 },
215 { "number", T_UBF, 3, BITFIELD( 1, 0, 7 ) },
216 { "other", T_INT, 4, 1 },
217 { "fill", T_UBF, 5, BITFIELD( 1, 0, 3 ) },
218 { "color", T_UBF, 6, BITFIELD( 1, 0, 5 ) },
219 { "name", T_STR, 15, 9 },
220 TERM_A } },
222 0xFF00,
223 "schema",
224 { TERM_F },
225 { { 4, 4, SS_DIRECT, nullptr }, TERM_S },
226 { { "shtsubsecs", T_INT, 8, 4 }, { "atrsubsecs", T_INT, 12, 4 }, { "xref_format", T_STR, 19, 5 }, TERM_A } },
228 0xFF7F,
229 "library",
230 { TERM_F },
231 { { 4, 4, SS_RECURSIVE, nullptr }, { 8, 4, SS_RECURSIVE, nullptr }, { 12, 4, SS_RECURSIVE, nullptr }, TERM_S },
232 { { "devsubsecs", T_INT, 4, 4 },
233 { "symsubsecs", T_INT, 8, 4 },
234 { "pacsubsecs", T_INT, 12, 4 },
235 { "children", T_INT, 8, 4 },
236 { "name", T_STR, 16, 8 },
237 TERM_A } },
239 0xFF7F,
240 "devices",
241 { TERM_F },
242 { { 4, 4, SS_DIRECT, nullptr }, TERM_S },
243 { { "children", T_INT, 8, 4 }, { "library", T_STR, 16, 8 }, TERM_A } },
245 0xFF7F,
246 "symbols",
247 { TERM_F },
248 { { 4, 4, SS_RECURSIVE, nullptr }, TERM_S },
249 { { "children", T_INT, 8, 4 }, { "library", T_STR, 16, 8 }, TERM_A } },
251 0xFF5F,
252 "packages",
253 { TERM_F },
254 { { 4, 4, SS_RECURSIVE, nullptr }, TERM_S },
255 { { "subsects", T_INT, 4, 4 },
256 { "children", T_INT, 8, 2 },
257 { "desc", T_STR, 10, 6 },
258 { "library", T_STR, 16, 8 },
259 TERM_A } },
261 0xFF00,
262 "schemasheet",
263 { TERM_F },
264 { { 2, 2, SS_DIRECT, nullptr }, TERM_S },
265 { { "minx", T_INT, 4, 2 },
266 { "miny", T_INT, 6, 2 },
267 { "maxx", T_INT, 8, 2 },
268 { "maxy", T_INT, 10, 2 },
269 { "partsubsecs", T_INT, 12, 4 },
270 { "bussubsecs", T_INT, 16, 4 },
271 { "netsubsecs", T_INT, 20, 4 },
272 TERM_A } },
274 0xFF37,
275 "board",
276 { TERM_F },
277 { { 12, 4, SS_RECURSIVE, "libraries" },
278 { 2, 2, SS_DIRECT, "plain" },
279 { 16, 4, SS_RECURSIVE, "elements" },
280 { 20, 4, SS_RECURSIVE, "signals" },
281 TERM_S },
282 { { "minx", T_INT, 4, 2 },
283 { "miny", T_INT, 6, 2 },
284 { "maxx", T_INT, 8, 2 },
285 { "maxy", T_INT, 10, 2 },
286 { "defsubsecs", T_INT, 12, 4 },
287 { "pacsubsecs", T_INT, 16, 4 },
288 { "netsubsecs", T_INT, 20, 4 },
289 TERM_A } },
291 0xFFB3,
292 "signal",
293 { TERM_F },
294 { { 2, 2, SS_DIRECT, nullptr }, TERM_S },
295 { { "minx", T_INT, 4, 2 },
296 { "miny", T_INT, 6, 2 },
297 { "maxx", T_INT, 8, 2 },
298 { "maxy", T_INT, 10, 2 },
299 { "airwires", T_BMB, 12, 0x02 },
300 { "netclass", T_UBF, 13, BITFIELD( 1, 0, 3 ) },
301 { "name", T_STR, 16, 8 },
302 TERM_A } },
304 0xFF7F,
305 "symbol",
306 { TERM_F },
307 { { 2, 2, SS_DIRECT, nullptr }, TERM_S },
308 { { "minx", T_INT, 4, 2 },
309 { "miny", T_INT, 6, 2 },
310 { "maxx", T_INT, 8, 2 },
311 { "maxy", T_INT, 10, 2 },
312 { "name", T_STR, 16, 8 },
313 TERM_A } },
314 // The package low byte is flags, not a layout selector, so match on the high byte alone;
315 // real libraries set bit 3 and bit 6 the bit-5-only mask rejected.
317 0xFF00,
318 "package",
319 { TERM_F },
320 { { 2, 2, SS_RECURSIVE, nullptr }, TERM_S },
321 { { "minx", T_INT, 4, 2 },
322 { "miny", T_INT, 6, 2 },
323 { "maxx", T_INT, 8, 2 },
324 { "maxy", T_INT, 10, 2 },
325 { "desc", T_STR, 13, 5 },
326 { "name", T_STR, 18, 6 },
327 TERM_A } },
329 0xFF00,
330 "schemanet",
331 { TERM_F },
332 { { 2, 2, SS_RECURSIVE, nullptr }, TERM_S },
333 { { "minx", T_INT, 4, 2 },
334 { "miny", T_INT, 6, 2 },
335 { "maxx", T_INT, 8, 2 },
336 { "maxy", T_INT, 10, 2 },
337 { "netclass", T_UBF, 13, BITFIELD( 1, 0, 3 ) },
338 { "name", T_STR, 16, 8 },
339 TERM_A } },
341 0xFF00,
342 "path",
343 { TERM_F },
344 { { 2, 2, SS_RECURSIVE, nullptr }, TERM_S },
345 { { "minx", T_INT, 4, 2 }, { "miny", T_INT, 6, 2 }, { "maxx", T_INT, 8, 2 }, { "maxy", T_INT, 10, 2 }, TERM_A } },
346 // The polygon low byte carries pour/rank flags, not a layout selector, so match on
347 // the high byte alone (real boards set bit 6 and others the narrow mask rejected).
349 0xFF00,
350 "polygon",
351 { TERM_F },
352 { { 2, 2, SS_DIRECT, nullptr }, TERM_S },
353 { { "minx", T_INT, 4, 2 },
354 { "miny", T_INT, 6, 2 },
355 { "maxx", T_INT, 8, 2 },
356 { "maxy", T_INT, 10, 2 },
357 { "width", T_INT, 12, 2 },
358 { "spacing", T_INT, 14, 2 },
359 { "isolate", T_INT, 16, 2 },
360 { "layer", T_UBF, 18, BITFIELD( 1, 0, 7 ) },
361 { "pour", T_BMB, 19, 0x01 },
362 { "rank", T_BMB, 19, BITFIELD( 1, 1, 3 ) },
363 { "thermals", T_BMB, 19, 0x80 },
364 { "orphans", T_BMB, 19, 0x40 },
365 TERM_A } },
367 0xFF00,
368 "wire",
369 { TERM_F },
370 { TERM_S },
371 { { "layer", T_UBF, 3, BITFIELD( 1, 0, 7 ) },
372 { "half_width", T_INT, 20, 2 },
373 { "stflags", T_BMB, 22, 0x33 },
374 { "clockwise", T_BMB, 22, 0x20 },
375 { "linetype", T_UBF, 23, BITFIELD( 1, 0, 7 ) },
376 { "linetype_0_x1", T_INT, 4, 4 },
377 { "linetype_0_y1", T_INT, 8, 4 },
378 { "linetype_0_x2", T_INT, 12, 4 },
379 { "linetype_0_y2", T_INT, 16, 4 },
380 { "arc_negflags", T_UBF, 19, BITFIELD( 1, 0, 4 ) },
381 { "arc_c1", T_INT, 7, 1 },
382 { "arc_c2", T_INT, 11, 1 },
383 { "arc_c3", T_INT, 15, 1 },
384 { "arc_x1", T_INT, 4, 3 },
385 { "arc_y1", T_INT, 8, 3 },
386 { "arc_x2", T_INT, 12, 3 },
387 { "arc_y2", T_INT, 16, 3 },
388 TERM_A } },
390 0xFF7F,
391 "arc",
392 { TERM_F },
393 { TERM_S },
394 { { "layer", T_UBF, 3, BITFIELD( 1, 0, 7 ) },
395 { "half_width", T_INT, 20, 2 },
396 { "clockwise", T_BMB, 22, 0x20 },
397 { "arctype", T_UBF, 23, BITFIELD( 1, 0, 7 ) },
398 { "arc_negflags", T_UBF, 19, BITFIELD( 1, 0, 7 ) },
399 { "arc_c1", T_INT, 7, 1 },
400 { "arc_c2", T_INT, 11, 1 },
401 { "arc_c3", T_INT, 15, 1 },
402 { "arc_x1", T_INT, 4, 3 },
403 { "arc_y1", T_INT, 8, 3 },
404 { "arc_x2", T_INT, 12, 3 },
405 { "arc_y2", T_INT, 16, 3 },
406 { "arctype_other_x1", T_INT, 4, 4 },
407 { "arctype_other_y1", T_INT, 8, 4 },
408 { "arctype_other_x2", T_INT, 12, 4 },
409 { "arctype_other_y2", T_INT, 16, 4 },
410 TERM_A } },
412 0xFF53,
413 "circle",
414 { TERM_F },
415 { TERM_S },
416 { { "layer", T_UBF, 3, BITFIELD( 1, 0, 7 ) },
417 { "x", T_INT, 4, 4 },
418 { "y", T_INT, 8, 4 },
419 { "radius", T_INT, 12, 4 },
420 { "half_width", T_INT, 20, 4 },
421 TERM_A } },
422 // The rectangle low byte is all flags; match on the high byte alone so the flag bits
423 // real boards set (0xa8, 0x8c, 0xa0) bind here instead of aborting the load.
425 0xFF00,
426 "rectangle",
427 { TERM_F },
428 { TERM_S },
429 { { "layer", T_UBF, 3, BITFIELD( 1, 0, 7 ) },
430 { "x1", T_INT, 4, 4 },
431 { "y1", T_INT, 8, 4 },
432 { "x2", T_INT, 12, 4 },
433 { "y2", T_INT, 16, 4 },
434 { "bin_rot", T_INT, 20, 2 },
435 TERM_A } },
437 0xFF00,
438 "junction",
439 { TERM_F },
440 { TERM_S },
441 { { "layer", T_UBF, 3, BITFIELD( 1, 0, 7 ) },
442 { "x", T_INT, 4, 4 },
443 { "y", T_INT, 8, 4 },
444 { "width_2", T_INT, 12, 2 },
445 TERM_A } },
447 0xFF53,
448 "hole",
449 { TERM_F },
450 { TERM_S },
451 { { "x", T_INT, 4, 4 },
452 { "y", T_INT, 8, 4 },
453 { "half_diameter", T_UBF, 12, BITFIELD( 2, 0, 15 ) },
454 { "half_drill", T_UBF, 12, BITFIELD( 2, 0, 15 ) },
455 TERM_A } },
456 // The via low byte is flags (the matched layout is identical for every value), so match
457 // on the high byte alone; real boards set bit 5 and others the bit-7-only mask rejected.
459 0xFF00,
460 "via",
461 { TERM_F },
462 { TERM_S },
463 { { "shape", T_INT, 2, 1 },
464 { "x", T_INT, 4, 4 },
465 { "y", T_INT, 8, 4 },
466 { "half_drill", T_UBF, 12, BITFIELD( 2, 0, 15 ) },
467 { "half_diameter", T_UBF, 14, BITFIELD( 2, 0, 15 ) },
468 { "layers", T_UBF, 16, BITFIELD( 1, 0, 7 ) },
469 { "stop", T_BMB, 17, 0x01 },
470 TERM_A } },
471 // Pads and SMDs use two record layouts distinguished only by bit 7 of the
472 // signature low byte, which the full-layout rows below discard along with the
473 // other low-byte flag bits. The bit-7-clear variant omits the rotation and flag
474 // words and stores the pad name inline at offset 16, exactly where the bit-7-set
475 // record keeps its rotation word, so without a dedicated row the shared offsets
476 // read the name bytes back as a bogus rotation. These bit-7-clear rows precede
477 // the masked rows so such a block binds here first; a bit-7-set block fails the
478 // match and falls through to the full-layout row. The bit-7-clear masks (0xFFDF
479 // pad, 0xFF80 smd) and the full-layout masks (0xFF5F pad, 0xFF00 smd) all ignore
480 // the low-byte flag bits real boards set, so only bit 7 selects the layout. The
481 // absent bin_rot leaves the pad unrotated, as the variant carries no angle.
483 0xFFDF,
484 "pad",
485 { TERM_F },
486 { TERM_S },
487 { { "shape", T_INT, 2, 1 },
488 { "x", T_INT, 4, 4 },
489 { "y", T_INT, 8, 4 },
490 { "half_drill", T_UBF, 12, BITFIELD( 2, 0, 15 ) },
491 { "half_diameter", T_UBF, 14, BITFIELD( 2, 0, 15 ) },
492 { "name", T_STR, 16, 8 },
493 TERM_A } },
495 0xFF80,
496 "smd",
497 { TERM_F },
498 { TERM_S },
499 { { "roundness", T_INT, 2, 1 },
500 { "layer", T_UBF, 3, BITFIELD( 1, 0, 7 ) },
501 { "x", T_INT, 4, 4 },
502 { "y", T_INT, 8, 4 },
503 { "half_dx", T_UBF, 12, BITFIELD( 2, 0, 15 ) },
504 { "half_dy", T_UBF, 14, BITFIELD( 2, 0, 15 ) },
505 { "name", T_STR, 16, 8 },
506 TERM_A } },
508 0xFF5F,
509 "pad",
510 { TERM_F },
511 { TERM_S },
512 { { "shape", T_INT, 2, 1 },
513 { "x", T_INT, 4, 4 },
514 { "y", T_INT, 8, 4 },
515 { "half_drill", T_UBF, 12, BITFIELD( 2, 0, 15 ) },
516 { "half_diameter", T_UBF, 14, BITFIELD( 2, 0, 15 ) },
517 { "bin_rot", T_INT, 16, 2 },
518 { "stop", T_BMB, 18, 0x01 },
519 { "thermals", T_BMB, 18, 0x04 },
520 { "first", T_BMB, 18, 0x08 },
521 { "name", T_STR, 19, 5 },
522 TERM_A } },
524 0xFF00,
525 "smd",
526 { TERM_F },
527 { TERM_S },
528 { { "roundness", T_INT, 2, 1 },
529 { "layer", T_UBF, 3, BITFIELD( 1, 0, 7 ) },
530 { "x", T_INT, 4, 4 },
531 { "y", T_INT, 8, 4 },
532 { "half_dx", T_UBF, 12, BITFIELD( 2, 0, 15 ) },
533 { "half_dy", T_UBF, 14, BITFIELD( 2, 0, 15 ) },
534 { "bin_rot", T_UBF, 16, BITFIELD( 2, 0, 11 ) },
535 { "stop", T_BMB, 18, 0x01 },
536 { "cream", T_BMB, 18, 0x02 },
537 { "thermals", T_BMB, 18, 0x04 },
538 { "first", T_BMB, 18, 0x08 },
539 { "name", T_STR, 19, 5 },
540 TERM_A } },
542 0xFF7F,
543 "pin",
544 { TERM_F },
545 { TERM_S },
546 { { "function", T_UBF, 2, BITFIELD( 1, 0, 1 ) },
547 { "visible", T_UBF, 2, BITFIELD( 1, 6, 7 ) },
548 { "x", T_INT, 4, 4 },
549 { "y", T_INT, 8, 4 },
550 { "direction", T_UBF, 12, BITFIELD( 1, 0, 3 ) },
551 { "length", T_UBF, 12, BITFIELD( 1, 4, 5 ) },
552 { "bin_rot", T_UBF, 12, BITFIELD( 1, 6, 7 ) },
553 { "swaplevel", T_INT, 13, 1 },
554 { "name", T_STR, 14, 10 },
555 TERM_A } },
557 0xFF7F,
558 "gate",
559 { TERM_F },
560 { TERM_S },
561 { { "x", T_INT, 4, 4 },
562 { "y", T_INT, 8, 4 },
563 { "addlevel", T_INT, 12, 1 },
564 { "swap", T_INT, 13, 1 },
565 { "symno", T_INT, 14, 2 },
566 { "name", T_STR, 16, 8 },
567 TERM_A } },
568 // Masking the element low byte with 0x53 leaks bit 6, so a real-world element
569 // with low byte 0x60 (spin plus another flag) fails to match. The whole low
570 // byte is flags here; rotation/mirror/spin are decoded from bytes 16-17, and
571 // 0x2e is a unique high byte, so match on the high byte alone.
573 0xFF00,
574 "element",
575 { TERM_F },
576 { { 2, 2, SS_DIRECT, nullptr }, TERM_S },
577 { { "x", T_INT, 4, 4 },
578 { "y", T_INT, 8, 4 },
579 { "library", T_INT, 12, 2 },
580 { "package", T_INT, 14, 2 },
581 { "bin_rot", T_UBF, 16, BITFIELD( 2, 0, 11 ) },
582 { "mirrored", T_BMB, 17, 0x10 },
583 { "spin", T_BMB, 17, 0x40 },
584 TERM_A } },
586 0xFF5F,
587 "element2",
588 { TERM_F },
589 { TERM_S },
590 { { "name", T_STR, 2, 8 }, { "value", T_STR, 10, 14 }, TERM_A } },
592 0xFF00,
593 "instance",
594 { TERM_F },
595 { { 2, 2, SS_DIRECT, nullptr }, TERM_S },
596 { { "x", T_INT, 4, 4 },
597 { "y", T_INT, 8, 4 },
598 { "placed", T_INT, 12, 2 },
599 { "gateno", T_INT, 14, 2 },
600 { "bin_rot", T_UBF, 16, BITFIELD( 2, 10, 11 ) },
601 { "mirrored", T_UBF, 16, BITFIELD( 2, 12, 12 ) },
602 { "smashed", T_BMB, 18, 0x01 },
603 TERM_A } },
605 0xFF53,
606 "text",
607 { TERM_F },
608 { TERM_S },
609 { { "layer", T_UBF, 3, BITFIELD( 1, 0, 7 ) },
610 { "x", T_INT, 4, 4 },
611 { "y", T_INT, 8, 4 },
612 { "half_size", T_INT, 12, 2 },
613 { "ratio", T_UBF, 14, BITFIELD( 2, 2, 6 ) },
614 { "bin_rot", T_UBF, 16, BITFIELD( 2, 0, 11 ) },
615 { "mirrored", T_UBF, 16, BITFIELD( 2, 12, 12 ) },
616 { "spin", T_UBF, 16, BITFIELD( 2, 14, 14 ) },
617 { "textfield", T_STR, 18, 5 },
618 TERM_A } },
619 // A text-family record whose inline 5-byte string did not fit spills the full
620 // string into a trailing 0x3200 record, with the bytes from offset 2 onward.
621 { EGKW_SECT_LONGTEXT, 0xFFFF, "longtext", { TERM_F }, { TERM_S }, { { "textfield", T_STR, 2, 22 }, TERM_A } },
623 0xFF00,
624 "netbuslabel",
625 { TERM_F },
626 { TERM_S },
627 { { "layer", T_UBF, 3, BITFIELD( 1, 0, 7 ) },
628 { "x", T_INT, 4, 4 },
629 { "y", T_INT, 8, 4 },
630 { "size", T_INT, 12, 2 },
631 { "ratio", T_UBF, 14, BITFIELD( 2, 2, 6 ) },
632 { "bin_rot", T_UBF, 16, BITFIELD( 2, 0, 11 ) },
633 { "mirrored", T_UBF, 16, BITFIELD( 2, 12, 12 ) },
634 { "spin", T_UBF, 16, BITFIELD( 2, 14, 14 ) },
635 { "textfield", T_STR, 18, 5 },
636 TERM_A } },
638 0xFF00,
639 "name",
640 { TERM_F },
641 { TERM_S },
642 { { "layer", T_UBF, 3, BITFIELD( 1, 0, 7 ) },
643 { "x", T_INT, 4, 4 },
644 { "y", T_INT, 8, 4 },
645 { "size", T_INT, 12, 2 },
646 { "ratio", T_UBF, 14, BITFIELD( 2, 2, 6 ) },
647 { "bin_rot", T_UBF, 16, BITFIELD( 2, 0, 11 ) },
648 { "mirrored", T_UBF, 16, BITFIELD( 2, 12, 12 ) },
649 { "spin", T_UBF, 16, BITFIELD( 2, 14, 14 ) },
650 { "textfield", T_STR, 18, 5 },
651 TERM_A } },
653 0xFF00,
654 "value",
655 { TERM_F },
656 { TERM_S },
657 { { "layer", T_UBF, 3, BITFIELD( 1, 0, 7 ) },
658 { "x", T_INT, 4, 4 },
659 { "y", T_INT, 8, 4 },
660 { "size", T_INT, 12, 2 },
661 { "ratio", T_UBF, 14, BITFIELD( 2, 2, 6 ) },
662 { "bin_rot", T_UBF, 16, BITFIELD( 2, 0, 11 ) },
663 { "mirrored", T_UBF, 16, BITFIELD( 2, 12, 12 ) },
664 { "spin", T_UBF, 16, BITFIELD( 2, 14, 14 ) },
665 { "textfield", T_STR, 18, 5 },
666 TERM_A } },
668 0xFF7F,
669 "packagevariant",
670 { TERM_F },
671 { { 2, 2, SS_DIRECT, nullptr }, TERM_S },
672 { { "package", T_INT, 4, 2 }, { "table", T_STR, 6, 13 }, { "name", T_STR, 19, 5 }, TERM_A } },
674 0xFF7F,
675 "device",
676 { TERM_F },
677 // Subsections stream in [variants, gates] order; the variant count lives at
678 // offset 4 and the gate count at offset 2 (matches pyeagle's DeviceSection).
679 { { 4, 2, SS_RECURSIVE, "variants" }, { 2, 2, SS_RECURSIVE, "gates" }, TERM_S },
680 { { "gates", T_INT, 2, 2 },
681 { "variants", T_INT, 4, 2 },
682 { "prefix", T_STR, 8, 5 },
683 { "desc", T_STR, 13, 5 },
684 { "name", T_STR, 18, 5 },
685 TERM_A } },
687 0xFF00,
688 "part",
689 { TERM_F },
690 { { 2, 2, SS_RECURSIVE, nullptr }, TERM_S },
691 { { "lib", T_INT, 4, 2 },
692 { "device", T_INT, 6, 2 },
693 { "variant", T_INT, 8, 1 },
694 { "technology", T_INT, 9, 2 },
695 { "name", T_STR, 11, 5 },
696 { "value", T_STR, 16, 8 },
697 TERM_A } },
698 { EGKW_SECT_SCHEMABUS, 0xFF00, nullptr, { TERM_F }, { TERM_S }, { TERM_A } },
699 { EGKW_SECT_VARIANTCONNECTIONS, 0xFF7F, "variantconnections", { TERM_F }, { TERM_S }, { TERM_A } },
700 { EGKW_SECT_SCHEMACONNECTION, 0xFF00, nullptr, { TERM_F }, { TERM_S }, { TERM_A } },
702 0xFF57,
703 "contactref",
704 { TERM_F },
705 { TERM_S },
706 { { "partnumber", T_INT, 4, 2 }, { "pin", T_INT, 6, 2 }, TERM_A } },
708 0xFF7F,
709 "smashedpart",
710 { TERM_F },
711 { TERM_S },
712 { { "layer", T_UBF, 3, BITFIELD( 1, 0, 7 ) },
713 { "x", T_INT, 4, 4 },
714 { "y", T_INT, 8, 4 },
715 { "size", T_INT, 12, 2 },
716 { "ratio", T_UBF, 14, BITFIELD( 2, 2, 6 ) },
717 { "bin_rot", T_UBF, 16, BITFIELD( 2, 0, 11 ) },
718 { "mirrored", T_UBF, 16, BITFIELD( 2, 12, 12 ) },
719 { "spin", T_UBF, 16, BITFIELD( 2, 14, 14 ) },
720 { "textfield", T_STR, 18, 5 },
721 TERM_A } },
723 0xFF7F,
724 "smashedgate",
725 { TERM_F },
726 { TERM_S },
727 { { "layer", T_UBF, 3, BITFIELD( 1, 0, 7 ) },
728 { "x", T_INT, 4, 4 },
729 { "y", T_INT, 8, 4 },
730 { "size", T_INT, 12, 2 },
731 { "ratio", T_UBF, 14, BITFIELD( 2, 2, 6 ) },
732 { "bin_rot", T_UBF, 16, BITFIELD( 2, 0, 11 ) },
733 { "mirrored", T_UBF, 16, BITFIELD( 2, 12, 12 ) },
734 { "spin", T_UBF, 16, BITFIELD( 2, 14, 14 ) },
735 { "textfield", T_STR, 18, 5 },
736 TERM_A } },
737 // The attribute low byte is flags like the other text-family records, so match on the
738 // high byte alone (real boards set bit 5, which the bit-7-only mask rejected).
740 0xFF00,
741 "attribute",
742 { TERM_F },
743 { TERM_S },
744 { { "layer", T_UBF, 3, BITFIELD( 1, 0, 7 ) },
745 { "x", T_INT, 4, 4 },
746 { "y", T_INT, 8, 4 },
747 { "size", T_INT, 12, 2 },
748 { "ratio", T_UBF, 14, BITFIELD( 2, 2, 6 ) },
749 { "bin_rot", T_UBF, 16, BITFIELD( 2, 0, 11 ) },
750 { "mirrored", T_UBF, 16, BITFIELD( 2, 12, 12 ) },
751 { "spin", T_UBF, 16, BITFIELD( 2, 14, 14 ) },
752 { "textfield", T_STR, 18, 5 },
753 TERM_A } },
755 0xFF7F,
756 "attribute-value",
757 { TERM_F },
758 { TERM_S },
759 { { "symbol", T_STR, 2, 5 }, { "attribute", T_STR, 7, 17 }, TERM_A } },
761 0xFF00,
762 "frame",
763 { TERM_F },
764 { TERM_S },
765 { { "layer", T_UBF, 3, BITFIELD( 1, 0, 7 ) },
766 { "x1", T_INT, 4, 4 },
767 { "y1", T_INT, 8, 4 },
768 { "x2", T_INT, 12, 4 },
769 { "y2", T_INT, 16, 4 },
770 { "cols", T_INT, 20, 1 },
771 { "rows", T_INT, 21, 1 },
772 { "borders", T_INT, 22, 1 },
773 TERM_A } },
775 0xFF7F,
776 "smashedxref",
777 { TERM_F },
778 { TERM_S },
779 { { "layer", T_UBF, 3, BITFIELD( 1, 0, 7 ) },
780 { "x", T_INT, 4, 4 },
781 { "y", T_INT, 8, 4 },
782 { "size", T_INT, 12, 2 },
783 { "ratio", T_UBF, 14, BITFIELD( 2, 2, 6 ) },
784 { "bin_rot", T_UBF, 16, BITFIELD( 2, 0, 11 ) },
785 { "mirrored", T_UBF, 16, BITFIELD( 2, 12, 12 ) },
786 { "spin", T_UBF, 16, BITFIELD( 2, 14, 14 ) },
787 { "textfield", T_STR, 18, 5 },
788 TERM_A } },
789
790 // unknown leaves
791 { 0x5300, 0xFFFF, nullptr, { TERM_F }, { TERM_S }, { TERM_A } },
792 { 0x2d84, 0xFFFF, nullptr, { TERM_F }, { TERM_S }, { TERM_A } },
793 { 0, 0, nullptr, { TERM_F }, { TERM_S }, { TERM_A } } // end of table
794};
795} // namespace
796
797
799{
800 auto child = std::make_unique<EGB_NODE>();
801 child->id = aId;
802 child->name = aName;
803 child->parent = this;
804 children.push_back( std::move( child ) );
805
806 return children.back().get();
807}
808
809
811{
812 aChild->parent = this;
813 children.push_back( std::move( aChild ) );
814
815 return children.back().get();
816}
817
818
819wxString EAGLE_BIN_PARSER::EGB_NODE::Prop( const wxString& aKey ) const
820{
821 auto it = props.find( aKey );
822 return it == props.end() ? wxString() : it->second;
823}
824
825
826long EAGLE_BIN_PARSER::EGB_NODE::PropLong( const wxString& aKey ) const
827{
828 long val = 0;
829 Prop( aKey ).ToLong( &val );
830
831 return val;
832}
833
834
835wxString EAGLE_BIN_PARSER::EGB_NODE::PropDoubled( const wxString& aKey ) const
836{
837 wxLongLong_t val = 0;
838 Prop( aKey ).ToLongLong( &val );
839
840 return wxString::Format( wxS( "%lld" ), val * 2 );
841}
842
843
845{
846 for( const auto& child : children )
847 {
848 if( child->id == aId )
849 return child.get();
850 }
851
852 return nullptr;
853}
854
855
857{
858 for( const auto& child : children )
859 {
860 if( child->name == aName )
861 return child.get();
862 }
863
864 return nullptr;
865}
866
867
870
871
872bool EAGLE_BIN_PARSER::IsBinaryEagle( wxInputStream& aStream )
873{
874 uint8_t buf[2] = { 0, 0 };
875
876 if( !aStream.IsOk() )
877 return false;
878
879 aStream.Read( buf, 2 );
880
881 if( aStream.LastRead() != 2 )
882 return false;
883
884 if( buf[0] == 0x10 && ( buf[1] == 0x00 || buf[1] == 0x80 ) )
885 return true;
886
887 return false;
888}
889
890
891void EAGLE_BIN_PARSER::requireBytes( size_t aOffs, size_t aLen ) const
892{
893 if( m_buf == nullptr || aOffs > m_buf->size() || aLen > m_buf->size() - aOffs )
894 THROW_IO_ERROR( _( "Short read in Eagle binary file (field out of bounds)." ) );
895}
896
897
898uint32_t EAGLE_BIN_PARSER::loadU32( size_t aOffs, unsigned aLen ) const
899{
900 requireBytes( aOffs, aLen );
901
902 uint32_t l = 0;
903
904 for( unsigned n = 0; n < aLen; n++ )
905 {
906 l <<= 8;
907 l |= ( *m_buf )[aOffs + aLen - n - 1];
908 }
909
910 return l;
911}
912
913
914int32_t EAGLE_BIN_PARSER::loadS32( size_t aOffs, unsigned aLen ) const
915{
916 requireBytes( aOffs, aLen );
917
918 uint32_t l = 0;
919
920 if( ( *m_buf )[aOffs + aLen - 1] & 0x80 )
921 l = 0xFFFFFFFF;
922
923 for( unsigned n = 0; n < aLen; n++ )
924 {
925 l <<= 8;
926 l |= ( *m_buf )[aOffs + aLen - n - 1];
927 }
928
929 return static_cast<int32_t>( l );
930}
931
932
933bool EAGLE_BIN_PARSER::loadBmb( size_t aOffs, uint32_t aMask ) const
934{
935 requireBytes( aOffs, 1 );
936
937 return ( ( *m_buf )[aOffs] & aMask ) != 0;
938}
939
940
941uint32_t EAGLE_BIN_PARSER::loadUbf( size_t aOffs, uint32_t aField ) const
942{
943 unsigned first = ( aField >> 8 ) & 0xff;
944 unsigned last = aField & 0xff;
945 uint32_t mask = ( 1u << ( last - first + 1 ) ) - 1;
946
947 // The high byte of the descriptor is the read length; keeping it inline ties
948 // the offset and field together rather than splitting them into locals.
949 uint32_t val = loadU32( aOffs, ( aField >> 16 ) & 0xff ) >> first;
950
951 return val & mask;
952}
953
954
955wxString EAGLE_BIN_PARSER::loadStr( size_t aOffs, unsigned aLen ) const
956{
957 requireBytes( aOffs, aLen );
958
959 const char* start = reinterpret_cast<const char*>( m_buf->data() + aOffs );
960
961 // The field is fixed length and NUL padded; stop at the first NUL but never
962 // run past the field.
963 size_t n = 0;
964
965 while( n < aLen && start[n] != '\0' )
966 n++;
967
968 return wxString::FromUTF8( start, n );
969}
970
971
972double EAGLE_BIN_PARSER::loadDouble( size_t aOffs ) const
973{
974 static_assert( sizeof( double ) == 8, "Eagle binary doubles are 8-byte IEEE-754" );
975
976 requireBytes( aOffs, sizeof( double ) );
977
978 // The file stores a little-endian IEEE-754 double. Assemble the bit pattern
979 // from individual bytes so decoding does not depend on host byte order, then
980 // reinterpret those bits as a double.
981 uint64_t bits = 0;
982
983 for( unsigned n = 0; n < sizeof( double ); n++ )
984 bits |= static_cast<uint64_t>( ( *m_buf )[aOffs + n] ) << ( 8 * n );
985
986 double d = 0.0;
987 memcpy( &d, &bits, sizeof( d ) );
988
989 return d;
990}
991
992
993int EAGLE_BIN_PARSER::readBlock( long& aNumBlocks, EGB_NODE* aParent )
994{
995 // Over-counted subsection counts can drive the walk past the last block onto the
996 // free-text sentinel, which readNotes() owns. Returning zero here lets callers
997 // collapse the remaining phantom iterations. Checked before the 24-byte guard
998 // because the free-text section can be shorter than a block header.
999 if( m_pos + 2 <= m_buf->size() && ( *m_buf )[m_pos] == 0x13 && ( *m_buf )[m_pos + 1] == 0x12 )
1000 {
1001 aNumBlocks = 0;
1002 return 0;
1003 }
1004
1005 if( m_pos + 24 > m_buf->size() )
1006 THROW_IO_ERROR( _( "Short read in Eagle binary file (truncated block)." ) );
1007
1008 size_t blockStart = m_pos;
1009 m_pos += 24;
1010
1011 int processed = 1;
1012
1013 // The top-level drawing record carries the total block count.
1014 if( aNumBlocks < 0 && ( *m_buf )[blockStart] == 0x10 )
1015 aNumBlocks = loadS32( blockStart + 4, 4 );
1016
1017 const SCRIPT_ROW* sc = nullptr;
1018
1019 for( const SCRIPT_ROW* row = g_script; row->cmd != 0; row++ )
1020 {
1021 unsigned cmdh = ( row->cmd >> 8 ) & 0xFF;
1022 unsigned cmdl = row->cmd & 0xFF;
1023 unsigned mskh = ( row->cmdMask >> 8 ) & 0xFF;
1024 unsigned mskl = row->cmdMask & 0xFF;
1025
1026 if( ( cmdh != ( ( *m_buf )[blockStart] & mskh ) ) || ( cmdl != ( ( *m_buf )[blockStart + 1] & mskl ) ) )
1027 {
1028 continue;
1029 }
1030
1031 bool match = true;
1032
1033 for( const FMATCH* fm = row->fmatch; fm->offs != 0; fm++ )
1034 {
1035 if( loadS32( blockStart + fm->offs, fm->len ) != fm->val )
1036 {
1037 match = false;
1038 break;
1039 }
1040 }
1041
1042 if( match )
1043 {
1044 sc = row;
1045 break;
1046 }
1047 }
1048
1049 if( sc == nullptr )
1050 {
1051 THROW_IO_ERROR( wxString::Format( _( "Unknown Eagle binary block id 0x%02x%02x at offset %zu." ),
1052 (unsigned) ( *m_buf )[blockStart], (unsigned) ( *m_buf )[blockStart + 1],
1053 blockStart ) );
1054 }
1055
1056 EGB_NODE* node =
1057 aParent->AddChild( static_cast<int>( sc->cmd ),
1058 sc->name ? wxString::FromUTF8( sc->name ) : wxString( wxS( "UNKNOWN" ) ) );
1059
1060 for( const ATTR* at = sc->attrs; at->name != nullptr; at++ )
1061 {
1062 wxString val;
1063
1064 switch( at->type )
1065 {
1066 // KiCad's Eagle XML reader parses boolean attributes as "yes"/"no", so
1067 // emit T_BMB fields that way rather than "1"/"0".
1068 case T_BMB: val = loadBmb( blockStart + at->offs, at->len ) ? wxS( "yes" ) : wxS( "no" ); break;
1069 case T_UBF: val = wxString::Format( wxS( "%u" ), loadUbf( blockStart + at->offs, at->len ) ); break;
1070 case T_INT: val = wxString::Format( wxS( "%d" ), loadS32( blockStart + at->offs, at->len ) ); break;
1071 case T_DBL: val = wxString::FromCDouble( loadDouble( blockStart + at->offs ) ); break;
1072 case T_STR:
1073 {
1074 size_t foff = blockStart + at->offs;
1075 val = loadStr( foff, at->len );
1076
1077 // A 0x7F-marked field defers to a 32-bit little-endian pointer into the
1078 // free-text blob. Record the raw pointer (loadStr would NUL-truncate one
1079 // whose high byte is zero) while keeping the inline value for fallback.
1080 if( foff + 5 <= m_buf->size() && ( *m_buf )[foff] == 0x7F )
1081 m_longRefs.push_back( { node, wxString::FromUTF8( at->name ), loadU32( foff + 1, 4 ) } );
1082
1083 break;
1084 }
1085 }
1086
1087 node->props[wxString::FromUTF8( at->name )] = val;
1088 }
1089
1090 aNumBlocks--;
1091
1092 for( const SUBSECT* ss = sc->subs; ss->offs != 0; ss++ )
1093 {
1094 uint32_t numch = loadU32( blockStart + ss->offs, ss->len );
1095 EGB_NODE* lpar = node;
1096
1097 if( ss->treeName != nullptr )
1098 lpar = node->AddChild( 0, wxString::FromUTF8( ss->treeName ) );
1099
1100 if( ss->ssType == SS_DIRECT )
1101 {
1102 for( uint32_t n = 0; n < numch && aNumBlocks > 0; n++ )
1103 {
1104 int res = readBlock( aNumBlocks, lpar );
1105
1106 if( res == 0 )
1107 break;
1108
1109 processed += res;
1110 }
1111 }
1112 else
1113 {
1114 if( ss->ssType == SS_RECURSIVE_MINUS_1 && numch > 0 )
1115 numch--;
1116
1117 long rem = numch;
1118
1119 for( uint32_t n = 0; n < numch && rem > 0; n++ )
1120 {
1121 int res = readBlock( rem, lpar );
1122
1123 if( res == 0 )
1124 break;
1125
1126 aNumBlocks -= res;
1127 processed += res;
1128 }
1129 }
1130 }
1131
1132 return processed;
1133}
1134
1135
1137{
1138 m_freeText.clear();
1139 m_freeTextCursor = 0;
1140
1141 if( m_pos + 8 > m_buf->size() )
1142 return false;
1143
1144 // The free-text section starts with the 0x1312 sentinel.
1145 if( ( *m_buf )[m_pos] != 0x13 || ( *m_buf )[m_pos + 1] != 0x12 )
1146 return false;
1147
1148 int textLen = loadS32( m_pos + 4, 2 );
1149 m_pos += 8;
1150
1151 if( textLen < 0 )
1152 return false;
1153
1154 // A trailing 4-byte checksum follows the text payload.
1155 size_t total = static_cast<size_t>( textLen ) + 4;
1156
1157 if( m_pos + total > m_buf->size() )
1158 return false;
1159
1160 // Split the blob into NUL-delimited strings; an empty string terminates.
1161 // Each string is also keyed by its byte offset in the blob so deferred 0x7F
1162 // pointer references can be resolved directly.
1163 size_t blobStart = m_pos;
1164 size_t end = m_pos + total;
1165 size_t cur = m_pos;
1166
1167 m_freeTextByOffset.clear();
1168
1169 while( cur < end && ( *m_buf )[cur] != '\0' )
1170 {
1171 size_t s = cur;
1172
1173 while( cur < end && ( *m_buf )[cur] != '\0' )
1174 cur++;
1175
1176 wxString str = wxString::FromUTF8( reinterpret_cast<const char*>( m_buf->data() + s ),
1177 cur - s );
1178 m_freeText.push_back( str );
1179 m_freeTextByOffset[s - blobStart] = str;
1180 cur++; // skip the NUL
1181 }
1182
1183 m_pos = end;
1184 return true;
1185}
1186
1187
1189{
1190 if( m_freeTextCursor >= m_freeText.size() )
1191 {
1192 wxLogTrace( traceEagleIo, wxS( "Eagle bin: free-text reference out of strings" ) );
1193 m_invalidText = wxS( "<invalid>" );
1194 return m_invalidText;
1195 }
1196
1197 return m_freeText[m_freeTextCursor++];
1198}
1199
1200
1202{
1203 if( m_freeTextByOffset.empty() || m_longRefs.empty() )
1204 return;
1205
1206 // The pointers are absolute addresses with an unstored base, so recover it by
1207 // consensus: the most common (pointer - boundary) difference is the base. A file
1208 // can reference several blob regions with different bases, so iterate, resolving
1209 // the dominant base's references each round until no more can be placed.
1210 std::vector<bool> done( m_longRefs.size(), false );
1211 std::vector<wxString> value( m_longRefs.size() );
1212
1213 while( true )
1214 {
1215 std::map<long long, int> votes;
1216
1217 for( size_t i = 0; i < m_longRefs.size(); i++ )
1218 {
1219 if( done[i] )
1220 continue;
1221
1222 for( const auto& [offset, str] : m_freeTextByOffset )
1223 {
1224 if( offset > m_longRefs[i].ptr )
1225 break;
1226
1227 votes[static_cast<long long>( m_longRefs[i].ptr ) - static_cast<long long>( offset )]++;
1228 }
1229 }
1230
1231 long long base = -1;
1232 int best = 0;
1233
1234 for( const auto& [cand, count] : votes )
1235 {
1236 if( count > best )
1237 {
1238 best = count;
1239 base = cand;
1240 }
1241 }
1242
1243 if( base < 0 )
1244 break;
1245
1246 int progress = 0;
1247
1248 for( size_t i = 0; i < m_longRefs.size(); i++ )
1249 {
1250 if( done[i] )
1251 continue;
1252
1253 auto it = m_freeTextByOffset.find(
1254 static_cast<size_t>( static_cast<long long>( m_longRefs[i].ptr ) - base ) );
1255
1256 if( it != m_freeTextByOffset.end() )
1257 {
1258 value[i] = it->second;
1259 done[i] = true;
1260 progress++;
1261 }
1262 }
1263
1264 if( progress == 0 )
1265 break;
1266 }
1267
1268 // Commit only if every reference resolved: a partial mix would desync the
1269 // sequential fallback for the misses, so in that case leave all the inline 0x7F
1270 // markers for postprocFreeText() to resolve in order instead.
1271 bool allResolved = std::all_of( done.begin(), done.end(), []( bool d ) { return d; } );
1272
1273 if( allResolved )
1274 {
1275 for( size_t i = 0; i < m_longRefs.size(); i++ )
1276 m_longRefs[i].node->props[m_longRefs[i].field] = value[i];
1277 }
1278}
1279
1280
1282{
1283 if( m_pos + 4 > m_buf->size() )
1284 return false;
1285
1286 // DRC start sentinel 0x10 0x04 0x00 0x20.
1287 if( !( ( *m_buf )[m_pos] == 0x10 && ( *m_buf )[m_pos + 1] == 0x04 && ( *m_buf )[m_pos + 2] == 0x00
1288 && ( *m_buf )[m_pos + 3] == 0x20 ) )
1289 {
1290 return false;
1291 }
1292
1293 m_pos += 4;
1294
1295 // Walk the variable-length preamble looking for the 0x12345678 end marker
1296 // that immediately follows a NUL byte.
1297 bool found = false;
1298
1299 while( !found )
1300 {
1301 if( m_pos + 1 > m_buf->size() )
1302 return false;
1303
1304 uint8_t c = ( *m_buf )[m_pos++];
1305
1306 if( c == '\0' )
1307 {
1308 if( m_pos + 4 > m_buf->size() )
1309 return false;
1310
1311 if( ( *m_buf )[m_pos] == 0x78 && ( *m_buf )[m_pos + 1] == 0x56 && ( *m_buf )[m_pos + 2] == 0x34
1312 && ( *m_buf )[m_pos + 3] == 0x12 )
1313 {
1314 found = true;
1315 }
1316
1317 m_pos += 4;
1318 }
1319 }
1320
1321 const size_t kDrcLen = 244;
1322
1323 if( m_pos + kDrcLen > m_buf->size() )
1324 return false;
1325
1326 size_t b = m_pos;
1327
1328 auto mil = [&]( size_t aOffs ) -> long
1329 {
1330 return static_cast<long>( loadS32( b + aOffs, 4 ) / 2.54 / 100 );
1331 };
1332
1333 aDrc.mdWireWire = mil( 0 );
1334 aDrc.msWidth = mil( 64 );
1335 aDrc.rvPadTop = loadDouble( b + 84 );
1336 aDrc.rvPadInner = loadDouble( b + 92 );
1337 aDrc.rvPadBottom = loadDouble( b + 100 );
1338
1339 m_pos += kDrcLen;
1340 return true;
1341}
1342
1343
1344void EAGLE_BIN_PARSER::fixLongText( EGB_NODE* aNode, const wxString& aField )
1345{
1346 auto it = aNode->props.find( aField );
1347
1348 if( it == aNode->props.end() || it->second.IsEmpty() )
1349 return;
1350
1351 // A leading 0x7F byte marks a deferred long-text reference into the notes that
1352 // pointer resolution could not place; fall back to sequential order. Compare the
1353 // raw code unit (resolved fields can now hold non-ASCII text whose first
1354 // character would assert if cast to a single byte).
1355 if( it->second[0].GetValue() == 0x7F )
1356 it->second = nextLongText();
1357}
1358
1359
1360void EAGLE_BIN_PARSER::arcDecode( EGB_NODE* aElem, int aArcType, int aLineType )
1361{
1362 auto fixThreeByte = []( long num, bool neg ) -> long
1363 {
1364 if( num < 0 && neg )
1365 return num;
1366 else if( num > 0 && neg )
1367 return num - 0x800000;
1368 else if( num < 0 && !neg )
1369 return num + 0x800000;
1370
1371 return num;
1372 };
1373
1374 auto fixOneByte = []( long num ) -> long
1375 {
1376 return num < 0 ? num + 0x80 : num;
1377 };
1378
1379 auto setLong = [&]( const wxString& aKey, long aVal )
1380 {
1381 aElem->props[aKey] = wxString::Format( wxS( "%ld" ), aVal );
1382 };
1383
1384 // Linear interpolation of the unconstrained center coordinate. The 64-bit
1385 // product cannot overflow (Eagle stores 24-bit coordinates) while a plain
1386 // long would on 32-bit platforms, and integer division truncates toward zero.
1387 auto interpolate = []( int64_t aNumerator, int64_t aSpan, int64_t aDivisor, int64_t aOffset ) -> long
1388 {
1389 return static_cast<long>( aNumerator * aSpan / aDivisor + aOffset );
1390 };
1391
1392 if( aLineType == 129 || aArcType == 0 )
1393 {
1394 long arcFlags = aElem->PropLong( wxS( "arc_negflags" ) );
1395 long x1 = fixThreeByte( aElem->PropLong( wxS( "arc_x1" ) ), arcFlags & 0x02 );
1396 long y1 = fixThreeByte( aElem->PropLong( wxS( "arc_y1" ) ), arcFlags & 0x04 );
1397 long x2 = fixThreeByte( aElem->PropLong( wxS( "arc_x2" ) ), arcFlags & 0x08 );
1398 long y2 = fixThreeByte( aElem->PropLong( wxS( "arc_y2" ) ), arcFlags & 0x10 );
1399
1400 long c = fixOneByte( aElem->PropLong( wxS( "arc_c1" ) ) )
1401 + 256 * fixOneByte( aElem->PropLong( wxS( "arc_c2" ) ) )
1402 + 256L * 256 * fixOneByte( aElem->PropLong( wxS( "arc_c3" ) ) );
1403 c = fixThreeByte( c, arcFlags & 0x01 );
1404
1405 setLong( wxS( "x1" ), x1 );
1406 setLong( wxS( "y1" ), y1 );
1407 setLong( wxS( "x2" ), x2 );
1408 setLong( wxS( "y2" ), y2 );
1409
1410 long x3 = ( x1 + x2 ) / 2;
1411 long y3 = ( y1 + y2 ) / 2;
1412 long cx = 0, cy = 0;
1413
1414 if( x1 == x2 && y1 == y2 )
1415 {
1416 // Degenerate arc with coincident endpoints; both interpolation
1417 // branches would divide by zero, so collapse it to a point.
1418 cx = x1;
1419 cy = y1;
1420 }
1421 else if( std::abs( x2 - x1 ) < std::abs( y2 - y1 ) )
1422 {
1423 cx = c;
1424 cy = interpolate( x3 - cx, x2 - x1, y2 - y1, y3 );
1425 }
1426 else
1427 {
1428 cy = c;
1429 cx = interpolate( y3 - cy, y2 - y1, x2 - x1, x3 );
1430 }
1431
1432 long radius = static_cast<long>( std::hypot( cx - x2, cy - y2 ) );
1433 setLong( wxS( "radius" ), radius );
1434 setLong( wxS( "x" ), cx );
1435 setLong( wxS( "y" ), cy );
1436
1437 if( cx == x2 && cy == y1 && x2 < x1 && y2 > y1 )
1438 {
1439 aElem->props[wxS( "StartAngle" )] = wxS( "90" );
1440 aElem->props[wxS( "Delta" )] = wxS( "90" );
1441 }
1442 else if( cx == x1 && cy == y2 && x2 < x1 && y1 > y2 )
1443 {
1444 aElem->props[wxS( "StartAngle" )] = wxS( "0" );
1445 aElem->props[wxS( "Delta" )] = wxS( "90" );
1446 }
1447 else if( cx == x2 && cy == y1 && x2 > x1 && y1 > y2 )
1448 {
1449 aElem->props[wxS( "StartAngle" )] = wxS( "270" );
1450 aElem->props[wxS( "Delta" )] = wxS( "90" );
1451 }
1452 else if( cx == x1 && cy == y2 && x2 > x1 && y2 > y1 )
1453 {
1454 aElem->props[wxS( "StartAngle" )] = wxS( "180" );
1455 aElem->props[wxS( "Delta" )] = wxS( "90" );
1456 }
1457 else
1458 {
1459 double theta1 = 180.0 - 180.0 / M_PI * atan2( cy - y1, x1 - cx );
1460 double theta2 = 180.0 - 180.0 / M_PI * atan2( cy - y2, x2 - cx );
1461 double deltaTheta = theta2 - theta1;
1462
1463 while( theta1 > 360 )
1464 theta1 -= 360;
1465
1466 while( deltaTheta < -180 )
1467 deltaTheta += 360;
1468
1469 while( deltaTheta > 180 )
1470 deltaTheta -= 360;
1471
1472 setLong( wxS( "StartAngle" ), static_cast<long>( theta1 ) );
1473 setLong( wxS( "Delta" ), static_cast<long>( deltaTheta ) );
1474 }
1475 }
1476 else if( ( aLineType > 0 && aLineType < 129 ) || aArcType > 0 )
1477 {
1478 long x1 = 0, y1 = 0, x2 = 0, y2 = 0, cx = 0, cy = 0;
1479
1480 if( aElem->HasProp( wxS( "arctype_other_x1" ) ) )
1481 {
1482 x1 = aElem->PropLong( wxS( "arctype_other_x1" ) );
1483 y1 = aElem->PropLong( wxS( "arctype_other_y1" ) );
1484 x2 = aElem->PropLong( wxS( "arctype_other_x2" ) );
1485 y2 = aElem->PropLong( wxS( "arctype_other_y2" ) );
1486 }
1487 else
1488 {
1489 x1 = aElem->PropLong( wxS( "linetype_0_x1" ) );
1490 y1 = aElem->PropLong( wxS( "linetype_0_y1" ) );
1491 x2 = aElem->PropLong( wxS( "linetype_0_x2" ) );
1492 y2 = aElem->PropLong( wxS( "linetype_0_y2" ) );
1493 }
1494
1495 bool cxyOk = true;
1496 auto setAngles = [&]( const char* aStart, const char* aDelta )
1497 {
1498 aElem->props[wxS( "StartAngle" )] = wxString::FromUTF8( aStart );
1499 aElem->props[wxS( "Delta" )] = wxString::FromUTF8( aDelta );
1500 };
1501
1502 if( aLineType == 0x78 || aArcType == 0x01 )
1503 {
1504 cx = std::min( x1, x2 );
1505 cy = std::min( y1, y2 );
1506 setAngles( "180", "90" );
1507 }
1508 else if( aLineType == 0x79 || aArcType == 0x02 )
1509 {
1510 cx = std::max( x1, x2 );
1511 cy = std::min( y1, y2 );
1512 setAngles( "270", "90" );
1513 }
1514 else if( aLineType == 0x7a || aArcType == 0x03 )
1515 {
1516 cx = std::max( x1, x2 );
1517 cy = std::max( y1, y2 );
1518 setAngles( "0", "90" );
1519 }
1520 else if( aLineType == 0x7b || aArcType == 0x04 )
1521 {
1522 cx = std::min( x1, x2 );
1523 cy = std::max( y1, y2 );
1524 setAngles( "90", "90" );
1525 }
1526 else if( aLineType == 0x7c || aArcType == 0x05 )
1527 {
1528 cx = ( x1 + x2 ) / 2;
1529 cy = ( y1 + y2 ) / 2;
1530 setAngles( "90", "180" );
1531 }
1532 else if( aLineType == 0x7d || aArcType == 0x06 )
1533 {
1534 cx = ( x1 + x2 ) / 2;
1535 cy = ( y1 + y2 ) / 2;
1536 setAngles( "270", "180" );
1537 }
1538 else if( aLineType == 0x7e || aArcType == 0x07 )
1539 {
1540 cx = ( x1 + x2 ) / 2;
1541 cy = ( y1 + y2 ) / 2;
1542 setAngles( "180", "180" );
1543 }
1544 else if( aLineType == 0x7f || aArcType == 0x08 )
1545 {
1546 cx = ( x1 + x2 ) / 2;
1547 cy = ( y1 + y2 ) / 2;
1548 setAngles( "0", "180" );
1549 }
1550 else
1551 {
1552 cxyOk = false;
1553 }
1554
1555 if( !cxyOk )
1556 cx = cy = 0;
1557
1558 long radius = static_cast<long>( std::hypot( cx - x2, cy - y2 ) );
1559 setLong( wxS( "radius" ), radius );
1560 setLong( wxS( "x" ), cx );
1561 setLong( wxS( "y" ), cy );
1562 }
1563}
1564
1565
1567{
1568 if( aRoot->id == EGKW_SECT_LINE )
1569 {
1570 int lineType = aRoot->HasProp( wxS( "linetype" ) ) ? (int) aRoot->PropLong( wxS( "linetype" ) ) : -1;
1571
1572 if( lineType >= 0 )
1573 {
1574 // Straight and arc wires both keep their endpoints in the linetype_0
1575 // fields.
1576 aRoot->props[wxS( "x1" )] = aRoot->Prop( wxS( "linetype_0_x1" ) );
1577 aRoot->props[wxS( "y1" )] = aRoot->Prop( wxS( "linetype_0_y1" ) );
1578 aRoot->props[wxS( "x2" )] = aRoot->Prop( wxS( "linetype_0_x2" ) );
1579 aRoot->props[wxS( "y2" )] = aRoot->Prop( wxS( "linetype_0_y2" ) );
1580 aRoot->props[wxS( "width" )] = aRoot->PropDoubled( wxS( "half_width" ) );
1581 }
1582
1583 if( lineType > 0 )
1584 {
1585 // arcDecode adds the center, radius and swept angle. KiCad's wire
1586 // reader needs the endpoints plus a "curve" (the swept angle).
1587 arcDecode( aRoot, -1, lineType );
1588
1589 // A zero swept angle is a straight segment; emitting curve="0" would abort the
1590 // shared wire reader in ConvertArcCenter, so leave the wire uncurved.
1591 if( aRoot->HasProp( wxS( "Delta" ) ) && aRoot->PropLong( wxS( "Delta" ) ) != 0 )
1592 aRoot->props[wxS( "curve" )] = aRoot->Prop( wxS( "Delta" ) );
1593 }
1594 }
1595
1596 for( const auto& child : aRoot->children )
1597 postprocWires( child.get() );
1598}
1599
1600
1602{
1603 if( aRoot->id == EGKW_SECT_ARC )
1604 {
1605 int arcType = aRoot->HasProp( wxS( "arctype" ) ) ? (int) aRoot->PropLong( wxS( "arctype" ) ) : -1;
1606
1607 if( arcType == 0 )
1608 {
1609 aRoot->props[wxS( "x1" )] = aRoot->Prop( wxS( "arc_x1" ) );
1610 aRoot->props[wxS( "y1" )] = aRoot->Prop( wxS( "arc_y1" ) );
1611 aRoot->props[wxS( "x2" )] = aRoot->Prop( wxS( "arc_x2" ) );
1612 aRoot->props[wxS( "y2" )] = aRoot->Prop( wxS( "arc_y2" ) );
1613 }
1614 else if( arcType > 0 )
1615 {
1616 aRoot->props[wxS( "x1" )] = aRoot->Prop( wxS( "arctype_other_x1" ) );
1617 aRoot->props[wxS( "y1" )] = aRoot->Prop( wxS( "arctype_other_y1" ) );
1618 aRoot->props[wxS( "x2" )] = aRoot->Prop( wxS( "arctype_other_x2" ) );
1619 aRoot->props[wxS( "y2" )] = aRoot->Prop( wxS( "arctype_other_y2" ) );
1620 }
1621
1622 if( arcType >= 0 )
1623 aRoot->props[wxS( "width" )] = aRoot->PropDoubled( wxS( "half_width" ) );
1624
1625 arcDecode( aRoot, arcType, -1 );
1626
1627 // A zero swept angle is a straight segment; emitting curve="0" would abort the
1628 // shared reader in ConvertArcCenter, so leave it uncurved.
1629 if( aRoot->HasProp( wxS( "Delta" ) ) && aRoot->PropLong( wxS( "Delta" ) ) != 0 )
1630 aRoot->props[wxS( "curve" )] = aRoot->Prop( wxS( "Delta" ) );
1631 }
1632
1633 for( const auto& child : aRoot->children )
1634 postprocArcs( child.get() );
1635}
1636
1637
1639{
1640 if( aRoot->id == EGKW_SECT_VIA )
1641 {
1642 // KiCad requires an "extent" layer-range string. The binary layers byte
1643 // is not a 1:1 map and pre-v6 vias are through-hole (0xF0 sentinel), so
1644 // span the full copper stack. Blind/buried vias are out of scope.
1645 aRoot->props[wxS( "extent" )] = wxS( "1-16" );
1646 }
1647
1648 for( const auto& child : aRoot->children )
1649 postprocVias( child.get() );
1650}
1651
1652
1654{
1655 // Binary coordinates are decimicrons (0.1 um); KiCad's XML reader assumes
1656 // millimetres for unitless values. Rewrite every dimensional attribute as a
1657 // millimetre decimal so the reader scales it correctly. Counts, layer
1658 // numbers, ratios, angles and booleans are left untouched.
1659 static const wxString dimAttrs[] = { wxS( "x" ), wxS( "y" ), wxS( "x1" ), wxS( "y1" ),
1660 wxS( "x2" ), wxS( "y2" ), wxS( "x3" ), wxS( "y3" ),
1661 wxS( "width" ), wxS( "drill" ), wxS( "diameter" ), wxS( "radius" ),
1662 wxS( "size" ), wxS( "dx" ), wxS( "dy" ), wxS( "spacing" ),
1663 wxS( "isolate" ) };
1664
1665 for( const wxString& key : dimAttrs )
1666 {
1667 auto it = aRoot->props.find( key );
1668
1669 if( it == aRoot->props.end() )
1670 continue;
1671
1672 double du = 0;
1673
1674 if( it->second.ToCDouble( &du ) )
1675 it->second = wxString::FromCDouble( du * 0.0001, 4 );
1676 }
1677
1678 for( const auto& child : aRoot->children )
1679 postprocUnits( child.get() );
1680}
1681
1682
1684{
1685 if( aRoot->id == EGKW_SECT_CIRCLE && aRoot->HasProp( wxS( "half_width" ) ) )
1686 {
1687 aRoot->props[wxS( "width" )] = aRoot->PropDoubled( wxS( "half_width" ) );
1688 }
1689
1690 for( const auto& child : aRoot->children )
1691 postprocCircles( child.get() );
1692}
1693
1694
1696{
1697 if( aRoot->id == EGKW_SECT_SMD )
1698 {
1699 if( aRoot->HasProp( wxS( "half_dx" ) ) )
1700 {
1701 aRoot->props[wxS( "dx" )] = aRoot->PropDoubled( wxS( "half_dx" ) );
1702 }
1703
1704 if( aRoot->HasProp( wxS( "half_dy" ) ) )
1705 {
1706 aRoot->props[wxS( "dy" )] = aRoot->PropDoubled( wxS( "half_dy" ) );
1707 }
1708 }
1709
1710 for( const auto& child : aRoot->children )
1711 postprocSmd( child.get() );
1712}
1713
1714
1716{
1717 if( aRoot->id == EGKW_SECT_PAD || aRoot->id == EGKW_SECT_HOLE || aRoot->id == EGKW_SECT_VIA
1718 || aRoot->id == EGKW_SECT_TEXT )
1719 {
1720 if( aRoot->HasProp( wxS( "half_drill" ) ) )
1721 {
1722 aRoot->props[wxS( "drill" )] = aRoot->PropDoubled( wxS( "half_drill" ) );
1723 }
1724
1725 if( aRoot->HasProp( wxS( "half_diameter" ) ) )
1726 {
1727 aRoot->props[wxS( "diameter" )] = aRoot->PropDoubled( wxS( "half_diameter" ) );
1728 }
1729
1730 if( aRoot->HasProp( wxS( "half_size" ) ) )
1731 {
1732 aRoot->props[wxS( "size" )] = aRoot->PropDoubled( wxS( "half_size" ) );
1733 }
1734 }
1735
1736 for( const auto& child : aRoot->children )
1737 postprocDimensions( child.get() );
1738}
1739
1740
1742{
1743 switch( aId )
1744 {
1745 case EGKW_SECT_SMD:
1746 case EGKW_SECT_PIN:
1748 case EGKW_SECT_PAD:
1749 case EGKW_SECT_TEXT:
1757 case EGKW_SECT_INSTANCE:
1758 case EGKW_SECT_ELEMENT: return true;
1759 default: return false;
1760 }
1761}
1762
1763
1765{
1766 if( isRotatable( aRoot->id ) && aRoot->HasProp( wxS( "bin_rot" ) ) )
1767 {
1768 // mirrored/spin are read as T_BMB ("yes"/"no") or as a T_UBF integer
1769 // depending on the record, so treat anything other than the false tokens
1770 // as set.
1771 auto flagSet = [&]( const wxString& aKey )
1772 {
1773 if( !aRoot->HasProp( aKey ) )
1774 return false;
1775
1776 const wxString v = aRoot->Prop( aKey );
1777 return v != wxS( "no" ) && v != wxS( "0" );
1778 };
1779
1780 bool mirrored = flagSet( wxS( "mirrored" ) );
1781 bool spin = flagSet( wxS( "spin" ) );
1782
1783 long deg = aRoot->PropLong( wxS( "bin_rot" ) );
1784
1785 // Pins and instances store rotation as a two-bit quadrant count; every
1786 // other rotatable record stores a twelve-bit angle where a full turn is
1787 // 4096 units (pads and rectangles carry it in the low bits of a wider
1788 // field, hence the mask).
1789 double degrees;
1790
1791 if( aRoot->id == EGKW_SECT_PIN || aRoot->id == EGKW_SECT_INSTANCE )
1792 degrees = ( deg & 0x3 ) * 90.0;
1793 else
1794 degrees = 360.0 * ( deg & 0x0FFF ) / 4096.0;
1795
1796 wxString rot;
1797
1798 if( spin )
1799 rot << wxS( "S" );
1800
1801 if( mirrored )
1802 rot << wxS( "M" );
1803
1804 rot << wxS( "R" ) << wxString::FromCDouble( degrees, 4 );
1805
1806 aRoot->props[wxS( "rot" )] = rot;
1807 }
1808
1809 for( const auto& child : aRoot->children )
1810 postprocRotation( child.get() );
1811}
1812
1813
1815{
1816 switch( aRoot->id )
1817 {
1818 case EGKW_SECT_TEXT:
1825 case EGKW_SECT_SMASHEDXREF: fixLongText( aRoot, wxS( "textfield" ) ); break;
1826
1827 case EGKW_SECT_LAYER:
1828 case EGKW_SECT_LIBRARY:
1829 case EGKW_SECT_SIGNAL:
1830 case EGKW_SECT_SYMBOL:
1832 case EGKW_SECT_PAD:
1833 case EGKW_SECT_SMD:
1834 case EGKW_SECT_PIN:
1835 case EGKW_SECT_GATE: fixLongText( aRoot, wxS( "name" ) ); break;
1836
1837 // Multi-field records consume free-text in the field order Eagle serialized
1838 // them, which matches pyeagle's parse() call order (value-then-name, etc.).
1839 case EGKW_SECT_ELEMENT2:
1840 case EGKW_SECT_PART:
1841 fixLongText( aRoot, wxS( "value" ) );
1842 fixLongText( aRoot, wxS( "name" ) );
1843 break;
1844
1845 case EGKW_SECT_DEVICES:
1846 case EGKW_SECT_SYMBOLS: fixLongText( aRoot, wxS( "library" ) ); break;
1847
1849 fixLongText( aRoot, wxS( "name" ) );
1850 fixLongText( aRoot, wxS( "table" ) );
1851 break;
1852
1853 case EGKW_SECT_PACKAGE:
1854 fixLongText( aRoot, wxS( "name" ) );
1855 fixLongText( aRoot, wxS( "desc" ) );
1856 break;
1857
1858 case EGKW_SECT_PACKAGES:
1859 fixLongText( aRoot, wxS( "library" ) );
1860 fixLongText( aRoot, wxS( "desc" ) );
1861 break;
1862
1863 case EGKW_SECT_SCHEMA: fixLongText( aRoot, wxS( "xref_format" ) ); break;
1864
1866 fixLongText( aRoot, wxS( "attribute" ) );
1867 fixLongText( aRoot, wxS( "symbol" ) );
1868 break;
1869
1870 case EGKW_SECT_DEVICE:
1871 fixLongText( aRoot, wxS( "name" ) );
1872 fixLongText( aRoot, wxS( "desc" ) );
1873 fixLongText( aRoot, wxS( "prefix" ) );
1874 break;
1875
1876 default: break;
1877 }
1878
1879 for( const auto& child : aRoot->children )
1880 postprocFreeText( child.get() );
1881}
1882
1883
1885{
1886 switch( aId )
1887 {
1888 case EGKW_SECT_TEXT:
1895 case EGKW_SECT_SMASHEDXREF: return true;
1896 default: return false;
1897 }
1898}
1899
1900
1902{
1903 // A text-family record with a string too long for its 5-byte inline field is
1904 // immediately followed by exactly one 0x3200 record carrying the full string.
1905 // Fold that string back onto the preceding text sibling and drop the record;
1906 // the XML schema has no standalone longtext element. A longtext with no valid
1907 // text predecessor (malformed input, or a second consecutive longtext) is
1908 // dropped rather than emitted, so invalid XML never reaches the shared reader.
1909 std::vector<std::unique_ptr<EGB_NODE>> kept;
1910 EGB_NODE* eligible = nullptr;
1911
1912 for( auto& child : aRoot->children )
1913 {
1914 if( child->id == EGKW_SECT_LONGTEXT )
1915 {
1916 if( eligible != nullptr )
1917 eligible->props[wxS( "textfield" )] = child->Prop( wxS( "textfield" ) );
1918
1919 eligible = nullptr;
1920 continue;
1921 }
1922
1923 kept.push_back( std::move( child ) );
1924 eligible = ( kept.back()->HasProp( wxS( "textfield" ) ) && isLongTextHost( kept.back()->id ) )
1925 ? kept.back().get()
1926 : nullptr;
1927 }
1928
1929 aRoot->children = std::move( kept );
1930
1931 for( const auto& child : aRoot->children )
1932 postprocLongText( child.get() );
1933}
1934
1935
1937{
1938 // The XML reader takes a text element's string from its PCDATA body, not an
1939 // attribute, so surface the decoded textfield as node content.
1940 if( isLongTextHost( aRoot->id ) && aRoot->HasProp( wxS( "textfield" ) ) )
1941 aRoot->content = aRoot->Prop( wxS( "textfield" ) );
1942
1943 for( const auto& child : aRoot->children )
1944 postprocTextContent( child.get() );
1945}
1946
1947
1949{
1950 // Move every drawing/layer under the synthetic drawing/layers node, keeping
1951 // order. Reparenting is by ownership transfer.
1952 std::vector<std::unique_ptr<EGB_NODE>> kept;
1953
1954 for( auto& child : aDrawing->children )
1955 {
1956 if( child->id == EGKW_SECT_LAYER )
1957 {
1958 // The binary stores "visible" as a 2-bit field, but KiCad's reader
1959 // parses it as a yes/no bool. Normalize to a truthy token.
1960 if( child->HasProp( wxS( "visible" ) ) )
1961 {
1962 child->props[wxS( "visible" )] = child->PropLong( wxS( "visible" ) ) != 0 ? wxS( "yes" ) : wxS( "no" );
1963 }
1964
1965 child->parent = aLayers;
1966 aLayers->children.push_back( std::move( child ) );
1967 }
1968 else
1969 {
1970 kept.push_back( std::move( child ) );
1971 }
1972 }
1973
1974 aDrawing->children = std::move( kept );
1975}
1976
1977
1978void EAGLE_BIN_PARSER::postprocDrc( EGB_NODE* aDrcNode, const DRC_CTX& aDrc )
1979{
1980 auto addParam = [&]( const wxString& aName, const wxString& aValue )
1981 {
1982 EGB_NODE* p = aDrcNode->AddChild( EGKW_SECT_DRC, wxS( "param" ) );
1983 p->props[wxS( "name" )] = aName;
1984 p->props[wxS( "value" )] = aValue;
1985 };
1986
1987 addParam( wxS( "mdWireWire" ), wxString::Format( wxS( "%ldmil" ), aDrc.mdWireWire ) );
1988 addParam( wxS( "msWidth" ), wxString::Format( wxS( "%ldmil" ), aDrc.msWidth ) );
1989 addParam( wxS( "rvPadTop" ), wxString::FromCDouble( aDrc.rvPadTop ) );
1990 addParam( wxS( "rvPadInner" ), wxString::FromCDouble( aDrc.rvPadInner ) );
1991 addParam( wxS( "rvPadBottom" ), wxString::FromCDouble( aDrc.rvPadBottom ) );
1992}
1993
1994
1996{
1997 // In a board, the libraries node holds packages nodes directly; the XML
1998 // schema expects each wrapped in a library node. Wrap every bare packages
1999 // child.
2000 if( aLibraries == nullptr )
2001 return;
2002
2003 if( aLibraries->FindChildById( EGKW_SECT_LIBRARY ) != nullptr )
2004 return; // already a proper library subtree
2005
2006 std::vector<std::unique_ptr<EGB_NODE>> wrapped;
2007
2008 for( auto& child : aLibraries->children )
2009 {
2010 if( child->id != EGKW_SECT_PACKAGES )
2011 continue;
2012
2013 auto lib = std::make_unique<EGB_NODE>();
2014 lib->id = EGKW_SECT_LIBRARY;
2015 lib->name = wxS( "library" );
2016 lib->parent = aLibraries;
2017 child->parent = lib.get();
2018 lib->children.push_back( std::move( child ) );
2019 wrapped.push_back( std::move( lib ) );
2020 }
2021
2022 if( !wrapped.empty() )
2023 aLibraries->children = std::move( wrapped );
2024}
2025
2026
2028{
2029 if( aElements == nullptr )
2030 return;
2031
2032 // Each element is followed (as a child) by an element2 record carrying its
2033 // name and value; merge those up onto the element.
2034 for( auto& el : aElements->children )
2035 {
2036 if( el->children.empty() || el->children.front()->id != EGKW_SECT_ELEMENT2 )
2037 continue;
2038
2039 for( auto& el2 : el->children )
2040 {
2041 if( el2->id != EGKW_SECT_ELEMENT2 )
2042 continue;
2043
2044 for( const auto& [key, value] : el2->props )
2045 {
2046 if( key == wxS( "name" ) )
2047 {
2048 if( value == wxS( "-" ) )
2049 el->props[wxS( "name" )] = wxS( "HYPHEN" );
2050 else
2051 el->props[wxS( "name" )] = value;
2052 }
2053 else if( key == wxS( "value" ) )
2054 {
2055 el->props[wxS( "value" )] = value;
2056 }
2057 }
2058 }
2059 }
2060}
2061
2062
2064{
2065 if( aLibraries == nullptr )
2066 return;
2067
2068 // The binary references libraries and packages by 1-based ordinal, but the
2069 // XML schema (and KiCad's reader) resolve them by name. Give every library
2070 // and package a unique non-empty name, then rewrite each element's numeric
2071 // library/package references to those names so footprint lookup succeeds.
2072
2073 std::map<wxString, int> seenLibs;
2074
2075 for( size_t li = 0; li < aLibraries->children.size(); li++ )
2076 {
2077 EGB_NODE* lib = aLibraries->children[li].get();
2078
2079 if( lib->id != EGKW_SECT_LIBRARY )
2080 continue;
2081
2083
2084 // The library name is carried on the inner packages node; fall back to
2085 // the ordinal when it is blank.
2086 wxString libName = pkgs ? pkgs->Prop( wxS( "library" ) ) : wxString();
2087
2088 if( libName.IsEmpty() )
2089 libName = wxString::Format( wxS( "lib%zu" ), li + 1 );
2090
2091 // A board can embed several single-package libraries Eagle named after their sole
2092 // component, so library names repeat. The reader keys footprints by (library, package)
2093 // and rejects a duplicate pair, so disambiguate repeated library names the same way
2094 // package names are disambiguated below.
2095 if( int& libCount = seenLibs[libName]; libCount++ > 0 )
2096 libName = wxString::Format( wxS( "%s_%d" ), libName, libCount );
2097
2098 lib->props[wxS( "name" )] = libName;
2099
2100 if( pkgs == nullptr )
2101 continue;
2102
2103 std::map<wxString, int> seen;
2104
2105 for( size_t pi = 0; pi < pkgs->children.size(); pi++ )
2106 {
2107 EGB_NODE* pkg = pkgs->children[pi].get();
2108 wxString name = pkg->Prop( wxS( "name" ) );
2109
2110 if( name.IsEmpty() )
2111 name = wxString::Format( wxS( "pkg%zu" ), pi + 1 );
2112
2113 // Disambiguate repeated names so the per-library package map stays
2114 // unique.
2115 if( int& count = seen[name]; count++ > 0 )
2116 name = wxString::Format( wxS( "%s_%d" ), name, count );
2117
2118 pkg->props[wxS( "name" )] = name;
2119 }
2120 }
2121
2122 if( aElements == nullptr )
2123 return;
2124
2125 auto nameByIdx = [&]( EGB_NODE* aParent, long aIdx ) -> wxString
2126 {
2127 if( aParent == nullptr || aIdx < 1 || aIdx > (long) aParent->children.size() )
2128 return wxString();
2129
2130 return aParent->children[aIdx - 1]->Prop( wxS( "name" ) );
2131 };
2132
2133 for( auto& el : aElements->children )
2134 {
2135 if( el->id != EGKW_SECT_ELEMENT )
2136 continue;
2137
2138 long libIdx = el->PropLong( wxS( "library" ) );
2139 EGB_NODE* lib = ( libIdx >= 1 && libIdx <= (long) aLibraries->children.size() )
2140 ? aLibraries->children[libIdx - 1].get()
2141 : nullptr;
2142
2143 if( lib == nullptr )
2144 continue;
2145
2146 el->props[wxS( "library" )] = lib->Prop( wxS( "name" ) );
2147
2149 wxString pkgName = nameByIdx( pkgs, el->PropLong( wxS( "package" ) ) );
2150
2151 if( !pkgName.IsEmpty() )
2152 el->props[wxS( "package" )] = pkgName;
2153 }
2154}
2155
2156
2158{
2159 if( aSignals == nullptr )
2160 return;
2161
2162 // Flatten any nested signal so every signal sits directly under signals.
2163 // Connectivity of nested nets is not preserved, matching Eagle's own
2164 // binary-to-XML conversion. Iterate by index because nested signals are
2165 // appended to the same vector and must themselves be flattened, which
2166 // handles three or more levels of nesting.
2167 for( size_t i = 0; i < aSignals->children.size(); i++ )
2168 {
2169 EGB_NODE* sig = aSignals->children[i].get();
2170
2171 if( sig->id != EGKW_SECT_SIGNAL )
2172 continue;
2173
2174 std::vector<std::unique_ptr<EGB_NODE>> kept;
2175 std::vector<std::unique_ptr<EGB_NODE>> promoted;
2176
2177 for( auto& inner : sig->children )
2178 {
2179 if( inner->id == EGKW_SECT_SIGNAL )
2180 {
2181 inner->parent = aSignals;
2182 promoted.push_back( std::move( inner ) );
2183 }
2184 else
2185 {
2186 kept.push_back( std::move( inner ) );
2187 }
2188 }
2189
2190 sig->children = std::move( kept );
2191
2192 // Append after rebuilding sig->children; this may reallocate, but sig
2193 // is only dereferenced above and i indexes the (stable) container.
2194 for( auto& p : promoted )
2195 aSignals->children.push_back( std::move( p ) );
2196 }
2197}
2198
2199
2200void EAGLE_BIN_PARSER::postprocContactRefs( EGB_NODE* aSignals, EGB_NODE* aElements, EGB_NODE* aLibraries )
2201{
2202 if( aSignals == nullptr || aElements == nullptr || aLibraries == nullptr )
2203 return;
2204
2205 auto elemByIdx = [&]( long aIdx ) -> EGB_NODE*
2206 {
2207 if( aIdx < 1 || aIdx > (long) aElements->children.size() )
2208 return nullptr;
2209
2210 return aElements->children[aIdx - 1].get();
2211 };
2212
2213 auto libByIdx = [&]( long aIdx ) -> EGB_NODE*
2214 {
2215 if( aIdx < 1 || aIdx > (long) aLibraries->children.size() )
2216 return nullptr;
2217
2218 return aLibraries->children[aIdx - 1].get();
2219 };
2220
2221 auto pkgByIdx = [&]( EGB_NODE* aLib, long aIdx ) -> EGB_NODE*
2222 {
2223 if( aLib == nullptr )
2224 return nullptr;
2225
2226 EGB_NODE* pkgs = aLib->FindChildById( EGKW_SECT_PACKAGES );
2227
2228 if( pkgs == nullptr || aIdx < 1 || aIdx > (long) pkgs->children.size() )
2229 return nullptr;
2230
2231 return pkgs->children[aIdx - 1].get();
2232 };
2233
2234 for( auto& sig : aSignals->children )
2235 {
2236 // Resolve every contactref, regardless of sibling order; wires or
2237 // polygons may precede the contactrefs within a signal.
2238 for( auto& cr : sig->children )
2239 {
2240 if( cr->id != EGKW_SECT_CONTACTREF )
2241 continue;
2242
2243 long partNum = cr->PropLong( wxS( "partnumber" ) );
2244 EGB_NODE* elem = elemByIdx( partNum );
2245
2246 if( elem == nullptr )
2247 continue;
2248
2249 cr->props[wxS( "element" )] = elem->Prop( wxS( "name" ) );
2250
2251 // Resolve the pad name by walking the package pads/pins/smd in order.
2252 EGB_NODE* lib = libByIdx( elem->PropLong( wxS( "library" ) ) );
2253 EGB_NODE* pkg = pkgByIdx( lib, elem->PropLong( wxS( "package" ) ) );
2254
2255 if( pkg == nullptr )
2256 {
2257 cr->props[wxS( "pad" )] = wxS( "PIN_NOT_FOUND" );
2258 continue;
2259 }
2260
2261 long pinNum = cr->PropLong( wxS( "pin" ) );
2262 EGB_NODE* found = nullptr;
2263
2264 for( const auto& child : pkg->children )
2265 {
2266 int kind = child->id & 0xFF00;
2267
2268 if( kind == EGKW_SECT_PAD || kind == EGKW_SECT_SMD || kind == EGKW_SECT_PIN )
2269 {
2270 if( --pinNum < 1 )
2271 {
2272 found = child.get();
2273 break;
2274 }
2275 }
2276 }
2277
2278 if( found == nullptr )
2279 cr->props[wxS( "pad" )] = wxS( "PIN_NOT_FOUND" );
2280 else if( found->HasProp( wxS( "name" ) ) )
2281 cr->props[wxS( "pad" )] = found->Prop( wxS( "name" ) );
2282 else
2283 cr->props[wxS( "pad" )] = cr->Prop( wxS( "pin" ) );
2284 }
2285 }
2286}
2287
2288
2290{
2291 EGB_NODE* drawing = aRoot->children.empty() ? nullptr : aRoot->children.front().get();
2292
2293 if( drawing == nullptr )
2294 THROW_IO_ERROR( _( "Eagle binary file has no drawing section." ) );
2295
2296 // KiCad's XML reader resolves the layer map from drawing/layers, so the
2297 // synthetic node must live under drawing, not the eagle root.
2298 EGB_NODE* layers = drawing->AddChild( EGKW_SECT_LAYERS, wxS( "layers" ) );
2299
2300 EGB_NODE* board = drawing->FindChildById( EGKW_SECT_BOARD );
2301 EGB_NODE* drcNode = nullptr;
2302 EGB_NODE* libraries = nullptr;
2303 EGB_NODE* signals = nullptr;
2304 EGB_NODE* elements = nullptr;
2305
2306 if( board != nullptr )
2307 {
2308 drcNode = board->AddChild( EGKW_SECT_DRC, wxS( "designrules" ) );
2309 libraries = board->FindChildByName( wxS( "libraries" ) );
2310
2311 if( libraries == nullptr )
2312 THROW_IO_ERROR( _( "Eagle binary layout is missing a board/libraries node." ) );
2313
2314 signals = board->FindChildByName( wxS( "signals" ) );
2315 elements = board->FindChildByName( wxS( "elements" ) );
2316 }
2317
2318 // Fold trailing longtext records onto their text siblings before any pass
2319 // walks the tree by sibling order.
2320 postprocLongText( aRoot );
2321
2322 postprocLayers( drawing, layers );
2323
2324 if( drcNode != nullptr )
2325 postprocDrc( drcNode, aDrc );
2326
2327 postprocLibs( libraries );
2328 postprocElements( elements );
2329 postprocSignals( signals );
2330
2331 postprocWires( aRoot );
2332 postprocArcs( aRoot );
2333 postprocVias( aRoot );
2334 postprocCircles( aRoot );
2335 postprocSmd( aRoot );
2336 postprocDimensions( aRoot );
2337
2338 // Resolve long-text names before contactrefs copy element and pad names,
2339 // and before postprocNames disambiguates package names.
2340 postprocFreeText( aRoot );
2341
2342 // Move resolved text strings into node content after the free-text pass has
2343 // backfilled any 0x7F deferred references.
2344 postprocTextContent( aRoot );
2345
2346 // postprocContactRefs reads element library/package ordinals, so it must run
2347 // before postprocNames rewrites those ordinals into names.
2348 postprocContactRefs( signals, elements, libraries );
2349 postprocNames( libraries, elements );
2350
2351 postprocRotation( aRoot );
2352
2353 // Backfill XML-required attributes (frame columns) shared with the schematic path.
2354 postprocRequiredAttrs( aRoot );
2355
2356 // Custom element attributes decode without a name and would abort the shared
2357 // reader; prune them after every naming pass has had a chance to backfill one.
2358 postprocAttributes( aRoot );
2359
2360 // Must run last so every dimensional attribute has its final value before
2361 // the decimicron-to-millimetre rewrite.
2362 postprocUnits( aRoot );
2363}
2364
2365
2367{
2368 // The binary attribute record carries the placement of a custom element
2369 // attribute but not its name, and there is no reliable path to recover one.
2370 // The XML schema makes name required on <attribute>, so emitting a nameless
2371 // one aborts the shared reader. Drop the unrecoverable nodes rather than
2372 // synthesize invalid XML; only the displayed text placement is lost.
2373 std::vector<std::unique_ptr<EGB_NODE>> kept;
2374
2375 for( auto& child : aRoot->children )
2376 {
2377 if( child->id == EGKW_SECT_ATTRIBUTE && !child->HasProp( wxS( "name" ) ) )
2378 continue;
2379
2380 kept.push_back( std::move( child ) );
2381 }
2382
2383 aRoot->children = std::move( kept );
2384
2385 for( const auto& child : aRoot->children )
2386 postprocAttributes( child.get() );
2387}
2388
2389
2390wxXmlNode* EAGLE_BIN_PARSER::toXml( const EGB_NODE* aNode ) const
2391{
2392 wxXmlNode* xml = new wxXmlNode( wxXML_ELEMENT_NODE, aNode->name );
2393
2394 for( const auto& [key, value] : aNode->props )
2395 xml->AddAttribute( key, value );
2396
2397 if( !aNode->content.IsEmpty() )
2398 xml->AddChild( new wxXmlNode( wxXML_TEXT_NODE, wxEmptyString, aNode->content ) );
2399
2400 // wxXmlNode::AddChild appends to the end of the sibling chain, preserving
2401 // document order.
2402 for( const auto& child : aNode->children )
2403 xml->AddChild( toXml( child.get() ) );
2404
2405 return xml;
2406}
2407
2408
2409std::unique_ptr<wxXmlDocument> EAGLE_BIN_PARSER::Parse( const std::vector<uint8_t>& aBytes )
2410{
2411 m_buf = &aBytes;
2412 m_pos = 0;
2413 m_longRefs.clear();
2414
2415 if( aBytes.size() < 24 )
2416 THROW_IO_ERROR( _( "File is too small to be an Eagle binary board." ) );
2417
2418 m_root = std::make_unique<EGB_NODE>();
2419 m_root->id = 0;
2420 m_root->name = wxS( "eagle" );
2421
2422 long numBlocks = -1;
2423 readBlock( numBlocks, m_root.get() );
2424
2425 // A schematic drawing carries a `schema` section where a board carries `board`.
2426 EGB_NODE* drawing = m_root->children.empty() ? nullptr : m_root->children.front().get();
2427 bool isSchematic = drawing && drawing->FindChildById( EGKW_SECT_SCHEMA ) != nullptr;
2428
2429 // EAGLE_DOC requires a version attribute on the <eagle> root for every drawing kind
2430 // (board, schematic and standalone library). Synthesize one from the drawing's binary
2431 // version bytes; the value only feeds behavioural gating that already tolerates a coarse
2432 // version.
2433 if( drawing != nullptr )
2434 {
2435 long v1 = drawing->HasProp( wxS( "v1" ) ) ? drawing->PropLong( wxS( "v1" ) ) : 5;
2436 long v2 = drawing->HasProp( wxS( "v2" ) ) ? drawing->PropLong( wxS( "v2" ) ) : 0;
2437 m_root->props[wxS( "version" )] = wxString::Format( wxS( "%ld.%ld" ), v1, v2 );
2438 }
2439
2440 if( isSchematic )
2441 {
2442 // Long names/values are stored as 0x7F references into the trailing
2443 // free-text section, present in schematics as well as boards. Read it and
2444 // resolve every reference by its embedded pointer (order-independent).
2445 readNotes();
2447
2449 }
2450 else
2451 {
2452 DRC_CTX drc;
2453
2454 // The trailing notes and DRC sections are present only in v4/v5 boards;
2455 // missing sections are tolerated and fall back to defaults.
2456 readNotes();
2458 readDrc( drc );
2459
2460 postProcess( m_root.get(), drc );
2461 }
2462
2463 auto doc = std::make_unique<wxXmlDocument>();
2464 doc->SetRoot( toXml( m_root.get() ) );
2465
2466 m_buf = nullptr;
2467 return doc;
2468}
2469
2470
2472{
2473 EGB_NODE* drawing = aRoot->children.empty() ? nullptr : aRoot->children.front().get();
2474
2475 if( drawing == nullptr )
2476 THROW_IO_ERROR( _( "Eagle binary schematic has no drawing section." ) );
2477
2478 EGB_NODE* schematic = drawing->FindChildById( EGKW_SECT_SCHEMA );
2479
2480 if( schematic == nullptr )
2481 THROW_IO_ERROR( _( "Eagle binary file has no schematic section." ) );
2482
2483 // The schema section becomes the XML <schematic> element.
2484 schematic->name = wxS( "schematic" );
2485
2486 // Normalize geometry and text attributes shared with the board path before
2487 // the tree is restructured (these passes match on section id, not name).
2488 postprocLongText( aRoot );
2489 postprocWires( aRoot );
2490 postprocArcs( aRoot );
2491 postprocCircles( aRoot );
2492 postprocFreeText( aRoot );
2493 postprocTextContent( aRoot );
2494 postprocRotation( aRoot );
2495 postprocRequiredAttrs( aRoot );
2496 postprocSchAttrs( aRoot );
2497 postprocUnits( aRoot );
2498 renameSchSections( schematic );
2499
2500 std::vector<EGB_NODE*> libList = resolveSchLibraries( schematic );
2501 resegmentSchSheets( schematic, libList );
2502}
2503
2504
2505std::vector<EAGLE_BIN_PARSER::EGB_NODE*>
2507{
2508 std::vector<EGB_NODE*> out;
2509
2510 if( aParent != nullptr )
2511 {
2512 for( const auto& child : aParent->children )
2513 {
2514 if( child->id == aChildId )
2515 out.push_back( child.get() );
2516 }
2517 }
2518
2519 return out;
2520}
2521
2522
2523wxString EAGLE_BIN_PARSER::nameByOrdinal( const std::vector<EGB_NODE*>& aList, long aIdx )
2524{
2525 // The binary references symbols/devicesets/variants/gates by 1-based ordinal.
2526 if( aIdx >= 1 && aIdx <= (long) aList.size() )
2527 return aList[aIdx - 1]->Prop( wxS( "name" ) );
2528
2529 return wxString();
2530}
2531
2532
2534{
2535 // The shared XML reader marks <frame columns> #REQUIRED. Frames appear in boards,
2536 // schematics and library symbols alike, so rename the binary "cols" field on every
2537 // drawing kind rather than only the schematic path. The board path prunes its own
2538 // nameless attributes, so the <attribute> name backfill stays in postprocSchAttrs.
2539 aRoot->ForEach(
2540 [&]( EGB_NODE* aNode )
2541 {
2542 if( aNode->id == EGKW_SECT_FRAME && aNode->HasProp( wxS( "cols" ) ) )
2543 aNode->props[wxS( "columns" )] = aNode->Prop( wxS( "cols" ) );
2544 } );
2545}
2546
2547
2549{
2550 // Normalize per-element attributes to their XML names/values before the unit
2551 // conversion rewrites dimensional fields.
2552 aRoot->ForEach(
2553 [&]( EGB_NODE* aNode )
2554 {
2555 // A placed <attribute> carries its key in the text field; the reader
2556 // needs it as the required name attribute.
2557 if( aNode->id == EGKW_SECT_ATTRIBUTE && !aNode->HasProp( wxS( "name" ) ) )
2558 {
2559 aNode->props[wxS( "name" )] = aNode->HasProp( wxS( "textfield" ) )
2560 ? aNode->Prop( wxS( "textfield" ) )
2561 : aNode->content;
2562 }
2563
2564 switch( aNode->id )
2565 {
2566 case EGKW_SECT_TEXT:
2572 {
2573 // Eagle stores text height at half its real value; reuse the
2574 // overflow-safe doubling accessor the board path uses.
2575 wxString sizeKey;
2576
2577 if( aNode->HasProp( wxS( "half_size" ) ) )
2578 sizeKey = wxS( "half_size" );
2579 else if( aNode->HasProp( wxS( "size" ) ) )
2580 sizeKey = wxS( "size" );
2581
2582 if( !sizeKey.IsEmpty() && aNode->PropLong( sizeKey ) >= 0 )
2583 aNode->props[wxS( "size" )] = aNode->PropDoubled( sizeKey );
2584
2585 break;
2586 }
2587 default: break;
2588 }
2589 } );
2590}
2591
2592
2594{
2595 // Rename binary sections to their XML element names. The binary "device" record
2596 // (0x37) is the XML <deviceset>; its "variants" child is the XML <devices>.
2597 aSchematic->ForEach(
2598 [&]( EGB_NODE* aNode )
2599 {
2600 switch( aNode->id )
2601 {
2602 case EGKW_SECT_DEVICES: aNode->name = wxS( "devicesets" ); break;
2603 case EGKW_SECT_DEVICE: aNode->name = wxS( "deviceset" ); break;
2604 case EGKW_SECT_SCHEMASHEET: aNode->name = wxS( "sheet" ); break;
2605 case EGKW_SECT_SCHEMANET: aNode->name = wxS( "net" ); break;
2606 case EGKW_SECT_PACKAGEVARIANT: aNode->name = wxS( "device" ); break;
2607 default:
2608 if( aNode->name == wxS( "variants" ) )
2609 aNode->name = wxS( "devices" );
2610
2611 break;
2612 }
2613 } );
2614}
2615
2616
2617std::vector<EAGLE_BIN_PARSER::EGB_NODE*>
2619{
2620 auto wrapChildren = []( EGB_NODE* aParent, const wxString& aContainer,
2621 const std::function<bool( const EGB_NODE* )>& aPred ) -> EGB_NODE*
2622 {
2623 std::vector<std::unique_ptr<EGB_NODE>> kept;
2624 std::vector<std::unique_ptr<EGB_NODE>> moved;
2625
2626 for( auto& child : aParent->children )
2627 {
2628 if( aPred( child.get() ) )
2629 moved.push_back( std::move( child ) );
2630 else
2631 kept.push_back( std::move( child ) );
2632 }
2633
2634 aParent->children = std::move( kept );
2635
2636 if( moved.empty() )
2637 return nullptr;
2638
2639 EGB_NODE* container = aParent->AddChild( 0, aContainer );
2640
2641 for( auto& node : moved )
2642 container->AdoptChild( std::move( node ) );
2643
2644 return container;
2645 };
2646
2647 // Hoist the libraries into their XML container. The binary stores a schema's
2648 // sheets, parts and nets as one flat stream where a schemasheet record delimits
2649 // a sheet rather than containing it, so they are re-segmented afterwards in
2650 // stream order.
2651 wrapChildren( aSchematic, wxS( "libraries" ),
2652 []( const EGB_NODE* n ) { return n->id == EGKW_SECT_LIBRARY; } );
2653
2654 EGB_NODE* libraries = aSchematic->FindChildByName( wxS( "libraries" ) );
2655
2656 std::vector<EGB_NODE*> libList = childrenById( libraries, EGKW_SECT_LIBRARY );
2657
2658 // Resolve library names and gate->symbol references.
2659 for( size_t li = 0; li < libList.size(); li++ )
2660 {
2661 EGB_NODE* lib = libList[li];
2662 EGB_NODE* devicesets = lib->FindChildById( EGKW_SECT_DEVICES );
2663 EGB_NODE* symbolsNode = lib->FindChildById( EGKW_SECT_SYMBOLS );
2664
2665 // The library name rides on the inner devices/symbols/packages node.
2666 wxString libName;
2667
2669 {
2670 if( EGB_NODE* n = lib->FindChildById( id ); n && !n->Prop( wxS( "library" ) ).IsEmpty() )
2671 {
2672 libName = n->Prop( wxS( "library" ) );
2673 break;
2674 }
2675 }
2676
2677 if( libName.IsEmpty() )
2678 libName = wxString::Format( wxS( "lib%zu" ), li + 1 );
2679
2680 lib->props[wxS( "name" )] = libName;
2681
2682 // Footprint packages are irrelevant to schematic import and only drag in
2683 // board-only required attributes (dx/dy on smd/pad). Drop them.
2684 lib->children.erase(
2685 std::remove_if( lib->children.begin(), lib->children.end(),
2686 []( const std::unique_ptr<EGB_NODE>& n )
2687 { return n->id == EGKW_SECT_PACKAGES; } ),
2688 lib->children.end() );
2689
2690 std::vector<EGB_NODE*> symbols = childrenById( symbolsNode, EGKW_SECT_SYMBOL );
2691 std::vector<EGB_NODE*> devicesetList = childrenById( devicesets, EGKW_SECT_DEVICE );
2692
2693 // Devicesets are keyed by name in the reader, so every name must be
2694 // unique and non-empty.
2695 std::map<wxString, int> dsSeen;
2696
2697 for( size_t di = 0; di < devicesetList.size(); di++ )
2698 {
2699 EGB_NODE* ds = devicesetList[di];
2700 wxString name = ds->Prop( wxS( "name" ) );
2701
2702 if( name.IsEmpty() )
2703 name = wxString::Format( wxS( "dset%zu" ), di + 1 );
2704
2705 if( int& count = dsSeen[name]; count++ > 0 )
2706 name = wxString::Format( wxS( "%s_%d" ), name, count );
2707
2708 ds->props[wxS( "name" )] = name;
2709
2710 EGB_NODE* gatesNode = ds->FindChildByName( wxS( "gates" ) );
2711
2712 for( EGB_NODE* gate : childrenById( gatesNode, EGKW_SECT_GATE ) )
2713 gate->props[wxS( "symbol" )] = nameByOrdinal( symbols, gate->PropLong( wxS( "symno" ) ) );
2714 }
2715 }
2716
2717 return libList;
2718}
2719
2720
2722 const std::vector<EGB_NODE*>& aLibList )
2723{
2724 auto adopt = []( EGB_NODE* aParent, EGB_NODE*& aSlot, const wxString& aName,
2725 std::unique_ptr<EGB_NODE> aNode )
2726 {
2727 if( aSlot == nullptr )
2728 aSlot = aParent->AddChild( 0, aName );
2729
2730 aSlot->AdoptChild( std::move( aNode ) );
2731 };
2732
2733 // Re-segment the flat, stream-ordered schema body into per-sheet structure.
2734 // The order is: <libraries>, then for each sheet a schemasheet header followed
2735 // by that sheet's parts (each owning its placed instances) and nets, then the
2736 // next sheet, and so on. A schemasheet therefore delimits the sheet that the
2737 // parts/nets that follow it belong to.
2738 std::vector<std::unique_ptr<EGB_NODE>> flat = std::move( aSchematic->children );
2739 aSchematic->children.clear();
2740
2741 std::vector<std::unique_ptr<EGB_NODE>> sheetNodes;
2742 std::vector<std::unique_ptr<EGB_NODE>> globalParts;
2743 std::map<wxString, bool> seenPart;
2744
2745 // Lazily-created containers for the sheet currently being assembled.
2746 EGB_NODE* curSheet = nullptr;
2747 EGB_NODE* curPlain = nullptr;
2748 EGB_NODE* curInstances = nullptr;
2749 EGB_NODE* curNets = nullptr;
2750 EGB_NODE* curBusses = nullptr;
2751
2752 for( auto& node : flat )
2753 {
2754 // Keep the libraries container at the head of <schematic>.
2755 if( node->name == wxS( "libraries" ) )
2756 {
2757 aSchematic->children.push_back( std::move( node ) );
2758 continue;
2759 }
2760
2761 switch( node->id )
2762 {
2764 {
2765 // Open a new sheet; its already-decoded drawables become <plain>.
2766 curSheet = node.get();
2767 curPlain = curInstances = curNets = curBusses = nullptr;
2768
2769 std::vector<std::unique_ptr<EGB_NODE>> drawables = std::move( curSheet->children );
2770 curSheet->children.clear();
2771
2772 for( auto& drawable : drawables )
2773 adopt( curSheet, curPlain, wxS( "plain" ), std::move( drawable ) );
2774
2775 sheetNodes.push_back( std::move( node ) );
2776 break;
2777 }
2778
2779 case EGKW_SECT_PART:
2780 {
2781 EGB_NODE* part = node.get();
2782 long libno = part->PropLong( wxS( "lib" ) );
2783 long devno = part->PropLong( wxS( "device" ) );
2784 long varno = part->PropLong( wxS( "variant" ) );
2785
2786 EGB_NODE* lib = ( libno >= 1 && libno <= (long) aLibList.size() ) ? aLibList[libno - 1]
2787 : nullptr;
2788 std::vector<EGB_NODE*> devicesets =
2789 childrenById( lib ? lib->FindChildById( EGKW_SECT_DEVICES ) : nullptr,
2791 EGB_NODE* ds = ( devno >= 1 && devno <= (long) devicesets.size() ) ? devicesets[devno - 1]
2792 : nullptr;
2793 std::vector<EGB_NODE*> variants =
2794 childrenById( ds ? ds->FindChildByName( wxS( "devices" ) ) : nullptr,
2796 std::vector<EGB_NODE*> gates =
2797 childrenById( ds ? ds->FindChildByName( wxS( "gates" ) ) : nullptr,
2799
2800 wxString partName = part->Prop( wxS( "name" ) );
2801
2802 part->props[wxS( "library" )] = lib ? lib->Prop( wxS( "name" ) ) : wxString();
2803 part->props[wxS( "deviceset" )] = ds ? ds->Prop( wxS( "name" ) ) : wxString();
2804 part->props[wxS( "device" )] = nameByOrdinal( variants, varno );
2805
2806 // The decoded "technology" is a raw ordinal, but the XML attribute is a
2807 // technology name (almost always empty) that the reader appends to the
2808 // symbol lookup key; leaving the ordinal there breaks symbol resolution.
2809 part->props.erase( wxS( "technology" ) );
2810
2811 // Peel the placed gate instances onto the current sheet, resolved.
2812 std::vector<std::unique_ptr<EGB_NODE>> partKept;
2813
2814 for( auto& sub : part->children )
2815 {
2816 if( sub->id != EGKW_SECT_INSTANCE || curSheet == nullptr )
2817 {
2818 partKept.push_back( std::move( sub ) );
2819 continue;
2820 }
2821
2822 sub->props[wxS( "part" )] = partName;
2823 sub->props[wxS( "gate" )] = nameByOrdinal( gates, sub->PropLong( wxS( "gateno" ) ) );
2824 adopt( curSheet, curInstances, wxS( "instances" ), std::move( sub ) );
2825 }
2826
2827 part->children = std::move( partKept );
2828
2829 // One global <part> per unique name.
2830 if( !seenPart[partName] )
2831 {
2832 seenPart[partName] = true;
2833 node->parent = aSchematic;
2834 globalParts.push_back( std::move( node ) );
2835 }
2836
2837 break;
2838 }
2839
2841 {
2842 node->props[wxS( "class" )] =
2843 node->HasProp( wxS( "netclass" ) ) ? node->Prop( wxS( "netclass" ) ) : wxString( wxS( "0" ) );
2844
2845 for( EGB_NODE* seg : childrenById( node.get(), EGKW_SECT_PATH ) )
2846 seg->name = wxS( "segment" );
2847
2848 if( curSheet != nullptr )
2849 adopt( curSheet, curNets, wxS( "nets" ), std::move( node ) );
2850
2851 break;
2852 }
2853
2855 {
2856 if( curSheet != nullptr )
2857 adopt( curSheet, curBusses, wxS( "busses" ), std::move( node ) );
2858
2859 break;
2860 }
2861
2862 default:
2863 {
2864 // Free graphics that follow the sheet header.
2865 if( curSheet != nullptr )
2866 adopt( curSheet, curPlain, wxS( "plain" ), std::move( node ) );
2867
2868 break;
2869 }
2870 }
2871 }
2872
2873 flat.clear();
2874
2875 EGB_NODE* sheetsNode = aSchematic->AddChild( 0, wxS( "sheets" ) );
2876
2877 for( auto& sheet : sheetNodes )
2878 sheetsNode->AdoptChild( std::move( sheet ) );
2879
2880 EGB_NODE* partsNode = aSchematic->AddChild( 0, wxS( "parts" ) );
2881
2882 for( auto& part : globalParts )
2883 partsNode->AdoptChild( std::move( part ) );
2884}
const char * name
wxXmlNode * toXml(const EGB_NODE *aNode) const
void postprocSignals(EGB_NODE *aSignals)
std::unique_ptr< wxXmlDocument > Parse(const std::vector< uint8_t > &aBytes)
Parse a binary Eagle board into an XML DOM compatible with the XML walker.
static wxString nameByOrdinal(const std::vector< EGB_NODE * > &aList, long aIdx)
void requireBytes(size_t aOffs, size_t aLen) const
uint32_t loadUbf(size_t aOffs, uint32_t aField) const
void postprocCircles(EGB_NODE *aRoot)
bool readDrc(DRC_CTX &aDrc)
void postprocSchAttrs(EGB_NODE *aRoot)
void postprocFreeText(EGB_NODE *aRoot)
std::vector< EGB_NODE * > resolveSchLibraries(EGB_NODE *aSchematic)
bool isRotatable(int aId) const
void postprocLibs(EGB_NODE *aLibraries)
std::vector< LONG_REF > m_longRefs
const wxString & nextLongText()
uint32_t loadU32(size_t aOffs, unsigned aLen) const
void postprocTextContent(EGB_NODE *aRoot)
size_t m_pos
current read cursor
void postprocUnits(EGB_NODE *aRoot)
void postprocArcs(EGB_NODE *aRoot)
wxString loadStr(size_t aOffs, unsigned aLen) const
void fixLongText(EGB_NODE *aNode, const wxString &aField)
void renameSchSections(EGB_NODE *aSchematic)
void postProcess(EGB_NODE *aRoot, const DRC_CTX &aDrc)
int readBlock(long &aNumBlocks, EGB_NODE *aParent)
void postprocSmd(EGB_NODE *aRoot)
const std::vector< uint8_t > * m_buf
file contents, not owned
void postprocContactRefs(EGB_NODE *aSignals, EGB_NODE *aElements, EGB_NODE *aLibraries)
void postProcessSchematic(EGB_NODE *aRoot)
void postprocWires(EGB_NODE *aRoot)
std::vector< wxString > m_freeText
NUL-delimited notes strings.
void postprocLongText(EGB_NODE *aRoot)
void postprocLayers(EGB_NODE *aDrawing, EGB_NODE *aLayers)
bool loadBmb(size_t aOffs, uint32_t aMask) const
void postprocRotation(EGB_NODE *aRoot)
void postprocAttributes(EGB_NODE *aRoot)
void arcDecode(EGB_NODE *aElem, int aArcType, int aLineType)
double loadDouble(size_t aOffs) const
bool isLongTextHost(int aId) const
static bool IsBinaryEagle(wxInputStream &aStream)
Probe the first two bytes for the binary magic.
void postprocDimensions(EGB_NODE *aRoot)
void resegmentSchSheets(EGB_NODE *aSchematic, const std::vector< EGB_NODE * > &aLibList)
std::unique_ptr< EGB_NODE > m_root
wxString m_invalidText
returned when out of strings
static std::vector< EGB_NODE * > childrenById(EGB_NODE *aParent, int aChildId)
void postprocRequiredAttrs(EGB_NODE *aRoot)
std::map< size_t, wxString > m_freeTextByOffset
Free-text strings keyed by their byte offset within the blob, for pointer (0x7F-reference) resolution...
void postprocDrc(EGB_NODE *aDrcNode, const DRC_CTX &aDrc)
void postprocVias(EGB_NODE *aRoot)
void postprocNames(EGB_NODE *aLibraries, EGB_NODE *aElements)
void postprocElements(EGB_NODE *aElements)
int32_t loadS32(size_t aOffs, unsigned aLen) const
#define TERM_A
#define TERM_S
#define TERM_F
@ EGKW_SECT_ARC
@ EGKW_SECT_FRAME
@ EGKW_SECT_LONGTEXT
@ EGKW_SECT_DRC
@ EGKW_SECT_LAYER
@ EGKW_SECT_PACKAGES
@ EGKW_SECT_SMASHEDVALUE
@ EGKW_SECT_TEXT
@ EGKW_SECT_PAD
@ EGKW_SECT_GATE
@ EGKW_SECT_DEVICES
@ EGKW_SECT_SMASHEDXREF
@ EGKW_SECT_RECTANGLE
@ EGKW_SECT_SCHEMASHEET
@ EGKW_SECT_PATH
@ EGKW_SECT_CONTACTREF
@ EGKW_SECT_POLYGON
@ EGKW_SECT_PACKAGEVARIANT
@ EGKW_SECT_NETBUSLABEL
@ EGKW_SECT_SMASHEDGATE
@ EGKW_SECT_CIRCLE
@ EGKW_SECT_GRID
@ EGKW_SECT_START
@ EGKW_SECT_PACKAGE
@ EGKW_SECT_SCHEMACONNECTION
@ EGKW_SECT_DEVICE
@ EGKW_SECT_SCHEMANET
@ EGKW_SECT_HOLE
@ EGKW_SECT_BOARD
@ EGKW_SECT_SYMBOL
@ EGKW_SECT_ATTRIBUTE
@ EGKW_SECT_SMASHEDPART
@ EGKW_SECT_LINE
@ EGKW_SECT_PIN
@ EGKW_SECT_LAYERS
@ EGKW_SECT_VARIANTCONNECTIONS
@ EGKW_SECT_VIA
@ EGKW_SECT_SCHEMA
@ EGKW_SECT_ATTRIBUTEVALUE
@ EGKW_SECT_SMASHEDNAME
@ EGKW_SECT_UNKNOWN11
@ EGKW_SECT_SMD
@ EGKW_SECT_ELEMENT2
@ EGKW_SECT_SCHEMABUS
@ EGKW_SECT_PART
@ EGKW_SECT_SIGNAL
@ EGKW_SECT_ELEMENT
@ EGKW_SECT_JUNCTION
@ EGKW_SECT_INSTANCE
@ EGKW_SECT_LIBRARY
@ EGKW_SECT_SYMBOLS
@ EGKW_SECT_FREETEXT
#define _(s)
const wxChar *const traceEagleIo
#define THROW_IO_ERROR(msg)
macro which captures the "call site" values of FILE_, __FUNCTION & LINE
This file contains miscellaneous commonly used macros and functions.
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:400
DRC values pulled from the trailing 244-byte block (or sane defaults).
Lightweight mutable tree node for the intermediate Eagle binary tree.
std::map< wxString, wxString > props
long PropLong(const wxString &aKey) const
EGB_NODE * AdoptChild(std::unique_ptr< EGB_NODE > aChild)
Move an existing node in as a child, repointing its parent link.
std::vector< std::unique_ptr< EGB_NODE > > children
EGB_NODE * FindChildById(int aId) const
bool HasProp(const wxString &aKey) const
void ForEach(FN &&aFn)
Apply aFn to this node and every descendant, pre-order.
EGB_NODE * AddChild(int aId, const wxString &aName)
wxString PropDoubled(const wxString &aKey) const
Format a property doubled in 64-bit so the half-to-full widening cannot overflow a 32-bit long.
wxString Prop(const wxString &aKey) const
EGB_NODE * FindChildByName(const wxString &aName) const
wxString content
PCDATA emitted as the XML node's text body.
bool moved
VECTOR3I v1(5, 5, 5)
VECTOR3I res
int radius
VECTOR2I end
VECTOR2I v2(1, 0)
#define M_PI
wxLogTrace helper definitions.