You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

3655 lines
130 KiB

4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
  1. /*
  2. * This program source code file is part of KiCad, a free EDA CAD application.
  3. *
  4. * Copyright (C) 2019-2020 Thomas Pointhuber <thomas.pointhuber@gmx.at>
  5. * Copyright (C) 2021-2023 KiCad Developers, see AUTHORS.txt for contributors.
  6. *
  7. * This program is free software; you can redistribute it and/or
  8. * modify it under the terms of the GNU General Public License
  9. * as published by the Free Software Foundation; either version 2
  10. * of the License, or (at your option) any later version.
  11. *
  12. * This program is distributed in the hope that it will be useful,
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. * GNU General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU General Public License
  18. * along with this program; if not, you may find one here:
  19. * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
  20. * or you may search the http://www.gnu.org website for the version 2 license,
  21. * or you may write to the Free Software Foundation, Inc.,
  22. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
  23. */
  24. #include "altium_pcb.h"
  25. #include "altium_parser_pcb.h"
  26. #include "plugins/altium/altium_parser.h"
  27. #include <plugins/altium/altium_parser_utils.h>
  28. #include <board.h>
  29. #include <board_design_settings.h>
  30. #include <pcb_dimension.h>
  31. #include <pad.h>
  32. #include <pcb_shape.h>
  33. #include <pcb_text.h>
  34. #include <pcb_track.h>
  35. #include <string_utils.h>
  36. #include <zone.h>
  37. #include <board_stackup_manager/stackup_predefined_prms.h>
  38. #include <compoundfilereader.h>
  39. #include <convert_basic_shapes_to_polygon.h>
  40. #include <font/outline_font.h>
  41. #include <project.h>
  42. #include <trigo.h>
  43. #include <utf.h>
  44. #include <wx/docview.h>
  45. #include <wx/log.h>
  46. #include <wx/mstream.h>
  47. #include <wx/wfstream.h>
  48. #include <wx/zstream.h>
  49. #include <progress_reporter.h>
  50. #include <magic_enum.hpp>
  51. constexpr double BOLD_FACTOR = 1.75; // CSS font-weight-normal is 400; bold is 700
  52. bool IsAltiumLayerCopper( ALTIUM_LAYER aLayer )
  53. {
  54. return ( aLayer >= ALTIUM_LAYER::TOP_LAYER && aLayer <= ALTIUM_LAYER::BOTTOM_LAYER )
  55. || aLayer == ALTIUM_LAYER::MULTI_LAYER; // TODO: add IsAltiumLayerAPlane?
  56. }
  57. bool IsAltiumLayerAPlane( ALTIUM_LAYER aLayer )
  58. {
  59. return aLayer >= ALTIUM_LAYER::INTERNAL_PLANE_1 && aLayer <= ALTIUM_LAYER::INTERNAL_PLANE_16;
  60. }
  61. FOOTPRINT* ALTIUM_PCB::HelperGetFootprint( uint16_t aComponent ) const
  62. {
  63. if( aComponent == ALTIUM_COMPONENT_NONE || m_components.size() <= aComponent )
  64. {
  65. THROW_IO_ERROR( wxString::Format( wxT( "Component creator tries to access component id %d "
  66. "of %d existing components" ),
  67. aComponent, m_components.size() ) );
  68. }
  69. return m_components.at( aComponent );
  70. }
  71. PCB_SHAPE* ALTIUM_PCB::HelperCreateAndAddShape( uint16_t aComponent )
  72. {
  73. if( aComponent == ALTIUM_COMPONENT_NONE )
  74. {
  75. PCB_SHAPE* shape = new PCB_SHAPE( m_board );
  76. m_board->Add( shape, ADD_MODE::APPEND );
  77. return shape;
  78. }
  79. else
  80. {
  81. if( m_components.size() <= aComponent )
  82. {
  83. THROW_IO_ERROR( wxString::Format( wxT( "Component creator tries to access component "
  84. "id %d of %d existing components" ),
  85. aComponent,
  86. m_components.size() ) );
  87. }
  88. FOOTPRINT* footprint = m_components.at( aComponent );
  89. PCB_SHAPE* fpShape = new PCB_SHAPE( footprint );
  90. footprint->Add( fpShape, ADD_MODE::APPEND );
  91. return fpShape;
  92. }
  93. }
  94. void HelperShapeLineChainFromAltiumVertices( SHAPE_LINE_CHAIN& aLine,
  95. const std::vector<ALTIUM_VERTICE>& aVertices )
  96. {
  97. for( const ALTIUM_VERTICE& vertex : aVertices )
  98. {
  99. if( vertex.isRound )
  100. {
  101. EDA_ANGLE angle( vertex.endangle - vertex.startangle, DEGREES_T );
  102. angle.Normalize();
  103. double startradiant = DEG2RAD( vertex.startangle );
  104. double endradiant = DEG2RAD( vertex.endangle );
  105. VECTOR2I arcStartOffset = VECTOR2I( KiROUND( std::cos( startradiant ) * vertex.radius ),
  106. -KiROUND( std::sin( startradiant ) * vertex.radius ) );
  107. VECTOR2I arcEndOffset = VECTOR2I( KiROUND( std::cos( endradiant ) * vertex.radius ),
  108. -KiROUND( std::sin( endradiant ) * vertex.radius ) );
  109. VECTOR2I arcStart = vertex.center + arcStartOffset;
  110. VECTOR2I arcEnd = vertex.center + arcEndOffset;
  111. if( GetLineLength( arcStart, vertex.position )
  112. < GetLineLength( arcEnd, vertex.position ) )
  113. {
  114. aLine.Append( SHAPE_ARC( vertex.center, arcStart, -angle ) );
  115. }
  116. else
  117. {
  118. aLine.Append( SHAPE_ARC( vertex.center, arcEnd, angle ) );
  119. }
  120. }
  121. else
  122. {
  123. aLine.Append( vertex.position );
  124. }
  125. }
  126. aLine.SetClosed( true );
  127. }
  128. PCB_LAYER_ID ALTIUM_PCB::GetKicadLayer( ALTIUM_LAYER aAltiumLayer ) const
  129. {
  130. auto override = m_layermap.find( aAltiumLayer );
  131. if( override != m_layermap.end() )
  132. {
  133. return override->second;
  134. }
  135. switch( aAltiumLayer )
  136. {
  137. case ALTIUM_LAYER::UNKNOWN: return UNDEFINED_LAYER;
  138. case ALTIUM_LAYER::TOP_LAYER: return F_Cu;
  139. case ALTIUM_LAYER::MID_LAYER_1: return In1_Cu;
  140. case ALTIUM_LAYER::MID_LAYER_2: return In2_Cu;
  141. case ALTIUM_LAYER::MID_LAYER_3: return In3_Cu;
  142. case ALTIUM_LAYER::MID_LAYER_4: return In4_Cu;
  143. case ALTIUM_LAYER::MID_LAYER_5: return In5_Cu;
  144. case ALTIUM_LAYER::MID_LAYER_6: return In6_Cu;
  145. case ALTIUM_LAYER::MID_LAYER_7: return In7_Cu;
  146. case ALTIUM_LAYER::MID_LAYER_8: return In8_Cu;
  147. case ALTIUM_LAYER::MID_LAYER_9: return In9_Cu;
  148. case ALTIUM_LAYER::MID_LAYER_10: return In10_Cu;
  149. case ALTIUM_LAYER::MID_LAYER_11: return In11_Cu;
  150. case ALTIUM_LAYER::MID_LAYER_12: return In12_Cu;
  151. case ALTIUM_LAYER::MID_LAYER_13: return In13_Cu;
  152. case ALTIUM_LAYER::MID_LAYER_14: return In14_Cu;
  153. case ALTIUM_LAYER::MID_LAYER_15: return In15_Cu;
  154. case ALTIUM_LAYER::MID_LAYER_16: return In16_Cu;
  155. case ALTIUM_LAYER::MID_LAYER_17: return In17_Cu;
  156. case ALTIUM_LAYER::MID_LAYER_18: return In18_Cu;
  157. case ALTIUM_LAYER::MID_LAYER_19: return In19_Cu;
  158. case ALTIUM_LAYER::MID_LAYER_20: return In20_Cu;
  159. case ALTIUM_LAYER::MID_LAYER_21: return In21_Cu;
  160. case ALTIUM_LAYER::MID_LAYER_22: return In22_Cu;
  161. case ALTIUM_LAYER::MID_LAYER_23: return In23_Cu;
  162. case ALTIUM_LAYER::MID_LAYER_24: return In24_Cu;
  163. case ALTIUM_LAYER::MID_LAYER_25: return In25_Cu;
  164. case ALTIUM_LAYER::MID_LAYER_26: return In26_Cu;
  165. case ALTIUM_LAYER::MID_LAYER_27: return In27_Cu;
  166. case ALTIUM_LAYER::MID_LAYER_28: return In28_Cu;
  167. case ALTIUM_LAYER::MID_LAYER_29: return In29_Cu;
  168. case ALTIUM_LAYER::MID_LAYER_30: return In30_Cu;
  169. case ALTIUM_LAYER::BOTTOM_LAYER: return B_Cu;
  170. case ALTIUM_LAYER::TOP_OVERLAY: return F_SilkS;
  171. case ALTIUM_LAYER::BOTTOM_OVERLAY: return B_SilkS;
  172. case ALTIUM_LAYER::TOP_PASTE: return F_Paste;
  173. case ALTIUM_LAYER::BOTTOM_PASTE: return B_Paste;
  174. case ALTIUM_LAYER::TOP_SOLDER: return F_Mask;
  175. case ALTIUM_LAYER::BOTTOM_SOLDER: return B_Mask;
  176. case ALTIUM_LAYER::INTERNAL_PLANE_1: return UNDEFINED_LAYER;
  177. case ALTIUM_LAYER::INTERNAL_PLANE_2: return UNDEFINED_LAYER;
  178. case ALTIUM_LAYER::INTERNAL_PLANE_3: return UNDEFINED_LAYER;
  179. case ALTIUM_LAYER::INTERNAL_PLANE_4: return UNDEFINED_LAYER;
  180. case ALTIUM_LAYER::INTERNAL_PLANE_5: return UNDEFINED_LAYER;
  181. case ALTIUM_LAYER::INTERNAL_PLANE_6: return UNDEFINED_LAYER;
  182. case ALTIUM_LAYER::INTERNAL_PLANE_7: return UNDEFINED_LAYER;
  183. case ALTIUM_LAYER::INTERNAL_PLANE_8: return UNDEFINED_LAYER;
  184. case ALTIUM_LAYER::INTERNAL_PLANE_9: return UNDEFINED_LAYER;
  185. case ALTIUM_LAYER::INTERNAL_PLANE_10: return UNDEFINED_LAYER;
  186. case ALTIUM_LAYER::INTERNAL_PLANE_11: return UNDEFINED_LAYER;
  187. case ALTIUM_LAYER::INTERNAL_PLANE_12: return UNDEFINED_LAYER;
  188. case ALTIUM_LAYER::INTERNAL_PLANE_13: return UNDEFINED_LAYER;
  189. case ALTIUM_LAYER::INTERNAL_PLANE_14: return UNDEFINED_LAYER;
  190. case ALTIUM_LAYER::INTERNAL_PLANE_15: return UNDEFINED_LAYER;
  191. case ALTIUM_LAYER::INTERNAL_PLANE_16: return UNDEFINED_LAYER;
  192. case ALTIUM_LAYER::DRILL_GUIDE: return Dwgs_User;
  193. case ALTIUM_LAYER::KEEP_OUT_LAYER: return Margin;
  194. case ALTIUM_LAYER::MECHANICAL_1: return User_1; //Edge_Cuts;
  195. case ALTIUM_LAYER::MECHANICAL_2: return User_2;
  196. case ALTIUM_LAYER::MECHANICAL_3: return User_3;
  197. case ALTIUM_LAYER::MECHANICAL_4: return User_4;
  198. case ALTIUM_LAYER::MECHANICAL_5: return User_5;
  199. case ALTIUM_LAYER::MECHANICAL_6: return User_6;
  200. case ALTIUM_LAYER::MECHANICAL_7: return User_7;
  201. case ALTIUM_LAYER::MECHANICAL_8: return User_8;
  202. case ALTIUM_LAYER::MECHANICAL_9: return User_9;
  203. case ALTIUM_LAYER::MECHANICAL_10: return Dwgs_User;
  204. case ALTIUM_LAYER::MECHANICAL_11: return Eco2_User; //Eco1 is used for unknown elements
  205. case ALTIUM_LAYER::MECHANICAL_12: return F_Fab;
  206. case ALTIUM_LAYER::MECHANICAL_13: return B_Fab; // Don't use courtyard layers for other purposes
  207. case ALTIUM_LAYER::MECHANICAL_14: return UNDEFINED_LAYER;
  208. case ALTIUM_LAYER::MECHANICAL_15: return UNDEFINED_LAYER;
  209. case ALTIUM_LAYER::MECHANICAL_16: return UNDEFINED_LAYER;
  210. case ALTIUM_LAYER::DRILL_DRAWING: return Dwgs_User;
  211. case ALTIUM_LAYER::MULTI_LAYER: return UNDEFINED_LAYER;
  212. case ALTIUM_LAYER::CONNECTIONS: return UNDEFINED_LAYER;
  213. case ALTIUM_LAYER::BACKGROUND: return UNDEFINED_LAYER;
  214. case ALTIUM_LAYER::DRC_ERROR_MARKERS: return UNDEFINED_LAYER;
  215. case ALTIUM_LAYER::SELECTIONS: return UNDEFINED_LAYER;
  216. case ALTIUM_LAYER::VISIBLE_GRID_1: return UNDEFINED_LAYER;
  217. case ALTIUM_LAYER::VISIBLE_GRID_2: return UNDEFINED_LAYER;
  218. case ALTIUM_LAYER::PAD_HOLES: return UNDEFINED_LAYER;
  219. case ALTIUM_LAYER::VIA_HOLES: return UNDEFINED_LAYER;
  220. default: return UNDEFINED_LAYER;
  221. }
  222. }
  223. std::vector<PCB_LAYER_ID> ALTIUM_PCB::GetKicadLayersToIterate( ALTIUM_LAYER aAltiumLayer ) const
  224. {
  225. static std::set<ALTIUM_LAYER> altiumLayersWithWarning;
  226. if( aAltiumLayer == ALTIUM_LAYER::MULTI_LAYER )
  227. {
  228. std::vector<PCB_LAYER_ID> layers;
  229. layers.reserve( MAX_CU_LAYERS ); // TODO: only use Cu layers which are on the board
  230. for( PCB_LAYER_ID layer = PCB_LAYER_ID::F_Cu; layer <= PCB_LAYER_ID::B_Cu;
  231. layer = static_cast<PCB_LAYER_ID>( static_cast<int>( layer ) + 1 ) )
  232. {
  233. layers.emplace_back( layer );
  234. }
  235. return layers;
  236. }
  237. PCB_LAYER_ID klayer = GetKicadLayer( aAltiumLayer );
  238. if( klayer == UNDEFINED_LAYER )
  239. {
  240. wxLogWarning( _( "Altium layer (%d) has no KiCad equivalent. It has been moved to KiCad "
  241. "layer Eco1_User." ),
  242. aAltiumLayer );
  243. klayer = Eco1_User;
  244. }
  245. return { klayer };
  246. }
  247. ALTIUM_PCB::ALTIUM_PCB( BOARD* aBoard, PROGRESS_REPORTER* aProgressReporter )
  248. {
  249. m_board = aBoard;
  250. m_progressReporter = aProgressReporter;
  251. m_doneCount = 0;
  252. m_lastProgressCount = 0;
  253. m_totalCount = 0;
  254. m_highest_pour_index = 0;
  255. }
  256. ALTIUM_PCB::~ALTIUM_PCB()
  257. {
  258. }
  259. void ALTIUM_PCB::checkpoint()
  260. {
  261. const unsigned PROGRESS_DELTA = 250;
  262. if( m_progressReporter )
  263. {
  264. if( ++m_doneCount > m_lastProgressCount + PROGRESS_DELTA )
  265. {
  266. m_progressReporter->SetCurrentProgress( ( (double) m_doneCount )
  267. / std::max( 1U, m_totalCount ) );
  268. if( !m_progressReporter->KeepRefreshing() )
  269. THROW_IO_ERROR( _( "Open cancelled by user." ) );
  270. m_lastProgressCount = m_doneCount;
  271. }
  272. }
  273. }
  274. void ALTIUM_PCB::Parse( const ALTIUM_COMPOUND_FILE& altiumPcbFile,
  275. const std::map<ALTIUM_PCB_DIR, std::string>& aFileMapping )
  276. {
  277. // this vector simply declares in which order which functions to call.
  278. const std::vector<std::tuple<bool, ALTIUM_PCB_DIR, PARSE_FUNCTION_POINTER_fp>> parserOrder = {
  279. { true, ALTIUM_PCB_DIR::FILE_HEADER,
  280. [this]( const ALTIUM_COMPOUND_FILE& aFile, auto fileHeader )
  281. {
  282. this->ParseFileHeader( aFile, fileHeader );
  283. } },
  284. { true, ALTIUM_PCB_DIR::BOARD6,
  285. [this]( const ALTIUM_COMPOUND_FILE& aFile, auto fileHeader )
  286. {
  287. this->ParseBoard6Data( aFile, fileHeader );
  288. } },
  289. { false, ALTIUM_PCB_DIR::EXTENDPRIMITIVEINFORMATION,
  290. [this]( const ALTIUM_COMPOUND_FILE& aFile, auto fileHeader )
  291. {
  292. this->ParseExtendedPrimitiveInformationData( aFile, fileHeader );
  293. } },
  294. { true, ALTIUM_PCB_DIR::COMPONENTS6,
  295. [this]( const ALTIUM_COMPOUND_FILE& aFile, auto fileHeader )
  296. {
  297. this->ParseComponents6Data( aFile, fileHeader );
  298. } },
  299. { true, ALTIUM_PCB_DIR::MODELS,
  300. [this, aFileMapping]( const ALTIUM_COMPOUND_FILE& aFile, auto fileHeader )
  301. {
  302. std::vector<std::string> dir{ aFileMapping.at( ALTIUM_PCB_DIR::MODELS ) };
  303. this->ParseModelsData( aFile, fileHeader, dir );
  304. } },
  305. { true, ALTIUM_PCB_DIR::COMPONENTBODIES6,
  306. [this]( const ALTIUM_COMPOUND_FILE& aFile, auto fileHeader )
  307. {
  308. this->ParseComponentsBodies6Data( aFile, fileHeader );
  309. } },
  310. { true, ALTIUM_PCB_DIR::NETS6,
  311. [this]( const ALTIUM_COMPOUND_FILE& aFile, auto fileHeader )
  312. {
  313. this->ParseNets6Data( aFile, fileHeader );
  314. } },
  315. { true, ALTIUM_PCB_DIR::CLASSES6,
  316. [this]( const ALTIUM_COMPOUND_FILE& aFile, auto fileHeader )
  317. {
  318. this->ParseClasses6Data( aFile, fileHeader );
  319. } },
  320. { true, ALTIUM_PCB_DIR::RULES6,
  321. [this]( const ALTIUM_COMPOUND_FILE& aFile, auto fileHeader )
  322. {
  323. this->ParseRules6Data( aFile, fileHeader );
  324. } },
  325. { true, ALTIUM_PCB_DIR::DIMENSIONS6,
  326. [this]( const ALTIUM_COMPOUND_FILE& aFile, auto fileHeader )
  327. {
  328. this->ParseDimensions6Data( aFile, fileHeader );
  329. } },
  330. { true, ALTIUM_PCB_DIR::POLYGONS6,
  331. [this]( const ALTIUM_COMPOUND_FILE& aFile, auto fileHeader )
  332. {
  333. this->ParsePolygons6Data( aFile, fileHeader );
  334. } },
  335. { true, ALTIUM_PCB_DIR::ARCS6,
  336. [this]( const ALTIUM_COMPOUND_FILE& aFile, auto fileHeader )
  337. {
  338. this->ParseArcs6Data( aFile, fileHeader );
  339. } },
  340. { true, ALTIUM_PCB_DIR::PADS6,
  341. [this]( const ALTIUM_COMPOUND_FILE& aFile, auto fileHeader )
  342. {
  343. this->ParsePads6Data( aFile, fileHeader );
  344. } },
  345. { true, ALTIUM_PCB_DIR::VIAS6,
  346. [this]( const ALTIUM_COMPOUND_FILE& aFile, auto fileHeader )
  347. {
  348. this->ParseVias6Data( aFile, fileHeader );
  349. } },
  350. { true, ALTIUM_PCB_DIR::TRACKS6,
  351. [this]( const ALTIUM_COMPOUND_FILE& aFile, auto fileHeader )
  352. {
  353. this->ParseTracks6Data( aFile, fileHeader );
  354. } },
  355. { false, ALTIUM_PCB_DIR::WIDESTRINGS6,
  356. [this]( const ALTIUM_COMPOUND_FILE& aFile, auto fileHeader )
  357. {
  358. this->ParseWideStrings6Data( aFile, fileHeader );
  359. } },
  360. { true, ALTIUM_PCB_DIR::TEXTS6,
  361. [this]( const ALTIUM_COMPOUND_FILE& aFile, auto fileHeader )
  362. {
  363. this->ParseTexts6Data( aFile, fileHeader );
  364. } },
  365. { true, ALTIUM_PCB_DIR::FILLS6,
  366. [this]( const ALTIUM_COMPOUND_FILE& aFile, auto fileHeader )
  367. {
  368. this->ParseFills6Data( aFile, fileHeader );
  369. } },
  370. { false, ALTIUM_PCB_DIR::BOARDREGIONS,
  371. [this]( const ALTIUM_COMPOUND_FILE& aFile, auto fileHeader )
  372. {
  373. this->ParseBoardRegionsData( aFile, fileHeader );
  374. } },
  375. { true, ALTIUM_PCB_DIR::SHAPEBASEDREGIONS6,
  376. [this]( const ALTIUM_COMPOUND_FILE& aFile, auto fileHeader )
  377. {
  378. this->ParseShapeBasedRegions6Data( aFile, fileHeader );
  379. } },
  380. { true, ALTIUM_PCB_DIR::REGIONS6,
  381. [this]( const ALTIUM_COMPOUND_FILE& aFile, auto fileHeader )
  382. {
  383. this->ParseRegions6Data( aFile, fileHeader );
  384. } }
  385. };
  386. if( m_progressReporter != nullptr )
  387. {
  388. // Count number of records we will read for the progress reporter
  389. for( const std::tuple<bool, ALTIUM_PCB_DIR, PARSE_FUNCTION_POINTER_fp>& cur : parserOrder )
  390. {
  391. bool isRequired;
  392. ALTIUM_PCB_DIR directory;
  393. PARSE_FUNCTION_POINTER_fp fp;
  394. std::tie( isRequired, directory, fp ) = cur;
  395. if( directory == ALTIUM_PCB_DIR::FILE_HEADER )
  396. continue;
  397. const auto& mappedDirectory = aFileMapping.find( directory );
  398. if( mappedDirectory == aFileMapping.end() )
  399. continue;
  400. const std::vector<std::string> mappedFile{ mappedDirectory->second, "Header" };
  401. const CFB::COMPOUND_FILE_ENTRY* file = altiumPcbFile.FindStream( mappedFile );
  402. if( file == nullptr )
  403. continue;
  404. ALTIUM_PARSER reader( altiumPcbFile, file );
  405. uint32_t numOfRecords = reader.Read<uint32_t>();
  406. if( reader.HasParsingError() )
  407. {
  408. wxLogError( _( "'%s' was not parsed correctly." ), FormatPath( mappedFile ) );
  409. continue;
  410. }
  411. m_totalCount += numOfRecords;
  412. if( reader.GetRemainingBytes() != 0 )
  413. {
  414. wxLogError( _( "'%s' was not fully parsed." ), FormatPath( mappedFile ) );
  415. continue;
  416. }
  417. }
  418. }
  419. // Parse data in specified order
  420. for( const std::tuple<bool, ALTIUM_PCB_DIR, PARSE_FUNCTION_POINTER_fp>& cur : parserOrder )
  421. {
  422. bool isRequired;
  423. ALTIUM_PCB_DIR directory;
  424. PARSE_FUNCTION_POINTER_fp fp;
  425. std::tie( isRequired, directory, fp ) = cur;
  426. const auto& mappedDirectory = aFileMapping.find( directory );
  427. if( mappedDirectory == aFileMapping.end() )
  428. {
  429. wxASSERT_MSG( !isRequired, wxString::Format( wxT( "Altium Directory of kind %d was "
  430. "expected, but no mapping is "
  431. "present in the code" ),
  432. directory ) );
  433. continue;
  434. }
  435. std::vector<std::string> mappedFile{ mappedDirectory->second };
  436. if( directory != ALTIUM_PCB_DIR::FILE_HEADER )
  437. mappedFile.emplace_back( "Data" );
  438. const CFB::COMPOUND_FILE_ENTRY* file = altiumPcbFile.FindStream( mappedFile );
  439. if( file != nullptr )
  440. {
  441. fp( altiumPcbFile, file );
  442. }
  443. else if( isRequired )
  444. {
  445. wxLogError( _( "File not found: '%s' for directory '%s'." ), FormatPath( mappedFile ),
  446. magic_enum::enum_name( directory ) );
  447. }
  448. }
  449. // fixup zone priorities since Altium stores them in the opposite order
  450. for( ZONE* zone : m_polygons )
  451. {
  452. if( !zone )
  453. continue;
  454. // Altium "fills" - not poured in Altium
  455. if( zone->GetAssignedPriority() == 1000 )
  456. {
  457. // Unlikely, but you never know
  458. if( m_highest_pour_index >= 1000 )
  459. zone->SetAssignedPriority( m_highest_pour_index + 1 );
  460. continue;
  461. }
  462. int priority = m_highest_pour_index - zone->GetAssignedPriority();
  463. zone->SetAssignedPriority( priority >= 0 ? priority : 0 );
  464. }
  465. // change priority of outer zone to zero
  466. for( std::pair<const ALTIUM_LAYER, ZONE*>& zone : m_outer_plane )
  467. zone.second->SetAssignedPriority( 0 );
  468. // Altium doesn't appear to store either the dimension value nor the dimensioned object in
  469. // the dimension record. (Yes, there is a REFERENCE0OBJECTID, but it doesn't point to the
  470. // dimensioned object.) We attempt to plug this gap by finding a colocated arc or circle
  471. // and using its radius. If there are more than one such arcs/circles, well, :shrug:.
  472. for( PCB_DIM_RADIAL* dim : m_radialDimensions )
  473. {
  474. int radius = 0;
  475. for( BOARD_ITEM* item : m_board->Drawings() )
  476. {
  477. if( item->Type() != PCB_SHAPE_T )
  478. continue;
  479. PCB_SHAPE* shape = static_cast<PCB_SHAPE*>( item );
  480. if( shape->GetShape() != SHAPE_T::ARC && shape->GetShape() != SHAPE_T::CIRCLE )
  481. continue;
  482. if( shape->GetPosition() == dim->GetPosition() )
  483. {
  484. radius = shape->GetRadius();
  485. break;
  486. }
  487. }
  488. if( radius == 0 )
  489. {
  490. for( PCB_TRACK* track : m_board->Tracks() )
  491. {
  492. if( track->Type() != PCB_ARC_T )
  493. continue;
  494. PCB_ARC* arc = static_cast<PCB_ARC*>( track );
  495. if( arc->GetCenter() == dim->GetPosition() )
  496. {
  497. radius = arc->GetRadius();
  498. break;
  499. }
  500. }
  501. }
  502. // Move the radius point onto the circumference
  503. VECTOR2I radialLine = dim->GetEnd() - dim->GetStart();
  504. int totalLength = radialLine.EuclideanNorm();
  505. // Enforce a minimum on the radialLine else we won't have enough precision to get the
  506. // angle from it.
  507. radialLine = radialLine.Resize( std::max( radius, 2 ) );
  508. dim->SetEnd( dim->GetStart() + (VECTOR2I) radialLine );
  509. dim->SetLeaderLength( totalLength - radius );
  510. dim->Update();
  511. }
  512. // center board
  513. BOX2I bbbox = m_board->GetBoardEdgesBoundingBox();
  514. int w = m_board->GetPageSettings().GetWidthIU( pcbIUScale.IU_PER_MILS );
  515. int h = m_board->GetPageSettings().GetHeightIU( pcbIUScale.IU_PER_MILS );
  516. int desired_x = ( w - bbbox.GetWidth() ) / 2;
  517. int desired_y = ( h - bbbox.GetHeight() ) / 2;
  518. VECTOR2I movementVector( desired_x - bbbox.GetX(), desired_y - bbbox.GetY() );
  519. m_board->Move( movementVector );
  520. BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings();
  521. bds.SetAuxOrigin( bds.GetAuxOrigin() + movementVector );
  522. bds.SetGridOrigin( bds.GetGridOrigin() + movementVector );
  523. m_board->SetModified();
  524. }
  525. FOOTPRINT* ALTIUM_PCB::ParseFootprint( const ALTIUM_COMPOUND_FILE& altiumLibFile,
  526. const wxString& aFootprintName )
  527. {
  528. std::unique_ptr<FOOTPRINT> footprint = std::make_unique<FOOTPRINT>( m_board );
  529. // TODO: what should we do with those layers?
  530. m_layermap.emplace( ALTIUM_LAYER::MECHANICAL_14, Eco2_User );
  531. m_layermap.emplace( ALTIUM_LAYER::MECHANICAL_15, Eco2_User );
  532. m_layermap.emplace( ALTIUM_LAYER::MECHANICAL_16, Eco2_User );
  533. m_unicodeStrings.clear();
  534. m_extendedPrimitiveInformationMaps.clear();
  535. // TODO: WideStrings are stored as parameterMap in the case of footprints, not as binary
  536. // std::string unicodeStringsStreamName = aFootprintName.ToStdString() + "\\WideStrings";
  537. // const CFB::COMPOUND_FILE_ENTRY* unicodeStringsData = altiumLibFile.FindStream( unicodeStringsStreamName );
  538. // if( unicodeStringsData != nullptr )
  539. // {
  540. // ParseWideStrings6Data( altiumLibFile, unicodeStringsData );
  541. // }
  542. wxString fpDirName = altiumLibFile.FindLibFootprintDirName(aFootprintName);
  543. if( fpDirName.IsEmpty() )
  544. {
  545. THROW_IO_ERROR(
  546. wxString::Format( _( "Footprint directory not found: '%s'." ), aFootprintName ) );
  547. }
  548. const std::vector<std::string> streamName{ fpDirName.ToStdString(), "Data" };
  549. const CFB::COMPOUND_FILE_ENTRY* footprintData = altiumLibFile.FindStream( streamName );
  550. if( footprintData == nullptr )
  551. {
  552. THROW_IO_ERROR( wxString::Format( _( "File not found: '%s'." ),
  553. FormatPath( streamName ) ) );
  554. }
  555. ALTIUM_PARSER parser( altiumLibFile, footprintData );
  556. parser.ReadAndSetSubrecordLength();
  557. //wxString footprintName = parser.ReadWxString(); // Not used (single-byte char set)
  558. parser.SkipSubrecord();
  559. LIB_ID fpID = AltiumToKiCadLibID( "", aFootprintName ); // TODO: library name
  560. footprint->SetFPID( fpID );
  561. const std::vector<std::string> parametersStreamName{ fpDirName.ToStdString(),
  562. "Parameters" };
  563. const CFB::COMPOUND_FILE_ENTRY* parametersData =
  564. altiumLibFile.FindStream( parametersStreamName );
  565. if( parametersData != nullptr )
  566. {
  567. ALTIUM_PARSER parametersReader( altiumLibFile, parametersData );
  568. std::map<wxString, wxString> parameterProperties = parametersReader.ReadProperties();
  569. wxString description = ALTIUM_PARSER::ReadString( parameterProperties,
  570. wxT( "DESCRIPTION" ),
  571. wxT( "" ) );
  572. footprint->SetLibDescription( description );
  573. }
  574. else
  575. {
  576. wxLogError( _( "File not found: '%s'." ), FormatPath( parametersStreamName ) );
  577. footprint->SetLibDescription( wxT( "" ) );
  578. }
  579. const std::vector<std::string> extendedPrimitiveInformationStreamName{
  580. fpDirName.ToStdString(), "ExtendedPrimitiveInformation", "Data"
  581. };
  582. const CFB::COMPOUND_FILE_ENTRY* extendedPrimitiveInformationData =
  583. altiumLibFile.FindStream( extendedPrimitiveInformationStreamName );
  584. if( extendedPrimitiveInformationData != nullptr )
  585. ParseExtendedPrimitiveInformationData( altiumLibFile, extendedPrimitiveInformationData );
  586. footprint->SetReference( wxT( "REF**" ) );
  587. footprint->SetValue( aFootprintName );
  588. footprint->Reference().SetVisible( true ); // TODO: extract visibility information
  589. footprint->Value().SetVisible( true );
  590. const VECTOR2I defaultTextSize( pcbIUScale.mmToIU( 1.0 ), pcbIUScale.mmToIU( 1.0 ) );
  591. const int defaultTextThickness( pcbIUScale.mmToIU( 0.15 ) );
  592. for( PCB_FIELD* field : footprint->Fields() )
  593. {
  594. field->SetTextSize( defaultTextSize );
  595. field->SetTextThickness( defaultTextThickness );
  596. }
  597. for( int primitiveIndex = 0; parser.GetRemainingBytes() >= 4; primitiveIndex++ )
  598. {
  599. ALTIUM_RECORD recordtype = static_cast<ALTIUM_RECORD>( parser.Peek<uint8_t>() );
  600. switch( recordtype )
  601. {
  602. case ALTIUM_RECORD::ARC:
  603. {
  604. AARC6 arc( parser );
  605. ConvertArcs6ToFootprintItem( footprint.get(), arc, primitiveIndex, false );
  606. break;
  607. }
  608. case ALTIUM_RECORD::PAD:
  609. {
  610. APAD6 pad( parser );
  611. ConvertPads6ToFootprintItem( footprint.get(), pad );
  612. break;
  613. }
  614. case ALTIUM_RECORD::VIA:
  615. {
  616. AVIA6 via( parser );
  617. // TODO: implement
  618. break;
  619. }
  620. case ALTIUM_RECORD::TRACK:
  621. {
  622. ATRACK6 track( parser );
  623. ConvertTracks6ToFootprintItem( footprint.get(), track, primitiveIndex, false );
  624. break;
  625. }
  626. case ALTIUM_RECORD::TEXT:
  627. {
  628. ATEXT6 text( parser, m_unicodeStrings );
  629. ConvertTexts6ToFootprintItem( footprint.get(), text );
  630. break;
  631. }
  632. case ALTIUM_RECORD::FILL:
  633. {
  634. AFILL6 fill( parser );
  635. ConvertFills6ToFootprintItem( footprint.get(), fill, false );
  636. break;
  637. }
  638. case ALTIUM_RECORD::REGION:
  639. {
  640. AREGION6 region( parser, false );
  641. ConvertShapeBasedRegions6ToFootprintItem( footprint.get(), region, primitiveIndex );
  642. break;
  643. }
  644. case ALTIUM_RECORD::MODEL:
  645. {
  646. ACOMPONENTBODY6 componentBody( parser );
  647. // Won't be supported for now, as we would need to extract the model
  648. break;
  649. }
  650. default:
  651. THROW_IO_ERROR( wxString::Format( _( "Record of unknown type: '%d'." ), recordtype ) );
  652. }
  653. }
  654. // Auto-position reference and value
  655. footprint->AutoPositionFields();
  656. if( parser.HasParsingError() )
  657. {
  658. THROW_IO_ERROR( wxString::Format( wxT( "%s stream was not parsed correctly" ),
  659. FormatPath( streamName ) ) );
  660. }
  661. if( parser.GetRemainingBytes() != 0 )
  662. {
  663. THROW_IO_ERROR( wxString::Format( wxT( "%s stream is not fully parsed" ),
  664. FormatPath( streamName ) ) );
  665. }
  666. return footprint.release();
  667. }
  668. int ALTIUM_PCB::GetNetCode( uint16_t aId ) const
  669. {
  670. if( aId == ALTIUM_NET_UNCONNECTED )
  671. {
  672. return NETINFO_LIST::UNCONNECTED;
  673. }
  674. else if( m_altiumToKicadNetcodes.size() < aId )
  675. {
  676. THROW_IO_ERROR( wxString::Format( wxT( "Netcode with id %d does not exist. Only %d nets "
  677. "are known" ),
  678. aId, m_altiumToKicadNetcodes.size() ) );
  679. }
  680. else
  681. {
  682. return m_altiumToKicadNetcodes[ aId ];
  683. }
  684. }
  685. const ARULE6* ALTIUM_PCB::GetRule( ALTIUM_RULE_KIND aKind, const wxString& aName ) const
  686. {
  687. const auto rules = m_rules.find( aKind );
  688. if( rules == m_rules.end() )
  689. return nullptr;
  690. for( const ARULE6& rule : rules->second )
  691. {
  692. if( rule.name == aName )
  693. return &rule;
  694. }
  695. return nullptr;
  696. }
  697. const ARULE6* ALTIUM_PCB::GetRuleDefault( ALTIUM_RULE_KIND aKind ) const
  698. {
  699. const auto rules = m_rules.find( aKind );
  700. if( rules == m_rules.end() )
  701. return nullptr;
  702. for( const ARULE6& rule : rules->second )
  703. {
  704. if( rule.scope1expr == wxT( "All" ) && rule.scope2expr == wxT( "All" ) )
  705. return &rule;
  706. }
  707. return nullptr;
  708. }
  709. void ALTIUM_PCB::ParseFileHeader( const ALTIUM_COMPOUND_FILE& aAltiumPcbFile,
  710. const CFB::COMPOUND_FILE_ENTRY* aEntry )
  711. {
  712. ALTIUM_PARSER reader( aAltiumPcbFile, aEntry );
  713. reader.ReadAndSetSubrecordLength();
  714. wxString header = reader.ReadWxString();
  715. //std::cout << "HEADER: " << header << std::endl; // tells me: PCB 5.0 Binary File
  716. //reader.SkipSubrecord();
  717. // TODO: does not seem to work all the time at the moment
  718. //if( reader.GetRemainingBytes() != 0 )
  719. // THROW_IO_ERROR( "FileHeader stream is not fully parsed" );
  720. }
  721. void ALTIUM_PCB::ParseExtendedPrimitiveInformationData( const ALTIUM_COMPOUND_FILE& aAltiumPcbFile,
  722. const CFB::COMPOUND_FILE_ENTRY* aEntry )
  723. {
  724. if( m_progressReporter )
  725. m_progressReporter->Report( _( "Loading extended primitive information data..." ) );
  726. ALTIUM_PARSER reader( aAltiumPcbFile, aEntry );
  727. while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
  728. {
  729. checkpoint();
  730. AEXTENDED_PRIMITIVE_INFORMATION elem( reader );
  731. m_extendedPrimitiveInformationMaps[elem.primitiveObjectId].emplace( elem.primitiveIndex,
  732. std::move( elem ) );
  733. }
  734. if( reader.GetRemainingBytes() != 0 )
  735. THROW_IO_ERROR( wxT( "ExtendedPrimitiveInformation stream is not fully parsed" ) );
  736. }
  737. void ALTIUM_PCB::ParseBoard6Data( const ALTIUM_COMPOUND_FILE& aAltiumPcbFile,
  738. const CFB::COMPOUND_FILE_ENTRY* aEntry )
  739. {
  740. if( m_progressReporter )
  741. m_progressReporter->Report( _( "Loading board data..." ) );
  742. ALTIUM_PARSER reader( aAltiumPcbFile, aEntry );
  743. checkpoint();
  744. ABOARD6 elem( reader );
  745. if( reader.GetRemainingBytes() != 0 )
  746. THROW_IO_ERROR( wxT( "Board6 stream is not fully parsed" ) );
  747. m_board->GetDesignSettings().SetAuxOrigin( elem.sheetpos );
  748. m_board->GetDesignSettings().SetGridOrigin( elem.sheetpos );
  749. // read layercount from stackup, because LAYERSETSCOUNT is not always correct?!
  750. size_t layercount = 0;
  751. size_t layerid = static_cast<size_t>( ALTIUM_LAYER::TOP_LAYER );
  752. while( layerid < elem.stackup.size() && layerid != 0 )
  753. {
  754. layerid = elem.stackup[ layerid - 1 ].nextId;
  755. layercount++;
  756. }
  757. size_t kicadLayercount = ( layercount % 2 == 0 ) ? layercount : layercount + 1;
  758. m_board->SetCopperLayerCount( kicadLayercount );
  759. BOARD_DESIGN_SETTINGS& designSettings = m_board->GetDesignSettings();
  760. BOARD_STACKUP& stackup = designSettings.GetStackupDescriptor();
  761. // create board stackup
  762. stackup.RemoveAll(); // Just to be sure
  763. stackup.BuildDefaultStackupList( &designSettings, layercount );
  764. auto it = stackup.GetList().begin();
  765. // find first copper layer
  766. for( ; it != stackup.GetList().end() && ( *it )->GetType() != BS_ITEM_TYPE_COPPER; ++it )
  767. ;
  768. auto curLayer = static_cast<int>( F_Cu );
  769. for( size_t altiumLayerId = static_cast<size_t>( ALTIUM_LAYER::TOP_LAYER );
  770. altiumLayerId < elem.stackup.size() && altiumLayerId != 0;
  771. altiumLayerId = elem.stackup[altiumLayerId - 1].nextId )
  772. {
  773. // array starts with 0, but stackup with 1
  774. ABOARD6_LAYER_STACKUP& layer = elem.stackup.at( altiumLayerId - 1 );
  775. // handle unused layer in case of odd layercount
  776. if( layer.nextId == 0 && layercount != kicadLayercount )
  777. {
  778. m_board->SetLayerName( ( *it )->GetBrdLayerId(), wxT( "[unused]" ) );
  779. if( ( *it )->GetType() != BS_ITEM_TYPE_COPPER )
  780. THROW_IO_ERROR( wxT( "Board6 stream, unexpected item while parsing stackup" ) );
  781. ( *it )->SetThickness( 0 );
  782. ++it;
  783. if( ( *it )->GetType() != BS_ITEM_TYPE_DIELECTRIC )
  784. THROW_IO_ERROR( wxT( "Board6 stream, unexpected item while parsing stackup" ) );
  785. ( *it )->SetThickness( 0, 0 );
  786. ( *it )->SetThicknessLocked( true, 0 );
  787. ++it;
  788. }
  789. m_layermap.insert( { static_cast<ALTIUM_LAYER>( altiumLayerId ),
  790. static_cast<PCB_LAYER_ID>( curLayer++ ) } );
  791. if( ( *it )->GetType() != BS_ITEM_TYPE_COPPER )
  792. THROW_IO_ERROR( wxT( "Board6 stream, unexpected item while parsing stackup" ) );
  793. ( *it )->SetThickness( layer.copperthick );
  794. ALTIUM_LAYER alayer = static_cast<ALTIUM_LAYER>( altiumLayerId );
  795. PCB_LAYER_ID klayer = ( *it )->GetBrdLayerId();
  796. m_board->SetLayerName( klayer, layer.name );
  797. if( layer.copperthick == 0 )
  798. m_board->SetLayerType( klayer, LAYER_T::LT_JUMPER ); // used for things like wirebonding
  799. else if( IsAltiumLayerAPlane( alayer ) )
  800. m_board->SetLayerType( klayer, LAYER_T::LT_POWER );
  801. if( klayer == B_Cu )
  802. {
  803. if( layer.nextId != 0 )
  804. THROW_IO_ERROR( wxT( "Board6 stream, unexpected id while parsing last stackup layer" ) );
  805. // overwrite entry from internal -> bottom
  806. m_layermap[alayer] = B_Cu;
  807. break;
  808. }
  809. ++it;
  810. if( ( *it )->GetType() != BS_ITEM_TYPE_DIELECTRIC )
  811. THROW_IO_ERROR( wxT( "Board6 stream, unexpected item while parsing stackup" ) );
  812. ( *it )->SetThickness( layer.dielectricthick, 0 );
  813. ( *it )->SetMaterial( layer.dielectricmaterial.empty() ?
  814. NotSpecifiedPrm() :
  815. wxString( layer.dielectricmaterial ) );
  816. ( *it )->SetEpsilonR( layer.dielectricconst, 0 );
  817. ++it;
  818. }
  819. // Set name of all non-cu layers
  820. for( size_t altiumLayerId = static_cast<size_t>( ALTIUM_LAYER::TOP_OVERLAY );
  821. altiumLayerId <= static_cast<size_t>( ALTIUM_LAYER::BOTTOM_SOLDER ); altiumLayerId++ )
  822. {
  823. // array starts with 0, but stackup with 1
  824. ABOARD6_LAYER_STACKUP& layer = elem.stackup.at( altiumLayerId - 1 );
  825. ALTIUM_LAYER alayer = static_cast<ALTIUM_LAYER>( altiumLayerId );
  826. PCB_LAYER_ID klayer = GetKicadLayer( alayer );
  827. m_board->SetLayerName( klayer, layer.name );
  828. }
  829. for( size_t altiumLayerId = static_cast<size_t>( ALTIUM_LAYER::MECHANICAL_1 );
  830. altiumLayerId <= static_cast<size_t>( ALTIUM_LAYER::MECHANICAL_16 ); altiumLayerId++ )
  831. {
  832. // array starts with 0, but stackup with 1
  833. ABOARD6_LAYER_STACKUP& layer = elem.stackup.at( altiumLayerId - 1 );
  834. ALTIUM_LAYER alayer = static_cast<ALTIUM_LAYER>( altiumLayerId );
  835. PCB_LAYER_ID klayer = GetKicadLayer( alayer );
  836. m_board->SetLayerName( klayer, layer.name );
  837. }
  838. HelperCreateBoardOutline( elem.board_vertices );
  839. }
  840. void ALTIUM_PCB::HelperCreateBoardOutline( const std::vector<ALTIUM_VERTICE>& aVertices )
  841. {
  842. SHAPE_LINE_CHAIN lineChain;
  843. HelperShapeLineChainFromAltiumVertices( lineChain, aVertices );
  844. STROKE_PARAMS stroke( m_board->GetDesignSettings().GetLineThickness( Edge_Cuts ),
  845. PLOT_DASH_TYPE::SOLID );
  846. for( int i = 0; i <= lineChain.PointCount() && i != -1; i = lineChain.NextShape( i ) )
  847. {
  848. if( lineChain.IsArcStart( i ) )
  849. {
  850. const SHAPE_ARC& currentArc = lineChain.Arc( lineChain.ArcIndex( i ) );
  851. int nextShape = lineChain.NextShape( i );
  852. bool isLastShape = nextShape < 0;
  853. PCB_SHAPE* shape = new PCB_SHAPE( m_board, SHAPE_T::ARC );
  854. m_board->Add( shape, ADD_MODE::APPEND );
  855. shape->SetStroke( stroke );
  856. shape->SetLayer( Edge_Cuts );
  857. shape->SetArcGeometry( currentArc.GetP0(), currentArc.GetArcMid(), currentArc.GetP1() );
  858. }
  859. else
  860. {
  861. const SEG& seg = lineChain.Segment( i );
  862. PCB_SHAPE* shape = new PCB_SHAPE( m_board, SHAPE_T::SEGMENT );
  863. m_board->Add( shape, ADD_MODE::APPEND );
  864. shape->SetStroke( stroke );
  865. shape->SetLayer( Edge_Cuts );
  866. shape->SetStart( seg.A );
  867. shape->SetEnd( seg.B );
  868. }
  869. }
  870. }
  871. void ALTIUM_PCB::ParseClasses6Data( const ALTIUM_COMPOUND_FILE& aAltiumPcbFile,
  872. const CFB::COMPOUND_FILE_ENTRY* aEntry )
  873. {
  874. if( m_progressReporter )
  875. m_progressReporter->Report( _( "Loading netclasses..." ) );
  876. ALTIUM_PARSER reader( aAltiumPcbFile, aEntry );
  877. while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
  878. {
  879. checkpoint();
  880. ACLASS6 elem( reader );
  881. if( elem.kind == ALTIUM_CLASS_KIND::NET_CLASS )
  882. {
  883. std::shared_ptr<NETCLASS> nc = std::make_shared<NETCLASS>( elem.name );
  884. for( const wxString& name : elem.names )
  885. {
  886. m_board->GetDesignSettings().m_NetSettings->m_NetClassPatternAssignments.push_back(
  887. {
  888. std::make_unique<EDA_COMBINED_MATCHER>( name, CTX_NETCLASS ),
  889. nc->GetName()
  890. } );
  891. }
  892. if( m_board->GetDesignSettings().m_NetSettings->m_NetClasses.count( nc->GetName() ) )
  893. {
  894. // Name conflict, happens in some unknown circumstances
  895. // unique_ptr will delete nc on this code path
  896. wxLogWarning( _( "More than one Altium netclass with name '%s' found. "
  897. "Only the first one will be imported." ), elem.name );
  898. }
  899. else
  900. {
  901. m_board->GetDesignSettings().m_NetSettings->m_NetClasses[ nc->GetName() ] = nc;
  902. }
  903. }
  904. }
  905. if( reader.GetRemainingBytes() != 0 )
  906. THROW_IO_ERROR( wxT( "Classes6 stream is not fully parsed" ) );
  907. m_board->m_LegacyNetclassesLoaded = true;
  908. }
  909. void ALTIUM_PCB::ParseComponents6Data( const ALTIUM_COMPOUND_FILE& aAltiumPcbFile,
  910. const CFB::COMPOUND_FILE_ENTRY* aEntry )
  911. {
  912. if( m_progressReporter )
  913. m_progressReporter->Report( _( "Loading components..." ) );
  914. ALTIUM_PARSER reader( aAltiumPcbFile, aEntry );
  915. uint16_t componentId = 0;
  916. while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
  917. {
  918. checkpoint();
  919. ACOMPONENT6 elem( reader );
  920. FOOTPRINT* footprint = new FOOTPRINT( m_board );
  921. m_board->Add( footprint, ADD_MODE::APPEND );
  922. m_components.emplace_back( footprint );
  923. LIB_ID fpID = AltiumToKiCadLibID( elem.sourcefootprintlibrary, elem.pattern );
  924. footprint->SetFPID( fpID );
  925. footprint->SetPosition( elem.position );
  926. footprint->SetOrientationDegrees( elem.rotation );
  927. // KiCad netlisting requires parts to have non-digit + digit annotation.
  928. // If the reference begins with a number, we prepend 'UNK' (unknown) for the source designator
  929. wxString reference = elem.sourcedesignator;
  930. if( reference.find_first_not_of( "0123456789" ) == wxString::npos )
  931. reference.Prepend( wxT( "UNK" ) );
  932. footprint->SetReference( reference );
  933. footprint->SetLocked( elem.locked );
  934. footprint->Reference().SetVisible( elem.nameon );
  935. footprint->Value().SetVisible( elem.commenton );
  936. footprint->SetLayer( elem.layer == ALTIUM_LAYER::TOP_LAYER ? F_Cu : B_Cu );
  937. componentId++;
  938. }
  939. if( reader.GetRemainingBytes() != 0 )
  940. THROW_IO_ERROR( wxT( "Components6 stream is not fully parsed" ) );
  941. }
  942. /// Normalize angle to be aMin < angle <= aMax angle is in degrees.
  943. double normalizeAngleDegrees( double Angle, double aMin, double aMax )
  944. {
  945. while( Angle < aMin )
  946. Angle += 360.0;
  947. while( Angle >= aMax )
  948. Angle -= 360.0;
  949. return Angle;
  950. }
  951. void ALTIUM_PCB::ParseComponentsBodies6Data( const ALTIUM_COMPOUND_FILE& aAltiumPcbFile,
  952. const CFB::COMPOUND_FILE_ENTRY* aEntry )
  953. {
  954. if( m_progressReporter )
  955. m_progressReporter->Report( _( "Loading component 3D models..." ) );
  956. ALTIUM_PARSER reader( aAltiumPcbFile, aEntry );
  957. while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
  958. {
  959. checkpoint();
  960. ACOMPONENTBODY6 elem( reader ); // TODO: implement
  961. if( elem.component == ALTIUM_COMPONENT_NONE )
  962. continue; // TODO: we do not support components for the board yet
  963. if( m_components.size() <= elem.component )
  964. {
  965. THROW_IO_ERROR( wxString::Format( wxT( "ComponentsBodies6 stream tries to access "
  966. "component id %d of %d existing components" ),
  967. elem.component,
  968. m_components.size() ) );
  969. }
  970. if( !elem.modelIsEmbedded )
  971. continue;
  972. auto modelTuple = m_models.find( elem.modelId );
  973. if( modelTuple == m_models.end() )
  974. {
  975. wxLogError( wxT( "ComponentsBodies6 stream tries to access model id %s which does not "
  976. "exist" ),
  977. elem.modelId );
  978. continue;
  979. }
  980. FOOTPRINT* footprint = m_components.at( elem.component );
  981. const VECTOR2I& fpPosition = footprint->GetPosition();
  982. FP_3DMODEL modelSettings;
  983. modelSettings.m_Filename = modelTuple->second;
  984. modelSettings.m_Offset.x = pcbIUScale.IUTomm((int) elem.modelPosition.x - fpPosition.x );
  985. modelSettings.m_Offset.y = -pcbIUScale.IUTomm((int) elem.modelPosition.y - fpPosition.y );
  986. modelSettings.m_Offset.z = pcbIUScale.IUTomm( (int) elem.modelPosition.z );
  987. EDA_ANGLE orientation = footprint->GetOrientation();
  988. if( footprint->IsFlipped() )
  989. {
  990. modelSettings.m_Offset.y = -modelSettings.m_Offset.y;
  991. orientation = -orientation;
  992. }
  993. RotatePoint( &modelSettings.m_Offset.x, &modelSettings.m_Offset.y, orientation );
  994. modelSettings.m_Rotation.x = normalizeAngleDegrees( -elem.modelRotation.x, -180, 180 );
  995. modelSettings.m_Rotation.y = normalizeAngleDegrees( -elem.modelRotation.y, -180, 180 );
  996. modelSettings.m_Rotation.z = normalizeAngleDegrees( -elem.modelRotation.z
  997. + elem.rotation
  998. + orientation.AsDegrees(),
  999. -180, 180 );
  1000. modelSettings.m_Opacity = elem.bodyOpacity;
  1001. footprint->Models().push_back( modelSettings );
  1002. }
  1003. if( reader.GetRemainingBytes() != 0 )
  1004. THROW_IO_ERROR( wxT( "ComponentsBodies6 stream is not fully parsed" ) );
  1005. }
  1006. void ALTIUM_PCB::HelperParseDimensions6Linear( const ADIMENSION6& aElem )
  1007. {
  1008. if( aElem.referencePoint.size() != 2 )
  1009. THROW_IO_ERROR( wxT( "Incorrect number of reference points for linear dimension object" ) );
  1010. PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
  1011. if( klayer == UNDEFINED_LAYER )
  1012. {
  1013. wxLogWarning( _( "Dimension found on an Altium layer (%d) with no KiCad equivalent. "
  1014. "It has been moved to KiCad layer Eco1_User." ),
  1015. aElem.layer );
  1016. klayer = Eco1_User;
  1017. }
  1018. VECTOR2I referencePoint0 = aElem.referencePoint.at( 0 );
  1019. VECTOR2I referencePoint1 = aElem.referencePoint.at( 1 );
  1020. PCB_DIM_ALIGNED* dimension = new PCB_DIM_ALIGNED( m_board, PCB_DIM_ALIGNED_T );
  1021. m_board->Add( dimension, ADD_MODE::APPEND );
  1022. dimension->SetPrecision( static_cast<DIM_PRECISION>( aElem.textprecision ) );
  1023. dimension->SetLayer( klayer );
  1024. dimension->SetStart( referencePoint0 );
  1025. if( referencePoint0 != aElem.xy1 )
  1026. {
  1027. /**
  1028. * Basically REFERENCE0POINT and REFERENCE1POINT are the two end points of the dimension.
  1029. * XY1 is the position of the arrow above REFERENCE0POINT. those three points are not
  1030. * necessarily in 90degree angle, but KiCad requires this to show the correct measurements.
  1031. *
  1032. * Therefore, we take the vector of REFERENCE0POINT -> XY1, calculate the normal, and
  1033. * intersect it with REFERENCE1POINT pointing the same direction as REFERENCE0POINT -> XY1.
  1034. * This should give us a valid measurement point where we can place the drawsegment.
  1035. */
  1036. VECTOR2I direction = aElem.xy1 - referencePoint0;
  1037. VECTOR2I directionNormalVector = VECTOR2I( -direction.y, direction.x );
  1038. SEG segm1( referencePoint0, referencePoint0 + directionNormalVector );
  1039. SEG segm2( referencePoint1, referencePoint1 + direction );
  1040. OPT_VECTOR2I intersection( segm1.Intersect( segm2, true, true ) );
  1041. if( !intersection )
  1042. THROW_IO_ERROR( wxT( "Invalid dimension. This should never happen." ) );
  1043. dimension->SetEnd( *intersection );
  1044. int height = static_cast<int>( EuclideanNorm( direction ) );
  1045. if( ( direction.x > 0 || direction.y < 0 ) != ( aElem.angle >= 180.0 ) )
  1046. height = -height;
  1047. dimension->SetHeight( height );
  1048. }
  1049. else
  1050. {
  1051. dimension->SetEnd( referencePoint1 );
  1052. }
  1053. dimension->SetLineThickness( aElem.linewidth );
  1054. dimension->SetUnitsFormat( DIM_UNITS_FORMAT::NO_SUFFIX );
  1055. dimension->SetPrefix( aElem.textprefix );
  1056. // Suffix normally (but not always) holds the units
  1057. wxRegEx units( wxS( "(mm)|(in)|(mils)|(thou)|(')|(\")" ), wxRE_ADVANCED );
  1058. if( units.Matches( aElem.textsuffix ) )
  1059. dimension->SetUnitsFormat( DIM_UNITS_FORMAT::BARE_SUFFIX );
  1060. else
  1061. dimension->SetSuffix( aElem.textsuffix );
  1062. dimension->SetTextThickness( aElem.textlinewidth );
  1063. dimension->SetTextSize( VECTOR2I( aElem.textheight, aElem.textheight ) );
  1064. dimension->SetItalic( aElem.textitalic );
  1065. #if 0 // we don't currently support bold; map to thicker text
  1066. dimension->Text().SetBold( aElem.textbold );
  1067. #else
  1068. if( aElem.textbold )
  1069. dimension->SetTextThickness( dimension->GetTextThickness() * BOLD_FACTOR );
  1070. #endif
  1071. switch( aElem.textunit )
  1072. {
  1073. case ALTIUM_UNIT::INCHES:
  1074. dimension->SetUnits( EDA_UNITS::INCHES );
  1075. break;
  1076. case ALTIUM_UNIT::MILS:
  1077. dimension->SetUnits( EDA_UNITS::MILS );
  1078. break;
  1079. case ALTIUM_UNIT::MILLIMETERS:
  1080. case ALTIUM_UNIT::CENTIMETER:
  1081. dimension->SetUnits( EDA_UNITS::MILLIMETRES );
  1082. break;
  1083. default:
  1084. break;
  1085. }
  1086. }
  1087. void ALTIUM_PCB::HelperParseDimensions6Radial(const ADIMENSION6 &aElem)
  1088. {
  1089. if( aElem.referencePoint.size() < 2 )
  1090. THROW_IO_ERROR( wxT( "Not enough reference points for radial dimension object" ) );
  1091. PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
  1092. if( klayer == UNDEFINED_LAYER )
  1093. {
  1094. wxLogWarning( _( "Dimension found on an Altium layer (%d) with no KiCad equivalent. "
  1095. "It has been moved to KiCad layer Eco1_User." ),
  1096. aElem.layer );
  1097. klayer = Eco1_User;
  1098. }
  1099. VECTOR2I referencePoint0 = aElem.referencePoint.at( 0 );
  1100. VECTOR2I referencePoint1 = aElem.referencePoint.at( 1 );
  1101. PCB_DIM_RADIAL* dimension = new PCB_DIM_RADIAL( m_board );
  1102. m_board->Add( dimension, ADD_MODE::APPEND );
  1103. m_radialDimensions.push_back( dimension );
  1104. dimension->SetPrecision( static_cast<DIM_PRECISION>( aElem.textprecision ) );
  1105. dimension->SetLayer( klayer );
  1106. dimension->SetStart( referencePoint0 );
  1107. dimension->SetEnd( aElem.xy1 );
  1108. dimension->SetLineThickness( aElem.linewidth );
  1109. dimension->SetKeepTextAligned( false );
  1110. dimension->SetPrefix( aElem.textprefix );
  1111. // Suffix normally holds the units
  1112. dimension->SetUnitsFormat( aElem.textsuffix.IsEmpty() ? DIM_UNITS_FORMAT::NO_SUFFIX
  1113. : DIM_UNITS_FORMAT::BARE_SUFFIX );
  1114. switch( aElem.textunit )
  1115. {
  1116. case ALTIUM_UNIT::INCHES:
  1117. dimension->SetUnits( EDA_UNITS::INCHES );
  1118. break;
  1119. case ALTIUM_UNIT::MILS:
  1120. dimension->SetUnits( EDA_UNITS::MILS );
  1121. break;
  1122. case ALTIUM_UNIT::MILLIMETERS:
  1123. case ALTIUM_UNIT::CENTIMETER:
  1124. dimension->SetUnits( EDA_UNITS::MILLIMETRES );
  1125. break;
  1126. default:
  1127. break;
  1128. }
  1129. if( aElem.textPoint.empty() )
  1130. {
  1131. wxLogError( wxT( "No text position present for leader dimension object" ) );
  1132. return;
  1133. }
  1134. dimension->SetTextPos( aElem.textPoint.at( 0 ) );
  1135. dimension->SetTextThickness( aElem.textlinewidth );
  1136. dimension->SetTextSize( VECTOR2I( aElem.textheight, aElem.textheight ) );
  1137. dimension->SetItalic( aElem.textitalic );
  1138. #if 0 // we don't currently support bold; map to thicker text
  1139. dimension->SetBold( aElem.textbold );
  1140. #else
  1141. if( aElem.textbold )
  1142. dimension->SetTextThickness( dimension->GetTextThickness() * BOLD_FACTOR );
  1143. #endif
  1144. // It's unclear exactly how Altium figures it's text positioning, but this gets us reasonably
  1145. // close.
  1146. dimension->SetVertJustify( GR_TEXT_V_ALIGN_BOTTOM );
  1147. dimension->SetHorizJustify( GR_TEXT_H_ALIGN_LEFT );
  1148. int yAdjust = dimension->GetTextBox().GetCenter().y - dimension->GetTextPos().y;
  1149. dimension->SetTextPos( dimension->GetTextPos() + VECTOR2I( 0, yAdjust + aElem.textgap ) );
  1150. dimension->SetVertJustify( GR_TEXT_V_ALIGN_CENTER );
  1151. }
  1152. void ALTIUM_PCB::HelperParseDimensions6Leader( const ADIMENSION6& aElem )
  1153. {
  1154. PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
  1155. if( klayer == UNDEFINED_LAYER )
  1156. {
  1157. wxLogWarning( _( "Dimension found on an Altium layer (%d) with no KiCad equivalent. "
  1158. "It has been moved to KiCad layer Eco1_User." ),
  1159. aElem.layer );
  1160. klayer = Eco1_User;
  1161. }
  1162. if( !aElem.referencePoint.empty() )
  1163. {
  1164. VECTOR2I referencePoint0 = aElem.referencePoint.at( 0 );
  1165. // line
  1166. VECTOR2I last = referencePoint0;
  1167. for( size_t i = 1; i < aElem.referencePoint.size(); i++ )
  1168. {
  1169. PCB_SHAPE* shape = new PCB_SHAPE( m_board, SHAPE_T::SEGMENT );
  1170. m_board->Add( shape, ADD_MODE::APPEND );
  1171. shape->SetLayer( klayer );
  1172. shape->SetStroke( STROKE_PARAMS( aElem.linewidth, PLOT_DASH_TYPE::SOLID ) );
  1173. shape->SetStart( last );
  1174. shape->SetEnd( aElem.referencePoint.at( i ) );
  1175. last = aElem.referencePoint.at( i );
  1176. }
  1177. // arrow
  1178. if( aElem.referencePoint.size() >= 2 )
  1179. {
  1180. VECTOR2I dirVec = aElem.referencePoint.at( 1 ) - referencePoint0;
  1181. if( dirVec.x != 0 || dirVec.y != 0 )
  1182. {
  1183. double scaling = EuclideanNorm( dirVec ) / aElem.arrowsize;
  1184. VECTOR2I arrVec = VECTOR2I( KiROUND( dirVec.x / scaling ),
  1185. KiROUND( dirVec.y / scaling ) );
  1186. RotatePoint( arrVec, EDA_ANGLE( 20.0, DEGREES_T ) );
  1187. PCB_SHAPE* shape1 = new PCB_SHAPE( m_board, SHAPE_T::SEGMENT );
  1188. m_board->Add( shape1, ADD_MODE::APPEND );
  1189. shape1->SetLayer( klayer );
  1190. shape1->SetStroke( STROKE_PARAMS( aElem.linewidth, PLOT_DASH_TYPE::SOLID ) );
  1191. shape1->SetStart( referencePoint0 );
  1192. shape1->SetEnd( referencePoint0 + arrVec );
  1193. RotatePoint( arrVec, EDA_ANGLE( -40.0, DEGREES_T ) );
  1194. PCB_SHAPE* shape2 = new PCB_SHAPE( m_board, SHAPE_T::SEGMENT );
  1195. m_board->Add( shape2, ADD_MODE::APPEND );
  1196. shape2->SetLayer( klayer );
  1197. shape2->SetStroke( STROKE_PARAMS( aElem.linewidth, PLOT_DASH_TYPE::SOLID ) );
  1198. shape2->SetStart( referencePoint0 );
  1199. shape2->SetEnd( referencePoint0 + arrVec );
  1200. }
  1201. }
  1202. }
  1203. if( aElem.textPoint.empty() )
  1204. {
  1205. wxLogError( wxT( "No text position present for leader dimension object" ) );
  1206. return;
  1207. }
  1208. PCB_TEXT* text = new PCB_TEXT( m_board );
  1209. m_board->Add( text, ADD_MODE::APPEND );
  1210. text->SetText( aElem.textformat );
  1211. text->SetPosition( aElem.textPoint.at( 0 ) );
  1212. text->SetLayer( klayer );
  1213. text->SetTextSize( VECTOR2I( aElem.textheight, aElem.textheight ) ); // TODO: parse text width
  1214. text->SetTextThickness( aElem.textlinewidth );
  1215. text->SetHorizJustify( GR_TEXT_H_ALIGN_LEFT );
  1216. text->SetVertJustify( GR_TEXT_V_ALIGN_BOTTOM );
  1217. }
  1218. void ALTIUM_PCB::HelperParseDimensions6Datum( const ADIMENSION6& aElem )
  1219. {
  1220. PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
  1221. if( klayer == UNDEFINED_LAYER )
  1222. {
  1223. wxLogWarning( _( "Dimension found on an Altium layer (%d) with no KiCad equivalent. "
  1224. "It has been moved to KiCad layer Eco1_User." ),
  1225. aElem.layer );
  1226. klayer = Eco1_User;
  1227. }
  1228. for( size_t i = 0; i < aElem.referencePoint.size(); i++ )
  1229. {
  1230. PCB_SHAPE* shape = new PCB_SHAPE( m_board, SHAPE_T::SEGMENT );
  1231. m_board->Add( shape, ADD_MODE::APPEND );
  1232. shape->SetLayer( klayer );
  1233. shape->SetStroke( STROKE_PARAMS( aElem.linewidth, PLOT_DASH_TYPE::SOLID ) );
  1234. shape->SetStart( aElem.referencePoint.at( i ) );
  1235. // shape->SetEnd( /* TODO: seems to be based on TEXTY */ );
  1236. }
  1237. }
  1238. void ALTIUM_PCB::HelperParseDimensions6Center( const ADIMENSION6& aElem )
  1239. {
  1240. PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
  1241. if( klayer == UNDEFINED_LAYER )
  1242. {
  1243. wxLogWarning( _( "Dimension found on an Altium layer (%d) with no KiCad equivalent. "
  1244. "It has been moved to KiCad layer Eco1_User." ),
  1245. aElem.layer );
  1246. klayer = Eco1_User;
  1247. }
  1248. VECTOR2I vec = VECTOR2I( 0, aElem.height / 2 );
  1249. RotatePoint( vec, EDA_ANGLE( aElem.angle, DEGREES_T ) );
  1250. PCB_DIM_CENTER* dimension = new PCB_DIM_CENTER( m_board );
  1251. m_board->Add( dimension, ADD_MODE::APPEND );
  1252. dimension->SetLayer( klayer );
  1253. dimension->SetLineThickness( aElem.linewidth );
  1254. dimension->SetStart( aElem.xy1 );
  1255. dimension->SetEnd( aElem.xy1 + vec );
  1256. }
  1257. void ALTIUM_PCB::ParseDimensions6Data( const ALTIUM_COMPOUND_FILE& aAltiumPcbFile,
  1258. const CFB::COMPOUND_FILE_ENTRY* aEntry )
  1259. {
  1260. if( m_progressReporter )
  1261. m_progressReporter->Report( _( "Loading dimension drawings..." ) );
  1262. ALTIUM_PARSER reader( aAltiumPcbFile, aEntry );
  1263. while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
  1264. {
  1265. checkpoint();
  1266. ADIMENSION6 elem( reader );
  1267. switch( elem.kind )
  1268. {
  1269. case ALTIUM_DIMENSION_KIND::LINEAR:
  1270. HelperParseDimensions6Linear( elem );
  1271. break;
  1272. case ALTIUM_DIMENSION_KIND::RADIAL:
  1273. HelperParseDimensions6Radial( elem );
  1274. break;
  1275. case ALTIUM_DIMENSION_KIND::LEADER:
  1276. HelperParseDimensions6Leader( elem );
  1277. break;
  1278. case ALTIUM_DIMENSION_KIND::DATUM:
  1279. wxLogError( _( "Ignored dimension of kind %d (not yet supported)." ), elem.kind );
  1280. // HelperParseDimensions6Datum( elem );
  1281. break;
  1282. case ALTIUM_DIMENSION_KIND::CENTER:
  1283. HelperParseDimensions6Center( elem );
  1284. break;
  1285. default:
  1286. wxLogError( _( "Ignored dimension of kind %d (not yet supported)." ), elem.kind );
  1287. break;
  1288. }
  1289. }
  1290. if( reader.GetRemainingBytes() != 0 )
  1291. THROW_IO_ERROR( wxT( "Dimensions6 stream is not fully parsed" ) );
  1292. }
  1293. void ALTIUM_PCB::ParseModelsData( const ALTIUM_COMPOUND_FILE& aAltiumPcbFile,
  1294. const CFB::COMPOUND_FILE_ENTRY* aEntry,
  1295. const std::vector<std::string>& aRootDir )
  1296. {
  1297. if( m_progressReporter )
  1298. m_progressReporter->Report( _( "Loading 3D models..." ) );
  1299. ALTIUM_PARSER reader( aAltiumPcbFile, aEntry );
  1300. if( reader.GetRemainingBytes() == 0 )
  1301. return;
  1302. wxString projectPath = wxPathOnly( m_board->GetFileName() );
  1303. // TODO: set KIPRJMOD always after import (not only when loading project)?
  1304. wxSetEnv( PROJECT_VAR_NAME, projectPath );
  1305. // TODO: make this path configurable?
  1306. const wxString altiumModelDir = wxT( "ALTIUM_EMBEDDED_MODELS" );
  1307. wxFileName altiumModelsPath = wxFileName::DirName( projectPath );
  1308. wxString kicadModelPrefix = wxT( "${KIPRJMOD}/" ) + altiumModelDir + wxT( "/" );
  1309. if( !altiumModelsPath.AppendDir( altiumModelDir ) )
  1310. THROW_IO_ERROR( wxT( "Cannot construct directory path for step models" ) );
  1311. // Create dir if it does not exist
  1312. if( !altiumModelsPath.DirExists() )
  1313. {
  1314. if( !altiumModelsPath.Mkdir() )
  1315. {
  1316. wxLogError( _( "Failed to create folder '%s'." ) + wxS( " " )
  1317. + _( "No 3D-models will be imported." ),
  1318. altiumModelsPath.GetFullPath() );
  1319. return;
  1320. }
  1321. }
  1322. int idx = 0;
  1323. wxString invalidChars = wxFileName::GetForbiddenChars();
  1324. while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
  1325. {
  1326. checkpoint();
  1327. AMODEL elem( reader );
  1328. std::vector<std::string> stepPath = aRootDir;
  1329. stepPath.emplace_back( std::to_string( idx ) );
  1330. bool validName = !elem.name.IsEmpty() && elem.name.IsAscii() &&
  1331. wxString::npos == elem.name.find_first_of( invalidChars );
  1332. wxString storageName = !validName ? wxString::Format( wxT( "model_%d" ), idx )
  1333. : elem.name;
  1334. wxFileName storagePath( altiumModelsPath.GetPath(), storageName );
  1335. idx++;
  1336. const CFB::COMPOUND_FILE_ENTRY* stepEntry = aAltiumPcbFile.FindStream( stepPath );
  1337. if( stepEntry == nullptr )
  1338. {
  1339. wxLogError( _( "File not found: '%s'. 3D-model not imported." ),
  1340. FormatPath( stepPath ) );
  1341. continue;
  1342. }
  1343. size_t stepSize = static_cast<size_t>( stepEntry->size );
  1344. std::vector<char> stepContent( stepSize );
  1345. // read file into buffer
  1346. aAltiumPcbFile.GetCompoundFileReader().ReadFile( stepEntry, 0, stepContent.data(),
  1347. stepSize );
  1348. if( !storagePath.IsDirWritable() )
  1349. {
  1350. wxLogError( _( "Insufficient permissions to save file '%s'." ),
  1351. storagePath.GetFullPath() );
  1352. continue;
  1353. }
  1354. wxMemoryInputStream stepStream( stepContent.data(), stepSize );
  1355. wxZlibInputStream zlibInputStream( stepStream );
  1356. wxFFileOutputStream outputStream( storagePath.GetFullPath() );
  1357. outputStream.Write( zlibInputStream );
  1358. outputStream.Close();
  1359. m_models.insert( { elem.id, kicadModelPrefix + storageName } );
  1360. }
  1361. if( reader.GetRemainingBytes() != 0 )
  1362. THROW_IO_ERROR( wxT( "Models stream is not fully parsed" ) );
  1363. }
  1364. void ALTIUM_PCB::ParseNets6Data( const ALTIUM_COMPOUND_FILE& aAltiumPcbFile,
  1365. const CFB::COMPOUND_FILE_ENTRY* aEntry )
  1366. {
  1367. if( m_progressReporter )
  1368. m_progressReporter->Report( _( "Loading nets..." ) );
  1369. ALTIUM_PARSER reader( aAltiumPcbFile, aEntry );
  1370. wxASSERT( m_altiumToKicadNetcodes.empty() );
  1371. while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
  1372. {
  1373. checkpoint();
  1374. ANET6 elem( reader );
  1375. NETINFO_ITEM* netInfo = new NETINFO_ITEM( m_board, elem.name, 0 );
  1376. m_board->Add( netInfo, ADD_MODE::APPEND );
  1377. m_altiumToKicadNetcodes.push_back( netInfo->GetNetCode() );
  1378. }
  1379. if( reader.GetRemainingBytes() != 0 )
  1380. THROW_IO_ERROR( wxT( "Nets6 stream is not fully parsed" ) );
  1381. }
  1382. void ALTIUM_PCB::ParsePolygons6Data( const ALTIUM_COMPOUND_FILE& aAltiumPcbFile,
  1383. const CFB::COMPOUND_FILE_ENTRY* aEntry )
  1384. {
  1385. if( m_progressReporter )
  1386. m_progressReporter->Report( _( "Loading polygons..." ) );
  1387. ALTIUM_PARSER reader( aAltiumPcbFile, aEntry );
  1388. while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
  1389. {
  1390. checkpoint();
  1391. APOLYGON6 elem( reader );
  1392. SHAPE_LINE_CHAIN linechain;
  1393. HelperShapeLineChainFromAltiumVertices( linechain, elem.vertices );
  1394. if( linechain.PointCount() < 3 )
  1395. {
  1396. // We have found multiple Altium files with polygon records containing nothing but two
  1397. // coincident vertices. These polygons do not appear when opening the file in Altium.
  1398. // https://gitlab.com/kicad/code/kicad/-/issues/8183
  1399. // Also, polygons with less than 3 points are not supported in KiCad.
  1400. //
  1401. // wxLogError( _( "Polygon has only %d point extracted from %ld vertices. At least 2 "
  1402. // "points are required." ),
  1403. // linechain.PointCount(),
  1404. // elem.vertices.size() );
  1405. m_polygons.emplace_back( nullptr );
  1406. continue;
  1407. }
  1408. ZONE* zone = new ZONE( m_board );
  1409. m_board->Add( zone, ADD_MODE::APPEND );
  1410. m_polygons.emplace_back( zone );
  1411. zone->SetNetCode( GetNetCode( elem.net ) );
  1412. zone->SetPosition( elem.vertices.at( 0 ).position );
  1413. zone->SetLocked( elem.locked );
  1414. zone->SetAssignedPriority( elem.pourindex > 0 ? elem.pourindex : 0 );
  1415. zone->Outline()->AddOutline( linechain );
  1416. HelperSetZoneLayers( zone, elem.layer );
  1417. if( elem.pourindex > m_highest_pour_index )
  1418. m_highest_pour_index = elem.pourindex;
  1419. // TODO: more flexible rule parsing
  1420. const ARULE6* clearanceRule = GetRuleDefault( ALTIUM_RULE_KIND::PLANE_CLEARANCE );
  1421. if( clearanceRule != nullptr )
  1422. zone->SetLocalClearance( clearanceRule->planeclearanceClearance );
  1423. const ARULE6* polygonConnectRule = GetRuleDefault( ALTIUM_RULE_KIND::POLYGON_CONNECT );
  1424. if( polygonConnectRule != nullptr )
  1425. {
  1426. switch( polygonConnectRule->polygonconnectStyle )
  1427. {
  1428. case ALTIUM_CONNECT_STYLE::DIRECT:
  1429. zone->SetPadConnection( ZONE_CONNECTION::FULL );
  1430. break;
  1431. case ALTIUM_CONNECT_STYLE::NONE:
  1432. zone->SetPadConnection( ZONE_CONNECTION::NONE );
  1433. break;
  1434. default:
  1435. case ALTIUM_CONNECT_STYLE::RELIEF:
  1436. zone->SetPadConnection( ZONE_CONNECTION::THERMAL );
  1437. break;
  1438. }
  1439. // TODO: correct variables?
  1440. zone->SetThermalReliefSpokeWidth(
  1441. polygonConnectRule->polygonconnectReliefconductorwidth );
  1442. zone->SetThermalReliefGap( polygonConnectRule->polygonconnectAirgapwidth );
  1443. if( polygonConnectRule->polygonconnectReliefconductorwidth < zone->GetMinThickness() )
  1444. zone->SetMinThickness( polygonConnectRule->polygonconnectReliefconductorwidth );
  1445. }
  1446. if( IsAltiumLayerAPlane( elem.layer ) )
  1447. {
  1448. // outer zone will be set to priority 0 later.
  1449. zone->SetAssignedPriority( 1 );
  1450. // check if this is the outer zone by simply comparing the BBOX
  1451. const auto& outer_plane = m_outer_plane.find( elem.layer );
  1452. if( outer_plane == m_outer_plane.end()
  1453. || zone->GetBoundingBox().Contains( outer_plane->second->GetBoundingBox() ) )
  1454. {
  1455. m_outer_plane[elem.layer] = zone;
  1456. }
  1457. }
  1458. if( elem.hatchstyle != ALTIUM_POLYGON_HATCHSTYLE::SOLID
  1459. && elem.hatchstyle != ALTIUM_POLYGON_HATCHSTYLE::UNKNOWN )
  1460. {
  1461. zone->SetFillMode( ZONE_FILL_MODE::HATCH_PATTERN );
  1462. zone->SetHatchThickness( elem.trackwidth );
  1463. if( elem.hatchstyle == ALTIUM_POLYGON_HATCHSTYLE::NONE )
  1464. {
  1465. // use a small hack to get us only an outline (hopefully)
  1466. const BOX2I& bbox = zone->GetBoundingBox();
  1467. zone->SetHatchGap( std::max( bbox.GetHeight(), bbox.GetWidth() ) );
  1468. }
  1469. else
  1470. {
  1471. zone->SetHatchGap( elem.gridsize - elem.trackwidth );
  1472. }
  1473. if( elem.hatchstyle == ALTIUM_POLYGON_HATCHSTYLE::DEGREE_45 )
  1474. zone->SetHatchOrientation( ANGLE_45 );
  1475. }
  1476. zone->SetBorderDisplayStyle( ZONE_BORDER_DISPLAY_STYLE::DIAGONAL_EDGE,
  1477. ZONE::GetDefaultHatchPitch(), true );
  1478. }
  1479. if( reader.GetRemainingBytes() != 0 )
  1480. THROW_IO_ERROR( wxT( "Polygons6 stream is not fully parsed" ) );
  1481. }
  1482. void ALTIUM_PCB::ParseRules6Data( const ALTIUM_COMPOUND_FILE& aAltiumPcbFile,
  1483. const CFB::COMPOUND_FILE_ENTRY* aEntry )
  1484. {
  1485. if( m_progressReporter )
  1486. m_progressReporter->Report( _( "Loading rules..." ) );
  1487. ALTIUM_PARSER reader( aAltiumPcbFile, aEntry );
  1488. while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
  1489. {
  1490. checkpoint();
  1491. ARULE6 elem( reader );
  1492. m_rules[elem.kind].emplace_back( elem );
  1493. }
  1494. // sort rules by priority
  1495. for( std::pair<const ALTIUM_RULE_KIND, std::vector<ARULE6>>& val : m_rules )
  1496. {
  1497. std::sort( val.second.begin(), val.second.end(),
  1498. []( const ARULE6& lhs, const ARULE6& rhs )
  1499. {
  1500. return lhs.priority < rhs.priority;
  1501. } );
  1502. }
  1503. const ARULE6* soldermaskRule = GetRuleDefault( ALTIUM_RULE_KIND::SOLDER_MASK_EXPANSION );
  1504. const ARULE6* pastemaskRule = GetRuleDefault( ALTIUM_RULE_KIND::PASTE_MASK_EXPANSION );
  1505. if( soldermaskRule )
  1506. m_board->GetDesignSettings().m_SolderMaskExpansion = soldermaskRule->soldermaskExpansion;
  1507. if( pastemaskRule )
  1508. m_board->GetDesignSettings().m_SolderPasteMargin = pastemaskRule->pastemaskExpansion;
  1509. if( reader.GetRemainingBytes() != 0 )
  1510. THROW_IO_ERROR( wxT( "Rules6 stream is not fully parsed" ) );
  1511. }
  1512. void ALTIUM_PCB::ParseBoardRegionsData( const ALTIUM_COMPOUND_FILE& aAltiumPcbFile,
  1513. const CFB::COMPOUND_FILE_ENTRY* aEntry )
  1514. {
  1515. if( m_progressReporter )
  1516. m_progressReporter->Report( _( "Loading board regions..." ) );
  1517. ALTIUM_PARSER reader( aAltiumPcbFile, aEntry );
  1518. while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
  1519. {
  1520. checkpoint();
  1521. AREGION6 elem( reader, false );
  1522. // TODO: implement?
  1523. }
  1524. if( reader.GetRemainingBytes() != 0 )
  1525. THROW_IO_ERROR( wxT( "BoardRegions stream is not fully parsed" ) );
  1526. }
  1527. void ALTIUM_PCB::ParseShapeBasedRegions6Data( const ALTIUM_COMPOUND_FILE& aAltiumPcbFile,
  1528. const CFB::COMPOUND_FILE_ENTRY* aEntry )
  1529. {
  1530. if( m_progressReporter )
  1531. m_progressReporter->Report( _( "Loading polygons..." ) );
  1532. ALTIUM_PARSER reader( aAltiumPcbFile, aEntry );
  1533. /* TODO: use Header section of file */
  1534. for( int primitiveIndex = 0; reader.GetRemainingBytes() >= 4; primitiveIndex++ )
  1535. {
  1536. checkpoint();
  1537. AREGION6 elem( reader, true );
  1538. if( elem.component == ALTIUM_COMPONENT_NONE
  1539. || elem.kind == ALTIUM_REGION_KIND::BOARD_CUTOUT )
  1540. {
  1541. // TODO: implement all different types for footprints
  1542. ConvertShapeBasedRegions6ToBoardItem( elem );
  1543. }
  1544. else
  1545. {
  1546. FOOTPRINT* footprint = HelperGetFootprint( elem.component );
  1547. ConvertShapeBasedRegions6ToFootprintItem( footprint, elem, primitiveIndex );
  1548. }
  1549. }
  1550. if( reader.GetRemainingBytes() != 0 )
  1551. THROW_IO_ERROR( "ShapeBasedRegions6 stream is not fully parsed" );
  1552. }
  1553. void ALTIUM_PCB::ConvertShapeBasedRegions6ToBoardItem( const AREGION6& aElem )
  1554. {
  1555. if( aElem.kind == ALTIUM_REGION_KIND::BOARD_CUTOUT )
  1556. {
  1557. HelperCreateBoardOutline( aElem.outline );
  1558. }
  1559. else if( aElem.kind == ALTIUM_REGION_KIND::POLYGON_CUTOUT || aElem.is_keepout )
  1560. {
  1561. SHAPE_LINE_CHAIN linechain;
  1562. HelperShapeLineChainFromAltiumVertices( linechain, aElem.outline );
  1563. if( linechain.PointCount() < 3 )
  1564. {
  1565. // We have found multiple Altium files with polygon records containing nothing but
  1566. // two coincident vertices. These polygons do not appear when opening the file in
  1567. // Altium. https://gitlab.com/kicad/code/kicad/-/issues/8183
  1568. // Also, polygons with less than 3 points are not supported in KiCad.
  1569. return;
  1570. }
  1571. ZONE* zone = new ZONE( m_board );
  1572. m_board->Add( zone, ADD_MODE::APPEND );
  1573. zone->SetIsRuleArea( true );
  1574. HelperSetZoneKeepoutRestrictions( zone, aElem.keepoutrestrictions );
  1575. zone->SetPosition( aElem.outline.at( 0 ).position );
  1576. zone->Outline()->AddOutline( linechain );
  1577. HelperSetZoneLayers( zone, aElem.layer );
  1578. zone->SetBorderDisplayStyle( ZONE_BORDER_DISPLAY_STYLE::DIAGONAL_EDGE,
  1579. ZONE::GetDefaultHatchPitch(), true );
  1580. }
  1581. else if( aElem.kind == ALTIUM_REGION_KIND::DASHED_OUTLINE )
  1582. {
  1583. PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
  1584. if( klayer == UNDEFINED_LAYER )
  1585. {
  1586. wxLogWarning(
  1587. _( "Dashed outline found on an Altium layer (%d) with no KiCad equivalent. "
  1588. "It has been moved to KiCad layer Eco1_User." ),
  1589. aElem.layer );
  1590. klayer = Eco1_User;
  1591. }
  1592. SHAPE_LINE_CHAIN linechain;
  1593. HelperShapeLineChainFromAltiumVertices( linechain, aElem.outline );
  1594. if( linechain.PointCount() < 3 )
  1595. {
  1596. // We have found multiple Altium files with polygon records containing nothing but
  1597. // two coincident vertices. These polygons do not appear when opening the file in
  1598. // Altium. https://gitlab.com/kicad/code/kicad/-/issues/8183
  1599. // Also, polygons with less than 3 points are not supported in KiCad.
  1600. return;
  1601. }
  1602. PCB_SHAPE* shape = new PCB_SHAPE( m_board, SHAPE_T::POLY );
  1603. shape->SetPolyShape( linechain );
  1604. shape->SetFilled( false );
  1605. shape->SetLayer( klayer );
  1606. shape->SetStroke( STROKE_PARAMS( pcbIUScale.mmToIU( 0.1 ), PLOT_DASH_TYPE::DASH ) );
  1607. m_board->Add( shape, ADD_MODE::APPEND );
  1608. }
  1609. else if( aElem.kind == ALTIUM_REGION_KIND::COPPER )
  1610. {
  1611. if( aElem.subpolyindex == ALTIUM_POLYGON_NONE )
  1612. {
  1613. for( PCB_LAYER_ID klayer : GetKicadLayersToIterate( aElem.layer ) )
  1614. {
  1615. ConvertShapeBasedRegions6ToBoardItemOnLayer( aElem, klayer );
  1616. }
  1617. }
  1618. }
  1619. else
  1620. {
  1621. wxLogError( _( "Ignored polygon shape of kind %d (not yet supported)." ), aElem.kind );
  1622. }
  1623. }
  1624. void ALTIUM_PCB::ConvertShapeBasedRegions6ToFootprintItem( FOOTPRINT* aFootprint,
  1625. const AREGION6& aElem,
  1626. const int aPrimitiveIndex )
  1627. {
  1628. if( aElem.kind == ALTIUM_REGION_KIND::POLYGON_CUTOUT || aElem.is_keepout )
  1629. {
  1630. SHAPE_LINE_CHAIN linechain;
  1631. HelperShapeLineChainFromAltiumVertices( linechain, aElem.outline );
  1632. if( linechain.PointCount() < 3 )
  1633. {
  1634. // We have found multiple Altium files with polygon records containing nothing but
  1635. // two coincident vertices. These polygons do not appear when opening the file in
  1636. // Altium. https://gitlab.com/kicad/code/kicad/-/issues/8183
  1637. // Also, polygons with less than 3 points are not supported in KiCad.
  1638. return;
  1639. }
  1640. ZONE* zone = new ZONE( aFootprint );
  1641. aFootprint->Add( zone, ADD_MODE::APPEND );
  1642. zone->SetIsRuleArea( true );
  1643. HelperSetZoneKeepoutRestrictions( zone, aElem.keepoutrestrictions );
  1644. zone->SetPosition( aElem.outline.at( 0 ).position );
  1645. zone->Outline()->AddOutline( linechain );
  1646. HelperSetZoneLayers( zone, aElem.layer );
  1647. zone->SetBorderDisplayStyle( ZONE_BORDER_DISPLAY_STYLE::DIAGONAL_EDGE,
  1648. ZONE::GetDefaultHatchPitch(), true );
  1649. }
  1650. else if( aElem.kind == ALTIUM_REGION_KIND::COPPER )
  1651. {
  1652. if( aElem.subpolyindex == ALTIUM_POLYGON_NONE )
  1653. {
  1654. for( PCB_LAYER_ID klayer : GetKicadLayersToIterate( aElem.layer ) )
  1655. ConvertShapeBasedRegions6ToFootprintItemOnLayer( aFootprint, aElem, klayer,
  1656. aPrimitiveIndex );
  1657. }
  1658. }
  1659. else if( aElem.kind == ALTIUM_REGION_KIND::DASHED_OUTLINE )
  1660. {
  1661. PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
  1662. if( klayer == UNDEFINED_LAYER )
  1663. {
  1664. wxLogWarning(
  1665. _( "Dashed outline found on an Altium layer (%d) with no KiCad equivalent. "
  1666. "It has been moved to KiCad layer Eco1_User." ),
  1667. aElem.layer );
  1668. klayer = Eco1_User;
  1669. }
  1670. SHAPE_LINE_CHAIN linechain;
  1671. HelperShapeLineChainFromAltiumVertices( linechain, aElem.outline );
  1672. if( linechain.PointCount() < 3 )
  1673. {
  1674. // We have found multiple Altium files with polygon records containing nothing but
  1675. // two coincident vertices. These polygons do not appear when opening the file in
  1676. // Altium. https://gitlab.com/kicad/code/kicad/-/issues/8183
  1677. // Also, polygons with less than 3 points are not supported in KiCad.
  1678. return;
  1679. }
  1680. PCB_SHAPE* shape = new PCB_SHAPE( aFootprint, SHAPE_T::POLY );
  1681. shape->SetPolyShape( linechain );
  1682. shape->SetFilled( false );
  1683. shape->SetLayer( klayer );
  1684. shape->SetStroke( STROKE_PARAMS( pcbIUScale.mmToIU( 0.1 ), PLOT_DASH_TYPE::DASH ) );
  1685. aFootprint->Add( shape, ADD_MODE::APPEND );
  1686. }
  1687. else
  1688. {
  1689. wxLogError( _( "Ignored polygon shape of kind %d (not yet supported)." ), aElem.kind );
  1690. }
  1691. }
  1692. void ALTIUM_PCB::ConvertShapeBasedRegions6ToBoardItemOnLayer( const AREGION6& aElem,
  1693. PCB_LAYER_ID aLayer )
  1694. {
  1695. SHAPE_LINE_CHAIN linechain;
  1696. HelperShapeLineChainFromAltiumVertices( linechain, aElem.outline );
  1697. if( linechain.PointCount() < 3 )
  1698. {
  1699. // We have found multiple Altium files with polygon records containing nothing
  1700. // but two coincident vertices. These polygons do not appear when opening the
  1701. // file in Altium. https://gitlab.com/kicad/code/kicad/-/issues/8183
  1702. // Also, polygons with less than 3 points are not supported in KiCad.
  1703. return;
  1704. }
  1705. SHAPE_POLY_SET polySet;
  1706. polySet.AddOutline( linechain );
  1707. for( const std::vector<ALTIUM_VERTICE>& hole : aElem.holes )
  1708. {
  1709. SHAPE_LINE_CHAIN hole_linechain;
  1710. HelperShapeLineChainFromAltiumVertices( hole_linechain, hole );
  1711. if( hole_linechain.PointCount() < 3 )
  1712. continue;
  1713. polySet.AddHole( hole_linechain );
  1714. }
  1715. PCB_SHAPE* shape = new PCB_SHAPE( m_board, SHAPE_T::POLY );
  1716. shape->SetPolyShape( polySet );
  1717. shape->SetFilled( true );
  1718. shape->SetLayer( aLayer );
  1719. shape->SetStroke( STROKE_PARAMS( 0 ) );
  1720. m_board->Add( shape, ADD_MODE::APPEND );
  1721. }
  1722. void ALTIUM_PCB::ConvertShapeBasedRegions6ToFootprintItemOnLayer( FOOTPRINT* aFootprint,
  1723. const AREGION6& aElem,
  1724. PCB_LAYER_ID aLayer,
  1725. const int aPrimitiveIndex )
  1726. {
  1727. SHAPE_LINE_CHAIN linechain;
  1728. HelperShapeLineChainFromAltiumVertices( linechain, aElem.outline );
  1729. if( linechain.PointCount() < 3 )
  1730. {
  1731. // We have found multiple Altium files with polygon records containing nothing
  1732. // but two coincident vertices. These polygons do not appear when opening the
  1733. // file in Altium. https://gitlab.com/kicad/code/kicad/-/issues/8183
  1734. // Also, polygons with less than 3 points are not supported in KiCad.
  1735. return;
  1736. }
  1737. SHAPE_POLY_SET polySet;
  1738. polySet.AddOutline( linechain );
  1739. for( const std::vector<ALTIUM_VERTICE>& hole : aElem.holes )
  1740. {
  1741. SHAPE_LINE_CHAIN hole_linechain;
  1742. HelperShapeLineChainFromAltiumVertices( hole_linechain, hole );
  1743. if( hole_linechain.PointCount() < 3 )
  1744. continue;
  1745. polySet.AddHole( hole_linechain );
  1746. }
  1747. if( aLayer == F_Cu || aLayer == B_Cu )
  1748. {
  1749. PAD* pad = new PAD( aFootprint );
  1750. LSET padLayers;
  1751. padLayers.set( aLayer );
  1752. pad->SetKeepTopBottom( false ); // TODO: correct? This seems to be KiCad default on import
  1753. pad->SetAttribute( PAD_ATTRIB::SMD );
  1754. pad->SetShape( PAD_SHAPE::CUSTOM );
  1755. int anchorSize = 1;
  1756. VECTOR2I anchorPos = linechain.CPoint( 0 );
  1757. pad->SetShape( PAD_SHAPE::CUSTOM );
  1758. pad->SetAnchorPadShape( PAD_SHAPE::CIRCLE );
  1759. pad->SetSize( { anchorSize, anchorSize } );
  1760. pad->SetPosition( anchorPos );
  1761. SHAPE_POLY_SET shapePolys = polySet;
  1762. shapePolys.Move( -anchorPos );
  1763. pad->AddPrimitivePoly( shapePolys, 0, true );
  1764. auto& map = m_extendedPrimitiveInformationMaps[ALTIUM_RECORD::REGION];
  1765. auto it = map.find( aPrimitiveIndex );
  1766. if( it != map.end() )
  1767. {
  1768. const AEXTENDED_PRIMITIVE_INFORMATION& info = it->second;
  1769. if( info.pastemaskexpansionmode == ALTIUM_MODE::MANUAL )
  1770. {
  1771. pad->SetLocalSolderPasteMargin(
  1772. info.pastemaskexpansionmanual ? info.pastemaskexpansionmanual : 1 );
  1773. }
  1774. if( info.soldermaskexpansionmode == ALTIUM_MODE::MANUAL )
  1775. {
  1776. pad->SetLocalSolderMaskMargin(
  1777. info.soldermaskexpansionmanual ? info.soldermaskexpansionmanual : 1 );
  1778. }
  1779. if( info.pastemaskexpansionmode != ALTIUM_MODE::NONE )
  1780. padLayers.set( aLayer == F_Cu ? F_Paste : B_Paste );
  1781. if( info.soldermaskexpansionmode != ALTIUM_MODE::NONE )
  1782. padLayers.set( aLayer == F_Cu ? F_Mask : B_Mask );
  1783. }
  1784. pad->SetLayerSet( padLayers );
  1785. aFootprint->Add( pad, ADD_MODE::APPEND );
  1786. }
  1787. else
  1788. {
  1789. PCB_SHAPE* shape = new PCB_SHAPE( aFootprint, SHAPE_T::POLY );
  1790. shape->SetPolyShape( polySet );
  1791. shape->SetFilled( true );
  1792. shape->SetLayer( aLayer );
  1793. shape->SetStroke( STROKE_PARAMS( 0 ) );
  1794. aFootprint->Add( shape, ADD_MODE::APPEND );
  1795. }
  1796. }
  1797. void ALTIUM_PCB::ParseRegions6Data( const ALTIUM_COMPOUND_FILE& aAltiumPcbFile,
  1798. const CFB::COMPOUND_FILE_ENTRY* aEntry )
  1799. {
  1800. if( m_progressReporter )
  1801. m_progressReporter->Report( _( "Loading zone fills..." ) );
  1802. ALTIUM_PARSER reader( aAltiumPcbFile, aEntry );
  1803. for( ZONE* zone : m_polygons )
  1804. {
  1805. if( zone )
  1806. zone->UnFill(); // just to be sure
  1807. }
  1808. while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
  1809. {
  1810. checkpoint();
  1811. AREGION6 elem( reader, false );
  1812. if( elem.subpolyindex != ALTIUM_POLYGON_NONE )
  1813. {
  1814. if( m_polygons.size() <= elem.subpolyindex )
  1815. {
  1816. THROW_IO_ERROR( wxString::Format( "Region stream tries to access polygon id %d "
  1817. "of %d existing polygons.",
  1818. elem.subpolyindex,
  1819. m_polygons.size() ) );
  1820. }
  1821. ZONE *zone = m_polygons.at( elem.subpolyindex );
  1822. if( zone == nullptr )
  1823. {
  1824. continue; // we know the zone id, but because we do not know the layer we did not
  1825. // add it!
  1826. }
  1827. PCB_LAYER_ID klayer = GetKicadLayer( elem.layer );
  1828. if( klayer == UNDEFINED_LAYER )
  1829. continue; // Just skip it for now. Users can fill it themselves.
  1830. SHAPE_LINE_CHAIN linechain;
  1831. for( const ALTIUM_VERTICE& vertice : elem.outline )
  1832. linechain.Append( vertice.position );
  1833. linechain.Append( elem.outline.at( 0 ).position );
  1834. linechain.SetClosed( true );
  1835. SHAPE_POLY_SET fill;
  1836. fill.AddOutline( linechain );
  1837. for( const std::vector<ALTIUM_VERTICE>& hole : elem.holes )
  1838. {
  1839. SHAPE_LINE_CHAIN hole_linechain;
  1840. for( const ALTIUM_VERTICE& vertice : hole )
  1841. hole_linechain.Append( vertice.position );
  1842. hole_linechain.Append( hole.at( 0 ).position );
  1843. hole_linechain.SetClosed( true );
  1844. fill.AddHole( hole_linechain );
  1845. }
  1846. if( zone->HasFilledPolysForLayer( klayer ) )
  1847. fill.BooleanAdd( *zone->GetFill( klayer ), SHAPE_POLY_SET::PM_STRICTLY_SIMPLE );
  1848. fill.Fracture( SHAPE_POLY_SET::PM_STRICTLY_SIMPLE );
  1849. zone->SetFilledPolysList( klayer, fill );
  1850. zone->SetIsFilled( true );
  1851. zone->SetNeedRefill( false );
  1852. }
  1853. }
  1854. if( reader.GetRemainingBytes() != 0 )
  1855. THROW_IO_ERROR( wxT( "Regions6 stream is not fully parsed" ) );
  1856. }
  1857. void ALTIUM_PCB::ParseArcs6Data( const ALTIUM_COMPOUND_FILE& aAltiumPcbFile,
  1858. const CFB::COMPOUND_FILE_ENTRY* aEntry )
  1859. {
  1860. if( m_progressReporter )
  1861. m_progressReporter->Report( _( "Loading arcs..." ) );
  1862. ALTIUM_PARSER reader( aAltiumPcbFile, aEntry );
  1863. for( int primitiveIndex = 0; reader.GetRemainingBytes() >= 4; primitiveIndex++ )
  1864. {
  1865. checkpoint();
  1866. AARC6 elem( reader );
  1867. if( elem.component == ALTIUM_COMPONENT_NONE )
  1868. {
  1869. ConvertArcs6ToBoardItem( elem, primitiveIndex );
  1870. }
  1871. else
  1872. {
  1873. FOOTPRINT* footprint = HelperGetFootprint( elem.component );
  1874. ConvertArcs6ToFootprintItem( footprint, elem, primitiveIndex, true );
  1875. }
  1876. }
  1877. if( reader.GetRemainingBytes() != 0 )
  1878. THROW_IO_ERROR( "Arcs6 stream is not fully parsed" );
  1879. }
  1880. void ALTIUM_PCB::ConvertArcs6ToPcbShape( const AARC6& aElem, PCB_SHAPE* aShape )
  1881. {
  1882. if( aElem.startangle == 0. && aElem.endangle == 360. )
  1883. {
  1884. aShape->SetShape( SHAPE_T::CIRCLE );
  1885. // TODO: other variants to define circle?
  1886. aShape->SetStart( aElem.center );
  1887. aShape->SetEnd( aElem.center - VECTOR2I( 0, aElem.radius ) );
  1888. }
  1889. else
  1890. {
  1891. aShape->SetShape( SHAPE_T::ARC );
  1892. EDA_ANGLE includedAngle( aElem.endangle - aElem.startangle, DEGREES_T );
  1893. EDA_ANGLE startAngle( aElem.endangle, DEGREES_T );
  1894. VECTOR2I startOffset = VECTOR2I( KiROUND( startAngle.Cos() * aElem.radius ),
  1895. -KiROUND( startAngle.Sin() * aElem.radius ) );
  1896. aShape->SetCenter( aElem.center );
  1897. aShape->SetStart( aElem.center + startOffset );
  1898. aShape->SetArcAngleAndEnd( includedAngle.Normalize(), true );
  1899. }
  1900. }
  1901. void ALTIUM_PCB::ConvertArcs6ToBoardItem( const AARC6& aElem, const int aPrimitiveIndex )
  1902. {
  1903. if( aElem.is_polygonoutline || aElem.subpolyindex != ALTIUM_POLYGON_NONE )
  1904. return;
  1905. if( aElem.is_keepout || aElem.layer == ALTIUM_LAYER::KEEP_OUT_LAYER
  1906. || IsAltiumLayerAPlane( aElem.layer ) )
  1907. {
  1908. // This is not the actual board item. We can use it to create the polygon for the region
  1909. PCB_SHAPE shape( nullptr );
  1910. ConvertArcs6ToPcbShape( aElem, &shape );
  1911. shape.SetStroke( STROKE_PARAMS( aElem.width, PLOT_DASH_TYPE::SOLID ) );
  1912. HelperPcpShapeAsBoardKeepoutRegion( shape, aElem.layer, aElem.keepoutrestrictions );
  1913. }
  1914. else
  1915. {
  1916. for( PCB_LAYER_ID klayer : GetKicadLayersToIterate( aElem.layer ) )
  1917. ConvertArcs6ToBoardItemOnLayer( aElem, klayer );
  1918. }
  1919. for( const auto& layerExpansionMask :
  1920. HelperGetSolderAndPasteMaskExpansions( ALTIUM_RECORD::ARC, aPrimitiveIndex, aElem.layer ) )
  1921. {
  1922. int width = aElem.width + ( layerExpansionMask.second * 2 );
  1923. if( width > 1 )
  1924. {
  1925. PCB_SHAPE* arc = new PCB_SHAPE( m_board );
  1926. ConvertArcs6ToPcbShape( aElem, arc );
  1927. arc->SetStroke( STROKE_PARAMS( width, PLOT_DASH_TYPE::SOLID ) );
  1928. arc->SetLayer( layerExpansionMask.first );
  1929. m_board->Add( arc, ADD_MODE::APPEND );
  1930. }
  1931. }
  1932. }
  1933. void ALTIUM_PCB::ConvertArcs6ToFootprintItem( FOOTPRINT* aFootprint, const AARC6& aElem,
  1934. const int aPrimitiveIndex, const bool aIsBoardImport )
  1935. {
  1936. if( aElem.is_polygonoutline || aElem.subpolyindex != ALTIUM_POLYGON_NONE )
  1937. return;
  1938. if( aElem.is_keepout || aElem.layer == ALTIUM_LAYER::KEEP_OUT_LAYER
  1939. || IsAltiumLayerAPlane( aElem.layer ) )
  1940. {
  1941. // This is not the actual board item. We can use it to create the polygon for the region
  1942. PCB_SHAPE shape( nullptr );
  1943. ConvertArcs6ToPcbShape( aElem, &shape );
  1944. shape.SetStroke( STROKE_PARAMS( aElem.width, PLOT_DASH_TYPE::SOLID ) );
  1945. HelperPcpShapeAsFootprintKeepoutRegion( aFootprint, shape, aElem.layer,
  1946. aElem.keepoutrestrictions );
  1947. }
  1948. else
  1949. {
  1950. for( PCB_LAYER_ID klayer : GetKicadLayersToIterate( aElem.layer ) )
  1951. {
  1952. if( aIsBoardImport && IsCopperLayer( klayer ) && aElem.net != ALTIUM_NET_UNCONNECTED )
  1953. {
  1954. // Special case: do to not lose net connections in footprints
  1955. ConvertArcs6ToBoardItemOnLayer( aElem, klayer );
  1956. }
  1957. else
  1958. {
  1959. ConvertArcs6ToFootprintItemOnLayer( aFootprint, aElem, klayer );
  1960. }
  1961. }
  1962. }
  1963. for( const auto& layerExpansionMask :
  1964. HelperGetSolderAndPasteMaskExpansions( ALTIUM_RECORD::ARC, aPrimitiveIndex, aElem.layer ) )
  1965. {
  1966. int width = aElem.width + ( layerExpansionMask.second * 2 );
  1967. if( width > 1 )
  1968. {
  1969. PCB_SHAPE* arc = new PCB_SHAPE( aFootprint );
  1970. ConvertArcs6ToPcbShape( aElem, arc );
  1971. arc->SetStroke( STROKE_PARAMS( width, PLOT_DASH_TYPE::SOLID ) );
  1972. arc->SetLayer( layerExpansionMask.first );
  1973. aFootprint->Add( arc, ADD_MODE::APPEND );
  1974. }
  1975. }
  1976. }
  1977. void ALTIUM_PCB::ConvertArcs6ToBoardItemOnLayer( const AARC6& aElem, PCB_LAYER_ID aLayer )
  1978. {
  1979. if( IsCopperLayer( aLayer ) && aElem.net != ALTIUM_NET_UNCONNECTED )
  1980. {
  1981. // TODO: This is not the actual board item. We use it for now to calculate the arc points. This could be improved!
  1982. PCB_SHAPE shape( nullptr, SHAPE_T::ARC );
  1983. EDA_ANGLE includedAngle( aElem.endangle - aElem.startangle, DEGREES_T );
  1984. EDA_ANGLE startAngle( aElem.endangle, DEGREES_T );
  1985. VECTOR2I startOffset = VECTOR2I( KiROUND( startAngle.Cos() * aElem.radius ),
  1986. -KiROUND( startAngle.Sin() * aElem.radius ) );
  1987. shape.SetCenter( aElem.center );
  1988. shape.SetStart( aElem.center + startOffset );
  1989. shape.SetArcAngleAndEnd( includedAngle.Normalize(), true );
  1990. // Create actual arc
  1991. SHAPE_ARC shapeArc( shape.GetCenter(), shape.GetStart(), shape.GetArcAngle(), aElem.width );
  1992. PCB_ARC* arc = new PCB_ARC( m_board, &shapeArc );
  1993. arc->SetWidth( aElem.width );
  1994. arc->SetLayer( aLayer );
  1995. arc->SetNetCode( GetNetCode( aElem.net ) );
  1996. m_board->Add( arc, ADD_MODE::APPEND );
  1997. }
  1998. else
  1999. {
  2000. PCB_SHAPE* arc = new PCB_SHAPE( m_board );
  2001. ConvertArcs6ToPcbShape( aElem, arc );
  2002. arc->SetStroke( STROKE_PARAMS( aElem.width, PLOT_DASH_TYPE::SOLID ) );
  2003. arc->SetLayer( aLayer );
  2004. m_board->Add( arc, ADD_MODE::APPEND );
  2005. }
  2006. }
  2007. void ALTIUM_PCB::ConvertArcs6ToFootprintItemOnLayer( FOOTPRINT* aFootprint, const AARC6& aElem,
  2008. PCB_LAYER_ID aLayer )
  2009. {
  2010. PCB_SHAPE* arc = new PCB_SHAPE( aFootprint );
  2011. ConvertArcs6ToPcbShape( aElem, arc );
  2012. arc->SetStroke( STROKE_PARAMS( aElem.width, PLOT_DASH_TYPE::SOLID ) );
  2013. arc->SetLayer( aLayer );
  2014. aFootprint->Add( arc, ADD_MODE::APPEND );
  2015. }
  2016. void ALTIUM_PCB::ParsePads6Data( const ALTIUM_COMPOUND_FILE& aAltiumPcbFile,
  2017. const CFB::COMPOUND_FILE_ENTRY* aEntry )
  2018. {
  2019. if( m_progressReporter )
  2020. m_progressReporter->Report( _( "Loading pads..." ) );
  2021. ALTIUM_PARSER reader( aAltiumPcbFile, aEntry );
  2022. while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
  2023. {
  2024. checkpoint();
  2025. APAD6 elem( reader );
  2026. if( elem.component == ALTIUM_COMPONENT_NONE )
  2027. {
  2028. ConvertPads6ToBoardItem( elem );
  2029. }
  2030. else
  2031. {
  2032. FOOTPRINT* footprint = HelperGetFootprint( elem.component );
  2033. ConvertPads6ToFootprintItem( footprint, elem );
  2034. }
  2035. }
  2036. if( reader.GetRemainingBytes() != 0 )
  2037. THROW_IO_ERROR( wxT( "Pads6 stream is not fully parsed" ) );
  2038. }
  2039. void ALTIUM_PCB::ConvertPads6ToBoardItem( const APAD6& aElem )
  2040. {
  2041. // It is possible to place altium pads on non-copper layers -> we need to interpolate them using drawings!
  2042. if( !IsAltiumLayerCopper( aElem.layer ) && !IsAltiumLayerAPlane( aElem.layer )
  2043. && aElem.layer != ALTIUM_LAYER::MULTI_LAYER )
  2044. {
  2045. ConvertPads6ToBoardItemOnNonCopper( aElem );
  2046. }
  2047. else
  2048. {
  2049. // We cannot add a pad directly into the PCB
  2050. FOOTPRINT* footprint = new FOOTPRINT( m_board );
  2051. footprint->SetPosition( aElem.position );
  2052. ConvertPads6ToFootprintItemOnCopper( footprint, aElem );
  2053. m_board->Add( footprint, ADD_MODE::APPEND );
  2054. }
  2055. }
  2056. void ALTIUM_PCB::ConvertPads6ToFootprintItem( FOOTPRINT* aFootprint, const APAD6& aElem )
  2057. {
  2058. // It is possible to place altium pads on non-copper layers -> we need to interpolate them using drawings!
  2059. if( !IsAltiumLayerCopper( aElem.layer ) && !IsAltiumLayerAPlane( aElem.layer )
  2060. && aElem.layer != ALTIUM_LAYER::MULTI_LAYER )
  2061. {
  2062. ConvertPads6ToFootprintItemOnNonCopper( aFootprint, aElem );
  2063. }
  2064. else
  2065. {
  2066. ConvertPads6ToFootprintItemOnCopper( aFootprint, aElem );
  2067. }
  2068. }
  2069. void ALTIUM_PCB::ConvertPads6ToFootprintItemOnCopper( FOOTPRINT* aFootprint, const APAD6& aElem )
  2070. {
  2071. PAD* pad = new PAD( aFootprint );
  2072. pad->SetKeepTopBottom( false ); // TODO: correct? This seems to be KiCad default on import
  2073. pad->SetNumber( aElem.name );
  2074. pad->SetNetCode( GetNetCode( aElem.net ) );
  2075. pad->SetPosition( aElem.position );
  2076. pad->SetOrientationDegrees( aElem.direction );
  2077. pad->SetSize( aElem.topsize );
  2078. if( aElem.holesize == 0 )
  2079. {
  2080. pad->SetAttribute( PAD_ATTRIB::SMD );
  2081. }
  2082. else
  2083. {
  2084. if( aElem.layer != ALTIUM_LAYER::MULTI_LAYER )
  2085. {
  2086. // TODO: I assume other values are possible as well?
  2087. wxLogError( _( "Footprint %s pad %s is not marked as multilayer, but is a TH pad." ),
  2088. aFootprint->GetReference(),
  2089. aElem.name );
  2090. }
  2091. pad->SetAttribute( aElem.plated ? PAD_ATTRIB::PTH : PAD_ATTRIB::NPTH );
  2092. if( !aElem.sizeAndShape || aElem.sizeAndShape->holeshape == ALTIUM_PAD_HOLE_SHAPE::ROUND )
  2093. {
  2094. pad->SetDrillShape( PAD_DRILL_SHAPE_T::PAD_DRILL_SHAPE_CIRCLE );
  2095. pad->SetDrillSize( VECTOR2I( aElem.holesize, aElem.holesize ) );
  2096. }
  2097. else
  2098. {
  2099. switch( aElem.sizeAndShape->holeshape )
  2100. {
  2101. case ALTIUM_PAD_HOLE_SHAPE::ROUND:
  2102. wxFAIL_MSG( wxT( "Round holes are handled before the switch" ) );
  2103. break;
  2104. case ALTIUM_PAD_HOLE_SHAPE::SQUARE:
  2105. wxLogWarning( _( "Footprint %s pad %s has a square hole (not yet supported)." ),
  2106. aFootprint->GetReference(),
  2107. aElem.name );
  2108. pad->SetDrillShape( PAD_DRILL_SHAPE_T::PAD_DRILL_SHAPE_CIRCLE );
  2109. pad->SetDrillSize( VECTOR2I( aElem.holesize, aElem.holesize ) ); // Workaround
  2110. // TODO: elem.sizeAndShape->slotsize was 0 in testfile. Either use holesize in
  2111. // this case or rect holes have a different id
  2112. break;
  2113. case ALTIUM_PAD_HOLE_SHAPE::SLOT:
  2114. {
  2115. pad->SetDrillShape( PAD_DRILL_SHAPE_T::PAD_DRILL_SHAPE_OBLONG );
  2116. EDA_ANGLE slotRotation( aElem.sizeAndShape->slotrotation, DEGREES_T );
  2117. slotRotation.Normalize();
  2118. if( slotRotation.IsHorizontal() )
  2119. {
  2120. pad->SetDrillSize( VECTOR2I( aElem.sizeAndShape->slotsize, aElem.holesize ) );
  2121. }
  2122. else if( slotRotation.IsVertical() )
  2123. {
  2124. pad->SetDrillSize( VECTOR2I( aElem.holesize, aElem.sizeAndShape->slotsize ) );
  2125. }
  2126. else
  2127. {
  2128. wxLogWarning( _( "Footprint %s pad %s has a hole-rotation of %f degrees. "
  2129. "KiCad only supports 90 degree rotations." ),
  2130. aFootprint->GetReference(),
  2131. aElem.name,
  2132. slotRotation.AsDegrees() );
  2133. }
  2134. break;
  2135. }
  2136. default:
  2137. case ALTIUM_PAD_HOLE_SHAPE::UNKNOWN:
  2138. wxLogError( _( "Footprint %s pad %s uses a hole of unknown kind %d." ),
  2139. aFootprint->GetReference(),
  2140. aElem.name,
  2141. aElem.sizeAndShape->holeshape );
  2142. pad->SetDrillShape( PAD_DRILL_SHAPE_T::PAD_DRILL_SHAPE_CIRCLE );
  2143. pad->SetDrillSize( VECTOR2I( aElem.holesize, aElem.holesize ) ); // Workaround
  2144. break;
  2145. }
  2146. }
  2147. if( aElem.sizeAndShape )
  2148. pad->SetOffset( aElem.sizeAndShape->holeoffset[0] );
  2149. }
  2150. if( aElem.padmode != ALTIUM_PAD_MODE::SIMPLE )
  2151. {
  2152. wxLogError( _( "Footprint %s pad %s uses a complex pad stack (not yet supported.)" ),
  2153. aFootprint->GetReference(), aElem.name );
  2154. }
  2155. switch( aElem.topshape )
  2156. {
  2157. case ALTIUM_PAD_SHAPE::RECT:
  2158. pad->SetShape( PAD_SHAPE::RECTANGLE );
  2159. break;
  2160. case ALTIUM_PAD_SHAPE::CIRCLE:
  2161. if( aElem.sizeAndShape
  2162. && aElem.sizeAndShape->alt_shape[0] == ALTIUM_PAD_SHAPE_ALT::ROUNDRECT )
  2163. {
  2164. pad->SetShape( PAD_SHAPE::ROUNDRECT ); // 100 = round, 0 = rectangular
  2165. double ratio = aElem.sizeAndShape->cornerradius[0] / 200.;
  2166. pad->SetRoundRectRadiusRatio( ratio );
  2167. }
  2168. else if( aElem.topsize.x == aElem.topsize.y )
  2169. {
  2170. pad->SetShape( PAD_SHAPE::CIRCLE );
  2171. }
  2172. else
  2173. {
  2174. pad->SetShape( PAD_SHAPE::OVAL );
  2175. }
  2176. break;
  2177. case ALTIUM_PAD_SHAPE::OCTAGONAL:
  2178. pad->SetShape( PAD_SHAPE::CHAMFERED_RECT );
  2179. pad->SetChamferPositions( RECT_CHAMFER_ALL );
  2180. pad->SetChamferRectRatio( 0.25 );
  2181. break;
  2182. case ALTIUM_PAD_SHAPE::UNKNOWN:
  2183. default:
  2184. wxLogError( _( "Footprint %s pad %s uses an unknown pad-shape." ),
  2185. aFootprint->GetReference(), aElem.name );
  2186. break;
  2187. }
  2188. if( pad->GetAttribute() == PAD_ATTRIB::NPTH && pad->HasHole() )
  2189. {
  2190. // KiCad likes NPTH pads to be the same size & shape as their holes
  2191. pad->SetShape( pad->GetDrillShape() == PAD_DRILL_SHAPE_CIRCLE ? PAD_SHAPE::CIRCLE
  2192. : PAD_SHAPE::OVAL );
  2193. pad->SetSize( pad->GetDrillSize() );
  2194. }
  2195. switch( aElem.layer )
  2196. {
  2197. case ALTIUM_LAYER::TOP_LAYER:
  2198. pad->SetLayer( F_Cu );
  2199. pad->SetLayerSet( PAD::SMDMask() );
  2200. break;
  2201. case ALTIUM_LAYER::BOTTOM_LAYER:
  2202. pad->SetLayer( B_Cu );
  2203. pad->SetLayerSet( FlipLayerMask( PAD::SMDMask() ) );
  2204. break;
  2205. case ALTIUM_LAYER::MULTI_LAYER:
  2206. pad->SetLayerSet( aElem.plated ? PAD::PTHMask() : PAD::UnplatedHoleMask() );
  2207. break;
  2208. default:
  2209. PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
  2210. pad->SetLayer( klayer );
  2211. pad->SetLayerSet( LSET( 1, klayer ) );
  2212. break;
  2213. }
  2214. if( aElem.pastemaskexpansionmode == ALTIUM_MODE::MANUAL )
  2215. pad->SetLocalSolderPasteMargin( aElem.pastemaskexpansionmanual );
  2216. if( aElem.soldermaskexpansionmode == ALTIUM_MODE::MANUAL )
  2217. pad->SetLocalSolderMaskMargin( aElem.soldermaskexpansionmanual );
  2218. if( aElem.is_tent_top )
  2219. pad->SetLayerSet( pad->GetLayerSet().reset( F_Mask ) );
  2220. if( aElem.is_tent_bottom )
  2221. pad->SetLayerSet( pad->GetLayerSet().reset( B_Mask ) );
  2222. aFootprint->Add( pad, ADD_MODE::APPEND );
  2223. }
  2224. void ALTIUM_PCB::ConvertPads6ToBoardItemOnNonCopper( const APAD6& aElem )
  2225. {
  2226. PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
  2227. if( klayer == UNDEFINED_LAYER )
  2228. {
  2229. wxLogWarning( _( "Non-copper pad %s found on an Altium layer (%d) with no KiCad "
  2230. "equivalent. It has been moved to KiCad layer Eco1_User." ),
  2231. aElem.name, aElem.layer );
  2232. klayer = Eco1_User;
  2233. }
  2234. PCB_SHAPE* pad = new PCB_SHAPE( m_board );
  2235. HelperParsePad6NonCopper( aElem, klayer, pad );
  2236. m_board->Add( pad, ADD_MODE::APPEND );
  2237. }
  2238. void ALTIUM_PCB::ConvertPads6ToFootprintItemOnNonCopper( FOOTPRINT* aFootprint, const APAD6& aElem )
  2239. {
  2240. PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
  2241. if( klayer == UNDEFINED_LAYER )
  2242. {
  2243. wxLogWarning(
  2244. _( "Non-copper pad %s found on an Altium layer (%d) with no KiCad equivalent. "
  2245. "It has been moved to KiCad layer Eco1_User." ),
  2246. aElem.name, aElem.layer );
  2247. klayer = Eco1_User;
  2248. }
  2249. PCB_SHAPE* pad = new PCB_SHAPE( aFootprint );
  2250. HelperParsePad6NonCopper( aElem, klayer, pad );
  2251. aFootprint->Add( pad, ADD_MODE::APPEND );
  2252. }
  2253. void ALTIUM_PCB::HelperParsePad6NonCopper( const APAD6& aElem, PCB_LAYER_ID aLayer,
  2254. PCB_SHAPE* aShape )
  2255. {
  2256. if( aElem.net != ALTIUM_NET_UNCONNECTED )
  2257. {
  2258. wxLogError( _( "Non-copper pad %s is connected to a net, which is not supported." ),
  2259. aElem.name );
  2260. }
  2261. if( aElem.holesize != 0 )
  2262. {
  2263. wxLogError( _( "Non-copper pad %s has a hole, which is not supported." ), aElem.name );
  2264. }
  2265. if( aElem.padmode != ALTIUM_PAD_MODE::SIMPLE )
  2266. {
  2267. wxLogWarning( _( "Non-copper pad %s has a complex pad stack (not yet supported)." ),
  2268. aElem.name );
  2269. }
  2270. switch( aElem.topshape )
  2271. {
  2272. case ALTIUM_PAD_SHAPE::RECT:
  2273. {
  2274. // filled rect
  2275. aShape->SetShape( SHAPE_T::POLY );
  2276. aShape->SetFilled( true );
  2277. aShape->SetLayer( aLayer );
  2278. aShape->SetStroke( STROKE_PARAMS( 0 ) );
  2279. aShape->SetPolyPoints(
  2280. { aElem.position + VECTOR2I( aElem.topsize.x / 2, aElem.topsize.y / 2 ),
  2281. aElem.position + VECTOR2I( aElem.topsize.x / 2, -aElem.topsize.y / 2 ),
  2282. aElem.position + VECTOR2I( -aElem.topsize.x / 2, -aElem.topsize.y / 2 ),
  2283. aElem.position + VECTOR2I( -aElem.topsize.x / 2, aElem.topsize.y / 2 ) } );
  2284. if( aElem.direction != 0 )
  2285. aShape->Rotate( aElem.position, EDA_ANGLE( aElem.direction, DEGREES_T ) );
  2286. }
  2287. break;
  2288. case ALTIUM_PAD_SHAPE::CIRCLE:
  2289. if( aElem.sizeAndShape
  2290. && aElem.sizeAndShape->alt_shape[0] == ALTIUM_PAD_SHAPE_ALT::ROUNDRECT )
  2291. {
  2292. // filled roundrect
  2293. int cornerradius = aElem.sizeAndShape->cornerradius[0];
  2294. int offset = ( std::min( aElem.topsize.x, aElem.topsize.y ) * cornerradius ) / 200;
  2295. aShape->SetLayer( aLayer );
  2296. aShape->SetStroke( STROKE_PARAMS( offset * 2, PLOT_DASH_TYPE::SOLID ) );
  2297. if( cornerradius < 100 )
  2298. {
  2299. int offsetX = aElem.topsize.x / 2 - offset;
  2300. int offsetY = aElem.topsize.y / 2 - offset;
  2301. VECTOR2I p11 = aElem.position + VECTOR2I( offsetX, offsetY );
  2302. VECTOR2I p12 = aElem.position + VECTOR2I( offsetX, -offsetY );
  2303. VECTOR2I p22 = aElem.position + VECTOR2I( -offsetX, -offsetY );
  2304. VECTOR2I p21 = aElem.position + VECTOR2I( -offsetX, offsetY );
  2305. aShape->SetShape( SHAPE_T::POLY );
  2306. aShape->SetFilled( true );
  2307. aShape->SetPolyPoints( { p11, p12, p22, p21 } );
  2308. }
  2309. else if( aElem.topsize.x == aElem.topsize.y )
  2310. {
  2311. // circle
  2312. aShape->SetShape( SHAPE_T::CIRCLE );
  2313. aShape->SetFilled( true );
  2314. aShape->SetStart( aElem.position );
  2315. aShape->SetEnd( aElem.position - VECTOR2I( 0, aElem.topsize.x / 4 ) );
  2316. aShape->SetStroke( STROKE_PARAMS( aElem.topsize.x / 2, PLOT_DASH_TYPE::SOLID ) );
  2317. }
  2318. else if( aElem.topsize.x < aElem.topsize.y )
  2319. {
  2320. // short vertical line
  2321. aShape->SetShape( SHAPE_T::SEGMENT );
  2322. VECTOR2I pointOffset( 0, ( aElem.topsize.y - aElem.topsize.x ) / 2 );
  2323. aShape->SetStart( aElem.position + pointOffset );
  2324. aShape->SetEnd( aElem.position - pointOffset );
  2325. }
  2326. else
  2327. {
  2328. // short horizontal line
  2329. aShape->SetShape( SHAPE_T::SEGMENT );
  2330. VECTOR2I pointOffset( ( aElem.topsize.x - aElem.topsize.y ) / 2, 0 );
  2331. aShape->SetStart( aElem.position + pointOffset );
  2332. aShape->SetEnd( aElem.position - pointOffset );
  2333. }
  2334. if( aElem.direction != 0 )
  2335. aShape->Rotate( aElem.position, EDA_ANGLE( aElem.direction, DEGREES_T ) );
  2336. }
  2337. else if( aElem.topsize.x == aElem.topsize.y )
  2338. {
  2339. // filled circle
  2340. aShape->SetShape( SHAPE_T::CIRCLE );
  2341. aShape->SetFilled( true );
  2342. aShape->SetLayer( aLayer );
  2343. aShape->SetStart( aElem.position );
  2344. aShape->SetEnd( aElem.position - VECTOR2I( 0, aElem.topsize.x / 4 ) );
  2345. aShape->SetStroke( STROKE_PARAMS( aElem.topsize.x / 2, PLOT_DASH_TYPE::SOLID ) );
  2346. }
  2347. else
  2348. {
  2349. // short line
  2350. aShape->SetShape( SHAPE_T::SEGMENT );
  2351. aShape->SetLayer( aLayer );
  2352. aShape->SetStroke( STROKE_PARAMS( std::min( aElem.topsize.x, aElem.topsize.y ),
  2353. PLOT_DASH_TYPE::SOLID ) );
  2354. if( aElem.topsize.x < aElem.topsize.y )
  2355. {
  2356. VECTOR2I offset( 0, ( aElem.topsize.y - aElem.topsize.x ) / 2 );
  2357. aShape->SetStart( aElem.position + offset );
  2358. aShape->SetEnd( aElem.position - offset );
  2359. }
  2360. else
  2361. {
  2362. VECTOR2I offset( ( aElem.topsize.x - aElem.topsize.y ) / 2, 0 );
  2363. aShape->SetStart( aElem.position + offset );
  2364. aShape->SetEnd( aElem.position - offset );
  2365. }
  2366. if( aElem.direction != 0 )
  2367. aShape->Rotate( aElem.position, EDA_ANGLE( aElem.direction, DEGREES_T ) );
  2368. }
  2369. break;
  2370. case ALTIUM_PAD_SHAPE::OCTAGONAL:
  2371. {
  2372. // filled octagon
  2373. aShape->SetShape( SHAPE_T::POLY );
  2374. aShape->SetFilled( true );
  2375. aShape->SetLayer( aLayer );
  2376. aShape->SetStroke( STROKE_PARAMS( 0 ) );
  2377. VECTOR2I p11 = aElem.position + VECTOR2I( aElem.topsize.x / 2, aElem.topsize.y / 2 );
  2378. VECTOR2I p12 = aElem.position + VECTOR2I( aElem.topsize.x / 2, -aElem.topsize.y / 2 );
  2379. VECTOR2I p22 = aElem.position + VECTOR2I( -aElem.topsize.x / 2, -aElem.topsize.y / 2 );
  2380. VECTOR2I p21 = aElem.position + VECTOR2I( -aElem.topsize.x / 2, aElem.topsize.y / 2 );
  2381. int chamfer = std::min( aElem.topsize.x, aElem.topsize.y ) / 4;
  2382. VECTOR2I chamferX( chamfer, 0 );
  2383. VECTOR2I chamferY( 0, chamfer );
  2384. aShape->SetPolyPoints( { p11 - chamferX, p11 - chamferY, p12 + chamferY, p12 - chamferX,
  2385. p22 + chamferX, p22 + chamferY, p21 - chamferY, p21 + chamferX } );
  2386. if( aElem.direction != 0. )
  2387. aShape->Rotate( aElem.position, EDA_ANGLE( aElem.direction, DEGREES_T ) );
  2388. }
  2389. break;
  2390. case ALTIUM_PAD_SHAPE::UNKNOWN:
  2391. default:
  2392. wxLogError( _( "Non-copper pad %s uses an unknown pad-shape." ), aElem.name );
  2393. break;
  2394. }
  2395. }
  2396. void ALTIUM_PCB::ParseVias6Data( const ALTIUM_COMPOUND_FILE& aAltiumPcbFile,
  2397. const CFB::COMPOUND_FILE_ENTRY* aEntry )
  2398. {
  2399. if( m_progressReporter )
  2400. m_progressReporter->Report( _( "Loading vias..." ) );
  2401. ALTIUM_PARSER reader( aAltiumPcbFile, aEntry );
  2402. while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
  2403. {
  2404. checkpoint();
  2405. AVIA6 elem( reader );
  2406. PCB_VIA* via = new PCB_VIA( m_board );
  2407. m_board->Add( via, ADD_MODE::APPEND );
  2408. via->SetPosition( elem.position );
  2409. via->SetWidth( elem.diameter );
  2410. via->SetDrill( elem.holesize );
  2411. via->SetNetCode( GetNetCode( elem.net ) );
  2412. via->SetLocked( elem.is_locked );
  2413. bool start_layer_outside = elem.layer_start == ALTIUM_LAYER::TOP_LAYER
  2414. || elem.layer_start == ALTIUM_LAYER::BOTTOM_LAYER;
  2415. bool end_layer_outside = elem.layer_end == ALTIUM_LAYER::TOP_LAYER
  2416. || elem.layer_end == ALTIUM_LAYER::BOTTOM_LAYER;
  2417. if( start_layer_outside && end_layer_outside )
  2418. {
  2419. via->SetViaType( VIATYPE::THROUGH );
  2420. }
  2421. else if( ( !start_layer_outside ) && ( !end_layer_outside ) )
  2422. {
  2423. via->SetViaType( VIATYPE::BLIND_BURIED );
  2424. }
  2425. else
  2426. {
  2427. via->SetViaType( VIATYPE::MICROVIA ); // TODO: always a microvia?
  2428. }
  2429. PCB_LAYER_ID start_klayer = GetKicadLayer( elem.layer_start );
  2430. PCB_LAYER_ID end_klayer = GetKicadLayer( elem.layer_end );
  2431. if( !IsCopperLayer( start_klayer ) || !IsCopperLayer( end_klayer ) )
  2432. {
  2433. wxLogError( _( "Via from layer %d to %d uses a non-copper layer, which is not "
  2434. "supported." ),
  2435. elem.layer_start,
  2436. elem.layer_end );
  2437. continue; // just assume through-hole instead.
  2438. }
  2439. // we need VIATYPE set!
  2440. via->SetLayerPair( start_klayer, end_klayer );
  2441. }
  2442. if( reader.GetRemainingBytes() != 0 )
  2443. THROW_IO_ERROR( wxT( "Vias6 stream is not fully parsed" ) );
  2444. }
  2445. void ALTIUM_PCB::ParseTracks6Data( const ALTIUM_COMPOUND_FILE& aAltiumPcbFile,
  2446. const CFB::COMPOUND_FILE_ENTRY* aEntry )
  2447. {
  2448. if( m_progressReporter )
  2449. m_progressReporter->Report( _( "Loading tracks..." ) );
  2450. ALTIUM_PARSER reader( aAltiumPcbFile, aEntry );
  2451. for( int primitiveIndex = 0; reader.GetRemainingBytes() >= 4; primitiveIndex++ )
  2452. {
  2453. checkpoint();
  2454. ATRACK6 elem( reader );
  2455. if( elem.component == ALTIUM_COMPONENT_NONE )
  2456. {
  2457. ConvertTracks6ToBoardItem( elem, primitiveIndex );
  2458. }
  2459. else
  2460. {
  2461. FOOTPRINT* footprint = HelperGetFootprint( elem.component );
  2462. ConvertTracks6ToFootprintItem( footprint, elem, primitiveIndex, true );
  2463. }
  2464. }
  2465. if( reader.GetRemainingBytes() != 0 )
  2466. THROW_IO_ERROR( "Tracks6 stream is not fully parsed" );
  2467. }
  2468. void ALTIUM_PCB::ConvertTracks6ToBoardItem( const ATRACK6& aElem, const int aPrimitiveIndex )
  2469. {
  2470. if( aElem.is_polygonoutline || aElem.subpolyindex != ALTIUM_POLYGON_NONE )
  2471. return;
  2472. if( aElem.is_keepout || aElem.layer == ALTIUM_LAYER::KEEP_OUT_LAYER
  2473. || IsAltiumLayerAPlane( aElem.layer ) )
  2474. {
  2475. // This is not the actual board item. We can use it to create the polygon for the region
  2476. PCB_SHAPE shape( nullptr, SHAPE_T::SEGMENT );
  2477. shape.SetStart( aElem.start );
  2478. shape.SetEnd( aElem.end );
  2479. shape.SetStroke( STROKE_PARAMS( aElem.width, PLOT_DASH_TYPE::SOLID ) );
  2480. HelperPcpShapeAsBoardKeepoutRegion( shape, aElem.layer, aElem.keepoutrestrictions );
  2481. }
  2482. else
  2483. {
  2484. for( PCB_LAYER_ID klayer : GetKicadLayersToIterate( aElem.layer ) )
  2485. {
  2486. ConvertTracks6ToBoardItemOnLayer( aElem, klayer );
  2487. }
  2488. }
  2489. for( const auto& layerExpansionMask : HelperGetSolderAndPasteMaskExpansions(
  2490. ALTIUM_RECORD::TRACK, aPrimitiveIndex, aElem.layer ) )
  2491. {
  2492. int width = aElem.width + ( layerExpansionMask.second * 2 );
  2493. if( width > 1 )
  2494. {
  2495. PCB_SHAPE* seg = new PCB_SHAPE( m_board, SHAPE_T::SEGMENT );
  2496. seg->SetStart( aElem.start );
  2497. seg->SetEnd( aElem.end );
  2498. seg->SetStroke( STROKE_PARAMS( width, PLOT_DASH_TYPE::SOLID ) );
  2499. seg->SetLayer( layerExpansionMask.first );
  2500. m_board->Add( seg, ADD_MODE::APPEND );
  2501. }
  2502. }
  2503. }
  2504. void ALTIUM_PCB::ConvertTracks6ToFootprintItem( FOOTPRINT* aFootprint, const ATRACK6& aElem,
  2505. const int aPrimitiveIndex,
  2506. const bool aIsBoardImport )
  2507. {
  2508. if( aElem.is_polygonoutline || aElem.subpolyindex != ALTIUM_POLYGON_NONE )
  2509. return;
  2510. if( aElem.is_keepout || aElem.layer == ALTIUM_LAYER::KEEP_OUT_LAYER
  2511. || IsAltiumLayerAPlane( aElem.layer ) )
  2512. {
  2513. // This is not the actual board item. We can use it to create the polygon for the region
  2514. PCB_SHAPE shape( nullptr, SHAPE_T::SEGMENT );
  2515. shape.SetStart( aElem.start );
  2516. shape.SetEnd( aElem.end );
  2517. shape.SetStroke( STROKE_PARAMS( aElem.width, PLOT_DASH_TYPE::SOLID ) );
  2518. HelperPcpShapeAsFootprintKeepoutRegion( aFootprint, shape, aElem.layer,
  2519. aElem.keepoutrestrictions );
  2520. }
  2521. else
  2522. {
  2523. for( PCB_LAYER_ID klayer : GetKicadLayersToIterate( aElem.layer ) )
  2524. {
  2525. if( aIsBoardImport && IsCopperLayer( klayer ) && aElem.net != ALTIUM_NET_UNCONNECTED )
  2526. {
  2527. // Special case: do to not lose net connections in footprints
  2528. ConvertTracks6ToBoardItemOnLayer( aElem, klayer );
  2529. }
  2530. else
  2531. {
  2532. ConvertTracks6ToFootprintItemOnLayer( aFootprint, aElem, klayer );
  2533. }
  2534. }
  2535. }
  2536. for( const auto& layerExpansionMask : HelperGetSolderAndPasteMaskExpansions(
  2537. ALTIUM_RECORD::TRACK, aPrimitiveIndex, aElem.layer ) )
  2538. {
  2539. int width = aElem.width + ( layerExpansionMask.second * 2 );
  2540. if( width > 1 )
  2541. {
  2542. PCB_SHAPE* seg = new PCB_SHAPE( aFootprint, SHAPE_T::SEGMENT );
  2543. seg->SetStart( aElem.start );
  2544. seg->SetEnd( aElem.end );
  2545. seg->SetStroke( STROKE_PARAMS( width, PLOT_DASH_TYPE::SOLID ) );
  2546. seg->SetLayer( layerExpansionMask.first );
  2547. aFootprint->Add( seg, ADD_MODE::APPEND );
  2548. }
  2549. }
  2550. }
  2551. void ALTIUM_PCB::ConvertTracks6ToBoardItemOnLayer( const ATRACK6& aElem, PCB_LAYER_ID aLayer )
  2552. {
  2553. if( IsCopperLayer( aLayer ) && aElem.net != ALTIUM_NET_UNCONNECTED )
  2554. {
  2555. PCB_TRACK* track = new PCB_TRACK( m_board );
  2556. track->SetStart( aElem.start );
  2557. track->SetEnd( aElem.end );
  2558. track->SetWidth( aElem.width );
  2559. track->SetLayer( aLayer );
  2560. track->SetNetCode( GetNetCode( aElem.net ) );
  2561. m_board->Add( track, ADD_MODE::APPEND );
  2562. }
  2563. else
  2564. {
  2565. PCB_SHAPE* seg = new PCB_SHAPE( m_board, SHAPE_T::SEGMENT );
  2566. seg->SetStart( aElem.start );
  2567. seg->SetEnd( aElem.end );
  2568. seg->SetStroke( STROKE_PARAMS( aElem.width, PLOT_DASH_TYPE::SOLID ) );
  2569. seg->SetLayer( aLayer );
  2570. m_board->Add( seg, ADD_MODE::APPEND );
  2571. }
  2572. }
  2573. void ALTIUM_PCB::ConvertTracks6ToFootprintItemOnLayer( FOOTPRINT* aFootprint, const ATRACK6& aElem,
  2574. PCB_LAYER_ID aLayer )
  2575. {
  2576. PCB_SHAPE* seg = new PCB_SHAPE( aFootprint, SHAPE_T::SEGMENT );
  2577. seg->SetStart( aElem.start );
  2578. seg->SetEnd( aElem.end );
  2579. seg->SetStroke( STROKE_PARAMS( aElem.width, PLOT_DASH_TYPE::SOLID ) );
  2580. seg->SetLayer( aLayer );
  2581. aFootprint->Add( seg, ADD_MODE::APPEND );
  2582. }
  2583. void ALTIUM_PCB::ParseWideStrings6Data( const ALTIUM_COMPOUND_FILE& aAltiumPcbFile,
  2584. const CFB::COMPOUND_FILE_ENTRY* aEntry )
  2585. {
  2586. if( m_progressReporter )
  2587. m_progressReporter->Report( _( "Loading unicode strings..." ) );
  2588. ALTIUM_PARSER reader( aAltiumPcbFile, aEntry );
  2589. m_unicodeStrings = reader.ReadWideStringTable();
  2590. if( reader.GetRemainingBytes() != 0 )
  2591. THROW_IO_ERROR( wxT( "WideStrings6 stream is not fully parsed" ) );
  2592. }
  2593. void ALTIUM_PCB::ParseTexts6Data( const ALTIUM_COMPOUND_FILE& aAltiumPcbFile,
  2594. const CFB::COMPOUND_FILE_ENTRY* aEntry )
  2595. {
  2596. if( m_progressReporter )
  2597. m_progressReporter->Report( _( "Loading text..." ) );
  2598. ALTIUM_PARSER reader( aAltiumPcbFile, aEntry );
  2599. while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
  2600. {
  2601. checkpoint();
  2602. ATEXT6 elem( reader, m_unicodeStrings );
  2603. if( elem.component == ALTIUM_COMPONENT_NONE )
  2604. {
  2605. ConvertTexts6ToBoardItem( elem );
  2606. }
  2607. else
  2608. {
  2609. FOOTPRINT* footprint = HelperGetFootprint( elem.component );
  2610. ConvertTexts6ToFootprintItem( footprint, elem );
  2611. }
  2612. }
  2613. if( reader.GetRemainingBytes() != 0 )
  2614. THROW_IO_ERROR( wxT( "Texts6 stream is not fully parsed" ) );
  2615. }
  2616. void ALTIUM_PCB::ConvertTexts6ToBoardItem( const ATEXT6& aElem )
  2617. {
  2618. if( aElem.fonttype == ALTIUM_TEXT_TYPE::BARCODE )
  2619. {
  2620. wxLogError( _( "Ignored barcode on Altium layer %d (not yet supported)." ), aElem.layer );
  2621. return;
  2622. }
  2623. for( PCB_LAYER_ID klayer : GetKicadLayersToIterate( aElem.layer ) )
  2624. ConvertTexts6ToBoardItemOnLayer( aElem, klayer );
  2625. }
  2626. void ALTIUM_PCB::ConvertTexts6ToFootprintItem( FOOTPRINT* aFootprint, const ATEXT6& aElem )
  2627. {
  2628. if( aElem.fonttype == ALTIUM_TEXT_TYPE::BARCODE )
  2629. {
  2630. wxLogError( _( "Ignored barcode on Altium layer %d (not yet supported)." ), aElem.layer );
  2631. return;
  2632. }
  2633. for( PCB_LAYER_ID klayer : GetKicadLayersToIterate( aElem.layer ) )
  2634. ConvertTexts6ToFootprintItemOnLayer( aFootprint, aElem, klayer );
  2635. }
  2636. void ALTIUM_PCB::ConvertTexts6ToBoardItemOnLayer( const ATEXT6& aElem, PCB_LAYER_ID aLayer )
  2637. {
  2638. PCB_TEXT* pcbText = new PCB_TEXT( m_board );
  2639. // TODO: improve parsing of variables
  2640. wxString trimmedText = aElem.text;
  2641. trimmedText.Trim();
  2642. if( trimmedText.CmpNoCase( wxT( ".Layer_Name" ) ) == 0 )
  2643. pcbText->SetText( wxT( "${LAYER}" ) );
  2644. else
  2645. pcbText->SetText( aElem.text );
  2646. pcbText->SetLayer( aLayer );
  2647. pcbText->SetPosition( aElem.position );
  2648. pcbText->SetTextAngle( EDA_ANGLE( aElem.rotation, DEGREES_T ) );
  2649. ConvertTexts6ToEdaTextSettings( aElem, pcbText );
  2650. m_board->Add( pcbText, ADD_MODE::APPEND );
  2651. }
  2652. void ALTIUM_PCB::ConvertTexts6ToFootprintItemOnLayer( FOOTPRINT* aFootprint, const ATEXT6& aElem,
  2653. PCB_LAYER_ID aLayer )
  2654. {
  2655. PCB_TEXT* fpText;
  2656. if( aElem.isDesignator )
  2657. {
  2658. fpText = &aFootprint->Reference(); // TODO: handle multiple layers
  2659. }
  2660. else if( aElem.isComment )
  2661. {
  2662. fpText = &aFootprint->Value(); // TODO: handle multiple layers
  2663. }
  2664. else
  2665. {
  2666. fpText = new PCB_TEXT( aFootprint );
  2667. aFootprint->Add( fpText, ADD_MODE::APPEND );
  2668. }
  2669. // TODO: improve parsing of variables
  2670. wxString trimmedText = aElem.text;
  2671. trimmedText.Trim();
  2672. if( !aElem.isDesignator && trimmedText.CmpNoCase( wxT( ".Designator" ) ) == 0 )
  2673. fpText->SetText( wxT( "${REFERENCE}" ) );
  2674. else if( !aElem.isComment && trimmedText.CmpNoCase( wxT( ".Comment" ) ) == 0 )
  2675. fpText->SetText( wxT( "${VALUE}" ) );
  2676. else if( trimmedText.CmpNoCase( wxT( ".Layer_Name" ) ) == 0 )
  2677. fpText->SetText( wxT( "${LAYER}" ) );
  2678. else
  2679. fpText->SetText( aElem.text );
  2680. fpText->SetKeepUpright( false );
  2681. fpText->SetLayer( aLayer );
  2682. fpText->SetPosition( aElem.position );
  2683. fpText->SetTextAngle( EDA_ANGLE( aElem.rotation, DEGREES_T ) );
  2684. ConvertTexts6ToEdaTextSettings( aElem, fpText );
  2685. }
  2686. void ALTIUM_PCB::ConvertTexts6ToEdaTextSettings( const ATEXT6& aElem, EDA_TEXT* aEdaText )
  2687. {
  2688. aEdaText->SetTextSize( VECTOR2I( aElem.height, aElem.height ) ); // TODO: parse text width
  2689. if( aElem.fonttype == ALTIUM_TEXT_TYPE::TRUETYPE )
  2690. {
  2691. KIFONT::FONT* font = KIFONT::FONT::GetFont( aElem.fontname, aElem.isBold, aElem.isItalic );
  2692. aEdaText->SetFont( font );
  2693. if( font->IsOutline() )
  2694. {
  2695. // TODO: why is this required? Somehow, truetype size is calculated differently
  2696. if( font->GetName().Contains( wxS( "Arial" ) ) )
  2697. aEdaText->SetTextSize( VECTOR2I( aElem.height * 0.63, aElem.height * 0.63 ) );
  2698. else
  2699. aEdaText->SetTextSize( VECTOR2I( aElem.height * 0.5, aElem.height * 0.5 ) );
  2700. }
  2701. }
  2702. aEdaText->SetTextThickness( aElem.strokewidth );
  2703. aEdaText->SetBold( aElem.isBold );
  2704. aEdaText->SetItalic( aElem.isItalic );
  2705. aEdaText->SetMirrored( aElem.isMirrored );
  2706. // Altium position always specifies the bottom left corner
  2707. aEdaText->SetHorizJustify( GR_TEXT_H_ALIGN_LEFT );
  2708. aEdaText->SetVertJustify( GR_TEXT_V_ALIGN_BOTTOM );
  2709. // TODO: correct the position and set proper justification
  2710. }
  2711. void ALTIUM_PCB::ParseFills6Data( const ALTIUM_COMPOUND_FILE& aAltiumPcbFile,
  2712. const CFB::COMPOUND_FILE_ENTRY* aEntry )
  2713. {
  2714. if( m_progressReporter )
  2715. m_progressReporter->Report( _( "Loading rectangles..." ) );
  2716. ALTIUM_PARSER reader( aAltiumPcbFile, aEntry );
  2717. while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
  2718. {
  2719. checkpoint();
  2720. AFILL6 elem( reader );
  2721. if( elem.component == ALTIUM_COMPONENT_NONE )
  2722. {
  2723. ConvertFills6ToBoardItem( elem );
  2724. }
  2725. else
  2726. {
  2727. FOOTPRINT* footprint = HelperGetFootprint( elem.component );
  2728. ConvertFills6ToFootprintItem( footprint, elem, true );
  2729. }
  2730. }
  2731. if( reader.GetRemainingBytes() != 0 )
  2732. THROW_IO_ERROR( "Fills6 stream is not fully parsed" );
  2733. }
  2734. void ALTIUM_PCB::ConvertFills6ToBoardItem( const AFILL6& aElem )
  2735. {
  2736. if( aElem.is_keepout || aElem.layer == ALTIUM_LAYER::KEEP_OUT_LAYER
  2737. || aElem.net != ALTIUM_NET_UNCONNECTED )
  2738. {
  2739. ConvertFills6ToBoardItemWithNet( aElem );
  2740. }
  2741. else
  2742. {
  2743. for( PCB_LAYER_ID klayer : GetKicadLayersToIterate( aElem.layer ) )
  2744. ConvertFills6ToBoardItemOnLayer( aElem, klayer );
  2745. }
  2746. }
  2747. void ALTIUM_PCB::ConvertFills6ToFootprintItem( FOOTPRINT* aFootprint, const AFILL6& aElem,
  2748. const bool aIsBoardImport )
  2749. {
  2750. if( aElem.is_keepout
  2751. || aElem.layer == ALTIUM_LAYER::KEEP_OUT_LAYER ) // TODO: what about plane layers?
  2752. {
  2753. // This is not the actual board item. We can use it to create the polygon for the region
  2754. PCB_SHAPE shape( nullptr, SHAPE_T::RECTANGLE );
  2755. shape.SetStart( aElem.pos1 );
  2756. shape.SetEnd( aElem.pos2 );
  2757. shape.SetStroke( STROKE_PARAMS( 0, PLOT_DASH_TYPE::SOLID ) );
  2758. if( aElem.rotation != 0. )
  2759. {
  2760. VECTOR2I center( ( aElem.pos1.x + aElem.pos2.x ) / 2,
  2761. ( aElem.pos1.y + aElem.pos2.y ) / 2 );
  2762. shape.Rotate( center, EDA_ANGLE( aElem.rotation, DEGREES_T ) );
  2763. }
  2764. HelperPcpShapeAsFootprintKeepoutRegion( aFootprint, shape, aElem.layer,
  2765. aElem.keepoutrestrictions );
  2766. }
  2767. else if( aIsBoardImport && IsAltiumLayerCopper( aElem.layer )
  2768. && aElem.net != ALTIUM_NET_UNCONNECTED )
  2769. {
  2770. // Special case: do to not lose net connections in footprints
  2771. ConvertFills6ToBoardItemWithNet( aElem );
  2772. }
  2773. else
  2774. {
  2775. for( PCB_LAYER_ID klayer : GetKicadLayersToIterate( aElem.layer ) )
  2776. ConvertFills6ToFootprintItemOnLayer( aFootprint, aElem, klayer );
  2777. }
  2778. }
  2779. void ALTIUM_PCB::ConvertFills6ToBoardItemWithNet( const AFILL6& aElem )
  2780. {
  2781. ZONE* zone = new ZONE( m_board );
  2782. m_board->Add( zone, ADD_MODE::APPEND );
  2783. zone->SetNetCode( GetNetCode( aElem.net ) );
  2784. zone->SetPosition( aElem.pos1 );
  2785. zone->SetAssignedPriority( 1000 );
  2786. HelperSetZoneLayers( zone, aElem.layer );
  2787. VECTOR2I p11( aElem.pos1.x, aElem.pos1.y );
  2788. VECTOR2I p12( aElem.pos1.x, aElem.pos2.y );
  2789. VECTOR2I p22( aElem.pos2.x, aElem.pos2.y );
  2790. VECTOR2I p21( aElem.pos2.x, aElem.pos1.y );
  2791. VECTOR2I center( ( aElem.pos1.x + aElem.pos2.x ) / 2, ( aElem.pos1.y + aElem.pos2.y ) / 2 );
  2792. const int outlineIdx = -1; // this is the id of the copper zone main outline
  2793. zone->AppendCorner( p11, outlineIdx );
  2794. zone->AppendCorner( p12, outlineIdx );
  2795. zone->AppendCorner( p22, outlineIdx );
  2796. zone->AppendCorner( p21, outlineIdx );
  2797. // should be correct?
  2798. zone->SetLocalClearance( 0 );
  2799. zone->SetPadConnection( ZONE_CONNECTION::FULL );
  2800. if( aElem.is_keepout || aElem.layer == ALTIUM_LAYER::KEEP_OUT_LAYER )
  2801. {
  2802. zone->SetIsRuleArea( true );
  2803. HelperSetZoneKeepoutRestrictions( zone, aElem.keepoutrestrictions );
  2804. }
  2805. if( aElem.rotation != 0. )
  2806. zone->Rotate( center, EDA_ANGLE( aElem.rotation, DEGREES_T ) );
  2807. zone->SetBorderDisplayStyle( ZONE_BORDER_DISPLAY_STYLE::DIAGONAL_EDGE,
  2808. ZONE::GetDefaultHatchPitch(), true );
  2809. }
  2810. void ALTIUM_PCB::ConvertFills6ToBoardItemOnLayer( const AFILL6& aElem, PCB_LAYER_ID aLayer )
  2811. {
  2812. PCB_SHAPE* fill = new PCB_SHAPE( m_board, SHAPE_T::RECTANGLE );
  2813. fill->SetFilled( true );
  2814. fill->SetLayer( aLayer );
  2815. fill->SetStroke( STROKE_PARAMS( 0 ) );
  2816. fill->SetStart( aElem.pos1 );
  2817. fill->SetEnd( aElem.pos2 );
  2818. if( aElem.rotation != 0. )
  2819. {
  2820. // TODO: Do we need SHAPE_T::POLY for non 90° rotations?
  2821. VECTOR2I center( ( aElem.pos1.x + aElem.pos2.x ) / 2, ( aElem.pos1.y + aElem.pos2.y ) / 2 );
  2822. fill->Rotate( center, EDA_ANGLE( aElem.rotation, DEGREES_T ) );
  2823. }
  2824. m_board->Add( fill, ADD_MODE::APPEND );
  2825. }
  2826. void ALTIUM_PCB::ConvertFills6ToFootprintItemOnLayer( FOOTPRINT* aFootprint, const AFILL6& aElem,
  2827. PCB_LAYER_ID aLayer )
  2828. {
  2829. PCB_SHAPE* fill = new PCB_SHAPE( aFootprint, SHAPE_T::RECTANGLE );
  2830. fill->SetFilled( true );
  2831. fill->SetLayer( aLayer );
  2832. fill->SetStroke( STROKE_PARAMS( 0 ) );
  2833. fill->SetStart( aElem.pos1 );
  2834. fill->SetEnd( aElem.pos2 );
  2835. if( aElem.rotation != 0. )
  2836. {
  2837. // TODO: Do we need SHAPE_T::POLY for non 90° rotations?
  2838. VECTOR2I center( ( aElem.pos1.x + aElem.pos2.x ) / 2, ( aElem.pos1.y + aElem.pos2.y ) / 2 );
  2839. fill->Rotate( center, EDA_ANGLE( aElem.rotation, DEGREES_T ) );
  2840. }
  2841. aFootprint->Add( fill, ADD_MODE::APPEND );
  2842. }
  2843. void ALTIUM_PCB::HelperSetZoneLayers( ZONE* aZone, const ALTIUM_LAYER aAltiumLayer )
  2844. {
  2845. if( aAltiumLayer == ALTIUM_LAYER::MULTI_LAYER || aAltiumLayer == ALTIUM_LAYER::KEEP_OUT_LAYER )
  2846. {
  2847. aZone->SetLayerSet( LSET::AllCuMask() );
  2848. }
  2849. else
  2850. {
  2851. LSET layerSet;
  2852. for( PCB_LAYER_ID klayer : GetKicadLayersToIterate( aAltiumLayer ) )
  2853. layerSet.set( klayer );
  2854. aZone->SetLayerSet( layerSet );
  2855. }
  2856. }
  2857. void ALTIUM_PCB::HelperSetZoneKeepoutRestrictions( ZONE* aZone, const uint8_t aKeepoutRestrictions )
  2858. {
  2859. bool keepoutRestrictionVia = ( aKeepoutRestrictions & 0x01 ) != 0;
  2860. bool keepoutRestrictionTrack = ( aKeepoutRestrictions & 0x02 ) != 0;
  2861. bool keepoutRestrictionCopper = ( aKeepoutRestrictions & 0x04 ) != 0;
  2862. bool keepoutRestrictionSMDPad = ( aKeepoutRestrictions & 0x08 ) != 0;
  2863. bool keepoutRestrictionTHPad = ( aKeepoutRestrictions & 0x10 ) != 0;
  2864. aZone->SetDoNotAllowVias( keepoutRestrictionVia );
  2865. aZone->SetDoNotAllowTracks( keepoutRestrictionTrack );
  2866. aZone->SetDoNotAllowCopperPour( keepoutRestrictionCopper );
  2867. aZone->SetDoNotAllowPads( keepoutRestrictionSMDPad && keepoutRestrictionTHPad );
  2868. aZone->SetDoNotAllowFootprints( false );
  2869. }
  2870. void ALTIUM_PCB::HelperPcpShapeAsBoardKeepoutRegion( const PCB_SHAPE& aShape,
  2871. const ALTIUM_LAYER aAltiumLayer,
  2872. const uint8_t aKeepoutRestrictions )
  2873. {
  2874. ZONE* zone = new ZONE( m_board );
  2875. zone->SetIsRuleArea( true );
  2876. HelperSetZoneLayers( zone, aAltiumLayer );
  2877. HelperSetZoneKeepoutRestrictions( zone, aKeepoutRestrictions );
  2878. aShape.EDA_SHAPE::TransformShapeToPolygon( *zone->Outline(), 0, ARC_HIGH_DEF, ERROR_INSIDE );
  2879. zone->SetBorderDisplayStyle( ZONE_BORDER_DISPLAY_STYLE::DIAGONAL_EDGE,
  2880. ZONE::GetDefaultHatchPitch(), true );
  2881. m_board->Add( zone, ADD_MODE::APPEND );
  2882. }
  2883. void ALTIUM_PCB::HelperPcpShapeAsFootprintKeepoutRegion( FOOTPRINT* aFootprint,
  2884. const PCB_SHAPE& aShape,
  2885. const ALTIUM_LAYER aAltiumLayer,
  2886. const uint8_t aKeepoutRestrictions )
  2887. {
  2888. ZONE* zone = new ZONE( aFootprint );
  2889. zone->SetIsRuleArea( true );
  2890. HelperSetZoneLayers( zone, aAltiumLayer );
  2891. HelperSetZoneKeepoutRestrictions( zone, aKeepoutRestrictions );
  2892. aShape.EDA_SHAPE::TransformShapeToPolygon( *zone->Outline(), 0, ARC_HIGH_DEF, ERROR_INSIDE );
  2893. zone->SetBorderDisplayStyle( ZONE_BORDER_DISPLAY_STYLE::DIAGONAL_EDGE,
  2894. ZONE::GetDefaultHatchPitch(), true );
  2895. // TODO: zone->SetLocalCoord(); missing?
  2896. aFootprint->Add( zone, ADD_MODE::APPEND );
  2897. }
  2898. std::vector<std::pair<PCB_LAYER_ID, int>> ALTIUM_PCB::HelperGetSolderAndPasteMaskExpansions(
  2899. const ALTIUM_RECORD aType, const int aPrimitiveIndex, const ALTIUM_LAYER aAltiumLayer )
  2900. {
  2901. if( m_extendedPrimitiveInformationMaps.count( aType ) == 0 )
  2902. return {}; // there is nothing to parse
  2903. auto elems =
  2904. m_extendedPrimitiveInformationMaps[ALTIUM_RECORD::TRACK].equal_range( aPrimitiveIndex );
  2905. if( elems.first == elems.second )
  2906. return {}; // there is nothing to parse
  2907. std::vector<std::pair<PCB_LAYER_ID, int>> layerExpansionPairs;
  2908. for( auto it = elems.first; it != elems.second; ++it )
  2909. {
  2910. const AEXTENDED_PRIMITIVE_INFORMATION& pInf = it->second;
  2911. if( pInf.type == AEXTENDED_PRIMITIVE_INFORMATION_TYPE::MASK )
  2912. {
  2913. if( pInf.soldermaskexpansionmode == ALTIUM_MODE::MANUAL
  2914. || pInf.soldermaskexpansionmode == ALTIUM_MODE::RULE )
  2915. {
  2916. // TODO: what layers can lead to solder or paste mask usage? E.g. KEEP_OUT_LAYER and other top/bottom layers
  2917. if( aAltiumLayer == ALTIUM_LAYER::TOP_LAYER
  2918. || aAltiumLayer == ALTIUM_LAYER::MULTI_LAYER )
  2919. {
  2920. layerExpansionPairs.emplace_back( F_Mask, pInf.soldermaskexpansionmanual );
  2921. }
  2922. if( aAltiumLayer == ALTIUM_LAYER::BOTTOM_LAYER
  2923. || aAltiumLayer == ALTIUM_LAYER::MULTI_LAYER )
  2924. {
  2925. layerExpansionPairs.emplace_back( B_Mask, pInf.soldermaskexpansionmanual );
  2926. }
  2927. }
  2928. if( pInf.pastemaskexpansionmode == ALTIUM_MODE::MANUAL
  2929. || pInf.pastemaskexpansionmode == ALTIUM_MODE::RULE )
  2930. {
  2931. if( aAltiumLayer == ALTIUM_LAYER::TOP_LAYER
  2932. || aAltiumLayer == ALTIUM_LAYER::MULTI_LAYER )
  2933. {
  2934. layerExpansionPairs.emplace_back( F_Paste, pInf.pastemaskexpansionmanual );
  2935. }
  2936. if( aAltiumLayer == ALTIUM_LAYER::BOTTOM_LAYER
  2937. || aAltiumLayer == ALTIUM_LAYER::MULTI_LAYER )
  2938. {
  2939. layerExpansionPairs.emplace_back( B_Paste, pInf.pastemaskexpansionmanual );
  2940. }
  2941. }
  2942. }
  2943. }
  2944. return layerExpansionPairs;
  2945. }