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.

502 lines
16 KiB

  1. /**
  2. * @file dialog_gfx_import.cpp
  3. * @brief Dialog to import a vector graphics file on a given board layer.
  4. */
  5. /*
  6. * This program source code file is part of KiCad, a free EDA CAD application.
  7. *
  8. * Copyright (C) 2018 Jean-Pierre Charras, jp.charras at wanadoo.fr
  9. * Copyright (C) 1992-2019 KiCad Developers, see AUTHORS.txt for contributors.
  10. *
  11. * This program is free software; you can redistribute it and/or
  12. * modify it under the terms of the GNU General Public License
  13. * as published by the Free Software Foundation; either version 2
  14. * of the License, or (at your option) any later version.
  15. *
  16. * This program is distributed in the hope that it will be useful,
  17. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  18. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  19. * GNU General Public License for more details.
  20. *
  21. * You should have received a copy of the GNU General Public License
  22. * along with this program; if not, you may find one here:
  23. * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
  24. * or you may search the http://www.gnu.org website for the version 2 license,
  25. * or you may write to the Free Software Foundation, Inc.,
  26. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
  27. */
  28. #include "dialog_import_gfx.h"
  29. #include <advanced_config.h>
  30. #include <convert_to_biu.h>
  31. #include <kiface_i.h>
  32. #include <pcb_layer_box_selector.h>
  33. #include <wildcards_and_files_ext.h>
  34. #include <class_board.h>
  35. #include <class_module.h>
  36. #include <class_edge_mod.h>
  37. #include <class_text_mod.h>
  38. #include <class_pcb_text.h>
  39. // Keys to store setup in config
  40. #define IMPORT_GFX_LAYER_OPTION_KEY "GfxImportBrdLayer"
  41. #define IMPORT_GFX_PLACEMENT_INTERACTIVE_KEY "GfxImportPlacementInteractive"
  42. #define IMPORT_GFX_LAST_FILE_KEY "GfxImportLastFile"
  43. #define IMPORT_GFX_POSITION_UNITS_KEY "GfxImportPositionUnits"
  44. #define IMPORT_GFX_POSITION_X_KEY "GfxImportPositionX"
  45. #define IMPORT_GFX_POSITION_Y_KEY "GfxImportPositionY"
  46. #define IMPORT_GFX_LINEWIDTH_UNITS_KEY "GfxImportLineWidthUnits"
  47. #define IMPORT_GFX_LINEWIDTH_KEY "GfxImportLineWidth"
  48. // Static members of DIALOG_IMPORT_GFX, to remember
  49. // the user's choices during the session
  50. wxString DIALOG_IMPORT_GFX::m_filename;
  51. bool DIALOG_IMPORT_GFX::m_placementInteractive = true;
  52. LAYER_NUM DIALOG_IMPORT_GFX::m_layer = Dwgs_User;
  53. double DIALOG_IMPORT_GFX::m_scaleImport = 1.0; // Do not change the imported items size
  54. DIALOG_IMPORT_GFX::DIALOG_IMPORT_GFX( PCB_BASE_FRAME* aParent, bool aImportAsFootprintGraphic )
  55. : DIALOG_IMPORT_GFX_BASE( aParent )
  56. {
  57. m_parent = aParent;
  58. if( aImportAsFootprintGraphic )
  59. m_importer.reset( new GRAPHICS_IMPORTER_MODULE( m_parent->GetBoard()->m_Modules ) );
  60. else
  61. m_importer.reset( new GRAPHICS_IMPORTER_BOARD( m_parent->GetBoard() ) );
  62. // construct an import manager with options from config
  63. {
  64. GRAPHICS_IMPORT_MGR::TYPE_LIST blacklist;
  65. if( !ADVANCED_CFG::GetCfg().m_enableSvgImport )
  66. blacklist.push_back( GRAPHICS_IMPORT_MGR::SVG );
  67. m_gfxImportMgr = std::make_unique<GRAPHICS_IMPORT_MGR>( blacklist );
  68. }
  69. m_config = Kiface().KifaceSettings();
  70. m_originImportUnits = 0;
  71. m_importOrigin.x = 0.0; // always in mm
  72. m_importOrigin.y = 0.0; // always in mm
  73. m_default_lineWidth = 0.2; // always in mm
  74. m_lineWidthImportUnits = 0;
  75. if( m_config )
  76. {
  77. m_layer = m_config->Read( IMPORT_GFX_LAYER_OPTION_KEY, (long)Dwgs_User );
  78. m_placementInteractive = m_config->Read( IMPORT_GFX_PLACEMENT_INTERACTIVE_KEY, true );
  79. m_filename = m_config->Read( IMPORT_GFX_LAST_FILE_KEY, wxEmptyString );
  80. m_config->Read( IMPORT_GFX_LINEWIDTH_KEY, &m_default_lineWidth, 0.2 );
  81. m_config->Read( IMPORT_GFX_POSITION_UNITS_KEY, &m_originImportUnits, 0 );
  82. m_config->Read( IMPORT_GFX_POSITION_X_KEY, &m_importOrigin.x, 0.0 );
  83. m_config->Read( IMPORT_GFX_POSITION_Y_KEY, &m_importOrigin.y, 0.0 );
  84. }
  85. m_choiceUnitLineWidth->SetSelection( m_lineWidthImportUnits );
  86. showPCBdefaultLineWidth();
  87. m_DxfPcbPositionUnits->SetSelection( m_originImportUnits );
  88. showPcbImportOffsets();
  89. m_textCtrlFileName->SetValue( m_filename );
  90. m_rbInteractivePlacement->SetValue( m_placementInteractive );
  91. m_rbAbsolutePlacement->SetValue( not m_placementInteractive );
  92. m_textCtrlImportScale->SetValue( wxString::Format( "%f", m_scaleImport ) );
  93. // Configure the layers list selector
  94. m_SelLayerBox->SetLayersHotkeys( false ); // Do not display hotkeys
  95. m_SelLayerBox->SetNotAllowedLayerSet( LSET::AllCuMask() ); // Do not use copper layers
  96. m_SelLayerBox->SetBoardFrame( m_parent );
  97. m_SelLayerBox->Resync();
  98. if( m_SelLayerBox->SetLayerSelection( m_layer ) < 0 )
  99. {
  100. m_layer = Dwgs_User;
  101. m_SelLayerBox->SetLayerSelection( m_layer );
  102. }
  103. m_sdbSizerOK->SetDefault();
  104. GetSizer()->Fit( this );
  105. GetSizer()->SetSizeHints( this );
  106. Centre();
  107. }
  108. DIALOG_IMPORT_GFX::~DIALOG_IMPORT_GFX()
  109. {
  110. updatePcbImportOffsets_mm();
  111. m_layer = m_SelLayerBox->GetLayerSelection();
  112. if( m_config )
  113. {
  114. m_config->Write( IMPORT_GFX_LAYER_OPTION_KEY, (long)m_layer );
  115. m_config->Write( IMPORT_GFX_PLACEMENT_INTERACTIVE_KEY, m_placementInteractive );
  116. m_config->Write( IMPORT_GFX_LAST_FILE_KEY, m_filename );
  117. m_config->Write( IMPORT_GFX_POSITION_UNITS_KEY, m_originImportUnits );
  118. m_config->Write( IMPORT_GFX_POSITION_X_KEY, m_importOrigin.x );
  119. m_config->Write( IMPORT_GFX_POSITION_Y_KEY, m_importOrigin.y );
  120. m_lineWidthImportUnits = getPCBdefaultLineWidthMM();
  121. m_config->Write( IMPORT_GFX_LINEWIDTH_KEY, m_default_lineWidth );
  122. m_config->Write( IMPORT_GFX_LINEWIDTH_UNITS_KEY, m_lineWidthImportUnits );
  123. }
  124. }
  125. void DIALOG_IMPORT_GFX::DIALOG_IMPORT_GFX::onUnitPositionSelection( wxCommandEvent& event )
  126. {
  127. // Collect last entered values:
  128. updatePcbImportOffsets_mm();
  129. m_originImportUnits = m_DxfPcbPositionUnits->GetSelection();;
  130. showPcbImportOffsets();
  131. }
  132. double DIALOG_IMPORT_GFX::getPCBdefaultLineWidthMM()
  133. {
  134. double value = DoubleValueFromString( UNSCALED_UNITS, m_textCtrlLineWidth->GetValue() );
  135. switch( m_lineWidthImportUnits )
  136. {
  137. default:
  138. case 0: // display units = mm
  139. break;
  140. case 1: // display units = mil
  141. value *= 25.4 / 1000;
  142. break;
  143. case 2: // display units = inch
  144. value *= 25.4;
  145. break;
  146. }
  147. return value; // value is in mm
  148. }
  149. void DIALOG_IMPORT_GFX::onUnitWidthSelection( wxCommandEvent& event )
  150. {
  151. m_default_lineWidth = getPCBdefaultLineWidthMM();
  152. // Switch to new units
  153. m_lineWidthImportUnits = m_choiceUnitLineWidth->GetSelection();
  154. showPCBdefaultLineWidth();
  155. }
  156. void DIALOG_IMPORT_GFX::showPcbImportOffsets()
  157. {
  158. // Display m_importOrigin value according to the unit selection:
  159. VECTOR2D offset = m_importOrigin;
  160. if( m_originImportUnits ) // Units are inches
  161. offset = m_importOrigin / 25.4;
  162. m_DxfPcbXCoord->SetValue( wxString::Format( "%f", offset.x ) );
  163. m_DxfPcbYCoord->SetValue( wxString::Format( "%f", offset.y ) );
  164. }
  165. void DIALOG_IMPORT_GFX::showPCBdefaultLineWidth()
  166. {
  167. double value;
  168. switch( m_lineWidthImportUnits )
  169. {
  170. default:
  171. case 0: // display units = mm
  172. value = m_default_lineWidth;
  173. break;
  174. case 1: // display units = mil
  175. value = m_default_lineWidth / 25.4 * 1000;
  176. break;
  177. case 2: // display units = inch
  178. value = m_default_lineWidth / 25.4;
  179. break;
  180. }
  181. m_textCtrlLineWidth->SetValue( wxString::Format( "%f", value ) );
  182. }
  183. void DIALOG_IMPORT_GFX::onBrowseFiles( wxCommandEvent& event )
  184. {
  185. wxString path;
  186. wxString filename;
  187. if( !m_filename.IsEmpty() )
  188. {
  189. wxFileName fn( m_filename );
  190. path = fn.GetPath();
  191. filename = fn.GetFullName();
  192. }
  193. // Generate the list of handled file formats
  194. wxString wildcardsDesc;
  195. wxString allWildcards;
  196. for( auto pluginType : m_gfxImportMgr->GetImportableFileTypes() )
  197. {
  198. auto plugin = m_gfxImportMgr->GetPlugin( pluginType );
  199. const auto wildcards = plugin->GetWildcards();
  200. wildcardsDesc += "|" + plugin->GetName() + " (" + wildcards + ")|" + wildcards;
  201. allWildcards += wildcards + ";";
  202. }
  203. wildcardsDesc = "All supported formats|" + allWildcards + wildcardsDesc;
  204. wxFileDialog dlg( m_parent, _( "Open File" ), path, filename,
  205. wildcardsDesc, wxFD_OPEN|wxFD_FILE_MUST_EXIST );
  206. if( dlg.ShowModal() != wxID_OK )
  207. return;
  208. wxString fileName = dlg.GetPath();
  209. if( fileName.IsEmpty() )
  210. return;
  211. m_filename = fileName;
  212. m_textCtrlFileName->SetValue( fileName );
  213. }
  214. void DIALOG_IMPORT_GFX::onOKClick( wxCommandEvent& event )
  215. {
  216. m_filename = m_textCtrlFileName->GetValue();
  217. if( m_filename.IsEmpty() )
  218. {
  219. wxMessageBox( _( "Error: No DXF filename!" ) );
  220. return;
  221. }
  222. updatePcbImportOffsets_mm(); // Update m_importOriginX and m_importOriginY;
  223. m_layer = m_SelLayerBox->GetLayerSelection();
  224. if( m_layer < 0 )
  225. {
  226. wxMessageBox( _( "Please, select a valid layer" ) );
  227. return;
  228. }
  229. m_default_lineWidth = getPCBdefaultLineWidthMM();
  230. m_importer->SetLayer( PCB_LAYER_ID( m_layer ) );
  231. auto plugin = m_gfxImportMgr->GetPluginByExt( wxFileName( m_filename ).GetExt() );
  232. if( plugin )
  233. {
  234. // Set coordinates offset for import (offset is given in mm)
  235. m_importer->SetImportOffsetMM( m_importOrigin );
  236. m_scaleImport = DoubleValueFromString( UNSCALED_UNITS, m_textCtrlImportScale->GetValue() );
  237. m_importer->SetLineWidthMM( m_default_lineWidth );
  238. m_importer->SetPlugin( std::move( plugin ) );
  239. LOCALE_IO dummy; // Ensure floats can be read.
  240. if( m_importer->Load( m_filename ) )
  241. m_importer->Import( m_scaleImport );
  242. // Get warning messages:
  243. const std::string& warnings = m_importer->GetMessages();
  244. if( !warnings.empty() )
  245. wxMessageBox( warnings.c_str(), _( "Not Handled Items" ) );
  246. event.Skip();
  247. }
  248. else
  249. {
  250. wxMessageBox( _( "There is no plugin to handle this file type" ) );
  251. }
  252. }
  253. // Used only in legacy canvas by the board editor.
  254. bool InvokeDialogImportGfxBoard( PCB_BASE_FRAME* aCaller )
  255. {
  256. DIALOG_IMPORT_GFX dlg( aCaller );
  257. if( dlg.ShowModal() != wxID_OK )
  258. return false;
  259. auto& list = dlg.GetImportedItems();
  260. // Ensure the list is not empty:
  261. if( list.empty() )
  262. {
  263. wxMessageBox( _( "No graphic items found in file to import") );
  264. return false;
  265. }
  266. PICKED_ITEMS_LIST picklist; // the pick list for undo command
  267. ITEM_PICKER item_picker( nullptr, UR_NEW );
  268. BOARD* board = aCaller->GetBoard();
  269. // Now prepare a block move command to place the new items, if interactive placement,
  270. // and prepare the undo command.
  271. EDA_RECT bbox; // the new items bounding box, for block move if interactive placement.
  272. bool bboxInit = true; // true until the bounding box is initialized
  273. BLOCK_SELECTOR& blockmove = aCaller->GetScreen()->m_BlockLocate;
  274. if( dlg.IsPlacementInteractive() )
  275. aCaller->HandleBlockBegin( NULL, BLOCK_PRESELECT_MOVE, wxPoint( 0, 0) );
  276. PICKED_ITEMS_LIST& blockitemsList = blockmove.GetItems();
  277. for( auto it = list.begin(); it != list.end(); ++it )
  278. {
  279. BOARD_ITEM* item = static_cast<BOARD_ITEM*>( it->release() );
  280. if( dlg.IsPlacementInteractive() )
  281. item->SetFlags( IS_MOVED );
  282. board->Add( item );
  283. item_picker.SetItem( item );
  284. picklist.PushItem( item_picker );
  285. if( dlg.IsPlacementInteractive() )
  286. {
  287. blockitemsList.PushItem( item_picker );
  288. if( bboxInit )
  289. bbox = item->GetBoundingBox();
  290. else
  291. bbox.Merge( item->GetBoundingBox() );
  292. bboxInit = false;
  293. }
  294. }
  295. aCaller->SaveCopyInUndoList( picklist, UR_NEW, wxPoint( 0, 0 ) );
  296. aCaller->OnModify();
  297. if( dlg.IsPlacementInteractive() )
  298. {
  299. // Finish block move command:
  300. wxPoint cpos = aCaller->GetNearestGridPosition( bbox.Centre() );
  301. blockmove.SetOrigin( bbox.GetOrigin() );
  302. blockmove.SetSize( bbox.GetSize() );
  303. blockmove.SetLastCursorPosition( cpos );
  304. aCaller->HandleBlockEnd( NULL );
  305. }
  306. return true;
  307. }
  308. // Used only in legacy canvas by the footprint editor.
  309. bool InvokeDialogImportGfxModule( PCB_BASE_FRAME* aCaller, MODULE* aModule )
  310. {
  311. if( !aModule )
  312. return false;
  313. DIALOG_IMPORT_GFX dlg( aCaller, true );
  314. if( dlg.ShowModal() != wxID_OK )
  315. return false;
  316. auto& list = dlg.GetImportedItems();
  317. // Ensure the list is not empty:
  318. if( list.empty() )
  319. {
  320. wxMessageBox( _( "No graphic items found in file to import") );
  321. return false;
  322. }
  323. aCaller->SaveCopyInUndoList( aModule, UR_CHANGED );
  324. PICKED_ITEMS_LIST picklist; // the pick list for undo command
  325. ITEM_PICKER item_picker( nullptr, UR_NEW );
  326. // Now prepare a block move command to place the new items, if interactive placement,
  327. // and prepare the undo command.
  328. EDA_RECT bbox; // the new items bounding box, for block move if interactive placement.
  329. bool bboxInit = true; // true until the bounding box is initialized
  330. BLOCK_SELECTOR& blockmove = aCaller->GetScreen()->m_BlockLocate;
  331. if( dlg.IsPlacementInteractive() )
  332. aCaller->HandleBlockBegin( nullptr, BLOCK_PRESELECT_MOVE, wxPoint( 0, 0) );
  333. PICKED_ITEMS_LIST& blockitemsList = blockmove.GetItems();
  334. for( auto it = list.begin(); it != list.end(); ++it )
  335. {
  336. BOARD_ITEM* item = static_cast<BOARD_ITEM*>( it->release() );
  337. aModule->Add( item );
  338. if( dlg.IsPlacementInteractive() )
  339. {
  340. item->SetFlags( IS_MOVED );
  341. item_picker.SetItem( item );
  342. blockitemsList.PushItem( item_picker );
  343. if( bboxInit )
  344. bbox = item->GetBoundingBox();
  345. else
  346. bbox.Merge( item->GetBoundingBox() );
  347. bboxInit = false;
  348. }
  349. }
  350. aCaller->OnModify();
  351. if( dlg.IsPlacementInteractive() )
  352. {
  353. // Finish block move command:
  354. wxPoint cpos = aCaller->GetNearestGridPosition( bbox.Centre() );
  355. blockmove.SetOrigin( bbox.GetOrigin() );
  356. blockmove.SetSize( bbox.GetSize() );
  357. blockmove.SetLastCursorPosition( cpos );
  358. aCaller->HandleBlockEnd( NULL );
  359. }
  360. return true;
  361. }
  362. void DIALOG_IMPORT_GFX::originOptionOnUpdateUI( wxUpdateUIEvent& event )
  363. {
  364. if( m_rbInteractivePlacement->GetValue() != m_placementInteractive )
  365. m_rbInteractivePlacement->SetValue( m_placementInteractive );
  366. if( m_rbAbsolutePlacement->GetValue() == m_placementInteractive )
  367. m_rbAbsolutePlacement->SetValue( not m_placementInteractive );
  368. m_DxfPcbPositionUnits->Enable( not m_placementInteractive );
  369. m_DxfPcbXCoord->Enable( not m_placementInteractive );
  370. m_DxfPcbYCoord->Enable( not m_placementInteractive );
  371. }
  372. void DIALOG_IMPORT_GFX::updatePcbImportOffsets_mm()
  373. {
  374. m_importOrigin.x = DoubleValueFromString( UNSCALED_UNITS, m_DxfPcbXCoord->GetValue() );
  375. m_importOrigin.y = DoubleValueFromString( UNSCALED_UNITS, m_DxfPcbYCoord->GetValue() );
  376. if( m_originImportUnits ) // Units are inches
  377. {
  378. m_importOrigin = m_importOrigin * 25.4;
  379. }
  380. return;
  381. }