KiCad PCB EDA Suite
Loading...
Searching...
No Matches
pcbnew_jobs_handler.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright (C) 2022 Mark Roszko <[email protected]>
5 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
6 *
7 * This program is free software: you can redistribute it and/or modify it
8 * under the terms of the GNU General Public License as published by the
9 * Free Software Foundation, either version 3 of the License, or (at your
10 * option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * 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, see <https://www.gnu.org/licenses/>.
19 */
20
21#include <richio.h>
22#include <wx/crt.h>
23#include <wx/dir.h>
24#include <wx/zipstrm.h>
25#include <wx/filename.h>
26#include <wx/tokenzr.h>
27#include <wx/wfstream.h>
28
29#include <nlohmann/json.hpp>
30
31#include "pcbnew_jobs_handler.h"
32#include <board_loader.h>
33#include <jobs/scratch_doc.h>
34#include <board_commit.h>
41#include <drc/drc_engine.h>
43#include <drc/drc_item.h>
44#include <drc/drc_report.h>
47#include <footprint.h>
49#include <jobs/job_fp_upgrade.h>
66#include <jobs/job_pcb_render.h>
67#include <jobs/job_pcb_drc.h>
68#include <jobs/job_pcb_import.h>
71#include <eda_units.h>
73#include <lset.h>
74#include <cli/exit_codes.h>
80#include <tool/tool_manager.h>
81#include <tools/drc_tool.h>
82#include <filename_resolver.h>
87#include <kiface_base.h>
88#include <macros.h>
89#include <pad.h>
90#include <pcb_marker.h>
94#include <kiface_ids.h>
97#include <pcbnew_settings.h>
98#include <pcbplot.h>
99#include <pcb_plotter.h>
100#include <pcb_edit_frame.h>
101#include <pcb_track.h>
102#include <pgm_base.h>
105#include <project_pcb.h>
108#include <reporter.h>
109#include <scoped_set_reset.h>
110#include <progress_reporter.h>
112#include <export_vrml.h>
113#include <kiplatform/io.h>
120#include <dialogs/dialog_plot.h>
125#include <paths.h>
127
128#include <locale_io.h>
129#include <confirm.h>
130
131
132#ifdef _WIN32
133#ifdef TRANSPARENT
134#undef TRANSPARENT
135#endif
136#endif
137
138
140 JOB_DISPATCHER( aKiway ),
141 m_cliBoard( nullptr ),
142 m_toolManager( nullptr )
143{
144 Register( "3d", std::bind( &PCBNEW_JOBS_HANDLER::JobExportStep, this, std::placeholders::_1 ),
145 [aKiway]( JOB* job, wxWindow* aParent ) -> bool
146 {
147 JOB_EXPORT_PCB_3D* svgJob = dynamic_cast<JOB_EXPORT_PCB_3D*>( job );
148
149 PCB_EDIT_FRAME* editFrame =
150 dynamic_cast<PCB_EDIT_FRAME*>( aKiway->Player( FRAME_PCB_EDITOR, false ) );
151
152 wxCHECK( svgJob && editFrame, false );
153
154 DIALOG_EXPORT_STEP dlg( editFrame, aParent, "", svgJob );
155 return dlg.ShowModal() == wxID_OK;
156 } );
157 Register( "render", std::bind( &PCBNEW_JOBS_HANDLER::JobExportRender, this, std::placeholders::_1 ),
158 []( JOB* job, wxWindow* aParent ) -> bool
159 {
160 JOB_PCB_RENDER* renderJob = dynamic_cast<JOB_PCB_RENDER*>( job );
161
162 wxCHECK( renderJob, false );
163
164 DIALOG_RENDER_JOB dlg( aParent, renderJob );
165 return dlg.ShowModal() == wxID_OK;
166 } );
167 Register( "upgrade", std::bind( &PCBNEW_JOBS_HANDLER::JobUpgrade, this, std::placeholders::_1 ),
168 []( JOB* job, wxWindow* aParent ) -> bool
169 {
170 return true;
171 } );
172 Register( "pcb_import", std::bind( &PCBNEW_JOBS_HANDLER::JobImport, this, std::placeholders::_1 ),
173 []( JOB* job, wxWindow* aParent ) -> bool
174 {
175 return true;
176 } );
177 Register( "svg", std::bind( &PCBNEW_JOBS_HANDLER::JobExportSvg, this, std::placeholders::_1 ),
178 [aKiway]( JOB* job, wxWindow* aParent ) -> bool
179 {
180 JOB_EXPORT_PCB_SVG* svgJob = dynamic_cast<JOB_EXPORT_PCB_SVG*>( job );
181
182 PCB_EDIT_FRAME* editFrame =
183 dynamic_cast<PCB_EDIT_FRAME*>( aKiway->Player( FRAME_PCB_EDITOR, false ) );
184
185 wxCHECK( svgJob && editFrame, false );
186
187 DIALOG_PLOT dlg( editFrame, aParent, svgJob );
188 return dlg.ShowModal() == wxID_OK;
189 } );
190 Register( "gencad", std::bind( &PCBNEW_JOBS_HANDLER::JobExportGencad, this, std::placeholders::_1 ),
191 [aKiway]( JOB* job, wxWindow* aParent ) -> bool
192 {
193 JOB_EXPORT_PCB_GENCAD* gencadJob = dynamic_cast<JOB_EXPORT_PCB_GENCAD*>( job );
194
195 PCB_EDIT_FRAME* editFrame =
196 dynamic_cast<PCB_EDIT_FRAME*>( aKiway->Player( FRAME_PCB_EDITOR, false ) );
197
198 wxCHECK( gencadJob && editFrame, false );
199
200 DIALOG_GENCAD_EXPORT_OPTIONS dlg( editFrame, gencadJob->GetSettingsDialogTitle(), gencadJob );
201 return dlg.ShowModal() == wxID_OK;
202 } );
203 Register( "dxf", std::bind( &PCBNEW_JOBS_HANDLER::JobExportDxf, this, std::placeholders::_1 ),
204 [aKiway]( JOB* job, wxWindow* aParent ) -> bool
205 {
206 JOB_EXPORT_PCB_DXF* dxfJob = dynamic_cast<JOB_EXPORT_PCB_DXF*>( job );
207
208 PCB_EDIT_FRAME* editFrame =
209 dynamic_cast<PCB_EDIT_FRAME*>( aKiway->Player( FRAME_PCB_EDITOR, false ) );
210
211 wxCHECK( dxfJob && editFrame, false );
212
213 DIALOG_PLOT dlg( editFrame, aParent, dxfJob );
214 return dlg.ShowModal() == wxID_OK;
215 } );
216 Register( "pdf", std::bind( &PCBNEW_JOBS_HANDLER::JobExportPdf, this, std::placeholders::_1 ),
217 [aKiway]( JOB* job, wxWindow* aParent ) -> bool
218 {
219 JOB_EXPORT_PCB_PDF* pdfJob = dynamic_cast<JOB_EXPORT_PCB_PDF*>( job );
220
221 PCB_EDIT_FRAME* editFrame =
222 dynamic_cast<PCB_EDIT_FRAME*>( aKiway->Player( FRAME_PCB_EDITOR, false ) );
223
224 wxCHECK( pdfJob && editFrame, false );
225
226 DIALOG_PLOT dlg( editFrame, aParent, pdfJob );
227 return dlg.ShowModal() == wxID_OK;
228 } );
229 Register( "png", std::bind( &PCBNEW_JOBS_HANDLER::JobExportPng, this, std::placeholders::_1 ),
230 [aKiway]( JOB* job, wxWindow* aParent ) -> bool
231 {
232 JOB_EXPORT_PCB_PNG* pngJob = dynamic_cast<JOB_EXPORT_PCB_PNG*>( job );
233
234 PCB_EDIT_FRAME* editFrame =
235 dynamic_cast<PCB_EDIT_FRAME*>( aKiway->Player( FRAME_PCB_EDITOR, false ) );
236
237 wxCHECK( pngJob && editFrame, false );
238
239 DIALOG_PLOT dlg( editFrame, aParent, pngJob );
240 return dlg.ShowModal() == wxID_OK;
241 } );
242 Register( "ps", std::bind( &PCBNEW_JOBS_HANDLER::JobExportPs, this, std::placeholders::_1 ),
243 [aKiway]( JOB* job, wxWindow* aParent ) -> bool
244 {
245 JOB_EXPORT_PCB_PS* psJob = dynamic_cast<JOB_EXPORT_PCB_PS*>( job );
246
247 PCB_EDIT_FRAME* editFrame =
248 dynamic_cast<PCB_EDIT_FRAME*>( aKiway->Player( FRAME_PCB_EDITOR, false ) );
249
250 wxCHECK( psJob && editFrame, false );
251
252 DIALOG_PLOT dlg( editFrame, aParent, psJob );
253 return dlg.ShowModal() == wxID_OK;
254 } );
255 Register( "stats", std::bind( &PCBNEW_JOBS_HANDLER::JobExportStats, this, std::placeholders::_1 ),
256 [aKiway]( JOB* job, wxWindow* aParent ) -> bool
257 {
258 JOB_EXPORT_PCB_STATS* statsJob = dynamic_cast<JOB_EXPORT_PCB_STATS*>( job );
259
260 PCB_EDIT_FRAME* editFrame =
261 dynamic_cast<PCB_EDIT_FRAME*>( aKiway->Player( FRAME_PCB_EDITOR, false ) );
262
263 wxCHECK( statsJob && editFrame, false );
264
265 if( statsJob->m_filename.IsEmpty() && editFrame->GetBoard() )
266 {
267 wxFileName boardName = editFrame->GetBoard()->GetFileName();
268 statsJob->m_filename = boardName.GetFullPath();
269 }
270
271 wxWindow* parent = aParent ? aParent : static_cast<wxWindow*>( editFrame );
272
273 DIALOG_BOARD_STATS_JOB dlg( parent, statsJob );
274
275 return dlg.ShowModal() == wxID_OK;
276 } );
277 Register( "gerber", std::bind( &PCBNEW_JOBS_HANDLER::JobExportGerber, this, std::placeholders::_1 ),
278 [aKiway]( JOB* job, wxWindow* aParent ) -> bool
279 {
280 JOB_EXPORT_PCB_GERBER* gJob = dynamic_cast<JOB_EXPORT_PCB_GERBER*>( job );
281
282 PCB_EDIT_FRAME* editFrame =
283 dynamic_cast<PCB_EDIT_FRAME*>( aKiway->Player( FRAME_PCB_EDITOR, false ) );
284
285 wxCHECK( gJob && editFrame, false );
286
287 DIALOG_PLOT dlg( editFrame, aParent, gJob );
288 return dlg.ShowModal() == wxID_OK;
289 } );
290 Register( "gerbers", std::bind( &PCBNEW_JOBS_HANDLER::JobExportGerbers, this, std::placeholders::_1 ),
291 [aKiway]( JOB* job, wxWindow* aParent ) -> bool
292 {
293 JOB_EXPORT_PCB_GERBERS* gJob = dynamic_cast<JOB_EXPORT_PCB_GERBERS*>( job );
294
295 PCB_EDIT_FRAME* editFrame =
296 dynamic_cast<PCB_EDIT_FRAME*>( aKiway->Player( FRAME_PCB_EDITOR, false ) );
297
298 wxCHECK( gJob && editFrame, false );
299
300 DIALOG_PLOT dlg( editFrame, aParent, gJob );
301 return dlg.ShowModal() == wxID_OK;
302 } );
303 Register(
304 "hpgl",
305 [&]( JOB* aJob )
306 {
307 m_reporter->Report( _( "Plotting to HPGL is no longer supported as of KiCad 10.0.\n" ),
310 },
311 [aKiway]( JOB* job, wxWindow* aParent ) -> bool
312 {
313 PCB_EDIT_FRAME* editFrame = dynamic_cast<PCB_EDIT_FRAME*>( aKiway->Player( FRAME_PCB_EDITOR, false ) );
314
315 wxCHECK( editFrame, false );
316
317 DisplayErrorMessage( editFrame, _( "Plotting to HPGL is no longer supported as of KiCad 10.0." ) );
318 return false;
319 } );
320 Register( "drill", std::bind( &PCBNEW_JOBS_HANDLER::JobExportDrill, this, std::placeholders::_1 ),
321 [aKiway]( JOB* job, wxWindow* aParent ) -> bool
322 {
323 JOB_EXPORT_PCB_DRILL* drillJob = dynamic_cast<JOB_EXPORT_PCB_DRILL*>( job );
324
325 PCB_EDIT_FRAME* editFrame =
326 dynamic_cast<PCB_EDIT_FRAME*>( aKiway->Player( FRAME_PCB_EDITOR, false ) );
327
328 wxCHECK( drillJob && editFrame, false );
329
330 DIALOG_GENDRILL dlg( editFrame, drillJob, aParent );
331 return dlg.ShowModal() == wxID_OK;
332 } );
333 Register( "pos", std::bind( &PCBNEW_JOBS_HANDLER::JobExportPos, this, std::placeholders::_1 ),
334 [aKiway]( JOB* job, wxWindow* aParent ) -> bool
335 {
336 JOB_EXPORT_PCB_POS* posJob = dynamic_cast<JOB_EXPORT_PCB_POS*>( job );
337
338 PCB_EDIT_FRAME* editFrame =
339 dynamic_cast<PCB_EDIT_FRAME*>( aKiway->Player( FRAME_PCB_EDITOR, false ) );
340
341 wxCHECK( posJob && editFrame, false );
342
343 DIALOG_GEN_FOOTPRINT_POSITION dlg( posJob, editFrame, aParent );
344 return dlg.ShowModal() == wxID_OK;
345 } );
346 Register( "fpupgrade", std::bind( &PCBNEW_JOBS_HANDLER::JobExportFpUpgrade, this, std::placeholders::_1 ),
347 []( JOB* job, wxWindow* aParent ) -> bool
348 {
349 return true;
350 } );
351 Register( "fpsvg", std::bind( &PCBNEW_JOBS_HANDLER::JobExportFpSvg, this, std::placeholders::_1 ),
352 []( JOB* job, wxWindow* aParent ) -> bool
353 {
354 return true;
355 } );
356 Register( "pcb_diff", std::bind( &PCBNEW_JOBS_HANDLER::JobDiff, this, std::placeholders::_1 ),
357 []( JOB* job, wxWindow* aParent ) -> bool
358 {
359 return true;
360 } );
361 Register( "fp_diff", std::bind( &PCBNEW_JOBS_HANDLER::JobFpDiff, this, std::placeholders::_1 ),
362 []( JOB* job, wxWindow* aParent ) -> bool
363 {
364 return true;
365 } );
366 Register( "drc", std::bind( &PCBNEW_JOBS_HANDLER::JobExportDrc, this, std::placeholders::_1 ),
367 []( JOB* job, wxWindow* aParent ) -> bool
368 {
369 JOB_PCB_DRC* drcJob = dynamic_cast<JOB_PCB_DRC*>( job );
370
371 wxCHECK( drcJob, false );
372
373 DIALOG_DRC_JOB_CONFIG dlg( aParent, drcJob );
374 return dlg.ShowModal() == wxID_OK;
375 } );
376 Register( "ipc2581", std::bind( &PCBNEW_JOBS_HANDLER::JobExportIpc2581, this, std::placeholders::_1 ),
377 [aKiway]( JOB* job, wxWindow* aParent ) -> bool
378 {
379 JOB_EXPORT_PCB_IPC2581* ipcJob = dynamic_cast<JOB_EXPORT_PCB_IPC2581*>( job );
380
381 PCB_EDIT_FRAME* editFrame =
382 dynamic_cast<PCB_EDIT_FRAME*>( aKiway->Player( FRAME_PCB_EDITOR, false ) );
383
384 wxCHECK( ipcJob && editFrame, false );
385
386 DIALOG_EXPORT_2581 dlg( ipcJob, editFrame, aParent );
387 return dlg.ShowModal() == wxID_OK;
388 } );
389 Register( "ipcd356", std::bind( &PCBNEW_JOBS_HANDLER::JobExportIpcD356, this, std::placeholders::_1 ),
390 []( JOB* job, wxWindow* aParent ) -> bool
391 {
392 return true;
393 } );
394 Register( "odb", std::bind( &PCBNEW_JOBS_HANDLER::JobExportOdb, this, std::placeholders::_1 ),
395 [aKiway]( JOB* job, wxWindow* aParent ) -> bool
396 {
397 JOB_EXPORT_PCB_ODB* odbJob = dynamic_cast<JOB_EXPORT_PCB_ODB*>( job );
398
399 PCB_EDIT_FRAME* editFrame =
400 dynamic_cast<PCB_EDIT_FRAME*>( aKiway->Player( FRAME_PCB_EDITOR, false ) );
401
402 wxCHECK( odbJob && editFrame, false );
403
404 DIALOG_EXPORT_ODBPP dlg( odbJob, editFrame, aParent );
405 return dlg.ShowModal() == wxID_OK;
406 } );
407}
408
409
413
414
416{
417 delete m_cliBoard;
418 m_cliBoard = nullptr;
419 m_toolManager.reset();
420}
421
422
424{
425 TOOL_MANAGER* toolManager = nullptr;
426 if( Pgm().IsGUI() )
427 {
428 // we assume the PCB we are working on here is the one in the frame
429 // so use the frame's tool manager
430 PCB_EDIT_FRAME* editFrame = (PCB_EDIT_FRAME*) m_kiway->Player( FRAME_PCB_EDITOR, false );
431 if( editFrame )
432 toolManager = editFrame->GetToolManager();
433 }
434 else
435 {
436 if( m_toolManager == nullptr )
437 {
438 m_toolManager = std::make_unique<TOOL_MANAGER>();
439 }
440
441 toolManager = m_toolManager.get();
442
443 toolManager->SetEnvironment( aBrd, nullptr, nullptr, Kiface().KifaceSettings(), nullptr );
444 }
445 return toolManager;
446}
447
448
449BOARD* PCBNEW_JOBS_HANDLER::getBoard( const wxString& aPath )
450{
451 BOARD* brd = nullptr;
452 SETTINGS_MANAGER& settingsManager = Pgm().GetSettingsManager();
453 wxString loadError;
454
455 auto getProjectForBoard = [&]( const wxString& aBoardPath ) -> PROJECT*
456 {
457 wxFileName pro = aBoardPath;
458 pro.SetExt( FILEEXT::ProjectFileExtension );
459 pro.MakeAbsolute();
460
461 PROJECT* project = settingsManager.GetProject( pro.GetFullPath() );
462
463 if( !project )
464 {
465 settingsManager.LoadProject( pro.GetFullPath(), true );
466 project = settingsManager.GetProject( pro.GetFullPath() );
467 }
468
469 return project;
470 };
471
472 auto loadBoardFromPath = [&]( const wxString& aBoardPath ) -> BOARD*
473 {
474 PROJECT* project = getProjectForBoard( aBoardPath );
475
477
478 if( !project || pluginType == PCB_IO_MGR::FILE_TYPE_NONE )
479 return nullptr;
480
481 try
482 {
483 std::unique_ptr<BOARD> loadedBoard = BOARD_LOADER::Load( aBoardPath, pluginType, project );
484 return loadedBoard.release();
485 }
486 catch( const IO_ERROR& ioe )
487 {
488 loadError = ioe.What();
489 return nullptr;
490 }
491 catch( ... )
492 {
493 return nullptr;
494 }
495 };
496
497 if( !Pgm().IsGUI() && Pgm().GetSettingsManager().IsProjectOpen() )
498 {
499 wxString pcbPath = aPath;
500
501 if( pcbPath.IsEmpty() )
502 {
503 wxFileName path = Pgm().GetSettingsManager().Prj().GetProjectFullName();
505 path.MakeAbsolute();
506 pcbPath = path.GetFullPath();
507 }
508
509 if( !m_cliBoard )
510 m_cliBoard = loadBoardFromPath( pcbPath );
511
512 brd = m_cliBoard;
513 }
514 else if( Pgm().IsGUI() && Pgm().GetSettingsManager().IsProjectOpen() )
515 {
516 PCB_EDIT_FRAME* editFrame = (PCB_EDIT_FRAME*) m_kiway->Player( FRAME_PCB_EDITOR, false );
517
518 if( editFrame )
519 brd = editFrame->GetBoard();
520 }
521 else
522 {
523 brd = loadBoardFromPath( aPath );
524 }
525
526 if( !brd )
527 {
528 wxString msg = _( "Failed to load board" );
529
530 if( !loadError.IsEmpty() )
531 msg += wxString::Format( wxS( ": %s" ), loadError );
532
533 m_reporter->Report( msg + '\n', RPT_SEVERITY_ERROR );
534 }
535
536 return brd;
537}
538
539
540LSEQ PCBNEW_JOBS_HANDLER::convertLayerArg( wxString& aLayerString, BOARD* aBoard ) const
541{
542 std::map<wxString, LSET> layerUserMasks;
543 std::map<wxString, LSET> layerMasks;
544 std::map<wxString, LSET> layerGuiMasks;
545
546 // Build list of layer names and their layer mask:
547 for( PCB_LAYER_ID layer : LSET::AllLayersMask() )
548 {
549 // Add user layer name
550 if( aBoard )
551 layerUserMasks[aBoard->GetLayerName( layer )] = LSET( { layer } );
552
553 // Add layer name used in pcb files
554 layerMasks[LSET::Name( layer )] = LSET( { layer } );
555 // Add layer name using GUI canonical layer name
556 layerGuiMasks[LayerName( layer )] = LSET( { layer } );
557 }
558
559 // Add list of grouped layer names used in pcb files
560 layerMasks[wxT( "*" )] = LSET::AllLayersMask();
561 layerMasks[wxT( "*.Cu" )] = LSET::AllCuMask();
562 layerMasks[wxT( "*In.Cu" )] = LSET::InternalCuMask();
563 layerMasks[wxT( "F&B.Cu" )] = LSET( { F_Cu, B_Cu } );
564 layerMasks[wxT( "*.Adhes" )] = LSET( { B_Adhes, F_Adhes } );
565 layerMasks[wxT( "*.Paste" )] = LSET( { B_Paste, F_Paste } );
566 layerMasks[wxT( "*.Mask" )] = LSET( { B_Mask, F_Mask } );
567 layerMasks[wxT( "*.SilkS" )] = LSET( { B_SilkS, F_SilkS } );
568 layerMasks[wxT( "*.Fab" )] = LSET( { B_Fab, F_Fab } );
569 layerMasks[wxT( "*.CrtYd" )] = LSET( { B_CrtYd, F_CrtYd } );
570
571 // Add list of grouped layer names using GUI canonical layer names
572 layerGuiMasks[wxT( "*.Adhesive" )] = LSET( { B_Adhes, F_Adhes } );
573 layerGuiMasks[wxT( "*.Silkscreen" )] = LSET( { B_SilkS, F_SilkS } );
574 layerGuiMasks[wxT( "*.Courtyard" )] = LSET( { B_CrtYd, F_CrtYd } );
575
576 LSEQ layerMask;
577
578 auto pushLayers = [&]( const LSET& layerSet )
579 {
580 for( PCB_LAYER_ID layer : layerSet.Seq() )
581 layerMask.push_back( layer );
582 };
583
584 if( !aLayerString.IsEmpty() )
585 {
586 wxStringTokenizer layerTokens( aLayerString, "," );
587
588 while( layerTokens.HasMoreTokens() )
589 {
590 std::string token = TO_UTF8( layerTokens.GetNextToken().Trim( true ).Trim( false ) );
591
592 if( layerUserMasks.contains( token ) )
593 pushLayers( layerUserMasks.at( token ) );
594 else if( layerMasks.count( token ) )
595 pushLayers( layerMasks.at( token ) );
596 else if( layerGuiMasks.count( token ) )
597 pushLayers( layerGuiMasks.at( token ) );
598 else
599 m_reporter->Report( wxString::Format( _( "Invalid layer name '%s'\n" ), token ) );
600 }
601 }
602
603 return layerMask;
604}
605
606
608{
609 JOB_EXPORT_PCB_3D* aStepJob = dynamic_cast<JOB_EXPORT_PCB_3D*>( aJob );
610
611 if( aStepJob == nullptr )
613
614 BOARD* brd = getBoard( aStepJob->m_filename );
615
616 if( !brd )
618
619 if( !aStepJob->m_variant.IsEmpty() )
620 brd->SetCurrentVariant( aStepJob->m_variant );
621
622 if( aStepJob->GetConfiguredOutputPath().IsEmpty() )
623 {
624 wxFileName fn = brd->GetFileName();
625 fn.SetName( fn.GetName() );
626
627 switch( aStepJob->m_format )
628 {
638 default:
639 m_reporter->Report( _( "Unknown export format" ), RPT_SEVERITY_ERROR );
640 return CLI::EXIT_CODES::ERR_UNKNOWN; // shouldnt have gotten here
641 }
642
643 aStepJob->SetWorkingOutputPath( fn.GetFullName() );
644 }
645
646 wxString outPath = resolveJobOutputPath( aJob, brd );
647
648 if( !PATHS::EnsurePathExists( outPath, true ) )
649 {
650 m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
652 }
653
655 {
656 double scale = 0.0;
657 switch( aStepJob->m_vrmlUnits )
658 {
659 case JOB_EXPORT_PCB_3D::VRML_UNITS::MM: scale = 1.0; break;
660 case JOB_EXPORT_PCB_3D::VRML_UNITS::METERS: scale = 0.001; break;
661 case JOB_EXPORT_PCB_3D::VRML_UNITS::TENTHS: scale = 10.0 / 25.4; break;
662 case JOB_EXPORT_PCB_3D::VRML_UNITS::INCH: scale = 1.0 / 25.4; break;
663 }
664
665 EXPORTER_VRML vrmlExporter( brd );
666 wxString messages;
667
668 double originX = pcbIUScale.IUTomm( aStepJob->m_3dparams.m_Origin.x );
669 double originY = pcbIUScale.IUTomm( aStepJob->m_3dparams.m_Origin.y );
670
671 if( !aStepJob->m_hasUserOrigin )
672 {
673 BOX2I bbox = brd->ComputeBoundingBox( true, true );
674 originX = pcbIUScale.IUTomm( bbox.GetCenter().x );
675 originY = pcbIUScale.IUTomm( bbox.GetCenter().y );
676 }
677
678 bool success = vrmlExporter.ExportVRML_File(
679 brd->GetProject(), &messages, outPath, scale, aStepJob->m_3dparams.m_IncludeUnspecified,
680 aStepJob->m_3dparams.m_IncludeDNP, !aStepJob->m_vrmlModelDir.IsEmpty(), aStepJob->m_vrmlRelativePaths,
681 aStepJob->m_vrmlModelDir, originX, originY );
682
683 if( success )
684 {
685 m_reporter->Report( wxString::Format( _( "Successfully exported VRML to %s" ), outPath ),
687 }
688 else
689 {
690 m_reporter->Report( _( "Error exporting VRML" ), RPT_SEVERITY_ERROR );
692 }
693 }
694 else
695 {
696 EXPORTER_STEP_PARAMS params = aStepJob->m_3dparams;
697
698 switch( aStepJob->m_format )
699 {
709 default:
710 m_reporter->Report( _( "Unknown export format" ), RPT_SEVERITY_ERROR );
711 return CLI::EXIT_CODES::ERR_UNKNOWN; // shouldnt have gotten here
712 }
713
714 EXPORTER_STEP stepExporter( brd, params, m_reporter );
715 stepExporter.m_outputFile = aStepJob->GetFullOutputPath( brd->GetProject() );
716
717 if( !stepExporter.Export() )
719 }
720
721 return CLI::EXIT_CODES::OK;
722}
723
724
726{
727 JOB_PCB_RENDER* aRenderJob = dynamic_cast<JOB_PCB_RENDER*>( aJob );
728
729 if( aRenderJob == nullptr )
731
732 // Reject width and height being invalid
733 // Final bit of sanity because this can blow things up
734 if( aRenderJob->m_width <= 0 || aRenderJob->m_height <= 0 )
735 {
736 m_reporter->Report( _( "Invalid image dimensions" ), RPT_SEVERITY_ERROR );
738 }
739
740 BOARD* brd = getBoard( aRenderJob->m_filename );
741
742 if( !brd )
744
745 if( !aRenderJob->m_variant.IsEmpty() )
746 brd->SetCurrentVariant( aRenderJob->m_variant );
747
748 if( aRenderJob->GetConfiguredOutputPath().IsEmpty() )
749 {
750 wxFileName fn = brd->GetFileName();
751
752 switch( aRenderJob->m_format )
753 {
756 default:
757 m_reporter->Report( _( "Unknown export format" ), RPT_SEVERITY_ERROR );
758 return CLI::EXIT_CODES::ERR_UNKNOWN; // shouldnt have gotten here
759 }
760
761 // set the name to board name + "side", its lazy but its hard to generate anything truely unique
762 // incase someone is doing this in a jobset with multiple jobs, they should be setting the output themselves
763 // or we do a hash based on all the options
764 fn.SetName( wxString::Format( "%s-%d", fn.GetName(), static_cast<int>( aRenderJob->m_side ) ) );
765
766 aRenderJob->SetWorkingOutputPath( fn.GetFullName() );
767 }
768
769 wxString outPath = resolveJobOutputPath( aJob, brd );
770
771 if( !PATHS::EnsurePathExists( outPath, true ) )
772 {
773 m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
775 }
776
777 BOARD_ADAPTER boardAdapter;
778
779 boardAdapter.SetBoard( brd );
780 boardAdapter.m_IsBoardView = false;
781
783
785 {
786 cfg.m_Render = userCfg->m_Render;
787 cfg.m_Camera = userCfg->m_Camera;
788 cfg.m_LayerPresets = userCfg->m_LayerPresets;
789 }
790
791 if( aRenderJob->m_appearancePreset.empty() )
792 {
793 // Force display 3D models
795 cfg.m_Render.show_footprints_dnp = true;
799 }
800
801 if( aRenderJob->m_quality == JOB_PCB_RENDER::QUALITY::BASIC )
802 {
803 // Silkscreen is pixelated without antialiasing
805
806 cfg.m_Render.raytrace_backfloor = aRenderJob->m_floor;
807 cfg.m_Render.raytrace_post_processing = aRenderJob->m_floor;
808
810 cfg.m_Render.raytrace_reflections = false;
811 cfg.m_Render.raytrace_shadows = aRenderJob->m_floor;
812
813 // Better colors
815
816 // Tracks below soldermask are not visible without refractions
819 }
820 else if( aRenderJob->m_quality == JOB_PCB_RENDER::QUALITY::HIGH )
821 {
823 cfg.m_Render.raytrace_backfloor = true;
827 cfg.m_Render.raytrace_shadows = true;
830 }
831 else if( aRenderJob->m_quality == JOB_PCB_RENDER::QUALITY::JOB_SETTINGS )
832 {
834 cfg.m_Render.raytrace_backfloor = aRenderJob->m_floor;
837 }
838
840 aRenderJob->m_lightTopIntensity.z, 1.0 );
841
843 COLOR4D( aRenderJob->m_lightBottomIntensity.x, aRenderJob->m_lightBottomIntensity.y,
844 aRenderJob->m_lightBottomIntensity.z, 1.0 );
845
847 COLOR4D( aRenderJob->m_lightCameraIntensity.x, aRenderJob->m_lightCameraIntensity.y,
848 aRenderJob->m_lightCameraIntensity.z, 1.0 );
849
850 COLOR4D lightColor( aRenderJob->m_lightSideIntensity.x, aRenderJob->m_lightSideIntensity.y,
851 aRenderJob->m_lightSideIntensity.z, 1.0 );
852
854 lightColor, lightColor, lightColor, lightColor, lightColor, lightColor, lightColor, lightColor,
855 };
856
857 int sideElevation = aRenderJob->m_lightSideElevation;
858
860 sideElevation, sideElevation, sideElevation, sideElevation,
861 -sideElevation, -sideElevation, -sideElevation, -sideElevation,
862 };
863
865 45, 135, 225, 315, 45, 135, 225, 315,
866 };
867
868 cfg.m_CurrentPreset = aRenderJob->m_appearancePreset;
870 boardAdapter.m_Cfg = &cfg;
871
872 // Apply the preset's layer visibility and colors to the render settings
873 if( !aRenderJob->m_appearancePreset.empty() )
874 {
875 wxString presetName = wxString::FromUTF8( aRenderJob->m_appearancePreset );
876
877 if( presetName == FOLLOW_PCB || presetName == FOLLOW_PLOT_SETTINGS )
878 {
879 boardAdapter.SetVisibleLayers( boardAdapter.GetVisibleLayers() );
880 }
881 else if( LAYER_PRESET_3D* preset = cfg.FindPreset( presetName ) )
882 {
883 boardAdapter.SetVisibleLayers( preset->layers );
884 boardAdapter.SetLayerColors( preset->colors );
885
886 if( preset->name.Lower() == _( "legacy colors" ) )
887 cfg.m_UseStackupColors = false;
888 }
889 }
890
893 && aRenderJob->m_format == JOB_PCB_RENDER::FORMAT::PNG ) )
894 {
895 boardAdapter.m_ColorOverrides[LAYER_3D_BACKGROUND_TOP] = COLOR4D( 1.0, 1.0, 1.0, 0.0 );
896 boardAdapter.m_ColorOverrides[LAYER_3D_BACKGROUND_BOTTOM] = COLOR4D( 1.0, 1.0, 1.0, 0.0 );
897 }
898
900
901 static std::map<JOB_PCB_RENDER::SIDE, VIEW3D_TYPE> s_viewCmdMap = {
908 };
909
911
912 wxSize windowSize( aRenderJob->m_width, aRenderJob->m_height );
913 TRACK_BALL camera( 2 * RANGE_SCALE_3D );
914
915 camera.SetProjection( projection );
916 camera.SetCurWindowSize( windowSize );
917
918 RENDER_3D_RAYTRACE_RAM raytrace( boardAdapter, camera );
919 raytrace.SetCurWindowSize( windowSize );
920
921 for( bool first = true; raytrace.Redraw( false, m_reporter, m_reporter ); first = false )
922 {
923 if( first )
924 {
925 const float cmTo3D = boardAdapter.BiuTo3dUnits() * pcbIUScale.mmToIU( 10.0 );
926
927 // First redraw resets lookat point to the board center, so set up the camera here
928 camera.ViewCommand_T1( s_viewCmdMap[aRenderJob->m_side] );
929
930 camera.SetLookAtPos_T1( camera.GetLookAtPos_T1()
931 + SFVEC3F( aRenderJob->m_pivot.x, aRenderJob->m_pivot.y, aRenderJob->m_pivot.z )
932 * cmTo3D );
933
934 camera.Pan_T1( SFVEC3F( aRenderJob->m_pan.x, aRenderJob->m_pan.y, aRenderJob->m_pan.z ) );
935
936 camera.Zoom_T1( aRenderJob->m_zoom );
937
938 camera.RotateX_T1( DEG2RAD( aRenderJob->m_rotation.x ) );
939 camera.RotateY_T1( DEG2RAD( aRenderJob->m_rotation.y ) );
940 camera.RotateZ_T1( DEG2RAD( aRenderJob->m_rotation.z ) );
941
942 camera.Interpolate( 1.0f );
943 camera.SetT0_and_T1_current_T();
944 camera.ParametersChanged();
945 }
946 }
947
948 uint8_t* rgbaBuffer = raytrace.GetBuffer();
949 wxSize realSize = raytrace.GetRealBufferSize();
950 bool success = !!rgbaBuffer;
951
952 if( rgbaBuffer )
953 {
954 const unsigned int wxh = realSize.x * realSize.y;
955
956 unsigned char* rgbBuffer = (unsigned char*) malloc( wxh * 3 );
957 unsigned char* alphaBuffer = (unsigned char*) malloc( wxh );
958
959 unsigned char* rgbaPtr = rgbaBuffer;
960 unsigned char* rgbPtr = rgbBuffer;
961 unsigned char* alphaPtr = alphaBuffer;
962
963 for( int y = 0; y < realSize.y; y++ )
964 {
965 for( int x = 0; x < realSize.x; x++ )
966 {
967 rgbPtr[0] = rgbaPtr[0];
968 rgbPtr[1] = rgbaPtr[1];
969 rgbPtr[2] = rgbaPtr[2];
970 alphaPtr[0] = rgbaPtr[3];
971
972 rgbaPtr += 4;
973 rgbPtr += 3;
974 alphaPtr += 1;
975 }
976 }
977
978 wxImage image( realSize );
979 image.SetData( rgbBuffer );
980 image.SetAlpha( alphaBuffer );
981 image = image.Mirror( false );
982
983 image.SetOption( wxIMAGE_OPTION_QUALITY, 90 );
984 image.SaveFile( outPath,
985 aRenderJob->m_format == JOB_PCB_RENDER::FORMAT::PNG ? wxBITMAP_TYPE_PNG : wxBITMAP_TYPE_JPEG );
986 }
987
988 if( success )
989 {
990 m_reporter->Report( _( "Successfully created 3D render image" ) + wxS( "\n" ), RPT_SEVERITY_INFO );
991 return CLI::EXIT_CODES::OK;
992 }
993 else
994 {
995 m_reporter->Report( _( "Error creating 3D render image" ) + wxS( "\n" ), RPT_SEVERITY_ERROR );
997 }
998}
999
1000
1002{
1003 JOB_EXPORT_PCB_SVG* aSvgJob = dynamic_cast<JOB_EXPORT_PCB_SVG*>( aJob );
1004
1005 if( aSvgJob == nullptr )
1007
1008 BOARD* brd = getBoard( aSvgJob->m_filename );
1009 TOOL_MANAGER* toolManager = getToolManager( brd );
1010
1011 if( !brd )
1013
1014 if( !aSvgJob->m_variant.IsEmpty() )
1015 brd->SetCurrentVariant( aSvgJob->m_variant );
1016
1018 {
1019 if( aSvgJob->GetConfiguredOutputPath().IsEmpty() )
1020 {
1021 wxFileName fn = brd->GetFileName();
1022 fn.SetName( fn.GetName() );
1024
1025 aSvgJob->SetWorkingOutputPath( fn.GetFullName() );
1026 }
1027 }
1028
1029 wxString outPath = resolveJobOutputPath( aJob, brd, &aSvgJob->m_drawingSheet );
1030
1032 {
1033 m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
1035 }
1036
1037 if( aSvgJob->m_checkZonesBeforePlot )
1038 {
1039 if( !toolManager->FindTool( ZONE_FILLER_TOOL_NAME ) )
1040 toolManager->RegisterTool( new ZONE_FILLER_TOOL );
1041
1042 toolManager->GetTool<ZONE_FILLER_TOOL>()->FillAllZones( nullptr, m_progressReporter, true );
1043 }
1044
1045 if( aSvgJob->m_argLayers )
1046 aSvgJob->m_plotLayerSequence = convertLayerArg( aSvgJob->m_argLayers.value(), brd );
1047
1048 if( aSvgJob->m_argCommonLayers )
1049 aSvgJob->m_plotOnAllLayersSequence = convertLayerArg( aSvgJob->m_argCommonLayers.value(), brd );
1050
1051 if( aSvgJob->m_plotLayerSequence.size() < 1 )
1052 {
1053 m_reporter->Report( _( "At least one layer must be specified\n" ), RPT_SEVERITY_ERROR );
1055 }
1056
1057 PCB_PLOT_PARAMS plotOpts;
1058 PCB_PLOTTER::PlotJobToPlotOpts( plotOpts, aSvgJob, *m_reporter );
1059
1060 PCB_PLOTTER plotter( brd, m_reporter, plotOpts );
1061
1062 std::optional<wxString> layerName;
1063 std::optional<wxString> sheetName;
1064 std::optional<wxString> sheetPath;
1065 std::vector<wxString> outputPaths;
1066
1068 {
1069 if( aJob->GetVarOverrides().contains( wxT( "LAYER" ) ) )
1070 layerName = aSvgJob->GetVarOverrides().at( wxT( "LAYER" ) );
1071
1072 if( aJob->GetVarOverrides().contains( wxT( "SHEETNAME" ) ) )
1073 sheetName = aSvgJob->GetVarOverrides().at( wxT( "SHEETNAME" ) );
1074
1075 if( aJob->GetVarOverrides().contains( wxT( "SHEETPATH" ) ) )
1076 sheetPath = aSvgJob->GetVarOverrides().at( wxT( "SHEETPATH" ) );
1077 }
1078
1079 if( !plotter.Plot( outPath, aSvgJob->m_plotLayerSequence, aSvgJob->m_plotOnAllLayersSequence, false,
1080 aSvgJob->m_genMode == JOB_EXPORT_PCB_SVG::GEN_MODE::SINGLE, layerName, sheetName, sheetPath,
1081 &outputPaths ) )
1082 {
1084 }
1085
1086 for( const wxString& outputPath : outputPaths )
1087 aSvgJob->AddOutput( outputPath );
1088
1089 return CLI::EXIT_CODES::OK;
1090}
1091
1092
1094{
1095 JOB_EXPORT_PCB_DXF* aDxfJob = dynamic_cast<JOB_EXPORT_PCB_DXF*>( aJob );
1096
1097 if( aDxfJob == nullptr )
1099
1100 BOARD* brd = getBoard( aDxfJob->m_filename );
1101
1102 if( !brd )
1104
1105 if( !aDxfJob->m_variant.IsEmpty() )
1106 brd->SetCurrentVariant( aDxfJob->m_variant );
1107
1108 TOOL_MANAGER* toolManager = getToolManager( brd );
1109
1110 if( aDxfJob->m_checkZonesBeforePlot )
1111 {
1112 if( !toolManager->FindTool( ZONE_FILLER_TOOL_NAME ) )
1113 toolManager->RegisterTool( new ZONE_FILLER_TOOL );
1114
1115 toolManager->GetTool<ZONE_FILLER_TOOL>()->FillAllZones( nullptr, m_progressReporter, true );
1116 }
1117
1118 if( aDxfJob->m_argLayers )
1119 aDxfJob->m_plotLayerSequence = convertLayerArg( aDxfJob->m_argLayers.value(), brd );
1120
1121 if( aDxfJob->m_argCommonLayers )
1122 aDxfJob->m_plotOnAllLayersSequence = convertLayerArg( aDxfJob->m_argCommonLayers.value(), brd );
1123
1124 if( aDxfJob->m_plotLayerSequence.size() < 1 )
1125 {
1126 m_reporter->Report( _( "At least one layer must be specified\n" ), RPT_SEVERITY_ERROR );
1128 }
1129
1131 {
1132 if( aDxfJob->GetConfiguredOutputPath().IsEmpty() )
1133 {
1134 wxFileName fn = brd->GetFileName();
1135 fn.SetName( fn.GetName() );
1137
1138 aDxfJob->SetWorkingOutputPath( fn.GetFullName() );
1139 }
1140 }
1141
1142 wxString outPath = resolveJobOutputPath( aJob, brd, &aDxfJob->m_drawingSheet );
1143
1145 {
1146 m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
1148 }
1149
1150 PCB_PLOT_PARAMS plotOpts;
1151 PCB_PLOTTER::PlotJobToPlotOpts( plotOpts, aDxfJob, *m_reporter );
1152
1153 PCB_PLOTTER plotter( brd, m_reporter, plotOpts );
1154
1155 std::optional<wxString> layerName;
1156 std::optional<wxString> sheetName;
1157 std::optional<wxString> sheetPath;
1158
1160 {
1161 if( aJob->GetVarOverrides().contains( wxT( "LAYER" ) ) )
1162 layerName = aDxfJob->GetVarOverrides().at( wxT( "LAYER" ) );
1163
1164 if( aJob->GetVarOverrides().contains( wxT( "SHEETNAME" ) ) )
1165 sheetName = aDxfJob->GetVarOverrides().at( wxT( "SHEETNAME" ) );
1166
1167 if( aJob->GetVarOverrides().contains( wxT( "SHEETPATH" ) ) )
1168 sheetPath = aDxfJob->GetVarOverrides().at( wxT( "SHEETPATH" ) );
1169 }
1170
1171 std::vector<wxString> outputPaths;
1172
1173 if( !plotter.Plot( outPath, aDxfJob->m_plotLayerSequence, aDxfJob->m_plotOnAllLayersSequence, false,
1174 aDxfJob->m_genMode == JOB_EXPORT_PCB_DXF::GEN_MODE::SINGLE, layerName, sheetName, sheetPath,
1175 &outputPaths ) )
1176 {
1178 }
1179
1180 for( const wxString& outputPath : outputPaths )
1181 aJob->AddOutput( outputPath );
1182
1183 return CLI::EXIT_CODES::OK;
1184}
1185
1186
1188{
1189 bool plotAllLayersOneFile = false;
1190 JOB_EXPORT_PCB_PDF* pdfJob = dynamic_cast<JOB_EXPORT_PCB_PDF*>( aJob );
1191
1192 if( pdfJob == nullptr )
1194
1195 BOARD* brd = getBoard( pdfJob->m_filename );
1196
1197 if( !brd )
1199
1200 if( !pdfJob->m_variant.IsEmpty() )
1201 brd->SetCurrentVariant( pdfJob->m_variant );
1202
1203 TOOL_MANAGER* toolManager = getToolManager( brd );
1204
1205 if( pdfJob->m_checkZonesBeforePlot )
1206 {
1207 if( !toolManager->FindTool( ZONE_FILLER_TOOL_NAME ) )
1208 toolManager->RegisterTool( new ZONE_FILLER_TOOL );
1209
1210 toolManager->GetTool<ZONE_FILLER_TOOL>()->FillAllZones( nullptr, m_progressReporter, true );
1211 }
1212
1213 if( pdfJob->m_argLayers )
1214 pdfJob->m_plotLayerSequence = convertLayerArg( pdfJob->m_argLayers.value(), brd );
1215
1216 if( pdfJob->m_argCommonLayers )
1217 pdfJob->m_plotOnAllLayersSequence = convertLayerArg( pdfJob->m_argCommonLayers.value(), brd );
1218
1220 plotAllLayersOneFile = true;
1221
1222 if( pdfJob->m_plotLayerSequence.size() < 1 )
1223 {
1224 m_reporter->Report( _( "At least one layer must be specified\n" ), RPT_SEVERITY_ERROR );
1226 }
1227
1228 const bool outputIsSingle = plotAllLayersOneFile || pdfJob->m_pdfSingle;
1229
1230 if( outputIsSingle && pdfJob->GetConfiguredOutputPath().IsEmpty() )
1231 {
1232 wxFileName fn = brd->GetFileName();
1233 fn.SetName( fn.GetName() );
1235
1236 pdfJob->SetWorkingOutputPath( fn.GetFullName() );
1237 }
1238
1239 wxString outPath = resolveJobOutputPath( pdfJob, brd, &pdfJob->m_drawingSheet );
1240
1241 PCB_PLOT_PARAMS plotOpts;
1242 PCB_PLOTTER::PlotJobToPlotOpts( plotOpts, pdfJob, *m_reporter );
1243
1244 PCB_PLOTTER pcbPlotter( brd, m_reporter, plotOpts );
1245
1246 if( !PATHS::EnsurePathExists( outPath, outputIsSingle ) )
1247 {
1248 m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
1250 }
1251
1252 std::optional<wxString> layerName;
1253 std::optional<wxString> sheetName;
1254 std::optional<wxString> sheetPath;
1255
1256 if( plotAllLayersOneFile )
1257 {
1258 if( pdfJob->GetVarOverrides().contains( wxT( "LAYER" ) ) )
1259 layerName = pdfJob->GetVarOverrides().at( wxT( "LAYER" ) );
1260
1261 if( pdfJob->GetVarOverrides().contains( wxT( "SHEETNAME" ) ) )
1262 sheetName = pdfJob->GetVarOverrides().at( wxT( "SHEETNAME" ) );
1263
1264 if( pdfJob->GetVarOverrides().contains( wxT( "SHEETPATH" ) ) )
1265 sheetPath = pdfJob->GetVarOverrides().at( wxT( "SHEETPATH" ) );
1266 }
1267
1268 std::vector<wxString> outputPaths;
1269
1270 if( !pcbPlotter.Plot( outPath, pdfJob->m_plotLayerSequence, pdfJob->m_plotOnAllLayersSequence, false,
1271 outputIsSingle, layerName, sheetName, sheetPath, &outputPaths ) )
1272 {
1274 }
1275
1276 for( const wxString& outputPath : outputPaths )
1277 aJob->AddOutput( outputPath );
1278
1279 return CLI::EXIT_CODES::OK;
1280}
1281
1282
1284{
1285 JOB_EXPORT_PCB_PNG* pngJob = dynamic_cast<JOB_EXPORT_PCB_PNG*>( aJob );
1286
1287 if( pngJob == nullptr )
1289
1290 BOARD* brd = getBoard( pngJob->m_filename );
1291
1292 if( !brd )
1294
1295 if( !pngJob->m_variant.IsEmpty() )
1296 brd->SetCurrentVariant( pngJob->m_variant );
1297
1298 TOOL_MANAGER* toolManager = getToolManager( brd );
1299
1300 if( pngJob->m_checkZonesBeforePlot )
1301 {
1302 if( !toolManager->FindTool( ZONE_FILLER_TOOL_NAME ) )
1303 toolManager->RegisterTool( new ZONE_FILLER_TOOL );
1304
1305 toolManager->GetTool<ZONE_FILLER_TOOL>()->FillAllZones( nullptr, m_progressReporter, true );
1306 }
1307
1308 if( pngJob->m_argLayers )
1309 pngJob->m_plotLayerSequence = convertLayerArg( pngJob->m_argLayers.value(), brd );
1310
1311 if( pngJob->m_argCommonLayers )
1312 pngJob->m_plotOnAllLayersSequence = convertLayerArg( pngJob->m_argCommonLayers.value(), brd );
1313
1314 if( pngJob->m_plotLayerSequence.size() < 1 )
1315 {
1316 m_reporter->Report( _( "At least one layer must be specified\n" ), RPT_SEVERITY_ERROR );
1318 }
1319
1320 if( pngJob->GetConfiguredOutputPath().IsEmpty() )
1321 {
1322 wxFileName fn = brd->GetFileName();
1323 fn.SetName( fn.GetName() );
1325
1326 pngJob->SetWorkingOutputPath( fn.GetFullName() );
1327 }
1328
1329 wxString outPath = resolveJobOutputPath( pngJob, brd, &pngJob->m_drawingSheet );
1330
1331 PCB_PLOT_PARAMS plotOpts;
1332 PCB_PLOTTER::PlotJobToPlotOpts( plotOpts, pngJob, *m_reporter );
1333
1334 PCB_PLOTTER pcbPlotter( brd, m_reporter, plotOpts );
1335
1336 if( !PATHS::EnsurePathExists( outPath, false ) )
1337 {
1338 m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
1340 }
1341
1342 std::vector<wxString> outputPaths;
1343
1344 if( !pcbPlotter.Plot( outPath, pngJob->m_plotLayerSequence, pngJob->m_plotOnAllLayersSequence, false, false,
1345 std::nullopt, std::nullopt, std::nullopt, &outputPaths ) )
1346 {
1348 }
1349
1350 for( const wxString& outputPath : outputPaths )
1351 aJob->AddOutput( outputPath );
1352
1353 return CLI::EXIT_CODES::OK;
1354}
1355
1356
1358{
1359 JOB_EXPORT_PCB_PS* psJob = dynamic_cast<JOB_EXPORT_PCB_PS*>( aJob );
1360
1361 if( psJob == nullptr )
1363
1364 BOARD* brd = getBoard( psJob->m_filename );
1365
1366 if( !brd )
1368
1369 if( !psJob->m_variant.IsEmpty() )
1370 brd->SetCurrentVariant( psJob->m_variant );
1371
1372 TOOL_MANAGER* toolManager = getToolManager( brd );
1373
1374 if( psJob->m_checkZonesBeforePlot )
1375 {
1376 if( !toolManager->FindTool( ZONE_FILLER_TOOL_NAME ) )
1377 toolManager->RegisterTool( new ZONE_FILLER_TOOL );
1378
1379 toolManager->GetTool<ZONE_FILLER_TOOL>()->FillAllZones( nullptr, m_progressReporter, true );
1380 }
1381
1382 if( psJob->m_argLayers )
1383 psJob->m_plotLayerSequence = convertLayerArg( psJob->m_argLayers.value(), brd );
1384
1385 if( psJob->m_argCommonLayers )
1386 psJob->m_plotOnAllLayersSequence = convertLayerArg( psJob->m_argCommonLayers.value(), brd );
1387
1388 if( psJob->m_plotLayerSequence.size() < 1 )
1389 {
1390 m_reporter->Report( _( "At least one layer must be specified\n" ), RPT_SEVERITY_ERROR );
1392 }
1393
1394 bool isSingle = psJob->m_genMode == JOB_EXPORT_PCB_PS::GEN_MODE::SINGLE;
1395
1396 if( isSingle )
1397 {
1398 if( psJob->GetConfiguredOutputPath().IsEmpty() )
1399 {
1400 wxFileName fn = brd->GetFileName();
1401 fn.SetName( fn.GetName() );
1403
1404 psJob->SetWorkingOutputPath( fn.GetFullName() );
1405 }
1406 }
1407
1408 wxString outPath = resolveJobOutputPath( psJob, brd, &psJob->m_drawingSheet );
1409
1410 if( !PATHS::EnsurePathExists( outPath, isSingle ) )
1411 {
1412 m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
1414 }
1415
1416 PCB_PLOT_PARAMS plotOpts;
1417 PCB_PLOTTER::PlotJobToPlotOpts( plotOpts, psJob, *m_reporter );
1418
1419 PCB_PLOTTER pcbPlotter( brd, m_reporter, plotOpts );
1420
1421 std::optional<wxString> layerName;
1422 std::optional<wxString> sheetName;
1423 std::optional<wxString> sheetPath;
1424
1425 if( isSingle )
1426 {
1427 if( aJob->GetVarOverrides().contains( wxT( "LAYER" ) ) )
1428 layerName = psJob->GetVarOverrides().at( wxT( "LAYER" ) );
1429
1430 if( aJob->GetVarOverrides().contains( wxT( "SHEETNAME" ) ) )
1431 sheetName = psJob->GetVarOverrides().at( wxT( "SHEETNAME" ) );
1432
1433 if( aJob->GetVarOverrides().contains( wxT( "SHEETPATH" ) ) )
1434 sheetPath = psJob->GetVarOverrides().at( wxT( "SHEETPATH" ) );
1435 }
1436
1437 std::vector<wxString> outputPaths;
1438
1439 if( !pcbPlotter.Plot( outPath, psJob->m_plotLayerSequence, psJob->m_plotOnAllLayersSequence, false, isSingle,
1440 layerName, sheetName, sheetPath, &outputPaths ) )
1441 {
1443 }
1444
1445 for( const wxString& outputPath : outputPaths )
1446 aJob->AddOutput( outputPath );
1447
1448 return CLI::EXIT_CODES::OK;
1449}
1450
1451
1453{
1454 int exitCode = CLI::EXIT_CODES::OK;
1455 JOB_EXPORT_PCB_GERBERS* aGerberJob = dynamic_cast<JOB_EXPORT_PCB_GERBERS*>( aJob );
1456
1457 if( aGerberJob == nullptr )
1459
1460 BOARD* brd = getBoard( aGerberJob->m_filename );
1461
1462 if( !brd )
1464
1465 if( !aGerberJob->m_variant.IsEmpty() )
1466 brd->SetCurrentVariant( aGerberJob->m_variant );
1467
1468 wxString outPath = resolveJobOutputPath( aJob, brd, &aGerberJob->m_drawingSheet );
1469
1470 if( !PATHS::EnsurePathExists( outPath, false ) )
1471 {
1472 m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
1474 }
1475
1476 TOOL_MANAGER* toolManager = getToolManager( brd );
1477
1478 if( aGerberJob->m_checkZonesBeforePlot )
1479 {
1480 if( !toolManager->FindTool( ZONE_FILLER_TOOL_NAME ) )
1481 toolManager->RegisterTool( new ZONE_FILLER_TOOL );
1482
1483 toolManager->GetTool<ZONE_FILLER_TOOL>()->FillAllZones( nullptr, m_progressReporter, true );
1484 }
1485
1486 bool hasLayerListSpecified = false; // will be true if the user layer list is not empty
1487
1488 if( aGerberJob->m_argLayers )
1489 {
1490 if( !aGerberJob->m_argLayers.value().empty() )
1491 {
1492 aGerberJob->m_plotLayerSequence = convertLayerArg( aGerberJob->m_argLayers.value(), brd );
1493 hasLayerListSpecified = true;
1494 }
1495 else
1496 {
1498 }
1499 }
1500
1501 if( aGerberJob->m_argCommonLayers )
1502 aGerberJob->m_plotOnAllLayersSequence = convertLayerArg( aGerberJob->m_argCommonLayers.value(), brd );
1503
1504 PCB_PLOT_PARAMS boardPlotOptions = brd->GetPlotOptions();
1505 GERBER_JOBFILE_WRITER jobfile_writer( brd );
1506
1507 wxString fileExt;
1508
1509 if( aGerberJob->m_useBoardPlotParams )
1510 {
1511 // The board plot options are saved with all copper layers enabled, even those that don't
1512 // exist in the current stackup. This is done so the layers are automatically enabled in the plot
1513 // dialog when the user enables them. We need to filter out these not-enabled layers here so
1514 // we don't plot 32 layers when we only have 4, etc.
1515 LSET plotLayers = ( boardPlotOptions.GetLayerSelection() & LSET::AllNonCuMask() )
1516 | ( brd->GetEnabledLayers() & LSET::AllCuMask() );
1517 aGerberJob->m_plotLayerSequence = plotLayers.SeqStackupForPlotting();
1518 aGerberJob->m_plotOnAllLayersSequence = boardPlotOptions.GetPlotOnAllLayersSequence();
1519 }
1520 else
1521 {
1522 // default to the board enabled layers, but only if the user has not specifed a layer list
1523 // ( m_plotLayerSequence can be empty with a broken user layer list)
1524 if( aGerberJob->m_plotLayerSequence.empty() && !hasLayerListSpecified )
1526 }
1527
1528 // Ensure layers to plot are restricted to enabled layers of the board to plot
1529 LSET layersToPlot = LSET( { aGerberJob->m_plotLayerSequence } ) & brd->GetEnabledLayers();
1530
1531 for( PCB_LAYER_ID layer : layersToPlot.UIOrder() )
1532 {
1533 LSEQ plotSequence;
1534
1535 // Base layer always gets plotted first.
1536 plotSequence.push_back( layer );
1537
1538 // Now all the "include on all" layers
1539 for( PCB_LAYER_ID layer_all : aGerberJob->m_plotOnAllLayersSequence )
1540 {
1541 // Don't plot the same layer more than once;
1542 if( find( plotSequence.begin(), plotSequence.end(), layer_all ) != plotSequence.end() )
1543 continue;
1544
1545 plotSequence.push_back( layer_all );
1546 }
1547
1548 // Pick the basename from the board file
1549 wxFileName fn( brd->GetFileName() );
1550 wxString layerName = brd->GetLayerName( layer );
1551 wxString sheetName;
1552 wxString sheetPath;
1553 PCB_PLOT_PARAMS plotOpts;
1554
1555 if( aGerberJob->m_useBoardPlotParams )
1556 plotOpts = boardPlotOptions;
1557 else
1558 PCB_PLOTTER::PlotJobToPlotOpts( plotOpts, aGerberJob, *m_reporter );
1559
1560 if( plotOpts.GetUseGerberProtelExtensions() )
1561 fileExt = GetGerberProtelExtension( layer );
1562 else
1564
1565 PCB_PLOTTER::BuildPlotFileName( &fn, outPath, layerName, fileExt );
1566 wxString fullname = fn.GetFullName();
1567
1568 if( m_progressReporter )
1569 {
1570 m_progressReporter->AdvancePhase( wxString::Format( _( "Exporting %s" ), fullname ) );
1571 m_progressReporter->KeepRefreshing();
1572 }
1573
1574 jobfile_writer.AddGbrFile( layer, fullname );
1575
1576 if( aJob->GetVarOverrides().contains( wxT( "LAYER" ) ) )
1577 layerName = aJob->GetVarOverrides().at( wxT( "LAYER" ) );
1578
1579 if( aJob->GetVarOverrides().contains( wxT( "SHEETNAME" ) ) )
1580 sheetName = aJob->GetVarOverrides().at( wxT( "SHEETNAME" ) );
1581
1582 if( aJob->GetVarOverrides().contains( wxT( "SHEETPATH" ) ) )
1583 sheetPath = aJob->GetVarOverrides().at( wxT( "SHEETPATH" ) );
1584
1585 // We are feeding it one layer at the start here to silence a logic check
1586 GERBER_PLOTTER* plotter;
1587 plotter = (GERBER_PLOTTER*) StartPlotBoard( brd, &plotOpts, layer, layerName, fn.GetFullPath(), sheetName,
1588 sheetPath );
1589
1590 if( plotter )
1591 {
1592 m_reporter->Report( wxString::Format( _( "Plotted to '%s'.\n" ), fn.GetFullPath() ), RPT_SEVERITY_ACTION );
1593
1594 PlotBoardLayers( brd, plotter, plotSequence, plotOpts );
1595 plotter->EndPlot();
1596 aJob->AddOutput( fn.GetFullPath() );
1597 }
1598 else
1599 {
1600 m_reporter->Report( wxString::Format( _( "Failed to plot to '%s'.\n" ), fn.GetFullPath() ),
1603 }
1604
1605 delete plotter;
1606 }
1607
1608 if( aGerberJob->m_createJobsFile )
1609 {
1610 wxFileName fn( brd->GetFileName() );
1611
1612 // Build gerber job file from basename
1614 jobfile_writer.CreateJobFile( fn.GetFullPath() );
1615 aJob->AddOutput( fn.GetFullPath() );
1616 }
1617
1618 return exitCode;
1619}
1620
1621
1623{
1624 JOB_EXPORT_PCB_GENCAD* aGencadJob = dynamic_cast<JOB_EXPORT_PCB_GENCAD*>( aJob );
1625
1626 if( aGencadJob == nullptr )
1628
1629 BOARD* brd = getBoard( aGencadJob->m_filename );
1630
1631 if( !brd )
1633
1634 GENCAD_EXPORTER exporter( brd );
1635
1636 VECTOR2I GencadOffset;
1637 VECTOR2I auxOrigin = brd->GetDesignSettings().GetAuxOrigin();
1638 GencadOffset.x = aGencadJob->m_useDrillOrigin ? auxOrigin.x : 0;
1639 GencadOffset.y = aGencadJob->m_useDrillOrigin ? auxOrigin.y : 0;
1640
1641 exporter.FlipBottomPads( aGencadJob->m_flipBottomPads );
1642 exporter.UsePinNamesUnique( aGencadJob->m_useUniquePins );
1643 exporter.UseIndividualShapes( aGencadJob->m_useIndividualShapes );
1644 exporter.SetPlotOffet( GencadOffset );
1645 exporter.StoreOriginCoordsInFile( aGencadJob->m_storeOriginCoords );
1646
1647 if( aGencadJob->GetConfiguredOutputPath().IsEmpty() )
1648 {
1649 wxFileName fn = brd->GetFileName();
1650 fn.SetName( fn.GetName() );
1651 fn.SetExt( FILEEXT::GencadFileExtension );
1652
1653 aGencadJob->SetWorkingOutputPath( fn.GetFullName() );
1654 }
1655
1656 wxString outPath = resolveJobOutputPath( aJob, brd );
1657
1658 if( !PATHS::EnsurePathExists( outPath, true ) )
1659 {
1660 m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
1662 }
1663
1664 if( !exporter.WriteFile( outPath ) )
1665 {
1666 m_reporter->Report( wxString::Format( _( "Failed to create file '%s'.\n" ), outPath ), RPT_SEVERITY_ERROR );
1667
1669 }
1670
1671 aJob->AddOutput( outPath );
1672 m_reporter->Report( _( "Successfully created genCAD file\n" ), RPT_SEVERITY_INFO );
1673
1674 return CLI::EXIT_CODES::OK;
1675}
1676
1677
1679{
1680 JOB_EXPORT_PCB_STATS* statsJob = dynamic_cast<JOB_EXPORT_PCB_STATS*>( aJob );
1681
1682 if( statsJob == nullptr )
1684
1685 BOARD* brd = getBoard( statsJob->m_filename );
1686
1687 if( !brd )
1689
1692
1697
1698 ComputeBoardStatistics( brd, options, data );
1699
1700 wxString projectName;
1701
1702 if( brd->GetProject() )
1703 projectName = brd->GetProject()->GetProjectName();
1704
1705 wxFileName boardFile = brd->GetFileName();
1706
1707 if( boardFile.GetName().IsEmpty() )
1708 boardFile = wxFileName( statsJob->m_filename );
1709
1711 UNITS_PROVIDER unitsProvider( pcbIUScale, unitsForReport );
1712
1713 wxString report;
1714
1716 report = FormatBoardStatisticsJson( data, brd, unitsProvider, projectName, boardFile.GetName() );
1717 else
1718 report = FormatBoardStatisticsReport( data, brd, unitsProvider, projectName, boardFile.GetName() );
1719
1720 if( statsJob->GetConfiguredOutputPath().IsEmpty() && statsJob->GetWorkingOutputPath().IsEmpty() )
1721 statsJob->SetDefaultOutputPath( boardFile.GetFullPath() );
1722
1723 wxString outPath = resolveJobOutputPath( aJob, brd );
1724
1725 if( !PATHS::EnsurePathExists( outPath, true ) )
1726 {
1727 m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
1729 }
1730
1731 FILE* outFile = wxFopen( outPath, wxS( "wt" ) );
1732
1733 if( !outFile )
1734 {
1735 m_reporter->Report( wxString::Format( _( "Failed to create file '%s'.\n" ), outPath ), RPT_SEVERITY_ERROR );
1737 }
1738
1739 if( fprintf( outFile, "%s", TO_UTF8( report ) ) < 0 )
1740 {
1741 fclose( outFile );
1742 m_reporter->Report( wxString::Format( _( "Error writing file '%s'.\n" ), outPath ), RPT_SEVERITY_ERROR );
1744 }
1745
1746 fclose( outFile );
1747
1748 m_reporter->Report( wxString::Format( _( "Wrote board statistics to '%s'.\n" ), outPath ), RPT_SEVERITY_ACTION );
1749
1750 statsJob->AddOutput( outPath );
1751
1752 return CLI::EXIT_CODES::OK;
1753}
1754
1755
1757{
1758 int exitCode = CLI::EXIT_CODES::OK;
1759 JOB_EXPORT_PCB_GERBER* aGerberJob = dynamic_cast<JOB_EXPORT_PCB_GERBER*>( aJob );
1760
1761 if( aGerberJob == nullptr )
1763
1764 BOARD* brd = getBoard( aGerberJob->m_filename );
1765
1766 if( !brd )
1768
1769 if( !aGerberJob->m_variant.IsEmpty() )
1770 brd->SetCurrentVariant( aGerberJob->m_variant );
1771
1772 TOOL_MANAGER* toolManager = getToolManager( brd );
1773
1774 if( aGerberJob->m_argLayers )
1775 aGerberJob->m_plotLayerSequence = convertLayerArg( aGerberJob->m_argLayers.value(), brd );
1776
1777 if( aGerberJob->m_argCommonLayers )
1778 aGerberJob->m_plotOnAllLayersSequence = convertLayerArg( aGerberJob->m_argCommonLayers.value(), brd );
1779
1780 if( aGerberJob->m_plotLayerSequence.size() < 1 )
1781 {
1782 m_reporter->Report( _( "At least one layer must be specified\n" ), RPT_SEVERITY_ERROR );
1784 }
1785
1786 if( aGerberJob->GetConfiguredOutputPath().IsEmpty() )
1787 {
1788 wxFileName fn = brd->GetFileName();
1789 fn.SetName( fn.GetName() );
1791
1792 aGerberJob->SetWorkingOutputPath( fn.GetFullName() );
1793 }
1794
1795 wxString outPath = resolveJobOutputPath( aJob, brd );
1796
1797 if( aGerberJob->m_checkZonesBeforePlot )
1798 {
1799 if( !toolManager->FindTool( ZONE_FILLER_TOOL_NAME ) )
1800 toolManager->RegisterTool( new ZONE_FILLER_TOOL );
1801
1802 toolManager->GetTool<ZONE_FILLER_TOOL>()->FillAllZones( nullptr, m_progressReporter, true );
1803 }
1804
1805 PCB_PLOT_PARAMS plotOpts;
1806 PCB_PLOTTER::PlotJobToPlotOpts( plotOpts, aGerberJob, *m_reporter );
1807 plotOpts.SetLayerSelection( aGerberJob->m_plotLayerSequence );
1809
1811 wxString layerName;
1812 wxString sheetName;
1813 wxString sheetPath;
1814
1815 // The first layer will be treated as the layer name for the gerber header,
1816 // the other layers will be treated equivalent to the "Plot on All Layers" option
1817 // in the GUI
1818 if( aGerberJob->m_plotLayerSequence.size() >= 1 )
1819 {
1820 layer = aGerberJob->m_plotLayerSequence.front();
1821 layerName = brd->GetLayerName( layer );
1822 }
1823
1824 if( aJob->GetVarOverrides().contains( wxT( "LAYER" ) ) )
1825 layerName = aJob->GetVarOverrides().at( wxT( "LAYER" ) );
1826
1827 if( aJob->GetVarOverrides().contains( wxT( "SHEETNAME" ) ) )
1828 sheetName = aJob->GetVarOverrides().at( wxT( "SHEETNAME" ) );
1829
1830 if( aJob->GetVarOverrides().contains( wxT( "SHEETPATH" ) ) )
1831 sheetPath = aJob->GetVarOverrides().at( wxT( "SHEETPATH" ) );
1832
1833 // We are feeding it one layer at the start here to silence a logic check
1834 PLOTTER* plotter = StartPlotBoard( brd, &plotOpts, layer, layerName, outPath, sheetName, sheetPath );
1835
1836 if( plotter )
1837 {
1838 PlotBoardLayers( brd, plotter, aGerberJob->m_plotLayerSequence, plotOpts );
1839 plotter->EndPlot();
1840 }
1841 else
1842 {
1843 m_reporter->Report( wxString::Format( _( "Failed to plot to '%s'.\n" ), outPath ), RPT_SEVERITY_ERROR );
1845 }
1846
1847 delete plotter;
1848
1849 return exitCode;
1850}
1851
1854
1855
1857{
1858 JOB_EXPORT_PCB_DRILL* aDrillJob = dynamic_cast<JOB_EXPORT_PCB_DRILL*>( aJob );
1859
1860 if( aDrillJob == nullptr )
1862
1863 BOARD* brd = getBoard( aDrillJob->m_filename );
1864
1865 if( !brd )
1867
1868 wxString outPath = resolveJobOutputPath( aJob, brd );
1869
1870 if( !PATHS::EnsurePathExists( outPath ) )
1871 {
1872 m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
1874 }
1875
1876 std::unique_ptr<GENDRILL_WRITER_BASE> drillWriter;
1877
1879 drillWriter = std::make_unique<EXCELLON_WRITER>( brd );
1880 else
1881 drillWriter = std::make_unique<GERBER_WRITER>( brd );
1882
1883 VECTOR2I offset;
1884
1886 offset = VECTOR2I( 0, 0 );
1887 else
1888 offset = brd->GetDesignSettings().GetAuxOrigin();
1889
1890 PLOT_FORMAT mapFormat = PLOT_FORMAT::PDF;
1891
1892 switch( aDrillJob->m_mapFormat )
1893 {
1898 default:
1900 }
1901
1902
1903 if( aDrillJob->m_generateReport && aDrillJob->m_reportPath.IsEmpty() )
1904 {
1905 wxFileName fn = outPath;
1906 fn.SetFullName( brd->GetFileName() );
1907 fn.SetName( fn.GetName() + "-drill" );
1908 fn.SetExt( FILEEXT::ReportFileExtension );
1909
1910 aDrillJob->m_reportPath = fn.GetFullPath();
1911 }
1912
1914 {
1916
1917 switch( aDrillJob->m_zeroFormat )
1918 {
1920
1922
1924
1926 default: zeroFmt = EXCELLON_WRITER::DECIMAL_FORMAT; break;
1927 }
1928
1929 DRILL_PRECISION precision;
1930
1932 precision = precisionListForInches;
1933 else
1934 precision = precisionListForMetric;
1935
1936 EXCELLON_WRITER* excellonWriter = dynamic_cast<EXCELLON_WRITER*>( drillWriter.get() );
1937
1938 if( excellonWriter == nullptr )
1940
1941 excellonWriter->SetFormat( aDrillJob->m_drillUnits == JOB_EXPORT_PCB_DRILL::DRILL_UNITS::MM, zeroFmt,
1942 precision.m_Lhs, precision.m_Rhs );
1943 excellonWriter->SetOptions( aDrillJob->m_excellonMirrorY, aDrillJob->m_excellonMinimalHeader, offset,
1944 aDrillJob->m_excellonCombinePTHNPTH );
1945 excellonWriter->SetRouteModeForOvalHoles( aDrillJob->m_excellonOvalDrillRoute );
1946 excellonWriter->SetMapFileFormat( mapFormat );
1947
1948 if( !excellonWriter->CreateDrillandMapFilesSet( outPath, true, aDrillJob->m_generateMap, m_reporter ) )
1949 {
1951 }
1952
1953 aDrillJob->AddOutput( outPath );
1954
1955 if( aDrillJob->m_generateReport )
1956 {
1957 wxString reportPath = aDrillJob->ResolveOutputPath( aDrillJob->m_reportPath, true, brd->GetProject() );
1958
1959 if( !excellonWriter->GenDrillReportFile( reportPath ) )
1960 {
1962 }
1963
1964 aDrillJob->AddOutput( reportPath );
1965 }
1966 }
1968 {
1969 GERBER_WRITER* gerberWriter = dynamic_cast<GERBER_WRITER*>( drillWriter.get() );
1970
1971 if( gerberWriter == nullptr )
1973
1974 // Set gerber precision: only 5 or 6 digits for mantissa are allowed
1975 // (SetFormat() accept 5 or 6, and any other value set the precision to 5)
1976 // the integer part precision is always 4, and units always mm
1977 gerberWriter->SetFormat( aDrillJob->m_gerberPrecision );
1978 gerberWriter->SetOptions( offset );
1979 gerberWriter->SetMapFileFormat( mapFormat );
1980
1981 if( !gerberWriter->CreateDrillandMapFilesSet( outPath, true, aDrillJob->m_generateMap,
1982 aDrillJob->m_generateTenting, m_reporter ) )
1983 {
1985 }
1986
1987 aDrillJob->AddOutput( outPath );
1988
1989 if( aDrillJob->m_generateReport )
1990 {
1991 wxString reportPath = aDrillJob->ResolveOutputPath( aDrillJob->m_reportPath, true, brd->GetProject() );
1992
1993 if( !gerberWriter->GenDrillReportFile( reportPath ) )
1994 {
1996 }
1997
1998 aDrillJob->AddOutput( reportPath );
1999 }
2000 }
2001
2002 return CLI::EXIT_CODES::OK;
2003}
2004
2005
2007{
2008 JOB_EXPORT_PCB_POS* aPosJob = dynamic_cast<JOB_EXPORT_PCB_POS*>( aJob );
2009
2010 if( aPosJob == nullptr )
2012
2013 BOARD* brd = getBoard( aPosJob->m_filename );
2014
2015 if( !brd )
2017
2018 if( aPosJob->GetConfiguredOutputPath().IsEmpty() )
2019 {
2020 wxFileName fn = brd->GetFileName();
2021 fn.SetName( fn.GetName() );
2022
2025 else if( aPosJob->m_format == JOB_EXPORT_PCB_POS::FORMAT::CSV )
2026 fn.SetExt( FILEEXT::CsvFileExtension );
2027 else if( aPosJob->m_format == JOB_EXPORT_PCB_POS::FORMAT::GERBER )
2028 fn.SetExt( FILEEXT::GerberFileExtension );
2029
2030 aPosJob->SetWorkingOutputPath( fn.GetFullName() );
2031 }
2032
2033 wxString outPath = resolveJobOutputPath( aJob, brd );
2034
2035 if( !PATHS::EnsurePathExists( outPath, true ) )
2036 {
2037 m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
2039 }
2040
2042 {
2043 wxFileName fn( outPath );
2044 wxString baseName = fn.GetName();
2045
2046 auto exportPlaceFile = [&]( bool frontSide, bool backSide, const wxString& curr_outPath ) -> bool
2047 {
2048 FILE* file = wxFopen( curr_outPath, wxS( "wt" ) );
2049 wxCHECK( file, false );
2050
2051 PLACE_FILE_EXPORTER exporter( brd, aPosJob->m_units == JOB_EXPORT_PCB_POS::UNITS::MM, aPosJob->m_smdOnly,
2052 aPosJob->m_excludeFootprintsWithTh, aPosJob->m_excludeDNP,
2053 aPosJob->m_excludeBOM, frontSide, backSide,
2055 aPosJob->m_useDrillPlaceFileOrigin, aPosJob->m_negateBottomX );
2056
2057 // Set variant for variant-aware DNP/BOM/position file filtering
2058 exporter.SetVariant( aPosJob->m_variant );
2059
2060 std::string data = exporter.GenPositionData();
2061 fputs( data.c_str(), file );
2062 fclose( file );
2063
2064 return true;
2065 };
2066
2067 if( aPosJob->m_side == JOB_EXPORT_PCB_POS::SIDE::BOTH && !aPosJob->m_singleFile )
2068 {
2069 fn.SetName( PLACE_FILE_EXPORTER::DecorateFilename( baseName, true, false ) );
2070
2071 if( aPosJob->m_format == JOB_EXPORT_PCB_POS::FORMAT::CSV && !aPosJob->m_nakedFilename )
2072 fn.SetName( fn.GetName() + wxT( "-" ) + FILEEXT::FootprintPlaceFileExtension );
2073
2074 if( exportPlaceFile( true, false, fn.GetFullPath() ) )
2075 {
2076 m_reporter->Report( wxString::Format( _( "Wrote front position data to '%s'.\n" ), fn.GetFullPath() ),
2078
2079 aPosJob->AddOutput( fn.GetFullPath() );
2080 }
2081 else
2082 {
2084 }
2085
2086 fn.SetName( PLACE_FILE_EXPORTER::DecorateFilename( baseName, false, true ) );
2087
2088 if( aPosJob->m_format == JOB_EXPORT_PCB_POS::FORMAT::CSV && !aPosJob->m_nakedFilename )
2089 fn.SetName( fn.GetName() + wxT( "-" ) + FILEEXT::FootprintPlaceFileExtension );
2090
2091 if( exportPlaceFile( false, true, fn.GetFullPath() ) )
2092 {
2093 m_reporter->Report( wxString::Format( _( "Wrote back position data to '%s'.\n" ), fn.GetFullPath() ),
2095
2096 aPosJob->AddOutput( fn.GetFullPath() );
2097 }
2098 else
2099 {
2101 }
2102 }
2103 else
2104 {
2105 bool front = aPosJob->m_side == JOB_EXPORT_PCB_POS::SIDE::FRONT
2107
2108 bool back = aPosJob->m_side == JOB_EXPORT_PCB_POS::SIDE::BACK
2110
2111 if( !aPosJob->m_nakedFilename )
2112 {
2113 fn.SetName( PLACE_FILE_EXPORTER::DecorateFilename( fn.GetName(), front, back ) );
2114
2116 fn.SetName( fn.GetName() + wxT( "-" ) + FILEEXT::FootprintPlaceFileExtension );
2117 }
2118
2119 if( exportPlaceFile( front, back, fn.GetFullPath() ) )
2120 {
2121 m_reporter->Report( wxString::Format( _( "Wrote position data to '%s'.\n" ), fn.GetFullPath() ),
2123
2124 aPosJob->AddOutput( fn.GetFullPath() );
2125 }
2126 else
2127 {
2129 }
2130 }
2131 }
2132 else if( aPosJob->m_format == JOB_EXPORT_PCB_POS::FORMAT::GERBER )
2133 {
2134 PLACEFILE_GERBER_WRITER exporter( brd );
2135
2136 // Set variant for variant-aware DNP/BOM/position file filtering
2137 exporter.SetVariant( aPosJob->m_variant );
2138
2139 PCB_LAYER_ID gbrLayer = F_Cu;
2140 wxString outPath_base = outPath;
2141
2143 {
2144 if( aPosJob->m_side == JOB_EXPORT_PCB_POS::SIDE::BOTH || !aPosJob->m_nakedFilename )
2145 outPath = exporter.GetPlaceFileName( outPath, gbrLayer );
2146
2147 if( exporter.CreatePlaceFile( outPath, gbrLayer, aPosJob->m_gerberBoardEdge, aPosJob->m_excludeDNP,
2148 aPosJob->m_excludeBOM )
2149 >= 0 )
2150 {
2151 m_reporter->Report( wxString::Format( _( "Wrote front position data to '%s'.\n" ), outPath ),
2153
2154 aPosJob->AddOutput( outPath );
2155 }
2156 else
2157 {
2159 }
2160 }
2161
2163 {
2164 gbrLayer = B_Cu;
2165
2166 outPath = outPath_base;
2167
2168 if( aPosJob->m_side == JOB_EXPORT_PCB_POS::SIDE::BOTH || !aPosJob->m_nakedFilename )
2169 outPath = exporter.GetPlaceFileName( outPath, gbrLayer );
2170
2171 if( exporter.CreatePlaceFile( outPath, gbrLayer, aPosJob->m_gerberBoardEdge, aPosJob->m_excludeDNP,
2172 aPosJob->m_excludeBOM )
2173 >= 0 )
2174 {
2175 m_reporter->Report( wxString::Format( _( "Wrote back position data to '%s'.\n" ), outPath ),
2177
2178 aPosJob->AddOutput( outPath );
2179 }
2180 else
2181 {
2183 }
2184 }
2185 }
2186
2187 return CLI::EXIT_CODES::OK;
2188}
2189
2190
2192{
2193 JOB_FP_UPGRADE* upgradeJob = dynamic_cast<JOB_FP_UPGRADE*>( aJob );
2194
2195 if( upgradeJob == nullptr )
2197
2199
2200 if( !upgradeJob->m_outputLibraryPath.IsEmpty() )
2201 {
2202 if( wxFile::Exists( upgradeJob->m_outputLibraryPath ) || wxDir::Exists( upgradeJob->m_outputLibraryPath ) )
2203 {
2204 m_reporter->Report( _( "Output path must not conflict with existing path\n" ), RPT_SEVERITY_ERROR );
2206 }
2207 }
2208 else if( fileType != PCB_IO_MGR::KICAD_SEXP )
2209 {
2210 m_reporter->Report( _( "Output path must be specified to convert legacy and non-KiCad libraries\n" ),
2212
2214 }
2215
2217 {
2218 if( !wxDir::Exists( upgradeJob->m_libraryPath ) )
2219 {
2220 m_reporter->Report( _( "Footprint library path does not exist or is not accessible\n" ),
2223 }
2224
2226 FP_CACHE fpLib( &pcb_io, upgradeJob->m_libraryPath );
2227
2228 try
2229 {
2230 fpLib.Load();
2231 }
2232 catch( ... )
2233 {
2234 m_reporter->Report( _( "Unable to load library\n" ), RPT_SEVERITY_ERROR );
2236 }
2237
2238 if( m_progressReporter )
2239 m_progressReporter->KeepRefreshing();
2240
2241 bool shouldSave = upgradeJob->m_force;
2242
2243 for( const auto& footprint : fpLib.GetFootprints() )
2244 {
2245 if( footprint.second->GetFootprint()->GetFileFormatVersionAtLoad() < SEXPR_BOARD_FILE_VERSION )
2246 shouldSave = true;
2247 }
2248
2249 if( shouldSave )
2250 {
2251 try
2252 {
2253 if( !upgradeJob->m_outputLibraryPath.IsEmpty() )
2254 fpLib.SetPath( upgradeJob->m_outputLibraryPath );
2255
2256 fpLib.Save();
2257 }
2258 catch( ... )
2259 {
2260 m_reporter->Report( _( "Unable to save library\n" ), RPT_SEVERITY_ERROR );
2262 }
2263 }
2264 else
2265 {
2266 m_reporter->Report( _( "Footprint library was not updated\n" ), RPT_SEVERITY_ERROR );
2267 }
2268 }
2269 else
2270 {
2271 if( !PCB_IO_MGR::ConvertLibrary( {}, upgradeJob->m_libraryPath, upgradeJob->m_outputLibraryPath,
2272 nullptr /* REPORTER */ ) )
2273 {
2274 m_reporter->Report( ( "Unable to convert library\n" ), RPT_SEVERITY_ERROR );
2276 }
2277 }
2278
2279 return CLI::EXIT_CODES::OK;
2280}
2281
2282
2284{
2285 JOB_FP_EXPORT_SVG* svgJob = dynamic_cast<JOB_FP_EXPORT_SVG*>( aJob );
2286
2287 if( svgJob == nullptr )
2289
2291 FP_CACHE fpLib( &pcb_io, svgJob->m_libraryPath );
2292
2293 if( svgJob->m_argLayers )
2294 {
2295 if( !svgJob->m_argLayers.value().empty() )
2296 svgJob->m_plotLayerSequence = convertLayerArg( svgJob->m_argLayers.value(), nullptr );
2297 else
2299 }
2300
2301 try
2302 {
2303 fpLib.Load();
2304 }
2305 catch( ... )
2306 {
2307 m_reporter->Report( _( "Unable to load library\n" ), RPT_SEVERITY_ERROR );
2309 }
2310
2311 wxString outPath = svgJob->GetFullOutputPath( nullptr );
2312
2313 if( !PATHS::EnsurePathExists( outPath, true ) )
2314 {
2315 m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
2317 }
2318
2319 int exitCode = CLI::EXIT_CODES::OK;
2320 bool singleFpPlotted = false;
2321
2322 for( const auto& [fpName, fpCacheEntry] : fpLib.GetFootprints() )
2323 {
2324 if( m_progressReporter )
2325 {
2326 m_progressReporter->AdvancePhase( wxString::Format( _( "Exporting %s" ), fpName ) );
2327 m_progressReporter->KeepRefreshing();
2328 }
2329
2330 if( !svgJob->m_footprint.IsEmpty() )
2331 {
2332 // skip until we find the right footprint
2333 if( fpName != svgJob->m_footprint )
2334 continue;
2335 else
2336 singleFpPlotted = true;
2337 }
2338
2339 exitCode = doFpExportSvg( svgJob, fpCacheEntry->GetFootprint().get() );
2340
2341 if( exitCode != CLI::EXIT_CODES::OK )
2342 break;
2343 }
2344
2345 if( !svgJob->m_footprint.IsEmpty() && !singleFpPlotted )
2346 {
2347 m_reporter->Report( _( "The given footprint could not be found to export." ) + wxS( "\n" ),
2349 }
2350
2351 return CLI::EXIT_CODES::OK;
2352}
2353
2354
2356{
2357 // the hack for now is we create fake boards containing the footprint and plot the board
2358 // until we refactor better plot api later
2359 std::unique_ptr<BOARD> brd = BOARD_LOADER::CreateEmptyBoard( Pgm().GetSettingsManager().GetProject( "" ) );
2360 brd->GetProject()->ApplyTextVars( aSvgJob->GetVarOverrides() );
2361 brd->SynchronizeProperties();
2362
2363 FOOTPRINT* fp = dynamic_cast<FOOTPRINT*>( aFootprint->Clone() );
2364
2365 if( fp == nullptr )
2367
2368 fp->SetLink( niluuid );
2369 fp->SetFlags( IS_NEW );
2370 fp->SetParent( brd.get() );
2371
2372 for( PAD* pad : fp->Pads() )
2373 {
2374 pad->SetLocalRatsnestVisible( false );
2375 pad->SetNetCode( 0 );
2376 }
2377
2378 fp->SetOrientation( ANGLE_0 );
2379 fp->SetPosition( VECTOR2I( 0, 0 ) );
2380
2381 brd->Add( fp, ADD_MODE::INSERT, true );
2382
2383 wxFileName outputFile;
2384 outputFile.SetPath( aSvgJob->GetFullOutputPath( nullptr ) );
2385 outputFile.SetName( aFootprint->GetFPID().GetLibItemName().wx_str() );
2386 outputFile.SetExt( FILEEXT::SVGFileExtension );
2387
2388 m_reporter->Report( wxString::Format( _( "Plotting footprint '%s' to '%s'\n" ),
2389 aFootprint->GetFPID().GetLibItemName().wx_str(), outputFile.GetFullPath() ),
2391
2392 PCB_PLOT_PARAMS plotOpts;
2393 PCB_PLOTTER::PlotJobToPlotOpts( plotOpts, aSvgJob, *m_reporter );
2394
2395 // always fixed for the svg plot
2396 plotOpts.SetPlotFrameRef( false );
2397 plotOpts.SetSvgFitPageToBoard( true );
2398 plotOpts.SetMirror( false );
2399 plotOpts.SetSkipPlotNPTH_Pads( false );
2400
2401 if( plotOpts.GetSketchPadsOnFabLayers() )
2402 {
2403 plotOpts.SetPlotPadNumbers( true );
2404 }
2405
2406 PCB_PLOTTER plotter( brd.get(), m_reporter, plotOpts );
2407
2408 if( !plotter.Plot( outputFile.GetFullPath(), aSvgJob->m_plotLayerSequence, aSvgJob->m_plotOnAllLayersSequence,
2409 false, true, wxEmptyString, wxEmptyString, wxEmptyString ) )
2410 {
2411 m_reporter->Report( _( "Error creating svg file" ) + wxS( "\n" ), RPT_SEVERITY_ERROR );
2413 }
2414
2415 aSvgJob->AddOutput( outputFile.GetFullPath() );
2416
2417 return CLI::EXIT_CODES::OK;
2418}
2419
2420
2422{
2423 JOB_PCB_DRC* drcJob = dynamic_cast<JOB_PCB_DRC*>( aJob );
2424
2425 if( drcJob == nullptr )
2427
2428 BOARD* brd = getBoard( drcJob->m_filename );
2429
2430 if( !brd )
2432
2433 // Running DRC requires libraries be loaded, so make sure they have been
2435 adapter->AsyncLoad();
2436 adapter->BlockUntilLoaded();
2437
2438 if( drcJob->GetConfiguredOutputPath().IsEmpty() )
2439 {
2440 wxFileName fn = brd->GetFileName();
2441 fn.SetName( fn.GetName() + wxS( "-drc" ) );
2442
2444 fn.SetExt( FILEEXT::JsonFileExtension );
2445 else
2446 fn.SetExt( FILEEXT::ReportFileExtension );
2447
2448 drcJob->SetWorkingOutputPath( fn.GetFullName() );
2449 }
2450
2451 wxString outPath = resolveJobOutputPath( aJob, brd );
2452
2453 if( !PATHS::EnsurePathExists( outPath, true ) )
2454 {
2455 m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
2457 }
2458
2459 EDA_UNITS units;
2460
2461 switch( drcJob->m_units )
2462 {
2463 case JOB_PCB_DRC::UNITS::INCH: units = EDA_UNITS::INCH; break;
2464 case JOB_PCB_DRC::UNITS::MILS: units = EDA_UNITS::MILS; break;
2465 case JOB_PCB_DRC::UNITS::MM: units = EDA_UNITS::MM; break;
2466 default: units = EDA_UNITS::MM; break;
2467 }
2468
2469 std::shared_ptr<DRC_ENGINE> drcEngine = brd->GetDesignSettings().m_DRCEngine;
2470 std::unique_ptr<NETLIST> netlist = std::make_unique<NETLIST>();
2471
2472 drcEngine->SetDrawingSheet( getDrawingSheetProxyView( brd ) );
2473
2474 // BOARD_COMMIT uses TOOL_MANAGER to grab the board internally so we must give it one
2475 TOOL_MANAGER* toolManager = getToolManager( brd );
2476
2477 BOARD_COMMIT commit( toolManager );
2478 bool checkParity = drcJob->m_parity;
2479 std::string netlist_str;
2480
2481 if( checkParity )
2482 {
2483 wxString annotateMsg = _( "Schematic parity tests require a fully annotated schematic." );
2484 netlist_str = annotateMsg;
2485
2486 // The KIFACE_NETLIST_SCHEMATIC function has some broken-ness that the schematic
2487 // frame's version does not, but it is the only one that works in CLI, so we use it
2488 // if we don't have the sch frame open.
2489 // TODO: clean this up, see https://gitlab.com/kicad/code/kicad/-/issues/19929
2490 if( m_kiway->Player( FRAME_SCH, false ) )
2491 {
2492 m_kiway->ExpressMail( FRAME_SCH, MAIL_SCH_GET_NETLIST, netlist_str );
2493 }
2494 else
2495 {
2496 wxFileName schematicPath( drcJob->m_filename );
2497 schematicPath.MakeAbsolute();
2498 schematicPath.SetExt( FILEEXT::KiCadSchematicFileExtension );
2499
2500 if( !schematicPath.Exists() )
2501 schematicPath.SetExt( FILEEXT::LegacySchematicFileExtension );
2502
2503 if( !schematicPath.Exists() )
2504 {
2505 m_reporter->Report( _( "Failed to fetch schematic netlist for parity tests.\n" ), RPT_SEVERITY_ERROR );
2506 checkParity = false;
2507 }
2508 else
2509 {
2510 typedef bool ( *NETLIST_FN_PTR )( const wxString&, std::string& );
2511 KIFACE* eeschema = m_kiway->KiFACE( KIWAY::FACE_SCH );
2512 NETLIST_FN_PTR netlister = (NETLIST_FN_PTR) eeschema->IfaceOrAddress( KIFACE_NETLIST_SCHEMATIC );
2513 ( *netlister )( schematicPath.GetFullPath(), netlist_str );
2514 }
2515 }
2516
2517 if( netlist_str == annotateMsg )
2518 {
2519 m_reporter->Report( wxString( netlist_str ) + wxT( "\n" ), RPT_SEVERITY_ERROR );
2520 checkParity = false;
2521 }
2522 }
2523
2524 if( checkParity )
2525 {
2526 try
2527 {
2528 STRING_LINE_READER* lineReader = new STRING_LINE_READER( netlist_str, _( "Eeschema netlist" ) );
2529 KICAD_NETLIST_READER netlistReader( lineReader, netlist.get() );
2530
2531 netlistReader.LoadNetlist();
2532 }
2533 catch( const IO_ERROR& )
2534 {
2535 m_reporter->Report( _( "Failed to fetch schematic netlist for parity tests.\n" ), RPT_SEVERITY_ERROR );
2536 checkParity = false;
2537 }
2538
2539 drcEngine->SetSchematicNetlist( netlist.get() );
2540 }
2541
2542 if( drcJob->m_refillZones )
2543 {
2544 if( !toolManager->FindTool( ZONE_FILLER_TOOL_NAME ) )
2545 toolManager->RegisterTool( new ZONE_FILLER_TOOL );
2546
2547 toolManager->GetTool<ZONE_FILLER_TOOL>()->FillAllZones( nullptr, m_progressReporter, true );
2548 }
2549
2550 drcEngine->SetProgressReporter( m_progressReporter );
2551 drcEngine->SetViolationHandler(
2552 [&]( const std::shared_ptr<DRC_ITEM>& aItem, const VECTOR2I& aPos, int aLayer,
2553 const std::function<void( PCB_MARKER* )>& aPathGenerator )
2554 {
2555 PCB_MARKER* marker = new PCB_MARKER( aItem, aPos, aLayer );
2556 aPathGenerator( marker );
2557 commit.Add( marker );
2558 } );
2559
2560 brd->RecordDRCExclusions();
2561 brd->DeleteMARKERs( true, true );
2562 drcEngine->RunTests( units, drcJob->m_reportAllTrackErrors, checkParity );
2563 drcEngine->ClearViolationHandler();
2564
2565 commit.Push( _( "DRC" ), SKIP_UNDO | SKIP_SET_DIRTY );
2566
2567 // Update the exclusion status on any excluded markers that still exist.
2568 brd->ResolveDRCExclusions( false );
2569
2570 std::shared_ptr<DRC_ITEMS_PROVIDER> markersProvider =
2571 std::make_shared<DRC_ITEMS_PROVIDER>( brd, MARKER_BASE::MARKER_DRC, MARKER_BASE::MARKER_DRAWING_SHEET );
2572
2573 std::shared_ptr<DRC_ITEMS_PROVIDER> ratsnestProvider =
2574 std::make_shared<DRC_ITEMS_PROVIDER>( brd, MARKER_BASE::MARKER_RATSNEST );
2575
2576 std::shared_ptr<DRC_ITEMS_PROVIDER> fpWarningsProvider =
2577 std::make_shared<DRC_ITEMS_PROVIDER>( brd, MARKER_BASE::MARKER_PARITY );
2578
2579 markersProvider->SetSeverities( drcJob->m_severity );
2580 ratsnestProvider->SetSeverities( drcJob->m_severity );
2581 fpWarningsProvider->SetSeverities( drcJob->m_severity );
2582
2583 m_reporter->Report( wxString::Format( _( "Found %d violations\n" ), markersProvider->GetCount() ),
2585 m_reporter->Report( wxString::Format( _( "Found %d unconnected items\n" ), ratsnestProvider->GetCount() ),
2587
2588 if( checkParity )
2589 {
2590 m_reporter->Report(
2591 wxString::Format( _( "Found %d schematic parity issues\n" ), fpWarningsProvider->GetCount() ),
2593 }
2594
2595 DRC_REPORT reportWriter( brd, units, markersProvider, ratsnestProvider, fpWarningsProvider );
2596
2597 bool wroteReport = false;
2598
2600 wroteReport = reportWriter.WriteJsonReport( outPath );
2601 else
2602 wroteReport = reportWriter.WriteTextReport( outPath );
2603
2604 if( !wroteReport )
2605 {
2606 m_reporter->Report( wxString::Format( _( "Unable to save DRC report to %s\n" ), outPath ), RPT_SEVERITY_ERROR );
2608 }
2609
2610 drcJob->AddOutput( outPath );
2611
2612 m_reporter->Report( wxString::Format( _( "Saved DRC Report to %s\n" ), outPath ), RPT_SEVERITY_ACTION );
2613
2614 if( drcJob->m_refillZones && drcJob->m_saveBoard )
2615 {
2616 if( BOARD_LOADER::SaveBoard( drcJob->m_filename, brd ) )
2617 {
2618 m_reporter->Report( _( "Saved board\n" ), RPT_SEVERITY_ACTION );
2619 }
2620 else
2621 {
2622 m_reporter->Report( _( "Failed to save board.\n" ), RPT_SEVERITY_ERROR );
2623
2625 }
2626 }
2627
2628 if( drcJob->m_exitCodeViolations )
2629 {
2630 if( markersProvider->GetCount() > 0 || ratsnestProvider->GetCount() > 0 || fpWarningsProvider->GetCount() > 0 )
2631 {
2633 }
2634 }
2635
2637}
2638
2639
2641{
2642 JOB_EXPORT_PCB_IPC2581* job = dynamic_cast<JOB_EXPORT_PCB_IPC2581*>( aJob );
2643
2644 if( job == nullptr )
2646
2647 BOARD* brd = getBoard( job->m_filename );
2648
2649 if( !brd )
2651
2652 if( !job->m_variant.IsEmpty() )
2653 brd->SetCurrentVariant( job->m_variant );
2654
2655 if( job->GetConfiguredOutputPath().IsEmpty() )
2656 {
2657 wxFileName fn = brd->GetFileName();
2658 fn.SetExt( job->m_compress ? std::string( "zip" ) : FILEEXT::Ipc2581FileExtension );
2659
2660 job->SetWorkingOutputPath( fn.GetFullName() );
2661 }
2662
2663 wxString outPath = resolveJobOutputPath( aJob, brd );
2664
2665 if( !PATHS::EnsurePathExists( outPath, true ) )
2666 {
2667 m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
2669 }
2670
2673
2675}
2676
2677
2679{
2680 JOB_EXPORT_PCB_IPCD356* job = dynamic_cast<JOB_EXPORT_PCB_IPCD356*>( aJob );
2681
2682 if( job == nullptr )
2684
2685 BOARD* brd = getBoard( job->m_filename );
2686
2687 if( !brd )
2689
2690 if( job->GetConfiguredOutputPath().IsEmpty() )
2691 {
2692 wxFileName fn = brd->GetFileName();
2693 fn.SetName( fn.GetName() );
2694 fn.SetExt( FILEEXT::IpcD356FileExtension );
2695
2696 job->SetWorkingOutputPath( fn.GetFullName() );
2697 }
2698
2699 wxString outPath = resolveJobOutputPath( aJob, brd );
2700
2701 if( !PATHS::EnsurePathExists( outPath, true ) )
2702 {
2703 m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
2705 }
2706
2707 IPC356D_WRITER exporter( brd );
2708
2709 bool success = exporter.Write( outPath );
2710
2711 if( success )
2712 {
2713 aJob->AddOutput( outPath );
2714 m_reporter->Report( _( "Successfully created IPC-D-356 file\n" ), RPT_SEVERITY_INFO );
2716 }
2717 else
2718 {
2719 m_reporter->Report( _( "Failed to create IPC-D-356 file\n" ), RPT_SEVERITY_ERROR );
2721 }
2722}
2723
2724
2726{
2727 JOB_EXPORT_PCB_ODB* job = dynamic_cast<JOB_EXPORT_PCB_ODB*>( aJob );
2728
2729 if( job == nullptr )
2731
2732 BOARD* brd = getBoard( job->m_filename );
2733
2734 if( !brd )
2736
2737 if( !job->m_variant.IsEmpty() )
2738 brd->SetCurrentVariant( job->m_variant );
2739
2740 if( job->GetConfiguredOutputPath().IsEmpty() )
2741 {
2743 {
2744 // just basic folder name
2745 job->SetWorkingOutputPath( "odb" );
2746 }
2747 else
2748 {
2749 wxFileName fn( brd->GetFileName() );
2750 fn.SetName( fn.GetName() + wxS( "-odb" ) );
2751
2752 switch( job->m_compressionMode )
2753 {
2755
2756 case JOB_EXPORT_PCB_ODB::ODB_COMPRESSION::TGZ: fn.SetExt( "tgz" ); break;
2757
2758 default: break;
2759 };
2760
2761 job->SetWorkingOutputPath( fn.GetFullName() );
2762 }
2763 }
2764
2765 wxString outPath = resolveJobOutputPath( job, brd );
2766
2767 // The helper handles output path creation, so hand it a job that already has fully-resolved
2768 // token context (title block and project overrides applied above).
2770
2771 if( !m_reporter )
2773
2774 if( job->m_checkZonesBeforeExport )
2775 {
2776 TOOL_MANAGER* toolManager = getToolManager( brd );
2777
2778 if( !toolManager->FindTool( ZONE_FILLER_TOOL_NAME ) )
2779 toolManager->RegisterTool( new ZONE_FILLER_TOOL );
2780
2781 toolManager->GetTool<ZONE_FILLER_TOOL>()->FillAllZones( nullptr, m_progressReporter, true );
2782 }
2783
2785 aJob->AddOutput( outPath );
2786
2787 if( m_reporter->HasMessageOfSeverity( RPT_SEVERITY_ERROR ) )
2789
2791}
2792
2794{
2795 JOB_PCB_UPGRADE* job = dynamic_cast<JOB_PCB_UPGRADE*>( aJob );
2796
2797 if( job == nullptr )
2799
2800 bool shouldSave = job->m_force;
2801
2802 try
2803 {
2805 BOARD* brd = getBoard( job->m_filename );
2807 shouldSave = true;
2808
2809 if( shouldSave )
2810 {
2811 pi->SaveBoard( brd->GetFileName(), brd );
2812 m_reporter->Report( _( "Successfully saved board file using the latest format\n" ), RPT_SEVERITY_INFO );
2813 }
2814 else
2815 {
2816 m_reporter->Report( _( "Board file was not updated\n" ), RPT_SEVERITY_ERROR );
2817 }
2818 }
2819 catch( const IO_ERROR& ioe )
2820 {
2821 wxString msg =
2822 wxString::Format( _( "Error saving board file '%s'.\n%s" ), job->m_filename, ioe.What().GetData() );
2823 m_reporter->Report( msg, RPT_SEVERITY_ERROR );
2825 }
2826
2828}
2829
2830// Most job handlers need to align the running job with the board before resolving any
2831// output paths with variables in them like ${REVISION}.
2832wxString PCBNEW_JOBS_HANDLER::resolveJobOutputPath( JOB* aJob, BOARD* aBoard, const wxString* aDrawingSheet )
2833{
2834 aJob->SetTitleBlock( aBoard->GetTitleBlock() );
2835
2836 if( aDrawingSheet && !aDrawingSheet->IsEmpty() )
2837 loadOverrideDrawingSheet( aBoard, *aDrawingSheet );
2838
2839 PROJECT* project = aBoard->GetProject();
2840
2841 if( project )
2842 project->ApplyTextVars( aJob->GetVarOverrides() );
2843
2844 aBoard->SynchronizeProperties();
2845
2846 return aJob->GetFullOutputPath( project );
2847}
2848
2849
2851{
2852 DS_PROXY_VIEW_ITEM* drawingSheet = new DS_PROXY_VIEW_ITEM( pcbIUScale, &aBrd->GetPageSettings(), aBrd->GetProject(),
2853 &aBrd->GetTitleBlock(), &aBrd->GetProperties() );
2854
2855 drawingSheet->SetSheetName( std::string() );
2856 drawingSheet->SetSheetPath( std::string() );
2857 drawingSheet->SetIsFirstPage( true );
2858
2859 drawingSheet->SetFileName( TO_UTF8( aBrd->GetFileName() ) );
2860
2861 wxString currentVariant = aBrd->GetCurrentVariant();
2862 wxString variantDesc = aBrd->GetVariantDescription( currentVariant );
2863 drawingSheet->SetVariantName( TO_UTF8( currentVariant ) );
2864 drawingSheet->SetVariantDesc( TO_UTF8( variantDesc ) );
2865
2866 return drawingSheet;
2867}
2868
2869
2870void PCBNEW_JOBS_HANDLER::loadOverrideDrawingSheet( BOARD* aBrd, const wxString& aSheetPath )
2871{
2872 // dont bother attempting to load a empty path, if there was one
2873 if( aSheetPath.IsEmpty() )
2874 return;
2875
2876 auto loadSheet = [&]( const wxString& path ) -> bool
2877 {
2880 resolver.SetProject( aBrd->GetProject() );
2881 resolver.SetProgramBase( &Pgm() );
2882
2883 wxString filename = resolver.ResolvePath( BASE_SCREEN::m_DrawingSheetFileName,
2884 aBrd->GetProject()->GetProjectPath(), { aBrd->GetEmbeddedFiles() } );
2885 wxString msg;
2886
2887 if( !DS_DATA_MODEL::GetTheInstance().LoadDrawingSheet( filename, &msg ) )
2888 {
2889 m_reporter->Report( wxString::Format( _( "Error loading drawing sheet '%s'." ), path ) + wxS( "\n" ) + msg
2890 + wxS( "\n" ),
2892 return false;
2893 }
2894
2895 return true;
2896 };
2897
2898 if( loadSheet( aSheetPath ) )
2899 return;
2900
2901 // failed loading custom path, revert back to default
2902 loadSheet( aBrd->GetProject()->GetProjectFile().m_BoardDrawingSheetFile );
2903}
2904
2905
2906// Resolve a KiCad layer name (canonical board-file name such as "F.Cu", or the GUI display name)
2907// to its layer id. Returns UNDEFINED_LAYER when no layer matches.
2908static PCB_LAYER_ID resolveKiCadLayerName( const wxString& aName )
2909{
2910 for( PCB_LAYER_ID layer : LSET::AllLayersMask().Seq() )
2911 {
2912 if( LSET::Name( layer ) == aName || LayerName( layer ) == aName )
2913 return layer;
2914 }
2915
2916 return UNDEFINED_LAYER;
2917}
2918
2919
2921{
2922 JOB_PCB_IMPORT* job = dynamic_cast<JOB_PCB_IMPORT*>( aJob );
2923
2924 if( !job )
2926
2927 // Check that input file exists
2928 if( !wxFile::Exists( job->m_inputFile ) )
2929 {
2930 m_reporter->Report( wxString::Format( _( "Input file not found: '%s'\n" ),
2931 job->m_inputFile ),
2934 }
2935
2936 // Map job format to PCB_IO file type
2938
2939 switch( job->m_format )
2940 {
2942
2944
2946
2948
2950
2952
2954
2956 }
2957
2958 // FindPluginTypeFromBoardPath returns FILE_TYPE_NONE (not PCB_FILE_UNKNOWN) when no plugin
2959 // claims the file. Quiet sentinel: lets the top-level `import` command try the schematic face.
2961 {
2962 m_reporter->Report( wxString::Format( _( "No PCB importer recognizes the file format of "
2963 "'%s'\n" ),
2964 job->m_inputFile ),
2967 }
2968
2969 // Determine output path
2970 wxString outputPath = job->GetConfiguredOutputPath();
2971
2972 if( outputPath.IsEmpty() )
2974
2975 BOARD* board = nullptr;
2976 wxString formatName = PCB_IO_MGR::ShowType( fileType );
2977 std::vector<wxString> warnings;
2978
2979 // Real source-to-KiCad layer decisions, captured by our mapping callback so the report can
2980 // show them and so explicit overrides can be validated.
2981 struct CAPTURED_LAYER
2982 {
2983 wxString m_source;
2984 PCB_LAYER_ID m_target;
2985 wxString m_method;
2986 };
2987
2988 std::vector<CAPTURED_LAYER> capturedLayers;
2989 std::set<wxString> seenSourceLayers;
2990 bool layersCaptured = false;
2991
2992 try
2993 {
2995
2996 if( !pi )
2997 {
2998 m_reporter->Report( wxString::Format( _( "No plugin found for file type '%s'\n" ), formatName ),
3001 }
3002
3003 // Replace the plugin's default best-guess callback so we can apply explicit overrides and
3004 // capture the resulting mapping. Only mappable importers expose their source layers; for
3005 // others the report falls back to listing the imported board's enabled layers.
3006 if( LAYER_MAPPABLE_PLUGIN* mappable = dynamic_cast<LAYER_MAPPABLE_PLUGIN*>( pi.get() ) )
3007 {
3008 if( !job->m_layerMap.empty() || job->m_reportFormat != IMPORT_REPORT_FORMAT::NONE )
3009 {
3010 mappable->RegisterCallback(
3011 [&]( const std::vector<INPUT_LAYER_DESC>& aDescs )
3012 -> std::map<wxString, PCB_LAYER_ID>
3013 {
3014 std::map<wxString, PCB_LAYER_ID> result;
3015
3016 for( const INPUT_LAYER_DESC& desc : aDescs )
3017 {
3018 PCB_LAYER_ID target = desc.AutoMapLayer;
3019 wxString method = wxS( "auto" );
3020
3021 if( auto it = job->m_layerMap.find( desc.Name );
3022 it != job->m_layerMap.end() )
3023 {
3024 PCB_LAYER_ID resolved = resolveKiCadLayerName( it->second );
3025
3026 if( resolved == UNDEFINED_LAYER )
3027 {
3028 warnings.push_back( wxString::Format(
3029 _( "Layer map entry '%s' -> '%s' names an unknown "
3030 "KiCad layer; using automatic mapping instead" ),
3031 desc.Name, it->second ) );
3032 }
3033 else if( !desc.PermittedLayers.Contains( resolved ) )
3034 {
3035 warnings.push_back( wxString::Format(
3036 _( "Layer map entry '%s' -> '%s' is not a permitted "
3037 "target for this layer; using automatic mapping "
3038 "instead" ),
3039 desc.Name, it->second ) );
3040 }
3041 else
3042 {
3043 target = resolved;
3044 method = wxS( "explicit" );
3045 }
3046 }
3047
3048 if( desc.Required && target == UNDEFINED_LAYER )
3049 {
3050 warnings.push_back( wxString::Format(
3051 _( "No KiCad layer mapping for required source layer "
3052 "'%s'; its items will not be imported" ),
3053 desc.Name ) );
3054 }
3055
3056 result.emplace( desc.Name, target );
3057 capturedLayers.push_back( { desc.Name, target, method } );
3058 seenSourceLayers.insert( desc.Name );
3059 }
3060
3061 layersCaptured = true;
3062 return result;
3063 } );
3064 }
3065 }
3066 else if( !job->m_layerMap.empty() )
3067 {
3068 warnings.push_back( _( "A layer map was provided, but this importer does not support "
3069 "layer remapping; it will be ignored" ) );
3070 }
3071
3072 m_reporter->Report(
3073 wxString::Format( _( "Importing '%s' using %s format...\n" ), job->m_inputFile, formatName ),
3075
3076 board = pi->LoadBoard( job->m_inputFile, nullptr, nullptr, nullptr );
3077
3078 if( !board )
3079 {
3080 m_reporter->Report( _( "Failed to load board\n" ), RPT_SEVERITY_ERROR );
3082 }
3083
3084 // Flag explicit map entries that never matched a source layer so typos do not pass silently.
3085 if( layersCaptured )
3086 {
3087 for( const auto& [source, target] : job->m_layerMap )
3088 {
3089 if( !seenSourceLayers.contains( source ) )
3090 {
3091 warnings.push_back( wxString::Format(
3092 _( "Layer map entry '%s' does not match any source layer in the "
3093 "imported file; it will be ignored" ),
3094 source ) );
3095 }
3096 }
3097 }
3098
3099 // Save as KiCad format
3101 kicadPlugin->SaveBoard( outputPath, board );
3102
3103 m_reporter->Report( wxString::Format( _( "Successfully saved imported board to '%s'\n" ), outputPath ),
3105
3106 // Generate report if requested
3108 {
3109 IMPORT_REPORT_DATA reportData;
3110
3111 reportData.m_sourceFile = wxFileName( job->m_inputFile ).GetFullName();
3112 reportData.m_sourceFormat = formatName;
3113 reportData.m_outputFile = wxFileName( outputPath ).GetFullName();
3114
3115 size_t trackCount = 0;
3116 size_t viaCount = 0;
3117
3118 for( PCB_TRACK* track : board->Tracks() )
3119 {
3120 if( track->Type() == PCB_VIA_T )
3121 viaCount++;
3122 else
3123 trackCount++;
3124 }
3125
3126 reportData.m_statistics = {
3127 { wxS( "footprints" ), board->Footprints().size() },
3128 { wxS( "tracks" ), trackCount },
3129 { wxS( "vias" ), viaCount },
3130 { wxS( "zones" ), board->Zones().size() }
3131 };
3132
3133 // Build layer mapping info, carried only in the JSON report. Prefer the real
3134 // source-to-KiCad decisions captured during load; fall back to the imported board's
3135 // enabled layers for importers that do not expose a mappable layer set.
3136 nlohmann::json layerMappings = nlohmann::json::object();
3137
3138 if( layersCaptured )
3139 {
3140 for( const CAPTURED_LAYER& mapped : capturedLayers )
3141 {
3142 std::string kicadLayer = mapped.m_target == UNDEFINED_LAYER
3143 ? std::string()
3144 : LSET::Name( mapped.m_target ).ToStdString();
3145
3146 layerMappings[mapped.m_source.ToStdString()] = {
3147 { "kicad_layer", kicadLayer },
3148 { "method", mapped.m_method.ToStdString() }
3149 };
3150 }
3151 }
3152 else
3153 {
3154 for( PCB_LAYER_ID layer : board->GetEnabledLayers().Seq() )
3155 {
3156 wxString layerName = board->GetLayerName( layer );
3157
3158 layerMappings[layerName.ToStdString()] = {
3159 { "kicad_layer", LSET::Name( layer ).ToStdString() },
3160 { "method", "auto" }
3161 };
3162 }
3163 }
3164
3165 reportData.m_extraJson["layer_mapping"] = layerMappings;
3166 reportData.m_warnings = warnings;
3167
3168 WriteImportReport( m_reporter, job->m_reportFormat, job->m_reportFile, reportData );
3169 }
3170 else
3171 {
3172 // No report requested, but explicit-mapping problems still need to surface.
3173 for( const wxString& warning : warnings )
3174 m_reporter->Report( warning + wxS( "\n" ), RPT_SEVERITY_WARNING );
3175 }
3176
3177 delete board;
3178 }
3179 catch( const IO_ERROR& ioe )
3180 {
3181 m_reporter->Report( wxString::Format( _( "Error during import: %s\n" ), ioe.What() ), RPT_SEVERITY_ERROR );
3182
3183 delete board;
3185 }
3186
3188}
3189
3190
3191// ============================================================================
3192// JobDiff: pcb_diff implementation
3193// ============================================================================
3196#include <diff_merge/diff_scene.h>
3197#include <diff_merge/pcb_differ.h>
3199#include <jobs/job_pcb_diff.h>
3200
3201
3202// Load a board into a SCRATCH_DOC<BOARD> that keeps its project attached for
3203// the document's lifetime — the differ/applier read PROJECT_FILE-scoped fields
3204// (drawing-sheet path, DRC severities, net classes). The destructor severs the
3205// BOARD->project link in the right order. Used by every PCB diff/merge job.
3206static SCRATCH_DOC<BOARD> loadScratchBoard( SETTINGS_MANAGER& aMgr, const wxString& aPath,
3207 bool aInitializeAfterLoad = true )
3208{
3209 return LoadScratchDoc<BOARD>(
3210 aMgr, aPath,
3211 [aPath, aInitializeAfterLoad]( PROJECT* aProject ) -> std::unique_ptr<BOARD>
3212 {
3213 PCB_IO_MGR::PCB_FILE_T pluginType =
3215
3216 if( !aProject || pluginType == PCB_IO_MGR::FILE_TYPE_NONE )
3217 return nullptr;
3218
3220 opts.initialize_after_load = aInitializeAfterLoad;
3221
3222 try
3223 {
3224 return BOARD_LOADER::Load( aPath, pluginType, aProject, opts );
3225 }
3226 catch( ... )
3227 {
3228 return nullptr;
3229 }
3230 },
3231 []( BOARD* aBoard )
3232 {
3233 if( aBoard )
3234 aBoard->ClearProject();
3235 } );
3236}
3237
3238
3240{
3241 JOB_PCB_DIFF* diffJob = dynamic_cast<JOB_PCB_DIFF*>( aJob );
3242
3243 if( !diffJob )
3245
3246 // SCRATCH_DOC<BOARD> keeps each board's project attached for the lifetime
3247 // of the diff, which the differ needs to read project-file-scoped fields
3248 // (m_BoardDrawingSheetFile, etc). The previous loadStandaloneBoard +
3249 // ClearProject-up-front path would null those out before the differ ran.
3251
3252 SCRATCH_DOC<BOARD> aScratch = loadScratchBoard( diffMgr, diffJob->m_inputA );
3253 SCRATCH_DOC<BOARD> bScratch = loadScratchBoard( diffMgr, diffJob->m_inputB );
3254
3255 BOARD* boardA = aScratch.doc.get();
3256 BOARD* boardB = bScratch.doc.get();
3257
3258 if( !boardA )
3259 {
3260 m_reporter->Report( wxString::Format( _( "Failed to load %s\n" ), diffJob->m_inputA ), RPT_SEVERITY_ERROR );
3262 }
3263
3264 if( !boardB )
3265 {
3266 m_reporter->Report( wxString::Format( _( "Failed to load %s\n" ), diffJob->m_inputB ), RPT_SEVERITY_ERROR );
3268 }
3269
3270 KICAD_DIFF::PCB_DIFFER differ( boardA, boardB, diffJob->m_inputB );
3272
3273 int diffExitCode = KICAD_DIFF::DiffExitCode( result );
3274
3275 if( diffJob->m_exitCodeOnly )
3276 return diffExitCode;
3277
3278 // The board geometry rendered beneath the change overlay (PNG/SVG only)
3279 // matches what the interactive dialog draws.
3281 KICAD_DIFF::MakeEmitOptions( *diffJob, diffJob->m_inputA, diffJob->m_inputB );
3283 emitOpts.referenceGeometry = [&]( const KIGFX::COLOR4D& aColor )
3284 { return KICAD_DIFF::ExtractBoardGeometry( *boardA, aColor ); };
3285 emitOpts.comparisonGeometry = [&]( const KIGFX::COLOR4D& aColor )
3286 { return KICAD_DIFF::ExtractBoardGeometry( *boardB, aColor ); };
3287
3288 return KICAD_DIFF::EmitDiffResult( result, emitOpts, diffExitCode, *m_reporter );
3289}
3290
3291
3292// ============================================================================
3293// JobMerge: pcb_merge implementation
3294// ============================================================================
3299
3300
3301int PCBNEW_JOBS_HANDLER::RunMerge( KICAD_DIFF::DOC_KIND aKind, const wxString& aAncestor,
3302 const wxString& aOurs, const wxString& aTheirs,
3303 const wxString& aOutput, bool aInteractive, bool aSingleFile,
3304 REPORTER* aReporter )
3305{
3306 // Restore m_reporter on scope exit so a caller's transient (often
3307 // stack-local) reporter doesn't outlive this call as a dangling member.
3309 aReporter ? aReporter : m_reporter );
3310
3312 return runFpLibMerge( aAncestor, aOurs, aTheirs, aOutput, aSingleFile );
3313
3314 return runPcbMerge( aAncestor, aOurs, aTheirs, aOutput, aInteractive );
3315}
3316
3317
3318int PCBNEW_JOBS_HANDLER::runPcbMerge( const wxString& aAncestor, const wxString& aOurs,
3319 const wxString& aTheirs, const wxString& aOutput,
3320 bool aInteractive )
3321{
3322 // Use SCRATCH_DOC<BOARD> so each input keeps its project attached for the
3323 // life of the merge — necessary for any doc-level resolution that mutates
3324 // PROJECT_FILE-scoped state (DRC severities, net classes) and needs to be
3325 // saved as a sibling .kicad_pro. SCRATCH_DOC's destructor severs the
3326 // BOARD->project link in the right order and unloads the project from
3327 // the manager, avoiding the dangling-PROJECT_FILE::m_BoardSettings pointer
3328 // the previous up-front-ClearProject loadStandaloneBoard path had to
3329 // guard against.
3331
3332 SCRATCH_DOC<BOARD> ancestorScratch = loadScratchBoard( mgr, aAncestor );
3333 SCRATCH_DOC<BOARD> oursScratch = loadScratchBoard( mgr, aOurs );
3334 SCRATCH_DOC<BOARD> theirsScratch = loadScratchBoard( mgr, aTheirs );
3335
3336 BOARD* ancestor = ancestorScratch.doc.get();
3337 BOARD* ours = oursScratch.doc.get();
3338 BOARD* theirs = theirsScratch.doc.get();
3339
3340 if( !ancestor || !ours || !theirs )
3341 {
3342 m_reporter->Report( _( "Failed to load one or more input boards\n" ), RPT_SEVERITY_ERROR );
3344 }
3345
3346 KICAD_DIFF::PCB_DIFFER ourDiff( ancestor, ours );
3347 KICAD_DIFF::PCB_DIFFER theirDiff( ancestor, theirs );
3348
3349 KICAD_DIFF::DOCUMENT_DIFF ourDocDiff = ourDiff.Diff();
3350 KICAD_DIFF::DOCUMENT_DIFF theirDocDiff = theirDiff.Diff();
3351
3353 KICAD_DIFF::MERGE_PLAN plan = engine.Plan( ourDocDiff, theirDocDiff );
3354
3355 // A cancelled dialog leaves plan unresolved and falls through to the
3356 // marker flow below.
3357 if( aInteractive && !plan.Resolved() )
3358 {
3359 if( !Pgm().IsGUI() )
3360 {
3361 m_reporter->Report( _( "--interactive requires a GUI KiCad process; the console "
3362 "kicad-cli cannot open dialogs.\n" ),
3365 }
3366
3367 // Geometry context so the conflict viewer can render the actual
3368 // boards behind the conflict bbox highlight.
3369 const KICAD_DIFF::DIFF_COLOR_THEME theme;
3374
3375 // Build per-side bbox lookups so a "moved on theirs" item highlights
3376 // at its theirs-side coordinates when the user previews Theirs.
3378 KICAD_DIFF::CollectChangeBBoxes( theirDocDiff, ctx.theirsBBoxes );
3379
3380 DIALOG_KICAD_MERGE_3WAY dlg( wxTheApp->GetTopWindow(), plan, std::move( ctx ) );
3381
3382 if( dlg.ShowModal() == wxID_APPLY )
3383 plan = dlg.GetResolvedPlan();
3384 }
3385
3386 // Snapshot of the plan before the applier moves it; drives the
3387 // unresolved-conflict report below.
3388 const KICAD_DIFF::MERGE_PLAN planSnapshot = plan;
3389
3390 KICAD_DIFF::PCB_MERGE_APPLIER applier( ancestor, ours, theirs, std::move( plan ) );
3391 std::unique_ptr<BOARD> merged = applier.Apply();
3392
3393 if( !merged )
3394 {
3395 m_reporter->Report( _( "Merge applier failed to produce a board\n" ), RPT_SEVERITY_ERROR );
3397 }
3398
3399 // Serialize to the output path using the canonical PCB IO.
3400 PCB_IO_KICAD_SEXPR pcbIO;
3401
3402 try
3403 {
3404 pcbIO.SaveBoard( aOutput, merged.get() );
3405 }
3406 catch( const IO_ERROR& ioe )
3407 {
3408 m_reporter->Report( wxString::Format( _( "Failed to save merged board: %s\n" ), ioe.What() ),
3411 }
3412
3413 // BOARD_DESIGN_SETTINGS fields like m_DRCSeverities serialize to
3414 // .kicad_pro, not .kicad_pcb. Mirror only those specific fields onto
3415 // ancestor (still linked to its project via BOARD::SetProject) then
3416 // save ancestor's project alongside the merged board file. Whole-
3417 // BOARD_DESIGN_SETTINGS copy would alias shared_ptr<NET_SETTINGS>
3418 // across BOARDs and crash on ClearProject during SCRATCH_DOC release;
3419 // single-field mirror avoids that.
3420 if( applier.GetReport().projectFileTouched && ancestor && ancestor->GetProject() )
3421 {
3422 ancestor->GetDesignSettings().m_DRCSeverities = merged->GetDesignSettings().m_DRCSeverities;
3423
3424 // Mirror net settings (the applier copied them onto the result via
3425 // NET_SETTINGS::CopyFrom; do the same here to ancestor, which still
3426 // owns the project's nested-settings registration so SaveProjectCopy
3427 // walks the right entry).
3428 if( ancestor->GetDesignSettings().m_NetSettings && merged->GetDesignSettings().m_NetSettings )
3429 {
3430 ancestor->GetDesignSettings().m_NetSettings->CopyFrom( *merged->GetDesignSettings().m_NetSettings );
3431 }
3432
3433 // The applier stages drawing-sheet resolutions on the report (the
3434 // result BOARD is project-less). Mirror onto ancestor's project here
3435 // before SaveProjectCopy walks the PROJECT_FILE.
3436 if( applier.GetReport().drawingSheetFileSet )
3437 {
3439 }
3440
3441 wxFileName proFn( aOutput );
3442 proFn.SetExt( FILEEXT::ProjectFileExtension );
3443
3444 // JSON-patch path: flush ancestor's in-memory project to its JSON
3445 // cache, then patch only the diffed DOC_PROP fields onto the output
3446 // file. This preserves any non-diffed fields the user had at the
3447 // output path (text variables, last paths, layer presets etc.) that
3448 // a full SaveProjectCopy would silently overwrite.
3449 PROJECT_FILE& ancProj = ancestor->GetProject()->GetProjectFile();
3450 ancProj.Store();
3451
3452 const KICAD_DIFF::PCB_MERGE_APPLIER::REPORT& mergeReport = applier.GetReport();
3453
3454 // PROJECT_FILE::Store() flushes the project file's own params but not
3455 // its registered NESTED_SETTINGS. Flush only the nested settings the
3456 // merge resolved so the surgical patch does not overwrite unrelated
3457 // project subtrees.
3458 if( mergeReport.drcSeveritiesTouched )
3459 ancestor->GetDesignSettings().SaveToFile( wxEmptyString, true );
3460
3461 if( mergeReport.netClassesTouched && ancestor->GetDesignSettings().m_NetSettings )
3462 ancestor->GetDesignSettings().m_NetSettings->SaveToFile( wxEmptyString, true );
3463
3464 std::set<wxString> touched;
3465 if( mergeReport.drcSeveritiesTouched )
3466 touched.insert( KICAD_DIFF::DOC_PROP_DRC_SEVERITIES );
3467
3468 if( mergeReport.netClassesTouched )
3469 touched.insert( KICAD_DIFF::DOC_PROP_NET_CLASSES );
3470
3471 if( applier.GetReport().drawingSheetFileSet )
3472 touched.insert( KICAD_DIFF::DOC_PROP_DRAWING_SHEET );
3473
3474 if( !KICAD_DIFF::ApplyProjectFilePatches( proFn.GetFullPath(), *ancProj.Internals(), touched ) )
3475 {
3476 // Patch failed (existing output unparseable or write error).
3477 // Fall back to the legacy full-copy path so the user still gets
3478 // a project file even if it overwrites non-diffed customisations.
3479 if( !mgr.SaveProjectCopy( proFn.GetFullPath(), ancestor->GetProject() ) )
3480 {
3481 m_reporter->Report(
3482 wxString::Format( _( "Failed to save merged project file: %s\n" ), proFn.GetFullPath() ),
3485 }
3486 }
3487
3488 // Write a project-dir sibling file from staged report content. Empty
3489 // content removes the file so a TAKE_ANCESTOR resolution against an
3490 // ancestor with no file clears stale content at the output path.
3491 auto writeStagedFile = [&]( const wxString& aPath, const wxString& aContent, const wxString& aLabel ) -> bool
3492 {
3493 if( aContent.IsEmpty() )
3494 {
3495 if( wxFileExists( aPath ) )
3496 wxRemoveFile( aPath );
3497
3498 return true;
3499 }
3500
3501 wxFile out;
3502
3503 if( !out.Open( aPath, wxFile::write ) || !out.Write( aContent ) )
3504 {
3505 m_reporter->Report( wxString::Format( _( "Failed to save merged %s: %s\n" ), aLabel, aPath ),
3507 return false;
3508 }
3509
3510 return true;
3511 };
3512
3513 // Custom DRC rules: write the applier's staged content next to the
3514 // merged board so the chosen side's rules apply at next DRC run.
3515 if( applier.GetReport().customDrcRulesSet )
3516 {
3517 wxFileName druFn( aOutput );
3518 druFn.SetExt( FILEEXT::DesignRulesFileExtension );
3519
3520 if( !writeStagedFile( druFn.GetFullPath(), applier.GetReport().customDrcRules, _( "custom DRC rules" ) ) )
3521 {
3523 }
3524 }
3525
3526 // Footprint / symbol library tables: write into the merged project
3527 // directory. Both files have no extension.
3528 if( applier.GetReport().fpLibTableSet )
3529 {
3530 wxFileName fpFn( aOutput );
3531 fpFn.SetFullName( wxString::FromUTF8( FILEEXT::FootprintLibraryTableFileName ) );
3532
3533 if( !writeStagedFile( fpFn.GetFullPath(), applier.GetReport().fpLibTable, _( "footprint library table" ) ) )
3534 {
3536 }
3537 }
3538
3539 if( applier.GetReport().symLibTableSet )
3540 {
3541 wxFileName symFn( aOutput );
3542 symFn.SetFullName( wxString::FromUTF8( FILEEXT::SymbolLibraryTableFileName ) );
3543
3544 if( !writeStagedFile( symFn.GetFullPath(), applier.GetReport().symLibTable, _( "symbol library table" ) ) )
3545 {
3547 }
3548 }
3549 }
3550
3551 // Surface post-apply validator findings (refdes collisions, schema
3552 // mismatch, missed connectivity rebuild). Advisory — they do not change the
3553 // exit code, only the merge's resolved/unresolved status does.
3555 m_reporter->Report( wxString::Format( wxS( "%s: %s\n" ), f.validator, f.message ), f.severity );
3556
3557 // The merged board was written to m_outputPath above, so the output is
3558 // always a valid file. Unresolved conflicts are reported and signalled via
3559 // the exit code; the user resolves them with the interactive mergetool.
3560 if( !planSnapshot.Resolved() )
3561 {
3562 m_reporter->Report( wxString::Format( _( "Merge completed with %zu unresolved conflict(s) in %s\n" ),
3563 planSnapshot.ConflictCount(), aOutput ),
3566 }
3567
3569}
3570
3571
3572// ============================================================================
3573// JobFpDiff: fp_diff implementation
3574// ============================================================================
3576#include <jobs/job_fp_diff.h>
3577
3578
3579// Load one side of a footprint-library diff into its owner vector and name map.
3580// When aAllowEmpty is set an empty path resolves to a clean (empty) side; the
3581// non-interactive job path leaves it unset so a missing path is an input error.
3582static int loadFootprintLibrarySide( const wxString& aPath,
3583 std::vector<std::unique_ptr<FOOTPRINT>>& aOwners,
3584 KICAD_DIFF::FP_LIB_DIFFER::FOOTPRINT_MAP& aMap, bool aAllowEmpty,
3585 REPORTER& aReporter )
3586{
3587 if( aAllowEmpty && aPath.IsEmpty() )
3589
3590 try
3591 {
3592 auto loaded = KICAD_DIFF::FP_LIB_DIFFER::LoadLibrary( aPath );
3593 aOwners = std::move( loaded.first );
3594 aMap = std::move( loaded.second );
3596 }
3597 catch( const IO_ERROR& ioe )
3598 {
3599 aReporter.Report( wxString::Format( _( "Failed to load %s: %s\n" ), aPath, ioe.What() ),
3601 }
3602 catch( const std::exception& e )
3603 {
3604 aReporter.Report(
3605 wxString::Format( _( "Failed to load %s: %s\n" ), aPath, wxString::FromUTF8( e.what() ) ),
3607 }
3608
3610}
3611
3612
3613// Flatten a footprint-library name map into a single DOCUMENT_GEOMETRY tinted
3614// with the supplied per-side theme colour.
3617{
3619
3620 for( const auto& [name, footprint] : aMap )
3621 {
3622 if( footprint )
3623 KICAD_DIFF::AppendGeometry( geometry, KICAD_DIFF::ExtractFootprintGeometry( *footprint, aColor ) );
3624 }
3625
3626 return geometry;
3627}
3628
3629
3631{
3632 JOB_FP_DIFF* diffJob = dynamic_cast<JOB_FP_DIFF*>( aJob );
3633
3634 if( !diffJob )
3636
3637 wxFileName dirA( diffJob->m_inputA );
3638 dirA.MakeAbsolute();
3639 wxFileName dirB( diffJob->m_inputB );
3640 dirB.MakeAbsolute();
3641
3642 std::vector<std::unique_ptr<FOOTPRINT>> ownersA;
3643 std::vector<std::unique_ptr<FOOTPRINT>> ownersB;
3646
3647 if( int rc = loadFootprintLibrarySide( dirA.GetFullPath(), ownersA, mapA, false, *m_reporter );
3649 {
3650 return rc;
3651 }
3652
3653 if( int rc = loadFootprintLibrarySide( dirB.GetFullPath(), ownersB, mapB, false, *m_reporter );
3655 {
3656 return rc;
3657 }
3658
3659 KICAD_DIFF::FP_LIB_DIFFER differ( mapA, mapB, diffJob->m_inputB );
3661
3662 int diffExitCode = KICAD_DIFF::DiffExitCode( result );
3663
3664 if( diffJob->m_exitCodeOnly )
3665 return diffExitCode;
3666
3668 KICAD_DIFF::MakeEmitOptions( *diffJob, diffJob->m_inputA, diffJob->m_inputB );
3670 emitOpts.referenceGeometry = [&]( const KIGFX::COLOR4D& aColor )
3671 { return footprintLibraryGeometry( mapA, aColor ); };
3672 emitOpts.comparisonGeometry = [&]( const KIGFX::COLOR4D& aColor )
3673 { return footprintLibraryGeometry( mapB, aColor ); };
3674
3675 return KICAD_DIFF::EmitDiffResult( result, emitOpts, diffExitCode, *m_reporter );
3676}
3677
3678
3679// ============================================================================
3680// JobOpenDiffDialog: load two on-disk files and open DIALOG_KICAD_DIFF.
3681// Dispatched from the project manager / PR-review dialog via KIWAY.
3682// ============================================================================
3686#include <jobs/scratch_doc.h>
3687
3688
3690 const wxString& aFileB, const wxString& aLabelA,
3691 const wxString& aLabelB, wxWindow* aParent,
3692 REPORTER* aReporter )
3693{
3694 // Restore m_reporter on scope exit so a caller's transient (often
3695 // stack-local) reporter doesn't outlive this call as a dangling member.
3697 aReporter ? aReporter : m_reporter );
3698
3699 wxWindow* parent = aParent ? aParent : ( wxTheApp ? wxTheApp->GetTopWindow() : nullptr );
3700
3702
3703 auto loadBoardScratch = [&]( const wxString& aPath )
3704 {
3705 return loadScratchBoard( mgr, aPath, /* aInitializeAfterLoad */ false );
3706 };
3707
3710 KICAD_DIFF::DOCUMENT_GEOMETRY compGeometry;
3711
3712 auto loadFootprintFile = [&]( const wxString& aPath ) -> std::unique_ptr<FOOTPRINT>
3713 {
3714 if( aPath.IsEmpty() )
3715 return nullptr;
3716
3717 wxFileName fn( aPath );
3718 fn.MakeAbsolute();
3719
3720 // A single .kicad_mod's internal (footprint ...) name need not match its
3721 // filename, so load the file's sole footprint via ImportFootprint rather
3722 // than FootprintLoad (which treats the directory as a .pretty library and
3723 // keys by basename), matching runFpLibMerge's single-file path.
3725 wxString name;
3726 return std::unique_ptr<FOOTPRINT>( io.ImportFootprint( fn.GetFullPath(), name ) );
3727 };
3728
3729 switch( aKind )
3730 {
3732 {
3733 SCRATCH_DOC<BOARD> a = loadBoardScratch( aFileA );
3734 SCRATCH_DOC<BOARD> b = loadBoardScratch( aFileB );
3735
3736 // Synthesize empty boards for ADDED / REMOVED sides so the differ
3737 // can still produce a meaningful per-item list rather than failing
3738 // on an empty input file.
3739 BOARD emptyA;
3740 BOARD emptyB;
3741
3742 if( !a.doc && !aFileA.IsEmpty() )
3743 {
3744 m_reporter->Report( wxString::Format( _( "Failed to load %s\n" ), aFileA ), RPT_SEVERITY_ERROR );
3746 }
3747
3748 if( !b.doc && !aFileB.IsEmpty() )
3749 {
3750 m_reporter->Report( wxString::Format( _( "Failed to load %s\n" ), aFileB ), RPT_SEVERITY_ERROR );
3752 }
3753
3754 BOARD* boardA = a.doc ? a.doc.get() : &emptyA;
3755 BOARD* boardB = b.doc ? b.doc.get() : &emptyB;
3756
3757 KICAD_DIFF::PCB_DIFFER differ( boardA, boardB, aFileB );
3758 result = differ.Diff();
3759
3760 // Extract background geometry so the dialog's canvas shows the
3761 // actual board outline + footprint footprints beneath the diff
3762 // bbox rectangles. Theme defaults: muted blue (ref) / gold (comp).
3763 const KICAD_DIFF::DIFF_COLOR_THEME theme;
3764 refGeometry = KICAD_DIFF::ExtractBoardGeometry( *boardA, theme.reference );
3765 compGeometry = KICAD_DIFF::ExtractBoardGeometry( *boardB, theme.comparison );
3766
3767 const wxString labelA = aLabelA.IsEmpty() ? aFileA : aLabelA;
3768 const wxString labelB = aLabelB.IsEmpty() ? aFileB : aLabelB;
3769
3771 parent, labelA, labelB, result, std::move( refGeometry ), std::move( compGeometry ),
3772 [boardA, boardB, color = theme.reference]( WIDGET_DIFF_CANVAS& aCanvas, const KIID_PATH& )
3773 {
3774 KICAD_DIFF::ConfigurePcbDiffCanvasContext( aCanvas, boardA, boardB, color );
3775 } );
3776 dlg.ShowModal();
3777
3779 }
3781 {
3782 std::vector<std::unique_ptr<FOOTPRINT>> ownersA;
3783 std::vector<std::unique_ptr<FOOTPRINT>> ownersB;
3786
3787 if( int rc = loadFootprintLibrarySide( aFileA, ownersA, mapA, true, *m_reporter );
3789 {
3790 return rc;
3791 }
3792
3793 if( int rc = loadFootprintLibrarySide( aFileB, ownersB, mapB, true, *m_reporter );
3795 {
3796 return rc;
3797 }
3798
3799 KICAD_DIFF::FP_LIB_DIFFER differ( mapA, mapB, aFileB );
3800 result = differ.Diff();
3801
3802 const KICAD_DIFF::DIFF_COLOR_THEME theme;
3803 refGeometry = footprintLibraryGeometry( mapA, theme.reference );
3804 compGeometry = footprintLibraryGeometry( mapB, theme.comparison );
3805 break;
3806 }
3808 {
3809 std::unique_ptr<FOOTPRINT> footprintA;
3810 std::unique_ptr<FOOTPRINT> footprintB;
3811
3812 try
3813 {
3814 footprintA = loadFootprintFile( aFileA );
3815 }
3816 catch( const IO_ERROR& ioe )
3817 {
3818 m_reporter->Report( wxString::Format( _( "Failed to load %s: %s\n" ), aFileA, ioe.What() ),
3821 }
3822
3823 try
3824 {
3825 footprintB = loadFootprintFile( aFileB );
3826 }
3827 catch( const IO_ERROR& ioe )
3828 {
3829 m_reporter->Report( wxString::Format( _( "Failed to load %s: %s\n" ), aFileB, ioe.What() ),
3832 }
3833
3836 const wxString nameA = wxFileName( aFileA ).GetName();
3837 const wxString nameB = wxFileName( aFileB ).GetName();
3838 const wxString itemName = !nameB.IsEmpty() ? nameB : nameA;
3839
3840 if( footprintA )
3841 mapA[itemName] = footprintA.get();
3842
3843 if( footprintB )
3844 mapB[itemName] = footprintB.get();
3845
3846 KICAD_DIFF::FP_LIB_DIFFER differ( mapA, mapB, aFileB );
3847 result = differ.Diff();
3848
3849 const KICAD_DIFF::DIFF_COLOR_THEME theme;
3850
3851 if( footprintA )
3852 refGeometry = KICAD_DIFF::ExtractFootprintGeometry( *footprintA, theme.reference );
3853
3854 if( footprintB )
3855 compGeometry = KICAD_DIFF::ExtractFootprintGeometry( *footprintB, theme.comparison );
3856
3857 break;
3858 }
3859 default:
3860 m_reporter->Report( _( "Unsupported document kind for this dispatcher.\n" ), RPT_SEVERITY_ERROR );
3862 }
3863
3864 const wxString labelA = aLabelA.IsEmpty() ? aFileA : aLabelA;
3865 const wxString labelB = aLabelB.IsEmpty() ? aFileB : aLabelB;
3866
3867 DIALOG_KICAD_DIFF dlg( parent, labelA, labelB, result, std::move( refGeometry ), std::move( compGeometry ) );
3868 dlg.ShowModal();
3869
3871}
3872
3873
3874// ============================================================================
3875// JobFpLibMerge: 3-way merge of .pretty footprint libraries.
3876// ============================================================================
3879
3880
3881int PCBNEW_JOBS_HANDLER::runFpLibMerge( const wxString& aAncestor, const wxString& aOurs,
3882 const wxString& aTheirs, const wxString& aOutput,
3883 bool aSingleFile )
3884{
3885 if( aOutput.IsEmpty() )
3886 {
3887 m_reporter->Report( _( "--output is required\n" ), RPT_SEVERITY_ERROR );
3889 }
3890
3891 struct LIB_SIDE
3892 {
3893 std::vector<std::unique_ptr<FOOTPRINT>> owners;
3895 };
3896
3897 LIB_SIDE ancestor, ours, theirs;
3898
3899 // Accept either a `.pretty` directory (library mode) or a single `.kicad_
3900 // mod` file (git's per-file driver mode). Extension autodetection works
3901 // for native invocations, but git's external driver passes temp paths
3902 // (`.merge_file_XXX`) with no extension, so the `--single-file` flag
3903 // overrides on demand.
3904 auto isSingleFile = [&]( const wxString& aPath )
3905 {
3906 if( aSingleFile )
3907 return true;
3908
3909 return wxFileName( aPath ).GetExt() == FILEEXT::KiCadFootprintFileExtension;
3910 };
3911
3912 auto loadSide = [&]( const wxString& aPath, LIB_SIDE& aSide ) -> int
3913 {
3914 try
3915 {
3916 if( isSingleFile( aPath ) )
3917 {
3919 wxString name;
3920 std::unique_ptr<FOOTPRINT> fp( io.ImportFootprint( aPath, name ) );
3921
3922 if( !fp )
3924
3925 // Use the footprint's own item-name (LIB_ID) if set,
3926 // falling back to the file basename. Both sides must
3927 // agree for the differ/applier to align them.
3928 const UTF8& itemName = fp->GetFPID().GetLibItemName();
3929 const wxString key = itemName.empty() ? name : itemName.wx_str();
3930
3931 aSide.map[key] = fp.get();
3932 aSide.owners.push_back( std::move( fp ) );
3934 }
3935
3936 auto loaded = KICAD_DIFF::FP_LIB_DIFFER::LoadLibrary( aPath );
3937 aSide.owners = std::move( loaded.first );
3938 aSide.map = std::move( loaded.second );
3940 }
3941 catch( const IO_ERROR& ioe )
3942 {
3943 m_reporter->Report( wxString::Format( _( "Failed to load %s: %s\n" ), aPath, ioe.What() ),
3945 }
3946 catch( const std::exception& e )
3947 {
3948 m_reporter->Report(
3949 wxString::Format( _( "Failed to load %s: %s\n" ), aPath, wxString::FromUTF8( e.what() ) ),
3951 }
3952
3954 };
3955
3956 if( int rc = loadSide( aAncestor, ancestor ); rc != CLI::EXIT_CODES::SUCCESS )
3957 return rc;
3958
3959 if( int rc = loadSide( aOurs, ours ); rc != CLI::EXIT_CODES::SUCCESS )
3960 return rc;
3961
3962 if( int rc = loadSide( aTheirs, theirs ); rc != CLI::EXIT_CODES::SUCCESS )
3963 return rc;
3964
3965 KICAD_DIFF::FP_LIB_DIFFER ourDiff( ancestor.map, ours.map, aOurs );
3966 KICAD_DIFF::FP_LIB_DIFFER theirDiff( ancestor.map, theirs.map, aTheirs );
3967
3968 KICAD_DIFF::DOCUMENT_DIFF ourDocDiff = ourDiff.Diff();
3969 KICAD_DIFF::DOCUMENT_DIFF theirDocDiff = theirDiff.Diff();
3970
3972 KICAD_DIFF::MERGE_PLAN plan = engine.Plan( ourDocDiff, theirDocDiff );
3973
3974 const KICAD_DIFF::MERGE_PLAN planSnapshot = plan;
3975
3976 KICAD_DIFF::FP_LIB_MERGE_APPLIER applier( ancestor.map, ours.map, theirs.map, std::move( plan ) );
3977 std::vector<std::unique_ptr<FOOTPRINT>> merged = applier.Apply();
3978
3979 // Per-property footprint merge isn't implemented; MERGE_PROPS resolutions
3980 // are downgraded to TAKE_OURS. Surface that as unresolved so the user sees
3981 // a marker instead of silent partial-merge.
3982 const bool hadSilentFallback = applier.GetReport().mergePropsFallback > 0;
3983
3984 const bool singleFileOutput = isSingleFile( aOutput );
3985
3986 // .pretty is a directory; .kicad_mod is a single file. wxFileName parses
3987 // a path ending in `.pretty` as a file with that extension, so library
3988 // mode uses DirName(); single-file mode keeps the file path as-is and
3989 // hands it directly to FootprintSave, which auto-detects .kicad_mod via
3990 // its own extension check.
3991 wxFileName outFn;
3992
3993 if( singleFileOutput )
3994 outFn = wxFileName( aOutput );
3995 else
3996 outFn = wxFileName::DirName( aOutput );
3997
3998 outFn.MakeAbsolute();
3999
4000 // In library mode wxFileName::DirName treats `foo.pretty` as a directory,
4001 // so GetPath() returns the .pretty itself. In single-file mode GetPath()
4002 // returns the file's parent dir. Either way it's the directory we Mkdir
4003 // into.
4004 const wxString outDir = outFn.GetPath();
4005
4006 if( !wxFileName::DirExists( outDir ) && !wxFileName::Mkdir( outDir, wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL ) )
4007 {
4008 m_reporter->Report( wxString::Format( _( "Cannot create output directory %s\n" ), outDir ),
4011 }
4012
4013 try
4014 {
4016
4017 if( singleFileOutput )
4018 {
4019 // Git per-file driver mode: one merged footprint -> one .kicad_mod.
4020 // Multiple survivors would lose data; flag that as an error since
4021 // single-file input by definition has at most one footprint per
4022 // side.
4023 if( merged.size() > 1 )
4024 {
4025 m_reporter->Report( _( "Single-file fp merge produced multiple footprints; refusing to "
4026 "collapse into one .kicad_mod\n" ),
4029 }
4030
4031 if( merged.empty() )
4032 {
4033 // All sides deleted the footprint. Remove the output file if
4034 // it existed, leaving nothing where the merged content would
4035 // have gone.
4036 if( wxFileName::FileExists( outFn.GetFullPath() ) )
4037 wxRemoveFile( outFn.GetFullPath() );
4038 }
4039 else if( wxFileName( outFn.GetFullPath() ).GetExt() == FILEEXT::KiCadFootprintFileExtension )
4040 {
4041 // FootprintSave's .kicad_mod extension autodetection handles
4042 // the write to the path as-given.
4043 io.FootprintSave( outFn.GetFullPath(), merged.front().get(), nullptr );
4044 }
4045 else
4046 {
4047 // Git driver mode: output is an extension-less temp path
4048 // (typically `.merge_file_XXX`). FootprintSave's
4049 // autodetection would treat it as a library directory.
4050 // Format directly via PRETTIFIED_FILE_OUTPUTFORMATTER, the
4051 // same writer the sexpr lib cache uses.
4052 PRETTIFIED_FILE_OUTPUTFORMATTER formatter( outFn.GetFullPath() );
4053 io.SetOutputFormatter( &formatter );
4054 io.Format( merged.front().get() );
4055 formatter.Finish();
4056 }
4057 }
4058 else
4059 {
4060 // Library mode. Footprints in `merged` are the survivors. Any
4061 // footprint already in the output `.pretty` but absent from
4062 // `merged` is a stale leftover from a previous invocation (or a
4063 // resolved DELETE / TAKE_ANCESTOR-with-no-ancestor case). Delete
4064 // those before saving the survivors, otherwise the resolved
4065 // DELETE never propagates to disk.
4066 std::set<wxString> mergedNames;
4067
4068 for( const auto& fp : merged )
4069 {
4070 if( fp )
4071 mergedNames.insert( fp->GetFPID().GetLibItemName() );
4072 }
4073
4074 wxArrayString existing;
4075 io.FootprintEnumerate( existing, outDir, false, nullptr );
4076
4077 for( const wxString& name : existing )
4078 {
4079 if( !mergedNames.count( name ) )
4080 io.FootprintDelete( outDir, name, nullptr );
4081 }
4082
4083 for( const auto& fp : merged )
4084 {
4085 if( !fp )
4086 continue;
4087
4088 const wxString name = fp->GetFPID().GetLibItemName();
4089
4090 if( io.FootprintExists( outDir, name, nullptr ) )
4091 io.FootprintDelete( outDir, name, nullptr );
4092
4093 io.FootprintSave( outDir, fp.get(), nullptr );
4094 }
4095 }
4096 }
4097 catch( const IO_ERROR& ioe )
4098 {
4099 m_reporter->Report( wxString::Format( _( "Failed to save merged footprint library: %s\n" ), ioe.What() ),
4102 }
4103
4104 // The merged library was saved above, so the output is always valid.
4105 if( !planSnapshot.Resolved() || hadSilentFallback )
4106 {
4107 // Conflict count = engine-unresolved ∪ applier-downgraded (deduped, so
4108 // an item that was both unresolved and silently downgraded counts once).
4109 std::set<KIID_PATH> conflicts( planSnapshot.unresolved.begin(), planSnapshot.unresolved.end() );
4110
4111 for( const KIID_PATH& id : applier.GetReport().mergePropsFallbackIds )
4112 conflicts.insert( id );
4113
4114 m_reporter->Report( wxString::Format( _( "Footprint library merge completed with %zu unresolved "
4115 "conflict(s) in %s\n" ),
4116 conflicts.size(), aOutput ),
4119 }
4120
4122}
@ VIEW3D_BOTTOM
Definition 3d_enums.h:77
const char * name
constexpr EDA_IU_SCALE pcbIUScale
Definition base_units.h:121
KIFACE_BASE & Kiface()
Global KIFACE_BASE "get" accessor.
#define RANGE_SCALE_3D
This defines the range that all coord will have to be rendered.
wxString FormatBoardStatisticsJson(const BOARD_STATISTICS_DATA &aData, BOARD *aBoard, const UNITS_PROVIDER &aUnitsProvider, const wxString &aProjectName, const wxString &aBoardName)
void ComputeBoardStatistics(BOARD *aBoard, const BOARD_STATISTICS_OPTIONS &aOptions, BOARD_STATISTICS_DATA &aData)
wxString FormatBoardStatisticsReport(const BOARD_STATISTICS_DATA &aData, BOARD *aBoard, const UNITS_PROVIDER &aUnitsProvider, const wxString &aProjectName, const wxString &aBoardName)
void InitializeBoardStatisticsData(BOARD_STATISTICS_DATA &aData)
BOX2< VECTOR2I > BOX2I
Definition box2.h:918
PROJECTION_TYPE
Definition camera.h:36
static wxString m_DrawingSheetFileName
the name of the drawing sheet file, or empty to use the default drawing sheet
Definition base_screen.h:81
Helper class to handle information needed to display 3D board.
double BiuTo3dUnits() const noexcept
Board integer units To 3D units.
void SetVisibleLayers(const std::bitset< LAYER_3D_END > &aLayers)
std::bitset< LAYER_3D_END > GetVisibleLayers() const
void SetBoard(BOARD *aBoard) noexcept
Set current board to be rendered.
void SetLayerColors(const std::map< int, COLOR4D > &aColors)
EDA_3D_VIEWER_SETTINGS * m_Cfg
std::map< int, COLOR4D > m_ColorOverrides
allows to override color scheme colors
void Set3dCacheManager(S3D_CACHE *aCacheMgr) noexcept
Update the cache manager pointer.
virtual void Push(const wxString &aMessage=wxEmptyString, int aCommitFlags=0) override
Execute the changes.
std::shared_ptr< NET_SETTINGS > m_NetSettings
std::map< int, SEVERITY > m_DRCSeverities
std::shared_ptr< DRC_ENGINE > m_DRCEngine
const VECTOR2I & GetAuxOrigin() const
static std::unique_ptr< BOARD > CreateEmptyBoard(PROJECT *aProject)
static std::unique_ptr< BOARD > Load(const wxString &aFileName, PCB_IO_MGR::PCB_FILE_T aFormat, PROJECT *aProject, const OPTIONS &aOptions)
static bool SaveBoard(wxString &aFileName, BOARD *aBoard, PCB_IO_MGR::PCB_FILE_T aFormat)
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:373
void SetCurrentVariant(const wxString &aVariant)
Definition board.cpp:2880
const PAGE_INFO & GetPageSettings() const
Definition board.h:897
const ZONES & Zones() const
Definition board.h:425
void RecordDRCExclusions()
Scan existing markers and record data from any that are Excluded.
Definition board.cpp:391
TITLE_BLOCK & GetTitleBlock()
Definition board.h:903
const std::map< wxString, wxString > & GetProperties() const
Definition board.h:465
const FOOTPRINTS & Footprints() const
Definition board.h:421
const TRACKS & Tracks() const
Definition board.h:419
const wxString & GetFileName() const
Definition board.h:410
std::vector< PCB_MARKER * > ResolveDRCExclusions(bool aCreateMarkers)
Rebuild DRC markers from the serialized data in BOARD_DESIGN_SETTINGS.
Definition board.cpp:448
wxString GetVariantDescription(const wxString &aVariantName) const
Definition board.cpp:2999
int GetFileFormatVersionAtLoad() const
Definition board.h:521
const PCB_PLOT_PARAMS & GetPlotOptions() const
Definition board.h:900
const wxString GetLayerName(PCB_LAYER_ID aLayer) const
Return the name of a aLayer.
Definition board.cpp:793
wxString GetCurrentVariant() const
Definition board.h:469
PROJECT * GetProject() const
Definition board.h:658
BOARD_DESIGN_SETTINGS & GetDesignSettings() const
Definition board.cpp:1149
const LSET & GetEnabledLayers() const
A proxy function that calls the corresponding function in m_BoardSettings.
Definition board.cpp:1034
void SynchronizeProperties()
Copy the current project's text variables into the boards property cache.
Definition board.cpp:2860
BOX2I ComputeBoundingBox(bool aBoardEdgesOnly=false, bool aPhysicalLayersOnly=false) const
Calculate the bounding box containing all board items (or board edge segments).
Definition board.cpp:2450
void DeleteMARKERs()
Delete all MARKERS from the board.
Definition board.cpp:1836
constexpr const Vec GetCenter() const
Definition box2.h:226
void SetProjection(PROJECTION_TYPE aProjection)
Definition camera.h:202
void RotateY_T1(float aAngleInRadians)
Definition camera.cpp:682
bool Zoom_T1(float aFactor)
Definition camera.cpp:625
bool SetCurWindowSize(const wxSize &aSize)
Update the windows size of the camera.
Definition camera.cpp:567
bool ViewCommand_T1(VIEW3D_TYPE aRequestedView)
Definition camera.cpp:106
void RotateX_T1(float aAngleInRadians)
Definition camera.cpp:676
void SetLookAtPos_T1(const SFVEC3F &aLookAtPos)
Definition camera.h:158
const SFVEC3F & GetLookAtPos_T1() const
Definition camera.h:163
void RotateZ_T1(float aAngleInRadians)
Definition camera.cpp:688
bool ParametersChanged()
Definition camera.cpp:726
Reporter forwarding messages to stdout or stderr as appropriate.
Definition reporter.h:234
COMMIT & Add(EDA_ITEM *aItem, BASE_SCREEN *aScreen=nullptr)
Add a new item to the model.
Definition commit.h:74
static bool GenerateFile(JOB_EXPORT_PCB_IPC2581 &aJob, BOARD *aBoard, PROGRESS_REPORTER *aProgressReporter, REPORTER *aReporter)
static void GenerateODBPPFiles(const JOB_EXPORT_PCB_ODB &aJob, BOARD *aBoard, PCB_EDIT_FRAME *aParentFrame=nullptr, PROGRESS_REPORTER *aProgressReporter=nullptr, REPORTER *aErrorReporter=nullptr)
The dialog to create footprint position files and choose options (one or 2 files, units and force all...
File-compare dialog (Phase 7).
3-way merge resolution dialog (Phase 8).
const KICAD_DIFF::MERGE_PLAN & GetResolvedPlan() const
Returns the plan with the user's resolutions applied.
A dialog to set the plot options and create plot files in various formats.
Definition dialog_plot.h:37
int ShowModal() override
bool WriteJsonReport(const wxString &aFullFileName)
bool WriteTextReport(const wxString &aFullFileName)
Helper to handle drill precision format in excellon files.
static DS_DATA_MODEL & GetTheInstance()
Return the instance of DS_DATA_MODEL used in the application.
void SetSheetPath(const std::string &aSheetPath)
Set the sheet path displayed in the title block.
void SetVariantName(const std::string &aVariant)
Set the current variant name and description to be shown on the drawing sheet.
void SetVariantDesc(const std::string &aVariantDesc)
void SetSheetName(const std::string &aSheetName)
Set the sheet name displayed in the title block.
void SetIsFirstPage(bool aIsFirstPage)
Change if this is first page.
void SetFileName(const std::string &aFileName)
Set the file name displayed in the title block.
LAYER_PRESET_3D * FindPreset(const wxString &aName)
std::vector< LAYER_PRESET_3D > m_LayerPresets
void SetFlags(EDA_ITEM_FLAGS aMask)
Definition eda_item.h:152
virtual void SetParent(EDA_ITEM *aParent)
Definition eda_item.cpp:89
Create Excellon drill, drill map, and drill report files.
void SetFormat(bool aMetric, ZEROS_FMT aZerosFmt=DECIMAL_FORMAT, int aLeftDigits=0, int aRightDigits=0)
Initialize internal parameters to match the given format.
bool CreateDrillandMapFilesSet(const wxString &aPlotDirectory, bool aGenDrill, bool aGenMap, REPORTER *aReporter=nullptr)
Create the full set of Excellon drill file for the board.
void SetOptions(bool aMirror, bool aMinimalHeader, const VECTOR2I &aOffset, bool aMerge_PTH_NPTH)
Initialize internal parameters to match drill options.
void SetRouteModeForOvalHoles(bool aUseRouteModeForOvalHoles)
wxString m_outputFile
Wrapper to expose an API for writing VRML files, without exposing all the many structures used in the...
Definition export_vrml.h:33
bool ExportVRML_File(PROJECT *aProject, wxString *aMessages, const wxString &aFullFileName, double aMMtoWRMLunit, bool aIncludeUnspecified, bool aIncludeDNP, bool aExport3DFiles, bool aUseRelativePaths, const wxString &a3D_Subdir, double aXRef, double aYRef)
Exports the board and its footprint shapes 3D (vrml files only) as a vrml file.
Provide an extensible class to resolve 3D model paths.
An interface to the global shared library manager that is schematic-specific and linked to one projec...
void SetPosition(const VECTOR2I &aPos) override
void SetLink(const KIID &aLink)
Definition footprint.h:1176
void SetOrientation(const EDA_ANGLE &aNewAngle)
EDA_ITEM * Clone() const override
Invoke a function on all children.
std::deque< PAD * > & Pads()
Definition footprint.h:375
const LIB_ID & GetFPID() const
Definition footprint.h:441
void SetPath(const wxString &aPath)
void Save(FOOTPRINT *aFootprintFilter=nullptr)
Save the footprint cache or a single footprint from it to disk.
boost::ptr_map< wxString, FP_CACHE_ENTRY > & GetFootprints()
Export board to GenCAD file format.
void UseIndividualShapes(bool aUnique)
Make pad shapes unique.
void UsePinNamesUnique(bool aUnique)
Make pin names unique.
void StoreOriginCoordsInFile(bool aStore)
Store origin coordinate in GenCAD file.
void FlipBottomPads(bool aFlip)
Flip pad shapes on the bottom side.
void SetPlotOffet(VECTOR2I aOffset)
Set the coordinates offset when exporting items.
bool WriteFile(const wxString &aFullFileName)
Export a GenCAD file.
void SetMapFileFormat(PLOT_FORMAT aMapFmt)
Initialize the format for the drill map file.
bool GenDrillReportFile(const wxString &aFullFileName, REPORTER *aReporter=nullptr)
Create a plain text report file giving a list of drill values and drill count for through holes,...
GERBER_JOBFILE_WRITER is a class used to create Gerber job file a Gerber job file stores info to make...
bool CreateJobFile(const wxString &aFullFilename)
Creates a Gerber job file.
void AddGbrFile(PCB_LAYER_ID aLayer, wxString &aFilename)
add a gerber file name and type in job file list
virtual bool EndPlot() override
Used to create Gerber drill files.
bool CreateDrillandMapFilesSet(const wxString &aPlotDirectory, bool aGenDrill, bool aGenMap, bool aGenTenting, REPORTER *aReporter=nullptr)
Create the full set of Excellon drill file for the board filenames are computed from the board name,...
void SetOptions(const VECTOR2I &aOffset)
Initialize internal parameters to match drill options.
void SetFormat(int aRightDigits=6)
Initialize internal parameters to match the given format.
Hold an error message and may be used when throwing exceptions containing meaningful error messages.
virtual const wxString What() const
A composite of Problem() and Where()
Wrapper to expose an API for writing IPC-D356 files.
Definition export_d356.h:54
bool Write(const wxString &aFilename)
Generates and writes the netlist to a given path.
wxString m_inputB
Comparison document (file or directory)
wxString m_inputA
Reference document (file or directory)
void Register(const std::string &aJobTypeName, std::function< int(JOB *job)> aHandler, std::function< bool(JOB *job, wxWindow *aParent)> aConfigHandler)
JOB_DISPATCHER(KIWAY *aKiway)
PROGRESS_REPORTER * m_progressReporter
REPORTER * m_reporter
JOB_EXPORT_PCB_3D::FORMAT m_format
EXPORTER_STEP_PARAMS m_3dparams
Despite the name; also used for other formats.
wxString GetSettingsDialogTitle() const override
ODB_COMPRESSION m_compressionMode
@ ALL_LAYERS_ONE_FILE
DEPRECATED MODE.
bool m_pdfSingle
This is a hack to deal with cli having the wrong behavior We will deprecate out the wrong behavior,...
GEN_MODE m_pdfGenMode
The background color specified in a hex string.
LSEQ m_plotOnAllLayersSequence
Used by SVG & PDF.
std::optional< wxString > m_argLayers
std::optional< wxString > m_argCommonLayers
LSEQ m_plotLayerSequence
Layers to include on all individual layer prints.
wxString m_variant
Variant name for variant-aware filtering.
void SetDefaultOutputPath(const wxString &aReferenceName)
wxString m_libraryPath
wxString m_outputLibraryPath
Job: diff two PCB files end-to-end via PCB_DIFFER.
bool m_saveBoard
Definition job_pcb_drc.h:36
bool m_reportAllTrackErrors
Definition job_pcb_drc.h:32
bool m_refillZones
Definition job_pcb_drc.h:35
Job to import a non-KiCad PCB file to KiCad format.
std::map< wxString, wxString > m_layerMap
Explicit overrides from source layer name to KiCad layer name (canonical board-file name,...
wxString m_reportFile
IMPORT_REPORT_FORMAT m_reportFormat
wxString m_inputFile
VECTOR3D m_lightBottomIntensity
VECTOR3D m_lightTopIntensity
VECTOR3D m_lightCameraIntensity
VECTOR3D m_rotation
wxString m_filename
bool m_useBoardStackupColors
VECTOR3D m_lightSideIntensity
std::string m_appearancePreset
bool m_exitCodeViolations
Definition job_rc.h:52
int m_severity
Definition job_rc.h:49
UNITS m_units
Definition job_rc.h:48
OUTPUT_FORMAT m_format
Definition job_rc.h:50
wxString m_filename
Definition job_rc.h:47
An simple container class that lets us dispatch output jobs to kifaces.
Definition job.h:184
wxString ResolveOutputPath(const wxString &aPath, bool aPathIsDirectory, PROJECT *aProject) const
Definition job.cpp:100
void AddOutput(wxString aOutputPath)
Definition job.h:216
wxString GetFullOutputPath(PROJECT *aProject) const
Returns the full output path for the job, taking into account the configured output path,...
Definition job.cpp:150
wxString GetWorkingOutputPath() const
Returns the working output path for the job, if one has been set.
Definition job.h:246
wxString GetConfiguredOutputPath() const
Returns the configured output path for the job.
Definition job.h:235
void SetTitleBlock(const TITLE_BLOCK &aTitleBlock)
Definition job.h:204
void SetWorkingOutputPath(const wxString &aPath)
Sets a transient output path for the job, it takes priority over the configured output path when GetF...
Definition job.h:241
const std::map< wxString, wxString > & GetVarOverrides() const
Definition job.h:197
JSON_SETTINGS_INTERNALS * Internals()
virtual bool Store()
Stores the current parameters into the JSON document represented by this object Note: this doesn't do...
Diff two .pretty footprint library directories.
static std::pair< std::vector< std::unique_ptr< FOOTPRINT > >, FOOTPRINT_MAP > LoadLibrary(const wxString &aPrettyPath)
Load a .pretty directory into a FOOTPRINT_MAP.
DOCUMENT_DIFF Diff() override
Produce a DOCUMENT_DIFF of the inputs the concrete differ was constructed with.
std::map< wxString, const FOOTPRINT * > FOOTPRINT_MAP
Three-way merge plan generator.
MERGE_PLAN Plan(const DOCUMENT_DIFF &aAncestorOurs, const DOCUMENT_DIFF &aAncestorTheirs) const
Plan the merge given the canonical pair of diffs.
const REPORT & GetReport() const
std::vector< std::unique_ptr< ITEM > > Apply()
Diff two already-parsed BOARDs and produce a DOCUMENT_DIFF.
Definition pcb_differ.h:52
DOCUMENT_DIFF Diff() override
Produce a DOCUMENT_DIFF of the inputs the concrete differ was constructed with.
Materialize a MERGE_PLAN into a real merged BOARD.
std::unique_ptr< BOARD > Apply()
Produce the merged board.
const REPORT & GetReport() const
Read the new s-expression based KiCad netlist format.
virtual void LoadNetlist() override
Load the contents of the netlist file into aNetlist.
A color representation with 4 components: red, green, blue, alpha.
Definition color4d.h:101
A minimalistic software bus for communications between various DLLs/DSOs (DSOs) within the same KiCad...
Definition kiway.h:311
virtual KIWAY_PLAYER * Player(FRAME_T aFrameType, bool doCreate=true, wxTopLevelWindow *aParent=nullptr)
Return the KIWAY_PLAYER* given a FRAME_T.
Definition kiway.cpp:398
@ FACE_SCH
eeschema DSO
Definition kiway.h:318
Plugin class for import plugins that support remappable layers.
void AsyncLoad()
Loads all available libraries for this adapter type in the background.
const UTF8 & GetLibItemName() const
Definition lib_id.h:98
LSEQ is a sequence (and therefore also a set) of PCB_LAYER_IDs.
Definition lseq.h:47
LSET is a set of PCB_LAYER_IDs.
Definition lset.h:37
static const LSET & AllCuMask()
return AllCuMask( MAX_CU_LAYERS );
Definition lset.cpp:604
LSEQ UIOrder() const
Return the copper, technical and user layers in the order shown in layer widget.
Definition lset.cpp:739
LSEQ SeqStackupForPlotting() const
Return the sequence that is typical for a bottom-to-top stack-up.
Definition lset.cpp:400
static LSET AllNonCuMask()
Return a mask holding all layer minus CU layers.
Definition lset.cpp:623
LSEQ Seq(const LSEQ &aSequence) const
Return an LSEQ from the union of this LSET and a desired sequence.
Definition lset.cpp:309
static LSET AllCuMask(int aCuLayerCount)
Return a mask holding the requested number of Cu PCB_LAYER_IDs.
Definition lset.cpp:595
static const LSET & AllLayersMask()
Definition lset.cpp:637
static const LSET & InternalCuMask()
Return a complete set of internal copper layers which is all Cu layers except F_Cu and B_Cu.
Definition lset.cpp:573
static wxString Name(PCB_LAYER_ID aLayerId)
Return the fixed name association with aLayerId.
Definition lset.cpp:184
@ MARKER_DRAWING_SHEET
Definition marker_base.h:52
bool SaveToFile(const wxString &aDirectory="", bool aForce=false) override
Calls Store() and then saves the JSON document contents into the parent JSON_SETTINGS.
void CopyFrom(NET_SETTINGS &aOther)
Deep-copy the persisted contents of aOther into this instance.
Definition pad.h:61
static bool EnsurePathExists(const wxString &aPath, bool aPathToFile=false)
Attempts to create a given path if it does not exist.
Definition paths.cpp:518
int RunMerge(KICAD_DIFF::DOC_KIND aKind, const wxString &aAncestor, const wxString &aOurs, const wxString &aTheirs, const wxString &aOutput, bool aInteractive, bool aSingleFile, REPORTER *aReporter)
Non-job entry points (reached via the kiface KIFACE_MERGE_DOCUMENT / KIFACE_OPEN_DIFF_DIALOG function...
DS_PROXY_VIEW_ITEM * getDrawingSheetProxyView(BOARD *aBrd)
wxString resolveJobOutputPath(JOB *aJob, BOARD *aBoard, const wxString *aDrawingSheet=nullptr)
int runPcbMerge(const wxString &aAncestor, const wxString &aOurs, const wxString &aTheirs, const wxString &aOutput, bool aInteractive)
void loadOverrideDrawingSheet(BOARD *brd, const wxString &aSheetPath)
PCBNEW_JOBS_HANDLER(KIWAY *aKiway)
TOOL_MANAGER * getToolManager(BOARD *aBrd)
BOARD * getBoard(const wxString &aPath=wxEmptyString)
int runFpLibMerge(const wxString &aAncestor, const wxString &aOurs, const wxString &aTheirs, const wxString &aOutput, bool aSingleFile)
std::unique_ptr< TOOL_MANAGER > m_toolManager
int OpenDiffDialog(KICAD_DIFF::DOC_KIND aKind, const wxString &aFileA, const wxString &aFileB, const wxString &aLabelA, const wxString &aLabelB, wxWindow *aParent, REPORTER *aReporter)
LSEQ convertLayerArg(wxString &aLayerString, BOARD *aBoard) const
void ClearCachedBoard()
Clear the cached CLI board so the next job reloads from the current project.
int doFpExportSvg(JOB_FP_EXPORT_SVG *aSvgJob, const FOOTPRINT *aFootprint)
BOARD * GetBoard() const
The main frame for Pcbnew.
A #PLUGIN derivation for saving and loading Pcbnew s-expression formatted files.
FOOTPRINT * ImportFootprint(const wxString &aFootprintPath, wxString &aFootprintNameOut, const std::map< std::string, UTF8 > *aProperties=nullptr) override
Load a single footprint from aFootprintPath and put its name in aFootprintNameOut.
void FootprintDelete(const wxString &aLibraryPath, const wxString &aFootprintName, const std::map< std::string, UTF8 > *aProperties=nullptr) override
Delete aFootprintName from the library at aLibraryPath.
void FootprintEnumerate(wxArrayString &aFootprintNames, const wxString &aLibraryPath, bool aBestEfforts, const std::map< std::string, UTF8 > *aProperties=nullptr) override
Return a list of footprint names contained within the library at aLibraryPath.
bool FootprintExists(const wxString &aLibraryPath, const wxString &aFootprintName, const std::map< std::string, UTF8 > *aProperties=nullptr) override
Check for the existence of a footprint.
void FootprintSave(const wxString &aLibraryPath, const FOOTPRINT *aFootprint, const std::map< std::string, UTF8 > *aProperties=nullptr) override
Write aFootprint to an existing library located at aLibraryPath.
void SaveBoard(const wxString &aFileName, BOARD *aBoard, const std::map< std::string, UTF8 > *aProperties=nullptr) override
Write aBoard to a storage file in a format that this PCB_IO implementation knows about or it can be u...
void Format(const BOARD_ITEM *aItem) const
Output aItem to aFormatter in s-expression format.
void SetOutputFormatter(OUTPUTFORMATTER *aFormatter)
static bool ConvertLibrary(const std::map< std::string, UTF8 > &aOldFileProps, const wxString &aOldFilePath, const wxString &aNewFilePath, REPORTER *aReporter)
Convert a schematic symbol library to the latest KiCad format.
PCB_FILE_T
The set of file types that the PCB_IO_MGR knows about, and for which there has been a plugin written,...
Definition pcb_io_mgr.h:52
@ KICAD_SEXP
S-expression Pcbnew file format.
Definition pcb_io_mgr.h:54
@ ALTIUM_DESIGNER
Definition pcb_io_mgr.h:59
@ CADSTAR_PCB_ARCHIVE
Definition pcb_io_mgr.h:60
@ PCB_FILE_UNKNOWN
0 is not a legal menu id on Mac
Definition pcb_io_mgr.h:53
static PCB_IO * FindPlugin(PCB_FILE_T aFileType)
Return a #PLUGIN which the caller can use to import, export, save, or load design documents.
static PCB_FILE_T FindPluginTypeFromBoardPath(const wxString &aFileName, int aCtl=0)
Return a plugin type given a path for a board file.
static PCB_FILE_T GuessPluginTypeFromLibPath(const wxString &aLibPath, int aCtl=0)
Return a plugin type given a footprint library's libPath.
static const wxString ShowType(PCB_FILE_T aFileType)
Return a brief name for a plugin given aFileType enum.
static void PlotJobToPlotOpts(PCB_PLOT_PARAMS &aOpts, JOB_EXPORT_PCB_PLOT *aJob, REPORTER &aReporter)
Translate a JOB to PCB_PLOT_PARAMS.
bool Plot(const wxString &aOutputPath, const LSEQ &aLayersToPlot, const LSEQ &aCommonLayers, bool aUseGerberFileExtensions, bool aOutputPathIsSingle=false, std::optional< wxString > aLayerName=std::nullopt, std::optional< wxString > aSheetName=std::nullopt, std::optional< wxString > aSheetPath=std::nullopt, std::vector< wxString > *aOutputFiles=nullptr)
static void BuildPlotFileName(wxFileName *aFilename, const wxString &aOutputDir, const wxString &aSuffix, const wxString &aExtension)
Complete a plot filename.
Parameters and options when plotting/printing a board.
LSEQ GetPlotOnAllLayersSequence() const
void SetSkipPlotNPTH_Pads(bool aSkip)
void SetLayerSelection(const LSET &aSelection)
void SetPlotOnAllLayersSequence(LSEQ aSeq)
void SetPlotFrameRef(bool aFlag)
void SetPlotPadNumbers(bool aFlag)
LSET GetLayerSelection() const
void SetMirror(bool aFlag)
bool GetSketchPadsOnFabLayers() const
void SetSvgFitPageToBoard(int aSvgFitPageToBoard)
bool GetUseGerberProtelExtensions() const
virtual SETTINGS_MANAGER & GetSettingsManager() const
Definition pgm_base.h:124
Used to create Gerber drill files.
const wxString GetPlaceFileName(const wxString &aFullBaseFilename, PCB_LAYER_ID aLayer) const
void SetVariant(const wxString &aVariant)
Set the variant name for variant-aware filtering.
int CreatePlaceFile(const wxString &aFullFilename, PCB_LAYER_ID aLayer, bool aIncludeBrdEdges, bool aExcludeDNP, bool aExcludeBOM)
Create an pnp gerber file.
The ASCII format of the kicad place file is:
static wxString DecorateFilename(const wxString &aBaseName, bool aFront, bool aBack)
std::string GenPositionData()
build a string filled with the position data
void SetVariant(const wxString &aVariant)
Set the variant name for variant-aware export.
Base plotter engine class.
Definition plotter.h:133
virtual bool EndPlot()=0
bool Finish() override
Runs prettification over the buffered bytes, writes them to the sibling temp file,...
Definition richio.cpp:696
The backing store for a PROJECT, in JSON format.
wxString m_BoardDrawingSheetFile
PcbNew params.
static S3D_CACHE * Get3DCacheManager(PROJECT *aProject, bool updateProjDir=false)
Return a pointer to an instance of the 3D cache manager.
static FOOTPRINT_LIBRARY_ADAPTER * FootprintLibAdapter(PROJECT *aProject)
Container for project specific data.
Definition project.h:62
virtual const wxString GetProjectFullName() const
Return the full path and name of the project.
Definition project.cpp:177
virtual const wxString GetProjectPath() const
Return the full path of the project.
Definition project.cpp:183
virtual const wxString GetProjectName() const
Return the short name of the project.
Definition project.cpp:195
virtual PROJECT_FILE & GetProjectFile() const
Definition project.h:200
bool Redraw(bool aIsMoving, REPORTER *aStatusReporter, REPORTER *aWarningReporter) override
Redraw the view.
void SetCurWindowSize(const wxSize &aSize) override
Before each render, the canvas will tell the render what is the size of its windows,...
A pure virtual class used to derive REPORTER objects from.
Definition reporter.h:71
virtual REPORTER & Report(const wxString &aText, SEVERITY aSeverity=RPT_SEVERITY_UNDEFINED)
Report a string with a given severity.
Definition reporter.h:100
RAII class that sets an value at construction and resets it to the original value at destruction.
bool SaveProjectCopy(const wxString &aFullPath, PROJECT *aProject=nullptr)
Save a copy of the current project under the given path.
bool LoadProject(const wxString &aFullPath, bool aSetActive=true)
Load a project or sets up a new project with a specified path.
PROJECT * GetProject(const wxString &aFullPath) const
Retrieve a loaded project by name.
PROJECT & Prj() const
A helper while we are not MDI-capable – return the one and only project.
Is a LINE_READER that reads from a multiline 8 bit wide std::string.
Definition richio.h:222
TOOL_MANAGER * GetToolManager() const
Return the MVC controller.
Master controller class:
TOOL_BASE * FindTool(int aId) const
Search for a tool with given ID.
void RegisterTool(TOOL_BASE *aTool)
Add a tool to the manager set and sets it up.
void SetEnvironment(EDA_ITEM *aModel, KIGFX::VIEW *aView, KIGFX::VIEW_CONTROLS *aViewControls, APP_SETTINGS_BASE *aSettings, TOOLS_HOLDER *aFrame)
Set the work environment (model, view, view controls and the parent window).
void Pan_T1(const SFVEC3F &aDeltaOffsetInc) override
void SetT0_and_T1_current_T() override
This will set T0 and T1 with the current values.
void Interpolate(float t) override
It will update the matrix to interpolate between T0 and T1 values.
An 8 bit string that is assuredly encoded in UTF8, and supplies special conversion support to and fro...
Definition utf8.h:67
bool empty() const
Definition utf8.h:105
wxString wx_str() const
Definition utf8.cpp:41
GAL-backed canvas for visualizing a KICAD_DIFF::DIFF_SCENE.
Handle actions specific to filling copper zones.
wxString GetDefaultPlotExtension(PLOT_FORMAT aFormat)
Return the default plot extension for a format.
void DisplayErrorMessage(wxWindow *aParent, const wxString &aText, const wxString &aExtraInfo)
Display an error message with aMessage.
Definition confirm.cpp:217
This file is part of the common library.
static DRILL_PRECISION precisionListForInches(2, 4)
static DRILL_PRECISION precisionListForMetric(3, 3)
#define _(s)
#define FOLLOW_PLOT_SETTINGS
#define FOLLOW_PCB
static constexpr EDA_ANGLE ANGLE_0
Definition eda_angle.h:411
#define IS_NEW
New item, just created.
EDA_UNITS
Definition eda_units.h:44
static FILENAME_RESOLVER * resolver
@ FRAME_PCB_EDITOR
Definition frame_type.h:38
@ FRAME_SCH
Definition frame_type.h:30
Classes used in drill files, map files and report files generation.
Classes used in drill files, map files and report files generation.
Classes used to generate a Gerber job file in JSON.
Classes used in place file generation.
static const std::string LegacySchematicFileExtension
static const std::string BrepFileExtension
static const std::string SymbolLibraryTableFileName
static const std::string JpegFileExtension
static const std::string GerberJobFileExtension
static const std::string GerberFileExtension
static const std::string XaoFileExtension
static const std::string ReportFileExtension
static const std::string GltfBinaryFileExtension
static const std::string ProjectFileExtension
static const std::string PngFileExtension
static const std::string FootprintPlaceFileExtension
static const std::string JsonFileExtension
static const std::string KiCadSchematicFileExtension
static const std::string CsvFileExtension
static const std::string U3DFileExtension
static const std::string PdfFileExtension
static const std::string Ipc2581FileExtension
static const std::string FootprintLibraryTableFileName
static const std::string GencadFileExtension
static const std::string StlFileExtension
static const std::string IpcD356FileExtension
static const std::string PlyFileExtension
static const std::string StepFileExtension
static const std::string SVGFileExtension
static const std::string DesignRulesFileExtension
static const std::string VrmlFileExtension
static const std::string KiCadFootprintFileExtension
static const std::string ArchiveFileExtension
static const std::string KiCadPcbFileExtension
std::unique_ptr< T > IO_RELEASER
Helper to hold and release an IO_BASE object when exceptions are thrown.
Definition io_mgr.h:33
void WriteImportReport(REPORTER *aReporter, IMPORT_REPORT_FORMAT aFormat, const wxString &aReportFile, const IMPORT_REPORT_DATA &aData)
Emit an import report in the requested format to aReportFile, or to aReporter (at INFO severity) when...
wxString DefaultImportOutputPath(const wxString &aInputFile, const wxString &aKiCadExt)
Build the default output path for an import by swapping the input file's extension for the given KiCa...
@ KIFACE_NETLIST_SCHEMATIC
Definition kiface_ids.h:38
KIID niluuid(0)
#define KICTL_KICAD_ONLY
chosen file is from KiCad according to user
wxString LayerName(int aLayer)
Returns the default display name for a given layer.
Definition layer_id.cpp:31
@ LAYER_3D_BACKGROUND_TOP
Definition layer_ids.h:551
@ LAYER_3D_BACKGROUND_BOTTOM
Definition layer_ids.h:550
PCB_LAYER_ID
A quick note on layer IDs:
Definition layer_ids.h:56
@ F_CrtYd
Definition layer_ids.h:112
@ B_Adhes
Definition layer_ids.h:99
@ F_Paste
Definition layer_ids.h:100
@ F_Adhes
Definition layer_ids.h:98
@ B_Mask
Definition layer_ids.h:94
@ B_Cu
Definition layer_ids.h:61
@ F_Mask
Definition layer_ids.h:93
@ B_Paste
Definition layer_ids.h:101
@ F_Fab
Definition layer_ids.h:115
@ F_SilkS
Definition layer_ids.h:96
@ B_CrtYd
Definition layer_ids.h:111
@ UNDEFINED_LAYER
Definition layer_ids.h:57
@ B_SilkS
Definition layer_ids.h:97
@ F_Cu
Definition layer_ids.h:60
@ B_Fab
Definition layer_ids.h:114
This file contains miscellaneous commonly used macros and functions.
@ MAIL_SCH_GET_NETLIST
Definition mail_type.h:46
static const int ERR_ARGS
Definition exit_codes.h:31
static const int OK
Definition exit_codes.h:30
static const int ERR_RC_VIOLATIONS
Rules check violation count was greater than 0.
Definition exit_codes.h:37
static const int ERR_INVALID_INPUT_FILE
Definition exit_codes.h:33
static const int SUCCESS
Definition exit_codes.h:29
static const int ERR_INVALID_OUTPUT_CONFLICT
Definition exit_codes.h:34
static const int ERR_UNKNOWN_FILE_FORMAT
No plugin for the requested face recognized the input file format.
Definition exit_codes.h:42
static const int ERR_UNKNOWN
Definition exit_codes.h:32
void CollectChangeBBoxes(const DOCUMENT_DIFF &aDiff, std::map< KIID_PATH, BOX2I > &aOut)
Walk a DOCUMENT_DIFF and populate a (KIID_PATH → BOX2I) map with each changed item's bbox,...
const wxString DOC_PROP_NET_CLASSES
LIB_MERGE_APPLIER< FOOTPRINT > FP_LIB_MERGE_APPLIER
Footprint-library 3-way merge applier. See LIB_MERGE_APPLIER for behavior.
void AppendGeometry(DOCUMENT_GEOMETRY &aDst, DOCUMENT_GEOMETRY &&aSrc)
Move all primitives from aSrc into aDst.
DOCUMENT_GEOMETRY ExtractFootprintGeometry(const FOOTPRINT &aFootprint, const KIGFX::COLOR4D &aColor)
Extract drawable context geometry from a single FOOTPRINT.
DIFF_EMIT_OPTIONS MakeEmitOptions(const JOB_DIFF_BASE &aJob, const wxString &aLabelA, const wxString &aLabelB)
Build a DIFF_EMIT_OPTIONS pre-filled from the job's format, resolved output path and the supplied per...
const wxString DOC_PROP_DRAWING_SHEET
DOC_KIND
Document type a diff/merge entry point should route to, derived from a file path's extension.
int EmitDiffResult(const DOCUMENT_DIFF &aResult, const DIFF_EMIT_OPTIONS &aOptions, int aDiffExitCode, REPORTER &aReporter)
Emit a computed DOCUMENT_DIFF in the requested format.
const wxString DOC_PROP_DRC_SEVERITIES
DOCUMENT_GEOMETRY ExtractBoardGeometry(const BOARD &aBoard, const KIGFX::COLOR4D &aColor)
Extract a coarse outline of a BOARD into a DOCUMENT_GEOMETRY for use as background context in DIFF_SC...
bool ApplyProjectFilePatches(const wxString &aOutputProPath, const nlohmann::json &aSource, const std::set< wxString > &aDocProps, DOC_KIND aKind)
Higher-level orchestrator: load the existing aOutputProPath as JSON (or start from aSource if the fil...
int DiffExitCode(const DOCUMENT_DIFF &aResult)
Map a computed diff onto its CLI exit code – SUCCESS when empty, otherwise ERR_RC_VIOLATIONS.
#define SEXPR_BOARD_FILE_VERSION
Current s-expression file format version. 2 was the last legacy format version.
#define CTL_FOR_LIBRARY
Format output for a footprint library instead of clipboard or BOARD.
static PCB_LAYER_ID resolveKiCadLayerName(const wxString &aName)
static int loadFootprintLibrarySide(const wxString &aPath, std::vector< std::unique_ptr< FOOTPRINT > > &aOwners, KICAD_DIFF::FP_LIB_DIFFER::FOOTPRINT_MAP &aMap, bool aAllowEmpty, REPORTER &aReporter)
static DRILL_PRECISION precisionListForInches(2, 4)
static DRILL_PRECISION precisionListForMetric(3, 3)
static SCRATCH_DOC< BOARD > loadScratchBoard(SETTINGS_MANAGER &aMgr, const wxString &aPath, bool aInitializeAfterLoad=true)
static KICAD_DIFF::DOCUMENT_GEOMETRY footprintLibraryGeometry(const KICAD_DIFF::FP_LIB_DIFFER::FOOTPRINT_MAP &aMap, const KIGFX::COLOR4D &aColor)
const wxString GetGerberProtelExtension(int aLayer)
Definition pcbplot.cpp:39
PLOTTER * StartPlotBoard(BOARD *aBoard, const PCB_PLOT_PARAMS *aPlotOpts, int aLayer, const wxString &aLayerName, const wxString &aFullFileName, const wxString &aSheetName, const wxString &aSheetPath, const wxString &aPageName=wxT("1"), const wxString &aPageNumber=wxEmptyString, const int aPageCount=1)
Open a new plotfile using the options (and especially the format) specified in the options and prepar...
void PlotBoardLayers(BOARD *aBoard, PLOTTER *aPlotter, const LSEQ &aLayerSequence, const PCB_PLOT_PARAMS &aPlotOptions)
Plot a sequence of board layer IDs.
PGM_BASE & Pgm()
The global program "get" accessor.
see class PGM_BASE
PLOT_FORMAT
The set of supported output plot formats.
Definition plotter.h:60
Plotting engines similar to ps (PostScript, Gerber, svg)
@ RPT_SEVERITY_WARNING
@ RPT_SEVERITY_ERROR
@ RPT_SEVERITY_INFO
@ RPT_SEVERITY_ACTION
#define SKIP_SET_DIRTY
Definition sch_commit.h:38
#define SKIP_UNDO
Definition sch_commit.h:36
SCRATCH_DOC< DOC > LoadScratchDoc(SETTINGS_MANAGER &aMgr, const wxString &aDocPath, Loader aLoader, ClearFn aClearFn)
Construct a SCRATCH_DOC by loading a project non-active and then handing it to the caller's document ...
T * GetAppSettings(const char *aFilename)
const int scale
MODEL3D_FORMAT_TYPE fileType(const char *aFileName)
#define TO_UTF8(wxstring)
Convert a wxString to a UTF8 encoded C string for all wxWidgets build modes.
Phase 8 context for the conflict canvas.
std::vector< KIGFX::COLOR4D > raytrace_lightColor
Describes an imported layer and how it could be mapped to KiCad Layers.
KIGFX::COLOR4D reference
Default color for source-document context geometry.
Definition diff_scene.h:287
Describes how a computed DOCUMENT_DIFF should be emitted by a diff job.
std::function< DOCUMENT_GEOMETRY(const KIGFX::COLOR4D &)> comparisonGeometry
DOC_KIND docKind
Source document type, propagated onto the scene so the PNG/SVG renderer sizes its viewport with the m...
std::function< DOCUMENT_GEOMETRY(const KIGFX::COLOR4D &)> referenceGeometry
The full set of changes between two parsed documents of one type.
Aggregate of background geometry extracted from one source document.
Definition diff_scene.h:163
Result of planning a 3-way merge.
std::size_t ConflictCount() const
std::vector< KIID_PATH > unresolved
Report on the application after Apply() runs.
wxString fpLibTable
fp-lib-table content the applier resolved.
VALIDATION_REPORT validation
Post-apply validator pipeline result (refdes uniqueness, connectivity-rebuild-ack,...
bool projectFileTouched
True iff the applier resolved state that lives in the .kicad_pro or a project sibling file.
wxString customDrcRules
Custom DRC rules (.kicad_dru) content the applier resolved.
wxString symLibTable
sym-lib-table content the applier resolved.
wxString drawingSheetFile
Drawing sheet path the applier resolved (from a doc-level resolution).
Outcome of a single validator run.
std::vector< VALIDATION_FAILURE > failures
Implement a participant in the KIWAY alchemy.
Definition kiway.h:152
Move-only RAII wrapper for "load a KiCad document into a non-active scratch PROJECT and clean up afte...
std::unique_ptr< DOC > doc
static void checkParity(CREEPAGE_PARITY_FIXTURE &aFixture, const std::string &aBoard)
std::string netlist
std::string path
IbisParser parser & reporter
wxString result
Test unit parsing edge cases and error handling.
Declaration for a track ball camera.
double DEG2RAD(double deg)
Definition trigo.h:162
@ PCB_VIA_T
class PCB_VIA, a via (like a track segment on a copper layer)
Definition typeinfo.h:90
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683
Definition of file extensions used in Kicad.
glm::vec3 SFVEC3F
Definition xv3d_types.h:40
#define ZONE_FILLER_TOOL_NAME