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>
50#include <jobs/job_fp_upgrade.h>
67#include <jobs/job_pcb_render.h>
68#include <jobs/job_pcb_drc.h>
69#include <jobs/job_pcb_import.h>
72#include <eda_units.h>
74#include <lset.h>
75#include <cli/exit_codes.h>
81#include <tool/tool_manager.h>
82#include <tools/drc_tool.h>
83#include <filename_resolver.h>
88#include <kiface_base.h>
89#include <macros.h>
90#include <pad.h>
91#include <pcb_marker.h>
95#include <kiface_ids.h>
98#include <pcbnew_settings.h>
99#include <pcbplot.h>
100#include <pcb_plotter.h>
101#include <pcb_edit_frame.h>
102#include <pcb_track.h>
103#include <pgm_base.h>
106#include <project_pcb.h>
109#include <reporter.h>
110#include <scoped_set_reset.h>
111#include <progress_reporter.h>
113#include <export_vrml.h>
114#include <kiplatform/io.h>
121#include <dialogs/dialog_plot.h>
126#include <paths.h>
128
129#include <locale_io.h>
130#include <confirm.h>
131
132
133#ifdef _WIN32
134#ifdef TRANSPARENT
135#undef TRANSPARENT
136#endif
137#endif
138
139
141 JOB_DISPATCHER( aKiway ),
142 m_cliBoard( nullptr ),
143 m_toolManager( nullptr )
144{
145 Register( "3d", std::bind( &PCBNEW_JOBS_HANDLER::JobExportStep, this, std::placeholders::_1 ),
146 [aKiway]( JOB* job, wxWindow* aParent ) -> bool
147 {
148 JOB_EXPORT_PCB_3D* svgJob = dynamic_cast<JOB_EXPORT_PCB_3D*>( job );
149
150 PCB_EDIT_FRAME* editFrame =
151 dynamic_cast<PCB_EDIT_FRAME*>( aKiway->Player( FRAME_PCB_EDITOR, false ) );
152
153 wxCHECK( svgJob && editFrame, false );
154
155 DIALOG_EXPORT_STEP dlg( editFrame, aParent, "", svgJob );
156 return dlg.ShowModal() == wxID_OK;
157 } );
158 Register( "render", std::bind( &PCBNEW_JOBS_HANDLER::JobExportRender, this, std::placeholders::_1 ),
159 []( JOB* job, wxWindow* aParent ) -> bool
160 {
161 JOB_PCB_RENDER* renderJob = dynamic_cast<JOB_PCB_RENDER*>( job );
162
163 wxCHECK( renderJob, false );
164
165 DIALOG_RENDER_JOB dlg( aParent, renderJob );
166 return dlg.ShowModal() == wxID_OK;
167 } );
168 Register( "upgrade", std::bind( &PCBNEW_JOBS_HANDLER::JobUpgrade, this, std::placeholders::_1 ),
169 []( JOB* job, wxWindow* aParent ) -> bool
170 {
171 return true;
172 } );
173 Register( "pcb_import", std::bind( &PCBNEW_JOBS_HANDLER::JobImport, this, std::placeholders::_1 ),
174 []( JOB* job, wxWindow* aParent ) -> bool
175 {
176 return true;
177 } );
178 Register( "svg", std::bind( &PCBNEW_JOBS_HANDLER::JobExportSvg, this, std::placeholders::_1 ),
179 [aKiway]( JOB* job, wxWindow* aParent ) -> bool
180 {
181 JOB_EXPORT_PCB_SVG* svgJob = dynamic_cast<JOB_EXPORT_PCB_SVG*>( job );
182
183 PCB_EDIT_FRAME* editFrame =
184 dynamic_cast<PCB_EDIT_FRAME*>( aKiway->Player( FRAME_PCB_EDITOR, false ) );
185
186 wxCHECK( svgJob && editFrame, false );
187
188 DIALOG_PLOT dlg( editFrame, aParent, svgJob );
189 return dlg.ShowModal() == wxID_OK;
190 } );
191 Register( "gencad", std::bind( &PCBNEW_JOBS_HANDLER::JobExportGencad, this, std::placeholders::_1 ),
192 [aKiway]( JOB* job, wxWindow* aParent ) -> bool
193 {
194 JOB_EXPORT_PCB_GENCAD* gencadJob = dynamic_cast<JOB_EXPORT_PCB_GENCAD*>( job );
195
196 PCB_EDIT_FRAME* editFrame =
197 dynamic_cast<PCB_EDIT_FRAME*>( aKiway->Player( FRAME_PCB_EDITOR, false ) );
198
199 wxCHECK( gencadJob && editFrame, false );
200
201 DIALOG_GENCAD_EXPORT_OPTIONS dlg( editFrame, gencadJob->GetSettingsDialogTitle(), gencadJob );
202 return dlg.ShowModal() == wxID_OK;
203 } );
204 Register( "dxf", std::bind( &PCBNEW_JOBS_HANDLER::JobExportDxf, this, std::placeholders::_1 ),
205 [aKiway]( JOB* job, wxWindow* aParent ) -> bool
206 {
207 JOB_EXPORT_PCB_DXF* dxfJob = dynamic_cast<JOB_EXPORT_PCB_DXF*>( job );
208
209 PCB_EDIT_FRAME* editFrame =
210 dynamic_cast<PCB_EDIT_FRAME*>( aKiway->Player( FRAME_PCB_EDITOR, false ) );
211
212 wxCHECK( dxfJob && editFrame, false );
213
214 DIALOG_PLOT dlg( editFrame, aParent, dxfJob );
215 return dlg.ShowModal() == wxID_OK;
216 } );
217 Register( "pdf", std::bind( &PCBNEW_JOBS_HANDLER::JobExportPdf, this, std::placeholders::_1 ),
218 [aKiway]( JOB* job, wxWindow* aParent ) -> bool
219 {
220 JOB_EXPORT_PCB_PDF* pdfJob = dynamic_cast<JOB_EXPORT_PCB_PDF*>( job );
221
222 PCB_EDIT_FRAME* editFrame =
223 dynamic_cast<PCB_EDIT_FRAME*>( aKiway->Player( FRAME_PCB_EDITOR, false ) );
224
225 wxCHECK( pdfJob && editFrame, false );
226
227 DIALOG_PLOT dlg( editFrame, aParent, pdfJob );
228 return dlg.ShowModal() == wxID_OK;
229 } );
230 Register( "png", std::bind( &PCBNEW_JOBS_HANDLER::JobExportPng, this, std::placeholders::_1 ),
231 [aKiway]( JOB* job, wxWindow* aParent ) -> bool
232 {
233 JOB_EXPORT_PCB_PNG* pngJob = dynamic_cast<JOB_EXPORT_PCB_PNG*>( job );
234
235 PCB_EDIT_FRAME* editFrame =
236 dynamic_cast<PCB_EDIT_FRAME*>( aKiway->Player( FRAME_PCB_EDITOR, false ) );
237
238 wxCHECK( pngJob && editFrame, false );
239
240 DIALOG_PLOT dlg( editFrame, aParent, pngJob );
241 return dlg.ShowModal() == wxID_OK;
242 } );
243 Register( "ps", std::bind( &PCBNEW_JOBS_HANDLER::JobExportPs, this, std::placeholders::_1 ),
244 [aKiway]( JOB* job, wxWindow* aParent ) -> bool
245 {
246 JOB_EXPORT_PCB_PS* psJob = dynamic_cast<JOB_EXPORT_PCB_PS*>( job );
247
248 PCB_EDIT_FRAME* editFrame =
249 dynamic_cast<PCB_EDIT_FRAME*>( aKiway->Player( FRAME_PCB_EDITOR, false ) );
250
251 wxCHECK( psJob && editFrame, false );
252
253 DIALOG_PLOT dlg( editFrame, aParent, psJob );
254 return dlg.ShowModal() == wxID_OK;
255 } );
256 Register( "stats", std::bind( &PCBNEW_JOBS_HANDLER::JobExportStats, this, std::placeholders::_1 ),
257 [aKiway]( JOB* job, wxWindow* aParent ) -> bool
258 {
259 JOB_EXPORT_PCB_STATS* statsJob = dynamic_cast<JOB_EXPORT_PCB_STATS*>( job );
260
261 PCB_EDIT_FRAME* editFrame =
262 dynamic_cast<PCB_EDIT_FRAME*>( aKiway->Player( FRAME_PCB_EDITOR, false ) );
263
264 wxCHECK( statsJob && editFrame, false );
265
266 if( statsJob->m_filename.IsEmpty() && editFrame->GetBoard() )
267 {
268 wxFileName boardName = editFrame->GetBoard()->GetFileName();
269 statsJob->m_filename = boardName.GetFullPath();
270 }
271
272 wxWindow* parent = aParent ? aParent : static_cast<wxWindow*>( editFrame );
273
274 DIALOG_BOARD_STATS_JOB dlg( parent, statsJob );
275
276 return dlg.ShowModal() == wxID_OK;
277 } );
278 Register( "gerber", std::bind( &PCBNEW_JOBS_HANDLER::JobExportGerber, this, std::placeholders::_1 ),
279 [aKiway]( JOB* job, wxWindow* aParent ) -> bool
280 {
281 JOB_EXPORT_PCB_GERBER* gJob = dynamic_cast<JOB_EXPORT_PCB_GERBER*>( job );
282
283 PCB_EDIT_FRAME* editFrame =
284 dynamic_cast<PCB_EDIT_FRAME*>( aKiway->Player( FRAME_PCB_EDITOR, false ) );
285
286 wxCHECK( gJob && editFrame, false );
287
288 DIALOG_PLOT dlg( editFrame, aParent, gJob );
289 return dlg.ShowModal() == wxID_OK;
290 } );
291 Register( "gerbers", std::bind( &PCBNEW_JOBS_HANDLER::JobExportGerbers, this, std::placeholders::_1 ),
292 [aKiway]( JOB* job, wxWindow* aParent ) -> bool
293 {
294 JOB_EXPORT_PCB_GERBERS* gJob = dynamic_cast<JOB_EXPORT_PCB_GERBERS*>( job );
295
296 PCB_EDIT_FRAME* editFrame =
297 dynamic_cast<PCB_EDIT_FRAME*>( aKiway->Player( FRAME_PCB_EDITOR, false ) );
298
299 wxCHECK( gJob && editFrame, false );
300
301 DIALOG_PLOT dlg( editFrame, aParent, gJob );
302 return dlg.ShowModal() == wxID_OK;
303 } );
304 Register(
305 "hpgl",
306 [&]( JOB* aJob )
307 {
308 m_reporter->Report( _( "Plotting to HPGL is no longer supported as of KiCad 10.0.\n" ),
311 },
312 [aKiway]( JOB* job, wxWindow* aParent ) -> bool
313 {
314 PCB_EDIT_FRAME* editFrame = dynamic_cast<PCB_EDIT_FRAME*>( aKiway->Player( FRAME_PCB_EDITOR, false ) );
315
316 wxCHECK( editFrame, false );
317
318 DisplayErrorMessage( editFrame, _( "Plotting to HPGL is no longer supported as of KiCad 10.0." ) );
319 return false;
320 } );
321 Register( "drill", std::bind( &PCBNEW_JOBS_HANDLER::JobExportDrill, this, std::placeholders::_1 ),
322 [aKiway]( JOB* job, wxWindow* aParent ) -> bool
323 {
324 JOB_EXPORT_PCB_DRILL* drillJob = dynamic_cast<JOB_EXPORT_PCB_DRILL*>( job );
325
326 PCB_EDIT_FRAME* editFrame =
327 dynamic_cast<PCB_EDIT_FRAME*>( aKiway->Player( FRAME_PCB_EDITOR, false ) );
328
329 wxCHECK( drillJob && editFrame, false );
330
331 DIALOG_GENDRILL dlg( editFrame, drillJob, aParent );
332 return dlg.ShowModal() == wxID_OK;
333 } );
334 Register( "pos", std::bind( &PCBNEW_JOBS_HANDLER::JobExportPos, this, std::placeholders::_1 ),
335 [aKiway]( JOB* job, wxWindow* aParent ) -> bool
336 {
337 JOB_EXPORT_PCB_POS* posJob = dynamic_cast<JOB_EXPORT_PCB_POS*>( job );
338
339 PCB_EDIT_FRAME* editFrame =
340 dynamic_cast<PCB_EDIT_FRAME*>( aKiway->Player( FRAME_PCB_EDITOR, false ) );
341
342 wxCHECK( posJob && editFrame, false );
343
344 DIALOG_GEN_FOOTPRINT_POSITION dlg( posJob, editFrame, aParent );
345 return dlg.ShowModal() == wxID_OK;
346 } );
347 Register( "fpupgrade", std::bind( &PCBNEW_JOBS_HANDLER::JobExportFpUpgrade, this, std::placeholders::_1 ),
348 []( JOB* job, wxWindow* aParent ) -> bool
349 {
350 return true;
351 } );
352 Register( "fpsvg", std::bind( &PCBNEW_JOBS_HANDLER::JobExportFpSvg, this, std::placeholders::_1 ),
353 []( JOB* job, wxWindow* aParent ) -> bool
354 {
355 return true;
356 } );
357 Register( "pcb_diff", std::bind( &PCBNEW_JOBS_HANDLER::JobDiff, this, std::placeholders::_1 ),
358 []( JOB* job, wxWindow* aParent ) -> bool
359 {
360 return true;
361 } );
362 Register( "fp_diff", std::bind( &PCBNEW_JOBS_HANDLER::JobFpDiff, this, std::placeholders::_1 ),
363 []( JOB* job, wxWindow* aParent ) -> bool
364 {
365 return true;
366 } );
367 Register( "drc", std::bind( &PCBNEW_JOBS_HANDLER::JobExportDrc, this, std::placeholders::_1 ),
368 []( JOB* job, wxWindow* aParent ) -> bool
369 {
370 JOB_PCB_DRC* drcJob = dynamic_cast<JOB_PCB_DRC*>( job );
371
372 wxCHECK( drcJob, false );
373
374 DIALOG_DRC_JOB_CONFIG dlg( aParent, drcJob );
375 return dlg.ShowModal() == wxID_OK;
376 } );
377 Register( "ipc2581", std::bind( &PCBNEW_JOBS_HANDLER::JobExportIpc2581, this, std::placeholders::_1 ),
378 [aKiway]( JOB* job, wxWindow* aParent ) -> bool
379 {
380 JOB_EXPORT_PCB_IPC2581* ipcJob = dynamic_cast<JOB_EXPORT_PCB_IPC2581*>( job );
381
382 PCB_EDIT_FRAME* editFrame =
383 dynamic_cast<PCB_EDIT_FRAME*>( aKiway->Player( FRAME_PCB_EDITOR, false ) );
384
385 wxCHECK( ipcJob && editFrame, false );
386
387 DIALOG_EXPORT_2581 dlg( ipcJob, editFrame, aParent );
388 return dlg.ShowModal() == wxID_OK;
389 } );
390 Register( "ipcd356", std::bind( &PCBNEW_JOBS_HANDLER::JobExportIpcD356, this, std::placeholders::_1 ),
391 []( JOB* job, wxWindow* aParent ) -> bool
392 {
393 return true;
394 } );
395 Register( "odb", std::bind( &PCBNEW_JOBS_HANDLER::JobExportOdb, this, std::placeholders::_1 ),
396 [aKiway]( JOB* job, wxWindow* aParent ) -> bool
397 {
398 JOB_EXPORT_PCB_ODB* odbJob = dynamic_cast<JOB_EXPORT_PCB_ODB*>( job );
399
400 PCB_EDIT_FRAME* editFrame =
401 dynamic_cast<PCB_EDIT_FRAME*>( aKiway->Player( FRAME_PCB_EDITOR, false ) );
402
403 wxCHECK( odbJob && editFrame, false );
404
405 DIALOG_EXPORT_ODBPP dlg( odbJob, editFrame, aParent );
406 return dlg.ShowModal() == wxID_OK;
407 } );
408}
409
410
414
415
417{
418 m_cliBoard.reset();
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.reset( loadBoardFromPath( pcbPath ) );
511
512 brd = m_cliBoard.get();
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 m_cliBoard.reset( loadBoardFromPath( aPath ) );
524 brd = m_cliBoard.get();
525 }
526
527 if( !brd )
528 {
529 wxString msg = _( "Failed to load board" );
530
531 if( !loadError.IsEmpty() )
532 msg += wxString::Format( wxS( ": %s" ), loadError );
533
534 m_reporter->Report( msg + '\n', RPT_SEVERITY_ERROR );
535 }
536
537 return brd;
538}
539
540
541LSEQ PCBNEW_JOBS_HANDLER::convertLayerArg( wxString& aLayerString, BOARD* aBoard ) const
542{
543 std::map<wxString, LSET> layerUserMasks;
544 std::map<wxString, LSET> layerMasks;
545 std::map<wxString, LSET> layerGuiMasks;
546
547 // Build list of layer names and their layer mask:
548 for( PCB_LAYER_ID layer : LSET::AllLayersMask() )
549 {
550 // Add user layer name
551 if( aBoard )
552 layerUserMasks[aBoard->GetLayerName( layer )] = LSET( { layer } );
553
554 // Add layer name used in pcb files
555 layerMasks[LSET::Name( layer )] = LSET( { layer } );
556 // Add layer name using GUI canonical layer name
557 layerGuiMasks[LayerName( layer )] = LSET( { layer } );
558 }
559
560 // Add list of grouped layer names used in pcb files
561 layerMasks[wxT( "*" )] = LSET::AllLayersMask();
562 layerMasks[wxT( "*.Cu" )] = LSET::AllCuMask();
563 layerMasks[wxT( "*In.Cu" )] = LSET::InternalCuMask();
564 layerMasks[wxT( "F&B.Cu" )] = LSET( { F_Cu, B_Cu } );
565 layerMasks[wxT( "*.Adhes" )] = LSET( { B_Adhes, F_Adhes } );
566 layerMasks[wxT( "*.Paste" )] = LSET( { B_Paste, F_Paste } );
567 layerMasks[wxT( "*.Mask" )] = LSET( { B_Mask, F_Mask } );
568 layerMasks[wxT( "*.SilkS" )] = LSET( { B_SilkS, F_SilkS } );
569 layerMasks[wxT( "*.Fab" )] = LSET( { B_Fab, F_Fab } );
570 layerMasks[wxT( "*.CrtYd" )] = LSET( { B_CrtYd, F_CrtYd } );
571
572 // Add list of grouped layer names using GUI canonical layer names
573 layerGuiMasks[wxT( "*.Adhesive" )] = LSET( { B_Adhes, F_Adhes } );
574 layerGuiMasks[wxT( "*.Silkscreen" )] = LSET( { B_SilkS, F_SilkS } );
575 layerGuiMasks[wxT( "*.Courtyard" )] = LSET( { B_CrtYd, F_CrtYd } );
576
577 LSEQ layerMask;
578
579 auto pushLayers = [&]( const LSET& layerSet )
580 {
581 for( PCB_LAYER_ID layer : layerSet.Seq() )
582 layerMask.push_back( layer );
583 };
584
585 if( !aLayerString.IsEmpty() )
586 {
587 wxStringTokenizer layerTokens( aLayerString, "," );
588
589 while( layerTokens.HasMoreTokens() )
590 {
591 std::string token = TO_UTF8( layerTokens.GetNextToken().Trim( true ).Trim( false ) );
592
593 if( layerUserMasks.contains( token ) )
594 pushLayers( layerUserMasks.at( token ) );
595 else if( layerMasks.count( token ) )
596 pushLayers( layerMasks.at( token ) );
597 else if( layerGuiMasks.count( token ) )
598 pushLayers( layerGuiMasks.at( token ) );
599 else
600 m_reporter->Report( wxString::Format( _( "Invalid layer name '%s'\n" ), token ) );
601 }
602 }
603
604 return layerMask;
605}
606
607
609{
610 JOB_EXPORT_PCB_3D* aStepJob = dynamic_cast<JOB_EXPORT_PCB_3D*>( aJob );
611
612 if( aStepJob == nullptr )
614
615 BOARD* brd = getBoard( aStepJob->m_filename );
616
617 if( !brd )
619
620 if( !aStepJob->m_variant.IsEmpty() )
621 brd->SetCurrentVariant( aStepJob->m_variant );
622
623 if( aStepJob->GetConfiguredOutputPath().IsEmpty() )
624 {
625 wxFileName fn = brd->GetFileName();
626 fn.SetName( fn.GetName() );
627
628 switch( aStepJob->m_format )
629 {
639 default:
640 m_reporter->Report( _( "Unknown export format" ), RPT_SEVERITY_ERROR );
641 return CLI::EXIT_CODES::ERR_UNKNOWN; // shouldnt have gotten here
642 }
643
644 aStepJob->SetWorkingOutputPath( fn.GetFullName() );
645 }
646
647 wxString outPath = resolveJobOutputPath( aJob, brd );
648
649 if( !PATHS::EnsurePathExists( outPath, true ) )
650 {
651 m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
653 }
654
656 {
657 double scale = 0.0;
658 switch( aStepJob->m_vrmlUnits )
659 {
660 case JOB_EXPORT_PCB_3D::VRML_UNITS::MM: scale = 1.0; break;
661 case JOB_EXPORT_PCB_3D::VRML_UNITS::METERS: scale = 0.001; break;
662 case JOB_EXPORT_PCB_3D::VRML_UNITS::TENTHS: scale = 10.0 / 25.4; break;
663 case JOB_EXPORT_PCB_3D::VRML_UNITS::INCH: scale = 1.0 / 25.4; break;
664 }
665
666 EXPORTER_VRML vrmlExporter( brd );
667 wxString messages;
668
669 double originX = pcbIUScale.IUTomm( aStepJob->m_3dparams.m_Origin.x );
670 double originY = pcbIUScale.IUTomm( aStepJob->m_3dparams.m_Origin.y );
671
672 if( !aStepJob->m_hasUserOrigin )
673 {
674 BOX2I bbox = brd->ComputeBoundingBox( true, true );
675 originX = pcbIUScale.IUTomm( bbox.GetCenter().x );
676 originY = pcbIUScale.IUTomm( bbox.GetCenter().y );
677 }
678
679 bool success = vrmlExporter.ExportVRML_File(
680 brd->GetProject(), &messages, outPath, scale, aStepJob->m_3dparams.m_IncludeUnspecified,
681 aStepJob->m_3dparams.m_IncludeDNP, !aStepJob->m_vrmlModelDir.IsEmpty(), aStepJob->m_vrmlRelativePaths,
682 aStepJob->m_vrmlModelDir, originX, originY );
683
684 if( success )
685 {
686 m_reporter->Report( wxString::Format( _( "Successfully exported VRML to %s" ), outPath ),
688 }
689 else
690 {
691 m_reporter->Report( _( "Error exporting VRML" ), RPT_SEVERITY_ERROR );
693 }
694 }
695 else
696 {
697 EXPORTER_STEP_PARAMS params = aStepJob->m_3dparams;
698
699 switch( aStepJob->m_format )
700 {
710 default:
711 m_reporter->Report( _( "Unknown export format" ), RPT_SEVERITY_ERROR );
712 return CLI::EXIT_CODES::ERR_UNKNOWN; // shouldnt have gotten here
713 }
714
715 EXPORTER_STEP stepExporter( brd, params, m_reporter );
716 stepExporter.m_outputFile = aStepJob->GetFullOutputPath( brd->GetProject() );
717
718 if( !stepExporter.Export() )
720 }
721
722 return CLI::EXIT_CODES::OK;
723}
724
725
727{
728 JOB_PCB_RENDER* aRenderJob = dynamic_cast<JOB_PCB_RENDER*>( aJob );
729
730 if( aRenderJob == nullptr )
732
733 // Reject width and height being invalid
734 // Final bit of sanity because this can blow things up
735 if( aRenderJob->m_width <= 0 || aRenderJob->m_height <= 0 )
736 {
737 m_reporter->Report( _( "Invalid image dimensions" ), RPT_SEVERITY_ERROR );
739 }
740
741 BOARD* brd = getBoard( aRenderJob->m_filename );
742
743 if( !brd )
745
746 if( !aRenderJob->m_variant.IsEmpty() )
747 brd->SetCurrentVariant( aRenderJob->m_variant );
748
749 if( aRenderJob->GetConfiguredOutputPath().IsEmpty() )
750 {
751 wxFileName fn = brd->GetFileName();
752
753 switch( aRenderJob->m_format )
754 {
757 default:
758 m_reporter->Report( _( "Unknown export format" ), RPT_SEVERITY_ERROR );
759 return CLI::EXIT_CODES::ERR_UNKNOWN; // shouldnt have gotten here
760 }
761
762 // set the name to board name + "side", its lazy but its hard to generate anything truely unique
763 // incase someone is doing this in a jobset with multiple jobs, they should be setting the output themselves
764 // or we do a hash based on all the options
765 fn.SetName( wxString::Format( "%s-%d", fn.GetName(), static_cast<int>( aRenderJob->m_side ) ) );
766
767 aRenderJob->SetWorkingOutputPath( fn.GetFullName() );
768 }
769
770 wxString outPath = resolveJobOutputPath( aJob, brd );
771
772 if( !PATHS::EnsurePathExists( outPath, true ) )
773 {
774 m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
776 }
777
778 BOARD_ADAPTER boardAdapter;
779
780 boardAdapter.SetBoard( brd );
781 boardAdapter.m_IsBoardView = false;
782
784
786 {
787 cfg.m_Render = userCfg->m_Render;
788 cfg.m_Camera = userCfg->m_Camera;
789 cfg.m_LayerPresets = userCfg->m_LayerPresets;
790 }
791
792 if( aRenderJob->m_appearancePreset.empty() )
793 {
794 // Force display 3D models
796 cfg.m_Render.show_footprints_dnp = true;
800 }
801
802 if( aRenderJob->m_quality == JOB_PCB_RENDER::QUALITY::BASIC )
803 {
804 // Silkscreen is pixelated without antialiasing
806
807 cfg.m_Render.raytrace_backfloor = aRenderJob->m_floor;
808 cfg.m_Render.raytrace_post_processing = aRenderJob->m_floor;
809
811 cfg.m_Render.raytrace_reflections = false;
812 cfg.m_Render.raytrace_shadows = aRenderJob->m_floor;
813
814 // Better colors
816
817 // Tracks below soldermask are not visible without refractions
820 }
821 else if( aRenderJob->m_quality == JOB_PCB_RENDER::QUALITY::HIGH )
822 {
824 cfg.m_Render.raytrace_backfloor = true;
828 cfg.m_Render.raytrace_shadows = true;
831 }
832 else if( aRenderJob->m_quality == JOB_PCB_RENDER::QUALITY::JOB_SETTINGS )
833 {
835 cfg.m_Render.raytrace_backfloor = aRenderJob->m_floor;
838 }
839
841 aRenderJob->m_lightTopIntensity.z, 1.0 );
842
844 COLOR4D( aRenderJob->m_lightBottomIntensity.x, aRenderJob->m_lightBottomIntensity.y,
845 aRenderJob->m_lightBottomIntensity.z, 1.0 );
846
848 COLOR4D( aRenderJob->m_lightCameraIntensity.x, aRenderJob->m_lightCameraIntensity.y,
849 aRenderJob->m_lightCameraIntensity.z, 1.0 );
850
851 COLOR4D lightColor( aRenderJob->m_lightSideIntensity.x, aRenderJob->m_lightSideIntensity.y,
852 aRenderJob->m_lightSideIntensity.z, 1.0 );
853
855 lightColor, lightColor, lightColor, lightColor, lightColor, lightColor, lightColor, lightColor,
856 };
857
858 int sideElevation = aRenderJob->m_lightSideElevation;
859
861 sideElevation, sideElevation, sideElevation, sideElevation,
862 -sideElevation, -sideElevation, -sideElevation, -sideElevation,
863 };
864
866 45, 135, 225, 315, 45, 135, 225, 315,
867 };
868
869 cfg.m_CurrentPreset = aRenderJob->m_appearancePreset;
871 boardAdapter.m_Cfg = &cfg;
872
873 // Apply the preset's layer visibility and colors to the render settings
874 if( !aRenderJob->m_appearancePreset.empty() )
875 {
876 wxString presetName = wxString::FromUTF8( aRenderJob->m_appearancePreset );
877
878 if( presetName == FOLLOW_PCB || presetName == FOLLOW_PLOT_SETTINGS )
879 {
880 boardAdapter.SetVisibleLayers( boardAdapter.GetVisibleLayers() );
881 }
882 else if( LAYER_PRESET_3D* preset = cfg.FindPreset( presetName ) )
883 {
884 boardAdapter.SetVisibleLayers( preset->layers );
885 boardAdapter.SetLayerColors( preset->colors );
886
887 if( preset->name.Lower() == _( "legacy colors" ) )
888 cfg.m_UseStackupColors = false;
889 }
890 }
891
894 && aRenderJob->m_format == JOB_PCB_RENDER::FORMAT::PNG ) )
895 {
896 boardAdapter.m_ColorOverrides[LAYER_3D_BACKGROUND_TOP] = COLOR4D( 1.0, 1.0, 1.0, 0.0 );
897 boardAdapter.m_ColorOverrides[LAYER_3D_BACKGROUND_BOTTOM] = COLOR4D( 1.0, 1.0, 1.0, 0.0 );
898 }
899
901
902 static std::map<JOB_PCB_RENDER::SIDE, VIEW3D_TYPE> s_viewCmdMap = {
909 };
910
912
913 wxSize windowSize( aRenderJob->m_width, aRenderJob->m_height );
914 TRACK_BALL camera( 2 * RANGE_SCALE_3D );
915
916 camera.SetProjection( projection );
917 camera.SetCurWindowSize( windowSize );
918
919 RENDER_3D_RAYTRACE_RAM raytrace( boardAdapter, camera );
920 raytrace.SetCurWindowSize( windowSize );
921
922 for( bool first = true; raytrace.Redraw( false, m_reporter, m_reporter ); first = false )
923 {
924 if( first )
925 {
926 const float cmTo3D = boardAdapter.BiuTo3dUnits() * pcbIUScale.mmToIU( 10.0 );
927
928 // First redraw resets lookat point to the board center, so set up the camera here
929 camera.ViewCommand_T1( s_viewCmdMap[aRenderJob->m_side] );
930
931 camera.SetLookAtPos_T1( camera.GetLookAtPos_T1()
932 + SFVEC3F( aRenderJob->m_pivot.x, aRenderJob->m_pivot.y, aRenderJob->m_pivot.z )
933 * cmTo3D );
934
935 camera.Pan_T1( SFVEC3F( aRenderJob->m_pan.x, aRenderJob->m_pan.y, aRenderJob->m_pan.z ) );
936
937 camera.Zoom_T1( aRenderJob->m_zoom );
938
939 camera.RotateX_T1( DEG2RAD( aRenderJob->m_rotation.x ) );
940 camera.RotateY_T1( DEG2RAD( aRenderJob->m_rotation.y ) );
941 camera.RotateZ_T1( DEG2RAD( aRenderJob->m_rotation.z ) );
942
943 camera.Interpolate( 1.0f );
944 camera.SetT0_and_T1_current_T();
945 camera.ParametersChanged();
946 }
947 }
948
949 uint8_t* rgbaBuffer = raytrace.GetBuffer();
950 wxSize realSize = raytrace.GetRealBufferSize();
951 bool success = !!rgbaBuffer;
952
953 if( rgbaBuffer )
954 {
955 const unsigned int wxh = realSize.x * realSize.y;
956
957 unsigned char* rgbBuffer = (unsigned char*) malloc( wxh * 3 );
958 unsigned char* alphaBuffer = (unsigned char*) malloc( wxh );
959
960 unsigned char* rgbaPtr = rgbaBuffer;
961 unsigned char* rgbPtr = rgbBuffer;
962 unsigned char* alphaPtr = alphaBuffer;
963
964 for( int y = 0; y < realSize.y; y++ )
965 {
966 for( int x = 0; x < realSize.x; x++ )
967 {
968 rgbPtr[0] = rgbaPtr[0];
969 rgbPtr[1] = rgbaPtr[1];
970 rgbPtr[2] = rgbaPtr[2];
971 alphaPtr[0] = rgbaPtr[3];
972
973 rgbaPtr += 4;
974 rgbPtr += 3;
975 alphaPtr += 1;
976 }
977 }
978
979 wxImage image( realSize );
980 image.SetData( rgbBuffer );
981 image.SetAlpha( alphaBuffer );
982 image = image.Mirror( false );
983
984 image.SetOption( wxIMAGE_OPTION_QUALITY, 90 );
985 image.SaveFile( outPath,
986 aRenderJob->m_format == JOB_PCB_RENDER::FORMAT::PNG ? wxBITMAP_TYPE_PNG : wxBITMAP_TYPE_JPEG );
987 }
988
989 if( success )
990 {
991 m_reporter->Report( _( "Successfully created 3D render image" ) + wxS( "\n" ), RPT_SEVERITY_INFO );
992 return CLI::EXIT_CODES::OK;
993 }
994 else
995 {
996 m_reporter->Report( _( "Error creating 3D render image" ) + wxS( "\n" ), RPT_SEVERITY_ERROR );
998 }
999}
1000
1001
1003{
1004 JOB_EXPORT_PCB_SVG* aSvgJob = dynamic_cast<JOB_EXPORT_PCB_SVG*>( aJob );
1005
1006 if( aSvgJob == nullptr )
1008
1009 BOARD* brd = getBoard( aSvgJob->m_filename );
1010 TOOL_MANAGER* toolManager = getToolManager( brd );
1011
1012 if( !brd )
1014
1015 if( !aSvgJob->m_variant.IsEmpty() )
1016 brd->SetCurrentVariant( aSvgJob->m_variant );
1017
1019 {
1020 if( aSvgJob->GetConfiguredOutputPath().IsEmpty() )
1021 {
1022 wxFileName fn = brd->GetFileName();
1023 fn.SetName( fn.GetName() );
1025
1026 aSvgJob->SetWorkingOutputPath( fn.GetFullName() );
1027 }
1028 }
1029
1030 wxString outPath = resolveJobOutputPath( aJob, brd, &aSvgJob->m_drawingSheet );
1031
1033 {
1034 m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
1036 }
1037
1038 if( aSvgJob->m_checkZonesBeforePlot )
1039 {
1040 if( !toolManager->FindTool( ZONE_FILLER_TOOL_NAME ) )
1041 toolManager->RegisterTool( new ZONE_FILLER_TOOL );
1042
1043 toolManager->GetTool<ZONE_FILLER_TOOL>()->FillAllZones( nullptr, m_progressReporter, true );
1044 }
1045
1046 if( aSvgJob->m_argLayers )
1047 aSvgJob->m_plotLayerSequence = convertLayerArg( aSvgJob->m_argLayers.value(), brd );
1048
1049 if( aSvgJob->m_argCommonLayers )
1050 aSvgJob->m_plotOnAllLayersSequence = convertLayerArg( aSvgJob->m_argCommonLayers.value(), brd );
1051
1052 if( aSvgJob->m_plotLayerSequence.size() < 1 )
1053 {
1054 m_reporter->Report( _( "At least one layer must be specified\n" ), RPT_SEVERITY_ERROR );
1056 }
1057
1058 PCB_PLOT_PARAMS plotOpts;
1059 PCB_PLOTTER::PlotJobToPlotOpts( plotOpts, aSvgJob, *m_reporter );
1060
1061 PCB_PLOTTER plotter( brd, m_reporter, plotOpts );
1062
1063 std::optional<wxString> layerName;
1064 std::optional<wxString> sheetName;
1065 std::optional<wxString> sheetPath;
1066 std::vector<wxString> outputPaths;
1067
1069 {
1070 if( aJob->GetVarOverrides().contains( wxT( "LAYER" ) ) )
1071 layerName = aSvgJob->GetVarOverrides().at( wxT( "LAYER" ) );
1072
1073 if( aJob->GetVarOverrides().contains( wxT( "SHEETNAME" ) ) )
1074 sheetName = aSvgJob->GetVarOverrides().at( wxT( "SHEETNAME" ) );
1075
1076 if( aJob->GetVarOverrides().contains( wxT( "SHEETPATH" ) ) )
1077 sheetPath = aSvgJob->GetVarOverrides().at( wxT( "SHEETPATH" ) );
1078 }
1079
1080 if( !plotter.Plot( outPath, aSvgJob->m_plotLayerSequence, aSvgJob->m_plotOnAllLayersSequence, false,
1081 aSvgJob->m_genMode == JOB_EXPORT_PCB_SVG::GEN_MODE::SINGLE, layerName, sheetName, sheetPath,
1082 &outputPaths ) )
1083 {
1085 }
1086
1087 for( const wxString& outputPath : outputPaths )
1088 aSvgJob->AddOutput( outputPath );
1089
1090 return CLI::EXIT_CODES::OK;
1091}
1092
1093
1095{
1096 JOB_EXPORT_PCB_DXF* aDxfJob = dynamic_cast<JOB_EXPORT_PCB_DXF*>( aJob );
1097
1098 if( aDxfJob == nullptr )
1100
1101 BOARD* brd = getBoard( aDxfJob->m_filename );
1102
1103 if( !brd )
1105
1106 if( !aDxfJob->m_variant.IsEmpty() )
1107 brd->SetCurrentVariant( aDxfJob->m_variant );
1108
1109 TOOL_MANAGER* toolManager = getToolManager( brd );
1110
1111 if( aDxfJob->m_checkZonesBeforePlot )
1112 {
1113 if( !toolManager->FindTool( ZONE_FILLER_TOOL_NAME ) )
1114 toolManager->RegisterTool( new ZONE_FILLER_TOOL );
1115
1116 toolManager->GetTool<ZONE_FILLER_TOOL>()->FillAllZones( nullptr, m_progressReporter, true );
1117 }
1118
1119 if( aDxfJob->m_argLayers )
1120 aDxfJob->m_plotLayerSequence = convertLayerArg( aDxfJob->m_argLayers.value(), brd );
1121
1122 if( aDxfJob->m_argCommonLayers )
1123 aDxfJob->m_plotOnAllLayersSequence = convertLayerArg( aDxfJob->m_argCommonLayers.value(), brd );
1124
1125 if( aDxfJob->m_plotLayerSequence.size() < 1 )
1126 {
1127 m_reporter->Report( _( "At least one layer must be specified\n" ), RPT_SEVERITY_ERROR );
1129 }
1130
1132 {
1133 if( aDxfJob->GetConfiguredOutputPath().IsEmpty() )
1134 {
1135 wxFileName fn = brd->GetFileName();
1136 fn.SetName( fn.GetName() );
1138
1139 aDxfJob->SetWorkingOutputPath( fn.GetFullName() );
1140 }
1141 }
1142
1143 wxString outPath = resolveJobOutputPath( aJob, brd, &aDxfJob->m_drawingSheet );
1144
1146 {
1147 m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
1149 }
1150
1151 PCB_PLOT_PARAMS plotOpts;
1152 PCB_PLOTTER::PlotJobToPlotOpts( plotOpts, aDxfJob, *m_reporter );
1153
1154 PCB_PLOTTER plotter( brd, m_reporter, plotOpts );
1155
1156 std::optional<wxString> layerName;
1157 std::optional<wxString> sheetName;
1158 std::optional<wxString> sheetPath;
1159
1161 {
1162 if( aJob->GetVarOverrides().contains( wxT( "LAYER" ) ) )
1163 layerName = aDxfJob->GetVarOverrides().at( wxT( "LAYER" ) );
1164
1165 if( aJob->GetVarOverrides().contains( wxT( "SHEETNAME" ) ) )
1166 sheetName = aDxfJob->GetVarOverrides().at( wxT( "SHEETNAME" ) );
1167
1168 if( aJob->GetVarOverrides().contains( wxT( "SHEETPATH" ) ) )
1169 sheetPath = aDxfJob->GetVarOverrides().at( wxT( "SHEETPATH" ) );
1170 }
1171
1172 std::vector<wxString> outputPaths;
1173
1174 if( !plotter.Plot( outPath, aDxfJob->m_plotLayerSequence, aDxfJob->m_plotOnAllLayersSequence, false,
1175 aDxfJob->m_genMode == JOB_EXPORT_PCB_DXF::GEN_MODE::SINGLE, layerName, sheetName, sheetPath,
1176 &outputPaths ) )
1177 {
1179 }
1180
1181 for( const wxString& outputPath : outputPaths )
1182 aJob->AddOutput( outputPath );
1183
1184 return CLI::EXIT_CODES::OK;
1185}
1186
1187
1189{
1190 bool plotAllLayersOneFile = false;
1191 JOB_EXPORT_PCB_PDF* pdfJob = dynamic_cast<JOB_EXPORT_PCB_PDF*>( aJob );
1192
1193 if( pdfJob == nullptr )
1195
1196 BOARD* brd = getBoard( pdfJob->m_filename );
1197
1198 if( !brd )
1200
1201 if( !pdfJob->m_variant.IsEmpty() )
1202 brd->SetCurrentVariant( pdfJob->m_variant );
1203
1204 TOOL_MANAGER* toolManager = getToolManager( brd );
1205
1206 if( pdfJob->m_checkZonesBeforePlot )
1207 {
1208 if( !toolManager->FindTool( ZONE_FILLER_TOOL_NAME ) )
1209 toolManager->RegisterTool( new ZONE_FILLER_TOOL );
1210
1211 toolManager->GetTool<ZONE_FILLER_TOOL>()->FillAllZones( nullptr, m_progressReporter, true );
1212 }
1213
1214 if( pdfJob->m_argLayers )
1215 pdfJob->m_plotLayerSequence = convertLayerArg( pdfJob->m_argLayers.value(), brd );
1216
1217 if( pdfJob->m_argCommonLayers )
1218 pdfJob->m_plotOnAllLayersSequence = convertLayerArg( pdfJob->m_argCommonLayers.value(), brd );
1219
1221 plotAllLayersOneFile = true;
1222
1223 if( pdfJob->m_plotLayerSequence.size() < 1 )
1224 {
1225 m_reporter->Report( _( "At least one layer must be specified\n" ), RPT_SEVERITY_ERROR );
1227 }
1228
1229 const bool outputIsSingle = plotAllLayersOneFile || pdfJob->m_pdfSingle;
1230
1231 if( outputIsSingle && pdfJob->GetConfiguredOutputPath().IsEmpty() )
1232 {
1233 wxFileName fn = brd->GetFileName();
1234 fn.SetName( fn.GetName() );
1236
1237 pdfJob->SetWorkingOutputPath( fn.GetFullName() );
1238 }
1239
1240 wxString outPath = resolveJobOutputPath( pdfJob, brd, &pdfJob->m_drawingSheet );
1241
1242 PCB_PLOT_PARAMS plotOpts;
1243 PCB_PLOTTER::PlotJobToPlotOpts( plotOpts, pdfJob, *m_reporter );
1244
1245 PCB_PLOTTER pcbPlotter( brd, m_reporter, plotOpts );
1246
1247 if( !PATHS::EnsurePathExists( outPath, outputIsSingle ) )
1248 {
1249 m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
1251 }
1252
1253 std::optional<wxString> layerName;
1254 std::optional<wxString> sheetName;
1255 std::optional<wxString> sheetPath;
1256
1257 if( plotAllLayersOneFile )
1258 {
1259 if( pdfJob->GetVarOverrides().contains( wxT( "LAYER" ) ) )
1260 layerName = pdfJob->GetVarOverrides().at( wxT( "LAYER" ) );
1261
1262 if( pdfJob->GetVarOverrides().contains( wxT( "SHEETNAME" ) ) )
1263 sheetName = pdfJob->GetVarOverrides().at( wxT( "SHEETNAME" ) );
1264
1265 if( pdfJob->GetVarOverrides().contains( wxT( "SHEETPATH" ) ) )
1266 sheetPath = pdfJob->GetVarOverrides().at( wxT( "SHEETPATH" ) );
1267 }
1268
1269 std::vector<wxString> outputPaths;
1270
1271 if( !pcbPlotter.Plot( outPath, pdfJob->m_plotLayerSequence, pdfJob->m_plotOnAllLayersSequence, false,
1272 outputIsSingle, layerName, sheetName, sheetPath, &outputPaths ) )
1273 {
1275 }
1276
1277 for( const wxString& outputPath : outputPaths )
1278 aJob->AddOutput( outputPath );
1279
1280 return CLI::EXIT_CODES::OK;
1281}
1282
1283
1285{
1286 JOB_EXPORT_PCB_PNG* pngJob = dynamic_cast<JOB_EXPORT_PCB_PNG*>( aJob );
1287
1288 if( pngJob == nullptr )
1290
1291 BOARD* brd = getBoard( pngJob->m_filename );
1292
1293 if( !brd )
1295
1296 if( !pngJob->m_variant.IsEmpty() )
1297 brd->SetCurrentVariant( pngJob->m_variant );
1298
1299 TOOL_MANAGER* toolManager = getToolManager( brd );
1300
1301 if( pngJob->m_checkZonesBeforePlot )
1302 {
1303 if( !toolManager->FindTool( ZONE_FILLER_TOOL_NAME ) )
1304 toolManager->RegisterTool( new ZONE_FILLER_TOOL );
1305
1306 toolManager->GetTool<ZONE_FILLER_TOOL>()->FillAllZones( nullptr, m_progressReporter, true );
1307 }
1308
1309 if( pngJob->m_argLayers )
1310 pngJob->m_plotLayerSequence = convertLayerArg( pngJob->m_argLayers.value(), brd );
1311
1312 if( pngJob->m_argCommonLayers )
1313 pngJob->m_plotOnAllLayersSequence = convertLayerArg( pngJob->m_argCommonLayers.value(), brd );
1314
1315 if( pngJob->m_plotLayerSequence.size() < 1 )
1316 {
1317 m_reporter->Report( _( "At least one layer must be specified\n" ), RPT_SEVERITY_ERROR );
1319 }
1320
1321 if( pngJob->GetConfiguredOutputPath().IsEmpty() )
1322 {
1323 wxFileName fn = brd->GetFileName();
1324 fn.SetName( fn.GetName() );
1326
1327 pngJob->SetWorkingOutputPath( fn.GetFullName() );
1328 }
1329
1330 wxString outPath = resolveJobOutputPath( pngJob, brd, &pngJob->m_drawingSheet );
1331
1332 PCB_PLOT_PARAMS plotOpts;
1333 PCB_PLOTTER::PlotJobToPlotOpts( plotOpts, pngJob, *m_reporter );
1334
1335 PCB_PLOTTER pcbPlotter( brd, m_reporter, plotOpts );
1336
1337 if( !PATHS::EnsurePathExists( outPath, false ) )
1338 {
1339 m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
1341 }
1342
1343 std::vector<wxString> outputPaths;
1344
1345 if( !pcbPlotter.Plot( outPath, pngJob->m_plotLayerSequence, pngJob->m_plotOnAllLayersSequence, false, false,
1346 std::nullopt, std::nullopt, std::nullopt, &outputPaths ) )
1347 {
1349 }
1350
1351 for( const wxString& outputPath : outputPaths )
1352 aJob->AddOutput( outputPath );
1353
1354 return CLI::EXIT_CODES::OK;
1355}
1356
1357
1359{
1360 JOB_EXPORT_PCB_PS* psJob = dynamic_cast<JOB_EXPORT_PCB_PS*>( aJob );
1361
1362 if( psJob == nullptr )
1364
1365 BOARD* brd = getBoard( psJob->m_filename );
1366
1367 if( !brd )
1369
1370 if( !psJob->m_variant.IsEmpty() )
1371 brd->SetCurrentVariant( psJob->m_variant );
1372
1373 TOOL_MANAGER* toolManager = getToolManager( brd );
1374
1375 if( psJob->m_checkZonesBeforePlot )
1376 {
1377 if( !toolManager->FindTool( ZONE_FILLER_TOOL_NAME ) )
1378 toolManager->RegisterTool( new ZONE_FILLER_TOOL );
1379
1380 toolManager->GetTool<ZONE_FILLER_TOOL>()->FillAllZones( nullptr, m_progressReporter, true );
1381 }
1382
1383 if( psJob->m_argLayers )
1384 psJob->m_plotLayerSequence = convertLayerArg( psJob->m_argLayers.value(), brd );
1385
1386 if( psJob->m_argCommonLayers )
1387 psJob->m_plotOnAllLayersSequence = convertLayerArg( psJob->m_argCommonLayers.value(), brd );
1388
1389 if( psJob->m_plotLayerSequence.size() < 1 )
1390 {
1391 m_reporter->Report( _( "At least one layer must be specified\n" ), RPT_SEVERITY_ERROR );
1393 }
1394
1395 bool isSingle = psJob->m_genMode == JOB_EXPORT_PCB_PS::GEN_MODE::SINGLE;
1396
1397 if( isSingle )
1398 {
1399 if( psJob->GetConfiguredOutputPath().IsEmpty() )
1400 {
1401 wxFileName fn = brd->GetFileName();
1402 fn.SetName( fn.GetName() );
1404
1405 psJob->SetWorkingOutputPath( fn.GetFullName() );
1406 }
1407 }
1408
1409 wxString outPath = resolveJobOutputPath( psJob, brd, &psJob->m_drawingSheet );
1410
1411 if( !PATHS::EnsurePathExists( outPath, isSingle ) )
1412 {
1413 m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
1415 }
1416
1417 PCB_PLOT_PARAMS plotOpts;
1418 PCB_PLOTTER::PlotJobToPlotOpts( plotOpts, psJob, *m_reporter );
1419
1420 PCB_PLOTTER pcbPlotter( brd, m_reporter, plotOpts );
1421
1422 std::optional<wxString> layerName;
1423 std::optional<wxString> sheetName;
1424 std::optional<wxString> sheetPath;
1425
1426 if( isSingle )
1427 {
1428 if( aJob->GetVarOverrides().contains( wxT( "LAYER" ) ) )
1429 layerName = psJob->GetVarOverrides().at( wxT( "LAYER" ) );
1430
1431 if( aJob->GetVarOverrides().contains( wxT( "SHEETNAME" ) ) )
1432 sheetName = psJob->GetVarOverrides().at( wxT( "SHEETNAME" ) );
1433
1434 if( aJob->GetVarOverrides().contains( wxT( "SHEETPATH" ) ) )
1435 sheetPath = psJob->GetVarOverrides().at( wxT( "SHEETPATH" ) );
1436 }
1437
1438 std::vector<wxString> outputPaths;
1439
1440 if( !pcbPlotter.Plot( outPath, psJob->m_plotLayerSequence, psJob->m_plotOnAllLayersSequence, false, isSingle,
1441 layerName, sheetName, sheetPath, &outputPaths ) )
1442 {
1444 }
1445
1446 for( const wxString& outputPath : outputPaths )
1447 aJob->AddOutput( outputPath );
1448
1449 return CLI::EXIT_CODES::OK;
1450}
1451
1452
1454{
1455 int exitCode = CLI::EXIT_CODES::OK;
1456 JOB_EXPORT_PCB_GERBERS* aGerberJob = dynamic_cast<JOB_EXPORT_PCB_GERBERS*>( aJob );
1457
1458 if( aGerberJob == nullptr )
1460
1461 BOARD* brd = getBoard( aGerberJob->m_filename );
1462
1463 if( !brd )
1465
1466 if( !aGerberJob->m_variant.IsEmpty() )
1467 brd->SetCurrentVariant( aGerberJob->m_variant );
1468
1469 wxString outPath = resolveJobOutputPath( aJob, brd, &aGerberJob->m_drawingSheet );
1470
1471 if( !PATHS::EnsurePathExists( outPath, false ) )
1472 {
1473 m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
1475 }
1476
1477 TOOL_MANAGER* toolManager = getToolManager( brd );
1478
1479 if( aGerberJob->m_checkZonesBeforePlot )
1480 {
1481 if( !toolManager->FindTool( ZONE_FILLER_TOOL_NAME ) )
1482 toolManager->RegisterTool( new ZONE_FILLER_TOOL );
1483
1484 toolManager->GetTool<ZONE_FILLER_TOOL>()->FillAllZones( nullptr, m_progressReporter, true );
1485 }
1486
1487 bool hasLayerListSpecified = false; // will be true if the user layer list is not empty
1488
1489 if( aGerberJob->m_argLayers )
1490 {
1491 if( !aGerberJob->m_argLayers.value().empty() )
1492 {
1493 aGerberJob->m_plotLayerSequence = convertLayerArg( aGerberJob->m_argLayers.value(), brd );
1494 hasLayerListSpecified = true;
1495 }
1496 else
1497 {
1499 }
1500 }
1501
1502 if( aGerberJob->m_argCommonLayers )
1503 aGerberJob->m_plotOnAllLayersSequence = convertLayerArg( aGerberJob->m_argCommonLayers.value(), brd );
1504
1505 PCB_PLOT_PARAMS boardPlotOptions = brd->GetPlotOptions();
1506 GERBER_JOBFILE_WRITER jobfile_writer( brd );
1507
1508 wxString fileExt;
1509
1510 if( aGerberJob->m_useBoardPlotParams )
1511 {
1512 // The board plot options are saved with all copper layers enabled, even those that don't
1513 // exist in the current stackup. This is done so the layers are automatically enabled in the plot
1514 // dialog when the user enables them. We need to filter out these not-enabled layers here so
1515 // we don't plot 32 layers when we only have 4, etc.
1516 LSET plotLayers = ( boardPlotOptions.GetLayerSelection() & LSET::AllNonCuMask() )
1517 | ( brd->GetEnabledLayers() & LSET::AllCuMask() );
1518 aGerberJob->m_plotLayerSequence = plotLayers.SeqStackupForPlotting();
1519 aGerberJob->m_plotOnAllLayersSequence = boardPlotOptions.GetPlotOnAllLayersSequence();
1520 }
1521 else
1522 {
1523 // default to the board enabled layers, but only if the user has not specifed a layer list
1524 // ( m_plotLayerSequence can be empty with a broken user layer list)
1525 if( aGerberJob->m_plotLayerSequence.empty() && !hasLayerListSpecified )
1527 }
1528
1529 // Ensure layers to plot are restricted to enabled layers of the board to plot
1530 LSET layersToPlot = LSET( { aGerberJob->m_plotLayerSequence } ) & brd->GetEnabledLayers();
1531
1532 for( PCB_LAYER_ID layer : layersToPlot.UIOrder() )
1533 {
1534 LSEQ plotSequence;
1535
1536 // Base layer always gets plotted first.
1537 plotSequence.push_back( layer );
1538
1539 // Now all the "include on all" layers
1540 for( PCB_LAYER_ID layer_all : aGerberJob->m_plotOnAllLayersSequence )
1541 {
1542 // Don't plot the same layer more than once;
1543 if( find( plotSequence.begin(), plotSequence.end(), layer_all ) != plotSequence.end() )
1544 continue;
1545
1546 plotSequence.push_back( layer_all );
1547 }
1548
1549 // Pick the basename from the board file
1550 wxFileName fn( brd->GetFileName() );
1551 wxString layerName = brd->GetLayerName( layer );
1552 wxString sheetName;
1553 wxString sheetPath;
1554 PCB_PLOT_PARAMS plotOpts;
1555
1556 if( aGerberJob->m_useBoardPlotParams )
1557 plotOpts = boardPlotOptions;
1558 else
1559 PCB_PLOTTER::PlotJobToPlotOpts( plotOpts, aGerberJob, *m_reporter );
1560
1561 if( plotOpts.GetUseGerberProtelExtensions() )
1562 fileExt = GetGerberProtelExtension( layer );
1563 else
1565
1566 PCB_PLOTTER::BuildPlotFileName( &fn, outPath, layerName, fileExt );
1567 wxString fullname = fn.GetFullName();
1568
1569 if( m_progressReporter )
1570 {
1571 m_progressReporter->AdvancePhase( wxString::Format( _( "Exporting %s" ), fullname ) );
1572 m_progressReporter->KeepRefreshing();
1573 }
1574
1575 jobfile_writer.AddGbrFile( layer, fullname );
1576
1577 if( aJob->GetVarOverrides().contains( wxT( "LAYER" ) ) )
1578 layerName = aJob->GetVarOverrides().at( wxT( "LAYER" ) );
1579
1580 if( aJob->GetVarOverrides().contains( wxT( "SHEETNAME" ) ) )
1581 sheetName = aJob->GetVarOverrides().at( wxT( "SHEETNAME" ) );
1582
1583 if( aJob->GetVarOverrides().contains( wxT( "SHEETPATH" ) ) )
1584 sheetPath = aJob->GetVarOverrides().at( wxT( "SHEETPATH" ) );
1585
1586 // We are feeding it one layer at the start here to silence a logic check
1587 GERBER_PLOTTER* plotter;
1588 plotter = (GERBER_PLOTTER*) StartPlotBoard( brd, &plotOpts, layer, layerName, fn.GetFullPath(), sheetName,
1589 sheetPath );
1590
1591 if( plotter )
1592 {
1593 m_reporter->Report( wxString::Format( _( "Plotted to '%s'.\n" ), fn.GetFullPath() ), RPT_SEVERITY_ACTION );
1594
1595 PlotBoardLayers( brd, plotter, plotSequence, plotOpts );
1596 plotter->EndPlot();
1597 aJob->AddOutput( fn.GetFullPath() );
1598 }
1599 else
1600 {
1601 m_reporter->Report( wxString::Format( _( "Failed to plot to '%s'.\n" ), fn.GetFullPath() ),
1604 }
1605
1606 delete plotter;
1607 }
1608
1609 if( aGerberJob->m_createJobsFile )
1610 {
1611 wxFileName fn( brd->GetFileName() );
1612
1613 // Build gerber job file from basename
1615 jobfile_writer.CreateJobFile( fn.GetFullPath() );
1616 aJob->AddOutput( fn.GetFullPath() );
1617 }
1618
1619 return exitCode;
1620}
1621
1622
1624{
1625 JOB_EXPORT_PCB_GENCAD* aGencadJob = dynamic_cast<JOB_EXPORT_PCB_GENCAD*>( aJob );
1626
1627 if( aGencadJob == nullptr )
1629
1630 BOARD* brd = getBoard( aGencadJob->m_filename );
1631
1632 if( !brd )
1634
1635 GENCAD_EXPORTER exporter( brd );
1636
1637 VECTOR2I GencadOffset;
1638 VECTOR2I auxOrigin = brd->GetDesignSettings().GetAuxOrigin();
1639 GencadOffset.x = aGencadJob->m_useDrillOrigin ? auxOrigin.x : 0;
1640 GencadOffset.y = aGencadJob->m_useDrillOrigin ? auxOrigin.y : 0;
1641
1642 exporter.FlipBottomPads( aGencadJob->m_flipBottomPads );
1643 exporter.UsePinNamesUnique( aGencadJob->m_useUniquePins );
1644 exporter.UseIndividualShapes( aGencadJob->m_useIndividualShapes );
1645 exporter.SetPlotOffet( GencadOffset );
1646 exporter.StoreOriginCoordsInFile( aGencadJob->m_storeOriginCoords );
1647
1648 if( aGencadJob->GetConfiguredOutputPath().IsEmpty() )
1649 {
1650 wxFileName fn = brd->GetFileName();
1651 fn.SetName( fn.GetName() );
1652 fn.SetExt( FILEEXT::GencadFileExtension );
1653
1654 aGencadJob->SetWorkingOutputPath( fn.GetFullName() );
1655 }
1656
1657 wxString outPath = resolveJobOutputPath( aJob, brd );
1658
1659 if( !PATHS::EnsurePathExists( outPath, true ) )
1660 {
1661 m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
1663 }
1664
1665 if( !exporter.WriteFile( outPath ) )
1666 {
1667 m_reporter->Report( wxString::Format( _( "Failed to create file '%s'.\n" ), outPath ), RPT_SEVERITY_ERROR );
1668
1670 }
1671
1672 aJob->AddOutput( outPath );
1673 m_reporter->Report( _( "Successfully created genCAD file\n" ), RPT_SEVERITY_INFO );
1674
1675 return CLI::EXIT_CODES::OK;
1676}
1677
1678
1680{
1681 JOB_EXPORT_PCB_STATS* statsJob = dynamic_cast<JOB_EXPORT_PCB_STATS*>( aJob );
1682
1683 if( statsJob == nullptr )
1685
1686 BOARD* brd = getBoard( statsJob->m_filename );
1687
1688 if( !brd )
1690
1693
1698
1699 ComputeBoardStatistics( brd, options, data );
1700
1701 wxString projectName;
1702
1703 if( brd->GetProject() )
1704 projectName = brd->GetProject()->GetProjectName();
1705
1706 wxFileName boardFile = brd->GetFileName();
1707
1708 if( boardFile.GetName().IsEmpty() )
1709 boardFile = wxFileName( statsJob->m_filename );
1710
1712 UNITS_PROVIDER unitsProvider( pcbIUScale, unitsForReport );
1713
1714 wxString report;
1715
1717 report = FormatBoardStatisticsJson( data, brd, unitsProvider, projectName, boardFile.GetName() );
1718 else
1719 report = FormatBoardStatisticsReport( data, brd, unitsProvider, projectName, boardFile.GetName() );
1720
1721 if( statsJob->GetConfiguredOutputPath().IsEmpty() && statsJob->GetWorkingOutputPath().IsEmpty() )
1722 statsJob->SetDefaultOutputPath( boardFile.GetFullPath() );
1723
1724 wxString outPath = resolveJobOutputPath( aJob, brd );
1725
1726 if( !PATHS::EnsurePathExists( outPath, true ) )
1727 {
1728 m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
1730 }
1731
1732 FILE* outFile = wxFopen( outPath, wxS( "wt" ) );
1733
1734 if( !outFile )
1735 {
1736 m_reporter->Report( wxString::Format( _( "Failed to create file '%s'.\n" ), outPath ), RPT_SEVERITY_ERROR );
1738 }
1739
1740 if( fprintf( outFile, "%s", TO_UTF8( report ) ) < 0 )
1741 {
1742 fclose( outFile );
1743 m_reporter->Report( wxString::Format( _( "Error writing file '%s'.\n" ), outPath ), RPT_SEVERITY_ERROR );
1745 }
1746
1747 fclose( outFile );
1748
1749 m_reporter->Report( wxString::Format( _( "Wrote board statistics to '%s'.\n" ), outPath ), RPT_SEVERITY_ACTION );
1750
1751 statsJob->AddOutput( outPath );
1752
1753 return CLI::EXIT_CODES::OK;
1754}
1755
1756
1758{
1759 int exitCode = CLI::EXIT_CODES::OK;
1760 JOB_EXPORT_PCB_GERBER* aGerberJob = dynamic_cast<JOB_EXPORT_PCB_GERBER*>( aJob );
1761
1762 if( aGerberJob == nullptr )
1764
1765 BOARD* brd = getBoard( aGerberJob->m_filename );
1766
1767 if( !brd )
1769
1770 if( !aGerberJob->m_variant.IsEmpty() )
1771 brd->SetCurrentVariant( aGerberJob->m_variant );
1772
1773 TOOL_MANAGER* toolManager = getToolManager( brd );
1774
1775 if( aGerberJob->m_argLayers )
1776 aGerberJob->m_plotLayerSequence = convertLayerArg( aGerberJob->m_argLayers.value(), brd );
1777
1778 if( aGerberJob->m_argCommonLayers )
1779 aGerberJob->m_plotOnAllLayersSequence = convertLayerArg( aGerberJob->m_argCommonLayers.value(), brd );
1780
1781 if( aGerberJob->m_plotLayerSequence.size() < 1 )
1782 {
1783 m_reporter->Report( _( "At least one layer must be specified\n" ), RPT_SEVERITY_ERROR );
1785 }
1786
1787 if( aGerberJob->GetConfiguredOutputPath().IsEmpty() )
1788 {
1789 wxFileName fn = brd->GetFileName();
1790 fn.SetName( fn.GetName() );
1792
1793 aGerberJob->SetWorkingOutputPath( fn.GetFullName() );
1794 }
1795
1796 wxString outPath = resolveJobOutputPath( aJob, brd );
1797
1798 if( aGerberJob->m_checkZonesBeforePlot )
1799 {
1800 if( !toolManager->FindTool( ZONE_FILLER_TOOL_NAME ) )
1801 toolManager->RegisterTool( new ZONE_FILLER_TOOL );
1802
1803 toolManager->GetTool<ZONE_FILLER_TOOL>()->FillAllZones( nullptr, m_progressReporter, true );
1804 }
1805
1806 PCB_PLOT_PARAMS plotOpts;
1807 PCB_PLOTTER::PlotJobToPlotOpts( plotOpts, aGerberJob, *m_reporter );
1808 plotOpts.SetLayerSelection( aGerberJob->m_plotLayerSequence );
1810
1812 wxString layerName;
1813 wxString sheetName;
1814 wxString sheetPath;
1815
1816 // The first layer will be treated as the layer name for the gerber header,
1817 // the other layers will be treated equivalent to the "Plot on All Layers" option
1818 // in the GUI
1819 if( aGerberJob->m_plotLayerSequence.size() >= 1 )
1820 {
1821 layer = aGerberJob->m_plotLayerSequence.front();
1822 layerName = brd->GetLayerName( layer );
1823 }
1824
1825 if( aJob->GetVarOverrides().contains( wxT( "LAYER" ) ) )
1826 layerName = aJob->GetVarOverrides().at( wxT( "LAYER" ) );
1827
1828 if( aJob->GetVarOverrides().contains( wxT( "SHEETNAME" ) ) )
1829 sheetName = aJob->GetVarOverrides().at( wxT( "SHEETNAME" ) );
1830
1831 if( aJob->GetVarOverrides().contains( wxT( "SHEETPATH" ) ) )
1832 sheetPath = aJob->GetVarOverrides().at( wxT( "SHEETPATH" ) );
1833
1834 // We are feeding it one layer at the start here to silence a logic check
1835 PLOTTER* plotter = StartPlotBoard( brd, &plotOpts, layer, layerName, outPath, sheetName, sheetPath );
1836
1837 if( plotter )
1838 {
1839 PlotBoardLayers( brd, plotter, aGerberJob->m_plotLayerSequence, plotOpts );
1840 plotter->EndPlot();
1841 }
1842 else
1843 {
1844 m_reporter->Report( wxString::Format( _( "Failed to plot to '%s'.\n" ), outPath ), RPT_SEVERITY_ERROR );
1846 }
1847
1848 delete plotter;
1849
1850 return exitCode;
1851}
1852
1855
1856
1858{
1859 JOB_EXPORT_PCB_DRILL* aDrillJob = dynamic_cast<JOB_EXPORT_PCB_DRILL*>( aJob );
1860
1861 if( aDrillJob == nullptr )
1863
1864 BOARD* brd = getBoard( aDrillJob->m_filename );
1865
1866 if( !brd )
1868
1869 wxString outPath = resolveJobOutputPath( aJob, brd );
1870
1871 if( !PATHS::EnsurePathExists( outPath ) )
1872 {
1873 m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
1875 }
1876
1877 std::unique_ptr<GENDRILL_WRITER_BASE> drillWriter;
1878
1880 drillWriter = std::make_unique<EXCELLON_WRITER>( brd );
1881 else
1882 drillWriter = std::make_unique<GERBER_WRITER>( brd );
1883
1884 VECTOR2I offset;
1885
1887 offset = VECTOR2I( 0, 0 );
1888 else
1889 offset = brd->GetDesignSettings().GetAuxOrigin();
1890
1891 PLOT_FORMAT mapFormat = PLOT_FORMAT::PDF;
1892
1893 switch( aDrillJob->m_mapFormat )
1894 {
1899 default:
1901 }
1902
1903
1904 if( aDrillJob->m_generateReport && aDrillJob->m_reportPath.IsEmpty() )
1905 {
1906 wxFileName fn = outPath;
1907 fn.SetFullName( brd->GetFileName() );
1908 fn.SetName( fn.GetName() + "-drill" );
1909 fn.SetExt( FILEEXT::ReportFileExtension );
1910
1911 aDrillJob->m_reportPath = fn.GetFullPath();
1912 }
1913
1915 {
1917
1918 switch( aDrillJob->m_zeroFormat )
1919 {
1921
1923
1925
1927 default: zeroFmt = EXCELLON_WRITER::DECIMAL_FORMAT; break;
1928 }
1929
1930 DRILL_PRECISION precision;
1931
1933 precision = precisionListForInches;
1934 else
1935 precision = precisionListForMetric;
1936
1937 EXCELLON_WRITER* excellonWriter = dynamic_cast<EXCELLON_WRITER*>( drillWriter.get() );
1938
1939 if( excellonWriter == nullptr )
1941
1942 excellonWriter->SetFormat( aDrillJob->m_drillUnits == JOB_EXPORT_PCB_DRILL::DRILL_UNITS::MM, zeroFmt,
1943 precision.m_Lhs, precision.m_Rhs );
1944 excellonWriter->SetOptions( aDrillJob->m_excellonMirrorY, aDrillJob->m_excellonMinimalHeader, offset,
1945 aDrillJob->m_excellonCombinePTHNPTH );
1946 excellonWriter->SetRouteModeForOvalHoles( aDrillJob->m_excellonOvalDrillRoute );
1947 excellonWriter->SetMapFileFormat( mapFormat );
1948
1949 if( !excellonWriter->CreateDrillandMapFilesSet( outPath, true, aDrillJob->m_generateMap, m_reporter ) )
1950 {
1952 }
1953
1954 aDrillJob->AddOutput( outPath );
1955
1956 if( aDrillJob->m_generateReport )
1957 {
1958 wxString reportPath = aDrillJob->ResolveOutputPath( aDrillJob->m_reportPath, true, brd->GetProject() );
1959
1960 if( !excellonWriter->GenDrillReportFile( reportPath ) )
1961 {
1963 }
1964
1965 aDrillJob->AddOutput( reportPath );
1966 }
1967 }
1969 {
1970 GERBER_WRITER* gerberWriter = dynamic_cast<GERBER_WRITER*>( drillWriter.get() );
1971
1972 if( gerberWriter == nullptr )
1974
1975 // Set gerber precision: only 5 or 6 digits for mantissa are allowed
1976 // (SetFormat() accept 5 or 6, and any other value set the precision to 5)
1977 // the integer part precision is always 4, and units always mm
1978 gerberWriter->SetFormat( aDrillJob->m_gerberPrecision );
1979 gerberWriter->SetOptions( offset );
1980 gerberWriter->SetMapFileFormat( mapFormat );
1981
1982 if( !gerberWriter->CreateDrillandMapFilesSet( outPath, true, aDrillJob->m_generateMap,
1983 aDrillJob->m_generateTenting, m_reporter ) )
1984 {
1986 }
1987
1988 aDrillJob->AddOutput( outPath );
1989
1990 if( aDrillJob->m_generateReport )
1991 {
1992 wxString reportPath = aDrillJob->ResolveOutputPath( aDrillJob->m_reportPath, true, brd->GetProject() );
1993
1994 if( !gerberWriter->GenDrillReportFile( reportPath ) )
1995 {
1997 }
1998
1999 aDrillJob->AddOutput( reportPath );
2000 }
2001 }
2002
2003 return CLI::EXIT_CODES::OK;
2004}
2005
2006
2008{
2009 JOB_EXPORT_PCB_POS* aPosJob = dynamic_cast<JOB_EXPORT_PCB_POS*>( aJob );
2010
2011 if( aPosJob == nullptr )
2013
2014 BOARD* brd = getBoard( aPosJob->m_filename );
2015
2016 if( !brd )
2018
2019 if( aPosJob->GetConfiguredOutputPath().IsEmpty() )
2020 {
2021 wxFileName fn = brd->GetFileName();
2022 fn.SetName( fn.GetName() );
2023
2026 else if( aPosJob->m_format == JOB_EXPORT_PCB_POS::FORMAT::CSV )
2027 fn.SetExt( FILEEXT::CsvFileExtension );
2028 else if( aPosJob->m_format == JOB_EXPORT_PCB_POS::FORMAT::GERBER )
2029 fn.SetExt( FILEEXT::GerberFileExtension );
2030
2031 aPosJob->SetWorkingOutputPath( fn.GetFullName() );
2032 }
2033
2034 wxString outPath = resolveJobOutputPath( aJob, brd );
2035
2036 if( !PATHS::EnsurePathExists( outPath, true ) )
2037 {
2038 m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
2040 }
2041
2043 {
2044 wxFileName fn( outPath );
2045 wxString baseName = fn.GetName();
2046
2047 auto exportPlaceFile = [&]( bool frontSide, bool backSide, const wxString& curr_outPath ) -> bool
2048 {
2049 FILE* file = wxFopen( curr_outPath, wxS( "wt" ) );
2050 wxCHECK( file, false );
2051
2052 PLACE_FILE_EXPORTER exporter( brd, aPosJob->m_units == JOB_EXPORT_PCB_POS::UNITS::MM, aPosJob->m_smdOnly,
2053 aPosJob->m_excludeFootprintsWithTh, aPosJob->m_excludeDNP,
2054 aPosJob->m_excludeBOM, frontSide, backSide,
2056 aPosJob->m_useDrillPlaceFileOrigin, aPosJob->m_negateBottomX );
2057
2058 // Set variant for variant-aware DNP/BOM/position file filtering
2059 exporter.SetVariant( aPosJob->m_variant );
2060
2061 std::string data = exporter.GenPositionData();
2062 fputs( data.c_str(), file );
2063 fclose( file );
2064
2065 return true;
2066 };
2067
2068 if( aPosJob->m_side == JOB_EXPORT_PCB_POS::SIDE::BOTH && !aPosJob->m_singleFile )
2069 {
2070 fn.SetName( PLACE_FILE_EXPORTER::DecorateFilename( baseName, true, false ) );
2071
2072 if( aPosJob->m_format == JOB_EXPORT_PCB_POS::FORMAT::CSV && !aPosJob->m_nakedFilename )
2073 fn.SetName( fn.GetName() + wxT( "-" ) + FILEEXT::FootprintPlaceFileExtension );
2074
2075 if( exportPlaceFile( true, false, fn.GetFullPath() ) )
2076 {
2077 m_reporter->Report( wxString::Format( _( "Wrote front position data to '%s'.\n" ), fn.GetFullPath() ),
2079
2080 aPosJob->AddOutput( fn.GetFullPath() );
2081 }
2082 else
2083 {
2085 }
2086
2087 fn.SetName( PLACE_FILE_EXPORTER::DecorateFilename( baseName, false, true ) );
2088
2089 if( aPosJob->m_format == JOB_EXPORT_PCB_POS::FORMAT::CSV && !aPosJob->m_nakedFilename )
2090 fn.SetName( fn.GetName() + wxT( "-" ) + FILEEXT::FootprintPlaceFileExtension );
2091
2092 if( exportPlaceFile( false, true, fn.GetFullPath() ) )
2093 {
2094 m_reporter->Report( wxString::Format( _( "Wrote back position data to '%s'.\n" ), fn.GetFullPath() ),
2096
2097 aPosJob->AddOutput( fn.GetFullPath() );
2098 }
2099 else
2100 {
2102 }
2103 }
2104 else
2105 {
2106 bool front = aPosJob->m_side == JOB_EXPORT_PCB_POS::SIDE::FRONT
2108
2109 bool back = aPosJob->m_side == JOB_EXPORT_PCB_POS::SIDE::BACK
2111
2112 if( !aPosJob->m_nakedFilename )
2113 {
2114 fn.SetName( PLACE_FILE_EXPORTER::DecorateFilename( fn.GetName(), front, back ) );
2115
2117 fn.SetName( fn.GetName() + wxT( "-" ) + FILEEXT::FootprintPlaceFileExtension );
2118 }
2119
2120 if( exportPlaceFile( front, back, fn.GetFullPath() ) )
2121 {
2122 m_reporter->Report( wxString::Format( _( "Wrote position data to '%s'.\n" ), fn.GetFullPath() ),
2124
2125 aPosJob->AddOutput( fn.GetFullPath() );
2126 }
2127 else
2128 {
2130 }
2131 }
2132 }
2133 else if( aPosJob->m_format == JOB_EXPORT_PCB_POS::FORMAT::GERBER )
2134 {
2135 PLACEFILE_GERBER_WRITER exporter( brd );
2136
2137 // Set variant for variant-aware DNP/BOM/position file filtering
2138 exporter.SetVariant( aPosJob->m_variant );
2139
2140 PCB_LAYER_ID gbrLayer = F_Cu;
2141 wxString outPath_base = outPath;
2142
2144 {
2145 if( aPosJob->m_side == JOB_EXPORT_PCB_POS::SIDE::BOTH || !aPosJob->m_nakedFilename )
2146 outPath = exporter.GetPlaceFileName( outPath, gbrLayer );
2147
2148 if( exporter.CreatePlaceFile( outPath, gbrLayer, aPosJob->m_gerberBoardEdge, aPosJob->m_excludeDNP,
2149 aPosJob->m_excludeBOM )
2150 >= 0 )
2151 {
2152 m_reporter->Report( wxString::Format( _( "Wrote front position data to '%s'.\n" ), outPath ),
2154
2155 aPosJob->AddOutput( outPath );
2156 }
2157 else
2158 {
2160 }
2161 }
2162
2164 {
2165 gbrLayer = B_Cu;
2166
2167 outPath = outPath_base;
2168
2169 if( aPosJob->m_side == JOB_EXPORT_PCB_POS::SIDE::BOTH || !aPosJob->m_nakedFilename )
2170 outPath = exporter.GetPlaceFileName( outPath, gbrLayer );
2171
2172 if( exporter.CreatePlaceFile( outPath, gbrLayer, aPosJob->m_gerberBoardEdge, aPosJob->m_excludeDNP,
2173 aPosJob->m_excludeBOM )
2174 >= 0 )
2175 {
2176 m_reporter->Report( wxString::Format( _( "Wrote back position data to '%s'.\n" ), outPath ),
2178
2179 aPosJob->AddOutput( outPath );
2180 }
2181 else
2182 {
2184 }
2185 }
2186 }
2187
2188 return CLI::EXIT_CODES::OK;
2189}
2190
2191
2193{
2194 JOB_FP_UPGRADE* upgradeJob = dynamic_cast<JOB_FP_UPGRADE*>( aJob );
2195
2196 if( upgradeJob == nullptr )
2198
2200
2201 if( !upgradeJob->m_outputLibraryPath.IsEmpty() )
2202 {
2203 if( wxFile::Exists( upgradeJob->m_outputLibraryPath ) || wxDir::Exists( upgradeJob->m_outputLibraryPath ) )
2204 {
2205 m_reporter->Report( _( "Output path must not conflict with existing path\n" ), RPT_SEVERITY_ERROR );
2207 }
2208 }
2209 else if( fileType != PCB_IO_MGR::KICAD_SEXP )
2210 {
2211 m_reporter->Report( _( "Output path must be specified to convert legacy and non-KiCad libraries\n" ),
2213
2215 }
2216
2218 {
2219 if( !wxDir::Exists( upgradeJob->m_libraryPath ) )
2220 {
2221 m_reporter->Report( _( "Footprint library path does not exist or is not accessible\n" ),
2224 }
2225
2227 FP_CACHE fpLib( &pcb_io, upgradeJob->m_libraryPath );
2228
2229 try
2230 {
2231 fpLib.Load();
2232 }
2233 catch( ... )
2234 {
2235 m_reporter->Report( _( "Unable to load library\n" ), RPT_SEVERITY_ERROR );
2237 }
2238
2239 if( m_progressReporter )
2240 m_progressReporter->KeepRefreshing();
2241
2242 bool shouldSave = upgradeJob->m_force;
2243
2244 for( const auto& footprint : fpLib.GetFootprints() )
2245 {
2246 if( footprint.second->GetFootprint()->GetFileFormatVersionAtLoad() < SEXPR_BOARD_FILE_VERSION )
2247 shouldSave = true;
2248 }
2249
2250 if( shouldSave )
2251 {
2252 try
2253 {
2254 if( !upgradeJob->m_outputLibraryPath.IsEmpty() )
2255 fpLib.SetPath( upgradeJob->m_outputLibraryPath );
2256
2257 fpLib.Save();
2258 }
2259 catch( ... )
2260 {
2261 m_reporter->Report( _( "Unable to save library\n" ), RPT_SEVERITY_ERROR );
2263 }
2264 }
2265 else
2266 {
2267 m_reporter->Report( _( "Footprint library was not updated\n" ), RPT_SEVERITY_ERROR );
2268 }
2269 }
2270 else
2271 {
2272 if( !PCB_IO_MGR::ConvertLibrary( {}, upgradeJob->m_libraryPath, upgradeJob->m_outputLibraryPath,
2273 nullptr /* REPORTER */ ) )
2274 {
2275 m_reporter->Report( ( "Unable to convert library\n" ), RPT_SEVERITY_ERROR );
2277 }
2278 }
2279
2280 return CLI::EXIT_CODES::OK;
2281}
2282
2283
2285{
2286 JOB_FP_EXPORT_SVG* svgJob = dynamic_cast<JOB_FP_EXPORT_SVG*>( aJob );
2287
2288 if( svgJob == nullptr )
2290
2292 FP_CACHE fpLib( &pcb_io, svgJob->m_libraryPath );
2293
2294 if( svgJob->m_argLayers )
2295 {
2296 if( !svgJob->m_argLayers.value().empty() )
2297 svgJob->m_plotLayerSequence = convertLayerArg( svgJob->m_argLayers.value(), nullptr );
2298 else
2300 }
2301
2302 try
2303 {
2304 fpLib.Load();
2305 }
2306 catch( ... )
2307 {
2308 m_reporter->Report( _( "Unable to load library\n" ), RPT_SEVERITY_ERROR );
2310 }
2311
2312 wxString outPath = svgJob->GetFullOutputPath( nullptr );
2313
2314 if( !PATHS::EnsurePathExists( outPath, true ) )
2315 {
2316 m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
2318 }
2319
2320 int exitCode = CLI::EXIT_CODES::OK;
2321 bool singleFpPlotted = false;
2322
2323 for( const auto& [fpName, fpCacheEntry] : fpLib.GetFootprints() )
2324 {
2325 if( m_progressReporter )
2326 {
2327 m_progressReporter->AdvancePhase( wxString::Format( _( "Exporting %s" ), fpName ) );
2328 m_progressReporter->KeepRefreshing();
2329 }
2330
2331 if( !svgJob->m_footprint.IsEmpty() )
2332 {
2333 // skip until we find the right footprint
2334 if( fpName != svgJob->m_footprint )
2335 continue;
2336 else
2337 singleFpPlotted = true;
2338 }
2339
2340 exitCode = doFpExportSvg( svgJob, fpCacheEntry->GetFootprint().get() );
2341
2342 if( exitCode != CLI::EXIT_CODES::OK )
2343 break;
2344 }
2345
2346 if( !svgJob->m_footprint.IsEmpty() && !singleFpPlotted )
2347 {
2348 m_reporter->Report( _( "The given footprint could not be found to export." ) + wxS( "\n" ),
2350 }
2351
2352 return CLI::EXIT_CODES::OK;
2353}
2354
2355
2357{
2358 // the hack for now is we create fake boards containing the footprint and plot the board
2359 // until we refactor better plot api later
2360 std::unique_ptr<BOARD> brd = BOARD_LOADER::CreateEmptyBoard( Pgm().GetSettingsManager().GetProject( "" ) );
2361 brd->GetProject()->ApplyTextVars( aSvgJob->GetVarOverrides() );
2362 brd->SynchronizeProperties();
2363
2364 FOOTPRINT* fp = dynamic_cast<FOOTPRINT*>( aFootprint->Clone() );
2365
2366 if( fp == nullptr )
2368
2369 fp->SetLink( niluuid );
2370 fp->SetFlags( IS_NEW );
2371 fp->SetParent( brd.get() );
2372
2373 for( PAD* pad : fp->Pads() )
2374 {
2375 pad->SetLocalRatsnestVisible( false );
2376 pad->SetNetCode( 0 );
2377 }
2378
2379 fp->SetOrientation( ANGLE_0 );
2380 fp->SetPosition( VECTOR2I( 0, 0 ) );
2381
2382 brd->Add( fp, ADD_MODE::INSERT, true );
2383
2384 wxFileName outputFile;
2385 outputFile.SetPath( aSvgJob->GetFullOutputPath( nullptr ) );
2386 outputFile.SetName( aFootprint->GetFPID().GetLibItemName().wx_str() );
2387 outputFile.SetExt( FILEEXT::SVGFileExtension );
2388
2389 m_reporter->Report( wxString::Format( _( "Plotting footprint '%s' to '%s'\n" ),
2390 aFootprint->GetFPID().GetLibItemName().wx_str(), outputFile.GetFullPath() ),
2392
2393 PCB_PLOT_PARAMS plotOpts;
2394 PCB_PLOTTER::PlotJobToPlotOpts( plotOpts, aSvgJob, *m_reporter );
2395
2396 // always fixed for the svg plot
2397 plotOpts.SetPlotFrameRef( false );
2398 plotOpts.SetSvgFitPageToBoard( true );
2399 plotOpts.SetMirror( false );
2400 plotOpts.SetSkipPlotNPTH_Pads( false );
2401
2402 if( plotOpts.GetSketchPadsOnFabLayers() )
2403 {
2404 plotOpts.SetPlotPadNumbers( true );
2405 }
2406
2407 PCB_PLOTTER plotter( brd.get(), m_reporter, plotOpts );
2408
2409 if( !plotter.Plot( outputFile.GetFullPath(), aSvgJob->m_plotLayerSequence, aSvgJob->m_plotOnAllLayersSequence,
2410 false, true, wxEmptyString, wxEmptyString, wxEmptyString ) )
2411 {
2412 m_reporter->Report( _( "Error creating svg file" ) + wxS( "\n" ), RPT_SEVERITY_ERROR );
2414 }
2415
2416 aSvgJob->AddOutput( outputFile.GetFullPath() );
2417
2418 return CLI::EXIT_CODES::OK;
2419}
2420
2421
2423{
2424 JOB_PCB_DRC* drcJob = dynamic_cast<JOB_PCB_DRC*>( aJob );
2425
2426 if( drcJob == nullptr )
2428
2429 BOARD* brd = getBoard( drcJob->m_filename );
2430
2431 if( !brd )
2433
2434 // Running DRC requires libraries be loaded, so make sure they have been
2436 adapter->AsyncLoad();
2437 adapter->BlockUntilLoaded();
2438
2439 if( drcJob->GetConfiguredOutputPath().IsEmpty() )
2440 {
2441 wxFileName fn = brd->GetFileName();
2442 fn.SetName( fn.GetName() + wxS( "-drc" ) );
2443
2445 fn.SetExt( FILEEXT::JsonFileExtension );
2446 else
2447 fn.SetExt( FILEEXT::ReportFileExtension );
2448
2449 drcJob->SetWorkingOutputPath( fn.GetFullName() );
2450 }
2451
2452 wxString outPath = resolveJobOutputPath( aJob, brd );
2453
2454 if( !PATHS::EnsurePathExists( outPath, true ) )
2455 {
2456 m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
2458 }
2459
2460 EDA_UNITS units;
2461
2462 switch( drcJob->m_units )
2463 {
2464 case JOB_PCB_DRC::UNITS::INCH: units = EDA_UNITS::INCH; break;
2465 case JOB_PCB_DRC::UNITS::MILS: units = EDA_UNITS::MILS; break;
2466 case JOB_PCB_DRC::UNITS::MM: units = EDA_UNITS::MM; break;
2467 default: units = EDA_UNITS::MM; break;
2468 }
2469
2470 std::shared_ptr<DRC_ENGINE> drcEngine = brd->GetDesignSettings().m_DRCEngine;
2471 std::unique_ptr<NETLIST> netlist = std::make_unique<NETLIST>();
2472
2473 drcEngine->SetDrawingSheet( getDrawingSheetProxyView( brd ) );
2474
2475 // BOARD_COMMIT uses TOOL_MANAGER to grab the board internally so we must give it one
2476 TOOL_MANAGER* toolManager = getToolManager( brd );
2477
2478 BOARD_COMMIT commit( toolManager );
2479 bool checkParity = drcJob->m_parity;
2480 std::string netlist_str;
2481
2482 if( checkParity )
2483 {
2484 wxString annotateMsg = _( "Schematic parity tests require a fully annotated schematic." );
2485 netlist_str = annotateMsg;
2486
2487 // The KIFACE_NETLIST_SCHEMATIC function has some broken-ness that the schematic
2488 // frame's version does not, but it is the only one that works in CLI, so we use it
2489 // if we don't have the sch frame open.
2490 // TODO: clean this up, see https://gitlab.com/kicad/code/kicad/-/issues/19929
2491 if( m_kiway->Player( FRAME_SCH, false ) )
2492 {
2493 m_kiway->ExpressMail( FRAME_SCH, MAIL_SCH_GET_NETLIST, netlist_str );
2494 }
2495 else
2496 {
2497 wxFileName schematicPath( drcJob->m_filename );
2498 schematicPath.MakeAbsolute();
2499 schematicPath.SetExt( FILEEXT::KiCadSchematicFileExtension );
2500
2501 if( !schematicPath.Exists() )
2502 schematicPath.SetExt( FILEEXT::LegacySchematicFileExtension );
2503
2504 if( !schematicPath.Exists() )
2505 {
2506 m_reporter->Report( _( "Failed to fetch schematic netlist for parity tests.\n" ), RPT_SEVERITY_ERROR );
2507 checkParity = false;
2508 }
2509 else
2510 {
2511 typedef bool ( *NETLIST_FN_PTR )( const wxString&, std::string& );
2512 KIFACE* eeschema = m_kiway->KiFACE( KIWAY::FACE_SCH );
2513 NETLIST_FN_PTR netlister = (NETLIST_FN_PTR) eeschema->IfaceOrAddress( KIFACE_NETLIST_SCHEMATIC );
2514 ( *netlister )( schematicPath.GetFullPath(), netlist_str );
2515 }
2516 }
2517
2518 if( netlist_str == MAIL_SCH_GET_NETLIST_CANCELLED )
2519 {
2520 checkParity = false;
2521 }
2522 else if( netlist_str == annotateMsg )
2523 {
2524 m_reporter->Report( annotateMsg + wxT( "\n" ), RPT_SEVERITY_ERROR );
2525 checkParity = false;
2526 }
2527 }
2528
2529 if( checkParity )
2530 {
2531 try
2532 {
2533 STRING_LINE_READER* lineReader = new STRING_LINE_READER( netlist_str, _( "Eeschema netlist" ) );
2534 KICAD_NETLIST_READER netlistReader( lineReader, netlist.get() );
2535
2536 netlistReader.LoadNetlist();
2537 }
2538 catch( const IO_ERROR& )
2539 {
2540 m_reporter->Report( _( "Failed to fetch schematic netlist for parity tests.\n" ), RPT_SEVERITY_ERROR );
2541 checkParity = false;
2542 }
2543
2544 drcEngine->SetSchematicNetlist( netlist.get() );
2545 }
2546
2547 if( drcJob->m_refillZones )
2548 {
2549 if( !toolManager->FindTool( ZONE_FILLER_TOOL_NAME ) )
2550 toolManager->RegisterTool( new ZONE_FILLER_TOOL );
2551
2552 toolManager->GetTool<ZONE_FILLER_TOOL>()->FillAllZones( nullptr, m_progressReporter, true );
2553 }
2554
2555 drcEngine->SetProgressReporter( m_progressReporter );
2556 drcEngine->SetViolationHandler(
2557 [&]( const std::shared_ptr<DRC_ITEM>& aItem, const VECTOR2I& aPos, int aLayer,
2558 const std::function<void( PCB_MARKER* )>& aPathGenerator )
2559 {
2560 PCB_MARKER* marker = new PCB_MARKER( aItem, aPos, aLayer );
2561 aPathGenerator( marker );
2562 commit.Add( marker );
2563 } );
2564
2565 brd->RecordDRCExclusions();
2566 brd->DeleteMARKERs( true, true );
2567 drcEngine->RunTests( units, drcJob->m_reportAllTrackErrors, checkParity );
2568 drcEngine->ClearViolationHandler();
2569
2570 commit.Push( _( "DRC" ), SKIP_UNDO | SKIP_SET_DIRTY );
2571
2572 // Update the exclusion status on any excluded markers that still exist.
2573 brd->ResolveDRCExclusions( false );
2574
2575 std::shared_ptr<DRC_ITEMS_PROVIDER> markersProvider =
2576 std::make_shared<DRC_ITEMS_PROVIDER>( brd, MARKER_BASE::MARKER_DRC, MARKER_BASE::MARKER_DRAWING_SHEET );
2577
2578 std::shared_ptr<DRC_ITEMS_PROVIDER> ratsnestProvider =
2579 std::make_shared<DRC_ITEMS_PROVIDER>( brd, MARKER_BASE::MARKER_RATSNEST );
2580
2581 std::shared_ptr<DRC_ITEMS_PROVIDER> fpWarningsProvider =
2582 std::make_shared<DRC_ITEMS_PROVIDER>( brd, MARKER_BASE::MARKER_PARITY );
2583
2584 markersProvider->SetSeverities( drcJob->m_severity );
2585 ratsnestProvider->SetSeverities( drcJob->m_severity );
2586 fpWarningsProvider->SetSeverities( drcJob->m_severity );
2587
2588 m_reporter->Report( wxString::Format( _( "Found %d violations\n" ), markersProvider->GetCount() ),
2590 m_reporter->Report( wxString::Format( _( "Found %d unconnected items\n" ), ratsnestProvider->GetCount() ),
2592
2593 if( checkParity )
2594 {
2595 m_reporter->Report(
2596 wxString::Format( _( "Found %d schematic parity issues\n" ), fpWarningsProvider->GetCount() ),
2598 }
2599
2600 DRC_REPORT reportWriter( brd, units, markersProvider, ratsnestProvider, fpWarningsProvider );
2601
2602 bool wroteReport = false;
2603
2605 wroteReport = reportWriter.WriteJsonReport( outPath );
2606 else
2607 wroteReport = reportWriter.WriteTextReport( outPath );
2608
2609 if( !wroteReport )
2610 {
2611 m_reporter->Report( wxString::Format( _( "Unable to save DRC report to %s\n" ), outPath ), RPT_SEVERITY_ERROR );
2613 }
2614
2615 drcJob->AddOutput( outPath );
2616
2617 m_reporter->Report( wxString::Format( _( "Saved DRC Report to %s\n" ), outPath ), RPT_SEVERITY_ACTION );
2618
2619 if( drcJob->m_refillZones && drcJob->m_saveBoard )
2620 {
2621 if( BOARD_LOADER::SaveBoard( drcJob->m_filename, brd ) )
2622 {
2623 m_reporter->Report( _( "Saved board\n" ), RPT_SEVERITY_ACTION );
2624 }
2625 else
2626 {
2627 m_reporter->Report( _( "Failed to save board.\n" ), RPT_SEVERITY_ERROR );
2628
2630 }
2631 }
2632
2633 if( drcJob->m_exitCodeViolations )
2634 {
2635 if( markersProvider->GetCount() > 0 || ratsnestProvider->GetCount() > 0 || fpWarningsProvider->GetCount() > 0 )
2636 {
2638 }
2639 }
2640
2642}
2643
2644
2646{
2647 JOB_EXPORT_PCB_IPC2581* job = dynamic_cast<JOB_EXPORT_PCB_IPC2581*>( aJob );
2648
2649 if( job == nullptr )
2651
2652 BOARD* brd = getBoard( job->m_filename );
2653
2654 if( !brd )
2656
2657 if( !job->m_variant.IsEmpty() )
2658 brd->SetCurrentVariant( job->m_variant );
2659
2660 if( job->GetConfiguredOutputPath().IsEmpty() )
2661 {
2662 wxFileName fn = brd->GetFileName();
2663 fn.SetExt( job->m_compress ? std::string( "zip" ) : FILEEXT::Ipc2581FileExtension );
2664
2665 job->SetWorkingOutputPath( fn.GetFullName() );
2666 }
2667
2668 wxString outPath = resolveJobOutputPath( aJob, brd );
2669
2670 if( !PATHS::EnsurePathExists( outPath, true ) )
2671 {
2672 m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
2674 }
2675
2678
2680}
2681
2682
2684{
2685 JOB_EXPORT_PCB_IPCD356* job = dynamic_cast<JOB_EXPORT_PCB_IPCD356*>( aJob );
2686
2687 if( job == nullptr )
2689
2690 BOARD* brd = getBoard( job->m_filename );
2691
2692 if( !brd )
2694
2695 if( job->GetConfiguredOutputPath().IsEmpty() )
2696 {
2697 wxFileName fn = brd->GetFileName();
2698 fn.SetName( fn.GetName() );
2699 fn.SetExt( FILEEXT::IpcD356FileExtension );
2700
2701 job->SetWorkingOutputPath( fn.GetFullName() );
2702 }
2703
2704 wxString outPath = resolveJobOutputPath( aJob, brd );
2705
2706 if( !PATHS::EnsurePathExists( outPath, true ) )
2707 {
2708 m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
2710 }
2711
2712 IPC356D_WRITER exporter( brd );
2713
2714 bool success = exporter.Write( outPath );
2715
2716 if( success )
2717 {
2718 aJob->AddOutput( outPath );
2719 m_reporter->Report( _( "Successfully created IPC-D-356 file\n" ), RPT_SEVERITY_INFO );
2721 }
2722 else
2723 {
2724 m_reporter->Report( _( "Failed to create IPC-D-356 file\n" ), RPT_SEVERITY_ERROR );
2726 }
2727}
2728
2729
2731{
2732 JOB_EXPORT_PCB_ODB* job = dynamic_cast<JOB_EXPORT_PCB_ODB*>( aJob );
2733
2734 if( job == nullptr )
2736
2737 BOARD* brd = getBoard( job->m_filename );
2738
2739 if( !brd )
2741
2742 if( !job->m_variant.IsEmpty() )
2743 brd->SetCurrentVariant( job->m_variant );
2744
2745 if( job->GetConfiguredOutputPath().IsEmpty() )
2746 {
2748 {
2749 // just basic folder name
2750 job->SetWorkingOutputPath( "odb" );
2751 }
2752 else
2753 {
2754 wxFileName fn( brd->GetFileName() );
2755 fn.SetName( fn.GetName() + wxS( "-odb" ) );
2756
2757 switch( job->m_compressionMode )
2758 {
2760
2761 case JOB_EXPORT_PCB_ODB::ODB_COMPRESSION::TGZ: fn.SetExt( "tgz" ); break;
2762
2763 default: break;
2764 };
2765
2766 job->SetWorkingOutputPath( fn.GetFullName() );
2767 }
2768 }
2769
2770 wxString outPath = resolveJobOutputPath( job, brd );
2771
2772 // The helper handles output path creation, so hand it a job that already has fully-resolved
2773 // token context (title block and project overrides applied above).
2775
2776 if( !m_reporter )
2778
2779 if( job->m_checkZonesBeforeExport )
2780 {
2781 TOOL_MANAGER* toolManager = getToolManager( brd );
2782
2783 if( !toolManager->FindTool( ZONE_FILLER_TOOL_NAME ) )
2784 toolManager->RegisterTool( new ZONE_FILLER_TOOL );
2785
2786 toolManager->GetTool<ZONE_FILLER_TOOL>()->FillAllZones( nullptr, m_progressReporter, true );
2787 }
2788
2790 aJob->AddOutput( outPath );
2791
2792 if( m_reporter->HasMessageOfSeverity( RPT_SEVERITY_ERROR ) )
2794
2796}
2797
2799{
2800 JOB_PCB_UPGRADE* job = dynamic_cast<JOB_PCB_UPGRADE*>( aJob );
2801
2802 if( job == nullptr )
2804
2805 bool shouldSave = job->m_force;
2806
2807 try
2808 {
2810 BOARD* brd = getBoard( job->m_filename );
2812 shouldSave = true;
2813
2814 if( shouldSave )
2815 {
2816 pi->SaveBoard( brd->GetFileName(), brd );
2817 m_reporter->Report( _( "Successfully saved board file using the latest format\n" ), RPT_SEVERITY_INFO );
2818 }
2819 else
2820 {
2821 m_reporter->Report( _( "Board file was not updated\n" ), RPT_SEVERITY_ERROR );
2822 }
2823 }
2824 catch( const IO_ERROR& ioe )
2825 {
2826 wxString msg =
2827 wxString::Format( _( "Error saving board file '%s'.\n%s" ), job->m_filename, ioe.What().GetData() );
2828 m_reporter->Report( msg, RPT_SEVERITY_ERROR );
2830 }
2831
2833}
2834
2835// Most job handlers need to align the running job with the board before resolving any
2836// output paths with variables in them like ${REVISION}.
2837wxString PCBNEW_JOBS_HANDLER::resolveJobOutputPath( JOB* aJob, BOARD* aBoard, const wxString* aDrawingSheet )
2838{
2839 aJob->SetTitleBlock( aBoard->GetTitleBlock() );
2840
2841 if( aDrawingSheet && !aDrawingSheet->IsEmpty() )
2842 loadOverrideDrawingSheet( aBoard, *aDrawingSheet );
2843
2844 PROJECT* project = aBoard->GetProject();
2845
2846 if( project )
2847 project->ApplyTextVars( aJob->GetVarOverrides() );
2848
2849 aBoard->SynchronizeProperties();
2850
2851 return aJob->GetFullOutputPath( project );
2852}
2853
2854
2856{
2857 DS_PROXY_VIEW_ITEM* drawingSheet = new DS_PROXY_VIEW_ITEM( pcbIUScale, &aBrd->GetPageSettings(), aBrd->GetProject(),
2858 &aBrd->GetTitleBlock(), &aBrd->GetProperties() );
2859
2860 drawingSheet->SetSheetName( std::string() );
2861 drawingSheet->SetSheetPath( std::string() );
2862 drawingSheet->SetIsFirstPage( true );
2863
2864 drawingSheet->SetFileName( TO_UTF8( aBrd->GetFileName() ) );
2865
2866 wxString currentVariant = aBrd->GetCurrentVariant();
2867 wxString variantDesc = aBrd->GetVariantDescription( currentVariant );
2868 drawingSheet->SetVariantName( TO_UTF8( currentVariant ) );
2869 drawingSheet->SetVariantDesc( TO_UTF8( variantDesc ) );
2870
2871 return drawingSheet;
2872}
2873
2874
2875void PCBNEW_JOBS_HANDLER::loadOverrideDrawingSheet( BOARD* aBrd, const wxString& aSheetPath )
2876{
2877 // dont bother attempting to load a empty path, if there was one
2878 if( aSheetPath.IsEmpty() )
2879 return;
2880
2881 auto loadSheet = [&]( const wxString& path ) -> bool
2882 {
2885 resolver.SetProject( aBrd->GetProject() );
2886 resolver.SetProgramBase( &Pgm() );
2887
2888 wxString filename = resolver.ResolvePath( BASE_SCREEN::m_DrawingSheetFileName,
2889 aBrd->GetProject()->GetProjectPath(), { aBrd->GetEmbeddedFiles() } );
2890 wxString msg;
2891
2892 if( !DS_DATA_MODEL::GetTheInstance().LoadDrawingSheet( filename, &msg ) )
2893 {
2894 m_reporter->Report( wxString::Format( _( "Error loading drawing sheet '%s'." ), path ) + wxS( "\n" ) + msg
2895 + wxS( "\n" ),
2897 return false;
2898 }
2899
2900 return true;
2901 };
2902
2903 if( loadSheet( aSheetPath ) )
2904 return;
2905
2906 // failed loading custom path, revert back to default
2907 loadSheet( aBrd->GetProject()->GetProjectFile().m_BoardDrawingSheetFile );
2908}
2909
2910
2911// Resolve a KiCad layer name (canonical board-file name such as "F.Cu", or the GUI display name)
2912// to its layer id. Returns UNDEFINED_LAYER when no layer matches.
2913static PCB_LAYER_ID resolveKiCadLayerName( const wxString& aName )
2914{
2915 for( PCB_LAYER_ID layer : LSET::AllLayersMask().Seq() )
2916 {
2917 if( LSET::Name( layer ) == aName || LayerName( layer ) == aName )
2918 return layer;
2919 }
2920
2921 return UNDEFINED_LAYER;
2922}
2923
2924
2926{
2927 JOB_PCB_IMPORT* job = dynamic_cast<JOB_PCB_IMPORT*>( aJob );
2928
2929 if( !job )
2931
2932 // Check that input file exists
2933 if( !wxFile::Exists( job->m_inputFile ) )
2934 {
2935 m_reporter->Report( wxString::Format( _( "Input file not found: '%s'\n" ),
2936 job->m_inputFile ),
2939 }
2940
2941 // Map job format to PCB_IO file type
2943
2944 switch( job->m_format )
2945 {
2947
2949
2951
2953
2955
2957
2959
2961 }
2962
2963 // FindPluginTypeFromBoardPath returns FILE_TYPE_NONE (not PCB_FILE_UNKNOWN) when no plugin
2964 // claims the file. Quiet sentinel: lets the top-level `import` command try the schematic face.
2966 {
2967 m_reporter->Report( wxString::Format( _( "No PCB importer recognizes the file format of "
2968 "'%s'\n" ),
2969 job->m_inputFile ),
2972 }
2973
2974 // Determine output path
2975 wxString outputPath = job->GetConfiguredOutputPath();
2976
2977 if( outputPath.IsEmpty() )
2979
2980 // The generated footprint library and its table row belong to the *active* project, so an
2981 // import with no project loaded needs a transient active one at the output location (never
2982 // written to disk; LoadProject returns false yet still registers it, hence the GetProject()
2983 // check).
2985 PROJECT* projectPtr = nullptr;
2986 bool createdTransientProject = false;
2987
2988 if( mgr.IsProjectOpenNotDummy() )
2989 {
2990 projectPtr = &mgr.Prj();
2991 }
2992 else
2993 {
2994 wxFileName projectFn( outputPath );
2995 projectFn.SetExt( FILEEXT::ProjectFileExtension );
2996
2997 mgr.LoadProject( projectFn.GetFullPath(), true );
2998 projectPtr = mgr.GetProject( projectFn.GetFullPath() );
2999 createdTransientProject = ( projectPtr != nullptr );
3000 }
3001
3002 if( !projectPtr )
3003 {
3004 m_reporter->Report( _( "Could not establish a project for the import\n" ),
3007 }
3008
3009 // unloads the transient project on every exit path
3010 struct TRANSIENT_PROJECT_GUARD
3011 {
3012 SETTINGS_MANAGER& m_mgr;
3013 PROJECT* m_project;
3014 bool m_active;
3015
3016 ~TRANSIENT_PROJECT_GUARD()
3017 {
3018 if( m_active )
3019 m_mgr.UnloadProject( m_project, false );
3020 }
3021 } transientProjectGuard{ mgr, projectPtr, createdTransientProject };
3022
3023 BOARD* board = nullptr;
3024 wxString formatName = PCB_IO_MGR::ShowType( fileType );
3025 std::vector<wxString> warnings;
3026
3027 // Real source-to-KiCad layer decisions, captured by our mapping callback so the report can
3028 // show them and so explicit overrides can be validated.
3029 struct CAPTURED_LAYER
3030 {
3031 wxString m_source;
3032 PCB_LAYER_ID m_target;
3033 wxString m_method;
3034 };
3035
3036 std::vector<CAPTURED_LAYER> capturedLayers;
3037 std::set<wxString> seenSourceLayers;
3038 bool layersCaptured = false;
3039
3040 try
3041 {
3043
3044 if( !pi )
3045 {
3046 m_reporter->Report( wxString::Format( _( "No plugin found for file type '%s'\n" ), formatName ),
3049 }
3050
3051 // Replace the plugin's default best-guess callback so we can apply explicit overrides and
3052 // capture the resulting mapping. Only mappable importers expose their source layers; for
3053 // others the report falls back to listing the imported board's enabled layers.
3054 if( LAYER_MAPPABLE_PLUGIN* mappable = dynamic_cast<LAYER_MAPPABLE_PLUGIN*>( pi.get() ) )
3055 {
3056 if( !job->m_layerMap.empty() || job->m_reportFormat != IMPORT_REPORT_FORMAT::NONE )
3057 {
3058 mappable->RegisterCallback(
3059 [&]( const std::vector<INPUT_LAYER_DESC>& aDescs )
3060 -> std::map<wxString, PCB_LAYER_ID>
3061 {
3062 std::map<wxString, PCB_LAYER_ID> result;
3063
3064 for( const INPUT_LAYER_DESC& desc : aDescs )
3065 {
3066 PCB_LAYER_ID target = desc.AutoMapLayer;
3067 wxString method = wxS( "auto" );
3068
3069 if( auto it = job->m_layerMap.find( desc.Name );
3070 it != job->m_layerMap.end() )
3071 {
3072 PCB_LAYER_ID resolved = resolveKiCadLayerName( it->second );
3073
3074 if( resolved == UNDEFINED_LAYER )
3075 {
3076 warnings.push_back( wxString::Format(
3077 _( "Layer map entry '%s' -> '%s' names an unknown "
3078 "KiCad layer; using automatic mapping instead" ),
3079 desc.Name, it->second ) );
3080 }
3081 else if( !desc.PermittedLayers.Contains( resolved ) )
3082 {
3083 warnings.push_back( wxString::Format(
3084 _( "Layer map entry '%s' -> '%s' is not a permitted "
3085 "target for this layer; using automatic mapping "
3086 "instead" ),
3087 desc.Name, it->second ) );
3088 }
3089 else
3090 {
3091 target = resolved;
3092 method = wxS( "explicit" );
3093 }
3094 }
3095
3096 if( desc.Required && target == UNDEFINED_LAYER )
3097 {
3098 warnings.push_back( wxString::Format(
3099 _( "No KiCad layer mapping for required source layer "
3100 "'%s'; its items will not be imported" ),
3101 desc.Name ) );
3102 }
3103
3104 result.emplace( desc.Name, target );
3105 capturedLayers.push_back( { desc.Name, target, method } );
3106 seenSourceLayers.insert( desc.Name );
3107 }
3108
3109 layersCaptured = true;
3110 return result;
3111 } );
3112 }
3113 }
3114 else if( !job->m_layerMap.empty() )
3115 {
3116 warnings.push_back( _( "A layer map was provided, but this importer does not support "
3117 "layer remapping; it will be ignored" ) );
3118 }
3119
3120 m_reporter->Report(
3121 wxString::Format( _( "Importing '%s' using %s format...\n" ), job->m_inputFile, formatName ),
3123
3124 board = pi->LoadBoard( job->m_inputFile, nullptr, nullptr, nullptr );
3125
3126 if( !board )
3127 {
3128 m_reporter->Report( _( "Failed to load board\n" ), RPT_SEVERITY_ERROR );
3130 }
3131
3132 // Extract a project footprint library and re-link FPIDs, as the board editor's import
3133 // does; without it the saved board references a nickname no library table row resolves.
3135 {
3136 ReconcileImportedFootprints( *pi, *board, *projectPtr, job->m_inputFile, nullptr,
3137 *m_reporter );
3138 }
3139
3140 // Flag explicit map entries that never matched a source layer so typos do not pass silently.
3141 if( layersCaptured )
3142 {
3143 for( const auto& [source, target] : job->m_layerMap )
3144 {
3145 if( !seenSourceLayers.contains( source ) )
3146 {
3147 warnings.push_back( wxString::Format(
3148 _( "Layer map entry '%s' does not match any source layer in the "
3149 "imported file; it will be ignored" ),
3150 source ) );
3151 }
3152 }
3153 }
3154
3155 // Save as KiCad format
3157 kicadPlugin->SaveBoard( outputPath, board );
3158
3159 m_reporter->Report( wxString::Format( _( "Successfully saved imported board to '%s'\n" ), outputPath ),
3161
3162 // Generate report if requested
3164 {
3165 IMPORT_REPORT_DATA reportData;
3166
3167 reportData.m_sourceFile = wxFileName( job->m_inputFile ).GetFullName();
3168 reportData.m_sourceFormat = formatName;
3169 reportData.m_outputFile = wxFileName( outputPath ).GetFullName();
3170
3171 size_t trackCount = 0;
3172 size_t viaCount = 0;
3173
3174 for( PCB_TRACK* track : board->Tracks() )
3175 {
3176 if( track->Type() == PCB_VIA_T )
3177 viaCount++;
3178 else
3179 trackCount++;
3180 }
3181
3182 reportData.m_statistics = {
3183 { wxS( "footprints" ), board->Footprints().size() },
3184 { wxS( "tracks" ), trackCount },
3185 { wxS( "vias" ), viaCount },
3186 { wxS( "zones" ), board->Zones().size() }
3187 };
3188
3189 // Build layer mapping info, carried only in the JSON report. Prefer the real
3190 // source-to-KiCad decisions captured during load; fall back to the imported board's
3191 // enabled layers for importers that do not expose a mappable layer set.
3192 nlohmann::json layerMappings = nlohmann::json::object();
3193
3194 if( layersCaptured )
3195 {
3196 for( const CAPTURED_LAYER& mapped : capturedLayers )
3197 {
3198 std::string kicadLayer = mapped.m_target == UNDEFINED_LAYER
3199 ? std::string()
3200 : LSET::Name( mapped.m_target ).ToStdString();
3201
3202 layerMappings[mapped.m_source.ToStdString()] = {
3203 { "kicad_layer", kicadLayer },
3204 { "method", mapped.m_method.ToStdString() }
3205 };
3206 }
3207 }
3208 else
3209 {
3210 for( PCB_LAYER_ID layer : board->GetEnabledLayers().Seq() )
3211 {
3212 wxString layerName = board->GetLayerName( layer );
3213
3214 layerMappings[layerName.ToStdString()] = {
3215 { "kicad_layer", LSET::Name( layer ).ToStdString() },
3216 { "method", "auto" }
3217 };
3218 }
3219 }
3220
3221 reportData.m_extraJson["layer_mapping"] = layerMappings;
3222 reportData.m_warnings = warnings;
3223
3224 WriteImportReport( m_reporter, job->m_reportFormat, job->m_reportFile, reportData );
3225 }
3226 else
3227 {
3228 // No report requested, but explicit-mapping problems still need to surface.
3229 for( const wxString& warning : warnings )
3230 m_reporter->Report( warning + wxS( "\n" ), RPT_SEVERITY_WARNING );
3231 }
3232
3233 delete board;
3234 }
3235 catch( const IO_ERROR& ioe )
3236 {
3237 m_reporter->Report( wxString::Format( _( "Error during import: %s\n" ), ioe.What() ), RPT_SEVERITY_ERROR );
3238
3239 delete board;
3241 }
3242
3244}
3245
3246
3247// ============================================================================
3248// JobDiff: pcb_diff implementation
3249// ============================================================================
3252#include <diff_merge/diff_scene.h>
3253#include <diff_merge/pcb_differ.h>
3255#include <jobs/job_pcb_diff.h>
3256
3257
3258// Load a board into a SCRATCH_DOC<BOARD> that keeps its project attached for
3259// the document's lifetime — the differ/applier read PROJECT_FILE-scoped fields
3260// (drawing-sheet path, DRC severities, net classes). The destructor severs the
3261// BOARD->project link in the right order. Used by every PCB diff/merge job.
3262static SCRATCH_DOC<BOARD> loadScratchBoard( SETTINGS_MANAGER& aMgr, const wxString& aPath,
3263 bool aInitializeAfterLoad = true )
3264{
3265 return LoadScratchDoc<BOARD>(
3266 aMgr, aPath,
3267 [aPath, aInitializeAfterLoad]( PROJECT* aProject ) -> std::unique_ptr<BOARD>
3268 {
3269 PCB_IO_MGR::PCB_FILE_T pluginType =
3271
3272 if( !aProject || pluginType == PCB_IO_MGR::FILE_TYPE_NONE )
3273 return nullptr;
3274
3276 opts.initialize_after_load = aInitializeAfterLoad;
3277
3278 try
3279 {
3280 return BOARD_LOADER::Load( aPath, pluginType, aProject, opts );
3281 }
3282 catch( ... )
3283 {
3284 return nullptr;
3285 }
3286 },
3287 []( BOARD* aBoard )
3288 {
3289 if( aBoard )
3290 aBoard->ClearProject();
3291 } );
3292}
3293
3294
3296{
3297 JOB_PCB_DIFF* diffJob = dynamic_cast<JOB_PCB_DIFF*>( aJob );
3298
3299 if( !diffJob )
3301
3302 // SCRATCH_DOC<BOARD> keeps each board's project attached for the lifetime
3303 // of the diff, which the differ needs to read project-file-scoped fields
3304 // (m_BoardDrawingSheetFile, etc). The previous loadStandaloneBoard +
3305 // ClearProject-up-front path would null those out before the differ ran.
3307
3308 SCRATCH_DOC<BOARD> aScratch = loadScratchBoard( diffMgr, diffJob->m_inputA );
3309 SCRATCH_DOC<BOARD> bScratch = loadScratchBoard( diffMgr, diffJob->m_inputB );
3310
3311 BOARD* boardA = aScratch.doc.get();
3312 BOARD* boardB = bScratch.doc.get();
3313
3314 if( !boardA )
3315 {
3316 m_reporter->Report( wxString::Format( _( "Failed to load %s\n" ), diffJob->m_inputA ), RPT_SEVERITY_ERROR );
3318 }
3319
3320 if( !boardB )
3321 {
3322 m_reporter->Report( wxString::Format( _( "Failed to load %s\n" ), diffJob->m_inputB ), RPT_SEVERITY_ERROR );
3324 }
3325
3326 KICAD_DIFF::PCB_DIFFER differ( boardA, boardB, diffJob->m_inputB );
3328
3329 int diffExitCode = KICAD_DIFF::DiffExitCode( result );
3330
3331 if( diffJob->m_exitCodeOnly )
3332 return diffExitCode;
3333
3334 // The board geometry rendered beneath the change overlay (PNG/SVG only)
3335 // matches what the interactive dialog draws.
3337 KICAD_DIFF::MakeEmitOptions( *diffJob, diffJob->m_inputA, diffJob->m_inputB );
3339 emitOpts.referenceGeometry = [&]( const KIGFX::COLOR4D& aColor )
3340 { return KICAD_DIFF::ExtractBoardGeometry( *boardA, aColor ); };
3341 emitOpts.comparisonGeometry = [&]( const KIGFX::COLOR4D& aColor )
3342 { return KICAD_DIFF::ExtractBoardGeometry( *boardB, aColor ); };
3343
3344 return KICAD_DIFF::EmitDiffResult( result, emitOpts, diffExitCode, *m_reporter );
3345}
3346
3347
3348// ============================================================================
3349// JobMerge: pcb_merge implementation
3350// ============================================================================
3355
3356
3357int PCBNEW_JOBS_HANDLER::RunMerge( KICAD_DIFF::DOC_KIND aKind, const wxString& aAncestor,
3358 const wxString& aOurs, const wxString& aTheirs,
3359 const wxString& aOutput, bool aInteractive, bool aSingleFile,
3360 REPORTER* aReporter )
3361{
3362 // Restore m_reporter on scope exit so a caller's transient (often
3363 // stack-local) reporter doesn't outlive this call as a dangling member.
3365 aReporter ? aReporter : m_reporter );
3366
3368 return runFpLibMerge( aAncestor, aOurs, aTheirs, aOutput, aSingleFile );
3369
3370 return runPcbMerge( aAncestor, aOurs, aTheirs, aOutput, aInteractive );
3371}
3372
3373
3374int PCBNEW_JOBS_HANDLER::runPcbMerge( const wxString& aAncestor, const wxString& aOurs,
3375 const wxString& aTheirs, const wxString& aOutput,
3376 bool aInteractive )
3377{
3378 // Use SCRATCH_DOC<BOARD> so each input keeps its project attached for the
3379 // life of the merge — necessary for any doc-level resolution that mutates
3380 // PROJECT_FILE-scoped state (DRC severities, net classes) and needs to be
3381 // saved as a sibling .kicad_pro. SCRATCH_DOC's destructor severs the
3382 // BOARD->project link in the right order and unloads the project from
3383 // the manager, avoiding the dangling-PROJECT_FILE::m_BoardSettings pointer
3384 // the previous up-front-ClearProject loadStandaloneBoard path had to
3385 // guard against.
3387
3388 SCRATCH_DOC<BOARD> ancestorScratch = loadScratchBoard( mgr, aAncestor );
3389 SCRATCH_DOC<BOARD> oursScratch = loadScratchBoard( mgr, aOurs );
3390 SCRATCH_DOC<BOARD> theirsScratch = loadScratchBoard( mgr, aTheirs );
3391
3392 BOARD* ancestor = ancestorScratch.doc.get();
3393 BOARD* ours = oursScratch.doc.get();
3394 BOARD* theirs = theirsScratch.doc.get();
3395
3396 if( !ancestor || !ours || !theirs )
3397 {
3398 m_reporter->Report( _( "Failed to load one or more input boards\n" ), RPT_SEVERITY_ERROR );
3400 }
3401
3402 KICAD_DIFF::PCB_DIFFER ourDiff( ancestor, ours );
3403 KICAD_DIFF::PCB_DIFFER theirDiff( ancestor, theirs );
3404
3405 KICAD_DIFF::DOCUMENT_DIFF ourDocDiff = ourDiff.Diff();
3406 KICAD_DIFF::DOCUMENT_DIFF theirDocDiff = theirDiff.Diff();
3407
3409 KICAD_DIFF::MERGE_PLAN plan = engine.Plan( ourDocDiff, theirDocDiff );
3410
3411 // A cancelled dialog leaves plan unresolved and falls through to the
3412 // marker flow below.
3413 if( aInteractive && !plan.Resolved() )
3414 {
3415 if( !Pgm().IsGUI() )
3416 {
3417 m_reporter->Report( _( "--interactive requires a GUI KiCad process; the console "
3418 "kicad-cli cannot open dialogs.\n" ),
3421 }
3422
3423 // Geometry context so the conflict viewer can render the actual
3424 // boards behind the conflict bbox highlight.
3425 const KICAD_DIFF::DIFF_COLOR_THEME theme;
3430
3431 // Build per-side bbox lookups so a "moved on theirs" item highlights
3432 // at its theirs-side coordinates when the user previews Theirs.
3434 KICAD_DIFF::CollectChangeBBoxes( theirDocDiff, ctx.theirsBBoxes );
3435
3436 DIALOG_KICAD_MERGE_3WAY dlg( wxTheApp->GetTopWindow(), plan, std::move( ctx ) );
3437
3438 if( dlg.ShowModal() == wxID_APPLY )
3439 plan = dlg.GetResolvedPlan();
3440 }
3441
3442 // Snapshot of the plan before the applier moves it; drives the
3443 // unresolved-conflict report below.
3444 const KICAD_DIFF::MERGE_PLAN planSnapshot = plan;
3445
3446 KICAD_DIFF::PCB_MERGE_APPLIER applier( ancestor, ours, theirs, std::move( plan ) );
3447 std::unique_ptr<BOARD> merged = applier.Apply();
3448
3449 if( !merged )
3450 {
3451 m_reporter->Report( _( "Merge applier failed to produce a board\n" ), RPT_SEVERITY_ERROR );
3453 }
3454
3455 // Serialize to the output path using the canonical PCB IO.
3456 PCB_IO_KICAD_SEXPR pcbIO;
3457
3458 try
3459 {
3460 pcbIO.SaveBoard( aOutput, merged.get() );
3461 }
3462 catch( const IO_ERROR& ioe )
3463 {
3464 m_reporter->Report( wxString::Format( _( "Failed to save merged board: %s\n" ), ioe.What() ),
3467 }
3468
3469 // BOARD_DESIGN_SETTINGS fields like m_DRCSeverities serialize to
3470 // .kicad_pro, not .kicad_pcb. Mirror only those specific fields onto
3471 // ancestor (still linked to its project via BOARD::SetProject) then
3472 // save ancestor's project alongside the merged board file. Whole-
3473 // BOARD_DESIGN_SETTINGS copy would alias shared_ptr<NET_SETTINGS>
3474 // across BOARDs and crash on ClearProject during SCRATCH_DOC release;
3475 // single-field mirror avoids that.
3476 if( applier.GetReport().projectFileTouched && ancestor && ancestor->GetProject() )
3477 {
3478 ancestor->GetDesignSettings().m_DRCSeverities = merged->GetDesignSettings().m_DRCSeverities;
3479
3480 // Mirror net settings (the applier copied them onto the result via
3481 // NET_SETTINGS::CopyFrom; do the same here to ancestor, which still
3482 // owns the project's nested-settings registration so SaveProjectCopy
3483 // walks the right entry).
3484 if( ancestor->GetDesignSettings().m_NetSettings && merged->GetDesignSettings().m_NetSettings )
3485 {
3486 ancestor->GetDesignSettings().m_NetSettings->CopyFrom( *merged->GetDesignSettings().m_NetSettings );
3487 }
3488
3489 // The applier stages drawing-sheet resolutions on the report (the
3490 // result BOARD is project-less). Mirror onto ancestor's project here
3491 // before SaveProjectCopy walks the PROJECT_FILE.
3492 if( applier.GetReport().drawingSheetFileSet )
3493 {
3495 }
3496
3497 wxFileName proFn( aOutput );
3498 proFn.SetExt( FILEEXT::ProjectFileExtension );
3499
3500 // JSON-patch path: flush ancestor's in-memory project to its JSON
3501 // cache, then patch only the diffed DOC_PROP fields onto the output
3502 // file. This preserves any non-diffed fields the user had at the
3503 // output path (text variables, last paths, layer presets etc.) that
3504 // a full SaveProjectCopy would silently overwrite.
3505 PROJECT_FILE& ancProj = ancestor->GetProject()->GetProjectFile();
3506 ancProj.Store();
3507
3508 const KICAD_DIFF::PCB_MERGE_APPLIER::REPORT& mergeReport = applier.GetReport();
3509
3510 // PROJECT_FILE::Store() flushes the project file's own params but not
3511 // its registered NESTED_SETTINGS. Flush only the nested settings the
3512 // merge resolved so the surgical patch does not overwrite unrelated
3513 // project subtrees.
3514 if( mergeReport.drcSeveritiesTouched )
3515 ancestor->GetDesignSettings().SaveToFile( wxEmptyString, true );
3516
3517 if( mergeReport.netClassesTouched && ancestor->GetDesignSettings().m_NetSettings )
3518 ancestor->GetDesignSettings().m_NetSettings->SaveToFile( wxEmptyString, true );
3519
3520 std::set<wxString> touched;
3521 if( mergeReport.drcSeveritiesTouched )
3522 touched.insert( KICAD_DIFF::DOC_PROP_DRC_SEVERITIES );
3523
3524 if( mergeReport.netClassesTouched )
3525 touched.insert( KICAD_DIFF::DOC_PROP_NET_CLASSES );
3526
3527 if( applier.GetReport().drawingSheetFileSet )
3528 touched.insert( KICAD_DIFF::DOC_PROP_DRAWING_SHEET );
3529
3530 if( !KICAD_DIFF::ApplyProjectFilePatches( proFn.GetFullPath(), *ancProj.Internals(), touched ) )
3531 {
3532 // Patch failed (existing output unparseable or write error).
3533 // Fall back to the legacy full-copy path so the user still gets
3534 // a project file even if it overwrites non-diffed customisations.
3535 if( !mgr.SaveProjectCopy( proFn.GetFullPath(), ancestor->GetProject() ) )
3536 {
3537 m_reporter->Report(
3538 wxString::Format( _( "Failed to save merged project file: %s\n" ), proFn.GetFullPath() ),
3541 }
3542 }
3543
3544 // Write a project-dir sibling file from staged report content. Empty
3545 // content removes the file so a TAKE_ANCESTOR resolution against an
3546 // ancestor with no file clears stale content at the output path.
3547 auto writeStagedFile = [&]( const wxString& aPath, const wxString& aContent, const wxString& aLabel ) -> bool
3548 {
3549 if( aContent.IsEmpty() )
3550 {
3551 if( wxFileExists( aPath ) )
3552 wxRemoveFile( aPath );
3553
3554 return true;
3555 }
3556
3557 wxFile out;
3558
3559 if( !out.Open( aPath, wxFile::write ) || !out.Write( aContent ) )
3560 {
3561 m_reporter->Report( wxString::Format( _( "Failed to save merged %s: %s\n" ), aLabel, aPath ),
3563 return false;
3564 }
3565
3566 return true;
3567 };
3568
3569 // Custom DRC rules: write the applier's staged content next to the
3570 // merged board so the chosen side's rules apply at next DRC run.
3571 if( applier.GetReport().customDrcRulesSet )
3572 {
3573 wxFileName druFn( aOutput );
3574 druFn.SetExt( FILEEXT::DesignRulesFileExtension );
3575
3576 if( !writeStagedFile( druFn.GetFullPath(), applier.GetReport().customDrcRules, _( "custom DRC rules" ) ) )
3577 {
3579 }
3580 }
3581
3582 // Footprint / symbol library tables: write into the merged project
3583 // directory. Both files have no extension.
3584 if( applier.GetReport().fpLibTableSet )
3585 {
3586 wxFileName fpFn( aOutput );
3587 fpFn.SetFullName( wxString::FromUTF8( FILEEXT::FootprintLibraryTableFileName ) );
3588
3589 if( !writeStagedFile( fpFn.GetFullPath(), applier.GetReport().fpLibTable, _( "footprint library table" ) ) )
3590 {
3592 }
3593 }
3594
3595 if( applier.GetReport().symLibTableSet )
3596 {
3597 wxFileName symFn( aOutput );
3598 symFn.SetFullName( wxString::FromUTF8( FILEEXT::SymbolLibraryTableFileName ) );
3599
3600 if( !writeStagedFile( symFn.GetFullPath(), applier.GetReport().symLibTable, _( "symbol library table" ) ) )
3601 {
3603 }
3604 }
3605 }
3606
3607 // Surface post-apply validator findings (refdes collisions, schema
3608 // mismatch, missed connectivity rebuild). Advisory — they do not change the
3609 // exit code, only the merge's resolved/unresolved status does.
3611 m_reporter->Report( wxString::Format( wxS( "%s: %s\n" ), f.validator, f.message ), f.severity );
3612
3613 // The merged board was written to m_outputPath above, so the output is
3614 // always a valid file. Unresolved conflicts are reported and signalled via
3615 // the exit code; the user resolves them with the interactive mergetool.
3616 if( !planSnapshot.Resolved() )
3617 {
3618 m_reporter->Report( wxString::Format( _( "Merge completed with %zu unresolved conflict(s) in %s\n" ),
3619 planSnapshot.ConflictCount(), aOutput ),
3622 }
3623
3625}
3626
3627
3628// ============================================================================
3629// JobFpDiff: fp_diff implementation
3630// ============================================================================
3632#include <jobs/job_fp_diff.h>
3633
3634
3635// Load one side of a footprint-library diff into its owner vector and name map.
3636// When aAllowEmpty is set an empty path resolves to a clean (empty) side; the
3637// non-interactive job path leaves it unset so a missing path is an input error.
3638static int loadFootprintLibrarySide( const wxString& aPath,
3639 std::vector<std::unique_ptr<FOOTPRINT>>& aOwners,
3640 KICAD_DIFF::FP_LIB_DIFFER::FOOTPRINT_MAP& aMap, bool aAllowEmpty,
3641 REPORTER& aReporter )
3642{
3643 if( aAllowEmpty && aPath.IsEmpty() )
3645
3646 try
3647 {
3648 auto loaded = KICAD_DIFF::FP_LIB_DIFFER::LoadLibrary( aPath );
3649 aOwners = std::move( loaded.first );
3650 aMap = std::move( loaded.second );
3652 }
3653 catch( const IO_ERROR& ioe )
3654 {
3655 aReporter.Report( wxString::Format( _( "Failed to load %s: %s\n" ), aPath, ioe.What() ),
3657 }
3658 catch( const std::exception& e )
3659 {
3660 aReporter.Report(
3661 wxString::Format( _( "Failed to load %s: %s\n" ), aPath, wxString::FromUTF8( e.what() ) ),
3663 }
3664
3666}
3667
3668
3669// Flatten a footprint-library name map into a single DOCUMENT_GEOMETRY tinted
3670// with the supplied per-side theme colour.
3673{
3675
3676 for( const auto& [name, footprint] : aMap )
3677 {
3678 if( footprint )
3679 KICAD_DIFF::AppendGeometry( geometry, KICAD_DIFF::ExtractFootprintGeometry( *footprint, aColor ) );
3680 }
3681
3682 return geometry;
3683}
3684
3685
3687{
3688 JOB_FP_DIFF* diffJob = dynamic_cast<JOB_FP_DIFF*>( aJob );
3689
3690 if( !diffJob )
3692
3693 wxFileName dirA( diffJob->m_inputA );
3694 dirA.MakeAbsolute();
3695 wxFileName dirB( diffJob->m_inputB );
3696 dirB.MakeAbsolute();
3697
3698 std::vector<std::unique_ptr<FOOTPRINT>> ownersA;
3699 std::vector<std::unique_ptr<FOOTPRINT>> ownersB;
3702
3703 if( int rc = loadFootprintLibrarySide( dirA.GetFullPath(), ownersA, mapA, false, *m_reporter );
3705 {
3706 return rc;
3707 }
3708
3709 if( int rc = loadFootprintLibrarySide( dirB.GetFullPath(), ownersB, mapB, false, *m_reporter );
3711 {
3712 return rc;
3713 }
3714
3715 KICAD_DIFF::FP_LIB_DIFFER differ( mapA, mapB, diffJob->m_inputB );
3717
3718 int diffExitCode = KICAD_DIFF::DiffExitCode( result );
3719
3720 if( diffJob->m_exitCodeOnly )
3721 return diffExitCode;
3722
3724 KICAD_DIFF::MakeEmitOptions( *diffJob, diffJob->m_inputA, diffJob->m_inputB );
3726 emitOpts.referenceGeometry = [&]( const KIGFX::COLOR4D& aColor )
3727 { return footprintLibraryGeometry( mapA, aColor ); };
3728 emitOpts.comparisonGeometry = [&]( const KIGFX::COLOR4D& aColor )
3729 { return footprintLibraryGeometry( mapB, aColor ); };
3730
3731 return KICAD_DIFF::EmitDiffResult( result, emitOpts, diffExitCode, *m_reporter );
3732}
3733
3734
3735// ============================================================================
3736// JobOpenDiffDialog: load two on-disk files and open DIALOG_KICAD_DIFF.
3737// Dispatched from the project manager / PR-review dialog via KIWAY.
3738// ============================================================================
3742#include <jobs/scratch_doc.h>
3743
3744
3746 const wxString& aFileB, const wxString& aLabelA,
3747 const wxString& aLabelB, wxWindow* aParent,
3748 REPORTER* aReporter )
3749{
3750 // Restore m_reporter on scope exit so a caller's transient (often
3751 // stack-local) reporter doesn't outlive this call as a dangling member.
3753 aReporter ? aReporter : m_reporter );
3754
3755 wxWindow* parent = aParent ? aParent : ( wxTheApp ? wxTheApp->GetTopWindow() : nullptr );
3756
3758
3759 auto loadBoardScratch = [&]( const wxString& aPath )
3760 {
3761 return loadScratchBoard( mgr, aPath, /* aInitializeAfterLoad */ false );
3762 };
3763
3766 KICAD_DIFF::DOCUMENT_GEOMETRY compGeometry;
3767
3768 auto loadFootprintFile = [&]( const wxString& aPath ) -> std::unique_ptr<FOOTPRINT>
3769 {
3770 if( aPath.IsEmpty() )
3771 return nullptr;
3772
3773 wxFileName fn( aPath );
3774 fn.MakeAbsolute();
3775
3776 // A single .kicad_mod's internal (footprint ...) name need not match its
3777 // filename, so load the file's sole footprint via ImportFootprint rather
3778 // than FootprintLoad (which treats the directory as a .pretty library and
3779 // keys by basename), matching runFpLibMerge's single-file path.
3781 wxString name;
3782 return std::unique_ptr<FOOTPRINT>( io.ImportFootprint( fn.GetFullPath(), name ) );
3783 };
3784
3785 switch( aKind )
3786 {
3788 {
3789 SCRATCH_DOC<BOARD> a = loadBoardScratch( aFileA );
3790 SCRATCH_DOC<BOARD> b = loadBoardScratch( aFileB );
3791
3792 // Synthesize empty boards for ADDED / REMOVED sides so the differ
3793 // can still produce a meaningful per-item list rather than failing
3794 // on an empty input file.
3795 BOARD emptyA;
3796 BOARD emptyB;
3797
3798 if( !a.doc && !aFileA.IsEmpty() )
3799 {
3800 m_reporter->Report( wxString::Format( _( "Failed to load %s\n" ), aFileA ), RPT_SEVERITY_ERROR );
3802 }
3803
3804 if( !b.doc && !aFileB.IsEmpty() )
3805 {
3806 m_reporter->Report( wxString::Format( _( "Failed to load %s\n" ), aFileB ), RPT_SEVERITY_ERROR );
3808 }
3809
3810 BOARD* boardA = a.doc ? a.doc.get() : &emptyA;
3811 BOARD* boardB = b.doc ? b.doc.get() : &emptyB;
3812
3813 KICAD_DIFF::PCB_DIFFER differ( boardA, boardB, aFileB );
3814 result = differ.Diff();
3815
3816 // Extract background geometry so the dialog's canvas shows the
3817 // actual board outline + footprint footprints beneath the diff
3818 // bbox rectangles. Theme defaults: muted blue (ref) / gold (comp).
3819 const KICAD_DIFF::DIFF_COLOR_THEME theme;
3820 refGeometry = KICAD_DIFF::ExtractBoardGeometry( *boardA, theme.reference );
3821 compGeometry = KICAD_DIFF::ExtractBoardGeometry( *boardB, theme.comparison );
3822
3823 const wxString labelA = aLabelA.IsEmpty() ? aFileA : aLabelA;
3824 const wxString labelB = aLabelB.IsEmpty() ? aFileB : aLabelB;
3825
3827 parent, labelA, labelB, result, std::move( refGeometry ), std::move( compGeometry ),
3828 [boardA, boardB, color = theme.reference]( WIDGET_DIFF_CANVAS& aCanvas, const KIID_PATH& )
3829 {
3830 KICAD_DIFF::ConfigurePcbDiffCanvasContext( aCanvas, boardA, boardB, color );
3831 } );
3832 dlg.ShowModal();
3833
3835 }
3837 {
3838 std::vector<std::unique_ptr<FOOTPRINT>> ownersA;
3839 std::vector<std::unique_ptr<FOOTPRINT>> ownersB;
3842
3843 if( int rc = loadFootprintLibrarySide( aFileA, ownersA, mapA, true, *m_reporter );
3845 {
3846 return rc;
3847 }
3848
3849 if( int rc = loadFootprintLibrarySide( aFileB, ownersB, mapB, true, *m_reporter );
3851 {
3852 return rc;
3853 }
3854
3855 KICAD_DIFF::FP_LIB_DIFFER differ( mapA, mapB, aFileB );
3856 result = differ.Diff();
3857
3858 const KICAD_DIFF::DIFF_COLOR_THEME theme;
3859 refGeometry = footprintLibraryGeometry( mapA, theme.reference );
3860 compGeometry = footprintLibraryGeometry( mapB, theme.comparison );
3861 break;
3862 }
3864 {
3865 std::unique_ptr<FOOTPRINT> footprintA;
3866 std::unique_ptr<FOOTPRINT> footprintB;
3867
3868 try
3869 {
3870 footprintA = loadFootprintFile( aFileA );
3871 }
3872 catch( const IO_ERROR& ioe )
3873 {
3874 m_reporter->Report( wxString::Format( _( "Failed to load %s: %s\n" ), aFileA, ioe.What() ),
3877 }
3878
3879 try
3880 {
3881 footprintB = loadFootprintFile( aFileB );
3882 }
3883 catch( const IO_ERROR& ioe )
3884 {
3885 m_reporter->Report( wxString::Format( _( "Failed to load %s: %s\n" ), aFileB, ioe.What() ),
3888 }
3889
3892 const wxString nameA = wxFileName( aFileA ).GetName();
3893 const wxString nameB = wxFileName( aFileB ).GetName();
3894 const wxString itemName = !nameB.IsEmpty() ? nameB : nameA;
3895
3896 if( footprintA )
3897 mapA[itemName] = footprintA.get();
3898
3899 if( footprintB )
3900 mapB[itemName] = footprintB.get();
3901
3902 KICAD_DIFF::FP_LIB_DIFFER differ( mapA, mapB, aFileB );
3903 result = differ.Diff();
3904
3905 const KICAD_DIFF::DIFF_COLOR_THEME theme;
3906
3907 if( footprintA )
3908 refGeometry = KICAD_DIFF::ExtractFootprintGeometry( *footprintA, theme.reference );
3909
3910 if( footprintB )
3911 compGeometry = KICAD_DIFF::ExtractFootprintGeometry( *footprintB, theme.comparison );
3912
3913 break;
3914 }
3915 default:
3916 m_reporter->Report( _( "Unsupported document kind for this dispatcher.\n" ), RPT_SEVERITY_ERROR );
3918 }
3919
3920 const wxString labelA = aLabelA.IsEmpty() ? aFileA : aLabelA;
3921 const wxString labelB = aLabelB.IsEmpty() ? aFileB : aLabelB;
3922
3923 DIALOG_KICAD_DIFF dlg( parent, labelA, labelB, result, std::move( refGeometry ), std::move( compGeometry ) );
3924 dlg.ShowModal();
3925
3927}
3928
3929
3930// ============================================================================
3931// JobFpLibMerge: 3-way merge of .pretty footprint libraries.
3932// ============================================================================
3935
3936
3937int PCBNEW_JOBS_HANDLER::runFpLibMerge( const wxString& aAncestor, const wxString& aOurs,
3938 const wxString& aTheirs, const wxString& aOutput,
3939 bool aSingleFile )
3940{
3941 if( aOutput.IsEmpty() )
3942 {
3943 m_reporter->Report( _( "--output is required\n" ), RPT_SEVERITY_ERROR );
3945 }
3946
3947 struct LIB_SIDE
3948 {
3949 std::vector<std::unique_ptr<FOOTPRINT>> owners;
3951 };
3952
3953 LIB_SIDE ancestor, ours, theirs;
3954
3955 // Accept either a `.pretty` directory (library mode) or a single `.kicad_
3956 // mod` file (git's per-file driver mode). Extension autodetection works
3957 // for native invocations, but git's external driver passes temp paths
3958 // (`.merge_file_XXX`) with no extension, so the `--single-file` flag
3959 // overrides on demand.
3960 auto isSingleFile = [&]( const wxString& aPath )
3961 {
3962 if( aSingleFile )
3963 return true;
3964
3965 return wxFileName( aPath ).GetExt() == FILEEXT::KiCadFootprintFileExtension;
3966 };
3967
3968 auto loadSide = [&]( const wxString& aPath, LIB_SIDE& aSide ) -> int
3969 {
3970 try
3971 {
3972 if( isSingleFile( aPath ) )
3973 {
3975 wxString name;
3976 std::unique_ptr<FOOTPRINT> fp( io.ImportFootprint( aPath, name ) );
3977
3978 if( !fp )
3980
3981 // Use the footprint's own item-name (LIB_ID) if set,
3982 // falling back to the file basename. Both sides must
3983 // agree for the differ/applier to align them.
3984 const UTF8& itemName = fp->GetFPID().GetLibItemName();
3985 const wxString key = itemName.empty() ? name : itemName.wx_str();
3986
3987 aSide.map[key] = fp.get();
3988 aSide.owners.push_back( std::move( fp ) );
3990 }
3991
3992 auto loaded = KICAD_DIFF::FP_LIB_DIFFER::LoadLibrary( aPath );
3993 aSide.owners = std::move( loaded.first );
3994 aSide.map = std::move( loaded.second );
3996 }
3997 catch( const IO_ERROR& ioe )
3998 {
3999 m_reporter->Report( wxString::Format( _( "Failed to load %s: %s\n" ), aPath, ioe.What() ),
4001 }
4002 catch( const std::exception& e )
4003 {
4004 m_reporter->Report(
4005 wxString::Format( _( "Failed to load %s: %s\n" ), aPath, wxString::FromUTF8( e.what() ) ),
4007 }
4008
4010 };
4011
4012 if( int rc = loadSide( aAncestor, ancestor ); rc != CLI::EXIT_CODES::SUCCESS )
4013 return rc;
4014
4015 if( int rc = loadSide( aOurs, ours ); rc != CLI::EXIT_CODES::SUCCESS )
4016 return rc;
4017
4018 if( int rc = loadSide( aTheirs, theirs ); rc != CLI::EXIT_CODES::SUCCESS )
4019 return rc;
4020
4021 KICAD_DIFF::FP_LIB_DIFFER ourDiff( ancestor.map, ours.map, aOurs );
4022 KICAD_DIFF::FP_LIB_DIFFER theirDiff( ancestor.map, theirs.map, aTheirs );
4023
4024 KICAD_DIFF::DOCUMENT_DIFF ourDocDiff = ourDiff.Diff();
4025 KICAD_DIFF::DOCUMENT_DIFF theirDocDiff = theirDiff.Diff();
4026
4028 KICAD_DIFF::MERGE_PLAN plan = engine.Plan( ourDocDiff, theirDocDiff );
4029
4030 const KICAD_DIFF::MERGE_PLAN planSnapshot = plan;
4031
4032 KICAD_DIFF::FP_LIB_MERGE_APPLIER applier( ancestor.map, ours.map, theirs.map, std::move( plan ) );
4033 std::vector<std::unique_ptr<FOOTPRINT>> merged = applier.Apply();
4034
4035 // Per-property footprint merge isn't implemented; MERGE_PROPS resolutions
4036 // are downgraded to TAKE_OURS. Surface that as unresolved so the user sees
4037 // a marker instead of silent partial-merge.
4038 const bool hadSilentFallback = applier.GetReport().mergePropsFallback > 0;
4039
4040 const bool singleFileOutput = isSingleFile( aOutput );
4041
4042 // .pretty is a directory; .kicad_mod is a single file. wxFileName parses
4043 // a path ending in `.pretty` as a file with that extension, so library
4044 // mode uses DirName(); single-file mode keeps the file path as-is and
4045 // hands it directly to FootprintSave, which auto-detects .kicad_mod via
4046 // its own extension check.
4047 wxFileName outFn;
4048
4049 if( singleFileOutput )
4050 outFn = wxFileName( aOutput );
4051 else
4052 outFn = wxFileName::DirName( aOutput );
4053
4054 outFn.MakeAbsolute();
4055
4056 // In library mode wxFileName::DirName treats `foo.pretty` as a directory,
4057 // so GetPath() returns the .pretty itself. In single-file mode GetPath()
4058 // returns the file's parent dir. Either way it's the directory we Mkdir
4059 // into.
4060 const wxString outDir = outFn.GetPath();
4061
4062 if( !wxFileName::DirExists( outDir ) && !wxFileName::Mkdir( outDir, wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL ) )
4063 {
4064 m_reporter->Report( wxString::Format( _( "Cannot create output directory %s\n" ), outDir ),
4067 }
4068
4069 try
4070 {
4072
4073 if( singleFileOutput )
4074 {
4075 // Git per-file driver mode: one merged footprint -> one .kicad_mod.
4076 // Multiple survivors would lose data; flag that as an error since
4077 // single-file input by definition has at most one footprint per
4078 // side.
4079 if( merged.size() > 1 )
4080 {
4081 m_reporter->Report( _( "Single-file fp merge produced multiple footprints; refusing to "
4082 "collapse into one .kicad_mod\n" ),
4085 }
4086
4087 if( merged.empty() )
4088 {
4089 // All sides deleted the footprint. Remove the output file if
4090 // it existed, leaving nothing where the merged content would
4091 // have gone.
4092 if( wxFileName::FileExists( outFn.GetFullPath() ) )
4093 wxRemoveFile( outFn.GetFullPath() );
4094 }
4095 else if( wxFileName( outFn.GetFullPath() ).GetExt() == FILEEXT::KiCadFootprintFileExtension )
4096 {
4097 // FootprintSave's .kicad_mod extension autodetection handles
4098 // the write to the path as-given.
4099 io.FootprintSave( outFn.GetFullPath(), merged.front().get(), nullptr );
4100 }
4101 else
4102 {
4103 // Git driver mode: output is an extension-less temp path
4104 // (typically `.merge_file_XXX`). FootprintSave's
4105 // autodetection would treat it as a library directory.
4106 // Format directly via PRETTIFIED_FILE_OUTPUTFORMATTER, the
4107 // same writer the sexpr lib cache uses.
4108 PRETTIFIED_FILE_OUTPUTFORMATTER formatter( outFn.GetFullPath() );
4109 io.SetOutputFormatter( &formatter );
4110 io.Format( merged.front().get() );
4111 formatter.Finish();
4112 }
4113 }
4114 else
4115 {
4116 // Library mode. Footprints in `merged` are the survivors. Any
4117 // footprint already in the output `.pretty` but absent from
4118 // `merged` is a stale leftover from a previous invocation (or a
4119 // resolved DELETE / TAKE_ANCESTOR-with-no-ancestor case). Delete
4120 // those before saving the survivors, otherwise the resolved
4121 // DELETE never propagates to disk.
4122 std::set<wxString> mergedNames;
4123
4124 for( const auto& fp : merged )
4125 {
4126 if( fp )
4127 mergedNames.insert( fp->GetFPID().GetLibItemName() );
4128 }
4129
4130 wxArrayString existing;
4131 io.FootprintEnumerate( existing, outDir, false, nullptr );
4132
4133 for( const wxString& name : existing )
4134 {
4135 if( !mergedNames.count( name ) )
4136 io.FootprintDelete( outDir, name, nullptr );
4137 }
4138
4139 for( const auto& fp : merged )
4140 {
4141 if( !fp )
4142 continue;
4143
4144 const wxString name = fp->GetFPID().GetLibItemName();
4145
4146 if( io.FootprintExists( outDir, name, nullptr ) )
4147 io.FootprintDelete( outDir, name, nullptr );
4148
4149 io.FootprintSave( outDir, fp.get(), nullptr );
4150 }
4151 }
4152 }
4153 catch( const IO_ERROR& ioe )
4154 {
4155 m_reporter->Report( wxString::Format( _( "Failed to save merged footprint library: %s\n" ), ioe.What() ),
4158 }
4159
4160 // The merged library was saved above, so the output is always valid.
4161 if( !planSnapshot.Resolved() || hadSilentFallback )
4162 {
4163 // Conflict count = engine-unresolved ∪ applier-downgraded (deduped, so
4164 // an item that was both unresolved and silently downgraded counts once).
4165 std::set<KIID_PATH> conflicts( planSnapshot.unresolved.begin(), planSnapshot.unresolved.end() );
4166
4167 for( const KIID_PATH& id : applier.GetReport().mergePropsFallbackIds )
4168 conflicts.insert( id );
4169
4170 m_reporter->Report( wxString::Format( _( "Footprint library merge completed with %zu unresolved "
4171 "conflict(s) in %s\n" ),
4172 conflicts.size(), aOutput ),
4175 }
4176
4178}
@ VIEW3D_BOTTOM
Definition 3d_enums.h:77
const char * name
constexpr EDA_IU_SCALE pcbIUScale
Definition base_units.h:121
KIFACE_BASE & Kiface()
Global KIFACE_BASE "get" accessor.
#define RANGE_SCALE_3D
This defines the range that all coord will have to be rendered.
wxString FormatBoardStatisticsJson(const BOARD_STATISTICS_DATA &aData, BOARD *aBoard, const UNITS_PROVIDER &aUnitsProvider, const wxString &aProjectName, const wxString &aBoardName)
void ComputeBoardStatistics(BOARD *aBoard, const BOARD_STATISTICS_OPTIONS &aOptions, BOARD_STATISTICS_DATA &aData)
wxString FormatBoardStatisticsReport(const BOARD_STATISTICS_DATA &aData, BOARD *aBoard, const UNITS_PROVIDER &aUnitsProvider, const wxString &aProjectName, const wxString &aBoardName)
void InitializeBoardStatisticsData(BOARD_STATISTICS_DATA &aData)
BOX2< VECTOR2I > BOX2I
Definition box2.h:918
PROJECTION_TYPE
Definition camera.h:36
static wxString m_DrawingSheetFileName
the name of the drawing sheet file, or empty to use the default drawing sheet
Definition base_screen.h:81
Helper class to handle information needed to display 3D board.
double BiuTo3dUnits() const noexcept
Board integer units To 3D units.
void SetVisibleLayers(const std::bitset< LAYER_3D_END > &aLayers)
std::bitset< LAYER_3D_END > GetVisibleLayers() const
void SetBoard(BOARD *aBoard) noexcept
Set current board to be rendered.
void SetLayerColors(const std::map< int, COLOR4D > &aColors)
EDA_3D_VIEWER_SETTINGS * m_Cfg
std::map< int, COLOR4D > m_ColorOverrides
allows to override color scheme colors
void Set3dCacheManager(S3D_CACHE *aCacheMgr) noexcept
Update the cache manager pointer.
virtual void Push(const wxString &aMessage=wxEmptyString, int aCommitFlags=0) override
Execute the changes.
std::shared_ptr< NET_SETTINGS > m_NetSettings
std::map< int, SEVERITY > m_DRCSeverities
std::shared_ptr< DRC_ENGINE > m_DRCEngine
const VECTOR2I & GetAuxOrigin() const
static std::unique_ptr< BOARD > CreateEmptyBoard(PROJECT *aProject)
static std::unique_ptr< BOARD > Load(const wxString &aFileName, PCB_IO_MGR::PCB_FILE_T aFormat, PROJECT *aProject, const OPTIONS &aOptions)
static bool SaveBoard(wxString &aFileName, BOARD *aBoard, PCB_IO_MGR::PCB_FILE_T aFormat)
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:373
void SetCurrentVariant(const wxString &aVariant)
Definition board.cpp:2949
const PAGE_INFO & GetPageSettings() const
Definition board.h:901
const ZONES & Zones() const
Definition board.h:425
void RecordDRCExclusions()
Scan existing markers and record data from any that are Excluded.
Definition board.cpp:392
TITLE_BLOCK & GetTitleBlock()
Definition board.h:907
const std::map< wxString, wxString > & GetProperties() const
Definition board.h:469
const FOOTPRINTS & Footprints() const
Definition board.h:421
const TRACKS & Tracks() const
Definition board.h:419
const wxString & GetFileName() const
Definition board.h:410
std::vector< PCB_MARKER * > ResolveDRCExclusions(bool aCreateMarkers)
Rebuild DRC markers from the serialized data in BOARD_DESIGN_SETTINGS.
Definition board.cpp:454
wxString GetVariantDescription(const wxString &aVariantName) const
Definition board.cpp:3068
int GetFileFormatVersionAtLoad() const
Definition board.h:525
const PCB_PLOT_PARAMS & GetPlotOptions() const
Definition board.h:904
const wxString GetLayerName(PCB_LAYER_ID aLayer) const
Return the name of a aLayer.
Definition board.cpp:802
wxString GetCurrentVariant() const
Definition board.h:473
PROJECT * GetProject() const
Definition board.h:662
BOARD_DESIGN_SETTINGS & GetDesignSettings() const
Definition board.cpp:1158
const LSET & GetEnabledLayers() const
A proxy function that calls the corresponding function in m_BoardSettings.
Definition board.cpp:1043
void SynchronizeProperties()
Copy the current project's text variables into the boards property cache.
Definition board.cpp:2929
BOX2I ComputeBoundingBox(bool aBoardEdgesOnly=false, bool aPhysicalLayersOnly=false) const
Calculate the bounding box containing all board items (or board edge segments).
Definition board.cpp:2510
void DeleteMARKERs()
Delete all MARKERS from the board.
Definition board.cpp:1852
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:270
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:1192
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:444
void SetPath(const wxString &aPath)
void Save(FOOTPRINT *aFootprintFilter=nullptr)
Save the footprint cache or a single footprint from it to disk.
boost::ptr_map< wxString, FP_CACHE_ENTRY > & GetFootprints()
Export board to GenCAD file format.
void UseIndividualShapes(bool aUnique)
Make pad shapes unique.
void UsePinNamesUnique(bool aUnique)
Make pin names unique.
void StoreOriginCoordsInFile(bool aStore)
Store origin coordinate in GenCAD file.
void FlipBottomPads(bool aFlip)
Flip pad shapes on the bottom side.
void SetPlotOffet(VECTOR2I aOffset)
Set the coordinates offset when exporting items.
bool WriteFile(const wxString &aFullFileName)
Export a GenCAD file.
void SetMapFileFormat(PLOT_FORMAT aMapFmt)
Initialize the format for the drill map file.
bool GenDrillReportFile(const wxString &aFullFileName, REPORTER *aReporter=nullptr)
Create a plain text report file giving a list of drill values and drill count for through holes,...
GERBER_JOBFILE_WRITER is a class used to create Gerber job file a Gerber job file stores info to make...
bool CreateJobFile(const wxString &aFullFilename)
Creates a Gerber job file.
void AddGbrFile(PCB_LAYER_ID aLayer, wxString &aFilename)
add a gerber file name and type in job file list
virtual bool EndPlot() override
Used to create Gerber drill files.
bool CreateDrillandMapFilesSet(const wxString &aPlotDirectory, bool aGenDrill, bool aGenMap, bool aGenTenting, REPORTER *aReporter=nullptr)
Create the full set of Excellon drill file for the board filenames are computed from the board name,...
void SetOptions(const VECTOR2I &aOffset)
Initialize internal parameters to match drill options.
void SetFormat(int aRightDigits=6)
Initialize internal parameters to match the given format.
Hold an error message and may be used when throwing exceptions containing meaningful error messages.
virtual const wxString What() const
A composite of Problem() and Where()
Wrapper to expose an API for writing IPC-D356 files.
Definition export_d356.h:54
bool Write(const wxString &aFilename)
Generates and writes the netlist to a given path.
wxString m_inputB
Comparison document (file or directory)
wxString m_inputA
Reference document (file or directory)
void Register(const std::string &aJobTypeName, std::function< int(JOB *job)> aHandler, std::function< bool(JOB *job, wxWindow *aParent)> aConfigHandler)
JOB_DISPATCHER(KIWAY *aKiway)
PROGRESS_REPORTER * m_progressReporter
REPORTER * m_reporter
JOB_EXPORT_PCB_3D::FORMAT m_format
EXPORTER_STEP_PARAMS m_3dparams
Despite the name; also used for other formats.
wxString GetSettingsDialogTitle() const override
ODB_COMPRESSION m_compressionMode
@ ALL_LAYERS_ONE_FILE
DEPRECATED MODE.
bool m_pdfSingle
This is a hack to deal with cli having the wrong behavior We will deprecate out the wrong behavior,...
GEN_MODE m_pdfGenMode
The background color specified in a hex string.
LSEQ m_plotOnAllLayersSequence
Used by SVG & PDF.
std::optional< wxString > m_argLayers
std::optional< wxString > m_argCommonLayers
LSEQ m_plotLayerSequence
Layers to include on all individual layer prints.
wxString m_variant
Variant name for variant-aware filtering.
void SetDefaultOutputPath(const wxString &aReferenceName)
wxString m_libraryPath
wxString m_outputLibraryPath
Job: diff two PCB files end-to-end via PCB_DIFFER.
bool m_saveBoard
Definition job_pcb_drc.h:36
bool m_reportAllTrackErrors
Definition job_pcb_drc.h:32
bool m_refillZones
Definition job_pcb_drc.h:35
Job to import a non-KiCad PCB file to KiCad format.
std::map< wxString, wxString > m_layerMap
Explicit overrides from source layer name to KiCad layer name (canonical board-file name,...
wxString m_reportFile
IMPORT_REPORT_FORMAT m_reportFormat
wxString m_inputFile
VECTOR3D m_lightBottomIntensity
VECTOR3D m_lightTopIntensity
VECTOR3D m_lightCameraIntensity
VECTOR3D m_rotation
wxString m_filename
bool m_useBoardStackupColors
VECTOR3D m_lightSideIntensity
std::string m_appearancePreset
bool m_exitCodeViolations
Definition job_rc.h:52
int m_severity
Definition job_rc.h:49
UNITS m_units
Definition job_rc.h:48
OUTPUT_FORMAT m_format
Definition job_rc.h:50
wxString m_filename
Definition job_rc.h:47
An simple container class that lets us dispatch output jobs to kifaces.
Definition job.h:184
wxString ResolveOutputPath(const wxString &aPath, bool aPathIsDirectory, PROJECT *aProject) const
Definition job.cpp:100
void AddOutput(wxString aOutputPath)
Definition job.h:216
wxString GetFullOutputPath(PROJECT *aProject) const
Returns the full output path for the job, taking into account the configured output path,...
Definition job.cpp:150
wxString GetWorkingOutputPath() const
Returns the working output path for the job, if one has been set.
Definition job.h:246
wxString GetConfiguredOutputPath() const
Returns the configured output path for the job.
Definition job.h:235
void SetTitleBlock(const TITLE_BLOCK &aTitleBlock)
Definition job.h:204
void SetWorkingOutputPath(const wxString &aPath)
Sets a transient output path for the job, it takes priority over the configured output path when GetF...
Definition job.h:241
const std::map< wxString, wxString > & GetVarOverrides() const
Definition job.h:197
JSON_SETTINGS_INTERNALS * Internals()
virtual bool Store()
Stores the current parameters into the JSON document represented by this object Note: this doesn't do...
Diff two .pretty footprint library directories.
static std::pair< std::vector< std::unique_ptr< FOOTPRINT > >, FOOTPRINT_MAP > LoadLibrary(const wxString &aPrettyPath)
Load a .pretty directory into a FOOTPRINT_MAP.
DOCUMENT_DIFF Diff() override
Produce a DOCUMENT_DIFF of the inputs the concrete differ was constructed with.
std::map< wxString, const FOOTPRINT * > FOOTPRINT_MAP
Three-way merge plan generator.
MERGE_PLAN Plan(const DOCUMENT_DIFF &aAncestorOurs, const DOCUMENT_DIFF &aAncestorTheirs) const
Plan the merge given the canonical pair of diffs.
const REPORT & GetReport() const
std::vector< std::unique_ptr< ITEM > > Apply()
Diff two already-parsed BOARDs and produce a DOCUMENT_DIFF.
Definition pcb_differ.h:52
DOCUMENT_DIFF Diff() override
Produce a DOCUMENT_DIFF of the inputs the concrete differ was constructed with.
Materialize a MERGE_PLAN into a real merged BOARD.
std::unique_ptr< BOARD > Apply()
Produce the merged board.
const REPORT & GetReport() const
Read the new s-expression based KiCad netlist format.
virtual void LoadNetlist() override
Load the contents of the netlist file into aNetlist.
A color representation with 4 components: red, green, blue, alpha.
Definition color4d.h:101
A minimalistic software bus for communications between various DLLs/DSOs (DSOs) within the same KiCad...
Definition kiway.h:311
virtual KIWAY_PLAYER * Player(FRAME_T aFrameType, bool doCreate=true, wxTopLevelWindow *aParent=nullptr)
Return the KIWAY_PLAYER* given a FRAME_T.
Definition kiway.cpp:388
@ 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)
std::unique_ptr< BOARD > m_cliBoard
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 bool ImportGeneratesProjectLibrary(PCB_FILE_T aFileType)
Return true when importing aFileType should materialize a project footprint library.
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:690
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:63
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:201
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:72
virtual REPORTER & Report(const wxString &aText, SEVERITY aSeverity=RPT_SEVERITY_UNDEFINED)
Report a string with a given severity.
Definition reporter.h:101
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.
bool UnloadProject(PROJECT *aProject, bool aSave=true)
Save, unload and unregister the given PROJECT.
PROJECT & Prj() const
A helper while we are not MDI-capable – return the one and only project.
bool IsProjectOpenNotDummy() const
Helper for checking if we have a project open that is not a dummy 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
FOOTPRINT_IMPORT_RECONCILE_RESULT ReconcileImportedFootprints(std::vector< std::unique_ptr< FOOTPRINT > > aDefinitions, BOARD &aBoard, PROJECT &aProject, const wxString &aBoardPath, const std::map< std::string, UTF8 > *aProperties, REPORTER &aReporter)
Reconcile aBoard against the definitions an importer retained while loading it.
@ 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:559
@ LAYER_3D_BACKGROUND_BOTTOM
Definition layer_ids.h:558
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
#define MAIL_SCH_GET_NETLIST_CANCELLED
Reply payload for MAIL_SCH_GET_NETLIST when the user deliberately aborts netlist generation (for exam...
Definition mail_type.h:66
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