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