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, see <https://www.gnu.org/licenses/>.
19 */
20
21#include "altium_pcb.h"
22#include "altium_parser_pcb.h"
28#include <io/io_utils.h>
30
31#include <board.h>
34#include <footprint.h>
35#include <layer_range.h>
36#include <pcb_dimension.h>
37#include <pad.h>
38#include <pcb_shape.h>
39#include <pcb_text.h>
40#include <pcb_textbox.h>
41#include <pcb_track.h>
42#include <pcb_barcode.h>
43#include <pcb_generator.h>
44#include <generators_mgr.h>
46#include <router/pns_meander.h>
48#include <core/profile.h>
49#include <string_utils.h>
50#include <tools/pad_tool.h>
51#include <zone.h>
52
54
55#include <cmath>
56#include <set>
57
58#include <advanced_config.h>
59#include <compoundfilereader.h>
61#include <font/outline_font.h>
62#include <project.h>
63#include <reporter.h>
64#include <trigo.h>
65#include <utf.h>
66#include <wx/docview.h>
67#include <wx/log.h>
68#include <wx/mstream.h>
69#include <wx/wfstream.h>
70#include <wx/zstream.h>
71#include <progress_reporter.h>
72#include <magic_enum.hpp>
73#include <thread_pool.h>
74
75
76constexpr double BOLD_FACTOR = 1.75; // CSS font-weight-normal is 400; bold is 700
77
78// Slots in an Altium padstack's per-layer arrays, which run top, mid 1 through 30, bottom
79constexpr int ALTIUM_TOP_PADSTACK_IDX = 0;
80constexpr int ALTIUM_MID1_PADSTACK_IDX = 1;
81constexpr int ALTIUM_MID2_PADSTACK_IDX = 2;
82constexpr int ALTIUM_BOTTOM_PADSTACK_IDX = 31;
83constexpr int ALTIUM_PADSTACK_IDX_COUNT = 32;
84
85
87{
88 return ( aLayer >= ALTIUM_LAYER::TOP_LAYER && aLayer <= ALTIUM_LAYER::BOTTOM_LAYER )
89 || aLayer == ALTIUM_LAYER::MULTI_LAYER; // TODO: add IsAltiumLayerAPlane?
90}
91
92
97
98
99wxString AltiumUnnamedNetName( const BOARD& aBoard, int& aCounter )
100{
101 wxString name;
102
103 do
104 {
105 name = wxString::Format( wxT( "__ALTIUM_UNNAMED_NET_%d" ), ++aCounter );
106 } while( aBoard.FindNet( name ) );
107
108 return name;
109}
110
111
112// Altium scope expressions are not case sensitive, so neither is the unrestricted scope
113static bool IsAltiumScopeAll( const wxString& aExpr )
114{
115 return aExpr.IsSameAs( wxS( "All" ), false );
116}
117
118
119static bool GetAltiumNetclassScopeName( const ARULE6& aRule, wxString* aNetclassName )
120{
121 static const wxString prefix = wxT( "InNetClass('" );
122
123 if( !IsAltiumScopeAll( aRule.scope2expr ) || !aRule.scope1expr.StartsWith( prefix )
124 || !aRule.scope1expr.EndsWith( wxT( "')" ) ) )
125 {
126 return false;
127 }
128
129 *aNetclassName = aRule.scope1expr.Mid( prefix.Length(),
130 aRule.scope1expr.Length() - prefix.Length() - 2 );
131
132 return !aNetclassName->IsEmpty();
133}
134
135
136void ApplyAltiumNetclassRules( const std::map<ALTIUM_RULE_KIND, std::vector<ARULE6>>& aRulesByKind,
137 NET_SETTINGS& aNetSettings, std::vector<const ARULE6*>* aUnresolved )
138{
139 const std::map<wxString, std::shared_ptr<NETCLASS>>& netclasses = aNetSettings.GetNetclasses();
140
141 for( const auto& [kind, rules] : aRulesByKind )
142 {
145 {
146 continue;
147 }
148
149 std::set<wxString> applied;
150
151 for( const ARULE6& rule : rules )
152 {
153 wxString netclassName;
154
155 if( !rule.enabled || !GetAltiumNetclassScopeName( rule, &netclassName ) )
156 continue;
157
158 auto it = netclasses.find( netclassName );
159
160 if( it == netclasses.end() )
161 {
162 if( aUnresolved )
163 aUnresolved->push_back( &rule );
164
165 continue;
166 }
167
168 // rules are sorted by ascending Altium priority, so the first match is the winner
169 if( !applied.insert( netclassName ).second )
170 continue;
171
172 const std::shared_ptr<NETCLASS>& netclass = it->second;
173
174 switch( kind )
175 {
176 case ALTIUM_RULE_KIND::CLEARANCE: netclass->SetClearance( rule.clearanceGap ); break;
177
178 case ALTIUM_RULE_KIND::WIDTH: netclass->SetTrackWidth( rule.preferredWidth ); break;
179
181 netclass->SetViaDiameter( rule.width );
182 netclass->SetViaDrill( rule.holeWidth );
183 break;
184
185 default: break;
186 }
187 }
188 }
189}
190
191
192FOOTPRINT* ALTIUM_PCB::HelperGetFootprint( uint16_t aComponent ) const
193{
194 if( aComponent == ALTIUM_COMPONENT_NONE || m_components.size() <= aComponent )
195 {
196 THROW_IO_ERRORF( wxT( "Component creator tries to access component id %u of %u existing components" ),
197 (unsigned)aComponent, (unsigned)m_components.size() );
198 }
199
200 return m_components.at( aComponent );
201}
202
203
204std::shared_ptr<EMBEDDED_FILES::EMBEDDED_FILE>
205ALTIUM_PCB::HelperEmbedModel( FOOTPRINT* aFootprint, const wxString& aModelName,
206 const std::vector<char>& aCompressedData, bool& aIsNew )
207{
208 EMBEDDED_FILES* embeddedFiles = aFootprint->GetEmbeddedFiles();
209 const auto& files = embeddedFiles->EmbeddedFileMap();
210 auto it = files.find( aModelName );
211
212 aIsNew = it == files.end();
213
214 // Several bodies of one component routinely share a model, and inflating it per body would
215 // cost a full STEP decompression each time
216 if( !aIsNew )
217 return it->second;
218
219 auto file = std::make_shared<EMBEDDED_FILES::EMBEDDED_FILE>();
220 file->name = aModelName;
222
223 wxMemoryInputStream compressedStream( aCompressedData.data(), aCompressedData.size() );
224 wxZlibInputStream zlibStream( compressedStream );
225
226 // Altium compresses STEP at roughly 5:1, so guess high and double rather than reallocate
227 // on every read
228 file->decompressedData.resize( aCompressedData.size() * 6 );
229 size_t offset = 0;
230
231 while( !zlibStream.Eof() )
232 {
233 zlibStream.Read( file->decompressedData.data() + offset,
234 file->decompressedData.size() - offset );
235
236 size_t bytesRead = zlibStream.LastRead();
237
238 if( !bytesRead )
239 break;
240
241 offset += bytesRead;
242
243 if( offset >= file->decompressedData.size() )
244 file->decompressedData.resize( 2 * file->decompressedData.size() );
245 }
246
247 file->decompressedData.resize( offset );
248
249 // The guess above overshoots by up to 6x and resize() keeps the capacity, which the board
250 // would then hold for as long as it is open
251 file->decompressedData.shrink_to_fit();
252
253 embeddedFiles->AddFile( file );
254
255 return file;
256}
257
258
260 const std::vector<ALTIUM_VERTICE>& aVertices )
261{
262 for( const ALTIUM_VERTICE& vertex : aVertices )
263 {
264 if( vertex.isRound )
265 {
266 EDA_ANGLE angle( vertex.endangle - vertex.startangle, DEGREES_T );
267 angle.Normalize();
268
269 double startradiant = DEG2RAD( vertex.startangle );
270 double endradiant = DEG2RAD( vertex.endangle );
271 VECTOR2I arcStartOffset = KiROUND( std::cos( startradiant ) * vertex.radius,
272 -std::sin( startradiant ) * vertex.radius );
273
274 VECTOR2I arcEndOffset = KiROUND( std::cos( endradiant ) * vertex.radius,
275 -std::sin( endradiant ) * vertex.radius );
276
277 VECTOR2I arcStart = vertex.center + arcStartOffset;
278 VECTOR2I arcEnd = vertex.center + arcEndOffset;
279
280 bool isShort = arcStart.Distance( arcEnd ) < pcbIUScale.mmToIU( 0.001 )
281 || angle.AsDegrees() < 0.2;
282
283 if( arcStart.Distance( vertex.position )
284 < arcEnd.Distance( vertex.position ) )
285 {
286 if( !isShort )
287 {
288 aLine.Append( SHAPE_ARC( vertex.center, arcStart, -angle ) );
289 }
290 else
291 {
292 aLine.Append( arcStart );
293 aLine.Append( arcEnd );
294 }
295 }
296 else
297 {
298 if( !isShort )
299 {
300 aLine.Append( SHAPE_ARC( vertex.center, arcEnd, angle ) );
301 }
302 else
303 {
304 aLine.Append( arcEnd );
305 aLine.Append( arcStart );
306 }
307 }
308 }
309 else
310 {
311 aLine.Append( vertex.position );
312 }
313 }
314
315 aLine.SetClosed( true );
316}
317
318
320{
321 auto override = m_layermap.find( aAltiumLayer );
322
323 if( override != m_layermap.end() )
324 {
325 return override->second;
326 }
327
328 if( aAltiumLayer >= ALTIUM_LAYER::V7_MECHANICAL_17 && aAltiumLayer <= ALTIUM_LAYER::V7_MECHANICAL_LAST )
329 {
330 // Layer "Mechanical 17" would correspond to altiumOrd 16
331 int altiumOrd = static_cast<int>( aAltiumLayer ) - static_cast<int>( ALTIUM_LAYER::V7_MECHANICAL_1 );
332
333 if( ( altiumOrd + 1 ) > MAX_USER_DEFINED_LAYERS )
334 return UNDEFINED_LAYER;
335
336 // Convert to KiCad User_* layers
337 return static_cast<PCB_LAYER_ID>( static_cast<int>( User_1 ) + altiumOrd * 2 );
338 }
339
340 switch( aAltiumLayer )
341 {
343
344 case ALTIUM_LAYER::TOP_LAYER: return F_Cu;
375 case ALTIUM_LAYER::BOTTOM_LAYER: return B_Cu;
376
379 case ALTIUM_LAYER::TOP_PASTE: return F_Paste;
381 case ALTIUM_LAYER::TOP_SOLDER: return F_Mask;
383
400
403
420
431
432 default: return UNDEFINED_LAYER;
433 }
434}
435
436
437std::vector<PCB_LAYER_ID> ALTIUM_PCB::GetKicadLayersToIterate( ALTIUM_LAYER aAltiumLayer ) const
438{
439 if( aAltiumLayer == ALTIUM_LAYER::MULTI_LAYER || aAltiumLayer == ALTIUM_LAYER::KEEP_OUT_LAYER )
440 {
441 int layerCount = m_board ? m_board->GetCopperLayerCount() : 32;
442 std::vector<PCB_LAYER_ID> layers;
443 layers.reserve( layerCount );
444
445 for( PCB_LAYER_ID layer : LAYER_RANGE( F_Cu, B_Cu, layerCount ) )
446 {
447 if( !m_board || m_board->IsLayerEnabled( layer ) )
448 layers.emplace_back( layer );
449 }
450
451 return layers;
452 }
453
454 PCB_LAYER_ID klayer = GetKicadLayer( aAltiumLayer );
455
456 if( klayer == UNDEFINED_LAYER )
457 return {};
458
459 return { klayer };
460}
461
462
463ALTIUM_PCB::ALTIUM_PCB( BOARD* aBoard, PROGRESS_REPORTER* aProgressReporter,
464 LAYER_MAPPING_HANDLER& aHandler, REPORTER* aReporter,
465 const wxString& aLibrary, const wxString& aFootprintName )
466{
467 m_board = aBoard;
468 m_progressReporter = aProgressReporter;
469 m_layerMappingHandler = aHandler;
470 m_reporter = aReporter;
471 m_doneCount = 0;
473 m_totalCount = 0;
475 m_library = aLibrary;
476 m_footprintName = aFootprintName;
477}
478
482
484{
485 const unsigned PROGRESS_DELTA = 250;
486
488 {
489 if( ++m_doneCount > m_lastProgressCount + PROGRESS_DELTA )
490 {
491 m_progressReporter->SetCurrentProgress( (double) m_doneCount / std::max( 1U, m_totalCount ) );
492
493 if( !m_progressReporter->KeepRefreshing() )
495
497 }
498 }
499}
500
502 const std::map<ALTIUM_PCB_DIR, std::string>& aFileMapping,
503 const std::map<std::string, UTF8>* aProperties )
504{
505 if( aProperties )
506 MapSchematicNetNames( *aProperties );
507
508 // this vector simply declares in which order which functions to call.
509 const std::vector<std::tuple<bool, ALTIUM_PCB_DIR, PARSE_FUNCTION_POINTER_fp>> parserOrder = {
511 [this]( const ALTIUM_PCB_COMPOUND_FILE& aFile, auto fileHeader )
512 {
513 this->ParseFileHeader( aFile, fileHeader );
514 } },
516 [this]( const ALTIUM_PCB_COMPOUND_FILE& aFile, auto fileHeader )
517 {
518 this->ParseBoard6Data( aFile, fileHeader );
519 } },
521 [this]( const ALTIUM_PCB_COMPOUND_FILE& aFile, auto fileHeader )
522 {
523 this->ParseExtendedPrimitiveInformationData( aFile, fileHeader );
524 } },
526 [this]( const ALTIUM_PCB_COMPOUND_FILE& aFile, auto fileHeader )
527 {
528 this->ParseComponents6Data( aFile, fileHeader );
529 } },
530 { false, ALTIUM_PCB_DIR::MODELS,
531 [this, aFileMapping]( const ALTIUM_PCB_COMPOUND_FILE& aFile, auto fileHeader )
532 {
533 std::vector<std::string> dir{ aFileMapping.at( ALTIUM_PCB_DIR::MODELS ) };
534 this->ParseModelsData( aFile, fileHeader, dir );
535 } },
537 [this]( const ALTIUM_PCB_COMPOUND_FILE& aFile, auto fileHeader )
538 {
539 this->ParseComponentsBodies6Data( aFile, fileHeader );
540 } },
541 { true, ALTIUM_PCB_DIR::NETS6,
542 [this]( const ALTIUM_PCB_COMPOUND_FILE& aFile, auto fileHeader )
543 {
544 this->ParseNets6Data( aFile, fileHeader );
545 } },
547 [this]( const ALTIUM_PCB_COMPOUND_FILE& aFile, auto fileHeader )
548 {
549 this->ParseClasses6Data( aFile, fileHeader );
550 } },
552 [this]( const ALTIUM_PCB_COMPOUND_FILE& aFile, auto fileHeader )
553 {
554 this->ParseRules6Data( aFile, fileHeader );
555 } },
557 [this]( const ALTIUM_PCB_COMPOUND_FILE& aFile, auto fileHeader )
558 {
559 this->ParseDimensions6Data( aFile, fileHeader );
560 } },
562 [this]( const ALTIUM_PCB_COMPOUND_FILE& aFile, auto fileHeader )
563 {
564 this->ParsePolygons6Data( aFile, fileHeader );
565 } },
566 { true, ALTIUM_PCB_DIR::ARCS6,
567 [this]( const ALTIUM_PCB_COMPOUND_FILE& aFile, auto fileHeader )
568 {
569 this->ParseArcs6Data( aFile, fileHeader );
570 } },
571 { true, ALTIUM_PCB_DIR::PADS6,
572 [this]( const ALTIUM_PCB_COMPOUND_FILE& aFile, auto fileHeader )
573 {
574 this->ParsePads6Data( aFile, fileHeader );
575 } },
576 { true, ALTIUM_PCB_DIR::VIAS6,
577 [this]( const ALTIUM_PCB_COMPOUND_FILE& aFile, auto fileHeader )
578 {
579 this->ParseVias6Data( aFile, fileHeader );
580 } },
582 [this]( const ALTIUM_PCB_COMPOUND_FILE& aFile, auto fileHeader )
583 {
584 this->ParseTracks6Data( aFile, fileHeader );
585 } },
587 [this]( const ALTIUM_PCB_COMPOUND_FILE& aFile, auto fileHeader )
588 {
589 this->ParseSmartUnions6Data( aFile, fileHeader );
590 } },
592 [this]( const ALTIUM_PCB_COMPOUND_FILE& aFile, auto fileHeader )
593 {
594 this->ParseUnionNamesData( aFile, fileHeader );
595 } },
597 [this]( const ALTIUM_PCB_COMPOUND_FILE& aFile, auto fileHeader )
598 {
599 this->ParseWideStrings6Data( aFile, fileHeader );
600 } },
602 [this]( const ALTIUM_PCB_COMPOUND_FILE& aFile, auto fileHeader )
603 {
604 this->ParseTexts6Data( aFile, fileHeader );
605 } },
607 [this]( const ALTIUM_PCB_COMPOUND_FILE& aFile, auto fileHeader )
608 {
609 this->ParseFills6Data( aFile, fileHeader );
610 } },
612 [this]( const ALTIUM_PCB_COMPOUND_FILE& aFile, auto fileHeader )
613 {
614 this->ParseBoardRegionsData( aFile, fileHeader );
615 } },
617 [this]( const ALTIUM_PCB_COMPOUND_FILE& aFile, auto fileHeader )
618 {
619 this->ParseShapeBasedRegions6Data( aFile, fileHeader );
620 } },
622 [this]( const ALTIUM_PCB_COMPOUND_FILE& aFile, auto fileHeader )
623 {
624 this->ParseRegions6Data( aFile, fileHeader );
625 } }
626 };
627
628 if( m_progressReporter != nullptr )
629 {
630 // Count number of records we will read for the progress reporter
631 for( const std::tuple<bool, ALTIUM_PCB_DIR, PARSE_FUNCTION_POINTER_fp>& cur : parserOrder )
632 {
633 bool isRequired;
636 std::tie( isRequired, directory, fp ) = cur;
637
639 continue;
640
641 const auto& mappedDirectory = aFileMapping.find( directory );
642
643 if( mappedDirectory == aFileMapping.end() )
644 continue;
645
646 const std::vector<std::string> mappedFile{ mappedDirectory->second, "Header" };
647 const CFB::COMPOUND_FILE_ENTRY* file = altiumPcbFile.FindStream( mappedFile );
648
649 if( file == nullptr )
650 continue;
651
652 ALTIUM_BINARY_PARSER reader( altiumPcbFile, file );
653 uint32_t numOfRecords = reader.Read<uint32_t>();
654
655 if( reader.HasParsingError() )
656 {
657 if( m_reporter )
658 {
659 m_reporter->Report( wxString::Format( _( "'%s' was not parsed correctly." ),
660 FormatPath( mappedFile ) ),
662 }
663
664 continue;
665 }
666
667 m_totalCount += numOfRecords;
668
669 if( reader.GetRemainingBytes() != 0 )
670 {
671 if( m_reporter )
672 {
673 m_reporter->Report( wxString::Format( _( "'%s' was not fully parsed." ),
674 FormatPath( mappedFile ) ),
676 }
677
678 continue;
679 }
680 }
681 }
682
683 const auto& boardDirectory = aFileMapping.find( ALTIUM_PCB_DIR::BOARD6 );
684
685 if( boardDirectory != aFileMapping.end() )
686 {
687 std::vector<std::string> mappedFile{ boardDirectory->second, "Data" };
688
689 const CFB::COMPOUND_FILE_ENTRY* file = altiumPcbFile.FindStream( mappedFile );
690
691 if( !file )
692 {
693 THROW_IO_ERROR( _( "This file does not appear to be in a valid PCB Binary Version 6.0 format. In "
694 "Altium Designer, make sure to save as \"PCB Binary Files (*.PcbDoc)\"." ) );
695 }
696 }
697
698 // Parse data in specified order
699 for( const std::tuple<bool, ALTIUM_PCB_DIR, PARSE_FUNCTION_POINTER_fp>& cur : parserOrder )
700 {
701 bool isRequired;
704 std::tie( isRequired, directory, fp ) = cur;
705
706 const auto& mappedDirectory = aFileMapping.find( directory );
707
708 if( mappedDirectory == aFileMapping.end() )
709 {
710 wxASSERT_MSG( !isRequired, wxString::Format( wxT( "Altium Directory of kind %d was "
711 "expected, but no mapping is "
712 "present in the code" ),
713 directory ) );
714 continue;
715 }
716
717 std::vector<std::string> mappedFile{ mappedDirectory->second };
718
720 mappedFile.emplace_back( "Data" );
721
722 const CFB::COMPOUND_FILE_ENTRY* file = altiumPcbFile.FindStream( mappedFile );
723
724 if( file != nullptr )
725 {
726 fp( altiumPcbFile, file );
727 }
728 else if( isRequired )
729 {
730 if( m_reporter )
731 {
732 m_reporter->Report( wxString::Format( _( "File not found: '%s' for directory '%s'." ),
733 FormatPath( mappedFile ),
734 magic_enum::enum_name( directory ) ),
736 }
737 }
738 }
739
740 // Rebuild interactive length-tuning meanders from the SmartUnions definitions now that all
741 // copper that the unions reference has been created and added to the board.
743
744 // Components6 is parsed before Pads6, so the mounting style can only be derived once every
745 // pad has been attached to its footprint.
747
748 // fixup zone priorities since Altium stores them in the opposite order
749 for( ZONE* zone : m_polygons )
750 {
751 if( !zone )
752 continue;
753
754 // Altium "fills" - not poured in Altium
755 if( zone->GetAssignedPriority() == 1000 )
756 {
757 // Unlikely, but you never know
758 if( m_highest_pour_index >= 1000 )
759 zone->SetAssignedPriority( m_highest_pour_index + 1 );
760
761 continue;
762 }
763
764 int priority = m_highest_pour_index - zone->GetAssignedPriority();
765
766 zone->SetAssignedPriority( priority >= 0 ? priority : 0 );
767 }
768
769 // change priority of outer zone to zero
770 for( std::pair<const ALTIUM_LAYER, ZONE*>& zone : m_outer_plane )
771 zone.second->SetAssignedPriority( 0 );
772
773 // Simplify and fracture zone fills in case we constructed them from tracks (hatched fill)
774 for( ZONE* zone : m_polygons )
775 {
776 if( !zone )
777 continue;
778
779 for( PCB_LAYER_ID layer : zone->GetLayerSet() )
780 {
781 if( !zone->HasFilledPolysForLayer( layer ) )
782 continue;
783
784 zone->GetFilledPolysList( layer )->Fracture();
785 }
786 }
787
788 // Altium doesn't appear to store either the dimension value nor the dimensioned object in
789 // the dimension record. (Yes, there is a REFERENCE0OBJECTID, but it doesn't point to the
790 // dimensioned object.) We attempt to plug this gap by finding a colocated arc or circle
791 // and using its radius. If there are more than one such arcs/circles, well, :shrug:.
793 {
794 int radius = 0;
795
796 for( BOARD_ITEM* item : m_board->Drawings() )
797 {
798 if( item->Type() != PCB_SHAPE_T )
799 continue;
800
801 PCB_SHAPE* shape = static_cast<PCB_SHAPE*>( item );
802
803 if( shape->GetShape() != SHAPE_T::ARC && shape->GetShape() != SHAPE_T::CIRCLE )
804 continue;
805
806 if( shape->GetPosition() == dim->GetPosition() )
807 {
808 radius = shape->GetRadius();
809 break;
810 }
811 }
812
813 if( radius == 0 )
814 {
815 for( PCB_TRACK* track : m_board->Tracks() )
816 {
817 if( track->Type() != PCB_ARC_T )
818 continue;
819
820 PCB_ARC* arc = static_cast<PCB_ARC*>( track );
821
822 if( arc->GetCenter() == dim->GetPosition() )
823 {
824 radius = arc->GetRadius();
825 break;
826 }
827 }
828 }
829
830 // Move the radius point onto the circumference
831 VECTOR2I radialLine = dim->GetEnd() - dim->GetStart();
832 int totalLength = radialLine.EuclideanNorm();
833
834 // Enforce a minimum on the radialLine else we won't have enough precision to get the
835 // angle from it.
836 radialLine = radialLine.Resize( std::max( radius, 2 ) );
837 dim->SetEnd( dim->GetStart() + (VECTOR2I) radialLine );
838 dim->SetLeaderLength( totalLength - radius );
839 dim->Update();
840 }
841
842 // center board
843 BOX2I bbbox = m_board->GetBoardEdgesBoundingBox();
844
845 int w = m_board->GetPageSettings().GetWidthIU( pcbIUScale.IU_PER_MILS );
846 int h = m_board->GetPageSettings().GetHeightIU( pcbIUScale.IU_PER_MILS );
847
848 int desired_x = ( w - bbbox.GetWidth() ) / 2;
849 int desired_y = ( h - bbbox.GetHeight() ) / 2;
850
851 VECTOR2I movementVector( desired_x - bbbox.GetX(), desired_y - bbbox.GetY() );
852 m_board->Move( movementVector );
853
854 BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings();
855 bds.SetAuxOrigin( bds.GetAuxOrigin() + movementVector );
856 bds.SetGridOrigin( bds.GetGridOrigin() + movementVector );
857
858 m_board->m_LegacyDesignSettingsLoaded = true;
859 m_board->SetModified();
860}
861
862
863std::unique_ptr<FOOTPRINT> ALTIUM_PCB::ParseFootprint( ALTIUM_PCB_COMPOUND_FILE& altiumLibFile,
864 const wxString& aFootprintName )
865{
866 std::unique_ptr<FOOTPRINT> footprint = std::make_unique<FOOTPRINT>( m_board );
867
868 m_unicodeStrings.clear();
870
871 const std::vector<std::string> libStreamName{ "Library", "Data" };
872 const CFB::COMPOUND_FILE_ENTRY* libStream = altiumLibFile.FindStream( libStreamName );
873
874 if( libStream == nullptr )
875 THROW_IO_ERRORF( _( "File not found: '%s'." ), FormatPath( libStreamName ) );
876
877 ALTIUM_BINARY_PARSER libParser( altiumLibFile, libStream );
878 ALIBRARY libData( libParser );
879
881
882 // TODO: WideStrings are stored as parameterMap in the case of footprints, not as binary
883 // std::string unicodeStringsStreamName = aFootprintName.ToStdString() + "\\WideStrings";
884 // const CFB::COMPOUND_FILE_ENTRY* unicodeStringsData = altiumLibFile.FindStream( unicodeStringsStreamName );
885 // if( unicodeStringsData != nullptr )
886 // {
887 // ParseWideStrings6Data( altiumLibFile, unicodeStringsData );
888 // }
889
890 std::tuple<wxString, const CFB::COMPOUND_FILE_ENTRY*> ret =
891 altiumLibFile.FindLibFootprintDirName( aFootprintName );
892
893 wxString fpDirName = std::get<0>( ret );
894 const CFB::COMPOUND_FILE_ENTRY* footprintStream = std::get<1>( ret );
895
896 if( fpDirName.IsEmpty() )
897 THROW_IO_ERRORF( _( "Footprint directory not found: '%s'." ), aFootprintName );
898
899 const std::vector<std::string> streamName{ fpDirName.ToStdString(), "Data" };
900 const CFB::COMPOUND_FILE_ENTRY* footprintData = altiumLibFile.FindStream( footprintStream, { "Data" } );
901
902 if( !footprintData )
903 THROW_IO_ERRORF( _( "File not found: '%s'." ), FormatPath( streamName ) );
904
905 ALTIUM_BINARY_PARSER parser( altiumLibFile, footprintData );
906
908 //wxString footprintName = parser.ReadWxString(); // Not used (single-byte char set)
909 parser.SkipSubrecord();
910
911 LIB_ID fpID = AltiumToKiCadLibID( "", aFootprintName ); // TODO: library name
912 footprint->SetFPID( fpID );
913
914 const std::vector<std::string> parametersStreamName{ fpDirName.ToStdString(), "Parameters" };
915 const CFB::COMPOUND_FILE_ENTRY* parametersData = altiumLibFile.FindStream( footprintStream, { "Parameters" } );
916
917 if( parametersData != nullptr )
918 {
919 ALTIUM_BINARY_PARSER parametersReader( altiumLibFile, parametersData );
920 std::map<wxString, wxString> parameterProperties = parametersReader.ReadProperties();
921 wxString description = ALTIUM_PROPS_UTILS::ReadString( parameterProperties, wxT( "DESCRIPTION" ), wxT( "" ) );
922 footprint->SetLibDescription( description );
923 }
924 else
925 {
926 if( m_reporter )
927 {
928 m_reporter->Report( wxString::Format( _( "File not found: '%s'." ), FormatPath( parametersStreamName ) ),
930 }
931
932 footprint->SetLibDescription( wxT( "" ) );
933 }
934
935 const std::vector<std::string> extendedPrimitiveInformationStreamName{
936 "ExtendedPrimitiveInformation", "Data"
937 };
938 const CFB::COMPOUND_FILE_ENTRY* extendedPrimitiveInformationData =
939 altiumLibFile.FindStream( footprintStream, extendedPrimitiveInformationStreamName );
940
941 if( extendedPrimitiveInformationData != nullptr )
942 ParseExtendedPrimitiveInformationData( altiumLibFile, extendedPrimitiveInformationData );
943
944 footprint->SetReference( wxT( "REF**" ) );
945 footprint->SetValue( aFootprintName );
946 footprint->Reference().SetVisible( true ); // TODO: extract visibility information
947 footprint->Value().SetVisible( true );
948
949 const VECTOR2I defaultTextSize( pcbIUScale.mmToIU( 1.0 ), pcbIUScale.mmToIU( 1.0 ) );
950 const int defaultTextThickness( pcbIUScale.mmToIU( 0.15 ) );
951
952 for( PCB_FIELD* field : footprint->GetFields() )
953 {
954 field->SetTextSize( defaultTextSize );
955 field->SetTextThickness( defaultTextThickness );
956 }
957
958 for( int primitiveIndex = 0; parser.GetRemainingBytes() >= 4; primitiveIndex++ )
959 {
960 ALTIUM_RECORD recordtype = static_cast<ALTIUM_RECORD>( parser.Peek<uint8_t>() );
961
962 switch( recordtype )
963 {
965 {
966 AARC6 arc( parser );
967 ConvertArcs6ToFootprintItem( footprint.get(), arc, primitiveIndex, false );
968 break;
969 }
971 {
972 APAD6 pad( parser );
973 ConvertPads6ToFootprintItem( footprint.get(), pad );
974 break;
975 }
977 {
978 AVIA6 via( parser );
979 ConvertVias6ToFootprintItem( footprint.get(), via );
980 break;
981 }
983 {
984 ATRACK6 track( parser );
985 ConvertTracks6ToFootprintItem( footprint.get(), track, primitiveIndex, false );
986 break;
987 }
989 {
990 ATEXT6 text( parser, m_unicodeStrings );
991 ConvertTexts6ToFootprintItem( footprint.get(), text );
992 break;
993 }
995 {
996 AFILL6 fill( parser );
997 ConvertFills6ToFootprintItem( footprint.get(), fill, false );
998 break;
999 }
1001 {
1002 AREGION6 region( parser, false );
1003 ConvertShapeBasedRegions6ToFootprintItem( footprint.get(), region, primitiveIndex );
1004 break;
1005 }
1007 {
1008 ACOMPONENTBODY6 componentBody( parser );
1009 ConvertComponentBody6ToFootprintItem( altiumLibFile, footprint.get(), componentBody );
1010 break;
1011 }
1012 default:
1013 THROW_IO_ERRORF( _( "Record of unknown type: '%d'." ), recordtype );
1014 }
1015 }
1016
1017
1018 // Loop over this multiple times to catch pads that are jumpered to each other by multiple shapes
1019 for( bool changes = true; changes; )
1020 {
1021 changes = false;
1022
1023 alg::for_all_pairs( footprint->Pads().begin(), footprint->Pads().end(),
1024 [&changes]( PAD* aPad1, PAD* aPad2 )
1025 {
1026 if( !( aPad1->GetNumber().IsEmpty() ^ aPad2->GetNumber().IsEmpty() ) )
1027 return;
1028
1029 for( PCB_LAYER_ID layer : aPad1->GetLayerSet() )
1030 {
1031 std::shared_ptr<SHAPE> shape1 = aPad1->GetEffectiveShape( layer );
1032 std::shared_ptr<SHAPE> shape2 = aPad2->GetEffectiveShape( layer );
1033
1034 if( shape1->Collide( shape2.get() ) )
1035 {
1036 if( aPad1->GetNumber().IsEmpty() )
1037 aPad1->SetNumber( aPad2->GetNumber() );
1038 else
1039 aPad2->SetNumber( aPad1->GetNumber() );
1040
1041 changes = true;
1042 }
1043 }
1044 } );
1045 }
1046
1047 // Auto-position reference and value
1048 footprint->AutoPositionFields();
1049
1050 // Altium has no mounting style to copy, so derive it from the pads using the same heuristic
1051 // as KiCad's footprint checker. Unlike the board importer this can be done here, because a
1052 // library footprint's pads are converted inline above.
1053 footprint->SetAttributes( footprint->GetAttributes() | footprint->GetLikelyAttribute() );
1054
1055 if( parser.HasParsingError() )
1056 THROW_IO_ERRORF( wxT( "%s stream was not parsed correctly" ), FormatPath( streamName ) );
1057
1058 if( parser.GetRemainingBytes() != 0 )
1059 THROW_IO_ERRORF( wxT( "%s stream is not fully parsed" ), FormatPath( streamName ) );
1060
1061 return footprint;
1062}
1063
1064int ALTIUM_PCB::GetNetCode( uint16_t aId ) const
1065{
1066 if( aId == ALTIUM_NET_UNCONNECTED )
1067 {
1069 }
1070 else if( aId >= m_altiumToKicadNetcodes.size() )
1071 {
1072 THROW_IO_ERRORF( wxT( "Netcode with id %d does not exist. Only %zu nets are known" ),
1073 aId, m_altiumToKicadNetcodes.size() );
1074 }
1075 else
1076 {
1077 return m_altiumToKicadNetcodes[ aId ];
1078 }
1079}
1080
1081const ARULE6* ALTIUM_PCB::GetRule( ALTIUM_RULE_KIND aKind, const wxString& aName ) const
1082{
1083 const auto rules = m_rules.find( aKind );
1084
1085 if( rules == m_rules.end() )
1086 return nullptr;
1087
1088 for( const ARULE6& rule : rules->second )
1089 {
1090 if( rule.enabled && rule.name == aName )
1091 return &rule;
1092 }
1093
1094 return nullptr;
1095}
1096
1098{
1099 const auto rules = m_rules.find( aKind );
1100
1101 if( rules == m_rules.end() )
1102 return nullptr;
1103
1104 for( const ARULE6& rule : rules->second )
1105 {
1106 if( rule.enabled && IsAltiumScopeAll( rule.scope1expr ) && IsAltiumScopeAll( rule.scope2expr ) )
1107 return &rule;
1108 }
1109
1110 return nullptr;
1111}
1112
1113
1115{
1116 const auto rules = m_rules.find( aKind );
1117
1118 if( rules == m_rules.end() )
1119 return nullptr;
1120
1121 if( const ARULE6* match = selectAltiumPolygonRule( rules->second ) )
1122 return match;
1123
1124 // Fall back to the default (All/All) rule
1125 return GetRuleDefault( aKind );
1126}
1127
1128
1130 const CFB::COMPOUND_FILE_ENTRY* aEntry )
1131{
1132 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
1133
1135 wxString header = reader.ReadWxString();
1136
1137 //std::cout << "HEADER: " << header << std::endl; // tells me: PCB 5.0 Binary File
1138
1139 //reader.SkipSubrecord();
1140
1141 // TODO: does not seem to work all the time at the moment
1142 //if( reader.GetRemainingBytes() != 0 )
1143 // THROW_IO_ERROR( "FileHeader stream is not fully parsed" );
1144}
1145
1146
1148 const CFB::COMPOUND_FILE_ENTRY* aEntry )
1149{
1150 if( m_progressReporter )
1151 m_progressReporter->Report( _( "Loading extended primitive information data..." ) );
1152
1153 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
1154
1155 while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
1156 {
1157 checkpoint();
1158 AEXTENDED_PRIMITIVE_INFORMATION elem( reader );
1159
1161 std::move( elem ) );
1162 }
1163
1164 if( reader.GetRemainingBytes() != 0 )
1165 THROW_IO_ERROR( wxT( "ExtendedPrimitiveInformation stream is not fully parsed" ) );
1166}
1167
1168
1170 const CFB::COMPOUND_FILE_ENTRY* aEntry )
1171{
1172 if( m_progressReporter )
1173 m_progressReporter->Report( _( "Loading board data..." ) );
1174
1175 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
1176
1177 checkpoint();
1178 ABOARD6 elem( reader );
1179
1180 if( reader.GetRemainingBytes() != 0 )
1181 THROW_IO_ERROR( wxT( "Board6 stream is not fully parsed" ) );
1182
1183 m_board->GetDesignSettings().SetAuxOrigin( elem.origin );
1184 m_board->GetDesignSettings().SetGridOrigin( elem.origin );
1185
1186 // read layercount from stackup, because LAYERSETSCOUNT is not always correct?!
1187 size_t layercount = 0;
1188 size_t layerid = static_cast<size_t>( ALTIUM_LAYER::TOP_LAYER );
1189
1190 while( layerid < elem.stackup.size() && layerid != 0 )
1191 {
1192 layerid = elem.stackup[ layerid - 1 ].nextId;
1193 layercount++;
1194 }
1195
1196 size_t kicadLayercount = ( layercount % 2 == 0 ) ? layercount : layercount + 1;
1197 m_board->SetCopperLayerCount( kicadLayercount );
1198
1199 BOARD_DESIGN_SETTINGS& designSettings = m_board->GetDesignSettings();
1200 BOARD_STACKUP& stackup = designSettings.GetStackupDescriptor();
1201
1202 // create board stackup
1203 stackup.RemoveAll(); // Just to be sure
1204 stackup.BuildDefaultStackupList( &designSettings, layercount );
1205
1206 auto it = stackup.GetList().begin();
1207
1208 // find first copper layer
1209 for( ; it != stackup.GetList().end() && ( *it )->GetType() != BS_ITEM_TYPE_COPPER; ++it )
1210 ;
1211
1212 auto cuLayer = LAYER_RANGE( F_Cu, B_Cu, 32 ).begin();
1213
1214 for( size_t altiumLayerId = static_cast<size_t>( ALTIUM_LAYER::TOP_LAYER );
1215 altiumLayerId < elem.stackup.size() && altiumLayerId != 0;
1216 altiumLayerId = elem.stackup[altiumLayerId - 1].nextId )
1217 {
1218 // array starts with 0, but stackup with 1
1219 ABOARD6_LAYER_STACKUP& layer = elem.stackup.at( altiumLayerId - 1 );
1220
1221 // handle unused layer in case of odd layercount
1222 if( layer.nextId == 0 && layercount != kicadLayercount )
1223 {
1224 m_board->SetLayerName( ( *it )->GetBrdLayerId(), wxT( "[unused]" ) );
1225
1226 if( ( *it )->GetType() != BS_ITEM_TYPE_COPPER )
1227 THROW_IO_ERROR( wxT( "Board6 stream, unexpected item while parsing stackup" ) );
1228
1229 ( *it )->SetThickness( 0 );
1230
1231 ++it;
1232
1233 if( ( *it )->GetType() != BS_ITEM_TYPE_DIELECTRIC )
1234 THROW_IO_ERROR( wxT( "Board6 stream, unexpected item while parsing stackup" ) );
1235
1236 ( *it )->SetThickness( 0, 0 );
1237 ( *it )->SetThicknessLocked( true, 0 );
1238 ++it;
1239 }
1240
1241 m_layermap.insert( { static_cast<ALTIUM_LAYER>( altiumLayerId ), *cuLayer } );
1242 ++cuLayer;
1243
1244 if( ( *it )->GetType() != BS_ITEM_TYPE_COPPER )
1245 THROW_IO_ERROR( wxT( "Board6 stream, unexpected item while parsing stackup" ) );
1246
1247 ( *it )->SetThickness( layer.copperthick );
1248
1249 ALTIUM_LAYER alayer = static_cast<ALTIUM_LAYER>( altiumLayerId );
1250 PCB_LAYER_ID klayer = ( *it )->GetBrdLayerId();
1251
1252 m_board->SetLayerName( klayer, layer.name );
1253
1254 if( layer.copperthick == 0 )
1255 m_board->SetLayerType( klayer, LAYER_T::LT_JUMPER ); // used for things like wirebonding
1256 else if( IsAltiumLayerAPlane( alayer ) )
1257 m_board->SetLayerType( klayer, LAYER_T::LT_POWER );
1258
1259 if( klayer == B_Cu )
1260 {
1261 if( layer.nextId != 0 )
1262 THROW_IO_ERROR( wxT( "Board6 stream, unexpected id while parsing last stackup layer" ) );
1263
1264 // overwrite entry from internal -> bottom
1265 m_layermap[alayer] = B_Cu;
1266 break;
1267 }
1268
1269 ++it;
1270
1271 if( ( *it )->GetType() != BS_ITEM_TYPE_DIELECTRIC )
1272 THROW_IO_ERROR( wxT( "Board6 stream, unexpected item while parsing stackup" ) );
1273
1274 ( *it )->SetThickness( layer.dielectricthick, 0 );
1275 ( *it )->SetMaterial( layer.dielectricmaterial.empty() ? NotSpecifiedPrm()
1276 : wxString( layer.dielectricmaterial ) );
1277 ( *it )->SetEpsilonR( layer.dielectricconst, 0 );
1278
1279 if( layer.dielectriclosstangent > 0. )
1280 ( *it )->SetLossTangent( layer.dielectriclosstangent, 0 );
1281
1282 ++it;
1283 }
1284
1286 remapUnsureLayers( elem.stackup );
1287
1288 // Set name of all non-cu layers
1289 for( const ABOARD6_LAYER_STACKUP& layer : elem.stackup )
1290 {
1291 ALTIUM_LAYER alayer = static_cast<ALTIUM_LAYER>( layer.layerId );
1292
1293 if( ( alayer >= ALTIUM_LAYER::TOP_OVERLAY && alayer <= ALTIUM_LAYER::BOTTOM_SOLDER )
1294 || ( alayer >= ALTIUM_LAYER::MECHANICAL_1 && alayer <= ALTIUM_LAYER::MECHANICAL_16 )
1296 {
1297 PCB_LAYER_ID klayer = GetKicadLayer( alayer );
1298 m_board->SetLayerName( klayer, layer.name );
1299 }
1300 }
1301
1302 if( elem.discardedVertices > 0 && m_reporter )
1303 {
1304 m_reporter->Report( wxString::Format( _( "Board outline has %d vertices outside the "
1305 "coordinate range; they were dropped." ),
1306 elem.discardedVertices ),
1308 }
1309
1312 m_board->GetDesignSettings().SetBoardThickness( stackup.BuildBoardThicknessFromStackup() );
1313 designSettings.m_HasStackup = true;
1314}
1315
1316
1318{
1319 m_padstackLayerIndex.clear();
1320
1321 for( const auto& [altiumLayer, kicadLayer] : m_layermap )
1322 {
1323 if( altiumLayer < ALTIUM_LAYER::TOP_LAYER || altiumLayer > ALTIUM_LAYER::BOTTOM_LAYER )
1324 continue;
1325
1326 if( IsCopperLayer( kicadLayer ) )
1327 {
1328 m_padstackLayerIndex[kicadLayer] = static_cast<int>( altiumLayer )
1329 - static_cast<int>( ALTIUM_LAYER::TOP_LAYER );
1330 }
1331 }
1332}
1333
1334
1336{
1337 // Footprint libraries carry no stackup, so Altium's mid layer N is KiCad's In N
1338 if( m_padstackLayerIndex.empty() )
1339 {
1340 size_t ordinal = CopperLayerToOrdinal( aLayer );
1341
1342 return ordinal < ALTIUM_PADSTACK_IDX_COUNT ? static_cast<int>( ordinal ) : -1;
1343 }
1344
1345 auto it = m_padstackLayerIndex.find( aLayer );
1346
1347 return it == m_padstackLayerIndex.end() ? -1 : it->second;
1348}
1349
1350
1351// Helper to detect if a layer name indicates a courtyard layer
1352static bool IsLayerNameCourtyard( const wxString& aName )
1353{
1354 wxString nameLower = aName.Lower();
1355 return nameLower.Contains( wxT( "courtyard" ) ) || nameLower.Contains( wxT( "court yard" ) )
1356 || nameLower.Contains( wxT( "crtyd" ) );
1357}
1358
1359
1360// Helper to detect if a layer name indicates an assembly layer
1361static bool IsLayerNameAssembly( const wxString& aName )
1362{
1363 wxString nameLower = aName.Lower();
1364 return nameLower.Contains( wxT( "assembly" ) ) || nameLower.Contains( wxT( "assy" ) );
1365}
1366
1367
1368// Helper to detect if a layer name indicates a top-side layer
1369static bool IsLayerNameTopSide( const wxString& aName )
1370{
1371 bool isTop = false;
1372
1373 auto check = [&isTop]( bool aTopCond, bool aBotCond )
1374 {
1375 if( aTopCond && aBotCond )
1376 return false;
1377
1378 if( !aTopCond && !aBotCond )
1379 return false;
1380
1381 isTop = aTopCond;
1382 return true;
1383 };
1384
1385 wxString lower = aName.Lower();
1386
1387 if( check( lower.StartsWith( "top" ), lower.StartsWith( "bot" ) ) )
1388 return isTop;
1389
1390 if( check( lower.EndsWith( "_t" ), lower.EndsWith( "_b" ) ) )
1391 return isTop;
1392
1393 if( check( lower.EndsWith( ".t" ), lower.EndsWith( ".b" ) ) )
1394 return isTop;
1395
1396 if( check( lower.Contains( "top" ), lower.Contains( "bot" ) ) )
1397 return isTop;
1398
1399 return true; // Unknown
1400}
1401
1402
1403void ALTIUM_PCB::remapUnsureLayers( std::vector<ABOARD6_LAYER_STACKUP>& aStackup )
1404{
1405 LSET enabledLayers = m_board->GetEnabledLayers();
1406 LSET validRemappingLayers = enabledLayers | LSET::AllBoardTechMask() |
1408
1409 if( aStackup.size() == 0 )
1410 return;
1411
1412 std::vector<INPUT_LAYER_DESC> inputLayers;
1413 std::map<wxString, ALTIUM_LAYER> altiumLayerNameMap;
1414
1415 ABOARD6_LAYER_STACKUP& curLayer = aStackup[0];
1416 ALTIUM_LAYER layer_num;
1417 INPUT_LAYER_DESC iLdesc;
1418
1419 // Track which courtyard layers we've mapped to avoid duplicates
1420 bool frontCourtyardMapped = false;
1421 bool backCourtyardMapped = false;
1422
1423 for( size_t ii = 0; ii < aStackup.size(); ii++ )
1424 {
1425 curLayer = aStackup[ii];
1426 layer_num = static_cast<ALTIUM_LAYER>( curLayer.layerId );
1427
1428 // Skip UI-only layers and pseudo-layers that have no physical representation
1429 if( layer_num == ALTIUM_LAYER::MULTI_LAYER
1430 || layer_num == ALTIUM_LAYER::CONNECTIONS
1431 || layer_num == ALTIUM_LAYER::BACKGROUND
1432 || layer_num == ALTIUM_LAYER::DRC_ERROR_MARKERS
1433 || layer_num == ALTIUM_LAYER::SELECTIONS
1434 || layer_num == ALTIUM_LAYER::VISIBLE_GRID_1
1435 || layer_num == ALTIUM_LAYER::VISIBLE_GRID_2
1436 || layer_num == ALTIUM_LAYER::PAD_HOLES
1437 || layer_num == ALTIUM_LAYER::VIA_HOLES )
1438 {
1439 continue;
1440 }
1441
1442 // Skip disabled mechanical layers (mapped to UNDEFINED_LAYER by
1443 // HelperFillMechanicalLayerAssignments)
1444 auto existingMapping = m_layermap.find( layer_num );
1445
1446 if( existingMapping != m_layermap.end()
1447 && existingMapping->second == PCB_LAYER_ID::UNDEFINED_LAYER )
1448 {
1449 continue;
1450 }
1451
1452 // Skip unused copper layers not present in the board's stackup. Used copper layers
1453 // were added to m_layermap during stackup parsing; any copper layer not in the map
1454 // is unused and should not appear in the dialog.
1455 if( ( ( layer_num >= ALTIUM_LAYER::TOP_LAYER && layer_num <= ALTIUM_LAYER::BOTTOM_LAYER )
1456 || IsAltiumLayerAPlane( layer_num ) )
1457 && existingMapping == m_layermap.end() )
1458 {
1459 continue;
1460 }
1461
1462 // Use existing mapping as auto-match default if available
1463 if( existingMapping != m_layermap.end() )
1464 {
1465 iLdesc.AutoMapLayer = existingMapping->second;
1466 }
1467 // Check if the layer name indicates a courtyard layer
1468 else if( IsLayerNameCourtyard( curLayer.name ) )
1469 {
1470 bool isTopSide = IsLayerNameTopSide( curLayer.name );
1471
1472 if( isTopSide && !frontCourtyardMapped )
1473 {
1474 iLdesc.AutoMapLayer = F_CrtYd;
1475 frontCourtyardMapped = true;
1476 }
1477 else if( !isTopSide && !backCourtyardMapped )
1478 {
1479 iLdesc.AutoMapLayer = B_CrtYd;
1480 backCourtyardMapped = true;
1481 }
1482 else if( !frontCourtyardMapped )
1483 {
1484 iLdesc.AutoMapLayer = F_CrtYd;
1485 frontCourtyardMapped = true;
1486 }
1487 else if( !backCourtyardMapped )
1488 {
1489 iLdesc.AutoMapLayer = B_CrtYd;
1490 backCourtyardMapped = true;
1491 }
1492 else
1493 {
1494 iLdesc.AutoMapLayer = GetKicadLayer( layer_num );
1495 }
1496 }
1497 // Check if the layer name indicates an assembly layer (map to Fab)
1498 else if( IsLayerNameAssembly( curLayer.name ) )
1499 {
1500 bool isTopSide = IsLayerNameTopSide( curLayer.name );
1501 iLdesc.AutoMapLayer = isTopSide ? F_Fab : B_Fab;
1502 }
1503 else
1504 {
1505 iLdesc.AutoMapLayer = GetKicadLayer( layer_num );
1506 }
1507
1508 iLdesc.Name = curLayer.name;
1509 iLdesc.PermittedLayers = validRemappingLayers;
1510 iLdesc.Required = layer_num >= ALTIUM_LAYER::TOP_LAYER
1511 && layer_num <= ALTIUM_LAYER::BOTTOM_LAYER;
1512
1513 inputLayers.push_back( iLdesc );
1514 altiumLayerNameMap.insert( { curLayer.name, layer_num } );
1515 m_layerNames.insert( { layer_num, curLayer.name } );
1516 }
1517
1518 if( inputLayers.size() == 0 )
1519 return;
1520
1521 // Callback:
1522 std::map<wxString, PCB_LAYER_ID> reMappedLayers = m_layerMappingHandler( inputLayers );
1523
1524 for( std::pair<wxString, PCB_LAYER_ID> layerPair : reMappedLayers )
1525 {
1526 if( layerPair.second == PCB_LAYER_ID::UNDEFINED_LAYER )
1527 {
1528 // Layer mapping handler returned UNDEFINED_LAYER - skip this layer
1529 // This can happen for layers that don't have a KiCad equivalent
1530 if( m_reporter )
1531 {
1532 m_reporter->Report( wxString::Format( _( "Layer '%s' could not be mapped and "
1533 "will be skipped." ),
1534 layerPair.first ),
1536 }
1537
1538 continue;
1539 }
1540
1541 ALTIUM_LAYER altiumID = altiumLayerNameMap.at( layerPair.first );
1542 m_layermap.insert_or_assign( altiumID, layerPair.second );
1543 enabledLayers |= LSET( { layerPair.second } );
1544 }
1545
1546 // Explicitly mark unmatched dialog layers as UNDEFINED_LAYER so they are not imported
1547 // via the GetKicadLayer() hardcoded switch fallthrough
1548 for( const auto& [name, altLayer] : altiumLayerNameMap )
1549 {
1550 if( reMappedLayers.find( name ) == reMappedLayers.end()
1551 || reMappedLayers.at( name ) == PCB_LAYER_ID::UNDEFINED_LAYER )
1552 {
1553 m_layermap.insert_or_assign( altLayer, PCB_LAYER_ID::UNDEFINED_LAYER );
1554 }
1555 }
1556
1557 m_board->SetEnabledLayers( enabledLayers );
1558 m_board->SetVisibleLayers( enabledLayers );
1559}
1560
1561
1562void ALTIUM_PCB::HelperFillMechanicalLayerAssignments( const std::vector<ABOARD6_LAYER_STACKUP>& aStackup )
1563{
1564 for( const ABOARD6_LAYER_STACKUP& layer : aStackup )
1565 {
1566 ALTIUM_LAYER alayer = static_cast<ALTIUM_LAYER>( layer.layerId );
1567
1568 if( ( alayer >= ALTIUM_LAYER::MECHANICAL_1 && alayer <= ALTIUM_LAYER::MECHANICAL_16 )
1570 {
1571 if( !layer.mechenabled )
1572 {
1573 m_layermap.emplace( alayer, UNDEFINED_LAYER ); // Disabled layer, do not import
1574 continue;
1575 }
1576
1578
1579 switch( layer.mechkind )
1580 {
1581 case ALTIUM_MECHKIND::ASSEMBLY_TOP: target = F_Fab; break;
1582 case ALTIUM_MECHKIND::ASSEMBLY_BOT: target = B_Fab; break;
1583
1584 case ALTIUM_MECHKIND::COURTYARD_TOP: target = F_CrtYd; break;
1585 case ALTIUM_MECHKIND::COURTYARD_BOT: target = B_CrtYd; break;
1586
1587 case ALTIUM_MECHKIND::GLUE_POINTS_TOP: target = F_Adhes; break;
1588 case ALTIUM_MECHKIND::GLUE_POINTS_BOT: target = B_Adhes; break;
1589
1590 case ALTIUM_MECHKIND::ASSEMBLY_NOTES: target = Cmts_User; break;
1591 case ALTIUM_MECHKIND::FAB_NOTES: target = Cmts_User; break;
1592
1593 case ALTIUM_MECHKIND::DIMENSIONS: target = Dwgs_User; break;
1594
1595 case ALTIUM_MECHKIND::DIMENSIONS_TOP: target = F_Fab; break;
1596 case ALTIUM_MECHKIND::DIMENSIONS_BOT: target = B_Fab; break;
1597
1598 case ALTIUM_MECHKIND::VALUE_TOP: target = F_Fab; break;
1599 case ALTIUM_MECHKIND::VALUE_BOT: target = B_Fab; break;
1600
1601 case ALTIUM_MECHKIND::DESIGNATOR_TOP: target = F_Fab; break;
1602 case ALTIUM_MECHKIND::DESIGNATOR_BOT: target = B_Fab; break;
1603
1604 case ALTIUM_MECHKIND::COMPONENT_OUTLINE_TOP: target = F_Fab; break;
1605 case ALTIUM_MECHKIND::COMPONENT_OUTLINE_BOT: target = B_Fab; break;
1606
1607 case ALTIUM_MECHKIND::COMPONENT_CENTER_TOP: target = F_Fab; break;
1608 case ALTIUM_MECHKIND::COMPONENT_CENTER_BOT: target = B_Fab; break;
1609
1610 case ALTIUM_MECHKIND::BOARD: target = Edge_Cuts; break;
1611 case ALTIUM_MECHKIND::BOARD_SHAPE: target = Edge_Cuts; break;
1612 case ALTIUM_MECHKIND::V_CUT: target = Edge_Cuts; break;
1613
1614 default: break;
1615 }
1616
1617 if( target != UNDEFINED_LAYER )
1618 m_layermap.emplace( alayer, target );
1619 }
1620 }
1621}
1622
1623
1624void ALTIUM_PCB::HelperCreateBoardOutline( const std::vector<ALTIUM_VERTICE>& aVertices )
1625{
1626 SHAPE_LINE_CHAIN lineChain;
1627 HelperShapeLineChainFromAltiumVertices( lineChain, aVertices );
1628
1629 STROKE_PARAMS stroke( m_board->GetDesignSettings().GetLineThickness( Edge_Cuts ),
1631
1632 for( int i = 0; i <= lineChain.PointCount() && i != -1; i = lineChain.NextShape( i ) )
1633 {
1634 if( lineChain.IsArcStart( i ) )
1635 {
1636 const SHAPE_ARC& currentArc = lineChain.Arc( lineChain.ArcIndex( i ) );
1637
1638 std::unique_ptr<PCB_SHAPE> shape = std::make_unique<PCB_SHAPE>( m_board, SHAPE_T::ARC );
1639
1640 shape->SetStroke( stroke );
1641 shape->SetLayer( Edge_Cuts );
1642 shape->SetArcGeometry( currentArc.GetP0(), currentArc.GetArcMid(), currentArc.GetP1() );
1643
1644 m_board->Add( shape.release(), ADD_MODE::APPEND );
1645 }
1646 else
1647 {
1648 const SEG& seg = lineChain.Segment( i );
1649
1650 std::unique_ptr<PCB_SHAPE> shape = std::make_unique<PCB_SHAPE>( m_board, SHAPE_T::SEGMENT );
1651
1652 shape->SetStroke( stroke );
1653 shape->SetLayer( Edge_Cuts );
1654 shape->SetStart( seg.A );
1655 shape->SetEnd( seg.B );
1656
1657 m_board->Add( shape.release(), ADD_MODE::APPEND );
1658 }
1659 }
1660}
1661
1662
1664 const CFB::COMPOUND_FILE_ENTRY* aEntry )
1665{
1666 if( m_progressReporter )
1667 m_progressReporter->Report( _( "Loading netclasses..." ) );
1668
1669 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
1670
1671 while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
1672 {
1673 checkpoint();
1674 ACLASS6 elem( reader );
1675
1677 {
1678 std::shared_ptr<NETCLASS> nc = std::make_shared<NETCLASS>( elem.name, false );
1679
1680 for( const wxString& name : elem.names )
1681 {
1682 m_board->GetDesignSettings().m_NetSettings->SetNetclassPatternAssignment( SchematicCasedNetName( name ),
1683 nc->GetName() );
1684 }
1685
1686 if( m_board->GetDesignSettings().m_NetSettings->HasNetclass( nc->GetName() ) )
1687 {
1688 // Name conflict, happens in some unknown circumstances
1689 // unique_ptr will delete nc on this code path
1690 if( m_reporter )
1691 {
1692 wxString msg;
1693 msg.Printf( _( "More than one Altium netclass with name '%s' found. "
1694 "Only the first one will be imported." ), elem.name );
1695 m_reporter->Report( msg, RPT_SEVERITY_ERROR );
1696 }
1697 }
1698 else
1699 {
1700 m_board->GetDesignSettings().m_NetSettings->SetNetclass( nc->GetName(), nc );
1701 }
1702 }
1703 }
1704
1705 if( reader.GetRemainingBytes() != 0 )
1706 THROW_IO_ERROR( wxT( "Classes6 stream is not fully parsed" ) );
1707
1708 // Now that all netclasses and pattern assignments are set up, resolve the pattern
1709 // assignments to direct netclass assignments on each net.
1711
1712 m_board->m_LegacyNetclassesLoaded = true;
1713}
1714
1715
1717{
1718 std::shared_ptr<NET_SETTINGS> netSettings = m_board->GetDesignSettings().m_NetSettings;
1719
1720 netSettings->RecomputeEffectiveNetclasses();
1721
1722 for( NETINFO_ITEM* net : m_board->GetNetInfo() )
1723 {
1724 if( net->GetNetCode() <= 0 )
1725 continue;
1726
1727 if( std::shared_ptr<NETCLASS> netclass = netSettings->GetEffectiveNetClass( net->GetNetname() ) )
1728 net->SetNetClass( netclass );
1729 }
1730}
1731
1732
1734 const CFB::COMPOUND_FILE_ENTRY* aEntry )
1735{
1736 if( m_progressReporter )
1737 m_progressReporter->Report( _( "Loading components..." ) );
1738
1739 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
1740
1741 while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
1742 {
1743 checkpoint();
1744 ACOMPONENT6 elem( reader );
1745
1746 std::unique_ptr<FOOTPRINT> footprint = std::make_unique<FOOTPRINT>( m_board );
1747
1748 // Altium stores the footprint library information needed to find the footprint in the
1749 // source library in the sourcefootprintlibrary field. Since Altium is a Windows-only
1750 // program, the path separator is always a backslash. We need strip the extra path information
1751 // here to prevent overly-long LIB_IDs because KiCad doesn't store the full path to the
1752 // footprint library in the design file, only in a library table.
1753 wxFileName libName( elem.sourcefootprintlibrary, wxPATH_WIN );
1754
1755 // The pattern field may also contain a path when Altium stores it with a full library path.
1756 // Extract just the footprint name portion to avoid creating invalid filenames.
1757 wxString fpName = elem.pattern;
1758
1759 if( fpName.Contains( wxT( "\\" ) ) || fpName.Contains( wxT( "/" ) ) )
1760 {
1761 wxFileName fpPath( fpName, wxPATH_WIN );
1762 fpName = fpPath.GetFullName();
1763 }
1764
1765 LIB_ID fpID = AltiumToKiCadLibID( libName.GetName(), fpName );
1766
1767 footprint->SetFPID( fpID );
1768
1769 footprint->SetPosition( elem.position );
1770 footprint->SetOrientationDegrees( elem.rotation );
1771
1772 // KiCad netlisting requires parts to have non-digit + digit annotation.
1773 // If the reference begins with a number, we prepend 'UNK' (unknown) for the source designator
1774 wxString reference = elem.sourcedesignator;
1775
1776 if( reference.find_first_not_of( "0123456789" ) == wxString::npos )
1777 reference.Prepend( wxT( "UNK" ) );
1778
1779 footprint->SetReference( reference );
1780
1782 KIID pathid( elem.sourceHierachicalPath );
1784 path.push_back( pathid );
1785 path.push_back( id );
1786
1787 footprint->SetPath( path );
1788 footprint->SetSheetname( elem.sourceHierachicalPath );
1789 footprint->SetSheetfile( elem.sourceHierachicalPath + wxT( ".kicad_sch" ));
1790
1791 footprint->SetLocked( elem.locked );
1792 footprint->Reference().SetVisible( elem.nameon );
1793 footprint->Value().SetVisible( elem.commenton );
1794 footprint->SetLayer( elem.layer == ALTIUM_LAYER::TOP_LAYER ? F_Cu : B_Cu );
1795
1796 m_components.emplace_back( footprint.get() );
1797 m_board->Add( footprint.release(), ADD_MODE::APPEND );
1798 }
1799
1800 if( reader.GetRemainingBytes() != 0 )
1801 THROW_IO_ERROR( wxT( "Components6 stream is not fully parsed" ) );
1802}
1803
1804
1806double normalizeAngleDegrees( double Angle, double aMin, double aMax )
1807{
1808 while( Angle < aMin )
1809 Angle += 360.0;
1810
1811 while( Angle >= aMax )
1812 Angle -= 360.0;
1813
1814 return Angle;
1815}
1816
1817
1819 FOOTPRINT* aFootprint,
1820 const ACOMPONENTBODY6& aElem )
1821{
1822 if( m_progressReporter )
1823 m_progressReporter->Report( _( "Loading component 3D models..." ) );
1824
1825 if( !aElem.modelIsEmbedded )
1826 return;
1827
1828 auto model = aAltiumPcbFile.GetLibModel( aElem.modelId );
1829
1830 if( !model )
1831 {
1832 if( m_reporter )
1833 {
1834 m_reporter->Report( wxString::Format( wxT( "Model %s not found for footprint %s" ),
1835 aElem.modelId, aFootprint->GetReference() ),
1837 }
1838
1839 return;
1840 }
1841
1842 wxString modelName = aElem.modelName.IsEmpty() ? model->first.name : aElem.modelName;
1843 bool isNew = false;
1844
1845 std::shared_ptr<EMBEDDED_FILES::EMBEDDED_FILE> file =
1846 HelperEmbedModel( aFootprint, modelName, model->second, isNew );
1847
1848 if( isNew )
1850
1851 FP_3DMODEL modelSettings;
1852
1853 modelSettings.m_Filename = file->GetLink();
1854
1855 modelSettings.m_Offset.x = pcbIUScale.IUTomm( (int) aElem.modelPosition.x );
1856 modelSettings.m_Offset.y = -pcbIUScale.IUTomm( (int) aElem.modelPosition.y );
1857 modelSettings.m_Offset.z = pcbIUScale.IUTomm( (int) aElem.modelPosition.z );
1858
1859 EDA_ANGLE orientation = aFootprint->GetOrientation();
1860
1861 if( aFootprint->IsFlipped() )
1862 {
1863 modelSettings.m_Offset.y = -modelSettings.m_Offset.y;
1864 orientation = -orientation;
1865 }
1866
1867 VECTOR3D modelRotation( aElem.modelRotation );
1868
1869 if( ( aElem.body_projection == 1 ) != aFootprint->IsFlipped() )
1870 {
1871 modelRotation.x += 180;
1872 modelRotation.z = -modelRotation.z;
1873
1874 modelSettings.m_Offset.z = -DEFAULT_BOARD_THICKNESS_MM - modelSettings.m_Offset.z;
1875 }
1876
1877 RotatePoint( &modelSettings.m_Offset.x, &modelSettings.m_Offset.y, orientation );
1878
1879 modelSettings.m_Rotation.x = normalizeAngleDegrees( -modelRotation.x, -180, 180 );
1880 modelSettings.m_Rotation.y = normalizeAngleDegrees( -modelRotation.y, -180, 180 );
1881 modelSettings.m_Rotation.z = normalizeAngleDegrees( -modelRotation.z + aElem.rotation
1882 + orientation.AsDegrees(),
1883 -180, 180 );
1884 modelSettings.m_Opacity = aElem.body_opacity_3d;
1885
1886 aFootprint->Models().push_back( modelSettings );
1887}
1888
1889
1891 const CFB::COMPOUND_FILE_ENTRY* aEntry )
1892{
1893 if( m_progressReporter )
1894 m_progressReporter->Report( _( "Loading component 3D models..." ) );
1895
1897 BS::multi_future<void> embeddedFutures;
1898
1899 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
1900
1901 while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
1902 {
1903 checkpoint();
1904 ACOMPONENTBODY6 elem( reader );
1905
1906 static const bool skipComponentBodies = ADVANCED_CFG::GetCfg().m_ImportSkipComponentBodies;
1907
1908 if( skipComponentBodies )
1909 continue;
1910
1911 if( elem.component == ALTIUM_COMPONENT_NONE )
1912 continue; // TODO: we do not support components for the board yet
1913
1914 if( m_components.size() <= elem.component )
1915 {
1916 THROW_IO_ERRORF( wxT( "ComponentsBodies6 stream tries to access component id %d of %zu existing "
1917 "components" ),
1918 elem.component,
1919 m_components.size() );
1920 }
1921
1922 if( !elem.modelIsEmbedded )
1923 continue;
1924
1925 auto modelTuple = m_EmbeddedModels.find( elem.modelId );
1926
1927 if( modelTuple == m_EmbeddedModels.end() )
1928 {
1929 if( m_reporter )
1930 {
1931 wxString msg;
1932 msg.Printf( wxT( "ComponentsBodies6 stream tries to access model id %s which does "
1933 "not exist" ), elem.modelId );
1934 m_reporter->Report( msg, RPT_SEVERITY_ERROR );
1935 }
1936
1937 continue;
1938 }
1939
1940 const ALTIUM_EMBEDDED_MODEL_DATA& modelData = modelTuple->second;
1941 FOOTPRINT* footprint = m_components.at( elem.component );
1942 bool isNew = false;
1943
1944 std::shared_ptr<EMBEDDED_FILES::EMBEDDED_FILE> file =
1945 HelperEmbedModel( footprint, modelData.m_modelname, modelData.m_data, isNew );
1946
1947 if( isNew )
1948 {
1949 // The task has to own the payload too; a throw further down the stream skips the
1950 // wait below and multi_future abandons whatever is still running
1951 embeddedFutures.push_back( tp.submit_task(
1952 [file]()
1953 {
1954 EMBEDDED_FILES::CompressAndEncode( *file );
1955 } ) );
1956 }
1957
1958 FP_3DMODEL modelSettings;
1959
1960 modelSettings.m_Filename = file->GetLink();
1961 VECTOR2I fpPosition = footprint->GetPosition();
1962
1963 modelSettings.m_Offset.x =
1964 pcbIUScale.IUTomm( KiROUND( elem.modelPosition.x - fpPosition.x ) );
1965 modelSettings.m_Offset.y =
1966 -pcbIUScale.IUTomm( KiROUND( elem.modelPosition.y - fpPosition.y ) );
1967 modelSettings.m_Offset.z = pcbIUScale.IUTomm( KiROUND( elem.modelPosition.z ) );
1968
1969 EDA_ANGLE orientation = footprint->GetOrientation();
1970
1971 if( footprint->IsFlipped() )
1972 {
1973 modelSettings.m_Offset.y = -modelSettings.m_Offset.y;
1974 orientation = -orientation;
1975 }
1976
1977 if( ( elem.body_projection == 1 ) != footprint->IsFlipped() )
1978 {
1979 elem.modelRotation.x += 180;
1980 elem.modelRotation.z = -elem.modelRotation.z;
1981
1982 modelSettings.m_Offset.z =
1983 -pcbIUScale.IUTomm( m_board->GetDesignSettings().GetBoardThickness() )
1984 - modelSettings.m_Offset.z;
1985 }
1986
1987 RotatePoint( &modelSettings.m_Offset.x, &modelSettings.m_Offset.y, orientation );
1988
1989 modelSettings.m_Rotation.x = normalizeAngleDegrees( -elem.modelRotation.x, -180, 180 );
1990 modelSettings.m_Rotation.y = normalizeAngleDegrees( -elem.modelRotation.y, -180, 180 );
1991 modelSettings.m_Rotation.z = normalizeAngleDegrees( -elem.modelRotation.z + elem.rotation
1992 + orientation.AsDegrees(),
1993 -180, 180 );
1994
1995 modelSettings.m_Opacity = elem.body_opacity_3d;
1996
1997 footprint->Models().push_back( modelSettings );
1998 }
1999
2000 embeddedFutures.wait();
2001
2002 if( reader.GetRemainingBytes() != 0 )
2003 THROW_IO_ERROR( wxT( "ComponentsBodies6 stream is not fully parsed" ) );
2004}
2005
2006
2008{
2009 if( aElem.referencePoint.size() != 2 )
2010 THROW_IO_ERROR( wxT( "Incorrect number of reference points for linear dimension object" ) );
2011
2012 PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
2013
2014 if( klayer == UNDEFINED_LAYER )
2015 {
2016 if( m_reporter )
2017 {
2018 m_reporter->Report( wxString::Format(
2019 _( "Dimension found on an Altium layer (%d) with no KiCad equivalent. "
2020 "It has been moved to KiCad layer Eco1_User." ), aElem.layer ),
2022 }
2023
2024 klayer = Eco1_User;
2025 }
2026
2027 VECTOR2I referencePoint0 = aElem.referencePoint.at( 0 );
2028 VECTOR2I referencePoint1 = aElem.referencePoint.at( 1 );
2029
2030 std::unique_ptr<PCB_DIM_ALIGNED> dimension = std::make_unique<PCB_DIM_ALIGNED>( m_board, PCB_DIM_ALIGNED_T );
2031
2032 dimension->SetPrecision( static_cast<DIM_PRECISION>( aElem.textprecision ) );
2033 dimension->SetLayer( klayer );
2034 dimension->SetStart( referencePoint0 );
2035
2036 if( referencePoint0 != aElem.xy1 )
2037 {
2047 VECTOR2I direction = aElem.xy1 - referencePoint0;
2048 VECTOR2I referenceDiff = referencePoint1 - referencePoint0;
2049 VECTOR2I directionNormalVector = direction.Perpendicular();
2050 SEG segm1( referencePoint0, referencePoint0 + directionNormalVector );
2051 SEG segm2( referencePoint1, referencePoint1 + direction );
2052 OPT_VECTOR2I intersection( segm1.Intersect( segm2, true, true ) );
2053
2054 if( !intersection )
2055 THROW_IO_ERROR( wxT( "Invalid dimension. This should never happen." ) );
2056
2057 dimension->SetEnd( *intersection );
2058
2059 int height = direction.EuclideanNorm();
2060
2061 if( direction.Cross( referenceDiff ) > 0 )
2062 height = -height;
2063
2064 dimension->SetHeight( height );
2065 }
2066 else
2067 {
2068 dimension->SetEnd( referencePoint1 );
2069 }
2070
2071 dimension->SetLineThickness( aElem.linewidth );
2072
2073 dimension->SetUnitsFormat( DIM_UNITS_FORMAT::NO_SUFFIX );
2074 dimension->SetPrefix( aElem.textprefix );
2075
2076
2077 int dist = ( dimension->GetEnd() - dimension->GetStart() ).EuclideanNorm();
2078
2079 if( dist < 3 * dimension->GetArrowLength() )
2080 dimension->SetArrowDirection( DIM_ARROW_DIRECTION::INWARD );
2081
2082 // Suffix normally (but not always) holds the units
2083 wxRegEx units( wxS( "(mm)|(in)|(mils)|(thou)|(')|(\")" ), wxRE_ADVANCED );
2084
2085 if( units.Matches( aElem.textsuffix ) )
2086 dimension->SetUnitsFormat( DIM_UNITS_FORMAT::BARE_SUFFIX );
2087 else
2088 dimension->SetSuffix( aElem.textsuffix );
2089
2090 dimension->SetTextThickness( aElem.textlinewidth );
2091 dimension->SetTextSize( VECTOR2I( aElem.textheight, aElem.textheight ) );
2092 dimension->SetItalic( aElem.textitalic );
2093
2094#if 0 // we don't currently support bold; map to thicker text
2095 dimension->Text().SetBold( aElem.textbold );
2096#else
2097 if( aElem.textbold )
2098 dimension->SetTextThickness( dimension->GetTextThickness() * BOLD_FACTOR );
2099#endif
2100
2101 switch( aElem.textunit )
2102 {
2103 case ALTIUM_UNIT::INCH: dimension->SetUnits( EDA_UNITS::INCH ); break;
2104 case ALTIUM_UNIT::MILS: dimension->SetUnits( EDA_UNITS::MILS ); break;
2105 case ALTIUM_UNIT::MM: dimension->SetUnits( EDA_UNITS::MM ); break;
2106 case ALTIUM_UNIT::CM: dimension->SetUnits( EDA_UNITS::MM ); break;
2107 default: break;
2108 }
2109
2110 m_board->Add( dimension.release(), ADD_MODE::APPEND );
2111}
2112
2113
2115{
2116 if( aElem.referencePoint.size() < 2 )
2117 THROW_IO_ERROR( wxT( "Not enough reference points for radial dimension object" ) );
2118
2119 PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
2120
2121 if( klayer == UNDEFINED_LAYER )
2122 {
2123 if( m_reporter )
2124 {
2125 m_reporter->Report( wxString::Format(
2126 _( "Dimension found on an Altium layer (%d) with no KiCad equivalent. "
2127 "It has been moved to KiCad layer Eco1_User." ),
2128 aElem.layer ), RPT_SEVERITY_INFO );
2129 }
2130
2131 klayer = Eco1_User;
2132 }
2133
2134 VECTOR2I referencePoint0 = aElem.referencePoint.at( 0 );
2135
2136 std::unique_ptr<PCB_DIM_RADIAL> dimension = std::make_unique<PCB_DIM_RADIAL>( m_board );
2137
2138 dimension->SetPrecision( static_cast<DIM_PRECISION>( aElem.textprecision ) );
2139 dimension->SetLayer( klayer );
2140 dimension->SetStart( referencePoint0 );
2141 dimension->SetEnd( aElem.xy1 );
2142 dimension->SetLineThickness( aElem.linewidth );
2143 dimension->SetKeepTextAligned( false );
2144
2145 dimension->SetPrefix( aElem.textprefix );
2146
2147 // Suffix normally holds the units
2148 dimension->SetUnitsFormat( aElem.textsuffix.IsEmpty() ? DIM_UNITS_FORMAT::NO_SUFFIX
2150
2151 switch( aElem.textunit )
2152 {
2153 case ALTIUM_UNIT::INCH: dimension->SetUnits( EDA_UNITS::INCH ); break;
2154 case ALTIUM_UNIT::MILS: dimension->SetUnits( EDA_UNITS::MILS ); break;
2155 case ALTIUM_UNIT::MM: dimension->SetUnits( EDA_UNITS::MM ); break;
2156 case ALTIUM_UNIT::CM: dimension->SetUnits( EDA_UNITS::MM ); break;
2157 default: break;
2158 }
2159
2160 if( aElem.textPoint.empty() )
2161 {
2162 if( m_reporter )
2163 {
2164 m_reporter->Report( wxT( "No text position present for leader dimension object" ),
2166 }
2167
2168 return;
2169 }
2170
2171 dimension->SetTextPos( aElem.textPoint.at( 0 ) );
2172 dimension->SetTextThickness( aElem.textlinewidth );
2173 dimension->SetTextSize( VECTOR2I( aElem.textheight, aElem.textheight ) );
2174 dimension->SetItalic( aElem.textitalic );
2175
2176#if 0 // we don't currently support bold; map to thicker text
2177 dimension->SetBold( aElem.textbold );
2178#else
2179 if( aElem.textbold )
2180 dimension->SetTextThickness( dimension->GetTextThickness() * BOLD_FACTOR );
2181#endif
2182
2183 // It's unclear exactly how Altium figures it's text positioning, but this gets us reasonably
2184 // close.
2185 dimension->SetVertJustify( GR_TEXT_V_ALIGN_BOTTOM );
2186 dimension->SetHorizJustify( GR_TEXT_H_ALIGN_LEFT );
2187
2188 int yAdjust = dimension->GetTextBox( nullptr ).GetCenter().y - dimension->GetTextPos().y;
2189 dimension->SetTextPos( dimension->GetTextPos() + VECTOR2I( 0, yAdjust + aElem.textgap ) );
2190 dimension->SetVertJustify( GR_TEXT_V_ALIGN_CENTER );
2191
2192 m_radialDimensions.push_back( dimension.get() );
2193 m_board->Add( dimension.release(), ADD_MODE::APPEND );
2194}
2195
2196
2198{
2199 PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
2200
2201 if( klayer == UNDEFINED_LAYER )
2202 {
2203 if( m_reporter )
2204 {
2205 wxString msg;
2206 msg.Printf( _( "Dimension found on an Altium layer (%d) with no KiCad equivalent. "
2207 "It has been moved to KiCad layer Eco1_User." ), aElem.layer );
2208 m_reporter->Report( msg, RPT_SEVERITY_ERROR );
2209 }
2210
2211 klayer = Eco1_User;
2212 }
2213
2214 if( !aElem.referencePoint.empty() )
2215 {
2216 VECTOR2I referencePoint0 = aElem.referencePoint.at( 0 );
2217
2218 // line
2219 VECTOR2I last = referencePoint0;
2220 for( size_t i = 1; i < aElem.referencePoint.size(); i++ )
2221 {
2222 std::unique_ptr<PCB_SHAPE> shape = std::make_unique<PCB_SHAPE>( m_board, SHAPE_T::SEGMENT );
2223
2224 shape->SetLayer( klayer );
2225 shape->SetStroke( STROKE_PARAMS( aElem.linewidth, LINE_STYLE::SOLID ) );
2226 shape->SetStart( last );
2227 shape->SetEnd( aElem.referencePoint.at( i ) );
2228 last = aElem.referencePoint.at( i );
2229
2230 m_board->Add( shape.release(), ADD_MODE::APPEND );
2231 }
2232
2233 // arrow
2234 if( aElem.referencePoint.size() >= 2 )
2235 {
2236 VECTOR2I dirVec = aElem.referencePoint.at( 1 ) - referencePoint0;
2237
2238 if( dirVec.x != 0 || dirVec.y != 0 )
2239 {
2240 double scaling = (double) dirVec.EuclideanNorm() / aElem.arrowsize;
2241 VECTOR2I arrVec = KiROUND( dirVec.x / scaling, dirVec.y / scaling );
2242 RotatePoint( arrVec, EDA_ANGLE( 20.0, DEGREES_T ) );
2243
2244 {
2245 std::unique_ptr<PCB_SHAPE> shape1 = std::make_unique<PCB_SHAPE>( m_board, SHAPE_T::SEGMENT );
2246
2247 shape1->SetLayer( klayer );
2248 shape1->SetStroke( STROKE_PARAMS( aElem.linewidth, LINE_STYLE::SOLID ) );
2249 shape1->SetStart( referencePoint0 );
2250 shape1->SetEnd( referencePoint0 + arrVec );
2251
2252 m_board->Add( shape1.release(), ADD_MODE::APPEND );
2253 }
2254
2255 RotatePoint( arrVec, EDA_ANGLE( -40.0, DEGREES_T ) );
2256
2257 {
2258 std::unique_ptr<PCB_SHAPE> shape2 = std::make_unique<PCB_SHAPE>( m_board, SHAPE_T::SEGMENT );
2259
2260 shape2->SetLayer( klayer );
2261 shape2->SetStroke( STROKE_PARAMS( aElem.linewidth, LINE_STYLE::SOLID ) );
2262 shape2->SetStart( referencePoint0 );
2263 shape2->SetEnd( referencePoint0 + arrVec );
2264
2265 m_board->Add( shape2.release(), ADD_MODE::APPEND );
2266 }
2267 }
2268 }
2269 }
2270
2271 if( aElem.textPoint.empty() )
2272 {
2273 if( m_reporter )
2274 {
2275 m_reporter->Report( wxT( "No text position present for leader dimension object" ),
2277 }
2278
2279 return;
2280 }
2281
2282 std::unique_ptr<PCB_TEXT> text = std::make_unique<PCB_TEXT>( m_board );
2283
2284 text->SetText( aElem.textformat );
2285 text->SetPosition( aElem.textPoint.at( 0 ) );
2286 text->SetLayer( klayer );
2287 text->SetTextSize( VECTOR2I( aElem.textheight, aElem.textheight ) ); // TODO: parse text width
2288 text->SetTextThickness( aElem.textlinewidth );
2289 text->SetHorizJustify( GR_TEXT_H_ALIGN_LEFT );
2290 text->SetVertJustify( GR_TEXT_V_ALIGN_BOTTOM );
2291
2292 m_board->Add( text.release(), ADD_MODE::APPEND );
2293}
2294
2295
2297{
2298 PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
2299
2300 if( klayer == UNDEFINED_LAYER )
2301 {
2302 if( m_reporter )
2303 {
2304 wxString msg;
2305 msg.Printf( _( "Dimension found on an Altium layer (%d) with no KiCad equivalent. "
2306 "It has been moved to KiCad layer Eco1_User." ), aElem.layer );
2307 m_reporter->Report( msg, RPT_SEVERITY_INFO );
2308 }
2309
2310 klayer = Eco1_User;
2311 }
2312
2313 for( size_t i = 0; i < aElem.referencePoint.size(); i++ )
2314 {
2315 std::unique_ptr<PCB_SHAPE> shape = std::make_unique<PCB_SHAPE>( m_board, SHAPE_T::SEGMENT );
2316
2317 shape->SetLayer( klayer );
2318 shape->SetStroke( STROKE_PARAMS( aElem.linewidth, LINE_STYLE::SOLID ) );
2319 shape->SetStart( aElem.referencePoint.at( i ) );
2320 // shape->SetEnd( /* TODO: seems to be based on TEXTY */ );
2321
2322 m_board->Add( shape.release(), ADD_MODE::APPEND );
2323 }
2324}
2325
2326
2328{
2329 PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
2330
2331 if( klayer == UNDEFINED_LAYER )
2332 {
2333 if( m_reporter )
2334 {
2335 wxString msg;
2336 msg.Printf( _( "Dimension found on an Altium layer (%d) with no KiCad equivalent. "
2337 "It has been moved to KiCad layer Eco1_User." ), aElem.layer );
2338 m_reporter->Report( msg, RPT_SEVERITY_INFO );
2339 }
2340
2341 klayer = Eco1_User;
2342 }
2343
2344 VECTOR2I vec = VECTOR2I( 0, aElem.height / 2 );
2345 RotatePoint( vec, EDA_ANGLE( aElem.angle, DEGREES_T ) );
2346
2347 std::unique_ptr<PCB_DIM_CENTER> dimension = std::make_unique<PCB_DIM_CENTER>( m_board );
2348
2349 dimension->SetLayer( klayer );
2350 dimension->SetLineThickness( aElem.linewidth );
2351 dimension->SetStart( aElem.xy1 );
2352 dimension->SetEnd( aElem.xy1 + vec );
2353
2354 m_board->Add( dimension.release(), ADD_MODE::APPEND );
2355}
2356
2357
2359 const CFB::COMPOUND_FILE_ENTRY* aEntry )
2360{
2361 if( m_progressReporter )
2362 m_progressReporter->Report( _( "Loading dimension drawings..." ) );
2363
2364 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
2365
2366 while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
2367 {
2368 checkpoint();
2369 ADIMENSION6 elem( reader );
2370
2371 switch( elem.kind )
2372 {
2375 break;
2377 if( m_reporter )
2378 {
2379 m_reporter->Report( wxString::Format( _( "Ignored Angular dimension (not yet supported)." ) ),
2381 }
2382 break;
2385 break;
2388 break;
2390 if( m_reporter )
2391 {
2392 m_reporter->Report( wxString::Format( _( "Ignored Datum dimension (not yet supported)." ) ),
2394 }
2395 // HelperParseDimensions6Datum( elem );
2396 break;
2398 if( m_reporter )
2399 {
2400 m_reporter->Report( wxString::Format( _( "Ignored Baseline dimension (not yet supported)." ) ),
2402 }
2403 break;
2406 break;
2408 if( m_reporter )
2409 {
2410 m_reporter->Report( wxString::Format( _( "Ignored Linear dimension (not yet supported)." ) ),
2412 }
2413 break;
2415 if( m_reporter )
2416 {
2417 m_reporter->Report( wxString::Format( _( "Ignored Radial dimension (not yet supported)." ) ),
2419 }
2420 break;
2421 default:
2422 if( m_reporter )
2423 {
2424 wxString msg;
2425 msg.Printf( _( "Ignored dimension of kind %d (not yet supported)." ), elem.kind );
2426 m_reporter->Report( msg, RPT_SEVERITY_INFO );
2427 }
2428 break;
2429 }
2430 }
2431
2432 if( reader.GetRemainingBytes() != 0 )
2433 THROW_IO_ERROR( wxT( "Dimensions6 stream is not fully parsed" ) );
2434}
2435
2436
2438 const CFB::COMPOUND_FILE_ENTRY* aEntry,
2439 const std::vector<std::string>& aRootDir )
2440{
2441 if( m_progressReporter )
2442 m_progressReporter->Report( _( "Loading 3D models..." ) );
2443
2444 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
2445
2446 if( reader.GetRemainingBytes() == 0 )
2447 return;
2448
2449 int idx = 0;
2450 wxString invalidChars = wxFileName::GetForbiddenChars();
2451
2452 while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
2453 {
2454 checkpoint();
2455 AMODEL elem( reader );
2456
2457 std::vector<std::string> stepPath = aRootDir;
2458 stepPath.emplace_back( std::to_string( idx ) );
2459
2460 bool validName = !elem.name.IsEmpty() && elem.name.IsAscii()
2461 && wxString::npos == elem.name.find_first_of( invalidChars );
2462 wxString storageName = validName ? elem.name : wxString::Format( wxT( "model_%d" ), idx );
2463
2464 idx++;
2465
2466 const CFB::COMPOUND_FILE_ENTRY* stepEntry = aAltiumPcbFile.FindStream( stepPath );
2467
2468 if( stepEntry == nullptr )
2469 {
2470 if( m_reporter )
2471 {
2472 wxString msg;
2473 msg.Printf( _( "File not found: '%s'. 3D-model not imported." ), FormatPath( stepPath ) );
2474 m_reporter->Report( msg, RPT_SEVERITY_ERROR );
2475 }
2476
2477 continue;
2478 }
2479
2480 size_t stepSize = static_cast<size_t>( stepEntry->size );
2481 std::vector<char> stepContent( stepSize );
2482
2483 // read file into buffer
2484 aAltiumPcbFile.GetCompoundFileReader().ReadFile( stepEntry, 0, stepContent.data(),
2485 stepSize );
2486
2487 m_EmbeddedModels.insert( std::make_pair(
2488 elem.id, ALTIUM_EMBEDDED_MODEL_DATA( storageName, elem.rotation, elem.z_offset,
2489 std::move( stepContent ) ) ) );
2490 }
2491
2492 // Append _<index> to duplicate filenames
2493 std::map<wxString, std::vector<wxString>> nameIdMap;
2494
2495 for( auto& [id, data] : m_EmbeddedModels )
2496 nameIdMap[data.m_modelname].push_back( id );
2497
2498 for( auto& [name, ids] : nameIdMap )
2499 {
2500 for( size_t i = 1; i < ids.size(); i++ )
2501 {
2502 const wxString& id = ids[i];
2503
2504 auto modelTuple = m_EmbeddedModels.find( id );
2505
2506 if( modelTuple == m_EmbeddedModels.end() )
2507 continue;
2508
2509 wxString modelName = modelTuple->second.m_modelname;
2510
2511 if( modelName.Contains( "." ) )
2512 {
2513 wxString ext;
2514 wxString baseName = modelName.BeforeLast( '.', &ext );
2515
2516 modelTuple->second.m_modelname = baseName + '_' + std::to_string( i ) + '.' + ext;
2517 }
2518 else
2519 {
2520 modelTuple->second.m_modelname = modelName + '_' + std::to_string( i );
2521 }
2522 }
2523 }
2524
2525 if( reader.GetRemainingBytes() != 0 )
2526 THROW_IO_ERROR( wxT( "Models stream is not fully parsed" ) );
2527}
2528
2529
2530static void altiumCollectSchematicNetNames( const wxString& aFileName, std::map<wxString, wxString>& aNames,
2531 std::set<wxString>& aAmbiguous )
2532{
2533 auto collect = [&]( const std::map<wxString, wxString>& aProps )
2534 {
2535 int record = ALTIUM_PROPS_UTILS::ReadInt( aProps, wxT( "RECORD" ), 0 );
2536 wxString name;
2537
2538 switch( record )
2539 {
2540 case 16: // sheet entry
2541 case 18: // port
2542 name = ALTIUM_PROPS_UTILS::ReadString( aProps, wxT( "NAME" ), wxT( "" ) );
2543 break;
2544
2545 case 17: // power port
2546 case 25: // net label
2547 name = ALTIUM_PROPS_UTILS::ReadString( aProps, wxT( "TEXT" ), wxT( "" ) );
2548 break;
2549
2550 default: return;
2551 }
2552
2553 if( name.IsEmpty() )
2554 return;
2555
2556 wxString key = name.Upper();
2557 auto it = aNames.find( key );
2558
2559 if( it == aNames.end() )
2560 aNames.emplace( key, name );
2561 else if( it->second != name )
2562 aAmbiguous.insert( key );
2563 };
2564
2566 {
2567 ALTIUM_COMPOUND_FILE schFile( aFileName );
2568 const CFB::COMPOUND_FILE_ENTRY* header = schFile.FindStream( { "FileHeader" } );
2569
2570 if( !header )
2571 return;
2572
2573 ALTIUM_BINARY_PARSER reader( schFile, header );
2574
2575 while( reader.GetRemainingBytes() > 0 )
2576 collect( reader.ReadProperties() );
2577 }
2578 else
2579 {
2580 ALTIUM_ASCII_PARSER reader( aFileName );
2581
2582 while( reader.CanRead() )
2583 collect( reader.ReadProperties() );
2584 }
2585}
2586
2587
2588void ALTIUM_PCB::MapSchematicNetNames( const std::map<std::string, UTF8>& aProperties )
2589{
2590 std::set<wxString> ambiguous;
2591
2592 for( int i = 0;; i++ )
2593 {
2594 auto it = aProperties.find( "sch" + std::to_string( i ) );
2595
2596 if( it == aProperties.end() )
2597 break;
2598
2599 try
2600 {
2601 altiumCollectSchematicNetNames( it->second.wx_str(), m_schematicNetNames, ambiguous );
2602 }
2603 catch( ... )
2604 {
2605 // an unreadable schematic must not break the board import
2606 }
2607 }
2608
2609 for( const wxString& key : ambiguous )
2610 m_schematicNetNames.erase( key );
2611}
2612
2613
2614wxString ALTIUM_PCB::SchematicCasedNetName( const wxString& aNetName ) const
2615{
2616 auto it = m_schematicNetNames.find( aNetName.Upper() );
2617
2618 if( it != m_schematicNetNames.end() )
2619 return it->second;
2620
2621 return aNetName;
2622}
2623
2624
2626 const CFB::COMPOUND_FILE_ENTRY* aEntry )
2627{
2628 if( m_progressReporter )
2629 m_progressReporter->Report( _( "Loading nets..." ) );
2630
2631 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
2632
2633 wxASSERT( m_altiumToKicadNetcodes.empty() );
2634
2635 int unnamedNetCount = 0;
2636
2637 while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
2638 {
2639 checkpoint();
2640 ANET6 elem( reader );
2641
2642 wxString netName = SchematicCasedNetName( elem.name );
2643
2644 if( netName.IsEmpty() )
2645 {
2646 netName = AltiumUnnamedNetName( *m_board, unnamedNetCount );
2647
2648 if( m_reporter )
2649 {
2650 m_reporter->Report( wxString::Format( _( "Altium net %zu has no name; imported as '%s'." ),
2651 m_altiumToKicadNetcodes.size(), netName ),
2653 }
2654 }
2655
2656 NETINFO_ITEM* netInfo = new NETINFO_ITEM( m_board, netName, -1 );
2657 m_board->Add( netInfo, ADD_MODE::APPEND );
2658
2659 // needs to be called after m_board->Add() as assign us the NetCode
2660 m_altiumToKicadNetcodes.push_back( netInfo->GetNetCode() );
2661 }
2662
2663 if( reader.GetRemainingBytes() != 0 )
2664 THROW_IO_ERROR( wxT( "Nets6 stream is not fully parsed" ) );
2665}
2666
2668 const CFB::COMPOUND_FILE_ENTRY* aEntry )
2669{
2670 if( m_progressReporter )
2671 m_progressReporter->Report( _( "Loading polygons..." ) );
2672
2673 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
2674
2675 while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
2676 {
2677 checkpoint();
2678 APOLYGON6 elem( reader );
2679
2680 if( elem.discardedVertices > 0 && m_reporter )
2681 {
2682 m_reporter->Report( wxString::Format( _( "Polygon on layer '%s' has %d vertices "
2683 "outside the coordinate range; they were "
2684 "dropped." ),
2685 LayerName( GetKicadLayer( elem.layer ) ),
2686 elem.discardedVertices ),
2688 }
2689
2690 SHAPE_LINE_CHAIN linechain;
2692
2693 if( linechain.PointCount() < 3 )
2694 {
2695 // We have found multiple Altium files with polygon records containing nothing but two
2696 // coincident vertices. These polygons do not appear when opening the file in Altium.
2697 // https://gitlab.com/kicad/code/kicad/-/issues/8183
2698 // Also, polygons with less than 3 points are not supported in KiCad.
2699 //
2700 // wxLogError( _( "Polygon has only %d point extracted from %ld vertices. At least 2 "
2701 // "points are required." ),
2702 // linechain.PointCount(),
2703 // elem.vertices.size() );
2704
2705 m_polygons.emplace_back( nullptr );
2706 continue;
2707 }
2708
2709 SHAPE_POLY_SET outline( linechain );
2710
2712 {
2713 // Altium "Hatched" or "None" polygon outlines have thickness, convert it to KiCad's representation.
2715 ARC_HIGH_DEF, true );
2716 }
2717
2718 if( outline.OutlineCount() != 1 && m_reporter )
2719 {
2720 wxString msg;
2721 msg.Printf( _( "Polygon outline count is %d, expected 1." ), outline.OutlineCount() );
2722
2723 m_reporter->Report( msg, RPT_SEVERITY_ERROR );
2724 }
2725
2726 if( outline.OutlineCount() == 0 )
2727 continue;
2728
2729 std::unique_ptr<ZONE> zone = std::make_unique<ZONE>(m_board);
2730
2731 // Be sure to set the zone layer before setting the net code
2732 // so that we know that this is a copper zone and so needs a valid net code.
2733 HelperSetZoneLayers( *zone, elem.layer );
2734 zone->SetNetCode( GetNetCode( elem.net ) );
2735 zone->SetPosition( elem.vertices.at( 0 ).position );
2736 zone->SetLocked( elem.locked );
2737 zone->SetAssignedPriority( elem.pourindex > 0 ? elem.pourindex : 0 );
2738 zone->Outline()->AddOutline( outline.Outline( 0 ) );
2739
2740 if( elem.pourindex > m_highest_pour_index )
2742
2743 const ARULE6* planeClearanceRule = GetRuleForPolygon( ALTIUM_RULE_KIND::PLANE_CLEARANCE );
2744 const ARULE6* zoneClearanceRule = GetRuleForPolygon( ALTIUM_RULE_KIND::CLEARANCE );
2745 int planeLayers = 0;
2746 int signalLayers = 0;
2747 int clearance = 0;
2748
2749 for( PCB_LAYER_ID layer : zone->GetLayerSet() )
2750 {
2751 LAYER_T layerType = m_board->GetLayerType( layer );
2752
2753 if( layerType == LT_POWER || layerType == LT_MIXED )
2754 planeLayers++;
2755
2756 if( layerType == LT_SIGNAL || layerType == LT_MIXED )
2757 signalLayers++;
2758 }
2759
2760 if( planeLayers > 0 && planeClearanceRule )
2761 clearance = std::max( clearance, planeClearanceRule->planeclearanceClearance );
2762
2763 if( signalLayers > 0 && zoneClearanceRule )
2764 clearance = std::max( clearance, zoneClearanceRule->clearanceGap );
2765
2766 if( clearance > 0 )
2767 zone->SetLocalClearance( clearance );
2768
2769 const ARULE6* polygonConnectRule = GetRuleForPolygon( ALTIUM_RULE_KIND::POLYGON_CONNECT );
2770
2771 if( polygonConnectRule != nullptr )
2772 {
2773 switch( polygonConnectRule->polygonconnectStyle )
2774 {
2776 zone->SetPadConnection( ZONE_CONNECTION::FULL );
2777 break;
2778
2780 zone->SetPadConnection( ZONE_CONNECTION::NONE );
2781 break;
2782
2783 default:
2785 zone->SetPadConnection( ZONE_CONNECTION::THERMAL );
2786 break;
2787 }
2788
2789 // TODO: correct variables?
2790 zone->SetThermalReliefSpokeWidth(
2791 polygonConnectRule->polygonconnectReliefconductorwidth );
2792 zone->SetThermalReliefGap( polygonConnectRule->polygonconnectAirgapwidth );
2793
2794 if( polygonConnectRule->polygonconnectReliefconductorwidth < zone->GetMinThickness() )
2795 zone->SetMinThickness( polygonConnectRule->polygonconnectReliefconductorwidth );
2796 }
2797
2798 if( IsAltiumLayerAPlane( elem.layer ) )
2799 {
2800 // outer zone will be set to priority 0 later.
2801 zone->SetAssignedPriority( 1 );
2802
2803 // check if this is the outer zone by simply comparing the BBOX
2804 const auto& outer_plane = m_outer_plane.find( elem.layer );
2805 if( outer_plane == m_outer_plane.end()
2806 || zone->GetBoundingBox().Contains( outer_plane->second->GetBoundingBox() ) )
2807 {
2808 m_outer_plane[elem.layer] = zone.get();
2809 }
2810 }
2811
2814 {
2815 zone->SetFillMode( ZONE_FILL_MODE::HATCH_PATTERN );
2816 zone->SetHatchThickness( elem.trackwidth );
2817
2819 {
2820 // use a small hack to get us only an outline (hopefully)
2821 const BOX2I& bbox = zone->GetBoundingBox();
2822 zone->SetHatchGap( std::max( bbox.GetHeight(), bbox.GetWidth() ) );
2823 }
2824 else
2825 {
2826 zone->SetHatchGap( elem.gridsize - elem.trackwidth );
2827 }
2828
2830 zone->SetHatchOrientation( ANGLE_45 );
2831 }
2832
2833 zone->SetBorderDisplayStyle( ZONE_BORDER_DISPLAY_STYLE::DIAGONAL_EDGE,
2835
2836 m_polygons.emplace_back( zone.get() );
2837 m_board->Add( zone.release(), ADD_MODE::APPEND );
2838 }
2839
2840 if( reader.GetRemainingBytes() != 0 )
2841 THROW_IO_ERROR( wxT( "Polygons6 stream is not fully parsed" ) );
2842}
2843
2845 const CFB::COMPOUND_FILE_ENTRY* aEntry )
2846{
2847 if( m_progressReporter )
2848 m_progressReporter->Report( _( "Loading rules..." ) );
2849
2850 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
2851
2852 while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
2853 {
2854 checkpoint();
2855 ARULE6 elem( reader );
2856
2857 m_rules[elem.kind].emplace_back( elem );
2858 }
2859
2860 // Sort by ARULE6::priority ascending. Altium priority 1 is the most specific, so the
2861 // first element after sorting is the highest-priority Altium rule.
2862 for( std::pair<const ALTIUM_RULE_KIND, std::vector<ARULE6>>& val : m_rules )
2863 {
2864 std::sort( val.second.begin(), val.second.end(),
2865 []( const ARULE6& lhs, const ARULE6& rhs )
2866 {
2867 return lhs.priority < rhs.priority;
2868 } );
2869 }
2870
2871 const ARULE6* clearanceRule = GetRuleDefault( ALTIUM_RULE_KIND::CLEARANCE );
2872 const ARULE6* trackWidthRule = GetRuleDefault( ALTIUM_RULE_KIND::WIDTH );
2873 const ARULE6* routingViasRule = GetRuleDefault( ALTIUM_RULE_KIND::ROUTING_VIAS );
2874 const ARULE6* holeSizeRule = GetRuleDefault( ALTIUM_RULE_KIND::HOLE_SIZE );
2877
2878 if( clearanceRule )
2879 m_board->GetDesignSettings().m_MinClearance = clearanceRule->clearanceGap;
2880
2881 if( trackWidthRule )
2882 {
2883 m_board->GetDesignSettings().m_TrackMinWidth = trackWidthRule->minLimit;
2884 // TODO: construct a custom rule for preferredWidth and maxLimit values
2885 }
2886
2887 if( routingViasRule )
2888 {
2889 m_board->GetDesignSettings().m_ViasMinSize = routingViasRule->minWidth;
2890 m_board->GetDesignSettings().m_MinThroughDrill = routingViasRule->minHoleWidth;
2891 }
2892
2893 if( holeSizeRule )
2894 {
2895 // TODO: construct a custom rule for minLimit / maxLimit values
2896 }
2897
2898 if( holeToHoleRule )
2899 m_board->GetDesignSettings().m_HoleToHoleMin = holeToHoleRule->clearanceGap;
2900
2901 if( boardOutlineRule )
2902 m_board->GetDesignSettings().m_CopperEdgeClearance = boardOutlineRule->clearanceGap;
2903
2906
2907 if( soldermaskRule )
2908 m_board->GetDesignSettings().m_SolderMaskExpansion = soldermaskRule->soldermaskExpansion;
2909
2910 if( pastemaskRule )
2911 m_board->GetDesignSettings().m_SolderPasteMargin = pastemaskRule->pastemaskExpansion;
2912
2913 std::shared_ptr<NET_SETTINGS> netSettings = m_board->GetDesignSettings().m_NetSettings;
2914 std::shared_ptr<NETCLASS> defaultNetclass = netSettings->GetDefaultNetclass();
2915
2916 if( clearanceRule )
2917 defaultNetclass->SetClearance( clearanceRule->clearanceGap );
2918
2919 if( trackWidthRule )
2920 defaultNetclass->SetTrackWidth( trackWidthRule->preferredWidth );
2921
2922 if( routingViasRule )
2923 {
2924 defaultNetclass->SetViaDiameter( routingViasRule->width );
2925 defaultNetclass->SetViaDrill( routingViasRule->holeWidth );
2926 }
2927
2928 std::vector<const ARULE6*> unresolvedNetclassRules;
2929
2930 ApplyAltiumNetclassRules( m_rules, *netSettings, &unresolvedNetclassRules );
2931
2932 if( m_reporter )
2933 {
2934 for( const ARULE6* rule : unresolvedNetclassRules )
2935 {
2936 wxString netclassName;
2937 GetAltiumNetclassScopeName( *rule, &netclassName );
2938
2939 m_reporter->Report( wxString::Format( _( "Altium rule '%s' applies to netclass '%s', which this "
2940 "board does not define. Its constraint is not imported." ),
2941 rule->name, netclassName ),
2943 }
2944 }
2945
2946 // Composite netclasses cached the values we just changed
2948
2949 if( reader.GetRemainingBytes() != 0 )
2950 THROW_IO_ERROR( wxT( "Rules6 stream is not fully parsed" ) );
2951}
2952
2954 const CFB::COMPOUND_FILE_ENTRY* aEntry )
2955{
2956 if( m_progressReporter )
2957 m_progressReporter->Report( _( "Loading board regions..." ) );
2958
2959 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
2960
2961 while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
2962 {
2963 checkpoint();
2964 AREGION6 elem( reader, false );
2965
2966 // TODO: implement?
2967 }
2968
2969 if( reader.GetRemainingBytes() != 0 )
2970 THROW_IO_ERROR( wxT( "BoardRegions stream is not fully parsed" ) );
2971}
2972
2974 const CFB::COMPOUND_FILE_ENTRY* aEntry )
2975{
2976 if( m_progressReporter )
2977 m_progressReporter->Report( _( "Loading polygons..." ) );
2978
2979 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
2980
2981 /* TODO: use Header section of file */
2982 for( int primitiveIndex = 0; reader.GetRemainingBytes() >= 4; primitiveIndex++ )
2983 {
2984 checkpoint();
2985 AREGION6 elem( reader, true );
2986
2989 {
2990 // TODO: implement all different types for footprints
2991 ConvertShapeBasedRegions6ToBoardItem( elem, primitiveIndex );
2992 }
2993 else
2994 {
2995 FOOTPRINT* footprint = HelperGetFootprint( elem.component );
2996 ConvertShapeBasedRegions6ToFootprintItem( footprint, elem, primitiveIndex );
2997 }
2998 }
2999
3000 if( reader.GetRemainingBytes() != 0 )
3001 THROW_IO_ERROR( wxT( "ShapeBasedRegions6 stream is not fully parsed" ) );
3002}
3003
3004
3005void ALTIUM_PCB::ConvertShapeBasedRegions6ToBoardItem( const AREGION6& aElem, const int aPrimitiveIndex )
3006{
3008 {
3010 }
3011 else if( aElem.kind == ALTIUM_REGION_KIND::POLYGON_CUTOUT || aElem.is_keepout )
3012 {
3013 SHAPE_LINE_CHAIN linechain;
3015
3016 if( linechain.PointCount() < 3 )
3017 {
3018 // We have found multiple Altium files with polygon records containing nothing but
3019 // two coincident vertices. These polygons do not appear when opening the file in
3020 // Altium. https://gitlab.com/kicad/code/kicad/-/issues/8183
3021 // Also, polygons with less than 3 points are not supported in KiCad.
3022 return;
3023 }
3024
3025 // A polygon cutout only removes copper, a keepout carries its own mask
3026 uint8_t restrictions = aElem.is_keepout ? HelperGetKeepoutRestrictions( aElem.keepoutrestrictions, aElem.layer )
3028
3029 if( restrictions == 0 )
3030 return;
3031
3032 std::unique_ptr<ZONE> zone = std::make_unique<ZONE>( m_board );
3033
3034 zone->SetIsRuleArea( true );
3035
3036 HelperSetZoneKeepoutRestrictions( *zone, restrictions );
3037
3038 zone->SetPosition( aElem.outline.at( 0 ).position );
3039 zone->Outline()->AddOutline( linechain );
3040
3041 HelperSetZoneLayers( *zone, aElem.layer );
3042
3043 zone->SetBorderDisplayStyle( ZONE_BORDER_DISPLAY_STYLE::DIAGONAL_EDGE,
3045
3046 m_board->Add( zone.release(), ADD_MODE::APPEND );
3047 }
3048 else if( aElem.is_teardrop )
3049 {
3050 SHAPE_LINE_CHAIN linechain;
3052
3053 if( linechain.PointCount() < 3 )
3054 {
3055 // Polygons with less than 3 points are not supported in KiCad.
3056 return;
3057 }
3058
3059 std::unique_ptr<ZONE> zone = std::make_unique<ZONE>( m_board );
3060
3061 zone->SetPosition( aElem.outline.at( 0 ).position );
3062 zone->Outline()->AddOutline( linechain );
3063
3064 HelperSetZoneLayers( *zone, aElem.layer );
3065 zone->SetNetCode( GetNetCode( aElem.net ) );
3066 zone->SetTeardropAreaType( TEARDROP_TYPE::TD_UNSPECIFIED );
3067 zone->SetHatchStyle( ZONE_BORDER_DISPLAY_STYLE::INVISIBLE_BORDER );
3068
3069 SHAPE_POLY_SET fill;
3070 fill.Append( linechain );
3071 fill.Fracture();
3072
3073 for( PCB_LAYER_ID klayer : GetKicadLayersToIterate( aElem.layer ) )
3074 zone->SetFilledPolysList( klayer, fill );
3075
3076 zone->SetIsFilled( true );
3077 zone->SetNeedRefill( false );
3078
3079 m_board->Add( zone.release(), ADD_MODE::APPEND );
3080 }
3081 else if( aElem.kind == ALTIUM_REGION_KIND::DASHED_OUTLINE )
3082 {
3083 PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
3084
3085 if( klayer == UNDEFINED_LAYER )
3086 {
3087 if( m_reporter )
3088 {
3089 wxString msg;
3090 msg.Printf( _( "Dashed outline found on an Altium layer (%d) with no KiCad equivalent. "
3091 "It has been moved to KiCad layer Eco1_User." ), aElem.layer );
3092 m_reporter->Report( msg, RPT_SEVERITY_ERROR );
3093 }
3094
3095 klayer = Eco1_User;
3096 }
3097
3098 SHAPE_LINE_CHAIN linechain;
3100
3101 if( linechain.PointCount() < 3 )
3102 {
3103 // We have found multiple Altium files with polygon records containing nothing but
3104 // two coincident vertices. These polygons do not appear when opening the file in
3105 // Altium. https://gitlab.com/kicad/code/kicad/-/issues/8183
3106 // Also, polygons with less than 3 points are not supported in KiCad.
3107 return;
3108 }
3109
3110 std::unique_ptr<PCB_SHAPE> shape = std::make_unique<PCB_SHAPE>( m_board, SHAPE_T::POLY );
3111
3112 shape->SetPolyShape( linechain );
3113 shape->SetFilled( false );
3114 shape->SetLayer( klayer );
3115 shape->SetStroke( STROKE_PARAMS( pcbIUScale.mmToIU( 0.1 ), LINE_STYLE::DASH ) );
3116
3117 m_board->Add( shape.release(), ADD_MODE::APPEND );
3118 }
3119 else if( aElem.kind == ALTIUM_REGION_KIND::COPPER )
3120 {
3121 if( aElem.polygon == ALTIUM_POLYGON_NONE )
3122 {
3123 for( PCB_LAYER_ID klayer : GetKicadLayersToIterate( aElem.layer ) )
3124 ConvertShapeBasedRegions6ToBoardItemOnLayer( aElem, klayer, aPrimitiveIndex );
3125 }
3126 }
3127 else
3128 {
3129 if( m_reporter )
3130 {
3131 wxString msg;
3132 msg.Printf( _( "Ignored polygon shape of kind %d (not yet supported)." ), aElem.kind );
3133 m_reporter->Report( msg, RPT_SEVERITY_ERROR );
3134 }
3135 }
3136}
3137
3138
3140 const AREGION6& aElem,
3141 const int aPrimitiveIndex )
3142{
3143 if( aElem.kind == ALTIUM_REGION_KIND::POLYGON_CUTOUT || aElem.is_keepout )
3144 {
3145 SHAPE_LINE_CHAIN linechain;
3147
3148 if( linechain.PointCount() < 3 )
3149 {
3150 // We have found multiple Altium files with polygon records containing nothing but
3151 // two coincident vertices. These polygons do not appear when opening the file in
3152 // Altium. https://gitlab.com/kicad/code/kicad/-/issues/8183
3153 // Also, polygons with less than 3 points are not supported in KiCad.
3154 return;
3155 }
3156
3157 // A polygon cutout only removes copper, a keepout carries its own mask
3158 uint8_t restrictions = aElem.is_keepout ? HelperGetKeepoutRestrictions( aElem.keepoutrestrictions, aElem.layer )
3160
3161 if( restrictions == 0 )
3162 return;
3163
3164 std::unique_ptr<ZONE> zone = std::make_unique<ZONE>( aFootprint );
3165
3166 zone->SetIsRuleArea( true );
3167
3168 HelperSetZoneKeepoutRestrictions( *zone, restrictions );
3169
3170 zone->SetPosition( aElem.outline.at( 0 ).position );
3171 zone->Outline()->AddOutline( linechain );
3172
3173 HelperFootprintZoneToLibFrame( *zone, *aFootprint );
3174
3175 HelperSetZoneLayers( *zone, aElem.layer );
3176
3177 zone->SetBorderDisplayStyle( ZONE_BORDER_DISPLAY_STYLE::DIAGONAL_EDGE,
3179
3180 aFootprint->Add( zone.release(), ADD_MODE::APPEND );
3181 }
3182 else if( aElem.kind == ALTIUM_REGION_KIND::COPPER )
3183 {
3184 if( aElem.polygon == ALTIUM_POLYGON_NONE )
3185 {
3186 for( PCB_LAYER_ID klayer : GetKicadLayersToIterate( aElem.layer ) )
3187 {
3188 ConvertShapeBasedRegions6ToFootprintItemOnLayer( aFootprint, aElem, klayer,
3189 aPrimitiveIndex );
3190 }
3191 }
3192 }
3195 {
3197 ? Edge_Cuts
3198 : GetKicadLayer( aElem.layer );
3199
3200 if( klayer == UNDEFINED_LAYER )
3201 {
3202 if( !m_footprintName.IsEmpty() )
3203 {
3204 if( m_reporter )
3205 {
3206 wxString msg;
3207 msg.Printf( _( "Loading library '%s':\n"
3208 "Footprint %s contains a dashed outline on Altium layer (%d) with "
3209 "no KiCad equivalent. It has been moved to KiCad layer Eco1_User." ),
3210 m_library,
3212 aElem.layer );
3213 m_reporter->Report( msg, RPT_SEVERITY_ERROR );
3214 }
3215 }
3216 else
3217 {
3218 if( m_reporter )
3219 {
3220 wxString msg;
3221 msg.Printf( _( "Footprint %s contains a dashed outline on Altium layer (%d) with "
3222 "no KiCad equivalent. It has been moved to KiCad layer Eco1_User." ),
3223 aFootprint->GetReference(),
3224 aElem.layer );
3225 m_reporter->Report( msg, RPT_SEVERITY_ERROR );
3226 }
3227 }
3228
3229 klayer = Eco1_User;
3230 }
3231
3232 SHAPE_LINE_CHAIN linechain;
3234
3235 if( linechain.PointCount() < 3 )
3236 {
3237 // We have found multiple Altium files with polygon records containing nothing but
3238 // two coincident vertices. These polygons do not appear when opening the file in
3239 // Altium. https://gitlab.com/kicad/code/kicad/-/issues/8183
3240 // Also, polygons with less than 3 points are not supported in KiCad.
3241 return;
3242 }
3243
3244 std::unique_ptr<PCB_SHAPE> shape = std::make_unique<PCB_SHAPE>( aFootprint, SHAPE_T::POLY );
3245
3246 shape->SetPolyShape( linechain );
3247 shape->SetFilled( false );
3248 shape->SetLayer( klayer );
3249
3251 shape->SetStroke( STROKE_PARAMS( pcbIUScale.mmToIU( 0.1 ), LINE_STYLE::DASH ) );
3252 else
3253 shape->SetStroke( STROKE_PARAMS( pcbIUScale.mmToIU( 0.1 ), LINE_STYLE::SOLID ) );
3254
3255 aFootprint->Add( shape.release(), ADD_MODE::APPEND );
3256 }
3257 else
3258 {
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 contains polygon shape of kind %d (not yet supported)." ),
3266 m_library,
3268 aElem.kind );
3269 m_reporter->Report( msg, RPT_SEVERITY_ERROR );
3270 }
3271 }
3272 else
3273 {
3274 if( m_reporter )
3275 {
3276 wxString msg;
3277 msg.Printf( _( "Footprint %s contains polygon shape of kind %d (not yet supported)." ),
3278 aFootprint->GetReference(),
3279 aElem.kind );
3280 m_reporter->Report( msg, RPT_SEVERITY_ERROR );
3281 }
3282 }
3283 }
3284}
3285
3286
3288 const int aPrimitiveIndex )
3289{
3290 SHAPE_LINE_CHAIN linechain;
3292
3293 if( linechain.PointCount() < 3 )
3294 {
3295 // We have found multiple Altium files with polygon records containing nothing
3296 // but two coincident vertices. These polygons do not appear when opening the
3297 // file in Altium. https://gitlab.com/kicad/code/kicad/-/issues/8183
3298 // Also, polygons with less than 3 points are not supported in KiCad.
3299 return;
3300 }
3301
3302 SHAPE_POLY_SET polySet;
3303 polySet.AddOutline( linechain );
3304
3305 for( const std::vector<ALTIUM_VERTICE>& hole : aElem.holes )
3306 {
3307 SHAPE_LINE_CHAIN hole_linechain;
3308 HelperShapeLineChainFromAltiumVertices( hole_linechain, hole );
3309
3310 if( hole_linechain.PointCount() < 3 )
3311 continue;
3312
3313 polySet.AddHole( hole_linechain );
3314 }
3315
3316 std::unique_ptr<PCB_SHAPE> shape = std::make_unique<PCB_SHAPE>( m_board, SHAPE_T::POLY );
3317
3318 shape->SetPolyShape( polySet );
3319 shape->SetFilled( true );
3320 shape->SetLayer( aLayer );
3321 shape->SetStroke( STROKE_PARAMS( 0 ) );
3322
3323 if( IsCopperLayer( aLayer ) && aElem.net != ALTIUM_NET_UNCONNECTED )
3324 {
3325 shape->SetNetCode( GetNetCode( aElem.net ) );
3326 }
3327
3328 m_board->Add( shape.release(), ADD_MODE::APPEND );
3329
3330 // Guard skips dup mask shapes when a MULTI_LAYER region iterates every copper layer
3331 if( aLayer == F_Cu || aLayer == B_Cu )
3332 {
3333 for( const auto& layerExpansionMask :
3335 {
3336 const PCB_LAYER_ID maskLayer = layerExpansionMask.first;
3337
3338 if( ( ( maskLayer == F_Mask || maskLayer == F_Paste ) && aLayer != F_Cu )
3339 || ( ( maskLayer == B_Mask || maskLayer == B_Paste ) && aLayer != B_Cu ) )
3340 {
3341 continue;
3342 }
3343
3344 int expansion = layerExpansionMask.second;
3345
3346 SHAPE_POLY_SET expandedPolySet = polySet;
3347 expandedPolySet.Inflate( expansion, CORNER_STRATEGY::ROUND_ALL_CORNERS, ARC_HIGH_DEF );
3348
3349 std::unique_ptr<PCB_SHAPE> maskShape = std::make_unique<PCB_SHAPE>( m_board, SHAPE_T::POLY );
3350
3351 maskShape->SetPolyShape( expandedPolySet );
3352 maskShape->SetFilled( true );
3353 maskShape->SetLayer( maskLayer );
3354 maskShape->SetStroke( STROKE_PARAMS( 0 ) );
3355
3356 m_board->Add( maskShape.release(), ADD_MODE::APPEND );
3357 }
3358 }
3359}
3360
3361
3363 const AREGION6& aElem,
3364 PCB_LAYER_ID aLayer,
3365 const int aPrimitiveIndex )
3366{
3367 SHAPE_LINE_CHAIN linechain;
3369
3370 if( linechain.PointCount() < 3 )
3371 {
3372 // We have found multiple Altium files with polygon records containing nothing
3373 // but two coincident vertices. These polygons do not appear when opening the
3374 // file in Altium. https://gitlab.com/kicad/code/kicad/-/issues/8183
3375 // Also, polygons with less than 3 points are not supported in KiCad.
3376 return;
3377 }
3378
3379 SHAPE_POLY_SET polySet;
3380 polySet.AddOutline( linechain );
3381
3382 for( const std::vector<ALTIUM_VERTICE>& hole : aElem.holes )
3383 {
3384 SHAPE_LINE_CHAIN hole_linechain;
3385 HelperShapeLineChainFromAltiumVertices( hole_linechain, hole );
3386
3387 if( hole_linechain.PointCount() < 3 )
3388 continue;
3389
3390 polySet.AddHole( hole_linechain );
3391 }
3392
3393 if( aLayer == F_Cu || aLayer == B_Cu )
3394 {
3395 std::unique_ptr<PAD> pad = std::make_unique<PAD>( aFootprint );
3396
3397 LSET padLayers;
3398 padLayers.set( aLayer );
3399
3400 pad->SetAttribute( PAD_ATTRIB::SMD );
3401 pad->SetPadstackMode( PADSTACK::MODE::NORMAL );
3403 pad->SetThermalSpokeAngle( ANGLE_90 );
3404
3405 int anchorSize = 1;
3406 VECTOR2I anchorPos = linechain.CPoint( 0 );
3407
3408 pad->SetAnchorPadShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::CIRCLE );
3409 pad->SetSize( PADSTACK::ALL_LAYERS, { anchorSize, anchorSize } );
3410 pad->SetPosition( anchorPos );
3411 pad->SetNetCode( GetNetCode( aElem.net ) );
3412
3413 // The primitives below are board-absolute, but a pad defaults to its footprint's angle
3414 pad->SetOrientation( ANGLE_0 );
3415
3416 SHAPE_POLY_SET shapePolys = polySet;
3417 shapePolys.Move( -anchorPos );
3418 pad->AddPrimitivePoly( PADSTACK::ALL_LAYERS, shapePolys, 0, true );
3419
3421 auto it = map.find( aPrimitiveIndex );
3422
3423 if( it != map.end() )
3424 {
3425 const AEXTENDED_PRIMITIVE_INFORMATION& info = it->second;
3426
3427 if( info.pastemaskexpansionmode == ALTIUM_MODE::MANUAL )
3428 {
3429 pad->SetLocalSolderPasteMargin( info.pastemaskexpansionmanual );
3430 }
3431
3432 if( info.soldermaskexpansionmode == ALTIUM_MODE::MANUAL )
3433 {
3434 pad->SetLocalSolderMaskMargin( info.soldermaskexpansionmanual );
3435 }
3436
3437 if( info.pastemaskexpansionmode != ALTIUM_MODE::NONE )
3438 padLayers.set( aLayer == F_Cu ? F_Paste : B_Paste );
3439
3440 if( info.soldermaskexpansionmode != ALTIUM_MODE::NONE )
3441 padLayers.set( aLayer == F_Cu ? F_Mask : B_Mask );
3442 }
3443
3444 pad->SetLayerSet( padLayers );
3445
3446 aFootprint->Add( pad.release(), ADD_MODE::APPEND );
3447 }
3448 else
3449 {
3450 std::unique_ptr<PCB_SHAPE> shape = std::make_unique<PCB_SHAPE>( aFootprint, SHAPE_T::POLY );
3451
3452 shape->SetPolyShape( polySet );
3453 shape->SetFilled( true );
3454 shape->SetLayer( aLayer );
3455 shape->SetStroke( STROKE_PARAMS( 0 ) );
3456
3457 aFootprint->Add( shape.release(), ADD_MODE::APPEND );
3458 }
3459}
3460
3461
3463 const CFB::COMPOUND_FILE_ENTRY* aEntry )
3464{
3465 if( m_progressReporter )
3466 m_progressReporter->Report( _( "Loading zone fills..." ) );
3467
3468 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
3469
3470 while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
3471 {
3472 checkpoint();
3473 AREGION6 elem( reader, false );
3474
3475 if( elem.polygon != ALTIUM_POLYGON_NONE )
3476 {
3477 if( m_polygons.size() <= elem.polygon )
3478 {
3479 THROW_IO_ERRORF( wxT( "Region stream tries to access polygon id %d of %d existing polygons." ),
3480 elem.polygon, m_polygons.size() );
3481 }
3482
3483 ZONE* zone = m_polygons.at( elem.polygon );
3484
3485 if( zone == nullptr )
3486 continue; // we know the zone id, but because we do not know the layer we did not add it!
3487
3488 PCB_LAYER_ID klayer = GetKicadLayer( elem.layer );
3489
3490 if( klayer == UNDEFINED_LAYER )
3491 continue; // Just skip it for now. Users can fill it themselves.
3492
3493 SHAPE_LINE_CHAIN linechain;
3494
3495 for( const ALTIUM_VERTICE& vertice : elem.outline )
3496 linechain.Append( vertice.position );
3497
3498 linechain.Append( elem.outline.at( 0 ).position );
3499 linechain.SetClosed( true );
3500
3501 SHAPE_POLY_SET fill;
3502 fill.AddOutline( linechain );
3503
3504 for( const std::vector<ALTIUM_VERTICE>& hole : elem.holes )
3505 {
3506 SHAPE_LINE_CHAIN hole_linechain;
3507
3508 for( const ALTIUM_VERTICE& vertice : hole )
3509 hole_linechain.Append( vertice.position );
3510
3511 hole_linechain.Append( hole.at( 0 ).position );
3512 hole_linechain.SetClosed( true );
3513 fill.AddHole( hole_linechain );
3514 }
3515
3516 if( zone->HasFilledPolysForLayer( klayer ) )
3517 fill.BooleanAdd( *zone->GetFill( klayer ) );
3518
3519 fill.Fracture();
3520
3521 zone->SetFilledPolysList( klayer, fill );
3522 zone->SetIsFilled( true );
3523 zone->SetNeedRefill( false );
3524 }
3525 }
3526
3527 if( reader.GetRemainingBytes() != 0 )
3528 THROW_IO_ERROR( wxT( "Regions6 stream is not fully parsed" ) );
3529}
3530
3531
3533 const CFB::COMPOUND_FILE_ENTRY* aEntry )
3534{
3535 if( m_progressReporter )
3536 m_progressReporter->Report( _( "Loading arcs..." ) );
3537
3538 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
3539
3540 for( int primitiveIndex = 0; reader.GetRemainingBytes() >= 4; primitiveIndex++ )
3541 {
3542 checkpoint();
3543 AARC6 elem( reader );
3544
3545 if( elem.component == ALTIUM_COMPONENT_NONE )
3546 {
3547 ConvertArcs6ToBoardItem( elem, primitiveIndex );
3548 }
3549 else
3550 {
3551 FOOTPRINT* footprint = HelperGetFootprint( elem.component );
3552 ConvertArcs6ToFootprintItem( footprint, elem, primitiveIndex, true );
3553 }
3554 }
3555
3556 if( reader.GetRemainingBytes() != 0 )
3557 THROW_IO_ERROR( wxT( "Arcs6 stream is not fully parsed" ) );
3558}
3559
3560
3562{
3563 if( aElem.startangle == 0. && aElem.endangle == 360. )
3564 {
3565 aShape->SetShape( SHAPE_T::CIRCLE );
3566
3567 // TODO: other variants to define circle?
3568 aShape->SetStart( aElem.center );
3569 aShape->SetEnd( aElem.center - VECTOR2I( 0, aElem.radius ) );
3570 }
3571 else
3572 {
3573 aShape->SetShape( SHAPE_T::ARC );
3574
3575 EDA_ANGLE includedAngle( aElem.endangle - aElem.startangle, DEGREES_T );
3576 EDA_ANGLE startAngle( aElem.endangle, DEGREES_T );
3577
3578 VECTOR2I startOffset = VECTOR2I( KiROUND( startAngle.Cos() * aElem.radius ),
3579 -KiROUND( startAngle.Sin() * aElem.radius ) );
3580
3581 aShape->SetCenter( aElem.center );
3582 aShape->SetStart( aElem.center + startOffset );
3583 aShape->SetArcAngleAndEnd( includedAngle.Normalize(), true );
3584 }
3585}
3586
3587
3588void ALTIUM_PCB::ConvertArcs6ToBoardItem( const AARC6& aElem, const int aPrimitiveIndex )
3589{
3590 if( aElem.polygon != ALTIUM_POLYGON_NONE && aElem.polygon != ALTIUM_POLYGON_BOARD )
3591 {
3592 if( m_polygons.size() <= aElem.polygon )
3593 {
3594 THROW_IO_ERRORF( wxT( "Tracks stream tries to access polygon id %u of %zu existing polygons." ),
3595 aElem.polygon, m_polygons.size() );
3596 }
3597
3598 ZONE* zone = m_polygons.at( aElem.polygon );
3599
3600 if( zone == nullptr )
3601 {
3602 return; // we know the zone id, but because we do not know the layer we did not
3603 // add it!
3604 }
3605
3606 PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
3607
3608 if( klayer == UNDEFINED_LAYER )
3609 return; // Just skip it for now. Users can fill it themselves.
3610
3611 if( !zone->HasFilledPolysForLayer( klayer ) )
3612 return;
3613
3614 SHAPE_POLY_SET* fill = zone->GetFill( klayer );
3615
3616 // This is not the actual board item. We can use it to create the polygon for the region
3617 PCB_SHAPE shape( nullptr );
3618
3619 ConvertArcs6ToPcbShape( aElem, &shape );
3621
3622 shape.EDA_SHAPE::TransformShapeToPolygon( *fill, 0, ARC_HIGH_DEF, ERROR_INSIDE );
3623 // Will be simplified and fractured later
3624
3625 zone->SetIsFilled( true );
3626 zone->SetNeedRefill( false );
3627
3628 return;
3629 }
3630
3631 if( aElem.is_keepout || aElem.layer == ALTIUM_LAYER::KEEP_OUT_LAYER
3632 || IsAltiumLayerAPlane( aElem.layer ) )
3633 {
3634 // This is not the actual board item. We can use it to create the polygon for the region
3635 PCB_SHAPE shape( nullptr );
3636
3637 ConvertArcs6ToPcbShape( aElem, &shape );
3639
3641 }
3642 else
3643 {
3644 for( PCB_LAYER_ID klayer : GetKicadLayersToIterate( aElem.layer ) )
3645 ConvertArcs6ToBoardItemOnLayer( aElem, klayer );
3646 }
3647
3648 for( const auto& layerExpansionMask :
3650 {
3651 int width = aElem.width + ( layerExpansionMask.second * 2 );
3652
3653 if( width > 1 )
3654 {
3655 std::unique_ptr<PCB_SHAPE> arc = std::make_unique<PCB_SHAPE>( m_board );
3656
3657 ConvertArcs6ToPcbShape( aElem, arc.get() );
3658 arc->SetStroke( STROKE_PARAMS( width, LINE_STYLE::SOLID ) );
3659 arc->SetLayer( layerExpansionMask.first );
3660
3661 m_board->Add( arc.release(), ADD_MODE::APPEND );
3662 }
3663 }
3664}
3665
3666
3668 const int aPrimitiveIndex, const bool aIsBoardImport )
3669{
3670 if( aElem.polygon != ALTIUM_POLYGON_NONE )
3671 {
3672 wxFAIL_MSG( wxString::Format( "Altium: Unexpected footprint Arc with polygon id %d",
3673 aElem.polygon ) );
3674 return;
3675 }
3676
3677 if( aElem.is_keepout || aElem.layer == ALTIUM_LAYER::KEEP_OUT_LAYER
3678 || IsAltiumLayerAPlane( aElem.layer ) )
3679 {
3680 // This is not the actual board item. We can use it to create the polygon for the region
3681 PCB_SHAPE shape( nullptr );
3682
3683 ConvertArcs6ToPcbShape( aElem, &shape );
3685
3686 HelperPcpShapeAsFootprintKeepoutRegion( aFootprint, shape, aElem.layer,
3687 aElem.keepoutrestrictions );
3688 }
3689 else
3690 {
3691 for( PCB_LAYER_ID klayer : GetKicadLayersToIterate( aElem.layer ) )
3692 {
3693 if( aIsBoardImport && IsCopperLayer( klayer ) && aElem.net != ALTIUM_NET_UNCONNECTED )
3694 {
3695 // Special case: do to not lose net connections in footprints
3696 ConvertArcs6ToBoardItemOnLayer( aElem, klayer );
3697 }
3698 else
3699 {
3700 ConvertArcs6ToFootprintItemOnLayer( aFootprint, aElem, klayer );
3701 }
3702 }
3703 }
3704
3705 for( const auto& layerExpansionMask :
3707 {
3708 int width = aElem.width + ( layerExpansionMask.second * 2 );
3709
3710 if( width > 1 )
3711 {
3712 std::unique_ptr<PCB_SHAPE> arc = std::make_unique<PCB_SHAPE>( aFootprint );
3713
3714 ConvertArcs6ToPcbShape( aElem, arc.get() );
3715 arc->SetStroke( STROKE_PARAMS( width, LINE_STYLE::SOLID ) );
3716 arc->SetLayer( layerExpansionMask.first );
3717
3718 aFootprint->Add( arc.release(), ADD_MODE::APPEND );
3719 }
3720 }
3721}
3722
3723
3725{
3726 if( IsCopperLayer( aLayer ) && aElem.net != ALTIUM_NET_UNCONNECTED )
3727 {
3728 double sweepDegrees = aElem.endangle - aElem.startangle;
3729 EDA_ANGLE includedAngle( sweepDegrees, DEGREES_T );
3730 EDA_ANGLE startAngle( aElem.endangle, DEGREES_T );
3731
3732 VECTOR2I startOffset = VECTOR2I( KiROUND( startAngle.Cos() * aElem.radius ),
3733 -KiROUND( startAngle.Sin() * aElem.radius ) );
3734
3735 auto addArc = [&]( const VECTOR2I& aStart, const EDA_ANGLE& aAngle )
3736 {
3737 SHAPE_ARC shapeArc( aElem.center, aStart, aAngle, aElem.width );
3738 std::unique_ptr<PCB_ARC> arc = std::make_unique<PCB_ARC>( m_board, &shapeArc );
3739
3740 arc->SetWidth( aElem.width );
3741 arc->SetLayer( aLayer );
3742 arc->SetNetCode( GetNetCode( aElem.net ) );
3743
3744 PCB_ARC* added = arc.release();
3745 m_board->Add( added, ADD_MODE::APPEND );
3746
3747 if( aElem.unionindex != 0 )
3748 m_unionToBoardItems[static_cast<int>( aElem.unionindex )].push_back( added );
3749 };
3750
3751 // PCB_ARC cannot represent a closed sweep, so emit the ring as two halves
3752 if( std::abs( sweepDegrees ) >= 359.999 )
3753 {
3754 EDA_ANGLE halfSweep( sweepDegrees < 0. ? -180. : 180., DEGREES_T );
3755
3756 addArc( aElem.center + startOffset, halfSweep );
3757 addArc( aElem.center - startOffset, halfSweep );
3758 return;
3759 }
3760
3761 includedAngle.Normalize();
3762
3763 if( includedAngle.AsDegrees() >= 0.1 )
3764 addArc( aElem.center + startOffset, includedAngle );
3765 }
3766 else
3767 {
3768 std::unique_ptr<PCB_SHAPE> arc = std::make_unique<PCB_SHAPE>(m_board);
3769
3770 ConvertArcs6ToPcbShape( aElem, arc.get() );
3771 arc->SetStroke( STROKE_PARAMS( aElem.width, LINE_STYLE::SOLID ) );
3772 arc->SetLayer( aLayer );
3773
3774 m_board->Add( arc.release(), ADD_MODE::APPEND );
3775 }
3776}
3777
3778
3780 PCB_LAYER_ID aLayer )
3781{
3782 std::unique_ptr<PCB_SHAPE> arc = std::make_unique<PCB_SHAPE>( aFootprint );
3783
3784 ConvertArcs6ToPcbShape( aElem, arc.get() );
3785 arc->SetStroke( STROKE_PARAMS( aElem.width, LINE_STYLE::SOLID ) );
3786 arc->SetLayer( aLayer );
3787
3788 aFootprint->Add( arc.release(), ADD_MODE::APPEND );
3789}
3790
3791
3793 const CFB::COMPOUND_FILE_ENTRY* aEntry )
3794{
3795 if( m_progressReporter )
3796 m_progressReporter->Report( _( "Loading pads..." ) );
3797
3798 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
3799
3800 while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
3801 {
3802 checkpoint();
3803 APAD6 elem( reader );
3804
3805 if( elem.component == ALTIUM_COMPONENT_NONE )
3806 {
3808 }
3809 else
3810 {
3811 FOOTPRINT* footprint = HelperGetFootprint( elem.component );
3812 ConvertPads6ToFootprintItem( footprint, elem );
3813 }
3814 }
3815
3816 if( reader.GetRemainingBytes() != 0 )
3817 THROW_IO_ERROR( wxT( "Pads6 stream is not fully parsed" ) );
3818}
3819
3820
3822{
3823 // It is possible to place altium pads on non-copper layers -> we need to interpolate them using drawings!
3824 if( !IsAltiumLayerCopper( aElem.layer ) && !IsAltiumLayerAPlane( aElem.layer )
3825 && aElem.layer != ALTIUM_LAYER::MULTI_LAYER )
3826 {
3828 }
3829 else
3830 {
3831 // We cannot add a pad directly into the PCB
3832 std::unique_ptr<FOOTPRINT> footprint = std::make_unique<FOOTPRINT>( m_board );
3833 footprint->SetPosition( aElem.position );
3834
3835 // This wrapper exists only to carry a free-standing pad; it has no schematic symbol and
3836 // nothing to buy or place, so keep it out of the BOM and the placement files.
3837 footprint->SetAttributes( FP_BOARD_ONLY | FP_EXCLUDE_FROM_BOM | FP_EXCLUDE_FROM_POS_FILES );
3838
3839 ConvertPads6ToFootprintItemOnCopper( footprint.get(), aElem );
3840
3841 m_board->Add( footprint.release(), ADD_MODE::APPEND );
3842 }
3843}
3844
3845
3847{
3848 std::unique_ptr<PAD> pad = std::make_unique<PAD>( aFootprint );
3849
3850 pad->SetNumber( "" );
3851 pad->SetNetCode( GetNetCode( aElem.net ) );
3852
3853 pad->SetPosition( aElem.position );
3854 pad->SetDrillSize( VECTOR2I( aElem.holesize, aElem.holesize ) );
3855 pad->SetDrillShape( PAD_DRILL_SHAPE::CIRCLE );
3856 pad->SetAttribute( PAD_ATTRIB::PTH );
3857
3858 // Pads are always through holes in KiCad
3859 pad->SetLayerSet( LSET().AllCuMask() );
3860
3861 if( aElem.viamode == ALTIUM_PAD_MODE::SIMPLE )
3862 {
3863 pad->Padstack().SetMode( PADSTACK::MODE::NORMAL );
3864 pad->SetSize( PADSTACK::ALL_LAYERS, VECTOR2I( aElem.diameter, aElem.diameter ) );
3866 }
3868 {
3869 pad->Padstack().SetMode( PADSTACK::MODE::FRONT_INNER_BACK );
3870
3874
3875 pad->SetSize( F_Cu, VECTOR2I( top, top ) );
3876 pad->SetShape( F_Cu, PAD_SHAPE::CIRCLE );
3877
3878 pad->SetSize( PADSTACK::INNER_LAYERS, VECTOR2I( mid, mid ) );
3880
3881 pad->SetSize( B_Cu, VECTOR2I( bot, bot ) );
3882 pad->SetShape( B_Cu, PAD_SHAPE::CIRCLE );
3883 }
3884 else
3885 {
3886 pad->Padstack().SetMode( PADSTACK::MODE::CUSTOM );
3887
3888 LSET cuLayers = LSET::AllCuMask();
3889
3890 if( m_board )
3891 cuLayers &= m_board->GetEnabledLayers();
3892
3893 for( PCB_LAYER_ID layer : cuLayers )
3894 {
3895 int altiumIdx = HelperGetPadstackLayerIndex( layer );
3896
3897 // Internal planes carry no padstack entry; the via keeps its nominal land there
3898 int diameter = altiumIdx < 0 ? aElem.diameter : aElem.diameter_by_layer[altiumIdx];
3899
3900 pad->SetSize( layer, VECTOR2I( diameter, diameter ) );
3901 pad->SetShape( layer, PAD_SHAPE::CIRCLE );
3902 }
3903 }
3904
3905 if( aElem.is_tent_top )
3906 {
3907 pad->Padstack().FrontOuterLayers().has_solder_mask = true;
3908 }
3909 else
3910 {
3911 pad->Padstack().FrontOuterLayers().has_solder_mask = false;
3912 pad->SetLayerSet( pad->GetLayerSet().set( F_Mask ) );
3913 }
3914
3915 if( aElem.is_tent_bottom )
3916 {
3917 pad->Padstack().BackOuterLayers().has_solder_mask = true;
3918 }
3919 else
3920 {
3921 pad->Padstack().BackOuterLayers().has_solder_mask = false;
3922 pad->SetLayerSet( pad->GetLayerSet().set( B_Mask ) );
3923 }
3924
3925 if( aElem.is_locked )
3926 pad->SetLocked( true );
3927
3928 if( aElem.soldermask_expansion_manual )
3929 {
3930 pad->Padstack().FrontOuterLayers().solder_mask_margin = aElem.soldermask_expansion_front;
3931 pad->Padstack().BackOuterLayers().solder_mask_margin = aElem.soldermask_expansion_back;
3932 }
3933
3934
3935 aFootprint->Add( pad.release(), ADD_MODE::APPEND );
3936}
3937
3938
3940{
3941 // It is possible to place altium pads on non-copper layers -> we need to interpolate them using drawings!
3942 if( !IsAltiumLayerCopper( aElem.layer ) && !IsAltiumLayerAPlane( aElem.layer )
3943 && aElem.layer != ALTIUM_LAYER::MULTI_LAYER )
3944 {
3945 ConvertPads6ToFootprintItemOnNonCopper( aFootprint, aElem );
3946 }
3947 else
3948 {
3949 ConvertPads6ToFootprintItemOnCopper( aFootprint, aElem );
3950 }
3951}
3952
3953
3955{
3956 std::unique_ptr<PAD> pad = std::make_unique<PAD>( aFootprint );
3957
3958 pad->SetNumber( aElem.name );
3959 pad->SetNetCode( GetNetCode( aElem.net ) );
3960
3961 pad->SetPosition( aElem.position );
3962 pad->SetOrientationDegrees( aElem.direction );
3963 pad->SetThermalSpokeAngle( ANGLE_90 );
3964
3965 if( aElem.holesize == 0 )
3966 {
3967 pad->SetAttribute( PAD_ATTRIB::SMD );
3968 }
3969 else
3970 {
3971 if( aElem.layer != ALTIUM_LAYER::MULTI_LAYER )
3972 {
3973 // TODO: I assume other values are possible as well?
3974 if( !m_footprintName.IsEmpty() )
3975 {
3976 if( m_reporter )
3977 {
3978 wxString msg;
3979 msg.Printf( _( "Error loading library '%s':\n"
3980 "Footprint %s pad %s is not marked as multilayer, but is a TH pad." ),
3981 m_library,
3983 aElem.name );
3984 m_reporter->Report( msg, RPT_SEVERITY_ERROR );
3985 }
3986 }
3987 else
3988 {
3989 if( m_reporter )
3990 {
3991 wxString msg;
3992 msg.Printf( _( "Footprint %s pad %s is not marked as multilayer, but is a TH pad." ),
3993 aFootprint->GetReference(),
3994 aElem.name );
3995 m_reporter->Report( msg, RPT_SEVERITY_ERROR );
3996 }
3997 }
3998 }
3999
4000 pad->SetAttribute( aElem.plated ? PAD_ATTRIB::PTH : PAD_ATTRIB::NPTH );
4001
4003 {
4004 pad->SetDrillShape( PAD_DRILL_SHAPE::CIRCLE );
4005 pad->SetDrillSize( VECTOR2I( aElem.holesize, aElem.holesize ) );
4006 }
4007 else
4008 {
4009 switch( aElem.sizeAndShape->holeshape )
4010 {
4012 wxFAIL_MSG( wxT( "Round holes are handled before the switch" ) );
4013 break;
4014
4016 if( !m_footprintName.IsEmpty() )
4017 {
4018 if( m_reporter )
4019 {
4020 wxString msg;
4021 msg.Printf( _( "Loading library '%s':\n"
4022 "Footprint %s pad %s has a square hole (not yet supported)." ),
4023 m_library,
4025 aElem.name );
4026 m_reporter->Report( msg, RPT_SEVERITY_DEBUG );
4027 }
4028 }
4029 else
4030 {
4031 if( m_reporter )
4032 {
4033 wxString msg;
4034 msg.Printf( _( "Footprint %s pad %s has a square hole (not yet supported)." ),
4035 aFootprint->GetReference(),
4036 aElem.name );
4037 m_reporter->Report( msg, RPT_SEVERITY_DEBUG );
4038 }
4039 }
4040
4041 pad->SetDrillShape( PAD_DRILL_SHAPE::CIRCLE );
4042 pad->SetDrillSize( VECTOR2I( aElem.holesize, aElem.holesize ) ); // Workaround
4043 // TODO: elem.sizeAndShape->slotsize was 0 in testfile. Either use holesize in
4044 // this case or rect holes have a different id
4045 break;
4046
4048 {
4049 pad->SetDrillShape( PAD_DRILL_SHAPE::OBLONG );
4050 bool slotRotationSupported;
4051 pad->SetDrillSize( altiumSlotDrillSize( aElem.holesize, aElem.sizeAndShape->slotsize,
4052 aElem.sizeAndShape->slotrotation, slotRotationSupported ) );
4053
4054 if( !slotRotationSupported )
4055 {
4056 EDA_ANGLE slotRotation( aElem.sizeAndShape->slotrotation, DEGREES_T );
4057 slotRotation.Normalize();
4058
4059 if( !m_footprintName.IsEmpty() )
4060 {
4061 if( m_reporter )
4062 {
4063 wxString msg;
4064 msg.Printf( _( "Loading library '%s':\n"
4065 "Footprint %s pad %s has a hole-rotation of %d degrees. "
4066 "KiCad only supports 90 degree rotations." ),
4067 m_library,
4069 aElem.name,
4070 KiROUND( slotRotation.AsDegrees() ) );
4071 m_reporter->Report( msg, RPT_SEVERITY_DEBUG );
4072 }
4073 }
4074 else
4075 {
4076 if( m_reporter )
4077 {
4078 wxString msg;
4079 msg.Printf( _( "Footprint %s pad %s has a hole-rotation of %d degrees. "
4080 "KiCad only supports 90 degree rotations." ),
4081 aFootprint->GetReference(),
4082 aElem.name,
4083 KiROUND( slotRotation.AsDegrees() ) );
4084 m_reporter->Report( msg, RPT_SEVERITY_DEBUG );
4085 }
4086 }
4087 }
4088
4089 break;
4090 }
4091
4092 default:
4094 if( !m_footprintName.IsEmpty() )
4095 {
4096 if( m_reporter )
4097 {
4098 wxString msg;
4099 msg.Printf( _( "Error loading library '%s':\n"
4100 "Footprint %s pad %s uses a hole of unknown kind %d." ),
4101 m_library,
4103 aElem.name,
4104 aElem.sizeAndShape->holeshape );
4105 m_reporter->Report( msg, RPT_SEVERITY_DEBUG );
4106 }
4107 }
4108 else
4109 {
4110 if( m_reporter )
4111 {
4112 wxString msg;
4113 msg.Printf( _( "Footprint %s pad %s uses a hole of unknown kind %d." ),
4114 aFootprint->GetReference(),
4115 aElem.name,
4116 aElem.sizeAndShape->holeshape );
4117 m_reporter->Report( msg, RPT_SEVERITY_DEBUG );
4118 }
4119 }
4120
4121 pad->SetDrillShape( PAD_DRILL_SHAPE::CIRCLE );
4122 pad->SetDrillSize( VECTOR2I( aElem.holesize, aElem.holesize ) ); // Workaround
4123 break;
4124 }
4125 }
4126 }
4127
4128 PADSTACK& ps = pad->Padstack();
4129
4130 auto setCopperGeometry =
4131 [&]( PCB_LAYER_ID aLayer, int aAltiumIdx, ALTIUM_PAD_SHAPE aShape,
4132 const VECTOR2I& aSize )
4133 {
4134 bool hasAltiumEntry = aElem.sizeAndShape && aAltiumIdx >= 0
4135 && aAltiumIdx < ALTIUM_PADSTACK_IDX_COUNT;
4136
4137 ps.SetSize( aSize, aLayer );
4138
4139 if( aElem.holesize != 0 && hasAltiumEntry )
4140 ps.SetOffset( aElem.sizeAndShape->holeoffset[aAltiumIdx], aLayer );
4141
4142 switch( aShape )
4143 {
4145 ps.SetShape( PAD_SHAPE::RECTANGLE, aLayer );
4146 break;
4147
4149 if( hasAltiumEntry
4150 && aElem.sizeAndShape->alt_shape[aAltiumIdx] == ALTIUM_PAD_SHAPE_ALT::ROUNDRECT )
4151 {
4152 ps.SetShape( PAD_SHAPE::ROUNDRECT, aLayer ); // 100 = round, 0 = rectangular
4153 double ratio = aElem.sizeAndShape->cornerradius[aAltiumIdx] / 200.;
4154 ps.SetRoundRectRadiusRatio( ratio, aLayer );
4155 }
4156 else if( aSize.x == aSize.y )
4157 {
4158 ps.SetShape( PAD_SHAPE::CIRCLE, aLayer );
4159 }
4160 else
4161 {
4162 ps.SetShape( PAD_SHAPE::OVAL, aLayer );
4163 }
4164
4165 break;
4166
4168 ps.SetShape( PAD_SHAPE::CHAMFERED_RECT, aLayer );
4170 ps.SetChamferRatio( 0.25, aLayer );
4171 break;
4172
4174 default:
4175 if( !m_footprintName.IsEmpty() )
4176 {
4177 if( m_reporter )
4178 {
4179 wxString msg;
4180 msg.Printf( _( "Error loading library '%s':\n"
4181 "Footprint %s pad %s uses an unknown pad shape." ),
4182 m_library,
4184 aElem.name );
4185 m_reporter->Report( msg, RPT_SEVERITY_DEBUG );
4186 }
4187 }
4188 else
4189 {
4190 if( m_reporter )
4191 {
4192 wxString msg;
4193 msg.Printf( _( "Footprint %s pad %s uses an unknown pad shape." ),
4194 aFootprint->GetReference(),
4195 aElem.name );
4196 m_reporter->Report( msg, RPT_SEVERITY_DEBUG );
4197 }
4198 }
4199 break;
4200 }
4201 };
4202
4203 switch( aElem.padmode )
4204 {
4207 setCopperGeometry( PADSTACK::ALL_LAYERS, ALTIUM_TOP_PADSTACK_IDX, aElem.topshape,
4208 aElem.topsize );
4209 break;
4210
4213 setCopperGeometry( F_Cu, ALTIUM_TOP_PADSTACK_IDX, aElem.topshape, aElem.topsize );
4214 setCopperGeometry( PADSTACK::INNER_LAYERS, ALTIUM_MID1_PADSTACK_IDX, aElem.midshape,
4215 aElem.midsize );
4216 setCopperGeometry( B_Cu, ALTIUM_BOTTOM_PADSTACK_IDX, aElem.botshape, aElem.botsize );
4217 break;
4218
4220 {
4222
4223 setCopperGeometry( F_Cu, HelperGetPadstackLayerIndex( F_Cu ), aElem.topshape,
4224 aElem.topsize );
4225 setCopperGeometry( B_Cu, HelperGetPadstackLayerIndex( B_Cu ), aElem.botshape,
4226 aElem.botsize );
4227
4228 LSET intLayers = aFootprint->BoardLayerSet() & LSET::InternalCuMask();
4229
4230 for( PCB_LAYER_ID layer : intLayers )
4231 {
4232 int idx = HelperGetPadstackLayerIndex( layer );
4233 int inner = idx - ALTIUM_MID2_PADSTACK_IDX;
4234
4235 // Mid layer 1 is carried in the record itself, and internal planes have no entry at
4236 // all, so both fall back to it
4237 if( !aElem.sizeAndShape || inner < 0
4238 || inner >= static_cast<int>( std::size( aElem.sizeAndShape->inner_size ) ) )
4239 {
4240 setCopperGeometry( layer, idx, aElem.midshape, aElem.midsize );
4241 }
4242 else
4243 {
4244 const APAD6_SIZE_AND_SHAPE& shape = *aElem.sizeAndShape;
4245
4246 setCopperGeometry( layer, idx, shape.inner_shape[inner],
4247 VECTOR2I( shape.inner_size[inner].x,
4248 shape.inner_size[inner].y ) );
4249 }
4250 }
4251
4252 break;
4253 }
4254 }
4255
4256 switch( aElem.layer )
4257 {
4259 pad->SetLayer( F_Cu );
4260 pad->SetLayerSet( PAD::SMDMask() );
4261 break;
4262
4264 pad->SetLayer( B_Cu );
4265 pad->SetLayerSet( PAD::SMDMask().FlipStandardLayers() );
4266 break;
4267
4269 pad->SetLayerSet( aElem.plated ? PAD::PTHMask() : PAD::UnplatedHoleMask() );
4270 break;
4271
4272 default:
4273 PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
4274 pad->SetLayer( klayer );
4275 pad->SetLayerSet( LSET( { klayer } ) );
4276 break;
4277 }
4278
4280 pad->SetLocalSolderPasteMargin( aElem.pastemaskexpansionmanual );
4281
4283 pad->SetLocalSolderMaskMargin( aElem.soldermaskexpansionmanual );
4284
4285 if( aElem.is_tent_top )
4286 pad->SetLayerSet( pad->GetLayerSet().reset( F_Mask ) );
4287
4288 if( aElem.is_tent_bottom )
4289 pad->SetLayerSet( pad->GetLayerSet().reset( B_Mask ) );
4290
4291 pad->SetPadToDieLength( aElem.pad_to_die_length );
4292 pad->SetPadToDieDelay( aElem.pad_to_die_delay );
4293
4294 aFootprint->Add( pad.release(), ADD_MODE::APPEND );
4295}
4296
4297
4299{
4300 PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
4301
4302 if( klayer == UNDEFINED_LAYER )
4303 {
4304 if( m_reporter )
4305 {
4306 wxString msg;
4307 msg.Printf( _( "Non-copper pad %s found on an Altium layer (%d) with no KiCad "
4308 "equivalent. It has been moved to KiCad layer Eco1_User." ),
4309 aElem.name, aElem.layer );
4310 m_reporter->Report( msg, RPT_SEVERITY_INFO );
4311 }
4312
4313 klayer = Eco1_User;
4314 }
4315
4316 std::unique_ptr<PCB_SHAPE> pad = std::make_unique<PCB_SHAPE>( m_board );
4317
4318 HelperParsePad6NonCopper( aElem, klayer, pad.get() );
4319
4320 m_board->Add( pad.release(), ADD_MODE::APPEND );
4321}
4322
4323
4325{
4326 PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
4327
4328 if( klayer == UNDEFINED_LAYER )
4329 {
4330 if( !m_footprintName.IsEmpty() )
4331 {
4332 if( m_reporter )
4333 {
4334 wxString msg;
4335 msg.Printf( _( "Loading library '%s':\n"
4336 "Footprint %s non-copper pad %s found on an Altium layer (%d) with no "
4337 "KiCad equivalent. It has been moved to KiCad layer Eco1_User." ),
4338 m_library,
4340 aElem.name,
4341 aElem.layer );
4342 m_reporter->Report( msg, RPT_SEVERITY_INFO );
4343 }
4344 }
4345 else
4346 {
4347 if( m_reporter )
4348 {
4349 wxString msg;
4350 msg.Printf( _( "Footprint %s non-copper pad %s found on an Altium layer (%d) with no "
4351 "KiCad equivalent. It has been moved to KiCad layer Eco1_User." ),
4352 aFootprint->GetReference(),
4353 aElem.name,
4354 aElem.layer );
4355 m_reporter->Report( msg, RPT_SEVERITY_INFO );
4356 }
4357 }
4358
4359 klayer = Eco1_User;
4360 }
4361
4362 std::unique_ptr<PCB_SHAPE> pad = std::make_unique<PCB_SHAPE>( aFootprint );
4363
4364 HelperParsePad6NonCopper( aElem, klayer, pad.get() );
4365
4366 aFootprint->Add( pad.release(), ADD_MODE::APPEND );
4367}
4368
4369
4371 PCB_SHAPE* aShape )
4372{
4373 if( aElem.net != ALTIUM_NET_UNCONNECTED )
4374 {
4375 if( m_reporter )
4376 {
4377 wxString msg;
4378 msg.Printf( _( "Non-copper pad %s is connected to a net, which is not supported." ),
4379 aElem.name );
4380 m_reporter->Report( msg, RPT_SEVERITY_DEBUG );
4381 }
4382 }
4383
4384 if( aElem.holesize != 0 )
4385 {
4386 if( m_reporter )
4387 {
4388 wxString msg;
4389 msg.Printf( _( "Non-copper pad %s has a hole, which is not supported." ), aElem.name );
4390 m_reporter->Report( msg, RPT_SEVERITY_DEBUG );
4391 }
4392 }
4393
4394 if( aElem.padmode != ALTIUM_PAD_MODE::SIMPLE )
4395 {
4396 if( m_reporter )
4397 {
4398 wxString msg;
4399 msg.Printf( _( "Non-copper pad %s has a complex pad stack (not yet supported)." ),
4400 aElem.name );
4401 m_reporter->Report( msg, RPT_SEVERITY_DEBUG );
4402 }
4403 }
4404
4405 switch( aElem.topshape )
4406 {
4408 {
4409 // filled rect
4410 aShape->SetShape( SHAPE_T::POLY );
4411 aShape->SetFilled( true );
4412 aShape->SetLayer( aLayer );
4413 aShape->SetStroke( STROKE_PARAMS( 0 ) );
4414
4415 aShape->SetPolyPoints( { aElem.position + VECTOR2I( aElem.topsize.x / 2, aElem.topsize.y / 2 ),
4416 aElem.position + VECTOR2I( aElem.topsize.x / 2, -aElem.topsize.y / 2 ),
4417 aElem.position + VECTOR2I( -aElem.topsize.x / 2, -aElem.topsize.y / 2 ),
4418 aElem.position + VECTOR2I( -aElem.topsize.x / 2, aElem.topsize.y / 2 ) } );
4419
4420 if( aElem.direction != 0 )
4421 aShape->Rotate( aElem.position, EDA_ANGLE( aElem.direction, DEGREES_T ) );
4422 }
4423 break;
4424
4426 if( aElem.sizeAndShape
4428 {
4429 // filled roundrect
4430 int cornerradius = aElem.sizeAndShape->cornerradius[0];
4431 int offset = ( std::min( aElem.topsize.x, aElem.topsize.y ) * cornerradius ) / 200;
4432
4433 aShape->SetLayer( aLayer );
4434 aShape->SetStroke( STROKE_PARAMS( offset * 2, LINE_STYLE::SOLID ) );
4435
4436 if( cornerradius < 100 )
4437 {
4438 int offsetX = aElem.topsize.x / 2 - offset;
4439 int offsetY = aElem.topsize.y / 2 - offset;
4440
4441 VECTOR2I p11 = aElem.position + VECTOR2I( offsetX, offsetY );
4442 VECTOR2I p12 = aElem.position + VECTOR2I( offsetX, -offsetY );
4443 VECTOR2I p22 = aElem.position + VECTOR2I( -offsetX, -offsetY );
4444 VECTOR2I p21 = aElem.position + VECTOR2I( -offsetX, offsetY );
4445
4446 aShape->SetShape( SHAPE_T::POLY );
4447 aShape->SetFilled( true );
4448 aShape->SetPolyPoints( { p11, p12, p22, p21 } );
4449 }
4450 else if( aElem.topsize.x == aElem.topsize.y )
4451 {
4452 // circle
4453 aShape->SetShape( SHAPE_T::CIRCLE );
4454 aShape->SetFilled( true );
4455 aShape->SetStart( aElem.position );
4456 aShape->SetEnd( aElem.position - VECTOR2I( 0, aElem.topsize.x / 4 ) );
4457 aShape->SetStroke( STROKE_PARAMS( aElem.topsize.x / 2, LINE_STYLE::SOLID ) );
4458 }
4459 else if( aElem.topsize.x < aElem.topsize.y )
4460 {
4461 // short vertical line
4462 aShape->SetShape( SHAPE_T::SEGMENT );
4463 VECTOR2I pointOffset( 0, ( aElem.topsize.y / 2 - aElem.topsize.x / 2 ) );
4464 aShape->SetStart( aElem.position + pointOffset );
4465 aShape->SetEnd( aElem.position - pointOffset );
4466 }
4467 else
4468 {
4469 // short horizontal line
4470 aShape->SetShape( SHAPE_T::SEGMENT );
4471 VECTOR2I pointOffset( ( aElem.topsize.x / 2 - aElem.topsize.y / 2 ), 0 );
4472 aShape->SetStart( aElem.position + pointOffset );
4473 aShape->SetEnd( aElem.position - pointOffset );
4474 }
4475
4476 if( aElem.direction != 0 )
4477 aShape->Rotate( aElem.position, EDA_ANGLE( aElem.direction, DEGREES_T ) );
4478 }
4479 else if( aElem.topsize.x == aElem.topsize.y )
4480 {
4481 // filled circle
4482 aShape->SetShape( SHAPE_T::CIRCLE );
4483 aShape->SetFilled( true );
4484 aShape->SetLayer( aLayer );
4485 aShape->SetStart( aElem.position );
4486 aShape->SetEnd( aElem.position - VECTOR2I( 0, aElem.topsize.x / 4 ) );
4487 aShape->SetStroke( STROKE_PARAMS( aElem.topsize.x / 2, LINE_STYLE::SOLID ) );
4488 }
4489 else
4490 {
4491 // short line
4492 aShape->SetShape( SHAPE_T::SEGMENT );
4493 aShape->SetLayer( aLayer );
4494 aShape->SetStroke( STROKE_PARAMS( std::min( aElem.topsize.x, aElem.topsize.y ),
4496
4497 if( aElem.topsize.x < aElem.topsize.y )
4498 {
4499 VECTOR2I offset( 0, ( aElem.topsize.y / 2 - aElem.topsize.x / 2 ) );
4500 aShape->SetStart( aElem.position + offset );
4501 aShape->SetEnd( aElem.position - offset );
4502 }
4503 else
4504 {
4505 VECTOR2I offset( ( aElem.topsize.x / 2 - aElem.topsize.y / 2 ), 0 );
4506 aShape->SetStart( aElem.position + offset );
4507 aShape->SetEnd( aElem.position - offset );
4508 }
4509
4510 if( aElem.direction != 0 )
4511 aShape->Rotate( aElem.position, EDA_ANGLE( aElem.direction, DEGREES_T ) );
4512 }
4513 break;
4514
4516 {
4517 // filled octagon
4518 aShape->SetShape( SHAPE_T::POLY );
4519 aShape->SetFilled( true );
4520 aShape->SetLayer( aLayer );
4521 aShape->SetStroke( STROKE_PARAMS( 0 ) );
4522
4523 VECTOR2I p11 = aElem.position + VECTOR2I( aElem.topsize.x / 2, aElem.topsize.y / 2 );
4524 VECTOR2I p12 = aElem.position + VECTOR2I( aElem.topsize.x / 2, -aElem.topsize.y / 2 );
4525 VECTOR2I p22 = aElem.position + VECTOR2I( -aElem.topsize.x / 2, -aElem.topsize.y / 2 );
4526 VECTOR2I p21 = aElem.position + VECTOR2I( -aElem.topsize.x / 2, aElem.topsize.y / 2 );
4527
4528 int chamfer = std::min( aElem.topsize.x, aElem.topsize.y ) / 4;
4529 VECTOR2I chamferX( chamfer, 0 );
4530 VECTOR2I chamferY( 0, chamfer );
4531
4532 aShape->SetPolyPoints( { p11 - chamferX, p11 - chamferY, p12 + chamferY, p12 - chamferX,
4533 p22 + chamferX, p22 + chamferY, p21 - chamferY, p21 + chamferX } );
4534
4535 if( aElem.direction != 0. )
4536 aShape->Rotate( aElem.position, EDA_ANGLE( aElem.direction, DEGREES_T ) );
4537 }
4538 break;
4539
4541 default:
4542 if( m_reporter )
4543 {
4544 wxString msg;
4545 msg.Printf( _( "Non-copper pad %s uses an unknown pad shape." ), aElem.name );
4546 m_reporter->Report( msg, RPT_SEVERITY_DEBUG );
4547 }
4548
4549 break;
4550 }
4551}
4552
4553
4555 const CFB::COMPOUND_FILE_ENTRY* aEntry )
4556{
4557 if( m_progressReporter )
4558 m_progressReporter->Report( _( "Loading vias..." ) );
4559
4560 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
4561
4562 while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
4563 {
4564 checkpoint();
4565 AVIA6 elem( reader );
4566
4567 std::unique_ptr<PCB_VIA> via = std::make_unique<PCB_VIA>( m_board );
4568
4569 via->SetPosition( elem.position );
4570 via->SetDrill( elem.holesize );
4571 via->SetNetCode( GetNetCode( elem.net ) );
4572 via->SetLocked( elem.is_locked );
4573
4574 bool start_layer_outside = elem.layer_start == ALTIUM_LAYER::TOP_LAYER
4576 bool end_layer_outside = elem.layer_end == ALTIUM_LAYER::TOP_LAYER
4578
4579 if( start_layer_outside && end_layer_outside )
4580 {
4581 via->SetViaType( VIATYPE::THROUGH );
4582 }
4583 else if( ( !start_layer_outside ) && ( !end_layer_outside ) )
4584 {
4585 via->SetViaType( VIATYPE::BURIED );
4586 }
4587 else
4588 {
4589 via->SetViaType( VIATYPE::BLIND );
4590 }
4591
4592 // TODO: Altium has a specific flag for microvias, independent of start/end layer
4593#if 0
4594 if( something )
4595 via->SetViaType( VIATYPE::MICROVIA );
4596#endif
4597
4598 PCB_LAYER_ID start_klayer = GetKicadLayer( elem.layer_start );
4599 PCB_LAYER_ID end_klayer = GetKicadLayer( elem.layer_end );
4600
4601 if( !IsCopperLayer( start_klayer ) || !IsCopperLayer( end_klayer ) )
4602 {
4603 if( m_reporter )
4604 {
4605 wxString msg;
4606 msg.Printf( _( "Via from layer %d to %d uses a non-copper layer, which is not "
4607 "supported." ),
4608 elem.layer_start,
4609 elem.layer_end );
4610 m_reporter->Report( msg, RPT_SEVERITY_DEBUG );
4611 }
4612
4613 continue; // just assume through-hole instead.
4614 }
4615
4616 // we need VIATYPE set!
4617 via->SetLayerPair( start_klayer, end_klayer );
4618
4619 switch( elem.viamode )
4620 {
4621 default:
4623 via->SetWidth( PADSTACK::ALL_LAYERS, elem.diameter );
4624 break;
4625
4627 via->Padstack().SetMode( PADSTACK::MODE::FRONT_INNER_BACK );
4631 break;
4632
4634 {
4635 via->Padstack().SetMode( PADSTACK::MODE::CUSTOM );
4636
4637 LSET cuLayers = m_board->GetEnabledLayers() & LSET::AllCuMask();
4638
4639 for( PCB_LAYER_ID layer : cuLayers )
4640 {
4641 int altiumLayer = HelperGetPadstackLayerIndex( layer );
4642
4643 // Internal planes carry no padstack entry; the via keeps its nominal land there
4644 via->SetWidth( layer, altiumLayer < 0 ? elem.diameter
4645 : elem.diameter_by_layer[altiumLayer] );
4646 }
4647
4648 break;
4649 }
4650 }
4651
4652 // Altium can size the solder mask opening from the hole edge instead of the via land.
4653 // KiCad vias cannot represent a hole-referenced opening, so when the resulting opening
4654 // does not clear the via land the pad copper is covered and the via is effectively tented.
4658 via->GetWidth( F_Cu ) );
4659
4660 bool tentBottom = altiumViaSideIsTented( elem.is_tent_bottom,
4664 via->GetWidth( B_Cu ) );
4665
4666 via->SetFrontTentingMode( tentTop ? TENTING_MODE::TENTED : TENTING_MODE::NOT_TENTED );
4667 via->SetBackTentingMode( tentBottom ? TENTING_MODE::TENTED : TENTING_MODE::NOT_TENTED );
4668
4669 m_board->Add( via.release(), ADD_MODE::APPEND );
4670 }
4671
4672 if( reader.GetRemainingBytes() != 0 )
4673 THROW_IO_ERROR( wxT( "Vias6 stream is not fully parsed" ) );
4674}
4675
4677 const CFB::COMPOUND_FILE_ENTRY* aEntry )
4678{
4679 if( m_progressReporter )
4680 m_progressReporter->Report( _( "Loading tracks..." ) );
4681
4682 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
4683
4684 for( int primitiveIndex = 0; reader.GetRemainingBytes() >= 4; primitiveIndex++ )
4685 {
4686 checkpoint();
4687 ATRACK6 elem( reader );
4688
4689 if( elem.component == ALTIUM_COMPONENT_NONE )
4690 {
4691 ConvertTracks6ToBoardItem( elem, primitiveIndex );
4692 }
4693 else
4694 {
4695 FOOTPRINT* footprint = HelperGetFootprint( elem.component );
4696 ConvertTracks6ToFootprintItem( footprint, elem, primitiveIndex, true );
4697 }
4698 }
4699
4700 if( reader.GetRemainingBytes() != 0 )
4701 THROW_IO_ERROR( wxT( "Tracks6 stream is not fully parsed" ) );
4702}
4703
4704
4705void ALTIUM_PCB::ConvertTracks6ToBoardItem( const ATRACK6& aElem, const int aPrimitiveIndex )
4706{
4707 if( aElem.polygon != ALTIUM_POLYGON_NONE && aElem.polygon != ALTIUM_POLYGON_BOARD )
4708 {
4709 if( m_polygons.size() <= aElem.polygon )
4710 {
4711 // Can happen when reading old Altium files: just skip this item
4712 if( m_reporter )
4713 {
4714 wxString msg;
4715 msg.Printf( wxT( "ATRACK6 stream tries to access polygon id %u "
4716 "of %u existing polygons; skipping it" ),
4717 static_cast<unsigned>( aElem.polygon ),
4718 static_cast<unsigned>( m_polygons.size() ) );
4719 m_reporter->Report( msg, RPT_SEVERITY_DEBUG );
4720 }
4721
4722 return;
4723 }
4724
4725 ZONE* zone = m_polygons.at( aElem.polygon );
4726
4727 if( zone == nullptr )
4728 {
4729 return; // we know the zone id, but because we do not know the layer we did not
4730 // add it!
4731 }
4732
4733 PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
4734
4735 if( klayer == UNDEFINED_LAYER )
4736 return; // Just skip it for now. Users can fill it themselves.
4737
4738 if( !zone->HasFilledPolysForLayer( klayer ) )
4739 return;
4740
4741 SHAPE_POLY_SET* fill = zone->GetFill( klayer );
4742
4743 PCB_SHAPE shape( nullptr, SHAPE_T::SEGMENT );
4744 shape.SetStart( aElem.start );
4745 shape.SetEnd( aElem.end );
4747
4748 shape.EDA_SHAPE::TransformShapeToPolygon( *fill, 0, ARC_HIGH_DEF, ERROR_INSIDE );
4749 // Will be simplified and fractured later
4750
4751 zone->SetIsFilled( true );
4752 zone->SetNeedRefill( false );
4753
4754 return;
4755 }
4756
4757 if( aElem.is_keepout || aElem.layer == ALTIUM_LAYER::KEEP_OUT_LAYER
4758 || IsAltiumLayerAPlane( aElem.layer ) )
4759 {
4760 // This is not the actual board item. We can use it to create the polygon for the region
4761 PCB_SHAPE shape( nullptr, SHAPE_T::SEGMENT );
4762 shape.SetStart( aElem.start );
4763 shape.SetEnd( aElem.end );
4765
4767 }
4768 else
4769 {
4770 for( PCB_LAYER_ID klayer : GetKicadLayersToIterate( aElem.layer ) )
4771 ConvertTracks6ToBoardItemOnLayer( aElem, klayer );
4772 }
4773
4774 for( const auto& layerExpansionMask : HelperGetSolderAndPasteMaskExpansions( ALTIUM_RECORD::TRACK,
4775 aPrimitiveIndex, aElem.layer ) )
4776 {
4777 int width = aElem.width + ( layerExpansionMask.second * 2 );
4778 if( width > 1 )
4779 {
4780 std::unique_ptr<PCB_SHAPE> seg = std::make_unique<PCB_SHAPE>( m_board, SHAPE_T::SEGMENT );
4781
4782 seg->SetStart( aElem.start );
4783 seg->SetEnd( aElem.end );
4784 seg->SetStroke( STROKE_PARAMS( width, LINE_STYLE::SOLID ) );
4785 seg->SetLayer( layerExpansionMask.first );
4786
4787 m_board->Add( seg.release(), ADD_MODE::APPEND );
4788 }
4789 }
4790}
4791
4792
4794 const int aPrimitiveIndex,
4795 const bool aIsBoardImport )
4796{
4797 if( aElem.polygon != ALTIUM_POLYGON_NONE )
4798 {
4799 wxFAIL_MSG( wxString::Format( wxT( "Altium: Unexpected footprint Track with polygon id %u" ),
4800 (unsigned)aElem.polygon ) );
4801 return;
4802 }
4803
4804 if( aElem.is_keepout || aElem.layer == ALTIUM_LAYER::KEEP_OUT_LAYER
4805 || IsAltiumLayerAPlane( aElem.layer ) )
4806 {
4807 // This is not the actual board item. We can use it to create the polygon for the region
4808 PCB_SHAPE shape( nullptr, SHAPE_T::SEGMENT );
4809 shape.SetStart( aElem.start );
4810 shape.SetEnd( aElem.end );
4812
4813 HelperPcpShapeAsFootprintKeepoutRegion( aFootprint, shape, aElem.layer,
4814 aElem.keepoutrestrictions );
4815 }
4816 else
4817 {
4818 for( PCB_LAYER_ID klayer : GetKicadLayersToIterate( aElem.layer ) )
4819 {
4820 if( aIsBoardImport && IsCopperLayer( klayer ) && aElem.net != ALTIUM_NET_UNCONNECTED )
4821 {
4822 // Special case: do to not lose net connections in footprints
4823 ConvertTracks6ToBoardItemOnLayer( aElem, klayer );
4824 }
4825 else
4826 {
4827 ConvertTracks6ToFootprintItemOnLayer( aFootprint, aElem, klayer );
4828 }
4829 }
4830 }
4831
4832 for( const auto& layerExpansionMask : HelperGetSolderAndPasteMaskExpansions( ALTIUM_RECORD::TRACK,
4833 aPrimitiveIndex, aElem.layer ) )
4834 {
4835 int width = aElem.width + ( layerExpansionMask.second * 2 );
4836 if( width > 1 )
4837 {
4838 std::unique_ptr<PCB_SHAPE> seg = std::make_unique<PCB_SHAPE>( aFootprint, SHAPE_T::SEGMENT );
4839
4840 seg->SetStart( aElem.start );
4841 seg->SetEnd( aElem.end );
4842 seg->SetStroke( STROKE_PARAMS( width, LINE_STYLE::SOLID ) );
4843 seg->SetLayer( layerExpansionMask.first );
4844
4845 aFootprint->Add( seg.release(), ADD_MODE::APPEND );
4846 }
4847 }
4848}
4849
4850
4852{
4853 if( IsCopperLayer( aLayer ) && aElem.net != ALTIUM_NET_UNCONNECTED )
4854 {
4855 std::unique_ptr<PCB_TRACK> track = std::make_unique<PCB_TRACK>( m_board );
4856
4857 track->SetStart( aElem.start );
4858 track->SetEnd( aElem.end );
4859 track->SetWidth( aElem.width );
4860 track->SetLayer( aLayer );
4861 track->SetNetCode( GetNetCode( aElem.net ) );
4862
4863 PCB_TRACK* added = track.release();
4864 m_board->Add( added, ADD_MODE::APPEND );
4865
4866 if( aElem.unionindex != 0 )
4867 m_unionToBoardItems[static_cast<int>( aElem.unionindex )].push_back( added );
4868 }
4869 else
4870 {
4871 std::unique_ptr<PCB_SHAPE> seg = std::make_unique<PCB_SHAPE>( m_board, SHAPE_T::SEGMENT );
4872
4873 seg->SetStart( aElem.start );
4874 seg->SetEnd( aElem.end );
4875 seg->SetStroke( STROKE_PARAMS( aElem.width, LINE_STYLE::SOLID ) );
4876 seg->SetLayer( aLayer );
4877
4878 m_board->Add( seg.release(), ADD_MODE::APPEND );
4879 }
4880}
4881
4882
4884 PCB_LAYER_ID aLayer )
4885{
4886 std::unique_ptr<PCB_SHAPE> seg = std::make_unique<PCB_SHAPE>( aFootprint, SHAPE_T::SEGMENT );
4887
4888 seg->SetStart( aElem.start );
4889 seg->SetEnd( aElem.end );
4890 seg->SetStroke( STROKE_PARAMS( aElem.width, LINE_STYLE::SOLID ) );
4891 seg->SetLayer( aLayer );
4892
4893 aFootprint->Add( seg.release(), ADD_MODE::APPEND );
4894}
4895
4896
4898 const CFB::COMPOUND_FILE_ENTRY* aEntry )
4899{
4900 if( m_progressReporter )
4901 m_progressReporter->Report( _( "Loading unicode strings..." ) );
4902
4903 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
4904
4906
4907 if( reader.GetRemainingBytes() != 0 )
4908 THROW_IO_ERROR( wxT( "WideStrings6 stream is not fully parsed" ) );
4909}
4910
4912 const CFB::COMPOUND_FILE_ENTRY* aEntry )
4913{
4914 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
4915
4916 while( reader.GetRemainingBytes() >= 4 )
4917 {
4918 checkpoint();
4919 ASMARTUNION6 elem( reader );
4920
4921 if( elem.is_tuning && elem.unionindex != 0 )
4922 m_tuningUnions.emplace_back( std::move( elem ) );
4923 }
4924
4925 if( reader.GetRemainingBytes() != 0 )
4926 THROW_IO_ERROR( wxT( "SmartUnions6 stream is not fully parsed" ) );
4927}
4928
4929
4931 const CFB::COMPOUND_FILE_ENTRY* aEntry )
4932{
4933 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
4934
4935 // Discard the leading record count, otherwise the wide-string table desyncs by four bytes
4936 reader.Read<uint32_t>();
4938
4939 if( reader.GetRemainingBytes() != 0 )
4940 THROW_IO_ERROR( wxT( "UnionNames stream is not fully parsed" ) );
4941}
4942
4943
4945{
4946 int created = 0;
4947
4948 for( const ASMARTUNION6& tuning : m_tuningUnions )
4949 {
4950 auto itemsIt = m_unionToBoardItems.find( tuning.unionindex );
4951
4952 // Altium commits the tuned copper as ordinary tracks and arcs. Without those primitives
4953 // there is nothing to wrap, so drop the meander rather than fabricate geometry.
4954 if( itemsIt == m_unionToBoardItems.end() || itemsIt->second.empty() )
4955 continue;
4956
4957 // Without a baseline the pattern can be neither re-tuned nor reset, so wrapping the copper
4958 // would only take it away from the user
4959 if( tuning.baseline.size() < 2
4960 || ( tuning.is_diffpair && tuning.baselinecoupled.size() < 2 ) )
4961 {
4962 continue;
4963 }
4964
4965 const std::vector<BOARD_ITEM*>& items = itemsIt->second;
4966
4967 LENGTH_TUNING_MODE mode = tuning.is_diffpair ? LENGTH_TUNING_MODE::DIFF_PAIR
4969
4970 SHAPE_LINE_CHAIN baseLine( tuning.baseline );
4971
4972 PCB_LAYER_ID layer = items.front()->GetLayer();
4973
4974 PCB_GENERATOR* generator = GENERATORS_MGR::Instance().CreateFromType( wxS( "tuning_pattern" ) );
4975
4976 if( !generator )
4977 continue;
4978
4979 std::unique_ptr<PCB_TUNING_PATTERN> pattern( static_cast<PCB_TUNING_PATTERN*>( generator ) );
4980 pattern->SetParent( m_board );
4981 pattern->SetLayer( layer );
4982 pattern->SetTuningMode( mode );
4983
4984 // Preserve Altium's interactive union name so the meander keeps its designer-visible label.
4985 if( auto nameIt = m_unionNames.find( tuning.unionindex );
4986 nameIt != m_unionNames.end() && !nameIt->second.IsEmpty() )
4987 {
4988 pattern->SetName( nameIt->second );
4989 }
4990
4991 pattern->SetMaxAmplitude( tuning.amplitude );
4992 pattern->SetMinAmplitude( tuning.minamplitude );
4993 pattern->SetSpacing( tuning.gap );
4994 pattern->SetSingleSided( tuning.singleside );
4995
4996 // Altium "Style" selects mitered (chamfered) versus rounded corners. The committed
4997 // copper carries the real geometry; this only governs a later interactive re-tune.
4998 pattern->SetRounded( tuning.style != 0 );
4999
5000 if( tuning.mitterradiusratio > 0.0 )
5001 {
5002 int percent = KiROUND( tuning.mitterradiusratio * 100.0 );
5003 pattern->SetCornerRadiusPercentage( std::clamp( percent, 0, 100 ) );
5004 }
5005
5006 int netCode = -1;
5007 bool singleNet = true;
5008
5009 for( BOARD_ITEM* item : items )
5010 {
5011 pattern->AddItem( item );
5012
5013 if( BOARD_CONNECTED_ITEM* bci = dynamic_cast<BOARD_CONNECTED_ITEM*>( item ) )
5014 {
5015 if( netCode < 0 )
5016 netCode = bci->GetNetCode();
5017 else if( netCode != bci->GetNetCode() )
5018 singleNet = false;
5019 }
5020 }
5021
5022 // SetNetCode reassigns the net of every member, so only apply it when the union is on a
5023 // single net. Differential-pair meanders span two nets that must both be preserved.
5024 if( netCode >= 0 && singleNet )
5025 {
5026 pattern->SetNetCode( netCode );
5027 }
5028 else
5029 {
5030 // Name the pattern after the net at the baseline start, the one an edit snaps to
5031 const VECTOR2I& origin = baseLine.CPoint( 0 );
5032 SEG::ecoord bestDist = std::numeric_limits<SEG::ecoord>::max();
5033 wxString bestNet;
5034
5035 for( BOARD_ITEM* item : items )
5036 {
5037 PCB_TRACK* track = dynamic_cast<PCB_TRACK*>( item );
5038
5039 if( !track )
5040 continue;
5041
5042 SEG::ecoord dist = SEG( track->GetStart(), track->GetEnd() ).SquaredDistance( origin );
5043
5044 if( dist < bestDist )
5045 {
5046 bestDist = dist;
5047 bestNet = track->GetNetname();
5048 }
5049 }
5050
5051 pattern->SetLastNetName( bestNet );
5052 }
5053
5054 if( PCB_TRACK* track = dynamic_cast<PCB_TRACK*>( items.front() ) )
5055 pattern->SetWidth( track->GetWidth() );
5056
5057 pattern->SetBaseLine( baseLine );
5058 pattern->SetPosition( baseLine.CPoint( 0 ) );
5059 pattern->SetEnd( baseLine.CLastPoint() );
5060
5061 if( mode == LENGTH_TUNING_MODE::DIFF_PAIR )
5062 {
5063 SHAPE_LINE_CHAIN baseLineCoupled( tuning.baselinecoupled );
5064
5065 pattern->SetBaseLineCoupled( baseLineCoupled );
5066
5067 int centreToCentre = baseLine.Distance( baseLineCoupled.CPoint( 0 ), false );
5068
5069 pattern->SetDiffPairGap( std::max( centreToCentre - pattern->GetWidth(), 0 ) );
5070 }
5071
5072 m_board->Add( pattern.release(), ADD_MODE::INSERT );
5073 created++;
5074 }
5075
5076 if( m_reporter && created > 0 )
5077 {
5078 m_reporter->Report( wxString::Format( _( "Imported %d length-tuning pattern(s)." ),
5079 created ),
5081 }
5082}
5083
5084
5086{
5087 // Altium has no per-component mounting style to copy, so derive it from the pads the way
5088 // KiCad's own footprint checker does. Using the same heuristic keeps the imported value in
5089 // agreement with FOOTPRINT::CheckFootprintAttributes(), which would otherwise report every
5090 // footprint we just wrote as a type mismatch.
5091 //
5092 // Only m_components is walked, so importing into a board that already holds footprints
5093 // cannot rewrite them, and only a missing style is filled in.
5094 for( FOOTPRINT* footprint : m_components )
5095 {
5096 if( !footprint )
5097 continue;
5098
5099 if( footprint->GetAttributes() & ( FP_SMD | FP_THROUGH_HOLE ) )
5100 continue;
5101
5102 footprint->SetAttributes( footprint->GetAttributes() | footprint->GetLikelyAttribute() );
5103 }
5104}
5105
5106
5108 const CFB::COMPOUND_FILE_ENTRY* aEntry )
5109{
5110 if( m_progressReporter )
5111 m_progressReporter->Report( _( "Loading text..." ) );
5112
5113 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
5114
5115 while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
5116 {
5117 checkpoint();
5118 ATEXT6 elem( reader, m_unicodeStrings );
5119
5120 if( elem.component == ALTIUM_COMPONENT_NONE )
5121 {
5123 }
5124 else
5125 {
5126 FOOTPRINT* footprint = HelperGetFootprint( elem.component );
5127 ConvertTexts6ToFootprintItem( footprint, elem );
5128 }
5129 }
5130
5131 if( reader.GetRemainingBytes() != 0 )
5132 THROW_IO_ERROR( wxT( "Texts6 stream is not fully parsed" ) );
5133}
5134
5135
5137{
5138 if( aElem.fonttype == ALTIUM_TEXT_TYPE::BARCODE )
5139 {
5140 for( PCB_LAYER_ID klayer : GetKicadLayersToIterate( aElem.layer ) )
5141 ConvertBarcodes6ToBoardItemOnLayer( aElem, klayer );
5142
5143 return;
5144 }
5145
5146 for( PCB_LAYER_ID klayer : GetKicadLayersToIterate( aElem.layer ) )
5147 ConvertTexts6ToBoardItemOnLayer( aElem, klayer );
5148}
5149
5150
5152{
5153 if( aElem.fonttype == ALTIUM_TEXT_TYPE::BARCODE )
5154 {
5155 for( PCB_LAYER_ID klayer : GetKicadLayersToIterate( aElem.layer ) )
5156 ConvertBarcodes6ToFootprintItemOnLayer( aFootprint, aElem, klayer );
5157 return;
5158 }
5159
5160 for( PCB_LAYER_ID klayer : GetKicadLayersToIterate( aElem.layer ) )
5161 ConvertTexts6ToFootprintItemOnLayer( aFootprint, aElem, klayer );
5162}
5163
5164
5166{
5167 std::unique_ptr<PCB_TEXTBOX> pcbTextbox = std::make_unique<PCB_TEXTBOX>( m_board );
5168 std::unique_ptr<PCB_TEXT> pcbText = std::make_unique<PCB_TEXT>( m_board );
5169
5170 bool isTextbox = aElem.isFrame && !aElem.isInverted; // Textbox knockout is not supported
5171
5172 static const std::map<wxString, wxString> variableMap = {
5173 { "LAYER_NAME", "LAYER" },
5174 { "PRINT_DATE", "CURRENT_DATE"},
5175 };
5176
5177 wxString kicadText = AltiumPcbSpecialStringsToKiCadStrings( aElem.text, variableMap );
5178 BOARD_ITEM* item = pcbText.get();
5179 EDA_TEXT* text = pcbText.get();
5180
5181 if( isTextbox )
5182 {
5183 item = pcbTextbox.get();
5184 text = pcbTextbox.get();
5185 }
5186
5187 text->SetText( kicadText );
5188
5189 // Set the layer before the alignment helpers run. HelperSetTextAlignmentAndPos measures the
5190 // text via GetTextBox(), which resolves layer-dependent special strings such as ${LAYER}.
5191 item->SetLayer( aLayer );
5192
5194
5195 if( isTextbox )
5196 HelperSetTextboxAlignmentAndPos( aElem, pcbTextbox.get() );
5197 else
5199
5200 item->SetIsKnockout( aElem.isInverted );
5201
5202 if( isTextbox )
5203 m_board->Add( pcbTextbox.release(), ADD_MODE::APPEND );
5204 else
5205 m_board->Add( pcbText.release(), ADD_MODE::APPEND );
5206}
5207
5208
5210 PCB_LAYER_ID aLayer )
5211{
5212 std::unique_ptr<PCB_TEXTBOX> fpTextbox = std::make_unique<PCB_TEXTBOX>( aFootprint );
5213 std::unique_ptr<PCB_TEXT> fpText = std::make_unique<PCB_TEXT>( aFootprint );
5214
5215 BOARD_ITEM* item = fpText.get();
5216 EDA_TEXT* text = fpText.get();
5217
5218 bool isTextbox = aElem.isFrame && !aElem.isInverted; // Textbox knockout is not supported
5219 bool toAdd = false;
5220
5221 if( aElem.isDesignator )
5222 {
5223 item = &aFootprint->Reference(); // TODO: handle multiple layers
5224 text = &aFootprint->Reference();
5225 }
5226 else if( aElem.isComment )
5227 {
5228 item = &aFootprint->Value(); // TODO: handle multiple layers
5229 text = &aFootprint->Value();
5230 }
5231 else
5232 {
5233 item = fpText.get();
5234 text = fpText.get();
5235 toAdd = true;
5236 }
5237
5238 static const std::map<wxString, wxString> variableMap = {
5239 { "DESIGNATOR", "REFERENCE" },
5240 { "COMMENT", "VALUE" },
5241 { "VALUE", "ALTIUM_VALUE" },
5242 { "LAYER_NAME", "LAYER" },
5243 { "PRINT_DATE", "CURRENT_DATE"},
5244 };
5245
5246 if( isTextbox )
5247 {
5248 item = fpTextbox.get();
5249 text = fpTextbox.get();
5250 }
5251
5252 wxString kicadText = AltiumPcbSpecialStringsToKiCadStrings( aElem.text, variableMap );
5253
5254 text->SetText( kicadText );
5255
5256 // Set the layer before the alignment helpers run. HelperSetTextAlignmentAndPos measures the
5257 // text via GetTextBox(), which resolves layer-dependent special strings such as ${LAYER}.
5258 item->SetLayer( aLayer );
5259
5261
5262 if( isTextbox )
5263 HelperSetTextboxAlignmentAndPos( aElem, fpTextbox.get() );
5264 else
5266
5267 text->SetKeepUpright( false );
5268 item->SetIsKnockout( aElem.isInverted );
5269
5270 if( toAdd )
5271 {
5272 if( isTextbox )
5273 aFootprint->Add( fpTextbox.release(), ADD_MODE::APPEND );
5274 else
5275 aFootprint->Add( fpText.release(), ADD_MODE::APPEND );
5276 }
5277}
5278
5279
5281{
5282 std::unique_ptr<PCB_BARCODE> pcbBarcode = std::make_unique<PCB_BARCODE>( m_board );
5283
5284 pcbBarcode->SetLayer( aLayer );
5285 pcbBarcode->SetPosition( aElem.position );
5286 pcbBarcode->SetWidth( aElem.textbox_rect_width );
5287 pcbBarcode->SetHeight( aElem.textbox_rect_height );
5288 pcbBarcode->SetMargin( aElem.barcode_margin );
5289 pcbBarcode->SetText( aElem.text );
5290
5291 switch( aElem.barcode_type )
5292 {
5293 case ALTIUM_BARCODE_TYPE::CODE39: pcbBarcode->SetKind( BARCODE_T::CODE_39 ); break;
5294 case ALTIUM_BARCODE_TYPE::CODE128: pcbBarcode->SetKind( BARCODE_T::CODE_128 ); break;
5295 default: pcbBarcode->SetKind( BARCODE_T::CODE_39 ); break;
5296 }
5297
5298 pcbBarcode->SetIsKnockout( aElem.barcode_inverted );
5299 pcbBarcode->AssembleBarcode();
5300
5301 m_board->Add( pcbBarcode.release(), ADD_MODE::APPEND );
5302}
5303
5304
5306 PCB_LAYER_ID aLayer )
5307{
5308 std::unique_ptr<PCB_BARCODE> fpBarcode = std::make_unique<PCB_BARCODE>( aFootprint );
5309
5310 fpBarcode->SetLayer( aLayer );
5311 fpBarcode->SetPosition( aElem.position );
5312 fpBarcode->SetWidth( aElem.textbox_rect_width );
5313 fpBarcode->SetHeight( aElem.textbox_rect_height );
5314 fpBarcode->SetMargin( aElem.barcode_margin );
5315 fpBarcode->SetText( aElem.text );
5316
5317 switch( aElem.barcode_type )
5318 {
5319 case ALTIUM_BARCODE_TYPE::CODE39: fpBarcode->SetKind( BARCODE_T::CODE_39 ); break;
5320 case ALTIUM_BARCODE_TYPE::CODE128: fpBarcode->SetKind( BARCODE_T::CODE_128 ); break;
5321 default: fpBarcode->SetKind( BARCODE_T::CODE_39 ); break;
5322 }
5323
5324 fpBarcode->SetIsKnockout( aElem.barcode_inverted );
5325 fpBarcode->AssembleBarcode();
5326
5327 aFootprint->Add( fpBarcode.release(), ADD_MODE::APPEND );
5328}
5329
5330
5332{
5333 int margin = aElem.isOffsetBorder ? aElem.text_offset_width : aElem.margin_border_width;
5334
5335 // Altium textboxes do not have borders
5336 aTextbox->SetBorderEnabled( false );
5337
5338 // Calculate position
5339 VECTOR2I kposition = aElem.position;
5340
5341 if( aElem.isMirrored )
5342 kposition.x -= aElem.textbox_rect_width;
5343
5344 kposition.y -= aElem.textbox_rect_height;
5345
5346#if 0
5347 // Compensate for KiCad's textbox margin
5348 int charWidth = aTextbox->GetTextWidth();
5349 int charHeight = aTextbox->GetTextHeight();
5350
5351 VECTOR2I kicadMargin;
5352
5353 if( !aTextbox->GetFont() || aTextbox->GetFont()->IsStroke() )
5354 kicadMargin = VECTOR2I( charWidth * 0.933, charHeight * 0.67 );
5355 else
5356 kicadMargin = VECTOR2I( charWidth * 0.808, charHeight * 0.844 );
5357
5358 aTextbox->SetEnd( VECTOR2I( aElem.textbox_rect_width, aElem.textbox_rect_height )
5359 + kicadMargin * 2 - margin * 2 );
5360
5361 kposition = kposition - kicadMargin + margin;
5362#else
5363 aTextbox->SetMarginBottom( margin );
5364 aTextbox->SetMarginLeft( margin );
5365 aTextbox->SetMarginRight( margin );
5366 aTextbox->SetMarginTop( margin );
5367
5368 aTextbox->SetEnd( VECTOR2I( aElem.textbox_rect_width, aElem.textbox_rect_height ) );
5369#endif
5370
5371 RotatePoint( kposition, aElem.position, EDA_ANGLE( aElem.rotation, DEGREES_T ) );
5372
5373 aTextbox->SetPosition( kposition );
5374
5377
5378 switch( justification )
5379 {
5385 break;
5391 break;
5397 break;
5398 default:
5399 if( m_reporter )
5400 {
5401 wxString msg;
5402 msg.Printf( _( "Unknown textbox justification %d, aText %s" ), justification,
5403 aElem.text );
5404 m_reporter->Report( msg, RPT_SEVERITY_DEBUG );
5405 }
5406
5409 break;
5410 }
5411
5412 aTextbox->SetTextAngle( EDA_ANGLE( aElem.rotation, DEGREES_T ) );
5413}
5414
5415
5417{
5418 VECTOR2I kposition = aElem.position;
5419
5420 int margin = aElem.isOffsetBorder ? aElem.text_offset_width : aElem.margin_border_width;
5421 int rectWidth = aElem.textbox_rect_width - margin * 2;
5422 int rectHeight = aElem.height;
5423
5424 // Altium auto-sizes the bounding box of a free string (non-frame text) from its own glyph
5425 // rasterizer, and stores a slightly different width for otherwise identical strings placed on
5426 // different layers (e.g. the copper and soldermask copies of the same label, which Altium may
5427 // also give different stroke widths). Anchoring the KiCad text to that per-record width drives
5428 // the two copies apart. Center the text on its bare glyph run instead, measured from the
5429 // already-populated EDA_TEXT with the pen inflation removed, so copies that share a glyph run
5430 // stay coincident regardless of stroke width.
5431 if( !aElem.isFrame )
5432 {
5433 rectWidth = aText->GetTextBox( nullptr ).GetWidth();
5434
5435 if( KIFONT::FONT* font = aText->GetFont(); !font || font->IsStroke() )
5436 rectWidth -= 3 * aText->GetEffectiveTextPenWidth();
5437 }
5438
5439 if( aElem.isMirrored )
5440 rectWidth = -rectWidth;
5441
5444
5445 switch( justification )
5446 {
5450
5451 kposition.y -= rectHeight;
5452 break;
5456
5457 kposition.y -= rectHeight / 2;
5458 break;
5462 break;
5466
5467 kposition.x += rectWidth / 2;
5468 kposition.y -= rectHeight;
5469 break;
5473
5474 kposition.x += rectWidth / 2;
5475 kposition.y -= rectHeight / 2;
5476 break;
5480
5481 kposition.x += rectWidth / 2;
5482 break;
5486
5487 kposition.x += rectWidth;
5488 kposition.y -= rectHeight;
5489 break;
5493
5494 kposition.x += rectWidth;
5495 kposition.y -= rectHeight / 2;
5496 break;
5500
5501 kposition.x += rectWidth;
5502 break;
5503 default:
5506 break;
5507 }
5508
5509 int charWidth = aText->GetTextWidth();
5510 int charHeight = aText->GetTextHeight();
5511
5512 // Correct for KiCad's baseline offset.
5513 // Text height and font must be set correctly before calling.
5514 if( !aText->GetFont() || aText->GetFont()->IsStroke() )
5515 {
5516 switch( aText->GetVertJustify() )
5517 {
5518 case GR_TEXT_V_ALIGN_TOP: kposition.y -= charHeight * 0.0407; break;
5519 case GR_TEXT_V_ALIGN_CENTER: kposition.y += charHeight * 0.0355; break;
5520 case GR_TEXT_V_ALIGN_BOTTOM: kposition.y += charHeight * 0.1225; break;
5521 default: break;
5522 }
5523 }
5524 else
5525 {
5526 switch( aText->GetVertJustify() )
5527 {
5528 case GR_TEXT_V_ALIGN_TOP: kposition.y -= charWidth * 0.016; break;
5529 case GR_TEXT_V_ALIGN_CENTER: kposition.y += charWidth * 0.085; break;
5530 case GR_TEXT_V_ALIGN_BOTTOM: kposition.y += charWidth * 0.17; break;
5531 default: break;
5532 }
5533 }
5534
5535 RotatePoint( kposition, aElem.position, EDA_ANGLE( aElem.rotation, DEGREES_T ) );
5536
5537 aText->SetTextPos( kposition );
5538 aText->SetTextAngle( EDA_ANGLE( aElem.rotation, DEGREES_T ) );
5539}
5540
5541
5543{
5544 aEdaText.SetTextSize( VECTOR2I( aElem.height, aElem.height ) );
5545
5547 {
5548 KIFONT::FONT* font = KIFONT::FONT::GetFont( aElem.fontname, aElem.isBold, aElem.isItalic );
5549 aEdaText.SetFont( font );
5550
5551 if( font->IsOutline() )
5552 {
5553 // TODO: why is this required? Somehow, truetype size is calculated differently
5554 if( font->GetName().Contains( wxS( "Arial" ) ) )
5555 aEdaText.SetTextSize( VECTOR2I( aElem.height * 0.63, aElem.height * 0.63 ) );
5556 else
5557 aEdaText.SetTextSize( VECTOR2I( aElem.height * 0.5, aElem.height * 0.5 ) );
5558 }
5559 }
5560
5561 aEdaText.SetTextThickness( aElem.strokewidth );
5562 aEdaText.SetBoldFlag( aElem.isBold );
5563
5564 // The imported width is already bolded; store the base so the Bold flag doesn't double it.
5566
5567 aEdaText.SetItalic( aElem.isItalic );
5568 aEdaText.SetMirrored( aElem.isMirrored );
5569}
5570
5571
5573 const CFB::COMPOUND_FILE_ENTRY* aEntry )
5574{
5575 if( m_progressReporter )
5576 m_progressReporter->Report( _( "Loading rectangles..." ) );
5577
5578 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
5579
5580 while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
5581 {
5582 checkpoint();
5583 AFILL6 elem( reader );
5584
5585 if( elem.component == ALTIUM_COMPONENT_NONE )
5586 {
5588 }
5589 else
5590 {
5591 FOOTPRINT* footprint = HelperGetFootprint( elem.component );
5592 ConvertFills6ToFootprintItem( footprint, elem, true );
5593 }
5594 }
5595
5596 if( reader.GetRemainingBytes() != 0 )
5597 THROW_IO_ERROR( wxT( "Fills6 stream is not fully parsed" ) );
5598}
5599
5600
5602{
5603 if( aElem.is_keepout || aElem.layer == ALTIUM_LAYER::KEEP_OUT_LAYER )
5604 {
5605 // This is not the actual board item. We can use it to create the polygon for the region
5606 PCB_SHAPE shape( nullptr, SHAPE_T::RECTANGLE );
5607
5608 shape.SetStart( aElem.pos1 );
5609 shape.SetEnd( aElem.pos2 );
5610 shape.SetFilled( true );
5612
5613 if( aElem.rotation != 0. )
5614 {
5615 VECTOR2I center( aElem.pos1.x / 2 + aElem.pos2.x / 2,
5616 aElem.pos1.y / 2 + aElem.pos2.y / 2 );
5617 shape.Rotate( center, EDA_ANGLE( aElem.rotation, DEGREES_T ) );
5618 }
5619
5621 }
5622 else
5623 {
5624 for( PCB_LAYER_ID klayer : GetKicadLayersToIterate( aElem.layer ) )
5625 ConvertFills6ToBoardItemOnLayer( aElem, klayer );
5626 }
5627}
5628
5629
5631 const bool aIsBoardImport )
5632{
5633 if( aElem.is_keepout
5634 || aElem.layer == ALTIUM_LAYER::KEEP_OUT_LAYER ) // TODO: what about plane layers?
5635 {
5636 // This is not the actual board item. We can use it to create the polygon for the region
5637 PCB_SHAPE shape( nullptr, SHAPE_T::RECTANGLE );
5638
5639 shape.SetStart( aElem.pos1 );
5640 shape.SetEnd( aElem.pos2 );
5641 shape.SetFilled( true );
5643
5644 if( aElem.rotation != 0. )
5645 {
5646 VECTOR2I center( aElem.pos1.x / 2 + aElem.pos2.x / 2,
5647 aElem.pos1.y / 2 + aElem.pos2.y / 2 );
5648 shape.Rotate( center, EDA_ANGLE( aElem.rotation, DEGREES_T ) );
5649 }
5650
5651 HelperPcpShapeAsFootprintKeepoutRegion( aFootprint, shape, aElem.layer,
5652 aElem.keepoutrestrictions );
5653 }
5654 else if( aIsBoardImport && IsAltiumLayerCopper( aElem.layer )
5655 && aElem.net != ALTIUM_NET_UNCONNECTED )
5656 {
5657 // Special case: do to not lose net connections in footprints
5658 for( PCB_LAYER_ID klayer : GetKicadLayersToIterate( aElem.layer ) )
5659 ConvertFills6ToBoardItemOnLayer( aElem, klayer );
5660 }
5661 else
5662 {
5663 for( PCB_LAYER_ID klayer : GetKicadLayersToIterate( aElem.layer ) )
5664 ConvertFills6ToFootprintItemOnLayer( aFootprint, aElem, klayer );
5665 }
5666}
5667
5668
5670{
5671 std::unique_ptr<PCB_SHAPE> fill = std::make_unique<PCB_SHAPE>( m_board, SHAPE_T::RECTANGLE );
5672
5673 fill->SetFilled( true );
5674 fill->SetLayer( aLayer );
5675 fill->SetStroke( STROKE_PARAMS( 0 ) );
5676
5677 fill->SetStart( aElem.pos1 );
5678 fill->SetEnd( aElem.pos2 );
5679
5680 if( IsCopperLayer( aLayer ) && aElem.net != ALTIUM_NET_UNCONNECTED )
5681 {
5682 fill->SetNetCode( GetNetCode( aElem.net ) );
5683 }
5684
5685 if( aElem.rotation != 0. )
5686 {
5687 // TODO: Do we need SHAPE_T::POLY for non 90° rotations?
5688 VECTOR2I center( aElem.pos1.x / 2 + aElem.pos2.x / 2,
5689 aElem.pos1.y / 2 + aElem.pos2.y / 2 );
5690 fill->Rotate( center, EDA_ANGLE( aElem.rotation, DEGREES_T ) );
5691 }
5692
5693 m_board->Add( fill.release(), ADD_MODE::APPEND );
5694}
5695
5696
5698 PCB_LAYER_ID aLayer )
5699{
5700 if( aLayer == F_Cu || aLayer == B_Cu )
5701 {
5702 std::unique_ptr<PAD> pad = std::make_unique<PAD>( aFootprint );
5703
5704 LSET padLayers;
5705 padLayers.set( aLayer );
5706
5707 pad->SetAttribute( PAD_ATTRIB::SMD );
5708 EDA_ANGLE rotation( aElem.rotation, DEGREES_T );
5709
5710 // Handle rotation multiples of 90 degrees
5711 if( rotation.IsCardinal() )
5712 {
5713 pad->SetPadstackMode( PADSTACK::MODE::NORMAL );
5715
5716 int width = std::abs( aElem.pos2.x - aElem.pos1.x );
5717 int height = std::abs( aElem.pos2.y - aElem.pos1.y );
5718
5719 // Swap width and height for 90 or 270 degree rotations
5720 if( rotation.IsCardinal90() )
5721 std::swap( width, height );
5722
5723 pad->SetSize( PADSTACK::ALL_LAYERS, { width, height } );
5724 pad->SetPosition( aElem.pos1 / 2 + aElem.pos2 / 2 );
5725 }
5726 else
5727 {
5728 pad->SetPadstackMode( PADSTACK::MODE::NORMAL );
5730
5731 int anchorSize = std::min( std::abs( aElem.pos2.x - aElem.pos1.x ),
5732 std::abs( aElem.pos2.y - aElem.pos1.y ) );
5733 VECTOR2I anchorPos = aElem.pos1;
5734
5735 pad->SetAnchorPadShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::CIRCLE );
5736 pad->SetSize( PADSTACK::ALL_LAYERS, { anchorSize, anchorSize } );
5737 pad->SetPosition( anchorPos );
5738
5739 SHAPE_POLY_SET shapePolys;
5740 shapePolys.NewOutline();
5741 shapePolys.Append( aElem.pos1.x - anchorPos.x, aElem.pos1.y - anchorPos.y );
5742 shapePolys.Append( aElem.pos2.x - anchorPos.x, aElem.pos1.y - anchorPos.y );
5743 shapePolys.Append( aElem.pos2.x - anchorPos.x, aElem.pos2.y - anchorPos.y );
5744 shapePolys.Append( aElem.pos1.x - anchorPos.x, aElem.pos2.y - anchorPos.y );
5745 shapePolys.Outline( 0 ).SetClosed( true );
5746
5747 VECTOR2I center( aElem.pos1.x / 2 + aElem.pos2.x / 2 - anchorPos.x,
5748 aElem.pos1.y / 2 + aElem.pos2.y / 2 - anchorPos.y );
5749 shapePolys.Rotate( EDA_ANGLE( aElem.rotation, DEGREES_T ), center );
5750 pad->AddPrimitivePoly( F_Cu, shapePolys, 0, true );
5751 }
5752
5753 pad->SetThermalSpokeAngle( ANGLE_90 );
5754 pad->SetLayerSet( padLayers );
5755
5756 aFootprint->Add( pad.release(), ADD_MODE::APPEND );
5757 }
5758 else
5759 {
5760 std::unique_ptr<PCB_SHAPE> fill = std::make_unique<PCB_SHAPE>( aFootprint, SHAPE_T::RECTANGLE );
5761
5762 fill->SetFilled( true );
5763 fill->SetLayer( aLayer );
5764 fill->SetStroke( STROKE_PARAMS( 0 ) );
5765
5766 fill->SetStart( aElem.pos1 );
5767 fill->SetEnd( aElem.pos2 );
5768
5769 if( aElem.rotation != 0. )
5770 {
5771 VECTOR2I center( aElem.pos1.x / 2 + aElem.pos2.x / 2,
5772 aElem.pos1.y / 2 + aElem.pos2.y / 2 );
5773 fill->Rotate( center, EDA_ANGLE( aElem.rotation, DEGREES_T ) );
5774 }
5775
5776 aFootprint->Add( fill.release(), ADD_MODE::APPEND );
5777 }
5778}
5779
5780
5781void ALTIUM_PCB::HelperSetZoneLayers( ZONE& aZone, const ALTIUM_LAYER aAltiumLayer )
5782{
5783 LSET layerSet;
5784
5785 for( PCB_LAYER_ID klayer : GetKicadLayersToIterate( aAltiumLayer ) )
5786 layerSet.set( klayer );
5787
5788 aZone.SetLayerSet( layerSet );
5789}
5790
5791
5792void ALTIUM_PCB::HelperSetZoneKeepoutRestrictions( ZONE& aZone, const uint8_t aKeepoutRestrictions )
5793{
5794 bool keepoutRestrictionVia = ( aKeepoutRestrictions & ALTIUM_KEEPOUT_VIA ) != 0;
5795 bool keepoutRestrictionTrack = ( aKeepoutRestrictions & ALTIUM_KEEPOUT_TRACK ) != 0;
5796 bool keepoutRestrictionCopper = ( aKeepoutRestrictions & ALTIUM_KEEPOUT_COPPER ) != 0;
5797 bool keepoutRestrictionSMDPad = ( aKeepoutRestrictions & ALTIUM_KEEPOUT_SMD_PAD ) != 0;
5798 bool keepoutRestrictionTHPad = ( aKeepoutRestrictions & ALTIUM_KEEPOUT_TH_PAD ) != 0;
5799
5800 aZone.SetDoNotAllowVias( keepoutRestrictionVia );
5801 aZone.SetDoNotAllowTracks( keepoutRestrictionTrack );
5802 aZone.SetDoNotAllowZoneFills( keepoutRestrictionCopper );
5803 aZone.SetDoNotAllowPads( keepoutRestrictionSMDPad && keepoutRestrictionTHPad );
5804 aZone.SetDoNotAllowFootprints( false );
5805}
5806
5807
5808uint8_t ALTIUM_PCB::HelperGetKeepoutRestrictions( const uint8_t aKeepoutRestrictions, const ALTIUM_LAYER aAltiumLayer )
5809{
5810 // An internal plane is negative, so every primitive drawn on one cuts copper out of it
5811 // whatever else the mask says
5812 if( IsAltiumLayerAPlane( aAltiumLayer ) )
5813 return static_cast<uint8_t>( aKeepoutRestrictions | ALTIUM_KEEPOUT_COPPER );
5814
5815 if( aKeepoutRestrictions != 0 )
5816 return aKeepoutRestrictions;
5817
5818 // Altium leaves the mask empty on the Keep-Out layer because the layer already means
5819 // "keep everything out"
5820 if( aAltiumLayer == ALTIUM_LAYER::KEEP_OUT_LAYER )
5821 return ALTIUM_KEEPOUT_ALL;
5822
5823 if( m_reporter )
5824 {
5825 m_reporter->Report( _( "Ignored a keep-out area with no restrictions." ), RPT_SEVERITY_INFO );
5826 }
5827
5828 return 0;
5829}
5830
5831
5833{
5834 // A footprint zone stores its outline in the footprint's local frame and derives its board
5835 // position by applying the footprint transform. The importer builds the outline in board
5836 // coordinates, so it must be re-based here or the zone drifts by the footprint offset when the
5837 // board is re-centered at the end of the import.
5838 const TRANSFORM_TRS& xform = aFootprint.GetTransform();
5839 SHAPE_POLY_SET& poly = *aZone.Outline();
5840
5841 for( auto it = poly.IterateWithHoles(); it; it++ )
5842 poly.SetVertex( it.GetIndex(), xform.InverseApply( *it ) );
5843}
5844
5845
5847 const ALTIUM_LAYER aAltiumLayer,
5848 const uint8_t aKeepoutRestrictions )
5849{
5850 uint8_t restrictions = HelperGetKeepoutRestrictions( aKeepoutRestrictions, aAltiumLayer );
5851
5852 if( restrictions == 0 )
5853 return;
5854
5855 std::unique_ptr<ZONE> zone = std::make_unique<ZONE>( m_board );
5856
5857 zone->SetIsRuleArea( true );
5858
5859 HelperSetZoneLayers( *zone, aAltiumLayer );
5860 HelperSetZoneKeepoutRestrictions( *zone, restrictions );
5861
5862 aShape.EDA_SHAPE::TransformShapeToPolygon( *zone->Outline(), 0, ARC_HIGH_DEF, ERROR_INSIDE );
5863
5864 zone->SetBorderDisplayStyle( ZONE_BORDER_DISPLAY_STYLE::DIAGONAL_EDGE,
5866
5867 m_board->Add( zone.release(), ADD_MODE::APPEND );
5868}
5869
5870
5872 const PCB_SHAPE& aShape,
5873 const ALTIUM_LAYER aAltiumLayer,
5874 const uint8_t aKeepoutRestrictions )
5875{
5876 uint8_t restrictions = HelperGetKeepoutRestrictions( aKeepoutRestrictions, aAltiumLayer );
5877
5878 if( restrictions == 0 )
5879 return;
5880
5881 std::unique_ptr<ZONE> zone = std::make_unique<ZONE>( aFootprint );
5882
5883 zone->SetIsRuleArea( true );
5884
5885 HelperSetZoneLayers( *zone, aAltiumLayer );
5886 HelperSetZoneKeepoutRestrictions( *zone, restrictions );
5887
5888 aShape.EDA_SHAPE::TransformShapeToPolygon( *zone->Outline(), 0, ARC_HIGH_DEF, ERROR_INSIDE );
5889
5890 HelperFootprintZoneToLibFrame( *zone, *aFootprint );
5891
5892 zone->SetBorderDisplayStyle( ZONE_BORDER_DISPLAY_STYLE::DIAGONAL_EDGE,
5894
5895 aFootprint->Add( zone.release(), ADD_MODE::APPEND );
5896}
5897
5898
5899std::vector<std::pair<PCB_LAYER_ID, int>> ALTIUM_PCB::HelperGetSolderAndPasteMaskExpansions(
5900 const ALTIUM_RECORD aType, const int aPrimitiveIndex, const ALTIUM_LAYER aAltiumLayer )
5901{
5902 if( m_extendedPrimitiveInformationMaps.count( aType ) == 0 )
5903 return {}; // there is nothing to parse
5904
5905 auto elems = m_extendedPrimitiveInformationMaps[aType].equal_range( aPrimitiveIndex );
5906
5907 if( elems.first == elems.second )
5908 return {}; // there is nothing to parse
5909
5910 std::vector<std::pair<PCB_LAYER_ID, int>> layerExpansionPairs;
5911
5912 for( auto it = elems.first; it != elems.second; ++it )
5913 {
5914 const AEXTENDED_PRIMITIVE_INFORMATION& pInf = it->second;
5915
5917 {
5920 {
5921 // TODO: what layers can lead to solder or paste mask usage? E.g. KEEP_OUT_LAYER and other top/bottom layers
5922 if( aAltiumLayer == ALTIUM_LAYER::TOP_LAYER
5923 || aAltiumLayer == ALTIUM_LAYER::MULTI_LAYER )
5924 {
5925 layerExpansionPairs.emplace_back( F_Mask, pInf.soldermaskexpansionmanual );
5926 }
5927
5928 if( aAltiumLayer == ALTIUM_LAYER::BOTTOM_LAYER
5929 || aAltiumLayer == ALTIUM_LAYER::MULTI_LAYER )
5930 {
5931 layerExpansionPairs.emplace_back( B_Mask, pInf.soldermaskexpansionmanual );
5932 }
5933 }
5936 {
5937 if( aAltiumLayer == ALTIUM_LAYER::TOP_LAYER
5938 || aAltiumLayer == ALTIUM_LAYER::MULTI_LAYER )
5939 {
5940 layerExpansionPairs.emplace_back( F_Paste, pInf.pastemaskexpansionmanual );
5941 }
5942
5943 if( aAltiumLayer == ALTIUM_LAYER::BOTTOM_LAYER
5944 || aAltiumLayer == ALTIUM_LAYER::MULTI_LAYER )
5945 {
5946 layerExpansionPairs.emplace_back( B_Paste, pInf.pastemaskexpansionmanual );
5947 }
5948 }
5949 }
5950 }
5951
5952 return layerExpansionPairs;
5953}
const char * name
std::string FormatPath(const std::vector< std::string > &aVectorPath)
Helper for debug logging (vector -> string)
VECTOR2I altiumSlotDrillSize(uint32_t aHoleSize, uint32_t aSlotSize, double aSlotRotation, bool &aRotationSupported)
Convert an Altium slot's independent rotation and dimensions to KiCad's cardinal drill shape.
const ARULE6 * selectAltiumPolygonRule(const std::vector< ARULE6 > &aRulesByPriorityAsc)
Select the highest Altium-priority rule whose scope references polygons.
bool altiumViaSideIsTented(bool aTentFlag, bool aManual, bool aFromHole, uint32_t aHoleSize, int32_t aMaskExpansion, int aLandDiameter)
Decide whether one side of an Altium via should be tented when imported into KiCad.
const uint8_t ALTIUM_KEEPOUT_VIA
ALTIUM_TEXT_POSITION
const uint8_t ALTIUM_KEEPOUT_COPPER
const uint8_t ALTIUM_KEEPOUT_SMD_PAD
ALTIUM_RULE_KIND
ALTIUM_PAD_SHAPE
const uint8_t ALTIUM_KEEPOUT_TH_PAD
const uint8_t ALTIUM_KEEPOUT_TRACK
const uint8_t ALTIUM_KEEPOUT_ALL
const uint16_t ALTIUM_NET_UNCONNECTED
const uint16_t ALTIUM_POLYGON_NONE
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)
constexpr int ALTIUM_PADSTACK_IDX_COUNT
constexpr int ALTIUM_BOTTOM_PADSTACK_IDX
static bool IsLayerNameAssembly(const wxString &aName)
void HelperShapeLineChainFromAltiumVertices(SHAPE_LINE_CHAIN &aLine, const std::vector< ALTIUM_VERTICE > &aVertices)
double normalizeAngleDegrees(double Angle, double aMin, double aMax)
Normalize angle to be aMin < angle <= aMax angle is in degrees.
constexpr double BOLD_FACTOR
constexpr int ALTIUM_TOP_PADSTACK_IDX
static void altiumCollectSchematicNetNames(const wxString &aFileName, std::map< wxString, wxString > &aNames, std::set< wxString > &aAmbiguous)
static bool GetAltiumNetclassScopeName(const ARULE6 &aRule, wxString *aNetclassName)
wxString AltiumUnnamedNetName(const BOARD &aBoard, int &aCounter)
Invent a placeholder name for an Altium net that has none.
constexpr int ALTIUM_MID2_PADSTACK_IDX
static bool IsAltiumScopeAll(const wxString &aExpr)
static bool IsLayerNameCourtyard(const wxString &aName)
bool IsAltiumLayerCopper(ALTIUM_LAYER aLayer)
void ApplyAltiumNetclassRules(const std::map< ALTIUM_RULE_KIND, std::vector< ARULE6 > > &aRulesByKind, NET_SETTINGS &aNetSettings, std::vector< const ARULE6 * > *aUnresolved)
Copy the values of Altium rules scoped to a single netclass onto that netclass.
bool IsAltiumLayerAPlane(ALTIUM_LAYER aLayer)
static bool IsLayerNameTopSide(const wxString &aName)
constexpr int ALTIUM_MID1_PADSTACK_IDX
void ApplyAltiumNetclassRules(const std::map< ALTIUM_RULE_KIND, std::vector< ARULE6 > > &aRulesByKind, NET_SETTINGS &aNetSettings, std::vector< const ARULE6 * > *aUnresolved=nullptr)
Copy the values of Altium rules scoped to a single netclass onto that netclass.
wxString AltiumUnnamedNetName(const BOARD &aBoard, int &aCounter)
Invent a placeholder name for an Altium net that has none.
ALTIUM_PCB_DIR
Definition altium_pcb.h:38
@ EXTENDPRIMITIVEINFORMATION
Definition altium_pcb.h:56
std::function< void(const ALTIUM_PCB_COMPOUND_FILE &, const CFB::COMPOUND_FILE_ENTRY *)> PARSE_FUNCTION_POINTER_fp
Definition altium_pcb.h:149
KIID AltiumUniqueIdToKiid(const wxString &aUniqueId)
Derive a stable KIID from an Altium component unique id.
@ ERROR_INSIDE
constexpr int ARC_HIGH_DEF
Definition base_units.h:137
constexpr EDA_IU_SCALE pcbIUScale
Definition base_units.h:121
LAYER_T
The allowed types of layers, same as Specctra DSN spec.
Definition board.h:241
@ LT_POWER
Definition board.h:244
@ LT_MIXED
Definition board.h:245
@ LT_JUMPER
Definition board.h:246
@ LT_SIGNAL
Definition board.h:243
#define DEFAULT_BOARD_THICKNESS_MM
@ BS_ITEM_TYPE_COPPER
@ BS_ITEM_TYPE_DIELECTRIC
BOX2< VECTOR2I > BOX2I
Definition box2.h:927
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:995
static const ADVANCED_CFG & GetCfg()
Get the singleton instance's config, which is shared by all consumers.
std::map< wxString, wxString > ReadProperties()
std::map< uint32_t, wxString > ReadWideStringTable()
std::map< wxString, wxString > ReadProperties(std::function< std::map< wxString, wxString >(const std::string &)> handleBinaryData=[](const std::string &) { return std::map< wxString, wxString >();})
const CFB::CompoundFileReader & GetCompoundFileReader() const
const CFB::COMPOUND_FILE_ENTRY * FindStream(const std::vector< std::string > &aStreamPath) const
const std::pair< AMODEL, std::vector< char > > * GetLibModel(const wxString &aModelID) const
std::tuple< wxString, const CFB::COMPOUND_FILE_ENTRY * > FindLibFootprintDirName(const wxString &aFpUnicodeName)
PROGRESS_REPORTER * m_progressReporter
optional; may be nullptr
Definition altium_pcb.h:376
std::shared_ptr< EMBEDDED_FILES::EMBEDDED_FILE > HelperEmbedModel(FOOTPRINT *aFootprint, const wxString &aModelName, const std::vector< char > &aCompressedData, bool &aIsNew)
Return the 3D model aModelName embedded in aFootprint, inflating and embedding aCompressedData first ...
void ParseClasses6Data(const ALTIUM_PCB_COMPOUND_FILE &aAltiumPcbFile, const CFB::COMPOUND_FILE_ENTRY *aEntry)
std::vector< PCB_DIM_RADIAL * > m_radialDimensions
Definition altium_pcb.h:355
const ARULE6 * GetRuleForPolygon(ALTIUM_RULE_KIND aKind) const
void ConvertArcs6ToFootprintItemOnLayer(FOOTPRINT *aFootprint, const AARC6 &aElem, PCB_LAYER_ID aLayer)
void ConvertTracks6ToBoardItem(const ATRACK6 &aElem, const int aPrimitiveIndex)
void ConvertTracks6ToFootprintItem(FOOTPRINT *aFootprint, const ATRACK6 &aElem, const int aPrimitiveIndex, const bool aIsBoardImport)
int m_highest_pour_index
Altium stores pour order across all layers.
Definition altium_pcb.h:386
void HelperCreateTuningPatterns()
void ConvertTexts6ToFootprintItemOnLayer(FOOTPRINT *aFootprint, const ATEXT6 &aElem, PCB_LAYER_ID aLayer)
std::map< ALTIUM_LAYER, PCB_LAYER_ID > m_layermap
Definition altium_pcb.h:359
void ParseVias6Data(const ALTIUM_PCB_COMPOUND_FILE &aAltiumPcbFile, const CFB::COMPOUND_FILE_ENTRY *aEntry)
void HelperParseDimensions6Leader(const ADIMENSION6 &aElem)
wxString m_footprintName
for footprint library loading error reporting
Definition altium_pcb.h:383
std::vector< FOOTPRINT * > m_components
Definition altium_pcb.h:353
void HelperFillMechanicalLayerAssignments(const std::vector< ABOARD6_LAYER_STACKUP > &aStackup)
void ParseShapeBasedRegions6Data(const ALTIUM_PCB_COMPOUND_FILE &aAltiumPcbFile, const CFB::COMPOUND_FILE_ENTRY *aEntry)
const ARULE6 * GetRuleDefault(ALTIUM_RULE_KIND aKind) const
void HelperParsePad6NonCopper(const APAD6 &aElem, PCB_LAYER_ID aLayer, PCB_SHAPE *aShape)
void ParseRegions6Data(const ALTIUM_PCB_COMPOUND_FILE &aAltiumPcbFile, const CFB::COMPOUND_FILE_ENTRY *aEntry)
std::vector< PCB_LAYER_ID > GetKicadLayersToIterate(ALTIUM_LAYER aAltiumLayer) const
void ConvertShapeBasedRegions6ToFootprintItem(FOOTPRINT *aFootprint, const AREGION6 &aElem, const int aPrimitiveIndex)
void HelperPcpShapeAsFootprintKeepoutRegion(FOOTPRINT *aFootprint, const PCB_SHAPE &aShape, const ALTIUM_LAYER aAltiumLayer, const uint8_t aKeepoutRestrictions)
void ConvertArcs6ToBoardItem(const AARC6 &aElem, const int aPrimitiveIndex)
void ConvertShapeBasedRegions6ToFootprintItemOnLayer(FOOTPRINT *aFootprint, const AREGION6 &aElem, PCB_LAYER_ID aLayer, const int aPrimitiveIndex)
std::map< ALTIUM_LAYER, ZONE * > m_outer_plane
Definition altium_pcb.h:372
std::vector< int > m_altiumToKicadNetcodes
Definition altium_pcb.h:357
std::map< uint32_t, wxString > m_unionNames
Definition altium_pcb.h:369
unsigned m_totalCount
for progress reporting
Definition altium_pcb.h:380
void ParseComponents6Data(const ALTIUM_PCB_COMPOUND_FILE &aAltiumPcbFile, const CFB::COMPOUND_FILE_ENTRY *aEntry)
void HelperPcpShapeAsBoardKeepoutRegion(const PCB_SHAPE &aShape, const ALTIUM_LAYER aAltiumLayer, const uint8_t aKeepoutRestrictions)
std::map< wxString, ALTIUM_EMBEDDED_MODEL_DATA > m_EmbeddedModels
Definition altium_pcb.h:363
void ConvertFills6ToBoardItemOnLayer(const AFILL6 &aElem, PCB_LAYER_ID aLayer)
std::vector< std::pair< PCB_LAYER_ID, int > > HelperGetSolderAndPasteMaskExpansions(const ALTIUM_RECORD aType, const int aPrimitiveIndex, const ALTIUM_LAYER aAltiumLayer)
void ConvertBarcodes6ToBoardItemOnLayer(const ATEXT6 &aElem, PCB_LAYER_ID aLayer)
void ConvertComponentBody6ToFootprintItem(const ALTIUM_PCB_COMPOUND_FILE &aAltiumPcbFile, FOOTPRINT *aFootprint, const ACOMPONENTBODY6 &aElem)
void HelperSetTextAlignmentAndPos(const ATEXT6 &aElem, EDA_TEXT *aEdaText)
void ParseBoard6Data(const ALTIUM_PCB_COMPOUND_FILE &aAltiumPcbFile, const CFB::COMPOUND_FILE_ENTRY *aEntry)
void ConvertFills6ToFootprintItem(FOOTPRINT *aFootprint, const AFILL6 &aElem, const bool aIsBoardImport)
void ParseExtendedPrimitiveInformationData(const ALTIUM_PCB_COMPOUND_FILE &aAltiumPcbFile, const CFB::COMPOUND_FILE_ENTRY *aEntry)
void ParseFileHeader(const ALTIUM_PCB_COMPOUND_FILE &aAltiumPcbFile, const CFB::COMPOUND_FILE_ENTRY *aEntry)
void HelperCreateBoardOutline(const std::vector< ALTIUM_VERTICE > &aVertices)
std::map< int, std::vector< BOARD_ITEM * > > m_unionToBoardItems
Definition altium_pcb.h:370
void HelperAssignNetclassesToNets()
Rebuild the composite netclasses and point every net at its effective netclass.
std::map< ALTIUM_RULE_KIND, std::vector< ARULE6 > > m_rules
Definition altium_pcb.h:364
void ConvertVias6ToFootprintItem(FOOTPRINT *aFootprint, const AVIA6 &aElem)
void ParseRules6Data(const ALTIUM_PCB_COMPOUND_FILE &aAltiumPcbFile, const CFB::COMPOUND_FILE_ENTRY *aEntry)
void MapSchematicNetNames(const std::map< std::string, UTF8 > &aProperties)
void HelperSetZoneKeepoutRestrictions(ZONE &aZone, const uint8_t aKeepoutRestrictions)
unsigned m_doneCount
Definition altium_pcb.h:378
void HelperParseDimensions6Linear(const ADIMENSION6 &aElem)
void ParseTracks6Data(const ALTIUM_PCB_COMPOUND_FILE &aAltiumPcbFile, const CFB::COMPOUND_FILE_ENTRY *aEntry)
std::map< uint32_t, wxString > m_unicodeStrings
Definition altium_pcb.h:356
void ConvertTexts6ToBoardItem(const ATEXT6 &aElem)
void HelperParseDimensions6Center(const ADIMENSION6 &aElem)
void HelperParseDimensions6Radial(const ADIMENSION6 &aElem)
void ParseUnionNamesData(const ALTIUM_PCB_COMPOUND_FILE &aAltiumPcbFile, const CFB::COMPOUND_FILE_ENTRY *aEntry)
BOARD * m_board
Definition altium_pcb.h:352
void ParseBoardRegionsData(const ALTIUM_PCB_COMPOUND_FILE &aAltiumPcbFile, const CFB::COMPOUND_FILE_ENTRY *aEntry)
void ParseArcs6Data(const ALTIUM_PCB_COMPOUND_FILE &aAltiumPcbFile, const CFB::COMPOUND_FILE_ENTRY *aEntry)
void ConvertTexts6ToBoardItemOnLayer(const ATEXT6 &aElem, PCB_LAYER_ID aLayer)
void ConvertPads6ToFootprintItemOnCopper(FOOTPRINT *aFootprint, const APAD6 &aElem)
std::unique_ptr< FOOTPRINT > ParseFootprint(ALTIUM_PCB_COMPOUND_FILE &altiumLibFile, const wxString &aFootprintName)
REPORTER * m_reporter
optional; may be nullptr
Definition altium_pcb.h:377
int GetNetCode(uint16_t aId) const
void ConvertTexts6ToEdaTextSettings(const ATEXT6 &aElem, EDA_TEXT &aEdaText)
void HelperBuildPadstackLayerIndex()
wxString m_library
for footprint library loading error reporting
Definition altium_pcb.h:382
void ConvertPads6ToFootprintItemOnNonCopper(FOOTPRINT *aFootprint, const APAD6 &aElem)
std::map< wxString, wxString > m_schematicNetNames
Definition altium_pcb.h:358
void ConvertShapeBasedRegions6ToBoardItemOnLayer(const AREGION6 &aElem, PCB_LAYER_ID aLayer, const int aPrimitiveIndex)
void ConvertTexts6ToFootprintItem(FOOTPRINT *aFootprint, const ATEXT6 &aElem)
unsigned m_lastProgressCount
Definition altium_pcb.h:379
void ParseFills6Data(const ALTIUM_PCB_COMPOUND_FILE &aAltiumPcbFile, const CFB::COMPOUND_FILE_ENTRY *aEntry)
uint8_t HelperGetKeepoutRestrictions(const uint8_t aKeepoutRestrictions, const ALTIUM_LAYER aAltiumLayer)
Resolve an Altium keepout restriction mask, substituting the restrictions implied by the layer when t...
void ConvertArcs6ToFootprintItem(FOOTPRINT *aFootprint, const AARC6 &aElem, const int aPrimitiveIndex, const bool aIsBoardImport)
void HelperParseDimensions6Datum(const ADIMENSION6 &aElem)
void ConvertPads6ToBoardItem(const APAD6 &aElem)
void ConvertFills6ToFootprintItemOnLayer(FOOTPRINT *aFootprint, const AFILL6 &aElem, PCB_LAYER_ID aLayer)
void ParseWideStrings6Data(const ALTIUM_PCB_COMPOUND_FILE &aAltiumPcbFile, const CFB::COMPOUND_FILE_ENTRY *aEntry)
void remapUnsureLayers(std::vector< ABOARD6_LAYER_STACKUP > &aStackup)
std::vector< ASMARTUNION6 > m_tuningUnions
Definition altium_pcb.h:368
void HelperSetFootprintMountingStyles()
std::vector< ZONE * > m_polygons
Definition altium_pcb.h:354
void ParsePads6Data(const ALTIUM_PCB_COMPOUND_FILE &aAltiumPcbFile, const CFB::COMPOUND_FILE_ENTRY *aEntry)
void ConvertArcs6ToPcbShape(const AARC6 &aElem, PCB_SHAPE *aShape)
void ConvertTracks6ToBoardItemOnLayer(const ATRACK6 &aElem, PCB_LAYER_ID aLayer)
void ConvertFills6ToBoardItem(const AFILL6 &aElem)
std::map< PCB_LAYER_ID, int > m_padstackLayerIndex
Definition altium_pcb.h:361
void Parse(const ALTIUM_PCB_COMPOUND_FILE &aAltiumPcbFile, const std::map< ALTIUM_PCB_DIR, std::string > &aFileMapping, const std::map< std::string, UTF8 > *aProperties=nullptr)
FOOTPRINT * HelperGetFootprint(uint16_t aComponent) const
void HelperFootprintZoneToLibFrame(ZONE &aZone, const FOOTPRINT &aFootprint)
LAYER_MAPPING_HANDLER m_layerMappingHandler
Definition altium_pcb.h:374
void ConvertShapeBasedRegions6ToBoardItem(const AREGION6 &aElem, const int aPrimitiveIndex)
void ParsePolygons6Data(const ALTIUM_PCB_COMPOUND_FILE &aAltiumPcbFile, const CFB::COMPOUND_FILE_ENTRY *aEntry)
PCB_LAYER_ID GetKicadLayer(ALTIUM_LAYER aAltiumLayer) const
int HelperGetPadstackLayerIndex(PCB_LAYER_ID aLayer) const
Return the slot aLayer occupies in an Altium padstack's per-layer arrays.
void checkpoint()
void ParseComponentsBodies6Data(const ALTIUM_PCB_COMPOUND_FILE &aAltiumPcbFile, const CFB::COMPOUND_FILE_ENTRY *aEntry)
void ConvertTracks6ToFootprintItemOnLayer(FOOTPRINT *aFootprint, const ATRACK6 &aElem, PCB_LAYER_ID aLayer)
void ParseSmartUnions6Data(const ALTIUM_PCB_COMPOUND_FILE &aAltiumPcbFile, const CFB::COMPOUND_FILE_ENTRY *aEntry)
wxString SchematicCasedNetName(const wxString &aNetName) const
ALTIUM_PCB(BOARD *aBoard, PROGRESS_REPORTER *aProgressReporter, LAYER_MAPPING_HANDLER &aLayerMappingHandler, REPORTER *aReporter=nullptr, const wxString &aLibrary=wxEmptyString, const wxString &aFootprintName=wxEmptyString)
void ConvertArcs6ToBoardItemOnLayer(const AARC6 &aElem, PCB_LAYER_ID aLayer)
void ParseTexts6Data(const ALTIUM_PCB_COMPOUND_FILE &aAltiumPcbFile, const CFB::COMPOUND_FILE_ENTRY *aEntry)
std::map< ALTIUM_RECORD, std::multimap< int, const AEXTENDED_PRIMITIVE_INFORMATION > > m_extendedPrimitiveInformationMaps
Definition altium_pcb.h:366
void ParseModelsData(const ALTIUM_PCB_COMPOUND_FILE &aAltiumPcbFile, const CFB::COMPOUND_FILE_ENTRY *aEntry, const std::vector< std::string > &aRootDir)
std::map< ALTIUM_LAYER, wxString > m_layerNames
Definition altium_pcb.h:360
void ConvertPads6ToBoardItemOnNonCopper(const APAD6 &aElem)
void HelperSetTextboxAlignmentAndPos(const ATEXT6 &aElem, PCB_TEXTBOX *aPcbTextbox)
void ConvertBarcodes6ToFootprintItemOnLayer(FOOTPRINT *aFootprint, const ATEXT6 &aElem, PCB_LAYER_ID aLayer)
void ParseNets6Data(const ALTIUM_PCB_COMPOUND_FILE &aAltiumPcbFile, const CFB::COMPOUND_FILE_ENTRY *aEntry)
void ConvertPads6ToFootprintItem(FOOTPRINT *aFootprint, const APAD6 &aElem)
const ARULE6 * GetRule(ALTIUM_RULE_KIND aKind, const wxString &aName) const
void ParseDimensions6Data(const ALTIUM_PCB_COMPOUND_FILE &aAltiumPcbFile, const CFB::COMPOUND_FILE_ENTRY *aEntry)
void HelperSetZoneLayers(ZONE &aZone, const ALTIUM_LAYER aAltiumLayer)
static int ReadInt(const std::map< wxString, wxString > &aProps, const wxString &aKey, int aDefault)
static wxString ReadString(const std::map< wxString, wxString > &aProps, const wxString &aKey, const wxString &aDefault)
BASE_SET & set(size_t pos)
Definition base_set.h:126
A base class derived from BOARD_ITEM for items that can be connected and have a net,...
Container for design settings for a BOARD object.
void SetGridOrigin(const VECTOR2I &aOrigin)
const VECTOR2I & GetGridOrigin() const
void SetAuxOrigin(const VECTOR2I &aOrigin)
const VECTOR2I & GetAuxOrigin() const
BOARD_STACKUP & GetStackupDescriptor()
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition board_item.h:84
virtual void SetIsKnockout(bool aKnockout)
Definition board_item.h:414
virtual void SetLayer(PCB_LAYER_ID aLayer)
Set the layer this item is on.
Definition board_item.h:374
virtual LSET BoardLayerSet() const
Return the LSET for the board that this item resides on.
Manage layers needed to make a physical board.
void RemoveAll()
Delete all items in list and clear the list.
const std::vector< BOARD_STACKUP_ITEM * > & GetList() const
int BuildBoardThicknessFromStackup() const
void BuildDefaultStackupList(const BOARD_DESIGN_SETTINGS *aSettings, int aActiveCopperLayersCount=0)
Create a default stackup, according to the current BOARD_DESIGN_SETTINGS settings.
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:409
NETINFO_ITEM * FindNet(int aNetcode) const
Search for a net with the given netcode.
Definition board.cpp:2980
constexpr coord_type GetY() const
Definition box2.h:205
constexpr size_type GetWidth() const
Definition box2.h:211
constexpr coord_type GetX() const
Definition box2.h:204
constexpr size_type GetHeight() const
Definition box2.h:212
EDA_ANGLE Normalize()
Definition eda_angle.h:229
double Sin() const
Definition eda_angle.h:178
double AsDegrees() const
Definition eda_angle.h:116
bool IsCardinal() const
Definition eda_angle.cpp:40
bool IsCardinal90() const
Definition eda_angle.cpp:54
double Cos() const
Definition eda_angle.h:197
void SetCenter(const VECTOR2I &aCenter)
int GetRadius() const
SHAPE_T GetShape() const
Definition eda_shape.h:175
virtual void SetFilled(bool aFlag)
Definition eda_shape.h:142
void SetPolyPoints(const std::vector< VECTOR2I > &aPoints)
A mix-in class (via multiple inheritance) that handles texts such as labels, parts,...
Definition eda_text.h:94
virtual void SetTextSize(VECTOR2I aNewSize, bool aEnforceMinTextSize=true)
Definition eda_text.cpp:495
virtual void SetTextPos(const VECTOR2I &aPoint)
Definition eda_text.cpp:539
virtual int GetTextHeight() const
Definition eda_text.h:307
KIFONT::FONT * GetFont() const
Definition eda_text.h:286
void SetMirrored(bool isMirrored)
Definition eda_text.cpp:349
BOX2I GetTextBox(const RENDER_SETTINGS *aSettings, int aLine=-1) const
Useful in multiline texts to calculate the full text or a line area (for zones filling,...
Definition eda_text.cpp:737
void SetVertJustify(GR_TEXT_V_ALIGN_T aType)
Definition eda_text.cpp:373
virtual int GetTextWidth() const
Definition eda_text.h:304
void SetBoldFlag(bool aBold)
Set only the bold flag, without changing the font.
Definition eda_text.cpp:319
void MigrateLegacyBoldStrokeWidth()
Migrate a pre-v11 bold stroke text so its stored thickness holds the base (non-bold) width.
Definition eda_text.cpp:327
virtual void SetTextThickness(int aWidth)
The TextThickness is that set by the user.
Definition eda_text.cpp:245
int GetEffectiveTextPenWidth(int aDefaultPenWidth=0) const
The EffectiveTextPenWidth uses the text thickness if > 1 or aDefaultPenWidth.
Definition eda_text.cpp:422
GR_TEXT_V_ALIGN_T GetVertJustify() const
Definition eda_text.h:242
virtual void SetTextAngle(const EDA_ANGLE &aAngle)
Definition eda_text.cpp:263
void SetItalic(bool aItalic)
Set the text to be italic - this will also update the font if needed.
Definition eda_text.cpp:285
void SetFont(KIFONT::FONT *aFont)
Definition eda_text.cpp:458
void SetHorizJustify(GR_TEXT_H_ALIGN_T aType)
Definition eda_text.cpp:365
EMBEDDED_FILE * AddFile(const wxFileName &aName, bool aOverwrite)
Load a file from disk and adds it to the collection.
const std::map< wxString, std::shared_ptr< EMBEDDED_FILE > > & EmbeddedFileMap() const
Provide an iterable view of the file collection.
static RETURN_CODE CompressAndEncode(EMBEDDED_FILE &aFile)
Take data from the #decompressedData buffer and compresses it using ZSTD into the #compressedEncodedD...
EDA_ANGLE GetOrientation() const
Definition footprint.h:438
const TRANSFORM_TRS & GetTransform() const
Definition footprint.h:451
PCB_FIELD & Value()
read/write accessors:
Definition footprint.h:939
bool IsFlipped() const
Definition footprint.h:660
PCB_FIELD & Reference()
Definition footprint.h:940
void Add(BOARD_ITEM *aItem, ADD_MODE aMode=ADD_MODE::INSERT, bool aSkipConnectivity=false) override
Removes an item from the container.
std::vector< FP_3DMODEL > & Models()
Definition footprint.h:424
const wxString & GetReference() const
Definition footprint.h:901
EMBEDDED_FILES * GetEmbeddedFiles() override
Definition footprint.h:1389
VECTOR2I GetPosition() const override
Definition footprint.h:435
VECTOR3D m_Offset
3D model offset (mm)
Definition footprint.h:183
double m_Opacity
Definition footprint.h:184
VECTOR3D m_Rotation
3D model rotation (degrees)
Definition footprint.h:182
wxString m_Filename
The 3D shape filename in 3D library.
Definition footprint.h:185
PCB_GENERATOR * CreateFromType(const wxString &aTypeStr)
static GENERATORS_MGR & Instance()
FONT is an abstract base class for both outline and stroke fonts.
Definition font.h:94
static FONT * GetFont(const wxString &aFontName=wxEmptyString, bool aBold=false, bool aItalic=false, const std::vector< wxString > *aEmbeddedFiles=nullptr, bool aForDrawingSheet=false)
Definition font.cpp:143
virtual bool IsStroke() const
Definition font.h:101
const wxString & GetName() const
Definition font.h:112
virtual bool IsOutline() const
Definition font.h:102
Definition kiid.h:46
LAYER_RANGE_ITERATOR begin() const
A logical library item identifier and consists of various portions much like a URI.
Definition lib_id.h:45
LSET is a set of PCB_LAYER_IDs.
Definition lset.h:37
static const LSET & AllBoardTechMask()
Return a mask holding board technical layers (no CU layer) on both side.
Definition lset.cpp:679
static const LSET & AllCuMask()
return AllCuMask( MAX_CU_LAYERS );
Definition lset.cpp:604
static const LSET & UserMask()
Definition lset.cpp:686
static LSET UserDefinedLayersMask(int aUserDefinedLayerCount=MAX_USER_DEFINED_LAYERS)
Return a mask with the requested number of user defined layers.
Definition lset.cpp:700
static const LSET & InternalCuMask()
Return a complete set of internal copper layers which is all Cu layers except F_Cu and B_Cu.
Definition lset.cpp:573
Handle the data for a net.
Definition netinfo.h:50
int GetNetCode() const
Definition netinfo.h:104
static const int UNCONNECTED
Constant that holds the "unconnected net" number (typically 0) all items "connected" to this net are ...
Definition netinfo.h:280
NET_SETTINGS stores various net-related settings in a project context.
const std::map< wxString, std::shared_ptr< NETCLASS > > & GetNetclasses() const
Gets all netclasses.
A PADSTACK defines the characteristics of a single or multi-layer pad, in the IPC sense of the word.
Definition padstack.h:156
void SetRoundRectRadiusRatio(double aRatio, PCB_LAYER_ID aLayer)
Definition padstack.cpp:946
void SetMode(MODE aMode)
void SetChamferRatio(double aRatio, PCB_LAYER_ID aLayer)
Definition padstack.cpp:975
void SetOffset(const VECTOR2I &aOffset, PCB_LAYER_ID aLayer)
Definition padstack.cpp:910
void SetShape(PAD_SHAPE aShape, PCB_LAYER_ID aLayer)
Definition padstack.cpp:880
void SetChamferPositions(int aPositions, PCB_LAYER_ID aLayer)
Definition padstack.cpp:993
@ NORMAL
Shape is the same on all layers.
Definition padstack.h:170
@ CUSTOM
Shapes can be defined on arbitrary layers.
Definition padstack.h:172
@ FRONT_INNER_BACK
Up to three shapes can be defined (F_Cu, inner copper layers, B_Cu)
Definition padstack.h:171
void SetSize(const VECTOR2I &aSize, PCB_LAYER_ID aLayer)
Definition padstack.cpp:854
static constexpr PCB_LAYER_ID ALL_LAYERS
! The layer identifier to use for the single defintion on normal padstacks
Definition padstack.h:179
static constexpr PCB_LAYER_ID INNER_LAYERS
! The layer identifier to use for "inner layers" on top/inner/bottom padstacks
Definition padstack.h:182
Definition pad.h:61
static LSET PTHMask()
layer set for a through hole pad
Definition pad.cpp:606
static LSET UnplatedHoleMask()
layer set for a mechanical unplated through hole pad
Definition pad.cpp:627
static LSET SMDMask()
layer set for a SMD pad on Front layer
Definition pad.cpp:613
double GetRadius() const
virtual VECTOR2I GetCenter() const override
This defaults to the center of the bounding box if not overridden.
Definition pcb_track.h:294
A radial dimension indicates either the radius or diameter of an arc or circle.
void Rotate(const VECTOR2I &aRotCentre, const EDA_ANGLE &aAngle) override
Rotate this object.
void SetArcAngleAndEnd(const EDA_ANGLE &aAngle, bool aCheckNegativeAngle=false)
Definition pcb_shape.h:107
void SetShape(SHAPE_T aShape) override
Definition pcb_shape.h:207
void SetPosition(const VECTOR2I &aPos) override
Definition pcb_shape.h:75
void SetEnd(const VECTOR2I &aEnd) override
void SetLayer(PCB_LAYER_ID aLayer) override
Set the layer this item is on.
void SetStart(const VECTOR2I &aStart) override
void SetStroke(const STROKE_PARAMS &aStroke) override
VECTOR2I GetPosition() const override
Definition pcb_shape.h:76
void SetBorderEnabled(bool enabled)
void SetMarginTop(int aTop)
void SetMarginLeft(int aLeft)
void SetMarginBottom(int aBottom)
void SetTextAngle(const EDA_ANGLE &aAngle) override
void SetMarginRight(int aRight)
const VECTOR2I & GetStart() const
Definition pcb_track.h:93
const VECTOR2I & GetEnd() const
Definition pcb_track.h:90
A progress reporter interface for use in multi-threaded environments.
A pure virtual class used to derive REPORTER objects from.
Definition reporter.h:73
Definition seg.h:38
VECTOR2I A
Definition seg.h:45
ecoord SquaredDistance(const SEG &aSeg) const
Definition seg.cpp:76
VECTOR2I::extended_type ecoord
Definition seg.h:40
VECTOR2I B
Definition seg.h:46
OPT_VECTOR2I Intersect(const SEG &aSeg, bool aIgnoreEndpoints=false, bool aLines=false) const
Compute intersection point of segment (this) with segment aSeg.
Definition seg.cpp:442
const VECTOR2I & GetArcMid() const
Definition shape_arc.h:116
const VECTOR2I & GetP1() const
Definition shape_arc.h:115
const VECTOR2I & GetP0() const
Definition shape_arc.h:114
Represent a polyline containing arcs as well as line segments: A chain of connected line and/or arc s...
const SHAPE_ARC & Arc(size_t aArc) const
int Distance(const VECTOR2I &aP, bool aOutlineOnly) const
void SetClosed(bool aClosed)
Mark the line chain as closed (i.e.
int PointCount() const
Return the number of points (vertices) in this line chain.
ssize_t ArcIndex(size_t aSegment) const
Return the arc index for the given segment index.
SEG Segment(int aIndex) const
Return a copy of the aIndex-th segment in the line chain.
int NextShape(int aPointIndex) const
Return the vertex index of the next shape in the chain, or -1 if aPointIndex is the last shape.
void Append(int aX, int aY, bool aAllowDuplication=false)
Append a new point at the end of the line chain.
const VECTOR2I & CPoint(int aIndex) const
Return a reference to a given point in the line chain.
const VECTOR2I & CLastPoint() const
Return the last point in the line chain.
bool IsArcStart(size_t aIndex) const
Represent a set of closed polygons.
void Rotate(const EDA_ANGLE &aAngle, const VECTOR2I &aCenter={ 0, 0 }) override
Rotate all vertices by a given angle.
void BooleanAdd(const SHAPE_POLY_SET &b)
Perform boolean polyset union.
ITERATOR IterateWithHoles(int aOutline)
int AddOutline(const SHAPE_LINE_CHAIN &aOutline)
Adds a new outline to the set and returns its index.
void SetVertex(const VERTEX_INDEX &aIndex, const VECTOR2I &aPos)
Accessor function to set the position of a specific point.
void Inflate(int aAmount, CORNER_STRATEGY aCornerStrategy, int aMaxError, bool aSimplify=false)
Perform outline inflation/deflation.
int Append(int x, int y, int aOutline=-1, int aHole=-1, bool aAllowDuplication=false)
Appends a vertex at the end of the given outline/hole (default: the last outline)
int AddHole(const SHAPE_LINE_CHAIN &aHole, int aOutline=-1)
Adds a new hole to the given outline (default: last) and returns its index.
SHAPE_LINE_CHAIN & Outline(int aIndex)
Return the reference to aIndex-th outline in the set.
int NewOutline()
Creates a new empty polygon in the set and returns its index.
int OutlineCount() const
Return the number of outlines in the set.
void Move(const VECTOR2I &aVector) override
void Fracture(bool aSimplify=true)
Convert a set of polygons with holes to a single outline with "slits"/"fractures" connecting the oute...
Simple container to manage line stroke parameters.
VECTOR2I InverseApply(const VECTOR2I &aPoint) const
constexpr extended_type Cross(const VECTOR2< T > &aVector) const
Compute cross product of self with aVector.
Definition vector2d.h:534
double Distance(const VECTOR2< extended_type > &aVector) const
Compute the distance between two vectors.
Definition vector2d.h:549
T EuclideanNorm() const
Compute the Euclidean norm of the vector, which is defined as sqrt(x ** 2 + y ** 2).
Definition vector2d.h:279
constexpr VECTOR2< T > Perpendicular() const
Compute the perpendicular vector.
Definition vector2d.h:310
VECTOR2< T > Resize(T aNewLength) const
Return a vector of the same direction, but length specified in aNewLength.
Definition vector2d.h:381
Handle a list of polygons defining a copper zone.
Definition zone.h:70
void SetNeedRefill(bool aNeedRefill)
Definition zone.h:310
void SetDoNotAllowPads(bool aEnable)
Definition zone.h:826
SHAPE_POLY_SET * Outline()
Definition zone.h:418
SHAPE_POLY_SET * GetFill(PCB_LAYER_ID aLayer)
Definition zone.h:699
void SetDoNotAllowTracks(bool aEnable)
Definition zone.h:825
void SetFilledPolysList(PCB_LAYER_ID aLayer, const SHAPE_POLY_SET &aPolysList)
Set the list of filled polygons.
Definition zone.h:721
void SetIsFilled(bool isFilled)
Definition zone.h:307
bool HasFilledPolysForLayer(PCB_LAYER_ID aLayer) const
Definition zone.h:683
void SetLayerSet(const LSET &aLayerSet) override
Definition zone.cpp:666
void SetDoNotAllowVias(bool aEnable)
Definition zone.h:824
void SetDoNotAllowFootprints(bool aEnable)
Definition zone.h:827
void SetDoNotAllowZoneFills(bool aEnable)
Definition zone.h:823
static int GetDefaultHatchPitch()
Definition zone.cpp:1617
@ CHAMFER_ACUTE_CORNERS
Acute angles are chamfered.
@ ROUND_ALL_CORNERS
All angles are rounded.
#define _(s)
static constexpr EDA_ANGLE ANGLE_0
Definition eda_angle.h:422
static constexpr EDA_ANGLE ANGLE_90
Definition eda_angle.h:424
@ DEGREES_T
Definition eda_angle.h:31
static constexpr EDA_ANGLE ANGLE_45
Definition eda_angle.h:423
@ SEGMENT
Definition eda_shape.h:56
@ RECTANGLE
Use RECTANGLE instead of RECT to avoid collision in a Windows header.
Definition eda_shape.h:57
@ FP_SMD
Definition footprint.h:86
@ FP_EXCLUDE_FROM_POS_FILES
Definition footprint.h:87
@ FP_BOARD_ONLY
Definition footprint.h:89
@ FP_EXCLUDE_FROM_BOM
Definition footprint.h:88
@ FP_THROUGH_HOLE
Definition footprint.h:85
bool m_ImportSkipComponentBodies
Skip importing component bodies when importing some format files, such as Altium.
#define THROW_IO_ERROR(msg)
macro which captures the "call site" values of FILE_, __FUNCTION & LINE
#define THROW_IO_ERRORF(msg,...)
#define THROW_IO_CANCELLED()
wxString LayerName(int aLayer)
Returns the default display name for a given layer.
Definition layer_id.cpp:31
#define MAX_USER_DEFINED_LAYERS
Definition layer_ids.h:173
bool IsCopperLayer(int aLayerId)
Test whether a layer is a copper layer.
Definition layer_ids.h:703
size_t CopperLayerToOrdinal(PCB_LAYER_ID aLayer)
Converts KiCad copper layer enum to an ordinal between the front and back layers.
Definition layer_ids.h:945
PCB_LAYER_ID
A quick note on layer IDs:
Definition layer_ids.h:56
@ User_16
Definition layer_ids.h:135
@ In22_Cu
Definition layer_ids.h:83
@ In11_Cu
Definition layer_ids.h:72
@ In29_Cu
Definition layer_ids.h:90
@ In30_Cu
Definition layer_ids.h:91
@ User_15
Definition layer_ids.h:134
@ User_8
Definition layer_ids.h:127
@ F_CrtYd
Definition layer_ids.h:112
@ User_11
Definition layer_ids.h:130
@ In17_Cu
Definition layer_ids.h:78
@ B_Adhes
Definition layer_ids.h:99
@ Edge_Cuts
Definition layer_ids.h:108
@ Dwgs_User
Definition layer_ids.h:103
@ F_Paste
Definition layer_ids.h:100
@ In9_Cu
Definition layer_ids.h:70
@ Cmts_User
Definition layer_ids.h:104
@ User_6
Definition layer_ids.h:125
@ User_7
Definition layer_ids.h:126
@ In19_Cu
Definition layer_ids.h:80
@ In7_Cu
Definition layer_ids.h:68
@ In28_Cu
Definition layer_ids.h:89
@ In26_Cu
Definition layer_ids.h:87
@ F_Adhes
Definition layer_ids.h:98
@ B_Mask
Definition layer_ids.h:94
@ B_Cu
Definition layer_ids.h:61
@ User_14
Definition layer_ids.h:133
@ User_5
Definition layer_ids.h:124
@ Eco1_User
Definition layer_ids.h:105
@ F_Mask
Definition layer_ids.h:93
@ In21_Cu
Definition layer_ids.h:82
@ In23_Cu
Definition layer_ids.h:84
@ B_Paste
Definition layer_ids.h:101
@ In15_Cu
Definition layer_ids.h:76
@ In2_Cu
Definition layer_ids.h:63
@ User_10
Definition layer_ids.h:129
@ User_9
Definition layer_ids.h:128
@ F_Fab
Definition layer_ids.h:115
@ In10_Cu
Definition layer_ids.h:71
@ Margin
Definition layer_ids.h:109
@ F_SilkS
Definition layer_ids.h:96
@ In4_Cu
Definition layer_ids.h:65
@ B_CrtYd
Definition layer_ids.h:111
@ UNDEFINED_LAYER
Definition layer_ids.h:57
@ In16_Cu
Definition layer_ids.h:77
@ In24_Cu
Definition layer_ids.h:85
@ In1_Cu
Definition layer_ids.h:62
@ User_3
Definition layer_ids.h:122
@ User_1
Definition layer_ids.h:120
@ User_12
Definition layer_ids.h:131
@ B_SilkS
Definition layer_ids.h:97
@ In13_Cu
Definition layer_ids.h:74
@ User_4
Definition layer_ids.h:123
@ In8_Cu
Definition layer_ids.h:69
@ In14_Cu
Definition layer_ids.h:75
@ User_13
Definition layer_ids.h:132
@ User_2
Definition layer_ids.h:121
@ In12_Cu
Definition layer_ids.h:73
@ In27_Cu
Definition layer_ids.h:88
@ In6_Cu
Definition layer_ids.h:67
@ In5_Cu
Definition layer_ids.h:66
@ In3_Cu
Definition layer_ids.h:64
@ In20_Cu
Definition layer_ids.h:81
@ F_Cu
Definition layer_ids.h:60
@ In18_Cu
Definition layer_ids.h:79
@ In25_Cu
Definition layer_ids.h:86
@ B_Fab
Definition layer_ids.h:114
const std::vector< uint8_t > COMPOUND_FILE_HEADER
Definition io_utils.cpp:30
bool fileHasBinaryHeader(const wxString &aFilePath, const std::vector< uint8_t > &aHeader, size_t aOffset)
Check if a file starts with a defined binary header.
Definition io_utils.cpp:60
void for_all_pairs(_InputIterator __first, _InputIterator __last, _Function __f)
Apply a function to every possible pair of elements of a sequence.
Definition kicad_algo.h:80
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:411
@ NPTH
like PAD_PTH, but not plated mechanical use only, no connection allowed
Definition padstack.h:102
@ SMD
Smd pad, appears on the solder paste layer (default)
Definition padstack.h:98
@ PTH
Plated through hole pad.
Definition padstack.h:97
@ CHAMFERED_RECT
Definition padstack.h:59
@ ROUNDRECT
Definition padstack.h:56
@ RECTANGLE
Definition padstack.h:53
BARCODE class definition.
@ INWARD
>--—<
DIM_PRECISION
LENGTH_TUNING_MODE
@ DIFF_PAIR
std::function< std::map< wxString, PCB_LAYER_ID >(const std::vector< INPUT_LAYER_DESC > &)> LAYER_MAPPING_HANDLER
Pointer to a function that takes a map of source and KiCad layers and returns a re-mapped version.
@ RPT_SEVERITY_WARNING
@ RPT_SEVERITY_ERROR
@ RPT_SEVERITY_DEBUG
@ RPT_SEVERITY_INFO
std::optional< VECTOR2I > OPT_VECTOR2I
Definition seg.h:35
wxString NotSpecifiedPrm()
double startangle
uint16_t component
uint32_t unionindex
uint32_t width
ALTIUM_LAYER layer
uint8_t keepoutrestrictions
uint16_t polygon
VECTOR2I center
uint32_t radius
uint16_t net
double endangle
VECTOR2I origin
std::vector< ABOARD6_LAYER_STACKUP > stackup
std::vector< ALTIUM_VERTICE > board_vertices
std::vector< wxString > names
ALTIUM_CLASS_KIND kind
wxString name
wxString sourceHierachicalPath
wxString sourcefootprintlibrary
ALTIUM_LAYER layer
wxString sourcedesignator
wxString sourceUniqueID
ALTIUM_UNIT textunit
uint32_t textlinewidth
ALTIUM_LAYER layer
std::vector< VECTOR2I > textPoint
ALTIUM_DIMENSION_KIND kind
std::vector< VECTOR2I > referencePoint
AEXTENDED_PRIMITIVE_INFORMATION_TYPE type
VECTOR2I pos2
ALTIUM_LAYER layer
VECTOR2I pos1
uint8_t keepoutrestrictions
uint16_t component
std::vector< ABOARD6_LAYER_STACKUP > stackup
std::vector< char > m_data
Definition altium_pcb.h:113
const VECTOR2I position
double z_offset
wxString id
wxString name
VECTOR3D rotation
wxString name
ALTIUM_PAD_SHAPE inner_shape[29]
ALTIUM_PAD_HOLE_SHAPE holeshape
ALTIUM_PAD_SHAPE_ALT alt_shape[32]
int32_t soldermaskexpansionmanual
uint16_t net
ALTIUM_LAYER layer
std::unique_ptr< APAD6_SIZE_AND_SHAPE > sizeAndShape
ALTIUM_PAD_SHAPE topshape
ALTIUM_PAD_MODE padmode
ALTIUM_MODE pastemaskexpansionmode
uint32_t holesize
double direction
ALTIUM_MODE soldermaskexpansionmode
wxString name
int32_t pad_to_die_delay
bool is_tent_bottom
VECTOR2I botsize
ALTIUM_PAD_SHAPE botshape
uint16_t component
VECTOR2I midsize
ALTIUM_PAD_SHAPE midshape
int32_t pastemaskexpansionmanual
VECTOR2I position
VECTOR2I topsize
int32_t pad_to_die_length
std::vector< ALTIUM_VERTICE > vertices
ALTIUM_POLYGON_HATCHSTYLE hatchstyle
ALTIUM_LAYER layer
uint8_t keepoutrestrictions
ALTIUM_LAYER layer
std::vector< ALTIUM_VERTICE > outline
std::vector< std::vector< ALTIUM_VERTICE > > holes
uint16_t component
uint16_t polygon
ALTIUM_REGION_KIND kind
ALTIUM_RULE_KIND kind
ALTIUM_CONNECT_STYLE polygonconnectStyle
wxString scope1expr
wxString name
int planeclearanceClearance
int32_t polygonconnectReliefconductorwidth
int pastemaskExpansion
wxString scope2expr
int soldermaskExpansion
int32_t polygonconnectAirgapwidth
uint32_t text_offset_width
VECTOR2I barcode_margin
uint32_t textbox_rect_height
uint16_t component
ALTIUM_TEXT_POSITION textbox_rect_justification
wxString text
uint32_t textbox_rect_width
double rotation
uint32_t margin_border_width
uint32_t height
wxString fontname
bool isJustificationValid
ALTIUM_BARCODE_TYPE barcode_type
ALTIUM_LAYER layer
VECTOR2I position
ALTIUM_TEXT_TYPE fonttype
bool isOffsetBorder
bool barcode_inverted
uint32_t strokewidth
uint32_t unionindex
uint32_t width
uint16_t polygon
uint8_t keepoutrestrictions
VECTOR2I start
ALTIUM_LAYER layer
uint16_t component
uint32_t diameter
uint16_t net
VECTOR2I position
bool soldermask_expansion_from_hole
int32_t soldermask_expansion_front
bool is_tent_bottom
bool soldermask_expansion_manual
ALTIUM_PAD_MODE viamode
int32_t soldermask_expansion_back
uint32_t diameter_by_layer[32]
ALTIUM_LAYER layer_start
ALTIUM_LAYER layer_end
uint32_t holesize
Describes an imported layer and how it could be mapped to KiCad Layers.
PCB_LAYER_ID AutoMapLayer
Best guess as to what the equivalent KiCad layer might be.
bool Required
Should we require the layer to be assigned?
LSET PermittedLayers
KiCad layers that the imported layer can be mapped onto.
wxString Name
Imported layer name as displayed in original application.
std::string path
KIBIS top(path, &reporter)
KIBIS_MODEL * model
VECTOR2I center
int radius
VECTOR2I end
int clearance
@ GR_TEXT_H_ALIGN_CENTER
@ GR_TEXT_H_ALIGN_RIGHT
@ GR_TEXT_H_ALIGN_LEFT
@ GR_TEXT_V_ALIGN_BOTTOM
@ GR_TEXT_V_ALIGN_CENTER
@ GR_TEXT_V_ALIGN_TOP
thread_pool & GetKiCadThreadPool()
Get a reference to the current thread pool.
static thread_pool * tp
BS::priority_thread_pool thread_pool
Definition thread_pool.h:27
void RotatePoint(int *pX, int *pY, const EDA_ANGLE &aAngle)
Calculate the new point of coord coord pX, pY, for a rotation center 0, 0.
Definition trigo.cpp:225
double DEG2RAD(double deg)
Definition trigo.h:172
@ PCB_SHAPE_T
class PCB_SHAPE, a segment not on copper layers
Definition typeinfo.h:80
@ PCB_DIM_ALIGNED_T
class PCB_DIM_ALIGNED, a linear dimension (graphic item)
Definition typeinfo.h:94
@ PCB_ARC_T
class PCB_ARC, an arc track segment on a copper layer
Definition typeinfo.h:90
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683
VECTOR3< double > VECTOR3D
Definition vector3.h:230
@ THERMAL
Use thermal relief for pads.
Definition zones.h:46
@ NONE
Pads are not covered.
Definition zones.h:45
@ FULL
pads are covered by copper
Definition zones.h:47