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"
27 #include "plugins/altium/altium_parser.h"
28 #include <plugins/altium/altium_parser_utils.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 
43 #include <board_stackup_manager/stackup_predefined_prms.h>
44 
45 #include <compoundfilereader.h>
46 #include <convert_basic_shapes_to_polygon.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 
ParseAltiumPcb(BOARD * aBoard,const wxString & aFileName,PROGRESS_REPORTER * aProgressReporter,const std::map<ALTIUM_PCB_DIR,std::string> & aFileMapping)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 
IsAltiumLayerCopper(ALTIUM_LAYER aLayer)105 bool IsAltiumLayerCopper( ALTIUM_LAYER aLayer )
106 {
107     return aLayer >= ALTIUM_LAYER::TOP_LAYER && aLayer <= ALTIUM_LAYER::BOTTOM_LAYER;
108 }
109 
110 
IsAltiumLayerAPlane(ALTIUM_LAYER aLayer)111 bool IsAltiumLayerAPlane( ALTIUM_LAYER aLayer )
112 {
113     return aLayer >= ALTIUM_LAYER::INTERNAL_PLANE_1 && aLayer <= ALTIUM_LAYER::INTERNAL_PLANE_16;
114 }
115 
116 
HelperCreateAndAddShape(uint16_t aComponent)117 PCB_SHAPE* ALTIUM_PCB::HelperCreateAndAddShape( uint16_t aComponent )
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 
HelperShapeSetLocalCoord(PCB_SHAPE * aShape,uint16_t aComponent)144 void ALTIUM_PCB::HelperShapeSetLocalCoord( PCB_SHAPE* aShape, uint16_t aComponent )
145 {
146     if( aComponent != ALTIUM_COMPONENT_NONE )
147     {
148         FP_SHAPE* fpShape = dynamic_cast<FP_SHAPE*>( aShape );
149 
150         if( fpShape )
151         {
152             fpShape->SetLocalCoord();
153 
154             // TODO: SetLocalCoord() does not update the polygon shape!
155             // This workaround converts the poly shape into the local coordinates
156             SHAPE_POLY_SET& polyShape = fpShape->GetPolyShape();
157             if( !polyShape.IsEmpty() )
158             {
159                 FOOTPRINT* fp = m_components.at( aComponent );
160 
161                 polyShape.Move( -fp->GetPosition() );
162                 polyShape.Rotate( -fp->GetOrientationRadians() );
163             }
164         }
165     }
166 }
167 
168 
HelperShapeLineChainFromAltiumVertices(SHAPE_LINE_CHAIN & aLine,const std::vector<ALTIUM_VERTICE> & aVertices)169 void HelperShapeLineChainFromAltiumVertices( SHAPE_LINE_CHAIN& aLine,
170                                              const std::vector<ALTIUM_VERTICE>& aVertices )
171 {
172     for( const ALTIUM_VERTICE& vertex : aVertices )
173     {
174         if( vertex.isRound )
175         {
176             double angle = NormalizeAngleDegreesPos( vertex.endangle - vertex.startangle );
177 
178             double  startradiant   = DEG2RAD( vertex.startangle );
179             double  endradiant     = DEG2RAD( vertex.endangle );
180             wxPoint arcStartOffset = wxPoint( KiROUND( std::cos( startradiant ) * vertex.radius ),
181                                              -KiROUND( std::sin( startradiant ) * vertex.radius ) );
182 
183             wxPoint arcEndOffset = wxPoint( KiROUND( std::cos( endradiant ) * vertex.radius ),
184                                            -KiROUND( std::sin( endradiant ) * vertex.radius ) );
185 
186             wxPoint arcStart = vertex.center + arcStartOffset;
187             wxPoint arcEnd   = vertex.center + arcEndOffset;
188 
189             if( GetLineLength( arcStart, vertex.position )
190                     < GetLineLength( arcEnd, vertex.position ) )
191             {
192                 aLine.Append( SHAPE_ARC( vertex.center, arcStart, -angle ) );
193             }
194             else
195             {
196                 aLine.Append( SHAPE_ARC( vertex.center, arcEnd, angle ) );
197             }
198         }
199         else
200         {
201             aLine.Append( vertex.position );
202         }
203     }
204 
205     aLine.SetClosed( true );
206 }
207 
208 
GetKicadLayer(ALTIUM_LAYER aAltiumLayer) const209 PCB_LAYER_ID ALTIUM_PCB::GetKicadLayer( ALTIUM_LAYER aAltiumLayer ) const
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     {
219     case ALTIUM_LAYER::UNKNOWN:           return UNDEFINED_LAYER;
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;
255     case ALTIUM_LAYER::BOTTOM_OVERLAY:    return B_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 
261     case ALTIUM_LAYER::INTERNAL_PLANE_1:  return UNDEFINED_LAYER;
262     case ALTIUM_LAYER::INTERNAL_PLANE_2:  return UNDEFINED_LAYER;
263     case ALTIUM_LAYER::INTERNAL_PLANE_3:  return UNDEFINED_LAYER;
264     case ALTIUM_LAYER::INTERNAL_PLANE_4:  return UNDEFINED_LAYER;
265     case ALTIUM_LAYER::INTERNAL_PLANE_5:  return UNDEFINED_LAYER;
266     case ALTIUM_LAYER::INTERNAL_PLANE_6:  return UNDEFINED_LAYER;
267     case ALTIUM_LAYER::INTERNAL_PLANE_7:  return UNDEFINED_LAYER;
268     case ALTIUM_LAYER::INTERNAL_PLANE_8:  return UNDEFINED_LAYER;
269     case ALTIUM_LAYER::INTERNAL_PLANE_9:  return UNDEFINED_LAYER;
270     case ALTIUM_LAYER::INTERNAL_PLANE_10: return UNDEFINED_LAYER;
271     case ALTIUM_LAYER::INTERNAL_PLANE_11: return UNDEFINED_LAYER;
272     case ALTIUM_LAYER::INTERNAL_PLANE_12: return UNDEFINED_LAYER;
273     case ALTIUM_LAYER::INTERNAL_PLANE_13: return UNDEFINED_LAYER;
274     case ALTIUM_LAYER::INTERNAL_PLANE_14: return UNDEFINED_LAYER;
275     case ALTIUM_LAYER::INTERNAL_PLANE_15: return UNDEFINED_LAYER;
276     case ALTIUM_LAYER::INTERNAL_PLANE_16: return UNDEFINED_LAYER;
277 
278     case ALTIUM_LAYER::DRILL_GUIDE:       return Dwgs_User;
279     case ALTIUM_LAYER::KEEP_OUT_LAYER:    return Margin;
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;
290     case ALTIUM_LAYER::MECHANICAL_10:     return Dwgs_User;
291     case ALTIUM_LAYER::MECHANICAL_11:     return Eco2_User; //Eco1 is used for unknown elements
292     case ALTIUM_LAYER::MECHANICAL_12:     return F_Fab;
293     case ALTIUM_LAYER::MECHANICAL_13:     return B_Fab; // Don't use courtyard layers for other purposes
294     case ALTIUM_LAYER::MECHANICAL_14:     return UNDEFINED_LAYER;
295     case ALTIUM_LAYER::MECHANICAL_15:     return UNDEFINED_LAYER;
296     case ALTIUM_LAYER::MECHANICAL_16:     return UNDEFINED_LAYER;
297 
298     case ALTIUM_LAYER::DRILL_DRAWING:     return Dwgs_User;
299     case ALTIUM_LAYER::MULTI_LAYER:       return UNDEFINED_LAYER;
300     case ALTIUM_LAYER::CONNECTIONS:       return UNDEFINED_LAYER;
301     case ALTIUM_LAYER::BACKGROUND:        return UNDEFINED_LAYER;
302     case ALTIUM_LAYER::DRC_ERROR_MARKERS: return UNDEFINED_LAYER;
303     case ALTIUM_LAYER::SELECTIONS:        return UNDEFINED_LAYER;
304     case ALTIUM_LAYER::VISIBLE_GRID_1:    return UNDEFINED_LAYER;
305     case ALTIUM_LAYER::VISIBLE_GRID_2:    return UNDEFINED_LAYER;
306     case ALTIUM_LAYER::PAD_HOLES:         return UNDEFINED_LAYER;
307     case ALTIUM_LAYER::VIA_HOLES:         return UNDEFINED_LAYER;
308 
309     default:                              return UNDEFINED_LAYER;
310     }
311 }
312 
313 
ALTIUM_PCB(BOARD * aBoard,PROGRESS_REPORTER * aProgressReporter)314 ALTIUM_PCB::ALTIUM_PCB( BOARD* aBoard, PROGRESS_REPORTER* aProgressReporter )
315 {
316     m_board              = aBoard;
317     m_progressReporter = aProgressReporter;
318     m_doneCount = 0;
319     m_lastProgressCount = 0;
320     m_totalCount = 0;
321     m_num_nets           = 0;
322     m_highest_pour_index = 0;
323 }
324 
~ALTIUM_PCB()325 ALTIUM_PCB::~ALTIUM_PCB()
326 {
327 }
328 
checkpoint()329 void ALTIUM_PCB::checkpoint()
330 {
331     const unsigned PROGRESS_DELTA = 250;
332 
333     if( m_progressReporter )
334     {
335         if( ++m_doneCount > m_lastProgressCount + PROGRESS_DELTA )
336         {
337             m_progressReporter->SetCurrentProgress( ( (double) m_doneCount )
338                                                     / std::max( 1U, m_totalCount ) );
339 
340             if( !m_progressReporter->KeepRefreshing() )
341                 THROW_IO_ERROR( ( "Open cancelled by user." ) );
342 
343             m_lastProgressCount = m_doneCount;
344         }
345     }
346 }
347 
Parse(const CFB::CompoundFileReader & aReader,const std::map<ALTIUM_PCB_DIR,std::string> & aFileMapping)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 = {
353         { true, ALTIUM_PCB_DIR::FILE_HEADER,
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                 } },
363         { true, ALTIUM_PCB_DIR::COMPONENTS6,
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                 } },
374         { true, ALTIUM_PCB_DIR::COMPONENTBODIES6,
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                 } },
394         { true, ALTIUM_PCB_DIR::DIMENSIONS6,
395                 [this]( auto aReader, auto fileHeader )
396                 {
397                     this->ParseDimensions6Data( aReader, fileHeader );
398                 } },
399         { true, ALTIUM_PCB_DIR::POLYGONS6,
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         { false, ALTIUM_PCB_DIR::WIDESTRINGS6,
425                 [this]( auto aReader, auto fileHeader )
426                 {
427                     this->ParseWideStrings6Data( aReader, fileHeader );
428                 } },
429         { true, ALTIUM_PCB_DIR::TEXTS6,
430                 [this]( auto aReader, auto fileHeader )
431                 {
432                     this->ParseTexts6Data( aReader, fileHeader );
433                 } },
434         { true, ALTIUM_PCB_DIR::FILLS6,
435                 [this]( auto aReader, auto fileHeader )
436                 {
437                     this->ParseFills6Data( aReader, fileHeader );
438                 } },
439         { false, ALTIUM_PCB_DIR::BOARDREGIONS,
440                 [this]( auto aReader, auto fileHeader )
441                 {
442                     this->ParseBoardRegionsData( aReader, fileHeader );
443                 } },
444         { true, ALTIUM_PCB_DIR::SHAPEBASEDREGIONS6,
445                 [this]( auto aReader, auto fileHeader )
446                 {
447                     this->ParseShapeBasedRegions6Data( aReader, fileHeader );
448                 } },
449         { true, ALTIUM_PCB_DIR::REGIONS6,
450                 [this]( auto aReader, auto fileHeader )
451                 {
452                     this->ParseRegions6Data( aReader, fileHeader );
453                 } }
454     };
455 
456     if( m_progressReporter != nullptr )
457     {
458         // Count number of records we will read for the progress reporter
459         for( const std::tuple<bool, ALTIUM_PCB_DIR, PARSE_FUNCTION_POINTER_fp>& cur : parserOrder )
460         {
461             bool                      isRequired;
462             ALTIUM_PCB_DIR            directory;
463             PARSE_FUNCTION_POINTER_fp fp;
464             std::tie( isRequired, directory, fp ) = cur;
465 
466             if( directory == ALTIUM_PCB_DIR::FILE_HEADER )
467             {
468                 continue;
469             }
470 
471             const auto& mappedDirectory = aFileMapping.find( directory );
472             if( mappedDirectory == aFileMapping.end() )
473             {
474                 continue;
475             }
476 
477             std::string mappedFile = mappedDirectory->second + "Header";
478 
479             const CFB::COMPOUND_FILE_ENTRY* file = FindStream( aReader, mappedFile.c_str() );
480             if( file == nullptr )
481             {
482                 continue;
483             }
484 
485             ALTIUM_PARSER reader( aReader, file );
486             uint32_t      numOfRecords = reader.Read<uint32_t>();
487 
488             if( reader.HasParsingError() )
489             {
490                 wxLogError( _( "'%s' was not parsed correctly." ), mappedFile );
491                 continue;
492             }
493 
494             m_totalCount += numOfRecords;
495 
496             if( reader.GetRemainingBytes() != 0 )
497             {
498                 wxLogError( _( "'%s' was not fully parsed." ), mappedFile );
499                 continue;
500             }
501         }
502     }
503 
504     // Parse data in specified order
505     for( const std::tuple<bool, ALTIUM_PCB_DIR, PARSE_FUNCTION_POINTER_fp>& cur : parserOrder )
506     {
507         bool                      isRequired;
508         ALTIUM_PCB_DIR            directory;
509         PARSE_FUNCTION_POINTER_fp fp;
510         std::tie( isRequired, directory, fp ) = cur;
511 
512         const auto& mappedDirectory = aFileMapping.find( directory );
513 
514         if( mappedDirectory == aFileMapping.end() )
515         {
516             wxASSERT_MSG( !isRequired, wxString::Format( "Altium Directory of kind %d was expected, "
517                                                          "but no mapping is present in the code",
518                                                          directory ) );
519             continue;
520         }
521 
522         std::string mappedFile = mappedDirectory->second;
523 
524         if( directory != ALTIUM_PCB_DIR::FILE_HEADER )
525             mappedFile += "Data";
526 
527         const CFB::COMPOUND_FILE_ENTRY* file = FindStream( aReader, mappedFile.c_str() );
528 
529         if( file != nullptr )
530             fp( aReader, file );
531         else if( isRequired )
532             wxLogError( _( "File not found: '%s'." ), mappedFile );
533     }
534 
535     // fixup zone priorities since Altium stores them in the opposite order
536     for( ZONE* zone : m_polygons )
537     {
538         if( !zone )
539             continue;
540 
541         // Altium "fills" - not poured in Altium
542         if( zone->GetPriority() == 1000 )
543         {
544             // Unlikely, but you never know
545             if( m_highest_pour_index >= 1000 )
546                 zone->SetPriority( m_highest_pour_index + 1 );
547 
548             continue;
549         }
550 
551         int priority = m_highest_pour_index - zone->GetPriority();
552 
553         zone->SetPriority( priority >= 0 ? priority : 0 );
554     }
555 
556     // change priority of outer zone to zero
557     for( std::pair<const ALTIUM_LAYER, ZONE*>& zone : m_outer_plane )
558         zone.second->SetPriority( 0 );
559 
560     // Altium doesn't appear to store either the dimension value nor the dimensioned object in
561     // the dimension record.  (Yes, there is a REFERENCE0OBJECTID, but it doesn't point to the
562     // dimensioned object.)  We attempt to plug this gap by finding a colocated arc or circle
563     // and using its radius.  If there are more than one such arcs/circles, well, :shrug:.
564     for( PCB_DIMENSION_BASE* dim : m_radialDimensions )
565     {
566         int radius = 0;
567 
568         for( BOARD_ITEM* item : m_board->Drawings() )
569         {
570             if( item->Type() != PCB_SHAPE_T )
571                 continue;
572 
573             PCB_SHAPE* shape = static_cast<PCB_SHAPE*>( item );
574 
575             if( shape->GetShape() != SHAPE_T::ARC && shape->GetShape() != SHAPE_T::CIRCLE )
576                 continue;
577 
578             if( shape->GetPosition() == dim->GetPosition() )
579             {
580                 radius = shape->GetRadius();
581                 break;
582             }
583         }
584 
585         if( radius == 0 )
586         {
587             for( PCB_TRACK* track : m_board->Tracks() )
588             {
589                 if( track->Type() != PCB_ARC_T )
590                     continue;
591 
592                 PCB_ARC* arc = static_cast<PCB_ARC*>( track );
593 
594                 if( arc->GetCenter() == dim->GetPosition() )
595                 {
596                     radius = arc->GetRadius();
597                     break;
598                 }
599             }
600         }
601 
602         // Force a measured value, calculate the value text, and then stick it into the override
603         // text (since leaders don't have calculated text).
604         dim->SetMeasuredValue( radius );
605         dim->SetText( dim->GetPrefix() + dim->GetValueText() + dim->GetSuffix() );
606         dim->SetPrefix( wxEmptyString );
607         dim->SetSuffix( wxEmptyString );
608 
609         // Move the leader line start to the radius point
610         VECTOR2I radialLine = dim->GetEnd() - dim->GetStart();
611         radialLine = radialLine.Resize( radius );
612         dim->SetStart( dim->GetStart() + (wxPoint) radialLine );
613     }
614 
615     // center board
616     EDA_RECT bbbox = m_board->GetBoardEdgesBoundingBox();
617 
618     int w = m_board->GetPageSettings().GetWidthIU();
619     int h = m_board->GetPageSettings().GetHeightIU();
620 
621     int desired_x = ( w - bbbox.GetWidth() ) / 2;
622     int desired_y = ( h - bbbox.GetHeight() ) / 2;
623 
624     wxPoint movementVector( desired_x - bbbox.GetX(), desired_y - bbbox.GetY() );
625     m_board->Move( movementVector );
626 
627     BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings();
628     bds.SetAuxOrigin( bds.GetAuxOrigin() + movementVector );
629     bds.SetGridOrigin( bds.GetGridOrigin() + movementVector );
630 
631     m_board->SetModified();
632 }
633 
GetNetCode(uint16_t aId) const634 int ALTIUM_PCB::GetNetCode( uint16_t aId ) const
635 {
636     if( aId == ALTIUM_NET_UNCONNECTED )
637     {
638         return NETINFO_LIST::UNCONNECTED;
639     }
640     else if( m_num_nets < aId )
641     {
642         THROW_IO_ERROR( wxString::Format(
643                 "Netcode with id %d does not exist. Only %d nets are known", aId, m_num_nets ) );
644     }
645     else
646     {
647         return aId + 1;
648     }
649 }
650 
GetRule(ALTIUM_RULE_KIND aKind,const wxString & aName) const651 const ARULE6* ALTIUM_PCB::GetRule( ALTIUM_RULE_KIND aKind, const wxString& aName ) const
652 {
653     const auto rules = m_rules.find( aKind );
654     if( rules == m_rules.end() )
655     {
656         return nullptr;
657     }
658     for( const ARULE6& rule : rules->second )
659     {
660         if( rule.name == aName )
661         {
662             return &rule;
663         }
664     }
665     return nullptr;
666 }
667 
GetRuleDefault(ALTIUM_RULE_KIND aKind) const668 const ARULE6* ALTIUM_PCB::GetRuleDefault( ALTIUM_RULE_KIND aKind ) const
669 {
670     const auto rules = m_rules.find( aKind );
671     if( rules == m_rules.end() )
672     {
673         return nullptr;
674     }
675     for( const ARULE6& rule : rules->second )
676     {
677         if( rule.scope1expr == "All" && rule.scope2expr == "All" )
678         {
679             return &rule;
680         }
681     }
682     return nullptr;
683 }
684 
ParseFileHeader(const CFB::CompoundFileReader & aReader,const CFB::COMPOUND_FILE_ENTRY * aEntry)685 void ALTIUM_PCB::ParseFileHeader( const CFB::CompoundFileReader& aReader,
686                                   const CFB::COMPOUND_FILE_ENTRY* aEntry )
687 {
688     ALTIUM_PARSER reader( aReader, aEntry );
689 
690     reader.ReadAndSetSubrecordLength();
691     wxString header = reader.ReadWxString();
692 
693     //std::cout << "HEADER: " << header << std::endl;  // tells me: PCB 5.0 Binary File
694 
695     //reader.SkipSubrecord();
696 
697     // TODO: does not seem to work all the time at the moment
698     //if( reader.GetRemainingBytes() != 0 )
699     //{
700     //    THROW_IO_ERROR( "FileHeader stream is not fully parsed" );
701     //}
702 }
703 
ParseBoard6Data(const CFB::CompoundFileReader & aReader,const CFB::COMPOUND_FILE_ENTRY * aEntry)704 void ALTIUM_PCB::ParseBoard6Data( const CFB::CompoundFileReader& aReader,
705                                   const CFB::COMPOUND_FILE_ENTRY* aEntry )
706 {
707     if( m_progressReporter )
708         m_progressReporter->Report( "Loading board data..." );
709 
710     ALTIUM_PARSER reader( aReader, aEntry );
711 
712     checkpoint();
713     ABOARD6 elem( reader );
714 
715     if( reader.GetRemainingBytes() != 0 )
716     {
717         THROW_IO_ERROR( "Board6 stream is not fully parsed" );
718     }
719 
720     m_board->GetDesignSettings().SetAuxOrigin( elem.sheetpos );
721     m_board->GetDesignSettings().SetGridOrigin( elem.sheetpos );
722 
723     // read layercount from stackup, because LAYERSETSCOUNT is not always correct?!
724     size_t layercount = 0;
725     for( size_t i                                = static_cast<size_t>( ALTIUM_LAYER::TOP_LAYER );
726             i < elem.stackup.size() && i != 0; i = elem.stackup[i - 1].nextId, layercount++ )
727         ;
728     size_t kicadLayercount = ( layercount % 2 == 0 ) ? layercount : layercount + 1;
729     m_board->SetCopperLayerCount( kicadLayercount );
730 
731     BOARD_DESIGN_SETTINGS& designSettings = m_board->GetDesignSettings();
732     BOARD_STACKUP&         stackup        = designSettings.GetStackupDescriptor();
733 
734     // create board stackup
735     stackup.RemoveAll(); // Just to be sure
736     stackup.BuildDefaultStackupList( &designSettings, layercount );
737 
738     auto it = stackup.GetList().begin();
739     // find first copper layer
740     for( ; it != stackup.GetList().end() && ( *it )->GetType() != BS_ITEM_TYPE_COPPER; ++it )
741         ;
742 
743     auto curLayer = static_cast<int>( F_Cu );
744     for( size_t altiumLayerId = static_cast<size_t>( ALTIUM_LAYER::TOP_LAYER );
745             altiumLayerId < elem.stackup.size() && altiumLayerId != 0;
746             altiumLayerId = elem.stackup[altiumLayerId - 1].nextId )
747     {
748         // array starts with 0, but stackup with 1
749         ABOARD6_LAYER_STACKUP& layer = elem.stackup.at( altiumLayerId - 1 );
750 
751         // handle unused layer in case of odd layercount
752         if( layer.nextId == 0 && layercount != kicadLayercount )
753         {
754             m_board->SetLayerName( ( *it )->GetBrdLayerId(), "[unused]" );
755 
756             if( ( *it )->GetType() != BS_ITEM_TYPE_COPPER )
757             {
758                 THROW_IO_ERROR( "Board6 stream, unexpected item while parsing stackup" );
759             }
760             ( *it )->SetThickness( 0 );
761 
762             ++it;
763             if( ( *it )->GetType() != BS_ITEM_TYPE_DIELECTRIC )
764             {
765                 THROW_IO_ERROR( "Board6 stream, unexpected item while parsing stackup" );
766             }
767             ( *it )->SetThickness( 0, 0 );
768             ( *it )->SetThicknessLocked( true, 0 );
769             ++it;
770         }
771 
772         m_layermap.insert( { static_cast<ALTIUM_LAYER>( altiumLayerId ),
773                 static_cast<PCB_LAYER_ID>( curLayer++ ) } );
774 
775         if( ( *it )->GetType() != BS_ITEM_TYPE_COPPER )
776             THROW_IO_ERROR( "Board6 stream, unexpected item while parsing stackup" );
777 
778         ( *it )->SetThickness( layer.copperthick );
779 
780         ALTIUM_LAYER alayer = static_cast<ALTIUM_LAYER>( altiumLayerId );
781         PCB_LAYER_ID klayer = ( *it )->GetBrdLayerId();
782 
783         m_board->SetLayerName( klayer, layer.name );
784 
785         if( layer.copperthick == 0 )
786         {
787             m_board->SetLayerType( klayer, LAYER_T::LT_JUMPER ); // used for things like wirebonding
788         }
789         else if( IsAltiumLayerAPlane( alayer ) )
790         {
791             m_board->SetLayerType( klayer, LAYER_T::LT_POWER );
792         }
793 
794         if( klayer == B_Cu )
795         {
796             if( layer.nextId != 0 )
797                 THROW_IO_ERROR( "Board6 stream, unexpected id while parsing last stackup layer" );
798 
799             // overwrite entry from internal -> bottom
800             m_layermap[alayer] = B_Cu;
801             break;
802         }
803 
804         ++it;
805 
806         if( ( *it )->GetType() != BS_ITEM_TYPE_DIELECTRIC )
807             THROW_IO_ERROR( "Board6 stream, unexpected item while parsing stackup" );
808 
809         ( *it )->SetThickness( layer.dielectricthick, 0 );
810         ( *it )->SetMaterial( layer.dielectricmaterial.empty() ?
811                                       NotSpecifiedPrm() :
812                                       wxString( layer.dielectricmaterial ) );
813         ( *it )->SetEpsilonR( layer.dielectricconst, 0 );
814 
815         ++it;
816     }
817 
818     // Set name of all non-cu layers
819     for( size_t altiumLayerId = static_cast<size_t>( ALTIUM_LAYER::TOP_OVERLAY );
820          altiumLayerId <= static_cast<size_t>( ALTIUM_LAYER::BOTTOM_SOLDER ); altiumLayerId++ )
821     {
822         // array starts with 0, but stackup with 1
823         ABOARD6_LAYER_STACKUP& layer = elem.stackup.at( altiumLayerId - 1 );
824 
825         ALTIUM_LAYER alayer = static_cast<ALTIUM_LAYER>( altiumLayerId );
826         PCB_LAYER_ID klayer = GetKicadLayer( alayer );
827 
828         m_board->SetLayerName( klayer, layer.name );
829     }
830 
831     for( size_t altiumLayerId = static_cast<size_t>( ALTIUM_LAYER::MECHANICAL_1 );
832          altiumLayerId <= static_cast<size_t>( ALTIUM_LAYER::MECHANICAL_16 ); altiumLayerId++ )
833     {
834         // array starts with 0, but stackup with 1
835         ABOARD6_LAYER_STACKUP& layer = elem.stackup.at( altiumLayerId - 1 );
836 
837         ALTIUM_LAYER alayer = static_cast<ALTIUM_LAYER>( altiumLayerId );
838         PCB_LAYER_ID klayer = GetKicadLayer( alayer );
839 
840         m_board->SetLayerName( klayer, layer.name );
841     }
842 
843     HelperCreateBoardOutline( elem.board_vertices );
844 }
845 
HelperCreateBoardOutline(const std::vector<ALTIUM_VERTICE> & aVertices)846 void ALTIUM_PCB::HelperCreateBoardOutline( const std::vector<ALTIUM_VERTICE>& aVertices )
847 {
848     if( !aVertices.empty() )
849     {
850         const ALTIUM_VERTICE* last = &aVertices.at( 0 );
851         for( size_t i = 0; i < aVertices.size(); i++ )
852         {
853             const ALTIUM_VERTICE* cur = &aVertices.at( ( i + 1 ) % aVertices.size() );
854 
855             PCB_SHAPE* shape = new PCB_SHAPE( m_board );
856             m_board->Add( shape, ADD_MODE::APPEND );
857 
858             shape->SetWidth( m_board->GetDesignSettings().GetLineThickness( Edge_Cuts ) );
859             shape->SetLayer( Edge_Cuts );
860 
861             if( !last->isRound && !cur->isRound )
862             {
863                 shape->SetShape( SHAPE_T::SEGMENT );
864                 shape->SetStart( last->position );
865                 shape->SetEnd( cur->position );
866             }
867             else if( cur->isRound )
868             {
869                 shape->SetShape( SHAPE_T::ARC );
870 
871                 double  includedAngle  = cur->endangle - cur->startangle;
872                 double  startradiant   = DEG2RAD( cur->startangle );
873                 wxPoint arcStartOffset = wxPoint( KiROUND( std::cos( startradiant ) * cur->radius ),
874                                                  -KiROUND( std::sin( startradiant ) * cur->radius ) );
875                 wxPoint arcStart       = cur->center + arcStartOffset;
876 
877                 shape->SetCenter( cur->center );
878                 shape->SetStart( arcStart );
879                 shape->SetArcAngleAndEnd( -NormalizeAngleDegreesPos( includedAngle ) * 10.0, true );
880 
881                 if( !last->isRound )
882                 {
883                     double  endradiant   = DEG2RAD( cur->endangle );
884                     wxPoint arcEndOffset = wxPoint( KiROUND( std::cos( endradiant ) * cur->radius ),
885                                                    -KiROUND( std::sin( endradiant ) * cur->radius ) );
886                     wxPoint arcEnd       = cur->center + arcEndOffset;
887 
888                     PCB_SHAPE* shape2 = new PCB_SHAPE( m_board, SHAPE_T::SEGMENT );
889                     m_board->Add( shape2, ADD_MODE::APPEND );
890                     shape2->SetWidth( m_board->GetDesignSettings().GetLineThickness( Edge_Cuts ) );
891                     shape2->SetLayer( Edge_Cuts );
892                     shape2->SetStart( last->position );
893 
894                     // TODO: this is more of a hack than the real solution
895                     double lineLengthStart = GetLineLength( last->position, arcStart );
896                     double lineLengthEnd   = GetLineLength( last->position, arcEnd );
897 
898                     if( lineLengthStart > lineLengthEnd )
899                         shape2->SetEnd( cur->center + arcEndOffset );
900                     else
901                         shape2->SetEnd( cur->center + arcStartOffset );
902                 }
903             }
904             last = cur;
905         }
906     }
907 }
908 
ParseClasses6Data(const CFB::CompoundFileReader & aReader,const CFB::COMPOUND_FILE_ENTRY * aEntry)909 void ALTIUM_PCB::ParseClasses6Data( const CFB::CompoundFileReader& aReader,
910                                     const CFB::COMPOUND_FILE_ENTRY* aEntry )
911 {
912     if( m_progressReporter )
913         m_progressReporter->Report( "Loading netclasses..." );
914 
915     ALTIUM_PARSER reader( aReader, aEntry );
916 
917     while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
918     {
919         checkpoint();
920         ACLASS6 elem( reader );
921         if( elem.kind == ALTIUM_CLASS_KIND::NET_CLASS )
922         {
923             NETCLASSPTR nc = std::make_shared<NETCLASS>( elem.name );
924 
925             for( const auto& name : elem.names )
926             {
927                 // TODO: it seems it can happen that we have names not attached to any net.
928                 nc->Add( name );
929             }
930 
931             if( !m_board->GetDesignSettings().GetNetClasses().Add( nc ) )
932             {
933                 // Name conflict, this is likely a bad board file.
934                 // unique_ptr will delete nc on this code path
935                 THROW_IO_ERROR( wxString::Format( _( "Duplicate netclass name '%s'." ), elem.name ) );
936             }
937         }
938     }
939 
940     if( reader.GetRemainingBytes() != 0 )
941     {
942         THROW_IO_ERROR( "Classes6 stream is not fully parsed" );
943     }
944 
945     m_board->m_LegacyNetclassesLoaded = true;
946 }
947 
ParseComponents6Data(const CFB::CompoundFileReader & aReader,const CFB::COMPOUND_FILE_ENTRY * aEntry)948 void ALTIUM_PCB::ParseComponents6Data( const CFB::CompoundFileReader& aReader,
949                                        const CFB::COMPOUND_FILE_ENTRY* aEntry )
950 {
951     if( m_progressReporter )
952         m_progressReporter->Report( "Loading components..." );
953 
954     ALTIUM_PARSER reader( aReader, aEntry );
955 
956     uint16_t componentId = 0;
957     while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
958     {
959         checkpoint();
960         ACOMPONENT6 elem( reader );
961 
962         FOOTPRINT* footprint = new FOOTPRINT( m_board );
963         m_board->Add( footprint, ADD_MODE::APPEND );
964         m_components.emplace_back( footprint );
965 
966         LIB_ID fpID = AltiumToKiCadLibID( elem.sourcefootprintlibrary, elem.pattern );
967 
968         footprint->SetFPID( fpID );
969 
970         footprint->SetPosition( elem.position );
971         footprint->SetOrientationDegrees( elem.rotation );
972 
973         // KiCad netlisting requires parts to have non-digit + digit annotation.
974         // If the reference begins with a number, we prepend 'UNK' (unknown) for the source designator
975         wxString reference = elem.sourcedesignator;
976         if( reference.find_first_not_of( "0123456789" ) == wxString::npos )
977             reference.Prepend( "UNK" );
978         footprint->SetReference( reference );
979 
980         footprint->SetLocked( elem.locked );
981         footprint->Reference().SetVisible( elem.nameon );
982         footprint->Value().SetVisible( elem.commenton );
983         footprint->SetLayer( elem.layer == ALTIUM_LAYER::TOP_LAYER ? F_Cu : B_Cu );
984 
985         componentId++;
986     }
987 
988     if( reader.GetRemainingBytes() != 0 )
989     {
990         THROW_IO_ERROR( "Components6 stream is not fully parsed" );
991     }
992 }
993 
994 
ParseComponentsBodies6Data(const CFB::CompoundFileReader & aReader,const CFB::COMPOUND_FILE_ENTRY * aEntry)995 void ALTIUM_PCB::ParseComponentsBodies6Data( const CFB::CompoundFileReader& aReader,
996                                              const CFB::COMPOUND_FILE_ENTRY* aEntry )
997 {
998     if( m_progressReporter )
999         m_progressReporter->Report( "Loading component 3D models..." );
1000 
1001     ALTIUM_PARSER reader( aReader, aEntry );
1002 
1003     while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
1004     {
1005         checkpoint();
1006         ACOMPONENTBODY6 elem( reader ); // TODO: implement
1007 
1008         if( elem.component == ALTIUM_COMPONENT_NONE )
1009             continue; // TODO: we do not support components for the board yet
1010 
1011         if( m_components.size() <= elem.component )
1012         {
1013             THROW_IO_ERROR( wxString::Format(
1014                     "ComponentsBodies6 stream tries to access component id %d of %d existing components",
1015                     elem.component, m_components.size() ) );
1016         }
1017 
1018         if( !elem.modelIsEmbedded )
1019             continue;
1020 
1021         auto modelTuple = m_models.find( elem.modelId );
1022 
1023         if( modelTuple == m_models.end() )
1024         {
1025             THROW_IO_ERROR( wxString::Format(
1026                     "ComponentsBodies6 stream tries to access model id %s which does not exist",
1027                     elem.modelId ) );
1028         }
1029 
1030         FOOTPRINT*     footprint  = m_components.at( elem.component );
1031         const wxPoint& fpPosition = footprint->GetPosition();
1032 
1033         FP_3DMODEL modelSettings;
1034 
1035         modelSettings.m_Filename = modelTuple->second;
1036 
1037         modelSettings.m_Offset.x = Iu2Millimeter((int) elem.modelPosition.x - fpPosition.x );
1038         modelSettings.m_Offset.y = -Iu2Millimeter((int) elem.modelPosition.y - fpPosition.y );
1039         modelSettings.m_Offset.z = Iu2Millimeter( (int) elem.modelPosition.z );
1040 
1041         double orientation = footprint->GetOrientation();
1042 
1043         if( footprint->IsFlipped() )
1044         {
1045             modelSettings.m_Offset.y = -modelSettings.m_Offset.y;
1046             orientation              = -orientation;
1047         }
1048 
1049         RotatePoint( &modelSettings.m_Offset.x, &modelSettings.m_Offset.y, orientation );
1050 
1051         modelSettings.m_Rotation.x = NormalizeAngleDegrees( -elem.modelRotation.x, -180, 180 );
1052         modelSettings.m_Rotation.y = NormalizeAngleDegrees( -elem.modelRotation.y, -180, 180 );
1053         modelSettings.m_Rotation.z = NormalizeAngleDegrees( -elem.modelRotation.z
1054                                                                 + elem.rotation
1055                                                                 + orientation / 10, -180, 180 );
1056         modelSettings.m_Opacity = elem.bodyOpacity;
1057 
1058         footprint->Models().push_back( modelSettings );
1059     }
1060 
1061     if( reader.GetRemainingBytes() != 0 )
1062         THROW_IO_ERROR( "ComponentsBodies6 stream is not fully parsed" );
1063 }
1064 
1065 
HelperParseDimensions6Linear(const ADIMENSION6 & aElem)1066 void ALTIUM_PCB::HelperParseDimensions6Linear( const ADIMENSION6& aElem )
1067 {
1068     if( aElem.referencePoint.size() != 2 )
1069         THROW_IO_ERROR( "Incorrect number of reference points for linear dimension object" );
1070 
1071     PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
1072 
1073     if( klayer == UNDEFINED_LAYER )
1074     {
1075         wxLogWarning( _( "Dimension found on an Altium layer (%d) with no KiCad equivalent. "
1076                          "It has been moved to KiCad layer Eco1_User." ),
1077                       aElem.layer );
1078         klayer = Eco1_User;
1079     }
1080 
1081     wxPoint referencePoint0 = aElem.referencePoint.at( 0 );
1082     wxPoint referencePoint1 = aElem.referencePoint.at( 1 );
1083 
1084     PCB_DIM_ALIGNED* dimension = new PCB_DIM_ALIGNED( m_board );
1085     m_board->Add( dimension, ADD_MODE::APPEND );
1086 
1087     dimension->SetPrecision( aElem.textprecision );
1088     dimension->SetLayer( klayer );
1089     dimension->SetStart( referencePoint0 );
1090 
1091     if( referencePoint0 != aElem.xy1 )
1092     {
1093         /**
1094          * Basically REFERENCE0POINT and REFERENCE1POINT are the two end points of the dimension.
1095          * XY1 is the position of the arrow above REFERENCE0POINT. those three points are not
1096          * necessarily in 90degree angle, but KiCad requires this to show the correct measurements.
1097          *
1098          * Therefore, we take the vector of REFERENCE0POINT -> XY1, calculate the normal, and
1099          * intersect it with REFERENCE1POINT pointing the same direction as REFERENCE0POINT -> XY1.
1100          * This should give us a valid measurement point where we can place the drawsegment.
1101          */
1102         wxPoint direction             = aElem.xy1 - referencePoint0;
1103         wxPoint directionNormalVector = wxPoint( -direction.y, direction.x );
1104         SEG     segm1( referencePoint0, referencePoint0 + directionNormalVector );
1105         SEG     segm2( referencePoint1, referencePoint1 + direction );
1106         wxPoint intersection( segm1.Intersect( segm2, true, true ).get() );
1107         dimension->SetEnd( intersection );
1108 
1109         int height = static_cast<int>( EuclideanNorm( direction ) );
1110 
1111         if( direction.x <= 0 && direction.y <= 0 ) // TODO: I suspect this is not always correct
1112             height = -height;
1113 
1114         dimension->SetHeight( height );
1115     }
1116     else
1117     {
1118         dimension->SetEnd( referencePoint1 );
1119     }
1120 
1121     dimension->SetLineThickness( aElem.linewidth );
1122 
1123     dimension->SetPrefix( aElem.textprefix );
1124 
1125     // Suffix normally holds the units
1126     dimension->SetUnitsFormat( aElem.textsuffix.IsEmpty() ? DIM_UNITS_FORMAT::NO_SUFFIX
1127                                                           : DIM_UNITS_FORMAT::BARE_SUFFIX );
1128 
1129     dimension->Text().SetTextThickness( aElem.textlinewidth );
1130     dimension->Text().SetTextSize( wxSize( aElem.textheight, aElem.textheight ) );
1131     dimension->Text().SetBold( aElem.textbold );
1132     dimension->Text().SetItalic( aElem.textitalic );
1133 
1134     switch( aElem.textunit )
1135     {
1136     case ALTIUM_UNIT::INCHES:
1137         dimension->SetUnits( EDA_UNITS::INCHES );
1138         break;
1139     case ALTIUM_UNIT::MILS:
1140         dimension->SetUnits( EDA_UNITS::MILS );
1141         break;
1142     case ALTIUM_UNIT::MILLIMETERS:
1143     case ALTIUM_UNIT::CENTIMETER:
1144         dimension->SetUnits( EDA_UNITS::MILLIMETRES );
1145         break;
1146     default:
1147         break;
1148     }
1149 }
1150 
1151 
HelperParseDimensions6Radial(const ADIMENSION6 & aElem)1152 void ALTIUM_PCB::HelperParseDimensions6Radial(const ADIMENSION6 &aElem)
1153 {
1154     if( aElem.referencePoint.size() < 2 )
1155         THROW_IO_ERROR( "Not enough reference points for radial dimension object" );
1156 
1157     PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
1158 
1159     if( klayer == UNDEFINED_LAYER )
1160     {
1161         wxLogWarning( _( "Dimension found on an Altium layer (%d) with no KiCad equivalent. "
1162                          "It has been moved to KiCad layer Eco1_User." ),
1163                       aElem.layer );
1164         klayer = Eco1_User;
1165     }
1166 
1167     wxPoint referencePoint0 = aElem.referencePoint.at( 0 );
1168     wxPoint referencePoint1 = aElem.referencePoint.at( 1 );
1169 
1170     //
1171     // We don't have radial dimensions yet so fake it with a leader:
1172 
1173     PCB_DIM_LEADER* dimension = new PCB_DIM_LEADER( m_board );
1174     m_board->Add( dimension, ADD_MODE::APPEND );
1175     m_radialDimensions.push_back( dimension );
1176 
1177     dimension->SetPrecision( aElem.textprecision );
1178     dimension->SetLayer( klayer );
1179     dimension->SetStart( referencePoint0 );
1180     dimension->SetEnd( aElem.xy1 );
1181     dimension->SetLineThickness( aElem.linewidth );
1182 
1183     dimension->SetPrefix( aElem.textprefix );
1184 
1185     // Suffix normally holds the units
1186     dimension->SetUnitsFormat( aElem.textsuffix.IsEmpty() ? DIM_UNITS_FORMAT::NO_SUFFIX
1187                                                           : DIM_UNITS_FORMAT::BARE_SUFFIX );
1188 
1189     switch( aElem.textunit )
1190     {
1191     case ALTIUM_UNIT::INCHES:
1192         dimension->SetUnits( EDA_UNITS::INCHES );
1193         break;
1194     case ALTIUM_UNIT::MILS:
1195         dimension->SetUnits( EDA_UNITS::MILS );
1196         break;
1197     case ALTIUM_UNIT::MILLIMETERS:
1198     case ALTIUM_UNIT::CENTIMETER:
1199         dimension->SetUnits( EDA_UNITS::MILLIMETRES );
1200         break;
1201     default:
1202         break;
1203     }
1204 
1205     if( aElem.textPoint.empty() )
1206     {
1207         wxLogError( "No text position present for leader dimension object" );
1208         return;
1209     }
1210 
1211     dimension->Text().SetPosition( aElem.textPoint.at( 0 ) );
1212     dimension->Text().SetTextThickness( aElem.textlinewidth );
1213     dimension->Text().SetTextSize( wxSize( aElem.textheight, aElem.textheight ) );
1214     dimension->Text().SetBold( aElem.textbold );
1215     dimension->Text().SetItalic( aElem.textitalic );
1216     dimension->Text().SetVertJustify( EDA_TEXT_VJUSTIFY_T::GR_TEXT_VJUSTIFY_BOTTOM );
1217     dimension->Text().SetHorizJustify( EDA_TEXT_HJUSTIFY_T::GR_TEXT_HJUSTIFY_LEFT );
1218 
1219     int yAdjust = dimension->Text().GetCenter().y - dimension->Text().GetPosition().y;
1220     dimension->Text().Move( wxPoint( 0, yAdjust + aElem.textgap ) );
1221     dimension->Text().SetVertJustify( EDA_TEXT_VJUSTIFY_T::GR_TEXT_VJUSTIFY_CENTER );
1222 }
1223 
1224 
HelperParseDimensions6Leader(const ADIMENSION6 & aElem)1225 void ALTIUM_PCB::HelperParseDimensions6Leader( const ADIMENSION6& aElem )
1226 {
1227     PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
1228 
1229     if( klayer == UNDEFINED_LAYER )
1230     {
1231         wxLogWarning( _( "Dimension found on an Altium layer (%d) with no KiCad equivalent. "
1232                          "It has been moved to KiCad layer Eco1_User." ),
1233                       aElem.layer );
1234         klayer = Eco1_User;
1235     }
1236 
1237     if( !aElem.referencePoint.empty() )
1238     {
1239         wxPoint referencePoint0 = aElem.referencePoint.at( 0 );
1240 
1241         // line
1242         wxPoint last = referencePoint0;
1243         for( size_t i = 1; i < aElem.referencePoint.size(); i++ )
1244         {
1245             PCB_SHAPE* shape = new PCB_SHAPE( m_board, SHAPE_T::SEGMENT );
1246             m_board->Add( shape, ADD_MODE::APPEND );
1247             shape->SetLayer( klayer );
1248             shape->SetWidth( aElem.linewidth );
1249             shape->SetStart( last );
1250             shape->SetEnd( aElem.referencePoint.at( i ) );
1251             last = aElem.referencePoint.at( i );
1252         }
1253 
1254         // arrow
1255         if( aElem.referencePoint.size() >= 2 )
1256         {
1257             wxPoint dirVec = aElem.referencePoint.at( 1 ) - referencePoint0;
1258             if( dirVec.x != 0 || dirVec.y != 0 )
1259             {
1260                 double  scaling = EuclideanNorm( dirVec ) / aElem.arrowsize;
1261                 wxPoint arrVec =
1262                         wxPoint( KiROUND( dirVec.x / scaling ), KiROUND( dirVec.y / scaling ) );
1263                 RotatePoint( &arrVec, 200. );
1264 
1265                 PCB_SHAPE* shape1 = new PCB_SHAPE( m_board, SHAPE_T::SEGMENT );
1266                 m_board->Add( shape1, ADD_MODE::APPEND );
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, SHAPE_T::SEGMENT );
1275                 m_board->Add( shape2, ADD_MODE::APPEND );
1276                 shape2->SetLayer( klayer );
1277                 shape2->SetWidth( aElem.linewidth );
1278                 shape2->SetStart( referencePoint0 );
1279                 shape2->SetEnd( referencePoint0 + arrVec );
1280             }
1281         }
1282     }
1283 
1284     if( aElem.textPoint.empty() )
1285     {
1286         wxLogError( "No text position present for leader dimension object" );
1287         return;
1288     }
1289 
1290     PCB_TEXT* text = new PCB_TEXT( m_board );
1291     m_board->Add( text, ADD_MODE::APPEND );
1292     text->SetText( aElem.textformat );
1293     text->SetPosition( aElem.textPoint.at( 0 ) );
1294     text->SetLayer( klayer );
1295     text->SetTextSize( wxSize( aElem.textheight, aElem.textheight ) ); // TODO: parse text width
1296     text->SetTextThickness( aElem.textlinewidth );
1297     text->SetHorizJustify( EDA_TEXT_HJUSTIFY_T::GR_TEXT_HJUSTIFY_LEFT );
1298     text->SetVertJustify( EDA_TEXT_VJUSTIFY_T::GR_TEXT_VJUSTIFY_BOTTOM );
1299 }
1300 
1301 
HelperParseDimensions6Datum(const ADIMENSION6 & aElem)1302 void ALTIUM_PCB::HelperParseDimensions6Datum( const ADIMENSION6& aElem )
1303 {
1304     PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
1305 
1306     if( klayer == UNDEFINED_LAYER )
1307     {
1308         wxLogWarning( _( "Dimension found on an Altium layer (%d) with no KiCad equivalent. "
1309                          "It has been moved to KiCad layer Eco1_User." ),
1310                       aElem.layer );
1311         klayer = Eco1_User;
1312     }
1313 
1314     for( size_t i = 0; i < aElem.referencePoint.size(); i++ )
1315     {
1316         PCB_SHAPE* shape = new PCB_SHAPE( m_board, SHAPE_T::SEGMENT );
1317         m_board->Add( shape, ADD_MODE::APPEND );
1318         shape->SetLayer( klayer );
1319         shape->SetWidth( aElem.linewidth );
1320         shape->SetStart( aElem.referencePoint.at( i ) );
1321         // shape->SetEnd( /* TODO: seems to be based on TEXTY */ );
1322     }
1323 }
1324 
1325 
HelperParseDimensions6Center(const ADIMENSION6 & aElem)1326 void ALTIUM_PCB::HelperParseDimensions6Center( const ADIMENSION6& aElem )
1327 {
1328     PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
1329 
1330     if( klayer == UNDEFINED_LAYER )
1331     {
1332         wxLogWarning( _( "Dimension found on an Altium layer (%d) with no KiCad equivalent. "
1333                          "It has been moved to KiCad layer Eco1_User." ),
1334                       aElem.layer );
1335         klayer = Eco1_User;
1336     }
1337 
1338     wxPoint vec = wxPoint( 0, aElem.height / 2 );
1339     RotatePoint( &vec, aElem.angle * 10. );
1340 
1341     PCB_DIM_CENTER* dimension = new PCB_DIM_CENTER( m_board );
1342     m_board->Add( dimension, ADD_MODE::APPEND );
1343     dimension->SetLayer( klayer );
1344     dimension->SetLineThickness( aElem.linewidth );
1345     dimension->SetStart( aElem.xy1 );
1346     dimension->SetEnd( aElem.xy1 + vec );
1347 }
1348 
1349 
ParseDimensions6Data(const CFB::CompoundFileReader & aReader,const CFB::COMPOUND_FILE_ENTRY * aEntry)1350 void ALTIUM_PCB::ParseDimensions6Data( const CFB::CompoundFileReader& aReader,
1351                                        const CFB::COMPOUND_FILE_ENTRY* aEntry )
1352 {
1353     if( m_progressReporter )
1354         m_progressReporter->Report( "Loading dimension drawings..." );
1355 
1356     ALTIUM_PARSER reader( aReader, aEntry );
1357 
1358     while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
1359     {
1360         checkpoint();
1361         ADIMENSION6 elem( reader );
1362 
1363         switch( elem.kind )
1364         {
1365         case ALTIUM_DIMENSION_KIND::LINEAR:
1366             HelperParseDimensions6Linear( elem );
1367             break;
1368         case ALTIUM_DIMENSION_KIND::RADIAL:
1369             HelperParseDimensions6Radial( elem );
1370             break;
1371         case ALTIUM_DIMENSION_KIND::LEADER:
1372             HelperParseDimensions6Leader( elem );
1373             break;
1374         case ALTIUM_DIMENSION_KIND::DATUM:
1375             wxLogError( _( "Ignored dimension of kind %d (not yet supported)." ), elem.kind );
1376             // HelperParseDimensions6Datum( elem );
1377             break;
1378         case ALTIUM_DIMENSION_KIND::CENTER:
1379             HelperParseDimensions6Center( elem );
1380             break;
1381         default:
1382             wxLogError( _( "Ignored dimension of kind %d (not yet supported)." ), elem.kind );
1383             break;
1384         }
1385     }
1386 
1387     if( reader.GetRemainingBytes() != 0 )
1388         THROW_IO_ERROR( "Dimensions6 stream is not fully parsed" );
1389 }
1390 
1391 
ParseModelsData(const CFB::CompoundFileReader & aReader,const CFB::COMPOUND_FILE_ENTRY * aEntry,const wxString & aRootDir)1392 void ALTIUM_PCB::ParseModelsData( const CFB::CompoundFileReader& aReader,
1393                                   const CFB::COMPOUND_FILE_ENTRY* aEntry, const wxString& aRootDir )
1394 {
1395     if( m_progressReporter )
1396         m_progressReporter->Report( "Loading 3D models..." );
1397 
1398     ALTIUM_PARSER reader( aReader, aEntry );
1399 
1400     if( reader.GetRemainingBytes() == 0 )
1401         return;
1402 
1403     wxString projectPath = wxPathOnly( m_board->GetFileName() );
1404     // TODO: set KIPRJMOD always after import (not only when loading project)?
1405     wxSetEnv( PROJECT_VAR_NAME, projectPath );
1406 
1407     // TODO: make this path configurable?
1408     const wxString altiumModelDir = "ALTIUM_EMBEDDED_MODELS";
1409 
1410     wxFileName altiumModelsPath = wxFileName::DirName( projectPath );
1411     wxString   kicadModelPrefix = "${KIPRJMOD}/" + altiumModelDir + "/";
1412 
1413     if( !altiumModelsPath.AppendDir( altiumModelDir ) )
1414     {
1415         THROW_IO_ERROR( "Cannot construct directory path for step models" );
1416     }
1417 
1418     // Create dir if it does not exist
1419     if( !altiumModelsPath.DirExists() )
1420     {
1421         if( !altiumModelsPath.Mkdir() )
1422         {
1423             wxLogError( _( "Failed to create folder '%s'." ) + wxS( " " )
1424                       + _( "No 3D-models will be imported." ),
1425                         altiumModelsPath.GetFullPath() );
1426             return;
1427         }
1428     }
1429 
1430     int      idx = 0;
1431     wxString invalidChars = wxFileName::GetForbiddenChars();
1432 
1433     while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
1434     {
1435         checkpoint();
1436         AMODEL elem( reader );
1437 
1438         wxString       stepPath = wxString::Format( aRootDir + "%d", idx );
1439         bool           validName = !elem.name.IsEmpty() && elem.name.IsAscii() &&
1440                                    wxString::npos == elem.name.find_first_of( invalidChars );
1441         wxString       storageName = !validName ? wxString::Format( "model_%d", idx ) : elem.name;
1442         wxFileName     storagePath( altiumModelsPath.GetPath(), storageName );
1443 
1444         idx++;
1445 
1446         const CFB::COMPOUND_FILE_ENTRY* stepEntry = FindStream( aReader, stepPath.c_str() );
1447 
1448         if( stepEntry == nullptr )
1449         {
1450             wxLogError( _( "File not found: '%s'. 3D-model not imported." ), stepPath );
1451             continue;
1452         }
1453 
1454         size_t                  stepSize = static_cast<size_t>( stepEntry->size );
1455         std::unique_ptr<char[]> stepContent( new char[stepSize] );
1456 
1457         // read file into buffer
1458         aReader.ReadFile( stepEntry, 0, stepContent.get(), stepSize );
1459 
1460         if( !storagePath.IsDirWritable() )
1461         {
1462             wxLogError( _( "Insufficient permissions to save file '%s'." ),
1463                         storagePath.GetFullPath() );
1464             continue;
1465         }
1466 
1467         wxMemoryInputStream stepStream( stepContent.get(), stepSize );
1468         wxZlibInputStream   zlibInputStream( stepStream );
1469 
1470         wxFFileOutputStream outputStream( storagePath.GetFullPath() );
1471         outputStream.Write( zlibInputStream );
1472         outputStream.Close();
1473 
1474         m_models.insert( { elem.id, kicadModelPrefix + storageName } );
1475     }
1476 
1477     if( reader.GetRemainingBytes() != 0 )
1478         THROW_IO_ERROR( "Models stream is not fully parsed" );
1479 }
1480 
1481 
ParseNets6Data(const CFB::CompoundFileReader & aReader,const CFB::COMPOUND_FILE_ENTRY * aEntry)1482 void ALTIUM_PCB::ParseNets6Data( const CFB::CompoundFileReader& aReader,
1483                                  const CFB::COMPOUND_FILE_ENTRY* aEntry )
1484 {
1485     if( m_progressReporter )
1486         m_progressReporter->Report( _( "Loading nets..." ) );
1487 
1488     ALTIUM_PARSER reader( aReader, aEntry );
1489 
1490     wxASSERT( m_num_nets == 0 );
1491     while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
1492     {
1493         checkpoint();
1494         ANET6 elem( reader );
1495 
1496         m_board->Add( new NETINFO_ITEM( m_board, elem.name, ++m_num_nets ), ADD_MODE::APPEND );
1497     }
1498 
1499     if( reader.GetRemainingBytes() != 0 )
1500         THROW_IO_ERROR( "Nets6 stream is not fully parsed" );
1501 }
1502 
ParsePolygons6Data(const CFB::CompoundFileReader & aReader,const CFB::COMPOUND_FILE_ENTRY * aEntry)1503 void ALTIUM_PCB::ParsePolygons6Data( const CFB::CompoundFileReader& aReader,
1504                                      const CFB::COMPOUND_FILE_ENTRY* aEntry )
1505 {
1506     if( m_progressReporter )
1507         m_progressReporter->Report( _( "Loading polygons..." ) );
1508 
1509     ALTIUM_PARSER reader( aReader, aEntry );
1510 
1511     while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
1512     {
1513         checkpoint();
1514         APOLYGON6 elem( reader );
1515 
1516         PCB_LAYER_ID klayer = GetKicadLayer( elem.layer );
1517 
1518         if( klayer == UNDEFINED_LAYER )
1519         {
1520             wxLogWarning( _( "Polygon found on an Altium layer (%d) with no KiCad equivalent. "
1521                              "It has been moved to KiCad layer Eco1_User." ),
1522                           elem.layer );
1523             klayer = Eco1_User;
1524         }
1525 
1526         SHAPE_LINE_CHAIN linechain;
1527         HelperShapeLineChainFromAltiumVertices( linechain, elem.vertices );
1528 
1529         if( linechain.PointCount() < 2 )
1530         {
1531             // We have found multiple Altium files with polygon records containing nothing but two
1532             // coincident vertices.  These polygons do not appear when opening the file in Altium.
1533             // https://gitlab.com/kicad/code/kicad/-/issues/8183
1534             //
1535             // wxLogError( _( "Polygon has only %d point extracted from %ld vertices. At least 2 "
1536             //                "points are required." ),
1537             //             linechain.PointCount(),
1538             //             elem.vertices.size() );
1539 
1540             m_polygons.emplace_back( nullptr );
1541             continue;
1542         }
1543 
1544         ZONE* zone = new ZONE( m_board );
1545         m_board->Add( zone, ADD_MODE::APPEND );
1546         m_polygons.emplace_back( zone );
1547 
1548         zone->SetFillVersion( 6 );
1549         zone->SetNetCode( GetNetCode( elem.net ) );
1550         zone->SetLayer( klayer );
1551         zone->SetPosition( elem.vertices.at( 0 ).position );
1552         zone->SetLocked( elem.locked );
1553         zone->SetPriority( elem.pourindex > 0 ? elem.pourindex : 0 );
1554         zone->Outline()->AddOutline( linechain );
1555 
1556         if( elem.pourindex > m_highest_pour_index )
1557             m_highest_pour_index = elem.pourindex;
1558 
1559         // TODO: more flexible rule parsing
1560         const ARULE6* clearanceRule = GetRuleDefault( ALTIUM_RULE_KIND::PLANE_CLEARANCE );
1561 
1562         if( clearanceRule != nullptr )
1563             zone->SetLocalClearance( clearanceRule->planeclearanceClearance );
1564 
1565         const ARULE6* polygonConnectRule = GetRuleDefault( ALTIUM_RULE_KIND::POLYGON_CONNECT );
1566 
1567         if( polygonConnectRule != nullptr )
1568         {
1569             switch( polygonConnectRule->polygonconnectStyle )
1570             {
1571             case ALTIUM_CONNECT_STYLE::DIRECT:
1572                 zone->SetPadConnection( ZONE_CONNECTION::FULL );
1573                 break;
1574 
1575             case ALTIUM_CONNECT_STYLE::NONE:
1576                 zone->SetPadConnection( ZONE_CONNECTION::NONE );
1577                 break;
1578 
1579             default:
1580             case ALTIUM_CONNECT_STYLE::RELIEF:
1581                 zone->SetPadConnection( ZONE_CONNECTION::THERMAL );
1582                 break;
1583             }
1584 
1585             // TODO: correct variables?
1586             zone->SetThermalReliefSpokeWidth(
1587                     polygonConnectRule->polygonconnectReliefconductorwidth );
1588             zone->SetThermalReliefGap( polygonConnectRule->polygonconnectAirgapwidth );
1589 
1590             if( polygonConnectRule->polygonconnectReliefconductorwidth < zone->GetMinThickness() )
1591                 zone->SetMinThickness( polygonConnectRule->polygonconnectReliefconductorwidth );
1592         }
1593 
1594         if( IsAltiumLayerAPlane( elem.layer ) )
1595         {
1596             // outer zone will be set to priority 0 later.
1597             zone->SetPriority( 1 );
1598 
1599             // check if this is the outer zone by simply comparing the BBOX
1600             const auto& outer_plane = m_outer_plane.find( elem.layer );
1601             if( outer_plane == m_outer_plane.end()
1602                     || zone->GetBoundingBox().Contains( outer_plane->second->GetBoundingBox() ) )
1603             {
1604                 m_outer_plane[elem.layer] = zone;
1605             }
1606         }
1607 
1608         if( elem.hatchstyle != ALTIUM_POLYGON_HATCHSTYLE::SOLID
1609                 && elem.hatchstyle != ALTIUM_POLYGON_HATCHSTYLE::UNKNOWN )
1610         {
1611             zone->SetFillMode( ZONE_FILL_MODE::HATCH_PATTERN );
1612             zone->SetHatchThickness( elem.trackwidth );
1613 
1614             if( elem.hatchstyle == ALTIUM_POLYGON_HATCHSTYLE::NONE )
1615             {
1616                 // use a small hack to get us only an outline (hopefully)
1617                 const EDA_RECT& bbox = zone->GetBoundingBox();
1618                 zone->SetHatchGap( std::max( bbox.GetHeight(), bbox.GetWidth() ) );
1619             }
1620             else
1621             {
1622                 zone->SetHatchGap( elem.gridsize - elem.trackwidth );
1623             }
1624 
1625             zone->SetHatchOrientation( elem.hatchstyle == ALTIUM_POLYGON_HATCHSTYLE::DEGREE_45 ? 45 : 0 );
1626         }
1627 
1628         zone->SetBorderDisplayStyle( ZONE_BORDER_DISPLAY_STYLE::DIAGONAL_EDGE,
1629                                      ZONE::GetDefaultHatchPitch(), true );
1630     }
1631 
1632     if( reader.GetRemainingBytes() != 0 )
1633     {
1634         THROW_IO_ERROR( "Polygons6 stream is not fully parsed" );
1635     }
1636 }
1637 
ParseRules6Data(const CFB::CompoundFileReader & aReader,const CFB::COMPOUND_FILE_ENTRY * aEntry)1638 void ALTIUM_PCB::ParseRules6Data( const CFB::CompoundFileReader& aReader,
1639                                   const CFB::COMPOUND_FILE_ENTRY* aEntry )
1640 {
1641     if( m_progressReporter )
1642         m_progressReporter->Report( _( "Loading rules..." ) );
1643 
1644     ALTIUM_PARSER reader( aReader, aEntry );
1645 
1646     while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
1647     {
1648         checkpoint();
1649         ARULE6 elem( reader );
1650 
1651         m_rules[elem.kind].emplace_back( elem );
1652     }
1653 
1654     // sort rules by priority
1655     for( auto&& val : m_rules )
1656     {
1657         std::sort( val.second.begin(), val.second.end(),
1658                 []( const auto& lhs, const auto& rhs )
1659                 {
1660                     return lhs.priority < rhs.priority;
1661                 } );
1662     }
1663 
1664     if( reader.GetRemainingBytes() != 0 )
1665     {
1666         THROW_IO_ERROR( "Rules6 stream is not fully parsed" );
1667     }
1668 }
1669 
ParseBoardRegionsData(const CFB::CompoundFileReader & aReader,const CFB::COMPOUND_FILE_ENTRY * aEntry)1670 void ALTIUM_PCB::ParseBoardRegionsData( const CFB::CompoundFileReader& aReader,
1671                                         const CFB::COMPOUND_FILE_ENTRY* aEntry )
1672 {
1673     if( m_progressReporter )
1674         m_progressReporter->Report( _( "Loading board regions..." ) );
1675 
1676     ALTIUM_PARSER reader( aReader, aEntry );
1677 
1678     while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
1679     {
1680         checkpoint();
1681         AREGION6 elem( reader, false );
1682 
1683         // TODO: implement?
1684     }
1685 
1686     if( reader.GetRemainingBytes() != 0 )
1687     {
1688         THROW_IO_ERROR( "BoardRegions stream is not fully parsed" );
1689     }
1690 }
1691 
ParseShapeBasedRegions6Data(const CFB::CompoundFileReader & aReader,const CFB::COMPOUND_FILE_ENTRY * aEntry)1692 void ALTIUM_PCB::ParseShapeBasedRegions6Data( const CFB::CompoundFileReader& aReader,
1693                                               const CFB::COMPOUND_FILE_ENTRY* aEntry )
1694 {
1695     if( m_progressReporter )
1696         m_progressReporter->Report( _( "Loading zones..." ) );
1697 
1698     ALTIUM_PARSER reader( aReader, aEntry );
1699 
1700     while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
1701     {
1702         checkpoint();
1703         AREGION6 elem( reader, true );
1704 
1705         if( elem.kind == ALTIUM_REGION_KIND::BOARD_CUTOUT )
1706         {
1707             HelperCreateBoardOutline( elem.outline );
1708         }
1709         else if( elem.kind == ALTIUM_REGION_KIND::POLYGON_CUTOUT || elem.is_keepout )
1710         {
1711             SHAPE_LINE_CHAIN linechain;
1712             HelperShapeLineChainFromAltiumVertices( linechain, elem.outline );
1713 
1714             if( linechain.PointCount() < 2 )
1715             {
1716                 // We have found multiple Altium files with polygon records containing nothing but
1717                 // two coincident vertices.  These polygons do not appear when opening the file in
1718                 // Altium.  https://gitlab.com/kicad/code/kicad/-/issues/8183
1719                 //
1720                 // wxLogError( _( "ShapeBasedRegion has only %d point extracted from %ld vertices. "
1721                 //                "At least 2 points are required." ),
1722                 //              linechain.PointCount(),
1723                 //              elem.outline.size() );
1724                 continue;
1725             }
1726 
1727             ZONE* zone = new ZONE( m_board );
1728             m_board->Add( zone, ADD_MODE::APPEND );
1729 
1730             zone->SetFillVersion( 6 );
1731             zone->SetIsRuleArea( true );
1732             zone->SetDoNotAllowTracks( false );
1733             zone->SetDoNotAllowVias( false );
1734             zone->SetDoNotAllowPads( false );
1735             zone->SetDoNotAllowFootprints( false );
1736             zone->SetDoNotAllowCopperPour( true );
1737 
1738             zone->SetPosition( elem.outline.at( 0 ).position );
1739             zone->Outline()->AddOutline( linechain );
1740 
1741             if( elem.layer == ALTIUM_LAYER::MULTI_LAYER )
1742             {
1743                 zone->SetLayer( F_Cu );
1744                 zone->SetLayerSet( LSET::AllCuMask() );
1745             }
1746             else
1747             {
1748                 PCB_LAYER_ID klayer = GetKicadLayer( elem.layer );
1749 
1750                 if( klayer == UNDEFINED_LAYER )
1751                 {
1752                     wxLogWarning( _( "Zone found on an Altium layer (%d) with no KiCad equivalent. "
1753                                      "It has been moved to KiCad layer Eco1_User." ),
1754                                   elem.layer );
1755                     klayer = Eco1_User;
1756                 }
1757                 zone->SetLayer( klayer );
1758             }
1759 
1760             zone->SetBorderDisplayStyle( ZONE_BORDER_DISPLAY_STYLE::DIAGONAL_EDGE,
1761                                          ZONE::GetDefaultHatchPitch(), true );
1762         }
1763         else if( elem.kind == ALTIUM_REGION_KIND::COPPER )
1764         {
1765             if( elem.subpolyindex == ALTIUM_POLYGON_NONE )
1766             {
1767                 PCB_LAYER_ID klayer = GetKicadLayer( elem.layer );
1768 
1769                 if( klayer == UNDEFINED_LAYER )
1770                 {
1771                     wxLogWarning( _( "Polygon found on an Altium layer (%d) with no KiCad equivalent. "
1772                                      "It has been moved to KiCad layer Eco1_User." ),
1773                                   elem.layer );
1774                     klayer = Eco1_User;
1775                 }
1776 
1777                 SHAPE_LINE_CHAIN linechain;
1778                 HelperShapeLineChainFromAltiumVertices( linechain, elem.outline );
1779 
1780                 if( linechain.PointCount() < 2 )
1781                 {
1782                     // We have found multiple Altium files with polygon records containing nothing
1783                     // but two coincident vertices.  These polygons do not appear when opening the
1784                     // file in Altium.  https://gitlab.com/kicad/code/kicad/-/issues/8183
1785                     //
1786                     // wxLogError( _( "Polygon has only %d point extracted from %ld vertices. At "
1787                     //                "least 2 points are required." ),
1788                     //             linechain.PointCount(),
1789                     //             elem.outline.size() );
1790 
1791                     continue;
1792                 }
1793 
1794                 PCB_SHAPE* shape = new PCB_SHAPE( m_board, SHAPE_T::POLY );
1795                 m_board->Add( shape, ADD_MODE::APPEND );
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 
ParseRegions6Data(const CFB::CompoundFileReader & aReader,const CFB::COMPOUND_FILE_ENTRY * aEntry)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 ),
1885                                      SHAPE_POLY_SET::PM_STRICTLY_SIMPLE );
1886 
1887             SHAPE_POLY_SET finalPolys = rawPolys;
1888             finalPolys.Fracture( SHAPE_POLY_SET::PM_STRICTLY_SIMPLE );
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 
ParseArcs6Data(const CFB::CompoundFileReader & aReader,const CFB::COMPOUND_FILE_ENTRY * aEntry)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 
1931             if( elem.startangle == 0. && elem.endangle == 360. )
1932             { // TODO: other variants to define circle?
1933                 shape.SetShape( SHAPE_T::CIRCLE );
1934                 shape.SetStart( elem.center );
1935                 shape.SetEnd( elem.center - wxPoint( 0, elem.radius ) );
1936             }
1937             else
1938             {
1939                 shape.SetShape( SHAPE_T::ARC );
1940 
1941                 double  includedAngle  = elem.endangle - elem.startangle;
1942                 double  startradiant   = DEG2RAD( elem.startangle );
1943                 wxPoint arcStartOffset = wxPoint( KiROUND( std::cos( startradiant ) * elem.radius ),
1944                                                  -KiROUND( std::sin( startradiant ) * elem.radius ) );
1945 
1946                 shape.SetCenter( elem.center );
1947                 shape.SetStart( elem.center + arcStartOffset );
1948                 shape.SetArcAngleAndEnd( -NormalizeAngleDegreesPos( includedAngle ) * 10.0, true );
1949             }
1950 
1951             ZONE* zone = new ZONE( m_board );
1952             m_board->Add( zone, ADD_MODE::APPEND );
1953 
1954             zone->SetFillVersion( 6 );
1955             zone->SetIsRuleArea( true );
1956             zone->SetDoNotAllowTracks( false );
1957             zone->SetDoNotAllowVias( false );
1958             zone->SetDoNotAllowPads( false );
1959             zone->SetDoNotAllowFootprints( false );
1960             zone->SetDoNotAllowCopperPour( true );
1961 
1962             if( elem.layer == ALTIUM_LAYER::MULTI_LAYER )
1963             {
1964                 zone->SetLayer( F_Cu );
1965                 zone->SetLayerSet( LSET::AllCuMask() );
1966             }
1967             else
1968             {
1969                 PCB_LAYER_ID klayer = GetKicadLayer( elem.layer );
1970 
1971                 if( klayer == UNDEFINED_LAYER )
1972                 {
1973                     wxLogWarning( _( "Arc keepout found on an Altium layer (%d) with no KiCad "
1974                                      "equivalent. It has been moved to KiCad layer Eco1_User." ),
1975                                   elem.layer );
1976                     klayer = Eco1_User;
1977                 }
1978                 zone->SetLayer( klayer );
1979             }
1980 
1981             shape.TransformShapeWithClearanceToPolygon( *zone->Outline(), klayer, 0, ARC_HIGH_DEF,
1982                                                         ERROR_INSIDE );
1983             zone->Outline()->Simplify( SHAPE_POLY_SET::PM_STRICTLY_SIMPLE ); // the outline is not a single polygon!
1984 
1985             zone->SetBorderDisplayStyle( ZONE_BORDER_DISPLAY_STYLE::DIAGONAL_EDGE,
1986                                          ZONE::GetDefaultHatchPitch(), true );
1987             continue;
1988         }
1989 
1990         if( klayer == UNDEFINED_LAYER )
1991         {
1992             wxLogWarning( _( "Arc found on an Altium layer (%d) with no KiCad equivalent. "
1993                              "It has been moved to KiCad layer Eco1_User." ),
1994                           elem.layer );
1995             klayer = Eco1_User;
1996         }
1997 
1998         if( klayer >= F_Cu && klayer <= B_Cu )
1999         {
2000             double  angle          = -NormalizeAngleDegreesPos( elem.endangle - elem.startangle );
2001             double  startradiant   = DEG2RAD( elem.startangle );
2002             wxPoint arcStartOffset = wxPoint( KiROUND( std::cos( startradiant ) * elem.radius ),
2003                                               -KiROUND( std::sin( startradiant ) * elem.radius ) );
2004 
2005             arcStartOffset += elem.center;
2006 
2007             // If it's a circle then add two 180-degree arcs
2008             if( elem.startangle == 0. && elem.endangle == 360. )
2009                 angle = 180.;
2010 
2011             SHAPE_ARC shapeArc( elem.center, arcStartOffset, angle, elem.width );
2012             PCB_ARC*  arc = new PCB_ARC( m_board, &shapeArc );
2013             m_board->Add( arc, ADD_MODE::APPEND );
2014 
2015             arc->SetWidth( elem.width );
2016             arc->SetLayer( klayer );
2017             arc->SetNetCode( GetNetCode( elem.net ) );
2018 
2019             // Add second 180-degree arc for a circle
2020             if( elem.startangle == 0. && elem.endangle == 360. )
2021             {
2022                 shapeArc = SHAPE_ARC( elem.center, arcStartOffset, -angle, elem.width );
2023                 arc = new PCB_ARC( m_board, &shapeArc );
2024                 m_board->Add( arc, ADD_MODE::APPEND );
2025 
2026                 arc->SetWidth( elem.width );
2027                 arc->SetLayer( klayer );
2028                 arc->SetNetCode( GetNetCode( elem.net ) );
2029             }
2030         }
2031         else
2032         {
2033             PCB_SHAPE* shape = HelperCreateAndAddShape( elem.component );
2034             shape->SetWidth( elem.width );
2035             shape->SetLayer( klayer );
2036 
2037             if( elem.startangle == 0. && elem.endangle == 360. )
2038             { // TODO: other variants to define circle?
2039                 shape->SetShape( SHAPE_T::CIRCLE );
2040                 shape->SetStart( elem.center );
2041                 shape->SetEnd( elem.center - wxPoint( 0, elem.radius ) );
2042             }
2043             else
2044             {
2045                 shape->SetShape( SHAPE_T::ARC );
2046 
2047                 double  includedAngle  = elem.endangle - elem.startangle;
2048                 double  startradiant   = DEG2RAD( elem.startangle );
2049                 wxPoint arcStartOffset = wxPoint( KiROUND( std::cos( startradiant ) * elem.radius ),
2050                                                   -KiROUND( std::sin( startradiant ) * elem.radius ) );
2051 
2052                 shape->SetCenter( elem.center );
2053                 shape->SetStart( elem.center + arcStartOffset );
2054                 shape->SetArcAngleAndEnd( -NormalizeAngleDegreesPos( includedAngle ) * 10.0, true );
2055             }
2056 
2057             HelperShapeSetLocalCoord( shape, elem.component );
2058         }
2059     }
2060 
2061     if( reader.GetRemainingBytes() != 0 )
2062     {
2063         THROW_IO_ERROR( "Arcs6 stream is not fully parsed" );
2064     }
2065 }
2066 
2067 
ParsePads6Data(const CFB::CompoundFileReader & aReader,const CFB::COMPOUND_FILE_ENTRY * aEntry)2068 void ALTIUM_PCB::ParsePads6Data( const CFB::CompoundFileReader& aReader,
2069                                  const CFB::COMPOUND_FILE_ENTRY* aEntry )
2070 {
2071     if( m_progressReporter )
2072         m_progressReporter->Report( _( "Loading pads..." ) );
2073 
2074     ALTIUM_PARSER reader( aReader, aEntry );
2075 
2076     while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
2077     {
2078         checkpoint();
2079         APAD6 elem( reader );
2080 
2081         // It is possible to place altium pads on non-copper layers -> we need to interpolate them using drawings!
2082         if( !IsAltiumLayerCopper( elem.layer ) && !IsAltiumLayerAPlane( elem.layer )
2083                 && elem.layer != ALTIUM_LAYER::MULTI_LAYER )
2084         {
2085             HelperParsePad6NonCopper( elem );
2086             continue;
2087         }
2088 
2089         // Create Pad
2090         FOOTPRINT* footprint = nullptr;
2091 
2092         if( elem.component == ALTIUM_COMPONENT_NONE )
2093         {
2094             footprint = new FOOTPRINT( m_board ); // We cannot add a pad directly into the PCB
2095             m_board->Add( footprint, ADD_MODE::APPEND );
2096             footprint->SetPosition( elem.position );
2097         }
2098         else
2099         {
2100             if( m_components.size() <= elem.component )
2101             {
2102                 THROW_IO_ERROR( wxString::Format( "Pads6 stream tries to access component id %d "
2103                                                   "of %d existing components",
2104                                                   elem.component,
2105                                                   m_components.size() ) );
2106             }
2107             footprint = m_components.at( elem.component );
2108         }
2109 
2110         PAD* pad = new PAD( footprint );
2111         footprint->Add( pad, ADD_MODE::APPEND );
2112 
2113         pad->SetNumber( elem.name );
2114         pad->SetNetCode( GetNetCode( elem.net ) );
2115         pad->SetLocked( elem.is_locked );
2116 
2117         pad->SetPosition( elem.position );
2118         pad->SetOrientationDegrees( elem.direction );
2119         pad->SetLocalCoord();
2120 
2121         pad->SetSize( elem.topsize );
2122 
2123         if( elem.holesize == 0 )
2124         {
2125             pad->SetAttribute( PAD_ATTRIB::SMD );
2126         }
2127         else
2128         {
2129             if( elem.layer != ALTIUM_LAYER::MULTI_LAYER )
2130             {
2131                 // TODO: I assume other values are possible as well?
2132                 wxLogError( _( "Footprint %s pad %s is not marked as multilayer, but is a TH pad." ),
2133                             footprint->GetReference(),
2134                             elem.name );
2135             }
2136             pad->SetAttribute( elem.plated ? PAD_ATTRIB::PTH :
2137                                              PAD_ATTRIB::NPTH );
2138             if( !elem.sizeAndShape || elem.sizeAndShape->holeshape == ALTIUM_PAD_HOLE_SHAPE::ROUND )
2139             {
2140                 pad->SetDrillShape( PAD_DRILL_SHAPE_T::PAD_DRILL_SHAPE_CIRCLE );
2141                 pad->SetDrillSize( wxSize( elem.holesize, elem.holesize ) );
2142             }
2143             else
2144             {
2145                 switch( elem.sizeAndShape->holeshape )
2146                 {
2147                 case ALTIUM_PAD_HOLE_SHAPE::ROUND:
2148                     wxFAIL_MSG( "Round holes are handled before the switch" );
2149                     break;
2150 
2151                 case ALTIUM_PAD_HOLE_SHAPE::SQUARE:
2152                     wxLogWarning( _( "Footprint %s pad %s has a square hole (not yet supported)." ),
2153                                   footprint->GetReference(),
2154                                   elem.name );
2155                     pad->SetDrillShape( PAD_DRILL_SHAPE_T::PAD_DRILL_SHAPE_CIRCLE );
2156                     pad->SetDrillSize( wxSize( elem.holesize, elem.holesize ) ); // Workaround
2157                     // TODO: elem.sizeAndShape->slotsize was 0 in testfile. Either use holesize in this case or rect holes have a different id
2158                     break;
2159 
2160                 case ALTIUM_PAD_HOLE_SHAPE::SLOT:
2161                 {
2162                     pad->SetDrillShape( PAD_DRILL_SHAPE_T::PAD_DRILL_SHAPE_OBLONG );
2163                     double normalizedSlotrotation =
2164                             NormalizeAngleDegreesPos( elem.sizeAndShape->slotrotation );
2165 
2166                     if( normalizedSlotrotation == 0. || normalizedSlotrotation == 180. )
2167                     {
2168                         pad->SetDrillSize( wxSize( elem.sizeAndShape->slotsize, elem.holesize ) );
2169                     }
2170                     else
2171                     {
2172                         if( normalizedSlotrotation != 90. && normalizedSlotrotation != 270. )
2173                         {
2174                             wxLogWarning( _( "Footprint %s pad %s has a hole-rotation of %f "
2175                                              "degrees. KiCad only supports 90 degree rotations." ),
2176                                           footprint->GetReference(),
2177                                           elem.name,
2178                                           normalizedSlotrotation );
2179                         }
2180 
2181                         pad->SetDrillSize( wxSize( elem.holesize, elem.sizeAndShape->slotsize ) );
2182                     }
2183                 }
2184                 break;
2185 
2186                 default:
2187                 case ALTIUM_PAD_HOLE_SHAPE::UNKNOWN:
2188                     wxLogError( _( "Footprint %s pad %s uses a hole of unknown kind %d." ),
2189                                 footprint->GetReference(),
2190                                 elem.name,
2191                                 elem.sizeAndShape->holeshape );
2192                     pad->SetDrillShape( PAD_DRILL_SHAPE_T::PAD_DRILL_SHAPE_CIRCLE );
2193                     pad->SetDrillSize( wxSize( elem.holesize, elem.holesize ) ); // Workaround
2194                     break;
2195                 }
2196             }
2197 
2198             if( elem.sizeAndShape )
2199             {
2200                 pad->SetOffset( elem.sizeAndShape->holeoffset[0] );
2201             }
2202         }
2203 
2204         if( elem.padmode != ALTIUM_PAD_MODE::SIMPLE )
2205         {
2206             wxLogError( _( "Footprint %s pad %s uses a complex pad stack (not yet supported.)" ),
2207                         footprint->GetReference(),
2208                         elem.name );
2209         }
2210 
2211         switch( elem.topshape )
2212         {
2213         case ALTIUM_PAD_SHAPE::RECT:
2214             pad->SetShape( PAD_SHAPE::RECT );
2215             break;
2216         case ALTIUM_PAD_SHAPE::CIRCLE:
2217             if( elem.sizeAndShape
2218                     && elem.sizeAndShape->alt_shape[0] == ALTIUM_PAD_SHAPE_ALT::ROUNDRECT )
2219             {
2220                 pad->SetShape( PAD_SHAPE::ROUNDRECT ); // 100 = round, 0 = rectangular
2221                 double ratio = elem.sizeAndShape->cornerradius[0] / 200.;
2222                 pad->SetRoundRectRadiusRatio( ratio );
2223             }
2224             else if( elem.topsize.x == elem.topsize.y )
2225             {
2226                 pad->SetShape( PAD_SHAPE::CIRCLE );
2227             }
2228             else
2229             {
2230                 pad->SetShape( PAD_SHAPE::OVAL );
2231             }
2232             break;
2233         case ALTIUM_PAD_SHAPE::OCTAGONAL:
2234             pad->SetShape( PAD_SHAPE::CHAMFERED_RECT );
2235             pad->SetChamferPositions( RECT_CHAMFER_ALL );
2236             pad->SetChamferRectRatio( 0.25 );
2237             break;
2238         case ALTIUM_PAD_SHAPE::UNKNOWN:
2239         default:
2240             wxLogError( _( "Footprint %s pad %s uses an unknown pad-shape." ),
2241                         footprint->GetReference(),
2242                         elem.name );
2243             break;
2244         }
2245 
2246         switch( elem.layer )
2247         {
2248         case ALTIUM_LAYER::TOP_LAYER:
2249             pad->SetLayer( F_Cu );
2250             pad->SetLayerSet( PAD::SMDMask() );
2251             break;
2252         case ALTIUM_LAYER::BOTTOM_LAYER:
2253             pad->SetLayer( B_Cu );
2254             pad->SetLayerSet( FlipLayerMask( PAD::SMDMask() ) );
2255             break;
2256         case ALTIUM_LAYER::MULTI_LAYER:
2257             pad->SetLayerSet( elem.plated ? PAD::PTHMask() : PAD::UnplatedHoleMask() );
2258             break;
2259         default:
2260             PCB_LAYER_ID klayer = GetKicadLayer( elem.layer );
2261             pad->SetLayer( klayer );
2262             pad->SetLayerSet( LSET( 1, klayer ) );
2263             break;
2264         }
2265 
2266         if( elem.pastemaskexpansionmode == ALTIUM_PAD_RULE::MANUAL )
2267         {
2268             pad->SetLocalSolderPasteMargin( elem.pastemaskexpansionmanual );
2269         }
2270 
2271         if( elem.soldermaskexpansionmode == ALTIUM_PAD_RULE::MANUAL )
2272         {
2273             pad->SetLocalSolderMaskMargin( elem.soldermaskexpansionmanual );
2274         }
2275 
2276         if( elem.is_tent_top )
2277         {
2278             pad->SetLayerSet( pad->GetLayerSet().reset( F_Mask ) );
2279         }
2280         if( elem.is_tent_bottom )
2281         {
2282             pad->SetLayerSet( pad->GetLayerSet().reset( B_Mask ) );
2283         }
2284     }
2285 
2286     if( reader.GetRemainingBytes() != 0 )
2287     {
2288         THROW_IO_ERROR( "Pads6 stream is not fully parsed" );
2289     }
2290 }
2291 
2292 
HelperParsePad6NonCopper(const APAD6 & aElem)2293 void ALTIUM_PCB::HelperParsePad6NonCopper( const APAD6& aElem )
2294 {
2295     PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
2296 
2297     if( klayer == UNDEFINED_LAYER )
2298     {
2299         wxLogWarning( _( "Non-copper pad %s found on an Altium layer (%d) with no KiCad equivalent. "
2300                          "It has been moved to KiCad layer Eco1_User." ),
2301                       aElem.name,
2302                       aElem.layer );
2303         klayer = Eco1_User;
2304     }
2305 
2306     if( aElem.net != ALTIUM_NET_UNCONNECTED )
2307     {
2308         wxLogError( _( "Non-copper pad %s is connected to a net, which is not supported." ),
2309                     aElem.name );
2310     }
2311 
2312     if( aElem.holesize != 0 )
2313     {
2314         wxLogError( _( "Non-copper pad %s has a hole, which is not supported." ), aElem.name );
2315     }
2316 
2317     if( aElem.padmode != ALTIUM_PAD_MODE::SIMPLE )
2318     {
2319         wxLogWarning( _( "Non-copper pad %s has a complex pad stack (not yet supported)." ),
2320                       aElem.name );
2321     }
2322 
2323     switch( aElem.topshape )
2324     {
2325     case ALTIUM_PAD_SHAPE::RECT:
2326     {
2327         // filled rect
2328         PCB_SHAPE* shape = HelperCreateAndAddShape( aElem.component );
2329         shape->SetShape( SHAPE_T::POLY );
2330         shape->SetFilled( true );
2331         shape->SetLayer( klayer );
2332         shape->SetWidth( 0 );
2333 
2334         shape->SetPolyPoints( { aElem.position + wxPoint( aElem.topsize.x / 2, aElem.topsize.y / 2 ),
2335                                 aElem.position + wxPoint( aElem.topsize.x / 2, -aElem.topsize.y / 2 ),
2336                                 aElem.position + wxPoint( -aElem.topsize.x / 2, -aElem.topsize.y / 2 ),
2337                                 aElem.position + wxPoint( -aElem.topsize.x / 2, aElem.topsize.y / 2 ) } );
2338 
2339         if( aElem.direction != 0 )
2340             shape->Rotate( aElem.position, aElem.direction * 10 );
2341 
2342         HelperShapeSetLocalCoord( shape, aElem.component );
2343     }
2344     break;
2345 
2346     case ALTIUM_PAD_SHAPE::CIRCLE:
2347         if( aElem.sizeAndShape
2348                 && aElem.sizeAndShape->alt_shape[0] == ALTIUM_PAD_SHAPE_ALT::ROUNDRECT )
2349         {
2350             // filled roundrect
2351             int cornerradius = aElem.sizeAndShape->cornerradius[0];
2352             int offset = ( std::min( aElem.topsize.x, aElem.topsize.y ) * cornerradius ) / 200;
2353 
2354             PCB_SHAPE* shape = HelperCreateAndAddShape( aElem.component );
2355             shape->SetLayer( klayer );
2356             shape->SetWidth( offset * 2 );
2357 
2358             if( cornerradius < 100 )
2359             {
2360                 int offsetX = aElem.topsize.x / 2 - offset;
2361                 int offsetY = aElem.topsize.y / 2 - offset;
2362 
2363                 wxPoint p11 = aElem.position + wxPoint( offsetX, offsetY );
2364                 wxPoint p12 = aElem.position + wxPoint( offsetX, -offsetY );
2365                 wxPoint p22 = aElem.position + wxPoint( -offsetX, -offsetY );
2366                 wxPoint p21 = aElem.position + wxPoint( -offsetX, offsetY );
2367 
2368                 shape->SetShape( SHAPE_T::POLY );
2369                 shape->SetFilled( true );
2370                 shape->SetPolyPoints( { p11, p12, p22, p21 } );
2371             }
2372             else if( aElem.topsize.x == aElem.topsize.y )
2373             {
2374                 // circle
2375                 shape->SetShape( SHAPE_T::CIRCLE );
2376                 shape->SetFilled( true );
2377                 shape->SetStart( aElem.position );
2378                 shape->SetEnd( aElem.position - wxPoint( 0, aElem.topsize.x / 4 ) );
2379                 shape->SetWidth( aElem.topsize.x / 2 );
2380             }
2381             else if( aElem.topsize.x < aElem.topsize.y )
2382             {
2383                 // short vertical line
2384                 shape->SetShape( SHAPE_T::SEGMENT );
2385                 wxPoint pointOffset( 0, ( aElem.topsize.y - aElem.topsize.x ) / 2 );
2386                 shape->SetStart( aElem.position + pointOffset );
2387                 shape->SetEnd( aElem.position - pointOffset );
2388             }
2389             else
2390             {
2391                 // short horizontal line
2392                 shape->SetShape( SHAPE_T::SEGMENT );
2393                 wxPoint pointOffset( ( aElem.topsize.x - aElem.topsize.y ) / 2, 0 );
2394                 shape->SetStart( aElem.position + pointOffset );
2395                 shape->SetEnd( aElem.position - pointOffset );
2396             }
2397 
2398             if( aElem.direction != 0 )
2399                 shape->Rotate( aElem.position, aElem.direction * 10 );
2400 
2401             HelperShapeSetLocalCoord( shape, aElem.component );
2402         }
2403         else if( aElem.topsize.x == aElem.topsize.y )
2404         {
2405             // filled circle
2406             PCB_SHAPE* shape = HelperCreateAndAddShape( aElem.component );
2407             shape->SetShape( SHAPE_T::CIRCLE );
2408             shape->SetFilled( true );
2409             shape->SetLayer( klayer );
2410             shape->SetStart( aElem.position );
2411             shape->SetEnd( aElem.position - wxPoint( 0, aElem.topsize.x / 4 ) );
2412             shape->SetWidth( aElem.topsize.x / 2 );
2413             HelperShapeSetLocalCoord( shape, aElem.component );
2414         }
2415         else
2416         {
2417             // short line
2418             PCB_SHAPE* shape = HelperCreateAndAddShape( aElem.component );
2419             shape->SetShape( SHAPE_T::SEGMENT );
2420             shape->SetLayer( klayer );
2421             shape->SetWidth( std::min( aElem.topsize.x, aElem.topsize.y ) );
2422 
2423             if( aElem.topsize.x < aElem.topsize.y )
2424             {
2425                 wxPoint offset( 0, ( aElem.topsize.y - aElem.topsize.x ) / 2 );
2426                 shape->SetStart( aElem.position + offset );
2427                 shape->SetEnd( aElem.position - offset );
2428             }
2429             else
2430             {
2431                 wxPoint offset( ( aElem.topsize.x - aElem.topsize.y ) / 2, 0 );
2432                 shape->SetStart( aElem.position + offset );
2433                 shape->SetEnd( aElem.position - offset );
2434             }
2435 
2436             if( aElem.direction != 0 )
2437                 shape->Rotate( aElem.position, aElem.direction * 10. );
2438 
2439             HelperShapeSetLocalCoord( shape, aElem.component );
2440         }
2441         break;
2442 
2443     case ALTIUM_PAD_SHAPE::OCTAGONAL:
2444     {
2445         // filled octagon
2446         PCB_SHAPE* shape = HelperCreateAndAddShape( aElem.component );
2447         shape->SetShape( SHAPE_T::POLY );
2448         shape->SetFilled( true );
2449         shape->SetLayer( klayer );
2450         shape->SetWidth( 0 );
2451 
2452         wxPoint p11 = aElem.position + wxPoint( aElem.topsize.x / 2, aElem.topsize.y / 2 );
2453         wxPoint p12 = aElem.position + wxPoint( aElem.topsize.x / 2, -aElem.topsize.y / 2 );
2454         wxPoint p22 = aElem.position + wxPoint( -aElem.topsize.x / 2, -aElem.topsize.y / 2 );
2455         wxPoint p21 = aElem.position + wxPoint( -aElem.topsize.x / 2, aElem.topsize.y / 2 );
2456 
2457         int     chamfer = std::min( aElem.topsize.x, aElem.topsize.y ) / 4;
2458         wxPoint chamferX( chamfer, 0 );
2459         wxPoint chamferY( 0, chamfer );
2460 
2461         shape->SetPolyPoints( { p11 - chamferX, p11 - chamferY, p12 + chamferY, p12 - chamferX,
2462                                 p22 + chamferX, p22 + chamferY, p21 - chamferY, p21 + chamferX } );
2463 
2464         if( aElem.direction != 0. )
2465             shape->Rotate( aElem.position, aElem.direction * 10 );
2466 
2467         HelperShapeSetLocalCoord( shape, aElem.component );
2468     }
2469         break;
2470 
2471     case ALTIUM_PAD_SHAPE::UNKNOWN:
2472     default:
2473         wxLogError( _( "Non-copper pad %s uses an unknown pad-shape." ), aElem.name );
2474         break;
2475     }
2476 }
2477 
ParseVias6Data(const CFB::CompoundFileReader & aReader,const CFB::COMPOUND_FILE_ENTRY * aEntry)2478 void ALTIUM_PCB::ParseVias6Data( const CFB::CompoundFileReader& aReader,
2479                                  const CFB::COMPOUND_FILE_ENTRY* aEntry )
2480 {
2481     if( m_progressReporter )
2482         m_progressReporter->Report( _( "Loading vias..." ) );
2483 
2484     ALTIUM_PARSER reader( aReader, aEntry );
2485 
2486     while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
2487     {
2488         checkpoint();
2489         AVIA6 elem( reader );
2490 
2491         PCB_VIA* via = new PCB_VIA( m_board );
2492         m_board->Add( via, ADD_MODE::APPEND );
2493 
2494         via->SetPosition( elem.position );
2495         via->SetWidth( elem.diameter );
2496         via->SetDrill( elem.holesize );
2497         via->SetNetCode( GetNetCode( elem.net ) );
2498         via->SetLocked( elem.is_locked );
2499 
2500         bool start_layer_outside = elem.layer_start == ALTIUM_LAYER::TOP_LAYER
2501                                    || elem.layer_start == ALTIUM_LAYER::BOTTOM_LAYER;
2502         bool end_layer_outside = elem.layer_end == ALTIUM_LAYER::TOP_LAYER
2503                                  || elem.layer_end == ALTIUM_LAYER::BOTTOM_LAYER;
2504 
2505         if( start_layer_outside && end_layer_outside )
2506         {
2507             via->SetViaType( VIATYPE::THROUGH );
2508         }
2509         else if( ( !start_layer_outside ) && ( !end_layer_outside ) )
2510         {
2511             via->SetViaType( VIATYPE::BLIND_BURIED );
2512         }
2513         else
2514         {
2515             via->SetViaType( VIATYPE::MICROVIA ); // TODO: always a microvia?
2516         }
2517 
2518         PCB_LAYER_ID start_klayer = GetKicadLayer( elem.layer_start );
2519         PCB_LAYER_ID end_klayer   = GetKicadLayer( elem.layer_end );
2520 
2521         if( !IsCopperLayer( start_klayer ) || !IsCopperLayer( end_klayer ) )
2522         {
2523             wxLogError( _( "Via from layer %d to %d uses a non-copper layer, which is not "
2524                            "supported." ),
2525                         elem.layer_start,
2526                         elem.layer_end );
2527             continue; // just assume through-hole instead.
2528         }
2529 
2530         // we need VIATYPE set!
2531         via->SetLayerPair( start_klayer, end_klayer );
2532     }
2533 
2534     if( reader.GetRemainingBytes() != 0 )
2535     {
2536         THROW_IO_ERROR( "Vias6 stream is not fully parsed" );
2537     }
2538 }
2539 
ParseTracks6Data(const CFB::CompoundFileReader & aReader,const CFB::COMPOUND_FILE_ENTRY * aEntry)2540 void ALTIUM_PCB::ParseTracks6Data( const CFB::CompoundFileReader& aReader,
2541                                    const CFB::COMPOUND_FILE_ENTRY* aEntry )
2542 {
2543     if( m_progressReporter )
2544         m_progressReporter->Report( _( "Loading tracks..." ) );
2545 
2546     ALTIUM_PARSER reader( aReader, aEntry );
2547 
2548     while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
2549     {
2550         checkpoint();
2551         ATRACK6 elem( reader );
2552 
2553         if( elem.is_polygonoutline || elem.subpolyindex != ALTIUM_POLYGON_NONE )
2554             continue;
2555 
2556         // element in plane is in fact substracted from the plane. Already done by Altium?
2557         //if( IsAltiumLayerAPlane( elem.layer ) )
2558         //    continue;
2559 
2560         PCB_LAYER_ID klayer = GetKicadLayer( elem.layer );
2561 
2562         if( elem.is_keepout || IsAltiumLayerAPlane( elem.layer ) )
2563         {
2564             PCB_SHAPE shape( nullptr, SHAPE_T::SEGMENT );
2565             shape.SetStart( elem.start );
2566             shape.SetEnd( elem.end );
2567             shape.SetWidth( elem.width );
2568 
2569             ZONE* zone = new ZONE( m_board );
2570             m_board->Add( zone, ADD_MODE::APPEND );
2571 
2572             zone->SetFillVersion( 6 );
2573             zone->SetIsRuleArea( true );
2574             zone->SetDoNotAllowTracks( false );
2575             zone->SetDoNotAllowVias( false );
2576             zone->SetDoNotAllowPads( false );
2577             zone->SetDoNotAllowFootprints( false );
2578             zone->SetDoNotAllowCopperPour( true );
2579 
2580             if( elem.layer == ALTIUM_LAYER::MULTI_LAYER )
2581             {
2582                 zone->SetLayer( F_Cu );
2583                 zone->SetLayerSet( LSET::AllCuMask() );
2584             }
2585             else
2586             {
2587                 PCB_LAYER_ID klayer = GetKicadLayer( elem.layer );
2588 
2589                 if( klayer == UNDEFINED_LAYER )
2590                 {
2591                     wxLogWarning( _( "Track keepout found on an Altium layer (%d) with no KiCad "
2592                                      "equivalent. It has been moved to KiCad layer Eco1_User." ),
2593                                   elem.layer );
2594                     klayer = Eco1_User;
2595                 }
2596                 zone->SetLayer( klayer );
2597             }
2598 
2599             shape.TransformShapeWithClearanceToPolygon( *zone->Outline(), klayer, 0, ARC_HIGH_DEF,
2600                                                         ERROR_INSIDE );
2601 
2602             zone->SetBorderDisplayStyle( ZONE_BORDER_DISPLAY_STYLE::DIAGONAL_EDGE,
2603                                          ZONE::GetDefaultHatchPitch(), true );
2604             continue;
2605         }
2606 
2607         if( klayer == UNDEFINED_LAYER )
2608         {
2609             wxLogWarning( _( "Track found on an Altium layer (%d) with no KiCad equivalent. "
2610                              "It has been moved to KiCad layer Eco1_User." ),
2611                           elem.layer );
2612             klayer = Eco1_User;
2613         }
2614 
2615         if( klayer >= F_Cu && klayer <= B_Cu )
2616         {
2617             PCB_TRACK* track = new PCB_TRACK( m_board );
2618             m_board->Add( track, ADD_MODE::APPEND );
2619 
2620             track->SetStart( elem.start );
2621             track->SetEnd( elem.end );
2622             track->SetWidth( elem.width );
2623             track->SetLayer( klayer );
2624             track->SetNetCode( GetNetCode( elem.net ) );
2625         }
2626         else
2627         {
2628             PCB_SHAPE* shape = HelperCreateAndAddShape( elem.component );
2629             shape->SetShape( SHAPE_T::SEGMENT );
2630             shape->SetStart( elem.start );
2631             shape->SetEnd( elem.end );
2632             shape->SetWidth( elem.width );
2633             shape->SetLayer( klayer );
2634             HelperShapeSetLocalCoord( shape, elem.component );
2635         }
2636 
2637         reader.SkipSubrecord();
2638     }
2639 
2640     if( reader.GetRemainingBytes() != 0 )
2641     {
2642         THROW_IO_ERROR( "Tracks6 stream is not fully parsed" );
2643     }
2644 }
2645 
ParseWideStrings6Data(const CFB::CompoundFileReader & aReader,const CFB::COMPOUND_FILE_ENTRY * aEntry)2646 void ALTIUM_PCB::ParseWideStrings6Data( const CFB::CompoundFileReader&  aReader,
2647                                         const CFB::COMPOUND_FILE_ENTRY* aEntry )
2648 {
2649     if( m_progressReporter )
2650         m_progressReporter->Report( _( "Loading unicode strings..." ) );
2651 
2652     ALTIUM_PARSER reader( aReader, aEntry );
2653 
2654     m_unicodeStrings = reader.ReadWideStringTable();
2655 
2656     if( reader.GetRemainingBytes() != 0 )
2657         THROW_IO_ERROR( "WideStrings6 stream is not fully parsed" );
2658 }
2659 
ParseTexts6Data(const CFB::CompoundFileReader & aReader,const CFB::COMPOUND_FILE_ENTRY * aEntry)2660 void ALTIUM_PCB::ParseTexts6Data( const CFB::CompoundFileReader& aReader,
2661                                   const CFB::COMPOUND_FILE_ENTRY* aEntry )
2662 {
2663     if( m_progressReporter )
2664         m_progressReporter->Report( _( "Loading text..." ) );
2665 
2666     ALTIUM_PARSER reader( aReader, aEntry );
2667 
2668     while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
2669     {
2670         checkpoint();
2671         ATEXT6 elem( reader, m_unicodeStrings );
2672 
2673         if( elem.fonttype == ALTIUM_TEXT_TYPE::BARCODE )
2674         {
2675             wxLogError( _( "Ignored barcode on Altium layer %d (not yet supported)." ),
2676                         elem.layer );
2677             continue;
2678         }
2679 
2680         // TODO: better approach to select if item belongs to a FOOTPRINT
2681         EDA_TEXT*   tx  = nullptr;
2682         BOARD_ITEM* itm = nullptr;
2683 
2684         if( elem.component == ALTIUM_COMPONENT_NONE )
2685         {
2686             PCB_TEXT* pcbText = new PCB_TEXT( m_board );
2687             tx = pcbText;
2688             itm = pcbText;
2689             m_board->Add( pcbText, ADD_MODE::APPEND );
2690         }
2691         else
2692         {
2693             if( m_components.size() <= elem.component )
2694             {
2695                 THROW_IO_ERROR( wxString::Format( "Texts6 stream tries to access component id %d "
2696                                                   "of %d existing components",
2697                                                   elem.component,
2698                                                   m_components.size() ) );
2699             }
2700 
2701             FOOTPRINT* footprint = m_components.at( elem.component );
2702             FP_TEXT*   fpText;
2703 
2704             if( elem.isDesignator )
2705             {
2706                 fpText = &footprint->Reference();
2707             }
2708             else if( elem.isComment )
2709             {
2710                 fpText = &footprint->Value();
2711             }
2712             else
2713             {
2714                 fpText = new FP_TEXT( footprint );
2715                 footprint->Add( fpText, ADD_MODE::APPEND );
2716             }
2717 
2718             fpText->SetKeepUpright( false );
2719 
2720             tx  = fpText;
2721             itm = fpText;
2722         }
2723 
2724         wxString trimmedText = elem.text.Trim();
2725         if( !elem.isDesignator && trimmedText.CmpNoCase( ".Designator" ) == 0 )
2726         {
2727             tx->SetText( "${REFERENCE}" );
2728         }
2729         else if( !elem.isComment && trimmedText.CmpNoCase( ".Comment" ) == 0 )
2730         {
2731             tx->SetText( "${VALUE}" );
2732         }
2733         else if( trimmedText.CmpNoCase( ".Layer_Name" ) == 0 )
2734         {
2735             tx->SetText( "${LAYER}" );
2736         }
2737         else
2738         {
2739             tx->SetText( elem.text );
2740         }
2741 
2742         itm->SetPosition( elem.position );
2743         tx->SetTextAngle( elem.rotation * 10. );
2744 
2745         if( elem.component != ALTIUM_COMPONENT_NONE )
2746         {
2747             FP_TEXT* fpText = dynamic_cast<FP_TEXT*>( tx );
2748 
2749             if( fpText )
2750             {
2751                 FOOTPRINT* parentFootprint = static_cast<FOOTPRINT*>( fpText->GetParent() );
2752                 double     orientation     = parentFootprint->GetOrientation();
2753 
2754                 fpText->SetTextAngle( fpText->GetTextAngle() - orientation );
2755                 fpText->SetLocalCoord();
2756             }
2757         }
2758 
2759         PCB_LAYER_ID klayer = GetKicadLayer( elem.layer );
2760 
2761         if( klayer == UNDEFINED_LAYER )
2762         {
2763             wxLogWarning( _( "Text found on an Altium layer (%d) with no KiCad equivalent. "
2764                              "It has been moved to KiCad layer Eco1_User." ),
2765                           elem.layer );
2766             klayer = Eco1_User;
2767         }
2768 
2769         itm->SetLayer( klayer );
2770 
2771         if( elem.fonttype == ALTIUM_TEXT_TYPE::TRUETYPE )
2772         {
2773             // TODO: why is this required? Somehow, truetype size is calculated differently
2774             tx->SetTextSize( wxSize( elem.height / 2, elem.height / 2 ) );
2775         }
2776         else
2777         {
2778             tx->SetTextSize( wxSize( elem.height, elem.height ) ); // TODO: parse text width
2779         }
2780 
2781         tx->SetTextThickness( elem.strokewidth );
2782         tx->SetBold( elem.isBold );
2783         tx->SetItalic( elem.isItalic );
2784         tx->SetMirrored( elem.isMirrored );
2785 
2786         if( elem.isDesignator || elem.isComment ) // That's just a bold assumption
2787         {
2788             tx->SetHorizJustify( EDA_TEXT_HJUSTIFY_T::GR_TEXT_HJUSTIFY_LEFT );
2789             tx->SetVertJustify( EDA_TEXT_VJUSTIFY_T::GR_TEXT_VJUSTIFY_BOTTOM );
2790         }
2791         else
2792         {
2793             switch( elem.textposition )
2794             {
2795             case ALTIUM_TEXT_POSITION::LEFT_TOP:
2796             case ALTIUM_TEXT_POSITION::LEFT_CENTER:
2797             case ALTIUM_TEXT_POSITION::LEFT_BOTTOM:
2798                 tx->SetHorizJustify( EDA_TEXT_HJUSTIFY_T::GR_TEXT_HJUSTIFY_LEFT );
2799                 break;
2800             case ALTIUM_TEXT_POSITION::CENTER_TOP:
2801             case ALTIUM_TEXT_POSITION::CENTER_CENTER:
2802             case ALTIUM_TEXT_POSITION::CENTER_BOTTOM:
2803                 tx->SetHorizJustify( EDA_TEXT_HJUSTIFY_T::GR_TEXT_HJUSTIFY_CENTER );
2804                 break;
2805             case ALTIUM_TEXT_POSITION::RIGHT_TOP:
2806             case ALTIUM_TEXT_POSITION::RIGHT_CENTER:
2807             case ALTIUM_TEXT_POSITION::RIGHT_BOTTOM:
2808                 tx->SetHorizJustify( EDA_TEXT_HJUSTIFY_T::GR_TEXT_HJUSTIFY_RIGHT );
2809                 break;
2810             default:
2811                 wxLogError( "Unexpected horizontal Text Position. This should never happen." );
2812                 break;
2813             }
2814 
2815             switch( elem.textposition )
2816             {
2817             case ALTIUM_TEXT_POSITION::LEFT_TOP:
2818             case ALTIUM_TEXT_POSITION::CENTER_TOP:
2819             case ALTIUM_TEXT_POSITION::RIGHT_TOP:
2820                 tx->SetVertJustify( EDA_TEXT_VJUSTIFY_T::GR_TEXT_VJUSTIFY_TOP );
2821                 break;
2822             case ALTIUM_TEXT_POSITION::LEFT_CENTER:
2823             case ALTIUM_TEXT_POSITION::CENTER_CENTER:
2824             case ALTIUM_TEXT_POSITION::RIGHT_CENTER:
2825                 tx->SetVertJustify( EDA_TEXT_VJUSTIFY_T::GR_TEXT_VJUSTIFY_CENTER );
2826                 break;
2827             case ALTIUM_TEXT_POSITION::LEFT_BOTTOM:
2828             case ALTIUM_TEXT_POSITION::CENTER_BOTTOM:
2829             case ALTIUM_TEXT_POSITION::RIGHT_BOTTOM:
2830                 tx->SetVertJustify( EDA_TEXT_VJUSTIFY_T::GR_TEXT_VJUSTIFY_BOTTOM );
2831                 break;
2832             default:
2833                 wxLogError( "Unexpected vertical text position. This should never happen." );
2834                 break;
2835             }
2836         }
2837     }
2838 
2839     if( reader.GetRemainingBytes() != 0 )
2840     {
2841         THROW_IO_ERROR( "Texts6 stream is not fully parsed" );
2842     }
2843 }
2844 
ParseFills6Data(const CFB::CompoundFileReader & aReader,const CFB::COMPOUND_FILE_ENTRY * aEntry)2845 void ALTIUM_PCB::ParseFills6Data( const CFB::CompoundFileReader& aReader,
2846                                   const CFB::COMPOUND_FILE_ENTRY* aEntry )
2847 {
2848     if( m_progressReporter )
2849         m_progressReporter->Report( _( "Loading rectangles..." ) );
2850 
2851     ALTIUM_PARSER reader( aReader, aEntry );
2852 
2853     while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
2854     {
2855         checkpoint();
2856         AFILL6 elem( reader );
2857 
2858         wxPoint p11( elem.pos1.x, elem.pos1.y );
2859         wxPoint p12( elem.pos1.x, elem.pos2.y );
2860         wxPoint p22( elem.pos2.x, elem.pos2.y );
2861         wxPoint p21( elem.pos2.x, elem.pos1.y );
2862 
2863         wxPoint center( ( elem.pos1.x + elem.pos2.x ) / 2, ( elem.pos1.y + elem.pos2.y ) / 2 );
2864 
2865         PCB_LAYER_ID klayer = GetKicadLayer( elem.layer );
2866 
2867         if( klayer == UNDEFINED_LAYER )
2868         {
2869             wxLogWarning( _( "Fill found on an Altium layer (%d) with no KiCad equivalent. "
2870                              "It has been moved to KiCad layer Eco1_User." ),
2871                           elem.layer );
2872             klayer = Eco1_User;
2873         }
2874 
2875         if( elem.is_keepout || elem.net != ALTIUM_NET_UNCONNECTED )
2876         {
2877             ZONE* zone = new ZONE( m_board );
2878             m_board->Add( zone, ADD_MODE::APPEND );
2879 
2880             zone->SetFillVersion( 6 );
2881             zone->SetNetCode( GetNetCode( elem.net ) );
2882             zone->SetLayer( klayer );
2883             zone->SetPosition( elem.pos1 );
2884             zone->SetPriority( 1000 );
2885 
2886             const int outlineIdx = -1; // this is the id of the copper zone main outline
2887             zone->AppendCorner( p11, outlineIdx );
2888             zone->AppendCorner( p12, outlineIdx );
2889             zone->AppendCorner( p22, outlineIdx );
2890             zone->AppendCorner( p21, outlineIdx );
2891 
2892             // should be correct?
2893             zone->SetLocalClearance( 0 );
2894             zone->SetPadConnection( ZONE_CONNECTION::FULL );
2895 
2896             if( elem.is_keepout )
2897             {
2898                 zone->SetIsRuleArea( true );
2899                 zone->SetDoNotAllowTracks( false );
2900                 zone->SetDoNotAllowVias( false );
2901                 zone->SetDoNotAllowPads( false );
2902                 zone->SetDoNotAllowFootprints( false );
2903                 zone->SetDoNotAllowCopperPour( true );
2904             }
2905 
2906             if( elem.rotation != 0. )
2907                 zone->Rotate( center, elem.rotation * 10 );
2908 
2909             zone->SetBorderDisplayStyle( ZONE_BORDER_DISPLAY_STYLE::DIAGONAL_EDGE,
2910                                          ZONE::GetDefaultHatchPitch(), true );
2911         }
2912         else
2913         {
2914             PCB_SHAPE* shape = new PCB_SHAPE( m_board, SHAPE_T::POLY );
2915             m_board->Add( shape, ADD_MODE::APPEND );
2916             shape->SetFilled( true );
2917             shape->SetLayer( klayer );
2918             shape->SetWidth( 0 );
2919 
2920             shape->SetPolyPoints( { p11, p12, p22, p21 } );
2921 
2922             if( elem.rotation != 0. )
2923                 shape->Rotate( center, elem.rotation * 10 );
2924         }
2925     }
2926 
2927     if( reader.GetRemainingBytes() != 0 )
2928     {
2929         THROW_IO_ERROR( "Fills6 stream is not fully parsed" );
2930     }
2931 }
2932