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