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.

477 lines
16 KiB

14 years ago
14 years ago
  1. /*
  2. * This program source code file is part of KiCad, a free EDA CAD application.
  3. *
  4. * Copyright (C) 1992-2019 Jean_Pierre Charras <jp.charras at wanadoo.fr>
  5. * Copyright (C) 1992-2021 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 <confirm.h>
  25. #include <core/arraydim.h>
  26. #include <pcb_edit_frame.h>
  27. #include <pcbnew_settings.h>
  28. #include <pcbplot.h>
  29. #include <gendrill_Excellon_writer.h>
  30. #include <gendrill_gerber_writer.h>
  31. #include <bitmaps.h>
  32. #include <tools/board_editor_control.h>
  33. #include <board.h>
  34. #include <board_design_settings.h>
  35. #include <footprint.h>
  36. #include <pad.h>
  37. #include <pcb_track.h>
  38. #include <paths.h>
  39. #include <project.h>
  40. #include <dialog_gendrill.h>
  41. #include <wildcards_and_files_ext.h>
  42. #include <reporter.h>
  43. #include <wx/msgdlg.h>
  44. #include <wx/dirdlg.h>
  45. #include <wx/filedlg.h>
  46. // list of allowed precision for EXCELLON files, for integer format:
  47. // Due to difference between inches and mm,
  48. // there are 2 precision values, one for inches and one for metric
  49. // Note: for decimla format, the precision is not used
  50. static DRILL_PRECISION precisionListForInches( 2, 4 );
  51. static DRILL_PRECISION precisionListForMetric( 3, 3 );
  52. /* This function displays the dialog frame for drill tools
  53. */
  54. int BOARD_EDITOR_CONTROL::GenerateDrillFiles( const TOOL_EVENT& aEvent )
  55. {
  56. PCB_EDIT_FRAME* editFrame = getEditFrame<PCB_EDIT_FRAME>();
  57. DIALOG_GENDRILL dlg( editFrame, editFrame );
  58. dlg.ShowModal();
  59. return 0;
  60. }
  61. DIALOG_GENDRILL::DIALOG_GENDRILL( PCB_EDIT_FRAME* aPcbEditFrame, wxWindow* aParent ) :
  62. DIALOG_GENDRILL_BASE( aParent )
  63. {
  64. m_pcbEditFrame = aPcbEditFrame;
  65. m_board = m_pcbEditFrame->GetBoard();
  66. m_plotOpts = m_pcbEditFrame->GetPlotSettings();
  67. // We use a sdbSizer to get platform-dependent ordering of the action buttons, but
  68. // that requires us to correct the button labels here.
  69. m_sdbSizerOK->SetLabel( _( "Generate Drill File" ) );
  70. m_sdbSizerApply->SetLabel( _( "Generate Map File" ) );
  71. m_sdbSizerCancel->SetLabel( _( "Close" ) );
  72. m_buttonsSizer->Layout();
  73. m_sdbSizerOK->SetDefault();
  74. SetReturnCode( 1 );
  75. initDialog();
  76. GetSizer()->SetSizeHints( this );
  77. }
  78. // Static members of DIALOG_GENDRILL
  79. int DIALOG_GENDRILL::m_UnitDrillIsInch = true; // Only for Excellon format
  80. int DIALOG_GENDRILL::m_ZerosFormat = EXCELLON_WRITER::DECIMAL_FORMAT;
  81. bool DIALOG_GENDRILL::m_MinimalHeader = false; // Only for Excellon format
  82. bool DIALOG_GENDRILL::m_Mirror = false; // Only for Excellon format
  83. bool DIALOG_GENDRILL::m_Merge_PTH_NPTH = false; // Only for Excellon format
  84. int DIALOG_GENDRILL::m_mapFileType = 1;
  85. int DIALOG_GENDRILL::m_drillFileType = 0;
  86. bool DIALOG_GENDRILL::m_UseRouteModeForOvalHoles = true; // Use G00 route mode to "drill" oval holes
  87. DIALOG_GENDRILL::~DIALOG_GENDRILL()
  88. {
  89. }
  90. void DIALOG_GENDRILL::initDialog()
  91. {
  92. auto cfg = m_pcbEditFrame->GetPcbNewSettings();
  93. m_Merge_PTH_NPTH = cfg->m_GenDrill.merge_pth_npth;
  94. m_MinimalHeader = cfg->m_GenDrill.minimal_header;
  95. m_Mirror = cfg->m_GenDrill.mirror;
  96. m_UnitDrillIsInch = cfg->m_GenDrill.unit_drill_is_inch;
  97. m_UseRouteModeForOvalHoles = cfg->m_GenDrill.use_route_for_oval_holes;
  98. m_drillFileType = cfg->m_GenDrill.drill_file_type;
  99. m_mapFileType = cfg->m_GenDrill.map_file_type;
  100. m_ZerosFormat = cfg->m_GenDrill.zeros_format;
  101. m_drillOriginIsAuxAxis = m_plotOpts.GetUseAuxOrigin();
  102. InitDisplayParams();
  103. }
  104. void DIALOG_GENDRILL::InitDisplayParams()
  105. {
  106. m_browseButton->SetBitmap( KiBitmap( BITMAPS::small_folder ) );
  107. m_rbExcellon->SetValue( m_drillFileType == 0 );
  108. m_rbGerberX2->SetValue( m_drillFileType == 1 );
  109. m_Choice_Unit->SetSelection( m_UnitDrillIsInch ? 1 : 0 );
  110. m_Choice_Zeros_Format->SetSelection( m_ZerosFormat );
  111. UpdatePrecisionOptions();
  112. m_Check_Minimal->SetValue( m_MinimalHeader );
  113. m_Choice_Drill_Offset->SetSelection( m_drillOriginIsAuxAxis ? 1 : 0 );
  114. m_Check_Mirror->SetValue( m_Mirror );
  115. m_Check_Merge_PTH_NPTH->SetValue( m_Merge_PTH_NPTH );
  116. m_Choice_Drill_Map->SetSelection( m_mapFileType );
  117. m_radioBoxOvalHoleMode->SetSelection( m_UseRouteModeForOvalHoles ? 0 : 1 );
  118. m_platedPadsHoleCount = 0;
  119. m_notplatedPadsHoleCount = 0;
  120. m_throughViasCount = 0;
  121. m_microViasCount = 0;
  122. m_blindOrBuriedViasCount = 0;
  123. for( FOOTPRINT* footprint : m_board->Footprints() )
  124. {
  125. for( PAD* pad : footprint->Pads() )
  126. {
  127. if( pad->GetDrillShape() == PAD_DRILL_SHAPE_CIRCLE )
  128. {
  129. if( pad->GetDrillSize().x != 0 )
  130. {
  131. if( pad->GetAttribute() == PAD_ATTRIB::NPTH )
  132. m_notplatedPadsHoleCount++;
  133. else
  134. m_platedPadsHoleCount++;
  135. }
  136. }
  137. else
  138. {
  139. if( pad->GetDrillSize().x != 0 && pad->GetDrillSize().y != 0 )
  140. {
  141. if( pad->GetAttribute() == PAD_ATTRIB::NPTH )
  142. m_notplatedPadsHoleCount++;
  143. else
  144. m_platedPadsHoleCount++;
  145. }
  146. }
  147. }
  148. }
  149. for( PCB_TRACK* track : m_board->Tracks() )
  150. {
  151. const PCB_VIA *via = dynamic_cast<const PCB_VIA*>( track );
  152. if( via )
  153. {
  154. switch( via->GetViaType() )
  155. {
  156. case VIATYPE::THROUGH: m_throughViasCount++; break;
  157. case VIATYPE::MICROVIA: m_microViasCount++; break;
  158. case VIATYPE::BLIND_BURIED: m_blindOrBuriedViasCount++; break;
  159. default: break;
  160. }
  161. }
  162. }
  163. // Display hole counts:
  164. m_PlatedPadsCountInfoMsg->SetLabel( wxString() << m_platedPadsHoleCount );
  165. m_NotPlatedPadsCountInfoMsg->SetLabel( wxString() << m_notplatedPadsHoleCount );
  166. m_ThroughViasInfoMsg->SetLabel( wxString() << m_throughViasCount );
  167. m_MicroViasInfoMsg->SetLabel( wxString() << m_microViasCount );
  168. m_BuriedViasInfoMsg->SetLabel( wxString() << m_blindOrBuriedViasCount );
  169. // Output directory
  170. m_outputDirectoryName->SetValue( m_plotOpts.GetOutputDirectory() );
  171. wxCommandEvent dummy;
  172. onFileFormatSelection( dummy );
  173. }
  174. void DIALOG_GENDRILL::onFileFormatSelection( wxCommandEvent& event )
  175. {
  176. bool enbl_Excellon = m_rbExcellon->GetValue();
  177. m_drillFileType = enbl_Excellon ? 0 : 1;
  178. m_Choice_Unit->Enable( enbl_Excellon );
  179. m_Choice_Zeros_Format->Enable( enbl_Excellon );
  180. m_Check_Mirror->Enable( enbl_Excellon );
  181. m_Check_Minimal->Enable( enbl_Excellon );
  182. m_Check_Merge_PTH_NPTH->Enable( enbl_Excellon );
  183. m_radioBoxOvalHoleMode->Enable( enbl_Excellon );
  184. if( enbl_Excellon )
  185. {
  186. UpdatePrecisionOptions();
  187. }
  188. else
  189. {
  190. m_staticTextPrecision->Enable( true );
  191. m_staticTextPrecision->SetLabel( m_plotOpts.GetGerberPrecision() == 6 ? "4.6" : "4.5" );
  192. }
  193. }
  194. void DIALOG_GENDRILL::UpdateConfig()
  195. {
  196. UpdateDrillParams();
  197. auto cfg = m_pcbEditFrame->GetPcbNewSettings();
  198. cfg->m_GenDrill.merge_pth_npth = m_Merge_PTH_NPTH;
  199. cfg->m_GenDrill.minimal_header = m_MinimalHeader;
  200. cfg->m_GenDrill.mirror = m_Mirror;
  201. cfg->m_GenDrill.unit_drill_is_inch = m_UnitDrillIsInch;
  202. cfg->m_GenDrill.use_route_for_oval_holes = m_UseRouteModeForOvalHoles;
  203. cfg->m_GenDrill.drill_file_type = m_drillFileType;
  204. cfg->m_GenDrill.map_file_type = m_mapFileType;
  205. cfg->m_GenDrill.zeros_format = m_ZerosFormat;
  206. }
  207. void DIALOG_GENDRILL::OnSelDrillUnitsSelected( wxCommandEvent& event )
  208. {
  209. UpdatePrecisionOptions();
  210. }
  211. void DIALOG_GENDRILL::OnGenMapFile( wxCommandEvent& event )
  212. {
  213. GenDrillAndMapFiles( false, true );
  214. }
  215. void DIALOG_GENDRILL::OnGenDrillFile( wxCommandEvent& event )
  216. {
  217. GenDrillAndMapFiles( true, false );
  218. }
  219. void DIALOG_GENDRILL::OnSelZerosFmtSelected( wxCommandEvent& event )
  220. {
  221. UpdatePrecisionOptions();
  222. }
  223. void DIALOG_GENDRILL::UpdatePrecisionOptions()
  224. {
  225. if( m_Choice_Unit->GetSelection()== 1 )
  226. {
  227. // Units = inches
  228. m_staticTextPrecision->SetLabel( precisionListForInches.GetPrecisionString() );
  229. }
  230. else
  231. {
  232. // metric options
  233. m_staticTextPrecision->SetLabel( precisionListForMetric.GetPrecisionString() );
  234. }
  235. if( m_Choice_Zeros_Format->GetSelection() == EXCELLON_WRITER::DECIMAL_FORMAT )
  236. m_staticTextPrecision->Enable( false );
  237. else
  238. m_staticTextPrecision->Enable( true );
  239. }
  240. void DIALOG_GENDRILL::OnOutputDirectoryBrowseClicked( wxCommandEvent& event )
  241. {
  242. // Build the absolute path of current output directory to preselect it in the file browser.
  243. wxString path = ExpandEnvVarSubstitutions( m_outputDirectoryName->GetValue(), &Prj() );
  244. path = Prj().AbsolutePath( path );
  245. wxDirDialog dirDialog( this, _( "Select Output Directory" ), path );
  246. if( dirDialog.ShowModal() == wxID_CANCEL )
  247. return;
  248. wxFileName dirName = wxFileName::DirName( dirDialog.GetPath() );
  249. wxFileName fn( Prj().AbsolutePath( m_board->GetFileName() ) );
  250. wxString defaultPath = fn.GetPathWithSep();
  251. wxString msg;
  252. msg.Printf( _( "Do you want to use a path relative to\n'%s'?" ), defaultPath );
  253. wxMessageDialog dialog( this, msg, _( "Plot Output Directory" ),
  254. wxYES_NO | wxICON_QUESTION | wxYES_DEFAULT );
  255. if( dialog.ShowModal() == wxID_YES )
  256. {
  257. if( !dirName.MakeRelativeTo( defaultPath ) )
  258. {
  259. wxMessageBox( _( "Cannot make path relative (target volume different from file volume)!" ),
  260. _( "Plot Output Directory" ), wxOK | wxICON_ERROR );
  261. }
  262. }
  263. m_outputDirectoryName->SetValue( dirName.GetFullPath() );
  264. }
  265. void DIALOG_GENDRILL::UpdateDrillParams()
  266. {
  267. wxString msg;
  268. // Set output directory and replace backslashes with forward ones
  269. wxString dirStr;
  270. dirStr = m_outputDirectoryName->GetValue();
  271. dirStr.Replace( wxT( "\\" ), wxT( "/" ) );
  272. m_plotOpts.SetOutputDirectory( dirStr );
  273. m_drillOriginIsAuxAxis = m_Choice_Drill_Offset->GetSelection() == 1;
  274. m_plotOpts.SetUseAuxOrigin( m_drillOriginIsAuxAxis );
  275. m_mapFileType = m_Choice_Drill_Map->GetSelection();
  276. m_UnitDrillIsInch = (m_Choice_Unit->GetSelection() == 0) ? false : true;
  277. m_MinimalHeader = m_Check_Minimal->IsChecked();
  278. m_Mirror = m_Check_Mirror->IsChecked();
  279. m_Merge_PTH_NPTH = m_Check_Merge_PTH_NPTH->IsChecked();
  280. m_ZerosFormat = m_Choice_Zeros_Format->GetSelection();
  281. m_UseRouteModeForOvalHoles = m_radioBoxOvalHoleMode->GetSelection() == 0;
  282. if( m_Choice_Drill_Offset->GetSelection() == 0 )
  283. m_DrillFileOffset = wxPoint( 0, 0 );
  284. else
  285. m_DrillFileOffset = m_board->GetDesignSettings().GetAuxOrigin();
  286. if( m_UnitDrillIsInch )
  287. m_Precision = precisionListForInches;
  288. else
  289. m_Precision = precisionListForMetric;
  290. if( !m_plotOpts.IsSameAs( m_board->GetPlotOptions() ) )
  291. {
  292. m_board->SetPlotOptions( m_plotOpts );
  293. m_pcbEditFrame->OnModify();
  294. }
  295. }
  296. void DIALOG_GENDRILL::GenDrillAndMapFiles( bool aGenDrill, bool aGenMap )
  297. {
  298. UpdateConfig(); // set params and Save drill options
  299. m_pcbEditFrame->ClearMsgPanel();
  300. WX_TEXT_CTRL_REPORTER reporter( m_messagesBox );
  301. const PLOT_FORMAT filefmt[6] = {
  302. // Keep these format ids in the same order than m_Choice_Drill_Map choices
  303. PLOT_FORMAT::HPGL,
  304. PLOT_FORMAT::POST,
  305. PLOT_FORMAT::GERBER,
  306. PLOT_FORMAT::DXF,
  307. PLOT_FORMAT::SVG,
  308. PLOT_FORMAT::PDF
  309. };
  310. unsigned choice = (unsigned) m_Choice_Drill_Map->GetSelection();
  311. if( choice >= arrayDim( filefmt ) )
  312. choice = 1;
  313. // Create output directory if it does not exist (also transform it in
  314. // absolute form). Bail if it fails
  315. wxString path = ExpandEnvVarSubstitutions( m_plotOpts.GetOutputDirectory(), &Prj() );
  316. wxFileName outputDir = wxFileName::DirName( path );
  317. wxString boardFilename = m_board->GetFileName();
  318. if( !EnsureFileDirectoryExists( &outputDir, boardFilename, &reporter ) )
  319. {
  320. wxString msg;
  321. msg.Printf( _( "Could not write drill and/or map files to folder '%s'." ),
  322. outputDir.GetPath() );
  323. DisplayError( this, msg );
  324. return;
  325. }
  326. if( m_drillFileType == 0 )
  327. {
  328. EXCELLON_WRITER excellonWriter( m_board );
  329. excellonWriter.SetFormat( !m_UnitDrillIsInch, (EXCELLON_WRITER::ZEROS_FMT) m_ZerosFormat,
  330. m_Precision.m_Lhs, m_Precision.m_Rhs );
  331. excellonWriter.SetOptions( m_Mirror, m_MinimalHeader, m_DrillFileOffset, m_Merge_PTH_NPTH );
  332. excellonWriter.SetRouteModeForOvalHoles( m_UseRouteModeForOvalHoles );
  333. excellonWriter.SetMapFileFormat( filefmt[choice] );
  334. excellonWriter.CreateDrillandMapFilesSet( outputDir.GetFullPath(), aGenDrill, aGenMap,
  335. &reporter );
  336. }
  337. else
  338. {
  339. GERBER_WRITER gerberWriter( m_board );
  340. // Set gerber precision: only 5 or 6 digits for mantissa are allowed
  341. // (SetFormat() accept 5 or 6, and any other value set the precision to 5)
  342. // the integer part precision is always 4, and units always mm
  343. gerberWriter.SetFormat( m_plotOpts.GetGerberPrecision() );
  344. gerberWriter.SetOptions( m_DrillFileOffset );
  345. gerberWriter.SetMapFileFormat( filefmt[choice] );
  346. gerberWriter.CreateDrillandMapFilesSet( outputDir.GetFullPath(), aGenDrill, aGenMap,
  347. &reporter );
  348. }
  349. }
  350. void DIALOG_GENDRILL::OnGenReportFile( wxCommandEvent& event )
  351. {
  352. UpdateConfig(); // set params and Save drill options
  353. wxFileName fn = m_board->GetFileName();
  354. fn.SetName( fn.GetName() + wxT( "-drl" ) );
  355. fn.SetExt( ReportFileExtension );
  356. wxString defaultPath = ExpandEnvVarSubstitutions( m_plotOpts.GetOutputDirectory(), &Prj() );
  357. defaultPath = Prj().AbsolutePath( defaultPath );
  358. if( defaultPath.IsEmpty() )
  359. defaultPath = PATHS::GetDefaultUserProjectsPath();
  360. wxFileDialog dlg( this, _( "Save Drill Report File" ), defaultPath, fn.GetFullName(),
  361. ReportFileWildcard(), wxFD_SAVE );
  362. if( dlg.ShowModal() == wxID_CANCEL )
  363. return;
  364. bool success;
  365. // Info is slightly different between Excellon and Gerber
  366. // (file ext, Merge PTH/NPTH option)
  367. if( m_drillFileType == 0 )
  368. {
  369. EXCELLON_WRITER excellonWriter( m_board );
  370. excellonWriter.SetMergeOption( m_Merge_PTH_NPTH );
  371. success = excellonWriter.GenDrillReportFile( dlg.GetPath() );
  372. }
  373. else
  374. {
  375. GERBER_WRITER gerberWriter( m_board );
  376. success = gerberWriter.GenDrillReportFile( dlg.GetPath() );
  377. }
  378. wxString msg;
  379. if( ! success )
  380. {
  381. msg.Printf( _( "Failed to create file '%s'." ), dlg.GetPath() );
  382. m_messagesBox->AppendText( msg );
  383. }
  384. else
  385. {
  386. msg.Printf( _( "Report file '%s' created." ), dlg.GetPath() );
  387. m_messagesBox->AppendText( msg );
  388. }
  389. }