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 height = ALTIUM_PROPS_UTILS::ReadKicadUnit( props, wxT( "HEIGHT" ), wxT( "0mil" ) );
698 sourcedesignator = ALTIUM_PROPS_UTILS::ReadString( props, wxT( "SOURCEDESIGNATOR" ), wxT( "" ) );
699
700 sourceUniqueID = ALTIUM_PROPS_UTILS::ReadString( props, wxT( "SOURCEUNIQUEID" ), wxT( "" ) );
701
702 // Remove leading backslash from sourceUniqueID to match schematic component unique IDs
703 if( sourceUniqueID.starts_with( wxT( "\\" ) ) )
705
706 sourceHierachicalPath = ALTIUM_PROPS_UTILS::ReadString( props, wxT( "SOURCEHIERARCHICALPATH" ), wxT( "" ) );
708 ALTIUM_PROPS_UTILS::ReadUnicodeString( props, wxT( "SOURCEFOOTPRINTLIBRARY" ), wxT( "" ) );
709 pattern = ALTIUM_PROPS_UTILS::ReadUnicodeString( props, wxT( "PATTERN" ), wxT( "" ) );
710
711 sourcecomponentlibrary = ALTIUM_PROPS_UTILS::ReadString( props, wxT( "SOURCECOMPONENTLIBRARY" ), wxT( "" ) );
712 sourcelibreference = ALTIUM_PROPS_UTILS::ReadString( props, wxT( "SOURCELIBREFERENCE" ), wxT( "" ) );
713
715 ALTIUM_PROPS_UTILS::ReadInt( props, wxT( "NAMEAUTOPOSITION" ), 0 ) );
717 ALTIUM_PROPS_UTILS::ReadInt( props, wxT( "COMMENTAUTOPOSITION" ), 0 ) );
718
719 if( aReader.HasParsingError() )
720 THROW_IO_ERROR( wxT( "Components6 stream was not parsed correctly" ) );
721}
722
724{
725 aReader.Skip( 2 );
726
727 std::map<wxString, wxString> props = aReader.ReadProperties();
728
729 if( props.empty() )
730 THROW_IO_ERROR( wxT( "Dimensions6 stream has no props" ) );
731
732 layer_v6 = altium_layer_from_name( ALTIUM_PROPS_UTILS::ReadString( props, wxT( "LAYER" ), wxT( "" ) ) );
733 layer_v7 = altium_layer_from_name( ALTIUM_PROPS_UTILS::ReadString( props, wxT( "LAYER_V7" ), wxT( "" ) ) );
734 kind = static_cast<ALTIUM_DIMENSION_KIND>( ALTIUM_PROPS_UTILS::ReadInt( props, wxT( "DIMENSIONKIND" ), 0 ) );
735
736 textformat = ALTIUM_PROPS_UTILS::ReadString( props, wxT( "TEXTFORMAT" ), wxT( "" ) );
737 textprefix = ALTIUM_PROPS_UTILS::ReadString( props, wxT( "TEXTPREFIX" ), wxT( "" ) );
738 textsuffix = ALTIUM_PROPS_UTILS::ReadString( props, wxT( "TEXTSUFFIX" ), wxT( "" ) );
739
740 height = ALTIUM_PROPS_UTILS::ReadKicadUnit( props, wxT( "HEIGHT" ), wxT( "0mil" ) );
741 angle = ALTIUM_PROPS_UTILS::ReadDouble( props, wxT( "ANGLE" ), 0. );
742
743 linewidth = ALTIUM_PROPS_UTILS::ReadKicadUnit( props, wxT( "LINEWIDTH" ), wxT( "10mil" ) );
744 textheight = ALTIUM_PROPS_UTILS::ReadKicadUnit( props, wxT( "TEXTHEIGHT" ), wxT( "10mil" ) );
745 textlinewidth = ALTIUM_PROPS_UTILS::ReadKicadUnit( props, wxT( "TEXTLINEWIDTH" ), wxT( "6mil" ) );
746 textprecision = ALTIUM_PROPS_UTILS::ReadInt( props, wxT( "TEXTPRECISION" ), 2 );
747 textbold = ALTIUM_PROPS_UTILS::ReadBool( props, wxT( "BOLD" ), false );
748 textitalic = ALTIUM_PROPS_UTILS::ReadBool( props, wxT( "ITALIC" ), false );
749 textgap = ALTIUM_PROPS_UTILS::ReadKicadUnit( props, wxT( "TEXTGAP" ), wxT( "10mil" ) );
750
751 arrowsize = ALTIUM_PROPS_UTILS::ReadKicadUnit( props, wxT( "ARROWSIZE" ), wxT( "60mil" ) );
752
753 wxString text_position_raw = ALTIUM_PROPS_UTILS::ReadString( props, wxT( "TEXTPOSITION" ), wxT( "" ) );
754
755 xy1 = VECTOR2I( ALTIUM_PROPS_UTILS::ReadKicadUnit( props, wxT( "X1" ), wxT( "0mil" ) ),
756 -ALTIUM_PROPS_UTILS::ReadKicadUnit( props, wxT( "Y1" ), wxT( "0mil" ) ) );
757
758 int refcount = ALTIUM_PROPS_UTILS::ReadInt( props, wxT( "REFERENCES_COUNT" ), 0 );
759
760 for( int i = 0; i < refcount; i++ )
761 {
762 const std::string refi = "REFERENCE" + std::to_string( i ) + "POINT";
763 const wxString ref( refi );
764 referencePoint.emplace_back( ALTIUM_PROPS_UTILS::ReadKicadUnit( props, ref + wxT( "X" ), wxT( "0mil" ) ),
765 -ALTIUM_PROPS_UTILS::ReadKicadUnit( props, ref + wxT( "Y" ), wxT( "0mil" ) ) );
766 }
767
768 for( size_t i = 1; i < std::numeric_limits<size_t>::max(); i++ )
769 {
770 const std::string texti = "TEXT" + std::to_string( i );
771 const std::string textix = texti + "X";
772 const std::string textiy = texti + "Y";
773
774 if( props.find( textix ) == props.end() || props.find( textiy ) == props.end() )
775 break; // it doesn't seem like we know beforehand how many vertices are inside a polygon
776
777 textPoint.emplace_back( ALTIUM_PROPS_UTILS::ReadKicadUnit( props, textix, wxT( "0mil" ) ),
778 -ALTIUM_PROPS_UTILS::ReadKicadUnit( props, textiy, wxT( "0mil" ) ) );
779 }
780
781 wxString dimensionunit = ALTIUM_PROPS_UTILS::ReadString( props, wxT( "TEXTDIMENSIONUNIT" ), wxT( "Millimeters" ) );
782
783 if( dimensionunit == wxT( "Inches" ) ) textunit = ALTIUM_UNIT::INCH;
784 else if( dimensionunit == wxT( "Mils" ) ) textunit = ALTIUM_UNIT::MILS;
785 else if( dimensionunit == wxT( "Millimeters" ) ) textunit = ALTIUM_UNIT::MM;
786 else if( dimensionunit == wxT( "Centimeters" ) ) textunit = ALTIUM_UNIT::CM;
788
790
791 if( aReader.HasParsingError() )
792 THROW_IO_ERROR( wxT( "Dimensions6 stream was not parsed correctly" ) );
793}
794
796{
797 std::map<wxString, wxString> properties = aReader.ReadProperties();
798
799 if( properties.empty() )
800 THROW_IO_ERROR( wxT( "Model stream has no properties!" ) );
801
802 name = ALTIUM_PROPS_UTILS::ReadString( properties, wxT( "NAME" ), wxT( "" ) );
803 id = ALTIUM_PROPS_UTILS::ReadString( properties, wxT( "ID" ), wxT( "" ) );
804 isEmbedded = ALTIUM_PROPS_UTILS::ReadBool( properties, wxT( "EMBED" ), false );
805
806 rotation.x = ALTIUM_PROPS_UTILS::ReadDouble( properties, wxT( "ROTX" ), 0. );
807 rotation.y = ALTIUM_PROPS_UTILS::ReadDouble( properties, wxT( "ROTY" ), 0. );
808 rotation.z = ALTIUM_PROPS_UTILS::ReadDouble( properties, wxT( "ROTZ" ), 0. );
809
810 z_offset = ALTIUM_PROPS_UTILS::ReadDouble( properties, wxT( "DZ" ), 0. );
811 checksum = ALTIUM_PROPS_UTILS::ReadInt( properties, wxT( "CHECKSUM" ), 0 );
812
813 if( aReader.HasParsingError() )
814 THROW_IO_ERROR( wxT( "Model stream was not parsed correctly" ) );
815}
816
818{
819 std::map<wxString, wxString> properties = aReader.ReadProperties();
820
821 if( properties.empty() )
822 THROW_IO_ERROR( wxT( "Nets6 stream has no properties" ) );
823
824 name = ALTIUM_PROPS_UTILS::ReadString( properties, wxT( "NAME" ), wxT( "" ) );
825
826 if( aReader.HasParsingError() )
827 THROW_IO_ERROR( wxT( "Nets6 stream was not parsed correctly" ) );
828}
829
831{
832 std::map<wxString, wxString> properties = aReader.ReadProperties();
833
834 if( properties.empty() )
835 THROW_IO_ERROR( wxT( "Polygons6 stream has no properties" ) );
836
837 layer_v6 = altium_layer_from_name( ALTIUM_PROPS_UTILS::ReadString( properties, wxT( "LAYER" ), wxT( "" ) ) );
838 layer_v7 = altium_layer_from_name( ALTIUM_PROPS_UTILS::ReadString( properties, wxT( "LAYER_V7" ), wxT( "" ) ) );
839 net = ALTIUM_PROPS_UTILS::ReadInt( properties, wxT( "NET" ), ALTIUM_NET_UNCONNECTED );
840 locked = ALTIUM_PROPS_UTILS::ReadBool( properties, wxT( "LOCKED" ), false );
841
842 // TODO: kind
843
844 gridsize = ALTIUM_PROPS_UTILS::ReadKicadUnit( properties, wxT( "GRIDSIZE" ), wxT( "0mil" ) );
845 trackwidth = ALTIUM_PROPS_UTILS::ReadKicadUnit( properties, wxT( "TRACKWIDTH" ), wxT( "0mil" ) );
846 minprimlength = ALTIUM_PROPS_UTILS::ReadKicadUnit( properties, wxT( "MINPRIMLENGTH" ), wxT( "0mil" ) );
847 useoctagons = ALTIUM_PROPS_UTILS::ReadBool( properties, wxT( "USEOCTAGONS" ), false );
848
849 pourindex = ALTIUM_PROPS_UTILS::ReadInt( properties, wxT( "POURINDEX" ), 0 );
850
851 wxString hatchstyleraw = ALTIUM_PROPS_UTILS::ReadString( properties, wxT( "HATCHSTYLE" ), wxT( "" ) );
852
853 if( hatchstyleraw == wxT( "Solid" ) ) hatchstyle = ALTIUM_POLYGON_HATCHSTYLE::SOLID;
854 else if( hatchstyleraw == wxT( "45Degree" ) ) hatchstyle = ALTIUM_POLYGON_HATCHSTYLE::DEGREE_45;
855 else if( hatchstyleraw == wxT( "90Degree" ) ) hatchstyle = ALTIUM_POLYGON_HATCHSTYLE::DEGREE_90;
856 else if( hatchstyleraw == wxT( "Horizontal" ) ) hatchstyle = ALTIUM_POLYGON_HATCHSTYLE::HORIZONTAL;
857 else if( hatchstyleraw == wxT( "Vertical" ) ) hatchstyle = ALTIUM_POLYGON_HATCHSTYLE::VERTICAL;
858 else if( hatchstyleraw == wxT( "None" ) ) hatchstyle = ALTIUM_POLYGON_HATCHSTYLE::NONE;
860
862
864
865 if( aReader.HasParsingError() )
866 THROW_IO_ERROR( wxT( "Polygons6 stream was not parsed correctly" ) );
867}
868
870{
871 aReader.Skip( 2 );
872
873 std::map<wxString, wxString> props = aReader.ReadProperties();
874
875 if( props.empty() )
876 THROW_IO_ERROR( wxT( "Rules6 stream has no props" ) );
877
878 name = ALTIUM_PROPS_UTILS::ReadString( props, wxT( "NAME" ), wxT( "" ) );
879 priority = ALTIUM_PROPS_UTILS::ReadInt( props, wxT( "PRIORITY" ), 1 );
880 enabled = ALTIUM_PROPS_UTILS::ReadBool( props, wxT( "ENABLED" ), true );
881
882 scope1expr = ALTIUM_PROPS_UTILS::ReadString( props, wxT( "SCOPE1EXPRESSION" ), wxT( "" ) );
883 scope2expr = ALTIUM_PROPS_UTILS::ReadString( props, wxT( "SCOPE2EXPRESSION" ), wxT( "" ) );
884
885 wxString rulekind = ALTIUM_PROPS_UTILS::ReadString( props, wxT( "RULEKIND" ), wxT( "" ) );
886 if( rulekind == wxT( "Clearance" ) )
887 {
889 clearanceGap = ALTIUM_PROPS_UTILS::ReadKicadUnit( props, wxT( "GAP" ), wxT( "10mil" ) );
890 }
891 else if( rulekind == wxT( "BoardOutlineClearance" ) )
892 {
894 clearanceGap = ALTIUM_PROPS_UTILS::ReadKicadUnit( props, wxT( "GAP" ), wxT( "10mil" ) );
895 }
896 else if( rulekind == wxT( "DiffPairsRouting" ) )
897 {
899 }
900 else if( rulekind == wxT( "Height" ) )
901 {
903 }
904 else if( rulekind == wxT( "HoleSize" ) )
905 {
907 minLimit = ALTIUM_PROPS_UTILS::ReadKicadUnit( props, wxT( "MINLIMIT" ), wxT( "1mil" ) );
908 maxLimit = ALTIUM_PROPS_UTILS::ReadKicadUnit( props, wxT( "MAXLIMIT" ), wxT( "150mil" ) );
909 }
910 else if( rulekind == wxT( "HoleToHoleClearance" ) )
911 {
913 clearanceGap = ALTIUM_PROPS_UTILS::ReadKicadUnit( props, wxT( "GAP" ), wxT( "10mil" ) );
914 }
915 else if( rulekind == wxT( "RoutingVias" ) )
916 {
918 width = ALTIUM_PROPS_UTILS::ReadKicadUnit( props, wxT( "WIDTH" ), wxT( "20mil" ) );
919 minWidth = ALTIUM_PROPS_UTILS::ReadKicadUnit( props, wxT( "MINWIDTH" ), wxT( "20mil" ) );
920 maxWidth = ALTIUM_PROPS_UTILS::ReadKicadUnit( props, wxT( "MAXWIDTH" ), wxT( "50mil" ) );
921 holeWidth = ALTIUM_PROPS_UTILS::ReadKicadUnit( props, wxT( "HOLEWIDTH" ), wxT( "10mil" ) );
922 minHoleWidth = ALTIUM_PROPS_UTILS::ReadKicadUnit( props, wxT( "MINHOLEWIDTH" ), wxT( "10mil" ) );
923 maxHoleWidth = ALTIUM_PROPS_UTILS::ReadKicadUnit( props, wxT( "MAXHOLEWIDTH" ), wxT( "28mil" ) );
924 }
925 else if( rulekind == wxT( "Width" ) )
926 {
928 minLimit = ALTIUM_PROPS_UTILS::ReadKicadUnit( props, wxT( "MINLIMIT" ), wxT( "6mil" ) );
929 maxLimit = ALTIUM_PROPS_UTILS::ReadKicadUnit( props, wxT( "MAXLIMIT" ), wxT( "40mil" ) );
930 // Altium writes PREFEREDWIDTH; fall back to the correct spelling in case that ever changes
932 props, wxT( "PREFEREDWIDTH" ),
933 ALTIUM_PROPS_UTILS::ReadString( props, wxT( "PREFERREDWIDTH" ), wxT( "6mil" ) ) );
934 }
935 else if( rulekind == wxT( "PasteMaskExpansion" ) )
936 {
938 pastemaskExpansion = ALTIUM_PROPS_UTILS::ReadKicadUnit( props, wxT( "EXPANSION" ), wxT( "0" ) );
939 }
940 else if( rulekind == wxT( "SolderMaskExpansion" ) )
941 {
943 soldermaskExpansion = ALTIUM_PROPS_UTILS::ReadKicadUnit( props, wxT( "EXPANSION" ), wxT( "4mil" ) );
944 }
945 else if( rulekind == wxT( "PlaneClearance" ) )
946 {
948 planeclearanceClearance = ALTIUM_PROPS_UTILS::ReadKicadUnit( props, wxT( "CLEARANCE" ), wxT( "10mil" ) );
949 }
950 else if( rulekind == wxT( "PolygonConnect" ) )
951 {
953 polygonconnectAirgapwidth = ALTIUM_PROPS_UTILS::ReadKicadUnit( props, wxT( "AIRGAPWIDTH" ), wxT( "10mil" ) );
954 polygonconnectReliefconductorwidth = ALTIUM_PROPS_UTILS::ReadKicadUnit( props, wxT( "RELIEFCONDUCTORWIDTH" ), wxT( "10mil" ) );
955 polygonconnectReliefentries = ALTIUM_PROPS_UTILS::ReadInt( props, wxT( "RELIEFENTRIES" ), 4 );
956
957 wxString style = ALTIUM_PROPS_UTILS::ReadString( props, wxT( "CONNECTSTYLE" ), wxT( "" ) );
958
959 if( style == wxT( "Direct" ) ) polygonconnectStyle = ALTIUM_CONNECT_STYLE::DIRECT;
960 else if( style == wxT( "Relief" ) ) polygonconnectStyle = ALTIUM_CONNECT_STYLE::RELIEF;
961 else if( style == wxT( "NoConnect" ) ) polygonconnectStyle = ALTIUM_CONNECT_STYLE::NONE;
963 }
964 else
965 {
967 }
968
969 if( aReader.HasParsingError() )
970 THROW_IO_ERROR( wxT( "Rules6 stream was not parsed correctly" ) );
971}
972
974{
975 std::map<wxString, wxString> props = aReader.ReadProperties();
976
977 if( props.empty() )
978 THROW_IO_ERROR( wxT( "SmartUnions stream has no props" ) );
979
980 unionindex = ALTIUM_PROPS_UTILS::ReadInt( props, wxT( "UNIONINDEX" ), 0 );
981 is_tuning = ALTIUM_PROPS_UTILS::ReadBool( props, wxT( "TRACETUNINGVALID" ), false );
982
983 // A tuned differential pair names its pair here; single-track tuning leaves it empty.
984 is_diffpair = !ALTIUM_PROPS_UTILS::ReadString( props, wxT( "TUNEDOBJECTDIFFPAIR" ),
985 wxT( "" ) ).IsEmpty();
986
987 style = ALTIUM_PROPS_UTILS::ReadInt( props, wxT( "STYLE" ), 0 );
988 gap = ALTIUM_PROPS_UTILS::ReadKicadUnit( props, wxT( "GAP" ), wxT( "0mil" ) );
989 amplitude = ALTIUM_PROPS_UTILS::ReadKicadUnit( props, wxT( "AMPLITUDE" ), wxT( "0mil" ) );
990 minamplitude = ALTIUM_PROPS_UTILS::ReadKicadUnit( props, wxT( "MINAMPLITUDE" ), wxT( "0mil" ) );
991 mitterradiusratio = ALTIUM_PROPS_UTILS::ReadDouble( props, wxT( "MITTERRADIUSRATIO" ), 0.0 );
992 singleside = ALTIUM_PROPS_UTILS::ReadBool( props, wxT( "SINGLESIDE" ), false );
993
994 // The un-meandered route the accordion replaced, as consecutive LINE<n> segments. Nothing
995 // else records it, so it is the only source for KiCad's tuning-pattern baseline
996 auto readBaseline = [&props]( const wxString& aPrefix, std::vector<VECTOR2I>& aOut )
997 {
998 for( int i = 0;; ++i )
999 {
1000 wxString prefix = wxString::Format( wxT( "%s%d." ), aPrefix, i );
1001 wxString firstX = prefix + wxT( "X1" );
1002
1003 if( props.find( firstX ) == props.end() )
1004 break;
1005
1006 VECTOR2I a( ALTIUM_PROPS_UTILS::ReadKicadUnit( props, firstX, wxT( "0mil" ) ),
1007 -ALTIUM_PROPS_UTILS::ReadKicadUnit( props, prefix + wxT( "Y1" ), wxT( "0mil" ) ) );
1008 VECTOR2I b( ALTIUM_PROPS_UTILS::ReadKicadUnit( props, prefix + wxT( "X2" ), wxT( "0mil" ) ),
1009 -ALTIUM_PROPS_UTILS::ReadKicadUnit( props, prefix + wxT( "Y2" ), wxT( "0mil" ) ) );
1010
1011 for( const VECTOR2I& pt : { a, b } )
1012 {
1013 if( aOut.empty() || aOut.back() != pt )
1014 aOut.push_back( pt );
1015 }
1016 }
1017 };
1018
1019 readBaseline( wxT( "LINE" ), baseline );
1020 readBaseline( wxT( "LINEOTHER" ), baselinecoupled );
1021
1022 if( aReader.HasParsingError() )
1023 THROW_IO_ERROR( wxT( "SmartUnions stream was not parsed correctly" ) );
1024}
1025
1027{
1028 ALTIUM_RECORD recordtype = static_cast<ALTIUM_RECORD>( aReader.Read<uint8_t>() );
1029 if( recordtype != ALTIUM_RECORD::ARC )
1030 {
1031 THROW_IO_ERROR( wxT( "Arcs6 stream has invalid recordtype" ) );
1032 }
1033
1034 // Subrecord 1
1035 aReader.ReadAndSetSubrecordLength();
1036
1037 layer_v6 = static_cast<ALTIUM_LAYER>( aReader.Read<uint8_t>() );
1038
1039 uint8_t flags1 = aReader.Read<uint8_t>();
1040 is_locked = ( flags1 & 0x04 ) == 0;
1041 is_polygonoutline = ( flags1 & 0x02 ) != 0;
1042
1043 uint8_t flags2 = aReader.Read<uint8_t>();
1044 is_keepout = flags2 == 2;
1045
1046 net = aReader.Read<uint16_t>();
1047 polygon = aReader.Read<uint16_t>();
1048 component = aReader.Read<uint16_t>();
1049 aReader.Skip( 4 );
1050 center = aReader.ReadVector2IPos();
1051 radius = aReader.ReadKicadUnit();
1052 startangle = aReader.Read<double>();
1053 endangle = aReader.Read<double>();
1054 width = aReader.ReadKicadUnit();
1055 subpolyindex = aReader.Read<uint16_t>();
1056
1057 int remaining = aReader.GetRemainingSubrecordBytes();
1058
1059 if( remaining >= 9 )
1060 {
1061 aReader.Skip( 1 );
1062 unionindex = aReader.Read<uint32_t>();
1063 layer_v7 = static_cast<ALTIUM_LAYER>( aReader.Read<uint32_t>() );
1064 }
1065
1066 if( remaining >= 10 )
1067 keepoutrestrictions = aReader.Read<uint8_t>();
1068 else
1070
1072
1073 aReader.SkipSubrecord();
1074
1075 if( aReader.HasParsingError() )
1076 {
1077 THROW_IO_ERROR( wxT( "Arcs6 stream was not parsed correctly" ) );
1078 }
1079}
1080
1082{
1083 ALTIUM_RECORD recordtype = static_cast<ALTIUM_RECORD>( aReader.Read<uint8_t>() );
1084
1085 if( recordtype != ALTIUM_RECORD::MODEL )
1086 THROW_IO_ERROR( wxT( "ComponentsBodies6 stream has invalid recordtype" ) );
1087
1088 aReader.ReadAndSetSubrecordLength();
1089
1090 aReader.Skip( 7 );
1091 component = aReader.Read<uint16_t>();
1092 aReader.Skip( 9 );
1093
1094 std::map<wxString, wxString> properties = aReader.ReadProperties();
1095
1096 if( properties.empty() )
1097 THROW_IO_ERROR( wxT( "ComponentsBodies6 stream has no properties" ) );
1098
1099 modelName = ALTIUM_PROPS_UTILS::ReadString( properties, wxT( "MODEL.NAME" ), wxT( "" ) );
1100 modelId = ALTIUM_PROPS_UTILS::ReadString( properties, wxT( "MODELID" ), wxT( "" ) );
1101 modelIsEmbedded = ALTIUM_PROPS_UTILS::ReadBool( properties, wxT( "MODEL.EMBED" ), false );
1102
1103 modelPosition.x = ALTIUM_PROPS_UTILS::ReadKicadUnit( properties, wxT( "MODEL.2D.X" ), wxT( "0mil" ) );
1104 modelPosition.y = -ALTIUM_PROPS_UTILS::ReadKicadUnit( properties, wxT( "MODEL.2D.Y" ), wxT( "0mil" ) );
1105 modelPosition.z = ALTIUM_PROPS_UTILS::ReadKicadUnit( properties, wxT( "MODEL.3D.DZ" ), wxT( "0mil" ) );
1106
1107 modelRotation.x = ALTIUM_PROPS_UTILS::ReadDouble( properties, wxT( "MODEL.3D.ROTX" ), 0. );
1108 modelRotation.y = ALTIUM_PROPS_UTILS::ReadDouble( properties, wxT( "MODEL.3D.ROTY" ), 0. );
1109 modelRotation.z = ALTIUM_PROPS_UTILS::ReadDouble( properties, wxT( "MODEL.3D.ROTZ" ), 0. );
1110
1111 rotation = ALTIUM_PROPS_UTILS::ReadDouble( properties, wxT( "MODEL.2D.ROTATION" ), 0. );
1112
1113 body_opacity_3d = ALTIUM_PROPS_UTILS::ReadDouble( properties, wxT( "BODYOPACITY3D" ), 1. );
1114 body_projection = ALTIUM_PROPS_UTILS::ReadInt( properties, wxT( "BODYPROJECTION" ), 0 );
1115
1116 aReader.SkipSubrecord();
1117
1118 if( aReader.HasParsingError() )
1119 THROW_IO_ERROR( wxT( "Components6 stream was not parsed correctly" ) );
1120}
1121
1123{
1124 ALTIUM_RECORD recordtype = static_cast<ALTIUM_RECORD>( aReader.Read<uint8_t>() );
1125
1126 if( recordtype != ALTIUM_RECORD::PAD )
1127 THROW_IO_ERROR( wxT( "Pads6 stream has invalid recordtype" ) );
1128
1129 // Subrecord 1
1130 size_t subrecord1 = aReader.ReadAndSetSubrecordLength();
1131
1132 if( subrecord1 == 0 )
1133 THROW_IO_ERROR( wxT( "Pads6 stream has no subrecord1 data" ) );
1134
1135 name = aReader.ReadWxString();
1136
1137 if( aReader.GetRemainingSubrecordBytes() != 0 )
1138 THROW_IO_ERROR( wxT( "Pads6 stream has invalid subrecord1 length" ) );
1139
1140 aReader.SkipSubrecord();
1141
1142 // Subrecord 2
1143 aReader.ReadAndSetSubrecordLength();
1144 aReader.SkipSubrecord();
1145
1146 // Subrecord 3
1147 aReader.ReadAndSetSubrecordLength();
1148 aReader.SkipSubrecord();
1149
1150 // Subrecord 4
1151 aReader.ReadAndSetSubrecordLength();
1152 aReader.SkipSubrecord();
1153
1154 // Subrecord 5
1155 size_t subrecord5 = aReader.ReadAndSetSubrecordLength();
1156
1157 ExpectSubrecordLengthAtLeast( "Pads6", "subrecord5", 110, subrecord5 );
1158
1159 layer_v6 = static_cast<ALTIUM_LAYER>( aReader.Read<uint8_t>() );
1162
1163 uint8_t flags1 = aReader.Read<uint8_t>();
1164 is_test_fab_top = ( flags1 & 0x80 ) != 0;
1165 is_tent_bottom = ( flags1 & 0x40 ) != 0;
1166 is_tent_top = ( flags1 & 0x20 ) != 0;
1167 is_locked = ( flags1 & 0x04 ) == 0;
1168
1169 uint8_t flags2 = aReader.Read<uint8_t>();
1170 is_test_fab_bottom = ( flags2 & 0x01 ) != 0;
1171
1172 net = aReader.Read<uint16_t>();
1173 aReader.Skip( 2 );
1174 component = aReader.Read<uint16_t>();
1175 aReader.Skip( 4 ); // to 13
1176
1177 position = aReader.ReadVector2IPos();
1178 topsize = aReader.ReadVector2ISize();
1179 midsize = aReader.ReadVector2ISize();
1180 botsize = aReader.ReadVector2ISize();
1181 holesize = aReader.ReadKicadUnit(); // to 49
1182
1183 topshape = static_cast<ALTIUM_PAD_SHAPE>( aReader.Read<uint8_t>() );
1184 midshape = static_cast<ALTIUM_PAD_SHAPE>( aReader.Read<uint8_t>() );
1185 botshape = static_cast<ALTIUM_PAD_SHAPE>( aReader.Read<uint8_t>() );
1186
1187 direction = aReader.Read<double>();
1188 plated = aReader.Read<uint8_t>() != 0;
1189 aReader.Skip( 1 );
1190 padmode = static_cast<ALTIUM_PAD_MODE>( aReader.Read<uint8_t>() );
1191 aReader.Skip( 23 );
1194 aReader.Skip( 7 );
1195 pastemaskexpansionmode = static_cast<ALTIUM_MODE>( aReader.Read<uint8_t>() );
1196 soldermaskexpansionmode = static_cast<ALTIUM_MODE>( aReader.Read<uint8_t>() );
1197 aReader.Skip( 3 ); // to 106
1198
1200 pad_to_die_delay = 0;
1201
1202 if( subrecord5 == 110 )
1203 {
1204 // Don't know exactly what this is, but it's always been 0 in the files with 110-byte subrecord5.
1205 // e.g. https://gitlab.com/kicad/code/kicad/-/issues/16514
1206 const uint32_t unknown = aReader.ReadKicadUnit(); // to 110
1207
1208 if( unknown != 0 )
1209 THROW_IO_ERRORF( wxT( "Pads6 stream subrecord5 + 106 has value %d, which is unexpected" ), unknown );
1210
1211 holerotation = 0;
1212 }
1213 else
1214 {
1215 // More than 110, need at least 114
1216 ExpectSubrecordLengthAtLeast( "Pads6", "subrecord5", 114, subrecord5 );
1217 holerotation = aReader.Read<double>(); // to 114
1218 }
1219
1220 if( subrecord5 >= 120 )
1221 {
1222 tolayer = static_cast<ALTIUM_LAYER>( aReader.Read<uint8_t>() );
1223 aReader.Skip( 2 );
1224 fromlayer = static_cast<ALTIUM_LAYER>( aReader.Read<uint8_t>() );
1225 //aReader.skip( 2 );
1226 }
1227 else if( subrecord5 == 171 )
1228 {
1229 }
1230
1231 if( subrecord5 >= 202 )
1232 {
1233 aReader.Skip( 40 );
1234 pad_to_die_length = aReader.ReadKicadUnit();
1235 aReader.Skip( 32 );
1236 pad_to_die_delay = KiROUND( aReader.Read<double>() * 1e18 );
1237 }
1238
1239 aReader.SkipSubrecord();
1240
1241 // Subrecord 6
1242 size_t subrecord6 = aReader.ReadAndSetSubrecordLength();
1243 // Known lengths: 596, 628, 651
1244 // 596 is the number of bytes read in this code-block
1245 if( subrecord6 >= 596 )
1246 {
1247 sizeAndShape = std::make_unique<APAD6_SIZE_AND_SHAPE>();
1248
1249 for( wxSize& size : sizeAndShape->inner_size )
1250 size.x = aReader.ReadKicadUnitX();
1251
1252 for( wxSize& size : sizeAndShape->inner_size )
1253 size.y = aReader.ReadKicadUnitY();
1254
1255 for( ALTIUM_PAD_SHAPE& shape : sizeAndShape->inner_shape )
1256 shape = static_cast<ALTIUM_PAD_SHAPE>( aReader.Read<uint8_t>() );
1257
1258 aReader.Skip( 1 );
1259
1260 sizeAndShape->holeshape = static_cast<ALTIUM_PAD_HOLE_SHAPE>( aReader.Read<uint8_t>() );
1261 sizeAndShape->slotsize = aReader.ReadKicadUnit();
1262 sizeAndShape->slotrotation = aReader.Read<double>();
1263
1264 for( VECTOR2I& pt : sizeAndShape->holeoffset )
1265 pt.x = aReader.ReadKicadUnitX();
1266
1267 for( VECTOR2I& pt : sizeAndShape->holeoffset )
1268 pt.y = aReader.ReadKicadUnitY();
1269
1270 aReader.Skip( 1 );
1271
1272 for( ALTIUM_PAD_SHAPE_ALT& shape : sizeAndShape->alt_shape )
1273 shape = static_cast<ALTIUM_PAD_SHAPE_ALT>( aReader.Read<uint8_t>() );
1274
1275 for( uint8_t& radius : sizeAndShape->cornerradius )
1276 radius = aReader.Read<uint8_t>();
1277 }
1278 else if( subrecord6 != 0 )
1279 {
1280 wxLogTrace( traceAltiumIo, wxT( "Pads6 stream has unexpected length for subrecord 6: %d." ), subrecord6 );
1281 }
1282
1284
1285 aReader.SkipSubrecord();
1286
1287 if( aReader.HasParsingError() )
1288 THROW_IO_ERROR( wxT( "Pads6 stream was not parsed correctly" ) );
1289}
1290
1292{
1293 ALTIUM_RECORD recordtype = static_cast<ALTIUM_RECORD>( aReader.Read<uint8_t>() );
1294
1295 if( recordtype != ALTIUM_RECORD::VIA )
1296 THROW_IO_ERROR( wxT( "Vias6 stream has invalid recordtype" ) );
1297
1298 // Subrecord 1
1299 size_t subrecord1 = aReader.ReadAndSetSubrecordLength();
1300
1301 aReader.Skip( 1 );
1302 uint8_t flags1 = aReader.Read<uint8_t>();
1303 is_test_fab_top = ( flags1 & 0x80 ) != 0;
1304 is_tent_bottom = ( flags1 & 0x40 ) != 0;
1305 is_tent_top = ( flags1 & 0x20 ) != 0;
1306 is_locked = ( flags1 & 0x04 ) == 0;
1307
1308 uint8_t flags2 = aReader.Read<uint8_t>();
1309 is_test_fab_bottom = ( flags2 & 0x01 ) != 0;
1310
1311 net = aReader.Read<uint16_t>();
1312 aReader.Skip( 2 ); // polygon
1313 component = aReader.Read<uint16_t>();
1314 aReader.Skip( 4 );
1315 position = aReader.ReadVector2IPos();
1316 diameter = aReader.ReadKicadUnit();
1317 holesize = aReader.ReadKicadUnit();
1318
1319 layer_start = static_cast<ALTIUM_LAYER>( aReader.Read<uint8_t>() );
1320 layer_end = static_cast<ALTIUM_LAYER>( aReader.Read<uint8_t>() );
1321
1322 if( subrecord1 <= 74 )
1323 {
1325 }
1326 else
1327 {
1328 uint8_t temp_byte = aReader.Read<uint8_t>(); // Unknown.
1329
1331 thermal_relief_conductorcount = aReader.Read<uint8_t>();
1332 aReader.Skip( 1 ); // Unknown.
1333
1335
1336 aReader.ReadKicadUnit(); // Unknown. 20mil?
1337 aReader.ReadKicadUnit(); // Unknown. 20mil?
1338
1339 aReader.Skip( 4 );
1341
1342 // Records that don't carry an explicit back expansion mirror the front value.
1344
1345 aReader.Skip( 8 );
1346
1347 temp_byte = aReader.Read<uint8_t>();
1348 soldermask_expansion_manual = temp_byte & 0x02;
1349
1350 soldermask_expansion_from_hole = aReader.Read<uint8_t>() & 0x01;
1351
1352 aReader.Skip( 6 );
1353
1354 viamode = static_cast<ALTIUM_PAD_MODE>( aReader.Read<uint8_t>() );
1355
1356 for( int ii = 0; ii < 32; ++ii )
1357 {
1358 diameter_by_layer[ii] = aReader.ReadKicadUnit();
1359 }
1360 }
1361
1362 if( subrecord1 >= 246 )
1363 {
1364 aReader.Skip( 38 );
1365 soldermask_expansion_linked = aReader.Read<uint8_t>() & 0x01;
1367
1370 }
1371
1372 if( subrecord1 >= 307 )
1373 {
1374 aReader.Skip( 45 );
1375
1376 pos_tolerance = aReader.ReadKicadUnit();
1377 neg_tolerance = aReader.ReadKicadUnit();
1378 }
1379
1380 aReader.SkipSubrecord();
1381
1382 if( aReader.HasParsingError() )
1383 THROW_IO_ERROR( wxT( "Vias6 stream was not parsed correctly" ) );
1384}
1385
1387{
1388 ALTIUM_RECORD recordtype = static_cast<ALTIUM_RECORD>( aReader.Read<uint8_t>() );
1389
1390 if( recordtype != ALTIUM_RECORD::TRACK )
1391 THROW_IO_ERROR( wxT( "Tracks6 stream has invalid recordtype" ) );
1392
1393 // Subrecord 1
1394 aReader.ReadAndSetSubrecordLength();
1395
1396 layer_v6 = static_cast<ALTIUM_LAYER>( aReader.Read<uint8_t>() );
1397
1398 uint8_t flags1 = aReader.Read<uint8_t>();
1399 is_locked = ( flags1 & 0x04 ) == 0;
1400 is_polygonoutline = ( flags1 & 0x02 ) != 0;
1401
1402 uint8_t flags2 = aReader.Read<uint8_t>();
1403 is_keepout = flags2 == 2;
1404
1405 net = aReader.Read<uint16_t>();
1406 polygon = aReader.Read<uint16_t>();
1407 component = aReader.Read<uint16_t>();
1408 aReader.Skip( 4 );
1409 start = aReader.ReadVector2IPos();
1410 end = aReader.ReadVector2IPos();
1411 width = aReader.ReadKicadUnit();
1412 subpolyindex = aReader.Read<uint16_t>();
1413 aReader.Skip( 1 );
1414
1415 int remaining = aReader.GetRemainingSubrecordBytes();
1416
1417 if( remaining >= 9 )
1418 {
1419 unionindex = aReader.Read<uint32_t>();
1420 aReader.Skip( 1 );
1421 layer_v7 = static_cast<ALTIUM_LAYER>( aReader.Read<uint32_t>() );
1422 }
1423
1424 if( remaining >= 10 )
1425 keepoutrestrictions = aReader.Read<uint8_t>();
1426 else
1428
1430
1431 aReader.SkipSubrecord();
1432
1433 if( aReader.HasParsingError() )
1434 THROW_IO_ERROR( wxT( "Tracks6 stream was not parsed correctly" ) );
1435}
1436
1437ATEXT6::ATEXT6( ALTIUM_BINARY_PARSER& aReader, std::map<uint32_t, wxString>& aStringTable )
1438{
1439 ALTIUM_RECORD recordtype = static_cast<ALTIUM_RECORD>( aReader.Read<uint8_t>() );
1440
1441 if( recordtype != ALTIUM_RECORD::TEXT )
1442 THROW_IO_ERROR( wxT( "Texts6 stream has invalid recordtype" ) );
1443
1444 // Subrecord 1 - Properties
1445 size_t subrecord1 = aReader.ReadAndSetSubrecordLength();
1446
1447 layer_v6 = static_cast<ALTIUM_LAYER>( aReader.Read<uint8_t>() );
1448 aReader.Skip( 6 );
1449 component = aReader.Read<uint16_t>();
1450 aReader.Skip( 4 );
1451 position = aReader.ReadVector2IPos();
1452 height = aReader.ReadKicadUnit();
1453 strokefonttype = static_cast<STROKE_FONT_TYPE>( aReader.Read<uint16_t>() );
1454 // TODO: The Serif font type doesn't match well with KiCad, we should replace it with a better match
1455
1456 rotation = aReader.Read<double>();
1457 isMirrored = aReader.Read<uint8_t>() != 0;
1458 strokewidth = aReader.ReadKicadUnit();
1459
1460 if( subrecord1 < 123 )
1461 {
1463 aReader.SkipSubrecord();
1464 return;
1465 }
1466
1467 isComment = aReader.Read<uint8_t>() != 0;
1468 isDesignator = aReader.Read<uint8_t>() != 0;
1469 aReader.Skip( 1 );
1470 fonttype = static_cast<ALTIUM_TEXT_TYPE>( aReader.Read<uint8_t>() );
1471 isBold = aReader.Read<uint8_t>() != 0;
1472 isItalic = aReader.Read<uint8_t>() != 0;
1473
1474 char fontData[64] = { 0 };
1475 aReader.ReadBytes( fontData, sizeof( fontData ) );
1476 fontname = wxString( fontData, wxMBConvUTF16LE(), sizeof( fontData ) ).BeforeFirst( '\0' );
1477
1478 char tmpbyte = aReader.Read<uint8_t>();
1479 isInverted = !!tmpbyte;
1480 margin_border_width = aReader.ReadKicadUnit(); // "Margin Border"
1481 widestring_index = aReader.Read<uint32_t>();
1482 aReader.Skip( 4 );
1483
1484 // An inverted rect in Altium is like a text box with the text inverted.
1485 isInvertedRect = aReader.Read<uint8_t>() != 0;
1486
1489 textbox_rect_justification = static_cast<ALTIUM_TEXT_POSITION>( aReader.Read<uint8_t>() );
1490 text_offset_width = aReader.ReadKicadUnit(); // "Text Offset"
1491
1492 int remaining = aReader.GetRemainingSubrecordBytes();
1493
1494 if( remaining >= 93 )
1495 {
1496 VECTOR2I unk_vec = aReader.ReadVector2ISize();
1497 wxLogTrace( traceAltiumImport, " Unk vec: %d, %d\n", unk_vec.x, unk_vec.y );
1498
1499 barcode_margin = aReader.ReadVector2ISize();
1500
1501 int32_t unk32 = aReader.ReadKicadUnit();
1502 wxLogTrace( traceAltiumImport, " Unk32: %d\n", unk32 );
1503
1504 barcode_type = static_cast<ALTIUM_BARCODE_TYPE>( aReader.Read<uint8_t>() );
1505 uint8_t unk8 = aReader.Read<uint8_t>();
1506 wxLogTrace( traceAltiumImport, " Unk8: %u\n", unk8 );
1507
1508 barcode_inverted = aReader.Read<uint8_t>() != 0;
1509 fonttype = static_cast<ALTIUM_TEXT_TYPE>( aReader.Read<uint8_t>() );
1510
1511 aReader.ReadBytes( fontData, sizeof( fontData ) );
1512 barcode_fontname = wxString( fontData, wxMBConvUTF16LE(), sizeof( fontData ) ).BeforeFirst( '\0' );
1513
1514 aReader.Read<uint8_t>();
1515 wxLogTrace( traceAltiumImport, " Unk8_2: %u\n", unk8 );
1516
1517 layer_v7 = static_cast<ALTIUM_LAYER>( aReader.Read<uint32_t>() );
1518 }
1519
1520 if( remaining >= 103 )
1521 {
1522 // "Frame" text type flag
1523 isFrame = aReader.Read<uint8_t>() != 0;
1524
1525 // Use "Offset" border value instead of "Margin"
1526 isOffsetBorder = aReader.Read<uint8_t>() != 0;
1527
1528 for( size_t ii = 0; ii < 8; ++ii )
1529 {
1530 uint8_t temp = aReader.Peek<uint8_t>();
1531 uint32_t temp32 = ii < 3 ? ALTIUM_PROPS_UTILS::ConvertToKicadUnit( aReader.Peek<uint32_t>() ) : 0;
1532 wxLogTrace( traceAltiumImport, "3ATEXT6 %zu:\t Byte:%u, Kicad:%u\n", ii, temp, temp32 );
1533 aReader.Skip( 1 );
1534 }
1535 }
1536 else
1537 {
1539 isOffsetBorder = false;
1540 }
1541
1542 if( remaining >= 115 )
1543 {
1544 // textbox_rect_justification will be wrong (5) when this flag is unset,
1545 // in that case, we should always use the left bottom justification.
1546 isJustificationValid = aReader.Read<uint8_t>() != 0;
1547 }
1548 else
1549 {
1550 isJustificationValid = false;
1551 }
1552
1553 aReader.SkipSubrecord();
1554
1555 // Subrecord 2 - Legacy 8bit string, max 255 chars, unknown codepage
1556 aReader.ReadAndSetSubrecordLength();
1557
1558 auto entry = aStringTable.find( widestring_index );
1559
1560 if( entry != aStringTable.end() )
1561 text = entry->second;
1562 else
1563 text = aReader.ReadWxString();
1564
1565 // Normalize Windows line endings
1566 text.Replace( wxT( "\r\n" ), wxT( "\n" ) );
1567
1568 aReader.SkipSubrecord();
1569
1570 // Altium only supports inverting truetype fonts
1572 {
1573 isInverted = false;
1574 isInvertedRect = false;
1575 }
1576
1578
1579 if( aReader.HasParsingError() )
1580 THROW_IO_ERROR( wxT( "Texts6 stream was not parsed correctly" ) );
1581}
1582
1584{
1585 ALTIUM_RECORD recordtype = static_cast<ALTIUM_RECORD>( aReader.Read<uint8_t>() );
1586
1587 if( recordtype != ALTIUM_RECORD::FILL )
1588 THROW_IO_ERROR( wxT( "Fills6 stream has invalid recordtype" ) );
1589
1590 // Subrecord 1
1591 aReader.ReadAndSetSubrecordLength();
1592
1593 layer_v6 = static_cast<ALTIUM_LAYER>( aReader.Read<uint8_t>() );
1594
1595 uint8_t flags1 = aReader.Read<uint8_t>();
1596 is_locked = ( flags1 & 0x04 ) == 0;
1597
1598 uint8_t flags2 = aReader.Read<uint8_t>();
1599 is_keepout = flags2 == 2;
1600
1601 net = aReader.Read<uint16_t>();
1602 aReader.Skip( 2 );
1603 component = aReader.Read<uint16_t>();
1604 aReader.Skip( 4 );
1605 pos1 = aReader.ReadVector2IPos();
1606 pos2 = aReader.ReadVector2IPos();
1607 rotation = aReader.Read<double>();
1608
1609 int remaining = aReader.GetRemainingSubrecordBytes();
1610
1611 if( remaining >= 9 )
1612 {
1613 aReader.Skip( 5 );
1614 layer_v7 = static_cast<ALTIUM_LAYER>( aReader.Read<uint32_t>() );
1615 }
1616
1617 if( remaining >= 10 )
1618 keepoutrestrictions = aReader.Read<uint8_t>();
1619 else
1621
1623
1624 aReader.SkipSubrecord();
1625
1626 if( aReader.HasParsingError() )
1627 THROW_IO_ERROR( wxT( "Fills6 stream was not parsed correctly" ) );
1628}
1629
1630AREGION6::AREGION6( ALTIUM_BINARY_PARSER& aReader, bool aExtendedVertices )
1631{
1632 ALTIUM_RECORD recordtype = static_cast<ALTIUM_RECORD>( aReader.Read<uint8_t>() );
1633
1634 if( recordtype != ALTIUM_RECORD::REGION )
1635 THROW_IO_ERROR( wxT( "Regions6 stream has invalid recordtype" ) );
1636
1637 // Subrecord 1
1638 aReader.ReadAndSetSubrecordLength();
1639
1640 layer_v6 = static_cast<ALTIUM_LAYER>( aReader.Read<uint8_t>() );
1641
1642 uint8_t flags1 = aReader.Read<uint8_t>();
1643 is_locked = ( flags1 & 0x04 ) == 0;
1644 is_teardrop = ( flags1 & 0x10 ) != 0;
1645
1646 uint8_t flags2 = aReader.Read<uint8_t>();
1647 is_keepout = flags2 == 2;
1648
1649 net = aReader.Read<uint16_t>();
1650 polygon = aReader.Read<uint16_t>();
1651 component = aReader.Read<uint16_t>();
1652 aReader.Skip( 5 );
1653 holecount = aReader.Read<uint16_t>();
1654 aReader.Skip( 2 );
1655
1656 std::map<wxString, wxString> properties = aReader.ReadProperties();
1657
1658 if( properties.empty() )
1659 THROW_IO_ERROR( wxT( "Regions6 stream has empty properties" ) );
1660
1661 layer_v7 = altium_layer_from_name( ALTIUM_PROPS_UTILS::ReadString( properties, wxT( "V7_LAYER" ), "" ) );
1662
1663 int pkind = ALTIUM_PROPS_UTILS::ReadInt( properties, wxT( "KIND" ), 0 );
1664 bool is_cutout = ALTIUM_PROPS_UTILS::ReadBool( properties, wxT( "ISBOARDCUTOUT" ), false );
1665
1666 is_shapebased = ALTIUM_PROPS_UTILS::ReadBool( properties, wxT( "ISSHAPEBASED" ), false );
1667
1668 // The truncated KEEPOUTRESTRIC spelling never matched a real key, so every region fell back
1669 // to the all-restrictions default
1670 keepoutrestrictions = static_cast<uint8_t>(
1671 ALTIUM_PROPS_UTILS::ReadInt( properties, wxT( "KEEPOUTRESTRICTIONS" ), ALTIUM_KEEPOUT_ALL ) );
1672
1673 // TODO: this can differ from the other subpolyindex?!
1674 // Note: "the other subpolyindex" is "polygon"
1675 subpolyindex = static_cast<uint16_t>(
1676 ALTIUM_PROPS_UTILS::ReadInt( properties, "SUBPOLYINDEX", ALTIUM_POLYGON_NONE ) );
1677
1678 switch( pkind )
1679 {
1680 case 0:
1681 if( is_cutout )
1682 {
1684 }
1685 else
1686 {
1688 }
1689
1690 break;
1691
1692 case 1:
1694 break;
1695
1696 case 2:
1698 break;
1699
1700 case 3:
1701 kind = ALTIUM_REGION_KIND::UNKNOWN_3; // TODO: what kind is this?
1702 break;
1703
1704 case 4:
1706 break;
1707
1708 default:
1710 break;
1711 }
1712
1713 uint32_t num_outline_vertices = aReader.Read<uint32_t>();
1714
1715 if( aExtendedVertices )
1716 num_outline_vertices++; // Has a closing vertex
1717
1718 for( uint32_t i = 0; i < num_outline_vertices; i++ )
1719 {
1720 if( aExtendedVertices )
1721 {
1722 bool isRound = aReader.Read<uint8_t>() != 0;
1723 VECTOR2I position = aReader.ReadVector2IPos();
1724 VECTOR2I center = aReader.ReadVector2IPos();
1725 int32_t radius = aReader.ReadKicadUnit();
1726 double angle1 = aReader.Read<double>();
1727 double angle2 = aReader.Read<double>();
1728 outline.emplace_back( isRound, radius, angle1, angle2, position, center );
1729 }
1730 else
1731 {
1732 // For some regions the coordinates are stored as double and not as int32_t
1733 int32_t x = ALTIUM_PROPS_UTILS::ConvertToKicadUnit( aReader.Read<double>() );
1734 int32_t y = ALTIUM_PROPS_UTILS::ConvertToKicadUnit( -aReader.Read<double>() );
1735 outline.emplace_back( VECTOR2I( x, y ) );
1736 }
1737 }
1738
1739 holes.resize( holecount );
1740 for( uint16_t k = 0; k < holecount; k++ )
1741 {
1742 uint32_t num_hole_vertices = aReader.Read<uint32_t>();
1743 holes.at( k ).reserve( num_hole_vertices );
1744
1745 for( uint32_t i = 0; i < num_hole_vertices; i++ )
1746 {
1747 int32_t x = ALTIUM_PROPS_UTILS::ConvertToKicadUnit( aReader.Read<double>() );
1748 int32_t y = ALTIUM_PROPS_UTILS::ConvertToKicadUnit( -aReader.Read<double>() );
1749 holes.at( k ).emplace_back( VECTOR2I( x, y ) );
1750 }
1751 }
1752
1754
1755 aReader.SkipSubrecord();
1756
1757 if( aReader.HasParsingError() )
1758 THROW_IO_ERROR( wxT( "Regions6 stream was not parsed correctly" ) );
1759}
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:982
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)
#define THROW_IO_ERROR(msg)
macro which captures the "call site" values of FILE_, __FUNCTION & LINE
#define THROW_IO_ERRORF(msg,...)
static const wxChar * traceAltiumImport
Flag to enable Altium importer logging.
const wxChar *const traceAltiumIo
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:437
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
uint16_t component
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:708