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