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