KiCad PCB EDA Suite
Loading...
Searching...
No Matches
pcb_io_autotrax.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 "pcb_io_autotrax.h"
26#include "autotrax_parser.h"
27
28#include <board.h>
30#include <footprint.h>
31#include <pad.h>
32#include <pcb_track.h>
33#include <pcb_shape.h>
34#include <pcb_text.h>
35#include <zone.h>
36#include <netinfo.h>
38#include <geometry/eda_angle.h>
40#include <reporter.h>
41#include <math/util.h>
42#include <math/vector2d.h>
43
45#include <math/box2.h>
46
47#include <algorithm>
48#include <cmath>
49#include <fstream>
50#include <set>
51#include <sstream>
52
53using AUTOTRAX::ARC;
55using AUTOTRAX::FILL;
57using AUTOTRAX::TEXT;
58using AUTOTRAX::TRACK;
59using AUTOTRAX::VIA;
60
61// AUTOTRAX::PAD and AUTOTRAX::COMPONENT collide with KiCad's global PAD class and
62// board.h's COMPONENT class, so both are always spelled out with the namespace
63// prefix here and the KiCad PAD class is reached as ::PAD.
64
65
67 PCB_IO( wxS( "Protel Autotrax" ) )
68{
69}
70
71
75
76
83static bool readFile( const wxString& aFileName, wxString& aOut, size_t aLimit = 0 )
84{
85 std::ifstream file( aFileName.fn_str(), std::ios::binary );
86
87 if( !file.is_open() )
88 return false;
89
90 std::string raw;
91
92 if( aLimit > 0 )
93 {
94 raw.resize( aLimit );
95 file.read( raw.data(), aLimit );
96 raw.resize( static_cast<size_t>( file.gcount() ) );
97 }
98 else
99 {
100 std::ostringstream ss;
101 ss << file.rdbuf();
102 raw = ss.str();
103 }
104
105 aOut = wxString::FromUTF8( raw.data(), raw.size() );
106
107 if( aOut.IsEmpty() && !raw.empty() )
108 aOut = wxString::From8BitData( raw.data(), raw.size() );
109
110 return true;
111}
112
113
114bool PCB_IO_AUTOTRAX::CanReadBoard( const wxString& aFileName ) const
115{
116 if( !PCB_IO::CanReadBoard( aFileName ) )
117 return false;
118
119 // The magic header is in the first non-blank line, so a small prefix read is
120 // enough to disambiguate from the gEDA .pcb files sharing this extension.
121 wxString contents;
122
123 if( !readFile( aFileName, contents, 4096 ) )
124 return false;
125
126 return AUTOTRAX_PARSER::Sniff( contents );
127}
128
129
130void PCB_IO_AUTOTRAX::loadBoard( const wxString& aFileName, BOARD& aBoard, bool aIsNewLoad,
131 const std::map<std::string, UTF8>* aProperties, PROJECT* aProject )
132{
133 m_props = aProperties;
134 m_board = &aBoard;
135
136 m_nets.clear();
137 m_footprintsByRef.clear();
138 m_maxY = 0;
139 m_isNewLoad = aIsNewLoad;
141 m_targetPads = 0;
142 m_offLayerPads = 0;
143 m_planePads = 0;
144 m_unmappedItems = 0;
145
146 wxString contents;
147
148 if( !readFile( aFileName, contents ) )
149 THROW_IO_ERRORF( _( "Could not read file '%s'." ), aFileName );
150
151 BOARD_DATA data;
152
153 AUTOTRAX_PARSER parser( &reporter() );
154
155 if( !parser.Parse( contents, data ) )
156 THROW_IO_ERRORF( _( "'%s' is not a valid Protel Autotrax file." ), aFileName );
157
158 buildBoard( data );
159}
160
161
162bool PCB_IO_AUTOTRAX::mapLayer( int aLayer, const FOOTPRINT* aFootprint, PCB_LAYER_ID& aResult ) const
163{
164 using namespace AUTOTRAX;
165
166 switch( aLayer )
167 {
168 case LAYER_TOP_COPPER: aResult = F_Cu; return true;
169 case LAYER_MID1: aResult = In1_Cu; return true;
170 case LAYER_MID2: aResult = In2_Cu; return true;
171 case LAYER_MID3: aResult = In3_Cu; return true;
172 case LAYER_MID4: aResult = In4_Cu; return true;
173 case LAYER_BOTTOM_COPPER: aResult = B_Cu; return true;
174 case LAYER_TOP_SILK: aResult = F_SilkS; return true;
175 case LAYER_BOTTOM_SILK: aResult = B_SilkS; return true;
176 case LAYER_GND_PLANE: aResult = In5_Cu; return true;
177 case LAYER_POWER_PLANE: aResult = In6_Cu; return true;
178 case LAYER_BOARD: aResult = Edge_Cuts; return true;
179 case LAYER_KEEPOUT: aResult = aFootprint ? User_1 : m_keepoutLayer; return true;
180 case LAYER_MULTI: aResult = F_Cu; return true; // through-all features
181 default:
182 // 0 (unset) has no KiCad target and is dropped.
183 return false;
184 }
185}
186
187
192
193
195{
196 if( m_targetPads )
197 {
198 reporter().Report( wxString::Format( _( "Autotrax import: %d pad(s) with a target shape were skipped." ),
199 m_targetPads ),
201 }
202
203 if( m_offLayerPads )
204 {
205 reporter().Report( wxString::Format( _( "Autotrax import: %d pad(s) on unsupported layers were skipped." ),
208 }
209
210 if( m_planePads )
211 {
212 reporter().Report( wxString::Format( _( "Autotrax import: %d pad(s) ask for a ground or power plane "
213 "connection, which is not imported." ),
214 m_planePads ),
216 }
217
218 if( m_unmappedItems )
219 {
220 reporter().Report( wxString::Format( _( "Autotrax import: %d item(s) on a layer with no KiCad equivalent "
221 "were skipped." ),
224 }
225}
226
227
228NETINFO_ITEM* PCB_IO_AUTOTRAX::getNet( const wxString& aNetName )
229{
230 if( aNetName.IsEmpty() )
231 return nullptr;
232
233 auto it = m_nets.find( aNetName );
234
235 if( it != m_nets.end() )
236 return it->second;
237
238 // Reuse a net already on the board (e.g. when appending) so codes and names
239 // are never duplicated.
240 if( NETINFO_ITEM* existing = m_board->FindNet( aNetName ) )
241 {
242 m_nets[aNetName] = existing;
243 return existing;
244 }
245
246 NETINFO_ITEM* net = new NETINFO_ITEM( m_board, aNetName );
247 m_board->Add( net );
248 m_nets[aNetName] = net;
249 return net;
250}
251
252
254{
255 return aFootprint ? static_cast<BOARD_ITEM*>( aFootprint ) : static_cast<BOARD_ITEM*>( m_board );
256}
257
258
260{
261 if( aFootprint )
262 aFootprint->Add( aItem );
263 else
264 m_board->Add( aItem, ADD_MODE::APPEND );
265}
266
267
269static BOX2D spanBox( double aX1, double aY1, double aX2, double aY2 )
270{
271 BOX2D box;
272 box.Merge( VECTOR2D( aX1, aY1 ) );
273 box.Merge( VECTOR2D( aX2, aY2 ) );
274 return box;
275}
276
277
281{
282 double startDeg;
283 double deltaDeg;
284};
285
286
291static std::vector<ARC_SPAN> arcSpansFromSegments( int aSegments )
292{
293 if( aSegments <= 0 || aSegments >= 15 )
294 return { { 0.0, 360.0 } };
295
296 auto isSet = [&]( int aQuadrant )
297 {
298 return ( aSegments >> ( ( aQuadrant + 4 ) % 4 ) ) & 1;
299 };
300
301 std::vector<ARC_SPAN> spans;
302
303 for( int quadrant = 0; quadrant < 4; ++quadrant )
304 {
305 if( !isSet( quadrant ) || isSet( quadrant - 1 ) )
306 continue;
307
308 int length = 1;
309
310 while( isSet( quadrant + length ) )
311 ++length;
312
313 spans.push_back( { 90.0 * quadrant, 90.0 * length } );
314 }
315
316 return spans;
317}
318
319
321static BOX2D arcBox( const ARC& aArc )
322{
323 BOX2D box;
324
325 for( const ARC_SPAN& span : arcSpansFromSegments( aArc.segments ) )
326 {
327 // Spans start and end on quadrant boundaries, so their extremes are among these points
328 for( double deg = span.startDeg; deg <= span.startDeg + span.deltaDeg; deg += 90.0 )
329 {
330 double rad = deg * M_PI / 180.0;
331 box.Merge( VECTOR2D( aArc.centerX + aArc.radius * std::cos( rad ),
332 aArc.centerY + aArc.radius * std::sin( rad ) ) );
333 }
334 }
335
336 return box;
337}
338
339
340void PCB_IO_AUTOTRAX::emitTrack( const TRACK& aTrack, FOOTPRINT* aFootprint )
341{
342 PCB_LAYER_ID layer;
343
344 if( !mapLayer( aTrack.layer, aFootprint, layer ) )
345 {
347 return;
348 }
349
350 // A free copper segment becomes a routed PCB_TRACK. Everything else, and any
351 // segment owned by a footprint, becomes a graphic PCB_SHAPE so it stays with
352 // the footprint (KiCad footprints cannot own PCB_TRACK objects).
353 if( IsCopperLayer( layer ) && !aFootprint )
354 {
355 PCB_TRACK* track = new PCB_TRACK( m_board );
356 track->SetStart( toBoard( aTrack.x1, aTrack.y1 ) );
357 track->SetEnd( toBoard( aTrack.x2, aTrack.y2 ) );
358 track->SetWidth( std::max( 1, toIU( aTrack.width ) ) );
359 track->SetLayer( layer );
360 m_board->Add( track, ADD_MODE::APPEND );
361 return;
362 }
363
364 PCB_SHAPE* shape = new PCB_SHAPE( parentOf( aFootprint ), SHAPE_T::SEGMENT );
365 shape->SetLayer( layer );
366 shape->SetStart( toBoard( aTrack.x1, aTrack.y1 ) );
367 shape->SetEnd( toBoard( aTrack.x2, aTrack.y2 ) );
368 shape->SetWidth( std::max( 1, toIU( aTrack.width ) ) );
369 addItem( shape, aFootprint );
370}
371
372
373void PCB_IO_AUTOTRAX::emitArc( const ARC& aArc, FOOTPRINT* aFootprint )
374{
375 PCB_LAYER_ID layer;
376
377 if( !mapLayer( aArc.layer, aFootprint, layer ) )
378 {
380 return;
381 }
382
383 int r = toIU( aArc.radius );
384 int width = std::max( 1, toIU( aArc.width ) );
385 VECTOR2I center = toBoard( aArc.centerX, aArc.centerY );
386
387 // A point on the circle for an angle in the Y-up file frame. toBoard()
388 // applies the Y flip so the resulting geometry lands in KiCad space.
389 auto pointAt = [&]( double aDeg )
390 {
391 double rad = aDeg * M_PI / 180.0;
392 return toBoard( aArc.centerX + aArc.radius * std::cos( rad ), aArc.centerY + aArc.radius * std::sin( rad ) );
393 };
394
395 for( const ARC_SPAN& span : arcSpansFromSegments( aArc.segments ) )
396 {
397 PCB_SHAPE* shape = new PCB_SHAPE( parentOf( aFootprint ) );
398 shape->SetLayer( layer );
399 shape->SetWidth( width );
400
401 if( span.deltaDeg >= 360.0 )
402 {
403 shape->SetShape( SHAPE_T::CIRCLE );
404 shape->SetCenter( center );
405 shape->SetEnd( VECTOR2I( center.x + r, center.y ) );
406 }
407 else
408 {
409 shape->SetShape( SHAPE_T::ARC );
410 shape->SetArcGeometry( pointAt( span.startDeg ), pointAt( span.startDeg + span.deltaDeg / 2.0 ),
411 pointAt( span.startDeg + span.deltaDeg ) );
412 }
413
414 addItem( shape, aFootprint );
415 }
416}
417
418
419void PCB_IO_AUTOTRAX::emitVia( const VIA& aVia, FOOTPRINT* aFootprint )
420{
421 int drill = std::max( 1, toIU( aVia.drill ) );
422 int diameter = std::max( drill + 2, toIU( aVia.diameter ) );
423
424 if( aFootprint )
425 {
426 // A via inside a component becomes a through-hole pad so it travels with
427 // the footprint.
429 pad.x = aVia.x;
430 pad.y = aVia.y;
431 pad.xSize = aVia.diameter;
432 pad.ySize = aVia.diameter;
433 pad.shape = 1;
434 pad.drill = aVia.drill;
436 emitPad( pad, aFootprint );
437 return;
438 }
439
440 PCB_VIA* via = new PCB_VIA( m_board );
441 via->SetPadstackMode( PADSTACK::MODE::NORMAL ); // AutoTrax doesn't have complex padstacks
442 via->SetPosition( toBoard( aVia.x, aVia.y ) );
443 via->SetDrill( drill );
444 via->SetWidth( PADSTACK::ALL_LAYERS, diameter );
445 via->SetViaType( VIATYPE::THROUGH );
446 via->SetLayerPair( F_Cu, B_Cu );
448}
449
450
451void PCB_IO_AUTOTRAX::emitPad( const AUTOTRAX::PAD& aPad, FOOTPRINT* aFootprint )
452{
453 // Shapes 5 and 6 are crosshair and moire targets, which have no pad equivalent
454 if( aPad.shape == 5 || aPad.shape == 6 )
455 {
456 m_targetPads++;
457 return;
458 }
459
460 // Plane connections only matter with a plane, and the plane layers import as plain copper
461 if( aPad.planeFlags > 1 )
462 m_planePads++;
463
464 // Autotrax only places pads on the top (1), bottom (6) or multi/through (13)
465 // layers; other pad layers are unsupported and dropped.
467 && aPad.layer != AUTOTRAX::LAYER_MULTI )
468 {
470 return;
471 }
472
473 PCB_LAYER_ID layer;
474
475 if( !mapLayer( aPad.layer, aFootprint, layer ) )
476 return;
477
478 // A free pad with no owning footprint still needs a footprint container so
479 // KiCad can hold the pad; create a throwaway one anchored at the pad.
480 FOOTPRINT* owner = aFootprint;
481 bool standalone = false;
482
483 if( !owner )
484 {
485 owner = new FOOTPRINT( m_board );
486 owner->SetPosition( toBoard( aPad.x, aPad.y ) );
487 standalone = true;
488 }
489
490 ::PAD* pad = new ::PAD( owner );
491 pad->SetNumber( aPad.name );
492 pad->SetPosition( toBoard( aPad.x, aPad.y ) );
493 pad->SetPadstackMode( PADSTACK::MODE::NORMAL ); // AutoTrax doesn't have complex padstacks
494
495 VECTOR2I size( std::max( 1, toIU( aPad.xSize ) ), std::max( 1, toIU( aPad.ySize ) ) );
496 pad->SetSize( PADSTACK::ALL_LAYERS, size );
497
498 switch( aPad.shape )
499 {
500 case 2: pad->SetShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::RECTANGLE ); break;
501
502 case 3: // octagon, approximated by chamfering all four corners
504 pad->SetChamferRectRatio( PADSTACK::ALL_LAYERS, 0.25 );
505 pad->SetChamferPositions( PADSTACK::ALL_LAYERS, RECT_CHAMFER_ALL );
506 break;
507
508 case 4: pad->SetShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::ROUNDRECT ); break;
509
510 default:
511 pad->SetShape( PADSTACK::ALL_LAYERS, ( aPad.xSize == aPad.ySize ) ? PAD_SHAPE::CIRCLE : PAD_SHAPE::OVAL );
512 break;
513 }
514
515 int drill = toIU( aPad.drill );
516
517 if( drill > 0 )
518 {
519 pad->SetAttribute( PAD_ATTRIB::PTH );
520 pad->SetDrillSize( VECTOR2I( drill, drill ) );
521 pad->SetLayerSet( ::PAD::PTHMask() );
522 }
523 else
524 {
525 pad->SetAttribute( PAD_ATTRIB::SMD );
526
527 LSET smdMask = ::PAD::SMDMask();
528
529 if( layer == B_Cu )
530 smdMask.FlipStandardLayers();
531
532 pad->SetLayerSet( smdMask );
533 }
534
535 owner->Add( pad );
536
537 if( standalone )
538 m_board->Add( owner, ADD_MODE::APPEND );
539}
540
541
542void PCB_IO_AUTOTRAX::emitFill( const FILL& aFill, FOOTPRINT* aFootprint )
543{
544 PCB_LAYER_ID layer;
545
546 if( !mapLayer( aFill.layer, aFootprint, layer ) )
547 {
549 return;
550 }
551
552 VECTOR2I p1 = toBoard( aFill.x1, aFill.y1 );
553 VECTOR2I p2 = toBoard( aFill.x2, aFill.y2 );
554
555 int minX = std::min( p1.x, p2.x );
556 int maxX = std::max( p1.x, p2.x );
557 int minY = std::min( p1.y, p2.y );
558 int maxY = std::max( p1.y, p2.y );
559
560 // Autotrax fills are rectangular pours. On copper layers they become a zone;
561 // elsewhere a filled rectangle graphic.
562 if( IsCopperLayer( layer ) && !aFootprint )
563 {
564 SHAPE_POLY_SET poly;
565 SHAPE_LINE_CHAIN outline;
566 outline.Append( VECTOR2I( minX, minY ) );
567 outline.Append( VECTOR2I( maxX, minY ) );
568 outline.Append( VECTOR2I( maxX, maxY ) );
569 outline.Append( VECTOR2I( minX, maxY ) );
570 outline.SetClosed( true );
571 poly.AddOutline( outline );
572
573 ZONE* zone = new ZONE( m_board );
574 zone->SetLayer( layer );
575 zone->SetOutline( new SHAPE_POLY_SET( poly ) );
576 zone->SetAssignedPriority( 0 );
577 m_board->Add( zone, ADD_MODE::APPEND );
578 return;
579 }
580
581 PCB_SHAPE* shape = new PCB_SHAPE( parentOf( aFootprint ), SHAPE_T::RECTANGLE );
582 shape->SetLayer( layer );
583 shape->SetStart( VECTOR2I( minX, minY ) );
584 shape->SetEnd( VECTOR2I( maxX, maxY ) );
585 shape->SetFilled( true );
586 shape->SetWidth( 0 );
587 addItem( shape, aFootprint );
588}
589
590
591bool PCB_IO_AUTOTRAX::applyText( const TEXT& aText, PCB_TEXT* aTarget )
592{
593 PCB_LAYER_ID layer;
594
595 if( !mapLayer( aText.layer, aTarget->GetParentFootprint(), layer ) )
596 {
598 return false;
599 }
600
601 aTarget->SetLayer( layer );
602 aTarget->SetPosition( toBoard( aText.x, aText.y ) );
603
604 int height = std::max( 1, toIU( aText.height ) );
605 aTarget->SetTextSize( VECTOR2I( height, height ) );
606 aTarget->SetTextThickness( std::max( 1, toIU( aText.width ) ) );
607
608 // Autotrax mirrors text after rotating it, so a mirrored string turns the other way
609 double angle = 90.0 * aText.direction;
610
611 aTarget->SetTextAngle( EDA_ANGLE( aText.mirrored ? -angle : angle, DEGREES_T ) );
612 aTarget->SetMirrored( aText.mirrored );
613 aTarget->SetKeepUpright( false );
614 return true;
615}
616
617
618void PCB_IO_AUTOTRAX::emitText( const TEXT& aText, FOOTPRINT* aFootprint )
619{
620 PCB_TEXT* text = new PCB_TEXT( parentOf( aFootprint ) );
621 text->SetText( aText.text );
622
623 if( !applyText( aText, text ) )
624 {
625 delete text;
626 return;
627 }
628
629 addItem( text, aFootprint );
630}
631
632
634{
635 FOOTPRINT* fp = new FOOTPRINT( m_board );
636 fp->SetPosition( toBoard( aComp.x, aComp.y ) );
637
638 if( !aComp.refdes.IsEmpty() )
639 fp->SetReference( aComp.refdes );
640
641 if( !aComp.value.IsEmpty() )
642 fp->SetValue( aComp.value );
643
644 if( !aComp.name.IsEmpty() )
645 fp->SetFPID( LIB_ID( wxEmptyString, aComp.name ) );
646
647 // A label on a dropped layer is hidden rather than shown on the field's default layer
648 bool refdesPlaced = !aComp.refdesText || applyText( *aComp.refdesText, &fp->Reference() );
649 bool valuePlaced = !aComp.valueText || applyText( *aComp.valueText, &fp->Value() );
650
651 fp->Reference().SetVisible( aComp.refdesVisible && refdesPlaced );
652 fp->Value().SetVisible( aComp.valueVisible && valuePlaced );
653
654 for( const TRACK& t : aComp.tracks )
655 emitTrack( t, fp );
656
657 for( const ARC& a : aComp.arcs )
658 emitArc( a, fp );
659
660 for( const VIA& v : aComp.vias )
661 emitVia( v, fp );
662
663 for( const AUTOTRAX::PAD& p : aComp.pads )
664 emitPad( p, fp );
665
666 for( const FILL& f : aComp.fills )
667 emitFill( f, fp );
668
669 for( const TEXT& s : aComp.texts )
670 emitText( s, fp );
671
672 m_board->Add( fp, ADD_MODE::APPEND );
673 m_footprintsByRef.emplace( aComp.refdes, fp );
674}
675
676
677void PCB_IO_AUTOTRAX::assignNets( const std::vector<NET_NODE>& aNodes )
678{
679 for( const NET_NODE& node : aNodes )
680 {
681 NETINFO_ITEM* net = getNet( node.netName );
682 std::vector<::PAD*> pads;
683 std::set<FOOTPRINT*> owners;
684
685 // Designators and pad names may both contain '-', so try every split
686 for( size_t dash = node.node.find( '-' ); dash != wxString::npos; dash = node.node.find( '-', dash + 1 ) )
687 {
688 wxString padName = node.node.Mid( dash + 1 );
689
690 if( padName.IsEmpty() )
691 continue;
692
693 auto [first, last] = m_footprintsByRef.equal_range( node.node.Left( dash ) );
694
695 for( auto it = first; it != last; ++it )
696 {
697 for( ::PAD* pad : it->second->Pads() )
698 {
699 if( pad->GetNumber() == padName )
700 {
701 pads.push_back( pad );
702 owners.insert( it->second );
703 }
704 }
705 }
706 }
707
708 if( owners.size() == 1 )
709 {
710 for( ::PAD* pad : pads )
711 pad->SetNet( net );
712 }
713 else if( owners.empty() )
714 {
715 reporter().Report( wxString::Format( _( "Autotrax import: net '%s' node '%s' matches no pad." ),
716 node.netName, node.node ),
718 }
719 else
720 {
721 reporter().Report( wxString::Format( _( "Autotrax import: net '%s' node '%s' matches pads in more than "
722 "one footprint and was not connected." ),
723 node.netName, node.node ),
725 }
726 }
727}
728
729
731{
732 // A single walk over every primitive (free and component-owned) collects what
733 // is needed before any item is emitted: the deepest inner copper layer
734 // referenced, the Y extent of the data and where the keepout layer goes.
735 //
736 // mapLayer() places the four inner copper layers on In1..In4 and the GND and
737 // Power planes on In5/In6, so the board must enable enough copper layers for
738 // the deepest inner layer actually referenced (otherwise items land on a
739 // disabled layer). A two-sided board keeps just F_Cu/B_Cu.
740 //
741 // The Y extent drives the Y-up -> Y-down flip about the data bounding box
742 // so all coordinates stay positive.
743 int innerNeeded = 0;
744 double maxYmils = 0.0;
745
746 // Designers often draw the board edge on the keepout layer, which also bounds the autorouter
747 bool boardLayerUsed = false;
748 bool freeKeepoutUsed = false;
749 bool componentKeepoutUsed = false;
750 BOX2D keepoutBox;
751 BOX2D drilledBox;
752
753 auto noteLayer = [&]( int aLayer, bool aFree )
754 {
755 switch( aLayer )
756 {
757 case AUTOTRAX::LAYER_MID1: innerNeeded = std::max( innerNeeded, 1 ); break;
758 case AUTOTRAX::LAYER_MID2: innerNeeded = std::max( innerNeeded, 2 ); break;
759 case AUTOTRAX::LAYER_MID3: innerNeeded = std::max( innerNeeded, 3 ); break;
760 case AUTOTRAX::LAYER_MID4: innerNeeded = std::max( innerNeeded, 4 ); break;
761 case AUTOTRAX::LAYER_GND_PLANE: innerNeeded = std::max( innerNeeded, 5 ); break;
762 case AUTOTRAX::LAYER_POWER_PLANE: innerNeeded = std::max( innerNeeded, 6 ); break;
763 case AUTOTRAX::LAYER_BOARD: boardLayerUsed = true; break;
764 case AUTOTRAX::LAYER_KEEPOUT: ( aFree ? freeKeepoutUsed : componentKeepoutUsed ) = true; break;
765 default: break;
766 }
767 };
768
769 auto noteOutline = [&]( int aLayer, bool aFree, const BOX2D& aBox )
770 {
771 noteLayer( aLayer, aFree );
772
773 if( aFree && aLayer == AUTOTRAX::LAYER_KEEPOUT )
774 keepoutBox.Merge( aBox );
775 };
776
777 auto noteText = [&]( const TEXT& aText, bool aFree )
778 {
779 noteLayer( aText.layer, aFree );
780 maxYmils = std::max( maxYmils, aText.y );
781 };
782
783 // BOARD_DATA and COMPONENT share the same six primitive members, so one
784 // generic walk covers both the free primitives and each component's.
785 auto scan = [&]( const auto& aContainer, bool aFree )
786 {
787 for( const TRACK& t : aContainer.tracks )
788 {
789 noteOutline( t.layer, aFree, spanBox( t.x1, t.y1, t.x2, t.y2 ) );
790 maxYmils = std::max( { maxYmils, t.y1, t.y2 } );
791 }
792
793 for( const ARC& a : aContainer.arcs )
794 {
795 noteOutline( a.layer, aFree, arcBox( a ) );
796 maxYmils = std::max( maxYmils, a.centerY + a.radius );
797 }
798
799 for( const VIA& v : aContainer.vias )
800 {
801 drilledBox.Merge( VECTOR2D( v.x, v.y ) );
802 maxYmils = std::max( maxYmils, v.y );
803 }
804
805 // emitPad() keeps only top, bottom and multi layer pads, so their layer needs no note
806 for( const AUTOTRAX::PAD& p : aContainer.pads )
807 {
808 drilledBox.Merge( VECTOR2D( p.x, p.y ) );
809 maxYmils = std::max( maxYmils, p.y );
810 }
811
812 for( const FILL& f : aContainer.fills )
813 {
814 noteOutline( f.layer, aFree, spanBox( f.x1, f.y1, f.x2, f.y2 ) );
815 maxYmils = std::max( { maxYmils, f.y1, f.y2 } );
816 }
817
818 for( const TEXT& s : aContainer.texts )
819 noteText( s, aFree );
820 };
821
822 scan( aData, true );
823
824 for( const AUTOTRAX::COMPONENT& c : aData.components )
825 {
826 scan( c, false );
827
828 for( const std::optional<TEXT>* label : { &c.refdesText, &c.valueText } )
829 {
830 if( *label )
831 noteText( **label, false );
832 }
833 }
834
835 m_board->SetCopperLayerCount( 2 + innerNeeded );
836 m_maxY = toIU( maxYmils );
837
838 // Only free outlines can be the board edge; footprint Edge_Cuts would join the board outline
839 bool keepoutIsEdge = keepoutBox.IsValid() && !boardLayerUsed
840 && ( !drilledBox.IsValid() || keepoutBox.Contains( drilledBox ) );
841
842 m_keepoutLayer = keepoutIsEdge ? Edge_Cuts : User_1;
843
844 if( keepoutIsEdge )
845 {
846 reporter().Report( _( "Autotrax import: keepout layer used as the board outline." ), RPT_SEVERITY_INFO );
847 }
848
849 if( componentKeepoutUsed || ( freeKeepoutUsed && !keepoutIsEdge ) )
850 {
851 m_board->SetEnabledLayers( m_board->GetEnabledLayers() | LSET( { User_1 } ) );
852
853 if( m_isNewLoad )
854 m_board->SetLayerName( User_1, _( "Keepout" ) );
855 }
856
857 for( const TRACK& t : aData.tracks )
858 emitTrack( t, nullptr );
859
860 for( const ARC& a : aData.arcs )
861 emitArc( a, nullptr );
862
863 for( const VIA& v : aData.vias )
864 emitVia( v, nullptr );
865
866 for( const AUTOTRAX::PAD& p : aData.pads )
867 emitPad( p, nullptr );
868
869 for( const FILL& f : aData.fills )
870 emitFill( f, nullptr );
871
872 for( const TEXT& s : aData.texts )
873 emitText( s, nullptr );
874
875 for( const AUTOTRAX::COMPONENT& c : aData.components )
876 buildComponent( c );
877
878 assignNets( aData.netNodes );
879 reportGaps();
880}
BOX2< VECTOR2D > BOX2D
Definition box2.h:915
Line-oriented parser for Protel Autotrax / Easytrax PCB files.
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 SetLayer(PCB_LAYER_ID aLayer) override
Set the layer this item is on.
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition board_item.h:84
virtual void SetLayer(PCB_LAYER_ID aLayer)
Set the layer this item is on.
Definition board_item.h:374
FOOTPRINT * GetParentFootprint() const
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:410
constexpr BOX2< Vec > & Merge(const BOX2< Vec > &aRect)
Modify the position and size of the rectangle in order to contain aRect.
Definition box2.h:594
constexpr bool Contains(const Vec &aPoint) const
Definition box2.h:165
constexpr bool IsValid() const
Definition box2.h:857
void SetCenter(const VECTOR2I &aCenter)
virtual void SetFilled(bool aFlag)
Definition eda_shape.h:142
void SetMirrored(bool isMirrored)
Definition eda_text.cpp:354
virtual void SetVisible(bool aVisible)
Definition eda_text.cpp:347
void SetKeepUpright(bool aKeepUpright)
Definition eda_text.cpp:386
void SetPosition(const VECTOR2I &aPos) override
void SetFPID(const LIB_ID &aFPID)
Definition footprint.h:475
PCB_FIELD & Value()
read/write accessors:
Definition footprint.h:947
void SetReference(const wxString &aReference)
Definition footprint.h:915
void SetValue(const wxString &aValue)
Definition footprint.h:938
PCB_FIELD & Reference()
Definition footprint.h:948
void Add(BOARD_ITEM *aItem, ADD_MODE aMode=ADD_MODE::INSERT, bool aSkipConnectivity=false) override
Adds an item to the container.
REPORTER * m_reporter
Reporter to log errors/warnings to, may be nullptr.
Definition io_base.h:238
A logical library item identifier and consists of various portions much like a URI.
Definition lib_id.h:45
LSET is a set of PCB_LAYER_IDs.
Definition lset.h:37
LSET & FlipStandardLayers(int aCopperLayersCount=0)
Flip the layers in this set.
Definition lset.cpp:481
Handle the data for a net.
Definition netinfo.h:50
static REPORTER & GetInstance()
Definition reporter.cpp:207
@ NORMAL
Shape is the same on all layers.
Definition padstack.h:170
static constexpr PCB_LAYER_ID ALL_LAYERS
! The layer identifier to use for the single defintion on normal padstacks
Definition padstack.h:179
Definition pad.h:61
static LSET PTHMask()
layer set for a through hole pad
Definition pad.cpp:616
static LSET SMDMask()
layer set for a SMD pad on Front layer
Definition pad.cpp:623
void buildComponent(const AUTOTRAX::COMPONENT &aComp)
NETINFO_ITEM * getNet(const wxString &aNetName)
bool CanReadBoard(const wxString &aFileName) const override
std::map< wxString, NETINFO_ITEM * > m_nets
int m_targetPads
pads with a crosshair or moire target shape
static int toIU(double aMils)
Convert a mil value to KiCad internal units (nm).
void buildBoard(const AUTOTRAX::BOARD_DATA &aData)
BOARD_ITEM * parentOf(FOOTPRINT *aFootprint) const
Parent for a primitive: the owning footprint, or the board for free items.
bool applyText(const AUTOTRAX::TEXT &aText, PCB_TEXT *aTarget)
Apply an Autotrax string record's placement, size, rotation and layer to aText.
void assignNets(const std::vector< AUTOTRAX::NET_NODE > &aNodes)
Attach each NETDEF "refdes-pad" node to the matching imported pads.
PCB_LAYER_ID m_keepoutLayer
Edge_Cuts when the keepout outlines the board.
bool mapLayer(int aLayer, const FOOTPRINT *aFootprint, PCB_LAYER_ID &aResult) const
Map an Autotrax layer number to a KiCad layer for an item owned by aFootprint, or a free item when it...
void addItem(BOARD_ITEM *aItem, FOOTPRINT *aFootprint)
Attach an item to its footprint, or append it to the board.
int m_maxY
board Y extent in IU, used to flip the Y axis
~PCB_IO_AUTOTRAX() override
std::multimap< wxString, FOOTPRINT * > m_footprintsByRef
footprints created by this load
VECTOR2I toBoard(double aX, double aY) const
Convert an Autotrax point (mils, Y-up) to a board point (nm, Y-down).
void emitTrack(const AUTOTRAX::TRACK &aTrack, FOOTPRINT *aFootprint)
void emitFill(const AUTOTRAX::FILL &aFill, FOOTPRINT *aFootprint)
int m_unmappedItems
items on a layer with no KiCad equivalent
void emitPad(const AUTOTRAX::PAD &aPad, FOOTPRINT *aFootprint)
int m_offLayerPads
pads on a layer KiCad cannot place them on
void emitArc(const AUTOTRAX::ARC &aArc, FOOTPRINT *aFootprint)
void loadBoard(const wxString &aFileName, BOARD &aBoard, bool aIsNewLoad, const std::map< std::string, UTF8 > *aProperties=nullptr, PROJECT *aProject=nullptr) override
Parse aFileName into aBoard.
void emitText(const AUTOTRAX::TEXT &aText, FOOTPRINT *aFootprint)
void reportGaps() const
Report what the import dropped or could not represent.
int m_planePads
pads asking for a ground or power plane connection
void emitVia(const AUTOTRAX::VIA &aVia, FOOTPRINT *aFootprint)
REPORTER & reporter() const
BOARD * m_board
The board BOARD being worked on, no ownership here.
Definition pcb_io.h:393
virtual bool CanReadBoard(const wxString &aFileName) const
Definition pcb_io.cpp:40
PCB_IO(const wxString &aName)
Definition pcb_io.h:372
const std::map< std::string, UTF8 > * m_props
Properties passed via Save() or Load(), no ownership, may be NULL.
Definition pcb_io.h:396
void SetWidth(int aWidth) override
void SetShape(SHAPE_T aShape) override
Definition pcb_shape.h:209
void SetEnd(const VECTOR2I &aEnd) override
void SetLayer(PCB_LAYER_ID aLayer) override
Set the layer this item is on.
void SetStart(const VECTOR2I &aStart) override
void SetArcGeometry(const VECTOR2I &aStart, const VECTOR2I &aMid, const VECTOR2I &aEnd) override
Set the three controlling points for an arc.
void SetTextThickness(int aWidth) override
The TextThickness is that set by the user.
Definition pcb_text.cpp:512
void SetTextSize(VECTOR2I aNewSize, bool aEnforceMinTextSize=true) override
Definition pcb_text.cpp:484
void SetPosition(const VECTOR2I &aPos) override
Definition pcb_text.h:102
void SetTextAngle(const EDA_ANGLE &aAngle) override
Definition pcb_text.cpp:572
void SetEnd(const VECTOR2I &aEnd)
Definition pcb_track.h:94
void SetStart(const VECTOR2I &aStart)
Definition pcb_track.h:97
virtual void SetWidth(int aWidth)
Definition pcb_track.h:91
Container for project specific data.
Definition project.h:63
A pure virtual class used to derive REPORTER objects from.
Definition reporter.h:73
virtual REPORTER & Report(const wxString &aText, SEVERITY aSeverity=RPT_SEVERITY_UNDEFINED)
Report a string with a given severity.
Definition reporter.h:102
Represent a polyline containing arcs as well as line segments: A chain of connected line and/or arc s...
void SetClosed(bool aClosed)
Mark the line chain as closed (i.e.
void Append(int aX, int aY, bool aAllowDuplication=false)
Append a new point at the end of the line chain.
Represent a set of closed polygons.
int AddOutline(const SHAPE_LINE_CHAIN &aOutline)
Adds a new outline to the set and returns its index.
Handle a list of polygons defining a copper zone.
Definition zone.h:70
virtual void SetLayer(PCB_LAYER_ID aLayer) override
Set the layer this item is on.
Definition zone.cpp:640
void SetAssignedPriority(unsigned aPriority)
Definition zone.h:117
void SetOutline(SHAPE_POLY_SET *aOutline)
Definition zone.h:424
#define _(s)
@ DEGREES_T
Definition eda_angle.h:30
@ SEGMENT
Definition eda_shape.h:56
@ RECTANGLE
Use RECTANGLE instead of RECT to avoid collision in a Windows header.
Definition eda_shape.h:57
#define THROW_IO_ERRORF(msg,...)
bool IsCopperLayer(int aLayerId)
Test whether a layer is a copper layer.
Definition layer_ids.h:703
PCB_LAYER_ID
A quick note on layer IDs:
Definition layer_ids.h:56
@ Edge_Cuts
Definition layer_ids.h:108
@ B_Cu
Definition layer_ids.h:61
@ In2_Cu
Definition layer_ids.h:63
@ F_SilkS
Definition layer_ids.h:96
@ In4_Cu
Definition layer_ids.h:65
@ In1_Cu
Definition layer_ids.h:62
@ User_1
Definition layer_ids.h:120
@ B_SilkS
Definition layer_ids.h:97
@ In6_Cu
Definition layer_ids.h:67
@ In5_Cu
Definition layer_ids.h:66
@ In3_Cu
Definition layer_ids.h:64
@ F_Cu
Definition layer_ids.h:60
@ LAYER_BOTTOM_COPPER
@ SMD
Smd pad, appears on the solder paste layer (default)
Definition padstack.h:98
@ PTH
Plated through hole pad.
Definition padstack.h:97
@ CHAMFERED_RECT
Definition padstack.h:59
@ ROUNDRECT
Definition padstack.h:56
@ RECTANGLE
Definition padstack.h:53
static std::vector< ARC_SPAN > arcSpansFromSegments(int aSegments)
Translate an Autotrax arc quadrant bitmask into the arc spans it represents.
static bool readFile(const wxString &aFileName, wxString &aOut, size_t aLimit=0)
Read a file into aOut.
static BOX2D arcBox(const ARC &aArc)
Bounding box of an arc's drawn quadrants, in mils.
static BOX2D spanBox(double aX1, double aY1, double aX2, double aY2)
Bounding box of two points, in mils.
@ RPT_SEVERITY_WARNING
@ RPT_SEVERITY_INFO
One arc as a (start angle, counterclockwise sweep) pair in the file's native Y-up frame.
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< NET_NODE > netNodes
std::vector< PAD > pads
std::vector< TRACK > tracks
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.
One refdes -> net membership row collected from the NETDEF section.
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).
VECTOR2I center
#define M_PI
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:708
VECTOR2< double > VECTOR2D
Definition vector2d.h:707