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 const wxFileOffset originalPos = aStream.TellI();
897
898 if( originalPos == wxInvalidOffset || aStream.SeekI( 0 ) == wxInvalidOffset )
899 return false;
900
901 aStream.Read( buf, 2 );
902 const size_t bytesRead = aStream.LastRead();
903
904 if( aStream.SeekI( originalPos ) == wxInvalidOffset || bytesRead != 2 )
905 return false;
906
907 if( buf[0] == 0x10 && ( buf[1] == 0x00 || buf[1] == 0x80 ) )
908 return true;
909
910 return false;
911}
912
913
914void EAGLE_BIN_PARSER::requireBytes( size_t aOffs, size_t aLen ) const
915{
916 if( m_buf == nullptr || aOffs > m_buf->size() || aLen > m_buf->size() - aOffs )
917 THROW_IO_ERROR( _( "Short read in Eagle binary file (field out of bounds)." ) );
918}
919
920
921uint32_t EAGLE_BIN_PARSER::loadU32( size_t aOffs, unsigned aLen ) const
922{
923 requireBytes( aOffs, aLen );
924
925 uint32_t l = 0;
926
927 for( unsigned n = 0; n < aLen; n++ )
928 {
929 l <<= 8;
930 l |= ( *m_buf )[aOffs + aLen - n - 1];
931 }
932
933 return l;
934}
935
936
937int32_t EAGLE_BIN_PARSER::loadS32( size_t aOffs, unsigned aLen ) const
938{
939 requireBytes( aOffs, aLen );
940
941 uint32_t l = 0;
942
943 if( ( *m_buf )[aOffs + aLen - 1] & 0x80 )
944 l = 0xFFFFFFFF;
945
946 for( unsigned n = 0; n < aLen; n++ )
947 {
948 l <<= 8;
949 l |= ( *m_buf )[aOffs + aLen - n - 1];
950 }
951
952 return static_cast<int32_t>( l );
953}
954
955
956bool EAGLE_BIN_PARSER::loadBmb( size_t aOffs, uint32_t aMask ) const
957{
958 requireBytes( aOffs, 1 );
959
960 return ( ( *m_buf )[aOffs] & aMask ) != 0;
961}
962
963
964uint32_t EAGLE_BIN_PARSER::loadUbf( size_t aOffs, uint32_t aField ) const
965{
966 unsigned first = ( aField >> 8 ) & 0xff;
967 unsigned last = aField & 0xff;
968 uint32_t mask = ( 1u << ( last - first + 1 ) ) - 1;
969
970 // The high byte of the descriptor is the read length; keeping it inline ties
971 // the offset and field together rather than splitting them into locals.
972 uint32_t val = loadU32( aOffs, ( aField >> 16 ) & 0xff ) >> first;
973
974 return val & mask;
975}
976
977
978wxString EAGLE_BIN_PARSER::loadStr( size_t aOffs, unsigned aLen ) const
979{
980 requireBytes( aOffs, aLen );
981
982 const char* start = reinterpret_cast<const char*>( m_buf->data() + aOffs );
983
984 // The field is fixed length and NUL padded; stop at the first NUL but never
985 // run past the field.
986 size_t n = 0;
987
988 while( n < aLen && start[n] != '\0' )
989 n++;
990
991 return wxString::FromUTF8( start, n );
992}
993
994
995double EAGLE_BIN_PARSER::loadDouble( size_t aOffs ) const
996{
997 static_assert( sizeof( double ) == 8, "Eagle binary doubles are 8-byte IEEE-754" );
998
999 requireBytes( aOffs, sizeof( double ) );
1000
1001 // The file stores a little-endian IEEE-754 double. Assemble the bit pattern
1002 // from individual bytes so decoding does not depend on host byte order, then
1003 // reinterpret those bits as a double.
1004 uint64_t bits = 0;
1005
1006 for( unsigned n = 0; n < sizeof( double ); n++ )
1007 bits |= static_cast<uint64_t>( ( *m_buf )[aOffs + n] ) << ( 8 * n );
1008
1009 double d = 0.0;
1010 memcpy( &d, &bits, sizeof( d ) );
1011
1012 return d;
1013}
1014
1015
1016int EAGLE_BIN_PARSER::readBlock( long& aNumBlocks, EGB_NODE* aParent )
1017{
1018 // Over-counted subsection counts can drive the walk past the last block onto the
1019 // free-text sentinel, which readNotes() owns. Returning zero here lets callers
1020 // collapse the remaining phantom iterations. Checked before the 24-byte guard
1021 // because the free-text section can be shorter than a block header.
1022 if( m_pos + 2 <= m_buf->size() && ( *m_buf )[m_pos] == 0x13 && ( *m_buf )[m_pos + 1] == 0x12 )
1023 {
1024 aNumBlocks = 0;
1025 return 0;
1026 }
1027
1028 if( m_pos + 24 > m_buf->size() )
1029 THROW_IO_ERROR( _( "Short read in Eagle binary file (truncated block)." ) );
1030
1031 size_t blockStart = m_pos;
1032 m_pos += 24;
1033
1034 int processed = 1;
1035
1036 // The top-level drawing record carries the total block count.
1037 if( aNumBlocks < 0 && ( *m_buf )[blockStart] == 0x10 )
1038 aNumBlocks = loadS32( blockStart + 4, 4 );
1039
1040 const SCRIPT_ROW* sc = nullptr;
1041
1042 for( const SCRIPT_ROW* row = g_script; row->cmd != 0; row++ )
1043 {
1044 unsigned cmdh = ( row->cmd >> 8 ) & 0xFF;
1045 unsigned cmdl = row->cmd & 0xFF;
1046 unsigned mskh = ( row->cmdMask >> 8 ) & 0xFF;
1047 unsigned mskl = row->cmdMask & 0xFF;
1048
1049 if( ( cmdh != ( ( *m_buf )[blockStart] & mskh ) ) || ( cmdl != ( ( *m_buf )[blockStart + 1] & mskl ) ) )
1050 {
1051 continue;
1052 }
1053
1054 bool match = true;
1055
1056 for( const FMATCH* fm = row->fmatch; fm->offs != 0; fm++ )
1057 {
1058 if( loadS32( blockStart + fm->offs, fm->len ) != fm->val )
1059 {
1060 match = false;
1061 break;
1062 }
1063 }
1064
1065 if( match )
1066 {
1067 // A v4/v5 pad can clear the same low-byte flag bits the short-row mask
1068 // ignores, so it would bind the Eagle 3.x inline-name layout and read an
1069 // empty name (and lose its rotation). Only v3 files carry that layout; let
1070 // newer files fall through to the full-layout row.
1071 if( m_majorVer > 3 && isV3InlineNamePadRow( row ) )
1072 continue;
1073
1074 sc = row;
1075 break;
1076 }
1077 }
1078
1079 if( sc == nullptr )
1080 {
1081 THROW_IO_ERRORF( _( "Unknown Eagle binary block id 0x%02x%02x at offset %zu." ),
1082 (unsigned) ( *m_buf )[blockStart],
1083 (unsigned) ( *m_buf )[blockStart + 1],
1084 blockStart );
1085 }
1086
1087 EGB_NODE* node = aParent->AddChild( static_cast<int>( sc->cmd ),
1088 sc->name ? wxString::FromUTF8( sc->name ) : wxString( wxS( "UNKNOWN" ) ) );
1089
1090 for( const ATTR* at = sc->attrs; at->name != nullptr; at++ )
1091 {
1092 wxString val;
1093
1094 switch( at->type )
1095 {
1096 // KiCad's Eagle XML reader parses boolean attributes as "yes"/"no", so
1097 // emit T_BMB fields that way rather than "1"/"0".
1098 case T_BMB: val = loadBmb( blockStart + at->offs, at->len ) ? wxS( "yes" ) : wxS( "no" ); break;
1099 case T_UBF: val = wxString::Format( wxS( "%u" ), loadUbf( blockStart + at->offs, at->len ) ); break;
1100 case T_INT: val = wxString::Format( wxS( "%d" ), loadS32( blockStart + at->offs, at->len ) ); break;
1101 case T_UINT: val = wxString::Format( wxS( "%u" ), loadU32( blockStart + at->offs, at->len ) ); break;
1102 case T_DBL: val = wxString::FromCDouble( loadDouble( blockStart + at->offs ) ); break;
1103 case T_STR:
1104 {
1105 size_t foff = blockStart + at->offs;
1106 val = loadStr( foff, at->len );
1107
1108 // A 0x7F-marked field defers to a 32-bit little-endian pointer into the
1109 // free-text blob. Record the raw pointer (loadStr would NUL-truncate one
1110 // whose high byte is zero) while keeping the inline value for fallback.
1111 if( foff + 5 <= m_buf->size() && ( *m_buf )[foff] == 0x7F )
1112 m_longRefs.push_back( { node, wxString::FromUTF8( at->name ), loadU32( foff + 1, 4 ) } );
1113
1114 break;
1115 }
1116 }
1117
1118 node->props[wxString::FromUTF8( at->name )] = val;
1119 }
1120
1121 aNumBlocks--;
1122
1123 for( const SUBSECT* ss = sc->subs; ss->offs != 0; ss++ )
1124 {
1125 uint32_t numch = loadU32( blockStart + ss->offs, ss->len );
1126 EGB_NODE* lpar = node;
1127
1128 if( ss->treeName != nullptr )
1129 lpar = node->AddChild( 0, wxString::FromUTF8( ss->treeName ) );
1130
1131 if( ss->ssType == SS_DIRECT )
1132 {
1133 for( uint32_t n = 0; n < numch && aNumBlocks > 0; n++ )
1134 {
1135 int res = readBlock( aNumBlocks, lpar );
1136
1137 if( res == 0 )
1138 break;
1139
1140 processed += res;
1141 }
1142 }
1143 else
1144 {
1145 if( ss->ssType == SS_RECURSIVE_MINUS_1 && numch > 0 )
1146 numch--;
1147
1148 long rem = numch;
1149
1150 for( uint32_t n = 0; n < numch && rem > 0; n++ )
1151 {
1152 int res = readBlock( rem, lpar );
1153
1154 if( res == 0 )
1155 break;
1156
1157 aNumBlocks -= res;
1158 processed += res;
1159 }
1160 }
1161 }
1162
1163 return processed;
1164}
1165
1166
1168{
1169 m_freeText.clear();
1170 m_freeTextCursor = 0;
1171
1172 if( m_pos + 8 > m_buf->size() )
1173 return false;
1174
1175 // The free-text section starts with the 0x1312 sentinel.
1176 if( ( *m_buf )[m_pos] != 0x13 || ( *m_buf )[m_pos + 1] != 0x12 )
1177 return false;
1178
1179 int textLen = loadS32( m_pos + 4, 2 );
1180 m_pos += 8;
1181
1182 if( textLen < 0 )
1183 return false;
1184
1185 // A trailing 4-byte checksum follows the text payload.
1186 size_t total = static_cast<size_t>( textLen ) + 4;
1187
1188 if( m_pos + total > m_buf->size() )
1189 return false;
1190
1191 // Split the blob into NUL-delimited strings; an empty string terminates.
1192 // Each string is also keyed by its byte offset in the blob so deferred 0x7F
1193 // pointer references can be resolved directly.
1194 size_t blobStart = m_pos;
1195 size_t end = m_pos + total;
1196 size_t cur = m_pos;
1197
1198 m_freeTextByOffset.clear();
1199
1200 while( cur < end && ( *m_buf )[cur] != '\0' )
1201 {
1202 size_t s = cur;
1203
1204 while( cur < end && ( *m_buf )[cur] != '\0' )
1205 cur++;
1206
1207 wxString str = wxString::FromUTF8( reinterpret_cast<const char*>( m_buf->data() + s ),
1208 cur - s );
1209 m_freeText.push_back( str );
1210 m_freeTextByOffset[s - blobStart] = str;
1211 cur++; // skip the NUL
1212 }
1213
1214 m_pos = end;
1215 return true;
1216}
1217
1218
1220{
1221 if( m_freeTextCursor >= m_freeText.size() )
1222 {
1223 wxLogTrace( traceEagleIo, wxS( "Eagle bin: free-text reference out of strings" ) );
1224 m_invalidText = wxS( "<invalid>" );
1225 return m_invalidText;
1226 }
1227
1228 return m_freeText[m_freeTextCursor++];
1229}
1230
1231
1233{
1234 if( m_freeTextByOffset.empty() || m_longRefs.empty() )
1235 return;
1236
1237 // The pointers are absolute addresses with an unstored base, so recover it by
1238 // consensus: the most common (pointer - boundary) difference is the base. A file
1239 // can reference several blob regions with different bases, so iterate, resolving
1240 // the dominant base's references each round until no more can be placed.
1241 std::vector<bool> done( m_longRefs.size(), false );
1242 std::vector<wxString> value( m_longRefs.size() );
1243
1244 while( true )
1245 {
1246 std::map<long long, int> votes;
1247
1248 for( size_t i = 0; i < m_longRefs.size(); i++ )
1249 {
1250 if( done[i] )
1251 continue;
1252
1253 for( const auto& [offset, str] : m_freeTextByOffset )
1254 {
1255 if( offset > m_longRefs[i].ptr )
1256 break;
1257
1258 votes[static_cast<long long>( m_longRefs[i].ptr ) - static_cast<long long>( offset )]++;
1259 }
1260 }
1261
1262 long long base = -1;
1263 int best = 0;
1264
1265 for( const auto& [cand, count] : votes )
1266 {
1267 if( count > best )
1268 {
1269 best = count;
1270 base = cand;
1271 }
1272 }
1273
1274 if( base < 0 )
1275 break;
1276
1277 int progress = 0;
1278
1279 for( size_t i = 0; i < m_longRefs.size(); i++ )
1280 {
1281 if( done[i] )
1282 continue;
1283
1284 auto it = m_freeTextByOffset.find(
1285 static_cast<size_t>( static_cast<long long>( m_longRefs[i].ptr ) - base ) );
1286
1287 if( it != m_freeTextByOffset.end() )
1288 {
1289 value[i] = it->second;
1290 done[i] = true;
1291 progress++;
1292 }
1293 }
1294
1295 if( progress == 0 )
1296 break;
1297 }
1298
1299 // Commit only if every reference resolved: a partial mix would desync the
1300 // sequential fallback for the misses, so in that case leave all the inline 0x7F
1301 // markers for postprocFreeText() to resolve in order instead.
1302 bool allResolved = std::all_of( done.begin(), done.end(), []( bool d ) { return d; } );
1303
1304 if( allResolved )
1305 {
1306 for( size_t i = 0; i < m_longRefs.size(); i++ )
1307 m_longRefs[i].node->props[m_longRefs[i].field] = value[i];
1308 }
1309}
1310
1311
1313{
1314 if( m_pos + 4 > m_buf->size() )
1315 return false;
1316
1317 // DRC start sentinel 0x10 0x04 0x00 0x20.
1318 if( !( ( *m_buf )[m_pos] == 0x10 && ( *m_buf )[m_pos + 1] == 0x04 && ( *m_buf )[m_pos + 2] == 0x00
1319 && ( *m_buf )[m_pos + 3] == 0x20 ) )
1320 {
1321 return false;
1322 }
1323
1324 m_pos += 4;
1325
1326 // Walk the variable-length preamble looking for the 0x12345678 end marker
1327 // that immediately follows a NUL byte.
1328 bool found = false;
1329
1330 while( !found )
1331 {
1332 if( m_pos + 1 > m_buf->size() )
1333 return false;
1334
1335 uint8_t c = ( *m_buf )[m_pos++];
1336
1337 if( c == '\0' )
1338 {
1339 if( m_pos + 4 > m_buf->size() )
1340 return false;
1341
1342 if( ( *m_buf )[m_pos] == 0x78 && ( *m_buf )[m_pos + 1] == 0x56 && ( *m_buf )[m_pos + 2] == 0x34
1343 && ( *m_buf )[m_pos + 3] == 0x12 )
1344 {
1345 found = true;
1346 }
1347
1348 m_pos += 4;
1349 }
1350 }
1351
1352 const size_t kDrcLen = 244;
1353
1354 if( m_pos + kDrcLen > m_buf->size() )
1355 return false;
1356
1357 size_t b = m_pos;
1358
1359 auto mil = [&]( size_t aOffs ) -> long
1360 {
1361 return static_cast<long>( loadS32( b + aOffs, 4 ) / 2.54 / 100 );
1362 };
1363
1364 aDrc.mdWireWire = mil( 0 );
1365 aDrc.msWidth = mil( 64 );
1366 aDrc.rvPadTop = loadDouble( b + 84 );
1367 aDrc.rvPadInner = loadDouble( b + 92 );
1368 aDrc.rvPadBottom = loadDouble( b + 100 );
1369
1370 m_pos += kDrcLen;
1371 return true;
1372}
1373
1374
1375void EAGLE_BIN_PARSER::fixLongText( EGB_NODE* aNode, const wxString& aField )
1376{
1377 auto it = aNode->props.find( aField );
1378
1379 if( it == aNode->props.end() || it->second.IsEmpty() )
1380 return;
1381
1382 // A leading 0x7F byte marks a deferred long-text reference into the notes that
1383 // pointer resolution could not place; fall back to sequential order. Compare the
1384 // raw code unit (resolved fields can now hold non-ASCII text whose first
1385 // character would assert if cast to a single byte).
1386 if( it->second[0].GetValue() == 0x7F )
1387 it->second = nextLongText();
1388}
1389
1390
1391void EAGLE_BIN_PARSER::arcDecode( EGB_NODE* aElem, int aArcType, int aLineType )
1392{
1393 auto fixThreeByte = []( long num, bool neg ) -> long
1394 {
1395 if( num < 0 && neg )
1396 return num;
1397 else if( num > 0 && neg )
1398 return num - 0x800000;
1399 else if( num < 0 && !neg )
1400 return num + 0x800000;
1401
1402 return num;
1403 };
1404
1405 auto setLong = [&]( const wxString& aKey, long aVal )
1406 {
1407 aElem->props[aKey] = wxString::Format( wxS( "%ld" ), aVal );
1408 };
1409
1410 // Linear interpolation of the unconstrained center coordinate. The 64-bit
1411 // product cannot overflow (Eagle stores 24-bit coordinates) while a plain
1412 // long would on 32-bit platforms, and integer division truncates toward zero.
1413 auto interpolate = []( int64_t aNumerator, int64_t aSpan, int64_t aDivisor, int64_t aOffset ) -> long
1414 {
1415 return static_cast<long>( aNumerator * aSpan / aDivisor + aOffset );
1416 };
1417
1418 if( aLineType == 129 || aArcType == 0 )
1419 {
1420 long arcFlags = aElem->PropLong( wxS( "arc_negflags" ) );
1421 long x1 = fixThreeByte( aElem->PropLong( wxS( "arc_x1" ) ), arcFlags & 0x02 );
1422 long y1 = fixThreeByte( aElem->PropLong( wxS( "arc_y1" ) ), arcFlags & 0x04 );
1423 long x2 = fixThreeByte( aElem->PropLong( wxS( "arc_x2" ) ), arcFlags & 0x08 );
1424 long y2 = fixThreeByte( aElem->PropLong( wxS( "arc_y2" ) ), arcFlags & 0x10 );
1425
1426 // The center is stored as three bytes interleaved with the endpoint fields
1427 // (offsets 7, 11, 15), so it cannot be read as a contiguous field. Reassemble
1428 // it as the little-endian 24-bit signed value loadS32() produces for the
1429 // endpoints, then reconcile its sign with the negflags bit the same way. The
1430 // low byte of each decoded field is the stored byte; masking recovers it
1431 // whether the field was read as signed or not.
1432 long c = ( aElem->PropLong( wxS( "arc_c1" ) ) & 0xFF )
1433 | ( ( aElem->PropLong( wxS( "arc_c2" ) ) & 0xFF ) << 8 )
1434 | ( ( aElem->PropLong( wxS( "arc_c3" ) ) & 0xFF ) << 16 );
1435
1436 if( c & 0x800000 )
1437 c -= 0x1000000;
1438
1439 c = fixThreeByte( c, arcFlags & 0x01 );
1440
1441 setLong( wxS( "x1" ), x1 );
1442 setLong( wxS( "y1" ), y1 );
1443 setLong( wxS( "x2" ), x2 );
1444 setLong( wxS( "y2" ), y2 );
1445
1446 long x3 = ( x1 + x2 ) / 2;
1447 long y3 = ( y1 + y2 ) / 2;
1448 long cx = 0, cy = 0;
1449
1450 if( x1 == x2 && y1 == y2 )
1451 {
1452 // Degenerate arc with coincident endpoints; both interpolation
1453 // branches would divide by zero, so collapse it to a point.
1454 cx = x1;
1455 cy = y1;
1456 }
1457 else if( std::abs( x2 - x1 ) < std::abs( y2 - y1 ) )
1458 {
1459 cx = c;
1460 cy = interpolate( x3 - cx, x2 - x1, y2 - y1, y3 );
1461 }
1462 else
1463 {
1464 cy = c;
1465 cx = interpolate( y3 - cy, y2 - y1, x2 - x1, x3 );
1466 }
1467
1468 long radius = static_cast<long>( std::hypot( cx - x2, cy - y2 ) );
1469 setLong( wxS( "radius" ), radius );
1470 setLong( wxS( "x" ), cx );
1471 setLong( wxS( "y" ), cy );
1472
1473 if( cx == x2 && cy == y1 && x2 < x1 && y2 > y1 )
1474 {
1475 aElem->props[wxS( "StartAngle" )] = wxS( "90" );
1476 aElem->props[wxS( "Delta" )] = wxS( "90" );
1477 }
1478 else if( cx == x1 && cy == y2 && x2 < x1 && y1 > y2 )
1479 {
1480 aElem->props[wxS( "StartAngle" )] = wxS( "0" );
1481 aElem->props[wxS( "Delta" )] = wxS( "90" );
1482 }
1483 else if( cx == x2 && cy == y1 && x2 > x1 && y1 > y2 )
1484 {
1485 aElem->props[wxS( "StartAngle" )] = wxS( "270" );
1486 aElem->props[wxS( "Delta" )] = wxS( "90" );
1487 }
1488 else if( cx == x1 && cy == y2 && x2 > x1 && y2 > y1 )
1489 {
1490 aElem->props[wxS( "StartAngle" )] = wxS( "180" );
1491 aElem->props[wxS( "Delta" )] = wxS( "90" );
1492 }
1493 else
1494 {
1495 double theta1 = 180.0 - 180.0 / M_PI * atan2( cy - y1, x1 - cx );
1496 double theta2 = 180.0 - 180.0 / M_PI * atan2( cy - y2, x2 - cx );
1497 double deltaTheta = theta2 - theta1;
1498
1499 while( theta1 > 360 )
1500 theta1 -= 360;
1501
1502 while( deltaTheta < -180 )
1503 deltaTheta += 360;
1504
1505 while( deltaTheta > 180 )
1506 deltaTheta -= 360;
1507
1508 setLong( wxS( "StartAngle" ), static_cast<long>( theta1 ) );
1509 setLong( wxS( "Delta" ), static_cast<long>( deltaTheta ) );
1510 }
1511 }
1512 else if( ( aLineType > 0 && aLineType < 129 ) || aArcType > 0 )
1513 {
1514 long x1 = 0, y1 = 0, x2 = 0, y2 = 0, cx = 0, cy = 0;
1515
1516 if( aElem->HasProp( wxS( "arctype_other_x1" ) ) )
1517 {
1518 x1 = aElem->PropLong( wxS( "arctype_other_x1" ) );
1519 y1 = aElem->PropLong( wxS( "arctype_other_y1" ) );
1520 x2 = aElem->PropLong( wxS( "arctype_other_x2" ) );
1521 y2 = aElem->PropLong( wxS( "arctype_other_y2" ) );
1522 }
1523 else
1524 {
1525 x1 = aElem->PropLong( wxS( "linetype_0_x1" ) );
1526 y1 = aElem->PropLong( wxS( "linetype_0_y1" ) );
1527 x2 = aElem->PropLong( wxS( "linetype_0_x2" ) );
1528 y2 = aElem->PropLong( wxS( "linetype_0_y2" ) );
1529 }
1530
1531 bool cxyOk = true;
1532
1533 // The arc type fixes only the swept magnitude; direction comes from the ccw flag,
1534 // cleared for the clockwise sweeps Eagle writes as a negative curve
1535 long dir = aElem->Prop( wxS( "ccw" ) ) == wxS( "yes" ) ? 1 : -1;
1536
1537 auto setAngles = [&]( int aStart, int aDelta )
1538 {
1539 setLong( wxS( "StartAngle" ), aStart );
1540 setLong( wxS( "Delta" ), dir * aDelta );
1541 };
1542
1543 if( aLineType == 0x78 || aArcType == 0x01 )
1544 {
1545 cx = std::min( x1, x2 );
1546 cy = std::min( y1, y2 );
1547 setAngles( 180, 90 );
1548 }
1549 else if( aLineType == 0x79 || aArcType == 0x02 )
1550 {
1551 cx = std::max( x1, x2 );
1552 cy = std::min( y1, y2 );
1553 setAngles( 270, 90 );
1554 }
1555 else if( aLineType == 0x7a || aArcType == 0x03 )
1556 {
1557 cx = std::max( x1, x2 );
1558 cy = std::max( y1, y2 );
1559 setAngles( 0, 90 );
1560 }
1561 else if( aLineType == 0x7b || aArcType == 0x04 )
1562 {
1563 cx = std::min( x1, x2 );
1564 cy = std::max( y1, y2 );
1565 setAngles( 90, 90 );
1566 }
1567 else if( aLineType == 0x7c || aArcType == 0x05 )
1568 {
1569 cx = ( x1 + x2 ) / 2;
1570 cy = ( y1 + y2 ) / 2;
1571 setAngles( 90, 180 );
1572 }
1573 else if( aLineType == 0x7d || aArcType == 0x06 )
1574 {
1575 cx = ( x1 + x2 ) / 2;
1576 cy = ( y1 + y2 ) / 2;
1577 setAngles( 270, 180 );
1578 }
1579 else if( aLineType == 0x7e || aArcType == 0x07 )
1580 {
1581 cx = ( x1 + x2 ) / 2;
1582 cy = ( y1 + y2 ) / 2;
1583 setAngles( 180, 180 );
1584 }
1585 else if( aLineType == 0x7f || aArcType == 0x08 )
1586 {
1587 cx = ( x1 + x2 ) / 2;
1588 cy = ( y1 + y2 ) / 2;
1589 setAngles( 0, 180 );
1590 }
1591 else
1592 {
1593 cxyOk = false;
1594 }
1595
1596 if( !cxyOk )
1597 cx = cy = 0;
1598
1599 long radius = static_cast<long>( std::hypot( cx - x2, cy - y2 ) );
1600 setLong( wxS( "radius" ), radius );
1601 setLong( wxS( "x" ), cx );
1602 setLong( wxS( "y" ), cy );
1603 }
1604}
1605
1606
1608{
1609 if( aRoot->id == EGKW_SECT_LINE )
1610 {
1611 int lineType = aRoot->HasProp( wxS( "linetype" ) ) ? (int) aRoot->PropLong( wxS( "linetype" ) ) : -1;
1612
1613 if( lineType >= 0 )
1614 {
1615 // Straight and arc wires both keep their endpoints in the linetype_0
1616 // fields.
1617 aRoot->props[wxS( "x1" )] = aRoot->Prop( wxS( "linetype_0_x1" ) );
1618 aRoot->props[wxS( "y1" )] = aRoot->Prop( wxS( "linetype_0_y1" ) );
1619 aRoot->props[wxS( "x2" )] = aRoot->Prop( wxS( "linetype_0_x2" ) );
1620 aRoot->props[wxS( "y2" )] = aRoot->Prop( wxS( "linetype_0_y2" ) );
1621 aRoot->props[wxS( "width" )] = aRoot->PropDoubled( wxS( "half_width" ) );
1622 }
1623
1624 if( lineType > 0 )
1625 {
1626 // arcDecode adds the center, radius and swept angle. KiCad's wire
1627 // reader needs the endpoints plus a "curve" (the swept angle).
1628 arcDecode( aRoot, -1, lineType );
1629
1630 // A zero swept angle is a straight segment; emitting curve="0" would abort the
1631 // shared wire reader in ConvertArcCenter, so leave the wire uncurved.
1632 if( aRoot->HasProp( wxS( "Delta" ) ) && aRoot->PropLong( wxS( "Delta" ) ) != 0 )
1633 aRoot->props[wxS( "curve" )] = aRoot->Prop( wxS( "Delta" ) );
1634 }
1635 }
1636
1637 for( const auto& child : aRoot->children )
1638 postprocWires( child.get() );
1639}
1640
1641
1643{
1644 if( aRoot->id == EGKW_SECT_ARC )
1645 {
1646 int arcType = aRoot->HasProp( wxS( "arctype" ) ) ? (int) aRoot->PropLong( wxS( "arctype" ) ) : -1;
1647
1648 if( arcType == 0 )
1649 {
1650 aRoot->props[wxS( "x1" )] = aRoot->Prop( wxS( "arc_x1" ) );
1651 aRoot->props[wxS( "y1" )] = aRoot->Prop( wxS( "arc_y1" ) );
1652 aRoot->props[wxS( "x2" )] = aRoot->Prop( wxS( "arc_x2" ) );
1653 aRoot->props[wxS( "y2" )] = aRoot->Prop( wxS( "arc_y2" ) );
1654 }
1655 else if( arcType > 0 )
1656 {
1657 aRoot->props[wxS( "x1" )] = aRoot->Prop( wxS( "arctype_other_x1" ) );
1658 aRoot->props[wxS( "y1" )] = aRoot->Prop( wxS( "arctype_other_y1" ) );
1659 aRoot->props[wxS( "x2" )] = aRoot->Prop( wxS( "arctype_other_x2" ) );
1660 aRoot->props[wxS( "y2" )] = aRoot->Prop( wxS( "arctype_other_y2" ) );
1661 }
1662
1663 if( arcType >= 0 )
1664 aRoot->props[wxS( "width" )] = aRoot->PropDoubled( wxS( "half_width" ) );
1665
1666 arcDecode( aRoot, arcType, -1 );
1667
1668 // A zero swept angle is a straight segment; emitting curve="0" would abort the
1669 // shared reader in ConvertArcCenter, so leave it uncurved.
1670 if( aRoot->HasProp( wxS( "Delta" ) ) && aRoot->PropLong( wxS( "Delta" ) ) != 0 )
1671 aRoot->props[wxS( "curve" )] = aRoot->Prop( wxS( "Delta" ) );
1672 }
1673
1674 for( const auto& child : aRoot->children )
1675 postprocArcs( child.get() );
1676}
1677
1678
1680{
1681 // Eagle stores a polygon outline as a chain of connected wire segments, but the
1682 // XML reader wants the outline as <vertex> nodes. Replace each direct wire child
1683 // with a vertex at the segment start point, carrying the segment's curvature as
1684 // the vertex-to-next curve. Runs after postprocWires/postprocArcs have populated
1685 // the endpoints and curve, and before postprocUnits scales the coordinates.
1686 if( aRoot->id == EGKW_SECT_POLYGON )
1687 {
1688 std::vector<std::unique_ptr<EGB_NODE>> rebuilt;
1689
1690 for( auto& child : aRoot->children )
1691 {
1692 if( child->id != EGKW_SECT_LINE )
1693 {
1694 rebuilt.push_back( std::move( child ) );
1695 continue;
1696 }
1697
1698 auto vertex = std::make_unique<EGB_NODE>();
1699 vertex->name = wxS( "vertex" );
1700 vertex->parent = aRoot;
1701 vertex->props[wxS( "x" )] = child->Prop( wxS( "x1" ) );
1702 vertex->props[wxS( "y" )] = child->Prop( wxS( "y1" ) );
1703
1704 if( child->HasProp( wxS( "curve" ) ) )
1705 vertex->props[wxS( "curve" )] = child->Prop( wxS( "curve" ) );
1706
1707 rebuilt.push_back( std::move( vertex ) );
1708 }
1709
1710 aRoot->children = std::move( rebuilt );
1711 }
1712
1713 for( const auto& child : aRoot->children )
1714 postprocPolygons( child.get() );
1715}
1716
1717
1719{
1720 if( aRoot->id == EGKW_SECT_VIA )
1721 {
1722 // KiCad requires an "extent" layer-range string. The binary layers byte
1723 // is not a 1:1 map and pre-v6 vias are through-hole (0xF0 sentinel), so
1724 // span the full copper stack. Blind/buried vias are out of scope.
1725 aRoot->props[wxS( "extent" )] = wxS( "1-16" );
1726 }
1727
1728 for( const auto& child : aRoot->children )
1729 postprocVias( child.get() );
1730}
1731
1732
1734{
1735 // Binary coordinates are decimicrons (0.1 um); KiCad's XML reader assumes
1736 // millimetres for unitless values. Rewrite every dimensional attribute as a
1737 // millimetre decimal so the reader scales it correctly. Counts, layer
1738 // numbers, ratios, angles and booleans are left untouched.
1739 static const wxString dimAttrs[] = { wxS( "x" ), wxS( "y" ), wxS( "x1" ), wxS( "y1" ),
1740 wxS( "x2" ), wxS( "y2" ), wxS( "x3" ), wxS( "y3" ),
1741 wxS( "width" ), wxS( "drill" ), wxS( "diameter" ), wxS( "radius" ),
1742 wxS( "size" ), wxS( "dx" ), wxS( "dy" ), wxS( "spacing" ),
1743 wxS( "isolate" ) };
1744
1745 for( const wxString& key : dimAttrs )
1746 {
1747 auto it = aRoot->props.find( key );
1748
1749 if( it == aRoot->props.end() )
1750 continue;
1751
1752 double du = 0;
1753
1754 if( it->second.ToCDouble( &du ) )
1755 it->second = wxString::FromCDouble( du * 0.0001, 4 );
1756 }
1757
1758 for( const auto& child : aRoot->children )
1759 postprocUnits( child.get() );
1760}
1761
1762
1764{
1765 if( aRoot->id == EGKW_SECT_CIRCLE && aRoot->HasProp( wxS( "half_width" ) ) )
1766 {
1767 aRoot->props[wxS( "width" )] = aRoot->PropDoubled( wxS( "half_width" ) );
1768 }
1769
1770 for( const auto& child : aRoot->children )
1771 postprocCircles( child.get() );
1772}
1773
1774
1776{
1777 if( aRoot->id == EGKW_SECT_SMD )
1778 {
1779 if( aRoot->HasProp( wxS( "half_dx" ) ) )
1780 {
1781 aRoot->props[wxS( "dx" )] = aRoot->PropDoubled( wxS( "half_dx" ) );
1782 }
1783
1784 if( aRoot->HasProp( wxS( "half_dy" ) ) )
1785 {
1786 aRoot->props[wxS( "dy" )] = aRoot->PropDoubled( wxS( "half_dy" ) );
1787 }
1788 }
1789
1790 for( const auto& child : aRoot->children )
1791 postprocSmd( child.get() );
1792}
1793
1794
1796{
1797 // The binary stores the pad shape as an ordinal, but the shared XML reader
1798 // matches it by name (square | round | octagon | long | offset) and silently
1799 // treats anything else as round, so every through-hole pad imported round. The
1800 // ordinal follows the same order the reader enumerates the names, verified on real
1801 // boards: resistor pads (2) draw as octagons and TO-92/diode/DIP pads (3) as
1802 // oblongs in Eagle. Rewrite the ordinal into that name; an out-of-range value is
1803 // dropped so the reader's own round default applies rather than a wrong shape.
1804 if( aRoot->id == EGKW_SECT_PAD && aRoot->HasProp( wxS( "shape" ) ) )
1805 {
1806 static const std::map<long, wxString> names = {
1807 { 0, wxS( "square" ) }, { 1, wxS( "round" ) }, { 2, wxS( "octagon" ) },
1808 { 3, wxS( "long" ) }, { 4, wxS( "offset" ) } };
1809
1810 auto it = names.find( aRoot->PropLong( wxS( "shape" ) ) );
1811
1812 if( it != names.end() )
1813 aRoot->props[wxS( "shape" )] = it->second;
1814 else
1815 aRoot->props.erase( wxS( "shape" ) );
1816 }
1817
1818 for( const auto& child : aRoot->children )
1819 postprocPadShapes( child.get() );
1820}
1821
1822
1824{
1825 if( aRoot->id == EGKW_SECT_PAD || aRoot->id == EGKW_SECT_HOLE || aRoot->id == EGKW_SECT_VIA
1826 || aRoot->id == EGKW_SECT_TEXT )
1827 {
1828 if( aRoot->HasProp( wxS( "half_drill" ) ) )
1829 {
1830 aRoot->props[wxS( "drill" )] = aRoot->PropDoubled( wxS( "half_drill" ) );
1831 }
1832
1833 if( aRoot->HasProp( wxS( "half_diameter" ) ) )
1834 {
1835 aRoot->props[wxS( "diameter" )] = aRoot->PropDoubled( wxS( "half_diameter" ) );
1836 }
1837
1838 if( aRoot->HasProp( wxS( "half_size" ) ) )
1839 {
1840 aRoot->props[wxS( "size" )] = aRoot->PropDoubled( wxS( "half_size" ) );
1841 }
1842 }
1843
1844 for( const auto& child : aRoot->children )
1845 postprocDimensions( child.get() );
1846}
1847
1848
1850{
1851 switch( aId )
1852 {
1853 case EGKW_SECT_SMD:
1854 case EGKW_SECT_PIN:
1856 case EGKW_SECT_PAD:
1857 case EGKW_SECT_TEXT:
1865 case EGKW_SECT_INSTANCE:
1866 case EGKW_SECT_ELEMENT: return true;
1867 default: return false;
1868 }
1869}
1870
1871
1873{
1874 if( isRotatable( aRoot->id ) && aRoot->HasProp( wxS( "bin_rot" ) ) )
1875 {
1876 // mirrored/spin are read as T_BMB ("yes"/"no") or as a T_UBF integer
1877 // depending on the record, so treat anything other than the false tokens
1878 // as set.
1879 auto flagSet = [&]( const wxString& aKey )
1880 {
1881 if( !aRoot->HasProp( aKey ) )
1882 return false;
1883
1884 const wxString v = aRoot->Prop( aKey );
1885 return v != wxS( "no" ) && v != wxS( "0" );
1886 };
1887
1888 bool mirrored = flagSet( wxS( "mirrored" ) );
1889 bool spin = flagSet( wxS( "spin" ) );
1890
1891 long deg = aRoot->PropLong( wxS( "bin_rot" ) );
1892
1893 // Pins and instances store rotation as a two-bit quadrant count; every
1894 // other rotatable record stores a twelve-bit angle where a full turn is
1895 // 4096 units (pads and rectangles carry it in the low bits of a wider
1896 // field, hence the mask).
1897 double degrees;
1898
1899 if( aRoot->id == EGKW_SECT_PIN || aRoot->id == EGKW_SECT_INSTANCE )
1900 degrees = ( deg & 0x3 ) * 90.0;
1901 else
1902 degrees = 360.0 * ( deg & 0x0FFF ) / 4096.0;
1903
1904 wxString rot;
1905
1906 if( spin )
1907 rot << wxS( "S" );
1908
1909 if( mirrored )
1910 rot << wxS( "M" );
1911
1912 rot << wxS( "R" ) << wxString::FromCDouble( degrees, 4 );
1913
1914 aRoot->props[wxS( "rot" )] = rot;
1915 }
1916
1917 for( const auto& child : aRoot->children )
1918 postprocRotation( child.get() );
1919}
1920
1921
1923{
1924 // The XML pin reader takes length/direction/visible/function as Eagle enum names, but
1925 // the binary stores them as the ordinals below (order per Eagle's DTD). Translate them
1926 // or the reader silently falls back to its defaults (long/io/both/none), losing the
1927 // real pin geometry and electrical type.
1928 static const wxString c_length[] = { wxS( "point" ), wxS( "short" ), wxS( "middle" ), wxS( "long" ) };
1929 static const wxString c_direction[] = { wxS( "nc" ), wxS( "in" ), wxS( "out" ), wxS( "io" ), wxS( "oc" ),
1930 wxS( "pwr" ), wxS( "pas" ), wxS( "hiz" ), wxS( "sup" ) };
1931 static const wxString c_visible[] = { wxS( "off" ), wxS( "pad" ), wxS( "pin" ), wxS( "both" ) };
1932 static const wxString c_function[] = { wxS( "none" ), wxS( "dot" ), wxS( "clk" ), wxS( "dotclk" ) };
1933
1934 auto mapField = [&]( EGB_NODE* aNode, const wxString& aKey, const wxString* aTable, size_t aCount )
1935 {
1936 if( !aNode->HasProp( aKey ) )
1937 return;
1938
1939 long idx = aNode->PropLong( aKey );
1940
1941 if( idx >= 0 && idx < (long) aCount )
1942 aNode->props[aKey] = aTable[idx];
1943 };
1944
1945 aRoot->ForEach(
1946 [&]( EGB_NODE* aNode )
1947 {
1948 if( aNode->id != EGKW_SECT_PIN )
1949 return;
1950
1951 mapField( aNode, wxS( "length" ), c_length, sizeof( c_length ) / sizeof( c_length[0] ) );
1952 mapField( aNode, wxS( "direction" ), c_direction, sizeof( c_direction ) / sizeof( c_direction[0] ) );
1953 mapField( aNode, wxS( "visible" ), c_visible, sizeof( c_visible ) / sizeof( c_visible[0] ) );
1954 mapField( aNode, wxS( "function" ), c_function, sizeof( c_function ) / sizeof( c_function[0] ) );
1955 } );
1956}
1957
1958
1960{
1961 switch( aRoot->id )
1962 {
1963 case EGKW_SECT_TEXT:
1970 case EGKW_SECT_SMASHEDXREF: fixLongText( aRoot, wxS( "textfield" ) ); break;
1971
1972 case EGKW_SECT_LAYER:
1973 case EGKW_SECT_LIBRARY:
1974 case EGKW_SECT_SIGNAL:
1975 case EGKW_SECT_SYMBOL:
1977 case EGKW_SECT_PAD:
1978 case EGKW_SECT_SMD:
1979 case EGKW_SECT_PIN:
1980 case EGKW_SECT_GATE: fixLongText( aRoot, wxS( "name" ) ); break;
1981
1982 // Multi-field records consume free-text in the field order Eagle serialized
1983 // them, which matches pyeagle's parse() call order (value-then-name, etc.).
1984 case EGKW_SECT_ELEMENT2:
1985 case EGKW_SECT_PART:
1986 fixLongText( aRoot, wxS( "value" ) );
1987 fixLongText( aRoot, wxS( "name" ) );
1988 break;
1989
1990 case EGKW_SECT_DEVICES:
1991 case EGKW_SECT_SYMBOLS: fixLongText( aRoot, wxS( "library" ) ); break;
1992
1994 fixLongText( aRoot, wxS( "name" ) );
1995 fixLongText( aRoot, wxS( "table" ) );
1996 break;
1997
1998 case EGKW_SECT_PACKAGE:
1999 fixLongText( aRoot, wxS( "name" ) );
2000 fixLongText( aRoot, wxS( "desc" ) );
2001 break;
2002
2003 case EGKW_SECT_PACKAGES:
2004 fixLongText( aRoot, wxS( "library" ) );
2005 fixLongText( aRoot, wxS( "desc" ) );
2006 break;
2007
2008 case EGKW_SECT_SCHEMA: fixLongText( aRoot, wxS( "xref_format" ) ); break;
2009
2011 fixLongText( aRoot, wxS( "attribute" ) );
2012 fixLongText( aRoot, wxS( "symbol" ) );
2013 break;
2014
2015 case EGKW_SECT_DEVICE:
2016 fixLongText( aRoot, wxS( "name" ) );
2017 fixLongText( aRoot, wxS( "desc" ) );
2018 fixLongText( aRoot, wxS( "prefix" ) );
2019 break;
2020
2021 default: break;
2022 }
2023
2024 for( const auto& child : aRoot->children )
2025 postprocFreeText( child.get() );
2026}
2027
2028
2030{
2031 switch( aId )
2032 {
2033 case EGKW_SECT_TEXT:
2040 case EGKW_SECT_SMASHEDXREF: return true;
2041 default: return false;
2042 }
2043}
2044
2045
2047{
2048 // A text-family record with a string too long for its 6-byte inline field is
2049 // immediately followed by exactly one 0x3200 record carrying the full string.
2050 // Fold that string back onto the preceding text sibling and drop the record;
2051 // the XML schema has no standalone longtext element. A longtext with no valid
2052 // text predecessor (malformed input, or a second consecutive longtext) is
2053 // dropped rather than emitted, so invalid XML never reaches the shared reader.
2054 std::vector<std::unique_ptr<EGB_NODE>> kept;
2055 EGB_NODE* eligible = nullptr;
2056
2057 for( auto& child : aRoot->children )
2058 {
2059 if( child->id == EGKW_SECT_LONGTEXT )
2060 {
2061 if( eligible != nullptr )
2062 eligible->props[wxS( "textfield" )] = child->Prop( wxS( "textfield" ) );
2063
2064 eligible = nullptr;
2065 continue;
2066 }
2067
2068 kept.push_back( std::move( child ) );
2069 eligible = ( kept.back()->HasProp( wxS( "textfield" ) ) && isLongTextHost( kept.back()->id ) )
2070 ? kept.back().get()
2071 : nullptr;
2072 }
2073
2074 aRoot->children = std::move( kept );
2075
2076 for( const auto& child : aRoot->children )
2077 postprocLongText( child.get() );
2078}
2079
2080
2082{
2083 // The XML reader takes a text element's string from its PCDATA body, not an
2084 // attribute, so surface the decoded textfield as node content.
2085 if( isLongTextHost( aRoot->id ) && aRoot->HasProp( wxS( "textfield" ) ) )
2086 aRoot->content = aRoot->Prop( wxS( "textfield" ) );
2087
2088 for( const auto& child : aRoot->children )
2089 postprocTextContent( child.get() );
2090}
2091
2092
2094{
2095 // Move every drawing/layer under the synthetic drawing/layers node, keeping
2096 // order. Reparenting is by ownership transfer.
2097 std::vector<std::unique_ptr<EGB_NODE>> kept;
2098
2099 for( auto& child : aDrawing->children )
2100 {
2101 if( child->id == EGKW_SECT_LAYER )
2102 {
2103 // The binary stores "visible" as a 2-bit field, but KiCad's reader
2104 // parses it as a yes/no bool. Normalize to a truthy token.
2105 if( child->HasProp( wxS( "visible" ) ) )
2106 {
2107 child->props[wxS( "visible" )] = child->PropLong( wxS( "visible" ) ) != 0 ? wxS( "yes" ) : wxS( "no" );
2108 }
2109
2110 child->parent = aLayers;
2111 aLayers->children.push_back( std::move( child ) );
2112 }
2113 else
2114 {
2115 kept.push_back( std::move( child ) );
2116 }
2117 }
2118
2119 aDrawing->children = std::move( kept );
2120}
2121
2122
2123void EAGLE_BIN_PARSER::postprocDrc( EGB_NODE* aDrcNode, const DRC_CTX& aDrc )
2124{
2125 auto addParam = [&]( const wxString& aName, const wxString& aValue )
2126 {
2127 EGB_NODE* p = aDrcNode->AddChild( EGKW_SECT_DRC, wxS( "param" ) );
2128 p->props[wxS( "name" )] = aName;
2129 p->props[wxS( "value" )] = aValue;
2130 };
2131
2132 addParam( wxS( "mdWireWire" ), wxString::Format( wxS( "%ldmil" ), aDrc.mdWireWire ) );
2133 addParam( wxS( "msWidth" ), wxString::Format( wxS( "%ldmil" ), aDrc.msWidth ) );
2134 addParam( wxS( "rvPadTop" ), wxString::FromCDouble( aDrc.rvPadTop ) );
2135 addParam( wxS( "rvPadInner" ), wxString::FromCDouble( aDrc.rvPadInner ) );
2136 addParam( wxS( "rvPadBottom" ), wxString::FromCDouble( aDrc.rvPadBottom ) );
2137}
2138
2139
2141{
2142 // In a board, the libraries node holds packages nodes directly; the XML
2143 // schema expects each wrapped in a library node. Wrap every bare packages
2144 // child.
2145 if( aLibraries == nullptr )
2146 return;
2147
2148 if( aLibraries->FindChildById( EGKW_SECT_LIBRARY ) != nullptr )
2149 return; // already a proper library subtree
2150
2151 std::vector<std::unique_ptr<EGB_NODE>> wrapped;
2152
2153 for( auto& child : aLibraries->children )
2154 {
2155 if( child->id != EGKW_SECT_PACKAGES )
2156 continue;
2157
2158 auto lib = std::make_unique<EGB_NODE>();
2159 lib->id = EGKW_SECT_LIBRARY;
2160 lib->name = wxS( "library" );
2161 lib->parent = aLibraries;
2162 child->parent = lib.get();
2163 lib->children.push_back( std::move( child ) );
2164 wrapped.push_back( std::move( lib ) );
2165 }
2166
2167 if( !wrapped.empty() )
2168 aLibraries->children = std::move( wrapped );
2169}
2170
2171
2173{
2174 if( aElements == nullptr )
2175 return;
2176
2177 // Each element is followed (as a child) by an element2 record carrying its
2178 // name and value; merge those up onto the element.
2179 for( auto& el : aElements->children )
2180 {
2181 if( el->children.empty() || el->children.front()->id != EGKW_SECT_ELEMENT2 )
2182 continue;
2183
2184 for( auto& el2 : el->children )
2185 {
2186 if( el2->id != EGKW_SECT_ELEMENT2 )
2187 continue;
2188
2189 for( const auto& [key, value] : el2->props )
2190 {
2191 if( key == wxS( "name" ) )
2192 {
2193 if( value == wxS( "-" ) )
2194 el->props[wxS( "name" )] = wxS( "HYPHEN" );
2195 else
2196 el->props[wxS( "name" )] = value;
2197 }
2198 else if( key == wxS( "value" ) )
2199 {
2200 el->props[wxS( "value" )] = value;
2201 }
2202 }
2203 }
2204 }
2205}
2206
2207
2209{
2210 if( aLibraries == nullptr )
2211 return;
2212
2213 // The binary references libraries and packages by 1-based ordinal, but the
2214 // XML schema (and KiCad's reader) resolve them by name. Give every library
2215 // and package a unique non-empty name, then rewrite each element's numeric
2216 // library/package references to those names so footprint lookup succeeds.
2217
2218 std::map<wxString, int> seenLibs;
2219
2220 for( size_t li = 0; li < aLibraries->children.size(); li++ )
2221 {
2222 EGB_NODE* lib = aLibraries->children[li].get();
2223
2224 if( lib->id != EGKW_SECT_LIBRARY )
2225 continue;
2226
2228
2229 // The library name is carried on the inner packages node; fall back to
2230 // the ordinal when it is blank.
2231 wxString libName = pkgs ? pkgs->Prop( wxS( "library" ) ) : wxString();
2232
2233 if( libName.IsEmpty() )
2234 libName = wxString::Format( wxS( "lib%zu" ), li + 1 );
2235
2236 // A board can embed several single-package libraries Eagle named after their sole
2237 // component, so library names repeat. The reader keys footprints by (library, package)
2238 // and rejects a duplicate pair, so disambiguate repeated library names the same way
2239 // package names are disambiguated below.
2240 if( int& libCount = seenLibs[libName]; libCount++ > 0 )
2241 libName = wxString::Format( wxS( "%s_%d" ), libName, libCount );
2242
2243 lib->props[wxS( "name" )] = libName;
2244
2245 if( pkgs == nullptr )
2246 continue;
2247
2248 std::map<wxString, int> seen;
2249
2250 for( size_t pi = 0; pi < pkgs->children.size(); pi++ )
2251 {
2252 EGB_NODE* pkg = pkgs->children[pi].get();
2253 wxString name = pkg->Prop( wxS( "name" ) );
2254
2255 if( name.IsEmpty() )
2256 name = wxString::Format( wxS( "pkg%zu" ), pi + 1 );
2257
2258 // Disambiguate repeated names so the per-library package map stays
2259 // unique.
2260 if( int& count = seen[name]; count++ > 0 )
2261 name = wxString::Format( wxS( "%s_%d" ), name, count );
2262
2263 pkg->props[wxS( "name" )] = name;
2264 }
2265 }
2266
2267 if( aElements == nullptr )
2268 return;
2269
2270 auto nameByIdx = [&]( EGB_NODE* aParent, long aIdx ) -> wxString
2271 {
2272 if( aParent == nullptr || aIdx < 1 || aIdx > (long) aParent->children.size() )
2273 return wxString();
2274
2275 return aParent->children[aIdx - 1]->Prop( wxS( "name" ) );
2276 };
2277
2278 for( auto& el : aElements->children )
2279 {
2280 if( el->id != EGKW_SECT_ELEMENT )
2281 continue;
2282
2283 long libIdx = el->PropLong( wxS( "library" ) );
2284 EGB_NODE* lib = ( libIdx >= 1 && libIdx <= (long) aLibraries->children.size() )
2285 ? aLibraries->children[libIdx - 1].get()
2286 : nullptr;
2287
2288 if( lib == nullptr )
2289 continue;
2290
2291 el->props[wxS( "library" )] = lib->Prop( wxS( "name" ) );
2292
2294 wxString pkgName = nameByIdx( pkgs, el->PropLong( wxS( "package" ) ) );
2295
2296 if( !pkgName.IsEmpty() )
2297 el->props[wxS( "package" )] = pkgName;
2298 }
2299}
2300
2301
2303{
2304 if( aSignals == nullptr )
2305 return;
2306
2307 // Flatten any nested signal so every signal sits directly under signals.
2308 // Connectivity of nested nets is not preserved, matching Eagle's own
2309 // binary-to-XML conversion. Iterate by index because nested signals are
2310 // appended to the same vector and must themselves be flattened, which
2311 // handles three or more levels of nesting.
2312 for( size_t i = 0; i < aSignals->children.size(); i++ )
2313 {
2314 EGB_NODE* sig = aSignals->children[i].get();
2315
2316 if( sig->id != EGKW_SECT_SIGNAL )
2317 continue;
2318
2319 std::vector<std::unique_ptr<EGB_NODE>> kept;
2320 std::vector<std::unique_ptr<EGB_NODE>> promoted;
2321
2322 for( auto& inner : sig->children )
2323 {
2324 if( inner->id == EGKW_SECT_SIGNAL )
2325 {
2326 inner->parent = aSignals;
2327 promoted.push_back( std::move( inner ) );
2328 }
2329 else
2330 {
2331 kept.push_back( std::move( inner ) );
2332 }
2333 }
2334
2335 sig->children = std::move( kept );
2336
2337 // Append after rebuilding sig->children; this may reallocate, but sig
2338 // is only dereferenced above and i indexes the (stable) container.
2339 for( auto& p : promoted )
2340 aSignals->children.push_back( std::move( p ) );
2341 }
2342}
2343
2344
2345void EAGLE_BIN_PARSER::postprocContactRefs( EGB_NODE* aSignals, EGB_NODE* aElements, EGB_NODE* aLibraries )
2346{
2347 if( aSignals == nullptr || aElements == nullptr || aLibraries == nullptr )
2348 return;
2349
2350 auto elemByIdx = [&]( long aIdx ) -> EGB_NODE*
2351 {
2352 if( aIdx < 1 || aIdx > (long) aElements->children.size() )
2353 return nullptr;
2354
2355 return aElements->children[aIdx - 1].get();
2356 };
2357
2358 auto libByIdx = [&]( long aIdx ) -> EGB_NODE*
2359 {
2360 if( aIdx < 1 || aIdx > (long) aLibraries->children.size() )
2361 return nullptr;
2362
2363 return aLibraries->children[aIdx - 1].get();
2364 };
2365
2366 auto pkgByIdx = [&]( EGB_NODE* aLib, long aIdx ) -> EGB_NODE*
2367 {
2368 if( aLib == nullptr )
2369 return nullptr;
2370
2371 EGB_NODE* pkgs = aLib->FindChildById( EGKW_SECT_PACKAGES );
2372
2373 if( pkgs == nullptr || aIdx < 1 || aIdx > (long) pkgs->children.size() )
2374 return nullptr;
2375
2376 return pkgs->children[aIdx - 1].get();
2377 };
2378
2379 for( auto& sig : aSignals->children )
2380 {
2381 // Resolve every contactref, regardless of sibling order; wires or
2382 // polygons may precede the contactrefs within a signal.
2383 for( auto& cr : sig->children )
2384 {
2385 if( cr->id != EGKW_SECT_CONTACTREF )
2386 continue;
2387
2388 long partNum = cr->PropLong( wxS( "partnumber" ) );
2389 EGB_NODE* elem = elemByIdx( partNum );
2390
2391 if( elem == nullptr )
2392 continue;
2393
2394 cr->props[wxS( "element" )] = elem->Prop( wxS( "name" ) );
2395
2396 // Resolve the pad name by walking the package pads/pins/smd in order.
2397 EGB_NODE* lib = libByIdx( elem->PropLong( wxS( "library" ) ) );
2398 EGB_NODE* pkg = pkgByIdx( lib, elem->PropLong( wxS( "package" ) ) );
2399
2400 if( pkg == nullptr )
2401 {
2402 cr->props[wxS( "pad" )] = wxS( "PIN_NOT_FOUND" );
2403 continue;
2404 }
2405
2406 long pinNum = cr->PropLong( wxS( "pin" ) );
2407 EGB_NODE* found = nullptr;
2408
2409 for( const auto& child : pkg->children )
2410 {
2411 int kind = child->id & 0xFF00;
2412
2413 if( kind == EGKW_SECT_PAD || kind == EGKW_SECT_SMD || kind == EGKW_SECT_PIN )
2414 {
2415 if( --pinNum < 1 )
2416 {
2417 found = child.get();
2418 break;
2419 }
2420 }
2421 }
2422
2423 if( found == nullptr )
2424 cr->props[wxS( "pad" )] = wxS( "PIN_NOT_FOUND" );
2425 else if( found->HasProp( wxS( "name" ) ) )
2426 cr->props[wxS( "pad" )] = found->Prop( wxS( "name" ) );
2427 else
2428 cr->props[wxS( "pad" )] = cr->Prop( wxS( "pin" ) );
2429 }
2430 }
2431}
2432
2433
2435{
2436 EGB_NODE* drawing = aRoot->children.empty() ? nullptr : aRoot->children.front().get();
2437
2438 if( drawing == nullptr )
2439 THROW_IO_ERROR( _( "Eagle binary file has no drawing section." ) );
2440
2441 // KiCad's XML reader resolves the layer map from drawing/layers, so the
2442 // synthetic node must live under drawing, not the eagle root.
2443 EGB_NODE* layers = drawing->AddChild( EGKW_SECT_LAYERS, wxS( "layers" ) );
2444
2445 EGB_NODE* board = drawing->FindChildById( EGKW_SECT_BOARD );
2446 EGB_NODE* drcNode = nullptr;
2447 EGB_NODE* libraries = nullptr;
2448 EGB_NODE* signals = nullptr;
2449 EGB_NODE* elements = nullptr;
2450
2451 if( board != nullptr )
2452 {
2453 drcNode = board->AddChild( EGKW_SECT_DRC, wxS( "designrules" ) );
2454 libraries = board->FindChildByName( wxS( "libraries" ) );
2455
2456 if( libraries == nullptr )
2457 THROW_IO_ERROR( _( "Eagle binary layout is missing a board/libraries node." ) );
2458
2459 signals = board->FindChildByName( wxS( "signals" ) );
2460 elements = board->FindChildByName( wxS( "elements" ) );
2461 }
2462
2463 // Fold trailing longtext records onto their text siblings before any pass
2464 // walks the tree by sibling order.
2465 postprocLongText( aRoot );
2466
2467 postprocLayers( drawing, layers );
2468
2469 if( drcNode != nullptr )
2470 postprocDrc( drcNode, aDrc );
2471
2472 postprocLibs( libraries );
2473 postprocElements( elements );
2474 postprocSignals( signals );
2475
2476 postprocWires( aRoot );
2477 postprocArcs( aRoot );
2478 postprocPolygons( aRoot );
2479 postprocVias( aRoot );
2480 postprocCircles( aRoot );
2481 postprocSmd( aRoot );
2482 postprocPadShapes( aRoot );
2483 postprocDimensions( aRoot );
2484
2485 // Resolve long-text names before contactrefs copy element and pad names,
2486 // and before postprocNames disambiguates package names.
2487 postprocFreeText( aRoot );
2488
2489 // Move resolved text strings into node content after the free-text pass has
2490 // backfilled any 0x7F deferred references.
2491 postprocTextContent( aRoot );
2492
2493 // postprocContactRefs reads element library/package ordinals, so it must run
2494 // before postprocNames rewrites those ordinals into names.
2495 postprocContactRefs( signals, elements, libraries );
2496 postprocNames( libraries, elements );
2497
2498 postprocRotation( aRoot );
2499
2500 // Backfill XML-required attributes (frame columns) shared with the schematic path.
2501 postprocRequiredAttrs( aRoot );
2502
2503 // Custom element attributes decode without a name and would abort the shared
2504 // reader; prune them after every naming pass has had a chance to backfill one.
2505 postprocAttributes( aRoot );
2506
2507 // Must run last so every dimensional attribute has its final value before
2508 // the decimicron-to-millimetre rewrite.
2509 postprocUnits( aRoot );
2510}
2511
2512
2514{
2515 // The binary attribute record carries the placement of a custom element
2516 // attribute but not its name, and there is no reliable path to recover one.
2517 // The XML schema makes name required on <attribute>, so emitting a nameless
2518 // one aborts the shared reader. Drop the unrecoverable nodes rather than
2519 // synthesize invalid XML; only the displayed text placement is lost.
2520 std::vector<std::unique_ptr<EGB_NODE>> kept;
2521
2522 for( auto& child : aRoot->children )
2523 {
2524 if( child->id == EGKW_SECT_ATTRIBUTE && !child->HasProp( wxS( "name" ) ) )
2525 continue;
2526
2527 kept.push_back( std::move( child ) );
2528 }
2529
2530 aRoot->children = std::move( kept );
2531
2532 for( const auto& child : aRoot->children )
2533 postprocAttributes( child.get() );
2534}
2535
2536
2537wxXmlNode* EAGLE_BIN_PARSER::toXml( const EGB_NODE* aNode ) const
2538{
2539 wxXmlNode* xml = new wxXmlNode( wxXML_ELEMENT_NODE, aNode->name );
2540
2541 for( const auto& [key, value] : aNode->props )
2542 xml->AddAttribute( key, value );
2543
2544 if( !aNode->content.IsEmpty() )
2545 xml->AddChild( new wxXmlNode( wxXML_TEXT_NODE, wxEmptyString, aNode->content ) );
2546
2547 // wxXmlNode::AddChild appends to the end of the sibling chain, preserving
2548 // document order.
2549 for( const auto& child : aNode->children )
2550 xml->AddChild( toXml( child.get() ) );
2551
2552 return xml;
2553}
2554
2555
2556std::unique_ptr<wxXmlDocument> EAGLE_BIN_PARSER::Parse( const std::vector<uint8_t>& aBytes )
2557{
2558 m_buf = &aBytes;
2559 m_pos = 0;
2560 m_longRefs.clear();
2561
2562 if( aBytes.size() < 24 )
2563 THROW_IO_ERROR( _( "File is too small to be an Eagle binary board." ) );
2564
2565 // The drawing header's major version selects the pad/SMD record layout, so it
2566 // must be known before readBlock() decodes any pad. It lives at a fixed offset
2567 // in the first block; the same value is surfaced as the drawing v1 attribute.
2568 m_majorVer = loadS32( 8, 1 );
2569
2570 m_root = std::make_unique<EGB_NODE>();
2571 m_root->id = 0;
2572 m_root->name = wxS( "eagle" );
2573
2574 long numBlocks = -1;
2575 readBlock( numBlocks, m_root.get() );
2576
2577 // A schematic drawing carries a `schema` section where a board carries `board`.
2578 EGB_NODE* drawing = m_root->children.empty() ? nullptr : m_root->children.front().get();
2579 bool isSchematic = drawing && drawing->FindChildById( EGKW_SECT_SCHEMA ) != nullptr;
2580
2581 // EAGLE_DOC requires a version attribute on the <eagle> root for every drawing kind
2582 // (board, schematic and standalone library). Synthesize one from the drawing's binary
2583 // version bytes; the value only feeds behavioural gating that already tolerates a coarse
2584 // version.
2585 if( drawing != nullptr )
2586 {
2587 long v1 = drawing->HasProp( wxS( "v1" ) ) ? drawing->PropLong( wxS( "v1" ) ) : 5;
2588 long v2 = drawing->HasProp( wxS( "v2" ) ) ? drawing->PropLong( wxS( "v2" ) ) : 0;
2589 m_root->props[wxS( "version" )] = wxString::Format( wxS( "%ld.%ld" ), v1, v2 );
2590 }
2591
2592 if( isSchematic )
2593 {
2594 // Long names/values are stored as 0x7F references into the trailing
2595 // free-text section, present in schematics as well as boards. Read it and
2596 // resolve every reference by its embedded pointer (order-independent).
2597 readNotes();
2599
2601 }
2602 else
2603 {
2604 DRC_CTX drc;
2605
2606 // The trailing notes and DRC sections are present only in v4/v5 boards;
2607 // missing sections are tolerated and fall back to defaults.
2608 readNotes();
2610 readDrc( drc );
2611
2612 postProcess( m_root.get(), drc );
2613 }
2614
2615 auto doc = std::make_unique<wxXmlDocument>();
2616 doc->SetRoot( toXml( m_root.get() ) );
2617
2618 m_buf = nullptr;
2619 return doc;
2620}
2621
2622
2624{
2625 EGB_NODE* drawing = aRoot->children.empty() ? nullptr : aRoot->children.front().get();
2626
2627 if( drawing == nullptr )
2628 THROW_IO_ERROR( _( "Eagle binary schematic has no drawing section." ) );
2629
2630 EGB_NODE* schematic = drawing->FindChildById( EGKW_SECT_SCHEMA );
2631
2632 if( schematic == nullptr )
2633 THROW_IO_ERROR( _( "Eagle binary file has no schematic section." ) );
2634
2635 // The schema section becomes the XML <schematic> element.
2636 schematic->name = wxS( "schematic" );
2637
2638 // The shared XML reader builds its layer-number map from drawing/layers, and the
2639 // schematic wire reader keys wire-vs-graphic on that map (layer 91 is Nets). Wrap the
2640 // decoded layer records the same way the board path does, or every net wire falls to
2641 // the LAYER_NOTES default and imports as a graphic line.
2642 EGB_NODE* layers = drawing->AddChild( EGKW_SECT_LAYERS, wxS( "layers" ) );
2643 postprocLayers( drawing, layers );
2644
2645 // Normalize geometry and text attributes shared with the board path before
2646 // the tree is restructured (these passes match on section id, not name).
2647 postprocLongText( aRoot );
2648 postprocWires( aRoot );
2649 postprocArcs( aRoot );
2650 postprocPolygons( aRoot );
2651 postprocCircles( aRoot );
2652 postprocFreeText( aRoot );
2653 postprocTextContent( aRoot );
2654 postprocRotation( aRoot );
2655 postprocRequiredAttrs( aRoot );
2656 postprocSchAttrs( aRoot );
2657 postprocPins( aRoot );
2658 postprocUnits( aRoot );
2659 renameSchSections( schematic );
2660
2661 std::vector<EGB_NODE*> libList = resolveSchLibraries( schematic );
2662 resegmentSchSheets( schematic, libList );
2663}
2664
2665
2666std::vector<EAGLE_BIN_PARSER::EGB_NODE*>
2668{
2669 std::vector<EGB_NODE*> out;
2670
2671 if( aParent != nullptr )
2672 {
2673 for( const auto& child : aParent->children )
2674 {
2675 if( child->id == aChildId )
2676 out.push_back( child.get() );
2677 }
2678 }
2679
2680 return out;
2681}
2682
2683
2684wxString EAGLE_BIN_PARSER::nameByOrdinal( const std::vector<EGB_NODE*>& aList, long aIdx )
2685{
2686 // The binary references symbols/devicesets/variants/gates by 1-based ordinal.
2687 if( aIdx >= 1 && aIdx <= (long) aList.size() )
2688 return aList[aIdx - 1]->Prop( wxS( "name" ) );
2689
2690 return wxString();
2691}
2692
2693
2695{
2696 // The shared XML reader marks <frame columns> #REQUIRED. Frames appear in boards,
2697 // schematics and library symbols alike, so rename the binary "cols" field on every
2698 // drawing kind rather than only the schematic path. The board path prunes its own
2699 // nameless attributes, so the <attribute> name backfill stays in postprocSchAttrs.
2700 aRoot->ForEach(
2701 [&]( EGB_NODE* aNode )
2702 {
2703 if( aNode->id == EGKW_SECT_FRAME && aNode->HasProp( wxS( "cols" ) ) )
2704 aNode->props[wxS( "columns" )] = aNode->Prop( wxS( "cols" ) );
2705 } );
2706}
2707
2708
2710{
2711 // Normalize per-element attributes to their XML names/values before the unit
2712 // conversion rewrites dimensional fields.
2713 aRoot->ForEach(
2714 [&]( EGB_NODE* aNode )
2715 {
2716 // A placed <attribute> carries its key in the text field; the reader
2717 // needs it as the required name attribute.
2718 if( aNode->id == EGKW_SECT_ATTRIBUTE && !aNode->HasProp( wxS( "name" ) ) )
2719 {
2720 aNode->props[wxS( "name" )] = aNode->HasProp( wxS( "textfield" ) )
2721 ? aNode->Prop( wxS( "textfield" ) )
2722 : aNode->content;
2723 }
2724
2725 switch( aNode->id )
2726 {
2727 case EGKW_SECT_TEXT:
2733 {
2734 // Eagle stores text height at half its real value; reuse the
2735 // overflow-safe doubling accessor the board path uses.
2736 wxString sizeKey;
2737
2738 if( aNode->HasProp( wxS( "half_size" ) ) )
2739 sizeKey = wxS( "half_size" );
2740 else if( aNode->HasProp( wxS( "size" ) ) )
2741 sizeKey = wxS( "size" );
2742
2743 if( !sizeKey.IsEmpty() && aNode->PropLong( sizeKey ) >= 0 )
2744 aNode->props[wxS( "size" )] = aNode->PropDoubled( sizeKey );
2745
2746 break;
2747 }
2748 default: break;
2749 }
2750 } );
2751}
2752
2753
2755{
2756 // Rename binary sections to their XML element names. The binary "device" record
2757 // (0x37) is the XML <deviceset>; its "variants" child is the XML <devices>.
2758 aSchematic->ForEach(
2759 [&]( EGB_NODE* aNode )
2760 {
2761 switch( aNode->id )
2762 {
2763 case EGKW_SECT_DEVICES: aNode->name = wxS( "devicesets" ); break;
2764 case EGKW_SECT_DEVICE: aNode->name = wxS( "deviceset" ); break;
2765 case EGKW_SECT_SCHEMASHEET: aNode->name = wxS( "sheet" ); break;
2766 case EGKW_SECT_SCHEMANET: aNode->name = wxS( "net" ); break;
2767 case EGKW_SECT_PACKAGEVARIANT: aNode->name = wxS( "device" ); break;
2768 // segment reader wants <label> not <netbuslabel> or it gets dropped
2769 case EGKW_SECT_NETBUSLABEL: aNode->name = wxS( "label" ); break;
2770 default:
2771 if( aNode->name == wxS( "variants" ) )
2772 aNode->name = wxS( "devices" );
2773
2774 break;
2775 }
2776 } );
2777}
2778
2779
2780std::vector<EAGLE_BIN_PARSER::EGB_NODE*>
2782{
2783 auto wrapChildren = []( EGB_NODE* aParent, const wxString& aContainer,
2784 const std::function<bool( const EGB_NODE* )>& aPred ) -> EGB_NODE*
2785 {
2786 std::vector<std::unique_ptr<EGB_NODE>> kept;
2787 std::vector<std::unique_ptr<EGB_NODE>> moved;
2788
2789 for( auto& child : aParent->children )
2790 {
2791 if( aPred( child.get() ) )
2792 moved.push_back( std::move( child ) );
2793 else
2794 kept.push_back( std::move( child ) );
2795 }
2796
2797 aParent->children = std::move( kept );
2798
2799 if( moved.empty() )
2800 return nullptr;
2801
2802 EGB_NODE* container = aParent->AddChild( 0, aContainer );
2803
2804 for( auto& node : moved )
2805 container->AdoptChild( std::move( node ) );
2806
2807 return container;
2808 };
2809
2810 // Hoist the libraries into their XML container. The binary stores a schema's
2811 // sheets, parts and nets as one flat stream where a schemasheet record delimits
2812 // a sheet rather than containing it, so they are re-segmented afterwards in
2813 // stream order.
2814 wrapChildren( aSchematic, wxS( "libraries" ),
2815 []( const EGB_NODE* n ) { return n->id == EGKW_SECT_LIBRARY; } );
2816
2817 EGB_NODE* libraries = aSchematic->FindChildByName( wxS( "libraries" ) );
2818
2819 std::vector<EGB_NODE*> libList = childrenById( libraries, EGKW_SECT_LIBRARY );
2820
2821 // Resolve library names and gate->symbol references.
2822 for( size_t li = 0; li < libList.size(); li++ )
2823 {
2824 EGB_NODE* lib = libList[li];
2825 EGB_NODE* devicesets = lib->FindChildById( EGKW_SECT_DEVICES );
2826 EGB_NODE* symbolsNode = lib->FindChildById( EGKW_SECT_SYMBOLS );
2827
2828 // The library name rides on the inner devices/symbols/packages node.
2829 wxString libName;
2830
2832 {
2833 if( EGB_NODE* n = lib->FindChildById( id ); n && !n->Prop( wxS( "library" ) ).IsEmpty() )
2834 {
2835 libName = n->Prop( wxS( "library" ) );
2836 break;
2837 }
2838 }
2839
2840 if( libName.IsEmpty() )
2841 libName = wxString::Format( wxS( "lib%zu" ), li + 1 );
2842
2843 lib->props[wxS( "name" )] = libName;
2844
2845 // Footprint packages are irrelevant to schematic import and only drag in
2846 // board-only required attributes (dx/dy on smd/pad). Drop them.
2847 lib->children.erase(
2848 std::remove_if( lib->children.begin(), lib->children.end(),
2849 []( const std::unique_ptr<EGB_NODE>& n )
2850 { return n->id == EGKW_SECT_PACKAGES; } ),
2851 lib->children.end() );
2852
2853 std::vector<EGB_NODE*> symbols = childrenById( symbolsNode, EGKW_SECT_SYMBOL );
2854 std::vector<EGB_NODE*> devicesetList = childrenById( devicesets, EGKW_SECT_DEVICE );
2855
2856 // Devicesets are keyed by name in the reader, so every name must be
2857 // unique and non-empty.
2858 std::map<wxString, int> dsSeen;
2859
2860 for( size_t di = 0; di < devicesetList.size(); di++ )
2861 {
2862 EGB_NODE* ds = devicesetList[di];
2863 wxString name = ds->Prop( wxS( "name" ) );
2864
2865 if( name.IsEmpty() )
2866 name = wxString::Format( wxS( "dset%zu" ), di + 1 );
2867
2868 if( int& count = dsSeen[name]; count++ > 0 )
2869 name = wxString::Format( wxS( "%s_%d" ), name, count );
2870
2871 ds->props[wxS( "name" )] = name;
2872
2873 EGB_NODE* gatesNode = ds->FindChildByName( wxS( "gates" ) );
2874
2875 for( EGB_NODE* gate : childrenById( gatesNode, EGKW_SECT_GATE ) )
2876 gate->props[wxS( "symbol" )] = nameByOrdinal( symbols, gate->PropLong( wxS( "symno" ) ) );
2877 }
2878 }
2879
2880 return libList;
2881}
2882
2883
2885 const std::vector<EGB_NODE*>& aLibList )
2886{
2887 auto adopt = []( EGB_NODE* aParent, EGB_NODE*& aSlot, const wxString& aName,
2888 std::unique_ptr<EGB_NODE> aNode )
2889 {
2890 if( aSlot == nullptr )
2891 aSlot = aParent->AddChild( 0, aName );
2892
2893 aSlot->AdoptChild( std::move( aNode ) );
2894 };
2895
2896 // Re-segment the flat, stream-ordered schema body into per-sheet structure.
2897 // The order is: <libraries>, then for each sheet a schemasheet header followed
2898 // by that sheet's parts (each owning its placed instances) and nets, then the
2899 // next sheet, and so on. A schemasheet therefore delimits the sheet that the
2900 // parts/nets that follow it belong to.
2901 std::vector<std::unique_ptr<EGB_NODE>> flat = std::move( aSchematic->children );
2902 aSchematic->children.clear();
2903
2904 std::vector<std::unique_ptr<EGB_NODE>> sheetNodes;
2905 std::vector<std::unique_ptr<EGB_NODE>> globalParts;
2906 std::map<wxString, bool> seenPart;
2907
2908 // Lazily-created containers for the sheet currently being assembled.
2909 EGB_NODE* curSheet = nullptr;
2910 EGB_NODE* curPlain = nullptr;
2911 EGB_NODE* curInstances = nullptr;
2912 EGB_NODE* curNets = nullptr;
2913 EGB_NODE* curBusses = nullptr;
2914
2915 for( auto& node : flat )
2916 {
2917 // Keep the libraries container at the head of <schematic>.
2918 if( node->name == wxS( "libraries" ) )
2919 {
2920 aSchematic->children.push_back( std::move( node ) );
2921 continue;
2922 }
2923
2924 switch( node->id )
2925 {
2927 {
2928 // Open a new sheet; its already-decoded drawables become <plain>.
2929 curSheet = node.get();
2930 curPlain = curInstances = curNets = curBusses = nullptr;
2931
2932 std::vector<std::unique_ptr<EGB_NODE>> drawables = std::move( curSheet->children );
2933 curSheet->children.clear();
2934
2935 for( auto& drawable : drawables )
2936 adopt( curSheet, curPlain, wxS( "plain" ), std::move( drawable ) );
2937
2938 sheetNodes.push_back( std::move( node ) );
2939 break;
2940 }
2941
2942 case EGKW_SECT_PART:
2943 {
2944 EGB_NODE* part = node.get();
2945 long libno = part->PropLong( wxS( "lib" ) );
2946 long devno = part->PropLong( wxS( "device" ) );
2947 long varno = part->PropLong( wxS( "variant" ) );
2948
2949 EGB_NODE* lib = ( libno >= 1 && libno <= (long) aLibList.size() ) ? aLibList[libno - 1]
2950 : nullptr;
2951 std::vector<EGB_NODE*> devicesets =
2952 childrenById( lib ? lib->FindChildById( EGKW_SECT_DEVICES ) : nullptr,
2954 EGB_NODE* ds = ( devno >= 1 && devno <= (long) devicesets.size() ) ? devicesets[devno - 1]
2955 : nullptr;
2956 std::vector<EGB_NODE*> variants =
2957 childrenById( ds ? ds->FindChildByName( wxS( "devices" ) ) : nullptr,
2959 std::vector<EGB_NODE*> gates =
2960 childrenById( ds ? ds->FindChildByName( wxS( "gates" ) ) : nullptr,
2962
2963 wxString partName = part->Prop( wxS( "name" ) );
2964
2965 part->props[wxS( "library" )] = lib ? lib->Prop( wxS( "name" ) ) : wxString();
2966 part->props[wxS( "deviceset" )] = ds ? ds->Prop( wxS( "name" ) ) : wxString();
2967 part->props[wxS( "device" )] = nameByOrdinal( variants, varno );
2968
2969 // The decoded "technology" is a raw ordinal, but the XML attribute is a
2970 // technology name (almost always empty) that the reader appends to the
2971 // symbol lookup key; leaving the ordinal there breaks symbol resolution.
2972 part->props.erase( wxS( "technology" ) );
2973
2974 // Peel the placed gate instances onto the current sheet, resolved.
2975 std::vector<std::unique_ptr<EGB_NODE>> partKept;
2976
2977 for( auto& sub : part->children )
2978 {
2979 if( sub->id != EGKW_SECT_INSTANCE || curSheet == nullptr )
2980 {
2981 partKept.push_back( std::move( sub ) );
2982 continue;
2983 }
2984
2985 sub->props[wxS( "part" )] = partName;
2986 sub->props[wxS( "gate" )] = nameByOrdinal( gates, sub->PropLong( wxS( "gateno" ) ) );
2987 adopt( curSheet, curInstances, wxS( "instances" ), std::move( sub ) );
2988 }
2989
2990 part->children = std::move( partKept );
2991
2992 // One global <part> per unique name.
2993 if( !seenPart[partName] )
2994 {
2995 seenPart[partName] = true;
2996 node->parent = aSchematic;
2997 globalParts.push_back( std::move( node ) );
2998 }
2999
3000 break;
3001 }
3002
3004 {
3005 node->props[wxS( "class" )] =
3006 node->HasProp( wxS( "netclass" ) ) ? node->Prop( wxS( "netclass" ) ) : wxString( wxS( "0" ) );
3007
3008 for( EGB_NODE* seg : childrenById( node.get(), EGKW_SECT_PATH ) )
3009 seg->name = wxS( "segment" );
3010
3011 if( curSheet != nullptr )
3012 adopt( curSheet, curNets, wxS( "nets" ), std::move( node ) );
3013
3014 break;
3015 }
3016
3018 {
3019 if( curSheet != nullptr )
3020 adopt( curSheet, curBusses, wxS( "busses" ), std::move( node ) );
3021
3022 break;
3023 }
3024
3025 default:
3026 {
3027 // Free graphics that follow the sheet header.
3028 if( curSheet != nullptr )
3029 adopt( curSheet, curPlain, wxS( "plain" ), std::move( node ) );
3030
3031 break;
3032 }
3033 }
3034 }
3035
3036 flat.clear();
3037
3038 EGB_NODE* sheetsNode = aSchematic->AddChild( 0, wxS( "sheets" ) );
3039
3040 for( auto& sheet : sheetNodes )
3041 sheetsNode->AdoptChild( std::move( sheet ) );
3042
3043 EGB_NODE* partsNode = aSchematic->AddChild( 0, wxS( "parts" ) );
3044
3045 for( auto& part : globalParts )
3046 partsNode->AdoptChild( std::move( part ) );
3047}
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 without changing the stream position.
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)
static KIGFX::CONSTRUCTION_GEOM::DRAWABLE drawable(const GRAPHIC_EDIT_GEOMETRY &aGeometry)
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:411
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.