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