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>
35#include <footprint.h>
36#include <layer_range.h>
37#include <layer_utils.h>
38#include <pcb_dimension.h>
39#include <pad.h>
40#include <pcb_shape.h>
41#include <pcb_text.h>
42#include <pcb_textbox.h>
43#include <pcb_track.h>
44#include <pcb_barcode.h>
45#include <pcb_generator.h>
46#include <generators_mgr.h>
48#include <router/pns_meander.h>
51#include <core/profile.h>
52#include <string_utils.h>
53#include <tools/pad_tool.h>
54#include <zone.h>
55
57
58#include <cmath>
59#include <set>
60
61#include <advanced_config.h>
62#include <compoundfilereader.h>
64#include <font/outline_font.h>
65#include <project.h>
66#include <reporter.h>
67#include <trigo.h>
68#include <utf.h>
69#include <wx/docview.h>
70#include <wx/log.h>
71#include <wx/mstream.h>
72#include <wx/wfstream.h>
73#include <wx/zstream.h>
74#include <progress_reporter.h>
75#include <magic_enum.hpp>
76#include <thread_pool.h>
77
78#include <limits>
79#include <unordered_set>
80
81
82constexpr double BOLD_FACTOR = 1.75; // CSS font-weight-normal is 400; bold is 700
83
84// Slots in an Altium padstack's per-layer arrays, which run top, mid 1 through 30, bottom
85constexpr int ALTIUM_TOP_PADSTACK_IDX = 0;
86constexpr int ALTIUM_MID1_PADSTACK_IDX = 1;
87constexpr int ALTIUM_MID2_PADSTACK_IDX = 2;
88constexpr int ALTIUM_BOTTOM_PADSTACK_IDX = 31;
89constexpr int ALTIUM_PADSTACK_IDX_COUNT = 32;
90
91
93{
94 return ( aLayer >= ALTIUM_LAYER::TOP_LAYER && aLayer <= ALTIUM_LAYER::BOTTOM_LAYER )
95 || aLayer == ALTIUM_LAYER::MULTI_LAYER; // TODO: add IsAltiumLayerAPlane?
96}
97
98
100{
102}
103
104
105wxString AltiumUnnamedNetName( const BOARD& aBoard, int& aCounter )
106{
107 wxString name;
108
109 do
110 {
111 name = wxString::Format( wxT( "__ALTIUM_UNNAMED_NET_%d" ), ++aCounter );
112 } while( aBoard.FindNet( name ) );
113
114 return name;
115}
116
117
118// Altium scope expressions are not case sensitive, so neither is the unrestricted scope
119static bool IsAltiumScopeAll( const wxString& aExpr )
120{
121 return aExpr.IsSameAs( wxS( "All" ), false );
122}
123
124
125static bool GetAltiumNetclassScopeName( const ARULE6& aRule, wxString* aNetclassName )
126{
127 static const wxString prefix = wxT( "InNetClass('" );
128
129 if( !IsAltiumScopeAll( aRule.scope2expr ) || !aRule.scope1expr.StartsWith( prefix )
130 || !aRule.scope1expr.EndsWith( wxT( "')" ) ) )
131 {
132 return false;
133 }
134
135 *aNetclassName = aRule.scope1expr.Mid( prefix.Length(),
136 aRule.scope1expr.Length() - prefix.Length() - 2 );
137
138 return !aNetclassName->IsEmpty();
139}
140
141
142void ApplyAltiumNetclassRules( const std::map<ALTIUM_RULE_KIND, std::vector<ARULE6>>& aRulesByKind,
143 NET_SETTINGS& aNetSettings, std::vector<const ARULE6*>* aUnresolved )
144{
145 const std::map<wxString, std::shared_ptr<NETCLASS>>& netclasses = aNetSettings.GetNetclasses();
146
147 for( const auto& [kind, rules] : aRulesByKind )
148 {
151 {
152 continue;
153 }
154
155 std::set<wxString> applied;
156
157 for( const ARULE6& rule : rules )
158 {
159 wxString netclassName;
160
161 if( !rule.enabled || !GetAltiumNetclassScopeName( rule, &netclassName ) )
162 continue;
163
164 auto it = netclasses.find( netclassName );
165
166 if( it == netclasses.end() )
167 {
168 if( aUnresolved )
169 aUnresolved->push_back( &rule );
170
171 continue;
172 }
173
174 // rules are sorted by ascending Altium priority, so the first match is the winner
175 if( !applied.insert( netclassName ).second )
176 continue;
177
178 const std::shared_ptr<NETCLASS>& netclass = it->second;
179
180 switch( kind )
181 {
182 case ALTIUM_RULE_KIND::CLEARANCE: netclass->SetClearance( rule.clearanceGap ); break;
183
184 case ALTIUM_RULE_KIND::WIDTH: netclass->SetTrackWidth( rule.preferredWidth ); break;
185
187 netclass->SetViaDiameter( rule.width );
188 netclass->SetViaDrill( rule.holeWidth );
189 break;
190
191 default: break;
192 }
193 }
194 }
195}
196
197
198FOOTPRINT* ALTIUM_PCB::HelperGetFootprint( uint16_t aComponent ) const
199{
200 if( aComponent == ALTIUM_COMPONENT_NONE || m_components.size() <= aComponent )
201 {
202 THROW_IO_ERRORF( wxT( "Component creator tries to access component id %u of %u existing components" ),
203 (unsigned)aComponent, (unsigned)m_components.size() );
204 }
205
206 return m_components.at( aComponent );
207}
208
209
210std::shared_ptr<EMBEDDED_FILES::EMBEDDED_FILE>
211ALTIUM_PCB::HelperEmbedModel( FOOTPRINT* aFootprint, const wxString& aModelName,
212 const std::vector<char>& aCompressedData, bool& aIsNew )
213{
214 EMBEDDED_FILES* embeddedFiles = aFootprint->GetEmbeddedFiles();
215 const auto& files = embeddedFiles->EmbeddedFileMap();
216 auto it = files.find( aModelName );
217
218 aIsNew = it == files.end();
219
220 // Several bodies of one component routinely share a model, and inflating it per body would
221 // cost a full STEP decompression each time
222 if( !aIsNew )
223 return it->second;
224
225 auto file = std::make_shared<EMBEDDED_FILES::EMBEDDED_FILE>();
226 file->name = aModelName;
228
229 wxMemoryInputStream compressedStream( aCompressedData.data(), aCompressedData.size() );
230 wxZlibInputStream zlibStream( compressedStream );
231
232 // Altium compresses STEP at roughly 5:1, so guess high and double rather than reallocate
233 // on every read
234 file->decompressedData.resize( aCompressedData.size() * 6 );
235 size_t offset = 0;
236
237 while( !zlibStream.Eof() )
238 {
239 zlibStream.Read( file->decompressedData.data() + offset,
240 file->decompressedData.size() - offset );
241
242 size_t bytesRead = zlibStream.LastRead();
243
244 if( !bytesRead )
245 break;
246
247 offset += bytesRead;
248
249 if( offset >= file->decompressedData.size() )
250 file->decompressedData.resize( 2 * file->decompressedData.size() );
251 }
252
253 file->decompressedData.resize( offset );
254
255 // The guess above overshoots by up to 6x and resize() keeps the capacity, which the board
256 // would then hold for as long as it is open
257 file->decompressedData.shrink_to_fit();
258
259 embeddedFiles->AddFile( file );
260
261 return file;
262}
263
264
266 const std::vector<ALTIUM_VERTICE>& aVertices )
267{
268 for( const ALTIUM_VERTICE& vertex : aVertices )
269 {
270 if( vertex.isRound )
271 {
272 EDA_ANGLE angle( vertex.endangle - vertex.startangle, DEGREES_T );
273 angle.Normalize();
274
275 double startradiant = DEG2RAD( vertex.startangle );
276 double endradiant = DEG2RAD( vertex.endangle );
277 VECTOR2I arcStartOffset = KiROUND( std::cos( startradiant ) * vertex.radius,
278 -std::sin( startradiant ) * vertex.radius );
279
280 VECTOR2I arcEndOffset = KiROUND( std::cos( endradiant ) * vertex.radius,
281 -std::sin( endradiant ) * vertex.radius );
282
283 VECTOR2I arcStart = vertex.center + arcStartOffset;
284 VECTOR2I arcEnd = vertex.center + arcEndOffset;
285
286 bool isShort = arcStart.Distance( arcEnd ) < pcbIUScale.mmToIU( 0.001 )
287 || angle.AsDegrees() < 0.2;
288
289 if( arcStart.Distance( vertex.position )
290 < arcEnd.Distance( vertex.position ) )
291 {
292 if( !isShort )
293 {
294 aLine.Append( SHAPE_ARC( vertex.center, arcStart, -angle ) );
295 }
296 else
297 {
298 aLine.Append( arcStart );
299 aLine.Append( arcEnd );
300 }
301 }
302 else
303 {
304 if( !isShort )
305 {
306 aLine.Append( SHAPE_ARC( vertex.center, arcEnd, angle ) );
307 }
308 else
309 {
310 aLine.Append( arcEnd );
311 aLine.Append( arcStart );
312 }
313 }
314 }
315 else
316 {
317 aLine.Append( vertex.position );
318 }
319 }
320
321 aLine.SetClosed( true );
322}
323
324
326{
327 auto override = m_layermap.find( aAltiumLayer );
328
329 if( override != m_layermap.end() )
330 {
331 return override->second;
332 }
333
334 if( aAltiumLayer >= ALTIUM_LAYER::V7_MECHANICAL_17 && aAltiumLayer <= ALTIUM_LAYER::V7_MECHANICAL_LAST )
335 {
336 // Layer "Mechanical 17" would correspond to altiumOrd 16
337 int altiumOrd = static_cast<int>( aAltiumLayer ) - static_cast<int>( ALTIUM_LAYER::V7_MECHANICAL_1 );
338
339 if( ( altiumOrd + 1 ) > MAX_USER_DEFINED_LAYERS )
340 return UNDEFINED_LAYER;
341
342 // Convert to KiCad User_* layers
343 return static_cast<PCB_LAYER_ID>( static_cast<int>( User_1 ) + altiumOrd * 2 );
344 }
345
346 switch( aAltiumLayer )
347 {
349
350 case ALTIUM_LAYER::TOP_LAYER: return F_Cu;
381 case ALTIUM_LAYER::BOTTOM_LAYER: return B_Cu;
382
385 case ALTIUM_LAYER::TOP_PASTE: return F_Paste;
387 case ALTIUM_LAYER::TOP_SOLDER: return F_Mask;
389
406
409
426
437
438 default: return UNDEFINED_LAYER;
439 }
440}
441
442
443std::vector<PCB_LAYER_ID> ALTIUM_PCB::GetKicadLayersToIterate( ALTIUM_LAYER aAltiumLayer ) const
444{
445 if( aAltiumLayer == ALTIUM_LAYER::MULTI_LAYER || aAltiumLayer == ALTIUM_LAYER::KEEP_OUT_LAYER )
446 {
447 int layerCount = m_board ? m_board->GetCopperLayerCount() : 32;
448 std::vector<PCB_LAYER_ID> layers;
449 layers.reserve( layerCount );
450
451 for( PCB_LAYER_ID layer : LAYER_RANGE( F_Cu, B_Cu, layerCount ) )
452 {
453 if( !m_board || m_board->IsLayerEnabled( layer ) )
454 layers.emplace_back( layer );
455 }
456
457 return layers;
458 }
459
460 PCB_LAYER_ID klayer = GetKicadLayer( aAltiumLayer );
461
462 if( klayer == UNDEFINED_LAYER )
463 return {};
464
465 return { klayer };
466}
467
468
469ALTIUM_PCB::ALTIUM_PCB( BOARD* aBoard, PROGRESS_REPORTER* aProgressReporter,
470 LAYER_MAPPING_HANDLER& aHandler, REPORTER* aReporter,
471 const wxString& aLibrary, const wxString& aFootprintName )
472{
473 m_board = aBoard;
474 m_progressReporter = aProgressReporter;
475 m_layerMappingHandler = aHandler;
476 m_reporter = aReporter;
477 m_doneCount = 0;
479 m_totalCount = 0;
481 m_library = aLibrary;
482 m_footprintName = aFootprintName;
483}
484
488
490{
491 const unsigned PROGRESS_DELTA = 250;
492
494 {
495 if( ++m_doneCount > m_lastProgressCount + PROGRESS_DELTA )
496 {
497 m_progressReporter->SetCurrentProgress( (double) m_doneCount / std::max( 1U, m_totalCount ) );
498
499 if( !m_progressReporter->KeepRefreshing() )
501
503 }
504 }
505}
506
507// A courtyard has to be one closed outline, so open pieces on the layer, such as a centre cross,
508// are drawn on the fabrication layer of that side instead.
509static void MoveOpenCourtyardItems( BOARD& aBoard )
510{
511 // Ends that the courtyard builder chains together count as meeting
512 const int epsilon = pcbIUScale.mmToIU( 0.02 );
513
514 for( FOOTPRINT* footprint : aBoard.Footprints() )
515 {
516 for( PCB_LAYER_ID courtyard : { F_CrtYd, B_CrtYd } )
517 {
518 std::vector<PCB_SHAPE*> pieces;
519 std::vector<VECTOR2I> nodes;
520 std::vector<int> degree;
521
522 auto nodeAt =
523 [&]( const VECTOR2I& aPt )
524 {
525 for( size_t i = 0; i < nodes.size(); i++ )
526 {
527 if( ( nodes[i] - aPt ).EuclideanNorm() <= epsilon )
528 return static_cast<int>( i );
529 }
530
531 nodes.push_back( aPt );
532 degree.push_back( 0 );
533 return static_cast<int>( nodes.size() ) - 1;
534 };
535
536 std::vector<std::pair<int, int>> ends;
537
538 for( BOARD_ITEM* item : footprint->GraphicalItems() )
539 {
540 if( item->GetLayer() != courtyard || item->Type() != PCB_SHAPE_T )
541 continue;
542
543 PCB_SHAPE* shape = static_cast<PCB_SHAPE*>( item );
544
545 if( shape->GetShape() != SHAPE_T::SEGMENT && shape->GetShape() != SHAPE_T::ARC )
546 continue;
547
548 int a = nodeAt( shape->GetStart() );
549 int b = nodeAt( shape->GetEnd() );
550
551 // File coordinates drift by nanometres, so ends that nearly meet are made to meet
552 if( shape->GetShape() == SHAPE_T::SEGMENT )
553 {
554 const int snap = pcbIUScale.mmToIU( 0.001 );
555
556 if( ( nodes[a] - shape->GetStart() ).EuclideanNorm() <= snap )
557 shape->SetStart( nodes[a] );
558
559 if( ( nodes[b] - shape->GetEnd() ).EuclideanNorm() <= snap )
560 shape->SetEnd( nodes[b] );
561 }
562
563 degree[a]++;
564 degree[b]++;
565 pieces.push_back( shape );
566 ends.emplace_back( a, b );
567 }
568
569 // An edge that hangs off the outline, directly or through other hanging edges, is open
570 std::vector<bool> hanging( pieces.size(), false );
571 std::vector<int> open;
572
573 for( size_t i = 0; i < nodes.size(); i++ )
574 {
575 if( degree[i] == 1 )
576 open.push_back( static_cast<int>( i ) );
577 }
578
579 while( !open.empty() )
580 {
581 const int node = open.back();
582 open.pop_back();
583
584 for( size_t i = 0; i < pieces.size(); i++ )
585 {
586 if( hanging[i] || ( ends[i].first != node && ends[i].second != node ) )
587 continue;
588
589 hanging[i] = true;
590 degree[ends[i].first]--;
591 degree[ends[i].second]--;
592
593 const int far_idx = ends[i].first == node ? ends[i].second : ends[i].first;
594
595 if( degree[far_idx] == 1 )
596 open.push_back( far_idx );
597 }
598 }
599
600 // More than the two lines of a centre mark and nothing closed is an open courtyard, which
601 // stays on the layer for DRC to report
602 const bool anyClosed =
603 std::any_of( hanging.begin(), hanging.end(), []( bool aHanging ) { return !aHanging; } );
604
605 if( !anyClosed && pieces.size() > 2 )
606 continue;
607
608 for( size_t i = 0; i < pieces.size(); i++ )
609 {
610 if( hanging[i] )
611 pieces[i]->SetLayer( courtyard == F_CrtYd ? F_Fab : B_Fab );
612 }
613 }
614 }
615}
616
617
619 const std::map<ALTIUM_PCB_DIR, std::string>& aFileMapping,
620 const std::map<std::string, UTF8>* aProperties )
621{
622 if( aProperties )
623 MapSchematicNetNames( *aProperties );
624
625 // this vector simply declares in which order which functions to call.
626 const std::vector<std::tuple<bool, ALTIUM_PCB_DIR, PARSE_FUNCTION_POINTER_fp>> parserOrder = {
628 [this]( const ALTIUM_PCB_COMPOUND_FILE& aFile, auto fileHeader )
629 {
630 this->ParseFileHeader( aFile, fileHeader );
631 } },
633 [this]( const ALTIUM_PCB_COMPOUND_FILE& aFile, auto fileHeader )
634 {
635 this->ParseBoard6Data( aFile, fileHeader );
636 } },
638 [this]( const ALTIUM_PCB_COMPOUND_FILE& aFile, auto fileHeader )
639 {
640 this->ParseExtendedPrimitiveInformationData( aFile, fileHeader );
641 } },
643 [this]( const ALTIUM_PCB_COMPOUND_FILE& aFile, auto fileHeader )
644 {
645 this->ParseComponents6Data( aFile, fileHeader );
646 } },
647 { false, ALTIUM_PCB_DIR::MODELS,
648 [this, aFileMapping]( const ALTIUM_PCB_COMPOUND_FILE& aFile, auto fileHeader )
649 {
650 std::vector<std::string> dir{ aFileMapping.at( ALTIUM_PCB_DIR::MODELS ) };
651 this->ParseModelsData( aFile, fileHeader, dir );
652 } },
654 [this]( const ALTIUM_PCB_COMPOUND_FILE& aFile, auto fileHeader )
655 {
656 this->ParseComponentsBodies6Data( aFile, fileHeader );
657 } },
658 { true, ALTIUM_PCB_DIR::NETS6,
659 [this]( const ALTIUM_PCB_COMPOUND_FILE& aFile, auto fileHeader )
660 {
661 this->ParseNets6Data( aFile, fileHeader );
662 } },
664 [this]( const ALTIUM_PCB_COMPOUND_FILE& aFile, auto fileHeader )
665 {
666 this->ParseClasses6Data( aFile, fileHeader );
667 } },
669 [this]( const ALTIUM_PCB_COMPOUND_FILE& aFile, auto fileHeader )
670 {
671 this->ParseRules6Data( aFile, fileHeader );
672 } },
674 [this]( const ALTIUM_PCB_COMPOUND_FILE& aFile, auto fileHeader )
675 {
676 this->ParseDimensions6Data( aFile, fileHeader );
677 } },
679 [this]( const ALTIUM_PCB_COMPOUND_FILE& aFile, auto fileHeader )
680 {
681 this->ParsePolygons6Data( aFile, fileHeader );
682 } },
683 { true, ALTIUM_PCB_DIR::ARCS6,
684 [this]( const ALTIUM_PCB_COMPOUND_FILE& aFile, auto fileHeader )
685 {
686 this->ParseArcs6Data( aFile, fileHeader );
687 } },
688 { true, ALTIUM_PCB_DIR::PADS6,
689 [this]( const ALTIUM_PCB_COMPOUND_FILE& aFile, auto fileHeader )
690 {
691 this->ParsePads6Data( aFile, fileHeader );
692 } },
693 { true, ALTIUM_PCB_DIR::VIAS6,
694 [this]( const ALTIUM_PCB_COMPOUND_FILE& aFile, auto fileHeader )
695 {
696 this->ParseVias6Data( aFile, fileHeader );
697 } },
699 [this]( const ALTIUM_PCB_COMPOUND_FILE& aFile, auto fileHeader )
700 {
701 this->ParseTracks6Data( aFile, fileHeader );
702 } },
704 [this]( const ALTIUM_PCB_COMPOUND_FILE& aFile, auto fileHeader )
705 {
706 this->ParseSmartUnions6Data( aFile, fileHeader );
707 } },
709 [this]( const ALTIUM_PCB_COMPOUND_FILE& aFile, auto fileHeader )
710 {
711 this->ParseUnionNamesData( aFile, fileHeader );
712 } },
714 [this]( const ALTIUM_PCB_COMPOUND_FILE& aFile, auto fileHeader )
715 {
716 this->ParseWideStrings6Data( aFile, fileHeader );
717 } },
719 [this]( const ALTIUM_PCB_COMPOUND_FILE& aFile, auto fileHeader )
720 {
721 this->ParseTexts6Data( aFile, fileHeader );
722 } },
724 [this]( const ALTIUM_PCB_COMPOUND_FILE& aFile, auto fileHeader )
725 {
726 this->ParseFills6Data( aFile, fileHeader );
727 } },
729 [this]( const ALTIUM_PCB_COMPOUND_FILE& aFile, auto fileHeader )
730 {
731 this->ParseBoardRegionsData( aFile, fileHeader );
732 } },
734 [this]( const ALTIUM_PCB_COMPOUND_FILE& aFile, auto fileHeader )
735 {
736 this->ParseShapeBasedRegions6Data( aFile, fileHeader );
737 } },
739 [this]( const ALTIUM_PCB_COMPOUND_FILE& aFile, auto fileHeader )
740 {
741 this->ParseRegions6Data( aFile, fileHeader );
742 } }
743 };
744
745 if( m_progressReporter != nullptr )
746 {
747 // Count number of records we will read for the progress reporter
748 for( const std::tuple<bool, ALTIUM_PCB_DIR, PARSE_FUNCTION_POINTER_fp>& cur : parserOrder )
749 {
750 bool isRequired;
753 std::tie( isRequired, directory, fp ) = cur;
754
756 continue;
757
758 const auto& mappedDirectory = aFileMapping.find( directory );
759
760 if( mappedDirectory == aFileMapping.end() )
761 continue;
762
763 const std::vector<std::string> mappedFile{ mappedDirectory->second, "Header" };
764 const CFB::COMPOUND_FILE_ENTRY* file = altiumPcbFile.FindStream( mappedFile );
765
766 if( file == nullptr )
767 continue;
768
769 ALTIUM_BINARY_PARSER reader( altiumPcbFile, file );
770 uint32_t numOfRecords = reader.Read<uint32_t>();
771
772 if( reader.HasParsingError() )
773 {
774 if( m_reporter )
775 {
776 m_reporter->Report( wxString::Format( _( "'%s' was not parsed correctly." ),
777 FormatPath( mappedFile ) ),
779 }
780
781 continue;
782 }
783
784 m_totalCount += numOfRecords;
785
786 if( reader.GetRemainingBytes() != 0 )
787 {
788 if( m_reporter )
789 {
790 m_reporter->Report( wxString::Format( _( "'%s' was not fully parsed." ),
791 FormatPath( mappedFile ) ),
793 }
794
795 continue;
796 }
797 }
798 }
799
800 const auto& boardDirectory = aFileMapping.find( ALTIUM_PCB_DIR::BOARD6 );
801
802 if( boardDirectory != aFileMapping.end() )
803 {
804 std::vector<std::string> mappedFile{ boardDirectory->second, "Data" };
805
806 const CFB::COMPOUND_FILE_ENTRY* file = altiumPcbFile.FindStream( mappedFile );
807
808 if( !file )
809 {
810 THROW_IO_ERROR( _( "This file does not appear to be in a valid PCB Binary Version 6.0 format. In "
811 "Altium Designer, make sure to save as \"PCB Binary Files (*.PcbDoc)\"." ) );
812 }
813 }
814
815 // Parse data in specified order
816 for( const std::tuple<bool, ALTIUM_PCB_DIR, PARSE_FUNCTION_POINTER_fp>& cur : parserOrder )
817 {
818 bool isRequired;
821 std::tie( isRequired, directory, fp ) = cur;
822
823 const auto& mappedDirectory = aFileMapping.find( directory );
824
825 if( mappedDirectory == aFileMapping.end() )
826 {
827 wxASSERT_MSG( !isRequired, wxString::Format( wxT( "Altium Directory of kind %d was "
828 "expected, but no mapping is "
829 "present in the code" ),
830 directory ) );
831 continue;
832 }
833
834 std::vector<std::string> mappedFile{ mappedDirectory->second };
835
837 mappedFile.emplace_back( "Data" );
838
839 const CFB::COMPOUND_FILE_ENTRY* file = altiumPcbFile.FindStream( mappedFile );
840
841 if( file != nullptr )
842 {
843 fp( altiumPcbFile, file );
844 }
845 else if( isRequired )
846 {
847 if( m_reporter )
848 {
849 m_reporter->Report( wxString::Format( _( "File not found: '%s' for directory '%s'." ),
850 FormatPath( mappedFile ),
851 magic_enum::enum_name( directory ) ),
853 }
854 }
855 }
856
857 // Components6 is parsed before Pads6, so the mounting style can only be derived once every
858 // pad has been attached to its footprint.
860
861 // Copper that touches no pad returns to the board here, before the tuning patterns claim it
863
864 // Rebuild interactive length-tuning meanders from the SmartUnions definitions now that all
865 // copper that the unions reference has been created and added to the board.
867
869
870 // fixup zone priorities since Altium stores them in the opposite order
871 for( ZONE* zone : m_polygons )
872 {
873 if( !zone )
874 continue;
875
876 // Altium "fills" - not poured in Altium
877 if( zone->GetAssignedPriority() == 1000 )
878 {
879 // Unlikely, but you never know
880 if( m_highest_pour_index >= 1000 )
881 zone->SetAssignedPriority( m_highest_pour_index + 1 );
882
883 continue;
884 }
885
886 int priority = m_highest_pour_index - zone->GetAssignedPriority();
887
888 zone->SetAssignedPriority( priority >= 0 ? priority : 0 );
889 }
890
891 // change priority of outer zone to zero
892 for( std::pair<const ALTIUM_LAYER, ZONE*>& zone : m_outer_plane )
893 zone.second->SetAssignedPriority( 0 );
894
895 // Simplify and fracture zone fills in case we constructed them from tracks (hatched fill)
897 BS::multi_future<void> fractureFutures;
898
899 for( ZONE* zone : m_polygons )
900 {
901 if( !zone )
902 continue;
903
904 for( PCB_LAYER_ID layer : zone->GetLayerSet() )
905 {
906 if( !zone->HasFilledPolysForLayer( layer ) )
907 continue;
908
909 // A hatched plane unions thousands of track outlines, so fills fracture concurrently
910 fractureFutures.push_back( tp.submit_task(
911 [fill = zone->GetFilledPolysList( layer )]()
912 {
913 fill->Fracture();
914 } ) );
915 }
916 }
917
918 // Wait for every task before rethrowing so none outlives the parse
919 fractureFutures.wait();
920 fractureFutures.get();
921
923
925
926 // Altium doesn't appear to store either the dimension value nor the dimensioned object in
927 // the dimension record. (Yes, there is a REFERENCE0OBJECTID, but it doesn't point to the
928 // dimensioned object.) We attempt to plug this gap by finding a colocated arc or circle
929 // and using its radius. If there are more than one such arcs/circles, well, :shrug:.
931 {
932 int radius = 0;
933
934 for( BOARD_ITEM* item : m_board->Drawings() )
935 {
936 if( item->Type() != PCB_SHAPE_T )
937 continue;
938
939 PCB_SHAPE* shape = static_cast<PCB_SHAPE*>( item );
940
941 if( shape->GetShape() != SHAPE_T::ARC && shape->GetShape() != SHAPE_T::CIRCLE )
942 continue;
943
944 if( shape->GetPosition() == dim->GetPosition() )
945 {
946 radius = shape->GetRadius();
947 break;
948 }
949 }
950
951 if( radius == 0 )
952 {
953 for( PCB_TRACK* track : m_board->Tracks() )
954 {
955 if( track->Type() != PCB_ARC_T )
956 continue;
957
958 PCB_ARC* arc = static_cast<PCB_ARC*>( track );
959
960 if( arc->GetCenter() == dim->GetPosition() )
961 {
962 radius = arc->GetRadius();
963 break;
964 }
965 }
966 }
967
968 // Move the radius point onto the circumference
969 VECTOR2I radialLine = dim->GetEnd() - dim->GetStart();
970 int totalLength = radialLine.EuclideanNorm();
971
972 // Enforce a minimum on the radialLine else we won't have enough precision to get the
973 // angle from it.
974 radialLine = radialLine.Resize( std::max( radius, 2 ) );
975 dim->SetEnd( dim->GetStart() + (VECTOR2I) radialLine );
976 dim->SetLeaderLength( totalLength - radius );
977 dim->Update();
978 }
979
980 // center board
981 BOX2I bbbox = m_board->GetBoardEdgesBoundingBox();
982
983 int w = m_board->GetPageSettings().GetWidthIU( pcbIUScale.IU_PER_MILS );
984 int h = m_board->GetPageSettings().GetHeightIU( pcbIUScale.IU_PER_MILS );
985
986 int desired_x = ( w - bbbox.GetWidth() ) / 2;
987 int desired_y = ( h - bbbox.GetHeight() ) / 2;
988
989 VECTOR2I movementVector( desired_x - bbbox.GetX(), desired_y - bbbox.GetY() );
990 m_board->Move( movementVector );
991
992 BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings();
993 bds.SetAuxOrigin( bds.GetAuxOrigin() + movementVector );
994 bds.SetGridOrigin( bds.GetGridOrigin() + movementVector );
995
996 m_board->m_LegacyDesignSettingsLoaded = true;
997 m_board->SetModified();
998}
999
1000
1001// CIRCLE/RECTANGLE bodies become the anchor as-is; others are demoted to a polygon primitive
1002// over a small circular anchor so their copper is not discarded
1003static void altiumMakePadLayerCustom( PAD* aPad, PCB_LAYER_ID aLayer, int aMaxError )
1004{
1005 PAD_SHAPE shape = aPad->GetShape( aLayer );
1006
1007 if( shape == PAD_SHAPE::CIRCLE || shape == PAD_SHAPE::RECTANGLE )
1008 {
1009 aPad->SetAnchorPadShape( aLayer, shape );
1010 aPad->SetShape( aLayer, PAD_SHAPE::CUSTOM );
1011 }
1012 else if( shape != PAD_SHAPE::CUSTOM )
1013 {
1014 SHAPE_POLY_SET body;
1015 aPad->TransformShapeToPolygon( body, aLayer, 0, aMaxError, ERROR_INSIDE );
1016
1017 int minExtent = std::min( aPad->GetSize( aLayer ).x, aPad->GetSize( aLayer ).y );
1018
1019 aPad->SetAnchorPadShape( aLayer, PAD_SHAPE::CIRCLE );
1020 aPad->SetSize( aLayer, VECTOR2I( minExtent, minExtent ) );
1021 aPad->SetShape( aLayer, PAD_SHAPE::CUSTOM );
1022
1023 PCB_SHAPE* bodyPrim = new PCB_SHAPE( nullptr, SHAPE_T::POLY );
1024 bodyPrim->SetFilled( true );
1025 bodyPrim->SetStroke( STROKE_PARAMS( 0, LINE_STYLE::SOLID ) );
1026 bodyPrim->SetPolyShape( body );
1027 bodyPrim->Move( -aPad->ShapePos( aLayer ) );
1028 bodyPrim->Rotate( VECTOR2I( 0, 0 ), -aPad->GetOrientation() );
1029 aPad->AddPrimitive( aLayer, bodyPrim );
1030 }
1031}
1032
1033
1034// Give every copper layer its own padstack entry, deep copying shared primitives, so one layer
1035// can change shape without the others; ALL_LAYERS aliases F_Cu so a NORMAL pad cannot be edited in place
1036static void altiumSplitPadstackPerLayer( PAD* aPad, const LSET& aBoardCopper )
1037{
1038 PADSTACK& padstack = aPad->Padstack();
1039
1040 if( padstack.Mode() == PADSTACK::MODE::CUSTOM )
1041 return;
1042
1043 // Entries for layers the board lacks would only collide with real ones when flipped
1044 const LSET copper = aPad->GetLayerSet() & aBoardCopper;
1045
1046 std::map<PCB_LAYER_ID, PADSTACK::COPPER_LAYER_PROPS> props;
1047
1048 for( PCB_LAYER_ID layer : copper )
1049 {
1050 PADSTACK::COPPER_LAYER_PROPS layerProps = padstack.CopperLayer( layer );
1051
1052 for( std::shared_ptr<PCB_SHAPE>& prim : layerProps.custom_shapes )
1053 prim = std::shared_ptr<PCB_SHAPE>( static_cast<PCB_SHAPE*>( prim->Clone() ) );
1054
1055 props[layer] = std::move( layerProps );
1056 }
1057
1058 padstack.SetMode( PADSTACK::MODE::CUSTOM );
1059
1060 for( auto& [layer, layerProps] : props )
1061 padstack.CopperLayer( layer ) = std::move( layerProps );
1062
1063 aPad->SetDirty();
1064}
1065
1066
1067// Lets aLayer change shape alone with the fewest padstack entries; outer layers only need front/inner/back
1068static void altiumIsolatePadLayer( PAD* aPad, PCB_LAYER_ID aLayer, const LSET& aBoardCopper )
1069{
1070 if( ( aPad->GetLayerSet() & LSET::AllCuMask() ).count() < 2 )
1071 return;
1072
1073 PADSTACK& padstack = aPad->Padstack();
1074
1075 if( IsInnerCopperLayer( aLayer ) )
1076 {
1077 altiumSplitPadstackPerLayer( aPad, aBoardCopper );
1078 }
1079 else if( padstack.Mode() == PADSTACK::MODE::NORMAL )
1080 {
1082
1083 // Materialized from the shared F_Cu entry before aLayer changes
1085 padstack.CopperLayer( B_Cu );
1086 aPad->SetDirty();
1087 }
1088}
1089
1090
1091static LSET altiumStackupCopper( const LSET& aDrawnCopper, int aMinCopperLayers )
1092{
1093 return LSET::AllCuMask( std::max( aMinCopperLayers, LAYER_UTILS::MinimalCopperLayerCount( aDrawnCopper ) ) );
1094}
1095
1096
1097// Altium addresses each mid layer directly, while the default footprint stackup has one inner
1098// layer that hides deeper items and spreads an In1 keepout to every inner layer
1099static void altiumSetFootprintStackup( FOOTPRINT* aFootprint, const LSET& aDrawnCopper, int aMinCopperLayers )
1100{
1101 if( ( aDrawnCopper & LSET::InternalCuMask() ).none() )
1102 return;
1103
1104 const LSET stackupCopper = altiumStackupCopper( aDrawnCopper, aMinCopperLayers );
1105
1106 for( ZONE* zone : aFootprint->Zones() )
1107 {
1108 LSET layers = zone->GetLayerSet();
1109
1110 if( ( layers & LSET::AllCuMask() ).count() > 1 )
1111 zone->SetLayerSet( layers & ( stackupCopper | ~LSET::AllCuMask() ) );
1112 }
1113
1114 // Every drawn layer is in the stackup, so copper outside it is a multi-layer expansion copy
1115 std::vector<BOARD_ITEM*> outsideStackup;
1116
1117 for( BOARD_ITEM* item : aFootprint->GraphicalItems() )
1118 {
1119 if( IsCopperLayer( item->GetLayer() ) && !stackupCopper.test( item->GetLayer() ) )
1120 outsideStackup.push_back( item );
1121 }
1122
1123 for( BOARD_ITEM* item : outsideStackup )
1124 {
1125 aFootprint->Remove( item );
1126 delete item;
1127 }
1128
1130
1132 aFootprint->SetStackupLayers( stackupCopper | userLayers );
1133}
1134
1135
1136std::unique_ptr<FOOTPRINT> ALTIUM_PCB::ParseFootprint( ALTIUM_PCB_COMPOUND_FILE& altiumLibFile,
1137 const wxString& aFootprintName )
1138{
1139 std::unique_ptr<FOOTPRINT> footprint = std::make_unique<FOOTPRINT>( m_board );
1140
1141 m_unicodeStrings.clear();
1143
1144 const std::vector<std::string> libStreamName{ "Library", "Data" };
1145 const CFB::COMPOUND_FILE_ENTRY* libStream = altiumLibFile.FindStream( libStreamName );
1146
1147 if( libStream == nullptr )
1148 THROW_IO_ERRORF( _( "File not found: '%s'." ), FormatPath( libStreamName ) );
1149
1150 ALTIUM_BINARY_PARSER libParser( altiumLibFile, libStream );
1151 ALIBRARY libData( libParser );
1152
1154
1155 // TODO: WideStrings are stored as parameterMap in the case of footprints, not as binary
1156 // std::string unicodeStringsStreamName = aFootprintName.ToStdString() + "\\WideStrings";
1157 // const CFB::COMPOUND_FILE_ENTRY* unicodeStringsData = altiumLibFile.FindStream( unicodeStringsStreamName );
1158 // if( unicodeStringsData != nullptr )
1159 // {
1160 // ParseWideStrings6Data( altiumLibFile, unicodeStringsData );
1161 // }
1162
1163 std::tuple<wxString, const CFB::COMPOUND_FILE_ENTRY*> ret =
1164 altiumLibFile.FindLibFootprintDirName( aFootprintName );
1165
1166 wxString fpDirName = std::get<0>( ret );
1167 const CFB::COMPOUND_FILE_ENTRY* footprintStream = std::get<1>( ret );
1168
1169 if( fpDirName.IsEmpty() )
1170 THROW_IO_ERRORF( _( "Footprint directory not found: '%s'." ), aFootprintName );
1171
1172 const std::vector<std::string> streamName{ fpDirName.ToStdString(), "Data" };
1173 const CFB::COMPOUND_FILE_ENTRY* footprintData = altiumLibFile.FindStream( footprintStream, { "Data" } );
1174
1175 if( !footprintData )
1176 THROW_IO_ERRORF( _( "File not found: '%s'." ), FormatPath( streamName ) );
1177
1178 ALTIUM_BINARY_PARSER parser( altiumLibFile, footprintData );
1179
1181 //wxString footprintName = parser.ReadWxString(); // Not used (single-byte char set)
1182 parser.SkipSubrecord();
1183
1184 LIB_ID fpID = AltiumToKiCadLibID( "", aFootprintName ); // TODO: library name
1185 footprint->SetFPID( fpID );
1186
1187 const std::vector<std::string> parametersStreamName{ fpDirName.ToStdString(), "Parameters" };
1188 const CFB::COMPOUND_FILE_ENTRY* parametersData = altiumLibFile.FindStream( footprintStream, { "Parameters" } );
1189
1190 if( parametersData != nullptr )
1191 {
1192 ALTIUM_BINARY_PARSER parametersReader( altiumLibFile, parametersData );
1193 std::map<wxString, wxString> parameterProperties = parametersReader.ReadProperties();
1194 wxString description = ALTIUM_PROPS_UTILS::ReadString( parameterProperties, wxT( "DESCRIPTION" ), wxT( "" ) );
1195 footprint->SetLibDescription( description );
1196 }
1197 else
1198 {
1199 if( m_reporter )
1200 {
1201 m_reporter->Report( wxString::Format( _( "File not found: '%s'." ), FormatPath( parametersStreamName ) ),
1203 }
1204
1205 footprint->SetLibDescription( wxT( "" ) );
1206 }
1207
1208 const std::vector<std::string> extendedPrimitiveInformationStreamName{
1209 "ExtendedPrimitiveInformation", "Data"
1210 };
1211 const CFB::COMPOUND_FILE_ENTRY* extendedPrimitiveInformationData =
1212 altiumLibFile.FindStream( footprintStream, extendedPrimitiveInformationStreamName );
1213
1214 if( extendedPrimitiveInformationData != nullptr )
1215 ParseExtendedPrimitiveInformationData( altiumLibFile, extendedPrimitiveInformationData );
1216
1217 footprint->SetReference( wxT( "REF**" ) );
1218 footprint->SetValue( aFootprintName );
1219 footprint->Reference().SetVisible( true ); // TODO: extract visibility information
1220 footprint->Value().SetVisible( true );
1221
1222 const VECTOR2I defaultTextSize( pcbIUScale.mmToIU( 1.0 ), pcbIUScale.mmToIU( 1.0 ) );
1223 const int defaultTextThickness( pcbIUScale.mmToIU( 0.15 ) );
1224
1225 for( PCB_FIELD* field : footprint->GetFields() )
1226 {
1227 field->SetTextSize( defaultTextSize );
1228 field->SetTextThickness( defaultTextThickness );
1229 }
1230
1231 // Multi-layer records expand to every copper layer here, so only a record's own layer counts
1232 LSET drawnCopper;
1233
1234 auto noteLayer =
1235 [&]( ALTIUM_LAYER aLayer )
1236 {
1237 PCB_LAYER_ID klayer = GetKicadLayer( aLayer );
1238
1239 if( IsCopperLayer( klayer ) )
1240 drawnCopper.set( klayer );
1241 };
1242
1243 for( int primitiveIndex = 0; parser.GetRemainingBytes() >= 4; primitiveIndex++ )
1244 {
1245 ALTIUM_RECORD recordtype = static_cast<ALTIUM_RECORD>( parser.Peek<uint8_t>() );
1246
1247 switch( recordtype )
1248 {
1249 case ALTIUM_RECORD::ARC:
1250 {
1251 AARC6 arc( parser );
1252 noteLayer( arc.layer );
1253 ConvertArcs6ToFootprintItem( footprint.get(), arc, primitiveIndex, false );
1254 break;
1255 }
1256 case ALTIUM_RECORD::PAD:
1257 {
1258 APAD6 pad( parser );
1259 noteLayer( pad.layer );
1260 ConvertPads6ToFootprintItem( footprint.get(), pad );
1261 break;
1262 }
1263 case ALTIUM_RECORD::VIA:
1264 {
1265 AVIA6 via( parser );
1266 noteLayer( via.layer_start );
1267 noteLayer( via.layer_end );
1268 ConvertVias6ToFootprintItem( footprint.get(), via );
1269 break;
1270 }
1272 {
1273 ATRACK6 track( parser );
1274 noteLayer( track.layer );
1275 ConvertTracks6ToFootprintItem( footprint.get(), track, primitiveIndex, false );
1276 break;
1277 }
1279 {
1280 ATEXT6 text( parser, m_unicodeStrings );
1281 noteLayer( text.layer );
1282 ConvertTexts6ToFootprintItem( footprint.get(), text );
1283 break;
1284 }
1286 {
1287 AFILL6 fill( parser );
1288 noteLayer( fill.layer );
1289 ConvertFills6ToFootprintItem( footprint.get(), fill, false );
1290 break;
1291 }
1293 {
1294 AREGION6 region( parser, false );
1295 noteLayer( region.layer );
1296
1298 && !region.is_keepout )
1299 {
1300 ConvertShapeBasedRegions6ToFootprintCopper( footprint.get(), region, primitiveIndex,
1302 }
1303 else
1304 {
1305 ConvertShapeBasedRegions6ToFootprintItem( footprint.get(), region, primitiveIndex );
1306 }
1307
1308 break;
1309 }
1311 {
1312 ACOMPONENTBODY6 componentBody( parser );
1313 ConvertComponentBody6ToFootprintItem( altiumLibFile, footprint.get(), componentBody );
1314 break;
1315 }
1316 default:
1317 THROW_IO_ERRORF( _( "Record of unknown type: '%d'." ), recordtype );
1318 }
1319 }
1320
1321
1322 // Altium splits a custom-shape pad into a numbered placeholder and an unnumbered region outline
1323 // fold the region whose outline contains the placeholder anchor; others pass through unchanged
1324 auto isRegionPad =
1325 []( PAD* aPad )
1326 {
1327 if( !aPad->GetNumber().IsEmpty() )
1328 return false;
1329
1330 for( PCB_LAYER_ID layer : { PADSTACK::ALL_LAYERS, F_Cu, B_Cu } )
1331 {
1332 if( aPad->GetShape( layer ) == PAD_SHAPE::CUSTOM
1333 && !aPad->GetPrimitives( layer ).empty() )
1334 {
1335 return true;
1336 }
1337 }
1338
1339 return false;
1340 };
1341
1342 auto commonCopperLayer =
1343 []( PAD* aRegionPad, PAD* aNamedPad ) -> PCB_LAYER_ID
1344 {
1345 if( aRegionPad->IsOnLayer( F_Cu ) && aNamedPad->IsOnLayer( F_Cu ) )
1346 return F_Cu;
1347
1348 if( aRegionPad->IsOnLayer( B_Cu ) && aNamedPad->IsOnLayer( B_Cu ) )
1349 return B_Cu;
1350
1351 return PADSTACK::ALL_LAYERS;
1352 };
1353
1354 const int maxError = m_board ? m_board->GetDesignSettings().m_MaxError : ARC_HIGH_DEF;
1355 const LSET stackupCopper = altiumStackupCopper( drawnCopper, 2 );
1356
1357 auto mergeRegionPadInto =
1358 [&]( PAD* aNamedPad, PAD* aRegionPad )
1359 {
1360 PCB_LAYER_ID layer = commonCopperLayer( aRegionPad, aNamedPad );
1361
1362 // A NORMAL padstack shares one shape, so a through pad would grow on every layer
1363 altiumIsolatePadLayer( aNamedPad, layer, stackupCopper );
1364 altiumMakePadLayerCustom( aNamedPad, layer, maxError );
1365
1366 // Region primitives are in the region pad's local frame; re-express in the named
1367 // pad's frame, undoing the orientation GetEffectivePolygon will later re-apply
1368 EDA_ANGLE namedAngle = aNamedPad->GetOrientation();
1369 VECTOR2I anchorShift = aRegionPad->ShapePos( PADSTACK::ALL_LAYERS )
1370 - aNamedPad->ShapePos( layer );
1371
1372 for( const std::shared_ptr<PCB_SHAPE>& src : aRegionPad->GetPrimitives( layer ) )
1373 {
1374 PCB_SHAPE* copy = static_cast<PCB_SHAPE*>( src->Clone() );
1375
1376 copy->Rotate( VECTOR2I( 0, 0 ), aRegionPad->GetOrientation() );
1377 copy->Move( anchorShift );
1378 copy->Rotate( VECTOR2I( 0, 0 ), -namedAngle );
1379
1380 aNamedPad->AddPrimitive( layer, copy );
1381 }
1382
1383 if( aRegionPad->GetLocalSolderMaskMargin().has_value()
1384 && !aNamedPad->GetLocalSolderMaskMargin().has_value() )
1385 {
1386 aNamedPad->SetLocalSolderMaskMargin(
1387 aRegionPad->GetLocalSolderMaskMargin().value() );
1388 }
1389
1390 if( aRegionPad->GetLocalSolderPasteMargin().has_value()
1391 && !aNamedPad->GetLocalSolderPasteMargin().has_value() )
1392 {
1393 aNamedPad->SetLocalSolderPasteMargin(
1394 aRegionPad->GetLocalSolderPasteMargin().value() );
1395 }
1396
1397 aNamedPad->SetLayerSet( aNamedPad->GetLayerSet() | aRegionPad->GetLayerSet() );
1398 };
1399
1400 std::vector<PAD*> regionPads;
1401 std::vector<PAD*> namedPads;
1402
1403 for( PAD* pad : footprint->Pads() )
1404 {
1405 if( isRegionPad( pad ) )
1406 regionPads.push_back( pad );
1407 else if( !pad->GetNumber().IsEmpty() )
1408 namedPads.push_back( pad );
1409 }
1410
1411 // A placeholder pad owns at most one region outline; consume matched pads so a second region
1412 // never folds into a pad whose shape already grew from an earlier merge
1413 std::unordered_set<PAD*> consumedNamed;
1414
1415 for( PAD* regionPad : regionPads )
1416 {
1417 PCB_LAYER_ID regionCu = regionPad->IsOnLayer( F_Cu ) ? F_Cu
1418 : regionPad->IsOnLayer( B_Cu ) ? B_Cu
1420
1421 std::shared_ptr<SHAPE_POLY_SET> outline =
1422 regionPad->GetEffectivePolygon( regionCu, ERROR_INSIDE );
1423
1424 if( !outline || outline->OutlineCount() == 0 )
1425 continue;
1426
1427 // Claim the closest numbered pad sharing a copper layer whose anchor sits inside the outline
1428 // containment is the signal it's the Altium placeholder for this region, not an overlap
1429 PAD* bestNamed = nullptr;
1430 int64_t bestDistSq = std::numeric_limits<int64_t>::max();
1431
1432 for( PAD* namedPad : namedPads )
1433 {
1434 if( consumedNamed.count( namedPad ) )
1435 continue;
1436
1437 if( commonCopperLayer( regionPad, namedPad ) == PADSTACK::ALL_LAYERS
1438 && !( namedPad->GetLayerSet() & regionPad->GetLayerSet() & LSET::AllCuMask() ).any() )
1439 {
1440 continue;
1441 }
1442
1443 if( !outline->Contains( namedPad->GetPosition(), -1, 0 ) )
1444 continue;
1445
1446 VECTOR2I delta = namedPad->GetPosition() - regionPad->GetPosition();
1447 int64_t distSq = (int64_t) delta.x * delta.x + (int64_t) delta.y * delta.y;
1448
1449 if( distSq < bestDistSq )
1450 {
1451 bestDistSq = distSq;
1452 bestNamed = namedPad;
1453 }
1454 }
1455
1456 if( bestNamed )
1457 {
1458 mergeRegionPadInto( bestNamed, regionPad );
1459 consumedNamed.insert( bestNamed );
1460 footprint->Remove( regionPad );
1461 delete regionPad;
1462 }
1463 }
1464
1465 std::set<PAD*> extensionPads;
1466
1467 // Loop over this multiple times to catch pads that are jumpered to each other by multiple shapes
1468 for( bool changes = true; changes; )
1469 {
1470 changes = HelperFoldPendingCopper( stackupCopper, maxError, extensionPads );
1471
1472 alg::for_all_pairs( footprint->Pads().begin(), footprint->Pads().end(),
1473 [&changes]( PAD* aPad1, PAD* aPad2 )
1474 {
1475 if( !( aPad1->GetNumber().IsEmpty() ^ aPad2->GetNumber().IsEmpty() ) )
1476 return;
1477
1478 // A pad answers for layers it lacks with its own shape, and masks carry no current
1479 const LSET sharedCopper = aPad1->GetLayerSet() & aPad2->GetLayerSet() & LSET::AllCuMask();
1480
1481 for( PCB_LAYER_ID layer : sharedCopper )
1482 {
1483 std::shared_ptr<SHAPE> shape1 = aPad1->GetEffectiveShape( layer );
1484 std::shared_ptr<SHAPE> shape2 = aPad2->GetEffectiveShape( layer );
1485
1486 if( shape1->Collide( shape2.get() ) )
1487 {
1488 if( aPad1->GetNumber().IsEmpty() )
1489 aPad1->SetNumber( aPad2->GetNumber() );
1490 else
1491 aPad2->SetNumber( aPad1->GetNumber() );
1492
1493 changes = true;
1494 }
1495 }
1496 } );
1497 }
1498
1499 HelperPlaceUnfoldedCopper();
1500
1501 // Auto-position reference and value
1502 footprint->AutoPositionFields();
1503
1504 // Altium has no mounting style to copy, so derive it from the pads using the same heuristic
1505 // as KiCad's footprint checker. Unlike the board importer this can be done here, because a
1506 // library footprint's pads are converted inline above.
1507 footprint->SetAttributes( footprint->GetAttributes() | footprint->GetLikelyAttribute() );
1508
1509 altiumSetFootprintStackup( footprint.get(), drawnCopper, 2 );
1510
1511 if( parser.HasParsingError() )
1512 THROW_IO_ERRORF( wxT( "%s stream was not parsed correctly" ), FormatPath( streamName ) );
1513
1514 if( parser.GetRemainingBytes() != 0 )
1515 THROW_IO_ERRORF( wxT( "%s stream is not fully parsed" ), FormatPath( streamName ) );
1516
1517 return footprint;
1518}
1519
1520int ALTIUM_PCB::GetNetCode( uint16_t aId ) const
1521{
1522 if( aId == ALTIUM_NET_UNCONNECTED )
1523 {
1525 }
1526 else if( aId >= m_altiumToKicadNetcodes.size() )
1527 {
1528 THROW_IO_ERRORF( wxT( "Netcode with id %d does not exist. Only %zu nets are known" ),
1529 aId, m_altiumToKicadNetcodes.size() );
1530 }
1531 else
1532 {
1533 return m_altiumToKicadNetcodes[ aId ];
1534 }
1535}
1536
1537const ARULE6* ALTIUM_PCB::GetRule( ALTIUM_RULE_KIND aKind, const wxString& aName ) const
1538{
1539 const auto rules = m_rules.find( aKind );
1540
1541 if( rules == m_rules.end() )
1542 return nullptr;
1543
1544 for( const ARULE6& rule : rules->second )
1545 {
1546 if( rule.enabled && rule.name == aName )
1547 return &rule;
1548 }
1549
1550 return nullptr;
1551}
1552
1554{
1555 const auto rules = m_rules.find( aKind );
1556
1557 if( rules == m_rules.end() )
1558 return nullptr;
1559
1560 for( const ARULE6& rule : rules->second )
1561 {
1562 if( rule.enabled && IsAltiumScopeAll( rule.scope1expr ) && IsAltiumScopeAll( rule.scope2expr ) )
1563 return &rule;
1564 }
1565
1566 return nullptr;
1567}
1568
1569
1571{
1572 const auto rules = m_rules.find( aKind );
1573
1574 if( rules == m_rules.end() )
1575 return nullptr;
1576
1577 if( const ARULE6* match = selectAltiumPolygonRule( rules->second ) )
1578 return match;
1579
1580 // Fall back to the default (All/All) rule
1581 return GetRuleDefault( aKind );
1582}
1583
1584
1586 const CFB::COMPOUND_FILE_ENTRY* aEntry )
1587{
1588 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
1589
1591 wxString header = reader.ReadWxString();
1592
1593 //std::cout << "HEADER: " << header << std::endl; // tells me: PCB 5.0 Binary File
1594
1595 //reader.SkipSubrecord();
1596
1597 // TODO: does not seem to work all the time at the moment
1598 //if( reader.GetRemainingBytes() != 0 )
1599 // THROW_IO_ERROR( "FileHeader stream is not fully parsed" );
1600}
1601
1602
1604 const CFB::COMPOUND_FILE_ENTRY* aEntry )
1605{
1606 if( m_progressReporter )
1607 m_progressReporter->Report( _( "Loading extended primitive information data..." ) );
1608
1609 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
1610
1611 while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
1612 {
1613 checkpoint();
1614 AEXTENDED_PRIMITIVE_INFORMATION elem( reader );
1615
1617 std::move( elem ) );
1618 }
1619
1620 if( reader.GetRemainingBytes() != 0 )
1621 THROW_IO_ERROR( wxT( "ExtendedPrimitiveInformation stream is not fully parsed" ) );
1622}
1623
1624
1626 const CFB::COMPOUND_FILE_ENTRY* aEntry )
1627{
1628 if( m_progressReporter )
1629 m_progressReporter->Report( _( "Loading board data..." ) );
1630
1631 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
1632
1633 checkpoint();
1634 ABOARD6 elem( reader );
1635
1636 if( reader.GetRemainingBytes() != 0 )
1637 THROW_IO_ERROR( wxT( "Board6 stream is not fully parsed" ) );
1638
1639 m_board->GetDesignSettings().SetAuxOrigin( elem.origin );
1640 m_board->GetDesignSettings().SetGridOrigin( elem.origin );
1641
1642 // read layercount from stackup, because LAYERSETSCOUNT is not always correct?!
1643 size_t layercount = 0;
1644 size_t layerid = static_cast<size_t>( ALTIUM_LAYER::TOP_LAYER );
1645
1646 while( layerid < elem.stackup.size() && layerid != 0 )
1647 {
1648 layerid = elem.stackup[ layerid - 1 ].nextId;
1649 layercount++;
1650 }
1651
1652 size_t kicadLayercount = ( layercount % 2 == 0 ) ? layercount : layercount + 1;
1653 m_board->SetCopperLayerCount( kicadLayercount );
1654
1655 BOARD_DESIGN_SETTINGS& designSettings = m_board->GetDesignSettings();
1656 BOARD_STACKUP& stackup = designSettings.GetStackupDescriptor();
1657
1658 // create board stackup
1659 stackup.RemoveAll(); // Just to be sure
1660 stackup.BuildDefaultStackupList( &designSettings, layercount );
1661
1662 auto it = stackup.GetList().begin();
1663
1664 // find first copper layer
1665 for( ; it != stackup.GetList().end() && ( *it )->GetType() != BS_ITEM_TYPE_COPPER; ++it )
1666 ;
1667
1668 auto cuLayer = LAYER_RANGE( F_Cu, B_Cu, 32 ).begin();
1669
1670 for( size_t altiumLayerId = static_cast<size_t>( ALTIUM_LAYER::TOP_LAYER );
1671 altiumLayerId < elem.stackup.size() && altiumLayerId != 0;
1672 altiumLayerId = elem.stackup[altiumLayerId - 1].nextId )
1673 {
1674 // array starts with 0, but stackup with 1
1675 ABOARD6_LAYER_STACKUP& layer = elem.stackup.at( altiumLayerId - 1 );
1676
1677 // handle unused layer in case of odd layercount
1678 if( layer.nextId == 0 && layercount != kicadLayercount )
1679 {
1680 m_board->SetLayerName( ( *it )->GetBrdLayerId(), wxT( "[unused]" ) );
1681
1682 if( ( *it )->GetType() != BS_ITEM_TYPE_COPPER )
1683 THROW_IO_ERROR( wxT( "Board6 stream, unexpected item while parsing stackup" ) );
1684
1685 ( *it )->SetThickness( 0 );
1686
1687 ++it;
1688
1689 if( ( *it )->GetType() != BS_ITEM_TYPE_DIELECTRIC )
1690 THROW_IO_ERROR( wxT( "Board6 stream, unexpected item while parsing stackup" ) );
1691
1692 ( *it )->SetThickness( 0, 0 );
1693 ( *it )->SetThicknessLocked( true, 0 );
1694 ++it;
1695 }
1696
1697 m_layermap.insert( { static_cast<ALTIUM_LAYER>( altiumLayerId ), *cuLayer } );
1698 ++cuLayer;
1699
1700 if( ( *it )->GetType() != BS_ITEM_TYPE_COPPER )
1701 THROW_IO_ERROR( wxT( "Board6 stream, unexpected item while parsing stackup" ) );
1702
1703 ( *it )->SetThickness( layer.copperthick );
1704
1705 ALTIUM_LAYER alayer = static_cast<ALTIUM_LAYER>( altiumLayerId );
1706 PCB_LAYER_ID klayer = ( *it )->GetBrdLayerId();
1707
1708 m_board->SetLayerName( klayer, layer.name );
1709
1710 if( layer.copperthick == 0 )
1711 m_board->SetLayerType( klayer, LAYER_T::LT_JUMPER ); // used for things like wirebonding
1712 else if( IsAltiumLayerAPlane( alayer ) )
1713 m_board->SetLayerType( klayer, LAYER_T::LT_POWER );
1714
1715 if( klayer == B_Cu )
1716 {
1717 if( layer.nextId != 0 )
1718 THROW_IO_ERROR( wxT( "Board6 stream, unexpected id while parsing last stackup layer" ) );
1719
1720 // overwrite entry from internal -> bottom
1721 m_layermap[alayer] = B_Cu;
1722 break;
1723 }
1724
1725 ++it;
1726
1727 if( ( *it )->GetType() != BS_ITEM_TYPE_DIELECTRIC )
1728 THROW_IO_ERROR( wxT( "Board6 stream, unexpected item while parsing stackup" ) );
1729
1730 ( *it )->SetThickness( layer.dielectricthick, 0 );
1731 ( *it )->SetMaterial( layer.dielectricmaterial.empty() ? NotSpecifiedPrm()
1732 : wxString( layer.dielectricmaterial ) );
1733 ( *it )->SetEpsilonR( layer.dielectricconst, 0 );
1734
1735 if( layer.dielectriclosstangent > 0. )
1736 ( *it )->SetLossTangent( layer.dielectriclosstangent, 0 );
1737
1738 ++it;
1739 }
1740
1743 remapUnsureLayers( elem.stackup );
1744
1745 // Set name of all non-cu layers
1746 for( const ABOARD6_LAYER_STACKUP& layer : elem.stackup )
1747 {
1748 ALTIUM_LAYER alayer = static_cast<ALTIUM_LAYER>( layer.layerId );
1749
1750 if( ( alayer >= ALTIUM_LAYER::TOP_OVERLAY && alayer <= ALTIUM_LAYER::BOTTOM_SOLDER )
1751 || ( alayer >= ALTIUM_LAYER::MECHANICAL_1 && alayer <= ALTIUM_LAYER::MECHANICAL_16 )
1753 {
1754 PCB_LAYER_ID klayer = GetKicadLayer( alayer );
1755 m_board->SetLayerName( klayer, layer.name );
1756 }
1757 }
1758
1759 if( elem.discardedVertices > 0 && m_reporter )
1760 {
1761 m_reporter->Report( wxString::Format( _( "Board outline has %d vertices outside the "
1762 "coordinate range; they were dropped." ),
1763 elem.discardedVertices ),
1765 }
1766
1768 m_board->GetDesignSettings().SetBoardThickness( stackup.BuildBoardThicknessFromStackup() );
1769 designSettings.m_HasStackup = true;
1770}
1771
1772
1774{
1775 m_padstackLayerIndex.clear();
1776
1777 for( const auto& [altiumLayer, kicadLayer] : m_layermap )
1778 {
1779 if( altiumLayer < ALTIUM_LAYER::TOP_LAYER || altiumLayer > ALTIUM_LAYER::BOTTOM_LAYER )
1780 continue;
1781
1782 if( IsCopperLayer( kicadLayer ) )
1783 {
1784 m_padstackLayerIndex[kicadLayer] = static_cast<int>( altiumLayer )
1785 - static_cast<int>( ALTIUM_LAYER::TOP_LAYER );
1786 }
1787 }
1788}
1789
1790
1792{
1793 // Footprint libraries carry no stackup, so Altium's mid layer N is KiCad's In N
1794 if( m_padstackLayerIndex.empty() )
1795 {
1796 size_t ordinal = CopperLayerToOrdinal( aLayer );
1797
1798 return ordinal < ALTIUM_PADSTACK_IDX_COUNT ? static_cast<int>( ordinal ) : -1;
1799 }
1800
1801 auto it = m_padstackLayerIndex.find( aLayer );
1802
1803 return it == m_padstackLayerIndex.end() ? -1 : it->second;
1804}
1805
1806
1807// Helper to detect if a layer name indicates a courtyard layer
1808static bool IsLayerNameCourtyard( const wxString& aName )
1809{
1810 wxString nameLower = aName.Lower();
1811 return nameLower.Contains( wxT( "courtyard" ) ) || nameLower.Contains( wxT( "court yard" ) )
1812 || nameLower.Contains( wxT( "crtyd" ) );
1813}
1814
1815
1816// Helper to detect if a layer name indicates an assembly layer
1817static bool IsLayerNameAssembly( const wxString& aName )
1818{
1819 wxString nameLower = aName.Lower();
1820 return nameLower.Contains( wxT( "assembly" ) ) || nameLower.Contains( wxT( "assy" ) );
1821}
1822
1823
1824// Helper to detect if a layer name indicates a top-side layer
1825static bool IsLayerNameTopSide( const wxString& aName )
1826{
1827 bool isTop = false;
1828
1829 auto check = [&isTop]( bool aTopCond, bool aBotCond )
1830 {
1831 if( aTopCond && aBotCond )
1832 return false;
1833
1834 if( !aTopCond && !aBotCond )
1835 return false;
1836
1837 isTop = aTopCond;
1838 return true;
1839 };
1840
1841 wxString lower = aName.Lower();
1842
1843 if( check( lower.StartsWith( "top" ), lower.StartsWith( "bot" ) ) )
1844 return isTop;
1845
1846 if( check( lower.EndsWith( "_t" ), lower.EndsWith( "_b" ) ) )
1847 return isTop;
1848
1849 if( check( lower.EndsWith( ".t" ), lower.EndsWith( ".b" ) ) )
1850 return isTop;
1851
1852 if( check( lower.Contains( "top" ), lower.Contains( "bot" ) ) )
1853 return isTop;
1854
1855 return true; // Unknown
1856}
1857
1858
1859void ALTIUM_PCB::remapUnsureLayers( std::vector<ABOARD6_LAYER_STACKUP>& aStackup )
1860{
1861 LSET enabledLayers = m_board->GetEnabledLayers();
1862 LSET validRemappingLayers = enabledLayers | LSET::AllBoardTechMask() |
1864
1865 if( aStackup.size() == 0 )
1866 return;
1867
1868 std::vector<INPUT_LAYER_DESC> inputLayers;
1869 std::map<wxString, ALTIUM_LAYER> altiumLayerNameMap;
1870
1871 ABOARD6_LAYER_STACKUP& curLayer = aStackup[0];
1872 ALTIUM_LAYER layer_num;
1873 INPUT_LAYER_DESC iLdesc;
1874
1875 // Track which courtyard layers we've mapped to avoid duplicates
1876 bool frontCourtyardMapped = false;
1877 bool backCourtyardMapped = false;
1878
1879 for( size_t ii = 0; ii < aStackup.size(); ii++ )
1880 {
1881 curLayer = aStackup[ii];
1882 layer_num = static_cast<ALTIUM_LAYER>( curLayer.layerId );
1883
1884 // Skip UI-only layers and pseudo-layers that have no physical representation
1885 if( layer_num == ALTIUM_LAYER::MULTI_LAYER
1886 || layer_num == ALTIUM_LAYER::CONNECTIONS
1887 || layer_num == ALTIUM_LAYER::BACKGROUND
1888 || layer_num == ALTIUM_LAYER::DRC_ERROR_MARKERS
1889 || layer_num == ALTIUM_LAYER::SELECTIONS
1890 || layer_num == ALTIUM_LAYER::VISIBLE_GRID_1
1891 || layer_num == ALTIUM_LAYER::VISIBLE_GRID_2
1892 || layer_num == ALTIUM_LAYER::PAD_HOLES
1893 || layer_num == ALTIUM_LAYER::VIA_HOLES )
1894 {
1895 continue;
1896 }
1897
1898 // Skip disabled mechanical layers (mapped to UNDEFINED_LAYER by
1899 // HelperFillMechanicalLayerAssignments)
1900 auto existingMapping = m_layermap.find( layer_num );
1901
1902 if( existingMapping != m_layermap.end()
1903 && existingMapping->second == PCB_LAYER_ID::UNDEFINED_LAYER )
1904 {
1905 continue;
1906 }
1907
1908 // Skip unused copper layers not present in the board's stackup. Used copper layers
1909 // were added to m_layermap during stackup parsing; any copper layer not in the map
1910 // is unused and should not appear in the dialog.
1911 if( ( ( layer_num >= ALTIUM_LAYER::TOP_LAYER && layer_num <= ALTIUM_LAYER::BOTTOM_LAYER )
1912 || IsAltiumLayerAPlane( layer_num ) )
1913 && existingMapping == m_layermap.end() )
1914 {
1915 continue;
1916 }
1917
1918 // Use existing mapping as auto-match default if available
1919 if( existingMapping != m_layermap.end() )
1920 {
1921 iLdesc.AutoMapLayer = existingMapping->second;
1922 }
1923 // Check if the layer name indicates a courtyard layer
1924 else if( IsLayerNameCourtyard( curLayer.name ) )
1925 {
1926 bool isTopSide = IsLayerNameTopSide( curLayer.name );
1927
1928 if( isTopSide && !frontCourtyardMapped )
1929 {
1930 iLdesc.AutoMapLayer = F_CrtYd;
1931 frontCourtyardMapped = true;
1932 }
1933 else if( !isTopSide && !backCourtyardMapped )
1934 {
1935 iLdesc.AutoMapLayer = B_CrtYd;
1936 backCourtyardMapped = true;
1937 }
1938 else if( !frontCourtyardMapped )
1939 {
1940 iLdesc.AutoMapLayer = F_CrtYd;
1941 frontCourtyardMapped = true;
1942 }
1943 else if( !backCourtyardMapped )
1944 {
1945 iLdesc.AutoMapLayer = B_CrtYd;
1946 backCourtyardMapped = true;
1947 }
1948 else
1949 {
1950 iLdesc.AutoMapLayer = GetKicadLayer( layer_num );
1951 }
1952 }
1953 // Check if the layer name indicates an assembly layer (map to Fab)
1954 else if( IsLayerNameAssembly( curLayer.name ) )
1955 {
1956 bool isTopSide = IsLayerNameTopSide( curLayer.name );
1957 iLdesc.AutoMapLayer = isTopSide ? F_Fab : B_Fab;
1958 }
1959 else
1960 {
1961 iLdesc.AutoMapLayer = GetKicadLayer( layer_num );
1962 }
1963
1964 iLdesc.Name = curLayer.name;
1965 iLdesc.PermittedLayers = validRemappingLayers;
1966 iLdesc.Required = layer_num >= ALTIUM_LAYER::TOP_LAYER
1967 && layer_num <= ALTIUM_LAYER::BOTTOM_LAYER;
1968
1969 inputLayers.push_back( iLdesc );
1970 altiumLayerNameMap.insert( { curLayer.name, layer_num } );
1971 m_layerNames.insert( { layer_num, curLayer.name } );
1972 }
1973
1974 if( inputLayers.size() == 0 )
1975 return;
1976
1977 // Callback:
1978 std::map<wxString, PCB_LAYER_ID> reMappedLayers = m_layerMappingHandler( inputLayers );
1979
1980 for( std::pair<wxString, PCB_LAYER_ID> layerPair : reMappedLayers )
1981 {
1982 if( layerPair.second == PCB_LAYER_ID::UNDEFINED_LAYER )
1983 {
1984 // Layer mapping handler returned UNDEFINED_LAYER - skip this layer
1985 // This can happen for layers that don't have a KiCad equivalent
1986 if( m_reporter )
1987 {
1988 m_reporter->Report( wxString::Format( _( "Layer '%s' could not be mapped and "
1989 "will be skipped." ),
1990 layerPair.first ),
1992 }
1993
1994 continue;
1995 }
1996
1997 ALTIUM_LAYER altiumID = altiumLayerNameMap.at( layerPair.first );
1998 m_layermap.insert_or_assign( altiumID, layerPair.second );
1999 enabledLayers |= LSET( { layerPair.second } );
2000 }
2001
2002 // Explicitly mark unmatched dialog layers as UNDEFINED_LAYER so they are not imported
2003 // via the GetKicadLayer() hardcoded switch fallthrough
2004 for( const auto& [name, altLayer] : altiumLayerNameMap )
2005 {
2006 if( reMappedLayers.find( name ) == reMappedLayers.end()
2007 || reMappedLayers.at( name ) == PCB_LAYER_ID::UNDEFINED_LAYER )
2008 {
2009 m_layermap.insert_or_assign( altLayer, PCB_LAYER_ID::UNDEFINED_LAYER );
2010 }
2011 }
2012
2013 m_board->SetEnabledLayers( enabledLayers );
2014 m_board->SetVisibleLayers( enabledLayers );
2015}
2016
2017
2018void ALTIUM_PCB::HelperFillMechanicalLayerAssignments( const std::vector<ABOARD6_LAYER_STACKUP>& aStackup )
2019{
2020 for( const ABOARD6_LAYER_STACKUP& layer : aStackup )
2021 {
2022 ALTIUM_LAYER alayer = static_cast<ALTIUM_LAYER>( layer.layerId );
2023
2024 if( ( alayer >= ALTIUM_LAYER::MECHANICAL_1 && alayer <= ALTIUM_LAYER::MECHANICAL_16 )
2026 {
2027 if( !layer.mechenabled )
2028 {
2029 m_layermap.emplace( alayer, UNDEFINED_LAYER ); // Disabled layer, do not import
2030 continue;
2031 }
2032
2034
2035 switch( layer.mechkind )
2036 {
2037 case ALTIUM_MECHKIND::ASSEMBLY_TOP: target = F_Fab; break;
2038 case ALTIUM_MECHKIND::ASSEMBLY_BOT: target = B_Fab; break;
2039
2040 case ALTIUM_MECHKIND::COURTYARD_TOP: target = F_CrtYd; break;
2041 case ALTIUM_MECHKIND::COURTYARD_BOT: target = B_CrtYd; break;
2042
2043 case ALTIUM_MECHKIND::GLUE_POINTS_TOP: target = F_Adhes; break;
2044 case ALTIUM_MECHKIND::GLUE_POINTS_BOT: target = B_Adhes; break;
2045
2046 case ALTIUM_MECHKIND::ASSEMBLY_NOTES: target = Cmts_User; break;
2047 case ALTIUM_MECHKIND::FAB_NOTES: target = Cmts_User; break;
2048
2049 case ALTIUM_MECHKIND::DIMENSIONS: target = Dwgs_User; break;
2050
2051 case ALTIUM_MECHKIND::DIMENSIONS_TOP: target = F_Fab; break;
2052 case ALTIUM_MECHKIND::DIMENSIONS_BOT: target = B_Fab; break;
2053
2054 case ALTIUM_MECHKIND::VALUE_TOP: target = F_Fab; break;
2055 case ALTIUM_MECHKIND::VALUE_BOT: target = B_Fab; break;
2056
2057 case ALTIUM_MECHKIND::DESIGNATOR_TOP: target = F_Fab; break;
2058 case ALTIUM_MECHKIND::DESIGNATOR_BOT: target = B_Fab; break;
2059
2060 case ALTIUM_MECHKIND::COMPONENT_OUTLINE_TOP: target = F_Fab; break;
2061 case ALTIUM_MECHKIND::COMPONENT_OUTLINE_BOT: target = B_Fab; break;
2062
2063 case ALTIUM_MECHKIND::COMPONENT_CENTER_TOP: target = F_Fab; break;
2064 case ALTIUM_MECHKIND::COMPONENT_CENTER_BOT: target = B_Fab; break;
2065
2066 // The board's own outline is the edge, so a drawing of the board shape would repeat it
2067 case ALTIUM_MECHKIND::BOARD: target = m_boardOutline.PointCount() >= 3 ? Dwgs_User : Edge_Cuts; break;
2068 case ALTIUM_MECHKIND::BOARD_SHAPE: target = m_boardOutline.PointCount() >= 3 ? Dwgs_User : Edge_Cuts; break;
2069 case ALTIUM_MECHKIND::V_CUT: target = Edge_Cuts; break;
2070
2071 default: break;
2072 }
2073
2074 if( target != UNDEFINED_LAYER )
2075 m_layermap.emplace( alayer, target );
2076 }
2077 }
2078}
2079
2080
2081void ALTIUM_PCB::HelperCreateBoardOutline( const std::vector<ALTIUM_VERTICE>& aVertices )
2082{
2083 SHAPE_LINE_CHAIN lineChain;
2084 HelperShapeLineChainFromAltiumVertices( lineChain, aVertices );
2085
2086 HelperCreateBoardOutline( lineChain );
2087}
2088
2089
2090namespace
2091{
2092struct OUTLINE_EDGE
2093{
2094 bool isArc = false;
2095 SHAPE_ARC arc;
2096 SEG seg;
2097};
2098
2099
2100std::vector<OUTLINE_EDGE> OutlineEdgesFromChain( const SHAPE_LINE_CHAIN& aChain )
2101{
2102 // vertices that differ by rounding leave edges DRC rejects as degenerate
2103 const int minEdge = pcbIUScale.mmToIU( 0.001 );
2104
2105 std::vector<OUTLINE_EDGE> edges;
2106
2107 for( int i = 0; i <= aChain.PointCount() && i != -1; i = aChain.NextShape( i ) )
2108 {
2109 if( aChain.IsArcStart( i ) )
2110 {
2111 edges.push_back( { true, aChain.Arc( aChain.ArcIndex( i ) ), SEG() } );
2112 }
2113 else if( aChain.Segment( i ).Length() > minEdge )
2114 {
2115 edges.push_back( { false, SHAPE_ARC(), aChain.Segment( i ) } );
2116 }
2117 }
2118
2119 return edges;
2120}
2121
2122
2123// A short edge between two longer ones is a jog from rounding in the file. A jog that turns
2124// back on its neighbours overlaps them, so drop it and let the neighbouring lines meet.
2125void RemoveOutlineJogs( std::vector<OUTLINE_EDGE>& aEdges, int& aCount, int& aLargestMove )
2126{
2127 const int maxJog = pcbIUScale.mmToIU( 0.05 );
2128
2129 // Direction an edge runs in where it starts or where it ends
2130 auto direction =
2131 []( const OUTLINE_EDGE& aEdge, bool aAtEnd ) -> VECTOR2I
2132 {
2133 if( !aEdge.isArc )
2134 return aEdge.seg.B - aEdge.seg.A;
2135
2136 const VECTOR2I radius = ( aAtEnd ? aEdge.arc.GetP1() : aEdge.arc.GetP0() ) - aEdge.arc.GetCenter();
2137
2138 return aEdge.arc.IsClockwise() ? VECTOR2I( radius.y, -radius.x ) : VECTOR2I( -radius.y, radius.x );
2139 };
2140
2141 for( bool changed = true; changed && aEdges.size() > 3; )
2142 {
2143 changed = false;
2144
2145 for( size_t i = 0; i < aEdges.size(); i++ )
2146 {
2147 OUTLINE_EDGE& jog = aEdges[i];
2148 OUTLINE_EDGE& prev = aEdges[( i + aEdges.size() - 1 ) % aEdges.size()];
2149 OUTLINE_EDGE& next = aEdges[( i + 1 ) % aEdges.size()];
2150
2151 if( jog.isArc || jog.seg.Length() > maxJog || ( prev.isArc && next.isArc ) )
2152 continue;
2153
2154 if( ( !prev.isArc && prev.seg.Length() <= maxJog ) || ( !next.isArc && next.seg.Length() <= maxJog ) )
2155 continue;
2156
2157 // Only a jog that doubles back on a neighbour overlaps it, a short chamfer is real geometry
2158 const VECTOR2I jogDirection = jog.seg.B - jog.seg.A;
2159
2160 if( jogDirection.Dot( direction( prev, true ) ) >= 0 && jogDirection.Dot( direction( next, false ) ) >= 0 )
2161 continue;
2162
2163 auto nearer = []( const SHAPE_ARC& aArc, const VECTOR2I& aPt )
2164 {
2165 return aArc.GetP0().Distance( aPt ) < aArc.GetP1().Distance( aPt ) ? aArc.GetP0() : aArc.GetP1();
2166 };
2167
2169
2170 if( prev.isArc )
2171 {
2172 moved = nearer( prev.arc, jog.seg.A );
2173 aLargestMove = std::max( aLargestMove, static_cast<int>( moved.Distance( next.seg.A ) ) );
2174 next.seg.A = moved;
2175 }
2176 else if( next.isArc )
2177 {
2178 moved = nearer( next.arc, jog.seg.B );
2179 aLargestMove = std::max( aLargestMove, static_cast<int>( moved.Distance( prev.seg.B ) ) );
2180 prev.seg.B = moved;
2181 }
2182 else
2183 {
2184 aLargestMove = std::max( aLargestMove, static_cast<int>( prev.seg.B.Distance( next.seg.A ) ) );
2185 next.seg.A = prev.seg.B;
2186 }
2187
2188 aCount++;
2189
2190 aEdges.erase( aEdges.begin() + i );
2191 changed = true;
2192 break;
2193 }
2194 }
2195}
2196
2197
2198void AddOutlineEdges( BOARD& aBoard, const std::vector<OUTLINE_EDGE>& aEdges )
2199{
2201
2202 for( const OUTLINE_EDGE& edge : aEdges )
2203 {
2204 std::unique_ptr<PCB_SHAPE> shape;
2205
2206 if( edge.isArc )
2207 {
2208 shape = std::make_unique<PCB_SHAPE>( &aBoard, SHAPE_T::ARC );
2209 shape->SetArcGeometry( edge.arc.GetP0(), edge.arc.GetArcMid(), edge.arc.GetP1() );
2210 }
2211 else
2212 {
2213 shape = std::make_unique<PCB_SHAPE>( &aBoard, SHAPE_T::SEGMENT );
2214 shape->SetStart( edge.seg.A );
2215 shape->SetEnd( edge.seg.B );
2216 }
2217
2218 shape->SetStroke( stroke );
2219 shape->SetLayer( Edge_Cuts );
2220
2221 aBoard.Add( shape.release(), ADD_MODE::APPEND );
2222 }
2223}
2224
2225
2226// How far along an edge a point on it lies, 0 at its start and 1 at its end
2227double FractionAlongEdge( const OUTLINE_EDGE& aEdge, const VECTOR2I& aPt )
2228{
2229 if( !aEdge.isArc )
2230 {
2231 VECTOR2D along = aEdge.seg.B - aEdge.seg.A;
2232 return VECTOR2D( aPt - aEdge.seg.A ).Dot( along ) / along.SquaredEuclideanNorm();
2233 }
2234
2235 const VECTOR2I centre = aEdge.arc.GetCenter();
2236 const EDA_ANGLE sweep = aEdge.arc.GetCentralAngle();
2237 EDA_ANGLE turned = EDA_ANGLE( aPt - centre ) - EDA_ANGLE( aEdge.arc.GetP0() - centre );
2238
2239 if( sweep.AsDegrees() < 0 )
2240 turned = -turned;
2241
2242 turned.Normalize();
2243 return turned.AsDegrees() / std::abs( sweep.AsDegrees() );
2244}
2245
2246
2247VECTOR2I PointAlongEdge( const OUTLINE_EDGE& aEdge, double aFraction )
2248{
2249 if( aFraction <= 0 )
2250 return aEdge.isArc ? aEdge.arc.GetP0() : aEdge.seg.A;
2251
2252 if( aFraction >= 1 )
2253 return aEdge.isArc ? aEdge.arc.GetP1() : aEdge.seg.B;
2254
2255 if( !aEdge.isArc )
2256 return aEdge.seg.A + KiROUND( VECTOR2D( aEdge.seg.B - aEdge.seg.A ) * aFraction );
2257
2258 VECTOR2I pt = aEdge.arc.GetP0();
2259 RotatePoint( pt, aEdge.arc.GetCenter(), -aEdge.arc.GetCentralAngle() * aFraction );
2260 return pt;
2261}
2262
2263
2264// An edge is part of the outline only where there is board on one side of it and none on the other
2265bool BoundsMaterial( const OUTLINE_EDGE& aEdge, double aFraction, const SHAPE_POLY_SET& aMaterial )
2266{
2267 const double probe = pcbIUScale.mmToIU( 0.05 );
2268 VECTOR2I pt = PointAlongEdge( aEdge, aFraction );
2270
2271 if( aEdge.isArc )
2272 normal = VECTOR2D( pt - aEdge.arc.GetCenter() ).Resize( 1.0 );
2273 else
2274 normal = VECTOR2D( aEdge.seg.B - aEdge.seg.A ).Perpendicular().Resize( 1.0 );
2275
2276 bool left = aMaterial.Contains( pt + KiROUND( normal * probe ) );
2277 bool right = aMaterial.Contains( pt - KiROUND( normal * probe ) );
2278
2279 return left != right;
2280}
2281
2282
2283// The parts of the edges that lie inside, or outside, the region; a part on a cutter is kept only where it
2284// bounds aMaterial
2285std::vector<OUTLINE_EDGE> ClipOutlineEdges( const std::vector<OUTLINE_EDGE>& aEdges,
2286 const std::vector<const SHAPE_LINE_CHAIN*>& aCutters,
2287 const SHAPE_POLY_SET& aRegion, bool aKeepInside,
2288 const SHAPE_POLY_SET* aMaterial )
2289{
2290 const int onCutter = pcbIUScale.mmToIU( 0.005 );
2291 std::vector<OUTLINE_EDGE> kept;
2292
2293 for( const OUTLINE_EDGE& edge : aEdges )
2294 {
2295 // Distance to the lines of a cutter, since the inside of a closed chain is at distance zero
2296 auto nearCutter =
2297 [&]( const VECTOR2I& aPt )
2298 {
2299 for( const SHAPE_LINE_CHAIN* cutter : aCutters )
2300 {
2301 if( cutter->SquaredDistance( aPt, true ) <= SEG::Square( onCutter ) )
2302 return true;
2303 }
2304
2305 return false;
2306 };
2307
2309
2310 if( edge.isArc )
2311 {
2312 chain.Append( edge.arc );
2313 }
2314 else
2315 {
2316 chain.Append( edge.seg.A );
2317 chain.Append( edge.seg.B );
2318 }
2319
2320 std::vector<double> cuts = { 0.0, 1.0 };
2321
2322 for( const SHAPE_LINE_CHAIN* cutter : aCutters )
2323 {
2324 std::vector<SHAPE_LINE_CHAIN::INTERSECTION> crossings;
2325 chain.Intersect( *cutter, crossings );
2326
2327 for( const SHAPE_LINE_CHAIN::INTERSECTION& crossing : crossings )
2328 cuts.push_back( std::clamp( FractionAlongEdge( edge, crossing.p ), 0.0, 1.0 ) );
2329 }
2330
2331 std::sort( cuts.begin(), cuts.end() );
2332
2333 // A vertex both chains share is reported twice, and a zero-length piece has no side
2334 cuts.erase( std::unique( cuts.begin(), cuts.end(),
2335 [&]( double aLeft, double aRight )
2336 {
2337 return PointAlongEdge( edge, aLeft ) == PointAlongEdge( edge, aRight );
2338 } ),
2339 cuts.end() );
2340
2341 enum class SIDE { KEEP, DROP, UNSURE };
2342
2343 auto subEdge =
2344 [&]( double aFrom, double aTo )
2345 {
2346 if( aFrom <= 0.0 && aTo >= 1.0 )
2347 return edge;
2348
2349 OUTLINE_EDGE piece = edge;
2350 VECTOR2I start = PointAlongEdge( edge, aFrom );
2351 VECTOR2I end = PointAlongEdge( edge, aTo );
2352
2353 if( edge.isArc )
2354 piece.arc = SHAPE_ARC( start, PointAlongEdge( edge, ( aFrom + aTo ) / 2 ), end, 0 );
2355 else
2356 piece.seg = SEG( start, end );
2357
2358 return piece;
2359 };
2360
2361 auto classify =
2362 [&]( size_t aPiece )
2363 {
2364 OUTLINE_EDGE piece = subEdge( cuts[aPiece], cuts[aPiece + 1] );
2365 VECTOR2I mid = PointAlongEdge( edge, ( cuts[aPiece] + cuts[aPiece + 1] ) / 2 );
2366 bool keep = nearCutter( mid ) ? aMaterial && BoundsMaterial( piece, 0.5, *aMaterial )
2367 : aRegion.Contains( mid ) == aKeepInside;
2368
2369 return keep ? SIDE::KEEP : SIDE::DROP;
2370 };
2371
2372 auto span =
2373 [&]( size_t aFrom, size_t aTo )
2374 {
2375 return PointAlongEdge( edge, cuts[aFrom] ).Distance( PointAlongEdge( edge, cuts[aTo] ) );
2376 };
2377
2378 std::vector<SIDE> sides;
2379
2380 // Pieces within the cutter tolerance cannot be classified reliably, so they are settled by run below
2381 for( size_t i = 0; i + 1 < cuts.size(); i++ )
2382 sides.push_back( span( i, i + 1 ) <= onCutter ? SIDE::UNSURE : classify( i ) );
2383
2384 for( size_t i = 0; i < sides.size(); )
2385 {
2386 if( sides[i] != SIDE::UNSURE )
2387 {
2388 i++;
2389 continue;
2390 }
2391
2392 size_t j = i;
2393
2394 while( j < sides.size() && sides[j] == SIDE::UNSURE )
2395 j++;
2396
2397 bool hasNeighbour = i > 0 || j < sides.size();
2398
2399 // A short run joins a kept neighbour, while a long run of short pieces is still a real span
2400 if( hasNeighbour && span( i, j ) <= onCutter )
2401 {
2402 bool nextToKept = ( i > 0 && sides[i - 1] == SIDE::KEEP )
2403 || ( j < sides.size() && sides[j] == SIDE::KEEP );
2404
2405 for( size_t k = i; k < j; k++ )
2406 sides[k] = nextToKept ? SIDE::KEEP : SIDE::DROP;
2407 }
2408 else
2409 {
2410 for( size_t k = i; k < j; k++ )
2411 sides[k] = classify( k );
2412 }
2413
2414 i = j;
2415 }
2416
2417 // Each kept run is one piece, as a shared edge meets its cutter at every cutter vertex
2418 for( size_t i = 0; i < sides.size(); )
2419 {
2420 if( sides[i] != SIDE::KEEP )
2421 {
2422 i++;
2423 continue;
2424 }
2425
2426 size_t j = i;
2427
2428 while( j < sides.size() && sides[j] == SIDE::KEEP )
2429 j++;
2430
2431 kept.push_back( subEdge( cuts[i], cuts[j] ) );
2432 i = j;
2433 }
2434 }
2435
2436 return kept;
2437}
2438} // namespace
2439
2440
2442{
2443 std::vector<OUTLINE_EDGE> edges = OutlineEdgesFromChain( aOutline );
2444
2445 RemoveOutlineJogs( edges, m_outlineJogCount, m_outlineJogLargest );
2446 AddOutlineEdges( *m_board, edges );
2447}
2448
2449
2451{
2453 board.SetClosed( true );
2454
2455 std::vector<OUTLINE_EDGE> edges = OutlineEdgesFromChain( m_boardOutline );
2456 SHAPE_POLY_SET region;
2457 bool merged = false;
2458
2459 if( board.PointCount() >= 3 )
2460 region.AddOutline( board );
2461
2462 std::vector<SHAPE_LINE_CHAIN> separate;
2463 std::vector<const SHAPE_LINE_CHAIN*> applied = { &board };
2464
2465 // The cutouts must keep their addresses while they are referenced
2466 std::vector<SHAPE_LINE_CHAIN> cutouts = m_boardCutouts;
2467
2468 for( SHAPE_LINE_CHAIN& cutout : cutouts )
2469 {
2470 cutout.SetClosed( true );
2471
2472 std::vector<SHAPE_LINE_CHAIN::INTERSECTION> crossings;
2473
2474 if( board.PointCount() < 3 || cutout.PointCount() < 3 || board.Intersect( cutout, crossings ) == 0 )
2475 {
2476 separate.push_back( cutout );
2477 continue;
2478 }
2479
2480 // A cutout over the board edge reshapes the outline: keep the board edges outside it and the
2481 // cutout edges inside the board, cutting arcs and lines only where they cross
2482 SHAPE_POLY_SET cutArea;
2483 cutArea.AddOutline( cutout );
2484
2485 // Boolean operations are not defined for arcs, and only the area is needed from the region
2486 SHAPE_POLY_SET material = region.CloneDropTriangulation();
2487 SHAPE_POLY_SET cutAreaFlat = cutArea.CloneDropTriangulation();
2488 material.ClearArcs();
2489 cutAreaFlat.ClearArcs();
2490 material.BooleanSubtract( cutAreaFlat );
2491
2492 // The cutout is cut by the board and by every cutout already applied, as they all bound the material.
2493 // A shared edge is taken from the board side, and only where the cut leaves board on one side of it
2494 std::vector<OUTLINE_EDGE> kept = ClipOutlineEdges( edges, { &cutout }, cutArea, false, &material );
2495 std::vector<OUTLINE_EDGE> inside =
2496 ClipOutlineEdges( OutlineEdgesFromChain( cutout ), applied, region, true, nullptr );
2497
2498 kept.insert( kept.end(), inside.begin(), inside.end() );
2499 edges = std::move( kept );
2500 region = std::move( material );
2501 applied.push_back( &cutout );
2502 merged = true;
2503 }
2504
2505 if( merged )
2506 {
2507 AddOutlineEdges( *m_board, edges );
2508 }
2509 else
2510 {
2512 }
2513
2514 for( const SHAPE_LINE_CHAIN& cutout : separate )
2515 HelperCreateBoardOutline( cutout );
2516
2517 m_boardOutline.Clear();
2518 m_boardCutouts.clear();
2519
2520 if( m_outlineJogCount > 0 && m_reporter )
2521 {
2522 m_reporter->Report( wxString::Format( _( "%d tiny jogs in the board outline were removed; the largest "
2523 "moved an outline end by %s." ),
2525 wxString::FromCDouble( pcbIUScale.IUTomm( m_outlineJogLargest ), 4 )
2526 + wxS( " mm" ) ),
2528 }
2529}
2530
2531
2533 const CFB::COMPOUND_FILE_ENTRY* aEntry )
2534{
2535 if( m_progressReporter )
2536 m_progressReporter->Report( _( "Loading netclasses..." ) );
2537
2538 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
2539
2540 while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
2541 {
2542 checkpoint();
2543 ACLASS6 elem( reader );
2544
2546 {
2547 std::shared_ptr<NETCLASS> nc = std::make_shared<NETCLASS>( elem.name, false );
2548
2549 for( const wxString& name : elem.names )
2550 {
2551 m_board->GetDesignSettings().m_NetSettings->SetNetclassPatternAssignment( SchematicCasedNetName( name ),
2552 nc->GetName() );
2553 }
2554
2555 if( m_board->GetDesignSettings().m_NetSettings->HasNetclass( nc->GetName() ) )
2556 {
2557 // Name conflict, happens in some unknown circumstances
2558 // unique_ptr will delete nc on this code path
2559 if( m_reporter )
2560 {
2561 wxString msg;
2562 msg.Printf( _( "More than one Altium netclass with name '%s' found. "
2563 "Only the first one will be imported." ), elem.name );
2564 m_reporter->Report( msg, RPT_SEVERITY_ERROR );
2565 }
2566 }
2567 else
2568 {
2569 m_board->GetDesignSettings().m_NetSettings->SetNetclass( nc->GetName(), nc );
2570 }
2571 }
2572 }
2573
2574 if( reader.GetRemainingBytes() != 0 )
2575 THROW_IO_ERROR( wxT( "Classes6 stream is not fully parsed" ) );
2576
2577 // Now that all netclasses and pattern assignments are set up, resolve the pattern
2578 // assignments to direct netclass assignments on each net.
2580
2581 m_board->m_LegacyNetclassesLoaded = true;
2582}
2583
2584
2586{
2587 std::shared_ptr<NET_SETTINGS> netSettings = m_board->GetDesignSettings().m_NetSettings;
2588
2589 netSettings->RecomputeEffectiveNetclasses();
2590
2591 for( NETINFO_ITEM* net : m_board->GetNetInfo() )
2592 {
2593 if( net->GetNetCode() <= 0 )
2594 continue;
2595
2596 if( std::shared_ptr<NETCLASS> netclass = netSettings->GetEffectiveNetClass( net->GetNetname() ) )
2597 net->SetNetClass( netclass );
2598 }
2599}
2600
2601
2603 const CFB::COMPOUND_FILE_ENTRY* aEntry )
2604{
2605 if( m_progressReporter )
2606 m_progressReporter->Report( _( "Loading components..." ) );
2607
2608 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
2609
2610 while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
2611 {
2612 checkpoint();
2613 ACOMPONENT6 elem( reader );
2614
2615 std::unique_ptr<FOOTPRINT> footprint = std::make_unique<FOOTPRINT>( m_board );
2616
2617 // Altium stores the footprint library information needed to find the footprint in the
2618 // source library in the sourcefootprintlibrary field. Since Altium is a Windows-only
2619 // program, the path separator is always a backslash. We need strip the extra path information
2620 // here to prevent overly-long LIB_IDs because KiCad doesn't store the full path to the
2621 // footprint library in the design file, only in a library table.
2622 wxFileName libName( elem.sourcefootprintlibrary, wxPATH_WIN );
2623
2624 // The pattern field may also contain a path when Altium stores it with a full library path.
2625 // Extract just the footprint name portion to avoid creating invalid filenames.
2626 wxString fpName = elem.pattern;
2627
2628 if( fpName.Contains( wxT( "\\" ) ) || fpName.Contains( wxT( "/" ) ) )
2629 {
2630 wxFileName fpPath( fpName, wxPATH_WIN );
2631 fpName = fpPath.GetFullName();
2632 }
2633
2634 LIB_ID fpID = AltiumToKiCadLibID( libName.GetName(), fpName );
2635
2636 footprint->SetFPID( fpID );
2637
2638 footprint->SetPosition( elem.position );
2639 footprint->SetOrientationDegrees( elem.rotation );
2640
2641 // KiCad netlisting requires parts to have non-digit + digit annotation.
2642 // If the reference begins with a number, we prepend 'UNK' (unknown) for the source designator
2643 wxString reference = elem.sourcedesignator;
2644
2645 if( reference.find_first_not_of( "0123456789" ) == wxString::npos )
2646 reference.Prepend( wxT( "UNK" ) );
2647
2648 footprint->SetReference( reference );
2649
2651 KIID pathid( elem.sourceHierachicalPath );
2653 path.push_back( pathid );
2654 path.push_back( id );
2655
2656 footprint->SetPath( path );
2657 footprint->SetSheetname( elem.sourceHierachicalPath );
2658 footprint->SetSheetfile( elem.sourceHierachicalPath + wxT( ".kicad_sch" ));
2659
2660 footprint->SetLocked( elem.locked );
2661 footprint->Reference().SetVisible( elem.nameon );
2662 footprint->Value().SetVisible( elem.commenton );
2663 footprint->SetLayer( elem.layer == ALTIUM_LAYER::TOP_LAYER ? F_Cu : B_Cu );
2664 footprint->SetHeight( elem.height );
2665
2666 m_components.emplace_back( footprint.get() );
2667 m_board->Add( footprint.release(), ADD_MODE::APPEND );
2668 }
2669
2670 if( reader.GetRemainingBytes() != 0 )
2671 THROW_IO_ERROR( wxT( "Components6 stream is not fully parsed" ) );
2672}
2673
2674
2676double normalizeAngleDegrees( double Angle, double aMin, double aMax )
2677{
2678 while( Angle < aMin )
2679 Angle += 360.0;
2680
2681 while( Angle >= aMax )
2682 Angle -= 360.0;
2683
2684 return Angle;
2685}
2686
2687
2689 FOOTPRINT* aFootprint,
2690 const ACOMPONENTBODY6& aElem )
2691{
2692 if( m_progressReporter )
2693 m_progressReporter->Report( _( "Loading component 3D models..." ) );
2694
2695 if( !aElem.modelIsEmbedded )
2696 return;
2697
2698 auto model = aAltiumPcbFile.GetLibModel( aElem.modelId );
2699
2700 if( !model )
2701 {
2702 if( m_reporter )
2703 {
2704 m_reporter->Report( wxString::Format( wxT( "Model %s not found for footprint %s" ),
2705 aElem.modelId, aFootprint->GetReference() ),
2707 }
2708
2709 return;
2710 }
2711
2712 wxString modelName = aElem.modelName.IsEmpty() ? model->first.name : aElem.modelName;
2713 bool isNew = false;
2714
2715 std::shared_ptr<EMBEDDED_FILES::EMBEDDED_FILE> file =
2716 HelperEmbedModel( aFootprint, modelName, model->second, isNew );
2717
2718 if( isNew )
2720
2721 FP_3DMODEL modelSettings;
2722
2723 modelSettings.m_Filename = file->GetLink();
2724
2725 modelSettings.m_Offset.x = pcbIUScale.IUTomm( (int) aElem.modelPosition.x );
2726 modelSettings.m_Offset.y = -pcbIUScale.IUTomm( (int) aElem.modelPosition.y );
2727 modelSettings.m_Offset.z = pcbIUScale.IUTomm( (int) aElem.modelPosition.z );
2728
2729 EDA_ANGLE orientation = aFootprint->GetOrientation();
2730
2731 if( aFootprint->IsFlipped() )
2732 {
2733 modelSettings.m_Offset.y = -modelSettings.m_Offset.y;
2734 orientation = -orientation;
2735 }
2736
2737 VECTOR3D modelRotation( aElem.modelRotation );
2738
2739 if( ( aElem.body_projection == 1 ) != aFootprint->IsFlipped() )
2740 {
2741 modelRotation.x += 180;
2742 modelRotation.z = -modelRotation.z;
2743
2744 modelSettings.m_Offset.z = -DEFAULT_BOARD_THICKNESS_MM - modelSettings.m_Offset.z;
2745 }
2746
2747 RotatePoint( &modelSettings.m_Offset.x, &modelSettings.m_Offset.y, orientation );
2748
2749 modelSettings.m_Rotation.x = normalizeAngleDegrees( -modelRotation.x, -180, 180 );
2750 modelSettings.m_Rotation.y = normalizeAngleDegrees( -modelRotation.y, -180, 180 );
2751 modelSettings.m_Rotation.z = normalizeAngleDegrees( -modelRotation.z + aElem.rotation
2752 + orientation.AsDegrees(),
2753 -180, 180 );
2754 modelSettings.m_Opacity = aElem.body_opacity_3d;
2755
2756 aFootprint->Models().push_back( modelSettings );
2757}
2758
2759
2761 const CFB::COMPOUND_FILE_ENTRY* aEntry )
2762{
2763 if( m_progressReporter )
2764 m_progressReporter->Report( _( "Loading component 3D models..." ) );
2765
2767 BS::multi_future<void> embeddedFutures;
2768
2769 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
2770
2771 while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
2772 {
2773 checkpoint();
2774 ACOMPONENTBODY6 elem( reader );
2775
2776 static const bool skipComponentBodies = ADVANCED_CFG::GetCfg().m_ImportSkipComponentBodies;
2777
2778 if( skipComponentBodies )
2779 continue;
2780
2781 if( elem.component == ALTIUM_COMPONENT_NONE )
2782 continue; // TODO: we do not support components for the board yet
2783
2784 if( m_components.size() <= elem.component )
2785 {
2786 THROW_IO_ERRORF( wxT( "ComponentsBodies6 stream tries to access component id %d of %zu existing "
2787 "components" ),
2788 elem.component,
2789 m_components.size() );
2790 }
2791
2792 if( !elem.modelIsEmbedded )
2793 continue;
2794
2795 auto modelTuple = m_EmbeddedModels.find( elem.modelId );
2796
2797 if( modelTuple == m_EmbeddedModels.end() )
2798 {
2799 if( m_reporter )
2800 {
2801 wxString msg;
2802 msg.Printf( wxT( "ComponentsBodies6 stream tries to access model id %s which does "
2803 "not exist" ), elem.modelId );
2804 m_reporter->Report( msg, RPT_SEVERITY_ERROR );
2805 }
2806
2807 continue;
2808 }
2809
2810 const ALTIUM_EMBEDDED_MODEL_DATA& modelData = modelTuple->second;
2811 FOOTPRINT* footprint = m_components.at( elem.component );
2812 bool isNew = false;
2813
2814 std::shared_ptr<EMBEDDED_FILES::EMBEDDED_FILE> file =
2815 HelperEmbedModel( footprint, modelData.m_modelname, modelData.m_data, isNew );
2816
2817 if( isNew )
2818 {
2819 // The task has to own the payload too; a throw further down the stream skips the
2820 // wait below and multi_future abandons whatever is still running
2821 embeddedFutures.push_back( tp.submit_task(
2822 [file]()
2823 {
2824 EMBEDDED_FILES::CompressAndEncode( *file );
2825 } ) );
2826 }
2827
2828 FP_3DMODEL modelSettings;
2829
2830 modelSettings.m_Filename = file->GetLink();
2831 VECTOR2I fpPosition = footprint->GetPosition();
2832
2833 modelSettings.m_Offset.x =
2834 pcbIUScale.IUTomm( KiROUND( elem.modelPosition.x - fpPosition.x ) );
2835 modelSettings.m_Offset.y =
2836 -pcbIUScale.IUTomm( KiROUND( elem.modelPosition.y - fpPosition.y ) );
2837 modelSettings.m_Offset.z = pcbIUScale.IUTomm( KiROUND( elem.modelPosition.z ) );
2838
2839 EDA_ANGLE orientation = footprint->GetOrientation();
2840
2841 if( footprint->IsFlipped() )
2842 {
2843 modelSettings.m_Offset.y = -modelSettings.m_Offset.y;
2844 orientation = -orientation;
2845 }
2846
2847 if( ( elem.body_projection == 1 ) != footprint->IsFlipped() )
2848 {
2849 elem.modelRotation.x += 180;
2850 elem.modelRotation.z = -elem.modelRotation.z;
2851
2852 modelSettings.m_Offset.z =
2853 -pcbIUScale.IUTomm( m_board->GetDesignSettings().GetBoardThickness() )
2854 - modelSettings.m_Offset.z;
2855 }
2856
2857 RotatePoint( &modelSettings.m_Offset.x, &modelSettings.m_Offset.y, orientation );
2858
2859 modelSettings.m_Rotation.x = normalizeAngleDegrees( -elem.modelRotation.x, -180, 180 );
2860 modelSettings.m_Rotation.y = normalizeAngleDegrees( -elem.modelRotation.y, -180, 180 );
2861 modelSettings.m_Rotation.z = normalizeAngleDegrees( -elem.modelRotation.z + elem.rotation
2862 + orientation.AsDegrees(),
2863 -180, 180 );
2864
2865 modelSettings.m_Opacity = elem.body_opacity_3d;
2866
2867 footprint->Models().push_back( modelSettings );
2868 }
2869
2870 embeddedFutures.wait();
2871
2872 if( reader.GetRemainingBytes() != 0 )
2873 THROW_IO_ERROR( wxT( "ComponentsBodies6 stream is not fully parsed" ) );
2874}
2875
2876
2878{
2879 if( aElem.referencePoint.size() != 2 )
2880 THROW_IO_ERROR( wxT( "Incorrect number of reference points for linear dimension object" ) );
2881
2882 PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
2883
2884 if( klayer == UNDEFINED_LAYER )
2885 {
2886 if( m_reporter )
2887 {
2888 m_reporter->Report( wxString::Format(
2889 _( "Dimension found on an Altium layer (%d) with no KiCad equivalent. "
2890 "It has been moved to KiCad layer Eco1_User." ), aElem.layer ),
2892 }
2893
2894 klayer = Eco1_User;
2895 }
2896
2897 VECTOR2I referencePoint0 = aElem.referencePoint.at( 0 );
2898 VECTOR2I referencePoint1 = aElem.referencePoint.at( 1 );
2899
2900 std::unique_ptr<PCB_DIM_ALIGNED> dimension = std::make_unique<PCB_DIM_ALIGNED>( m_board, PCB_DIM_ALIGNED_T );
2901
2902 dimension->SetPrecision( static_cast<DIM_PRECISION>( aElem.textprecision ) );
2903 dimension->SetLayer( klayer );
2904 dimension->SetStart( referencePoint0 );
2905
2906 if( referencePoint0 != aElem.xy1 )
2907 {
2917 VECTOR2I direction = aElem.xy1 - referencePoint0;
2918 VECTOR2I referenceDiff = referencePoint1 - referencePoint0;
2919 VECTOR2I directionNormalVector = direction.Perpendicular();
2920 SEG segm1( referencePoint0, referencePoint0 + directionNormalVector );
2921 SEG segm2( referencePoint1, referencePoint1 + direction );
2922 OPT_VECTOR2I intersection( segm1.Intersect( segm2, true, true ) );
2923
2924 if( !intersection )
2925 THROW_IO_ERROR( wxT( "Invalid dimension. This should never happen." ) );
2926
2927 dimension->SetEnd( *intersection );
2928
2929 int height = direction.EuclideanNorm();
2930
2931 if( direction.Cross( referenceDiff ) > 0 )
2932 height = -height;
2933
2934 dimension->SetHeight( height );
2935 }
2936 else
2937 {
2938 dimension->SetEnd( referencePoint1 );
2939 }
2940
2941 dimension->SetLineThickness( aElem.linewidth );
2942
2943 dimension->SetUnitsFormat( DIM_UNITS_FORMAT::NO_SUFFIX );
2944 dimension->SetPrefix( aElem.textprefix );
2945
2946
2947 int dist = ( dimension->GetEnd() - dimension->GetStart() ).EuclideanNorm();
2948
2949 if( dist < 3 * dimension->GetArrowLength() )
2950 dimension->SetArrowDirection( DIM_ARROW_DIRECTION::INWARD );
2951
2952 // Suffix normally (but not always) holds the units
2953 wxRegEx units( wxS( "(mm)|(in)|(mils)|(thou)|(')|(\")" ), wxRE_ADVANCED );
2954
2955 if( units.Matches( aElem.textsuffix ) )
2956 dimension->SetUnitsFormat( DIM_UNITS_FORMAT::BARE_SUFFIX );
2957 else
2958 dimension->SetSuffix( aElem.textsuffix );
2959
2960 dimension->SetTextThickness( aElem.textlinewidth );
2961 dimension->SetTextSize( VECTOR2I( aElem.textheight, aElem.textheight ) );
2962 dimension->SetItalic( aElem.textitalic );
2963
2964#if 0 // we don't currently support bold; map to thicker text
2965 dimension->Text().SetBold( aElem.textbold );
2966#else
2967 if( aElem.textbold )
2968 dimension->SetTextThickness( dimension->GetTextThickness() * BOLD_FACTOR );
2969#endif
2970
2971 switch( aElem.textunit )
2972 {
2973 case ALTIUM_UNIT::INCH: dimension->SetUnits( EDA_UNITS::INCH ); break;
2974 case ALTIUM_UNIT::MILS: dimension->SetUnits( EDA_UNITS::MILS ); break;
2975 case ALTIUM_UNIT::MM: dimension->SetUnits( EDA_UNITS::MM ); break;
2976 case ALTIUM_UNIT::CM: dimension->SetUnits( EDA_UNITS::MM ); break;
2977 default: break;
2978 }
2979
2980 m_board->Add( dimension.release(), ADD_MODE::APPEND );
2981}
2982
2983
2985{
2986 if( aElem.referencePoint.size() < 2 )
2987 THROW_IO_ERROR( wxT( "Not enough reference points for radial dimension object" ) );
2988
2989 PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
2990
2991 if( klayer == UNDEFINED_LAYER )
2992 {
2993 if( m_reporter )
2994 {
2995 m_reporter->Report( wxString::Format(
2996 _( "Dimension found on an Altium layer (%d) with no KiCad equivalent. "
2997 "It has been moved to KiCad layer Eco1_User." ),
2998 aElem.layer ), RPT_SEVERITY_INFO );
2999 }
3000
3001 klayer = Eco1_User;
3002 }
3003
3004 VECTOR2I referencePoint0 = aElem.referencePoint.at( 0 );
3005
3006 std::unique_ptr<PCB_DIM_RADIAL> dimension = std::make_unique<PCB_DIM_RADIAL>( m_board );
3007
3008 dimension->SetPrecision( static_cast<DIM_PRECISION>( aElem.textprecision ) );
3009 dimension->SetLayer( klayer );
3010 dimension->SetStart( referencePoint0 );
3011 dimension->SetEnd( aElem.xy1 );
3012 dimension->SetLineThickness( aElem.linewidth );
3013 dimension->SetKeepTextAligned( false );
3014
3015 dimension->SetPrefix( aElem.textprefix );
3016
3017 // Suffix normally holds the units
3018 dimension->SetUnitsFormat( aElem.textsuffix.IsEmpty() ? DIM_UNITS_FORMAT::NO_SUFFIX
3020
3021 switch( aElem.textunit )
3022 {
3023 case ALTIUM_UNIT::INCH: dimension->SetUnits( EDA_UNITS::INCH ); break;
3024 case ALTIUM_UNIT::MILS: dimension->SetUnits( EDA_UNITS::MILS ); break;
3025 case ALTIUM_UNIT::MM: dimension->SetUnits( EDA_UNITS::MM ); break;
3026 case ALTIUM_UNIT::CM: dimension->SetUnits( EDA_UNITS::MM ); break;
3027 default: break;
3028 }
3029
3030 if( aElem.textPoint.empty() )
3031 {
3032 if( m_reporter )
3033 {
3034 m_reporter->Report( wxT( "No text position present for leader dimension object" ),
3036 }
3037
3038 return;
3039 }
3040
3041 dimension->SetTextPos( aElem.textPoint.at( 0 ) );
3042 dimension->SetTextThickness( aElem.textlinewidth );
3043 dimension->SetTextSize( VECTOR2I( aElem.textheight, aElem.textheight ) );
3044 dimension->SetItalic( aElem.textitalic );
3045
3046#if 0 // we don't currently support bold; map to thicker text
3047 dimension->SetBold( aElem.textbold );
3048#else
3049 if( aElem.textbold )
3050 dimension->SetTextThickness( dimension->GetTextThickness() * BOLD_FACTOR );
3051#endif
3052
3053 // It's unclear exactly how Altium figures it's text positioning, but this gets us reasonably
3054 // close.
3055 dimension->SetVertJustify( GR_TEXT_V_ALIGN_BOTTOM );
3056 dimension->SetHorizJustify( GR_TEXT_H_ALIGN_LEFT );
3057
3058 int yAdjust = dimension->GetTextBox( nullptr ).GetCenter().y - dimension->GetTextPos().y;
3059 dimension->SetTextPos( dimension->GetTextPos() + VECTOR2I( 0, yAdjust + aElem.textgap ) );
3060 dimension->SetVertJustify( GR_TEXT_V_ALIGN_CENTER );
3061
3062 m_radialDimensions.push_back( dimension.get() );
3063 m_board->Add( dimension.release(), ADD_MODE::APPEND );
3064}
3065
3066
3068{
3069 PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
3070
3071 if( klayer == UNDEFINED_LAYER )
3072 {
3073 if( m_reporter )
3074 {
3075 wxString msg;
3076 msg.Printf( _( "Dimension found on an Altium layer (%d) with no KiCad equivalent. "
3077 "It has been moved to KiCad layer Eco1_User." ), aElem.layer );
3078 m_reporter->Report( msg, RPT_SEVERITY_ERROR );
3079 }
3080
3081 klayer = Eco1_User;
3082 }
3083
3084 if( !aElem.referencePoint.empty() )
3085 {
3086 VECTOR2I referencePoint0 = aElem.referencePoint.at( 0 );
3087
3088 // line
3089 VECTOR2I last = referencePoint0;
3090 for( size_t i = 1; i < aElem.referencePoint.size(); i++ )
3091 {
3092 std::unique_ptr<PCB_SHAPE> shape = std::make_unique<PCB_SHAPE>( m_board, SHAPE_T::SEGMENT );
3093
3094 shape->SetLayer( klayer );
3095 shape->SetStroke( STROKE_PARAMS( aElem.linewidth, LINE_STYLE::SOLID ) );
3096 shape->SetStart( last );
3097 shape->SetEnd( aElem.referencePoint.at( i ) );
3098 last = aElem.referencePoint.at( i );
3099
3100 m_board->Add( shape.release(), ADD_MODE::APPEND );
3101 }
3102
3103 // arrow
3104 if( aElem.referencePoint.size() >= 2 )
3105 {
3106 VECTOR2I dirVec = aElem.referencePoint.at( 1 ) - referencePoint0;
3107
3108 if( dirVec.x != 0 || dirVec.y != 0 )
3109 {
3110 double scaling = (double) dirVec.EuclideanNorm() / aElem.arrowsize;
3111 VECTOR2I arrVec = KiROUND( dirVec.x / scaling, dirVec.y / scaling );
3112 RotatePoint( arrVec, EDA_ANGLE( 20.0, DEGREES_T ) );
3113
3114 {
3115 std::unique_ptr<PCB_SHAPE> shape1 = std::make_unique<PCB_SHAPE>( m_board, SHAPE_T::SEGMENT );
3116
3117 shape1->SetLayer( klayer );
3118 shape1->SetStroke( STROKE_PARAMS( aElem.linewidth, LINE_STYLE::SOLID ) );
3119 shape1->SetStart( referencePoint0 );
3120 shape1->SetEnd( referencePoint0 + arrVec );
3121
3122 m_board->Add( shape1.release(), ADD_MODE::APPEND );
3123 }
3124
3125 RotatePoint( arrVec, EDA_ANGLE( -40.0, DEGREES_T ) );
3126
3127 {
3128 std::unique_ptr<PCB_SHAPE> shape2 = std::make_unique<PCB_SHAPE>( m_board, SHAPE_T::SEGMENT );
3129
3130 shape2->SetLayer( klayer );
3131 shape2->SetStroke( STROKE_PARAMS( aElem.linewidth, LINE_STYLE::SOLID ) );
3132 shape2->SetStart( referencePoint0 );
3133 shape2->SetEnd( referencePoint0 + arrVec );
3134
3135 m_board->Add( shape2.release(), ADD_MODE::APPEND );
3136 }
3137 }
3138 }
3139 }
3140
3141 if( aElem.textPoint.empty() )
3142 {
3143 if( m_reporter )
3144 {
3145 m_reporter->Report( wxT( "No text position present for leader dimension object" ),
3147 }
3148
3149 return;
3150 }
3151
3152 std::unique_ptr<PCB_TEXT> text = std::make_unique<PCB_TEXT>( m_board );
3153
3154 text->SetText( aElem.textformat );
3155 text->SetPosition( aElem.textPoint.at( 0 ) );
3156 text->SetLayer( klayer );
3157 text->SetTextSize( VECTOR2I( aElem.textheight, aElem.textheight ) ); // TODO: parse text width
3158 text->SetTextThickness( aElem.textlinewidth );
3159 text->SetHorizJustify( GR_TEXT_H_ALIGN_LEFT );
3160 text->SetVertJustify( GR_TEXT_V_ALIGN_BOTTOM );
3161
3162 m_board->Add( text.release(), ADD_MODE::APPEND );
3163}
3164
3165
3167{
3168 PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
3169
3170 if( klayer == UNDEFINED_LAYER )
3171 {
3172 if( m_reporter )
3173 {
3174 wxString msg;
3175 msg.Printf( _( "Dimension found on an Altium layer (%d) with no KiCad equivalent. "
3176 "It has been moved to KiCad layer Eco1_User." ), aElem.layer );
3177 m_reporter->Report( msg, RPT_SEVERITY_INFO );
3178 }
3179
3180 klayer = Eco1_User;
3181 }
3182
3183 for( size_t i = 0; i < aElem.referencePoint.size(); i++ )
3184 {
3185 std::unique_ptr<PCB_SHAPE> shape = std::make_unique<PCB_SHAPE>( m_board, SHAPE_T::SEGMENT );
3186
3187 shape->SetLayer( klayer );
3188 shape->SetStroke( STROKE_PARAMS( aElem.linewidth, LINE_STYLE::SOLID ) );
3189 shape->SetStart( aElem.referencePoint.at( i ) );
3190 // shape->SetEnd( /* TODO: seems to be based on TEXTY */ );
3191
3192 m_board->Add( shape.release(), ADD_MODE::APPEND );
3193 }
3194}
3195
3196
3198{
3199 PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
3200
3201 if( klayer == UNDEFINED_LAYER )
3202 {
3203 if( m_reporter )
3204 {
3205 wxString msg;
3206 msg.Printf( _( "Dimension found on an Altium layer (%d) with no KiCad equivalent. "
3207 "It has been moved to KiCad layer Eco1_User." ), aElem.layer );
3208 m_reporter->Report( msg, RPT_SEVERITY_INFO );
3209 }
3210
3211 klayer = Eco1_User;
3212 }
3213
3214 VECTOR2I vec = VECTOR2I( 0, aElem.height / 2 );
3215 RotatePoint( vec, EDA_ANGLE( aElem.angle, DEGREES_T ) );
3216
3217 std::unique_ptr<PCB_DIM_CENTER> dimension = std::make_unique<PCB_DIM_CENTER>( m_board );
3218
3219 dimension->SetLayer( klayer );
3220 dimension->SetLineThickness( aElem.linewidth );
3221 dimension->SetStart( aElem.xy1 );
3222 dimension->SetEnd( aElem.xy1 + vec );
3223
3224 m_board->Add( dimension.release(), ADD_MODE::APPEND );
3225}
3226
3227
3229 const CFB::COMPOUND_FILE_ENTRY* aEntry )
3230{
3231 if( m_progressReporter )
3232 m_progressReporter->Report( _( "Loading dimension drawings..." ) );
3233
3234 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
3235
3236 while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
3237 {
3238 checkpoint();
3239 ADIMENSION6 elem( reader );
3240
3241 switch( elem.kind )
3242 {
3245 break;
3247 if( m_reporter )
3248 {
3249 m_reporter->Report( wxString::Format( _( "Ignored Angular dimension (not yet supported)." ) ),
3251 }
3252 break;
3255 break;
3258 break;
3260 if( m_reporter )
3261 {
3262 m_reporter->Report( wxString::Format( _( "Ignored Datum dimension (not yet supported)." ) ),
3264 }
3265 // HelperParseDimensions6Datum( elem );
3266 break;
3268 if( m_reporter )
3269 {
3270 m_reporter->Report( wxString::Format( _( "Ignored Baseline dimension (not yet supported)." ) ),
3272 }
3273 break;
3276 break;
3278 if( m_reporter )
3279 {
3280 m_reporter->Report( wxString::Format( _( "Ignored Linear dimension (not yet supported)." ) ),
3282 }
3283 break;
3285 if( m_reporter )
3286 {
3287 m_reporter->Report( wxString::Format( _( "Ignored Radial dimension (not yet supported)." ) ),
3289 }
3290 break;
3291 default:
3292 if( m_reporter )
3293 {
3294 wxString msg;
3295 msg.Printf( _( "Ignored dimension of kind %d (not yet supported)." ), elem.kind );
3296 m_reporter->Report( msg, RPT_SEVERITY_INFO );
3297 }
3298 break;
3299 }
3300 }
3301
3302 if( reader.GetRemainingBytes() != 0 )
3303 THROW_IO_ERROR( wxT( "Dimensions6 stream is not fully parsed" ) );
3304}
3305
3306
3308 const CFB::COMPOUND_FILE_ENTRY* aEntry,
3309 const std::vector<std::string>& aRootDir )
3310{
3311 if( m_progressReporter )
3312 m_progressReporter->Report( _( "Loading 3D models..." ) );
3313
3314 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
3315
3316 if( reader.GetRemainingBytes() == 0 )
3317 return;
3318
3319 int idx = 0;
3320 wxString invalidChars = wxFileName::GetForbiddenChars();
3321
3322 while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
3323 {
3324 checkpoint();
3325 AMODEL elem( reader );
3326
3327 std::vector<std::string> stepPath = aRootDir;
3328 stepPath.emplace_back( std::to_string( idx ) );
3329
3330 bool validName = !elem.name.IsEmpty() && elem.name.IsAscii()
3331 && wxString::npos == elem.name.find_first_of( invalidChars );
3332 wxString storageName = validName ? elem.name : wxString::Format( wxT( "model_%d" ), idx );
3333
3334 idx++;
3335
3336 const CFB::COMPOUND_FILE_ENTRY* stepEntry = aAltiumPcbFile.FindStream( stepPath );
3337
3338 if( stepEntry == nullptr )
3339 {
3340 if( m_reporter )
3341 {
3342 wxString msg;
3343 msg.Printf( _( "File not found: '%s'. 3D-model not imported." ), FormatPath( stepPath ) );
3344 m_reporter->Report( msg, RPT_SEVERITY_ERROR );
3345 }
3346
3347 continue;
3348 }
3349
3350 size_t stepSize = static_cast<size_t>( stepEntry->size );
3351 std::vector<char> stepContent( stepSize );
3352
3353 // read file into buffer
3354 aAltiumPcbFile.GetCompoundFileReader().ReadFile( stepEntry, 0, stepContent.data(),
3355 stepSize );
3356
3357 m_EmbeddedModels.insert( std::make_pair(
3358 elem.id, ALTIUM_EMBEDDED_MODEL_DATA( storageName, elem.rotation, elem.z_offset,
3359 std::move( stepContent ) ) ) );
3360 }
3361
3362 // Append _<index> to duplicate filenames
3363 std::map<wxString, std::vector<wxString>> nameIdMap;
3364
3365 for( auto& [id, data] : m_EmbeddedModels )
3366 nameIdMap[data.m_modelname].push_back( id );
3367
3368 for( auto& [name, ids] : nameIdMap )
3369 {
3370 for( size_t i = 1; i < ids.size(); i++ )
3371 {
3372 const wxString& id = ids[i];
3373
3374 auto modelTuple = m_EmbeddedModels.find( id );
3375
3376 if( modelTuple == m_EmbeddedModels.end() )
3377 continue;
3378
3379 wxString modelName = modelTuple->second.m_modelname;
3380
3381 if( modelName.Contains( "." ) )
3382 {
3383 wxString ext;
3384 wxString baseName = modelName.BeforeLast( '.', &ext );
3385
3386 modelTuple->second.m_modelname = baseName + '_' + std::to_string( i ) + '.' + ext;
3387 }
3388 else
3389 {
3390 modelTuple->second.m_modelname = modelName + '_' + std::to_string( i );
3391 }
3392 }
3393 }
3394
3395 if( reader.GetRemainingBytes() != 0 )
3396 THROW_IO_ERROR( wxT( "Models stream is not fully parsed" ) );
3397}
3398
3399
3400static void altiumCollectSchematicNetNames( const wxString& aFileName, std::map<wxString, wxString>& aNames,
3401 std::set<wxString>& aAmbiguous )
3402{
3403 auto collect = [&]( const std::map<wxString, wxString>& aProps )
3404 {
3405 int record = ALTIUM_PROPS_UTILS::ReadInt( aProps, wxT( "RECORD" ), 0 );
3406 wxString name;
3407
3408 switch( record )
3409 {
3410 case 16: // sheet entry
3411 case 18: // port
3412 name = ALTIUM_PROPS_UTILS::ReadString( aProps, wxT( "NAME" ), wxT( "" ) );
3413 break;
3414
3415 case 17: // power port
3416 case 25: // net label
3417 name = ALTIUM_PROPS_UTILS::ReadString( aProps, wxT( "TEXT" ), wxT( "" ) );
3418 break;
3419
3420 default: return;
3421 }
3422
3423 if( name.IsEmpty() )
3424 return;
3425
3426 wxString key = name.Upper();
3427 auto it = aNames.find( key );
3428
3429 if( it == aNames.end() )
3430 aNames.emplace( key, name );
3431 else if( it->second != name )
3432 aAmbiguous.insert( key );
3433 };
3434
3436 {
3437 ALTIUM_COMPOUND_FILE schFile( aFileName );
3438 const CFB::COMPOUND_FILE_ENTRY* header = schFile.FindStream( { "FileHeader" } );
3439
3440 if( !header )
3441 return;
3442
3443 ALTIUM_BINARY_PARSER reader( schFile, header );
3444
3445 while( reader.GetRemainingBytes() > 0 )
3446 collect( reader.ReadProperties() );
3447 }
3448 else
3449 {
3450 ALTIUM_ASCII_PARSER reader( aFileName );
3451
3452 while( reader.CanRead() )
3453 collect( reader.ReadProperties() );
3454 }
3455}
3456
3457
3458void ALTIUM_PCB::MapSchematicNetNames( const std::map<std::string, UTF8>& aProperties )
3459{
3460 std::set<wxString> ambiguous;
3461
3462 for( int i = 0;; i++ )
3463 {
3464 auto it = aProperties.find( "sch" + std::to_string( i ) );
3465
3466 if( it == aProperties.end() )
3467 break;
3468
3469 try
3470 {
3471 altiumCollectSchematicNetNames( it->second.wx_str(), m_schematicNetNames, ambiguous );
3472 }
3473 catch( ... )
3474 {
3475 // an unreadable schematic must not break the board import
3476 }
3477 }
3478
3479 for( const wxString& key : ambiguous )
3480 m_schematicNetNames.erase( key );
3481}
3482
3483
3484wxString ALTIUM_PCB::SchematicCasedNetName( const wxString& aNetName ) const
3485{
3486 auto it = m_schematicNetNames.find( aNetName.Upper() );
3487
3488 if( it != m_schematicNetNames.end() )
3489 return it->second;
3490
3491 return aNetName;
3492}
3493
3494
3496 const CFB::COMPOUND_FILE_ENTRY* aEntry )
3497{
3498 if( m_progressReporter )
3499 m_progressReporter->Report( _( "Loading nets..." ) );
3500
3501 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
3502
3503 wxASSERT( m_altiumToKicadNetcodes.empty() );
3504
3505 int unnamedNetCount = 0;
3506
3507 while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
3508 {
3509 checkpoint();
3510 ANET6 elem( reader );
3511
3512 wxString netName = SchematicCasedNetName( elem.name );
3513
3514 if( netName.IsEmpty() )
3515 {
3516 netName = AltiumUnnamedNetName( *m_board, unnamedNetCount );
3517
3518 if( m_reporter )
3519 {
3520 m_reporter->Report( wxString::Format( _( "Altium net %zu has no name; imported as '%s'." ),
3521 m_altiumToKicadNetcodes.size(), netName ),
3523 }
3524 }
3525
3526 NETINFO_ITEM* netInfo = new NETINFO_ITEM( m_board, netName, -1 );
3527 m_board->Add( netInfo, ADD_MODE::APPEND );
3528
3529 // needs to be called after m_board->Add() as assign us the NetCode
3530 m_altiumToKicadNetcodes.push_back( netInfo->GetNetCode() );
3531 }
3532
3533 if( reader.GetRemainingBytes() != 0 )
3534 THROW_IO_ERROR( wxT( "Nets6 stream is not fully parsed" ) );
3535}
3536
3538 const CFB::COMPOUND_FILE_ENTRY* aEntry )
3539{
3540 if( m_progressReporter )
3541 m_progressReporter->Report( _( "Loading polygons..." ) );
3542
3543 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
3544
3545 while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
3546 {
3547 checkpoint();
3548 APOLYGON6 elem( reader );
3549
3550 if( elem.discardedVertices > 0 && m_reporter )
3551 {
3552 m_reporter->Report( wxString::Format( _( "Polygon on layer '%s' has %d vertices "
3553 "outside the coordinate range; they were "
3554 "dropped." ),
3555 LayerName( GetKicadLayer( elem.layer ) ),
3556 elem.discardedVertices ),
3558 }
3559
3560 SHAPE_LINE_CHAIN linechain;
3562
3563 if( linechain.PointCount() < 3 )
3564 {
3565 // We have found multiple Altium files with polygon records containing nothing but two
3566 // coincident vertices. These polygons do not appear when opening the file in Altium.
3567 // https://gitlab.com/kicad/code/kicad/-/issues/8183
3568 // Also, polygons with less than 3 points are not supported in KiCad.
3569 //
3570 // wxLogError( _( "Polygon has only %d point extracted from %ld vertices. At least 2 "
3571 // "points are required." ),
3572 // linechain.PointCount(),
3573 // elem.vertices.size() );
3574
3575 m_polygons.emplace_back( nullptr );
3576 continue;
3577 }
3578
3579 SHAPE_POLY_SET outline( linechain );
3580
3582 {
3583 // Altium "Hatched" or "None" polygon outlines have thickness, convert it to KiCad's representation.
3585 ARC_HIGH_DEF, true );
3586 }
3587
3588 if( outline.OutlineCount() != 1 && m_reporter )
3589 {
3590 wxString msg;
3591 msg.Printf( _( "Polygon outline count is %d, expected 1." ), outline.OutlineCount() );
3592
3593 m_reporter->Report( msg, RPT_SEVERITY_ERROR );
3594 }
3595
3596 if( outline.OutlineCount() == 0 )
3597 continue;
3598
3599 std::unique_ptr<ZONE> zone = std::make_unique<ZONE>(m_board);
3600
3601 // Be sure to set the zone layer before setting the net code
3602 // so that we know that this is a copper zone and so needs a valid net code.
3603 HelperSetZoneLayers( *zone, elem.layer );
3604 zone->SetNetCode( GetNetCode( elem.net ) );
3605 zone->SetPosition( elem.vertices.at( 0 ).position );
3606 zone->SetLocked( elem.locked );
3607 zone->SetAssignedPriority( elem.pourindex > 0 ? elem.pourindex : 0 );
3608 zone->Outline()->AddOutline( outline.Outline( 0 ) );
3609
3610 if( elem.pourindex > m_highest_pour_index )
3612
3613 const ARULE6* planeClearanceRule = GetRuleForPolygon( ALTIUM_RULE_KIND::PLANE_CLEARANCE );
3614 const ARULE6* zoneClearanceRule = GetRuleForPolygon( ALTIUM_RULE_KIND::CLEARANCE );
3615 int planeLayers = 0;
3616 int signalLayers = 0;
3617 int clearance = 0;
3618
3619 for( PCB_LAYER_ID layer : zone->GetLayerSet() )
3620 {
3621 LAYER_T layerType = m_board->GetLayerType( layer );
3622
3623 if( layerType == LT_POWER || layerType == LT_MIXED )
3624 planeLayers++;
3625
3626 if( layerType == LT_SIGNAL || layerType == LT_MIXED )
3627 signalLayers++;
3628 }
3629
3630 if( planeLayers > 0 && planeClearanceRule )
3631 clearance = std::max( clearance, planeClearanceRule->planeclearanceClearance );
3632
3633 if( signalLayers > 0 && zoneClearanceRule )
3634 clearance = std::max( clearance, zoneClearanceRule->clearanceGap );
3635
3636 if( clearance > 0 )
3637 zone->SetLocalClearance( clearance );
3638
3639 const ARULE6* polygonConnectRule = GetRuleForPolygon( ALTIUM_RULE_KIND::POLYGON_CONNECT );
3640
3641 if( polygonConnectRule != nullptr )
3642 {
3643 switch( polygonConnectRule->polygonconnectStyle )
3644 {
3646 zone->SetPadConnection( ZONE_CONNECTION::FULL );
3647 break;
3648
3650 zone->SetPadConnection( ZONE_CONNECTION::NONE );
3651 break;
3652
3653 default:
3655 zone->SetPadConnection( ZONE_CONNECTION::THERMAL );
3656 break;
3657 }
3658
3659 // TODO: correct variables?
3660 zone->SetThermalReliefSpokeWidth(
3661 polygonConnectRule->polygonconnectReliefconductorwidth );
3662 zone->SetThermalReliefGap( polygonConnectRule->polygonconnectAirgapwidth );
3663
3664 if( polygonConnectRule->polygonconnectReliefconductorwidth < zone->GetMinThickness() )
3665 zone->SetMinThickness( polygonConnectRule->polygonconnectReliefconductorwidth );
3666 }
3667
3668 if( IsAltiumLayerAPlane( elem.layer ) )
3669 {
3670 // outer zone will be set to priority 0 later.
3671 zone->SetAssignedPriority( 1 );
3672
3673 // check if this is the outer zone by simply comparing the BBOX
3674 const auto& outer_plane = m_outer_plane.find( elem.layer );
3675 if( outer_plane == m_outer_plane.end()
3676 || zone->GetBoundingBox().Contains( outer_plane->second->GetBoundingBox() ) )
3677 {
3678 m_outer_plane[elem.layer] = zone.get();
3679 }
3680 }
3681
3684 {
3685 zone->SetFillMode( ZONE_FILL_MODE::HATCH_PATTERN );
3686 zone->SetHatchThickness( elem.trackwidth );
3687
3689 {
3690 // use a small hack to get us only an outline (hopefully)
3691 const BOX2I& bbox = zone->GetBoundingBox();
3692 zone->SetHatchGap( std::max( bbox.GetHeight(), bbox.GetWidth() ) );
3693 }
3694 else
3695 {
3696 zone->SetHatchGap( elem.gridsize - elem.trackwidth );
3697 }
3698
3700 zone->SetHatchOrientation( ANGLE_45 );
3701 }
3702
3703 zone->SetBorderDisplayStyle( ZONE_BORDER_DISPLAY_STYLE::DIAGONAL_EDGE,
3705
3706 m_polygons.emplace_back( zone.get() );
3707 m_board->Add( zone.release(), ADD_MODE::APPEND );
3708 }
3709
3710 if( reader.GetRemainingBytes() != 0 )
3711 THROW_IO_ERROR( wxT( "Polygons6 stream is not fully parsed" ) );
3712}
3713
3715 const CFB::COMPOUND_FILE_ENTRY* aEntry )
3716{
3717 if( m_progressReporter )
3718 m_progressReporter->Report( _( "Loading rules..." ) );
3719
3720 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
3721
3722 while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
3723 {
3724 checkpoint();
3725 ARULE6 elem( reader );
3726
3727 m_rules[elem.kind].emplace_back( elem );
3728 }
3729
3730 // Sort by ARULE6::priority ascending. Altium priority 1 is the most specific, so the
3731 // first element after sorting is the highest-priority Altium rule.
3732 for( std::pair<const ALTIUM_RULE_KIND, std::vector<ARULE6>>& val : m_rules )
3733 {
3734 std::sort( val.second.begin(), val.second.end(),
3735 []( const ARULE6& lhs, const ARULE6& rhs )
3736 {
3737 return lhs.priority < rhs.priority;
3738 } );
3739 }
3740
3741 const ARULE6* clearanceRule = GetRuleDefault( ALTIUM_RULE_KIND::CLEARANCE );
3742 const ARULE6* trackWidthRule = GetRuleDefault( ALTIUM_RULE_KIND::WIDTH );
3743 const ARULE6* routingViasRule = GetRuleDefault( ALTIUM_RULE_KIND::ROUTING_VIAS );
3744 const ARULE6* holeSizeRule = GetRuleDefault( ALTIUM_RULE_KIND::HOLE_SIZE );
3747
3748 if( clearanceRule )
3749 m_board->GetDesignSettings().m_MinClearance = clearanceRule->clearanceGap;
3750
3751 if( trackWidthRule )
3752 {
3753 m_board->GetDesignSettings().m_TrackMinWidth = trackWidthRule->minLimit;
3754 // TODO: construct a custom rule for preferredWidth and maxLimit values
3755 }
3756
3757 if( routingViasRule )
3758 {
3759 m_board->GetDesignSettings().m_ViasMinSize = routingViasRule->minWidth;
3760 m_board->GetDesignSettings().m_MinThroughDrill = routingViasRule->minHoleWidth;
3761 }
3762
3763 if( holeSizeRule )
3764 {
3765 // TODO: construct a custom rule for minLimit / maxLimit values
3766 }
3767
3768 if( holeToHoleRule )
3769 m_board->GetDesignSettings().m_HoleToHoleMin = holeToHoleRule->clearanceGap;
3770
3771 if( boardOutlineRule )
3772 m_board->GetDesignSettings().m_CopperEdgeClearance = boardOutlineRule->clearanceGap;
3773
3776
3777 if( soldermaskRule )
3778 m_board->GetDesignSettings().m_SolderMaskExpansion = soldermaskRule->soldermaskExpansion;
3779
3780 if( pastemaskRule )
3781 m_board->GetDesignSettings().m_SolderPasteMargin = pastemaskRule->pastemaskExpansion;
3782
3783 std::shared_ptr<NET_SETTINGS> netSettings = m_board->GetDesignSettings().m_NetSettings;
3784 std::shared_ptr<NETCLASS> defaultNetclass = netSettings->GetDefaultNetclass();
3785
3786 if( clearanceRule )
3787 defaultNetclass->SetClearance( clearanceRule->clearanceGap );
3788
3789 if( trackWidthRule )
3790 defaultNetclass->SetTrackWidth( trackWidthRule->preferredWidth );
3791
3792 if( routingViasRule )
3793 {
3794 defaultNetclass->SetViaDiameter( routingViasRule->width );
3795 defaultNetclass->SetViaDrill( routingViasRule->holeWidth );
3796 }
3797
3798 std::vector<const ARULE6*> unresolvedNetclassRules;
3799
3800 ApplyAltiumNetclassRules( m_rules, *netSettings, &unresolvedNetclassRules );
3801
3802 if( m_reporter )
3803 {
3804 for( const ARULE6* rule : unresolvedNetclassRules )
3805 {
3806 wxString netclassName;
3807 GetAltiumNetclassScopeName( *rule, &netclassName );
3808
3809 m_reporter->Report( wxString::Format( _( "Altium rule '%s' applies to netclass '%s', which this "
3810 "board does not define. Its constraint is not imported." ),
3811 rule->name, netclassName ),
3813 }
3814 }
3815
3816 // Composite netclasses cached the values we just changed
3818
3819 if( reader.GetRemainingBytes() != 0 )
3820 THROW_IO_ERROR( wxT( "Rules6 stream is not fully parsed" ) );
3821}
3822
3824 const CFB::COMPOUND_FILE_ENTRY* aEntry )
3825{
3826 if( m_progressReporter )
3827 m_progressReporter->Report( _( "Loading board regions..." ) );
3828
3829 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
3830
3831 while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
3832 {
3833 checkpoint();
3834 AREGION6 elem( reader, false );
3835
3836 // TODO: implement?
3837 }
3838
3839 if( reader.GetRemainingBytes() != 0 )
3840 THROW_IO_ERROR( wxT( "BoardRegions stream is not fully parsed" ) );
3841}
3842
3844 const CFB::COMPOUND_FILE_ENTRY* aEntry )
3845{
3846 if( m_progressReporter )
3847 m_progressReporter->Report( _( "Loading polygons..." ) );
3848
3849 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
3850
3851 /* TODO: use Header section of file */
3852 for( int primitiveIndex = 0; reader.GetRemainingBytes() >= 4; primitiveIndex++ )
3853 {
3854 checkpoint();
3855 AREGION6 elem( reader, true );
3856
3859 {
3860 // TODO: implement all different types for footprints
3861 ConvertShapeBasedRegions6ToBoardItem( elem, primitiveIndex );
3862 }
3864 && !elem.is_keepout && elem.net != ALTIUM_NET_UNCONNECTED )
3865 {
3867 elem.net );
3868 }
3869 else
3870 {
3871 FOOTPRINT* footprint = HelperGetFootprint( elem.component );
3872 ConvertShapeBasedRegions6ToFootprintItem( footprint, elem, primitiveIndex );
3873 }
3874 }
3875
3876 if( reader.GetRemainingBytes() != 0 )
3877 THROW_IO_ERROR( wxT( "ShapeBasedRegions6 stream is not fully parsed" ) );
3878}
3879
3880
3882 const int aPrimitiveIndex, uint16_t aNet )
3883{
3884 for( PCB_LAYER_ID klayer : GetKicadLayersToIterate( aElem.layer ) )
3885 {
3886 // Outer copper already becomes a region pad; inner copper would be left unconnected
3887 if( !IsInnerCopperLayer( klayer ) )
3888 {
3889 ConvertShapeBasedRegions6ToFootprintItemOnLayer( aFootprint, aElem, klayer, aPrimitiveIndex );
3890 continue;
3891 }
3892
3893 SHAPE_LINE_CHAIN outline;
3895
3896 if( outline.PointCount() < 3 )
3897 continue;
3898
3899 SHAPE_POLY_SET polySet( outline );
3900
3901 for( const std::vector<ALTIUM_VERTICE>& hole : aElem.holes )
3902 {
3903 SHAPE_LINE_CHAIN holeChain;
3904 HelperShapeLineChainFromAltiumVertices( holeChain, hole );
3905
3906 if( holeChain.PointCount() >= 3 )
3907 polySet.AddHole( holeChain );
3908 }
3909
3910 std::unique_ptr<PCB_SHAPE> region = std::make_unique<PCB_SHAPE>( nullptr, SHAPE_T::POLY );
3911
3912 region->SetPolyShape( polySet );
3913 region->SetFilled( true );
3914 region->SetStroke( STROKE_PARAMS( 0 ) );
3915
3916 HelperDeferComponentCopper( aFootprint, klayer, aNet, std::move( region ),
3917 [this, aFootprint, aElem, klayer, aPrimitiveIndex]()
3918 {
3919 ConvertShapeBasedRegions6ToFootprintItemOnLayer( aFootprint, aElem, klayer,
3920 aPrimitiveIndex );
3921 } );
3922 }
3923}
3924
3925
3926void ALTIUM_PCB::ConvertShapeBasedRegions6ToBoardItem( const AREGION6& aElem, const int aPrimitiveIndex )
3927{
3929 {
3930 SHAPE_LINE_CHAIN cutout;
3932 m_boardCutouts.push_back( std::move( cutout ) );
3933 }
3934 else if( aElem.kind == ALTIUM_REGION_KIND::POLYGON_CUTOUT || aElem.is_keepout )
3935 {
3936 SHAPE_LINE_CHAIN linechain;
3938
3939 if( linechain.PointCount() < 3 )
3940 {
3941 // We have found multiple Altium files with polygon records containing nothing but
3942 // two coincident vertices. These polygons do not appear when opening the file in
3943 // Altium. https://gitlab.com/kicad/code/kicad/-/issues/8183
3944 // Also, polygons with less than 3 points are not supported in KiCad.
3945 return;
3946 }
3947
3948 // A polygon cutout only removes copper, a keepout carries its own mask
3949 uint8_t restrictions = aElem.is_keepout ? HelperGetKeepoutRestrictions( aElem.keepoutrestrictions, aElem.layer )
3951
3952 if( restrictions == 0 )
3953 return;
3954
3955 std::unique_ptr<ZONE> zone = std::make_unique<ZONE>( m_board );
3956
3957 zone->SetIsRuleArea( true );
3958
3959 HelperSetZoneKeepoutRestrictions( *zone, restrictions );
3960
3961 zone->SetPosition( aElem.outline.at( 0 ).position );
3962 zone->Outline()->AddOutline( linechain );
3963
3964 HelperSetZoneLayers( *zone, aElem.layer );
3965
3966 zone->SetBorderDisplayStyle( ZONE_BORDER_DISPLAY_STYLE::DIAGONAL_EDGE,
3968
3969 m_board->Add( zone.release(), ADD_MODE::APPEND );
3970 }
3971 else if( aElem.is_teardrop )
3972 {
3973 SHAPE_LINE_CHAIN linechain;
3975
3976 if( linechain.PointCount() < 3 )
3977 {
3978 // Polygons with less than 3 points are not supported in KiCad.
3979 return;
3980 }
3981
3982 std::unique_ptr<ZONE> zone = std::make_unique<ZONE>( m_board );
3983
3984 zone->SetPosition( aElem.outline.at( 0 ).position );
3985 zone->Outline()->AddOutline( linechain );
3986
3987 HelperSetZoneLayers( *zone, aElem.layer );
3988 zone->SetNetCode( GetNetCode( aElem.net ) );
3989 zone->SetTeardropAreaType( TEARDROP_TYPE::TD_UNSPECIFIED );
3990 zone->SetHatchStyle( ZONE_BORDER_DISPLAY_STYLE::INVISIBLE_BORDER );
3991
3992 SHAPE_POLY_SET fill;
3993 fill.Append( linechain );
3994 fill.Fracture();
3995
3996 for( PCB_LAYER_ID klayer : GetKicadLayersToIterate( aElem.layer ) )
3997 zone->SetFilledPolysList( klayer, fill );
3998
3999 zone->SetIsFilled( true );
4000 zone->SetNeedRefill( false );
4001
4002 m_board->Add( zone.release(), ADD_MODE::APPEND );
4003 }
4004 else if( aElem.kind == ALTIUM_REGION_KIND::DASHED_OUTLINE )
4005 {
4006 PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
4007
4008 if( klayer == UNDEFINED_LAYER )
4009 {
4010 if( m_reporter )
4011 {
4012 wxString msg;
4013 msg.Printf( _( "Dashed outline found on an Altium layer (%d) with no KiCad equivalent. "
4014 "It has been moved to KiCad layer Eco1_User." ), aElem.layer );
4015 m_reporter->Report( msg, RPT_SEVERITY_ERROR );
4016 }
4017
4018 klayer = Eco1_User;
4019 }
4020
4021 SHAPE_LINE_CHAIN linechain;
4023
4024 if( linechain.PointCount() < 3 )
4025 {
4026 // We have found multiple Altium files with polygon records containing nothing but
4027 // two coincident vertices. These polygons do not appear when opening the file in
4028 // Altium. https://gitlab.com/kicad/code/kicad/-/issues/8183
4029 // Also, polygons with less than 3 points are not supported in KiCad.
4030 return;
4031 }
4032
4033 std::unique_ptr<PCB_SHAPE> shape = std::make_unique<PCB_SHAPE>( m_board, SHAPE_T::POLY );
4034
4035 shape->SetPolyShape( linechain );
4036 shape->SetFilled( false );
4037 shape->SetLayer( klayer );
4038 shape->SetStroke( STROKE_PARAMS( pcbIUScale.mmToIU( 0.1 ), LINE_STYLE::DASH ) );
4039
4040 m_board->Add( shape.release(), ADD_MODE::APPEND );
4041 }
4042 else if( aElem.kind == ALTIUM_REGION_KIND::COPPER )
4043 {
4044 if( aElem.polygon == ALTIUM_POLYGON_NONE )
4045 {
4046 for( PCB_LAYER_ID klayer : GetKicadLayersToIterate( aElem.layer ) )
4047 ConvertShapeBasedRegions6ToBoardItemOnLayer( aElem, klayer, aPrimitiveIndex );
4048 }
4049 }
4050 else
4051 {
4052 if( m_reporter )
4053 {
4054 wxString msg;
4055 msg.Printf( _( "Ignored polygon shape of kind %d (not yet supported)." ), aElem.kind );
4056 m_reporter->Report( msg, RPT_SEVERITY_ERROR );
4057 }
4058 }
4059}
4060
4061
4063 const AREGION6& aElem,
4064 const int aPrimitiveIndex )
4065{
4066 if( aElem.kind == ALTIUM_REGION_KIND::POLYGON_CUTOUT || aElem.is_keepout )
4067 {
4068 SHAPE_LINE_CHAIN linechain;
4070
4071 if( linechain.PointCount() < 3 )
4072 {
4073 // We have found multiple Altium files with polygon records containing nothing but
4074 // two coincident vertices. These polygons do not appear when opening the file in
4075 // Altium. https://gitlab.com/kicad/code/kicad/-/issues/8183
4076 // Also, polygons with less than 3 points are not supported in KiCad.
4077 return;
4078 }
4079
4080 // A polygon cutout only removes copper, a keepout carries its own mask
4081 uint8_t restrictions = aElem.is_keepout ? HelperGetKeepoutRestrictions( aElem.keepoutrestrictions, aElem.layer )
4083
4084 if( restrictions == 0 )
4085 return;
4086
4087 std::unique_ptr<ZONE> zone = std::make_unique<ZONE>( aFootprint );
4088
4089 zone->SetIsRuleArea( true );
4090
4091 HelperSetZoneKeepoutRestrictions( *zone, restrictions );
4092
4093 zone->SetPosition( aElem.outline.at( 0 ).position );
4094 zone->Outline()->AddOutline( linechain );
4095
4096 HelperFootprintZoneToLibFrame( *zone, *aFootprint );
4097
4098 HelperSetZoneLayers( *zone, aElem.layer );
4099
4100 zone->SetBorderDisplayStyle( ZONE_BORDER_DISPLAY_STYLE::DIAGONAL_EDGE,
4102
4103 aFootprint->Add( zone.release(), ADD_MODE::APPEND );
4104 }
4105 else if( aElem.kind == ALTIUM_REGION_KIND::COPPER )
4106 {
4107 if( aElem.polygon == ALTIUM_POLYGON_NONE )
4108 {
4109 for( PCB_LAYER_ID klayer : GetKicadLayersToIterate( aElem.layer ) )
4110 {
4111 ConvertShapeBasedRegions6ToFootprintItemOnLayer( aFootprint, aElem, klayer,
4112 aPrimitiveIndex );
4113 }
4114 }
4115 }
4118 {
4120 ? Edge_Cuts
4121 : GetKicadLayer( aElem.layer );
4122
4123 if( klayer == UNDEFINED_LAYER )
4124 {
4125 if( !m_footprintName.IsEmpty() )
4126 {
4127 if( m_reporter )
4128 {
4129 wxString msg;
4130 msg.Printf( _( "Loading library '%s':\n"
4131 "Footprint %s contains a dashed outline on Altium layer (%d) with "
4132 "no KiCad equivalent. It has been moved to KiCad layer Eco1_User." ),
4133 m_library,
4135 aElem.layer );
4136 m_reporter->Report( msg, RPT_SEVERITY_ERROR );
4137 }
4138 }
4139 else
4140 {
4141 if( m_reporter )
4142 {
4143 wxString msg;
4144 msg.Printf( _( "Footprint %s contains a dashed outline on Altium layer (%d) with "
4145 "no KiCad equivalent. It has been moved to KiCad layer Eco1_User." ),
4146 aFootprint->GetReference(),
4147 aElem.layer );
4148 m_reporter->Report( msg, RPT_SEVERITY_ERROR );
4149 }
4150 }
4151
4152 klayer = Eco1_User;
4153 }
4154
4155 SHAPE_LINE_CHAIN linechain;
4157
4158 if( linechain.PointCount() < 3 )
4159 {
4160 // We have found multiple Altium files with polygon records containing nothing but
4161 // two coincident vertices. These polygons do not appear when opening the file in
4162 // Altium. https://gitlab.com/kicad/code/kicad/-/issues/8183
4163 // Also, polygons with less than 3 points are not supported in KiCad.
4164 return;
4165 }
4166
4167 std::unique_ptr<PCB_SHAPE> shape = std::make_unique<PCB_SHAPE>( aFootprint, SHAPE_T::POLY );
4168
4169 shape->SetPolyShape( linechain );
4170 shape->SetFilled( false );
4171 shape->SetLayer( klayer );
4172
4174 shape->SetStroke( STROKE_PARAMS( pcbIUScale.mmToIU( 0.1 ), LINE_STYLE::DASH ) );
4175 else
4176 shape->SetStroke( STROKE_PARAMS( pcbIUScale.mmToIU( 0.1 ), LINE_STYLE::SOLID ) );
4177
4178 aFootprint->Add( shape.release(), ADD_MODE::APPEND );
4179 }
4180 else
4181 {
4182 if( !m_footprintName.IsEmpty() )
4183 {
4184 if( m_reporter )
4185 {
4186 wxString msg;
4187 msg.Printf( _( "Error loading library '%s':\n"
4188 "Footprint %s contains polygon shape of kind %d (not yet supported)." ),
4189 m_library,
4191 aElem.kind );
4192 m_reporter->Report( msg, RPT_SEVERITY_ERROR );
4193 }
4194 }
4195 else
4196 {
4197 if( m_reporter )
4198 {
4199 wxString msg;
4200 msg.Printf( _( "Footprint %s contains polygon shape of kind %d (not yet supported)." ),
4201 aFootprint->GetReference(),
4202 aElem.kind );
4203 m_reporter->Report( msg, RPT_SEVERITY_ERROR );
4204 }
4205 }
4206 }
4207}
4208
4209
4211 const int aPrimitiveIndex )
4212{
4213 SHAPE_LINE_CHAIN linechain;
4215
4216 if( linechain.PointCount() < 3 )
4217 {
4218 // We have found multiple Altium files with polygon records containing nothing
4219 // but two coincident vertices. These polygons do not appear when opening the
4220 // file in Altium. https://gitlab.com/kicad/code/kicad/-/issues/8183
4221 // Also, polygons with less than 3 points are not supported in KiCad.
4222 return;
4223 }
4224
4225 SHAPE_POLY_SET polySet;
4226 polySet.AddOutline( linechain );
4227
4228 for( const std::vector<ALTIUM_VERTICE>& hole : aElem.holes )
4229 {
4230 SHAPE_LINE_CHAIN hole_linechain;
4231 HelperShapeLineChainFromAltiumVertices( hole_linechain, hole );
4232
4233 if( hole_linechain.PointCount() < 3 )
4234 continue;
4235
4236 polySet.AddHole( hole_linechain );
4237 }
4238
4239 std::unique_ptr<PCB_SHAPE> shape = std::make_unique<PCB_SHAPE>( m_board, SHAPE_T::POLY );
4240
4241 shape->SetPolyShape( polySet );
4242 shape->SetFilled( true );
4243 shape->SetLayer( aLayer );
4244 shape->SetStroke( STROKE_PARAMS( 0 ) );
4245
4246 if( IsCopperLayer( aLayer ) && aElem.net != ALTIUM_NET_UNCONNECTED )
4247 {
4248 shape->SetNetCode( GetNetCode( aElem.net ) );
4249 }
4250
4251 m_board->Add( shape.release(), ADD_MODE::APPEND );
4252
4253 // Guard skips dup mask shapes when a MULTI_LAYER region iterates every copper layer
4254 if( aLayer == F_Cu || aLayer == B_Cu )
4255 {
4256 for( const auto& layerExpansionMask :
4258 {
4259 const PCB_LAYER_ID maskLayer = layerExpansionMask.first;
4260
4261 if( ( ( maskLayer == F_Mask || maskLayer == F_Paste ) && aLayer != F_Cu )
4262 || ( ( maskLayer == B_Mask || maskLayer == B_Paste ) && aLayer != B_Cu ) )
4263 {
4264 continue;
4265 }
4266
4267 int expansion = layerExpansionMask.second;
4268
4269 SHAPE_POLY_SET expandedPolySet = polySet;
4270 expandedPolySet.Inflate( expansion, CORNER_STRATEGY::ROUND_ALL_CORNERS, ARC_HIGH_DEF );
4271
4272 std::unique_ptr<PCB_SHAPE> maskShape = std::make_unique<PCB_SHAPE>( m_board, SHAPE_T::POLY );
4273
4274 maskShape->SetPolyShape( expandedPolySet );
4275 maskShape->SetFilled( true );
4276 maskShape->SetLayer( maskLayer );
4277 maskShape->SetStroke( STROKE_PARAMS( 0 ) );
4278
4279 m_board->Add( maskShape.release(), ADD_MODE::APPEND );
4280 }
4281 }
4282}
4283
4284
4286 const AREGION6& aElem,
4287 PCB_LAYER_ID aLayer,
4288 const int aPrimitiveIndex )
4289{
4290 SHAPE_LINE_CHAIN linechain;
4292
4293 if( linechain.PointCount() < 3 )
4294 {
4295 // We have found multiple Altium files with polygon records containing nothing
4296 // but two coincident vertices. These polygons do not appear when opening the
4297 // file in Altium. https://gitlab.com/kicad/code/kicad/-/issues/8183
4298 // Also, polygons with less than 3 points are not supported in KiCad.
4299 return;
4300 }
4301
4302 SHAPE_POLY_SET polySet;
4303 polySet.AddOutline( linechain );
4304
4305 for( const std::vector<ALTIUM_VERTICE>& hole : aElem.holes )
4306 {
4307 SHAPE_LINE_CHAIN hole_linechain;
4308 HelperShapeLineChainFromAltiumVertices( hole_linechain, hole );
4309
4310 if( hole_linechain.PointCount() < 3 )
4311 continue;
4312
4313 polySet.AddHole( hole_linechain );
4314 }
4315
4316 if( aLayer == F_Cu || aLayer == B_Cu )
4317 {
4318 std::unique_ptr<PAD> pad = std::make_unique<PAD>( aFootprint );
4319
4320 LSET padLayers;
4321 padLayers.set( aLayer );
4322
4323 pad->SetAttribute( PAD_ATTRIB::SMD );
4324 pad->SetPadstackMode( PADSTACK::MODE::NORMAL );
4326 pad->SetThermalSpokeAngle( ANGLE_90 );
4327
4328 int anchorSize = 1;
4329 VECTOR2I anchorPos = linechain.CPoint( 0 );
4330
4331 pad->SetAnchorPadShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::CIRCLE );
4332 pad->SetSize( PADSTACK::ALL_LAYERS, { anchorSize, anchorSize } );
4333 pad->SetPosition( anchorPos );
4334 pad->SetNetCode( GetNetCode( aElem.net ) );
4335
4336 // The primitives below are board-absolute, but a pad defaults to its footprint's angle
4337 pad->SetOrientation( ANGLE_0 );
4338
4339 SHAPE_POLY_SET shapePolys = polySet;
4340 shapePolys.Move( -anchorPos );
4341 pad->AddPrimitivePoly( PADSTACK::ALL_LAYERS, shapePolys, 0, true );
4342
4344 auto it = map.find( aPrimitiveIndex );
4345
4346 if( it != map.end() )
4347 {
4348 const AEXTENDED_PRIMITIVE_INFORMATION& info = it->second;
4349
4350 if( info.pastemaskexpansionmode == ALTIUM_MODE::MANUAL )
4351 {
4352 pad->SetLocalSolderPasteMargin( info.pastemaskexpansionmanual );
4353 }
4354
4355 if( info.soldermaskexpansionmode == ALTIUM_MODE::MANUAL )
4356 {
4357 pad->SetLocalSolderMaskMargin( info.soldermaskexpansionmanual );
4358 }
4359
4360 if( info.pastemaskexpansionmode != ALTIUM_MODE::NONE )
4361 padLayers.set( aLayer == F_Cu ? F_Paste : B_Paste );
4362
4363 if( info.soldermaskexpansionmode != ALTIUM_MODE::NONE )
4364 padLayers.set( aLayer == F_Cu ? F_Mask : B_Mask );
4365 }
4366
4367 pad->SetLayerSet( padLayers );
4368
4369 aFootprint->Add( pad.release(), ADD_MODE::APPEND );
4370 }
4371 else
4372 {
4373 std::unique_ptr<PCB_SHAPE> shape = std::make_unique<PCB_SHAPE>( aFootprint, SHAPE_T::POLY );
4374
4375 shape->SetPolyShape( polySet );
4376 shape->SetFilled( true );
4377 shape->SetLayer( aLayer );
4378 shape->SetStroke( STROKE_PARAMS( 0 ) );
4379
4380 aFootprint->Add( shape.release(), ADD_MODE::APPEND );
4381 }
4382}
4383
4384
4386 const CFB::COMPOUND_FILE_ENTRY* aEntry )
4387{
4388 if( m_progressReporter )
4389 m_progressReporter->Report( _( "Loading zone fills..." ) );
4390
4391 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
4392
4393 while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
4394 {
4395 checkpoint();
4396 AREGION6 elem( reader, false );
4397
4398 if( elem.polygon != ALTIUM_POLYGON_NONE )
4399 {
4400 if( m_polygons.size() <= elem.polygon )
4401 {
4402 THROW_IO_ERRORF( wxT( "Region stream tries to access polygon id %d of %d existing polygons." ),
4403 elem.polygon, m_polygons.size() );
4404 }
4405
4406 ZONE* zone = m_polygons.at( elem.polygon );
4407
4408 if( zone == nullptr )
4409 continue; // we know the zone id, but because we do not know the layer we did not add it!
4410
4411 PCB_LAYER_ID klayer = GetKicadLayer( elem.layer );
4412
4413 if( klayer == UNDEFINED_LAYER )
4414 continue; // Just skip it for now. Users can fill it themselves.
4415
4416 SHAPE_LINE_CHAIN linechain;
4417
4418 for( const ALTIUM_VERTICE& vertice : elem.outline )
4419 linechain.Append( vertice.position );
4420
4421 linechain.Append( elem.outline.at( 0 ).position );
4422 linechain.SetClosed( true );
4423
4424 SHAPE_POLY_SET fill;
4425 fill.AddOutline( linechain );
4426
4427 for( const std::vector<ALTIUM_VERTICE>& hole : elem.holes )
4428 {
4429 SHAPE_LINE_CHAIN hole_linechain;
4430
4431 for( const ALTIUM_VERTICE& vertice : hole )
4432 hole_linechain.Append( vertice.position );
4433
4434 hole_linechain.Append( hole.at( 0 ).position );
4435 hole_linechain.SetClosed( true );
4436 fill.AddHole( hole_linechain );
4437 }
4438
4439 if( zone->HasFilledPolysForLayer( klayer ) )
4440 fill.BooleanAdd( *zone->GetFill( klayer ) );
4441
4442 fill.Fracture();
4443
4444 zone->SetFilledPolysList( klayer, fill );
4445 zone->SetIsFilled( true );
4446 zone->SetNeedRefill( false );
4447 }
4448 }
4449
4450 if( reader.GetRemainingBytes() != 0 )
4451 THROW_IO_ERROR( wxT( "Regions6 stream is not fully parsed" ) );
4452}
4453
4454
4456 const CFB::COMPOUND_FILE_ENTRY* aEntry )
4457{
4458 if( m_progressReporter )
4459 m_progressReporter->Report( _( "Loading arcs..." ) );
4460
4461 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
4462
4463 for( int primitiveIndex = 0; reader.GetRemainingBytes() >= 4; primitiveIndex++ )
4464 {
4465 checkpoint();
4466 AARC6 elem( reader );
4467
4468 if( elem.component == ALTIUM_COMPONENT_NONE )
4469 {
4470 ConvertArcs6ToBoardItem( elem, primitiveIndex );
4471 }
4472 else
4473 {
4474 FOOTPRINT* footprint = HelperGetFootprint( elem.component );
4475 ConvertArcs6ToFootprintItem( footprint, elem, primitiveIndex, true );
4476 }
4477 }
4478
4479 if( reader.GetRemainingBytes() != 0 )
4480 THROW_IO_ERROR( wxT( "Arcs6 stream is not fully parsed" ) );
4481}
4482
4483
4485{
4486 if( aElem.startangle == 0. && aElem.endangle == 360. )
4487 {
4488 aShape->SetShape( SHAPE_T::CIRCLE );
4489
4490 // TODO: other variants to define circle?
4491 aShape->SetStart( aElem.center );
4492 aShape->SetEnd( aElem.center - VECTOR2I( 0, aElem.radius ) );
4493 }
4494 else
4495 {
4496 aShape->SetShape( SHAPE_T::ARC );
4497
4498 EDA_ANGLE includedAngle( aElem.endangle - aElem.startangle, DEGREES_T );
4499 EDA_ANGLE startAngle( aElem.endangle, DEGREES_T );
4500
4501 VECTOR2I startOffset = VECTOR2I( KiROUND( startAngle.Cos() * aElem.radius ),
4502 -KiROUND( startAngle.Sin() * aElem.radius ) );
4503
4504 aShape->SetCenter( aElem.center );
4505 aShape->SetStart( aElem.center + startOffset );
4506 aShape->SetArcAngleAndEnd( includedAngle.Normalize(), true );
4507 }
4508}
4509
4510
4511void ALTIUM_PCB::ConvertArcs6ToBoardItem( const AARC6& aElem, const int aPrimitiveIndex )
4512{
4513 if( aElem.polygon != ALTIUM_POLYGON_NONE && aElem.polygon != ALTIUM_POLYGON_BOARD )
4514 {
4515 if( m_polygons.size() <= aElem.polygon )
4516 {
4517 THROW_IO_ERRORF( wxT( "Tracks stream tries to access polygon id %u of %zu existing polygons." ),
4518 aElem.polygon, m_polygons.size() );
4519 }
4520
4521 ZONE* zone = m_polygons.at( aElem.polygon );
4522
4523 if( zone == nullptr )
4524 {
4525 return; // we know the zone id, but because we do not know the layer we did not
4526 // add it!
4527 }
4528
4529 PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
4530
4531 if( klayer == UNDEFINED_LAYER )
4532 return; // Just skip it for now. Users can fill it themselves.
4533
4534 if( !zone->HasFilledPolysForLayer( klayer ) )
4535 return;
4536
4537 SHAPE_POLY_SET* fill = zone->GetFill( klayer );
4538
4539 // This is not the actual board item. We can use it to create the polygon for the region
4540 PCB_SHAPE shape( nullptr );
4541
4542 ConvertArcs6ToPcbShape( aElem, &shape );
4544
4545 shape.EDA_SHAPE::TransformShapeToPolygon( *fill, 0, ARC_HIGH_DEF, ERROR_INSIDE );
4546 // Will be simplified and fractured later
4547
4548 zone->SetIsFilled( true );
4549 zone->SetNeedRefill( false );
4550
4551 return;
4552 }
4553
4554 if( aElem.is_keepout || aElem.layer == ALTIUM_LAYER::KEEP_OUT_LAYER
4555 || IsAltiumLayerAPlane( aElem.layer ) )
4556 {
4557 // This is not the actual board item. We can use it to create the polygon for the region
4558 PCB_SHAPE shape( nullptr );
4559
4560 ConvertArcs6ToPcbShape( aElem, &shape );
4562
4564 }
4565 else
4566 {
4567 for( PCB_LAYER_ID klayer : GetKicadLayersToIterate( aElem.layer ) )
4568 ConvertArcs6ToBoardItemOnLayer( aElem, klayer );
4569 }
4570
4571 for( const auto& layerExpansionMask :
4573 {
4574 int width = aElem.width + ( layerExpansionMask.second * 2 );
4575
4576 if( width > 1 )
4577 {
4578 std::unique_ptr<PCB_SHAPE> arc = std::make_unique<PCB_SHAPE>( m_board );
4579
4580 ConvertArcs6ToPcbShape( aElem, arc.get() );
4581 arc->SetStroke( STROKE_PARAMS( width, LINE_STYLE::SOLID ) );
4582 arc->SetLayer( layerExpansionMask.first );
4583
4584 m_board->Add( arc.release(), ADD_MODE::APPEND );
4585 }
4586 }
4587}
4588
4589
4591 const int aPrimitiveIndex, const bool aIsBoardImport )
4592{
4593 if( aElem.polygon != ALTIUM_POLYGON_NONE )
4594 {
4595 wxFAIL_MSG( wxString::Format( "Altium: Unexpected footprint Arc with polygon id %d",
4596 aElem.polygon ) );
4597 return;
4598 }
4599
4600 if( aElem.is_keepout || aElem.layer == ALTIUM_LAYER::KEEP_OUT_LAYER
4601 || IsAltiumLayerAPlane( aElem.layer ) )
4602 {
4603 // This is not the actual board item. We can use it to create the polygon for the region
4604 PCB_SHAPE shape( nullptr );
4605
4606 ConvertArcs6ToPcbShape( aElem, &shape );
4608
4609 HelperPcpShapeAsFootprintKeepoutRegion( aFootprint, shape, aElem.layer,
4610 aElem.keepoutrestrictions );
4611 }
4612 else
4613 {
4614 for( PCB_LAYER_ID klayer : GetKicadLayersToIterate( aElem.layer ) )
4615 {
4616 if( IsCopperLayer( klayer ) && ( !aIsBoardImport || aElem.net != ALTIUM_NET_UNCONNECTED ) )
4617 {
4618 // Footprint graphics are not connected, so copper waits to join a pad it touches
4619 std::unique_ptr<PCB_SHAPE> arc = std::make_unique<PCB_SHAPE>( nullptr );
4620
4621 ConvertArcs6ToPcbShape( aElem, arc.get() );
4622 arc->SetStroke( STROKE_PARAMS( aElem.width, LINE_STYLE::SOLID ) );
4623
4624 HelperDeferComponentCopper( aFootprint, klayer, aIsBoardImport ? aElem.net : ALTIUM_NET_UNCONNECTED,
4625 std::move( arc ),
4626 [this, aFootprint, aElem, klayer, aIsBoardImport]()
4627 {
4628 if( aIsBoardImport )
4629 ConvertArcs6ToBoardItemOnLayer( aElem, klayer );
4630 else
4631 ConvertArcs6ToFootprintItemOnLayer( aFootprint, aElem, klayer );
4632 } );
4633 }
4634 else
4635 {
4636 ConvertArcs6ToFootprintItemOnLayer( aFootprint, aElem, klayer );
4637 }
4638 }
4639 }
4640
4641 for( const auto& layerExpansionMask :
4643 {
4644 int width = aElem.width + ( layerExpansionMask.second * 2 );
4645
4646 if( width > 1 )
4647 {
4648 std::unique_ptr<PCB_SHAPE> arc = std::make_unique<PCB_SHAPE>( aFootprint );
4649
4650 ConvertArcs6ToPcbShape( aElem, arc.get() );
4651 arc->SetStroke( STROKE_PARAMS( width, LINE_STYLE::SOLID ) );
4652 arc->SetLayer( layerExpansionMask.first );
4653
4654 aFootprint->Add( arc.release(), ADD_MODE::APPEND );
4655 }
4656 }
4657}
4658
4659
4661{
4662 if( IsCopperLayer( aLayer ) && aElem.net != ALTIUM_NET_UNCONNECTED )
4663 {
4664 double sweepDegrees = aElem.endangle - aElem.startangle;
4665 EDA_ANGLE includedAngle( sweepDegrees, DEGREES_T );
4666 EDA_ANGLE startAngle( aElem.endangle, DEGREES_T );
4667
4668 VECTOR2I startOffset = VECTOR2I( KiROUND( startAngle.Cos() * aElem.radius ),
4669 -KiROUND( startAngle.Sin() * aElem.radius ) );
4670
4671 auto addArc = [&]( const VECTOR2I& aStart, const EDA_ANGLE& aAngle )
4672 {
4673 SHAPE_ARC shapeArc( aElem.center, aStart, aAngle, aElem.width );
4674 std::unique_ptr<PCB_ARC> arc = std::make_unique<PCB_ARC>( m_board, &shapeArc );
4675
4676 arc->SetWidth( aElem.width );
4677 arc->SetLayer( aLayer );
4678 arc->SetNetCode( GetNetCode( aElem.net ) );
4679
4680 PCB_ARC* added = arc.release();
4681 m_board->Add( added, ADD_MODE::APPEND );
4682
4683 if( aElem.unionindex != 0 )
4684 m_unionToBoardItems[static_cast<int>( aElem.unionindex )].push_back( added );
4685 };
4686
4687 // PCB_ARC cannot represent a closed sweep, so emit the ring as two halves
4688 if( std::abs( sweepDegrees ) >= 359.999 )
4689 {
4690 EDA_ANGLE halfSweep( sweepDegrees < 0. ? -180. : 180., DEGREES_T );
4691
4692 addArc( aElem.center + startOffset, halfSweep );
4693 addArc( aElem.center - startOffset, halfSweep );
4694 return;
4695 }
4696
4697 includedAngle.Normalize();
4698
4699 if( includedAngle.AsDegrees() >= 0.1 )
4700 addArc( aElem.center + startOffset, includedAngle );
4701 }
4702 else
4703 {
4704 std::unique_ptr<PCB_SHAPE> arc = std::make_unique<PCB_SHAPE>(m_board);
4705
4706 ConvertArcs6ToPcbShape( aElem, arc.get() );
4707 arc->SetStroke( STROKE_PARAMS( aElem.width, LINE_STYLE::SOLID ) );
4708 arc->SetLayer( aLayer );
4709
4710 m_board->Add( arc.release(), ADD_MODE::APPEND );
4711 }
4712}
4713
4714
4716 PCB_LAYER_ID aLayer )
4717{
4718 std::unique_ptr<PCB_SHAPE> arc = std::make_unique<PCB_SHAPE>( aFootprint );
4719
4720 ConvertArcs6ToPcbShape( aElem, arc.get() );
4721 arc->SetStroke( STROKE_PARAMS( aElem.width, LINE_STYLE::SOLID ) );
4722 arc->SetLayer( aLayer );
4723
4724 aFootprint->Add( arc.release(), ADD_MODE::APPEND );
4725}
4726
4727
4729 const CFB::COMPOUND_FILE_ENTRY* aEntry )
4730{
4731 if( m_progressReporter )
4732 m_progressReporter->Report( _( "Loading pads..." ) );
4733
4734 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
4735
4736 while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
4737 {
4738 checkpoint();
4739 APAD6 elem( reader );
4740
4741 if( elem.component == ALTIUM_COMPONENT_NONE )
4742 {
4744 }
4745 else
4746 {
4747 FOOTPRINT* footprint = HelperGetFootprint( elem.component );
4748 ConvertPads6ToFootprintItem( footprint, elem );
4749 }
4750 }
4751
4752 if( reader.GetRemainingBytes() != 0 )
4753 THROW_IO_ERROR( wxT( "Pads6 stream is not fully parsed" ) );
4754}
4755
4756
4758{
4759 // It is possible to place altium pads on non-copper layers -> we need to interpolate them using drawings!
4760 if( !IsAltiumLayerCopper( aElem.layer ) && !IsAltiumLayerAPlane( aElem.layer )
4761 && aElem.layer != ALTIUM_LAYER::MULTI_LAYER )
4762 {
4764 }
4765 else
4766 {
4767 // We cannot add a pad directly into the PCB
4768 std::unique_ptr<FOOTPRINT> footprint = std::make_unique<FOOTPRINT>( m_board );
4769 footprint->SetPosition( aElem.position );
4770
4771 // This wrapper exists only to carry a free-standing pad; it has no schematic symbol and
4772 // nothing to buy or place, so keep it out of the BOM and the placement files.
4773 footprint->SetAttributes( FP_BOARD_ONLY | FP_EXCLUDE_FROM_BOM | FP_EXCLUDE_FROM_POS_FILES );
4774
4775 ConvertPads6ToFootprintItemOnCopper( footprint.get(), aElem );
4776
4777 m_board->Add( footprint.release(), ADD_MODE::APPEND );
4778 }
4779}
4780
4781
4783{
4784 std::unique_ptr<PAD> pad = std::make_unique<PAD>( aFootprint );
4785
4786 pad->SetNumber( "" );
4787 pad->SetNetCode( GetNetCode( aElem.net ) );
4788
4789 pad->SetPosition( aElem.position );
4790 pad->SetDrillSize( VECTOR2I( aElem.holesize, aElem.holesize ) );
4791 pad->SetDrillShape( PAD_DRILL_SHAPE::CIRCLE );
4792 pad->SetAttribute( PAD_ATTRIB::PTH );
4793
4794 // Pads are always through holes in KiCad
4795 pad->SetLayerSet( LSET().AllCuMask() );
4796
4797 if( aElem.viamode == ALTIUM_PAD_MODE::SIMPLE )
4798 {
4799 pad->Padstack().SetMode( PADSTACK::MODE::NORMAL );
4800 pad->SetSize( PADSTACK::ALL_LAYERS, VECTOR2I( aElem.diameter, aElem.diameter ) );
4802 }
4804 {
4805 pad->Padstack().SetMode( PADSTACK::MODE::FRONT_INNER_BACK );
4806
4810
4811 pad->SetSize( F_Cu, VECTOR2I( top, top ) );
4812 pad->SetShape( F_Cu, PAD_SHAPE::CIRCLE );
4813
4814 pad->SetSize( PADSTACK::INNER_LAYERS, VECTOR2I( mid, mid ) );
4816
4817 pad->SetSize( B_Cu, VECTOR2I( bot, bot ) );
4818 pad->SetShape( B_Cu, PAD_SHAPE::CIRCLE );
4819 }
4820 else
4821 {
4822 pad->Padstack().SetMode( PADSTACK::MODE::CUSTOM );
4823
4824 LSET cuLayers = LSET::AllCuMask();
4825
4826 if( m_board )
4827 cuLayers &= m_board->GetEnabledLayers();
4828
4829 for( PCB_LAYER_ID layer : cuLayers )
4830 {
4831 int altiumIdx = HelperGetPadstackLayerIndex( layer );
4832
4833 // Internal planes carry no padstack entry; the via keeps its nominal land there
4834 int diameter = altiumIdx < 0 ? aElem.diameter : aElem.diameter_by_layer[altiumIdx];
4835
4836 pad->SetSize( layer, VECTOR2I( diameter, diameter ) );
4837 pad->SetShape( layer, PAD_SHAPE::CIRCLE );
4838 }
4839 }
4840
4841 // Same rule as a board via, so a via folded into its component keeps its mask opening
4844 aElem.soldermask_expansion_front, pad->GetSize( F_Cu ).x );
4845
4848 aElem.soldermask_expansion_back, pad->GetSize( B_Cu ).x );
4849
4850 if( tentTop )
4851 {
4852 pad->Padstack().FrontOuterLayers().has_solder_mask = true;
4853 }
4854 else
4855 {
4856 pad->Padstack().FrontOuterLayers().has_solder_mask = false;
4857 pad->SetLayerSet( pad->GetLayerSet().set( F_Mask ) );
4858 }
4859
4860 if( tentBottom )
4861 {
4862 pad->Padstack().BackOuterLayers().has_solder_mask = true;
4863 }
4864 else
4865 {
4866 pad->Padstack().BackOuterLayers().has_solder_mask = false;
4867 pad->SetLayerSet( pad->GetLayerSet().set( B_Mask ) );
4868 }
4869
4870 if( aElem.is_locked )
4871 pad->SetLocked( true );
4872
4873 if( aElem.soldermask_expansion_manual )
4874 {
4875 pad->Padstack().FrontOuterLayers().solder_mask_margin = aElem.soldermask_expansion_front;
4876 pad->Padstack().BackOuterLayers().solder_mask_margin = aElem.soldermask_expansion_back;
4877 }
4878
4879 PAD* added = pad.release();
4880 aFootprint->Add( added, ADD_MODE::APPEND );
4881 return added;
4882}
4883
4884
4886{
4887 // It is possible to place altium pads on non-copper layers -> we need to interpolate them using drawings!
4888 if( !IsAltiumLayerCopper( aElem.layer ) && !IsAltiumLayerAPlane( aElem.layer )
4889 && aElem.layer != ALTIUM_LAYER::MULTI_LAYER )
4890 {
4891 ConvertPads6ToFootprintItemOnNonCopper( aFootprint, aElem );
4892 }
4893 else
4894 {
4895 ConvertPads6ToFootprintItemOnCopper( aFootprint, aElem );
4896 }
4897}
4898
4899
4901{
4902 std::unique_ptr<PAD> pad = std::make_unique<PAD>( aFootprint );
4903
4904 pad->SetNumber( aElem.name );
4905 pad->SetNetCode( GetNetCode( aElem.net ) );
4906
4907 pad->SetPosition( aElem.position );
4908 pad->SetOrientationDegrees( aElem.direction );
4909 pad->SetThermalSpokeAngle( ANGLE_90 );
4910
4911 if( aElem.holesize == 0 )
4912 {
4913 pad->SetAttribute( PAD_ATTRIB::SMD );
4914 }
4915 else
4916 {
4917 if( aElem.layer != ALTIUM_LAYER::MULTI_LAYER )
4918 {
4919 // TODO: I assume other values are possible as well?
4920 if( !m_footprintName.IsEmpty() )
4921 {
4922 if( m_reporter )
4923 {
4924 wxString msg;
4925 msg.Printf( _( "Error loading library '%s':\n"
4926 "Footprint %s pad %s is not marked as multilayer, but is a TH pad." ),
4927 m_library,
4929 aElem.name );
4930 m_reporter->Report( msg, RPT_SEVERITY_ERROR );
4931 }
4932 }
4933 else
4934 {
4935 if( m_reporter )
4936 {
4937 wxString msg;
4938 msg.Printf( _( "Footprint %s pad %s is not marked as multilayer, but is a TH pad." ),
4939 aFootprint->GetReference(),
4940 aElem.name );
4941 m_reporter->Report( msg, RPT_SEVERITY_ERROR );
4942 }
4943 }
4944 }
4945
4946 pad->SetAttribute( aElem.plated ? PAD_ATTRIB::PTH : PAD_ATTRIB::NPTH );
4947
4949 {
4950 pad->SetDrillShape( PAD_DRILL_SHAPE::CIRCLE );
4951 pad->SetDrillSize( VECTOR2I( aElem.holesize, aElem.holesize ) );
4952 }
4953 else
4954 {
4955 switch( aElem.sizeAndShape->holeshape )
4956 {
4958 wxFAIL_MSG( wxT( "Round holes are handled before the switch" ) );
4959 break;
4960
4962 if( !m_footprintName.IsEmpty() )
4963 {
4964 if( m_reporter )
4965 {
4966 wxString msg;
4967 msg.Printf( _( "Loading library '%s':\n"
4968 "Footprint %s pad %s has a square hole (not yet supported)." ),
4969 m_library,
4971 aElem.name );
4972 m_reporter->Report( msg, RPT_SEVERITY_DEBUG );
4973 }
4974 }
4975 else
4976 {
4977 if( m_reporter )
4978 {
4979 wxString msg;
4980 msg.Printf( _( "Footprint %s pad %s has a square hole (not yet supported)." ),
4981 aFootprint->GetReference(),
4982 aElem.name );
4983 m_reporter->Report( msg, RPT_SEVERITY_DEBUG );
4984 }
4985 }
4986
4987 pad->SetDrillShape( PAD_DRILL_SHAPE::CIRCLE );
4988 pad->SetDrillSize( VECTOR2I( aElem.holesize, aElem.holesize ) ); // Workaround
4989 // TODO: elem.sizeAndShape->slotsize was 0 in testfile. Either use holesize in
4990 // this case or rect holes have a different id
4991 break;
4992
4994 {
4995 pad->SetDrillShape( PAD_DRILL_SHAPE::OBLONG );
4996 bool slotRotationSupported;
4997 pad->SetDrillSize( altiumSlotDrillSize( aElem.holesize, aElem.sizeAndShape->slotsize,
4998 aElem.sizeAndShape->slotrotation, slotRotationSupported ) );
4999
5000 if( !slotRotationSupported )
5001 {
5002 EDA_ANGLE slotRotation( aElem.sizeAndShape->slotrotation, DEGREES_T );
5003 slotRotation.Normalize();
5004
5005 if( !m_footprintName.IsEmpty() )
5006 {
5007 if( m_reporter )
5008 {
5009 wxString msg;
5010 msg.Printf( _( "Loading library '%s':\n"
5011 "Footprint %s pad %s has a hole-rotation of %d degrees. "
5012 "KiCad only supports 90 degree rotations." ),
5013 m_library,
5015 aElem.name,
5016 KiROUND( slotRotation.AsDegrees() ) );
5017 m_reporter->Report( msg, RPT_SEVERITY_DEBUG );
5018 }
5019 }
5020 else
5021 {
5022 if( m_reporter )
5023 {
5024 wxString msg;
5025 msg.Printf( _( "Footprint %s pad %s has a hole-rotation of %d degrees. "
5026 "KiCad only supports 90 degree rotations." ),
5027 aFootprint->GetReference(),
5028 aElem.name,
5029 KiROUND( slotRotation.AsDegrees() ) );
5030 m_reporter->Report( msg, RPT_SEVERITY_DEBUG );
5031 }
5032 }
5033 }
5034
5035 break;
5036 }
5037
5038 default:
5040 if( !m_footprintName.IsEmpty() )
5041 {
5042 if( m_reporter )
5043 {
5044 wxString msg;
5045 msg.Printf( _( "Error loading library '%s':\n"
5046 "Footprint %s pad %s uses a hole of unknown kind %d." ),
5047 m_library,
5049 aElem.name,
5050 aElem.sizeAndShape->holeshape );
5051 m_reporter->Report( msg, RPT_SEVERITY_DEBUG );
5052 }
5053 }
5054 else
5055 {
5056 if( m_reporter )
5057 {
5058 wxString msg;
5059 msg.Printf( _( "Footprint %s pad %s uses a hole of unknown kind %d." ),
5060 aFootprint->GetReference(),
5061 aElem.name,
5062 aElem.sizeAndShape->holeshape );
5063 m_reporter->Report( msg, RPT_SEVERITY_DEBUG );
5064 }
5065 }
5066
5067 pad->SetDrillShape( PAD_DRILL_SHAPE::CIRCLE );
5068 pad->SetDrillSize( VECTOR2I( aElem.holesize, aElem.holesize ) ); // Workaround
5069 break;
5070 }
5071 }
5072 }
5073
5074 PADSTACK& ps = pad->Padstack();
5075
5076 auto setCopperGeometry =
5077 [&]( PCB_LAYER_ID aLayer, int aAltiumIdx, ALTIUM_PAD_SHAPE aShape,
5078 const VECTOR2I& aSize )
5079 {
5080 bool hasAltiumEntry = aElem.sizeAndShape && aAltiumIdx >= 0
5081 && aAltiumIdx < ALTIUM_PADSTACK_IDX_COUNT;
5082
5083 ps.SetSize( aSize, aLayer );
5084
5085 if( aElem.holesize != 0 && hasAltiumEntry )
5086 ps.SetOffset( aElem.sizeAndShape->holeoffset[aAltiumIdx], aLayer );
5087
5088 switch( aShape )
5089 {
5091 ps.SetShape( PAD_SHAPE::RECTANGLE, aLayer );
5092 break;
5093
5095 if( hasAltiumEntry
5096 && aElem.sizeAndShape->alt_shape[aAltiumIdx] == ALTIUM_PAD_SHAPE_ALT::ROUNDRECT )
5097 {
5098 ps.SetShape( PAD_SHAPE::ROUNDRECT, aLayer ); // 100 = round, 0 = rectangular
5099 double ratio = aElem.sizeAndShape->cornerradius[aAltiumIdx] / 200.;
5100 ps.SetRoundRectRadiusRatio( ratio, aLayer );
5101 }
5102 else if( aSize.x == aSize.y )
5103 {
5104 ps.SetShape( PAD_SHAPE::CIRCLE, aLayer );
5105 }
5106 else
5107 {
5108 ps.SetShape( PAD_SHAPE::OVAL, aLayer );
5109 }
5110
5111 break;
5112
5114 ps.SetShape( PAD_SHAPE::CHAMFERED_RECT, aLayer );
5116 ps.SetChamferRatio( 0.25, aLayer );
5117 break;
5118
5120 default:
5121 if( !m_footprintName.IsEmpty() )
5122 {
5123 if( m_reporter )
5124 {
5125 wxString msg;
5126 msg.Printf( _( "Error loading library '%s':\n"
5127 "Footprint %s pad %s uses an unknown pad shape." ),
5128 m_library,
5130 aElem.name );
5131 m_reporter->Report( msg, RPT_SEVERITY_DEBUG );
5132 }
5133 }
5134 else
5135 {
5136 if( m_reporter )
5137 {
5138 wxString msg;
5139 msg.Printf( _( "Footprint %s pad %s uses an unknown pad shape." ),
5140 aFootprint->GetReference(),
5141 aElem.name );
5142 m_reporter->Report( msg, RPT_SEVERITY_DEBUG );
5143 }
5144 }
5145 break;
5146 }
5147 };
5148
5149 switch( aElem.padmode )
5150 {
5153 setCopperGeometry( PADSTACK::ALL_LAYERS, ALTIUM_TOP_PADSTACK_IDX, aElem.topshape,
5154 aElem.topsize );
5155 break;
5156
5159 setCopperGeometry( F_Cu, ALTIUM_TOP_PADSTACK_IDX, aElem.topshape, aElem.topsize );
5160 setCopperGeometry( PADSTACK::INNER_LAYERS, ALTIUM_MID1_PADSTACK_IDX, aElem.midshape,
5161 aElem.midsize );
5162 setCopperGeometry( B_Cu, ALTIUM_BOTTOM_PADSTACK_IDX, aElem.botshape, aElem.botsize );
5163 break;
5164
5166 {
5168
5169 setCopperGeometry( F_Cu, HelperGetPadstackLayerIndex( F_Cu ), aElem.topshape,
5170 aElem.topsize );
5171 setCopperGeometry( B_Cu, HelperGetPadstackLayerIndex( B_Cu ), aElem.botshape,
5172 aElem.botsize );
5173
5174 LSET intLayers = aFootprint->BoardLayerSet() & LSET::InternalCuMask();
5175
5176 for( PCB_LAYER_ID layer : intLayers )
5177 {
5178 int idx = HelperGetPadstackLayerIndex( layer );
5179 int inner = idx - ALTIUM_MID2_PADSTACK_IDX;
5180
5181 // Mid layer 1 is carried in the record itself, and internal planes have no entry at
5182 // all, so both fall back to it
5183 if( !aElem.sizeAndShape || inner < 0
5184 || inner >= static_cast<int>( std::size( aElem.sizeAndShape->inner_size ) ) )
5185 {
5186 setCopperGeometry( layer, idx, aElem.midshape, aElem.midsize );
5187 }
5188 else
5189 {
5190 const APAD6_SIZE_AND_SHAPE& shape = *aElem.sizeAndShape;
5191
5192 setCopperGeometry( layer, idx, shape.inner_shape[inner],
5193 VECTOR2I( shape.inner_size[inner].x,
5194 shape.inner_size[inner].y ) );
5195 }
5196 }
5197
5198 break;
5199 }
5200 }
5201
5202 switch( aElem.layer )
5203 {
5205 pad->SetLayer( F_Cu );
5206 pad->SetLayerSet( PAD::SMDMask() );
5207 break;
5208
5210 pad->SetLayer( B_Cu );
5211 pad->SetLayerSet( PAD::SMDMask().FlipStandardLayers() );
5212 break;
5213
5215 pad->SetLayerSet( aElem.plated ? PAD::PTHMask() : PAD::UnplatedHoleMask() );
5216 break;
5217
5218 default:
5219 PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
5220 pad->SetLayer( klayer );
5221 pad->SetLayerSet( LSET( { klayer } ) );
5222 break;
5223 }
5224
5226 pad->SetLocalSolderPasteMargin( aElem.pastemaskexpansionmanual );
5227
5229 pad->SetLocalSolderMaskMargin( aElem.soldermaskexpansionmanual );
5230
5231 if( aElem.is_tent_top )
5232 pad->SetLayerSet( pad->GetLayerSet().reset( F_Mask ) );
5233
5234 if( aElem.is_tent_bottom )
5235 pad->SetLayerSet( pad->GetLayerSet().reset( B_Mask ) );
5236
5237 pad->SetPadToDieLength( aElem.pad_to_die_length );
5238 pad->SetPadToDieDelay( aElem.pad_to_die_delay );
5239
5240 aFootprint->Add( pad.release(), ADD_MODE::APPEND );
5241}
5242
5243
5245{
5246 PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
5247
5248 if( klayer == UNDEFINED_LAYER )
5249 {
5250 if( m_reporter )
5251 {
5252 wxString msg;
5253 msg.Printf( _( "Non-copper pad %s found on an Altium layer (%d) with no KiCad "
5254 "equivalent. It has been moved to KiCad layer Eco1_User." ),
5255 aElem.name, aElem.layer );
5256 m_reporter->Report( msg, RPT_SEVERITY_INFO );
5257 }
5258
5259 klayer = Eco1_User;
5260 }
5261
5262 std::unique_ptr<PCB_SHAPE> pad = std::make_unique<PCB_SHAPE>( m_board );
5263
5264 HelperParsePad6NonCopper( aElem, klayer, pad.get() );
5265
5266 m_board->Add( pad.release(), ADD_MODE::APPEND );
5267}
5268
5269
5271{
5272 PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
5273
5274 if( klayer == UNDEFINED_LAYER )
5275 {
5276 if( !m_footprintName.IsEmpty() )
5277 {
5278 if( m_reporter )
5279 {
5280 wxString msg;
5281 msg.Printf( _( "Loading library '%s':\n"
5282 "Footprint %s non-copper pad %s found on an Altium layer (%d) with no "
5283 "KiCad equivalent. It has been moved to KiCad layer Eco1_User." ),
5284 m_library,
5286 aElem.name,
5287 aElem.layer );
5288 m_reporter->Report( msg, RPT_SEVERITY_INFO );
5289 }
5290 }
5291 else
5292 {
5293 if( m_reporter )
5294 {
5295 wxString msg;
5296 msg.Printf( _( "Footprint %s non-copper pad %s found on an Altium layer (%d) with no "
5297 "KiCad equivalent. It has been moved to KiCad layer Eco1_User." ),
5298 aFootprint->GetReference(),
5299 aElem.name,
5300 aElem.layer );
5301 m_reporter->Report( msg, RPT_SEVERITY_INFO );
5302 }
5303 }
5304
5305 klayer = Eco1_User;
5306 }
5307
5308 std::unique_ptr<PCB_SHAPE> pad = std::make_unique<PCB_SHAPE>( aFootprint );
5309
5310 HelperParsePad6NonCopper( aElem, klayer, pad.get() );
5311
5312 aFootprint->Add( pad.release(), ADD_MODE::APPEND );
5313}
5314
5315
5317 PCB_SHAPE* aShape )
5318{
5319 if( aElem.net != ALTIUM_NET_UNCONNECTED )
5320 {
5321 if( m_reporter )
5322 {
5323 wxString msg;
5324 msg.Printf( _( "Non-copper pad %s is connected to a net, which is not supported." ),
5325 aElem.name );
5326 m_reporter->Report( msg, RPT_SEVERITY_DEBUG );
5327 }
5328 }
5329
5330 if( aElem.holesize != 0 )
5331 {
5332 if( m_reporter )
5333 {
5334 wxString msg;
5335 msg.Printf( _( "Non-copper pad %s has a hole, which is not supported." ), aElem.name );
5336 m_reporter->Report( msg, RPT_SEVERITY_DEBUG );
5337 }
5338 }
5339
5340 if( aElem.padmode != ALTIUM_PAD_MODE::SIMPLE )
5341 {
5342 if( m_reporter )
5343 {
5344 wxString msg;
5345 msg.Printf( _( "Non-copper pad %s has a complex pad stack (not yet supported)." ),
5346 aElem.name );
5347 m_reporter->Report( msg, RPT_SEVERITY_DEBUG );
5348 }
5349 }
5350
5351 switch( aElem.topshape )
5352 {
5354 {
5355 // filled rect
5356 aShape->SetShape( SHAPE_T::POLY );
5357 aShape->SetFilled( true );
5358 aShape->SetLayer( aLayer );
5359 aShape->SetStroke( STROKE_PARAMS( 0 ) );
5360
5361 aShape->SetPolyPoints( { aElem.position + VECTOR2I( aElem.topsize.x / 2, aElem.topsize.y / 2 ),
5362 aElem.position + VECTOR2I( aElem.topsize.x / 2, -aElem.topsize.y / 2 ),
5363 aElem.position + VECTOR2I( -aElem.topsize.x / 2, -aElem.topsize.y / 2 ),
5364 aElem.position + VECTOR2I( -aElem.topsize.x / 2, aElem.topsize.y / 2 ) } );
5365
5366 if( aElem.direction != 0 )
5367 aShape->Rotate( aElem.position, EDA_ANGLE( aElem.direction, DEGREES_T ) );
5368 }
5369 break;
5370
5372 if( aElem.sizeAndShape
5374 {
5375 // filled roundrect
5376 int cornerradius = aElem.sizeAndShape->cornerradius[0];
5377 int offset = ( std::min( aElem.topsize.x, aElem.topsize.y ) * cornerradius ) / 200;
5378
5379 aShape->SetLayer( aLayer );
5380 aShape->SetStroke( STROKE_PARAMS( offset * 2, LINE_STYLE::SOLID ) );
5381
5382 if( cornerradius < 100 )
5383 {
5384 int offsetX = aElem.topsize.x / 2 - offset;
5385 int offsetY = aElem.topsize.y / 2 - offset;
5386
5387 VECTOR2I p11 = aElem.position + VECTOR2I( offsetX, offsetY );
5388 VECTOR2I p12 = aElem.position + VECTOR2I( offsetX, -offsetY );
5389 VECTOR2I p22 = aElem.position + VECTOR2I( -offsetX, -offsetY );
5390 VECTOR2I p21 = aElem.position + VECTOR2I( -offsetX, offsetY );
5391
5392 aShape->SetShape( SHAPE_T::POLY );
5393 aShape->SetFilled( true );
5394 aShape->SetPolyPoints( { p11, p12, p22, p21 } );
5395 }
5396 else if( aElem.topsize.x == aElem.topsize.y )
5397 {
5398 // circle
5399 aShape->SetShape( SHAPE_T::CIRCLE );
5400 aShape->SetFilled( true );
5401 aShape->SetStart( aElem.position );
5402 aShape->SetEnd( aElem.position - VECTOR2I( 0, aElem.topsize.x / 4 ) );
5403 aShape->SetStroke( STROKE_PARAMS( aElem.topsize.x / 2, LINE_STYLE::SOLID ) );
5404 }
5405 else if( aElem.topsize.x < aElem.topsize.y )
5406 {
5407 // short vertical line
5408 aShape->SetShape( SHAPE_T::SEGMENT );
5409 VECTOR2I pointOffset( 0, ( aElem.topsize.y / 2 - aElem.topsize.x / 2 ) );
5410 aShape->SetStart( aElem.position + pointOffset );
5411 aShape->SetEnd( aElem.position - pointOffset );
5412 }
5413 else
5414 {
5415 // short horizontal line
5416 aShape->SetShape( SHAPE_T::SEGMENT );
5417 VECTOR2I pointOffset( ( aElem.topsize.x / 2 - aElem.topsize.y / 2 ), 0 );
5418 aShape->SetStart( aElem.position + pointOffset );
5419 aShape->SetEnd( aElem.position - pointOffset );
5420 }
5421
5422 if( aElem.direction != 0 )
5423 aShape->Rotate( aElem.position, EDA_ANGLE( aElem.direction, DEGREES_T ) );
5424 }
5425 else if( aElem.topsize.x == aElem.topsize.y )
5426 {
5427 // filled circle
5428 aShape->SetShape( SHAPE_T::CIRCLE );
5429 aShape->SetFilled( true );
5430 aShape->SetLayer( aLayer );
5431 aShape->SetStart( aElem.position );
5432 aShape->SetEnd( aElem.position - VECTOR2I( 0, aElem.topsize.x / 4 ) );
5433 aShape->SetStroke( STROKE_PARAMS( aElem.topsize.x / 2, LINE_STYLE::SOLID ) );
5434 }
5435 else
5436 {
5437 // short line
5438 aShape->SetShape( SHAPE_T::SEGMENT );
5439 aShape->SetLayer( aLayer );
5440 aShape->SetStroke( STROKE_PARAMS( std::min( aElem.topsize.x, aElem.topsize.y ),
5442
5443 if( aElem.topsize.x < aElem.topsize.y )
5444 {
5445 VECTOR2I offset( 0, ( aElem.topsize.y / 2 - aElem.topsize.x / 2 ) );
5446 aShape->SetStart( aElem.position + offset );
5447 aShape->SetEnd( aElem.position - offset );
5448 }
5449 else
5450 {
5451 VECTOR2I offset( ( aElem.topsize.x / 2 - aElem.topsize.y / 2 ), 0 );
5452 aShape->SetStart( aElem.position + offset );
5453 aShape->SetEnd( aElem.position - offset );
5454 }
5455
5456 if( aElem.direction != 0 )
5457 aShape->Rotate( aElem.position, EDA_ANGLE( aElem.direction, DEGREES_T ) );
5458 }
5459 break;
5460
5462 {
5463 // filled octagon
5464 aShape->SetShape( SHAPE_T::POLY );
5465 aShape->SetFilled( true );
5466 aShape->SetLayer( aLayer );
5467 aShape->SetStroke( STROKE_PARAMS( 0 ) );
5468
5469 VECTOR2I p11 = aElem.position + VECTOR2I( aElem.topsize.x / 2, aElem.topsize.y / 2 );
5470 VECTOR2I p12 = aElem.position + VECTOR2I( aElem.topsize.x / 2, -aElem.topsize.y / 2 );
5471 VECTOR2I p22 = aElem.position + VECTOR2I( -aElem.topsize.x / 2, -aElem.topsize.y / 2 );
5472 VECTOR2I p21 = aElem.position + VECTOR2I( -aElem.topsize.x / 2, aElem.topsize.y / 2 );
5473
5474 int chamfer = std::min( aElem.topsize.x, aElem.topsize.y ) / 4;
5475 VECTOR2I chamferX( chamfer, 0 );
5476 VECTOR2I chamferY( 0, chamfer );
5477
5478 aShape->SetPolyPoints( { p11 - chamferX, p11 - chamferY, p12 + chamferY, p12 - chamferX,
5479 p22 + chamferX, p22 + chamferY, p21 - chamferY, p21 + chamferX } );
5480
5481 if( aElem.direction != 0. )
5482 aShape->Rotate( aElem.position, EDA_ANGLE( aElem.direction, DEGREES_T ) );
5483 }
5484 break;
5485
5487 default:
5488 if( m_reporter )
5489 {
5490 wxString msg;
5491 msg.Printf( _( "Non-copper pad %s uses an unknown pad shape." ), aElem.name );
5492 m_reporter->Report( msg, RPT_SEVERITY_DEBUG );
5493 }
5494
5495 break;
5496 }
5497}
5498
5499
5501 const CFB::COMPOUND_FILE_ENTRY* aEntry )
5502{
5503 if( m_progressReporter )
5504 m_progressReporter->Report( _( "Loading vias..." ) );
5505
5506 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
5507
5508 while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
5509 {
5510 checkpoint();
5511 AVIA6 elem( reader );
5512
5513 bool throughVia = ( elem.layer_start == ALTIUM_LAYER::TOP_LAYER
5517
5518 // A footprint pad is always through-hole, so only a through via can join its component
5519 if( throughVia && elem.net != ALTIUM_NET_UNCONNECTED && elem.component != ALTIUM_COMPONENT_NONE
5520 && elem.component < m_components.size() && m_components[elem.component] )
5521 {
5522 m_pendingComponentVias.emplace_back( m_components[elem.component], elem );
5523 }
5524 else
5525 {
5527 }
5528 }
5529
5530 if( reader.GetRemainingBytes() != 0 )
5531 THROW_IO_ERROR( wxT( "Vias6 stream is not fully parsed" ) );
5532}
5533
5534
5536{
5537 std::unique_ptr<PCB_VIA> via = std::make_unique<PCB_VIA>( m_board );
5538
5539 via->SetPosition( aElem.position );
5540 via->SetDrill( aElem.holesize );
5541 via->SetNetCode( GetNetCode( aElem.net ) );
5542 via->SetLocked( aElem.is_locked );
5543
5544 bool start_layer_outside = aElem.layer_start == ALTIUM_LAYER::TOP_LAYER
5546 bool end_layer_outside = aElem.layer_end == ALTIUM_LAYER::TOP_LAYER
5548
5549 if( start_layer_outside && end_layer_outside )
5550 {
5551 via->SetViaType( VIATYPE::THROUGH );
5552 }
5553 else if( ( !start_layer_outside ) && ( !end_layer_outside ) )
5554 {
5555 via->SetViaType( VIATYPE::BURIED );
5556 }
5557 else
5558 {
5559 via->SetViaType( VIATYPE::BLIND );
5560 }
5561
5562 // TODO: Altium has a specific flag for microvias, independent of start/end layer
5563#if 0
5564 if( something )
5565 via->SetViaType( VIATYPE::MICROVIA );
5566#endif
5567
5568 PCB_LAYER_ID start_klayer = GetKicadLayer( aElem.layer_start );
5569 PCB_LAYER_ID end_klayer = GetKicadLayer( aElem.layer_end );
5570
5571 if( !IsCopperLayer( start_klayer ) || !IsCopperLayer( end_klayer ) )
5572 {
5573 if( m_reporter )
5574 {
5575 wxString msg;
5576 msg.Printf( _( "Via from layer %d to %d uses a non-copper layer, which is not "
5577 "supported." ),
5578 aElem.layer_start,
5579 aElem.layer_end );
5580 m_reporter->Report( msg, RPT_SEVERITY_DEBUG );
5581 }
5582
5583 return; // just assume through-hole instead.
5584 }
5585
5586 // we need VIATYPE set!
5587 via->SetLayerPair( start_klayer, end_klayer );
5588
5589 switch( aElem.viamode )
5590 {
5591 default:
5593 via->SetWidth( PADSTACK::ALL_LAYERS, aElem.diameter );
5594 break;
5595
5597 via->Padstack().SetMode( PADSTACK::MODE::FRONT_INNER_BACK );
5601 break;
5602
5604 {
5605 via->Padstack().SetMode( PADSTACK::MODE::CUSTOM );
5606
5607 LSET cuLayers = m_board->GetEnabledLayers() & LSET::AllCuMask();
5608
5609 for( PCB_LAYER_ID layer : cuLayers )
5610 {
5611 int altiumLayer = HelperGetPadstackLayerIndex( layer );
5612
5613 // Internal planes carry no padstack entry; the via keeps its nominal land there
5614 via->SetWidth( layer, altiumLayer < 0 ? aElem.diameter
5615 : aElem.diameter_by_layer[altiumLayer] );
5616 }
5617
5618 break;
5619 }
5620 }
5621
5622 // Altium can size the solder mask opening from the hole edge instead of the via land.
5623 // KiCad vias cannot represent a hole-referenced opening, so when the resulting opening
5624 // does not clear the via land the pad copper is covered and the via is effectively tented.
5628 via->GetWidth( F_Cu ) );
5629
5630 bool tentBottom = altiumViaSideIsTented( aElem.is_tent_bottom,
5634 via->GetWidth( B_Cu ) );
5635
5636 via->SetFrontTentingMode( tentTop ? TENTING_MODE::TENTED : TENTING_MODE::NOT_TENTED );
5637 via->SetBackTentingMode( tentBottom ? TENTING_MODE::TENTED : TENTING_MODE::NOT_TENTED );
5638
5639 m_board->Add( via.release(), ADD_MODE::APPEND );
5640}
5641
5643 const CFB::COMPOUND_FILE_ENTRY* aEntry )
5644{
5645 if( m_progressReporter )
5646 m_progressReporter->Report( _( "Loading tracks..." ) );
5647
5648 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
5649
5650 for( int primitiveIndex = 0; reader.GetRemainingBytes() >= 4; primitiveIndex++ )
5651 {
5652 checkpoint();
5653 ATRACK6 elem( reader );
5654
5655 if( elem.component == ALTIUM_COMPONENT_NONE )
5656 {
5657 ConvertTracks6ToBoardItem( elem, primitiveIndex );
5658 }
5659 else
5660 {
5661 FOOTPRINT* footprint = HelperGetFootprint( elem.component );
5662 ConvertTracks6ToFootprintItem( footprint, elem, primitiveIndex, true );
5663 }
5664 }
5665
5666 if( reader.GetRemainingBytes() != 0 )
5667 THROW_IO_ERROR( wxT( "Tracks6 stream is not fully parsed" ) );
5668}
5669
5670
5671void ALTIUM_PCB::ConvertTracks6ToBoardItem( const ATRACK6& aElem, const int aPrimitiveIndex )
5672{
5673 if( aElem.polygon != ALTIUM_POLYGON_NONE && aElem.polygon != ALTIUM_POLYGON_BOARD )
5674 {
5675 if( m_polygons.size() <= aElem.polygon )
5676 {
5677 // Can happen when reading old Altium files: just skip this item
5678 if( m_reporter )
5679 {
5680 wxString msg;
5681 msg.Printf( wxT( "ATRACK6 stream tries to access polygon id %u "
5682 "of %u existing polygons; skipping it" ),
5683 static_cast<unsigned>( aElem.polygon ),
5684 static_cast<unsigned>( m_polygons.size() ) );
5685 m_reporter->Report( msg, RPT_SEVERITY_DEBUG );
5686 }
5687
5688 return;
5689 }
5690
5691 ZONE* zone = m_polygons.at( aElem.polygon );
5692
5693 if( zone == nullptr )
5694 {
5695 return; // we know the zone id, but because we do not know the layer we did not
5696 // add it!
5697 }
5698
5699 PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
5700
5701 if( klayer == UNDEFINED_LAYER )
5702 return; // Just skip it for now. Users can fill it themselves.
5703
5704 if( !zone->HasFilledPolysForLayer( klayer ) )
5705 return;
5706
5707 SHAPE_POLY_SET* fill = zone->GetFill( klayer );
5708
5709 PCB_SHAPE shape( nullptr, SHAPE_T::SEGMENT );
5710 shape.SetStart( aElem.start );
5711 shape.SetEnd( aElem.end );
5713
5714 shape.EDA_SHAPE::TransformShapeToPolygon( *fill, 0, ARC_HIGH_DEF, ERROR_INSIDE );
5715 // Will be simplified and fractured later
5716
5717 zone->SetIsFilled( true );
5718 zone->SetNeedRefill( false );
5719
5720 return;
5721 }
5722
5723 if( aElem.is_keepout || aElem.layer == ALTIUM_LAYER::KEEP_OUT_LAYER
5724 || IsAltiumLayerAPlane( aElem.layer ) )
5725 {
5726 // This is not the actual board item. We can use it to create the polygon for the region
5727 PCB_SHAPE shape( nullptr, SHAPE_T::SEGMENT );
5728 shape.SetStart( aElem.start );
5729 shape.SetEnd( aElem.end );
5731
5733 }
5734 else
5735 {
5736 for( PCB_LAYER_ID klayer : GetKicadLayersToIterate( aElem.layer ) )
5737 ConvertTracks6ToBoardItemOnLayer( aElem, klayer );
5738 }
5739
5740 for( const auto& layerExpansionMask : HelperGetSolderAndPasteMaskExpansions( ALTIUM_RECORD::TRACK,
5741 aPrimitiveIndex, aElem.layer ) )
5742 {
5743 int width = aElem.width + ( layerExpansionMask.second * 2 );
5744 if( width > 1 )
5745 {
5746 std::unique_ptr<PCB_SHAPE> seg = std::make_unique<PCB_SHAPE>( m_board, SHAPE_T::SEGMENT );
5747
5748 seg->SetStart( aElem.start );
5749 seg->SetEnd( aElem.end );
5750 seg->SetStroke( STROKE_PARAMS( width, LINE_STYLE::SOLID ) );
5751 seg->SetLayer( layerExpansionMask.first );
5752
5753 m_board->Add( seg.release(), ADD_MODE::APPEND );
5754 }
5755 }
5756}
5757
5758
5760 const int aPrimitiveIndex,
5761 const bool aIsBoardImport )
5762{
5763 if( aElem.polygon != ALTIUM_POLYGON_NONE )
5764 {
5765 wxFAIL_MSG( wxString::Format( wxT( "Altium: Unexpected footprint Track with polygon id %u" ),
5766 (unsigned)aElem.polygon ) );
5767 return;
5768 }
5769
5770 if( aElem.is_keepout || aElem.layer == ALTIUM_LAYER::KEEP_OUT_LAYER
5771 || IsAltiumLayerAPlane( aElem.layer ) )
5772 {
5773 // This is not the actual board item. We can use it to create the polygon for the region
5774 PCB_SHAPE shape( nullptr, SHAPE_T::SEGMENT );
5775 shape.SetStart( aElem.start );
5776 shape.SetEnd( aElem.end );
5778
5779 HelperPcpShapeAsFootprintKeepoutRegion( aFootprint, shape, aElem.layer,
5780 aElem.keepoutrestrictions );
5781 }
5782 else
5783 {
5784 for( PCB_LAYER_ID klayer : GetKicadLayersToIterate( aElem.layer ) )
5785 {
5786 if( IsCopperLayer( klayer ) && ( !aIsBoardImport || aElem.net != ALTIUM_NET_UNCONNECTED ) )
5787 {
5788 // Footprint graphics are not connected, so copper waits to join a pad it touches
5789 std::unique_ptr<PCB_SHAPE> seg = std::make_unique<PCB_SHAPE>( nullptr, SHAPE_T::SEGMENT );
5790
5791 seg->SetStart( aElem.start );
5792 seg->SetEnd( aElem.end );
5793 seg->SetStroke( STROKE_PARAMS( aElem.width, LINE_STYLE::SOLID ) );
5794
5795 HelperDeferComponentCopper( aFootprint, klayer, aIsBoardImport ? aElem.net : ALTIUM_NET_UNCONNECTED,
5796 std::move( seg ),
5797 [this, aFootprint, aElem, klayer, aIsBoardImport]()
5798 {
5799 if( aIsBoardImport )
5800 ConvertTracks6ToBoardItemOnLayer( aElem, klayer );
5801 else
5802 ConvertTracks6ToFootprintItemOnLayer( aFootprint, aElem, klayer );
5803 } );
5804 }
5805 else
5806 {
5807 ConvertTracks6ToFootprintItemOnLayer( aFootprint, aElem, klayer );
5808 }
5809 }
5810 }
5811
5812 for( const auto& layerExpansionMask : HelperGetSolderAndPasteMaskExpansions( ALTIUM_RECORD::TRACK,
5813 aPrimitiveIndex, aElem.layer ) )
5814 {
5815 int width = aElem.width + ( layerExpansionMask.second * 2 );
5816 if( width > 1 )
5817 {
5818 std::unique_ptr<PCB_SHAPE> seg = std::make_unique<PCB_SHAPE>( aFootprint, SHAPE_T::SEGMENT );
5819
5820 seg->SetStart( aElem.start );
5821 seg->SetEnd( aElem.end );
5822 seg->SetStroke( STROKE_PARAMS( width, LINE_STYLE::SOLID ) );
5823 seg->SetLayer( layerExpansionMask.first );
5824
5825 aFootprint->Add( seg.release(), ADD_MODE::APPEND );
5826 }
5827 }
5828}
5829
5830
5832{
5833 if( IsCopperLayer( aLayer ) && aElem.net != ALTIUM_NET_UNCONNECTED )
5834 {
5835 std::unique_ptr<PCB_TRACK> track = std::make_unique<PCB_TRACK>( m_board );
5836
5837 track->SetStart( aElem.start );
5838 track->SetEnd( aElem.end );
5839 track->SetWidth( aElem.width );
5840 track->SetLayer( aLayer );
5841 track->SetNetCode( GetNetCode( aElem.net ) );
5842
5843 PCB_TRACK* added = track.release();
5844 m_board->Add( added, ADD_MODE::APPEND );
5845
5846 if( aElem.unionindex != 0 )
5847 m_unionToBoardItems[static_cast<int>( aElem.unionindex )].push_back( added );
5848 }
5849 else
5850 {
5851 std::unique_ptr<PCB_SHAPE> seg = std::make_unique<PCB_SHAPE>( m_board, SHAPE_T::SEGMENT );
5852
5853 seg->SetStart( aElem.start );
5854 seg->SetEnd( aElem.end );
5855 seg->SetStroke( STROKE_PARAMS( aElem.width, LINE_STYLE::SOLID ) );
5856 seg->SetLayer( aLayer );
5857
5858 m_board->Add( seg.release(), ADD_MODE::APPEND );
5859 }
5860}
5861
5862
5864 PCB_LAYER_ID aLayer )
5865{
5866 std::unique_ptr<PCB_SHAPE> seg = std::make_unique<PCB_SHAPE>( aFootprint, SHAPE_T::SEGMENT );
5867
5868 seg->SetStart( aElem.start );
5869 seg->SetEnd( aElem.end );
5870 seg->SetStroke( STROKE_PARAMS( aElem.width, LINE_STYLE::SOLID ) );
5871 seg->SetLayer( aLayer );
5872
5873 aFootprint->Add( seg.release(), ADD_MODE::APPEND );
5874}
5875
5876
5878 const CFB::COMPOUND_FILE_ENTRY* aEntry )
5879{
5880 if( m_progressReporter )
5881 m_progressReporter->Report( _( "Loading unicode strings..." ) );
5882
5883 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
5884
5886
5887 if( reader.GetRemainingBytes() != 0 )
5888 THROW_IO_ERROR( wxT( "WideStrings6 stream is not fully parsed" ) );
5889}
5890
5892 const CFB::COMPOUND_FILE_ENTRY* aEntry )
5893{
5894 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
5895
5896 while( reader.GetRemainingBytes() >= 4 )
5897 {
5898 checkpoint();
5899 ASMARTUNION6 elem( reader );
5900
5901 if( elem.is_tuning && elem.unionindex != 0 )
5902 m_tuningUnions.emplace_back( std::move( elem ) );
5903 }
5904
5905 if( reader.GetRemainingBytes() != 0 )
5906 THROW_IO_ERROR( wxT( "SmartUnions6 stream is not fully parsed" ) );
5907}
5908
5909
5911 const CFB::COMPOUND_FILE_ENTRY* aEntry )
5912{
5913 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
5914
5915 // Discard the leading record count, otherwise the wide-string table desyncs by four bytes
5916 reader.Read<uint32_t>();
5918
5919 if( reader.GetRemainingBytes() != 0 )
5920 THROW_IO_ERROR( wxT( "UnionNames stream is not fully parsed" ) );
5921}
5922
5923
5925{
5926 int created = 0;
5927
5928 for( const ASMARTUNION6& tuning : m_tuningUnions )
5929 {
5930 auto itemsIt = m_unionToBoardItems.find( tuning.unionindex );
5931
5932 // Altium commits the tuned copper as ordinary tracks and arcs. Without those primitives
5933 // there is nothing to wrap, so drop the meander rather than fabricate geometry.
5934 if( itemsIt == m_unionToBoardItems.end() || itemsIt->second.empty() )
5935 continue;
5936
5937 // Without a baseline the pattern can be neither re-tuned nor reset, so wrapping the copper
5938 // would only take it away from the user
5939 if( tuning.baseline.size() < 2
5940 || ( tuning.is_diffpair && tuning.baselinecoupled.size() < 2 ) )
5941 {
5942 continue;
5943 }
5944
5945 const std::vector<BOARD_ITEM*>& items = itemsIt->second;
5946
5947 LENGTH_TUNING_MODE mode = tuning.is_diffpair ? LENGTH_TUNING_MODE::DIFF_PAIR
5949
5950 SHAPE_LINE_CHAIN baseLine( tuning.baseline );
5951
5952 PCB_LAYER_ID layer = items.front()->GetLayer();
5953
5954 PCB_GENERATOR* generator = GENERATORS_MGR::Instance().CreateFromType( wxS( "tuning_pattern" ) );
5955
5956 if( !generator )
5957 continue;
5958
5959 std::unique_ptr<PCB_TUNING_PATTERN> pattern( static_cast<PCB_TUNING_PATTERN*>( generator ) );
5960 pattern->SetParent( m_board );
5961 pattern->SetLayer( layer );
5962 pattern->SetTuningMode( mode );
5963
5964 // Preserve Altium's interactive union name so the meander keeps its designer-visible label.
5965 if( auto nameIt = m_unionNames.find( tuning.unionindex );
5966 nameIt != m_unionNames.end() && !nameIt->second.IsEmpty() )
5967 {
5968 pattern->SetName( nameIt->second );
5969 }
5970
5971 pattern->SetMaxAmplitude( tuning.amplitude );
5972 pattern->SetMinAmplitude( tuning.minamplitude );
5973 pattern->SetSpacing( tuning.gap );
5974 pattern->SetSingleSided( tuning.singleside );
5975
5976 // Altium "Style" selects mitered (chamfered) versus rounded corners. The committed
5977 // copper carries the real geometry; this only governs a later interactive re-tune.
5978 pattern->SetRounded( tuning.style != 0 );
5979
5980 if( tuning.mitterradiusratio > 0.0 )
5981 {
5982 int percent = KiROUND( tuning.mitterradiusratio * 100.0 );
5983 pattern->SetCornerRadiusPercentage( std::clamp( percent, 0, 100 ) );
5984 }
5985
5986 int netCode = -1;
5987 bool singleNet = true;
5988
5989 for( BOARD_ITEM* item : items )
5990 {
5991 pattern->AddItem( item );
5992
5993 if( BOARD_CONNECTED_ITEM* bci = dynamic_cast<BOARD_CONNECTED_ITEM*>( item ) )
5994 {
5995 if( netCode < 0 )
5996 netCode = bci->GetNetCode();
5997 else if( netCode != bci->GetNetCode() )
5998 singleNet = false;
5999 }
6000 }
6001
6002 // SetNetCode reassigns the net of every member, so only apply it when the union is on a
6003 // single net. Differential-pair meanders span two nets that must both be preserved.
6004 if( netCode >= 0 && singleNet )
6005 {
6006 pattern->SetNetCode( netCode );
6007 }
6008 else
6009 {
6010 // Name the pattern after the net at the baseline start, the one an edit snaps to
6011 const VECTOR2I& origin = baseLine.CPoint( 0 );
6012 SEG::ecoord bestDist = std::numeric_limits<SEG::ecoord>::max();
6013 wxString bestNet;
6014
6015 for( BOARD_ITEM* item : items )
6016 {
6017 PCB_TRACK* track = dynamic_cast<PCB_TRACK*>( item );
6018
6019 if( !track )
6020 continue;
6021
6022 SEG::ecoord dist = SEG( track->GetStart(), track->GetEnd() ).SquaredDistance( origin );
6023
6024 if( dist < bestDist )
6025 {
6026 bestDist = dist;
6027 bestNet = track->GetNetname();
6028 }
6029 }
6030
6031 pattern->SetLastNetName( bestNet );
6032 }
6033
6034 if( PCB_TRACK* track = dynamic_cast<PCB_TRACK*>( items.front() ) )
6035 pattern->SetWidth( track->GetWidth() );
6036
6037 pattern->SetBaseLine( baseLine );
6038 pattern->SetPosition( baseLine.CPoint( 0 ) );
6039 pattern->SetEnd( baseLine.CLastPoint() );
6040
6041 if( mode == LENGTH_TUNING_MODE::DIFF_PAIR )
6042 {
6043 SHAPE_LINE_CHAIN baseLineCoupled( tuning.baselinecoupled );
6044
6045 pattern->SetBaseLineCoupled( baseLineCoupled );
6046
6047 int centreToCentre = baseLine.Distance( baseLineCoupled.CPoint( 0 ), false );
6048
6049 pattern->SetDiffPairGap( std::max( centreToCentre - pattern->GetWidth(), 0 ) );
6050 }
6051
6052 m_board->Add( pattern.release(), ADD_MODE::INSERT );
6053 created++;
6054 }
6055
6056 if( m_reporter && created > 0 )
6057 {
6058 m_reporter->Report( wxString::Format( _( "Imported %d length-tuning pattern(s)." ),
6059 created ),
6061 }
6062}
6063
6064
6066{
6067 for( FOOTPRINT* footprint : m_components )
6068 {
6069 if( !footprint )
6070 continue;
6071
6072 LSET drawnCopper;
6073
6074 // Board copper is bounded by the board, so single-layer items are the drawn layers
6075 footprint->RunOnChildren(
6076 [&]( BOARD_ITEM* aItem )
6077 {
6078 LSET copper = aItem->GetLayerSet() & LSET::AllCuMask();
6079
6080 if( copper.count() == 1 )
6081 drawnCopper |= copper;
6082 },
6084
6085 // Inner copper folded into a pad is drawn on that layer alone
6086 for( PAD* pad : footprint->Pads() )
6087 {
6088 if( pad->Padstack().Mode() != PADSTACK::MODE::CUSTOM )
6089 continue;
6090
6091 const LSET padInner = pad->GetLayerSet() & LSET::InternalCuMask();
6092
6093 for( PCB_LAYER_ID layer : padInner )
6094 {
6095 if( !pad->GetPrimitives( layer ).empty() )
6096 drawnCopper.set( layer );
6097 }
6098 }
6099
6100 // A bottom-side footprint's inner items are mirrored through the board's stackup
6101 altiumSetFootprintStackup( footprint, drawnCopper, m_board->GetCopperLayerCount() );
6102 }
6103}
6104
6105
6106void ALTIUM_PCB::HelperDeferComponentCopper( FOOTPRINT* aFootprint, PCB_LAYER_ID aLayer, uint16_t aNet,
6107 std::unique_ptr<PCB_SHAPE> aShape, std::function<void()> aFallback )
6108{
6109 aShape->SetLayer( aLayer );
6110
6111 m_pendingComponentCopper.push_back( { aFootprint, aLayer, GetNetCode( aNet ), std::move( aShape ),
6112 std::move( aFallback ) } );
6113}
6114
6115
6116static PAD* altiumFindTouchingPad( FOOTPRINT* aFootprint, PCB_LAYER_ID aLayer, int aNetCode, const SHAPE& aShape,
6117 const PAD* aExclude = nullptr )
6118{
6119 PAD* found = nullptr;
6120
6121 for( PAD* pad : aFootprint->Pads() )
6122 {
6123 if( pad == aExclude || pad->GetNumber().IsEmpty() || pad->GetNetCode() != aNetCode
6124 || !pad->IsOnLayer( aLayer ) )
6125 {
6126 continue;
6127 }
6128
6129 if( aShape.BBox().Intersects( pad->GetBoundingBox() )
6130 && aShape.Collide( pad->GetEffectiveShape( aLayer ).get() ) )
6131 {
6132 if( aNetCode > 0 )
6133 return pad;
6134
6135 // Without a net, copper bridging two pad numbers has no single owner
6136 if( found && found->GetNumber() != pad->GetNumber() )
6137 return nullptr;
6138
6139 found = pad;
6140 }
6141 }
6142
6143 return found;
6144}
6145
6146
6147bool ALTIUM_PCB::HelperFoldPendingCopper( const LSET& aCopper, int aMaxError, std::set<PAD*>& aExtensionPads )
6148{
6149 // A custom outline is placed at ShapePos, so a pad offset must come out with the position
6150 auto toPadFrame =
6151 []( PCB_SHAPE* aShape, const PAD* aPad, PCB_LAYER_ID aLayer )
6152 {
6153 aShape->Move( -aPad->ShapePos( aLayer ) );
6154 aShape->Rotate( VECTOR2I( 0, 0 ), -aPad->GetOrientation() );
6155 };
6156
6157 auto fold =
6158 [&]( PENDING_COMPONENT_COPPER& aPending, PAD* aPad )
6159 {
6160 PCB_LAYER_ID layer = aPending.layer;
6161 SHAPE_POLY_SET uncovered;
6162 SHAPE_POLY_SET padCopper;
6163
6164 aPending.shape->TransformShapeToPolygon( uncovered, layer, 0, aMaxError, ERROR_OUTSIDE );
6165 aPad->TransformShapeToPolygon( padCopper, layer, 0, aMaxError, ERROR_INSIDE );
6166 uncovered.BooleanSubtract( padCopper );
6167
6168 // Copper already inside the pad would only add a redundant primitive or pad
6169 if( uncovered.IsEmpty() )
6170 {
6171 aPending.shape.reset();
6172 return;
6173 }
6174
6175 if( IsInnerCopperLayer( layer ) )
6176 {
6177 // Inner layers carry no mask or paste, so the pad itself can grow there
6178 altiumIsolatePadLayer( aPad, layer, aCopper );
6179 altiumMakePadLayerCustom( aPad, layer, aMaxError );
6180 toPadFrame( aPending.shape.get(), aPad, layer );
6181 aPad->AddPrimitive( layer, aPending.shape.release() );
6182 return;
6183 }
6184
6185 if( aExtensionPads.count( aPad ) )
6186 {
6187 toPadFrame( aPending.shape.get(), aPad, layer );
6188 aPad->AddPrimitive( PADSTACK::ALL_LAYERS, aPending.shape.release() );
6189 aPad->SetDirty();
6190 return;
6191 }
6192
6193 // Growing the touched pad would open its mask over the copper, so a same-numbered
6194 // copper-only pad joins it instead
6195 SHAPE_POLY_SET outline;
6196 aPending.shape->TransformShapeToPolygon( outline, layer, 0, aMaxError, ERROR_INSIDE );
6197
6198 std::unique_ptr<PAD> extension = std::make_unique<PAD>( aPending.footprint );
6199
6200 VECTOR2I anchorPos = outline.Outline( 0 ).CPoint( 0 );
6201
6202 extension->SetAttribute( PAD_ATTRIB::SMD );
6203 extension->SetPadstackMode( PADSTACK::MODE::NORMAL );
6204 extension->SetShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::CUSTOM );
6205 extension->SetAnchorPadShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::CIRCLE );
6206 extension->SetSize( PADSTACK::ALL_LAYERS, { 1, 1 } );
6207 extension->SetPosition( anchorPos );
6208 extension->SetOrientation( ANGLE_0 );
6209 extension->SetThermalSpokeAngle( ANGLE_90 );
6210 extension->SetLayerSet( LSET{ layer } );
6211 extension->SetNumber( aPad->GetNumber() );
6212 extension->SetNetCode( aPad->GetNetCode() );
6213
6214 // Zones met the original copper solidly, not through thermal spokes
6215 extension->SetLocalZoneConnection( ZONE_CONNECTION::FULL );
6216
6217 toPadFrame( aPending.shape.get(), extension.get(), layer );
6218 extension->AddPrimitive( PADSTACK::ALL_LAYERS, aPending.shape.release() );
6219
6220 aExtensionPads.insert( extension.get() );
6221 aPending.footprint->Add( extension.release(), ADD_MODE::APPEND );
6222 };
6223
6224 bool changed = false;
6225
6227 {
6228 // Copper outside the stackup is a multi-layer copy that the stackup step discards
6229 if( !pending.shape || !aCopper.test( pending.layer ) )
6230 continue;
6231
6232 std::shared_ptr<SHAPE> shape = pending.shape->GetEffectiveShape( pending.layer );
6233
6234 if( PAD* pad = altiumFindTouchingPad( pending.footprint, pending.layer, pending.netcode, *shape ) )
6235 {
6236 fold( pending, pad );
6237 changed = true;
6238 }
6239 }
6240
6241 return changed;
6242}
6243
6244
6246{
6248 {
6249 if( pending.shape )
6250 pending.fallback();
6251 }
6252
6254}
6255
6256
6258{
6259 const int maxError = m_board->GetDesignSettings().m_MaxError;
6260 const LSET boardCopper = m_board->GetEnabledLayers() & LSET::AllCuMask();
6261
6262 // Copper-only pads made here to carry a pad's number on an outer layer; they own no mask or
6263 // paste, so further copper can join them without widening an aperture
6264 std::set<PAD*> extensionPads;
6265
6266 // Mirrors the land ConvertVias6ToBoardItem would give the via on each layer
6267 auto viaDiameterOn =
6268 [&]( const AVIA6& aVia, PCB_LAYER_ID aLayer ) -> uint32_t
6269 {
6270 switch( aVia.viamode )
6271 {
6273 if( aLayer == F_Cu )
6275
6276 if( aLayer == B_Cu )
6278
6280
6282 {
6283 int idx = HelperGetPadstackLayerIndex( aLayer );
6284 return idx < 0 ? aVia.diameter : aVia.diameter_by_layer[idx];
6285 }
6286
6287 default:
6288 return aVia.diameter;
6289 }
6290 };
6291
6292 // Repeat so copper reaching a pad only through other folded copper still joins it
6293 for( bool changed = true; changed; )
6294 {
6295 changed = HelperFoldPendingCopper( boardCopper, maxError, extensionPads );
6296
6297 for( auto it = m_pendingComponentVias.begin(); it != m_pendingComponentVias.end(); )
6298 {
6299 auto& [footprint, via] = *it;
6300 PAD* touched = nullptr;
6301
6302 for( PCB_LAYER_ID layer : boardCopper )
6303 {
6304 SHAPE_CIRCLE land( via.position, static_cast<int>( viaDiameterOn( via, layer ) / 2 ) );
6305
6306 touched = altiumFindTouchingPad( footprint, layer, GetNetCode( via.net ), land );
6307
6308 if( touched )
6309 break;
6310 }
6311
6312 if( !touched )
6313 {
6314 ++it;
6315 continue;
6316 }
6317
6318 PAD* viaPad = ConvertVias6ToFootprintItem( footprint, via );
6319 viaPad->SetNumber( touched->GetNumber() );
6320
6321 // A via meets zones solidly, which a pad only does when told to
6323
6324 it = m_pendingComponentVias.erase( it );
6325 changed = true;
6326 }
6327
6328 // Altium region pads arrive unnumbered and would lose their net on the next netlist update
6329 for( FOOTPRINT* footprint : m_components )
6330 {
6331 if( !footprint )
6332 continue;
6333
6334 for( PAD* pad : footprint->Pads() )
6335 {
6336 if( !pad->GetNumber().IsEmpty() || pad->GetNetCode() <= 0 )
6337 continue;
6338
6339 const LSET padCopper = pad->GetLayerSet() & LSET::AllCuMask();
6340
6341 for( PCB_LAYER_ID layer : padCopper )
6342 {
6343 PAD* touched = altiumFindTouchingPad( footprint, layer, pad->GetNetCode(),
6344 *pad->GetEffectiveShape( layer ), pad );
6345
6346 if( touched )
6347 {
6348 pad->SetNumber( touched->GetNumber() );
6349 changed = true;
6350 break;
6351 }
6352 }
6353 }
6354 }
6355 }
6356
6358
6359 for( const auto& [footprint, via] : m_pendingComponentVias )
6361
6362 m_pendingComponentVias.clear();
6363}
6364
6365
6367{
6368 // Altium has no per-component mounting style to copy, so derive it from the pads the way
6369 // KiCad's own footprint checker does. Using the same heuristic keeps the imported value in
6370 // agreement with FOOTPRINT_UTILS::CheckFootprintAttributes(), which would otherwise report every
6371 // footprint we just wrote as a type mismatch.
6372 //
6373 // Only m_components is walked, so importing into a board that already holds footprints
6374 // cannot rewrite them, and only a missing style is filled in.
6375 for( FOOTPRINT* footprint : m_components )
6376 {
6377 if( !footprint )
6378 continue;
6379
6380 if( footprint->GetAttributes() & ( FP_SMD | FP_THROUGH_HOLE ) )
6381 continue;
6382
6383 footprint->SetAttributes( footprint->GetAttributes() | footprint->GetLikelyAttribute() );
6384 }
6385}
6386
6387
6389 const CFB::COMPOUND_FILE_ENTRY* aEntry )
6390{
6391 if( m_progressReporter )
6392 m_progressReporter->Report( _( "Loading text..." ) );
6393
6394 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
6395
6396 while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
6397 {
6398 checkpoint();
6399 ATEXT6 elem( reader, m_unicodeStrings );
6400
6401 if( elem.component == ALTIUM_COMPONENT_NONE )
6402 {
6404 }
6405 else
6406 {
6407 FOOTPRINT* footprint = HelperGetFootprint( elem.component );
6408 ConvertTexts6ToFootprintItem( footprint, elem );
6409 }
6410 }
6411
6412 if( reader.GetRemainingBytes() != 0 )
6413 THROW_IO_ERROR( wxT( "Texts6 stream is not fully parsed" ) );
6414}
6415
6416
6418{
6419 if( aElem.fonttype == ALTIUM_TEXT_TYPE::BARCODE )
6420 {
6421 for( PCB_LAYER_ID klayer : GetKicadLayersToIterate( aElem.layer ) )
6422 ConvertBarcodes6ToBoardItemOnLayer( aElem, klayer );
6423
6424 return;
6425 }
6426
6427 for( PCB_LAYER_ID klayer : GetKicadLayersToIterate( aElem.layer ) )
6428 ConvertTexts6ToBoardItemOnLayer( aElem, klayer );
6429}
6430
6431
6433{
6434 if( aElem.fonttype == ALTIUM_TEXT_TYPE::BARCODE )
6435 {
6436 for( PCB_LAYER_ID klayer : GetKicadLayersToIterate( aElem.layer ) )
6437 ConvertBarcodes6ToFootprintItemOnLayer( aFootprint, aElem, klayer );
6438 return;
6439 }
6440
6441 for( PCB_LAYER_ID klayer : GetKicadLayersToIterate( aElem.layer ) )
6442 ConvertTexts6ToFootprintItemOnLayer( aFootprint, aElem, klayer );
6443}
6444
6445
6447{
6448 std::unique_ptr<PCB_TEXTBOX> pcbTextbox = std::make_unique<PCB_TEXTBOX>( m_board );
6449 std::unique_ptr<PCB_TEXT> pcbText = std::make_unique<PCB_TEXT>( m_board );
6450
6451 bool isTextbox = aElem.isFrame && !aElem.isInverted; // Textbox knockout is not supported
6452
6453 static const std::map<wxString, wxString> variableMap = {
6454 { "LAYER_NAME", "LAYER" },
6455 { "PRINT_DATE", "CURRENT_DATE" },
6456 { "PRINT_TIME", "CURRENT_TIME_LOCALE" },
6457 { "PCB_FILE_NAME_NO_PATH", "FILENAME" },
6458 };
6459
6460 wxString kicadText = AltiumPcbSpecialStringsToKiCadStrings( aElem.text, variableMap, &m_specialStrings );
6461 BOARD_ITEM* item = pcbText.get();
6462 EDA_TEXT* text = pcbText.get();
6463
6464 if( isTextbox )
6465 {
6466 item = pcbTextbox.get();
6467 text = pcbTextbox.get();
6468 }
6469
6470 text->SetText( kicadText );
6471
6472 // Set the layer before the alignment helpers run. HelperSetTextAlignmentAndPos measures the
6473 // text via GetTextBox(), which resolves layer-dependent special strings such as ${LAYER}.
6474 item->SetLayer( aLayer );
6475
6477
6478 if( isTextbox )
6479 HelperSetTextboxAlignmentAndPos( aElem, pcbTextbox.get() );
6480 else
6482
6483 item->SetIsKnockout( aElem.isInverted );
6484
6485 if( isTextbox )
6486 m_board->Add( pcbTextbox.release(), ADD_MODE::APPEND );
6487 else
6488 m_board->Add( pcbText.release(), ADD_MODE::APPEND );
6489}
6490
6491
6493 PCB_LAYER_ID aLayer )
6494{
6495 std::unique_ptr<PCB_TEXTBOX> fpTextbox = std::make_unique<PCB_TEXTBOX>( aFootprint );
6496 std::unique_ptr<PCB_TEXT> fpText = std::make_unique<PCB_TEXT>( aFootprint );
6497
6498 BOARD_ITEM* item = fpText.get();
6499 EDA_TEXT* text = fpText.get();
6500
6501 // A framed designator or comment still sets its field, which cannot be a textbox
6502 bool isField = aElem.isDesignator || aElem.isComment;
6503 bool isTextbox = aElem.isFrame && !aElem.isInverted && !isField; // Textbox knockout is not supported
6504 bool toAdd = false;
6505
6506 if( aElem.isDesignator )
6507 {
6508 item = &aFootprint->Reference(); // TODO: handle multiple layers
6509 text = &aFootprint->Reference();
6510 }
6511 else if( aElem.isComment )
6512 {
6513 item = &aFootprint->Value(); // TODO: handle multiple layers
6514 text = &aFootprint->Value();
6515 }
6516 else
6517 {
6518 item = fpText.get();
6519 text = fpText.get();
6520 toAdd = true;
6521 }
6522
6523 static const std::map<wxString, wxString> variableMap = {
6524 { "DESIGNATOR", "REFERENCE" },
6525 { "COMMENT", "VALUE" },
6526 { "VALUE", "ALTIUM_VALUE" },
6527 { "LAYER_NAME", "LAYER" },
6528 { "PRINT_DATE", "CURRENT_DATE" },
6529 { "PRINT_TIME", "CURRENT_TIME_LOCALE" },
6530 { "PCB_FILE_NAME_NO_PATH", "FILENAME" },
6531 };
6532
6533 if( isTextbox )
6534 {
6535 item = fpTextbox.get();
6536 text = fpTextbox.get();
6537 }
6538
6539 wxString kicadText = AltiumPcbSpecialStringsToKiCadStrings( aElem.text, variableMap, &m_specialStrings );
6540
6541 text->SetText( kicadText );
6542
6543 // Set the layer before the alignment helpers run. HelperSetTextAlignmentAndPos measures the
6544 // text via GetTextBox(), which resolves layer-dependent special strings such as ${LAYER}.
6545 item->SetLayer( aLayer );
6546
6548
6549 if( isTextbox )
6550 HelperSetTextboxAlignmentAndPos( aElem, fpTextbox.get() );
6551 else
6553
6554 text->SetKeepUpright( false );
6555 item->SetIsKnockout( aElem.isInverted );
6556
6557 if( toAdd )
6558 {
6559 if( isTextbox )
6560 aFootprint->Add( fpTextbox.release(), ADD_MODE::APPEND );
6561 else
6562 aFootprint->Add( fpText.release(), ADD_MODE::APPEND );
6563 }
6564}
6565
6566
6568{
6569 std::unique_ptr<PCB_BARCODE> pcbBarcode = std::make_unique<PCB_BARCODE>( m_board );
6570
6571 pcbBarcode->SetLayer( aLayer );
6572 pcbBarcode->SetPosition( aElem.position );
6573 pcbBarcode->SetWidth( aElem.textbox_rect_width );
6574 pcbBarcode->SetHeight( aElem.textbox_rect_height );
6575 pcbBarcode->SetMargin( aElem.barcode_margin );
6576 pcbBarcode->SetText( aElem.text );
6577
6578 switch( aElem.barcode_type )
6579 {
6580 case ALTIUM_BARCODE_TYPE::CODE39: pcbBarcode->SetKind( BARCODE_T::CODE_39 ); break;
6581 case ALTIUM_BARCODE_TYPE::CODE128: pcbBarcode->SetKind( BARCODE_T::CODE_128 ); break;
6582 default: pcbBarcode->SetKind( BARCODE_T::CODE_39 ); break;
6583 }
6584
6585 pcbBarcode->SetIsKnockout( aElem.barcode_inverted );
6586 pcbBarcode->AssembleBarcode();
6587
6588 m_board->Add( pcbBarcode.release(), ADD_MODE::APPEND );
6589}
6590
6591
6593 PCB_LAYER_ID aLayer )
6594{
6595 std::unique_ptr<PCB_BARCODE> fpBarcode = std::make_unique<PCB_BARCODE>( aFootprint );
6596
6597 fpBarcode->SetLayer( aLayer );
6598 fpBarcode->SetPosition( aElem.position );
6599 fpBarcode->SetWidth( aElem.textbox_rect_width );
6600 fpBarcode->SetHeight( aElem.textbox_rect_height );
6601 fpBarcode->SetMargin( aElem.barcode_margin );
6602 fpBarcode->SetText( aElem.text );
6603
6604 switch( aElem.barcode_type )
6605 {
6606 case ALTIUM_BARCODE_TYPE::CODE39: fpBarcode->SetKind( BARCODE_T::CODE_39 ); break;
6607 case ALTIUM_BARCODE_TYPE::CODE128: fpBarcode->SetKind( BARCODE_T::CODE_128 ); break;
6608 default: fpBarcode->SetKind( BARCODE_T::CODE_39 ); break;
6609 }
6610
6611 fpBarcode->SetIsKnockout( aElem.barcode_inverted );
6612 fpBarcode->AssembleBarcode();
6613
6614 aFootprint->Add( fpBarcode.release(), ADD_MODE::APPEND );
6615}
6616
6617
6619{
6620 int margin = aElem.isOffsetBorder ? aElem.text_offset_width : aElem.margin_border_width;
6621
6622 // Altium textboxes do not have borders
6623 aTextbox->SetBorderEnabled( false );
6624
6625 // Calculate position
6626 VECTOR2I kposition = aElem.position;
6627
6628 if( aElem.isMirrored )
6629 kposition.x -= aElem.textbox_rect_width;
6630
6631 kposition.y -= aElem.textbox_rect_height;
6632
6633#if 0
6634 // Compensate for KiCad's textbox margin
6635 int charWidth = aTextbox->GetTextWidth();
6636 int charHeight = aTextbox->GetTextHeight();
6637
6638 VECTOR2I kicadMargin;
6639
6640 if( !aTextbox->GetFont() || aTextbox->GetFont()->IsStroke() )
6641 kicadMargin = VECTOR2I( charWidth * 0.933, charHeight * 0.67 );
6642 else
6643 kicadMargin = VECTOR2I( charWidth * 0.808, charHeight * 0.844 );
6644
6645 aTextbox->SetEnd( VECTOR2I( aElem.textbox_rect_width, aElem.textbox_rect_height )
6646 + kicadMargin * 2 - margin * 2 );
6647
6648 kposition = kposition - kicadMargin + margin;
6649#else
6650 aTextbox->SetMarginBottom( margin );
6651 aTextbox->SetMarginLeft( margin );
6652 aTextbox->SetMarginRight( margin );
6653 aTextbox->SetMarginTop( margin );
6654
6655 aTextbox->SetEnd( VECTOR2I( aElem.textbox_rect_width, aElem.textbox_rect_height ) );
6656#endif
6657
6658 RotatePoint( kposition, aElem.position, EDA_ANGLE( aElem.rotation, DEGREES_T ) );
6659
6660 aTextbox->SetPosition( kposition );
6661
6664
6665 switch( justification )
6666 {
6672 break;
6678 break;
6684 break;
6685 default:
6686 if( m_reporter )
6687 {
6688 wxString msg;
6689 msg.Printf( _( "Unknown textbox justification %d, aText %s" ), justification,
6690 aElem.text );
6691 m_reporter->Report( msg, RPT_SEVERITY_DEBUG );
6692 }
6693
6696 break;
6697 }
6698
6699 aTextbox->SetTextAngle( EDA_ANGLE( aElem.rotation, DEGREES_T ) );
6700}
6701
6702
6704{
6705 VECTOR2I kposition = aElem.position;
6706
6707 int margin = aElem.isOffsetBorder ? aElem.text_offset_width : aElem.margin_border_width;
6708 int rectWidth = aElem.textbox_rect_width - margin * 2;
6709 int rectHeight = aElem.height;
6710
6711 // A frame justifies its text inside the margins of its own rectangle, not the glyph run
6712 if( aElem.isFrame )
6713 {
6714 kposition.x += aElem.isMirrored ? -margin : margin;
6715 kposition.y -= margin;
6716 rectHeight = static_cast<int>( aElem.textbox_rect_height ) - margin * 2;
6717 }
6718
6719 // Altium auto-sizes the bounding box of a free string (non-frame text) from its own glyph
6720 // rasterizer, and stores a slightly different width for otherwise identical strings placed on
6721 // different layers (e.g. the copper and soldermask copies of the same label, which Altium may
6722 // also give different stroke widths). Anchoring the KiCad text to that per-record width drives
6723 // the two copies apart. Center the text on its bare glyph run instead, measured from the
6724 // already-populated EDA_TEXT with the pen inflation removed, so copies that share a glyph run
6725 // stay coincident regardless of stroke width.
6726 if( !aElem.isFrame )
6727 {
6728 rectWidth = aText->GetTextBox( nullptr ).GetWidth();
6729
6730 if( KIFONT::FONT* font = aText->GetFont(); !font || font->IsStroke() )
6731 rectWidth -= 3 * aText->GetEffectiveTextPenWidth();
6732 }
6733
6734 if( aElem.isMirrored )
6735 rectWidth = -rectWidth;
6736
6739
6740 switch( justification )
6741 {
6745
6746 kposition.y -= rectHeight;
6747 break;
6751
6752 kposition.y -= rectHeight / 2;
6753 break;
6757 break;
6761
6762 kposition.x += rectWidth / 2;
6763 kposition.y -= rectHeight;
6764 break;
6768
6769 kposition.x += rectWidth / 2;
6770 kposition.y -= rectHeight / 2;
6771 break;
6775
6776 kposition.x += rectWidth / 2;
6777 break;
6781
6782 kposition.x += rectWidth;
6783 kposition.y -= rectHeight;
6784 break;
6788
6789 kposition.x += rectWidth;
6790 kposition.y -= rectHeight / 2;
6791 break;
6795
6796 kposition.x += rectWidth;
6797 break;
6798 default:
6801 break;
6802 }
6803
6804 int charWidth = aText->GetTextWidth();
6805 int charHeight = aText->GetTextHeight();
6806
6807 // Correct for KiCad's baseline offset.
6808 // Text height and font must be set correctly before calling.
6809 if( !aText->GetFont() || aText->GetFont()->IsStroke() )
6810 {
6811 switch( aText->GetVertJustify() )
6812 {
6813 case GR_TEXT_V_ALIGN_TOP: kposition.y -= charHeight * 0.0407; break;
6814 case GR_TEXT_V_ALIGN_CENTER: kposition.y += charHeight * 0.0355; break;
6815 case GR_TEXT_V_ALIGN_BOTTOM: kposition.y += charHeight * 0.1225; break;
6816 default: break;
6817 }
6818 }
6819 else
6820 {
6821 switch( aText->GetVertJustify() )
6822 {
6823 case GR_TEXT_V_ALIGN_TOP: kposition.y -= charWidth * 0.016; break;
6824 case GR_TEXT_V_ALIGN_CENTER: kposition.y += charWidth * 0.085; break;
6825 case GR_TEXT_V_ALIGN_BOTTOM: kposition.y += charWidth * 0.17; break;
6826 default: break;
6827 }
6828 }
6829
6830 RotatePoint( kposition, aElem.position, EDA_ANGLE( aElem.rotation, DEGREES_T ) );
6831
6832 aText->SetTextPos( kposition );
6833 aText->SetTextAngle( EDA_ANGLE( aElem.rotation, DEGREES_T ) );
6834}
6835
6836
6838{
6839 aEdaText.SetTextSize( VECTOR2I( aElem.height, aElem.height ) );
6840
6842 {
6843 KIFONT::FONT* font = KIFONT::FONT::GetFont( aElem.fontname, aElem.isBold, aElem.isItalic );
6844 aEdaText.SetFont( font );
6845
6846 if( font->IsOutline() )
6847 {
6848 // TODO: why is this required? Somehow, truetype size is calculated differently
6849 if( font->GetName().Contains( wxS( "Arial" ) ) )
6850 aEdaText.SetTextSize( VECTOR2I( aElem.height * 0.63, aElem.height * 0.63 ) );
6851 else
6852 aEdaText.SetTextSize( VECTOR2I( aElem.height * 0.5, aElem.height * 0.5 ) );
6853 }
6854 }
6855
6856 aEdaText.SetTextThickness( aElem.strokewidth );
6857 aEdaText.SetBoldFlag( aElem.isBold );
6858
6859 // The imported width is already bolded; store the base so the Bold flag doesn't double it.
6861
6862 aEdaText.SetItalic( aElem.isItalic );
6863 aEdaText.SetMirrored( aElem.isMirrored );
6864}
6865
6866
6868 const CFB::COMPOUND_FILE_ENTRY* aEntry )
6869{
6870 if( m_progressReporter )
6871 m_progressReporter->Report( _( "Loading rectangles..." ) );
6872
6873 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
6874
6875 while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
6876 {
6877 checkpoint();
6878 AFILL6 elem( reader );
6879
6880 if( elem.component == ALTIUM_COMPONENT_NONE )
6881 {
6883 }
6884 else
6885 {
6886 FOOTPRINT* footprint = HelperGetFootprint( elem.component );
6887 ConvertFills6ToFootprintItem( footprint, elem, true );
6888 }
6889 }
6890
6891 if( reader.GetRemainingBytes() != 0 )
6892 THROW_IO_ERROR( wxT( "Fills6 stream is not fully parsed" ) );
6893}
6894
6895
6897{
6898 if( aElem.is_keepout || aElem.layer == ALTIUM_LAYER::KEEP_OUT_LAYER )
6899 {
6900 // This is not the actual board item. We can use it to create the polygon for the region
6901 PCB_SHAPE shape( nullptr, SHAPE_T::RECTANGLE );
6902
6903 shape.SetStart( aElem.pos1 );
6904 shape.SetEnd( aElem.pos2 );
6905 shape.SetFilled( true );
6907
6908 if( aElem.rotation != 0. )
6909 {
6910 VECTOR2I center( aElem.pos1.x / 2 + aElem.pos2.x / 2,
6911 aElem.pos1.y / 2 + aElem.pos2.y / 2 );
6912 shape.Rotate( center, EDA_ANGLE( aElem.rotation, DEGREES_T ) );
6913 }
6914
6916 }
6917 else
6918 {
6919 for( PCB_LAYER_ID klayer : GetKicadLayersToIterate( aElem.layer ) )
6920 ConvertFills6ToBoardItemOnLayer( aElem, klayer );
6921 }
6922}
6923
6924
6926 const bool aIsBoardImport )
6927{
6928 if( aElem.is_keepout
6929 || aElem.layer == ALTIUM_LAYER::KEEP_OUT_LAYER ) // TODO: what about plane layers?
6930 {
6931 // This is not the actual board item. We can use it to create the polygon for the region
6932 PCB_SHAPE shape( nullptr, SHAPE_T::RECTANGLE );
6933
6934 shape.SetStart( aElem.pos1 );
6935 shape.SetEnd( aElem.pos2 );
6936 shape.SetFilled( true );
6938
6939 if( aElem.rotation != 0. )
6940 {
6941 VECTOR2I center( aElem.pos1.x / 2 + aElem.pos2.x / 2,
6942 aElem.pos1.y / 2 + aElem.pos2.y / 2 );
6943 shape.Rotate( center, EDA_ANGLE( aElem.rotation, DEGREES_T ) );
6944 }
6945
6946 HelperPcpShapeAsFootprintKeepoutRegion( aFootprint, shape, aElem.layer,
6947 aElem.keepoutrestrictions );
6948 }
6949 else if( IsAltiumLayerCopper( aElem.layer ) && ( !aIsBoardImport || aElem.net != ALTIUM_NET_UNCONNECTED ) )
6950 {
6951 // Footprint graphics are not connected, so copper waits to join a pad it touches
6952 for( PCB_LAYER_ID klayer : GetKicadLayersToIterate( aElem.layer ) )
6953 {
6954 // An outer library fill already becomes a pad
6955 if( !aIsBoardImport && !IsInnerCopperLayer( klayer ) )
6956 {
6957 ConvertFills6ToFootprintItemOnLayer( aFootprint, aElem, klayer );
6958 continue;
6959 }
6960
6961 std::unique_ptr<PCB_SHAPE> fill = std::make_unique<PCB_SHAPE>( nullptr, SHAPE_T::RECTANGLE );
6962
6963 ConvertFills6ToPcbShape( aElem, fill.get() );
6964
6965 HelperDeferComponentCopper( aFootprint, klayer, aIsBoardImport ? aElem.net : ALTIUM_NET_UNCONNECTED,
6966 std::move( fill ),
6967 [this, aFootprint, aElem, klayer, aIsBoardImport]()
6968 {
6969 if( aIsBoardImport )
6970 ConvertFills6ToBoardItemOnLayer( aElem, klayer );
6971 else
6972 ConvertFills6ToFootprintItemOnLayer( aFootprint, aElem, klayer );
6973 } );
6974 }
6975 }
6976 else
6977 {
6978 for( PCB_LAYER_ID klayer : GetKicadLayersToIterate( aElem.layer ) )
6979 ConvertFills6ToFootprintItemOnLayer( aFootprint, aElem, klayer );
6980 }
6981}
6982
6983
6985{
6986 aShape->SetShape( SHAPE_T::RECTANGLE );
6987 aShape->SetFilled( true );
6988 aShape->SetStroke( STROKE_PARAMS( 0 ) );
6989
6990 aShape->SetStart( aElem.pos1 );
6991 aShape->SetEnd( aElem.pos2 );
6992
6993 if( aElem.rotation != 0. )
6994 {
6995 // TODO: Do we need SHAPE_T::POLY for non 90° rotations?
6996 VECTOR2I center( aElem.pos1.x / 2 + aElem.pos2.x / 2,
6997 aElem.pos1.y / 2 + aElem.pos2.y / 2 );
6998 aShape->Rotate( center, EDA_ANGLE( aElem.rotation, DEGREES_T ) );
6999 }
7000}
7001
7002
7004{
7005 std::unique_ptr<PCB_SHAPE> fill = std::make_unique<PCB_SHAPE>( m_board, SHAPE_T::RECTANGLE );
7006
7007 ConvertFills6ToPcbShape( aElem, fill.get() );
7008 fill->SetLayer( aLayer );
7009
7010 if( IsCopperLayer( aLayer ) && aElem.net != ALTIUM_NET_UNCONNECTED )
7011 {
7012 fill->SetNetCode( GetNetCode( aElem.net ) );
7013 }
7014
7015 m_board->Add( fill.release(), ADD_MODE::APPEND );
7016}
7017
7018
7020 PCB_LAYER_ID aLayer )
7021{
7022 if( aLayer == F_Cu || aLayer == B_Cu )
7023 {
7024 std::unique_ptr<PAD> pad = std::make_unique<PAD>( aFootprint );
7025
7026 LSET padLayers;
7027 padLayers.set( aLayer );
7028
7029 pad->SetAttribute( PAD_ATTRIB::SMD );
7030 EDA_ANGLE rotation( aElem.rotation, DEGREES_T );
7031
7032 // Handle rotation multiples of 90 degrees
7033 if( rotation.IsCardinal() )
7034 {
7035 pad->SetPadstackMode( PADSTACK::MODE::NORMAL );
7037
7038 int width = std::abs( aElem.pos2.x - aElem.pos1.x );
7039 int height = std::abs( aElem.pos2.y - aElem.pos1.y );
7040
7041 // Swap width and height for 90 or 270 degree rotations
7042 if( rotation.IsCardinal90() )
7043 std::swap( width, height );
7044
7045 pad->SetSize( PADSTACK::ALL_LAYERS, { width, height } );
7046 pad->SetPosition( aElem.pos1 / 2 + aElem.pos2 / 2 );
7047 }
7048 else
7049 {
7050 pad->SetPadstackMode( PADSTACK::MODE::NORMAL );
7052
7053 int anchorSize = std::min( std::abs( aElem.pos2.x - aElem.pos1.x ),
7054 std::abs( aElem.pos2.y - aElem.pos1.y ) );
7055 VECTOR2I anchorPos = aElem.pos1;
7056
7057 pad->SetAnchorPadShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::CIRCLE );
7058 pad->SetSize( PADSTACK::ALL_LAYERS, { anchorSize, anchorSize } );
7059 pad->SetPosition( anchorPos );
7060
7061 SHAPE_POLY_SET shapePolys;
7062 shapePolys.NewOutline();
7063 shapePolys.Append( aElem.pos1.x - anchorPos.x, aElem.pos1.y - anchorPos.y );
7064 shapePolys.Append( aElem.pos2.x - anchorPos.x, aElem.pos1.y - anchorPos.y );
7065 shapePolys.Append( aElem.pos2.x - anchorPos.x, aElem.pos2.y - anchorPos.y );
7066 shapePolys.Append( aElem.pos1.x - anchorPos.x, aElem.pos2.y - anchorPos.y );
7067 shapePolys.Outline( 0 ).SetClosed( true );
7068
7069 VECTOR2I center( aElem.pos1.x / 2 + aElem.pos2.x / 2 - anchorPos.x,
7070 aElem.pos1.y / 2 + aElem.pos2.y / 2 - anchorPos.y );
7071 shapePolys.Rotate( EDA_ANGLE( aElem.rotation, DEGREES_T ), center );
7072 pad->AddPrimitivePoly( F_Cu, shapePolys, 0, true );
7073 }
7074
7075 pad->SetThermalSpokeAngle( ANGLE_90 );
7076 pad->SetLayerSet( padLayers );
7077
7078 aFootprint->Add( pad.release(), ADD_MODE::APPEND );
7079 }
7080 else
7081 {
7082 std::unique_ptr<PCB_SHAPE> fill = std::make_unique<PCB_SHAPE>( aFootprint, SHAPE_T::RECTANGLE );
7083
7084 fill->SetFilled( true );
7085 fill->SetLayer( aLayer );
7086 fill->SetStroke( STROKE_PARAMS( 0 ) );
7087
7088 fill->SetStart( aElem.pos1 );
7089 fill->SetEnd( aElem.pos2 );
7090
7091 if( aElem.rotation != 0. )
7092 {
7093 VECTOR2I center( aElem.pos1.x / 2 + aElem.pos2.x / 2,
7094 aElem.pos1.y / 2 + aElem.pos2.y / 2 );
7095 fill->Rotate( center, EDA_ANGLE( aElem.rotation, DEGREES_T ) );
7096 }
7097
7098 aFootprint->Add( fill.release(), ADD_MODE::APPEND );
7099 }
7100}
7101
7102
7103void ALTIUM_PCB::HelperSetZoneLayers( ZONE& aZone, const ALTIUM_LAYER aAltiumLayer )
7104{
7105 LSET layerSet;
7106
7107 for( PCB_LAYER_ID klayer : GetKicadLayersToIterate( aAltiumLayer ) )
7108 layerSet.set( klayer );
7109
7110 aZone.SetLayerSet( layerSet );
7111}
7112
7113
7114void ALTIUM_PCB::HelperSetZoneKeepoutRestrictions( ZONE& aZone, const uint8_t aKeepoutRestrictions )
7115{
7116 bool keepoutRestrictionVia = ( aKeepoutRestrictions & ALTIUM_KEEPOUT_VIA ) != 0;
7117 bool keepoutRestrictionTrack = ( aKeepoutRestrictions & ALTIUM_KEEPOUT_TRACK ) != 0;
7118 bool keepoutRestrictionCopper = ( aKeepoutRestrictions & ALTIUM_KEEPOUT_COPPER ) != 0;
7119 bool keepoutRestrictionSMDPad = ( aKeepoutRestrictions & ALTIUM_KEEPOUT_SMD_PAD ) != 0;
7120 bool keepoutRestrictionTHPad = ( aKeepoutRestrictions & ALTIUM_KEEPOUT_TH_PAD ) != 0;
7121
7122 aZone.SetDoNotAllowVias( keepoutRestrictionVia );
7123 aZone.SetDoNotAllowTracks( keepoutRestrictionTrack );
7124 aZone.SetDoNotAllowZoneFills( keepoutRestrictionCopper );
7125 aZone.SetDoNotAllowPads( keepoutRestrictionSMDPad && keepoutRestrictionTHPad );
7126 aZone.SetDoNotAllowFootprints( false );
7127}
7128
7129
7130uint8_t ALTIUM_PCB::HelperGetKeepoutRestrictions( const uint8_t aKeepoutRestrictions, const ALTIUM_LAYER aAltiumLayer )
7131{
7132 // An internal plane is negative, so every primitive drawn on one cuts copper out of it
7133 // whatever else the mask says
7134 if( IsAltiumLayerAPlane( aAltiumLayer ) )
7135 return static_cast<uint8_t>( aKeepoutRestrictions | ALTIUM_KEEPOUT_COPPER );
7136
7137 if( aKeepoutRestrictions != 0 )
7138 return aKeepoutRestrictions;
7139
7140 // Altium leaves the mask empty on the Keep-Out layer because the layer already means
7141 // "keep everything out"
7142 if( aAltiumLayer == ALTIUM_LAYER::KEEP_OUT_LAYER )
7143 return ALTIUM_KEEPOUT_ALL;
7144
7145 if( m_reporter )
7146 {
7147 m_reporter->Report( _( "Ignored a keep-out area with no restrictions." ), RPT_SEVERITY_INFO );
7148 }
7149
7150 return 0;
7151}
7152
7153
7155{
7156 // A footprint zone stores its outline in the footprint's local frame and derives its board
7157 // position by applying the footprint transform. The importer builds the outline in board
7158 // coordinates, so it must be re-based here or the zone drifts by the footprint offset when the
7159 // board is re-centered at the end of the import.
7160 const TRANSFORM_TRS& xform = aFootprint.GetTransform();
7161 SHAPE_POLY_SET& poly = *aZone.Outline();
7162
7163 for( auto it = poly.IterateWithHoles(); it; it++ )
7164 poly.SetVertex( it.GetIndex(), xform.InverseApply( *it ) );
7165}
7166
7167
7169 const ALTIUM_LAYER aAltiumLayer,
7170 const uint8_t aKeepoutRestrictions )
7171{
7172 uint8_t restrictions = HelperGetKeepoutRestrictions( aKeepoutRestrictions, aAltiumLayer );
7173
7174 if( restrictions == 0 )
7175 return;
7176
7177 std::unique_ptr<ZONE> zone = std::make_unique<ZONE>( m_board );
7178
7179 zone->SetIsRuleArea( true );
7180
7181 HelperSetZoneLayers( *zone, aAltiumLayer );
7182 HelperSetZoneKeepoutRestrictions( *zone, restrictions );
7183
7184 aShape.EDA_SHAPE::TransformShapeToPolygon( *zone->Outline(), 0, ARC_HIGH_DEF, ERROR_INSIDE );
7185
7186 zone->SetBorderDisplayStyle( ZONE_BORDER_DISPLAY_STYLE::DIAGONAL_EDGE,
7188
7189 m_board->Add( zone.release(), ADD_MODE::APPEND );
7190}
7191
7192
7194 const PCB_SHAPE& aShape,
7195 const ALTIUM_LAYER aAltiumLayer,
7196 const uint8_t aKeepoutRestrictions )
7197{
7198 uint8_t restrictions = HelperGetKeepoutRestrictions( aKeepoutRestrictions, aAltiumLayer );
7199
7200 if( restrictions == 0 )
7201 return;
7202
7203 std::unique_ptr<ZONE> zone = std::make_unique<ZONE>( aFootprint );
7204
7205 zone->SetIsRuleArea( true );
7206
7207 HelperSetZoneLayers( *zone, aAltiumLayer );
7208 HelperSetZoneKeepoutRestrictions( *zone, restrictions );
7209
7210 aShape.EDA_SHAPE::TransformShapeToPolygon( *zone->Outline(), 0, ARC_HIGH_DEF, ERROR_INSIDE );
7211
7212 HelperFootprintZoneToLibFrame( *zone, *aFootprint );
7213
7214 zone->SetBorderDisplayStyle( ZONE_BORDER_DISPLAY_STYLE::DIAGONAL_EDGE,
7216
7217 aFootprint->Add( zone.release(), ADD_MODE::APPEND );
7218}
7219
7220
7221std::vector<std::pair<PCB_LAYER_ID, int>> ALTIUM_PCB::HelperGetSolderAndPasteMaskExpansions(
7222 const ALTIUM_RECORD aType, const int aPrimitiveIndex, const ALTIUM_LAYER aAltiumLayer )
7223{
7224 if( m_extendedPrimitiveInformationMaps.count( aType ) == 0 )
7225 return {}; // there is nothing to parse
7226
7227 auto elems = m_extendedPrimitiveInformationMaps[aType].equal_range( aPrimitiveIndex );
7228
7229 if( elems.first == elems.second )
7230 return {}; // there is nothing to parse
7231
7232 std::vector<std::pair<PCB_LAYER_ID, int>> layerExpansionPairs;
7233
7234 for( auto it = elems.first; it != elems.second; ++it )
7235 {
7236 const AEXTENDED_PRIMITIVE_INFORMATION& pInf = it->second;
7237
7239 {
7242 {
7243 // TODO: what layers can lead to solder or paste mask usage? E.g. KEEP_OUT_LAYER and other top/bottom layers
7244 if( aAltiumLayer == ALTIUM_LAYER::TOP_LAYER
7245 || aAltiumLayer == ALTIUM_LAYER::MULTI_LAYER )
7246 {
7247 layerExpansionPairs.emplace_back( F_Mask, pInf.soldermaskexpansionmanual );
7248 }
7249
7250 if( aAltiumLayer == ALTIUM_LAYER::BOTTOM_LAYER
7251 || aAltiumLayer == ALTIUM_LAYER::MULTI_LAYER )
7252 {
7253 layerExpansionPairs.emplace_back( B_Mask, pInf.soldermaskexpansionmanual );
7254 }
7255 }
7258 {
7259 if( aAltiumLayer == ALTIUM_LAYER::TOP_LAYER
7260 || aAltiumLayer == ALTIUM_LAYER::MULTI_LAYER )
7261 {
7262 layerExpansionPairs.emplace_back( F_Paste, pInf.pastemaskexpansionmanual );
7263 }
7264
7265 if( aAltiumLayer == ALTIUM_LAYER::BOTTOM_LAYER
7266 || aAltiumLayer == ALTIUM_LAYER::MULTI_LAYER )
7267 {
7268 layerExpansionPairs.emplace_back( B_Paste, pInf.pastemaskexpansionmanual );
7269 }
7270 }
7271 }
7272 }
7273
7274 return layerExpansionPairs;
7275}
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
wxString AltiumPcbSpecialStringsToKiCadStrings(const wxString &aString, const std::map< wxString, wxString > &aOverrides, std::set< wxString > *aUnmapped)
Convert the special strings in Altium PCB text to KiCad variables.
LIB_ID AltiumToKiCadLibID(const wxString &aLibName, const wxString &aLibReference)
constexpr int ALTIUM_PADSTACK_IDX_COUNT
static LSET altiumStackupCopper(const LSET &aDrawnCopper, int aMinCopperLayers)
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.
static void altiumSplitPadstackPerLayer(PAD *aPad, const LSET &aBoardCopper)
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 void altiumSetFootprintStackup(FOOTPRINT *aFootprint, const LSET &aDrawnCopper, int aMinCopperLayers)
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)
static PAD * altiumFindTouchingPad(FOOTPRINT *aFootprint, PCB_LAYER_ID aLayer, int aNetCode, const SHAPE &aShape, const PAD *aExclude=nullptr)
static void MoveOpenCourtyardItems(BOARD &aBoard)
bool IsAltiumLayerCopper(ALTIUM_LAYER aLayer)
static void altiumMakePadLayerCustom(PAD *aPad, PCB_LAYER_ID aLayer, int aMaxError)
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.
static void altiumIsolatePadLayer(PAD *aPad, PCB_LAYER_ID aLayer, const LSET &aBoardCopper)
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:40
@ EXTENDPRIMITIVEINFORMATION
Definition altium_pcb.h:58
std::function< void(const ALTIUM_PCB_COMPOUND_FILE &, const CFB::COMPOUND_FILE_ENTRY *)> PARSE_FUNCTION_POINTER_fp
Definition altium_pcb.h:152
KIID AltiumUniqueIdToKiid(const wxString &aUniqueId)
Derive a stable KIID from an Altium component unique id.
@ ERROR_OUTSIDE
@ 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:243
@ LT_POWER
Definition board.h:246
@ LT_MIXED
Definition board.h:247
@ LT_JUMPER
Definition board.h:248
@ LT_SIGNAL
Definition board.h:245
#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:434
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:395
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:445
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:405
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:442
std::vector< FOOTPRINT * > m_components
Definition altium_pcb.h:393
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 ConvertShapeBasedRegions6ToFootprintCopper(FOOTPRINT *aFootprint, const AREGION6 &aElem, const int aPrimitiveIndex, uint16_t aNet)
Plain copper region of a footprint; inner layers wait to join the pad they touch.
void ParseRegions6Data(const ALTIUM_PCB_COMPOUND_FILE &aAltiumPcbFile, const CFB::COMPOUND_FILE_ENTRY *aEntry)
std::vector< PCB_LAYER_ID > GetKicadLayersToIterate(ALTIUM_LAYER aAltiumLayer) const
int m_outlineJogCount
Definition altium_pcb.h:400
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)
SHAPE_LINE_CHAIN m_boardOutline
Definition altium_pcb.h:398
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:430
std::vector< int > m_altiumToKicadNetcodes
Definition altium_pcb.h:403
std::map< uint32_t, wxString > m_unionNames
Definition altium_pcb.h:415
void HelperFinishBoardOutline()
void ConvertFills6ToPcbShape(const AFILL6 &aElem, PCB_SHAPE *aShape)
unsigned m_totalCount
for progress reporting
Definition altium_pcb.h:439
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)
void HelperDeferComponentCopper(FOOTPRINT *aFootprint, PCB_LAYER_ID aLayer, uint16_t aNet, std::unique_ptr< PCB_SHAPE > aShape, std::function< void()> aFallback)
Hold copper a footprint draws until its pads exist.
std::map< wxString, ALTIUM_EMBEDDED_MODEL_DATA > m_EmbeddedModels
Definition altium_pcb.h:409
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)
std::vector< PENDING_COMPONENT_COPPER > m_pendingComponentCopper
Definition altium_pcb.h:427
std::set< wxString > m_specialStrings
special strings in text that KiCad has no variable for
Definition altium_pcb.h:436
void HelperCreateBoardOutline(const std::vector< ALTIUM_VERTICE > &aVertices)
int m_outlineJogLargest
Definition altium_pcb.h:401
std::map< int, std::vector< BOARD_ITEM * > > m_unionToBoardItems
Definition altium_pcb.h:416
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:410
void ParseRules6Data(const ALTIUM_PCB_COMPOUND_FILE &aAltiumPcbFile, const CFB::COMPOUND_FILE_ENTRY *aEntry)
PAD * ConvertVias6ToFootprintItem(FOOTPRINT *aFootprint, const AVIA6 &aElem)
void MapSchematicNetNames(const std::map< std::string, UTF8 > &aProperties)
void HelperSetZoneKeepoutRestrictions(ZONE &aZone, const uint8_t aKeepoutRestrictions)
unsigned m_doneCount
Definition altium_pcb.h:437
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:402
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)
bool HelperFoldPendingCopper(const LSET &aCopper, int aMaxError, std::set< PAD * > &aExtensionPads)
Join each deferred copper shape on aCopper to the same-net numbered pad it touches.
BOARD * m_board
Definition altium_pcb.h:392
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:435
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:441
std::vector< SHAPE_LINE_CHAIN > m_boardCutouts
Definition altium_pcb.h:399
void ConvertPads6ToFootprintItemOnNonCopper(FOOTPRINT *aFootprint, const APAD6 &aElem)
std::map< wxString, wxString > m_schematicNetNames
Definition altium_pcb.h:404
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:438
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:414
void HelperSetFootprintMountingStyles()
std::vector< ZONE * > m_polygons
Definition altium_pcb.h:394
void ParsePads6Data(const ALTIUM_PCB_COMPOUND_FILE &aAltiumPcbFile, const CFB::COMPOUND_FILE_ENTRY *aEntry)
void HelperFoldComponentCopperIntoPads()
Join deferred component copper and vias to the same-net pads they touch.
void ConvertArcs6ToPcbShape(const AARC6 &aElem, PCB_SHAPE *aShape)
void ConvertTracks6ToBoardItemOnLayer(const ATRACK6 &aElem, PCB_LAYER_ID aLayer)
void ConvertFills6ToBoardItem(const AFILL6 &aElem)
void HelperSetFootprintStackups()
std::map< PCB_LAYER_ID, int > m_padstackLayerIndex
Definition altium_pcb.h:407
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)
void ConvertVias6ToBoardItem(const AVIA6 &aElem)
FOOTPRINT * HelperGetFootprint(uint16_t aComponent) const
void HelperFootprintZoneToLibFrame(ZONE &aZone, const FOOTPRINT &aFootprint)
LAYER_MAPPING_HANDLER m_layerMappingHandler
Definition altium_pcb.h:432
void ConvertShapeBasedRegions6ToBoardItem(const AREGION6 &aElem, const int aPrimitiveIndex)
std::vector< std::pair< FOOTPRINT *, AVIA6 > > m_pendingComponentVias
Definition altium_pcb.h:428
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:412
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:406
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 HelperPlaceUnfoldedCopper()
Run the fallback of every deferred copper shape no pad took.
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()
int GetLineThickness(PCB_LAYER_ID aLayer) const
Return the default graphic segment thickness from the layer class for the given layer.
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition board_item.h:85
virtual void SetIsKnockout(bool aKnockout)
Definition board_item.h:424
virtual void SetLayer(PCB_LAYER_ID aLayer)
Set the layer this item is on.
Definition board_item.h:383
virtual LSET BoardLayerSet() const
Return the LSET for the board that this item resides on.
virtual LSET GetLayerSet() const
Return a std::bitset of all layers on which the item physically resides.
Definition board_item.h:355
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:411
void Add(BOARD_ITEM *aItem, ADD_MODE aMode=ADD_MODE::INSERT, bool aSkipConnectivity=false) override
Removes an item from the container.
Definition board.cpp:1520
NETINFO_ITEM * FindNet(int aNetcode) const
Search for a net with the given netcode.
Definition board.cpp:2797
const FOOTPRINTS & Footprints() const
Definition board.h:465
BOARD_DESIGN_SETTINGS & GetDesignSettings()
Definition board.cpp:1290
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
constexpr bool Intersects(const BOX2< Vec > &aRect) const
Definition box2.h:308
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
int GetRadius() const
SHAPE_T GetShape() const
Definition eda_shape.h:177
virtual void SetFilled(bool aFlag)
Definition eda_shape.h:144
const VECTOR2I & GetEnd() const
Return the ending point of the graphic.
Definition eda_shape.h:333
const VECTOR2I & GetStart() const
Return the starting point of the graphic.
Definition eda_shape.h:283
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...
void SetStackupLayers(LSET aLayers)
If the footprint has a non-default stackup, set the layers that should be used for the stackup.
EDA_ANGLE GetOrientation() const
Definition footprint.h:382
ZONES & Zones()
Definition footprint.h:361
void Remove(BOARD_ITEM *aItem, REMOVE_MODE aMode=REMOVE_MODE::NORMAL) override
Removes an item from the container.
const TRANSFORM_TRS & GetTransform() const
Definition footprint.h:395
void SetStackupMode(FOOTPRINT_STACKUP aMode)
Set the stackup mode for this footprint.
PCB_FIELD & Value()
read/write accessors:
Definition footprint.h:831
std::deque< PAD * > & Pads()
Definition footprint.h:355
bool IsFlipped() const
Definition footprint.h:620
PCB_FIELD & Reference()
Definition footprint.h:832
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:375
const wxString & GetReference() const
Definition footprint.h:793
EMBEDDED_FILES * GetEmbeddedFiles() override
Definition footprint.h:1280
VECTOR2I GetPosition() const override
Definition footprint.h:379
DRAWINGS & GraphicalItems()
Definition footprint.h:358
VECTOR3D m_Offset
3D model offset (mm)
Definition footprint.h:134
double m_Opacity
Definition footprint.h:135
VECTOR3D m_Rotation
3D model rotation (degrees)
Definition footprint.h:133
wxString m_Filename
The 3D shape filename in 3D library.
Definition footprint.h:136
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:144
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:948
void SetMode(MODE aMode)
void SetChamferRatio(double aRatio, PCB_LAYER_ID aLayer)
Definition padstack.cpp:977
void SetOffset(const VECTOR2I &aOffset, PCB_LAYER_ID aLayer)
Definition padstack.cpp:912
void SetShape(PAD_SHAPE aShape, PCB_LAYER_ID aLayer)
Definition padstack.cpp:882
COPPER_LAYER_PROPS & CopperLayer(PCB_LAYER_ID aLayer)
void SetChamferPositions(int aPositions, PCB_LAYER_ID aLayer)
Definition padstack.cpp:995
@ 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
MODE Mode() const
Definition padstack.h:346
void SetSize(const VECTOR2I &aSize, PCB_LAYER_ID aLayer)
Definition padstack.cpp:856
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:615
const wxString & GetNumber() const
Definition pad.h:143
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:3136
void SetShape(PCB_LAYER_ID aLayer, PAD_SHAPE aShape)
Set the new shape of this pad.
Definition pad.h:196
void SetDirty()
Definition pad.h:549
static LSET UnplatedHoleMask()
layer set for a mechanical unplated through hole pad
Definition pad.cpp:636
PAD_SHAPE GetShape(PCB_LAYER_ID aLayer) const
Definition pad.h:205
void SetNumber(const wxString &aNumber)
Set the pad number (note that it can be alphanumeric, such as the array reference "AA12").
Definition pad.h:142
void AddPrimitive(PCB_LAYER_ID aLayer, PCB_SHAPE *aPrimitive)
Add item to the custom shape primitives list.
Definition pad.cpp:3527
void SetLocalSolderMaskMargin(std::optional< int > aMargin)
Definition pad.h:587
void SetLocalZoneConnection(ZONE_CONNECTION aType)
Definition pad.h:610
VECTOR2I GetSize(PCB_LAYER_ID aLayer) const
Definition pad.cpp:297
const PADSTACK & Padstack() const
Definition pad.h:331
EDA_ANGLE GetOrientation() const
Return the rotation angle of the pad.
Definition pad.cpp:1795
void SetSize(PCB_LAYER_ID aLayer, const VECTOR2I &aSize)
Definition pad.cpp:264
static LSET SMDMask()
layer set for a SMD pad on Front layer
Definition pad.cpp:622
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:1906
void SetLayerSet(const LSET &aLayers) override
Definition pad.cpp:2037
VECTOR2I GetCenter() const override
This defaults to the center of the bounding box if not overridden.
Definition pcb_track.h:307
double GetRadius() const
A radial dimension indicates either the radius or diameter of an arc or circle.
void SetCenter(const VECTOR2I &aCenter) override
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:109
void SetShape(SHAPE_T aShape) override
Definition pcb_shape.h:212
void SetPosition(const VECTOR2I &aPos) override
Definition pcb_shape.h:77
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 TransformShapeToPolygon(SHAPE_POLY_SET &aBuffer, PCB_LAYER_ID aLayer, int aClearance, int aError, ERROR_LOC aErrorLoc, bool ignoreLineWidth=false) const override
Convert the shape to a closed polygon.
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:78
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
int Length() const
Return the length (this).
Definition seg.h:340
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
static SEG::ecoord Square(int a)
Definition seg.h:119
EDA_ANGLE GetCentralAngle() const
Get the "central angle" of the arc - this is the angle at the point of the "pie slice".
bool IsClockwise() const
Definition shape_arc.h:323
const VECTOR2I & GetP1() const
Definition shape_arc.h:115
const VECTOR2I & GetP0() const
Definition shape_arc.h:114
const VECTOR2I & GetCenter() const
Represent a polyline containing arcs as well as line segments: A chain of connected line and/or arc s...
int Intersect(const SEG &aSeg, std::vector< INTERSECTION > &aIp) const
Find all intersection points between our line chain and the segment aSeg.
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)
void ClearArcs()
Removes all arc references from all the outlines and holes in the polyset.
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.
bool IsEmpty() const
Return true if the set is empty (no polygons at all)
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...
bool Contains(const VECTOR2I &aP, int aSubpolyIndex=-1, int aAccuracy=0, bool aUseBBoxCaches=false) const
Return true if a given subpolygon contains the point aP.
SHAPE_POLY_SET CloneDropTriangulation() const
void BooleanSubtract(const SHAPE_POLY_SET &b)
Perform boolean polyset difference.
An abstract shape on 2D plane.
Definition shape.h:124
virtual bool Collide(const VECTOR2I &aP, int aClearance=0, int *aActual=nullptr, VECTOR2I *aLocation=nullptr) const
Check if the boundary of shape (this) lies closer to the point aP than aClearance,...
Definition shape.h:181
virtual const BOX2I BBox(int aClearance=0) const =0
Compute a bounding box of the shape, with a margin of aClearance a collision.
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
constexpr extended_type SquaredEuclideanNorm() const
Compute the squared euclidean norm of the vector, which is defined as (x ** 2 + y ** 2).
Definition vector2d.h:312
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
constexpr extended_type Dot(const VECTOR2< T > &aVector) const
Compute dot product of self with aVector.
Definition vector2d.h:567
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:858
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:857
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:700
void SetDoNotAllowVias(bool aEnable)
Definition zone.h:856
void SetDoNotAllowFootprints(bool aEnable)
Definition zone.h:859
void SetDoNotAllowZoneFills(bool aEnable)
Definition zone.h:855
static int GetDefaultHatchPitch()
Definition zone.cpp:1672
@ KEEP
Exists in the target, written unchanged.
@ DROP
No equivalent, left out.
@ 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
@ RECURSE
Definition eda_item.h:51
@ 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
@ CUSTOM_LAYERS
Stackup handling where the footprint can have any number of copper layers, and objects on those layer...
Definition footprint.h:116
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:741
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:983
bool IsInnerCopperLayer(int aLayerId)
Test whether a layer is an inner (In1_Cu to In30_Cu) copper layer.
Definition layer_ids.h:763
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
int MinimalCopperLayerCount(const LSET &aLayers)
Return the smallest even copper layer count whose stackup contains every copper layer in aLayers.
LSET GetAllFootprintLayers(const FOOTPRINT &aFootprint)
Return the union of layers referenced by every item inside the footprint (including graphic items,...
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.
CITER next(CITER it)
Definition ptree.cpp:120
@ RPT_SEVERITY_WARNING
@ RPT_SEVERITY_ERROR
@ RPT_SEVERITY_DEBUG
@ RPT_SEVERITY_INFO
const double epsilon
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:116
std::unique_ptr< PCB_SHAPE > shape
Board coordinates, released once folded.
Definition altium_pcb.h:423
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
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.
The features of a padstack that can vary between copper layers All parameters are optional; leaving t...
Definition padstack.h:229
std::vector< std::shared_ptr< PCB_SHAPE > > custom_shapes
Definition padstack.h:241
Represent an intersection between two line segments.
bool moved
std::string path
KIBIS top(path, &reporter)
KIBIS_MODEL * model
VECTOR2I center
const SHAPE_LINE_CHAIN chain
int radius
VECTOR2I end
int clearance
static double sweep(float aStart, float aEnd)
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:178
@ 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
VECTOR2< double > VECTOR2D
Definition vector2d.h:707
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