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