KiCad PCB EDA Suite
altium_pcb.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  * Copyright (C) 2019-2020 Thomas Pointhuber <thomas.pointhuber@gmx.at>
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License
8  * as published by the Free Software Foundation; either version 2
9  * of the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, you may find one here:
18  * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
19  * or you may search the http://www.gnu.org website for the version 2 license,
20  * or you may write to the Free Software Foundation, Inc.,
21  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
22  */
23 
24 #include "altium_pcb.h"
25 #include "altium_parser_pcb.h"
28 
29 #include <board.h>
30 #include <board_design_settings.h>
31 #include <pcb_dimension.h>
32 #include <pad.h>
33 #include <pcb_shape.h>
34 #include <pcb_text.h>
35 #include <pcb_track.h>
36 #include <kicad_string.h>
37 
38 #include <fp_shape.h>
39 #include <fp_text.h>
40 #include <zone.h>
41 
43 
44 #include <compoundfilereader.h>
46 #include <project.h>
47 #include <trigo.h>
48 #include <utf.h>
49 #include <wx/docview.h>
50 #include <wx/log.h>
51 #include <wx/mstream.h>
52 #include <wx/wfstream.h>
53 #include <wx/zstream.h>
54 
55 
56 void ParseAltiumPcb( BOARD* aBoard, const wxString& aFileName,
57  const std::map<ALTIUM_PCB_DIR, std::string>& aFileMapping )
58 {
59  // Open file
60  FILE* fp = wxFopen( aFileName, "rb" );
61 
62  if( fp == nullptr )
63  {
64  wxLogError( wxString::Format( _( "Cannot open file '%s'" ), aFileName ) );
65  return;
66  }
67 
68  fseek( fp, 0, SEEK_END );
69  long len = ftell( fp );
70 
71  if( len < 0 )
72  {
73  fclose( fp );
74  THROW_IO_ERROR( "Reading error, cannot determine length of file" );
75  }
76 
77  std::unique_ptr<unsigned char[]> buffer( new unsigned char[len] );
78  fseek( fp, 0, SEEK_SET );
79 
80  size_t bytesRead = fread( buffer.get(), sizeof( unsigned char ), len, fp );
81  fclose( fp );
82 
83  if( static_cast<size_t>( len ) != bytesRead )
84  {
85  THROW_IO_ERROR( "Reading error" );
86  }
87 
88  try
89  {
90  CFB::CompoundFileReader reader( buffer.get(), bytesRead );
91 
92  // Parse File
93  ALTIUM_PCB pcb( aBoard );
94  pcb.Parse( reader, aFileMapping );
95  }
96  catch( CFB::CFBException& exception )
97  {
98  THROW_IO_ERROR( exception.what() );
99  }
100 }
101 
102 
104 {
105  return aLayer >= ALTIUM_LAYER::TOP_LAYER && aLayer <= ALTIUM_LAYER::BOTTOM_LAYER;
106 }
107 
108 
110 {
112 }
113 
114 
116 {
117  if( aComponent == ALTIUM_COMPONENT_NONE )
118  {
119  PCB_SHAPE* shape = new PCB_SHAPE( m_board );
120  m_board->Add( shape, ADD_MODE::APPEND );
121  return shape;
122  }
123  else
124  {
125  if( m_components.size() <= aComponent )
126  {
127  THROW_IO_ERROR( wxString::Format( "Component creator tries to access component id %d "
128  "of %d existing components",
129  aComponent,
130  m_components.size() ) );
131  }
132 
133  FOOTPRINT* footprint = m_components.at( aComponent );
134  PCB_SHAPE* fpShape = new FP_SHAPE( footprint );
135 
136  footprint->Add( fpShape, ADD_MODE::APPEND );
137  return fpShape;
138  }
139 }
140 
141 
142 void ALTIUM_PCB::HelperDrawsegmentSetLocalCoord( PCB_SHAPE* aShape, uint16_t aComponent )
143 {
144  if( aComponent != ALTIUM_COMPONENT_NONE )
145  {
146  FP_SHAPE* fpShape = dynamic_cast<FP_SHAPE*>( aShape );
147 
148  if( fpShape )
149  {
150  fpShape->SetLocalCoord();
151 
152  // TODO: SetLocalCoord() does not update the polygon shape!
153  // This workaround converts the poly shape into the local coordinates
154  SHAPE_POLY_SET& polyShape = fpShape->GetPolyShape();
155  if( !polyShape.IsEmpty() )
156  {
157  FOOTPRINT* fp = m_components.at( aComponent );
158 
159  polyShape.Move( -fp->GetPosition() );
160  polyShape.Rotate( -fp->GetOrientationRadians() );
161  }
162  }
163  }
164 }
165 
166 
168  SHAPE_LINE_CHAIN& aLine, const std::vector<ALTIUM_VERTICE>& aVertices )
169 {
170  for( auto& vertice : aVertices )
171  {
172  if( vertice.isRound )
173  {
174  double angle = NormalizeAngleDegreesPos( vertice.endangle - vertice.startangle );
175 
176  double startradiant = DEG2RAD( vertice.startangle );
177  double endradiant = DEG2RAD( vertice.endangle );
178  wxPoint arcStartOffset = wxPoint( KiROUND( std::cos( startradiant ) * vertice.radius ),
179  -KiROUND( std::sin( startradiant ) * vertice.radius ) );
180 
181  wxPoint arcEndOffset = wxPoint( KiROUND( std::cos( endradiant ) * vertice.radius ),
182  -KiROUND( std::sin( endradiant ) * vertice.radius ) );
183 
184  wxPoint arcStart = vertice.center + arcStartOffset;
185  wxPoint arcEnd = vertice.center + arcEndOffset;
186 
187  if( GetLineLength( arcStart, vertice.position )
188  < GetLineLength( arcEnd, vertice.position ) )
189  {
190  aLine.Append( SHAPE_ARC( vertice.center, arcStart, -angle ) );
191  }
192  else
193  {
194  aLine.Append( SHAPE_ARC( vertice.center, arcEnd, angle ) );
195  }
196  }
197  else
198  {
199  aLine.Append( vertice.position );
200  }
201  }
202 
203  aLine.SetClosed( true );
204 }
205 
206 
208 {
209  auto override = m_layermap.find( aAltiumLayer );
210  if( override != m_layermap.end() )
211  {
212  return override->second;
213  }
214 
215  switch( aAltiumLayer )
216  {
218 
219  case ALTIUM_LAYER::TOP_LAYER: return F_Cu;
220  case ALTIUM_LAYER::MID_LAYER_1: return In1_Cu;
221  case ALTIUM_LAYER::MID_LAYER_2: return In2_Cu;
222  case ALTIUM_LAYER::MID_LAYER_3: return In3_Cu;
223  case ALTIUM_LAYER::MID_LAYER_4: return In4_Cu;
224  case ALTIUM_LAYER::MID_LAYER_5: return In5_Cu;
225  case ALTIUM_LAYER::MID_LAYER_6: return In6_Cu;
226  case ALTIUM_LAYER::MID_LAYER_7: return In7_Cu;
227  case ALTIUM_LAYER::MID_LAYER_8: return In8_Cu;
228  case ALTIUM_LAYER::MID_LAYER_9: return In9_Cu;
229  case ALTIUM_LAYER::MID_LAYER_10: return In10_Cu;
230  case ALTIUM_LAYER::MID_LAYER_11: return In11_Cu;
231  case ALTIUM_LAYER::MID_LAYER_12: return In12_Cu;
232  case ALTIUM_LAYER::MID_LAYER_13: return In13_Cu;
233  case ALTIUM_LAYER::MID_LAYER_14: return In14_Cu;
234  case ALTIUM_LAYER::MID_LAYER_15: return In15_Cu;
235  case ALTIUM_LAYER::MID_LAYER_16: return In16_Cu;
236  case ALTIUM_LAYER::MID_LAYER_17: return In17_Cu;
237  case ALTIUM_LAYER::MID_LAYER_18: return In18_Cu;
238  case ALTIUM_LAYER::MID_LAYER_19: return In19_Cu;
239  case ALTIUM_LAYER::MID_LAYER_20: return In20_Cu;
240  case ALTIUM_LAYER::MID_LAYER_21: return In21_Cu;
241  case ALTIUM_LAYER::MID_LAYER_22: return In22_Cu;
242  case ALTIUM_LAYER::MID_LAYER_23: return In23_Cu;
243  case ALTIUM_LAYER::MID_LAYER_24: return In24_Cu;
244  case ALTIUM_LAYER::MID_LAYER_25: return In25_Cu;
245  case ALTIUM_LAYER::MID_LAYER_26: return In26_Cu;
246  case ALTIUM_LAYER::MID_LAYER_27: return In27_Cu;
247  case ALTIUM_LAYER::MID_LAYER_28: return In28_Cu;
248  case ALTIUM_LAYER::MID_LAYER_29: return In29_Cu;
249  case ALTIUM_LAYER::MID_LAYER_30: return In30_Cu;
250  case ALTIUM_LAYER::BOTTOM_LAYER: return B_Cu;
251 
252  case ALTIUM_LAYER::TOP_OVERLAY: return F_SilkS;
254  case ALTIUM_LAYER::TOP_PASTE: return F_Paste;
255  case ALTIUM_LAYER::BOTTOM_PASTE: return B_Paste;
256  case ALTIUM_LAYER::TOP_SOLDER: return F_Mask;
257  case ALTIUM_LAYER::BOTTOM_SOLDER: return B_Mask;
258 
275 
278 
279  case ALTIUM_LAYER::MECHANICAL_1: return User_1; //Edge_Cuts;
280  case ALTIUM_LAYER::MECHANICAL_2: return User_2;
281  case ALTIUM_LAYER::MECHANICAL_3: return User_3;
282  case ALTIUM_LAYER::MECHANICAL_4: return User_4;
283  case ALTIUM_LAYER::MECHANICAL_5: return User_5;
284  case ALTIUM_LAYER::MECHANICAL_6: return User_6;
285  case ALTIUM_LAYER::MECHANICAL_7: return User_7;
286  case ALTIUM_LAYER::MECHANICAL_8: return User_8;
287  case ALTIUM_LAYER::MECHANICAL_9: return User_9;
291  case ALTIUM_LAYER::MECHANICAL_13: return F_Fab;
292  case ALTIUM_LAYER::MECHANICAL_14: return B_Fab;
295 
306 
307  default: return UNDEFINED_LAYER;
308  }
309 }
310 
311 
313 {
314  m_board = aBoard;
315  m_num_nets = 0;
317 }
318 
320 {
321 }
322 
323 void ALTIUM_PCB::Parse( const CFB::CompoundFileReader& aReader,
324  const std::map<ALTIUM_PCB_DIR, std::string>& aFileMapping )
325 {
326  // this vector simply declares in which order which functions to call.
327  const std::vector<std::tuple<bool, ALTIUM_PCB_DIR, PARSE_FUNCTION_POINTER_fp>> parserOrder = {
329  [this]( auto aReader, auto fileHeader ) {
330  this->ParseFileHeader( aReader, fileHeader );
331  } },
332  { true, ALTIUM_PCB_DIR::BOARD6,
333  [this]( auto aReader, auto fileHeader ) {
334  this->ParseBoard6Data( aReader, fileHeader );
335  } },
337  [this]( auto aReader, auto fileHeader ) {
338  this->ParseComponents6Data( aReader, fileHeader );
339  } },
340  { true, ALTIUM_PCB_DIR::MODELS,
341  [this, aFileMapping]( auto aReader, auto fileHeader ) {
342  wxString dir( aFileMapping.at( ALTIUM_PCB_DIR::MODELS ) );
343  dir.RemoveLast( 4 ); // Remove "Data" from the path
344  this->ParseModelsData( aReader, fileHeader, dir );
345  } },
347  [this]( auto aReader, auto fileHeader ) {
348  this->ParseComponentsBodies6Data( aReader, fileHeader );
349  } },
350  { true, ALTIUM_PCB_DIR::NETS6,
351  [this]( auto aReader, auto fileHeader ) {
352  this->ParseNets6Data( aReader, fileHeader );
353  } },
354  { true, ALTIUM_PCB_DIR::CLASSES6,
355  [this]( auto aReader, auto fileHeader ) {
356  this->ParseClasses6Data( aReader, fileHeader );
357  } },
358  { true, ALTIUM_PCB_DIR::RULES6,
359  [this]( auto aReader, auto fileHeader ) {
360  this->ParseRules6Data( aReader, fileHeader );
361  } },
363  [this]( auto aReader, auto fileHeader ) {
364  this->ParseDimensions6Data( aReader, fileHeader );
365  } },
367  [this]( auto aReader, auto fileHeader ) {
368  this->ParsePolygons6Data( aReader, fileHeader );
369  } },
370  { true, ALTIUM_PCB_DIR::ARCS6,
371  [this]( auto aReader, auto fileHeader ) {
372  this->ParseArcs6Data( aReader, fileHeader );
373  } },
374  { true, ALTIUM_PCB_DIR::PADS6,
375  [this]( auto aReader, auto fileHeader ) {
376  this->ParsePads6Data( aReader, fileHeader );
377  } },
378  { true, ALTIUM_PCB_DIR::VIAS6,
379  [this]( auto aReader, auto fileHeader ) {
380  this->ParseVias6Data( aReader, fileHeader );
381  } },
382  { true, ALTIUM_PCB_DIR::TRACKS6,
383  [this]( auto aReader, auto fileHeader ) {
384  this->ParseTracks6Data( aReader, fileHeader );
385  } },
386  { true, ALTIUM_PCB_DIR::TEXTS6,
387  [this]( auto aReader, auto fileHeader ) {
388  this->ParseTexts6Data( aReader, fileHeader );
389  } },
390  { true, ALTIUM_PCB_DIR::FILLS6,
391  [this]( auto aReader, auto fileHeader ) {
392  this->ParseFills6Data( aReader, fileHeader );
393  } },
395  [this]( auto aReader, auto fileHeader ) {
396  this->ParseBoardRegionsData( aReader, fileHeader );
397  } },
399  [this]( auto aReader, auto fileHeader ) {
400  this->ParseShapeBasedRegions6Data( aReader, fileHeader );
401  } },
402  { true, ALTIUM_PCB_DIR::REGIONS6,
403  [this]( auto aReader, auto fileHeader ) {
404  this->ParseRegions6Data( aReader, fileHeader );
405  } }
406  };
407 
408  // Parse data in specified order
409  for( const std::tuple<bool, ALTIUM_PCB_DIR, PARSE_FUNCTION_POINTER_fp>& cur : parserOrder )
410  {
411  bool isRequired;
414  std::tie( isRequired, directory, fp ) = cur;
415 
416  const auto& mappedDirectory = aFileMapping.find( directory );
417  if( mappedDirectory == aFileMapping.end() )
418  {
419  wxASSERT_MSG( !isRequired, wxString::Format( "Altium Directory of kind %d was expected, "
420  "but no mapping is present in the code",
421  directory ) );
422  continue;
423  }
424 
425  const CFB::COMPOUND_FILE_ENTRY* file =
426  FindStream( aReader, mappedDirectory->second.c_str() );
427  if( file != nullptr )
428  {
429  fp( aReader, file );
430  }
431  else if( isRequired )
432  {
433  wxLogError( wxString::Format( _( "File not found: '%s'" ), mappedDirectory->second ) );
434  }
435  }
436 
437  // fixup zone priorities since Altium stores them in the opposite order
438  for( auto& zone : m_polygons )
439  {
440  if( !zone )
441  continue;
442 
443  // Altium "fills" - not poured in Altium
444  if( zone->GetPriority() == 1000 )
445  {
446  // Unlikely, but you never know
447  if( m_highest_pour_index >= 1000 )
448  zone->SetPriority( m_highest_pour_index + 1 );
449 
450  continue;
451  }
452 
453  int priority = m_highest_pour_index - zone->GetPriority();
454 
455  zone->SetPriority( priority >= 0 ? priority : 0 );
456  }
457 
458  // change priority of outer zone to zero
459  for( auto& zone : m_outer_plane )
460  {
461  zone.second->SetPriority( 0 );
462  }
463 
464  // center board
466 
467  int w = m_board->GetPageSettings().GetWidthIU();
468  int h = m_board->GetPageSettings().GetHeightIU();
469 
470  int desired_x = ( w - bbbox.GetWidth() ) / 2;
471  int desired_y = ( h - bbbox.GetHeight() ) / 2;
472 
473  wxPoint movementVector( desired_x - bbbox.GetX(), desired_y - bbbox.GetY() );
474  m_board->Move( movementVector );
475  m_board->GetDesignSettings().m_AuxOrigin += movementVector;
476  m_board->GetDesignSettings().m_GridOrigin += movementVector;
477 
478  m_board->SetModified();
479 }
480 
481 int ALTIUM_PCB::GetNetCode( uint16_t aId ) const
482 {
483  if( aId == ALTIUM_NET_UNCONNECTED )
484  {
486  }
487  else if( m_num_nets < aId )
488  {
490  "Netcode with id %d does not exist. Only %d nets are known", aId, m_num_nets ) );
491  }
492  else
493  {
494  return aId + 1;
495  }
496 }
497 
498 const ARULE6* ALTIUM_PCB::GetRule( ALTIUM_RULE_KIND aKind, const wxString& aName ) const
499 {
500  const auto rules = m_rules.find( aKind );
501  if( rules == m_rules.end() )
502  {
503  return nullptr;
504  }
505  for( const ARULE6& rule : rules->second )
506  {
507  if( rule.name == aName )
508  {
509  return &rule;
510  }
511  }
512  return nullptr;
513 }
514 
516 {
517  const auto rules = m_rules.find( aKind );
518  if( rules == m_rules.end() )
519  {
520  return nullptr;
521  }
522  for( const ARULE6& rule : rules->second )
523  {
524  if( rule.scope1expr == "All" && rule.scope2expr == "All" )
525  {
526  return &rule;
527  }
528  }
529  return nullptr;
530 }
531 
532 void ALTIUM_PCB::ParseFileHeader( const CFB::CompoundFileReader& aReader,
533  const CFB::COMPOUND_FILE_ENTRY* aEntry )
534 {
535  ALTIUM_PARSER reader( aReader, aEntry );
536 
537  reader.ReadAndSetSubrecordLength();
538  wxString header = reader.ReadWxString();
539 
540  //std::cout << "HEADER: " << header << std::endl; // tells me: PCB 5.0 Binary File
541 
542  //reader.SkipSubrecord();
543 
544  // TODO: does not seem to work all the time at the moment
545  //if( reader.GetRemainingBytes() != 0 )
546  //{
547  // THROW_IO_ERROR( "FileHeader stream is not fully parsed" );
548  //}
549 }
550 
551 void ALTIUM_PCB::ParseBoard6Data( const CFB::CompoundFileReader& aReader,
552  const CFB::COMPOUND_FILE_ENTRY* aEntry )
553 {
554  ALTIUM_PARSER reader( aReader, aEntry );
555 
556  ABOARD6 elem( reader );
557 
558  if( reader.GetRemainingBytes() != 0 )
559  {
560  THROW_IO_ERROR( "Board6 stream is not fully parsed" );
561  }
562 
565 
566  // read layercount from stackup, because LAYERSETSCOUNT is not always correct?!
567  size_t layercount = 0;
568  for( size_t i = static_cast<size_t>( ALTIUM_LAYER::TOP_LAYER );
569  i < elem.stackup.size() && i != 0; i = elem.stackup[i - 1].nextId, layercount++ )
570  ;
571  size_t kicadLayercount = ( layercount % 2 == 0 ) ? layercount : layercount + 1;
572  m_board->SetCopperLayerCount( kicadLayercount );
573 
574  BOARD_DESIGN_SETTINGS& designSettings = m_board->GetDesignSettings();
575  BOARD_STACKUP& stackup = designSettings.GetStackupDescriptor();
576 
577  // create board stackup
578  stackup.RemoveAll(); // Just to be sure
579  stackup.BuildDefaultStackupList( &designSettings, layercount );
580 
581  auto it = stackup.GetList().begin();
582  // find first copper layer
583  for( ; it != stackup.GetList().end() && ( *it )->GetType() != BS_ITEM_TYPE_COPPER; ++it )
584  ;
585 
586  auto curLayer = static_cast<int>( F_Cu );
587  for( size_t altiumLayerId = static_cast<size_t>( ALTIUM_LAYER::TOP_LAYER );
588  altiumLayerId < elem.stackup.size() && altiumLayerId != 0;
589  altiumLayerId = elem.stackup[altiumLayerId - 1].nextId )
590  {
591  // array starts with 0, but stackup with 1
592  ABOARD6_LAYER_STACKUP& layer = elem.stackup.at( altiumLayerId - 1 );
593 
594  // handle unused layer in case of odd layercount
595  if( layer.nextId == 0 && layercount != kicadLayercount )
596  {
597  m_board->SetLayerName( ( *it )->GetBrdLayerId(), "[unused]" );
598 
599  if( ( *it )->GetType() != BS_ITEM_TYPE_COPPER )
600  {
601  THROW_IO_ERROR( "Board6 stream, unexpected item while parsing stackup" );
602  }
603  ( *it )->SetThickness( 0 );
604 
605  ++it;
606  if( ( *it )->GetType() != BS_ITEM_TYPE_DIELECTRIC )
607  {
608  THROW_IO_ERROR( "Board6 stream, unexpected item while parsing stackup" );
609  }
610  ( *it )->SetThickness( 0, 0 );
611  ( *it )->SetThicknessLocked( true, 0 );
612  ++it;
613  }
614 
615  m_layermap.insert( { static_cast<ALTIUM_LAYER>( altiumLayerId ),
616  static_cast<PCB_LAYER_ID>( curLayer++ ) } );
617 
618  if( ( *it )->GetType() != BS_ITEM_TYPE_COPPER )
619  {
620  THROW_IO_ERROR( "Board6 stream, unexpected item while parsing stackup" );
621  }
622  ( *it )->SetThickness( layer.copperthick );
623 
624  ALTIUM_LAYER alayer = static_cast<ALTIUM_LAYER>( altiumLayerId );
625  PCB_LAYER_ID klayer = ( *it )->GetBrdLayerId();
626 
627  m_board->SetLayerName( klayer, layer.name );
628 
629  if( layer.copperthick == 0 )
630  {
631  m_board->SetLayerType( klayer, LAYER_T::LT_JUMPER ); // used for things like wirebonding
632  }
633  else if( IsAltiumLayerAPlane( alayer ) )
634  {
636  }
637 
638  if( klayer == B_Cu )
639  {
640  if( layer.nextId != 0 )
641  {
642  THROW_IO_ERROR( "Board6 stream, unexpected id while parsing last stackup layer" );
643  }
644  // overwrite entry from internal -> bottom
645  m_layermap[alayer] = B_Cu;
646  break;
647  }
648 
649  ++it;
650  if( ( *it )->GetType() != BS_ITEM_TYPE_DIELECTRIC )
651  {
652  THROW_IO_ERROR( "Board6 stream, unexpected item while parsing stackup" );
653  }
654  ( *it )->SetThickness( layer.dielectricthick, 0 );
655  ( *it )->SetMaterial( layer.dielectricmaterial.empty() ?
656  NotSpecifiedPrm() :
657  wxString( layer.dielectricmaterial ) );
658  ( *it )->SetEpsilonR( layer.dielectricconst, 0 );
659 
660  ++it;
661  }
662 
663  // Set name of all non-cu layers
664  for( size_t altiumLayerId = static_cast<size_t>( ALTIUM_LAYER::TOP_OVERLAY );
665  altiumLayerId <= static_cast<size_t>( ALTIUM_LAYER::BOTTOM_SOLDER ); altiumLayerId++ )
666  {
667  // array starts with 0, but stackup with 1
668  ABOARD6_LAYER_STACKUP& layer = elem.stackup.at( altiumLayerId - 1 );
669 
670  ALTIUM_LAYER alayer = static_cast<ALTIUM_LAYER>( altiumLayerId );
671  PCB_LAYER_ID klayer = GetKicadLayer( alayer );
672 
673  m_board->SetLayerName( klayer, layer.name );
674  }
675 
676  for( size_t altiumLayerId = static_cast<size_t>( ALTIUM_LAYER::MECHANICAL_1 );
677  altiumLayerId <= static_cast<size_t>( ALTIUM_LAYER::MECHANICAL_16 ); altiumLayerId++ )
678  {
679  // array starts with 0, but stackup with 1
680  ABOARD6_LAYER_STACKUP& layer = elem.stackup.at( altiumLayerId - 1 );
681 
682  ALTIUM_LAYER alayer = static_cast<ALTIUM_LAYER>( altiumLayerId );
683  PCB_LAYER_ID klayer = GetKicadLayer( alayer );
684 
685  m_board->SetLayerName( klayer, layer.name );
686  }
687 
689 }
690 
691 void ALTIUM_PCB::HelperCreateBoardOutline( const std::vector<ALTIUM_VERTICE>& aVertices )
692 {
693  if( !aVertices.empty() )
694  {
695  const ALTIUM_VERTICE* last = &aVertices.at( 0 );
696  for( size_t i = 0; i < aVertices.size(); i++ )
697  {
698  const ALTIUM_VERTICE* cur = &aVertices.at( ( i + 1 ) % aVertices.size() );
699 
700  PCB_SHAPE* shape = new PCB_SHAPE( m_board );
701  m_board->Add( shape, ADD_MODE::APPEND );
702 
704  shape->SetLayer( Edge_Cuts );
705 
706  if( !last->isRound && !cur->isRound )
707  {
709  shape->SetStart( last->position );
710  shape->SetEnd( cur->position );
711  }
712  else if( cur->isRound )
713  {
714  shape->SetShape( PCB_SHAPE_TYPE::ARC );
715  shape->SetAngle( -NormalizeAngleDegreesPos( cur->endangle - cur->startangle ) * 10. );
716 
717  double startradiant = DEG2RAD( cur->startangle );
718  wxPoint arcStartOffset = wxPoint( KiROUND( std::cos( startradiant ) * cur->radius ),
719  -KiROUND( std::sin( startradiant ) * cur->radius ) );
720  wxPoint arcStart = cur->center + arcStartOffset;
721  shape->SetCenter( cur->center );
722  shape->SetArcStart( arcStart );
723 
724  if( !last->isRound )
725  {
726  double endradiant = DEG2RAD( cur->endangle );
727  wxPoint arcEndOffset = wxPoint( KiROUND( std::cos( endradiant ) * cur->radius ),
728  -KiROUND( std::sin( endradiant ) * cur->radius ) );
729  wxPoint arcEnd = cur->center + arcEndOffset;
730 
731  PCB_SHAPE* shape2 = new PCB_SHAPE( m_board );
733  m_board->Add( shape2, ADD_MODE::APPEND );
735  shape2->SetLayer( Edge_Cuts );
736  shape2->SetStart( last->position );
737 
738  // TODO: this is more of a hack than the real solution
739  double lineLengthStart = GetLineLength( last->position, arcStart );
740  double lineLengthEnd = GetLineLength( last->position, arcEnd );
741  if( lineLengthStart > lineLengthEnd )
742  {
743  shape2->SetEnd( cur->center + arcEndOffset );
744  }
745  else
746  {
747  shape2->SetEnd( cur->center + arcStartOffset );
748  }
749  }
750  }
751  last = cur;
752  }
753  }
754 }
755 
756 void ALTIUM_PCB::ParseClasses6Data( const CFB::CompoundFileReader& aReader,
757  const CFB::COMPOUND_FILE_ENTRY* aEntry )
758 {
759  ALTIUM_PARSER reader( aReader, aEntry );
760 
761  while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
762  {
763  ACLASS6 elem( reader );
764 
765  if( elem.kind == ALTIUM_CLASS_KIND::NET_CLASS )
766  {
767  NETCLASSPTR nc = std::make_shared<NETCLASS>( elem.name );
768 
769  for( const auto& name : elem.names )
770  {
771  // TODO: it seems it can happen that we have names not attached to any net.
772  nc->Add( name );
773  }
774 
775  if( !m_board->GetDesignSettings().GetNetClasses().Add( nc ) )
776  {
777  // Name conflict, this is likely a bad board file.
778  // unique_ptr will delete nc on this code path
779  THROW_IO_ERROR( wxString::Format( _( "Duplicated Netclass name \"%s\"" ), elem.name ) );
780  }
781  }
782  }
783 
784  if( reader.GetRemainingBytes() != 0 )
785  {
786  THROW_IO_ERROR( "Classes6 stream is not fully parsed" );
787  }
788 
790 }
791 
792 void ALTIUM_PCB::ParseComponents6Data( const CFB::CompoundFileReader& aReader,
793  const CFB::COMPOUND_FILE_ENTRY* aEntry )
794 {
795  ALTIUM_PARSER reader( aReader, aEntry );
796 
797  uint16_t componentId = 0;
798  while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
799  {
800  ACOMPONENT6 elem( reader );
801 
802  FOOTPRINT* footprint = new FOOTPRINT( m_board );
803  m_board->Add( footprint, ADD_MODE::APPEND );
804  m_components.emplace_back( footprint );
805 
807 
808  footprint->SetFPID( fpID );
809 
810  footprint->SetPosition( elem.position );
811  footprint->SetOrientationDegrees( elem.rotation );
812 
813  // KiCad netlisting requires parts to have non-digit + digit annotation.
814  // If the reference begins with a number, we prepend 'UNK' (unknown) for the source designator
815  wxString reference = elem.sourcedesignator;
816  if( reference.find_first_not_of( "0123456789" ) == wxString::npos )
817  reference.Prepend( "UNK" );
818  footprint->SetReference( reference );
819 
820  footprint->SetLocked( elem.locked );
821  footprint->Reference().SetVisible( elem.nameon );
822  footprint->Value().SetVisible( elem.commenton );
823  footprint->SetLayer( elem.layer == ALTIUM_LAYER::TOP_LAYER ? F_Cu : B_Cu );
824 
825  componentId++;
826  }
827 
828  if( reader.GetRemainingBytes() != 0 )
829  {
830  THROW_IO_ERROR( "Components6 stream is not fully parsed" );
831  }
832 }
833 
834 
835 void ALTIUM_PCB::ParseComponentsBodies6Data( const CFB::CompoundFileReader& aReader,
836  const CFB::COMPOUND_FILE_ENTRY* aEntry )
837 {
838  ALTIUM_PARSER reader( aReader, aEntry );
839 
840  while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
841  {
842  ACOMPONENTBODY6 elem( reader ); // TODO: implement
843 
844  if( elem.component == ALTIUM_COMPONENT_NONE )
845  {
846  continue; // TODO: we do not support components for the board yet
847  }
848 
849  if( m_components.size() <= elem.component )
850  {
852  "ComponentsBodies6 stream tries to access component id %d of %d existing components",
853  elem.component, m_components.size() ) );
854  }
855 
856  if( !elem.modelIsEmbedded )
857  {
858  continue;
859  }
860 
861  auto modelTuple = m_models.find( elem.modelId );
862  if( modelTuple == m_models.end() )
863  {
865  "ComponentsBodies6 stream tries to access model id %s which does not exist",
866  elem.modelId ) );
867  }
868 
869  FOOTPRINT* footprint = m_components.at( elem.component );
870  const wxPoint& fpPosition = footprint->GetPosition();
871 
872  FP_3DMODEL modelSettings;
873 
874  modelSettings.m_Filename = modelTuple->second;
875 
876  modelSettings.m_Offset.x = Iu2Millimeter((int) elem.modelPosition.x - fpPosition.x );
877  modelSettings.m_Offset.y = -Iu2Millimeter((int) elem.modelPosition.y - fpPosition.y );
878  modelSettings.m_Offset.z = Iu2Millimeter( (int) elem.modelPosition.z );
879 
880  double orientation = footprint->GetOrientation();
881 
882  if( footprint->IsFlipped() )
883  {
884  modelSettings.m_Offset.y = -modelSettings.m_Offset.y;
885  orientation = -orientation;
886  }
887 
888  RotatePoint( &modelSettings.m_Offset.x, &modelSettings.m_Offset.y, orientation );
889 
890  modelSettings.m_Rotation.x = NormalizeAngleDegrees( -elem.modelRotation.x, -180, 180 );
891  modelSettings.m_Rotation.y = NormalizeAngleDegrees( -elem.modelRotation.y, -180, 180 );
892  modelSettings.m_Rotation.z = NormalizeAngleDegrees(
893  -elem.modelRotation.z + elem.rotation + orientation / 10, -180, 180 );
894 
895  modelSettings.m_Opacity = elem.bodyOpacity;
896 
897  footprint->Models().push_back( modelSettings );
898  }
899 
900  if( reader.GetRemainingBytes() != 0 )
901  {
902  THROW_IO_ERROR( "ComponentsBodies6 stream is not fully parsed" );
903  }
904 }
905 
906 
908 {
909  if( aElem.referencePoint.size() != 2 )
910  {
911  THROW_IO_ERROR( "Incorrect number of reference points for linear dimension object" );
912  }
913 
914  PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
915 
916  if( klayer == UNDEFINED_LAYER )
917  {
918  wxLogWarning( wxString::Format( _( "Dimension on Altium layer %d has no KiCad equivalent. "
919  "Put it on Eco1_User instead" ),
920  aElem.layer ) );
921  klayer = Eco1_User;
922  }
923 
924  wxPoint referencePoint0 = aElem.referencePoint.at( 0 );
925  wxPoint referencePoint1 = aElem.referencePoint.at( 1 );
926 
927  PCB_DIM_ALIGNED* dimension = new PCB_DIM_ALIGNED( m_board );
928  m_board->Add( dimension, ADD_MODE::APPEND );
929 
930  dimension->SetPrecision( aElem.textprecision );
931  dimension->SetLayer( klayer );
932  dimension->SetStart( referencePoint0 );
933 
934  if( referencePoint0 != aElem.xy1 )
935  {
945  wxPoint direction = aElem.xy1 - referencePoint0;
946  wxPoint directionNormalVector = wxPoint( -direction.y, direction.x );
947  SEG segm1( referencePoint0, referencePoint0 + directionNormalVector );
948  SEG segm2( referencePoint1, referencePoint1 + direction );
949  wxPoint intersection( segm1.Intersect( segm2, true, true ).get() );
950  dimension->SetEnd( intersection );
951 
952  int height = static_cast<int>( EuclideanNorm( direction ) );
953 
954  if( direction.x <= 0 && direction.y <= 0 ) // TODO: I suspect this is not always correct
955  {
956  height = -height;
957  }
958 
959  dimension->SetHeight( height );
960  }
961  else
962  {
963  dimension->SetEnd( referencePoint1 );
964  }
965 
966  dimension->SetLineThickness( aElem.linewidth );
967 
968  dimension->Text().SetTextThickness( aElem.textlinewidth );
969  dimension->Text().SetTextSize( wxSize( aElem.textheight, aElem.textheight ) );
970  dimension->Text().SetBold( aElem.textbold );
971  dimension->Text().SetItalic( aElem.textitalic );
972 
973  switch( aElem.textunit )
974  {
975  case ALTIUM_UNIT::INCHES:
976  dimension->SetUnits( EDA_UNITS::INCHES );
977  break;
978  case ALTIUM_UNIT::MILS:
979  dimension->SetUnits( EDA_UNITS::MILS );
980  break;
983  dimension->SetUnits( EDA_UNITS::MILLIMETRES );
984  break;
985  default:
986  break;
987  }
988 }
989 
991 {
992  PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
993  if( klayer == UNDEFINED_LAYER )
994  {
995  wxLogWarning( wxString::Format( _( "Dimension on Altium layer %d has no KiCad equivalent. "
996  "Put it on Eco1_User instead" ),
997  aElem.layer ) );
998  klayer = Eco1_User;
999  }
1000 
1001  if( !aElem.referencePoint.empty() )
1002  {
1003  wxPoint referencePoint0 = aElem.referencePoint.at( 0 );
1004 
1005  // line
1006  wxPoint last = referencePoint0;
1007  for( size_t i = 1; i < aElem.referencePoint.size(); i++ )
1008  {
1009  PCB_SHAPE* shape = new PCB_SHAPE( m_board );
1010  m_board->Add( shape, ADD_MODE::APPEND );
1012  shape->SetLayer( klayer );
1013  shape->SetWidth( aElem.linewidth );
1014  shape->SetStart( last );
1015  shape->SetEnd( aElem.referencePoint.at( i ) );
1016  last = aElem.referencePoint.at( i );
1017  }
1018 
1019  // arrow
1020  if( aElem.referencePoint.size() >= 2 )
1021  {
1022  wxPoint dirVec = aElem.referencePoint.at( 1 ) - referencePoint0;
1023  if( dirVec.x != 0 || dirVec.y != 0 )
1024  {
1025  double scaling = EuclideanNorm( dirVec ) / aElem.arrowsize;
1026  wxPoint arrVec =
1027  wxPoint( KiROUND( dirVec.x / scaling ), KiROUND( dirVec.y / scaling ) );
1028  RotatePoint( &arrVec, 200. );
1029 
1030  PCB_SHAPE* shape1 = new PCB_SHAPE( m_board );
1031  m_board->Add( shape1, ADD_MODE::APPEND );
1032  shape1->SetShape( PCB_SHAPE_TYPE::SEGMENT );
1033  shape1->SetLayer( klayer );
1034  shape1->SetWidth( aElem.linewidth );
1035  shape1->SetStart( referencePoint0 );
1036  shape1->SetEnd( referencePoint0 + arrVec );
1037 
1038  RotatePoint( &arrVec, -400. );
1039 
1040  PCB_SHAPE* shape2 = new PCB_SHAPE( m_board );
1041  m_board->Add( shape2, ADD_MODE::APPEND );
1042  shape2->SetShape( PCB_SHAPE_TYPE::SEGMENT );
1043  shape2->SetLayer( klayer );
1044  shape2->SetWidth( aElem.linewidth );
1045  shape2->SetStart( referencePoint0 );
1046  shape2->SetEnd( referencePoint0 + arrVec );
1047  }
1048  }
1049  }
1050 
1051  if( aElem.textPoint.empty() )
1052  {
1053  wxLogError( "No text position present for leader dimension object" );
1054  return;
1055  }
1056 
1057  PCB_TEXT* text = new PCB_TEXT( m_board );
1059  text->SetText( aElem.textformat );
1060  text->SetPosition( aElem.textPoint.at( 0 ) );
1061  text->SetLayer( klayer );
1062  text->SetTextSize( wxSize( aElem.textheight, aElem.textheight ) ); // TODO: parse text width
1063  text->SetTextThickness( aElem.textlinewidth );
1066 }
1067 
1069 {
1070  PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
1071  if( klayer == UNDEFINED_LAYER )
1072  {
1073  wxLogWarning( wxString::Format( _( "Dimension on Altium layer %d has no KiCad equivalent. "
1074  "Put it on Eco1_User instead" ),
1075  aElem.layer ) );
1076  klayer = Eco1_User;
1077  }
1078 
1079  for( size_t i = 0; i < aElem.referencePoint.size(); i++ )
1080  {
1081  PCB_SHAPE* shape = new PCB_SHAPE( m_board );
1082  m_board->Add( shape, ADD_MODE::APPEND );
1084  shape->SetLayer( klayer );
1085  shape->SetWidth( aElem.linewidth );
1086  shape->SetStart( aElem.referencePoint.at( i ) );
1087  // shape->SetEnd( /* TODO: seems to be based on TEXTY */ );
1088  }
1089 }
1090 
1092 {
1093  PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
1094  if( klayer == UNDEFINED_LAYER )
1095  {
1096  wxLogWarning( wxString::Format( _( "Dimension on Altium layer %d has no KiCad equivalent. "
1097  "Put it on Eco1_User instead" ),
1098  aElem.layer ) );
1099  klayer = Eco1_User;
1100  }
1101 
1102  wxPoint vec = wxPoint( 0, aElem.height / 2 );
1103  RotatePoint( &vec, aElem.angle * 10. );
1104 
1105  PCB_DIM_CENTER* dimension = new PCB_DIM_CENTER( m_board );
1106  m_board->Add( dimension, ADD_MODE::APPEND );
1107  dimension->SetLayer( klayer );
1108  dimension->SetLineThickness( aElem.linewidth );
1109  dimension->SetStart( aElem.xy1 );
1110  dimension->SetEnd( aElem.xy1 + vec );
1111 }
1112 
1113 
1114 void ALTIUM_PCB::ParseDimensions6Data( const CFB::CompoundFileReader& aReader,
1115  const CFB::COMPOUND_FILE_ENTRY* aEntry )
1116 {
1117  ALTIUM_PARSER reader( aReader, aEntry );
1118 
1119  while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
1120  {
1121  ADIMENSION6 elem( reader );
1122 
1123  switch( elem.kind )
1124  {
1127  break;
1130  break;
1132  wxLogWarning( wxString::Format( "Ignore dimension object of kind %d", elem.kind ) );
1133  // HelperParseDimensions6Datum( elem );
1134  break;
1137  break;
1138  default:
1139  wxLogWarning( wxString::Format( "Ignore dimension object of kind %d", elem.kind ) );
1140  break;
1141  }
1142  }
1143 
1144  if( reader.GetRemainingBytes() != 0 )
1145  {
1146  THROW_IO_ERROR( "Dimensions6 stream is not fully parsed" );
1147  }
1148 }
1149 
1150 
1151 void ALTIUM_PCB::ParseModelsData( const CFB::CompoundFileReader& aReader,
1152  const CFB::COMPOUND_FILE_ENTRY* aEntry, const wxString aRootDir )
1153 {
1154  ALTIUM_PARSER reader( aReader, aEntry );
1155 
1156  if( reader.GetRemainingBytes() == 0 )
1157  {
1158  return; // fast path: no 3d-models present which need to be imported -> no directory needs to be created
1159  }
1160 
1161  wxString projectPath = wxPathOnly( m_board->GetFileName() );
1162  // TODO: set KIPRJMOD always after import (not only when loading project)?
1163  wxSetEnv( PROJECT_VAR_NAME, projectPath );
1164 
1165  // TODO: make this path configurable?
1166  const wxString altiumModelDir = "ALTIUM_EMBEDDED_MODELS";
1167 
1168  wxFileName altiumModelsPath = wxFileName::DirName( projectPath );
1169  wxString kicadModelPrefix = "${KIPRJMOD}/" + altiumModelDir + "/";
1170  if( !altiumModelsPath.AppendDir( altiumModelDir ) )
1171  {
1172  THROW_IO_ERROR( "Cannot construct directory path for step models" );
1173  }
1174 
1175  // Create dir if it does not exist
1176  if( !altiumModelsPath.DirExists() )
1177  {
1178  if( !altiumModelsPath.Mkdir() )
1179  {
1180  wxLogError( wxString::Format(
1181  _( "Cannot create directory \"%s\" -> no 3D-models will be imported." ),
1182  altiumModelsPath.GetFullPath() ) );
1183  return;
1184  }
1185  }
1186 
1187  int idx = 0;
1188  while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
1189  {
1190  AMODEL elem( reader );
1191 
1192  wxString stepPath = aRootDir + std::to_string( idx++ );
1193 
1194  const CFB::COMPOUND_FILE_ENTRY* stepEntry = FindStream( aReader, stepPath.c_str() );
1195 
1196  size_t stepSize = static_cast<size_t>( stepEntry->size );
1197  std::unique_ptr<char[]> stepContent( new char[stepSize] );
1198 
1199  // read file into buffer
1200  aReader.ReadFile( stepEntry, 0, stepContent.get(), stepSize );
1201 
1202  wxFileName storagePath( altiumModelsPath.GetPath(), elem.name );
1203  if( !storagePath.IsDirWritable() )
1204  {
1205  wxLogError(
1206  wxString::Format( _( "You do not have write permissions to save file \"%s\"." ),
1207  storagePath.GetFullPath() ) );
1208  continue;
1209  }
1210 
1211  wxMemoryInputStream stepStream( stepContent.get(), stepSize );
1212  wxZlibInputStream zlibInputStream( stepStream );
1213 
1214  wxFFileOutputStream outputStream( storagePath.GetFullPath() );
1215  outputStream.Write( zlibInputStream );
1216  outputStream.Close();
1217 
1218  m_models.insert( { elem.id, kicadModelPrefix + elem.name } );
1219  }
1220 
1221  if( reader.GetRemainingBytes() != 0 )
1222  {
1223  THROW_IO_ERROR( "Models stream is not fully parsed" );
1224  }
1225 }
1226 
1227 
1228 void ALTIUM_PCB::ParseNets6Data( const CFB::CompoundFileReader& aReader,
1229  const CFB::COMPOUND_FILE_ENTRY* aEntry )
1230 {
1231  ALTIUM_PARSER reader( aReader, aEntry );
1232 
1233  wxASSERT( m_num_nets == 0 );
1234  while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
1235  {
1236  ANET6 elem( reader );
1237 
1239  }
1240 
1241  if( reader.GetRemainingBytes() != 0 )
1242  {
1243  THROW_IO_ERROR( "Nets6 stream is not fully parsed" );
1244  }
1245 }
1246 
1247 void ALTIUM_PCB::ParsePolygons6Data( const CFB::CompoundFileReader& aReader,
1248  const CFB::COMPOUND_FILE_ENTRY* aEntry )
1249 {
1250  ALTIUM_PARSER reader( aReader, aEntry );
1251 
1252  while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
1253  {
1254  APOLYGON6 elem( reader );
1255 
1256  PCB_LAYER_ID klayer = GetKicadLayer( elem.layer );
1257  if( klayer == UNDEFINED_LAYER )
1258  {
1259  wxLogWarning( wxString::Format( _( "Polygon on Altium layer %d has no KiCad equivalent. "
1260  "Ignore it instead" ),
1261  elem.layer ) );
1262  m_polygons.emplace_back( nullptr );
1263  continue;
1264  }
1265 
1266  SHAPE_LINE_CHAIN linechain;
1268 
1269  if( linechain.PointCount() < 2 )
1270  {
1271  wxLogError( wxString::Format( _( "Polygon has only %d point extracted from %ld vertices. "
1272  "At least 2 points are required." ),
1273  linechain.PointCount(),
1274  elem.vertices.size() ) );
1275  m_polygons.emplace_back( nullptr );
1276  continue;
1277  }
1278 
1279  ZONE* zone = new ZONE( m_board );
1280  m_board->Add( zone, ADD_MODE::APPEND );
1281  m_polygons.emplace_back( zone );
1282 
1283  zone->SetFillVersion( 6 );
1284  zone->SetNetCode( GetNetCode( elem.net ) );
1285  zone->SetLayer( klayer );
1286  zone->SetPosition( elem.vertices.at( 0 ).position );
1287  zone->SetLocked( elem.locked );
1288  zone->SetPriority( elem.pourindex > 0 ? elem.pourindex : 0 );
1289  zone->Outline()->AddOutline( linechain );
1290 
1291  if( elem.pourindex > m_highest_pour_index )
1293 
1294  // TODO: more flexible rule parsing
1295  const ARULE6* clearanceRule = GetRuleDefault( ALTIUM_RULE_KIND::PLANE_CLEARANCE );
1296 
1297  if( clearanceRule != nullptr )
1298  {
1299  zone->SetLocalClearance( clearanceRule->planeclearanceClearance );
1300  }
1301 
1302  const ARULE6* polygonConnectRule = GetRuleDefault( ALTIUM_RULE_KIND::POLYGON_CONNECT );
1303 
1304  if( polygonConnectRule != nullptr )
1305  {
1306  switch( polygonConnectRule->polygonconnectStyle )
1307  {
1310  break;
1311 
1314  break;
1315 
1316  default:
1319  break;
1320  }
1321 
1322  // TODO: correct variables?
1324  polygonConnectRule->polygonconnectReliefconductorwidth );
1325  zone->SetThermalReliefGap( polygonConnectRule->polygonconnectAirgapwidth );
1326 
1327  if( polygonConnectRule->polygonconnectReliefconductorwidth < zone->GetMinThickness() )
1328  zone->SetMinThickness( polygonConnectRule->polygonconnectReliefconductorwidth );
1329  }
1330 
1331  if( IsAltiumLayerAPlane( elem.layer ) )
1332  {
1333  // outer zone will be set to priority 0 later.
1334  zone->SetPriority( 1 );
1335 
1336  // check if this is the outer zone by simply comparing the BBOX
1337  const auto& cur_outer_plane = m_outer_plane.find( elem.layer );
1338  if( cur_outer_plane == m_outer_plane.end()
1339  || zone->GetBoundingBox().Contains(
1340  cur_outer_plane->second->GetBoundingBox() ) )
1341  {
1342  m_outer_plane[elem.layer] = zone;
1343  }
1344  }
1345 
1348  {
1350  zone->SetHatchThickness( elem.trackwidth );
1351 
1353  {
1354  // use a small hack to get us only an outline (hopefully)
1355  const EDA_RECT& bbox = zone->GetBoundingBox();
1356  zone->SetHatchGap( std::max( bbox.GetHeight(), bbox.GetWidth() ) );
1357  }
1358  else
1359  {
1360  zone->SetHatchGap( elem.gridsize - elem.trackwidth );
1361  }
1362 
1364  }
1365 
1367  ZONE::GetDefaultHatchPitch(), true );
1368  }
1369 
1370  if( reader.GetRemainingBytes() != 0 )
1371  {
1372  THROW_IO_ERROR( "Polygons6 stream is not fully parsed" );
1373  }
1374 }
1375 
1376 void ALTIUM_PCB::ParseRules6Data( const CFB::CompoundFileReader& aReader,
1377  const CFB::COMPOUND_FILE_ENTRY* aEntry )
1378 {
1379  ALTIUM_PARSER reader( aReader, aEntry );
1380 
1381  while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
1382  {
1383  ARULE6 elem( reader );
1384 
1385  m_rules[elem.kind].emplace_back( elem );
1386  }
1387 
1388  // sort rules by priority
1389  for( auto&& val : m_rules )
1390  {
1391  std::sort( val.second.begin(), val.second.end(),
1392  []( const auto& lhs, const auto& rhs )
1393  {
1394  return lhs.priority < rhs.priority;
1395  } );
1396  }
1397 
1398  if( reader.GetRemainingBytes() != 0 )
1399  {
1400  THROW_IO_ERROR( "Rules6 stream is not fully parsed" );
1401  }
1402 }
1403 
1404 void ALTIUM_PCB::ParseBoardRegionsData( const CFB::CompoundFileReader& aReader,
1405  const CFB::COMPOUND_FILE_ENTRY* aEntry )
1406 {
1407  ALTIUM_PARSER reader( aReader, aEntry );
1408 
1409  while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
1410  {
1411  AREGION6 elem( reader, false );
1412 
1413  // TODO: implement?
1414  }
1415 
1416  if( reader.GetRemainingBytes() != 0 )
1417  {
1418  THROW_IO_ERROR( "BoardRegions stream is not fully parsed" );
1419  }
1420 }
1421 
1422 void ALTIUM_PCB::ParseShapeBasedRegions6Data( const CFB::CompoundFileReader& aReader,
1423  const CFB::COMPOUND_FILE_ENTRY* aEntry )
1424 {
1425  ALTIUM_PARSER reader( aReader, aEntry );
1426 
1427  while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
1428  {
1429  AREGION6 elem( reader, true );
1430 
1432  {
1434  }
1435  else if( elem.kind == ALTIUM_REGION_KIND::POLYGON_CUTOUT || elem.is_keepout )
1436  {
1437  SHAPE_LINE_CHAIN linechain;
1439 
1440  if( linechain.PointCount() < 2 )
1441  {
1442  wxLogError( wxString::Format(
1443  _( "ShapeBasedRegion has only %d point extracted from %ld vertices. At least 2 points are required." ),
1444  linechain.PointCount(), elem.outline.size() ) );
1445  continue;
1446  }
1447 
1448  ZONE* zone = new ZONE( m_board );
1449  m_board->Add( zone, ADD_MODE::APPEND );
1450 
1451  zone->SetFillVersion( 6 );
1452  zone->SetIsRuleArea( true );
1453  zone->SetDoNotAllowTracks( false );
1454  zone->SetDoNotAllowVias( false );
1455  zone->SetDoNotAllowPads( false );
1456  zone->SetDoNotAllowFootprints( false );
1457  zone->SetDoNotAllowCopperPour( true );
1458 
1459  zone->SetPosition( elem.outline.at( 0 ).position );
1460  zone->Outline()->AddOutline( linechain );
1461 
1462  if( elem.layer == ALTIUM_LAYER::MULTI_LAYER )
1463  {
1464  zone->SetLayer( F_Cu );
1465  zone->SetLayerSet( LSET::AllCuMask() );
1466  }
1467  else
1468  {
1469  PCB_LAYER_ID klayer = GetKicadLayer( elem.layer );
1470  if( klayer == UNDEFINED_LAYER )
1471  {
1472  wxLogWarning( wxString::Format( _( "Zone on Altium layer %d has no KiCad "
1473  "equivalent. Put it on Eco1_User instead" ),
1474  elem.layer ) );
1475  klayer = Eco1_User;
1476  }
1477  zone->SetLayer( klayer );
1478  }
1479 
1481  ZONE::GetDefaultHatchPitch(), true );
1482  }
1483  else if( elem.kind == ALTIUM_REGION_KIND::COPPER )
1484  {
1485  if( elem.subpolyindex == ALTIUM_POLYGON_NONE )
1486  {
1487  PCB_LAYER_ID klayer = GetKicadLayer( elem.layer );
1488  if( klayer == UNDEFINED_LAYER )
1489  {
1490  wxLogWarning( wxString::Format( _( "Polygon on Altium layer %d has no KiCad "
1491  "equivalent. Put it on Eco1_User instead" ),
1492  elem.layer ) );
1493  klayer = Eco1_User;
1494  }
1495 
1496  SHAPE_LINE_CHAIN linechain;
1498 
1499  if( linechain.PointCount() < 2 )
1500  {
1501  wxLogError( wxString::Format( _( "Polygon has only %d point extracted from %ld "
1502  "vertices. At least 2 points are required." ),
1503  linechain.PointCount(), elem.outline.size() ) );
1504  continue;
1505  }
1506 
1507  PCB_SHAPE* shape = new PCB_SHAPE( m_board );
1508  m_board->Add( shape, ADD_MODE::APPEND );
1510  shape->SetFilled( true );
1511  shape->SetLayer( klayer );
1512  shape->SetWidth( 0 );
1513 
1514  shape->SetPolyShape( linechain );
1515  }
1516  }
1517  else
1518  {
1519  wxLogError( wxString::Format( "Ignore polygon shape of kind %d on layer %s, because "
1520  "not implemented yet",
1521  elem.kind,
1522  LSET::Name( GetKicadLayer( elem.layer ) ) ) );
1523  }
1524  }
1525 
1526  if( reader.GetRemainingBytes() != 0 )
1527  {
1528  THROW_IO_ERROR( "ShapeBasedRegions6 stream is not fully parsed" );
1529  }
1530 }
1531 
1532 void ALTIUM_PCB::ParseRegions6Data( const CFB::CompoundFileReader& aReader,
1533  const CFB::COMPOUND_FILE_ENTRY* aEntry )
1534 {
1535  ALTIUM_PARSER reader( aReader, aEntry );
1536 
1537  for( ZONE* zone : m_polygons )
1538  {
1539  if( zone )
1540  zone->UnFill(); // just to be sure
1541  }
1542 
1543  while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
1544  {
1545  AREGION6 elem( reader, false );
1546 
1547  if( elem.subpolyindex != ALTIUM_POLYGON_NONE )
1548  {
1549  if( m_polygons.size() <= elem.subpolyindex )
1550  {
1552  "Region stream tries to access polygon id %d of %d existing polygons",
1553  elem.subpolyindex, m_polygons.size() ) );
1554  }
1555 
1556  ZONE *zone = m_polygons.at( elem.subpolyindex );
1557 
1558  if( zone == nullptr )
1559  {
1560  continue; // we know the zone id, but because we do not know the layer we did not add it!
1561  }
1562 
1563  PCB_LAYER_ID klayer = GetKicadLayer( elem.layer );
1564  if( klayer == UNDEFINED_LAYER )
1565  {
1566  continue; // Just skip it for now. Users can fill it themselves.
1567  }
1568 
1569  SHAPE_LINE_CHAIN linechain;
1570  for( const ALTIUM_VERTICE& vertice : elem.outline )
1571  {
1572  linechain.Append( vertice.position );
1573  }
1574  linechain.Append( elem.outline.at( 0 ).position );
1575  linechain.SetClosed( true );
1576 
1577  SHAPE_POLY_SET rawPolys;
1578  rawPolys.AddOutline( linechain );
1579 
1580  for( const std::vector<ALTIUM_VERTICE>& hole : elem.holes )
1581  {
1582  SHAPE_LINE_CHAIN hole_linechain;
1583  for( const ALTIUM_VERTICE& vertice : hole )
1584  {
1585  hole_linechain.Append( vertice.position );
1586  }
1587  hole_linechain.Append( hole.at( 0 ).position );
1588  hole_linechain.SetClosed( true );
1589  rawPolys.AddHole( hole_linechain );
1590  }
1591 
1592  if( zone->GetFilledPolysUseThickness() )
1593  rawPolys.Deflate( zone->GetMinThickness() / 2, 32 );
1594 
1595  if( zone->HasFilledPolysForLayer( klayer ) )
1596  rawPolys.BooleanAdd( zone->RawPolysList( klayer ),
1598 
1599  SHAPE_POLY_SET finalPolys = rawPolys;
1601 
1602  zone->SetRawPolysList( klayer, rawPolys );
1603  zone->SetFilledPolysList( klayer, finalPolys );
1604  zone->SetIsFilled( true );
1605  zone->SetNeedRefill( false );
1606  }
1607  }
1608 
1609  if( reader.GetRemainingBytes() != 0 )
1610  {
1611  THROW_IO_ERROR( "Regions6 stream is not fully parsed" );
1612  }
1613 }
1614 
1615 
1616 void ALTIUM_PCB::ParseArcs6Data( const CFB::CompoundFileReader& aReader,
1617  const CFB::COMPOUND_FILE_ENTRY* aEntry )
1618 {
1619  ALTIUM_PARSER reader( aReader, aEntry );
1620 
1621  while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
1622  {
1623  AARC6 elem( reader );
1624 
1625  if( elem.is_polygonoutline || elem.subpolyindex != ALTIUM_POLYGON_NONE )
1626  continue;
1627 
1628  // element in plane is in fact substracted from the plane. Should be already done by Altium?
1629  //if( IsAltiumLayerAPlane( elem.layer ) )
1630  // continue;
1631 
1632  PCB_LAYER_ID klayer = GetKicadLayer( elem.layer );
1633 
1634  if( elem.is_keepout || IsAltiumLayerAPlane( elem.layer ) )
1635  {
1636  PCB_SHAPE shape( nullptr ); // just a helper to get the graphic
1637  shape.SetWidth( elem.width );
1638  shape.SetCenter( elem.center );
1639 
1640  if( elem.startangle == 0. && elem.endangle == 360. )
1641  { // TODO: other variants to define circle?
1643  shape.SetArcStart( elem.center - wxPoint( 0, elem.radius ) );
1644  }
1645  else
1646  {
1647  shape.SetShape( PCB_SHAPE_TYPE::ARC );
1648  shape.SetAngle( -NormalizeAngleDegreesPos( elem.endangle - elem.startangle ) * 10. );
1649 
1650  double startradiant = DEG2RAD( elem.startangle );
1651  wxPoint arcStartOffset = wxPoint( KiROUND( std::cos( startradiant ) * elem.radius ),
1652  -KiROUND( std::sin( startradiant ) * elem.radius ) );
1653  shape.SetArcStart( elem.center + arcStartOffset );
1654  }
1655 
1656  ZONE* zone = new ZONE( m_board );
1657  m_board->Add( zone, ADD_MODE::APPEND );
1658 
1659  zone->SetFillVersion( 6 );
1660  zone->SetIsRuleArea( true );
1661  zone->SetDoNotAllowTracks( false );
1662  zone->SetDoNotAllowVias( false );
1663  zone->SetDoNotAllowPads( false );
1664  zone->SetDoNotAllowFootprints( false );
1665  zone->SetDoNotAllowCopperPour( true );
1666 
1667  if( elem.layer == ALTIUM_LAYER::MULTI_LAYER )
1668  {
1669  zone->SetLayer( F_Cu );
1670  zone->SetLayerSet( LSET::AllCuMask() );
1671  }
1672  else
1673  {
1674  PCB_LAYER_ID klayer = GetKicadLayer( elem.layer );
1675  if( klayer == UNDEFINED_LAYER )
1676  {
1677  wxLogWarning( wxString::Format( _( "Arc Keepout on Altium layer %d has no "
1678  "KiCad equivalent. Put it on Eco1_User instead" ),
1679  elem.layer ) );
1680  klayer = Eco1_User;
1681  }
1682  zone->SetLayer( klayer );
1683  }
1684 
1685  shape.TransformShapeWithClearanceToPolygon( *zone->Outline(), klayer, 0, ARC_HIGH_DEF,
1686  ERROR_INSIDE );
1687  zone->Outline()->Simplify( SHAPE_POLY_SET::PM_STRICTLY_SIMPLE ); // the outline is not a single polygon!
1688 
1690  ZONE::GetDefaultHatchPitch(), true );
1691  continue;
1692  }
1693 
1694  if( klayer == UNDEFINED_LAYER )
1695  {
1696  wxLogWarning( wxString::Format( _( "Arc on Altium layer %d has no KiCad equivalent. "
1697  "Put it on Eco1_User instead" ),
1698  elem.layer ) );
1699  klayer = Eco1_User;
1700  }
1701 
1702  if( klayer >= F_Cu && klayer <= B_Cu )
1703  {
1704  double angle = -NormalizeAngleDegreesPos( elem.endangle - elem.startangle );
1705  double startradiant = DEG2RAD( elem.startangle );
1706  wxPoint arcStartOffset = wxPoint( KiROUND( std::cos( startradiant ) * elem.radius ),
1707  -KiROUND( std::sin( startradiant ) * elem.radius ) );
1708 
1709  SHAPE_ARC shapeArc( elem.center, elem.center + arcStartOffset, angle, elem.width );
1710  PCB_ARC* arc = new PCB_ARC( m_board, &shapeArc );
1711  m_board->Add( arc, ADD_MODE::APPEND );
1712 
1713  arc->SetWidth( elem.width );
1714  arc->SetLayer( klayer );
1715  arc->SetNetCode( GetNetCode( elem.net ) );
1716  }
1717  else
1718  {
1720  shape->SetCenter( elem.center );
1721  shape->SetWidth( elem.width );
1722  shape->SetLayer( klayer );
1723 
1724  if( elem.startangle == 0. && elem.endangle == 360. )
1725  { // TODO: other variants to define circle?
1726  shape->SetShape( PCB_SHAPE_TYPE::CIRCLE );
1727  shape->SetArcStart( elem.center - wxPoint( 0, elem.radius ) );
1728  }
1729  else
1730  {
1731  shape->SetShape( PCB_SHAPE_TYPE::ARC );
1732  shape->SetAngle( -NormalizeAngleDegreesPos( elem.endangle - elem.startangle ) * 10. );
1733 
1734  double startradiant = DEG2RAD( elem.startangle );
1735  wxPoint arcStartOffset = wxPoint( KiROUND( std::cos( startradiant ) * elem.radius ),
1736  -KiROUND( std::sin( startradiant ) * elem.radius ) );
1737  shape->SetArcStart( elem.center + arcStartOffset );
1738  }
1739 
1741  }
1742  }
1743 
1744  if( reader.GetRemainingBytes() != 0 )
1745  {
1746  THROW_IO_ERROR( "Arcs6 stream is not fully parsed" );
1747  }
1748 }
1749 
1750 
1751 void ALTIUM_PCB::ParsePads6Data( const CFB::CompoundFileReader& aReader,
1752  const CFB::COMPOUND_FILE_ENTRY* aEntry )
1753 {
1754  ALTIUM_PARSER reader( aReader, aEntry );
1755 
1756  while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
1757  {
1758  APAD6 elem( reader );
1759 
1760  // It is possible to place altium pads on non-copper layers -> we need to interpolate them using drawings!
1761  if( !IsAltiumLayerCopper( elem.layer ) && !IsAltiumLayerAPlane( elem.layer )
1762  && elem.layer != ALTIUM_LAYER::MULTI_LAYER )
1763  {
1764  HelperParsePad6NonCopper( elem );
1765  continue;
1766  }
1767 
1768  // Create Pad
1769  FOOTPRINT* footprint = nullptr;
1770 
1771  if( elem.component == ALTIUM_COMPONENT_NONE )
1772  {
1773  footprint = new FOOTPRINT( m_board ); // We cannot add a pad directly into the PCB
1774  m_board->Add( footprint, ADD_MODE::APPEND );
1775  footprint->SetPosition( elem.position );
1776  }
1777  else
1778  {
1779  if( m_components.size() <= elem.component )
1780  {
1781  THROW_IO_ERROR( wxString::Format( "Pads6 stream tries to access component id %d "
1782  "of %d existing components",
1783  elem.component,
1784  m_components.size() ) );
1785  }
1786  footprint = m_components.at( elem.component );
1787  }
1788 
1789  PAD* pad = new PAD( footprint );
1790  footprint->Add( pad, ADD_MODE::APPEND );
1791 
1792  pad->SetName( elem.name );
1793  pad->SetNetCode( GetNetCode( elem.net ) );
1794  pad->SetLocked( elem.is_locked );
1795 
1796  pad->SetPosition( elem.position );
1797  pad->SetOrientationDegrees( elem.direction );
1798  pad->SetLocalCoord();
1799 
1800  pad->SetSize( elem.topsize );
1801 
1802  if( elem.holesize == 0 )
1803  {
1804  pad->SetAttribute( PAD_ATTRIB::SMD );
1805  }
1806  else
1807  {
1808  if( elem.layer != ALTIUM_LAYER::MULTI_LAYER )
1809  {
1810  // TODO: I assume other values are possible as well?
1811  wxLogError( wxString::Format(
1812  "Pad '%s' of Footprint %s is not marked as multilayer, but it is an THT pad",
1813  elem.name, footprint->GetReference() ) );
1814  }
1815  pad->SetAttribute( elem.plated ? PAD_ATTRIB::PTH :
1816  PAD_ATTRIB::NPTH );
1817  if( !elem.sizeAndShape || elem.sizeAndShape->holeshape == ALTIUM_PAD_HOLE_SHAPE::ROUND )
1818  {
1820  pad->SetDrillSize( wxSize( elem.holesize, elem.holesize ) );
1821  }
1822  else
1823  {
1824  switch( elem.sizeAndShape->holeshape )
1825  {
1827  wxFAIL_MSG( "Round holes are handled before the switch" );
1828  break;
1829 
1831  wxLogWarning( wxString::Format( _( "Pad '%s' of Footprint %s has a square hole. "
1832  "KiCad does not support this yet" ),
1833  elem.name,
1834  footprint->GetReference() ) );
1836  pad->SetDrillSize( wxSize( elem.holesize, elem.holesize ) ); // Workaround
1837  // TODO: elem.sizeAndShape->slotsize was 0 in testfile. Either use holesize in this case or rect holes have a different id
1838  break;
1839 
1841  {
1843  double normalizedSlotrotation =
1844  NormalizeAngleDegreesPos( elem.sizeAndShape->slotrotation );
1845  if( normalizedSlotrotation == 0. || normalizedSlotrotation == 180. )
1846  {
1847  pad->SetDrillSize( wxSize( elem.sizeAndShape->slotsize, elem.holesize ) );
1848  }
1849  else
1850  {
1851  if( normalizedSlotrotation != 90. && normalizedSlotrotation != 270. )
1852  {
1853  wxLogWarning( wxString::Format( _( "Pad '%s' of Footprint %s has a "
1854  "hole-rotation of %f degree. KiCad "
1855  "only supports 90 degree angles" ),
1856  elem.name,
1857  footprint->GetReference(),
1858  normalizedSlotrotation ) );
1859  }
1860 
1861  pad->SetDrillSize( wxSize( elem.holesize, elem.sizeAndShape->slotsize ) );
1862  }
1863  }
1864  break;
1865 
1866  default:
1868  wxLogError( wxString::Format(
1869  "Pad '%s' of Footprint %s uses a hole of unknown kind %d", elem.name,
1870  footprint->GetReference(), elem.sizeAndShape->holeshape ) );
1872  pad->SetDrillSize( wxSize( elem.holesize, elem.holesize ) ); // Workaround
1873  break;
1874  }
1875  }
1876 
1877  if( elem.sizeAndShape )
1878  {
1879  pad->SetOffset( elem.sizeAndShape->holeoffset[0] );
1880  }
1881  }
1882 
1883  if( elem.padmode != ALTIUM_PAD_MODE::SIMPLE )
1884  {
1885  wxLogWarning( wxString::Format(
1886  _( "Pad '%s' of Footprint %s uses a complex pad stack (kind %d), which is not supported yet" ),
1887  elem.name, footprint->GetReference(), elem.padmode ) );
1888  }
1889 
1890  switch( elem.topshape )
1891  {
1893  pad->SetShape( PAD_SHAPE::RECT );
1894  break;
1896  if( elem.sizeAndShape
1897  && elem.sizeAndShape->alt_shape[0] == ALTIUM_PAD_SHAPE_ALT::ROUNDRECT )
1898  {
1899  pad->SetShape( PAD_SHAPE::ROUNDRECT ); // 100 = round, 0 = rectangular
1900  double ratio = elem.sizeAndShape->cornerradius[0] / 200.;
1901  pad->SetRoundRectRadiusRatio( ratio );
1902  }
1903  else if( elem.topsize.x == elem.topsize.y )
1904  {
1905  pad->SetShape( PAD_SHAPE::CIRCLE );
1906  }
1907  else
1908  {
1909  pad->SetShape( PAD_SHAPE::OVAL );
1910  }
1911  break;
1913  pad->SetShape( PAD_SHAPE::CHAMFERED_RECT );
1914  pad->SetChamferPositions( RECT_CHAMFER_ALL );
1915  pad->SetChamferRectRatio( 0.25 );
1916  break;
1918  default:
1919  wxLogError( wxString::Format( "Pad '%s' of Footprint %s uses a unknown pad-shape",
1920  elem.name, footprint->GetReference() ) );
1921  break;
1922  }
1923 
1924  switch( elem.layer )
1925  {
1927  pad->SetLayer( F_Cu );
1928  pad->SetLayerSet( PAD::SMDMask() );
1929  break;
1931  pad->SetLayer( B_Cu );
1932  pad->SetLayerSet( FlipLayerMask( PAD::SMDMask() ) );
1933  break;
1935  pad->SetLayerSet( elem.plated ? PAD::PTHMask() : PAD::UnplatedHoleMask() );
1936  break;
1937  default:
1938  PCB_LAYER_ID klayer = GetKicadLayer( elem.layer );
1939  pad->SetLayer( klayer );
1940  pad->SetLayerSet( LSET( 1, klayer ) );
1941  break;
1942  }
1943 
1945  {
1946  pad->SetLocalSolderPasteMargin( elem.pastemaskexpansionmanual );
1947  }
1948 
1950  {
1951  pad->SetLocalSolderMaskMargin( elem.soldermaskexpansionmanual );
1952  }
1953 
1954  if( elem.is_tent_top )
1955  {
1956  pad->SetLayerSet( pad->GetLayerSet().reset( F_Mask ) );
1957  }
1958  if( elem.is_tent_bottom )
1959  {
1960  pad->SetLayerSet( pad->GetLayerSet().reset( B_Mask ) );
1961  }
1962  }
1963 
1964  if( reader.GetRemainingBytes() != 0 )
1965  {
1966  THROW_IO_ERROR( "Pads6 stream is not fully parsed" );
1967  }
1968 }
1969 
1970 
1972 {
1973  PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
1974 
1975  if( klayer == UNDEFINED_LAYER )
1976  {
1977  wxLogWarning( wxString::Format( _( "Non-Copper Pad on Altium layer %d has no KiCad "
1978  "equivalent. Put it on Eco1_User instead" ),
1979  aElem.layer ) );
1980  klayer = Eco1_User;
1981  }
1982 
1983  if( aElem.net != ALTIUM_NET_UNCONNECTED )
1984  {
1985  wxLogError( wxString::Format( "Non-Copper Pad '%s' is connected to a net. This is not "
1986  "supported",
1987  aElem.name ) );
1988  }
1989 
1990  if( aElem.holesize != 0 )
1991  {
1992  wxLogError( wxString::Format( _( "Non-Copper Pad '%s' has a hole. This should not happen" ),
1993  aElem.name ) );
1994  }
1995 
1996  if( aElem.padmode != ALTIUM_PAD_MODE::SIMPLE )
1997  {
1998  wxLogWarning( wxString::Format( _( "Non-Copper Pad '%s' uses a complex pad stack (kind %d). "
1999  "This should not happen" ),
2000  aElem.name,
2001  aElem.padmode ) );
2002  }
2003 
2004  switch( aElem.topshape )
2005  {
2007  {
2008  // filled rect
2011  shape->SetFilled( true );
2012  shape->SetLayer( klayer );
2013  shape->SetWidth( 0 );
2014 
2015  shape->SetPolyPoints( { aElem.position + wxPoint( aElem.topsize.x / 2, aElem.topsize.y / 2 ),
2016  aElem.position + wxPoint( aElem.topsize.x / 2, -aElem.topsize.y / 2 ),
2017  aElem.position + wxPoint( -aElem.topsize.x / 2, -aElem.topsize.y / 2 ),
2018  aElem.position + wxPoint( -aElem.topsize.x / 2, aElem.topsize.y / 2 ) } );
2019 
2020  if( aElem.direction != 0 )
2021  shape->Rotate( aElem.position, aElem.direction * 10 );
2022 
2023  HelperDrawsegmentSetLocalCoord( shape, aElem.component );
2024  }
2025  break;
2026 
2028  if( aElem.sizeAndShape
2029  && aElem.sizeAndShape->alt_shape[0] == ALTIUM_PAD_SHAPE_ALT::ROUNDRECT )
2030  {
2031  // filled roundrect
2032  int cornerradius = aElem.sizeAndShape->cornerradius[0];
2033  int offset = ( std::min( aElem.topsize.x, aElem.topsize.y ) * cornerradius ) / 200;
2034 
2036  shape->SetLayer( klayer );
2037  shape->SetWidth( offset * 2 );
2038 
2039  if( cornerradius < 100 )
2040  {
2041  int offsetX = aElem.topsize.x / 2 - offset;
2042  int offsetY = aElem.topsize.y / 2 - offset;
2043 
2044  wxPoint p11 = aElem.position + wxPoint( offsetX, offsetY );
2045  wxPoint p12 = aElem.position + wxPoint( offsetX, -offsetY );
2046  wxPoint p22 = aElem.position + wxPoint( -offsetX, -offsetY );
2047  wxPoint p21 = aElem.position + wxPoint( -offsetX, offsetY );
2048 
2050  shape->SetFilled( true );
2051  shape->SetPolyPoints( { p11, p12, p22, p21 } );
2052  }
2053  else if( aElem.topsize.x == aElem.topsize.y )
2054  {
2055  // circle
2056  shape->SetShape( PCB_SHAPE_TYPE::CIRCLE );
2057  shape->SetFilled( true );
2058  shape->SetCenter( aElem.position );
2059  shape->SetWidth( aElem.topsize.x / 2 );
2060  shape->SetArcStart( aElem.position - wxPoint( 0, aElem.topsize.x / 4 ) );
2061  }
2062  else if( aElem.topsize.x < aElem.topsize.y )
2063  {
2064  // short vertical line
2066  wxPoint pointOffset( 0, ( aElem.topsize.y - aElem.topsize.x ) / 2 );
2067  shape->SetStart( aElem.position + pointOffset );
2068  shape->SetEnd( aElem.position - pointOffset );
2069  }
2070  else
2071  {
2072  // short horizontal line
2074  wxPoint pointOffset( ( aElem.topsize.x - aElem.topsize.y ) / 2, 0 );
2075  shape->SetStart( aElem.position + pointOffset );
2076  shape->SetEnd( aElem.position - pointOffset );
2077  }
2078 
2079  if( aElem.direction != 0 )
2080  shape->Rotate( aElem.position, aElem.direction * 10 );
2081 
2082  HelperDrawsegmentSetLocalCoord( shape, aElem.component );
2083  }
2084  else if( aElem.topsize.x == aElem.topsize.y )
2085  {
2086  // filled circle
2088  shape->SetShape( PCB_SHAPE_TYPE::CIRCLE );
2089  shape->SetFilled( true );
2090  shape->SetLayer( klayer );
2091  shape->SetCenter( aElem.position );
2092  shape->SetWidth( aElem.topsize.x / 2 );
2093  shape->SetArcStart( aElem.position - wxPoint( 0, aElem.topsize.x / 4 ) );
2094  HelperDrawsegmentSetLocalCoord( shape, aElem.component );
2095  }
2096  else
2097  {
2098  // short line
2101  shape->SetLayer( klayer );
2102  shape->SetWidth( std::min( aElem.topsize.x, aElem.topsize.y ) );
2103 
2104  if( aElem.topsize.x < aElem.topsize.y )
2105  {
2106  wxPoint offset( 0, ( aElem.topsize.y - aElem.topsize.x ) / 2 );
2107  shape->SetStart( aElem.position + offset );
2108  shape->SetEnd( aElem.position - offset );
2109  }
2110  else
2111  {
2112  wxPoint offset( ( aElem.topsize.x - aElem.topsize.y ) / 2, 0 );
2113  shape->SetStart( aElem.position + offset );
2114  shape->SetEnd( aElem.position - offset );
2115  }
2116 
2117  if( aElem.direction != 0 )
2118  shape->Rotate( aElem.position, aElem.direction * 10. );
2119 
2120  HelperDrawsegmentSetLocalCoord( shape, aElem.component );
2121  }
2122  break;
2123 
2125  {
2126  // filled octagon
2129  shape->SetFilled( true );
2130  shape->SetLayer( klayer );
2131  shape->SetWidth( 0 );
2132 
2133  wxPoint p11 = aElem.position + wxPoint( aElem.topsize.x / 2, aElem.topsize.y / 2 );
2134  wxPoint p12 = aElem.position + wxPoint( aElem.topsize.x / 2, -aElem.topsize.y / 2 );
2135  wxPoint p22 = aElem.position + wxPoint( -aElem.topsize.x / 2, -aElem.topsize.y / 2 );
2136  wxPoint p21 = aElem.position + wxPoint( -aElem.topsize.x / 2, aElem.topsize.y / 2 );
2137 
2138  int chamfer = std::min( aElem.topsize.x, aElem.topsize.y ) / 4;
2139  wxPoint chamferX( chamfer, 0 );
2140  wxPoint chamferY( 0, chamfer );
2141 
2142  shape->SetPolyPoints( { p11 - chamferX, p11 - chamferY, p12 + chamferY, p12 - chamferX,
2143  p22 + chamferX, p22 + chamferY, p21 - chamferY, p21 + chamferX } );
2144 
2145  if( aElem.direction != 0. )
2146  shape->Rotate( aElem.position, aElem.direction * 10 );
2147 
2148  HelperDrawsegmentSetLocalCoord( shape, aElem.component );
2149  }
2150  break;
2151 
2153  default:
2154  wxLogError(
2155  wxString::Format( "Non-Copper Pad '%s' uses a unknown pad-shape", aElem.name ) );
2156  break;
2157  }
2158 }
2159 
2160 void ALTIUM_PCB::ParseVias6Data( const CFB::CompoundFileReader& aReader,
2161  const CFB::COMPOUND_FILE_ENTRY* aEntry )
2162 {
2163  ALTIUM_PARSER reader( aReader, aEntry );
2164 
2165  while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
2166  {
2167  AVIA6 elem( reader );
2168 
2169  PCB_VIA* via = new PCB_VIA( m_board );
2171 
2172  via->SetPosition( elem.position );
2173  via->SetWidth( elem.diameter );
2174  via->SetDrill( elem.holesize );
2175  via->SetNetCode( GetNetCode( elem.net ) );
2176  via->SetLocked( elem.is_locked );
2177 
2178  bool start_layer_outside = elem.layer_start == ALTIUM_LAYER::TOP_LAYER
2180  bool end_layer_outside = elem.layer_end == ALTIUM_LAYER::TOP_LAYER
2182 
2183  if( start_layer_outside && end_layer_outside )
2184  {
2185  via->SetViaType( VIATYPE::THROUGH );
2186  }
2187  else if( ( !start_layer_outside ) && ( !end_layer_outside ) )
2188  {
2189  via->SetViaType( VIATYPE::BLIND_BURIED );
2190  }
2191  else
2192  {
2193  via->SetViaType( VIATYPE::MICROVIA ); // TODO: always a microvia?
2194  }
2195 
2196  PCB_LAYER_ID start_klayer = GetKicadLayer( elem.layer_start );
2197  PCB_LAYER_ID end_klayer = GetKicadLayer( elem.layer_end );
2198  if( !IsCopperLayer( start_klayer ) || !IsCopperLayer( end_klayer ) )
2199  {
2200  wxLogError( wxString::Format(
2201  "Via from layer %d <-> %d uses non-copper layer. This should not happen.",
2202  elem.layer_start, elem.layer_end ) );
2203  continue; // just assume through-hole instead.
2204  }
2205 
2206  // we need VIATYPE set!
2207  via->SetLayerPair( start_klayer, end_klayer );
2208  }
2209 
2210  if( reader.GetRemainingBytes() != 0 )
2211  {
2212  THROW_IO_ERROR( "Vias6 stream is not fully parsed" );
2213  }
2214 }
2215 
2216 void ALTIUM_PCB::ParseTracks6Data( const CFB::CompoundFileReader& aReader,
2217  const CFB::COMPOUND_FILE_ENTRY* aEntry )
2218 {
2219  ALTIUM_PARSER reader( aReader, aEntry );
2220 
2221  while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
2222  {
2223  ATRACK6 elem( reader );
2224 
2225  if( elem.is_polygonoutline || elem.subpolyindex != ALTIUM_POLYGON_NONE )
2226  continue;
2227 
2228  // element in plane is in fact substracted from the plane. Already done by Altium?
2229  //if( IsAltiumLayerAPlane( elem.layer ) )
2230  // continue;
2231 
2232  PCB_LAYER_ID klayer = GetKicadLayer( elem.layer );
2233 
2234  if( elem.is_keepout || IsAltiumLayerAPlane( elem.layer ) )
2235  {
2236  PCB_SHAPE shape( nullptr ); // just a helper to get the graphic
2238  shape.SetStart( elem.start );
2239  shape.SetEnd( elem.end );
2240  shape.SetWidth( elem.width );
2241 
2242  ZONE* zone = new ZONE( m_board );
2243  m_board->Add( zone, ADD_MODE::APPEND );
2244 
2245  zone->SetFillVersion( 6 );
2246  zone->SetIsRuleArea( true );
2247  zone->SetDoNotAllowTracks( false );
2248  zone->SetDoNotAllowVias( false );
2249  zone->SetDoNotAllowPads( false );
2250  zone->SetDoNotAllowFootprints( false );
2251  zone->SetDoNotAllowCopperPour( true );
2252 
2253  if( elem.layer == ALTIUM_LAYER::MULTI_LAYER )
2254  {
2255  zone->SetLayer( F_Cu );
2256  zone->SetLayerSet( LSET::AllCuMask() );
2257  }
2258  else
2259  {
2260  PCB_LAYER_ID klayer = GetKicadLayer( elem.layer );
2261  if( klayer == UNDEFINED_LAYER )
2262  {
2263  wxLogWarning( wxString::Format(
2264  _( "Track Keepout on Altium layer %d has no KiCad equivalent. Put it on Eco1_User instead" ),
2265  elem.layer ) );
2266  klayer = Eco1_User;
2267  }
2268  zone->SetLayer( klayer );
2269  }
2270 
2271  shape.TransformShapeWithClearanceToPolygon( *zone->Outline(), klayer, 0, ARC_HIGH_DEF,
2272  ERROR_INSIDE );
2273 
2275  ZONE::GetDefaultHatchPitch(), true );
2276  continue;
2277  }
2278 
2279  if( klayer == UNDEFINED_LAYER )
2280  {
2281  wxLogWarning( wxString::Format(
2282  _( "Track on Altium layer %d has no KiCad equivalent. Put it on Eco1_User instead" ),
2283  elem.layer ) );
2284  klayer = Eco1_User;
2285  }
2286 
2287  if( klayer >= F_Cu && klayer <= B_Cu )
2288  {
2289  PCB_TRACK* track = new PCB_TRACK( m_board );
2290  m_board->Add( track, ADD_MODE::APPEND );
2291 
2292  track->SetStart( elem.start );
2293  track->SetEnd( elem.end );
2294  track->SetWidth( elem.width );
2295  track->SetLayer( klayer );
2296  track->SetNetCode( GetNetCode( elem.net ) );
2297  }
2298  else
2299  {
2302  shape->SetStart( elem.start );
2303  shape->SetEnd( elem.end );
2304  shape->SetWidth( elem.width );
2305  shape->SetLayer( klayer );
2307  }
2308 
2309  reader.SkipSubrecord();
2310  }
2311 
2312  if( reader.GetRemainingBytes() != 0 )
2313  {
2314  THROW_IO_ERROR( "Tracks6 stream is not fully parsed" );
2315  }
2316 }
2317 
2318 void ALTIUM_PCB::ParseTexts6Data( const CFB::CompoundFileReader& aReader,
2319  const CFB::COMPOUND_FILE_ENTRY* aEntry )
2320 {
2321  ALTIUM_PARSER reader( aReader, aEntry );
2322 
2323  while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
2324  {
2325  ATEXT6 elem( reader );
2326 
2327  if( elem.fonttype == ALTIUM_TEXT_TYPE::BARCODE )
2328  {
2329  wxLogWarning( wxString::Format(
2330  _( "Ignore Barcode on Altium layer %d because it is not supported right now." ),
2331  elem.layer ) );
2332  continue;
2333  }
2334 
2335  // TODO: better approach to select if item belongs to a FOOTPRINT
2336  EDA_TEXT* tx = nullptr;
2337  BOARD_ITEM* itm = nullptr;
2338 
2339  if( elem.component == ALTIUM_COMPONENT_NONE )
2340  {
2341  PCB_TEXT* pcbText = new PCB_TEXT( m_board );
2342  tx = pcbText;
2343  itm = pcbText;
2344  m_board->Add( pcbText, ADD_MODE::APPEND );
2345  }
2346  else
2347  {
2348  if( m_components.size() <= elem.component )
2349  {
2350  THROW_IO_ERROR( wxString::Format( "Texts6 stream tries to access component id %d "
2351  "of %d existing components",
2352  elem.component,
2353  m_components.size() ) );
2354  }
2355 
2356  FOOTPRINT* footprint = m_components.at( elem.component );
2357  FP_TEXT* fpText;
2358 
2359  if( elem.isDesignator )
2360  {
2361  fpText = &footprint->Reference();
2362  }
2363  else if( elem.isComment )
2364  {
2365  fpText = &footprint->Value();
2366  }
2367  else
2368  {
2369  fpText = new FP_TEXT( footprint );
2370  footprint->Add( fpText, ADD_MODE::APPEND );
2371  }
2372 
2373  fpText->SetKeepUpright( false );
2374 
2375  tx = fpText;
2376  itm = fpText;
2377  }
2378 
2379  wxString trimmedText = elem.text.Trim();
2380  if( !elem.isDesignator && trimmedText.CmpNoCase( ".Designator" ) == 0 )
2381  {
2382  tx->SetText( "${REFERENCE}" );
2383  }
2384  else if( !elem.isComment && trimmedText.CmpNoCase( ".Comment" ) == 0 )
2385  {
2386  tx->SetText( "${VALUE}" );
2387  }
2388  else if( trimmedText.CmpNoCase( ".Layer_Name" ) == 0 )
2389  {
2390  tx->SetText( "${LAYER}" );
2391  }
2392  else
2393  {
2394  tx->SetText( elem.text );
2395  }
2396 
2397  itm->SetPosition( elem.position );
2398  tx->SetTextAngle( elem.rotation * 10. );
2399 
2400  if( elem.component != ALTIUM_COMPONENT_NONE )
2401  {
2402  FP_TEXT* fpText = dynamic_cast<FP_TEXT*>( tx );
2403 
2404  if( fpText )
2405  {
2406  FOOTPRINT* parentFootprint = static_cast<FOOTPRINT*>( fpText->GetParent() );
2407  double orientation = parentFootprint->GetOrientation();
2408 
2409  fpText->SetTextAngle( fpText->GetTextAngle() - orientation );
2410  fpText->SetLocalCoord();
2411  }
2412  }
2413 
2414  PCB_LAYER_ID klayer = GetKicadLayer( elem.layer );
2415 
2416  if( klayer == UNDEFINED_LAYER )
2417  {
2418  wxLogWarning( wxString::Format(
2419  _( "Text on Altium layer %d has no KiCad equivalent. Put it on Eco1_User instead" ),
2420  elem.layer ) );
2421  klayer = Eco1_User;
2422  }
2423 
2424  itm->SetLayer( klayer );
2425 
2426  if( elem.fonttype == ALTIUM_TEXT_TYPE::TRUETYPE )
2427  {
2428  // TODO: why is this required? Somehow, truetype size is calculated differently
2429  tx->SetTextSize( wxSize( elem.height / 2, elem.height / 2 ) );
2430  }
2431  else
2432  {
2433  tx->SetTextSize( wxSize( elem.height, elem.height ) ); // TODO: parse text width
2434  }
2435 
2436  tx->SetTextThickness( elem.strokewidth );
2437  tx->SetBold( elem.isBold );
2438  tx->SetItalic( elem.isItalic );
2439  tx->SetMirrored( elem.isMirrored );
2440 
2441  if( elem.isDesignator || elem.isComment ) // That's just a bold assumption
2442  {
2445  }
2446  else
2447  {
2448  switch( elem.textposition )
2449  {
2454  break;
2459  break;
2464  break;
2465  default:
2466  wxLogError( "Unexpected horizontal Text Position. This should never happen." );
2467  break;
2468  }
2469 
2470  switch( elem.textposition )
2471  {
2476  break;
2481  break;
2486  break;
2487  default:
2488  wxLogError( "Unexpected vertical text position. This should never happen." );
2489  break;
2490  }
2491  }
2492  }
2493 
2494  if( reader.GetRemainingBytes() != 0 )
2495  {
2496  THROW_IO_ERROR( "Texts6 stream is not fully parsed" );
2497  }
2498 }
2499 
2500 void ALTIUM_PCB::ParseFills6Data( const CFB::CompoundFileReader& aReader,
2501  const CFB::COMPOUND_FILE_ENTRY* aEntry )
2502 {
2503  ALTIUM_PARSER reader( aReader, aEntry );
2504 
2505  while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
2506  {
2507  AFILL6 elem( reader );
2508 
2509  wxPoint p11( elem.pos1.x, elem.pos1.y );
2510  wxPoint p12( elem.pos1.x, elem.pos2.y );
2511  wxPoint p22( elem.pos2.x, elem.pos2.y );
2512  wxPoint p21( elem.pos2.x, elem.pos1.y );
2513 
2514  wxPoint center( ( elem.pos1.x + elem.pos2.x ) / 2, ( elem.pos1.y + elem.pos2.y ) / 2 );
2515 
2516  PCB_LAYER_ID klayer = GetKicadLayer( elem.layer );
2517  if( klayer == UNDEFINED_LAYER )
2518  {
2519  wxLogWarning( wxString::Format( _( "Fill on Altium layer %d has no KiCad equivalent. "
2520  "Put it on Eco1_User instead" ),
2521  elem.layer ) );
2522  klayer = Eco1_User;
2523  }
2524 
2525  if( elem.is_keepout || elem.net != ALTIUM_NET_UNCONNECTED )
2526  {
2527  ZONE* zone = new ZONE( m_board );
2528  m_board->Add( zone, ADD_MODE::APPEND );
2529 
2530  zone->SetFillVersion( 6 );
2531  zone->SetNetCode( GetNetCode( elem.net ) );
2532  zone->SetLayer( klayer );
2533  zone->SetPosition( elem.pos1 );
2534  zone->SetPriority( 1000 );
2535 
2536  const int outlineIdx = -1; // this is the id of the copper zone main outline
2537  zone->AppendCorner( p11, outlineIdx );
2538  zone->AppendCorner( p12, outlineIdx );
2539  zone->AppendCorner( p22, outlineIdx );
2540  zone->AppendCorner( p21, outlineIdx );
2541 
2542  // should be correct?
2543  zone->SetLocalClearance( 0 );
2545 
2546  if( elem.is_keepout )
2547  {
2548  zone->SetIsRuleArea( true );
2549  zone->SetDoNotAllowTracks( false );
2550  zone->SetDoNotAllowVias( false );
2551  zone->SetDoNotAllowPads( false );
2552  zone->SetDoNotAllowFootprints( false );
2553  zone->SetDoNotAllowCopperPour( true );
2554  }
2555 
2556  if( elem.rotation != 0. )
2557  zone->Rotate( center, elem.rotation * 10 );
2558 
2560  ZONE::GetDefaultHatchPitch(), true );
2561  }
2562  else
2563  {
2564  PCB_SHAPE* shape = new PCB_SHAPE( m_board );
2565  m_board->Add( shape, ADD_MODE::APPEND );
2566 
2568  shape->SetFilled( true );
2569  shape->SetLayer( klayer );
2570  shape->SetWidth( 0 );
2571 
2572  shape->SetPolyPoints( { p11, p12, p22, p21 } );
2573 
2574  if( elem.rotation != 0. )
2575  shape->Rotate( center, elem.rotation * 10 );
2576  }
2577  }
2578 
2579  if( reader.GetRemainingBytes() != 0 )
2580  {
2581  THROW_IO_ERROR( "Fills6 stream is not fully parsed" );
2582  }
2583 }
void SetMirrored(bool isMirrored)
Definition: eda_text.h:195
double EuclideanNorm(const wxPoint &vector)
Euclidean norm of a 2D vector.
Definition: trigo.h:148
void SetReference(const wxString &aReference)
Definition: footprint.h:430
void HelperParseDimensions6Datum(const ADIMENSION6 &aElem)
static LSET AllCuMask(int aCuLayerCount=MAX_CU_LAYERS)
Return a mask holding the requested number of Cu PCB_LAYER_IDs.
Definition: lset.cpp:750
uint32_t textlinewidth
uint32_t linewidth
FP_3DMODEL::VECTOR3D modelPosition
int32_t soldermaskexpansionmanual
ALTIUM_RULE_KIND kind
double rotation
uint32_t holesize
void SetUnits(EDA_UNITS aUnits)
void SetModified()
Definition: eda_item.cpp:65
uint32_t width
void Parse(const CFB::CompoundFileReader &aReader, const std::map< ALTIUM_PCB_DIR, std::string > &aFileMapping)
Definition: altium_pcb.cpp:323
int planeclearanceClearance
void SetHatchThickness(int aThickness)
Definition: zone.h:251
void ParseVias6Data(const CFB::CompoundFileReader &aReader, const CFB::COMPOUND_FILE_ENTRY *aEntry)
wxString name
bool isDesignator
const PAGE_INFO & GetPageSettings() const
Definition: board.h:535
LSET FlipLayerMask(LSET aMask, int aCopperLayersCount)
Calculate the mask layer when flipping a footprint.
Definition: lset.cpp:567
void ParseShapeBasedRegions6Data(const CFB::CompoundFileReader &aReader, const CFB::COMPOUND_FILE_ENTRY *aEntry)
ALTIUM_PAD_SHAPE topshape
bool HasFilledPolysForLayer(PCB_LAYER_ID aLayer) const
Definition: zone.h:624
std::vector< ZONE * > m_polygons
Definition: altium_pcb.h:187
double GetOrientationRadians() const
Definition: footprint.h:183
wxPoint pos1
double GetLineLength(const wxPoint &aPointA, const wxPoint &aPointB)
Return the length of a line segment defined by aPointA and aPointB.
Definition: trigo.h:223
ALTIUM_LAYER layer
wxPoint m_GridOrigin
origin for grid offsets
const ARULE6 * GetRule(ALTIUM_RULE_KIND aKind, const wxString &aName) const
Definition: altium_pcb.cpp:498
std::list< FP_3DMODEL > & Models()
Definition: footprint.h:173
void SetHeight(int aHeight)
Set the distance from the feature points to the crossbar line.
void SetDoNotAllowTracks(bool aEnable)
Definition: zone.h:742
wxPoint start
wxSize topsize
SHAPE_POLY_SET & GetPolyShape()
Definition: pcb_shape.h:239
bool m_LegacyNetclassesLoaded
True if netclasses were loaded from the file.
Definition: board.h:272
void ParseComponents6Data(const CFB::CompoundFileReader &aReader, const CFB::COMPOUND_FILE_ENTRY *aEntry)
Definition: altium_pcb.cpp:792
ALTIUM_LAYER layer
void ParseRules6Data(const CFB::CompoundFileReader &aReader, const CFB::COMPOUND_FILE_ENTRY *aEntry)
void BooleanAdd(const SHAPE_POLY_SET &b, POLYGON_MODE aFastMode)
Perform boolean polyset difference For aFastMode meaning, see function booleanOp.
Manage layers needed to make a physical board.
ALTIUM_DIMENSION_KIND kind
virtual void SetLayer(PCB_LAYER_ID aLayer)
Set the layer this item is on.
Definition: board_item.h:192
virtual void SetPosition(const wxPoint &aPos)
Definition: eda_item.h:253
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition: board_item.h:80
void SetBorderDisplayStyle(ZONE_BORDER_DISPLAY_STYLE aHatchStyle, int aHatchPitch, bool aRebuildHatch)
Set all hatch parameters for the zone.
Definition: zone.cpp:898
ALTIUM_REGION_KIND kind
Definition: board.h:72
int GetX() const
Definition: eda_rect.h:98
void SetEnd(const wxPoint &aEnd)
Definition: pcb_track.h:104
wxPoint position
const EDA_RECT GetBoardEdgesBoundingBox() const
Return the board bounding box calculated using exclusively the board edges (graphics on Edge....
Definition: board.h:742
void ParsePolygons6Data(const CFB::CompoundFileReader &aReader, const CFB::COMPOUND_FILE_ENTRY *aEntry)
PCB_TEXT & Text()
void SetFilled(bool aFlag)
Definition: pcb_shape.h:73
OPT_VECTOR2I Intersect(const SEG &aSeg, bool aIgnoreEndpoints=false, bool aLines=false) const
Compute intersection point of segment (this) with segment aSeg.
Definition: seg.cpp:154
void SetTextAngle(double aAngle) override
Definition: fp_text.cpp:73
double startangle
size_t GetRemainingBytes() const
void SetLayerSet(LSET aLayerSet) override
Definition: zone.cpp:246
uint16_t component
SHAPE_POLY_SET * Outline()
Definition: zone.h:317
void SetItalic(bool isItalic)
Definition: eda_text.h:186
bool IsEmpty() const
#define PROJECT_VAR_NAME
A variable name whose value holds the current project directory.
Definition: project.h:38
Smd pad, appears on the solder paste layer (default)
int32_t polygonconnectAirgapwidth
wxPoint center
void SetVisible(bool aVisible)
Definition: eda_text.h:192
int GetWidth() const
Definition: eda_rect.h:109
ALTIUM_LAYER layer
double GetOrientation() const
Definition: footprint.h:181
double GetTextAngle() const
Definition: eda_text.h:181
void SetCopperLayerCount(int aCount)
Definition: board.cpp:459
bool SetNetCode(int aNetCode, bool aNoAssert)
Set net using a net code.
std::vector< std::vector< ALTIUM_VERTICE > > holes
wxString sourcedesignator
const uint16_t ALTIUM_POLYGON_NONE
ALTIUM_LAYER layer
ALTIUM_LAYER layer_end
void ParseComponentsBodies6Data(const CFB::CompoundFileReader &aReader, const CFB::COMPOUND_FILE_ENTRY *aEntry)
Definition: altium_pcb.cpp:835
void SetOrientationDegrees(double aOrientation)
Definition: footprint.h:180
ALTIUM_PAD_RULE soldermaskexpansionmode
ALTIUM_LAYER layer
void Rotate(double aAngle, const VECTOR2I &aCenter={ 0, 0 }) override
Rotate all vertices by a given angle.
void SetTextSize(const wxSize &aNewSize)
Definition: eda_text.h:244
virtual void SetLocked(bool aLocked)
Modify the 'lock' status for of the item.
Definition: board_item.h:255
uint16_t net
bool SetLayerType(PCB_LAYER_ID aLayer, LAYER_T aLayerType)
Change the type of the layer given by aLayer.
Definition: board.cpp:408
wxString sourcefootprintlibrary
void RotatePoint(int *pX, int *pY, double angle)
Definition: trigo.cpp:228
const double startangle
A logical library item identifier and consists of various portions much like a URI.
Definition: lib_id.h:51
wxString name
virtual void Rotate(const wxPoint &aRotCentre, double aAngle) override
Rotate this object.
Definition: pcb_shape.cpp:200
LIB_ID AltiumToKiCadLibID(wxString aLibName, wxString aLibReference)
PCB_LAYER_ID GetKicadLayer(ALTIUM_LAYER aAltiumLayer) const
Definition: altium_pcb.cpp:207
wxString text
int PointCount() const
Function PointCount()
void SetPriority(unsigned aPriority)
Definition: zone.h:117
const wxString & GetFileName() const
Definition: board.h:228
VECTOR3D m_Offset
3D model offset (mm)
Definition: footprint.h:95
void ParseArcs6Data(const CFB::CompoundFileReader &aReader, const CFB::COMPOUND_FILE_ENTRY *aEntry)
wxString scope2expr
BOARD_DESIGN_SETTINGS & GetDesignSettings() const
Definition: board.cpp:588
bool is_polygonoutline
uint32_t height
void SetFillMode(ZONE_FILL_MODE aFillMode)
Definition: zone.h:180
std::map< ALTIUM_LAYER, ZONE * > m_outer_plane
Definition: altium_pcb.h:193
bool Contains(const wxPoint &aPoint) const
Definition: eda_rect.cpp:57
const ARULE6 * GetRuleDefault(ALTIUM_RULE_KIND aKind) const
Definition: altium_pcb.cpp:515
void Append(int aX, int aY, bool aAllowDuplication=false)
Function Append()
ALTIUM_CONNECT_STYLE polygonconnectStyle
Plated through hole pad.
int GetNetCode(uint16_t aId) const
Definition: altium_pcb.cpp:481
FP_TEXT & Value()
read/write accessors:
Definition: footprint.h:457
std::vector< wxString > names
void SetLineThickness(int aWidth)
For better understanding of the points that make a dimension:
int32_t trackwidth
std::vector< wxPoint > textPoint
void SetIsRuleArea(bool aEnable)
Definition: zone.h:739
polygon (not yet used for tracks, but could be in microwave apps)
wxString textformat
FP_TEXT & Reference()
Definition: footprint.h:458
A mix-in class (via multiple inheritance) that handles texts such as labels, parts,...
Definition: eda_text.h:119
std::map< wxString, wxString > m_models
Definition: altium_pcb.h:188
void Rotate(const wxPoint &aCentre, double aAngle) override
Move the outlines.
Definition: zone.cpp:709
int GetLineThickness(PCB_LAYER_ID aLayer) const
Return the default graphic segment thickness from the layer class for the given layer.
uint16_t subpolyindex
void SetLayer(PCB_LAYER_ID aLayer) override
Set the layer this item is on.
const CFB::COMPOUND_FILE_ENTRY * FindStream(const CFB::CompoundFileReader &aReader, const char *aStreamName)
void Add(BOARD_ITEM *aItem, ADD_MODE aMode=ADD_MODE::INSERT) override
Adds an item to the container.
Definition: board.cpp:606
void ParsePads6Data(const CFB::CompoundFileReader &aReader, const CFB::COMPOUND_FILE_ENTRY *aEntry)
void SetClosed(bool aClosed)
Function SetClosed()
void HelperParseDimensions6Leader(const ADIMENSION6 &aElem)
Definition: altium_pcb.cpp:990
std::vector< FOOTPRINT * > m_components
Definition: altium_pcb.h:186
void ParseRegions6Data(const CFB::CompoundFileReader &aReader, const CFB::COMPOUND_FILE_ENTRY *aEntry)
like PAD_PTH, but not plated
size_t ReadAndSetSubrecordLength()
BOARD_STACKUP & GetStackupDescriptor()
PCB_LAYER_ID
A quick note on layer IDs:
double NormalizeAngleDegreesPos(double Angle)
Normalize angle to be in the 0.0 .
Definition: trigo.h:296
BOARD * m_board
Definition: altium_pcb.h:185
ALTIUM_PAD_RULE pastemaskexpansionmode
wxPoint position
uint16_t subpolyindex
double direction
LSET is a set of PCB_LAYER_IDs.
std::map< ALTIUM_LAYER, PCB_LAYER_ID > m_layermap
Definition: altium_pcb.h:190
pads are covered by copper
void ParseDimensions6Data(const CFB::CompoundFileReader &aReader, const CFB::COMPOUND_FILE_ENTRY *aEntry)
ALTIUM_RULE_KIND
int GetMinThickness() const
Definition: zone.h:241
virtual void SetText(const wxString &aText)
Definition: eda_text.cpp:114
const EDA_RECT GetBoundingBox() const override
Definition: zone.cpp:322
void SetHatchGap(int aStep)
Definition: zone.h:254
double endangle
void Move(const VECTOR2I &aVector) override
wxString id
void ParseTexts6Data(const CFB::CompoundFileReader &aReader, const CFB::COMPOUND_FILE_ENTRY *aEntry)
const wxPoint position
static int GetDefaultHatchPitch()
Definition: zone.cpp:1092
static const wxChar * Name(PCB_LAYER_ID aLayerId)
Return the fixed name association with aLayerId.
Definition: lset.cpp:82
wxString NotSpecifiedPrm()
void SetDoNotAllowPads(bool aEnable)
Definition: zone.h:743
Represent a set of closed polygons.
virtual void SetEnd(const wxPoint &aPoint)
ALTIUM_CLASS_KIND kind
void SetVertJustify(EDA_TEXT_VJUSTIFY_T aType)
Definition: eda_text.h:209
ALTIUM_LAYER layer
const uint16_t ALTIUM_NET_UNCONNECTED
bool SetLayerName(PCB_LAYER_ID aLayer, const wxString &aLayerName)
Changes the name of the layer given by aLayer.
Definition: board.cpp:376
Mark the center of a circle or arc with a cross shape.
void ParseClasses6Data(const CFB::CompoundFileReader &aReader, const CFB::COMPOUND_FILE_ENTRY *aEntry)
Definition: altium_pcb.cpp:756
ALTIUM_PCB_DIR
Definition: altium_pcb.h:34
wxString name
void Deflate(int aAmount, int aCircleSegmentsCount, CORNER_STRATEGY aCornerStrategy=ROUND_ALL_CORNERS)
void HelperShapeLineChainFromAltiumVertices(SHAPE_LINE_CHAIN &aLine, const std::vector< ALTIUM_VERTICE > &aVertices)
Definition: altium_pcb.cpp:167
void ParseFileHeader(const CFB::CompoundFileReader &aReader, const CFB::COMPOUND_FILE_ENTRY *aEntry)
Definition: altium_pcb.cpp:532
static LSET PTHMask()
layer set for a through hole pad
Definition: pad.cpp:151
void SetStart(const wxPoint &aStart)
Definition: pcb_track.h:107
void ParseBoardRegionsData(const CFB::CompoundFileReader &aReader, const CFB::COMPOUND_FILE_ENTRY *aEntry)
const wxString & GetReference() const
Definition: footprint.h:421
#define _(s)
uint16_t component
void SetDoNotAllowVias(bool aEnable)
Definition: zone.h:741
double m_Opacity
Definition: footprint.h:96
NETCLASSES & GetNetClasses() const
uint32_t holesize
void Simplify(POLYGON_MODE aFastMode)
Handle a list of polygons defining a copper zone.
Definition: zone.h:57
wxPoint sheetpos
void SetMinThickness(int aMinThickness)
Definition: zone.h:242
void SetCenter(const wxPoint &aCenterPoint)
Definition: pcb_shape.h:199
void SetIsFilled(bool isFilled)
Definition: zone.h:232
const int32_t radius
void SetPrecision(int aPrecision)
uint16_t net
void Fracture(POLYGON_MODE aFastMode)
Convert a single outline slitted ("fractured") polygon into a set ouf outlines with holes.
int AddHole(const SHAPE_LINE_CHAIN &aHole, int aOutline=-1)
Return the area of this poly set.
void HelperParseDimensions6Center(const ADIMENSION6 &aElem)
int GetHeight() const
Definition: eda_rect.h:110
const std::vector< BOARD_STACKUP_ITEM * > & GetList() const
void SetFilledPolysList(PCB_LAYER_ID aLayer, const SHAPE_POLY_SET &aPolysList)
Set the list of filled polygons.
Definition: zone.h:647
size_t m_num_nets
Definition: altium_pcb.h:189
int m_highest_pour_index
Altium stores pour order across all layers.
Definition: altium_pcb.h:196
bool is_keepout
const wxPoint center
FP_3DMODEL::VECTOR3D modelRotation
void Format(OUTPUTFORMATTER *out, int aNestLevel, int aCtl, const CPTREE &aTree)
Output a PTREE into s-expression format via an OUTPUTFORMATTER derivative.
Definition: ptree.cpp:200
wxString m_Filename
The 3D shape filename in 3D library.
Definition: footprint.h:97
void ParseModelsData(const CFB::CompoundFileReader &aReader, const CFB::COMPOUND_FILE_ENTRY *aEntry, const wxString aRootDir)
bool is_locked
double rotation
Definition: seg.h:40
bool is_polygonoutline
int AddOutline(const SHAPE_LINE_CHAIN &aOutline)
Adds a new hole to the given outline (default: last) and returns its index.
bool is_tent_bottom
void Move(const wxPoint &aMoveVector) override
Move this object.
Definition: board.cpp:266
uint16_t subpolyindex
bool IsFlipped() const
Definition: footprint.h:261
Use thermal relief for pads.
void SetFPID(const LIB_ID &aFPID)
Definition: footprint.h:186
bool Add(const NETCLASSPTR &aNetclass)
Add aNetclass and puts it into this NETCLASSES container.
Definition: netclass.cpp:90
void SetKeepUpright(bool aKeepUpright)
Definition: fp_text.h:115
void SetWidth(int aWidth)
Definition: pcb_track.h:101
void ParseTracks6Data(const CFB::CompoundFileReader &aReader, const CFB::COMPOUND_FILE_ENTRY *aEntry)
Handle the data for a net.
Definition: netinfo.h:64
void SetPadConnection(ZONE_CONNECTION aPadConnection)
Definition: zone.h:239
std::vector< ALTIUM_VERTICE > vertices
bool AppendCorner(wxPoint aPosition, int aHoleIdx, bool aAllowDuplication=false)
Add a new corner to the zone outline (to the main outline or a hole)
Definition: zone.cpp:838
void HelperParseDimensions6Linear(const ADIMENSION6 &aElem)
Definition: altium_pcb.cpp:907
void SetPosition(const wxPoint &aPos) override
Definition: zone.h:112
void RemoveAll()
Delete all items in list and clear the list.
uint32_t width
ALTIUM_TEXT_POSITION textposition
VECTOR3D m_Rotation
3D model rotation (degrees)
Definition: footprint.h:94
ALTIUM_LAYER layer
uint16_t net
ALTIUM_LAYER layer_start
const char * name
Definition: DXF_plotter.cpp:59
double DEG2RAD(double deg)
Definition: trigo.h:231
void HelperParsePad6NonCopper(const APAD6 &aElem)
void SetStart(const wxPoint &aStart)
Definition: pcb_shape.h:127
void ParseNets6Data(const CFB::CompoundFileReader &aReader, const CFB::COMPOUND_FILE_ENTRY *aEntry)
Information pertinent to a Pcbnew printed circuit board.
Definition: board.h:190
void SetHorizJustify(EDA_TEXT_HJUSTIFY_T aType)
Definition: eda_text.h:208
void SetDoNotAllowCopperPour(bool aEnable)
Definition: zone.h:740
SHAPE_LINE_CHAIN.
uint32_t radius
wxPoint position
static DIRECTION_45::AngleType angle(const VECTOR2I &a, const VECTOR2I &b)
std::vector< ALTIUM_VERTICE > outline
wxString ReadWxString()
Definition: altium_parser.h:69
int32_t pastemaskexpansionmanual
wxString name
Handle the component boundary box.
Definition: eda_rect.h:42
virtual void SetStart(const wxPoint &aPoint)
void SetLocked(bool isLocked) override
Set the #MODULE_is_LOCKED bit in the m_ModuleStatus.
Definition: footprint.h:287
usual segment : line with rounded ends
uint16_t net
void SetWidth(int aWidth)
Definition: pcb_shape.h:96
void SetLocalCoord()
Definition: fp_text.cpp:209
void HelperCreateBoardOutline(const std::vector< ALTIUM_VERTICE > &aVertices)
Definition: altium_pcb.cpp:691
void SetHatchOrientation(double aStep)
Definition: zone.h:257
int GetY() const
Definition: eda_rect.h:99
constexpr ret_type KiROUND(fp_type v)
Round a floating point number to an integer using "round halfway cases away from zero".
Definition: util.h:70
Pads are not covered.
wxPoint GetPosition() const override
Definition: footprint.h:177
double NormalizeAngleDegrees(double Angle, double aMin, double aMax)
Normalize angle to be aMin < angle <= aMax angle is in degrees.
Definition: trigo.h:323
SHAPE_POLY_SET & RawPolysList(PCB_LAYER_ID aLayer)
Definition: zone.h:715
bool IsAltiumLayerCopper(ALTIUM_LAYER aLayer)
Definition: altium_pcb.cpp:103
void SetFillVersion(int aVersion)
Definition: zone.h:690
bool IsCopperLayer(LAYER_NUM aLayerId)
Tests whether a layer is a copper layer.
ALTIUM_LAYER
void SetThermalReliefGap(int aThermalReliefGap)
Definition: zone.h:183
std::map< ALTIUM_RULE_KIND, std::vector< ARULE6 > > m_rules
Definition: altium_pcb.h:191
ALTIUM_PAD_MODE padmode
ALTIUM_LAYER layer
std::unique_ptr< APAD6_SIZE_AND_SHAPE > sizeAndShape
const double endangle
void SetLocalCoord()
Set relative coordinates from draw coordinates.
Definition: fp_shape.cpp:54
uint16_t component
void ParseFills6Data(const CFB::CompoundFileReader &aReader, const CFB::COMPOUND_FILE_ENTRY *aEntry)
virtual void SetLayer(PCB_LAYER_ID aLayer) override
Set the layer this item is on.
Definition: zone.cpp:238
std::vector< ALTIUM_VERTICE > board_vertices
void SetTextThickness(int aWidth)
The TextThickness is that set by the user.
Definition: eda_text.h:166
void SetPolyShape(const SHAPE_POLY_SET &aShape)
Definition: pcb_shape.h:247
static LSET UnplatedHoleMask()
layer set for a mechanical unplated through hole pad
Definition: pad.cpp:172
const int ALTIUM_COMPONENT_NONE
ALTIUM_UNIT textunit
void SetNeedRefill(bool aNeedRefill)
Definition: zone.h:235
ALTIUM_LAYER layer
void SetLocalClearance(int aClearance)
Definition: zone.h:158
virtual void SetTextAngle(double aAngle)
Definition: eda_text.h:174
void Add(BOARD_ITEM *aItem, ADD_MODE aMode=ADD_MODE::INSERT) override
Removes an item from the container.
Definition: footprint.cpp:472
void SkipSubrecord()
void SetRawPolysList(PCB_LAYER_ID aLayer, const SHAPE_POLY_SET &aPolysList)
Set the list of filled polygons.
Definition: zone.h:655
std::vector< wxPoint > referencePoint
Definition: pad.h:57
void SetPosition(const wxPoint &aPos) override
Definition: footprint.cpp:1442
std::vector< ABOARD6_LAYER_STACKUP > stackup
void ParseAltiumPcb(BOARD *aBoard, const wxString &aFileName, const std::map< ALTIUM_PCB_DIR, std::string > &aFileMapping)
Helper method which opens a Altium Board File and parses it.
Definition: altium_pcb.cpp:56
uint32_t strokewidth
void SetShape(PCB_SHAPE_TYPE aShape)
Definition: pcb_shape.h:109
Arcs (with rounded ends)
void TransformShapeWithClearanceToPolygon(SHAPE_POLY_SET &aCornerBuffer, PCB_LAYER_ID aLayer, int aClearanceValue, int aError, ERROR_LOC aErrorLoc, bool ignoreLineWidth=false) const override
Convert the draw segment to a closed polygon.
void BuildDefaultStackupList(const BOARD_DESIGN_SETTINGS *aSettings, int aActiveCopperLayersCount=0)
Create a default stackup, according to the current BOARD_DESIGN_SETTINGS settings.
void ParseBoard6Data(const CFB::CompoundFileReader &aReader, const CFB::COMPOUND_FILE_ENTRY *aEntry)
Definition: altium_pcb.cpp:551
BOARD_ITEM_CONTAINER * GetParent() const
Definition: board_item.h:166
bool is_tent_top
virtual void SetAngle(double aAngle, bool aUpdateEnd=true)
Set the angle for arcs, and normalizes it within the range 0 - 360 degrees.
Definition: pcb_shape.cpp:477
bool IsAltiumLayerAPlane(ALTIUM_LAYER aLayer)
Definition: altium_pcb.cpp:109
int32_t gridsize
static LSET SMDMask()
layer set for a SMD pad on Front layer
Definition: pad.cpp:158
void SetBold(bool aBold)
Definition: eda_text.h:189
uint32_t textheight
ALTIUM_POLYGON_HATCHSTYLE hatchstyle
static const int UNCONNECTED
Constant that forces initialization of a netinfo item to the NETINFO_ITEM ORPHANED (typically -1) whe...
Definition: netinfo.h:365
uint16_t component
#define THROW_IO_ERROR(msg)
Definition: ki_exception.h:38
PCB_SHAPE * HelperCreateAndAddDrawsegment(uint16_t aComponent)
Definition: altium_pcb.cpp:115
ALTIUM_TEXT_TYPE fonttype
void SetPolyPoints(const std::vector< wxPoint > &aPoints)
Definition: pcb_shape.cpp:1103
int32_t textprecision
wxPoint pos2
uint32_t diameter
void HelperDrawsegmentSetLocalCoord(PCB_SHAPE *aShape, uint16_t aComponent)
Definition: altium_pcb.cpp:142
wxPoint m_AuxOrigin
origin for plot exports
bool is_locked
wxString name
void SetEnd(const wxPoint &aEnd)
Definition: pcb_shape.h:137
void SetArcStart(const wxPoint &aArcStartPoint)
Initialize the start arc point.
Definition: pcb_shape.h:184
std::function< void(const CFB::CompoundFileReader &, const CFB::COMPOUND_FILE_ENTRY *)> PARSE_FUNCTION_POINTER_fp
Definition: altium_pcb.h:109
uint16_t net
void SetDoNotAllowFootprints(bool aEnable)
Definition: zone.h:744
bool GetFilledPolysUseThickness() const
Definition: zone.h:686
Container for design settings for a BOARD object.
wxString scope1expr
int32_t polygonconnectReliefconductorwidth
int32_t pourindex
void SetThermalReliefSpokeWidth(int aThermalReliefSpokeWidth)
Definition: zone.h:193
ALTIUM_PCB(BOARD *aBoard)
Definition: altium_pcb.cpp:312