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
2775 aJob->AddOutput( outPath );
2776
2777 if( m_reporter->HasMessageOfSeverity( RPT_SEVERITY_ERROR ) )
2779
2781}
2782
2784{
2785 JOB_PCB_UPGRADE* job = dynamic_cast<JOB_PCB_UPGRADE*>( aJob );
2786
2787 if( job == nullptr )
2789
2790 bool shouldSave = job->m_force;
2791
2792 try
2793 {
2795 BOARD* brd = getBoard( job->m_filename );
2797 shouldSave = true;
2798
2799 if( shouldSave )
2800 {
2801 pi->SaveBoard( brd->GetFileName(), brd );
2802 m_reporter->Report( _( "Successfully saved board file using the latest format\n" ), RPT_SEVERITY_INFO );
2803 }
2804 else
2805 {
2806 m_reporter->Report( _( "Board file was not updated\n" ), RPT_SEVERITY_ERROR );
2807 }
2808 }
2809 catch( const IO_ERROR& ioe )
2810 {
2811 wxString msg =
2812 wxString::Format( _( "Error saving board file '%s'.\n%s" ), job->m_filename, ioe.What().GetData() );
2813 m_reporter->Report( msg, RPT_SEVERITY_ERROR );
2815 }
2816
2818}
2819
2820// Most job handlers need to align the running job with the board before resolving any
2821// output paths with variables in them like ${REVISION}.
2822wxString PCBNEW_JOBS_HANDLER::resolveJobOutputPath( JOB* aJob, BOARD* aBoard, const wxString* aDrawingSheet )
2823{
2824 aJob->SetTitleBlock( aBoard->GetTitleBlock() );
2825
2826 if( aDrawingSheet && !aDrawingSheet->IsEmpty() )
2827 loadOverrideDrawingSheet( aBoard, *aDrawingSheet );
2828
2829 PROJECT* project = aBoard->GetProject();
2830
2831 if( project )
2832 project->ApplyTextVars( aJob->GetVarOverrides() );
2833
2834 aBoard->SynchronizeProperties();
2835
2836 return aJob->GetFullOutputPath( project );
2837}
2838
2839
2841{
2842 DS_PROXY_VIEW_ITEM* drawingSheet = new DS_PROXY_VIEW_ITEM( pcbIUScale, &aBrd->GetPageSettings(), aBrd->GetProject(),
2843 &aBrd->GetTitleBlock(), &aBrd->GetProperties() );
2844
2845 drawingSheet->SetSheetName( std::string() );
2846 drawingSheet->SetSheetPath( std::string() );
2847 drawingSheet->SetIsFirstPage( true );
2848
2849 drawingSheet->SetFileName( TO_UTF8( aBrd->GetFileName() ) );
2850
2851 wxString currentVariant = aBrd->GetCurrentVariant();
2852 wxString variantDesc = aBrd->GetVariantDescription( currentVariant );
2853 drawingSheet->SetVariantName( TO_UTF8( currentVariant ) );
2854 drawingSheet->SetVariantDesc( TO_UTF8( variantDesc ) );
2855
2856 return drawingSheet;
2857}
2858
2859
2860void PCBNEW_JOBS_HANDLER::loadOverrideDrawingSheet( BOARD* aBrd, const wxString& aSheetPath )
2861{
2862 // dont bother attempting to load a empty path, if there was one
2863 if( aSheetPath.IsEmpty() )
2864 return;
2865
2866 auto loadSheet = [&]( const wxString& path ) -> bool
2867 {
2870 resolver.SetProject( aBrd->GetProject() );
2871 resolver.SetProgramBase( &Pgm() );
2872
2873 wxString filename = resolver.ResolvePath( BASE_SCREEN::m_DrawingSheetFileName,
2874 aBrd->GetProject()->GetProjectPath(), { aBrd->GetEmbeddedFiles() } );
2875 wxString msg;
2876
2877 if( !DS_DATA_MODEL::GetTheInstance().LoadDrawingSheet( filename, &msg ) )
2878 {
2879 m_reporter->Report( wxString::Format( _( "Error loading drawing sheet '%s'." ), path ) + wxS( "\n" ) + msg
2880 + wxS( "\n" ),
2882 return false;
2883 }
2884
2885 return true;
2886 };
2887
2888 if( loadSheet( aSheetPath ) )
2889 return;
2890
2891 // failed loading custom path, revert back to default
2892 loadSheet( aBrd->GetProject()->GetProjectFile().m_BoardDrawingSheetFile );
2893}
2894
2895
2896// Resolve a KiCad layer name (canonical board-file name such as "F.Cu", or the GUI display name)
2897// to its layer id. Returns UNDEFINED_LAYER when no layer matches.
2898static PCB_LAYER_ID resolveKiCadLayerName( const wxString& aName )
2899{
2900 for( PCB_LAYER_ID layer : LSET::AllLayersMask().Seq() )
2901 {
2902 if( LSET::Name( layer ) == aName || LayerName( layer ) == aName )
2903 return layer;
2904 }
2905
2906 return UNDEFINED_LAYER;
2907}
2908
2909
2911{
2912 JOB_PCB_IMPORT* job = dynamic_cast<JOB_PCB_IMPORT*>( aJob );
2913
2914 if( !job )
2916
2917 // Check that input file exists
2918 if( !wxFile::Exists( job->m_inputFile ) )
2919 {
2920 m_reporter->Report( wxString::Format( _( "Input file not found: '%s'\n" ),
2921 job->m_inputFile ),
2924 }
2925
2926 // Map job format to PCB_IO file type
2928
2929 switch( job->m_format )
2930 {
2932
2934
2936
2938
2940
2942
2944
2946 }
2947
2948 // FindPluginTypeFromBoardPath returns FILE_TYPE_NONE (not PCB_FILE_UNKNOWN) when no plugin
2949 // claims the file. Quiet sentinel: lets the top-level `import` command try the schematic face.
2951 {
2952 m_reporter->Report( wxString::Format( _( "No PCB importer recognizes the file format of "
2953 "'%s'\n" ),
2954 job->m_inputFile ),
2957 }
2958
2959 // Determine output path
2960 wxString outputPath = job->GetConfiguredOutputPath();
2961
2962 if( outputPath.IsEmpty() )
2964
2965 BOARD* board = nullptr;
2966 wxString formatName = PCB_IO_MGR::ShowType( fileType );
2967 std::vector<wxString> warnings;
2968
2969 // Real source-to-KiCad layer decisions, captured by our mapping callback so the report can
2970 // show them and so explicit overrides can be validated.
2971 struct CAPTURED_LAYER
2972 {
2973 wxString m_source;
2974 PCB_LAYER_ID m_target;
2975 wxString m_method;
2976 };
2977
2978 std::vector<CAPTURED_LAYER> capturedLayers;
2979 std::set<wxString> seenSourceLayers;
2980 bool layersCaptured = false;
2981
2982 try
2983 {
2985
2986 if( !pi )
2987 {
2988 m_reporter->Report( wxString::Format( _( "No plugin found for file type '%s'\n" ), formatName ),
2991 }
2992
2993 // Replace the plugin's default best-guess callback so we can apply explicit overrides and
2994 // capture the resulting mapping. Only mappable importers expose their source layers; for
2995 // others the report falls back to listing the imported board's enabled layers.
2996 if( LAYER_MAPPABLE_PLUGIN* mappable = dynamic_cast<LAYER_MAPPABLE_PLUGIN*>( pi.get() ) )
2997 {
2998 if( !job->m_layerMap.empty() || job->m_reportFormat != IMPORT_REPORT_FORMAT::NONE )
2999 {
3000 mappable->RegisterCallback(
3001 [&]( const std::vector<INPUT_LAYER_DESC>& aDescs )
3002 -> std::map<wxString, PCB_LAYER_ID>
3003 {
3004 std::map<wxString, PCB_LAYER_ID> result;
3005
3006 for( const INPUT_LAYER_DESC& desc : aDescs )
3007 {
3008 PCB_LAYER_ID target = desc.AutoMapLayer;
3009 wxString method = wxS( "auto" );
3010
3011 if( auto it = job->m_layerMap.find( desc.Name );
3012 it != job->m_layerMap.end() )
3013 {
3014 PCB_LAYER_ID resolved = resolveKiCadLayerName( it->second );
3015
3016 if( resolved == UNDEFINED_LAYER )
3017 {
3018 warnings.push_back( wxString::Format(
3019 _( "Layer map entry '%s' -> '%s' names an unknown "
3020 "KiCad layer; using automatic mapping instead" ),
3021 desc.Name, it->second ) );
3022 }
3023 else if( !desc.PermittedLayers.Contains( resolved ) )
3024 {
3025 warnings.push_back( wxString::Format(
3026 _( "Layer map entry '%s' -> '%s' is not a permitted "
3027 "target for this layer; using automatic mapping "
3028 "instead" ),
3029 desc.Name, it->second ) );
3030 }
3031 else
3032 {
3033 target = resolved;
3034 method = wxS( "explicit" );
3035 }
3036 }
3037
3038 if( desc.Required && target == UNDEFINED_LAYER )
3039 {
3040 warnings.push_back( wxString::Format(
3041 _( "No KiCad layer mapping for required source layer "
3042 "'%s'; its items will not be imported" ),
3043 desc.Name ) );
3044 }
3045
3046 result.emplace( desc.Name, target );
3047 capturedLayers.push_back( { desc.Name, target, method } );
3048 seenSourceLayers.insert( desc.Name );
3049 }
3050
3051 layersCaptured = true;
3052 return result;
3053 } );
3054 }
3055 }
3056 else if( !job->m_layerMap.empty() )
3057 {
3058 warnings.push_back( _( "A layer map was provided, but this importer does not support "
3059 "layer remapping; it will be ignored" ) );
3060 }
3061
3062 m_reporter->Report(
3063 wxString::Format( _( "Importing '%s' using %s format...\n" ), job->m_inputFile, formatName ),
3065
3066 board = pi->LoadBoard( job->m_inputFile, nullptr, nullptr, nullptr );
3067
3068 if( !board )
3069 {
3070 m_reporter->Report( _( "Failed to load board\n" ), RPT_SEVERITY_ERROR );
3072 }
3073
3074 // Flag explicit map entries that never matched a source layer so typos do not pass silently.
3075 if( layersCaptured )
3076 {
3077 for( const auto& [source, target] : job->m_layerMap )
3078 {
3079 if( !seenSourceLayers.contains( source ) )
3080 {
3081 warnings.push_back( wxString::Format(
3082 _( "Layer map entry '%s' does not match any source layer in the "
3083 "imported file; it will be ignored" ),
3084 source ) );
3085 }
3086 }
3087 }
3088
3089 // Save as KiCad format
3091 kicadPlugin->SaveBoard( outputPath, board );
3092
3093 m_reporter->Report( wxString::Format( _( "Successfully saved imported board to '%s'\n" ), outputPath ),
3095
3096 // Generate report if requested
3098 {
3099 IMPORT_REPORT_DATA reportData;
3100
3101 reportData.m_sourceFile = wxFileName( job->m_inputFile ).GetFullName();
3102 reportData.m_sourceFormat = formatName;
3103 reportData.m_outputFile = wxFileName( outputPath ).GetFullName();
3104
3105 size_t trackCount = 0;
3106 size_t viaCount = 0;
3107
3108 for( PCB_TRACK* track : board->Tracks() )
3109 {
3110 if( track->Type() == PCB_VIA_T )
3111 viaCount++;
3112 else
3113 trackCount++;
3114 }
3115
3116 reportData.m_statistics = {
3117 { wxS( "footprints" ), board->Footprints().size() },
3118 { wxS( "tracks" ), trackCount },
3119 { wxS( "vias" ), viaCount },
3120 { wxS( "zones" ), board->Zones().size() }
3121 };
3122
3123 // Build layer mapping info, carried only in the JSON report. Prefer the real
3124 // source-to-KiCad decisions captured during load; fall back to the imported board's
3125 // enabled layers for importers that do not expose a mappable layer set.
3126 nlohmann::json layerMappings = nlohmann::json::object();
3127
3128 if( layersCaptured )
3129 {
3130 for( const CAPTURED_LAYER& mapped : capturedLayers )
3131 {
3132 std::string kicadLayer = mapped.m_target == UNDEFINED_LAYER
3133 ? std::string()
3134 : LSET::Name( mapped.m_target ).ToStdString();
3135
3136 layerMappings[mapped.m_source.ToStdString()] = {
3137 { "kicad_layer", kicadLayer },
3138 { "method", mapped.m_method.ToStdString() }
3139 };
3140 }
3141 }
3142 else
3143 {
3144 for( PCB_LAYER_ID layer : board->GetEnabledLayers().Seq() )
3145 {
3146 wxString layerName = board->GetLayerName( layer );
3147
3148 layerMappings[layerName.ToStdString()] = {
3149 { "kicad_layer", LSET::Name( layer ).ToStdString() },
3150 { "method", "auto" }
3151 };
3152 }
3153 }
3154
3155 reportData.m_extraJson["layer_mapping"] = layerMappings;
3156 reportData.m_warnings = warnings;
3157
3158 WriteImportReport( m_reporter, job->m_reportFormat, job->m_reportFile, reportData );
3159 }
3160 else
3161 {
3162 // No report requested, but explicit-mapping problems still need to surface.
3163 for( const wxString& warning : warnings )
3164 m_reporter->Report( warning + wxS( "\n" ), RPT_SEVERITY_WARNING );
3165 }
3166
3167 delete board;
3168 }
3169 catch( const IO_ERROR& ioe )
3170 {
3171 m_reporter->Report( wxString::Format( _( "Error during import: %s\n" ), ioe.What() ), RPT_SEVERITY_ERROR );
3172
3173 delete board;
3175 }
3176
3178}
3179
3180
3181// ============================================================================
3182// JobDiff: pcb_diff implementation
3183// ============================================================================
3186#include <diff_merge/diff_scene.h>
3187#include <diff_merge/pcb_differ.h>
3189#include <jobs/job_pcb_diff.h>
3190
3191
3192// Load a board into a SCRATCH_DOC<BOARD> that keeps its project attached for
3193// the document's lifetime — the differ/applier read PROJECT_FILE-scoped fields
3194// (drawing-sheet path, DRC severities, net classes). The destructor severs the
3195// BOARD->project link in the right order. Used by every PCB diff/merge job.
3196static SCRATCH_DOC<BOARD> loadScratchBoard( SETTINGS_MANAGER& aMgr, const wxString& aPath,
3197 bool aInitializeAfterLoad = true )
3198{
3199 return LoadScratchDoc<BOARD>(
3200 aMgr, aPath,
3201 [aPath, aInitializeAfterLoad]( PROJECT* aProject ) -> std::unique_ptr<BOARD>
3202 {
3203 PCB_IO_MGR::PCB_FILE_T pluginType =
3205
3206 if( !aProject || pluginType == PCB_IO_MGR::FILE_TYPE_NONE )
3207 return nullptr;
3208
3210 opts.initialize_after_load = aInitializeAfterLoad;
3211
3212 try
3213 {
3214 return BOARD_LOADER::Load( aPath, pluginType, aProject, opts );
3215 }
3216 catch( ... )
3217 {
3218 return nullptr;
3219 }
3220 },
3221 []( BOARD* aBoard )
3222 {
3223 if( aBoard )
3224 aBoard->ClearProject();
3225 } );
3226}
3227
3228
3230{
3231 JOB_PCB_DIFF* diffJob = dynamic_cast<JOB_PCB_DIFF*>( aJob );
3232
3233 if( !diffJob )
3235
3236 // SCRATCH_DOC<BOARD> keeps each board's project attached for the lifetime
3237 // of the diff, which the differ needs to read project-file-scoped fields
3238 // (m_BoardDrawingSheetFile, etc). The previous loadStandaloneBoard +
3239 // ClearProject-up-front path would null those out before the differ ran.
3241
3242 SCRATCH_DOC<BOARD> aScratch = loadScratchBoard( diffMgr, diffJob->m_inputA );
3243 SCRATCH_DOC<BOARD> bScratch = loadScratchBoard( diffMgr, diffJob->m_inputB );
3244
3245 BOARD* boardA = aScratch.doc.get();
3246 BOARD* boardB = bScratch.doc.get();
3247
3248 if( !boardA )
3249 {
3250 m_reporter->Report( wxString::Format( _( "Failed to load %s\n" ), diffJob->m_inputA ), RPT_SEVERITY_ERROR );
3252 }
3253
3254 if( !boardB )
3255 {
3256 m_reporter->Report( wxString::Format( _( "Failed to load %s\n" ), diffJob->m_inputB ), RPT_SEVERITY_ERROR );
3258 }
3259
3260 KICAD_DIFF::PCB_DIFFER differ( boardA, boardB, diffJob->m_inputB );
3262
3263 int diffExitCode = KICAD_DIFF::DiffExitCode( result );
3264
3265 if( diffJob->m_exitCodeOnly )
3266 return diffExitCode;
3267
3268 // The board geometry rendered beneath the change overlay (PNG/SVG only)
3269 // matches what the interactive dialog draws.
3271 KICAD_DIFF::MakeEmitOptions( *diffJob, diffJob->m_inputA, diffJob->m_inputB );
3273 emitOpts.referenceGeometry = [&]( const KIGFX::COLOR4D& aColor )
3274 { return KICAD_DIFF::ExtractBoardGeometry( *boardA, aColor ); };
3275 emitOpts.comparisonGeometry = [&]( const KIGFX::COLOR4D& aColor )
3276 { return KICAD_DIFF::ExtractBoardGeometry( *boardB, aColor ); };
3277
3278 return KICAD_DIFF::EmitDiffResult( result, emitOpts, diffExitCode, *m_reporter );
3279}
3280
3281
3282// ============================================================================
3283// JobMerge: pcb_merge implementation
3284// ============================================================================
3289
3290
3291int PCBNEW_JOBS_HANDLER::RunMerge( KICAD_DIFF::DOC_KIND aKind, const wxString& aAncestor,
3292 const wxString& aOurs, const wxString& aTheirs,
3293 const wxString& aOutput, bool aInteractive, bool aSingleFile,
3294 REPORTER* aReporter )
3295{
3296 // Restore m_reporter on scope exit so a caller's transient (often
3297 // stack-local) reporter doesn't outlive this call as a dangling member.
3299 aReporter ? aReporter : m_reporter );
3300
3302 return runFpLibMerge( aAncestor, aOurs, aTheirs, aOutput, aSingleFile );
3303
3304 return runPcbMerge( aAncestor, aOurs, aTheirs, aOutput, aInteractive );
3305}
3306
3307
3308int PCBNEW_JOBS_HANDLER::runPcbMerge( const wxString& aAncestor, const wxString& aOurs,
3309 const wxString& aTheirs, const wxString& aOutput,
3310 bool aInteractive )
3311{
3312 // Use SCRATCH_DOC<BOARD> so each input keeps its project attached for the
3313 // life of the merge — necessary for any doc-level resolution that mutates
3314 // PROJECT_FILE-scoped state (DRC severities, net classes) and needs to be
3315 // saved as a sibling .kicad_pro. SCRATCH_DOC's destructor severs the
3316 // BOARD->project link in the right order and unloads the project from
3317 // the manager, avoiding the dangling-PROJECT_FILE::m_BoardSettings pointer
3318 // the previous up-front-ClearProject loadStandaloneBoard path had to
3319 // guard against.
3321
3322 SCRATCH_DOC<BOARD> ancestorScratch = loadScratchBoard( mgr, aAncestor );
3323 SCRATCH_DOC<BOARD> oursScratch = loadScratchBoard( mgr, aOurs );
3324 SCRATCH_DOC<BOARD> theirsScratch = loadScratchBoard( mgr, aTheirs );
3325
3326 BOARD* ancestor = ancestorScratch.doc.get();
3327 BOARD* ours = oursScratch.doc.get();
3328 BOARD* theirs = theirsScratch.doc.get();
3329
3330 if( !ancestor || !ours || !theirs )
3331 {
3332 m_reporter->Report( _( "Failed to load one or more input boards\n" ), RPT_SEVERITY_ERROR );
3334 }
3335
3336 KICAD_DIFF::PCB_DIFFER ourDiff( ancestor, ours );
3337 KICAD_DIFF::PCB_DIFFER theirDiff( ancestor, theirs );
3338
3339 KICAD_DIFF::DOCUMENT_DIFF ourDocDiff = ourDiff.Diff();
3340 KICAD_DIFF::DOCUMENT_DIFF theirDocDiff = theirDiff.Diff();
3341
3343 KICAD_DIFF::MERGE_PLAN plan = engine.Plan( ourDocDiff, theirDocDiff );
3344
3345 // A cancelled dialog leaves plan unresolved and falls through to the
3346 // marker flow below.
3347 if( aInteractive && !plan.Resolved() )
3348 {
3349 if( !Pgm().IsGUI() )
3350 {
3351 m_reporter->Report( _( "--interactive requires a GUI KiCad process; the console "
3352 "kicad-cli cannot open dialogs.\n" ),
3355 }
3356
3357 // Geometry context so the conflict viewer can render the actual
3358 // boards behind the conflict bbox highlight.
3359 const KICAD_DIFF::DIFF_COLOR_THEME theme;
3364
3365 // Build per-side bbox lookups so a "moved on theirs" item highlights
3366 // at its theirs-side coordinates when the user previews Theirs.
3368 KICAD_DIFF::CollectChangeBBoxes( theirDocDiff, ctx.theirsBBoxes );
3369
3370 DIALOG_KICAD_MERGE_3WAY dlg( wxTheApp->GetTopWindow(), plan, std::move( ctx ) );
3371
3372 if( dlg.ShowModal() == wxID_APPLY )
3373 plan = dlg.GetResolvedPlan();
3374 }
3375
3376 // Snapshot of the plan before the applier moves it; drives the
3377 // unresolved-conflict report below.
3378 const KICAD_DIFF::MERGE_PLAN planSnapshot = plan;
3379
3380 KICAD_DIFF::PCB_MERGE_APPLIER applier( ancestor, ours, theirs, std::move( plan ) );
3381 std::unique_ptr<BOARD> merged = applier.Apply();
3382
3383 if( !merged )
3384 {
3385 m_reporter->Report( _( "Merge applier failed to produce a board\n" ), RPT_SEVERITY_ERROR );
3387 }
3388
3389 // Serialize to the output path using the canonical PCB IO.
3390 PCB_IO_KICAD_SEXPR pcbIO;
3391
3392 try
3393 {
3394 pcbIO.SaveBoard( aOutput, merged.get() );
3395 }
3396 catch( const IO_ERROR& ioe )
3397 {
3398 m_reporter->Report( wxString::Format( _( "Failed to save merged board: %s\n" ), ioe.What() ),
3401 }
3402
3403 // BOARD_DESIGN_SETTINGS fields like m_DRCSeverities serialize to
3404 // .kicad_pro, not .kicad_pcb. Mirror only those specific fields onto
3405 // ancestor (still linked to its project via BOARD::SetProject) then
3406 // save ancestor's project alongside the merged board file. Whole-
3407 // BOARD_DESIGN_SETTINGS copy would alias shared_ptr<NET_SETTINGS>
3408 // across BOARDs and crash on ClearProject during SCRATCH_DOC release;
3409 // single-field mirror avoids that.
3410 if( applier.GetReport().projectFileTouched && ancestor && ancestor->GetProject() )
3411 {
3412 ancestor->GetDesignSettings().m_DRCSeverities = merged->GetDesignSettings().m_DRCSeverities;
3413
3414 // Mirror net settings (the applier copied them onto the result via
3415 // NET_SETTINGS::CopyFrom; do the same here to ancestor, which still
3416 // owns the project's nested-settings registration so SaveProjectCopy
3417 // walks the right entry).
3418 if( ancestor->GetDesignSettings().m_NetSettings && merged->GetDesignSettings().m_NetSettings )
3419 {
3420 ancestor->GetDesignSettings().m_NetSettings->CopyFrom( *merged->GetDesignSettings().m_NetSettings );
3421 }
3422
3423 // The applier stages drawing-sheet resolutions on the report (the
3424 // result BOARD is project-less). Mirror onto ancestor's project here
3425 // before SaveProjectCopy walks the PROJECT_FILE.
3426 if( applier.GetReport().drawingSheetFileSet )
3427 {
3429 }
3430
3431 wxFileName proFn( aOutput );
3432 proFn.SetExt( FILEEXT::ProjectFileExtension );
3433
3434 // JSON-patch path: flush ancestor's in-memory project to its JSON
3435 // cache, then patch only the diffed DOC_PROP fields onto the output
3436 // file. This preserves any non-diffed fields the user had at the
3437 // output path (text variables, last paths, layer presets etc.) that
3438 // a full SaveProjectCopy would silently overwrite.
3439 PROJECT_FILE& ancProj = ancestor->GetProject()->GetProjectFile();
3440 ancProj.Store();
3441
3442 const KICAD_DIFF::PCB_MERGE_APPLIER::REPORT& mergeReport = applier.GetReport();
3443
3444 // PROJECT_FILE::Store() flushes the project file's own params but not
3445 // its registered NESTED_SETTINGS. Flush only the nested settings the
3446 // merge resolved so the surgical patch does not overwrite unrelated
3447 // project subtrees.
3448 if( mergeReport.drcSeveritiesTouched )
3449 ancestor->GetDesignSettings().SaveToFile( wxEmptyString, true );
3450
3451 if( mergeReport.netClassesTouched && ancestor->GetDesignSettings().m_NetSettings )
3452 ancestor->GetDesignSettings().m_NetSettings->SaveToFile( wxEmptyString, true );
3453
3454 std::set<wxString> touched;
3455 if( mergeReport.drcSeveritiesTouched )
3456 touched.insert( KICAD_DIFF::DOC_PROP_DRC_SEVERITIES );
3457
3458 if( mergeReport.netClassesTouched )
3459 touched.insert( KICAD_DIFF::DOC_PROP_NET_CLASSES );
3460
3461 if( applier.GetReport().drawingSheetFileSet )
3462 touched.insert( KICAD_DIFF::DOC_PROP_DRAWING_SHEET );
3463
3464 if( !KICAD_DIFF::ApplyProjectFilePatches( proFn.GetFullPath(), *ancProj.Internals(), touched ) )
3465 {
3466 // Patch failed (existing output unparseable or write error).
3467 // Fall back to the legacy full-copy path so the user still gets
3468 // a project file even if it overwrites non-diffed customisations.
3469 if( !mgr.SaveProjectCopy( proFn.GetFullPath(), ancestor->GetProject() ) )
3470 {
3471 m_reporter->Report(
3472 wxString::Format( _( "Failed to save merged project file: %s\n" ), proFn.GetFullPath() ),
3475 }
3476 }
3477
3478 // Write a project-dir sibling file from staged report content. Empty
3479 // content removes the file so a TAKE_ANCESTOR resolution against an
3480 // ancestor with no file clears stale content at the output path.
3481 auto writeStagedFile = [&]( const wxString& aPath, const wxString& aContent, const wxString& aLabel ) -> bool
3482 {
3483 if( aContent.IsEmpty() )
3484 {
3485 if( wxFileExists( aPath ) )
3486 wxRemoveFile( aPath );
3487
3488 return true;
3489 }
3490
3491 wxFile out;
3492
3493 if( !out.Open( aPath, wxFile::write ) || !out.Write( aContent ) )
3494 {
3495 m_reporter->Report( wxString::Format( _( "Failed to save merged %s: %s\n" ), aLabel, aPath ),
3497 return false;
3498 }
3499
3500 return true;
3501 };
3502
3503 // Custom DRC rules: write the applier's staged content next to the
3504 // merged board so the chosen side's rules apply at next DRC run.
3505 if( applier.GetReport().customDrcRulesSet )
3506 {
3507 wxFileName druFn( aOutput );
3508 druFn.SetExt( FILEEXT::DesignRulesFileExtension );
3509
3510 if( !writeStagedFile( druFn.GetFullPath(), applier.GetReport().customDrcRules, _( "custom DRC rules" ) ) )
3511 {
3513 }
3514 }
3515
3516 // Footprint / symbol library tables: write into the merged project
3517 // directory. Both files have no extension.
3518 if( applier.GetReport().fpLibTableSet )
3519 {
3520 wxFileName fpFn( aOutput );
3521 fpFn.SetFullName( wxString::FromUTF8( FILEEXT::FootprintLibraryTableFileName ) );
3522
3523 if( !writeStagedFile( fpFn.GetFullPath(), applier.GetReport().fpLibTable, _( "footprint library table" ) ) )
3524 {
3526 }
3527 }
3528
3529 if( applier.GetReport().symLibTableSet )
3530 {
3531 wxFileName symFn( aOutput );
3532 symFn.SetFullName( wxString::FromUTF8( FILEEXT::SymbolLibraryTableFileName ) );
3533
3534 if( !writeStagedFile( symFn.GetFullPath(), applier.GetReport().symLibTable, _( "symbol library table" ) ) )
3535 {
3537 }
3538 }
3539 }
3540
3541 // Surface post-apply validator findings (refdes collisions, schema
3542 // mismatch, missed connectivity rebuild). Advisory — they do not change the
3543 // exit code, only the merge's resolved/unresolved status does.
3545 m_reporter->Report( wxString::Format( wxS( "%s: %s\n" ), f.validator, f.message ), f.severity );
3546
3547 // The merged board was written to m_outputPath above, so the output is
3548 // always a valid file. Unresolved conflicts are reported and signalled via
3549 // the exit code; the user resolves them with the interactive mergetool.
3550 if( !planSnapshot.Resolved() )
3551 {
3552 m_reporter->Report( wxString::Format( _( "Merge completed with %zu unresolved conflict(s) in %s\n" ),
3553 planSnapshot.ConflictCount(), aOutput ),
3556 }
3557
3559}
3560
3561
3562// ============================================================================
3563// JobFpDiff: fp_diff implementation
3564// ============================================================================
3566#include <jobs/job_fp_diff.h>
3567
3568
3569// Load one side of a footprint-library diff into its owner vector and name map.
3570// When aAllowEmpty is set an empty path resolves to a clean (empty) side; the
3571// non-interactive job path leaves it unset so a missing path is an input error.
3572static int loadFootprintLibrarySide( const wxString& aPath,
3573 std::vector<std::unique_ptr<FOOTPRINT>>& aOwners,
3574 KICAD_DIFF::FP_LIB_DIFFER::FOOTPRINT_MAP& aMap, bool aAllowEmpty,
3575 REPORTER& aReporter )
3576{
3577 if( aAllowEmpty && aPath.IsEmpty() )
3579
3580 try
3581 {
3582 auto loaded = KICAD_DIFF::FP_LIB_DIFFER::LoadLibrary( aPath );
3583 aOwners = std::move( loaded.first );
3584 aMap = std::move( loaded.second );
3586 }
3587 catch( const IO_ERROR& ioe )
3588 {
3589 aReporter.Report( wxString::Format( _( "Failed to load %s: %s\n" ), aPath, ioe.What() ),
3591 }
3592 catch( const std::exception& e )
3593 {
3594 aReporter.Report(
3595 wxString::Format( _( "Failed to load %s: %s\n" ), aPath, wxString::FromUTF8( e.what() ) ),
3597 }
3598
3600}
3601
3602
3603// Flatten a footprint-library name map into a single DOCUMENT_GEOMETRY tinted
3604// with the supplied per-side theme colour.
3607{
3609
3610 for( const auto& [name, footprint] : aMap )
3611 {
3612 if( footprint )
3613 KICAD_DIFF::AppendGeometry( geometry, KICAD_DIFF::ExtractFootprintGeometry( *footprint, aColor ) );
3614 }
3615
3616 return geometry;
3617}
3618
3619
3621{
3622 JOB_FP_DIFF* diffJob = dynamic_cast<JOB_FP_DIFF*>( aJob );
3623
3624 if( !diffJob )
3626
3627 wxFileName dirA( diffJob->m_inputA );
3628 dirA.MakeAbsolute();
3629 wxFileName dirB( diffJob->m_inputB );
3630 dirB.MakeAbsolute();
3631
3632 std::vector<std::unique_ptr<FOOTPRINT>> ownersA;
3633 std::vector<std::unique_ptr<FOOTPRINT>> ownersB;
3636
3637 if( int rc = loadFootprintLibrarySide( dirA.GetFullPath(), ownersA, mapA, false, *m_reporter );
3639 {
3640 return rc;
3641 }
3642
3643 if( int rc = loadFootprintLibrarySide( dirB.GetFullPath(), ownersB, mapB, false, *m_reporter );
3645 {
3646 return rc;
3647 }
3648
3649 KICAD_DIFF::FP_LIB_DIFFER differ( mapA, mapB, diffJob->m_inputB );
3651
3652 int diffExitCode = KICAD_DIFF::DiffExitCode( result );
3653
3654 if( diffJob->m_exitCodeOnly )
3655 return diffExitCode;
3656
3658 KICAD_DIFF::MakeEmitOptions( *diffJob, diffJob->m_inputA, diffJob->m_inputB );
3660 emitOpts.referenceGeometry = [&]( const KIGFX::COLOR4D& aColor )
3661 { return footprintLibraryGeometry( mapA, aColor ); };
3662 emitOpts.comparisonGeometry = [&]( const KIGFX::COLOR4D& aColor )
3663 { return footprintLibraryGeometry( mapB, aColor ); };
3664
3665 return KICAD_DIFF::EmitDiffResult( result, emitOpts, diffExitCode, *m_reporter );
3666}
3667
3668
3669// ============================================================================
3670// JobOpenDiffDialog: load two on-disk files and open DIALOG_KICAD_DIFF.
3671// Dispatched from the project manager / PR-review dialog via KIWAY.
3672// ============================================================================
3676#include <jobs/scratch_doc.h>
3677
3678
3680 const wxString& aFileB, const wxString& aLabelA,
3681 const wxString& aLabelB, wxWindow* aParent,
3682 REPORTER* aReporter )
3683{
3684 // Restore m_reporter on scope exit so a caller's transient (often
3685 // stack-local) reporter doesn't outlive this call as a dangling member.
3687 aReporter ? aReporter : m_reporter );
3688
3689 wxWindow* parent = aParent ? aParent : ( wxTheApp ? wxTheApp->GetTopWindow() : nullptr );
3690
3692
3693 auto loadBoardScratch = [&]( const wxString& aPath )
3694 {
3695 return loadScratchBoard( mgr, aPath, /* aInitializeAfterLoad */ false );
3696 };
3697
3700 KICAD_DIFF::DOCUMENT_GEOMETRY compGeometry;
3701
3702 auto loadFootprintFile = [&]( const wxString& aPath ) -> std::unique_ptr<FOOTPRINT>
3703 {
3704 if( aPath.IsEmpty() )
3705 return nullptr;
3706
3707 wxFileName fn( aPath );
3708 fn.MakeAbsolute();
3709
3710 // A single .kicad_mod's internal (footprint ...) name need not match its
3711 // filename, so load the file's sole footprint via ImportFootprint rather
3712 // than FootprintLoad (which treats the directory as a .pretty library and
3713 // keys by basename), matching runFpLibMerge's single-file path.
3715 wxString name;
3716 return std::unique_ptr<FOOTPRINT>( io.ImportFootprint( fn.GetFullPath(), name ) );
3717 };
3718
3719 switch( aKind )
3720 {
3722 {
3723 SCRATCH_DOC<BOARD> a = loadBoardScratch( aFileA );
3724 SCRATCH_DOC<BOARD> b = loadBoardScratch( aFileB );
3725
3726 // Synthesize empty boards for ADDED / REMOVED sides so the differ
3727 // can still produce a meaningful per-item list rather than failing
3728 // on an empty input file.
3729 BOARD emptyA;
3730 BOARD emptyB;
3731
3732 if( !a.doc && !aFileA.IsEmpty() )
3733 {
3734 m_reporter->Report( wxString::Format( _( "Failed to load %s\n" ), aFileA ), RPT_SEVERITY_ERROR );
3736 }
3737
3738 if( !b.doc && !aFileB.IsEmpty() )
3739 {
3740 m_reporter->Report( wxString::Format( _( "Failed to load %s\n" ), aFileB ), RPT_SEVERITY_ERROR );
3742 }
3743
3744 BOARD* boardA = a.doc ? a.doc.get() : &emptyA;
3745 BOARD* boardB = b.doc ? b.doc.get() : &emptyB;
3746
3747 KICAD_DIFF::PCB_DIFFER differ( boardA, boardB, aFileB );
3748 result = differ.Diff();
3749
3750 // Extract background geometry so the dialog's canvas shows the
3751 // actual board outline + footprint footprints beneath the diff
3752 // bbox rectangles. Theme defaults: muted blue (ref) / gold (comp).
3753 const KICAD_DIFF::DIFF_COLOR_THEME theme;
3754 refGeometry = KICAD_DIFF::ExtractBoardGeometry( *boardA, theme.reference );
3755 compGeometry = KICAD_DIFF::ExtractBoardGeometry( *boardB, theme.comparison );
3756
3757 const wxString labelA = aLabelA.IsEmpty() ? aFileA : aLabelA;
3758 const wxString labelB = aLabelB.IsEmpty() ? aFileB : aLabelB;
3759
3761 parent, labelA, labelB, result, std::move( refGeometry ), std::move( compGeometry ),
3762 [boardA, boardB, color = theme.reference]( WIDGET_DIFF_CANVAS& aCanvas, const KIID_PATH& )
3763 {
3764 KICAD_DIFF::ConfigurePcbDiffCanvasContext( aCanvas, boardA, boardB, color );
3765 } );
3766 dlg.ShowModal();
3767
3769 }
3771 {
3772 std::vector<std::unique_ptr<FOOTPRINT>> ownersA;
3773 std::vector<std::unique_ptr<FOOTPRINT>> ownersB;
3776
3777 if( int rc = loadFootprintLibrarySide( aFileA, ownersA, mapA, true, *m_reporter );
3779 {
3780 return rc;
3781 }
3782
3783 if( int rc = loadFootprintLibrarySide( aFileB, ownersB, mapB, true, *m_reporter );
3785 {
3786 return rc;
3787 }
3788
3789 KICAD_DIFF::FP_LIB_DIFFER differ( mapA, mapB, aFileB );
3790 result = differ.Diff();
3791
3792 const KICAD_DIFF::DIFF_COLOR_THEME theme;
3793 refGeometry = footprintLibraryGeometry( mapA, theme.reference );
3794 compGeometry = footprintLibraryGeometry( mapB, theme.comparison );
3795 break;
3796 }
3798 {
3799 std::unique_ptr<FOOTPRINT> footprintA;
3800 std::unique_ptr<FOOTPRINT> footprintB;
3801
3802 try
3803 {
3804 footprintA = loadFootprintFile( aFileA );
3805 }
3806 catch( const IO_ERROR& ioe )
3807 {
3808 m_reporter->Report( wxString::Format( _( "Failed to load %s: %s\n" ), aFileA, ioe.What() ),
3811 }
3812
3813 try
3814 {
3815 footprintB = loadFootprintFile( aFileB );
3816 }
3817 catch( const IO_ERROR& ioe )
3818 {
3819 m_reporter->Report( wxString::Format( _( "Failed to load %s: %s\n" ), aFileB, ioe.What() ),
3822 }
3823
3826 const wxString nameA = wxFileName( aFileA ).GetName();
3827 const wxString nameB = wxFileName( aFileB ).GetName();
3828 const wxString itemName = !nameB.IsEmpty() ? nameB : nameA;
3829
3830 if( footprintA )
3831 mapA[itemName] = footprintA.get();
3832
3833 if( footprintB )
3834 mapB[itemName] = footprintB.get();
3835
3836 KICAD_DIFF::FP_LIB_DIFFER differ( mapA, mapB, aFileB );
3837 result = differ.Diff();
3838
3839 const KICAD_DIFF::DIFF_COLOR_THEME theme;
3840
3841 if( footprintA )
3842 refGeometry = KICAD_DIFF::ExtractFootprintGeometry( *footprintA, theme.reference );
3843
3844 if( footprintB )
3845 compGeometry = KICAD_DIFF::ExtractFootprintGeometry( *footprintB, theme.comparison );
3846
3847 break;
3848 }
3849 default:
3850 m_reporter->Report( _( "Unsupported document kind for this dispatcher.\n" ), RPT_SEVERITY_ERROR );
3852 }
3853
3854 const wxString labelA = aLabelA.IsEmpty() ? aFileA : aLabelA;
3855 const wxString labelB = aLabelB.IsEmpty() ? aFileB : aLabelB;
3856
3857 DIALOG_KICAD_DIFF dlg( parent, labelA, labelB, result, std::move( refGeometry ), std::move( compGeometry ) );
3858 dlg.ShowModal();
3859
3861}
3862
3863
3864// ============================================================================
3865// JobFpLibMerge: 3-way merge of .pretty footprint libraries.
3866// ============================================================================
3869
3870
3871int PCBNEW_JOBS_HANDLER::runFpLibMerge( const wxString& aAncestor, const wxString& aOurs,
3872 const wxString& aTheirs, const wxString& aOutput,
3873 bool aSingleFile )
3874{
3875 if( aOutput.IsEmpty() )
3876 {
3877 m_reporter->Report( _( "--output is required\n" ), RPT_SEVERITY_ERROR );
3879 }
3880
3881 struct LIB_SIDE
3882 {
3883 std::vector<std::unique_ptr<FOOTPRINT>> owners;
3885 };
3886
3887 LIB_SIDE ancestor, ours, theirs;
3888
3889 // Accept either a `.pretty` directory (library mode) or a single `.kicad_
3890 // mod` file (git's per-file driver mode). Extension autodetection works
3891 // for native invocations, but git's external driver passes temp paths
3892 // (`.merge_file_XXX`) with no extension, so the `--single-file` flag
3893 // overrides on demand.
3894 auto isSingleFile = [&]( const wxString& aPath )
3895 {
3896 if( aSingleFile )
3897 return true;
3898
3899 return wxFileName( aPath ).GetExt() == FILEEXT::KiCadFootprintFileExtension;
3900 };
3901
3902 auto loadSide = [&]( const wxString& aPath, LIB_SIDE& aSide ) -> int
3903 {
3904 try
3905 {
3906 if( isSingleFile( aPath ) )
3907 {
3909 wxString name;
3910 std::unique_ptr<FOOTPRINT> fp( io.ImportFootprint( aPath, name ) );
3911
3912 if( !fp )
3914
3915 // Use the footprint's own item-name (LIB_ID) if set,
3916 // falling back to the file basename. Both sides must
3917 // agree for the differ/applier to align them.
3918 const UTF8& itemName = fp->GetFPID().GetLibItemName();
3919 const wxString key = itemName.empty() ? name : itemName.wx_str();
3920
3921 aSide.map[key] = fp.get();
3922 aSide.owners.push_back( std::move( fp ) );
3924 }
3925
3926 auto loaded = KICAD_DIFF::FP_LIB_DIFFER::LoadLibrary( aPath );
3927 aSide.owners = std::move( loaded.first );
3928 aSide.map = std::move( loaded.second );
3930 }
3931 catch( const IO_ERROR& ioe )
3932 {
3933 m_reporter->Report( wxString::Format( _( "Failed to load %s: %s\n" ), aPath, ioe.What() ),
3935 }
3936 catch( const std::exception& e )
3937 {
3938 m_reporter->Report(
3939 wxString::Format( _( "Failed to load %s: %s\n" ), aPath, wxString::FromUTF8( e.what() ) ),
3941 }
3942
3944 };
3945
3946 if( int rc = loadSide( aAncestor, ancestor ); rc != CLI::EXIT_CODES::SUCCESS )
3947 return rc;
3948
3949 if( int rc = loadSide( aOurs, ours ); rc != CLI::EXIT_CODES::SUCCESS )
3950 return rc;
3951
3952 if( int rc = loadSide( aTheirs, theirs ); rc != CLI::EXIT_CODES::SUCCESS )
3953 return rc;
3954
3955 KICAD_DIFF::FP_LIB_DIFFER ourDiff( ancestor.map, ours.map, aOurs );
3956 KICAD_DIFF::FP_LIB_DIFFER theirDiff( ancestor.map, theirs.map, aTheirs );
3957
3958 KICAD_DIFF::DOCUMENT_DIFF ourDocDiff = ourDiff.Diff();
3959 KICAD_DIFF::DOCUMENT_DIFF theirDocDiff = theirDiff.Diff();
3960
3962 KICAD_DIFF::MERGE_PLAN plan = engine.Plan( ourDocDiff, theirDocDiff );
3963
3964 const KICAD_DIFF::MERGE_PLAN planSnapshot = plan;
3965
3966 KICAD_DIFF::FP_LIB_MERGE_APPLIER applier( ancestor.map, ours.map, theirs.map, std::move( plan ) );
3967 std::vector<std::unique_ptr<FOOTPRINT>> merged = applier.Apply();
3968
3969 // Per-property footprint merge isn't implemented; MERGE_PROPS resolutions
3970 // are downgraded to TAKE_OURS. Surface that as unresolved so the user sees
3971 // a marker instead of silent partial-merge.
3972 const bool hadSilentFallback = applier.GetReport().mergePropsFallback > 0;
3973
3974 const bool singleFileOutput = isSingleFile( aOutput );
3975
3976 // .pretty is a directory; .kicad_mod is a single file. wxFileName parses
3977 // a path ending in `.pretty` as a file with that extension, so library
3978 // mode uses DirName(); single-file mode keeps the file path as-is and
3979 // hands it directly to FootprintSave, which auto-detects .kicad_mod via
3980 // its own extension check.
3981 wxFileName outFn;
3982
3983 if( singleFileOutput )
3984 outFn = wxFileName( aOutput );
3985 else
3986 outFn = wxFileName::DirName( aOutput );
3987
3988 outFn.MakeAbsolute();
3989
3990 // In library mode wxFileName::DirName treats `foo.pretty` as a directory,
3991 // so GetPath() returns the .pretty itself. In single-file mode GetPath()
3992 // returns the file's parent dir. Either way it's the directory we Mkdir
3993 // into.
3994 const wxString outDir = outFn.GetPath();
3995
3996 if( !wxFileName::DirExists( outDir ) && !wxFileName::Mkdir( outDir, wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL ) )
3997 {
3998 m_reporter->Report( wxString::Format( _( "Cannot create output directory %s\n" ), outDir ),
4001 }
4002
4003 try
4004 {
4006
4007 if( singleFileOutput )
4008 {
4009 // Git per-file driver mode: one merged footprint -> one .kicad_mod.
4010 // Multiple survivors would lose data; flag that as an error since
4011 // single-file input by definition has at most one footprint per
4012 // side.
4013 if( merged.size() > 1 )
4014 {
4015 m_reporter->Report( _( "Single-file fp merge produced multiple footprints; refusing to "
4016 "collapse into one .kicad_mod\n" ),
4019 }
4020
4021 if( merged.empty() )
4022 {
4023 // All sides deleted the footprint. Remove the output file if
4024 // it existed, leaving nothing where the merged content would
4025 // have gone.
4026 if( wxFileName::FileExists( outFn.GetFullPath() ) )
4027 wxRemoveFile( outFn.GetFullPath() );
4028 }
4029 else if( wxFileName( outFn.GetFullPath() ).GetExt() == FILEEXT::KiCadFootprintFileExtension )
4030 {
4031 // FootprintSave's .kicad_mod extension autodetection handles
4032 // the write to the path as-given.
4033 io.FootprintSave( outFn.GetFullPath(), merged.front().get(), nullptr );
4034 }
4035 else
4036 {
4037 // Git driver mode: output is an extension-less temp path
4038 // (typically `.merge_file_XXX`). FootprintSave's
4039 // autodetection would treat it as a library directory.
4040 // Format directly via PRETTIFIED_FILE_OUTPUTFORMATTER, the
4041 // same writer the sexpr lib cache uses.
4042 PRETTIFIED_FILE_OUTPUTFORMATTER formatter( outFn.GetFullPath() );
4043 io.SetOutputFormatter( &formatter );
4044 io.Format( merged.front().get() );
4045 formatter.Finish();
4046 }
4047 }
4048 else
4049 {
4050 // Library mode. Footprints in `merged` are the survivors. Any
4051 // footprint already in the output `.pretty` but absent from
4052 // `merged` is a stale leftover from a previous invocation (or a
4053 // resolved DELETE / TAKE_ANCESTOR-with-no-ancestor case). Delete
4054 // those before saving the survivors, otherwise the resolved
4055 // DELETE never propagates to disk.
4056 std::set<wxString> mergedNames;
4057
4058 for( const auto& fp : merged )
4059 {
4060 if( fp )
4061 mergedNames.insert( fp->GetFPID().GetLibItemName() );
4062 }
4063
4064 wxArrayString existing;
4065 io.FootprintEnumerate( existing, outDir, false, nullptr );
4066
4067 for( const wxString& name : existing )
4068 {
4069 if( !mergedNames.count( name ) )
4070 io.FootprintDelete( outDir, name, nullptr );
4071 }
4072
4073 for( const auto& fp : merged )
4074 {
4075 if( !fp )
4076 continue;
4077
4078 const wxString name = fp->GetFPID().GetLibItemName();
4079
4080 if( io.FootprintExists( outDir, name, nullptr ) )
4081 io.FootprintDelete( outDir, name, nullptr );
4082
4083 io.FootprintSave( outDir, fp.get(), nullptr );
4084 }
4085 }
4086 }
4087 catch( const IO_ERROR& ioe )
4088 {
4089 m_reporter->Report( wxString::Format( _( "Failed to save merged footprint library: %s\n" ), ioe.What() ),
4092 }
4093
4094 // The merged library was saved above, so the output is always valid.
4095 if( !planSnapshot.Resolved() || hadSilentFallback )
4096 {
4097 // Conflict count = engine-unresolved ∪ applier-downgraded (deduped, so
4098 // an item that was both unresolved and silently downgraded counts once).
4099 std::set<KIID_PATH> conflicts( planSnapshot.unresolved.begin(), planSnapshot.unresolved.end() );
4100
4101 for( const KIID_PATH& id : applier.GetReport().mergePropsFallbackIds )
4102 conflicts.insert( id );
4103
4104 m_reporter->Report( wxString::Format( _( "Footprint library merge completed with %zu unresolved "
4105 "conflict(s) in %s\n" ),
4106 conflicts.size(), aOutput ),
4109 }
4110
4112}
@ 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:372
void SetCurrentVariant(const wxString &aVariant)
Definition board.cpp:2829
const PAGE_INFO & GetPageSettings() const
Definition board.h:889
const ZONES & Zones() const
Definition board.h:424
void RecordDRCExclusions()
Scan existing markers and record data from any that are Excluded.
Definition board.cpp:391
TITLE_BLOCK & GetTitleBlock()
Definition board.h:895
const std::map< wxString, wxString > & GetProperties() const
Definition board.h:457
const FOOTPRINTS & Footprints() const
Definition board.h:420
const TRACKS & Tracks() const
Definition board.h:418
const wxString & GetFileName() const
Definition board.h:409
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:2948
int GetFileFormatVersionAtLoad() const
Definition board.h:513
const PCB_PLOT_PARAMS & GetPlotOptions() const
Definition board.h:892
const wxString GetLayerName(PCB_LAYER_ID aLayer) const
Return the name of a aLayer.
Definition board.cpp:793
wxString GetCurrentVariant() const
Definition board.h:461
PROJECT * GetProject() const
Definition board.h:650
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:2809
BOX2I ComputeBoundingBox(bool aBoardEdgesOnly=false, bool aPhysicalLayersOnly=false) const
Calculate the bounding box containing all board items (or board edge segments).
Definition board.cpp:2399
void DeleteMARKERs()
Delete all MARKERS from the board.
Definition board.cpp:1785
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:156
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