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
945 const CFB::COMPOUND_FILE_ENTRY* aEntry )
946{
947 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
948
950 wxString header = reader.ReadWxString();
951
952 //std::cout << "HEADER: " << header << std::endl; // tells me: PCB 5.0 Binary File
953
954 //reader.SkipSubrecord();
955
956 // TODO: does not seem to work all the time at the moment
957 //if( reader.GetRemainingBytes() != 0 )
958 // THROW_IO_ERROR( "FileHeader stream is not fully parsed" );
959}
960
961
963 const CFB::COMPOUND_FILE_ENTRY* aEntry )
964{
966 m_progressReporter->Report( _( "Loading extended primitive information data..." ) );
967
968 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
969
970 while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
971 {
972 checkpoint();
973 AEXTENDED_PRIMITIVE_INFORMATION elem( reader );
974
976 std::move( elem ) );
977 }
978
979 if( reader.GetRemainingBytes() != 0 )
980 THROW_IO_ERROR( wxT( "ExtendedPrimitiveInformation stream is not fully parsed" ) );
981}
982
983
985 const CFB::COMPOUND_FILE_ENTRY* aEntry )
986{
988 m_progressReporter->Report( _( "Loading board data..." ) );
989
990 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
991
992 checkpoint();
993 ABOARD6 elem( reader );
994
995 if( reader.GetRemainingBytes() != 0 )
996 THROW_IO_ERROR( wxT( "Board6 stream is not fully parsed" ) );
997
998 m_board->GetDesignSettings().SetAuxOrigin( elem.sheetpos );
999 m_board->GetDesignSettings().SetGridOrigin( elem.sheetpos );
1000
1001 // read layercount from stackup, because LAYERSETSCOUNT is not always correct?!
1002 size_t layercount = 0;
1003 size_t layerid = static_cast<size_t>( ALTIUM_LAYER::TOP_LAYER );
1004
1005 while( layerid < elem.stackup.size() && layerid != 0 )
1006 {
1007 layerid = elem.stackup[ layerid - 1 ].nextId;
1008 layercount++;
1009 }
1010
1011 size_t kicadLayercount = ( layercount % 2 == 0 ) ? layercount : layercount + 1;
1012 m_board->SetCopperLayerCount( kicadLayercount );
1013
1014 BOARD_DESIGN_SETTINGS& designSettings = m_board->GetDesignSettings();
1015 BOARD_STACKUP& stackup = designSettings.GetStackupDescriptor();
1016
1017 // create board stackup
1018 stackup.RemoveAll(); // Just to be sure
1019 stackup.BuildDefaultStackupList( &designSettings, layercount );
1020
1021 auto it = stackup.GetList().begin();
1022
1023 // find first copper layer
1024 for( ; it != stackup.GetList().end() && ( *it )->GetType() != BS_ITEM_TYPE_COPPER; ++it )
1025 ;
1026
1027 auto cuLayer = LAYER_RANGE( F_Cu, B_Cu, 32 ).begin();
1028
1029 for( size_t altiumLayerId = static_cast<size_t>( ALTIUM_LAYER::TOP_LAYER );
1030 altiumLayerId < elem.stackup.size() && altiumLayerId != 0;
1031 altiumLayerId = elem.stackup[altiumLayerId - 1].nextId )
1032 {
1033 // array starts with 0, but stackup with 1
1034 ABOARD6_LAYER_STACKUP& layer = elem.stackup.at( altiumLayerId - 1 );
1035
1036 // handle unused layer in case of odd layercount
1037 if( layer.nextId == 0 && layercount != kicadLayercount )
1038 {
1039 m_board->SetLayerName( ( *it )->GetBrdLayerId(), wxT( "[unused]" ) );
1040
1041 if( ( *it )->GetType() != BS_ITEM_TYPE_COPPER )
1042 THROW_IO_ERROR( wxT( "Board6 stream, unexpected item while parsing stackup" ) );
1043
1044 ( *it )->SetThickness( 0 );
1045
1046 ++it;
1047
1048 if( ( *it )->GetType() != BS_ITEM_TYPE_DIELECTRIC )
1049 THROW_IO_ERROR( wxT( "Board6 stream, unexpected item while parsing stackup" ) );
1050
1051 ( *it )->SetThickness( 0, 0 );
1052 ( *it )->SetThicknessLocked( true, 0 );
1053 ++it;
1054 }
1055
1056 m_layermap.insert( { static_cast<ALTIUM_LAYER>( altiumLayerId ), *cuLayer } );
1057 ++cuLayer;
1058
1059 if( ( *it )->GetType() != BS_ITEM_TYPE_COPPER )
1060 THROW_IO_ERROR( wxT( "Board6 stream, unexpected item while parsing stackup" ) );
1061
1062 ( *it )->SetThickness( layer.copperthick );
1063
1064 ALTIUM_LAYER alayer = static_cast<ALTIUM_LAYER>( altiumLayerId );
1065 PCB_LAYER_ID klayer = ( *it )->GetBrdLayerId();
1066
1067 m_board->SetLayerName( klayer, layer.name );
1068
1069 if( layer.copperthick == 0 )
1070 m_board->SetLayerType( klayer, LAYER_T::LT_JUMPER ); // used for things like wirebonding
1071 else if( IsAltiumLayerAPlane( alayer ) )
1072 m_board->SetLayerType( klayer, LAYER_T::LT_POWER );
1073
1074 if( klayer == B_Cu )
1075 {
1076 if( layer.nextId != 0 )
1077 THROW_IO_ERROR( wxT( "Board6 stream, unexpected id while parsing last stackup layer" ) );
1078
1079 // overwrite entry from internal -> bottom
1080 m_layermap[alayer] = B_Cu;
1081 break;
1082 }
1083
1084 ++it;
1085
1086 if( ( *it )->GetType() != BS_ITEM_TYPE_DIELECTRIC )
1087 THROW_IO_ERROR( wxT( "Board6 stream, unexpected item while parsing stackup" ) );
1088
1089 ( *it )->SetThickness( layer.dielectricthick, 0 );
1090 ( *it )->SetMaterial( layer.dielectricmaterial.empty() ?
1091 NotSpecifiedPrm() :
1092 wxString( layer.dielectricmaterial ) );
1093 ( *it )->SetEpsilonR( layer.dielectricconst, 0 );
1094
1095 ++it;
1096 }
1097
1099 remapUnsureLayers( elem.stackup );
1100
1101 // Set name of all non-cu layers
1102 for( const ABOARD6_LAYER_STACKUP& layer : elem.stackup )
1103 {
1104 ALTIUM_LAYER alayer = static_cast<ALTIUM_LAYER>( layer.layerId );
1105
1106 if( ( alayer >= ALTIUM_LAYER::TOP_OVERLAY && alayer <= ALTIUM_LAYER::BOTTOM_SOLDER )
1107 || ( alayer >= ALTIUM_LAYER::MECHANICAL_1 && alayer <= ALTIUM_LAYER::MECHANICAL_16 )
1109 {
1110 PCB_LAYER_ID klayer = GetKicadLayer( alayer );
1111 m_board->SetLayerName( klayer, layer.name );
1112 }
1113 }
1114
1116 m_board->GetDesignSettings().SetBoardThickness( stackup.BuildBoardThicknessFromStackup() );
1117}
1118
1119
1120// Helper to detect if a layer name indicates a courtyard layer
1121static bool IsLayerNameCourtyard( const wxString& aName )
1122{
1123 wxString nameLower = aName.Lower();
1124 return nameLower.Contains( wxT( "courtyard" ) ) || nameLower.Contains( wxT( "court yard" ) )
1125 || nameLower.Contains( wxT( "crtyd" ) );
1126}
1127
1128
1129// Helper to detect if a layer name indicates an assembly layer
1130static bool IsLayerNameAssembly( const wxString& aName )
1131{
1132 wxString nameLower = aName.Lower();
1133 return nameLower.Contains( wxT( "assembly" ) ) || nameLower.Contains( wxT( "assy" ) );
1134}
1135
1136
1137// Helper to detect if a layer name indicates a top-side layer
1138static bool IsLayerNameTopSide( const wxString& aName )
1139{
1140 bool isTop = false;
1141
1142 auto check = [&isTop]( bool aTopCond, bool aBotCond )
1143 {
1144 if( aTopCond && aBotCond )
1145 return false;
1146
1147 if( !aTopCond && !aBotCond )
1148 return false;
1149
1150 isTop = aTopCond;
1151 return true;
1152 };
1153
1154 wxString lower = aName.Lower();
1155
1156 if( check( lower.StartsWith( "top" ), lower.StartsWith( "bot" ) ) )
1157 return isTop;
1158
1159 if( check( lower.EndsWith( "_t" ), lower.EndsWith( "_b" ) ) )
1160 return isTop;
1161
1162 if( check( lower.EndsWith( ".t" ), lower.EndsWith( ".b" ) ) )
1163 return isTop;
1164
1165 if( check( lower.Contains( "top" ), lower.Contains( "bot" ) ) )
1166 return isTop;
1167
1168 return true; // Unknown
1169}
1170
1171
1172void ALTIUM_PCB::remapUnsureLayers( std::vector<ABOARD6_LAYER_STACKUP>& aStackup )
1173{
1174 LSET enabledLayers = m_board->GetEnabledLayers();
1175 LSET validRemappingLayers = enabledLayers | LSET::AllBoardTechMask() |
1177
1178 if( aStackup.size() == 0 )
1179 return;
1180
1181 std::vector<INPUT_LAYER_DESC> inputLayers;
1182 std::map<wxString, ALTIUM_LAYER> altiumLayerNameMap;
1183
1184 ABOARD6_LAYER_STACKUP& curLayer = aStackup[0];
1185 ALTIUM_LAYER layer_num;
1186 INPUT_LAYER_DESC iLdesc;
1187
1188 // Track which courtyard layers we've mapped to avoid duplicates
1189 bool frontCourtyardMapped = false;
1190 bool backCourtyardMapped = false;
1191
1192 for( size_t ii = 0; ii < aStackup.size(); ii++ )
1193 {
1194 curLayer = aStackup[ii];
1195 layer_num = static_cast<ALTIUM_LAYER>( curLayer.layerId );
1196
1197 // Skip UI-only layers and pseudo-layers that have no physical representation
1198 if( layer_num == ALTIUM_LAYER::MULTI_LAYER
1199 || layer_num == ALTIUM_LAYER::CONNECTIONS
1200 || layer_num == ALTIUM_LAYER::BACKGROUND
1201 || layer_num == ALTIUM_LAYER::DRC_ERROR_MARKERS
1202 || layer_num == ALTIUM_LAYER::SELECTIONS
1203 || layer_num == ALTIUM_LAYER::VISIBLE_GRID_1
1204 || layer_num == ALTIUM_LAYER::VISIBLE_GRID_2
1205 || layer_num == ALTIUM_LAYER::PAD_HOLES
1206 || layer_num == ALTIUM_LAYER::VIA_HOLES )
1207 {
1208 continue;
1209 }
1210
1211 // Skip disabled mechanical layers (mapped to UNDEFINED_LAYER by
1212 // HelperFillMechanicalLayerAssignments)
1213 auto existingMapping = m_layermap.find( layer_num );
1214
1215 if( existingMapping != m_layermap.end()
1216 && existingMapping->second == PCB_LAYER_ID::UNDEFINED_LAYER )
1217 {
1218 continue;
1219 }
1220
1221 // Skip unused copper layers not present in the board's stackup. Used copper layers
1222 // were added to m_layermap during stackup parsing; any copper layer not in the map
1223 // is unused and should not appear in the dialog.
1224 if( layer_num >= ALTIUM_LAYER::TOP_LAYER && layer_num <= ALTIUM_LAYER::BOTTOM_LAYER
1225 && existingMapping == m_layermap.end() )
1226 {
1227 continue;
1228 }
1229
1230 // Use existing mapping as auto-match default if available
1231 if( existingMapping != m_layermap.end() )
1232 {
1233 iLdesc.AutoMapLayer = existingMapping->second;
1234 }
1235 // Check if the layer name indicates a courtyard layer
1236 else if( IsLayerNameCourtyard( curLayer.name ) )
1237 {
1238 bool isTopSide = IsLayerNameTopSide( curLayer.name );
1239
1240 if( isTopSide && !frontCourtyardMapped )
1241 {
1242 iLdesc.AutoMapLayer = F_CrtYd;
1243 frontCourtyardMapped = true;
1244 }
1245 else if( !isTopSide && !backCourtyardMapped )
1246 {
1247 iLdesc.AutoMapLayer = B_CrtYd;
1248 backCourtyardMapped = true;
1249 }
1250 else if( !frontCourtyardMapped )
1251 {
1252 iLdesc.AutoMapLayer = F_CrtYd;
1253 frontCourtyardMapped = true;
1254 }
1255 else if( !backCourtyardMapped )
1256 {
1257 iLdesc.AutoMapLayer = B_CrtYd;
1258 backCourtyardMapped = true;
1259 }
1260 else
1261 {
1262 iLdesc.AutoMapLayer = GetKicadLayer( layer_num );
1263 }
1264 }
1265 // Check if the layer name indicates an assembly layer (map to Fab)
1266 else if( IsLayerNameAssembly( curLayer.name ) )
1267 {
1268 bool isTopSide = IsLayerNameTopSide( curLayer.name );
1269 iLdesc.AutoMapLayer = isTopSide ? F_Fab : B_Fab;
1270 }
1271 else
1272 {
1273 iLdesc.AutoMapLayer = GetKicadLayer( layer_num );
1274 }
1275
1276 iLdesc.Name = curLayer.name;
1277 iLdesc.PermittedLayers = validRemappingLayers;
1278 iLdesc.Required = layer_num >= ALTIUM_LAYER::TOP_LAYER
1279 && layer_num <= ALTIUM_LAYER::BOTTOM_LAYER;
1280
1281 inputLayers.push_back( iLdesc );
1282 altiumLayerNameMap.insert( { curLayer.name, layer_num } );
1283 m_layerNames.insert( { layer_num, curLayer.name } );
1284 }
1285
1286 if( inputLayers.size() == 0 )
1287 return;
1288
1289 // Callback:
1290 std::map<wxString, PCB_LAYER_ID> reMappedLayers = m_layerMappingHandler( inputLayers );
1291
1292 for( std::pair<wxString, PCB_LAYER_ID> layerPair : reMappedLayers )
1293 {
1294 if( layerPair.second == PCB_LAYER_ID::UNDEFINED_LAYER )
1295 {
1296 // Layer mapping handler returned UNDEFINED_LAYER - skip this layer
1297 // This can happen for layers that don't have a KiCad equivalent
1298 if( m_reporter )
1299 {
1300 m_reporter->Report( wxString::Format( _( "Layer '%s' could not be mapped and "
1301 "will be skipped." ),
1302 layerPair.first ),
1304 }
1305
1306 continue;
1307 }
1308
1309 ALTIUM_LAYER altiumID = altiumLayerNameMap.at( layerPair.first );
1310 m_layermap.insert_or_assign( altiumID, layerPair.second );
1311 enabledLayers |= LSET( { layerPair.second } );
1312 }
1313
1314 // Explicitly mark unmatched dialog layers as UNDEFINED_LAYER so they are not imported
1315 // via the GetKicadLayer() hardcoded switch fallthrough
1316 for( const auto& [name, altLayer] : altiumLayerNameMap )
1317 {
1318 if( reMappedLayers.find( name ) == reMappedLayers.end()
1319 || reMappedLayers.at( name ) == PCB_LAYER_ID::UNDEFINED_LAYER )
1320 {
1321 m_layermap.insert_or_assign( altLayer, PCB_LAYER_ID::UNDEFINED_LAYER );
1322 }
1323 }
1324
1325 m_board->SetEnabledLayers( enabledLayers );
1326 m_board->SetVisibleLayers( enabledLayers );
1327}
1328
1329
1330void ALTIUM_PCB::HelperFillMechanicalLayerAssignments( const std::vector<ABOARD6_LAYER_STACKUP>& aStackup )
1331{
1332 for( const ABOARD6_LAYER_STACKUP& layer : aStackup )
1333 {
1334 ALTIUM_LAYER alayer = static_cast<ALTIUM_LAYER>( layer.layerId );
1335
1336 if( ( alayer >= ALTIUM_LAYER::MECHANICAL_1 && alayer <= ALTIUM_LAYER::MECHANICAL_16 )
1338 {
1339 if( !layer.mechenabled )
1340 {
1341 m_layermap.emplace( alayer, UNDEFINED_LAYER ); // Disabled layer, do not import
1342 continue;
1343 }
1344
1346
1347 switch( layer.mechkind )
1348 {
1349 case ALTIUM_MECHKIND::ASSEMBLY_TOP: target = F_Fab; break;
1350 case ALTIUM_MECHKIND::ASSEMBLY_BOT: target = B_Fab; break;
1351
1352 case ALTIUM_MECHKIND::COURTYARD_TOP: target = F_CrtYd; break;
1353 case ALTIUM_MECHKIND::COURTYARD_BOT: target = B_CrtYd; break;
1354
1355 case ALTIUM_MECHKIND::GLUE_POINTS_TOP: target = F_Adhes; break;
1356 case ALTIUM_MECHKIND::GLUE_POINTS_BOT: target = B_Adhes; break;
1357
1358 case ALTIUM_MECHKIND::ASSEMBLY_NOTES: target = Cmts_User; break;
1359 case ALTIUM_MECHKIND::FAB_NOTES: target = Cmts_User; break;
1360
1361 case ALTIUM_MECHKIND::DIMENSIONS: target = Dwgs_User; break;
1362
1363 case ALTIUM_MECHKIND::DIMENSIONS_TOP: target = F_Fab; break;
1364 case ALTIUM_MECHKIND::DIMENSIONS_BOT: target = B_Fab; break;
1365
1366 case ALTIUM_MECHKIND::VALUE_TOP: target = F_Fab; break;
1367 case ALTIUM_MECHKIND::VALUE_BOT: target = B_Fab; break;
1368
1369 case ALTIUM_MECHKIND::DESIGNATOR_TOP: target = F_Fab; break;
1370 case ALTIUM_MECHKIND::DESIGNATOR_BOT: target = B_Fab; break;
1371
1372 case ALTIUM_MECHKIND::COMPONENT_OUTLINE_TOP: target = F_Fab; break;
1373 case ALTIUM_MECHKIND::COMPONENT_OUTLINE_BOT: target = B_Fab; break;
1374
1375 case ALTIUM_MECHKIND::COMPONENT_CENTER_TOP: target = F_Fab; break;
1376 case ALTIUM_MECHKIND::COMPONENT_CENTER_BOT: target = B_Fab; break;
1377
1378 case ALTIUM_MECHKIND::BOARD: target = Edge_Cuts; break;
1379 case ALTIUM_MECHKIND::BOARD_SHAPE: target = Edge_Cuts; break;
1380 case ALTIUM_MECHKIND::V_CUT: target = Edge_Cuts; break;
1381
1382 default: break;
1383 }
1384
1385 if( target != UNDEFINED_LAYER )
1386 m_layermap.emplace( alayer, target );
1387 }
1388 }
1389}
1390
1391
1392void ALTIUM_PCB::HelperCreateBoardOutline( const std::vector<ALTIUM_VERTICE>& aVertices )
1393{
1394 SHAPE_LINE_CHAIN lineChain;
1395 HelperShapeLineChainFromAltiumVertices( lineChain, aVertices );
1396
1397 STROKE_PARAMS stroke( m_board->GetDesignSettings().GetLineThickness( Edge_Cuts ),
1399
1400 for( int i = 0; i <= lineChain.PointCount() && i != -1; i = lineChain.NextShape( i ) )
1401 {
1402 if( lineChain.IsArcStart( i ) )
1403 {
1404 const SHAPE_ARC& currentArc = lineChain.Arc( lineChain.ArcIndex( i ) );
1405
1406 std::unique_ptr<PCB_SHAPE> shape = std::make_unique<PCB_SHAPE>( m_board, SHAPE_T::ARC );
1407
1408 shape->SetStroke( stroke );
1409 shape->SetLayer( Edge_Cuts );
1410 shape->SetArcGeometry( currentArc.GetP0(), currentArc.GetArcMid(), currentArc.GetP1() );
1411
1412 m_board->Add( shape.release(), ADD_MODE::APPEND );
1413 }
1414 else
1415 {
1416 const SEG& seg = lineChain.Segment( i );
1417
1418 std::unique_ptr<PCB_SHAPE> shape = std::make_unique<PCB_SHAPE>( m_board, SHAPE_T::SEGMENT );
1419
1420 shape->SetStroke( stroke );
1421 shape->SetLayer( Edge_Cuts );
1422 shape->SetStart( seg.A );
1423 shape->SetEnd( seg.B );
1424
1425 m_board->Add( shape.release(), ADD_MODE::APPEND );
1426 }
1427 }
1428}
1429
1430
1432 const CFB::COMPOUND_FILE_ENTRY* aEntry )
1433{
1434 if( m_progressReporter )
1435 m_progressReporter->Report( _( "Loading netclasses..." ) );
1436
1437 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
1438
1439 while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
1440 {
1441 checkpoint();
1442 ACLASS6 elem( reader );
1443
1445 {
1446 std::shared_ptr<NETCLASS> nc = std::make_shared<NETCLASS>( elem.name );
1447
1448 for( const wxString& name : elem.names )
1449 {
1450 m_board->GetDesignSettings().m_NetSettings->SetNetclassPatternAssignment(
1451 name, nc->GetName() );
1452 }
1453
1454 if( m_board->GetDesignSettings().m_NetSettings->HasNetclass( nc->GetName() ) )
1455 {
1456 // Name conflict, happens in some unknown circumstances
1457 // unique_ptr will delete nc on this code path
1458 if( m_reporter )
1459 {
1460 wxString msg;
1461 msg.Printf( _( "More than one Altium netclass with name '%s' found. "
1462 "Only the first one will be imported." ), elem.name );
1463 m_reporter->Report( msg, RPT_SEVERITY_ERROR );
1464 }
1465 }
1466 else
1467 {
1468 m_board->GetDesignSettings().m_NetSettings->SetNetclass( nc->GetName(), nc );
1469 }
1470 }
1471 }
1472
1473 if( reader.GetRemainingBytes() != 0 )
1474 THROW_IO_ERROR( wxT( "Classes6 stream is not fully parsed" ) );
1475
1476 // Now that all netclasses and pattern assignments are set up, resolve the pattern
1477 // assignments to direct netclass assignments on each net.
1478 std::shared_ptr<NET_SETTINGS> netSettings = m_board->GetDesignSettings().m_NetSettings;
1479
1480 for( NETINFO_ITEM* net : m_board->GetNetInfo() )
1481 {
1482 if( net->GetNetCode() > 0 )
1483 {
1484 std::shared_ptr<NETCLASS> netclass = netSettings->GetEffectiveNetClass( net->GetNetname() );
1485
1486 if( netclass )
1487 net->SetNetClass( netclass );
1488 }
1489 }
1490
1491 m_board->m_LegacyNetclassesLoaded = true;
1492}
1493
1494
1496 const CFB::COMPOUND_FILE_ENTRY* aEntry )
1497{
1498 if( m_progressReporter )
1499 m_progressReporter->Report( _( "Loading components..." ) );
1500
1501 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
1502
1503 while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
1504 {
1505 checkpoint();
1506 ACOMPONENT6 elem( reader );
1507
1508 std::unique_ptr<FOOTPRINT> footprint = std::make_unique<FOOTPRINT>( m_board );
1509
1510 // Altium stores the footprint library information needed to find the footprint in the
1511 // source library in the sourcefootprintlibrary field. Since Altium is a Windows-only
1512 // program, the path separator is always a backslash. We need strip the extra path information
1513 // here to prevent overly-long LIB_IDs because KiCad doesn't store the full path to the
1514 // footprint library in the design file, only in a library table.
1515 wxFileName libName( elem.sourcefootprintlibrary, wxPATH_WIN );
1516
1517 // The pattern field may also contain a path when Altium stores it with a full library path.
1518 // Extract just the footprint name portion to avoid creating invalid filenames.
1519 wxString fpName = elem.pattern;
1520
1521 if( fpName.Contains( wxT( "\\" ) ) || fpName.Contains( wxT( "/" ) ) )
1522 {
1523 wxFileName fpPath( fpName, wxPATH_WIN );
1524 fpName = fpPath.GetFullName();
1525 }
1526
1527 LIB_ID fpID = AltiumToKiCadLibID( libName.GetName(), fpName );
1528
1529 footprint->SetFPID( fpID );
1530
1531 footprint->SetPosition( elem.position );
1532 footprint->SetOrientationDegrees( elem.rotation );
1533
1534 // KiCad netlisting requires parts to have non-digit + digit annotation.
1535 // If the reference begins with a number, we prepend 'UNK' (unknown) for the source designator
1536 wxString reference = elem.sourcedesignator;
1537
1538 if( reference.find_first_not_of( "0123456789" ) == wxString::npos )
1539 reference.Prepend( wxT( "UNK" ) );
1540
1541 footprint->SetReference( reference );
1542
1543 KIID id( elem.sourceUniqueID );
1544 KIID pathid( elem.sourceHierachicalPath );
1546 path.push_back( pathid );
1547 path.push_back( id );
1548
1549 footprint->SetPath( path );
1550 footprint->SetSheetname( elem.sourceHierachicalPath );
1551 footprint->SetSheetfile( elem.sourceHierachicalPath + wxT( ".kicad_sch" ));
1552
1553 footprint->SetLocked( elem.locked );
1554 footprint->Reference().SetVisible( elem.nameon );
1555 footprint->Value().SetVisible( elem.commenton );
1556 footprint->SetLayer( elem.layer == ALTIUM_LAYER::TOP_LAYER ? F_Cu : B_Cu );
1557
1558 m_components.emplace_back( footprint.get() );
1559 m_board->Add( footprint.release(), ADD_MODE::APPEND );
1560 }
1561
1562 if( reader.GetRemainingBytes() != 0 )
1563 THROW_IO_ERROR( wxT( "Components6 stream is not fully parsed" ) );
1564}
1565
1566
1568double normalizeAngleDegrees( double Angle, double aMin, double aMax )
1569{
1570 while( Angle < aMin )
1571 Angle += 360.0;
1572
1573 while( Angle >= aMax )
1574 Angle -= 360.0;
1575
1576 return Angle;
1577}
1578
1579
1581 FOOTPRINT* aFootprint,
1582 const ACOMPONENTBODY6& aElem )
1583{
1584 if( m_progressReporter )
1585 m_progressReporter->Report( _( "Loading component 3D models..." ) );
1586
1587 if( !aElem.modelIsEmbedded )
1588 return;
1589
1590 auto model = aAltiumPcbFile.GetLibModel( aElem.modelId );
1591
1592 if( !model )
1593 {
1594 if( m_reporter )
1595 {
1596 m_reporter->Report( wxString::Format( wxT( "Model %s not found for footprint %s" ),
1597 aElem.modelId, aFootprint->GetReference() ),
1599 }
1600
1601 return;
1602 }
1603
1605 file->name = aElem.modelName;
1606
1607 if( file->name.IsEmpty() )
1608 file->name = model->first.name;
1609
1610 // Decompress the model data before assigning
1611 std::vector<char> decompressedData;
1612 wxMemoryInputStream compressedStream( model->second.data(), model->second.size() );
1613 wxZlibInputStream zlibStream( compressedStream );
1614
1615 // Reserve some space, assuming decompressed data is larger -- STEP file
1616 // compression is typically 5:1 using zlib like Altium does
1617 decompressedData.resize( model->second.size() * 6 );
1618 size_t offset = 0;
1619
1620 while( !zlibStream.Eof() )
1621 {
1622 zlibStream.Read( decompressedData.data() + offset, decompressedData.size() - offset );
1623 size_t bytesRead = zlibStream.LastRead();
1624
1625 if( !bytesRead )
1626 break;
1627
1628 offset += bytesRead;
1629
1630 if( offset >= decompressedData.size() )
1631 decompressedData.resize( 2 * decompressedData.size() ); // Resizing is expensive, avoid if we can
1632 }
1633
1634 decompressedData.resize( offset );
1635
1636 file->decompressedData = std::move( decompressedData );
1638
1640 aFootprint->GetEmbeddedFiles()->AddFile( file );
1641
1642 FP_3DMODEL modelSettings;
1643
1644 modelSettings.m_Filename = aFootprint->GetEmbeddedFiles()->GetEmbeddedFileLink( *file );
1645
1646 modelSettings.m_Offset.x = pcbIUScale.IUTomm( (int) aElem.modelPosition.x );
1647 modelSettings.m_Offset.y = -pcbIUScale.IUTomm( (int) aElem.modelPosition.y );
1648 modelSettings.m_Offset.z = pcbIUScale.IUTomm( (int) aElem.modelPosition.z );
1649
1650 EDA_ANGLE orientation = aFootprint->GetOrientation();
1651
1652 if( aFootprint->IsFlipped() )
1653 {
1654 modelSettings.m_Offset.y = -modelSettings.m_Offset.y;
1655 orientation = -orientation;
1656 }
1657
1658 VECTOR3D modelRotation( aElem.modelRotation );
1659
1660 if( ( aElem.body_projection == 1 ) != aFootprint->IsFlipped() )
1661 {
1662 modelRotation.x += 180;
1663 modelRotation.z = -modelRotation.z;
1664
1665 modelSettings.m_Offset.z = -DEFAULT_BOARD_THICKNESS_MM - modelSettings.m_Offset.z;
1666 }
1667
1668 RotatePoint( &modelSettings.m_Offset.x, &modelSettings.m_Offset.y, orientation );
1669
1670 modelSettings.m_Rotation.x = normalizeAngleDegrees( -modelRotation.x, -180, 180 );
1671 modelSettings.m_Rotation.y = normalizeAngleDegrees( -modelRotation.y, -180, 180 );
1672 modelSettings.m_Rotation.z = normalizeAngleDegrees( -modelRotation.z + aElem.rotation
1673 + orientation.AsDegrees(),
1674 -180, 180 );
1675 modelSettings.m_Opacity = aElem.body_opacity_3d;
1676
1677 aFootprint->Models().push_back( modelSettings );
1678}
1679
1680
1682 const CFB::COMPOUND_FILE_ENTRY* aEntry )
1683{
1684 if( m_progressReporter )
1685 m_progressReporter->Report( _( "Loading component 3D models..." ) );
1686
1688 BS::multi_future<void> embeddedFutures;
1689
1690 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
1691
1692 while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
1693 {
1694 checkpoint();
1695 ACOMPONENTBODY6 elem( reader );
1696
1697 static const bool skipComponentBodies = ADVANCED_CFG::GetCfg().m_ImportSkipComponentBodies;
1698
1699 if( skipComponentBodies )
1700 continue;
1701
1702 if( elem.component == ALTIUM_COMPONENT_NONE )
1703 continue; // TODO: we do not support components for the board yet
1704
1705 if( m_components.size() <= elem.component )
1706 {
1707 THROW_IO_ERROR( wxString::Format( wxT( "ComponentsBodies6 stream tries to access "
1708 "component id %d of %zu existing components" ),
1709 elem.component,
1710 m_components.size() ) );
1711 }
1712
1713 if( !elem.modelIsEmbedded )
1714 continue;
1715
1716 auto modelTuple = m_EmbeddedModels.find( elem.modelId );
1717
1718 if( modelTuple == m_EmbeddedModels.end() )
1719 {
1720 if( m_reporter )
1721 {
1722 wxString msg;
1723 msg.Printf( wxT( "ComponentsBodies6 stream tries to access model id %s which does "
1724 "not exist" ), elem.modelId );
1725 m_reporter->Report( msg, RPT_SEVERITY_ERROR );
1726 }
1727
1728 continue;
1729 }
1730
1731 const ALTIUM_EMBEDDED_MODEL_DATA& modelData = modelTuple->second;
1732 FOOTPRINT* footprint = m_components.at( elem.component );
1733
1735 file->name = modelData.m_modelname;
1736
1737 wxMemoryInputStream compressedStream( modelData.m_data.data(), modelData.m_data.size() );
1738 wxZlibInputStream zlibStream( compressedStream );
1739 wxMemoryOutputStream decompressedStream;
1740
1741 zlibStream.Read( decompressedStream );
1742 file->decompressedData.resize( decompressedStream.GetSize() );
1743 decompressedStream.CopyTo( file->decompressedData.data(), file->decompressedData.size() );
1744
1745 footprint->GetEmbeddedFiles()->AddFile( file );
1746
1747 embeddedFutures.push_back( tp.submit_task(
1748 [file]()
1749 {
1750 EMBEDDED_FILES::CompressAndEncode( *file );
1751 } ) );
1752
1753 FP_3DMODEL modelSettings;
1754
1755 modelSettings.m_Filename = footprint->GetEmbeddedFiles()->GetEmbeddedFileLink( *file );
1756 VECTOR2I fpPosition = footprint->GetPosition();
1757
1758 modelSettings.m_Offset.x =
1759 pcbIUScale.IUTomm( KiROUND( elem.modelPosition.x - fpPosition.x ) );
1760 modelSettings.m_Offset.y =
1761 -pcbIUScale.IUTomm( KiROUND( elem.modelPosition.y - fpPosition.y ) );
1762 modelSettings.m_Offset.z = pcbIUScale.IUTomm( KiROUND( elem.modelPosition.z ) );
1763
1764 EDA_ANGLE orientation = footprint->GetOrientation();
1765
1766 if( footprint->IsFlipped() )
1767 {
1768 modelSettings.m_Offset.y = -modelSettings.m_Offset.y;
1769 orientation = -orientation;
1770 }
1771
1772 if( ( elem.body_projection == 1 ) != footprint->IsFlipped() )
1773 {
1774 elem.modelRotation.x += 180;
1775 elem.modelRotation.z = -elem.modelRotation.z;
1776
1777 modelSettings.m_Offset.z =
1778 -pcbIUScale.IUTomm( m_board->GetDesignSettings().GetBoardThickness() )
1779 - modelSettings.m_Offset.z;
1780 }
1781
1782 RotatePoint( &modelSettings.m_Offset.x, &modelSettings.m_Offset.y, orientation );
1783
1784 modelSettings.m_Rotation.x = normalizeAngleDegrees( -elem.modelRotation.x, -180, 180 );
1785 modelSettings.m_Rotation.y = normalizeAngleDegrees( -elem.modelRotation.y, -180, 180 );
1786 modelSettings.m_Rotation.z = normalizeAngleDegrees( -elem.modelRotation.z + elem.rotation
1787 + orientation.AsDegrees(),
1788 -180, 180 );
1789
1790 modelSettings.m_Opacity = elem.body_opacity_3d;
1791
1792 footprint->Models().push_back( modelSettings );
1793 }
1794
1795 embeddedFutures.wait();
1796
1797 if( reader.GetRemainingBytes() != 0 )
1798 THROW_IO_ERROR( wxT( "ComponentsBodies6 stream is not fully parsed" ) );
1799}
1800
1801
1803{
1804 if( aElem.referencePoint.size() != 2 )
1805 THROW_IO_ERROR( wxT( "Incorrect number of reference points for linear dimension object" ) );
1806
1807 PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
1808
1809 if( klayer == UNDEFINED_LAYER )
1810 {
1811 if( m_reporter )
1812 {
1813 m_reporter->Report( wxString::Format(
1814 _( "Dimension found on an Altium layer (%d) with no KiCad equivalent. "
1815 "It has been moved to KiCad layer Eco1_User." ), aElem.layer ),
1817 }
1818
1819 klayer = Eco1_User;
1820 }
1821
1822 VECTOR2I referencePoint0 = aElem.referencePoint.at( 0 );
1823 VECTOR2I referencePoint1 = aElem.referencePoint.at( 1 );
1824
1825 std::unique_ptr<PCB_DIM_ALIGNED> dimension = std::make_unique<PCB_DIM_ALIGNED>( m_board, PCB_DIM_ALIGNED_T );
1826
1827 dimension->SetPrecision( static_cast<DIM_PRECISION>( aElem.textprecision ) );
1828 dimension->SetLayer( klayer );
1829 dimension->SetStart( referencePoint0 );
1830
1831 if( referencePoint0 != aElem.xy1 )
1832 {
1842 VECTOR2I direction = aElem.xy1 - referencePoint0;
1843 VECTOR2I referenceDiff = referencePoint1 - referencePoint0;
1844 VECTOR2I directionNormalVector = direction.Perpendicular();
1845 SEG segm1( referencePoint0, referencePoint0 + directionNormalVector );
1846 SEG segm2( referencePoint1, referencePoint1 + direction );
1847 OPT_VECTOR2I intersection( segm1.Intersect( segm2, true, true ) );
1848
1849 if( !intersection )
1850 THROW_IO_ERROR( wxT( "Invalid dimension. This should never happen." ) );
1851
1852 dimension->SetEnd( *intersection );
1853
1854 int height = direction.EuclideanNorm();
1855
1856 if( direction.Cross( referenceDiff ) > 0 )
1857 height = -height;
1858
1859 dimension->SetHeight( height );
1860 }
1861 else
1862 {
1863 dimension->SetEnd( referencePoint1 );
1864 }
1865
1866 dimension->SetLineThickness( aElem.linewidth );
1867
1868 dimension->SetUnitsFormat( DIM_UNITS_FORMAT::NO_SUFFIX );
1869 dimension->SetPrefix( aElem.textprefix );
1870
1871
1872 int dist = ( dimension->GetEnd() - dimension->GetStart() ).EuclideanNorm();
1873
1874 if( dist < 3 * dimension->GetArrowLength() )
1875 dimension->SetArrowDirection( DIM_ARROW_DIRECTION::INWARD );
1876
1877 // Suffix normally (but not always) holds the units
1878 wxRegEx units( wxS( "(mm)|(in)|(mils)|(thou)|(')|(\")" ), wxRE_ADVANCED );
1879
1880 if( units.Matches( aElem.textsuffix ) )
1881 dimension->SetUnitsFormat( DIM_UNITS_FORMAT::BARE_SUFFIX );
1882 else
1883 dimension->SetSuffix( aElem.textsuffix );
1884
1885 dimension->SetTextThickness( aElem.textlinewidth );
1886 dimension->SetTextSize( VECTOR2I( aElem.textheight, aElem.textheight ) );
1887 dimension->SetItalic( aElem.textitalic );
1888
1889#if 0 // we don't currently support bold; map to thicker text
1890 dimension->Text().SetBold( aElem.textbold );
1891#else
1892 if( aElem.textbold )
1893 dimension->SetTextThickness( dimension->GetTextThickness() * BOLD_FACTOR );
1894#endif
1895
1896 switch( aElem.textunit )
1897 {
1898 case ALTIUM_UNIT::INCH: dimension->SetUnits( EDA_UNITS::INCH ); break;
1899 case ALTIUM_UNIT::MILS: dimension->SetUnits( EDA_UNITS::MILS ); break;
1900 case ALTIUM_UNIT::MM: dimension->SetUnits( EDA_UNITS::MM ); break;
1901 case ALTIUM_UNIT::CM: dimension->SetUnits( EDA_UNITS::MM ); break;
1902 default: break;
1903 }
1904
1905 m_board->Add( dimension.release(), ADD_MODE::APPEND );
1906}
1907
1908
1910{
1911 if( aElem.referencePoint.size() < 2 )
1912 THROW_IO_ERROR( wxT( "Not enough reference points for radial dimension object" ) );
1913
1914 PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
1915
1916 if( klayer == UNDEFINED_LAYER )
1917 {
1918 if( m_reporter )
1919 {
1920 m_reporter->Report( wxString::Format(
1921 _( "Dimension found on an Altium layer (%d) with no KiCad equivalent. "
1922 "It has been moved to KiCad layer Eco1_User." ),
1923 aElem.layer ), RPT_SEVERITY_INFO );
1924 }
1925
1926 klayer = Eco1_User;
1927 }
1928
1929 VECTOR2I referencePoint0 = aElem.referencePoint.at( 0 );
1930
1931 std::unique_ptr<PCB_DIM_RADIAL> dimension = std::make_unique<PCB_DIM_RADIAL>( m_board );
1932
1933 dimension->SetPrecision( static_cast<DIM_PRECISION>( aElem.textprecision ) );
1934 dimension->SetLayer( klayer );
1935 dimension->SetStart( referencePoint0 );
1936 dimension->SetEnd( aElem.xy1 );
1937 dimension->SetLineThickness( aElem.linewidth );
1938 dimension->SetKeepTextAligned( false );
1939
1940 dimension->SetPrefix( aElem.textprefix );
1941
1942 // Suffix normally holds the units
1943 dimension->SetUnitsFormat( aElem.textsuffix.IsEmpty() ? DIM_UNITS_FORMAT::NO_SUFFIX
1945
1946 switch( aElem.textunit )
1947 {
1948 case ALTIUM_UNIT::INCH: dimension->SetUnits( EDA_UNITS::INCH ); break;
1949 case ALTIUM_UNIT::MILS: dimension->SetUnits( EDA_UNITS::MILS ); break;
1950 case ALTIUM_UNIT::MM: dimension->SetUnits( EDA_UNITS::MM ); break;
1951 case ALTIUM_UNIT::CM: dimension->SetUnits( EDA_UNITS::MM ); break;
1952 default: break;
1953 }
1954
1955 if( aElem.textPoint.empty() )
1956 {
1957 if( m_reporter )
1958 {
1959 m_reporter->Report( wxT( "No text position present for leader dimension object" ),
1961 }
1962
1963 return;
1964 }
1965
1966 dimension->SetTextPos( aElem.textPoint.at( 0 ) );
1967 dimension->SetTextThickness( aElem.textlinewidth );
1968 dimension->SetTextSize( VECTOR2I( aElem.textheight, aElem.textheight ) );
1969 dimension->SetItalic( aElem.textitalic );
1970
1971#if 0 // we don't currently support bold; map to thicker text
1972 dimension->SetBold( aElem.textbold );
1973#else
1974 if( aElem.textbold )
1975 dimension->SetTextThickness( dimension->GetTextThickness() * BOLD_FACTOR );
1976#endif
1977
1978 // It's unclear exactly how Altium figures it's text positioning, but this gets us reasonably
1979 // close.
1980 dimension->SetVertJustify( GR_TEXT_V_ALIGN_BOTTOM );
1981 dimension->SetHorizJustify( GR_TEXT_H_ALIGN_LEFT );
1982
1983 int yAdjust = dimension->GetTextBox( nullptr ).GetCenter().y - dimension->GetTextPos().y;
1984 dimension->SetTextPos( dimension->GetTextPos() + VECTOR2I( 0, yAdjust + aElem.textgap ) );
1985 dimension->SetVertJustify( GR_TEXT_V_ALIGN_CENTER );
1986
1987 m_radialDimensions.push_back( dimension.get() );
1988 m_board->Add( dimension.release(), ADD_MODE::APPEND );
1989}
1990
1991
1993{
1994 PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
1995
1996 if( klayer == UNDEFINED_LAYER )
1997 {
1998 if( m_reporter )
1999 {
2000 wxString msg;
2001 msg.Printf( _( "Dimension found on an Altium layer (%d) with no KiCad equivalent. "
2002 "It has been moved to KiCad layer Eco1_User." ), aElem.layer );
2003 m_reporter->Report( msg, RPT_SEVERITY_ERROR );
2004 }
2005
2006 klayer = Eco1_User;
2007 }
2008
2009 if( !aElem.referencePoint.empty() )
2010 {
2011 VECTOR2I referencePoint0 = aElem.referencePoint.at( 0 );
2012
2013 // line
2014 VECTOR2I last = referencePoint0;
2015 for( size_t i = 1; i < aElem.referencePoint.size(); i++ )
2016 {
2017 std::unique_ptr<PCB_SHAPE> shape = std::make_unique<PCB_SHAPE>( m_board, SHAPE_T::SEGMENT );
2018
2019 shape->SetLayer( klayer );
2020 shape->SetStroke( STROKE_PARAMS( aElem.linewidth, LINE_STYLE::SOLID ) );
2021 shape->SetStart( last );
2022 shape->SetEnd( aElem.referencePoint.at( i ) );
2023 last = aElem.referencePoint.at( i );
2024
2025 m_board->Add( shape.release(), ADD_MODE::APPEND );
2026 }
2027
2028 // arrow
2029 if( aElem.referencePoint.size() >= 2 )
2030 {
2031 VECTOR2I dirVec = aElem.referencePoint.at( 1 ) - referencePoint0;
2032
2033 if( dirVec.x != 0 || dirVec.y != 0 )
2034 {
2035 double scaling = (double) dirVec.EuclideanNorm() / aElem.arrowsize;
2036 VECTOR2I arrVec = KiROUND( dirVec.x / scaling, dirVec.y / scaling );
2037 RotatePoint( arrVec, EDA_ANGLE( 20.0, DEGREES_T ) );
2038
2039 {
2040 std::unique_ptr<PCB_SHAPE> shape1 = std::make_unique<PCB_SHAPE>( m_board, SHAPE_T::SEGMENT );
2041
2042 shape1->SetLayer( klayer );
2043 shape1->SetStroke( STROKE_PARAMS( aElem.linewidth, LINE_STYLE::SOLID ) );
2044 shape1->SetStart( referencePoint0 );
2045 shape1->SetEnd( referencePoint0 + arrVec );
2046
2047 m_board->Add( shape1.release(), ADD_MODE::APPEND );
2048 }
2049
2050 RotatePoint( arrVec, EDA_ANGLE( -40.0, DEGREES_T ) );
2051
2052 {
2053 std::unique_ptr<PCB_SHAPE> shape2 = std::make_unique<PCB_SHAPE>( m_board, SHAPE_T::SEGMENT );
2054
2055 shape2->SetLayer( klayer );
2056 shape2->SetStroke( STROKE_PARAMS( aElem.linewidth, LINE_STYLE::SOLID ) );
2057 shape2->SetStart( referencePoint0 );
2058 shape2->SetEnd( referencePoint0 + arrVec );
2059
2060 m_board->Add( shape2.release(), ADD_MODE::APPEND );
2061 }
2062 }
2063 }
2064 }
2065
2066 if( aElem.textPoint.empty() )
2067 {
2068 if( m_reporter )
2069 {
2070 m_reporter->Report( wxT( "No text position present for leader dimension object" ),
2072 }
2073
2074 return;
2075 }
2076
2077 std::unique_ptr<PCB_TEXT> text = std::make_unique<PCB_TEXT>( m_board );
2078
2079 text->SetText( aElem.textformat );
2080 text->SetPosition( aElem.textPoint.at( 0 ) );
2081 text->SetLayer( klayer );
2082 text->SetTextSize( VECTOR2I( aElem.textheight, aElem.textheight ) ); // TODO: parse text width
2083 text->SetTextThickness( aElem.textlinewidth );
2084 text->SetHorizJustify( GR_TEXT_H_ALIGN_LEFT );
2085 text->SetVertJustify( GR_TEXT_V_ALIGN_BOTTOM );
2086
2087 m_board->Add( text.release(), ADD_MODE::APPEND );
2088}
2089
2090
2092{
2093 PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
2094
2095 if( klayer == UNDEFINED_LAYER )
2096 {
2097 if( m_reporter )
2098 {
2099 wxString msg;
2100 msg.Printf( _( "Dimension found on an Altium layer (%d) with no KiCad equivalent. "
2101 "It has been moved to KiCad layer Eco1_User." ), aElem.layer );
2102 m_reporter->Report( msg, RPT_SEVERITY_INFO );
2103 }
2104
2105 klayer = Eco1_User;
2106 }
2107
2108 for( size_t i = 0; i < aElem.referencePoint.size(); i++ )
2109 {
2110 std::unique_ptr<PCB_SHAPE> shape = std::make_unique<PCB_SHAPE>( m_board, SHAPE_T::SEGMENT );
2111
2112 shape->SetLayer( klayer );
2113 shape->SetStroke( STROKE_PARAMS( aElem.linewidth, LINE_STYLE::SOLID ) );
2114 shape->SetStart( aElem.referencePoint.at( i ) );
2115 // shape->SetEnd( /* TODO: seems to be based on TEXTY */ );
2116
2117 m_board->Add( shape.release(), ADD_MODE::APPEND );
2118 }
2119}
2120
2121
2123{
2124 PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
2125
2126 if( klayer == UNDEFINED_LAYER )
2127 {
2128 if( m_reporter )
2129 {
2130 wxString msg;
2131 msg.Printf( _( "Dimension found on an Altium layer (%d) with no KiCad equivalent. "
2132 "It has been moved to KiCad layer Eco1_User." ), aElem.layer );
2133 m_reporter->Report( msg, RPT_SEVERITY_INFO );
2134 }
2135
2136 klayer = Eco1_User;
2137 }
2138
2139 VECTOR2I vec = VECTOR2I( 0, aElem.height / 2 );
2140 RotatePoint( vec, EDA_ANGLE( aElem.angle, DEGREES_T ) );
2141
2142 std::unique_ptr<PCB_DIM_CENTER> dimension = std::make_unique<PCB_DIM_CENTER>( m_board );
2143
2144 dimension->SetLayer( klayer );
2145 dimension->SetLineThickness( aElem.linewidth );
2146 dimension->SetStart( aElem.xy1 );
2147 dimension->SetEnd( aElem.xy1 + vec );
2148
2149 m_board->Add( dimension.release(), ADD_MODE::APPEND );
2150}
2151
2152
2154 const CFB::COMPOUND_FILE_ENTRY* aEntry )
2155{
2156 if( m_progressReporter )
2157 m_progressReporter->Report( _( "Loading dimension drawings..." ) );
2158
2159 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
2160
2161 while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
2162 {
2163 checkpoint();
2164 ADIMENSION6 elem( reader );
2165
2166 switch( elem.kind )
2167 {
2170 break;
2172 if( m_reporter )
2173 {
2174 m_reporter->Report( wxString::Format( _( "Ignored Angular dimension (not yet supported)." ) ),
2176 }
2177 break;
2180 break;
2183 break;
2185 if( m_reporter )
2186 {
2187 m_reporter->Report( wxString::Format( _( "Ignored Datum dimension (not yet supported)." ) ),
2189 }
2190 // HelperParseDimensions6Datum( elem );
2191 break;
2193 if( m_reporter )
2194 {
2195 m_reporter->Report( wxString::Format( _( "Ignored Baseline dimension (not yet supported)." ) ),
2197 }
2198 break;
2201 break;
2203 if( m_reporter )
2204 {
2205 m_reporter->Report( wxString::Format( _( "Ignored Linear dimension (not yet supported)." ) ),
2207 }
2208 break;
2210 if( m_reporter )
2211 {
2212 m_reporter->Report( wxString::Format( _( "Ignored Radial dimension (not yet supported)." ) ),
2214 }
2215 break;
2216 default:
2217 if( m_reporter )
2218 {
2219 wxString msg;
2220 msg.Printf( _( "Ignored dimension of kind %d (not yet supported)." ), elem.kind );
2221 m_reporter->Report( msg, RPT_SEVERITY_INFO );
2222 }
2223 break;
2224 }
2225 }
2226
2227 if( reader.GetRemainingBytes() != 0 )
2228 THROW_IO_ERROR( wxT( "Dimensions6 stream is not fully parsed" ) );
2229}
2230
2231
2233 const CFB::COMPOUND_FILE_ENTRY* aEntry,
2234 const std::vector<std::string>& aRootDir )
2235{
2236 if( m_progressReporter )
2237 m_progressReporter->Report( _( "Loading 3D models..." ) );
2238
2239 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
2240
2241 if( reader.GetRemainingBytes() == 0 )
2242 return;
2243
2244 int idx = 0;
2245 wxString invalidChars = wxFileName::GetForbiddenChars();
2246
2247 while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
2248 {
2249 checkpoint();
2250 AMODEL elem( reader );
2251
2252 std::vector<std::string> stepPath = aRootDir;
2253 stepPath.emplace_back( std::to_string( idx ) );
2254
2255 bool validName = !elem.name.IsEmpty() && elem.name.IsAscii()
2256 && wxString::npos == elem.name.find_first_of( invalidChars );
2257 wxString storageName = validName ? elem.name : wxString::Format( wxT( "model_%d" ), idx );
2258
2259 idx++;
2260
2261 const CFB::COMPOUND_FILE_ENTRY* stepEntry = aAltiumPcbFile.FindStream( stepPath );
2262
2263 if( stepEntry == nullptr )
2264 {
2265 if( m_reporter )
2266 {
2267 wxString msg;
2268 msg.Printf( _( "File not found: '%s'. 3D-model not imported." ), FormatPath( stepPath ) );
2269 m_reporter->Report( msg, RPT_SEVERITY_ERROR );
2270 }
2271
2272 continue;
2273 }
2274
2275 size_t stepSize = static_cast<size_t>( stepEntry->size );
2276 std::vector<char> stepContent( stepSize );
2277
2278 // read file into buffer
2279 aAltiumPcbFile.GetCompoundFileReader().ReadFile( stepEntry, 0, stepContent.data(),
2280 stepSize );
2281
2282 m_EmbeddedModels.insert( std::make_pair(
2283 elem.id, ALTIUM_EMBEDDED_MODEL_DATA( storageName, elem.rotation, elem.z_offset,
2284 std::move( stepContent ) ) ) );
2285 }
2286
2287 // Append _<index> to duplicate filenames
2288 std::map<wxString, std::vector<wxString>> nameIdMap;
2289
2290 for( auto& [id, data] : m_EmbeddedModels )
2291 nameIdMap[data.m_modelname].push_back( id );
2292
2293 for( auto& [name, ids] : nameIdMap )
2294 {
2295 for( size_t i = 1; i < ids.size(); i++ )
2296 {
2297 const wxString& id = ids[i];
2298
2299 auto modelTuple = m_EmbeddedModels.find( id );
2300
2301 if( modelTuple == m_EmbeddedModels.end() )
2302 continue;
2303
2304 wxString modelName = modelTuple->second.m_modelname;
2305
2306 if( modelName.Contains( "." ) )
2307 {
2308 wxString ext;
2309 wxString baseName = modelName.BeforeLast( '.', &ext );
2310
2311 modelTuple->second.m_modelname = baseName + '_' + std::to_string( i ) + '.' + ext;
2312 }
2313 else
2314 {
2315 modelTuple->second.m_modelname = modelName + '_' + std::to_string( i );
2316 }
2317 }
2318 }
2319
2320 if( reader.GetRemainingBytes() != 0 )
2321 THROW_IO_ERROR( wxT( "Models stream is not fully parsed" ) );
2322}
2323
2324
2326 const CFB::COMPOUND_FILE_ENTRY* aEntry )
2327{
2328 if( m_progressReporter )
2329 m_progressReporter->Report( _( "Loading nets..." ) );
2330
2331 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
2332
2333 wxASSERT( m_altiumToKicadNetcodes.empty() );
2334
2335 while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
2336 {
2337 checkpoint();
2338 ANET6 elem( reader );
2339
2340 NETINFO_ITEM* netInfo = new NETINFO_ITEM( m_board, elem.name, -1 );
2341 m_board->Add( netInfo, ADD_MODE::APPEND );
2342
2343 // needs to be called after m_board->Add() as assign us the NetCode
2344 m_altiumToKicadNetcodes.push_back( netInfo->GetNetCode() );
2345 }
2346
2347 if( reader.GetRemainingBytes() != 0 )
2348 THROW_IO_ERROR( wxT( "Nets6 stream is not fully parsed" ) );
2349}
2350
2352 const CFB::COMPOUND_FILE_ENTRY* aEntry )
2353{
2354 if( m_progressReporter )
2355 m_progressReporter->Report( _( "Loading polygons..." ) );
2356
2357 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
2358
2359 while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
2360 {
2361 checkpoint();
2362 APOLYGON6 elem( reader );
2363
2364 SHAPE_LINE_CHAIN linechain;
2366
2367 if( linechain.PointCount() < 3 )
2368 {
2369 // We have found multiple Altium files with polygon records containing nothing but two
2370 // coincident vertices. These polygons do not appear when opening the file in Altium.
2371 // https://gitlab.com/kicad/code/kicad/-/issues/8183
2372 // Also, polygons with less than 3 points are not supported in KiCad.
2373 //
2374 // wxLogError( _( "Polygon has only %d point extracted from %ld vertices. At least 2 "
2375 // "points are required." ),
2376 // linechain.PointCount(),
2377 // elem.vertices.size() );
2378
2379 m_polygons.emplace_back( nullptr );
2380 continue;
2381 }
2382
2383 SHAPE_POLY_SET outline( linechain );
2384
2386 {
2387 // Altium "Hatched" or "None" polygon outlines have thickness, convert it to KiCad's representation.
2389 ARC_HIGH_DEF, true );
2390 }
2391
2392 if( outline.OutlineCount() != 1 && m_reporter )
2393 {
2394 wxString msg;
2395 msg.Printf( _( "Polygon outline count is %d, expected 1." ), outline.OutlineCount() );
2396
2397 m_reporter->Report( msg, RPT_SEVERITY_ERROR );
2398 }
2399
2400 if( outline.OutlineCount() == 0 )
2401 continue;
2402
2403 std::unique_ptr<ZONE> zone = std::make_unique<ZONE>(m_board);
2404
2405 // Be sure to set the zone layer before setting the net code
2406 // so that we know that this is a copper zone and so needs a valid net code.
2407 HelperSetZoneLayers( *zone, elem.layer );
2408 zone->SetNetCode( GetNetCode( elem.net ) );
2409 zone->SetPosition( elem.vertices.at( 0 ).position );
2410 zone->SetLocked( elem.locked );
2411 zone->SetAssignedPriority( elem.pourindex > 0 ? elem.pourindex : 0 );
2412 zone->Outline()->AddOutline( outline.Outline( 0 ) );
2413
2414 if( elem.pourindex > m_highest_pour_index )
2416
2417 const ARULE6* planeClearanceRule = GetRuleDefault( ALTIUM_RULE_KIND::PLANE_CLEARANCE );
2418 const ARULE6* zoneClearanceRule = GetRule( ALTIUM_RULE_KIND::CLEARANCE, wxT( "PolygonClearance" ) );
2419 int planeLayers = 0;
2420 int signalLayers = 0;
2421 int clearance = 0;
2422
2423 for( PCB_LAYER_ID layer : zone->GetLayerSet() )
2424 {
2425 LAYER_T layerType = m_board->GetLayerType( layer );
2426
2427 if( layerType == LT_POWER || layerType == LT_MIXED )
2428 planeLayers++;
2429
2430 if( layerType == LT_SIGNAL || layerType == LT_MIXED )
2431 signalLayers++;
2432 }
2433
2434 if( planeLayers > 0 && planeClearanceRule )
2435 clearance = std::max( clearance, planeClearanceRule->planeclearanceClearance );
2436
2437 if( signalLayers > 0 && zoneClearanceRule )
2438 clearance = std::max( clearance, zoneClearanceRule->clearanceGap );
2439
2440 if( clearance > 0 )
2441 zone->SetLocalClearance( clearance );
2442
2443 const ARULE6* polygonConnectRule = GetRuleDefault( ALTIUM_RULE_KIND::POLYGON_CONNECT );
2444
2445 if( polygonConnectRule != nullptr )
2446 {
2447 switch( polygonConnectRule->polygonconnectStyle )
2448 {
2450 zone->SetPadConnection( ZONE_CONNECTION::FULL );
2451 break;
2452
2454 zone->SetPadConnection( ZONE_CONNECTION::NONE );
2455 break;
2456
2457 default:
2459 zone->SetPadConnection( ZONE_CONNECTION::THERMAL );
2460 break;
2461 }
2462
2463 // TODO: correct variables?
2464 zone->SetThermalReliefSpokeWidth(
2465 polygonConnectRule->polygonconnectReliefconductorwidth );
2466 zone->SetThermalReliefGap( polygonConnectRule->polygonconnectAirgapwidth );
2467
2468 if( polygonConnectRule->polygonconnectReliefconductorwidth < zone->GetMinThickness() )
2469 zone->SetMinThickness( polygonConnectRule->polygonconnectReliefconductorwidth );
2470 }
2471
2472 if( IsAltiumLayerAPlane( elem.layer ) )
2473 {
2474 // outer zone will be set to priority 0 later.
2475 zone->SetAssignedPriority( 1 );
2476
2477 // check if this is the outer zone by simply comparing the BBOX
2478 const auto& outer_plane = m_outer_plane.find( elem.layer );
2479 if( outer_plane == m_outer_plane.end()
2480 || zone->GetBoundingBox().Contains( outer_plane->second->GetBoundingBox() ) )
2481 {
2482 m_outer_plane[elem.layer] = zone.get();
2483 }
2484 }
2485
2488 {
2489 zone->SetFillMode( ZONE_FILL_MODE::HATCH_PATTERN );
2490 zone->SetHatchThickness( elem.trackwidth );
2491
2493 {
2494 // use a small hack to get us only an outline (hopefully)
2495 const BOX2I& bbox = zone->GetBoundingBox();
2496 zone->SetHatchGap( std::max( bbox.GetHeight(), bbox.GetWidth() ) );
2497 }
2498 else
2499 {
2500 zone->SetHatchGap( elem.gridsize - elem.trackwidth );
2501 }
2502
2504 zone->SetHatchOrientation( ANGLE_45 );
2505 }
2506
2507 zone->SetBorderDisplayStyle( ZONE_BORDER_DISPLAY_STYLE::DIAGONAL_EDGE,
2509
2510 m_polygons.emplace_back( zone.get() );
2511 m_board->Add( zone.release(), ADD_MODE::APPEND );
2512 }
2513
2514 if( reader.GetRemainingBytes() != 0 )
2515 THROW_IO_ERROR( wxT( "Polygons6 stream is not fully parsed" ) );
2516}
2517
2519 const CFB::COMPOUND_FILE_ENTRY* aEntry )
2520{
2521 if( m_progressReporter )
2522 m_progressReporter->Report( _( "Loading rules..." ) );
2523
2524 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
2525
2526 while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
2527 {
2528 checkpoint();
2529 ARULE6 elem( reader );
2530
2531 m_rules[elem.kind].emplace_back( elem );
2532 }
2533
2534 // sort rules by priority
2535 for( std::pair<const ALTIUM_RULE_KIND, std::vector<ARULE6>>& val : m_rules )
2536 {
2537 std::sort( val.second.begin(), val.second.end(),
2538 []( const ARULE6& lhs, const ARULE6& rhs )
2539 {
2540 return lhs.priority < rhs.priority;
2541 } );
2542 }
2543
2544 const ARULE6* clearanceRule = GetRuleDefault( ALTIUM_RULE_KIND::CLEARANCE );
2545 const ARULE6* trackWidthRule = GetRuleDefault( ALTIUM_RULE_KIND::WIDTH );
2546 const ARULE6* routingViasRule = GetRuleDefault( ALTIUM_RULE_KIND::ROUTING_VIAS );
2547 const ARULE6* holeSizeRule = GetRuleDefault( ALTIUM_RULE_KIND::HOLE_SIZE );
2549
2550 if( clearanceRule )
2551 m_board->GetDesignSettings().m_MinClearance = clearanceRule->clearanceGap;
2552
2553 if( trackWidthRule )
2554 {
2555 m_board->GetDesignSettings().m_TrackMinWidth = trackWidthRule->minLimit;
2556 // TODO: construct a custom rule for preferredWidth and maxLimit values
2557 }
2558
2559 if( routingViasRule )
2560 {
2561 m_board->GetDesignSettings().m_ViasMinSize = routingViasRule->minWidth;
2562 m_board->GetDesignSettings().m_MinThroughDrill = routingViasRule->minHoleWidth;
2563 }
2564
2565 if( holeSizeRule )
2566 {
2567 // TODO: construct a custom rule for minLimit / maxLimit values
2568 }
2569
2570 if( holeToHoleRule )
2571 m_board->GetDesignSettings().m_HoleToHoleMin = holeToHoleRule->clearanceGap;
2572
2575
2576 if( soldermaskRule )
2577 m_board->GetDesignSettings().m_SolderMaskExpansion = soldermaskRule->soldermaskExpansion;
2578
2579 if( pastemaskRule )
2580 m_board->GetDesignSettings().m_SolderPasteMargin = pastemaskRule->pastemaskExpansion;
2581
2582 if( reader.GetRemainingBytes() != 0 )
2583 THROW_IO_ERROR( wxT( "Rules6 stream is not fully parsed" ) );
2584}
2585
2587 const CFB::COMPOUND_FILE_ENTRY* aEntry )
2588{
2589 if( m_progressReporter )
2590 m_progressReporter->Report( _( "Loading board regions..." ) );
2591
2592 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
2593
2594 while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
2595 {
2596 checkpoint();
2597 AREGION6 elem( reader, false );
2598
2599 // TODO: implement?
2600 }
2601
2602 if( reader.GetRemainingBytes() != 0 )
2603 THROW_IO_ERROR( wxT( "BoardRegions stream is not fully parsed" ) );
2604}
2605
2607 const CFB::COMPOUND_FILE_ENTRY* aEntry )
2608{
2609 if( m_progressReporter )
2610 m_progressReporter->Report( _( "Loading polygons..." ) );
2611
2612 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
2613
2614 /* TODO: use Header section of file */
2615 for( int primitiveIndex = 0; reader.GetRemainingBytes() >= 4; primitiveIndex++ )
2616 {
2617 checkpoint();
2618 AREGION6 elem( reader, true );
2619
2622 {
2623 // TODO: implement all different types for footprints
2625 }
2626 else
2627 {
2628 FOOTPRINT* footprint = HelperGetFootprint( elem.component );
2629 ConvertShapeBasedRegions6ToFootprintItem( footprint, elem, primitiveIndex );
2630 }
2631 }
2632
2633 if( reader.GetRemainingBytes() != 0 )
2634 THROW_IO_ERROR( "ShapeBasedRegions6 stream is not fully parsed" );
2635}
2636
2637
2639{
2641 {
2643 }
2644 else if( aElem.kind == ALTIUM_REGION_KIND::POLYGON_CUTOUT || aElem.is_keepout )
2645 {
2646 SHAPE_LINE_CHAIN linechain;
2648
2649 if( linechain.PointCount() < 3 )
2650 {
2651 // We have found multiple Altium files with polygon records containing nothing but
2652 // two coincident vertices. These polygons do not appear when opening the file in
2653 // Altium. https://gitlab.com/kicad/code/kicad/-/issues/8183
2654 // Also, polygons with less than 3 points are not supported in KiCad.
2655 return;
2656 }
2657
2658 std::unique_ptr<ZONE> zone = std::make_unique<ZONE>( m_board );
2659
2660 zone->SetIsRuleArea( true );
2661
2662 if( aElem.is_keepout )
2663 {
2665 }
2666 else if( aElem.kind == ALTIUM_REGION_KIND::POLYGON_CUTOUT )
2667 {
2668 zone->SetDoNotAllowZoneFills( true );
2669 zone->SetDoNotAllowVias( false );
2670 zone->SetDoNotAllowTracks( false );
2671 zone->SetDoNotAllowPads( false );
2672 zone->SetDoNotAllowFootprints( false );
2673 }
2674
2675 zone->SetPosition( aElem.outline.at( 0 ).position );
2676 zone->Outline()->AddOutline( linechain );
2677
2678 HelperSetZoneLayers( *zone, aElem.layer );
2679
2680 zone->SetBorderDisplayStyle( ZONE_BORDER_DISPLAY_STYLE::DIAGONAL_EDGE,
2682
2683 m_board->Add( zone.release(), ADD_MODE::APPEND );
2684 }
2685 else if( aElem.is_teardrop )
2686 {
2687 SHAPE_LINE_CHAIN linechain;
2689
2690 if( linechain.PointCount() < 3 )
2691 {
2692 // Polygons with less than 3 points are not supported in KiCad.
2693 return;
2694 }
2695
2696 std::unique_ptr<ZONE> zone = std::make_unique<ZONE>( m_board );
2697
2698 zone->SetPosition( aElem.outline.at( 0 ).position );
2699 zone->Outline()->AddOutline( linechain );
2700
2701 HelperSetZoneLayers( *zone, aElem.layer );
2702 zone->SetNetCode( GetNetCode( aElem.net ) );
2703 zone->SetTeardropAreaType( TEARDROP_TYPE::TD_UNSPECIFIED );
2704 zone->SetHatchStyle( ZONE_BORDER_DISPLAY_STYLE::INVISIBLE_BORDER );
2705
2706 SHAPE_POLY_SET fill;
2707 fill.Append( linechain );
2708 fill.Fracture();
2709
2710 for( PCB_LAYER_ID klayer : GetKicadLayersToIterate( aElem.layer ) )
2711 zone->SetFilledPolysList( klayer, fill );
2712
2713 zone->SetIsFilled( true );
2714 zone->SetNeedRefill( false );
2715
2716 m_board->Add( zone.release(), ADD_MODE::APPEND );
2717 }
2718 else if( aElem.kind == ALTIUM_REGION_KIND::DASHED_OUTLINE )
2719 {
2720 PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
2721
2722 if( klayer == UNDEFINED_LAYER )
2723 {
2724 if( m_reporter )
2725 {
2726 wxString msg;
2727 msg.Printf( _( "Dashed outline found on an Altium layer (%d) with no KiCad equivalent. "
2728 "It has been moved to KiCad layer Eco1_User." ), aElem.layer );
2729 m_reporter->Report( msg, RPT_SEVERITY_ERROR );
2730 }
2731
2732 klayer = Eco1_User;
2733 }
2734
2735 SHAPE_LINE_CHAIN linechain;
2737
2738 if( linechain.PointCount() < 3 )
2739 {
2740 // We have found multiple Altium files with polygon records containing nothing but
2741 // two coincident vertices. These polygons do not appear when opening the file in
2742 // Altium. https://gitlab.com/kicad/code/kicad/-/issues/8183
2743 // Also, polygons with less than 3 points are not supported in KiCad.
2744 return;
2745 }
2746
2747 std::unique_ptr<PCB_SHAPE> shape = std::make_unique<PCB_SHAPE>( m_board, SHAPE_T::POLY );
2748
2749 shape->SetPolyShape( linechain );
2750 shape->SetFilled( false );
2751 shape->SetLayer( klayer );
2752 shape->SetStroke( STROKE_PARAMS( pcbIUScale.mmToIU( 0.1 ), LINE_STYLE::DASH ) );
2753
2754 m_board->Add( shape.release(), ADD_MODE::APPEND );
2755 }
2756 else if( aElem.kind == ALTIUM_REGION_KIND::COPPER )
2757 {
2758 if( aElem.polygon == ALTIUM_POLYGON_NONE )
2759 {
2760 for( PCB_LAYER_ID klayer : GetKicadLayersToIterate( aElem.layer ) )
2762 }
2763 }
2764 else
2765 {
2766 if( m_reporter )
2767 {
2768 wxString msg;
2769 msg.Printf( _( "Ignored polygon shape of kind %d (not yet supported)." ), aElem.kind );
2770 m_reporter->Report( msg, RPT_SEVERITY_ERROR );
2771 }
2772 }
2773}
2774
2775
2777 const AREGION6& aElem,
2778 const int aPrimitiveIndex )
2779{
2780 if( aElem.kind == ALTIUM_REGION_KIND::POLYGON_CUTOUT || aElem.is_keepout )
2781 {
2782 SHAPE_LINE_CHAIN linechain;
2784
2785 if( linechain.PointCount() < 3 )
2786 {
2787 // We have found multiple Altium files with polygon records containing nothing but
2788 // two coincident vertices. These polygons do not appear when opening the file in
2789 // Altium. https://gitlab.com/kicad/code/kicad/-/issues/8183
2790 // Also, polygons with less than 3 points are not supported in KiCad.
2791 return;
2792 }
2793
2794 std::unique_ptr<ZONE> zone = std::make_unique<ZONE>( aFootprint );
2795
2796 zone->SetIsRuleArea( true );
2797
2798 if( aElem.is_keepout )
2799 {
2801 }
2802 else if( aElem.kind == ALTIUM_REGION_KIND::POLYGON_CUTOUT )
2803 {
2804 zone->SetDoNotAllowZoneFills( true );
2805 zone->SetDoNotAllowVias( false );
2806 zone->SetDoNotAllowTracks( false );
2807 zone->SetDoNotAllowPads( false );
2808 zone->SetDoNotAllowFootprints( false );
2809 }
2810
2811 zone->SetPosition( aElem.outline.at( 0 ).position );
2812 zone->Outline()->AddOutline( linechain );
2813
2814 HelperSetZoneLayers( *zone, aElem.layer );
2815
2816 zone->SetBorderDisplayStyle( ZONE_BORDER_DISPLAY_STYLE::DIAGONAL_EDGE,
2818
2819 aFootprint->Add( zone.release(), ADD_MODE::APPEND );
2820 }
2821 else if( aElem.kind == ALTIUM_REGION_KIND::COPPER )
2822 {
2823 if( aElem.polygon == ALTIUM_POLYGON_NONE )
2824 {
2825 for( PCB_LAYER_ID klayer : GetKicadLayersToIterate( aElem.layer ) )
2826 {
2827 ConvertShapeBasedRegions6ToFootprintItemOnLayer( aFootprint, aElem, klayer,
2828 aPrimitiveIndex );
2829 }
2830 }
2831 }
2834 {
2836 ? Edge_Cuts
2837 : GetKicadLayer( aElem.layer );
2838
2839 if( klayer == UNDEFINED_LAYER )
2840 {
2841 if( !m_footprintName.IsEmpty() )
2842 {
2843 if( m_reporter )
2844 {
2845 wxString msg;
2846 msg.Printf( _( "Loading library '%s':\n"
2847 "Footprint %s contains a dashed outline on Altium layer (%d) with "
2848 "no KiCad equivalent. It has been moved to KiCad layer Eco1_User." ),
2849 m_library,
2851 aElem.layer );
2852 m_reporter->Report( msg, RPT_SEVERITY_ERROR );
2853 }
2854 }
2855 else
2856 {
2857 if( m_reporter )
2858 {
2859 wxString msg;
2860 msg.Printf( _( "Footprint %s contains a dashed outline on Altium layer (%d) with "
2861 "no KiCad equivalent. It has been moved to KiCad layer Eco1_User." ),
2862 aFootprint->GetReference(),
2863 aElem.layer );
2864 m_reporter->Report( msg, RPT_SEVERITY_ERROR );
2865 }
2866 }
2867
2868 klayer = Eco1_User;
2869 }
2870
2871 SHAPE_LINE_CHAIN linechain;
2873
2874 if( linechain.PointCount() < 3 )
2875 {
2876 // We have found multiple Altium files with polygon records containing nothing but
2877 // two coincident vertices. These polygons do not appear when opening the file in
2878 // Altium. https://gitlab.com/kicad/code/kicad/-/issues/8183
2879 // Also, polygons with less than 3 points are not supported in KiCad.
2880 return;
2881 }
2882
2883 std::unique_ptr<PCB_SHAPE> shape = std::make_unique<PCB_SHAPE>( aFootprint, SHAPE_T::POLY );
2884
2885 shape->SetPolyShape( linechain );
2886 shape->SetFilled( false );
2887 shape->SetLayer( klayer );
2888
2890 shape->SetStroke( STROKE_PARAMS( pcbIUScale.mmToIU( 0.1 ), LINE_STYLE::DASH ) );
2891 else
2892 shape->SetStroke( STROKE_PARAMS( pcbIUScale.mmToIU( 0.1 ), LINE_STYLE::SOLID ) );
2893
2894 aFootprint->Add( shape.release(), ADD_MODE::APPEND );
2895 }
2896 else
2897 {
2898 if( !m_footprintName.IsEmpty() )
2899 {
2900 if( m_reporter )
2901 {
2902 wxString msg;
2903 msg.Printf( _( "Error loading library '%s':\n"
2904 "Footprint %s contains polygon shape of kind %d (not yet supported)." ),
2905 m_library,
2907 aElem.kind );
2908 m_reporter->Report( msg, RPT_SEVERITY_ERROR );
2909 }
2910 }
2911 else
2912 {
2913 if( m_reporter )
2914 {
2915 wxString msg;
2916 msg.Printf( _( "Footprint %s contains polygon shape of kind %d (not yet supported)." ),
2917 aFootprint->GetReference(),
2918 aElem.kind );
2919 m_reporter->Report( msg, RPT_SEVERITY_ERROR );
2920 }
2921 }
2922 }
2923}
2924
2925
2927 PCB_LAYER_ID aLayer )
2928{
2929 SHAPE_LINE_CHAIN linechain;
2931
2932 if( linechain.PointCount() < 3 )
2933 {
2934 // We have found multiple Altium files with polygon records containing nothing
2935 // but two coincident vertices. These polygons do not appear when opening the
2936 // file in Altium. https://gitlab.com/kicad/code/kicad/-/issues/8183
2937 // Also, polygons with less than 3 points are not supported in KiCad.
2938 return;
2939 }
2940
2941 SHAPE_POLY_SET polySet;
2942 polySet.AddOutline( linechain );
2943
2944 for( const std::vector<ALTIUM_VERTICE>& hole : aElem.holes )
2945 {
2946 SHAPE_LINE_CHAIN hole_linechain;
2947 HelperShapeLineChainFromAltiumVertices( hole_linechain, hole );
2948
2949 if( hole_linechain.PointCount() < 3 )
2950 continue;
2951
2952 polySet.AddHole( hole_linechain );
2953 }
2954
2955 std::unique_ptr<PCB_SHAPE> shape = std::make_unique<PCB_SHAPE>( m_board, SHAPE_T::POLY );
2956
2957 shape->SetPolyShape( polySet );
2958 shape->SetFilled( true );
2959 shape->SetLayer( aLayer );
2960 shape->SetStroke( STROKE_PARAMS( 0 ) );
2961
2962 if( IsCopperLayer( aLayer ) && aElem.net != ALTIUM_NET_UNCONNECTED )
2963 {
2964 shape->SetNetCode( GetNetCode( aElem.net ) );
2965 }
2966
2967 m_board->Add( shape.release(), ADD_MODE::APPEND );
2968}
2969
2970
2972 const AREGION6& aElem,
2973 PCB_LAYER_ID aLayer,
2974 const int aPrimitiveIndex )
2975{
2976 SHAPE_LINE_CHAIN linechain;
2978
2979 if( linechain.PointCount() < 3 )
2980 {
2981 // We have found multiple Altium files with polygon records containing nothing
2982 // but two coincident vertices. These polygons do not appear when opening the
2983 // file in Altium. https://gitlab.com/kicad/code/kicad/-/issues/8183
2984 // Also, polygons with less than 3 points are not supported in KiCad.
2985 return;
2986 }
2987
2988 SHAPE_POLY_SET polySet;
2989 polySet.AddOutline( linechain );
2990
2991 for( const std::vector<ALTIUM_VERTICE>& hole : aElem.holes )
2992 {
2993 SHAPE_LINE_CHAIN hole_linechain;
2994 HelperShapeLineChainFromAltiumVertices( hole_linechain, hole );
2995
2996 if( hole_linechain.PointCount() < 3 )
2997 continue;
2998
2999 polySet.AddHole( hole_linechain );
3000 }
3001
3002 if( aLayer == F_Cu || aLayer == B_Cu )
3003 {
3004 // TODO(JE) padstacks -- not sure what should happen here yet
3005 std::unique_ptr<PAD> pad = std::make_unique<PAD>( aFootprint );
3006
3007 LSET padLayers;
3008 padLayers.set( aLayer );
3009
3010 pad->SetAttribute( PAD_ATTRIB::SMD );
3012 pad->SetThermalSpokeAngle( ANGLE_90 );
3013
3014 int anchorSize = 1;
3015 VECTOR2I anchorPos = linechain.CPoint( 0 );
3016
3017 pad->SetAnchorPadShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::CIRCLE );
3018 pad->SetSize( PADSTACK::ALL_LAYERS, { anchorSize, anchorSize } );
3019 pad->SetPosition( anchorPos );
3020
3021 SHAPE_POLY_SET shapePolys = polySet;
3022 shapePolys.Move( -anchorPos );
3023 pad->AddPrimitivePoly( PADSTACK::ALL_LAYERS, shapePolys, 0, true );
3024
3026 auto it = map.find( aPrimitiveIndex );
3027
3028 if( it != map.end() )
3029 {
3030 const AEXTENDED_PRIMITIVE_INFORMATION& info = it->second;
3031
3032 if( info.pastemaskexpansionmode == ALTIUM_MODE::MANUAL )
3033 {
3034 pad->SetLocalSolderPasteMargin( info.pastemaskexpansionmanual );
3035 }
3036
3037 if( info.soldermaskexpansionmode == ALTIUM_MODE::MANUAL )
3038 {
3039 pad->SetLocalSolderMaskMargin( info.soldermaskexpansionmanual );
3040 }
3041
3042 if( info.pastemaskexpansionmode != ALTIUM_MODE::NONE )
3043 padLayers.set( aLayer == F_Cu ? F_Paste : B_Paste );
3044
3045 if( info.soldermaskexpansionmode != ALTIUM_MODE::NONE )
3046 padLayers.set( aLayer == F_Cu ? F_Mask : B_Mask );
3047 }
3048
3049 pad->SetLayerSet( padLayers );
3050
3051 aFootprint->Add( pad.release(), ADD_MODE::APPEND );
3052 }
3053 else
3054 {
3055 std::unique_ptr<PCB_SHAPE> shape = std::make_unique<PCB_SHAPE>( aFootprint, SHAPE_T::POLY );
3056
3057 shape->SetPolyShape( polySet );
3058 shape->SetFilled( true );
3059 shape->SetLayer( aLayer );
3060 shape->SetStroke( STROKE_PARAMS( 0 ) );
3061
3062 aFootprint->Add( shape.release(), ADD_MODE::APPEND );
3063 }
3064}
3065
3066
3068 const CFB::COMPOUND_FILE_ENTRY* aEntry )
3069{
3070 if( m_progressReporter )
3071 m_progressReporter->Report( _( "Loading zone fills..." ) );
3072
3073 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
3074
3075 while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
3076 {
3077 checkpoint();
3078 AREGION6 elem( reader, false );
3079
3080 if( elem.polygon != ALTIUM_POLYGON_NONE )
3081 {
3082 if( m_polygons.size() <= elem.polygon )
3083 {
3084 THROW_IO_ERROR( wxString::Format( "Region stream tries to access polygon id %d "
3085 "of %d existing polygons.",
3086 elem.polygon,
3087 m_polygons.size() ) );
3088 }
3089
3090 ZONE* zone = m_polygons.at( elem.polygon );
3091
3092 if( zone == nullptr )
3093 {
3094 continue; // we know the zone id, but because we do not know the layer we did not
3095 // add it!
3096 }
3097
3098 PCB_LAYER_ID klayer = GetKicadLayer( elem.layer );
3099
3100 if( klayer == UNDEFINED_LAYER )
3101 continue; // Just skip it for now. Users can fill it themselves.
3102
3103 SHAPE_LINE_CHAIN linechain;
3104
3105 for( const ALTIUM_VERTICE& vertice : elem.outline )
3106 linechain.Append( vertice.position );
3107
3108 linechain.Append( elem.outline.at( 0 ).position );
3109 linechain.SetClosed( true );
3110
3111 SHAPE_POLY_SET fill;
3112 fill.AddOutline( linechain );
3113
3114 for( const std::vector<ALTIUM_VERTICE>& hole : elem.holes )
3115 {
3116 SHAPE_LINE_CHAIN hole_linechain;
3117
3118 for( const ALTIUM_VERTICE& vertice : hole )
3119 hole_linechain.Append( vertice.position );
3120
3121 hole_linechain.Append( hole.at( 0 ).position );
3122 hole_linechain.SetClosed( true );
3123 fill.AddHole( hole_linechain );
3124 }
3125
3126 if( zone->HasFilledPolysForLayer( klayer ) )
3127 fill.BooleanAdd( *zone->GetFill( klayer ) );
3128
3129 fill.Fracture();
3130
3131 zone->SetFilledPolysList( klayer, fill );
3132 zone->SetIsFilled( true );
3133 zone->SetNeedRefill( false );
3134 }
3135 }
3136
3137 if( reader.GetRemainingBytes() != 0 )
3138 THROW_IO_ERROR( wxT( "Regions6 stream is not fully parsed" ) );
3139}
3140
3141
3143 const CFB::COMPOUND_FILE_ENTRY* aEntry )
3144{
3145 if( m_progressReporter )
3146 m_progressReporter->Report( _( "Loading arcs..." ) );
3147
3148 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
3149
3150 for( int primitiveIndex = 0; reader.GetRemainingBytes() >= 4; primitiveIndex++ )
3151 {
3152 checkpoint();
3153 AARC6 elem( reader );
3154
3155 if( elem.component == ALTIUM_COMPONENT_NONE )
3156 {
3157 ConvertArcs6ToBoardItem( elem, primitiveIndex );
3158 }
3159 else
3160 {
3161 FOOTPRINT* footprint = HelperGetFootprint( elem.component );
3162 ConvertArcs6ToFootprintItem( footprint, elem, primitiveIndex, true );
3163 }
3164 }
3165
3166 if( reader.GetRemainingBytes() != 0 )
3167 THROW_IO_ERROR( "Arcs6 stream is not fully parsed" );
3168}
3169
3170
3172{
3173 if( aElem.startangle == 0. && aElem.endangle == 360. )
3174 {
3175 aShape->SetShape( SHAPE_T::CIRCLE );
3176
3177 // TODO: other variants to define circle?
3178 aShape->SetStart( aElem.center );
3179 aShape->SetEnd( aElem.center - VECTOR2I( 0, aElem.radius ) );
3180 }
3181 else
3182 {
3183 aShape->SetShape( SHAPE_T::ARC );
3184
3185 EDA_ANGLE includedAngle( aElem.endangle - aElem.startangle, DEGREES_T );
3186 EDA_ANGLE startAngle( aElem.endangle, DEGREES_T );
3187
3188 VECTOR2I startOffset = VECTOR2I( KiROUND( startAngle.Cos() * aElem.radius ),
3189 -KiROUND( startAngle.Sin() * aElem.radius ) );
3190
3191 aShape->SetCenter( aElem.center );
3192 aShape->SetStart( aElem.center + startOffset );
3193 aShape->SetArcAngleAndEnd( includedAngle.Normalize(), true );
3194 }
3195}
3196
3197
3198void ALTIUM_PCB::ConvertArcs6ToBoardItem( const AARC6& aElem, const int aPrimitiveIndex )
3199{
3200 if( aElem.polygon != ALTIUM_POLYGON_NONE && aElem.polygon != ALTIUM_POLYGON_BOARD )
3201 {
3202 if( m_polygons.size() <= aElem.polygon )
3203 {
3204 THROW_IO_ERROR( wxString::Format( "Tracks stream tries to access polygon id %u "
3205 "of %zu existing polygons.",
3206 aElem.polygon, m_polygons.size() ) );
3207 }
3208
3209 ZONE* zone = m_polygons.at( aElem.polygon );
3210
3211 if( zone == nullptr )
3212 {
3213 return; // we know the zone id, but because we do not know the layer we did not
3214 // add it!
3215 }
3216
3217 PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
3218
3219 if( klayer == UNDEFINED_LAYER )
3220 return; // Just skip it for now. Users can fill it themselves.
3221
3222 if( !zone->HasFilledPolysForLayer( klayer ) )
3223 return;
3224
3225 SHAPE_POLY_SET* fill = zone->GetFill( klayer );
3226
3227 // This is not the actual board item. We can use it to create the polygon for the region
3228 PCB_SHAPE shape( nullptr );
3229
3230 ConvertArcs6ToPcbShape( aElem, &shape );
3232
3233 shape.EDA_SHAPE::TransformShapeToPolygon( *fill, 0, ARC_HIGH_DEF, ERROR_INSIDE );
3234 // Will be simplified and fractured later
3235
3236 zone->SetIsFilled( true );
3237 zone->SetNeedRefill( false );
3238
3239 return;
3240 }
3241
3242 if( aElem.is_keepout || aElem.layer == ALTIUM_LAYER::KEEP_OUT_LAYER
3243 || IsAltiumLayerAPlane( aElem.layer ) )
3244 {
3245 // This is not the actual board item. We can use it to create the polygon for the region
3246 PCB_SHAPE shape( nullptr );
3247
3248 ConvertArcs6ToPcbShape( aElem, &shape );
3250
3252 }
3253 else
3254 {
3255 for( PCB_LAYER_ID klayer : GetKicadLayersToIterate( aElem.layer ) )
3256 ConvertArcs6ToBoardItemOnLayer( aElem, klayer );
3257 }
3258
3259 for( const auto& layerExpansionMask :
3261 {
3262 int width = aElem.width + ( layerExpansionMask.second * 2 );
3263
3264 if( width > 1 )
3265 {
3266 std::unique_ptr<PCB_SHAPE> arc = std::make_unique<PCB_SHAPE>( m_board );
3267
3268 ConvertArcs6ToPcbShape( aElem, arc.get() );
3269 arc->SetStroke( STROKE_PARAMS( width, LINE_STYLE::SOLID ) );
3270 arc->SetLayer( layerExpansionMask.first );
3271
3272 m_board->Add( arc.release(), ADD_MODE::APPEND );
3273 }
3274 }
3275}
3276
3277
3279 const int aPrimitiveIndex, const bool aIsBoardImport )
3280{
3281 if( aElem.polygon != ALTIUM_POLYGON_NONE )
3282 {
3283 wxFAIL_MSG( wxString::Format( "Altium: Unexpected footprint Arc with polygon id %d",
3284 aElem.polygon ) );
3285 return;
3286 }
3287
3288 if( aElem.is_keepout || aElem.layer == ALTIUM_LAYER::KEEP_OUT_LAYER
3289 || IsAltiumLayerAPlane( aElem.layer ) )
3290 {
3291 // This is not the actual board item. We can use it to create the polygon for the region
3292 PCB_SHAPE shape( nullptr );
3293
3294 ConvertArcs6ToPcbShape( aElem, &shape );
3296
3297 HelperPcpShapeAsFootprintKeepoutRegion( aFootprint, shape, aElem.layer,
3298 aElem.keepoutrestrictions );
3299 }
3300 else
3301 {
3302 for( PCB_LAYER_ID klayer : GetKicadLayersToIterate( aElem.layer ) )
3303 {
3304 if( aIsBoardImport && IsCopperLayer( klayer ) && aElem.net != ALTIUM_NET_UNCONNECTED )
3305 {
3306 // Special case: do to not lose net connections in footprints
3307 ConvertArcs6ToBoardItemOnLayer( aElem, klayer );
3308 }
3309 else
3310 {
3311 ConvertArcs6ToFootprintItemOnLayer( aFootprint, aElem, klayer );
3312 }
3313 }
3314 }
3315
3316 for( const auto& layerExpansionMask :
3318 {
3319 int width = aElem.width + ( layerExpansionMask.second * 2 );
3320
3321 if( width > 1 )
3322 {
3323 std::unique_ptr<PCB_SHAPE> arc = std::make_unique<PCB_SHAPE>( aFootprint );
3324
3325 ConvertArcs6ToPcbShape( aElem, arc.get() );
3326 arc->SetStroke( STROKE_PARAMS( width, LINE_STYLE::SOLID ) );
3327 arc->SetLayer( layerExpansionMask.first );
3328
3329 aFootprint->Add( arc.release(), ADD_MODE::APPEND );
3330 }
3331 }
3332}
3333
3334
3336{
3337 if( IsCopperLayer( aLayer ) && aElem.net != ALTIUM_NET_UNCONNECTED )
3338 {
3339 EDA_ANGLE includedAngle( aElem.endangle - aElem.startangle, DEGREES_T );
3340 EDA_ANGLE startAngle( aElem.endangle, DEGREES_T );
3341
3342 includedAngle.Normalize();
3343
3344 VECTOR2I startOffset = VECTOR2I( KiROUND( startAngle.Cos() * aElem.radius ),
3345 -KiROUND( startAngle.Sin() * aElem.radius ) );
3346
3347 if( includedAngle.AsDegrees() >= 0.1 )
3348 {
3349 // TODO: This is not the actual board item. We use it for now to calculate the arc points. This could be improved!
3350 PCB_SHAPE shape( nullptr, SHAPE_T::ARC );
3351
3352 shape.SetCenter( aElem.center );
3353 shape.SetStart( aElem.center + startOffset );
3354 shape.SetArcAngleAndEnd( includedAngle, true );
3355
3356 // Create actual arc
3357 SHAPE_ARC shapeArc( shape.GetCenter(), shape.GetStart(), shape.GetArcAngle(),
3358 aElem.width );
3359 std::unique_ptr<PCB_ARC> arc = std::make_unique<PCB_ARC>( m_board, &shapeArc );
3360
3361 arc->SetWidth( aElem.width );
3362 arc->SetLayer( aLayer );
3363 arc->SetNetCode( GetNetCode( aElem.net ) );
3364
3365 m_board->Add( arc.release(), ADD_MODE::APPEND );
3366 }
3367 }
3368 else
3369 {
3370 std::unique_ptr<PCB_SHAPE> arc = std::make_unique<PCB_SHAPE>(m_board);
3371
3372 ConvertArcs6ToPcbShape( aElem, arc.get() );
3373 arc->SetStroke( STROKE_PARAMS( aElem.width, LINE_STYLE::SOLID ) );
3374 arc->SetLayer( aLayer );
3375
3376 m_board->Add( arc.release(), ADD_MODE::APPEND );
3377 }
3378}
3379
3380
3382 PCB_LAYER_ID aLayer )
3383{
3384 std::unique_ptr<PCB_SHAPE> arc = std::make_unique<PCB_SHAPE>( aFootprint );
3385
3386 ConvertArcs6ToPcbShape( aElem, arc.get() );
3387 arc->SetStroke( STROKE_PARAMS( aElem.width, LINE_STYLE::SOLID ) );
3388 arc->SetLayer( aLayer );
3389
3390 aFootprint->Add( arc.release(), ADD_MODE::APPEND );
3391}
3392
3393
3395 const CFB::COMPOUND_FILE_ENTRY* aEntry )
3396{
3397 if( m_progressReporter )
3398 m_progressReporter->Report( _( "Loading pads..." ) );
3399
3400 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
3401
3402 while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
3403 {
3404 checkpoint();
3405 APAD6 elem( reader );
3406
3407 if( elem.component == ALTIUM_COMPONENT_NONE )
3408 {
3410 }
3411 else
3412 {
3413 FOOTPRINT* footprint = HelperGetFootprint( elem.component );
3414 ConvertPads6ToFootprintItem( footprint, elem );
3415 }
3416 }
3417
3418 if( reader.GetRemainingBytes() != 0 )
3419 THROW_IO_ERROR( wxT( "Pads6 stream is not fully parsed" ) );
3420}
3421
3422
3424{
3425 // It is possible to place altium pads on non-copper layers -> we need to interpolate them using drawings!
3426 if( !IsAltiumLayerCopper( aElem.layer ) && !IsAltiumLayerAPlane( aElem.layer )
3427 && aElem.layer != ALTIUM_LAYER::MULTI_LAYER )
3428 {
3430 }
3431 else
3432 {
3433 // We cannot add a pad directly into the PCB
3434 std::unique_ptr<FOOTPRINT> footprint = std::make_unique<FOOTPRINT>( m_board );
3435 footprint->SetPosition( aElem.position );
3436
3437 ConvertPads6ToFootprintItemOnCopper( footprint.get(), aElem );
3438
3439 m_board->Add( footprint.release(), ADD_MODE::APPEND );
3440 }
3441}
3442
3443
3445{
3446 std::unique_ptr<PAD> pad = std::make_unique<PAD>( aFootprint );
3447
3448 pad->SetNumber( "" );
3449 pad->SetNetCode( GetNetCode( aElem.net ) );
3450
3451 pad->SetPosition( aElem.position );
3452 pad->SetSize( PADSTACK::ALL_LAYERS, VECTOR2I( aElem.diameter, aElem.diameter ) );
3453 pad->SetDrillSize( VECTOR2I( aElem.holesize, aElem.holesize ) );
3454 pad->SetDrillShape( PAD_DRILL_SHAPE::CIRCLE );
3456 pad->SetAttribute( PAD_ATTRIB::PTH );
3457
3458 // Pads are always through holes in KiCad
3459 pad->SetLayerSet( LSET().AllCuMask() );
3460
3461 if( aElem.viamode == ALTIUM_PAD_MODE::SIMPLE )
3462 {
3463 pad->Padstack().SetMode( PADSTACK::MODE::NORMAL );
3464 }
3466 {
3467 pad->Padstack().SetMode( PADSTACK::MODE::FRONT_INNER_BACK );
3468 pad->Padstack().SetSize( VECTOR2I( aElem.diameter_by_layer[1], aElem.diameter_by_layer[1] ),
3470 }
3471 else
3472 {
3473 pad->Padstack().SetMode( PADSTACK::MODE::CUSTOM );
3474
3475 LSET cuLayers = m_board->GetEnabledLayers() & LSET::AllCuMask();
3476
3477 for( PCB_LAYER_ID layer : cuLayers )
3478 {
3479 int altiumIdx = CopperLayerToOrdinal( layer );
3480
3481 if( altiumIdx < 32 )
3482 {
3483 pad->Padstack().SetSize( VECTOR2I( aElem.diameter_by_layer[altiumIdx],
3484 aElem.diameter_by_layer[altiumIdx] ), layer );
3485 }
3486 }
3487 }
3488
3489 if( aElem.is_tent_top )
3490 {
3491 pad->Padstack().FrontOuterLayers().has_solder_mask = true;
3492 }
3493 else
3494 {
3495 pad->Padstack().FrontOuterLayers().has_solder_mask = false;
3496 pad->SetLayerSet( pad->GetLayerSet().set( F_Mask ) );
3497 }
3498
3499 if( aElem.is_tent_bottom )
3500 {
3501 pad->Padstack().BackOuterLayers().has_solder_mask = true;
3502 }
3503 else
3504 {
3505 pad->Padstack().BackOuterLayers().has_solder_mask = false;
3506 pad->SetLayerSet( pad->GetLayerSet().set( B_Mask ) );
3507 }
3508
3509 if( aElem.is_locked )
3510 pad->SetLocked( true );
3511
3512 if( aElem.soldermask_expansion_manual )
3513 {
3514 pad->Padstack().FrontOuterLayers().solder_mask_margin = aElem.soldermask_expansion_front;
3515 pad->Padstack().BackOuterLayers().solder_mask_margin = aElem.soldermask_expansion_back;
3516 }
3517
3518
3519 aFootprint->Add( pad.release(), ADD_MODE::APPEND );
3520}
3521
3522
3524{
3525 // It is possible to place altium pads on non-copper layers -> we need to interpolate them using drawings!
3526 if( !IsAltiumLayerCopper( aElem.layer ) && !IsAltiumLayerAPlane( aElem.layer )
3527 && aElem.layer != ALTIUM_LAYER::MULTI_LAYER )
3528 {
3529 ConvertPads6ToFootprintItemOnNonCopper( aFootprint, aElem );
3530 }
3531 else
3532 {
3533 ConvertPads6ToFootprintItemOnCopper( aFootprint, aElem );
3534 }
3535}
3536
3537
3539{
3540 std::unique_ptr<PAD> pad = std::make_unique<PAD>( aFootprint );
3541
3542 pad->SetNumber( aElem.name );
3543 pad->SetNetCode( GetNetCode( aElem.net ) );
3544
3545 pad->SetPosition( aElem.position );
3546 pad->SetOrientationDegrees( aElem.direction );
3547 pad->SetThermalSpokeAngle( ANGLE_90 );
3548
3549 if( aElem.holesize == 0 )
3550 {
3551 pad->SetAttribute( PAD_ATTRIB::SMD );
3552 }
3553 else
3554 {
3555 if( aElem.layer != ALTIUM_LAYER::MULTI_LAYER )
3556 {
3557 // TODO: I assume other values are possible as well?
3558 if( !m_footprintName.IsEmpty() )
3559 {
3560 if( m_reporter )
3561 {
3562 wxString msg;
3563 msg.Printf( _( "Error loading library '%s':\n"
3564 "Footprint %s pad %s is not marked as multilayer, but is a TH pad." ),
3565 m_library,
3567 aElem.name );
3568 m_reporter->Report( msg, RPT_SEVERITY_ERROR );
3569 }
3570 }
3571 else
3572 {
3573 if( m_reporter )
3574 {
3575 wxString msg;
3576 msg.Printf( _( "Footprint %s pad %s is not marked as multilayer, but is a TH pad." ),
3577 aFootprint->GetReference(),
3578 aElem.name );
3579 m_reporter->Report( msg, RPT_SEVERITY_ERROR );
3580 }
3581 }
3582 }
3583
3584 pad->SetAttribute( aElem.plated ? PAD_ATTRIB::PTH : PAD_ATTRIB::NPTH );
3585
3587 {
3588 pad->SetDrillShape( PAD_DRILL_SHAPE::CIRCLE );
3589 pad->SetDrillSize( VECTOR2I( aElem.holesize, aElem.holesize ) );
3590 }
3591 else
3592 {
3593 switch( aElem.sizeAndShape->holeshape )
3594 {
3596 wxFAIL_MSG( wxT( "Round holes are handled before the switch" ) );
3597 break;
3598
3600 if( !m_footprintName.IsEmpty() )
3601 {
3602 if( m_reporter )
3603 {
3604 wxString msg;
3605 msg.Printf( _( "Loading library '%s':\n"
3606 "Footprint %s pad %s has a square hole (not yet supported)." ),
3607 m_library,
3609 aElem.name );
3610 m_reporter->Report( msg, RPT_SEVERITY_DEBUG );
3611 }
3612 }
3613 else
3614 {
3615 if( m_reporter )
3616 {
3617 wxString msg;
3618 msg.Printf( _( "Footprint %s pad %s has a square hole (not yet supported)." ),
3619 aFootprint->GetReference(),
3620 aElem.name );
3621 m_reporter->Report( msg, RPT_SEVERITY_DEBUG );
3622 }
3623 }
3624
3625 pad->SetDrillShape( PAD_DRILL_SHAPE::CIRCLE );
3626 pad->SetDrillSize( VECTOR2I( aElem.holesize, aElem.holesize ) ); // Workaround
3627 // TODO: elem.sizeAndShape->slotsize was 0 in testfile. Either use holesize in
3628 // this case or rect holes have a different id
3629 break;
3630
3632 {
3633 pad->SetDrillShape( PAD_DRILL_SHAPE::OBLONG );
3634 EDA_ANGLE slotRotation( aElem.sizeAndShape->slotrotation, DEGREES_T );
3635
3636 slotRotation.Normalize();
3637
3638 if( slotRotation.IsHorizontal() )
3639 {
3640 pad->SetDrillSize( VECTOR2I( aElem.sizeAndShape->slotsize, aElem.holesize ) );
3641 }
3642 else if( slotRotation.IsVertical() )
3643 {
3644 pad->SetDrillSize( VECTOR2I( aElem.holesize, aElem.sizeAndShape->slotsize ) );
3645 }
3646 else
3647 {
3648 if( !m_footprintName.IsEmpty() )
3649 {
3650 if( m_reporter )
3651 {
3652 wxString msg;
3653 msg.Printf( _( "Loading library '%s':\n"
3654 "Footprint %s pad %s has a hole-rotation of %d degrees. "
3655 "KiCad only supports 90 degree rotations." ),
3656 m_library,
3658 aElem.name,
3659 KiROUND( slotRotation.AsDegrees() ) );
3660 m_reporter->Report( msg, RPT_SEVERITY_DEBUG );
3661 }
3662 }
3663 else
3664 {
3665 if( m_reporter )
3666 {
3667 wxString msg;
3668 msg.Printf( _( "Footprint %s pad %s has a hole-rotation of %d degrees. "
3669 "KiCad only supports 90 degree rotations." ),
3670 aFootprint->GetReference(),
3671 aElem.name,
3672 KiROUND( slotRotation.AsDegrees() ) );
3673 m_reporter->Report( msg, RPT_SEVERITY_DEBUG );
3674 }
3675 }
3676 }
3677
3678 break;
3679 }
3680
3681 default:
3683 if( !m_footprintName.IsEmpty() )
3684 {
3685 if( m_reporter )
3686 {
3687 wxString msg;
3688 msg.Printf( _( "Error loading library '%s':\n"
3689 "Footprint %s pad %s uses a hole of unknown kind %d." ),
3690 m_library,
3692 aElem.name,
3693 aElem.sizeAndShape->holeshape );
3694 m_reporter->Report( msg, RPT_SEVERITY_DEBUG );
3695 }
3696 }
3697 else
3698 {
3699 if( m_reporter )
3700 {
3701 wxString msg;
3702 msg.Printf( _( "Footprint %s pad %s uses a hole of unknown kind %d." ),
3703 aFootprint->GetReference(),
3704 aElem.name,
3705 aElem.sizeAndShape->holeshape );
3706 m_reporter->Report( msg, RPT_SEVERITY_DEBUG );
3707 }
3708 }
3709
3710 pad->SetDrillShape( PAD_DRILL_SHAPE::CIRCLE );
3711 pad->SetDrillSize( VECTOR2I( aElem.holesize, aElem.holesize ) ); // Workaround
3712 break;
3713 }
3714 }
3715
3716 if( aElem.sizeAndShape )
3717 pad->SetOffset( PADSTACK::ALL_LAYERS, aElem.sizeAndShape->holeoffset[0] );
3718 }
3719
3720 PADSTACK& ps = pad->Padstack();
3721
3722 auto setCopperGeometry =
3723 [&]( PCB_LAYER_ID aLayer, ALTIUM_PAD_SHAPE aShape, const VECTOR2I& aSize )
3724 {
3725 int altLayer = CopperLayerToOrdinal( aLayer );
3726
3727 ps.SetSize( aSize, aLayer );
3728
3729 switch( aShape )
3730 {
3732 ps.SetShape( PAD_SHAPE::RECTANGLE, aLayer );
3733 break;
3734
3736 if( aElem.sizeAndShape
3738 {
3739 ps.SetShape( PAD_SHAPE::ROUNDRECT, aLayer ); // 100 = round, 0 = rectangular
3740 double ratio = aElem.sizeAndShape->cornerradius[altLayer] / 200.;
3741 ps.SetRoundRectRadiusRatio( ratio, aLayer );
3742 }
3743 else if( aElem.topsize.x == aElem.topsize.y )
3744 {
3745 ps.SetShape( PAD_SHAPE::CIRCLE, aLayer );
3746 }
3747 else
3748 {
3749 ps.SetShape( PAD_SHAPE::OVAL, aLayer );
3750 }
3751
3752 break;
3753
3755 ps.SetShape( PAD_SHAPE::CHAMFERED_RECT, aLayer );
3757 ps.SetChamferRatio( 0.25, aLayer );
3758 break;
3759
3761 default:
3762 if( !m_footprintName.IsEmpty() )
3763 {
3764 if( m_reporter )
3765 {
3766 wxString msg;
3767 msg.Printf( _( "Error loading library '%s':\n"
3768 "Footprint %s pad %s uses an unknown pad shape." ),
3769 m_library,
3771 aElem.name );
3772 m_reporter->Report( msg, RPT_SEVERITY_DEBUG );
3773 }
3774 }
3775 else
3776 {
3777 if( m_reporter )
3778 {
3779 wxString msg;
3780 msg.Printf( _( "Footprint %s pad %s uses an unknown pad shape." ),
3781 aFootprint->GetReference(),
3782 aElem.name );
3783 m_reporter->Report( msg, RPT_SEVERITY_DEBUG );
3784 }
3785 }
3786 break;
3787 }
3788 };
3789
3790 switch( aElem.padmode )
3791 {
3794 setCopperGeometry( PADSTACK::ALL_LAYERS, aElem.topshape, aElem.topsize );
3795 break;
3796
3799 setCopperGeometry( F_Cu, aElem.topshape, aElem.topsize );
3800 setCopperGeometry( PADSTACK::INNER_LAYERS, aElem.midshape, aElem.midsize );
3801 setCopperGeometry( B_Cu, aElem.botshape, aElem.botsize );
3802 break;
3803
3806
3807 setCopperGeometry( F_Cu, aElem.topshape, aElem.topsize );
3808 setCopperGeometry( B_Cu, aElem.botshape, aElem.botsize );
3809 setCopperGeometry( In1_Cu, aElem.midshape, aElem.midsize );
3810
3811 if( aElem.sizeAndShape )
3812 {
3813 size_t i = 0;
3814
3815 LSET intLayers = aFootprint->BoardLayerSet();
3816 intLayers &= LSET::InternalCuMask();
3817 intLayers.set( In1_Cu, false ); // Already handled above
3818
3819 for( PCB_LAYER_ID layer : intLayers )
3820 {
3821 setCopperGeometry( layer, aElem.sizeAndShape->inner_shape[i],
3822 VECTOR2I( aElem.sizeAndShape->inner_size[i].x,
3823 aElem.sizeAndShape->inner_size[i].y ) );
3824 i++;
3825 }
3826 }
3827
3828 break;
3829 }
3830
3831 switch( aElem.layer )
3832 {
3834 pad->SetLayer( F_Cu );
3835 pad->SetLayerSet( PAD::SMDMask() );
3836 break;
3837
3839 pad->SetLayer( B_Cu );
3840 pad->SetLayerSet( PAD::SMDMask().FlipStandardLayers() );
3841 break;
3842
3844 pad->SetLayerSet( aElem.plated ? PAD::PTHMask() : PAD::UnplatedHoleMask() );
3845 break;
3846
3847 default:
3848 PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
3849 pad->SetLayer( klayer );
3850 pad->SetLayerSet( LSET( { klayer } ) );
3851 break;
3852 }
3853
3855 pad->SetLocalSolderPasteMargin( aElem.pastemaskexpansionmanual );
3856
3858 pad->SetLocalSolderMaskMargin( aElem.soldermaskexpansionmanual );
3859
3860 if( aElem.is_tent_top )
3861 pad->SetLayerSet( pad->GetLayerSet().reset( F_Mask ) );
3862
3863 if( aElem.is_tent_bottom )
3864 pad->SetLayerSet( pad->GetLayerSet().reset( B_Mask ) );
3865
3866 pad->SetPadToDieLength( aElem.pad_to_die_length );
3867 pad->SetPadToDieDelay( aElem.pad_to_die_delay );
3868
3869 aFootprint->Add( pad.release(), ADD_MODE::APPEND );
3870}
3871
3872
3874{
3875 PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
3876
3877 if( klayer == UNDEFINED_LAYER )
3878 {
3879 if( m_reporter )
3880 {
3881 wxString msg;
3882 msg.Printf( _( "Non-copper pad %s found on an Altium layer (%d) with no KiCad "
3883 "equivalent. It has been moved to KiCad layer Eco1_User." ),
3884 aElem.name, aElem.layer );
3885 m_reporter->Report( msg, RPT_SEVERITY_INFO );
3886 }
3887
3888 klayer = Eco1_User;
3889 }
3890
3891 std::unique_ptr<PCB_SHAPE> pad = std::make_unique<PCB_SHAPE>( m_board );
3892
3893 HelperParsePad6NonCopper( aElem, klayer, pad.get() );
3894
3895 m_board->Add( pad.release(), ADD_MODE::APPEND );
3896}
3897
3898
3900{
3901 PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
3902
3903 if( klayer == UNDEFINED_LAYER )
3904 {
3905 if( !m_footprintName.IsEmpty() )
3906 {
3907 if( m_reporter )
3908 {
3909 wxString msg;
3910 msg.Printf( _( "Loading library '%s':\n"
3911 "Footprint %s non-copper pad %s found on an Altium layer (%d) with no "
3912 "KiCad equivalent. It has been moved to KiCad layer Eco1_User." ),
3913 m_library,
3915 aElem.name,
3916 aElem.layer );
3917 m_reporter->Report( msg, RPT_SEVERITY_INFO );
3918 }
3919 }
3920 else
3921 {
3922 if( m_reporter )
3923 {
3924 wxString msg;
3925 msg.Printf( _( "Footprint %s non-copper pad %s found on an Altium layer (%d) with no "
3926 "KiCad equivalent. It has been moved to KiCad layer Eco1_User." ),
3927 aFootprint->GetReference(),
3928 aElem.name,
3929 aElem.layer );
3930 m_reporter->Report( msg, RPT_SEVERITY_INFO );
3931 }
3932 }
3933
3934 klayer = Eco1_User;
3935 }
3936
3937 std::unique_ptr<PCB_SHAPE> pad = std::make_unique<PCB_SHAPE>( aFootprint );
3938
3939 HelperParsePad6NonCopper( aElem, klayer, pad.get() );
3940
3941 aFootprint->Add( pad.release(), ADD_MODE::APPEND );
3942}
3943
3944
3946 PCB_SHAPE* aShape )
3947{
3948 if( aElem.net != ALTIUM_NET_UNCONNECTED )
3949 {
3950 if( m_reporter )
3951 {
3952 wxString msg;
3953 msg.Printf( _( "Non-copper pad %s is connected to a net, which is not supported." ),
3954 aElem.name );
3955 m_reporter->Report( msg, RPT_SEVERITY_DEBUG );
3956 }
3957 }
3958
3959 if( aElem.holesize != 0 )
3960 {
3961 if( m_reporter )
3962 {
3963 wxString msg;
3964 msg.Printf( _( "Non-copper pad %s has a hole, which is not supported." ), aElem.name );
3965 m_reporter->Report( msg, RPT_SEVERITY_DEBUG );
3966 }
3967 }
3968
3969 if( aElem.padmode != ALTIUM_PAD_MODE::SIMPLE )
3970 {
3971 if( m_reporter )
3972 {
3973 wxString msg;
3974 msg.Printf( _( "Non-copper pad %s has a complex pad stack (not yet supported)." ),
3975 aElem.name );
3976 m_reporter->Report( msg, RPT_SEVERITY_DEBUG );
3977 }
3978 }
3979
3980 switch( aElem.topshape )
3981 {
3983 {
3984 // filled rect
3985 aShape->SetShape( SHAPE_T::POLY );
3986 aShape->SetFilled( true );
3987 aShape->SetLayer( aLayer );
3988 aShape->SetStroke( STROKE_PARAMS( 0 ) );
3989
3990 aShape->SetPolyPoints(
3991 { aElem.position + VECTOR2I( aElem.topsize.x / 2, aElem.topsize.y / 2 ),
3992 aElem.position + VECTOR2I( aElem.topsize.x / 2, -aElem.topsize.y / 2 ),
3993 aElem.position + VECTOR2I( -aElem.topsize.x / 2, -aElem.topsize.y / 2 ),
3994 aElem.position + VECTOR2I( -aElem.topsize.x / 2, aElem.topsize.y / 2 ) } );
3995
3996 if( aElem.direction != 0 )
3997 aShape->Rotate( aElem.position, EDA_ANGLE( aElem.direction, DEGREES_T ) );
3998 }
3999 break;
4000
4002 if( aElem.sizeAndShape
4004 {
4005 // filled roundrect
4006 int cornerradius = aElem.sizeAndShape->cornerradius[0];
4007 int offset = ( std::min( aElem.topsize.x, aElem.topsize.y ) * cornerradius ) / 200;
4008
4009 aShape->SetLayer( aLayer );
4010 aShape->SetStroke( STROKE_PARAMS( offset * 2, LINE_STYLE::SOLID ) );
4011
4012 if( cornerradius < 100 )
4013 {
4014 int offsetX = aElem.topsize.x / 2 - offset;
4015 int offsetY = aElem.topsize.y / 2 - offset;
4016
4017 VECTOR2I p11 = aElem.position + VECTOR2I( offsetX, offsetY );
4018 VECTOR2I p12 = aElem.position + VECTOR2I( offsetX, -offsetY );
4019 VECTOR2I p22 = aElem.position + VECTOR2I( -offsetX, -offsetY );
4020 VECTOR2I p21 = aElem.position + VECTOR2I( -offsetX, offsetY );
4021
4022 aShape->SetShape( SHAPE_T::POLY );
4023 aShape->SetFilled( true );
4024 aShape->SetPolyPoints( { p11, p12, p22, p21 } );
4025 }
4026 else if( aElem.topsize.x == aElem.topsize.y )
4027 {
4028 // circle
4029 aShape->SetShape( SHAPE_T::CIRCLE );
4030 aShape->SetFilled( true );
4031 aShape->SetStart( aElem.position );
4032 aShape->SetEnd( aElem.position - VECTOR2I( 0, aElem.topsize.x / 4 ) );
4033 aShape->SetStroke( STROKE_PARAMS( aElem.topsize.x / 2, LINE_STYLE::SOLID ) );
4034 }
4035 else if( aElem.topsize.x < aElem.topsize.y )
4036 {
4037 // short vertical line
4038 aShape->SetShape( SHAPE_T::SEGMENT );
4039 VECTOR2I pointOffset( 0, ( aElem.topsize.y / 2 - aElem.topsize.x / 2 ) );
4040 aShape->SetStart( aElem.position + pointOffset );
4041 aShape->SetEnd( aElem.position - pointOffset );
4042 }
4043 else
4044 {
4045 // short horizontal line
4046 aShape->SetShape( SHAPE_T::SEGMENT );
4047 VECTOR2I pointOffset( ( aElem.topsize.x / 2 - aElem.topsize.y / 2 ), 0 );
4048 aShape->SetStart( aElem.position + pointOffset );
4049 aShape->SetEnd( aElem.position - pointOffset );
4050 }
4051
4052 if( aElem.direction != 0 )
4053 aShape->Rotate( aElem.position, EDA_ANGLE( aElem.direction, DEGREES_T ) );
4054 }
4055 else if( aElem.topsize.x == aElem.topsize.y )
4056 {
4057 // filled circle
4058 aShape->SetShape( SHAPE_T::CIRCLE );
4059 aShape->SetFilled( true );
4060 aShape->SetLayer( aLayer );
4061 aShape->SetStart( aElem.position );
4062 aShape->SetEnd( aElem.position - VECTOR2I( 0, aElem.topsize.x / 4 ) );
4063 aShape->SetStroke( STROKE_PARAMS( aElem.topsize.x / 2, LINE_STYLE::SOLID ) );
4064 }
4065 else
4066 {
4067 // short line
4068 aShape->SetShape( SHAPE_T::SEGMENT );
4069 aShape->SetLayer( aLayer );
4070 aShape->SetStroke( STROKE_PARAMS( std::min( aElem.topsize.x, aElem.topsize.y ),
4072
4073 if( aElem.topsize.x < aElem.topsize.y )
4074 {
4075 VECTOR2I offset( 0, ( aElem.topsize.y / 2 - aElem.topsize.x / 2 ) );
4076 aShape->SetStart( aElem.position + offset );
4077 aShape->SetEnd( aElem.position - offset );
4078 }
4079 else
4080 {
4081 VECTOR2I offset( ( aElem.topsize.x / 2 - aElem.topsize.y / 2 ), 0 );
4082 aShape->SetStart( aElem.position + offset );
4083 aShape->SetEnd( aElem.position - offset );
4084 }
4085
4086 if( aElem.direction != 0 )
4087 aShape->Rotate( aElem.position, EDA_ANGLE( aElem.direction, DEGREES_T ) );
4088 }
4089 break;
4090
4092 {
4093 // filled octagon
4094 aShape->SetShape( SHAPE_T::POLY );
4095 aShape->SetFilled( true );
4096 aShape->SetLayer( aLayer );
4097 aShape->SetStroke( STROKE_PARAMS( 0 ) );
4098
4099 VECTOR2I p11 = aElem.position + VECTOR2I( aElem.topsize.x / 2, aElem.topsize.y / 2 );
4100 VECTOR2I p12 = aElem.position + VECTOR2I( aElem.topsize.x / 2, -aElem.topsize.y / 2 );
4101 VECTOR2I p22 = aElem.position + VECTOR2I( -aElem.topsize.x / 2, -aElem.topsize.y / 2 );
4102 VECTOR2I p21 = aElem.position + VECTOR2I( -aElem.topsize.x / 2, aElem.topsize.y / 2 );
4103
4104 int chamfer = std::min( aElem.topsize.x, aElem.topsize.y ) / 4;
4105 VECTOR2I chamferX( chamfer, 0 );
4106 VECTOR2I chamferY( 0, chamfer );
4107
4108 aShape->SetPolyPoints( { p11 - chamferX, p11 - chamferY, p12 + chamferY, p12 - chamferX,
4109 p22 + chamferX, p22 + chamferY, p21 - chamferY, p21 + chamferX } );
4110
4111 if( aElem.direction != 0. )
4112 aShape->Rotate( aElem.position, EDA_ANGLE( aElem.direction, DEGREES_T ) );
4113 }
4114 break;
4115
4117 default:
4118 if( m_reporter )
4119 {
4120 wxString msg;
4121 msg.Printf( _( "Non-copper pad %s uses an unknown pad shape." ), aElem.name );
4122 m_reporter->Report( msg, RPT_SEVERITY_DEBUG );
4123 }
4124
4125 break;
4126 }
4127}
4128
4129
4131 const CFB::COMPOUND_FILE_ENTRY* aEntry )
4132{
4133 if( m_progressReporter )
4134 m_progressReporter->Report( _( "Loading vias..." ) );
4135
4136 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
4137
4138 while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
4139 {
4140 checkpoint();
4141 AVIA6 elem( reader );
4142
4143 std::unique_ptr<PCB_VIA> via = std::make_unique<PCB_VIA>( m_board );
4144
4145 via->SetPosition( elem.position );
4146 via->SetDrill( elem.holesize );
4147 via->SetNetCode( GetNetCode( elem.net ) );
4148 via->SetLocked( elem.is_locked );
4149
4150 bool start_layer_outside = elem.layer_start == ALTIUM_LAYER::TOP_LAYER
4152 bool end_layer_outside = elem.layer_end == ALTIUM_LAYER::TOP_LAYER
4154
4155 if( start_layer_outside && end_layer_outside )
4156 {
4157 via->SetViaType( VIATYPE::THROUGH );
4158 }
4159 else if( ( !start_layer_outside ) && ( !end_layer_outside ) )
4160 {
4161 via->SetViaType( VIATYPE::BURIED );
4162 }
4163 else
4164 {
4165 via->SetViaType( VIATYPE::BLIND );
4166 }
4167
4168 // TODO: Altium has a specific flag for microvias, independent of start/end layer
4169#if 0
4170 if( something )
4171 via->SetViaType( VIATYPE::MICROVIA );
4172#endif
4173
4174 PCB_LAYER_ID start_klayer = GetKicadLayer( elem.layer_start );
4175 PCB_LAYER_ID end_klayer = GetKicadLayer( elem.layer_end );
4176
4177 if( !IsCopperLayer( start_klayer ) || !IsCopperLayer( end_klayer ) )
4178 {
4179 if( m_reporter )
4180 {
4181 wxString msg;
4182 msg.Printf( _( "Via from layer %d to %d uses a non-copper layer, which is not "
4183 "supported." ),
4184 elem.layer_start,
4185 elem.layer_end );
4186 m_reporter->Report( msg, RPT_SEVERITY_DEBUG );
4187 }
4188
4189 continue; // just assume through-hole instead.
4190 }
4191
4192 // we need VIATYPE set!
4193 via->SetLayerPair( start_klayer, end_klayer );
4194
4195 switch( elem.viamode )
4196 {
4197 default:
4199 via->SetWidth( PADSTACK::ALL_LAYERS, elem.diameter );
4200 break;
4201
4203 via->Padstack().SetMode( PADSTACK::MODE::FRONT_INNER_BACK );
4204 via->SetWidth( F_Cu, elem.diameter_by_layer[0] );
4205 via->SetWidth( PADSTACK::INNER_LAYERS, elem.diameter_by_layer[1] );
4206 via->SetWidth( B_Cu, elem.diameter_by_layer[31] );
4207 break;
4208
4210 {
4211 via->Padstack().SetMode( PADSTACK::MODE::CUSTOM );
4212
4213 LSET cuLayers = m_board->GetEnabledLayers() & LSET::AllCuMask();
4214
4215 for( PCB_LAYER_ID layer : cuLayers )
4216 {
4217 int altiumLayer = CopperLayerToOrdinal( layer );
4218 wxCHECK2_MSG( altiumLayer < 32, break,
4219 "Altium importer expects 32 or fewer copper layers" );
4220
4221 via->SetWidth( layer, elem.diameter_by_layer[altiumLayer] );
4222 }
4223
4224 break;
4225 }
4226 }
4227
4229 {
4230 via->SetFrontTentingMode( elem.is_tent_top ? TENTING_MODE::TENTED
4232 via->SetBackTentingMode( elem.is_tent_bottom ? TENTING_MODE::TENTED
4234 }
4235
4236 m_board->Add( via.release(), ADD_MODE::APPEND );
4237 }
4238
4239 if( reader.GetRemainingBytes() != 0 )
4240 THROW_IO_ERROR( wxT( "Vias6 stream is not fully parsed" ) );
4241}
4242
4244 const CFB::COMPOUND_FILE_ENTRY* aEntry )
4245{
4246 if( m_progressReporter )
4247 m_progressReporter->Report( _( "Loading tracks..." ) );
4248
4249 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
4250
4251 for( int primitiveIndex = 0; reader.GetRemainingBytes() >= 4; primitiveIndex++ )
4252 {
4253 checkpoint();
4254 ATRACK6 elem( reader );
4255
4256 if( elem.component == ALTIUM_COMPONENT_NONE )
4257 {
4258 ConvertTracks6ToBoardItem( elem, primitiveIndex );
4259 }
4260 else
4261 {
4262 FOOTPRINT* footprint = HelperGetFootprint( elem.component );
4263 ConvertTracks6ToFootprintItem( footprint, elem, primitiveIndex, true );
4264 }
4265 }
4266
4267 if( reader.GetRemainingBytes() != 0 )
4268 THROW_IO_ERROR( "Tracks6 stream is not fully parsed" );
4269}
4270
4271
4272void ALTIUM_PCB::ConvertTracks6ToBoardItem( const ATRACK6& aElem, const int aPrimitiveIndex )
4273{
4274 if( aElem.polygon != ALTIUM_POLYGON_NONE && aElem.polygon != ALTIUM_POLYGON_BOARD )
4275 {
4276 if( m_polygons.size() <= aElem.polygon )
4277 {
4278 // Can happen when reading old Altium files: just skip this item
4279 if( m_reporter )
4280 {
4281 wxString msg;
4282 msg.Printf( wxT( "ATRACK6 stream tries to access polygon id %u "
4283 "of %u existing polygons; skipping it" ),
4284 static_cast<unsigned>( aElem.polygon ),
4285 static_cast<unsigned>( m_polygons.size() ) );
4286 m_reporter->Report( msg, RPT_SEVERITY_DEBUG );
4287 }
4288
4289 return;
4290 }
4291
4292 ZONE* zone = m_polygons.at( aElem.polygon );
4293
4294 if( zone == nullptr )
4295 {
4296 return; // we know the zone id, but because we do not know the layer we did not
4297 // add it!
4298 }
4299
4300 PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
4301
4302 if( klayer == UNDEFINED_LAYER )
4303 return; // Just skip it for now. Users can fill it themselves.
4304
4305 if( !zone->HasFilledPolysForLayer( klayer ) )
4306 return;
4307
4308 SHAPE_POLY_SET* fill = zone->GetFill( klayer );
4309
4310 PCB_SHAPE shape( nullptr, SHAPE_T::SEGMENT );
4311 shape.SetStart( aElem.start );
4312 shape.SetEnd( aElem.end );
4314
4315 shape.EDA_SHAPE::TransformShapeToPolygon( *fill, 0, ARC_HIGH_DEF, ERROR_INSIDE );
4316 // Will be simplified and fractured later
4317
4318 zone->SetIsFilled( true );
4319 zone->SetNeedRefill( false );
4320
4321 return;
4322 }
4323
4324 if( aElem.is_keepout || aElem.layer == ALTIUM_LAYER::KEEP_OUT_LAYER
4325 || IsAltiumLayerAPlane( aElem.layer ) )
4326 {
4327 // This is not the actual board item. We can use it to create the polygon for the region
4328 PCB_SHAPE shape( nullptr, SHAPE_T::SEGMENT );
4329 shape.SetStart( aElem.start );
4330 shape.SetEnd( aElem.end );
4332
4334 }
4335 else
4336 {
4337 for( PCB_LAYER_ID klayer : GetKicadLayersToIterate( aElem.layer ) )
4338 ConvertTracks6ToBoardItemOnLayer( aElem, klayer );
4339 }
4340
4341 for( const auto& layerExpansionMask : HelperGetSolderAndPasteMaskExpansions(
4342 ALTIUM_RECORD::TRACK, aPrimitiveIndex, aElem.layer ) )
4343 {
4344 int width = aElem.width + ( layerExpansionMask.second * 2 );
4345 if( width > 1 )
4346 {
4347 std::unique_ptr<PCB_SHAPE> seg = std::make_unique<PCB_SHAPE>( m_board, SHAPE_T::SEGMENT );
4348
4349 seg->SetStart( aElem.start );
4350 seg->SetEnd( aElem.end );
4351 seg->SetStroke( STROKE_PARAMS( width, LINE_STYLE::SOLID ) );
4352 seg->SetLayer( layerExpansionMask.first );
4353
4354 m_board->Add( seg.release(), ADD_MODE::APPEND );
4355 }
4356 }
4357}
4358
4359
4361 const int aPrimitiveIndex,
4362 const bool aIsBoardImport )
4363{
4364 if( aElem.polygon != ALTIUM_POLYGON_NONE )
4365 {
4366 wxFAIL_MSG( wxString::Format( "Altium: Unexpected footprint Track with polygon id %u",
4367 (unsigned)aElem.polygon ) );
4368 return;
4369 }
4370
4371 if( aElem.is_keepout || aElem.layer == ALTIUM_LAYER::KEEP_OUT_LAYER
4372 || IsAltiumLayerAPlane( aElem.layer ) )
4373 {
4374 // This is not the actual board item. We can use it to create the polygon for the region
4375 PCB_SHAPE shape( nullptr, SHAPE_T::SEGMENT );
4376 shape.SetStart( aElem.start );
4377 shape.SetEnd( aElem.end );
4379
4380 HelperPcpShapeAsFootprintKeepoutRegion( aFootprint, shape, aElem.layer,
4381 aElem.keepoutrestrictions );
4382 }
4383 else
4384 {
4385 for( PCB_LAYER_ID klayer : GetKicadLayersToIterate( aElem.layer ) )
4386 {
4387 if( aIsBoardImport && IsCopperLayer( klayer ) && aElem.net != ALTIUM_NET_UNCONNECTED )
4388 {
4389 // Special case: do to not lose net connections in footprints
4390 ConvertTracks6ToBoardItemOnLayer( aElem, klayer );
4391 }
4392 else
4393 {
4394 ConvertTracks6ToFootprintItemOnLayer( aFootprint, aElem, klayer );
4395 }
4396 }
4397 }
4398
4399 for( const auto& layerExpansionMask : HelperGetSolderAndPasteMaskExpansions(
4400 ALTIUM_RECORD::TRACK, aPrimitiveIndex, aElem.layer ) )
4401 {
4402 int width = aElem.width + ( layerExpansionMask.second * 2 );
4403 if( width > 1 )
4404 {
4405 std::unique_ptr<PCB_SHAPE> seg = std::make_unique<PCB_SHAPE>( aFootprint, SHAPE_T::SEGMENT );
4406
4407 seg->SetStart( aElem.start );
4408 seg->SetEnd( aElem.end );
4409 seg->SetStroke( STROKE_PARAMS( width, LINE_STYLE::SOLID ) );
4410 seg->SetLayer( layerExpansionMask.first );
4411
4412 aFootprint->Add( seg.release(), ADD_MODE::APPEND );
4413 }
4414 }
4415}
4416
4417
4419{
4420 if( IsCopperLayer( aLayer ) && aElem.net != ALTIUM_NET_UNCONNECTED )
4421 {
4422 std::unique_ptr<PCB_TRACK> track = std::make_unique<PCB_TRACK>( m_board );
4423
4424 track->SetStart( aElem.start );
4425 track->SetEnd( aElem.end );
4426 track->SetWidth( aElem.width );
4427 track->SetLayer( aLayer );
4428 track->SetNetCode( GetNetCode( aElem.net ) );
4429
4430 m_board->Add( track.release(), ADD_MODE::APPEND );
4431 }
4432 else
4433 {
4434 std::unique_ptr<PCB_SHAPE> seg = std::make_unique<PCB_SHAPE>( m_board, SHAPE_T::SEGMENT );
4435
4436 seg->SetStart( aElem.start );
4437 seg->SetEnd( aElem.end );
4438 seg->SetStroke( STROKE_PARAMS( aElem.width, LINE_STYLE::SOLID ) );
4439 seg->SetLayer( aLayer );
4440
4441 m_board->Add( seg.release(), ADD_MODE::APPEND );
4442 }
4443}
4444
4445
4447 PCB_LAYER_ID aLayer )
4448{
4449 std::unique_ptr<PCB_SHAPE> seg = std::make_unique<PCB_SHAPE>( aFootprint, SHAPE_T::SEGMENT );
4450
4451 seg->SetStart( aElem.start );
4452 seg->SetEnd( aElem.end );
4453 seg->SetStroke( STROKE_PARAMS( aElem.width, LINE_STYLE::SOLID ) );
4454 seg->SetLayer( aLayer );
4455
4456 aFootprint->Add( seg.release(), ADD_MODE::APPEND );
4457}
4458
4459
4461 const CFB::COMPOUND_FILE_ENTRY* aEntry )
4462{
4463 if( m_progressReporter )
4464 m_progressReporter->Report( _( "Loading unicode strings..." ) );
4465
4466 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
4467
4469
4470 if( reader.GetRemainingBytes() != 0 )
4471 THROW_IO_ERROR( wxT( "WideStrings6 stream is not fully parsed" ) );
4472}
4473
4475 const CFB::COMPOUND_FILE_ENTRY* aEntry )
4476{
4477 if( m_progressReporter )
4478 m_progressReporter->Report( _( "Loading text..." ) );
4479
4480 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
4481
4482 while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
4483 {
4484 checkpoint();
4485 ATEXT6 elem( reader, m_unicodeStrings );
4486
4487 if( elem.component == ALTIUM_COMPONENT_NONE )
4488 {
4490 }
4491 else
4492 {
4493 FOOTPRINT* footprint = HelperGetFootprint( elem.component );
4494 ConvertTexts6ToFootprintItem( footprint, elem );
4495 }
4496 }
4497
4498 if( reader.GetRemainingBytes() != 0 )
4499 THROW_IO_ERROR( wxT( "Texts6 stream is not fully parsed" ) );
4500}
4501
4502
4504{
4505 if( aElem.fonttype == ALTIUM_TEXT_TYPE::BARCODE )
4506 {
4507 for( PCB_LAYER_ID klayer : GetKicadLayersToIterate( aElem.layer ) )
4508 ConvertBarcodes6ToBoardItemOnLayer( aElem, klayer );
4509 return;
4510 }
4511
4512 for( PCB_LAYER_ID klayer : GetKicadLayersToIterate( aElem.layer ) )
4513 ConvertTexts6ToBoardItemOnLayer( aElem, klayer );
4514}
4515
4516
4518{
4519 if( aElem.fonttype == ALTIUM_TEXT_TYPE::BARCODE )
4520 {
4521 for( PCB_LAYER_ID klayer : GetKicadLayersToIterate( aElem.layer ) )
4522 ConvertBarcodes6ToFootprintItemOnLayer( aFootprint, aElem, klayer );
4523 return;
4524 }
4525
4526 for( PCB_LAYER_ID klayer : GetKicadLayersToIterate( aElem.layer ) )
4527 ConvertTexts6ToFootprintItemOnLayer( aFootprint, aElem, klayer );
4528}
4529
4530
4532{
4533 std::unique_ptr<PCB_TEXTBOX> pcbTextbox = std::make_unique<PCB_TEXTBOX>( m_board );
4534 std::unique_ptr<PCB_TEXT> pcbText = std::make_unique<PCB_TEXT>( m_board );
4535
4536 bool isTextbox = aElem.isFrame && !aElem.isInverted; // Textbox knockout is not supported
4537
4538 static const std::map<wxString, wxString> variableMap = {
4539 { "LAYER_NAME", "LAYER" },
4540 { "PRINT_DATE", "CURRENT_DATE"},
4541 };
4542
4543 wxString kicadText = AltiumPcbSpecialStringsToKiCadStrings( aElem.text, variableMap );
4544 BOARD_ITEM* item = pcbText.get();
4545 EDA_TEXT* text = pcbText.get();
4546
4547 if( isTextbox )
4548 {
4549 item = pcbTextbox.get();
4550 text = pcbTextbox.get();
4551
4553 HelperSetTextboxAlignmentAndPos( aElem, pcbTextbox.get() );
4554 }
4555 else
4556 {
4559 }
4560
4561 text->SetText( kicadText );
4562 item->SetLayer( aLayer );
4563 item->SetIsKnockout( aElem.isInverted );
4564
4565 if( isTextbox )
4566 m_board->Add( pcbTextbox.release(), ADD_MODE::APPEND );
4567 else
4568 m_board->Add( pcbText.release(), ADD_MODE::APPEND );
4569}
4570
4571
4573 PCB_LAYER_ID aLayer )
4574{
4575 std::unique_ptr<PCB_TEXTBOX> fpTextbox = std::make_unique<PCB_TEXTBOX>( aFootprint );
4576 std::unique_ptr<PCB_TEXT> fpText = std::make_unique<PCB_TEXT>( aFootprint );
4577
4578 BOARD_ITEM* item = fpText.get();
4579 EDA_TEXT* text = fpText.get();
4580
4581 bool isTextbox = aElem.isFrame && !aElem.isInverted; // Textbox knockout is not supported
4582 bool toAdd = false;
4583
4584 if( aElem.isDesignator )
4585 {
4586 item = &aFootprint->Reference(); // TODO: handle multiple layers
4587 text = &aFootprint->Reference();
4588 }
4589 else if( aElem.isComment )
4590 {
4591 item = &aFootprint->Value(); // TODO: handle multiple layers
4592 text = &aFootprint->Value();
4593 }
4594 else
4595 {
4596 item = fpText.get();
4597 text = fpText.get();
4598 toAdd = true;
4599 }
4600
4601 static const std::map<wxString, wxString> variableMap = {
4602 { "DESIGNATOR", "REFERENCE" },
4603 { "COMMENT", "VALUE" },
4604 { "VALUE", "ALTIUM_VALUE" },
4605 { "LAYER_NAME", "LAYER" },
4606 { "PRINT_DATE", "CURRENT_DATE"},
4607 };
4608
4609 if( isTextbox )
4610 {
4611 item = fpTextbox.get();
4612 text = fpTextbox.get();
4613
4615 HelperSetTextboxAlignmentAndPos( aElem, fpTextbox.get() );
4616 }
4617 else
4618 {
4621 }
4622
4623 wxString kicadText = AltiumPcbSpecialStringsToKiCadStrings( aElem.text, variableMap );
4624
4625 text->SetText( kicadText );
4626 text->SetKeepUpright( false );
4627 item->SetLayer( aLayer );
4628 item->SetIsKnockout( aElem.isInverted );
4629
4630 if( toAdd )
4631 {
4632 if( isTextbox )
4633 aFootprint->Add( fpTextbox.release(), ADD_MODE::APPEND );
4634 else
4635 aFootprint->Add( fpText.release(), ADD_MODE::APPEND );
4636 }
4637}
4638
4639
4641{
4642 std::unique_ptr<PCB_BARCODE> pcbBarcode = std::make_unique<PCB_BARCODE>( m_board );
4643
4644 pcbBarcode->SetLayer( aLayer );
4645 pcbBarcode->SetPosition( aElem.position );
4646 pcbBarcode->SetWidth( aElem.textbox_rect_width );
4647 pcbBarcode->SetHeight( aElem.textbox_rect_height );
4648 pcbBarcode->SetMargin( aElem.barcode_margin );
4649 pcbBarcode->SetText( aElem.text );
4650
4651 switch( aElem.barcode_type )
4652 {
4653 case ALTIUM_BARCODE_TYPE::CODE39: pcbBarcode->SetKind( BARCODE_T::CODE_39 ); break;
4654 case ALTIUM_BARCODE_TYPE::CODE128: pcbBarcode->SetKind( BARCODE_T::CODE_128 ); break;
4655 default: pcbBarcode->SetKind( BARCODE_T::CODE_39 ); break;
4656 }
4657
4658 pcbBarcode->SetIsKnockout( aElem.barcode_inverted );
4659 pcbBarcode->AssembleBarcode();
4660
4661 m_board->Add( pcbBarcode.release(), ADD_MODE::APPEND );
4662}
4663
4664
4666 PCB_LAYER_ID aLayer )
4667{
4668 std::unique_ptr<PCB_BARCODE> fpBarcode = std::make_unique<PCB_BARCODE>( aFootprint );
4669
4670 fpBarcode->SetLayer( aLayer );
4671 fpBarcode->SetPosition( aElem.position );
4672 fpBarcode->SetWidth( aElem.textbox_rect_width );
4673 fpBarcode->SetHeight( aElem.textbox_rect_height );
4674 fpBarcode->SetMargin( aElem.barcode_margin );
4675 fpBarcode->SetText( aElem.text );
4676
4677 switch( aElem.barcode_type )
4678 {
4679 case ALTIUM_BARCODE_TYPE::CODE39: fpBarcode->SetKind( BARCODE_T::CODE_39 ); break;
4680 case ALTIUM_BARCODE_TYPE::CODE128: fpBarcode->SetKind( BARCODE_T::CODE_128 ); break;
4681 default: fpBarcode->SetKind( BARCODE_T::CODE_39 ); break;
4682 }
4683
4684 fpBarcode->SetIsKnockout( aElem.barcode_inverted );
4685 fpBarcode->AssembleBarcode();
4686
4687 aFootprint->Add( fpBarcode.release(), ADD_MODE::APPEND );
4688}
4689
4690
4692{
4693 int margin = aElem.isOffsetBorder ? aElem.text_offset_width : aElem.margin_border_width;
4694
4695 // Altium textboxes do not have borders
4696 aTextbox->SetBorderEnabled( false );
4697
4698 // Calculate position
4699 VECTOR2I kposition = aElem.position;
4700
4701 if( aElem.isMirrored )
4702 kposition.x -= aElem.textbox_rect_width;
4703
4704 kposition.y -= aElem.textbox_rect_height;
4705
4706#if 0
4707 // Compensate for KiCad's textbox margin
4708 int charWidth = aTextbox->GetTextWidth();
4709 int charHeight = aTextbox->GetTextHeight();
4710
4711 VECTOR2I kicadMargin;
4712
4713 if( !aTextbox->GetFont() || aTextbox->GetFont()->IsStroke() )
4714 kicadMargin = VECTOR2I( charWidth * 0.933, charHeight * 0.67 );
4715 else
4716 kicadMargin = VECTOR2I( charWidth * 0.808, charHeight * 0.844 );
4717
4718 aTextbox->SetEnd( VECTOR2I( aElem.textbox_rect_width, aElem.textbox_rect_height )
4719 + kicadMargin * 2 - margin * 2 );
4720
4721 kposition = kposition - kicadMargin + margin;
4722#else
4723 aTextbox->SetMarginBottom( margin );
4724 aTextbox->SetMarginLeft( margin );
4725 aTextbox->SetMarginRight( margin );
4726 aTextbox->SetMarginTop( margin );
4727
4728 aTextbox->SetEnd( VECTOR2I( aElem.textbox_rect_width, aElem.textbox_rect_height ) );
4729#endif
4730
4731 RotatePoint( kposition, aElem.position, EDA_ANGLE( aElem.rotation, DEGREES_T ) );
4732
4733 aTextbox->SetPosition( kposition );
4734
4735 ALTIUM_TEXT_POSITION justification = aElem.isJustificationValid
4738
4739 switch( justification )
4740 {
4746 break;
4752 break;
4758 break;
4759 default:
4760 if( m_reporter )
4761 {
4762 wxString msg;
4763 msg.Printf( _( "Unknown textbox justification %d, aText %s" ), justification,
4764 aElem.text );
4765 m_reporter->Report( msg, RPT_SEVERITY_DEBUG );
4766 }
4767
4770 break;
4771 }
4772
4773 aTextbox->SetTextAngle( EDA_ANGLE( aElem.rotation, DEGREES_T ) );
4774}
4775
4776
4778{
4779 VECTOR2I kposition = aElem.position;
4780
4781 int margin = aElem.isOffsetBorder ? aElem.text_offset_width : aElem.margin_border_width;
4782 int rectWidth = aElem.textbox_rect_width - margin * 2;
4783 int rectHeight = aElem.height;
4784
4785 if( aElem.isMirrored )
4786 rectWidth = -rectWidth;
4787
4788 ALTIUM_TEXT_POSITION justification = aElem.isJustificationValid
4791
4792 switch( justification )
4793 {
4797
4798 kposition.y -= rectHeight;
4799 break;
4803
4804 kposition.y -= rectHeight / 2;
4805 break;
4809 break;
4813
4814 kposition.x += rectWidth / 2;
4815 kposition.y -= rectHeight;
4816 break;
4820
4821 kposition.x += rectWidth / 2;
4822 kposition.y -= rectHeight / 2;
4823 break;
4827
4828 kposition.x += rectWidth / 2;
4829 break;
4833
4834 kposition.x += rectWidth;
4835 kposition.y -= rectHeight;
4836 break;
4840
4841 kposition.x += rectWidth;
4842 kposition.y -= rectHeight / 2;
4843 break;
4847
4848 kposition.x += rectWidth;
4849 break;
4850 default:
4853 break;
4854 }
4855
4856 int charWidth = aText->GetTextWidth();
4857 int charHeight = aText->GetTextHeight();
4858
4859 // Correct for KiCad's baseline offset.
4860 // Text height and font must be set correctly before calling.
4861 if( !aText->GetFont() || aText->GetFont()->IsStroke() )
4862 {
4863 switch( aText->GetVertJustify() )
4864 {
4865 case GR_TEXT_V_ALIGN_TOP: kposition.y -= charHeight * 0.0407; break;
4866 case GR_TEXT_V_ALIGN_CENTER: kposition.y += charHeight * 0.0355; break;
4867 case GR_TEXT_V_ALIGN_BOTTOM: kposition.y += charHeight * 0.1225; break;
4868 default: break;
4869 }
4870 }
4871 else
4872 {
4873 switch( aText->GetVertJustify() )
4874 {
4875 case GR_TEXT_V_ALIGN_TOP: kposition.y -= charWidth * 0.016; break;
4876 case GR_TEXT_V_ALIGN_CENTER: kposition.y += charWidth * 0.085; break;
4877 case GR_TEXT_V_ALIGN_BOTTOM: kposition.y += charWidth * 0.17; break;
4878 default: break;
4879 }
4880 }
4881
4882 RotatePoint( kposition, aElem.position, EDA_ANGLE( aElem.rotation, DEGREES_T ) );
4883
4884 aText->SetTextPos( kposition );
4885 aText->SetTextAngle( EDA_ANGLE( aElem.rotation, DEGREES_T ) );
4886}
4887
4888
4890{
4891 aEdaText.SetTextSize( VECTOR2I( aElem.height, aElem.height ) );
4892
4894 {
4895 KIFONT::FONT* font = KIFONT::FONT::GetFont( aElem.fontname, aElem.isBold, aElem.isItalic );
4896 aEdaText.SetFont( font );
4897
4898 if( font->IsOutline() )
4899 {
4900 // TODO: why is this required? Somehow, truetype size is calculated differently
4901 if( font->GetName().Contains( wxS( "Arial" ) ) )
4902 aEdaText.SetTextSize( VECTOR2I( aElem.height * 0.63, aElem.height * 0.63 ) );
4903 else
4904 aEdaText.SetTextSize( VECTOR2I( aElem.height * 0.5, aElem.height * 0.5 ) );
4905 }
4906 }
4907
4908 aEdaText.SetTextThickness( aElem.strokewidth );
4909 aEdaText.SetBoldFlag( aElem.isBold );
4910 aEdaText.SetItalic( aElem.isItalic );
4911 aEdaText.SetMirrored( aElem.isMirrored );
4912}
4913
4914
4916 const CFB::COMPOUND_FILE_ENTRY* aEntry )
4917{
4918 if( m_progressReporter )
4919 m_progressReporter->Report( _( "Loading rectangles..." ) );
4920
4921 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
4922
4923 while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
4924 {
4925 checkpoint();
4926 AFILL6 elem( reader );
4927
4928 if( elem.component == ALTIUM_COMPONENT_NONE )
4929 {
4931 }
4932 else
4933 {
4934 FOOTPRINT* footprint = HelperGetFootprint( elem.component );
4935 ConvertFills6ToFootprintItem( footprint, elem, true );
4936 }
4937 }
4938
4939 if( reader.GetRemainingBytes() != 0 )
4940 THROW_IO_ERROR( "Fills6 stream is not fully parsed" );
4941}
4942
4943
4945{
4946 if( aElem.is_keepout || aElem.layer == ALTIUM_LAYER::KEEP_OUT_LAYER )
4947 {
4948 // This is not the actual board item. We can use it to create the polygon for the region
4949 PCB_SHAPE shape( nullptr, SHAPE_T::RECTANGLE );
4950
4951 shape.SetStart( aElem.pos1 );
4952 shape.SetEnd( aElem.pos2 );
4953 shape.SetFilled( true );
4955
4956 if( aElem.rotation != 0. )
4957 {
4958 VECTOR2I center( aElem.pos1.x / 2 + aElem.pos2.x / 2,
4959 aElem.pos1.y / 2 + aElem.pos2.y / 2 );
4960 shape.Rotate( center, EDA_ANGLE( aElem.rotation, DEGREES_T ) );
4961 }
4962
4964 }
4965 else
4966 {
4967 for( PCB_LAYER_ID klayer : GetKicadLayersToIterate( aElem.layer ) )
4968 ConvertFills6ToBoardItemOnLayer( aElem, klayer );
4969 }
4970}
4971
4972
4974 const bool aIsBoardImport )
4975{
4976 if( aElem.is_keepout
4977 || aElem.layer == ALTIUM_LAYER::KEEP_OUT_LAYER ) // TODO: what about plane layers?
4978 {
4979 // This is not the actual board item. We can use it to create the polygon for the region
4980 PCB_SHAPE shape( nullptr, SHAPE_T::RECTANGLE );
4981
4982 shape.SetStart( aElem.pos1 );
4983 shape.SetEnd( aElem.pos2 );
4984 shape.SetFilled( true );
4986
4987 if( aElem.rotation != 0. )
4988 {
4989 VECTOR2I center( aElem.pos1.x / 2 + aElem.pos2.x / 2,
4990 aElem.pos1.y / 2 + aElem.pos2.y / 2 );
4991 shape.Rotate( center, EDA_ANGLE( aElem.rotation, DEGREES_T ) );
4992 }
4993
4994 HelperPcpShapeAsFootprintKeepoutRegion( aFootprint, shape, aElem.layer,
4995 aElem.keepoutrestrictions );
4996 }
4997 else if( aIsBoardImport && IsAltiumLayerCopper( aElem.layer )
4998 && aElem.net != ALTIUM_NET_UNCONNECTED )
4999 {
5000 // Special case: do to not lose net connections in footprints
5001 for( PCB_LAYER_ID klayer : GetKicadLayersToIterate( aElem.layer ) )
5002 ConvertFills6ToBoardItemOnLayer( aElem, klayer );
5003 }
5004 else
5005 {
5006 for( PCB_LAYER_ID klayer : GetKicadLayersToIterate( aElem.layer ) )
5007 ConvertFills6ToFootprintItemOnLayer( aFootprint, aElem, klayer );
5008 }
5009}
5010
5011
5013{
5014 std::unique_ptr<PCB_SHAPE> fill = std::make_unique<PCB_SHAPE>( m_board, SHAPE_T::RECTANGLE );
5015
5016 fill->SetFilled( true );
5017 fill->SetLayer( aLayer );
5018 fill->SetStroke( STROKE_PARAMS( 0 ) );
5019
5020 fill->SetStart( aElem.pos1 );
5021 fill->SetEnd( aElem.pos2 );
5022
5023 if( IsCopperLayer( aLayer ) && aElem.net != ALTIUM_NET_UNCONNECTED )
5024 {
5025 fill->SetNetCode( GetNetCode( aElem.net ) );
5026 }
5027
5028 if( aElem.rotation != 0. )
5029 {
5030 // TODO: Do we need SHAPE_T::POLY for non 90° rotations?
5031 VECTOR2I center( aElem.pos1.x / 2 + aElem.pos2.x / 2,
5032 aElem.pos1.y / 2 + aElem.pos2.y / 2 );
5033 fill->Rotate( center, EDA_ANGLE( aElem.rotation, DEGREES_T ) );
5034 }
5035
5036 m_board->Add( fill.release(), ADD_MODE::APPEND );
5037}
5038
5039
5041 PCB_LAYER_ID aLayer )
5042{
5043 if( aLayer == F_Cu || aLayer == B_Cu )
5044 {
5045 std::unique_ptr<PAD> pad = std::make_unique<PAD>( aFootprint );
5046
5047 LSET padLayers;
5048 padLayers.set( aLayer );
5049
5050 pad->SetAttribute( PAD_ATTRIB::SMD );
5051 EDA_ANGLE rotation( aElem.rotation, DEGREES_T );
5052
5053 // Handle rotation multiples of 90 degrees
5054 if( rotation.IsCardinal() )
5055 {
5057
5058 int width = std::abs( aElem.pos2.x - aElem.pos1.x );
5059 int height = std::abs( aElem.pos2.y - aElem.pos1.y );
5060
5061 // Swap width and height for 90 or 270 degree rotations
5062 if( rotation.IsCardinal90() )
5063 std::swap( width, height );
5064
5065 pad->SetSize( PADSTACK::ALL_LAYERS, { width, height } );
5066 pad->SetPosition( aElem.pos1 / 2 + aElem.pos2 / 2 );
5067 }
5068 else
5069 {
5071
5072 int anchorSize = std::min( std::abs( aElem.pos2.x - aElem.pos1.x ),
5073 std::abs( aElem.pos2.y - aElem.pos1.y ) );
5074 VECTOR2I anchorPos = aElem.pos1;
5075
5076 pad->SetAnchorPadShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::CIRCLE );
5077 pad->SetSize( PADSTACK::ALL_LAYERS, { anchorSize, anchorSize } );
5078 pad->SetPosition( anchorPos );
5079
5080 SHAPE_POLY_SET shapePolys;
5081 shapePolys.NewOutline();
5082 shapePolys.Append( aElem.pos1.x - anchorPos.x, aElem.pos1.y - anchorPos.y );
5083 shapePolys.Append( aElem.pos2.x - anchorPos.x, aElem.pos1.y - anchorPos.y );
5084 shapePolys.Append( aElem.pos2.x - anchorPos.x, aElem.pos2.y - anchorPos.y );
5085 shapePolys.Append( aElem.pos1.x - anchorPos.x, aElem.pos2.y - anchorPos.y );
5086 shapePolys.Outline( 0 ).SetClosed( true );
5087
5088 VECTOR2I center( aElem.pos1.x / 2 + aElem.pos2.x / 2 - anchorPos.x,
5089 aElem.pos1.y / 2 + aElem.pos2.y / 2 - anchorPos.y );
5090 shapePolys.Rotate( EDA_ANGLE( aElem.rotation, DEGREES_T ), center );
5091 pad->AddPrimitivePoly( F_Cu, shapePolys, 0, true );
5092 }
5093
5094 pad->SetThermalSpokeAngle( ANGLE_90 );
5095 pad->SetLayerSet( padLayers );
5096
5097 aFootprint->Add( pad.release(), ADD_MODE::APPEND );
5098 }
5099 else
5100 {
5101 std::unique_ptr<PCB_SHAPE> fill =
5102 std::make_unique<PCB_SHAPE>( aFootprint, SHAPE_T::RECTANGLE );
5103
5104 fill->SetFilled( true );
5105 fill->SetLayer( aLayer );
5106 fill->SetStroke( STROKE_PARAMS( 0 ) );
5107
5108 fill->SetStart( aElem.pos1 );
5109 fill->SetEnd( aElem.pos2 );
5110
5111 if( aElem.rotation != 0. )
5112 {
5113 VECTOR2I center( aElem.pos1.x / 2 + aElem.pos2.x / 2,
5114 aElem.pos1.y / 2 + aElem.pos2.y / 2 );
5115 fill->Rotate( center, EDA_ANGLE( aElem.rotation, DEGREES_T ) );
5116 }
5117
5118 aFootprint->Add( fill.release(), ADD_MODE::APPEND );
5119 }
5120}
5121
5122
5123void ALTIUM_PCB::HelperSetZoneLayers( ZONE& aZone, const ALTIUM_LAYER aAltiumLayer )
5124{
5125 LSET layerSet;
5126
5127 for( PCB_LAYER_ID klayer : GetKicadLayersToIterate( aAltiumLayer ) )
5128 layerSet.set( klayer );
5129
5130 aZone.SetLayerSet( layerSet );
5131}
5132
5133
5134void ALTIUM_PCB::HelperSetZoneKeepoutRestrictions( ZONE& aZone, const uint8_t aKeepoutRestrictions )
5135{
5136 bool keepoutRestrictionVia = ( aKeepoutRestrictions & 0x01 ) != 0;
5137 bool keepoutRestrictionTrack = ( aKeepoutRestrictions & 0x02 ) != 0;
5138 bool keepoutRestrictionCopper = ( aKeepoutRestrictions & 0x04 ) != 0;
5139 bool keepoutRestrictionSMDPad = ( aKeepoutRestrictions & 0x08 ) != 0;
5140 bool keepoutRestrictionTHPad = ( aKeepoutRestrictions & 0x10 ) != 0;
5141
5142 aZone.SetDoNotAllowVias( keepoutRestrictionVia );
5143 aZone.SetDoNotAllowTracks( keepoutRestrictionTrack );
5144 aZone.SetDoNotAllowZoneFills( keepoutRestrictionCopper );
5145 aZone.SetDoNotAllowPads( keepoutRestrictionSMDPad && keepoutRestrictionTHPad );
5146 aZone.SetDoNotAllowFootprints( false );
5147}
5148
5149
5151 const ALTIUM_LAYER aAltiumLayer,
5152 const uint8_t aKeepoutRestrictions )
5153{
5154 std::unique_ptr<ZONE> zone = std::make_unique<ZONE>( m_board );
5155
5156 zone->SetIsRuleArea( true );
5157
5158 HelperSetZoneLayers( *zone, aAltiumLayer );
5159 HelperSetZoneKeepoutRestrictions( *zone, aKeepoutRestrictions );
5160
5161 aShape.EDA_SHAPE::TransformShapeToPolygon( *zone->Outline(), 0, ARC_HIGH_DEF, ERROR_INSIDE );
5162
5163 zone->SetBorderDisplayStyle( ZONE_BORDER_DISPLAY_STYLE::DIAGONAL_EDGE,
5165
5166 m_board->Add( zone.release(), ADD_MODE::APPEND );
5167}
5168
5169
5171 const PCB_SHAPE& aShape,
5172 const ALTIUM_LAYER aAltiumLayer,
5173 const uint8_t aKeepoutRestrictions )
5174{
5175 std::unique_ptr<ZONE> zone = std::make_unique<ZONE>( aFootprint );
5176
5177 zone->SetIsRuleArea( true );
5178
5179 HelperSetZoneLayers( *zone, aAltiumLayer );
5180 HelperSetZoneKeepoutRestrictions( *zone, aKeepoutRestrictions );
5181
5182 aShape.EDA_SHAPE::TransformShapeToPolygon( *zone->Outline(), 0, ARC_HIGH_DEF, ERROR_INSIDE );
5183
5184 zone->SetBorderDisplayStyle( ZONE_BORDER_DISPLAY_STYLE::DIAGONAL_EDGE,
5186
5187 // TODO: zone->SetLocalCoord(); missing?
5188 aFootprint->Add( zone.release(), ADD_MODE::APPEND );
5189}
5190
5191
5192std::vector<std::pair<PCB_LAYER_ID, int>> ALTIUM_PCB::HelperGetSolderAndPasteMaskExpansions(
5193 const ALTIUM_RECORD aType, const int aPrimitiveIndex, const ALTIUM_LAYER aAltiumLayer )
5194{
5195 if( m_extendedPrimitiveInformationMaps.count( aType ) == 0 )
5196 return {}; // there is nothing to parse
5197
5198 auto elems =
5199 m_extendedPrimitiveInformationMaps[ALTIUM_RECORD::TRACK].equal_range( aPrimitiveIndex );
5200
5201 if( elems.first == elems.second )
5202 return {}; // there is nothing to parse
5203
5204 std::vector<std::pair<PCB_LAYER_ID, int>> layerExpansionPairs;
5205
5206 for( auto it = elems.first; it != elems.second; ++it )
5207 {
5208 const AEXTENDED_PRIMITIVE_INFORMATION& pInf = it->second;
5209
5211 {
5214 {
5215 // TODO: what layers can lead to solder or paste mask usage? E.g. KEEP_OUT_LAYER and other top/bottom layers
5216 if( aAltiumLayer == ALTIUM_LAYER::TOP_LAYER
5217 || aAltiumLayer == ALTIUM_LAYER::MULTI_LAYER )
5218 {
5219 layerExpansionPairs.emplace_back( F_Mask, pInf.soldermaskexpansionmanual );
5220 }
5221
5222 if( aAltiumLayer == ALTIUM_LAYER::BOTTOM_LAYER
5223 || aAltiumLayer == ALTIUM_LAYER::MULTI_LAYER )
5224 {
5225 layerExpansionPairs.emplace_back( B_Mask, pInf.soldermaskexpansionmanual );
5226 }
5227 }
5230 {
5231 if( aAltiumLayer == ALTIUM_LAYER::TOP_LAYER
5232 || aAltiumLayer == ALTIUM_LAYER::MULTI_LAYER )
5233 {
5234 layerExpansionPairs.emplace_back( F_Paste, pInf.pastemaskexpansionmanual );
5235 }
5236
5237 if( aAltiumLayer == ALTIUM_LAYER::BOTTOM_LAYER
5238 || aAltiumLayer == ALTIUM_LAYER::MULTI_LAYER )
5239 {
5240 layerExpansionPairs.emplace_back( B_Paste, pInf.pastemaskexpansionmanual );
5241 }
5242 }
5243 }
5244 }
5245
5246 return layerExpansionPairs;
5247}
const char * name
std::string FormatPath(const std::vector< std::string > &aVectorPath)
Helper for debug logging (vector -> string)
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:129
constexpr EDA_IU_SCALE pcbIUScale
Definition base_units.h:112
LAYER_T
The allowed types of layers, same as Specctra DSN spec.
Definition board.h:185
@ LT_POWER
Definition board.h:188
@ LT_MIXED
Definition board.h:189
@ LT_JUMPER
Definition board.h:190
@ LT_SIGNAL
Definition board.h:187
#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:291
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:276
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:301
void ConvertTexts6ToFootprintItemOnLayer(FOOTPRINT *aFootprint, const ATEXT6 &aElem, PCB_LAYER_ID aLayer)
std::map< ALTIUM_LAYER, PCB_LAYER_ID > m_layermap
Definition altium_pcb.h:279
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:298
std::vector< FOOTPRINT * > m_components
Definition altium_pcb.h:274
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:287
std::vector< int > m_altiumToKicadNetcodes
Definition altium_pcb.h:278
unsigned m_totalCount
for progress reporting
Definition altium_pcb.h:295
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:282
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:283
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:293
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:277
void ConvertTexts6ToBoardItem(const ATEXT6 &aElem)
void HelperParseDimensions6Center(const ADIMENSION6 &aElem)
void HelperParseDimensions6Radial(const ADIMENSION6 &aElem)
BOARD * m_board
Definition altium_pcb.h:273
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:292
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:297
void ConvertPads6ToFootprintItemOnNonCopper(FOOTPRINT *aFootprint, const APAD6 &aElem)
void ConvertTexts6ToFootprintItem(FOOTPRINT *aFootprint, const ATEXT6 &aElem)
unsigned m_lastProgressCount
Definition altium_pcb.h:294
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:275
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:289
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:285
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:280
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:325
virtual void SetLayer(PCB_LAYER_ID aLayer)
Set the layer this item is on.
Definition board_item.h:285
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:322
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:169
virtual void SetFilled(bool aFlag)
Definition eda_shape.h:136
void SetStart(const VECTOR2I &aStart)
Definition eda_shape.h:178
const VECTOR2I & GetStart() const
Return the starting point of the graphic.
Definition eda_shape.h:174
void SetShape(SHAPE_T aShape)
Definition eda_shape.h:168
void SetEnd(const VECTOR2I &aEnd)
Definition eda_shape.h:220
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:80
int GetTextHeight() const
Definition eda_text.h:267
void SetTextSize(VECTOR2I aNewSize, bool aEnforceMinTextSize=true)
Definition eda_text.cpp:546
void SetTextPos(const VECTOR2I &aPoint)
Definition eda_text.cpp:590
KIFONT::FONT * GetFont() const
Definition eda_text.h:247
void SetMirrored(bool isMirrored)
Definition eda_text.cpp:407
int GetTextWidth() const
Definition eda_text.h:264
void SetVertJustify(GR_TEXT_V_ALIGN_T aType)
Definition eda_text.cpp:431
void SetBoldFlag(bool aBold)
Set only the bold flag, without changing the font.
Definition eda_text.cpp:392
void SetTextThickness(int aWidth)
The TextThickness is that set by the user.
Definition eda_text.cpp:298
GR_TEXT_V_ALIGN_T GetVertJustify() const
Definition eda_text.h:203
virtual void SetTextAngle(const EDA_ANGLE &aAngle)
Definition eda_text.cpp:313
void SetItalic(bool aItalic)
Set the text to be italic - this will also update the font if needed.
Definition eda_text.cpp:321
void SetFont(KIFONT::FONT *aFont)
Definition eda_text.cpp:513
void SetHorizJustify(GR_TEXT_H_ALIGN_T aType)
Definition eda_text.cpp:423
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:330
PCB_FIELD & Value()
read/write accessors:
Definition footprint.h:787
bool IsFlipped() const
Definition footprint.h:524
PCB_FIELD & Reference()
Definition footprint.h:788
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:323
const wxString & GetReference() const
Definition footprint.h:751
EMBEDDED_FILES * GetEmbeddedFiles() override
Definition footprint.h:1203
VECTOR2I GetPosition() const override
Definition footprint.h:327
VECTOR3D m_Offset
3D model offset (mm)
Definition footprint.h:122
double m_Opacity
Definition footprint.h:123
VECTOR3D m_Rotation
3D model rotation (degrees)
Definition footprint.h:121
wxString m_Filename
The 3D shape filename in 3D library.
Definition footprint.h:124
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:49
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:54
int GetNetCode() const
Definition netinfo.h:106
static const int UNCONNECTED
Constant that holds the "unconnected net" number (typically 0) all items "connected" to this net are ...
Definition netinfo.h:247
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:369
static LSET UnplatedHoleMask()
layer set for a mechanical unplated through hole pad
Definition pad.cpp:390
static LSET SMDMask()
layer set for a SMD pad on Front layer
Definition pad.cpp:376
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:92
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:73
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:546
double Distance(const VECTOR2< extended_type > &aVector) const
Compute the distance between two vectors.
Definition vector2d.h:561
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:73
void SetNeedRefill(bool aNeedRefill)
Definition zone.h:301
void SetDoNotAllowPads(bool aEnable)
Definition zone.h:738
SHAPE_POLY_SET * GetFill(PCB_LAYER_ID aLayer)
Definition zone.h:613
void SetDoNotAllowTracks(bool aEnable)
Definition zone.h:737
void SetFilledPolysList(PCB_LAYER_ID aLayer, const SHAPE_POLY_SET &aPolysList)
Set the list of filled polygons.
Definition zone.h:634
void SetIsFilled(bool isFilled)
Definition zone.h:298
bool HasFilledPolysForLayer(PCB_LAYER_ID aLayer) const
Definition zone.h:597
void SetLayerSet(const LSET &aLayerSet) override
Definition zone.cpp:556
void SetDoNotAllowVias(bool aEnable)
Definition zone.h:736
void SetDoNotAllowFootprints(bool aEnable)
Definition zone.h:739
void SetDoNotAllowZoneFills(bool aEnable)
Definition zone.h:735
static int GetDefaultHatchPitch()
Definition zone.cpp:1333
@ 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:45
@ RECTANGLE
Use RECTANGLE instead of RECT to avoid collision in a Windows header.
Definition eda_shape.h:46
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:677
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:913
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
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:88
@ PCB_DIM_ALIGNED_T
class PCB_DIM_ALIGNED, a linear dimension (graphic item)
Definition typeinfo.h:102
@ PCB_ARC_T
class PCB_ARC, an arc track segment on a copper layer
Definition typeinfo.h:98
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:695
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