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