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.

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