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.

2293 lines
76 KiB

Added NETINFO_MAPPING, to ease saving nets with consecutive net codes (without modifying the net codes during the run time). Now, nets are saved with consecutive net codes (both modern & legacy plugins). Zones are saved together with their nets, without depending on the fact if there are any pads with such net. Therefore validation of zone net names was removed (pcbnew/class_board.cpp). Performed tests: - Changed a pad's net name from empty to existent - ok, name was changed. - Changed a pad's net name from empty to nonexistent - ok, error message is displayed, net name stays empty. - Changed a pad's net name from existent to empty - ok, net name became empty - Changed a pad's net name from existent to nonexistent - ok, error message is displayed, net name is not changed. - Drawn a zone that belongs to a net, then modified schematics so the net does not exist anymore. After reloading the net list, all pads/tracks are updated. Zones still belongs to the net that does not exist in the schematic (but still exists in .kicad_pcb file). After running DRC, the zone becomes not filled. - Undo & redo affects assignment of a polygon to a specific net (you may change net of a polygon, refill it and undo/redo the changes). - KiCad s-expr & legacy, Eagle, P-CAD boards seem to load without any problem (they also contain correct net names assigned to the appropriate pads). All types of board file formats were loaded, then saved in sexpr format and reopened with a KiCad built from the master branch (without my modifications). - A few boards were also saved using the legacy format and were opened with the master KiCad without any issues. - Change a net name for a pad, restore with undo/redo - ok - Remove everything, restore with undo - ok - Remove everything, reload netlist - ok Differences observed between files saved by the master branch KiCad and this one: - list of nets are not saved in any particular order, so net codes may differ - the default net class does not contain the unconnected net
12 years ago
Modular-Kicad milestone B), major portions: *) Rework the set language support, simplify it by using KIWAY. Now any major frame with a "change language" menu can change the language for all KIWAY_PLAYERs in the whole KIWAY. Multiple KIWAYs are not supported yet. *) Simplify "modal wxFrame" support, and add that support exclusively to KIWAY_PLAYER where it is inherited by all derivatives. The function KIWAY_PLAYER::ShowModal() is in the vtable and so is cross module capable. *) Remove the requirements and assumptions that the wxFrame hierarchy always had PCB_EDIT_FRAME and SCH_EDIT_FRAME as immediate parents of their viewers and editors. This is no longer the case, nor required. *) Use KIWAY::Player() everywhere to make KIWAY_PLAYERs, this registers the KIWAY_PLAYER within the KIWAY and makes it very easy to find an open frame quickly. It also gives control to the KIWAY as to frame hierarchical relationships. *) Change single_top to use the KIWAY for loading a KIFACE and instantiating the single KIWAY_PLAYER, see bullet immediately above. *) Add KIWAY::OnKiwayEnd() and call it from PGM_BASE at program termination, this gives the KIFACEs a chance to save their final configuration dope to disk. *) Add dedicated FRAME_T's for the modal frames, so m_Ident can be tested and these modal frames are distinctly different than their non-modal equivalents. KIWAY_PLAYER::IsModal() is !not! a valid test during the wxFrame's constructor, so this is another important reason for having a dedicated FRAME_T for each modal wxFrame. On balance, more lines were deleted than were added to achieve all this.
12 years ago
14 years ago
14 years ago
14 years ago
14 years ago
14 years ago
* KIWAY Milestone A): Make major modules into DLL/DSOs. ! The initial testing of this commit should be done using a Debug build so that all the wxASSERT()s are enabled. Also, be sure and keep enabled the USE_KIWAY_DLLs option. The tree won't likely build without it. Turning it off is senseless anyways. If you want stable code, go back to a prior version, the one tagged with "stable". * Relocate all functionality out of the wxApp derivative into more finely targeted purposes: a) DLL/DSO specific b) PROJECT specific c) EXE or process specific d) configuration file specific data e) configuration file manipulations functions. All of this functionality was blended into an extremely large wxApp derivative and that was incompatible with the desire to support multiple concurrently loaded DLL/DSO's ("KIFACE")s and multiple concurrently open projects. An amazing amount of organization come from simply sorting each bit of functionality into the proper box. * Switch to wxConfigBase from wxConfig everywhere except instantiation. * Add classes KIWAY, KIFACE, KIFACE_I, SEARCH_STACK, PGM_BASE, PGM_KICAD, PGM_SINGLE_TOP, * Remove "Return" prefix on many function names. * Remove obvious comments from CMakeLists.txt files, and from else() and endif()s. * Fix building boost for use in a DSO on linux. * Remove some of the assumptions in the CMakeLists.txt files that windows had to be the host platform when building windows binaries. * Reduce the number of wxStrings being constructed at program load time via static construction. * Pass wxConfigBase* to all SaveSettings() and LoadSettings() functions so that these functions are useful even when the wxConfigBase comes from another source, as is the case in the KICAD_MANAGER_FRAME. * Move the setting of the KIPRJMOD environment variable into class PROJECT, so that it can be moved into a project variable soon, and out of FP_LIB_TABLE. * Add the KIWAY_PLAYER which is associated with a particular PROJECT, and all its child wxFrames and wxDialogs now have a Kiway() member function which returns a KIWAY& that that window tree branch is in support of. This is like wxWindows DNA in that child windows get this member with proper value at time of construction. * Anticipate some of the needs for milestones B) and C) and make code adjustments now in an effort to reduce work in those milestones. * No testing has been done for python scripting, since milestone C) has that being largely reworked and re-thought-out.
12 years ago
Added NETINFO_MAPPING, to ease saving nets with consecutive net codes (without modifying the net codes during the run time). Now, nets are saved with consecutive net codes (both modern & legacy plugins). Zones are saved together with their nets, without depending on the fact if there are any pads with such net. Therefore validation of zone net names was removed (pcbnew/class_board.cpp). Performed tests: - Changed a pad's net name from empty to existent - ok, name was changed. - Changed a pad's net name from empty to nonexistent - ok, error message is displayed, net name stays empty. - Changed a pad's net name from existent to empty - ok, net name became empty - Changed a pad's net name from existent to nonexistent - ok, error message is displayed, net name is not changed. - Drawn a zone that belongs to a net, then modified schematics so the net does not exist anymore. After reloading the net list, all pads/tracks are updated. Zones still belongs to the net that does not exist in the schematic (but still exists in .kicad_pcb file). After running DRC, the zone becomes not filled. - Undo & redo affects assignment of a polygon to a specific net (you may change net of a polygon, refill it and undo/redo the changes). - KiCad s-expr & legacy, Eagle, P-CAD boards seem to load without any problem (they also contain correct net names assigned to the appropriate pads). All types of board file formats were loaded, then saved in sexpr format and reopened with a KiCad built from the master branch (without my modifications). - A few boards were also saved using the legacy format and were opened with the master KiCad without any issues. - Change a net name for a pad, restore with undo/redo - ok - Remove everything, restore with undo - ok - Remove everything, reload netlist - ok Differences observed between files saved by the master branch KiCad and this one: - list of nets are not saved in any particular order, so net codes may differ - the default net class does not contain the unconnected net
12 years ago
  1. /*
  2. * This program source code file is part of KiCad, a free EDA CAD application.
  3. *
  4. * Copyright (C) 2012 CERN
  5. * Copyright (C) 1992-2018 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 <fctsys.h>
  25. #include <kicad_string.h>
  26. #include <common.h>
  27. #include <build_version.h> // LEGACY_BOARD_FILE_VERSION
  28. #include <macros.h>
  29. #include <wildcards_and_files_ext.h>
  30. #include <base_units.h>
  31. #include <trace_helpers.h>
  32. #include <class_board.h>
  33. #include <class_module.h>
  34. #include <class_pcb_text.h>
  35. #include <class_dimension.h>
  36. #include <class_track.h>
  37. #include <class_zone.h>
  38. #include <class_drawsegment.h>
  39. #include <class_pcb_target.h>
  40. #include <class_edge_mod.h>
  41. #include <pcb_plot_params.h>
  42. #include <zones.h>
  43. #include <kicad_plugin.h>
  44. #include <pcb_parser.h>
  45. #include <wx/dir.h>
  46. #include <wx/filename.h>
  47. #include <wx/wfstream.h>
  48. #include <boost/ptr_container/ptr_map.hpp>
  49. #include <memory.h>
  50. #include <connectivity/connectivity_data.h>
  51. #include <convert_basic_shapes_to_polygon.h> // for enum RECT_CHAMFER_POSITIONS definition
  52. using namespace PCB_KEYS_T;
  53. ///> Removes empty nets (i.e. with node count equal zero) from net classes
  54. void filterNetClass( const BOARD& aBoard, NETCLASS& aNetClass )
  55. {
  56. auto connectivity = aBoard.GetConnectivity();
  57. for( NETCLASS::iterator it = aNetClass.begin(); it != aNetClass.end(); )
  58. {
  59. NETINFO_ITEM* netinfo = aBoard.FindNet( *it );
  60. if( netinfo && connectivity->GetNodeCount( netinfo->GetNet() ) <= 0 ) // hopefully there are no nets with negative
  61. aNetClass.Remove( it++ ); // node count, but you never know..
  62. else
  63. ++it;
  64. }
  65. }
  66. /**
  67. * Class FP_CACHE_ITEM
  68. * is helper class for creating a footprint library cache.
  69. *
  70. * The new footprint library design is a file path of individual module files
  71. * that contain a single module per file. This class is a helper only for the
  72. * footprint portion of the PLUGIN API, and only for the #PCB_IO plugin. It is
  73. * private to this implementation file so it is not placed into a header.
  74. */
  75. class FP_CACHE_ITEM
  76. {
  77. WX_FILENAME m_filename;
  78. std::unique_ptr<MODULE> m_module;
  79. public:
  80. FP_CACHE_ITEM( MODULE* aModule, const WX_FILENAME& aFileName );
  81. const WX_FILENAME& GetFileName() const { return m_filename; }
  82. const MODULE* GetModule() const { return m_module.get(); }
  83. };
  84. FP_CACHE_ITEM::FP_CACHE_ITEM( MODULE* aModule, const WX_FILENAME& aFileName ) :
  85. m_filename( aFileName ),
  86. m_module( aModule )
  87. { }
  88. typedef boost::ptr_map< wxString, FP_CACHE_ITEM > MODULE_MAP;
  89. typedef MODULE_MAP::iterator MODULE_ITER;
  90. typedef MODULE_MAP::const_iterator MODULE_CITER;
  91. class FP_CACHE
  92. {
  93. PCB_IO* m_owner; // Plugin object that owns the cache.
  94. wxFileName m_lib_path; // The path of the library.
  95. wxString m_lib_raw_path; // For quick comparisons.
  96. MODULE_MAP m_modules; // Map of footprint file name per MODULE*.
  97. bool m_cache_dirty; // Stored separately because it's expensive to check
  98. // m_cache_timestamp against all the files.
  99. long long m_cache_timestamp; // A hash of the timestamps for all the footprint
  100. // files.
  101. public:
  102. FP_CACHE( PCB_IO* aOwner, const wxString& aLibraryPath );
  103. wxString GetPath() const { return m_lib_raw_path; }
  104. bool IsWritable() const { return m_lib_path.IsOk() && m_lib_path.IsDirWritable(); }
  105. bool Exists() const { return m_lib_path.IsOk() && m_lib_path.DirExists(); }
  106. MODULE_MAP& GetModules() { return m_modules; }
  107. // Most all functions in this class throw IO_ERROR exceptions. There are no
  108. // error codes nor user interface calls from here, nor in any PLUGIN.
  109. // Catch these exceptions higher up please.
  110. /**
  111. * Function Save
  112. * Save the footprint cache or a single module from it to disk
  113. *
  114. * @param aModule if set, save only this module, otherwise, save the full library
  115. */
  116. void Save( MODULE* aModule = NULL );
  117. void Load();
  118. void Remove( const wxString& aFootprintName );
  119. /**
  120. * Function GetTimestamp
  121. * Generate a timestamp representing all source files in the cache (including the
  122. * parent directory).
  123. * Timestamps should not be considered ordered. They either match or they don't.
  124. */
  125. static long long GetTimestamp( const wxString& aLibPath );
  126. /**
  127. * Function IsModified
  128. * Return true if the cache is not up-to-date.
  129. */
  130. bool IsModified();
  131. /**
  132. * Function IsPath
  133. * checks if \a aPath is the same as the current cache path.
  134. *
  135. * This tests paths by converting \a aPath using the native separators. Internally
  136. * #FP_CACHE stores the current path using native separators. This prevents path
  137. * miscompares on Windows due to the fact that paths can be stored with / instead of \\
  138. * in the footprint library table.
  139. *
  140. * @param aPath is the library path to test against.
  141. * @return true if \a aPath is the same as the cache path.
  142. */
  143. bool IsPath( const wxString& aPath ) const;
  144. };
  145. FP_CACHE::FP_CACHE( PCB_IO* aOwner, const wxString& aLibraryPath )
  146. {
  147. m_owner = aOwner;
  148. m_lib_raw_path = aLibraryPath;
  149. m_lib_path.SetPath( aLibraryPath );
  150. m_cache_timestamp = 0;
  151. m_cache_dirty = true;
  152. }
  153. void FP_CACHE::Save( MODULE* aModule )
  154. {
  155. m_cache_timestamp = 0;
  156. if( !m_lib_path.DirExists() && !m_lib_path.Mkdir() )
  157. {
  158. THROW_IO_ERROR( wxString::Format( _( "Cannot create footprint library path \"%s\"" ),
  159. m_lib_raw_path ) );
  160. }
  161. if( !m_lib_path.IsDirWritable() )
  162. {
  163. THROW_IO_ERROR( wxString::Format( _( "Footprint library path \"%s\" is read only" ),
  164. m_lib_raw_path ) );
  165. }
  166. for( MODULE_ITER it = m_modules.begin(); it != m_modules.end(); ++it )
  167. {
  168. if( aModule && aModule != it->second->GetModule() )
  169. continue;
  170. WX_FILENAME fn = it->second->GetFileName();
  171. wxString tempFileName =
  172. #ifdef USE_TMP_FILE
  173. wxFileName::CreateTempFileName( fn.GetPath() );
  174. #else
  175. fn.GetFullPath();
  176. #endif
  177. // Allow file output stream to go out of scope to close the file stream before
  178. // renaming the file.
  179. {
  180. wxLogTrace( traceKicadPcbPlugin, wxT( "Creating temporary library file %s" ),
  181. GetChars( tempFileName ) );
  182. FILE_OUTPUTFORMATTER formatter( tempFileName );
  183. m_owner->SetOutputFormatter( &formatter );
  184. m_owner->Format( (BOARD_ITEM*) it->second->GetModule() );
  185. }
  186. #ifdef USE_TMP_FILE
  187. wxRemove( fn.GetFullPath() ); // it is not an error if this does not exist
  188. // Even on linux you can see an _intermittent_ error when calling wxRename(),
  189. // and it is fully inexplicable. See if this dodges the error.
  190. wxMilliSleep( 250L );
  191. if( !wxRenameFile( tempFileName, fn.GetFullPath() ) )
  192. {
  193. wxString msg = wxString::Format(
  194. _( "Cannot rename temporary file \"%s\" to footprint library file \"%s\"" ),
  195. GetChars( tempFileName ),
  196. GetChars( fn.GetFullPath() )
  197. );
  198. THROW_IO_ERROR( msg );
  199. }
  200. #endif
  201. m_cache_timestamp += fn.GetTimestamp();
  202. }
  203. m_cache_timestamp += m_lib_path.GetModificationTime().GetValue().GetValue();
  204. // If we've saved the full cache, we clear the dirty flag.
  205. if( !aModule )
  206. m_cache_dirty = false;
  207. }
  208. void FP_CACHE::Load()
  209. {
  210. m_cache_dirty = false;
  211. m_cache_timestamp = 0;
  212. wxDir dir( m_lib_raw_path );
  213. if( !dir.IsOpened() )
  214. {
  215. wxString msg = wxString::Format( _( "Footprint library path \"%s\" does not exist" ),
  216. m_lib_raw_path );
  217. THROW_IO_ERROR( msg );
  218. }
  219. wxString fullName;
  220. wxString fileSpec = wxT( "*." ) + KiCadFootprintFileExtension;
  221. // wxFileName construction is egregiously slow. Construct it once and just swap out
  222. // the filename thereafter.
  223. WX_FILENAME fn( m_lib_raw_path, wxT( "dummyName" ) );
  224. if( dir.GetFirst( &fullName, fileSpec ) )
  225. {
  226. wxString cacheError;
  227. do
  228. {
  229. fn.SetFullName( fullName );
  230. // Queue I/O errors so only files that fail to parse don't get loaded.
  231. try
  232. {
  233. FILE_LINE_READER reader( fn.GetFullPath() );
  234. m_owner->m_parser->SetLineReader( &reader );
  235. MODULE* footprint = (MODULE*) m_owner->m_parser->Parse();
  236. wxString fpName = fn.GetName();
  237. footprint->SetFPID( LIB_ID( wxEmptyString, fpName ) );
  238. m_modules.insert( fpName, new FP_CACHE_ITEM( footprint, fn ) );
  239. m_cache_timestamp += fn.GetTimestamp();
  240. }
  241. catch( const IO_ERROR& ioe )
  242. {
  243. if( !cacheError.IsEmpty() )
  244. cacheError += "\n\n";
  245. cacheError += ioe.What();
  246. }
  247. } while( dir.GetNext( &fullName ) );
  248. if( !cacheError.IsEmpty() )
  249. THROW_IO_ERROR( cacheError );
  250. }
  251. }
  252. void FP_CACHE::Remove( const wxString& aFootprintName )
  253. {
  254. MODULE_CITER it = m_modules.find( aFootprintName );
  255. if( it == m_modules.end() )
  256. {
  257. wxString msg = wxString::Format( _( "library \"%s\" has no footprint \"%s\" to delete" ),
  258. m_lib_raw_path,
  259. aFootprintName );
  260. THROW_IO_ERROR( msg );
  261. }
  262. // Remove the module from the cache and delete the module file from the library.
  263. wxString fullPath = it->second->GetFileName().GetFullPath();
  264. m_modules.erase( aFootprintName );
  265. wxRemoveFile( fullPath );
  266. }
  267. bool FP_CACHE::IsPath( const wxString& aPath ) const
  268. {
  269. return aPath == m_lib_raw_path;
  270. }
  271. bool FP_CACHE::IsModified()
  272. {
  273. m_cache_dirty = m_cache_dirty || GetTimestamp( m_lib_path.GetFullPath() ) != m_cache_timestamp;
  274. return m_cache_dirty;
  275. }
  276. long long FP_CACHE::GetTimestamp( const wxString& aLibPath )
  277. {
  278. wxString fileSpec = wxT( "*." ) + KiCadFootprintFileExtension;
  279. return TimestampDir( aLibPath, fileSpec );
  280. }
  281. void PCB_IO::Save( const wxString& aFileName, BOARD* aBoard, const PROPERTIES* aProperties )
  282. {
  283. LOCALE_IO toggle; // toggles on, then off, the C locale.
  284. init( aProperties );
  285. m_board = aBoard; // after init()
  286. // Prepare net mapping that assures that net codes saved in a file are consecutive integers
  287. m_mapping->SetBoard( aBoard );
  288. FILE_OUTPUTFORMATTER formatter( aFileName );
  289. m_out = &formatter; // no ownership
  290. m_out->Print( 0, "(kicad_pcb (version %d) (host pcbnew %s)\n", SEXPR_BOARD_FILE_VERSION,
  291. formatter.Quotew( GetBuildVersion() ).c_str() );
  292. Format( aBoard, 1 );
  293. m_out->Print( 0, ")\n" );
  294. }
  295. BOARD_ITEM* PCB_IO::Parse( const wxString& aClipboardSourceInput )
  296. {
  297. std::string input = TO_UTF8( aClipboardSourceInput );
  298. STRING_LINE_READER reader( input, wxT( "clipboard" ) );
  299. m_parser->SetLineReader( &reader );
  300. try
  301. {
  302. return m_parser->Parse();
  303. }
  304. catch( const PARSE_ERROR& parse_error )
  305. {
  306. if( m_parser->IsTooRecent() )
  307. throw FUTURE_FORMAT_ERROR( parse_error, m_parser->GetRequiredVersion() );
  308. else
  309. throw;
  310. }
  311. }
  312. void PCB_IO::Format( BOARD_ITEM* aItem, int aNestLevel ) const
  313. {
  314. LOCALE_IO toggle; // public API function, perform anything convenient for caller
  315. switch( aItem->Type() )
  316. {
  317. case PCB_T:
  318. format( static_cast<BOARD*>( aItem ), aNestLevel );
  319. break;
  320. case PCB_DIMENSION_T:
  321. format( static_cast<DIMENSION*>( aItem ), aNestLevel );
  322. break;
  323. case PCB_LINE_T:
  324. format( static_cast<DRAWSEGMENT*>( aItem ), aNestLevel );
  325. break;
  326. case PCB_MODULE_EDGE_T:
  327. format( static_cast<EDGE_MODULE*>( aItem ), aNestLevel );
  328. break;
  329. case PCB_TARGET_T:
  330. format( static_cast<PCB_TARGET*>( aItem ), aNestLevel );
  331. break;
  332. case PCB_MODULE_T:
  333. format( static_cast<MODULE*>( aItem ), aNestLevel );
  334. break;
  335. case PCB_PAD_T:
  336. format( static_cast<D_PAD*>( aItem ), aNestLevel );
  337. break;
  338. case PCB_TEXT_T:
  339. format( static_cast<TEXTE_PCB*>( aItem ), aNestLevel );
  340. break;
  341. case PCB_MODULE_TEXT_T:
  342. format( static_cast<TEXTE_MODULE*>( aItem ), aNestLevel );
  343. break;
  344. case PCB_TRACE_T:
  345. case PCB_VIA_T:
  346. format( static_cast<TRACK*>( aItem ), aNestLevel );
  347. break;
  348. case PCB_ZONE_AREA_T:
  349. format( static_cast<ZONE_CONTAINER*>( aItem ), aNestLevel );
  350. break;
  351. default:
  352. wxFAIL_MSG( wxT( "Cannot format item " ) + aItem->GetClass() );
  353. }
  354. }
  355. void PCB_IO::formatLayer( const BOARD_ITEM* aItem ) const
  356. {
  357. if( m_ctl & CTL_STD_LAYER_NAMES )
  358. {
  359. PCB_LAYER_ID layer = aItem->GetLayer();
  360. // English layer names should never need quoting.
  361. m_out->Print( 0, " (layer %s)", TO_UTF8( BOARD::GetStandardLayerName( layer ) ) );
  362. }
  363. else
  364. m_out->Print( 0, " (layer %s)", m_out->Quotew( aItem->GetLayerName() ).c_str() );
  365. }
  366. void PCB_IO::formatSetup( BOARD* aBoard, int aNestLevel ) const
  367. {
  368. const BOARD_DESIGN_SETTINGS& dsnSettings = aBoard->GetDesignSettings();
  369. // Setup
  370. m_out->Print( aNestLevel, "(setup\n" );
  371. // Save current default track width, for compatibility with older Pcbnew version;
  372. m_out->Print( aNestLevel+1, "(last_trace_width %s)\n",
  373. FormatInternalUnits( dsnSettings.GetCurrentTrackWidth() ).c_str() );
  374. // Save custom tracks width list (the first is not saved here: this is the netclass value
  375. for( unsigned ii = 1; ii < dsnSettings.m_TrackWidthList.size(); ii++ )
  376. m_out->Print( aNestLevel+1, "(user_trace_width %s)\n",
  377. FormatInternalUnits( dsnSettings.m_TrackWidthList[ii] ).c_str() );
  378. m_out->Print( aNestLevel+1, "(trace_clearance %s)\n",
  379. FormatInternalUnits( dsnSettings.GetDefault()->GetClearance() ).c_str() );
  380. // ZONE_SETTINGS
  381. m_out->Print( aNestLevel+1, "(zone_clearance %s)\n",
  382. FormatInternalUnits( aBoard->GetZoneSettings().m_ZoneClearance ).c_str() );
  383. m_out->Print( aNestLevel+1, "(zone_45_only %s)\n",
  384. aBoard->GetZoneSettings().m_Zone_45_Only ? "yes" : "no" );
  385. m_out->Print( aNestLevel+1, "(trace_min %s)\n",
  386. FormatInternalUnits( dsnSettings.m_TrackMinWidth ).c_str() );
  387. // Save current default via size, for compatibility with older Pcbnew version;
  388. m_out->Print( aNestLevel+1, "(via_size %s)\n",
  389. FormatInternalUnits( dsnSettings.GetDefault()->GetViaDiameter() ).c_str() );
  390. m_out->Print( aNestLevel+1, "(via_drill %s)\n",
  391. FormatInternalUnits( dsnSettings.GetDefault()->GetViaDrill() ).c_str() );
  392. m_out->Print( aNestLevel+1, "(via_min_size %s)\n",
  393. FormatInternalUnits( dsnSettings.m_ViasMinSize ).c_str() );
  394. m_out->Print( aNestLevel+1, "(via_min_drill %s)\n",
  395. FormatInternalUnits( dsnSettings.m_ViasMinDrill ).c_str() );
  396. // Save custom vias diameters list (the first is not saved here: this is
  397. // the netclass value
  398. for( unsigned ii = 1; ii < dsnSettings.m_ViasDimensionsList.size(); ii++ )
  399. m_out->Print( aNestLevel+1, "(user_via %s %s)\n",
  400. FormatInternalUnits( dsnSettings.m_ViasDimensionsList[ii].m_Diameter ).c_str(),
  401. FormatInternalUnits( dsnSettings.m_ViasDimensionsList[ii].m_Drill ).c_str() );
  402. // for old versions compatibility:
  403. if( dsnSettings.m_BlindBuriedViaAllowed )
  404. m_out->Print( aNestLevel+1, "(blind_buried_vias_allowed yes)\n" );
  405. m_out->Print( aNestLevel+1, "(uvia_size %s)\n",
  406. FormatInternalUnits( dsnSettings.GetDefault()->GetuViaDiameter() ).c_str() );
  407. m_out->Print( aNestLevel+1, "(uvia_drill %s)\n",
  408. FormatInternalUnits( dsnSettings.GetDefault()->GetuViaDrill() ).c_str() );
  409. m_out->Print( aNestLevel+1, "(uvias_allowed %s)\n",
  410. ( dsnSettings.m_MicroViasAllowed ) ? "yes" : "no" );
  411. m_out->Print( aNestLevel+1, "(uvia_min_size %s)\n",
  412. FormatInternalUnits( dsnSettings.m_MicroViasMinSize ).c_str() );
  413. m_out->Print( aNestLevel+1, "(uvia_min_drill %s)\n",
  414. FormatInternalUnits( dsnSettings.m_MicroViasMinDrill ).c_str() );
  415. // 6.0 TODO: are we going to update the tokens we save these under?
  416. // 6.0 TODO: need to save the LAYER_CLASS_OTHERS stuff
  417. // 6.0 TODO: need to save the TextItalic and TextUpright settings
  418. m_out->Print( aNestLevel+1, "(edge_width %s)\n",
  419. FormatInternalUnits( dsnSettings.m_LineThickness[ LAYER_CLASS_EDGES ] ).c_str() );
  420. m_out->Print( aNestLevel+1, "(segment_width %s)\n",
  421. FormatInternalUnits( dsnSettings.m_LineThickness[ LAYER_CLASS_COPPER ] ).c_str() );
  422. m_out->Print( aNestLevel+1, "(pcb_text_width %s)\n",
  423. FormatInternalUnits( dsnSettings.m_TextThickness[ LAYER_CLASS_COPPER ] ).c_str() );
  424. m_out->Print( aNestLevel+1, "(pcb_text_size %s %s)\n",
  425. FormatInternalUnits( dsnSettings.m_TextSize[ LAYER_CLASS_COPPER ].x ).c_str(),
  426. FormatInternalUnits( dsnSettings.m_TextSize[ LAYER_CLASS_COPPER ].y ).c_str() );
  427. m_out->Print( aNestLevel+1, "(mod_edge_width %s)\n",
  428. FormatInternalUnits( dsnSettings.m_LineThickness[ LAYER_CLASS_SILK ] ).c_str() );
  429. m_out->Print( aNestLevel+1, "(mod_text_size %s %s)\n",
  430. FormatInternalUnits( dsnSettings.m_TextSize[ LAYER_CLASS_SILK ].x ).c_str(),
  431. FormatInternalUnits( dsnSettings.m_TextSize[ LAYER_CLASS_SILK ].y ).c_str() );
  432. m_out->Print( aNestLevel+1, "(mod_text_width %s)\n",
  433. FormatInternalUnits( dsnSettings.m_TextThickness[ LAYER_CLASS_SILK ] ).c_str() );
  434. m_out->Print( aNestLevel+1, "(pad_size %s %s)\n",
  435. FormatInternalUnits( dsnSettings.m_Pad_Master.GetSize().x ).c_str(),
  436. FormatInternalUnits( dsnSettings.m_Pad_Master.GetSize().y ).c_str() );
  437. m_out->Print( aNestLevel+1, "(pad_drill %s)\n",
  438. FormatInternalUnits( dsnSettings.m_Pad_Master.GetDrillSize().x ).c_str() );
  439. m_out->Print( aNestLevel+1, "(pad_to_mask_clearance %s)\n",
  440. FormatInternalUnits( dsnSettings.m_SolderMaskMargin ).c_str() );
  441. if( dsnSettings.m_SolderMaskMinWidth )
  442. m_out->Print( aNestLevel+1, "(solder_mask_min_width %s)\n",
  443. FormatInternalUnits( dsnSettings.m_SolderMaskMinWidth ).c_str() );
  444. if( dsnSettings.m_SolderPasteMargin != 0 )
  445. m_out->Print( aNestLevel+1, "(pad_to_paste_clearance %s)\n",
  446. FormatInternalUnits( dsnSettings.m_SolderPasteMargin ).c_str() );
  447. if( dsnSettings.m_SolderPasteMarginRatio != 0 )
  448. m_out->Print( aNestLevel+1, "(pad_to_paste_clearance_ratio %s)\n",
  449. Double2Str( dsnSettings.m_SolderPasteMarginRatio ).c_str() );
  450. m_out->Print( aNestLevel+1, "(aux_axis_origin %s %s)\n",
  451. FormatInternalUnits( aBoard->GetAuxOrigin().x ).c_str(),
  452. FormatInternalUnits( aBoard->GetAuxOrigin().y ).c_str() );
  453. if( aBoard->GetGridOrigin().x || aBoard->GetGridOrigin().y )
  454. m_out->Print( aNestLevel+1, "(grid_origin %s %s)\n",
  455. FormatInternalUnits( aBoard->GetGridOrigin().x ).c_str(),
  456. FormatInternalUnits( aBoard->GetGridOrigin().y ).c_str() );
  457. m_out->Print( aNestLevel+1, "(visible_elements %X)\n",
  458. dsnSettings.GetVisibleElements() );
  459. aBoard->GetPlotOptions().Format( m_out, aNestLevel+1 );
  460. m_out->Print( aNestLevel, ")\n\n" );
  461. }
  462. void PCB_IO::formatGeneral( BOARD* aBoard, int aNestLevel ) const
  463. {
  464. const BOARD_DESIGN_SETTINGS& dsnSettings = aBoard->GetDesignSettings();
  465. m_out->Print( 0, "\n" );
  466. m_out->Print( aNestLevel, "(general\n" );
  467. // Write Bounding box info
  468. m_out->Print( aNestLevel+1, "(thickness %s)\n",
  469. FormatInternalUnits( dsnSettings.GetBoardThickness() ).c_str() );
  470. m_out->Print( aNestLevel+1, "(drawings %d)\n", aBoard->Drawings().Size() );
  471. m_out->Print( aNestLevel+1, "(tracks %d)\n", aBoard->GetNumSegmTrack() );
  472. m_out->Print( aNestLevel+1, "(modules %d)\n", aBoard->m_Modules.GetCount() );
  473. m_out->Print( aNestLevel+1, "(nets %d)\n", m_mapping->GetSize() );
  474. m_out->Print( aNestLevel, ")\n\n" );
  475. aBoard->GetPageSettings().Format( m_out, aNestLevel, m_ctl );
  476. aBoard->GetTitleBlock().Format( m_out, aNestLevel, m_ctl );
  477. }
  478. void PCB_IO::formatBoardLayers( BOARD* aBoard, int aNestLevel ) const
  479. {
  480. m_out->Print( aNestLevel, "(layers\n" );
  481. // Save only the used copper layers from front to back.
  482. LSET visible_layers = aBoard->GetVisibleLayers();
  483. for( LSEQ cu = aBoard->GetEnabledLayers().CuStack(); cu; ++cu )
  484. {
  485. PCB_LAYER_ID layer = *cu;
  486. m_out->Print( aNestLevel+1, "(%d %s %s", layer,
  487. m_out->Quotew( aBoard->GetLayerName( layer ) ).c_str(),
  488. LAYER::ShowType( aBoard->GetLayerType( layer ) ) );
  489. if( !visible_layers[layer] )
  490. m_out->Print( 0, " hide" );
  491. m_out->Print( 0, ")\n" );
  492. }
  493. // Save used non-copper layers in the order they are defined.
  494. // desired sequence for non Cu BOARD layers.
  495. static const PCB_LAYER_ID non_cu[] =
  496. {
  497. B_Adhes, // 32
  498. F_Adhes,
  499. B_Paste,
  500. F_Paste,
  501. B_SilkS,
  502. F_SilkS,
  503. B_Mask,
  504. F_Mask,
  505. Dwgs_User,
  506. Cmts_User,
  507. Eco1_User,
  508. Eco2_User,
  509. Edge_Cuts,
  510. Margin,
  511. B_CrtYd,
  512. F_CrtYd,
  513. B_Fab,
  514. F_Fab
  515. };
  516. for( LSEQ seq = aBoard->GetEnabledLayers().Seq( non_cu, arrayDim( non_cu ) ); seq; ++seq )
  517. {
  518. PCB_LAYER_ID layer = *seq;
  519. m_out->Print( aNestLevel+1, "(%d %s user", layer,
  520. m_out->Quotew( aBoard->GetLayerName( layer ) ).c_str() );
  521. if( !visible_layers[layer] )
  522. m_out->Print( 0, " hide" );
  523. m_out->Print( 0, ")\n" );
  524. }
  525. m_out->Print( aNestLevel, ")\n\n" );
  526. }
  527. void PCB_IO::formatNetInformation( BOARD* aBoard, int aNestLevel ) const
  528. {
  529. const BOARD_DESIGN_SETTINGS& dsnSettings = aBoard->GetDesignSettings();
  530. for( NETINFO_ITEM* net : *m_mapping )
  531. {
  532. m_out->Print( aNestLevel, "(net %d %s)\n",
  533. m_mapping->Translate( net->GetNet() ),
  534. m_out->Quotew( net->GetNetname() ).c_str() );
  535. }
  536. m_out->Print( 0, "\n" );
  537. // Save the default net class first.
  538. NETCLASS defaultNC = *dsnSettings.GetDefault();
  539. filterNetClass( *aBoard, defaultNC ); // Remove empty nets (from a copy of a netclass)
  540. defaultNC.Format( m_out, aNestLevel, m_ctl );
  541. // Save the rest of the net classes alphabetically.
  542. for( const auto& it : dsnSettings.m_NetClasses )
  543. {
  544. NETCLASS netclass = *it.second;
  545. filterNetClass( *aBoard, netclass ); // Remove empty nets (from a copy of a netclass)
  546. netclass.Format( m_out, aNestLevel, m_ctl );
  547. }
  548. }
  549. void PCB_IO::formatHeader( BOARD* aBoard, int aNestLevel ) const
  550. {
  551. formatGeneral( aBoard, aNestLevel );
  552. // Layers.
  553. formatBoardLayers( aBoard, aNestLevel );
  554. // Setup
  555. formatSetup( aBoard, aNestLevel );
  556. // Save net codes and names
  557. formatNetInformation( aBoard, aNestLevel );
  558. }
  559. void PCB_IO::format( BOARD* aBoard, int aNestLevel ) const
  560. {
  561. formatHeader( aBoard, aNestLevel );
  562. // Save the modules.
  563. for( MODULE* module = aBoard->m_Modules; module; module = module->Next() )
  564. {
  565. Format( module, aNestLevel );
  566. m_out->Print( 0, "\n" );
  567. }
  568. // Save the graphical items on the board (not owned by a module)
  569. for( auto item : aBoard->Drawings() )
  570. Format( item, aNestLevel );
  571. if( aBoard->Drawings().Size() )
  572. m_out->Print( 0, "\n" );
  573. // Do not save MARKER_PCBs, they can be regenerated easily.
  574. // Save the tracks and vias.
  575. for( TRACK* track = aBoard->m_Track; track; track = track->Next() )
  576. Format( track, aNestLevel );
  577. if( aBoard->m_Track.GetCount() )
  578. m_out->Print( 0, "\n" );
  579. /// @todo Add warning here that the old segment filed zones are no longer supported and
  580. /// will not be saved.
  581. // Save the polygon (which are the newer technology) zones.
  582. for( int i = 0; i < aBoard->GetAreaCount(); ++i )
  583. Format( aBoard->GetArea( i ), aNestLevel );
  584. }
  585. void PCB_IO::format( DIMENSION* aDimension, int aNestLevel ) const
  586. {
  587. m_out->Print( aNestLevel, "(dimension %s (width %s)",
  588. FormatInternalUnits( aDimension->GetValue() ).c_str(),
  589. FormatInternalUnits( aDimension->GetWidth() ).c_str() );
  590. formatLayer( aDimension );
  591. if( aDimension->GetTimeStamp() )
  592. m_out->Print( 0, " (tstamp %lX)", (unsigned long)aDimension->GetTimeStamp() );
  593. m_out->Print( 0, "\n" );
  594. Format( &aDimension->Text(), aNestLevel+1 );
  595. m_out->Print( aNestLevel+1, "(feature1 (pts (xy %s %s) (xy %s %s)))\n",
  596. FormatInternalUnits( aDimension->m_featureLineDO.x ).c_str(),
  597. FormatInternalUnits( aDimension->m_featureLineDO.y ).c_str(),
  598. FormatInternalUnits( aDimension->m_featureLineDF.x ).c_str(),
  599. FormatInternalUnits( aDimension->m_featureLineDF.y ).c_str() );
  600. m_out->Print( aNestLevel+1, "(feature2 (pts (xy %s %s) (xy %s %s)))\n",
  601. FormatInternalUnits( aDimension->m_featureLineGO.x ).c_str(),
  602. FormatInternalUnits( aDimension->m_featureLineGO.y ).c_str(),
  603. FormatInternalUnits( aDimension->m_featureLineGF.x ).c_str(),
  604. FormatInternalUnits( aDimension->m_featureLineGF.y ).c_str() );
  605. m_out->Print( aNestLevel+1, "(crossbar (pts (xy %s %s) (xy %s %s)))\n",
  606. FormatInternalUnits( aDimension->m_crossBarO.x ).c_str(),
  607. FormatInternalUnits( aDimension->m_crossBarO.y ).c_str(),
  608. FormatInternalUnits( aDimension->m_crossBarF.x ).c_str(),
  609. FormatInternalUnits( aDimension->m_crossBarF.y ).c_str() );
  610. m_out->Print( aNestLevel+1, "(arrow1a (pts (xy %s %s) (xy %s %s)))\n",
  611. FormatInternalUnits( aDimension->m_crossBarF.x ).c_str(),
  612. FormatInternalUnits( aDimension->m_crossBarF.y ).c_str(),
  613. FormatInternalUnits( aDimension->m_arrowD1F.x ).c_str(),
  614. FormatInternalUnits( aDimension->m_arrowD1F.y ).c_str() );
  615. m_out->Print( aNestLevel+1, "(arrow1b (pts (xy %s %s) (xy %s %s)))\n",
  616. FormatInternalUnits( aDimension->m_crossBarF.x ).c_str(),
  617. FormatInternalUnits( aDimension->m_crossBarF.y ).c_str(),
  618. FormatInternalUnits( aDimension->m_arrowD2F.x ).c_str(),
  619. FormatInternalUnits( aDimension->m_arrowD2F.y ).c_str() );
  620. m_out->Print( aNestLevel+1, "(arrow2a (pts (xy %s %s) (xy %s %s)))\n",
  621. FormatInternalUnits( aDimension->m_crossBarO.x ).c_str(),
  622. FormatInternalUnits( aDimension->m_crossBarO.y ).c_str(),
  623. FormatInternalUnits( aDimension->m_arrowG1F.x ).c_str(),
  624. FormatInternalUnits( aDimension->m_arrowG1F.y ).c_str() );
  625. m_out->Print( aNestLevel+1, "(arrow2b (pts (xy %s %s) (xy %s %s)))\n",
  626. FormatInternalUnits( aDimension->m_crossBarO.x ).c_str(),
  627. FormatInternalUnits( aDimension->m_crossBarO.y ).c_str(),
  628. FormatInternalUnits( aDimension->m_arrowG2F.x ).c_str(),
  629. FormatInternalUnits( aDimension->m_arrowG2F.y ).c_str() );
  630. m_out->Print( aNestLevel, ")\n" );
  631. }
  632. void PCB_IO::format( DRAWSEGMENT* aSegment, int aNestLevel ) const
  633. {
  634. switch( aSegment->GetShape() )
  635. {
  636. case S_SEGMENT: // Line
  637. m_out->Print( aNestLevel, "(gr_line (start %s) (end %s)",
  638. FormatInternalUnits( aSegment->GetStart() ).c_str(),
  639. FormatInternalUnits( aSegment->GetEnd() ).c_str() );
  640. if( aSegment->GetAngle() != 0.0 )
  641. m_out->Print( 0, " (angle %s)", FormatAngle( aSegment->GetAngle() ).c_str() );
  642. break;
  643. case S_CIRCLE: // Circle
  644. m_out->Print( aNestLevel, "(gr_circle (center %s) (end %s)",
  645. FormatInternalUnits( aSegment->GetStart() ).c_str(),
  646. FormatInternalUnits( aSegment->GetEnd() ).c_str() );
  647. break;
  648. case S_ARC: // Arc
  649. m_out->Print( aNestLevel, "(gr_arc (start %s) (end %s) (angle %s)",
  650. FormatInternalUnits( aSegment->GetStart() ).c_str(),
  651. FormatInternalUnits( aSegment->GetEnd() ).c_str(),
  652. FormatAngle( aSegment->GetAngle() ).c_str() );
  653. break;
  654. case S_POLYGON: // Polygon
  655. if( aSegment->IsPolyShapeValid() )
  656. {
  657. SHAPE_POLY_SET& poly = aSegment->GetPolyShape();
  658. SHAPE_LINE_CHAIN& outline = poly.Outline( 0 );
  659. int pointsCount = outline.PointCount();
  660. m_out->Print( aNestLevel, "(gr_poly (pts" );
  661. for( int ii = 0; ii < pointsCount; ++ii )
  662. {
  663. m_out->Print( 0, " (xy %s)", FormatInternalUnits( outline.CPoint( ii ) ).c_str() );
  664. }
  665. m_out->Print( 0, ")" );
  666. }
  667. else
  668. {
  669. wxFAIL_MSG( wxT( "Cannot format invalid polygon." ) );
  670. return;
  671. }
  672. break;
  673. case S_CURVE: // Bezier curve
  674. m_out->Print( aNestLevel, "(gr_curve (pts (xy %s) (xy %s) (xy %s) (xy %s))",
  675. FormatInternalUnits( aSegment->GetStart() ).c_str(),
  676. FormatInternalUnits( aSegment->GetBezControl1() ).c_str(),
  677. FormatInternalUnits( aSegment->GetBezControl2() ).c_str(),
  678. FormatInternalUnits( aSegment->GetEnd() ).c_str() );
  679. break;
  680. default:
  681. wxFAIL_MSG( wxT( "Cannot format invalid DRAWSEGMENT type." ) );
  682. return;
  683. };
  684. formatLayer( aSegment );
  685. m_out->Print( 0, " (width %s)", FormatInternalUnits( aSegment->GetWidth() ).c_str() );
  686. if( aSegment->GetTimeStamp() )
  687. m_out->Print( 0, " (tstamp %lX)", (unsigned long)aSegment->GetTimeStamp() );
  688. if( aSegment->GetStatus() )
  689. m_out->Print( 0, " (status %X)", aSegment->GetStatus() );
  690. m_out->Print( 0, ")\n" );
  691. }
  692. void PCB_IO::format( EDGE_MODULE* aModuleDrawing, int aNestLevel ) const
  693. {
  694. switch( aModuleDrawing->GetShape() )
  695. {
  696. case S_SEGMENT: // Line
  697. m_out->Print( aNestLevel, "(fp_line (start %s) (end %s)",
  698. FormatInternalUnits( aModuleDrawing->GetStart0() ).c_str(),
  699. FormatInternalUnits( aModuleDrawing->GetEnd0() ).c_str() );
  700. break;
  701. case S_CIRCLE: // Circle
  702. m_out->Print( aNestLevel, "(fp_circle (center %s) (end %s)",
  703. FormatInternalUnits( aModuleDrawing->GetStart0() ).c_str(),
  704. FormatInternalUnits( aModuleDrawing->GetEnd0() ).c_str() );
  705. break;
  706. case S_ARC: // Arc
  707. m_out->Print( aNestLevel, "(fp_arc (start %s) (end %s) (angle %s)",
  708. FormatInternalUnits( aModuleDrawing->GetStart0() ).c_str(),
  709. FormatInternalUnits( aModuleDrawing->GetEnd0() ).c_str(),
  710. FormatAngle( aModuleDrawing->GetAngle() ).c_str() );
  711. break;
  712. case S_POLYGON: // Polygonal segment
  713. if( aModuleDrawing->IsPolyShapeValid() )
  714. {
  715. SHAPE_POLY_SET& poly = aModuleDrawing->GetPolyShape();
  716. SHAPE_LINE_CHAIN& outline = poly.Outline( 0 );
  717. int pointsCount = outline.PointCount();
  718. m_out->Print( aNestLevel, "(fp_poly (pts" );
  719. for( int ii = 0; ii < pointsCount; ++ii )
  720. {
  721. int nestLevel = 0;
  722. if( ii && !( ii%4 ) ) // newline every 4 pts
  723. {
  724. nestLevel = aNestLevel + 1;
  725. m_out->Print( 0, "\n" );
  726. }
  727. m_out->Print( nestLevel, "%s(xy %s)",
  728. nestLevel ? "" : " ", FormatInternalUnits( outline.CPoint( ii ) ).c_str() );
  729. }
  730. m_out->Print( 0, ")" );
  731. }
  732. else
  733. {
  734. wxFAIL_MSG( wxT( "Cannot format invalid polygon." ) );
  735. return;
  736. }
  737. break;
  738. case S_CURVE: // Bezier curve
  739. m_out->Print( aNestLevel, "(fp_curve (pts (xy %s) (xy %s) (xy %s) (xy %s))",
  740. FormatInternalUnits( aModuleDrawing->GetStart0() ).c_str(),
  741. FormatInternalUnits( aModuleDrawing->GetBezier0_C1() ).c_str(),
  742. FormatInternalUnits( aModuleDrawing->GetBezier0_C2() ).c_str(),
  743. FormatInternalUnits( aModuleDrawing->GetEnd0() ).c_str() );
  744. break;
  745. default:
  746. wxFAIL_MSG( wxT( "Cannot format invalid DRAWSEGMENT type." ) );
  747. return;
  748. };
  749. formatLayer( aModuleDrawing );
  750. m_out->Print( 0, " (width %s)", FormatInternalUnits( aModuleDrawing->GetWidth() ).c_str() );
  751. m_out->Print( 0, ")\n" );
  752. }
  753. void PCB_IO::format( PCB_TARGET* aTarget, int aNestLevel ) const
  754. {
  755. m_out->Print( aNestLevel, "(target %s (at %s) (size %s)",
  756. ( aTarget->GetShape() ) ? "x" : "plus",
  757. FormatInternalUnits( aTarget->GetPosition() ).c_str(),
  758. FormatInternalUnits( aTarget->GetSize() ).c_str() );
  759. if( aTarget->GetWidth() != 0 )
  760. m_out->Print( 0, " (width %s)", FormatInternalUnits( aTarget->GetWidth() ).c_str() );
  761. formatLayer( aTarget );
  762. if( aTarget->GetTimeStamp() )
  763. m_out->Print( 0, " (tstamp %lX)", (unsigned long)aTarget->GetTimeStamp() );
  764. m_out->Print( 0, ")\n" );
  765. }
  766. void PCB_IO::format( MODULE* aModule, int aNestLevel ) const
  767. {
  768. if( !( m_ctl & CTL_OMIT_INITIAL_COMMENTS ) )
  769. {
  770. const wxArrayString* initial_comments = aModule->GetInitialComments();
  771. if( initial_comments )
  772. {
  773. for( unsigned i=0; i<initial_comments->GetCount(); ++i )
  774. m_out->Print( aNestLevel, "%s\n", TO_UTF8( (*initial_comments)[i] ) );
  775. m_out->Print( 0, "\n" ); // improve readability?
  776. }
  777. }
  778. m_out->Print( aNestLevel, "(module %s",
  779. m_out->Quotes( aModule->GetFPID().Format() ).c_str() );
  780. if( aModule->IsLocked() )
  781. m_out->Print( 0, " locked" );
  782. if( aModule->IsPlaced() )
  783. m_out->Print( 0, " placed" );
  784. formatLayer( aModule );
  785. m_out->Print( 0, " (tedit %lX)", (unsigned long)aModule->GetLastEditTime() );
  786. if( !( m_ctl & CTL_OMIT_TSTAMPS ) )
  787. {
  788. m_out->Print( 0, " (tstamp %lX)\n", (unsigned long)aModule->GetTimeStamp() );
  789. }
  790. else
  791. m_out->Print( 0, "\n" );
  792. if( !( m_ctl & CTL_OMIT_AT ) )
  793. {
  794. m_out->Print( aNestLevel+1, "(at %s", FormatInternalUnits( aModule->GetPosition() ).c_str() );
  795. if( aModule->GetOrientation() != 0.0 )
  796. m_out->Print( 0, " %s", FormatAngle( aModule->GetOrientation() ).c_str() );
  797. m_out->Print( 0, ")\n" );
  798. }
  799. if( !aModule->GetDescription().IsEmpty() )
  800. m_out->Print( aNestLevel+1, "(descr %s)\n",
  801. m_out->Quotew( aModule->GetDescription() ).c_str() );
  802. if( !aModule->GetKeywords().IsEmpty() )
  803. m_out->Print( aNestLevel+1, "(tags %s)\n",
  804. m_out->Quotew( aModule->GetKeywords() ).c_str() );
  805. if( !( m_ctl & CTL_OMIT_PATH ) && !!aModule->GetPath() )
  806. m_out->Print( aNestLevel+1, "(path %s)\n",
  807. m_out->Quotew( aModule->GetPath() ).c_str() );
  808. if( aModule->GetPlacementCost90() != 0 )
  809. m_out->Print( aNestLevel+1, "(autoplace_cost90 %d)\n", aModule->GetPlacementCost90() );
  810. if( aModule->GetPlacementCost180() != 0 )
  811. m_out->Print( aNestLevel+1, "(autoplace_cost180 %d)\n", aModule->GetPlacementCost180() );
  812. if( aModule->GetLocalSolderMaskMargin() != 0 )
  813. m_out->Print( aNestLevel+1, "(solder_mask_margin %s)\n",
  814. FormatInternalUnits( aModule->GetLocalSolderMaskMargin() ).c_str() );
  815. if( aModule->GetLocalSolderPasteMargin() != 0 )
  816. m_out->Print( aNestLevel+1, "(solder_paste_margin %s)\n",
  817. FormatInternalUnits( aModule->GetLocalSolderPasteMargin() ).c_str() );
  818. if( aModule->GetLocalSolderPasteMarginRatio() != 0 )
  819. m_out->Print( aNestLevel+1, "(solder_paste_ratio %s)\n",
  820. Double2Str( aModule->GetLocalSolderPasteMarginRatio() ).c_str() );
  821. if( aModule->GetLocalClearance() != 0 )
  822. m_out->Print( aNestLevel+1, "(clearance %s)\n",
  823. FormatInternalUnits( aModule->GetLocalClearance() ).c_str() );
  824. if( aModule->GetZoneConnection() != PAD_ZONE_CONN_INHERITED )
  825. m_out->Print( aNestLevel+1, "(zone_connect %d)\n", aModule->GetZoneConnection() );
  826. if( aModule->GetThermalWidth() != 0 )
  827. m_out->Print( aNestLevel+1, "(thermal_width %s)\n",
  828. FormatInternalUnits( aModule->GetThermalWidth() ).c_str() );
  829. if( aModule->GetThermalGap() != 0 )
  830. m_out->Print( aNestLevel+1, "(thermal_gap %s)\n",
  831. FormatInternalUnits( aModule->GetThermalGap() ).c_str() );
  832. // Attributes
  833. if( aModule->GetAttributes() != MOD_DEFAULT )
  834. {
  835. m_out->Print( aNestLevel+1, "(attr" );
  836. if( aModule->GetAttributes() & MOD_CMS )
  837. m_out->Print( 0, " smd" );
  838. if( aModule->GetAttributes() & MOD_VIRTUAL )
  839. m_out->Print( 0, " virtual" );
  840. m_out->Print( 0, ")\n" );
  841. }
  842. Format( (BOARD_ITEM*) &aModule->Reference(), aNestLevel+1 );
  843. Format( (BOARD_ITEM*) &aModule->Value(), aNestLevel+1 );
  844. // Save drawing elements.
  845. for( BOARD_ITEM* gr = aModule->GraphicalItemsList(); gr; gr = gr->Next() )
  846. Format( gr, aNestLevel+1 );
  847. // Save pads.
  848. for( D_PAD* pad = aModule->PadsList(); pad; pad = pad->Next() )
  849. format( pad, aNestLevel+1 );
  850. // Save 3D info.
  851. auto bs3D = aModule->Models().begin();
  852. auto es3D = aModule->Models().end();
  853. while( bs3D != es3D )
  854. {
  855. if( !bs3D->m_Filename.IsEmpty() )
  856. {
  857. m_out->Print( aNestLevel+1, "(model %s\n",
  858. m_out->Quotew( bs3D->m_Filename ).c_str() );
  859. /* Write 3D model offset in mm
  860. * 4.0.x wrote "at" which was actually in inches
  861. * 5.0.x onwards, 3D model offset is written using "offset"
  862. *
  863. * If the offset is all zero, write "at" (fewer file changes)
  864. * Otherwise, write "offset"
  865. */
  866. wxString offsetTag = "offset";
  867. if( bs3D->m_Offset.x == 0 &&
  868. bs3D->m_Offset.y == 0 &&
  869. bs3D->m_Offset.z == 0 )
  870. {
  871. offsetTag = "at";
  872. }
  873. m_out->Print( aNestLevel+2, "(%s (xyz %s %s %s))\n",
  874. offsetTag.ToStdString().c_str(),
  875. Double2Str( bs3D->m_Offset.x ).c_str(),
  876. Double2Str( bs3D->m_Offset.y ).c_str(),
  877. Double2Str( bs3D->m_Offset.z ).c_str() );
  878. m_out->Print( aNestLevel+2, "(scale (xyz %s %s %s))\n",
  879. Double2Str( bs3D->m_Scale.x ).c_str(),
  880. Double2Str( bs3D->m_Scale.y ).c_str(),
  881. Double2Str( bs3D->m_Scale.z ).c_str() );
  882. m_out->Print( aNestLevel+2, "(rotate (xyz %s %s %s))\n",
  883. Double2Str( bs3D->m_Rotation.x ).c_str(),
  884. Double2Str( bs3D->m_Rotation.y ).c_str(),
  885. Double2Str( bs3D->m_Rotation.z ).c_str() );
  886. m_out->Print( aNestLevel+1, ")\n" );
  887. }
  888. ++bs3D;
  889. }
  890. m_out->Print( aNestLevel, ")\n" );
  891. }
  892. void PCB_IO::formatLayers( LSET aLayerMask, int aNestLevel ) const
  893. {
  894. std::string output;
  895. if( aNestLevel == 0 )
  896. output += ' ';
  897. output += "(layers";
  898. static const LSET cu_all( LSET::AllCuMask() );
  899. static const LSET fr_bk( 2, B_Cu, F_Cu );
  900. static const LSET adhes( 2, B_Adhes, F_Adhes );
  901. static const LSET paste( 2, B_Paste, F_Paste );
  902. static const LSET silks( 2, B_SilkS, F_SilkS );
  903. static const LSET mask( 2, B_Mask, F_Mask );
  904. static const LSET crt_yd(2, B_CrtYd, F_CrtYd );
  905. static const LSET fab( 2, B_Fab, F_Fab );
  906. LSET cu_mask = cu_all;
  907. // output copper layers first, then non copper
  908. if( ( aLayerMask & cu_mask ) == cu_mask )
  909. {
  910. output += " *.Cu";
  911. aLayerMask &= ~cu_all; // clear bits, so they are not output again below
  912. }
  913. else if( ( aLayerMask & cu_mask ) == fr_bk )
  914. {
  915. output += " F&B.Cu";
  916. aLayerMask &= ~fr_bk;
  917. }
  918. if( ( aLayerMask & adhes ) == adhes )
  919. {
  920. output += " *.Adhes";
  921. aLayerMask &= ~adhes;
  922. }
  923. if( ( aLayerMask & paste ) == paste )
  924. {
  925. output += " *.Paste";
  926. aLayerMask &= ~paste;
  927. }
  928. if( ( aLayerMask & silks ) == silks )
  929. {
  930. output += " *.SilkS";
  931. aLayerMask &= ~silks;
  932. }
  933. if( ( aLayerMask & mask ) == mask )
  934. {
  935. output += " *.Mask";
  936. aLayerMask &= ~mask;
  937. }
  938. if( ( aLayerMask & crt_yd ) == crt_yd )
  939. {
  940. output += " *.CrtYd";
  941. aLayerMask &= ~crt_yd;
  942. }
  943. if( ( aLayerMask & fab ) == fab )
  944. {
  945. output += " *.Fab";
  946. aLayerMask &= ~fab;
  947. }
  948. // output any individual layers not handled in wildcard combos above
  949. wxString layerName;
  950. for( LAYER_NUM layer = 0; layer < PCB_LAYER_ID_COUNT; ++layer )
  951. {
  952. if( aLayerMask[layer] )
  953. {
  954. if( m_board && !( m_ctl & CTL_STD_LAYER_NAMES ) )
  955. layerName = m_board->GetLayerName( PCB_LAYER_ID( layer ) );
  956. else // I am being called from FootprintSave()
  957. layerName = BOARD::GetStandardLayerName( PCB_LAYER_ID( layer ) );
  958. output += ' ';
  959. output += m_out->Quotew( layerName );
  960. }
  961. }
  962. m_out->Print( aNestLevel, "%s)", output.c_str() );
  963. }
  964. void PCB_IO::format( D_PAD* aPad, int aNestLevel ) const
  965. {
  966. const char* shape;
  967. switch( aPad->GetShape() )
  968. {
  969. case PAD_SHAPE_CIRCLE: shape = "circle"; break;
  970. case PAD_SHAPE_RECT: shape = "rect"; break;
  971. case PAD_SHAPE_OVAL: shape = "oval"; break;
  972. case PAD_SHAPE_TRAPEZOID: shape = "trapezoid"; break;
  973. case PAD_SHAPE_CHAMFERED_RECT:
  974. case PAD_SHAPE_ROUNDRECT: shape = "roundrect"; break;
  975. case PAD_SHAPE_CUSTOM: shape = "custom"; break;
  976. default:
  977. THROW_IO_ERROR( wxString::Format( _( "unknown pad type: %d"), aPad->GetShape() ) );
  978. }
  979. const char* type;
  980. switch( aPad->GetAttribute() )
  981. {
  982. case PAD_ATTRIB_STANDARD: type = "thru_hole"; break;
  983. case PAD_ATTRIB_SMD: type = "smd"; break;
  984. case PAD_ATTRIB_CONN: type = "connect"; break;
  985. case PAD_ATTRIB_HOLE_NOT_PLATED: type = "np_thru_hole"; break;
  986. default:
  987. THROW_IO_ERROR( wxString::Format( "unknown pad attribute: %d", aPad->GetAttribute() ) );
  988. }
  989. m_out->Print( aNestLevel, "(pad %s %s %s",
  990. m_out->Quotew( aPad->GetName() ).c_str(),
  991. type, shape );
  992. m_out->Print( 0, " (at %s", FormatInternalUnits( aPad->GetPos0() ).c_str() );
  993. if( aPad->GetOrientation() != 0.0 )
  994. m_out->Print( 0, " %s", FormatAngle( aPad->GetOrientation() ).c_str() );
  995. m_out->Print( 0, ")" );
  996. m_out->Print( 0, " (size %s)", FormatInternalUnits( aPad->GetSize() ).c_str() );
  997. if( (aPad->GetDelta().GetWidth()) != 0 || (aPad->GetDelta().GetHeight() != 0 ) )
  998. m_out->Print( 0, " (rect_delta %s )", FormatInternalUnits( aPad->GetDelta() ).c_str() );
  999. wxSize sz = aPad->GetDrillSize();
  1000. wxPoint shapeoffset = aPad->GetOffset();
  1001. if( (sz.GetWidth() > 0) || (sz.GetHeight() > 0) ||
  1002. (shapeoffset.x != 0) || (shapeoffset.y != 0) )
  1003. {
  1004. m_out->Print( 0, " (drill" );
  1005. if( aPad->GetDrillShape() == PAD_DRILL_SHAPE_OBLONG )
  1006. m_out->Print( 0, " oval" );
  1007. if( sz.GetWidth() > 0 )
  1008. m_out->Print( 0, " %s", FormatInternalUnits( sz.GetWidth() ).c_str() );
  1009. if( sz.GetHeight() > 0 && sz.GetWidth() != sz.GetHeight() )
  1010. m_out->Print( 0, " %s", FormatInternalUnits( sz.GetHeight() ).c_str() );
  1011. if( (shapeoffset.x != 0) || (shapeoffset.y != 0) )
  1012. m_out->Print( 0, " (offset %s)", FormatInternalUnits( aPad->GetOffset() ).c_str() );
  1013. m_out->Print( 0, ")" );
  1014. }
  1015. formatLayers( aPad->GetLayerSet() );
  1016. // Output the radius ratio for rounded and chamfered rect pads
  1017. if( aPad->GetShape() == PAD_SHAPE_ROUNDRECT || aPad->GetShape() == PAD_SHAPE_CHAMFERED_RECT)
  1018. {
  1019. m_out->Print( 0, " (roundrect_rratio %s)",
  1020. Double2Str( aPad->GetRoundRectRadiusRatio() ).c_str() );
  1021. }
  1022. // Output the chamfer corners for chamfered rect pads
  1023. if( aPad->GetShape() == PAD_SHAPE_CHAMFERED_RECT)
  1024. {
  1025. m_out->Print( 0, "\n" );
  1026. m_out->Print( aNestLevel+1, "(chamfer_ratio %s)",
  1027. Double2Str( aPad->GetChamferRectRatio() ).c_str() );
  1028. m_out->Print( 0, " (chamfer" );
  1029. if( ( aPad->GetChamferPositions() & RECT_CHAMFER_TOP_LEFT ) )
  1030. m_out->Print( 0, " top_left" );
  1031. if( ( aPad->GetChamferPositions() & RECT_CHAMFER_TOP_RIGHT ) )
  1032. m_out->Print( 0, " top_right" );
  1033. if( ( aPad->GetChamferPositions() & RECT_CHAMFER_BOTTOM_LEFT ) )
  1034. m_out->Print( 0, " bottom_left" );
  1035. if( ( aPad->GetChamferPositions() & RECT_CHAMFER_BOTTOM_RIGHT ) )
  1036. m_out->Print( 0, " bottom_right" );
  1037. m_out->Print( 0, ")" );
  1038. }
  1039. std::string output;
  1040. // Unconnected pad is default net so don't save it.
  1041. if( !( m_ctl & CTL_OMIT_NETS ) && aPad->GetNetCode() != NETINFO_LIST::UNCONNECTED )
  1042. StrPrintf( &output, " (net %d %s)", m_mapping->Translate( aPad->GetNetCode() ),
  1043. m_out->Quotew( aPad->GetNetname() ).c_str() );
  1044. if( aPad->GetPadToDieLength() != 0 )
  1045. StrPrintf( &output, " (die_length %s)", FormatInternalUnits( aPad->GetPadToDieLength() ).c_str() );
  1046. if( aPad->GetLocalSolderMaskMargin() != 0 )
  1047. StrPrintf( &output, " (solder_mask_margin %s)",
  1048. FormatInternalUnits( aPad->GetLocalSolderMaskMargin() ).c_str() );
  1049. if( aPad->GetLocalSolderPasteMargin() != 0 )
  1050. StrPrintf( &output, " (solder_paste_margin %s)",
  1051. FormatInternalUnits( aPad->GetLocalSolderPasteMargin() ).c_str() );
  1052. if( aPad->GetLocalSolderPasteMarginRatio() != 0 )
  1053. StrPrintf( &output, " (solder_paste_margin_ratio %s)",
  1054. Double2Str( aPad->GetLocalSolderPasteMarginRatio() ).c_str() );
  1055. if( aPad->GetLocalClearance() != 0 )
  1056. StrPrintf( &output, " (clearance %s)", FormatInternalUnits( aPad->GetLocalClearance() ).c_str() );
  1057. if( aPad->GetZoneConnection() != PAD_ZONE_CONN_INHERITED )
  1058. StrPrintf( &output, " (zone_connect %d)", aPad->GetZoneConnection() );
  1059. if( aPad->GetThermalWidth() != 0 )
  1060. StrPrintf( &output, " (thermal_width %s)", FormatInternalUnits( aPad->GetThermalWidth() ).c_str() );
  1061. if( aPad->GetThermalGap() != 0 )
  1062. StrPrintf( &output, " (thermal_gap %s)", FormatInternalUnits( aPad->GetThermalGap() ).c_str() );
  1063. if( output.size() )
  1064. {
  1065. m_out->Print( 0, "\n" );
  1066. m_out->Print( aNestLevel+1, "%s", output.c_str()+1 ); // +1 skips 1st space on 1st element
  1067. }
  1068. if( aPad->GetShape() == PAD_SHAPE_CUSTOM )
  1069. {
  1070. m_out->Print( 0, "\n");
  1071. m_out->Print( aNestLevel+1, "(options" );
  1072. if( aPad->GetCustomShapeInZoneOpt() == CUST_PAD_SHAPE_IN_ZONE_CONVEXHULL )
  1073. m_out->Print( 0, " (clearance convexhull)" );
  1074. #if 1 // Set to 1 to output the default option
  1075. else
  1076. m_out->Print( 0, " (clearance outline)" );
  1077. #endif
  1078. // Output the anchor pad shape (circle/rect)
  1079. if( aPad->GetAnchorPadShape() == PAD_SHAPE_RECT )
  1080. shape = "rect";
  1081. else
  1082. shape = "circle";
  1083. m_out->Print( 0, " (anchor %s)", shape );
  1084. m_out->Print( 0, ")"); // end of (options ...
  1085. // Output graphic primitive of the pad shape
  1086. m_out->Print( 0, "\n");
  1087. m_out->Print( aNestLevel+1, "(primitives" );
  1088. int nested_level = aNestLevel+2;
  1089. // Output all basic shapes
  1090. for( unsigned icnt = 0; icnt < aPad->GetPrimitives().size(); ++icnt )
  1091. {
  1092. m_out->Print( 0, "\n");
  1093. const PAD_CS_PRIMITIVE& primitive = aPad->GetPrimitives()[icnt];
  1094. switch( primitive.m_Shape )
  1095. {
  1096. case S_SEGMENT: // usual segment : line with rounded ends
  1097. m_out->Print( nested_level, "(gr_line (start %s) (end %s) (width %s))",
  1098. FormatInternalUnits( primitive.m_Start ).c_str(),
  1099. FormatInternalUnits( primitive.m_End ).c_str(),
  1100. FormatInternalUnits( primitive.m_Thickness ).c_str() );
  1101. break;
  1102. case S_ARC: // Arc with rounded ends
  1103. m_out->Print( nested_level, "(gr_arc (start %s) (end %s) (angle %s) (width %s))",
  1104. FormatInternalUnits( primitive.m_Start ).c_str(),
  1105. FormatInternalUnits( primitive.m_End ).c_str(),
  1106. FormatAngle( primitive.m_ArcAngle ).c_str(),
  1107. FormatInternalUnits( primitive.m_Thickness ).c_str() );
  1108. break;
  1109. case S_CIRCLE: // ring or circle (circle if width == 0
  1110. m_out->Print( nested_level, "(gr_circle (center %s) (end %s %s) (width %s))",
  1111. FormatInternalUnits( primitive.m_Start ).c_str(),
  1112. FormatInternalUnits( primitive.m_Start.x + primitive.m_Radius ).c_str(),
  1113. FormatInternalUnits( primitive.m_Start.y ).c_str(),
  1114. FormatInternalUnits( primitive.m_Thickness ).c_str() );
  1115. break;
  1116. case S_POLYGON: // polygon
  1117. if( primitive.m_Poly.size() < 2 )
  1118. break; // Malformed polygon.
  1119. {
  1120. m_out->Print( nested_level, "(gr_poly (pts\n");
  1121. // Write the polygon corners coordinates:
  1122. const std::vector< wxPoint>& poly = primitive.m_Poly;
  1123. int newLine = 0;
  1124. for( unsigned ii = 0; ii < poly.size(); ii++ )
  1125. {
  1126. if( newLine == 0 )
  1127. m_out->Print( nested_level+1, " (xy %s)",
  1128. FormatInternalUnits( wxPoint( poly[ii].x, poly[ii].y ) ).c_str() );
  1129. else
  1130. m_out->Print( 0, " (xy %s)",
  1131. FormatInternalUnits( wxPoint( poly[ii].x, poly[ii].y ) ).c_str() );
  1132. if( ++newLine > 4 )
  1133. {
  1134. newLine = 0;
  1135. m_out->Print( 0, "\n" );
  1136. }
  1137. }
  1138. m_out->Print( 0, ") (width %s))", FormatInternalUnits( primitive.m_Thickness ).c_str() );
  1139. }
  1140. break;
  1141. default:
  1142. break;
  1143. }
  1144. }
  1145. m_out->Print( 0, "\n");
  1146. m_out->Print( aNestLevel+1, ")" ); // end of (basic_shapes
  1147. }
  1148. m_out->Print( 0, ")\n" );
  1149. }
  1150. void PCB_IO::format( TEXTE_PCB* aText, int aNestLevel ) const
  1151. {
  1152. m_out->Print( aNestLevel, "(gr_text %s (at %s",
  1153. m_out->Quotew( aText->GetText() ).c_str(),
  1154. FormatInternalUnits( aText->GetTextPos() ).c_str() );
  1155. if( aText->GetTextAngle() != 0.0 )
  1156. m_out->Print( 0, " %s", FormatAngle( aText->GetTextAngle() ).c_str() );
  1157. m_out->Print( 0, ")" );
  1158. formatLayer( aText );
  1159. if( aText->GetTimeStamp() )
  1160. m_out->Print( 0, " (tstamp %lX)", (unsigned long)aText->GetTimeStamp() );
  1161. m_out->Print( 0, "\n" );
  1162. aText->EDA_TEXT::Format( m_out, aNestLevel, m_ctl );
  1163. m_out->Print( aNestLevel, ")\n" );
  1164. }
  1165. void PCB_IO::format( TEXTE_MODULE* aText, int aNestLevel ) const
  1166. {
  1167. std::string type;
  1168. switch( aText->GetType() )
  1169. {
  1170. case TEXTE_MODULE::TEXT_is_REFERENCE: type = "reference"; break;
  1171. case TEXTE_MODULE::TEXT_is_VALUE: type = "value"; break;
  1172. case TEXTE_MODULE::TEXT_is_DIVERS: type = "user";
  1173. }
  1174. m_out->Print( aNestLevel, "(fp_text %s %s (at %s", type.c_str(),
  1175. m_out->Quotew( aText->GetText() ).c_str(),
  1176. FormatInternalUnits( aText->GetPos0() ).c_str() );
  1177. // Due to Pcbnew history, fp_text angle is saved as an absolute on screen angle,
  1178. // but internally the angle is held relative to its parent footprint. parent
  1179. // may be NULL when saving a footprint outside a BOARD.
  1180. double orient = aText->GetTextAngle();
  1181. MODULE* parent = (MODULE*) aText->GetParent();
  1182. if( parent )
  1183. {
  1184. // GetTextAngle() is always in -360..+360 range because of
  1185. // TEXTE_MODULE::SetTextAngle(), but summing that angle with an
  1186. // additional board angle could kick sum up >= 360 or <= -360, so to have
  1187. // consistent results, normalize again for the BOARD save. A footprint
  1188. // save does not use this code path since parent is NULL.
  1189. #if 0
  1190. // This one could be considered reasonable if you like positive angles
  1191. // in your board text.
  1192. orient = NormalizeAnglePos( orient + parent->GetOrientation() );
  1193. #else
  1194. // Choose compatibility for now, even though this is only a 720 degree clamp
  1195. // with two possible values for every angle.
  1196. orient = NormalizeAngle360Min( orient + parent->GetOrientation() );
  1197. #endif
  1198. }
  1199. if( orient != 0.0 )
  1200. m_out->Print( 0, " %s", FormatAngle( orient ).c_str() );
  1201. if( !aText->IsKeepUpright() )
  1202. m_out->Print( 0, " unlocked" );
  1203. m_out->Print( 0, ")" );
  1204. formatLayer( aText );
  1205. if( !aText->IsVisible() )
  1206. m_out->Print( 0, " hide" );
  1207. m_out->Print( 0, "\n" );
  1208. aText->EDA_TEXT::Format( m_out, aNestLevel, m_ctl | CTL_OMIT_HIDE );
  1209. m_out->Print( aNestLevel, ")\n" );
  1210. }
  1211. void PCB_IO::format( TRACK* aTrack, int aNestLevel ) const
  1212. {
  1213. if( aTrack->Type() == PCB_VIA_T )
  1214. {
  1215. PCB_LAYER_ID layer1, layer2;
  1216. const VIA* via = static_cast<const VIA*>( aTrack );
  1217. BOARD* board = (BOARD*) via->GetParent();
  1218. wxCHECK_RET( board != 0, wxT( "Via " ) + via->GetSelectMenuText( MILLIMETRES ) +
  1219. wxT( " has no parent." ) );
  1220. m_out->Print( aNestLevel, "(via" );
  1221. via->LayerPair( &layer1, &layer2 );
  1222. switch( via->GetViaType() )
  1223. {
  1224. case VIA_THROUGH: // Default shape not saved.
  1225. break;
  1226. case VIA_BLIND_BURIED:
  1227. m_out->Print( 0, " blind" );
  1228. break;
  1229. case VIA_MICROVIA:
  1230. m_out->Print( 0, " micro" );
  1231. break;
  1232. default:
  1233. THROW_IO_ERROR( wxString::Format( _( "unknown via type %d" ), via->GetViaType() ) );
  1234. }
  1235. m_out->Print( 0, " (at %s) (size %s)",
  1236. FormatInternalUnits( aTrack->GetStart() ).c_str(),
  1237. FormatInternalUnits( aTrack->GetWidth() ).c_str() );
  1238. if( via->GetDrill() != UNDEFINED_DRILL_DIAMETER )
  1239. m_out->Print( 0, " (drill %s)", FormatInternalUnits( via->GetDrill() ).c_str() );
  1240. m_out->Print( 0, " (layers %s %s)",
  1241. m_out->Quotew( m_board->GetLayerName( layer1 ) ).c_str(),
  1242. m_out->Quotew( m_board->GetLayerName( layer2 ) ).c_str() );
  1243. }
  1244. else
  1245. {
  1246. m_out->Print( aNestLevel, "(segment (start %s) (end %s) (width %s)",
  1247. FormatInternalUnits( aTrack->GetStart() ).c_str(), FormatInternalUnits( aTrack->GetEnd() ).c_str(),
  1248. FormatInternalUnits( aTrack->GetWidth() ).c_str() );
  1249. m_out->Print( 0, " (layer %s)", m_out->Quotew( aTrack->GetLayerName() ).c_str() );
  1250. }
  1251. m_out->Print( 0, " (net %d)", m_mapping->Translate( aTrack->GetNetCode() ) );
  1252. if( aTrack->GetTimeStamp() != 0 )
  1253. m_out->Print( 0, " (tstamp %lX)", (unsigned long)aTrack->GetTimeStamp() );
  1254. if( aTrack->GetStatus() != 0 )
  1255. m_out->Print( 0, " (status %X)", aTrack->GetStatus() );
  1256. m_out->Print( 0, ")\n" );
  1257. }
  1258. void PCB_IO::format( ZONE_CONTAINER* aZone, int aNestLevel ) const
  1259. {
  1260. // Save the NET info; For keepout zones, net code and net name are irrelevant
  1261. // so be sure a dummy value is stored, just for ZONE_CONTAINER compatibility
  1262. // (perhaps netcode and netname should be not stored)
  1263. m_out->Print( aNestLevel, "(zone (net %d) (net_name %s)",
  1264. aZone->GetIsKeepout() ? 0 : m_mapping->Translate( aZone->GetNetCode() ),
  1265. m_out->Quotew( aZone->GetIsKeepout() ? wxT("") : aZone->GetNetname() ).c_str() );
  1266. // If a zone exists on multiple layers, format accordingly
  1267. if( aZone->GetLayerSet().count() > 1 )
  1268. {
  1269. formatLayers( aZone->GetLayerSet() );
  1270. }
  1271. else
  1272. {
  1273. formatLayer( aZone );
  1274. }
  1275. m_out->Print( 0, " (tstamp %lX)", (unsigned long) aZone->GetTimeStamp() );
  1276. // Save the outline aux info
  1277. std::string hatch;
  1278. switch( aZone->GetHatchStyle() )
  1279. {
  1280. default:
  1281. case ZONE_CONTAINER::NO_HATCH: hatch = "none"; break;
  1282. case ZONE_CONTAINER::DIAGONAL_EDGE: hatch = "edge"; break;
  1283. case ZONE_CONTAINER::DIAGONAL_FULL: hatch = "full"; break;
  1284. }
  1285. m_out->Print( 0, " (hatch %s %s)\n", hatch.c_str(),
  1286. FormatInternalUnits( aZone->GetHatchPitch() ).c_str() );
  1287. if( aZone->GetPriority() > 0 )
  1288. m_out->Print( aNestLevel+1, "(priority %d)\n", aZone->GetPriority() );
  1289. m_out->Print( aNestLevel+1, "(connect_pads" );
  1290. switch( aZone->GetPadConnection() )
  1291. {
  1292. default:
  1293. case PAD_ZONE_CONN_THERMAL: // Default option not saved or loaded.
  1294. break;
  1295. case PAD_ZONE_CONN_THT_THERMAL:
  1296. m_out->Print( 0, " thru_hole_only" );
  1297. break;
  1298. case PAD_ZONE_CONN_FULL:
  1299. m_out->Print( 0, " yes" );
  1300. break;
  1301. case PAD_ZONE_CONN_NONE:
  1302. m_out->Print( 0, " no" );
  1303. break;
  1304. }
  1305. m_out->Print( 0, " (clearance %s))\n",
  1306. FormatInternalUnits( aZone->GetZoneClearance() ).c_str() );
  1307. m_out->Print( aNestLevel+1, "(min_thickness %s)",
  1308. FormatInternalUnits( aZone->GetMinThickness() ).c_str() );
  1309. m_out->Print( 0, "\n" );
  1310. if( aZone->GetIsKeepout() )
  1311. {
  1312. m_out->Print( aNestLevel+1, "(keepout (tracks %s) (vias %s) (copperpour %s))\n",
  1313. aZone->GetDoNotAllowTracks() ? "not_allowed" : "allowed",
  1314. aZone->GetDoNotAllowVias() ? "not_allowed" : "allowed",
  1315. aZone->GetDoNotAllowCopperPour() ? "not_allowed" : "allowed" );
  1316. }
  1317. m_out->Print( aNestLevel+1, "(fill" );
  1318. // Default is not filled.
  1319. if( aZone->IsFilled() )
  1320. m_out->Print( 0, " yes" );
  1321. // Default is polygon filled.
  1322. if( aZone->GetFillMode() == ZFM_HATCH_PATTERN )
  1323. m_out->Print( 0, " (mode hatch)" );
  1324. m_out->Print( 0, " (arc_segments %d) (thermal_gap %s) (thermal_bridge_width %s)",
  1325. aZone->GetArcSegmentCount(),
  1326. FormatInternalUnits( aZone->GetThermalReliefGap() ).c_str(),
  1327. FormatInternalUnits( aZone->GetThermalReliefCopperBridge() ).c_str() );
  1328. if( aZone->GetCornerSmoothingType() != ZONE_SETTINGS::SMOOTHING_NONE )
  1329. {
  1330. m_out->Print( 0, " (smoothing" );
  1331. switch( aZone->GetCornerSmoothingType() )
  1332. {
  1333. case ZONE_SETTINGS::SMOOTHING_CHAMFER:
  1334. m_out->Print( 0, " chamfer" );
  1335. break;
  1336. case ZONE_SETTINGS::SMOOTHING_FILLET:
  1337. m_out->Print( 0, " fillet" );
  1338. break;
  1339. default:
  1340. THROW_IO_ERROR( wxString::Format( _( "unknown zone corner smoothing type %d" ),
  1341. aZone->GetCornerSmoothingType() ) );
  1342. }
  1343. m_out->Print( 0, ")" );
  1344. if( aZone->GetCornerRadius() != 0 )
  1345. m_out->Print( 0, " (radius %s)",
  1346. FormatInternalUnits( aZone->GetCornerRadius() ).c_str() );
  1347. }
  1348. if( aZone->GetFillMode() == ZFM_HATCH_PATTERN )
  1349. {
  1350. m_out->Print( 0, "\n" );
  1351. m_out->Print( aNestLevel+2, "(hatch_thickness %s) (hatch_gap %s) (hatch_orientation %s)",
  1352. FormatInternalUnits( aZone->GetHatchFillTypeThickness() ).c_str(),
  1353. FormatInternalUnits( aZone->GetHatchFillTypeGap() ).c_str(),
  1354. Double2Str( aZone->GetHatchFillTypeOrientation() ).c_str() );
  1355. if( aZone->GetHatchFillTypeSmoothingLevel() > 0 )
  1356. {
  1357. m_out->Print( 0, "\n" );
  1358. m_out->Print( aNestLevel+2, "(hatch_smoothing_level %d) (hatch_smoothing_value %s)",
  1359. aZone->GetHatchFillTypeSmoothingLevel(),
  1360. Double2Str( aZone->GetHatchFillTypeSmoothingValue() ).c_str() );
  1361. }
  1362. }
  1363. m_out->Print( 0, ")\n" );
  1364. int newLine = 0;
  1365. if( aZone->GetNumCorners() )
  1366. {
  1367. bool new_polygon = true;
  1368. bool is_closed = false;
  1369. for( auto iterator = aZone->IterateWithHoles(); iterator; iterator++ )
  1370. {
  1371. if( new_polygon )
  1372. {
  1373. newLine = 0;
  1374. m_out->Print( aNestLevel+1, "(polygon\n" );
  1375. m_out->Print( aNestLevel+2, "(pts\n" );
  1376. new_polygon = false;
  1377. is_closed = false;
  1378. }
  1379. if( newLine == 0 )
  1380. m_out->Print( aNestLevel+3, "(xy %s %s)",
  1381. FormatInternalUnits( iterator->x ).c_str(), FormatInternalUnits( iterator->y ).c_str() );
  1382. else
  1383. m_out->Print( 0, " (xy %s %s)",
  1384. FormatInternalUnits( iterator->x ).c_str(), FormatInternalUnits( iterator->y ).c_str() );
  1385. if( newLine < 4 )
  1386. {
  1387. newLine += 1;
  1388. }
  1389. else
  1390. {
  1391. newLine = 0;
  1392. m_out->Print( 0, "\n" );
  1393. }
  1394. if( iterator.IsEndContour() )
  1395. {
  1396. is_closed = true;
  1397. if( newLine != 0 )
  1398. m_out->Print( 0, "\n" );
  1399. m_out->Print( aNestLevel+2, ")\n" );
  1400. m_out->Print( aNestLevel+1, ")\n" );
  1401. new_polygon = true;
  1402. }
  1403. }
  1404. if( !is_closed ) // Should not happen, but...
  1405. m_out->Print( aNestLevel+1, ")\n" );
  1406. }
  1407. // Save the PolysList (filled areas)
  1408. const SHAPE_POLY_SET& fv = aZone->GetFilledPolysList();
  1409. newLine = 0;
  1410. if( !fv.IsEmpty() )
  1411. {
  1412. bool new_polygon = true;
  1413. bool is_closed = false;
  1414. for( auto it = fv.CIterate(); it; ++it )
  1415. {
  1416. if( new_polygon )
  1417. {
  1418. newLine = 0;
  1419. m_out->Print( aNestLevel+1, "(filled_polygon\n" );
  1420. m_out->Print( aNestLevel+2, "(pts\n" );
  1421. new_polygon = false;
  1422. is_closed = false;
  1423. }
  1424. if( newLine == 0 )
  1425. m_out->Print( aNestLevel+3, "(xy %s %s)",
  1426. FormatInternalUnits( it->x ).c_str(), FormatInternalUnits( it->y ).c_str() );
  1427. else
  1428. m_out->Print( 0, " (xy %s %s)",
  1429. FormatInternalUnits( it->x ) .c_str(), FormatInternalUnits( it->y ).c_str() );
  1430. if( newLine < 4 )
  1431. {
  1432. newLine += 1;
  1433. }
  1434. else
  1435. {
  1436. newLine = 0;
  1437. m_out->Print( 0, "\n" );
  1438. }
  1439. if( it.IsEndContour() )
  1440. {
  1441. is_closed = true;
  1442. if( newLine != 0 )
  1443. m_out->Print( 0, "\n" );
  1444. m_out->Print( aNestLevel+2, ")\n" );
  1445. m_out->Print( aNestLevel+1, ")\n" );
  1446. new_polygon = true;
  1447. }
  1448. }
  1449. if( !is_closed ) // Should not happen, but...
  1450. m_out->Print( aNestLevel+1, ")\n" );
  1451. }
  1452. // Save the filling segments list
  1453. const auto& segs = aZone->FillSegments();
  1454. if( segs.size() )
  1455. {
  1456. m_out->Print( aNestLevel+1, "(fill_segments\n" );
  1457. for( ZONE_SEGMENT_FILL::const_iterator it = segs.begin(); it != segs.end(); ++it )
  1458. {
  1459. m_out->Print( aNestLevel+2, "(pts (xy %s) (xy %s))\n",
  1460. FormatInternalUnits( wxPoint( it->A ) ).c_str(),
  1461. FormatInternalUnits( wxPoint( it->B ) ).c_str() );
  1462. }
  1463. m_out->Print( aNestLevel+1, ")\n" );
  1464. }
  1465. m_out->Print( aNestLevel, ")\n" );
  1466. }
  1467. PCB_IO::PCB_IO( int aControlFlags ) :
  1468. m_cache( 0 ),
  1469. m_ctl( aControlFlags ),
  1470. m_parser( new PCB_PARSER() ),
  1471. m_mapping( new NETINFO_MAPPING() )
  1472. {
  1473. init( 0 );
  1474. m_out = &m_sf;
  1475. }
  1476. PCB_IO::~PCB_IO()
  1477. {
  1478. delete m_cache;
  1479. delete m_parser;
  1480. delete m_mapping;
  1481. }
  1482. BOARD* PCB_IO::Load( const wxString& aFileName, BOARD* aAppendToMe, const PROPERTIES* aProperties )
  1483. {
  1484. FILE_LINE_READER reader( aFileName );
  1485. init( aProperties );
  1486. m_parser->SetLineReader( &reader );
  1487. m_parser->SetBoard( aAppendToMe );
  1488. BOARD* board;
  1489. try
  1490. {
  1491. board = dynamic_cast<BOARD*>( m_parser->Parse() );
  1492. }
  1493. catch( const FUTURE_FORMAT_ERROR& )
  1494. {
  1495. // Don't wrap a FUTURE_FORMAT_ERROR in another
  1496. throw;
  1497. }
  1498. catch( const PARSE_ERROR& parse_error )
  1499. {
  1500. if( m_parser->IsTooRecent() )
  1501. throw FUTURE_FORMAT_ERROR( parse_error, m_parser->GetRequiredVersion() );
  1502. else
  1503. throw;
  1504. }
  1505. if( !board )
  1506. {
  1507. // The parser loaded something that was valid, but wasn't a board.
  1508. THROW_PARSE_ERROR( _( "this file does not contain a PCB" ),
  1509. m_parser->CurSource(), m_parser->CurLine(),
  1510. m_parser->CurLineNumber(), m_parser->CurOffset() );
  1511. }
  1512. // Give the filename to the board if it's new
  1513. if( !aAppendToMe )
  1514. board->SetFileName( aFileName );
  1515. return board;
  1516. }
  1517. void PCB_IO::init( const PROPERTIES* aProperties )
  1518. {
  1519. m_board = NULL;
  1520. m_reader = NULL;
  1521. m_loading_format_version = SEXPR_BOARD_FILE_VERSION;
  1522. m_props = aProperties;
  1523. }
  1524. void PCB_IO::validateCache( const wxString& aLibraryPath, bool checkModified )
  1525. {
  1526. if( !m_cache || !m_cache->IsPath( aLibraryPath ) || ( checkModified && m_cache->IsModified() ) )
  1527. {
  1528. // a spectacular episode in memory management:
  1529. delete m_cache;
  1530. m_cache = new FP_CACHE( this, aLibraryPath );
  1531. m_cache->Load();
  1532. }
  1533. }
  1534. void PCB_IO::FootprintEnumerate( wxArrayString& aFootprintNames,
  1535. const wxString& aLibraryPath,
  1536. const PROPERTIES* aProperties )
  1537. {
  1538. LOCALE_IO toggle; // toggles on, then off, the C locale.
  1539. wxDir dir( aLibraryPath );
  1540. init( aProperties );
  1541. wxString errorMsg;
  1542. try
  1543. {
  1544. validateCache( aLibraryPath );
  1545. }
  1546. catch( const IO_ERROR& ioe )
  1547. {
  1548. errorMsg = ioe.What();
  1549. }
  1550. // Some of the files may have been parsed correctly so we want to add the valid files to
  1551. // the library.
  1552. const MODULE_MAP& mods = m_cache->GetModules();
  1553. for( MODULE_CITER it = mods.begin(); it != mods.end(); ++it )
  1554. {
  1555. aFootprintNames.Add( it->first );
  1556. }
  1557. if( !errorMsg.IsEmpty() )
  1558. THROW_IO_ERROR( errorMsg );
  1559. }
  1560. const MODULE* PCB_IO::getFootprint( const wxString& aLibraryPath,
  1561. const wxString& aFootprintName,
  1562. const PROPERTIES* aProperties,
  1563. bool checkModified )
  1564. {
  1565. LOCALE_IO toggle; // toggles on, then off, the C locale.
  1566. init( aProperties );
  1567. try
  1568. {
  1569. validateCache( aLibraryPath, checkModified );
  1570. }
  1571. catch( const IO_ERROR& )
  1572. {
  1573. // do nothing with the error
  1574. }
  1575. const MODULE_MAP& mods = m_cache->GetModules();
  1576. MODULE_CITER it = mods.find( aFootprintName );
  1577. if( it == mods.end() )
  1578. {
  1579. return NULL;
  1580. }
  1581. return it->second->GetModule();
  1582. }
  1583. const MODULE* PCB_IO::GetEnumeratedFootprint( const wxString& aLibraryPath,
  1584. const wxString& aFootprintName,
  1585. const PROPERTIES* aProperties )
  1586. {
  1587. return getFootprint( aLibraryPath, aFootprintName, aProperties, false );
  1588. }
  1589. MODULE* PCB_IO::FootprintLoad( const wxString& aLibraryPath, const wxString& aFootprintName,
  1590. const PROPERTIES* aProperties )
  1591. {
  1592. const MODULE* footprint = getFootprint( aLibraryPath, aFootprintName, aProperties, true );
  1593. return footprint ? new MODULE( *footprint ) : nullptr;
  1594. }
  1595. void PCB_IO::FootprintSave( const wxString& aLibraryPath, const MODULE* aFootprint,
  1596. const PROPERTIES* aProperties )
  1597. {
  1598. LOCALE_IO toggle; // toggles on, then off, the C locale.
  1599. init( aProperties );
  1600. // In this public PLUGIN API function, we can safely assume it was
  1601. // called for saving into a library path.
  1602. m_ctl = CTL_FOR_LIBRARY;
  1603. validateCache( aLibraryPath );
  1604. if( !m_cache->IsWritable() )
  1605. {
  1606. if( !m_cache->Exists() )
  1607. {
  1608. const wxString msg = wxString::Format( _( "Library \"%s\" does not exist.\n"
  1609. "Would you like to create it?"),
  1610. GetChars( aLibraryPath ) );
  1611. if( wxMessageBox( msg, _( "Library Not Found"), wxYES_NO | wxICON_QUESTION ) != wxYES )
  1612. return;
  1613. // Save throws its own IO_ERROR on failure, so no need to recreate here
  1614. m_cache->Save( NULL );
  1615. }
  1616. else
  1617. {
  1618. wxString msg = wxString::Format( _( "Library \"%s\" is read only" ), aLibraryPath );
  1619. THROW_IO_ERROR( msg );
  1620. }
  1621. }
  1622. wxString footprintName = aFootprint->GetFPID().GetLibItemName();
  1623. MODULE_MAP& mods = m_cache->GetModules();
  1624. // Quietly overwrite module and delete module file from path for any by same name.
  1625. wxFileName fn( aLibraryPath, aFootprint->GetFPID().GetLibItemName(),
  1626. KiCadFootprintFileExtension );
  1627. #ifndef __WINDOWS__
  1628. // Write through symlinks, don't replace them
  1629. if( fn.Exists( wxFILE_EXISTS_SYMLINK ) )
  1630. {
  1631. char buffer[ PATH_MAX + 1 ];
  1632. ssize_t pathLen = readlink( TO_UTF8( fn.GetFullPath() ), buffer, PATH_MAX );
  1633. if( pathLen > 0 )
  1634. {
  1635. buffer[ pathLen ] = '\0';
  1636. fn.Assign( fn.GetPath() + wxT( "/" ) + wxString::FromUTF8( buffer ) );
  1637. fn.Normalize();
  1638. }
  1639. }
  1640. #endif
  1641. if( !fn.IsOk() )
  1642. {
  1643. THROW_IO_ERROR( wxString::Format( _( "Footprint file name \"%s\" is not valid." ),
  1644. fn.GetFullPath() ) );
  1645. }
  1646. if( fn.FileExists() && !fn.IsFileWritable() )
  1647. {
  1648. THROW_IO_ERROR( wxString::Format( _( "No write permissions to delete file \"%s\"" ),
  1649. fn.GetFullPath() ) );
  1650. }
  1651. wxString fullPath = fn.GetFullPath();
  1652. wxString fullName = fn.GetFullName();
  1653. MODULE_CITER it = mods.find( footprintName );
  1654. if( it != mods.end() )
  1655. {
  1656. wxLogTrace( traceKicadPcbPlugin, wxT( "Removing footprint file '%s'." ), fullPath );
  1657. mods.erase( footprintName );
  1658. wxRemoveFile( fullPath );
  1659. }
  1660. // I need my own copy for the cache
  1661. MODULE* module = new MODULE( *aFootprint );
  1662. // and it's time stamp must be 0, it should have no parent, orientation should
  1663. // be zero, and it should be on the front layer.
  1664. module->SetTimeStamp( 0 );
  1665. module->SetParent( 0 );
  1666. module->SetOrientation( 0 );
  1667. if( module->GetLayer() != F_Cu )
  1668. module->Flip( module->GetPosition() );
  1669. wxLogTrace( traceKicadPcbPlugin, wxT( "Creating s-expr footprint file '%s'." ), fullPath );
  1670. mods.insert( footprintName, new FP_CACHE_ITEM( module, WX_FILENAME( fn.GetPath(), fullName ) ) );
  1671. m_cache->Save( module );
  1672. }
  1673. void PCB_IO::FootprintDelete( const wxString& aLibraryPath, const wxString& aFootprintName,
  1674. const PROPERTIES* aProperties )
  1675. {
  1676. LOCALE_IO toggle; // toggles on, then off, the C locale.
  1677. init( aProperties );
  1678. validateCache( aLibraryPath );
  1679. if( !m_cache->IsWritable() )
  1680. {
  1681. THROW_IO_ERROR( wxString::Format( _( "Library \"%s\" is read only" ),
  1682. aLibraryPath.GetData() ) );
  1683. }
  1684. m_cache->Remove( aFootprintName );
  1685. }
  1686. long long PCB_IO::GetLibraryTimestamp( const wxString& aLibraryPath ) const
  1687. {
  1688. return FP_CACHE::GetTimestamp( aLibraryPath );
  1689. }
  1690. void PCB_IO::FootprintLibCreate( const wxString& aLibraryPath, const PROPERTIES* aProperties )
  1691. {
  1692. if( wxDir::Exists( aLibraryPath ) )
  1693. {
  1694. THROW_IO_ERROR( wxString::Format( _( "cannot overwrite library path \"%s\"" ),
  1695. aLibraryPath.GetData() ) );
  1696. }
  1697. LOCALE_IO toggle;
  1698. init( aProperties );
  1699. delete m_cache;
  1700. m_cache = new FP_CACHE( this, aLibraryPath );
  1701. m_cache->Save();
  1702. }
  1703. bool PCB_IO::FootprintLibDelete( const wxString& aLibraryPath, const PROPERTIES* aProperties )
  1704. {
  1705. wxFileName fn;
  1706. fn.SetPath( aLibraryPath );
  1707. // Return if there is no library path to delete.
  1708. if( !fn.DirExists() )
  1709. return false;
  1710. if( !fn.IsDirWritable() )
  1711. {
  1712. THROW_IO_ERROR( wxString::Format( _( "user does not have permission to delete directory \"%s\"" ),
  1713. aLibraryPath.GetData() ) );
  1714. }
  1715. wxDir dir( aLibraryPath );
  1716. if( dir.HasSubDirs() )
  1717. {
  1718. THROW_IO_ERROR( wxString::Format( _( "library directory \"%s\" has unexpected sub-directories" ),
  1719. aLibraryPath.GetData() ) );
  1720. }
  1721. // All the footprint files must be deleted before the directory can be deleted.
  1722. if( dir.HasFiles() )
  1723. {
  1724. unsigned i;
  1725. wxFileName tmp;
  1726. wxArrayString files;
  1727. wxDir::GetAllFiles( aLibraryPath, &files );
  1728. for( i = 0; i < files.GetCount(); i++ )
  1729. {
  1730. tmp = files[i];
  1731. if( tmp.GetExt() != KiCadFootprintFileExtension )
  1732. {
  1733. THROW_IO_ERROR( wxString::Format( _( "unexpected file \"%s\" was found in library path \"%s\"" ),
  1734. files[i].GetData(), aLibraryPath.GetData() ) );
  1735. }
  1736. }
  1737. for( i = 0; i < files.GetCount(); i++ )
  1738. {
  1739. wxRemoveFile( files[i] );
  1740. }
  1741. }
  1742. wxLogTrace( traceKicadPcbPlugin, wxT( "Removing footprint library \"%s\"" ),
  1743. aLibraryPath.GetData() );
  1744. // Some of the more elaborate wxRemoveFile() crap puts up its own wxLog dialog
  1745. // we don't want that. we want bare metal portability with no UI here.
  1746. if( !wxRmdir( aLibraryPath ) )
  1747. {
  1748. THROW_IO_ERROR( wxString::Format( _( "footprint library \"%s\" cannot be deleted" ),
  1749. aLibraryPath.GetData() ) );
  1750. }
  1751. // For some reason removing a directory in Windows is not immediately updated. This delay
  1752. // prevents an error when attempting to immediately recreate the same directory when over
  1753. // writing an existing library.
  1754. #ifdef __WINDOWS__
  1755. wxMilliSleep( 250L );
  1756. #endif
  1757. if( m_cache && !m_cache->IsPath( aLibraryPath ) )
  1758. {
  1759. delete m_cache;
  1760. m_cache = NULL;
  1761. }
  1762. return true;
  1763. }
  1764. bool PCB_IO::IsFootprintLibWritable( const wxString& aLibraryPath )
  1765. {
  1766. LOCALE_IO toggle;
  1767. init( NULL );
  1768. validateCache( aLibraryPath );
  1769. return m_cache->IsWritable();
  1770. }