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 <[email protected]>
5  * Copyright (C) 2021 KiCad Developers, see AUTHORS.txt for contributors.
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, you may find one here:
19  * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
20  * or you may search the http://www.gnu.org website for the version 2 license,
21  * or you may write to the Free Software Foundation, Inc.,
22  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
23  */
24 
25 #include "altium_pcb.h"
26 #include "altium_parser_pcb.h"
29 
30 #include <board.h>
31 #include <board_design_settings.h>
32 #include <pcb_dimension.h>
33 #include <pad.h>
34 #include <pcb_shape.h>
35 #include <pcb_text.h>
36 #include <pcb_track.h>
37 #include <string_utils.h>
38 
39 #include <fp_shape.h>
40 #include <fp_text.h>
41 #include <zone.h>
42 
44 
45 #include <compoundfilereader.h>
47 #include <project.h>
48 #include <trigo.h>
49 #include <utf.h>
50 #include <wx/docview.h>
51 #include <wx/log.h>
52 #include <wx/mstream.h>
53 #include <wx/wfstream.h>
54 #include <wx/zstream.h>
55 #include <progress_reporter.h>
56 
57 
58 void ParseAltiumPcb( BOARD* aBoard, const wxString& aFileName, PROGRESS_REPORTER* aProgressReporter,
59  const std::map<ALTIUM_PCB_DIR, std::string>& aFileMapping )
60 {
61  // Open file
62  FILE* fp = wxFopen( aFileName, wxT( "rb" ) );
63 
64  if( fp == nullptr )
65  {
66  wxLogError( _( "Cannot open file '%s'." ), aFileName );
67  return;
68  }
69 
70  fseek( fp, 0, SEEK_END );
71  long len = ftell( fp );
72 
73  if( len < 0 )
74  {
75  fclose( fp );
76  THROW_IO_ERROR( _( "Error reading file: cannot determine length." ) );
77  }
78 
79  std::unique_ptr<unsigned char[]> buffer( new unsigned char[len] );
80  fseek( fp, 0, SEEK_SET );
81 
82  size_t bytesRead = fread( buffer.get(), sizeof( unsigned char ), len, fp );
83  fclose( fp );
84 
85  if( static_cast<size_t>( len ) != bytesRead )
86  {
87  THROW_IO_ERROR( _( "Error reading file." ) );
88  }
89 
90  try
91  {
92  CFB::CompoundFileReader reader( buffer.get(), bytesRead );
93 
94  // Parse File
95  ALTIUM_PCB pcb( aBoard, aProgressReporter );
96  pcb.Parse( reader, aFileMapping );
97  }
98  catch( CFB::CFBException& exception )
99  {
100  THROW_IO_ERROR( exception.what() );
101  }
102 }
103 
104 
106 {
107  return aLayer >= ALTIUM_LAYER::TOP_LAYER && aLayer <= ALTIUM_LAYER::BOTTOM_LAYER;
108 }
109 
110 
112 {
114 }
115 
116 
118 {
119  if( aComponent == ALTIUM_COMPONENT_NONE )
120  {
121  PCB_SHAPE* shape = new PCB_SHAPE( m_board );
122  m_board->Add( shape, ADD_MODE::APPEND );
123  return shape;
124  }
125  else
126  {
127  if( m_components.size() <= aComponent )
128  {
129  THROW_IO_ERROR( wxString::Format( wxT( "Component creator tries to access component "
130  "id %d of %d existing components" ),
131  aComponent,
132  m_components.size() ) );
133  }
134 
135  FOOTPRINT* footprint = m_components.at( aComponent );
136  PCB_SHAPE* fpShape = new FP_SHAPE( footprint );
137 
138  footprint->Add( fpShape, ADD_MODE::APPEND );
139  return fpShape;
140  }
141 }
142 
143 
144 void ALTIUM_PCB::HelperShapeSetLocalCoord( PCB_SHAPE* aShape, uint16_t aComponent )
145 {
146  if( aComponent != ALTIUM_COMPONENT_NONE )
147  {
148  FP_SHAPE* fpShape = dynamic_cast<FP_SHAPE*>( aShape );
149 
150  if( fpShape )
151  {
152  fpShape->SetLocalCoord();
153 
154  // TODO: SetLocalCoord() does not update the polygon shape!
155  // This workaround converts the poly shape into the local coordinates
156  SHAPE_POLY_SET& polyShape = fpShape->GetPolyShape();
157  if( !polyShape.IsEmpty() )
158  {
159  FOOTPRINT* fp = m_components.at( aComponent );
160 
161  polyShape.Move( -fp->GetPosition() );
162  polyShape.Rotate( -fp->GetOrientationRadians() );
163  }
164  }
165  }
166 }
167 
168 
170  const std::vector<ALTIUM_VERTICE>& aVertices )
171 {
172  for( const ALTIUM_VERTICE& vertex : aVertices )
173  {
174  if( vertex.isRound )
175  {
176  double angle = NormalizeAngleDegreesPos( vertex.endangle - vertex.startangle );
177 
178  double startradiant = DEG2RAD( vertex.startangle );
179  double endradiant = DEG2RAD( vertex.endangle );
180  wxPoint arcStartOffset = wxPoint( KiROUND( std::cos( startradiant ) * vertex.radius ),
181  -KiROUND( std::sin( startradiant ) * vertex.radius ) );
182 
183  wxPoint arcEndOffset = wxPoint( KiROUND( std::cos( endradiant ) * vertex.radius ),
184  -KiROUND( std::sin( endradiant ) * vertex.radius ) );
185 
186  wxPoint arcStart = vertex.center + arcStartOffset;
187  wxPoint arcEnd = vertex.center + arcEndOffset;
188 
189  if( GetLineLength( arcStart, vertex.position )
190  < GetLineLength( arcEnd, vertex.position ) )
191  {
192  aLine.Append( SHAPE_ARC( vertex.center, arcStart, -angle ) );
193  }
194  else
195  {
196  aLine.Append( SHAPE_ARC( vertex.center, arcEnd, angle ) );
197  }
198  }
199  else
200  {
201  aLine.Append( vertex.position );
202  }
203  }
204 
205  aLine.SetClosed( true );
206 }
207 
208 
210 {
211  auto override = m_layermap.find( aAltiumLayer );
212  if( override != m_layermap.end() )
213  {
214  return override->second;
215  }
216 
217  switch( aAltiumLayer )
218  {
220 
221  case ALTIUM_LAYER::TOP_LAYER: return F_Cu;
222  case ALTIUM_LAYER::MID_LAYER_1: return In1_Cu;
223  case ALTIUM_LAYER::MID_LAYER_2: return In2_Cu;
224  case ALTIUM_LAYER::MID_LAYER_3: return In3_Cu;
225  case ALTIUM_LAYER::MID_LAYER_4: return In4_Cu;
226  case ALTIUM_LAYER::MID_LAYER_5: return In5_Cu;
227  case ALTIUM_LAYER::MID_LAYER_6: return In6_Cu;
228  case ALTIUM_LAYER::MID_LAYER_7: return In7_Cu;
229  case ALTIUM_LAYER::MID_LAYER_8: return In8_Cu;
230  case ALTIUM_LAYER::MID_LAYER_9: return In9_Cu;
231  case ALTIUM_LAYER::MID_LAYER_10: return In10_Cu;
232  case ALTIUM_LAYER::MID_LAYER_11: return In11_Cu;
233  case ALTIUM_LAYER::MID_LAYER_12: return In12_Cu;
234  case ALTIUM_LAYER::MID_LAYER_13: return In13_Cu;
235  case ALTIUM_LAYER::MID_LAYER_14: return In14_Cu;
236  case ALTIUM_LAYER::MID_LAYER_15: return In15_Cu;
237  case ALTIUM_LAYER::MID_LAYER_16: return In16_Cu;
238  case ALTIUM_LAYER::MID_LAYER_17: return In17_Cu;
239  case ALTIUM_LAYER::MID_LAYER_18: return In18_Cu;
240  case ALTIUM_LAYER::MID_LAYER_19: return In19_Cu;
241  case ALTIUM_LAYER::MID_LAYER_20: return In20_Cu;
242  case ALTIUM_LAYER::MID_LAYER_21: return In21_Cu;
243  case ALTIUM_LAYER::MID_LAYER_22: return In22_Cu;
244  case ALTIUM_LAYER::MID_LAYER_23: return In23_Cu;
245  case ALTIUM_LAYER::MID_LAYER_24: return In24_Cu;
246  case ALTIUM_LAYER::MID_LAYER_25: return In25_Cu;
247  case ALTIUM_LAYER::MID_LAYER_26: return In26_Cu;
248  case ALTIUM_LAYER::MID_LAYER_27: return In27_Cu;
249  case ALTIUM_LAYER::MID_LAYER_28: return In28_Cu;
250  case ALTIUM_LAYER::MID_LAYER_29: return In29_Cu;
251  case ALTIUM_LAYER::MID_LAYER_30: return In30_Cu;
252  case ALTIUM_LAYER::BOTTOM_LAYER: return B_Cu;
253 
254  case ALTIUM_LAYER::TOP_OVERLAY: return F_SilkS;
256  case ALTIUM_LAYER::TOP_PASTE: return F_Paste;
257  case ALTIUM_LAYER::BOTTOM_PASTE: return B_Paste;
258  case ALTIUM_LAYER::TOP_SOLDER: return F_Mask;
259  case ALTIUM_LAYER::BOTTOM_SOLDER: return B_Mask;
260 
277 
280 
281  case ALTIUM_LAYER::MECHANICAL_1: return User_1; //Edge_Cuts;
282  case ALTIUM_LAYER::MECHANICAL_2: return User_2;
283  case ALTIUM_LAYER::MECHANICAL_3: return User_3;
284  case ALTIUM_LAYER::MECHANICAL_4: return User_4;
285  case ALTIUM_LAYER::MECHANICAL_5: return User_5;
286  case ALTIUM_LAYER::MECHANICAL_6: return User_6;
287  case ALTIUM_LAYER::MECHANICAL_7: return User_7;
288  case ALTIUM_LAYER::MECHANICAL_8: return User_8;
289  case ALTIUM_LAYER::MECHANICAL_9: return User_9;
291  case ALTIUM_LAYER::MECHANICAL_11: return Eco2_User; //Eco1 is used for unknown elements
292  case ALTIUM_LAYER::MECHANICAL_12: return F_Fab;
293  case ALTIUM_LAYER::MECHANICAL_13: return B_Fab; // Don't use courtyard layers for other purposes
297 
308 
309  default: return UNDEFINED_LAYER;
310  }
311 }
312 
313 
314 ALTIUM_PCB::ALTIUM_PCB( BOARD* aBoard, PROGRESS_REPORTER* aProgressReporter )
315 {
316  m_board = aBoard;
317  m_progressReporter = aProgressReporter;
318  m_doneCount = 0;
320  m_totalCount = 0;
321  m_num_nets = 0;
323 }
324 
326 {
327 }
328 
330 {
331  const unsigned PROGRESS_DELTA = 250;
332 
333  if( m_progressReporter )
334  {
335  if( ++m_doneCount > m_lastProgressCount + PROGRESS_DELTA )
336  {
338  / std::max( 1U, m_totalCount ) );
339 
341  THROW_IO_ERROR( _( "Open cancelled by user." ) );
342 
344  }
345  }
346 }
347 
348 void ALTIUM_PCB::Parse( const CFB::CompoundFileReader& aReader,
349  const std::map<ALTIUM_PCB_DIR, std::string>& aFileMapping )
350 {
351  // this vector simply declares in which order which functions to call.
352  const std::vector<std::tuple<bool, ALTIUM_PCB_DIR, PARSE_FUNCTION_POINTER_fp>> parserOrder = {
354  [this]( auto aReader, auto fileHeader )
355  {
356  this->ParseFileHeader( aReader, fileHeader );
357  } },
358  { true, ALTIUM_PCB_DIR::BOARD6,
359  [this]( auto aReader, auto fileHeader )
360  {
361  this->ParseBoard6Data( aReader, fileHeader );
362  } },
364  [this]( auto aReader, auto fileHeader )
365  {
366  this->ParseComponents6Data( aReader, fileHeader );
367  } },
368  { true, ALTIUM_PCB_DIR::MODELS,
369  [this, aFileMapping]( auto aReader, auto fileHeader )
370  {
371  wxString dir( aFileMapping.at( ALTIUM_PCB_DIR::MODELS ) );
372  this->ParseModelsData( aReader, fileHeader, dir );
373  } },
375  [this]( auto aReader, auto fileHeader )
376  {
377  this->ParseComponentsBodies6Data( aReader, fileHeader );
378  } },
379  { true, ALTIUM_PCB_DIR::NETS6,
380  [this]( auto aReader, auto fileHeader )
381  {
382  this->ParseNets6Data( aReader, fileHeader );
383  } },
384  { true, ALTIUM_PCB_DIR::CLASSES6,
385  [this]( auto aReader, auto fileHeader )
386  {
387  this->ParseClasses6Data( aReader, fileHeader );
388  } },
389  { true, ALTIUM_PCB_DIR::RULES6,
390  [this]( auto aReader, auto fileHeader )
391  {
392  this->ParseRules6Data( aReader, fileHeader );
393  } },
395  [this]( auto aReader, auto fileHeader )
396  {
397  this->ParseDimensions6Data( aReader, fileHeader );
398  } },
400  [this]( auto aReader, auto fileHeader )
401  {
402  this->ParsePolygons6Data( aReader, fileHeader );
403  } },
404  { true, ALTIUM_PCB_DIR::ARCS6,
405  [this]( auto aReader, auto fileHeader )
406  {
407  this->ParseArcs6Data( aReader, fileHeader );
408  } },
409  { true, ALTIUM_PCB_DIR::PADS6,
410  [this]( auto aReader, auto fileHeader )
411  {
412  this->ParsePads6Data( aReader, fileHeader );
413  } },
414  { true, ALTIUM_PCB_DIR::VIAS6,
415  [this]( auto aReader, auto fileHeader )
416  {
417  this->ParseVias6Data( aReader, fileHeader );
418  } },
419  { true, ALTIUM_PCB_DIR::TRACKS6,
420  [this]( auto aReader, auto fileHeader )
421  {
422  this->ParseTracks6Data( aReader, fileHeader );
423  } },
425  [this]( auto aReader, auto fileHeader )
426  {
427  this->ParseWideStrings6Data( aReader, fileHeader );
428  } },
429  { true, ALTIUM_PCB_DIR::TEXTS6,
430  [this]( auto aReader, auto fileHeader )
431  {
432  this->ParseTexts6Data( aReader, fileHeader );
433  } },
434  { true, ALTIUM_PCB_DIR::FILLS6,
435  [this]( auto aReader, auto fileHeader )
436  {
437  this->ParseFills6Data( aReader, fileHeader );
438  } },
440  [this]( auto aReader, auto fileHeader )
441  {
442  this->ParseBoardRegionsData( aReader, fileHeader );
443  } },
445  [this]( auto aReader, auto fileHeader )
446  {
447  this->ParseShapeBasedRegions6Data( aReader, fileHeader );
448  } },
449  { true, ALTIUM_PCB_DIR::REGIONS6,
450  [this]( auto aReader, auto fileHeader )
451  {
452  this->ParseRegions6Data( aReader, fileHeader );
453  } }
454  };
455 
456  if( m_progressReporter != nullptr )
457  {
458  // Count number of records we will read for the progress reporter
459  for( const std::tuple<bool, ALTIUM_PCB_DIR, PARSE_FUNCTION_POINTER_fp>& cur : parserOrder )
460  {
461  bool isRequired;
464  std::tie( isRequired, directory, fp ) = cur;
465 
467  {
468  continue;
469  }
470 
471  const auto& mappedDirectory = aFileMapping.find( directory );
472  if( mappedDirectory == aFileMapping.end() )
473  {
474  continue;
475  }
476 
477  std::string mappedFile = mappedDirectory->second + "Header";
478 
479  const CFB::COMPOUND_FILE_ENTRY* file = FindStream( aReader, mappedFile.c_str() );
480  if( file == nullptr )
481  {
482  continue;
483  }
484 
485  ALTIUM_PARSER reader( aReader, file );
486  uint32_t numOfRecords = reader.Read<uint32_t>();
487 
488  if( reader.HasParsingError() )
489  {
490  wxLogError( _( "'%s' was not parsed correctly." ), mappedFile );
491  continue;
492  }
493 
494  m_totalCount += numOfRecords;
495 
496  if( reader.GetRemainingBytes() != 0 )
497  {
498  wxLogError( _( "'%s' was not fully parsed." ), mappedFile );
499  continue;
500  }
501  }
502  }
503 
504  // Parse data in specified order
505  for( const std::tuple<bool, ALTIUM_PCB_DIR, PARSE_FUNCTION_POINTER_fp>& cur : parserOrder )
506  {
507  bool isRequired;
510  std::tie( isRequired, directory, fp ) = cur;
511 
512  const auto& mappedDirectory = aFileMapping.find( directory );
513 
514  if( mappedDirectory == aFileMapping.end() )
515  {
516  wxASSERT_MSG( !isRequired, wxString::Format( wxT( "Altium Directory of kind %d was "
517  "expected, but no mapping is "
518  "present in the code" ),
519  directory ) );
520  continue;
521  }
522 
523  std::string mappedFile = mappedDirectory->second;
524 
526  mappedFile += "Data";
527 
528  const CFB::COMPOUND_FILE_ENTRY* file = FindStream( aReader, mappedFile.c_str() );
529 
530  if( file != nullptr )
531  fp( aReader, file );
532  else if( isRequired )
533  wxLogError( _( "File not found: '%s'." ), mappedFile );
534  }
535 
536  // fixup zone priorities since Altium stores them in the opposite order
537  for( ZONE* zone : m_polygons )
538  {
539  if( !zone )
540  continue;
541 
542  // Altium "fills" - not poured in Altium
543  if( zone->GetPriority() == 1000 )
544  {
545  // Unlikely, but you never know
546  if( m_highest_pour_index >= 1000 )
547  zone->SetPriority( m_highest_pour_index + 1 );
548 
549  continue;
550  }
551 
552  int priority = m_highest_pour_index - zone->GetPriority();
553 
554  zone->SetPriority( priority >= 0 ? priority : 0 );
555  }
556 
557  // change priority of outer zone to zero
558  for( std::pair<const ALTIUM_LAYER, ZONE*>& zone : m_outer_plane )
559  zone.second->SetPriority( 0 );
560 
561  // Altium doesn't appear to store either the dimension value nor the dimensioned object in
562  // the dimension record. (Yes, there is a REFERENCE0OBJECTID, but it doesn't point to the
563  // dimensioned object.) We attempt to plug this gap by finding a colocated arc or circle
564  // and using its radius. If there are more than one such arcs/circles, well, :shrug:.
566  {
567  int radius = 0;
568 
569  for( BOARD_ITEM* item : m_board->Drawings() )
570  {
571  if( item->Type() != PCB_SHAPE_T )
572  continue;
573 
574  PCB_SHAPE* shape = static_cast<PCB_SHAPE*>( item );
575 
576  if( shape->GetShape() != SHAPE_T::ARC && shape->GetShape() != SHAPE_T::CIRCLE )
577  continue;
578 
579  if( shape->GetPosition() == dim->GetPosition() )
580  {
581  radius = shape->GetRadius();
582  break;
583  }
584  }
585 
586  if( radius == 0 )
587  {
588  for( PCB_TRACK* track : m_board->Tracks() )
589  {
590  if( track->Type() != PCB_ARC_T )
591  continue;
592 
593  PCB_ARC* arc = static_cast<PCB_ARC*>( track );
594 
595  if( arc->GetCenter() == dim->GetPosition() )
596  {
597  radius = arc->GetRadius();
598  break;
599  }
600  }
601  }
602 
603  // Force a measured value, calculate the value text, and then stick it into the override
604  // text (since leaders don't have calculated text).
605  dim->SetMeasuredValue( radius );
606  dim->SetText( dim->GetPrefix() + dim->GetValueText() + dim->GetSuffix() );
607  dim->SetPrefix( wxEmptyString );
608  dim->SetSuffix( wxEmptyString );
609 
610  // Move the leader line start to the radius point
611  VECTOR2I radialLine = dim->GetEnd() - dim->GetStart();
612  radialLine = radialLine.Resize( radius );
613  dim->SetStart( dim->GetStart() + (wxPoint) radialLine );
614  }
615 
616  // center board
618 
619  int w = m_board->GetPageSettings().GetWidthIU();
620  int h = m_board->GetPageSettings().GetHeightIU();
621 
622  int desired_x = ( w - bbbox.GetWidth() ) / 2;
623  int desired_y = ( h - bbbox.GetHeight() ) / 2;
624 
625  wxPoint movementVector( desired_x - bbbox.GetX(), desired_y - bbbox.GetY() );
626  m_board->Move( movementVector );
627 
629  bds.SetAuxOrigin( bds.GetAuxOrigin() + movementVector );
630  bds.SetGridOrigin( bds.GetGridOrigin() + movementVector );
631 
632  m_board->SetModified();
633 }
634 
635 int ALTIUM_PCB::GetNetCode( uint16_t aId ) const
636 {
637  if( aId == ALTIUM_NET_UNCONNECTED )
638  {
640  }
641  else if( m_num_nets < aId )
642  {
643  THROW_IO_ERROR( wxString::Format( wxT( "Netcode with id %d does not exist. Only %d nets "
644  "are known" ),
645  aId, m_num_nets ) );
646  }
647  else
648  {
649  return aId + 1;
650  }
651 }
652 
653 const ARULE6* ALTIUM_PCB::GetRule( ALTIUM_RULE_KIND aKind, const wxString& aName ) const
654 {
655  const auto rules = m_rules.find( aKind );
656  if( rules == m_rules.end() )
657  {
658  return nullptr;
659  }
660  for( const ARULE6& rule : rules->second )
661  {
662  if( rule.name == aName )
663  {
664  return &rule;
665  }
666  }
667  return nullptr;
668 }
669 
671 {
672  const auto rules = m_rules.find( aKind );
673  if( rules == m_rules.end() )
674  {
675  return nullptr;
676  }
677  for( const ARULE6& rule : rules->second )
678  {
679  if( rule.scope1expr == wxT( "All" ) && rule.scope2expr == wxT( "All" ) )
680  {
681  return &rule;
682  }
683  }
684  return nullptr;
685 }
686 
687 void ALTIUM_PCB::ParseFileHeader( const CFB::CompoundFileReader& aReader,
688  const CFB::COMPOUND_FILE_ENTRY* aEntry )
689 {
690  ALTIUM_PARSER reader( aReader, aEntry );
691 
692  reader.ReadAndSetSubrecordLength();
693  wxString header = reader.ReadWxString();
694 
695  //std::cout << "HEADER: " << header << std::endl; // tells me: PCB 5.0 Binary File
696 
697  //reader.SkipSubrecord();
698 
699  // TODO: does not seem to work all the time at the moment
700  //if( reader.GetRemainingBytes() != 0 )
701  //{
702  // THROW_IO_ERROR( "FileHeader stream is not fully parsed" );
703  //}
704 }
705 
706 void ALTIUM_PCB::ParseBoard6Data( const CFB::CompoundFileReader& aReader,
707  const CFB::COMPOUND_FILE_ENTRY* aEntry )
708 {
709  if( m_progressReporter )
710  m_progressReporter->Report( _( "Loading board data..." ) );
711 
712  ALTIUM_PARSER reader( aReader, aEntry );
713 
714  checkpoint();
715  ABOARD6 elem( reader );
716 
717  if( reader.GetRemainingBytes() != 0 )
718  {
719  THROW_IO_ERROR( wxT( "Board6 stream is not fully parsed" ) );
720  }
721 
724 
725  // read layercount from stackup, because LAYERSETSCOUNT is not always correct?!
726  size_t layercount = 0;
727  for( size_t i = static_cast<size_t>( ALTIUM_LAYER::TOP_LAYER );
728  i < elem.stackup.size() && i != 0; i = elem.stackup[i - 1].nextId, layercount++ )
729  ;
730  size_t kicadLayercount = ( layercount % 2 == 0 ) ? layercount : layercount + 1;
731  m_board->SetCopperLayerCount( kicadLayercount );
732 
733  BOARD_DESIGN_SETTINGS& designSettings = m_board->GetDesignSettings();
734  BOARD_STACKUP& stackup = designSettings.GetStackupDescriptor();
735 
736  // create board stackup
737  stackup.RemoveAll(); // Just to be sure
738  stackup.BuildDefaultStackupList( &designSettings, layercount );
739 
740  auto it = stackup.GetList().begin();
741  // find first copper layer
742  for( ; it != stackup.GetList().end() && ( *it )->GetType() != BS_ITEM_TYPE_COPPER; ++it )
743  ;
744 
745  auto curLayer = static_cast<int>( F_Cu );
746  for( size_t altiumLayerId = static_cast<size_t>( ALTIUM_LAYER::TOP_LAYER );
747  altiumLayerId < elem.stackup.size() && altiumLayerId != 0;
748  altiumLayerId = elem.stackup[altiumLayerId - 1].nextId )
749  {
750  // array starts with 0, but stackup with 1
751  ABOARD6_LAYER_STACKUP& layer = elem.stackup.at( altiumLayerId - 1 );
752 
753  // handle unused layer in case of odd layercount
754  if( layer.nextId == 0 && layercount != kicadLayercount )
755  {
756  m_board->SetLayerName( ( *it )->GetBrdLayerId(), wxT( "[unused]" ) );
757 
758  if( ( *it )->GetType() != BS_ITEM_TYPE_COPPER )
759  {
760  THROW_IO_ERROR( wxT( "Board6 stream, unexpected item while parsing stackup" ) );
761  }
762  ( *it )->SetThickness( 0 );
763 
764  ++it;
765  if( ( *it )->GetType() != BS_ITEM_TYPE_DIELECTRIC )
766  {
767  THROW_IO_ERROR( wxT( "Board6 stream, unexpected item while parsing stackup" ) );
768  }
769  ( *it )->SetThickness( 0, 0 );
770  ( *it )->SetThicknessLocked( true, 0 );
771  ++it;
772  }
773 
774  m_layermap.insert( { static_cast<ALTIUM_LAYER>( altiumLayerId ),
775  static_cast<PCB_LAYER_ID>( curLayer++ ) } );
776 
777  if( ( *it )->GetType() != BS_ITEM_TYPE_COPPER )
778  THROW_IO_ERROR( wxT( "Board6 stream, unexpected item while parsing stackup" ) );
779 
780  ( *it )->SetThickness( layer.copperthick );
781 
782  ALTIUM_LAYER alayer = static_cast<ALTIUM_LAYER>( altiumLayerId );
783  PCB_LAYER_ID klayer = ( *it )->GetBrdLayerId();
784 
785  m_board->SetLayerName( klayer, layer.name );
786 
787  if( layer.copperthick == 0 )
788  {
789  m_board->SetLayerType( klayer, LAYER_T::LT_JUMPER ); // used for things like wirebonding
790  }
791  else if( IsAltiumLayerAPlane( alayer ) )
792  {
794  }
795 
796  if( klayer == B_Cu )
797  {
798  if( layer.nextId != 0 )
799  THROW_IO_ERROR( wxT( "Board6 stream, unexpected id while parsing last stackup layer" ) );
800 
801  // overwrite entry from internal -> bottom
802  m_layermap[alayer] = B_Cu;
803  break;
804  }
805 
806  ++it;
807 
808  if( ( *it )->GetType() != BS_ITEM_TYPE_DIELECTRIC )
809  THROW_IO_ERROR( wxT( "Board6 stream, unexpected item while parsing stackup" ) );
810 
811  ( *it )->SetThickness( layer.dielectricthick, 0 );
812  ( *it )->SetMaterial( layer.dielectricmaterial.empty() ?
813  NotSpecifiedPrm() :
814  wxString( layer.dielectricmaterial ) );
815  ( *it )->SetEpsilonR( layer.dielectricconst, 0 );
816 
817  ++it;
818  }
819 
820  // Set name of all non-cu layers
821  for( size_t altiumLayerId = static_cast<size_t>( ALTIUM_LAYER::TOP_OVERLAY );
822  altiumLayerId <= static_cast<size_t>( ALTIUM_LAYER::BOTTOM_SOLDER ); altiumLayerId++ )
823  {
824  // array starts with 0, but stackup with 1
825  ABOARD6_LAYER_STACKUP& layer = elem.stackup.at( altiumLayerId - 1 );
826 
827  ALTIUM_LAYER alayer = static_cast<ALTIUM_LAYER>( altiumLayerId );
828  PCB_LAYER_ID klayer = GetKicadLayer( alayer );
829 
830  m_board->SetLayerName( klayer, layer.name );
831  }
832 
833  for( size_t altiumLayerId = static_cast<size_t>( ALTIUM_LAYER::MECHANICAL_1 );
834  altiumLayerId <= static_cast<size_t>( ALTIUM_LAYER::MECHANICAL_16 ); altiumLayerId++ )
835  {
836  // array starts with 0, but stackup with 1
837  ABOARD6_LAYER_STACKUP& layer = elem.stackup.at( altiumLayerId - 1 );
838 
839  ALTIUM_LAYER alayer = static_cast<ALTIUM_LAYER>( altiumLayerId );
840  PCB_LAYER_ID klayer = GetKicadLayer( alayer );
841 
842  m_board->SetLayerName( klayer, layer.name );
843  }
844 
846 }
847 
848 void ALTIUM_PCB::HelperCreateBoardOutline( const std::vector<ALTIUM_VERTICE>& aVertices )
849 {
850  SHAPE_LINE_CHAIN lineChain;
851  HelperShapeLineChainFromAltiumVertices( lineChain, aVertices );
852 
853  for( int i = 0; i <= lineChain.PointCount() && i != -1; i = lineChain.NextShape( i ) )
854  {
855  if( lineChain.IsArcStart( i ) )
856  {
857  const SHAPE_ARC& currentArc = lineChain.Arc( lineChain.ArcIndex( i ) );
858  int nextShape = lineChain.NextShape( i );
859  bool isLastShape = nextShape < 0;
860 
861  PCB_SHAPE* shape = new PCB_SHAPE( m_board, SHAPE_T::ARC );
862  m_board->Add( shape, ADD_MODE::APPEND );
863 
865  shape->SetLayer( Edge_Cuts );
866  shape->SetArcGeometry( (wxPoint) currentArc.GetP0(), (wxPoint) currentArc.GetArcMid(),
867  (wxPoint) currentArc.GetP1() );
868  }
869  else
870  {
871  const SEG& seg = lineChain.Segment( i );
872 
873  PCB_SHAPE* shape = new PCB_SHAPE( m_board, SHAPE_T::SEGMENT );
874  m_board->Add( shape, ADD_MODE::APPEND );
875 
877  shape->SetLayer( Edge_Cuts );
878  shape->SetStart( (wxPoint) seg.A );
879  shape->SetEnd( (wxPoint) seg.B );
880  }
881  }
882 }
883 
884 void ALTIUM_PCB::ParseClasses6Data( const CFB::CompoundFileReader& aReader,
885  const CFB::COMPOUND_FILE_ENTRY* aEntry )
886 {
887  if( m_progressReporter )
888  m_progressReporter->Report( _( "Loading netclasses..." ) );
889 
890  ALTIUM_PARSER reader( aReader, aEntry );
891 
892  while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
893  {
894  checkpoint();
895  ACLASS6 elem( reader );
896  if( elem.kind == ALTIUM_CLASS_KIND::NET_CLASS )
897  {
898  NETCLASSPTR nc = std::make_shared<NETCLASS>( elem.name );
899 
900  for( const auto& name : elem.names )
901  {
902  // TODO: it seems it can happen that we have names not attached to any net.
903  nc->Add( name );
904  }
905 
906  if( !m_board->GetDesignSettings().GetNetClasses().Add( nc ) )
907  {
908  // Name conflict, this is likely a bad board file.
909  // unique_ptr will delete nc on this code path
910  THROW_IO_ERROR( wxString::Format( _( "Duplicate netclass name '%s'." ), elem.name ) );
911  }
912  }
913  }
914 
915  if( reader.GetRemainingBytes() != 0 )
916  {
917  THROW_IO_ERROR( wxT( "Classes6 stream is not fully parsed" ) );
918  }
919 
921 }
922 
923 void ALTIUM_PCB::ParseComponents6Data( const CFB::CompoundFileReader& aReader,
924  const CFB::COMPOUND_FILE_ENTRY* aEntry )
925 {
926  if( m_progressReporter )
927  m_progressReporter->Report( _( "Loading components..." ) );
928 
929  ALTIUM_PARSER reader( aReader, aEntry );
930 
931  uint16_t componentId = 0;
932  while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
933  {
934  checkpoint();
935  ACOMPONENT6 elem( reader );
936 
937  FOOTPRINT* footprint = new FOOTPRINT( m_board );
938  m_board->Add( footprint, ADD_MODE::APPEND );
939  m_components.emplace_back( footprint );
940 
942 
943  footprint->SetFPID( fpID );
944 
945  footprint->SetPosition( elem.position );
946  footprint->SetOrientationDegrees( elem.rotation );
947 
948  // KiCad netlisting requires parts to have non-digit + digit annotation.
949  // If the reference begins with a number, we prepend 'UNK' (unknown) for the source designator
950  wxString reference = elem.sourcedesignator;
951  if( reference.find_first_not_of( "0123456789" ) == wxString::npos )
952  reference.Prepend( wxT( "UNK" ) );
953  footprint->SetReference( reference );
954 
955  footprint->SetLocked( elem.locked );
956  footprint->Reference().SetVisible( elem.nameon );
957  footprint->Value().SetVisible( elem.commenton );
958  footprint->SetLayer( elem.layer == ALTIUM_LAYER::TOP_LAYER ? F_Cu : B_Cu );
959 
960  componentId++;
961  }
962 
963  if( reader.GetRemainingBytes() != 0 )
964  {
965  THROW_IO_ERROR( wxT( "Components6 stream is not fully parsed" ) );
966  }
967 }
968 
969 
970 void ALTIUM_PCB::ParseComponentsBodies6Data( const CFB::CompoundFileReader& aReader,
971  const CFB::COMPOUND_FILE_ENTRY* aEntry )
972 {
973  if( m_progressReporter )
974  m_progressReporter->Report( _( "Loading component 3D models..." ) );
975 
976  ALTIUM_PARSER reader( aReader, aEntry );
977 
978  while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
979  {
980  checkpoint();
981  ACOMPONENTBODY6 elem( reader ); // TODO: implement
982 
983  if( elem.component == ALTIUM_COMPONENT_NONE )
984  continue; // TODO: we do not support components for the board yet
985 
986  if( m_components.size() <= elem.component )
987  {
988  THROW_IO_ERROR( wxString::Format( wxT( "ComponentsBodies6 stream tries to access "
989  "component id %d of %d existing components" ),
990  elem.component,
991  m_components.size() ) );
992  }
993 
994  if( !elem.modelIsEmbedded )
995  continue;
996 
997  auto modelTuple = m_models.find( elem.modelId );
998 
999  if( modelTuple == m_models.end() )
1000  {
1001  THROW_IO_ERROR( wxString::Format( wxT( "ComponentsBodies6 stream tries to access "
1002  "model id %s which does not exist" ),
1003  elem.modelId ) );
1004  }
1005 
1006  FOOTPRINT* footprint = m_components.at( elem.component );
1007  const wxPoint& fpPosition = footprint->GetPosition();
1008 
1009  FP_3DMODEL modelSettings;
1010 
1011  modelSettings.m_Filename = modelTuple->second;
1012 
1013  modelSettings.m_Offset.x = Iu2Millimeter((int) elem.modelPosition.x - fpPosition.x );
1014  modelSettings.m_Offset.y = -Iu2Millimeter((int) elem.modelPosition.y - fpPosition.y );
1015  modelSettings.m_Offset.z = Iu2Millimeter( (int) elem.modelPosition.z );
1016 
1017  double orientation = footprint->GetOrientation();
1018 
1019  if( footprint->IsFlipped() )
1020  {
1021  modelSettings.m_Offset.y = -modelSettings.m_Offset.y;
1022  orientation = -orientation;
1023  }
1024 
1025  RotatePoint( &modelSettings.m_Offset.x, &modelSettings.m_Offset.y, orientation );
1026 
1027  modelSettings.m_Rotation.x = NormalizeAngleDegrees( -elem.modelRotation.x, -180, 180 );
1028  modelSettings.m_Rotation.y = NormalizeAngleDegrees( -elem.modelRotation.y, -180, 180 );
1029  modelSettings.m_Rotation.z = NormalizeAngleDegrees( -elem.modelRotation.z
1030  + elem.rotation
1031  + orientation / 10, -180, 180 );
1032  modelSettings.m_Opacity = elem.bodyOpacity;
1033 
1034  footprint->Models().push_back( modelSettings );
1035  }
1036 
1037  if( reader.GetRemainingBytes() != 0 )
1038  THROW_IO_ERROR( wxT( "ComponentsBodies6 stream is not fully parsed" ) );
1039 }
1040 
1041 
1043 {
1044  if( aElem.referencePoint.size() != 2 )
1045  THROW_IO_ERROR( wxT( "Incorrect number of reference points for linear dimension object" ) );
1046 
1047  PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
1048 
1049  if( klayer == UNDEFINED_LAYER )
1050  {
1051  wxLogWarning( _( "Dimension found on an Altium layer (%d) with no KiCad equivalent. "
1052  "It has been moved to KiCad layer Eco1_User." ),
1053  aElem.layer );
1054  klayer = Eco1_User;
1055  }
1056 
1057  wxPoint referencePoint0 = aElem.referencePoint.at( 0 );
1058  wxPoint referencePoint1 = aElem.referencePoint.at( 1 );
1059 
1060  PCB_DIM_ALIGNED* dimension = new PCB_DIM_ALIGNED( m_board );
1061  m_board->Add( dimension, ADD_MODE::APPEND );
1062 
1063  dimension->SetPrecision( aElem.textprecision );
1064  dimension->SetLayer( klayer );
1065  dimension->SetStart( referencePoint0 );
1066 
1067  if( referencePoint0 != aElem.xy1 )
1068  {
1078  wxPoint direction = aElem.xy1 - referencePoint0;
1079  wxPoint directionNormalVector = wxPoint( -direction.y, direction.x );
1080  SEG segm1( referencePoint0, referencePoint0 + directionNormalVector );
1081  SEG segm2( referencePoint1, referencePoint1 + direction );
1082  wxPoint intersection( segm1.Intersect( segm2, true, true ).get() );
1083  dimension->SetEnd( intersection );
1084 
1085  int height = static_cast<int>( EuclideanNorm( direction ) );
1086 
1087  if( direction.x <= 0 && direction.y <= 0 ) // TODO: I suspect this is not always correct
1088  height = -height;
1089 
1090  dimension->SetHeight( height );
1091  }
1092  else
1093  {
1094  dimension->SetEnd( referencePoint1 );
1095  }
1096 
1097  dimension->SetLineThickness( aElem.linewidth );
1098 
1099  dimension->SetPrefix( aElem.textprefix );
1100 
1101  // Suffix normally holds the units
1102  dimension->SetUnitsFormat( aElem.textsuffix.IsEmpty() ? DIM_UNITS_FORMAT::NO_SUFFIX
1104 
1105  dimension->Text().SetTextThickness( aElem.textlinewidth );
1106  dimension->Text().SetTextSize( wxSize( aElem.textheight, aElem.textheight ) );
1107  dimension->Text().SetBold( aElem.textbold );
1108  dimension->Text().SetItalic( aElem.textitalic );
1109 
1110  switch( aElem.textunit )
1111  {
1112  case ALTIUM_UNIT::INCHES:
1113  dimension->SetUnits( EDA_UNITS::INCHES );
1114  break;
1115  case ALTIUM_UNIT::MILS:
1116  dimension->SetUnits( EDA_UNITS::MILS );
1117  break;
1120  dimension->SetUnits( EDA_UNITS::MILLIMETRES );
1121  break;
1122  default:
1123  break;
1124  }
1125 }
1126 
1127 
1129 {
1130  if( aElem.referencePoint.size() < 2 )
1131  THROW_IO_ERROR( wxT( "Not enough reference points for radial dimension object" ) );
1132 
1133  PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
1134 
1135  if( klayer == UNDEFINED_LAYER )
1136  {
1137  wxLogWarning( _( "Dimension found on an Altium layer (%d) with no KiCad equivalent. "
1138  "It has been moved to KiCad layer Eco1_User." ),
1139  aElem.layer );
1140  klayer = Eco1_User;
1141  }
1142 
1143  wxPoint referencePoint0 = aElem.referencePoint.at( 0 );
1144  wxPoint referencePoint1 = aElem.referencePoint.at( 1 );
1145 
1146  //
1147  // We don't have radial dimensions yet so fake it with a leader:
1148 
1149  PCB_DIM_LEADER* dimension = new PCB_DIM_LEADER( m_board );
1150  m_board->Add( dimension, ADD_MODE::APPEND );
1151  m_radialDimensions.push_back( dimension );
1152 
1153  dimension->SetPrecision( aElem.textprecision );
1154  dimension->SetLayer( klayer );
1155  dimension->SetStart( referencePoint0 );
1156  dimension->SetEnd( aElem.xy1 );
1157  dimension->SetLineThickness( aElem.linewidth );
1158 
1159  dimension->SetPrefix( aElem.textprefix );
1160 
1161  // Suffix normally holds the units
1162  dimension->SetUnitsFormat( aElem.textsuffix.IsEmpty() ? DIM_UNITS_FORMAT::NO_SUFFIX
1164 
1165  switch( aElem.textunit )
1166  {
1167  case ALTIUM_UNIT::INCHES:
1168  dimension->SetUnits( EDA_UNITS::INCHES );
1169  break;
1170  case ALTIUM_UNIT::MILS:
1171  dimension->SetUnits( EDA_UNITS::MILS );
1172  break;
1175  dimension->SetUnits( EDA_UNITS::MILLIMETRES );
1176  break;
1177  default:
1178  break;
1179  }
1180 
1181  if( aElem.textPoint.empty() )
1182  {
1183  wxLogError( wxT( "No text position present for leader dimension object" ) );
1184  return;
1185  }
1186 
1187  dimension->Text().SetPosition( aElem.textPoint.at( 0 ) );
1188  dimension->Text().SetTextThickness( aElem.textlinewidth );
1189  dimension->Text().SetTextSize( wxSize( aElem.textheight, aElem.textheight ) );
1190  dimension->Text().SetBold( aElem.textbold );
1191  dimension->Text().SetItalic( aElem.textitalic );
1194 
1195  int yAdjust = dimension->Text().GetCenter().y - dimension->Text().GetPosition().y;
1196  dimension->Text().Move( wxPoint( 0, yAdjust + aElem.textgap ) );
1198 }
1199 
1200 
1202 {
1203  PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
1204 
1205  if( klayer == UNDEFINED_LAYER )
1206  {
1207  wxLogWarning( _( "Dimension found on an Altium layer (%d) with no KiCad equivalent. "
1208  "It has been moved to KiCad layer Eco1_User." ),
1209  aElem.layer );
1210  klayer = Eco1_User;
1211  }
1212 
1213  if( !aElem.referencePoint.empty() )
1214  {
1215  wxPoint referencePoint0 = aElem.referencePoint.at( 0 );
1216 
1217  // line
1218  wxPoint last = referencePoint0;
1219  for( size_t i = 1; i < aElem.referencePoint.size(); i++ )
1220  {
1221  PCB_SHAPE* shape = new PCB_SHAPE( m_board, SHAPE_T::SEGMENT );
1222  m_board->Add( shape, ADD_MODE::APPEND );
1223  shape->SetLayer( klayer );
1224  shape->SetWidth( aElem.linewidth );
1225  shape->SetStart( last );
1226  shape->SetEnd( aElem.referencePoint.at( i ) );
1227  last = aElem.referencePoint.at( i );
1228  }
1229 
1230  // arrow
1231  if( aElem.referencePoint.size() >= 2 )
1232  {
1233  wxPoint dirVec = aElem.referencePoint.at( 1 ) - referencePoint0;
1234  if( dirVec.x != 0 || dirVec.y != 0 )
1235  {
1236  double scaling = EuclideanNorm( dirVec ) / aElem.arrowsize;
1237  wxPoint arrVec =
1238  wxPoint( KiROUND( dirVec.x / scaling ), KiROUND( dirVec.y / scaling ) );
1239  RotatePoint( &arrVec, 200. );
1240 
1241  PCB_SHAPE* shape1 = new PCB_SHAPE( m_board, SHAPE_T::SEGMENT );
1242  m_board->Add( shape1, ADD_MODE::APPEND );
1243  shape1->SetLayer( klayer );
1244  shape1->SetWidth( aElem.linewidth );
1245  shape1->SetStart( referencePoint0 );
1246  shape1->SetEnd( referencePoint0 + arrVec );
1247 
1248  RotatePoint( &arrVec, -400. );
1249 
1250  PCB_SHAPE* shape2 = new PCB_SHAPE( m_board, SHAPE_T::SEGMENT );
1251  m_board->Add( shape2, ADD_MODE::APPEND );
1252  shape2->SetLayer( klayer );
1253  shape2->SetWidth( aElem.linewidth );
1254  shape2->SetStart( referencePoint0 );
1255  shape2->SetEnd( referencePoint0 + arrVec );
1256  }
1257  }
1258  }
1259 
1260  if( aElem.textPoint.empty() )
1261  {
1262  wxLogError( wxT( "No text position present for leader dimension object" ) );
1263  return;
1264  }
1265 
1266  PCB_TEXT* text = new PCB_TEXT( m_board );
1268  text->SetText( aElem.textformat );
1269  text->SetPosition( aElem.textPoint.at( 0 ) );
1270  text->SetLayer( klayer );
1271  text->SetTextSize( wxSize( aElem.textheight, aElem.textheight ) ); // TODO: parse text width
1272  text->SetTextThickness( aElem.textlinewidth );
1275 }
1276 
1277 
1279 {
1280  PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
1281 
1282  if( klayer == UNDEFINED_LAYER )
1283  {
1284  wxLogWarning( _( "Dimension found on an Altium layer (%d) with no KiCad equivalent. "
1285  "It has been moved to KiCad layer Eco1_User." ),
1286  aElem.layer );
1287  klayer = Eco1_User;
1288  }
1289 
1290  for( size_t i = 0; i < aElem.referencePoint.size(); i++ )
1291  {
1292  PCB_SHAPE* shape = new PCB_SHAPE( m_board, SHAPE_T::SEGMENT );
1293  m_board->Add( shape, ADD_MODE::APPEND );
1294  shape->SetLayer( klayer );
1295  shape->SetWidth( aElem.linewidth );
1296  shape->SetStart( aElem.referencePoint.at( i ) );
1297  // shape->SetEnd( /* TODO: seems to be based on TEXTY */ );
1298  }
1299 }
1300 
1301 
1303 {
1304  PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
1305 
1306  if( klayer == UNDEFINED_LAYER )
1307  {
1308  wxLogWarning( _( "Dimension found on an Altium layer (%d) with no KiCad equivalent. "
1309  "It has been moved to KiCad layer Eco1_User." ),
1310  aElem.layer );
1311  klayer = Eco1_User;
1312  }
1313 
1314  wxPoint vec = wxPoint( 0, aElem.height / 2 );
1315  RotatePoint( &vec, aElem.angle * 10. );
1316 
1317  PCB_DIM_CENTER* dimension = new PCB_DIM_CENTER( m_board );
1318  m_board->Add( dimension, ADD_MODE::APPEND );
1319  dimension->SetLayer( klayer );
1320  dimension->SetLineThickness( aElem.linewidth );
1321  dimension->SetStart( aElem.xy1 );
1322  dimension->SetEnd( aElem.xy1 + vec );
1323 }
1324 
1325 
1326 void ALTIUM_PCB::ParseDimensions6Data( const CFB::CompoundFileReader& aReader,
1327  const CFB::COMPOUND_FILE_ENTRY* aEntry )
1328 {
1329  if( m_progressReporter )
1330  m_progressReporter->Report( _( "Loading dimension drawings..." ) );
1331 
1332  ALTIUM_PARSER reader( aReader, aEntry );
1333 
1334  while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
1335  {
1336  checkpoint();
1337  ADIMENSION6 elem( reader );
1338 
1339  switch( elem.kind )
1340  {
1343  break;
1346  break;
1349  break;
1351  wxLogError( _( "Ignored dimension of kind %d (not yet supported)." ), elem.kind );
1352  // HelperParseDimensions6Datum( elem );
1353  break;
1356  break;
1357  default:
1358  wxLogError( _( "Ignored dimension of kind %d (not yet supported)." ), elem.kind );
1359  break;
1360  }
1361  }
1362 
1363  if( reader.GetRemainingBytes() != 0 )
1364  THROW_IO_ERROR( wxT( "Dimensions6 stream is not fully parsed" ) );
1365 }
1366 
1367 
1368 void ALTIUM_PCB::ParseModelsData( const CFB::CompoundFileReader& aReader,
1369  const CFB::COMPOUND_FILE_ENTRY* aEntry, const wxString& aRootDir )
1370 {
1371  if( m_progressReporter )
1372  m_progressReporter->Report( _( "Loading 3D models..." ) );
1373 
1374  ALTIUM_PARSER reader( aReader, aEntry );
1375 
1376  if( reader.GetRemainingBytes() == 0 )
1377  return;
1378 
1379  wxString projectPath = wxPathOnly( m_board->GetFileName() );
1380  // TODO: set KIPRJMOD always after import (not only when loading project)?
1381  wxSetEnv( PROJECT_VAR_NAME, projectPath );
1382 
1383  // TODO: make this path configurable?
1384  const wxString altiumModelDir = wxT( "ALTIUM_EMBEDDED_MODELS" );
1385 
1386  wxFileName altiumModelsPath = wxFileName::DirName( projectPath );
1387  wxString kicadModelPrefix = wxT( "${KIPRJMOD}/" ) + altiumModelDir + wxT( "/" );
1388 
1389  if( !altiumModelsPath.AppendDir( altiumModelDir ) )
1390  {
1391  THROW_IO_ERROR( wxT( "Cannot construct directory path for step models" ) );
1392  }
1393 
1394  // Create dir if it does not exist
1395  if( !altiumModelsPath.DirExists() )
1396  {
1397  if( !altiumModelsPath.Mkdir() )
1398  {
1399  wxLogError( _( "Failed to create folder '%s'." ) + wxS( " " )
1400  + _( "No 3D-models will be imported." ),
1401  altiumModelsPath.GetFullPath() );
1402  return;
1403  }
1404  }
1405 
1406  int idx = 0;
1407  wxString invalidChars = wxFileName::GetForbiddenChars();
1408 
1409  while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
1410  {
1411  checkpoint();
1412  AMODEL elem( reader );
1413 
1414  wxString stepPath = wxString::Format( aRootDir + wxT( "%d" ), idx );
1415  bool validName = !elem.name.IsEmpty() && elem.name.IsAscii() &&
1416  wxString::npos == elem.name.find_first_of( invalidChars );
1417  wxString storageName = !validName ? wxString::Format( wxT( "model_%d" ), idx )
1418  : elem.name;
1419  wxFileName storagePath( altiumModelsPath.GetPath(), storageName );
1420 
1421  idx++;
1422 
1423  const CFB::COMPOUND_FILE_ENTRY* stepEntry = FindStream( aReader, stepPath.c_str() );
1424 
1425  if( stepEntry == nullptr )
1426  {
1427  wxLogError( _( "File not found: '%s'. 3D-model not imported." ), stepPath );
1428  continue;
1429  }
1430 
1431  size_t stepSize = static_cast<size_t>( stepEntry->size );
1432  std::unique_ptr<char[]> stepContent( new char[stepSize] );
1433 
1434  // read file into buffer
1435  aReader.ReadFile( stepEntry, 0, stepContent.get(), stepSize );
1436 
1437  if( !storagePath.IsDirWritable() )
1438  {
1439  wxLogError( _( "Insufficient permissions to save file '%s'." ),
1440  storagePath.GetFullPath() );
1441  continue;
1442  }
1443 
1444  wxMemoryInputStream stepStream( stepContent.get(), stepSize );
1445  wxZlibInputStream zlibInputStream( stepStream );
1446 
1447  wxFFileOutputStream outputStream( storagePath.GetFullPath() );
1448  outputStream.Write( zlibInputStream );
1449  outputStream.Close();
1450 
1451  m_models.insert( { elem.id, kicadModelPrefix + storageName } );
1452  }
1453 
1454  if( reader.GetRemainingBytes() != 0 )
1455  THROW_IO_ERROR( wxT( "Models stream is not fully parsed" ) );
1456 }
1457 
1458 
1459 void ALTIUM_PCB::ParseNets6Data( const CFB::CompoundFileReader& aReader,
1460  const CFB::COMPOUND_FILE_ENTRY* aEntry )
1461 {
1462  if( m_progressReporter )
1463  m_progressReporter->Report( _( "Loading nets..." ) );
1464 
1465  ALTIUM_PARSER reader( aReader, aEntry );
1466 
1467  wxASSERT( m_num_nets == 0 );
1468  while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
1469  {
1470  checkpoint();
1471  ANET6 elem( reader );
1472 
1474  }
1475 
1476  if( reader.GetRemainingBytes() != 0 )
1477  THROW_IO_ERROR( wxT( "Nets6 stream is not fully parsed" ) );
1478 }
1479 
1480 void ALTIUM_PCB::ParsePolygons6Data( const CFB::CompoundFileReader& aReader,
1481  const CFB::COMPOUND_FILE_ENTRY* aEntry )
1482 {
1483  if( m_progressReporter )
1484  m_progressReporter->Report( _( "Loading polygons..." ) );
1485 
1486  ALTIUM_PARSER reader( aReader, aEntry );
1487 
1488  while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
1489  {
1490  checkpoint();
1491  APOLYGON6 elem( reader );
1492 
1493  PCB_LAYER_ID klayer = GetKicadLayer( elem.layer );
1494 
1495  if( klayer == UNDEFINED_LAYER )
1496  {
1497  wxLogWarning( _( "Polygon found on an Altium layer (%d) with no KiCad equivalent. "
1498  "It has been moved to KiCad layer Eco1_User." ),
1499  elem.layer );
1500  klayer = Eco1_User;
1501  }
1502 
1503  SHAPE_LINE_CHAIN linechain;
1505 
1506  if( linechain.PointCount() < 2 )
1507  {
1508  // We have found multiple Altium files with polygon records containing nothing but two
1509  // coincident vertices. These polygons do not appear when opening the file in Altium.
1510  // https://gitlab.com/kicad/code/kicad/-/issues/8183
1511  //
1512  // wxLogError( _( "Polygon has only %d point extracted from %ld vertices. At least 2 "
1513  // "points are required." ),
1514  // linechain.PointCount(),
1515  // elem.vertices.size() );
1516 
1517  m_polygons.emplace_back( nullptr );
1518  continue;
1519  }
1520 
1521  ZONE* zone = new ZONE( m_board );
1522  m_board->Add( zone, ADD_MODE::APPEND );
1523  m_polygons.emplace_back( zone );
1524 
1525  zone->SetFillVersion( 6 );
1526  zone->SetNetCode( GetNetCode( elem.net ) );
1527  zone->SetLayer( klayer );
1528  zone->SetPosition( elem.vertices.at( 0 ).position );
1529  zone->SetLocked( elem.locked );
1530  zone->SetPriority( elem.pourindex > 0 ? elem.pourindex : 0 );
1531  zone->Outline()->AddOutline( linechain );
1532 
1533  if( elem.pourindex > m_highest_pour_index )
1535 
1536  // TODO: more flexible rule parsing
1537  const ARULE6* clearanceRule = GetRuleDefault( ALTIUM_RULE_KIND::PLANE_CLEARANCE );
1538 
1539  if( clearanceRule != nullptr )
1540  zone->SetLocalClearance( clearanceRule->planeclearanceClearance );
1541 
1542  const ARULE6* polygonConnectRule = GetRuleDefault( ALTIUM_RULE_KIND::POLYGON_CONNECT );
1543 
1544  if( polygonConnectRule != nullptr )
1545  {
1546  switch( polygonConnectRule->polygonconnectStyle )
1547  {
1550  break;
1551 
1554  break;
1555 
1556  default:
1559  break;
1560  }
1561 
1562  // TODO: correct variables?
1564  polygonConnectRule->polygonconnectReliefconductorwidth );
1565  zone->SetThermalReliefGap( polygonConnectRule->polygonconnectAirgapwidth );
1566 
1567  if( polygonConnectRule->polygonconnectReliefconductorwidth < zone->GetMinThickness() )
1568  zone->SetMinThickness( polygonConnectRule->polygonconnectReliefconductorwidth );
1569  }
1570 
1571  if( IsAltiumLayerAPlane( elem.layer ) )
1572  {
1573  // outer zone will be set to priority 0 later.
1574  zone->SetPriority( 1 );
1575 
1576  // check if this is the outer zone by simply comparing the BBOX
1577  const auto& outer_plane = m_outer_plane.find( elem.layer );
1578  if( outer_plane == m_outer_plane.end()
1579  || zone->GetBoundingBox().Contains( outer_plane->second->GetBoundingBox() ) )
1580  {
1581  m_outer_plane[elem.layer] = zone;
1582  }
1583  }
1584 
1587  {
1589  zone->SetHatchThickness( elem.trackwidth );
1590 
1592  {
1593  // use a small hack to get us only an outline (hopefully)
1594  const EDA_RECT& bbox = zone->GetBoundingBox();
1595  zone->SetHatchGap( std::max( bbox.GetHeight(), bbox.GetWidth() ) );
1596  }
1597  else
1598  {
1599  zone->SetHatchGap( elem.gridsize - elem.trackwidth );
1600  }
1601 
1603  }
1604 
1606  ZONE::GetDefaultHatchPitch(), true );
1607  }
1608 
1609  if( reader.GetRemainingBytes() != 0 )
1610  {
1611  THROW_IO_ERROR( wxT( "Polygons6 stream is not fully parsed" ) );
1612  }
1613 }
1614 
1615 void ALTIUM_PCB::ParseRules6Data( const CFB::CompoundFileReader& aReader,
1616  const CFB::COMPOUND_FILE_ENTRY* aEntry )
1617 {
1618  if( m_progressReporter )
1619  m_progressReporter->Report( _( "Loading rules..." ) );
1620 
1621  ALTIUM_PARSER reader( aReader, aEntry );
1622 
1623  while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
1624  {
1625  checkpoint();
1626  ARULE6 elem( reader );
1627 
1628  m_rules[elem.kind].emplace_back( elem );
1629  }
1630 
1631  // sort rules by priority
1632  for( auto&& val : m_rules )
1633  {
1634  std::sort( val.second.begin(), val.second.end(),
1635  []( const auto& lhs, const auto& rhs )
1636  {
1637  return lhs.priority < rhs.priority;
1638  } );
1639  }
1640 
1641  if( reader.GetRemainingBytes() != 0 )
1642  {
1643  THROW_IO_ERROR( wxT( "Rules6 stream is not fully parsed" ) );
1644  }
1645 }
1646 
1647 void ALTIUM_PCB::ParseBoardRegionsData( const CFB::CompoundFileReader& aReader,
1648  const CFB::COMPOUND_FILE_ENTRY* aEntry )
1649 {
1650  if( m_progressReporter )
1651  m_progressReporter->Report( _( "Loading board regions..." ) );
1652 
1653  ALTIUM_PARSER reader( aReader, aEntry );
1654 
1655  while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
1656  {
1657  checkpoint();
1658  AREGION6 elem( reader, false );
1659 
1660  // TODO: implement?
1661  }
1662 
1663  if( reader.GetRemainingBytes() != 0 )
1664  {
1665  THROW_IO_ERROR( wxT( "BoardRegions stream is not fully parsed" ) );
1666  }
1667 }
1668 
1669 void ALTIUM_PCB::ParseShapeBasedRegions6Data( const CFB::CompoundFileReader& aReader,
1670  const CFB::COMPOUND_FILE_ENTRY* aEntry )
1671 {
1672  if( m_progressReporter )
1673  m_progressReporter->Report( _( "Loading zones..." ) );
1674 
1675  ALTIUM_PARSER reader( aReader, aEntry );
1676 
1677  while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
1678  {
1679  checkpoint();
1680  AREGION6 elem( reader, true );
1681 
1683  {
1685  }
1686  else if( elem.kind == ALTIUM_REGION_KIND::POLYGON_CUTOUT || elem.is_keepout )
1687  {
1688  SHAPE_LINE_CHAIN linechain;
1690 
1691  if( linechain.PointCount() < 2 )
1692  {
1693  // We have found multiple Altium files with polygon records containing nothing but
1694  // two coincident vertices. These polygons do not appear when opening the file in
1695  // Altium. https://gitlab.com/kicad/code/kicad/-/issues/8183
1696  //
1697  // wxLogError( _( "ShapeBasedRegion has only %d point extracted from %ld vertices. "
1698  // "At least 2 points are required." ),
1699  // linechain.PointCount(),
1700  // elem.outline.size() );
1701  continue;
1702  }
1703 
1704  ZONE* zone = new ZONE( m_board );
1705  m_board->Add( zone, ADD_MODE::APPEND );
1706 
1707  zone->SetFillVersion( 6 );
1708  zone->SetIsRuleArea( true );
1709  zone->SetDoNotAllowTracks( false );
1710  zone->SetDoNotAllowVias( false );
1711  zone->SetDoNotAllowPads( false );
1712  zone->SetDoNotAllowFootprints( false );
1713  zone->SetDoNotAllowCopperPour( true );
1714 
1715  zone->SetPosition( elem.outline.at( 0 ).position );
1716  zone->Outline()->AddOutline( linechain );
1717 
1718  if( elem.layer == ALTIUM_LAYER::MULTI_LAYER )
1719  {
1720  zone->SetLayer( F_Cu );
1721  zone->SetLayerSet( LSET::AllCuMask() );
1722  }
1723  else
1724  {
1725  PCB_LAYER_ID klayer = GetKicadLayer( elem.layer );
1726 
1727  if( klayer == UNDEFINED_LAYER )
1728  {
1729  wxLogWarning( _( "Zone found on an Altium layer (%d) with no KiCad equivalent. "
1730  "It has been moved to KiCad layer Eco1_User." ),
1731  elem.layer );
1732  klayer = Eco1_User;
1733  }
1734  zone->SetLayer( klayer );
1735  }
1736 
1738  ZONE::GetDefaultHatchPitch(), true );
1739  }
1740  else if( elem.kind == ALTIUM_REGION_KIND::COPPER )
1741  {
1742  if( elem.subpolyindex == ALTIUM_POLYGON_NONE )
1743  {
1744  PCB_LAYER_ID klayer = GetKicadLayer( elem.layer );
1745 
1746  if( klayer == UNDEFINED_LAYER )
1747  {
1748  wxLogWarning( _( "Polygon found on an Altium layer (%d) with no KiCad equivalent. "
1749  "It has been moved to KiCad layer Eco1_User." ),
1750  elem.layer );
1751  klayer = Eco1_User;
1752  }
1753 
1754  SHAPE_LINE_CHAIN linechain;
1756 
1757  if( linechain.PointCount() < 2 )
1758  {
1759  // We have found multiple Altium files with polygon records containing nothing
1760  // but two coincident vertices. These polygons do not appear when opening the
1761  // file in Altium. https://gitlab.com/kicad/code/kicad/-/issues/8183
1762  //
1763  // wxLogError( _( "Polygon has only %d point extracted from %ld vertices. At "
1764  // "least 2 points are required." ),
1765  // linechain.PointCount(),
1766  // elem.outline.size() );
1767 
1768  continue;
1769  }
1770 
1771  PCB_SHAPE* shape = new PCB_SHAPE( m_board, SHAPE_T::POLY );
1772  m_board->Add( shape, ADD_MODE::APPEND );
1773  shape->SetFilled( true );
1774  shape->SetLayer( klayer );
1775  shape->SetWidth( 0 );
1776 
1777  shape->SetPolyShape( linechain );
1778  }
1779  }
1780  else
1781  {
1782  wxLogError( _( "Ignored polygon shape of kind %d (not yet supported)." ), elem.kind );
1783  }
1784  }
1785 
1786  if( reader.GetRemainingBytes() != 0 )
1787  {
1788  THROW_IO_ERROR( wxT( "ShapeBasedRegions6 stream is not fully parsed" ) );
1789  }
1790 }
1791 
1792 void ALTIUM_PCB::ParseRegions6Data( const CFB::CompoundFileReader& aReader,
1793  const CFB::COMPOUND_FILE_ENTRY* aEntry )
1794 {
1795  if( m_progressReporter )
1796  m_progressReporter->Report( _( "Loading zone fills..." ) );
1797 
1798  ALTIUM_PARSER reader( aReader, aEntry );
1799 
1800  for( ZONE* zone : m_polygons )
1801  {
1802  if( zone )
1803  zone->UnFill(); // just to be sure
1804  }
1805 
1806  while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
1807  {
1808  checkpoint();
1809  AREGION6 elem( reader, false );
1810 
1811  if( elem.subpolyindex != ALTIUM_POLYGON_NONE )
1812  {
1813  if( m_polygons.size() <= elem.subpolyindex )
1814  {
1815  THROW_IO_ERROR( wxString::Format( "Region stream tries to access polygon id %d "
1816  "of %d existing polygons.",
1817  elem.subpolyindex,
1818  m_polygons.size() ) );
1819  }
1820 
1821  ZONE *zone = m_polygons.at( elem.subpolyindex );
1822 
1823  if( zone == nullptr )
1824  {
1825  continue; // we know the zone id, but because we do not know the layer we did not add it!
1826  }
1827 
1828  PCB_LAYER_ID klayer = GetKicadLayer( elem.layer );
1829  if( klayer == UNDEFINED_LAYER )
1830  {
1831  continue; // Just skip it for now. Users can fill it themselves.
1832  }
1833 
1834  SHAPE_LINE_CHAIN linechain;
1835  for( const ALTIUM_VERTICE& vertice : elem.outline )
1836  {
1837  linechain.Append( vertice.position );
1838  }
1839  linechain.Append( elem.outline.at( 0 ).position );
1840  linechain.SetClosed( true );
1841 
1842  SHAPE_POLY_SET rawPolys;
1843  rawPolys.AddOutline( linechain );
1844 
1845  for( const std::vector<ALTIUM_VERTICE>& hole : elem.holes )
1846  {
1847  SHAPE_LINE_CHAIN hole_linechain;
1848  for( const ALTIUM_VERTICE& vertice : hole )
1849  {
1850  hole_linechain.Append( vertice.position );
1851  }
1852  hole_linechain.Append( hole.at( 0 ).position );
1853  hole_linechain.SetClosed( true );
1854  rawPolys.AddHole( hole_linechain );
1855  }
1856 
1857  if( zone->GetFilledPolysUseThickness() )
1858  rawPolys.Deflate( zone->GetMinThickness() / 2, 32 );
1859 
1860  if( zone->HasFilledPolysForLayer( klayer ) )
1861  rawPolys.BooleanAdd( zone->RawPolysList( klayer ),
1863 
1864  SHAPE_POLY_SET finalPolys = rawPolys;
1866 
1867  zone->SetRawPolysList( klayer, rawPolys );
1868  zone->SetFilledPolysList( klayer, finalPolys );
1869  zone->SetIsFilled( true );
1870  zone->SetNeedRefill( false );
1871  }
1872  }
1873 
1874  if( reader.GetRemainingBytes() != 0 )
1875  {
1876  THROW_IO_ERROR( wxT( "Regions6 stream is not fully parsed" ) );
1877  }
1878 }
1879 
1880 
1881 void ALTIUM_PCB::ParseArcs6Data( const CFB::CompoundFileReader& aReader,
1882  const CFB::COMPOUND_FILE_ENTRY* aEntry )
1883 {
1884  if( m_progressReporter )
1885  m_progressReporter->Report( _( "Loading arcs..." ) );
1886 
1887  ALTIUM_PARSER reader( aReader, aEntry );
1888 
1889  while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
1890  {
1891  checkpoint();
1892  AARC6 elem( reader );
1893 
1894  if( elem.is_polygonoutline || elem.subpolyindex != ALTIUM_POLYGON_NONE )
1895  continue;
1896 
1897  // element in plane is in fact substracted from the plane. Should be already done by Altium?
1898  //if( IsAltiumLayerAPlane( elem.layer ) )
1899  // continue;
1900 
1901  PCB_LAYER_ID klayer = GetKicadLayer( elem.layer );
1902 
1903  if( elem.is_keepout || IsAltiumLayerAPlane( elem.layer ) )
1904  {
1905  PCB_SHAPE shape( nullptr ); // just a helper to get the graphic
1906  shape.SetWidth( elem.width );
1907 
1908  if( elem.startangle == 0. && elem.endangle == 360. )
1909  { // TODO: other variants to define circle?
1910  shape.SetShape( SHAPE_T::CIRCLE );
1911  shape.SetStart( elem.center );
1912  shape.SetEnd( elem.center - wxPoint( 0, elem.radius ) );
1913  }
1914  else
1915  {
1916  shape.SetShape( SHAPE_T::ARC );
1917 
1918  double includedAngle = elem.endangle - elem.startangle;
1919  double startradiant = DEG2RAD( elem.startangle );
1920  wxPoint arcStartOffset = wxPoint( KiROUND( std::cos( startradiant ) * elem.radius ),
1921  -KiROUND( std::sin( startradiant ) * elem.radius ) );
1922 
1923  shape.SetCenter( elem.center );
1924  shape.SetStart( elem.center + arcStartOffset );
1925  shape.SetArcAngleAndEnd( -NormalizeAngleDegreesPos( includedAngle ) * 10.0, true );
1926  }
1927 
1928  ZONE* zone = new ZONE( m_board );
1929  m_board->Add( zone, ADD_MODE::APPEND );
1930 
1931  zone->SetFillVersion( 6 );
1932  zone->SetIsRuleArea( true );
1933  zone->SetDoNotAllowTracks( false );
1934  zone->SetDoNotAllowVias( false );
1935  zone->SetDoNotAllowPads( false );
1936  zone->SetDoNotAllowFootprints( false );
1937  zone->SetDoNotAllowCopperPour( true );
1938 
1939  if( elem.layer == ALTIUM_LAYER::MULTI_LAYER )
1940  {
1941  zone->SetLayer( F_Cu );
1942  zone->SetLayerSet( LSET::AllCuMask() );
1943  }
1944  else
1945  {
1946  PCB_LAYER_ID klayer = GetKicadLayer( elem.layer );
1947 
1948  if( klayer == UNDEFINED_LAYER )
1949  {
1950  wxLogWarning( _( "Arc keepout found on an Altium layer (%d) with no KiCad "
1951  "equivalent. It has been moved to KiCad layer Eco1_User." ),
1952  elem.layer );
1953  klayer = Eco1_User;
1954  }
1955  zone->SetLayer( klayer );
1956  }
1957 
1958  shape.TransformShapeWithClearanceToPolygon( *zone->Outline(), klayer, 0, ARC_HIGH_DEF,
1959  ERROR_INSIDE );
1960  zone->Outline()->Simplify( SHAPE_POLY_SET::PM_STRICTLY_SIMPLE ); // the outline is not a single polygon!
1961 
1963  ZONE::GetDefaultHatchPitch(), true );
1964  continue;
1965  }
1966 
1967  if( klayer == UNDEFINED_LAYER )
1968  {
1969  wxLogWarning( _( "Arc found on an Altium layer (%d) with no KiCad equivalent. "
1970  "It has been moved to KiCad layer Eco1_User." ),
1971  elem.layer );
1972  klayer = Eco1_User;
1973  }
1974 
1975  if( klayer >= F_Cu && klayer <= B_Cu )
1976  {
1977  double angle = -NormalizeAngleDegreesPos( elem.endangle - elem.startangle );
1978  double startradiant = DEG2RAD( elem.startangle );
1979  wxPoint arcStartOffset = wxPoint( KiROUND( std::cos( startradiant ) * elem.radius ),
1980  -KiROUND( std::sin( startradiant ) * elem.radius ) );
1981 
1982  arcStartOffset += elem.center;
1983 
1984  // If it's a circle then add two 180-degree arcs
1985  if( elem.startangle == 0. && elem.endangle == 360. )
1986  angle = 180.;
1987 
1988  SHAPE_ARC shapeArc( elem.center, arcStartOffset, angle, elem.width );
1989  PCB_ARC* arc = new PCB_ARC( m_board, &shapeArc );
1990  m_board->Add( arc, ADD_MODE::APPEND );
1991 
1992  arc->SetWidth( elem.width );
1993  arc->SetLayer( klayer );
1994  arc->SetNetCode( GetNetCode( elem.net ) );
1995 
1996  // Add second 180-degree arc for a circle
1997  if( elem.startangle == 0. && elem.endangle == 360. )
1998  {
1999  shapeArc = SHAPE_ARC( elem.center, arcStartOffset, -angle, elem.width );
2000  arc = new PCB_ARC( m_board, &shapeArc );
2001  m_board->Add( arc, ADD_MODE::APPEND );
2002 
2003  arc->SetWidth( elem.width );
2004  arc->SetLayer( klayer );
2005  arc->SetNetCode( GetNetCode( elem.net ) );
2006  }
2007  }
2008  else
2009  {
2010  PCB_SHAPE* shape = HelperCreateAndAddShape( elem.component );
2011  shape->SetWidth( elem.width );
2012  shape->SetLayer( klayer );
2013 
2014  if( elem.startangle == 0. && elem.endangle == 360. )
2015  { // TODO: other variants to define circle?
2016  shape->SetShape( SHAPE_T::CIRCLE );
2017  shape->SetStart( elem.center );
2018  shape->SetEnd( elem.center - wxPoint( 0, elem.radius ) );
2019  }
2020  else
2021  {
2022  shape->SetShape( SHAPE_T::ARC );
2023 
2024  double includedAngle = elem.endangle - elem.startangle;
2025  double startradiant = DEG2RAD( elem.startangle );
2026  wxPoint arcStartOffset = wxPoint( KiROUND( std::cos( startradiant ) * elem.radius ),
2027  -KiROUND( std::sin( startradiant ) * elem.radius ) );
2028 
2029  shape->SetCenter( elem.center );
2030  shape->SetStart( elem.center + arcStartOffset );
2031  shape->SetArcAngleAndEnd( -NormalizeAngleDegreesPos( includedAngle ) * 10.0, true );
2032  }
2033 
2034  HelperShapeSetLocalCoord( shape, elem.component );
2035  }
2036  }
2037 
2038  if( reader.GetRemainingBytes() != 0 )
2039  {
2040  THROW_IO_ERROR( wxT( "Arcs6 stream is not fully parsed" ) );
2041  }
2042 }
2043 
2044 
2045 void ALTIUM_PCB::ParsePads6Data( const CFB::CompoundFileReader& aReader,
2046  const CFB::COMPOUND_FILE_ENTRY* aEntry )
2047 {
2048  if( m_progressReporter )
2049  m_progressReporter->Report( _( "Loading pads..." ) );
2050 
2051  ALTIUM_PARSER reader( aReader, aEntry );
2052 
2053  while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
2054  {
2055  checkpoint();
2056  APAD6 elem( reader );
2057 
2058  // It is possible to place altium pads on non-copper layers -> we need to interpolate them using drawings!
2059  if( !IsAltiumLayerCopper( elem.layer ) && !IsAltiumLayerAPlane( elem.layer )
2060  && elem.layer != ALTIUM_LAYER::MULTI_LAYER )
2061  {
2062  HelperParsePad6NonCopper( elem );
2063  continue;
2064  }
2065 
2066  // Create Pad
2067  FOOTPRINT* footprint = nullptr;
2068 
2069  if( elem.component == ALTIUM_COMPONENT_NONE )
2070  {
2071  footprint = new FOOTPRINT( m_board ); // We cannot add a pad directly into the PCB
2072  m_board->Add( footprint, ADD_MODE::APPEND );
2073  footprint->SetPosition( elem.position );
2074  }
2075  else
2076  {
2077  if( m_components.size() <= elem.component )
2078  {
2079  THROW_IO_ERROR( wxString::Format( wxT( "Pads6 stream tries to access component id "
2080  "%d of %d existing components" ),
2081  elem.component,
2082  m_components.size() ) );
2083  }
2084  footprint = m_components.at( elem.component );
2085  }
2086 
2087  PAD* pad = new PAD( footprint );
2088  footprint->Add( pad, ADD_MODE::APPEND );
2089 
2090  pad->SetNumber( elem.name );
2091  pad->SetNetCode( GetNetCode( elem.net ) );
2092  pad->SetLocked( elem.is_locked );
2093 
2094  pad->SetPosition( elem.position );
2095  pad->SetOrientationDegrees( elem.direction );
2096  pad->SetLocalCoord();
2097 
2098  pad->SetSize( elem.topsize );
2099 
2100  if( elem.holesize == 0 )
2101  {
2102  pad->SetAttribute( PAD_ATTRIB::SMD );
2103  }
2104  else
2105  {
2106  if( elem.layer != ALTIUM_LAYER::MULTI_LAYER )
2107  {
2108  // TODO: I assume other values are possible as well?
2109  wxLogError( _( "Footprint %s pad %s is not marked as multilayer, but is a TH pad." ),
2110  footprint->GetReference(),
2111  elem.name );
2112  }
2113  pad->SetAttribute( elem.plated ? PAD_ATTRIB::PTH :
2114  PAD_ATTRIB::NPTH );
2115  if( !elem.sizeAndShape || elem.sizeAndShape->holeshape == ALTIUM_PAD_HOLE_SHAPE::ROUND )
2116  {
2118  pad->SetDrillSize( wxSize( elem.holesize, elem.holesize ) );
2119  }
2120  else
2121  {
2122  switch( elem.sizeAndShape->holeshape )
2123  {
2125  wxFAIL_MSG( wxT( "Round holes are handled before the switch" ) );
2126  break;
2127 
2129  wxLogWarning( _( "Footprint %s pad %s has a square hole (not yet supported)." ),
2130  footprint->GetReference(),
2131  elem.name );
2133  pad->SetDrillSize( wxSize( elem.holesize, elem.holesize ) ); // Workaround
2134  // TODO: elem.sizeAndShape->slotsize was 0 in testfile. Either use holesize in this case or rect holes have a different id
2135  break;
2136 
2138  {
2140  double normalizedSlotrotation =
2141  NormalizeAngleDegreesPos( elem.sizeAndShape->slotrotation );
2142 
2143  if( normalizedSlotrotation == 0. || normalizedSlotrotation == 180. )
2144  {
2145  pad->SetDrillSize( wxSize( elem.sizeAndShape->slotsize, elem.holesize ) );
2146  }
2147  else if( normalizedSlotrotation == 90. || normalizedSlotrotation == 270. )
2148  {
2149  pad->SetDrillSize( wxSize( elem.holesize, elem.sizeAndShape->slotsize ) );
2150  }
2151  else
2152  {
2153  wxLogWarning( _( "Footprint %s pad %s has a hole-rotation of %f "
2154  "degrees. KiCad only supports 90 degree rotations." ),
2155  footprint->GetReference(),
2156  elem.name,
2157  normalizedSlotrotation );
2158  }
2159 
2160  }
2161  break;
2162 
2163  default:
2165  wxLogError( _( "Footprint %s pad %s uses a hole of unknown kind %d." ),
2166  footprint->GetReference(),
2167  elem.name,
2168  elem.sizeAndShape->holeshape );
2170  pad->SetDrillSize( wxSize( elem.holesize, elem.holesize ) ); // Workaround
2171  break;
2172  }
2173  }
2174 
2175  if( elem.sizeAndShape )
2176  {
2177  pad->SetOffset( elem.sizeAndShape->holeoffset[0] );
2178  }
2179  }
2180 
2181  if( elem.padmode != ALTIUM_PAD_MODE::SIMPLE )
2182  {
2183  wxLogError( _( "Footprint %s pad %s uses a complex pad stack (not yet supported.)" ),
2184  footprint->GetReference(),
2185  elem.name );
2186  }
2187 
2188  switch( elem.topshape )
2189  {
2191  pad->SetShape( PAD_SHAPE::RECT );
2192  break;
2194  if( elem.sizeAndShape
2195  && elem.sizeAndShape->alt_shape[0] == ALTIUM_PAD_SHAPE_ALT::ROUNDRECT )
2196  {
2197  pad->SetShape( PAD_SHAPE::ROUNDRECT ); // 100 = round, 0 = rectangular
2198  double ratio = elem.sizeAndShape->cornerradius[0] / 200.;
2199  pad->SetRoundRectRadiusRatio( ratio );
2200  }
2201  else if( elem.topsize.x == elem.topsize.y )
2202  {
2203  pad->SetShape( PAD_SHAPE::CIRCLE );
2204  }
2205  else
2206  {
2207  pad->SetShape( PAD_SHAPE::OVAL );
2208  }
2209  break;
2211  pad->SetShape( PAD_SHAPE::CHAMFERED_RECT );
2212  pad->SetChamferPositions( RECT_CHAMFER_ALL );
2213  pad->SetChamferRectRatio( 0.25 );
2214  break;
2216  default:
2217  wxLogError( _( "Footprint %s pad %s uses an unknown pad-shape." ),
2218  footprint->GetReference(),
2219  elem.name );
2220  break;
2221  }
2222 
2223  if( pad->GetAttribute() == PAD_ATTRIB::NPTH && pad->GetDrillSizeX() )
2224  {
2225  // KiCad likes NPTH pads to be the same size & shape as their holes
2226  pad->SetShape( pad->GetDrillShape() == PAD_DRILL_SHAPE_CIRCLE ? PAD_SHAPE::CIRCLE
2227  : PAD_SHAPE::OVAL );
2228  pad->SetSize( pad->GetDrillSize() );
2229  }
2230 
2231  switch( elem.layer )
2232  {
2234  pad->SetLayer( F_Cu );
2235  pad->SetLayerSet( PAD::SMDMask() );
2236  break;
2238  pad->SetLayer( B_Cu );
2239  pad->SetLayerSet( FlipLayerMask( PAD::SMDMask() ) );
2240  break;
2242  pad->SetLayerSet( elem.plated ? PAD::PTHMask() : PAD::UnplatedHoleMask() );
2243  break;
2244  default:
2245  PCB_LAYER_ID klayer = GetKicadLayer( elem.layer );
2246  pad->SetLayer( klayer );
2247  pad->SetLayerSet( LSET( 1, klayer ) );
2248  break;
2249  }
2250 
2252  {
2253  pad->SetLocalSolderPasteMargin( elem.pastemaskexpansionmanual );
2254  }
2255 
2257  {
2258  pad->SetLocalSolderMaskMargin( elem.soldermaskexpansionmanual );
2259  }
2260 
2261  if( elem.is_tent_top )
2262  {
2263  pad->SetLayerSet( pad->GetLayerSet().reset( F_Mask ) );
2264  }
2265  if( elem.is_tent_bottom )
2266  {
2267  pad->SetLayerSet( pad->GetLayerSet().reset( B_Mask ) );
2268  }
2269  }
2270 
2271  if( reader.GetRemainingBytes() != 0 )
2272  {
2273  THROW_IO_ERROR( wxT( "Pads6 stream is not fully parsed" ) );
2274  }
2275 }
2276 
2277 
2279 {
2280  PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
2281 
2282  if( klayer == UNDEFINED_LAYER )
2283  {
2284  wxLogWarning( _( "Non-copper pad %s found on an Altium layer (%d) with no KiCad equivalent. "
2285  "It has been moved to KiCad layer Eco1_User." ),
2286  aElem.name,
2287  aElem.layer );
2288  klayer = Eco1_User;
2289  }
2290 
2291  if( aElem.net != ALTIUM_NET_UNCONNECTED )
2292  {
2293  wxLogError( _( "Non-copper pad %s is connected to a net, which is not supported." ),
2294  aElem.name );
2295  }
2296 
2297  if( aElem.holesize != 0 )
2298  {
2299  wxLogError( _( "Non-copper pad %s has a hole, which is not supported." ), aElem.name );
2300  }
2301 
2302  if( aElem.padmode != ALTIUM_PAD_MODE::SIMPLE )
2303  {
2304  wxLogWarning( _( "Non-copper pad %s has a complex pad stack (not yet supported)." ),
2305  aElem.name );
2306  }
2307 
2308  switch( aElem.topshape )
2309  {
2311  {
2312  // filled rect
2313  PCB_SHAPE* shape = HelperCreateAndAddShape( aElem.component );
2314  shape->SetShape( SHAPE_T::POLY );
2315  shape->SetFilled( true );
2316  shape->SetLayer( klayer );
2317  shape->SetWidth( 0 );
2318 
2319  shape->SetPolyPoints( { aElem.position + wxPoint( aElem.topsize.x / 2, aElem.topsize.y / 2 ),
2320  aElem.position + wxPoint( aElem.topsize.x / 2, -aElem.topsize.y / 2 ),
2321  aElem.position + wxPoint( -aElem.topsize.x / 2, -aElem.topsize.y / 2 ),
2322  aElem.position + wxPoint( -aElem.topsize.x / 2, aElem.topsize.y / 2 ) } );
2323 
2324  if( aElem.direction != 0 )
2325  shape->Rotate( aElem.position, aElem.direction * 10 );
2326 
2327  HelperShapeSetLocalCoord( shape, aElem.component );
2328  }
2329  break;
2330 
2332  if( aElem.sizeAndShape
2333  && aElem.sizeAndShape->alt_shape[0] == ALTIUM_PAD_SHAPE_ALT::ROUNDRECT )
2334  {
2335  // filled roundrect
2336  int cornerradius = aElem.sizeAndShape->cornerradius[0];
2337  int offset = ( std::min( aElem.topsize.x, aElem.topsize.y ) * cornerradius ) / 200;
2338 
2339  PCB_SHAPE* shape = HelperCreateAndAddShape( aElem.component );
2340  shape->SetLayer( klayer );
2341  shape->SetWidth( offset * 2 );
2342 
2343  if( cornerradius < 100 )
2344  {
2345  int offsetX = aElem.topsize.x / 2 - offset;
2346  int offsetY = aElem.topsize.y / 2 - offset;
2347 
2348  wxPoint p11 = aElem.position + wxPoint( offsetX, offsetY );
2349  wxPoint p12 = aElem.position + wxPoint( offsetX, -offsetY );
2350  wxPoint p22 = aElem.position + wxPoint( -offsetX, -offsetY );
2351  wxPoint p21 = aElem.position + wxPoint( -offsetX, offsetY );
2352 
2353  shape->SetShape( SHAPE_T::POLY );
2354  shape->SetFilled( true );
2355  shape->SetPolyPoints( { p11, p12, p22, p21 } );
2356  }
2357  else if( aElem.topsize.x == aElem.topsize.y )
2358  {
2359  // circle
2360  shape->SetShape( SHAPE_T::CIRCLE );
2361  shape->SetFilled( true );
2362  shape->SetStart( aElem.position );
2363  shape->SetEnd( aElem.position - wxPoint( 0, aElem.topsize.x / 4 ) );
2364  shape->SetWidth( aElem.topsize.x / 2 );
2365  }
2366  else if( aElem.topsize.x < aElem.topsize.y )
2367  {
2368  // short vertical line
2369  shape->SetShape( SHAPE_T::SEGMENT );
2370  wxPoint pointOffset( 0, ( aElem.topsize.y - aElem.topsize.x ) / 2 );
2371  shape->SetStart( aElem.position + pointOffset );
2372  shape->SetEnd( aElem.position - pointOffset );
2373  }
2374  else
2375  {
2376  // short horizontal line
2377  shape->SetShape( SHAPE_T::SEGMENT );
2378  wxPoint pointOffset( ( aElem.topsize.x - aElem.topsize.y ) / 2, 0 );
2379  shape->SetStart( aElem.position + pointOffset );
2380  shape->SetEnd( aElem.position - pointOffset );
2381  }
2382 
2383  if( aElem.direction != 0 )
2384  shape->Rotate( aElem.position, aElem.direction * 10 );
2385 
2386  HelperShapeSetLocalCoord( shape, aElem.component );
2387  }
2388  else if( aElem.topsize.x == aElem.topsize.y )
2389  {
2390  // filled circle
2391  PCB_SHAPE* shape = HelperCreateAndAddShape( aElem.component );
2392  shape->SetShape( SHAPE_T::CIRCLE );
2393  shape->SetFilled( true );
2394  shape->SetLayer( klayer );
2395  shape->SetStart( aElem.position );
2396  shape->SetEnd( aElem.position - wxPoint( 0, aElem.topsize.x / 4 ) );
2397  shape->SetWidth( aElem.topsize.x / 2 );
2398  HelperShapeSetLocalCoord( shape, aElem.component );
2399  }
2400  else
2401  {
2402  // short line
2403  PCB_SHAPE* shape = HelperCreateAndAddShape( aElem.component );
2404  shape->SetShape( SHAPE_T::SEGMENT );
2405  shape->SetLayer( klayer );
2406  shape->SetWidth( std::min( aElem.topsize.x, aElem.topsize.y ) );
2407 
2408  if( aElem.topsize.x < aElem.topsize.y )
2409  {
2410  wxPoint offset( 0, ( aElem.topsize.y - aElem.topsize.x ) / 2 );
2411  shape->SetStart( aElem.position + offset );
2412  shape->SetEnd( aElem.position - offset );
2413  }
2414  else
2415  {
2416  wxPoint offset( ( aElem.topsize.x - aElem.topsize.y ) / 2, 0 );
2417  shape->SetStart( aElem.position + offset );
2418  shape->SetEnd( aElem.position - offset );
2419  }
2420 
2421  if( aElem.direction != 0 )
2422  shape->Rotate( aElem.position, aElem.direction * 10. );
2423 
2424  HelperShapeSetLocalCoord( shape, aElem.component );
2425  }
2426  break;
2427 
2429  {
2430  // filled octagon
2431  PCB_SHAPE* shape = HelperCreateAndAddShape( aElem.component );
2432  shape->SetShape( SHAPE_T::POLY );
2433  shape->SetFilled( true );
2434  shape->SetLayer( klayer );
2435  shape->SetWidth( 0 );
2436 
2437  wxPoint p11 = aElem.position + wxPoint( aElem.topsize.x / 2, aElem.topsize.y / 2 );
2438  wxPoint p12 = aElem.position + wxPoint( aElem.topsize.x / 2, -aElem.topsize.y / 2 );
2439  wxPoint p22 = aElem.position + wxPoint( -aElem.topsize.x / 2, -aElem.topsize.y / 2 );
2440  wxPoint p21 = aElem.position + wxPoint( -aElem.topsize.x / 2, aElem.topsize.y / 2 );
2441 
2442  int chamfer = std::min( aElem.topsize.x, aElem.topsize.y ) / 4;
2443  wxPoint chamferX( chamfer, 0 );
2444  wxPoint chamferY( 0, chamfer );
2445 
2446  shape->SetPolyPoints( { p11 - chamferX, p11 - chamferY, p12 + chamferY, p12 - chamferX,
2447  p22 + chamferX, p22 + chamferY, p21 - chamferY, p21 + chamferX } );
2448 
2449  if( aElem.direction != 0. )
2450  shape->Rotate( aElem.position, aElem.direction * 10 );
2451 
2452  HelperShapeSetLocalCoord( shape, aElem.component );
2453  }
2454  break;
2455 
2457  default:
2458  wxLogError( _( "Non-copper pad %s uses an unknown pad-shape." ), aElem.name );
2459  break;
2460  }
2461 }
2462 
2463 void ALTIUM_PCB::ParseVias6Data( const CFB::CompoundFileReader& aReader,
2464  const CFB::COMPOUND_FILE_ENTRY* aEntry )
2465 {
2466  if( m_progressReporter )
2467  m_progressReporter->Report( _( "Loading vias..." ) );
2468 
2469  ALTIUM_PARSER reader( aReader, aEntry );
2470 
2471  while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
2472  {
2473  checkpoint();
2474  AVIA6 elem( reader );
2475 
2476  PCB_VIA* via = new PCB_VIA( m_board );
2478 
2479  via->SetPosition( elem.position );
2480  via->SetWidth( elem.diameter );
2481  via->SetDrill( elem.holesize );
2482  via->SetNetCode( GetNetCode( elem.net ) );
2483  via->SetLocked( elem.is_locked );
2484 
2485  bool start_layer_outside = elem.layer_start == ALTIUM_LAYER::TOP_LAYER
2487  bool end_layer_outside = elem.layer_end == ALTIUM_LAYER::TOP_LAYER
2489 
2490  if( start_layer_outside && end_layer_outside )
2491  {
2492  via->SetViaType( VIATYPE::THROUGH );
2493  }
2494  else if( ( !start_layer_outside ) && ( !end_layer_outside ) )
2495  {
2496  via->SetViaType( VIATYPE::BLIND_BURIED );
2498  }
2499  else
2500  {
2501  via->SetViaType( VIATYPE::MICROVIA ); // TODO: always a microvia?
2503  }
2504 
2505  PCB_LAYER_ID start_klayer = GetKicadLayer( elem.layer_start );
2506  PCB_LAYER_ID end_klayer = GetKicadLayer( elem.layer_end );
2507 
2508  if( !IsCopperLayer( start_klayer ) || !IsCopperLayer( end_klayer ) )
2509  {
2510  wxLogError( _( "Via from layer %d to %d uses a non-copper layer, which is not "
2511  "supported." ),
2512  elem.layer_start,
2513  elem.layer_end );
2514  continue; // just assume through-hole instead.
2515  }
2516 
2517  // we need VIATYPE set!
2518  via->SetLayerPair( start_klayer, end_klayer );
2519  }
2520 
2521  if( reader.GetRemainingBytes() != 0 )
2522  {
2523  THROW_IO_ERROR( wxT( "Vias6 stream is not fully parsed" ) );
2524  }
2525 }
2526 
2527 void ALTIUM_PCB::ParseTracks6Data( const CFB::CompoundFileReader& aReader,
2528  const CFB::COMPOUND_FILE_ENTRY* aEntry )
2529 {
2530  if( m_progressReporter )
2531  m_progressReporter->Report( _( "Loading tracks..." ) );
2532 
2533  ALTIUM_PARSER reader( aReader, aEntry );
2534 
2535  while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
2536  {
2537  checkpoint();
2538  ATRACK6 elem( reader );
2539 
2540  if( elem.is_polygonoutline || elem.subpolyindex != ALTIUM_POLYGON_NONE )
2541  continue;
2542 
2543  // element in plane is in fact substracted from the plane. Already done by Altium?
2544  //if( IsAltiumLayerAPlane( elem.layer ) )
2545  // continue;
2546 
2547  PCB_LAYER_ID klayer = GetKicadLayer( elem.layer );
2548 
2549  if( elem.is_keepout || IsAltiumLayerAPlane( elem.layer ) )
2550  {
2551  PCB_SHAPE shape( nullptr, SHAPE_T::SEGMENT );
2552  shape.SetStart( elem.start );
2553  shape.SetEnd( elem.end );
2554  shape.SetWidth( elem.width );
2555 
2556  ZONE* zone = new ZONE( m_board );
2557  m_board->Add( zone, ADD_MODE::APPEND );
2558 
2559  zone->SetFillVersion( 6 );
2560  zone->SetIsRuleArea( true );
2561  zone->SetDoNotAllowTracks( false );
2562  zone->SetDoNotAllowVias( false );
2563  zone->SetDoNotAllowPads( false );
2564  zone->SetDoNotAllowFootprints( false );
2565  zone->SetDoNotAllowCopperPour( true );
2566 
2567  if( elem.layer == ALTIUM_LAYER::MULTI_LAYER )
2568  {
2569  zone->SetLayer( F_Cu );
2570  zone->SetLayerSet( LSET::AllCuMask() );
2571  }
2572  else
2573  {
2574  PCB_LAYER_ID klayer = GetKicadLayer( elem.layer );
2575 
2576  if( klayer == UNDEFINED_LAYER )
2577  {
2578  wxLogWarning( _( "Track keepout found on an Altium layer (%d) with no KiCad "
2579  "equivalent. It has been moved to KiCad layer Eco1_User." ),
2580  elem.layer );
2581  klayer = Eco1_User;
2582  }
2583  zone->SetLayer( klayer );
2584  }
2585 
2586  shape.TransformShapeWithClearanceToPolygon( *zone->Outline(), klayer, 0, ARC_HIGH_DEF,
2587  ERROR_INSIDE );
2588 
2590  ZONE::GetDefaultHatchPitch(), true );
2591  continue;
2592  }
2593 
2594  if( klayer == UNDEFINED_LAYER )
2595  {
2596  wxLogWarning( _( "Track found on an Altium layer (%d) with no KiCad equivalent. "
2597  "It has been moved to KiCad layer Eco1_User." ),
2598  elem.layer );
2599  klayer = Eco1_User;
2600  }
2601 
2602  if( klayer >= F_Cu && klayer <= B_Cu )
2603  {
2604  PCB_TRACK* track = new PCB_TRACK( m_board );
2605  m_board->Add( track, ADD_MODE::APPEND );
2606 
2607  track->SetStart( elem.start );
2608  track->SetEnd( elem.end );
2609  track->SetWidth( elem.width );
2610  track->SetLayer( klayer );
2611  track->SetNetCode( GetNetCode( elem.net ) );
2612  }
2613  else
2614  {
2615  PCB_SHAPE* shape = HelperCreateAndAddShape( elem.component );
2616  shape->SetShape( SHAPE_T::SEGMENT );
2617  shape->SetStart( elem.start );
2618  shape->SetEnd( elem.end );
2619  shape->SetWidth( elem.width );
2620  shape->SetLayer( klayer );
2621  HelperShapeSetLocalCoord( shape, elem.component );
2622  }
2623 
2624  reader.SkipSubrecord();
2625  }
2626 
2627  if( reader.GetRemainingBytes() != 0 )
2628  {
2629  THROW_IO_ERROR( wxT( "Tracks6 stream is not fully parsed" ) );
2630  }
2631 }
2632 
2633 void ALTIUM_PCB::ParseWideStrings6Data( const CFB::CompoundFileReader& aReader,
2634  const CFB::COMPOUND_FILE_ENTRY* aEntry )
2635 {
2636  if( m_progressReporter )
2637  m_progressReporter->Report( _( "Loading unicode strings..." ) );
2638 
2639  ALTIUM_PARSER reader( aReader, aEntry );
2640 
2642 
2643  if( reader.GetRemainingBytes() != 0 )
2644  THROW_IO_ERROR( wxT( "WideStrings6 stream is not fully parsed" ) );
2645 }
2646 
2647 void ALTIUM_PCB::ParseTexts6Data( const CFB::CompoundFileReader& aReader,
2648  const CFB::COMPOUND_FILE_ENTRY* aEntry )
2649 {
2650  if( m_progressReporter )
2651  m_progressReporter->Report( _( "Loading text..." ) );
2652 
2653  ALTIUM_PARSER reader( aReader, aEntry );
2654 
2655  while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
2656  {
2657  checkpoint();
2658  ATEXT6 elem( reader, m_unicodeStrings );
2659 
2660  if( elem.fonttype == ALTIUM_TEXT_TYPE::BARCODE )
2661  {
2662  wxLogError( _( "Ignored barcode on Altium layer %d (not yet supported)." ),
2663  elem.layer );
2664  continue;
2665  }
2666 
2667  // TODO: better approach to select if item belongs to a FOOTPRINT
2668  EDA_TEXT* tx = nullptr;
2669  BOARD_ITEM* itm = nullptr;
2670 
2671  if( elem.component == ALTIUM_COMPONENT_NONE )
2672  {
2673  PCB_TEXT* pcbText = new PCB_TEXT( m_board );
2674  tx = pcbText;
2675  itm = pcbText;
2676  m_board->Add( pcbText, ADD_MODE::APPEND );
2677  }
2678  else
2679  {
2680  if( m_components.size() <= elem.component )
2681  {
2682  THROW_IO_ERROR( wxString::Format( wxT( "Texts6 stream tries to access component "
2683  "id %d of %d existing components" ),
2684  elem.component,
2685  m_components.size() ) );
2686  }
2687 
2688  FOOTPRINT* footprint = m_components.at( elem.component );
2689  FP_TEXT* fpText;
2690 
2691  if( elem.isDesignator )
2692  {
2693  fpText = &footprint->Reference();
2694  }
2695  else if( elem.isComment )
2696  {
2697  fpText = &footprint->Value();
2698  }
2699  else
2700  {
2701  fpText = new FP_TEXT( footprint );
2702  footprint->Add( fpText, ADD_MODE::APPEND );
2703  }
2704 
2705  fpText->SetKeepUpright( false );
2706 
2707  tx = fpText;
2708  itm = fpText;
2709  }
2710 
2711  wxString trimmedText = elem.text.Trim();
2712  if( !elem.isDesignator && trimmedText.CmpNoCase( wxT( ".Designator" ) ) == 0 )
2713  {
2714  tx->SetText( wxT( "${REFERENCE}" ) );
2715  }
2716  else if( !elem.isComment && trimmedText.CmpNoCase( wxT( ".Comment" ) ) == 0 )
2717  {
2718  tx->SetText( wxT( "${VALUE}" ) );
2719  }
2720  else if( trimmedText.CmpNoCase( wxT( ".Layer_Name" ) ) == 0 )
2721  {
2722  tx->SetText( wxT( "${LAYER}" ) );
2723  }
2724  else
2725  {
2726  tx->SetText( elem.text );
2727  }
2728 
2729  itm->SetPosition( elem.position );
2730  tx->SetTextAngle( elem.rotation * 10. );
2731 
2732  if( elem.component != ALTIUM_COMPONENT_NONE )
2733  {
2734  FP_TEXT* fpText = dynamic_cast<FP_TEXT*>( tx );
2735 
2736  if( fpText )
2737  {
2738  FOOTPRINT* parentFootprint = static_cast<FOOTPRINT*>( fpText->GetParent() );
2739  double orientation = parentFootprint->GetOrientation();
2740 
2741  fpText->SetTextAngle( fpText->GetTextAngle() - orientation );
2742  fpText->SetLocalCoord();
2743  }
2744  }
2745 
2746  PCB_LAYER_ID klayer = GetKicadLayer( elem.layer );
2747 
2748  if( klayer == UNDEFINED_LAYER )
2749  {
2750  wxLogWarning( _( "Text found on an Altium layer (%d) with no KiCad equivalent. "
2751  "It has been moved to KiCad layer Eco1_User." ),
2752  elem.layer );
2753  klayer = Eco1_User;
2754  }
2755 
2756  itm->SetLayer( klayer );
2757 
2758  if( elem.fonttype == ALTIUM_TEXT_TYPE::TRUETYPE )
2759  {
2760  // TODO: why is this required? Somehow, truetype size is calculated differently
2761  tx->SetTextSize( wxSize( elem.height / 2, elem.height / 2 ) );
2762  }
2763  else
2764  {
2765  tx->SetTextSize( wxSize( elem.height, elem.height ) ); // TODO: parse text width
2766  }
2767 
2768  tx->SetTextThickness( elem.strokewidth );
2769  tx->SetBold( elem.isBold );
2770  tx->SetItalic( elem.isItalic );
2771  tx->SetMirrored( elem.isMirrored );
2772 
2773  if( elem.isDesignator || elem.isComment ) // That's just a bold assumption
2774  {
2777  }
2778  else
2779  {
2780  switch( elem.textposition )
2781  {
2786  break;
2791  break;
2796  break;
2797  default:
2798  wxLogError( wxT( "Unexpected horizontal Text Position. This should never happen." ) );
2799  break;
2800  }
2801 
2802  switch( elem.textposition )
2803  {
2808  break;
2813  break;
2818  break;
2819  default:
2820  wxLogError( wxT( "Unexpected vertical text position. This should never happen." ) );
2821  break;
2822  }
2823  }
2824  }
2825 
2826  if( reader.GetRemainingBytes() != 0 )
2827  {
2828  THROW_IO_ERROR( wxT( "Texts6 stream is not fully parsed" ) );
2829  }
2830 }
2831 
2832 void ALTIUM_PCB::ParseFills6Data( const CFB::CompoundFileReader& aReader,
2833  const CFB::COMPOUND_FILE_ENTRY* aEntry )
2834 {
2835  if( m_progressReporter )
2836  m_progressReporter->Report( _( "Loading rectangles..." ) );
2837 
2838  ALTIUM_PARSER reader( aReader, aEntry );
2839 
2840  while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
2841  {
2842  checkpoint();
2843  AFILL6 elem( reader );
2844 
2845  wxPoint p11( elem.pos1.x, elem.pos1.y );
2846  wxPoint p12( elem.pos1.x, elem.pos2.y );
2847  wxPoint p22( elem.pos2.x, elem.pos2.y );
2848  wxPoint p21( elem.pos2.x, elem.pos1.y );
2849 
2850  wxPoint center( ( elem.pos1.x + elem.pos2.x ) / 2, ( elem.pos1.y + elem.pos2.y ) / 2 );
2851 
2852  PCB_LAYER_ID klayer = GetKicadLayer( elem.layer );
2853 
2854  if( klayer == UNDEFINED_LAYER )
2855  {
2856  wxLogWarning( _( "Fill found on an Altium layer (%d) with no KiCad equivalent. "
2857  "It has been moved to KiCad layer Eco1_User." ),
2858  elem.layer );
2859  klayer = Eco1_User;
2860  }
2861 
2862  if( elem.is_keepout || elem.net != ALTIUM_NET_UNCONNECTED )
2863  {
2864  ZONE* zone = new ZONE( m_board );
2865  m_board->Add( zone, ADD_MODE::APPEND );
2866 
2867  zone->SetFillVersion( 6 );
2868  zone->SetNetCode( GetNetCode( elem.net ) );
2869  zone->SetLayer( klayer );
2870  zone->SetPosition( elem.pos1 );
2871  zone->SetPriority( 1000 );
2872 
2873  const int outlineIdx = -1; // this is the id of the copper zone main outline
2874  zone->AppendCorner( p11, outlineIdx );
2875  zone->AppendCorner( p12, outlineIdx );
2876  zone->AppendCorner( p22, outlineIdx );
2877  zone->AppendCorner( p21, outlineIdx );
2878 
2879  // should be correct?
2880  zone->SetLocalClearance( 0 );
2882 
2883  if( elem.is_keepout )
2884  {
2885  zone->SetIsRuleArea( true );
2886  zone->SetDoNotAllowTracks( false );
2887  zone->SetDoNotAllowVias( false );
2888  zone->SetDoNotAllowPads( false );
2889  zone->SetDoNotAllowFootprints( false );
2890  zone->SetDoNotAllowCopperPour( true );
2891  }
2892 
2893  if( elem.rotation != 0. )
2894  zone->Rotate( center, elem.rotation * 10 );
2895 
2897  ZONE::GetDefaultHatchPitch(), true );
2898  }
2899  else
2900  {
2901  PCB_SHAPE* shape = new PCB_SHAPE( m_board, SHAPE_T::POLY );
2902  m_board->Add( shape, ADD_MODE::APPEND );
2903  shape->SetFilled( true );
2904  shape->SetLayer( klayer );
2905  shape->SetWidth( 0 );
2906 
2907  shape->SetPolyPoints( { p11, p12, p22, p21 } );
2908 
2909  if( elem.rotation != 0. )
2910  shape->Rotate( center, elem.rotation * 10 );
2911  }
2912  }
2913 
2914  if( reader.GetRemainingBytes() != 0 )
2915  {
2916  THROW_IO_ERROR( wxT( "Fills6 stream is not fully parsed" ) );
2917  }
2918 }
void SetMirrored(bool isMirrored)
Definition: eda_text.h:209
double EuclideanNorm(const wxPoint &vector)
Euclidean norm of a 2D vector.
Definition: trigo.h:146
void SetReference(const wxString &aReference)
Definition: footprint.h:475
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:759
uint32_t textlinewidth
uint32_t linewidth
FP_3DMODEL::VECTOR3D modelPosition
int32_t soldermaskexpansionmanual
ALTIUM_RULE_KIND kind
double rotation
uint32_t holesize
const SHAPE_ARC & Arc(size_t aArc) const
void ParseAltiumPcb(BOARD *aBoard, const wxString &aFileName, PROGRESS_REPORTER *aProgressReporter, const std::map< ALTIUM_PCB_DIR, std::string > &aFileMapping)
Helper method which opens a Altium Board File and parses it.
Definition: altium_pcb.cpp:58
void SetUnits(EDA_UNITS aUnits)
void SetModified()
Definition: eda_item.cpp:65
uint32_t width
virtual wxPoint GetCenter() const override
This defaults to the center of the bounding box if not overridden.
Definition: pcb_track.h:281
void Parse(const CFB::CompoundFileReader &aReader, const std::map< ALTIUM_PCB_DIR, std::string > &aFileMapping)
Definition: altium_pcb.cpp:348
int planeclearanceClearance
void SetHatchThickness(int aThickness)
Definition: zone.h:254
void ParseVias6Data(const CFB::CompoundFileReader &aReader, const CFB::COMPOUND_FILE_ENTRY *aEntry)
wxString name
bool isDesignator
const PAGE_INFO & GetPageSettings() const
Definition: board.h:536
LSET FlipLayerMask(LSET aMask, int aCopperLayersCount)
Calculate the mask layer when flipping a footprint.
Definition: lset.cpp:576
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:629
std::vector< ZONE * > m_polygons
Definition: altium_pcb.h:195
double GetOrientationRadians() const
Definition: footprint.h:193
wxPoint pos1
virtual void Report(const wxString &aMessage)=0
Display aMessage in the progress bar dialog.
double GetLineLength(const wxPoint &aPointA, const wxPoint &aPointB)
Return the length of a line segment defined by aPointA and aPointB.
Definition: trigo.h:222
ALTIUM_LAYER layer
const ARULE6 * GetRule(ALTIUM_RULE_KIND aKind, const wxString &aName) const
Definition: altium_pcb.cpp:653
std::list< FP_3DMODEL > & Models()
Definition: footprint.h:183
void SetHeight(int aHeight)
Set the distance from the feature points to the crossbar line.
void SetDoNotAllowTracks(bool aEnable)
Definition: zone.h:747
wxPoint start
wxSize topsize
void SetFilled(bool aFlag)
Definition: eda_shape.h:92
void SetEnd(const wxPoint &aEnd)
Definition: eda_shape.h:135
bool m_LegacyNetclassesLoaded
True if netclasses were loaded from the file.
Definition: board.h:273
void ParseComponents6Data(const CFB::CompoundFileReader &aReader, const CFB::COMPOUND_FILE_ENTRY *aEntry)
Definition: altium_pcb.cpp:923
virtual void SetPosition(const wxPoint &aPos) override
Definition: pcb_text.h:81
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.
void SetArcGeometry(const wxPoint &aStart, const wxPoint &aMid, const wxPoint &aEnd)
Set the three controlling points for an arc.
Definition: eda_shape.cpp:504
unsigned m_totalCount
for progress reporting
Definition: altium_pcb.h:208
Manage layers needed to make a physical board.
ALTIUM_DIMENSION_KIND kind
void SetUnitsFormat(const DIM_UNITS_FORMAT aFormat)
virtual void SetLayer(PCB_LAYER_ID aLayer)
Set the layer this item is on.
Definition: board_item.h:164
virtual void SetPosition(const wxPoint &aPos)
Definition: eda_item.h:252
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition: board_item.h:49
void SetBorderDisplayStyle(ZONE_BORDER_DISPLAY_STYLE aHatchStyle, int aHatchPitch, bool aRebuildHatch)
Set all hatch parameters for the zone.
Definition: zone.cpp:875
ALTIUM_REGION_KIND kind
Definition: board.h:73
int GetX() const
Definition: eda_rect.h:107
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:738
void ParsePolygons6Data(const CFB::CompoundFileReader &aReader, const CFB::COMPOUND_FILE_ENTRY *aEntry)
PCB_TEXT & Text()
A progress reporter interface for use in multi-threaded environments.
void ParseWideStrings6Data(const CFB::CompoundFileReader &aReader, const CFB::COMPOUND_FILE_ENTRY *aEntry)
void SetPolyShape(const SHAPE_POLY_SET &aShape)
Definition: eda_shape.h:235
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:187
void SetTextAngle(double aAngle) override
Definition: fp_text.cpp:73
double startangle
size_t GetRemainingBytes() const
void SetLayerSet(LSET aLayerSet) override
Definition: zone.cpp:250
uint16_t component
SHAPE_POLY_SET * Outline()
Definition: zone.h:320
void SetItalic(bool isItalic)
Definition: eda_text.h:200
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
int GetWidth() const
Definition: eda_rect.h:118
ALTIUM_LAYER layer
double GetOrientation() const
Definition: footprint.h:191
double GetTextAngle() const
Definition: eda_text.h:195
void SetCopperLayerCount(int aCount)
Definition: board.cpp:461
bool SetNetCode(int aNetCode, bool aNoAssert)
Set net using a net code.
std::vector< std::vector< ALTIUM_VERTICE > > holes
class PCB_ARC, an arc track segment on a copper layer
Definition: typeinfo.h:97
wxString sourcedesignator
const uint16_t ALTIUM_POLYGON_NONE
ALTIUM_LAYER layer
void SetArcAngleAndEnd(double aAngle, bool aCheckNegativeAngle=false)
Set the end point from the angle center and start.
Definition: eda_shape.cpp:547
ALTIUM_LAYER layer_end
void ParseComponentsBodies6Data(const CFB::CompoundFileReader &aReader, const CFB::COMPOUND_FILE_ENTRY *aEntry)
Definition: altium_pcb.cpp:970
void SetOrientationDegrees(double aOrientation)
Definition: footprint.h:190
Abstract dimension API.
Definition: pcb_dimension.h:95
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:258
virtual void SetLocked(bool aLocked)
Modify the 'lock' status for of the item.
Definition: board_item.h:218
uint16_t net
std::map< uint32_t, wxString > ReadWideStringTable()
Definition: altium_parser.h:87
bool SetLayerType(PCB_LAYER_ID aLayer, LAYER_T aLayerType)
Change the type of the layer given by aLayer.
Definition: board.cpp:410
wxString sourcefootprintlibrary
void RotatePoint(int *pX, int *pY, double angle)
Definition: trigo.cpp:229
A logical library item identifier and consists of various portions much like a URI.
Definition: lib_id.h:51
const wxPoint & GetGridOrigin()
wxString name
virtual void Rotate(const wxPoint &aRotCentre, double aAngle) override
Rotate this object.
Definition: pcb_shape.cpp:109
unsigned m_lastProgressCount
Definition: altium_pcb.h:207
PCB_LAYER_ID GetKicadLayer(ALTIUM_LAYER aAltiumLayer) const
Definition: altium_pcb.cpp:209
wxString text
int PointCount() const
Return the number of points (vertices) in this line chain.
void SetPriority(unsigned aPriority)
Definition: zone.h:117
const wxString & GetFileName() const
Definition: board.h:229
VECTOR3D m_Offset
3D model offset (mm)
Definition: footprint.h:96
void ParseArcs6Data(const CFB::CompoundFileReader &aReader, const CFB::COMPOUND_FILE_ENTRY *aEntry)
wxString scope2expr
BOARD_DESIGN_SETTINGS & GetDesignSettings() const
Definition: board.cpp:590
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:203
bool Contains(const wxPoint &aPoint) const
Definition: eda_rect.cpp:57
void checkpoint()
Definition: altium_pcb.cpp:329
const ARULE6 * GetRuleDefault(ALTIUM_RULE_KIND aKind) const
Definition: altium_pcb.cpp:670
void Append(int aX, int aY, bool aAllowDuplication=false)
Append a new point at the end of the line chain.
ALTIUM_CONNECT_STYLE polygonconnectStyle
Plated through hole pad.
int GetNetCode(uint16_t aId) const
Definition: altium_pcb.cpp:635
FP_TEXT & Value()
read/write accessors:
Definition: footprint.h:502
std::vector< wxString > names
void SetLineThickness(int aWidth)
For better understanding of the points that make a dimension:
int32_t trackwidth
void HelperParseDimensions6Radial(const ADIMENSION6 &aElem)
std::vector< wxPoint > textPoint
void SetIsRuleArea(bool aEnable)
Definition: zone.h:744
wxString textformat
FP_TEXT & Reference()
Definition: footprint.h:503
A mix-in class (via multiple inheritance) that handles texts such as labels, parts,...
Definition: eda_text.h:140
void SetStart(const wxPoint &aStart)
Definition: eda_shape.h:110
std::map< wxString, wxString > m_models
Definition: altium_pcb.h:197
ssize_t ArcIndex(size_t aSegment) const
Return the arc index for the given segment index.
ALTIUM_PCB(BOARD *aBoard, PROGRESS_REPORTER *aProgressReporter)
Definition: altium_pcb.cpp:314
void Rotate(const wxPoint &aCentre, double aAngle) override
Move the outlines.
Definition: zone.cpp:689
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)
virtual void SetVisible(bool aVisible)
Definition: eda_text.h:206
bool IsArcStart(size_t aIndex) const
void Add(BOARD_ITEM *aItem, ADD_MODE aMode=ADD_MODE::INSERT) override
Adds an item to the container.
Definition: board.cpp:608
void ParsePads6Data(const CFB::CompoundFileReader &aReader, const CFB::COMPOUND_FILE_ENTRY *aEntry)
void SetClosed(bool aClosed)
Mark the line chain as closed (i.e.
void HelperParseDimensions6Leader(const ADIMENSION6 &aElem)
std::vector< FOOTPRINT * > m_components
Definition: altium_pcb.h:194
PCB_SHAPE * HelperCreateAndAddShape(uint16_t aComponent)
Definition: altium_pcb.cpp:117
void ParseRegions6Data(const CFB::CompoundFileReader &aReader, const CFB::COMPOUND_FILE_ENTRY *aEntry)
like PAD_PTH, but not plated
size_t ReadAndSetSubrecordLength()
BOARD_STACKUP & GetStackupDescriptor()
double NormalizeAngleDegreesPos(double Angle)
Normalize angle to be in the 0.0 .. 360.0 range: angle is in degrees.
Definition: trigo.h:297
BOARD * m_board
Definition: altium_pcb.h:193
ALTIUM_PAD_RULE pastemaskexpansionmode
wxPoint position
uint16_t subpolyindex
double direction
LSET is a set of PCB_LAYER_IDs.
Definition: layer_ids.h:516
std::map< ALTIUM_LAYER, PCB_LAYER_ID > m_layermap
Definition: altium_pcb.h:200
pads are covered by copper
void ParseDimensions6Data(const CFB::CompoundFileReader &aReader, const CFB::COMPOUND_FILE_ENTRY *aEntry)
ALTIUM_RULE_KIND
void SetAuxOrigin(const wxPoint &aOrigin)
int GetMinThickness() const
Definition: zone.h:244
wxString textsuffix
virtual void SetText(const wxString &aText)
Definition: eda_text.cpp:124
const EDA_RECT GetBoundingBox() const override
Definition: zone.cpp:320
void SetHatchGap(int aStep)
Definition: zone.h:257
double endangle
void Move(const VECTOR2I &aVector) override
wxString id
virtual wxPoint GetPosition() const override
Definition: pcb_text.h:76
void ParseTexts6Data(const CFB::CompoundFileReader &aReader, const CFB::COMPOUND_FILE_ENTRY *aEntry)
const wxPoint position
static int GetDefaultHatchPitch()
Definition: zone.cpp:1038
wxString NotSpecifiedPrm()
void SetDoNotAllowPads(bool aEnable)
Definition: zone.h:748
Represent a set of closed polygons.
virtual void SetEnd(const wxPoint &aPoint)
ALTIUM_CLASS_KIND kind
double GetRadius() const
Definition: pcb_track.cpp:946
void SetVertJustify(EDA_TEXT_VJUSTIFY_T aType)
Definition: eda_text.h:223
const VECTOR2I & GetP0() const
Definition: shape_arc.h:111
ALTIUM_LAYER layer
const uint16_t ALTIUM_NET_UNCONNECTED
unsigned m_doneCount
Definition: altium_pcb.h:206
void SetPrefix(const wxString &aPrefix)
bool SetLayerName(PCB_LAYER_ID aLayer, const wxString &aLayerName)
Changes the name of the layer given by aLayer.
Definition: board.cpp:378
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:884
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:169
const VECTOR2I & GetArcMid() const
Definition: shape_arc.h:113
void ParseFileHeader(const CFB::CompoundFileReader &aReader, const CFB::COMPOUND_FILE_ENTRY *aEntry)
Definition: altium_pcb.cpp:687
static LSET PTHMask()
layer set for a through hole pad
Definition: pad.cpp:159
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:466
#define _(s)
uint16_t component
void SetDoNotAllowVias(bool aEnable)
Definition: zone.h:746
double m_Opacity
Definition: footprint.h:97
void SetWidth(int aWidth)
Definition: eda_shape.h:97
NETCLASSES & GetNetClasses() const
virtual wxPoint GetCenter() const
This defaults to the center of the bounding box if not overridden.
Definition: board_item.h:81
uint32_t holesize
bool m_BlindBuriedViaAllowed
true to allow blind/buried vias
void Simplify(POLYGON_MODE aFastMode)
Handle a list of polygons defining a copper zone.
Definition: zone.h:56
wxPoint sheetpos
wxString textprefix
void SetMinThickness(int aMinThickness)
Definition: zone.h:245
void SetIsFilled(bool isFilled)
Definition: zone.h:235
void SetPrecision(int aPrecision)
void SetCenter(const wxPoint &aCenter)
Definition: eda_shape.cpp:419
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:119
const std::vector< BOARD_STACKUP_ITEM * > & GetList() const
void ParseModelsData(const CFB::CompoundFileReader &aReader, const CFB::COMPOUND_FILE_ENTRY *aEntry, const wxString &aRootDir)
SHAPE_POLY_SET & GetPolyShape()
Definition: eda_shape.h:227
bool IsCopperLayer(LAYER_NUM aLayerId)
Tests whether a layer is a copper layer.
Definition: layer_ids.h:808
void SetFilledPolysList(PCB_LAYER_ID aLayer, const SHAPE_POLY_SET &aPolysList)
Set the list of filled polygons.
Definition: zone.h:652
size_t m_num_nets
Definition: altium_pcb.h:199
int m_highest_pour_index
Altium stores pour order across all layers.
Definition: altium_pcb.h:211
bool is_keepout
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:98
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:268
uint16_t subpolyindex
wxPoint GetPosition() const override
Definition: pcb_shape.h:77
bool IsFlipped() const
Definition: footprint.h:281
Use thermal relief for pads.
void SetFPID(const LIB_ID &aFPID)
Definition: footprint.h:196
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
VECTOR2< T > Resize(T aNewLength) const
Return a vector of the same direction, but length specified in aNewLength.
Definition: vector2d.h:404
void SetWidth(int aWidth)
Definition: pcb_track.h:101
void ParseTracks6Data(const CFB::CompoundFileReader &aReader, const CFB::COMPOUND_FILE_ENTRY *aEntry)
LIB_ID AltiumToKiCadLibID(const wxString &aLibName, const wxString &aLibReference)
void Move(const wxPoint &aMoveVector) override
Move this object.
Definition: pcb_text.h:86
Handle the data for a net.
Definition: netinfo.h:66
void SetPolyPoints(const std::vector< wxPoint > &aPoints)
Definition: eda_shape.cpp:1085
SEG Segment(int aIndex)
Return a copy of the aIndex-th segment in the line chain.
void SetPadConnection(ZONE_CONNECTION aPadConnection)
Definition: zone.h:242
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:815
void HelperParseDimensions6Linear(const ADIMENSION6 &aElem)
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:95
ALTIUM_LAYER layer
uint16_t net
ALTIUM_LAYER layer_start
const char * name
Definition: DXF_plotter.cpp:56
const wxPoint & GetAuxOrigin()
double DEG2RAD(double deg)
Definition: trigo.h:229
void HelperParsePad6NonCopper(const APAD6 &aElem)
void ParseNets6Data(const CFB::CompoundFileReader &aReader, const CFB::COMPOUND_FILE_ENTRY *aEntry)
Information pertinent to a Pcbnew printed circuit board.
Definition: board.h:191
void SetHorizJustify(EDA_TEXT_HJUSTIFY_T aType)
Definition: eda_text.h:222
void SetDoNotAllowCopperPour(bool aEnable)
Definition: zone.h:745
Represent a polyline containing arcs as well as line segments: A chain of connected line and/or arc s...
PCB_LAYER_ID
A quick note on layer IDs:
Definition: layer_ids.h:65
uint32_t radius
wxPoint position
Definition: layer_ids.h:71
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
VECTOR2I A
Definition: seg.h:48
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:307
void SetShape(SHAPE_T aShape)
Definition: eda_shape.h:100
uint16_t net
void SetLocalCoord()
Definition: fp_text.cpp:209
void HelperCreateBoardOutline(const std::vector< ALTIUM_VERTICE > &aVertices)
Definition: altium_pcb.cpp:848
void SetHatchOrientation(double aStep)
Definition: zone.h:260
int GetY() const
Definition: eda_rect.h:108
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:73
void SetGridOrigin(const wxPoint &aOrigin)
Pads are not covered.
wxPoint GetPosition() const override
Definition: footprint.h:187
double NormalizeAngleDegrees(double Angle, double aMin, double aMax)
Normalize angle to be aMin < angle <= aMax angle is in degrees.
Definition: trigo.h:327
SHAPE_POLY_SET & RawPolysList(PCB_LAYER_ID aLayer)
Definition: zone.h:720
bool IsAltiumLayerCopper(ALTIUM_LAYER aLayer)
Definition: altium_pcb.cpp:105
void HelperShapeSetLocalCoord(PCB_SHAPE *aShape, uint16_t aComponent)
Definition: altium_pcb.cpp:144
void SetFillVersion(int aVersion)
Definition: zone.h:695
ALTIUM_LAYER
void SetThermalReliefGap(int aThermalReliefGap)
Definition: zone.h:183
std::map< ALTIUM_RULE_KIND, std::vector< ARULE6 > > m_rules
Definition: altium_pcb.h:201
ALTIUM_PAD_MODE padmode
ALTIUM_LAYER layer
std::unique_ptr< APAD6_SIZE_AND_SHAPE > sizeAndShape
A leader is a dimension-like object pointing to a specific point.
void SetLocalCoord()
Set relative coordinates from draw coordinates.
Definition: fp_shape.cpp:52
virtual bool KeepRefreshing(bool aWait=false)=0
Update the UI (if any).
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:242
std::vector< ALTIUM_VERTICE > board_vertices
void SetTextThickness(int aWidth)
The TextThickness is that set by the user.
Definition: eda_text.h:180
static LSET UnplatedHoleMask()
layer set for a mechanical unplated through hole pad
Definition: pad.cpp:180
const int ALTIUM_COMPONENT_NONE
ALTIUM_UNIT textunit
void SetNeedRefill(bool aNeedRefill)
Definition: zone.h:238
ALTIUM_LAYER layer
std::vector< PCB_DIMENSION_BASE * > m_radialDimensions
Definition: altium_pcb.h:196
void SetLocalClearance(int aClearance)
Definition: zone.h:158
virtual void SetTextAngle(double aAngle)
Definition: eda_text.h:188
SHAPE_T GetShape() const
Definition: eda_shape.h:101
bool m_MicroViasAllowed
true to allow micro vias
void Add(BOARD_ITEM *aItem, ADD_MODE aMode=ADD_MODE::INSERT) override
Removes an item from the container.
Definition: footprint.cpp:513
void SkipSubrecord()
void SetRawPolysList(PCB_LAYER_ID aLayer, const SHAPE_POLY_SET &aPolysList)
Set the list of filled polygons.
Definition: zone.h:660
int NextShape(int aPointIndex, bool aForwards=true) const
Return the vertex index of the next shape in the chain, or -1 if aPointIndex is the last shape.
int GetRadius() const
Definition: eda_shape.cpp:472
std::vector< wxPoint > referencePoint
Definition: pad.h:57
void SetPosition(const wxPoint &aPos) override
Definition: footprint.cpp:1562
std::vector< ABOARD6_LAYER_STACKUP > stackup
uint32_t strokewidth
void TransformShapeWithClearanceToPolygon(SHAPE_POLY_SET &aCornerBuffer, PCB_LAYER_ID aLayer, int aClearanceValue, int aError, ERROR_LOC aErrorLoc, bool ignoreLineWidth=false) const override
Convert the shape to a closed polygon.
Definition: pcb_shape.cpp:236
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:706
BOARD_ITEM_CONTAINER * GetParent() const
Definition: board_item.h:136
bool is_tent_top
bool IsAltiumLayerAPlane(ALTIUM_LAYER aLayer)
Definition: altium_pcb.cpp:111
int32_t gridsize
class PCB_SHAPE, a segment not on copper layers
Definition: typeinfo.h:90
static LSET SMDMask()
layer set for a SMD pad on Front layer
Definition: pad.cpp:166
void SetBold(bool aBold)
Definition: eda_text.h:203
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:372
uint16_t component
#define THROW_IO_ERROR(msg)
Definition: ki_exception.h:38
bool HasParsingError()
DRAWINGS & Drawings()
Definition: board.h:237
ALTIUM_TEXT_TYPE fonttype
int32_t textprecision
wxPoint pos2
uint32_t diameter
const VECTOR2I & GetP1() const
Definition: shape_arc.h:112
PROGRESS_REPORTER * m_progressReporter
optional; may be nullptr
Definition: altium_pcb.h:205
bool is_locked
TRACKS & Tracks()
Definition: board.h:231
wxString name
std::map< uint32_t, wxString > m_unicodeStrings
Definition: altium_pcb.h:198
std::function< void(const CFB::CompoundFileReader &, const CFB::COMPOUND_FILE_ENTRY *)> PARSE_FUNCTION_POINTER_fp
Definition: altium_pcb.h:112
uint16_t net
void SetDoNotAllowFootprints(bool aEnable)
Definition: zone.h:749
bool GetFilledPolysUseThickness() const
Definition: zone.h:691
Container for design settings for a BOARD object.
wxString scope1expr
int32_t polygonconnectReliefconductorwidth
int32_t pourindex
void SetThermalReliefSpokeWidth(int aThermalReliefSpokeWidth)
Definition: zone.h:194
virtual void SetCurrentProgress(double aProgress)=0
Set the progress value to aProgress (0..1).
VECTOR2I B
Definition: seg.h:49