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