KiCad PCB EDA Suite
Loading...
Searching...
No Matches
altium_pcb.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright (C) 2019-2020 Thomas Pointhuber <[email protected]>
5 * Copyright The 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"
30
31#include <board.h>
33#include <layer_range.h>
34#include <pcb_dimension.h>
35#include <pad.h>
36#include <pcb_shape.h>
37#include <pcb_text.h>
38#include <pcb_textbox.h>
39#include <pcb_track.h>
40#include <core/profile.h>
41#include <string_utils.h>
42#include <tools/pad_tool.h>
43#include <zone.h>
44
46
47#include <compoundfilereader.h>
49#include <font/outline_font.h>
50#include <project.h>
51#include <reporter.h>
52#include <trigo.h>
53#include <utf.h>
54#include <wx/docview.h>
55#include <wx/log.h>
56#include <wx/mstream.h>
57#include <wx/wfstream.h>
58#include <wx/zstream.h>
59#include <progress_reporter.h>
60#include <magic_enum.hpp>
61
62
63constexpr double BOLD_FACTOR = 1.75; // CSS font-weight-normal is 400; bold is 700
64
65
67{
68 return ( aLayer >= ALTIUM_LAYER::TOP_LAYER && aLayer <= ALTIUM_LAYER::BOTTOM_LAYER )
69 || aLayer == ALTIUM_LAYER::MULTI_LAYER; // TODO: add IsAltiumLayerAPlane?
70}
71
72
74{
76}
77
78FOOTPRINT* ALTIUM_PCB::HelperGetFootprint( uint16_t aComponent ) const
79{
80 if( aComponent == ALTIUM_COMPONENT_NONE || m_components.size() <= aComponent )
81 {
82 THROW_IO_ERROR( wxString::Format( wxT( "Component creator tries to access component id %u "
83 "of %u existing components" ),
84 (unsigned)aComponent, (unsigned)m_components.size() ) );
85 }
86
87 return m_components.at( aComponent );
88}
89
90
92 const std::vector<ALTIUM_VERTICE>& aVertices )
93{
94 for( const ALTIUM_VERTICE& vertex : aVertices )
95 {
96 if( vertex.isRound )
97 {
98 EDA_ANGLE angle( vertex.endangle - vertex.startangle, DEGREES_T );
99 angle.Normalize();
100
101 double startradiant = DEG2RAD( vertex.startangle );
102 double endradiant = DEG2RAD( vertex.endangle );
103 VECTOR2I arcStartOffset = VECTOR2I( KiROUND( std::cos( startradiant ) * vertex.radius ),
104 -KiROUND( std::sin( startradiant ) * vertex.radius ) );
105
106 VECTOR2I arcEndOffset = VECTOR2I( KiROUND( std::cos( endradiant ) * vertex.radius ),
107 -KiROUND( std::sin( endradiant ) * vertex.radius ) );
108
109 VECTOR2I arcStart = vertex.center + arcStartOffset;
110 VECTOR2I arcEnd = vertex.center + arcEndOffset;
111
112 bool isShort = arcStart.Distance( arcEnd ) < pcbIUScale.mmToIU( 0.001 )
113 || angle.AsDegrees() < 0.2;
114
115 if( arcStart.Distance( vertex.position )
116 < arcEnd.Distance( vertex.position ) )
117 {
118 if( !isShort )
119 {
120 aLine.Append( SHAPE_ARC( vertex.center, arcStart, -angle ) );
121 }
122 else
123 {
124 aLine.Append( arcStart );
125 aLine.Append( arcEnd );
126 }
127 }
128 else
129 {
130 if( !isShort )
131 {
132 aLine.Append( SHAPE_ARC( vertex.center, arcEnd, angle ) );
133 }
134 else
135 {
136 aLine.Append( arcEnd );
137 aLine.Append( arcStart );
138 }
139 }
140 }
141 else
142 {
143 aLine.Append( vertex.position );
144 }
145 }
146
147 aLine.SetClosed( true );
148}
149
150
152{
153 auto override = m_layermap.find( aAltiumLayer );
154
155 if( override != m_layermap.end() )
156 {
157 return override->second;
158 }
159
160 switch( aAltiumLayer )
161 {
162 case ALTIUM_LAYER::UNKNOWN: return UNDEFINED_LAYER;
163
164 case ALTIUM_LAYER::TOP_LAYER: return F_Cu;
165 case ALTIUM_LAYER::MID_LAYER_1: return In1_Cu;
166 case ALTIUM_LAYER::MID_LAYER_2: return In2_Cu;
167 case ALTIUM_LAYER::MID_LAYER_3: return In3_Cu;
168 case ALTIUM_LAYER::MID_LAYER_4: return In4_Cu;
169 case ALTIUM_LAYER::MID_LAYER_5: return In5_Cu;
170 case ALTIUM_LAYER::MID_LAYER_6: return In6_Cu;
171 case ALTIUM_LAYER::MID_LAYER_7: return In7_Cu;
172 case ALTIUM_LAYER::MID_LAYER_8: return In8_Cu;
173 case ALTIUM_LAYER::MID_LAYER_9: return In9_Cu;
174 case ALTIUM_LAYER::MID_LAYER_10: return In10_Cu;
175 case ALTIUM_LAYER::MID_LAYER_11: return In11_Cu;
176 case ALTIUM_LAYER::MID_LAYER_12: return In12_Cu;
177 case ALTIUM_LAYER::MID_LAYER_13: return In13_Cu;
178 case ALTIUM_LAYER::MID_LAYER_14: return In14_Cu;
179 case ALTIUM_LAYER::MID_LAYER_15: return In15_Cu;
180 case ALTIUM_LAYER::MID_LAYER_16: return In16_Cu;
181 case ALTIUM_LAYER::MID_LAYER_17: return In17_Cu;
182 case ALTIUM_LAYER::MID_LAYER_18: return In18_Cu;
183 case ALTIUM_LAYER::MID_LAYER_19: return In19_Cu;
184 case ALTIUM_LAYER::MID_LAYER_20: return In20_Cu;
185 case ALTIUM_LAYER::MID_LAYER_21: return In21_Cu;
186 case ALTIUM_LAYER::MID_LAYER_22: return In22_Cu;
187 case ALTIUM_LAYER::MID_LAYER_23: return In23_Cu;
188 case ALTIUM_LAYER::MID_LAYER_24: return In24_Cu;
189 case ALTIUM_LAYER::MID_LAYER_25: return In25_Cu;
190 case ALTIUM_LAYER::MID_LAYER_26: return In26_Cu;
191 case ALTIUM_LAYER::MID_LAYER_27: return In27_Cu;
192 case ALTIUM_LAYER::MID_LAYER_28: return In28_Cu;
193 case ALTIUM_LAYER::MID_LAYER_29: return In29_Cu;
194 case ALTIUM_LAYER::MID_LAYER_30: return In30_Cu;
195 case ALTIUM_LAYER::BOTTOM_LAYER: return B_Cu;
196
197 case ALTIUM_LAYER::TOP_OVERLAY: return F_SilkS;
198 case ALTIUM_LAYER::BOTTOM_OVERLAY: return B_SilkS;
199 case ALTIUM_LAYER::TOP_PASTE: return F_Paste;
200 case ALTIUM_LAYER::BOTTOM_PASTE: return B_Paste;
201 case ALTIUM_LAYER::TOP_SOLDER: return F_Mask;
202 case ALTIUM_LAYER::BOTTOM_SOLDER: return B_Mask;
203
204 case ALTIUM_LAYER::INTERNAL_PLANE_1: return UNDEFINED_LAYER;
205 case ALTIUM_LAYER::INTERNAL_PLANE_2: return UNDEFINED_LAYER;
206 case ALTIUM_LAYER::INTERNAL_PLANE_3: return UNDEFINED_LAYER;
207 case ALTIUM_LAYER::INTERNAL_PLANE_4: return UNDEFINED_LAYER;
208 case ALTIUM_LAYER::INTERNAL_PLANE_5: return UNDEFINED_LAYER;
209 case ALTIUM_LAYER::INTERNAL_PLANE_6: return UNDEFINED_LAYER;
210 case ALTIUM_LAYER::INTERNAL_PLANE_7: return UNDEFINED_LAYER;
211 case ALTIUM_LAYER::INTERNAL_PLANE_8: return UNDEFINED_LAYER;
212 case ALTIUM_LAYER::INTERNAL_PLANE_9: return UNDEFINED_LAYER;
213 case ALTIUM_LAYER::INTERNAL_PLANE_10: return UNDEFINED_LAYER;
214 case ALTIUM_LAYER::INTERNAL_PLANE_11: return UNDEFINED_LAYER;
215 case ALTIUM_LAYER::INTERNAL_PLANE_12: return UNDEFINED_LAYER;
216 case ALTIUM_LAYER::INTERNAL_PLANE_13: return UNDEFINED_LAYER;
217 case ALTIUM_LAYER::INTERNAL_PLANE_14: return UNDEFINED_LAYER;
218 case ALTIUM_LAYER::INTERNAL_PLANE_15: return UNDEFINED_LAYER;
219 case ALTIUM_LAYER::INTERNAL_PLANE_16: return UNDEFINED_LAYER;
220
221 case ALTIUM_LAYER::DRILL_GUIDE: return Dwgs_User;
222 case ALTIUM_LAYER::KEEP_OUT_LAYER: return Margin;
223
224 case ALTIUM_LAYER::MECHANICAL_1: return User_1; //Edge_Cuts;
225 case ALTIUM_LAYER::MECHANICAL_2: return User_2;
226 case ALTIUM_LAYER::MECHANICAL_3: return User_3;
227 case ALTIUM_LAYER::MECHANICAL_4: return User_4;
228 case ALTIUM_LAYER::MECHANICAL_5: return User_5;
229 case ALTIUM_LAYER::MECHANICAL_6: return User_6;
230 case ALTIUM_LAYER::MECHANICAL_7: return User_7;
231 case ALTIUM_LAYER::MECHANICAL_8: return User_8;
232 case ALTIUM_LAYER::MECHANICAL_9: return User_9;
233 case ALTIUM_LAYER::MECHANICAL_10: return User_10;
234 case ALTIUM_LAYER::MECHANICAL_11: return User_11; //Eco1 is used for unknown elements
235 case ALTIUM_LAYER::MECHANICAL_12: return F_Fab;
236 case ALTIUM_LAYER::MECHANICAL_13: return B_Fab; // Don't use courtyard layers for other purposes
237 case ALTIUM_LAYER::MECHANICAL_14: return User_12;
238 case ALTIUM_LAYER::MECHANICAL_15: return User_13;
239 case ALTIUM_LAYER::MECHANICAL_16: return User_14;
240
241 case ALTIUM_LAYER::DRILL_DRAWING: return Dwgs_User;
242 case ALTIUM_LAYER::MULTI_LAYER: return UNDEFINED_LAYER;
243 case ALTIUM_LAYER::CONNECTIONS: return UNDEFINED_LAYER;
244 case ALTIUM_LAYER::BACKGROUND: return UNDEFINED_LAYER;
245 case ALTIUM_LAYER::DRC_ERROR_MARKERS: return UNDEFINED_LAYER;
246 case ALTIUM_LAYER::SELECTIONS: return UNDEFINED_LAYER;
247 case ALTIUM_LAYER::VISIBLE_GRID_1: return UNDEFINED_LAYER;
248 case ALTIUM_LAYER::VISIBLE_GRID_2: return UNDEFINED_LAYER;
249 case ALTIUM_LAYER::PAD_HOLES: return UNDEFINED_LAYER;
250 case ALTIUM_LAYER::VIA_HOLES: return UNDEFINED_LAYER;
251
252 default: return UNDEFINED_LAYER;
253 }
254}
255
256
257std::vector<PCB_LAYER_ID> ALTIUM_PCB::GetKicadLayersToIterate( ALTIUM_LAYER aAltiumLayer ) const
258{
259 static std::set<ALTIUM_LAYER> altiumLayersWithWarning;
260
261 if( aAltiumLayer == ALTIUM_LAYER::MULTI_LAYER || aAltiumLayer == ALTIUM_LAYER::KEEP_OUT_LAYER )
262 {
263 int layerCount = m_board ? m_board->GetCopperLayerCount() : 32;
264 std::vector<PCB_LAYER_ID> layers;
265 layers.reserve( layerCount );
266
267 for( PCB_LAYER_ID layer : LAYER_RANGE( F_Cu, B_Cu, layerCount ) )
268 {
269 if( !m_board || m_board->IsLayerEnabled( layer ) )
270 layers.emplace_back( layer );
271 }
272
273 return layers;
274 }
275
276 PCB_LAYER_ID klayer = GetKicadLayer( aAltiumLayer );
277
278 if( klayer == UNDEFINED_LAYER )
279 {
280 auto it = m_layerNames.find( aAltiumLayer );
281 wxString layerName = it != m_layerNames.end() ? it->second : wxString::Format( wxT( "(%d)" ),
282 (int) aAltiumLayer );
283
284 if( m_reporter && altiumLayersWithWarning.insert( aAltiumLayer ).second )
285 {
286 m_reporter->Report( wxString::Format(
287 _( "Altium layer %s has no KiCad equivalent. It has been moved to KiCad "
288 "layer Eco1_User." ), layerName ), RPT_SEVERITY_INFO );
289 }
290
291 klayer = Eco1_User;
293 }
294
295 return { klayer };
296}
297
298
299ALTIUM_PCB::ALTIUM_PCB( BOARD* aBoard, PROGRESS_REPORTER* aProgressReporter,
300 LAYER_MAPPING_HANDLER& aHandler, REPORTER* aReporter,
301 const wxString& aLibrary, const wxString& aFootprintName )
302{
303 m_board = aBoard;
304 m_progressReporter = aProgressReporter;
305 m_layerMappingHandler = aHandler;
306 m_reporter = aReporter;
307 m_doneCount = 0;
309 m_totalCount = 0;
311 m_library = aLibrary;
312 m_footprintName = aFootprintName;
313}
314
316{
317}
318
320{
321 const unsigned PROGRESS_DELTA = 250;
322
324 {
325 if( ++m_doneCount > m_lastProgressCount + PROGRESS_DELTA )
326 {
328 / std::max( 1U, m_totalCount ) );
329
331 THROW_IO_ERROR( _( "Open cancelled by user." ) );
332
334 }
335 }
336}
337
339 const std::map<ALTIUM_PCB_DIR, std::string>& aFileMapping )
340{
341 // this vector simply declares in which order which functions to call.
342 const std::vector<std::tuple<bool, ALTIUM_PCB_DIR, PARSE_FUNCTION_POINTER_fp>> parserOrder = {
343 { true, ALTIUM_PCB_DIR::FILE_HEADER,
344 [this]( const ALTIUM_PCB_COMPOUND_FILE& aFile, auto fileHeader )
345 {
346 this->ParseFileHeader( aFile, fileHeader );
347 } },
348 { true, ALTIUM_PCB_DIR::BOARD6,
349 [this]( const ALTIUM_PCB_COMPOUND_FILE& aFile, auto fileHeader )
350 {
351 this->ParseBoard6Data( aFile, fileHeader );
352 } },
353 { false, ALTIUM_PCB_DIR::EXTENDPRIMITIVEINFORMATION,
354 [this]( const ALTIUM_PCB_COMPOUND_FILE& aFile, auto fileHeader )
355 {
356 this->ParseExtendedPrimitiveInformationData( aFile, fileHeader );
357 } },
358 { true, ALTIUM_PCB_DIR::COMPONENTS6,
359 [this]( const ALTIUM_PCB_COMPOUND_FILE& aFile, auto fileHeader )
360 {
361 this->ParseComponents6Data( aFile, fileHeader );
362 } },
363 { false, ALTIUM_PCB_DIR::MODELS,
364 [this, aFileMapping]( const ALTIUM_PCB_COMPOUND_FILE& aFile, auto fileHeader )
365 {
366 std::vector<std::string> dir{ aFileMapping.at( ALTIUM_PCB_DIR::MODELS ) };
367 this->ParseModelsData( aFile, fileHeader, dir );
368 } },
369 { true, ALTIUM_PCB_DIR::COMPONENTBODIES6,
370 [this]( const ALTIUM_PCB_COMPOUND_FILE& aFile, auto fileHeader )
371 {
372 this->ParseComponentsBodies6Data( aFile, fileHeader );
373 } },
374 { true, ALTIUM_PCB_DIR::NETS6,
375 [this]( const ALTIUM_PCB_COMPOUND_FILE& aFile, auto fileHeader )
376 {
377 this->ParseNets6Data( aFile, fileHeader );
378 } },
379 { true, ALTIUM_PCB_DIR::CLASSES6,
380 [this]( const ALTIUM_PCB_COMPOUND_FILE& aFile, auto fileHeader )
381 {
382 this->ParseClasses6Data( aFile, fileHeader );
383 } },
384 { true, ALTIUM_PCB_DIR::RULES6,
385 [this]( const ALTIUM_PCB_COMPOUND_FILE& aFile, auto fileHeader )
386 {
387 this->ParseRules6Data( aFile, fileHeader );
388 } },
389 { true, ALTIUM_PCB_DIR::DIMENSIONS6,
390 [this]( const ALTIUM_PCB_COMPOUND_FILE& aFile, auto fileHeader )
391 {
392 this->ParseDimensions6Data( aFile, fileHeader );
393 } },
394 { true, ALTIUM_PCB_DIR::POLYGONS6,
395 [this]( const ALTIUM_PCB_COMPOUND_FILE& aFile, auto fileHeader )
396 {
397 this->ParsePolygons6Data( aFile, fileHeader );
398 } },
399 { true, ALTIUM_PCB_DIR::ARCS6,
400 [this]( const ALTIUM_PCB_COMPOUND_FILE& aFile, auto fileHeader )
401 {
402 this->ParseArcs6Data( aFile, fileHeader );
403 } },
404 { true, ALTIUM_PCB_DIR::PADS6,
405 [this]( const ALTIUM_PCB_COMPOUND_FILE& aFile, auto fileHeader )
406 {
407 this->ParsePads6Data( aFile, fileHeader );
408 } },
409 { true, ALTIUM_PCB_DIR::VIAS6,
410 [this]( const ALTIUM_PCB_COMPOUND_FILE& aFile, auto fileHeader )
411 {
412 this->ParseVias6Data( aFile, fileHeader );
413 } },
414 { true, ALTIUM_PCB_DIR::TRACKS6,
415 [this]( const ALTIUM_PCB_COMPOUND_FILE& aFile, auto fileHeader )
416 {
417 this->ParseTracks6Data( aFile, fileHeader );
418 } },
419 { false, ALTIUM_PCB_DIR::WIDESTRINGS6,
420 [this]( const ALTIUM_PCB_COMPOUND_FILE& aFile, auto fileHeader )
421 {
422 this->ParseWideStrings6Data( aFile, fileHeader );
423 } },
424 { true, ALTIUM_PCB_DIR::TEXTS6,
425 [this]( const ALTIUM_PCB_COMPOUND_FILE& aFile, auto fileHeader )
426 {
427 this->ParseTexts6Data( aFile, fileHeader );
428 } },
429 { true, ALTIUM_PCB_DIR::FILLS6,
430 [this]( const ALTIUM_PCB_COMPOUND_FILE& aFile, auto fileHeader )
431 {
432 this->ParseFills6Data( aFile, fileHeader );
433 } },
434 { false, ALTIUM_PCB_DIR::BOARDREGIONS,
435 [this]( const ALTIUM_PCB_COMPOUND_FILE& aFile, auto fileHeader )
436 {
437 this->ParseBoardRegionsData( aFile, fileHeader );
438 } },
439 { true, ALTIUM_PCB_DIR::SHAPEBASEDREGIONS6,
440 [this]( const ALTIUM_PCB_COMPOUND_FILE& aFile, auto fileHeader )
441 {
442 this->ParseShapeBasedRegions6Data( aFile, fileHeader );
443 } },
444 { true, ALTIUM_PCB_DIR::REGIONS6,
445 [this]( const ALTIUM_PCB_COMPOUND_FILE& aFile, auto fileHeader )
446 {
447 this->ParseRegions6Data( aFile, fileHeader );
448 } }
449 };
450
451 if( m_progressReporter != nullptr )
452 {
453 // Count number of records we will read for the progress reporter
454 for( const std::tuple<bool, ALTIUM_PCB_DIR, PARSE_FUNCTION_POINTER_fp>& cur : parserOrder )
455 {
456 bool isRequired;
459 std::tie( isRequired, directory, fp ) = cur;
460
461 if( directory == ALTIUM_PCB_DIR::FILE_HEADER )
462 continue;
463
464 const auto& mappedDirectory = aFileMapping.find( directory );
465
466 if( mappedDirectory == aFileMapping.end() )
467 continue;
468
469 const std::vector<std::string> mappedFile{ mappedDirectory->second, "Header" };
470 const CFB::COMPOUND_FILE_ENTRY* file = altiumPcbFile.FindStream( mappedFile );
471
472 if( file == nullptr )
473 continue;
474
475 ALTIUM_BINARY_PARSER reader( altiumPcbFile, file );
476 uint32_t numOfRecords = reader.Read<uint32_t>();
477
478 if( reader.HasParsingError() )
479 {
480 if( m_reporter )
481 {
482 m_reporter->Report( wxString::Format( _( "'%s' was not parsed correctly." ),
483 FormatPath( mappedFile ) ),
485 }
486
487 continue;
488 }
489
490 m_totalCount += numOfRecords;
491
492 if( reader.GetRemainingBytes() != 0 )
493 {
494 if( m_reporter )
495 {
496 m_reporter->Report( wxString::Format( _( "'%s' was not fully parsed." ),
497 FormatPath( mappedFile ) ),
499 }
500
501 continue;
502 }
503 }
504 }
505
506 const auto& boardDirectory = aFileMapping.find( ALTIUM_PCB_DIR::BOARD6 );
507
508 if( boardDirectory != aFileMapping.end() )
509 {
510 std::vector<std::string> mappedFile{ boardDirectory->second, "Data" };
511
512 const CFB::COMPOUND_FILE_ENTRY* file = altiumPcbFile.FindStream( mappedFile );
513
514 if( !file )
515 {
517 "This file does not appear to be in a valid PCB Binary Version 6.0 format. In "
518 "Altium Designer, "
519 "make sure to save as \"PCB Binary Files (*.PcbDoc)\"." ) );
520 }
521 }
522
523 // Parse data in specified order
524 for( const std::tuple<bool, ALTIUM_PCB_DIR, PARSE_FUNCTION_POINTER_fp>& cur : parserOrder )
525 {
526 bool isRequired;
529 std::tie( isRequired, directory, fp ) = cur;
530
531 const auto& mappedDirectory = aFileMapping.find( directory );
532
533 if( mappedDirectory == aFileMapping.end() )
534 {
535 wxASSERT_MSG( !isRequired, wxString::Format( wxT( "Altium Directory of kind %d was "
536 "expected, but no mapping is "
537 "present in the code" ),
538 directory ) );
539 continue;
540 }
541
542 std::vector<std::string> mappedFile{ mappedDirectory->second };
543
544 if( directory != ALTIUM_PCB_DIR::FILE_HEADER )
545 mappedFile.emplace_back( "Data" );
546
547 const CFB::COMPOUND_FILE_ENTRY* file = altiumPcbFile.FindStream( mappedFile );
548
549 if( file != nullptr )
550 {
551 fp( altiumPcbFile, file );
552 }
553 else if( isRequired )
554 {
555 if( m_reporter )
556 {
557 m_reporter->Report( wxString::Format( _( "File not found: '%s' for directory '%s'." ),
558 FormatPath( mappedFile ),
559 magic_enum::enum_name( directory ) ),
561 }
562 }
563 }
564
565 // fixup zone priorities since Altium stores them in the opposite order
566 for( ZONE* zone : m_polygons )
567 {
568 if( !zone )
569 continue;
570
571 // Altium "fills" - not poured in Altium
572 if( zone->GetAssignedPriority() == 1000 )
573 {
574 // Unlikely, but you never know
575 if( m_highest_pour_index >= 1000 )
576 zone->SetAssignedPriority( m_highest_pour_index + 1 );
577
578 continue;
579 }
580
581 int priority = m_highest_pour_index - zone->GetAssignedPriority();
582
583 zone->SetAssignedPriority( priority >= 0 ? priority : 0 );
584 }
585
586 // change priority of outer zone to zero
587 for( std::pair<const ALTIUM_LAYER, ZONE*>& zone : m_outer_plane )
588 zone.second->SetAssignedPriority( 0 );
589
590 // Simplify and fracture zone fills in case we constructed them from tracks (hatched fill)
591 for( ZONE* zone : m_polygons )
592 {
593 if( !zone )
594 continue;
595
596 for( PCB_LAYER_ID layer : zone->GetLayerSet().Seq() )
597 {
598 if( !zone->HasFilledPolysForLayer( layer ) )
599 continue;
600
601 zone->GetFilledPolysList( layer )->Fracture();
602 }
603 }
604
605 // Altium doesn't appear to store either the dimension value nor the dimensioned object in
606 // the dimension record. (Yes, there is a REFERENCE0OBJECTID, but it doesn't point to the
607 // dimensioned object.) We attempt to plug this gap by finding a colocated arc or circle
608 // and using its radius. If there are more than one such arcs/circles, well, :shrug:.
610 {
611 int radius = 0;
612
613 for( BOARD_ITEM* item : m_board->Drawings() )
614 {
615 if( item->Type() != PCB_SHAPE_T )
616 continue;
617
618 PCB_SHAPE* shape = static_cast<PCB_SHAPE*>( item );
619
620 if( shape->GetShape() != SHAPE_T::ARC && shape->GetShape() != SHAPE_T::CIRCLE )
621 continue;
622
623 if( shape->GetPosition() == dim->GetPosition() )
624 {
625 radius = shape->GetRadius();
626 break;
627 }
628 }
629
630 if( radius == 0 )
631 {
632 for( PCB_TRACK* track : m_board->Tracks() )
633 {
634 if( track->Type() != PCB_ARC_T )
635 continue;
636
637 PCB_ARC* arc = static_cast<PCB_ARC*>( track );
638
639 if( arc->GetCenter() == dim->GetPosition() )
640 {
641 radius = arc->GetRadius();
642 break;
643 }
644 }
645 }
646
647 // Move the radius point onto the circumference
648 VECTOR2I radialLine = dim->GetEnd() - dim->GetStart();
649 int totalLength = radialLine.EuclideanNorm();
650
651 // Enforce a minimum on the radialLine else we won't have enough precision to get the
652 // angle from it.
653 radialLine = radialLine.Resize( std::max( radius, 2 ) );
654 dim->SetEnd( dim->GetStart() + (VECTOR2I) radialLine );
655 dim->SetLeaderLength( totalLength - radius );
656 dim->Update();
657 }
658
659 // center board
661
664
665 int desired_x = ( w - bbbox.GetWidth() ) / 2;
666 int desired_y = ( h - bbbox.GetHeight() ) / 2;
667
668 VECTOR2I movementVector( desired_x - bbbox.GetX(), desired_y - bbbox.GetY() );
669 m_board->Move( movementVector );
670
672 bds.SetAuxOrigin( bds.GetAuxOrigin() + movementVector );
673 bds.SetGridOrigin( bds.GetGridOrigin() + movementVector );
674
676}
677
678
680 const wxString& aFootprintName )
681{
682 std::unique_ptr<FOOTPRINT> footprint = std::make_unique<FOOTPRINT>( m_board );
683
684 // TODO: what should we do with those layers?
685 m_layermap.emplace( ALTIUM_LAYER::MECHANICAL_14, Eco2_User );
686 m_layermap.emplace( ALTIUM_LAYER::MECHANICAL_15, Eco2_User );
687 m_layermap.emplace( ALTIUM_LAYER::MECHANICAL_16, Eco2_User );
688
689 m_unicodeStrings.clear();
691
692 // TODO: WideStrings are stored as parameterMap in the case of footprints, not as binary
693 // std::string unicodeStringsStreamName = aFootprintName.ToStdString() + "\\WideStrings";
694 // const CFB::COMPOUND_FILE_ENTRY* unicodeStringsData = altiumLibFile.FindStream( unicodeStringsStreamName );
695 // if( unicodeStringsData != nullptr )
696 // {
697 // ParseWideStrings6Data( altiumLibFile, unicodeStringsData );
698 // }
699
700 std::tuple<wxString, const CFB::COMPOUND_FILE_ENTRY*> ret =
701 altiumLibFile.FindLibFootprintDirName( aFootprintName );
702
703 wxString fpDirName = std::get<0>( ret );
704 const CFB::COMPOUND_FILE_ENTRY* footprintStream = std::get<1>( ret );
705
706 if( fpDirName.IsEmpty() )
707 {
709 wxString::Format( _( "Footprint directory not found: '%s'." ), aFootprintName ) );
710 }
711
712 const std::vector<std::string> streamName{ fpDirName.ToStdString(), "Data" };
713 const CFB::COMPOUND_FILE_ENTRY* footprintData = altiumLibFile.FindStream( footprintStream, { "Data" } );
714
715 if( footprintData == nullptr )
716 {
717 THROW_IO_ERROR( wxString::Format( _( "File not found: '%s'." ),
718 FormatPath( streamName ) ) );
719 }
720
721 ALTIUM_BINARY_PARSER parser( altiumLibFile, footprintData );
722
724 //wxString footprintName = parser.ReadWxString(); // Not used (single-byte char set)
725 parser.SkipSubrecord();
726
727 LIB_ID fpID = AltiumToKiCadLibID( "", aFootprintName ); // TODO: library name
728 footprint->SetFPID( fpID );
729
730 const std::vector<std::string> parametersStreamName{ fpDirName.ToStdString(),
731 "Parameters" };
732 const CFB::COMPOUND_FILE_ENTRY* parametersData =
733 altiumLibFile.FindStream( footprintStream, { "Parameters" } );
734
735 if( parametersData != nullptr )
736 {
737 ALTIUM_BINARY_PARSER parametersReader( altiumLibFile, parametersData );
738 std::map<wxString, wxString> parameterProperties = parametersReader.ReadProperties();
739 wxString description = ALTIUM_PROPS_UTILS::ReadString( parameterProperties,
740 wxT( "DESCRIPTION" ), wxT( "" ) );
741 footprint->SetLibDescription( description );
742 }
743 else
744 {
745 if( m_reporter )
746 {
747 m_reporter->Report( wxString::Format( _( "File not found: '%s'." ),
748 FormatPath( parametersStreamName ) ),
750 }
751
752 footprint->SetLibDescription( wxT( "" ) );
753 }
754
755 const std::vector<std::string> extendedPrimitiveInformationStreamName{
756 "ExtendedPrimitiveInformation", "Data"
757 };
758 const CFB::COMPOUND_FILE_ENTRY* extendedPrimitiveInformationData =
759 altiumLibFile.FindStream( footprintStream, extendedPrimitiveInformationStreamName );
760
761 if( extendedPrimitiveInformationData != nullptr )
762 ParseExtendedPrimitiveInformationData( altiumLibFile, extendedPrimitiveInformationData );
763
764 footprint->SetReference( wxT( "REF**" ) );
765 footprint->SetValue( aFootprintName );
766 footprint->Reference().SetVisible( true ); // TODO: extract visibility information
767 footprint->Value().SetVisible( true );
768
769 const VECTOR2I defaultTextSize( pcbIUScale.mmToIU( 1.0 ), pcbIUScale.mmToIU( 1.0 ) );
770 const int defaultTextThickness( pcbIUScale.mmToIU( 0.15 ) );
771
772 for( PCB_FIELD* field : footprint->GetFields() )
773 {
774 field->SetTextSize( defaultTextSize );
775 field->SetTextThickness( defaultTextThickness );
776 }
777
778 for( int primitiveIndex = 0; parser.GetRemainingBytes() >= 4; primitiveIndex++ )
779 {
780 ALTIUM_RECORD recordtype = static_cast<ALTIUM_RECORD>( parser.Peek<uint8_t>() );
781
782 switch( recordtype )
783 {
784 case ALTIUM_RECORD::ARC:
785 {
786 AARC6 arc( parser );
787 ConvertArcs6ToFootprintItem( footprint.get(), arc, primitiveIndex, false );
788 break;
789 }
790 case ALTIUM_RECORD::PAD:
791 {
792 APAD6 pad( parser );
793 ConvertPads6ToFootprintItem( footprint.get(), pad );
794 break;
795 }
796 case ALTIUM_RECORD::VIA:
797 {
798 AVIA6 via( parser );
799 ConvertVias6ToFootprintItem( footprint.get(), via );
800 break;
801 }
802 case ALTIUM_RECORD::TRACK:
803 {
804 ATRACK6 track( parser );
805 ConvertTracks6ToFootprintItem( footprint.get(), track, primitiveIndex, false );
806 break;
807 }
808 case ALTIUM_RECORD::TEXT:
809 {
810 ATEXT6 text( parser, m_unicodeStrings );
811 ConvertTexts6ToFootprintItem( footprint.get(), text );
812 break;
813 }
814 case ALTIUM_RECORD::FILL:
815 {
816 AFILL6 fill( parser );
817 ConvertFills6ToFootprintItem( footprint.get(), fill, false );
818 break;
819 }
820 case ALTIUM_RECORD::REGION:
821 {
822 AREGION6 region( parser, false );
823 ConvertShapeBasedRegions6ToFootprintItem( footprint.get(), region, primitiveIndex );
824 break;
825 }
826 case ALTIUM_RECORD::MODEL:
827 {
828 ACOMPONENTBODY6 componentBody( parser );
829 ConvertComponentBody6ToFootprintItem( altiumLibFile, footprint.get(), componentBody );
830 break;
831 }
832 default:
833 THROW_IO_ERROR( wxString::Format( _( "Record of unknown type: '%d'." ), recordtype ) );
834 }
835 }
836
837
838 // Loop over this multiple times to catch pads that are jumpered to each other by multiple shapes
839 for( bool changes = true; changes; )
840 {
841 changes = false;
842
843 alg::for_all_pairs( footprint->Pads().begin(), footprint->Pads().end(),
844 [&changes]( PAD* aPad1, PAD* aPad2 )
845 {
846 if( !( aPad1->GetNumber().IsEmpty() ^ aPad2->GetNumber().IsEmpty() ) )
847 return;
848
849 for( PCB_LAYER_ID layer : aPad1->GetLayerSet().Seq() )
850 {
851 std::shared_ptr<SHAPE> shape1 = aPad1->GetEffectiveShape( layer );
852 std::shared_ptr<SHAPE> shape2 = aPad2->GetEffectiveShape( layer );
853
854 if( shape1->Collide( shape2.get() ) )
855 {
856 if( aPad1->GetNumber().IsEmpty() )
857 aPad1->SetNumber( aPad2->GetNumber() );
858 else
859 aPad2->SetNumber( aPad1->GetNumber() );
860
861 changes = true;
862 }
863 }
864 } );
865 }
866
867 // Auto-position reference and value
868 footprint->AutoPositionFields();
869
870 if( parser.HasParsingError() )
871 {
872 THROW_IO_ERROR( wxString::Format( wxT( "%s stream was not parsed correctly" ),
873 FormatPath( streamName ) ) );
874 }
875
876 if( parser.GetRemainingBytes() != 0 )
877 {
878 THROW_IO_ERROR( wxString::Format( wxT( "%s stream is not fully parsed" ),
879 FormatPath( streamName ) ) );
880 }
881
882 return footprint.release();
883}
884
885int ALTIUM_PCB::GetNetCode( uint16_t aId ) const
886{
887 if( aId == ALTIUM_NET_UNCONNECTED )
888 {
890 }
891 else if( m_altiumToKicadNetcodes.size() < aId )
892 {
893 THROW_IO_ERROR( wxString::Format( wxT( "Netcode with id %d does not exist. Only %d nets "
894 "are known" ),
895 aId, m_altiumToKicadNetcodes.size() ) );
896 }
897 else
898 {
899 return m_altiumToKicadNetcodes[ aId ];
900 }
901}
902
903const ARULE6* ALTIUM_PCB::GetRule( ALTIUM_RULE_KIND aKind, const wxString& aName ) const
904{
905 const auto rules = m_rules.find( aKind );
906
907 if( rules == m_rules.end() )
908 return nullptr;
909
910 for( const ARULE6& rule : rules->second )
911 {
912 if( rule.name == aName )
913 return &rule;
914 }
915
916 return nullptr;
917}
918
920{
921 const auto rules = m_rules.find( aKind );
922
923 if( rules == m_rules.end() )
924 return nullptr;
925
926 for( const ARULE6& rule : rules->second )
927 {
928 if( rule.scope1expr == wxT( "All" ) && rule.scope2expr == wxT( "All" ) )
929 return &rule;
930 }
931
932 return nullptr;
933}
934
936 const CFB::COMPOUND_FILE_ENTRY* aEntry )
937{
938 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
939
941 wxString header = reader.ReadWxString();
942
943 //std::cout << "HEADER: " << header << std::endl; // tells me: PCB 5.0 Binary File
944
945 //reader.SkipSubrecord();
946
947 // TODO: does not seem to work all the time at the moment
948 //if( reader.GetRemainingBytes() != 0 )
949 // THROW_IO_ERROR( "FileHeader stream is not fully parsed" );
950}
951
952
954 const CFB::COMPOUND_FILE_ENTRY* aEntry )
955{
957 m_progressReporter->Report( _( "Loading extended primitive information data..." ) );
958
959 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
960
961 while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
962 {
963 checkpoint();
964 AEXTENDED_PRIMITIVE_INFORMATION elem( reader );
965
967 std::move( elem ) );
968 }
969
970 if( reader.GetRemainingBytes() != 0 )
971 THROW_IO_ERROR( wxT( "ExtendedPrimitiveInformation stream is not fully parsed" ) );
972}
973
974
976 const CFB::COMPOUND_FILE_ENTRY* aEntry )
977{
979 m_progressReporter->Report( _( "Loading board data..." ) );
980
981 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
982
983 checkpoint();
984 ABOARD6 elem( reader );
985
986 if( reader.GetRemainingBytes() != 0 )
987 THROW_IO_ERROR( wxT( "Board6 stream is not fully parsed" ) );
988
991
992 // read layercount from stackup, because LAYERSETSCOUNT is not always correct?!
993 size_t layercount = 0;
994 size_t layerid = static_cast<size_t>( ALTIUM_LAYER::TOP_LAYER );
995
996 while( layerid < elem.stackup.size() && layerid != 0 )
997 {
998 layerid = elem.stackup[ layerid - 1 ].nextId;
999 layercount++;
1000 }
1001
1002 size_t kicadLayercount = ( layercount % 2 == 0 ) ? layercount : layercount + 1;
1003 m_board->SetCopperLayerCount( kicadLayercount );
1004
1005 BOARD_DESIGN_SETTINGS& designSettings = m_board->GetDesignSettings();
1006 BOARD_STACKUP& stackup = designSettings.GetStackupDescriptor();
1007
1008 // create board stackup
1009 stackup.RemoveAll(); // Just to be sure
1010 stackup.BuildDefaultStackupList( &designSettings, layercount );
1011
1012 auto it = stackup.GetList().begin();
1013
1014 // find first copper layer
1015 for( ; it != stackup.GetList().end() && ( *it )->GetType() != BS_ITEM_TYPE_COPPER; ++it )
1016 ;
1017
1018 auto cuLayer = LAYER_RANGE( F_Cu, B_Cu, 32 ).begin();
1019
1020 for( size_t altiumLayerId = static_cast<size_t>( ALTIUM_LAYER::TOP_LAYER );
1021 altiumLayerId < elem.stackup.size() && altiumLayerId != 0;
1022 altiumLayerId = elem.stackup[altiumLayerId - 1].nextId )
1023 {
1024 // array starts with 0, but stackup with 1
1025 ABOARD6_LAYER_STACKUP& layer = elem.stackup.at( altiumLayerId - 1 );
1026
1027 // handle unused layer in case of odd layercount
1028 if( layer.nextId == 0 && layercount != kicadLayercount )
1029 {
1030 m_board->SetLayerName( ( *it )->GetBrdLayerId(), wxT( "[unused]" ) );
1031
1032 if( ( *it )->GetType() != BS_ITEM_TYPE_COPPER )
1033 THROW_IO_ERROR( wxT( "Board6 stream, unexpected item while parsing stackup" ) );
1034
1035 ( *it )->SetThickness( 0 );
1036
1037 ++it;
1038
1039 if( ( *it )->GetType() != BS_ITEM_TYPE_DIELECTRIC )
1040 THROW_IO_ERROR( wxT( "Board6 stream, unexpected item while parsing stackup" ) );
1041
1042 ( *it )->SetThickness( 0, 0 );
1043 ( *it )->SetThicknessLocked( true, 0 );
1044 ++it;
1045 }
1046
1047 m_layermap.insert( { static_cast<ALTIUM_LAYER>( altiumLayerId ), *cuLayer } );
1048 ++cuLayer;
1049
1050 if( ( *it )->GetType() != BS_ITEM_TYPE_COPPER )
1051 THROW_IO_ERROR( wxT( "Board6 stream, unexpected item while parsing stackup" ) );
1052
1053 ( *it )->SetThickness( layer.copperthick );
1054
1055 ALTIUM_LAYER alayer = static_cast<ALTIUM_LAYER>( altiumLayerId );
1056 PCB_LAYER_ID klayer = ( *it )->GetBrdLayerId();
1057
1058 m_board->SetLayerName( klayer, layer.name );
1059
1060 if( layer.copperthick == 0 )
1061 m_board->SetLayerType( klayer, LAYER_T::LT_JUMPER ); // used for things like wirebonding
1062 else if( IsAltiumLayerAPlane( alayer ) )
1063 m_board->SetLayerType( klayer, LAYER_T::LT_POWER );
1064
1065 if( klayer == B_Cu )
1066 {
1067 if( layer.nextId != 0 )
1068 THROW_IO_ERROR( wxT( "Board6 stream, unexpected id while parsing last stackup layer" ) );
1069
1070 // overwrite entry from internal -> bottom
1071 m_layermap[alayer] = B_Cu;
1072 break;
1073 }
1074
1075 ++it;
1076
1077 if( ( *it )->GetType() != BS_ITEM_TYPE_DIELECTRIC )
1078 THROW_IO_ERROR( wxT( "Board6 stream, unexpected item while parsing stackup" ) );
1079
1080 ( *it )->SetThickness( layer.dielectricthick, 0 );
1081 ( *it )->SetMaterial( layer.dielectricmaterial.empty() ?
1082 NotSpecifiedPrm() :
1083 wxString( layer.dielectricmaterial ) );
1084 ( *it )->SetEpsilonR( layer.dielectricconst, 0 );
1085
1086 ++it;
1087 }
1088
1089 remapUnsureLayers( elem.stackup );
1090
1091 // Set name of all non-cu layers
1092 for( size_t altiumLayerId = static_cast<size_t>( ALTIUM_LAYER::TOP_OVERLAY );
1093 altiumLayerId <= static_cast<size_t>( ALTIUM_LAYER::BOTTOM_SOLDER ); altiumLayerId++ )
1094 {
1095 // array starts with 0, but stackup with 1
1096 ABOARD6_LAYER_STACKUP& layer = elem.stackup.at( altiumLayerId - 1 );
1097
1098 ALTIUM_LAYER alayer = static_cast<ALTIUM_LAYER>( altiumLayerId );
1099 PCB_LAYER_ID klayer = GetKicadLayer( alayer );
1100
1101 m_board->SetLayerName( klayer, layer.name );
1102 }
1103
1104 for( size_t altiumLayerId = static_cast<size_t>( ALTIUM_LAYER::MECHANICAL_1 );
1105 altiumLayerId <= static_cast<size_t>( ALTIUM_LAYER::MECHANICAL_16 ); altiumLayerId++ )
1106 {
1107 // array starts with 0, but stackup with 1
1108 ABOARD6_LAYER_STACKUP& layer = elem.stackup.at( altiumLayerId - 1 );
1109
1110 ALTIUM_LAYER alayer = static_cast<ALTIUM_LAYER>( altiumLayerId );
1111 PCB_LAYER_ID klayer = GetKicadLayer( alayer );
1112
1113 m_board->SetLayerName( klayer, layer.name );
1114 }
1115
1117}
1118
1119
1120void ALTIUM_PCB::remapUnsureLayers( std::vector<ABOARD6_LAYER_STACKUP>& aStackup )
1121{
1122 LSET enabledLayers = m_board->GetEnabledLayers();
1123 LSET validRemappingLayers = enabledLayers | LSET::AllBoardTechMask() |
1125
1126 if( aStackup.size() == 0 )
1127 return;
1128
1129 std::vector<INPUT_LAYER_DESC> inputLayers;
1130 std::map<wxString, ALTIUM_LAYER> altiumLayerNameMap;
1131
1132 ABOARD6_LAYER_STACKUP& curLayer = aStackup[0];
1133 ALTIUM_LAYER layer_num;
1134 INPUT_LAYER_DESC iLdesc;
1135
1136 auto next =
1137 [&]( size_t ii ) -> size_t
1138 {
1139 // Within the copper stack, the nextId can be used to hop over unused layers in
1140 // a particular Altium board. The IDs start with ALTIUM_LAYER::UNKNOWN but the
1141 // first copper layer in the array will be ALTIUM_LAYER::TOP_LAYER.
1142 if( layer_num < ALTIUM_LAYER::BOTTOM_LAYER )
1143 return curLayer.nextId - 1;
1144 else
1145 return ii + 1;
1146 };
1147
1148 for( size_t ii = 0; ii < aStackup.size(); ii = next( ii ) )
1149 {
1150 curLayer = aStackup[ii];
1151 layer_num = static_cast<ALTIUM_LAYER>( ii + 1 );
1152
1153 if( ii >= m_board->GetCopperLayerCount() && layer_num != ALTIUM_LAYER::BOTTOM_LAYER
1154 && !( layer_num >= ALTIUM_LAYER::TOP_OVERLAY
1155 && layer_num <= ALTIUM_LAYER::BOTTOM_SOLDER )
1156 && !( layer_num >= ALTIUM_LAYER::MECHANICAL_1
1157 && layer_num <= ALTIUM_LAYER::MECHANICAL_16 ) )
1158 {
1159 if( layer_num < ALTIUM_LAYER::BOTTOM_LAYER )
1160 continue;
1161
1162 iLdesc.AutoMapLayer = PCB_LAYER_ID::UNDEFINED_LAYER;
1163 }
1164 else
1165 {
1166 iLdesc.AutoMapLayer = GetKicadLayer( layer_num );
1167 }
1168
1169 iLdesc.Name = curLayer.name;
1170 iLdesc.PermittedLayers = validRemappingLayers;
1171 iLdesc.Required = ii < m_board->GetCopperLayerCount() || layer_num == ALTIUM_LAYER::BOTTOM_LAYER;
1172
1173 inputLayers.push_back( iLdesc );
1174 altiumLayerNameMap.insert( { curLayer.name, layer_num } );
1175 m_layerNames.insert( { layer_num, curLayer.name } );
1176 }
1177
1178 if( inputLayers.size() == 0 )
1179 return;
1180
1181 // Callback:
1182 std::map<wxString, PCB_LAYER_ID> reMappedLayers = m_layerMappingHandler( inputLayers );
1183 m_layermap.clear();
1184
1185 for( std::pair<wxString, PCB_LAYER_ID> layerPair : reMappedLayers )
1186 {
1187 if( layerPair.second == PCB_LAYER_ID::UNDEFINED_LAYER )
1188 {
1189 wxFAIL_MSG( wxT( "Unexpected Layer ID" ) );
1190 continue;
1191 }
1192
1193 ALTIUM_LAYER altiumID = altiumLayerNameMap.at( layerPair.first );
1194 m_layermap.insert_or_assign( altiumID, layerPair.second );
1195 enabledLayers |= LSET( { layerPair.second } );
1196 }
1197
1198 m_board->SetEnabledLayers( enabledLayers );
1199 m_board->SetVisibleLayers( enabledLayers );
1200}
1201
1202
1203void ALTIUM_PCB::HelperCreateBoardOutline( const std::vector<ALTIUM_VERTICE>& aVertices )
1204{
1205 SHAPE_LINE_CHAIN lineChain;
1206 HelperShapeLineChainFromAltiumVertices( lineChain, aVertices );
1207
1209 LINE_STYLE::SOLID );
1210
1211 for( int i = 0; i <= lineChain.PointCount() && i != -1; i = lineChain.NextShape( i ) )
1212 {
1213 if( lineChain.IsArcStart( i ) )
1214 {
1215 const SHAPE_ARC& currentArc = lineChain.Arc( lineChain.ArcIndex( i ) );
1216 int nextShape = lineChain.NextShape( i );
1217 bool isLastShape = nextShape < 0;
1218
1219 std::unique_ptr<PCB_SHAPE> shape = std::make_unique<PCB_SHAPE>( m_board, SHAPE_T::ARC );
1220
1221 shape->SetStroke( stroke );
1222 shape->SetLayer( Edge_Cuts );
1223 shape->SetArcGeometry( currentArc.GetP0(), currentArc.GetArcMid(), currentArc.GetP1() );
1224
1225 m_board->Add( shape.release(), ADD_MODE::APPEND );
1226 }
1227 else
1228 {
1229 const SEG& seg = lineChain.Segment( i );
1230
1231 std::unique_ptr<PCB_SHAPE> shape = std::make_unique<PCB_SHAPE>( m_board, SHAPE_T::SEGMENT );
1232
1233 shape->SetStroke( stroke );
1234 shape->SetLayer( Edge_Cuts );
1235 shape->SetStart( seg.A );
1236 shape->SetEnd( seg.B );
1237
1238 m_board->Add( shape.release(), ADD_MODE::APPEND );
1239 }
1240 }
1241}
1242
1243
1245 const CFB::COMPOUND_FILE_ENTRY* aEntry )
1246{
1247 if( m_progressReporter )
1248 m_progressReporter->Report( _( "Loading netclasses..." ) );
1249
1250 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
1251
1252 while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
1253 {
1254 checkpoint();
1255 ACLASS6 elem( reader );
1256
1257 if( elem.kind == ALTIUM_CLASS_KIND::NET_CLASS )
1258 {
1259 std::shared_ptr<NETCLASS> nc = std::make_shared<NETCLASS>( elem.name );
1260
1261 for( const wxString& name : elem.names )
1262 {
1263 m_board->GetDesignSettings().m_NetSettings->SetNetclassPatternAssignment(
1264 name, nc->GetName() );
1265 }
1266
1267 if( m_board->GetDesignSettings().m_NetSettings->HasNetclass( nc->GetName() ) )
1268 {
1269 // Name conflict, happens in some unknown circumstances
1270 // unique_ptr will delete nc on this code path
1271 if( m_reporter )
1272 {
1273 wxString msg;
1274 msg.Printf( _( "More than one Altium netclass with name '%s' found. "
1275 "Only the first one will be imported." ), elem.name );
1277 }
1278 }
1279 else
1280 {
1281 m_board->GetDesignSettings().m_NetSettings->SetNetclass( nc->GetName(), nc );
1282 }
1283 }
1284 }
1285
1286 if( reader.GetRemainingBytes() != 0 )
1287 THROW_IO_ERROR( wxT( "Classes6 stream is not fully parsed" ) );
1288
1290}
1291
1292
1294 const CFB::COMPOUND_FILE_ENTRY* aEntry )
1295{
1296 if( m_progressReporter )
1297 m_progressReporter->Report( _( "Loading components..." ) );
1298
1299 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
1300
1301 uint16_t componentId = 0;
1302
1303 while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
1304 {
1305 checkpoint();
1306 ACOMPONENT6 elem( reader );
1307
1308 std::unique_ptr<FOOTPRINT> footprint = std::make_unique<FOOTPRINT>( m_board );
1309
1310 // Altium stores the footprint library information needed to find the footprint in the
1311 // source library in the sourcefootprintlibrary field. Since Altium is a Windows-only
1312 // program, the path separator is always a backslash. We need strip the extra path information
1313 // here to prevent overly-long LIB_IDs because KiCad doesn't store the full path to the
1314 // footprint library in the design file, only in a library table.
1315 wxFileName libName( elem.sourcefootprintlibrary, wxPATH_WIN );
1316 LIB_ID fpID = AltiumToKiCadLibID( libName.GetName(), elem.pattern );
1317
1318 footprint->SetFPID( fpID );
1319
1320 footprint->SetPosition( elem.position );
1321 footprint->SetOrientationDegrees( elem.rotation );
1322
1323 // KiCad netlisting requires parts to have non-digit + digit annotation.
1324 // If the reference begins with a number, we prepend 'UNK' (unknown) for the source designator
1325 wxString reference = elem.sourcedesignator;
1326
1327 if( reference.find_first_not_of( "0123456789" ) == wxString::npos )
1328 reference.Prepend( wxT( "UNK" ) );
1329
1330 footprint->SetReference( reference );
1331
1332 footprint->SetLocked( elem.locked );
1333 footprint->Reference().SetVisible( elem.nameon );
1334 footprint->Value().SetVisible( elem.commenton );
1335 footprint->SetLayer( elem.layer == ALTIUM_LAYER::TOP_LAYER ? F_Cu : B_Cu );
1336
1337 m_components.emplace_back( footprint.get() );
1338 m_board->Add( footprint.release(), ADD_MODE::APPEND );
1339
1340 componentId++;
1341 }
1342
1343 if( reader.GetRemainingBytes() != 0 )
1344 THROW_IO_ERROR( wxT( "Components6 stream is not fully parsed" ) );
1345}
1346
1347
1349double normalizeAngleDegrees( double Angle, double aMin, double aMax )
1350{
1351 while( Angle < aMin )
1352 Angle += 360.0;
1353
1354 while( Angle >= aMax )
1355 Angle -= 360.0;
1356
1357 return Angle;
1358}
1359
1360
1362 FOOTPRINT* aFootprint,
1363 const ACOMPONENTBODY6& aElem )
1364{
1365 if( m_progressReporter )
1366 m_progressReporter->Report( _( "Loading component 3D models..." ) );
1367
1368 if( !aElem.modelIsEmbedded )
1369 return;
1370
1371 auto model = aAltiumPcbFile.GetLibModel( aElem.modelId );
1372
1373 if( !model )
1374 {
1375 if( m_reporter )
1376 {
1377 m_reporter->Report( wxString::Format( wxT( "Model %s not found for footprint %s" ),
1378 aElem.modelId, aFootprint->GetReference() ),
1380 }
1381
1382 return;
1383 }
1384
1385 const VECTOR2I& fpPosition = aFootprint->GetPosition();
1386
1388 file->name = aElem.modelName;
1389
1390 if( file->name.IsEmpty() )
1391 file->name = model->first.name;
1392
1393 // Decompress the model data before assigning
1394 std::vector<char> decompressedData;
1395 wxMemoryInputStream compressedStream( model->second.data(), model->second.size() );
1396 wxZlibInputStream zlibStream( compressedStream );
1397
1398 // Reserve some space, assuming decompressed data is larger -- STEP file
1399 // compression is typically 5:1 using zlib like Altium does
1400 decompressedData.resize( model->second.size() * 6 );
1401 size_t offset = 0;
1402
1403 while( !zlibStream.Eof() )
1404 {
1405 zlibStream.Read( decompressedData.data() + offset, decompressedData.size() - offset );
1406 size_t bytesRead = zlibStream.LastRead();
1407
1408 if( !bytesRead )
1409 break;
1410
1411 offset += bytesRead;
1412
1413 if( offset >= decompressedData.size() )
1414 decompressedData.resize( 2 * decompressedData.size() ); // Resizing is expensive, avoid if we can
1415 }
1416
1417 decompressedData.resize( offset );
1418
1419 file->decompressedData = std::move( decompressedData );
1421
1423 aFootprint->GetEmbeddedFiles()->AddFile( file );
1424
1425 FP_3DMODEL modelSettings;
1426
1427 modelSettings.m_Filename = aFootprint->GetEmbeddedFiles()->GetEmbeddedFileLink( *file );
1428
1429 modelSettings.m_Offset.x = pcbIUScale.IUTomm( (int) aElem.modelPosition.x );
1430 modelSettings.m_Offset.y = -pcbIUScale.IUTomm( (int) aElem.modelPosition.y );
1431 modelSettings.m_Offset.z = pcbIUScale.IUTomm( (int) aElem.modelPosition.z );
1432
1433 EDA_ANGLE orientation = aFootprint->GetOrientation();
1434
1435 if( aFootprint->IsFlipped() )
1436 {
1437 modelSettings.m_Offset.y = -modelSettings.m_Offset.y;
1438 orientation = -orientation;
1439 }
1440
1441 RotatePoint( &modelSettings.m_Offset.x, &modelSettings.m_Offset.y, orientation );
1442
1443 modelSettings.m_Rotation.x = normalizeAngleDegrees( -aElem.modelRotation.x, -180, 180 );
1444 modelSettings.m_Rotation.y = normalizeAngleDegrees( -aElem.modelRotation.y, -180, 180 );
1445 modelSettings.m_Rotation.z = normalizeAngleDegrees( -aElem.modelRotation.z + aElem.rotation
1446 + orientation.AsDegrees(),
1447 -180, 180 );
1448 modelSettings.m_Opacity = aElem.body_opacity_3d;
1449
1450 aFootprint->Models().push_back( modelSettings );
1451}
1452
1453
1455 const CFB::COMPOUND_FILE_ENTRY* aEntry )
1456{
1457 if( m_progressReporter )
1458 m_progressReporter->Report( _( "Loading component 3D models..." ) );
1459
1460 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
1461
1462 while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
1463 {
1464 checkpoint();
1465 ACOMPONENTBODY6 elem( reader );
1466
1467 if( elem.component == ALTIUM_COMPONENT_NONE )
1468 continue; // TODO: we do not support components for the board yet
1469
1470 if( m_components.size() <= elem.component )
1471 {
1472 THROW_IO_ERROR( wxString::Format( wxT( "ComponentsBodies6 stream tries to access "
1473 "component id %d of %zu existing components" ),
1474 elem.component,
1475 m_components.size() ) );
1476 }
1477
1478 if( !elem.modelIsEmbedded )
1479 continue;
1480
1481 auto modelTuple = m_EmbeddedModels.find( elem.modelId );
1482
1483 if( modelTuple == m_EmbeddedModels.end() )
1484 {
1485 if( m_reporter )
1486 {
1487 wxString msg;
1488 msg.Printf( wxT( "ComponentsBodies6 stream tries to access model id %s which does "
1489 "not exist" ), elem.modelId );
1491 }
1492
1493 continue;
1494 }
1495
1496 const ALTIUM_EMBEDDED_MODEL_DATA& modelData = modelTuple->second;
1497 FOOTPRINT* footprint = m_components.at( elem.component );
1498
1500 file->name = modelData.m_modelname;
1501
1502 wxMemoryInputStream compressedStream( modelData.m_data.data(), modelData.m_data.size() );
1503 wxZlibInputStream zlibStream( compressedStream );
1504 wxMemoryOutputStream decompressedStream;
1505
1506 zlibStream.Read( decompressedStream );
1507 file->decompressedData.resize( decompressedStream.GetSize() );
1508 decompressedStream.CopyTo( file->decompressedData.data(), file->decompressedData.size() );
1509
1511 footprint->GetEmbeddedFiles()->AddFile( file );
1512
1513 FP_3DMODEL modelSettings;
1514
1515 modelSettings.m_Filename = footprint->GetEmbeddedFiles()->GetEmbeddedFileLink( *file );
1516 VECTOR2I fpPosition = footprint->GetPosition();
1517
1518 modelSettings.m_Offset.x =
1519 pcbIUScale.IUTomm( KiROUND( elem.modelPosition.x - fpPosition.x ) );
1520 modelSettings.m_Offset.y =
1521 -pcbIUScale.IUTomm( KiROUND( elem.modelPosition.y - fpPosition.y ) );
1522 modelSettings.m_Offset.z = pcbIUScale.IUTomm( KiROUND( elem.modelPosition.z ) );
1523
1524 EDA_ANGLE orientation = footprint->GetOrientation();
1525
1526 if( footprint->IsFlipped() )
1527 {
1528 modelSettings.m_Offset.y = -modelSettings.m_Offset.y;
1529 orientation = -orientation;
1530 }
1531
1532 RotatePoint( &modelSettings.m_Offset.x, &modelSettings.m_Offset.y, orientation );
1533
1534 modelSettings.m_Rotation.x = normalizeAngleDegrees( -elem.modelRotation.x, -180, 180 );
1535 modelSettings.m_Rotation.y = normalizeAngleDegrees( -elem.modelRotation.y, -180, 180 );
1536 modelSettings.m_Rotation.z = normalizeAngleDegrees( -elem.modelRotation.z + elem.rotation
1537 + orientation.AsDegrees(),
1538 -180, 180 );
1539
1540 modelSettings.m_Opacity = elem.body_opacity_3d;
1541
1542 footprint->Models().push_back( modelSettings );
1543 }
1544
1545 if( reader.GetRemainingBytes() != 0 )
1546 THROW_IO_ERROR( wxT( "ComponentsBodies6 stream is not fully parsed" ) );
1547}
1548
1549
1551{
1552 if( aElem.referencePoint.size() != 2 )
1553 THROW_IO_ERROR( wxT( "Incorrect number of reference points for linear dimension object" ) );
1554
1555 PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
1556
1557 if( klayer == UNDEFINED_LAYER )
1558 {
1559 if( m_reporter )
1560 {
1561 m_reporter->Report( wxString::Format(
1562 _( "Dimension found on an Altium layer (%d) with no KiCad equivalent. "
1563 "It has been moved to KiCad layer Eco1_User." ), aElem.layer ),
1565 }
1566
1567 klayer = Eco1_User;
1568 }
1569
1570 VECTOR2I referencePoint0 = aElem.referencePoint.at( 0 );
1571 VECTOR2I referencePoint1 = aElem.referencePoint.at( 1 );
1572
1573 std::unique_ptr<PCB_DIM_ALIGNED> dimension = std::make_unique<PCB_DIM_ALIGNED>( m_board, PCB_DIM_ALIGNED_T );
1574
1575 dimension->SetPrecision( static_cast<DIM_PRECISION>( aElem.textprecision ) );
1576 dimension->SetLayer( klayer );
1577 dimension->SetStart( referencePoint0 );
1578
1579 if( referencePoint0 != aElem.xy1 )
1580 {
1590 VECTOR2I direction = aElem.xy1 - referencePoint0;
1591 VECTOR2I directionNormalVector = VECTOR2I( -direction.y, direction.x );
1592 SEG segm1( referencePoint0, referencePoint0 + directionNormalVector );
1593 SEG segm2( referencePoint1, referencePoint1 + direction );
1594 OPT_VECTOR2I intersection( segm1.Intersect( segm2, true, true ) );
1595
1596 if( !intersection )
1597 THROW_IO_ERROR( wxT( "Invalid dimension. This should never happen." ) );
1598
1599 dimension->SetEnd( *intersection );
1600
1601 int height = direction.EuclideanNorm();
1602
1603 if( ( direction.x > 0 || direction.y < 0 ) != ( aElem.angle >= 180.0 ) )
1604 height = -height;
1605
1606 dimension->SetHeight( height );
1607 }
1608 else
1609 {
1610 dimension->SetEnd( referencePoint1 );
1611 }
1612
1613 dimension->SetLineThickness( aElem.linewidth );
1614
1615 dimension->SetUnitsFormat( DIM_UNITS_FORMAT::NO_SUFFIX );
1616 dimension->SetPrefix( aElem.textprefix );
1617
1618 // Suffix normally (but not always) holds the units
1619 wxRegEx units( wxS( "(mm)|(in)|(mils)|(thou)|(')|(\")" ), wxRE_ADVANCED );
1620
1621 if( units.Matches( aElem.textsuffix ) )
1622 dimension->SetUnitsFormat( DIM_UNITS_FORMAT::BARE_SUFFIX );
1623 else
1624 dimension->SetSuffix( aElem.textsuffix );
1625
1626 dimension->SetTextThickness( aElem.textlinewidth );
1627 dimension->SetTextSize( VECTOR2I( aElem.textheight, aElem.textheight ) );
1628 dimension->SetItalic( aElem.textitalic );
1629
1630#if 0 // we don't currently support bold; map to thicker text
1631 dimension->Text().SetBold( aElem.textbold );
1632#else
1633 if( aElem.textbold )
1634 dimension->SetTextThickness( dimension->GetTextThickness() * BOLD_FACTOR );
1635#endif
1636
1637 switch( aElem.textunit )
1638 {
1639 case ALTIUM_UNIT::INCHES:
1640 dimension->SetUnits( EDA_UNITS::INCHES );
1641 break;
1642 case ALTIUM_UNIT::MILS:
1643 dimension->SetUnits( EDA_UNITS::MILS );
1644 break;
1645 case ALTIUM_UNIT::MILLIMETERS:
1646 case ALTIUM_UNIT::CENTIMETER:
1647 dimension->SetUnits( EDA_UNITS::MILLIMETRES );
1648 break;
1649 default:
1650 break;
1651 }
1652
1653 m_board->Add( dimension.release(), ADD_MODE::APPEND );
1654}
1655
1656
1658{
1659 if( aElem.referencePoint.size() < 2 )
1660 THROW_IO_ERROR( wxT( "Not enough reference points for radial dimension object" ) );
1661
1662 PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
1663
1664 if( klayer == UNDEFINED_LAYER )
1665 {
1666 if( m_reporter )
1667 {
1668 m_reporter->Report( wxString::Format(
1669 _( "Dimension found on an Altium layer (%d) with no KiCad equivalent. "
1670 "It has been moved to KiCad layer Eco1_User." ),
1671 aElem.layer ), RPT_SEVERITY_INFO );
1672 }
1673
1674 klayer = Eco1_User;
1675 }
1676
1677 VECTOR2I referencePoint0 = aElem.referencePoint.at( 0 );
1678 VECTOR2I referencePoint1 = aElem.referencePoint.at( 1 );
1679
1680 std::unique_ptr<PCB_DIM_RADIAL> dimension = std::make_unique<PCB_DIM_RADIAL>( m_board );
1681
1682 dimension->SetPrecision( static_cast<DIM_PRECISION>( aElem.textprecision ) );
1683 dimension->SetLayer( klayer );
1684 dimension->SetStart( referencePoint0 );
1685 dimension->SetEnd( aElem.xy1 );
1686 dimension->SetLineThickness( aElem.linewidth );
1687 dimension->SetKeepTextAligned( false );
1688
1689 dimension->SetPrefix( aElem.textprefix );
1690
1691 // Suffix normally holds the units
1692 dimension->SetUnitsFormat( aElem.textsuffix.IsEmpty() ? DIM_UNITS_FORMAT::NO_SUFFIX
1693 : DIM_UNITS_FORMAT::BARE_SUFFIX );
1694
1695 switch( aElem.textunit )
1696 {
1697 case ALTIUM_UNIT::INCHES:
1698 dimension->SetUnits( EDA_UNITS::INCHES );
1699 break;
1700 case ALTIUM_UNIT::MILS:
1701 dimension->SetUnits( EDA_UNITS::MILS );
1702 break;
1703 case ALTIUM_UNIT::MILLIMETERS:
1704 case ALTIUM_UNIT::CENTIMETER:
1705 dimension->SetUnits( EDA_UNITS::MILLIMETRES );
1706 break;
1707 default:
1708 break;
1709 }
1710
1711 if( aElem.textPoint.empty() )
1712 {
1713 if( m_reporter )
1714 {
1715 m_reporter->Report( wxT( "No text position present for leader dimension object" ),
1717 }
1718
1719 return;
1720 }
1721
1722 dimension->SetTextPos( aElem.textPoint.at( 0 ) );
1723 dimension->SetTextThickness( aElem.textlinewidth );
1724 dimension->SetTextSize( VECTOR2I( aElem.textheight, aElem.textheight ) );
1725 dimension->SetItalic( aElem.textitalic );
1726
1727#if 0 // we don't currently support bold; map to thicker text
1728 dimension->SetBold( aElem.textbold );
1729#else
1730 if( aElem.textbold )
1731 dimension->SetTextThickness( dimension->GetTextThickness() * BOLD_FACTOR );
1732#endif
1733
1734 // It's unclear exactly how Altium figures it's text positioning, but this gets us reasonably
1735 // close.
1736 dimension->SetVertJustify( GR_TEXT_V_ALIGN_BOTTOM );
1737 dimension->SetHorizJustify( GR_TEXT_H_ALIGN_LEFT );
1738
1739 int yAdjust = dimension->GetTextBox().GetCenter().y - dimension->GetTextPos().y;
1740 dimension->SetTextPos( dimension->GetTextPos() + VECTOR2I( 0, yAdjust + aElem.textgap ) );
1741 dimension->SetVertJustify( GR_TEXT_V_ALIGN_CENTER );
1742
1743 m_radialDimensions.push_back( dimension.get() );
1744 m_board->Add( dimension.release(), ADD_MODE::APPEND );
1745}
1746
1747
1749{
1750 PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
1751
1752 if( klayer == UNDEFINED_LAYER )
1753 {
1754 if( m_reporter )
1755 {
1756 wxString msg;
1757 msg.Printf( _( "Dimension found on an Altium layer (%d) with no KiCad equivalent. "
1758 "It has been moved to KiCad layer Eco1_User." ), aElem.layer );
1760 }
1761
1762 klayer = Eco1_User;
1763 }
1764
1765 if( !aElem.referencePoint.empty() )
1766 {
1767 VECTOR2I referencePoint0 = aElem.referencePoint.at( 0 );
1768
1769 // line
1770 VECTOR2I last = referencePoint0;
1771 for( size_t i = 1; i < aElem.referencePoint.size(); i++ )
1772 {
1773 std::unique_ptr<PCB_SHAPE> shape = std::make_unique<PCB_SHAPE>( m_board, SHAPE_T::SEGMENT );
1774
1775 shape->SetLayer( klayer );
1776 shape->SetStroke( STROKE_PARAMS( aElem.linewidth, LINE_STYLE::SOLID ) );
1777 shape->SetStart( last );
1778 shape->SetEnd( aElem.referencePoint.at( i ) );
1779 last = aElem.referencePoint.at( i );
1780
1781 m_board->Add( shape.release(), ADD_MODE::APPEND );
1782 }
1783
1784 // arrow
1785 if( aElem.referencePoint.size() >= 2 )
1786 {
1787 VECTOR2I dirVec = aElem.referencePoint.at( 1 ) - referencePoint0;
1788
1789 if( dirVec.x != 0 || dirVec.y != 0 )
1790 {
1791 double scaling = dirVec.EuclideanNorm() / aElem.arrowsize;
1792 VECTOR2I arrVec =
1793 VECTOR2I( KiROUND( dirVec.x / scaling ), KiROUND( dirVec.y / scaling ) );
1794 RotatePoint( arrVec, EDA_ANGLE( 20.0, DEGREES_T ) );
1795
1796 {
1797 std::unique_ptr<PCB_SHAPE> shape1 =
1798 std::make_unique<PCB_SHAPE>( m_board, SHAPE_T::SEGMENT );
1799
1800 shape1->SetLayer( klayer );
1801 shape1->SetStroke( STROKE_PARAMS( aElem.linewidth, LINE_STYLE::SOLID ) );
1802 shape1->SetStart( referencePoint0 );
1803 shape1->SetEnd( referencePoint0 + arrVec );
1804
1805 m_board->Add( shape1.release(), ADD_MODE::APPEND );
1806 }
1807
1808 RotatePoint( arrVec, EDA_ANGLE( -40.0, DEGREES_T ) );
1809
1810 {
1811 std::unique_ptr<PCB_SHAPE> shape2 =
1812 std::make_unique<PCB_SHAPE>( m_board, SHAPE_T::SEGMENT );
1813
1814 shape2->SetLayer( klayer );
1815 shape2->SetStroke( STROKE_PARAMS( aElem.linewidth, LINE_STYLE::SOLID ) );
1816 shape2->SetStart( referencePoint0 );
1817 shape2->SetEnd( referencePoint0 + arrVec );
1818
1819 m_board->Add( shape2.release(), ADD_MODE::APPEND );
1820 }
1821 }
1822 }
1823 }
1824
1825 if( aElem.textPoint.empty() )
1826 {
1827 if( m_reporter )
1828 {
1829 m_reporter->Report( wxT( "No text position present for leader dimension object" ),
1831 }
1832
1833 return;
1834 }
1835
1836 std::unique_ptr<PCB_TEXT> text = std::make_unique<PCB_TEXT>( m_board );
1837
1838 text->SetText( aElem.textformat );
1839 text->SetPosition( aElem.textPoint.at( 0 ) );
1840 text->SetLayer( klayer );
1841 text->SetTextSize( VECTOR2I( aElem.textheight, aElem.textheight ) ); // TODO: parse text width
1842 text->SetTextThickness( aElem.textlinewidth );
1843 text->SetHorizJustify( GR_TEXT_H_ALIGN_LEFT );
1844 text->SetVertJustify( GR_TEXT_V_ALIGN_BOTTOM );
1845
1846 m_board->Add( text.release(), ADD_MODE::APPEND );
1847}
1848
1849
1851{
1852 PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
1853
1854 if( klayer == UNDEFINED_LAYER )
1855 {
1856 if( m_reporter )
1857 {
1858 wxString msg;
1859 msg.Printf( _( "Dimension found on an Altium layer (%d) with no KiCad equivalent. "
1860 "It has been moved to KiCad layer Eco1_User." ), aElem.layer );
1862 }
1863
1864 klayer = Eco1_User;
1865 }
1866
1867 for( size_t i = 0; i < aElem.referencePoint.size(); i++ )
1868 {
1869 std::unique_ptr<PCB_SHAPE> shape = std::make_unique<PCB_SHAPE>( m_board, SHAPE_T::SEGMENT );
1870
1871 shape->SetLayer( klayer );
1872 shape->SetStroke( STROKE_PARAMS( aElem.linewidth, LINE_STYLE::SOLID ) );
1873 shape->SetStart( aElem.referencePoint.at( i ) );
1874 // shape->SetEnd( /* TODO: seems to be based on TEXTY */ );
1875
1876 m_board->Add( shape.release(), ADD_MODE::APPEND );
1877 }
1878}
1879
1880
1882{
1883 PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
1884
1885 if( klayer == UNDEFINED_LAYER )
1886 {
1887 if( m_reporter )
1888 {
1889 wxString msg;
1890 msg.Printf( _( "Dimension found on an Altium layer (%d) with no KiCad equivalent. "
1891 "It has been moved to KiCad layer Eco1_User." ), aElem.layer );
1893 }
1894
1895 klayer = Eco1_User;
1896 }
1897
1898 VECTOR2I vec = VECTOR2I( 0, aElem.height / 2 );
1899 RotatePoint( vec, EDA_ANGLE( aElem.angle, DEGREES_T ) );
1900
1901 std::unique_ptr<PCB_DIM_CENTER> dimension = std::make_unique<PCB_DIM_CENTER>( m_board );
1902
1903 dimension->SetLayer( klayer );
1904 dimension->SetLineThickness( aElem.linewidth );
1905 dimension->SetStart( aElem.xy1 );
1906 dimension->SetEnd( aElem.xy1 + vec );
1907
1908 m_board->Add( dimension.release(), ADD_MODE::APPEND );
1909}
1910
1911
1913 const CFB::COMPOUND_FILE_ENTRY* aEntry )
1914{
1915 if( m_progressReporter )
1916 m_progressReporter->Report( _( "Loading dimension drawings..." ) );
1917
1918 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
1919
1920 while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
1921 {
1922 checkpoint();
1923 ADIMENSION6 elem( reader );
1924
1925 switch( elem.kind )
1926 {
1927 case ALTIUM_DIMENSION_KIND::LINEAR:
1929 break;
1930 case ALTIUM_DIMENSION_KIND::ANGULAR:
1931 if( m_reporter )
1932 {
1934 wxString::Format( _( "Ignored Angular dimension (not yet supported)." ) ),
1936 }
1937 break;
1938 case ALTIUM_DIMENSION_KIND::RADIAL:
1940 break;
1941 case ALTIUM_DIMENSION_KIND::LEADER:
1943 break;
1944 case ALTIUM_DIMENSION_KIND::DATUM:
1945 if( m_reporter )
1946 {
1948 wxString::Format( _( "Ignored Datum dimension (not yet supported)." ) ),
1950 }
1951 // HelperParseDimensions6Datum( elem );
1952 break;
1953 case ALTIUM_DIMENSION_KIND::BASELINE:
1954 if( m_reporter )
1955 {
1957 wxString::Format( _( "Ignored Baseline dimension (not yet supported)." ) ),
1959 }
1960 break;
1961 case ALTIUM_DIMENSION_KIND::CENTER:
1963 break;
1964 case ALTIUM_DIMENSION_KIND::LINEAR_DIAMETER:
1965 if( m_reporter )
1966 {
1968 wxString::Format( _( "Ignored Linear dimension (not yet supported)." ) ),
1970 }
1971 break;
1972 case ALTIUM_DIMENSION_KIND::RADIAL_DIAMETER:
1973 if( m_reporter )
1974 {
1976 wxString::Format( _( "Ignored Radial dimension (not yet supported)." ) ),
1978 }
1979 break;
1980 default:
1981 if( m_reporter )
1982 {
1983 wxString msg;
1984 msg.Printf( _( "Ignored dimension of kind %d (not yet supported)." ), elem.kind );
1986 }
1987 break;
1988 }
1989 }
1990
1991 if( reader.GetRemainingBytes() != 0 )
1992 THROW_IO_ERROR( wxT( "Dimensions6 stream is not fully parsed" ) );
1993}
1994
1995
1997 const CFB::COMPOUND_FILE_ENTRY* aEntry,
1998 const std::vector<std::string>& aRootDir )
1999{
2000 if( m_progressReporter )
2001 m_progressReporter->Report( _( "Loading 3D models..." ) );
2002
2003 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
2004
2005 if( reader.GetRemainingBytes() == 0 )
2006 return;
2007
2008 int idx = 0;
2009 wxString invalidChars = wxFileName::GetForbiddenChars();
2010
2011 while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
2012 {
2013 checkpoint();
2014 AMODEL elem( reader );
2015
2016 std::vector<std::string> stepPath = aRootDir;
2017 stepPath.emplace_back( std::to_string( idx ) );
2018
2019 bool validName = !elem.name.IsEmpty() && elem.name.IsAscii() &&
2020 wxString::npos == elem.name.find_first_of( invalidChars );
2021 wxString storageName = !validName ? wxString::Format( wxT( "model_%d" ), idx )
2022 : elem.name;
2023
2024 idx++;
2025
2026 const CFB::COMPOUND_FILE_ENTRY* stepEntry = aAltiumPcbFile.FindStream( stepPath );
2027
2028 if( stepEntry == nullptr )
2029 {
2030 if( m_reporter )
2031 {
2032 wxString msg;
2033 msg.Printf( _( "File not found: '%s'. 3D-model not imported." ),
2034 FormatPath( stepPath ) );
2036 }
2037
2038 continue;
2039 }
2040
2041 size_t stepSize = static_cast<size_t>( stepEntry->size );
2042 std::vector<char> stepContent( stepSize );
2043
2044 // read file into buffer
2045 aAltiumPcbFile.GetCompoundFileReader().ReadFile( stepEntry, 0, stepContent.data(),
2046 stepSize );
2047
2048 m_EmbeddedModels.insert( std::make_pair(
2049 elem.id, ALTIUM_EMBEDDED_MODEL_DATA( storageName, elem.rotation, elem.z_offset,
2050 std::move( stepContent ) ) ) );
2051 }
2052
2053 if( reader.GetRemainingBytes() != 0 )
2054 THROW_IO_ERROR( wxT( "Models stream is not fully parsed" ) );
2055}
2056
2057
2059 const CFB::COMPOUND_FILE_ENTRY* aEntry )
2060{
2061 if( m_progressReporter )
2062 m_progressReporter->Report( _( "Loading nets..." ) );
2063
2064 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
2065
2066 wxASSERT( m_altiumToKicadNetcodes.empty() );
2067
2068 while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
2069 {
2070 checkpoint();
2071 ANET6 elem( reader );
2072
2073 NETINFO_ITEM* netInfo = new NETINFO_ITEM( m_board, elem.name, 0 );
2074 m_board->Add( netInfo, ADD_MODE::APPEND );
2075
2076 // needs to be called after m_board->Add() as assign us the NetCode
2077 m_altiumToKicadNetcodes.push_back( netInfo->GetNetCode() );
2078 }
2079
2080 if( reader.GetRemainingBytes() != 0 )
2081 THROW_IO_ERROR( wxT( "Nets6 stream is not fully parsed" ) );
2082}
2083
2085 const CFB::COMPOUND_FILE_ENTRY* aEntry )
2086{
2087 if( m_progressReporter )
2088 m_progressReporter->Report( _( "Loading polygons..." ) );
2089
2090 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
2091
2092 while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
2093 {
2094 checkpoint();
2095 APOLYGON6 elem( reader );
2096
2097 SHAPE_LINE_CHAIN linechain;
2099
2100 if( linechain.PointCount() < 3 )
2101 {
2102 // We have found multiple Altium files with polygon records containing nothing but two
2103 // coincident vertices. These polygons do not appear when opening the file in Altium.
2104 // https://gitlab.com/kicad/code/kicad/-/issues/8183
2105 // Also, polygons with less than 3 points are not supported in KiCad.
2106 //
2107 // wxLogError( _( "Polygon has only %d point extracted from %ld vertices. At least 2 "
2108 // "points are required." ),
2109 // linechain.PointCount(),
2110 // elem.vertices.size() );
2111
2112 m_polygons.emplace_back( nullptr );
2113 continue;
2114 }
2115
2116 SHAPE_POLY_SET outline( linechain );
2117
2118 if( elem.hatchstyle != ALTIUM_POLYGON_HATCHSTYLE::SOLID )
2119 {
2120 // Altium "Hatched" or "None" polygon outlines have thickness, convert it to KiCad's representation.
2121 outline.Inflate( elem.trackwidth / 2, CORNER_STRATEGY::CHAMFER_ACUTE_CORNERS,
2122 ARC_HIGH_DEF, true );
2123 }
2124
2125 if( outline.OutlineCount() != 1 && m_reporter )
2126 {
2127 wxString msg;
2128 msg.Printf( _( "Polygon outline count is %d, expected 1." ), outline.OutlineCount() );
2129
2131 }
2132
2133 if( outline.OutlineCount() == 0 )
2134 continue;
2135
2136 std::unique_ptr<ZONE> zone = std::make_unique<ZONE>(m_board);
2137 m_polygons.emplace_back(zone.get());
2138
2139 zone->SetNetCode( GetNetCode( elem.net ) );
2140 zone->SetPosition( elem.vertices.at( 0 ).position );
2141 zone->SetLocked( elem.locked );
2142 zone->SetAssignedPriority( elem.pourindex > 0 ? elem.pourindex : 0 );
2143 zone->Outline()->AddOutline( outline.Outline( 0 ) );
2144
2145 HelperSetZoneLayers( *zone, elem.layer );
2146
2147 if( elem.pourindex > m_highest_pour_index )
2149
2150 const ARULE6* planeClearanceRule = GetRuleDefault( ALTIUM_RULE_KIND::PLANE_CLEARANCE );
2151 const ARULE6* zoneClearanceRule = GetRule( ALTIUM_RULE_KIND::CLEARANCE,
2152 wxT( "PolygonClearance" ) );
2153 int planeLayers = 0;
2154 int signalLayers = 0;
2155 int clearance = 0;
2156
2157 for( PCB_LAYER_ID layer : zone->GetLayerSet().Seq() )
2158 {
2159 LAYER_T layerType = m_board->GetLayerType( layer );
2160
2161 if( layerType == LT_POWER || layerType == LT_MIXED )
2162 planeLayers++;
2163
2164 if( layerType == LT_SIGNAL || layerType == LT_MIXED )
2165 signalLayers++;
2166 }
2167
2168 if( planeLayers > 0 && planeClearanceRule )
2169 clearance = std::max( clearance, planeClearanceRule->planeclearanceClearance );
2170
2171 if( signalLayers > 0 && zoneClearanceRule )
2172 clearance = std::max( clearance, zoneClearanceRule->clearanceGap );
2173
2174 if( clearance > 0 )
2175 zone->SetLocalClearance( clearance );
2176
2177 const ARULE6* polygonConnectRule = GetRuleDefault( ALTIUM_RULE_KIND::POLYGON_CONNECT );
2178
2179 if( polygonConnectRule != nullptr )
2180 {
2181 switch( polygonConnectRule->polygonconnectStyle )
2182 {
2183 case ALTIUM_CONNECT_STYLE::DIRECT:
2184 zone->SetPadConnection( ZONE_CONNECTION::FULL );
2185 break;
2186
2187 case ALTIUM_CONNECT_STYLE::NONE:
2188 zone->SetPadConnection( ZONE_CONNECTION::NONE );
2189 break;
2190
2191 default:
2192 case ALTIUM_CONNECT_STYLE::RELIEF:
2193 zone->SetPadConnection( ZONE_CONNECTION::THERMAL );
2194 break;
2195 }
2196
2197 // TODO: correct variables?
2198 zone->SetThermalReliefSpokeWidth(
2199 polygonConnectRule->polygonconnectReliefconductorwidth );
2200 zone->SetThermalReliefGap( polygonConnectRule->polygonconnectAirgapwidth );
2201
2202 if( polygonConnectRule->polygonconnectReliefconductorwidth < zone->GetMinThickness() )
2203 zone->SetMinThickness( polygonConnectRule->polygonconnectReliefconductorwidth );
2204 }
2205
2206 if( IsAltiumLayerAPlane( elem.layer ) )
2207 {
2208 // outer zone will be set to priority 0 later.
2209 zone->SetAssignedPriority( 1 );
2210
2211 // check if this is the outer zone by simply comparing the BBOX
2212 const auto& outer_plane = m_outer_plane.find( elem.layer );
2213 if( outer_plane == m_outer_plane.end()
2214 || zone->GetBoundingBox().Contains( outer_plane->second->GetBoundingBox() ) )
2215 {
2216 m_outer_plane[elem.layer] = zone.get();
2217 }
2218 }
2219
2220 if( elem.hatchstyle != ALTIUM_POLYGON_HATCHSTYLE::SOLID
2221 && elem.hatchstyle != ALTIUM_POLYGON_HATCHSTYLE::UNKNOWN )
2222 {
2223 zone->SetFillMode( ZONE_FILL_MODE::HATCH_PATTERN );
2224 zone->SetHatchThickness( elem.trackwidth );
2225
2226 if( elem.hatchstyle == ALTIUM_POLYGON_HATCHSTYLE::NONE )
2227 {
2228 // use a small hack to get us only an outline (hopefully)
2229 const BOX2I& bbox = zone->GetBoundingBox();
2230 zone->SetHatchGap( std::max( bbox.GetHeight(), bbox.GetWidth() ) );
2231 }
2232 else
2233 {
2234 zone->SetHatchGap( elem.gridsize - elem.trackwidth );
2235 }
2236
2237 if( elem.hatchstyle == ALTIUM_POLYGON_HATCHSTYLE::DEGREE_45 )
2238 zone->SetHatchOrientation( ANGLE_45 );
2239 }
2240
2241 zone->SetBorderDisplayStyle( ZONE_BORDER_DISPLAY_STYLE::DIAGONAL_EDGE,
2243
2244 m_board->Add( zone.release(), ADD_MODE::APPEND );
2245 }
2246
2247 if( reader.GetRemainingBytes() != 0 )
2248 THROW_IO_ERROR( wxT( "Polygons6 stream is not fully parsed" ) );
2249}
2250
2252 const CFB::COMPOUND_FILE_ENTRY* aEntry )
2253{
2254 if( m_progressReporter )
2255 m_progressReporter->Report( _( "Loading rules..." ) );
2256
2257 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
2258
2259 while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
2260 {
2261 checkpoint();
2262 ARULE6 elem( reader );
2263
2264 m_rules[elem.kind].emplace_back( elem );
2265 }
2266
2267 // sort rules by priority
2268 for( std::pair<const ALTIUM_RULE_KIND, std::vector<ARULE6>>& val : m_rules )
2269 {
2270 std::sort( val.second.begin(), val.second.end(),
2271 []( const ARULE6& lhs, const ARULE6& rhs )
2272 {
2273 return lhs.priority < rhs.priority;
2274 } );
2275 }
2276
2277 const ARULE6* clearanceRule = GetRuleDefault( ALTIUM_RULE_KIND::CLEARANCE );
2278 const ARULE6* trackWidthRule = GetRuleDefault( ALTIUM_RULE_KIND::WIDTH );
2279 const ARULE6* routingViasRule = GetRuleDefault( ALTIUM_RULE_KIND::ROUTING_VIAS );
2280 const ARULE6* holeSizeRule = GetRuleDefault( ALTIUM_RULE_KIND::HOLE_SIZE );
2281 const ARULE6* holeToHoleRule = GetRuleDefault( ALTIUM_RULE_KIND::HOLE_TO_HOLE_CLEARANCE );
2282
2283 if( clearanceRule )
2285
2286 if( trackWidthRule )
2287 {
2289 // TODO: construct a custom rule for preferredWidth and maxLimit values
2290 }
2291
2292 if( routingViasRule )
2293 {
2294 m_board->GetDesignSettings().m_ViasMinSize = routingViasRule->minWidth;
2296 }
2297
2298 if( holeSizeRule )
2299 {
2300 // TODO: construct a custom rule for minLimit / maxLimit values
2301 }
2302
2303 if( holeToHoleRule )
2305
2306 const ARULE6* soldermaskRule = GetRuleDefault( ALTIUM_RULE_KIND::SOLDER_MASK_EXPANSION );
2307 const ARULE6* pastemaskRule = GetRuleDefault( ALTIUM_RULE_KIND::PASTE_MASK_EXPANSION );
2308
2309 if( soldermaskRule )
2311
2312 if( pastemaskRule )
2314
2315 if( reader.GetRemainingBytes() != 0 )
2316 THROW_IO_ERROR( wxT( "Rules6 stream is not fully parsed" ) );
2317}
2318
2320 const CFB::COMPOUND_FILE_ENTRY* aEntry )
2321{
2322 if( m_progressReporter )
2323 m_progressReporter->Report( _( "Loading board regions..." ) );
2324
2325 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
2326
2327 while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
2328 {
2329 checkpoint();
2330 AREGION6 elem( reader, false );
2331
2332 // TODO: implement?
2333 }
2334
2335 if( reader.GetRemainingBytes() != 0 )
2336 THROW_IO_ERROR( wxT( "BoardRegions stream is not fully parsed" ) );
2337}
2338
2340 const CFB::COMPOUND_FILE_ENTRY* aEntry )
2341{
2342 if( m_progressReporter )
2343 m_progressReporter->Report( _( "Loading polygons..." ) );
2344
2345 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
2346
2347 /* TODO: use Header section of file */
2348 for( int primitiveIndex = 0; reader.GetRemainingBytes() >= 4; primitiveIndex++ )
2349 {
2350 checkpoint();
2351 AREGION6 elem( reader, true );
2352
2354 || elem.kind == ALTIUM_REGION_KIND::BOARD_CUTOUT )
2355 {
2356 // TODO: implement all different types for footprints
2358 }
2359 else
2360 {
2361 FOOTPRINT* footprint = HelperGetFootprint( elem.component );
2362 ConvertShapeBasedRegions6ToFootprintItem( footprint, elem, primitiveIndex );
2363 }
2364 }
2365
2366 if( reader.GetRemainingBytes() != 0 )
2367 THROW_IO_ERROR( "ShapeBasedRegions6 stream is not fully parsed" );
2368}
2369
2370
2372{
2373 if( aElem.kind == ALTIUM_REGION_KIND::BOARD_CUTOUT )
2374 {
2376 }
2377 else if( aElem.kind == ALTIUM_REGION_KIND::POLYGON_CUTOUT || aElem.is_keepout )
2378 {
2379 SHAPE_LINE_CHAIN linechain;
2381
2382 if( linechain.PointCount() < 3 )
2383 {
2384 // We have found multiple Altium files with polygon records containing nothing but
2385 // two coincident vertices. These polygons do not appear when opening the file in
2386 // Altium. https://gitlab.com/kicad/code/kicad/-/issues/8183
2387 // Also, polygons with less than 3 points are not supported in KiCad.
2388 return;
2389 }
2390
2391 std::unique_ptr<ZONE> zone = std::make_unique<ZONE>( m_board );
2392
2393 zone->SetIsRuleArea( true );
2394
2395 if( aElem.is_keepout )
2396 {
2398 }
2399 else if( aElem.kind == ALTIUM_REGION_KIND::POLYGON_CUTOUT )
2400 {
2401 zone->SetDoNotAllowCopperPour( true );
2402 zone->SetDoNotAllowVias( false );
2403 zone->SetDoNotAllowTracks( false );
2404 zone->SetDoNotAllowPads( false );
2405 zone->SetDoNotAllowFootprints( false );
2406 }
2407
2408 zone->SetPosition( aElem.outline.at( 0 ).position );
2409 zone->Outline()->AddOutline( linechain );
2410
2411 HelperSetZoneLayers( *zone, aElem.layer );
2412
2413 zone->SetBorderDisplayStyle( ZONE_BORDER_DISPLAY_STYLE::DIAGONAL_EDGE,
2415
2416 m_board->Add( zone.release(), ADD_MODE::APPEND );
2417 }
2418 else if( aElem.kind == ALTIUM_REGION_KIND::DASHED_OUTLINE )
2419 {
2420 PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
2421
2422 if( klayer == UNDEFINED_LAYER )
2423 {
2424 if( m_reporter )
2425 {
2426 wxString msg;
2427 msg.Printf( _( "Dashed outline found on an Altium layer (%d) with no KiCad equivalent. "
2428 "It has been moved to KiCad layer Eco1_User." ), aElem.layer );
2430 }
2431
2432 klayer = Eco1_User;
2433 }
2434
2435 SHAPE_LINE_CHAIN linechain;
2437
2438 if( linechain.PointCount() < 3 )
2439 {
2440 // We have found multiple Altium files with polygon records containing nothing but
2441 // two coincident vertices. These polygons do not appear when opening the file in
2442 // Altium. https://gitlab.com/kicad/code/kicad/-/issues/8183
2443 // Also, polygons with less than 3 points are not supported in KiCad.
2444 return;
2445 }
2446
2447 std::unique_ptr<PCB_SHAPE> shape = std::make_unique<PCB_SHAPE>( m_board, SHAPE_T::POLY );
2448
2449 shape->SetPolyShape( linechain );
2450 shape->SetFilled( false );
2451 shape->SetLayer( klayer );
2452 shape->SetStroke( STROKE_PARAMS( pcbIUScale.mmToIU( 0.1 ), LINE_STYLE::DASH ) );
2453
2454 m_board->Add( shape.release(), ADD_MODE::APPEND );
2455 }
2456 else if( aElem.kind == ALTIUM_REGION_KIND::COPPER )
2457 {
2458 if( aElem.polygon == ALTIUM_POLYGON_NONE )
2459 {
2460 for( PCB_LAYER_ID klayer : GetKicadLayersToIterate( aElem.layer ) )
2462 }
2463 }
2464 else if( aElem.kind == ALTIUM_REGION_KIND::BOARD_CUTOUT )
2465 {
2467 }
2468 else
2469 {
2470 if( m_reporter )
2471 {
2472 wxString msg;
2473 msg.Printf( _( "Ignored polygon shape of kind %d (not yet supported)." ), aElem.kind );
2475 }
2476 }
2477}
2478
2479
2481 const AREGION6& aElem,
2482 const int aPrimitiveIndex )
2483{
2484 if( aElem.kind == ALTIUM_REGION_KIND::POLYGON_CUTOUT || aElem.is_keepout )
2485 {
2486 SHAPE_LINE_CHAIN linechain;
2488
2489 if( linechain.PointCount() < 3 )
2490 {
2491 // We have found multiple Altium files with polygon records containing nothing but
2492 // two coincident vertices. These polygons do not appear when opening the file in
2493 // Altium. https://gitlab.com/kicad/code/kicad/-/issues/8183
2494 // Also, polygons with less than 3 points are not supported in KiCad.
2495 return;
2496 }
2497
2498 std::unique_ptr<ZONE> zone = std::make_unique<ZONE>( aFootprint );
2499
2500 zone->SetIsRuleArea( true );
2501
2502 if( aElem.is_keepout )
2503 {
2505 }
2506 else if( aElem.kind == ALTIUM_REGION_KIND::POLYGON_CUTOUT )
2507 {
2508 zone->SetDoNotAllowCopperPour( true );
2509 zone->SetDoNotAllowVias( false );
2510 zone->SetDoNotAllowTracks( false );
2511 zone->SetDoNotAllowPads( false );
2512 zone->SetDoNotAllowFootprints( false );
2513 }
2514
2515 zone->SetPosition( aElem.outline.at( 0 ).position );
2516 zone->Outline()->AddOutline( linechain );
2517
2518 HelperSetZoneLayers( *zone, aElem.layer );
2519
2520 zone->SetBorderDisplayStyle( ZONE_BORDER_DISPLAY_STYLE::DIAGONAL_EDGE,
2522
2523 aFootprint->Add( zone.release(), ADD_MODE::APPEND );
2524 }
2525 else if( aElem.kind == ALTIUM_REGION_KIND::COPPER )
2526 {
2527 if( aElem.polygon == ALTIUM_POLYGON_NONE )
2528 {
2529 for( PCB_LAYER_ID klayer : GetKicadLayersToIterate( aElem.layer ) )
2530 {
2531 ConvertShapeBasedRegions6ToFootprintItemOnLayer( aFootprint, aElem, klayer,
2532 aPrimitiveIndex );
2533 }
2534 }
2535 }
2536 else if( aElem.kind == ALTIUM_REGION_KIND::DASHED_OUTLINE
2537 || aElem.kind == ALTIUM_REGION_KIND::BOARD_CUTOUT )
2538 {
2539 PCB_LAYER_ID klayer = aElem.kind == ALTIUM_REGION_KIND::BOARD_CUTOUT
2540 ? Edge_Cuts
2541 : GetKicadLayer( aElem.layer );
2542
2543 if( klayer == UNDEFINED_LAYER )
2544 {
2545 if( !m_footprintName.IsEmpty() )
2546 {
2547 if( m_reporter )
2548 {
2549 wxString msg;
2550 msg.Printf( _( "Loading library '%s':\n"
2551 "Footprint %s contains a dashed outline on Altium layer (%d) with "
2552 "no KiCad equivalent. It has been moved to KiCad layer Eco1_User." ),
2553 m_library,
2555 aElem.layer );
2557 }
2558 }
2559 else
2560 {
2561 if( m_reporter )
2562 {
2563 wxString msg;
2564 msg.Printf( _( "Footprint %s contains a dashed outline on Altium layer (%d) with "
2565 "no KiCad equivalent. It has been moved to KiCad layer Eco1_User." ),
2566 aFootprint->GetReference(),
2567 aElem.layer );
2569 }
2570 }
2571
2572 klayer = Eco1_User;
2573 }
2574
2575 SHAPE_LINE_CHAIN linechain;
2577
2578 if( linechain.PointCount() < 3 )
2579 {
2580 // We have found multiple Altium files with polygon records containing nothing but
2581 // two coincident vertices. These polygons do not appear when opening the file in
2582 // Altium. https://gitlab.com/kicad/code/kicad/-/issues/8183
2583 // Also, polygons with less than 3 points are not supported in KiCad.
2584 return;
2585 }
2586
2587 std::unique_ptr<PCB_SHAPE> shape = std::make_unique<PCB_SHAPE>( aFootprint, SHAPE_T::POLY );
2588
2589 shape->SetPolyShape( linechain );
2590 shape->SetFilled( false );
2591 shape->SetLayer( klayer );
2592
2593 if( aElem.kind == ALTIUM_REGION_KIND::DASHED_OUTLINE )
2594 shape->SetStroke( STROKE_PARAMS( pcbIUScale.mmToIU( 0.1 ), LINE_STYLE::DASH ) );
2595 else
2596 shape->SetStroke( STROKE_PARAMS( pcbIUScale.mmToIU( 0.1 ), LINE_STYLE::SOLID ) );
2597
2598 aFootprint->Add( shape.release(), ADD_MODE::APPEND );
2599 }
2600 else
2601 {
2602 if( !m_footprintName.IsEmpty() )
2603 {
2604 if( m_reporter )
2605 {
2606 wxString msg;
2607 msg.Printf( _( "Error loading library '%s':\n"
2608 "Footprint %s contains polygon shape of kind %d (not yet supported)." ),
2609 m_library,
2611 aElem.kind );
2613 }
2614 }
2615 else
2616 {
2617 if( m_reporter )
2618 {
2619 wxString msg;
2620 msg.Printf( _( "Footprint %s contains polygon shape of kind %d (not yet supported)." ),
2621 aFootprint->GetReference(),
2622 aElem.kind );
2624 }
2625 }
2626 }
2627}
2628
2629
2631 PCB_LAYER_ID aLayer )
2632{
2633 SHAPE_LINE_CHAIN linechain;
2635
2636 if( linechain.PointCount() < 3 )
2637 {
2638 // We have found multiple Altium files with polygon records containing nothing
2639 // but two coincident vertices. These polygons do not appear when opening the
2640 // file in Altium. https://gitlab.com/kicad/code/kicad/-/issues/8183
2641 // Also, polygons with less than 3 points are not supported in KiCad.
2642 return;
2643 }
2644
2645 SHAPE_POLY_SET polySet;
2646 polySet.AddOutline( linechain );
2647
2648 for( const std::vector<ALTIUM_VERTICE>& hole : aElem.holes )
2649 {
2650 SHAPE_LINE_CHAIN hole_linechain;
2651 HelperShapeLineChainFromAltiumVertices( hole_linechain, hole );
2652
2653 if( hole_linechain.PointCount() < 3 )
2654 continue;
2655
2656 polySet.AddHole( hole_linechain );
2657 }
2658
2659 std::unique_ptr<PCB_SHAPE> shape = std::make_unique<PCB_SHAPE>( m_board, SHAPE_T::POLY );
2660
2661 shape->SetPolyShape( polySet );
2662 shape->SetFilled( true );
2663 shape->SetLayer( aLayer );
2664 shape->SetStroke( STROKE_PARAMS( 0 ) );
2665
2666 if( IsCopperLayer( aLayer ) && aElem.net != ALTIUM_NET_UNCONNECTED )
2667 {
2668 shape->SetNetCode( GetNetCode( aElem.net ) );
2669 }
2670
2671 m_board->Add( shape.release(), ADD_MODE::APPEND );
2672}
2673
2674
2676 const AREGION6& aElem,
2677 PCB_LAYER_ID aLayer,
2678 const int aPrimitiveIndex )
2679{
2680 SHAPE_LINE_CHAIN linechain;
2682
2683 if( linechain.PointCount() < 3 )
2684 {
2685 // We have found multiple Altium files with polygon records containing nothing
2686 // but two coincident vertices. These polygons do not appear when opening the
2687 // file in Altium. https://gitlab.com/kicad/code/kicad/-/issues/8183
2688 // Also, polygons with less than 3 points are not supported in KiCad.
2689 return;
2690 }
2691
2692 SHAPE_POLY_SET polySet;
2693 polySet.AddOutline( linechain );
2694
2695 for( const std::vector<ALTIUM_VERTICE>& hole : aElem.holes )
2696 {
2697 SHAPE_LINE_CHAIN hole_linechain;
2698 HelperShapeLineChainFromAltiumVertices( hole_linechain, hole );
2699
2700 if( hole_linechain.PointCount() < 3 )
2701 continue;
2702
2703 polySet.AddHole( hole_linechain );
2704 }
2705
2706 if( aLayer == F_Cu || aLayer == B_Cu )
2707 {
2708 // TODO(JE) padstacks -- not sure what should happen here yet
2709 std::unique_ptr<PAD> pad = std::make_unique<PAD>( aFootprint );
2710
2711 LSET padLayers;
2712 padLayers.set( aLayer );
2713
2714 pad->SetAttribute( PAD_ATTRIB::SMD );
2715 pad->SetShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::CUSTOM );
2716 pad->SetThermalSpokeAngle( ANGLE_90 );
2717
2718 int anchorSize = 1;
2719 VECTOR2I anchorPos = linechain.CPoint( 0 );
2720
2721 pad->SetAnchorPadShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::CIRCLE );
2722 pad->SetSize( PADSTACK::ALL_LAYERS, { anchorSize, anchorSize } );
2723 pad->SetPosition( anchorPos );
2724
2725 SHAPE_POLY_SET shapePolys = polySet;
2726 shapePolys.Move( -anchorPos );
2727 pad->AddPrimitivePoly( PADSTACK::ALL_LAYERS, shapePolys, 0, true );
2728
2729 auto& map = m_extendedPrimitiveInformationMaps[ALTIUM_RECORD::REGION];
2730 auto it = map.find( aPrimitiveIndex );
2731
2732 if( it != map.end() )
2733 {
2734 const AEXTENDED_PRIMITIVE_INFORMATION& info = it->second;
2735
2736 if( info.pastemaskexpansionmode == ALTIUM_MODE::MANUAL )
2737 {
2738 pad->SetLocalSolderPasteMargin(
2739 info.pastemaskexpansionmanual ? info.pastemaskexpansionmanual : 1 );
2740 }
2741
2742 if( info.soldermaskexpansionmode == ALTIUM_MODE::MANUAL )
2743 {
2744 pad->SetLocalSolderMaskMargin(
2745 info.soldermaskexpansionmanual ? info.soldermaskexpansionmanual : 1 );
2746 }
2747
2748 if( info.pastemaskexpansionmode != ALTIUM_MODE::NONE )
2749 padLayers.set( aLayer == F_Cu ? F_Paste : B_Paste );
2750
2751 if( info.soldermaskexpansionmode != ALTIUM_MODE::NONE )
2752 padLayers.set( aLayer == F_Cu ? F_Mask : B_Mask );
2753 }
2754
2755 pad->SetLayerSet( padLayers );
2756
2757 aFootprint->Add( pad.release(), ADD_MODE::APPEND );
2758 }
2759 else
2760 {
2761 std::unique_ptr<PCB_SHAPE> shape = std::make_unique<PCB_SHAPE>( aFootprint, SHAPE_T::POLY );
2762
2763 shape->SetPolyShape( polySet );
2764 shape->SetFilled( true );
2765 shape->SetLayer( aLayer );
2766 shape->SetStroke( STROKE_PARAMS( 0 ) );
2767
2768 aFootprint->Add( shape.release(), ADD_MODE::APPEND );
2769 }
2770}
2771
2772
2774 const CFB::COMPOUND_FILE_ENTRY* aEntry )
2775{
2776 if( m_progressReporter )
2777 m_progressReporter->Report( _( "Loading zone fills..." ) );
2778
2779 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
2780
2781 while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
2782 {
2783 checkpoint();
2784 AREGION6 elem( reader, false );
2785
2786 if( elem.polygon != ALTIUM_POLYGON_NONE )
2787 {
2788 if( m_polygons.size() <= elem.polygon )
2789 {
2790 THROW_IO_ERROR( wxString::Format( "Region stream tries to access polygon id %d "
2791 "of %d existing polygons.",
2792 elem.polygon,
2793 m_polygons.size() ) );
2794 }
2795
2796 ZONE* zone = m_polygons.at( elem.polygon );
2797
2798 if( zone == nullptr )
2799 {
2800 continue; // we know the zone id, but because we do not know the layer we did not
2801 // add it!
2802 }
2803
2804 PCB_LAYER_ID klayer = GetKicadLayer( elem.layer );
2805
2806 if( klayer == UNDEFINED_LAYER )
2807 continue; // Just skip it for now. Users can fill it themselves.
2808
2809 SHAPE_LINE_CHAIN linechain;
2810
2811 for( const ALTIUM_VERTICE& vertice : elem.outline )
2812 linechain.Append( vertice.position );
2813
2814 linechain.Append( elem.outline.at( 0 ).position );
2815 linechain.SetClosed( true );
2816
2817 SHAPE_POLY_SET fill;
2818 fill.AddOutline( linechain );
2819
2820 for( const std::vector<ALTIUM_VERTICE>& hole : elem.holes )
2821 {
2822 SHAPE_LINE_CHAIN hole_linechain;
2823
2824 for( const ALTIUM_VERTICE& vertice : hole )
2825 hole_linechain.Append( vertice.position );
2826
2827 hole_linechain.Append( hole.at( 0 ).position );
2828 hole_linechain.SetClosed( true );
2829 fill.AddHole( hole_linechain );
2830 }
2831
2832 if( zone->HasFilledPolysForLayer( klayer ) )
2833 fill.BooleanAdd( *zone->GetFill( klayer ) );
2834
2835 fill.Fracture();
2836
2837 zone->SetFilledPolysList( klayer, fill );
2838 zone->SetIsFilled( true );
2839 zone->SetNeedRefill( false );
2840 }
2841 }
2842
2843 if( reader.GetRemainingBytes() != 0 )
2844 THROW_IO_ERROR( wxT( "Regions6 stream is not fully parsed" ) );
2845}
2846
2847
2849 const CFB::COMPOUND_FILE_ENTRY* aEntry )
2850{
2851 if( m_progressReporter )
2852 m_progressReporter->Report( _( "Loading arcs..." ) );
2853
2854 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
2855
2856 for( int primitiveIndex = 0; reader.GetRemainingBytes() >= 4; primitiveIndex++ )
2857 {
2858 checkpoint();
2859 AARC6 elem( reader );
2860
2861 if( elem.component == ALTIUM_COMPONENT_NONE )
2862 {
2863 ConvertArcs6ToBoardItem( elem, primitiveIndex );
2864 }
2865 else
2866 {
2867 FOOTPRINT* footprint = HelperGetFootprint( elem.component );
2868 ConvertArcs6ToFootprintItem( footprint, elem, primitiveIndex, true );
2869 }
2870 }
2871
2872 if( reader.GetRemainingBytes() != 0 )
2873 THROW_IO_ERROR( "Arcs6 stream is not fully parsed" );
2874}
2875
2876
2878{
2879 if( aElem.startangle == 0. && aElem.endangle == 360. )
2880 {
2881 aShape->SetShape( SHAPE_T::CIRCLE );
2882
2883 // TODO: other variants to define circle?
2884 aShape->SetStart( aElem.center );
2885 aShape->SetEnd( aElem.center - VECTOR2I( 0, aElem.radius ) );
2886 }
2887 else
2888 {
2889 aShape->SetShape( SHAPE_T::ARC );
2890
2891 EDA_ANGLE includedAngle( aElem.endangle - aElem.startangle, DEGREES_T );
2892 EDA_ANGLE startAngle( aElem.endangle, DEGREES_T );
2893
2894 VECTOR2I startOffset = VECTOR2I( KiROUND( startAngle.Cos() * aElem.radius ),
2895 -KiROUND( startAngle.Sin() * aElem.radius ) );
2896
2897 aShape->SetCenter( aElem.center );
2898 aShape->SetStart( aElem.center + startOffset );
2899 aShape->SetArcAngleAndEnd( includedAngle.Normalize(), true );
2900 }
2901}
2902
2903
2904void ALTIUM_PCB::ConvertArcs6ToBoardItem( const AARC6& aElem, const int aPrimitiveIndex )
2905{
2906 if( aElem.polygon != ALTIUM_POLYGON_NONE && aElem.polygon != ALTIUM_POLYGON_BOARD )
2907 {
2908 if( m_polygons.size() <= aElem.polygon )
2909 {
2910 THROW_IO_ERROR( wxString::Format( "Tracks stream tries to access polygon id %u "
2911 "of %zu existing polygons.",
2912 aElem.polygon, m_polygons.size() ) );
2913 }
2914
2915 ZONE* zone = m_polygons.at( aElem.polygon );
2916
2917 if( zone == nullptr )
2918 {
2919 return; // we know the zone id, but because we do not know the layer we did not
2920 // add it!
2921 }
2922
2923 PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
2924
2925 if( klayer == UNDEFINED_LAYER )
2926 return; // Just skip it for now. Users can fill it themselves.
2927
2928 if( !zone->HasFilledPolysForLayer( klayer ) )
2929 return;
2930
2931 SHAPE_POLY_SET* fill = zone->GetFill( klayer );
2932
2933 // This is not the actual board item. We can use it to create the polygon for the region
2934 PCB_SHAPE shape( nullptr );
2935
2936 ConvertArcs6ToPcbShape( aElem, &shape );
2937 shape.SetStroke( STROKE_PARAMS( aElem.width, LINE_STYLE::SOLID ) );
2938
2939 shape.EDA_SHAPE::TransformShapeToPolygon( *fill, 0, ARC_HIGH_DEF, ERROR_INSIDE );
2940 // Will be simplified and fractured later
2941
2942 zone->SetIsFilled( true );
2943 zone->SetNeedRefill( false );
2944
2945 return;
2946 }
2947
2948 if( aElem.is_keepout || aElem.layer == ALTIUM_LAYER::KEEP_OUT_LAYER
2949 || IsAltiumLayerAPlane( aElem.layer ) )
2950 {
2951 // This is not the actual board item. We can use it to create the polygon for the region
2952 PCB_SHAPE shape( nullptr );
2953
2954 ConvertArcs6ToPcbShape( aElem, &shape );
2955 shape.SetStroke( STROKE_PARAMS( aElem.width, LINE_STYLE::SOLID ) );
2956
2958 }
2959 else
2960 {
2961 for( PCB_LAYER_ID klayer : GetKicadLayersToIterate( aElem.layer ) )
2962 ConvertArcs6ToBoardItemOnLayer( aElem, klayer );
2963 }
2964
2965 for( const auto& layerExpansionMask :
2966 HelperGetSolderAndPasteMaskExpansions( ALTIUM_RECORD::ARC, aPrimitiveIndex, aElem.layer ) )
2967 {
2968 int width = aElem.width + ( layerExpansionMask.second * 2 );
2969
2970 if( width > 1 )
2971 {
2972 std::unique_ptr<PCB_SHAPE> arc = std::make_unique<PCB_SHAPE>( m_board );
2973
2974 ConvertArcs6ToPcbShape( aElem, arc.get() );
2975 arc->SetStroke( STROKE_PARAMS( width, LINE_STYLE::SOLID ) );
2976 arc->SetLayer( layerExpansionMask.first );
2977
2978 m_board->Add( arc.release(), ADD_MODE::APPEND );
2979 }
2980 }
2981}
2982
2983
2985 const int aPrimitiveIndex, const bool aIsBoardImport )
2986{
2987 if( aElem.polygon != ALTIUM_POLYGON_NONE )
2988 {
2989 wxFAIL_MSG( wxString::Format( "Altium: Unexpected footprint Arc with polygon id %d",
2990 aElem.polygon ) );
2991 return;
2992 }
2993
2994 if( aElem.is_keepout || aElem.layer == ALTIUM_LAYER::KEEP_OUT_LAYER
2995 || IsAltiumLayerAPlane( aElem.layer ) )
2996 {
2997 // This is not the actual board item. We can use it to create the polygon for the region
2998 PCB_SHAPE shape( nullptr );
2999
3000 ConvertArcs6ToPcbShape( aElem, &shape );
3001 shape.SetStroke( STROKE_PARAMS( aElem.width, LINE_STYLE::SOLID ) );
3002
3003 HelperPcpShapeAsFootprintKeepoutRegion( aFootprint, shape, aElem.layer,
3004 aElem.keepoutrestrictions );
3005 }
3006 else
3007 {
3008 for( PCB_LAYER_ID klayer : GetKicadLayersToIterate( aElem.layer ) )
3009 {
3010 if( aIsBoardImport && IsCopperLayer( klayer ) && aElem.net != ALTIUM_NET_UNCONNECTED )
3011 {
3012 // Special case: do to not lose net connections in footprints
3013 ConvertArcs6ToBoardItemOnLayer( aElem, klayer );
3014 }
3015 else
3016 {
3017 ConvertArcs6ToFootprintItemOnLayer( aFootprint, aElem, klayer );
3018 }
3019 }
3020 }
3021
3022 for( const auto& layerExpansionMask :
3023 HelperGetSolderAndPasteMaskExpansions( ALTIUM_RECORD::ARC, aPrimitiveIndex, aElem.layer ) )
3024 {
3025 int width = aElem.width + ( layerExpansionMask.second * 2 );
3026
3027 if( width > 1 )
3028 {
3029 std::unique_ptr<PCB_SHAPE> arc = std::make_unique<PCB_SHAPE>( aFootprint );
3030
3031 ConvertArcs6ToPcbShape( aElem, arc.get() );
3032 arc->SetStroke( STROKE_PARAMS( width, LINE_STYLE::SOLID ) );
3033 arc->SetLayer( layerExpansionMask.first );
3034
3035 aFootprint->Add( arc.release(), ADD_MODE::APPEND );
3036 }
3037 }
3038}
3039
3040
3042{
3043 if( IsCopperLayer( aLayer ) && aElem.net != ALTIUM_NET_UNCONNECTED )
3044 {
3045 EDA_ANGLE includedAngle( aElem.endangle - aElem.startangle, DEGREES_T );
3046 EDA_ANGLE startAngle( aElem.endangle, DEGREES_T );
3047
3048 includedAngle.Normalize();
3049
3050 VECTOR2I startOffset = VECTOR2I( KiROUND( startAngle.Cos() * aElem.radius ),
3051 -KiROUND( startAngle.Sin() * aElem.radius ) );
3052
3053 if( includedAngle.AsDegrees() >= 0.1 )
3054 {
3055 // TODO: This is not the actual board item. We use it for now to calculate the arc points. This could be improved!
3056 PCB_SHAPE shape( nullptr, SHAPE_T::ARC );
3057
3058 shape.SetCenter( aElem.center );
3059 shape.SetStart( aElem.center + startOffset );
3060 shape.SetArcAngleAndEnd( includedAngle, true );
3061
3062 // Create actual arc
3063 SHAPE_ARC shapeArc( shape.GetCenter(), shape.GetStart(), shape.GetArcAngle(),
3064 aElem.width );
3065 std::unique_ptr<PCB_ARC> arc = std::make_unique<PCB_ARC>( m_board, &shapeArc );
3066
3067 arc->SetWidth( aElem.width );
3068 arc->SetLayer( aLayer );
3069 arc->SetNetCode( GetNetCode( aElem.net ) );
3070
3071 m_board->Add( arc.release(), ADD_MODE::APPEND );
3072 }
3073 }
3074 else
3075 {
3076 std::unique_ptr<PCB_SHAPE> arc = std::make_unique<PCB_SHAPE>(m_board);
3077
3078 ConvertArcs6ToPcbShape( aElem, arc.get() );
3079 arc->SetStroke( STROKE_PARAMS( aElem.width, LINE_STYLE::SOLID ) );
3080 arc->SetLayer( aLayer );
3081
3082 m_board->Add( arc.release(), ADD_MODE::APPEND );
3083 }
3084}
3085
3086
3088 PCB_LAYER_ID aLayer )
3089{
3090 std::unique_ptr<PCB_SHAPE> arc = std::make_unique<PCB_SHAPE>( aFootprint );
3091
3092 ConvertArcs6ToPcbShape( aElem, arc.get() );
3093 arc->SetStroke( STROKE_PARAMS( aElem.width, LINE_STYLE::SOLID ) );
3094 arc->SetLayer( aLayer );
3095
3096 aFootprint->Add( arc.release(), ADD_MODE::APPEND );
3097}
3098
3099
3101 const CFB::COMPOUND_FILE_ENTRY* aEntry )
3102{
3103 if( m_progressReporter )
3104 m_progressReporter->Report( _( "Loading pads..." ) );
3105
3106 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
3107
3108 while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
3109 {
3110 checkpoint();
3111 APAD6 elem( reader );
3112
3113 if( elem.component == ALTIUM_COMPONENT_NONE )
3114 {
3116 }
3117 else
3118 {
3119 FOOTPRINT* footprint = HelperGetFootprint( elem.component );
3120 ConvertPads6ToFootprintItem( footprint, elem );
3121 }
3122 }
3123
3124 if( reader.GetRemainingBytes() != 0 )
3125 THROW_IO_ERROR( wxT( "Pads6 stream is not fully parsed" ) );
3126}
3127
3128
3130{
3131 // It is possible to place altium pads on non-copper layers -> we need to interpolate them using drawings!
3132 if( !IsAltiumLayerCopper( aElem.layer ) && !IsAltiumLayerAPlane( aElem.layer )
3133 && aElem.layer != ALTIUM_LAYER::MULTI_LAYER )
3134 {
3136 }
3137 else
3138 {
3139 // We cannot add a pad directly into the PCB
3140 std::unique_ptr<FOOTPRINT> footprint = std::make_unique<FOOTPRINT>( m_board );
3141 footprint->SetPosition( aElem.position );
3142
3143 ConvertPads6ToFootprintItemOnCopper( footprint.get(), aElem );
3144
3145 m_board->Add( footprint.release(), ADD_MODE::APPEND );
3146 }
3147}
3148
3149
3151{
3152 std::unique_ptr<PAD> pad = std::make_unique<PAD>( aFootprint );
3153
3154 pad->SetNumber( "" );
3155 pad->SetNetCode( GetNetCode( aElem.net ) );
3156
3157 pad->SetPosition( aElem.position );
3158 pad->SetSize( PADSTACK::ALL_LAYERS, VECTOR2I( aElem.diameter, aElem.diameter ) );
3159 pad->SetDrillSize( VECTOR2I( aElem.holesize, aElem.holesize ) );
3160 pad->SetDrillShape( PAD_DRILL_SHAPE::CIRCLE );
3161 pad->SetShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::CIRCLE );
3162 pad->SetAttribute( PAD_ATTRIB::PTH );
3163
3164 // Pads are always through holes in KiCad
3165 pad->SetLayerSet( LSET().AllCuMask() );
3166
3167 if( aElem.viamode == ALTIUM_PAD_MODE::SIMPLE )
3168 {
3169 pad->Padstack().SetMode( PADSTACK::MODE::NORMAL );
3170 }
3171 else if( aElem.viamode == ALTIUM_PAD_MODE::TOP_MIDDLE_BOTTOM )
3172 {
3173 pad->Padstack().SetMode( PADSTACK::MODE::FRONT_INNER_BACK );
3174 pad->Padstack().SetSize( VECTOR2I( aElem.diameter_by_layer[1], aElem.diameter_by_layer[1] ),
3176 }
3177 else
3178 {
3179 pad->Padstack().SetMode( PADSTACK::MODE::CUSTOM );
3180 int altiumIdx = 0;
3181
3182 for( PCB_LAYER_ID layer : LAYER_RANGE( F_Cu, B_Cu, 32 ) )
3183 {
3184 pad->Padstack().SetSize( VECTOR2I( aElem.diameter_by_layer[altiumIdx],
3185 aElem.diameter_by_layer[altiumIdx] ), layer );
3186 altiumIdx++;
3187 }
3188 }
3189
3190 if( aElem.is_tent_top )
3191 {
3192 pad->Padstack().FrontOuterLayers().has_solder_mask = true;
3193 }
3194 else
3195 {
3196 pad->Padstack().FrontOuterLayers().has_solder_mask = false;
3197 pad->SetLayerSet( pad->GetLayerSet().set( F_Mask ) );
3198 }
3199
3200 if( aElem.is_tent_bottom )
3201 {
3202 pad->Padstack().BackOuterLayers().has_solder_mask = true;
3203 }
3204 else
3205 {
3206 pad->Padstack().BackOuterLayers().has_solder_mask = false;
3207 pad->SetLayerSet( pad->GetLayerSet().set( B_Mask ) );
3208 }
3209
3210 if( aElem.is_locked )
3211 pad->SetLocked( true );
3212
3213 if( aElem.soldermask_expansion_manual )
3214 {
3215 pad->Padstack().FrontOuterLayers().solder_mask_margin = aElem.soldermask_expansion_front;
3216 pad->Padstack().BackOuterLayers().solder_mask_margin = aElem.soldermask_expansion_back;
3217 }
3218
3219
3220 aFootprint->Add( pad.release(), ADD_MODE::APPEND );
3221}
3222
3223
3225{
3226 // It is possible to place altium pads on non-copper layers -> we need to interpolate them using drawings!
3227 if( !IsAltiumLayerCopper( aElem.layer ) && !IsAltiumLayerAPlane( aElem.layer )
3228 && aElem.layer != ALTIUM_LAYER::MULTI_LAYER )
3229 {
3230 ConvertPads6ToFootprintItemOnNonCopper( aFootprint, aElem );
3231 }
3232 else
3233 {
3234 ConvertPads6ToFootprintItemOnCopper( aFootprint, aElem );
3235 }
3236}
3237
3238
3240{
3241 std::unique_ptr<PAD> pad = std::make_unique<PAD>( aFootprint );
3242
3243 pad->SetNumber( aElem.name );
3244 pad->SetNetCode( GetNetCode( aElem.net ) );
3245
3246 pad->SetPosition( aElem.position );
3247 pad->SetOrientationDegrees( aElem.direction );
3248 pad->SetThermalSpokeAngle( ANGLE_90 );
3249
3250 if( aElem.holesize == 0 )
3251 {
3252 pad->SetAttribute( PAD_ATTRIB::SMD );
3253 }
3254 else
3255 {
3256 if( aElem.layer != ALTIUM_LAYER::MULTI_LAYER )
3257 {
3258 // TODO: I assume other values are possible as well?
3259 if( !m_footprintName.IsEmpty() )
3260 {
3261 if( m_reporter )
3262 {
3263 wxString msg;
3264 msg.Printf( _( "Error loading library '%s':\n"
3265 "Footprint %s pad %s is not marked as multilayer, but is a TH pad." ),
3266 m_library,
3268 aElem.name );
3270 }
3271 }
3272 else
3273 {
3274 if( m_reporter )
3275 {
3276 wxString msg;
3277 msg.Printf( _( "Footprint %s pad %s is not marked as multilayer, but is a TH pad." ),
3278 aFootprint->GetReference(),
3279 aElem.name );
3281 }
3282 }
3283 }
3284
3285 pad->SetAttribute( aElem.plated ? PAD_ATTRIB::PTH : PAD_ATTRIB::NPTH );
3286
3287 if( !aElem.sizeAndShape || aElem.sizeAndShape->holeshape == ALTIUM_PAD_HOLE_SHAPE::ROUND )
3288 {
3289 pad->SetDrillShape( PAD_DRILL_SHAPE::CIRCLE );
3290 pad->SetDrillSize( VECTOR2I( aElem.holesize, aElem.holesize ) );
3291 }
3292 else
3293 {
3294 switch( aElem.sizeAndShape->holeshape )
3295 {
3296 case ALTIUM_PAD_HOLE_SHAPE::ROUND:
3297 wxFAIL_MSG( wxT( "Round holes are handled before the switch" ) );
3298 break;
3299
3300 case ALTIUM_PAD_HOLE_SHAPE::SQUARE:
3301 if( !m_footprintName.IsEmpty() )
3302 {
3303 if( m_reporter )
3304 {
3305 wxString msg;
3306 msg.Printf( _( "Loading library '%s':\n"
3307 "Footprint %s pad %s has a square hole (not yet supported)." ),
3308 m_library,
3310 aElem.name );
3312 }
3313 }
3314 else
3315 {
3316 if( m_reporter )
3317 {
3318 wxString msg;
3319 msg.Printf( _( "Footprint %s pad %s has a square hole (not yet supported)." ),
3320 aFootprint->GetReference(),
3321 aElem.name );
3323 }
3324 }
3325
3326 pad->SetDrillShape( PAD_DRILL_SHAPE::CIRCLE );
3327 pad->SetDrillSize( VECTOR2I( aElem.holesize, aElem.holesize ) ); // Workaround
3328 // TODO: elem.sizeAndShape->slotsize was 0 in testfile. Either use holesize in
3329 // this case or rect holes have a different id
3330 break;
3331
3332 case ALTIUM_PAD_HOLE_SHAPE::SLOT:
3333 {
3334 pad->SetDrillShape( PAD_DRILL_SHAPE::OBLONG );
3335 EDA_ANGLE slotRotation( aElem.sizeAndShape->slotrotation, DEGREES_T );
3336
3337 slotRotation.Normalize();
3338
3339 if( slotRotation.IsHorizontal() )
3340 {
3341 pad->SetDrillSize( VECTOR2I( aElem.sizeAndShape->slotsize, aElem.holesize ) );
3342 }
3343 else if( slotRotation.IsVertical() )
3344 {
3345 pad->SetDrillSize( VECTOR2I( aElem.holesize, aElem.sizeAndShape->slotsize ) );
3346 }
3347 else
3348 {
3349 if( !m_footprintName.IsEmpty() )
3350 {
3351 if( m_reporter )
3352 {
3353 wxString msg;
3354 msg.Printf( _( "Loading library '%s':\n"
3355 "Footprint %s pad %s has a hole-rotation of %f degrees. "
3356 "KiCad only supports 90 degree rotations." ),
3357 m_library,
3359 aElem.name,
3360 slotRotation.AsDegrees() );
3362 }
3363 }
3364 else
3365 {
3366 if( m_reporter )
3367 {
3368 wxString msg;
3369 msg.Printf( _( "Footprint %s pad %s has a hole-rotation of %f degrees. "
3370 "KiCad only supports 90 degree rotations." ),
3371 aFootprint->GetReference(),
3372 aElem.name,
3373 slotRotation.AsDegrees() );
3375 }
3376 }
3377 }
3378
3379 break;
3380 }
3381
3382 default:
3383 case ALTIUM_PAD_HOLE_SHAPE::UNKNOWN:
3384 if( !m_footprintName.IsEmpty() )
3385 {
3386 if( m_reporter )
3387 {
3388 wxString msg;
3389 msg.Printf( _( "Error loading library '%s':\n"
3390 "Footprint %s pad %s uses a hole of unknown kind %d." ),
3391 m_library,
3393 aElem.name,
3394 aElem.sizeAndShape->holeshape );
3396 }
3397 }
3398 else
3399 {
3400 if( m_reporter )
3401 {
3402 wxString msg;
3403 msg.Printf( _( "Footprint %s pad %s uses a hole of unknown kind %d." ),
3404 aFootprint->GetReference(),
3405 aElem.name,
3406 aElem.sizeAndShape->holeshape );
3408 }
3409 }
3410
3411 pad->SetDrillShape( PAD_DRILL_SHAPE::CIRCLE );
3412 pad->SetDrillSize( VECTOR2I( aElem.holesize, aElem.holesize ) ); // Workaround
3413 break;
3414 }
3415 }
3416
3417 if( aElem.sizeAndShape )
3418 pad->SetOffset( PADSTACK::ALL_LAYERS, aElem.sizeAndShape->holeoffset[0] );
3419 }
3420
3421 PADSTACK& ps = pad->Padstack();
3422
3423 auto setCopperGeometry =
3424 [&]( PCB_LAYER_ID aLayer, ALTIUM_PAD_SHAPE aShape, const VECTOR2I& aSize )
3425 {
3426 int altLayer = CopperLayerToOrdinal( aLayer );
3427
3428 ps.SetSize( aSize, aLayer );
3429
3430 switch( aShape )
3431 {
3432 case ALTIUM_PAD_SHAPE::RECT:
3433 ps.SetShape( PAD_SHAPE::RECTANGLE, aLayer );
3434 break;
3435
3436 case ALTIUM_PAD_SHAPE::CIRCLE:
3437 if( aElem.sizeAndShape
3438 && aElem.sizeAndShape->alt_shape[altLayer] == ALTIUM_PAD_SHAPE_ALT::ROUNDRECT )
3439 {
3440 ps.SetShape( PAD_SHAPE::ROUNDRECT, aLayer ); // 100 = round, 0 = rectangular
3441 double ratio = aElem.sizeAndShape->cornerradius[altLayer] / 200.;
3442 ps.SetRoundRectRadiusRatio( ratio, aLayer );
3443 }
3444 else if( aElem.topsize.x == aElem.topsize.y )
3445 {
3446 ps.SetShape( PAD_SHAPE::CIRCLE, aLayer );
3447 }
3448 else
3449 {
3450 ps.SetShape( PAD_SHAPE::OVAL, aLayer );
3451 }
3452
3453 break;
3454
3455 case ALTIUM_PAD_SHAPE::OCTAGONAL:
3456 ps.SetShape( PAD_SHAPE::CHAMFERED_RECT, aLayer );
3458 ps.SetChamferRatio( 0.25, aLayer );
3459 break;
3460
3461 case ALTIUM_PAD_SHAPE::UNKNOWN:
3462 default:
3463 if( !m_footprintName.IsEmpty() )
3464 {
3465 if( m_reporter )
3466 {
3467 wxString msg;
3468 msg.Printf( _( "Error loading library '%s':\n"
3469 "Footprint %s pad %s uses an unknown pad-shape." ),
3470 m_library,
3472 aElem.name );
3474 }
3475 }
3476 else
3477 {
3478 if( m_reporter )
3479 {
3480 wxString msg;
3481 msg.Printf( _( "Footprint %s pad %s uses an unknown pad-shape." ),
3482 aFootprint->GetReference(),
3483 aElem.name );
3485 }
3486 }
3487 break;
3488 }
3489 };
3490
3491 switch( aElem.padmode )
3492 {
3493 case ALTIUM_PAD_MODE::SIMPLE:
3495 setCopperGeometry( PADSTACK::ALL_LAYERS, aElem.topshape, aElem.topsize );
3496 break;
3497
3498 case ALTIUM_PAD_MODE::TOP_MIDDLE_BOTTOM:
3500 setCopperGeometry( F_Cu, aElem.topshape, aElem.topsize );
3501 setCopperGeometry( PADSTACK::INNER_LAYERS, aElem.midshape, aElem.midsize );
3502 setCopperGeometry( B_Cu, aElem.botshape, aElem.botsize );
3503 break;
3504
3505 case ALTIUM_PAD_MODE::FULL_STACK:
3507
3508 setCopperGeometry( F_Cu, aElem.topshape, aElem.topsize );
3509 setCopperGeometry( B_Cu, aElem.botshape, aElem.botsize );
3510 setCopperGeometry( In1_Cu, aElem.midshape, aElem.midsize );
3511
3512 if( aElem.sizeAndShape )
3513 {
3514 size_t i = 0;
3515
3517 {
3518 setCopperGeometry( layer, aElem.sizeAndShape->inner_shape[i],
3519 VECTOR2I( aElem.sizeAndShape->inner_size[i].x,
3520 aElem.sizeAndShape->inner_size[i].y ) );
3521 i++;
3522 }
3523 }
3524
3525 break;
3526 }
3527
3528 if( pad->GetAttribute() == PAD_ATTRIB::NPTH && pad->HasHole() )
3529 {
3530 // KiCad likes NPTH pads to be the same size & shape as their holes
3531 pad->SetShape( PADSTACK::ALL_LAYERS, pad->GetDrillShape() == PAD_DRILL_SHAPE::CIRCLE ? PAD_SHAPE::CIRCLE
3532 : PAD_SHAPE::OVAL );
3533 pad->SetSize( PADSTACK::ALL_LAYERS, pad->GetDrillSize() );
3534 }
3535
3536 switch( aElem.layer )
3537 {
3538 case ALTIUM_LAYER::TOP_LAYER:
3539 pad->SetLayer( F_Cu );
3540 pad->SetLayerSet( PAD::SMDMask() );
3541 break;
3542
3543 case ALTIUM_LAYER::BOTTOM_LAYER:
3544 pad->SetLayer( B_Cu );
3545 pad->SetLayerSet( PAD::SMDMask().Flip() );
3546 break;
3547
3548 case ALTIUM_LAYER::MULTI_LAYER:
3549 pad->SetLayerSet( aElem.plated ? PAD::PTHMask() : PAD::UnplatedHoleMask() );
3550 break;
3551
3552 default:
3553 PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
3554 pad->SetLayer( klayer );
3555 pad->SetLayerSet( LSET( { klayer } ) );
3556 break;
3557 }
3558
3559 if( aElem.pastemaskexpansionmode == ALTIUM_MODE::MANUAL )
3560 pad->SetLocalSolderPasteMargin( aElem.pastemaskexpansionmanual );
3561
3562 if( aElem.soldermaskexpansionmode == ALTIUM_MODE::MANUAL )
3563 pad->SetLocalSolderMaskMargin( aElem.soldermaskexpansionmanual );
3564
3565 if( aElem.is_tent_top )
3566 pad->SetLayerSet( pad->GetLayerSet().reset( F_Mask ) );
3567
3568 if( aElem.is_tent_bottom )
3569 pad->SetLayerSet( pad->GetLayerSet().reset( B_Mask ) );
3570
3571 aFootprint->Add( pad.release(), ADD_MODE::APPEND );
3572}
3573
3574
3576{
3577 PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
3578
3579 if( klayer == UNDEFINED_LAYER )
3580 {
3581 if( m_reporter )
3582 {
3583 wxString msg;
3584 msg.Printf( _( "Non-copper pad %s found on an Altium layer (%d) with no KiCad "
3585 "equivalent. It has been moved to KiCad layer Eco1_User." ),
3586 aElem.name, aElem.layer );
3588 }
3589
3590 klayer = Eco1_User;
3591 }
3592
3593 std::unique_ptr<PCB_SHAPE> pad = std::make_unique<PCB_SHAPE>( m_board );
3594
3595 HelperParsePad6NonCopper( aElem, klayer, pad.get() );
3596
3597 m_board->Add( pad.release(), ADD_MODE::APPEND );
3598}
3599
3600
3602{
3603 PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
3604
3605 if( klayer == UNDEFINED_LAYER )
3606 {
3607 if( !m_footprintName.IsEmpty() )
3608 {
3609 if( m_reporter )
3610 {
3611 wxString msg;
3612 msg.Printf( _( "Loading library '%s':\n"
3613 "Footprint %s non-copper pad %s found on an Altium layer (%d) with no "
3614 "KiCad equivalent. It has been moved to KiCad layer Eco1_User." ),
3615 m_library,
3617 aElem.name,
3618 aElem.layer );
3620 }
3621 }
3622 else
3623 {
3624 if( m_reporter )
3625 {
3626 wxString msg;
3627 msg.Printf( _( "Footprint %s non-copper pad %s found on an Altium layer (%d) with no "
3628 "KiCad equivalent. It has been moved to KiCad layer Eco1_User." ),
3629 aFootprint->GetReference(),
3630 aElem.name,
3631 aElem.layer );
3633 }
3634 }
3635
3636 klayer = Eco1_User;
3637 }
3638
3639 std::unique_ptr<PCB_SHAPE> pad = std::make_unique<PCB_SHAPE>( aFootprint );
3640
3641 HelperParsePad6NonCopper( aElem, klayer, pad.get() );
3642
3643 aFootprint->Add( pad.release(), ADD_MODE::APPEND );
3644}
3645
3646
3648 PCB_SHAPE* aShape )
3649{
3650 if( aElem.net != ALTIUM_NET_UNCONNECTED )
3651 {
3652 if( m_reporter )
3653 {
3654 wxString msg;
3655 msg.Printf( _( "Non-copper pad %s is connected to a net, which is not supported." ),
3656 aElem.name );
3658 }
3659 }
3660
3661 if( aElem.holesize != 0 )
3662 {
3663 if( m_reporter )
3664 {
3665 wxString msg;
3666 msg.Printf( _( "Non-copper pad %s has a hole, which is not supported." ), aElem.name );
3668 }
3669 }
3670
3671 if( aElem.padmode != ALTIUM_PAD_MODE::SIMPLE )
3672 {
3673 if( m_reporter )
3674 {
3675 wxString msg;
3676 msg.Printf( _( "Non-copper pad %s has a complex pad stack (not yet supported)." ),
3677 aElem.name );
3679 }
3680 }
3681
3682 switch( aElem.topshape )
3683 {
3684 case ALTIUM_PAD_SHAPE::RECT:
3685 {
3686 // filled rect
3687 aShape->SetShape( SHAPE_T::POLY );
3688 aShape->SetFilled( true );
3689 aShape->SetLayer( aLayer );
3690 aShape->SetStroke( STROKE_PARAMS( 0 ) );
3691
3692 aShape->SetPolyPoints(
3693 { aElem.position + VECTOR2I( aElem.topsize.x / 2, aElem.topsize.y / 2 ),
3694 aElem.position + VECTOR2I( aElem.topsize.x / 2, -aElem.topsize.y / 2 ),
3695 aElem.position + VECTOR2I( -aElem.topsize.x / 2, -aElem.topsize.y / 2 ),
3696 aElem.position + VECTOR2I( -aElem.topsize.x / 2, aElem.topsize.y / 2 ) } );
3697
3698 if( aElem.direction != 0 )
3699 aShape->Rotate( aElem.position, EDA_ANGLE( aElem.direction, DEGREES_T ) );
3700 }
3701 break;
3702
3703 case ALTIUM_PAD_SHAPE::CIRCLE:
3704 if( aElem.sizeAndShape
3705 && aElem.sizeAndShape->alt_shape[0] == ALTIUM_PAD_SHAPE_ALT::ROUNDRECT )
3706 {
3707 // filled roundrect
3708 int cornerradius = aElem.sizeAndShape->cornerradius[0];
3709 int offset = ( std::min( aElem.topsize.x, aElem.topsize.y ) * cornerradius ) / 200;
3710
3711 aShape->SetLayer( aLayer );
3712 aShape->SetStroke( STROKE_PARAMS( offset * 2, LINE_STYLE::SOLID ) );
3713
3714 if( cornerradius < 100 )
3715 {
3716 int offsetX = aElem.topsize.x / 2 - offset;
3717 int offsetY = aElem.topsize.y / 2 - offset;
3718
3719 VECTOR2I p11 = aElem.position + VECTOR2I( offsetX, offsetY );
3720 VECTOR2I p12 = aElem.position + VECTOR2I( offsetX, -offsetY );
3721 VECTOR2I p22 = aElem.position + VECTOR2I( -offsetX, -offsetY );
3722 VECTOR2I p21 = aElem.position + VECTOR2I( -offsetX, offsetY );
3723
3724 aShape->SetShape( SHAPE_T::POLY );
3725 aShape->SetFilled( true );
3726 aShape->SetPolyPoints( { p11, p12, p22, p21 } );
3727 }
3728 else if( aElem.topsize.x == aElem.topsize.y )
3729 {
3730 // circle
3731 aShape->SetShape( SHAPE_T::CIRCLE );
3732 aShape->SetFilled( true );
3733 aShape->SetStart( aElem.position );
3734 aShape->SetEnd( aElem.position - VECTOR2I( 0, aElem.topsize.x / 4 ) );
3735 aShape->SetStroke( STROKE_PARAMS( aElem.topsize.x / 2, LINE_STYLE::SOLID ) );
3736 }
3737 else if( aElem.topsize.x < aElem.topsize.y )
3738 {
3739 // short vertical line
3740 aShape->SetShape( SHAPE_T::SEGMENT );
3741 VECTOR2I pointOffset( 0, ( aElem.topsize.y / 2 - aElem.topsize.x / 2 ) );
3742 aShape->SetStart( aElem.position + pointOffset );
3743 aShape->SetEnd( aElem.position - pointOffset );
3744 }
3745 else
3746 {
3747 // short horizontal line
3748 aShape->SetShape( SHAPE_T::SEGMENT );
3749 VECTOR2I pointOffset( ( aElem.topsize.x / 2 - aElem.topsize.y / 2 ), 0 );
3750 aShape->SetStart( aElem.position + pointOffset );
3751 aShape->SetEnd( aElem.position - pointOffset );
3752 }
3753
3754 if( aElem.direction != 0 )
3755 aShape->Rotate( aElem.position, EDA_ANGLE( aElem.direction, DEGREES_T ) );
3756 }
3757 else if( aElem.topsize.x == aElem.topsize.y )
3758 {
3759 // filled circle
3760 aShape->SetShape( SHAPE_T::CIRCLE );
3761 aShape->SetFilled( true );
3762 aShape->SetLayer( aLayer );
3763 aShape->SetStart( aElem.position );
3764 aShape->SetEnd( aElem.position - VECTOR2I( 0, aElem.topsize.x / 4 ) );
3765 aShape->SetStroke( STROKE_PARAMS( aElem.topsize.x / 2, LINE_STYLE::SOLID ) );
3766 }
3767 else
3768 {
3769 // short line
3770 aShape->SetShape( SHAPE_T::SEGMENT );
3771 aShape->SetLayer( aLayer );
3772 aShape->SetStroke( STROKE_PARAMS( std::min( aElem.topsize.x, aElem.topsize.y ),
3773 LINE_STYLE::SOLID ) );
3774
3775 if( aElem.topsize.x < aElem.topsize.y )
3776 {
3777 VECTOR2I offset( 0, ( aElem.topsize.y / 2 - aElem.topsize.x / 2 ) );
3778 aShape->SetStart( aElem.position + offset );
3779 aShape->SetEnd( aElem.position - offset );
3780 }
3781 else
3782 {
3783 VECTOR2I offset( ( aElem.topsize.x / 2 - aElem.topsize.y / 2 ), 0 );
3784 aShape->SetStart( aElem.position + offset );
3785 aShape->SetEnd( aElem.position - offset );
3786 }
3787
3788 if( aElem.direction != 0 )
3789 aShape->Rotate( aElem.position, EDA_ANGLE( aElem.direction, DEGREES_T ) );
3790 }
3791 break;
3792
3793 case ALTIUM_PAD_SHAPE::OCTAGONAL:
3794 {
3795 // filled octagon
3796 aShape->SetShape( SHAPE_T::POLY );
3797 aShape->SetFilled( true );
3798 aShape->SetLayer( aLayer );
3799 aShape->SetStroke( STROKE_PARAMS( 0 ) );
3800
3801 VECTOR2I p11 = aElem.position + VECTOR2I( aElem.topsize.x / 2, aElem.topsize.y / 2 );
3802 VECTOR2I p12 = aElem.position + VECTOR2I( aElem.topsize.x / 2, -aElem.topsize.y / 2 );
3803 VECTOR2I p22 = aElem.position + VECTOR2I( -aElem.topsize.x / 2, -aElem.topsize.y / 2 );
3804 VECTOR2I p21 = aElem.position + VECTOR2I( -aElem.topsize.x / 2, aElem.topsize.y / 2 );
3805
3806 int chamfer = std::min( aElem.topsize.x, aElem.topsize.y ) / 4;
3807 VECTOR2I chamferX( chamfer, 0 );
3808 VECTOR2I chamferY( 0, chamfer );
3809
3810 aShape->SetPolyPoints( { p11 - chamferX, p11 - chamferY, p12 + chamferY, p12 - chamferX,
3811 p22 + chamferX, p22 + chamferY, p21 - chamferY, p21 + chamferX } );
3812
3813 if( aElem.direction != 0. )
3814 aShape->Rotate( aElem.position, EDA_ANGLE( aElem.direction, DEGREES_T ) );
3815 }
3816 break;
3817
3818 case ALTIUM_PAD_SHAPE::UNKNOWN:
3819 default:
3820 if( m_reporter )
3821 {
3822 wxString msg;
3823 msg.Printf( _( "Non-copper pad %s uses an unknown pad-shape." ), aElem.name );
3825 }
3826
3827 break;
3828 }
3829}
3830
3831
3833 const CFB::COMPOUND_FILE_ENTRY* aEntry )
3834{
3835 if( m_progressReporter )
3836 m_progressReporter->Report( _( "Loading vias..." ) );
3837
3838 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
3839
3840 while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
3841 {
3842 checkpoint();
3843 AVIA6 elem( reader );
3844
3845 std::unique_ptr<PCB_VIA> via = std::make_unique<PCB_VIA>( m_board );
3846
3847 via->SetPosition( elem.position );
3848 via->SetDrill( elem.holesize );
3849 via->SetNetCode( GetNetCode( elem.net ) );
3850 via->SetLocked( elem.is_locked );
3851
3852 bool start_layer_outside = elem.layer_start == ALTIUM_LAYER::TOP_LAYER
3853 || elem.layer_start == ALTIUM_LAYER::BOTTOM_LAYER;
3854 bool end_layer_outside = elem.layer_end == ALTIUM_LAYER::TOP_LAYER
3855 || elem.layer_end == ALTIUM_LAYER::BOTTOM_LAYER;
3856
3857 if( start_layer_outside && end_layer_outside )
3858 {
3859 via->SetViaType( VIATYPE::THROUGH );
3860 }
3861 else if( ( !start_layer_outside ) || ( !end_layer_outside ) )
3862 {
3863 via->SetViaType( VIATYPE::BLIND_BURIED );
3864 }
3865
3866 // TODO: Altium has a specific flag for microvias, independent of start/end layer
3867#if 0
3868 if( something )
3869 via->SetViaType( VIATYPE::MICROVIA );
3870#endif
3871
3872 PCB_LAYER_ID start_klayer = GetKicadLayer( elem.layer_start );
3873 PCB_LAYER_ID end_klayer = GetKicadLayer( elem.layer_end );
3874
3875 if( !IsCopperLayer( start_klayer ) || !IsCopperLayer( end_klayer ) )
3876 {
3877 if( m_reporter )
3878 {
3879 wxString msg;
3880 msg.Printf( _( "Via from layer %d to %d uses a non-copper layer, which is not "
3881 "supported." ),
3882 elem.layer_start,
3883 elem.layer_end );
3885 }
3886
3887 continue; // just assume through-hole instead.
3888 }
3889
3890 // we need VIATYPE set!
3891 via->SetLayerPair( start_klayer, end_klayer );
3892
3893 switch( elem.viamode )
3894 {
3895 default:
3896 case ALTIUM_PAD_MODE::SIMPLE:
3897 via->SetWidth( PADSTACK::ALL_LAYERS, elem.diameter );
3898 break;
3899
3900 case ALTIUM_PAD_MODE::TOP_MIDDLE_BOTTOM:
3901 via->Padstack().SetMode( PADSTACK::MODE::FRONT_INNER_BACK );
3902 via->SetWidth( F_Cu, elem.diameter_by_layer[0] );
3903 via->SetWidth( PADSTACK::INNER_LAYERS, elem.diameter_by_layer[1] );
3904 via->SetWidth( B_Cu, elem.diameter_by_layer[31] );
3905 break;
3906
3907 case ALTIUM_PAD_MODE::FULL_STACK:
3908 {
3909 via->Padstack().SetMode( PADSTACK::MODE::CUSTOM );
3910
3911 for( PCB_LAYER_ID layer : LAYER_RANGE( F_Cu, B_Cu, MAX_CU_LAYERS ) )
3912 {
3913 int altiumLayer = CopperLayerToOrdinal( layer );
3914 wxCHECK2_MSG( altiumLayer < 32, break,
3915 "Altium importer expects 32 or fewer copper layers" );
3916
3917 via->SetWidth( layer, elem.diameter_by_layer[altiumLayer] );
3918 }
3919
3920 break;
3921 }
3922 }
3923
3925 {
3926 via->SetFrontTentingMode( elem.is_tent_top ? TENTING_MODE::TENTED
3927 : TENTING_MODE::NOT_TENTED );
3928 via->SetBackTentingMode( elem.is_tent_bottom ? TENTING_MODE::TENTED
3929 : TENTING_MODE::NOT_TENTED );
3930 }
3931
3932 m_board->Add( via.release(), ADD_MODE::APPEND );
3933 }
3934
3935 if( reader.GetRemainingBytes() != 0 )
3936 THROW_IO_ERROR( wxT( "Vias6 stream is not fully parsed" ) );
3937}
3938
3940 const CFB::COMPOUND_FILE_ENTRY* aEntry )
3941{
3942 if( m_progressReporter )
3943 m_progressReporter->Report( _( "Loading tracks..." ) );
3944
3945 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
3946
3947 for( int primitiveIndex = 0; reader.GetRemainingBytes() >= 4; primitiveIndex++ )
3948 {
3949 checkpoint();
3950 ATRACK6 elem( reader );
3951
3952 if( elem.component == ALTIUM_COMPONENT_NONE )
3953 {
3954 ConvertTracks6ToBoardItem( elem, primitiveIndex );
3955 }
3956 else
3957 {
3958 FOOTPRINT* footprint = HelperGetFootprint( elem.component );
3959 ConvertTracks6ToFootprintItem( footprint, elem, primitiveIndex, true );
3960 }
3961 }
3962
3963 if( reader.GetRemainingBytes() != 0 )
3964 THROW_IO_ERROR( "Tracks6 stream is not fully parsed" );
3965}
3966
3967
3968void ALTIUM_PCB::ConvertTracks6ToBoardItem( const ATRACK6& aElem, const int aPrimitiveIndex )
3969{
3970 if( aElem.polygon != ALTIUM_POLYGON_NONE && aElem.polygon != ALTIUM_POLYGON_BOARD )
3971 {
3972 if( m_polygons.size() <= aElem.polygon )
3973 {
3974 // Can happen when reading old Altium files: just skip this item
3975 if( m_reporter )
3976 {
3977 wxString msg;
3978 msg.Printf( wxT( "ATRACK6 stream tries to access polygon id %u "
3979 "of %u existing polygons; skipping it" ),
3980 static_cast<unsigned>( aElem.polygon ),
3981 static_cast<unsigned>( m_polygons.size() ) );
3983 }
3984
3985 return;
3986 }
3987
3988 ZONE* zone = m_polygons.at( aElem.polygon );
3989
3990 if( zone == nullptr )
3991 {
3992 return; // we know the zone id, but because we do not know the layer we did not
3993 // add it!
3994 }
3995
3996 PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
3997
3998 if( klayer == UNDEFINED_LAYER )
3999 return; // Just skip it for now. Users can fill it themselves.
4000
4001 if( !zone->HasFilledPolysForLayer( klayer ) )
4002 return;
4003
4004 SHAPE_POLY_SET* fill = zone->GetFill( klayer );
4005
4006 PCB_SHAPE shape( nullptr, SHAPE_T::SEGMENT );
4007 shape.SetStart( aElem.start );
4008 shape.SetEnd( aElem.end );
4009 shape.SetStroke( STROKE_PARAMS( aElem.width, LINE_STYLE::SOLID ) );
4010
4011 shape.EDA_SHAPE::TransformShapeToPolygon( *fill, 0, ARC_HIGH_DEF, ERROR_INSIDE );
4012 // Will be simplified and fractured later
4013
4014 zone->SetIsFilled( true );
4015 zone->SetNeedRefill( false );
4016
4017 return;
4018 }
4019
4020 if( aElem.is_keepout || aElem.layer == ALTIUM_LAYER::KEEP_OUT_LAYER
4021 || IsAltiumLayerAPlane( aElem.layer ) )
4022 {
4023 // This is not the actual board item. We can use it to create the polygon for the region
4024 PCB_SHAPE shape( nullptr, SHAPE_T::SEGMENT );
4025 shape.SetStart( aElem.start );
4026 shape.SetEnd( aElem.end );
4027 shape.SetStroke( STROKE_PARAMS( aElem.width, LINE_STYLE::SOLID ) );
4028
4030 }
4031 else
4032 {
4033 for( PCB_LAYER_ID klayer : GetKicadLayersToIterate( aElem.layer ) )
4034 ConvertTracks6ToBoardItemOnLayer( aElem, klayer );
4035 }
4036
4037 for( const auto& layerExpansionMask : HelperGetSolderAndPasteMaskExpansions(
4038 ALTIUM_RECORD::TRACK, aPrimitiveIndex, aElem.layer ) )
4039 {
4040 int width = aElem.width + ( layerExpansionMask.second * 2 );
4041 if( width > 1 )
4042 {
4043 std::unique_ptr<PCB_SHAPE> seg = std::make_unique<PCB_SHAPE>( m_board, SHAPE_T::SEGMENT );
4044
4045 seg->SetStart( aElem.start );
4046 seg->SetEnd( aElem.end );
4047 seg->SetStroke( STROKE_PARAMS( width, LINE_STYLE::SOLID ) );
4048 seg->SetLayer( layerExpansionMask.first );
4049
4050 m_board->Add( seg.release(), ADD_MODE::APPEND );
4051 }
4052 }
4053}
4054
4055
4057 const int aPrimitiveIndex,
4058 const bool aIsBoardImport )
4059{
4060 if( aElem.polygon != ALTIUM_POLYGON_NONE )
4061 {
4062 wxFAIL_MSG( wxString::Format( "Altium: Unexpected footprint Track with polygon id %u",
4063 (unsigned)aElem.polygon ) );
4064 return;
4065 }
4066
4067 if( aElem.is_keepout || aElem.layer == ALTIUM_LAYER::KEEP_OUT_LAYER
4068 || IsAltiumLayerAPlane( aElem.layer ) )
4069 {
4070 // This is not the actual board item. We can use it to create the polygon for the region
4071 PCB_SHAPE shape( nullptr, SHAPE_T::SEGMENT );
4072 shape.SetStart( aElem.start );
4073 shape.SetEnd( aElem.end );
4074 shape.SetStroke( STROKE_PARAMS( aElem.width, LINE_STYLE::SOLID ) );
4075
4076 HelperPcpShapeAsFootprintKeepoutRegion( aFootprint, shape, aElem.layer,
4077 aElem.keepoutrestrictions );
4078 }
4079 else
4080 {
4081 for( PCB_LAYER_ID klayer : GetKicadLayersToIterate( aElem.layer ) )
4082 {
4083 if( aIsBoardImport && IsCopperLayer( klayer ) && aElem.net != ALTIUM_NET_UNCONNECTED )
4084 {
4085 // Special case: do to not lose net connections in footprints
4086 ConvertTracks6ToBoardItemOnLayer( aElem, klayer );
4087 }
4088 else
4089 {
4090 ConvertTracks6ToFootprintItemOnLayer( aFootprint, aElem, klayer );
4091 }
4092 }
4093 }
4094
4095 for( const auto& layerExpansionMask : HelperGetSolderAndPasteMaskExpansions(
4096 ALTIUM_RECORD::TRACK, aPrimitiveIndex, aElem.layer ) )
4097 {
4098 int width = aElem.width + ( layerExpansionMask.second * 2 );
4099 if( width > 1 )
4100 {
4101 std::unique_ptr<PCB_SHAPE> seg = std::make_unique<PCB_SHAPE>( aFootprint, SHAPE_T::SEGMENT );
4102
4103 seg->SetStart( aElem.start );
4104 seg->SetEnd( aElem.end );
4105 seg->SetStroke( STROKE_PARAMS( width, LINE_STYLE::SOLID ) );
4106 seg->SetLayer( layerExpansionMask.first );
4107
4108 aFootprint->Add( seg.release(), ADD_MODE::APPEND );
4109 }
4110 }
4111}
4112
4113
4115{
4116 if( IsCopperLayer( aLayer ) && aElem.net != ALTIUM_NET_UNCONNECTED )
4117 {
4118 std::unique_ptr<PCB_TRACK> track = std::make_unique<PCB_TRACK>( m_board );
4119
4120 track->SetStart( aElem.start );
4121 track->SetEnd( aElem.end );
4122 track->SetWidth( aElem.width );
4123 track->SetLayer( aLayer );
4124 track->SetNetCode( GetNetCode( aElem.net ) );
4125
4126 m_board->Add( track.release(), ADD_MODE::APPEND );
4127 }
4128 else
4129 {
4130 std::unique_ptr<PCB_SHAPE> seg = std::make_unique<PCB_SHAPE>( m_board, SHAPE_T::SEGMENT );
4131
4132 seg->SetStart( aElem.start );
4133 seg->SetEnd( aElem.end );
4134 seg->SetStroke( STROKE_PARAMS( aElem.width, LINE_STYLE::SOLID ) );
4135 seg->SetLayer( aLayer );
4136
4137 m_board->Add( seg.release(), ADD_MODE::APPEND );
4138 }
4139}
4140
4141
4143 PCB_LAYER_ID aLayer )
4144{
4145 std::unique_ptr<PCB_SHAPE> seg = std::make_unique<PCB_SHAPE>( aFootprint, SHAPE_T::SEGMENT );
4146
4147 seg->SetStart( aElem.start );
4148 seg->SetEnd( aElem.end );
4149 seg->SetStroke( STROKE_PARAMS( aElem.width, LINE_STYLE::SOLID ) );
4150 seg->SetLayer( aLayer );
4151
4152 aFootprint->Add( seg.release(), ADD_MODE::APPEND );
4153}
4154
4155
4157 const CFB::COMPOUND_FILE_ENTRY* aEntry )
4158{
4159 if( m_progressReporter )
4160 m_progressReporter->Report( _( "Loading unicode strings..." ) );
4161
4162 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
4163
4165
4166 if( reader.GetRemainingBytes() != 0 )
4167 THROW_IO_ERROR( wxT( "WideStrings6 stream is not fully parsed" ) );
4168}
4169
4171 const CFB::COMPOUND_FILE_ENTRY* aEntry )
4172{
4173 if( m_progressReporter )
4174 m_progressReporter->Report( _( "Loading text..." ) );
4175
4176 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
4177
4178 while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
4179 {
4180 checkpoint();
4181 ATEXT6 elem( reader, m_unicodeStrings );
4182
4183 if( elem.component == ALTIUM_COMPONENT_NONE )
4184 {
4186 }
4187 else
4188 {
4189 FOOTPRINT* footprint = HelperGetFootprint( elem.component );
4190 ConvertTexts6ToFootprintItem( footprint, elem );
4191 }
4192 }
4193
4194 if( reader.GetRemainingBytes() != 0 )
4195 THROW_IO_ERROR( wxT( "Texts6 stream is not fully parsed" ) );
4196}
4197
4198
4200{
4201 if( aElem.fonttype == ALTIUM_TEXT_TYPE::BARCODE )
4202 {
4203 if( m_reporter )
4204 {
4205 wxString msg;
4206 msg.Printf( _( "Ignored barcode on Altium layer %d (not yet supported)." ),
4207 aElem.layer );
4209 }
4210
4211 return;
4212 }
4213
4214 for( PCB_LAYER_ID klayer : GetKicadLayersToIterate( aElem.layer ) )
4215 ConvertTexts6ToBoardItemOnLayer( aElem, klayer );
4216}
4217
4218
4220{
4221 if( aElem.fonttype == ALTIUM_TEXT_TYPE::BARCODE )
4222 {
4223 if( !m_footprintName.IsEmpty() )
4224 {
4225 if( m_reporter )
4226 {
4227 wxString msg;
4228 msg.Printf( _( "Error loading library '%s':\n"
4229 "Footprint %s contains barcode on Altium layer %d (not yet supported)." ),
4230 m_library,
4232 aElem.layer );
4234 }
4235 }
4236 else
4237 {
4238 if( m_reporter )
4239 {
4240 wxString msg;
4241 msg.Printf( _( "Footprint %s contains barcode on Altium layer %d (not yet supported)." ),
4242 aFootprint->GetReference(),
4243 aElem.layer );
4245 }
4246 }
4247
4248 return;
4249 }
4250
4251 for( PCB_LAYER_ID klayer : GetKicadLayersToIterate( aElem.layer ) )
4252 ConvertTexts6ToFootprintItemOnLayer( aFootprint, aElem, klayer );
4253}
4254
4255
4257{
4258 std::unique_ptr<PCB_TEXTBOX> pcbTextbox = std::make_unique<PCB_TEXTBOX>( m_board );
4259 std::unique_ptr<PCB_TEXT> pcbText = std::make_unique<PCB_TEXT>( m_board );
4260
4261 bool isTextbox = aElem.isFrame && !aElem.isInverted; // Textbox knockout is not supported
4262
4263 static const std::map<wxString, wxString> variableMap = {
4264 { "LAYER_NAME", "LAYER" },
4265 { "PRINT_DATE", "CURRENT_DATE"},
4266 };
4267
4268 wxString kicadText = AltiumPcbSpecialStringsToKiCadStrings( aElem.text, variableMap );
4269 BOARD_ITEM* item = pcbText.get();
4270 EDA_TEXT* text = pcbText.get();
4271 int margin = aElem.isOffsetBorder ? aElem.text_offset_width : aElem.margin_border_width;
4272
4273 if( isTextbox )
4274 {
4275 item = pcbTextbox.get();
4276 text = pcbTextbox.get();
4277
4279 HelperSetTextboxAlignmentAndPos( aElem, pcbTextbox.get() );
4280 }
4281 else
4282 {
4285 }
4286
4287 text->SetText( kicadText );
4288 item->SetLayer( aLayer );
4289 item->SetIsKnockout( aElem.isInverted );
4290
4291 if( isTextbox )
4292 m_board->Add( pcbTextbox.release(), ADD_MODE::APPEND );
4293 else
4294 m_board->Add( pcbText.release(), ADD_MODE::APPEND );
4295}
4296
4297
4299 PCB_LAYER_ID aLayer )
4300{
4301 std::unique_ptr<PCB_TEXTBOX> fpTextbox = std::make_unique<PCB_TEXTBOX>( aFootprint );
4302 std::unique_ptr<PCB_TEXT> fpText = std::make_unique<PCB_TEXT>( aFootprint );
4303
4304 BOARD_ITEM* item = fpText.get();
4305 EDA_TEXT* text = fpText.get();
4306 PCB_FIELD* field = nullptr;
4307
4308 bool isTextbox = aElem.isFrame && !aElem.isInverted; // Textbox knockout is not supported
4309 bool toAdd = false;
4310
4311 if( aElem.isDesignator )
4312 {
4313 item = &aFootprint->Reference(); // TODO: handle multiple layers
4314 text = &aFootprint->Reference();
4315 field = &aFootprint->Reference();
4316 }
4317 else if( aElem.isComment )
4318 {
4319 item = &aFootprint->Value(); // TODO: handle multiple layers
4320 text = &aFootprint->Value();
4321 field = &aFootprint->Value();
4322 }
4323 else
4324 {
4325 item = fpText.get();
4326 text = fpText.get();
4327 toAdd = true;
4328 }
4329
4330 static const std::map<wxString, wxString> variableMap = {
4331 { "DESIGNATOR", "REFERENCE" },
4332 { "COMMENT", "VALUE" },
4333 { "VALUE", "ALTIUM_VALUE" },
4334 { "LAYER_NAME", "LAYER" },
4335 { "PRINT_DATE", "CURRENT_DATE"},
4336 };
4337
4338 if( isTextbox )
4339 {
4340 item = fpTextbox.get();
4341 text = fpTextbox.get();
4342
4344 HelperSetTextboxAlignmentAndPos( aElem, fpTextbox.get() );
4345 }
4346 else
4347 {
4350 }
4351
4352 wxString kicadText = AltiumPcbSpecialStringsToKiCadStrings( aElem.text, variableMap );
4353
4354 text->SetText( kicadText );
4355 text->SetKeepUpright( false );
4356 item->SetLayer( aLayer );
4357 item->SetIsKnockout( aElem.isInverted );
4358
4359 if( toAdd )
4360 {
4361 if( isTextbox )
4362 aFootprint->Add( fpTextbox.release(), ADD_MODE::APPEND );
4363 else
4364 aFootprint->Add( fpText.release(), ADD_MODE::APPEND );
4365 }
4366}
4367
4368
4370{
4371 int margin = aElem.isOffsetBorder ? aElem.text_offset_width : aElem.margin_border_width;
4372
4373 // Altium textboxes do not have borders
4374 aTextbox->SetBorderEnabled( false );
4375
4376 // Calculate position
4377 VECTOR2I kposition = aElem.position;
4378
4379 if( aElem.isMirrored )
4380 kposition.x -= aElem.textbox_rect_width;
4381
4382 kposition.y -= aElem.textbox_rect_height;
4383
4384#if 0
4385 // Compensate for KiCad's textbox margin
4386 int charWidth = aTextbox->GetTextWidth();
4387 int charHeight = aTextbox->GetTextHeight();
4388
4389 VECTOR2I kicadMargin;
4390
4391 if( !aTextbox->GetFont() || aTextbox->GetFont()->IsStroke() )
4392 kicadMargin = VECTOR2I( charWidth * 0.933, charHeight * 0.67 );
4393 else
4394 kicadMargin = VECTOR2I( charWidth * 0.808, charHeight * 0.844 );
4395
4396 aTextbox->SetEnd( VECTOR2I( aElem.textbox_rect_width, aElem.textbox_rect_height )
4397 + kicadMargin * 2 - margin * 2 );
4398
4399 kposition = kposition - kicadMargin + margin;
4400#else
4401 aTextbox->SetMarginBottom( margin );
4402 aTextbox->SetMarginLeft( margin );
4403 aTextbox->SetMarginRight( margin );
4404 aTextbox->SetMarginTop( margin );
4405
4406 aTextbox->SetEnd( VECTOR2I( aElem.textbox_rect_width, aElem.textbox_rect_height ) );
4407#endif
4408
4409 RotatePoint( kposition, aElem.position, EDA_ANGLE( aElem.rotation, DEGREES_T ) );
4410
4411 aTextbox->SetPosition( kposition );
4412
4413 ALTIUM_TEXT_POSITION justification = aElem.isJustificationValid
4415 : ALTIUM_TEXT_POSITION::LEFT_BOTTOM;
4416
4417 switch( justification )
4418 {
4419 case ALTIUM_TEXT_POSITION::LEFT_TOP:
4420 case ALTIUM_TEXT_POSITION::LEFT_CENTER:
4421 case ALTIUM_TEXT_POSITION::LEFT_BOTTOM:
4424 break;
4425 case ALTIUM_TEXT_POSITION::CENTER_TOP:
4426 case ALTIUM_TEXT_POSITION::CENTER_CENTER:
4427 case ALTIUM_TEXT_POSITION::CENTER_BOTTOM:
4430 break;
4431 case ALTIUM_TEXT_POSITION::RIGHT_TOP:
4432 case ALTIUM_TEXT_POSITION::RIGHT_CENTER:
4433 case ALTIUM_TEXT_POSITION::RIGHT_BOTTOM:
4436 break;
4437 default:
4438 if( m_reporter )
4439 {
4440 wxString msg;
4441 msg.Printf( _( "Unknown textbox justification %d, aText %s" ), justification,
4442 aElem.text );
4444 }
4445
4448 break;
4449 }
4450
4451 aTextbox->SetTextAngle( EDA_ANGLE( aElem.rotation, DEGREES_T ) );
4452}
4453
4454
4456{
4457 VECTOR2I kposition = aElem.position;
4458
4459 int margin = aElem.isOffsetBorder ? aElem.text_offset_width : aElem.margin_border_width;
4460 int rectWidth = aElem.textbox_rect_width - margin * 2;
4461 int rectHeight = aElem.height;
4462
4463 if( aElem.isMirrored )
4464 rectWidth = -rectWidth;
4465
4466 ALTIUM_TEXT_POSITION justification = aElem.isJustificationValid
4468 : ALTIUM_TEXT_POSITION::LEFT_BOTTOM;
4469
4470 switch( justification )
4471 {
4472 case ALTIUM_TEXT_POSITION::LEFT_TOP:
4475
4476 kposition.y -= rectHeight;
4477 break;
4478 case ALTIUM_TEXT_POSITION::LEFT_CENTER:
4481
4482 kposition.y -= rectHeight / 2;
4483 break;
4484 case ALTIUM_TEXT_POSITION::LEFT_BOTTOM:
4487 break;
4488 case ALTIUM_TEXT_POSITION::CENTER_TOP:
4491
4492 kposition.x += rectWidth / 2;
4493 kposition.y -= rectHeight;
4494 break;
4495 case ALTIUM_TEXT_POSITION::CENTER_CENTER:
4498
4499 kposition.x += rectWidth / 2;
4500 kposition.y -= rectHeight / 2;
4501 break;
4502 case ALTIUM_TEXT_POSITION::CENTER_BOTTOM:
4505
4506 kposition.x += rectWidth / 2;
4507 break;
4508 case ALTIUM_TEXT_POSITION::RIGHT_TOP:
4511
4512 kposition.x += rectWidth;
4513 kposition.y -= rectHeight;
4514 break;
4515 case ALTIUM_TEXT_POSITION::RIGHT_CENTER:
4518
4519 kposition.x += rectWidth;
4520 kposition.y -= rectHeight / 2;
4521 break;
4522 case ALTIUM_TEXT_POSITION::RIGHT_BOTTOM:
4525
4526 kposition.x += rectWidth;
4527 break;
4528 default:
4531 break;
4532 }
4533
4534 int charWidth = aText->GetTextWidth();
4535 int charHeight = aText->GetTextHeight();
4536
4537 // Correct for KiCad's baseline offset.
4538 // Text height and font must be set correctly before calling.
4539 if( !aText->GetFont() || aText->GetFont()->IsStroke() )
4540 {
4541 switch( aText->GetVertJustify() )
4542 {
4543 case GR_TEXT_V_ALIGN_TOP: kposition.y -= charHeight * 0.0407; break;
4544 case GR_TEXT_V_ALIGN_CENTER: kposition.y += charHeight * 0.0355; break;
4545 case GR_TEXT_V_ALIGN_BOTTOM: kposition.y += charHeight * 0.1225; break;
4546 default: break;
4547 }
4548 }
4549 else
4550 {
4551 switch( aText->GetVertJustify() )
4552 {
4553 case GR_TEXT_V_ALIGN_TOP: kposition.y -= charWidth * 0.016; break;
4554 case GR_TEXT_V_ALIGN_CENTER: kposition.y += charWidth * 0.085; break;
4555 case GR_TEXT_V_ALIGN_BOTTOM: kposition.y += charWidth * 0.17; break;
4556 default: break;
4557 }
4558 }
4559
4560 RotatePoint( kposition, aElem.position, EDA_ANGLE( aElem.rotation, DEGREES_T ) );
4561
4562 aText->SetTextPos( kposition );
4563 aText->SetTextAngle( EDA_ANGLE( aElem.rotation, DEGREES_T ) );
4564}
4565
4566
4568{
4569 aEdaText.SetTextSize( VECTOR2I( aElem.height, aElem.height ) );
4570
4571 if( aElem.fonttype == ALTIUM_TEXT_TYPE::TRUETYPE )
4572 {
4573 KIFONT::FONT* font = KIFONT::FONT::GetFont( aElem.fontname, aElem.isBold, aElem.isItalic );
4574 aEdaText.SetFont( font );
4575
4576 if( font->IsOutline() )
4577 {
4578 // TODO: why is this required? Somehow, truetype size is calculated differently
4579 if( font->GetName().Contains( wxS( "Arial" ) ) )
4580 aEdaText.SetTextSize( VECTOR2I( aElem.height * 0.63, aElem.height * 0.63 ) );
4581 else
4582 aEdaText.SetTextSize( VECTOR2I( aElem.height * 0.5, aElem.height * 0.5 ) );
4583 }
4584 }
4585
4586 aEdaText.SetTextThickness( aElem.strokewidth );
4587 aEdaText.SetBoldFlag( aElem.isBold );
4588 aEdaText.SetItalic( aElem.isItalic );
4589 aEdaText.SetMirrored( aElem.isMirrored );
4590}
4591
4592
4594 const CFB::COMPOUND_FILE_ENTRY* aEntry )
4595{
4596 if( m_progressReporter )
4597 m_progressReporter->Report( _( "Loading rectangles..." ) );
4598
4599 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
4600
4601 while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
4602 {
4603 checkpoint();
4604 AFILL6 elem( reader );
4605
4606 if( elem.component == ALTIUM_COMPONENT_NONE )
4607 {
4609 }
4610 else
4611 {
4612 FOOTPRINT* footprint = HelperGetFootprint( elem.component );
4613 ConvertFills6ToFootprintItem( footprint, elem, true );
4614 }
4615 }
4616
4617 if( reader.GetRemainingBytes() != 0 )
4618 THROW_IO_ERROR( "Fills6 stream is not fully parsed" );
4619}
4620
4621
4623{
4624 if( aElem.is_keepout || aElem.layer == ALTIUM_LAYER::KEEP_OUT_LAYER )
4625 {
4626 // This is not the actual board item. We can use it to create the polygon for the region
4627 PCB_SHAPE shape( nullptr, SHAPE_T::RECTANGLE );
4628
4629 shape.SetStart( aElem.pos1 );
4630 shape.SetEnd( aElem.pos2 );
4631 shape.SetFilled( true );
4632 shape.SetStroke( STROKE_PARAMS( 0, LINE_STYLE::SOLID ) );
4633
4634 if( aElem.rotation != 0. )
4635 {
4636 VECTOR2I center( aElem.pos1.x / 2 + aElem.pos2.x / 2,
4637 aElem.pos1.y / 2 + aElem.pos2.y / 2 );
4638 shape.Rotate( center, EDA_ANGLE( aElem.rotation, DEGREES_T ) );
4639 }
4640
4642 }
4643 else
4644 {
4645 for( PCB_LAYER_ID klayer : GetKicadLayersToIterate( aElem.layer ) )
4646 ConvertFills6ToBoardItemOnLayer( aElem, klayer );
4647 }
4648}
4649
4650
4652 const bool aIsBoardImport )
4653{
4654 if( aElem.is_keepout
4655 || aElem.layer == ALTIUM_LAYER::KEEP_OUT_LAYER ) // TODO: what about plane layers?
4656 {
4657 // This is not the actual board item. We can use it to create the polygon for the region
4658 PCB_SHAPE shape( nullptr, SHAPE_T::RECTANGLE );
4659
4660 shape.SetStart( aElem.pos1 );
4661 shape.SetEnd( aElem.pos2 );
4662 shape.SetFilled( true );
4663 shape.SetStroke( STROKE_PARAMS( 0, LINE_STYLE::SOLID ) );
4664
4665 if( aElem.rotation != 0. )
4666 {
4667 VECTOR2I center( aElem.pos1.x / 2 + aElem.pos2.x / 2,
4668 aElem.pos1.y / 2 + aElem.pos2.y / 2 );
4669 shape.Rotate( center, EDA_ANGLE( aElem.rotation, DEGREES_T ) );
4670 }
4671
4672 HelperPcpShapeAsFootprintKeepoutRegion( aFootprint, shape, aElem.layer,
4673 aElem.keepoutrestrictions );
4674 }
4675 else if( aIsBoardImport && IsAltiumLayerCopper( aElem.layer )
4676 && aElem.net != ALTIUM_NET_UNCONNECTED )
4677 {
4678 // Special case: do to not lose net connections in footprints
4679 for( PCB_LAYER_ID klayer : GetKicadLayersToIterate( aElem.layer ) )
4680 ConvertFills6ToBoardItemOnLayer( aElem, klayer );
4681 }
4682 else
4683 {
4684 for( PCB_LAYER_ID klayer : GetKicadLayersToIterate( aElem.layer ) )
4685 ConvertFills6ToFootprintItemOnLayer( aFootprint, aElem, klayer );
4686 }
4687}
4688
4689
4691{
4692 std::unique_ptr<PCB_SHAPE> fill = std::make_unique<PCB_SHAPE>( m_board, SHAPE_T::RECTANGLE );
4693
4694 fill->SetFilled( true );
4695 fill->SetLayer( aLayer );
4696 fill->SetStroke( STROKE_PARAMS( 0 ) );
4697
4698 fill->SetStart( aElem.pos1 );
4699 fill->SetEnd( aElem.pos2 );
4700
4701 if( IsCopperLayer( aLayer ) && aElem.net != ALTIUM_NET_UNCONNECTED )
4702 {
4703 fill->SetNetCode( GetNetCode( aElem.net ) );
4704 }
4705
4706 if( aElem.rotation != 0. )
4707 {
4708 // TODO: Do we need SHAPE_T::POLY for non 90° rotations?
4709 VECTOR2I center( aElem.pos1.x / 2 + aElem.pos2.x / 2,
4710 aElem.pos1.y / 2 + aElem.pos2.y / 2 );
4711 fill->Rotate( center, EDA_ANGLE( aElem.rotation, DEGREES_T ) );
4712 }
4713
4714 m_board->Add( fill.release(), ADD_MODE::APPEND );
4715}
4716
4717
4719 PCB_LAYER_ID aLayer )
4720{
4721 if( aLayer == F_Cu || aLayer == B_Cu )
4722 {
4723 std::unique_ptr<PAD> pad = std::make_unique<PAD>( aFootprint );
4724
4725 LSET padLayers;
4726 padLayers.set( aLayer );
4727
4728 pad->SetAttribute( PAD_ATTRIB::SMD );
4729 EDA_ANGLE rotation( aElem.rotation, DEGREES_T );
4730
4731 // Handle rotation multiples of 90 degrees
4732 if( rotation.IsCardinal() )
4733 {
4734 pad->SetShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::RECTANGLE );
4735
4736 int width = std::abs( aElem.pos2.x - aElem.pos1.x );
4737 int height = std::abs( aElem.pos2.y - aElem.pos1.y );
4738
4739 // Swap width and height for 90 or 270 degree rotations
4740 if( rotation.IsCardinal90() )
4741 std::swap( width, height );
4742
4743 pad->SetSize( PADSTACK::ALL_LAYERS, { width, height } );
4744 pad->SetPosition( aElem.pos1 / 2 + aElem.pos2 / 2 );
4745 }
4746 else
4747 {
4748 pad->SetShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::CUSTOM );
4749
4750 int anchorSize = std::min( std::abs( aElem.pos2.x - aElem.pos1.x ),
4751 std::abs( aElem.pos2.y - aElem.pos1.y ) );
4752 VECTOR2I anchorPos = aElem.pos1;
4753
4754 pad->SetAnchorPadShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::CIRCLE );
4755 pad->SetSize( PADSTACK::ALL_LAYERS, { anchorSize, anchorSize } );
4756 pad->SetPosition( anchorPos );
4757
4758 SHAPE_POLY_SET shapePolys;
4759 shapePolys.NewOutline();
4760 shapePolys.Append( aElem.pos1.x - anchorPos.x, aElem.pos1.y - anchorPos.y );
4761 shapePolys.Append( aElem.pos2.x - anchorPos.x, aElem.pos1.y - anchorPos.y );
4762 shapePolys.Append( aElem.pos2.x - anchorPos.x, aElem.pos2.y - anchorPos.y );
4763 shapePolys.Append( aElem.pos1.x - anchorPos.x, aElem.pos2.y - anchorPos.y );
4764 shapePolys.Outline( 0 ).SetClosed( true );
4765
4766 VECTOR2I center( aElem.pos1.x / 2 + aElem.pos2.x / 2 - anchorPos.x,
4767 aElem.pos1.y / 2 + aElem.pos2.y / 2 - anchorPos.y );
4768 shapePolys.Rotate( EDA_ANGLE( aElem.rotation, DEGREES_T ), center );
4769 pad->AddPrimitivePoly( F_Cu, shapePolys, 0, true );
4770 }
4771
4772 pad->SetThermalSpokeAngle( ANGLE_90 );
4773 pad->SetLayerSet( padLayers );
4774
4775 aFootprint->Add( pad.release(), ADD_MODE::APPEND );
4776 }
4777 else
4778 {
4779 std::unique_ptr<PCB_SHAPE> fill =
4780 std::make_unique<PCB_SHAPE>( aFootprint, SHAPE_T::RECTANGLE );
4781
4782 fill->SetFilled( true );
4783 fill->SetLayer( aLayer );
4784 fill->SetStroke( STROKE_PARAMS( 0 ) );
4785
4786 fill->SetStart( aElem.pos1 );
4787 fill->SetEnd( aElem.pos2 );
4788
4789 if( aElem.rotation != 0. )
4790 {
4791 VECTOR2I center( aElem.pos1.x / 2 + aElem.pos2.x / 2,
4792 aElem.pos1.y / 2 + aElem.pos2.y / 2 );
4793 fill->Rotate( center, EDA_ANGLE( aElem.rotation, DEGREES_T ) );
4794 }
4795
4796 aFootprint->Add( fill.release(), ADD_MODE::APPEND );
4797 }
4798}
4799
4800
4801void ALTIUM_PCB::HelperSetZoneLayers( ZONE& aZone, const ALTIUM_LAYER aAltiumLayer )
4802{
4803 LSET layerSet;
4804
4805 for( PCB_LAYER_ID klayer : GetKicadLayersToIterate( aAltiumLayer ) )
4806 layerSet.set( klayer );
4807
4808 aZone.SetLayerSet( layerSet );
4809}
4810
4811
4812void ALTIUM_PCB::HelperSetZoneKeepoutRestrictions( ZONE& aZone, const uint8_t aKeepoutRestrictions )
4813{
4814 bool keepoutRestrictionVia = ( aKeepoutRestrictions & 0x01 ) != 0;
4815 bool keepoutRestrictionTrack = ( aKeepoutRestrictions & 0x02 ) != 0;
4816 bool keepoutRestrictionCopper = ( aKeepoutRestrictions & 0x04 ) != 0;
4817 bool keepoutRestrictionSMDPad = ( aKeepoutRestrictions & 0x08 ) != 0;
4818 bool keepoutRestrictionTHPad = ( aKeepoutRestrictions & 0x10 ) != 0;
4819
4820 aZone.SetDoNotAllowVias( keepoutRestrictionVia );
4821 aZone.SetDoNotAllowTracks( keepoutRestrictionTrack );
4822 aZone.SetDoNotAllowCopperPour( keepoutRestrictionCopper );
4823 aZone.SetDoNotAllowPads( keepoutRestrictionSMDPad && keepoutRestrictionTHPad );
4824 aZone.SetDoNotAllowFootprints( false );
4825}
4826
4827
4829 const ALTIUM_LAYER aAltiumLayer,
4830 const uint8_t aKeepoutRestrictions )
4831{
4832 std::unique_ptr<ZONE> zone = std::make_unique<ZONE>( m_board );
4833
4834 zone->SetIsRuleArea( true );
4835
4836 HelperSetZoneLayers( *zone, aAltiumLayer );
4837 HelperSetZoneKeepoutRestrictions( *zone, aKeepoutRestrictions );
4838
4839 aShape.EDA_SHAPE::TransformShapeToPolygon( *zone->Outline(), 0, ARC_HIGH_DEF, ERROR_INSIDE );
4840
4841 zone->SetBorderDisplayStyle( ZONE_BORDER_DISPLAY_STYLE::DIAGONAL_EDGE,
4843
4844 m_board->Add( zone.release(), ADD_MODE::APPEND );
4845}
4846
4847
4849 const PCB_SHAPE& aShape,
4850 const ALTIUM_LAYER aAltiumLayer,
4851 const uint8_t aKeepoutRestrictions )
4852{
4853 std::unique_ptr<ZONE> zone = std::make_unique<ZONE>( aFootprint );
4854
4855 zone->SetIsRuleArea( true );
4856
4857 HelperSetZoneLayers( *zone, aAltiumLayer );
4858 HelperSetZoneKeepoutRestrictions( *zone, aKeepoutRestrictions );
4859
4860 aShape.EDA_SHAPE::TransformShapeToPolygon( *zone->Outline(), 0, ARC_HIGH_DEF, ERROR_INSIDE );
4861
4862 zone->SetBorderDisplayStyle( ZONE_BORDER_DISPLAY_STYLE::DIAGONAL_EDGE,
4864
4865 // TODO: zone->SetLocalCoord(); missing?
4866 aFootprint->Add( zone.release(), ADD_MODE::APPEND );
4867}
4868
4869
4870std::vector<std::pair<PCB_LAYER_ID, int>> ALTIUM_PCB::HelperGetSolderAndPasteMaskExpansions(
4871 const ALTIUM_RECORD aType, const int aPrimitiveIndex, const ALTIUM_LAYER aAltiumLayer )
4872{
4873 if( m_extendedPrimitiveInformationMaps.count( aType ) == 0 )
4874 return {}; // there is nothing to parse
4875
4876 auto elems =
4877 m_extendedPrimitiveInformationMaps[ALTIUM_RECORD::TRACK].equal_range( aPrimitiveIndex );
4878
4879 if( elems.first == elems.second )
4880 return {}; // there is nothing to parse
4881
4882 std::vector<std::pair<PCB_LAYER_ID, int>> layerExpansionPairs;
4883
4884 for( auto it = elems.first; it != elems.second; ++it )
4885 {
4886 const AEXTENDED_PRIMITIVE_INFORMATION& pInf = it->second;
4887
4888 if( pInf.type == AEXTENDED_PRIMITIVE_INFORMATION_TYPE::MASK )
4889 {
4890 if( pInf.soldermaskexpansionmode == ALTIUM_MODE::MANUAL
4891 || pInf.soldermaskexpansionmode == ALTIUM_MODE::RULE )
4892 {
4893 // TODO: what layers can lead to solder or paste mask usage? E.g. KEEP_OUT_LAYER and other top/bottom layers
4894 if( aAltiumLayer == ALTIUM_LAYER::TOP_LAYER
4895 || aAltiumLayer == ALTIUM_LAYER::MULTI_LAYER )
4896 {
4897 layerExpansionPairs.emplace_back( F_Mask, pInf.soldermaskexpansionmanual );
4898 }
4899
4900 if( aAltiumLayer == ALTIUM_LAYER::BOTTOM_LAYER
4901 || aAltiumLayer == ALTIUM_LAYER::MULTI_LAYER )
4902 {
4903 layerExpansionPairs.emplace_back( B_Mask, pInf.soldermaskexpansionmanual );
4904 }
4905 }
4906 if( pInf.pastemaskexpansionmode == ALTIUM_MODE::MANUAL
4907 || pInf.pastemaskexpansionmode == ALTIUM_MODE::RULE )
4908 {
4909 if( aAltiumLayer == ALTIUM_LAYER::TOP_LAYER
4910 || aAltiumLayer == ALTIUM_LAYER::MULTI_LAYER )
4911 {
4912 layerExpansionPairs.emplace_back( F_Paste, pInf.pastemaskexpansionmanual );
4913 }
4914
4915 if( aAltiumLayer == ALTIUM_LAYER::BOTTOM_LAYER
4916 || aAltiumLayer == ALTIUM_LAYER::MULTI_LAYER )
4917 {
4918 layerExpansionPairs.emplace_back( B_Paste, pInf.pastemaskexpansionmanual );
4919 }
4920 }
4921 }
4922 }
4923
4924 return layerExpansionPairs;
4925}
const char * name
Definition: DXF_plotter.cpp:59
std::string FormatPath(const std::vector< std::string > &aVectorPath)
Helper for debug logging (vector -> string)
ALTIUM_TEXT_POSITION
ALTIUM_RULE_KIND
ALTIUM_PAD_SHAPE
const uint16_t ALTIUM_NET_UNCONNECTED
const uint16_t ALTIUM_POLYGON_NONE
ALTIUM_LAYER
const uint16_t ALTIUM_POLYGON_BOARD
ALTIUM_RECORD
const int ALTIUM_COMPONENT_NONE
LIB_ID AltiumToKiCadLibID(const wxString &aLibName, const wxString &aLibReference)
wxString AltiumPcbSpecialStringsToKiCadStrings(const wxString &aString, const std::map< wxString, wxString > &aOverrides)
void HelperShapeLineChainFromAltiumVertices(SHAPE_LINE_CHAIN &aLine, const std::vector< ALTIUM_VERTICE > &aVertices)
Definition: altium_pcb.cpp:91
double normalizeAngleDegrees(double Angle, double aMin, double aMax)
Normalize angle to be aMin < angle <= aMax angle is in degrees.
constexpr double BOLD_FACTOR
Definition: altium_pcb.cpp:63
bool IsAltiumLayerCopper(ALTIUM_LAYER aLayer)
Definition: altium_pcb.cpp:66
bool IsAltiumLayerAPlane(ALTIUM_LAYER aLayer)
Definition: altium_pcb.cpp:73
ALTIUM_PCB_DIR
Definition: altium_pcb.h:38
std::function< void(const ALTIUM_PCB_COMPOUND_FILE &, const CFB::COMPOUND_FILE_ENTRY *)> PARSE_FUNCTION_POINTER_fp
Definition: altium_pcb.h:119
@ ERROR_INSIDE
Definition: approximation.h:34
constexpr int ARC_HIGH_DEF
Definition: base_units.h:120
constexpr EDA_IU_SCALE pcbIUScale
Definition: base_units.h:108
LAYER_T
The allowed types of layers, same as Specctra DSN spec.
Definition: board.h:158
@ LT_POWER
Definition: board.h:161
@ LT_MIXED
Definition: board.h:162
@ LT_SIGNAL
Definition: board.h:160
@ BS_ITEM_TYPE_COPPER
Definition: board_stackup.h:45
@ BS_ITEM_TYPE_DIELECTRIC
Definition: board_stackup.h:46
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition: box2.h:990
size_t GetRemainingBytes() const