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"
29
30#include <board.h>
32#include <pcb_dimension.h>
33#include <pad.h>
34#include <pcb_shape.h>
35#include <pcb_text.h>
36#include <pcb_track.h>
37#include <core/profile.h>
38#include <string_utils.h>
39#include <zone.h>
40
42
43#include <compoundfilereader.h>
45#include <font/outline_font.h>
46#include <project.h>
47#include <trigo.h>
48#include <utf.h>
49#include <wx/docview.h>
50#include <wx/log.h>
51#include <wx/mstream.h>
52#include <wx/wfstream.h>
53#include <wx/zstream.h>
54#include <progress_reporter.h>
55#include <magic_enum.hpp>
56
57
58constexpr double BOLD_FACTOR = 1.75; // CSS font-weight-normal is 400; bold is 700
59
60
62{
63 return ( aLayer >= ALTIUM_LAYER::TOP_LAYER && aLayer <= ALTIUM_LAYER::BOTTOM_LAYER )
64 || aLayer == ALTIUM_LAYER::MULTI_LAYER; // TODO: add IsAltiumLayerAPlane?
65}
66
67
69{
71}
72
73FOOTPRINT* ALTIUM_PCB::HelperGetFootprint( uint16_t aComponent ) const
74{
75 if( aComponent == ALTIUM_COMPONENT_NONE || m_components.size() <= aComponent )
76 {
77 THROW_IO_ERROR( wxString::Format( wxT( "Component creator tries to access component id %u "
78 "of %u existing components" ),
79 (unsigned)aComponent, (unsigned)m_components.size() ) );
80 }
81
82 return m_components.at( aComponent );
83}
84
85
87 const std::vector<ALTIUM_VERTICE>& aVertices )
88{
89 for( const ALTIUM_VERTICE& vertex : aVertices )
90 {
91 if( vertex.isRound )
92 {
93 EDA_ANGLE angle( vertex.endangle - vertex.startangle, DEGREES_T );
94 angle.Normalize();
95
96 double startradiant = DEG2RAD( vertex.startangle );
97 double endradiant = DEG2RAD( vertex.endangle );
98 VECTOR2I arcStartOffset = VECTOR2I( KiROUND( std::cos( startradiant ) * vertex.radius ),
99 -KiROUND( std::sin( startradiant ) * vertex.radius ) );
100
101 VECTOR2I arcEndOffset = VECTOR2I( KiROUND( std::cos( endradiant ) * vertex.radius ),
102 -KiROUND( std::sin( endradiant ) * vertex.radius ) );
103
104 VECTOR2I arcStart = vertex.center + arcStartOffset;
105 VECTOR2I arcEnd = vertex.center + arcEndOffset;
106
107 if( GetLineLength( arcStart, vertex.position )
108 < GetLineLength( arcEnd, vertex.position ) )
109 {
110 aLine.Append( SHAPE_ARC( vertex.center, arcStart, -angle ) );
111 }
112 else
113 {
114 aLine.Append( SHAPE_ARC( vertex.center, arcEnd, angle ) );
115 }
116 }
117 else
118 {
119 aLine.Append( vertex.position );
120 }
121 }
122
123 aLine.SetClosed( true );
124}
125
126
128{
129 auto override = m_layermap.find( aAltiumLayer );
130
131 if( override != m_layermap.end() )
132 {
133 return override->second;
134 }
135
136 switch( aAltiumLayer )
137 {
138 case ALTIUM_LAYER::UNKNOWN: return UNDEFINED_LAYER;
139
140 case ALTIUM_LAYER::TOP_LAYER: return F_Cu;
141 case ALTIUM_LAYER::MID_LAYER_1: return In1_Cu;
142 case ALTIUM_LAYER::MID_LAYER_2: return In2_Cu;
143 case ALTIUM_LAYER::MID_LAYER_3: return In3_Cu;
144 case ALTIUM_LAYER::MID_LAYER_4: return In4_Cu;
145 case ALTIUM_LAYER::MID_LAYER_5: return In5_Cu;
146 case ALTIUM_LAYER::MID_LAYER_6: return In6_Cu;
147 case ALTIUM_LAYER::MID_LAYER_7: return In7_Cu;
148 case ALTIUM_LAYER::MID_LAYER_8: return In8_Cu;
149 case ALTIUM_LAYER::MID_LAYER_9: return In9_Cu;
150 case ALTIUM_LAYER::MID_LAYER_10: return In10_Cu;
151 case ALTIUM_LAYER::MID_LAYER_11: return In11_Cu;
152 case ALTIUM_LAYER::MID_LAYER_12: return In12_Cu;
153 case ALTIUM_LAYER::MID_LAYER_13: return In13_Cu;
154 case ALTIUM_LAYER::MID_LAYER_14: return In14_Cu;
155 case ALTIUM_LAYER::MID_LAYER_15: return In15_Cu;
156 case ALTIUM_LAYER::MID_LAYER_16: return In16_Cu;
157 case ALTIUM_LAYER::MID_LAYER_17: return In17_Cu;
158 case ALTIUM_LAYER::MID_LAYER_18: return In18_Cu;
159 case ALTIUM_LAYER::MID_LAYER_19: return In19_Cu;
160 case ALTIUM_LAYER::MID_LAYER_20: return In20_Cu;
161 case ALTIUM_LAYER::MID_LAYER_21: return In21_Cu;
162 case ALTIUM_LAYER::MID_LAYER_22: return In22_Cu;
163 case ALTIUM_LAYER::MID_LAYER_23: return In23_Cu;
164 case ALTIUM_LAYER::MID_LAYER_24: return In24_Cu;
165 case ALTIUM_LAYER::MID_LAYER_25: return In25_Cu;
166 case ALTIUM_LAYER::MID_LAYER_26: return In26_Cu;
167 case ALTIUM_LAYER::MID_LAYER_27: return In27_Cu;
168 case ALTIUM_LAYER::MID_LAYER_28: return In28_Cu;
169 case ALTIUM_LAYER::MID_LAYER_29: return In29_Cu;
170 case ALTIUM_LAYER::MID_LAYER_30: return In30_Cu;
171 case ALTIUM_LAYER::BOTTOM_LAYER: return B_Cu;
172
173 case ALTIUM_LAYER::TOP_OVERLAY: return F_SilkS;
174 case ALTIUM_LAYER::BOTTOM_OVERLAY: return B_SilkS;
175 case ALTIUM_LAYER::TOP_PASTE: return F_Paste;
176 case ALTIUM_LAYER::BOTTOM_PASTE: return B_Paste;
177 case ALTIUM_LAYER::TOP_SOLDER: return F_Mask;
178 case ALTIUM_LAYER::BOTTOM_SOLDER: return B_Mask;
179
180 case ALTIUM_LAYER::INTERNAL_PLANE_1: return UNDEFINED_LAYER;
181 case ALTIUM_LAYER::INTERNAL_PLANE_2: return UNDEFINED_LAYER;
182 case ALTIUM_LAYER::INTERNAL_PLANE_3: return UNDEFINED_LAYER;
183 case ALTIUM_LAYER::INTERNAL_PLANE_4: return UNDEFINED_LAYER;
184 case ALTIUM_LAYER::INTERNAL_PLANE_5: return UNDEFINED_LAYER;
185 case ALTIUM_LAYER::INTERNAL_PLANE_6: return UNDEFINED_LAYER;
186 case ALTIUM_LAYER::INTERNAL_PLANE_7: return UNDEFINED_LAYER;
187 case ALTIUM_LAYER::INTERNAL_PLANE_8: return UNDEFINED_LAYER;
188 case ALTIUM_LAYER::INTERNAL_PLANE_9: return UNDEFINED_LAYER;
189 case ALTIUM_LAYER::INTERNAL_PLANE_10: return UNDEFINED_LAYER;
190 case ALTIUM_LAYER::INTERNAL_PLANE_11: return UNDEFINED_LAYER;
191 case ALTIUM_LAYER::INTERNAL_PLANE_12: return UNDEFINED_LAYER;
192 case ALTIUM_LAYER::INTERNAL_PLANE_13: return UNDEFINED_LAYER;
193 case ALTIUM_LAYER::INTERNAL_PLANE_14: return UNDEFINED_LAYER;
194 case ALTIUM_LAYER::INTERNAL_PLANE_15: return UNDEFINED_LAYER;
195 case ALTIUM_LAYER::INTERNAL_PLANE_16: return UNDEFINED_LAYER;
196
197 case ALTIUM_LAYER::DRILL_GUIDE: return Dwgs_User;
198 case ALTIUM_LAYER::KEEP_OUT_LAYER: return Margin;
199
200 case ALTIUM_LAYER::MECHANICAL_1: return User_1; //Edge_Cuts;
201 case ALTIUM_LAYER::MECHANICAL_2: return User_2;
202 case ALTIUM_LAYER::MECHANICAL_3: return User_3;
203 case ALTIUM_LAYER::MECHANICAL_4: return User_4;
204 case ALTIUM_LAYER::MECHANICAL_5: return User_5;
205 case ALTIUM_LAYER::MECHANICAL_6: return User_6;
206 case ALTIUM_LAYER::MECHANICAL_7: return User_7;
207 case ALTIUM_LAYER::MECHANICAL_8: return User_8;
208 case ALTIUM_LAYER::MECHANICAL_9: return User_9;
209 case ALTIUM_LAYER::MECHANICAL_10: return Dwgs_User;
210 case ALTIUM_LAYER::MECHANICAL_11: return Eco2_User; //Eco1 is used for unknown elements
211 case ALTIUM_LAYER::MECHANICAL_12: return F_Fab;
212 case ALTIUM_LAYER::MECHANICAL_13: return B_Fab; // Don't use courtyard layers for other purposes
213 case ALTIUM_LAYER::MECHANICAL_14: return UNDEFINED_LAYER;
214 case ALTIUM_LAYER::MECHANICAL_15: return UNDEFINED_LAYER;
215 case ALTIUM_LAYER::MECHANICAL_16: return UNDEFINED_LAYER;
216
217 case ALTIUM_LAYER::DRILL_DRAWING: return Dwgs_User;
218 case ALTIUM_LAYER::MULTI_LAYER: return UNDEFINED_LAYER;
219 case ALTIUM_LAYER::CONNECTIONS: return UNDEFINED_LAYER;
220 case ALTIUM_LAYER::BACKGROUND: return UNDEFINED_LAYER;
221 case ALTIUM_LAYER::DRC_ERROR_MARKERS: return UNDEFINED_LAYER;
222 case ALTIUM_LAYER::SELECTIONS: return UNDEFINED_LAYER;
223 case ALTIUM_LAYER::VISIBLE_GRID_1: return UNDEFINED_LAYER;
224 case ALTIUM_LAYER::VISIBLE_GRID_2: return UNDEFINED_LAYER;
225 case ALTIUM_LAYER::PAD_HOLES: return UNDEFINED_LAYER;
226 case ALTIUM_LAYER::VIA_HOLES: return UNDEFINED_LAYER;
227
228 default: return UNDEFINED_LAYER;
229 }
230}
231
232
233std::vector<PCB_LAYER_ID> ALTIUM_PCB::GetKicadLayersToIterate( ALTIUM_LAYER aAltiumLayer ) const
234{
235 static std::set<ALTIUM_LAYER> altiumLayersWithWarning;
236
237 if( aAltiumLayer == ALTIUM_LAYER::MULTI_LAYER || aAltiumLayer == ALTIUM_LAYER::KEEP_OUT_LAYER )
238 {
239 std::vector<PCB_LAYER_ID> layers;
240 layers.reserve( MAX_CU_LAYERS );
241
242 for( PCB_LAYER_ID layer : LSET::AllCuMask().Seq() )
243 {
244 if( !m_board || m_board->IsLayerEnabled( layer ) )
245 layers.emplace_back( layer );
246 }
247
248 return layers;
249 }
250
251 PCB_LAYER_ID klayer = GetKicadLayer( aAltiumLayer );
252
253 if( klayer == UNDEFINED_LAYER )
254 {
255 wxLogWarning( _( "Altium layer (%d) has no KiCad equivalent. It has been moved to KiCad "
256 "layer Eco1_User." ),
257 aAltiumLayer );
258 klayer = Eco1_User;
259 }
260
261 return { klayer };
262}
263
264
265ALTIUM_PCB::ALTIUM_PCB( BOARD* aBoard, PROGRESS_REPORTER* aProgressReporter,
266 const wxString& aLibrary, const wxString& aFootprintName )
267{
268 m_board = aBoard;
269 m_progressReporter = aProgressReporter;
270 m_doneCount = 0;
272 m_totalCount = 0;
274 m_library = aLibrary;
275 m_footprintName = aFootprintName;
276}
277
279{
280}
281
283{
284 const unsigned PROGRESS_DELTA = 250;
285
287 {
288 if( ++m_doneCount > m_lastProgressCount + PROGRESS_DELTA )
289 {
291 / std::max( 1U, m_totalCount ) );
292
294 THROW_IO_ERROR( _( "Open cancelled by user." ) );
295
297 }
298 }
299}
300
301void ALTIUM_PCB::Parse( const ALTIUM_COMPOUND_FILE& altiumPcbFile,
302 const std::map<ALTIUM_PCB_DIR, std::string>& aFileMapping )
303{
304 // this vector simply declares in which order which functions to call.
305 const std::vector<std::tuple<bool, ALTIUM_PCB_DIR, PARSE_FUNCTION_POINTER_fp>> parserOrder = {
306 { true, ALTIUM_PCB_DIR::FILE_HEADER,
307 [this]( const ALTIUM_COMPOUND_FILE& aFile, auto fileHeader )
308 {
309 this->ParseFileHeader( aFile, fileHeader );
310 } },
311 { true, ALTIUM_PCB_DIR::BOARD6,
312 [this]( const ALTIUM_COMPOUND_FILE& aFile, auto fileHeader )
313 {
314 this->ParseBoard6Data( aFile, fileHeader );
315 } },
316 { false, ALTIUM_PCB_DIR::EXTENDPRIMITIVEINFORMATION,
317 [this]( const ALTIUM_COMPOUND_FILE& aFile, auto fileHeader )
318 {
319 this->ParseExtendedPrimitiveInformationData( aFile, fileHeader );
320 } },
321 { true, ALTIUM_PCB_DIR::COMPONENTS6,
322 [this]( const ALTIUM_COMPOUND_FILE& aFile, auto fileHeader )
323 {
324 this->ParseComponents6Data( aFile, fileHeader );
325 } },
326 { false, ALTIUM_PCB_DIR::MODELS,
327 [this, aFileMapping]( const ALTIUM_COMPOUND_FILE& aFile, auto fileHeader )
328 {
329 std::vector<std::string> dir{ aFileMapping.at( ALTIUM_PCB_DIR::MODELS ) };
330 this->ParseModelsData( aFile, fileHeader, dir );
331 } },
332 { true, ALTIUM_PCB_DIR::COMPONENTBODIES6,
333 [this]( const ALTIUM_COMPOUND_FILE& aFile, auto fileHeader )
334 {
335 this->ParseComponentsBodies6Data( aFile, fileHeader );
336 } },
337 { true, ALTIUM_PCB_DIR::NETS6,
338 [this]( const ALTIUM_COMPOUND_FILE& aFile, auto fileHeader )
339 {
340 this->ParseNets6Data( aFile, fileHeader );
341 } },
342 { true, ALTIUM_PCB_DIR::CLASSES6,
343 [this]( const ALTIUM_COMPOUND_FILE& aFile, auto fileHeader )
344 {
345 this->ParseClasses6Data( aFile, fileHeader );
346 } },
347 { true, ALTIUM_PCB_DIR::RULES6,
348 [this]( const ALTIUM_COMPOUND_FILE& aFile, auto fileHeader )
349 {
350 this->ParseRules6Data( aFile, fileHeader );
351 } },
352 { true, ALTIUM_PCB_DIR::DIMENSIONS6,
353 [this]( const ALTIUM_COMPOUND_FILE& aFile, auto fileHeader )
354 {
355 this->ParseDimensions6Data( aFile, fileHeader );
356 } },
357 { true, ALTIUM_PCB_DIR::POLYGONS6,
358 [this]( const ALTIUM_COMPOUND_FILE& aFile, auto fileHeader )
359 {
360 this->ParsePolygons6Data( aFile, fileHeader );
361 } },
362 { true, ALTIUM_PCB_DIR::ARCS6,
363 [this]( const ALTIUM_COMPOUND_FILE& aFile, auto fileHeader )
364 {
365 this->ParseArcs6Data( aFile, fileHeader );
366 } },
367 { true, ALTIUM_PCB_DIR::PADS6,
368 [this]( const ALTIUM_COMPOUND_FILE& aFile, auto fileHeader )
369 {
370 this->ParsePads6Data( aFile, fileHeader );
371 } },
372 { true, ALTIUM_PCB_DIR::VIAS6,
373 [this]( const ALTIUM_COMPOUND_FILE& aFile, auto fileHeader )
374 {
375 this->ParseVias6Data( aFile, fileHeader );
376 } },
377 { true, ALTIUM_PCB_DIR::TRACKS6,
378 [this]( const ALTIUM_COMPOUND_FILE& aFile, auto fileHeader )
379 {
380 this->ParseTracks6Data( aFile, fileHeader );
381 } },
382 { false, ALTIUM_PCB_DIR::WIDESTRINGS6,
383 [this]( const ALTIUM_COMPOUND_FILE& aFile, auto fileHeader )
384 {
385 this->ParseWideStrings6Data( aFile, fileHeader );
386 } },
387 { true, ALTIUM_PCB_DIR::TEXTS6,
388 [this]( const ALTIUM_COMPOUND_FILE& aFile, auto fileHeader )
389 {
390 this->ParseTexts6Data( aFile, fileHeader );
391 } },
392 { true, ALTIUM_PCB_DIR::FILLS6,
393 [this]( const ALTIUM_COMPOUND_FILE& aFile, auto fileHeader )
394 {
395 this->ParseFills6Data( aFile, fileHeader );
396 } },
397 { false, ALTIUM_PCB_DIR::BOARDREGIONS,
398 [this]( const ALTIUM_COMPOUND_FILE& aFile, auto fileHeader )
399 {
400 this->ParseBoardRegionsData( aFile, fileHeader );
401 } },
402 { true, ALTIUM_PCB_DIR::SHAPEBASEDREGIONS6,
403 [this]( const ALTIUM_COMPOUND_FILE& aFile, auto fileHeader )
404 {
405 this->ParseShapeBasedRegions6Data( aFile, fileHeader );
406 } },
407 { true, ALTIUM_PCB_DIR::REGIONS6,
408 [this]( const ALTIUM_COMPOUND_FILE& aFile, auto fileHeader )
409 {
410 this->ParseRegions6Data( aFile, fileHeader );
411 } }
412 };
413
414 if( m_progressReporter != nullptr )
415 {
416 // Count number of records we will read for the progress reporter
417 for( const std::tuple<bool, ALTIUM_PCB_DIR, PARSE_FUNCTION_POINTER_fp>& cur : parserOrder )
418 {
419 bool isRequired;
422 std::tie( isRequired, directory, fp ) = cur;
423
424 if( directory == ALTIUM_PCB_DIR::FILE_HEADER )
425 continue;
426
427 const auto& mappedDirectory = aFileMapping.find( directory );
428
429 if( mappedDirectory == aFileMapping.end() )
430 continue;
431
432 const std::vector<std::string> mappedFile{ mappedDirectory->second, "Header" };
433 const CFB::COMPOUND_FILE_ENTRY* file = altiumPcbFile.FindStream( mappedFile );
434
435 if( file == nullptr )
436 continue;
437
438 ALTIUM_BINARY_PARSER reader( altiumPcbFile, file );
439 uint32_t numOfRecords = reader.Read<uint32_t>();
440
441 if( reader.HasParsingError() )
442 {
443 wxLogError( _( "'%s' was not parsed correctly." ), FormatPath( mappedFile ) );
444 continue;
445 }
446
447 m_totalCount += numOfRecords;
448
449 if( reader.GetRemainingBytes() != 0 )
450 {
451 wxLogError( _( "'%s' was not fully parsed." ), FormatPath( mappedFile ) );
452 continue;
453 }
454 }
455 }
456
457 const auto& boardDirectory = aFileMapping.find( ALTIUM_PCB_DIR::BOARD6 );
458
459 if( boardDirectory != aFileMapping.end() )
460 {
461 std::vector<std::string> mappedFile{ boardDirectory->second, "Data" };
462
463 const CFB::COMPOUND_FILE_ENTRY* file = altiumPcbFile.FindStream( mappedFile );
464
465 if( !file )
466 {
468 "This file does not appear to be in a valid PCB Binary Version 6.0 format. In "
469 "Altium Designer, "
470 "make sure to save as \"PCB Binary Files (*.PcbDoc)\"." ) );
471 }
472 }
473
474 // Parse data in specified order
475 for( const std::tuple<bool, ALTIUM_PCB_DIR, PARSE_FUNCTION_POINTER_fp>& cur : parserOrder )
476 {
477 bool isRequired;
480 std::tie( isRequired, directory, fp ) = cur;
481
482 const auto& mappedDirectory = aFileMapping.find( directory );
483
484 if( mappedDirectory == aFileMapping.end() )
485 {
486 wxASSERT_MSG( !isRequired, wxString::Format( wxT( "Altium Directory of kind %d was "
487 "expected, but no mapping is "
488 "present in the code" ),
489 directory ) );
490 continue;
491 }
492
493 std::vector<std::string> mappedFile{ mappedDirectory->second };
494
495 if( directory != ALTIUM_PCB_DIR::FILE_HEADER )
496 mappedFile.emplace_back( "Data" );
497
498 const CFB::COMPOUND_FILE_ENTRY* file = altiumPcbFile.FindStream( mappedFile );
499
500 if( file != nullptr )
501 {
502 fp( altiumPcbFile, file );
503 }
504 else if( isRequired )
505 {
506 wxLogError( _( "File not found: '%s' for directory '%s'." ), FormatPath( mappedFile ),
507 magic_enum::enum_name( directory ) );
508 }
509 }
510
511 // fixup zone priorities since Altium stores them in the opposite order
512 for( ZONE* zone : m_polygons )
513 {
514 if( !zone )
515 continue;
516
517 // Altium "fills" - not poured in Altium
518 if( zone->GetAssignedPriority() == 1000 )
519 {
520 // Unlikely, but you never know
521 if( m_highest_pour_index >= 1000 )
522 zone->SetAssignedPriority( m_highest_pour_index + 1 );
523
524 continue;
525 }
526
527 int priority = m_highest_pour_index - zone->GetAssignedPriority();
528
529 zone->SetAssignedPriority( priority >= 0 ? priority : 0 );
530 }
531
532 // change priority of outer zone to zero
533 for( std::pair<const ALTIUM_LAYER, ZONE*>& zone : m_outer_plane )
534 zone.second->SetAssignedPriority( 0 );
535
536 // Simplify and fracture zone fills in case we constructed them from tracks (hatched fill)
537 for( ZONE* zone : m_polygons )
538 {
539 if( !zone )
540 continue;
541
542 for( PCB_LAYER_ID layer : zone->GetLayerSet().Seq() )
543 {
544 if( !zone->HasFilledPolysForLayer( layer ) )
545 continue;
546
547 zone->GetFilledPolysList( layer )->Fracture( SHAPE_POLY_SET::PM_STRICTLY_SIMPLE );
548 }
549 }
550
551 // Altium doesn't appear to store either the dimension value nor the dimensioned object in
552 // the dimension record. (Yes, there is a REFERENCE0OBJECTID, but it doesn't point to the
553 // dimensioned object.) We attempt to plug this gap by finding a colocated arc or circle
554 // and using its radius. If there are more than one such arcs/circles, well, :shrug:.
556 {
557 int radius = 0;
558
559 for( BOARD_ITEM* item : m_board->Drawings() )
560 {
561 if( item->Type() != PCB_SHAPE_T )
562 continue;
563
564 PCB_SHAPE* shape = static_cast<PCB_SHAPE*>( item );
565
566 if( shape->GetShape() != SHAPE_T::ARC && shape->GetShape() != SHAPE_T::CIRCLE )
567 continue;
568
569 if( shape->GetPosition() == dim->GetPosition() )
570 {
571 radius = shape->GetRadius();
572 break;
573 }
574 }
575
576 if( radius == 0 )
577 {
578 for( PCB_TRACK* track : m_board->Tracks() )
579 {
580 if( track->Type() != PCB_ARC_T )
581 continue;
582
583 PCB_ARC* arc = static_cast<PCB_ARC*>( track );
584
585 if( arc->GetCenter() == dim->GetPosition() )
586 {
587 radius = arc->GetRadius();
588 break;
589 }
590 }
591 }
592
593 // Move the radius point onto the circumference
594 VECTOR2I radialLine = dim->GetEnd() - dim->GetStart();
595 int totalLength = radialLine.EuclideanNorm();
596
597 // Enforce a minimum on the radialLine else we won't have enough precision to get the
598 // angle from it.
599 radialLine = radialLine.Resize( std::max( radius, 2 ) );
600 dim->SetEnd( dim->GetStart() + (VECTOR2I) radialLine );
601 dim->SetLeaderLength( totalLength - radius );
602 dim->Update();
603 }
604
605 // center board
607
610
611 int desired_x = ( w - bbbox.GetWidth() ) / 2;
612 int desired_y = ( h - bbbox.GetHeight() ) / 2;
613
614 VECTOR2I movementVector( desired_x - bbbox.GetX(), desired_y - bbbox.GetY() );
615 m_board->Move( movementVector );
616
618 bds.SetAuxOrigin( bds.GetAuxOrigin() + movementVector );
619 bds.SetGridOrigin( bds.GetGridOrigin() + movementVector );
620
622}
623
624
626 const wxString& aFootprintName )
627{
628 std::unique_ptr<FOOTPRINT> footprint = std::make_unique<FOOTPRINT>( m_board );
629
630 // TODO: what should we do with those layers?
631 m_layermap.emplace( ALTIUM_LAYER::MECHANICAL_14, Eco2_User );
632 m_layermap.emplace( ALTIUM_LAYER::MECHANICAL_15, Eco2_User );
633 m_layermap.emplace( ALTIUM_LAYER::MECHANICAL_16, Eco2_User );
634
635 m_unicodeStrings.clear();
637
638 // TODO: WideStrings are stored as parameterMap in the case of footprints, not as binary
639 // std::string unicodeStringsStreamName = aFootprintName.ToStdString() + "\\WideStrings";
640 // const CFB::COMPOUND_FILE_ENTRY* unicodeStringsData = altiumLibFile.FindStream( unicodeStringsStreamName );
641 // if( unicodeStringsData != nullptr )
642 // {
643 // ParseWideStrings6Data( altiumLibFile, unicodeStringsData );
644 // }
645
646 std::tuple<wxString, const CFB::COMPOUND_FILE_ENTRY*> ret = altiumLibFile.FindLibFootprintDirName(aFootprintName);
647
648 wxString fpDirName = std::get<0>( ret );
649 const CFB::COMPOUND_FILE_ENTRY* footprintStream = std::get<1>( ret );
650
651 if( fpDirName.IsEmpty() )
652 {
654 wxString::Format( _( "Footprint directory not found: '%s'." ), aFootprintName ) );
655 }
656
657 const std::vector<std::string> streamName{ fpDirName.ToStdString(), "Data" };
658 const CFB::COMPOUND_FILE_ENTRY* footprintData = altiumLibFile.FindStream( footprintStream, { "Data" } );
659
660 if( footprintData == nullptr )
661 {
662 THROW_IO_ERROR( wxString::Format( _( "File not found: '%s'." ),
663 FormatPath( streamName ) ) );
664 }
665
666 ALTIUM_BINARY_PARSER parser( altiumLibFile, footprintData );
667
669 //wxString footprintName = parser.ReadWxString(); // Not used (single-byte char set)
670 parser.SkipSubrecord();
671
672 LIB_ID fpID = AltiumToKiCadLibID( "", aFootprintName ); // TODO: library name
673 footprint->SetFPID( fpID );
674
675 const std::vector<std::string> parametersStreamName{ fpDirName.ToStdString(),
676 "Parameters" };
677 const CFB::COMPOUND_FILE_ENTRY* parametersData =
678 altiumLibFile.FindStream( footprintStream, { "Parameters" } );
679
680 if( parametersData != nullptr )
681 {
682 ALTIUM_BINARY_PARSER parametersReader( altiumLibFile, parametersData );
683 std::map<wxString, wxString> parameterProperties = parametersReader.ReadProperties();
684 wxString description = ALTIUM_PROPS_UTILS::ReadString( parameterProperties,
685 wxT( "DESCRIPTION" ), wxT( "" ) );
686 footprint->SetLibDescription( description );
687 }
688 else
689 {
690 wxLogError( _( "File not found: '%s'." ), FormatPath( parametersStreamName ) );
691 footprint->SetLibDescription( wxT( "" ) );
692 }
693
694 const std::vector<std::string> extendedPrimitiveInformationStreamName{
695 "ExtendedPrimitiveInformation", "Data"
696 };
697 const CFB::COMPOUND_FILE_ENTRY* extendedPrimitiveInformationData =
698 altiumLibFile.FindStream( footprintStream, extendedPrimitiveInformationStreamName );
699
700 if( extendedPrimitiveInformationData != nullptr )
701 ParseExtendedPrimitiveInformationData( altiumLibFile, extendedPrimitiveInformationData );
702
703 footprint->SetReference( wxT( "REF**" ) );
704 footprint->SetValue( aFootprintName );
705 footprint->Reference().SetVisible( true ); // TODO: extract visibility information
706 footprint->Value().SetVisible( true );
707
708 const VECTOR2I defaultTextSize( pcbIUScale.mmToIU( 1.0 ), pcbIUScale.mmToIU( 1.0 ) );
709 const int defaultTextThickness( pcbIUScale.mmToIU( 0.15 ) );
710
711 for( PCB_FIELD* field : footprint->Fields() )
712 {
713 field->SetTextSize( defaultTextSize );
714 field->SetTextThickness( defaultTextThickness );
715 }
716
717 for( int primitiveIndex = 0; parser.GetRemainingBytes() >= 4; primitiveIndex++ )
718 {
719 ALTIUM_RECORD recordtype = static_cast<ALTIUM_RECORD>( parser.Peek<uint8_t>() );
720
721 switch( recordtype )
722 {
723 case ALTIUM_RECORD::ARC:
724 {
725 AARC6 arc( parser );
726 ConvertArcs6ToFootprintItem( footprint.get(), arc, primitiveIndex, false );
727 break;
728 }
729 case ALTIUM_RECORD::PAD:
730 {
731 APAD6 pad( parser );
732 ConvertPads6ToFootprintItem( footprint.get(), pad );
733 break;
734 }
735 case ALTIUM_RECORD::VIA:
736 {
737 AVIA6 via( parser );
738 // TODO: implement
739 break;
740 }
741 case ALTIUM_RECORD::TRACK:
742 {
743 ATRACK6 track( parser );
744 ConvertTracks6ToFootprintItem( footprint.get(), track, primitiveIndex, false );
745 break;
746 }
747 case ALTIUM_RECORD::TEXT:
748 {
749 ATEXT6 text( parser, m_unicodeStrings );
750 ConvertTexts6ToFootprintItem( footprint.get(), text );
751 break;
752 }
753 case ALTIUM_RECORD::FILL:
754 {
755 AFILL6 fill( parser );
756 ConvertFills6ToFootprintItem( footprint.get(), fill, false );
757 break;
758 }
759 case ALTIUM_RECORD::REGION:
760 {
761 AREGION6 region( parser, false );
762 ConvertShapeBasedRegions6ToFootprintItem( footprint.get(), region, primitiveIndex );
763 break;
764 }
765 case ALTIUM_RECORD::MODEL:
766 {
767 ACOMPONENTBODY6 componentBody( parser );
768 // Won't be supported for now, as we would need to extract the model
769 break;
770 }
771 default:
772 THROW_IO_ERROR( wxString::Format( _( "Record of unknown type: '%d'." ), recordtype ) );
773 }
774 }
775
776 // Auto-position reference and value
777 footprint->AutoPositionFields();
778
779 if( parser.HasParsingError() )
780 {
781 THROW_IO_ERROR( wxString::Format( wxT( "%s stream was not parsed correctly" ),
782 FormatPath( streamName ) ) );
783 }
784
785 if( parser.GetRemainingBytes() != 0 )
786 {
787 THROW_IO_ERROR( wxString::Format( wxT( "%s stream is not fully parsed" ),
788 FormatPath( streamName ) ) );
789 }
790
791 return footprint.release();
792}
793
794int ALTIUM_PCB::GetNetCode( uint16_t aId ) const
795{
796 if( aId == ALTIUM_NET_UNCONNECTED )
797 {
799 }
800 else if( m_altiumToKicadNetcodes.size() < aId )
801 {
802 THROW_IO_ERROR( wxString::Format( wxT( "Netcode with id %d does not exist. Only %d nets "
803 "are known" ),
804 aId, m_altiumToKicadNetcodes.size() ) );
805 }
806 else
807 {
808 return m_altiumToKicadNetcodes[ aId ];
809 }
810}
811
812const ARULE6* ALTIUM_PCB::GetRule( ALTIUM_RULE_KIND aKind, const wxString& aName ) const
813{
814 const auto rules = m_rules.find( aKind );
815
816 if( rules == m_rules.end() )
817 return nullptr;
818
819 for( const ARULE6& rule : rules->second )
820 {
821 if( rule.name == aName )
822 return &rule;
823 }
824
825 return nullptr;
826}
827
829{
830 const auto rules = m_rules.find( aKind );
831
832 if( rules == m_rules.end() )
833 return nullptr;
834
835 for( const ARULE6& rule : rules->second )
836 {
837 if( rule.scope1expr == wxT( "All" ) && rule.scope2expr == wxT( "All" ) )
838 return &rule;
839 }
840
841 return nullptr;
842}
843
845 const CFB::COMPOUND_FILE_ENTRY* aEntry )
846{
847 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
848
850 wxString header = reader.ReadWxString();
851
852 //std::cout << "HEADER: " << header << std::endl; // tells me: PCB 5.0 Binary File
853
854 //reader.SkipSubrecord();
855
856 // TODO: does not seem to work all the time at the moment
857 //if( reader.GetRemainingBytes() != 0 )
858 // THROW_IO_ERROR( "FileHeader stream is not fully parsed" );
859}
860
862 const CFB::COMPOUND_FILE_ENTRY* aEntry )
863{
865 m_progressReporter->Report( _( "Loading extended primitive information data..." ) );
866
867 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
868
869 while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
870 {
871 checkpoint();
872 AEXTENDED_PRIMITIVE_INFORMATION elem( reader );
873
875 std::move( elem ) );
876 }
877
878 if( reader.GetRemainingBytes() != 0 )
879 THROW_IO_ERROR( wxT( "ExtendedPrimitiveInformation stream is not fully parsed" ) );
880}
881
883 const CFB::COMPOUND_FILE_ENTRY* aEntry )
884{
886 m_progressReporter->Report( _( "Loading board data..." ) );
887
888 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
889
890 checkpoint();
891 ABOARD6 elem( reader );
892
893 if( reader.GetRemainingBytes() != 0 )
894 THROW_IO_ERROR( wxT( "Board6 stream is not fully parsed" ) );
895
898
899 // read layercount from stackup, because LAYERSETSCOUNT is not always correct?!
900 size_t layercount = 0;
901 size_t layerid = static_cast<size_t>( ALTIUM_LAYER::TOP_LAYER );
902
903 while( layerid < elem.stackup.size() && layerid != 0 )
904 {
905 layerid = elem.stackup[ layerid - 1 ].nextId;
906 layercount++;
907 }
908
909 size_t kicadLayercount = ( layercount % 2 == 0 ) ? layercount : layercount + 1;
910 m_board->SetCopperLayerCount( kicadLayercount );
911
913 BOARD_STACKUP& stackup = designSettings.GetStackupDescriptor();
914
915 // create board stackup
916 stackup.RemoveAll(); // Just to be sure
917 stackup.BuildDefaultStackupList( &designSettings, layercount );
918
919 auto it = stackup.GetList().begin();
920
921 // find first copper layer
922 for( ; it != stackup.GetList().end() && ( *it )->GetType() != BS_ITEM_TYPE_COPPER; ++it )
923 ;
924
925 auto curLayer = static_cast<int>( F_Cu );
926
927 for( size_t altiumLayerId = static_cast<size_t>( ALTIUM_LAYER::TOP_LAYER );
928 altiumLayerId < elem.stackup.size() && altiumLayerId != 0;
929 altiumLayerId = elem.stackup[altiumLayerId - 1].nextId )
930 {
931 // array starts with 0, but stackup with 1
932 ABOARD6_LAYER_STACKUP& layer = elem.stackup.at( altiumLayerId - 1 );
933
934 // handle unused layer in case of odd layercount
935 if( layer.nextId == 0 && layercount != kicadLayercount )
936 {
937 m_board->SetLayerName( ( *it )->GetBrdLayerId(), wxT( "[unused]" ) );
938
939 if( ( *it )->GetType() != BS_ITEM_TYPE_COPPER )
940 THROW_IO_ERROR( wxT( "Board6 stream, unexpected item while parsing stackup" ) );
941
942 ( *it )->SetThickness( 0 );
943
944 ++it;
945
946 if( ( *it )->GetType() != BS_ITEM_TYPE_DIELECTRIC )
947 THROW_IO_ERROR( wxT( "Board6 stream, unexpected item while parsing stackup" ) );
948
949 ( *it )->SetThickness( 0, 0 );
950 ( *it )->SetThicknessLocked( true, 0 );
951 ++it;
952 }
953
954 m_layermap.insert( { static_cast<ALTIUM_LAYER>( altiumLayerId ),
955 static_cast<PCB_LAYER_ID>( curLayer++ ) } );
956
957 if( ( *it )->GetType() != BS_ITEM_TYPE_COPPER )
958 THROW_IO_ERROR( wxT( "Board6 stream, unexpected item while parsing stackup" ) );
959
960 ( *it )->SetThickness( layer.copperthick );
961
962 ALTIUM_LAYER alayer = static_cast<ALTIUM_LAYER>( altiumLayerId );
963 PCB_LAYER_ID klayer = ( *it )->GetBrdLayerId();
964
965 m_board->SetLayerName( klayer, layer.name );
966
967 if( layer.copperthick == 0 )
968 m_board->SetLayerType( klayer, LAYER_T::LT_JUMPER ); // used for things like wirebonding
969 else if( IsAltiumLayerAPlane( alayer ) )
970 m_board->SetLayerType( klayer, LAYER_T::LT_POWER );
971
972 if( klayer == B_Cu )
973 {
974 if( layer.nextId != 0 )
975 THROW_IO_ERROR( wxT( "Board6 stream, unexpected id while parsing last stackup layer" ) );
976
977 // overwrite entry from internal -> bottom
978 m_layermap[alayer] = B_Cu;
979 break;
980 }
981
982 ++it;
983
984 if( ( *it )->GetType() != BS_ITEM_TYPE_DIELECTRIC )
985 THROW_IO_ERROR( wxT( "Board6 stream, unexpected item while parsing stackup" ) );
986
987 ( *it )->SetThickness( layer.dielectricthick, 0 );
988 ( *it )->SetMaterial( layer.dielectricmaterial.empty() ?
990 wxString( layer.dielectricmaterial ) );
991 ( *it )->SetEpsilonR( layer.dielectricconst, 0 );
992
993 ++it;
994 }
995
996 // Set name of all non-cu layers
997 for( size_t altiumLayerId = static_cast<size_t>( ALTIUM_LAYER::TOP_OVERLAY );
998 altiumLayerId <= static_cast<size_t>( ALTIUM_LAYER::BOTTOM_SOLDER ); altiumLayerId++ )
999 {
1000 // array starts with 0, but stackup with 1
1001 ABOARD6_LAYER_STACKUP& layer = elem.stackup.at( altiumLayerId - 1 );
1002
1003 ALTIUM_LAYER alayer = static_cast<ALTIUM_LAYER>( altiumLayerId );
1004 PCB_LAYER_ID klayer = GetKicadLayer( alayer );
1005
1006 m_board->SetLayerName( klayer, layer.name );
1007 }
1008
1009 for( size_t altiumLayerId = static_cast<size_t>( ALTIUM_LAYER::MECHANICAL_1 );
1010 altiumLayerId <= static_cast<size_t>( ALTIUM_LAYER::MECHANICAL_16 ); altiumLayerId++ )
1011 {
1012 // array starts with 0, but stackup with 1
1013 ABOARD6_LAYER_STACKUP& layer = elem.stackup.at( altiumLayerId - 1 );
1014
1015 ALTIUM_LAYER alayer = static_cast<ALTIUM_LAYER>( altiumLayerId );
1016 PCB_LAYER_ID klayer = GetKicadLayer( alayer );
1017
1018 m_board->SetLayerName( klayer, layer.name );
1019 }
1020
1022}
1023
1024void ALTIUM_PCB::HelperCreateBoardOutline( const std::vector<ALTIUM_VERTICE>& aVertices )
1025{
1026 SHAPE_LINE_CHAIN lineChain;
1027 HelperShapeLineChainFromAltiumVertices( lineChain, aVertices );
1028
1030 LINE_STYLE::SOLID );
1031
1032 for( int i = 0; i <= lineChain.PointCount() && i != -1; i = lineChain.NextShape( i ) )
1033 {
1034 if( lineChain.IsArcStart( i ) )
1035 {
1036 const SHAPE_ARC& currentArc = lineChain.Arc( lineChain.ArcIndex( i ) );
1037 int nextShape = lineChain.NextShape( i );
1038 bool isLastShape = nextShape < 0;
1039
1040 std::unique_ptr<PCB_SHAPE> shape = std::make_unique<PCB_SHAPE>( m_board, SHAPE_T::ARC );
1041
1042 shape->SetStroke( stroke );
1043 shape->SetLayer( Edge_Cuts );
1044 shape->SetArcGeometry( currentArc.GetP0(), currentArc.GetArcMid(), currentArc.GetP1() );
1045
1046 m_board->Add( shape.release(), ADD_MODE::APPEND );
1047 }
1048 else
1049 {
1050 const SEG& seg = lineChain.Segment( i );
1051
1052 std::unique_ptr<PCB_SHAPE> shape = std::make_unique<PCB_SHAPE>( m_board, SHAPE_T::SEGMENT );
1053
1054 shape->SetStroke( stroke );
1055 shape->SetLayer( Edge_Cuts );
1056 shape->SetStart( seg.A );
1057 shape->SetEnd( seg.B );
1058
1059 m_board->Add( shape.release(), ADD_MODE::APPEND );
1060 }
1061 }
1062}
1063
1065 const CFB::COMPOUND_FILE_ENTRY* aEntry )
1066{
1067 if( m_progressReporter )
1068 m_progressReporter->Report( _( "Loading netclasses..." ) );
1069
1070 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
1071
1072 while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
1073 {
1074 checkpoint();
1075 ACLASS6 elem( reader );
1076
1077 if( elem.kind == ALTIUM_CLASS_KIND::NET_CLASS )
1078 {
1079 std::shared_ptr<NETCLASS> nc = std::make_shared<NETCLASS>( elem.name );
1080
1081 for( const wxString& name : elem.names )
1082 {
1083 m_board->GetDesignSettings().m_NetSettings->m_NetClassPatternAssignments.push_back(
1084 {
1085 std::make_unique<EDA_COMBINED_MATCHER>( name, CTX_NETCLASS ),
1086 nc->GetName()
1087 } );
1088 }
1089
1090 if( m_board->GetDesignSettings().m_NetSettings->m_NetClasses.count( nc->GetName() ) )
1091 {
1092 // Name conflict, happens in some unknown circumstances
1093 // unique_ptr will delete nc on this code path
1094 wxLogWarning( _( "More than one Altium netclass with name '%s' found. "
1095 "Only the first one will be imported." ), elem.name );
1096 }
1097 else
1098 {
1099 m_board->GetDesignSettings().m_NetSettings->m_NetClasses[ nc->GetName() ] = nc;
1100 }
1101 }
1102 }
1103
1104 if( reader.GetRemainingBytes() != 0 )
1105 THROW_IO_ERROR( wxT( "Classes6 stream is not fully parsed" ) );
1106
1108}
1109
1111 const CFB::COMPOUND_FILE_ENTRY* aEntry )
1112{
1113 if( m_progressReporter )
1114 m_progressReporter->Report( _( "Loading components..." ) );
1115
1116 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
1117
1118 uint16_t componentId = 0;
1119
1120 while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
1121 {
1122 checkpoint();
1123 ACOMPONENT6 elem( reader );
1124
1125 std::unique_ptr<FOOTPRINT> footprint = std::make_unique<FOOTPRINT>( m_board );
1126
1128
1129 footprint->SetFPID( fpID );
1130
1131 footprint->SetPosition( elem.position );
1132 footprint->SetOrientationDegrees( elem.rotation );
1133
1134 // KiCad netlisting requires parts to have non-digit + digit annotation.
1135 // If the reference begins with a number, we prepend 'UNK' (unknown) for the source designator
1136 wxString reference = elem.sourcedesignator;
1137
1138 if( reference.find_first_not_of( "0123456789" ) == wxString::npos )
1139 reference.Prepend( wxT( "UNK" ) );
1140
1141 footprint->SetReference( reference );
1142
1143 footprint->SetLocked( elem.locked );
1144 footprint->Reference().SetVisible( elem.nameon );
1145 footprint->Value().SetVisible( elem.commenton );
1146 footprint->SetLayer( elem.layer == ALTIUM_LAYER::TOP_LAYER ? F_Cu : B_Cu );
1147
1148 m_components.emplace_back( footprint.get() );
1149 m_board->Add( footprint.release(), ADD_MODE::APPEND );
1150
1151 componentId++;
1152 }
1153
1154 if( reader.GetRemainingBytes() != 0 )
1155 THROW_IO_ERROR( wxT( "Components6 stream is not fully parsed" ) );
1156}
1157
1158
1160double normalizeAngleDegrees( double Angle, double aMin, double aMax )
1161{
1162 while( Angle < aMin )
1163 Angle += 360.0;
1164
1165 while( Angle >= aMax )
1166 Angle -= 360.0;
1167
1168 return Angle;
1169}
1170
1171
1173 const CFB::COMPOUND_FILE_ENTRY* aEntry )
1174{
1175 if( m_progressReporter )
1176 m_progressReporter->Report( _( "Loading component 3D models..." ) );
1177
1178 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
1179
1180 while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
1181 {
1182 checkpoint();
1183 ACOMPONENTBODY6 elem( reader ); // TODO: implement
1184
1185 if( elem.component == ALTIUM_COMPONENT_NONE )
1186 continue; // TODO: we do not support components for the board yet
1187
1188 if( m_components.size() <= elem.component )
1189 {
1190 THROW_IO_ERROR( wxString::Format( wxT( "ComponentsBodies6 stream tries to access "
1191 "component id %d of %d existing components" ),
1192 elem.component,
1193 m_components.size() ) );
1194 }
1195
1196 if( !elem.modelIsEmbedded )
1197 continue;
1198
1199 auto modelTuple = m_models.find( elem.modelId );
1200
1201 if( modelTuple == m_models.end() )
1202 {
1203 wxLogError( wxT( "ComponentsBodies6 stream tries to access model id %s which does not "
1204 "exist" ),
1205 elem.modelId );
1206 continue;
1207 }
1208
1209 FOOTPRINT* footprint = m_components.at( elem.component );
1210 const VECTOR2I& fpPosition = footprint->GetPosition();
1211
1212 FP_3DMODEL modelSettings;
1213
1214 modelSettings.m_Filename = modelTuple->second;
1215
1216 modelSettings.m_Offset.x = pcbIUScale.IUTomm((int) elem.modelPosition.x - fpPosition.x );
1217 modelSettings.m_Offset.y = -pcbIUScale.IUTomm((int) elem.modelPosition.y - fpPosition.y );
1218 modelSettings.m_Offset.z = pcbIUScale.IUTomm( (int) elem.modelPosition.z );
1219
1220 EDA_ANGLE orientation = footprint->GetOrientation();
1221
1222 if( footprint->IsFlipped() )
1223 {
1224 modelSettings.m_Offset.y = -modelSettings.m_Offset.y;
1225 orientation = -orientation;
1226 }
1227
1228 RotatePoint( &modelSettings.m_Offset.x, &modelSettings.m_Offset.y, orientation );
1229
1230 modelSettings.m_Rotation.x = normalizeAngleDegrees( -elem.modelRotation.x, -180, 180 );
1231 modelSettings.m_Rotation.y = normalizeAngleDegrees( -elem.modelRotation.y, -180, 180 );
1232 modelSettings.m_Rotation.z = normalizeAngleDegrees( -elem.modelRotation.z
1233 + elem.rotation
1234 + orientation.AsDegrees(),
1235 -180, 180 );
1236 modelSettings.m_Opacity = elem.bodyOpacity;
1237
1238 footprint->Models().push_back( modelSettings );
1239 }
1240
1241 if( reader.GetRemainingBytes() != 0 )
1242 THROW_IO_ERROR( wxT( "ComponentsBodies6 stream is not fully parsed" ) );
1243}
1244
1245
1247{
1248 if( aElem.referencePoint.size() != 2 )
1249 THROW_IO_ERROR( wxT( "Incorrect number of reference points for linear dimension object" ) );
1250
1251 PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
1252
1253 if( klayer == UNDEFINED_LAYER )
1254 {
1255 wxLogWarning( _( "Dimension found on an Altium layer (%d) with no KiCad equivalent. "
1256 "It has been moved to KiCad layer Eco1_User." ),
1257 aElem.layer );
1258 klayer = Eco1_User;
1259 }
1260
1261 VECTOR2I referencePoint0 = aElem.referencePoint.at( 0 );
1262 VECTOR2I referencePoint1 = aElem.referencePoint.at( 1 );
1263
1264 std::unique_ptr<PCB_DIM_ALIGNED> dimension = std::make_unique<PCB_DIM_ALIGNED>( m_board, PCB_DIM_ALIGNED_T );
1265
1266 dimension->SetPrecision( static_cast<DIM_PRECISION>( aElem.textprecision ) );
1267 dimension->SetLayer( klayer );
1268 dimension->SetStart( referencePoint0 );
1269
1270 if( referencePoint0 != aElem.xy1 )
1271 {
1281 VECTOR2I direction = aElem.xy1 - referencePoint0;
1282 VECTOR2I directionNormalVector = VECTOR2I( -direction.y, direction.x );
1283 SEG segm1( referencePoint0, referencePoint0 + directionNormalVector );
1284 SEG segm2( referencePoint1, referencePoint1 + direction );
1285 OPT_VECTOR2I intersection( segm1.Intersect( segm2, true, true ) );
1286
1287 if( !intersection )
1288 THROW_IO_ERROR( wxT( "Invalid dimension. This should never happen." ) );
1289
1290 dimension->SetEnd( *intersection );
1291
1292 int height = static_cast<int>( EuclideanNorm( direction ) );
1293
1294 if( ( direction.x > 0 || direction.y < 0 ) != ( aElem.angle >= 180.0 ) )
1295 height = -height;
1296
1297 dimension->SetHeight( height );
1298 }
1299 else
1300 {
1301 dimension->SetEnd( referencePoint1 );
1302 }
1303
1304 dimension->SetLineThickness( aElem.linewidth );
1305
1306 dimension->SetUnitsFormat( DIM_UNITS_FORMAT::NO_SUFFIX );
1307 dimension->SetPrefix( aElem.textprefix );
1308
1309 // Suffix normally (but not always) holds the units
1310 wxRegEx units( wxS( "(mm)|(in)|(mils)|(thou)|(')|(\")" ), wxRE_ADVANCED );
1311
1312 if( units.Matches( aElem.textsuffix ) )
1313 dimension->SetUnitsFormat( DIM_UNITS_FORMAT::BARE_SUFFIX );
1314 else
1315 dimension->SetSuffix( aElem.textsuffix );
1316
1317 dimension->SetTextThickness( aElem.textlinewidth );
1318 dimension->SetTextSize( VECTOR2I( aElem.textheight, aElem.textheight ) );
1319 dimension->SetItalic( aElem.textitalic );
1320
1321#if 0 // we don't currently support bold; map to thicker text
1322 dimension->Text().SetBold( aElem.textbold );
1323#else
1324 if( aElem.textbold )
1325 dimension->SetTextThickness( dimension->GetTextThickness() * BOLD_FACTOR );
1326#endif
1327
1328 switch( aElem.textunit )
1329 {
1330 case ALTIUM_UNIT::INCHES:
1331 dimension->SetUnits( EDA_UNITS::INCHES );
1332 break;
1333 case ALTIUM_UNIT::MILS:
1334 dimension->SetUnits( EDA_UNITS::MILS );
1335 break;
1336 case ALTIUM_UNIT::MILLIMETERS:
1337 case ALTIUM_UNIT::CENTIMETER:
1338 dimension->SetUnits( EDA_UNITS::MILLIMETRES );
1339 break;
1340 default:
1341 break;
1342 }
1343
1344 m_board->Add( dimension.release(), ADD_MODE::APPEND );
1345}
1346
1347
1349{
1350 if( aElem.referencePoint.size() < 2 )
1351 THROW_IO_ERROR( wxT( "Not enough reference points for radial dimension object" ) );
1352
1353 PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
1354
1355 if( klayer == UNDEFINED_LAYER )
1356 {
1357 wxLogWarning( _( "Dimension found on an Altium layer (%d) with no KiCad equivalent. "
1358 "It has been moved to KiCad layer Eco1_User." ),
1359 aElem.layer );
1360 klayer = Eco1_User;
1361 }
1362
1363 VECTOR2I referencePoint0 = aElem.referencePoint.at( 0 );
1364 VECTOR2I referencePoint1 = aElem.referencePoint.at( 1 );
1365
1366 std::unique_ptr<PCB_DIM_RADIAL> dimension = std::make_unique<PCB_DIM_RADIAL>( m_board );
1367
1368 dimension->SetPrecision( static_cast<DIM_PRECISION>( aElem.textprecision ) );
1369 dimension->SetLayer( klayer );
1370 dimension->SetStart( referencePoint0 );
1371 dimension->SetEnd( aElem.xy1 );
1372 dimension->SetLineThickness( aElem.linewidth );
1373 dimension->SetKeepTextAligned( false );
1374
1375 dimension->SetPrefix( aElem.textprefix );
1376
1377 // Suffix normally holds the units
1378 dimension->SetUnitsFormat( aElem.textsuffix.IsEmpty() ? DIM_UNITS_FORMAT::NO_SUFFIX
1379 : DIM_UNITS_FORMAT::BARE_SUFFIX );
1380
1381 switch( aElem.textunit )
1382 {
1383 case ALTIUM_UNIT::INCHES:
1384 dimension->SetUnits( EDA_UNITS::INCHES );
1385 break;
1386 case ALTIUM_UNIT::MILS:
1387 dimension->SetUnits( EDA_UNITS::MILS );
1388 break;
1389 case ALTIUM_UNIT::MILLIMETERS:
1390 case ALTIUM_UNIT::CENTIMETER:
1391 dimension->SetUnits( EDA_UNITS::MILLIMETRES );
1392 break;
1393 default:
1394 break;
1395 }
1396
1397 if( aElem.textPoint.empty() )
1398 {
1399 wxLogError( wxT( "No text position present for leader dimension object" ) );
1400 return;
1401 }
1402
1403 dimension->SetTextPos( aElem.textPoint.at( 0 ) );
1404 dimension->SetTextThickness( aElem.textlinewidth );
1405 dimension->SetTextSize( VECTOR2I( aElem.textheight, aElem.textheight ) );
1406 dimension->SetItalic( aElem.textitalic );
1407
1408#if 0 // we don't currently support bold; map to thicker text
1409 dimension->SetBold( aElem.textbold );
1410#else
1411 if( aElem.textbold )
1412 dimension->SetTextThickness( dimension->GetTextThickness() * BOLD_FACTOR );
1413#endif
1414
1415 // It's unclear exactly how Altium figures it's text positioning, but this gets us reasonably
1416 // close.
1417 dimension->SetVertJustify( GR_TEXT_V_ALIGN_BOTTOM );
1418 dimension->SetHorizJustify( GR_TEXT_H_ALIGN_LEFT );
1419
1420 int yAdjust = dimension->GetTextBox().GetCenter().y - dimension->GetTextPos().y;
1421 dimension->SetTextPos( dimension->GetTextPos() + VECTOR2I( 0, yAdjust + aElem.textgap ) );
1422 dimension->SetVertJustify( GR_TEXT_V_ALIGN_CENTER );
1423
1424 m_radialDimensions.push_back( dimension.get() );
1425 m_board->Add( dimension.release(), ADD_MODE::APPEND );
1426}
1427
1428
1430{
1431 PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
1432
1433 if( klayer == UNDEFINED_LAYER )
1434 {
1435 wxLogWarning( _( "Dimension found on an Altium layer (%d) with no KiCad equivalent. "
1436 "It has been moved to KiCad layer Eco1_User." ),
1437 aElem.layer );
1438 klayer = Eco1_User;
1439 }
1440
1441 if( !aElem.referencePoint.empty() )
1442 {
1443 VECTOR2I referencePoint0 = aElem.referencePoint.at( 0 );
1444
1445 // line
1446 VECTOR2I last = referencePoint0;
1447 for( size_t i = 1; i < aElem.referencePoint.size(); i++ )
1448 {
1449 std::unique_ptr<PCB_SHAPE> shape = std::make_unique<PCB_SHAPE>( m_board, SHAPE_T::SEGMENT );
1450
1451 shape->SetLayer( klayer );
1452 shape->SetStroke( STROKE_PARAMS( aElem.linewidth, LINE_STYLE::SOLID ) );
1453 shape->SetStart( last );
1454 shape->SetEnd( aElem.referencePoint.at( i ) );
1455 last = aElem.referencePoint.at( i );
1456
1457 m_board->Add( shape.release(), ADD_MODE::APPEND );
1458 }
1459
1460 // arrow
1461 if( aElem.referencePoint.size() >= 2 )
1462 {
1463 VECTOR2I dirVec = aElem.referencePoint.at( 1 ) - referencePoint0;
1464
1465 if( dirVec.x != 0 || dirVec.y != 0 )
1466 {
1467 double scaling = EuclideanNorm( dirVec ) / aElem.arrowsize;
1468 VECTOR2I arrVec =
1469 VECTOR2I( KiROUND( dirVec.x / scaling ), KiROUND( dirVec.y / scaling ) );
1470 RotatePoint( arrVec, EDA_ANGLE( 20.0, DEGREES_T ) );
1471
1472 {
1473 std::unique_ptr<PCB_SHAPE> shape1 =
1474 std::make_unique<PCB_SHAPE>( m_board, SHAPE_T::SEGMENT );
1475
1476 shape1->SetLayer( klayer );
1477 shape1->SetStroke( STROKE_PARAMS( aElem.linewidth, LINE_STYLE::SOLID ) );
1478 shape1->SetStart( referencePoint0 );
1479 shape1->SetEnd( referencePoint0 + arrVec );
1480
1481 m_board->Add( shape1.release(), ADD_MODE::APPEND );
1482 }
1483
1484 RotatePoint( arrVec, EDA_ANGLE( -40.0, DEGREES_T ) );
1485
1486 {
1487 std::unique_ptr<PCB_SHAPE> shape2 =
1488 std::make_unique<PCB_SHAPE>( m_board, SHAPE_T::SEGMENT );
1489
1490 shape2->SetLayer( klayer );
1491 shape2->SetStroke( STROKE_PARAMS( aElem.linewidth, LINE_STYLE::SOLID ) );
1492 shape2->SetStart( referencePoint0 );
1493 shape2->SetEnd( referencePoint0 + arrVec );
1494
1495 m_board->Add( shape2.release(), ADD_MODE::APPEND );
1496 }
1497 }
1498 }
1499 }
1500
1501 if( aElem.textPoint.empty() )
1502 {
1503 wxLogError( wxT( "No text position present for leader dimension object" ) );
1504 return;
1505 }
1506
1507 std::unique_ptr<PCB_TEXT> text = std::make_unique<PCB_TEXT>( m_board );
1508
1509 text->SetText( aElem.textformat );
1510 text->SetPosition( aElem.textPoint.at( 0 ) );
1511 text->SetLayer( klayer );
1512 text->SetTextSize( VECTOR2I( aElem.textheight, aElem.textheight ) ); // TODO: parse text width
1513 text->SetTextThickness( aElem.textlinewidth );
1514 text->SetHorizJustify( GR_TEXT_H_ALIGN_LEFT );
1515 text->SetVertJustify( GR_TEXT_V_ALIGN_BOTTOM );
1516
1517 m_board->Add( text.release(), ADD_MODE::APPEND );
1518}
1519
1520
1522{
1523 PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
1524
1525 if( klayer == UNDEFINED_LAYER )
1526 {
1527 wxLogWarning( _( "Dimension found on an Altium layer (%d) with no KiCad equivalent. "
1528 "It has been moved to KiCad layer Eco1_User." ),
1529 aElem.layer );
1530 klayer = Eco1_User;
1531 }
1532
1533 for( size_t i = 0; i < aElem.referencePoint.size(); i++ )
1534 {
1535 std::unique_ptr<PCB_SHAPE> shape = std::make_unique<PCB_SHAPE>( m_board, SHAPE_T::SEGMENT );
1536
1537 shape->SetLayer( klayer );
1538 shape->SetStroke( STROKE_PARAMS( aElem.linewidth, LINE_STYLE::SOLID ) );
1539 shape->SetStart( aElem.referencePoint.at( i ) );
1540 // shape->SetEnd( /* TODO: seems to be based on TEXTY */ );
1541
1542 m_board->Add( shape.release(), ADD_MODE::APPEND );
1543 }
1544}
1545
1546
1548{
1549 PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
1550
1551 if( klayer == UNDEFINED_LAYER )
1552 {
1553 wxLogWarning( _( "Dimension found on an Altium layer (%d) with no KiCad equivalent. "
1554 "It has been moved to KiCad layer Eco1_User." ),
1555 aElem.layer );
1556 klayer = Eco1_User;
1557 }
1558
1559 VECTOR2I vec = VECTOR2I( 0, aElem.height / 2 );
1560 RotatePoint( vec, EDA_ANGLE( aElem.angle, DEGREES_T ) );
1561
1562 std::unique_ptr<PCB_DIM_CENTER> dimension = std::make_unique<PCB_DIM_CENTER>( m_board );
1563
1564 dimension->SetLayer( klayer );
1565 dimension->SetLineThickness( aElem.linewidth );
1566 dimension->SetStart( aElem.xy1 );
1567 dimension->SetEnd( aElem.xy1 + vec );
1568
1569 m_board->Add( dimension.release(), ADD_MODE::APPEND );
1570}
1571
1572
1574 const CFB::COMPOUND_FILE_ENTRY* aEntry )
1575{
1576 if( m_progressReporter )
1577 m_progressReporter->Report( _( "Loading dimension drawings..." ) );
1578
1579 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
1580
1581 while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
1582 {
1583 checkpoint();
1584 ADIMENSION6 elem( reader );
1585
1586 switch( elem.kind )
1587 {
1588 case ALTIUM_DIMENSION_KIND::LINEAR:
1590 break;
1591 case ALTIUM_DIMENSION_KIND::RADIAL:
1593 break;
1594 case ALTIUM_DIMENSION_KIND::LEADER:
1596 break;
1597 case ALTIUM_DIMENSION_KIND::DATUM:
1598 wxLogError( _( "Ignored dimension of kind %d (not yet supported)." ), elem.kind );
1599 // HelperParseDimensions6Datum( elem );
1600 break;
1601 case ALTIUM_DIMENSION_KIND::CENTER:
1603 break;
1604 default:
1605 wxLogError( _( "Ignored dimension of kind %d (not yet supported)." ), elem.kind );
1606 break;
1607 }
1608 }
1609
1610 if( reader.GetRemainingBytes() != 0 )
1611 THROW_IO_ERROR( wxT( "Dimensions6 stream is not fully parsed" ) );
1612}
1613
1614
1616 const CFB::COMPOUND_FILE_ENTRY* aEntry,
1617 const std::vector<std::string>& aRootDir )
1618{
1619 if( m_progressReporter )
1620 m_progressReporter->Report( _( "Loading 3D models..." ) );
1621
1622 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
1623
1624 if( reader.GetRemainingBytes() == 0 )
1625 return;
1626
1627 wxString projectPath = wxPathOnly( m_board->GetFileName() );
1628 // TODO: set KIPRJMOD always after import (not only when loading project)?
1629 wxSetEnv( PROJECT_VAR_NAME, projectPath );
1630
1631 // TODO: make this path configurable?
1632 const wxString altiumModelDir = wxT( "ALTIUM_EMBEDDED_MODELS" );
1633
1634 wxFileName altiumModelsPath = wxFileName::DirName( projectPath );
1635 wxString kicadModelPrefix = wxT( "${KIPRJMOD}/" ) + altiumModelDir + wxT( "/" );
1636
1637 if( !altiumModelsPath.AppendDir( altiumModelDir ) )
1638 THROW_IO_ERROR( wxT( "Cannot construct directory path for step models" ) );
1639
1640 // Create dir if it does not exist
1641 if( !altiumModelsPath.DirExists() )
1642 {
1643 if( !altiumModelsPath.Mkdir() )
1644 {
1645 wxLogError( _( "Failed to create folder '%s'." ) + wxS( " " )
1646 + _( "No 3D-models will be imported." ),
1647 altiumModelsPath.GetFullPath() );
1648 return;
1649 }
1650 }
1651
1652 int idx = 0;
1653 wxString invalidChars = wxFileName::GetForbiddenChars();
1654
1655 while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
1656 {
1657 checkpoint();
1658 AMODEL elem( reader );
1659
1660 std::vector<std::string> stepPath = aRootDir;
1661 stepPath.emplace_back( std::to_string( idx ) );
1662
1663 bool validName = !elem.name.IsEmpty() && elem.name.IsAscii() &&
1664 wxString::npos == elem.name.find_first_of( invalidChars );
1665 wxString storageName = !validName ? wxString::Format( wxT( "model_%d" ), idx )
1666 : elem.name;
1667 wxFileName storagePath( altiumModelsPath.GetPath(), storageName );
1668
1669 idx++;
1670
1671 const CFB::COMPOUND_FILE_ENTRY* stepEntry = aAltiumPcbFile.FindStream( stepPath );
1672
1673 if( stepEntry == nullptr )
1674 {
1675 wxLogError( _( "File not found: '%s'. 3D-model not imported." ),
1676 FormatPath( stepPath ) );
1677 continue;
1678 }
1679
1680 size_t stepSize = static_cast<size_t>( stepEntry->size );
1681 std::vector<char> stepContent( stepSize );
1682
1683 // read file into buffer
1684 aAltiumPcbFile.GetCompoundFileReader().ReadFile( stepEntry, 0, stepContent.data(),
1685 stepSize );
1686
1687 if( !storagePath.IsDirWritable() )
1688 {
1689 wxLogError( _( "Insufficient permissions to save file '%s'." ),
1690 storagePath.GetFullPath() );
1691 continue;
1692 }
1693
1694 wxMemoryInputStream stepStream( stepContent.data(), stepSize );
1695 wxZlibInputStream zlibInputStream( stepStream );
1696
1697 wxFFileOutputStream outputStream( storagePath.GetFullPath() );
1698
1699 if( !outputStream.IsOk() )
1700 {
1701 wxLogError( _( "Unable to write file '%s'." ), storagePath.GetFullPath() );
1702 continue;
1703 }
1704
1705 outputStream.Write( zlibInputStream );
1706 outputStream.Close();
1707
1708 m_models.insert( { elem.id, kicadModelPrefix + storageName } );
1709 }
1710
1711 if( reader.GetRemainingBytes() != 0 )
1712 THROW_IO_ERROR( wxT( "Models stream is not fully parsed" ) );
1713}
1714
1715
1717 const CFB::COMPOUND_FILE_ENTRY* aEntry )
1718{
1719 if( m_progressReporter )
1720 m_progressReporter->Report( _( "Loading nets..." ) );
1721
1722 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
1723
1724 wxASSERT( m_altiumToKicadNetcodes.empty() );
1725 while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
1726 {
1727 checkpoint();
1728 ANET6 elem( reader );
1729
1730 NETINFO_ITEM* netInfo = new NETINFO_ITEM( m_board, elem.name, 0 );
1731 m_board->Add( netInfo, ADD_MODE::APPEND );
1732
1733 // needs to be called after m_board->Add() as assign us the NetCode
1734 m_altiumToKicadNetcodes.push_back( netInfo->GetNetCode() );
1735 }
1736
1737 if( reader.GetRemainingBytes() != 0 )
1738 THROW_IO_ERROR( wxT( "Nets6 stream is not fully parsed" ) );
1739}
1740
1742 const CFB::COMPOUND_FILE_ENTRY* aEntry )
1743{
1744 if( m_progressReporter )
1745 m_progressReporter->Report( _( "Loading polygons..." ) );
1746
1747 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
1748
1749 while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
1750 {
1751 checkpoint();
1752 APOLYGON6 elem( reader );
1753
1754 SHAPE_LINE_CHAIN linechain;
1756
1757 if( linechain.PointCount() < 3 )
1758 {
1759 // We have found multiple Altium files with polygon records containing nothing but two
1760 // coincident vertices. These polygons do not appear when opening the file in Altium.
1761 // https://gitlab.com/kicad/code/kicad/-/issues/8183
1762 // Also, polygons with less than 3 points are not supported in KiCad.
1763 //
1764 // wxLogError( _( "Polygon has only %d point extracted from %ld vertices. At least 2 "
1765 // "points are required." ),
1766 // linechain.PointCount(),
1767 // elem.vertices.size() );
1768
1769 m_polygons.emplace_back( nullptr );
1770 continue;
1771 }
1772
1773 std::unique_ptr<ZONE> zone = std::make_unique<ZONE>( m_board );
1774 m_polygons.emplace_back( zone.get() );
1775
1776 zone->SetNetCode( GetNetCode( elem.net ) );
1777 zone->SetPosition( elem.vertices.at( 0 ).position );
1778 zone->SetLocked( elem.locked );
1779 zone->SetAssignedPriority( elem.pourindex > 0 ? elem.pourindex : 0 );
1780 zone->Outline()->AddOutline( linechain );
1781
1782 HelperSetZoneLayers( *zone, elem.layer );
1783
1784 if( elem.pourindex > m_highest_pour_index )
1786
1787 const ARULE6* planeClearanceRule = GetRuleDefault( ALTIUM_RULE_KIND::PLANE_CLEARANCE );
1788 const ARULE6* zoneClearanceRule = GetRule( ALTIUM_RULE_KIND::CLEARANCE,
1789 wxT( "PolygonClearance" ) );
1790 int planeLayers = 0;
1791 int signalLayers = 0;
1792 int clearance = 0;
1793
1794 for( PCB_LAYER_ID layer : zone->GetLayerSet().Seq() )
1795 {
1796 LAYER_T layerType = m_board->GetLayerType( layer );
1797
1798 if( layerType == LT_POWER || layerType == LT_MIXED )
1799 planeLayers++;
1800
1801 if( layerType == LT_SIGNAL || layerType == LT_MIXED )
1802 signalLayers++;
1803 }
1804
1805 if( planeLayers > 0 && planeClearanceRule )
1806 clearance = std::max( clearance, planeClearanceRule->planeclearanceClearance );
1807
1808 if( signalLayers > 0 && zoneClearanceRule )
1809 clearance = std::max( clearance, zoneClearanceRule->clearanceGap );
1810
1811 if( clearance > 0 )
1812 zone->SetLocalClearance( clearance );
1813
1814 const ARULE6* polygonConnectRule = GetRuleDefault( ALTIUM_RULE_KIND::POLYGON_CONNECT );
1815
1816 if( polygonConnectRule != nullptr )
1817 {
1818 switch( polygonConnectRule->polygonconnectStyle )
1819 {
1820 case ALTIUM_CONNECT_STYLE::DIRECT:
1821 zone->SetPadConnection( ZONE_CONNECTION::FULL );
1822 break;
1823
1824 case ALTIUM_CONNECT_STYLE::NONE:
1825 zone->SetPadConnection( ZONE_CONNECTION::NONE );
1826 break;
1827
1828 default:
1829 case ALTIUM_CONNECT_STYLE::RELIEF:
1830 zone->SetPadConnection( ZONE_CONNECTION::THERMAL );
1831 break;
1832 }
1833
1834 // TODO: correct variables?
1835 zone->SetThermalReliefSpokeWidth(
1836 polygonConnectRule->polygonconnectReliefconductorwidth );
1837 zone->SetThermalReliefGap( polygonConnectRule->polygonconnectAirgapwidth );
1838
1839 if( polygonConnectRule->polygonconnectReliefconductorwidth < zone->GetMinThickness() )
1840 zone->SetMinThickness( polygonConnectRule->polygonconnectReliefconductorwidth );
1841 }
1842
1843 if( IsAltiumLayerAPlane( elem.layer ) )
1844 {
1845 // outer zone will be set to priority 0 later.
1846 zone->SetAssignedPriority( 1 );
1847
1848 // check if this is the outer zone by simply comparing the BBOX
1849 const auto& outer_plane = m_outer_plane.find( elem.layer );
1850 if( outer_plane == m_outer_plane.end()
1851 || zone->GetBoundingBox().Contains( outer_plane->second->GetBoundingBox() ) )
1852 {
1853 m_outer_plane[elem.layer] = zone.get();
1854 }
1855 }
1856
1857 if( elem.hatchstyle != ALTIUM_POLYGON_HATCHSTYLE::SOLID
1858 && elem.hatchstyle != ALTIUM_POLYGON_HATCHSTYLE::UNKNOWN )
1859 {
1860 zone->SetFillMode( ZONE_FILL_MODE::HATCH_PATTERN );
1861 zone->SetHatchThickness( elem.trackwidth );
1862
1863 if( elem.hatchstyle == ALTIUM_POLYGON_HATCHSTYLE::NONE )
1864 {
1865 // use a small hack to get us only an outline (hopefully)
1866 const BOX2I& bbox = zone->GetBoundingBox();
1867 zone->SetHatchGap( std::max( bbox.GetHeight(), bbox.GetWidth() ) );
1868 }
1869 else
1870 {
1871 zone->SetHatchGap( elem.gridsize - elem.trackwidth );
1872 }
1873
1874 if( elem.hatchstyle == ALTIUM_POLYGON_HATCHSTYLE::DEGREE_45 )
1875 zone->SetHatchOrientation( ANGLE_45 );
1876 }
1877
1878 zone->SetBorderDisplayStyle( ZONE_BORDER_DISPLAY_STYLE::DIAGONAL_EDGE,
1880
1881 m_board->Add( zone.release(), ADD_MODE::APPEND );
1882 }
1883
1884 if( reader.GetRemainingBytes() != 0 )
1885 THROW_IO_ERROR( wxT( "Polygons6 stream is not fully parsed" ) );
1886}
1887
1889 const CFB::COMPOUND_FILE_ENTRY* aEntry )
1890{
1891 if( m_progressReporter )
1892 m_progressReporter->Report( _( "Loading rules..." ) );
1893
1894 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
1895
1896 while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
1897 {
1898 checkpoint();
1899 ARULE6 elem( reader );
1900
1901 m_rules[elem.kind].emplace_back( elem );
1902 }
1903
1904 // sort rules by priority
1905 for( std::pair<const ALTIUM_RULE_KIND, std::vector<ARULE6>>& val : m_rules )
1906 {
1907 std::sort( val.second.begin(), val.second.end(),
1908 []( const ARULE6& lhs, const ARULE6& rhs )
1909 {
1910 return lhs.priority < rhs.priority;
1911 } );
1912 }
1913
1914 const ARULE6* clearanceRule = GetRuleDefault( ALTIUM_RULE_KIND::CLEARANCE );
1915 const ARULE6* trackWidthRule = GetRuleDefault( ALTIUM_RULE_KIND::WIDTH );
1916 const ARULE6* routingViasRule = GetRuleDefault( ALTIUM_RULE_KIND::ROUTING_VIAS );
1917 const ARULE6* holeSizeRule = GetRuleDefault( ALTIUM_RULE_KIND::HOLE_SIZE );
1918 const ARULE6* holeToHoleRule = GetRuleDefault( ALTIUM_RULE_KIND::HOLE_TO_HOLE_CLEARANCE );
1919
1920 if( clearanceRule )
1922
1923 if( trackWidthRule )
1924 {
1926 // TODO: construct a custom rule for preferredWidth and maxLimit values
1927 }
1928
1929 if( routingViasRule )
1930 {
1931 m_board->GetDesignSettings().m_ViasMinSize = routingViasRule->minWidth;
1933 }
1934
1935 if( holeSizeRule )
1936 {
1937 // TODO: construct a custom rule for minLimit / maxLimit values
1938 }
1939
1940 if( holeToHoleRule )
1942
1943 const ARULE6* soldermaskRule = GetRuleDefault( ALTIUM_RULE_KIND::SOLDER_MASK_EXPANSION );
1944 const ARULE6* pastemaskRule = GetRuleDefault( ALTIUM_RULE_KIND::PASTE_MASK_EXPANSION );
1945
1946 if( soldermaskRule )
1948
1949 if( pastemaskRule )
1951
1952 if( reader.GetRemainingBytes() != 0 )
1953 THROW_IO_ERROR( wxT( "Rules6 stream is not fully parsed" ) );
1954}
1955
1957 const CFB::COMPOUND_FILE_ENTRY* aEntry )
1958{
1959 if( m_progressReporter )
1960 m_progressReporter->Report( _( "Loading board regions..." ) );
1961
1962 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
1963
1964 while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
1965 {
1966 checkpoint();
1967 AREGION6 elem( reader, false );
1968
1969 // TODO: implement?
1970 }
1971
1972 if( reader.GetRemainingBytes() != 0 )
1973 THROW_IO_ERROR( wxT( "BoardRegions stream is not fully parsed" ) );
1974}
1975
1977 const CFB::COMPOUND_FILE_ENTRY* aEntry )
1978{
1979 if( m_progressReporter )
1980 m_progressReporter->Report( _( "Loading polygons..." ) );
1981
1982 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
1983
1984 /* TODO: use Header section of file */
1985 for( int primitiveIndex = 0; reader.GetRemainingBytes() >= 4; primitiveIndex++ )
1986 {
1987 checkpoint();
1988 AREGION6 elem( reader, true );
1989
1991 || elem.kind == ALTIUM_REGION_KIND::BOARD_CUTOUT )
1992 {
1993 // TODO: implement all different types for footprints
1995 }
1996 else
1997 {
1998 FOOTPRINT* footprint = HelperGetFootprint( elem.component );
1999 ConvertShapeBasedRegions6ToFootprintItem( footprint, elem, primitiveIndex );
2000 }
2001 }
2002
2003 if( reader.GetRemainingBytes() != 0 )
2004 THROW_IO_ERROR( "ShapeBasedRegions6 stream is not fully parsed" );
2005}
2006
2007
2009{
2010 if( aElem.kind == ALTIUM_REGION_KIND::BOARD_CUTOUT )
2011 {
2013 }
2014 else if( aElem.kind == ALTIUM_REGION_KIND::POLYGON_CUTOUT || aElem.is_keepout )
2015 {
2016 SHAPE_LINE_CHAIN linechain;
2018
2019 if( linechain.PointCount() < 3 )
2020 {
2021 // We have found multiple Altium files with polygon records containing nothing but
2022 // two coincident vertices. These polygons do not appear when opening the file in
2023 // Altium. https://gitlab.com/kicad/code/kicad/-/issues/8183
2024 // Also, polygons with less than 3 points are not supported in KiCad.
2025 return;
2026 }
2027
2028 std::unique_ptr<ZONE> zone = std::make_unique<ZONE>( m_board );
2029
2030 zone->SetIsRuleArea( true );
2031
2032 if( aElem.is_keepout )
2033 {
2035 }
2036 else if( aElem.kind == ALTIUM_REGION_KIND::POLYGON_CUTOUT )
2037 {
2038 zone->SetDoNotAllowCopperPour( true );
2039 zone->SetDoNotAllowVias( false );
2040 zone->SetDoNotAllowTracks( false );
2041 zone->SetDoNotAllowPads( false );
2042 zone->SetDoNotAllowFootprints( false );
2043 }
2044
2045 zone->SetPosition( aElem.outline.at( 0 ).position );
2046 zone->Outline()->AddOutline( linechain );
2047
2048 HelperSetZoneLayers( *zone, aElem.layer );
2049
2050 zone->SetBorderDisplayStyle( ZONE_BORDER_DISPLAY_STYLE::DIAGONAL_EDGE,
2052
2053 m_board->Add( zone.release(), ADD_MODE::APPEND );
2054 }
2055 else if( aElem.kind == ALTIUM_REGION_KIND::DASHED_OUTLINE )
2056 {
2057 PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
2058
2059 if( klayer == UNDEFINED_LAYER )
2060 {
2061 wxLogWarning(
2062 _( "Dashed outline found on an Altium layer (%d) with no KiCad equivalent. "
2063 "It has been moved to KiCad layer Eco1_User." ),
2064 aElem.layer );
2065 klayer = Eco1_User;
2066 }
2067
2068 SHAPE_LINE_CHAIN linechain;
2070
2071 if( linechain.PointCount() < 3 )
2072 {
2073 // We have found multiple Altium files with polygon records containing nothing but
2074 // two coincident vertices. These polygons do not appear when opening the file in
2075 // Altium. https://gitlab.com/kicad/code/kicad/-/issues/8183
2076 // Also, polygons with less than 3 points are not supported in KiCad.
2077 return;
2078 }
2079
2080 std::unique_ptr<PCB_SHAPE> shape = std::make_unique<PCB_SHAPE>( m_board, SHAPE_T::POLY );
2081
2082 shape->SetPolyShape( linechain );
2083 shape->SetFilled( false );
2084 shape->SetLayer( klayer );
2085 shape->SetStroke( STROKE_PARAMS( pcbIUScale.mmToIU( 0.1 ), LINE_STYLE::DASH ) );
2086
2087 m_board->Add( shape.release(), ADD_MODE::APPEND );
2088 }
2089 else if( aElem.kind == ALTIUM_REGION_KIND::COPPER )
2090 {
2091 if( aElem.polygon == ALTIUM_POLYGON_NONE )
2092 {
2093 for( PCB_LAYER_ID klayer : GetKicadLayersToIterate( aElem.layer ) )
2095 }
2096 }
2097 else if( aElem.kind == ALTIUM_REGION_KIND::BOARD_CUTOUT )
2098 {
2100 }
2101 else
2102 {
2103 wxLogError( _( "Ignored polygon shape of kind %d (not yet supported)." ), aElem.kind );
2104 }
2105}
2106
2107
2109 const AREGION6& aElem,
2110 const int aPrimitiveIndex )
2111{
2112 if( aElem.kind == ALTIUM_REGION_KIND::POLYGON_CUTOUT || aElem.is_keepout )
2113 {
2114 SHAPE_LINE_CHAIN linechain;
2116
2117 if( linechain.PointCount() < 3 )
2118 {
2119 // We have found multiple Altium files with polygon records containing nothing but
2120 // two coincident vertices. These polygons do not appear when opening the file in
2121 // Altium. https://gitlab.com/kicad/code/kicad/-/issues/8183
2122 // Also, polygons with less than 3 points are not supported in KiCad.
2123 return;
2124 }
2125
2126 std::unique_ptr<ZONE> zone = std::make_unique<ZONE>( aFootprint );
2127
2128 zone->SetIsRuleArea( true );
2129
2130 if( aElem.is_keepout )
2131 {
2133 }
2134 else if( aElem.kind == ALTIUM_REGION_KIND::POLYGON_CUTOUT )
2135 {
2136 zone->SetDoNotAllowCopperPour( true );
2137 zone->SetDoNotAllowVias( false );
2138 zone->SetDoNotAllowTracks( false );
2139 zone->SetDoNotAllowPads( false );
2140 zone->SetDoNotAllowFootprints( false );
2141 }
2142
2143 zone->SetPosition( aElem.outline.at( 0 ).position );
2144 zone->Outline()->AddOutline( linechain );
2145
2146 HelperSetZoneLayers( *zone, aElem.layer );
2147
2148 zone->SetBorderDisplayStyle( ZONE_BORDER_DISPLAY_STYLE::DIAGONAL_EDGE,
2150
2151 aFootprint->Add( zone.release(), ADD_MODE::APPEND );
2152 }
2153 else if( aElem.kind == ALTIUM_REGION_KIND::COPPER )
2154 {
2155 if( aElem.polygon == ALTIUM_POLYGON_NONE )
2156 {
2157 for( PCB_LAYER_ID klayer : GetKicadLayersToIterate( aElem.layer ) )
2158 {
2159 ConvertShapeBasedRegions6ToFootprintItemOnLayer( aFootprint, aElem, klayer,
2160 aPrimitiveIndex );
2161 }
2162 }
2163 }
2164 else if( aElem.kind == ALTIUM_REGION_KIND::DASHED_OUTLINE
2165 || aElem.kind == ALTIUM_REGION_KIND::BOARD_CUTOUT )
2166 {
2167 PCB_LAYER_ID klayer = aElem.kind == ALTIUM_REGION_KIND::BOARD_CUTOUT
2168 ? Edge_Cuts
2169 : GetKicadLayer( aElem.layer );
2170
2171 if( klayer == UNDEFINED_LAYER )
2172 {
2173 if( !m_footprintName.IsEmpty() )
2174 {
2175 wxLogWarning( _( "Loading library '%s':\n"
2176 "Footprint %s contains a dashed outline on Altium layer (%d) with "
2177 "no KiCad equivalent. It has been moved to KiCad layer Eco1_User." ),
2178 m_library,
2180 aElem.layer );
2181 }
2182 else
2183 {
2184 wxLogWarning( _( "Footprint %s contains a dashed outline on Altium layer (%d) with "
2185 "no KiCad equivalent. It has been moved to KiCad layer Eco1_User." ),
2186 aFootprint->GetReference(),
2187 aElem.layer );
2188 }
2189
2190 klayer = Eco1_User;
2191 }
2192
2193 SHAPE_LINE_CHAIN linechain;
2195
2196 if( linechain.PointCount() < 3 )
2197 {
2198 // We have found multiple Altium files with polygon records containing nothing but
2199 // two coincident vertices. These polygons do not appear when opening the file in
2200 // Altium. https://gitlab.com/kicad/code/kicad/-/issues/8183
2201 // Also, polygons with less than 3 points are not supported in KiCad.
2202 return;
2203 }
2204
2205 std::unique_ptr<PCB_SHAPE> shape = std::make_unique<PCB_SHAPE>( aFootprint, SHAPE_T::POLY );
2206
2207 shape->SetPolyShape( linechain );
2208 shape->SetFilled( false );
2209 shape->SetLayer( klayer );
2210
2211 if( aElem.kind == ALTIUM_REGION_KIND::DASHED_OUTLINE )
2212 shape->SetStroke( STROKE_PARAMS( pcbIUScale.mmToIU( 0.1 ), LINE_STYLE::DASH ) );
2213 else
2214 shape->SetStroke( STROKE_PARAMS( pcbIUScale.mmToIU( 0.1 ), LINE_STYLE::SOLID ) );
2215
2216 aFootprint->Add( shape.release(), ADD_MODE::APPEND );
2217 }
2218 else
2219 {
2220 if( !m_footprintName.IsEmpty() )
2221 {
2222 wxLogError( _( "Error loading library '%s':\n"
2223 "Footprint %s contains polygon shape of kind %d (not yet supported)." ),
2224 m_library,
2226 aElem.kind );
2227 }
2228 else
2229 {
2230 wxLogError( _( "Footprint %s contains polygon shape of kind %d (not yet supported)." ),
2231 aFootprint->GetReference(),
2232 aElem.kind );
2233 }
2234 }
2235}
2236
2237
2239 PCB_LAYER_ID aLayer )
2240{
2241 SHAPE_LINE_CHAIN linechain;
2243
2244 if( linechain.PointCount() < 3 )
2245 {
2246 // We have found multiple Altium files with polygon records containing nothing
2247 // but two coincident vertices. These polygons do not appear when opening the
2248 // file in Altium. https://gitlab.com/kicad/code/kicad/-/issues/8183
2249 // Also, polygons with less than 3 points are not supported in KiCad.
2250 return;
2251 }
2252
2253 SHAPE_POLY_SET polySet;
2254 polySet.AddOutline( linechain );
2255
2256 for( const std::vector<ALTIUM_VERTICE>& hole : aElem.holes )
2257 {
2258 SHAPE_LINE_CHAIN hole_linechain;
2259 HelperShapeLineChainFromAltiumVertices( hole_linechain, hole );
2260
2261 if( hole_linechain.PointCount() < 3 )
2262 continue;
2263
2264 polySet.AddHole( hole_linechain );
2265 }
2266
2267 std::unique_ptr<PCB_SHAPE> shape = std::make_unique<PCB_SHAPE>( m_board, SHAPE_T::POLY );
2268
2269 shape->SetPolyShape( polySet );
2270 shape->SetFilled( true );
2271 shape->SetLayer( aLayer );
2272 shape->SetStroke( STROKE_PARAMS( 0 ) );
2273
2274 if( IsCopperLayer( aLayer ) && aElem.net != ALTIUM_NET_UNCONNECTED )
2275 {
2276 shape->SetNetCode( GetNetCode( aElem.net ) );
2277 }
2278
2279 m_board->Add( shape.release(), ADD_MODE::APPEND );
2280}
2281
2282
2284 const AREGION6& aElem,
2285 PCB_LAYER_ID aLayer,
2286 const int aPrimitiveIndex )
2287{
2288 SHAPE_LINE_CHAIN linechain;
2290
2291 if( linechain.PointCount() < 3 )
2292 {
2293 // We have found multiple Altium files with polygon records containing nothing
2294 // but two coincident vertices. These polygons do not appear when opening the
2295 // file in Altium. https://gitlab.com/kicad/code/kicad/-/issues/8183
2296 // Also, polygons with less than 3 points are not supported in KiCad.
2297 return;
2298 }
2299
2300 SHAPE_POLY_SET polySet;
2301 polySet.AddOutline( linechain );
2302
2303 for( const std::vector<ALTIUM_VERTICE>& hole : aElem.holes )
2304 {
2305 SHAPE_LINE_CHAIN hole_linechain;
2306 HelperShapeLineChainFromAltiumVertices( hole_linechain, hole );
2307
2308 if( hole_linechain.PointCount() < 3 )
2309 continue;
2310
2311 polySet.AddHole( hole_linechain );
2312 }
2313
2314 if( aLayer == F_Cu || aLayer == B_Cu )
2315 {
2316 std::unique_ptr<PAD> pad = std::make_unique<PAD>( aFootprint );
2317
2318 LSET padLayers;
2319 padLayers.set( aLayer );
2320
2321 pad->SetKeepTopBottom( false ); // TODO: correct? This seems to be KiCad default on import
2322 pad->SetAttribute( PAD_ATTRIB::SMD );
2323 pad->SetShape( PAD_SHAPE::CUSTOM );
2324 pad->SetThermalSpokeAngle( ANGLE_90 );
2325
2326 int anchorSize = 1;
2327 VECTOR2I anchorPos = linechain.CPoint( 0 );
2328
2329 pad->SetShape( PAD_SHAPE::CUSTOM );
2330 pad->SetAnchorPadShape( PAD_SHAPE::CIRCLE );
2331 pad->SetSize( { anchorSize, anchorSize } );
2332 pad->SetPosition( anchorPos );
2333
2334 SHAPE_POLY_SET shapePolys = polySet;
2335 shapePolys.Move( -anchorPos );
2336 pad->AddPrimitivePoly( shapePolys, 0, true );
2337
2338 auto& map = m_extendedPrimitiveInformationMaps[ALTIUM_RECORD::REGION];
2339 auto it = map.find( aPrimitiveIndex );
2340
2341 if( it != map.end() )
2342 {
2343 const AEXTENDED_PRIMITIVE_INFORMATION& info = it->second;
2344
2345 if( info.pastemaskexpansionmode == ALTIUM_MODE::MANUAL )
2346 {
2347 pad->SetLocalSolderPasteMargin(
2348 info.pastemaskexpansionmanual ? info.pastemaskexpansionmanual : 1 );
2349 }
2350
2351 if( info.soldermaskexpansionmode == ALTIUM_MODE::MANUAL )
2352 {
2353 pad->SetLocalSolderMaskMargin(
2354 info.soldermaskexpansionmanual ? info.soldermaskexpansionmanual : 1 );
2355 }
2356
2357 if( info.pastemaskexpansionmode != ALTIUM_MODE::NONE )
2358 padLayers.set( aLayer == F_Cu ? F_Paste : B_Paste );
2359
2360 if( info.soldermaskexpansionmode != ALTIUM_MODE::NONE )
2361 padLayers.set( aLayer == F_Cu ? F_Mask : B_Mask );
2362 }
2363
2364 pad->SetLayerSet( padLayers );
2365
2366 aFootprint->Add( pad.release(), ADD_MODE::APPEND );
2367 }
2368 else
2369 {
2370 std::unique_ptr<PCB_SHAPE> shape = std::make_unique<PCB_SHAPE>( aFootprint, SHAPE_T::POLY );
2371
2372 shape->SetPolyShape( polySet );
2373 shape->SetFilled( true );
2374 shape->SetLayer( aLayer );
2375 shape->SetStroke( STROKE_PARAMS( 0 ) );
2376
2377 aFootprint->Add( shape.release(), ADD_MODE::APPEND );
2378 }
2379}
2380
2381
2383 const CFB::COMPOUND_FILE_ENTRY* aEntry )
2384{
2385 if( m_progressReporter )
2386 m_progressReporter->Report( _( "Loading zone fills..." ) );
2387
2388 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
2389
2390 while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
2391 {
2392 checkpoint();
2393 AREGION6 elem( reader, false );
2394
2395 if( elem.polygon != ALTIUM_POLYGON_NONE )
2396 {
2397 if( m_polygons.size() <= elem.polygon )
2398 {
2399 THROW_IO_ERROR( wxString::Format( "Region stream tries to access polygon id %d "
2400 "of %d existing polygons.",
2401 elem.polygon,
2402 m_polygons.size() ) );
2403 }
2404
2405 ZONE* zone = m_polygons.at( elem.polygon );
2406
2407 if( zone == nullptr )
2408 {
2409 continue; // we know the zone id, but because we do not know the layer we did not
2410 // add it!
2411 }
2412
2413 PCB_LAYER_ID klayer = GetKicadLayer( elem.layer );
2414
2415 if( klayer == UNDEFINED_LAYER )
2416 continue; // Just skip it for now. Users can fill it themselves.
2417
2418 SHAPE_LINE_CHAIN linechain;
2419
2420 for( const ALTIUM_VERTICE& vertice : elem.outline )
2421 linechain.Append( vertice.position );
2422
2423 linechain.Append( elem.outline.at( 0 ).position );
2424 linechain.SetClosed( true );
2425
2426 SHAPE_POLY_SET fill;
2427 fill.AddOutline( linechain );
2428
2429 for( const std::vector<ALTIUM_VERTICE>& hole : elem.holes )
2430 {
2431 SHAPE_LINE_CHAIN hole_linechain;
2432
2433 for( const ALTIUM_VERTICE& vertice : hole )
2434 hole_linechain.Append( vertice.position );
2435
2436 hole_linechain.Append( hole.at( 0 ).position );
2437 hole_linechain.SetClosed( true );
2438 fill.AddHole( hole_linechain );
2439 }
2440
2441 if( zone->HasFilledPolysForLayer( klayer ) )
2442 fill.BooleanAdd( *zone->GetFill( klayer ), SHAPE_POLY_SET::PM_STRICTLY_SIMPLE );
2443
2445
2446 zone->SetFilledPolysList( klayer, fill );
2447 zone->SetIsFilled( true );
2448 zone->SetNeedRefill( false );
2449 }
2450 }
2451
2452 if( reader.GetRemainingBytes() != 0 )
2453 THROW_IO_ERROR( wxT( "Regions6 stream is not fully parsed" ) );
2454}
2455
2456
2458 const CFB::COMPOUND_FILE_ENTRY* aEntry )
2459{
2460 if( m_progressReporter )
2461 m_progressReporter->Report( _( "Loading arcs..." ) );
2462
2463 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
2464
2465 for( int primitiveIndex = 0; reader.GetRemainingBytes() >= 4; primitiveIndex++ )
2466 {
2467 checkpoint();
2468 AARC6 elem( reader );
2469
2470 if( elem.component == ALTIUM_COMPONENT_NONE )
2471 {
2472 ConvertArcs6ToBoardItem( elem, primitiveIndex );
2473 }
2474 else
2475 {
2476 FOOTPRINT* footprint = HelperGetFootprint( elem.component );
2477 ConvertArcs6ToFootprintItem( footprint, elem, primitiveIndex, true );
2478 }
2479 }
2480
2481 if( reader.GetRemainingBytes() != 0 )
2482 THROW_IO_ERROR( "Arcs6 stream is not fully parsed" );
2483}
2484
2485
2487{
2488 if( aElem.startangle == 0. && aElem.endangle == 360. )
2489 {
2490 aShape->SetShape( SHAPE_T::CIRCLE );
2491
2492 // TODO: other variants to define circle?
2493 aShape->SetStart( aElem.center );
2494 aShape->SetEnd( aElem.center - VECTOR2I( 0, aElem.radius ) );
2495 }
2496 else
2497 {
2498 aShape->SetShape( SHAPE_T::ARC );
2499
2500 EDA_ANGLE includedAngle( aElem.endangle - aElem.startangle, DEGREES_T );
2501 EDA_ANGLE startAngle( aElem.endangle, DEGREES_T );
2502
2503 VECTOR2I startOffset = VECTOR2I( KiROUND( startAngle.Cos() * aElem.radius ),
2504 -KiROUND( startAngle.Sin() * aElem.radius ) );
2505
2506 aShape->SetCenter( aElem.center );
2507 aShape->SetStart( aElem.center + startOffset );
2508 aShape->SetArcAngleAndEnd( includedAngle.Normalize(), true );
2509 }
2510}
2511
2512
2513void ALTIUM_PCB::ConvertArcs6ToBoardItem( const AARC6& aElem, const int aPrimitiveIndex )
2514{
2515 if( aElem.polygon != ALTIUM_POLYGON_NONE && aElem.polygon != ALTIUM_POLYGON_BOARD )
2516 {
2517 if( m_polygons.size() <= aElem.polygon )
2518 {
2519 THROW_IO_ERROR( wxString::Format( "Tracks stream tries to access polygon id %u "
2520 "of %zu existing polygons.",
2521 aElem.polygon, m_polygons.size() ) );
2522 }
2523
2524 ZONE* zone = m_polygons.at( aElem.polygon );
2525
2526 if( zone == nullptr )
2527 {
2528 return; // we know the zone id, but because we do not know the layer we did not
2529 // add it!
2530 }
2531
2532 PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
2533
2534 if( klayer == UNDEFINED_LAYER )
2535 return; // Just skip it for now. Users can fill it themselves.
2536
2537 SHAPE_POLY_SET* fill = zone->GetFill( klayer );
2538
2539 // This is not the actual board item. We can use it to create the polygon for the region
2540 PCB_SHAPE shape( nullptr );
2541
2542 ConvertArcs6ToPcbShape( aElem, &shape );
2543 shape.SetStroke( STROKE_PARAMS( aElem.width, LINE_STYLE::SOLID ) );
2544
2545 shape.EDA_SHAPE::TransformShapeToPolygon( *fill, 0, ARC_HIGH_DEF, ERROR_INSIDE );
2546 // Will be simplified and fractured later
2547
2548 zone->SetIsFilled( true );
2549 zone->SetNeedRefill( false );
2550
2551 return;
2552 }
2553
2554 if( aElem.is_keepout || aElem.layer == ALTIUM_LAYER::KEEP_OUT_LAYER
2555 || IsAltiumLayerAPlane( aElem.layer ) )
2556 {
2557 // This is not the actual board item. We can use it to create the polygon for the region
2558 PCB_SHAPE shape( nullptr );
2559
2560 ConvertArcs6ToPcbShape( aElem, &shape );
2561 shape.SetStroke( STROKE_PARAMS( aElem.width, LINE_STYLE::SOLID ) );
2562
2564 }
2565 else
2566 {
2567 for( PCB_LAYER_ID klayer : GetKicadLayersToIterate( aElem.layer ) )
2568 ConvertArcs6ToBoardItemOnLayer( aElem, klayer );
2569 }
2570
2571 for( const auto& layerExpansionMask :
2572 HelperGetSolderAndPasteMaskExpansions( ALTIUM_RECORD::ARC, aPrimitiveIndex, aElem.layer ) )
2573 {
2574 int width = aElem.width + ( layerExpansionMask.second * 2 );
2575
2576 if( width > 1 )
2577 {
2578 std::unique_ptr<PCB_SHAPE> arc = std::make_unique<PCB_SHAPE>( m_board );
2579
2580 ConvertArcs6ToPcbShape( aElem, arc.get() );
2581 arc->SetStroke( STROKE_PARAMS( width, LINE_STYLE::SOLID ) );
2582 arc->SetLayer( layerExpansionMask.first );
2583
2584 m_board->Add( arc.release(), ADD_MODE::APPEND );
2585 }
2586 }
2587}
2588
2589
2591 const int aPrimitiveIndex, const bool aIsBoardImport )
2592{
2593 if( aElem.polygon != ALTIUM_POLYGON_NONE )
2594 {
2595 wxFAIL_MSG( wxString::Format( "Altium: Unexpected footprint Arc with polygon id %d",
2596 aElem.polygon ) );
2597 return;
2598 }
2599
2600 if( aElem.is_keepout || aElem.layer == ALTIUM_LAYER::KEEP_OUT_LAYER
2601 || IsAltiumLayerAPlane( aElem.layer ) )
2602 {
2603 // This is not the actual board item. We can use it to create the polygon for the region
2604 PCB_SHAPE shape( nullptr );
2605
2606 ConvertArcs6ToPcbShape( aElem, &shape );
2607 shape.SetStroke( STROKE_PARAMS( aElem.width, LINE_STYLE::SOLID ) );
2608
2609 HelperPcpShapeAsFootprintKeepoutRegion( aFootprint, shape, aElem.layer,
2610 aElem.keepoutrestrictions );
2611 }
2612 else
2613 {
2614 for( PCB_LAYER_ID klayer : GetKicadLayersToIterate( aElem.layer ) )
2615 {
2616 if( aIsBoardImport && IsCopperLayer( klayer ) && aElem.net != ALTIUM_NET_UNCONNECTED )
2617 {
2618 // Special case: do to not lose net connections in footprints
2619 ConvertArcs6ToBoardItemOnLayer( aElem, klayer );
2620 }
2621 else
2622 {
2623 ConvertArcs6ToFootprintItemOnLayer( aFootprint, aElem, klayer );
2624 }
2625 }
2626 }
2627
2628 for( const auto& layerExpansionMask :
2629 HelperGetSolderAndPasteMaskExpansions( ALTIUM_RECORD::ARC, aPrimitiveIndex, aElem.layer ) )
2630 {
2631 int width = aElem.width + ( layerExpansionMask.second * 2 );
2632
2633 if( width > 1 )
2634 {
2635 std::unique_ptr<PCB_SHAPE> arc = std::make_unique<PCB_SHAPE>( aFootprint );
2636
2637 ConvertArcs6ToPcbShape( aElem, arc.get() );
2638 arc->SetStroke( STROKE_PARAMS( width, LINE_STYLE::SOLID ) );
2639 arc->SetLayer( layerExpansionMask.first );
2640
2641 aFootprint->Add( arc.release(), ADD_MODE::APPEND );
2642 }
2643 }
2644}
2645
2646
2648{
2649 if( IsCopperLayer( aLayer ) && aElem.net != ALTIUM_NET_UNCONNECTED )
2650 {
2651 // TODO: This is not the actual board item. We use it for now to calculate the arc points. This could be improved!
2652 PCB_SHAPE shape( nullptr, SHAPE_T::ARC );
2653
2654 EDA_ANGLE includedAngle( aElem.endangle - aElem.startangle, DEGREES_T );
2655 EDA_ANGLE startAngle( aElem.endangle, DEGREES_T );
2656
2657 VECTOR2I startOffset = VECTOR2I( KiROUND( startAngle.Cos() * aElem.radius ),
2658 -KiROUND( startAngle.Sin() * aElem.radius ) );
2659
2660 shape.SetCenter( aElem.center );
2661 shape.SetStart( aElem.center + startOffset );
2662 shape.SetArcAngleAndEnd( includedAngle.Normalize(), true );
2663
2664 // Create actual arc
2665 SHAPE_ARC shapeArc( shape.GetCenter(), shape.GetStart(), shape.GetArcAngle(), aElem.width );
2666 std::unique_ptr<PCB_ARC> arc = std::make_unique<PCB_ARC>( m_board, &shapeArc );
2667
2668 arc->SetWidth( aElem.width );
2669 arc->SetLayer( aLayer );
2670 arc->SetNetCode( GetNetCode( aElem.net ) );
2671
2672 m_board->Add( arc.release(), ADD_MODE::APPEND );
2673 }
2674 else
2675 {
2676 std::unique_ptr<PCB_SHAPE> arc = std::make_unique<PCB_SHAPE>( m_board );
2677
2678 ConvertArcs6ToPcbShape( aElem, arc.get() );
2679 arc->SetStroke( STROKE_PARAMS( aElem.width, LINE_STYLE::SOLID ) );
2680 arc->SetLayer( aLayer );
2681
2682 m_board->Add( arc.release(), ADD_MODE::APPEND );
2683 }
2684}
2685
2686
2688 PCB_LAYER_ID aLayer )
2689{
2690 std::unique_ptr<PCB_SHAPE> arc = std::make_unique<PCB_SHAPE>( aFootprint );
2691
2692 ConvertArcs6ToPcbShape( aElem, arc.get() );
2693 arc->SetStroke( STROKE_PARAMS( aElem.width, LINE_STYLE::SOLID ) );
2694 arc->SetLayer( aLayer );
2695
2696 aFootprint->Add( arc.release(), ADD_MODE::APPEND );
2697}
2698
2699
2701 const CFB::COMPOUND_FILE_ENTRY* aEntry )
2702{
2703 if( m_progressReporter )
2704 m_progressReporter->Report( _( "Loading pads..." ) );
2705
2706 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
2707
2708 while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
2709 {
2710 checkpoint();
2711 APAD6 elem( reader );
2712
2713 if( elem.component == ALTIUM_COMPONENT_NONE )
2714 {
2716 }
2717 else
2718 {
2719 FOOTPRINT* footprint = HelperGetFootprint( elem.component );
2720 ConvertPads6ToFootprintItem( footprint, elem );
2721 }
2722 }
2723
2724 if( reader.GetRemainingBytes() != 0 )
2725 THROW_IO_ERROR( wxT( "Pads6 stream is not fully parsed" ) );
2726}
2727
2728
2730{
2731 // It is possible to place altium pads on non-copper layers -> we need to interpolate them using drawings!
2732 if( !IsAltiumLayerCopper( aElem.layer ) && !IsAltiumLayerAPlane( aElem.layer )
2733 && aElem.layer != ALTIUM_LAYER::MULTI_LAYER )
2734 {
2736 }
2737 else
2738 {
2739 // We cannot add a pad directly into the PCB
2740 std::unique_ptr<FOOTPRINT> footprint = std::make_unique<FOOTPRINT>( m_board );
2741 footprint->SetPosition( aElem.position );
2742
2743 ConvertPads6ToFootprintItemOnCopper( footprint.get(), aElem );
2744
2745 m_board->Add( footprint.release(), ADD_MODE::APPEND );
2746 }
2747}
2748
2749
2751{
2752 // It is possible to place altium pads on non-copper layers -> we need to interpolate them using drawings!
2753 if( !IsAltiumLayerCopper( aElem.layer ) && !IsAltiumLayerAPlane( aElem.layer )
2754 && aElem.layer != ALTIUM_LAYER::MULTI_LAYER )
2755 {
2756 ConvertPads6ToFootprintItemOnNonCopper( aFootprint, aElem );
2757 }
2758 else
2759 {
2760 ConvertPads6ToFootprintItemOnCopper( aFootprint, aElem );
2761 }
2762}
2763
2764
2766{
2767 std::unique_ptr<PAD> pad = std::make_unique<PAD>( aFootprint );
2768
2769 pad->SetKeepTopBottom( false ); // TODO: correct? This seems to be KiCad default on import
2770
2771 pad->SetNumber( aElem.name );
2772 pad->SetNetCode( GetNetCode( aElem.net ) );
2773
2774 pad->SetPosition( aElem.position );
2775 pad->SetOrientationDegrees( aElem.direction );
2776 pad->SetSize( aElem.topsize );
2777 pad->SetThermalSpokeAngle( ANGLE_90 );
2778
2779 if( aElem.holesize == 0 )
2780 {
2781 pad->SetAttribute( PAD_ATTRIB::SMD );
2782 }
2783 else
2784 {
2785 if( aElem.layer != ALTIUM_LAYER::MULTI_LAYER )
2786 {
2787 // TODO: I assume other values are possible as well?
2788 if( !m_footprintName.IsEmpty() )
2789 {
2790 wxLogError( _( "Error loading library '%s':\n"
2791 "Footprint %s pad %s is not marked as multilayer, but is a TH pad." ),
2792 m_library,
2794 aElem.name );
2795 }
2796 else
2797 {
2798 wxLogError( _( "Footprint %s pad %s is not marked as multilayer, but is a TH pad." ),
2799 aFootprint->GetReference(),
2800 aElem.name );
2801 }
2802 }
2803
2804 pad->SetAttribute( aElem.plated ? PAD_ATTRIB::PTH : PAD_ATTRIB::NPTH );
2805
2806 if( !aElem.sizeAndShape || aElem.sizeAndShape->holeshape == ALTIUM_PAD_HOLE_SHAPE::ROUND )
2807 {
2808 pad->SetDrillShape( PAD_DRILL_SHAPE_T::PAD_DRILL_SHAPE_CIRCLE );
2809 pad->SetDrillSize( VECTOR2I( aElem.holesize, aElem.holesize ) );
2810 }
2811 else
2812 {
2813 switch( aElem.sizeAndShape->holeshape )
2814 {
2815 case ALTIUM_PAD_HOLE_SHAPE::ROUND:
2816 wxFAIL_MSG( wxT( "Round holes are handled before the switch" ) );
2817 break;
2818
2819 case ALTIUM_PAD_HOLE_SHAPE::SQUARE:
2820 if( !m_footprintName.IsEmpty() )
2821 {
2822 wxLogWarning( _( "Loading library '%s':\n"
2823 "Footprint %s pad %s has a square hole (not yet supported)." ),
2824 m_library,
2826 aElem.name );
2827 }
2828 else
2829 {
2830 wxLogWarning( _( "Footprint %s pad %s has a square hole (not yet supported)." ),
2831 aFootprint->GetReference(),
2832 aElem.name );
2833 }
2834
2835 pad->SetDrillShape( PAD_DRILL_SHAPE_T::PAD_DRILL_SHAPE_CIRCLE );
2836 pad->SetDrillSize( VECTOR2I( aElem.holesize, aElem.holesize ) ); // Workaround
2837 // TODO: elem.sizeAndShape->slotsize was 0 in testfile. Either use holesize in
2838 // this case or rect holes have a different id
2839 break;
2840
2841 case ALTIUM_PAD_HOLE_SHAPE::SLOT:
2842 {
2843 pad->SetDrillShape( PAD_DRILL_SHAPE_T::PAD_DRILL_SHAPE_OBLONG );
2844 EDA_ANGLE slotRotation( aElem.sizeAndShape->slotrotation, DEGREES_T );
2845
2846 slotRotation.Normalize();
2847
2848 if( slotRotation.IsHorizontal() )
2849 {
2850 pad->SetDrillSize( VECTOR2I( aElem.sizeAndShape->slotsize, aElem.holesize ) );
2851 }
2852 else if( slotRotation.IsVertical() )
2853 {
2854 pad->SetDrillSize( VECTOR2I( aElem.holesize, aElem.sizeAndShape->slotsize ) );
2855 }
2856 else
2857 {
2858 if( !m_footprintName.IsEmpty() )
2859 {
2860 wxLogWarning( _( "Loading library '%s':\n"
2861 "Footprint %s pad %s has a hole-rotation of %f degrees. "
2862 "KiCad only supports 90 degree rotations." ),
2863 m_library,
2865 aElem.name,
2866 slotRotation.AsDegrees() );
2867 }
2868 else
2869 {
2870 wxLogWarning( _( "Footprint %s pad %s has a hole-rotation of %f degrees. "
2871 "KiCad only supports 90 degree rotations." ),
2872 aFootprint->GetReference(),
2873 aElem.name,
2874 slotRotation.AsDegrees() );
2875 }
2876 }
2877
2878 break;
2879 }
2880
2881 default:
2882 case ALTIUM_PAD_HOLE_SHAPE::UNKNOWN:
2883 if( !m_footprintName.IsEmpty() )
2884 {
2885 wxLogError( _( "Error loading library '%s':\n"
2886 "Footprint %s pad %s uses a hole of unknown kind %d." ),
2887 m_library,
2889 aElem.name,
2890 aElem.sizeAndShape->holeshape );
2891 }
2892 else
2893 {
2894 wxLogError( _( "Footprint %s pad %s uses a hole of unknown kind %d." ),
2895 aFootprint->GetReference(),
2896 aElem.name,
2897 aElem.sizeAndShape->holeshape );
2898 }
2899
2900 pad->SetDrillShape( PAD_DRILL_SHAPE_T::PAD_DRILL_SHAPE_CIRCLE );
2901 pad->SetDrillSize( VECTOR2I( aElem.holesize, aElem.holesize ) ); // Workaround
2902 break;
2903 }
2904 }
2905
2906 if( aElem.sizeAndShape )
2907 pad->SetOffset( aElem.sizeAndShape->holeoffset[0] );
2908 }
2909
2910 if( aElem.padmode != ALTIUM_PAD_MODE::SIMPLE )
2911 {
2912 if( !m_footprintName.IsEmpty() )
2913 {
2914 wxLogError( _( "Error loading library '%s':\n"
2915 "Footprint %s pad %s uses a complex pad stack (not yet supported)." ),
2916 m_library,
2918 aElem.name );
2919 }
2920 else
2921 {
2922 wxLogError( _( "Footprint %s pad %s uses a complex pad stack (not yet supported)." ),
2923 aFootprint->GetReference(),
2924 aElem.name );
2925 }
2926 }
2927
2928 switch( aElem.topshape )
2929 {
2930 case ALTIUM_PAD_SHAPE::RECT:
2931 pad->SetShape( PAD_SHAPE::RECTANGLE );
2932 break;
2933
2934 case ALTIUM_PAD_SHAPE::CIRCLE:
2935 if( aElem.sizeAndShape
2936 && aElem.sizeAndShape->alt_shape[0] == ALTIUM_PAD_SHAPE_ALT::ROUNDRECT )
2937 {
2938 pad->SetShape( PAD_SHAPE::ROUNDRECT ); // 100 = round, 0 = rectangular
2939 double ratio = aElem.sizeAndShape->cornerradius[0] / 200.;
2940 pad->SetRoundRectRadiusRatio( ratio );
2941 }
2942 else if( aElem.topsize.x == aElem.topsize.y )
2943 {
2944 pad->SetShape( PAD_SHAPE::CIRCLE );
2945 }
2946 else
2947 {
2948 pad->SetShape( PAD_SHAPE::OVAL );
2949 }
2950
2951 break;
2952
2953 case ALTIUM_PAD_SHAPE::OCTAGONAL:
2954 pad->SetShape( PAD_SHAPE::CHAMFERED_RECT );
2955 pad->SetChamferPositions( RECT_CHAMFER_ALL );
2956 pad->SetChamferRectRatio( 0.25 );
2957 break;
2958
2959 case ALTIUM_PAD_SHAPE::UNKNOWN:
2960 default:
2961 if( !m_footprintName.IsEmpty() )
2962 {
2963 wxLogError( _( "Error loading library '%s':\n"
2964 "Footprint %s pad %s uses an unknown pad-shape." ),
2965 m_library,
2967 aElem.name );
2968 }
2969 else
2970 {
2971 wxLogError( _( "Footprint %s pad %s uses an unknown pad-shape." ),
2972 aFootprint->GetReference(),
2973 aElem.name );
2974 }
2975 break;
2976 }
2977
2978 if( pad->GetAttribute() == PAD_ATTRIB::NPTH && pad->HasHole() )
2979 {
2980 // KiCad likes NPTH pads to be the same size & shape as their holes
2981 pad->SetShape( pad->GetDrillShape() == PAD_DRILL_SHAPE_CIRCLE ? PAD_SHAPE::CIRCLE
2982 : PAD_SHAPE::OVAL );
2983 pad->SetSize( pad->GetDrillSize() );
2984 }
2985
2986 switch( aElem.layer )
2987 {
2988 case ALTIUM_LAYER::TOP_LAYER:
2989 pad->SetLayer( F_Cu );
2990 pad->SetLayerSet( PAD::SMDMask() );
2991 break;
2992
2993 case ALTIUM_LAYER::BOTTOM_LAYER:
2994 pad->SetLayer( B_Cu );
2995 pad->SetLayerSet( FlipLayerMask( PAD::SMDMask() ) );
2996 break;
2997
2998 case ALTIUM_LAYER::MULTI_LAYER:
2999 pad->SetLayerSet( aElem.plated ? PAD::PTHMask() : PAD::UnplatedHoleMask() );
3000 break;
3001
3002 default:
3003 PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
3004 pad->SetLayer( klayer );
3005 pad->SetLayerSet( LSET( 1, klayer ) );
3006 break;
3007 }
3008
3009 if( aElem.pastemaskexpansionmode == ALTIUM_MODE::MANUAL )
3010 pad->SetLocalSolderPasteMargin( aElem.pastemaskexpansionmanual );
3011
3012 if( aElem.soldermaskexpansionmode == ALTIUM_MODE::MANUAL )
3013 pad->SetLocalSolderMaskMargin( aElem.soldermaskexpansionmanual );
3014
3015 if( aElem.is_tent_top )
3016 pad->SetLayerSet( pad->GetLayerSet().reset( F_Mask ) );
3017
3018 if( aElem.is_tent_bottom )
3019 pad->SetLayerSet( pad->GetLayerSet().reset( B_Mask ) );
3020
3021 aFootprint->Add( pad.release(), ADD_MODE::APPEND );
3022}
3023
3024
3026{
3027 PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
3028
3029 if( klayer == UNDEFINED_LAYER )
3030 {
3031 wxLogWarning( _( "Non-copper pad %s found on an Altium layer (%d) with no KiCad "
3032 "equivalent. It has been moved to KiCad layer Eco1_User." ),
3033 aElem.name, aElem.layer );
3034 klayer = Eco1_User;
3035 }
3036
3037 std::unique_ptr<PCB_SHAPE> pad = std::make_unique<PCB_SHAPE>( m_board );
3038
3039 HelperParsePad6NonCopper( aElem, klayer, pad.get() );
3040
3041 m_board->Add( pad.release(), ADD_MODE::APPEND );
3042}
3043
3044
3046{
3047 PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
3048
3049 if( klayer == UNDEFINED_LAYER )
3050 {
3051 if( !m_footprintName.IsEmpty() )
3052 {
3053 wxLogWarning( _( "Loading library '%s':\n"
3054 "Footprint %s non-copper pad %s found on an Altium layer (%d) with no "
3055 "KiCad equivalent. It has been moved to KiCad layer Eco1_User." ),
3056 m_library,
3058 aElem.name,
3059 aElem.layer );
3060 }
3061 else
3062 {
3063 wxLogWarning( _( "Footprint %s non-copper pad %s found on an Altium layer (%d) with no "
3064 "KiCad equivalent. It has been moved to KiCad layer Eco1_User." ),
3065 aFootprint->GetReference(),
3066 aElem.name,
3067 aElem.layer );
3068 }
3069
3070 klayer = Eco1_User;
3071 }
3072
3073 std::unique_ptr<PCB_SHAPE> pad = std::make_unique<PCB_SHAPE>( aFootprint );
3074
3075 HelperParsePad6NonCopper( aElem, klayer, pad.get() );
3076
3077 aFootprint->Add( pad.release(), ADD_MODE::APPEND );
3078}
3079
3080
3082 PCB_SHAPE* aShape )
3083{
3084 if( aElem.net != ALTIUM_NET_UNCONNECTED )
3085 {
3086 wxLogError( _( "Non-copper pad %s is connected to a net, which is not supported." ),
3087 aElem.name );
3088 }
3089
3090 if( aElem.holesize != 0 )
3091 {
3092 wxLogError( _( "Non-copper pad %s has a hole, which is not supported." ), aElem.name );
3093 }
3094
3095 if( aElem.padmode != ALTIUM_PAD_MODE::SIMPLE )
3096 {
3097 wxLogWarning( _( "Non-copper pad %s has a complex pad stack (not yet supported)." ),
3098 aElem.name );
3099 }
3100
3101 switch( aElem.topshape )
3102 {
3103 case ALTIUM_PAD_SHAPE::RECT:
3104 {
3105 // filled rect
3106 aShape->SetShape( SHAPE_T::POLY );
3107 aShape->SetFilled( true );
3108 aShape->SetLayer( aLayer );
3109 aShape->SetStroke( STROKE_PARAMS( 0 ) );
3110
3111 aShape->SetPolyPoints(
3112 { aElem.position + VECTOR2I( aElem.topsize.x / 2, aElem.topsize.y / 2 ),
3113 aElem.position + VECTOR2I( aElem.topsize.x / 2, -aElem.topsize.y / 2 ),
3114 aElem.position + VECTOR2I( -aElem.topsize.x / 2, -aElem.topsize.y / 2 ),
3115 aElem.position + VECTOR2I( -aElem.topsize.x / 2, aElem.topsize.y / 2 ) } );
3116
3117 if( aElem.direction != 0 )
3118 aShape->Rotate( aElem.position, EDA_ANGLE( aElem.direction, DEGREES_T ) );
3119 }
3120 break;
3121
3122 case ALTIUM_PAD_SHAPE::CIRCLE:
3123 if( aElem.sizeAndShape
3124 && aElem.sizeAndShape->alt_shape[0] == ALTIUM_PAD_SHAPE_ALT::ROUNDRECT )
3125 {
3126 // filled roundrect
3127 int cornerradius = aElem.sizeAndShape->cornerradius[0];
3128 int offset = ( std::min( aElem.topsize.x, aElem.topsize.y ) * cornerradius ) / 200;
3129
3130 aShape->SetLayer( aLayer );
3131 aShape->SetStroke( STROKE_PARAMS( offset * 2, LINE_STYLE::SOLID ) );
3132
3133 if( cornerradius < 100 )
3134 {
3135 int offsetX = aElem.topsize.x / 2 - offset;
3136 int offsetY = aElem.topsize.y / 2 - offset;
3137
3138 VECTOR2I p11 = aElem.position + VECTOR2I( offsetX, offsetY );
3139 VECTOR2I p12 = aElem.position + VECTOR2I( offsetX, -offsetY );
3140 VECTOR2I p22 = aElem.position + VECTOR2I( -offsetX, -offsetY );
3141 VECTOR2I p21 = aElem.position + VECTOR2I( -offsetX, offsetY );
3142
3143 aShape->SetShape( SHAPE_T::POLY );
3144 aShape->SetFilled( true );
3145 aShape->SetPolyPoints( { p11, p12, p22, p21 } );
3146 }
3147 else if( aElem.topsize.x == aElem.topsize.y )
3148 {
3149 // circle
3150 aShape->SetShape( SHAPE_T::CIRCLE );
3151 aShape->SetFilled( true );
3152 aShape->SetStart( aElem.position );
3153 aShape->SetEnd( aElem.position - VECTOR2I( 0, aElem.topsize.x / 4 ) );
3154 aShape->SetStroke( STROKE_PARAMS( aElem.topsize.x / 2, LINE_STYLE::SOLID ) );
3155 }
3156 else if( aElem.topsize.x < aElem.topsize.y )
3157 {
3158 // short vertical line
3159 aShape->SetShape( SHAPE_T::SEGMENT );
3160 VECTOR2I pointOffset( 0, ( aElem.topsize.y - aElem.topsize.x ) / 2 );
3161 aShape->SetStart( aElem.position + pointOffset );
3162 aShape->SetEnd( aElem.position - pointOffset );
3163 }
3164 else
3165 {
3166 // short horizontal line
3167 aShape->SetShape( SHAPE_T::SEGMENT );
3168 VECTOR2I pointOffset( ( aElem.topsize.x - aElem.topsize.y ) / 2, 0 );
3169 aShape->SetStart( aElem.position + pointOffset );
3170 aShape->SetEnd( aElem.position - pointOffset );
3171 }
3172
3173 if( aElem.direction != 0 )
3174 aShape->Rotate( aElem.position, EDA_ANGLE( aElem.direction, DEGREES_T ) );
3175 }
3176 else if( aElem.topsize.x == aElem.topsize.y )
3177 {
3178 // filled circle
3179 aShape->SetShape( SHAPE_T::CIRCLE );
3180 aShape->SetFilled( true );
3181 aShape->SetLayer( aLayer );
3182 aShape->SetStart( aElem.position );
3183 aShape->SetEnd( aElem.position - VECTOR2I( 0, aElem.topsize.x / 4 ) );
3184 aShape->SetStroke( STROKE_PARAMS( aElem.topsize.x / 2, LINE_STYLE::SOLID ) );
3185 }
3186 else
3187 {
3188 // short line
3189 aShape->SetShape( SHAPE_T::SEGMENT );
3190 aShape->SetLayer( aLayer );
3191 aShape->SetStroke( STROKE_PARAMS( std::min( aElem.topsize.x, aElem.topsize.y ),
3192 LINE_STYLE::SOLID ) );
3193
3194 if( aElem.topsize.x < aElem.topsize.y )
3195 {
3196 VECTOR2I offset( 0, ( aElem.topsize.y - aElem.topsize.x ) / 2 );
3197 aShape->SetStart( aElem.position + offset );
3198 aShape->SetEnd( aElem.position - offset );
3199 }
3200 else
3201 {
3202 VECTOR2I offset( ( aElem.topsize.x - aElem.topsize.y ) / 2, 0 );
3203 aShape->SetStart( aElem.position + offset );
3204 aShape->SetEnd( aElem.position - offset );
3205 }
3206
3207 if( aElem.direction != 0 )
3208 aShape->Rotate( aElem.position, EDA_ANGLE( aElem.direction, DEGREES_T ) );
3209 }
3210 break;
3211
3212 case ALTIUM_PAD_SHAPE::OCTAGONAL:
3213 {
3214 // filled octagon
3215 aShape->SetShape( SHAPE_T::POLY );
3216 aShape->SetFilled( true );
3217 aShape->SetLayer( aLayer );
3218 aShape->SetStroke( STROKE_PARAMS( 0 ) );
3219
3220 VECTOR2I p11 = aElem.position + VECTOR2I( aElem.topsize.x / 2, aElem.topsize.y / 2 );
3221 VECTOR2I p12 = aElem.position + VECTOR2I( aElem.topsize.x / 2, -aElem.topsize.y / 2 );
3222 VECTOR2I p22 = aElem.position + VECTOR2I( -aElem.topsize.x / 2, -aElem.topsize.y / 2 );
3223 VECTOR2I p21 = aElem.position + VECTOR2I( -aElem.topsize.x / 2, aElem.topsize.y / 2 );
3224
3225 int chamfer = std::min( aElem.topsize.x, aElem.topsize.y ) / 4;
3226 VECTOR2I chamferX( chamfer, 0 );
3227 VECTOR2I chamferY( 0, chamfer );
3228
3229 aShape->SetPolyPoints( { p11 - chamferX, p11 - chamferY, p12 + chamferY, p12 - chamferX,
3230 p22 + chamferX, p22 + chamferY, p21 - chamferY, p21 + chamferX } );
3231
3232 if( aElem.direction != 0. )
3233 aShape->Rotate( aElem.position, EDA_ANGLE( aElem.direction, DEGREES_T ) );
3234 }
3235 break;
3236
3237 case ALTIUM_PAD_SHAPE::UNKNOWN:
3238 default:
3239 wxLogError( _( "Non-copper pad %s uses an unknown pad-shape." ), aElem.name );
3240 break;
3241 }
3242}
3243
3244
3246 const CFB::COMPOUND_FILE_ENTRY* aEntry )
3247{
3248 if( m_progressReporter )
3249 m_progressReporter->Report( _( "Loading vias..." ) );
3250
3251 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
3252
3253 while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
3254 {
3255 checkpoint();
3256 AVIA6 elem( reader );
3257
3258 std::unique_ptr<PCB_VIA> via = std::make_unique<PCB_VIA>( m_board );
3259
3260 via->SetPosition( elem.position );
3261 via->SetWidth( elem.diameter );
3262 via->SetDrill( elem.holesize );
3263 via->SetNetCode( GetNetCode( elem.net ) );
3264 via->SetLocked( elem.is_locked );
3265
3266 bool start_layer_outside = elem.layer_start == ALTIUM_LAYER::TOP_LAYER
3267 || elem.layer_start == ALTIUM_LAYER::BOTTOM_LAYER;
3268 bool end_layer_outside = elem.layer_end == ALTIUM_LAYER::TOP_LAYER
3269 || elem.layer_end == ALTIUM_LAYER::BOTTOM_LAYER;
3270
3271 if( start_layer_outside && end_layer_outside )
3272 {
3273 via->SetViaType( VIATYPE::THROUGH );
3274 }
3275 else if( ( !start_layer_outside ) && ( !end_layer_outside ) )
3276 {
3277 via->SetViaType( VIATYPE::BLIND_BURIED );
3278 }
3279 else
3280 {
3281 via->SetViaType( VIATYPE::MICROVIA ); // TODO: always a microvia?
3282 }
3283
3284 PCB_LAYER_ID start_klayer = GetKicadLayer( elem.layer_start );
3285 PCB_LAYER_ID end_klayer = GetKicadLayer( elem.layer_end );
3286
3287 if( !IsCopperLayer( start_klayer ) || !IsCopperLayer( end_klayer ) )
3288 {
3289 wxLogError( _( "Via from layer %d to %d uses a non-copper layer, which is not "
3290 "supported." ),
3291 elem.layer_start,
3292 elem.layer_end );
3293 continue; // just assume through-hole instead.
3294 }
3295
3296 // we need VIATYPE set!
3297 via->SetLayerPair( start_klayer, end_klayer );
3298
3299 m_board->Add( via.release(), ADD_MODE::APPEND );
3300 }
3301
3302 if( reader.GetRemainingBytes() != 0 )
3303 THROW_IO_ERROR( wxT( "Vias6 stream is not fully parsed" ) );
3304}
3305
3307 const CFB::COMPOUND_FILE_ENTRY* aEntry )
3308{
3309 if( m_progressReporter )
3310 m_progressReporter->Report( _( "Loading tracks..." ) );
3311
3312 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
3313
3314 for( int primitiveIndex = 0; reader.GetRemainingBytes() >= 4; primitiveIndex++ )
3315 {
3316 checkpoint();
3317 ATRACK6 elem( reader );
3318
3319 if( elem.component == ALTIUM_COMPONENT_NONE )
3320 {
3321 ConvertTracks6ToBoardItem( elem, primitiveIndex );
3322 }
3323 else
3324 {
3325 FOOTPRINT* footprint = HelperGetFootprint( elem.component );
3326 ConvertTracks6ToFootprintItem( footprint, elem, primitiveIndex, true );
3327 }
3328 }
3329
3330 if( reader.GetRemainingBytes() != 0 )
3331 THROW_IO_ERROR( "Tracks6 stream is not fully parsed" );
3332}
3333
3334
3335void ALTIUM_PCB::ConvertTracks6ToBoardItem( const ATRACK6& aElem, const int aPrimitiveIndex )
3336{
3337 if( aElem.polygon != ALTIUM_POLYGON_NONE && aElem.polygon != ALTIUM_POLYGON_BOARD )
3338 {
3339 if( m_polygons.size() <= aElem.polygon )
3340 {
3341 // Can happen when reading old Altium files: just skip this item
3342 wxLogError( "ATRACK6 stream tries to access polygon id %u "
3343 "of %u existing polygons. Skip it",
3344 (unsigned) aElem.polygon,
3345 (unsigned)m_polygons.size() );
3346 return;
3347 }
3348
3349 ZONE* zone = m_polygons.at( aElem.polygon );
3350
3351 if( zone == nullptr )
3352 {
3353 return; // we know the zone id, but because we do not know the layer we did not
3354 // add it!
3355 }
3356
3357 PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
3358
3359 if( klayer == UNDEFINED_LAYER )
3360 return; // Just skip it for now. Users can fill it themselves.
3361
3362 SHAPE_POLY_SET* fill = zone->GetFill( klayer );
3363
3364 PCB_SHAPE shape( nullptr, SHAPE_T::SEGMENT );
3365 shape.SetStart( aElem.start );
3366 shape.SetEnd( aElem.end );
3367 shape.SetStroke( STROKE_PARAMS( aElem.width, LINE_STYLE::SOLID ) );
3368
3369 shape.EDA_SHAPE::TransformShapeToPolygon( *fill, 0, ARC_HIGH_DEF, ERROR_INSIDE );
3370 // Will be simplified and fractured later
3371
3372 zone->SetIsFilled( true );
3373 zone->SetNeedRefill( false );
3374
3375 return;
3376 }
3377
3378 if( aElem.is_keepout || aElem.layer == ALTIUM_LAYER::KEEP_OUT_LAYER
3379 || IsAltiumLayerAPlane( aElem.layer ) )
3380 {
3381 // This is not the actual board item. We can use it to create the polygon for the region
3382 PCB_SHAPE shape( nullptr, SHAPE_T::SEGMENT );
3383 shape.SetStart( aElem.start );
3384 shape.SetEnd( aElem.end );
3385 shape.SetStroke( STROKE_PARAMS( aElem.width, LINE_STYLE::SOLID ) );
3386
3388 }
3389 else
3390 {
3391 for( PCB_LAYER_ID klayer : GetKicadLayersToIterate( aElem.layer ) )
3392 ConvertTracks6ToBoardItemOnLayer( aElem, klayer );
3393 }
3394
3395 for( const auto& layerExpansionMask : HelperGetSolderAndPasteMaskExpansions(
3396 ALTIUM_RECORD::TRACK, aPrimitiveIndex, aElem.layer ) )
3397 {
3398 int width = aElem.width + ( layerExpansionMask.second * 2 );
3399 if( width > 1 )
3400 {
3401 std::unique_ptr<PCB_SHAPE> seg = std::make_unique<PCB_SHAPE>( m_board, SHAPE_T::SEGMENT );
3402
3403 seg->SetStart( aElem.start );
3404 seg->SetEnd( aElem.end );
3405 seg->SetStroke( STROKE_PARAMS( width, LINE_STYLE::SOLID ) );
3406 seg->SetLayer( layerExpansionMask.first );
3407
3408 m_board->Add( seg.release(), ADD_MODE::APPEND );
3409 }
3410 }
3411}
3412
3413
3415 const int aPrimitiveIndex,
3416 const bool aIsBoardImport )
3417{
3418 if( aElem.polygon != ALTIUM_POLYGON_NONE )
3419 {
3420 wxFAIL_MSG( wxString::Format( "Altium: Unexpected footprint Track with polygon id %u",
3421 (unsigned)aElem.polygon ) );
3422 return;
3423 }
3424
3425 if( aElem.is_keepout || aElem.layer == ALTIUM_LAYER::KEEP_OUT_LAYER
3426 || IsAltiumLayerAPlane( aElem.layer ) )
3427 {
3428 // This is not the actual board item. We can use it to create the polygon for the region
3429 PCB_SHAPE shape( nullptr, SHAPE_T::SEGMENT );
3430 shape.SetStart( aElem.start );
3431 shape.SetEnd( aElem.end );
3432 shape.SetStroke( STROKE_PARAMS( aElem.width, LINE_STYLE::SOLID ) );
3433
3434 HelperPcpShapeAsFootprintKeepoutRegion( aFootprint, shape, aElem.layer,
3435 aElem.keepoutrestrictions );
3436 }
3437 else
3438 {
3439 for( PCB_LAYER_ID klayer : GetKicadLayersToIterate( aElem.layer ) )
3440 {
3441 if( aIsBoardImport && IsCopperLayer( klayer ) && aElem.net != ALTIUM_NET_UNCONNECTED )
3442 {
3443 // Special case: do to not lose net connections in footprints
3444 ConvertTracks6ToBoardItemOnLayer( aElem, klayer );
3445 }
3446 else
3447 {
3448 ConvertTracks6ToFootprintItemOnLayer( aFootprint, aElem, klayer );
3449 }
3450 }
3451 }
3452
3453 for( const auto& layerExpansionMask : HelperGetSolderAndPasteMaskExpansions(
3454 ALTIUM_RECORD::TRACK, aPrimitiveIndex, aElem.layer ) )
3455 {
3456 int width = aElem.width + ( layerExpansionMask.second * 2 );
3457 if( width > 1 )
3458 {
3459 std::unique_ptr<PCB_SHAPE> seg = std::make_unique<PCB_SHAPE>( aFootprint, SHAPE_T::SEGMENT );
3460
3461 seg->SetStart( aElem.start );
3462 seg->SetEnd( aElem.end );
3463 seg->SetStroke( STROKE_PARAMS( width, LINE_STYLE::SOLID ) );
3464 seg->SetLayer( layerExpansionMask.first );
3465
3466 aFootprint->Add( seg.release(), ADD_MODE::APPEND );
3467 }
3468 }
3469}
3470
3471
3473{
3474 if( IsCopperLayer( aLayer ) && aElem.net != ALTIUM_NET_UNCONNECTED )
3475 {
3476 std::unique_ptr<PCB_TRACK> track = std::make_unique<PCB_TRACK>( m_board );
3477
3478 track->SetStart( aElem.start );
3479 track->SetEnd( aElem.end );
3480 track->SetWidth( aElem.width );
3481 track->SetLayer( aLayer );
3482 track->SetNetCode( GetNetCode( aElem.net ) );
3483
3484 m_board->Add( track.release(), ADD_MODE::APPEND );
3485 }
3486 else
3487 {
3488 std::unique_ptr<PCB_SHAPE> seg = std::make_unique<PCB_SHAPE>( m_board, SHAPE_T::SEGMENT );
3489
3490 seg->SetStart( aElem.start );
3491 seg->SetEnd( aElem.end );
3492 seg->SetStroke( STROKE_PARAMS( aElem.width, LINE_STYLE::SOLID ) );
3493 seg->SetLayer( aLayer );
3494
3495 m_board->Add( seg.release(), ADD_MODE::APPEND );
3496 }
3497}
3498
3499
3501 PCB_LAYER_ID aLayer )
3502{
3503 std::unique_ptr<PCB_SHAPE> seg = std::make_unique<PCB_SHAPE>( aFootprint, SHAPE_T::SEGMENT );
3504
3505 seg->SetStart( aElem.start );
3506 seg->SetEnd( aElem.end );
3507 seg->SetStroke( STROKE_PARAMS( aElem.width, LINE_STYLE::SOLID ) );
3508 seg->SetLayer( aLayer );
3509
3510 aFootprint->Add( seg.release(), ADD_MODE::APPEND );
3511}
3512
3513
3515 const CFB::COMPOUND_FILE_ENTRY* aEntry )
3516{
3517 if( m_progressReporter )
3518 m_progressReporter->Report( _( "Loading unicode strings..." ) );
3519
3520 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
3521
3523
3524 if( reader.GetRemainingBytes() != 0 )
3525 THROW_IO_ERROR( wxT( "WideStrings6 stream is not fully parsed" ) );
3526}
3527
3529 const CFB::COMPOUND_FILE_ENTRY* aEntry )
3530{
3531 if( m_progressReporter )
3532 m_progressReporter->Report( _( "Loading text..." ) );
3533
3534 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
3535
3536 while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
3537 {
3538 checkpoint();
3539 ATEXT6 elem( reader, m_unicodeStrings );
3540
3541 if( elem.component == ALTIUM_COMPONENT_NONE )
3542 {
3544 }
3545 else
3546 {
3547 FOOTPRINT* footprint = HelperGetFootprint( elem.component );
3548 ConvertTexts6ToFootprintItem( footprint, elem );
3549 }
3550 }
3551
3552 if( reader.GetRemainingBytes() != 0 )
3553 THROW_IO_ERROR( wxT( "Texts6 stream is not fully parsed" ) );
3554}
3555
3556
3558{
3559 if( aElem.fonttype == ALTIUM_TEXT_TYPE::BARCODE )
3560 {
3561 wxLogError( _( "Ignored barcode on Altium layer %d (not yet supported)." ), aElem.layer );
3562 return;
3563 }
3564
3565 for( PCB_LAYER_ID klayer : GetKicadLayersToIterate( aElem.layer ) )
3566 ConvertTexts6ToBoardItemOnLayer( aElem, klayer );
3567}
3568
3569
3571{
3572 if( aElem.fonttype == ALTIUM_TEXT_TYPE::BARCODE )
3573 {
3574 if( !m_footprintName.IsEmpty() )
3575 {
3576 wxLogError( _( "Error loading library '%s':\n"
3577 "Footprint %s contains barcode on Altium layer %d (not yet supported)." ),
3578 m_library,
3580 aElem.layer );
3581 }
3582 else
3583 {
3584 wxLogError( _( "Footprint %s contains barcode on Altium layer %d (not yet supported)." ),
3585 aFootprint->GetReference(),
3586 aElem.layer );
3587 }
3588
3589 return;
3590 }
3591
3592 for( PCB_LAYER_ID klayer : GetKicadLayersToIterate( aElem.layer ) )
3593 ConvertTexts6ToFootprintItemOnLayer( aFootprint, aElem, klayer );
3594}
3595
3596
3598{
3599 std::unique_ptr<PCB_TEXT> pcbText = std::make_unique<PCB_TEXT>( m_board );
3600
3601 static const std::map<wxString, wxString> variableMap = {
3602 { "LAYER_NAME", "LAYER" },
3603 { "PRINT_DATE", "CURRENT_DATE"},
3604 };
3605
3606 wxString kicadText = AltiumPcbSpecialStringsToKiCadStrings( aElem.text, variableMap );
3607
3608 pcbText->SetText(kicadText);
3609 pcbText->SetLayer( aLayer );
3610 pcbText->SetPosition( aElem.position );
3611 pcbText->SetTextAngle( EDA_ANGLE( aElem.rotation, DEGREES_T ) );
3612
3613 ConvertTexts6ToEdaTextSettings( aElem, *pcbText );
3614
3615 m_board->Add( pcbText.release(), ADD_MODE::APPEND );
3616}
3617
3618
3620 PCB_LAYER_ID aLayer )
3621{
3622 PCB_TEXT* fpText;
3623
3624 if( aElem.isDesignator )
3625 {
3626 fpText = &aFootprint->Reference(); // TODO: handle multiple layers
3627 }
3628 else if( aElem.isComment )
3629 {
3630 fpText = &aFootprint->Value(); // TODO: handle multiple layers
3631 }
3632 else
3633 {
3634 fpText = new PCB_TEXT( aFootprint );
3635 aFootprint->Add( fpText, ADD_MODE::APPEND );
3636 }
3637
3638 static const std::map<wxString, wxString> variableMap = {
3639 { "DESIGNATOR", "REFERENCE" },
3640 { "COMMENT", "VALUE" },
3641 { "VALUE", "ALTIUM_VALUE" },
3642 { "LAYER_NAME", "LAYER" },
3643 { "PRINT_DATE", "CURRENT_DATE"},
3644 };
3645
3646 wxString kicadText = AltiumPcbSpecialStringsToKiCadStrings( aElem.text, variableMap );
3647
3648 fpText->SetText(kicadText);
3649 fpText->SetKeepUpright( false );
3650 fpText->SetLayer( aLayer );
3651 fpText->SetPosition( aElem.position );
3652 fpText->SetTextAngle( EDA_ANGLE( aElem.rotation, DEGREES_T ) );
3653
3654 ConvertTexts6ToEdaTextSettings( aElem, *fpText );
3655}
3656
3657
3659{
3660 aEdaText.SetTextSize( VECTOR2I( aElem.height, aElem.height ) ); // TODO: parse text width
3661
3662 if( aElem.fonttype == ALTIUM_TEXT_TYPE::TRUETYPE )
3663 {
3664 KIFONT::FONT* font = KIFONT::FONT::GetFont( aElem.fontname, aElem.isBold, aElem.isItalic );
3665 aEdaText.SetFont( font );
3666
3667 if( font->IsOutline() )
3668 {
3669 // TODO: why is this required? Somehow, truetype size is calculated differently
3670 if( font->GetName().Contains( wxS( "Arial" ) ) )
3671 aEdaText.SetTextSize( VECTOR2I( aElem.height * 0.63, aElem.height * 0.63 ) );
3672 else
3673 aEdaText.SetTextSize( VECTOR2I( aElem.height * 0.5, aElem.height * 0.5 ) );
3674 }
3675 }
3676
3677 aEdaText.SetTextThickness( aElem.strokewidth );
3678 aEdaText.SetBoldFlag( aElem.isBold );
3679 aEdaText.SetItalic( aElem.isItalic );
3680 aEdaText.SetMirrored( aElem.isMirrored );
3681
3682 // Altium position always specifies the bottom left corner
3685
3686 // TODO: correct the position and set proper justification
3687}
3688
3689
3691 const CFB::COMPOUND_FILE_ENTRY* aEntry )
3692{
3693 if( m_progressReporter )
3694 m_progressReporter->Report( _( "Loading rectangles..." ) );
3695
3696 ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
3697
3698 while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
3699 {
3700 checkpoint();
3701 AFILL6 elem( reader );
3702
3703 if( elem.component == ALTIUM_COMPONENT_NONE )
3704 {
3706 }
3707 else
3708 {
3709 FOOTPRINT* footprint = HelperGetFootprint( elem.component );
3710 ConvertFills6ToFootprintItem( footprint, elem, true );
3711 }
3712 }
3713
3714 if( reader.GetRemainingBytes() != 0 )
3715 THROW_IO_ERROR( "Fills6 stream is not fully parsed" );
3716}
3717
3718
3720{
3721 if( aElem.is_keepout || aElem.layer == ALTIUM_LAYER::KEEP_OUT_LAYER )
3722 {
3723 // This is not the actual board item. We can use it to create the polygon for the region
3724 PCB_SHAPE shape( nullptr, SHAPE_T::RECTANGLE );
3725
3726 shape.SetStart( aElem.pos1 );
3727 shape.SetEnd( aElem.pos2 );
3728 shape.SetFilled( true );
3729 shape.SetStroke( STROKE_PARAMS( 0, LINE_STYLE::SOLID ) );
3730
3731 if( aElem.rotation != 0. )
3732 {
3733 VECTOR2I center( ( aElem.pos1.x + aElem.pos2.x ) / 2,
3734 ( aElem.pos1.y + aElem.pos2.y ) / 2 );
3735 shape.Rotate( center, EDA_ANGLE( aElem.rotation, DEGREES_T ) );
3736 }
3737
3739 }
3740 else
3741 {
3742 for( PCB_LAYER_ID klayer : GetKicadLayersToIterate( aElem.layer ) )
3743 ConvertFills6ToBoardItemOnLayer( aElem, klayer );
3744 }
3745}
3746
3747
3749 const bool aIsBoardImport )
3750{
3751 if( aElem.is_keepout
3752 || aElem.layer == ALTIUM_LAYER::KEEP_OUT_LAYER ) // TODO: what about plane layers?
3753 {
3754 // This is not the actual board item. We can use it to create the polygon for the region
3755 PCB_SHAPE shape( nullptr, SHAPE_T::RECTANGLE );
3756
3757 shape.SetStart( aElem.pos1 );
3758 shape.SetEnd( aElem.pos2 );
3759 shape.SetFilled( true );
3760 shape.SetStroke( STROKE_PARAMS( 0, LINE_STYLE::SOLID ) );
3761
3762 if( aElem.rotation != 0. )
3763 {
3764 VECTOR2I center( ( aElem.pos1.x + aElem.pos2.x ) / 2,
3765 ( aElem.pos1.y + aElem.pos2.y ) / 2 );
3766 shape.Rotate( center, EDA_ANGLE( aElem.rotation, DEGREES_T ) );
3767 }
3768
3769 HelperPcpShapeAsFootprintKeepoutRegion( aFootprint, shape, aElem.layer,
3770 aElem.keepoutrestrictions );
3771 }
3772 else if( aIsBoardImport && IsAltiumLayerCopper( aElem.layer )
3773 && aElem.net != ALTIUM_NET_UNCONNECTED )
3774 {
3775 // Special case: do to not lose net connections in footprints
3776 for( PCB_LAYER_ID klayer : GetKicadLayersToIterate( aElem.layer ) )
3777 ConvertFills6ToBoardItemOnLayer( aElem, klayer );
3778 }
3779 else
3780 {
3781 for( PCB_LAYER_ID klayer : GetKicadLayersToIterate( aElem.layer ) )
3782 ConvertFills6ToFootprintItemOnLayer( aFootprint, aElem, klayer );
3783 }
3784}
3785
3786
3788{
3789 std::unique_ptr<PCB_SHAPE> fill = std::make_unique<PCB_SHAPE>( m_board, SHAPE_T::RECTANGLE );
3790
3791 fill->SetFilled( true );
3792 fill->SetLayer( aLayer );
3793 fill->SetStroke( STROKE_PARAMS( 0 ) );
3794
3795 fill->SetStart( aElem.pos1 );
3796 fill->SetEnd( aElem.pos2 );
3797
3798 if( IsCopperLayer( aLayer ) && aElem.net != ALTIUM_NET_UNCONNECTED )
3799 {
3800 fill->SetNetCode( GetNetCode( aElem.net ) );
3801 }
3802
3803 if( aElem.rotation != 0. )
3804 {
3805 // TODO: Do we need SHAPE_T::POLY for non 90° rotations?
3806 VECTOR2I center( ( aElem.pos1.x + aElem.pos2.x ) / 2, ( aElem.pos1.y + aElem.pos2.y ) / 2 );
3807 fill->Rotate( center, EDA_ANGLE( aElem.rotation, DEGREES_T ) );
3808 }
3809
3810 m_board->Add( fill.release(), ADD_MODE::APPEND );
3811}
3812
3813
3815 PCB_LAYER_ID aLayer )
3816{
3817 std::unique_ptr<PCB_SHAPE> fill = std::make_unique<PCB_SHAPE>( aFootprint, SHAPE_T::RECTANGLE );
3818
3819 fill->SetFilled( true );
3820 fill->SetLayer( aLayer );
3821 fill->SetStroke( STROKE_PARAMS( 0 ) );
3822
3823 fill->SetStart( aElem.pos1 );
3824 fill->SetEnd( aElem.pos2 );
3825
3826 if( aElem.rotation != 0. )
3827 {
3828 // TODO: Do we need SHAPE_T::POLY for non 90° rotations?
3829 VECTOR2I center( ( aElem.pos1.x + aElem.pos2.x ) / 2, ( aElem.pos1.y + aElem.pos2.y ) / 2 );
3830 fill->Rotate( center, EDA_ANGLE( aElem.rotation, DEGREES_T ) );
3831 }
3832
3833 aFootprint->Add( fill.release(), ADD_MODE::APPEND );
3834}
3835
3836
3837void ALTIUM_PCB::HelperSetZoneLayers( ZONE& aZone, const ALTIUM_LAYER aAltiumLayer )
3838{
3839 LSET layerSet;
3840
3841 for( PCB_LAYER_ID klayer : GetKicadLayersToIterate( aAltiumLayer ) )
3842 layerSet.set( klayer );
3843
3844 aZone.SetLayerSet( layerSet );
3845}
3846
3847
3848void ALTIUM_PCB::HelperSetZoneKeepoutRestrictions( ZONE& aZone, const uint8_t aKeepoutRestrictions )
3849{
3850 bool keepoutRestrictionVia = ( aKeepoutRestrictions & 0x01 ) != 0;
3851 bool keepoutRestrictionTrack = ( aKeepoutRestrictions & 0x02 ) != 0;
3852 bool keepoutRestrictionCopper = ( aKeepoutRestrictions & 0x04 ) != 0;
3853 bool keepoutRestrictionSMDPad = ( aKeepoutRestrictions & 0x08 ) != 0;
3854 bool keepoutRestrictionTHPad = ( aKeepoutRestrictions & 0x10 ) != 0;
3855
3856 aZone.SetDoNotAllowVias( keepoutRestrictionVia );
3857 aZone.SetDoNotAllowTracks( keepoutRestrictionTrack );
3858 aZone.SetDoNotAllowCopperPour( keepoutRestrictionCopper );
3859 aZone.SetDoNotAllowPads( keepoutRestrictionSMDPad && keepoutRestrictionTHPad );
3860 aZone.SetDoNotAllowFootprints( false );
3861}
3862
3863
3865 const ALTIUM_LAYER aAltiumLayer,
3866 const uint8_t aKeepoutRestrictions )
3867{
3868 std::unique_ptr<ZONE> zone = std::make_unique<ZONE>( m_board );
3869
3870 zone->SetIsRuleArea( true );
3871
3872 HelperSetZoneLayers( *zone, aAltiumLayer );
3873 HelperSetZoneKeepoutRestrictions( *zone, aKeepoutRestrictions );
3874
3875 aShape.EDA_SHAPE::TransformShapeToPolygon( *zone->Outline(), 0, ARC_HIGH_DEF, ERROR_INSIDE );
3876
3877 zone->SetBorderDisplayStyle( ZONE_BORDER_DISPLAY_STYLE::DIAGONAL_EDGE,
3879
3880 m_board->Add( zone.release(), ADD_MODE::APPEND );
3881}
3882
3883
3885 const PCB_SHAPE& aShape,
3886 const ALTIUM_LAYER aAltiumLayer,
3887 const uint8_t aKeepoutRestrictions )
3888{
3889 std::unique_ptr<ZONE> zone = std::make_unique<ZONE>( aFootprint );
3890
3891 zone->SetIsRuleArea( true );
3892
3893 HelperSetZoneLayers( *zone, aAltiumLayer );
3894 HelperSetZoneKeepoutRestrictions( *zone, aKeepoutRestrictions );
3895
3896 aShape.EDA_SHAPE::TransformShapeToPolygon( *zone->Outline(), 0, ARC_HIGH_DEF, ERROR_INSIDE );
3897
3898 zone->SetBorderDisplayStyle( ZONE_BORDER_DISPLAY_STYLE::DIAGONAL_EDGE,
3900
3901 // TODO: zone->SetLocalCoord(); missing?
3902 aFootprint->Add( zone.release(), ADD_MODE::APPEND );
3903}
3904
3905
3906std::vector<std::pair<PCB_LAYER_ID, int>> ALTIUM_PCB::HelperGetSolderAndPasteMaskExpansions(
3907 const ALTIUM_RECORD aType, const int aPrimitiveIndex, const ALTIUM_LAYER aAltiumLayer )
3908{
3909 if( m_extendedPrimitiveInformationMaps.count( aType ) == 0 )
3910 return {}; // there is nothing to parse
3911
3912 auto elems =
3913 m_extendedPrimitiveInformationMaps[ALTIUM_RECORD::TRACK].equal_range( aPrimitiveIndex );
3914
3915 if( elems.first == elems.second )
3916 return {}; // there is nothing to parse
3917
3918 std::vector<std::pair<PCB_LAYER_ID, int>> layerExpansionPairs;
3919
3920 for( auto it = elems.first; it != elems.second; ++it )
3921 {
3922 const AEXTENDED_PRIMITIVE_INFORMATION& pInf = it->second;
3923
3924 if( pInf.type == AEXTENDED_PRIMITIVE_INFORMATION_TYPE::MASK )
3925 {
3926 if( pInf.soldermaskexpansionmode == ALTIUM_MODE::MANUAL
3927 || pInf.soldermaskexpansionmode == ALTIUM_MODE::RULE )
3928 {
3929 // TODO: what layers can lead to solder or paste mask usage? E.g. KEEP_OUT_LAYER and other top/bottom layers
3930 if( aAltiumLayer == ALTIUM_LAYER::TOP_LAYER
3931 || aAltiumLayer == ALTIUM_LAYER::MULTI_LAYER )
3932 {
3933 layerExpansionPairs.emplace_back( F_Mask, pInf.soldermaskexpansionmanual );
3934 }
3935
3936 if( aAltiumLayer == ALTIUM_LAYER::BOTTOM_LAYER
3937 || aAltiumLayer == ALTIUM_LAYER::MULTI_LAYER )
3938 {
3939 layerExpansionPairs.emplace_back( B_Mask, pInf.soldermaskexpansionmanual );
3940 }
3941 }
3942 if( pInf.pastemaskexpansionmode == ALTIUM_MODE::MANUAL
3943 || pInf.pastemaskexpansionmode == ALTIUM_MODE::RULE )
3944 {
3945 if( aAltiumLayer == ALTIUM_LAYER::TOP_LAYER
3946 || aAltiumLayer == ALTIUM_LAYER::MULTI_LAYER )
3947 {
3948 layerExpansionPairs.emplace_back( F_Paste, pInf.pastemaskexpansionmanual );
3949 }
3950
3951 if( aAltiumLayer == ALTIUM_LAYER::BOTTOM_LAYER
3952 || aAltiumLayer == ALTIUM_LAYER::MULTI_LAYER )
3953 {
3954 layerExpansionPairs.emplace_back( B_Paste, pInf.pastemaskexpansionmanual );
3955 }
3956 }
3957 }
3958 }
3959
3960 return layerExpansionPairs;
3961}
const char * name
Definition: DXF_plotter.cpp:57
std::string FormatPath(const std::vector< std::string > &aVectorPath)
Helper for debug logging (vector -> string)
ALTIUM_RULE_KIND
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:86
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:58
bool IsAltiumLayerCopper(ALTIUM_LAYER aLayer)
Definition: altium_pcb.cpp:61
bool IsAltiumLayerAPlane(ALTIUM_LAYER aLayer)
Definition: altium_pcb.cpp:68
std::function< void(const ALTIUM_COMPOUND_FILE &, const CFB::COMPOUND_FILE_ENTRY *)> PARSE_FUNCTION_POINTER_fp
Definition: altium_pcb.h:104
ALTIUM_PCB_DIR
Definition: altium_pcb.h:36
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:150
@ LT_POWER
Definition: board.h:153
@ LT_MIXED
Definition: board.h:154
@ LT_SIGNAL
Definition: board.h:152
@ BS_ITEM_TYPE_COPPER
Definition: board_stackup.h:44
@ BS_ITEM_TYPE_DIELECTRIC
Definition: board_stackup.h:45
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
std::tuple< wxString, const CFB::COMPOUND_FILE_ENTRY * > FindLibFootprintDirName(const wxString &aFpUnicodeName)
const CFB::COMPOUND_FILE_ENTRY * FindStream(const std::vector< std::string > &aStreamPath) const
void ParseFileHeader(const ALTIUM_COMPOUND_FILE &aAltiumPcbFile, const CFB::COMPOUND_FILE_ENTRY *aEntry)
Definition: altium_pcb.cpp:844
PROGRESS_REPORTER * m_progressReporter
optional; may be nullptr
Definition: altium_pcb.h:255
void ParseShapeBasedRegions6Data(const ALTIUM_COMPOUND_FILE &aAltiumPcbFile, const CFB::COMPOUND_FILE_ENTRY *aEntry)
std::vector< PCB_DIM_RADIAL * > m_radialDimensions
Definition: altium_pcb.h:244
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:264
void ConvertTexts6ToFootprintItemOnLayer(FOOTPRINT *aFootprint, const ATEXT6 &aElem, PCB_LAYER_ID aLayer)
std::map< ALTIUM_LAYER, PCB_LAYER_ID > m_layermap
Definition: altium_pcb.h:248
void ConvertShapeBasedRegions6ToBoardItemOnLayer(const AREGION6 &aElem, PCB_LAYER_ID aLayer)
void HelperParseDimensions6Leader(const ADIMENSION6 &aElem)
wxString m_footprintName
for footprint library loading error reporting
Definition: altium_pcb.h:261
std::vector< FOOTPRINT * > m_components
Definition: altium_pcb.h:242
const ARULE6 * GetRuleDefault(ALTIUM_RULE_KIND aKind) const
Definition: altium_pcb.cpp:828
void HelperParsePad6NonCopper(const APAD6 &aElem, PCB_LAYER_ID aLayer, PCB_SHAPE *aShape)
std::vector< PCB_LAYER_ID > GetKicadLayersToIterate(ALTIUM_LAYER aAltiumLayer) const
Definition: altium_pcb.cpp:233
void ParseRegions6Data(const ALTIUM_COMPOUND_FILE &aAltiumPcbFile, const CFB::COMPOUND_FILE_ENTRY *aEntry)
void ParseBoard6Data(const ALTIUM_COMPOUND_FILE &aAltiumPcbFile, const CFB::COMPOUND_FILE_ENTRY *aEntry)
Definition: altium_pcb.cpp:882
void ParseComponentsBodies6Data(const ALTIUM_COMPOUND_FILE &aAltiumPcbFile, const CFB::COMPOUND_FILE_ENTRY *aEntry)
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 ParseFills6Data(const ALTIUM_COMPOUND_FILE &aAltiumPcbFile, const CFB::COMPOUND_FILE_ENTRY *aEntry)
void Parse(const ALTIUM_COMPOUND_FILE &aAltiumPcbFile, const std::map< ALTIUM_PCB_DIR, std::string > &aFileMapping)
Definition: altium_pcb.cpp:301
void ParseBoardRegionsData(const ALTIUM_COMPOUND_FILE &aAltiumPcbFile, const CFB::COMPOUND_FILE_ENTRY *aEntry)
ALTIUM_PCB(BOARD *aBoard, PROGRESS_REPORTER *aProgressReporter, const wxString &aLibrary=wxEmptyString, const wxString &aFootprintName=wxEmptyString)
Definition: altium_pcb.cpp:265
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:253
std::vector< int > m_altiumToKicadNetcodes
Definition: altium_pcb.h:247
unsigned m_totalCount
for progress reporting
Definition: altium_pcb.h:258
void HelperPcpShapeAsBoardKeepoutRegion(const PCB_SHAPE &aShape, const ALTIUM_LAYER aAltiumLayer, const uint8_t aKeepoutRestrictions)
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 ConvertFills6ToFootprintItem(FOOTPRINT *aFootprint, const AFILL6 &aElem, const bool aIsBoardImport)
void ConvertShapeBasedRegions6ToBoardItem(const AREGION6 &aElem)
void HelperCreateBoardOutline(const std::vector< ALTIUM_VERTICE > &aVertices)
void ParseVias6Data(const ALTIUM_COMPOUND_FILE &aAltiumPcbFile, const CFB::COMPOUND_FILE_ENTRY *aEntry)
std::map< ALTIUM_RULE_KIND, std::vector< ARULE6 > > m_rules
Definition: altium_pcb.h:249
void HelperSetZoneKeepoutRestrictions(ZONE &aZone, const uint8_t aKeepoutRestrictions)
void ParseClasses6Data(const ALTIUM_COMPOUND_FILE &aAltiumPcbFile, const CFB::COMPOUND_FILE_ENTRY *aEntry)
unsigned m_doneCount
Definition: altium_pcb.h:256
void HelperParseDimensions6Linear(const ADIMENSION6 &aElem)
std::map< uint32_t, wxString > m_unicodeStrings
Definition: altium_pcb.h:246
void ConvertTexts6ToBoardItem(const ATEXT6 &aElem)
FOOTPRINT * ParseFootprint(ALTIUM_COMPOUND_FILE &altiumLibFile, const wxString &aFootprintName)
Definition: altium_pcb.cpp:625
void ParseArcs6Data(const ALTIUM_COMPOUND_FILE &aAltiumPcbFile, const CFB::COMPOUND_FILE_ENTRY *aEntry)
void HelperParseDimensions6Center(const ADIMENSION6 &aElem)
void HelperParseDimensions6Radial(const ADIMENSION6 &aElem)
void ParsePads6Data(const ALTIUM_COMPOUND_FILE &aAltiumPcbFile, const CFB::COMPOUND_FILE_ENTRY *aEntry)
BOARD * m_board
Definition: altium_pcb.h:241
void ParseWideStrings6Data(const ALTIUM_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)
void ParseComponents6Data(const ALTIUM_COMPOUND_FILE &aAltiumPcbFile, const CFB::COMPOUND_FILE_ENTRY *aEntry)
int GetNetCode(uint16_t aId) const
Definition: altium_pcb.cpp:794
void ConvertTexts6ToEdaTextSettings(const ATEXT6 &aElem, EDA_TEXT &aEdaText)
wxString m_library
for footprint library loading error reporting
Definition: altium_pcb.h:260
void ConvertPads6ToFootprintItemOnNonCopper(FOOTPRINT *aFootprint, const APAD6 &aElem)
void ConvertTexts6ToFootprintItem(FOOTPRINT *aFootprint, const ATEXT6 &aElem)
unsigned m_lastProgressCount
Definition: altium_pcb.h:257
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)
std::vector< ZONE * > m_polygons
Definition: altium_pcb.h:243
std::map< wxString, wxString > m_models
Definition: altium_pcb.h:245
void ConvertArcs6ToPcbShape(const AARC6 &aElem, PCB_SHAPE *aShape)
void ConvertTracks6ToBoardItemOnLayer(const ATRACK6 &aElem, PCB_LAYER_ID aLayer)
void ConvertFills6ToBoardItem(const AFILL6 &aElem)
void ParseModelsData(const ALTIUM_COMPOUND_FILE &aAltiumPcbFile, const CFB::COMPOUND_FILE_ENTRY *aEntry, const std::vector< std::string > &aRootDir)
void ParseRules6Data(const ALTIUM_COMPOUND_FILE &aAltiumPcbFile, const CFB::COMPOUND_FILE_ENTRY *aEntry)
FOOTPRINT * HelperGetFootprint(uint16_t aComponent) const
Definition: altium_pcb.cpp:73
PCB_LAYER_ID GetKicadLayer(ALTIUM_LAYER aAltiumLayer) const
Definition: altium_pcb.cpp:127
void checkpoint()
Definition: altium_pcb.cpp:282
void ConvertTracks6ToFootprintItemOnLayer(FOOTPRINT *aFootprint, const ATRACK6 &aElem, PCB_LAYER_ID aLayer)
void ParseTexts6Data(const ALTIUM_COMPOUND_FILE &aAltiumPcbFile, const CFB::COMPOUND_FILE_ENTRY *aEntry)
void ParsePolygons6Data(const ALTIUM_COMPOUND_FILE &aAltiumPcbFile, const CFB::COMPOUND_FILE_ENTRY *aEntry)
void ConvertArcs6ToBoardItemOnLayer(const AARC6 &aElem, PCB_LAYER_ID aLayer)
std::map< ALTIUM_RECORD, std::multimap< int, const AEXTENDED_PRIMITIVE_INFORMATION > > m_extendedPrimitiveInformationMaps
Definition: altium_pcb.h:251
void ParseTracks6Data(const ALTIUM_COMPOUND_FILE &aAltiumPcbFile, const CFB::COMPOUND_FILE_ENTRY *aEntry)
void ConvertPads6ToBoardItemOnNonCopper(const APAD6 &aElem)
void ParseNets6Data(const ALTIUM_COMPOUND_FILE &aAltiumPcbFile, const CFB::COMPOUND_FILE_ENTRY *aEntry)
void ParseExtendedPrimitiveInformationData(const ALTIUM_COMPOUND_FILE &aAltiumPcbFile, const CFB::COMPOUND_FILE_ENTRY *aEntry)
Definition: altium_pcb.cpp:861
void ParseDimensions6Data(const ALTIUM_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:812
void HelperSetZoneLayers(ZONE &aZone, const ALTIUM_LAYER aAltiumLayer)
static wxString ReadString(const std::map< wxString, wxString > &aProps, const wxString &aKey, const wxString &aDefault)
Container for design settings for a BOARD object.
std::shared_ptr< NET_SETTINGS > m_NetSettings
void SetGridOrigin(const VECTOR2I &aOrigin)
const VECTOR2I & GetGridOrigin()
const VECTOR2I & GetAuxOrigin()
void SetAuxOrigin(const VECTOR2I &aOrigin)
BOARD_STACKUP & GetStackupDescriptor()
int GetLineThickness(PCB_LAYER_ID aLayer) const
Return the default graphic segment thickness from the layer class for the given layer.
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition: board_item.h:77
virtual void SetLayer(PCB_LAYER_ID aLayer)
Set the layer this item is on.
Definition: board_item.h:260
Manage layers needed to make a physical board.
void RemoveAll()
Delete all items in list and clear the list.
const std::vector< BOARD_STACKUP_ITEM * > & GetList() const
void BuildDefaultStackupList(const BOARD_DESIGN_SETTINGS *aSettings, int aActiveCopperLayersCount=0)
Create a default stackup, according to the current BOARD_DESIGN_SETTINGS settings.
Information pertinent to a Pcbnew printed circuit board.
Definition: board.h:282
void Add(BOARD_ITEM *aItem, ADD_MODE aMode=ADD_MODE::INSERT, bool aSkipConnectivity=false) override
Removes an item from the container.
Definition: board.cpp:882
const BOX2I GetBoardEdgesBoundingBox() const
Return the board bounding box calculated using exclusively the board edges (graphics on Edge....
Definition: board.h:911
const PAGE_INFO & GetPageSettings() const
Definition: board.h:671
bool IsLayerEnabled(PCB_LAYER_ID aLayer) const
A proxy function that calls the correspondent function in m_BoardSettings tests whether a given layer...
Definition: board.cpp:706
LAYER_T GetLayerType(PCB_LAYER_ID aLayer) const
Return the type of the copper layer given by aLayer.
Definition: board.cpp:601
bool SetLayerName(PCB_LAYER_ID aLayer, const wxString &aLayerName)
Changes the name of the layer given by aLayer.
Definition: board.cpp:583
void Move(const VECTOR2I &aMoveVector) override
Move this object.
Definition: board.cpp:493
const TRACKS & Tracks() const
Definition: board.h:321
bool m_LegacyNetclassesLoaded
True if netclasses were loaded from the file.
Definition: board.h:369
void SetCopperLayerCount(int aCount)
Definition: board.cpp:662
const wxString & GetFileName() const
Definition: board.h:319
bool SetLayerType(PCB_LAYER_ID aLayer, LAYER_T aLayerType)
Change the type of the layer given by aLayer.
Definition: board.cpp:613
BOARD_DESIGN_SETTINGS & GetDesignSettings() const
Definition: board.cpp:797
const DRAWINGS & Drawings() const
Definition: board.h:325
size_type GetHeight() const
Definition: box2.h:205
size_type GetWidth() const
Definition: box2.h:204
coord_type GetY() const
Definition: box2.h:198
coord_type GetX() const
Definition: box2.h:197
EDA_ANGLE Normalize()
Definition: eda_angle.h:255
double Sin() const
Definition: eda_angle.h:212
double AsDegrees() const
Definition: eda_angle.h:155
bool IsHorizontal() const
Definition: eda_angle.h:180
bool IsVertical() const
Definition: eda_angle.h:185
double Cos() const
Definition: eda_angle.h:227
void SetModified()
Definition: eda_item.cpp:64
EDA_ANGLE GetArcAngle() const
Definition: eda_shape.cpp:663
void SetCenter(const VECTOR2I &aCenter)
Definition: eda_shape.cpp:545
void SetFilled(bool aFlag)
Definition: eda_shape.h:96
int GetRadius() const
Definition: eda_shape.cpp:593
SHAPE_T GetShape() const
Definition: eda_shape.h:120
void SetStart(const VECTOR2I &aStart)
Definition: eda_shape.h:129
const VECTOR2I & GetStart() const
Return the starting point of the graphic.
Definition: eda_shape.h:125
void SetShape(SHAPE_T aShape)
Definition: eda_shape.h:119
void SetEnd(const VECTOR2I &aEnd)
Definition: eda_shape.h:154
void SetArcAngleAndEnd(const EDA_ANGLE &aAngle, bool aCheckNegativeAngle=false)
Set the end point from the angle center and start.
Definition: eda_shape.cpp:691
void SetPolyPoints(const std::vector< VECTOR2I > &aPoints)
Definition: eda_shape.cpp:1204
A mix-in class (via multiple inheritance) that handles texts such as labels, parts,...
Definition: eda_text.h:83
void SetTextSize(VECTOR2I aNewSize, bool aEnforceMinTextSize=true)
Definition: eda_text.cpp:372
void SetMirrored(bool isMirrored)
Definition: eda_text.cpp:250
void SetVertJustify(GR_TEXT_V_ALIGN_T aType)
Definition: eda_text.cpp:274
void SetBoldFlag(bool aBold)
Definition: eda_text.cpp:235
void SetTextThickness(int aWidth)
The TextThickness is that set by the user.
Definition: eda_text.cpp:195
void SetKeepUpright(bool aKeepUpright)
Definition: eda_text.cpp:282
virtual void SetText(const wxString &aText)
Definition: eda_text.cpp:181
virtual void SetTextAngle(const EDA_ANGLE &aAngle)
Definition: eda_text.cpp:203
void SetItalic(bool aItalic)
Definition: eda_text.cpp:211
void SetFont(KIFONT::FONT *aFont)
Definition: eda_text.cpp:356
void SetHorizJustify(GR_TEXT_H_ALIGN_T aType)
Definition: eda_text.cpp:266
EDA_ANGLE GetOrientation() const
Definition: footprint.h:212
PCB_FIELD & Value()
read/write accessors:
Definition: footprint.h:624
bool IsFlipped() const
Definition: footprint.h:377
PCB_FIELD & Reference()
Definition: footprint.h:625
void Add(BOARD_ITEM *aItem, ADD_MODE aMode=ADD_MODE::INSERT, bool aSkipConnectivity=false) override
Removes an item from the container.
Definition: footprint.cpp:968
std::vector< FP_3DMODEL > & Models()
Definition: footprint.h:205
const wxString & GetReference() const
Definition: footprint.h:588
VECTOR2I GetPosition() const override
Definition: footprint.h:209
VECTOR3D m_Offset
3D model offset (mm)
Definition: footprint.h:98
double m_Opacity
Definition: footprint.h:99
VECTOR3D m_Rotation
3D model rotation (degrees)
Definition: footprint.h:97
wxString m_Filename
The 3D shape filename in 3D library.
Definition: footprint.h:100
FONT is an abstract base class for both outline and stroke fonts.
Definition: font.h:131
static FONT * GetFont(const wxString &aFontName=wxEmptyString, bool aBold=false, bool aItalic=false)
Definition: font.cpp:146
const wxString & GetName() const
Definition: font.h:147
virtual bool IsOutline() const
Definition: font.h:139
A logical library item identifier and consists of various portions much like a URI.
Definition: lib_id.h:49
LSET is a set of PCB_LAYER_IDs.
Definition: layer_ids.h:575
static LSET AllCuMask(int aCuLayerCount=MAX_CU_LAYERS)
Return a mask holding the requested number of Cu PCB_LAYER_IDs.
Definition: lset.cpp:863
Handle the data for a net.
Definition: netinfo.h:56
int GetNetCode() const
Definition: netinfo.h:108
static const int UNCONNECTED
Constant that holds the "unconnected net" number (typically 0) all items "connected" to this net are ...
Definition: netinfo.h:375
static LSET PTHMask()
layer set for a through hole pad
Definition: pad.cpp:349
static LSET UnplatedHoleMask()
layer set for a mechanical unplated through hole pad
Definition: pad.cpp:370
static LSET SMDMask()
layer set for a SMD pad on Front layer
Definition: pad.cpp:356
int GetHeightIU(double aIUScale) const
Gets the page height in IU.
Definition: page_info.h:162
int GetWidthIU(double aIUScale) const
Gets the page width in IU.
Definition: page_info.h:153
double GetRadius() const
Definition: pcb_track.cpp:1548
virtual VECTOR2I GetCenter() const override
This defaults to the center of the bounding box if not overridden.
Definition: pcb_track.h:322
A radial dimension indicates either the radius or diameter of an arc or circle.
VECTOR2I GetCenter() const override
This defaults to the center of the bounding box if not overridden.
Definition: pcb_shape.h:75
void Rotate(const VECTOR2I &aRotCentre, const EDA_ANGLE &aAngle) override
Rotate this object.
Definition: pcb_shape.cpp:524
void SetLayer(PCB_LAYER_ID aLayer) override
Set the layer this item is on.
Definition: pcb_shape.cpp:315
void SetStroke(const STROKE_PARAMS &aStroke) override
Definition: pcb_shape.h:86
VECTOR2I GetPosition() const override
Definition: pcb_shape.h:73
virtual void SetPosition(const VECTOR2I &aPos) override
Definition: pcb_text.h:87
A progress reporter interface for use in multi-threaded environments.
virtual bool KeepRefreshing(bool aWait=false)=0
Update the UI (if any).
virtual void Report(const wxString &aMessage)=0
Display aMessage in the progress bar dialog.
virtual void SetCurrentProgress(double aProgress)=0
Set the progress value to aProgress (0..1).
Definition: seg.h:42
VECTOR2I A
Definition: seg.h:49
VECTOR2I B
Definition: seg.h:50
OPT_VECTOR2I Intersect(const SEG &aSeg, bool aIgnoreEndpoints=false, bool aLines=false) const
Compute intersection point of segment (this) with segment aSeg.
Definition: seg.cpp:196
const VECTOR2I & GetArcMid() const
Definition: shape_arc.h:115
const VECTOR2I & GetP1() const
Definition: shape_arc.h:114
const VECTOR2I & GetP0() const
Definition: shape_arc.h:113
Represent a polyline containing arcs as well as line segments: A chain of connected line and/or arc s...
const SHAPE_ARC & Arc(size_t aArc) const
void SetClosed(bool aClosed)
Mark the line chain as closed (i.e.
int PointCount() const
Return the number of points (vertices) in this line chain.
ssize_t ArcIndex(size_t aSegment) const
Return the arc index for the given segment index.
SEG Segment(int aIndex) const
Return a copy of the aIndex-th segment in the line chain.
int NextShape(int aPointIndex) const
Return the vertex index of the next shape in the chain, or -1 if aPointIndex is the last shape.
void Append(int aX, int aY, bool aAllowDuplication=false)
Append a new point at the end of the line chain.
const VECTOR2I & CPoint(int aIndex) const
Return a reference to a given point in the line chain.
bool IsArcStart(size_t aIndex) const
Represent a set of closed polygons.
void Fracture(POLYGON_MODE aFastMode)
Convert a set of polygons with holes to a single outline with "slits"/"fractures" connecting the oute...
int AddOutline(const SHAPE_LINE_CHAIN &aOutline)
Adds a new outline to the set and returns its index.
void BooleanAdd(const SHAPE_POLY_SET &b, POLYGON_MODE aFastMode)
Perform boolean polyset union For aFastMode meaning, see function booleanOp.
int AddHole(const SHAPE_LINE_CHAIN &aHole, int aOutline=-1)
Adds a new hole to the given outline (default: last) and returns its index.
void Move(const VECTOR2I &aVector) override
Simple container to manage line stroke parameters.
Definition: stroke_params.h:81
T EuclideanNorm() const
Compute the Euclidean norm of the vector, which is defined as sqrt(x ** 2 + y ** 2).
Definition: vector2d.h:265
VECTOR2< T > Resize(T aNewLength) const
Return a vector of the same direction, but length specified in aNewLength.
Definition: vector2d.h:350
T y
Definition: vector3.h:63
T z
Definition: vector3.h:64
T x
Definition: vector3.h:62
Handle a list of polygons defining a copper zone.
Definition: zone.h:72
void SetNeedRefill(bool aNeedRefill)
Definition: zone.h:264
void SetDoNotAllowPads(bool aEnable)
Definition: zone.h:721
void SetDoNotAllowCopperPour(bool aEnable)
Definition: zone.h:718
SHAPE_POLY_SET * GetFill(PCB_LAYER_ID aLayer)
Definition: zone.h:621
void SetDoNotAllowTracks(bool aEnable)
Definition: zone.h:720
void SetFilledPolysList(PCB_LAYER_ID aLayer, const SHAPE_POLY_SET &aPolysList)
Set the list of filled polygons.
Definition: zone.h:636
void SetIsFilled(bool isFilled)
Definition: zone.h:261
bool HasFilledPolysForLayer(PCB_LAYER_ID aLayer) const
Definition: zone.h:607
void SetDoNotAllowVias(bool aEnable)
Definition: zone.h:719
void SetLayerSet(LSET aLayerSet) override
Definition: zone.cpp:267
void SetDoNotAllowFootprints(bool aEnable)
Definition: zone.h:722
static int GetDefaultHatchPitch()
Definition: zone.cpp:1051
#define _(s)
static constexpr EDA_ANGLE ANGLE_90
Definition: eda_angle.h:437
@ DEGREES_T
Definition: eda_angle.h:31
static constexpr EDA_ANGLE ANGLE_45
Definition: eda_angle.h:436
@ CTX_NETCLASS
@ ERROR_INSIDE
#define THROW_IO_ERROR(msg)
Definition: ki_exception.h:39
#define MAX_CU_LAYERS
Definition: layer_ids.h:142
bool IsCopperLayer(int aLayerId)
Tests whether a layer is a copper layer.
Definition: layer_ids.h:881
PCB_LAYER_ID
A quick note on layer IDs:
Definition: layer_ids.h:60
@ In22_Cu
Definition: layer_ids.h:86
@ In11_Cu
Definition: layer_ids.h:75
@ In29_Cu
Definition: layer_ids.h:93
@ In30_Cu
Definition: layer_ids.h:94
@ User_8
Definition: layer_ids.h:130
@ In17_Cu
Definition: layer_ids.h:81
@ Edge_Cuts
Definition: layer_ids.h:113
@ Dwgs_User
Definition: layer_ids.h:109
@ F_Paste
Definition: layer_ids.h:101
@ In9_Cu
Definition: layer_ids.h:73
@ User_6
Definition: layer_ids.h:128
@ User_7
Definition: layer_ids.h:129
@ In19_Cu
Definition: layer_ids.h:83
@ In7_Cu
Definition: layer_ids.h:71
@ In28_Cu
Definition: layer_ids.h:92
@ In26_Cu
Definition: layer_ids.h:90
@ B_Mask
Definition: layer_ids.h:106
@ B_Cu
Definition: layer_ids.h:95
@ User_5
Definition: layer_ids.h:127
@ Eco1_User
Definition: layer_ids.h:111
@ F_Mask
Definition: layer_ids.h:107
@ In21_Cu
Definition: layer_ids.h:85
@ In23_Cu
Definition: layer_ids.h:87
@ B_Paste
Definition: layer_ids.h:100
@ In15_Cu
Definition: layer_ids.h:79
@ In2_Cu
Definition: layer_ids.h:66
@ User_9
Definition: layer_ids.h:131
@ F_Fab
Definition: layer_ids.h:120
@ In10_Cu
Definition: layer_ids.h:74
@ Margin
Definition: layer_ids.h:114
@ F_SilkS
Definition: layer_ids.h:104
@ In4_Cu
Definition: layer_ids.h:68
@ UNDEFINED_LAYER
Definition: layer_ids.h:61
@ Eco2_User
Definition: layer_ids.h:112
@ In16_Cu
Definition: layer_ids.h:80
@ In24_Cu
Definition: layer_ids.h:88
@ In1_Cu
Definition: layer_ids.h:65
@ User_3
Definition: layer_ids.h:125
@ User_1
Definition: layer_ids.h:123
@ B_SilkS
Definition: layer_ids.h:103
@ In13_Cu
Definition: layer_ids.h:77
@ User_4
Definition: layer_ids.h:126
@ In8_Cu
Definition: layer_ids.h:72
@ In14_Cu
Definition: layer_ids.h:78
@ User_2
Definition: layer_ids.h:124
@ In12_Cu
Definition: layer_ids.h:76
@ In27_Cu
Definition: layer_ids.h:91
@ In6_Cu
Definition: layer_ids.h:70
@ In5_Cu
Definition: layer_ids.h:69
@ In3_Cu
Definition: layer_ids.h:67
@ In20_Cu
Definition: layer_ids.h:84
@ F_Cu
Definition: layer_ids.h:64
@ In18_Cu
Definition: layer_ids.h:82
@ In25_Cu
Definition: layer_ids.h:89
@ B_Fab
Definition: layer_ids.h:119
LSET FlipLayerMask(LSET aMask, int aCopperLayersCount)
Calculate the mask layer when flipping a footprint.
Definition: lset.cpp:680
@ PAD_DRILL_SHAPE_CIRCLE
Definition: pad_shapes.h:54
DIM_PRECISION
Definition: pcb_dimension.h:47
#define PROJECT_VAR_NAME
A variable name whose value holds the current project directory.
Definition: project.h:39
std::optional< VECTOR2I > OPT_VECTOR2I
Definition: seg.h:39
wxString NotSpecifiedPrm()
double startangle
uint16_t component
bool is_keepout
uint32_t width
ALTIUM_LAYER layer
uint8_t keepoutrestrictions
uint16_t polygon
VECTOR2I center
uint32_t radius
uint16_t net
double endangle
VECTOR2I sheetpos
std::vector< ABOARD6_LAYER_STACKUP > stackup
std::vector< ALTIUM_VERTICE > board_vertices
std::vector< wxString > names
ALTIUM_CLASS_KIND kind
wxString name
wxString sourcefootprintlibrary
ALTIUM_LAYER layer
wxString sourcedesignator
int32_t textprecision
ALTIUM_UNIT textunit
wxString textsuffix
uint32_t textlinewidth
wxString textformat
ALTIUM_LAYER layer
std::vector< VECTOR2I > textPoint
wxString textprefix
uint32_t linewidth
ALTIUM_DIMENSION_KIND kind
uint32_t textheight
std::vector< VECTOR2I > referencePoint
AEXTENDED_PRIMITIVE_INFORMATION_TYPE type
VECTOR2I pos2
ALTIUM_LAYER layer
uint16_t net
VECTOR2I pos1
double rotation
uint8_t keepoutrestrictions
uint16_t component
const VECTOR2I position
wxString id
wxString name
wxString name
int32_t soldermaskexpansionmanual
uint16_t net
ALTIUM_LAYER layer
std::unique_ptr< APAD6_SIZE_AND_SHAPE > sizeAndShape
ALTIUM_PAD_SHAPE topshape
ALTIUM_PAD_MODE padmode
ALTIUM_MODE pastemaskexpansionmode
uint32_t holesize
double direction
ALTIUM_MODE soldermaskexpansionmode
wxString name
bool is_tent_bottom
uint16_t component
int32_t pastemaskexpansionmanual
bool is_tent_top
VECTOR2I position
VECTOR2I topsize
std::vector< ALTIUM_VERTICE > vertices
ALTIUM_POLYGON_HATCHSTYLE hatchstyle
int32_t pourindex
int32_t trackwidth
ALTIUM_LAYER layer
int32_t gridsize
uint8_t keepoutrestrictions
ALTIUM_LAYER layer
std::vector< ALTIUM_VERTICE > outline
std::vector< std::vector< ALTIUM_VERTICE > > holes
uint16_t component
uint16_t net
uint16_t polygon
ALTIUM_REGION_KIND kind
ALTIUM_RULE_KIND kind
ALTIUM_CONNECT_STYLE polygonconnectStyle
wxString scope1expr
wxString name
int planeclearanceClearance
int32_t polygonconnectReliefconductorwidth
int pastemaskExpansion
wxString scope2expr
int soldermaskExpansion
int32_t polygonconnectAirgapwidth
uint16_t component
wxString text
double rotation
uint32_t height
wxString fontname
ALTIUM_LAYER layer
VECTOR2I position
ALTIUM_TEXT_TYPE fonttype
bool isDesignator
uint32_t strokewidth
VECTOR2I end
uint32_t width
uint16_t net
uint16_t polygon
uint8_t keepoutrestrictions
VECTOR2I start
ALTIUM_LAYER layer
uint16_t component
uint32_t diameter
uint16_t net
VECTOR2I position
bool is_locked
ALTIUM_LAYER layer_start
ALTIUM_LAYER layer_end
uint32_t holesize
constexpr double IUTomm(int iu) const
Definition: base_units.h:86
const double IU_PER_MILS
Definition: base_units.h:77
constexpr int mmToIU(double mm) const
Definition: base_units.h:88
@ GR_TEXT_H_ALIGN_LEFT
@ GR_TEXT_V_ALIGN_BOTTOM
@ GR_TEXT_V_ALIGN_CENTER
void RotatePoint(int *pX, int *pY, const EDA_ANGLE &aAngle)
Calculate the new point of coord coord pX, pY, for a rotation center 0, 0.
Definition: trigo.cpp:228
double DEG2RAD(double deg)
Definition: trigo.h:200
double GetLineLength(const VECTOR2I &aPointA, const VECTOR2I &aPointB)
Return the length of a line segment defined by aPointA and aPointB.
Definition: trigo.h:194
double EuclideanNorm(const VECTOR2I &vector)
Definition: trigo.h:128
@ PCB_SHAPE_T
class PCB_SHAPE, a segment not on copper layers
Definition: typeinfo.h:88
@ PCB_DIM_ALIGNED_T
class PCB_DIM_ALIGNED, a linear dimension (graphic item)
Definition: typeinfo.h:101
@ PCB_ARC_T
class PCB_ARC, an arc track segment on a copper layer
Definition: typeinfo.h:98
constexpr ret_type KiROUND(fp_type v)
Round a floating point number to an integer using "round halfway cases away from zero".
Definition: util.h:118
VECTOR2< int > VECTOR2I
Definition: vector2d.h:588