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