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