KiCad PCB EDA Suite
Loading...
Searching...
No Matches
altium_parser_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) 2020 Thomas Pointhuber <[email protected]>
5 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, you may find one here:
19 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
20 * or you may search the http://www.gnu.org website for the version 2 license,
21 * or you may write to the Free Software Foundation, Inc.,
22 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
23 */
24
25#include <cmath>
26#include <map>
27#include <set>
28#include <unordered_map>
29
30#include <ki_exception.h>
31#include <math/util.h>
32
33#include <wx/log.h>
34#include <wx/translation.h>
35
36#include "altium_parser_pcb.h"
39
40
48static const wxChar* traceAltiumImport = wxT( "KICAD_ALTIUM_IMPORT" );
49
50
51/*
52 * Returns an Altium layer id from V6 and V7 file format data, compatible with the rest of the parser.
53 */
55{
57 return aV7Layer;
58
59 return aV6Layer;
60}
61
62
63bool altiumScopeExprMatchesPolygon( const wxString& aExpr )
64{
65 // INPOLY/ISPOLY are prefixes of INPOLYGON/ISPOLYGON, so two checks cover all four tokens.
66 wxString upper = aExpr.Upper();
67 return upper.Contains( wxT( "INPOLY" ) ) || upper.Contains( wxT( "ISPOLY" ) );
68}
69
70
71const ARULE6* selectAltiumPolygonRule( const std::vector<ARULE6>& aRulesByPriorityAsc )
72{
73 for( const ARULE6& rule : aRulesByPriorityAsc )
74 {
75 if( altiumScopeExprMatchesPolygon( rule.scope1expr )
76 || altiumScopeExprMatchesPolygon( rule.scope2expr ) )
77 {
78 return &rule;
79 }
80 }
81
82 return nullptr;
83}
84
85
86bool altiumViaSideIsTented( bool aTentFlag, bool aManual, bool aFromHole, uint32_t aHoleSize,
87 int32_t aMaskExpansion, int aLandDiameter )
88{
89 if( aTentFlag )
90 return true;
91
92 if( aManual && aFromHole )
93 {
94 int64_t opening = static_cast<int64_t>( aHoleSize ) + 2LL * aMaskExpansion;
95
96 return opening <= static_cast<int64_t>( aLandDiameter );
97 }
98
99 return false;
100}
101
102
103/*
104 * Returns V7 layer ids for Mechanical 17 and above. Otherwise, V6 layer ids.
105 */
106ALTIUM_LAYER altium_layer_from_name( const wxString& aName )
107{
108 if( aName.IsEmpty() )
110
111 static const std::unordered_map<std::string, ALTIUM_LAYER> hash_map = {
112 { "TOP", ALTIUM_LAYER::TOP_LAYER },
113 { "MID1", ALTIUM_LAYER::MID_LAYER_1 },
114 { "MID2", ALTIUM_LAYER::MID_LAYER_2 },
115 { "MID3", ALTIUM_LAYER::MID_LAYER_3 },
116 { "MID4", ALTIUM_LAYER::MID_LAYER_4 },
117 { "MID5", ALTIUM_LAYER::MID_LAYER_5 },
118 { "MID6", ALTIUM_LAYER::MID_LAYER_6 },
119 { "MID7", ALTIUM_LAYER::MID_LAYER_7 },
120 { "MID8", ALTIUM_LAYER::MID_LAYER_8 },
121 { "MID9", ALTIUM_LAYER::MID_LAYER_9 },
122 { "MID10", ALTIUM_LAYER::MID_LAYER_10 },
123 { "MID11", ALTIUM_LAYER::MID_LAYER_11 },
124 { "MID12", ALTIUM_LAYER::MID_LAYER_12 },
125 { "MID13", ALTIUM_LAYER::MID_LAYER_13 },
126 { "MID14", ALTIUM_LAYER::MID_LAYER_14 },
127 { "MID15", ALTIUM_LAYER::MID_LAYER_15 },
128 { "MID16", ALTIUM_LAYER::MID_LAYER_16 },
129 { "MID17", ALTIUM_LAYER::MID_LAYER_17 },
130 { "MID18", ALTIUM_LAYER::MID_LAYER_18 },
131 { "MID19", ALTIUM_LAYER::MID_LAYER_19 },
132 { "MID20", ALTIUM_LAYER::MID_LAYER_20 },
133 { "MID21", ALTIUM_LAYER::MID_LAYER_21 },
134 { "MID22", ALTIUM_LAYER::MID_LAYER_22 },
135 { "MID23", ALTIUM_LAYER::MID_LAYER_23 },
136 { "MID24", ALTIUM_LAYER::MID_LAYER_24 },
137 { "MID25", ALTIUM_LAYER::MID_LAYER_25 },
138 { "MID26", ALTIUM_LAYER::MID_LAYER_26 },
139 { "MID27", ALTIUM_LAYER::MID_LAYER_27 },
140 { "MID28", ALTIUM_LAYER::MID_LAYER_28 },
141 { "MID29", ALTIUM_LAYER::MID_LAYER_29 },
142 { "MID30", ALTIUM_LAYER::MID_LAYER_30 },
143 { "BOTTOM", ALTIUM_LAYER::BOTTOM_LAYER },
144
145 { "TOPOVERLAY", ALTIUM_LAYER::TOP_OVERLAY },
146 { "BOTTOMOVERLAY", ALTIUM_LAYER::BOTTOM_OVERLAY },
147 { "TOPPASTE", ALTIUM_LAYER::TOP_PASTE },
148 { "BOTTOMPASTE", ALTIUM_LAYER::BOTTOM_PASTE },
149 { "TOPSOLDER", ALTIUM_LAYER::TOP_SOLDER },
150 { "BOTTOMSOLDER", ALTIUM_LAYER::BOTTOM_SOLDER },
151
152 { "PLANE1", ALTIUM_LAYER::INTERNAL_PLANE_1 },
153 { "PLANE2", ALTIUM_LAYER::INTERNAL_PLANE_2 },
154 { "PLANE3", ALTIUM_LAYER::INTERNAL_PLANE_3 },
155 { "PLANE4", ALTIUM_LAYER::INTERNAL_PLANE_4 },
156 { "PLANE5", ALTIUM_LAYER::INTERNAL_PLANE_5 },
157 { "PLANE6", ALTIUM_LAYER::INTERNAL_PLANE_6 },
158 { "PLANE7", ALTIUM_LAYER::INTERNAL_PLANE_7 },
159 { "PLANE8", ALTIUM_LAYER::INTERNAL_PLANE_8 },
160 { "PLANE9", ALTIUM_LAYER::INTERNAL_PLANE_9 },
161 { "PLANE10", ALTIUM_LAYER::INTERNAL_PLANE_10 },
162 { "PLANE11", ALTIUM_LAYER::INTERNAL_PLANE_11 },
163 { "PLANE12", ALTIUM_LAYER::INTERNAL_PLANE_12 },
164 { "PLANE13", ALTIUM_LAYER::INTERNAL_PLANE_13 },
165 { "PLANE14", ALTIUM_LAYER::INTERNAL_PLANE_14 },
166 { "PLANE15", ALTIUM_LAYER::INTERNAL_PLANE_15 },
167 { "PLANE16", ALTIUM_LAYER::INTERNAL_PLANE_16 },
168
169 { "DRILLGUIDE", ALTIUM_LAYER::DRILL_GUIDE },
170 { "KEEPOUT", ALTIUM_LAYER::KEEP_OUT_LAYER },
171
172 { "MECHANICAL1", ALTIUM_LAYER::MECHANICAL_1 },
173 { "MECHANICAL2", ALTIUM_LAYER::MECHANICAL_2 },
174 { "MECHANICAL3", ALTIUM_LAYER::MECHANICAL_3 },
175 { "MECHANICAL4", ALTIUM_LAYER::MECHANICAL_4 },
176 { "MECHANICAL5", ALTIUM_LAYER::MECHANICAL_5 },
177 { "MECHANICAL6", ALTIUM_LAYER::MECHANICAL_6 },
178 { "MECHANICAL7", ALTIUM_LAYER::MECHANICAL_7 },
179 { "MECHANICAL8", ALTIUM_LAYER::MECHANICAL_8 },
180 { "MECHANICAL9", ALTIUM_LAYER::MECHANICAL_9 },
181 { "MECHANICAL10", ALTIUM_LAYER::MECHANICAL_10 },
182 { "MECHANICAL11", ALTIUM_LAYER::MECHANICAL_11 },
183 { "MECHANICAL12", ALTIUM_LAYER::MECHANICAL_12 },
184 { "MECHANICAL13", ALTIUM_LAYER::MECHANICAL_13 },
185 { "MECHANICAL14", ALTIUM_LAYER::MECHANICAL_14 },
186 { "MECHANICAL15", ALTIUM_LAYER::MECHANICAL_15 },
187 { "MECHANICAL16", ALTIUM_LAYER::MECHANICAL_16 },
188
189 { "DRILLDRAWING", ALTIUM_LAYER::DRILL_DRAWING },
190 { "MULTILAYER", ALTIUM_LAYER::MULTI_LAYER },
191
192 // FIXME: the following mapping is just a guess
193 { "CONNECTIONS", ALTIUM_LAYER::CONNECTIONS },
194 { "BACKGROUND", ALTIUM_LAYER::BACKGROUND },
195 { "DRCERRORMARKERS", ALTIUM_LAYER::DRC_ERROR_MARKERS },
196 { "SELECTIONS", ALTIUM_LAYER::SELECTIONS },
197 { "VISIBLEGRID1", ALTIUM_LAYER::VISIBLE_GRID_1 },
198 { "VISIBLEGRID2", ALTIUM_LAYER::VISIBLE_GRID_2 },
199 { "PADHOLES", ALTIUM_LAYER::PAD_HOLES },
200 { "VIAHOLES", ALTIUM_LAYER::VIA_HOLES },
201 };
202
203 auto it = hash_map.find( std::string( aName.c_str() ) );
204
205 if( it != hash_map.end() )
206 return it->second;
207
208 // Try V7 format mechanical layers
209 const wxString mechanicalStr( "MECHANICAL" );
210
211 if( aName.StartsWith( mechanicalStr ) )
212 {
213 unsigned long val = 0;
214
215 if( aName.Mid( mechanicalStr.length() ).ToULong( &val ) )
216 return static_cast<ALTIUM_LAYER>( static_cast<int>( ALTIUM_LAYER::V7_MECHANICAL_BASE ) + val );
217 }
218
219 wxLogError( _( "Unknown mapping of the Altium layer '%s'." ), aName );
221}
222
223
225{
226 static const std::unordered_map<std::string, ALTIUM_MECHKIND> hash_map = {
227 { "AssemblyTop", ALTIUM_MECHKIND::ASSEMBLY_TOP },
228 { "AssemblyBottom", ALTIUM_MECHKIND::ASSEMBLY_BOT },
229
230 { "AssemblyNotes", ALTIUM_MECHKIND::ASSEMBLY_NOTES },
231 { "Board", ALTIUM_MECHKIND::BOARD },
232
233 { "CoatingTop", ALTIUM_MECHKIND::COATING_TOP },
234 { "CoatingBottom", ALTIUM_MECHKIND::COATING_BOT },
235
236 { "ComponentCenterTop", ALTIUM_MECHKIND::COMPONENT_CENTER_TOP },
237 { "ComponentCenterBottom", ALTIUM_MECHKIND::COMPONENT_CENTER_BOT },
238
239 { "ComponentOutlineTop", ALTIUM_MECHKIND::COMPONENT_OUTLINE_TOP },
240 { "ComponentOutlineBottom", ALTIUM_MECHKIND::COMPONENT_OUTLINE_BOT },
241
242 { "CourtyardTop", ALTIUM_MECHKIND::COURTYARD_TOP },
243 { "CourtyardBottom", ALTIUM_MECHKIND::COURTYARD_BOT },
244
245 { "DesignatorTop", ALTIUM_MECHKIND::DESIGNATOR_TOP },
246 { "DesignatorBottom", ALTIUM_MECHKIND::DESIGNATOR_BOT },
247
248 { "Dimensions", ALTIUM_MECHKIND::DIMENSIONS },
249 { "DimensionsTop", ALTIUM_MECHKIND::DIMENSIONS_TOP },
250 { "DimensionsBottom", ALTIUM_MECHKIND::DIMENSIONS_BOT },
251
252 { "FabNotes", ALTIUM_MECHKIND::FAB_NOTES },
253
254 { "GluePointsTop", ALTIUM_MECHKIND::GLUE_POINTS_TOP },
255 { "GluePointsBottom", ALTIUM_MECHKIND::GLUE_POINTS_BOT },
256
257 { "GoldPlatingTop", ALTIUM_MECHKIND::GOLD_PLATING_TOP },
258 { "GoldPlatingBottom", ALTIUM_MECHKIND::GOLD_PLATING_BOT },
259
260 { "ValueTop", ALTIUM_MECHKIND::VALUE_TOP },
261 { "ValueBottom", ALTIUM_MECHKIND::VALUE_BOT },
262
263 { "VCut", ALTIUM_MECHKIND::V_CUT },
264
265 { "3DBodyTop", ALTIUM_MECHKIND::BODY_3D_TOP },
266 { "3DBodyBottom", ALTIUM_MECHKIND::BODY_3D_BOT },
267
268 { "RouteToolPath", ALTIUM_MECHKIND::ROUTE_TOOL_PATH },
269 { "Sheet", ALTIUM_MECHKIND::SHEET },
270 { "BoardShape", ALTIUM_MECHKIND::BOARD_SHAPE },
271 };
272
273 auto it = hash_map.find( std::string( aName.c_str() ) );
274
275 if( it != hash_map.end() )
276 {
277 return it->second;
278 }
279 else
280 {
281 wxLogError( _( "Unknown mapping of the Altium layer kind '%s'." ), aName );
283 }
284}
285
286
287void altium_parse_polygons( std::map<wxString, wxString>& aProps,
288 std::vector<ALTIUM_VERTICE>& aVertices )
289{
290 for( size_t i = 0; i < std::numeric_limits<size_t>::max(); i++ )
291 {
292 const wxString si = std::to_string( i );
293
294 const wxString vxi = wxT( "VX" ) + si;
295 const wxString vyi = wxT( "VY" ) + si;
296
297 if( aProps.find( vxi ) == aProps.end() || aProps.find( vyi ) == aProps.end() )
298 break; // it doesn't seem like we know beforehand how many vertices are inside a polygon
299
300 const bool isRound = ALTIUM_PROPS_UTILS::ReadInt( aProps, wxT( "KIND" ) + si, 0 ) != 0;
301 const int32_t radius = ALTIUM_PROPS_UTILS::ReadKicadUnit( aProps, wxT( "R" ) + si, wxT( "0mil" ) );
302 const double sa = ALTIUM_PROPS_UTILS::ReadDouble( aProps, wxT( "SA" ) + si, 0. );
303 const double ea = ALTIUM_PROPS_UTILS::ReadDouble( aProps, wxT( "EA" ) + si, 0. );
304 const VECTOR2I vp = VECTOR2I( ALTIUM_PROPS_UTILS::ReadKicadUnit( aProps, vxi, wxT( "0mil" ) ),
305 -ALTIUM_PROPS_UTILS::ReadKicadUnit( aProps, vyi, wxT( "0mil" ) ) );
306 const VECTOR2I cp = VECTOR2I( ALTIUM_PROPS_UTILS::ReadKicadUnit( aProps, wxT( "CX" ) + si, wxT( "0mil" ) ),
307 -ALTIUM_PROPS_UTILS::ReadKicadUnit( aProps, wxT( "CY" ) + si, wxT( "0mil" ) ) );
308
309 aVertices.emplace_back( isRound, radius, sa, ea, vp, cp );
310 }
311}
312
313
314static ALTIUM_MODE ReadAltiumModeFromProperties( const std::map<wxString, wxString>& aProps,
315 wxString aKey )
316{
317 wxString mode = ALTIUM_PROPS_UTILS::ReadString( aProps, aKey, wxT( "" ) );
318
319 if( mode == wxT( "None" ) )
320 return ALTIUM_MODE::NONE;
321 else if( mode == wxT( "Rule" ) )
322 return ALTIUM_MODE::RULE;
323 else if( mode == wxT( "Manual" ) )
324 return ALTIUM_MODE::MANUAL;
325
326 wxLogError( _( "Unknown Mode string: '%s'." ), mode );
328}
329
330
331static ALTIUM_RECORD ReadAltiumRecordFromProperties( const std::map<wxString, wxString>& aProps,
332 wxString aKey )
333{
334 wxString record = ALTIUM_PROPS_UTILS::ReadString( aProps, aKey, wxT( "" ) );
335
336 if( record == wxT( "Arc" ) )
337 return ALTIUM_RECORD::ARC;
338 else if( record == wxT( "Pad" ) )
339 return ALTIUM_RECORD::PAD;
340 else if( record == wxT( "Via" ) )
341 return ALTIUM_RECORD::VIA;
342 else if( record == wxT( "Track" ) )
344 else if( record == wxT( "Text" ) )
345 return ALTIUM_RECORD::TEXT;
346 else if( record == wxT( "Fill" ) )
347 return ALTIUM_RECORD::FILL;
348 else if( record == wxT( "Region" ) ) // correct?
350 else if( record == wxT( "Model" ) )
352
353 wxLogError( _( "Unknown Record name string: '%s'." ), record );
355}
356
357
360 const std::map<wxString, wxString>& aProps, wxString aKey )
361{
362 wxString parsedType = ALTIUM_PROPS_UTILS::ReadString( aProps, aKey, wxT( "" ) );
363
364 if( parsedType == wxT( "Mask" ) )
366
367 wxLogError( _( "Unknown Extended Primitive Information type: '%s'." ), parsedType );
369}
370
371
380static void ExpectSubrecordLengthAtLeast( const std::string& aStreamType,
381 const std::string& aSubrecordName, size_t aExpectedLength,
382 size_t aActualLength )
383{
384 if( aActualLength < aExpectedLength )
385 {
386 THROW_IO_ERROR( wxString::Format( "%s stream %s has length %d, "
387 "which is unexpected (expected at least %d)",
388 aStreamType, aSubrecordName, aActualLength,
389 aExpectedLength ) );
390 }
391}
392
393
395{
396 const std::map<wxString, wxString> props = aReader.ReadProperties();
397
398 if( props.empty() )
399 THROW_IO_ERROR( wxT( "ExtendedPrimitiveInformation stream has no properties!" ) );
400
401 primitiveIndex = ALTIUM_PROPS_UTILS::ReadInt( props, wxT( "PRIMITIVEINDEX" ), -1 );
402 primitiveObjectId = ReadAltiumRecordFromProperties( props, wxT( "PRIMITIVEOBJECTID" ) );
404
405 pastemaskexpansionmode = ReadAltiumModeFromProperties( props, wxT( "PASTEMASKEXPANSIONMODE" ) );
407 props, wxT( "PASTEMASKEXPANSION_MANUAL" ), wxT( "0mil" ) );
409 ReadAltiumModeFromProperties( props, wxT( "SOLDERMASKEXPANSIONMODE" ) );
411 props, wxT( "SOLDERMASKEXPANSION_MANUAL" ), wxT( "0mil" ) );
412}
413
414
415ABOARD6_LAYER_STACKUP::ABOARD6_LAYER_STACKUP( const std::map<wxString, wxString>& aProps, const wxString& aPrefix,
416 uint32_t aLayerIdFallback )
417{
418 // LAYERID is specific to V7 format
419 layerId = ALTIUM_PROPS_UTILS::ReadInt( aProps, aPrefix + wxT( "LAYERID" ), aLayerIdFallback );
420
421 name = ALTIUM_PROPS_UTILS::ReadString( aProps, aPrefix + wxT( "NAME" ), wxT( "" ) );
422 nextId = ALTIUM_PROPS_UTILS::ReadInt( aProps, aPrefix + wxT( "NEXT" ), 0 );
423 prevId = ALTIUM_PROPS_UTILS::ReadInt( aProps, aPrefix + wxT( "PREV" ), 0 );
424 copperthick = ALTIUM_PROPS_UTILS::ReadKicadUnit( aProps, aPrefix + wxT( "COPTHICK" ), wxT( "1.4mil" ) );
425
426 dielectricconst = ALTIUM_PROPS_UTILS::ReadDouble( aProps, aPrefix + wxT( "DIELCONST" ), 0. );
427 dielectricthick = ALTIUM_PROPS_UTILS::ReadKicadUnit( aProps, aPrefix + wxT( "DIELHEIGHT" ), wxT( "60mil" ) );
428 dielectricmaterial = ALTIUM_PROPS_UTILS::ReadString( aProps, aPrefix + wxT( "DIELMATERIAL" ), wxT( "FR-4" ) );
429
430 // TODO: In some component libraries MECHENABLED may be FALSE but the layers show up as used.
431 // (we should check if any objects exists on these layers?)
432 wxString mechEnabled = ALTIUM_PROPS_UTILS::ReadString( aProps, aPrefix + wxT( "MECHENABLED" ), wxT( "" ) );
433
434 mechenabled = !mechEnabled.Contains( wxS( "FALSE" ) );
435
436 if( mechenabled )
437 {
438 wxString mechKind = ALTIUM_PROPS_UTILS::ReadString( aProps, aPrefix + wxT( "MECHKIND" ), wxT( "" ) );
439
440 if( !mechKind.IsEmpty() )
442 }
443}
444
445
446static wxString MakeAltiumDielectricKey( const wxString& aMaterial, int32_t aHeight, double aConst )
447{
448 return wxString::Format( wxT( "%s|%d|%d" ), aMaterial, aHeight,
449 static_cast<int>( std::lround( aConst * 1000.0 ) ) );
450}
451
452
453// Build a lookup from the modern physical stackup keys (LAYER_V8_<n> / V9_STACK_LAYER<n>) so we
454// can recover dielectric properties that the legacy LAYER<n> keys do not carry. Currently only the
455// loss tangent is missing from the legacy records, so we key the lookup on the dielectric material,
456// height and permittivity that both formats share. If two distinct dielectrics share the same key
457// but report different loss tangents the mapping is ambiguous, so we flag it and skip the backfill
458// for that key rather than guess.
459static std::map<wxString, double> ReadAltiumDielectricLossTangents( const std::map<wxString, wxString>& aProps )
460{
461 std::map<wxString, double> tangents;
462 std::set<wxString> ambiguous;
463
464 auto scan = [&]( const wxString& aFamily )
465 {
466 for( size_t i = 0; i < std::numeric_limits<size_t>::max(); i++ )
467 {
468 const wxString prefix = aFamily + std::to_string( i );
469
470 if( aProps.find( prefix + wxT( "NAME" ) ) == aProps.end() )
471 break;
472
473 if( aProps.find( prefix + wxT( "DIELLOSSTANGENT" ) ) == aProps.end() )
474 continue;
475
476 wxString material = ALTIUM_PROPS_UTILS::ReadString( aProps, prefix + wxT( "DIELMATERIAL" ),
477 wxT( "" ) );
478 int32_t height = ALTIUM_PROPS_UTILS::ReadKicadUnit( aProps, prefix + wxT( "DIELHEIGHT" ),
479 wxT( "0mil" ) );
480 double diconst = ALTIUM_PROPS_UTILS::ReadDouble( aProps, prefix + wxT( "DIELCONST" ), 0. );
481 double tangent = ALTIUM_PROPS_UTILS::ReadDouble( aProps, prefix + wxT( "DIELLOSSTANGENT" ),
482 0. );
483
484 wxString key = MakeAltiumDielectricKey( material, height, diconst );
485
486 auto [it, inserted] = tangents.insert( { key, tangent } );
487
488 if( !inserted && std::abs( it->second - tangent ) > 1e-9 )
489 ambiguous.insert( key );
490 }
491 };
492
493 // Prefer V9 keys (most recent); fall back to the V8 physical stackup keys only for dielectrics
494 // the V9 scan did not already provide.
495 scan( wxT( "V9_STACK_LAYER" ) );
496 scan( wxT( "LAYER_V8_" ) );
497
498 for( const wxString& key : ambiguous )
499 tangents.erase( key );
500
501 return tangents;
502}
503
504
505static std::vector<ABOARD6_LAYER_STACKUP> ReadAltiumStackupFromProperties( const std::map<wxString, wxString>& aProps )
506{
507 std::vector<ABOARD6_LAYER_STACKUP> stackup;
508
509 for( size_t i = 1; i < std::numeric_limits<size_t>::max(); i++ )
510 {
511 const wxString layeri = wxString( wxT( "LAYER" ) ) << std::to_string( i );
512 const wxString layername = layeri + wxT( "NAME" );
513
514 auto layernameit = aProps.find( layername );
515
516 if( layernameit == aProps.end() )
517 break;
518
519 ABOARD6_LAYER_STACKUP l( aProps, layeri, i );
520 stackup.push_back( l );
521 }
522
523 // V7 format layers
524 for( size_t i = 0; i < std::numeric_limits<size_t>::max(); i++ )
525 {
526 const wxString layeri = wxString( wxT( "LAYERV7_" ) ) << std::to_string( i );
527 const wxString layername = layeri + wxT( "NAME" );
528
529 auto layernameit = aProps.find( layername );
530
531 if( layernameit == aProps.end() )
532 break;
533
534 ABOARD6_LAYER_STACKUP l( aProps, layeri, 0 );
535 stackup.push_back( l );
536 }
537
538 // The legacy LAYER<n> records do not store the dielectric loss tangent, but the modern V8/V9
539 // physical stackup keys do. Backfill it onto the matching dielectric records.
540 std::map<wxString, double> tangents = ReadAltiumDielectricLossTangents( aProps );
541
542 if( !tangents.empty() )
543 {
544 for( ABOARD6_LAYER_STACKUP& l : stackup )
545 {
546 wxString key = MakeAltiumDielectricKey( l.dielectricmaterial, l.dielectricthick,
547 l.dielectricconst );
548
549 auto it = tangents.find( key );
550
551 if( it != tangents.end() )
552 l.dielectriclosstangent = it->second;
553 }
554 }
555
556 return stackup;
557}
558
559
561{
562 std::map<wxString, wxString> props = aReader.ReadProperties();
563
564 if( props.empty() )
565 THROW_IO_ERROR( wxT( "Library stream has no properties!" ) );
566
567 layercount = ALTIUM_PROPS_UTILS::ReadInt( props, wxT( "LAYERSETSCOUNT" ), 1 ) + 1;
568
570
572 {
573 wxString originalName = l.name;
574
575 // Ensure that layer names are unique in KiCad
576 for( int ii = 2; !layerNames.insert( l.name ).second; ii++ )
577 l.name = wxString::Format( wxT( "%s %d" ), originalName, ii );
578 }
579
580 if( aReader.HasParsingError() )
581 THROW_IO_ERROR( wxT( "Library stream was not parsed correctly!" ) );
582}
583
584
586{
587 std::map<wxString, wxString> props = aReader.ReadProperties();
588
589 if( props.empty() )
590 THROW_IO_ERROR( wxT( "Board6 stream has no properties!" ) );
591
592 sheetpos = VECTOR2I( ALTIUM_PROPS_UTILS::ReadKicadUnit( props, wxT( "SHEETX" ), wxT( "0mil" ) ),
593 -ALTIUM_PROPS_UTILS::ReadKicadUnit( props, wxT( "SHEETY" ), wxT( "0mil" ) ) );
594 sheetsize = wxSize( ALTIUM_PROPS_UTILS::ReadKicadUnit( props, wxT( "SHEETWIDTH" ), wxT( "0mil" ) ),
595 ALTIUM_PROPS_UTILS::ReadKicadUnit( props, wxT( "SHEETHEIGHT" ), wxT( "0mil" ) ) );
596
597 layercount = ALTIUM_PROPS_UTILS::ReadInt( props, wxT( "LAYERSETSCOUNT" ), 1 ) + 1;
598
600
602 {
603 wxString originalName = l.name;
604
605 // Ensure that layer names are unique in KiCad
606 for( int ii = 2; !layerNames.insert( l.name ).second; ii++ )
607 l.name = wxString::Format( wxT( "%s %d" ), originalName, ii );
608 }
609
611
612 if( aReader.HasParsingError() )
613 THROW_IO_ERROR( wxT( "Board6 stream was not parsed correctly!" ) );
614}
615
617{
618 std::map<wxString, wxString> properties = aReader.ReadProperties();
619
620 if( properties.empty() )
621 THROW_IO_ERROR( wxT( "Classes6 stream has no properties!" ) );
622
623 name = ALTIUM_PROPS_UTILS::ReadString( properties, wxT( "NAME" ), wxT( "" ) );
624 uniqueid = ALTIUM_PROPS_UTILS::ReadString( properties, wxT( "UNIQUEID" ), wxT( "" ) );
625 kind = static_cast<ALTIUM_CLASS_KIND>( ALTIUM_PROPS_UTILS::ReadInt( properties, wxT( "KIND" ), -1 ) );
626
627 for( size_t i = 0; i < std::numeric_limits<size_t>::max(); i++ )
628 {
629 auto mit = properties.find( wxT( "M" ) + wxString( std::to_string( i ) ) );
630
631 if( mit == properties.end() )
632 break; // it doesn't seem like we know beforehand how many components are in the netclass
633
634 names.push_back( mit->second );
635 }
636
637 if( aReader.HasParsingError() )
638 THROW_IO_ERROR( wxT( "Classes6 stream was not parsed correctly" ) );
639}
640
642{
643 std::map<wxString, wxString> props = aReader.ReadProperties();
644
645 if( props.empty() )
646 THROW_IO_ERROR( wxT( "Components6 stream has no props" ) );
647
648 layer = altium_layer_from_name( ALTIUM_PROPS_UTILS::ReadString( props, wxT( "LAYER" ), wxT( "" ) ) );
649 position = VECTOR2I( ALTIUM_PROPS_UTILS::ReadKicadUnit( props, wxT( "X" ), wxT( "0mil" ) ),
650 -ALTIUM_PROPS_UTILS::ReadKicadUnit( props, wxT( "Y" ), wxT( "0mil" ) ) );
651 rotation = ALTIUM_PROPS_UTILS::ReadDouble( props, wxT( "ROTATION" ), 0. );
652 locked = ALTIUM_PROPS_UTILS::ReadBool( props, wxT( "LOCKED" ), false );
653 nameon = ALTIUM_PROPS_UTILS::ReadBool( props, wxT( "NAMEON" ), true );
654 commenton = ALTIUM_PROPS_UTILS::ReadBool( props, wxT( "COMMENTON" ), false );
655 sourcedesignator = ALTIUM_PROPS_UTILS::ReadString( props, wxT( "SOURCEDESIGNATOR" ), wxT( "" ) );
656
657 sourceUniqueID = ALTIUM_PROPS_UTILS::ReadString( props, wxT( "SOURCEUNIQUEID" ), wxT( "" ) );
658
659 // Remove leading backslash from sourceUniqueID to match schematic component unique IDs
660 if( sourceUniqueID.starts_with( wxT( "\\" ) ) )
662
663 sourceHierachicalPath = ALTIUM_PROPS_UTILS::ReadString( props, wxT( "SOURCEHIERARCHICALPATH" ), wxT( "" ) );
665 ALTIUM_PROPS_UTILS::ReadUnicodeString( props, wxT( "SOURCEFOOTPRINTLIBRARY" ), wxT( "" ) );
666 pattern = ALTIUM_PROPS_UTILS::ReadUnicodeString( props, wxT( "PATTERN" ), wxT( "" ) );
667
668 sourcecomponentlibrary = ALTIUM_PROPS_UTILS::ReadString( props, wxT( "SOURCECOMPONENTLIBRARY" ), wxT( "" ) );
669 sourcelibreference = ALTIUM_PROPS_UTILS::ReadString( props, wxT( "SOURCELIBREFERENCE" ), wxT( "" ) );
670
672 ALTIUM_PROPS_UTILS::ReadInt( props, wxT( "NAMEAUTOPOSITION" ), 0 ) );
674 ALTIUM_PROPS_UTILS::ReadInt( props, wxT( "COMMENTAUTOPOSITION" ), 0 ) );
675
676 if( aReader.HasParsingError() )
677 THROW_IO_ERROR( wxT( "Components6 stream was not parsed correctly" ) );
678}
679
681{
682 aReader.Skip( 2 );
683
684 std::map<wxString, wxString> props = aReader.ReadProperties();
685
686 if( props.empty() )
687 THROW_IO_ERROR( wxT( "Dimensions6 stream has no props" ) );
688
689 layer_v6 = altium_layer_from_name( ALTIUM_PROPS_UTILS::ReadString( props, wxT( "LAYER" ), wxT( "" ) ) );
690 layer_v7 = altium_layer_from_name( ALTIUM_PROPS_UTILS::ReadString( props, wxT( "LAYER_V7" ), wxT( "" ) ) );
691 kind = static_cast<ALTIUM_DIMENSION_KIND>( ALTIUM_PROPS_UTILS::ReadInt( props, wxT( "DIMENSIONKIND" ), 0 ) );
692
693 textformat = ALTIUM_PROPS_UTILS::ReadString( props, wxT( "TEXTFORMAT" ), wxT( "" ) );
694 textprefix = ALTIUM_PROPS_UTILS::ReadString( props, wxT( "TEXTPREFIX" ), wxT( "" ) );
695 textsuffix = ALTIUM_PROPS_UTILS::ReadString( props, wxT( "TEXTSUFFIX" ), wxT( "" ) );
696
697 height = ALTIUM_PROPS_UTILS::ReadKicadUnit( props, wxT( "HEIGHT" ), wxT( "0mil" ) );
698 angle = ALTIUM_PROPS_UTILS::ReadDouble( props, wxT( "ANGLE" ), 0. );
699
700 linewidth = ALTIUM_PROPS_UTILS::ReadKicadUnit( props, wxT( "LINEWIDTH" ), wxT( "10mil" ) );
701 textheight = ALTIUM_PROPS_UTILS::ReadKicadUnit( props, wxT( "TEXTHEIGHT" ), wxT( "10mil" ) );
702 textlinewidth = ALTIUM_PROPS_UTILS::ReadKicadUnit( props, wxT( "TEXTLINEWIDTH" ), wxT( "6mil" ) );
703 textprecision = ALTIUM_PROPS_UTILS::ReadInt( props, wxT( "TEXTPRECISION" ), 2 );
704 textbold = ALTIUM_PROPS_UTILS::ReadBool( props, wxT( "TEXTLINEWIDTH" ), false );
705 textitalic = ALTIUM_PROPS_UTILS::ReadBool( props, wxT( "ITALIC" ), false );
706 textgap = ALTIUM_PROPS_UTILS::ReadKicadUnit( props, wxT( "TEXTGAP" ), wxT( "10mil" ) );
707
708 arrowsize = ALTIUM_PROPS_UTILS::ReadKicadUnit( props, wxT( "ARROWSIZE" ), wxT( "60mil" ) );
709
710 wxString text_position_raw = ALTIUM_PROPS_UTILS::ReadString( props, wxT( "TEXTPOSITION" ), wxT( "" ) );
711
712 xy1 = VECTOR2I( ALTIUM_PROPS_UTILS::ReadKicadUnit( props, wxT( "X1" ), wxT( "0mil" ) ),
713 -ALTIUM_PROPS_UTILS::ReadKicadUnit( props, wxT( "Y1" ), wxT( "0mil" ) ) );
714
715 int refcount = ALTIUM_PROPS_UTILS::ReadInt( props, wxT( "REFERENCES_COUNT" ), 0 );
716
717 for( int i = 0; i < refcount; i++ )
718 {
719 const std::string refi = "REFERENCE" + std::to_string( i ) + "POINT";
720 const wxString ref( refi );
721 referencePoint.emplace_back( ALTIUM_PROPS_UTILS::ReadKicadUnit( props, ref + wxT( "X" ), wxT( "0mil" ) ),
722 -ALTIUM_PROPS_UTILS::ReadKicadUnit( props, ref + wxT( "Y" ), wxT( "0mil" ) ) );
723 }
724
725 for( size_t i = 1; i < std::numeric_limits<size_t>::max(); i++ )
726 {
727 const std::string texti = "TEXT" + std::to_string( i );
728 const std::string textix = texti + "X";
729 const std::string textiy = texti + "Y";
730
731 if( props.find( textix ) == props.end() || props.find( textiy ) == props.end() )
732 break; // it doesn't seem like we know beforehand how many vertices are inside a polygon
733
734 textPoint.emplace_back( ALTIUM_PROPS_UTILS::ReadKicadUnit( props, textix, wxT( "0mil" ) ),
735 -ALTIUM_PROPS_UTILS::ReadKicadUnit( props, textiy, wxT( "0mil" ) ) );
736 }
737
738 wxString dimensionunit = ALTIUM_PROPS_UTILS::ReadString( props, wxT( "TEXTDIMENSIONUNIT" ), wxT( "Millimeters" ) );
739
740 if( dimensionunit == wxT( "Inches" ) ) textunit = ALTIUM_UNIT::INCH;
741 else if( dimensionunit == wxT( "Mils" ) ) textunit = ALTIUM_UNIT::MILS;
742 else if( dimensionunit == wxT( "Millimeters" ) ) textunit = ALTIUM_UNIT::MM;
743 else if( dimensionunit == wxT( "Centimeters" ) ) textunit = ALTIUM_UNIT::CM;
745
747
748 if( aReader.HasParsingError() )
749 THROW_IO_ERROR( wxT( "Dimensions6 stream was not parsed correctly" ) );
750}
751
753{
754 std::map<wxString, wxString> properties = aReader.ReadProperties();
755
756 if( properties.empty() )
757 THROW_IO_ERROR( wxT( "Model stream has no properties!" ) );
758
759 name = ALTIUM_PROPS_UTILS::ReadString( properties, wxT( "NAME" ), wxT( "" ) );
760 id = ALTIUM_PROPS_UTILS::ReadString( properties, wxT( "ID" ), wxT( "" ) );
761 isEmbedded = ALTIUM_PROPS_UTILS::ReadBool( properties, wxT( "EMBED" ), false );
762
763 rotation.x = ALTIUM_PROPS_UTILS::ReadDouble( properties, wxT( "ROTX" ), 0. );
764 rotation.y = ALTIUM_PROPS_UTILS::ReadDouble( properties, wxT( "ROTY" ), 0. );
765 rotation.z = ALTIUM_PROPS_UTILS::ReadDouble( properties, wxT( "ROTZ" ), 0. );
766
767 z_offset = ALTIUM_PROPS_UTILS::ReadDouble( properties, wxT( "DZ" ), 0. );
768 checksum = ALTIUM_PROPS_UTILS::ReadInt( properties, wxT( "CHECKSUM" ), 0 );
769
770 if( aReader.HasParsingError() )
771 THROW_IO_ERROR( wxT( "Model stream was not parsed correctly" ) );
772}
773
775{
776 std::map<wxString, wxString> properties = aReader.ReadProperties();
777
778 if( properties.empty() )
779 THROW_IO_ERROR( wxT( "Nets6 stream has no properties" ) );
780
781 name = ALTIUM_PROPS_UTILS::ReadString( properties, wxT( "NAME" ), wxT( "" ) );
782
783 if( aReader.HasParsingError() )
784 THROW_IO_ERROR( wxT( "Nets6 stream was not parsed correctly" ) );
785}
786
788{
789 std::map<wxString, wxString> properties = aReader.ReadProperties();
790
791 if( properties.empty() )
792 THROW_IO_ERROR( wxT( "Polygons6 stream has no properties" ) );
793
794 layer_v6 = altium_layer_from_name( ALTIUM_PROPS_UTILS::ReadString( properties, wxT( "LAYER" ), wxT( "" ) ) );
795 layer_v7 = altium_layer_from_name( ALTIUM_PROPS_UTILS::ReadString( properties, wxT( "LAYER_V7" ), wxT( "" ) ) );
796 net = ALTIUM_PROPS_UTILS::ReadInt( properties, wxT( "NET" ), ALTIUM_NET_UNCONNECTED );
797 locked = ALTIUM_PROPS_UTILS::ReadBool( properties, wxT( "LOCKED" ), false );
798
799 // TODO: kind
800
801 gridsize = ALTIUM_PROPS_UTILS::ReadKicadUnit( properties, wxT( "GRIDSIZE" ), wxT( "0mil" ) );
802 trackwidth = ALTIUM_PROPS_UTILS::ReadKicadUnit( properties, wxT( "TRACKWIDTH" ), wxT( "0mil" ) );
803 minprimlength = ALTIUM_PROPS_UTILS::ReadKicadUnit( properties, wxT( "MINPRIMLENGTH" ), wxT( "0mil" ) );
804 useoctagons = ALTIUM_PROPS_UTILS::ReadBool( properties, wxT( "USEOCTAGONS" ), false );
805
806 pourindex = ALTIUM_PROPS_UTILS::ReadInt( properties, wxT( "POURINDEX" ), 0 );
807
808 wxString hatchstyleraw = ALTIUM_PROPS_UTILS::ReadString( properties, wxT( "HATCHSTYLE" ), wxT( "" ) );
809
810 if( hatchstyleraw == wxT( "Solid" ) ) hatchstyle = ALTIUM_POLYGON_HATCHSTYLE::SOLID;
811 else if( hatchstyleraw == wxT( "45Degree" ) ) hatchstyle = ALTIUM_POLYGON_HATCHSTYLE::DEGREE_45;
812 else if( hatchstyleraw == wxT( "90Degree" ) ) hatchstyle = ALTIUM_POLYGON_HATCHSTYLE::DEGREE_90;
813 else if( hatchstyleraw == wxT( "Horizontal" ) ) hatchstyle = ALTIUM_POLYGON_HATCHSTYLE::HORIZONTAL;
814 else if( hatchstyleraw == wxT( "Vertical" ) ) hatchstyle = ALTIUM_POLYGON_HATCHSTYLE::VERTICAL;
815 else if( hatchstyleraw == wxT( "None" ) ) hatchstyle = ALTIUM_POLYGON_HATCHSTYLE::NONE;
817
818 altium_parse_polygons( properties, vertices );
819
821
822 if( aReader.HasParsingError() )
823 THROW_IO_ERROR( wxT( "Polygons6 stream was not parsed correctly" ) );
824}
825
827{
828 aReader.Skip( 2 );
829
830 std::map<wxString, wxString> props = aReader.ReadProperties();
831
832 if( props.empty() )
833 THROW_IO_ERROR( wxT( "Rules6 stream has no props" ) );
834
835 name = ALTIUM_PROPS_UTILS::ReadString( props, wxT( "NAME" ), wxT( "" ) );
836 priority = ALTIUM_PROPS_UTILS::ReadInt( props, wxT( "PRIORITY" ), 1 );
837
838 scope1expr = ALTIUM_PROPS_UTILS::ReadString( props, wxT( "SCOPE1EXPRESSION" ), wxT( "" ) );
839 scope2expr = ALTIUM_PROPS_UTILS::ReadString( props, wxT( "SCOPE2EXPRESSION" ), wxT( "" ) );
840
841 wxString rulekind = ALTIUM_PROPS_UTILS::ReadString( props, wxT( "RULEKIND" ), wxT( "" ) );
842 if( rulekind == wxT( "Clearance" ) )
843 {
845 clearanceGap = ALTIUM_PROPS_UTILS::ReadKicadUnit( props, wxT( "GAP" ), wxT( "10mil" ) );
846 }
847 else if( rulekind == wxT( "DiffPairsRouting" ) )
848 {
850 }
851 else if( rulekind == wxT( "Height" ) )
852 {
854 }
855 else if( rulekind == wxT( "HoleSize" ) )
856 {
858 minLimit = ALTIUM_PROPS_UTILS::ReadKicadUnit( props, wxT( "MINLIMIT" ), wxT( "1mil" ) );
859 maxLimit = ALTIUM_PROPS_UTILS::ReadKicadUnit( props, wxT( "MAXLIMIT" ), wxT( "150mil" ) );
860 }
861 else if( rulekind == wxT( "HoleToHoleClearance" ) )
862 {
864 clearanceGap = ALTIUM_PROPS_UTILS::ReadKicadUnit( props, wxT( "GAP" ), wxT( "10mil" ) );
865 }
866 else if( rulekind == wxT( "RoutingVias" ) )
867 {
869 width = ALTIUM_PROPS_UTILS::ReadKicadUnit( props, wxT( "WIDTH" ), wxT( "20mil" ) );
870 minWidth = ALTIUM_PROPS_UTILS::ReadKicadUnit( props, wxT( "MINWIDTH" ), wxT( "20mil" ) );
871 maxWidth = ALTIUM_PROPS_UTILS::ReadKicadUnit( props, wxT( "MAXWIDTH" ), wxT( "50mil" ) );
872 holeWidth = ALTIUM_PROPS_UTILS::ReadKicadUnit( props, wxT( "HOLEWIDTH" ), wxT( "10mil" ) );
873 minHoleWidth = ALTIUM_PROPS_UTILS::ReadKicadUnit( props, wxT( "MINHOLEWIDTH" ), wxT( "10mil" ) );
874 maxHoleWidth = ALTIUM_PROPS_UTILS::ReadKicadUnit( props, wxT( "MAXHOLEWIDTH" ), wxT( "28mil" ) );
875 }
876 else if( rulekind == wxT( "Width" ) )
877 {
879 minLimit = ALTIUM_PROPS_UTILS::ReadKicadUnit( props, wxT( "MINLIMIT" ), wxT( "6mil" ) );
880 maxLimit = ALTIUM_PROPS_UTILS::ReadKicadUnit( props, wxT( "MAXLIMIT" ), wxT( "40mil" ) );
881 preferredWidth = ALTIUM_PROPS_UTILS::ReadKicadUnit( props, wxT( "PREFERREDWIDTH" ), wxT( "6mil" ) );
882}
883 else if( rulekind == wxT( "PasteMaskExpansion" ) )
884 {
886 pastemaskExpansion = ALTIUM_PROPS_UTILS::ReadKicadUnit( props, wxT( "EXPANSION" ), wxT( "0" ) );
887 }
888 else if( rulekind == wxT( "SolderMaskExpansion" ) )
889 {
891 soldermaskExpansion = ALTIUM_PROPS_UTILS::ReadKicadUnit( props, wxT( "EXPANSION" ), wxT( "4mil" ) );
892 }
893 else if( rulekind == wxT( "PlaneClearance" ) )
894 {
896 planeclearanceClearance = ALTIUM_PROPS_UTILS::ReadKicadUnit( props, wxT( "CLEARANCE" ), wxT( "10mil" ) );
897 }
898 else if( rulekind == wxT( "PolygonConnect" ) )
899 {
901 polygonconnectAirgapwidth = ALTIUM_PROPS_UTILS::ReadKicadUnit( props, wxT( "AIRGAPWIDTH" ), wxT( "10mil" ) );
902 polygonconnectReliefconductorwidth = ALTIUM_PROPS_UTILS::ReadKicadUnit( props, wxT( "RELIEFCONDUCTORWIDTH" ), wxT( "10mil" ) );
903 polygonconnectReliefentries = ALTIUM_PROPS_UTILS::ReadInt( props, wxT( "RELIEFENTRIES" ), 4 );
904
905 wxString style = ALTIUM_PROPS_UTILS::ReadString( props, wxT( "CONNECTSTYLE" ), wxT( "" ) );
906
907 if( style == wxT( "Direct" ) ) polygonconnectStyle = ALTIUM_CONNECT_STYLE::DIRECT;
908 else if( style == wxT( "Relief" ) ) polygonconnectStyle = ALTIUM_CONNECT_STYLE::RELIEF;
909 else if( style == wxT( "NoConnect" ) ) polygonconnectStyle = ALTIUM_CONNECT_STYLE::NONE;
911 }
912 else
913 {
915 }
916
917 if( aReader.HasParsingError() )
918 THROW_IO_ERROR( wxT( "Rules6 stream was not parsed correctly" ) );
919}
920
922{
923 ALTIUM_RECORD recordtype = static_cast<ALTIUM_RECORD>( aReader.Read<uint8_t>() );
924 if( recordtype != ALTIUM_RECORD::ARC )
925 {
926 THROW_IO_ERROR( wxT( "Arcs6 stream has invalid recordtype" ) );
927 }
928
929 // Subrecord 1
931
932 layer_v6 = static_cast<ALTIUM_LAYER>( aReader.Read<uint8_t>() );
933
934 uint8_t flags1 = aReader.Read<uint8_t>();
935 is_locked = ( flags1 & 0x04 ) == 0;
936 is_polygonoutline = ( flags1 & 0x02 ) != 0;
937
938 uint8_t flags2 = aReader.Read<uint8_t>();
939 is_keepout = flags2 == 2;
940
941 net = aReader.Read<uint16_t>();
942 polygon = aReader.Read<uint16_t>();
943 component = aReader.Read<uint16_t>();
944 aReader.Skip( 4 );
945 center = aReader.ReadVector2IPos();
946 radius = aReader.ReadKicadUnit();
947 startangle = aReader.Read<double>();
948 endangle = aReader.Read<double>();
949 width = aReader.ReadKicadUnit();
950 subpolyindex = aReader.Read<uint16_t>();
951
952 int remaining = aReader.GetRemainingSubrecordBytes();
953
954 if( remaining >= 9 )
955 {
956 aReader.Skip( 5 );
957 layer_v7 = static_cast<ALTIUM_LAYER>( aReader.Read<uint32_t>() );
958 }
959
960 if( remaining >= 10 )
961 keepoutrestrictions = aReader.Read<uint8_t>();
962 else
963 keepoutrestrictions = is_keepout ? 0x1F : 0;
964
966
967 aReader.SkipSubrecord();
968
969 if( aReader.HasParsingError() )
970 {
971 THROW_IO_ERROR( wxT( "Arcs6 stream was not parsed correctly" ) );
972 }
973}
974
976{
977 ALTIUM_RECORD recordtype = static_cast<ALTIUM_RECORD>( aReader.Read<uint8_t>() );
978
979 if( recordtype != ALTIUM_RECORD::MODEL )
980 THROW_IO_ERROR( wxT( "ComponentsBodies6 stream has invalid recordtype" ) );
981
983
984 aReader.Skip( 7 );
985 component = aReader.Read<uint16_t>();
986 aReader.Skip( 9 );
987
988 std::map<wxString, wxString> properties = aReader.ReadProperties();
989
990 if( properties.empty() )
991 THROW_IO_ERROR( wxT( "ComponentsBodies6 stream has no properties" ) );
992
993 modelName = ALTIUM_PROPS_UTILS::ReadString( properties, wxT( "MODEL.NAME" ), wxT( "" ) );
994 modelId = ALTIUM_PROPS_UTILS::ReadString( properties, wxT( "MODELID" ), wxT( "" ) );
995 modelIsEmbedded = ALTIUM_PROPS_UTILS::ReadBool( properties, wxT( "MODEL.EMBED" ), false );
996
997 modelPosition.x = ALTIUM_PROPS_UTILS::ReadKicadUnit( properties, wxT( "MODEL.2D.X" ), wxT( "0mil" ) );
998 modelPosition.y = -ALTIUM_PROPS_UTILS::ReadKicadUnit( properties, wxT( "MODEL.2D.Y" ), wxT( "0mil" ) );
999 modelPosition.z = ALTIUM_PROPS_UTILS::ReadKicadUnit( properties, wxT( "MODEL.3D.DZ" ), wxT( "0mil" ) );
1000
1001 modelRotation.x = ALTIUM_PROPS_UTILS::ReadDouble( properties, wxT( "MODEL.3D.ROTX" ), 0. );
1002 modelRotation.y = ALTIUM_PROPS_UTILS::ReadDouble( properties, wxT( "MODEL.3D.ROTY" ), 0. );
1003 modelRotation.z = ALTIUM_PROPS_UTILS::ReadDouble( properties, wxT( "MODEL.3D.ROTZ" ), 0. );
1004
1005 rotation = ALTIUM_PROPS_UTILS::ReadDouble( properties, wxT( "MODEL.2D.ROTATION" ), 0. );
1006
1007 body_opacity_3d = ALTIUM_PROPS_UTILS::ReadDouble( properties, wxT( "BODYOPACITY3D" ), 1. );
1008 body_projection = ALTIUM_PROPS_UTILS::ReadInt( properties, wxT( "BODYPROJECTION" ), 0 );
1009
1010 aReader.SkipSubrecord();
1011
1012 if( aReader.HasParsingError() )
1013 THROW_IO_ERROR( wxT( "Components6 stream was not parsed correctly" ) );
1014}
1015
1017{
1018 ALTIUM_RECORD recordtype = static_cast<ALTIUM_RECORD>( aReader.Read<uint8_t>() );
1019
1020 if( recordtype != ALTIUM_RECORD::PAD )
1021 THROW_IO_ERROR( wxT( "Pads6 stream has invalid recordtype" ) );
1022
1023 // Subrecord 1
1024 size_t subrecord1 = aReader.ReadAndSetSubrecordLength();
1025
1026 if( subrecord1 == 0 )
1027 THROW_IO_ERROR( wxT( "Pads6 stream has no subrecord1 data" ) );
1028
1029 name = aReader.ReadWxString();
1030
1031 if( aReader.GetRemainingSubrecordBytes() != 0 )
1032 THROW_IO_ERROR( wxT( "Pads6 stream has invalid subrecord1 length" ) );
1033
1034 aReader.SkipSubrecord();
1035
1036 // Subrecord 2
1037 aReader.ReadAndSetSubrecordLength();
1038 aReader.SkipSubrecord();
1039
1040 // Subrecord 3
1041 aReader.ReadAndSetSubrecordLength();
1042 aReader.SkipSubrecord();
1043
1044 // Subrecord 4
1045 aReader.ReadAndSetSubrecordLength();
1046 aReader.SkipSubrecord();
1047
1048 // Subrecord 5
1049 size_t subrecord5 = aReader.ReadAndSetSubrecordLength();
1050
1051 ExpectSubrecordLengthAtLeast( "Pads6", "subrecord5", 110, subrecord5 );
1052
1053 layer_v6 = static_cast<ALTIUM_LAYER>( aReader.Read<uint8_t>() );
1056
1057 uint8_t flags1 = aReader.Read<uint8_t>();
1058 is_test_fab_top = ( flags1 & 0x80 ) != 0;
1059 is_tent_bottom = ( flags1 & 0x40 ) != 0;
1060 is_tent_top = ( flags1 & 0x20 ) != 0;
1061 is_locked = ( flags1 & 0x04 ) == 0;
1062
1063 uint8_t flags2 = aReader.Read<uint8_t>();
1064 is_test_fab_bottom = ( flags2 & 0x01 ) != 0;
1065
1066 net = aReader.Read<uint16_t>();
1067 aReader.Skip( 2 );
1068 component = aReader.Read<uint16_t>();
1069 aReader.Skip( 4 ); // to 13
1070
1071 position = aReader.ReadVector2IPos();
1072 topsize = aReader.ReadVector2ISize();
1073 midsize = aReader.ReadVector2ISize();
1074 botsize = aReader.ReadVector2ISize();
1075 holesize = aReader.ReadKicadUnit(); // to 49
1076
1077 topshape = static_cast<ALTIUM_PAD_SHAPE>( aReader.Read<uint8_t>() );
1078 midshape = static_cast<ALTIUM_PAD_SHAPE>( aReader.Read<uint8_t>() );
1079 botshape = static_cast<ALTIUM_PAD_SHAPE>( aReader.Read<uint8_t>() );
1080
1081 direction = aReader.Read<double>();
1082 plated = aReader.Read<uint8_t>() != 0;
1083 aReader.Skip( 1 );
1084 padmode = static_cast<ALTIUM_PAD_MODE>( aReader.Read<uint8_t>() );
1085 aReader.Skip( 23 );
1088 aReader.Skip( 7 );
1089 pastemaskexpansionmode = static_cast<ALTIUM_MODE>( aReader.Read<uint8_t>() );
1090 soldermaskexpansionmode = static_cast<ALTIUM_MODE>( aReader.Read<uint8_t>() );
1091 aReader.Skip( 3 ); // to 106
1092
1094 pad_to_die_delay = 0;
1095
1096 if( subrecord5 == 110 )
1097 {
1098 // Don't know exactly what this is, but it's always been 0 in the files with 110-byte subrecord5.
1099 // e.g. https://gitlab.com/kicad/code/kicad/-/issues/16514
1100 const uint32_t unknown = aReader.ReadKicadUnit(); // to 110
1101
1102 if( unknown != 0 )
1103 {
1104 THROW_IO_ERROR( wxString::Format( "Pads6 stream subrecord5 + 106 has value %d, which is unexpected",
1105 unknown ) );
1106 }
1107 holerotation = 0;
1108 }
1109 else
1110 {
1111 // More than 110, need at least 114
1112 ExpectSubrecordLengthAtLeast( "Pads6", "subrecord5", 114, subrecord5 );
1113 holerotation = aReader.Read<double>(); // to 114
1114 }
1115
1116 if( subrecord5 >= 120 )
1117 {
1118 tolayer = static_cast<ALTIUM_LAYER>( aReader.Read<uint8_t>() );
1119 aReader.Skip( 2 );
1120 fromlayer = static_cast<ALTIUM_LAYER>( aReader.Read<uint8_t>() );
1121 //aReader.skip( 2 );
1122 }
1123 else if( subrecord5 == 171 )
1124 {
1125 }
1126
1127 if( subrecord5 >= 202 )
1128 {
1129 aReader.Skip( 40 );
1130 pad_to_die_length = aReader.ReadKicadUnit();
1131 aReader.Skip( 32 );
1132 pad_to_die_delay = KiROUND( aReader.Read<double>() * 1e18 );
1133 }
1134
1135 aReader.SkipSubrecord();
1136
1137 // Subrecord 6
1138 size_t subrecord6 = aReader.ReadAndSetSubrecordLength();
1139 // Known lengths: 596, 628, 651
1140 // 596 is the number of bytes read in this code-block
1141 if( subrecord6 >= 596 )
1142 {
1143 sizeAndShape = std::make_unique<APAD6_SIZE_AND_SHAPE>();
1144
1145 for( wxSize& size : sizeAndShape->inner_size )
1146 size.x = aReader.ReadKicadUnitX();
1147
1148 for( wxSize& size : sizeAndShape->inner_size )
1149 size.y = aReader.ReadKicadUnitY();
1150
1151 for( ALTIUM_PAD_SHAPE& shape : sizeAndShape->inner_shape )
1152 shape = static_cast<ALTIUM_PAD_SHAPE>( aReader.Read<uint8_t>() );
1153
1154 aReader.Skip( 1 );
1155
1156 sizeAndShape->holeshape = static_cast<ALTIUM_PAD_HOLE_SHAPE>( aReader.Read<uint8_t>() );
1157 sizeAndShape->slotsize = aReader.ReadKicadUnit();
1158 sizeAndShape->slotrotation = aReader.Read<double>();
1159
1160 for( VECTOR2I& pt : sizeAndShape->holeoffset )
1161 pt.x = aReader.ReadKicadUnitX();
1162
1163 for( VECTOR2I& pt : sizeAndShape->holeoffset )
1164 pt.y = aReader.ReadKicadUnitY();
1165
1166 aReader.Skip( 1 );
1167
1168 for( ALTIUM_PAD_SHAPE_ALT& shape : sizeAndShape->alt_shape )
1169 shape = static_cast<ALTIUM_PAD_SHAPE_ALT>( aReader.Read<uint8_t>() );
1170
1171 for( uint8_t& radius : sizeAndShape->cornerradius )
1172 radius = aReader.Read<uint8_t>();
1173 }
1174 else if( subrecord6 != 0 )
1175 {
1176 wxLogError( _( "Pads6 stream has unexpected length for subrecord 6: %d." ), subrecord6 );
1177 }
1178
1180
1181 aReader.SkipSubrecord();
1182
1183 if( aReader.HasParsingError() )
1184 THROW_IO_ERROR( wxT( "Pads6 stream was not parsed correctly" ) );
1185}
1186
1188{
1189 ALTIUM_RECORD recordtype = static_cast<ALTIUM_RECORD>( aReader.Read<uint8_t>() );
1190
1191 if( recordtype != ALTIUM_RECORD::VIA )
1192 THROW_IO_ERROR( wxT( "Vias6 stream has invalid recordtype" ) );
1193
1194 // Subrecord 1
1195 size_t subrecord1 = aReader.ReadAndSetSubrecordLength();
1196
1197 aReader.Skip( 1 );
1198 uint8_t flags1 = aReader.Read<uint8_t>();
1199 is_test_fab_top = ( flags1 & 0x80 ) != 0;
1200 is_tent_bottom = ( flags1 & 0x40 ) != 0;
1201 is_tent_top = ( flags1 & 0x20 ) != 0;
1202 is_locked = ( flags1 & 0x04 ) == 0;
1203
1204 uint8_t flags2 = aReader.Read<uint8_t>();
1205 is_test_fab_bottom = ( flags2 & 0x01 ) != 0;
1206
1207 net = aReader.Read<uint16_t>();
1208 aReader.Skip( 8 );
1209 position = aReader.ReadVector2IPos();
1210 diameter = aReader.ReadKicadUnit();
1211 holesize = aReader.ReadKicadUnit();
1212
1213 layer_start = static_cast<ALTIUM_LAYER>( aReader.Read<uint8_t>() );
1214 layer_end = static_cast<ALTIUM_LAYER>( aReader.Read<uint8_t>() );
1215
1216 if( subrecord1 <= 74 )
1217 {
1219 }
1220 else
1221 {
1222 uint8_t temp_byte = aReader.Read<uint8_t>(); // Unknown.
1223
1225 thermal_relief_conductorcount = aReader.Read<uint8_t>();
1226 aReader.Skip( 1 ); // Unknown.
1227
1229
1230 aReader.ReadKicadUnit(); // Unknown. 20mil?
1231 aReader.ReadKicadUnit(); // Unknown. 20mil?
1232
1233 aReader.Skip( 4 );
1235
1236 // Records that don't carry an explicit back expansion mirror the front value.
1238
1239 aReader.Skip( 8 );
1240
1241 temp_byte = aReader.Read<uint8_t>();
1242 soldermask_expansion_manual = temp_byte & 0x02;
1243
1244 soldermask_expansion_from_hole = aReader.Read<uint8_t>() & 0x01;
1245
1246 aReader.Skip( 6 );
1247
1248 viamode = static_cast<ALTIUM_PAD_MODE>( aReader.Read<uint8_t>() );
1249
1250 for( int ii = 0; ii < 32; ++ii )
1251 {
1252 diameter_by_layer[ii] = aReader.ReadKicadUnit();
1253 }
1254 }
1255
1256 if( subrecord1 >= 246 )
1257 {
1258 aReader.Skip( 38 );
1259 soldermask_expansion_linked = aReader.Read<uint8_t>() & 0x01;
1261
1264 }
1265
1266 if( subrecord1 >= 307 )
1267 {
1268 aReader.Skip( 45 );
1269
1270 pos_tolerance = aReader.ReadKicadUnit();
1271 neg_tolerance = aReader.ReadKicadUnit();
1272 }
1273
1274 aReader.SkipSubrecord();
1275
1276 if( aReader.HasParsingError() )
1277 THROW_IO_ERROR( wxT( "Vias6 stream was not parsed correctly" ) );
1278}
1279
1281{
1282 ALTIUM_RECORD recordtype = static_cast<ALTIUM_RECORD>( aReader.Read<uint8_t>() );
1283
1284 if( recordtype != ALTIUM_RECORD::TRACK )
1285 THROW_IO_ERROR( wxT( "Tracks6 stream has invalid recordtype" ) );
1286
1287 // Subrecord 1
1288 aReader.ReadAndSetSubrecordLength();
1289
1290 layer_v6 = static_cast<ALTIUM_LAYER>( aReader.Read<uint8_t>() );
1291
1292 uint8_t flags1 = aReader.Read<uint8_t>();
1293 is_locked = ( flags1 & 0x04 ) == 0;
1294 is_polygonoutline = ( flags1 & 0x02 ) != 0;
1295
1296 uint8_t flags2 = aReader.Read<uint8_t>();
1297 is_keepout = flags2 == 2;
1298
1299 net = aReader.Read<uint16_t>();
1300 polygon = aReader.Read<uint16_t>();
1301 component = aReader.Read<uint16_t>();
1302 aReader.Skip( 4 );
1303 start = aReader.ReadVector2IPos();
1304 end = aReader.ReadVector2IPos();
1305 width = aReader.ReadKicadUnit();
1306 subpolyindex = aReader.Read<uint16_t>();
1307 aReader.Skip( 1 );
1308
1309 int remaining = aReader.GetRemainingSubrecordBytes();
1310
1311 if( remaining >= 9 )
1312 {
1313 aReader.Skip( 5 );
1314 layer_v7 = static_cast<ALTIUM_LAYER>( aReader.Read<uint32_t>() );
1315 }
1316
1317 if( remaining >= 10 )
1318 keepoutrestrictions = aReader.Read<uint8_t>();
1319 else
1320 keepoutrestrictions = is_keepout ? 0x1F : 0;
1321
1323
1324 aReader.SkipSubrecord();
1325
1326 if( aReader.HasParsingError() )
1327 THROW_IO_ERROR( wxT( "Tracks6 stream was not parsed correctly" ) );
1328}
1329
1330ATEXT6::ATEXT6( ALTIUM_BINARY_PARSER& aReader, std::map<uint32_t, wxString>& aStringTable )
1331{
1332 ALTIUM_RECORD recordtype = static_cast<ALTIUM_RECORD>( aReader.Read<uint8_t>() );
1333
1334 if( recordtype != ALTIUM_RECORD::TEXT )
1335 THROW_IO_ERROR( wxT( "Texts6 stream has invalid recordtype" ) );
1336
1337 // Subrecord 1 - Properties
1338 size_t subrecord1 = aReader.ReadAndSetSubrecordLength();
1339
1340 layer_v6 = static_cast<ALTIUM_LAYER>( aReader.Read<uint8_t>() );
1341 aReader.Skip( 6 );
1342 component = aReader.Read<uint16_t>();
1343 aReader.Skip( 4 );
1344 position = aReader.ReadVector2IPos();
1345 height = aReader.ReadKicadUnit();
1346 strokefonttype = static_cast<STROKE_FONT_TYPE>( aReader.Read<uint16_t>() );
1347 // TODO: The Serif font type doesn't match well with KiCad, we should replace it with a better match
1348
1349 rotation = aReader.Read<double>();
1350 isMirrored = aReader.Read<uint8_t>() != 0;
1351 strokewidth = aReader.ReadKicadUnit();
1352
1353 if( subrecord1 < 123 )
1354 {
1356 aReader.SkipSubrecord();
1357 return;
1358 }
1359
1360 isComment = aReader.Read<uint8_t>() != 0;
1361 isDesignator = aReader.Read<uint8_t>() != 0;
1362 aReader.Skip( 1 );
1363 fonttype = static_cast<ALTIUM_TEXT_TYPE>( aReader.Read<uint8_t>() );
1364 isBold = aReader.Read<uint8_t>() != 0;
1365 isItalic = aReader.Read<uint8_t>() != 0;
1366
1367 char fontData[64] = { 0 };
1368 aReader.ReadBytes( fontData, sizeof( fontData ) );
1369 fontname = wxString( fontData, wxMBConvUTF16LE(), sizeof( fontData ) ).BeforeFirst( '\0' );
1370
1371 char tmpbyte = aReader.Read<uint8_t>();
1372 isInverted = !!tmpbyte;
1373 margin_border_width = aReader.ReadKicadUnit(); // "Margin Border"
1374 widestring_index = aReader.Read<uint32_t>();
1375 aReader.Skip( 4 );
1376
1377 // An inverted rect in Altium is like a text box with the text inverted.
1378 isInvertedRect = aReader.Read<uint8_t>() != 0;
1379
1382 textbox_rect_justification = static_cast<ALTIUM_TEXT_POSITION>( aReader.Read<uint8_t>() );
1383 text_offset_width = aReader.ReadKicadUnit(); // "Text Offset"
1384
1385 int remaining = aReader.GetRemainingSubrecordBytes();
1386
1387 if( remaining >= 93 )
1388 {
1389 VECTOR2I unk_vec = aReader.ReadVector2ISize();
1390 wxLogTrace( traceAltiumImport, " Unk vec: %d, %d\n", unk_vec.x, unk_vec.y );
1391
1392 barcode_margin = aReader.ReadVector2ISize();
1393
1394 int32_t unk32 = aReader.ReadKicadUnit();
1395 wxLogTrace( traceAltiumImport, " Unk32: %d\n", unk32 );
1396
1397 barcode_type = static_cast<ALTIUM_BARCODE_TYPE>( aReader.Read<uint8_t>() );
1398 uint8_t unk8 = aReader.Read<uint8_t>();
1399 wxLogTrace( traceAltiumImport, " Unk8: %u\n", unk8 );
1400
1401 barcode_inverted = aReader.Read<uint8_t>() != 0;
1402 fonttype = static_cast<ALTIUM_TEXT_TYPE>( aReader.Read<uint8_t>() );
1403
1404 aReader.ReadBytes( fontData, sizeof( fontData ) );
1405 barcode_fontname = wxString( fontData, wxMBConvUTF16LE(), sizeof( fontData ) ).BeforeFirst( '\0' );
1406
1407 aReader.Read<uint8_t>();
1408 wxLogTrace( traceAltiumImport, " Unk8_2: %u\n", unk8 );
1409
1410 layer_v7 = static_cast<ALTIUM_LAYER>( aReader.Read<uint32_t>() );
1411 }
1412
1413 if( remaining >= 103 )
1414 {
1415 // "Frame" text type flag
1416 isFrame = aReader.Read<uint8_t>() != 0;
1417
1418 // Use "Offset" border value instead of "Margin"
1419 isOffsetBorder = aReader.Read<uint8_t>() != 0;
1420
1421 for( size_t ii = 0; ii < 8; ++ii )
1422 {
1423 uint8_t temp = aReader.Peek<uint8_t>();
1424 uint32_t temp32 = ii < 3 ? ALTIUM_PROPS_UTILS::ConvertToKicadUnit( aReader.Peek<uint32_t>() ) : 0;
1425 wxLogTrace( traceAltiumImport, "3ATEXT6 %zu:\t Byte:%u, Kicad:%u\n", ii, temp, temp32 );
1426 aReader.Skip( 1 );
1427 }
1428 }
1429 else
1430 {
1432 isOffsetBorder = false;
1433 }
1434
1435 if( remaining >= 115 )
1436 {
1437 // textbox_rect_justification will be wrong (5) when this flag is unset,
1438 // in that case, we should always use the left bottom justification.
1439 isJustificationValid = aReader.Read<uint8_t>() != 0;
1440 }
1441 else
1442 {
1443 isJustificationValid = false;
1444 }
1445
1446 aReader.SkipSubrecord();
1447
1448 // Subrecord 2 - Legacy 8bit string, max 255 chars, unknown codepage
1449 aReader.ReadAndSetSubrecordLength();
1450
1451 auto entry = aStringTable.find( widestring_index );
1452
1453 if( entry != aStringTable.end() )
1454 text = entry->second;
1455 else
1456 text = aReader.ReadWxString();
1457
1458 // Normalize Windows line endings
1459 text.Replace( wxT( "\r\n" ), wxT( "\n" ) );
1460
1461 aReader.SkipSubrecord();
1462
1463 // Altium only supports inverting truetype fonts
1465 {
1466 isInverted = false;
1467 isInvertedRect = false;
1468 }
1469
1471
1472 if( aReader.HasParsingError() )
1473 THROW_IO_ERROR( wxT( "Texts6 stream was not parsed correctly" ) );
1474}
1475
1477{
1478 ALTIUM_RECORD recordtype = static_cast<ALTIUM_RECORD>( aReader.Read<uint8_t>() );
1479
1480 if( recordtype != ALTIUM_RECORD::FILL )
1481 THROW_IO_ERROR( wxT( "Fills6 stream has invalid recordtype" ) );
1482
1483 // Subrecord 1
1484 aReader.ReadAndSetSubrecordLength();
1485
1486 layer_v6 = static_cast<ALTIUM_LAYER>( aReader.Read<uint8_t>() );
1487
1488 uint8_t flags1 = aReader.Read<uint8_t>();
1489 is_locked = ( flags1 & 0x04 ) == 0;
1490
1491 uint8_t flags2 = aReader.Read<uint8_t>();
1492 is_keepout = flags2 == 2;
1493
1494 net = aReader.Read<uint16_t>();
1495 aReader.Skip( 2 );
1496 component = aReader.Read<uint16_t>();
1497 aReader.Skip( 4 );
1498 pos1 = aReader.ReadVector2IPos();
1499 pos2 = aReader.ReadVector2IPos();
1500 rotation = aReader.Read<double>();
1501
1502 int remaining = aReader.GetRemainingSubrecordBytes();
1503
1504 if( remaining >= 9 )
1505 {
1506 aReader.Skip( 5 );
1507 layer_v7 = static_cast<ALTIUM_LAYER>( aReader.Read<uint32_t>() );
1508 }
1509
1510 if( remaining >= 10 )
1511 keepoutrestrictions = aReader.Read<uint8_t>();
1512 else
1513 keepoutrestrictions = is_keepout ? 0x1F : 0;
1514
1516
1517 aReader.SkipSubrecord();
1518
1519 if( aReader.HasParsingError() )
1520 THROW_IO_ERROR( wxT( "Fills6 stream was not parsed correctly" ) );
1521}
1522
1523AREGION6::AREGION6( ALTIUM_BINARY_PARSER& aReader, bool aExtendedVertices )
1524{
1525 ALTIUM_RECORD recordtype = static_cast<ALTIUM_RECORD>( aReader.Read<uint8_t>() );
1526
1527 if( recordtype != ALTIUM_RECORD::REGION )
1528 THROW_IO_ERROR( wxT( "Regions6 stream has invalid recordtype" ) );
1529
1530 // Subrecord 1
1531 aReader.ReadAndSetSubrecordLength();
1532
1533 layer_v6 = static_cast<ALTIUM_LAYER>( aReader.Read<uint8_t>() );
1534
1535 uint8_t flags1 = aReader.Read<uint8_t>();
1536 is_locked = ( flags1 & 0x04 ) == 0;
1537 is_teardrop = ( flags1 & 0x10 ) != 0;
1538
1539 uint8_t flags2 = aReader.Read<uint8_t>();
1540 is_keepout = flags2 == 2;
1541
1542 net = aReader.Read<uint16_t>();
1543 polygon = aReader.Read<uint16_t>();
1544 component = aReader.Read<uint16_t>();
1545 aReader.Skip( 5 );
1546 holecount = aReader.Read<uint16_t>();
1547 aReader.Skip( 2 );
1548
1549 std::map<wxString, wxString> properties = aReader.ReadProperties();
1550
1551 if( properties.empty() )
1552 THROW_IO_ERROR( wxT( "Regions6 stream has empty properties" ) );
1553
1554 layer_v7 = altium_layer_from_name( ALTIUM_PROPS_UTILS::ReadString( properties, wxT( "V7_LAYER" ), "" ) );
1555
1556 int pkind = ALTIUM_PROPS_UTILS::ReadInt( properties, wxT( "KIND" ), 0 );
1557 bool is_cutout = ALTIUM_PROPS_UTILS::ReadBool( properties, wxT( "ISBOARDCUTOUT" ), false );
1558
1559 is_shapebased = ALTIUM_PROPS_UTILS::ReadBool( properties, wxT( "ISSHAPEBASED" ), false );
1560 keepoutrestrictions = static_cast<uint8_t>(
1561 ALTIUM_PROPS_UTILS::ReadInt( properties, wxT( "KEEPOUTRESTRIC" ), 0x1F ) );
1562
1563 // TODO: this can differ from the other subpolyindex?!
1564 // Note: "the other subpolyindex" is "polygon"
1565 subpolyindex = static_cast<uint16_t>(
1566 ALTIUM_PROPS_UTILS::ReadInt( properties, "SUBPOLYINDEX", ALTIUM_POLYGON_NONE ) );
1567
1568 switch( pkind )
1569 {
1570 case 0:
1571 if( is_cutout )
1572 {
1574 }
1575 else
1576 {
1578 }
1579
1580 break;
1581
1582 case 1:
1584 break;
1585
1586 case 2:
1588 break;
1589
1590 case 3:
1591 kind = ALTIUM_REGION_KIND::UNKNOWN_3; // TODO: what kind is this?
1592 break;
1593
1594 case 4:
1596 break;
1597
1598 default:
1600 break;
1601 }
1602
1603 uint32_t num_outline_vertices = aReader.Read<uint32_t>();
1604
1605 if( aExtendedVertices )
1606 num_outline_vertices++; // Has a closing vertex
1607
1608 for( uint32_t i = 0; i < num_outline_vertices; i++ )
1609 {
1610 if( aExtendedVertices )
1611 {
1612 bool isRound = aReader.Read<uint8_t>() != 0;
1613 VECTOR2I position = aReader.ReadVector2IPos();
1614 VECTOR2I center = aReader.ReadVector2IPos();
1615 int32_t radius = aReader.ReadKicadUnit();
1616 double angle1 = aReader.Read<double>();
1617 double angle2 = aReader.Read<double>();
1618 outline.emplace_back( isRound, radius, angle1, angle2, position, center );
1619 }
1620 else
1621 {
1622 // For some regions the coordinates are stored as double and not as int32_t
1623 int32_t x = ALTIUM_PROPS_UTILS::ConvertToKicadUnit( aReader.Read<double>() );
1624 int32_t y = ALTIUM_PROPS_UTILS::ConvertToKicadUnit( -aReader.Read<double>() );
1625 outline.emplace_back( VECTOR2I( x, y ) );
1626 }
1627 }
1628
1629 holes.resize( holecount );
1630 for( uint16_t k = 0; k < holecount; k++ )
1631 {
1632 uint32_t num_hole_vertices = aReader.Read<uint32_t>();
1633 holes.at( k ).reserve( num_hole_vertices );
1634
1635 for( uint32_t i = 0; i < num_hole_vertices; i++ )
1636 {
1637 int32_t x = ALTIUM_PROPS_UTILS::ConvertToKicadUnit( aReader.Read<double>() );
1638 int32_t y = ALTIUM_PROPS_UTILS::ConvertToKicadUnit( -aReader.Read<double>() );
1639 holes.at( k ).emplace_back( VECTOR2I( x, y ) );
1640 }
1641 }
1642
1644
1645 aReader.SkipSubrecord();
1646
1647 if( aReader.HasParsingError() )
1648 THROW_IO_ERROR( wxT( "Regions6 stream was not parsed correctly" ) );
1649}
bool altiumScopeExprMatchesPolygon(const wxString &aExpr)
Return true if an Altium rule scope expression targets polygon pour primitives (matches InPolygon,...
void altium_parse_polygons(std::map< wxString, wxString > &aProps, std::vector< ALTIUM_VERTICE > &aVertices)
ALTIUM_MECHKIND altium_mechkind_from_name(const wxString &aName)
static std::map< wxString, double > ReadAltiumDielectricLossTangents(const std::map< wxString, wxString > &aProps)
static void ExpectSubrecordLengthAtLeast(const std::string &aStreamType, const std::string &aSubrecordName, size_t aExpectedLength, size_t aActualLength)
Throw an IO_ERROR if the actual length is less than the expected length.
ALTIUM_LAYER altium_layer_from_name(const wxString &aName)
static wxString MakeAltiumDielectricKey(const wxString &aMaterial, int32_t aHeight, double aConst)
static AEXTENDED_PRIMITIVE_INFORMATION_TYPE ReadAltiumExtendedPrimitiveInformationTypeFromProperties(const std::map< wxString, wxString > &aProps, wxString aKey)
ALTIUM_LAYER altium_versioned_layer(ALTIUM_LAYER aV6Layer, ALTIUM_LAYER aV7Layer)
const ARULE6 * selectAltiumPolygonRule(const std::vector< ARULE6 > &aRulesByPriorityAsc)
Select the highest Altium-priority rule whose scope references polygons.
static ALTIUM_MODE ReadAltiumModeFromProperties(const std::map< wxString, wxString > &aProps, wxString aKey)
static std::vector< ABOARD6_LAYER_STACKUP > ReadAltiumStackupFromProperties(const std::map< wxString, wxString > &aProps)
static ALTIUM_RECORD ReadAltiumRecordFromProperties(const std::map< wxString, wxString > &aProps, wxString aKey)
bool altiumViaSideIsTented(bool aTentFlag, bool aManual, bool aFromHole, uint32_t aHoleSize, int32_t aMaskExpansion, int aLandDiameter)
Decide whether one side of an Altium via should be tented when imported into KiCad.
ALTIUM_PAD_SHAPE_ALT
ALTIUM_TEXT_POSITION
ALTIUM_MECHKIND
ALTIUM_PAD_MODE
ALTIUM_TEXT_TYPE
ALTIUM_PAD_SHAPE
ALTIUM_BARCODE_TYPE
ALTIUM_DIMENSION_KIND
ALTIUM_PAD_HOLE_SHAPE
const uint16_t ALTIUM_NET_UNCONNECTED
ALTIUM_CLASS_KIND
const uint16_t ALTIUM_POLYGON_NONE
AEXTENDED_PRIMITIVE_INFORMATION_TYPE
ALTIUM_RECORD
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:990
void Skip(size_t aLength)
size_t GetRemainingSubrecordBytes() const
std::map< wxString, wxString > ReadProperties(std::function< std::map< wxString, wxString >(const std::string &)> handleBinaryData=[](const std::string &) { return std::map< wxString, wxString >();})
int ReadBytes(char *aOut, size_t aSize)
static int ReadInt(const std::map< wxString, wxString > &aProps, const wxString &aKey, int aDefault)
static int32_t ReadKicadUnit(const std::map< wxString, wxString > &aProps, const wxString &aKey, const wxString &aDefault)
static bool ReadBool(const std::map< wxString, wxString > &aProps, const wxString &aKey, bool aDefault)
static wxString ReadUnicodeString(const std::map< wxString, wxString > &aProps, const wxString &aKey, const wxString &aDefault)
static wxString ReadString(const std::map< wxString, wxString > &aProps, const wxString &aKey, const wxString &aDefault)
static double ReadDouble(const std::map< wxString, wxString > &aProps, const wxString &aKey, double aDefault)
static int32_t ConvertToKicadUnit(const double aValue)
#define _(s)
static const wxChar * traceAltiumImport
Flag to enable Altium importer logging.
#define THROW_IO_ERROR(msg)
macro which captures the "call site" values of FILE_, __FUNCTION & LINE
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:400
double startangle
uint16_t component
uint32_t width
ALTIUM_LAYER layer
AARC6(ALTIUM_BINARY_PARSER &aReader)
uint8_t keepoutrestrictions
uint16_t polygon
VECTOR2I center
uint32_t radius
uint16_t net
ALTIUM_LAYER layer_v6
ALTIUM_LAYER layer_v7
bool is_polygonoutline
double endangle
uint16_t subpolyindex
ABOARD6_LAYER_STACKUP(const std::map< wxString, wxString > &aProps, const wxString &aPrefix, uint32_t aLayerIdFallback)
VECTOR2I sheetpos
std::set< wxString > layerNames
ABOARD6(ALTIUM_BINARY_PARSER &aReader)
std::vector< ABOARD6_LAYER_STACKUP > stackup
std::vector< ALTIUM_VERTICE > board_vertices
wxString uniqueid
std::vector< wxString > names
ALTIUM_CLASS_KIND kind
wxString name
ACLASS6(ALTIUM_BINARY_PARSER &aReader)
wxString sourceHierachicalPath
ALTIUM_TEXT_POSITION commentautoposition
ALTIUM_TEXT_POSITION nameautoposition
ACOMPONENT6(ALTIUM_BINARY_PARSER &aReader)
wxString sourcefootprintlibrary
ALTIUM_LAYER layer
wxString sourcelibreference
wxString sourcedesignator
wxString sourceUniqueID
wxString sourcecomponentlibrary
ACOMPONENTBODY6(ALTIUM_BINARY_PARSER &aReader)
ALTIUM_UNIT textunit
uint32_t textlinewidth
ALTIUM_LAYER layer
ALTIUM_LAYER layer_v6
std::vector< VECTOR2I > textPoint
ALTIUM_DIMENSION_KIND kind
ALTIUM_LAYER layer_v7
ADIMENSION6(ALTIUM_BINARY_PARSER &aReader)
std::vector< VECTOR2I > referencePoint
AEXTENDED_PRIMITIVE_INFORMATION_TYPE type
AEXTENDED_PRIMITIVE_INFORMATION(ALTIUM_BINARY_PARSER &aReader)
VECTOR2I pos2
ALTIUM_LAYER layer
uint16_t net
VECTOR2I pos1
double rotation
uint8_t keepoutrestrictions
AFILL6(ALTIUM_BINARY_PARSER &aReader)
ALTIUM_LAYER layer_v6
uint16_t component
ALTIUM_LAYER layer_v7
std::set< wxString > layerNames
std::vector< ABOARD6_LAYER_STACKUP > stackup
ALIBRARY(ALTIUM_BINARY_PARSER &aReader)
double z_offset
int32_t checksum
wxString name
VECTOR3D rotation
AMODEL(ALTIUM_BINARY_PARSER &aReader)
ANET6(ALTIUM_BINARY_PARSER &aReader)
wxString name
double holerotation
int32_t soldermaskexpansionmanual
uint16_t net
ALTIUM_LAYER layer
APAD6(ALTIUM_BINARY_PARSER &aReader)
std::unique_ptr< APAD6_SIZE_AND_SHAPE > sizeAndShape
ALTIUM_LAYER layer_v7
ALTIUM_LAYER tolayer
ALTIUM_PAD_SHAPE topshape
ALTIUM_LAYER layer_v6
ALTIUM_PAD_MODE padmode
ALTIUM_MODE pastemaskexpansionmode
uint32_t holesize
double direction
ALTIUM_MODE soldermaskexpansionmode
wxString name
bool is_test_fab_bottom
int32_t pad_to_die_delay
bool is_tent_bottom
VECTOR2I botsize
ALTIUM_PAD_SHAPE botshape
uint16_t component
VECTOR2I midsize
ALTIUM_PAD_SHAPE midshape
int32_t pastemaskexpansionmanual
ALTIUM_LAYER fromlayer
VECTOR2I position
VECTOR2I topsize
bool is_test_fab_top
int32_t pad_to_die_length
ALTIUM_LAYER layer_v6
int32_t minprimlength
std::vector< ALTIUM_VERTICE > vertices
APOLYGON6(ALTIUM_BINARY_PARSER &aReader)
ALTIUM_LAYER layer_v7
ALTIUM_POLYGON_HATCHSTYLE hatchstyle
ALTIUM_LAYER layer
uint8_t keepoutrestrictions
ALTIUM_LAYER layer
AREGION6(ALTIUM_BINARY_PARSER &aReader, bool aExtendedVertices)
ALTIUM_LAYER layer_v7
uint16_t holecount
ALTIUM_LAYER layer_v6
std::vector< ALTIUM_VERTICE > outline
uint16_t subpolyindex
std::vector< std::vector< ALTIUM_VERTICE > > holes
uint16_t component
uint16_t polygon
ALTIUM_REGION_KIND kind
ALTIUM_RULE_KIND kind
ALTIUM_CONNECT_STYLE polygonconnectStyle
wxString scope1expr
wxString name
int planeclearanceClearance
int32_t polygonconnectReliefconductorwidth
int pastemaskExpansion
ARULE6()=default
wxString scope2expr
int soldermaskExpansion
int32_t polygonconnectAirgapwidth
int polygonconnectReliefentries
uint32_t text_offset_width
VECTOR2I barcode_margin
uint32_t textbox_rect_height
uint16_t component
ALTIUM_LAYER layer_v6
ALTIUM_TEXT_POSITION textbox_rect_justification
bool isInvertedRect
wxString text
uint32_t widestring_index
uint32_t textbox_rect_width
double rotation
uint32_t margin_border_width
uint32_t height
wxString fontname
bool isJustificationValid
ALTIUM_BARCODE_TYPE barcode_type
ALTIUM_LAYER layer
VECTOR2I position
ALTIUM_LAYER layer_v7
ALTIUM_TEXT_TYPE fonttype
STROKE_FONT_TYPE strokefonttype
wxString barcode_fontname
bool isOffsetBorder
ATEXT6(ALTIUM_BINARY_PARSER &aReader, std::map< uint32_t, wxString > &aStringTable)
bool barcode_inverted
uint32_t strokewidth
ALTIUM_LAYER layer_v6
ATRACK6(ALTIUM_BINARY_PARSER &aReader)
uint32_t width
bool is_polygonoutline
ALTIUM_LAYER layer_v7
uint16_t polygon
uint16_t subpolyindex
uint8_t keepoutrestrictions
VECTOR2I start
ALTIUM_LAYER layer
uint16_t component
bool soldermask_expansion_linked
uint32_t thermal_relief_conductorcount
uint32_t diameter
uint32_t neg_tolerance
uint16_t net
VECTOR2I position
int32_t thermal_relief_airgap
bool is_test_fab_top
bool soldermask_expansion_from_hole
int32_t soldermask_expansion_front
uint32_t pos_tolerance
bool is_tent_bottom
bool is_test_fab_bottom
bool soldermask_expansion_manual
ALTIUM_PAD_MODE viamode
AVIA6(ALTIUM_BINARY_PARSER &aReader)
uint32_t thermal_relief_conductorwidth
int32_t soldermask_expansion_back
uint32_t diameter_by_layer[32]
ALTIUM_LAYER layer_start
ALTIUM_LAYER layer_end
uint32_t holesize
VECTOR2I center
int radius
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:687