KiCad PCB EDA Suite
Loading...
Searching...
No Matches
autotrax_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 * Format interpretation derived from pcb-rnd src_plugins/io_autotrax:
5 * Copyright (C) 2016, 2017, 2018, 2020 Tibor 'Igor2' Palinkas
6 * Copyright (C) 2016, 2017 Erich S. Heinzle
7 * Used under GPL v2-or-later.
8 *
9 * Copyright (C) 2026 KiCad Developers, see AUTHORS.txt for contributors.
10 *
11 * This program is free software; you can redistribute it and/or
12 * modify it under the terms of the GNU General Public License
13 * as published by the Free Software Foundation; either version 2
14 * of the License, or (at your option) any later version.
15 *
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
20 *
21 * You should have received a copy of the GNU General Public License
22 * along with this program. If not, see <https://www.gnu.org/licenses/>.
23 */
24
25#include "autotrax_parser.h"
26
27#include <reporter.h>
28#include <wx/tokenzr.h>
29#include <wx/translation.h>
30
31using namespace AUTOTRAX;
32
33
37static double toDouble( const wxString& aToken )
38{
39 double value = 0.0;
40 aToken.ToCDouble( &value );
41 return value;
42}
43
44
46static int toInt( const wxString& aToken )
47{
48 long value = 0;
49 aToken.ToCLong( &value );
50 return static_cast<int>( value );
51}
52
53
54bool AUTOTRAX_PARSER::Sniff( const wxString& aContents )
55{
56 // Empty lines are retained so a blank or comment preamble does not hide the
57 // magic header on the first line of data.
58 for( wxString line : wxStringTokenize( aContents, wxT( "\n" ), wxTOKEN_RET_EMPTY ) )
59 {
60 line.Trim( true ).Trim( false );
61
62 if( line.IsEmpty() || line[0] == '#' )
63 continue;
64
65 return line.StartsWith( wxT( "PCB FILE 4" ) ) || line.StartsWith( wxT( "PCB FILE 5" ) );
66 }
67
68 return false;
69}
70
71
72void AUTOTRAX_PARSER::warn( const wxString& aMsg ) const
73{
74 if( m_reporter )
75 m_reporter->Report( wxString::Format( _( "Autotrax import, line %d: %s" ), m_lineNo, aMsg ),
77}
78
79
80bool AUTOTRAX_PARSER::nextLine( wxString& aLine )
81{
82 if( m_index >= m_lines.GetCount() )
83 return false;
84
85 // Every keyword, label and free-text field is surrounded by optional
86 // whitespace (including the trailing CR of DOS line endings), so trimming
87 // here lets the rest of the parser compare and tokenize clean lines.
88 aLine = m_lines[m_index++];
89 aLine.Trim( true ).Trim( false );
90 m_lineNo = static_cast<int>( m_index );
91 return true;
92}
93
94
95wxArrayString AUTOTRAX_PARSER::tokenize( const wxString& aLine )
96{
97 return wxStringTokenize( aLine, wxT( " \t\r\n" ), wxTOKEN_STRTOK );
98}
99
100
102{
103 wxString line;
104
105 if( !nextLine( line ) )
106 return false;
107
108 wxArrayString tok = tokenize( line );
109
110 if( tok.GetCount() < 6 )
111 {
112 warn( _( "insufficient track fields" ) );
113 return false;
114 }
115
116 aOut.x1 = toDouble( tok[0] );
117 aOut.y1 = toDouble( tok[1] );
118 aOut.x2 = toDouble( tok[2] );
119 aOut.y2 = toDouble( tok[3] );
120 aOut.width = toDouble( tok[4] );
121 aOut.layer = toInt( tok[5] );
122 return true;
123}
124
125
127{
128 wxString line;
129
130 if( !nextLine( line ) )
131 return false;
132
133 wxArrayString tok = tokenize( line );
134
135 if( tok.GetCount() < 6 )
136 {
137 warn( _( "insufficient arc fields" ) );
138 return false;
139 }
140
141 aOut.centerX = toDouble( tok[0] );
142 aOut.centerY = toDouble( tok[1] );
143 aOut.radius = toDouble( tok[2] );
144 aOut.segments = toInt( tok[3] );
145 aOut.width = toDouble( tok[4] );
146 aOut.layer = toInt( tok[5] );
147 return true;
148}
149
150
152{
153 wxString line;
154
155 if( !nextLine( line ) )
156 return false;
157
158 wxArrayString tok = tokenize( line );
159
160 if( tok.GetCount() < 4 )
161 {
162 warn( _( "insufficient via fields" ) );
163 return false;
164 }
165
166 aOut.x = toDouble( tok[0] );
167 aOut.y = toDouble( tok[1] );
168 aOut.diameter = toDouble( tok[2] );
169 aOut.drill = toDouble( tok[3] );
170 return true;
171}
172
173
175{
176 wxString line;
177
178 if( !nextLine( line ) )
179 return false;
180
181 wxArrayString tok = tokenize( line );
182
183 // The attribute line carries eight fields; the pad name is on the next line
184 // and is always consumed so the record is left fully read on error too.
185 if( tok.GetCount() < 8 )
186 {
187 warn( _( "insufficient pad fields" ) );
188 nextLine( line );
189 return false;
190 }
191
192 aOut.x = toDouble( tok[0] );
193 aOut.y = toDouble( tok[1] );
194 aOut.xSize = toDouble( tok[2] );
195 aOut.ySize = toDouble( tok[3] );
196 aOut.shape = toInt( tok[4] );
197 aOut.drill = toDouble( tok[5] );
198 aOut.planeFlags = toInt( tok[6] );
199 aOut.layer = toInt( tok[7] );
200
201 if( nextLine( line ) )
202 aOut.name = line;
203
204 return true;
205}
206
207
209{
210 wxString line;
211
212 if( !nextLine( line ) )
213 return false;
214
215 wxArrayString tok = tokenize( line );
216
217 if( tok.GetCount() < 5 )
218 {
219 warn( _( "insufficient fill fields" ) );
220 return false;
221 }
222
223 aOut.x1 = toDouble( tok[0] );
224 aOut.y1 = toDouble( tok[1] );
225 aOut.x2 = toDouble( tok[2] );
226 aOut.y2 = toDouble( tok[3] );
227 aOut.layer = toInt( tok[4] );
228 return true;
229}
230
231
233{
234 wxString line;
235
236 if( !nextLine( line ) )
237 return false;
238
239 if( !parseTextData( line, aOut ) )
240 return false;
241
242 // The text string is on the following line; an empty line yields empty text.
243 if( nextLine( line ) )
244 aOut.text = line;
245
246 return true;
247}
248
249
250bool AUTOTRAX_PARSER::parseTextData( const wxString& aLine, TEXT& aOut ) const
251{
252 wxArrayString tok = tokenize( aLine );
253
254 if( tok.GetCount() < 6 )
255 {
256 warn( _( "insufficient text fields" ) );
257 return false;
258 }
259
260 aOut.x = toDouble( tok[0] );
261 aOut.y = toDouble( tok[1] );
262 aOut.height = toDouble( tok[2] );
263
264 // Rotation codes 16..19 are the mirrored forms of 0..3
265 int rotation = toInt( tok[3] );
266 aOut.direction = rotation & 3;
267 aOut.mirrored = ( rotation & 16 ) != 0;
268
269 aOut.width = toDouble( tok[4] );
270 aOut.layer = toInt( tok[5] );
271 return true;
272}
273
274
276{
277 wxString line;
278
279 // Three fixed string lines: refdes, footprint name, value.
280 if( nextLine( line ) )
281 aOut.refdes = line;
282
283 if( nextLine( line ) )
284 aOut.name = line;
285
286 if( nextLine( line ) )
287 aOut.value = line;
288
289 // The comment (value) placement precedes the designator placement
290 TEXT label;
291
292 if( nextLine( line ) && parseTextData( line, label ) )
293 aOut.valueText = label;
294
295 if( nextLine( line ) && parseTextData( line, label ) )
296 aOut.refdesText = label;
297
298 // Component origin, then designator and comment status where 2 hides the label
299 if( nextLine( line ) )
300 {
301 wxArrayString tok = tokenize( line );
302
303 if( tok.GetCount() >= 2 )
304 {
305 aOut.x = toDouble( tok[0] );
306 aOut.y = toDouble( tok[1] );
307
308 if( tok.GetCount() >= 4 )
309 {
310 aOut.refdesVisible = toInt( tok[2] ) != 2;
311 aOut.valueVisible = toInt( tok[3] ) != 2;
312 }
313 }
314 else
315 {
316 warn( _( "insufficient component fields" ) );
317 }
318 }
319
320 while( nextLine( line ) )
321 {
322 wxString kw = line;
323
324 if( kw.StartsWith( wxT( "ENDCOMP" ) ) )
325 break;
326
327 if( kw == wxT( "CT" ) )
328 {
329 TRACK t;
330
331 if( parseTrack( t ) )
332 aOut.tracks.push_back( t );
333 }
334 else if( kw == wxT( "CA" ) )
335 {
336 ARC a;
337
338 if( parseArc( a ) )
339 aOut.arcs.push_back( a );
340 }
341 else if( kw == wxT( "CV" ) )
342 {
343 VIA v;
344
345 if( parseVia( v ) )
346 aOut.vias.push_back( v );
347 }
348 else if( kw == wxT( "CF" ) )
349 {
350 FILL f;
351
352 if( parseFill( f ) )
353 aOut.fills.push_back( f );
354 }
355 else if( kw == wxT( "CP" ) )
356 {
357 PAD p;
358
359 if( parsePad( p ) )
360 aOut.pads.push_back( p );
361 }
362 else if( kw == wxT( "CS" ) )
363 {
364 TEXT s;
365
366 if( parseText( s ) )
367 aOut.texts.push_back( s );
368 }
369 }
370}
371
372
374{
375 // A NETDEF section names a net on the line following the NETDEF keyword, then
376 // describes it with optional component blocks ([..]) and a node list ((..)).
377 // The importer collects the "refdes-pad" node tokens inside (..) and the node
378 // table ({..}) is skipped. Component metadata is already carried by COMP.
379 wxString line;
380
381 if( !nextLine( line ) )
382 return;
383
384 wxString netName = line;
385
386 bool inNet = false;
387 bool inNodeTable = false;
388
389 while( nextLine( line ) )
390 {
391 wxString s = line;
392
393 if( s.StartsWith( wxT( "ENDPCB" ) ) )
394 {
395 m_index--; // let the top-level loop see ENDPCB
396 return;
397 }
398
399 if( s.IsEmpty() )
400 continue;
401
402 if( s[0] == '[' )
403 {
404 // Skip the component block up to its closing ']'.
405 while( nextLine( line ) )
406 {
407 if( line == wxT( "]" ) )
408 break;
409 }
410 }
411 else if( s[0] == '(' )
412 {
413 inNet = true;
414 }
415 else if( s[0] == ')' )
416 {
417 return; // a NETDEF describes a single net; done at its closing ')'
418 }
419 else if( s[0] == '{' )
420 {
421 inNodeTable = true;
422 }
423 else if( s[0] == '}' )
424 {
425 inNodeTable = false;
426 }
427 else if( inNet && !inNodeTable && !netName.IsEmpty() )
428 {
429 m_board->netNodes.push_back( { netName, s } );
430 }
431 }
432}
433
434
435bool AUTOTRAX_PARSER::Parse( const wxString& aContents, BOARD_DATA& aBoard )
436{
437 m_board = &aBoard;
438
439 // Empty lines are retained (wxTOKEN_RET_EMPTY) because COMP header parsing
440 // counts the refdes/name/value/placement lines positionally.
441 m_lines = wxStringTokenize( aContents, wxT( "\n" ), wxTOKEN_RET_EMPTY );
442 m_index = 0;
443 m_lineNo = 0;
444
445 bool haveHeader = false;
446 wxString line;
447
448 while( nextLine( line ) )
449 {
450 wxString kw = line;
451
452 if( kw.IsEmpty() )
453 continue;
454
455 if( kw.StartsWith( wxT( "PCB FILE 4" ) ) )
456 {
457 aBoard.version = 4;
458 haveHeader = true;
459 }
460 else if( kw.StartsWith( wxT( "PCB FILE 5" ) ) )
461 {
462 aBoard.version = 5;
463 haveHeader = true;
464 }
465 else if( kw.StartsWith( wxT( "ENDPCB" ) ) )
466 {
467 break;
468 }
469 else if( kw.StartsWith( wxT( "NETDEF" ) ) )
470 {
471 parseNetDef();
472 }
473 else if( kw.StartsWith( wxT( "COMP" ) ) )
474 {
477 aBoard.components.push_back( std::move( comp ) );
478 }
479 else if( kw == wxT( "FT" ) )
480 {
481 TRACK t;
482
483 if( parseTrack( t ) )
484 aBoard.tracks.push_back( t );
485 }
486 else if( kw == wxT( "FA" ) )
487 {
488 ARC a;
489
490 if( parseArc( a ) )
491 aBoard.arcs.push_back( a );
492 }
493 else if( kw == wxT( "FV" ) )
494 {
495 VIA v;
496
497 if( parseVia( v ) )
498 aBoard.vias.push_back( v );
499 }
500 else if( kw == wxT( "FF" ) )
501 {
502 FILL f;
503
504 if( parseFill( f ) )
505 aBoard.fills.push_back( f );
506 }
507 else if( kw == wxT( "FP" ) )
508 {
509 PAD p;
510
511 if( parsePad( p ) )
512 aBoard.pads.push_back( p );
513 }
514 else if( kw == wxT( "FS" ) )
515 {
516 TEXT s;
517
518 if( parseText( s ) )
519 aBoard.texts.push_back( s );
520 }
521 }
522
523 return haveHeader;
524}
static int toInt(const wxString &aToken)
Parse a token as an integer using the C locale, returning 0 on failure.
static double toDouble(const wxString &aToken)
Parse a token as a double using the C locale, returning 0 on failure so a malformed field degrades to...
bool parseVia(AUTOTRAX::VIA &aOut)
wxArrayString m_lines
AUTOTRAX::BOARD_DATA * m_board
static bool Sniff(const wxString &aContents)
Cheap content sniff: the first non-blank, non-comment line is the magic header "PCB FILE 4" (Autotrax...
bool Parse(const wxString &aContents, AUTOTRAX::BOARD_DATA &aBoard)
Parse aContents into aBoard.
void parseComponent(AUTOTRAX::COMPONENT &aOut)
bool parseArc(AUTOTRAX::ARC &aOut)
bool parseTrack(AUTOTRAX::TRACK &aOut)
void warn(const wxString &aMsg) const
bool parseText(AUTOTRAX::TEXT &aOut)
bool parseTextData(const wxString &aLine, AUTOTRAX::TEXT &aOut) const
Parse the numeric line of a string record, which COMP headers reuse for label placement.
REPORTER * m_reporter
bool parseFill(AUTOTRAX::FILL &aOut)
bool nextLine(wxString &aLine)
Return the next line trimmed of surrounding whitespace, or false at end of input.
static wxArrayString tokenize(const wxString &aLine)
Tokenize a whitespace-separated data line into C-locale-parseable tokens.
bool parsePad(AUTOTRAX::PAD &aOut)
#define _(s)
@ RPT_SEVERITY_WARNING
Free or component arc (FA / CA).
int segments
quadrant bitmask; 15 = full circle
Everything parsed out of an Autotrax/Easytrax file, before any KiCad object is created.
std::vector< COMPONENT > components
std::vector< VIA > vias
std::vector< PAD > pads
std::vector< TRACK > tracks
int version
4 = Autotrax, 5 = Easytrax
std::vector< TEXT > texts
std::vector< ARC > arcs
std::vector< FILL > fills
A placed component (COMP .. ENDCOMP) holding its own primitives.
std::vector< ARC > arcs
std::optional< TEXT > valueText
comment placement, text unused
std::vector< TRACK > tracks
std::vector< VIA > vias
std::vector< PAD > pads
std::optional< TEXT > refdesText
designator placement, text unused
std::vector< FILL > fills
std::vector< TEXT > texts
Free or component rectangular fill (FF / CF), Autotrax's only pour.
Free or component pad/pin (FP / CP).
int shape
1 round, 2 rect, 3 octagon, 4 round-rect
Free or component string (FS / CS).
int direction
0..3, multiplied by 90 degrees counterclockwise
Free or component track segment (FT / CT). All coordinates are in mils.
Free or component via (FV / CV).
KIBIS_COMPONENT * comp