KiCad PCB EDA Suite
Loading...
Searching...
No Matches
altium_pcb.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright (C) 2019-2020 Thomas Pointhuber <[email protected]>
5 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, you may find one here:
19 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
20 * or you may search the http://www.gnu.org website for the version 2 license,
21 * or you may write to the Free Software Foundation, Inc.,
22 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
23 */
24
25#include "altium_pcb.h"
26#include "altium_parser_pcb.h"
31
32#include <board.h>
35#include <footprint.h>
36#include <layer_range.h>
37#include <pcb_dimension.h>
38#include <pad.h>
39#include <pcb_shape.h>
40#include <pcb_text.h>
41#include <pcb_textbox.h>
42#include <pcb_track.h>
43#include <pcb_barcode.h>
44#include <core/profile.h>
45#include <string_utils.h>
46#include <tools/pad_tool.h>
47#include <zone.h>
48
50
51#include <advanced_config.h>
52#include <compoundfilereader.h>
54#include <font/outline_font.h>
55#include <project.h>
56#include <reporter.h>
57#include <trigo.h>
58#include <utf.h>
59#include <wx/docview.h>
60#include <wx/log.h>
61#include <wx/mstream.h>
62#include <wx/wfstream.h>
63#include <wx/zstream.h>
64#include <progress_reporter.h>
65#include <magic_enum.hpp>
66#include <thread_pool.h>
67
68
69constexpr double BOLD_FACTOR = 1.75; // CSS font-weight-normal is 400; bold is 700
70
71
73{
74 return ( aLayer >= ALTIUM_LAYER::TOP_LAYER && aLayer <= ALTIUM_LAYER::BOTTOM_LAYER )
75 || aLayer == ALTIUM_LAYER::MULTI_LAYER; // TODO: add IsAltiumLayerAPlane?
76}
77
78
83
84FOOTPRINT* ALTIUM_PCB::HelperGetFootprint( uint16_t aComponent ) const
85{
86 if( aComponent == ALTIUM_COMPONENT_NONE || m_components.size() <= aComponent )
87 {
88 THROW_IO_ERROR( wxString::Format( wxT( "Component creator tries to access component id %u "
89 "of %u existing components" ),
90 (unsigned)aComponent, (unsigned)m_components.size() ) );
91 }
92
93 return m_components.at( aComponent );
94}
95
96
98 const std::vector<ALTIUM_VERTICE>& aVertices )
99{
100 for( const ALTIUM_VERTICE& vertex : aVertices )
101 {
102 if( vertex.isRound )
103 {
104 EDA_ANGLE angle( vertex.endangle - vertex.startangle, DEGREES_T );
105 angle.Normalize();
106
107 double startradiant = DEG2RAD( vertex.startangle );
108 double endradiant = DEG2RAD( vertex.endangle );
109 VECTOR2I arcStartOffset = KiROUND( std::cos( startradiant ) * vertex.radius,
110 -std::sin( startradiant ) * vertex.radius );
111
112 VECTOR2I arcEndOffset = KiROUND( std::cos( endradiant ) * vertex.radius,
113 -std::sin( endradiant ) * vertex.radius );
114
115 VECTOR2I arcStart = vertex.center + arcStartOffset;
116 VECTOR2I arcEnd = vertex.center + arcEndOffset;
117
118 bool isShort = arcStart.Distance( arcEnd ) < pcbIUScale.mmToIU( 0.001 )
119 || angle.AsDegrees() < 0.2;
120
121 if( arcStart.Distance( vertex.position )
122 < arcEnd.Distance( vertex.position ) )
123 {
124 if( !isShort )
125 {
126 aLine.Append( SHAPE_ARC( vertex.center, arcStart, -angle ) );
127 }
128 else
129 {
130 aLine.Append( arcStart );
131 aLine.Append( arcEnd );
132 }
133 }
134 else
135 {
136 if( !isShort )
137 {
138 aLine.Append( SHAPE_ARC( vertex.center, arcEnd, angle ) );
139 }
140 else
141 {
142 aLine.Append( arcEnd );
143 aLine.Append( arcStart );
144 }
145 }
146 }
147 else
148 {
149 aLine.Append( vertex.position );
150 }
151 }
152
153 aLine.SetClosed( true );
154}
155
156
158{
159 auto override = m_layermap.find( aAltiumLayer );
160
161 if( override != m_layermap.end() )
162 {
163 return override->second;
164 }
165
166 if( aAltiumLayer >= ALTIUM_LAYER::V7_MECHANICAL_17 && aAltiumLayer <= ALTIUM_LAYER::V7_MECHANICAL_LAST )
167 {
168 // Layer "Mechanical 17" would correspond to altiumOrd 16
169 int altiumOrd = static_cast<int>( aAltiumLayer ) - static_cast<int>( ALTIUM_LAYER::V7_MECHANICAL_1 );
170
171 if( ( altiumOrd + 1 ) > MAX_USER_DEFINED_LAYERS )
172 return UNDEFINED_LAYER;
173
174 // Convert to KiCad User_* layers
175 return static_cast<PCB_LAYER_ID>( static_cast<int>( User_1 ) + altiumOrd * 2 );
176 }
177
178 switch( aAltiumLayer )
179 {
181
182 case ALTIUM_LAYER::TOP_LAYER: return F_Cu;
213 case ALTIUM_LAYER::BOTTOM_LAYER: return B_Cu;
214
217 case ALTIUM_LAYER::TOP_PASTE: return F_Paste;
219 case ALTIUM_LAYER::TOP_SOLDER: return F_Mask;
221
238
241
258
269
270 default: return UNDEFINED_LAYER;
271 }
272}
273
274
275std::vector<PCB_LAYER_ID> ALTIUM_PCB::GetKicadLayersToIterate( ALTIUM_LAYER aAltiumLayer ) const
276{
277 if( aAltiumLayer == ALTIUM_LAYER::MULTI_LAYER || aAltiumLayer == ALTIUM_LAYER::KEEP_OUT_LAYER )
278 {
279 int layerCount = m_board ? m_board->GetCopperLayerCount() : 32;
280 std::vector<PCB_LAYER_ID> layers;
281 layers.reserve( layerCount );
282
283 for( PCB_LAYER_ID layer : LAYER_RANGE( F_Cu, B_Cu, layerCount ) )
284 {
285 if( !m_board || m_board->IsLayerEnabled( layer ) )
286 layers.emplace_back( layer );
287 }
288
289 return layers;
290 }
291
292 PCB_LAYER_ID klayer = GetKicadLayer( aAltiumLayer );
293
294 if( klayer == UNDEFINED_LAYER )
295 return {};
296
297 return { klayer };
298}
299
300
301ALTIUM_PCB::ALTIUM_PCB( BOARD* aBoard, PROGRESS_REPORTER* aProgressReporter,
302 LAYER_MAPPING_HANDLER& aHandler, REPORTER* aReporter,
303 const wxString& aLibrary, const wxString& aFootprintName )
304{
305 m_board = aBoard;
306 m_progressReporter = aProgressReporter;
307 m_layerMappingHandler = aHandler;
308 m_reporter = aReporter;
309 m_doneCount = 0;
311 m_totalCount = 0;
313 m_library = aLibrary;
314 m_footprintName = aFootprintName;
315}
316
320
322{
323 const unsigned PROGRESS_DELTA = 250;
324
326 {
327 if( ++m_doneCount > m_lastProgressCount + PROGRESS_DELTA )
328 {
329 m_progressReporter->SetCurrentProgress( ( (double) m_doneCount )
330 / std::max( 1U, m_totalCount ) );
331
332 if( !m_progressReporter->KeepRefreshing() )
333 THROW_IO_ERROR( _( "File import canceled by user." ) );
334
336 }
337 }
338}
339
341 const std::map<ALTIUM_PCB_DIR, std::string>& aFileMapping )
342{
343 // this vector simply declares in which order which functions to call.
344 const std::vector<std::tuple<bool, ALTIUM_PCB_DIR, PARSE_FUNCTION_POINTER_fp>> parserOrder = {
346 [this]( const ALTIUM_PCB_COMPOUND_FILE& aFile, auto fileHeader )
347 {
348 this->ParseFileHeader( aFile, fileHeader );
349 } },
351 [this]( const ALTIUM_PCB_COMPOUND_FILE& aFile, auto fileHeader )
352 {
353 this->ParseBoard6Data( aFile, fileHeader );
354 } },
356 [this]( const ALTIUM_PCB_COMPOUND_FILE& aFile, auto fileHeader )
357 {
358 this->ParseExtendedPrimitiveInformationData( aFile, fileHeader );
359 } },
361 [this]( const ALTIUM_PCB_COMPOUND_FILE& aFile, auto fileHeader )
362 {
363 this->ParseComponents6Data( aFile, fileHeader );
364 } },
365 { false, ALTIUM_PCB_DIR::MODELS,
366 [this, aFileMapping]( const ALTIUM_PCB_COMPOUND_FILE& aFile, auto fileHeader )
367 {
368 std::vector<std::string> dir{ aFileMapping.at( ALTIUM_PCB_DIR::MODELS ) };
369 this->ParseModelsData( aFile, fileHeader, dir );
370 } },
372 [this]( const ALTIUM_PCB_COMPOUND_FILE& aFile, auto fileHeader )
373 {
374 this->ParseComponentsBodies6Data( aFile, fileHeader );
375 } },
376 { true, ALTIUM_PCB_DIR::NETS6,
377 [this]( const ALTIUM_PCB_COMPOUND_FILE& aFile, auto fileHeader )
378 {
379 this->ParseNets6Data( aFile, fileHeader );
380 } },
382 [this]( const ALTIUM_PCB_COMPOUND_FILE& aFile, auto fileHeader )
383 {
384 this->ParseClasses6Data( aFile, fileHeader );
385 } },
387 [this]( const ALTIUM_PCB_COMPOUND_FILE& aFile, auto fileHeader )
388 {
389 this->ParseRules6Data( aFile, fileHeader );
390 } },
392 [this]( const ALTIUM_PCB_COMPOUND_FILE& aFile, auto fileHeader )
393 {
394 this->ParseDimensions6Data( aFile, fileHeader );
395 } },
397 [this]( const ALTIUM_PCB_COMPOUND_FILE& aFile, auto fileHeader )
398 {
399 this->ParsePolygons6Data( aFile, fileHeader );
400 } },
401 { true, ALTIUM_PCB_DIR::ARCS6,
402 [this]( const ALTIUM_PCB_COMPOUND_FILE& aFile, auto fileHeader )
403 {
404 this->ParseArcs6Data( aFile, fileHeader );
405 } },
406 { true, ALTIUM_PCB_DIR::PADS6,
407 [this]( const ALTIUM_PCB_COMPOUND_FILE& aFile, auto fileHeader )
408 {
409 this->ParsePads6Data( aFile, fileHeader );
410 } },
411 { true, ALTIUM_PCB_DIR::VIAS6,
412 [this]( const ALTIUM_PCB_COMPOUND_FILE& aFile, auto fileHeader )
413 {
414 this->ParseVias6Data( aFile, fileHeader );
415 } },
417 [this]( const ALTIUM_PCB_COMPOUND_FILE& aFile, auto fileHeader )
418 {
419 this->ParseTracks6Data( aFile, fileHeader );
420 } },
422 [this]( const ALTIUM_PCB_COMPOUND_FILE& aFile, auto fileHeader )
423 {
424 this->ParseWideStrings6Data( aFile, fileHeader );
425 } },
427 [this]( const ALTIUM_PCB_COMPOUND_FILE& aFile, auto fileHeader )
428 {
429 this->ParseTexts6Data( aFile, fileHeader );
430 } },
432 [this]( const ALTIUM_PCB_COMPOUND_FILE& aFile, auto fileHeader )
433 {
434 this->ParseFills6Data( aFile, fileHeader );
435 } },
437 [this]( const ALTIUM_PCB_COMPOUND_FILE& aFile, auto fileHeader )
438 {
439 this->ParseBoardRegionsData( aFile, fileHeader );
440 } },
442 [this]( const ALTIUM_PCB_COMPOUND_FILE& aFile, auto fileHeader )
443 {
444 this->ParseShapeBasedRegions6Data( aFile, fileHeader );
445 } },
447 [this]( const ALTIUM_PCB_COMPOUND_FILE& aFile, auto fileHeader )
448 {
449 this->ParseRegions6Data( aFile, fileHeader );
450 } }
451 };
452
453 if( m_progressReporter != nullptr )
454 {
455 // Count number of records we will read for the progress reporter
456 for( const std::tuple<bool, ALTIUM_PCB_DIR, PARSE_FUNCTION_POINTER_fp>& cur : parserOrder )
457 {
458 bool isRequired;
461 std::tie( isRequired, directory, fp ) = cur;
462
464 continue;
465
466 const auto& mappedDirectory = aFileMapping.find( directory );
467
468 if( mappedDirectory == aFileMapping.end() )
469 continue;
470
471 const std::vector<std::string> mappedFile{ mappedDirectory->second, "Header" };
472 const CFB::COMPOUND_FILE_ENTRY* file = altiumPcbFile.FindStream( mappedFile );
473
474 if( file == nullptr )
475 continue;
476
477 ALTIUM_BINARY_PARSER reader( altiumPcbFile, file );
478 uint32_t numOfRecords = reader.Read<uint32_t>();
479
480 if( reader.HasParsingError() )
481 {
482 if( m_reporter )
483 {
484 m_reporter->Report( wxString::Format( _( "'%s' was not parsed correctly." ),
485 FormatPath( mappedFile ) ),
487 }
488
489 continue;
490 }
491
492 m_totalCount += numOfRecords;
493
494 if( reader.GetRemainingBytes() != 0 )
495 {
496 if( m_reporter )
497 {
498 m_reporter->Report( wxString::Format( _( "'%s' was not fully parsed." ),
499 FormatPath( mappedFile ) ),
501 }
502
503 continue;
504 }
505 }
506 }
507
508 const auto& boardDirectory = aFileMapping.find( ALTIUM_PCB_DIR::BOARD6 );
509
510 if( boardDirectory != aFileMapping.end() )
511 {
512 std::vector<std::string> mappedFile{ boardDirectory->second, "Data" };
513
514 const CFB::COMPOUND_FILE_ENTRY* file = altiumPcbFile.FindStream( mappedFile );
515
516 if( !file )
517 {
519 "This file does not appear to be in a valid PCB Binary Version 6.0 format. In "
520 "Altium Designer, "
521 "make sure to save as \"PCB Binary Files (*.PcbDoc)\"." ) );
522 }
523 }
524
525 // Parse data in specified order
526 for( const std::tuple<bool, ALTIUM_PCB_DIR, PARSE_FUNCTION_POINTER_fp>& cur : parserOrder )
527 {
528 bool isRequired;
531 std::tie( isRequired, directory, fp ) = cur;
532
533 const auto& mappedDirectory = aFileMapping.find( directory );
534
535 if( mappedDirectory == aFileMapping.end() )
536 {
537 wxASSERT_MSG( !isRequired, wxString::Format( wxT( "Altium Directory of kind %d was "
538 "expected, but no mapping is "
539 "present in the code" ),
540 directory ) );
541 continue;
542 }
543
544 std::vector<std::string> mappedFile{ mappedDirectory->second };
545
547 mappedFile.emplace_back( "Data" );
548
549 const CFB::COMPOUND_FILE_ENTRY* file = altiumPcbFile.FindStream( mappedFile );
550
551 if( file != nullptr )
552 {
553 fp( altiumPcbFile, file );
554 }
555 else if( isRequired )
556 {
557 if( m_reporter )
558 {
559 m_reporter->Report( wxString::Format( _( "File not found: '%s' for directory '%s'." ),
560 FormatPath( mappedFile ),
561 magic_enum::enum_name( directory ) ),
563 }
564 }
565 }
566
567 // fixup zone priorities since Altium stores them in the opposite order
568 for( ZONE* zone : m_polygons )
569 {
570 if( !zone )
571 continue;
572
573 // Altium "fills" - not poured in Altium
574 if( zone->GetAssignedPriority() == 1000 )
575 {
576 // Unlikely, but you never know
577 if( m_highest_pour_index >= 1000 )
578 zone->SetAssignedPriority( m_highest_pour_index + 1 );
579
580 continue;
581 }
582
583 int priority = m_highest_pour_index - zone->GetAssignedPriority();
584
585 zone->SetAssignedPriority( priority >= 0 ? priority : 0 );
586 }
587
588 // change priority of outer zone to zero
589 for( std::pair<const ALTIUM_LAYER, ZONE*>& zone : m_outer_plane )
590 zone.second->SetAssignedPriority( 0 );
591
592 // Simplify and fracture zone fills in case we constructed them from tracks (hatched fill)
593 for( ZONE* zone : m_polygons )
594 {
595 if( !zone )
596 continue;
597
598 for( PCB_LAYER_ID layer : zone->GetLayerSet() )
599 {
600 if( !zone->HasFilledPolysForLayer( layer ) )
601 continue;
602
603 zone->GetFilledPolysList( layer )->Fracture();
604 }
605 }
606
607 // Altium doesn't appear to store either the dimension value nor the dimensioned object in
608 // the dimension record. (Yes, there is a REFERENCE0OBJECTID, but it doesn't point to the
609 // dimensioned object.) We attempt to plug this gap by finding a colocated arc or circle
610 // and using its radius. If there are more than one such arcs/circles, well, :shrug:.
612 {
613 int radius = 0;
614
615 for( BOARD_ITEM* item : m_board->Drawings() )
616 {
617 if( item->Type() != PCB_SHAPE_T )
618 continue;
619
620 PCB_SHAPE* shape = static_cast<PCB_SHAPE*>( item );
621
622 if( shape->GetShape() != SHAPE_T::ARC && shape->GetShape() != SHAPE_T::CIRCLE )
623 continue;
624
625 if( shape->GetPosition() == dim->GetPosition() )
626 {
627 radius = shape->GetRadius();
628 break;
629 }
630 }
631
632 if( radius == 0 )
633 {
634 for( PCB_TRACK* track : m_board->Tracks() )
635 {
636 if( track->Type() != PCB_ARC_T )
637 continue;
638
639 PCB_ARC* arc = static_cast<PCB_ARC*>( track );
640
641 if( arc->GetCenter() == dim->GetPosition() )
642 {
643 radius = arc->GetRadius();
644 break;
645 }
646 }
647 }
648
649 // Move the radius point onto the circumference
650 VECTOR2I radialLine = dim->GetEnd() - dim->GetStart();
651 int totalLength = radialLine.EuclideanNorm();
652
653 // Enforce a minimum on the radialLine else we won't have enough precision to get the
654 // angle from it.
655 radialLine = radialLine.Resize( std::max( radius, 2 ) );
656 dim->SetEnd( dim->GetStart() + (VECTOR2I) radialLine );
657 dim->SetLeaderLength( totalLength - radius );
658 dim->Update();
659 }
660
661 // center board
662 BOX2I bbbox = m_board->GetBoardEdgesBoundingBox();
663
664 int w = m_board->GetPageSettings().GetWidthIU( pcbIUScale.IU_PER_MILS );
665 int h = m_board->GetPageSettings().GetHeightIU( pcbIUScale.IU_PER_MILS );
666
667 int desired_x = ( w - bbbox.GetWidth() ) / 2;
668 int desired_y = ( h - bbbox.GetHeight() ) / 2;
669
670 VECTOR2I movementVector( desired_x - bbbox.GetX(), desired_y - bbbox.GetY() );
671 m_board->Move( movementVector );
672
673 BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings();
674 bds.SetAuxOrigin( bds.GetAuxOrigin() + movementVector );
675 bds.SetGridOrigin( bds.GetGridOrigin() + movementVector );
676
677 m_board->SetModified();
678}
679
680
682 const wxString& aFootprintName )
683{
684 std::unique_ptr<FOOTPRINT> footprint = std::make_unique<FOOTPRINT>( m_board );
685
686 m_unicodeStrings.clear();
688
689 const std::vector<std::string> libStreamName{ "Library", "Data" };
690 const CFB::COMPOUND_FILE_ENTRY* libStream = altiumLibFile.FindStream( libStreamName );
691
692 if( libStream == nullptr )
693 {
694 THROW_IO_ERROR( wxString::Format( _( "File not found: '%s'." ), FormatPath( libStreamName ) ) );
695 }
696
697 ALTIUM_BINARY_PARSER libParser( altiumLibFile, libStream );
698 ALIBRARY libData( libParser );
699
701
702 // TODO: WideStrings are stored as parameterMap in the case of footprints, not as binary
703 // std::string unicodeStringsStreamName = aFootprintName.ToStdString() + "\\WideStrings";
704 // const CFB::COMPOUND_FILE_ENTRY* unicodeStringsData = altiumLibFile.FindStream( unicodeStringsStreamName );
705 // if( unicodeStringsData != nullptr )
706 // {
707 // ParseWideStrings6Data( altiumLibFile, unicodeStringsData );
708 // }
709
710 std::tuple<wxString, const CFB::COMPOUND_FILE_ENTRY*> ret =
711 altiumLibFile.FindLibFootprintDirName( aFootprintName );
712
713 wxString fpDirName = std::get<0>( ret );
714 const CFB::COMPOUND_FILE_ENTRY* footprintStream = std::get<1>( ret );
715
716 if( fpDirName.IsEmpty() )
717 {
719 wxString::Format( _( "Footprint directory not found: '%s'." ), aFootprintName ) );
720 }
721
722 const std::vector<std::string> streamName{ fpDirName.ToStdString(), "Data" };
723 const CFB::COMPOUND_FILE_ENTRY* footprintData = altiumLibFile.FindStream( footprintStream, { "Data" } );
724
725 if( footprintData == nullptr )
726 {
727 THROW_IO_ERROR( wxString::Format( _( "File not found: '%s'." ),
728 FormatPath( streamName ) ) );
729 }
730
731 ALTIUM_BINARY_PARSER parser( altiumLibFile, footprintData );
732
734 //wxString footprintName = parser.ReadWxString(); // Not used (single-byte char set)
735 parser.SkipSubrecord();
736
737 LIB_ID fpID = AltiumToKiCadLibID( "", aFootprintName ); // TODO: library name
738 footprint->SetFPID( fpID );
739
740 const std::vector<std::string> parametersStreamName{ fpDirName.ToStdString(),
741 "Parameters" };
742 const CFB::COMPOUND_FILE_ENTRY* parametersData =
743 altiumLibFile.FindStream( footprintStream, { "Parameters" } );
744
745 if( parametersData != nullptr )
746 {
747 ALTIUM_BINARY_PARSER parametersReader( altiumLibFile, parametersData );
748 std::map<wxString, wxString> parameterProperties = parametersReader.ReadProperties();
749 wxString description = ALTIUM_PROPS_UTILS::ReadString( parameterProperties,
750 wxT( "DESCRIPTION" ), wxT( "" ) );
751 footprint->SetLibDescription( description );
752 }
753 else
754 {
755 if( m_reporter )
756 {
757 m_reporter->Report( wxString::Format( _( "File not found: '%s'." ),
758 FormatPath( parametersStreamName ) ),
760 }
761
762 footprint->SetLibDescription( wxT( "" ) );
763 }
764
765 const std::vector<std::string> extendedPrimitiveInformationStreamName{
766 "ExtendedPrimitiveInformation", "Data"
767 };
768 const CFB::COMPOUND_FILE_ENTRY* extendedPrimitiveInformationData =
769 altiumLibFile.FindStream( footprintStream, extendedPrimitiveInformationStreamName );
770
771 if( extendedPrimitiveInformationData != nullptr )
772 ParseExtendedPrimitiveInformationData( altiumLibFile, extendedPrimitiveInformationData );
773
774 footprint->SetReference( wxT( "REF**" ) );
775 footprint->SetValue( aFootprintName );
776 footprint->Reference().SetVisible( true ); // TODO: extract visibility information
777 footprint->Value().SetVisible( true );
778
779 const VECTOR2I defaultTextSize( pcbIUScale.mmToIU( 1.0 ), pcbIUScale.mmToIU( 1.0 ) );
780 const int defaultTextThickness( pcbIUScale.mmToIU( 0.15 ) );
781
782 for( PCB_FIELD* field : footprint->GetFields() )
783 {
784 field->SetTextSize( defaultTextSize );
785 field->SetTextThickness( defaultTextThickness );
786 }
787
788 for( int primitiveIndex = 0; parser.GetRemainingBytes() >= 4; primitiveIndex++ )
789 {
790 ALTIUM_RECORD recordtype = static_cast<ALTIUM_RECORD>( parser.Peek<uint8_t>() );
791
792 switch( recordtype )
793 {
795 {
796 AARC6 arc( parser );
797 ConvertArcs6ToFootprintItem( footprint.get(), arc, primitiveIndex, false );
798 break;
799 }
801 {
802 APAD6 pad( parser );
803 ConvertPads6ToFootprintItem( footprint.get(), pad );
804 break;
805 }
807 {
808 AVIA6 via( parser );
809 ConvertVias6ToFootprintItem( footprint.get(), via );
810 break;
811 }
813 {
814 ATRACK6 track( parser );
815 ConvertTracks6ToFootprintItem( footprint.get(), track, primitiveIndex, false );
816 break;
817 }
819 {
820 ATEXT6 text( parser, m_unicodeStrings );
821 ConvertTexts6ToFootprintItem( footprint.get(), text );
822 break;
823 }
825 {
826 AFILL6 fill( parser );
827 ConvertFills6ToFootprintItem( footprint.get(), fill, false );
828 break;
829 }
831 {
832 AREGION6 region( parser, false );
833 ConvertShapeBasedRegions6ToFootprintItem( footprint.get(), region, primitiveIndex );
834 break;
835 }
837 {
838 ACOMPONENTBODY6 componentBody( parser );
839 ConvertComponentBody6ToFootprintItem( altiumLibFile, footprint.get(), componentBody );
840 break;
841 }
842 default:
843 THROW_IO_ERROR( wxString::Format( _( "Record of unknown type: '%d'." ), recordtype ) );
844 }
845 }
846
847
848 // Loop over this multiple times to catch pads that are jumpered to each other by multiple shapes
849 for( bool changes = true; changes; )
850 {
851 changes = false;
852
853 alg::for_all_pairs( footprint->Pads().begin(), footprint->Pads().end(),
854 [&changes]( PAD* aPad1, PAD* aPad2 )
855 {
856 if( !( aPad1->GetNumber().IsEmpty() ^ aPad2->GetNumber().IsEmpty() ) )
857 return;
858
859 for( PCB_LAYER_ID layer : aPad1->GetLayerSet() )
860 {
861 std::shared_ptr<SHAPE> shape1 = aPad1->GetEffectiveShape( layer );
862 std::shared_ptr<SHAPE> shape2 = aPad2->GetEffectiveShape( layer );
863
864 if( shape1->Collide( shape2.get() ) )
865 {
866 if( aPad1->GetNumber().IsEmpty() )
867 aPad1->SetNumber( aPad2->GetNumber() );
868 else
869 aPad2->SetNumber( aPad1->GetNumber() );
870
871 changes = true;
872 }
873 }
874 } );
875 }
876
877 // Auto-position reference and value
878 footprint->AutoPositionFields();
879
880 if( parser.HasParsingError() )
881 {
882 THROW_IO_ERROR( wxString::Format( wxT( "%s stream was not parsed correctly" ),
883 FormatPath( streamName ) ) );
884 }
885
886 if( parser.GetRemainingBytes() != 0 )
887 {
888 THROW_IO_ERROR( wxString::Format( wxT( "%s stream is not fully parsed" ),
889 FormatPath( streamName ) ) );
890 }
891
892 return footprint.release();
893}
894
895int ALTIUM_PCB::GetNetCode( uint16_t aId ) const
896{
897 if( aId == ALTIUM_NET_UNCONNECTED )
898 {
900 }
901 else if( m_altiumToKicadNetcodes.size() < aId )
902 {
903 THROW_IO_ERROR( wxString::Format( wxT( "Netcode with id %d does not exist. Only %d nets "
904 "are known" ),
905 aId, m_altiumToKicadNetcodes.size() ) );
906 }
907 else
908 {
909 return m_altiumToKicadNetcodes[ aId ];
910 }
911}
912
913const ARULE6* ALTIUM_PCB::GetRule( ALTIUM_RULE_KIND aKind, const wxString& aName ) const
914{
915 const auto rules = m_rules.find( aKind );
916
917 if( rules == m_rules.end() )
918 return nullptr;
919
920 for( const ARULE6& rule : rules->second )
921 {
922 if( rule.name == aName )
923 return &rule;
924 }
925
926 return nullptr;
927}
928
930{
931 const auto rules = m_rules.find( aKind );
932
933 if( rules == m_rules.end() )
934 return nullptr;
935
936 for( const ARULE6& rule : rules->second )
937 {
938 if( rule.scope1expr == wxT( "All" ) && rule.scope2expr == wxT( "All" ) )
939 return &rule;
940 }
941
942 return nullptr;
943}
944
945
947{
948 const auto rules = m_rules.find( aKind );
949
950 if( rules == m_rules.end() )
951 return nullptr;
952
953 if( const ARULE6* match = selectAltiumPolygonRule( rules->second ) )
954 return match;
955
956 // Fall back to the default (All/All) rule
957 return GetRuleDefault( aKind );
958}
959
960
962 const CFB::COMPOUND_FILE_ENTRY* aEntry )
963{
964 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
965
967 wxString header = reader.ReadWxString();
968
969 //std::cout << "HEADER: " << header << std::endl; // tells me: PCB 5.0 Binary File
970
971 //reader.SkipSubrecord();
972
973 // TODO: does not seem to work all the time at the moment
974 //if( reader.GetRemainingBytes() != 0 )
975 // THROW_IO_ERROR( "FileHeader stream is not fully parsed" );
976}
977
978
980 const CFB::COMPOUND_FILE_ENTRY* aEntry )
981{
983 m_progressReporter->Report( _( "Loading extended primitive information data..." ) );
984
985 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
986
987 while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
988 {
989 checkpoint();
990 AEXTENDED_PRIMITIVE_INFORMATION elem( reader );
991
993 std::move( elem ) );
994 }
995
996 if( reader.GetRemainingBytes() != 0 )
997 THROW_IO_ERROR( wxT( "ExtendedPrimitiveInformation stream is not fully parsed" ) );
998}
999
1000
1002 const CFB::COMPOUND_FILE_ENTRY* aEntry )
1003{
1004 if( m_progressReporter )
1005 m_progressReporter->Report( _( "Loading board data..." ) );
1006
1007 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
1008
1009 checkpoint();
1010 ABOARD6 elem( reader );
1011
1012 if( reader.GetRemainingBytes() != 0 )
1013 THROW_IO_ERROR( wxT( "Board6 stream is not fully parsed" ) );
1014
1015 m_board->GetDesignSettings().SetAuxOrigin( elem.sheetpos );
1016 m_board->GetDesignSettings().SetGridOrigin( elem.sheetpos );
1017
1018 // read layercount from stackup, because LAYERSETSCOUNT is not always correct?!
1019 size_t layercount = 0;
1020 size_t layerid = static_cast<size_t>( ALTIUM_LAYER::TOP_LAYER );
1021
1022 while( layerid < elem.stackup.size() && layerid != 0 )
1023 {
1024 layerid = elem.stackup[ layerid - 1 ].nextId;
1025 layercount++;
1026 }
1027
1028 size_t kicadLayercount = ( layercount % 2 == 0 ) ? layercount : layercount + 1;
1029 m_board->SetCopperLayerCount( kicadLayercount );
1030
1031 BOARD_DESIGN_SETTINGS& designSettings = m_board->GetDesignSettings();
1032 BOARD_STACKUP& stackup = designSettings.GetStackupDescriptor();
1033
1034 // create board stackup
1035 stackup.RemoveAll(); // Just to be sure
1036 stackup.BuildDefaultStackupList( &designSettings, layercount );
1037
1038 auto it = stackup.GetList().begin();
1039
1040 // find first copper layer
1041 for( ; it != stackup.GetList().end() && ( *it )->GetType() != BS_ITEM_TYPE_COPPER; ++it )
1042 ;
1043
1044 auto cuLayer = LAYER_RANGE( F_Cu, B_Cu, 32 ).begin();
1045
1046 for( size_t altiumLayerId = static_cast<size_t>( ALTIUM_LAYER::TOP_LAYER );
1047 altiumLayerId < elem.stackup.size() && altiumLayerId != 0;
1048 altiumLayerId = elem.stackup[altiumLayerId - 1].nextId )
1049 {
1050 // array starts with 0, but stackup with 1
1051 ABOARD6_LAYER_STACKUP& layer = elem.stackup.at( altiumLayerId - 1 );
1052
1053 // handle unused layer in case of odd layercount
1054 if( layer.nextId == 0 && layercount != kicadLayercount )
1055 {
1056 m_board->SetLayerName( ( *it )->GetBrdLayerId(), wxT( "[unused]" ) );
1057
1058 if( ( *it )->GetType() != BS_ITEM_TYPE_COPPER )
1059 THROW_IO_ERROR( wxT( "Board6 stream, unexpected item while parsing stackup" ) );
1060
1061 ( *it )->SetThickness( 0 );
1062
1063 ++it;
1064
1065 if( ( *it )->GetType() != BS_ITEM_TYPE_DIELECTRIC )
1066 THROW_IO_ERROR( wxT( "Board6 stream, unexpected item while parsing stackup" ) );
1067
1068 ( *it )->SetThickness( 0, 0 );
1069 ( *it )->SetThicknessLocked( true, 0 );
1070 ++it;
1071 }
1072
1073 m_layermap.insert( { static_cast<ALTIUM_LAYER>( altiumLayerId ), *cuLayer } );
1074 ++cuLayer;
1075
1076 if( ( *it )->GetType() != BS_ITEM_TYPE_COPPER )
1077 THROW_IO_ERROR( wxT( "Board6 stream, unexpected item while parsing stackup" ) );
1078
1079 ( *it )->SetThickness( layer.copperthick );
1080
1081 ALTIUM_LAYER alayer = static_cast<ALTIUM_LAYER>( altiumLayerId );
1082 PCB_LAYER_ID klayer = ( *it )->GetBrdLayerId();
1083
1084 m_board->SetLayerName( klayer, layer.name );
1085
1086 if( layer.copperthick == 0 )
1087 m_board->SetLayerType( klayer, LAYER_T::LT_JUMPER ); // used for things like wirebonding
1088 else if( IsAltiumLayerAPlane( alayer ) )
1089 m_board->SetLayerType( klayer, LAYER_T::LT_POWER );
1090
1091 if( klayer == B_Cu )
1092 {
1093 if( layer.nextId != 0 )
1094 THROW_IO_ERROR( wxT( "Board6 stream, unexpected id while parsing last stackup layer" ) );
1095
1096 // overwrite entry from internal -> bottom
1097 m_layermap[alayer] = B_Cu;
1098 break;
1099 }
1100
1101 ++it;
1102
1103 if( ( *it )->GetType() != BS_ITEM_TYPE_DIELECTRIC )
1104 THROW_IO_ERROR( wxT( "Board6 stream, unexpected item while parsing stackup" ) );
1105
1106 ( *it )->SetThickness( layer.dielectricthick, 0 );
1107 ( *it )->SetMaterial( layer.dielectricmaterial.empty() ?
1108 NotSpecifiedPrm() :
1109 wxString( layer.dielectricmaterial ) );
1110 ( *it )->SetEpsilonR( layer.dielectricconst, 0 );
1111
1112 if( layer.dielectriclosstangent > 0. )
1113 ( *it )->SetLossTangent( layer.dielectriclosstangent, 0 );
1114
1115 ++it;
1116 }
1117
1119 remapUnsureLayers( elem.stackup );
1120
1121 // Set name of all non-cu layers
1122 for( const ABOARD6_LAYER_STACKUP& layer : elem.stackup )
1123 {
1124 ALTIUM_LAYER alayer = static_cast<ALTIUM_LAYER>( layer.layerId );
1125
1126 if( ( alayer >= ALTIUM_LAYER::TOP_OVERLAY && alayer <= ALTIUM_LAYER::BOTTOM_SOLDER )
1127 || ( alayer >= ALTIUM_LAYER::MECHANICAL_1 && alayer <= ALTIUM_LAYER::MECHANICAL_16 )
1129 {
1130 PCB_LAYER_ID klayer = GetKicadLayer( alayer );
1131 m_board->SetLayerName( klayer, layer.name );
1132 }
1133 }
1134
1136 m_board->GetDesignSettings().SetBoardThickness( stackup.BuildBoardThicknessFromStackup() );
1137}
1138
1139
1140// Helper to detect if a layer name indicates a courtyard layer
1141static bool IsLayerNameCourtyard( const wxString& aName )
1142{
1143 wxString nameLower = aName.Lower();
1144 return nameLower.Contains( wxT( "courtyard" ) ) || nameLower.Contains( wxT( "court yard" ) )
1145 || nameLower.Contains( wxT( "crtyd" ) );
1146}
1147
1148
1149// Helper to detect if a layer name indicates an assembly layer
1150static bool IsLayerNameAssembly( const wxString& aName )
1151{
1152 wxString nameLower = aName.Lower();
1153 return nameLower.Contains( wxT( "assembly" ) ) || nameLower.Contains( wxT( "assy" ) );
1154}
1155
1156
1157// Helper to detect if a layer name indicates a top-side layer
1158static bool IsLayerNameTopSide( const wxString& aName )
1159{
1160 bool isTop = false;
1161
1162 auto check = [&isTop]( bool aTopCond, bool aBotCond )
1163 {
1164 if( aTopCond && aBotCond )
1165 return false;
1166
1167 if( !aTopCond && !aBotCond )
1168 return false;
1169
1170 isTop = aTopCond;
1171 return true;
1172 };
1173
1174 wxString lower = aName.Lower();
1175
1176 if( check( lower.StartsWith( "top" ), lower.StartsWith( "bot" ) ) )
1177 return isTop;
1178
1179 if( check( lower.EndsWith( "_t" ), lower.EndsWith( "_b" ) ) )
1180 return isTop;
1181
1182 if( check( lower.EndsWith( ".t" ), lower.EndsWith( ".b" ) ) )
1183 return isTop;
1184
1185 if( check( lower.Contains( "top" ), lower.Contains( "bot" ) ) )
1186 return isTop;
1187
1188 return true; // Unknown
1189}
1190
1191
1192void ALTIUM_PCB::remapUnsureLayers( std::vector<ABOARD6_LAYER_STACKUP>& aStackup )
1193{
1194 LSET enabledLayers = m_board->GetEnabledLayers();
1195 LSET validRemappingLayers = enabledLayers | LSET::AllBoardTechMask() |
1197
1198 if( aStackup.size() == 0 )
1199 return;
1200
1201 std::vector<INPUT_LAYER_DESC> inputLayers;
1202 std::map<wxString, ALTIUM_LAYER> altiumLayerNameMap;
1203
1204 ABOARD6_LAYER_STACKUP& curLayer = aStackup[0];
1205 ALTIUM_LAYER layer_num;
1206 INPUT_LAYER_DESC iLdesc;
1207
1208 // Track which courtyard layers we've mapped to avoid duplicates
1209 bool frontCourtyardMapped = false;
1210 bool backCourtyardMapped = false;
1211
1212 for( size_t ii = 0; ii < aStackup.size(); ii++ )
1213 {
1214 curLayer = aStackup[ii];
1215 layer_num = static_cast<ALTIUM_LAYER>( curLayer.layerId );
1216
1217 // Skip UI-only layers and pseudo-layers that have no physical representation
1218 if( layer_num == ALTIUM_LAYER::MULTI_LAYER
1219 || layer_num == ALTIUM_LAYER::CONNECTIONS
1220 || layer_num == ALTIUM_LAYER::BACKGROUND
1221 || layer_num == ALTIUM_LAYER::DRC_ERROR_MARKERS
1222 || layer_num == ALTIUM_LAYER::SELECTIONS
1223 || layer_num == ALTIUM_LAYER::VISIBLE_GRID_1
1224 || layer_num == ALTIUM_LAYER::VISIBLE_GRID_2
1225 || layer_num == ALTIUM_LAYER::PAD_HOLES
1226 || layer_num == ALTIUM_LAYER::VIA_HOLES )
1227 {
1228 continue;
1229 }
1230
1231 // Skip disabled mechanical layers (mapped to UNDEFINED_LAYER by
1232 // HelperFillMechanicalLayerAssignments)
1233 auto existingMapping = m_layermap.find( layer_num );
1234
1235 if( existingMapping != m_layermap.end()
1236 && existingMapping->second == PCB_LAYER_ID::UNDEFINED_LAYER )
1237 {
1238 continue;
1239 }
1240
1241 // Skip unused copper layers not present in the board's stackup. Used copper layers
1242 // were added to m_layermap during stackup parsing; any copper layer not in the map
1243 // is unused and should not appear in the dialog.
1244 if( layer_num >= ALTIUM_LAYER::TOP_LAYER && layer_num <= ALTIUM_LAYER::BOTTOM_LAYER
1245 && existingMapping == m_layermap.end() )
1246 {
1247 continue;
1248 }
1249
1250 // Use existing mapping as auto-match default if available
1251 if( existingMapping != m_layermap.end() )
1252 {
1253 iLdesc.AutoMapLayer = existingMapping->second;
1254 }
1255 // Check if the layer name indicates a courtyard layer
1256 else if( IsLayerNameCourtyard( curLayer.name ) )
1257 {
1258 bool isTopSide = IsLayerNameTopSide( curLayer.name );
1259
1260 if( isTopSide && !frontCourtyardMapped )
1261 {
1262 iLdesc.AutoMapLayer = F_CrtYd;
1263 frontCourtyardMapped = true;
1264 }
1265 else if( !isTopSide && !backCourtyardMapped )
1266 {
1267 iLdesc.AutoMapLayer = B_CrtYd;
1268 backCourtyardMapped = true;
1269 }
1270 else if( !frontCourtyardMapped )
1271 {
1272 iLdesc.AutoMapLayer = F_CrtYd;
1273 frontCourtyardMapped = true;
1274 }
1275 else if( !backCourtyardMapped )
1276 {
1277 iLdesc.AutoMapLayer = B_CrtYd;
1278 backCourtyardMapped = true;
1279 }
1280 else
1281 {
1282 iLdesc.AutoMapLayer = GetKicadLayer( layer_num );
1283 }
1284 }
1285 // Check if the layer name indicates an assembly layer (map to Fab)
1286 else if( IsLayerNameAssembly( curLayer.name ) )
1287 {
1288 bool isTopSide = IsLayerNameTopSide( curLayer.name );
1289 iLdesc.AutoMapLayer = isTopSide ? F_Fab : B_Fab;
1290 }
1291 else
1292 {
1293 iLdesc.AutoMapLayer = GetKicadLayer( layer_num );
1294 }
1295
1296 iLdesc.Name = curLayer.name;
1297 iLdesc.PermittedLayers = validRemappingLayers;
1298 iLdesc.Required = layer_num >= ALTIUM_LAYER::TOP_LAYER
1299 && layer_num <= ALTIUM_LAYER::BOTTOM_LAYER;
1300
1301 inputLayers.push_back( iLdesc );
1302 altiumLayerNameMap.insert( { curLayer.name, layer_num } );
1303 m_layerNames.insert( { layer_num, curLayer.name } );
1304 }
1305
1306 if( inputLayers.size() == 0 )
1307 return;
1308
1309 // Callback:
1310 std::map<wxString, PCB_LAYER_ID> reMappedLayers = m_layerMappingHandler( inputLayers );
1311
1312 for( std::pair<wxString, PCB_LAYER_ID> layerPair : reMappedLayers )
1313 {
1314 if( layerPair.second == PCB_LAYER_ID::UNDEFINED_LAYER )
1315 {
1316 // Layer mapping handler returned UNDEFINED_LAYER - skip this layer
1317 // This can happen for layers that don't have a KiCad equivalent
1318 if( m_reporter )
1319 {
1320 m_reporter->Report( wxString::Format( _( "Layer '%s' could not be mapped and "
1321 "will be skipped." ),
1322 layerPair.first ),
1324 }
1325
1326 continue;
1327 }
1328
1329 ALTIUM_LAYER altiumID = altiumLayerNameMap.at( layerPair.first );
1330 m_layermap.insert_or_assign( altiumID, layerPair.second );
1331 enabledLayers |= LSET( { layerPair.second } );
1332 }
1333
1334 // Explicitly mark unmatched dialog layers as UNDEFINED_LAYER so they are not imported
1335 // via the GetKicadLayer() hardcoded switch fallthrough
1336 for( const auto& [name, altLayer] : altiumLayerNameMap )
1337 {
1338 if( reMappedLayers.find( name ) == reMappedLayers.end()
1339 || reMappedLayers.at( name ) == PCB_LAYER_ID::UNDEFINED_LAYER )
1340 {
1341 m_layermap.insert_or_assign( altLayer, PCB_LAYER_ID::UNDEFINED_LAYER );
1342 }
1343 }
1344
1345 m_board->SetEnabledLayers( enabledLayers );
1346 m_board->SetVisibleLayers( enabledLayers );
1347}
1348
1349
1350void ALTIUM_PCB::HelperFillMechanicalLayerAssignments( const std::vector<ABOARD6_LAYER_STACKUP>& aStackup )
1351{
1352 for( const ABOARD6_LAYER_STACKUP& layer : aStackup )
1353 {
1354 ALTIUM_LAYER alayer = static_cast<ALTIUM_LAYER>( layer.layerId );
1355
1356 if( ( alayer >= ALTIUM_LAYER::MECHANICAL_1 && alayer <= ALTIUM_LAYER::MECHANICAL_16 )
1358 {
1359 if( !layer.mechenabled )
1360 {
1361 m_layermap.emplace( alayer, UNDEFINED_LAYER ); // Disabled layer, do not import
1362 continue;
1363 }
1364
1366
1367 switch( layer.mechkind )
1368 {
1369 case ALTIUM_MECHKIND::ASSEMBLY_TOP: target = F_Fab; break;
1370 case ALTIUM_MECHKIND::ASSEMBLY_BOT: target = B_Fab; break;
1371
1372 case ALTIUM_MECHKIND::COURTYARD_TOP: target = F_CrtYd; break;
1373 case ALTIUM_MECHKIND::COURTYARD_BOT: target = B_CrtYd; break;
1374
1375 case ALTIUM_MECHKIND::GLUE_POINTS_TOP: target = F_Adhes; break;
1376 case ALTIUM_MECHKIND::GLUE_POINTS_BOT: target = B_Adhes; break;
1377
1378 case ALTIUM_MECHKIND::ASSEMBLY_NOTES: target = Cmts_User; break;
1379 case ALTIUM_MECHKIND::FAB_NOTES: target = Cmts_User; break;
1380
1381 case ALTIUM_MECHKIND::DIMENSIONS: target = Dwgs_User; break;
1382
1383 case ALTIUM_MECHKIND::DIMENSIONS_TOP: target = F_Fab; break;
1384 case ALTIUM_MECHKIND::DIMENSIONS_BOT: target = B_Fab; break;
1385
1386 case ALTIUM_MECHKIND::VALUE_TOP: target = F_Fab; break;
1387 case ALTIUM_MECHKIND::VALUE_BOT: target = B_Fab; break;
1388
1389 case ALTIUM_MECHKIND::DESIGNATOR_TOP: target = F_Fab; break;
1390 case ALTIUM_MECHKIND::DESIGNATOR_BOT: target = B_Fab; break;
1391
1392 case ALTIUM_MECHKIND::COMPONENT_OUTLINE_TOP: target = F_Fab; break;
1393 case ALTIUM_MECHKIND::COMPONENT_OUTLINE_BOT: target = B_Fab; break;
1394
1395 case ALTIUM_MECHKIND::COMPONENT_CENTER_TOP: target = F_Fab; break;
1396 case ALTIUM_MECHKIND::COMPONENT_CENTER_BOT: target = B_Fab; break;
1397
1398 case ALTIUM_MECHKIND::BOARD: target = Edge_Cuts; break;
1399 case ALTIUM_MECHKIND::BOARD_SHAPE: target = Edge_Cuts; break;
1400 case ALTIUM_MECHKIND::V_CUT: target = Edge_Cuts; break;
1401
1402 default: break;
1403 }
1404
1405 if( target != UNDEFINED_LAYER )
1406 m_layermap.emplace( alayer, target );
1407 }
1408 }
1409}
1410
1411
1412void ALTIUM_PCB::HelperCreateBoardOutline( const std::vector<ALTIUM_VERTICE>& aVertices )
1413{
1414 SHAPE_LINE_CHAIN lineChain;
1415 HelperShapeLineChainFromAltiumVertices( lineChain, aVertices );
1416
1417 STROKE_PARAMS stroke( m_board->GetDesignSettings().GetLineThickness( Edge_Cuts ),
1419
1420 for( int i = 0; i <= lineChain.PointCount() && i != -1; i = lineChain.NextShape( i ) )
1421 {
1422 if( lineChain.IsArcStart( i ) )
1423 {
1424 const SHAPE_ARC& currentArc = lineChain.Arc( lineChain.ArcIndex( i ) );
1425
1426 std::unique_ptr<PCB_SHAPE> shape = std::make_unique<PCB_SHAPE>( m_board, SHAPE_T::ARC );
1427
1428 shape->SetStroke( stroke );
1429 shape->SetLayer( Edge_Cuts );
1430 shape->SetArcGeometry( currentArc.GetP0(), currentArc.GetArcMid(), currentArc.GetP1() );
1431
1432 m_board->Add( shape.release(), ADD_MODE::APPEND );
1433 }
1434 else
1435 {
1436 const SEG& seg = lineChain.Segment( i );
1437
1438 std::unique_ptr<PCB_SHAPE> shape = std::make_unique<PCB_SHAPE>( m_board, SHAPE_T::SEGMENT );
1439
1440 shape->SetStroke( stroke );
1441 shape->SetLayer( Edge_Cuts );
1442 shape->SetStart( seg.A );
1443 shape->SetEnd( seg.B );
1444
1445 m_board->Add( shape.release(), ADD_MODE::APPEND );
1446 }
1447 }
1448}
1449
1450
1452 const CFB::COMPOUND_FILE_ENTRY* aEntry )
1453{
1454 if( m_progressReporter )
1455 m_progressReporter->Report( _( "Loading netclasses..." ) );
1456
1457 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
1458
1459 while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
1460 {
1461 checkpoint();
1462 ACLASS6 elem( reader );
1463
1465 {
1466 std::shared_ptr<NETCLASS> nc = std::make_shared<NETCLASS>( elem.name );
1467
1468 for( const wxString& name : elem.names )
1469 {
1470 m_board->GetDesignSettings().m_NetSettings->SetNetclassPatternAssignment(
1471 name, nc->GetName() );
1472 }
1473
1474 if( m_board->GetDesignSettings().m_NetSettings->HasNetclass( nc->GetName() ) )
1475 {
1476 // Name conflict, happens in some unknown circumstances
1477 // unique_ptr will delete nc on this code path
1478 if( m_reporter )
1479 {
1480 wxString msg;
1481 msg.Printf( _( "More than one Altium netclass with name '%s' found. "
1482 "Only the first one will be imported." ), elem.name );
1483 m_reporter->Report( msg, RPT_SEVERITY_ERROR );
1484 }
1485 }
1486 else
1487 {
1488 m_board->GetDesignSettings().m_NetSettings->SetNetclass( nc->GetName(), nc );
1489 }
1490 }
1491 }
1492
1493 if( reader.GetRemainingBytes() != 0 )
1494 THROW_IO_ERROR( wxT( "Classes6 stream is not fully parsed" ) );
1495
1496 // Now that all netclasses and pattern assignments are set up, resolve the pattern
1497 // assignments to direct netclass assignments on each net.
1498 std::shared_ptr<NET_SETTINGS> netSettings = m_board->GetDesignSettings().m_NetSettings;
1499
1500 for( NETINFO_ITEM* net : m_board->GetNetInfo() )
1501 {
1502 if( net->GetNetCode() > 0 )
1503 {
1504 std::shared_ptr<NETCLASS> netclass = netSettings->GetEffectiveNetClass( net->GetNetname() );
1505
1506 if( netclass )
1507 net->SetNetClass( netclass );
1508 }
1509 }
1510
1511 m_board->m_LegacyNetclassesLoaded = true;
1512}
1513
1514
1516 const CFB::COMPOUND_FILE_ENTRY* aEntry )
1517{
1518 if( m_progressReporter )
1519 m_progressReporter->Report( _( "Loading components..." ) );
1520
1521 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
1522
1523 while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
1524 {
1525 checkpoint();
1526 ACOMPONENT6 elem( reader );
1527
1528 std::unique_ptr<FOOTPRINT> footprint = std::make_unique<FOOTPRINT>( m_board );
1529
1530 // Altium stores the footprint library information needed to find the footprint in the
1531 // source library in the sourcefootprintlibrary field. Since Altium is a Windows-only
1532 // program, the path separator is always a backslash. We need strip the extra path information
1533 // here to prevent overly-long LIB_IDs because KiCad doesn't store the full path to the
1534 // footprint library in the design file, only in a library table.
1535 wxFileName libName( elem.sourcefootprintlibrary, wxPATH_WIN );
1536
1537 // The pattern field may also contain a path when Altium stores it with a full library path.
1538 // Extract just the footprint name portion to avoid creating invalid filenames.
1539 wxString fpName = elem.pattern;
1540
1541 if( fpName.Contains( wxT( "\\" ) ) || fpName.Contains( wxT( "/" ) ) )
1542 {
1543 wxFileName fpPath( fpName, wxPATH_WIN );
1544 fpName = fpPath.GetFullName();
1545 }
1546
1547 LIB_ID fpID = AltiumToKiCadLibID( libName.GetName(), fpName );
1548
1549 footprint->SetFPID( fpID );
1550
1551 footprint->SetPosition( elem.position );
1552 footprint->SetOrientationDegrees( elem.rotation );
1553
1554 // KiCad netlisting requires parts to have non-digit + digit annotation.
1555 // If the reference begins with a number, we prepend 'UNK' (unknown) for the source designator
1556 wxString reference = elem.sourcedesignator;
1557
1558 if( reference.find_first_not_of( "0123456789" ) == wxString::npos )
1559 reference.Prepend( wxT( "UNK" ) );
1560
1561 footprint->SetReference( reference );
1562
1564 KIID pathid( elem.sourceHierachicalPath );
1566 path.push_back( pathid );
1567 path.push_back( id );
1568
1569 footprint->SetPath( path );
1570 footprint->SetSheetname( elem.sourceHierachicalPath );
1571 footprint->SetSheetfile( elem.sourceHierachicalPath + wxT( ".kicad_sch" ));
1572
1573 footprint->SetLocked( elem.locked );
1574 footprint->Reference().SetVisible( elem.nameon );
1575 footprint->Value().SetVisible( elem.commenton );
1576 footprint->SetLayer( elem.layer == ALTIUM_LAYER::TOP_LAYER ? F_Cu : B_Cu );
1577
1578 m_components.emplace_back( footprint.get() );
1579 m_board->Add( footprint.release(), ADD_MODE::APPEND );
1580 }
1581
1582 if( reader.GetRemainingBytes() != 0 )
1583 THROW_IO_ERROR( wxT( "Components6 stream is not fully parsed" ) );
1584}
1585
1586
1588double normalizeAngleDegrees( double Angle, double aMin, double aMax )
1589{
1590 while( Angle < aMin )
1591 Angle += 360.0;
1592
1593 while( Angle >= aMax )
1594 Angle -= 360.0;
1595
1596 return Angle;
1597}
1598
1599
1601 FOOTPRINT* aFootprint,
1602 const ACOMPONENTBODY6& aElem )
1603{
1604 if( m_progressReporter )
1605 m_progressReporter->Report( _( "Loading component 3D models..." ) );
1606
1607 if( !aElem.modelIsEmbedded )
1608 return;
1609
1610 auto model = aAltiumPcbFile.GetLibModel( aElem.modelId );
1611
1612 if( !model )
1613 {
1614 if( m_reporter )
1615 {
1616 m_reporter->Report( wxString::Format( wxT( "Model %s not found for footprint %s" ),
1617 aElem.modelId, aFootprint->GetReference() ),
1619 }
1620
1621 return;
1622 }
1623
1625 file->name = aElem.modelName;
1626
1627 if( file->name.IsEmpty() )
1628 file->name = model->first.name;
1629
1630 // Decompress the model data before assigning
1631 std::vector<char> decompressedData;
1632 wxMemoryInputStream compressedStream( model->second.data(), model->second.size() );
1633 wxZlibInputStream zlibStream( compressedStream );
1634
1635 // Reserve some space, assuming decompressed data is larger -- STEP file
1636 // compression is typically 5:1 using zlib like Altium does
1637 decompressedData.resize( model->second.size() * 6 );
1638 size_t offset = 0;
1639
1640 while( !zlibStream.Eof() )
1641 {
1642 zlibStream.Read( decompressedData.data() + offset, decompressedData.size() - offset );
1643 size_t bytesRead = zlibStream.LastRead();
1644
1645 if( !bytesRead )
1646 break;
1647
1648 offset += bytesRead;
1649
1650 if( offset >= decompressedData.size() )
1651 decompressedData.resize( 2 * decompressedData.size() ); // Resizing is expensive, avoid if we can
1652 }
1653
1654 decompressedData.resize( offset );
1655
1656 file->decompressedData = std::move( decompressedData );
1658
1660 aFootprint->GetEmbeddedFiles()->AddFile( file );
1661
1662 FP_3DMODEL modelSettings;
1663
1664 modelSettings.m_Filename = aFootprint->GetEmbeddedFiles()->GetEmbeddedFileLink( *file );
1665
1666 modelSettings.m_Offset.x = pcbIUScale.IUTomm( (int) aElem.modelPosition.x );
1667 modelSettings.m_Offset.y = -pcbIUScale.IUTomm( (int) aElem.modelPosition.y );
1668 modelSettings.m_Offset.z = pcbIUScale.IUTomm( (int) aElem.modelPosition.z );
1669
1670 EDA_ANGLE orientation = aFootprint->GetOrientation();
1671
1672 if( aFootprint->IsFlipped() )
1673 {
1674 modelSettings.m_Offset.y = -modelSettings.m_Offset.y;
1675 orientation = -orientation;
1676 }
1677
1678 VECTOR3D modelRotation( aElem.modelRotation );
1679
1680 if( ( aElem.body_projection == 1 ) != aFootprint->IsFlipped() )
1681 {
1682 modelRotation.x += 180;
1683 modelRotation.z = -modelRotation.z;
1684
1685 modelSettings.m_Offset.z = -DEFAULT_BOARD_THICKNESS_MM - modelSettings.m_Offset.z;
1686 }
1687
1688 RotatePoint( &modelSettings.m_Offset.x, &modelSettings.m_Offset.y, orientation );
1689
1690 modelSettings.m_Rotation.x = normalizeAngleDegrees( -modelRotation.x, -180, 180 );
1691 modelSettings.m_Rotation.y = normalizeAngleDegrees( -modelRotation.y, -180, 180 );
1692 modelSettings.m_Rotation.z = normalizeAngleDegrees( -modelRotation.z + aElem.rotation
1693 + orientation.AsDegrees(),
1694 -180, 180 );
1695 modelSettings.m_Opacity = aElem.body_opacity_3d;
1696
1697 aFootprint->Models().push_back( modelSettings );
1698}
1699
1700
1702 const CFB::COMPOUND_FILE_ENTRY* aEntry )
1703{
1704 if( m_progressReporter )
1705 m_progressReporter->Report( _( "Loading component 3D models..." ) );
1706
1708 BS::multi_future<void> embeddedFutures;
1709
1710 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
1711
1712 while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
1713 {
1714 checkpoint();
1715 ACOMPONENTBODY6 elem( reader );
1716
1717 static const bool skipComponentBodies = ADVANCED_CFG::GetCfg().m_ImportSkipComponentBodies;
1718
1719 if( skipComponentBodies )
1720 continue;
1721
1722 if( elem.component == ALTIUM_COMPONENT_NONE )
1723 continue; // TODO: we do not support components for the board yet
1724
1725 if( m_components.size() <= elem.component )
1726 {
1727 THROW_IO_ERROR( wxString::Format( wxT( "ComponentsBodies6 stream tries to access "
1728 "component id %d of %zu existing components" ),
1729 elem.component,
1730 m_components.size() ) );
1731 }
1732
1733 if( !elem.modelIsEmbedded )
1734 continue;
1735
1736 auto modelTuple = m_EmbeddedModels.find( elem.modelId );
1737
1738 if( modelTuple == m_EmbeddedModels.end() )
1739 {
1740 if( m_reporter )
1741 {
1742 wxString msg;
1743 msg.Printf( wxT( "ComponentsBodies6 stream tries to access model id %s which does "
1744 "not exist" ), elem.modelId );
1745 m_reporter->Report( msg, RPT_SEVERITY_ERROR );
1746 }
1747
1748 continue;
1749 }
1750
1751 const ALTIUM_EMBEDDED_MODEL_DATA& modelData = modelTuple->second;
1752 FOOTPRINT* footprint = m_components.at( elem.component );
1753
1755 file->name = modelData.m_modelname;
1756
1757 wxMemoryInputStream compressedStream( modelData.m_data.data(), modelData.m_data.size() );
1758 wxZlibInputStream zlibStream( compressedStream );
1759 wxMemoryOutputStream decompressedStream;
1760
1761 zlibStream.Read( decompressedStream );
1762 file->decompressedData.resize( decompressedStream.GetSize() );
1763 decompressedStream.CopyTo( file->decompressedData.data(), file->decompressedData.size() );
1764
1765 footprint->GetEmbeddedFiles()->AddFile( file );
1766
1767 embeddedFutures.push_back( tp.submit_task(
1768 [file]()
1769 {
1770 EMBEDDED_FILES::CompressAndEncode( *file );
1771 } ) );
1772
1773 FP_3DMODEL modelSettings;
1774
1775 modelSettings.m_Filename = footprint->GetEmbeddedFiles()->GetEmbeddedFileLink( *file );
1776 VECTOR2I fpPosition = footprint->GetPosition();
1777
1778 modelSettings.m_Offset.x =
1779 pcbIUScale.IUTomm( KiROUND( elem.modelPosition.x - fpPosition.x ) );
1780 modelSettings.m_Offset.y =
1781 -pcbIUScale.IUTomm( KiROUND( elem.modelPosition.y - fpPosition.y ) );
1782 modelSettings.m_Offset.z = pcbIUScale.IUTomm( KiROUND( elem.modelPosition.z ) );
1783
1784 EDA_ANGLE orientation = footprint->GetOrientation();
1785
1786 if( footprint->IsFlipped() )
1787 {
1788 modelSettings.m_Offset.y = -modelSettings.m_Offset.y;
1789 orientation = -orientation;
1790 }
1791
1792 if( ( elem.body_projection == 1 ) != footprint->IsFlipped() )
1793 {
1794 elem.modelRotation.x += 180;
1795 elem.modelRotation.z = -elem.modelRotation.z;
1796
1797 modelSettings.m_Offset.z =
1798 -pcbIUScale.IUTomm( m_board->GetDesignSettings().GetBoardThickness() )
1799 - modelSettings.m_Offset.z;
1800 }
1801
1802 RotatePoint( &modelSettings.m_Offset.x, &modelSettings.m_Offset.y, orientation );
1803
1804 modelSettings.m_Rotation.x = normalizeAngleDegrees( -elem.modelRotation.x, -180, 180 );
1805 modelSettings.m_Rotation.y = normalizeAngleDegrees( -elem.modelRotation.y, -180, 180 );
1806 modelSettings.m_Rotation.z = normalizeAngleDegrees( -elem.modelRotation.z + elem.rotation
1807 + orientation.AsDegrees(),
1808 -180, 180 );
1809
1810 modelSettings.m_Opacity = elem.body_opacity_3d;
1811
1812 footprint->Models().push_back( modelSettings );
1813 }
1814
1815 embeddedFutures.wait();
1816
1817 if( reader.GetRemainingBytes() != 0 )
1818 THROW_IO_ERROR( wxT( "ComponentsBodies6 stream is not fully parsed" ) );
1819}
1820
1821
1823{
1824 if( aElem.referencePoint.size() != 2 )
1825 THROW_IO_ERROR( wxT( "Incorrect number of reference points for linear dimension object" ) );
1826
1827 PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
1828
1829 if( klayer == UNDEFINED_LAYER )
1830 {
1831 if( m_reporter )
1832 {
1833 m_reporter->Report( wxString::Format(
1834 _( "Dimension found on an Altium layer (%d) with no KiCad equivalent. "
1835 "It has been moved to KiCad layer Eco1_User." ), aElem.layer ),
1837 }
1838
1839 klayer = Eco1_User;
1840 }
1841
1842 VECTOR2I referencePoint0 = aElem.referencePoint.at( 0 );
1843 VECTOR2I referencePoint1 = aElem.referencePoint.at( 1 );
1844
1845 std::unique_ptr<PCB_DIM_ALIGNED> dimension = std::make_unique<PCB_DIM_ALIGNED>( m_board, PCB_DIM_ALIGNED_T );
1846
1847 dimension->SetPrecision( static_cast<DIM_PRECISION>( aElem.textprecision ) );
1848 dimension->SetLayer( klayer );
1849 dimension->SetStart( referencePoint0 );
1850
1851 if( referencePoint0 != aElem.xy1 )
1852 {
1862 VECTOR2I direction = aElem.xy1 - referencePoint0;
1863 VECTOR2I referenceDiff = referencePoint1 - referencePoint0;
1864 VECTOR2I directionNormalVector = direction.Perpendicular();
1865 SEG segm1( referencePoint0, referencePoint0 + directionNormalVector );
1866 SEG segm2( referencePoint1, referencePoint1 + direction );
1867 OPT_VECTOR2I intersection( segm1.Intersect( segm2, true, true ) );
1868
1869 if( !intersection )
1870 THROW_IO_ERROR( wxT( "Invalid dimension. This should never happen." ) );
1871
1872 dimension->SetEnd( *intersection );
1873
1874 int height = direction.EuclideanNorm();
1875
1876 if( direction.Cross( referenceDiff ) > 0 )
1877 height = -height;
1878
1879 dimension->SetHeight( height );
1880 }
1881 else
1882 {
1883 dimension->SetEnd( referencePoint1 );
1884 }
1885
1886 dimension->SetLineThickness( aElem.linewidth );
1887
1888 dimension->SetUnitsFormat( DIM_UNITS_FORMAT::NO_SUFFIX );
1889 dimension->SetPrefix( aElem.textprefix );
1890
1891
1892 int dist = ( dimension->GetEnd() - dimension->GetStart() ).EuclideanNorm();
1893
1894 if( dist < 3 * dimension->GetArrowLength() )
1895 dimension->SetArrowDirection( DIM_ARROW_DIRECTION::INWARD );
1896
1897 // Suffix normally (but not always) holds the units
1898 wxRegEx units( wxS( "(mm)|(in)|(mils)|(thou)|(')|(\")" ), wxRE_ADVANCED );
1899
1900 if( units.Matches( aElem.textsuffix ) )
1901 dimension->SetUnitsFormat( DIM_UNITS_FORMAT::BARE_SUFFIX );
1902 else
1903 dimension->SetSuffix( aElem.textsuffix );
1904
1905 dimension->SetTextThickness( aElem.textlinewidth );
1906 dimension->SetTextSize( VECTOR2I( aElem.textheight, aElem.textheight ) );
1907 dimension->SetItalic( aElem.textitalic );
1908
1909#if 0 // we don't currently support bold; map to thicker text
1910 dimension->Text().SetBold( aElem.textbold );
1911#else
1912 if( aElem.textbold )
1913 dimension->SetTextThickness( dimension->GetTextThickness() * BOLD_FACTOR );
1914#endif
1915
1916 switch( aElem.textunit )
1917 {
1918 case ALTIUM_UNIT::INCH: dimension->SetUnits( EDA_UNITS::INCH ); break;
1919 case ALTIUM_UNIT::MILS: dimension->SetUnits( EDA_UNITS::MILS ); break;
1920 case ALTIUM_UNIT::MM: dimension->SetUnits( EDA_UNITS::MM ); break;
1921 case ALTIUM_UNIT::CM: dimension->SetUnits( EDA_UNITS::MM ); break;
1922 default: break;
1923 }
1924
1925 m_board->Add( dimension.release(), ADD_MODE::APPEND );
1926}
1927
1928
1930{
1931 if( aElem.referencePoint.size() < 2 )
1932 THROW_IO_ERROR( wxT( "Not enough reference points for radial dimension object" ) );
1933
1934 PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
1935
1936 if( klayer == UNDEFINED_LAYER )
1937 {
1938 if( m_reporter )
1939 {
1940 m_reporter->Report( wxString::Format(
1941 _( "Dimension found on an Altium layer (%d) with no KiCad equivalent. "
1942 "It has been moved to KiCad layer Eco1_User." ),
1943 aElem.layer ), RPT_SEVERITY_INFO );
1944 }
1945
1946 klayer = Eco1_User;
1947 }
1948
1949 VECTOR2I referencePoint0 = aElem.referencePoint.at( 0 );
1950
1951 std::unique_ptr<PCB_DIM_RADIAL> dimension = std::make_unique<PCB_DIM_RADIAL>( m_board );
1952
1953 dimension->SetPrecision( static_cast<DIM_PRECISION>( aElem.textprecision ) );
1954 dimension->SetLayer( klayer );
1955 dimension->SetStart( referencePoint0 );
1956 dimension->SetEnd( aElem.xy1 );
1957 dimension->SetLineThickness( aElem.linewidth );
1958 dimension->SetKeepTextAligned( false );
1959
1960 dimension->SetPrefix( aElem.textprefix );
1961
1962 // Suffix normally holds the units
1963 dimension->SetUnitsFormat( aElem.textsuffix.IsEmpty() ? DIM_UNITS_FORMAT::NO_SUFFIX
1965
1966 switch( aElem.textunit )
1967 {
1968 case ALTIUM_UNIT::INCH: dimension->SetUnits( EDA_UNITS::INCH ); break;
1969 case ALTIUM_UNIT::MILS: dimension->SetUnits( EDA_UNITS::MILS ); break;
1970 case ALTIUM_UNIT::MM: dimension->SetUnits( EDA_UNITS::MM ); break;
1971 case ALTIUM_UNIT::CM: dimension->SetUnits( EDA_UNITS::MM ); break;
1972 default: break;
1973 }
1974
1975 if( aElem.textPoint.empty() )
1976 {
1977 if( m_reporter )
1978 {
1979 m_reporter->Report( wxT( "No text position present for leader dimension object" ),
1981 }
1982
1983 return;
1984 }
1985
1986 dimension->SetTextPos( aElem.textPoint.at( 0 ) );
1987 dimension->SetTextThickness( aElem.textlinewidth );
1988 dimension->SetTextSize( VECTOR2I( aElem.textheight, aElem.textheight ) );
1989 dimension->SetItalic( aElem.textitalic );
1990
1991#if 0 // we don't currently support bold; map to thicker text
1992 dimension->SetBold( aElem.textbold );
1993#else
1994 if( aElem.textbold )
1995 dimension->SetTextThickness( dimension->GetTextThickness() * BOLD_FACTOR );
1996#endif
1997
1998 // It's unclear exactly how Altium figures it's text positioning, but this gets us reasonably
1999 // close.
2000 dimension->SetVertJustify( GR_TEXT_V_ALIGN_BOTTOM );
2001 dimension->SetHorizJustify( GR_TEXT_H_ALIGN_LEFT );
2002
2003 int yAdjust = dimension->GetTextBox( nullptr ).GetCenter().y - dimension->GetTextPos().y;
2004 dimension->SetTextPos( dimension->GetTextPos() + VECTOR2I( 0, yAdjust + aElem.textgap ) );
2005 dimension->SetVertJustify( GR_TEXT_V_ALIGN_CENTER );
2006
2007 m_radialDimensions.push_back( dimension.get() );
2008 m_board->Add( dimension.release(), ADD_MODE::APPEND );
2009}
2010
2011
2013{
2014 PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
2015
2016 if( klayer == UNDEFINED_LAYER )
2017 {
2018 if( m_reporter )
2019 {
2020 wxString msg;
2021 msg.Printf( _( "Dimension found on an Altium layer (%d) with no KiCad equivalent. "
2022 "It has been moved to KiCad layer Eco1_User." ), aElem.layer );
2023 m_reporter->Report( msg, RPT_SEVERITY_ERROR );
2024 }
2025
2026 klayer = Eco1_User;
2027 }
2028
2029 if( !aElem.referencePoint.empty() )
2030 {
2031 VECTOR2I referencePoint0 = aElem.referencePoint.at( 0 );
2032
2033 // line
2034 VECTOR2I last = referencePoint0;
2035 for( size_t i = 1; i < aElem.referencePoint.size(); i++ )
2036 {
2037 std::unique_ptr<PCB_SHAPE> shape = std::make_unique<PCB_SHAPE>( m_board, SHAPE_T::SEGMENT );
2038
2039 shape->SetLayer( klayer );
2040 shape->SetStroke( STROKE_PARAMS( aElem.linewidth, LINE_STYLE::SOLID ) );
2041 shape->SetStart( last );
2042 shape->SetEnd( aElem.referencePoint.at( i ) );
2043 last = aElem.referencePoint.at( i );
2044
2045 m_board->Add( shape.release(), ADD_MODE::APPEND );
2046 }
2047
2048 // arrow
2049 if( aElem.referencePoint.size() >= 2 )
2050 {
2051 VECTOR2I dirVec = aElem.referencePoint.at( 1 ) - referencePoint0;
2052
2053 if( dirVec.x != 0 || dirVec.y != 0 )
2054 {
2055 double scaling = (double) dirVec.EuclideanNorm() / aElem.arrowsize;
2056 VECTOR2I arrVec = KiROUND( dirVec.x / scaling, dirVec.y / scaling );
2057 RotatePoint( arrVec, EDA_ANGLE( 20.0, DEGREES_T ) );
2058
2059 {
2060 std::unique_ptr<PCB_SHAPE> shape1 = std::make_unique<PCB_SHAPE>( m_board, SHAPE_T::SEGMENT );
2061
2062 shape1->SetLayer( klayer );
2063 shape1->SetStroke( STROKE_PARAMS( aElem.linewidth, LINE_STYLE::SOLID ) );
2064 shape1->SetStart( referencePoint0 );
2065 shape1->SetEnd( referencePoint0 + arrVec );
2066
2067 m_board->Add( shape1.release(), ADD_MODE::APPEND );
2068 }
2069
2070 RotatePoint( arrVec, EDA_ANGLE( -40.0, DEGREES_T ) );
2071
2072 {
2073 std::unique_ptr<PCB_SHAPE> shape2 = std::make_unique<PCB_SHAPE>( m_board, SHAPE_T::SEGMENT );
2074
2075 shape2->SetLayer( klayer );
2076 shape2->SetStroke( STROKE_PARAMS( aElem.linewidth, LINE_STYLE::SOLID ) );
2077 shape2->SetStart( referencePoint0 );
2078 shape2->SetEnd( referencePoint0 + arrVec );
2079
2080 m_board->Add( shape2.release(), ADD_MODE::APPEND );
2081 }
2082 }
2083 }
2084 }
2085
2086 if( aElem.textPoint.empty() )
2087 {
2088 if( m_reporter )
2089 {
2090 m_reporter->Report( wxT( "No text position present for leader dimension object" ),
2092 }
2093
2094 return;
2095 }
2096
2097 std::unique_ptr<PCB_TEXT> text = std::make_unique<PCB_TEXT>( m_board );
2098
2099 text->SetText( aElem.textformat );
2100 text->SetPosition( aElem.textPoint.at( 0 ) );
2101 text->SetLayer( klayer );
2102 text->SetTextSize( VECTOR2I( aElem.textheight, aElem.textheight ) ); // TODO: parse text width
2103 text->SetTextThickness( aElem.textlinewidth );
2104 text->SetHorizJustify( GR_TEXT_H_ALIGN_LEFT );
2105 text->SetVertJustify( GR_TEXT_V_ALIGN_BOTTOM );
2106
2107 m_board->Add( text.release(), ADD_MODE::APPEND );
2108}
2109
2110
2112{
2113 PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
2114
2115 if( klayer == UNDEFINED_LAYER )
2116 {
2117 if( m_reporter )
2118 {
2119 wxString msg;
2120 msg.Printf( _( "Dimension found on an Altium layer (%d) with no KiCad equivalent. "
2121 "It has been moved to KiCad layer Eco1_User." ), aElem.layer );
2122 m_reporter->Report( msg, RPT_SEVERITY_INFO );
2123 }
2124
2125 klayer = Eco1_User;
2126 }
2127
2128 for( size_t i = 0; i < aElem.referencePoint.size(); i++ )
2129 {
2130 std::unique_ptr<PCB_SHAPE> shape = std::make_unique<PCB_SHAPE>( m_board, SHAPE_T::SEGMENT );
2131
2132 shape->SetLayer( klayer );
2133 shape->SetStroke( STROKE_PARAMS( aElem.linewidth, LINE_STYLE::SOLID ) );
2134 shape->SetStart( aElem.referencePoint.at( i ) );
2135 // shape->SetEnd( /* TODO: seems to be based on TEXTY */ );
2136
2137 m_board->Add( shape.release(), ADD_MODE::APPEND );
2138 }
2139}
2140
2141
2143{
2144 PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
2145
2146 if( klayer == UNDEFINED_LAYER )
2147 {
2148 if( m_reporter )
2149 {
2150 wxString msg;
2151 msg.Printf( _( "Dimension found on an Altium layer (%d) with no KiCad equivalent. "
2152 "It has been moved to KiCad layer Eco1_User." ), aElem.layer );
2153 m_reporter->Report( msg, RPT_SEVERITY_INFO );
2154 }
2155
2156 klayer = Eco1_User;
2157 }
2158
2159 VECTOR2I vec = VECTOR2I( 0, aElem.height / 2 );
2160 RotatePoint( vec, EDA_ANGLE( aElem.angle, DEGREES_T ) );
2161
2162 std::unique_ptr<PCB_DIM_CENTER> dimension = std::make_unique<PCB_DIM_CENTER>( m_board );
2163
2164 dimension->SetLayer( klayer );
2165 dimension->SetLineThickness( aElem.linewidth );
2166 dimension->SetStart( aElem.xy1 );
2167 dimension->SetEnd( aElem.xy1 + vec );
2168
2169 m_board->Add( dimension.release(), ADD_MODE::APPEND );
2170}
2171
2172
2174 const CFB::COMPOUND_FILE_ENTRY* aEntry )
2175{
2176 if( m_progressReporter )
2177 m_progressReporter->Report( _( "Loading dimension drawings..." ) );
2178
2179 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
2180
2181 while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
2182 {
2183 checkpoint();
2184 ADIMENSION6 elem( reader );
2185
2186 switch( elem.kind )
2187 {
2190 break;
2192 if( m_reporter )
2193 {
2194 m_reporter->Report( wxString::Format( _( "Ignored Angular dimension (not yet supported)." ) ),
2196 }
2197 break;
2200 break;
2203 break;
2205 if( m_reporter )
2206 {
2207 m_reporter->Report( wxString::Format( _( "Ignored Datum dimension (not yet supported)." ) ),
2209 }
2210 // HelperParseDimensions6Datum( elem );
2211 break;
2213 if( m_reporter )
2214 {
2215 m_reporter->Report( wxString::Format( _( "Ignored Baseline dimension (not yet supported)." ) ),
2217 }
2218 break;
2221 break;
2223 if( m_reporter )
2224 {
2225 m_reporter->Report( wxString::Format( _( "Ignored Linear dimension (not yet supported)." ) ),
2227 }
2228 break;
2230 if( m_reporter )
2231 {
2232 m_reporter->Report( wxString::Format( _( "Ignored Radial dimension (not yet supported)." ) ),
2234 }
2235 break;
2236 default:
2237 if( m_reporter )
2238 {
2239 wxString msg;
2240 msg.Printf( _( "Ignored dimension of kind %d (not yet supported)." ), elem.kind );
2241 m_reporter->Report( msg, RPT_SEVERITY_INFO );
2242 }
2243 break;
2244 }
2245 }
2246
2247 if( reader.GetRemainingBytes() != 0 )
2248 THROW_IO_ERROR( wxT( "Dimensions6 stream is not fully parsed" ) );
2249}
2250
2251
2253 const CFB::COMPOUND_FILE_ENTRY* aEntry,
2254 const std::vector<std::string>& aRootDir )
2255{
2256 if( m_progressReporter )
2257 m_progressReporter->Report( _( "Loading 3D models..." ) );
2258
2259 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
2260
2261 if( reader.GetRemainingBytes() == 0 )
2262 return;
2263
2264 int idx = 0;
2265 wxString invalidChars = wxFileName::GetForbiddenChars();
2266
2267 while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
2268 {
2269 checkpoint();
2270 AMODEL elem( reader );
2271
2272 std::vector<std::string> stepPath = aRootDir;
2273 stepPath.emplace_back( std::to_string( idx ) );
2274
2275 bool validName = !elem.name.IsEmpty() && elem.name.IsAscii()
2276 && wxString::npos == elem.name.find_first_of( invalidChars );
2277 wxString storageName = validName ? elem.name : wxString::Format( wxT( "model_%d" ), idx );
2278
2279 idx++;
2280
2281 const CFB::COMPOUND_FILE_ENTRY* stepEntry = aAltiumPcbFile.FindStream( stepPath );
2282
2283 if( stepEntry == nullptr )
2284 {
2285 if( m_reporter )
2286 {
2287 wxString msg;
2288 msg.Printf( _( "File not found: '%s'. 3D-model not imported." ), FormatPath( stepPath ) );
2289 m_reporter->Report( msg, RPT_SEVERITY_ERROR );
2290 }
2291
2292 continue;
2293 }
2294
2295 size_t stepSize = static_cast<size_t>( stepEntry->size );
2296 std::vector<char> stepContent( stepSize );
2297
2298 // read file into buffer
2299 aAltiumPcbFile.GetCompoundFileReader().ReadFile( stepEntry, 0, stepContent.data(),
2300 stepSize );
2301
2302 m_EmbeddedModels.insert( std::make_pair(
2303 elem.id, ALTIUM_EMBEDDED_MODEL_DATA( storageName, elem.rotation, elem.z_offset,
2304 std::move( stepContent ) ) ) );
2305 }
2306
2307 // Append _<index> to duplicate filenames
2308 std::map<wxString, std::vector<wxString>> nameIdMap;
2309
2310 for( auto& [id, data] : m_EmbeddedModels )
2311 nameIdMap[data.m_modelname].push_back( id );
2312
2313 for( auto& [name, ids] : nameIdMap )
2314 {
2315 for( size_t i = 1; i < ids.size(); i++ )
2316 {
2317 const wxString& id = ids[i];
2318
2319 auto modelTuple = m_EmbeddedModels.find( id );
2320
2321 if( modelTuple == m_EmbeddedModels.end() )
2322 continue;
2323
2324 wxString modelName = modelTuple->second.m_modelname;
2325
2326 if( modelName.Contains( "." ) )
2327 {
2328 wxString ext;
2329 wxString baseName = modelName.BeforeLast( '.', &ext );
2330
2331 modelTuple->second.m_modelname = baseName + '_' + std::to_string( i ) + '.' + ext;
2332 }
2333 else
2334 {
2335 modelTuple->second.m_modelname = modelName + '_' + std::to_string( i );
2336 }
2337 }
2338 }
2339
2340 if( reader.GetRemainingBytes() != 0 )
2341 THROW_IO_ERROR( wxT( "Models stream is not fully parsed" ) );
2342}
2343
2344
2346 const CFB::COMPOUND_FILE_ENTRY* aEntry )
2347{
2348 if( m_progressReporter )
2349 m_progressReporter->Report( _( "Loading nets..." ) );
2350
2351 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
2352
2353 wxASSERT( m_altiumToKicadNetcodes.empty() );
2354
2355 while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
2356 {
2357 checkpoint();
2358 ANET6 elem( reader );
2359
2360 NETINFO_ITEM* netInfo = new NETINFO_ITEM( m_board, elem.name, -1 );
2361 m_board->Add( netInfo, ADD_MODE::APPEND );
2362
2363 // needs to be called after m_board->Add() as assign us the NetCode
2364 m_altiumToKicadNetcodes.push_back( netInfo->GetNetCode() );
2365 }
2366
2367 if( reader.GetRemainingBytes() != 0 )
2368 THROW_IO_ERROR( wxT( "Nets6 stream is not fully parsed" ) );
2369}
2370
2372 const CFB::COMPOUND_FILE_ENTRY* aEntry )
2373{
2374 if( m_progressReporter )
2375 m_progressReporter->Report( _( "Loading polygons..." ) );
2376
2377 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
2378
2379 while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
2380 {
2381 checkpoint();
2382 APOLYGON6 elem( reader );
2383
2384 SHAPE_LINE_CHAIN linechain;
2386
2387 if( linechain.PointCount() < 3 )
2388 {
2389 // We have found multiple Altium files with polygon records containing nothing but two
2390 // coincident vertices. These polygons do not appear when opening the file in Altium.
2391 // https://gitlab.com/kicad/code/kicad/-/issues/8183
2392 // Also, polygons with less than 3 points are not supported in KiCad.
2393 //
2394 // wxLogError( _( "Polygon has only %d point extracted from %ld vertices. At least 2 "
2395 // "points are required." ),
2396 // linechain.PointCount(),
2397 // elem.vertices.size() );
2398
2399 m_polygons.emplace_back( nullptr );
2400 continue;
2401 }
2402
2403 SHAPE_POLY_SET outline( linechain );
2404
2406 {
2407 // Altium "Hatched" or "None" polygon outlines have thickness, convert it to KiCad's representation.
2409 ARC_HIGH_DEF, true );
2410 }
2411
2412 if( outline.OutlineCount() != 1 && m_reporter )
2413 {
2414 wxString msg;
2415 msg.Printf( _( "Polygon outline count is %d, expected 1." ), outline.OutlineCount() );
2416
2417 m_reporter->Report( msg, RPT_SEVERITY_ERROR );
2418 }
2419
2420 if( outline.OutlineCount() == 0 )
2421 continue;
2422
2423 std::unique_ptr<ZONE> zone = std::make_unique<ZONE>(m_board);
2424
2425 // Be sure to set the zone layer before setting the net code
2426 // so that we know that this is a copper zone and so needs a valid net code.
2427 HelperSetZoneLayers( *zone, elem.layer );
2428 zone->SetNetCode( GetNetCode( elem.net ) );
2429 zone->SetPosition( elem.vertices.at( 0 ).position );
2430 zone->SetLocked( elem.locked );
2431 zone->SetAssignedPriority( elem.pourindex > 0 ? elem.pourindex : 0 );
2432 zone->Outline()->AddOutline( outline.Outline( 0 ) );
2433
2434 if( elem.pourindex > m_highest_pour_index )
2436
2437 const ARULE6* planeClearanceRule = GetRuleForPolygon( ALTIUM_RULE_KIND::PLANE_CLEARANCE );
2438 const ARULE6* zoneClearanceRule = GetRuleForPolygon( ALTIUM_RULE_KIND::CLEARANCE );
2439 int planeLayers = 0;
2440 int signalLayers = 0;
2441 int clearance = 0;
2442
2443 for( PCB_LAYER_ID layer : zone->GetLayerSet() )
2444 {
2445 LAYER_T layerType = m_board->GetLayerType( layer );
2446
2447 if( layerType == LT_POWER || layerType == LT_MIXED )
2448 planeLayers++;
2449
2450 if( layerType == LT_SIGNAL || layerType == LT_MIXED )
2451 signalLayers++;
2452 }
2453
2454 if( planeLayers > 0 && planeClearanceRule )
2455 clearance = std::max( clearance, planeClearanceRule->planeclearanceClearance );
2456
2457 if( signalLayers > 0 && zoneClearanceRule )
2458 clearance = std::max( clearance, zoneClearanceRule->clearanceGap );
2459
2460 if( clearance > 0 )
2461 zone->SetLocalClearance( clearance );
2462
2463 const ARULE6* polygonConnectRule = GetRuleForPolygon( ALTIUM_RULE_KIND::POLYGON_CONNECT );
2464
2465 if( polygonConnectRule != nullptr )
2466 {
2467 switch( polygonConnectRule->polygonconnectStyle )
2468 {
2470 zone->SetPadConnection( ZONE_CONNECTION::FULL );
2471 break;
2472
2474 zone->SetPadConnection( ZONE_CONNECTION::NONE );
2475 break;
2476
2477 default:
2479 zone->SetPadConnection( ZONE_CONNECTION::THERMAL );
2480 break;
2481 }
2482
2483 // TODO: correct variables?
2484 zone->SetThermalReliefSpokeWidth(
2485 polygonConnectRule->polygonconnectReliefconductorwidth );
2486 zone->SetThermalReliefGap( polygonConnectRule->polygonconnectAirgapwidth );
2487
2488 if( polygonConnectRule->polygonconnectReliefconductorwidth < zone->GetMinThickness() )
2489 zone->SetMinThickness( polygonConnectRule->polygonconnectReliefconductorwidth );
2490 }
2491
2492 if( IsAltiumLayerAPlane( elem.layer ) )
2493 {
2494 // outer zone will be set to priority 0 later.
2495 zone->SetAssignedPriority( 1 );
2496
2497 // check if this is the outer zone by simply comparing the BBOX
2498 const auto& outer_plane = m_outer_plane.find( elem.layer );
2499 if( outer_plane == m_outer_plane.end()
2500 || zone->GetBoundingBox().Contains( outer_plane->second->GetBoundingBox() ) )
2501 {
2502 m_outer_plane[elem.layer] = zone.get();
2503 }
2504 }
2505
2508 {
2509 zone->SetFillMode( ZONE_FILL_MODE::HATCH_PATTERN );
2510 zone->SetHatchThickness( elem.trackwidth );
2511
2513 {
2514 // use a small hack to get us only an outline (hopefully)
2515 const BOX2I& bbox = zone->GetBoundingBox();
2516 zone->SetHatchGap( std::max( bbox.GetHeight(), bbox.GetWidth() ) );
2517 }
2518 else
2519 {
2520 zone->SetHatchGap( elem.gridsize - elem.trackwidth );
2521 }
2522
2524 zone->SetHatchOrientation( ANGLE_45 );
2525 }
2526
2527 zone->SetBorderDisplayStyle( ZONE_BORDER_DISPLAY_STYLE::DIAGONAL_EDGE,
2529
2530 m_polygons.emplace_back( zone.get() );
2531 m_board->Add( zone.release(), ADD_MODE::APPEND );
2532 }
2533
2534 if( reader.GetRemainingBytes() != 0 )
2535 THROW_IO_ERROR( wxT( "Polygons6 stream is not fully parsed" ) );
2536}
2537
2539 const CFB::COMPOUND_FILE_ENTRY* aEntry )
2540{
2541 if( m_progressReporter )
2542 m_progressReporter->Report( _( "Loading rules..." ) );
2543
2544 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
2545
2546 while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
2547 {
2548 checkpoint();
2549 ARULE6 elem( reader );
2550
2551 m_rules[elem.kind].emplace_back( elem );
2552 }
2553
2554 // Sort by ARULE6::priority ascending. Altium priority 1 is the most specific, so the
2555 // first element after sorting is the highest-priority Altium rule.
2556 for( std::pair<const ALTIUM_RULE_KIND, std::vector<ARULE6>>& val : m_rules )
2557 {
2558 std::sort( val.second.begin(), val.second.end(),
2559 []( const ARULE6& lhs, const ARULE6& rhs )
2560 {
2561 return lhs.priority < rhs.priority;
2562 } );
2563 }
2564
2565 const ARULE6* clearanceRule = GetRuleDefault( ALTIUM_RULE_KIND::CLEARANCE );
2566 const ARULE6* trackWidthRule = GetRuleDefault( ALTIUM_RULE_KIND::WIDTH );
2567 const ARULE6* routingViasRule = GetRuleDefault( ALTIUM_RULE_KIND::ROUTING_VIAS );
2568 const ARULE6* holeSizeRule = GetRuleDefault( ALTIUM_RULE_KIND::HOLE_SIZE );
2570
2571 if( clearanceRule )
2572 m_board->GetDesignSettings().m_MinClearance = clearanceRule->clearanceGap;
2573
2574 if( trackWidthRule )
2575 {
2576 m_board->GetDesignSettings().m_TrackMinWidth = trackWidthRule->minLimit;
2577 // TODO: construct a custom rule for preferredWidth and maxLimit values
2578 }
2579
2580 if( routingViasRule )
2581 {
2582 m_board->GetDesignSettings().m_ViasMinSize = routingViasRule->minWidth;
2583 m_board->GetDesignSettings().m_MinThroughDrill = routingViasRule->minHoleWidth;
2584 }
2585
2586 if( holeSizeRule )
2587 {
2588 // TODO: construct a custom rule for minLimit / maxLimit values
2589 }
2590
2591 if( holeToHoleRule )
2592 m_board->GetDesignSettings().m_HoleToHoleMin = holeToHoleRule->clearanceGap;
2593
2596
2597 if( soldermaskRule )
2598 m_board->GetDesignSettings().m_SolderMaskExpansion = soldermaskRule->soldermaskExpansion;
2599
2600 if( pastemaskRule )
2601 m_board->GetDesignSettings().m_SolderPasteMargin = pastemaskRule->pastemaskExpansion;
2602
2603 if( reader.GetRemainingBytes() != 0 )
2604 THROW_IO_ERROR( wxT( "Rules6 stream is not fully parsed" ) );
2605}
2606
2608 const CFB::COMPOUND_FILE_ENTRY* aEntry )
2609{
2610 if( m_progressReporter )
2611 m_progressReporter->Report( _( "Loading board regions..." ) );
2612
2613 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
2614
2615 while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
2616 {
2617 checkpoint();
2618 AREGION6 elem( reader, false );
2619
2620 // TODO: implement?
2621 }
2622
2623 if( reader.GetRemainingBytes() != 0 )
2624 THROW_IO_ERROR( wxT( "BoardRegions stream is not fully parsed" ) );
2625}
2626
2628 const CFB::COMPOUND_FILE_ENTRY* aEntry )
2629{
2630 if( m_progressReporter )
2631 m_progressReporter->Report( _( "Loading polygons..." ) );
2632
2633 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
2634
2635 /* TODO: use Header section of file */
2636 for( int primitiveIndex = 0; reader.GetRemainingBytes() >= 4; primitiveIndex++ )
2637 {
2638 checkpoint();
2639 AREGION6 elem( reader, true );
2640
2643 {
2644 // TODO: implement all different types for footprints
2646 }
2647 else
2648 {
2649 FOOTPRINT* footprint = HelperGetFootprint( elem.component );
2650 ConvertShapeBasedRegions6ToFootprintItem( footprint, elem, primitiveIndex );
2651 }
2652 }
2653
2654 if( reader.GetRemainingBytes() != 0 )
2655 THROW_IO_ERROR( "ShapeBasedRegions6 stream is not fully parsed" );
2656}
2657
2658
2660{
2662 {
2664 }
2665 else if( aElem.kind == ALTIUM_REGION_KIND::POLYGON_CUTOUT || aElem.is_keepout )
2666 {
2667 SHAPE_LINE_CHAIN linechain;
2669
2670 if( linechain.PointCount() < 3 )
2671 {
2672 // We have found multiple Altium files with polygon records containing nothing but
2673 // two coincident vertices. These polygons do not appear when opening the file in
2674 // Altium. https://gitlab.com/kicad/code/kicad/-/issues/8183
2675 // Also, polygons with less than 3 points are not supported in KiCad.
2676 return;
2677 }
2678
2679 std::unique_ptr<ZONE> zone = std::make_unique<ZONE>( m_board );
2680
2681 zone->SetIsRuleArea( true );
2682
2683 if( aElem.is_keepout )
2684 {
2686 }
2687 else if( aElem.kind == ALTIUM_REGION_KIND::POLYGON_CUTOUT )
2688 {
2689 zone->SetDoNotAllowZoneFills( true );
2690 zone->SetDoNotAllowVias( false );
2691 zone->SetDoNotAllowTracks( false );
2692 zone->SetDoNotAllowPads( false );
2693 zone->SetDoNotAllowFootprints( false );
2694 }
2695
2696 zone->SetPosition( aElem.outline.at( 0 ).position );
2697 zone->Outline()->AddOutline( linechain );
2698
2699 HelperSetZoneLayers( *zone, aElem.layer );
2700
2701 zone->SetBorderDisplayStyle( ZONE_BORDER_DISPLAY_STYLE::DIAGONAL_EDGE,
2703
2704 m_board->Add( zone.release(), ADD_MODE::APPEND );
2705 }
2706 else if( aElem.is_teardrop )
2707 {
2708 SHAPE_LINE_CHAIN linechain;
2710
2711 if( linechain.PointCount() < 3 )
2712 {
2713 // Polygons with less than 3 points are not supported in KiCad.
2714 return;
2715 }
2716
2717 std::unique_ptr<ZONE> zone = std::make_unique<ZONE>( m_board );
2718
2719 zone->SetPosition( aElem.outline.at( 0 ).position );
2720 zone->Outline()->AddOutline( linechain );
2721
2722 HelperSetZoneLayers( *zone, aElem.layer );
2723 zone->SetNetCode( GetNetCode( aElem.net ) );
2724 zone->SetTeardropAreaType( TEARDROP_TYPE::TD_UNSPECIFIED );
2725 zone->SetHatchStyle( ZONE_BORDER_DISPLAY_STYLE::INVISIBLE_BORDER );
2726
2727 SHAPE_POLY_SET fill;
2728 fill.Append( linechain );
2729 fill.Fracture();
2730
2731 for( PCB_LAYER_ID klayer : GetKicadLayersToIterate( aElem.layer ) )
2732 zone->SetFilledPolysList( klayer, fill );
2733
2734 zone->SetIsFilled( true );
2735 zone->SetNeedRefill( false );
2736
2737 m_board->Add( zone.release(), ADD_MODE::APPEND );
2738 }
2739 else if( aElem.kind == ALTIUM_REGION_KIND::DASHED_OUTLINE )
2740 {
2741 PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
2742
2743 if( klayer == UNDEFINED_LAYER )
2744 {
2745 if( m_reporter )
2746 {
2747 wxString msg;
2748 msg.Printf( _( "Dashed outline found on an Altium layer (%d) with no KiCad equivalent. "
2749 "It has been moved to KiCad layer Eco1_User." ), aElem.layer );
2750 m_reporter->Report( msg, RPT_SEVERITY_ERROR );
2751 }
2752
2753 klayer = Eco1_User;
2754 }
2755
2756 SHAPE_LINE_CHAIN linechain;
2758
2759 if( linechain.PointCount() < 3 )
2760 {
2761 // We have found multiple Altium files with polygon records containing nothing but
2762 // two coincident vertices. These polygons do not appear when opening the file in
2763 // Altium. https://gitlab.com/kicad/code/kicad/-/issues/8183
2764 // Also, polygons with less than 3 points are not supported in KiCad.
2765 return;
2766 }
2767
2768 std::unique_ptr<PCB_SHAPE> shape = std::make_unique<PCB_SHAPE>( m_board, SHAPE_T::POLY );
2769
2770 shape->SetPolyShape( linechain );
2771 shape->SetFilled( false );
2772 shape->SetLayer( klayer );
2773 shape->SetStroke( STROKE_PARAMS( pcbIUScale.mmToIU( 0.1 ), LINE_STYLE::DASH ) );
2774
2775 m_board->Add( shape.release(), ADD_MODE::APPEND );
2776 }
2777 else if( aElem.kind == ALTIUM_REGION_KIND::COPPER )
2778 {
2779 if( aElem.polygon == ALTIUM_POLYGON_NONE )
2780 {
2781 for( PCB_LAYER_ID klayer : GetKicadLayersToIterate( aElem.layer ) )
2783 }
2784 }
2785 else
2786 {
2787 if( m_reporter )
2788 {
2789 wxString msg;
2790 msg.Printf( _( "Ignored polygon shape of kind %d (not yet supported)." ), aElem.kind );
2791 m_reporter->Report( msg, RPT_SEVERITY_ERROR );
2792 }
2793 }
2794}
2795
2796
2798 const AREGION6& aElem,
2799 const int aPrimitiveIndex )
2800{
2801 if( aElem.kind == ALTIUM_REGION_KIND::POLYGON_CUTOUT || aElem.is_keepout )
2802 {
2803 SHAPE_LINE_CHAIN linechain;
2805
2806 if( linechain.PointCount() < 3 )
2807 {
2808 // We have found multiple Altium files with polygon records containing nothing but
2809 // two coincident vertices. These polygons do not appear when opening the file in
2810 // Altium. https://gitlab.com/kicad/code/kicad/-/issues/8183
2811 // Also, polygons with less than 3 points are not supported in KiCad.
2812 return;
2813 }
2814
2815 std::unique_ptr<ZONE> zone = std::make_unique<ZONE>( aFootprint );
2816
2817 zone->SetIsRuleArea( true );
2818
2819 if( aElem.is_keepout )
2820 {
2822 }
2823 else if( aElem.kind == ALTIUM_REGION_KIND::POLYGON_CUTOUT )
2824 {
2825 zone->SetDoNotAllowZoneFills( true );
2826 zone->SetDoNotAllowVias( false );
2827 zone->SetDoNotAllowTracks( false );
2828 zone->SetDoNotAllowPads( false );
2829 zone->SetDoNotAllowFootprints( false );
2830 }
2831
2832 zone->SetPosition( aElem.outline.at( 0 ).position );
2833 zone->Outline()->AddOutline( linechain );
2834
2835 HelperSetZoneLayers( *zone, aElem.layer );
2836
2837 zone->SetBorderDisplayStyle( ZONE_BORDER_DISPLAY_STYLE::DIAGONAL_EDGE,
2839
2840 aFootprint->Add( zone.release(), ADD_MODE::APPEND );
2841 }
2842 else if( aElem.kind == ALTIUM_REGION_KIND::COPPER )
2843 {
2844 if( aElem.polygon == ALTIUM_POLYGON_NONE )
2845 {
2846 for( PCB_LAYER_ID klayer : GetKicadLayersToIterate( aElem.layer ) )
2847 {
2848 ConvertShapeBasedRegions6ToFootprintItemOnLayer( aFootprint, aElem, klayer,
2849 aPrimitiveIndex );
2850 }
2851 }
2852 }
2855 {
2857 ? Edge_Cuts
2858 : GetKicadLayer( aElem.layer );
2859
2860 if( klayer == UNDEFINED_LAYER )
2861 {
2862 if( !m_footprintName.IsEmpty() )
2863 {
2864 if( m_reporter )
2865 {
2866 wxString msg;
2867 msg.Printf( _( "Loading library '%s':\n"
2868 "Footprint %s contains a dashed outline on Altium layer (%d) with "
2869 "no KiCad equivalent. It has been moved to KiCad layer Eco1_User." ),
2870 m_library,
2872 aElem.layer );
2873 m_reporter->Report( msg, RPT_SEVERITY_ERROR );
2874 }
2875 }
2876 else
2877 {
2878 if( m_reporter )
2879 {
2880 wxString msg;
2881 msg.Printf( _( "Footprint %s contains a dashed outline on Altium layer (%d) with "
2882 "no KiCad equivalent. It has been moved to KiCad layer Eco1_User." ),
2883 aFootprint->GetReference(),
2884 aElem.layer );
2885 m_reporter->Report( msg, RPT_SEVERITY_ERROR );
2886 }
2887 }
2888
2889 klayer = Eco1_User;
2890 }
2891
2892 SHAPE_LINE_CHAIN linechain;
2894
2895 if( linechain.PointCount() < 3 )
2896 {
2897 // We have found multiple Altium files with polygon records containing nothing but
2898 // two coincident vertices. These polygons do not appear when opening the file in
2899 // Altium. https://gitlab.com/kicad/code/kicad/-/issues/8183
2900 // Also, polygons with less than 3 points are not supported in KiCad.
2901 return;
2902 }
2903
2904 std::unique_ptr<PCB_SHAPE> shape = std::make_unique<PCB_SHAPE>( aFootprint, SHAPE_T::POLY );
2905
2906 shape->SetPolyShape( linechain );
2907 shape->SetFilled( false );
2908 shape->SetLayer( klayer );
2909
2911 shape->SetStroke( STROKE_PARAMS( pcbIUScale.mmToIU( 0.1 ), LINE_STYLE::DASH ) );
2912 else
2913 shape->SetStroke( STROKE_PARAMS( pcbIUScale.mmToIU( 0.1 ), LINE_STYLE::SOLID ) );
2914
2915 aFootprint->Add( shape.release(), ADD_MODE::APPEND );
2916 }
2917 else
2918 {
2919 if( !m_footprintName.IsEmpty() )
2920 {
2921 if( m_reporter )
2922 {
2923 wxString msg;
2924 msg.Printf( _( "Error loading library '%s':\n"
2925 "Footprint %s contains polygon shape of kind %d (not yet supported)." ),
2926 m_library,
2928 aElem.kind );
2929 m_reporter->Report( msg, RPT_SEVERITY_ERROR );
2930 }
2931 }
2932 else
2933 {
2934 if( m_reporter )
2935 {
2936 wxString msg;
2937 msg.Printf( _( "Footprint %s contains polygon shape of kind %d (not yet supported)." ),
2938 aFootprint->GetReference(),
2939 aElem.kind );
2940 m_reporter->Report( msg, RPT_SEVERITY_ERROR );
2941 }
2942 }
2943 }
2944}
2945
2946
2948 PCB_LAYER_ID aLayer )
2949{
2950 SHAPE_LINE_CHAIN linechain;
2952
2953 if( linechain.PointCount() < 3 )
2954 {
2955 // We have found multiple Altium files with polygon records containing nothing
2956 // but two coincident vertices. These polygons do not appear when opening the
2957 // file in Altium. https://gitlab.com/kicad/code/kicad/-/issues/8183
2958 // Also, polygons with less than 3 points are not supported in KiCad.
2959 return;
2960 }
2961
2962 SHAPE_POLY_SET polySet;
2963 polySet.AddOutline( linechain );
2964
2965 for( const std::vector<ALTIUM_VERTICE>& hole : aElem.holes )
2966 {
2967 SHAPE_LINE_CHAIN hole_linechain;
2968 HelperShapeLineChainFromAltiumVertices( hole_linechain, hole );
2969
2970 if( hole_linechain.PointCount() < 3 )
2971 continue;
2972
2973 polySet.AddHole( hole_linechain );
2974 }
2975
2976 std::unique_ptr<PCB_SHAPE> shape = std::make_unique<PCB_SHAPE>( m_board, SHAPE_T::POLY );
2977
2978 shape->SetPolyShape( polySet );
2979 shape->SetFilled( true );
2980 shape->SetLayer( aLayer );
2981 shape->SetStroke( STROKE_PARAMS( 0 ) );
2982
2983 if( IsCopperLayer( aLayer ) && aElem.net != ALTIUM_NET_UNCONNECTED )
2984 {
2985 shape->SetNetCode( GetNetCode( aElem.net ) );
2986 }
2987
2988 m_board->Add( shape.release(), ADD_MODE::APPEND );
2989}
2990
2991
2993 const AREGION6& aElem,
2994 PCB_LAYER_ID aLayer,
2995 const int aPrimitiveIndex )
2996{
2997 SHAPE_LINE_CHAIN linechain;
2999
3000 if( linechain.PointCount() < 3 )
3001 {
3002 // We have found multiple Altium files with polygon records containing nothing
3003 // but two coincident vertices. These polygons do not appear when opening the
3004 // file in Altium. https://gitlab.com/kicad/code/kicad/-/issues/8183
3005 // Also, polygons with less than 3 points are not supported in KiCad.
3006 return;
3007 }
3008
3009 SHAPE_POLY_SET polySet;
3010 polySet.AddOutline( linechain );
3011
3012 for( const std::vector<ALTIUM_VERTICE>& hole : aElem.holes )
3013 {
3014 SHAPE_LINE_CHAIN hole_linechain;
3015 HelperShapeLineChainFromAltiumVertices( hole_linechain, hole );
3016
3017 if( hole_linechain.PointCount() < 3 )
3018 continue;
3019
3020 polySet.AddHole( hole_linechain );
3021 }
3022
3023 if( aLayer == F_Cu || aLayer == B_Cu )
3024 {
3025 // TODO(JE) padstacks -- not sure what should happen here yet
3026 std::unique_ptr<PAD> pad = std::make_unique<PAD>( aFootprint );
3027
3028 LSET padLayers;
3029 padLayers.set( aLayer );
3030
3031 pad->SetAttribute( PAD_ATTRIB::SMD );
3033 pad->SetThermalSpokeAngle( ANGLE_90 );
3034
3035 int anchorSize = 1;
3036 VECTOR2I anchorPos = linechain.CPoint( 0 );
3037
3038 pad->SetAnchorPadShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::CIRCLE );
3039 pad->SetSize( PADSTACK::ALL_LAYERS, { anchorSize, anchorSize } );
3040 pad->SetPosition( anchorPos );
3041
3042 SHAPE_POLY_SET shapePolys = polySet;
3043 shapePolys.Move( -anchorPos );
3044 pad->AddPrimitivePoly( PADSTACK::ALL_LAYERS, shapePolys, 0, true );
3045
3047 auto it = map.find( aPrimitiveIndex );
3048
3049 if( it != map.end() )
3050 {
3051 const AEXTENDED_PRIMITIVE_INFORMATION& info = it->second;
3052
3053 if( info.pastemaskexpansionmode == ALTIUM_MODE::MANUAL )
3054 {
3055 pad->SetLocalSolderPasteMargin( info.pastemaskexpansionmanual );
3056 }
3057
3058 if( info.soldermaskexpansionmode == ALTIUM_MODE::MANUAL )
3059 {
3060 pad->SetLocalSolderMaskMargin( info.soldermaskexpansionmanual );
3061 }
3062
3063 if( info.pastemaskexpansionmode != ALTIUM_MODE::NONE )
3064 padLayers.set( aLayer == F_Cu ? F_Paste : B_Paste );
3065
3066 if( info.soldermaskexpansionmode != ALTIUM_MODE::NONE )
3067 padLayers.set( aLayer == F_Cu ? F_Mask : B_Mask );
3068 }
3069
3070 pad->SetLayerSet( padLayers );
3071
3072 aFootprint->Add( pad.release(), ADD_MODE::APPEND );
3073 }
3074 else
3075 {
3076 std::unique_ptr<PCB_SHAPE> shape = std::make_unique<PCB_SHAPE>( aFootprint, SHAPE_T::POLY );
3077
3078 shape->SetPolyShape( polySet );
3079 shape->SetFilled( true );
3080 shape->SetLayer( aLayer );
3081 shape->SetStroke( STROKE_PARAMS( 0 ) );
3082
3083 aFootprint->Add( shape.release(), ADD_MODE::APPEND );
3084 }
3085}
3086
3087
3089 const CFB::COMPOUND_FILE_ENTRY* aEntry )
3090{
3091 if( m_progressReporter )
3092 m_progressReporter->Report( _( "Loading zone fills..." ) );
3093
3094 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
3095
3096 while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
3097 {
3098 checkpoint();
3099 AREGION6 elem( reader, false );
3100
3101 if( elem.polygon != ALTIUM_POLYGON_NONE )
3102 {
3103 if( m_polygons.size() <= elem.polygon )
3104 {
3105 THROW_IO_ERROR( wxString::Format( "Region stream tries to access polygon id %d "
3106 "of %d existing polygons.",
3107 elem.polygon,
3108 m_polygons.size() ) );
3109 }
3110
3111 ZONE* zone = m_polygons.at( elem.polygon );
3112
3113 if( zone == nullptr )
3114 {
3115 continue; // we know the zone id, but because we do not know the layer we did not
3116 // add it!
3117 }
3118
3119 PCB_LAYER_ID klayer = GetKicadLayer( elem.layer );
3120
3121 if( klayer == UNDEFINED_LAYER )
3122 continue; // Just skip it for now. Users can fill it themselves.
3123
3124 SHAPE_LINE_CHAIN linechain;
3125
3126 for( const ALTIUM_VERTICE& vertice : elem.outline )
3127 linechain.Append( vertice.position );
3128
3129 linechain.Append( elem.outline.at( 0 ).position );
3130 linechain.SetClosed( true );
3131
3132 SHAPE_POLY_SET fill;
3133 fill.AddOutline( linechain );
3134
3135 for( const std::vector<ALTIUM_VERTICE>& hole : elem.holes )
3136 {
3137 SHAPE_LINE_CHAIN hole_linechain;
3138
3139 for( const ALTIUM_VERTICE& vertice : hole )
3140 hole_linechain.Append( vertice.position );
3141
3142 hole_linechain.Append( hole.at( 0 ).position );
3143 hole_linechain.SetClosed( true );
3144 fill.AddHole( hole_linechain );
3145 }
3146
3147 if( zone->HasFilledPolysForLayer( klayer ) )
3148 fill.BooleanAdd( *zone->GetFill( klayer ) );
3149
3150 fill.Fracture();
3151
3152 zone->SetFilledPolysList( klayer, fill );
3153 zone->SetIsFilled( true );
3154 zone->SetNeedRefill( false );
3155 }
3156 }
3157
3158 if( reader.GetRemainingBytes() != 0 )
3159 THROW_IO_ERROR( wxT( "Regions6 stream is not fully parsed" ) );
3160}
3161
3162
3164 const CFB::COMPOUND_FILE_ENTRY* aEntry )
3165{
3166 if( m_progressReporter )
3167 m_progressReporter->Report( _( "Loading arcs..." ) );
3168
3169 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
3170
3171 for( int primitiveIndex = 0; reader.GetRemainingBytes() >= 4; primitiveIndex++ )
3172 {
3173 checkpoint();
3174 AARC6 elem( reader );
3175
3176 if( elem.component == ALTIUM_COMPONENT_NONE )
3177 {
3178 ConvertArcs6ToBoardItem( elem, primitiveIndex );
3179 }
3180 else
3181 {
3182 FOOTPRINT* footprint = HelperGetFootprint( elem.component );
3183 ConvertArcs6ToFootprintItem( footprint, elem, primitiveIndex, true );
3184 }
3185 }
3186
3187 if( reader.GetRemainingBytes() != 0 )
3188 THROW_IO_ERROR( "Arcs6 stream is not fully parsed" );
3189}
3190
3191
3193{
3194 if( aElem.startangle == 0. && aElem.endangle == 360. )
3195 {
3196 aShape->SetShape( SHAPE_T::CIRCLE );
3197
3198 // TODO: other variants to define circle?
3199 aShape->SetStart( aElem.center );
3200 aShape->SetEnd( aElem.center - VECTOR2I( 0, aElem.radius ) );
3201 }
3202 else
3203 {
3204 aShape->SetShape( SHAPE_T::ARC );
3205
3206 EDA_ANGLE includedAngle( aElem.endangle - aElem.startangle, DEGREES_T );
3207 EDA_ANGLE startAngle( aElem.endangle, DEGREES_T );
3208
3209 VECTOR2I startOffset = VECTOR2I( KiROUND( startAngle.Cos() * aElem.radius ),
3210 -KiROUND( startAngle.Sin() * aElem.radius ) );
3211
3212 aShape->SetCenter( aElem.center );
3213 aShape->SetStart( aElem.center + startOffset );
3214 aShape->SetArcAngleAndEnd( includedAngle.Normalize(), true );
3215 }
3216}
3217
3218
3219void ALTIUM_PCB::ConvertArcs6ToBoardItem( const AARC6& aElem, const int aPrimitiveIndex )
3220{
3221 if( aElem.polygon != ALTIUM_POLYGON_NONE && aElem.polygon != ALTIUM_POLYGON_BOARD )
3222 {
3223 if( m_polygons.size() <= aElem.polygon )
3224 {
3225 THROW_IO_ERROR( wxString::Format( "Tracks stream tries to access polygon id %u "
3226 "of %zu existing polygons.",
3227 aElem.polygon, m_polygons.size() ) );
3228 }
3229
3230 ZONE* zone = m_polygons.at( aElem.polygon );
3231
3232 if( zone == nullptr )
3233 {
3234 return; // we know the zone id, but because we do not know the layer we did not
3235 // add it!
3236 }
3237
3238 PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
3239
3240 if( klayer == UNDEFINED_LAYER )
3241 return; // Just skip it for now. Users can fill it themselves.
3242
3243 if( !zone->HasFilledPolysForLayer( klayer ) )
3244 return;
3245
3246 SHAPE_POLY_SET* fill = zone->GetFill( klayer );
3247
3248 // This is not the actual board item. We can use it to create the polygon for the region
3249 PCB_SHAPE shape( nullptr );
3250
3251 ConvertArcs6ToPcbShape( aElem, &shape );
3253
3254 shape.EDA_SHAPE::TransformShapeToPolygon( *fill, 0, ARC_HIGH_DEF, ERROR_INSIDE );
3255 // Will be simplified and fractured later
3256
3257 zone->SetIsFilled( true );
3258 zone->SetNeedRefill( false );
3259
3260 return;
3261 }
3262
3263 if( aElem.is_keepout || aElem.layer == ALTIUM_LAYER::KEEP_OUT_LAYER
3264 || IsAltiumLayerAPlane( aElem.layer ) )
3265 {
3266 // This is not the actual board item. We can use it to create the polygon for the region
3267 PCB_SHAPE shape( nullptr );
3268
3269 ConvertArcs6ToPcbShape( aElem, &shape );
3271
3273 }
3274 else
3275 {
3276 for( PCB_LAYER_ID klayer : GetKicadLayersToIterate( aElem.layer ) )
3277 ConvertArcs6ToBoardItemOnLayer( aElem, klayer );
3278 }
3279
3280 for( const auto& layerExpansionMask :
3282 {
3283 int width = aElem.width + ( layerExpansionMask.second * 2 );
3284
3285 if( width > 1 )
3286 {
3287 std::unique_ptr<PCB_SHAPE> arc = std::make_unique<PCB_SHAPE>( m_board );
3288
3289 ConvertArcs6ToPcbShape( aElem, arc.get() );
3290 arc->SetStroke( STROKE_PARAMS( width, LINE_STYLE::SOLID ) );
3291 arc->SetLayer( layerExpansionMask.first );
3292
3293 m_board->Add( arc.release(), ADD_MODE::APPEND );
3294 }
3295 }
3296}
3297
3298
3300 const int aPrimitiveIndex, const bool aIsBoardImport )
3301{
3302 if( aElem.polygon != ALTIUM_POLYGON_NONE )
3303 {
3304 wxFAIL_MSG( wxString::Format( "Altium: Unexpected footprint Arc with polygon id %d",
3305 aElem.polygon ) );
3306 return;
3307 }
3308
3309 if( aElem.is_keepout || aElem.layer == ALTIUM_LAYER::KEEP_OUT_LAYER
3310 || IsAltiumLayerAPlane( aElem.layer ) )
3311 {
3312 // This is not the actual board item. We can use it to create the polygon for the region
3313 PCB_SHAPE shape( nullptr );
3314
3315 ConvertArcs6ToPcbShape( aElem, &shape );
3317
3318 HelperPcpShapeAsFootprintKeepoutRegion( aFootprint, shape, aElem.layer,
3319 aElem.keepoutrestrictions );
3320 }
3321 else
3322 {
3323 for( PCB_LAYER_ID klayer : GetKicadLayersToIterate( aElem.layer ) )
3324 {
3325 if( aIsBoardImport && IsCopperLayer( klayer ) && aElem.net != ALTIUM_NET_UNCONNECTED )
3326 {
3327 // Special case: do to not lose net connections in footprints
3328 ConvertArcs6ToBoardItemOnLayer( aElem, klayer );
3329 }
3330 else
3331 {
3332 ConvertArcs6ToFootprintItemOnLayer( aFootprint, aElem, klayer );
3333 }
3334 }
3335 }
3336
3337 for( const auto& layerExpansionMask :
3339 {
3340 int width = aElem.width + ( layerExpansionMask.second * 2 );
3341
3342 if( width > 1 )
3343 {
3344 std::unique_ptr<PCB_SHAPE> arc = std::make_unique<PCB_SHAPE>( aFootprint );
3345
3346 ConvertArcs6ToPcbShape( aElem, arc.get() );
3347 arc->SetStroke( STROKE_PARAMS( width, LINE_STYLE::SOLID ) );
3348 arc->SetLayer( layerExpansionMask.first );
3349
3350 aFootprint->Add( arc.release(), ADD_MODE::APPEND );
3351 }
3352 }
3353}
3354
3355
3357{
3358 if( IsCopperLayer( aLayer ) && aElem.net != ALTIUM_NET_UNCONNECTED )
3359 {
3360 EDA_ANGLE includedAngle( aElem.endangle - aElem.startangle, DEGREES_T );
3361 EDA_ANGLE startAngle( aElem.endangle, DEGREES_T );
3362
3363 includedAngle.Normalize();
3364
3365 VECTOR2I startOffset = VECTOR2I( KiROUND( startAngle.Cos() * aElem.radius ),
3366 -KiROUND( startAngle.Sin() * aElem.radius ) );
3367
3368 if( includedAngle.AsDegrees() >= 0.1 )
3369 {
3370 // TODO: This is not the actual board item. We use it for now to calculate the arc points. This could be improved!
3371 PCB_SHAPE shape( nullptr, SHAPE_T::ARC );
3372
3373 shape.SetCenter( aElem.center );
3374 shape.SetStart( aElem.center + startOffset );
3375 shape.SetArcAngleAndEnd( includedAngle, true );
3376
3377 // Create actual arc
3378 SHAPE_ARC shapeArc( shape.GetCenter(), shape.GetStart(), shape.GetArcAngle(),
3379 aElem.width );
3380 std::unique_ptr<PCB_ARC> arc = std::make_unique<PCB_ARC>( m_board, &shapeArc );
3381
3382 arc->SetWidth( aElem.width );
3383 arc->SetLayer( aLayer );
3384 arc->SetNetCode( GetNetCode( aElem.net ) );
3385
3386 m_board->Add( arc.release(), ADD_MODE::APPEND );
3387 }
3388 }
3389 else
3390 {
3391 std::unique_ptr<PCB_SHAPE> arc = std::make_unique<PCB_SHAPE>(m_board);
3392
3393 ConvertArcs6ToPcbShape( aElem, arc.get() );
3394 arc->SetStroke( STROKE_PARAMS( aElem.width, LINE_STYLE::SOLID ) );
3395 arc->SetLayer( aLayer );
3396
3397 m_board->Add( arc.release(), ADD_MODE::APPEND );
3398 }
3399}
3400
3401
3403 PCB_LAYER_ID aLayer )
3404{
3405 std::unique_ptr<PCB_SHAPE> arc = std::make_unique<PCB_SHAPE>( aFootprint );
3406
3407 ConvertArcs6ToPcbShape( aElem, arc.get() );
3408 arc->SetStroke( STROKE_PARAMS( aElem.width, LINE_STYLE::SOLID ) );
3409 arc->SetLayer( aLayer );
3410
3411 aFootprint->Add( arc.release(), ADD_MODE::APPEND );
3412}
3413
3414
3416 const CFB::COMPOUND_FILE_ENTRY* aEntry )
3417{
3418 if( m_progressReporter )
3419 m_progressReporter->Report( _( "Loading pads..." ) );
3420
3421 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
3422
3423 while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
3424 {
3425 checkpoint();
3426 APAD6 elem( reader );
3427
3428 if( elem.component == ALTIUM_COMPONENT_NONE )
3429 {
3431 }
3432 else
3433 {
3434 FOOTPRINT* footprint = HelperGetFootprint( elem.component );
3435 ConvertPads6ToFootprintItem( footprint, elem );
3436 }
3437 }
3438
3439 if( reader.GetRemainingBytes() != 0 )
3440 THROW_IO_ERROR( wxT( "Pads6 stream is not fully parsed" ) );
3441}
3442
3443
3445{
3446 // It is possible to place altium pads on non-copper layers -> we need to interpolate them using drawings!
3447 if( !IsAltiumLayerCopper( aElem.layer ) && !IsAltiumLayerAPlane( aElem.layer )
3448 && aElem.layer != ALTIUM_LAYER::MULTI_LAYER )
3449 {
3451 }
3452 else
3453 {
3454 // We cannot add a pad directly into the PCB
3455 std::unique_ptr<FOOTPRINT> footprint = std::make_unique<FOOTPRINT>( m_board );
3456 footprint->SetPosition( aElem.position );
3457
3458 ConvertPads6ToFootprintItemOnCopper( footprint.get(), aElem );
3459
3460 m_board->Add( footprint.release(), ADD_MODE::APPEND );
3461 }
3462}
3463
3464
3466{
3467 std::unique_ptr<PAD> pad = std::make_unique<PAD>( aFootprint );
3468
3469 pad->SetNumber( "" );
3470 pad->SetNetCode( GetNetCode( aElem.net ) );
3471
3472 pad->SetPosition( aElem.position );
3473 pad->SetSize( PADSTACK::ALL_LAYERS, VECTOR2I( aElem.diameter, aElem.diameter ) );
3474 pad->SetDrillSize( VECTOR2I( aElem.holesize, aElem.holesize ) );
3475 pad->SetDrillShape( PAD_DRILL_SHAPE::CIRCLE );
3477 pad->SetAttribute( PAD_ATTRIB::PTH );
3478
3479 // Pads are always through holes in KiCad
3480 pad->SetLayerSet( LSET().AllCuMask() );
3481
3482 if( aElem.viamode == ALTIUM_PAD_MODE::SIMPLE )
3483 {
3484 pad->Padstack().SetMode( PADSTACK::MODE::NORMAL );
3485 }
3487 {
3488 pad->Padstack().SetMode( PADSTACK::MODE::FRONT_INNER_BACK );
3489 pad->Padstack().SetSize( VECTOR2I( aElem.diameter_by_layer[1], aElem.diameter_by_layer[1] ),
3491 }
3492 else
3493 {
3494 pad->Padstack().SetMode( PADSTACK::MODE::CUSTOM );
3495
3496 LSET cuLayers = LSET::AllCuMask();
3497
3498 if( m_board )
3499 cuLayers &= m_board->GetEnabledLayers();
3500
3501 for( PCB_LAYER_ID layer : cuLayers )
3502 {
3503 int altiumIdx = CopperLayerToOrdinal( layer );
3504
3505 if( altiumIdx < 32 )
3506 {
3507 pad->Padstack().SetSize( VECTOR2I( aElem.diameter_by_layer[altiumIdx],
3508 aElem.diameter_by_layer[altiumIdx] ), layer );
3509 }
3510 }
3511 }
3512
3513 if( aElem.is_tent_top )
3514 {
3515 pad->Padstack().FrontOuterLayers().has_solder_mask = true;
3516 }
3517 else
3518 {
3519 pad->Padstack().FrontOuterLayers().has_solder_mask = false;
3520 pad->SetLayerSet( pad->GetLayerSet().set( F_Mask ) );
3521 }
3522
3523 if( aElem.is_tent_bottom )
3524 {
3525 pad->Padstack().BackOuterLayers().has_solder_mask = true;
3526 }
3527 else
3528 {
3529 pad->Padstack().BackOuterLayers().has_solder_mask = false;
3530 pad->SetLayerSet( pad->GetLayerSet().set( B_Mask ) );
3531 }
3532
3533 if( aElem.is_locked )
3534 pad->SetLocked( true );
3535
3536 if( aElem.soldermask_expansion_manual )
3537 {
3538 pad->Padstack().FrontOuterLayers().solder_mask_margin = aElem.soldermask_expansion_front;
3539 pad->Padstack().BackOuterLayers().solder_mask_margin = aElem.soldermask_expansion_back;
3540 }
3541
3542
3543 aFootprint->Add( pad.release(), ADD_MODE::APPEND );
3544}
3545
3546
3548{
3549 // It is possible to place altium pads on non-copper layers -> we need to interpolate them using drawings!
3550 if( !IsAltiumLayerCopper( aElem.layer ) && !IsAltiumLayerAPlane( aElem.layer )
3551 && aElem.layer != ALTIUM_LAYER::MULTI_LAYER )
3552 {
3553 ConvertPads6ToFootprintItemOnNonCopper( aFootprint, aElem );
3554 }
3555 else
3556 {
3557 ConvertPads6ToFootprintItemOnCopper( aFootprint, aElem );
3558 }
3559}
3560
3561
3563{
3564 std::unique_ptr<PAD> pad = std::make_unique<PAD>( aFootprint );
3565
3566 pad->SetNumber( aElem.name );
3567 pad->SetNetCode( GetNetCode( aElem.net ) );
3568
3569 pad->SetPosition( aElem.position );
3570 pad->SetOrientationDegrees( aElem.direction );
3571 pad->SetThermalSpokeAngle( ANGLE_90 );
3572
3573 if( aElem.holesize == 0 )
3574 {
3575 pad->SetAttribute( PAD_ATTRIB::SMD );
3576 }
3577 else
3578 {
3579 if( aElem.layer != ALTIUM_LAYER::MULTI_LAYER )
3580 {
3581 // TODO: I assume other values are possible as well?
3582 if( !m_footprintName.IsEmpty() )
3583 {
3584 if( m_reporter )
3585 {
3586 wxString msg;
3587 msg.Printf( _( "Error loading library '%s':\n"
3588 "Footprint %s pad %s is not marked as multilayer, but is a TH pad." ),
3589 m_library,
3591 aElem.name );
3592 m_reporter->Report( msg, RPT_SEVERITY_ERROR );
3593 }
3594 }
3595 else
3596 {
3597 if( m_reporter )
3598 {
3599 wxString msg;
3600 msg.Printf( _( "Footprint %s pad %s is not marked as multilayer, but is a TH pad." ),
3601 aFootprint->GetReference(),
3602 aElem.name );
3603 m_reporter->Report( msg, RPT_SEVERITY_ERROR );
3604 }
3605 }
3606 }
3607
3608 pad->SetAttribute( aElem.plated ? PAD_ATTRIB::PTH : PAD_ATTRIB::NPTH );
3609
3611 {
3612 pad->SetDrillShape( PAD_DRILL_SHAPE::CIRCLE );
3613 pad->SetDrillSize( VECTOR2I( aElem.holesize, aElem.holesize ) );
3614 }
3615 else
3616 {
3617 switch( aElem.sizeAndShape->holeshape )
3618 {
3620 wxFAIL_MSG( wxT( "Round holes are handled before the switch" ) );
3621 break;
3622
3624 if( !m_footprintName.IsEmpty() )
3625 {
3626 if( m_reporter )
3627 {
3628 wxString msg;
3629 msg.Printf( _( "Loading library '%s':\n"
3630 "Footprint %s pad %s has a square hole (not yet supported)." ),
3631 m_library,
3633 aElem.name );
3634 m_reporter->Report( msg, RPT_SEVERITY_DEBUG );
3635 }
3636 }
3637 else
3638 {
3639 if( m_reporter )
3640 {
3641 wxString msg;
3642 msg.Printf( _( "Footprint %s pad %s has a square hole (not yet supported)." ),
3643 aFootprint->GetReference(),
3644 aElem.name );
3645 m_reporter->Report( msg, RPT_SEVERITY_DEBUG );
3646 }
3647 }
3648
3649 pad->SetDrillShape( PAD_DRILL_SHAPE::CIRCLE );
3650 pad->SetDrillSize( VECTOR2I( aElem.holesize, aElem.holesize ) ); // Workaround
3651 // TODO: elem.sizeAndShape->slotsize was 0 in testfile. Either use holesize in
3652 // this case or rect holes have a different id
3653 break;
3654
3656 {
3657 pad->SetDrillShape( PAD_DRILL_SHAPE::OBLONG );
3658 EDA_ANGLE slotRotation( aElem.sizeAndShape->slotrotation, DEGREES_T );
3659
3660 slotRotation.Normalize();
3661
3662 if( slotRotation.IsHorizontal() )
3663 {
3664 pad->SetDrillSize( VECTOR2I( aElem.sizeAndShape->slotsize, aElem.holesize ) );
3665 }
3666 else if( slotRotation.IsVertical() )
3667 {
3668 pad->SetDrillSize( VECTOR2I( aElem.holesize, aElem.sizeAndShape->slotsize ) );
3669 }
3670 else
3671 {
3672 if( !m_footprintName.IsEmpty() )
3673 {
3674 if( m_reporter )
3675 {
3676 wxString msg;
3677 msg.Printf( _( "Loading library '%s':\n"
3678 "Footprint %s pad %s has a hole-rotation of %d degrees. "
3679 "KiCad only supports 90 degree rotations." ),
3680 m_library,
3682 aElem.name,
3683 KiROUND( slotRotation.AsDegrees() ) );
3684 m_reporter->Report( msg, RPT_SEVERITY_DEBUG );
3685 }
3686 }
3687 else
3688 {
3689 if( m_reporter )
3690 {
3691 wxString msg;
3692 msg.Printf( _( "Footprint %s pad %s has a hole-rotation of %d degrees. "
3693 "KiCad only supports 90 degree rotations." ),
3694 aFootprint->GetReference(),
3695 aElem.name,
3696 KiROUND( slotRotation.AsDegrees() ) );
3697 m_reporter->Report( msg, RPT_SEVERITY_DEBUG );
3698 }
3699 }
3700 }
3701
3702 break;
3703 }
3704
3705 default:
3707 if( !m_footprintName.IsEmpty() )
3708 {
3709 if( m_reporter )
3710 {
3711 wxString msg;
3712 msg.Printf( _( "Error loading library '%s':\n"
3713 "Footprint %s pad %s uses a hole of unknown kind %d." ),
3714 m_library,
3716 aElem.name,
3717 aElem.sizeAndShape->holeshape );
3718 m_reporter->Report( msg, RPT_SEVERITY_DEBUG );
3719 }
3720 }
3721 else
3722 {
3723 if( m_reporter )
3724 {
3725 wxString msg;
3726 msg.Printf( _( "Footprint %s pad %s uses a hole of unknown kind %d." ),
3727 aFootprint->GetReference(),
3728 aElem.name,
3729 aElem.sizeAndShape->holeshape );
3730 m_reporter->Report( msg, RPT_SEVERITY_DEBUG );
3731 }
3732 }
3733
3734 pad->SetDrillShape( PAD_DRILL_SHAPE::CIRCLE );
3735 pad->SetDrillSize( VECTOR2I( aElem.holesize, aElem.holesize ) ); // Workaround
3736 break;
3737 }
3738 }
3739
3740 if( aElem.sizeAndShape )
3741 pad->SetOffset( PADSTACK::ALL_LAYERS, aElem.sizeAndShape->holeoffset[0] );
3742 }
3743
3744 PADSTACK& ps = pad->Padstack();
3745
3746 auto setCopperGeometry =
3747 [&]( PCB_LAYER_ID aLayer, ALTIUM_PAD_SHAPE aShape, const VECTOR2I& aSize )
3748 {
3749 int altLayer = CopperLayerToOrdinal( aLayer );
3750
3751 ps.SetSize( aSize, aLayer );
3752
3753 switch( aShape )
3754 {
3756 ps.SetShape( PAD_SHAPE::RECTANGLE, aLayer );
3757 break;
3758
3760 if( aElem.sizeAndShape
3762 {
3763 ps.SetShape( PAD_SHAPE::ROUNDRECT, aLayer ); // 100 = round, 0 = rectangular
3764 double ratio = aElem.sizeAndShape->cornerradius[altLayer] / 200.;
3765 ps.SetRoundRectRadiusRatio( ratio, aLayer );
3766 }
3767 else if( aElem.topsize.x == aElem.topsize.y )
3768 {
3769 ps.SetShape( PAD_SHAPE::CIRCLE, aLayer );
3770 }
3771 else
3772 {
3773 ps.SetShape( PAD_SHAPE::OVAL, aLayer );
3774 }
3775
3776 break;
3777
3779 ps.SetShape( PAD_SHAPE::CHAMFERED_RECT, aLayer );
3781 ps.SetChamferRatio( 0.25, aLayer );
3782 break;
3783
3785 default:
3786 if( !m_footprintName.IsEmpty() )
3787 {
3788 if( m_reporter )
3789 {
3790 wxString msg;
3791 msg.Printf( _( "Error loading library '%s':\n"
3792 "Footprint %s pad %s uses an unknown pad shape." ),
3793 m_library,
3795 aElem.name );
3796 m_reporter->Report( msg, RPT_SEVERITY_DEBUG );
3797 }
3798 }
3799 else
3800 {
3801 if( m_reporter )
3802 {
3803 wxString msg;
3804 msg.Printf( _( "Footprint %s pad %s uses an unknown pad shape." ),
3805 aFootprint->GetReference(),
3806 aElem.name );
3807 m_reporter->Report( msg, RPT_SEVERITY_DEBUG );
3808 }
3809 }
3810 break;
3811 }
3812 };
3813
3814 switch( aElem.padmode )
3815 {
3818 setCopperGeometry( PADSTACK::ALL_LAYERS, aElem.topshape, aElem.topsize );
3819 break;
3820
3823 setCopperGeometry( F_Cu, aElem.topshape, aElem.topsize );
3824 setCopperGeometry( PADSTACK::INNER_LAYERS, aElem.midshape, aElem.midsize );
3825 setCopperGeometry( B_Cu, aElem.botshape, aElem.botsize );
3826 break;
3827
3830
3831 setCopperGeometry( F_Cu, aElem.topshape, aElem.topsize );
3832 setCopperGeometry( B_Cu, aElem.botshape, aElem.botsize );
3833 setCopperGeometry( In1_Cu, aElem.midshape, aElem.midsize );
3834
3835 if( aElem.sizeAndShape )
3836 {
3837 size_t i = 0;
3838
3839 LSET intLayers = aFootprint->BoardLayerSet();
3840 intLayers &= LSET::InternalCuMask();
3841 intLayers.set( In1_Cu, false ); // Already handled above
3842
3843 for( PCB_LAYER_ID layer : intLayers )
3844 {
3845 setCopperGeometry( layer, aElem.sizeAndShape->inner_shape[i],
3846 VECTOR2I( aElem.sizeAndShape->inner_size[i].x,
3847 aElem.sizeAndShape->inner_size[i].y ) );
3848 i++;
3849 }
3850 }
3851
3852 break;
3853 }
3854
3855 switch( aElem.layer )
3856 {
3858 pad->SetLayer( F_Cu );
3859 pad->SetLayerSet( PAD::SMDMask() );
3860 break;
3861
3863 pad->SetLayer( B_Cu );
3864 pad->SetLayerSet( PAD::SMDMask().FlipStandardLayers() );
3865 break;
3866
3868 pad->SetLayerSet( aElem.plated ? PAD::PTHMask() : PAD::UnplatedHoleMask() );
3869 break;
3870
3871 default:
3872 PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
3873 pad->SetLayer( klayer );
3874 pad->SetLayerSet( LSET( { klayer } ) );
3875 break;
3876 }
3877
3879 pad->SetLocalSolderPasteMargin( aElem.pastemaskexpansionmanual );
3880
3882 pad->SetLocalSolderMaskMargin( aElem.soldermaskexpansionmanual );
3883
3884 if( aElem.is_tent_top )
3885 pad->SetLayerSet( pad->GetLayerSet().reset( F_Mask ) );
3886
3887 if( aElem.is_tent_bottom )
3888 pad->SetLayerSet( pad->GetLayerSet().reset( B_Mask ) );
3889
3890 pad->SetPadToDieLength( aElem.pad_to_die_length );
3891 pad->SetPadToDieDelay( aElem.pad_to_die_delay );
3892
3893 aFootprint->Add( pad.release(), ADD_MODE::APPEND );
3894}
3895
3896
3898{
3899 PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
3900
3901 if( klayer == UNDEFINED_LAYER )
3902 {
3903 if( m_reporter )
3904 {
3905 wxString msg;
3906 msg.Printf( _( "Non-copper pad %s found on an Altium layer (%d) with no KiCad "
3907 "equivalent. It has been moved to KiCad layer Eco1_User." ),
3908 aElem.name, aElem.layer );
3909 m_reporter->Report( msg, RPT_SEVERITY_INFO );
3910 }
3911
3912 klayer = Eco1_User;
3913 }
3914
3915 std::unique_ptr<PCB_SHAPE> pad = std::make_unique<PCB_SHAPE>( m_board );
3916
3917 HelperParsePad6NonCopper( aElem, klayer, pad.get() );
3918
3919 m_board->Add( pad.release(), ADD_MODE::APPEND );
3920}
3921
3922
3924{
3925 PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
3926
3927 if( klayer == UNDEFINED_LAYER )
3928 {
3929 if( !m_footprintName.IsEmpty() )
3930 {
3931 if( m_reporter )
3932 {
3933 wxString msg;
3934 msg.Printf( _( "Loading library '%s':\n"
3935 "Footprint %s non-copper pad %s found on an Altium layer (%d) with no "
3936 "KiCad equivalent. It has been moved to KiCad layer Eco1_User." ),
3937 m_library,
3939 aElem.name,
3940 aElem.layer );
3941 m_reporter->Report( msg, RPT_SEVERITY_INFO );
3942 }
3943 }
3944 else
3945 {
3946 if( m_reporter )
3947 {
3948 wxString msg;
3949 msg.Printf( _( "Footprint %s non-copper pad %s found on an Altium layer (%d) with no "
3950 "KiCad equivalent. It has been moved to KiCad layer Eco1_User." ),
3951 aFootprint->GetReference(),
3952 aElem.name,
3953 aElem.layer );
3954 m_reporter->Report( msg, RPT_SEVERITY_INFO );
3955 }
3956 }
3957
3958 klayer = Eco1_User;
3959 }
3960
3961 std::unique_ptr<PCB_SHAPE> pad = std::make_unique<PCB_SHAPE>( aFootprint );
3962
3963 HelperParsePad6NonCopper( aElem, klayer, pad.get() );
3964
3965 aFootprint->Add( pad.release(), ADD_MODE::APPEND );
3966}
3967
3968
3970 PCB_SHAPE* aShape )
3971{
3972 if( aElem.net != ALTIUM_NET_UNCONNECTED )
3973 {
3974 if( m_reporter )
3975 {
3976 wxString msg;
3977 msg.Printf( _( "Non-copper pad %s is connected to a net, which is not supported." ),
3978 aElem.name );
3979 m_reporter->Report( msg, RPT_SEVERITY_DEBUG );
3980 }
3981 }
3982
3983 if( aElem.holesize != 0 )
3984 {
3985 if( m_reporter )
3986 {
3987 wxString msg;
3988 msg.Printf( _( "Non-copper pad %s has a hole, which is not supported." ), aElem.name );
3989 m_reporter->Report( msg, RPT_SEVERITY_DEBUG );
3990 }
3991 }
3992
3993 if( aElem.padmode != ALTIUM_PAD_MODE::SIMPLE )
3994 {
3995 if( m_reporter )
3996 {
3997 wxString msg;
3998 msg.Printf( _( "Non-copper pad %s has a complex pad stack (not yet supported)." ),
3999 aElem.name );
4000 m_reporter->Report( msg, RPT_SEVERITY_DEBUG );
4001 }
4002 }
4003
4004 switch( aElem.topshape )
4005 {
4007 {
4008 // filled rect
4009 aShape->SetShape( SHAPE_T::POLY );
4010 aShape->SetFilled( true );
4011 aShape->SetLayer( aLayer );
4012 aShape->SetStroke( STROKE_PARAMS( 0 ) );
4013
4014 aShape->SetPolyPoints(
4015 { aElem.position + VECTOR2I( aElem.topsize.x / 2, aElem.topsize.y / 2 ),
4016 aElem.position + VECTOR2I( aElem.topsize.x / 2, -aElem.topsize.y / 2 ),
4017 aElem.position + VECTOR2I( -aElem.topsize.x / 2, -aElem.topsize.y / 2 ),
4018 aElem.position + VECTOR2I( -aElem.topsize.x / 2, aElem.topsize.y / 2 ) } );
4019
4020 if( aElem.direction != 0 )
4021 aShape->Rotate( aElem.position, EDA_ANGLE( aElem.direction, DEGREES_T ) );
4022 }
4023 break;
4024
4026 if( aElem.sizeAndShape
4028 {
4029 // filled roundrect
4030 int cornerradius = aElem.sizeAndShape->cornerradius[0];
4031 int offset = ( std::min( aElem.topsize.x, aElem.topsize.y ) * cornerradius ) / 200;
4032
4033 aShape->SetLayer( aLayer );
4034 aShape->SetStroke( STROKE_PARAMS( offset * 2, LINE_STYLE::SOLID ) );
4035
4036 if( cornerradius < 100 )
4037 {
4038 int offsetX = aElem.topsize.x / 2 - offset;
4039 int offsetY = aElem.topsize.y / 2 - offset;
4040
4041 VECTOR2I p11 = aElem.position + VECTOR2I( offsetX, offsetY );
4042 VECTOR2I p12 = aElem.position + VECTOR2I( offsetX, -offsetY );
4043 VECTOR2I p22 = aElem.position + VECTOR2I( -offsetX, -offsetY );
4044 VECTOR2I p21 = aElem.position + VECTOR2I( -offsetX, offsetY );
4045
4046 aShape->SetShape( SHAPE_T::POLY );
4047 aShape->SetFilled( true );
4048 aShape->SetPolyPoints( { p11, p12, p22, p21 } );
4049 }
4050 else if( aElem.topsize.x == aElem.topsize.y )
4051 {
4052 // circle
4053 aShape->SetShape( SHAPE_T::CIRCLE );
4054 aShape->SetFilled( true );
4055 aShape->SetStart( aElem.position );
4056 aShape->SetEnd( aElem.position - VECTOR2I( 0, aElem.topsize.x / 4 ) );
4057 aShape->SetStroke( STROKE_PARAMS( aElem.topsize.x / 2, LINE_STYLE::SOLID ) );
4058 }
4059 else if( aElem.topsize.x < aElem.topsize.y )
4060 {
4061 // short vertical line
4062 aShape->SetShape( SHAPE_T::SEGMENT );
4063 VECTOR2I pointOffset( 0, ( aElem.topsize.y / 2 - aElem.topsize.x / 2 ) );
4064 aShape->SetStart( aElem.position + pointOffset );
4065 aShape->SetEnd( aElem.position - pointOffset );
4066 }
4067 else
4068 {
4069 // short horizontal line
4070 aShape->SetShape( SHAPE_T::SEGMENT );
4071 VECTOR2I pointOffset( ( aElem.topsize.x / 2 - aElem.topsize.y / 2 ), 0 );
4072 aShape->SetStart( aElem.position + pointOffset );
4073 aShape->SetEnd( aElem.position - pointOffset );
4074 }
4075
4076 if( aElem.direction != 0 )
4077 aShape->Rotate( aElem.position, EDA_ANGLE( aElem.direction, DEGREES_T ) );
4078 }
4079 else if( aElem.topsize.x == aElem.topsize.y )
4080 {
4081 // filled circle
4082 aShape->SetShape( SHAPE_T::CIRCLE );
4083 aShape->SetFilled( true );
4084 aShape->SetLayer( aLayer );
4085 aShape->SetStart( aElem.position );
4086 aShape->SetEnd( aElem.position - VECTOR2I( 0, aElem.topsize.x / 4 ) );
4087 aShape->SetStroke( STROKE_PARAMS( aElem.topsize.x / 2, LINE_STYLE::SOLID ) );
4088 }
4089 else
4090 {
4091 // short line
4092 aShape->SetShape( SHAPE_T::SEGMENT );
4093 aShape->SetLayer( aLayer );
4094 aShape->SetStroke( STROKE_PARAMS( std::min( aElem.topsize.x, aElem.topsize.y ),
4096
4097 if( aElem.topsize.x < aElem.topsize.y )
4098 {
4099 VECTOR2I offset( 0, ( aElem.topsize.y / 2 - aElem.topsize.x / 2 ) );
4100 aShape->SetStart( aElem.position + offset );
4101 aShape->SetEnd( aElem.position - offset );
4102 }
4103 else
4104 {
4105 VECTOR2I offset( ( aElem.topsize.x / 2 - aElem.topsize.y / 2 ), 0 );
4106 aShape->SetStart( aElem.position + offset );
4107 aShape->SetEnd( aElem.position - offset );
4108 }
4109
4110 if( aElem.direction != 0 )
4111 aShape->Rotate( aElem.position, EDA_ANGLE( aElem.direction, DEGREES_T ) );
4112 }
4113 break;
4114
4116 {
4117 // filled octagon
4118 aShape->SetShape( SHAPE_T::POLY );
4119 aShape->SetFilled( true );
4120 aShape->SetLayer( aLayer );
4121 aShape->SetStroke( STROKE_PARAMS( 0 ) );
4122
4123 VECTOR2I p11 = aElem.position + VECTOR2I( aElem.topsize.x / 2, aElem.topsize.y / 2 );
4124 VECTOR2I p12 = aElem.position + VECTOR2I( aElem.topsize.x / 2, -aElem.topsize.y / 2 );
4125 VECTOR2I p22 = aElem.position + VECTOR2I( -aElem.topsize.x / 2, -aElem.topsize.y / 2 );
4126 VECTOR2I p21 = aElem.position + VECTOR2I( -aElem.topsize.x / 2, aElem.topsize.y / 2 );
4127
4128 int chamfer = std::min( aElem.topsize.x, aElem.topsize.y ) / 4;
4129 VECTOR2I chamferX( chamfer, 0 );
4130 VECTOR2I chamferY( 0, chamfer );
4131
4132 aShape->SetPolyPoints( { p11 - chamferX, p11 - chamferY, p12 + chamferY, p12 - chamferX,
4133 p22 + chamferX, p22 + chamferY, p21 - chamferY, p21 + chamferX } );
4134
4135 if( aElem.direction != 0. )
4136 aShape->Rotate( aElem.position, EDA_ANGLE( aElem.direction, DEGREES_T ) );
4137 }
4138 break;
4139
4141 default:
4142 if( m_reporter )
4143 {
4144 wxString msg;
4145 msg.Printf( _( "Non-copper pad %s uses an unknown pad shape." ), aElem.name );
4146 m_reporter->Report( msg, RPT_SEVERITY_DEBUG );
4147 }
4148
4149 break;
4150 }
4151}
4152
4153
4155 const CFB::COMPOUND_FILE_ENTRY* aEntry )
4156{
4157 if( m_progressReporter )
4158 m_progressReporter->Report( _( "Loading vias..." ) );
4159
4160 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
4161
4162 while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
4163 {
4164 checkpoint();
4165 AVIA6 elem( reader );
4166
4167 std::unique_ptr<PCB_VIA> via = std::make_unique<PCB_VIA>( m_board );
4168
4169 via->SetPosition( elem.position );
4170 via->SetDrill( elem.holesize );
4171 via->SetNetCode( GetNetCode( elem.net ) );
4172 via->SetLocked( elem.is_locked );
4173
4174 bool start_layer_outside = elem.layer_start == ALTIUM_LAYER::TOP_LAYER
4176 bool end_layer_outside = elem.layer_end == ALTIUM_LAYER::TOP_LAYER
4178
4179 if( start_layer_outside && end_layer_outside )
4180 {
4181 via->SetViaType( VIATYPE::THROUGH );
4182 }
4183 else if( ( !start_layer_outside ) && ( !end_layer_outside ) )
4184 {
4185 via->SetViaType( VIATYPE::BURIED );
4186 }
4187 else
4188 {
4189 via->SetViaType( VIATYPE::BLIND );
4190 }
4191
4192 // TODO: Altium has a specific flag for microvias, independent of start/end layer
4193#if 0
4194 if( something )
4195 via->SetViaType( VIATYPE::MICROVIA );
4196#endif
4197
4198 PCB_LAYER_ID start_klayer = GetKicadLayer( elem.layer_start );
4199 PCB_LAYER_ID end_klayer = GetKicadLayer( elem.layer_end );
4200
4201 if( !IsCopperLayer( start_klayer ) || !IsCopperLayer( end_klayer ) )
4202 {
4203 if( m_reporter )
4204 {
4205 wxString msg;
4206 msg.Printf( _( "Via from layer %d to %d uses a non-copper layer, which is not "
4207 "supported." ),
4208 elem.layer_start,
4209 elem.layer_end );
4210 m_reporter->Report( msg, RPT_SEVERITY_DEBUG );
4211 }
4212
4213 continue; // just assume through-hole instead.
4214 }
4215
4216 // we need VIATYPE set!
4217 via->SetLayerPair( start_klayer, end_klayer );
4218
4219 switch( elem.viamode )
4220 {
4221 default:
4223 via->SetWidth( PADSTACK::ALL_LAYERS, elem.diameter );
4224 break;
4225
4227 via->Padstack().SetMode( PADSTACK::MODE::FRONT_INNER_BACK );
4228 via->SetWidth( F_Cu, elem.diameter_by_layer[0] );
4229 via->SetWidth( PADSTACK::INNER_LAYERS, elem.diameter_by_layer[1] );
4230 via->SetWidth( B_Cu, elem.diameter_by_layer[31] );
4231 break;
4232
4234 {
4235 via->Padstack().SetMode( PADSTACK::MODE::CUSTOM );
4236
4237 LSET cuLayers = m_board->GetEnabledLayers() & LSET::AllCuMask();
4238
4239 for( PCB_LAYER_ID layer : cuLayers )
4240 {
4241 int altiumLayer = CopperLayerToOrdinal( layer );
4242 wxCHECK2_MSG( altiumLayer < 32, break,
4243 "Altium importer expects 32 or fewer copper layers" );
4244
4245 via->SetWidth( layer, elem.diameter_by_layer[altiumLayer] );
4246 }
4247
4248 break;
4249 }
4250 }
4251
4252 // Altium can size the solder mask opening from the hole edge instead of the via land.
4253 // KiCad vias cannot represent a hole-referenced opening, so when the resulting opening
4254 // does not clear the via land the pad copper is covered and the via is effectively tented.
4258 via->GetWidth( F_Cu ) );
4259
4260 bool tentBottom = altiumViaSideIsTented( elem.is_tent_bottom,
4264 via->GetWidth( B_Cu ) );
4265
4266 via->SetFrontTentingMode( tentTop ? TENTING_MODE::TENTED : TENTING_MODE::NOT_TENTED );
4267 via->SetBackTentingMode( tentBottom ? TENTING_MODE::TENTED : TENTING_MODE::NOT_TENTED );
4268
4269 m_board->Add( via.release(), ADD_MODE::APPEND );
4270 }
4271
4272 if( reader.GetRemainingBytes() != 0 )
4273 THROW_IO_ERROR( wxT( "Vias6 stream is not fully parsed" ) );
4274}
4275
4277 const CFB::COMPOUND_FILE_ENTRY* aEntry )
4278{
4279 if( m_progressReporter )
4280 m_progressReporter->Report( _( "Loading tracks..." ) );
4281
4282 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
4283
4284 for( int primitiveIndex = 0; reader.GetRemainingBytes() >= 4; primitiveIndex++ )
4285 {
4286 checkpoint();
4287 ATRACK6 elem( reader );
4288
4289 if( elem.component == ALTIUM_COMPONENT_NONE )
4290 {
4291 ConvertTracks6ToBoardItem( elem, primitiveIndex );
4292 }
4293 else
4294 {
4295 FOOTPRINT* footprint = HelperGetFootprint( elem.component );
4296 ConvertTracks6ToFootprintItem( footprint, elem, primitiveIndex, true );
4297 }
4298 }
4299
4300 if( reader.GetRemainingBytes() != 0 )
4301 THROW_IO_ERROR( "Tracks6 stream is not fully parsed" );
4302}
4303
4304
4305void ALTIUM_PCB::ConvertTracks6ToBoardItem( const ATRACK6& aElem, const int aPrimitiveIndex )
4306{
4307 if( aElem.polygon != ALTIUM_POLYGON_NONE && aElem.polygon != ALTIUM_POLYGON_BOARD )
4308 {
4309 if( m_polygons.size() <= aElem.polygon )
4310 {
4311 // Can happen when reading old Altium files: just skip this item
4312 if( m_reporter )
4313 {
4314 wxString msg;
4315 msg.Printf( wxT( "ATRACK6 stream tries to access polygon id %u "
4316 "of %u existing polygons; skipping it" ),
4317 static_cast<unsigned>( aElem.polygon ),
4318 static_cast<unsigned>( m_polygons.size() ) );
4319 m_reporter->Report( msg, RPT_SEVERITY_DEBUG );
4320 }
4321
4322 return;
4323 }
4324
4325 ZONE* zone = m_polygons.at( aElem.polygon );
4326
4327 if( zone == nullptr )
4328 {
4329 return; // we know the zone id, but because we do not know the layer we did not
4330 // add it!
4331 }
4332
4333 PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
4334
4335 if( klayer == UNDEFINED_LAYER )
4336 return; // Just skip it for now. Users can fill it themselves.
4337
4338 if( !zone->HasFilledPolysForLayer( klayer ) )
4339 return;
4340
4341 SHAPE_POLY_SET* fill = zone->GetFill( klayer );
4342
4343 PCB_SHAPE shape( nullptr, SHAPE_T::SEGMENT );
4344 shape.SetStart( aElem.start );
4345 shape.SetEnd( aElem.end );
4347
4348 shape.EDA_SHAPE::TransformShapeToPolygon( *fill, 0, ARC_HIGH_DEF, ERROR_INSIDE );
4349 // Will be simplified and fractured later
4350
4351 zone->SetIsFilled( true );
4352 zone->SetNeedRefill( false );
4353
4354 return;
4355 }
4356
4357 if( aElem.is_keepout || aElem.layer == ALTIUM_LAYER::KEEP_OUT_LAYER
4358 || IsAltiumLayerAPlane( aElem.layer ) )
4359 {
4360 // This is not the actual board item. We can use it to create the polygon for the region
4361 PCB_SHAPE shape( nullptr, SHAPE_T::SEGMENT );
4362 shape.SetStart( aElem.start );
4363 shape.SetEnd( aElem.end );
4365
4367 }
4368 else
4369 {
4370 for( PCB_LAYER_ID klayer : GetKicadLayersToIterate( aElem.layer ) )
4371 ConvertTracks6ToBoardItemOnLayer( aElem, klayer );
4372 }
4373
4374 for( const auto& layerExpansionMask : HelperGetSolderAndPasteMaskExpansions(
4375 ALTIUM_RECORD::TRACK, aPrimitiveIndex, aElem.layer ) )
4376 {
4377 int width = aElem.width + ( layerExpansionMask.second * 2 );
4378 if( width > 1 )
4379 {
4380 std::unique_ptr<PCB_SHAPE> seg = std::make_unique<PCB_SHAPE>( m_board, SHAPE_T::SEGMENT );
4381
4382 seg->SetStart( aElem.start );
4383 seg->SetEnd( aElem.end );
4384 seg->SetStroke( STROKE_PARAMS( width, LINE_STYLE::SOLID ) );
4385 seg->SetLayer( layerExpansionMask.first );
4386
4387 m_board->Add( seg.release(), ADD_MODE::APPEND );
4388 }
4389 }
4390}
4391
4392
4394 const int aPrimitiveIndex,
4395 const bool aIsBoardImport )
4396{
4397 if( aElem.polygon != ALTIUM_POLYGON_NONE )
4398 {
4399 wxFAIL_MSG( wxString::Format( "Altium: Unexpected footprint Track with polygon id %u",
4400 (unsigned)aElem.polygon ) );
4401 return;
4402 }
4403
4404 if( aElem.is_keepout || aElem.layer == ALTIUM_LAYER::KEEP_OUT_LAYER
4405 || IsAltiumLayerAPlane( aElem.layer ) )
4406 {
4407 // This is not the actual board item. We can use it to create the polygon for the region
4408 PCB_SHAPE shape( nullptr, SHAPE_T::SEGMENT );
4409 shape.SetStart( aElem.start );
4410 shape.SetEnd( aElem.end );
4412
4413 HelperPcpShapeAsFootprintKeepoutRegion( aFootprint, shape, aElem.layer,
4414 aElem.keepoutrestrictions );
4415 }
4416 else
4417 {
4418 for( PCB_LAYER_ID klayer : GetKicadLayersToIterate( aElem.layer ) )
4419 {
4420 if( aIsBoardImport && IsCopperLayer( klayer ) && aElem.net != ALTIUM_NET_UNCONNECTED )
4421 {
4422 // Special case: do to not lose net connections in footprints
4423 ConvertTracks6ToBoardItemOnLayer( aElem, klayer );
4424 }
4425 else
4426 {
4427 ConvertTracks6ToFootprintItemOnLayer( aFootprint, aElem, klayer );
4428 }
4429 }
4430 }
4431
4432 for( const auto& layerExpansionMask : HelperGetSolderAndPasteMaskExpansions(
4433 ALTIUM_RECORD::TRACK, aPrimitiveIndex, aElem.layer ) )
4434 {
4435 int width = aElem.width + ( layerExpansionMask.second * 2 );
4436 if( width > 1 )
4437 {
4438 std::unique_ptr<PCB_SHAPE> seg = std::make_unique<PCB_SHAPE>( aFootprint, SHAPE_T::SEGMENT );
4439
4440 seg->SetStart( aElem.start );
4441 seg->SetEnd( aElem.end );
4442 seg->SetStroke( STROKE_PARAMS( width, LINE_STYLE::SOLID ) );
4443 seg->SetLayer( layerExpansionMask.first );
4444
4445 aFootprint->Add( seg.release(), ADD_MODE::APPEND );
4446 }
4447 }
4448}
4449
4450
4452{
4453 if( IsCopperLayer( aLayer ) && aElem.net != ALTIUM_NET_UNCONNECTED )
4454 {
4455 std::unique_ptr<PCB_TRACK> track = std::make_unique<PCB_TRACK>( m_board );
4456
4457 track->SetStart( aElem.start );
4458 track->SetEnd( aElem.end );
4459 track->SetWidth( aElem.width );
4460 track->SetLayer( aLayer );
4461 track->SetNetCode( GetNetCode( aElem.net ) );
4462
4463 m_board->Add( track.release(), ADD_MODE::APPEND );
4464 }
4465 else
4466 {
4467 std::unique_ptr<PCB_SHAPE> seg = std::make_unique<PCB_SHAPE>( m_board, SHAPE_T::SEGMENT );
4468
4469 seg->SetStart( aElem.start );
4470 seg->SetEnd( aElem.end );
4471 seg->SetStroke( STROKE_PARAMS( aElem.width, LINE_STYLE::SOLID ) );
4472 seg->SetLayer( aLayer );
4473
4474 m_board->Add( seg.release(), ADD_MODE::APPEND );
4475 }
4476}
4477
4478
4480 PCB_LAYER_ID aLayer )
4481{
4482 std::unique_ptr<PCB_SHAPE> seg = std::make_unique<PCB_SHAPE>( aFootprint, SHAPE_T::SEGMENT );
4483
4484 seg->SetStart( aElem.start );
4485 seg->SetEnd( aElem.end );
4486 seg->SetStroke( STROKE_PARAMS( aElem.width, LINE_STYLE::SOLID ) );
4487 seg->SetLayer( aLayer );
4488
4489 aFootprint->Add( seg.release(), ADD_MODE::APPEND );
4490}
4491
4492
4494 const CFB::COMPOUND_FILE_ENTRY* aEntry )
4495{
4496 if( m_progressReporter )
4497 m_progressReporter->Report( _( "Loading unicode strings..." ) );
4498
4499 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
4500
4502
4503 if( reader.GetRemainingBytes() != 0 )
4504 THROW_IO_ERROR( wxT( "WideStrings6 stream is not fully parsed" ) );
4505}
4506
4508 const CFB::COMPOUND_FILE_ENTRY* aEntry )
4509{
4510 if( m_progressReporter )
4511 m_progressReporter->Report( _( "Loading text..." ) );
4512
4513 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
4514
4515 while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
4516 {
4517 checkpoint();
4518 ATEXT6 elem( reader, m_unicodeStrings );
4519
4520 if( elem.component == ALTIUM_COMPONENT_NONE )
4521 {
4523 }
4524 else
4525 {
4526 FOOTPRINT* footprint = HelperGetFootprint( elem.component );
4527 ConvertTexts6ToFootprintItem( footprint, elem );
4528 }
4529 }
4530
4531 if( reader.GetRemainingBytes() != 0 )
4532 THROW_IO_ERROR( wxT( "Texts6 stream is not fully parsed" ) );
4533}
4534
4535
4537{
4538 if( aElem.fonttype == ALTIUM_TEXT_TYPE::BARCODE )
4539 {
4540 for( PCB_LAYER_ID klayer : GetKicadLayersToIterate( aElem.layer ) )
4541 ConvertBarcodes6ToBoardItemOnLayer( aElem, klayer );
4542 return;
4543 }
4544
4545 for( PCB_LAYER_ID klayer : GetKicadLayersToIterate( aElem.layer ) )
4546 ConvertTexts6ToBoardItemOnLayer( aElem, klayer );
4547}
4548
4549
4551{
4552 if( aElem.fonttype == ALTIUM_TEXT_TYPE::BARCODE )
4553 {
4554 for( PCB_LAYER_ID klayer : GetKicadLayersToIterate( aElem.layer ) )
4555 ConvertBarcodes6ToFootprintItemOnLayer( aFootprint, aElem, klayer );
4556 return;
4557 }
4558
4559 for( PCB_LAYER_ID klayer : GetKicadLayersToIterate( aElem.layer ) )
4560 ConvertTexts6ToFootprintItemOnLayer( aFootprint, aElem, klayer );
4561}
4562
4563
4565{
4566 std::unique_ptr<PCB_TEXTBOX> pcbTextbox = std::make_unique<PCB_TEXTBOX>( m_board );
4567 std::unique_ptr<PCB_TEXT> pcbText = std::make_unique<PCB_TEXT>( m_board );
4568
4569 bool isTextbox = aElem.isFrame && !aElem.isInverted; // Textbox knockout is not supported
4570
4571 static const std::map<wxString, wxString> variableMap = {
4572 { "LAYER_NAME", "LAYER" },
4573 { "PRINT_DATE", "CURRENT_DATE"},
4574 };
4575
4576 wxString kicadText = AltiumPcbSpecialStringsToKiCadStrings( aElem.text, variableMap );
4577 BOARD_ITEM* item = pcbText.get();
4578 EDA_TEXT* text = pcbText.get();
4579
4580 if( isTextbox )
4581 {
4582 item = pcbTextbox.get();
4583 text = pcbTextbox.get();
4584
4586 HelperSetTextboxAlignmentAndPos( aElem, pcbTextbox.get() );
4587 }
4588 else
4589 {
4592 }
4593
4594 text->SetText( kicadText );
4595 item->SetLayer( aLayer );
4596 item->SetIsKnockout( aElem.isInverted );
4597
4598 if( isTextbox )
4599 m_board->Add( pcbTextbox.release(), ADD_MODE::APPEND );
4600 else
4601 m_board->Add( pcbText.release(), ADD_MODE::APPEND );
4602}
4603
4604
4606 PCB_LAYER_ID aLayer )
4607{
4608 std::unique_ptr<PCB_TEXTBOX> fpTextbox = std::make_unique<PCB_TEXTBOX>( aFootprint );
4609 std::unique_ptr<PCB_TEXT> fpText = std::make_unique<PCB_TEXT>( aFootprint );
4610
4611 BOARD_ITEM* item = fpText.get();
4612 EDA_TEXT* text = fpText.get();
4613
4614 bool isTextbox = aElem.isFrame && !aElem.isInverted; // Textbox knockout is not supported
4615 bool toAdd = false;
4616
4617 if( aElem.isDesignator )
4618 {
4619 item = &aFootprint->Reference(); // TODO: handle multiple layers
4620 text = &aFootprint->Reference();
4621 }
4622 else if( aElem.isComment )
4623 {
4624 item = &aFootprint->Value(); // TODO: handle multiple layers
4625 text = &aFootprint->Value();
4626 }
4627 else
4628 {
4629 item = fpText.get();
4630 text = fpText.get();
4631 toAdd = true;
4632 }
4633
4634 static const std::map<wxString, wxString> variableMap = {
4635 { "DESIGNATOR", "REFERENCE" },
4636 { "COMMENT", "VALUE" },
4637 { "VALUE", "ALTIUM_VALUE" },
4638 { "LAYER_NAME", "LAYER" },
4639 { "PRINT_DATE", "CURRENT_DATE"},
4640 };
4641
4642 if( isTextbox )
4643 {
4644 item = fpTextbox.get();
4645 text = fpTextbox.get();
4646
4648 HelperSetTextboxAlignmentAndPos( aElem, fpTextbox.get() );
4649 }
4650 else
4651 {
4654 }
4655
4656 wxString kicadText = AltiumPcbSpecialStringsToKiCadStrings( aElem.text, variableMap );
4657
4658 text->SetText( kicadText );
4659 text->SetKeepUpright( false );
4660 item->SetLayer( aLayer );
4661 item->SetIsKnockout( aElem.isInverted );
4662
4663 if( toAdd )
4664 {
4665 if( isTextbox )
4666 aFootprint->Add( fpTextbox.release(), ADD_MODE::APPEND );
4667 else
4668 aFootprint->Add( fpText.release(), ADD_MODE::APPEND );
4669 }
4670}
4671
4672
4674{
4675 std::unique_ptr<PCB_BARCODE> pcbBarcode = std::make_unique<PCB_BARCODE>( m_board );
4676
4677 pcbBarcode->SetLayer( aLayer );
4678 pcbBarcode->SetPosition( aElem.position );
4679 pcbBarcode->SetWidth( aElem.textbox_rect_width );
4680 pcbBarcode->SetHeight( aElem.textbox_rect_height );
4681 pcbBarcode->SetMargin( aElem.barcode_margin );
4682 pcbBarcode->SetText( aElem.text );
4683
4684 switch( aElem.barcode_type )
4685 {
4686 case ALTIUM_BARCODE_TYPE::CODE39: pcbBarcode->SetKind( BARCODE_T::CODE_39 ); break;
4687 case ALTIUM_BARCODE_TYPE::CODE128: pcbBarcode->SetKind( BARCODE_T::CODE_128 ); break;
4688 default: pcbBarcode->SetKind( BARCODE_T::CODE_39 ); break;
4689 }
4690
4691 pcbBarcode->SetIsKnockout( aElem.barcode_inverted );
4692 pcbBarcode->AssembleBarcode();
4693
4694 m_board->Add( pcbBarcode.release(), ADD_MODE::APPEND );
4695}
4696
4697
4699 PCB_LAYER_ID aLayer )
4700{
4701 std::unique_ptr<PCB_BARCODE> fpBarcode = std::make_unique<PCB_BARCODE>( aFootprint );
4702
4703 fpBarcode->SetLayer( aLayer );
4704 fpBarcode->SetPosition( aElem.position );
4705 fpBarcode->SetWidth( aElem.textbox_rect_width );
4706 fpBarcode->SetHeight( aElem.textbox_rect_height );
4707 fpBarcode->SetMargin( aElem.barcode_margin );
4708 fpBarcode->SetText( aElem.text );
4709
4710 switch( aElem.barcode_type )
4711 {
4712 case ALTIUM_BARCODE_TYPE::CODE39: fpBarcode->SetKind( BARCODE_T::CODE_39 ); break;
4713 case ALTIUM_BARCODE_TYPE::CODE128: fpBarcode->SetKind( BARCODE_T::CODE_128 ); break;
4714 default: fpBarcode->SetKind( BARCODE_T::CODE_39 ); break;
4715 }
4716
4717 fpBarcode->SetIsKnockout( aElem.barcode_inverted );
4718 fpBarcode->AssembleBarcode();
4719
4720 aFootprint->Add( fpBarcode.release(), ADD_MODE::APPEND );
4721}
4722
4723
4725{
4726 int margin = aElem.isOffsetBorder ? aElem.text_offset_width : aElem.margin_border_width;
4727
4728 // Altium textboxes do not have borders
4729 aTextbox->SetBorderEnabled( false );
4730
4731 // Calculate position
4732 VECTOR2I kposition = aElem.position;
4733
4734 if( aElem.isMirrored )
4735 kposition.x -= aElem.textbox_rect_width;
4736
4737 kposition.y -= aElem.textbox_rect_height;
4738
4739#if 0
4740 // Compensate for KiCad's textbox margin
4741 int charWidth = aTextbox->GetTextWidth();
4742 int charHeight = aTextbox->GetTextHeight();
4743
4744 VECTOR2I kicadMargin;
4745
4746 if( !aTextbox->GetFont() || aTextbox->GetFont()->IsStroke() )
4747 kicadMargin = VECTOR2I( charWidth * 0.933, charHeight * 0.67 );
4748 else
4749 kicadMargin = VECTOR2I( charWidth * 0.808, charHeight * 0.844 );
4750
4751 aTextbox->SetEnd( VECTOR2I( aElem.textbox_rect_width, aElem.textbox_rect_height )
4752 + kicadMargin * 2 - margin * 2 );
4753
4754 kposition = kposition - kicadMargin + margin;
4755#else
4756 aTextbox->SetMarginBottom( margin );
4757 aTextbox->SetMarginLeft( margin );
4758 aTextbox->SetMarginRight( margin );
4759 aTextbox->SetMarginTop( margin );
4760
4761 aTextbox->SetEnd( VECTOR2I( aElem.textbox_rect_width, aElem.textbox_rect_height ) );
4762#endif
4763
4764 RotatePoint( kposition, aElem.position, EDA_ANGLE( aElem.rotation, DEGREES_T ) );
4765
4766 aTextbox->SetPosition( kposition );
4767
4768 ALTIUM_TEXT_POSITION justification = aElem.isJustificationValid
4771
4772 switch( justification )
4773 {
4779 break;
4785 break;
4791 break;
4792 default:
4793 if( m_reporter )
4794 {
4795 wxString msg;
4796 msg.Printf( _( "Unknown textbox justification %d, aText %s" ), justification,
4797 aElem.text );
4798 m_reporter->Report( msg, RPT_SEVERITY_DEBUG );
4799 }
4800
4803 break;
4804 }
4805
4806 aTextbox->SetTextAngle( EDA_ANGLE( aElem.rotation, DEGREES_T ) );
4807}
4808
4809
4811{
4812 VECTOR2I kposition = aElem.position;
4813
4814 int margin = aElem.isOffsetBorder ? aElem.text_offset_width : aElem.margin_border_width;
4815 int rectWidth = aElem.textbox_rect_width - margin * 2;
4816 int rectHeight = aElem.height;
4817
4818 if( aElem.isMirrored )
4819 rectWidth = -rectWidth;
4820
4821 ALTIUM_TEXT_POSITION justification = aElem.isJustificationValid
4824
4825 switch( justification )
4826 {
4830
4831 kposition.y -= rectHeight;
4832 break;
4836
4837 kposition.y -= rectHeight / 2;
4838 break;
4842 break;
4846
4847 kposition.x += rectWidth / 2;
4848 kposition.y -= rectHeight;
4849 break;
4853
4854 kposition.x += rectWidth / 2;
4855 kposition.y -= rectHeight / 2;
4856 break;
4860
4861 kposition.x += rectWidth / 2;
4862 break;
4866
4867 kposition.x += rectWidth;
4868 kposition.y -= rectHeight;
4869 break;
4873
4874 kposition.x += rectWidth;
4875 kposition.y -= rectHeight / 2;
4876 break;
4880
4881 kposition.x += rectWidth;
4882 break;
4883 default:
4886 break;
4887 }
4888
4889 int charWidth = aText->GetTextWidth();
4890 int charHeight = aText->GetTextHeight();
4891
4892 // Correct for KiCad's baseline offset.
4893 // Text height and font must be set correctly before calling.
4894 if( !aText->GetFont() || aText->GetFont()->IsStroke() )
4895 {
4896 switch( aText->GetVertJustify() )
4897 {
4898 case GR_TEXT_V_ALIGN_TOP: kposition.y -= charHeight * 0.0407; break;
4899 case GR_TEXT_V_ALIGN_CENTER: kposition.y += charHeight * 0.0355; break;
4900 case GR_TEXT_V_ALIGN_BOTTOM: kposition.y += charHeight * 0.1225; break;
4901 default: break;
4902 }
4903 }
4904 else
4905 {
4906 switch( aText->GetVertJustify() )
4907 {
4908 case GR_TEXT_V_ALIGN_TOP: kposition.y -= charWidth * 0.016; break;
4909 case GR_TEXT_V_ALIGN_CENTER: kposition.y += charWidth * 0.085; break;
4910 case GR_TEXT_V_ALIGN_BOTTOM: kposition.y += charWidth * 0.17; break;
4911 default: break;
4912 }
4913 }
4914
4915 RotatePoint( kposition, aElem.position, EDA_ANGLE( aElem.rotation, DEGREES_T ) );
4916
4917 aText->SetTextPos( kposition );
4918 aText->SetTextAngle( EDA_ANGLE( aElem.rotation, DEGREES_T ) );
4919}
4920
4921
4923{
4924 aEdaText.SetTextSize( VECTOR2I( aElem.height, aElem.height ) );
4925
4927 {
4928 KIFONT::FONT* font = KIFONT::FONT::GetFont( aElem.fontname, aElem.isBold, aElem.isItalic );
4929 aEdaText.SetFont( font );
4930
4931 if( font->IsOutline() )
4932 {
4933 // TODO: why is this required? Somehow, truetype size is calculated differently
4934 if( font->GetName().Contains( wxS( "Arial" ) ) )
4935 aEdaText.SetTextSize( VECTOR2I( aElem.height * 0.63, aElem.height * 0.63 ) );
4936 else
4937 aEdaText.SetTextSize( VECTOR2I( aElem.height * 0.5, aElem.height * 0.5 ) );
4938 }
4939 }
4940
4941 aEdaText.SetTextThickness( aElem.strokewidth );
4942 aEdaText.SetBoldFlag( aElem.isBold );
4943 aEdaText.SetItalic( aElem.isItalic );
4944 aEdaText.SetMirrored( aElem.isMirrored );
4945}
4946
4947
4949 const CFB::COMPOUND_FILE_ENTRY* aEntry )
4950{
4951 if( m_progressReporter )
4952 m_progressReporter->Report( _( "Loading rectangles..." ) );
4953
4954 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
4955
4956 while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
4957 {
4958 checkpoint();
4959 AFILL6 elem( reader );
4960
4961 if( elem.component == ALTIUM_COMPONENT_NONE )
4962 {
4964 }
4965 else
4966 {
4967 FOOTPRINT* footprint = HelperGetFootprint( elem.component );
4968 ConvertFills6ToFootprintItem( footprint, elem, true );
4969 }
4970 }
4971
4972 if( reader.GetRemainingBytes() != 0 )
4973 THROW_IO_ERROR( "Fills6 stream is not fully parsed" );
4974}
4975
4976
4978{
4979 if( aElem.is_keepout || aElem.layer == ALTIUM_LAYER::KEEP_OUT_LAYER )
4980 {
4981 // This is not the actual board item. We can use it to create the polygon for the region
4982 PCB_SHAPE shape( nullptr, SHAPE_T::RECTANGLE );
4983
4984 shape.SetStart( aElem.pos1 );
4985 shape.SetEnd( aElem.pos2 );
4986 shape.SetFilled( true );
4988
4989 if( aElem.rotation != 0. )
4990 {
4991 VECTOR2I center( aElem.pos1.x / 2 + aElem.pos2.x / 2,
4992 aElem.pos1.y / 2 + aElem.pos2.y / 2 );
4993 shape.Rotate( center, EDA_ANGLE( aElem.rotation, DEGREES_T ) );
4994 }
4995
4997 }
4998 else
4999 {
5000 for( PCB_LAYER_ID klayer : GetKicadLayersToIterate( aElem.layer ) )
5001 ConvertFills6ToBoardItemOnLayer( aElem, klayer );
5002 }
5003}
5004
5005
5007 const bool aIsBoardImport )
5008{
5009 if( aElem.is_keepout
5010 || aElem.layer == ALTIUM_LAYER::KEEP_OUT_LAYER ) // TODO: what about plane layers?
5011 {
5012 // This is not the actual board item. We can use it to create the polygon for the region
5013 PCB_SHAPE shape( nullptr, SHAPE_T::RECTANGLE );
5014
5015 shape.SetStart( aElem.pos1 );
5016 shape.SetEnd( aElem.pos2 );
5017 shape.SetFilled( true );
5019
5020 if( aElem.rotation != 0. )
5021 {
5022 VECTOR2I center( aElem.pos1.x / 2 + aElem.pos2.x / 2,
5023 aElem.pos1.y / 2 + aElem.pos2.y / 2 );
5024 shape.Rotate( center, EDA_ANGLE( aElem.rotation, DEGREES_T ) );
5025 }
5026
5027 HelperPcpShapeAsFootprintKeepoutRegion( aFootprint, shape, aElem.layer,
5028 aElem.keepoutrestrictions );
5029 }
5030 else if( aIsBoardImport && IsAltiumLayerCopper( aElem.layer )
5031 && aElem.net != ALTIUM_NET_UNCONNECTED )
5032 {
5033 // Special case: do to not lose net connections in footprints
5034 for( PCB_LAYER_ID klayer : GetKicadLayersToIterate( aElem.layer ) )
5035 ConvertFills6ToBoardItemOnLayer( aElem, klayer );
5036 }
5037 else
5038 {
5039 for( PCB_LAYER_ID klayer : GetKicadLayersToIterate( aElem.layer ) )
5040 ConvertFills6ToFootprintItemOnLayer( aFootprint, aElem, klayer );
5041 }
5042}
5043
5044
5046{
5047 std::unique_ptr<PCB_SHAPE> fill = std::make_unique<PCB_SHAPE>( m_board, SHAPE_T::RECTANGLE );
5048
5049 fill->SetFilled( true );
5050 fill->SetLayer( aLayer );
5051 fill->SetStroke( STROKE_PARAMS( 0 ) );
5052
5053 fill->SetStart( aElem.pos1 );
5054 fill->SetEnd( aElem.pos2 );
5055
5056 if( IsCopperLayer( aLayer ) && aElem.net != ALTIUM_NET_UNCONNECTED )
5057 {
5058 fill->SetNetCode( GetNetCode( aElem.net ) );
5059 }
5060
5061 if( aElem.rotation != 0. )
5062 {
5063 // TODO: Do we need SHAPE_T::POLY for non 90° rotations?
5064 VECTOR2I center( aElem.pos1.x / 2 + aElem.pos2.x / 2,
5065 aElem.pos1.y / 2 + aElem.pos2.y / 2 );
5066 fill->Rotate( center, EDA_ANGLE( aElem.rotation, DEGREES_T ) );
5067 }
5068
5069 m_board->Add( fill.release(), ADD_MODE::APPEND );
5070}
5071
5072
5074 PCB_LAYER_ID aLayer )
5075{
5076 if( aLayer == F_Cu || aLayer == B_Cu )
5077 {
5078 std::unique_ptr<PAD> pad = std::make_unique<PAD>( aFootprint );
5079
5080 LSET padLayers;
5081 padLayers.set( aLayer );
5082
5083 pad->SetAttribute( PAD_ATTRIB::SMD );
5084 EDA_ANGLE rotation( aElem.rotation, DEGREES_T );
5085
5086 // Handle rotation multiples of 90 degrees
5087 if( rotation.IsCardinal() )
5088 {
5090
5091 int width = std::abs( aElem.pos2.x - aElem.pos1.x );
5092 int height = std::abs( aElem.pos2.y - aElem.pos1.y );
5093
5094 // Swap width and height for 90 or 270 degree rotations
5095 if( rotation.IsCardinal90() )
5096 std::swap( width, height );
5097
5098 pad->SetSize( PADSTACK::ALL_LAYERS, { width, height } );
5099 pad->SetPosition( aElem.pos1 / 2 + aElem.pos2 / 2 );
5100 }
5101 else
5102 {
5104
5105 int anchorSize = std::min( std::abs( aElem.pos2.x - aElem.pos1.x ),
5106 std::abs( aElem.pos2.y - aElem.pos1.y ) );
5107 VECTOR2I anchorPos = aElem.pos1;
5108
5109 pad->SetAnchorPadShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::CIRCLE );
5110 pad->SetSize( PADSTACK::ALL_LAYERS, { anchorSize, anchorSize } );
5111 pad->SetPosition( anchorPos );
5112
5113 SHAPE_POLY_SET shapePolys;
5114 shapePolys.NewOutline();
5115 shapePolys.Append( aElem.pos1.x - anchorPos.x, aElem.pos1.y - anchorPos.y );
5116 shapePolys.Append( aElem.pos2.x - anchorPos.x, aElem.pos1.y - anchorPos.y );
5117 shapePolys.Append( aElem.pos2.x - anchorPos.x, aElem.pos2.y - anchorPos.y );
5118 shapePolys.Append( aElem.pos1.x - anchorPos.x, aElem.pos2.y - anchorPos.y );
5119 shapePolys.Outline( 0 ).SetClosed( true );
5120
5121 VECTOR2I center( aElem.pos1.x / 2 + aElem.pos2.x / 2 - anchorPos.x,
5122 aElem.pos1.y / 2 + aElem.pos2.y / 2 - anchorPos.y );
5123 shapePolys.Rotate( EDA_ANGLE( aElem.rotation, DEGREES_T ), center );
5124 pad->AddPrimitivePoly( F_Cu, shapePolys, 0, true );
5125 }
5126
5127 pad->SetThermalSpokeAngle( ANGLE_90 );
5128 pad->SetLayerSet( padLayers );
5129
5130 aFootprint->Add( pad.release(), ADD_MODE::APPEND );
5131 }
5132 else
5133 {
5134 std::unique_ptr<PCB_SHAPE> fill =
5135 std::make_unique<PCB_SHAPE>( aFootprint, SHAPE_T::RECTANGLE );
5136
5137 fill->SetFilled( true );
5138 fill->SetLayer( aLayer );
5139 fill->SetStroke( STROKE_PARAMS( 0 ) );
5140
5141 fill->SetStart( aElem.pos1 );
5142 fill->SetEnd( aElem.pos2 );
5143
5144 if( aElem.rotation != 0. )
5145 {
5146 VECTOR2I center( aElem.pos1.x / 2 + aElem.pos2.x / 2,
5147 aElem.pos1.y / 2 + aElem.pos2.y / 2 );
5148 fill->Rotate( center, EDA_ANGLE( aElem.rotation, DEGREES_T ) );
5149 }
5150
5151 aFootprint->Add( fill.release(), ADD_MODE::APPEND );
5152 }
5153}
5154
5155
5156void ALTIUM_PCB::HelperSetZoneLayers( ZONE& aZone, const ALTIUM_LAYER aAltiumLayer )
5157{
5158 LSET layerSet;
5159
5160 for( PCB_LAYER_ID klayer : GetKicadLayersToIterate( aAltiumLayer ) )
5161 layerSet.set( klayer );
5162
5163 aZone.SetLayerSet( layerSet );
5164}
5165
5166
5167void ALTIUM_PCB::HelperSetZoneKeepoutRestrictions( ZONE& aZone, const uint8_t aKeepoutRestrictions )
5168{
5169 bool keepoutRestrictionVia = ( aKeepoutRestrictions & 0x01 ) != 0;
5170 bool keepoutRestrictionTrack = ( aKeepoutRestrictions & 0x02 ) != 0;
5171 bool keepoutRestrictionCopper = ( aKeepoutRestrictions & 0x04 ) != 0;
5172 bool keepoutRestrictionSMDPad = ( aKeepoutRestrictions & 0x08 ) != 0;
5173 bool keepoutRestrictionTHPad = ( aKeepoutRestrictions & 0x10 ) != 0;
5174
5175 aZone.SetDoNotAllowVias( keepoutRestrictionVia );
5176 aZone.SetDoNotAllowTracks( keepoutRestrictionTrack );
5177 aZone.SetDoNotAllowZoneFills( keepoutRestrictionCopper );
5178 aZone.SetDoNotAllowPads( keepoutRestrictionSMDPad && keepoutRestrictionTHPad );
5179 aZone.SetDoNotAllowFootprints( false );
5180}
5181
5182
5184 const ALTIUM_LAYER aAltiumLayer,
5185 const uint8_t aKeepoutRestrictions )
5186{
5187 std::unique_ptr<ZONE> zone = std::make_unique<ZONE>( m_board );
5188
5189 zone->SetIsRuleArea( true );
5190
5191 HelperSetZoneLayers( *zone, aAltiumLayer );
5192 HelperSetZoneKeepoutRestrictions( *zone, aKeepoutRestrictions );
5193
5194 aShape.EDA_SHAPE::TransformShapeToPolygon( *zone->Outline(), 0, ARC_HIGH_DEF, ERROR_INSIDE );
5195
5196 zone->SetBorderDisplayStyle( ZONE_BORDER_DISPLAY_STYLE::DIAGONAL_EDGE,
5198
5199 m_board->Add( zone.release(), ADD_MODE::APPEND );
5200}
5201
5202
5204 const PCB_SHAPE& aShape,
5205 const ALTIUM_LAYER aAltiumLayer,
5206 const uint8_t aKeepoutRestrictions )
5207{
5208 std::unique_ptr<ZONE> zone = std::make_unique<ZONE>( aFootprint );
5209
5210 zone->SetIsRuleArea( true );
5211
5212 HelperSetZoneLayers( *zone, aAltiumLayer );
5213 HelperSetZoneKeepoutRestrictions( *zone, aKeepoutRestrictions );
5214
5215 aShape.EDA_SHAPE::TransformShapeToPolygon( *zone->Outline(), 0, ARC_HIGH_DEF, ERROR_INSIDE );
5216
5217 zone->SetBorderDisplayStyle( ZONE_BORDER_DISPLAY_STYLE::DIAGONAL_EDGE,
5219
5220 // TODO: zone->SetLocalCoord(); missing?
5221 aFootprint->Add( zone.release(), ADD_MODE::APPEND );
5222}
5223
5224
5225std::vector<std::pair<PCB_LAYER_ID, int>> ALTIUM_PCB::HelperGetSolderAndPasteMaskExpansions(
5226 const ALTIUM_RECORD aType, const int aPrimitiveIndex, const ALTIUM_LAYER aAltiumLayer )
5227{
5228 if( m_extendedPrimitiveInformationMaps.count( aType ) == 0 )
5229 return {}; // there is nothing to parse
5230
5231 auto elems =
5232 m_extendedPrimitiveInformationMaps[ALTIUM_RECORD::TRACK].equal_range( aPrimitiveIndex );
5233
5234 if( elems.first == elems.second )
5235 return {}; // there is nothing to parse
5236
5237 std::vector<std::pair<PCB_LAYER_ID, int>> layerExpansionPairs;
5238
5239 for( auto it = elems.first; it != elems.second; ++it )
5240 {
5241 const AEXTENDED_PRIMITIVE_INFORMATION& pInf = it->second;
5242
5244 {
5247 {
5248 // TODO: what layers can lead to solder or paste mask usage? E.g. KEEP_OUT_LAYER and other top/bottom layers
5249 if( aAltiumLayer == ALTIUM_LAYER::TOP_LAYER
5250 || aAltiumLayer == ALTIUM_LAYER::MULTI_LAYER )
5251 {
5252 layerExpansionPairs.emplace_back( F_Mask, pInf.soldermaskexpansionmanual );
5253 }
5254
5255 if( aAltiumLayer == ALTIUM_LAYER::BOTTOM_LAYER
5256 || aAltiumLayer == ALTIUM_LAYER::MULTI_LAYER )
5257 {
5258 layerExpansionPairs.emplace_back( B_Mask, pInf.soldermaskexpansionmanual );
5259 }
5260 }
5263 {
5264 if( aAltiumLayer == ALTIUM_LAYER::TOP_LAYER
5265 || aAltiumLayer == ALTIUM_LAYER::MULTI_LAYER )
5266 {
5267 layerExpansionPairs.emplace_back( F_Paste, pInf.pastemaskexpansionmanual );
5268 }
5269
5270 if( aAltiumLayer == ALTIUM_LAYER::BOTTOM_LAYER
5271 || aAltiumLayer == ALTIUM_LAYER::MULTI_LAYER )
5272 {
5273 layerExpansionPairs.emplace_back( B_Paste, pInf.pastemaskexpansionmanual );
5274 }
5275 }
5276 }
5277 }
5278
5279 return layerExpansionPairs;
5280}
const char * name
std::string FormatPath(const std::vector< std::string > &aVectorPath)
Helper for debug logging (vector -> string)
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.
ALTIUM_TEXT_POSITION
ALTIUM_RULE_KIND
ALTIUM_PAD_SHAPE
const uint16_t ALTIUM_NET_UNCONNECTED
const uint16_t ALTIUM_POLYGON_NONE
const uint16_t ALTIUM_POLYGON_BOARD
ALTIUM_RECORD
const int ALTIUM_COMPONENT_NONE
LIB_ID AltiumToKiCadLibID(const wxString &aLibName, const wxString &aLibReference)
wxString AltiumPcbSpecialStringsToKiCadStrings(const wxString &aString, const std::map< wxString, wxString > &aOverrides)
static bool IsLayerNameAssembly(const wxString &aName)
void HelperShapeLineChainFromAltiumVertices(SHAPE_LINE_CHAIN &aLine, const std::vector< ALTIUM_VERTICE > &aVertices)
double normalizeAngleDegrees(double Angle, double aMin, double aMax)
Normalize angle to be aMin < angle <= aMax angle is in degrees.
constexpr double BOLD_FACTOR
static bool IsLayerNameCourtyard(const wxString &aName)
bool IsAltiumLayerCopper(ALTIUM_LAYER aLayer)
bool IsAltiumLayerAPlane(ALTIUM_LAYER aLayer)
static bool IsLayerNameTopSide(const wxString &aName)
ALTIUM_PCB_DIR
Definition altium_pcb.h:38
@ EXTENDPRIMITIVEINFORMATION
Definition altium_pcb.h:56
std::function< void(const ALTIUM_PCB_COMPOUND_FILE &, const CFB::COMPOUND_FILE_ENTRY *)> PARSE_FUNCTION_POINTER_fp
Definition altium_pcb.h:120
KIID AltiumUniqueIdToKiid(const wxString &aUniqueId)
Derive a stable KIID from an Altium component unique id.
@ ERROR_INSIDE
constexpr int ARC_HIGH_DEF
Definition base_units.h:141
constexpr EDA_IU_SCALE pcbIUScale
Definition base_units.h:125
LAYER_T
The allowed types of layers, same as Specctra DSN spec.
Definition board.h:186
@ LT_POWER
Definition board.h:189
@ LT_MIXED
Definition board.h:190
@ LT_JUMPER
Definition board.h:191
@ LT_SIGNAL
Definition board.h:188
#define DEFAULT_BOARD_THICKNESS_MM
@ BS_ITEM_TYPE_COPPER
@ BS_ITEM_TYPE_DIELECTRIC
BOX2< VECTOR2I > BOX2I
Definition box2.h:922
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:990
static const ADVANCED_CFG & GetCfg()
Get the singleton instance's config, which is shared by all consumers.
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:292
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:277
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:302
void ConvertTexts6ToFootprintItemOnLayer(FOOTPRINT *aFootprint, const ATEXT6 &aElem, PCB_LAYER_ID aLayer)
std::map< ALTIUM_LAYER, PCB_LAYER_ID > m_layermap
Definition altium_pcb.h:280
void ConvertShapeBasedRegions6ToBoardItemOnLayer(const AREGION6 &aElem, PCB_LAYER_ID aLayer)
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:299
std::vector< FOOTPRINT * > m_components
Definition altium_pcb.h:275
void HelperFillMechanicalLayerAssignments(const std::vector< ABOARD6_LAYER_STACKUP > &aStackup)
void ParseShapeBasedRegions6Data(const ALTIUM_PCB_COMPOUND_FILE &aAltiumPcbFile, const CFB::COMPOUND_FILE_ENTRY *aEntry)
const ARULE6 * GetRuleDefault(ALTIUM_RULE_KIND aKind) const
void HelperParsePad6NonCopper(const APAD6 &aElem, PCB_LAYER_ID aLayer, PCB_SHAPE *aShape)
void ParseRegions6Data(const ALTIUM_PCB_COMPOUND_FILE &aAltiumPcbFile, const CFB::COMPOUND_FILE_ENTRY *aEntry)
std::vector< PCB_LAYER_ID > GetKicadLayersToIterate(ALTIUM_LAYER aAltiumLayer) const
void ConvertShapeBasedRegions6ToFootprintItem(FOOTPRINT *aFootprint, const AREGION6 &aElem, const int aPrimitiveIndex)
void HelperPcpShapeAsFootprintKeepoutRegion(FOOTPRINT *aFootprint, const PCB_SHAPE &aShape, const ALTIUM_LAYER aAltiumLayer, const uint8_t aKeepoutRestrictions)
void ConvertArcs6ToBoardItem(const AARC6 &aElem, const int aPrimitiveIndex)
void ConvertShapeBasedRegions6ToFootprintItemOnLayer(FOOTPRINT *aFootprint, const AREGION6 &aElem, PCB_LAYER_ID aLayer, const int aPrimitiveIndex)
std::map< ALTIUM_LAYER, ZONE * > m_outer_plane
Definition altium_pcb.h:288
std::vector< int > m_altiumToKicadNetcodes
Definition altium_pcb.h:279
unsigned m_totalCount
for progress reporting
Definition altium_pcb.h:296
void ParseComponents6Data(const ALTIUM_PCB_COMPOUND_FILE &aAltiumPcbFile, const CFB::COMPOUND_FILE_ENTRY *aEntry)
void HelperPcpShapeAsBoardKeepoutRegion(const PCB_SHAPE &aShape, const ALTIUM_LAYER aAltiumLayer, const uint8_t aKeepoutRestrictions)
std::map< wxString, ALTIUM_EMBEDDED_MODEL_DATA > m_EmbeddedModels
Definition altium_pcb.h:283
void ConvertFills6ToBoardItemOnLayer(const AFILL6 &aElem, PCB_LAYER_ID aLayer)
std::vector< std::pair< PCB_LAYER_ID, int > > HelperGetSolderAndPasteMaskExpansions(const ALTIUM_RECORD aType, const int aPrimitiveIndex, const ALTIUM_LAYER aAltiumLayer)
void ConvertBarcodes6ToBoardItemOnLayer(const ATEXT6 &aElem, PCB_LAYER_ID aLayer)
void ConvertComponentBody6ToFootprintItem(const ALTIUM_PCB_COMPOUND_FILE &aAltiumPcbFile, FOOTPRINT *aFootprint, const ACOMPONENTBODY6 &aElem)
void HelperSetTextAlignmentAndPos(const ATEXT6 &aElem, EDA_TEXT *aEdaText)
void ParseBoard6Data(const ALTIUM_PCB_COMPOUND_FILE &aAltiumPcbFile, const CFB::COMPOUND_FILE_ENTRY *aEntry)
void ConvertFills6ToFootprintItem(FOOTPRINT *aFootprint, const AFILL6 &aElem, const bool aIsBoardImport)
void ParseExtendedPrimitiveInformationData(const ALTIUM_PCB_COMPOUND_FILE &aAltiumPcbFile, const CFB::COMPOUND_FILE_ENTRY *aEntry)
void ParseFileHeader(const ALTIUM_PCB_COMPOUND_FILE &aAltiumPcbFile, const CFB::COMPOUND_FILE_ENTRY *aEntry)
void ConvertShapeBasedRegions6ToBoardItem(const AREGION6 &aElem)
void HelperCreateBoardOutline(const std::vector< ALTIUM_VERTICE > &aVertices)
std::map< ALTIUM_RULE_KIND, std::vector< ARULE6 > > m_rules
Definition altium_pcb.h:284
void ConvertVias6ToFootprintItem(FOOTPRINT *aFootprint, const AVIA6 &aElem)
void ParseRules6Data(const ALTIUM_PCB_COMPOUND_FILE &aAltiumPcbFile, const CFB::COMPOUND_FILE_ENTRY *aEntry)
void HelperSetZoneKeepoutRestrictions(ZONE &aZone, const uint8_t aKeepoutRestrictions)
unsigned m_doneCount
Definition altium_pcb.h:294
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:278
void ConvertTexts6ToBoardItem(const ATEXT6 &aElem)
void HelperParseDimensions6Center(const ADIMENSION6 &aElem)
void HelperParseDimensions6Radial(const ADIMENSION6 &aElem)
BOARD * m_board
Definition altium_pcb.h:274
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)
FOOTPRINT * ParseFootprint(ALTIUM_PCB_COMPOUND_FILE &altiumLibFile, const wxString &aFootprintName)
REPORTER * m_reporter
optional; may be nullptr
Definition altium_pcb.h:293
int GetNetCode(uint16_t aId) const
void ConvertTexts6ToEdaTextSettings(const ATEXT6 &aElem, EDA_TEXT &aEdaText)
wxString m_library
for footprint library loading error reporting
Definition altium_pcb.h:298
void ConvertPads6ToFootprintItemOnNonCopper(FOOTPRINT *aFootprint, const APAD6 &aElem)
void ConvertTexts6ToFootprintItem(FOOTPRINT *aFootprint, const ATEXT6 &aElem)
unsigned m_lastProgressCount
Definition altium_pcb.h:295
void ParseFills6Data(const ALTIUM_PCB_COMPOUND_FILE &aAltiumPcbFile, const CFB::COMPOUND_FILE_ENTRY *aEntry)
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< ZONE * > m_polygons
Definition altium_pcb.h:276
void ParsePads6Data(const ALTIUM_PCB_COMPOUND_FILE &aAltiumPcbFile, const CFB::COMPOUND_FILE_ENTRY *aEntry)
void ConvertArcs6ToPcbShape(const AARC6 &aElem, PCB_SHAPE *aShape)
void ConvertTracks6ToBoardItemOnLayer(const ATRACK6 &aElem, PCB_LAYER_ID aLayer)
void ConvertFills6ToBoardItem(const AFILL6 &aElem)
FOOTPRINT * HelperGetFootprint(uint16_t aComponent) const
LAYER_MAPPING_HANDLER m_layerMappingHandler
Definition altium_pcb.h:290
void ParsePolygons6Data(const ALTIUM_PCB_COMPOUND_FILE &aAltiumPcbFile, const CFB::COMPOUND_FILE_ENTRY *aEntry)
PCB_LAYER_ID GetKicadLayer(ALTIUM_LAYER aAltiumLayer) const
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 Parse(const ALTIUM_PCB_COMPOUND_FILE &aAltiumPcbFile, const std::map< ALTIUM_PCB_DIR, std::string > &aFileMapping)
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:286
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:281
void ConvertPads6ToBoardItemOnNonCopper(const APAD6 &aElem)
void HelperSetTextboxAlignmentAndPos(const ATEXT6 &aElem, PCB_TEXTBOX *aPcbTextbox)
void ConvertBarcodes6ToFootprintItemOnLayer(FOOTPRINT *aFootprint, const ATEXT6 &aElem, PCB_LAYER_ID aLayer)
void ParseNets6Data(const ALTIUM_PCB_COMPOUND_FILE &aAltiumPcbFile, const CFB::COMPOUND_FILE_ENTRY *aEntry)
void ConvertPads6ToFootprintItem(FOOTPRINT *aFootprint, const APAD6 &aElem)
const ARULE6 * GetRule(ALTIUM_RULE_KIND aKind, const wxString &aName) const
void ParseDimensions6Data(const ALTIUM_PCB_COMPOUND_FILE &aAltiumPcbFile, const CFB::COMPOUND_FILE_ENTRY *aEntry)
void HelperSetZoneLayers(ZONE &aZone, const ALTIUM_LAYER aAltiumLayer)
static wxString ReadString(const std::map< wxString, wxString > &aProps, const wxString &aKey, const wxString &aDefault)
BASE_SET & set(size_t pos)
Definition base_set.h:116
Container for design settings for a BOARD object.
void SetGridOrigin(const VECTOR2I &aOrigin)
const VECTOR2I & GetGridOrigin() const
void SetAuxOrigin(const VECTOR2I &aOrigin)
const VECTOR2I & GetAuxOrigin() const
BOARD_STACKUP & GetStackupDescriptor()
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition board_item.h:84
virtual void SetIsKnockout(bool aKnockout)
Definition board_item.h:356
virtual void SetLayer(PCB_LAYER_ID aLayer)
Set the layer this item is on.
Definition board_item.h:316
virtual LSET BoardLayerSet() const
Return the LSET for the board that this item resides on.
Manage layers needed to make a physical board.
void RemoveAll()
Delete all items in list and clear the list.
const std::vector< BOARD_STACKUP_ITEM * > & GetList() const
int BuildBoardThicknessFromStackup() const
void BuildDefaultStackupList(const BOARD_DESIGN_SETTINGS *aSettings, int aActiveCopperLayersCount=0)
Create a default stackup, according to the current BOARD_DESIGN_SETTINGS settings.
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:323
constexpr coord_type GetY() const
Definition box2.h:208
constexpr size_type GetWidth() const
Definition box2.h:214
constexpr coord_type GetX() const
Definition box2.h:207
constexpr size_type GetHeight() const
Definition box2.h:215
EDA_ANGLE Normalize()
Definition eda_angle.h:229
double Sin() const
Definition eda_angle.h:178
double AsDegrees() const
Definition eda_angle.h:116
bool IsHorizontal() const
Definition eda_angle.h:142
bool IsCardinal() const
Definition eda_angle.cpp:40
bool IsVertical() const
Definition eda_angle.h:148
bool IsCardinal90() const
Definition eda_angle.cpp:54
double Cos() const
Definition eda_angle.h:197
EDA_ANGLE GetArcAngle() const
void SetCenter(const VECTOR2I &aCenter)
int GetRadius() const
SHAPE_T GetShape() const
Definition eda_shape.h:189
virtual void SetFilled(bool aFlag)
Definition eda_shape.h:156
void SetStart(const VECTOR2I &aStart)
Definition eda_shape.h:198
const VECTOR2I & GetStart() const
Return the starting point of the graphic.
Definition eda_shape.h:194
void SetShape(SHAPE_T aShape)
Definition eda_shape.h:188
void SetEnd(const VECTOR2I &aEnd)
Definition eda_shape.h:240
void SetArcAngleAndEnd(const EDA_ANGLE &aAngle, bool aCheckNegativeAngle=false)
Set the end point from the angle center and start.
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:93
int GetTextHeight() const
Definition eda_text.h:292
void SetTextSize(VECTOR2I aNewSize, bool aEnforceMinTextSize=true)
Definition eda_text.cpp:536
void SetTextPos(const VECTOR2I &aPoint)
Definition eda_text.cpp:580
KIFONT::FONT * GetFont() const
Definition eda_text.h:272
void SetMirrored(bool isMirrored)
Definition eda_text.cpp:392
int GetTextWidth() const
Definition eda_text.h:289
void SetVertJustify(GR_TEXT_V_ALIGN_T aType)
Definition eda_text.cpp:416
void SetBoldFlag(bool aBold)
Set only the bold flag, without changing the font.
Definition eda_text.cpp:377
void SetTextThickness(int aWidth)
The TextThickness is that set by the user.
Definition eda_text.cpp:283
GR_TEXT_V_ALIGN_T GetVertJustify() const
Definition eda_text.h:228
virtual void SetTextAngle(const EDA_ANGLE &aAngle)
Definition eda_text.cpp:298
void SetItalic(bool aItalic)
Set the text to be italic - this will also update the font if needed.
Definition eda_text.cpp:306
void SetFont(KIFONT::FONT *aFont)
Definition eda_text.cpp:499
void SetHorizJustify(GR_TEXT_H_ALIGN_T aType)
Definition eda_text.cpp:408
wxString GetEmbeddedFileLink(const EMBEDDED_FILE &aFile) const
Return the link for an embedded file.
EMBEDDED_FILE * AddFile(const wxFileName &aName, bool aOverwrite)
Load a file from disk and adds it to the collection.
static RETURN_CODE CompressAndEncode(EMBEDDED_FILE &aFile)
Take data from the #decompressedData buffer and compresses it using ZSTD into the #compressedEncodedD...
EDA_ANGLE GetOrientation() const
Definition footprint.h:408
PCB_FIELD & Value()
read/write accessors:
Definition footprint.h:865
bool IsFlipped() const
Definition footprint.h:602
PCB_FIELD & Reference()
Definition footprint.h:866
void Add(BOARD_ITEM *aItem, ADD_MODE aMode=ADD_MODE::INSERT, bool aSkipConnectivity=false) override
Removes an item from the container.
std::vector< FP_3DMODEL > & Models()
Definition footprint.h:394
const wxString & GetReference() const
Definition footprint.h:829
EMBEDDED_FILES * GetEmbeddedFiles() override
Definition footprint.h:1293
VECTOR2I GetPosition() const override
Definition footprint.h:405
VECTOR3D m_Offset
3D model offset (mm)
Definition footprint.h:173
double m_Opacity
Definition footprint.h:174
VECTOR3D m_Rotation
3D model rotation (degrees)
Definition footprint.h:172
wxString m_Filename
The 3D shape filename in 3D library.
Definition footprint.h:175
FONT is an abstract base class for both outline and stroke fonts.
Definition font.h:98
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:147
virtual bool IsStroke() const
Definition font.h:105
const wxString & GetName() const
Definition font.h:116
virtual bool IsOutline() const
Definition font.h:106
Definition kiid.h:48
LAYER_RANGE_ITERATOR begin() const
A logical library item identifier and consists of various portions much like a URI.
Definition lib_id.h:49
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:683
static const LSET & AllCuMask()
return AllCuMask( MAX_CU_LAYERS );
Definition lset.cpp:608
static const LSET & UserMask()
Definition lset.cpp:690
static LSET UserDefinedLayersMask(int aUserDefinedLayerCount=MAX_USER_DEFINED_LAYERS)
Return a mask with the requested number of user defined layers.
Definition lset.cpp:704
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:577
Handle the data for a net.
Definition netinfo.h:50
int GetNetCode() const
Definition netinfo.h:98
static const int UNCONNECTED
Constant that holds the "unconnected net" number (typically 0) all items "connected" to this net are ...
Definition netinfo.h:260
A PADSTACK defines the characteristics of a single or multi-layer pad, in the IPC sense of the word.
Definition padstack.h:157
void SetRoundRectRadiusRatio(double aRatio, PCB_LAYER_ID aLayer)
Definition padstack.cpp:947
void SetMode(MODE aMode)
void SetChamferRatio(double aRatio, PCB_LAYER_ID aLayer)
Definition padstack.cpp:976
void SetShape(PAD_SHAPE aShape, PCB_LAYER_ID aLayer)
Definition padstack.cpp:887
void SetChamferPositions(int aPositions, PCB_LAYER_ID aLayer)
Definition padstack.cpp:994
@ NORMAL
Shape is the same on all layers.
Definition padstack.h:171
@ CUSTOM
Shapes can be defined on arbitrary layers.
Definition padstack.h:173
@ FRONT_INNER_BACK
Up to three shapes can be defined (F_Cu, inner copper layers, B_Cu)
Definition padstack.h:172
void SetSize(const VECTOR2I &aSize, PCB_LAYER_ID aLayer)
Definition padstack.cpp:861
static constexpr PCB_LAYER_ID ALL_LAYERS
! Temporary layer identifier to identify code that is not padstack-aware
Definition padstack.h:177
static constexpr PCB_LAYER_ID INNER_LAYERS
! The layer identifier to use for "inner layers" on top/inner/bottom padstacks
Definition padstack.h:180
Definition pad.h:65
static LSET PTHMask()
layer set for a through hole pad
Definition pad.cpp:372
static LSET UnplatedHoleMask()
layer set for a mechanical unplated through hole pad
Definition pad.cpp:393
static LSET SMDMask()
layer set for a SMD pad on Front layer
Definition pad.cpp:379
double GetRadius() const
virtual VECTOR2I GetCenter() const override
This defaults to the center of the bounding box if not overridden.
Definition pcb_track.h:297
A radial dimension indicates either the radius or diameter of an arc or circle.
VECTOR2I GetCenter() const override
This defaults to the center of the bounding box if not overridden.
Definition pcb_shape.h:81
void Rotate(const VECTOR2I &aRotCentre, const EDA_ANGLE &aAngle) override
Rotate this object.
void SetPosition(const VECTOR2I &aPos) override
Definition pcb_shape.h:78
void SetLayer(PCB_LAYER_ID aLayer) override
Set the layer this item is on.
void SetStroke(const STROKE_PARAMS &aStroke) override
Definition pcb_shape.h:98
VECTOR2I GetPosition() const override
Definition pcb_shape.h:79
void SetBorderEnabled(bool enabled)
void SetMarginTop(int aTop)
Definition pcb_textbox.h:92
void SetMarginLeft(int aLeft)
Definition pcb_textbox.h:91
void SetMarginBottom(int aBottom)
Definition pcb_textbox.h:94
void SetTextAngle(const EDA_ANGLE &aAngle) override
void SetMarginRight(int aRight)
Definition pcb_textbox.h:93
A progress reporter interface for use in multi-threaded environments.
A pure virtual class used to derive REPORTER objects from.
Definition reporter.h:75
Definition seg.h:42
VECTOR2I A
Definition seg.h:49
VECTOR2I B
Definition seg.h:50
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:446
const VECTOR2I & GetArcMid() const
Definition shape_arc.h:120
const VECTOR2I & GetP1() const
Definition shape_arc.h:119
const VECTOR2I & GetP0() const
Definition shape_arc.h:118
Represent a polyline containing arcs as well as line segments: A chain of connected line and/or arc s...
const SHAPE_ARC & Arc(size_t aArc) const
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.
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.
int AddOutline(const SHAPE_LINE_CHAIN &aOutline)
Adds a new outline to the set and returns its index.
void Inflate(int aAmount, CORNER_STRATEGY aCornerStrategy, int aMaxError, bool aSimplify=false)
Perform outline inflation/deflation.
int Append(int x, int y, int aOutline=-1, int aHole=-1, bool aAllowDuplication=false)
Appends a vertex at the end of the given outline/hole (default: the last outline)
int AddHole(const SHAPE_LINE_CHAIN &aHole, int aOutline=-1)
Adds a new hole to the given outline (default: last) and returns its index.
SHAPE_LINE_CHAIN & Outline(int aIndex)
Return the reference to aIndex-th outline in the set.
int NewOutline()
Creates a new empty polygon in the set and returns its index.
int OutlineCount() const
Return the number of outlines in the set.
void Move(const VECTOR2I &aVector) override
void Fracture(bool aSimplify=true)
Convert a set of polygons with holes to a single outline with "slits"/"fractures" connecting the oute...
Simple container to manage line stroke parameters.
constexpr extended_type Cross(const VECTOR2< T > &aVector) const
Compute cross product of self with aVector.
Definition vector2d.h:538
double Distance(const VECTOR2< extended_type > &aVector) const
Compute the distance between two vectors.
Definition vector2d.h:553
T EuclideanNorm() const
Compute the Euclidean norm of the vector, which is defined as sqrt(x ** 2 + y ** 2).
Definition vector2d.h:283
constexpr VECTOR2< T > Perpendicular() const
Compute the perpendicular vector.
Definition vector2d.h:314
VECTOR2< T > Resize(T aNewLength) const
Return a vector of the same direction, but length specified in aNewLength.
Definition vector2d.h:385
Handle a list of polygons defining a copper zone.
Definition zone.h:74
void SetNeedRefill(bool aNeedRefill)
Definition zone.h:314
void SetDoNotAllowPads(bool aEnable)
Definition zone.h:821
SHAPE_POLY_SET * GetFill(PCB_LAYER_ID aLayer)
Definition zone.h:695
void SetDoNotAllowTracks(bool aEnable)
Definition zone.h:820
void SetFilledPolysList(PCB_LAYER_ID aLayer, const SHAPE_POLY_SET &aPolysList)
Set the list of filled polygons.
Definition zone.h:717
void SetIsFilled(bool isFilled)
Definition zone.h:311
bool HasFilledPolysForLayer(PCB_LAYER_ID aLayer) const
Definition zone.h:679
void SetLayerSet(const LSET &aLayerSet) override
Definition zone.cpp:628
void SetDoNotAllowVias(bool aEnable)
Definition zone.h:819
void SetDoNotAllowFootprints(bool aEnable)
Definition zone.h:822
void SetDoNotAllowZoneFills(bool aEnable)
Definition zone.h:818
static int GetDefaultHatchPitch()
Definition zone.cpp:1430
@ CHAMFER_ACUTE_CORNERS
Acute angles are chamfered.
#define _(s)
static constexpr EDA_ANGLE ANGLE_90
Definition eda_angle.h:413
@ DEGREES_T
Definition eda_angle.h:31
static constexpr EDA_ANGLE ANGLE_45
Definition eda_angle.h:412
@ SEGMENT
Definition eda_shape.h:50
@ RECTANGLE
Use RECTANGLE instead of RECT to avoid collision in a Windows header.
Definition eda_shape.h:51
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 MAX_USER_DEFINED_LAYERS
Definition layer_ids.h:177
bool IsCopperLayer(int aLayerId)
Test whether a layer is a copper layer.
Definition layer_ids.h:679
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:915
PCB_LAYER_ID
A quick note on layer IDs:
Definition layer_ids.h:60
@ User_16
Definition layer_ids.h:139
@ In22_Cu
Definition layer_ids.h:87
@ In11_Cu
Definition layer_ids.h:76
@ In29_Cu
Definition layer_ids.h:94
@ In30_Cu
Definition layer_ids.h:95
@ User_15
Definition layer_ids.h:138
@ User_8
Definition layer_ids.h:131
@ F_CrtYd
Definition layer_ids.h:116
@ User_11
Definition layer_ids.h:134
@ In17_Cu
Definition layer_ids.h:82
@ B_Adhes
Definition layer_ids.h:103
@ Edge_Cuts
Definition layer_ids.h:112
@ Dwgs_User
Definition layer_ids.h:107
@ F_Paste
Definition layer_ids.h:104
@ In9_Cu
Definition layer_ids.h:74
@ Cmts_User
Definition layer_ids.h:108
@ User_6
Definition layer_ids.h:129
@ User_7
Definition layer_ids.h:130
@ In19_Cu
Definition layer_ids.h:84
@ In7_Cu
Definition layer_ids.h:72
@ In28_Cu
Definition layer_ids.h:93
@ In26_Cu
Definition layer_ids.h:91
@ F_Adhes
Definition layer_ids.h:102
@ B_Mask
Definition layer_ids.h:98
@ B_Cu
Definition layer_ids.h:65
@ User_14
Definition layer_ids.h:137
@ User_5
Definition layer_ids.h:128
@ Eco1_User
Definition layer_ids.h:109
@ F_Mask
Definition layer_ids.h:97
@ In21_Cu
Definition layer_ids.h:86
@ In23_Cu
Definition layer_ids.h:88
@ B_Paste
Definition layer_ids.h:105
@ In15_Cu
Definition layer_ids.h:80
@ In2_Cu
Definition layer_ids.h:67
@ User_10
Definition layer_ids.h:133
@ User_9
Definition layer_ids.h:132
@ F_Fab
Definition layer_ids.h:119
@ In10_Cu
Definition layer_ids.h:75
@ Margin
Definition layer_ids.h:113
@ F_SilkS
Definition layer_ids.h:100
@ In4_Cu
Definition layer_ids.h:69
@ B_CrtYd
Definition layer_ids.h:115
@ UNDEFINED_LAYER
Definition layer_ids.h:61
@ In16_Cu
Definition layer_ids.h:81
@ In24_Cu
Definition layer_ids.h:89
@ In1_Cu
Definition layer_ids.h:66
@ User_3
Definition layer_ids.h:126
@ User_1
Definition layer_ids.h:124
@ User_12
Definition layer_ids.h:135
@ B_SilkS
Definition layer_ids.h:101
@ In13_Cu
Definition layer_ids.h:78
@ User_4
Definition layer_ids.h:127
@ In8_Cu
Definition layer_ids.h:73
@ In14_Cu
Definition layer_ids.h:79
@ User_13
Definition layer_ids.h:136
@ User_2
Definition layer_ids.h:125
@ In12_Cu
Definition layer_ids.h:77
@ In27_Cu
Definition layer_ids.h:92
@ In6_Cu
Definition layer_ids.h:71
@ In5_Cu
Definition layer_ids.h:70
@ In3_Cu
Definition layer_ids.h:68
@ In20_Cu
Definition layer_ids.h:85
@ F_Cu
Definition layer_ids.h:64
@ In18_Cu
Definition layer_ids.h:83
@ In25_Cu
Definition layer_ids.h:90
@ B_Fab
Definition layer_ids.h:118
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:84
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:400
@ NPTH
like PAD_PTH, but not plated mechanical use only, no connection allowed
Definition padstack.h:103
@ SMD
Smd pad, appears on the solder paste layer (default)
Definition padstack.h:99
@ PTH
Plated through hole pad.
Definition padstack.h:98
@ CHAMFERED_RECT
Definition padstack.h:60
@ ROUNDRECT
Definition padstack.h:57
@ RECTANGLE
Definition padstack.h:54
BARCODE class definition.
@ INWARD
>--—<
DIM_PRECISION
std::function< std::map< wxString, PCB_LAYER_ID >(const std::vector< INPUT_LAYER_DESC > &)> LAYER_MAPPING_HANDLER
Pointer to a function that takes a map of source and KiCad layers and returns a re-mapped version.
@ RPT_SEVERITY_WARNING
@ RPT_SEVERITY_ERROR
@ RPT_SEVERITY_DEBUG
@ RPT_SEVERITY_INFO
std::optional< VECTOR2I > OPT_VECTOR2I
Definition seg.h:39
wxString NotSpecifiedPrm()
double startangle
uint16_t component
uint32_t width
ALTIUM_LAYER layer
uint8_t keepoutrestrictions
uint16_t polygon
VECTOR2I center
uint32_t radius
uint16_t net
double endangle
VECTOR2I sheetpos
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
uint16_t net
VECTOR2I pos1
double rotation
uint8_t keepoutrestrictions
uint16_t component
std::vector< ABOARD6_LAYER_STACKUP > stackup
std::vector< char > m_data
Definition altium_pcb.h:110
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 width
uint16_t polygon
uint8_t keepoutrestrictions
VECTOR2I start
ALTIUM_LAYER layer
uint16_t component
uint32_t diameter
uint16_t net
VECTOR2I position
bool soldermask_expansion_from_hole
int32_t soldermask_expansion_front
bool is_tent_bottom
bool soldermask_expansion_manual
ALTIUM_PAD_MODE viamode
int32_t soldermask_expansion_back
uint32_t diameter_by_layer[32]
ALTIUM_LAYER layer_start
ALTIUM_LAYER layer_end
uint32_t holesize
std::vector< char > decompressedData
Describes an imported layer and how it could be mapped to KiCad Layers.
PCB_LAYER_ID AutoMapLayer
Best guess as to what the equivalent KiCad layer might be.
bool Required
Should we require the layer to be assigned?
LSET PermittedLayers
KiCad layers that the imported layer can be mapped onto.
wxString Name
Imported layer name as displayed in original application.
std::string path
KIBIS_MODEL * model
std::vector< std::string > header
VECTOR2I center
int radius
VECTOR2I end
int clearance
@ GR_TEXT_H_ALIGN_CENTER
@ GR_TEXT_H_ALIGN_RIGHT
@ GR_TEXT_H_ALIGN_LEFT
@ GR_TEXT_V_ALIGN_BOTTOM
@ GR_TEXT_V_ALIGN_CENTER
@ GR_TEXT_V_ALIGN_TOP
thread_pool & GetKiCadThreadPool()
Get a reference to the current thread pool.
static thread_pool * tp
BS::priority_thread_pool thread_pool
Definition thread_pool.h:31
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:229
double DEG2RAD(double deg)
Definition trigo.h:166
@ PCB_SHAPE_T
class PCB_SHAPE, a segment not on copper layers
Definition typeinfo.h:85
@ PCB_DIM_ALIGNED_T
class PCB_DIM_ALIGNED, a linear dimension (graphic item)
Definition typeinfo.h:99
@ PCB_ARC_T
class PCB_ARC, an arc track segment on a copper layer
Definition typeinfo.h:95
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:687
VECTOR3< double > VECTOR3D
Definition vector3.h:230
@ THERMAL
Use thermal relief for pads.
Definition zones.h:50
@ NONE
Pads are not covered.
Definition zones.h:49
@ FULL
pads are covered by copper
Definition zones.h:51