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 wxArrayString tok = tokenize( line );
240
241 if( tok.GetCount() < 6 )
242 {
243 warn( _( "insufficient text fields" ) );
244 return false;
245 }
246
247 aOut.x = toDouble( tok[0] );
248 aOut.y = toDouble( tok[1] );
249 aOut.height = toDouble( tok[2] );
250 aOut.direction = toInt( tok[3] ) % 4; // mirroring bit ignored
251 aOut.width = toDouble( tok[4] );
252 aOut.layer = toInt( tok[5] );
253
254 // The text string is on the following line; an empty line yields empty text.
255 if( nextLine( line ) )
256 aOut.text = line;
257
258 return true;
259}
260
261
263{
264 wxString line;
265
266 // Three fixed string lines: refdes, footprint name, value.
267 if( nextLine( line ) )
268 aOut.refdes = line;
269
270 if( nextLine( line ) )
271 aOut.name = line;
272
273 if( nextLine( line ) )
274 aOut.value = line;
275
276 // Two text-placement lines (refdes/value label positions) are ignored.
277 nextLine( line );
278 nextLine( line );
279
280 // Component origin.
281 if( nextLine( line ) )
282 {
283 wxArrayString tok = tokenize( line );
284
285 if( tok.GetCount() >= 2 )
286 {
287 aOut.x = toDouble( tok[0] );
288 aOut.y = toDouble( tok[1] );
289 }
290 else
291 {
292 warn( _( "insufficient component fields" ) );
293 }
294 }
295
296 while( nextLine( line ) )
297 {
298 wxString kw = line;
299
300 if( kw.StartsWith( wxT( "ENDCOMP" ) ) )
301 break;
302
303 if( kw == wxT( "CT" ) )
304 {
305 TRACK t;
306
307 if( parseTrack( t ) )
308 aOut.tracks.push_back( t );
309 }
310 else if( kw == wxT( "CA" ) )
311 {
312 ARC a;
313
314 if( parseArc( a ) )
315 aOut.arcs.push_back( a );
316 }
317 else if( kw == wxT( "CV" ) )
318 {
319 VIA v;
320
321 if( parseVia( v ) )
322 aOut.vias.push_back( v );
323 }
324 else if( kw == wxT( "CF" ) )
325 {
326 FILL f;
327
328 if( parseFill( f ) )
329 aOut.fills.push_back( f );
330 }
331 else if( kw == wxT( "CP" ) )
332 {
333 PAD p;
334
335 if( parsePad( p ) )
336 aOut.pads.push_back( p );
337 }
338 else if( kw == wxT( "CS" ) )
339 {
340 TEXT s;
341
342 if( parseText( s ) )
343 aOut.texts.push_back( s );
344 }
345 }
346}
347
348
350{
351 // A NETDEF section names a net on the line following the NETDEF keyword, then
352 // describes it with optional component blocks ([..]) and a node list ((..)).
353 // The importer collects the "refdes-pad" node tokens inside (..) and the node
354 // table ({..}) is skipped. Component metadata is already carried by COMP.
355 wxString line;
356
357 if( !nextLine( line ) )
358 return;
359
360 wxString netName = line;
361
362 bool inNet = false;
363 bool inNodeTable = false;
364
365 while( nextLine( line ) )
366 {
367 wxString s = line;
368
369 if( s.StartsWith( wxT( "ENDPCB" ) ) )
370 {
371 m_index--; // let the top-level loop see ENDPCB
372 return;
373 }
374
375 if( s.IsEmpty() )
376 continue;
377
378 if( s[0] == '[' )
379 {
380 // Skip the component block up to its closing ']'.
381 while( nextLine( line ) )
382 {
383 if( line == wxT( "]" ) )
384 break;
385 }
386 }
387 else if( s[0] == '(' )
388 {
389 inNet = true;
390 }
391 else if( s[0] == ')' )
392 {
393 return; // a NETDEF describes a single net; done at its closing ')'
394 }
395 else if( s[0] == '{' )
396 {
397 inNodeTable = true;
398 }
399 else if( s[0] == '}' )
400 {
401 inNodeTable = false;
402 }
403 else if( inNet && !inNodeTable && !netName.IsEmpty() )
404 {
405 m_board->netNodes.push_back( { netName, s } );
406 }
407 }
408}
409
410
411bool AUTOTRAX_PARSER::Parse( const wxString& aContents, BOARD_DATA& aBoard )
412{
413 m_board = &aBoard;
414
415 // Empty lines are retained (wxTOKEN_RET_EMPTY) because COMP header parsing
416 // counts the refdes/name/value/placement lines positionally.
417 m_lines = wxStringTokenize( aContents, wxT( "\n" ), wxTOKEN_RET_EMPTY );
418 m_index = 0;
419 m_lineNo = 0;
420
421 bool haveHeader = false;
422 wxString line;
423
424 while( nextLine( line ) )
425 {
426 wxString kw = line;
427
428 if( kw.IsEmpty() )
429 continue;
430
431 if( kw.StartsWith( wxT( "PCB FILE 4" ) ) )
432 {
433 aBoard.version = 4;
434 haveHeader = true;
435 }
436 else if( kw.StartsWith( wxT( "PCB FILE 5" ) ) )
437 {
438 aBoard.version = 5;
439 haveHeader = true;
440 }
441 else if( kw.StartsWith( wxT( "ENDPCB" ) ) )
442 {
443 break;
444 }
445 else if( kw.StartsWith( wxT( "NETDEF" ) ) )
446 {
447 parseNetDef();
448 }
449 else if( kw.StartsWith( wxT( "COMP" ) ) )
450 {
453 aBoard.components.push_back( std::move( comp ) );
454 }
455 else if( kw == wxT( "FT" ) )
456 {
457 TRACK t;
458
459 if( parseTrack( t ) )
460 aBoard.tracks.push_back( t );
461 }
462 else if( kw == wxT( "FA" ) )
463 {
464 ARC a;
465
466 if( parseArc( a ) )
467 aBoard.arcs.push_back( a );
468 }
469 else if( kw == wxT( "FV" ) )
470 {
471 VIA v;
472
473 if( parseVia( v ) )
474 aBoard.vias.push_back( v );
475 }
476 else if( kw == wxT( "FF" ) )
477 {
478 FILL f;
479
480 if( parseFill( f ) )
481 aBoard.fills.push_back( f );
482 }
483 else if( kw == wxT( "FP" ) )
484 {
485 PAD p;
486
487 if( parsePad( p ) )
488 aBoard.pads.push_back( p );
489 }
490 else if( kw == wxT( "FS" ) )
491 {
492 TEXT s;
493
494 if( parseText( s ) )
495 aBoard.texts.push_back( s );
496 }
497 }
498
499 return haveHeader;
500}
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