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.

296 lines
9.3 KiB

  1. /*
  2. * This program source code file is part of KiCad, a free EDA CAD application.
  3. *
  4. * Copyright (C) 2019 Thomas Pointhuber <thomas.pointhuber@gmx.at>
  5. * Copyright (C) 2020-2023 KiCad Developers, see AUTHORS.txt for contributors.
  6. *
  7. * This program is free software; you can redistribute it and/or
  8. * modify it under the terms of the GNU General Public License
  9. * as published by the Free Software Foundation; either version 2
  10. * of the License, or (at your option) any later version.
  11. *
  12. * This program is distributed in the hope that it will be useful,
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. * GNU General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU General Public License
  18. * along with this program; if not, you may find one here:
  19. * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
  20. * or you may search the http://www.gnu.org website for the version 2 license,
  21. * or you may write to the Free Software Foundation, Inc.,
  22. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
  23. */
  24. /**
  25. * @brief Pcbnew PLUGIN for Altium *.PcbDoc format.
  26. */
  27. #include <wx/string.h>
  28. #include <pcb_io_altium_designer.h>
  29. #include <altium_pcb.h>
  30. #include <io/io_utils.h>
  31. #include <io/altium/altium_parser.h>
  32. #include <pcb_io/pcb_io.h>
  33. #include <board.h>
  34. #include <compoundfilereader.h>
  35. #include <utf.h>
  36. PCB_IO_ALTIUM_DESIGNER::PCB_IO_ALTIUM_DESIGNER()
  37. {
  38. m_board = nullptr;
  39. m_props = nullptr;
  40. }
  41. PCB_IO_ALTIUM_DESIGNER::~PCB_IO_ALTIUM_DESIGNER()
  42. {
  43. }
  44. bool PCB_IO_ALTIUM_DESIGNER::checkFileHeader( const wxString& aFileName )
  45. {
  46. // Compound File Binary Format header
  47. return IO_UTILS::fileStartsWithBinaryHeader( aFileName, IO_UTILS::COMPOUND_FILE_HEADER );
  48. }
  49. bool PCB_IO_ALTIUM_DESIGNER::CanReadBoard( const wxString& aFileName ) const
  50. {
  51. if( !PCB_IO::CanReadBoard( aFileName ) )
  52. return false;
  53. return checkFileHeader( aFileName );
  54. }
  55. bool PCB_IO_ALTIUM_DESIGNER::CanReadFootprintLib( const wxString& aFileName ) const
  56. {
  57. if( !PCB_IO::CanReadFootprintLib( aFileName ) )
  58. return false;
  59. return checkFileHeader( aFileName );
  60. }
  61. BOARD* PCB_IO_ALTIUM_DESIGNER::LoadBoard( const wxString& aFileName, BOARD* aAppendToMe,
  62. const STRING_UTF8_MAP* aProperties, PROJECT* aProject,
  63. PROGRESS_REPORTER* aProgressReporter )
  64. {
  65. m_props = aProperties;
  66. m_board = aAppendToMe ? aAppendToMe : new BOARD();
  67. // Give the filename to the board if it's new
  68. if( !aAppendToMe )
  69. m_board->SetFileName( aFileName );
  70. // clang-format off
  71. const std::map<ALTIUM_PCB_DIR, std::string> mapping = {
  72. { ALTIUM_PCB_DIR::FILE_HEADER, "FileHeader" },
  73. { ALTIUM_PCB_DIR::ARCS6, "Arcs6" },
  74. { ALTIUM_PCB_DIR::BOARD6, "Board6" },
  75. { ALTIUM_PCB_DIR::BOARDREGIONS, "BoardRegions" },
  76. { ALTIUM_PCB_DIR::CLASSES6, "Classes6" },
  77. { ALTIUM_PCB_DIR::COMPONENTS6, "Components6" },
  78. { ALTIUM_PCB_DIR::COMPONENTBODIES6, "ComponentBodies6" },
  79. { ALTIUM_PCB_DIR::DIMENSIONS6, "Dimensions6" },
  80. { ALTIUM_PCB_DIR::EXTENDPRIMITIVEINFORMATION, "ExtendedPrimitiveInformation" },
  81. { ALTIUM_PCB_DIR::FILLS6, "Fills6" },
  82. { ALTIUM_PCB_DIR::MODELS, "Models" },
  83. { ALTIUM_PCB_DIR::NETS6, "Nets6" },
  84. { ALTIUM_PCB_DIR::PADS6, "Pads6" },
  85. { ALTIUM_PCB_DIR::POLYGONS6, "Polygons6" },
  86. { ALTIUM_PCB_DIR::REGIONS6, "Regions6" },
  87. { ALTIUM_PCB_DIR::RULES6, "Rules6" },
  88. { ALTIUM_PCB_DIR::SHAPEBASEDREGIONS6, "ShapeBasedRegions6" },
  89. { ALTIUM_PCB_DIR::TEXTS6, "Texts6" },
  90. { ALTIUM_PCB_DIR::TRACKS6, "Tracks6" },
  91. { ALTIUM_PCB_DIR::VIAS6, "Vias6" },
  92. { ALTIUM_PCB_DIR::WIDESTRINGS6, "WideStrings6" }
  93. };
  94. // clang-format on
  95. ALTIUM_COMPOUND_FILE altiumPcbFile( aFileName );
  96. try
  97. {
  98. // Parse File
  99. ALTIUM_PCB pcb( m_board, aProgressReporter );
  100. pcb.Parse( altiumPcbFile, mapping );
  101. }
  102. catch( CFB::CFBException& exception )
  103. {
  104. THROW_IO_ERROR( exception.what() );
  105. }
  106. return m_board;
  107. }
  108. long long PCB_IO_ALTIUM_DESIGNER::GetLibraryTimestamp( const wxString& aLibraryPath ) const
  109. {
  110. // File hasn't been loaded yet.
  111. if( aLibraryPath.IsEmpty() )
  112. {
  113. return 0;
  114. }
  115. wxFileName fn( aLibraryPath );
  116. if( fn.IsFileReadable() && fn.GetModificationTime().IsValid() )
  117. {
  118. return fn.GetModificationTime().GetValue().GetValue();
  119. }
  120. else
  121. {
  122. return 0;
  123. }
  124. }
  125. void PCB_IO_ALTIUM_DESIGNER::loadAltiumLibrary( const wxString& aLibraryPath )
  126. {
  127. try
  128. {
  129. auto it = m_fplibFiles.find( aLibraryPath );
  130. if( it != m_fplibFiles.end() )
  131. return; // Already loaded
  132. if( aLibraryPath.Lower().EndsWith( wxS( ".pcblib" ) ) )
  133. {
  134. m_fplibFiles[aLibraryPath].emplace_back(
  135. std::make_unique<ALTIUM_COMPOUND_FILE>( aLibraryPath ) );
  136. }
  137. else if( aLibraryPath.Lower().EndsWith( wxS( ".intlib" ) ) )
  138. {
  139. std::unique_ptr<ALTIUM_COMPOUND_FILE> intCom =
  140. std::make_unique<ALTIUM_COMPOUND_FILE>( aLibraryPath );
  141. std::map<wxString, const CFB::COMPOUND_FILE_ENTRY*> pcbLibFiles =
  142. intCom->EnumDir( L"PCBLib" );
  143. for( const auto& [pcbLibName, pcbCfe] : pcbLibFiles )
  144. m_fplibFiles[aLibraryPath].push_back( intCom->DecodeIntLibStream( *pcbCfe ) );
  145. }
  146. }
  147. catch( CFB::CFBException& exception )
  148. {
  149. THROW_IO_ERROR( exception.what() );
  150. }
  151. }
  152. void PCB_IO_ALTIUM_DESIGNER::FootprintEnumerate( wxArrayString& aFootprintNames,
  153. const wxString& aLibraryPath, bool aBestEfforts,
  154. const STRING_UTF8_MAP* aProperties )
  155. {
  156. loadAltiumLibrary( aLibraryPath );
  157. auto it = m_fplibFiles.find( aLibraryPath );
  158. if( it == m_fplibFiles.end() )
  159. return; // No footprint libraries in file, ignore.
  160. try
  161. {
  162. for( auto& altiumLibFile : it->second )
  163. {
  164. // Map code-page-dependent names to unicode names
  165. std::map<wxString, wxString> patternMap = altiumLibFile->ListLibFootprints();
  166. const std::vector<std::string> streamName = { "Library", "Data" };
  167. const CFB::COMPOUND_FILE_ENTRY* libraryData = altiumLibFile->FindStream( streamName );
  168. if( libraryData == nullptr )
  169. {
  170. THROW_IO_ERROR( wxString::Format( _( "File not found: '%s'." ),
  171. FormatPath( streamName ) ) );
  172. }
  173. ALTIUM_PARSER parser( *altiumLibFile, libraryData );
  174. std::map<wxString, wxString> properties = parser.ReadProperties();
  175. uint32_t numberOfFootprints = parser.Read<uint32_t>();
  176. aFootprintNames.Alloc( numberOfFootprints );
  177. for( size_t i = 0; i < numberOfFootprints; i++ )
  178. {
  179. parser.ReadAndSetSubrecordLength();
  180. wxScopedCharBuffer charBuffer = parser.ReadCharBuffer();
  181. wxString fpPattern( charBuffer, wxConvISO8859_1 );
  182. auto it = patternMap.find( fpPattern );
  183. if( it != patternMap.end() )
  184. {
  185. aFootprintNames.Add( it->second ); // Proper unicode name
  186. }
  187. else
  188. {
  189. THROW_IO_ERROR(
  190. wxString::Format( "Component name not found: '%s'", fpPattern ) );
  191. }
  192. parser.SkipSubrecord();
  193. }
  194. if( parser.HasParsingError() )
  195. {
  196. THROW_IO_ERROR( wxString::Format( "%s stream was not parsed correctly",
  197. FormatPath( streamName ) ) );
  198. }
  199. if( parser.GetRemainingBytes() != 0 )
  200. {
  201. THROW_IO_ERROR( wxString::Format( "%s stream is not fully parsed",
  202. FormatPath( streamName ) ) );
  203. }
  204. }
  205. }
  206. catch( CFB::CFBException& exception )
  207. {
  208. THROW_IO_ERROR( exception.what() );
  209. }
  210. }
  211. FOOTPRINT* PCB_IO_ALTIUM_DESIGNER::FootprintLoad( const wxString& aLibraryPath,
  212. const wxString& aFootprintName, bool aKeepUUID,
  213. const STRING_UTF8_MAP* aProperties )
  214. {
  215. loadAltiumLibrary( aLibraryPath );
  216. auto it = m_fplibFiles.find( aLibraryPath );
  217. if( it == m_fplibFiles.end() )
  218. THROW_IO_ERROR( _( "No footprints in library" ) );
  219. try
  220. {
  221. for( auto& altiumLibFile : it->second )
  222. {
  223. auto [dirName, fpCfe] = altiumLibFile->FindLibFootprintDirName( aFootprintName );
  224. if( dirName.IsEmpty() )
  225. continue;
  226. // Parse File
  227. ALTIUM_PCB pcb( m_board, nullptr );
  228. return pcb.ParseFootprint( *altiumLibFile, aFootprintName );
  229. }
  230. }
  231. catch( CFB::CFBException& exception )
  232. {
  233. THROW_IO_ERROR( exception.what() );
  234. }
  235. THROW_IO_ERROR(
  236. wxString::Format( _( "Footprint directory not found: '%s'." ), aFootprintName ) );
  237. return nullptr;
  238. }