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