KiCad PCB EDA Suite
Loading...
Searching...
No Matches
altium_pcb.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright (C) 2019-2020 Thomas Pointhuber <[email protected]>
5 * Copyright (C) 2021-2024 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 Dwgs_User;
234 case ALTIUM_LAYER::MECHANICAL_11: return Eco2_User; //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 UNDEFINED_LAYER;
238 case ALTIUM_LAYER::MECHANICAL_15: return UNDEFINED_LAYER;
239 case ALTIUM_LAYER::MECHANICAL_16: return UNDEFINED_LAYER;
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( SHAPE_POLY_SET::PM_STRICTLY_SIMPLE );
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->Fields() )
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 std::vector<INPUT_LAYER_DESC> inputLayers;
1127 std::map<wxString, ALTIUM_LAYER> altiumLayerNameMap;
1128
1129 for( size_t ii = 0; ii < aStackup.size(); )
1130 {
1131 ABOARD6_LAYER_STACKUP& curLayer = aStackup[ii];
1132 ALTIUM_LAYER layer_num = static_cast<ALTIUM_LAYER>( ii + 1 );
1133 INPUT_LAYER_DESC iLdesc;
1134
1135 if( ii >= m_board->GetCopperLayerCount() && layer_num != ALTIUM_LAYER::BOTTOM_LAYER
1136 && !( layer_num >= ALTIUM_LAYER::TOP_OVERLAY
1137 && layer_num <= ALTIUM_LAYER::BOTTOM_SOLDER )
1138 && !( layer_num >= ALTIUM_LAYER::MECHANICAL_1
1139 && layer_num <= ALTIUM_LAYER::MECHANICAL_16 ) )
1140 {
1141 if( layer_num < ALTIUM_LAYER::BOTTOM_LAYER )
1142 continue;
1143
1144 iLdesc.AutoMapLayer = PCB_LAYER_ID::UNDEFINED_LAYER;
1145 }
1146 else
1147 {
1148 iLdesc.AutoMapLayer = GetKicadLayer( layer_num );
1149 }
1150
1151 iLdesc.Name = curLayer.name;
1152 iLdesc.PermittedLayers = validRemappingLayers;
1153 iLdesc.Required = ii < m_board->GetCopperLayerCount() || layer_num == ALTIUM_LAYER::BOTTOM_LAYER;
1154
1155 inputLayers.push_back( iLdesc );
1156 altiumLayerNameMap.insert( { curLayer.name, layer_num } );
1157 m_layerNames.insert( { layer_num, curLayer.name } );
1158
1159 // Within the copper stack, the nextId can be used to hop over unused layers in a particular
1160 // Altium board. The IDs start with ALTIUM_LAYER::UNKNOWN but the first copper layer in the
1161 // array will be ALTIUM_LAYER::TOP_LAYER.
1162 if( layer_num < ALTIUM_LAYER::BOTTOM_LAYER )
1163 ii = curLayer.nextId - 1;
1164 else
1165 ++ii;
1166 }
1167
1168 if( inputLayers.size() == 0 )
1169 return;
1170
1171 // Callback:
1172 std::map<wxString, PCB_LAYER_ID> reMappedLayers = m_layerMappingHandler( inputLayers );
1173 m_layermap.clear();
1174
1175 for( std::pair<wxString, PCB_LAYER_ID> layerPair : reMappedLayers )
1176 {
1177 if( layerPair.second == PCB_LAYER_ID::UNDEFINED_LAYER )
1178 {
1179 wxFAIL_MSG( wxT( "Unexpected Layer ID" ) );
1180 continue;
1181 }
1182
1183 ALTIUM_LAYER altiumID = altiumLayerNameMap.at( layerPair.first );
1184 m_layermap.insert_or_assign( altiumID, layerPair.second );
1185 enabledLayers |= LSET( { layerPair.second } );
1186 }
1187
1188 m_board->SetEnabledLayers( enabledLayers );
1189 m_board->SetVisibleLayers( enabledLayers );
1190}
1191
1192
1193void ALTIUM_PCB::HelperCreateBoardOutline( const std::vector<ALTIUM_VERTICE>& aVertices )
1194{
1195 SHAPE_LINE_CHAIN lineChain;
1196 HelperShapeLineChainFromAltiumVertices( lineChain, aVertices );
1197
1199 LINE_STYLE::SOLID );
1200
1201 for( int i = 0; i <= lineChain.PointCount() && i != -1; i = lineChain.NextShape( i ) )
1202 {
1203 if( lineChain.IsArcStart( i ) )
1204 {
1205 const SHAPE_ARC& currentArc = lineChain.Arc( lineChain.ArcIndex( i ) );
1206 int nextShape = lineChain.NextShape( i );
1207 bool isLastShape = nextShape < 0;
1208
1209 std::unique_ptr<PCB_SHAPE> shape = std::make_unique<PCB_SHAPE>( m_board, SHAPE_T::ARC );
1210
1211 shape->SetStroke( stroke );
1212 shape->SetLayer( Edge_Cuts );
1213 shape->SetArcGeometry( currentArc.GetP0(), currentArc.GetArcMid(), currentArc.GetP1() );
1214
1215 m_board->Add( shape.release(), ADD_MODE::APPEND );
1216 }
1217 else
1218 {
1219 const SEG& seg = lineChain.Segment( i );
1220
1221 std::unique_ptr<PCB_SHAPE> shape = std::make_unique<PCB_SHAPE>( m_board, SHAPE_T::SEGMENT );
1222
1223 shape->SetStroke( stroke );
1224 shape->SetLayer( Edge_Cuts );
1225 shape->SetStart( seg.A );
1226 shape->SetEnd( seg.B );
1227
1228 m_board->Add( shape.release(), ADD_MODE::APPEND );
1229 }
1230 }
1231}
1232
1233
1235 const CFB::COMPOUND_FILE_ENTRY* aEntry )
1236{
1237 if( m_progressReporter )
1238 m_progressReporter->Report( _( "Loading netclasses..." ) );
1239
1240 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
1241
1242 while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
1243 {
1244 checkpoint();
1245 ACLASS6 elem( reader );
1246
1247 if( elem.kind == ALTIUM_CLASS_KIND::NET_CLASS )
1248 {
1249 std::shared_ptr<NETCLASS> nc = std::make_shared<NETCLASS>( elem.name );
1250
1251 for( const wxString& name : elem.names )
1252 {
1253 m_board->GetDesignSettings().m_NetSettings->SetNetclassPatternAssignment(
1254 name, nc->GetName() );
1255 }
1256
1257 if( m_board->GetDesignSettings().m_NetSettings->HasNetclass( nc->GetName() ) )
1258 {
1259 // Name conflict, happens in some unknown circumstances
1260 // unique_ptr will delete nc on this code path
1261 if( m_reporter )
1262 {
1263 wxString msg;
1264 msg.Printf( _( "More than one Altium netclass with name '%s' found. "
1265 "Only the first one will be imported." ), elem.name );
1267 }
1268 }
1269 else
1270 {
1271 m_board->GetDesignSettings().m_NetSettings->SetNetclass( nc->GetName(), nc );
1272 }
1273 }
1274 }
1275
1276 if( reader.GetRemainingBytes() != 0 )
1277 THROW_IO_ERROR( wxT( "Classes6 stream is not fully parsed" ) );
1278
1280}
1281
1282
1284 const CFB::COMPOUND_FILE_ENTRY* aEntry )
1285{
1286 if( m_progressReporter )
1287 m_progressReporter->Report( _( "Loading components..." ) );
1288
1289 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
1290
1291 uint16_t componentId = 0;
1292
1293 while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
1294 {
1295 checkpoint();
1296 ACOMPONENT6 elem( reader );
1297
1298 std::unique_ptr<FOOTPRINT> footprint = std::make_unique<FOOTPRINT>( m_board );
1299
1301
1302 footprint->SetFPID( fpID );
1303
1304 footprint->SetPosition( elem.position );
1305 footprint->SetOrientationDegrees( elem.rotation );
1306
1307 // KiCad netlisting requires parts to have non-digit + digit annotation.
1308 // If the reference begins with a number, we prepend 'UNK' (unknown) for the source designator
1309 wxString reference = elem.sourcedesignator;
1310
1311 if( reference.find_first_not_of( "0123456789" ) == wxString::npos )
1312 reference.Prepend( wxT( "UNK" ) );
1313
1314 footprint->SetReference( reference );
1315
1316 footprint->SetLocked( elem.locked );
1317 footprint->Reference().SetVisible( elem.nameon );
1318 footprint->Value().SetVisible( elem.commenton );
1319 footprint->SetLayer( elem.layer == ALTIUM_LAYER::TOP_LAYER ? F_Cu : B_Cu );
1320
1321 m_components.emplace_back( footprint.get() );
1322 m_board->Add( footprint.release(), ADD_MODE::APPEND );
1323
1324 componentId++;
1325 }
1326
1327 if( reader.GetRemainingBytes() != 0 )
1328 THROW_IO_ERROR( wxT( "Components6 stream is not fully parsed" ) );
1329}
1330
1331
1333double normalizeAngleDegrees( double Angle, double aMin, double aMax )
1334{
1335 while( Angle < aMin )
1336 Angle += 360.0;
1337
1338 while( Angle >= aMax )
1339 Angle -= 360.0;
1340
1341 return Angle;
1342}
1343
1344
1346 FOOTPRINT* aFootprint,
1347 const ACOMPONENTBODY6& aElem )
1348{
1349 if( m_progressReporter )
1350 m_progressReporter->Report( _( "Loading component 3D models..." ) );
1351
1352 if( !aElem.modelIsEmbedded )
1353 return;
1354
1355 auto model = aAltiumPcbFile.GetLibModel( aElem.modelId );
1356
1357 if( !model )
1358 {
1359 if( m_reporter )
1360 {
1361 m_reporter->Report( wxString::Format( wxT( "Model %s not found for footprint %s" ),
1362 aElem.modelId, aFootprint->GetReference() ),
1364 }
1365
1366 return;
1367 }
1368
1369 const VECTOR2I& fpPosition = aFootprint->GetPosition();
1370
1372 file->name = aElem.modelName;
1373
1374 if( file->name.IsEmpty() )
1375 file->name = model->first.name;
1376
1377 // Decompress the model data before assigning
1378 std::vector<char> decompressedData;
1379 wxMemoryInputStream compressedStream( model->second.data(), model->second.size() );
1380 wxZlibInputStream zlibStream( compressedStream );
1381
1382 // Reserve some space, assuming decompressed data is larger -- STEP file
1383 // compression is typically 5:1 using zlib like Altium does
1384 decompressedData.resize( model->second.size() * 6 );
1385 size_t offset = 0;
1386
1387 while( !zlibStream.Eof() )
1388 {
1389 zlibStream.Read( decompressedData.data() + offset, decompressedData.size() - offset );
1390 size_t bytesRead = zlibStream.LastRead();
1391
1392 if( !bytesRead )
1393 break;
1394
1395 offset += bytesRead;
1396
1397 if( offset >= decompressedData.size() )
1398 decompressedData.resize( 2 * decompressedData.size() ); // Resizing is expensive, avoid if we can
1399 }
1400
1401 decompressedData.resize( offset );
1402
1403 file->decompressedData = std::move( decompressedData );
1405
1407 aFootprint->GetEmbeddedFiles()->AddFile( file );
1408
1409 FP_3DMODEL modelSettings;
1410
1411 modelSettings.m_Filename = aFootprint->GetEmbeddedFiles()->GetEmbeddedFileLink( *file );
1412
1413 modelSettings.m_Offset.x = pcbIUScale.IUTomm( (int) aElem.modelPosition.x );
1414 modelSettings.m_Offset.y = -pcbIUScale.IUTomm( (int) aElem.modelPosition.y );
1415 modelSettings.m_Offset.z = pcbIUScale.IUTomm( (int) aElem.modelPosition.z );
1416
1417 EDA_ANGLE orientation = aFootprint->GetOrientation();
1418
1419 if( aFootprint->IsFlipped() )
1420 {
1421 modelSettings.m_Offset.y = -modelSettings.m_Offset.y;
1422 orientation = -orientation;
1423 }
1424
1425 RotatePoint( &modelSettings.m_Offset.x, &modelSettings.m_Offset.y, orientation );
1426
1427 modelSettings.m_Rotation.x = normalizeAngleDegrees( -aElem.modelRotation.x, -180, 180 );
1428 modelSettings.m_Rotation.y = normalizeAngleDegrees( -aElem.modelRotation.y, -180, 180 );
1429 modelSettings.m_Rotation.z = normalizeAngleDegrees( -aElem.modelRotation.z + aElem.rotation
1430 + orientation.AsDegrees(),
1431 -180, 180 );
1432 modelSettings.m_Opacity = aElem.body_opacity_3d;
1433
1434 aFootprint->Models().push_back( modelSettings );
1435}
1436
1437
1439 const CFB::COMPOUND_FILE_ENTRY* aEntry )
1440{
1441 if( m_progressReporter )
1442 m_progressReporter->Report( _( "Loading component 3D models..." ) );
1443
1444 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
1445
1446 while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
1447 {
1448 checkpoint();
1449 ACOMPONENTBODY6 elem( reader );
1450
1451 if( elem.component == ALTIUM_COMPONENT_NONE )
1452 continue; // TODO: we do not support components for the board yet
1453
1454 if( m_components.size() <= elem.component )
1455 {
1456 THROW_IO_ERROR( wxString::Format( wxT( "ComponentsBodies6 stream tries to access "
1457 "component id %d of %zu existing components" ),
1458 elem.component,
1459 m_components.size() ) );
1460 }
1461
1462 if( !elem.modelIsEmbedded )
1463 continue;
1464
1465 auto modelTuple = m_EmbeddedModels.find( elem.modelId );
1466
1467 if( modelTuple == m_EmbeddedModels.end() )
1468 {
1469 if( m_reporter )
1470 {
1471 wxString msg;
1472 msg.Printf( wxT( "ComponentsBodies6 stream tries to access model id %s which does "
1473 "not exist" ), elem.modelId );
1475 }
1476
1477 continue;
1478 }
1479
1480 const ALTIUM_EMBEDDED_MODEL_DATA& modelData = modelTuple->second;
1481 FOOTPRINT* footprint = m_components.at( elem.component );
1482
1484 file->name = modelData.m_modelname;
1485
1486 wxMemoryInputStream compressedStream( modelData.m_data.data(), modelData.m_data.size() );
1487 wxZlibInputStream zlibStream( compressedStream );
1488 wxMemoryOutputStream decompressedStream;
1489
1490 zlibStream.Read( decompressedStream );
1491 file->decompressedData.resize( decompressedStream.GetSize() );
1492 decompressedStream.CopyTo( file->decompressedData.data(), file->decompressedData.size() );
1493
1495 footprint->GetEmbeddedFiles()->AddFile( file );
1496
1497 FP_3DMODEL modelSettings;
1498
1499 modelSettings.m_Filename = footprint->GetEmbeddedFiles()->GetEmbeddedFileLink( *file );
1500 VECTOR2I fpPosition = footprint->GetPosition();
1501
1502 modelSettings.m_Offset.x =
1503 pcbIUScale.IUTomm( KiROUND( elem.modelPosition.x - fpPosition.x ) );
1504 modelSettings.m_Offset.y =
1505 -pcbIUScale.IUTomm( KiROUND( elem.modelPosition.y - fpPosition.y ) );
1506 modelSettings.m_Offset.z = pcbIUScale.IUTomm( KiROUND( elem.modelPosition.z ) );
1507
1508 EDA_ANGLE orientation = footprint->GetOrientation();
1509
1510 if( footprint->IsFlipped() )
1511 {
1512 modelSettings.m_Offset.y = -modelSettings.m_Offset.y;
1513 orientation = -orientation;
1514 }
1515
1516 RotatePoint( &modelSettings.m_Offset.x, &modelSettings.m_Offset.y, orientation );
1517
1518 modelSettings.m_Rotation.x = normalizeAngleDegrees( -elem.modelRotation.x, -180, 180 );
1519 modelSettings.m_Rotation.y = normalizeAngleDegrees( -elem.modelRotation.y, -180, 180 );
1520 modelSettings.m_Rotation.z = normalizeAngleDegrees( -elem.modelRotation.z + elem.rotation
1521 + orientation.AsDegrees(),
1522 -180, 180 );
1523
1524 modelSettings.m_Opacity = elem.body_opacity_3d;
1525
1526 footprint->Models().push_back( modelSettings );
1527 }
1528
1529 if( reader.GetRemainingBytes() != 0 )
1530 THROW_IO_ERROR( wxT( "ComponentsBodies6 stream is not fully parsed" ) );
1531}
1532
1533
1535{
1536 if( aElem.referencePoint.size() != 2 )
1537 THROW_IO_ERROR( wxT( "Incorrect number of reference points for linear dimension object" ) );
1538
1539 PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
1540
1541 if( klayer == UNDEFINED_LAYER )
1542 {
1543 if( m_reporter )
1544 {
1545 m_reporter->Report( wxString::Format(
1546 _( "Dimension found on an Altium layer (%d) with no KiCad equivalent. "
1547 "It has been moved to KiCad layer Eco1_User." ), aElem.layer ),
1549 }
1550
1551 klayer = Eco1_User;
1552 }
1553
1554 VECTOR2I referencePoint0 = aElem.referencePoint.at( 0 );
1555 VECTOR2I referencePoint1 = aElem.referencePoint.at( 1 );
1556
1557 std::unique_ptr<PCB_DIM_ALIGNED> dimension = std::make_unique<PCB_DIM_ALIGNED>( m_board, PCB_DIM_ALIGNED_T );
1558
1559 dimension->SetPrecision( static_cast<DIM_PRECISION>( aElem.textprecision ) );
1560 dimension->SetLayer( klayer );
1561 dimension->SetStart( referencePoint0 );
1562
1563 if( referencePoint0 != aElem.xy1 )
1564 {
1574 VECTOR2I direction = aElem.xy1 - referencePoint0;
1575 VECTOR2I directionNormalVector = VECTOR2I( -direction.y, direction.x );
1576 SEG segm1( referencePoint0, referencePoint0 + directionNormalVector );
1577 SEG segm2( referencePoint1, referencePoint1 + direction );
1578 OPT_VECTOR2I intersection( segm1.Intersect( segm2, true, true ) );
1579
1580 if( !intersection )
1581 THROW_IO_ERROR( wxT( "Invalid dimension. This should never happen." ) );
1582
1583 dimension->SetEnd( *intersection );
1584
1585 int height = direction.EuclideanNorm();
1586
1587 if( ( direction.x > 0 || direction.y < 0 ) != ( aElem.angle >= 180.0 ) )
1588 height = -height;
1589
1590 dimension->SetHeight( height );
1591 }
1592 else
1593 {
1594 dimension->SetEnd( referencePoint1 );
1595 }
1596
1597 dimension->SetLineThickness( aElem.linewidth );
1598
1599 dimension->SetUnitsFormat( DIM_UNITS_FORMAT::NO_SUFFIX );
1600 dimension->SetPrefix( aElem.textprefix );
1601
1602 // Suffix normally (but not always) holds the units
1603 wxRegEx units( wxS( "(mm)|(in)|(mils)|(thou)|(')|(\")" ), wxRE_ADVANCED );
1604
1605 if( units.Matches( aElem.textsuffix ) )
1606 dimension->SetUnitsFormat( DIM_UNITS_FORMAT::BARE_SUFFIX );
1607 else
1608 dimension->SetSuffix( aElem.textsuffix );
1609
1610 dimension->SetTextThickness( aElem.textlinewidth );
1611 dimension->SetTextSize( VECTOR2I( aElem.textheight, aElem.textheight ) );
1612 dimension->SetItalic( aElem.textitalic );
1613
1614#if 0 // we don't currently support bold; map to thicker text
1615 dimension->Text().SetBold( aElem.textbold );
1616#else
1617 if( aElem.textbold )
1618 dimension->SetTextThickness( dimension->GetTextThickness() * BOLD_FACTOR );
1619#endif
1620
1621 switch( aElem.textunit )
1622 {
1623 case ALTIUM_UNIT::INCHES:
1624 dimension->SetUnits( EDA_UNITS::INCHES );
1625 break;
1626 case ALTIUM_UNIT::MILS:
1627 dimension->SetUnits( EDA_UNITS::MILS );
1628 break;
1629 case ALTIUM_UNIT::MILLIMETERS:
1630 case ALTIUM_UNIT::CENTIMETER:
1631 dimension->SetUnits( EDA_UNITS::MILLIMETRES );
1632 break;
1633 default:
1634 break;
1635 }
1636
1637 m_board->Add( dimension.release(), ADD_MODE::APPEND );
1638}
1639
1640
1642{
1643 if( aElem.referencePoint.size() < 2 )
1644 THROW_IO_ERROR( wxT( "Not enough reference points for radial dimension object" ) );
1645
1646 PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
1647
1648 if( klayer == UNDEFINED_LAYER )
1649 {
1650 if( m_reporter )
1651 {
1652 m_reporter->Report( wxString::Format(
1653 _( "Dimension found on an Altium layer (%d) with no KiCad equivalent. "
1654 "It has been moved to KiCad layer Eco1_User." ),
1655 aElem.layer ), RPT_SEVERITY_INFO );
1656 }
1657
1658 klayer = Eco1_User;
1659 }
1660
1661 VECTOR2I referencePoint0 = aElem.referencePoint.at( 0 );
1662 VECTOR2I referencePoint1 = aElem.referencePoint.at( 1 );
1663
1664 std::unique_ptr<PCB_DIM_RADIAL> dimension = std::make_unique<PCB_DIM_RADIAL>( m_board );
1665
1666 dimension->SetPrecision( static_cast<DIM_PRECISION>( aElem.textprecision ) );
1667 dimension->SetLayer( klayer );
1668 dimension->SetStart( referencePoint0 );
1669 dimension->SetEnd( aElem.xy1 );
1670 dimension->SetLineThickness( aElem.linewidth );
1671 dimension->SetKeepTextAligned( false );
1672
1673 dimension->SetPrefix( aElem.textprefix );
1674
1675 // Suffix normally holds the units
1676 dimension->SetUnitsFormat( aElem.textsuffix.IsEmpty() ? DIM_UNITS_FORMAT::NO_SUFFIX
1677 : DIM_UNITS_FORMAT::BARE_SUFFIX );
1678
1679 switch( aElem.textunit )
1680 {
1681 case ALTIUM_UNIT::INCHES:
1682 dimension->SetUnits( EDA_UNITS::INCHES );
1683 break;
1684 case ALTIUM_UNIT::MILS:
1685 dimension->SetUnits( EDA_UNITS::MILS );
1686 break;
1687 case ALTIUM_UNIT::MILLIMETERS:
1688 case ALTIUM_UNIT::CENTIMETER:
1689 dimension->SetUnits( EDA_UNITS::MILLIMETRES );
1690 break;
1691 default:
1692 break;
1693 }
1694
1695 if( aElem.textPoint.empty() )
1696 {
1697 if( m_reporter )
1698 {
1699 m_reporter->Report( wxT( "No text position present for leader dimension object" ),
1701 }
1702
1703 return;
1704 }
1705
1706 dimension->SetTextPos( aElem.textPoint.at( 0 ) );
1707 dimension->SetTextThickness( aElem.textlinewidth );
1708 dimension->SetTextSize( VECTOR2I( aElem.textheight, aElem.textheight ) );
1709 dimension->SetItalic( aElem.textitalic );
1710
1711#if 0 // we don't currently support bold; map to thicker text
1712 dimension->SetBold( aElem.textbold );
1713#else
1714 if( aElem.textbold )
1715 dimension->SetTextThickness( dimension->GetTextThickness() * BOLD_FACTOR );
1716#endif
1717
1718 // It's unclear exactly how Altium figures it's text positioning, but this gets us reasonably
1719 // close.
1720 dimension->SetVertJustify( GR_TEXT_V_ALIGN_BOTTOM );
1721 dimension->SetHorizJustify( GR_TEXT_H_ALIGN_LEFT );
1722
1723 int yAdjust = dimension->GetTextBox().GetCenter().y - dimension->GetTextPos().y;
1724 dimension->SetTextPos( dimension->GetTextPos() + VECTOR2I( 0, yAdjust + aElem.textgap ) );
1725 dimension->SetVertJustify( GR_TEXT_V_ALIGN_CENTER );
1726
1727 m_radialDimensions.push_back( dimension.get() );
1728 m_board->Add( dimension.release(), ADD_MODE::APPEND );
1729}
1730
1731
1733{
1734 PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
1735
1736 if( klayer == UNDEFINED_LAYER )
1737 {
1738 if( m_reporter )
1739 {
1740 wxString msg;
1741 msg.Printf( _( "Dimension found on an Altium layer (%d) with no KiCad equivalent. "
1742 "It has been moved to KiCad layer Eco1_User." ), aElem.layer );
1744 }
1745
1746 klayer = Eco1_User;
1747 }
1748
1749 if( !aElem.referencePoint.empty() )
1750 {
1751 VECTOR2I referencePoint0 = aElem.referencePoint.at( 0 );
1752
1753 // line
1754 VECTOR2I last = referencePoint0;
1755 for( size_t i = 1; i < aElem.referencePoint.size(); i++ )
1756 {
1757 std::unique_ptr<PCB_SHAPE> shape = std::make_unique<PCB_SHAPE>( m_board, SHAPE_T::SEGMENT );
1758
1759 shape->SetLayer( klayer );
1760 shape->SetStroke( STROKE_PARAMS( aElem.linewidth, LINE_STYLE::SOLID ) );
1761 shape->SetStart( last );
1762 shape->SetEnd( aElem.referencePoint.at( i ) );
1763 last = aElem.referencePoint.at( i );
1764
1765 m_board->Add( shape.release(), ADD_MODE::APPEND );
1766 }
1767
1768 // arrow
1769 if( aElem.referencePoint.size() >= 2 )
1770 {
1771 VECTOR2I dirVec = aElem.referencePoint.at( 1 ) - referencePoint0;
1772
1773 if( dirVec.x != 0 || dirVec.y != 0 )
1774 {
1775 double scaling = dirVec.EuclideanNorm() / aElem.arrowsize;
1776 VECTOR2I arrVec =
1777 VECTOR2I( KiROUND( dirVec.x / scaling ), KiROUND( dirVec.y / scaling ) );
1778 RotatePoint( arrVec, EDA_ANGLE( 20.0, DEGREES_T ) );
1779
1780 {
1781 std::unique_ptr<PCB_SHAPE> shape1 =
1782 std::make_unique<PCB_SHAPE>( m_board, SHAPE_T::SEGMENT );
1783
1784 shape1->SetLayer( klayer );
1785 shape1->SetStroke( STROKE_PARAMS( aElem.linewidth, LINE_STYLE::SOLID ) );
1786 shape1->SetStart( referencePoint0 );
1787 shape1->SetEnd( referencePoint0 + arrVec );
1788
1789 m_board->Add( shape1.release(), ADD_MODE::APPEND );
1790 }
1791
1792 RotatePoint( arrVec, EDA_ANGLE( -40.0, DEGREES_T ) );
1793
1794 {
1795 std::unique_ptr<PCB_SHAPE> shape2 =
1796 std::make_unique<PCB_SHAPE>( m_board, SHAPE_T::SEGMENT );
1797
1798 shape2->SetLayer( klayer );
1799 shape2->SetStroke( STROKE_PARAMS( aElem.linewidth, LINE_STYLE::SOLID ) );
1800 shape2->SetStart( referencePoint0 );
1801 shape2->SetEnd( referencePoint0 + arrVec );
1802
1803 m_board->Add( shape2.release(), ADD_MODE::APPEND );
1804 }
1805 }
1806 }
1807 }
1808
1809 if( aElem.textPoint.empty() )
1810 {
1811 if( m_reporter )
1812 {
1813 m_reporter->Report( wxT( "No text position present for leader dimension object" ),
1815 }
1816
1817 return;
1818 }
1819
1820 std::unique_ptr<PCB_TEXT> text = std::make_unique<PCB_TEXT>( m_board );
1821
1822 text->SetText( aElem.textformat );
1823 text->SetPosition( aElem.textPoint.at( 0 ) );
1824 text->SetLayer( klayer );
1825 text->SetTextSize( VECTOR2I( aElem.textheight, aElem.textheight ) ); // TODO: parse text width
1826 text->SetTextThickness( aElem.textlinewidth );
1827 text->SetHorizJustify( GR_TEXT_H_ALIGN_LEFT );
1828 text->SetVertJustify( GR_TEXT_V_ALIGN_BOTTOM );
1829
1830 m_board->Add( text.release(), ADD_MODE::APPEND );
1831}
1832
1833
1835{
1836 PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
1837
1838 if( klayer == UNDEFINED_LAYER )
1839 {
1840 if( m_reporter )
1841 {
1842 wxString msg;
1843 msg.Printf( _( "Dimension found on an Altium layer (%d) with no KiCad equivalent. "
1844 "It has been moved to KiCad layer Eco1_User." ), aElem.layer );
1846 }
1847
1848 klayer = Eco1_User;
1849 }
1850
1851 for( size_t i = 0; i < aElem.referencePoint.size(); i++ )
1852 {
1853 std::unique_ptr<PCB_SHAPE> shape = std::make_unique<PCB_SHAPE>( m_board, SHAPE_T::SEGMENT );
1854
1855 shape->SetLayer( klayer );
1856 shape->SetStroke( STROKE_PARAMS( aElem.linewidth, LINE_STYLE::SOLID ) );
1857 shape->SetStart( aElem.referencePoint.at( i ) );
1858 // shape->SetEnd( /* TODO: seems to be based on TEXTY */ );
1859
1860 m_board->Add( shape.release(), ADD_MODE::APPEND );
1861 }
1862}
1863
1864
1866{
1867 PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
1868
1869 if( klayer == UNDEFINED_LAYER )
1870 {
1871 if( m_reporter )
1872 {
1873 wxString msg;
1874 msg.Printf( _( "Dimension found on an Altium layer (%d) with no KiCad equivalent. "
1875 "It has been moved to KiCad layer Eco1_User." ), aElem.layer );
1877 }
1878
1879 klayer = Eco1_User;
1880 }
1881
1882 VECTOR2I vec = VECTOR2I( 0, aElem.height / 2 );
1883 RotatePoint( vec, EDA_ANGLE( aElem.angle, DEGREES_T ) );
1884
1885 std::unique_ptr<PCB_DIM_CENTER> dimension = std::make_unique<PCB_DIM_CENTER>( m_board );
1886
1887 dimension->SetLayer( klayer );
1888 dimension->SetLineThickness( aElem.linewidth );
1889 dimension->SetStart( aElem.xy1 );
1890 dimension->SetEnd( aElem.xy1 + vec );
1891
1892 m_board->Add( dimension.release(), ADD_MODE::APPEND );
1893}
1894
1895
1897 const CFB::COMPOUND_FILE_ENTRY* aEntry )
1898{
1899 if( m_progressReporter )
1900 m_progressReporter->Report( _( "Loading dimension drawings..." ) );
1901
1902 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
1903
1904 while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
1905 {
1906 checkpoint();
1907 ADIMENSION6 elem( reader );
1908
1909 switch( elem.kind )
1910 {
1911 case ALTIUM_DIMENSION_KIND::LINEAR:
1913 break;
1914 case ALTIUM_DIMENSION_KIND::ANGULAR:
1915 if( m_reporter )
1916 {
1918 wxString::Format( _( "Ignored Angular dimension (not yet supported)." ) ),
1920 }
1921 break;
1922 case ALTIUM_DIMENSION_KIND::RADIAL:
1924 break;
1925 case ALTIUM_DIMENSION_KIND::LEADER:
1927 break;
1928 case ALTIUM_DIMENSION_KIND::DATUM:
1929 if( m_reporter )
1930 {
1932 wxString::Format( _( "Ignored Datum dimension (not yet supported)." ) ),
1934 }
1935 // HelperParseDimensions6Datum( elem );
1936 break;
1937 case ALTIUM_DIMENSION_KIND::BASELINE:
1938 if( m_reporter )
1939 {
1941 wxString::Format( _( "Ignored Baseline dimension (not yet supported)." ) ),
1943 }
1944 break;
1945 case ALTIUM_DIMENSION_KIND::CENTER:
1947 break;
1948 case ALTIUM_DIMENSION_KIND::LINEAR_DIAMETER:
1949 if( m_reporter )
1950 {
1952 wxString::Format( _( "Ignored Linear dimension (not yet supported)." ) ),
1954 }
1955 break;
1956 case ALTIUM_DIMENSION_KIND::RADIAL_DIAMETER:
1957 if( m_reporter )
1958 {
1960 wxString::Format( _( "Ignored Radial dimension (not yet supported)." ) ),
1962 }
1963 break;
1964 default:
1965 if( m_reporter )
1966 {
1967 wxString msg;
1968 msg.Printf( _( "Ignored dimension of kind %d (not yet supported)." ), elem.kind );
1970 }
1971 break;
1972 }
1973 }
1974
1975 if( reader.GetRemainingBytes() != 0 )
1976 THROW_IO_ERROR( wxT( "Dimensions6 stream is not fully parsed" ) );
1977}
1978
1979
1981 const CFB::COMPOUND_FILE_ENTRY* aEntry,
1982 const std::vector<std::string>& aRootDir )
1983{
1984 if( m_progressReporter )
1985 m_progressReporter->Report( _( "Loading 3D models..." ) );
1986
1987 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
1988
1989 if( reader.GetRemainingBytes() == 0 )
1990 return;
1991
1992 int idx = 0;
1993 wxString invalidChars = wxFileName::GetForbiddenChars();
1994
1995 while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
1996 {
1997 checkpoint();
1998 AMODEL elem( reader );
1999
2000 std::vector<std::string> stepPath = aRootDir;
2001 stepPath.emplace_back( std::to_string( idx ) );
2002
2003 bool validName = !elem.name.IsEmpty() && elem.name.IsAscii() &&
2004 wxString::npos == elem.name.find_first_of( invalidChars );
2005 wxString storageName = !validName ? wxString::Format( wxT( "model_%d" ), idx )
2006 : elem.name;
2007
2008 idx++;
2009
2010 const CFB::COMPOUND_FILE_ENTRY* stepEntry = aAltiumPcbFile.FindStream( stepPath );
2011
2012 if( stepEntry == nullptr )
2013 {
2014 if( m_reporter )
2015 {
2016 wxString msg;
2017 msg.Printf( _( "File not found: '%s'. 3D-model not imported." ),
2018 FormatPath( stepPath ) );
2020 }
2021
2022 continue;
2023 }
2024
2025 size_t stepSize = static_cast<size_t>( stepEntry->size );
2026 std::vector<char> stepContent( stepSize );
2027
2028 // read file into buffer
2029 aAltiumPcbFile.GetCompoundFileReader().ReadFile( stepEntry, 0, stepContent.data(),
2030 stepSize );
2031
2032 m_EmbeddedModels.insert( std::make_pair(
2033 elem.id, ALTIUM_EMBEDDED_MODEL_DATA( storageName, elem.rotation, elem.z_offset,
2034 std::move( stepContent ) ) ) );
2035 }
2036
2037 if( reader.GetRemainingBytes() != 0 )
2038 THROW_IO_ERROR( wxT( "Models stream is not fully parsed" ) );
2039}
2040
2041
2043 const CFB::COMPOUND_FILE_ENTRY* aEntry )
2044{
2045 if( m_progressReporter )
2046 m_progressReporter->Report( _( "Loading nets..." ) );
2047
2048 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
2049
2050 wxASSERT( m_altiumToKicadNetcodes.empty() );
2051
2052 while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
2053 {
2054 checkpoint();
2055 ANET6 elem( reader );
2056
2057 NETINFO_ITEM* netInfo = new NETINFO_ITEM( m_board, elem.name, 0 );
2058 m_board->Add( netInfo, ADD_MODE::APPEND );
2059
2060 // needs to be called after m_board->Add() as assign us the NetCode
2061 m_altiumToKicadNetcodes.push_back( netInfo->GetNetCode() );
2062 }
2063
2064 if( reader.GetRemainingBytes() != 0 )
2065 THROW_IO_ERROR( wxT( "Nets6 stream is not fully parsed" ) );
2066}
2067
2069 const CFB::COMPOUND_FILE_ENTRY* aEntry )
2070{
2071 if( m_progressReporter )
2072 m_progressReporter->Report( _( "Loading polygons..." ) );
2073
2074 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
2075
2076 while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
2077 {
2078 checkpoint();
2079 APOLYGON6 elem( reader );
2080
2081 SHAPE_LINE_CHAIN linechain;
2083
2084 if( linechain.PointCount() < 3 )
2085 {
2086 // We have found multiple Altium files with polygon records containing nothing but two
2087 // coincident vertices. These polygons do not appear when opening the file in Altium.
2088 // https://gitlab.com/kicad/code/kicad/-/issues/8183
2089 // Also, polygons with less than 3 points are not supported in KiCad.
2090 //
2091 // wxLogError( _( "Polygon has only %d point extracted from %ld vertices. At least 2 "
2092 // "points are required." ),
2093 // linechain.PointCount(),
2094 // elem.vertices.size() );
2095
2096 m_polygons.emplace_back( nullptr );
2097 continue;
2098 }
2099
2100 SHAPE_POLY_SET outline( linechain );
2101
2102 if( elem.hatchstyle != ALTIUM_POLYGON_HATCHSTYLE::SOLID )
2103 {
2104 // Altium "Hatched" or "None" polygon outlines have thickness, convert it to KiCad's representation.
2105 outline.Inflate( elem.trackwidth / 2, CORNER_STRATEGY::CHAMFER_ACUTE_CORNERS,
2106 ARC_HIGH_DEF, true );
2107 }
2108
2109 if( outline.OutlineCount() != 1 && m_reporter )
2110 {
2111 wxString msg;
2112 msg.Printf( _( "Polygon outline count is %d, expected 1." ), outline.OutlineCount() );
2113
2115 }
2116
2117 if( outline.OutlineCount() == 0 )
2118 continue;
2119
2120 std::unique_ptr<ZONE> zone = std::make_unique<ZONE>(m_board);
2121 m_polygons.emplace_back(zone.get());
2122
2123 zone->SetNetCode( GetNetCode( elem.net ) );
2124 zone->SetPosition( elem.vertices.at( 0 ).position );
2125 zone->SetLocked( elem.locked );
2126 zone->SetAssignedPriority( elem.pourindex > 0 ? elem.pourindex : 0 );
2127 zone->Outline()->AddOutline( outline.Outline( 0 ) );
2128
2129 HelperSetZoneLayers( *zone, elem.layer );
2130
2131 if( elem.pourindex > m_highest_pour_index )
2133
2134 const ARULE6* planeClearanceRule = GetRuleDefault( ALTIUM_RULE_KIND::PLANE_CLEARANCE );
2135 const ARULE6* zoneClearanceRule = GetRule( ALTIUM_RULE_KIND::CLEARANCE,
2136 wxT( "PolygonClearance" ) );
2137 int planeLayers = 0;
2138 int signalLayers = 0;
2139 int clearance = 0;
2140
2141 for( PCB_LAYER_ID layer : zone->GetLayerSet().Seq() )
2142 {
2143 LAYER_T layerType = m_board->GetLayerType( layer );
2144
2145 if( layerType == LT_POWER || layerType == LT_MIXED )
2146 planeLayers++;
2147
2148 if( layerType == LT_SIGNAL || layerType == LT_MIXED )
2149 signalLayers++;
2150 }
2151
2152 if( planeLayers > 0 && planeClearanceRule )
2153 clearance = std::max( clearance, planeClearanceRule->planeclearanceClearance );
2154
2155 if( signalLayers > 0 && zoneClearanceRule )
2156 clearance = std::max( clearance, zoneClearanceRule->clearanceGap );
2157
2158 if( clearance > 0 )
2159 zone->SetLocalClearance( clearance );
2160
2161 const ARULE6* polygonConnectRule = GetRuleDefault( ALTIUM_RULE_KIND::POLYGON_CONNECT );
2162
2163 if( polygonConnectRule != nullptr )
2164 {
2165 switch( polygonConnectRule->polygonconnectStyle )
2166 {
2167 case ALTIUM_CONNECT_STYLE::DIRECT:
2168 zone->SetPadConnection( ZONE_CONNECTION::FULL );
2169 break;
2170
2171 case ALTIUM_CONNECT_STYLE::NONE:
2172 zone->SetPadConnection( ZONE_CONNECTION::NONE );
2173 break;
2174
2175 default:
2176 case ALTIUM_CONNECT_STYLE::RELIEF:
2177 zone->SetPadConnection( ZONE_CONNECTION::THERMAL );
2178 break;
2179 }
2180
2181 // TODO: correct variables?
2182 zone->SetThermalReliefSpokeWidth(
2183 polygonConnectRule->polygonconnectReliefconductorwidth );
2184 zone->SetThermalReliefGap( polygonConnectRule->polygonconnectAirgapwidth );
2185
2186 if( polygonConnectRule->polygonconnectReliefconductorwidth < zone->GetMinThickness() )
2187 zone->SetMinThickness( polygonConnectRule->polygonconnectReliefconductorwidth );
2188 }
2189
2190 if( IsAltiumLayerAPlane( elem.layer ) )
2191 {
2192 // outer zone will be set to priority 0 later.
2193 zone->SetAssignedPriority( 1 );
2194
2195 // check if this is the outer zone by simply comparing the BBOX
2196 const auto& outer_plane = m_outer_plane.find( elem.layer );
2197 if( outer_plane == m_outer_plane.end()
2198 || zone->GetBoundingBox().Contains( outer_plane->second->GetBoundingBox() ) )
2199 {
2200 m_outer_plane[elem.layer] = zone.get();
2201 }
2202 }
2203
2204 if( elem.hatchstyle != ALTIUM_POLYGON_HATCHSTYLE::SOLID
2205 && elem.hatchstyle != ALTIUM_POLYGON_HATCHSTYLE::UNKNOWN )
2206 {
2207 zone->SetFillMode( ZONE_FILL_MODE::HATCH_PATTERN );
2208 zone->SetHatchThickness( elem.trackwidth );
2209
2210 if( elem.hatchstyle == ALTIUM_POLYGON_HATCHSTYLE::NONE )
2211 {
2212 // use a small hack to get us only an outline (hopefully)
2213 const BOX2I& bbox = zone->GetBoundingBox();
2214 zone->SetHatchGap( std::max( bbox.GetHeight(), bbox.GetWidth() ) );
2215 }
2216 else
2217 {
2218 zone->SetHatchGap( elem.gridsize - elem.trackwidth );
2219 }
2220
2221 if( elem.hatchstyle == ALTIUM_POLYGON_HATCHSTYLE::DEGREE_45 )
2222 zone->SetHatchOrientation( ANGLE_45 );
2223 }
2224
2225 zone->SetBorderDisplayStyle( ZONE_BORDER_DISPLAY_STYLE::DIAGONAL_EDGE,
2227
2228 m_board->Add( zone.release(), ADD_MODE::APPEND );
2229 }
2230
2231 if( reader.GetRemainingBytes() != 0 )
2232 THROW_IO_ERROR( wxT( "Polygons6 stream is not fully parsed" ) );
2233}
2234
2236 const CFB::COMPOUND_FILE_ENTRY* aEntry )
2237{
2238 if( m_progressReporter )
2239 m_progressReporter->Report( _( "Loading rules..." ) );
2240
2241 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
2242
2243 while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
2244 {
2245 checkpoint();
2246 ARULE6 elem( reader );
2247
2248 m_rules[elem.kind].emplace_back( elem );
2249 }
2250
2251 // sort rules by priority
2252 for( std::pair<const ALTIUM_RULE_KIND, std::vector<ARULE6>>& val : m_rules )
2253 {
2254 std::sort( val.second.begin(), val.second.end(),
2255 []( const ARULE6& lhs, const ARULE6& rhs )
2256 {
2257 return lhs.priority < rhs.priority;
2258 } );
2259 }
2260
2261 const ARULE6* clearanceRule = GetRuleDefault( ALTIUM_RULE_KIND::CLEARANCE );
2262 const ARULE6* trackWidthRule = GetRuleDefault( ALTIUM_RULE_KIND::WIDTH );
2263 const ARULE6* routingViasRule = GetRuleDefault( ALTIUM_RULE_KIND::ROUTING_VIAS );
2264 const ARULE6* holeSizeRule = GetRuleDefault( ALTIUM_RULE_KIND::HOLE_SIZE );
2265 const ARULE6* holeToHoleRule = GetRuleDefault( ALTIUM_RULE_KIND::HOLE_TO_HOLE_CLEARANCE );
2266
2267 if( clearanceRule )
2269
2270 if( trackWidthRule )
2271 {
2273 // TODO: construct a custom rule for preferredWidth and maxLimit values
2274 }
2275
2276 if( routingViasRule )
2277 {
2278 m_board->GetDesignSettings().m_ViasMinSize = routingViasRule->minWidth;
2280 }
2281
2282 if( holeSizeRule )
2283 {
2284 // TODO: construct a custom rule for minLimit / maxLimit values
2285 }
2286
2287 if( holeToHoleRule )
2289
2290 const ARULE6* soldermaskRule = GetRuleDefault( ALTIUM_RULE_KIND::SOLDER_MASK_EXPANSION );
2291 const ARULE6* pastemaskRule = GetRuleDefault( ALTIUM_RULE_KIND::PASTE_MASK_EXPANSION );
2292
2293 if( soldermaskRule )
2295
2296 if( pastemaskRule )
2298
2299 if( reader.GetRemainingBytes() != 0 )
2300 THROW_IO_ERROR( wxT( "Rules6 stream is not fully parsed" ) );
2301}
2302
2304 const CFB::COMPOUND_FILE_ENTRY* aEntry )
2305{
2306 if( m_progressReporter )
2307 m_progressReporter->Report( _( "Loading board regions..." ) );
2308
2309 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
2310
2311 while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
2312 {
2313 checkpoint();
2314 AREGION6 elem( reader, false );
2315
2316 // TODO: implement?
2317 }
2318
2319 if( reader.GetRemainingBytes() != 0 )
2320 THROW_IO_ERROR( wxT( "BoardRegions stream is not fully parsed" ) );
2321}
2322
2324 const CFB::COMPOUND_FILE_ENTRY* aEntry )
2325{
2326 if( m_progressReporter )
2327 m_progressReporter->Report( _( "Loading polygons..." ) );
2328
2329 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
2330
2331 /* TODO: use Header section of file */
2332 for( int primitiveIndex = 0; reader.GetRemainingBytes() >= 4; primitiveIndex++ )
2333 {
2334 checkpoint();
2335 AREGION6 elem( reader, true );
2336
2338 || elem.kind == ALTIUM_REGION_KIND::BOARD_CUTOUT )
2339 {
2340 // TODO: implement all different types for footprints
2342 }
2343 else
2344 {
2345 FOOTPRINT* footprint = HelperGetFootprint( elem.component );
2346 ConvertShapeBasedRegions6ToFootprintItem( footprint, elem, primitiveIndex );
2347 }
2348 }
2349
2350 if( reader.GetRemainingBytes() != 0 )
2351 THROW_IO_ERROR( "ShapeBasedRegions6 stream is not fully parsed" );
2352}
2353
2354
2356{
2357 if( aElem.kind == ALTIUM_REGION_KIND::BOARD_CUTOUT )
2358 {
2360 }
2361 else if( aElem.kind == ALTIUM_REGION_KIND::POLYGON_CUTOUT || aElem.is_keepout )
2362 {
2363 SHAPE_LINE_CHAIN linechain;
2365
2366 if( linechain.PointCount() < 3 )
2367 {
2368 // We have found multiple Altium files with polygon records containing nothing but
2369 // two coincident vertices. These polygons do not appear when opening the file in
2370 // Altium. https://gitlab.com/kicad/code/kicad/-/issues/8183
2371 // Also, polygons with less than 3 points are not supported in KiCad.
2372 return;
2373 }
2374
2375 std::unique_ptr<ZONE> zone = std::make_unique<ZONE>( m_board );
2376
2377 zone->SetIsRuleArea( true );
2378
2379 if( aElem.is_keepout )
2380 {
2382 }
2383 else if( aElem.kind == ALTIUM_REGION_KIND::POLYGON_CUTOUT )
2384 {
2385 zone->SetDoNotAllowCopperPour( true );
2386 zone->SetDoNotAllowVias( false );
2387 zone->SetDoNotAllowTracks( false );
2388 zone->SetDoNotAllowPads( false );
2389 zone->SetDoNotAllowFootprints( false );
2390 }
2391
2392 zone->SetPosition( aElem.outline.at( 0 ).position );
2393 zone->Outline()->AddOutline( linechain );
2394
2395 HelperSetZoneLayers( *zone, aElem.layer );
2396
2397 zone->SetBorderDisplayStyle( ZONE_BORDER_DISPLAY_STYLE::DIAGONAL_EDGE,
2399
2400 m_board->Add( zone.release(), ADD_MODE::APPEND );
2401 }
2402 else if( aElem.kind == ALTIUM_REGION_KIND::DASHED_OUTLINE )
2403 {
2404 PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
2405
2406 if( klayer == UNDEFINED_LAYER )
2407 {
2408 if( m_reporter )
2409 {
2410 wxString msg;
2411 msg.Printf( _( "Dashed outline found on an Altium layer (%d) with no KiCad equivalent. "
2412 "It has been moved to KiCad layer Eco1_User." ), aElem.layer );
2414 }
2415
2416 klayer = Eco1_User;
2417 }
2418
2419 SHAPE_LINE_CHAIN linechain;
2421
2422 if( linechain.PointCount() < 3 )
2423 {
2424 // We have found multiple Altium files with polygon records containing nothing but
2425 // two coincident vertices. These polygons do not appear when opening the file in
2426 // Altium. https://gitlab.com/kicad/code/kicad/-/issues/8183
2427 // Also, polygons with less than 3 points are not supported in KiCad.
2428 return;
2429 }
2430
2431 std::unique_ptr<PCB_SHAPE> shape = std::make_unique<PCB_SHAPE>( m_board, SHAPE_T::POLY );
2432
2433 shape->SetPolyShape( linechain );
2434 shape->SetFilled( false );
2435 shape->SetLayer( klayer );
2436 shape->SetStroke( STROKE_PARAMS( pcbIUScale.mmToIU( 0.1 ), LINE_STYLE::DASH ) );
2437
2438 m_board->Add( shape.release(), ADD_MODE::APPEND );
2439 }
2440 else if( aElem.kind == ALTIUM_REGION_KIND::COPPER )
2441 {
2442 if( aElem.polygon == ALTIUM_POLYGON_NONE )
2443 {
2444 for( PCB_LAYER_ID klayer : GetKicadLayersToIterate( aElem.layer ) )
2446 }
2447 }
2448 else if( aElem.kind == ALTIUM_REGION_KIND::BOARD_CUTOUT )
2449 {
2451 }
2452 else
2453 {
2454 if( m_reporter )
2455 {
2456 wxString msg;
2457 msg.Printf( _( "Ignored polygon shape of kind %d (not yet supported)." ), aElem.kind );
2459 }
2460 }
2461}
2462
2463
2465 const AREGION6& aElem,
2466 const int aPrimitiveIndex )
2467{
2468 if( aElem.kind == ALTIUM_REGION_KIND::POLYGON_CUTOUT || aElem.is_keepout )
2469 {
2470 SHAPE_LINE_CHAIN linechain;
2472
2473 if( linechain.PointCount() < 3 )
2474 {
2475 // We have found multiple Altium files with polygon records containing nothing but
2476 // two coincident vertices. These polygons do not appear when opening the file in
2477 // Altium. https://gitlab.com/kicad/code/kicad/-/issues/8183
2478 // Also, polygons with less than 3 points are not supported in KiCad.
2479 return;
2480 }
2481
2482 std::unique_ptr<ZONE> zone = std::make_unique<ZONE>( aFootprint );
2483
2484 zone->SetIsRuleArea( true );
2485
2486 if( aElem.is_keepout )
2487 {
2489 }
2490 else if( aElem.kind == ALTIUM_REGION_KIND::POLYGON_CUTOUT )
2491 {
2492 zone->SetDoNotAllowCopperPour( true );
2493 zone->SetDoNotAllowVias( false );
2494 zone->SetDoNotAllowTracks( false );
2495 zone->SetDoNotAllowPads( false );
2496 zone->SetDoNotAllowFootprints( false );
2497 }
2498
2499 zone->SetPosition( aElem.outline.at( 0 ).position );
2500 zone->Outline()->AddOutline( linechain );
2501
2502 HelperSetZoneLayers( *zone, aElem.layer );
2503
2504 zone->SetBorderDisplayStyle( ZONE_BORDER_DISPLAY_STYLE::DIAGONAL_EDGE,
2506
2507 aFootprint->Add( zone.release(), ADD_MODE::APPEND );
2508 }
2509 else if( aElem.kind == ALTIUM_REGION_KIND::COPPER )
2510 {
2511 if( aElem.polygon == ALTIUM_POLYGON_NONE )
2512 {
2513 for( PCB_LAYER_ID klayer : GetKicadLayersToIterate( aElem.layer ) )
2514 {
2515 ConvertShapeBasedRegions6ToFootprintItemOnLayer( aFootprint, aElem, klayer,
2516 aPrimitiveIndex );
2517 }
2518 }
2519 }
2520 else if( aElem.kind == ALTIUM_REGION_KIND::DASHED_OUTLINE
2521 || aElem.kind == ALTIUM_REGION_KIND::BOARD_CUTOUT )
2522 {
2523 PCB_LAYER_ID klayer = aElem.kind == ALTIUM_REGION_KIND::BOARD_CUTOUT
2524 ? Edge_Cuts
2525 : GetKicadLayer( aElem.layer );
2526
2527 if( klayer == UNDEFINED_LAYER )
2528 {
2529 if( !m_footprintName.IsEmpty() )
2530 {
2531 if( m_reporter )
2532 {
2533 wxString msg;
2534 msg.Printf( _( "Loading library '%s':\n"
2535 "Footprint %s contains a dashed outline on Altium layer (%d) with "
2536 "no KiCad equivalent. It has been moved to KiCad layer Eco1_User." ),
2537 m_library,
2539 aElem.layer );
2541 }
2542 }
2543 else
2544 {
2545 if( m_reporter )
2546 {
2547 wxString msg;
2548 msg.Printf( _( "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 aFootprint->GetReference(),
2551 aElem.layer );
2553 }
2554 }
2555
2556 klayer = Eco1_User;
2557 }
2558
2559 SHAPE_LINE_CHAIN linechain;
2561
2562 if( linechain.PointCount() < 3 )
2563 {
2564 // We have found multiple Altium files with polygon records containing nothing but
2565 // two coincident vertices. These polygons do not appear when opening the file in
2566 // Altium. https://gitlab.com/kicad/code/kicad/-/issues/8183
2567 // Also, polygons with less than 3 points are not supported in KiCad.
2568 return;
2569 }
2570
2571 std::unique_ptr<PCB_SHAPE> shape = std::make_unique<PCB_SHAPE>( aFootprint, SHAPE_T::POLY );
2572
2573 shape->SetPolyShape( linechain );
2574 shape->SetFilled( false );
2575 shape->SetLayer( klayer );
2576
2577 if( aElem.kind == ALTIUM_REGION_KIND::DASHED_OUTLINE )
2578 shape->SetStroke( STROKE_PARAMS( pcbIUScale.mmToIU( 0.1 ), LINE_STYLE::DASH ) );
2579 else
2580 shape->SetStroke( STROKE_PARAMS( pcbIUScale.mmToIU( 0.1 ), LINE_STYLE::SOLID ) );
2581
2582 aFootprint->Add( shape.release(), ADD_MODE::APPEND );
2583 }
2584 else
2585 {
2586 if( !m_footprintName.IsEmpty() )
2587 {
2588 if( m_reporter )
2589 {
2590 wxString msg;
2591 msg.Printf( _( "Error loading library '%s':\n"
2592 "Footprint %s contains polygon shape of kind %d (not yet supported)." ),
2593 m_library,
2595 aElem.kind );
2597 }
2598 }
2599 else
2600 {
2601 if( m_reporter )
2602 {
2603 wxString msg;
2604 msg.Printf( _( "Footprint %s contains polygon shape of kind %d (not yet supported)." ),
2605 aFootprint->GetReference(),
2606 aElem.kind );
2608 }
2609 }
2610 }
2611}
2612
2613
2615 PCB_LAYER_ID aLayer )
2616{
2617 SHAPE_LINE_CHAIN linechain;
2619
2620 if( linechain.PointCount() < 3 )
2621 {
2622 // We have found multiple Altium files with polygon records containing nothing
2623 // but two coincident vertices. These polygons do not appear when opening the
2624 // file in Altium. https://gitlab.com/kicad/code/kicad/-/issues/8183
2625 // Also, polygons with less than 3 points are not supported in KiCad.
2626 return;
2627 }
2628
2629 SHAPE_POLY_SET polySet;
2630 polySet.AddOutline( linechain );
2631
2632 for( const std::vector<ALTIUM_VERTICE>& hole : aElem.holes )
2633 {
2634 SHAPE_LINE_CHAIN hole_linechain;
2635 HelperShapeLineChainFromAltiumVertices( hole_linechain, hole );
2636
2637 if( hole_linechain.PointCount() < 3 )
2638 continue;
2639
2640 polySet.AddHole( hole_linechain );
2641 }
2642
2643 std::unique_ptr<PCB_SHAPE> shape = std::make_unique<PCB_SHAPE>( m_board, SHAPE_T::POLY );
2644
2645 shape->SetPolyShape( polySet );
2646 shape->SetFilled( true );
2647 shape->SetLayer( aLayer );
2648 shape->SetStroke( STROKE_PARAMS( 0 ) );
2649
2650 if( IsCopperLayer( aLayer ) && aElem.net != ALTIUM_NET_UNCONNECTED )
2651 {
2652 shape->SetNetCode( GetNetCode( aElem.net ) );
2653 }
2654
2655 m_board->Add( shape.release(), ADD_MODE::APPEND );
2656}
2657
2658
2660 const AREGION6& aElem,
2661 PCB_LAYER_ID aLayer,
2662 const int aPrimitiveIndex )
2663{
2664 SHAPE_LINE_CHAIN linechain;
2666
2667 if( linechain.PointCount() < 3 )
2668 {
2669 // We have found multiple Altium files with polygon records containing nothing
2670 // but two coincident vertices. These polygons do not appear when opening the
2671 // file in Altium. https://gitlab.com/kicad/code/kicad/-/issues/8183
2672 // Also, polygons with less than 3 points are not supported in KiCad.
2673 return;
2674 }
2675
2676 SHAPE_POLY_SET polySet;
2677 polySet.AddOutline( linechain );
2678
2679 for( const std::vector<ALTIUM_VERTICE>& hole : aElem.holes )
2680 {
2681 SHAPE_LINE_CHAIN hole_linechain;
2682 HelperShapeLineChainFromAltiumVertices( hole_linechain, hole );
2683
2684 if( hole_linechain.PointCount() < 3 )
2685 continue;
2686
2687 polySet.AddHole( hole_linechain );
2688 }
2689
2690 if( aLayer == F_Cu || aLayer == B_Cu )
2691 {
2692 // TODO(JE) padstacks -- not sure what should happen here yet
2693 std::unique_ptr<PAD> pad = std::make_unique<PAD>( aFootprint );
2694
2695 LSET padLayers;
2696 padLayers.set( aLayer );
2697
2698 pad->SetAttribute( PAD_ATTRIB::SMD );
2699 pad->SetShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::CUSTOM );
2700 pad->SetThermalSpokeAngle( ANGLE_90 );
2701
2702 int anchorSize = 1;
2703 VECTOR2I anchorPos = linechain.CPoint( 0 );
2704
2705 pad->SetAnchorPadShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::CIRCLE );
2706 pad->SetSize( PADSTACK::ALL_LAYERS, { anchorSize, anchorSize } );
2707 pad->SetPosition( anchorPos );
2708
2709 SHAPE_POLY_SET shapePolys = polySet;
2710 shapePolys.Move( -anchorPos );
2711 pad->AddPrimitivePoly( PADSTACK::ALL_LAYERS, shapePolys, 0, true );
2712
2713 auto& map = m_extendedPrimitiveInformationMaps[ALTIUM_RECORD::REGION];
2714 auto it = map.find( aPrimitiveIndex );
2715
2716 if( it != map.end() )
2717 {
2718 const AEXTENDED_PRIMITIVE_INFORMATION& info = it->second;
2719
2720 if( info.pastemaskexpansionmode == ALTIUM_MODE::MANUAL )
2721 {
2722 pad->SetLocalSolderPasteMargin(
2723 info.pastemaskexpansionmanual ? info.pastemaskexpansionmanual : 1 );
2724 }
2725
2726 if( info.soldermaskexpansionmode == ALTIUM_MODE::MANUAL )
2727 {
2728 pad->SetLocalSolderMaskMargin(
2729 info.soldermaskexpansionmanual ? info.soldermaskexpansionmanual : 1 );
2730 }
2731
2732 if( info.pastemaskexpansionmode != ALTIUM_MODE::NONE )
2733 padLayers.set( aLayer == F_Cu ? F_Paste : B_Paste );
2734
2735 if( info.soldermaskexpansionmode != ALTIUM_MODE::NONE )
2736 padLayers.set( aLayer == F_Cu ? F_Mask : B_Mask );
2737 }
2738
2739 pad->SetLayerSet( padLayers );
2740
2741 aFootprint->Add( pad.release(), ADD_MODE::APPEND );
2742 }
2743 else
2744 {
2745 std::unique_ptr<PCB_SHAPE> shape = std::make_unique<PCB_SHAPE>( aFootprint, SHAPE_T::POLY );
2746
2747 shape->SetPolyShape( polySet );
2748 shape->SetFilled( true );
2749 shape->SetLayer( aLayer );
2750 shape->SetStroke( STROKE_PARAMS( 0 ) );
2751
2752 aFootprint->Add( shape.release(), ADD_MODE::APPEND );
2753 }
2754}
2755
2756
2758 const CFB::COMPOUND_FILE_ENTRY* aEntry )
2759{
2760 if( m_progressReporter )
2761 m_progressReporter->Report( _( "Loading zone fills..." ) );
2762
2763 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
2764
2765 while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
2766 {
2767 checkpoint();
2768 AREGION6 elem( reader, false );
2769
2770 if( elem.polygon != ALTIUM_POLYGON_NONE )
2771 {
2772 if( m_polygons.size() <= elem.polygon )
2773 {
2774 THROW_IO_ERROR( wxString::Format( "Region stream tries to access polygon id %d "
2775 "of %d existing polygons.",
2776 elem.polygon,
2777 m_polygons.size() ) );
2778 }
2779
2780 ZONE* zone = m_polygons.at( elem.polygon );
2781
2782 if( zone == nullptr )
2783 {
2784 continue; // we know the zone id, but because we do not know the layer we did not
2785 // add it!
2786 }
2787
2788 PCB_LAYER_ID klayer = GetKicadLayer( elem.layer );
2789
2790 if( klayer == UNDEFINED_LAYER )
2791 continue; // Just skip it for now. Users can fill it themselves.
2792
2793 SHAPE_LINE_CHAIN linechain;
2794
2795 for( const ALTIUM_VERTICE& vertice : elem.outline )
2796 linechain.Append( vertice.position );
2797
2798 linechain.Append( elem.outline.at( 0 ).position );
2799 linechain.SetClosed( true );
2800
2801 SHAPE_POLY_SET fill;
2802 fill.AddOutline( linechain );
2803
2804 for( const std::vector<ALTIUM_VERTICE>& hole : elem.holes )
2805 {
2806 SHAPE_LINE_CHAIN hole_linechain;
2807
2808 for( const ALTIUM_VERTICE& vertice : hole )
2809 hole_linechain.Append( vertice.position );
2810
2811 hole_linechain.Append( hole.at( 0 ).position );
2812 hole_linechain.SetClosed( true );
2813 fill.AddHole( hole_linechain );
2814 }
2815
2816 if( zone->HasFilledPolysForLayer( klayer ) )
2817 fill.BooleanAdd( *zone->GetFill( klayer ), SHAPE_POLY_SET::PM_STRICTLY_SIMPLE );
2818
2820
2821 zone->SetFilledPolysList( klayer, fill );
2822 zone->SetIsFilled( true );
2823 zone->SetNeedRefill( false );
2824 }
2825 }
2826
2827 if( reader.GetRemainingBytes() != 0 )
2828 THROW_IO_ERROR( wxT( "Regions6 stream is not fully parsed" ) );
2829}
2830
2831
2833 const CFB::COMPOUND_FILE_ENTRY* aEntry )
2834{
2835 if( m_progressReporter )
2836 m_progressReporter->Report( _( "Loading arcs..." ) );
2837
2838 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
2839
2840 for( int primitiveIndex = 0; reader.GetRemainingBytes() >= 4; primitiveIndex++ )
2841 {
2842 checkpoint();
2843 AARC6 elem( reader );
2844
2845 if( elem.component == ALTIUM_COMPONENT_NONE )
2846 {
2847 ConvertArcs6ToBoardItem( elem, primitiveIndex );
2848 }
2849 else
2850 {
2851 FOOTPRINT* footprint = HelperGetFootprint( elem.component );
2852 ConvertArcs6ToFootprintItem( footprint, elem, primitiveIndex, true );
2853 }
2854 }
2855
2856 if( reader.GetRemainingBytes() != 0 )
2857 THROW_IO_ERROR( "Arcs6 stream is not fully parsed" );
2858}
2859
2860
2862{
2863 if( aElem.startangle == 0. && aElem.endangle == 360. )
2864 {
2865 aShape->SetShape( SHAPE_T::CIRCLE );
2866
2867 // TODO: other variants to define circle?
2868 aShape->SetStart( aElem.center );
2869 aShape->SetEnd( aElem.center - VECTOR2I( 0, aElem.radius ) );
2870 }
2871 else
2872 {
2873 aShape->SetShape( SHAPE_T::ARC );
2874
2875 EDA_ANGLE includedAngle( aElem.endangle - aElem.startangle, DEGREES_T );
2876 EDA_ANGLE startAngle( aElem.endangle, DEGREES_T );
2877
2878 VECTOR2I startOffset = VECTOR2I( KiROUND( startAngle.Cos() * aElem.radius ),
2879 -KiROUND( startAngle.Sin() * aElem.radius ) );
2880
2881 aShape->SetCenter( aElem.center );
2882 aShape->SetStart( aElem.center + startOffset );
2883 aShape->SetArcAngleAndEnd( includedAngle.Normalize(), true );
2884 }
2885}
2886
2887
2888void ALTIUM_PCB::ConvertArcs6ToBoardItem( const AARC6& aElem, const int aPrimitiveIndex )
2889{
2890 if( aElem.polygon != ALTIUM_POLYGON_NONE && aElem.polygon != ALTIUM_POLYGON_BOARD )
2891 {
2892 if( m_polygons.size() <= aElem.polygon )
2893 {
2894 THROW_IO_ERROR( wxString::Format( "Tracks stream tries to access polygon id %u "
2895 "of %zu existing polygons.",
2896 aElem.polygon, m_polygons.size() ) );
2897 }
2898
2899 ZONE* zone = m_polygons.at( aElem.polygon );
2900
2901 if( zone == nullptr )
2902 {
2903 return; // we know the zone id, but because we do not know the layer we did not
2904 // add it!
2905 }
2906
2907 PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
2908
2909 if( klayer == UNDEFINED_LAYER )
2910 return; // Just skip it for now. Users can fill it themselves.
2911
2912 if( !zone->HasFilledPolysForLayer( klayer ) )
2913 return;
2914
2915 SHAPE_POLY_SET* fill = zone->GetFill( klayer );
2916
2917 // This is not the actual board item. We can use it to create the polygon for the region
2918 PCB_SHAPE shape( nullptr );
2919
2920 ConvertArcs6ToPcbShape( aElem, &shape );
2921 shape.SetStroke( STROKE_PARAMS( aElem.width, LINE_STYLE::SOLID ) );
2922
2923 shape.EDA_SHAPE::TransformShapeToPolygon( *fill, 0, ARC_HIGH_DEF, ERROR_INSIDE );
2924 // Will be simplified and fractured later
2925
2926 zone->SetIsFilled( true );
2927 zone->SetNeedRefill( false );
2928
2929 return;
2930 }
2931
2932 if( aElem.is_keepout || aElem.layer == ALTIUM_LAYER::KEEP_OUT_LAYER
2933 || IsAltiumLayerAPlane( aElem.layer ) )
2934 {
2935 // This is not the actual board item. We can use it to create the polygon for the region
2936 PCB_SHAPE shape( nullptr );
2937
2938 ConvertArcs6ToPcbShape( aElem, &shape );
2939 shape.SetStroke( STROKE_PARAMS( aElem.width, LINE_STYLE::SOLID ) );
2940
2942 }
2943 else
2944 {
2945 for( PCB_LAYER_ID klayer : GetKicadLayersToIterate( aElem.layer ) )
2946 ConvertArcs6ToBoardItemOnLayer( aElem, klayer );
2947 }
2948
2949 for( const auto& layerExpansionMask :
2950 HelperGetSolderAndPasteMaskExpansions( ALTIUM_RECORD::ARC, aPrimitiveIndex, aElem.layer ) )
2951 {
2952 int width = aElem.width + ( layerExpansionMask.second * 2 );
2953
2954 if( width > 1 )
2955 {
2956 std::unique_ptr<PCB_SHAPE> arc = std::make_unique<PCB_SHAPE>( m_board );
2957
2958 ConvertArcs6ToPcbShape( aElem, arc.get() );
2959 arc->SetStroke( STROKE_PARAMS( width, LINE_STYLE::SOLID ) );
2960 arc->SetLayer( layerExpansionMask.first );
2961
2962 m_board->Add( arc.release(), ADD_MODE::APPEND );
2963 }
2964 }
2965}
2966
2967
2969 const int aPrimitiveIndex, const bool aIsBoardImport )
2970{
2971 if( aElem.polygon != ALTIUM_POLYGON_NONE )
2972 {
2973 wxFAIL_MSG( wxString::Format( "Altium: Unexpected footprint Arc with polygon id %d",
2974 aElem.polygon ) );
2975 return;
2976 }
2977
2978 if( aElem.is_keepout || aElem.layer == ALTIUM_LAYER::KEEP_OUT_LAYER
2979 || IsAltiumLayerAPlane( aElem.layer ) )
2980 {
2981 // This is not the actual board item. We can use it to create the polygon for the region
2982 PCB_SHAPE shape( nullptr );
2983
2984 ConvertArcs6ToPcbShape( aElem, &shape );
2985 shape.SetStroke( STROKE_PARAMS( aElem.width, LINE_STYLE::SOLID ) );
2986
2987 HelperPcpShapeAsFootprintKeepoutRegion( aFootprint, shape, aElem.layer,
2988 aElem.keepoutrestrictions );
2989 }
2990 else
2991 {
2992 for( PCB_LAYER_ID klayer : GetKicadLayersToIterate( aElem.layer ) )
2993 {
2994 if( aIsBoardImport && IsCopperLayer( klayer ) && aElem.net != ALTIUM_NET_UNCONNECTED )
2995 {
2996 // Special case: do to not lose net connections in footprints
2997 ConvertArcs6ToBoardItemOnLayer( aElem, klayer );
2998 }
2999 else
3000 {
3001 ConvertArcs6ToFootprintItemOnLayer( aFootprint, aElem, klayer );
3002 }
3003 }
3004 }
3005
3006 for( const auto& layerExpansionMask :
3007 HelperGetSolderAndPasteMaskExpansions( ALTIUM_RECORD::ARC, aPrimitiveIndex, aElem.layer ) )
3008 {
3009 int width = aElem.width + ( layerExpansionMask.second * 2 );
3010
3011 if( width > 1 )
3012 {
3013 std::unique_ptr<PCB_SHAPE> arc = std::make_unique<PCB_SHAPE>( aFootprint );
3014
3015 ConvertArcs6ToPcbShape( aElem, arc.get() );
3016 arc->SetStroke( STROKE_PARAMS( width, LINE_STYLE::SOLID ) );
3017 arc->SetLayer( layerExpansionMask.first );
3018
3019 aFootprint->Add( arc.release(), ADD_MODE::APPEND );
3020 }
3021 }
3022}
3023
3024
3026{
3027 if( IsCopperLayer( aLayer ) && aElem.net != ALTIUM_NET_UNCONNECTED )
3028 {
3029 EDA_ANGLE includedAngle( aElem.endangle - aElem.startangle, DEGREES_T );
3030 EDA_ANGLE startAngle( aElem.endangle, DEGREES_T );
3031
3032 includedAngle.Normalize();
3033
3034 VECTOR2I startOffset = VECTOR2I( KiROUND( startAngle.Cos() * aElem.radius ),
3035 -KiROUND( startAngle.Sin() * aElem.radius ) );
3036
3037 if( includedAngle.AsDegrees() >= 0.1 )
3038 {
3039 // TODO: This is not the actual board item. We use it for now to calculate the arc points. This could be improved!
3040 PCB_SHAPE shape( nullptr, SHAPE_T::ARC );
3041
3042 shape.SetCenter( aElem.center );
3043 shape.SetStart( aElem.center + startOffset );
3044 shape.SetArcAngleAndEnd( includedAngle, true );
3045
3046 // Create actual arc
3047 SHAPE_ARC shapeArc( shape.GetCenter(), shape.GetStart(), shape.GetArcAngle(),
3048 aElem.width );
3049 std::unique_ptr<PCB_ARC> arc = std::make_unique<PCB_ARC>( m_board, &shapeArc );
3050
3051 arc->SetWidth( aElem.width );
3052 arc->SetLayer( aLayer );
3053 arc->SetNetCode( GetNetCode( aElem.net ) );
3054
3055 m_board->Add( arc.release(), ADD_MODE::APPEND );
3056 }
3057 }
3058 else
3059 {
3060 std::unique_ptr<PCB_SHAPE> arc = std::make_unique<PCB_SHAPE>(m_board);
3061
3062 ConvertArcs6ToPcbShape( aElem, arc.get() );
3063 arc->SetStroke( STROKE_PARAMS( aElem.width, LINE_STYLE::SOLID ) );
3064 arc->SetLayer( aLayer );
3065
3066 m_board->Add( arc.release(), ADD_MODE::APPEND );
3067 }
3068}
3069
3070
3072 PCB_LAYER_ID aLayer )
3073{
3074 std::unique_ptr<PCB_SHAPE> arc = std::make_unique<PCB_SHAPE>( aFootprint );
3075
3076 ConvertArcs6ToPcbShape( aElem, arc.get() );
3077 arc->SetStroke( STROKE_PARAMS( aElem.width, LINE_STYLE::SOLID ) );
3078 arc->SetLayer( aLayer );
3079
3080 aFootprint->Add( arc.release(), ADD_MODE::APPEND );
3081}
3082
3083
3085 const CFB::COMPOUND_FILE_ENTRY* aEntry )
3086{
3087 if( m_progressReporter )
3088 m_progressReporter->Report( _( "Loading pads..." ) );
3089
3090 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
3091
3092 while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
3093 {
3094 checkpoint();
3095 APAD6 elem( reader );
3096
3097 if( elem.component == ALTIUM_COMPONENT_NONE )
3098 {
3100 }
3101 else
3102 {
3103 FOOTPRINT* footprint = HelperGetFootprint( elem.component );
3104 ConvertPads6ToFootprintItem( footprint, elem );
3105 }
3106 }
3107
3108 if( reader.GetRemainingBytes() != 0 )
3109 THROW_IO_ERROR( wxT( "Pads6 stream is not fully parsed" ) );
3110}
3111
3112
3114{
3115 // It is possible to place altium pads on non-copper layers -> we need to interpolate them using drawings!
3116 if( !IsAltiumLayerCopper( aElem.layer ) && !IsAltiumLayerAPlane( aElem.layer )
3117 && aElem.layer != ALTIUM_LAYER::MULTI_LAYER )
3118 {
3120 }
3121 else
3122 {
3123 // We cannot add a pad directly into the PCB
3124 std::unique_ptr<FOOTPRINT> footprint = std::make_unique<FOOTPRINT>( m_board );
3125 footprint->SetPosition( aElem.position );
3126
3127 ConvertPads6ToFootprintItemOnCopper( footprint.get(), aElem );
3128
3129 m_board->Add( footprint.release(), ADD_MODE::APPEND );
3130 }
3131}
3132
3133
3135{
3136 std::unique_ptr<PAD> pad = std::make_unique<PAD>( aFootprint );
3137
3138 pad->SetNumber( "" );
3139 pad->SetNetCode( GetNetCode( aElem.net ) );
3140
3141 pad->SetPosition( aElem.position );
3142 pad->SetSize( PADSTACK::ALL_LAYERS, VECTOR2I( aElem.diameter, aElem.diameter ) );
3143 pad->SetDrillSize( VECTOR2I( aElem.holesize, aElem.holesize ) );
3144 pad->SetDrillShape( PAD_DRILL_SHAPE::CIRCLE );
3145 pad->SetShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::CIRCLE );
3146 pad->SetAttribute( PAD_ATTRIB::PTH );
3147
3148 // Pads are always through holes in KiCad
3149 pad->SetLayerSet( LSET().AllCuMask() );
3150
3151 if( aElem.viamode == ALTIUM_PAD_MODE::SIMPLE )
3152 {
3153 pad->Padstack().SetMode( PADSTACK::MODE::NORMAL );
3154 }
3155 else if( aElem.viamode == ALTIUM_PAD_MODE::TOP_MIDDLE_BOTTOM )
3156 {
3157 pad->Padstack().SetMode( PADSTACK::MODE::FRONT_INNER_BACK );
3158 pad->Padstack().SetSize( VECTOR2I( aElem.diameter_by_layer[1], aElem.diameter_by_layer[1] ),
3160 }
3161 else
3162 {
3163 pad->Padstack().SetMode( PADSTACK::MODE::CUSTOM );
3164 int altiumIdx = 0;
3165
3166 for( PCB_LAYER_ID layer : LAYER_RANGE( F_Cu, B_Cu, 32 ) )
3167 {
3168 pad->Padstack().SetSize( VECTOR2I( aElem.diameter_by_layer[altiumIdx],
3169 aElem.diameter_by_layer[altiumIdx] ), layer );
3170 altiumIdx++;
3171 }
3172 }
3173
3174 if( aElem.is_tent_top )
3175 {
3176 pad->Padstack().FrontOuterLayers().has_solder_mask = true;
3177 }
3178 else
3179 {
3180 pad->Padstack().FrontOuterLayers().has_solder_mask = false;
3181 pad->SetLayerSet( pad->GetLayerSet().set( F_Mask ) );
3182 }
3183
3184 if( aElem.is_tent_bottom )
3185 {
3186 pad->Padstack().BackOuterLayers().has_solder_mask = true;
3187 }
3188 else
3189 {
3190 pad->Padstack().BackOuterLayers().has_solder_mask = false;
3191 pad->SetLayerSet( pad->GetLayerSet().set( B_Mask ) );
3192 }
3193
3194 if( aElem.is_locked )
3195 pad->SetLocked( true );
3196
3197 if( aElem.soldermask_expansion_manual )
3198 {
3199 pad->Padstack().FrontOuterLayers().solder_mask_margin = aElem.soldermask_expansion_front;
3200 pad->Padstack().BackOuterLayers().solder_mask_margin = aElem.soldermask_expansion_back;
3201 }
3202
3203
3204 aFootprint->Add( pad.release(), ADD_MODE::APPEND );
3205}
3206
3207
3209{
3210 // It is possible to place altium pads on non-copper layers -> we need to interpolate them using drawings!
3211 if( !IsAltiumLayerCopper( aElem.layer ) && !IsAltiumLayerAPlane( aElem.layer )
3212 && aElem.layer != ALTIUM_LAYER::MULTI_LAYER )
3213 {
3214 ConvertPads6ToFootprintItemOnNonCopper( aFootprint, aElem );
3215 }
3216 else
3217 {
3218 ConvertPads6ToFootprintItemOnCopper( aFootprint, aElem );
3219 }
3220}
3221
3222
3224{
3225 std::unique_ptr<PAD> pad = std::make_unique<PAD>( aFootprint );
3226
3227 pad->SetNumber( aElem.name );
3228 pad->SetNetCode( GetNetCode( aElem.net ) );
3229
3230 pad->SetPosition( aElem.position );
3231 pad->SetOrientationDegrees( aElem.direction );
3232 pad->SetThermalSpokeAngle( ANGLE_90 );
3233
3234 if( aElem.holesize == 0 )
3235 {
3236 pad->SetAttribute( PAD_ATTRIB::SMD );
3237 }
3238 else
3239 {
3240 if( aElem.layer != ALTIUM_LAYER::MULTI_LAYER )
3241 {
3242 // TODO: I assume other values are possible as well?
3243 if( !m_footprintName.IsEmpty() )
3244 {
3245 if( m_reporter )
3246 {
3247 wxString msg;
3248 msg.Printf( _( "Error loading library '%s':\n"
3249 "Footprint %s pad %s is not marked as multilayer, but is a TH pad." ),
3250 m_library,
3252 aElem.name );
3254 }
3255 }
3256 else
3257 {
3258 if( m_reporter )
3259 {
3260 wxString msg;
3261 msg.Printf( _( "Footprint %s pad %s is not marked as multilayer, but is a TH pad." ),
3262 aFootprint->GetReference(),
3263 aElem.name );
3265 }
3266 }
3267 }
3268
3269 pad->SetAttribute( aElem.plated ? PAD_ATTRIB::PTH : PAD_ATTRIB::NPTH );
3270
3271 if( !aElem.sizeAndShape || aElem.sizeAndShape->holeshape == ALTIUM_PAD_HOLE_SHAPE::ROUND )
3272 {
3273 pad->SetDrillShape( PAD_DRILL_SHAPE::CIRCLE );
3274 pad->SetDrillSize( VECTOR2I( aElem.holesize, aElem.holesize ) );
3275 }
3276 else
3277 {
3278 switch( aElem.sizeAndShape->holeshape )
3279 {
3280 case ALTIUM_PAD_HOLE_SHAPE::ROUND:
3281 wxFAIL_MSG( wxT( "Round holes are handled before the switch" ) );
3282 break;
3283
3284 case ALTIUM_PAD_HOLE_SHAPE::SQUARE:
3285 if( !m_footprintName.IsEmpty() )
3286 {
3287 if( m_reporter )
3288 {
3289 wxString msg;
3290 msg.Printf( _( "Loading library '%s':\n"
3291 "Footprint %s pad %s has a square hole (not yet supported)." ),
3292 m_library,
3294 aElem.name );
3296 }
3297 }
3298 else
3299 {
3300 if( m_reporter )
3301 {
3302 wxString msg;
3303 msg.Printf( _( "Footprint %s pad %s has a square hole (not yet supported)." ),
3304 aFootprint->GetReference(),
3305 aElem.name );
3307 }
3308 }
3309
3310 pad->SetDrillShape( PAD_DRILL_SHAPE::CIRCLE );
3311 pad->SetDrillSize( VECTOR2I( aElem.holesize, aElem.holesize ) ); // Workaround
3312 // TODO: elem.sizeAndShape->slotsize was 0 in testfile. Either use holesize in
3313 // this case or rect holes have a different id
3314 break;
3315
3316 case ALTIUM_PAD_HOLE_SHAPE::SLOT:
3317 {
3318 pad->SetDrillShape( PAD_DRILL_SHAPE::OBLONG );
3319 EDA_ANGLE slotRotation( aElem.sizeAndShape->slotrotation, DEGREES_T );
3320
3321 slotRotation.Normalize();
3322
3323 if( slotRotation.IsHorizontal() )
3324 {
3325 pad->SetDrillSize( VECTOR2I( aElem.sizeAndShape->slotsize, aElem.holesize ) );
3326 }
3327 else if( slotRotation.IsVertical() )
3328 {
3329 pad->SetDrillSize( VECTOR2I( aElem.holesize, aElem.sizeAndShape->slotsize ) );
3330 }
3331 else
3332 {
3333 if( !m_footprintName.IsEmpty() )
3334 {
3335 if( m_reporter )
3336 {
3337 wxString msg;
3338 msg.Printf( _( "Loading library '%s':\n"
3339 "Footprint %s pad %s has a hole-rotation of %f degrees. "
3340 "KiCad only supports 90 degree rotations." ),
3341 m_library,
3343 aElem.name,
3344 slotRotation.AsDegrees() );
3346 }
3347 }
3348 else
3349 {
3350 if( m_reporter )
3351 {
3352 wxString msg;
3353 msg.Printf( _( "Footprint %s pad %s has a hole-rotation of %f degrees. "
3354 "KiCad only supports 90 degree rotations." ),
3355 aFootprint->GetReference(),
3356 aElem.name,
3357 slotRotation.AsDegrees() );
3359 }
3360 }
3361 }
3362
3363 break;
3364 }
3365
3366 default:
3367 case ALTIUM_PAD_HOLE_SHAPE::UNKNOWN:
3368 if( !m_footprintName.IsEmpty() )
3369 {
3370 if( m_reporter )
3371 {
3372 wxString msg;
3373 msg.Printf( _( "Error loading library '%s':\n"
3374 "Footprint %s pad %s uses a hole of unknown kind %d." ),
3375 m_library,
3377 aElem.name,
3378 aElem.sizeAndShape->holeshape );
3380 }
3381 }
3382 else
3383 {
3384 if( m_reporter )
3385 {
3386 wxString msg;
3387 msg.Printf( _( "Footprint %s pad %s uses a hole of unknown kind %d." ),
3388 aFootprint->GetReference(),
3389 aElem.name,
3390 aElem.sizeAndShape->holeshape );
3392 }
3393 }
3394
3395 pad->SetDrillShape( PAD_DRILL_SHAPE::CIRCLE );
3396 pad->SetDrillSize( VECTOR2I( aElem.holesize, aElem.holesize ) ); // Workaround
3397 break;
3398 }
3399 }
3400
3401 if( aElem.sizeAndShape )
3402 pad->SetOffset( PADSTACK::ALL_LAYERS, aElem.sizeAndShape->holeoffset[0] );
3403 }
3404
3405 PADSTACK& ps = pad->Padstack();
3406
3407 auto setCopperGeometry =
3408 [&]( PCB_LAYER_ID aLayer, ALTIUM_PAD_SHAPE aShape, const VECTOR2I& aSize )
3409 {
3410 int altLayer = CopperLayerToOrdinal( aLayer );
3411
3412 ps.SetSize( aSize, aLayer );
3413
3414 switch( aShape )
3415 {
3416 case ALTIUM_PAD_SHAPE::RECT:
3417 ps.SetShape( PAD_SHAPE::RECTANGLE, aLayer );
3418 break;
3419
3420 case ALTIUM_PAD_SHAPE::CIRCLE:
3421 if( aElem.sizeAndShape
3422 && aElem.sizeAndShape->alt_shape[altLayer] == ALTIUM_PAD_SHAPE_ALT::ROUNDRECT )
3423 {
3424 ps.SetShape( PAD_SHAPE::ROUNDRECT, aLayer ); // 100 = round, 0 = rectangular
3425 double ratio = aElem.sizeAndShape->cornerradius[altLayer] / 200.;
3426 ps.SetRoundRectRadiusRatio( ratio, aLayer );
3427 }
3428 else if( aElem.topsize.x == aElem.topsize.y )
3429 {
3430 ps.SetShape( PAD_SHAPE::CIRCLE, aLayer );
3431 }
3432 else
3433 {
3434 ps.SetShape( PAD_SHAPE::OVAL, aLayer );
3435 }
3436
3437 break;
3438
3439 case ALTIUM_PAD_SHAPE::OCTAGONAL:
3440 ps.SetShape( PAD_SHAPE::CHAMFERED_RECT, aLayer );
3442 ps.SetChamferRatio( 0.25, aLayer );
3443 break;
3444
3445 case ALTIUM_PAD_SHAPE::UNKNOWN:
3446 default:
3447 if( !m_footprintName.IsEmpty() )
3448 {
3449 if( m_reporter )
3450 {
3451 wxString msg;
3452 msg.Printf( _( "Error loading library '%s':\n"
3453 "Footprint %s pad %s uses an unknown pad-shape." ),
3454 m_library,
3456 aElem.name );
3458 }
3459 }
3460 else
3461 {
3462 if( m_reporter )
3463 {
3464 wxString msg;
3465 msg.Printf( _( "Footprint %s pad %s uses an unknown pad-shape." ),
3466 aFootprint->GetReference(),
3467 aElem.name );
3469 }
3470 }
3471 break;
3472 }
3473 };
3474
3475 switch( aElem.padmode )
3476 {
3477 case ALTIUM_PAD_MODE::SIMPLE:
3479 setCopperGeometry( PADSTACK::ALL_LAYERS, aElem.topshape, aElem.topsize );
3480 break;
3481
3482 case ALTIUM_PAD_MODE::TOP_MIDDLE_BOTTOM:
3484 setCopperGeometry( F_Cu, aElem.topshape, aElem.topsize );
3485 setCopperGeometry( PADSTACK::INNER_LAYERS, aElem.midshape, aElem.midsize );
3486 setCopperGeometry( B_Cu, aElem.botshape, aElem.botsize );
3487 break;
3488
3489 case ALTIUM_PAD_MODE::FULL_STACK:
3491
3492 setCopperGeometry( F_Cu, aElem.topshape, aElem.topsize );
3493 setCopperGeometry( B_Cu, aElem.botshape, aElem.botsize );
3494 setCopperGeometry( In1_Cu, aElem.midshape, aElem.midsize );
3495
3496 if( aElem.sizeAndShape )
3497 {
3498 size_t i = 0;
3499
3501 {
3502 setCopperGeometry( layer, aElem.sizeAndShape->inner_shape[i],
3503 VECTOR2I( aElem.sizeAndShape->inner_size[i].x,
3504 aElem.sizeAndShape->inner_size[i].y ) );
3505 i++;
3506 }
3507 }
3508
3509 break;
3510 }
3511
3512 if( pad->GetAttribute() == PAD_ATTRIB::NPTH && pad->HasHole() )
3513 {
3514 // KiCad likes NPTH pads to be the same size & shape as their holes
3515 pad->SetShape( PADSTACK::ALL_LAYERS, pad->GetDrillShape() == PAD_DRILL_SHAPE::CIRCLE ? PAD_SHAPE::CIRCLE
3516 : PAD_SHAPE::OVAL );
3517 pad->SetSize( PADSTACK::ALL_LAYERS, pad->GetDrillSize() );
3518 }
3519
3520 switch( aElem.layer )
3521 {
3522 case ALTIUM_LAYER::TOP_LAYER:
3523 pad->SetLayer( F_Cu );
3524 pad->SetLayerSet( PAD::SMDMask() );
3525 break;
3526
3527 case ALTIUM_LAYER::BOTTOM_LAYER:
3528 pad->SetLayer( B_Cu );
3529 pad->SetLayerSet( PAD::SMDMask().Flip() );
3530 break;
3531
3532 case ALTIUM_LAYER::MULTI_LAYER:
3533 pad->SetLayerSet( aElem.plated ? PAD::PTHMask() : PAD::UnplatedHoleMask() );
3534 break;
3535
3536 default:
3537 PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
3538 pad->SetLayer( klayer );
3539 pad->SetLayerSet( LSET( { klayer } ) );
3540 break;
3541 }
3542
3543 if( aElem.pastemaskexpansionmode == ALTIUM_MODE::MANUAL )
3544 pad->SetLocalSolderPasteMargin( aElem.pastemaskexpansionmanual );
3545
3546 if( aElem.soldermaskexpansionmode == ALTIUM_MODE::MANUAL )
3547 pad->SetLocalSolderMaskMargin( aElem.soldermaskexpansionmanual );
3548
3549 if( aElem.is_tent_top )
3550 pad->SetLayerSet( pad->GetLayerSet().reset( F_Mask ) );
3551
3552 if( aElem.is_tent_bottom )
3553 pad->SetLayerSet( pad->GetLayerSet().reset( B_Mask ) );
3554
3555 aFootprint->Add( pad.release(), ADD_MODE::APPEND );
3556}
3557
3558
3560{
3561 PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
3562
3563 if( klayer == UNDEFINED_LAYER )
3564 {
3565 if( m_reporter )
3566 {
3567 wxString msg;
3568 msg.Printf( _( "Non-copper pad %s found on an Altium layer (%d) with no KiCad "
3569 "equivalent. It has been moved to KiCad layer Eco1_User." ),
3570 aElem.name, aElem.layer );
3572 }
3573
3574 klayer = Eco1_User;
3575 }
3576
3577 std::unique_ptr<PCB_SHAPE> pad = std::make_unique<PCB_SHAPE>( m_board );
3578
3579 HelperParsePad6NonCopper( aElem, klayer, pad.get() );
3580
3581 m_board->Add( pad.release(), ADD_MODE::APPEND );
3582}
3583
3584
3586{
3587 PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
3588
3589 if( klayer == UNDEFINED_LAYER )
3590 {
3591 if( !m_footprintName.IsEmpty() )
3592 {
3593 if( m_reporter )
3594 {
3595 wxString msg;
3596 msg.Printf( _( "Loading library '%s':\n"
3597 "Footprint %s non-copper pad %s found on an Altium layer (%d) with no "
3598 "KiCad equivalent. It has been moved to KiCad layer Eco1_User." ),
3599 m_library,
3601 aElem.name,
3602 aElem.layer );
3604 }
3605 }
3606 else
3607 {
3608 if( m_reporter )
3609 {
3610 wxString msg;
3611 msg.Printf( _( "Footprint %s non-copper pad %s found on an Altium layer (%d) with no "
3612 "KiCad equivalent. It has been moved to KiCad layer Eco1_User." ),
3613 aFootprint->GetReference(),
3614 aElem.name,
3615 aElem.layer );
3617 }
3618 }
3619
3620 klayer = Eco1_User;
3621 }
3622
3623 std::unique_ptr<PCB_SHAPE> pad = std::make_unique<PCB_SHAPE>( aFootprint );
3624
3625 HelperParsePad6NonCopper( aElem, klayer, pad.get() );
3626
3627 aFootprint->Add( pad.release(), ADD_MODE::APPEND );
3628}
3629
3630
3632 PCB_SHAPE* aShape )
3633{
3634 if( aElem.net != ALTIUM_NET_UNCONNECTED )
3635 {
3636 if( m_reporter )
3637 {
3638 wxString msg;
3639 msg.Printf( _( "Non-copper pad %s is connected to a net, which is not supported." ),
3640 aElem.name );
3642 }
3643 }
3644
3645 if( aElem.holesize != 0 )
3646 {
3647 if( m_reporter )
3648 {
3649 wxString msg;
3650 msg.Printf( _( "Non-copper pad %s has a hole, which is not supported." ), aElem.name );
3652 }
3653 }
3654
3655 if( aElem.padmode != ALTIUM_PAD_MODE::SIMPLE )
3656 {
3657 if( m_reporter )
3658 {
3659 wxString msg;
3660 msg.Printf( _( "Non-copper pad %s has a complex pad stack (not yet supported)." ),
3661 aElem.name );
3663 }
3664 }
3665
3666 switch( aElem.topshape )
3667 {
3668 case ALTIUM_PAD_SHAPE::RECT:
3669 {
3670 // filled rect
3671 aShape->SetShape( SHAPE_T::POLY );
3672 aShape->SetFilled( true );
3673 aShape->SetLayer( aLayer );
3674 aShape->SetStroke( STROKE_PARAMS( 0 ) );
3675
3676 aShape->SetPolyPoints(
3677 { aElem.position + VECTOR2I( aElem.topsize.x / 2, aElem.topsize.y / 2 ),
3678 aElem.position + VECTOR2I( aElem.topsize.x / 2, -aElem.topsize.y / 2 ),
3679 aElem.position + VECTOR2I( -aElem.topsize.x / 2, -aElem.topsize.y / 2 ),
3680 aElem.position + VECTOR2I( -aElem.topsize.x / 2, aElem.topsize.y / 2 ) } );
3681
3682 if( aElem.direction != 0 )
3683 aShape->Rotate( aElem.position, EDA_ANGLE( aElem.direction, DEGREES_T ) );
3684 }
3685 break;
3686
3687 case ALTIUM_PAD_SHAPE::CIRCLE:
3688 if( aElem.sizeAndShape
3689 && aElem.sizeAndShape->alt_shape[0] == ALTIUM_PAD_SHAPE_ALT::ROUNDRECT )
3690 {
3691 // filled roundrect
3692 int cornerradius = aElem.sizeAndShape->cornerradius[0];
3693 int offset = ( std::min( aElem.topsize.x, aElem.topsize.y ) * cornerradius ) / 200;
3694
3695 aShape->SetLayer( aLayer );
3696 aShape->SetStroke( STROKE_PARAMS( offset * 2, LINE_STYLE::SOLID ) );
3697
3698 if( cornerradius < 100 )
3699 {
3700 int offsetX = aElem.topsize.x / 2 - offset;
3701 int offsetY = aElem.topsize.y / 2 - offset;
3702
3703 VECTOR2I p11 = aElem.position + VECTOR2I( offsetX, offsetY );
3704 VECTOR2I p12 = aElem.position + VECTOR2I( offsetX, -offsetY );
3705 VECTOR2I p22 = aElem.position + VECTOR2I( -offsetX, -offsetY );
3706 VECTOR2I p21 = aElem.position + VECTOR2I( -offsetX, offsetY );
3707
3708 aShape->SetShape( SHAPE_T::POLY );
3709 aShape->SetFilled( true );
3710 aShape->SetPolyPoints( { p11, p12, p22, p21 } );
3711 }
3712 else if( aElem.topsize.x == aElem.topsize.y )
3713 {
3714 // circle
3715 aShape->SetShape( SHAPE_T::CIRCLE );
3716 aShape->SetFilled( true );
3717 aShape->SetStart( aElem.position );
3718 aShape->SetEnd( aElem.position - VECTOR2I( 0, aElem.topsize.x / 4 ) );
3719 aShape->SetStroke( STROKE_PARAMS( aElem.topsize.x / 2, LINE_STYLE::SOLID ) );
3720 }
3721 else if( aElem.topsize.x < aElem.topsize.y )
3722 {
3723 // short vertical line
3724 aShape->SetShape( SHAPE_T::SEGMENT );
3725 VECTOR2I pointOffset( 0, ( aElem.topsize.y / 2 - aElem.topsize.x / 2 ) );
3726 aShape->SetStart( aElem.position + pointOffset );
3727 aShape->SetEnd( aElem.position - pointOffset );
3728 }
3729 else
3730 {
3731 // short horizontal line
3732 aShape->SetShape( SHAPE_T::SEGMENT );
3733 VECTOR2I pointOffset( ( aElem.topsize.x / 2 - aElem.topsize.y / 2 ), 0 );
3734 aShape->SetStart( aElem.position + pointOffset );
3735 aShape->SetEnd( aElem.position - pointOffset );
3736 }
3737
3738 if( aElem.direction != 0 )
3739 aShape->Rotate( aElem.position, EDA_ANGLE( aElem.direction, DEGREES_T ) );
3740 }
3741 else if( aElem.topsize.x == aElem.topsize.y )
3742 {
3743 // filled circle
3744 aShape->SetShape( SHAPE_T::CIRCLE );
3745 aShape->SetFilled( true );
3746 aShape->SetLayer( aLayer );
3747 aShape->SetStart( aElem.position );
3748 aShape->SetEnd( aElem.position - VECTOR2I( 0, aElem.topsize.x / 4 ) );
3749 aShape->SetStroke( STROKE_PARAMS( aElem.topsize.x / 2, LINE_STYLE::SOLID ) );
3750 }
3751 else
3752 {
3753 // short line
3754 aShape->SetShape( SHAPE_T::SEGMENT );
3755 aShape->SetLayer( aLayer );
3756 aShape->SetStroke( STROKE_PARAMS( std::min( aElem.topsize.x, aElem.topsize.y ),
3757 LINE_STYLE::SOLID ) );
3758
3759 if( aElem.topsize.x < aElem.topsize.y )
3760 {
3761 VECTOR2I offset( 0, ( aElem.topsize.y / 2 - aElem.topsize.x / 2 ) );
3762 aShape->SetStart( aElem.position + offset );
3763 aShape->SetEnd( aElem.position - offset );
3764 }
3765 else
3766 {
3767 VECTOR2I offset( ( aElem.topsize.x / 2 - aElem.topsize.y / 2 ), 0 );
3768 aShape->SetStart( aElem.position + offset );
3769 aShape->SetEnd( aElem.position - offset );
3770 }
3771
3772 if( aElem.direction != 0 )
3773 aShape->Rotate( aElem.position, EDA_ANGLE( aElem.direction, DEGREES_T ) );
3774 }
3775 break;
3776
3777 case ALTIUM_PAD_SHAPE::OCTAGONAL:
3778 {
3779 // filled octagon
3780 aShape->SetShape( SHAPE_T::POLY );
3781 aShape->SetFilled( true );
3782 aShape->SetLayer( aLayer );
3783 aShape->SetStroke( STROKE_PARAMS( 0 ) );
3784
3785 VECTOR2I p11 = aElem.position + VECTOR2I( aElem.topsize.x / 2, aElem.topsize.y / 2 );
3786 VECTOR2I p12 = aElem.position + VECTOR2I( aElem.topsize.x / 2, -aElem.topsize.y / 2 );
3787 VECTOR2I p22 = aElem.position + VECTOR2I( -aElem.topsize.x / 2, -aElem.topsize.y / 2 );
3788 VECTOR2I p21 = aElem.position + VECTOR2I( -aElem.topsize.x / 2, aElem.topsize.y / 2 );
3789
3790 int chamfer = std::min( aElem.topsize.x, aElem.topsize.y ) / 4;
3791 VECTOR2I chamferX( chamfer, 0 );
3792 VECTOR2I chamferY( 0, chamfer );
3793
3794 aShape->SetPolyPoints( { p11 - chamferX, p11 - chamferY, p12 + chamferY, p12 - chamferX,
3795 p22 + chamferX, p22 + chamferY, p21 - chamferY, p21 + chamferX } );
3796
3797 if( aElem.direction != 0. )
3798 aShape->Rotate( aElem.position, EDA_ANGLE( aElem.direction, DEGREES_T ) );
3799 }
3800 break;
3801
3802 case ALTIUM_PAD_SHAPE::UNKNOWN:
3803 default:
3804 if( m_reporter )
3805 {
3806 wxString msg;
3807 msg.Printf( _( "Non-copper pad %s uses an unknown pad-shape." ), aElem.name );
3809 }
3810
3811 break;
3812 }
3813}
3814
3815
3817 const CFB::COMPOUND_FILE_ENTRY* aEntry )
3818{
3819 if( m_progressReporter )
3820 m_progressReporter->Report( _( "Loading vias..." ) );
3821
3822 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
3823
3824 while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
3825 {
3826 checkpoint();
3827 AVIA6 elem( reader );
3828
3829 std::unique_ptr<PCB_VIA> via = std::make_unique<PCB_VIA>( m_board );
3830
3831 via->SetPosition( elem.position );
3832 // TODO(JE) padstacks
3833 via->SetWidth( PADSTACK::ALL_LAYERS, elem.diameter );
3834 via->SetDrill( elem.holesize );
3835 via->SetNetCode( GetNetCode( elem.net ) );
3836 via->SetLocked( elem.is_locked );
3837
3838 bool start_layer_outside = elem.layer_start == ALTIUM_LAYER::TOP_LAYER
3839 || elem.layer_start == ALTIUM_LAYER::BOTTOM_LAYER;
3840 bool end_layer_outside = elem.layer_end == ALTIUM_LAYER::TOP_LAYER
3841 || elem.layer_end == ALTIUM_LAYER::BOTTOM_LAYER;
3842
3843 if( start_layer_outside && end_layer_outside )
3844 {
3845 via->SetViaType( VIATYPE::THROUGH );
3846 }
3847 else if( ( !start_layer_outside ) && ( !end_layer_outside ) )
3848 {
3849 via->SetViaType( VIATYPE::BLIND_BURIED );
3850 }
3851 else
3852 {
3853 via->SetViaType( VIATYPE::MICROVIA ); // TODO: always a microvia?
3854 }
3855
3856 PCB_LAYER_ID start_klayer = GetKicadLayer( elem.layer_start );
3857 PCB_LAYER_ID end_klayer = GetKicadLayer( elem.layer_end );
3858
3859 if( !IsCopperLayer( start_klayer ) || !IsCopperLayer( end_klayer ) )
3860 {
3861 if( m_reporter )
3862 {
3863 wxString msg;
3864 msg.Printf( _( "Via from layer %d to %d uses a non-copper layer, which is not "
3865 "supported." ),
3866 elem.layer_start,
3867 elem.layer_end );
3869 }
3870
3871 continue; // just assume through-hole instead.
3872 }
3873
3874 // we need VIATYPE set!
3875 via->SetLayerPair( start_klayer, end_klayer );
3876
3878 {
3879 via->SetFrontTentingMode( elem.is_tent_top ? TENTING_MODE::TENTED
3880 : TENTING_MODE::NOT_TENTED );
3881 via->SetBackTentingMode( elem.is_tent_bottom ? TENTING_MODE::TENTED
3882 : TENTING_MODE::NOT_TENTED );
3883 }
3884
3885 m_board->Add( via.release(), ADD_MODE::APPEND );
3886 }
3887
3888 if( reader.GetRemainingBytes() != 0 )
3889 THROW_IO_ERROR( wxT( "Vias6 stream is not fully parsed" ) );
3890}
3891
3893 const CFB::COMPOUND_FILE_ENTRY* aEntry )
3894{
3895 if( m_progressReporter )
3896 m_progressReporter->Report( _( "Loading tracks..." ) );
3897
3898 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
3899
3900 for( int primitiveIndex = 0; reader.GetRemainingBytes() >= 4; primitiveIndex++ )
3901 {
3902 checkpoint();
3903 ATRACK6 elem( reader );
3904
3905 if( elem.component == ALTIUM_COMPONENT_NONE )
3906 {
3907 ConvertTracks6ToBoardItem( elem, primitiveIndex );
3908 }
3909 else
3910 {
3911 FOOTPRINT* footprint = HelperGetFootprint( elem.component );
3912 ConvertTracks6ToFootprintItem( footprint, elem, primitiveIndex, true );
3913 }
3914 }
3915
3916 if( reader.GetRemainingBytes() != 0 )
3917 THROW_IO_ERROR( "Tracks6 stream is not fully parsed" );
3918}
3919
3920
3921void ALTIUM_PCB::ConvertTracks6ToBoardItem( const ATRACK6& aElem, const int aPrimitiveIndex )
3922{
3923 if( aElem.polygon != ALTIUM_POLYGON_NONE && aElem.polygon != ALTIUM_POLYGON_BOARD )
3924 {
3925 if( m_polygons.size() <= aElem.polygon )
3926 {
3927 // Can happen when reading old Altium files: just skip this item
3928 if( m_reporter )
3929 {
3930 wxString msg;
3931 msg.Printf( wxT( "ATRACK6 stream tries to access polygon id %u "
3932 "of %u existing polygons; skipping it" ),
3933 static_cast<unsigned>( aElem.polygon ),
3934 static_cast<unsigned>( m_polygons.size() ) );
3936 }
3937
3938 return;
3939 }
3940
3941 ZONE* zone = m_polygons.at( aElem.polygon );
3942
3943 if( zone == nullptr )
3944 {
3945 return; // we know the zone id, but because we do not know the layer we did not
3946 // add it!
3947 }
3948
3949 PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
3950
3951 if( klayer == UNDEFINED_LAYER )
3952 return; // Just skip it for now. Users can fill it themselves.
3953
3954 if( !zone->HasFilledPolysForLayer( klayer ) )
3955 return;
3956
3957 SHAPE_POLY_SET* fill = zone->GetFill( klayer );
3958
3959 PCB_SHAPE shape( nullptr, SHAPE_T::SEGMENT );
3960 shape.SetStart( aElem.start );
3961 shape.SetEnd( aElem.end );
3962 shape.SetStroke( STROKE_PARAMS( aElem.width, LINE_STYLE::SOLID ) );
3963
3964 shape.EDA_SHAPE::TransformShapeToPolygon( *fill, 0, ARC_HIGH_DEF, ERROR_INSIDE );
3965 // Will be simplified and fractured later
3966
3967 zone->SetIsFilled( true );
3968 zone->SetNeedRefill( false );
3969
3970 return;
3971 }
3972
3973 if( aElem.is_keepout || aElem.layer == ALTIUM_LAYER::KEEP_OUT_LAYER
3974 || IsAltiumLayerAPlane( aElem.layer ) )
3975 {
3976 // This is not the actual board item. We can use it to create the polygon for the region
3977 PCB_SHAPE shape( nullptr, SHAPE_T::SEGMENT );
3978 shape.SetStart( aElem.start );
3979 shape.SetEnd( aElem.end );
3980 shape.SetStroke( STROKE_PARAMS( aElem.width, LINE_STYLE::SOLID ) );
3981
3983 }
3984 else
3985 {
3986 for( PCB_LAYER_ID klayer : GetKicadLayersToIterate( aElem.layer ) )
3987 ConvertTracks6ToBoardItemOnLayer( aElem, klayer );
3988 }
3989
3990 for( const auto& layerExpansionMask : HelperGetSolderAndPasteMaskExpansions(
3991 ALTIUM_RECORD::TRACK, aPrimitiveIndex, aElem.layer ) )
3992 {
3993 int width = aElem.width + ( layerExpansionMask.second * 2 );
3994 if( width > 1 )
3995 {
3996 std::unique_ptr<PCB_SHAPE> seg = std::make_unique<PCB_SHAPE>( m_board, SHAPE_T::SEGMENT );
3997
3998 seg->SetStart( aElem.start );
3999 seg->SetEnd( aElem.end );
4000 seg->SetStroke( STROKE_PARAMS( width, LINE_STYLE::SOLID ) );
4001 seg->SetLayer( layerExpansionMask.first );
4002
4003 m_board->Add( seg.release(), ADD_MODE::APPEND );
4004 }
4005 }
4006}
4007
4008
4010 const int aPrimitiveIndex,
4011 const bool aIsBoardImport )
4012{
4013 if( aElem.polygon != ALTIUM_POLYGON_NONE )
4014 {
4015 wxFAIL_MSG( wxString::Format( "Altium: Unexpected footprint Track with polygon id %u",
4016 (unsigned)aElem.polygon ) );
4017 return;
4018 }
4019
4020 if( aElem.is_keepout || aElem.layer == ALTIUM_LAYER::KEEP_OUT_LAYER
4021 || IsAltiumLayerAPlane( aElem.layer ) )
4022 {
4023 // This is not the actual board item. We can use it to create the polygon for the region
4024 PCB_SHAPE shape( nullptr, SHAPE_T::SEGMENT );
4025 shape.SetStart( aElem.start );
4026 shape.SetEnd( aElem.end );
4027 shape.SetStroke( STROKE_PARAMS( aElem.width, LINE_STYLE::SOLID ) );
4028
4029 HelperPcpShapeAsFootprintKeepoutRegion( aFootprint, shape, aElem.layer,
4030 aElem.keepoutrestrictions );
4031 }
4032 else
4033 {
4034 for( PCB_LAYER_ID klayer : GetKicadLayersToIterate( aElem.layer ) )
4035 {
4036 if( aIsBoardImport && IsCopperLayer( klayer ) && aElem.net != ALTIUM_NET_UNCONNECTED )
4037 {
4038 // Special case: do to not lose net connections in footprints
4039 ConvertTracks6ToBoardItemOnLayer( aElem, klayer );
4040 }
4041 else
4042 {
4043 ConvertTracks6ToFootprintItemOnLayer( aFootprint, aElem, klayer );
4044 }
4045 }
4046 }
4047
4048 for( const auto& layerExpansionMask : HelperGetSolderAndPasteMaskExpansions(
4049 ALTIUM_RECORD::TRACK, aPrimitiveIndex, aElem.layer ) )
4050 {
4051 int width = aElem.width + ( layerExpansionMask.second * 2 );
4052 if( width > 1 )
4053 {
4054 std::unique_ptr<PCB_SHAPE> seg = std::make_unique<PCB_SHAPE>( aFootprint, SHAPE_T::SEGMENT );
4055
4056 seg->SetStart( aElem.start );
4057 seg->SetEnd( aElem.end );
4058 seg->SetStroke( STROKE_PARAMS( width, LINE_STYLE::SOLID ) );
4059 seg->SetLayer( layerExpansionMask.first );
4060
4061 aFootprint->Add( seg.release(), ADD_MODE::APPEND );
4062 }
4063 }
4064}
4065
4066
4068{
4069 if( IsCopperLayer( aLayer ) && aElem.net != ALTIUM_NET_UNCONNECTED )
4070 {
4071 std::unique_ptr<PCB_TRACK> track = std::make_unique<PCB_TRACK>( m_board );
4072
4073 track->SetStart( aElem.start );
4074 track->SetEnd( aElem.end );
4075 track->SetWidth( aElem.width );
4076 track->SetLayer( aLayer );
4077 track->SetNetCode( GetNetCode( aElem.net ) );
4078
4079 m_board->Add( track.release(), ADD_MODE::APPEND );
4080 }
4081 else
4082 {
4083 std::unique_ptr<PCB_SHAPE> seg = std::make_unique<PCB_SHAPE>( m_board, SHAPE_T::SEGMENT );
4084
4085 seg->SetStart( aElem.start );
4086 seg->SetEnd( aElem.end );
4087 seg->SetStroke( STROKE_PARAMS( aElem.width, LINE_STYLE::SOLID ) );
4088 seg->SetLayer( aLayer );
4089
4090 m_board->Add( seg.release(), ADD_MODE::APPEND );
4091 }
4092}
4093
4094
4096 PCB_LAYER_ID aLayer )
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( aElem.width, LINE_STYLE::SOLID ) );
4103 seg->SetLayer( aLayer );
4104
4105 aFootprint->Add( seg.release(), ADD_MODE::APPEND );
4106}
4107
4108
4110 const CFB::COMPOUND_FILE_ENTRY* aEntry )
4111{
4112 if( m_progressReporter )
4113 m_progressReporter->Report( _( "Loading unicode strings..." ) );
4114
4115 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
4116
4118
4119 if( reader.GetRemainingBytes() != 0 )
4120 THROW_IO_ERROR( wxT( "WideStrings6 stream is not fully parsed" ) );
4121}
4122
4124 const CFB::COMPOUND_FILE_ENTRY* aEntry )
4125{
4126 if( m_progressReporter )
4127 m_progressReporter->Report( _( "Loading text..." ) );
4128
4129 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
4130
4131 while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
4132 {
4133 checkpoint();
4134 ATEXT6 elem( reader, m_unicodeStrings );
4135
4136 if( elem.component == ALTIUM_COMPONENT_NONE )
4137 {
4139 }
4140 else
4141 {
4142 FOOTPRINT* footprint = HelperGetFootprint( elem.component );
4143 ConvertTexts6ToFootprintItem( footprint, elem );
4144 }
4145 }
4146
4147 if( reader.GetRemainingBytes() != 0 )
4148 THROW_IO_ERROR( wxT( "Texts6 stream is not fully parsed" ) );
4149}
4150
4151
4153{
4154 if( aElem.fonttype == ALTIUM_TEXT_TYPE::BARCODE )
4155 {
4156 if( m_reporter )
4157 {
4158 wxString msg;
4159 msg.Printf( _( "Ignored barcode on Altium layer %d (not yet supported)." ),
4160 aElem.layer );
4162 }
4163
4164 return;
4165 }
4166
4167 for( PCB_LAYER_ID klayer : GetKicadLayersToIterate( aElem.layer ) )
4168 ConvertTexts6ToBoardItemOnLayer( aElem, klayer );
4169}
4170
4171
4173{
4174 if( aElem.fonttype == ALTIUM_TEXT_TYPE::BARCODE )
4175 {
4176 if( !m_footprintName.IsEmpty() )
4177 {
4178 if( m_reporter )
4179 {
4180 wxString msg;
4181 msg.Printf( _( "Error loading library '%s':\n"
4182 "Footprint %s contains barcode on Altium layer %d (not yet supported)." ),
4183 m_library,
4185 aElem.layer );
4187 }
4188 }
4189 else
4190 {
4191 if( m_reporter )
4192 {
4193 wxString msg;
4194 msg.Printf( _( "Footprint %s contains barcode on Altium layer %d (not yet supported)." ),
4195 aFootprint->GetReference(),
4196 aElem.layer );
4198 }
4199 }
4200
4201 return;
4202 }
4203
4204 for( PCB_LAYER_ID klayer : GetKicadLayersToIterate( aElem.layer ) )
4205 ConvertTexts6ToFootprintItemOnLayer( aFootprint, aElem, klayer );
4206}
4207
4208
4210{
4211 std::unique_ptr<PCB_TEXTBOX> pcbTextbox = std::make_unique<PCB_TEXTBOX>( m_board );
4212 std::unique_ptr<PCB_TEXT> pcbText = std::make_unique<PCB_TEXT>( m_board );
4213
4214 bool isTextbox = aElem.isFrame && !aElem.isInverted; // Textbox knockout is not supported
4215
4216 static const std::map<wxString, wxString> variableMap = {
4217 { "LAYER_NAME", "LAYER" },
4218 { "PRINT_DATE", "CURRENT_DATE"},
4219 };
4220
4221 wxString kicadText = AltiumPcbSpecialStringsToKiCadStrings( aElem.text, variableMap );
4222 BOARD_ITEM* item = pcbText.get();
4223 EDA_TEXT* text = pcbText.get();
4224 int margin = aElem.isOffsetBorder ? aElem.text_offset_width : aElem.margin_border_width;
4225
4226 if( isTextbox )
4227 {
4228 item = pcbTextbox.get();
4229 text = pcbTextbox.get();
4230
4232 HelperSetTextboxAlignmentAndPos( aElem, pcbTextbox.get() );
4233 }
4234 else
4235 {
4238 }
4239
4240 text->SetText( kicadText );
4241 item->SetLayer( aLayer );
4242 item->SetIsKnockout( aElem.isInverted );
4243
4244 if( isTextbox )
4245 m_board->Add( pcbTextbox.release(), ADD_MODE::APPEND );
4246 else
4247 m_board->Add( pcbText.release(), ADD_MODE::APPEND );
4248}
4249
4250
4252 PCB_LAYER_ID aLayer )
4253{
4254 std::unique_ptr<PCB_TEXTBOX> fpTextbox = std::make_unique<PCB_TEXTBOX>( aFootprint );
4255 std::unique_ptr<PCB_TEXT> fpText = std::make_unique<PCB_TEXT>( aFootprint );
4256
4257 BOARD_ITEM* item = fpText.get();
4258 EDA_TEXT* text = fpText.get();
4259 PCB_FIELD* field = nullptr;
4260
4261 bool isTextbox = aElem.isFrame && !aElem.isInverted; // Textbox knockout is not supported
4262 bool toAdd = false;
4263
4264 if( aElem.isDesignator )
4265 {
4266 item = &aFootprint->Reference(); // TODO: handle multiple layers
4267 text = &aFootprint->Reference();
4268 field = &aFootprint->Reference();
4269 }
4270 else if( aElem.isComment )
4271 {
4272 item = &aFootprint->Value(); // TODO: handle multiple layers
4273 text = &aFootprint->Value();
4274 field = &aFootprint->Value();
4275 }
4276 else
4277 {
4278 item = fpText.get();
4279 text = fpText.get();
4280 toAdd = true;
4281 }
4282
4283 static const std::map<wxString, wxString> variableMap = {
4284 { "DESIGNATOR", "REFERENCE" },
4285 { "COMMENT", "VALUE" },
4286 { "VALUE", "ALTIUM_VALUE" },
4287 { "LAYER_NAME", "LAYER" },
4288 { "PRINT_DATE", "CURRENT_DATE"},
4289 };
4290
4291 if( isTextbox )
4292 {
4293 item = fpTextbox.get();
4294 text = fpTextbox.get();
4295
4297 HelperSetTextboxAlignmentAndPos( aElem, fpTextbox.get() );
4298 }
4299 else
4300 {
4303 }
4304
4305 wxString kicadText = AltiumPcbSpecialStringsToKiCadStrings( aElem.text, variableMap );
4306
4307 text->SetText( kicadText );
4308 text->SetKeepUpright( false );
4309 item->SetLayer( aLayer );
4310 item->SetIsKnockout( aElem.isInverted );
4311
4312 if( toAdd )
4313 {
4314 if( isTextbox )
4315 aFootprint->Add( fpTextbox.release(), ADD_MODE::APPEND );
4316 else
4317 aFootprint->Add( fpText.release(), ADD_MODE::APPEND );
4318 }
4319}
4320
4321
4323{
4324 int margin = aElem.isOffsetBorder ? aElem.text_offset_width : aElem.margin_border_width;
4325
4326 // Altium textboxes do not have borders
4327 aTextbox->SetBorderEnabled( false );
4328
4329 // Calculate position
4330 VECTOR2I kposition = aElem.position;
4331
4332 if( aElem.isMirrored )
4333 kposition.x -= aElem.textbox_rect_width;
4334
4335 kposition.y -= aElem.textbox_rect_height;
4336
4337#if 0
4338 // Compensate for KiCad's textbox margin
4339 int charWidth = aTextbox->GetTextWidth();
4340 int charHeight = aTextbox->GetTextHeight();
4341
4342 VECTOR2I kicadMargin;
4343
4344 if( !aTextbox->GetFont() || aTextbox->GetFont()->IsStroke() )
4345 kicadMargin = VECTOR2I( charWidth * 0.933, charHeight * 0.67 );
4346 else
4347 kicadMargin = VECTOR2I( charWidth * 0.808, charHeight * 0.844 );
4348
4349 aTextbox->SetEnd( VECTOR2I( aElem.textbox_rect_width, aElem.textbox_rect_height )
4350 + kicadMargin * 2 - margin * 2 );
4351
4352 kposition = kposition - kicadMargin + margin;
4353#else
4354 aTextbox->SetMarginBottom( margin );
4355 aTextbox->SetMarginLeft( margin );
4356 aTextbox->SetMarginRight( margin );
4357 aTextbox->SetMarginTop( margin );
4358
4359 aTextbox->SetEnd( VECTOR2I( aElem.textbox_rect_width, aElem.textbox_rect_height ) );
4360#endif
4361
4362 RotatePoint( kposition, aElem.position, EDA_ANGLE( aElem.rotation, DEGREES_T ) );
4363
4364 aTextbox->SetPosition( kposition );
4365
4366 ALTIUM_TEXT_POSITION justification = aElem.isJustificationValid
4368 : ALTIUM_TEXT_POSITION::LEFT_BOTTOM;
4369
4370 switch( justification )
4371 {
4372 case ALTIUM_TEXT_POSITION::LEFT_TOP:
4373 case ALTIUM_TEXT_POSITION::LEFT_CENTER:
4374 case ALTIUM_TEXT_POSITION::LEFT_BOTTOM:
4377 break;
4378 case ALTIUM_TEXT_POSITION::CENTER_TOP:
4379 case ALTIUM_TEXT_POSITION::CENTER_CENTER:
4380 case ALTIUM_TEXT_POSITION::CENTER_BOTTOM:
4383 break;
4384 case ALTIUM_TEXT_POSITION::RIGHT_TOP:
4385 case ALTIUM_TEXT_POSITION::RIGHT_CENTER:
4386 case ALTIUM_TEXT_POSITION::RIGHT_BOTTOM:
4389 break;
4390 default:
4391 if( m_reporter )
4392 {
4393 wxString msg;
4394 msg.Printf( _( "Unknown textbox justification %d, aText %s" ), justification,
4395 aElem.text );
4397 }
4398
4401 break;
4402 }
4403
4404 aTextbox->SetTextAngle( EDA_ANGLE( aElem.rotation, DEGREES_T ) );
4405}
4406
4407
4409{
4410 VECTOR2I kposition = aElem.position;
4411
4412 int margin = aElem.isOffsetBorder ? aElem.text_offset_width : aElem.margin_border_width;
4413 int rectWidth = aElem.textbox_rect_width - margin * 2;
4414 int rectHeight = aElem.height;
4415
4416 if( aElem.isMirrored )
4417 rectWidth = -rectWidth;
4418
4419 ALTIUM_TEXT_POSITION justification = aElem.isJustificationValid
4421 : ALTIUM_TEXT_POSITION::LEFT_BOTTOM;
4422
4423 switch( justification )
4424 {
4425 case ALTIUM_TEXT_POSITION::LEFT_TOP:
4428
4429 kposition.y -= rectHeight;
4430 break;
4431 case ALTIUM_TEXT_POSITION::LEFT_CENTER:
4434
4435 kposition.y -= rectHeight / 2;
4436 break;
4437 case ALTIUM_TEXT_POSITION::LEFT_BOTTOM:
4440 break;
4441 case ALTIUM_TEXT_POSITION::CENTER_TOP:
4444
4445 kposition.x += rectWidth / 2;
4446 kposition.y -= rectHeight;
4447 break;
4448 case ALTIUM_TEXT_POSITION::CENTER_CENTER:
4451
4452 kposition.x += rectWidth / 2;
4453 kposition.y -= rectHeight / 2;
4454 break;
4455 case ALTIUM_TEXT_POSITION::CENTER_BOTTOM:
4458
4459 kposition.x += rectWidth / 2;
4460 break;
4461 case ALTIUM_TEXT_POSITION::RIGHT_TOP:
4464
4465 kposition.x += rectWidth;
4466 kposition.y -= rectHeight;
4467 break;
4468 case ALTIUM_TEXT_POSITION::RIGHT_CENTER:
4471
4472 kposition.x += rectWidth;
4473 kposition.y -= rectHeight / 2;
4474 break;
4475 case ALTIUM_TEXT_POSITION::RIGHT_BOTTOM:
4478
4479 kposition.x += rectWidth;
4480 break;
4481 default:
4484 break;
4485 }
4486
4487 int charWidth = aText->GetTextWidth();
4488 int charHeight = aText->GetTextHeight();
4489
4490 // Correct for KiCad's baseline offset.
4491 // Text height and font must be set correctly before calling.
4492 if( !aText->GetFont() || aText->GetFont()->IsStroke() )
4493 {
4494 switch( aText->GetVertJustify() )
4495 {
4496 case GR_TEXT_V_ALIGN_TOP: kposition.y -= charHeight * 0.0407; break;
4497 case GR_TEXT_V_ALIGN_CENTER: kposition.y += charHeight * 0.0355; break;
4498 case GR_TEXT_V_ALIGN_BOTTOM: kposition.y += charHeight * 0.1225; break;
4499 default: break;
4500 }
4501 }
4502 else
4503 {
4504 switch( aText->GetVertJustify() )
4505 {
4506 case GR_TEXT_V_ALIGN_TOP: kposition.y -= charWidth * 0.016; break;
4507 case GR_TEXT_V_ALIGN_CENTER: kposition.y += charWidth * 0.085; break;
4508 case GR_TEXT_V_ALIGN_BOTTOM: kposition.y += charWidth * 0.17; break;
4509 default: break;
4510 }
4511 }
4512
4513 RotatePoint( kposition, aElem.position, EDA_ANGLE( aElem.rotation, DEGREES_T ) );
4514
4515 aText->SetTextPos( kposition );
4516 aText->SetTextAngle( EDA_ANGLE( aElem.rotation, DEGREES_T ) );
4517}
4518
4519
4521{
4522 aEdaText.SetTextSize( VECTOR2I( aElem.height, aElem.height ) );
4523
4524 if( aElem.fonttype == ALTIUM_TEXT_TYPE::TRUETYPE )
4525 {
4526 KIFONT::FONT* font = KIFONT::FONT::GetFont( aElem.fontname, aElem.isBold, aElem.isItalic );
4527 aEdaText.SetFont( font );
4528
4529 if( font->IsOutline() )
4530 {
4531 // TODO: why is this required? Somehow, truetype size is calculated differently
4532 if( font->GetName().Contains( wxS( "Arial" ) ) )
4533 aEdaText.SetTextSize( VECTOR2I( aElem.height * 0.63, aElem.height * 0.63 ) );
4534 else
4535 aEdaText.SetTextSize( VECTOR2I( aElem.height * 0.5, aElem.height * 0.5 ) );
4536 }
4537 }
4538
4539 aEdaText.SetTextThickness( aElem.strokewidth );
4540 aEdaText.SetBoldFlag( aElem.isBold );
4541 aEdaText.SetItalic( aElem.isItalic );
4542 aEdaText.SetMirrored( aElem.isMirrored );
4543}
4544
4545
4547 const CFB::COMPOUND_FILE_ENTRY* aEntry )
4548{
4549 if( m_progressReporter )
4550 m_progressReporter->Report( _( "Loading rectangles..." ) );
4551
4552 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
4553
4554 while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
4555 {
4556 checkpoint();
4557 AFILL6 elem( reader );
4558
4559 if( elem.component == ALTIUM_COMPONENT_NONE )
4560 {
4562 }
4563 else
4564 {
4565 FOOTPRINT* footprint = HelperGetFootprint( elem.component );
4566 ConvertFills6ToFootprintItem( footprint, elem, true );
4567 }
4568 }
4569
4570 if( reader.GetRemainingBytes() != 0 )
4571 THROW_IO_ERROR( "Fills6 stream is not fully parsed" );
4572}
4573
4574
4576{
4577 if( aElem.is_keepout || aElem.layer == ALTIUM_LAYER::KEEP_OUT_LAYER )
4578 {
4579 // This is not the actual board item. We can use it to create the polygon for the region
4580 PCB_SHAPE shape( nullptr, SHAPE_T::RECTANGLE );
4581
4582 shape.SetStart( aElem.pos1 );
4583 shape.SetEnd( aElem.pos2 );
4584 shape.SetFilled( true );
4585 shape.SetStroke( STROKE_PARAMS( 0, LINE_STYLE::SOLID ) );
4586
4587 if( aElem.rotation != 0. )
4588 {
4589 VECTOR2I center( aElem.pos1.x / 2 + aElem.pos2.x / 2,
4590 aElem.pos1.y / 2 + aElem.pos2.y / 2 );
4591 shape.Rotate( center, EDA_ANGLE( aElem.rotation, DEGREES_T ) );
4592 }
4593
4595 }
4596 else
4597 {
4598 for( PCB_LAYER_ID klayer : GetKicadLayersToIterate( aElem.layer ) )
4599 ConvertFills6ToBoardItemOnLayer( aElem, klayer );
4600 }
4601}
4602
4603
4605 const bool aIsBoardImport )
4606{
4607 if( aElem.is_keepout
4608 || aElem.layer == ALTIUM_LAYER::KEEP_OUT_LAYER ) // TODO: what about plane layers?
4609 {
4610 // This is not the actual board item. We can use it to create the polygon for the region
4611 PCB_SHAPE shape( nullptr, SHAPE_T::RECTANGLE );
4612
4613 shape.SetStart( aElem.pos1 );
4614 shape.SetEnd( aElem.pos2 );
4615 shape.SetFilled( true );
4616 shape.SetStroke( STROKE_PARAMS( 0, LINE_STYLE::SOLID ) );
4617
4618 if( aElem.rotation != 0. )
4619 {
4620 VECTOR2I center( aElem.pos1.x / 2 + aElem.pos2.x / 2,
4621 aElem.pos1.y / 2 + aElem.pos2.y / 2 );
4622 shape.Rotate( center, EDA_ANGLE( aElem.rotation, DEGREES_T ) );
4623 }
4624
4625 HelperPcpShapeAsFootprintKeepoutRegion( aFootprint, shape, aElem.layer,
4626 aElem.keepoutrestrictions );
4627 }
4628 else if( aIsBoardImport && IsAltiumLayerCopper( aElem.layer )
4629 && aElem.net != ALTIUM_NET_UNCONNECTED )
4630 {
4631 // Special case: do to not lose net connections in footprints
4632 for( PCB_LAYER_ID klayer : GetKicadLayersToIterate( aElem.layer ) )
4633 ConvertFills6ToBoardItemOnLayer( aElem, klayer );
4634 }
4635 else
4636 {
4637 for( PCB_LAYER_ID klayer : GetKicadLayersToIterate( aElem.layer ) )
4638 ConvertFills6ToFootprintItemOnLayer( aFootprint, aElem, klayer );
4639 }
4640}
4641
4642
4644{
4645 std::unique_ptr<PCB_SHAPE> fill = std::make_unique<PCB_SHAPE>( m_board, SHAPE_T::RECTANGLE );
4646
4647 fill->SetFilled( true );
4648 fill->SetLayer( aLayer );
4649 fill->SetStroke( STROKE_PARAMS( 0 ) );
4650
4651 fill->SetStart( aElem.pos1 );
4652 fill->SetEnd( aElem.pos2 );
4653
4654 if( IsCopperLayer( aLayer ) && aElem.net != ALTIUM_NET_UNCONNECTED )
4655 {
4656 fill->SetNetCode( GetNetCode( aElem.net ) );
4657 }
4658
4659 if( aElem.rotation != 0. )
4660 {
4661 // TODO: Do we need SHAPE_T::POLY for non 90° rotations?
4662 VECTOR2I center( aElem.pos1.x / 2 + aElem.pos2.x / 2,
4663 aElem.pos1.y / 2 + aElem.pos2.y / 2 );
4664 fill->Rotate( center, EDA_ANGLE( aElem.rotation, DEGREES_T ) );
4665 }
4666
4667 m_board->Add( fill.release(), ADD_MODE::APPEND );
4668}
4669
4670
4672 PCB_LAYER_ID aLayer )
4673{
4674 if( aLayer == F_Cu || aLayer == B_Cu )
4675 {
4676 std::unique_ptr<PAD> pad = std::make_unique<PAD>( aFootprint );
4677
4678 LSET padLayers;
4679 padLayers.set( aLayer );
4680
4681 pad->SetAttribute( PAD_ATTRIB::SMD );
4682 EDA_ANGLE rotation( aElem.rotation, DEGREES_T );
4683
4684 // Handle rotation multiples of 90 degrees
4685 if( rotation.IsCardinal() )
4686 {
4687 pad->SetShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::RECTANGLE );
4688
4689 int width = std::abs( aElem.pos2.x - aElem.pos1.x );
4690 int height = std::abs( aElem.pos2.y - aElem.pos1.y );
4691
4692 // Swap width and height for 90 or 270 degree rotations
4693 if( rotation.IsCardinal90() )
4694 std::swap( width, height );
4695
4696 pad->SetSize( PADSTACK::ALL_LAYERS, { width, height } );
4697 pad->SetPosition( aElem.pos1 / 2 + aElem.pos2 / 2 );
4698 }
4699 else
4700 {
4701 pad->SetShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::CUSTOM );
4702
4703 int anchorSize = std::min( std::abs( aElem.pos2.x - aElem.pos1.x ),
4704 std::abs( aElem.pos2.y - aElem.pos1.y ) );
4705 VECTOR2I anchorPos = aElem.pos1;
4706
4707 pad->SetAnchorPadShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::CIRCLE );
4708 pad->SetSize( PADSTACK::ALL_LAYERS, { anchorSize, anchorSize } );
4709 pad->SetPosition( anchorPos );
4710
4711 SHAPE_POLY_SET shapePolys;
4712 shapePolys.NewOutline();
4713 shapePolys.Append( aElem.pos1.x - anchorPos.x, aElem.pos1.y - anchorPos.y );
4714 shapePolys.Append( aElem.pos2.x - anchorPos.x, aElem.pos1.y - anchorPos.y );
4715 shapePolys.Append( aElem.pos2.x - anchorPos.x, aElem.pos2.y - anchorPos.y );
4716 shapePolys.Append( aElem.pos1.x - anchorPos.x, aElem.pos2.y - anchorPos.y );
4717 shapePolys.Outline( 0 ).SetClosed( true );
4718
4719 VECTOR2I center( aElem.pos1.x / 2 + aElem.pos2.x / 2 - anchorPos.x,
4720 aElem.pos1.y / 2 + aElem.pos2.y / 2 - anchorPos.y );
4721 shapePolys.Rotate( EDA_ANGLE( aElem.rotation, DEGREES_T ), center );
4722 pad->AddPrimitivePoly( F_Cu, shapePolys, 0, true );
4723 }
4724
4725 pad->SetThermalSpokeAngle( ANGLE_90 );
4726 pad->SetLayerSet( padLayers );
4727
4728 aFootprint->Add( pad.release(), ADD_MODE::APPEND );
4729 }
4730 else
4731 {
4732 std::unique_ptr<PCB_SHAPE> fill =
4733 std::make_unique<PCB_SHAPE>( aFootprint, SHAPE_T::RECTANGLE );
4734
4735 fill->SetFilled( true );
4736 fill->SetLayer( aLayer );
4737 fill->SetStroke( STROKE_PARAMS( 0 ) );
4738
4739 fill->SetStart( aElem.pos1 );
4740 fill->SetEnd( aElem.pos2 );
4741
4742 if( aElem.rotation != 0. )
4743 {
4744 VECTOR2I center( aElem.pos1.x / 2 + aElem.pos2.x / 2,
4745 aElem.pos1.y / 2 + aElem.pos2.y / 2 );
4746 fill->Rotate( center, EDA_ANGLE( aElem.rotation, DEGREES_T ) );
4747 }
4748
4749 aFootprint->Add( fill.release(), ADD_MODE::APPEND );
4750 }
4751}
4752
4753
4754void ALTIUM_PCB::HelperSetZoneLayers( ZONE& aZone, const ALTIUM_LAYER aAltiumLayer )
4755{
4756 LSET layerSet;
4757
4758 for( PCB_LAYER_ID klayer : GetKicadLayersToIterate( aAltiumLayer ) )
4759 layerSet.set( klayer );
4760
4761 aZone.SetLayerSet( layerSet );
4762}
4763
4764
4765void ALTIUM_PCB::HelperSetZoneKeepoutRestrictions( ZONE& aZone, const uint8_t aKeepoutRestrictions )
4766{
4767 bool keepoutRestrictionVia = ( aKeepoutRestrictions & 0x01 ) != 0;
4768 bool keepoutRestrictionTrack = ( aKeepoutRestrictions & 0x02 ) != 0;
4769 bool keepoutRestrictionCopper = ( aKeepoutRestrictions & 0x04 ) != 0;
4770 bool keepoutRestrictionSMDPad = ( aKeepoutRestrictions & 0x08 ) != 0;
4771 bool keepoutRestrictionTHPad = ( aKeepoutRestrictions & 0x10 ) != 0;
4772
4773 aZone.SetDoNotAllowVias( keepoutRestrictionVia );
4774 aZone.SetDoNotAllowTracks( keepoutRestrictionTrack );
4775 aZone.SetDoNotAllowCopperPour( keepoutRestrictionCopper );
4776 aZone.SetDoNotAllowPads( keepoutRestrictionSMDPad && keepoutRestrictionTHPad );
4777 aZone.SetDoNotAllowFootprints( false );
4778}
4779
4780
4782 const ALTIUM_LAYER aAltiumLayer,
4783 const uint8_t aKeepoutRestrictions )
4784{
4785 std::unique_ptr<ZONE> zone = std::make_unique<ZONE>( m_board );
4786
4787 zone->SetIsRuleArea( true );
4788
4789 HelperSetZoneLayers( *zone, aAltiumLayer );
4790 HelperSetZoneKeepoutRestrictions( *zone, aKeepoutRestrictions );
4791
4792 aShape.EDA_SHAPE::TransformShapeToPolygon( *zone->Outline(), 0, ARC_HIGH_DEF, ERROR_INSIDE );
4793
4794 zone->SetBorderDisplayStyle( ZONE_BORDER_DISPLAY_STYLE::DIAGONAL_EDGE,
4796
4797 m_board->Add( zone.release(), ADD_MODE::APPEND );
4798}
4799
4800
4802 const PCB_SHAPE& aShape,
4803 const ALTIUM_LAYER aAltiumLayer,
4804 const uint8_t aKeepoutRestrictions )
4805{
4806 std::unique_ptr<ZONE> zone = std::make_unique<ZONE>( aFootprint );
4807
4808 zone->SetIsRuleArea( true );
4809
4810 HelperSetZoneLayers( *zone, aAltiumLayer );
4811 HelperSetZoneKeepoutRestrictions( *zone, aKeepoutRestrictions );
4812
4813 aShape.EDA_SHAPE::TransformShapeToPolygon( *zone->Outline(), 0, ARC_HIGH_DEF, ERROR_INSIDE );
4814
4815 zone->SetBorderDisplayStyle( ZONE_BORDER_DISPLAY_STYLE::DIAGONAL_EDGE,
4817
4818 // TODO: zone->SetLocalCoord(); missing?
4819 aFootprint->Add( zone.release(), ADD_MODE::APPEND );
4820}
4821
4822
4823std::vector<std::pair<PCB_LAYER_ID, int>> ALTIUM_PCB::HelperGetSolderAndPasteMaskExpansions(
4824 const ALTIUM_RECORD aType, const int aPrimitiveIndex, const ALTIUM_LAYER aAltiumLayer )
4825{
4826 if( m_extendedPrimitiveInformationMaps.count( aType ) == 0 )
4827 return {}; // there is nothing to parse
4828
4829 auto elems =
4830 m_extendedPrimitiveInformationMaps[ALTIUM_RECORD::TRACK].equal_range( aPrimitiveIndex );
4831
4832 if( elems.first == elems.second )
4833 return {}; // there is nothing to parse
4834
4835 std::vector<std::pair<PCB_LAYER_ID, int>> layerExpansionPairs;
4836
4837 for( auto it = elems.first; it != elems.second; ++it )
4838 {
4839 const AEXTENDED_PRIMITIVE_INFORMATION& pInf = it->second;
4840
4841 if( pInf.type == AEXTENDED_PRIMITIVE_INFORMATION_TYPE::MASK )
4842 {
4843 if( pInf.soldermaskexpansionmode == ALTIUM_MODE::MANUAL
4844 || pInf.soldermaskexpansionmode == ALTIUM_MODE::RULE )
4845 {
4846 // TODO: what layers can lead to solder or paste mask usage? E.g. KEEP_OUT_LAYER and other top/bottom layers
4847 if( aAltiumLayer == ALTIUM_LAYER::TOP_LAYER
4848 || aAltiumLayer == ALTIUM_LAYER::MULTI_LAYER )
4849 {
4850 layerExpansionPairs.emplace_back( F_Mask, pInf.soldermaskexpansionmanual );
4851 }
4852
4853 if( aAltiumLayer == ALTIUM_LAYER::BOTTOM_LAYER
4854 || aAltiumLayer == ALTIUM_LAYER::MULTI_LAYER )
4855 {
4856 layerExpansionPairs.emplace_back( B_Mask, pInf.soldermaskexpansionmanual );
4857 }
4858 }
4859 if( pInf.pastemaskexpansionmode == ALTIUM_MODE::MANUAL
4860 || pInf.pastemaskexpansionmode == ALTIUM_MODE::RULE )
4861 {
4862 if( aAltiumLayer == ALTIUM_LAYER::TOP_LAYER
4863 || aAltiumLayer == ALTIUM_LAYER::MULTI_LAYER )
4864 {
4865 layerExpansionPairs.emplace_back( F_Paste, pInf.pastemaskexpansionmanual );
4866 }
4867
4868 if( aAltiumLayer == ALTIUM_LAYER::BOTTOM_LAYER
4869 || aAltiumLayer == ALTIUM_LAYER::MULTI_LAYER )
4870 {
4871 layerExpansionPairs.emplace_back( B_Paste, pInf.pastemaskexpansionmanual );
4872 }
4873 }
4874 }
4875 }
4876
4877 return layerExpansionPairs;
4878}
const char * name
Definition: DXF_plotter.cpp:57
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:153
@ LT_POWER
Definition: board.h:156
@ LT_MIXED
Definition: board.h:157
@ LT_SIGNAL
Definition: board.h:155
@ 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:115
Container for design settings for a BOARD object.