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, you may find one here:
23 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
24 * or you may search the http://www.gnu.org website for the version 2 license,
25 * or you may write to the Free Software Foundation, Inc.,
26 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
27 */
28
29#include "autotrax_parser.h"
30
31#include <reporter.h>
32#include <wx/tokenzr.h>
33#include <wx/translation.h>
34
35using namespace AUTOTRAX;
36
37
41static double toDouble( const wxString& aToken )
42{
43 double value = 0.0;
44 aToken.ToCDouble( &value );
45 return value;
46}
47
48
50static int toInt( const wxString& aToken )
51{
52 long value = 0;
53 aToken.ToCLong( &value );
54 return static_cast<int>( value );
55}
56
57
58bool AUTOTRAX_PARSER::Sniff( const wxString& aContents )
59{
60 // Empty lines are retained so a blank or comment preamble does not hide the
61 // magic header on the first line of data.
62 for( wxString line : wxStringTokenize( aContents, wxT( "\n" ), wxTOKEN_RET_EMPTY ) )
63 {
64 line.Trim( true ).Trim( false );
65
66 if( line.IsEmpty() || line[0] == '#' )
67 continue;
68
69 return line.StartsWith( wxT( "PCB FILE 4" ) ) || line.StartsWith( wxT( "PCB FILE 5" ) );
70 }
71
72 return false;
73}
74
75
76void AUTOTRAX_PARSER::warn( const wxString& aMsg ) const
77{
78 if( m_reporter )
79 m_reporter->Report( wxString::Format( _( "Autotrax import, line %d: %s" ), m_lineNo, aMsg ),
81}
82
83
84bool AUTOTRAX_PARSER::nextLine( wxString& aLine )
85{
86 if( m_index >= m_lines.GetCount() )
87 return false;
88
89 // Every keyword, label and free-text field is surrounded by optional
90 // whitespace (including the trailing CR of DOS line endings), so trimming
91 // here lets the rest of the parser compare and tokenize clean lines.
92 aLine = m_lines[m_index++];
93 aLine.Trim( true ).Trim( false );
94 m_lineNo = static_cast<int>( m_index );
95 return true;
96}
97
98
99wxArrayString AUTOTRAX_PARSER::tokenize( const wxString& aLine )
100{
101 return wxStringTokenize( aLine, wxT( " \t\r\n" ), wxTOKEN_STRTOK );
102}
103
104
106{
107 wxString line;
108
109 if( !nextLine( line ) )
110 return false;
111
112 wxArrayString tok = tokenize( line );
113
114 if( tok.GetCount() < 6 )
115 {
116 warn( _( "insufficient track fields" ) );
117 return false;
118 }
119
120 aOut.x1 = toDouble( tok[0] );
121 aOut.y1 = toDouble( tok[1] );
122 aOut.x2 = toDouble( tok[2] );
123 aOut.y2 = toDouble( tok[3] );
124 aOut.width = toDouble( tok[4] );
125 aOut.layer = toInt( tok[5] );
126 return true;
127}
128
129
131{
132 wxString line;
133
134 if( !nextLine( line ) )
135 return false;
136
137 wxArrayString tok = tokenize( line );
138
139 if( tok.GetCount() < 6 )
140 {
141 warn( _( "insufficient arc fields" ) );
142 return false;
143 }
144
145 aOut.centerX = toDouble( tok[0] );
146 aOut.centerY = toDouble( tok[1] );
147 aOut.radius = toDouble( tok[2] );
148 aOut.segments = toInt( tok[3] );
149 aOut.width = toDouble( tok[4] );
150 aOut.layer = toInt( tok[5] );
151 return true;
152}
153
154
156{
157 wxString line;
158
159 if( !nextLine( line ) )
160 return false;
161
162 wxArrayString tok = tokenize( line );
163
164 if( tok.GetCount() < 4 )
165 {
166 warn( _( "insufficient via fields" ) );
167 return false;
168 }
169
170 aOut.x = toDouble( tok[0] );
171 aOut.y = toDouble( tok[1] );
172 aOut.diameter = toDouble( tok[2] );
173 aOut.drill = toDouble( tok[3] );
174 return true;
175}
176
177
179{
180 wxString line;
181
182 if( !nextLine( line ) )
183 return false;
184
185 wxArrayString tok = tokenize( line );
186
187 // The attribute line carries eight fields; the pad name is on the next line
188 // and is always consumed so the record is left fully read on error too.
189 if( tok.GetCount() < 8 )
190 {
191 warn( _( "insufficient pad fields" ) );
192 nextLine( line );
193 return false;
194 }
195
196 aOut.x = toDouble( tok[0] );
197 aOut.y = toDouble( tok[1] );
198 aOut.xSize = toDouble( tok[2] );
199 aOut.ySize = toDouble( tok[3] );
200 aOut.shape = toInt( tok[4] );
201 aOut.drill = toDouble( tok[5] );
202 aOut.planeFlags = toInt( tok[6] );
203 aOut.layer = toInt( tok[7] );
204
205 if( nextLine( line ) )
206 aOut.name = line;
207
208 return true;
209}
210
211
213{
214 wxString line;
215
216 if( !nextLine( line ) )
217 return false;
218
219 wxArrayString tok = tokenize( line );
220
221 if( tok.GetCount() < 5 )
222 {
223 warn( _( "insufficient fill fields" ) );
224 return false;
225 }
226
227 aOut.x1 = toDouble( tok[0] );
228 aOut.y1 = toDouble( tok[1] );
229 aOut.x2 = toDouble( tok[2] );
230 aOut.y2 = toDouble( tok[3] );
231 aOut.layer = toInt( tok[4] );
232 return true;
233}
234
235
237{
238 wxString line;
239
240 if( !nextLine( line ) )
241 return false;
242
243 wxArrayString tok = tokenize( line );
244
245 if( tok.GetCount() < 6 )
246 {
247 warn( _( "insufficient text fields" ) );
248 return false;
249 }
250
251 aOut.x = toDouble( tok[0] );
252 aOut.y = toDouble( tok[1] );
253 aOut.height = toDouble( tok[2] );
254 aOut.direction = toInt( tok[3] ) % 4; // mirroring bit ignored
255 aOut.width = toDouble( tok[4] );
256 aOut.layer = toInt( tok[5] );
257
258 // The text string is on the following line; an empty line yields empty text.
259 if( nextLine( line ) )
260 aOut.text = line;
261
262 return true;
263}
264
265
267{
268 wxString line;
269
270 // Three fixed string lines: refdes, footprint name, value.
271 if( nextLine( line ) )
272 aOut.refdes = line;
273
274 if( nextLine( line ) )
275 aOut.name = line;
276
277 if( nextLine( line ) )
278 aOut.value = line;
279
280 // Two text-placement lines (refdes/value label positions) are ignored.
281 nextLine( line );
282 nextLine( line );
283
284 // Component origin.
285 if( nextLine( line ) )
286 {
287 wxArrayString tok = tokenize( line );
288
289 if( tok.GetCount() >= 2 )
290 {
291 aOut.x = toDouble( tok[0] );
292 aOut.y = toDouble( tok[1] );
293 }
294 else
295 {
296 warn( _( "insufficient component fields" ) );
297 }
298 }
299
300 while( nextLine( line ) )
301 {
302 wxString kw = line;
303
304 if( kw.StartsWith( wxT( "ENDCOMP" ) ) )
305 break;
306
307 if( kw == wxT( "CT" ) )
308 {
309 TRACK t;
310
311 if( parseTrack( t ) )
312 aOut.tracks.push_back( t );
313 }
314 else if( kw == wxT( "CA" ) )
315 {
316 ARC a;
317
318 if( parseArc( a ) )
319 aOut.arcs.push_back( a );
320 }
321 else if( kw == wxT( "CV" ) )
322 {
323 VIA v;
324
325 if( parseVia( v ) )
326 aOut.vias.push_back( v );
327 }
328 else if( kw == wxT( "CF" ) )
329 {
330 FILL f;
331
332 if( parseFill( f ) )
333 aOut.fills.push_back( f );
334 }
335 else if( kw == wxT( "CP" ) )
336 {
337 PAD p;
338
339 if( parsePad( p ) )
340 aOut.pads.push_back( p );
341 }
342 else if( kw == wxT( "CS" ) )
343 {
344 TEXT s;
345
346 if( parseText( s ) )
347 aOut.texts.push_back( s );
348 }
349 }
350}
351
352
354{
355 // A NETDEF section names a net on the line following the NETDEF keyword, then
356 // describes it with optional component blocks ([..]) and a node list ((..)).
357 // The importer collects the "refdes-pad" node tokens inside (..) and the node
358 // table ({..}) is skipped. Component metadata is already carried by COMP.
359 wxString line;
360
361 if( !nextLine( line ) )
362 return;
363
364 wxString netName = line;
365
366 bool inNet = false;
367 bool inNodeTable = false;
368
369 while( nextLine( line ) )
370 {
371 wxString s = line;
372
373 if( s.StartsWith( wxT( "ENDPCB" ) ) )
374 {
375 m_index--; // let the top-level loop see ENDPCB
376 return;
377 }
378
379 if( s.IsEmpty() )
380 continue;
381
382 if( s[0] == '[' )
383 {
384 // Skip the component block up to its closing ']'.
385 while( nextLine( line ) )
386 {
387 if( line == wxT( "]" ) )
388 break;
389 }
390 }
391 else if( s[0] == '(' )
392 {
393 inNet = true;
394 }
395 else if( s[0] == ')' )
396 {
397 return; // a NETDEF describes a single net; done at its closing ')'
398 }
399 else if( s[0] == '{' )
400 {
401 inNodeTable = true;
402 }
403 else if( s[0] == '}' )
404 {
405 inNodeTable = false;
406 }
407 else if( inNet && !inNodeTable && !netName.IsEmpty() )
408 {
409 m_board->netNodes.push_back( { netName, s } );
410 }
411 }
412}
413
414
415bool AUTOTRAX_PARSER::Parse( const wxString& aContents, BOARD_DATA& aBoard )
416{
417 m_board = &aBoard;
418
419 // Empty lines are retained (wxTOKEN_RET_EMPTY) because COMP header parsing
420 // counts the refdes/name/value/placement lines positionally.
421 m_lines = wxStringTokenize( aContents, wxT( "\n" ), wxTOKEN_RET_EMPTY );
422 m_index = 0;
423 m_lineNo = 0;
424
425 bool haveHeader = false;
426 wxString line;
427
428 while( nextLine( line ) )
429 {
430 wxString kw = line;
431
432 if( kw.IsEmpty() )
433 continue;
434
435 if( kw.StartsWith( wxT( "PCB FILE 4" ) ) )
436 {
437 aBoard.version = 4;
438 haveHeader = true;
439 }
440 else if( kw.StartsWith( wxT( "PCB FILE 5" ) ) )
441 {
442 aBoard.version = 5;
443 haveHeader = true;
444 }
445 else if( kw.StartsWith( wxT( "ENDPCB" ) ) )
446 {
447 break;
448 }
449 else if( kw.StartsWith( wxT( "NETDEF" ) ) )
450 {
451 parseNetDef();
452 }
453 else if( kw.StartsWith( wxT( "COMP" ) ) )
454 {
457 aBoard.components.push_back( std::move( comp ) );
458 }
459 else if( kw == wxT( "FT" ) )
460 {
461 TRACK t;
462
463 if( parseTrack( t ) )
464 aBoard.tracks.push_back( t );
465 }
466 else if( kw == wxT( "FA" ) )
467 {
468 ARC a;
469
470 if( parseArc( a ) )
471 aBoard.arcs.push_back( a );
472 }
473 else if( kw == wxT( "FV" ) )
474 {
475 VIA v;
476
477 if( parseVia( v ) )
478 aBoard.vias.push_back( v );
479 }
480 else if( kw == wxT( "FF" ) )
481 {
482 FILL f;
483
484 if( parseFill( f ) )
485 aBoard.fills.push_back( f );
486 }
487 else if( kw == wxT( "FP" ) )
488 {
489 PAD p;
490
491 if( parsePad( p ) )
492 aBoard.pads.push_back( p );
493 }
494 else if( kw == wxT( "FS" ) )
495 {
496 TEXT s;
497
498 if( parseText( s ) )
499 aBoard.texts.push_back( s );
500 }
501 }
502
503 return haveHeader;
504}
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)
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::vector< TRACK > tracks
std::vector< VIA > vias
std::vector< PAD > pads
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
Free or component track segment (FT / CT). All coordinates are in mils.
Free or component via (FV / CV).
KIBIS_COMPONENT * comp