KiCad PCB EDA Suite
altium_pcb.cpp
Go to the documentation of this file.
1 /*
2  * This program source code file is part of KiCad, a free EDA CAD application.
3  *
4  * Copyright (C) 2019-2020 Thomas Pointhuber <thomas.pointhuber@gmx.at>
5  * 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, "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( "Component creator tries to access component id %d "
130  "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::HelperDrawsegmentSetLocalCoord( 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( auto& vertice : aVertices )
173  {
174  if( vertice.isRound )
175  {
176  double angle = NormalizeAngleDegreesPos( vertice.endangle - vertice.startangle );
177 
178  double startradiant = DEG2RAD( vertice.startangle );
179  double endradiant = DEG2RAD( vertice.endangle );
180  wxPoint arcStartOffset = wxPoint( KiROUND( std::cos( startradiant ) * vertice.radius ),
181  -KiROUND( std::sin( startradiant ) * vertice.radius ) );
182 
183  wxPoint arcEndOffset = wxPoint( KiROUND( std::cos( endradiant ) * vertice.radius ),
184  -KiROUND( std::sin( endradiant ) * vertice.radius ) );
185 
186  wxPoint arcStart = vertice.center + arcStartOffset;
187  wxPoint arcEnd = vertice.center + arcEndOffset;
188 
189  if( GetLineLength( arcStart, vertice.position )
190  < GetLineLength( arcEnd, vertice.position ) )
191  {
192  aLine.Append( SHAPE_ARC( vertice.center, arcStart, -angle ) );
193  }
194  else
195  {
196  aLine.Append( SHAPE_ARC( vertice.center, arcEnd, angle ) );
197  }
198  }
199  else
200  {
201  aLine.Append( vertice.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;
293  case ALTIUM_LAYER::MECHANICAL_13: return F_Fab;
294  case ALTIUM_LAYER::MECHANICAL_14: return B_Fab;
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  } },
424  { true, ALTIUM_PCB_DIR::TEXTS6,
425  [this]( auto aReader, auto fileHeader )
426  {
427  this->ParseTexts6Data( aReader, fileHeader );
428  } },
429  { true, ALTIUM_PCB_DIR::FILLS6,
430  [this]( auto aReader, auto fileHeader )
431  {
432  this->ParseFills6Data( aReader, fileHeader );
433  } },
435  [this]( auto aReader, auto fileHeader )
436  {
437  this->ParseBoardRegionsData( aReader, fileHeader );
438  } },
440  [this]( auto aReader, auto fileHeader )
441  {
442  this->ParseShapeBasedRegions6Data( aReader, fileHeader );
443  } },
444  { true, ALTIUM_PCB_DIR::REGIONS6,
445  [this]( auto aReader, auto fileHeader )
446  {
447  this->ParseRegions6Data( aReader, fileHeader );
448  } }
449  };
450 
451  if( m_progressReporter != nullptr )
452  {
453  // Count number of records we will read for the progress reporter
454  for( const std::tuple<bool, ALTIUM_PCB_DIR, PARSE_FUNCTION_POINTER_fp>& cur : parserOrder )
455  {
456  bool isRequired;
459  std::tie( isRequired, directory, fp ) = cur;
460 
462  {
463  continue;
464  }
465 
466  const auto& mappedDirectory = aFileMapping.find( directory );
467  if( mappedDirectory == aFileMapping.end() )
468  {
469  continue;
470  }
471 
472  std::string mappedFile = mappedDirectory->second + "Header";
473 
474  const CFB::COMPOUND_FILE_ENTRY* file = FindStream( aReader, mappedFile.c_str() );
475  if( file == nullptr )
476  {
477  continue;
478  }
479 
480  ALTIUM_PARSER reader( aReader, file );
481  uint32_t numOfRecords = reader.Read<uint32_t>();
482 
483  if( reader.HasParsingError() )
484  {
485  wxLogError( _( "'%s' was not parsed correctly." ), mappedFile );
486  continue;
487  }
488 
489  m_totalCount += numOfRecords;
490 
491  if( reader.GetRemainingBytes() != 0 )
492  {
493  wxLogError( _( "'%s' was not fully parsed." ), mappedFile );
494  continue;
495  }
496  }
497  }
498 
499  // Parse data in specified order
500  for( const std::tuple<bool, ALTIUM_PCB_DIR, PARSE_FUNCTION_POINTER_fp>& cur : parserOrder )
501  {
502  bool isRequired;
505  std::tie( isRequired, directory, fp ) = cur;
506 
507  const auto& mappedDirectory = aFileMapping.find( directory );
508 
509  if( mappedDirectory == aFileMapping.end() )
510  {
511  wxASSERT_MSG( !isRequired, wxString::Format( "Altium Directory of kind %d was expected, "
512  "but no mapping is present in the code",
513  directory ) );
514  continue;
515  }
516 
517  std::string mappedFile = mappedDirectory->second;
518 
520  mappedFile += "Data";
521 
522  const CFB::COMPOUND_FILE_ENTRY* file = FindStream( aReader, mappedFile.c_str() );
523 
524  if( file != nullptr )
525  fp( aReader, file );
526  else if( isRequired )
527  wxLogError( _( "File not found: '%s'." ), mappedFile );
528  }
529 
530  // fixup zone priorities since Altium stores them in the opposite order
531  for( ZONE* zone : m_polygons )
532  {
533  if( !zone )
534  continue;
535 
536  // Altium "fills" - not poured in Altium
537  if( zone->GetPriority() == 1000 )
538  {
539  // Unlikely, but you never know
540  if( m_highest_pour_index >= 1000 )
541  zone->SetPriority( m_highest_pour_index + 1 );
542 
543  continue;
544  }
545 
546  int priority = m_highest_pour_index - zone->GetPriority();
547 
548  zone->SetPriority( priority >= 0 ? priority : 0 );
549  }
550 
551  // change priority of outer zone to zero
552  for( std::pair<const ALTIUM_LAYER, ZONE*>& zone : m_outer_plane )
553  zone.second->SetPriority( 0 );
554 
555  // Altium doesn't appear to store either the dimension value nor the dimensioned object in
556  // the dimension record. (Yes, there is a REFERENCE0OBJECTID, but it doesn't point to the
557  // dimensioned object.) We attempt to plug this gap by finding a colocated arc or circle
558  // and using its radius. If there are more than one such arcs/circles, well, :shrug:.
560  {
561  int radius = 0;
562 
563  for( BOARD_ITEM* item : m_board->Drawings() )
564  {
565  if( item->Type() != PCB_SHAPE_T )
566  continue;
567 
568  PCB_SHAPE* shape = static_cast<PCB_SHAPE*>( item );
569 
570  if( shape->GetShape() != SHAPE_T::ARC && shape->GetShape() != SHAPE_T::CIRCLE )
571  continue;
572 
573  if( shape->GetPosition() == dim->GetPosition() )
574  {
575  radius = shape->GetRadius();
576  break;
577  }
578  }
579 
580  if( radius == 0 )
581  {
582  for( PCB_TRACK* track : m_board->Tracks() )
583  {
584  if( track->Type() != PCB_ARC_T )
585  continue;
586 
587  PCB_ARC* arc = static_cast<PCB_ARC*>( track );
588 
589  if( arc->GetCenter() == dim->GetPosition() )
590  {
591  radius = arc->GetRadius();
592  break;
593  }
594  }
595  }
596 
597  // Force a measured value, calculate the value text, and then stick it into the override
598  // text (since leaders don't have calculated text).
599  dim->SetMeasuredValue( radius );
600  dim->SetText( dim->GetPrefix() + dim->GetValueText() + dim->GetSuffix() );
601  dim->SetPrefix( wxEmptyString );
602  dim->SetSuffix( wxEmptyString );
603 
604  // Move the leader line start to the radius point
605  VECTOR2I radialLine = dim->GetEnd() - dim->GetStart();
606  radialLine = radialLine.Resize( radius );
607  dim->SetStart( dim->GetStart() + (wxPoint) radialLine );
608  }
609 
610  // center board
612 
613  int w = m_board->GetPageSettings().GetWidthIU();
614  int h = m_board->GetPageSettings().GetHeightIU();
615 
616  int desired_x = ( w - bbbox.GetWidth() ) / 2;
617  int desired_y = ( h - bbbox.GetHeight() ) / 2;
618 
619  wxPoint movementVector( desired_x - bbbox.GetX(), desired_y - bbbox.GetY() );
620  m_board->Move( movementVector );
621  m_board->GetDesignSettings().m_AuxOrigin += movementVector;
622  m_board->GetDesignSettings().m_GridOrigin += movementVector;
623 
624  m_board->SetModified();
625 }
626 
627 int ALTIUM_PCB::GetNetCode( uint16_t aId ) const
628 {
629  if( aId == ALTIUM_NET_UNCONNECTED )
630  {
632  }
633  else if( m_num_nets < aId )
634  {
636  "Netcode with id %d does not exist. Only %d nets are known", aId, m_num_nets ) );
637  }
638  else
639  {
640  return aId + 1;
641  }
642 }
643 
644 const ARULE6* ALTIUM_PCB::GetRule( ALTIUM_RULE_KIND aKind, const wxString& aName ) const
645 {
646  const auto rules = m_rules.find( aKind );
647  if( rules == m_rules.end() )
648  {
649  return nullptr;
650  }
651  for( const ARULE6& rule : rules->second )
652  {
653  if( rule.name == aName )
654  {
655  return &rule;
656  }
657  }
658  return nullptr;
659 }
660 
662 {
663  const auto rules = m_rules.find( aKind );
664  if( rules == m_rules.end() )
665  {
666  return nullptr;
667  }
668  for( const ARULE6& rule : rules->second )
669  {
670  if( rule.scope1expr == "All" && rule.scope2expr == "All" )
671  {
672  return &rule;
673  }
674  }
675  return nullptr;
676 }
677 
678 void ALTIUM_PCB::ParseFileHeader( const CFB::CompoundFileReader& aReader,
679  const CFB::COMPOUND_FILE_ENTRY* aEntry )
680 {
681  ALTIUM_PARSER reader( aReader, aEntry );
682 
683  reader.ReadAndSetSubrecordLength();
684  wxString header = reader.ReadWxString();
685 
686  //std::cout << "HEADER: " << header << std::endl; // tells me: PCB 5.0 Binary File
687 
688  //reader.SkipSubrecord();
689 
690  // TODO: does not seem to work all the time at the moment
691  //if( reader.GetRemainingBytes() != 0 )
692  //{
693  // THROW_IO_ERROR( "FileHeader stream is not fully parsed" );
694  //}
695 }
696 
697 void ALTIUM_PCB::ParseBoard6Data( const CFB::CompoundFileReader& aReader,
698  const CFB::COMPOUND_FILE_ENTRY* aEntry )
699 {
700  if( m_progressReporter )
701  m_progressReporter->Report( "Loading board data..." );
702 
703  ALTIUM_PARSER reader( aReader, aEntry );
704 
705  checkpoint();
706  ABOARD6 elem( reader );
707 
708  if( reader.GetRemainingBytes() != 0 )
709  {
710  THROW_IO_ERROR( "Board6 stream is not fully parsed" );
711  }
712 
715 
716  // read layercount from stackup, because LAYERSETSCOUNT is not always correct?!
717  size_t layercount = 0;
718  for( size_t i = static_cast<size_t>( ALTIUM_LAYER::TOP_LAYER );
719  i < elem.stackup.size() && i != 0; i = elem.stackup[i - 1].nextId, layercount++ )
720  ;
721  size_t kicadLayercount = ( layercount % 2 == 0 ) ? layercount : layercount + 1;
722  m_board->SetCopperLayerCount( kicadLayercount );
723 
724  BOARD_DESIGN_SETTINGS& designSettings = m_board->GetDesignSettings();
725  BOARD_STACKUP& stackup = designSettings.GetStackupDescriptor();
726 
727  // create board stackup
728  stackup.RemoveAll(); // Just to be sure
729  stackup.BuildDefaultStackupList( &designSettings, layercount );
730 
731  auto it = stackup.GetList().begin();
732  // find first copper layer
733  for( ; it != stackup.GetList().end() && ( *it )->GetType() != BS_ITEM_TYPE_COPPER; ++it )
734  ;
735 
736  auto curLayer = static_cast<int>( F_Cu );
737  for( size_t altiumLayerId = static_cast<size_t>( ALTIUM_LAYER::TOP_LAYER );
738  altiumLayerId < elem.stackup.size() && altiumLayerId != 0;
739  altiumLayerId = elem.stackup[altiumLayerId - 1].nextId )
740  {
741  // array starts with 0, but stackup with 1
742  ABOARD6_LAYER_STACKUP& layer = elem.stackup.at( altiumLayerId - 1 );
743 
744  // handle unused layer in case of odd layercount
745  if( layer.nextId == 0 && layercount != kicadLayercount )
746  {
747  m_board->SetLayerName( ( *it )->GetBrdLayerId(), "[unused]" );
748 
749  if( ( *it )->GetType() != BS_ITEM_TYPE_COPPER )
750  {
751  THROW_IO_ERROR( "Board6 stream, unexpected item while parsing stackup" );
752  }
753  ( *it )->SetThickness( 0 );
754 
755  ++it;
756  if( ( *it )->GetType() != BS_ITEM_TYPE_DIELECTRIC )
757  {
758  THROW_IO_ERROR( "Board6 stream, unexpected item while parsing stackup" );
759  }
760  ( *it )->SetThickness( 0, 0 );
761  ( *it )->SetThicknessLocked( true, 0 );
762  ++it;
763  }
764 
765  m_layermap.insert( { static_cast<ALTIUM_LAYER>( altiumLayerId ),
766  static_cast<PCB_LAYER_ID>( curLayer++ ) } );
767 
768  if( ( *it )->GetType() != BS_ITEM_TYPE_COPPER )
769  {
770  THROW_IO_ERROR( "Board6 stream, unexpected item while parsing stackup" );
771  }
772  ( *it )->SetThickness( layer.copperthick );
773 
774  ALTIUM_LAYER alayer = static_cast<ALTIUM_LAYER>( altiumLayerId );
775  PCB_LAYER_ID klayer = ( *it )->GetBrdLayerId();
776 
777  m_board->SetLayerName( klayer, layer.name );
778 
779  if( layer.copperthick == 0 )
780  {
781  m_board->SetLayerType( klayer, LAYER_T::LT_JUMPER ); // used for things like wirebonding
782  }
783  else if( IsAltiumLayerAPlane( alayer ) )
784  {
786  }
787 
788  if( klayer == B_Cu )
789  {
790  if( layer.nextId != 0 )
791  {
792  THROW_IO_ERROR( "Board6 stream, unexpected id while parsing last stackup layer" );
793  }
794  // overwrite entry from internal -> bottom
795  m_layermap[alayer] = B_Cu;
796  break;
797  }
798 
799  ++it;
800  if( ( *it )->GetType() != BS_ITEM_TYPE_DIELECTRIC )
801  {
802  THROW_IO_ERROR( "Board6 stream, unexpected item while parsing stackup" );
803  }
804  ( *it )->SetThickness( layer.dielectricthick, 0 );
805  ( *it )->SetMaterial( layer.dielectricmaterial.empty() ?
806  NotSpecifiedPrm() :
807  wxString( layer.dielectricmaterial ) );
808  ( *it )->SetEpsilonR( layer.dielectricconst, 0 );
809 
810  ++it;
811  }
812 
813  // Set name of all non-cu layers
814  for( size_t altiumLayerId = static_cast<size_t>( ALTIUM_LAYER::TOP_OVERLAY );
815  altiumLayerId <= static_cast<size_t>( ALTIUM_LAYER::BOTTOM_SOLDER ); altiumLayerId++ )
816  {
817  // array starts with 0, but stackup with 1
818  ABOARD6_LAYER_STACKUP& layer = elem.stackup.at( altiumLayerId - 1 );
819 
820  ALTIUM_LAYER alayer = static_cast<ALTIUM_LAYER>( altiumLayerId );
821  PCB_LAYER_ID klayer = GetKicadLayer( alayer );
822 
823  m_board->SetLayerName( klayer, layer.name );
824  }
825 
826  for( size_t altiumLayerId = static_cast<size_t>( ALTIUM_LAYER::MECHANICAL_1 );
827  altiumLayerId <= static_cast<size_t>( ALTIUM_LAYER::MECHANICAL_16 ); altiumLayerId++ )
828  {
829  // array starts with 0, but stackup with 1
830  ABOARD6_LAYER_STACKUP& layer = elem.stackup.at( altiumLayerId - 1 );
831 
832  ALTIUM_LAYER alayer = static_cast<ALTIUM_LAYER>( altiumLayerId );
833  PCB_LAYER_ID klayer = GetKicadLayer( alayer );
834 
835  m_board->SetLayerName( klayer, layer.name );
836  }
837 
839 }
840 
841 void ALTIUM_PCB::HelperCreateBoardOutline( const std::vector<ALTIUM_VERTICE>& aVertices )
842 {
843  if( !aVertices.empty() )
844  {
845  const ALTIUM_VERTICE* last = &aVertices.at( 0 );
846  for( size_t i = 0; i < aVertices.size(); i++ )
847  {
848  const ALTIUM_VERTICE* cur = &aVertices.at( ( i + 1 ) % aVertices.size() );
849 
850  PCB_SHAPE* shape = new PCB_SHAPE( m_board );
851  m_board->Add( shape, ADD_MODE::APPEND );
852 
854  shape->SetLayer( Edge_Cuts );
855 
856  if( !last->isRound && !cur->isRound )
857  {
858  shape->SetShape( SHAPE_T::SEGMENT );
859  shape->SetStart( last->position );
860  shape->SetEnd( cur->position );
861  }
862  else if( cur->isRound )
863  {
864  shape->SetShape( SHAPE_T::ARC );
865  shape->SetAngle( -NormalizeAngleDegreesPos( cur->endangle - cur->startangle ) * 10. );
866 
867  double startradiant = DEG2RAD( cur->startangle );
868  wxPoint arcStartOffset = wxPoint( KiROUND( std::cos( startradiant ) * cur->radius ),
869  -KiROUND( std::sin( startradiant ) * cur->radius ) );
870  wxPoint arcStart = cur->center + arcStartOffset;
871  shape->SetCenter( cur->center );
872  shape->SetArcStart( arcStart );
873 
874  if( !last->isRound )
875  {
876  double endradiant = DEG2RAD( cur->endangle );
877  wxPoint arcEndOffset = wxPoint( KiROUND( std::cos( endradiant ) * cur->radius ),
878  -KiROUND( std::sin( endradiant ) * cur->radius ) );
879  wxPoint arcEnd = cur->center + arcEndOffset;
880 
881  PCB_SHAPE* shape2 = new PCB_SHAPE( m_board );
882  shape2->SetShape( SHAPE_T::SEGMENT );
883  m_board->Add( shape2, ADD_MODE::APPEND );
885  shape2->SetLayer( Edge_Cuts );
886  shape2->SetStart( last->position );
887 
888  // TODO: this is more of a hack than the real solution
889  double lineLengthStart = GetLineLength( last->position, arcStart );
890  double lineLengthEnd = GetLineLength( last->position, arcEnd );
891  if( lineLengthStart > lineLengthEnd )
892  {
893  shape2->SetEnd( cur->center + arcEndOffset );
894  }
895  else
896  {
897  shape2->SetEnd( cur->center + arcStartOffset );
898  }
899  }
900  }
901  last = cur;
902  }
903  }
904 }
905 
906 void ALTIUM_PCB::ParseClasses6Data( const CFB::CompoundFileReader& aReader,
907  const CFB::COMPOUND_FILE_ENTRY* aEntry )
908 {
909  if( m_progressReporter )
910  m_progressReporter->Report( "Loading netclasses..." );
911 
912  ALTIUM_PARSER reader( aReader, aEntry );
913 
914  while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
915  {
916  checkpoint();
917  ACLASS6 elem( reader );
918 
919  if( elem.kind == ALTIUM_CLASS_KIND::NET_CLASS )
920  {
921  NETCLASSPTR nc = std::make_shared<NETCLASS>( elem.name );
922 
923  for( const auto& name : elem.names )
924  {
925  // TODO: it seems it can happen that we have names not attached to any net.
926  nc->Add( name );
927  }
928 
929  if( !m_board->GetDesignSettings().GetNetClasses().Add( nc ) )
930  {
931  // Name conflict, this is likely a bad board file.
932  // unique_ptr will delete nc on this code path
933  THROW_IO_ERROR( wxString::Format( _( "Duplicate netclass name '%s'." ), elem.name ) );
934  }
935  }
936  }
937 
938  if( reader.GetRemainingBytes() != 0 )
939  {
940  THROW_IO_ERROR( "Classes6 stream is not fully parsed" );
941  }
942 
944 }
945 
946 void ALTIUM_PCB::ParseComponents6Data( const CFB::CompoundFileReader& aReader,
947  const CFB::COMPOUND_FILE_ENTRY* aEntry )
948 {
949  if( m_progressReporter )
950  m_progressReporter->Report( "Loading components..." );
951 
952  ALTIUM_PARSER reader( aReader, aEntry );
953 
954  uint16_t componentId = 0;
955  while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
956  {
957  checkpoint();
958  ACOMPONENT6 elem( reader );
959 
960  FOOTPRINT* footprint = new FOOTPRINT( m_board );
961  m_board->Add( footprint, ADD_MODE::APPEND );
962  m_components.emplace_back( footprint );
963 
965 
966  footprint->SetFPID( fpID );
967 
968  footprint->SetPosition( elem.position );
969  footprint->SetOrientationDegrees( elem.rotation );
970 
971  // KiCad netlisting requires parts to have non-digit + digit annotation.
972  // If the reference begins with a number, we prepend 'UNK' (unknown) for the source designator
973  wxString reference = elem.sourcedesignator;
974  if( reference.find_first_not_of( "0123456789" ) == wxString::npos )
975  reference.Prepend( "UNK" );
976  footprint->SetReference( reference );
977 
978  footprint->SetLocked( elem.locked );
979  footprint->Reference().SetVisible( elem.nameon );
980  footprint->Value().SetVisible( elem.commenton );
981  footprint->SetLayer( elem.layer == ALTIUM_LAYER::TOP_LAYER ? F_Cu : B_Cu );
982 
983  componentId++;
984  }
985 
986  if( reader.GetRemainingBytes() != 0 )
987  {
988  THROW_IO_ERROR( "Components6 stream is not fully parsed" );
989  }
990 }
991 
992 
993 void ALTIUM_PCB::ParseComponentsBodies6Data( const CFB::CompoundFileReader& aReader,
994  const CFB::COMPOUND_FILE_ENTRY* aEntry )
995 {
996  if( m_progressReporter )
997  m_progressReporter->Report( "Loading component 3D models..." );
998 
999  ALTIUM_PARSER reader( aReader, aEntry );
1000 
1001  while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
1002  {
1003  checkpoint();
1004  ACOMPONENTBODY6 elem( reader ); // TODO: implement
1005 
1006  if( elem.component == ALTIUM_COMPONENT_NONE )
1007  continue; // TODO: we do not support components for the board yet
1008 
1009  if( m_components.size() <= elem.component )
1010  {
1012  "ComponentsBodies6 stream tries to access component id %d of %d existing components",
1013  elem.component, m_components.size() ) );
1014  }
1015 
1016  if( !elem.modelIsEmbedded )
1017  continue;
1018 
1019  auto modelTuple = m_models.find( elem.modelId );
1020 
1021  if( modelTuple == m_models.end() )
1022  {
1024  "ComponentsBodies6 stream tries to access model id %s which does not exist",
1025  elem.modelId ) );
1026  }
1027 
1028  FOOTPRINT* footprint = m_components.at( elem.component );
1029  const wxPoint& fpPosition = footprint->GetPosition();
1030 
1031  FP_3DMODEL modelSettings;
1032 
1033  modelSettings.m_Filename = modelTuple->second;
1034 
1035  modelSettings.m_Offset.x = Iu2Millimeter((int) elem.modelPosition.x - fpPosition.x );
1036  modelSettings.m_Offset.y = -Iu2Millimeter((int) elem.modelPosition.y - fpPosition.y );
1037  modelSettings.m_Offset.z = Iu2Millimeter( (int) elem.modelPosition.z );
1038 
1039  double orientation = footprint->GetOrientation();
1040 
1041  if( footprint->IsFlipped() )
1042  {
1043  modelSettings.m_Offset.y = -modelSettings.m_Offset.y;
1044  orientation = -orientation;
1045  }
1046 
1047  RotatePoint( &modelSettings.m_Offset.x, &modelSettings.m_Offset.y, orientation );
1048 
1049  modelSettings.m_Rotation.x = NormalizeAngleDegrees( -elem.modelRotation.x, -180, 180 );
1050  modelSettings.m_Rotation.y = NormalizeAngleDegrees( -elem.modelRotation.y, -180, 180 );
1051  modelSettings.m_Rotation.z = NormalizeAngleDegrees( -elem.modelRotation.z
1052  + elem.rotation
1053  + orientation / 10, -180, 180 );
1054  modelSettings.m_Opacity = elem.bodyOpacity;
1055 
1056  footprint->Models().push_back( modelSettings );
1057  }
1058 
1059  if( reader.GetRemainingBytes() != 0 )
1060  THROW_IO_ERROR( "ComponentsBodies6 stream is not fully parsed" );
1061 }
1062 
1063 
1065 {
1066  if( aElem.referencePoint.size() != 2 )
1067  THROW_IO_ERROR( "Incorrect number of reference points for linear dimension object" );
1068 
1069  PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
1070 
1071  if( klayer == UNDEFINED_LAYER )
1072  {
1073  wxLogWarning( _( "Dimension found on an Altium layer (%d) with no KiCad equivalent. "
1074  "It has been moved to KiCad layer Eco1_User." ),
1075  aElem.layer );
1076  klayer = Eco1_User;
1077  }
1078 
1079  wxPoint referencePoint0 = aElem.referencePoint.at( 0 );
1080  wxPoint referencePoint1 = aElem.referencePoint.at( 1 );
1081 
1082  PCB_DIM_ALIGNED* dimension = new PCB_DIM_ALIGNED( m_board );
1083  m_board->Add( dimension, ADD_MODE::APPEND );
1084 
1085  dimension->SetPrecision( aElem.textprecision );
1086  dimension->SetLayer( klayer );
1087  dimension->SetStart( referencePoint0 );
1088 
1089  if( referencePoint0 != aElem.xy1 )
1090  {
1100  wxPoint direction = aElem.xy1 - referencePoint0;
1101  wxPoint directionNormalVector = wxPoint( -direction.y, direction.x );
1102  SEG segm1( referencePoint0, referencePoint0 + directionNormalVector );
1103  SEG segm2( referencePoint1, referencePoint1 + direction );
1104  wxPoint intersection( segm1.Intersect( segm2, true, true ).get() );
1105  dimension->SetEnd( intersection );
1106 
1107  int height = static_cast<int>( EuclideanNorm( direction ) );
1108 
1109  if( direction.x <= 0 && direction.y <= 0 ) // TODO: I suspect this is not always correct
1110  height = -height;
1111 
1112  dimension->SetHeight( height );
1113  }
1114  else
1115  {
1116  dimension->SetEnd( referencePoint1 );
1117  }
1118 
1119  dimension->SetLineThickness( aElem.linewidth );
1120 
1121  dimension->SetPrefix( aElem.textprefix );
1122 
1123  // Suffix normally holds the units
1124  dimension->SetUnitsFormat( aElem.textsuffix.IsEmpty() ? DIM_UNITS_FORMAT::NO_SUFFIX
1126 
1127  dimension->Text().SetTextThickness( aElem.textlinewidth );
1128  dimension->Text().SetTextSize( wxSize( aElem.textheight, aElem.textheight ) );
1129  dimension->Text().SetBold( aElem.textbold );
1130  dimension->Text().SetItalic( aElem.textitalic );
1131 
1132  switch( aElem.textunit )
1133  {
1134  case ALTIUM_UNIT::INCHES:
1135  dimension->SetUnits( EDA_UNITS::INCHES );
1136  break;
1137  case ALTIUM_UNIT::MILS:
1138  dimension->SetUnits( EDA_UNITS::MILS );
1139  break;
1142  dimension->SetUnits( EDA_UNITS::MILLIMETRES );
1143  break;
1144  default:
1145  break;
1146  }
1147 }
1148 
1149 
1151 {
1152  if( aElem.referencePoint.size() < 2 )
1153  THROW_IO_ERROR( "Not enough reference points for radial dimension object" );
1154 
1155  PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
1156 
1157  if( klayer == UNDEFINED_LAYER )
1158  {
1159  wxLogWarning( _( "Dimension found on an Altium layer (%d) with no KiCad equivalent. "
1160  "It has been moved to KiCad layer Eco1_User." ),
1161  aElem.layer );
1162  klayer = Eco1_User;
1163  }
1164 
1165  wxPoint referencePoint0 = aElem.referencePoint.at( 0 );
1166  wxPoint referencePoint1 = aElem.referencePoint.at( 1 );
1167 
1168  //
1169  // We don't have radial dimensions yet so fake it with a leader:
1170 
1171  PCB_DIM_LEADER* dimension = new PCB_DIM_LEADER( m_board );
1172  m_board->Add( dimension, ADD_MODE::APPEND );
1173  m_radialDimensions.push_back( dimension );
1174 
1175  dimension->SetPrecision( aElem.textprecision );
1176  dimension->SetLayer( klayer );
1177  dimension->SetStart( referencePoint0 );
1178  dimension->SetEnd( aElem.xy1 );
1179  dimension->SetLineThickness( aElem.linewidth );
1180 
1181  dimension->SetPrefix( aElem.textprefix );
1182 
1183  // Suffix normally holds the units
1184  dimension->SetUnitsFormat( aElem.textsuffix.IsEmpty() ? DIM_UNITS_FORMAT::NO_SUFFIX
1186 
1187  switch( aElem.textunit )
1188  {
1189  case ALTIUM_UNIT::INCHES:
1190  dimension->SetUnits( EDA_UNITS::INCHES );
1191  break;
1192  case ALTIUM_UNIT::MILS:
1193  dimension->SetUnits( EDA_UNITS::MILS );
1194  break;
1197  dimension->SetUnits( EDA_UNITS::MILLIMETRES );
1198  break;
1199  default:
1200  break;
1201  }
1202 
1203  if( aElem.textPoint.empty() )
1204  {
1205  wxLogError( "No text position present for leader dimension object" );
1206  return;
1207  }
1208 
1209  dimension->Text().SetPosition( aElem.textPoint.at( 0 ) );
1210  dimension->Text().SetTextThickness( aElem.textlinewidth );
1211  dimension->Text().SetTextSize( wxSize( aElem.textheight, aElem.textheight ) );
1212  dimension->Text().SetBold( aElem.textbold );
1213  dimension->Text().SetItalic( aElem.textitalic );
1216 
1217  int yAdjust = dimension->Text().GetCenter().y - dimension->Text().GetPosition().y;
1218  dimension->Text().Move( wxPoint( 0, yAdjust + aElem.textgap ) );
1220 }
1221 
1222 
1224 {
1225  PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
1226 
1227  if( klayer == UNDEFINED_LAYER )
1228  {
1229  wxLogWarning( _( "Dimension found on an Altium layer (%d) with no KiCad equivalent. "
1230  "It has been moved to KiCad layer Eco1_User." ),
1231  aElem.layer );
1232  klayer = Eco1_User;
1233  }
1234 
1235  if( !aElem.referencePoint.empty() )
1236  {
1237  wxPoint referencePoint0 = aElem.referencePoint.at( 0 );
1238 
1239  // line
1240  wxPoint last = referencePoint0;
1241  for( size_t i = 1; i < aElem.referencePoint.size(); i++ )
1242  {
1243  PCB_SHAPE* shape = new PCB_SHAPE( m_board );
1244  m_board->Add( shape, ADD_MODE::APPEND );
1245  shape->SetShape( SHAPE_T::SEGMENT );
1246  shape->SetLayer( klayer );
1247  shape->SetWidth( aElem.linewidth );
1248  shape->SetStart( last );
1249  shape->SetEnd( aElem.referencePoint.at( i ) );
1250  last = aElem.referencePoint.at( i );
1251  }
1252 
1253  // arrow
1254  if( aElem.referencePoint.size() >= 2 )
1255  {
1256  wxPoint dirVec = aElem.referencePoint.at( 1 ) - referencePoint0;
1257  if( dirVec.x != 0 || dirVec.y != 0 )
1258  {
1259  double scaling = EuclideanNorm( dirVec ) / aElem.arrowsize;
1260  wxPoint arrVec =
1261  wxPoint( KiROUND( dirVec.x / scaling ), KiROUND( dirVec.y / scaling ) );
1262  RotatePoint( &arrVec, 200. );
1263 
1264  PCB_SHAPE* shape1 = new PCB_SHAPE( m_board );
1265  m_board->Add( shape1, ADD_MODE::APPEND );
1266  shape1->SetShape( SHAPE_T::SEGMENT );
1267  shape1->SetLayer( klayer );
1268  shape1->SetWidth( aElem.linewidth );
1269  shape1->SetStart( referencePoint0 );
1270  shape1->SetEnd( referencePoint0 + arrVec );
1271 
1272  RotatePoint( &arrVec, -400. );
1273 
1274  PCB_SHAPE* shape2 = new PCB_SHAPE( m_board );
1275  m_board->Add( shape2, ADD_MODE::APPEND );
1276  shape2->SetShape( SHAPE_T::SEGMENT );
1277  shape2->SetLayer( klayer );
1278  shape2->SetWidth( aElem.linewidth );
1279  shape2->SetStart( referencePoint0 );
1280  shape2->SetEnd( referencePoint0 + arrVec );
1281  }
1282  }
1283  }
1284 
1285  if( aElem.textPoint.empty() )
1286  {
1287  wxLogError( "No text position present for leader dimension object" );
1288  return;
1289  }
1290 
1291  PCB_TEXT* text = new PCB_TEXT( m_board );
1293  text->SetText( aElem.textformat );
1294  text->SetPosition( aElem.textPoint.at( 0 ) );
1295  text->SetLayer( klayer );
1296  text->SetTextSize( wxSize( aElem.textheight, aElem.textheight ) ); // TODO: parse text width
1297  text->SetTextThickness( aElem.textlinewidth );
1300 }
1301 
1302 
1304 {
1305  PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
1306 
1307  if( klayer == UNDEFINED_LAYER )
1308  {
1309  wxLogWarning( _( "Dimension found on an Altium layer (%d) with no KiCad equivalent. "
1310  "It has been moved to KiCad layer Eco1_User." ),
1311  aElem.layer );
1312  klayer = Eco1_User;
1313  }
1314 
1315  for( size_t i = 0; i < aElem.referencePoint.size(); i++ )
1316  {
1317  PCB_SHAPE* shape = new PCB_SHAPE( m_board );
1318  m_board->Add( shape, ADD_MODE::APPEND );
1319  shape->SetShape( SHAPE_T::SEGMENT );
1320  shape->SetLayer( klayer );
1321  shape->SetWidth( aElem.linewidth );
1322  shape->SetStart( aElem.referencePoint.at( i ) );
1323  // shape->SetEnd( /* TODO: seems to be based on TEXTY */ );
1324  }
1325 }
1326 
1327 
1329 {
1330  PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
1331 
1332  if( klayer == UNDEFINED_LAYER )
1333  {
1334  wxLogWarning( _( "Dimension found on an Altium layer (%d) with no KiCad equivalent. "
1335  "It has been moved to KiCad layer Eco1_User." ),
1336  aElem.layer );
1337  klayer = Eco1_User;
1338  }
1339 
1340  wxPoint vec = wxPoint( 0, aElem.height / 2 );
1341  RotatePoint( &vec, aElem.angle * 10. );
1342 
1343  PCB_DIM_CENTER* dimension = new PCB_DIM_CENTER( m_board );
1344  m_board->Add( dimension, ADD_MODE::APPEND );
1345  dimension->SetLayer( klayer );
1346  dimension->SetLineThickness( aElem.linewidth );
1347  dimension->SetStart( aElem.xy1 );
1348  dimension->SetEnd( aElem.xy1 + vec );
1349 }
1350 
1351 
1352 void ALTIUM_PCB::ParseDimensions6Data( const CFB::CompoundFileReader& aReader,
1353  const CFB::COMPOUND_FILE_ENTRY* aEntry )
1354 {
1355  if( m_progressReporter )
1356  m_progressReporter->Report( "Loading dimension drawings..." );
1357 
1358  ALTIUM_PARSER reader( aReader, aEntry );
1359 
1360  while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
1361  {
1362  checkpoint();
1363  ADIMENSION6 elem( reader );
1364 
1365  switch( elem.kind )
1366  {
1369  break;
1372  break;
1375  break;
1377  wxLogError( _( "Ignored dimension of kind %d (not yet supported)." ), elem.kind );
1378  // HelperParseDimensions6Datum( elem );
1379  break;
1382  break;
1383  default:
1384  wxLogError( _( "Ignored dimension of kind %d (not yet supported)." ), elem.kind );
1385  break;
1386  }
1387  }
1388 
1389  if( reader.GetRemainingBytes() != 0 )
1390  THROW_IO_ERROR( "Dimensions6 stream is not fully parsed" );
1391 }
1392 
1393 
1394 void ALTIUM_PCB::ParseModelsData( const CFB::CompoundFileReader& aReader,
1395  const CFB::COMPOUND_FILE_ENTRY* aEntry, const wxString& aRootDir )
1396 {
1397  if( m_progressReporter )
1398  m_progressReporter->Report( "Loading 3D models..." );
1399 
1400  ALTIUM_PARSER reader( aReader, aEntry );
1401 
1402  if( reader.GetRemainingBytes() == 0 )
1403  return;
1404 
1405  wxString projectPath = wxPathOnly( m_board->GetFileName() );
1406  // TODO: set KIPRJMOD always after import (not only when loading project)?
1407  wxSetEnv( PROJECT_VAR_NAME, projectPath );
1408 
1409  // TODO: make this path configurable?
1410  const wxString altiumModelDir = "ALTIUM_EMBEDDED_MODELS";
1411 
1412  wxFileName altiumModelsPath = wxFileName::DirName( projectPath );
1413  wxString kicadModelPrefix = "${KIPRJMOD}/" + altiumModelDir + "/";
1414 
1415  if( !altiumModelsPath.AppendDir( altiumModelDir ) )
1416  {
1417  THROW_IO_ERROR( "Cannot construct directory path for step models" );
1418  }
1419 
1420  // Create dir if it does not exist
1421  if( !altiumModelsPath.DirExists() )
1422  {
1423  if( !altiumModelsPath.Mkdir() )
1424  {
1425  wxLogError( _( "Failed to create folder '%s'." ) + wxS( " " )
1426  + _( "No 3D-models will be imported." ),
1427  altiumModelsPath.GetFullPath() );
1428  return;
1429  }
1430  }
1431 
1432  int idx = 0;
1433 
1434  while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
1435  {
1436  checkpoint();
1437  AMODEL elem( reader );
1438 
1439  wxString stepPath = wxString::Format( aRootDir + "%d", idx );
1440  wxString storageName = elem.name.IsEmpty() ? wxString::Format( "%d", idx ) : elem.name;
1441  wxFileName storagePath( altiumModelsPath.GetPath(), storageName );
1442 
1443  idx++;
1444 
1445  const CFB::COMPOUND_FILE_ENTRY* stepEntry = FindStream( aReader, stepPath.c_str() );
1446 
1447  if( stepEntry == nullptr )
1448  {
1449  wxLogError( _( "File not found: '%s'. 3D-model not imported." ), stepPath );
1450  continue;
1451  }
1452 
1453  size_t stepSize = static_cast<size_t>( stepEntry->size );
1454  std::unique_ptr<char[]> stepContent( new char[stepSize] );
1455 
1456  // read file into buffer
1457  aReader.ReadFile( stepEntry, 0, stepContent.get(), stepSize );
1458 
1459  if( !storagePath.IsDirWritable() )
1460  {
1461  wxLogError( _( "Insufficient permissions to save file '%s'." ),
1462  storagePath.GetFullPath() );
1463  continue;
1464  }
1465 
1466  wxMemoryInputStream stepStream( stepContent.get(), stepSize );
1467  wxZlibInputStream zlibInputStream( stepStream );
1468 
1469  wxFFileOutputStream outputStream( storagePath.GetFullPath() );
1470  outputStream.Write( zlibInputStream );
1471  outputStream.Close();
1472 
1473  m_models.insert( { elem.id, kicadModelPrefix + elem.name } );
1474  }
1475 
1476  if( reader.GetRemainingBytes() != 0 )
1477  THROW_IO_ERROR( "Models stream is not fully parsed" );
1478 }
1479 
1480 
1481 void ALTIUM_PCB::ParseNets6Data( const CFB::CompoundFileReader& aReader,
1482  const CFB::COMPOUND_FILE_ENTRY* aEntry )
1483 {
1484  if( m_progressReporter )
1485  m_progressReporter->Report( _( "Loading nets..." ) );
1486 
1487  ALTIUM_PARSER reader( aReader, aEntry );
1488 
1489  wxASSERT( m_num_nets == 0 );
1490  while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
1491  {
1492  checkpoint();
1493  ANET6 elem( reader );
1494 
1496  }
1497 
1498  if( reader.GetRemainingBytes() != 0 )
1499  THROW_IO_ERROR( "Nets6 stream is not fully parsed" );
1500 }
1501 
1502 void ALTIUM_PCB::ParsePolygons6Data( const CFB::CompoundFileReader& aReader,
1503  const CFB::COMPOUND_FILE_ENTRY* aEntry )
1504 {
1505  if( m_progressReporter )
1506  m_progressReporter->Report( _( "Loading polygons..." ) );
1507 
1508  ALTIUM_PARSER reader( aReader, aEntry );
1509 
1510  while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
1511  {
1512  checkpoint();
1513  APOLYGON6 elem( reader );
1514 
1515  PCB_LAYER_ID klayer = GetKicadLayer( elem.layer );
1516 
1517  if( klayer == UNDEFINED_LAYER )
1518  {
1519  wxLogWarning( _( "Polygon found on an Altium layer (%d) with no KiCad equivalent. "
1520  "It has been moved to KiCad layer Eco1_User." ),
1521  elem.layer );
1522  klayer = Eco1_User;
1523  }
1524 
1525  SHAPE_LINE_CHAIN linechain;
1527 
1528  if( linechain.PointCount() < 2 )
1529  {
1530  // We have found multiple Altium files with polygon records containing nothing but two
1531  // coincident vertices. These polygons do not appear when opening the file in Altium.
1532  // https://gitlab.com/kicad/code/kicad/-/issues/8183
1533  //
1534  // wxLogError( _( "Polygon has only %d point extracted from %ld vertices. At least 2 "
1535  // "points are required." ),
1536  // linechain.PointCount(),
1537  // elem.vertices.size() );
1538 
1539  m_polygons.emplace_back( nullptr );
1540  continue;
1541  }
1542 
1543  ZONE* zone = new ZONE( m_board );
1544  m_board->Add( zone, ADD_MODE::APPEND );
1545  m_polygons.emplace_back( zone );
1546 
1547  zone->SetFillVersion( 6 );
1548  zone->SetNetCode( GetNetCode( elem.net ) );
1549  zone->SetLayer( klayer );
1550  zone->SetPosition( elem.vertices.at( 0 ).position );
1551  zone->SetLocked( elem.locked );
1552  zone->SetPriority( elem.pourindex > 0 ? elem.pourindex : 0 );
1553  zone->Outline()->AddOutline( linechain );
1554 
1555  if( elem.pourindex > m_highest_pour_index )
1557 
1558  // TODO: more flexible rule parsing
1559  const ARULE6* clearanceRule = GetRuleDefault( ALTIUM_RULE_KIND::PLANE_CLEARANCE );
1560 
1561  if( clearanceRule != nullptr )
1562  zone->SetLocalClearance( clearanceRule->planeclearanceClearance );
1563 
1564  const ARULE6* polygonConnectRule = GetRuleDefault( ALTIUM_RULE_KIND::POLYGON_CONNECT );
1565 
1566  if( polygonConnectRule != nullptr )
1567  {
1568  switch( polygonConnectRule->polygonconnectStyle )
1569  {
1572  break;
1573 
1576  break;
1577 
1578  default:
1581  break;
1582  }
1583 
1584  // TODO: correct variables?
1586  polygonConnectRule->polygonconnectReliefconductorwidth );
1587  zone->SetThermalReliefGap( polygonConnectRule->polygonconnectAirgapwidth );
1588 
1589  if( polygonConnectRule->polygonconnectReliefconductorwidth < zone->GetMinThickness() )
1590  zone->SetMinThickness( polygonConnectRule->polygonconnectReliefconductorwidth );
1591  }
1592 
1593  if( IsAltiumLayerAPlane( elem.layer ) )
1594  {
1595  // outer zone will be set to priority 0 later.
1596  zone->SetPriority( 1 );
1597 
1598  // check if this is the outer zone by simply comparing the BBOX
1599  const auto& outer_plane = m_outer_plane.find( elem.layer );
1600  if( outer_plane == m_outer_plane.end()
1601  || zone->GetBoundingBox().Contains( outer_plane->second->GetBoundingBox() ) )
1602  {
1603  m_outer_plane[elem.layer] = zone;
1604  }
1605  }
1606 
1609  {
1611  zone->SetHatchThickness( elem.trackwidth );
1612 
1614  {
1615  // use a small hack to get us only an outline (hopefully)
1616  const EDA_RECT& bbox = zone->GetBoundingBox();
1617  zone->SetHatchGap( std::max( bbox.GetHeight(), bbox.GetWidth() ) );
1618  }
1619  else
1620  {
1621  zone->SetHatchGap( elem.gridsize - elem.trackwidth );
1622  }
1623 
1625  }
1626 
1628  ZONE::GetDefaultHatchPitch(), true );
1629  }
1630 
1631  if( reader.GetRemainingBytes() != 0 )
1632  {
1633  THROW_IO_ERROR( "Polygons6 stream is not fully parsed" );
1634  }
1635 }
1636 
1637 void ALTIUM_PCB::ParseRules6Data( const CFB::CompoundFileReader& aReader,
1638  const CFB::COMPOUND_FILE_ENTRY* aEntry )
1639 {
1640  if( m_progressReporter )
1641  m_progressReporter->Report( _( "Loading rules..." ) );
1642 
1643  ALTIUM_PARSER reader( aReader, aEntry );
1644 
1645  while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
1646  {
1647  checkpoint();
1648  ARULE6 elem( reader );
1649 
1650  m_rules[elem.kind].emplace_back( elem );
1651  }
1652 
1653  // sort rules by priority
1654  for( auto&& val : m_rules )
1655  {
1656  std::sort( val.second.begin(), val.second.end(),
1657  []( const auto& lhs, const auto& rhs )
1658  {
1659  return lhs.priority < rhs.priority;
1660  } );
1661  }
1662 
1663  if( reader.GetRemainingBytes() != 0 )
1664  {
1665  THROW_IO_ERROR( "Rules6 stream is not fully parsed" );
1666  }
1667 }
1668 
1669 void ALTIUM_PCB::ParseBoardRegionsData( const CFB::CompoundFileReader& aReader,
1670  const CFB::COMPOUND_FILE_ENTRY* aEntry )
1671 {
1672  if( m_progressReporter )
1673  m_progressReporter->Report( _( "Loading board regions..." ) );
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, false );
1681 
1682  // TODO: implement?
1683  }
1684 
1685  if( reader.GetRemainingBytes() != 0 )
1686  {
1687  THROW_IO_ERROR( "BoardRegions stream is not fully parsed" );
1688  }
1689 }
1690 
1691 void ALTIUM_PCB::ParseShapeBasedRegions6Data( const CFB::CompoundFileReader& aReader,
1692  const CFB::COMPOUND_FILE_ENTRY* aEntry )
1693 {
1694  if( m_progressReporter )
1695  m_progressReporter->Report( _( "Loading zones..." ) );
1696 
1697  ALTIUM_PARSER reader( aReader, aEntry );
1698 
1699  while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
1700  {
1701  checkpoint();
1702  AREGION6 elem( reader, true );
1703 
1705  {
1707  }
1708  else if( elem.kind == ALTIUM_REGION_KIND::POLYGON_CUTOUT || elem.is_keepout )
1709  {
1710  SHAPE_LINE_CHAIN linechain;
1712 
1713  if( linechain.PointCount() < 2 )
1714  {
1715  // We have found multiple Altium files with polygon records containing nothing but
1716  // two coincident vertices. These polygons do not appear when opening the file in
1717  // Altium. https://gitlab.com/kicad/code/kicad/-/issues/8183
1718  //
1719  // wxLogError( _( "ShapeBasedRegion has only %d point extracted from %ld vertices. "
1720  // "At least 2 points are required." ),
1721  // linechain.PointCount(),
1722  // elem.outline.size() );
1723  continue;
1724  }
1725 
1726  ZONE* zone = new ZONE( m_board );
1727  m_board->Add( zone, ADD_MODE::APPEND );
1728 
1729  zone->SetFillVersion( 6 );
1730  zone->SetIsRuleArea( true );
1731  zone->SetDoNotAllowTracks( false );
1732  zone->SetDoNotAllowVias( false );
1733  zone->SetDoNotAllowPads( false );
1734  zone->SetDoNotAllowFootprints( false );
1735  zone->SetDoNotAllowCopperPour( true );
1736 
1737  zone->SetPosition( elem.outline.at( 0 ).position );
1738  zone->Outline()->AddOutline( linechain );
1739 
1740  if( elem.layer == ALTIUM_LAYER::MULTI_LAYER )
1741  {
1742  zone->SetLayer( F_Cu );
1743  zone->SetLayerSet( LSET::AllCuMask() );
1744  }
1745  else
1746  {
1747  PCB_LAYER_ID klayer = GetKicadLayer( elem.layer );
1748 
1749  if( klayer == UNDEFINED_LAYER )
1750  {
1751  wxLogWarning( _( "Zone found on an Altium layer (%d) with no KiCad equivalent. "
1752  "It has been moved to KiCad layer Eco1_User." ),
1753  elem.layer );
1754  klayer = Eco1_User;
1755  }
1756  zone->SetLayer( klayer );
1757  }
1758 
1760  ZONE::GetDefaultHatchPitch(), true );
1761  }
1762  else if( elem.kind == ALTIUM_REGION_KIND::COPPER )
1763  {
1764  if( elem.subpolyindex == ALTIUM_POLYGON_NONE )
1765  {
1766  PCB_LAYER_ID klayer = GetKicadLayer( elem.layer );
1767 
1768  if( klayer == UNDEFINED_LAYER )
1769  {
1770  wxLogWarning( _( "Polygon found on an Altium layer (%d) with no KiCad equivalent. "
1771  "It has been moved to KiCad layer Eco1_User." ),
1772  elem.layer );
1773  klayer = Eco1_User;
1774  }
1775 
1776  SHAPE_LINE_CHAIN linechain;
1778 
1779  if( linechain.PointCount() < 2 )
1780  {
1781  // We have found multiple Altium files with polygon records containing nothing
1782  // but two coincident vertices. These polygons do not appear when opening the
1783  // file in Altium. https://gitlab.com/kicad/code/kicad/-/issues/8183
1784  //
1785  // wxLogError( _( "Polygon has only %d point extracted from %ld vertices. At "
1786  // "least 2 points are required." ),
1787  // linechain.PointCount(),
1788  // elem.outline.size() );
1789 
1790  continue;
1791  }
1792 
1793  PCB_SHAPE* shape = new PCB_SHAPE( m_board );
1794  m_board->Add( shape, ADD_MODE::APPEND );
1795  shape->SetShape( SHAPE_T::POLY );
1796  shape->SetFilled( true );
1797  shape->SetLayer( klayer );
1798  shape->SetWidth( 0 );
1799 
1800  shape->SetPolyShape( linechain );
1801  }
1802  }
1803  else
1804  {
1805  wxLogError( _( "Ignored polygon shape of kind %d (not yet supported)." ), elem.kind );
1806  }
1807  }
1808 
1809  if( reader.GetRemainingBytes() != 0 )
1810  {
1811  THROW_IO_ERROR( "ShapeBasedRegions6 stream is not fully parsed" );
1812  }
1813 }
1814 
1815 void ALTIUM_PCB::ParseRegions6Data( const CFB::CompoundFileReader& aReader,
1816  const CFB::COMPOUND_FILE_ENTRY* aEntry )
1817 {
1818  if( m_progressReporter )
1819  m_progressReporter->Report( _( "Loading zone fills..." ) );
1820 
1821  ALTIUM_PARSER reader( aReader, aEntry );
1822 
1823  for( ZONE* zone : m_polygons )
1824  {
1825  if( zone )
1826  zone->UnFill(); // just to be sure
1827  }
1828 
1829  while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
1830  {
1831  checkpoint();
1832  AREGION6 elem( reader, false );
1833 
1834  if( elem.subpolyindex != ALTIUM_POLYGON_NONE )
1835  {
1836  if( m_polygons.size() <= elem.subpolyindex )
1837  {
1838  THROW_IO_ERROR( wxString::Format( "Region stream tries to access polygon id %d "
1839  "of %d existing polygons.",
1840  elem.subpolyindex,
1841  m_polygons.size() ) );
1842  }
1843 
1844  ZONE *zone = m_polygons.at( elem.subpolyindex );
1845 
1846  if( zone == nullptr )
1847  {
1848  continue; // we know the zone id, but because we do not know the layer we did not add it!
1849  }
1850 
1851  PCB_LAYER_ID klayer = GetKicadLayer( elem.layer );
1852  if( klayer == UNDEFINED_LAYER )
1853  {
1854  continue; // Just skip it for now. Users can fill it themselves.
1855  }
1856 
1857  SHAPE_LINE_CHAIN linechain;
1858  for( const ALTIUM_VERTICE& vertice : elem.outline )
1859  {
1860  linechain.Append( vertice.position );
1861  }
1862  linechain.Append( elem.outline.at( 0 ).position );
1863  linechain.SetClosed( true );
1864 
1865  SHAPE_POLY_SET rawPolys;
1866  rawPolys.AddOutline( linechain );
1867 
1868  for( const std::vector<ALTIUM_VERTICE>& hole : elem.holes )
1869  {
1870  SHAPE_LINE_CHAIN hole_linechain;
1871  for( const ALTIUM_VERTICE& vertice : hole )
1872  {
1873  hole_linechain.Append( vertice.position );
1874  }
1875  hole_linechain.Append( hole.at( 0 ).position );
1876  hole_linechain.SetClosed( true );
1877  rawPolys.AddHole( hole_linechain );
1878  }
1879 
1880  if( zone->GetFilledPolysUseThickness() )
1881  rawPolys.Deflate( zone->GetMinThickness() / 2, 32 );
1882 
1883  if( zone->HasFilledPolysForLayer( klayer ) )
1884  rawPolys.BooleanAdd( zone->RawPolysList( klayer ),
1886 
1887  SHAPE_POLY_SET finalPolys = rawPolys;
1889 
1890  zone->SetRawPolysList( klayer, rawPolys );
1891  zone->SetFilledPolysList( klayer, finalPolys );
1892  zone->SetIsFilled( true );
1893  zone->SetNeedRefill( false );
1894  }
1895  }
1896 
1897  if( reader.GetRemainingBytes() != 0 )
1898  {
1899  THROW_IO_ERROR( "Regions6 stream is not fully parsed" );
1900  }
1901 }
1902 
1903 
1904 void ALTIUM_PCB::ParseArcs6Data( const CFB::CompoundFileReader& aReader,
1905  const CFB::COMPOUND_FILE_ENTRY* aEntry )
1906 {
1907  if( m_progressReporter )
1908  m_progressReporter->Report( _( "Loading arcs..." ) );
1909 
1910  ALTIUM_PARSER reader( aReader, aEntry );
1911 
1912  while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
1913  {
1914  checkpoint();
1915  AARC6 elem( reader );
1916 
1917  if( elem.is_polygonoutline || elem.subpolyindex != ALTIUM_POLYGON_NONE )
1918  continue;
1919 
1920  // element in plane is in fact substracted from the plane. Should be already done by Altium?
1921  //if( IsAltiumLayerAPlane( elem.layer ) )
1922  // continue;
1923 
1924  PCB_LAYER_ID klayer = GetKicadLayer( elem.layer );
1925 
1926  if( elem.is_keepout || IsAltiumLayerAPlane( elem.layer ) )
1927  {
1928  PCB_SHAPE shape( nullptr ); // just a helper to get the graphic
1929  shape.SetWidth( elem.width );
1930  shape.SetCenter( elem.center );
1931 
1932  if( elem.startangle == 0. && elem.endangle == 360. )
1933  { // TODO: other variants to define circle?
1934  shape.SetShape( SHAPE_T::CIRCLE );
1935  shape.SetArcStart( elem.center - wxPoint( 0, elem.radius ) );
1936  }
1937  else
1938  {
1939  shape.SetShape( SHAPE_T::ARC );
1940  double startradiant = DEG2RAD( elem.startangle );
1941  wxPoint arcStartOffset = wxPoint( KiROUND( std::cos( startradiant ) * elem.radius ),
1942  -KiROUND( std::sin( startradiant ) * elem.radius ) );
1943  shape.SetArcStart( elem.center + arcStartOffset );
1944  shape.SetAngle( -NormalizeAngleDegreesPos( elem.endangle - elem.startangle ) * 10. );
1945  }
1946 
1947  ZONE* zone = new ZONE( m_board );
1948  m_board->Add( zone, ADD_MODE::APPEND );
1949 
1950  zone->SetFillVersion( 6 );
1951  zone->SetIsRuleArea( true );
1952  zone->SetDoNotAllowTracks( false );
1953  zone->SetDoNotAllowVias( false );
1954  zone->SetDoNotAllowPads( false );
1955  zone->SetDoNotAllowFootprints( false );
1956  zone->SetDoNotAllowCopperPour( true );
1957 
1958  if( elem.layer == ALTIUM_LAYER::MULTI_LAYER )
1959  {
1960  zone->SetLayer( F_Cu );
1961  zone->SetLayerSet( LSET::AllCuMask() );
1962  }
1963  else
1964  {
1965  PCB_LAYER_ID klayer = GetKicadLayer( elem.layer );
1966 
1967  if( klayer == UNDEFINED_LAYER )
1968  {
1969  wxLogWarning( _( "Arc keepout found on an Altium layer (%d) with no KiCad "
1970  "equivalent. It has been moved to KiCad layer Eco1_User." ),
1971  elem.layer );
1972  klayer = Eco1_User;
1973  }
1974  zone->SetLayer( klayer );
1975  }
1976 
1977  shape.TransformShapeWithClearanceToPolygon( *zone->Outline(), klayer, 0, ARC_HIGH_DEF,
1978  ERROR_INSIDE );
1979  zone->Outline()->Simplify( SHAPE_POLY_SET::PM_STRICTLY_SIMPLE ); // the outline is not a single polygon!
1980 
1982  ZONE::GetDefaultHatchPitch(), true );
1983  continue;
1984  }
1985 
1986  if( klayer == UNDEFINED_LAYER )
1987  {
1988  wxLogWarning( _( "Arc found on an Altium layer (%d) with no KiCad equivalent. "
1989  "It has been moved to KiCad layer Eco1_User." ),
1990  elem.layer );
1991  klayer = Eco1_User;
1992  }
1993 
1994  if( klayer >= F_Cu && klayer <= B_Cu )
1995  {
1996  double angle = -NormalizeAngleDegreesPos( elem.endangle - elem.startangle );
1997  double startradiant = DEG2RAD( elem.startangle );
1998  wxPoint arcStartOffset = wxPoint( KiROUND( std::cos( startradiant ) * elem.radius ),
1999  -KiROUND( std::sin( startradiant ) * elem.radius ) );
2000 
2001  arcStartOffset += elem.center;
2002 
2003  // If it's a circle then add two 180-degree arcs
2004  if( elem.startangle == 0. && elem.endangle == 360. )
2005  angle = 180.;
2006 
2007  SHAPE_ARC shapeArc( elem.center, arcStartOffset, angle, elem.width );
2008  PCB_ARC* arc = new PCB_ARC( m_board, &shapeArc );
2009  m_board->Add( arc, ADD_MODE::APPEND );
2010 
2011  arc->SetWidth( elem.width );
2012  arc->SetLayer( klayer );
2013  arc->SetNetCode( GetNetCode( elem.net ) );
2014 
2015  // Add second 180-degree arc for a circle
2016  if( elem.startangle == 0. && elem.endangle == 360. )
2017  {
2018  shapeArc = SHAPE_ARC( elem.center, arcStartOffset, -angle, elem.width );
2019  arc = new PCB_ARC( m_board, &shapeArc );
2020  m_board->Add( arc, ADD_MODE::APPEND );
2021 
2022  arc->SetWidth( elem.width );
2023  arc->SetLayer( klayer );
2024  arc->SetNetCode( GetNetCode( elem.net ) );
2025  }
2026  }
2027  else
2028  {
2030  shape->SetCenter( elem.center );
2031  shape->SetWidth( elem.width );
2032  shape->SetLayer( klayer );
2033 
2034  if( elem.startangle == 0. && elem.endangle == 360. )
2035  { // TODO: other variants to define circle?
2036  shape->SetShape( SHAPE_T::CIRCLE );
2037  shape->SetArcStart( elem.center - wxPoint( 0, elem.radius ) );
2038  }
2039  else
2040  {
2041  shape->SetShape( SHAPE_T::ARC );
2042  shape->SetAngle( -NormalizeAngleDegreesPos( elem.endangle - elem.startangle ) * 10. );
2043 
2044  double startradiant = DEG2RAD( elem.startangle );
2045  wxPoint arcStartOffset = wxPoint( KiROUND( std::cos( startradiant ) * elem.radius ),
2046  -KiROUND( std::sin( startradiant ) * elem.radius ) );
2047  shape->SetArcStart( elem.center + arcStartOffset );
2048  }
2049 
2051  }
2052  }
2053 
2054  if( reader.GetRemainingBytes() != 0 )
2055  {
2056  THROW_IO_ERROR( "Arcs6 stream is not fully parsed" );
2057  }
2058 }
2059 
2060 
2061 void ALTIUM_PCB::ParsePads6Data( const CFB::CompoundFileReader& aReader,
2062  const CFB::COMPOUND_FILE_ENTRY* aEntry )
2063 {
2064  if( m_progressReporter )
2065  m_progressReporter->Report( _( "Loading pads..." ) );
2066 
2067  ALTIUM_PARSER reader( aReader, aEntry );
2068 
2069  while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
2070  {
2071  checkpoint();
2072  APAD6 elem( reader );
2073 
2074  // It is possible to place altium pads on non-copper layers -> we need to interpolate them using drawings!
2075  if( !IsAltiumLayerCopper( elem.layer ) && !IsAltiumLayerAPlane( elem.layer )
2076  && elem.layer != ALTIUM_LAYER::MULTI_LAYER )
2077  {
2078  HelperParsePad6NonCopper( elem );
2079  continue;
2080  }
2081 
2082  // Create Pad
2083  FOOTPRINT* footprint = nullptr;
2084 
2085  if( elem.component == ALTIUM_COMPONENT_NONE )
2086  {
2087  footprint = new FOOTPRINT( m_board ); // We cannot add a pad directly into the PCB
2088  m_board->Add( footprint, ADD_MODE::APPEND );
2089  footprint->SetPosition( elem.position );
2090  }
2091  else
2092  {
2093  if( m_components.size() <= elem.component )
2094  {
2095  THROW_IO_ERROR( wxString::Format( "Pads6 stream tries to access component id %d "
2096  "of %d existing components",
2097  elem.component,
2098  m_components.size() ) );
2099  }
2100  footprint = m_components.at( elem.component );
2101  }
2102 
2103  PAD* pad = new PAD( footprint );
2104  footprint->Add( pad, ADD_MODE::APPEND );
2105 
2106  pad->SetNumber( elem.name );
2107  pad->SetNetCode( GetNetCode( elem.net ) );
2108  pad->SetLocked( elem.is_locked );
2109 
2110  pad->SetPosition( elem.position );
2111  pad->SetOrientationDegrees( elem.direction );
2112  pad->SetLocalCoord();
2113 
2114  pad->SetSize( elem.topsize );
2115 
2116  if( elem.holesize == 0 )
2117  {
2118  pad->SetAttribute( PAD_ATTRIB::SMD );
2119  }
2120  else
2121  {
2122  if( elem.layer != ALTIUM_LAYER::MULTI_LAYER )
2123  {
2124  // TODO: I assume other values are possible as well?
2125  wxLogError( _( "Footprint %s pad %s is not marked as multilayer, but is a TH pad." ),
2126  footprint->GetReference(),
2127  elem.name );
2128  }
2129  pad->SetAttribute( elem.plated ? PAD_ATTRIB::PTH :
2130  PAD_ATTRIB::NPTH );
2131  if( !elem.sizeAndShape || elem.sizeAndShape->holeshape == ALTIUM_PAD_HOLE_SHAPE::ROUND )
2132  {
2134  pad->SetDrillSize( wxSize( elem.holesize, elem.holesize ) );
2135  }
2136  else
2137  {
2138  switch( elem.sizeAndShape->holeshape )
2139  {
2141  wxFAIL_MSG( "Round holes are handled before the switch" );
2142  break;
2143 
2145  wxLogWarning( _( "Footprint %s pad %s has a square hole (not yet supported)." ),
2146  footprint->GetReference(),
2147  elem.name );
2149  pad->SetDrillSize( wxSize( elem.holesize, elem.holesize ) ); // Workaround
2150  // TODO: elem.sizeAndShape->slotsize was 0 in testfile. Either use holesize in this case or rect holes have a different id
2151  break;
2152 
2154  {
2156  double normalizedSlotrotation =
2157  NormalizeAngleDegreesPos( elem.sizeAndShape->slotrotation );
2158 
2159  if( normalizedSlotrotation == 0. || normalizedSlotrotation == 180. )
2160  {
2161  pad->SetDrillSize( wxSize( elem.sizeAndShape->slotsize, elem.holesize ) );
2162  }
2163  else
2164  {
2165  if( normalizedSlotrotation != 90. && normalizedSlotrotation != 270. )
2166  {
2167  wxLogWarning( _( "Footprint %s pad %s has a hole-rotation of %f "
2168  "degrees. KiCad only supports 90 degree rotations." ),
2169  footprint->GetReference(),
2170  elem.name,
2171  normalizedSlotrotation );
2172  }
2173 
2174  pad->SetDrillSize( wxSize( elem.holesize, elem.sizeAndShape->slotsize ) );
2175  }
2176  }
2177  break;
2178 
2179  default:
2181  wxLogError( _( "Footprint %s pad %s uses a hole of unknown kind %d." ),
2182  footprint->GetReference(),
2183  elem.name,
2184  elem.sizeAndShape->holeshape );
2186  pad->SetDrillSize( wxSize( elem.holesize, elem.holesize ) ); // Workaround
2187  break;
2188  }
2189  }
2190 
2191  if( elem.sizeAndShape )
2192  {
2193  pad->SetOffset( elem.sizeAndShape->holeoffset[0] );
2194  }
2195  }
2196 
2197  if( elem.padmode != ALTIUM_PAD_MODE::SIMPLE )
2198  {
2199  wxLogError( _( "Footprint %s pad %s uses a complex pad stack (not yet supported.)" ),
2200  footprint->GetReference(),
2201  elem.name );
2202  }
2203 
2204  switch( elem.topshape )
2205  {
2207  pad->SetShape( PAD_SHAPE::RECT );
2208  break;
2210  if( elem.sizeAndShape
2211  && elem.sizeAndShape->alt_shape[0] == ALTIUM_PAD_SHAPE_ALT::ROUNDRECT )
2212  {
2213  pad->SetShape( PAD_SHAPE::ROUNDRECT ); // 100 = round, 0 = rectangular
2214  double ratio = elem.sizeAndShape->cornerradius[0] / 200.;
2215  pad->SetRoundRectRadiusRatio( ratio );
2216  }
2217  else if( elem.topsize.x == elem.topsize.y )
2218  {
2219  pad->SetShape( PAD_SHAPE::CIRCLE );
2220  }
2221  else
2222  {
2223  pad->SetShape( PAD_SHAPE::OVAL );
2224  }
2225  break;
2227  pad->SetShape( PAD_SHAPE::CHAMFERED_RECT );
2228  pad->SetChamferPositions( RECT_CHAMFER_ALL );
2229  pad->SetChamferRectRatio( 0.25 );
2230  break;
2232  default:
2233  wxLogError( _( "Footprint %s pad %s uses an unknown pad-shape." ),
2234  footprint->GetReference(),
2235  elem.name );
2236  break;
2237  }
2238 
2239  switch( elem.layer )
2240  {
2242  pad->SetLayer( F_Cu );
2243  pad->SetLayerSet( PAD::SMDMask() );
2244  break;
2246  pad->SetLayer( B_Cu );
2247  pad->SetLayerSet( FlipLayerMask( PAD::SMDMask() ) );
2248  break;
2250  pad->SetLayerSet( elem.plated ? PAD::PTHMask() : PAD::UnplatedHoleMask() );
2251  break;
2252  default:
2253  PCB_LAYER_ID klayer = GetKicadLayer( elem.layer );
2254  pad->SetLayer( klayer );
2255  pad->SetLayerSet( LSET( 1, klayer ) );
2256  break;
2257  }
2258 
2260  {
2261  pad->SetLocalSolderPasteMargin( elem.pastemaskexpansionmanual );
2262  }
2263 
2265  {
2266  pad->SetLocalSolderMaskMargin( elem.soldermaskexpansionmanual );
2267  }
2268 
2269  if( elem.is_tent_top )
2270  {
2271  pad->SetLayerSet( pad->GetLayerSet().reset( F_Mask ) );
2272  }
2273  if( elem.is_tent_bottom )
2274  {
2275  pad->SetLayerSet( pad->GetLayerSet().reset( B_Mask ) );
2276  }
2277  }
2278 
2279  if( reader.GetRemainingBytes() != 0 )
2280  {
2281  THROW_IO_ERROR( "Pads6 stream is not fully parsed" );
2282  }
2283 }
2284 
2285 
2287 {
2288  PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
2289 
2290  if( klayer == UNDEFINED_LAYER )
2291  {
2292  wxLogWarning( _( "Non-copper pad %s found on an Altium layer (%d) with no KiCad equivalent. "
2293  "It has been moved to KiCad layer Eco1_User." ),
2294  aElem.name,
2295  aElem.layer );
2296  klayer = Eco1_User;
2297  }
2298 
2299  if( aElem.net != ALTIUM_NET_UNCONNECTED )
2300  {
2301  wxLogError( _( "Non-copper pad %s is connected to a net, which is not supported." ),
2302  aElem.name );
2303  }
2304 
2305  if( aElem.holesize != 0 )
2306  {
2307  wxLogError( _( "Non-copper pad %s has a hole, which is not supported." ), aElem.name );
2308  }
2309 
2310  if( aElem.padmode != ALTIUM_PAD_MODE::SIMPLE )
2311  {
2312  wxLogWarning( _( "Non-copper pad %s has a complex pad stack (not yet supported)." ),
2313  aElem.name );
2314  }
2315 
2316  switch( aElem.topshape )
2317  {
2319  {
2320  // filled rect
2322  shape->SetShape( SHAPE_T::POLY );
2323  shape->SetFilled( true );
2324  shape->SetLayer( klayer );
2325  shape->SetWidth( 0 );
2326 
2327  shape->SetPolyPoints( { aElem.position + wxPoint( aElem.topsize.x / 2, aElem.topsize.y / 2 ),
2328  aElem.position + wxPoint( aElem.topsize.x / 2, -aElem.topsize.y / 2 ),
2329  aElem.position + wxPoint( -aElem.topsize.x / 2, -aElem.topsize.y / 2 ),
2330  aElem.position + wxPoint( -aElem.topsize.x / 2, aElem.topsize.y / 2 ) } );
2331 
2332  if( aElem.direction != 0 )
2333  shape->Rotate( aElem.position, aElem.direction * 10 );
2334 
2335  HelperDrawsegmentSetLocalCoord( shape, aElem.component );
2336  }
2337  break;
2338 
2340  if( aElem.sizeAndShape
2341  && aElem.sizeAndShape->alt_shape[0] == ALTIUM_PAD_SHAPE_ALT::ROUNDRECT )
2342  {
2343  // filled roundrect
2344  int cornerradius = aElem.sizeAndShape->cornerradius[0];
2345  int offset = ( std::min( aElem.topsize.x, aElem.topsize.y ) * cornerradius ) / 200;
2346 
2348  shape->SetLayer( klayer );
2349  shape->SetWidth( offset * 2 );
2350 
2351  if( cornerradius < 100 )
2352  {
2353  int offsetX = aElem.topsize.x / 2 - offset;
2354  int offsetY = aElem.topsize.y / 2 - offset;
2355 
2356  wxPoint p11 = aElem.position + wxPoint( offsetX, offsetY );
2357  wxPoint p12 = aElem.position + wxPoint( offsetX, -offsetY );
2358  wxPoint p22 = aElem.position + wxPoint( -offsetX, -offsetY );
2359  wxPoint p21 = aElem.position + wxPoint( -offsetX, offsetY );
2360 
2361  shape->SetShape( SHAPE_T::POLY );
2362  shape->SetFilled( true );
2363  shape->SetPolyPoints( { p11, p12, p22, p21 } );
2364  }
2365  else if( aElem.topsize.x == aElem.topsize.y )
2366  {
2367  // circle
2368  shape->SetShape( SHAPE_T::CIRCLE );
2369  shape->SetFilled( true );
2370  shape->SetCenter( aElem.position );
2371  shape->SetWidth( aElem.topsize.x / 2 );
2372  shape->SetArcStart( aElem.position - wxPoint( 0, aElem.topsize.x / 4 ) );
2373  }
2374  else if( aElem.topsize.x < aElem.topsize.y )
2375  {
2376  // short vertical line
2377  shape->SetShape( SHAPE_T::SEGMENT );
2378  wxPoint pointOffset( 0, ( aElem.topsize.y - aElem.topsize.x ) / 2 );
2379  shape->SetStart( aElem.position + pointOffset );
2380  shape->SetEnd( aElem.position - pointOffset );
2381  }
2382  else
2383  {
2384  // short horizontal line
2385  shape->SetShape( SHAPE_T::SEGMENT );
2386  wxPoint pointOffset( ( aElem.topsize.x - aElem.topsize.y ) / 2, 0 );
2387  shape->SetStart( aElem.position + pointOffset );
2388  shape->SetEnd( aElem.position - pointOffset );
2389  }
2390 
2391  if( aElem.direction != 0 )
2392  shape->Rotate( aElem.position, aElem.direction * 10 );
2393 
2394  HelperDrawsegmentSetLocalCoord( shape, aElem.component );
2395  }
2396  else if( aElem.topsize.x == aElem.topsize.y )
2397  {
2398  // filled circle
2400  shape->SetShape( SHAPE_T::CIRCLE );
2401  shape->SetFilled( true );
2402  shape->SetLayer( klayer );
2403  shape->SetCenter( aElem.position );
2404  shape->SetWidth( aElem.topsize.x / 2 );
2405  shape->SetArcStart( aElem.position - wxPoint( 0, aElem.topsize.x / 4 ) );
2406  HelperDrawsegmentSetLocalCoord( shape, aElem.component );
2407  }
2408  else
2409  {
2410  // short line
2412  shape->SetShape( SHAPE_T::SEGMENT );
2413  shape->SetLayer( klayer );
2414  shape->SetWidth( std::min( aElem.topsize.x, aElem.topsize.y ) );
2415 
2416  if( aElem.topsize.x < aElem.topsize.y )
2417  {
2418  wxPoint offset( 0, ( aElem.topsize.y - aElem.topsize.x ) / 2 );
2419  shape->SetStart( aElem.position + offset );
2420  shape->SetEnd( aElem.position - offset );
2421  }
2422  else
2423  {
2424  wxPoint offset( ( aElem.topsize.x - aElem.topsize.y ) / 2, 0 );
2425  shape->SetStart( aElem.position + offset );
2426  shape->SetEnd( aElem.position - offset );
2427  }
2428 
2429  if( aElem.direction != 0 )
2430  shape->Rotate( aElem.position, aElem.direction * 10. );
2431 
2432  HelperDrawsegmentSetLocalCoord( shape, aElem.component );
2433  }
2434  break;
2435 
2437  {
2438  // filled octagon
2440  shape->SetShape( SHAPE_T::POLY );
2441  shape->SetFilled( true );
2442  shape->SetLayer( klayer );
2443  shape->SetWidth( 0 );
2444 
2445  wxPoint p11 = aElem.position + wxPoint( aElem.topsize.x / 2, aElem.topsize.y / 2 );
2446  wxPoint p12 = aElem.position + wxPoint( aElem.topsize.x / 2, -aElem.topsize.y / 2 );
2447  wxPoint p22 = aElem.position + wxPoint( -aElem.topsize.x / 2, -aElem.topsize.y / 2 );
2448  wxPoint p21 = aElem.position + wxPoint( -aElem.topsize.x / 2, aElem.topsize.y / 2 );
2449 
2450  int chamfer = std::min( aElem.topsize.x, aElem.topsize.y ) / 4;
2451  wxPoint chamferX( chamfer, 0 );
2452  wxPoint chamferY( 0, chamfer );
2453 
2454  shape->SetPolyPoints( { p11 - chamferX, p11 - chamferY, p12 + chamferY, p12 - chamferX,
2455  p22 + chamferX, p22 + chamferY, p21 - chamferY, p21 + chamferX } );
2456 
2457  if( aElem.direction != 0. )
2458  shape->Rotate( aElem.position, aElem.direction * 10 );
2459 
2460  HelperDrawsegmentSetLocalCoord( shape, aElem.component );
2461  }
2462  break;
2463 
2465  default:
2466  wxLogError( _( "Non-copper pad %s uses an unknown pad-shape." ), aElem.name );
2467  break;
2468  }
2469 }
2470 
2471 void ALTIUM_PCB::ParseVias6Data( const CFB::CompoundFileReader& aReader,
2472  const CFB::COMPOUND_FILE_ENTRY* aEntry )
2473 {
2474  if( m_progressReporter )
2475  m_progressReporter->Report( _( "Loading vias..." ) );
2476 
2477  ALTIUM_PARSER reader( aReader, aEntry );
2478 
2479  while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
2480  {
2481  checkpoint();
2482  AVIA6 elem( reader );
2483 
2484  PCB_VIA* via = new PCB_VIA( m_board );
2486 
2487  via->SetPosition( elem.position );
2488  via->SetWidth( elem.diameter );
2489  via->SetDrill( elem.holesize );
2490  via->SetNetCode( GetNetCode( elem.net ) );
2491  via->SetLocked( elem.is_locked );
2492 
2493  bool start_layer_outside = elem.layer_start == ALTIUM_LAYER::TOP_LAYER
2495  bool end_layer_outside = elem.layer_end == ALTIUM_LAYER::TOP_LAYER
2497 
2498  if( start_layer_outside && end_layer_outside )
2499  {
2500  via->SetViaType( VIATYPE::THROUGH );
2501  }
2502  else if( ( !start_layer_outside ) && ( !end_layer_outside ) )
2503  {
2504  via->SetViaType( VIATYPE::BLIND_BURIED );
2505  }
2506  else
2507  {
2508  via->SetViaType( VIATYPE::MICROVIA ); // TODO: always a microvia?
2509  }
2510 
2511  PCB_LAYER_ID start_klayer = GetKicadLayer( elem.layer_start );
2512  PCB_LAYER_ID end_klayer = GetKicadLayer( elem.layer_end );
2513 
2514  if( !IsCopperLayer( start_klayer ) || !IsCopperLayer( end_klayer ) )
2515  {
2516  wxLogError( _( "Via from layer %d to %d uses a non-copper layer, which is not "
2517  "supported." ),
2518  elem.layer_start,
2519  elem.layer_end );
2520  continue; // just assume through-hole instead.
2521  }
2522 
2523  // we need VIATYPE set!
2524  via->SetLayerPair( start_klayer, end_klayer );
2525  }
2526 
2527  if( reader.GetRemainingBytes() != 0 )
2528  {
2529  THROW_IO_ERROR( "Vias6 stream is not fully parsed" );
2530  }
2531 }
2532 
2533 void ALTIUM_PCB::ParseTracks6Data( const CFB::CompoundFileReader& aReader,
2534  const CFB::COMPOUND_FILE_ENTRY* aEntry )
2535 {
2536  if( m_progressReporter )
2537  m_progressReporter->Report( _( "Loading tracks..." ) );
2538 
2539  ALTIUM_PARSER reader( aReader, aEntry );
2540 
2541  while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
2542  {
2543  checkpoint();
2544  ATRACK6 elem( reader );
2545 
2546  if( elem.is_polygonoutline || elem.subpolyindex != ALTIUM_POLYGON_NONE )
2547  continue;
2548 
2549  // element in plane is in fact substracted from the plane. Already done by Altium?
2550  //if( IsAltiumLayerAPlane( elem.layer ) )
2551  // continue;
2552 
2553  PCB_LAYER_ID klayer = GetKicadLayer( elem.layer );
2554 
2555  if( elem.is_keepout || IsAltiumLayerAPlane( elem.layer ) )
2556  {
2557  PCB_SHAPE shape( nullptr ); // just a helper to get the graphic
2558  shape.SetShape( SHAPE_T::SEGMENT );
2559  shape.SetStart( elem.start );
2560  shape.SetEnd( elem.end );
2561  shape.SetWidth( elem.width );
2562 
2563  ZONE* zone = new ZONE( m_board );
2564  m_board->Add( zone, ADD_MODE::APPEND );
2565 
2566  zone->SetFillVersion( 6 );
2567  zone->SetIsRuleArea( true );
2568  zone->SetDoNotAllowTracks( false );
2569  zone->SetDoNotAllowVias( false );
2570  zone->SetDoNotAllowPads( false );
2571  zone->SetDoNotAllowFootprints( false );
2572  zone->SetDoNotAllowCopperPour( true );
2573 
2574  if( elem.layer == ALTIUM_LAYER::MULTI_LAYER )
2575  {
2576  zone->SetLayer( F_Cu );
2577  zone->SetLayerSet( LSET::AllCuMask() );
2578  }
2579  else
2580  {
2581  PCB_LAYER_ID klayer = GetKicadLayer( elem.layer );
2582 
2583  if( klayer == UNDEFINED_LAYER )
2584  {
2585  wxLogWarning( _( "Track keepout found on an Altium layer (%d) with no KiCad "
2586  "equivalent. It has been moved to KiCad layer Eco1_User." ),
2587  elem.layer );
2588  klayer = Eco1_User;
2589  }
2590  zone->SetLayer( klayer );
2591  }
2592 
2593  shape.TransformShapeWithClearanceToPolygon( *zone->Outline(), klayer, 0, ARC_HIGH_DEF,
2594  ERROR_INSIDE );
2595 
2597  ZONE::GetDefaultHatchPitch(), true );
2598  continue;
2599  }
2600 
2601  if( klayer == UNDEFINED_LAYER )
2602  {
2603  wxLogWarning( _( "Track found on an Altium layer (%d) with no KiCadequivalent. "
2604  "It has been moved to KiCad layer Eco1_User." ),
2605  elem.layer );
2606  klayer = Eco1_User;
2607  }
2608 
2609  if( klayer >= F_Cu && klayer <= B_Cu )
2610  {
2611  PCB_TRACK* track = new PCB_TRACK( m_board );
2612  m_board->Add( track, ADD_MODE::APPEND );
2613 
2614  track->SetStart( elem.start );
2615  track->SetEnd( elem.end );
2616  track->SetWidth( elem.width );
2617  track->SetLayer( klayer );
2618  track->SetNetCode( GetNetCode( elem.net ) );
2619  }
2620  else
2621  {
2623  shape->SetShape( SHAPE_T::SEGMENT );
2624  shape->SetStart( elem.start );
2625  shape->SetEnd( elem.end );
2626  shape->SetWidth( elem.width );
2627  shape->SetLayer( klayer );
2629  }
2630 
2631  reader.SkipSubrecord();
2632  }
2633 
2634  if( reader.GetRemainingBytes() != 0 )
2635  {
2636  THROW_IO_ERROR( "Tracks6 stream is not fully parsed" );
2637  }
2638 }
2639 
2640 void ALTIUM_PCB::ParseTexts6Data( const CFB::CompoundFileReader& aReader,
2641  const CFB::COMPOUND_FILE_ENTRY* aEntry )
2642 {
2643  if( m_progressReporter )
2644  m_progressReporter->Report( _( "Loading text..." ) );
2645 
2646  ALTIUM_PARSER reader( aReader, aEntry );
2647 
2648  while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
2649  {
2650  checkpoint();
2651  ATEXT6 elem( reader );
2652 
2653  if( elem.fonttype == ALTIUM_TEXT_TYPE::BARCODE )
2654  {
2655  wxLogError( _( "Ignored barcode on Altium layer %d (not yet supported)." ),
2656  elem.layer );
2657  continue;
2658  }
2659 
2660  // TODO: better approach to select if item belongs to a FOOTPRINT
2661  EDA_TEXT* tx = nullptr;
2662  BOARD_ITEM* itm = nullptr;
2663 
2664  if( elem.component == ALTIUM_COMPONENT_NONE )
2665  {
2666  PCB_TEXT* pcbText = new PCB_TEXT( m_board );
2667  tx = pcbText;
2668  itm = pcbText;
2669  m_board->Add( pcbText, ADD_MODE::APPEND );
2670  }
2671  else
2672  {
2673  if( m_components.size() <= elem.component )
2674  {
2675  THROW_IO_ERROR( wxString::Format( "Texts6 stream tries to access component id %d "
2676  "of %d existing components",
2677  elem.component,
2678  m_components.size() ) );
2679  }
2680 
2681  FOOTPRINT* footprint = m_components.at( elem.component );
2682  FP_TEXT* fpText;
2683 
2684  if( elem.isDesignator )
2685  {
2686  fpText = &footprint->Reference();
2687  }
2688  else if( elem.isComment )
2689  {
2690  fpText = &footprint->Value();
2691  }
2692  else
2693  {
2694  fpText = new FP_TEXT( footprint );
2695  footprint->Add( fpText, ADD_MODE::APPEND );
2696  }
2697 
2698  fpText->SetKeepUpright( false );
2699 
2700  tx = fpText;
2701  itm = fpText;
2702  }
2703 
2704  wxString trimmedText = elem.text.Trim();
2705  if( !elem.isDesignator && trimmedText.CmpNoCase( ".Designator" ) == 0 )
2706  {
2707  tx->SetText( "${REFERENCE}" );
2708  }
2709  else if( !elem.isComment && trimmedText.CmpNoCase( ".Comment" ) == 0 )
2710  {
2711  tx->SetText( "${VALUE}" );
2712  }
2713  else if( trimmedText.CmpNoCase( ".Layer_Name" ) == 0 )
2714  {
2715  tx->SetText( "${LAYER}" );
2716  }
2717  else
2718  {
2719  tx->SetText( elem.text );
2720  }
2721 
2722  itm->SetPosition( elem.position );
2723  tx->SetTextAngle( elem.rotation * 10. );
2724 
2725  if( elem.component != ALTIUM_COMPONENT_NONE )
2726  {
2727  FP_TEXT* fpText = dynamic_cast<FP_TEXT*>( tx );
2728 
2729  if( fpText )
2730  {
2731  FOOTPRINT* parentFootprint = static_cast<FOOTPRINT*>( fpText->GetParent() );
2732  double orientation = parentFootprint->GetOrientation();
2733 
2734  fpText->SetTextAngle( fpText->GetTextAngle() - orientation );
2735  fpText->SetLocalCoord();
2736  }
2737  }
2738 
2739  PCB_LAYER_ID klayer = GetKicadLayer( elem.layer );
2740 
2741  if( klayer == UNDEFINED_LAYER )
2742  {
2743  wxLogWarning( _( "Text found on an Altium layer (%d) with no KiCad equivalent. "
2744  "It has been moved to KiCad layer Eco1_User." ),
2745  elem.layer );
2746  klayer = Eco1_User;
2747  }
2748 
2749  itm->SetLayer( klayer );
2750 
2751  if( elem.fonttype == ALTIUM_TEXT_TYPE::TRUETYPE )
2752  {
2753  // TODO: why is this required? Somehow, truetype size is calculated differently
2754  tx->SetTextSize( wxSize( elem.height / 2, elem.height / 2 ) );
2755  }
2756  else
2757  {
2758  tx->SetTextSize( wxSize( elem.height, elem.height ) ); // TODO: parse text width
2759  }
2760 
2761  tx->SetTextThickness( elem.strokewidth );
2762  tx->SetBold( elem.isBold );
2763  tx->SetItalic( elem.isItalic );
2764  tx->SetMirrored( elem.isMirrored );
2765 
2766  if( elem.isDesignator || elem.isComment ) // That's just a bold assumption
2767  {
2770  }
2771  else
2772  {
2773  switch( elem.textposition )
2774  {
2779  break;
2784  break;
2789  break;
2790  default:
2791  wxLogError( "Unexpected horizontal Text Position. This should never happen." );
2792  break;
2793  }
2794 
2795  switch( elem.textposition )
2796  {
2801  break;
2806  break;
2811  break;
2812  default:
2813  wxLogError( "Unexpected vertical text position. This should never happen." );
2814  break;
2815  }
2816  }
2817  }
2818 
2819  if( reader.GetRemainingBytes() != 0 )
2820  {
2821  THROW_IO_ERROR( "Texts6 stream is not fully parsed" );
2822  }
2823 }
2824 
2825 void ALTIUM_PCB::ParseFills6Data( const CFB::CompoundFileReader& aReader,
2826  const CFB::COMPOUND_FILE_ENTRY* aEntry )
2827 {
2828  if( m_progressReporter )
2829  m_progressReporter->Report( _( "Loading rectangles..." ) );
2830 
2831  ALTIUM_PARSER reader( aReader, aEntry );
2832 
2833  while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
2834  {
2835  checkpoint();
2836  AFILL6 elem( reader );
2837 
2838  wxPoint p11( elem.pos1.x, elem.pos1.y );
2839  wxPoint p12( elem.pos1.x, elem.pos2.y );
2840  wxPoint p22( elem.pos2.x, elem.pos2.y );
2841  wxPoint p21( elem.pos2.x, elem.pos1.y );
2842 
2843  wxPoint center( ( elem.pos1.x + elem.pos2.x ) / 2, ( elem.pos1.y + elem.pos2.y ) / 2 );
2844 
2845  PCB_LAYER_ID klayer = GetKicadLayer( elem.layer );
2846 
2847  if( klayer == UNDEFINED_LAYER )
2848  {
2849  wxLogWarning( _( "Fill found on an Altium layer (%d) with no KiCad equivalent. "
2850  "It has been moved to KiCad layer Eco1_User." ),
2851  elem.layer );
2852  klayer = Eco1_User;
2853  }
2854 
2855  if( elem.is_keepout || elem.net != ALTIUM_NET_UNCONNECTED )
2856  {
2857  ZONE* zone = new ZONE( m_board );
2858  m_board->Add( zone, ADD_MODE::APPEND );
2859 
2860  zone->SetFillVersion( 6 );
2861  zone->SetNetCode( GetNetCode( elem.net ) );
2862  zone->SetLayer( klayer );
2863  zone->SetPosition( elem.pos1 );
2864  zone->SetPriority( 1000 );
2865 
2866  const int outlineIdx = -1; // this is the id of the copper zone main outline
2867  zone->AppendCorner( p11, outlineIdx );
2868  zone->AppendCorner( p12, outlineIdx );
2869  zone->AppendCorner( p22, outlineIdx );
2870  zone->AppendCorner( p21, outlineIdx );
2871 
2872  // should be correct?
2873  zone->SetLocalClearance( 0 );
2875 
2876  if( elem.is_keepout )
2877  {
2878  zone->SetIsRuleArea( true );
2879  zone->SetDoNotAllowTracks( false );
2880  zone->SetDoNotAllowVias( false );
2881  zone->SetDoNotAllowPads( false );
2882  zone->SetDoNotAllowFootprints( false );
2883  zone->SetDoNotAllowCopperPour( true );
2884  }
2885 
2886  if( elem.rotation != 0. )
2887  zone->Rotate( center, elem.rotation * 10 );
2888 
2890  ZONE::GetDefaultHatchPitch(), true );
2891  }
2892  else
2893  {
2894  PCB_SHAPE* shape = new PCB_SHAPE( m_board );
2895  m_board->Add( shape, ADD_MODE::APPEND );
2896 
2897  shape->SetShape( SHAPE_T::POLY );
2898  shape->SetFilled( true );
2899  shape->SetLayer( klayer );
2900  shape->SetWidth( 0 );
2901 
2902  shape->SetPolyPoints( { p11, p12, p22, p21 } );
2903 
2904  if( elem.rotation != 0. )
2905  shape->Rotate( center, elem.rotation * 10 );
2906  }
2907  }
2908 
2909  if( reader.GetRemainingBytes() != 0 )
2910  {
2911  THROW_IO_ERROR( "Fills6 stream is not fully parsed" );
2912  }
2913 }
void SetMirrored(bool isMirrored)
Definition: eda_text.h:188
double EuclideanNorm(const wxPoint &vector)
Euclidean norm of a 2D vector.
Definition: trigo.h:146
void SetReference(const wxString &aReference)
Definition: footprint.h:439
void HelperParseDimensions6Datum(const ADIMENSION6 &aElem)
static LSET AllCuMask(int aCuLayerCount=MAX_CU_LAYERS)
Return a mask holding the requested number of Cu PCB_LAYER_IDs.
Definition: lset.cpp:750
uint32_t textlinewidth
uint32_t linewidth
FP_3DMODEL::VECTOR3D modelPosition
int32_t soldermaskexpansionmanual
ALTIUM_RULE_KIND kind
double rotation
uint32_t holesize
void 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:282
void Parse(const CFB::CompoundFileReader &aReader, const std::map< ALTIUM_PCB_DIR, std::string > &aFileMapping)
Definition: altium_pcb.cpp:348
Arcs (with rounded ends)
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:535
LSET FlipLayerMask(LSET aMask, int aCopperLayersCount)
Calculate the mask layer when flipping a footprint.
Definition: lset.cpp:567
void ParseShapeBasedRegions6Data(const CFB::CompoundFileReader &aReader, const CFB::COMPOUND_FILE_ENTRY *aEntry)
ALTIUM_PAD_SHAPE topshape
bool HasFilledPolysForLayer(PCB_LAYER_ID aLayer) const
Definition: zone.h:627
std::vector< ZONE * > m_polygons
Definition: altium_pcb.h:193
double GetOrientationRadians() const
Definition: footprint.h:192
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
wxPoint m_GridOrigin
origin for grid offsets
const ARULE6 * GetRule(ALTIUM_RULE_KIND aKind, const wxString &aName) const
Definition: altium_pcb.cpp:644
std::list< FP_3DMODEL > & Models()
Definition: footprint.h:182
void SetHeight(int aHeight)
Set the distance from the feature points to the crossbar line.
void SetShape(SHAPE_T aShape)
Definition: pcb_shape.h:109
void SetDoNotAllowTracks(bool aEnable)
Definition: zone.h:745
polygon (not yet used for tracks, but could be in microwave apps)
wxPoint start
wxSize topsize
SHAPE_POLY_SET & GetPolyShape()
Definition: pcb_shape.h:240
bool m_LegacyNetclassesLoaded
True if netclasses were loaded from the file.
Definition: board.h:272
void ParseComponents6Data(const CFB::CompoundFileReader &aReader, const CFB::COMPOUND_FILE_ENTRY *aEntry)
Definition: altium_pcb.cpp:946
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.
unsigned m_totalCount
for progress reporting
Definition: altium_pcb.h:205
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:192
virtual void SetPosition(const wxPoint &aPos)
Definition: eda_item.h:253
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition: board_item.h:80
void SetBorderDisplayStyle(ZONE_BORDER_DISPLAY_STYLE aHatchStyle, int aHatchPitch, bool aRebuildHatch)
Set all hatch parameters for the zone.
Definition: zone.cpp:896
ALTIUM_REGION_KIND kind
Definition: board.h:72
SHAPE_T GetShape() const
Definition: pcb_shape.h:110
int GetX() const
Definition: eda_rect.h:98
void SetEnd(const wxPoint &aEnd)
Definition: pcb_track.h:104
wxPoint position
const EDA_RECT GetBoardEdgesBoundingBox() const
Return the board bounding box calculated using exclusively the board edges (graphics on Edge....
Definition: board.h:742
void ParsePolygons6Data(const CFB::CompoundFileReader &aReader, const CFB::COMPOUND_FILE_ENTRY *aEntry)
PCB_TEXT & Text()
A progress reporter interface for use in multi-threaded environments.
void SetFilled(bool aFlag)
Definition: pcb_shape.h:73
OPT_VECTOR2I Intersect(const SEG &aSeg, bool aIgnoreEndpoints=false, bool aLines=false) const
Compute intersection point of segment (this) with segment aSeg.
Definition: seg.cpp:154
void SetTextAngle(double aAngle) override
Definition: fp_text.cpp:73
double startangle
size_t GetRemainingBytes() const
void SetLayerSet(LSET aLayerSet) override
Definition: zone.cpp:246
uint16_t component
SHAPE_POLY_SET * Outline()
Definition: zone.h:320
int GetRadius() const
Return the radius of this item.
Definition: pcb_shape.cpp:487
void SetItalic(bool isItalic)
Definition: eda_text.h:179
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:109
usual segment : line with rounded ends
ALTIUM_LAYER layer
double GetOrientation() const
Definition: footprint.h:190
double GetTextAngle() const
Definition: eda_text.h:174
void SetCopperLayerCount(int aCount)
Definition: board.cpp:460
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
ALTIUM_LAYER layer_end
void ParseComponentsBodies6Data(const CFB::CompoundFileReader &aReader, const CFB::COMPOUND_FILE_ENTRY *aEntry)
Definition: altium_pcb.cpp:993
void SetOrientationDegrees(double aOrientation)
Definition: footprint.h:189
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:237
virtual void SetLocked(bool aLocked)
Modify the 'lock' status for of the item.
Definition: board_item.h:252
uint16_t net
bool SetLayerType(PCB_LAYER_ID aLayer, LAYER_T aLayerType)
Change the type of the layer given by aLayer.
Definition: board.cpp:409
wxString sourcefootprintlibrary
void RotatePoint(int *pX, int *pY, double angle)
Definition: trigo.cpp:229
const double startangle
A logical library item identifier and consists of various portions much like a URI.
Definition: lib_id.h:51
wxString name
virtual void Rotate(const wxPoint &aRotCentre, double aAngle) override
Rotate this object.
Definition: pcb_shape.cpp:241
unsigned m_lastProgressCount
Definition: altium_pcb.h:204
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:228
VECTOR3D m_Offset
3D model offset (mm)
Definition: footprint.h:95
void ParseArcs6Data(const CFB::CompoundFileReader &aReader, const CFB::COMPOUND_FILE_ENTRY *aEntry)
wxString scope2expr
BOARD_DESIGN_SETTINGS & GetDesignSettings() const
Definition: board.cpp:589
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:200
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:661
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:627
FP_TEXT & Value()
read/write accessors:
Definition: footprint.h:466
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:742
wxString textformat
FP_TEXT & Reference()
Definition: footprint.h:467
A mix-in class (via multiple inheritance) that handles texts such as labels, parts,...
Definition: eda_text.h:119
std::map< wxString, wxString > m_models
Definition: altium_pcb.h:195
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:707
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:185
void Add(BOARD_ITEM *aItem, ADD_MODE aMode=ADD_MODE::INSERT) override
Adds an item to the container.
Definition: board.cpp:607
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:192
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:191
ALTIUM_PAD_RULE pastemaskexpansionmode
wxPoint position
uint16_t subpolyindex
double direction
LSET is a set of PCB_LAYER_IDs.
Definition: layer_ids.h:502
std::map< ALTIUM_LAYER, PCB_LAYER_ID > m_layermap
Definition: altium_pcb.h:197
pads are covered by copper
void ParseDimensions6Data(const CFB::CompoundFileReader &aReader, const CFB::COMPOUND_FILE_ENTRY *aEntry)
ALTIUM_RULE_KIND
int GetMinThickness() const
Definition: zone.h:244
wxString textsuffix
virtual void SetText(const wxString &aText)
Definition: eda_text.cpp:114
const EDA_RECT GetBoundingBox() const override
Definition: zone.cpp:322
void SetHatchGap(int aStep)
Definition: zone.h: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:1088
wxString NotSpecifiedPrm()
void SetDoNotAllowPads(bool aEnable)
Definition: zone.h:746
Represent a set of closed polygons.
virtual void SetEnd(const wxPoint &aPoint)
ALTIUM_CLASS_KIND kind
double GetRadius() const
Definition: pcb_track.cpp:968
void SetVertJustify(EDA_TEXT_VJUSTIFY_T aType)
Definition: eda_text.h:202
ALTIUM_LAYER layer
const uint16_t ALTIUM_NET_UNCONNECTED
unsigned m_doneCount
Definition: altium_pcb.h:203
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:377
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:906
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
void ParseFileHeader(const CFB::CompoundFileReader &aReader, const CFB::COMPOUND_FILE_ENTRY *aEntry)
Definition: altium_pcb.cpp:678
static LSET PTHMask()
layer set for a through hole pad
Definition: pad.cpp:168
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:430
#define _(s)
uint16_t component
void SetDoNotAllowVias(bool aEnable)
Definition: zone.h:744
double m_Opacity
Definition: footprint.h:96
NETCLASSES & GetNetClasses() const
virtual wxPoint GetCenter() const
This defaults to the center of the bounding box if not overridden.
Definition: board_item.h:112
uint32_t holesize
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 SetCenter(const wxPoint &aCenterPoint)
Definition: pcb_shape.h:198
void SetIsFilled(bool isFilled)
Definition: zone.h:235
const int32_t radius
void SetPrecision(int aPrecision)
uint16_t net
void Fracture(POLYGON_MODE aFastMode)
Convert a single outline slitted ("fractured") polygon into a set ouf outlines with holes.
int AddHole(const SHAPE_LINE_CHAIN &aHole, int aOutline=-1)
Return the area of this poly set.
void HelperParseDimensions6Center(const ADIMENSION6 &aElem)
int GetHeight() const
Definition: eda_rect.h:110
const std::vector< BOARD_STACKUP_ITEM * > & GetList() const
void ParseModelsData(const CFB::CompoundFileReader &aReader, const CFB::COMPOUND_FILE_ENTRY *aEntry, const wxString &aRootDir)
bool IsCopperLayer(LAYER_NUM aLayerId)
Tests whether a layer is a copper layer.
Definition: layer_ids.h:787
void SetFilledPolysList(PCB_LAYER_ID aLayer, const SHAPE_POLY_SET &aPolysList)
Set the list of filled polygons.
Definition: zone.h:650
size_t m_num_nets
Definition: altium_pcb.h:196
int m_highest_pour_index
Altium stores pour order across all layers.
Definition: altium_pcb.h:208
bool is_keepout
const wxPoint center
FP_3DMODEL::VECTOR3D modelRotation
void Format(OUTPUTFORMATTER *out, int aNestLevel, int aCtl, const CPTREE &aTree)
Output a PTREE into s-expression format via an OUTPUTFORMATTER derivative.
Definition: ptree.cpp:200
wxString m_Filename
The 3D shape filename in 3D library.
Definition: footprint.h:97
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:267
uint16_t subpolyindex
wxPoint GetPosition() const override
Definition: pcb_shape.cpp:70
bool IsFlipped() const
Definition: footprint.h:270
Use thermal relief for pads.
void SetFPID(const LIB_ID &aFPID)
Definition: footprint.h:195
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:64
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:836
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:94
ALTIUM_LAYER layer
uint16_t net
ALTIUM_LAYER layer_start
const char * name
Definition: DXF_plotter.cpp:56
double DEG2RAD(double deg)
Definition: trigo.h:229
void HelperParsePad6NonCopper(const APAD6 &aElem)
void SetStart(const wxPoint &aStart)
Definition: pcb_shape.h:127
void ParseNets6Data(const CFB::CompoundFileReader &aReader, const CFB::COMPOUND_FILE_ENTRY *aEntry)
Information pertinent to a Pcbnew printed circuit board.
Definition: board.h:190
void SetHorizJustify(EDA_TEXT_HJUSTIFY_T aType)
Definition: eda_text.h:201
void SetDoNotAllowCopperPour(bool aEnable)
Definition: zone.h:743
Represent a polyline (an zero-thickness chain of connected line segments).
PCB_LAYER_ID
A quick note on layer IDs:
Definition: layer_ids.h:64
uint32_t radius
wxPoint position
Definition: layer_ids.h:70
static DIRECTION_45::AngleType angle(const VECTOR2I &a, const VECTOR2I &b)
std::vector< ALTIUM_VERTICE > outline
wxString ReadWxString()
Definition: altium_parser.h:69
int32_t pastemaskexpansionmanual
wxString name
Handle the component boundary box.
Definition: eda_rect.h:42
virtual void SetStart(const wxPoint &aPoint)
void SetLocked(bool isLocked) override
Set the #MODULE_is_LOCKED bit in the m_ModuleStatus.
Definition: footprint.h:296
uint16_t net
void SetWidth(int aWidth)
Definition: pcb_shape.h:96
void SetLocalCoord()
Definition: fp_text.cpp:209
void HelperCreateBoardOutline(const std::vector< ALTIUM_VERTICE > &aVertices)
Definition: altium_pcb.cpp:841
void SetHatchOrientation(double aStep)
Definition: zone.h:260
int GetY() const
Definition: eda_rect.h:99
constexpr ret_type KiROUND(fp_type v)
Round a floating point number to an integer using "round halfway cases away from zero".
Definition: util.h:73
Pads are not covered.
wxPoint GetPosition() const override
Definition: footprint.h:186
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:718
bool IsAltiumLayerCopper(ALTIUM_LAYER aLayer)
Definition: altium_pcb.cpp:105
void SetFillVersion(int aVersion)
Definition: zone.h:693
ALTIUM_LAYER
void SetThermalReliefGap(int aThermalReliefGap)
Definition: zone.h:183
std::map< ALTIUM_RULE_KIND, std::vector< ARULE6 > > m_rules
Definition: altium_pcb.h:198
ALTIUM_PAD_MODE padmode
ALTIUM_LAYER layer
std::unique_ptr< APAD6_SIZE_AND_SHAPE > sizeAndShape
const double endangle
A leader is a dimension-like object pointing to a specific point.
void SetLocalCoord()
Set relative coordinates from draw coordinates.
Definition: fp_shape.cpp:54
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:238
std::vector< ALTIUM_VERTICE > board_vertices
void SetTextThickness(int aWidth)
The TextThickness is that set by the user.
Definition: eda_text.h:159
void SetPolyShape(const SHAPE_POLY_SET &aShape)
Definition: pcb_shape.h:248
static LSET UnplatedHoleMask()
layer set for a mechanical unplated through hole pad
Definition: pad.cpp:189
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:194
void SetLocalClearance(int aClearance)
Definition: zone.h:158
virtual void SetTextAngle(double aAngle)
Definition: eda_text.h:167
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:658
std::vector< wxPoint > referencePoint
Definition: pad.h:57
void SetPosition(const wxPoint &aPos) override
Definition: footprint.cpp:1499
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 draw segment to a closed polygon.
void BuildDefaultStackupList(const BOARD_DESIGN_SETTINGS *aSettings, int aActiveCopperLayersCount=0)
Create a default stackup, according to the current BOARD_DESIGN_SETTINGS settings.
void ParseBoard6Data(const CFB::CompoundFileReader &aReader, const CFB::COMPOUND_FILE_ENTRY *aEntry)
Definition: altium_pcb.cpp:697
BOARD_ITEM_CONTAINER * GetParent() const
Definition: board_item.h:166
bool is_tent_top
virtual void SetAngle(double aAngle, bool aUpdateEnd=true)
Set the angle for arcs, and normalizes it within the range 0 - 360 degrees.
Definition: pcb_shape.cpp:519
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:175
void SetBold(bool aBold)
Definition: eda_text.h:182
uint32_t textheight
ALTIUM_POLYGON_HATCHSTYLE hatchstyle
static const int UNCONNECTED
Constant that forces initialization of a netinfo item to the NETINFO_ITEM ORPHANED (typically -1) whe...
Definition: netinfo.h:365
uint16_t component
#define THROW_IO_ERROR(msg)
Definition: ki_exception.h:38
bool HasParsingError()
PCB_SHAPE * HelperCreateAndAddDrawsegment(uint16_t aComponent)
Definition: altium_pcb.cpp:117
DRAWINGS & Drawings()
Definition: board.h:236
ALTIUM_TEXT_TYPE fonttype
void SetPolyPoints(const std::vector< wxPoint > &aPoints)
Definition: pcb_shape.cpp:1153
int32_t textprecision
wxPoint pos2
uint32_t diameter
void HelperDrawsegmentSetLocalCoord(PCB_SHAPE *aShape, uint16_t aComponent)
Definition: altium_pcb.cpp:144
PROGRESS_REPORTER * m_progressReporter
optional; may be nullptr
Definition: altium_pcb.h:202
wxPoint m_AuxOrigin
origin for plot exports
bool is_locked
TRACKS & Tracks()
Definition: board.h:230
wxString name
void SetEnd(const wxPoint &aEnd)
Definition: pcb_shape.h:137
void SetArcStart(const wxPoint &aArcStartPoint)
Initialize the start arc point.
Definition: pcb_shape.h:183
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:747
bool GetFilledPolysUseThickness() const
Definition: zone.h:689
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).