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