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 <import_net_names.h>
22#include <richio.h>
23#include <string_utils.h>
24#include <wx/crt.h>
25#include <wx/dir.h>
26#include <wx/zipstrm.h>
27#include <wx/filename.h>
28#include <wx/tokenzr.h>
29#include <wx/wfstream.h>
30
31#include <nlohmann/json.hpp>
32
33#include "pcbnew_jobs_handler.h"
34#include <board_loader.h>
35#include <jobs/scratch_doc.h>
36#include <board_commit.h>
43#include <trace_helpers.h>
45#include <pcb_drill_chart.h>
46#include <drc/drc_engine.h>
48#include <drc/drc_item.h>
49#include <drc/drc_report.h>
52#include <footprint.h>
54#include <jobs/job_export_bom.h>
56#include <jobs/job_fp_upgrade.h>
75#include <jobs/job_pcb_render.h>
76#include <jobs/job_pcb_drc.h>
77#include <jobs/job_pcb_import.h>
80#include <eda_units.h>
82#include <lset.h>
83#include <cli/exit_codes.h>
89#include <tool/tool_manager.h>
90#include <tools/drc_tool.h>
91#include <filename_resolver.h>
96#include <kiface_base.h>
97#include <macros.h>
98#include <pad.h>
99#include <pcb_marker.h>
100#include <project/project_file.h>
101#include <exporters/export_idf.h>
104#include <kiface_ids.h>
107#include <pcbnew_settings.h>
108#include <pcbplot.h>
109#include <pcb_plotter.h>
110#include <pcb_edit_frame.h>
111#include <pcb_track.h>
112#include <pgm_base.h>
115#include <project_pcb.h>
118#include <reporter.h>
119#include <scoped_set_reset.h>
120#include <progress_reporter.h>
122#include <export_vrml.h>
123#include <kiplatform/io.h>
134#include <dialogs/dialog_plot.h>
142#include <api/api_pcb_utils.h>
143#include <api/board/board.pb.h>
144#include <google/protobuf/util/json_util.h>
145#include <fstream>
146#include <paths.h>
147#include <streamwrapper.h>
149
150#include <locale_io.h>
151#include <confirm.h>
152
153
154#ifdef _WIN32
155#ifdef TRANSPARENT
156#undef TRANSPARENT
157#endif
158#endif
159
160
162 JOB_DISPATCHER( aKiway ),
163 m_cliBoard( nullptr ),
164 m_toolManager( nullptr )
165{
166 Register( "bom", std::bind( &PCBNEW_JOBS_HANDLER::JobExportBom, this, std::placeholders::_1 ),
167 [aKiway]( JOB* job, wxWindow* aParent ) -> bool
168 {
169 JOB_EXPORT_BOM* bomJob = dynamic_cast<JOB_EXPORT_BOM*>( job );
170
171 PCB_EDIT_FRAME* editFrame = static_cast<PCB_EDIT_FRAME*>( aKiway->Player( FRAME_PCB_EDITOR, false ) );
172
173 wxCHECK( bomJob && editFrame, false );
174
175 DIALOG_FOOTPRINT_FIELDS_TABLE dlg( editFrame, bomJob );
176
177 if( dlg.WasAborted() )
178 return false;
179
180 return dlg.ShowModal() == wxID_OK;
181 } );
182 Register( "3d", std::bind( &PCBNEW_JOBS_HANDLER::JobExportStep, this, std::placeholders::_1 ),
183 [aKiway]( JOB* job, wxWindow* aParent ) -> bool
184 {
185 JOB_EXPORT_PCB_3D* svgJob = dynamic_cast<JOB_EXPORT_PCB_3D*>( job );
186
187 PCB_EDIT_FRAME* editFrame =
188 dynamic_cast<PCB_EDIT_FRAME*>( aKiway->Player( FRAME_PCB_EDITOR, false ) );
189
190 wxCHECK( svgJob && editFrame, false );
191
192 DIALOG_EXPORT_STEP dlg( editFrame, aParent, "", svgJob );
193 return dlg.ShowModal() == wxID_OK;
194 } );
195 Register( "render", std::bind( &PCBNEW_JOBS_HANDLER::JobExportRender, this, std::placeholders::_1 ),
196 []( JOB* job, wxWindow* aParent ) -> bool
197 {
198 JOB_PCB_RENDER* renderJob = dynamic_cast<JOB_PCB_RENDER*>( job );
199
200 wxCHECK( renderJob, false );
201
202 DIALOG_RENDER_JOB dlg( aParent, renderJob );
203 return dlg.ShowModal() == wxID_OK;
204 } );
205 Register( "upgrade", std::bind( &PCBNEW_JOBS_HANDLER::JobUpgrade, this, std::placeholders::_1 ),
206 []( JOB* job, wxWindow* aParent ) -> bool
207 {
208 return true;
209 } );
210 Register( "pcb_import", std::bind( &PCBNEW_JOBS_HANDLER::JobImport, this, std::placeholders::_1 ),
211 []( JOB* job, wxWindow* aParent ) -> bool
212 {
213 return true;
214 } );
215 Register( "svg", std::bind( &PCBNEW_JOBS_HANDLER::JobExportSvg, this, std::placeholders::_1 ),
216 [aKiway]( JOB* job, wxWindow* aParent ) -> bool
217 {
218 JOB_EXPORT_PCB_SVG* svgJob = dynamic_cast<JOB_EXPORT_PCB_SVG*>( job );
219
220 PCB_EDIT_FRAME* editFrame =
221 dynamic_cast<PCB_EDIT_FRAME*>( aKiway->Player( FRAME_PCB_EDITOR, false ) );
222
223 wxCHECK( svgJob && editFrame, false );
224
225 DIALOG_PLOT dlg( editFrame, aParent, svgJob );
226 return dlg.ShowModal() == wxID_OK;
227 } );
228 Register( "gencad", std::bind( &PCBNEW_JOBS_HANDLER::JobExportGencad, this, std::placeholders::_1 ),
229 [aKiway]( JOB* job, wxWindow* aParent ) -> bool
230 {
231 JOB_EXPORT_PCB_GENCAD* gencadJob = dynamic_cast<JOB_EXPORT_PCB_GENCAD*>( job );
232
233 PCB_EDIT_FRAME* editFrame =
234 dynamic_cast<PCB_EDIT_FRAME*>( aKiway->Player( FRAME_PCB_EDITOR, false ) );
235
236 wxCHECK( gencadJob && editFrame, false );
237
238 DIALOG_GENCAD_EXPORT_OPTIONS dlg( editFrame, gencadJob->GetSettingsDialogTitle(), gencadJob );
239 return dlg.ShowModal() == wxID_OK;
240 } );
241 Register( "dxf", std::bind( &PCBNEW_JOBS_HANDLER::JobExportDxf, this, std::placeholders::_1 ),
242 [aKiway]( JOB* job, wxWindow* aParent ) -> bool
243 {
244 JOB_EXPORT_PCB_DXF* dxfJob = dynamic_cast<JOB_EXPORT_PCB_DXF*>( job );
245
246 PCB_EDIT_FRAME* editFrame =
247 dynamic_cast<PCB_EDIT_FRAME*>( aKiway->Player( FRAME_PCB_EDITOR, false ) );
248
249 wxCHECK( dxfJob && editFrame, false );
250
251 DIALOG_PLOT dlg( editFrame, aParent, dxfJob );
252 return dlg.ShowModal() == wxID_OK;
253 } );
254 Register( "pdf", std::bind( &PCBNEW_JOBS_HANDLER::JobExportPdf, this, std::placeholders::_1 ),
255 [aKiway]( JOB* job, wxWindow* aParent ) -> bool
256 {
257 JOB_EXPORT_PCB_PDF* pdfJob = dynamic_cast<JOB_EXPORT_PCB_PDF*>( job );
258
259 PCB_EDIT_FRAME* editFrame =
260 dynamic_cast<PCB_EDIT_FRAME*>( aKiway->Player( FRAME_PCB_EDITOR, false ) );
261
262 wxCHECK( pdfJob && editFrame, false );
263
264 DIALOG_PLOT dlg( editFrame, aParent, pdfJob );
265 return dlg.ShowModal() == wxID_OK;
266 } );
267 Register( "png", std::bind( &PCBNEW_JOBS_HANDLER::JobExportPng, this, std::placeholders::_1 ),
268 [aKiway]( JOB* job, wxWindow* aParent ) -> bool
269 {
270 JOB_EXPORT_PCB_PNG* pngJob = dynamic_cast<JOB_EXPORT_PCB_PNG*>( job );
271
272 PCB_EDIT_FRAME* editFrame =
273 dynamic_cast<PCB_EDIT_FRAME*>( aKiway->Player( FRAME_PCB_EDITOR, false ) );
274
275 wxCHECK( pngJob && editFrame, false );
276
277 DIALOG_PLOT dlg( editFrame, aParent, pngJob );
278 return dlg.ShowModal() == wxID_OK;
279 } );
280 Register( "ps", std::bind( &PCBNEW_JOBS_HANDLER::JobExportPs, this, std::placeholders::_1 ),
281 [aKiway]( JOB* job, wxWindow* aParent ) -> bool
282 {
283 JOB_EXPORT_PCB_PS* psJob = dynamic_cast<JOB_EXPORT_PCB_PS*>( job );
284
285 PCB_EDIT_FRAME* editFrame =
286 dynamic_cast<PCB_EDIT_FRAME*>( aKiway->Player( FRAME_PCB_EDITOR, false ) );
287
288 wxCHECK( psJob && editFrame, false );
289
290 DIALOG_PLOT dlg( editFrame, aParent, psJob );
291 return dlg.ShowModal() == wxID_OK;
292 } );
293 Register( "stats", std::bind( &PCBNEW_JOBS_HANDLER::JobExportStats, this, std::placeholders::_1 ),
294 [aKiway]( JOB* job, wxWindow* aParent ) -> bool
295 {
296 JOB_EXPORT_PCB_STATS* statsJob = dynamic_cast<JOB_EXPORT_PCB_STATS*>( job );
297
298 PCB_EDIT_FRAME* editFrame =
299 dynamic_cast<PCB_EDIT_FRAME*>( aKiway->Player( FRAME_PCB_EDITOR, false ) );
300
301 wxCHECK( statsJob && editFrame, false );
302
303 if( statsJob->m_filename.IsEmpty() && editFrame->GetBoard() )
304 {
305 wxFileName boardName = editFrame->GetBoard()->GetFileName();
306 statsJob->m_filename = boardName.GetFullPath();
307 }
308
309 wxWindow* parent = aParent ? aParent : static_cast<wxWindow*>( editFrame );
310
311 DIALOG_BOARD_STATS_JOB dlg( parent, statsJob );
312
313 return dlg.ShowModal() == wxID_OK;
314 } );
315 Register( "stackup", std::bind( &PCBNEW_JOBS_HANDLER::JobExportStackup, this, std::placeholders::_1 ),
316 [aKiway]( JOB* job, wxWindow* aParent ) -> bool
317 {
318 JOB_EXPORT_PCB_STACKUP* stackupJob = dynamic_cast<JOB_EXPORT_PCB_STACKUP*>( job );
319
320 PCB_EDIT_FRAME* editFrame =
321 dynamic_cast<PCB_EDIT_FRAME*>( aKiway->Player( FRAME_PCB_EDITOR, false ) );
322
323 wxCHECK( stackupJob && editFrame, false );
324
325 if( stackupJob->m_filename.IsEmpty() && editFrame->GetBoard() )
326 {
327 wxFileName boardName = editFrame->GetBoard()->GetFileName();
328 stackupJob->m_filename = boardName.GetFullPath();
329 }
330
331 wxWindow* parent = aParent ? aParent : static_cast<wxWindow*>( editFrame );
332
333 DIALOG_BOARD_STACKUP_JOB dlg( parent, stackupJob );
334
335 return dlg.ShowModal() == wxID_OK;
336 } );
337 Register( "gerber", std::bind( &PCBNEW_JOBS_HANDLER::JobExportGerber, this, std::placeholders::_1 ),
338 [aKiway]( JOB* job, wxWindow* aParent ) -> bool
339 {
340 JOB_EXPORT_PCB_GERBER* gJob = dynamic_cast<JOB_EXPORT_PCB_GERBER*>( job );
341
342 PCB_EDIT_FRAME* editFrame =
343 dynamic_cast<PCB_EDIT_FRAME*>( aKiway->Player( FRAME_PCB_EDITOR, false ) );
344
345 wxCHECK( gJob && editFrame, false );
346
347 DIALOG_PLOT dlg( editFrame, aParent, gJob );
348 return dlg.ShowModal() == wxID_OK;
349 } );
350 Register( "gerbers", std::bind( &PCBNEW_JOBS_HANDLER::JobExportGerbers, this, std::placeholders::_1 ),
351 [aKiway]( JOB* job, wxWindow* aParent ) -> bool
352 {
353 JOB_EXPORT_PCB_GERBERS* gJob = dynamic_cast<JOB_EXPORT_PCB_GERBERS*>( job );
354
355 PCB_EDIT_FRAME* editFrame =
356 dynamic_cast<PCB_EDIT_FRAME*>( aKiway->Player( FRAME_PCB_EDITOR, false ) );
357
358 wxCHECK( gJob && editFrame, false );
359
360 DIALOG_PLOT dlg( editFrame, aParent, gJob );
361 return dlg.ShowModal() == wxID_OK;
362 } );
363 Register(
364 "hpgl",
365 [&]( JOB* aJob )
366 {
367 m_reporter->Report( _( "Plotting to HPGL is no longer supported as of KiCad 10.0.\n" ),
370 },
371 [aKiway]( JOB* job, wxWindow* aParent ) -> bool
372 {
373 PCB_EDIT_FRAME* editFrame = dynamic_cast<PCB_EDIT_FRAME*>( aKiway->Player( FRAME_PCB_EDITOR, false ) );
374
375 wxCHECK( editFrame, false );
376
377 DisplayErrorMessage( editFrame, _( "Plotting to HPGL is no longer supported as of KiCad 10.0." ) );
378 return false;
379 } );
380 Register( "drill", std::bind( &PCBNEW_JOBS_HANDLER::JobExportDrill, this, std::placeholders::_1 ),
381 [aKiway]( JOB* job, wxWindow* aParent ) -> bool
382 {
383 JOB_EXPORT_PCB_DRILL* drillJob = dynamic_cast<JOB_EXPORT_PCB_DRILL*>( job );
384
385 PCB_EDIT_FRAME* editFrame =
386 dynamic_cast<PCB_EDIT_FRAME*>( aKiway->Player( FRAME_PCB_EDITOR, false ) );
387
388 wxCHECK( drillJob && editFrame, false );
389
390 DIALOG_GENDRILL dlg( editFrame, drillJob, aParent );
391 return dlg.ShowModal() == wxID_OK;
392 } );
393 Register( "pos", std::bind( &PCBNEW_JOBS_HANDLER::JobExportPos, this, std::placeholders::_1 ),
394 [aKiway]( JOB* job, wxWindow* aParent ) -> bool
395 {
396 JOB_EXPORT_PCB_POS* posJob = dynamic_cast<JOB_EXPORT_PCB_POS*>( job );
397
398 PCB_EDIT_FRAME* editFrame =
399 dynamic_cast<PCB_EDIT_FRAME*>( aKiway->Player( FRAME_PCB_EDITOR, false ) );
400
401 wxCHECK( posJob && editFrame, false );
402
403 DIALOG_GEN_FOOTPRINT_POSITION dlg( posJob, editFrame, aParent );
404 return dlg.ShowModal() == wxID_OK;
405 } );
406 Register( "fpupgrade", std::bind( &PCBNEW_JOBS_HANDLER::JobExportFpUpgrade, this, std::placeholders::_1 ),
407 []( JOB* job, wxWindow* aParent ) -> bool
408 {
409 return true;
410 } );
411 Register( "fpsvg", std::bind( &PCBNEW_JOBS_HANDLER::JobExportFpSvg, this, std::placeholders::_1 ),
412 []( JOB* job, wxWindow* aParent ) -> bool
413 {
414 return true;
415 } );
416 Register( "pcb_diff", std::bind( &PCBNEW_JOBS_HANDLER::JobDiff, this, std::placeholders::_1 ),
417 []( JOB* job, wxWindow* aParent ) -> bool
418 {
419 return true;
420 } );
421 Register( "fp_diff", std::bind( &PCBNEW_JOBS_HANDLER::JobFpDiff, this, std::placeholders::_1 ),
422 []( JOB* job, wxWindow* aParent ) -> bool
423 {
424 return true;
425 } );
426 Register( "drc", std::bind( &PCBNEW_JOBS_HANDLER::JobExportDrc, this, std::placeholders::_1 ),
427 []( JOB* job, wxWindow* aParent ) -> bool
428 {
429 JOB_PCB_DRC* drcJob = dynamic_cast<JOB_PCB_DRC*>( job );
430
431 wxCHECK( drcJob, false );
432
433 DIALOG_DRC_JOB_CONFIG dlg( aParent, drcJob );
434 return dlg.ShowModal() == wxID_OK;
435 } );
436 Register( "ipc2581", std::bind( &PCBNEW_JOBS_HANDLER::JobExportIpc2581, this, std::placeholders::_1 ),
437 [aKiway]( JOB* job, wxWindow* aParent ) -> bool
438 {
439 JOB_EXPORT_PCB_IPC2581* ipcJob = dynamic_cast<JOB_EXPORT_PCB_IPC2581*>( job );
440
441 PCB_EDIT_FRAME* editFrame =
442 dynamic_cast<PCB_EDIT_FRAME*>( aKiway->Player( FRAME_PCB_EDITOR, false ) );
443
444 wxCHECK( ipcJob && editFrame, false );
445
446 DIALOG_EXPORT_2581 dlg( ipcJob, editFrame, aParent );
447 return dlg.ShowModal() == wxID_OK;
448 } );
449 Register( "ipcd356", std::bind( &PCBNEW_JOBS_HANDLER::JobExportIpcD356, this, std::placeholders::_1 ),
450 []( JOB* job, wxWindow* aParent ) -> bool
451 {
452 return true;
453 } );
454 Register( "odb", std::bind( &PCBNEW_JOBS_HANDLER::JobExportOdb, this, std::placeholders::_1 ),
455 [aKiway]( JOB* job, wxWindow* aParent ) -> bool
456 {
457 JOB_EXPORT_PCB_ODB* odbJob = dynamic_cast<JOB_EXPORT_PCB_ODB*>( job );
458
459 PCB_EDIT_FRAME* editFrame =
460 dynamic_cast<PCB_EDIT_FRAME*>( aKiway->Player( FRAME_PCB_EDITOR, false ) );
461
462 wxCHECK( odbJob && editFrame, false );
463
464 DIALOG_EXPORT_ODBPP dlg( odbJob, editFrame, aParent );
465 return dlg.ShowModal() == wxID_OK;
466 } );
467 Register( "idf", std::bind( &PCBNEW_JOBS_HANDLER::JobExportIdf, this, std::placeholders::_1 ),
468 [aKiway]( JOB* job, wxWindow* aParent ) -> bool
469 {
470 JOB_EXPORT_PCB_IDF* idfJob = dynamic_cast<JOB_EXPORT_PCB_IDF*>( job );
471
472 PCB_EDIT_FRAME* editFrame =
473 dynamic_cast<PCB_EDIT_FRAME*>( aKiway->Player( FRAME_PCB_EDITOR, false ) );
474
475 wxCHECK( idfJob && editFrame, false );
476
477 DIALOG_EXPORT_IDF3 dlg( idfJob, editFrame, aParent );
478 return dlg.ShowModal() == wxID_OK;
479 } );
480}
481
482
486
487
489{
490 m_cliBoard.reset();
491 m_toolManager.reset();
492}
493
494
496{
497 TOOL_MANAGER* toolManager = nullptr;
498 if( Pgm().IsGUI() )
499 {
500 // we assume the PCB we are working on here is the one in the frame
501 // so use the frame's tool manager
502 PCB_EDIT_FRAME* editFrame = (PCB_EDIT_FRAME*) m_kiway->Player( FRAME_PCB_EDITOR, false );
503 if( editFrame )
504 toolManager = editFrame->GetToolManager();
505 }
506 else
507 {
508 if( m_toolManager == nullptr )
509 {
510 m_toolManager = std::make_unique<TOOL_MANAGER>();
511 }
512
513 toolManager = m_toolManager.get();
514
515 toolManager->SetEnvironment( aBrd, nullptr, nullptr, Kiface().KifaceSettings(), nullptr );
516 }
517 return toolManager;
518}
519
520
521BOARD* PCBNEW_JOBS_HANDLER::getBoard( const wxString& aPath )
522{
523 BOARD* brd = nullptr;
524 SETTINGS_MANAGER& settingsManager = Pgm().GetSettingsManager();
525 wxString loadError;
526
527 auto getProjectForBoard = [&]( const wxString& aBoardPath ) -> PROJECT*
528 {
529 wxFileName pro = aBoardPath;
530 pro.SetExt( FILEEXT::ProjectFileExtension );
531 pro.MakeAbsolute();
532
533 PROJECT* project = settingsManager.GetProject( pro.GetFullPath() );
534
535 if( !project )
536 {
537 settingsManager.LoadProject( pro.GetFullPath(), true );
538 project = settingsManager.GetProject( pro.GetFullPath() );
539 }
540
541 return project;
542 };
543
544 auto loadBoardFromPath = [&]( const wxString& aBoardPath ) -> BOARD*
545 {
546 PROJECT* project = getProjectForBoard( aBoardPath );
547
549
550 if( !project || pluginType == PCB_IO_MGR::FILE_TYPE_NONE )
551 return nullptr;
552
553 try
554 {
555 std::unique_ptr<BOARD> loadedBoard = BOARD_LOADER::Load( aBoardPath, pluginType, project );
556 return loadedBoard.release();
557 }
558 catch( const IO_ERROR& ioe )
559 {
560 loadError = ioe.What();
561 return nullptr;
562 }
563 catch( ... )
564 {
565 return nullptr;
566 }
567 };
568
569 if( !Pgm().IsGUI() && Pgm().GetSettingsManager().IsProjectOpen() )
570 {
571 wxString pcbPath = aPath;
572
573 if( pcbPath.IsEmpty() )
574 {
575 wxFileName path = Pgm().GetSettingsManager().Prj().GetProjectFullName();
577 path.MakeAbsolute();
578 pcbPath = path.GetFullPath();
579 }
580
581 if( !m_cliBoard )
582 m_cliBoard.reset( loadBoardFromPath( pcbPath ) );
583
584 brd = m_cliBoard.get();
585 }
586 else if( Pgm().IsGUI() && Pgm().GetSettingsManager().IsProjectOpen() )
587 {
588 PCB_EDIT_FRAME* editFrame = (PCB_EDIT_FRAME*) m_kiway->Player( FRAME_PCB_EDITOR, false );
589
590 if( editFrame )
591 brd = editFrame->GetBoard();
592 }
593 else
594 {
595 m_cliBoard.reset( loadBoardFromPath( aPath ) );
596 brd = m_cliBoard.get();
597 }
598
599 if( !brd )
600 {
601 wxString msg = _( "Failed to load board" );
602
603 if( !loadError.IsEmpty() )
604 msg += wxString::Format( wxS( ": %s" ), loadError );
605
606 m_reporter->Report( msg + '\n', RPT_SEVERITY_ERROR );
607 }
608
609 return brd;
610}
611
612
613LSEQ PCBNEW_JOBS_HANDLER::convertLayerArg( wxString& aLayerString, BOARD* aBoard ) const
614{
615 std::map<wxString, LSET> layerUserMasks;
616 std::map<wxString, LSET> layerMasks;
617 std::map<wxString, LSET> layerGuiMasks;
618
619 // Build list of layer names and their layer mask:
620 for( PCB_LAYER_ID layer : LSET::AllLayersMask() )
621 {
622 // Add user layer name
623 if( aBoard )
624 layerUserMasks[aBoard->GetLayerName( layer )] = LSET( { layer } );
625
626 // Add layer name used in pcb files
627 layerMasks[LSET::Name( layer )] = LSET( { layer } );
628 // Add layer name using GUI canonical layer name
629 layerGuiMasks[LayerName( layer )] = LSET( { layer } );
630 }
631
632 // Add list of grouped layer names used in pcb files
633 layerMasks[wxT( "*" )] = LSET::AllLayersMask();
634 layerMasks[wxT( "*.Cu" )] = LSET::AllCuMask();
635 layerMasks[wxT( "*In.Cu" )] = LSET::InternalCuMask();
636 layerMasks[wxT( "F&B.Cu" )] = LSET( { F_Cu, B_Cu } );
637 layerMasks[wxT( "*.Adhes" )] = LSET( { B_Adhes, F_Adhes } );
638 layerMasks[wxT( "*.Paste" )] = LSET( { B_Paste, F_Paste } );
639 layerMasks[wxT( "*.Mask" )] = LSET( { B_Mask, F_Mask } );
640 layerMasks[wxT( "*.SilkS" )] = LSET( { B_SilkS, F_SilkS } );
641 layerMasks[wxT( "*.Fab" )] = LSET( { B_Fab, F_Fab } );
642 layerMasks[wxT( "*.CrtYd" )] = LSET( { B_CrtYd, F_CrtYd } );
643
644 // Add list of grouped layer names using GUI canonical layer names
645 layerGuiMasks[wxT( "*.Adhesive" )] = LSET( { B_Adhes, F_Adhes } );
646 layerGuiMasks[wxT( "*.Silkscreen" )] = LSET( { B_SilkS, F_SilkS } );
647 layerGuiMasks[wxT( "*.Courtyard" )] = LSET( { B_CrtYd, F_CrtYd } );
648
649 LSEQ layerMask;
650
651 auto pushLayers = [&]( const LSET& layerSet )
652 {
653 for( PCB_LAYER_ID layer : layerSet.Seq() )
654 layerMask.push_back( layer );
655 };
656
657 if( !aLayerString.IsEmpty() )
658 {
659 wxStringTokenizer layerTokens( aLayerString, "," );
660
661 while( layerTokens.HasMoreTokens() )
662 {
663 std::string token = TO_UTF8( layerTokens.GetNextToken().Trim( true ).Trim( false ) );
664
665 if( layerUserMasks.contains( token ) )
666 pushLayers( layerUserMasks.at( token ) );
667 else if( layerMasks.count( token ) )
668 pushLayers( layerMasks.at( token ) );
669 else if( layerGuiMasks.count( token ) )
670 pushLayers( layerGuiMasks.at( token ) );
671 else
672 m_reporter->Report( wxString::Format( _( "Invalid layer name '%s'\n" ), token ) );
673 }
674 }
675
676 return layerMask;
677}
678
679
681{
682 JOB_EXPORT_BOM* aBomJob = dynamic_cast<JOB_EXPORT_BOM*>( aJob );
683
684 wxCHECK( aBomJob, CLI::EXIT_CODES::ERR_UNKNOWN );
685
686 BOARD* board = getBoard( aBomJob->m_filename );
687
688 if( !board )
690
691 aJob->SetTitleBlock( board->GetTitleBlock() );
692 board->GetProject()->ApplyTextVars( aJob->GetVarOverrides() );
693
694 wxString currentVariant = aBomJob->GetSelectedVariant();
695
696 if( !currentVariant.IsEmpty() && currentVariant != wxS( "all" ) )
697 board->SetCurrentVariant( currentVariant );
698
699 FOOTPRINT_REFERENCE_LIST referenceList;
700
701 // Annotation warning check (and gather footprints for data model)
702 bool hasWarned = false;
703 for( FOOTPRINT* fp : board->Footprints() )
704 {
705 referenceList.push_back( FOOTPRINT_REF( *fp ) );
706
707 if( !fp->IsAnnotated() && !hasWarned )
708 {
709 m_reporter->Report( _( "Warning: board has unannotated footprints, please use the PCB "
710 "editor to annotate them\n" ),
712 hasWarned = true;
713 }
714 }
715
716 // Build our data model
717 FOOTPRINT_FIELDS_EDITOR_GRID_DATA_MODEL dataModel( referenceList );
718 dataModel.SetCurrentVariant( currentVariant );
719
720 // Mandatory fields first
721 for( FIELD_T fieldId : MANDATORY_FIELDS )
722 dataModel.AddColumn( GetDefaultFieldName( fieldId, UNTRANSLATED ),
723 GetDefaultFieldName( fieldId, TRANSLATED ), false );
724
725 // Generated/virtual fields (e.g. ${QUANTITY}, ${ITEM_NUMBER}) present only in the fields table
728 false );
731 false );
732
733 // Attribute fields (boolean flags on footprints)
734 dataModel.AddColumn( wxS( "${DNP}" ), GetGeneratedFieldDisplayName( wxS( "${DNP}" ) ), false );
735 dataModel.AddColumn( wxS( "${EXCLUDE_FROM_BOM}" ), GetGeneratedFieldDisplayName( wxS( "${EXCLUDE_FROM_BOM}" ) ),
736 false );
737 dataModel.AddColumn( wxS( "${EXCLUDE_FROM_BOARD}" ), GetGeneratedFieldDisplayName( wxS( "${EXCLUDE_FROM_BOARD}" ) ),
738 false );
739 dataModel.AddColumn( wxS( "${EXCLUDE_FROM_SIM}" ), GetGeneratedFieldDisplayName( wxS( "${EXCLUDE_FROM_SIM}" ) ),
740 false );
741
742 // User field names in footprints second
743 std::set<wxString> userFieldNames;
744
745 for( size_t i = 0; i < referenceList.size(); ++i )
746 {
747 FOOTPRINT& footprint = referenceList[i].GetFootprint();
748
749 for( PCB_FIELD* field : footprint.GetFields() )
750 {
751 if( !field->IsMandatory() && !field->IsPrivate() )
752 userFieldNames.insert( field->GetName() );
753 }
754 }
755
756 for( const wxString& fieldName : userFieldNames )
757 dataModel.AddColumn( fieldName, GetGeneratedFieldDisplayName( fieldName ), true );
758
759 // Add any templateFieldNames which aren't already present in the userFieldNames
760 // TODO: template field names not implemented in board editor
761
762 BOM_PRESET preset;
763
764 // Load a preset if one is specified
765 if( !aBomJob->m_bomPresetName.IsEmpty() )
766 {
767 // Find the preset
768 std::optional<BOM_PRESET> boardPreset;
769
770 for( const BOM_PRESET& p : BOM_PRESET::BuiltInPresets() )
771 {
772 if( p.name == aBomJob->m_bomPresetName )
773 {
774 boardPreset = p;
775 break;
776 }
777 }
778
779 for( const BOM_PRESET& p : board->GetDesignSettings().m_BomPresets )
780 {
781 if( p.name == aBomJob->m_bomPresetName )
782 {
783 boardPreset = p;
784 break;
785 }
786 }
787
788 if( !boardPreset )
789 {
790 m_reporter->Report(
791 wxString::Format( _( "BOM preset '%s' not found" ) + wxS( "\n" ), aBomJob->m_bomPresetName ),
793
795 }
796
797 preset = *boardPreset;
798 }
799 else
800 {
801 // Normalize field names so that bare generated-field tokens (e.g. "QUANTITY") are
802 // accepted alongside the canonical "${QUANTITY}" form. Shell expansion of ${VAR}
803 // inside double quotes silently produces an empty string, so this also guards against
804 // that common CLI pitfall.
805 auto normalizeFieldName = [&dataModel]( const wxString& aName ) -> wxString
806 {
807 if( aName.IsEmpty() )
808 return wxEmptyString;
809
810 if( IsGeneratedField( aName ) )
811 return aName;
812
813 wxString wrapped = wxS( "${" ) + aName + wxS( "}" );
814
815 if( IsGeneratedField( wrapped ) && dataModel.GetFieldNameCol( wrapped ) != -1 )
816 return wrapped;
817
818 return aName;
819 };
820
821 size_t i = 0;
822
823 for( const wxString& rawFieldName : aBomJob->m_fieldsOrdered )
824 {
825 wxString fieldName = normalizeFieldName( rawFieldName );
826
827 if( fieldName.IsEmpty() )
828 {
829 i++;
830 continue;
831 }
832
833 // Handle wildcard. We allow the wildcard anywhere in the list, but it needs to respect
834 // fields that come before and after the wildcard.
835 if( fieldName == wxS( "*" ) )
836 {
837 for( const BOM_FIELD& modelField : dataModel.GetFieldsOrdered() )
838 {
839 struct BOM_FIELD field;
840
841 field.name = modelField.name;
842 field.show = true;
843 field.groupBy = false;
844 field.label = field.name;
845
846 bool fieldAlreadyPresent = false;
847
848 for( BOM_FIELD& presetField : preset.fieldsOrdered )
849 {
850 if( presetField.name == field.name )
851 {
852 fieldAlreadyPresent = true;
853 break;
854 }
855 }
856
857 bool fieldLaterInList = false;
858
859 for( const wxString& fieldInList : aBomJob->m_fieldsOrdered )
860 {
861 if( normalizeFieldName( fieldInList ) == field.name )
862 {
863 fieldLaterInList = true;
864 break;
865 }
866 }
867
868 if( !fieldAlreadyPresent && !fieldLaterInList )
869 preset.fieldsOrdered.emplace_back( field );
870 }
871
872 continue;
873 }
874
875 struct BOM_FIELD field;
876
877 field.name = fieldName;
878 field.show = !fieldName.StartsWith( wxT( "__" ), &field.name );
879 field.name = normalizeFieldName( field.name );
880
881 if( ( aBomJob->m_fieldsLabels.size() > i ) && !aBomJob->m_fieldsLabels[i].IsEmpty() )
882 field.label = aBomJob->m_fieldsLabels[i];
883 else if( IsGeneratedField( field.name ) )
884 field.label = GetGeneratedFieldDisplayName( field.name );
885 else
886 field.label = field.name;
887
888 preset.fieldsOrdered.emplace_back( field );
889 i++;
890 }
891
892 for( const wxString& rawFieldName : aBomJob->m_fieldsGroupBy )
893 {
894 wxString fieldName = rawFieldName;
895 fieldName.StartsWith( wxT( "__" ), &fieldName );
896 fieldName = normalizeFieldName( fieldName );
897
898 if( fieldName.IsEmpty() )
899 continue;
900
901 bool fieldAlreadyPresent = false;
902
903 for( BOM_FIELD& field : preset.fieldsOrdered )
904 {
905 if( field.name == fieldName )
906 {
907 field.groupBy = true;
908 fieldAlreadyPresent = true;
909 break;
910 }
911 }
912
913 if( !fieldAlreadyPresent )
914 preset.fieldsOrdered.push_back( { fieldName, GetGeneratedFieldDisplayName( fieldName ), false, true } );
915 }
916
917 preset.sortAsc = aBomJob->m_sortAsc;
918 preset.sortField = normalizeFieldName( aBomJob->m_sortField );
919 preset.filterString = aBomJob->m_filterString;
920 preset.filterScope = aBomJob->m_filterScope;
921 preset.groupSymbols = aBomJob->m_groupSymbols;
922 preset.excludeDNP = aBomJob->m_excludeDNP;
923 }
924
925 BOM_FMT_PRESET fmt;
926
927 // Load a format preset if one is specified
928 if( !aBomJob->m_bomFmtPresetName.IsEmpty() )
929 {
930 std::optional<BOM_FMT_PRESET> boardFmtPreset;
931
933 {
934 if( p.name == aBomJob->m_bomFmtPresetName )
935 {
936 boardFmtPreset = p;
937 break;
938 }
939 }
940
941 for( const BOM_FMT_PRESET& p : board->GetDesignSettings().m_BomFmtPresets )
942 {
943 if( p.name == aBomJob->m_bomFmtPresetName )
944 {
945 boardFmtPreset = p;
946 break;
947 }
948 }
949
950 if( !boardFmtPreset )
951 {
952 m_reporter->Report( wxString::Format( _( "BOM format preset '%s' not found" ) + wxS( "\n" ),
953 aBomJob->m_bomFmtPresetName ),
955
957 }
958
959 fmt = *boardFmtPreset;
960 }
961 else
962 {
963 fmt.fieldDelimiter = aBomJob->m_fieldDelimiter;
964 fmt.stringDelimiter = aBomJob->m_stringDelimiter;
965 fmt.refDelimiter = aBomJob->m_refDelimiter;
967 fmt.keepTabs = aBomJob->m_keepTabs;
968 fmt.keepLineBreaks = aBomJob->m_keepLineBreaks;
970 }
971
972 if( aBomJob->GetConfiguredOutputPath().IsEmpty() )
973 {
974 wxFileName fn = board->GetFileName();
975 fn.SetName( fn.GetName() );
976 fn.SetExt( FILEEXT::CsvFileExtension );
977
978 aBomJob->SetConfiguredOutputPath( fn.GetFullName() );
979 }
980
981 wxString configuredPath = aBomJob->GetConfiguredOutputPath();
982 bool hasVariantPlaceholder = configuredPath.Contains( wxS( "${VARIANT}" ) );
983
984 // Determine which variants to process
985 std::vector<wxString> variantsToProcess;
986
987 if( aBomJob->m_variantNames.size() > 1 && hasVariantPlaceholder )
988 {
989 variantsToProcess = aBomJob->m_variantNames;
990 }
991 else
992 {
993 variantsToProcess.push_back( currentVariant );
994 }
995
996 for( const wxString& variantName : variantsToProcess )
997 {
998 std::vector<wxString> singleVariant = { variantName };
999 dataModel.SetVariantNames( singleVariant );
1000 dataModel.SetCurrentVariant( variantName );
1001 dataModel.UpdateReferences( dataModel.GetReferenceList() );
1002 dataModel.ApplyBomPreset( preset );
1003
1004 wxString outPath;
1005
1006 if( hasVariantPlaceholder )
1007 {
1008 wxString variantPath = ExpandVariantOutputPath( configuredPath, variantName );
1009 aBomJob->SetConfiguredOutputPath( variantPath );
1010 outPath = aBomJob->GetFullOutputPath( board->GetProject() );
1011 aBomJob->SetConfiguredOutputPath( configuredPath );
1012 }
1013 else
1014 {
1015 outPath = aBomJob->GetFullOutputPath( board->GetProject() );
1016 }
1017
1018 if( !PATHS::EnsurePathExists( outPath, true ) )
1019 {
1020 m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
1022 }
1023
1024 wxFile f;
1025
1026 if( !f.Open( outPath, wxFile::write ) )
1027 {
1028 m_reporter->Report( wxString::Format( _( "Unable to open destination '%s'" ), outPath ),
1030
1032 }
1033
1034 bool res = f.Write( dataModel.Export( fmt ) );
1035
1036 if( !res )
1038
1039 aJob->AddOutput( outPath );
1040
1041 m_reporter->Report( wxString::Format( _( "Wrote bill of materials to '%s'." ), outPath ), RPT_SEVERITY_ACTION );
1042 }
1043
1044 return CLI::EXIT_CODES::OK;
1045}
1046
1047
1049{
1050 JOB_EXPORT_PCB_3D* aStepJob = dynamic_cast<JOB_EXPORT_PCB_3D*>( aJob );
1051
1052 if( aStepJob == nullptr )
1054
1055 BOARD* brd = getBoard( aStepJob->m_filename );
1056
1057 if( !brd )
1059
1060 if( !aStepJob->m_variant.IsEmpty() )
1061 brd->SetCurrentVariant( aStepJob->m_variant );
1062
1063 if( aStepJob->GetConfiguredOutputPath().IsEmpty() )
1064 {
1065 wxFileName fn = brd->GetFileName();
1066 fn.SetName( fn.GetName() );
1067
1068 switch( aStepJob->m_format )
1069 {
1079 default:
1080 m_reporter->Report( _( "Unknown export format" ), RPT_SEVERITY_ERROR );
1081 return CLI::EXIT_CODES::ERR_UNKNOWN; // shouldnt have gotten here
1082 }
1083
1084 aStepJob->SetWorkingOutputPath( fn.GetFullName() );
1085 }
1086
1087 wxString outPath = resolveJobOutputPath( aJob, brd );
1088
1089 if( !PATHS::EnsurePathExists( outPath, true ) )
1090 {
1091 m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
1093 }
1094
1095 if( aStepJob->m_format == JOB_EXPORT_PCB_3D::FORMAT::VRML )
1096 {
1097 double scale = 0.0;
1098 switch( aStepJob->m_vrmlUnits )
1099 {
1100 case JOB_EXPORT_PCB_3D::VRML_UNITS::MM: scale = 1.0; break;
1101 case JOB_EXPORT_PCB_3D::VRML_UNITS::METERS: scale = 0.001; break;
1102 case JOB_EXPORT_PCB_3D::VRML_UNITS::TENTHS: scale = 10.0 / 25.4; break;
1103 case JOB_EXPORT_PCB_3D::VRML_UNITS::INCH: scale = 1.0 / 25.4; break;
1104 }
1105
1106 EXPORTER_VRML vrmlExporter( brd );
1107 wxString messages;
1108
1109 double originX = pcbIUScale.IUTomm( aStepJob->m_3dparams.m_Origin.x );
1110 double originY = pcbIUScale.IUTomm( aStepJob->m_3dparams.m_Origin.y );
1111
1112 if( !aStepJob->m_hasUserOrigin )
1113 {
1114 BOX2I bbox = brd->ComputeBoundingBox( true, true );
1115 originX = pcbIUScale.IUTomm( bbox.GetCenter().x );
1116 originY = pcbIUScale.IUTomm( bbox.GetCenter().y );
1117 }
1118
1119 bool success = vrmlExporter.ExportVRML_File(
1120 brd->GetProject(), &messages, outPath, scale, aStepJob->m_3dparams.m_IncludeUnspecified,
1121 aStepJob->m_3dparams.m_IncludeDNP, !aStepJob->m_vrmlModelDir.IsEmpty(), aStepJob->m_vrmlRelativePaths,
1122 aStepJob->m_vrmlModelDir, originX, originY );
1123
1124 if( success )
1125 {
1126 m_reporter->Report( wxString::Format( _( "Successfully exported VRML to %s" ), outPath ),
1128 }
1129 else
1130 {
1131 m_reporter->Report( _( "Error exporting VRML" ), RPT_SEVERITY_ERROR );
1133 }
1134 }
1135 else
1136 {
1137 EXPORTER_STEP_PARAMS params = aStepJob->m_3dparams;
1138
1139 switch( aStepJob->m_format )
1140 {
1150 default:
1151 m_reporter->Report( _( "Unknown export format" ), RPT_SEVERITY_ERROR );
1152 return CLI::EXIT_CODES::ERR_UNKNOWN; // shouldnt have gotten here
1153 }
1154
1155 EXPORTER_STEP stepExporter( brd, params, m_reporter );
1156 stepExporter.m_outputFile = aStepJob->GetFullOutputPath( brd->GetProject() );
1157
1158 if( !stepExporter.Export() )
1160 }
1161
1162 return CLI::EXIT_CODES::OK;
1163}
1164
1165
1167{
1168 JOB_PCB_RENDER* aRenderJob = dynamic_cast<JOB_PCB_RENDER*>( aJob );
1169
1170 if( aRenderJob == nullptr )
1172
1173 // Reject width and height being invalid
1174 // Final bit of sanity because this can blow things up
1175 if( aRenderJob->m_width <= 0 || aRenderJob->m_height <= 0 )
1176 {
1177 m_reporter->Report( _( "Invalid image dimensions" ), RPT_SEVERITY_ERROR );
1179 }
1180
1181 BOARD* brd = getBoard( aRenderJob->m_filename );
1182
1183 if( !brd )
1185
1186 if( !aRenderJob->m_variant.IsEmpty() )
1187 brd->SetCurrentVariant( aRenderJob->m_variant );
1188
1189 if( aRenderJob->GetConfiguredOutputPath().IsEmpty() )
1190 {
1191 wxFileName fn = brd->GetFileName();
1192
1193 switch( aRenderJob->m_format )
1194 {
1197 default:
1198 m_reporter->Report( _( "Unknown export format" ), RPT_SEVERITY_ERROR );
1199 return CLI::EXIT_CODES::ERR_UNKNOWN; // shouldnt have gotten here
1200 }
1201
1202 // set the name to board name + "side", its lazy but its hard to generate anything truely unique
1203 // incase someone is doing this in a jobset with multiple jobs, they should be setting the output themselves
1204 // or we do a hash based on all the options
1205 fn.SetName( wxString::Format( "%s-%d", fn.GetName(), static_cast<int>( aRenderJob->m_side ) ) );
1206
1207 aRenderJob->SetWorkingOutputPath( fn.GetFullName() );
1208 }
1209
1210 wxString outPath = resolveJobOutputPath( aJob, brd );
1211
1212 if( !PATHS::EnsurePathExists( outPath, true ) )
1213 {
1214 m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
1216 }
1217
1218 BOARD_ADAPTER boardAdapter;
1219
1220 boardAdapter.SetBoard( brd );
1221 boardAdapter.m_IsBoardView = false;
1222
1224
1226 {
1227 cfg.m_Render = userCfg->m_Render;
1228 cfg.m_Camera = userCfg->m_Camera;
1229 cfg.m_LayerPresets = userCfg->m_LayerPresets;
1230 }
1231
1232 if( aRenderJob->m_appearancePreset.empty() )
1233 {
1234 // Force display 3D models
1236 cfg.m_Render.show_footprints_dnp = true;
1240 }
1241
1242 if( aRenderJob->m_quality == JOB_PCB_RENDER::QUALITY::BASIC )
1243 {
1244 // Silkscreen is pixelated without antialiasing
1246
1247 cfg.m_Render.raytrace_backfloor = aRenderJob->m_floor;
1248 cfg.m_Render.raytrace_post_processing = aRenderJob->m_floor;
1249
1251 cfg.m_Render.raytrace_reflections = false;
1252 cfg.m_Render.raytrace_shadows = aRenderJob->m_floor;
1253
1254 // Better colors
1256
1257 // Tracks below soldermask are not visible without refractions
1258 cfg.m_Render.raytrace_refractions = true;
1260 }
1261 else if( aRenderJob->m_quality == JOB_PCB_RENDER::QUALITY::HIGH )
1262 {
1264 cfg.m_Render.raytrace_backfloor = true;
1267 cfg.m_Render.raytrace_reflections = true;
1268 cfg.m_Render.raytrace_shadows = true;
1269 cfg.m_Render.raytrace_refractions = true;
1271 }
1272 else if( aRenderJob->m_quality == JOB_PCB_RENDER::QUALITY::JOB_SETTINGS )
1273 {
1275 cfg.m_Render.raytrace_backfloor = aRenderJob->m_floor;
1278 }
1279
1281 aRenderJob->m_lightTopIntensity.z, 1.0 );
1282
1284 COLOR4D( aRenderJob->m_lightBottomIntensity.x, aRenderJob->m_lightBottomIntensity.y,
1285 aRenderJob->m_lightBottomIntensity.z, 1.0 );
1286
1288 COLOR4D( aRenderJob->m_lightCameraIntensity.x, aRenderJob->m_lightCameraIntensity.y,
1289 aRenderJob->m_lightCameraIntensity.z, 1.0 );
1290
1291 COLOR4D lightColor( aRenderJob->m_lightSideIntensity.x, aRenderJob->m_lightSideIntensity.y,
1292 aRenderJob->m_lightSideIntensity.z, 1.0 );
1293
1295 lightColor, lightColor, lightColor, lightColor, lightColor, lightColor, lightColor, lightColor,
1296 };
1297
1298 int sideElevation = aRenderJob->m_lightSideElevation;
1299
1301 sideElevation, sideElevation, sideElevation, sideElevation,
1302 -sideElevation, -sideElevation, -sideElevation, -sideElevation,
1303 };
1304
1306 45, 135, 225, 315, 45, 135, 225, 315,
1307 };
1308
1309 cfg.m_CurrentPreset = aRenderJob->m_appearancePreset;
1311 boardAdapter.m_Cfg = &cfg;
1312
1313 // Apply the preset's layer visibility and colors to the render settings
1314 if( !aRenderJob->m_appearancePreset.empty() )
1315 {
1316 wxString presetName = wxString::FromUTF8( aRenderJob->m_appearancePreset );
1317
1318 if( presetName == FOLLOW_PCB || presetName == FOLLOW_PLOT_SETTINGS )
1319 {
1320 boardAdapter.SetVisibleLayers( boardAdapter.GetVisibleLayers() );
1321 }
1322 else if( LAYER_PRESET_3D* preset = cfg.FindPreset( presetName ) )
1323 {
1324 boardAdapter.SetVisibleLayers( preset->layers );
1325 boardAdapter.SetLayerColors( preset->colors );
1326
1327 if( preset->name.Lower() == _( "legacy colors" ) )
1328 cfg.m_UseStackupColors = false;
1329 }
1330 }
1331
1334 && aRenderJob->m_format == JOB_PCB_RENDER::FORMAT::PNG ) )
1335 {
1336 boardAdapter.m_ColorOverrides[LAYER_3D_BACKGROUND_TOP] = COLOR4D( 1.0, 1.0, 1.0, 0.0 );
1337 boardAdapter.m_ColorOverrides[LAYER_3D_BACKGROUND_BOTTOM] = COLOR4D( 1.0, 1.0, 1.0, 0.0 );
1338 }
1339
1341
1342 static std::map<JOB_PCB_RENDER::SIDE, VIEW3D_TYPE> s_viewCmdMap = {
1349 };
1350
1352
1353 wxSize windowSize( aRenderJob->m_width, aRenderJob->m_height );
1354 TRACK_BALL camera( 2 * RANGE_SCALE_3D );
1355
1356 camera.SetProjection( projection );
1357 camera.SetCurWindowSize( windowSize );
1358
1359 RENDER_3D_RAYTRACE_RAM raytrace( boardAdapter, camera );
1360 raytrace.SetCurWindowSize( windowSize );
1361
1362 std::shared_ptr<REPORTER> reporter( m_reporter, []( REPORTER* ) {} );
1363 raytrace.SetReporters( reporter, reporter );
1364
1365 for( bool first = true; raytrace.Redraw( false ); first = false )
1366 {
1367 if( first )
1368 {
1369 const float cmTo3D = boardAdapter.BiuTo3dUnits() * pcbIUScale.mmToIU( 10.0 );
1370
1371 // First redraw resets lookat point to the board center, so set up the camera here
1372 camera.ViewCommand_T1( s_viewCmdMap[aRenderJob->m_side] );
1373
1374 camera.SetLookAtPos_T1( camera.GetLookAtPos_T1()
1375 + SFVEC3F( aRenderJob->m_pivot.x, aRenderJob->m_pivot.y, aRenderJob->m_pivot.z )
1376 * cmTo3D );
1377
1378 camera.Pan_T1( SFVEC3F( aRenderJob->m_pan.x, aRenderJob->m_pan.y, aRenderJob->m_pan.z ) );
1379
1380 camera.Zoom_T1( aRenderJob->m_zoom );
1381
1382 camera.RotateX_T1( DEG2RAD( aRenderJob->m_rotation.x ) );
1383 camera.RotateY_T1( DEG2RAD( aRenderJob->m_rotation.y ) );
1384 camera.RotateZ_T1( DEG2RAD( aRenderJob->m_rotation.z ) );
1385
1386 camera.Interpolate( 1.0f );
1387 camera.SetT0_and_T1_current_T();
1388 camera.ParametersChanged();
1389 }
1390 }
1391
1392 uint8_t* rgbaBuffer = raytrace.GetBuffer();
1393 wxSize realSize = raytrace.GetRealBufferSize();
1394 bool success = !!rgbaBuffer;
1395
1396 if( rgbaBuffer )
1397 {
1398 const unsigned int wxh = realSize.x * realSize.y;
1399
1400 unsigned char* rgbBuffer = (unsigned char*) malloc( wxh * 3 );
1401 unsigned char* alphaBuffer = (unsigned char*) malloc( wxh );
1402
1403 unsigned char* rgbaPtr = rgbaBuffer;
1404 unsigned char* rgbPtr = rgbBuffer;
1405 unsigned char* alphaPtr = alphaBuffer;
1406
1407 for( int y = 0; y < realSize.y; y++ )
1408 {
1409 for( int x = 0; x < realSize.x; x++ )
1410 {
1411 rgbPtr[0] = rgbaPtr[0];
1412 rgbPtr[1] = rgbaPtr[1];
1413 rgbPtr[2] = rgbaPtr[2];
1414 alphaPtr[0] = rgbaPtr[3];
1415
1416 rgbaPtr += 4;
1417 rgbPtr += 3;
1418 alphaPtr += 1;
1419 }
1420 }
1421
1422 wxImage image( realSize );
1423 image.SetData( rgbBuffer );
1424 image.SetAlpha( alphaBuffer );
1425 image = image.Mirror( false );
1426
1427 image.SetOption( wxIMAGE_OPTION_QUALITY, 90 );
1428 image.SaveFile( outPath,
1429 aRenderJob->m_format == JOB_PCB_RENDER::FORMAT::PNG ? wxBITMAP_TYPE_PNG : wxBITMAP_TYPE_JPEG );
1430 }
1431
1432 if( success )
1433 {
1434 m_reporter->Report( _( "Successfully created 3D render image" ) + wxS( "\n" ), RPT_SEVERITY_INFO );
1435 return CLI::EXIT_CODES::OK;
1436 }
1437 else
1438 {
1439 m_reporter->Report( _( "Error creating 3D render image" ) + wxS( "\n" ), RPT_SEVERITY_ERROR );
1441 }
1442}
1443
1444
1446 TOOL_MANAGER* aToolManager )
1447{
1448 if( aJob->m_checkZonesBeforePlot )
1449 {
1450 if( !aToolManager->FindTool( ZONE_FILLER_TOOL_NAME ) )
1451 aToolManager->RegisterTool( new ZONE_FILLER_TOOL );
1452
1453 aToolManager->GetTool<ZONE_FILLER_TOOL>()->FillAllZones( nullptr, m_progressReporter, true );
1454 }
1455
1456 if( aJob->m_argLayers )
1457 aJob->m_plotLayerSequence = convertLayerArg( aJob->m_argLayers.value(), aBoard );
1458
1459 if( aJob->m_argCommonLayers )
1460 aJob->m_plotOnAllLayersSequence = convertLayerArg( aJob->m_argCommonLayers.value(), aBoard );
1461
1462 if( aJob->m_plotLayerSequence.empty() )
1463 {
1464 m_reporter->Report( _( "At least one layer must be specified\n" ), RPT_SEVERITY_ERROR );
1465 return false;
1466 }
1467
1468 return true;
1469}
1470
1471
1473 PLOT_FORMAT aFormat, bool aSingleOutput )
1474{
1475 if( aSingleOutput && aJob->GetConfiguredOutputPath().IsEmpty() )
1476 {
1477 wxFileName fn = aBoard->GetFileName();
1478 fn.SetExt( GetDefaultPlotExtension( aFormat ) );
1479 aJob->SetWorkingOutputPath( fn.GetFullName() );
1480 }
1481
1482 return resolveJobOutputPath( aJob, aBoard, &aJob->m_drawingSheet );
1483}
1484
1485
1486// PDF single-document output need not use overrides; PNG never uses them.
1487static int plotJob( JOB_EXPORT_PCB_PLOT* aJob, PCB_PLOTTER& aPlotter, const wxString& aOutPath,
1488 bool aSingleOutput, bool aUseOverrides )
1489{
1490 std::optional<wxString> layerName;
1491 std::optional<wxString> sheetName;
1492 std::optional<wxString> sheetPath;
1493
1494 if( aUseOverrides )
1495 {
1496 if( aJob->GetVarOverrides().contains( wxT( "LAYER" ) ) )
1497 layerName = aJob->GetVarOverrides().at( wxT( "LAYER" ) );
1498
1499 if( aJob->GetVarOverrides().contains( wxT( "SHEETNAME" ) ) )
1500 sheetName = aJob->GetVarOverrides().at( wxT( "SHEETNAME" ) );
1501
1502 if( aJob->GetVarOverrides().contains( wxT( "SHEETPATH" ) ) )
1503 sheetPath = aJob->GetVarOverrides().at( wxT( "SHEETPATH" ) );
1504 }
1505
1506 std::vector<wxString> outputPaths;
1507
1508 if( !aPlotter.Plot( aOutPath, aJob->m_plotLayerSequence, aJob->m_plotOnAllLayersSequence, false,
1509 aSingleOutput, layerName, sheetName, sheetPath, &outputPaths ) )
1510 {
1512 }
1513
1514 for( const wxString& outputPath : outputPaths )
1515 aJob->AddOutput( outputPath );
1516
1517 return CLI::EXIT_CODES::OK;
1518}
1519
1520
1522{
1523 JOB_EXPORT_PCB_SVG* aSvgJob = dynamic_cast<JOB_EXPORT_PCB_SVG*>( aJob );
1524
1525 if( aSvgJob == nullptr )
1527
1528 BOARD* brd = getBoard( aSvgJob->m_filename );
1529 TOOL_MANAGER* toolManager = getToolManager( brd );
1530
1531 if( !brd )
1533
1534 if( !aSvgJob->m_variant.IsEmpty() )
1535 brd->SetCurrentVariant( aSvgJob->m_variant );
1536
1537 const bool isSingle = aSvgJob->m_genMode == JOB_EXPORT_PCB_SVG::GEN_MODE::SINGLE;
1538
1539 wxString outPath = resolvePlotOutputPath( aSvgJob, brd, PLOT_FORMAT::SVG, isSingle );
1540
1541 if( !PATHS::EnsurePathExists( outPath, isSingle ) )
1542 {
1543 m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
1545 }
1546
1547 if( !preparePlotLayers( aSvgJob, brd, toolManager ) )
1549
1550 PCB_PLOT_PARAMS plotOpts;
1551 PCB_PLOTTER::PlotJobToPlotOpts( plotOpts, aSvgJob, *m_reporter );
1552
1553 PCB_PLOTTER plotter( brd, m_reporter, plotOpts );
1554
1555 return plotJob( aSvgJob, plotter, outPath, isSingle, isSingle );
1556}
1557
1558
1560{
1561 JOB_EXPORT_PCB_DXF* aDxfJob = dynamic_cast<JOB_EXPORT_PCB_DXF*>( aJob );
1562
1563 if( aDxfJob == nullptr )
1565
1566 BOARD* brd = getBoard( aDxfJob->m_filename );
1567
1568 if( !brd )
1570
1571 if( !aDxfJob->m_variant.IsEmpty() )
1572 brd->SetCurrentVariant( aDxfJob->m_variant );
1573
1574 TOOL_MANAGER* toolManager = getToolManager( brd );
1575
1576 if( !preparePlotLayers( aDxfJob, brd, toolManager ) )
1578
1579 const bool isSingle = aDxfJob->m_genMode == JOB_EXPORT_PCB_DXF::GEN_MODE::SINGLE;
1580
1581 wxString outPath = resolvePlotOutputPath( aDxfJob, brd, PLOT_FORMAT::DXF, isSingle );
1582
1583 if( !PATHS::EnsurePathExists( outPath, isSingle ) )
1584 {
1585 m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
1587 }
1588
1589 PCB_PLOT_PARAMS plotOpts;
1590 PCB_PLOTTER::PlotJobToPlotOpts( plotOpts, aDxfJob, *m_reporter );
1591
1592 PCB_PLOTTER plotter( brd, m_reporter, plotOpts );
1593
1594 return plotJob( aDxfJob, plotter, outPath, isSingle, isSingle );
1595}
1596
1597
1599{
1600 bool plotAllLayersOneFile = false;
1601 JOB_EXPORT_PCB_PDF* pdfJob = dynamic_cast<JOB_EXPORT_PCB_PDF*>( aJob );
1602
1603 if( pdfJob == nullptr )
1605
1606 BOARD* brd = getBoard( pdfJob->m_filename );
1607
1608 if( !brd )
1610
1611 if( !pdfJob->m_variant.IsEmpty() )
1612 brd->SetCurrentVariant( pdfJob->m_variant );
1613
1614 TOOL_MANAGER* toolManager = getToolManager( brd );
1615
1616 if( !preparePlotLayers( pdfJob, brd, toolManager ) )
1618
1620 plotAllLayersOneFile = true;
1621
1622 const bool outputIsSingle = plotAllLayersOneFile || pdfJob->m_pdfSingle;
1623
1624 wxString outPath = resolvePlotOutputPath( pdfJob, brd, PLOT_FORMAT::PDF, outputIsSingle );
1625
1626 PCB_PLOT_PARAMS plotOpts;
1627 PCB_PLOTTER::PlotJobToPlotOpts( plotOpts, pdfJob, *m_reporter );
1628
1629 PCB_PLOTTER pcbPlotter( brd, m_reporter, plotOpts );
1630
1631 if( !PATHS::EnsurePathExists( outPath, outputIsSingle ) )
1632 {
1633 m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
1635 }
1636
1637 return plotJob( pdfJob, pcbPlotter, outPath, outputIsSingle, plotAllLayersOneFile );
1638}
1639
1640
1642{
1643 JOB_EXPORT_PCB_PNG* pngJob = dynamic_cast<JOB_EXPORT_PCB_PNG*>( aJob );
1644
1645 if( pngJob == nullptr )
1647
1648 BOARD* brd = getBoard( pngJob->m_filename );
1649
1650 if( !brd )
1652
1653 if( !pngJob->m_variant.IsEmpty() )
1654 brd->SetCurrentVariant( pngJob->m_variant );
1655
1656 TOOL_MANAGER* toolManager = getToolManager( brd );
1657
1658 if( !preparePlotLayers( pngJob, brd, toolManager ) )
1660
1661 bool isSingle = pngJob->m_genMode == JOB_EXPORT_PCB_PNG::GEN_MODE::SINGLE;
1662
1663 wxString outPath = resolvePlotOutputPath( pngJob, brd, PLOT_FORMAT::PNG, isSingle );
1664
1665 PCB_PLOT_PARAMS plotOpts;
1666 PCB_PLOTTER::PlotJobToPlotOpts( plotOpts, pngJob, *m_reporter );
1667
1668 PCB_PLOTTER pcbPlotter( brd, m_reporter, plotOpts );
1669
1670 if( !PATHS::EnsurePathExists( outPath, isSingle ) )
1671 {
1672 m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
1674 }
1675
1676 return plotJob( pngJob, pcbPlotter, outPath, isSingle, false );
1677}
1678
1679
1681{
1682 JOB_EXPORT_PCB_PS* psJob = dynamic_cast<JOB_EXPORT_PCB_PS*>( aJob );
1683
1684 if( psJob == nullptr )
1686
1687 BOARD* brd = getBoard( psJob->m_filename );
1688
1689 if( !brd )
1691
1692 if( !psJob->m_variant.IsEmpty() )
1693 brd->SetCurrentVariant( psJob->m_variant );
1694
1695 TOOL_MANAGER* toolManager = getToolManager( brd );
1696
1697 if( !preparePlotLayers( psJob, brd, toolManager ) )
1699
1700 bool isSingle = psJob->m_genMode == JOB_EXPORT_PCB_PS::GEN_MODE::SINGLE;
1701
1702 wxString outPath = resolvePlotOutputPath( psJob, brd, PLOT_FORMAT::POST, isSingle );
1703
1704 if( !PATHS::EnsurePathExists( outPath, isSingle ) )
1705 {
1706 m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
1708 }
1709
1710 PCB_PLOT_PARAMS plotOpts;
1711 PCB_PLOTTER::PlotJobToPlotOpts( plotOpts, psJob, *m_reporter );
1712
1713 PCB_PLOTTER pcbPlotter( brd, m_reporter, plotOpts );
1714
1715 return plotJob( psJob, pcbPlotter, outPath, isSingle, isSingle );
1716}
1717
1718
1720{
1721 int exitCode = CLI::EXIT_CODES::OK;
1722 JOB_EXPORT_PCB_GERBERS* aGerberJob = dynamic_cast<JOB_EXPORT_PCB_GERBERS*>( aJob );
1723
1724 if( aGerberJob == nullptr )
1726
1727 BOARD* brd = getBoard( aGerberJob->m_filename );
1728
1729 if( !brd )
1731
1732 if( !aGerberJob->m_variant.IsEmpty() )
1733 brd->SetCurrentVariant( aGerberJob->m_variant );
1734
1735 wxString outPath = resolveJobOutputPath( aJob, brd, &aGerberJob->m_drawingSheet );
1736
1737 if( !PATHS::EnsurePathExists( outPath, false ) )
1738 {
1739 m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
1741 }
1742
1743 TOOL_MANAGER* toolManager = getToolManager( brd );
1744
1745 if( aGerberJob->m_checkZonesBeforePlot )
1746 {
1747 if( !toolManager->FindTool( ZONE_FILLER_TOOL_NAME ) )
1748 toolManager->RegisterTool( new ZONE_FILLER_TOOL );
1749
1750 toolManager->GetTool<ZONE_FILLER_TOOL>()->FillAllZones( nullptr, m_progressReporter, true );
1751 }
1752
1753 bool hasLayerListSpecified = false; // will be true if the user layer list is not empty
1754
1755 if( aGerberJob->m_argLayers )
1756 {
1757 if( !aGerberJob->m_argLayers.value().empty() )
1758 {
1759 aGerberJob->m_plotLayerSequence = convertLayerArg( aGerberJob->m_argLayers.value(), brd );
1760 hasLayerListSpecified = true;
1761 }
1762 else
1763 {
1765 }
1766 }
1767
1768 if( aGerberJob->m_argCommonLayers )
1769 aGerberJob->m_plotOnAllLayersSequence = convertLayerArg( aGerberJob->m_argCommonLayers.value(), brd );
1770
1771 PCB_PLOT_PARAMS boardPlotOptions = brd->GetPlotOptions();
1772 GERBER_JOBFILE_WRITER jobfile_writer( brd );
1773
1774 wxString fileExt;
1775
1776 if( aGerberJob->m_useBoardPlotParams )
1777 {
1778 // The board plot options are saved with all copper layers enabled, even those that don't
1779 // exist in the current stackup. This is done so the layers are automatically enabled in the plot
1780 // dialog when the user enables them. We need to filter out these not-enabled layers here so
1781 // we don't plot 32 layers when we only have 4, etc.
1782 LSET plotLayers = ( boardPlotOptions.GetLayerSelection() & LSET::AllNonCuMask() )
1783 | ( brd->GetEnabledLayers() & LSET::AllCuMask() );
1784 aGerberJob->m_plotLayerSequence = plotLayers.SeqStackupForPlotting();
1785 aGerberJob->m_plotOnAllLayersSequence = boardPlotOptions.GetPlotOnAllLayersSequence();
1786 }
1787 else
1788 {
1789 // default to the board enabled layers, but only if the user has not specifed a layer list
1790 // ( m_plotLayerSequence can be empty with a broken user layer list)
1791 if( aGerberJob->m_plotLayerSequence.empty() && !hasLayerListSpecified )
1793 }
1794
1795 // Ensure layers to plot are restricted to enabled layers of the board to plot
1796 LSET layersToPlot = LSET( { aGerberJob->m_plotLayerSequence } ) & brd->GetEnabledLayers();
1797
1798 // Once for the run, not per file, so an update rebuilds each chart once. Common layers
1799 // included or a chart plotted as one is never looked at
1800 LSET preflightLayers = layersToPlot;
1801
1802 for( PCB_LAYER_ID commonLayer : aGerberJob->m_plotOnAllLayersSequence )
1803 preflightLayers.set( commonLayer );
1804
1805 RefreshGeneratedTables( *brd );
1806
1807 for( PCB_LAYER_ID layer : layersToPlot.UIOrder() )
1808 {
1809 LSEQ plotSequence;
1810
1811 // Base layer always gets plotted first.
1812 plotSequence.push_back( layer );
1813
1814 // Now all the "include on all" layers
1815 for( PCB_LAYER_ID layer_all : aGerberJob->m_plotOnAllLayersSequence )
1816 {
1817 // Don't plot the same layer more than once;
1818 if( find( plotSequence.begin(), plotSequence.end(), layer_all ) != plotSequence.end() )
1819 continue;
1820
1821 plotSequence.push_back( layer_all );
1822 }
1823
1824 // Pick the basename from the board file
1825 wxFileName fn( brd->GetFileName() );
1826 wxString layerName = brd->GetLayerName( layer );
1827 wxString sheetName;
1828 wxString sheetPath;
1829 PCB_PLOT_PARAMS plotOpts;
1830
1831 if( aGerberJob->m_useBoardPlotParams )
1832 plotOpts = boardPlotOptions;
1833 else
1834 PCB_PLOTTER::PlotJobToPlotOpts( plotOpts, aGerberJob, *m_reporter );
1835
1836 if( plotOpts.GetUseGerberProtelExtensions() )
1837 fileExt = GetGerberProtelExtension( layer );
1838 else
1840
1841 PCB_PLOTTER::BuildPlotFileName( &fn, outPath, layerName, fileExt );
1842 wxString fullname = fn.GetFullName();
1843
1844 if( m_progressReporter )
1845 {
1846 m_progressReporter->AdvancePhase( wxString::Format( _( "Exporting %s" ), fullname ) );
1847 m_progressReporter->KeepRefreshing();
1848 }
1849
1850 jobfile_writer.AddGbrFile( layer, fullname );
1851
1852 if( aJob->GetVarOverrides().contains( wxT( "LAYER" ) ) )
1853 layerName = aJob->GetVarOverrides().at( wxT( "LAYER" ) );
1854
1855 if( aJob->GetVarOverrides().contains( wxT( "SHEETNAME" ) ) )
1856 sheetName = aJob->GetVarOverrides().at( wxT( "SHEETNAME" ) );
1857
1858 if( aJob->GetVarOverrides().contains( wxT( "SHEETPATH" ) ) )
1859 sheetPath = aJob->GetVarOverrides().at( wxT( "SHEETPATH" ) );
1860
1861 // We are feeding it one layer at the start here to silence a logic check
1862 GERBER_PLOTTER* plotter;
1863 plotter = (GERBER_PLOTTER*) StartPlotBoard( brd, &plotOpts, layer, layerName, fn.GetFullPath(), sheetName,
1864 sheetPath );
1865
1866 if( plotter )
1867 {
1868 m_reporter->Report( wxString::Format( _( "Plotted to '%s'.\n" ), fn.GetFullPath() ), RPT_SEVERITY_ACTION );
1869
1870 PlotBoardLayers( brd, plotter, plotSequence, plotOpts );
1871 plotter->EndPlot();
1872 aJob->AddOutput( fn.GetFullPath() );
1873 }
1874 else
1875 {
1876 m_reporter->Report( wxString::Format( _( "Failed to plot to '%s'.\n" ), fn.GetFullPath() ),
1879 }
1880
1881 delete plotter;
1882 }
1883
1884 if( aGerberJob->m_createJobsFile )
1885 {
1886 wxFileName fn( brd->GetFileName() );
1887
1888 // Build gerber job file from basename
1890 jobfile_writer.CreateJobFile( fn.GetFullPath() );
1891 aJob->AddOutput( fn.GetFullPath() );
1892 }
1893
1894 return exitCode;
1895}
1896
1897
1899{
1900 JOB_EXPORT_PCB_GENCAD* aGencadJob = dynamic_cast<JOB_EXPORT_PCB_GENCAD*>( aJob );
1901
1902 if( aGencadJob == nullptr )
1904
1905 BOARD* brd = getBoard( aGencadJob->m_filename );
1906
1907 if( !brd )
1909
1910 GENCAD_EXPORTER exporter( brd );
1911
1912 VECTOR2I GencadOffset;
1913 VECTOR2I auxOrigin = brd->GetDesignSettings().GetAuxOrigin();
1914 GencadOffset.x = aGencadJob->m_useDrillOrigin ? auxOrigin.x : 0;
1915 GencadOffset.y = aGencadJob->m_useDrillOrigin ? auxOrigin.y : 0;
1916
1917 exporter.FlipBottomPads( aGencadJob->m_flipBottomPads );
1918 exporter.UsePinNamesUnique( aGencadJob->m_useUniquePins );
1919 exporter.UseIndividualShapes( aGencadJob->m_useIndividualShapes );
1920 exporter.SetPlotOffet( GencadOffset );
1921 exporter.StoreOriginCoordsInFile( aGencadJob->m_storeOriginCoords );
1922
1923 if( aGencadJob->GetConfiguredOutputPath().IsEmpty() )
1924 {
1925 wxFileName fn = brd->GetFileName();
1926 fn.SetName( fn.GetName() );
1927 fn.SetExt( FILEEXT::GencadFileExtension );
1928
1929 aGencadJob->SetWorkingOutputPath( fn.GetFullName() );
1930 }
1931
1932 wxString outPath = resolveJobOutputPath( aJob, brd );
1933
1934 if( !PATHS::EnsurePathExists( outPath, true ) )
1935 {
1936 m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
1938 }
1939
1940 if( !exporter.WriteFile( outPath ) )
1941 {
1942 m_reporter->Report( wxString::Format( _( "Failed to create file '%s'.\n" ), outPath ), RPT_SEVERITY_ERROR );
1943
1945 }
1946
1947 aJob->AddOutput( outPath );
1948 m_reporter->Report( _( "Successfully created genCAD file\n" ), RPT_SEVERITY_INFO );
1949
1950 return CLI::EXIT_CODES::OK;
1951}
1952
1953
1955{
1956 JOB_EXPORT_PCB_STATS* statsJob = dynamic_cast<JOB_EXPORT_PCB_STATS*>( aJob );
1957
1958 if( statsJob == nullptr )
1960
1961 BOARD* brd = getBoard( statsJob->m_filename );
1962
1963 if( !brd )
1965
1968
1973
1974 ComputeBoardStatistics( brd, options, data );
1975
1976 wxString projectName;
1977
1978 if( brd->GetProject() )
1979 projectName = brd->GetProject()->GetProjectName();
1980
1981 wxFileName boardFile = brd->GetFileName();
1982
1983 if( boardFile.GetName().IsEmpty() )
1984 boardFile = wxFileName( statsJob->m_filename );
1985
1987 UNITS_PROVIDER unitsProvider( pcbIUScale, unitsForReport );
1988
1989 wxString report;
1990
1992 report = FormatBoardStatisticsJson( data, brd, unitsProvider, projectName, boardFile.GetName() );
1993 else
1994 report = FormatBoardStatisticsReport( data, brd, unitsProvider, projectName, boardFile.GetName() );
1995
1996 if( statsJob->GetConfiguredOutputPath().IsEmpty() && statsJob->GetWorkingOutputPath().IsEmpty() )
1997 statsJob->SetDefaultOutputPath( boardFile.GetFullPath() );
1998
1999 wxString outPath = resolveJobOutputPath( aJob, brd );
2000
2001 if( !PATHS::EnsurePathExists( outPath, true ) )
2002 {
2003 m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
2005 }
2006
2007 FILE* outFile = wxFopen( outPath, wxS( "wt" ) );
2008
2009 if( !outFile )
2010 {
2011 m_reporter->Report( wxString::Format( _( "Failed to create file '%s'.\n" ), outPath ), RPT_SEVERITY_ERROR );
2013 }
2014
2015 if( fprintf( outFile, "%s", TO_UTF8( report ) ) < 0 )
2016 {
2017 fclose( outFile );
2018 m_reporter->Report( wxString::Format( _( "Error writing file '%s'.\n" ), outPath ), RPT_SEVERITY_ERROR );
2020 }
2021
2022 fclose( outFile );
2023
2024 m_reporter->Report( wxString::Format( _( "Wrote board statistics to '%s'.\n" ), outPath ), RPT_SEVERITY_ACTION );
2025
2026 statsJob->AddOutput( outPath );
2027
2028 return CLI::EXIT_CODES::OK;
2029}
2030
2031
2033{
2034 JOB_EXPORT_PCB_STACKUP* stackupJob = dynamic_cast<JOB_EXPORT_PCB_STACKUP*>( aJob );
2035
2036 if( stackupJob == nullptr )
2038
2039 BOARD* brd = getBoard( stackupJob->m_filename );
2040
2041 if( !brd )
2043
2044 wxFileName boardFile = brd->GetFileName();
2045
2046 if( boardFile.GetName().IsEmpty() )
2047 boardFile = wxFileName( stackupJob->m_filename );
2048
2049 wxString output;
2050
2051 switch( stackupJob->m_format )
2052 {
2054 {
2055 kiapi::board::BoardStackup stackupMsg;
2056 kiapi::board::PackBoardStackup( *brd, stackupMsg );
2057
2058 google::protobuf::util::JsonPrintOptions jsonOptions;
2059 jsonOptions.add_whitespace = true;
2060
2061 std::string json;
2062
2063 if( !google::protobuf::util::MessageToJsonString( stackupMsg, &json, jsonOptions ).ok() )
2064 {
2065 m_reporter->Report( _( "Failed to serialize board stackup\n" ), RPT_SEVERITY_ERROR );
2067 }
2068
2069 output = wxString::FromUTF8( json );
2070 break;
2071 }
2072
2074 {
2076 BOARD_STACKUP stackup = bds.GetStackupDescriptor();
2077 stackup.SynchronizeWithBoard( &bds );
2078
2079 for( BOARD_STACKUP_ITEM* item : stackup.GetList() )
2080 {
2081 if( item->GetBrdLayerId() != UNDEFINED_LAYER )
2082 item->SetLayerName( brd->GetLayerName( item->GetBrdLayerId() ) );
2083 }
2084
2085 EDA_UNITS unitsForReport =
2087
2088 STACKUP_CSV_OPTIONS options;
2089 options.includeColor = stackupJob->m_includeColor;
2090 options.includeMaterial = stackupJob->m_includeMaterial;
2091 options.includeThickness = stackupJob->m_includeThickness;
2092 options.includeEpsilonR = stackupJob->m_includeEpsilonR;
2093 options.includeLossTangent = stackupJob->m_includeLossTangent;
2094 options.includeFinish = stackupJob->m_includeFinish;
2095 options.includeBoardOptions = stackupJob->m_includeBoardOptions;
2096
2097 output = BuildStackupCsv( stackup, unitsForReport, options );
2098 break;
2099 }
2100 }
2101
2102 if( stackupJob->GetConfiguredOutputPath().IsEmpty() && stackupJob->GetWorkingOutputPath().IsEmpty() )
2103 stackupJob->SetDefaultOutputPath( boardFile.GetFullPath() );
2104
2105 wxString outPath = resolveJobOutputPath( aJob, brd );
2106
2107 if( !PATHS::EnsurePathExists( outPath, true ) )
2108 {
2109 m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
2111 }
2112
2113 OPEN_OSTREAM( outFile, TO_UTF8( outPath ) );
2114
2115 if( !outFile )
2116 {
2117 m_reporter->Report( wxString::Format( _( "Failed to create file '%s'.\n" ), outPath ), RPT_SEVERITY_ERROR );
2119 }
2120
2121 outFile << TO_UTF8( output );
2122
2123 const bool writeOk = static_cast<bool>( outFile );
2124 CLOSE_STREAM( outFile );
2125
2126 if( !writeOk )
2127 {
2128 m_reporter->Report( wxString::Format( _( "Error writing file '%s'.\n" ), outPath ), RPT_SEVERITY_ERROR );
2130 }
2131
2132 m_reporter->Report( wxString::Format( _( "Wrote board stackup to '%s'.\n" ), outPath ), RPT_SEVERITY_ACTION );
2133
2134 stackupJob->AddOutput( outPath );
2135
2136 return CLI::EXIT_CODES::OK;
2137}
2138
2139
2141{
2142 int exitCode = CLI::EXIT_CODES::OK;
2143 JOB_EXPORT_PCB_GERBER* aGerberJob = dynamic_cast<JOB_EXPORT_PCB_GERBER*>( aJob );
2144
2145 if( aGerberJob == nullptr )
2147
2148 BOARD* brd = getBoard( aGerberJob->m_filename );
2149
2150 if( !brd )
2152
2153 if( !aGerberJob->m_variant.IsEmpty() )
2154 brd->SetCurrentVariant( aGerberJob->m_variant );
2155
2156 TOOL_MANAGER* toolManager = getToolManager( brd );
2157
2158 if( aGerberJob->m_argLayers )
2159 aGerberJob->m_plotLayerSequence = convertLayerArg( aGerberJob->m_argLayers.value(), brd );
2160
2161 if( aGerberJob->m_argCommonLayers )
2162 aGerberJob->m_plotOnAllLayersSequence = convertLayerArg( aGerberJob->m_argCommonLayers.value(), brd );
2163
2164 if( aGerberJob->m_plotLayerSequence.size() < 1 )
2165 {
2166 m_reporter->Report( _( "At least one layer must be specified\n" ), RPT_SEVERITY_ERROR );
2168 }
2169
2170 if( aGerberJob->GetConfiguredOutputPath().IsEmpty() )
2171 {
2172 wxFileName fn = brd->GetFileName();
2173 fn.SetName( fn.GetName() );
2175
2176 aGerberJob->SetWorkingOutputPath( fn.GetFullName() );
2177 }
2178
2179 wxString outPath = resolveJobOutputPath( aJob, brd, &aGerberJob->m_drawingSheet );
2180
2181 if( aGerberJob->m_checkZonesBeforePlot )
2182 {
2183 if( !toolManager->FindTool( ZONE_FILLER_TOOL_NAME ) )
2184 toolManager->RegisterTool( new ZONE_FILLER_TOOL );
2185
2186 toolManager->GetTool<ZONE_FILLER_TOOL>()->FillAllZones( nullptr, m_progressReporter, true );
2187 }
2188
2189 PCB_PLOT_PARAMS plotOpts;
2190 PCB_PLOTTER::PlotJobToPlotOpts( plotOpts, aGerberJob, *m_reporter );
2191 plotOpts.SetLayerSelection( aGerberJob->m_plotLayerSequence );
2193
2195 wxString layerName;
2196 wxString sheetName;
2197 wxString sheetPath;
2198
2199 // The first layer will be treated as the layer name for the gerber header,
2200 // the other layers will be treated equivalent to the "Plot on All Layers" option
2201 // in the GUI
2202 if( aGerberJob->m_plotLayerSequence.size() >= 1 )
2203 {
2204 layer = aGerberJob->m_plotLayerSequence.front();
2205 layerName = brd->GetLayerName( layer );
2206 }
2207
2208 if( aJob->GetVarOverrides().contains( wxT( "LAYER" ) ) )
2209 layerName = aJob->GetVarOverrides().at( wxT( "LAYER" ) );
2210
2211 if( aJob->GetVarOverrides().contains( wxT( "SHEETNAME" ) ) )
2212 sheetName = aJob->GetVarOverrides().at( wxT( "SHEETNAME" ) );
2213
2214 if( aJob->GetVarOverrides().contains( wxT( "SHEETPATH" ) ) )
2215 sheetPath = aJob->GetVarOverrides().at( wxT( "SHEETPATH" ) );
2216
2217 // We are feeding it one layer at the start here to silence a logic check
2218
2219 // It drives StartPlotBoard directly rather than PCB_PLOTTER::Plot, so it needs its own
2220 // preflight or a FAIL policy would still emit a stale chart
2221 {
2222 LSET preflightLayers( { aGerberJob->m_plotLayerSequence } );
2223
2224 RefreshGeneratedTables( *brd );
2225 }
2226
2227 PLOTTER* plotter = StartPlotBoard( brd, &plotOpts, layer, layerName, outPath, sheetName, sheetPath );
2228
2229 if( plotter )
2230 {
2231 PlotBoardLayers( brd, plotter, aGerberJob->m_plotLayerSequence, plotOpts );
2232 plotter->EndPlot();
2233 }
2234 else
2235 {
2236 m_reporter->Report( wxString::Format( _( "Failed to plot to '%s'.\n" ), outPath ), RPT_SEVERITY_ERROR );
2238 }
2239
2240 delete plotter;
2241
2242 return exitCode;
2243}
2244
2247
2248
2250{
2251 JOB_EXPORT_PCB_DRILL* aDrillJob = dynamic_cast<JOB_EXPORT_PCB_DRILL*>( aJob );
2252
2253 if( aDrillJob == nullptr )
2255
2256 BOARD* brd = getBoard( aDrillJob->m_filename );
2257
2258 if( !brd )
2260
2261 wxString outPath = resolveJobOutputPath( aJob, brd );
2262
2263 if( !PATHS::EnsurePathExists( outPath ) )
2264 {
2265 m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
2267 }
2268
2269 std::unique_ptr<GENDRILL_WRITER_BASE> drillWriter;
2270
2272 drillWriter = std::make_unique<EXCELLON_WRITER>( brd );
2273 else
2274 drillWriter = std::make_unique<GERBER_WRITER>( brd );
2275
2276 VECTOR2I offset;
2277
2279 offset = VECTOR2I( 0, 0 );
2280 else
2281 offset = brd->GetDesignSettings().GetAuxOrigin();
2282
2283 PLOT_FORMAT mapFormat = PLOT_FORMAT::PDF;
2284
2285 switch( aDrillJob->m_mapFormat )
2286 {
2291 default:
2293 }
2294
2295
2296 if( aDrillJob->m_generateReport && aDrillJob->m_reportPath.IsEmpty() )
2297 {
2298 wxFileName fn = outPath;
2299 fn.SetFullName( brd->GetFileName() );
2300 fn.SetName( fn.GetName() + "-drill" );
2301 fn.SetExt( FILEEXT::ReportFileExtension );
2302
2303 aDrillJob->m_reportPath = fn.GetFullPath();
2304 }
2305
2307 {
2309
2310 switch( aDrillJob->m_zeroFormat )
2311 {
2313
2315
2317
2319 default: zeroFmt = EXCELLON_WRITER::DECIMAL_FORMAT; break;
2320 }
2321
2322 DRILL_PRECISION precision;
2323
2325 precision = precisionListForInches;
2326 else
2327 precision = precisionListForMetric;
2328
2329 EXCELLON_WRITER* excellonWriter = dynamic_cast<EXCELLON_WRITER*>( drillWriter.get() );
2330
2331 if( excellonWriter == nullptr )
2333
2334 excellonWriter->SetFormat( aDrillJob->m_drillUnits == JOB_EXPORT_PCB_DRILL::DRILL_UNITS::MM, zeroFmt,
2335 precision.m_Lhs, precision.m_Rhs );
2336 excellonWriter->SetOptions( aDrillJob->m_excellonMirrorY, aDrillJob->m_excellonMinimalHeader, offset,
2337 aDrillJob->m_excellonCombinePTHNPTH );
2338 excellonWriter->SetRouteModeForOvalHoles( aDrillJob->m_excellonOvalDrillRoute );
2339 excellonWriter->SetMapFileFormat( mapFormat );
2340
2341 if( !excellonWriter->CreateDrillandMapFilesSet( outPath, true, aDrillJob->m_generateMap, m_reporter ) )
2342 {
2344 }
2345
2346 for( const wxString& outputFile : drillWriter->GetCreatedFiles() )
2347 aDrillJob->AddOutput( outputFile );
2348
2349 if( aDrillJob->m_generateReport )
2350 {
2351 wxString reportPath = aDrillJob->ResolveOutputPath( aDrillJob->m_reportPath, true, brd->GetProject() );
2352
2353 if( !excellonWriter->GenDrillReportFile( reportPath ) )
2354 {
2356 }
2357
2358 aDrillJob->AddOutput( reportPath );
2359 }
2360 }
2362 {
2363 GERBER_WRITER* gerberWriter = dynamic_cast<GERBER_WRITER*>( drillWriter.get() );
2364
2365 if( gerberWriter == nullptr )
2367
2368 // Set gerber precision: only 5 or 6 digits for mantissa are allowed
2369 // (SetFormat() accept 5 or 6, and any other value set the precision to 5)
2370 // the integer part precision is always 4, and units always mm
2371 gerberWriter->SetFormat( aDrillJob->m_gerberPrecision );
2372 gerberWriter->SetOptions( offset );
2373 gerberWriter->SetMapFileFormat( mapFormat );
2374
2375 if( !gerberWriter->CreateDrillandMapFilesSet( outPath, true, aDrillJob->m_generateMap,
2376 aDrillJob->m_generateTenting, m_reporter ) )
2377 {
2379 }
2380
2381 for( const wxString& outputFile : drillWriter->GetCreatedFiles() )
2382 aDrillJob->AddOutput( outputFile );
2383
2384 if( aDrillJob->m_generateReport )
2385 {
2386 wxString reportPath = aDrillJob->ResolveOutputPath( aDrillJob->m_reportPath, true, brd->GetProject() );
2387
2388 if( !gerberWriter->GenDrillReportFile( reportPath ) )
2389 {
2391 }
2392
2393 aDrillJob->AddOutput( reportPath );
2394 }
2395 }
2396
2397 return CLI::EXIT_CODES::OK;
2398}
2399
2400
2402{
2403 JOB_EXPORT_PCB_POS* aPosJob = dynamic_cast<JOB_EXPORT_PCB_POS*>( aJob );
2404
2405 if( aPosJob == nullptr )
2407
2408 BOARD* brd = getBoard( aPosJob->m_filename );
2409
2410 if( !brd )
2412
2413 if( aPosJob->GetConfiguredOutputPath().IsEmpty() )
2414 {
2415 wxFileName fn = brd->GetFileName();
2416 fn.SetName( fn.GetName() );
2417
2420 else if( aPosJob->m_format == JOB_EXPORT_PCB_POS::FORMAT::CSV )
2421 fn.SetExt( FILEEXT::CsvFileExtension );
2422 else if( aPosJob->m_format == JOB_EXPORT_PCB_POS::FORMAT::GERBER )
2423 fn.SetExt( FILEEXT::GerberFileExtension );
2424
2425 aPosJob->SetWorkingOutputPath( fn.GetFullName() );
2426 }
2427
2428 wxString outPath = resolveJobOutputPath( aJob, brd );
2429
2430 if( !PATHS::EnsurePathExists( outPath, true ) )
2431 {
2432 m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
2434 }
2435
2437 {
2438 wxFileName fn( outPath );
2439 wxString baseName = fn.GetName();
2440
2441 auto exportPlaceFile = [&]( bool frontSide, bool backSide, const wxString& curr_outPath ) -> bool
2442 {
2443 FILE* file = wxFopen( curr_outPath, wxS( "wt" ) );
2444 wxCHECK( file, false );
2445
2446 PLACE_FILE_EXPORTER exporter( brd, aPosJob->m_units == JOB_EXPORT_PCB_POS::UNITS::MM, aPosJob->m_smdOnly,
2447 aPosJob->m_excludeFootprintsWithTh, aPosJob->m_excludeDNP,
2448 aPosJob->m_excludeBOM, frontSide, backSide,
2450 aPosJob->m_useDrillPlaceFileOrigin, aPosJob->m_negateBottomX );
2451
2452 // Set variant for variant-aware DNP/BOM/position file filtering
2453 exporter.SetVariant( aPosJob->m_variant );
2454
2455 std::string data = exporter.GenPositionData();
2456 fputs( data.c_str(), file );
2457 fclose( file );
2458
2459 return true;
2460 };
2461
2462 if( aPosJob->m_side == JOB_EXPORT_PCB_POS::SIDE::BOTH && !aPosJob->m_singleFile )
2463 {
2464 fn.SetName( PLACE_FILE_EXPORTER::DecorateFilename( baseName, true, false ) );
2465
2466 if( aPosJob->m_format == JOB_EXPORT_PCB_POS::FORMAT::CSV && !aPosJob->m_nakedFilename )
2467 fn.SetName( fn.GetName() + wxT( "-" ) + FILEEXT::FootprintPlaceFileExtension );
2468
2469 if( exportPlaceFile( true, false, fn.GetFullPath() ) )
2470 {
2471 m_reporter->Report( wxString::Format( _( "Wrote front position data to '%s'.\n" ), fn.GetFullPath() ),
2473
2474 aPosJob->AddOutput( fn.GetFullPath() );
2475 }
2476 else
2477 {
2479 }
2480
2481 fn.SetName( PLACE_FILE_EXPORTER::DecorateFilename( baseName, false, true ) );
2482
2483 if( aPosJob->m_format == JOB_EXPORT_PCB_POS::FORMAT::CSV && !aPosJob->m_nakedFilename )
2484 fn.SetName( fn.GetName() + wxT( "-" ) + FILEEXT::FootprintPlaceFileExtension );
2485
2486 if( exportPlaceFile( false, true, fn.GetFullPath() ) )
2487 {
2488 m_reporter->Report( wxString::Format( _( "Wrote back position data to '%s'.\n" ), fn.GetFullPath() ),
2490
2491 aPosJob->AddOutput( fn.GetFullPath() );
2492 }
2493 else
2494 {
2496 }
2497 }
2498 else
2499 {
2500 bool front = aPosJob->m_side == JOB_EXPORT_PCB_POS::SIDE::FRONT
2502
2503 bool back = aPosJob->m_side == JOB_EXPORT_PCB_POS::SIDE::BACK
2505
2506 if( !aPosJob->m_nakedFilename )
2507 {
2508 fn.SetName( PLACE_FILE_EXPORTER::DecorateFilename( fn.GetName(), front, back ) );
2509
2511 fn.SetName( fn.GetName() + wxT( "-" ) + FILEEXT::FootprintPlaceFileExtension );
2512 }
2513
2514 if( exportPlaceFile( front, back, fn.GetFullPath() ) )
2515 {
2516 m_reporter->Report( wxString::Format( _( "Wrote position data to '%s'.\n" ), fn.GetFullPath() ),
2518
2519 aPosJob->AddOutput( fn.GetFullPath() );
2520 }
2521 else
2522 {
2524 }
2525 }
2526 }
2527 else if( aPosJob->m_format == JOB_EXPORT_PCB_POS::FORMAT::GERBER )
2528 {
2529 PLACEFILE_GERBER_WRITER exporter( brd );
2530
2531 // Set variant for variant-aware DNP/BOM/position file filtering
2532 exporter.SetVariant( aPosJob->m_variant );
2533
2534 PCB_LAYER_ID gbrLayer = F_Cu;
2535 wxString outPath_base = outPath;
2536
2538 {
2539 if( aPosJob->m_side == JOB_EXPORT_PCB_POS::SIDE::BOTH || !aPosJob->m_nakedFilename )
2540 outPath = exporter.GetPlaceFileName( outPath, gbrLayer );
2541
2542 if( exporter.CreatePlaceFile( outPath, gbrLayer, aPosJob->m_gerberBoardEdge, aPosJob->m_excludeDNP,
2543 aPosJob->m_excludeBOM )
2544 >= 0 )
2545 {
2546 m_reporter->Report( wxString::Format( _( "Wrote front position data to '%s'.\n" ), outPath ),
2548
2549 aPosJob->AddOutput( outPath );
2550 }
2551 else
2552 {
2554 }
2555 }
2556
2558 {
2559 gbrLayer = B_Cu;
2560
2561 outPath = outPath_base;
2562
2563 if( aPosJob->m_side == JOB_EXPORT_PCB_POS::SIDE::BOTH || !aPosJob->m_nakedFilename )
2564 outPath = exporter.GetPlaceFileName( outPath, gbrLayer );
2565
2566 if( exporter.CreatePlaceFile( outPath, gbrLayer, aPosJob->m_gerberBoardEdge, aPosJob->m_excludeDNP,
2567 aPosJob->m_excludeBOM )
2568 >= 0 )
2569 {
2570 m_reporter->Report( wxString::Format( _( "Wrote back position data to '%s'.\n" ), outPath ),
2572
2573 aPosJob->AddOutput( outPath );
2574 }
2575 else
2576 {
2578 }
2579 }
2580 }
2581
2582 return CLI::EXIT_CODES::OK;
2583}
2584
2585
2587{
2588 JOB_FP_UPGRADE* upgradeJob = dynamic_cast<JOB_FP_UPGRADE*>( aJob );
2589
2590 if( upgradeJob == nullptr )
2592
2594
2595 if( !upgradeJob->m_outputLibraryPath.IsEmpty() )
2596 {
2597 if( wxFile::Exists( upgradeJob->m_outputLibraryPath ) || wxDir::Exists( upgradeJob->m_outputLibraryPath ) )
2598 {
2599 m_reporter->Report( _( "Output path must not conflict with existing path\n" ), RPT_SEVERITY_ERROR );
2601 }
2602 }
2603 else if( fileType != PCB_IO_MGR::KICAD_SEXP )
2604 {
2605 m_reporter->Report( _( "Output path must be specified to convert legacy and non-KiCad libraries\n" ),
2607
2609 }
2610
2612 {
2613 if( !wxDir::Exists( upgradeJob->m_libraryPath ) )
2614 {
2615 m_reporter->Report( _( "Footprint library path does not exist or is not accessible\n" ),
2618 }
2619
2621 FP_CACHE fpLib( &pcb_io, upgradeJob->m_libraryPath );
2622
2623 try
2624 {
2625 fpLib.Load();
2626 }
2627 catch( ... )
2628 {
2629 m_reporter->Report( _( "Unable to load library\n" ), RPT_SEVERITY_ERROR );
2631 }
2632
2633 if( m_progressReporter )
2634 m_progressReporter->KeepRefreshing();
2635
2636 bool shouldSave = upgradeJob->m_force;
2637
2638 for( const auto& footprint : fpLib.GetFootprints() )
2639 {
2640 if( footprint.second->GetFootprint()->GetFileFormatVersionAtLoad() < SEXPR_BOARD_FILE_VERSION )
2641 shouldSave = true;
2642 }
2643
2644 if( shouldSave )
2645 {
2646 try
2647 {
2648 if( !upgradeJob->m_outputLibraryPath.IsEmpty() )
2649 fpLib.SetPath( upgradeJob->m_outputLibraryPath );
2650
2651 fpLib.Save();
2652 }
2653 catch( ... )
2654 {
2655 m_reporter->Report( _( "Unable to save library\n" ), RPT_SEVERITY_ERROR );
2657 }
2658 }
2659 else
2660 {
2661 m_reporter->Report( _( "Footprint library was not updated\n" ), RPT_SEVERITY_ERROR );
2662 }
2663 }
2664 else
2665 {
2666 if( !PCB_IO_MGR::ConvertLibrary( {}, upgradeJob->m_libraryPath, upgradeJob->m_outputLibraryPath,
2667 nullptr /* REPORTER */ ) )
2668 {
2669 m_reporter->Report( ( "Unable to convert library\n" ), RPT_SEVERITY_ERROR );
2671 }
2672 }
2673
2674 return CLI::EXIT_CODES::OK;
2675}
2676
2677
2679{
2680 JOB_FP_EXPORT_SVG* svgJob = dynamic_cast<JOB_FP_EXPORT_SVG*>( aJob );
2681
2682 if( svgJob == nullptr )
2684
2686 FP_CACHE fpLib( &pcb_io, svgJob->m_libraryPath );
2687
2688 if( svgJob->m_argLayers )
2689 {
2690 if( !svgJob->m_argLayers.value().empty() )
2691 svgJob->m_plotLayerSequence = convertLayerArg( svgJob->m_argLayers.value(), nullptr );
2692 else
2694 }
2695
2696 try
2697 {
2698 fpLib.Load();
2699 }
2700 catch( ... )
2701 {
2702 m_reporter->Report( _( "Unable to load library\n" ), RPT_SEVERITY_ERROR );
2704 }
2705
2706 wxString outPath = svgJob->GetFullOutputPath( nullptr );
2707
2708 if( !PATHS::EnsurePathExists( outPath, true ) )
2709 {
2710 m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
2712 }
2713
2714 int exitCode = CLI::EXIT_CODES::OK;
2715 bool singleFpPlotted = false;
2716
2717 for( const auto& [fpName, fpCacheEntry] : fpLib.GetFootprints() )
2718 {
2719 if( m_progressReporter )
2720 {
2721 m_progressReporter->AdvancePhase( wxString::Format( _( "Exporting %s" ), fpName ) );
2722 m_progressReporter->KeepRefreshing();
2723 }
2724
2725 if( !svgJob->m_footprint.IsEmpty() )
2726 {
2727 // skip until we find the right footprint
2728 if( fpName != svgJob->m_footprint )
2729 continue;
2730 else
2731 singleFpPlotted = true;
2732 }
2733
2734 exitCode = doFpExportSvg( svgJob, fpCacheEntry->GetFootprint().get() );
2735
2736 if( exitCode != CLI::EXIT_CODES::OK )
2737 break;
2738 }
2739
2740 if( !svgJob->m_footprint.IsEmpty() && !singleFpPlotted )
2741 {
2742 m_reporter->Report( _( "The given footprint could not be found to export." ) + wxS( "\n" ),
2744 }
2745
2746 return CLI::EXIT_CODES::OK;
2747}
2748
2749
2751{
2752 wxFileName outputFile;
2753 outputFile.SetPath( aSvgJob->GetFullOutputPath( nullptr ) );
2754 outputFile.SetName( aFootprint->GetFPID().GetLibItemName().wx_str() );
2755 outputFile.SetExt( FILEEXT::SVGFileExtension );
2756
2757 m_reporter->Report( wxString::Format( _( "Plotting footprint '%s' to '%s'\n" ),
2758 aFootprint->GetFPID().GetLibItemName().wx_str(), outputFile.GetFullPath() ),
2760
2761 PCB_PLOT_PARAMS plotOpts;
2762 PCB_PLOTTER::PlotJobToPlotOpts( plotOpts, aSvgJob, *m_reporter );
2763
2764 if( plotOpts.GetSketchPadsOnFabLayers() )
2765 {
2766 plotOpts.SetPlotPadNumbers( true );
2767 }
2768
2769 if( !PlotFootprintToSVG( *aFootprint, *Pgm().GetSettingsManager().GetProject( "" ), &aSvgJob->GetVarOverrides(),
2770 plotOpts, aSvgJob->m_plotLayerSequence, aSvgJob->m_plotOnAllLayersSequence,
2771 outputFile.GetFullPath(), m_reporter ) )
2772 {
2773 m_reporter->Report( _( "Error creating svg file" ) + wxS( "\n" ), RPT_SEVERITY_ERROR );
2775 }
2776
2777 aSvgJob->AddOutput( outputFile.GetFullPath() );
2778
2779 return CLI::EXIT_CODES::OK;
2780}
2781
2782
2784{
2785 JOB_PCB_DRC* drcJob = dynamic_cast<JOB_PCB_DRC*>( aJob );
2786
2787 if( drcJob == nullptr )
2789
2790 BOARD* brd = getBoard( drcJob->m_filename );
2791
2792 if( !brd )
2794
2795 // Running DRC requires libraries be loaded, so make sure they have been
2797 adapter->AsyncLoad();
2798 adapter->BlockUntilLoaded();
2799
2800 if( drcJob->GetConfiguredOutputPath().IsEmpty() )
2801 {
2802 wxFileName fn = brd->GetFileName();
2803 fn.SetName( fn.GetName() + wxS( "-drc" ) );
2804
2806 fn.SetExt( FILEEXT::JsonFileExtension );
2807 else
2808 fn.SetExt( FILEEXT::ReportFileExtension );
2809
2810 drcJob->SetWorkingOutputPath( fn.GetFullName() );
2811 }
2812
2813 wxString outPath = resolveJobOutputPath( aJob, brd );
2814
2815 if( !PATHS::EnsurePathExists( outPath, true ) )
2816 {
2817 m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
2819 }
2820
2821 EDA_UNITS units;
2822
2823 switch( drcJob->m_units )
2824 {
2825 case JOB_PCB_DRC::UNITS::INCH: units = EDA_UNITS::INCH; break;
2826 case JOB_PCB_DRC::UNITS::MILS: units = EDA_UNITS::MILS; break;
2827 case JOB_PCB_DRC::UNITS::MM: units = EDA_UNITS::MM; break;
2828 default: units = EDA_UNITS::MM; break;
2829 }
2830
2831 std::shared_ptr<DRC_ENGINE> drcEngine = brd->GetDesignSettings().m_DRCEngine;
2832 std::unique_ptr<NETLIST> netlist = std::make_unique<NETLIST>();
2833
2834 if( !drcEngine->RulesValid() )
2835 {
2836 try
2837 {
2838 drcEngine->InitEngine( brd->GetDesignRulesPath() );
2839 }
2840 catch( const PARSE_ERROR& error )
2841 {
2842 m_reporter->Report( _( "DRC incomplete: could not compile custom design rules." )
2843 + wxS( "\n" ) + error.What() + wxS( "\n" ),
2846 }
2847 }
2848
2849 drcEngine->SetDrawingSheet( getDrawingSheetProxyView( brd ) );
2850
2851 // BOARD_COMMIT uses TOOL_MANAGER to grab the board internally so we must give it one
2852 TOOL_MANAGER* toolManager = getToolManager( brd );
2853
2854 BOARD_COMMIT commit( toolManager );
2855 bool checkParity = drcJob->m_parity;
2856 std::string netlist_str;
2857
2858 if( checkParity )
2859 {
2860 wxString annotateMsg = _( "Schematic parity tests require a fully annotated schematic." );
2861 netlist_str = annotateMsg;
2862
2863 // The KIFACE_NETLIST_SCHEMATIC function has some broken-ness that the schematic
2864 // frame's version does not, but it is the only one that works in CLI, so we use it
2865 // if we don't have the sch frame open.
2866 // TODO: clean this up, see https://gitlab.com/kicad/code/kicad/-/issues/19929
2867 if( m_kiway->Player( FRAME_SCH, false ) )
2868 {
2869 m_kiway->ExpressMail( FRAME_SCH, MAIL_SCH_GET_NETLIST, netlist_str );
2870 }
2871 else
2872 {
2873 wxFileName schematicPath( drcJob->m_filename );
2874 schematicPath.MakeAbsolute();
2875 schematicPath.SetExt( FILEEXT::KiCadSchematicFileExtension );
2876
2877 if( !schematicPath.Exists() )
2878 schematicPath.SetExt( FILEEXT::LegacySchematicFileExtension );
2879
2880 if( !schematicPath.Exists() )
2881 {
2882 m_reporter->Report( _( "Failed to fetch schematic netlist for parity tests.\n" ), RPT_SEVERITY_ERROR );
2883 checkParity = false;
2884 }
2885 else
2886 {
2887 typedef bool ( *NETLIST_FN_PTR )( const wxString&, std::string&, KIWAY* );
2888 KIFACE* eeschema = m_kiway->KiFACE( KIWAY::FACE_SCH );
2889 NETLIST_FN_PTR netlister = (NETLIST_FN_PTR) eeschema->IfaceOrAddress( KIFACE_NETLIST_SCHEMATIC );
2890 ( *netlister )( schematicPath.GetFullPath(), netlist_str, m_kiway );
2891 }
2892 }
2893
2894 if( netlist_str == MAIL_SCH_GET_NETLIST_CANCELLED )
2895 {
2896 checkParity = false;
2897 }
2898 else if( netlist_str == annotateMsg )
2899 {
2900 m_reporter->Report( annotateMsg + wxT( "\n" ), RPT_SEVERITY_ERROR );
2901 checkParity = false;
2902 }
2903 }
2904
2905 if( checkParity )
2906 {
2907 try
2908 {
2909 STRING_LINE_READER* lineReader = new STRING_LINE_READER( netlist_str, _( "Eeschema netlist" ) );
2910 KICAD_NETLIST_READER netlistReader( lineReader, netlist.get() );
2911
2912 netlistReader.LoadNetlist();
2913 }
2914 catch( const IO_ERROR& )
2915 {
2916 m_reporter->Report( _( "Failed to fetch schematic netlist for parity tests.\n" ), RPT_SEVERITY_ERROR );
2917 checkParity = false;
2918 }
2919
2920 drcEngine->SetSchematicNetlist( netlist.get() );
2921 }
2922
2923 if( drcJob->m_refillZones )
2924 {
2925 if( !toolManager->FindTool( ZONE_FILLER_TOOL_NAME ) )
2926 toolManager->RegisterTool( new ZONE_FILLER_TOOL );
2927
2928 toolManager->GetTool<ZONE_FILLER_TOOL>()->FillAllZones( nullptr, m_progressReporter, true );
2929 }
2930
2931 drcEngine->SetProgressReporter( m_progressReporter );
2932 drcEngine->SetViolationHandler(
2933 [&]( const std::shared_ptr<DRC_ITEM>& aItem, const VECTOR2I& aPos, int aLayer,
2934 const std::function<void( PCB_MARKER* )>& aPathGenerator )
2935 {
2936 PCB_MARKER* marker = new PCB_MARKER( aItem, aPos, aLayer );
2937 aPathGenerator( marker );
2938 commit.Add( marker );
2939 } );
2940
2941 brd->RecordDRCExclusions();
2942 brd->DeleteMARKERs( true, true );
2943 drcEngine->RunTests( units, drcJob->m_reportAllTrackErrors, checkParity );
2944 drcEngine->ClearViolationHandler();
2945
2946 commit.Push( _( "DRC" ), SKIP_UNDO | SKIP_SET_DIRTY );
2947
2948 // Update the exclusion status on any excluded markers that still exist.
2949 brd->ResolveDRCExclusions( false );
2950
2951 std::shared_ptr<DRC_ITEMS_PROVIDER> markersProvider =
2952 std::make_shared<DRC_ITEMS_PROVIDER>( brd, MARKER_BASE::MARKER_DRC, MARKER_BASE::MARKER_DRAWING_SHEET );
2953
2954 std::shared_ptr<DRC_ITEMS_PROVIDER> ratsnestProvider =
2955 std::make_shared<DRC_ITEMS_PROVIDER>( brd, MARKER_BASE::MARKER_RATSNEST );
2956
2957 std::shared_ptr<DRC_ITEMS_PROVIDER> fpWarningsProvider =
2958 std::make_shared<DRC_ITEMS_PROVIDER>( brd, MARKER_BASE::MARKER_PARITY );
2959
2960 markersProvider->SetSeverities( drcJob->m_severity );
2961 ratsnestProvider->SetSeverities( drcJob->m_severity );
2962 fpWarningsProvider->SetSeverities( drcJob->m_severity );
2963
2964 m_reporter->Report( wxString::Format( _( "Found %d violations\n" ), markersProvider->GetCount() ),
2966 m_reporter->Report( wxString::Format( _( "Found %d unconnected items\n" ), ratsnestProvider->GetCount() ),
2968
2969 if( checkParity )
2970 {
2971 m_reporter->Report(
2972 wxString::Format( _( "Found %d schematic parity issues\n" ), fpWarningsProvider->GetCount() ),
2974 }
2975
2976 DRC_REPORT reportWriter( brd, units, markersProvider, ratsnestProvider, fpWarningsProvider );
2977
2978 bool wroteReport = false;
2979
2981 wroteReport = reportWriter.WriteJsonReport( outPath );
2982 else
2983 wroteReport = reportWriter.WriteTextReport( outPath );
2984
2985 if( !wroteReport )
2986 {
2987 m_reporter->Report( wxString::Format( _( "Unable to save DRC report to %s\n" ), outPath ), RPT_SEVERITY_ERROR );
2989 }
2990
2991 drcJob->AddOutput( outPath );
2992
2993 m_reporter->Report( wxString::Format( _( "Saved DRC Report to %s\n" ), outPath ), RPT_SEVERITY_ACTION );
2994
2995 if( drcJob->m_refillZones && drcJob->m_saveBoard )
2996 {
2997 if( BOARD_LOADER::SaveBoard( drcJob->m_filename, *brd ) )
2998 {
2999 m_reporter->Report( _( "Saved board\n" ), RPT_SEVERITY_ACTION );
3000 }
3001 else
3002 {
3003 m_reporter->Report( _( "Failed to save board.\n" ), RPT_SEVERITY_ERROR );
3004
3006 }
3007 }
3008
3009 if( drcJob->m_exitCodeViolations )
3010 {
3011 if( markersProvider->GetCount() > 0 || ratsnestProvider->GetCount() > 0 || fpWarningsProvider->GetCount() > 0 )
3012 {
3014 }
3015 }
3016
3018}
3019
3020
3022{
3023 if( !aJob.m_checkZonesBeforeExport )
3024 {
3025 return;
3026 }
3027
3028 TOOL_MANAGER* toolManager = getToolManager( aBoard );
3029
3030 if( !toolManager->FindTool( ZONE_FILLER_TOOL_NAME ) )
3031 {
3032 toolManager->RegisterTool( new ZONE_FILLER_TOOL );
3033 }
3034
3035 toolManager->GetTool<ZONE_FILLER_TOOL>()->FillAllZones( nullptr, m_progressReporter, true );
3036}
3037
3038
3040{
3041 JOB_EXPORT_PCB_IPC2581* job = dynamic_cast<JOB_EXPORT_PCB_IPC2581*>( aJob );
3042
3043 if( job == nullptr )
3045
3046 BOARD* brd = getBoard( job->m_filename );
3047
3048 if( !brd )
3050
3051 struct VARIANT_RESTORE
3052 {
3053 BOARD* m_board;
3054 wxString m_variant;
3055
3056 ~VARIANT_RESTORE() { m_board->SetCurrentVariant( m_variant ); }
3057 } restore{ brd, brd->GetCurrentVariant() };
3058
3059 if( !job->m_variantNames.empty() )
3060 brd->SetCurrentVariant( job->m_variantNames.front() );
3061
3062 if( job->GetConfiguredOutputPath().IsEmpty() )
3063 {
3064 wxFileName fn = brd->GetFileName();
3065 fn.SetExt( job->m_compress ? std::string( "zip" ) : FILEEXT::Ipc2581FileExtension );
3066
3067 job->SetWorkingOutputPath( fn.GetFullName() );
3068 }
3069
3070 wxString outPath = resolveJobOutputPath( aJob, brd );
3071
3072 if( !PATHS::EnsurePathExists( outPath, true ) )
3073 {
3074 m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
3076 }
3077
3078 refillFabZones( *job, brd );
3079
3082
3084}
3085
3086
3088{
3089 JOB_EXPORT_PCB_IPCD356* job = dynamic_cast<JOB_EXPORT_PCB_IPCD356*>( aJob );
3090
3091 if( job == nullptr )
3093
3094 BOARD* brd = getBoard( job->m_filename );
3095
3096 if( !brd )
3098
3099 if( job->GetConfiguredOutputPath().IsEmpty() )
3100 {
3101 wxFileName fn = brd->GetFileName();
3102 fn.SetName( fn.GetName() );
3103 fn.SetExt( FILEEXT::IpcD356FileExtension );
3104
3105 job->SetWorkingOutputPath( fn.GetFullName() );
3106 }
3107
3108 wxString outPath = resolveJobOutputPath( aJob, brd );
3109
3110 if( !PATHS::EnsurePathExists( outPath, true ) )
3111 {
3112 m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
3114 }
3115
3116 IPC356D_WRITER exporter( brd );
3117
3118 bool success = exporter.Write( outPath );
3119
3120 if( success )
3121 {
3122 aJob->AddOutput( outPath );
3123 m_reporter->Report( _( "Successfully created IPC-D-356 file\n" ), RPT_SEVERITY_INFO );
3125 }
3126 else
3127 {
3128 m_reporter->Report( _( "Failed to create IPC-D-356 file\n" ), RPT_SEVERITY_ERROR );
3130 }
3131}
3132
3133
3135{
3136 JOB_EXPORT_PCB_IDF* job = dynamic_cast<JOB_EXPORT_PCB_IDF*>( aJob );
3137
3138 if( job == nullptr )
3140
3141 BOARD* brd = getBoard( job->m_filename );
3142
3143 if( !brd )
3145
3146 if( job->GetConfiguredOutputPath().IsEmpty() )
3147 {
3148 wxFileName fn = brd->GetFileName();
3150
3151 job->SetWorkingOutputPath( fn.GetFullName() );
3152 }
3153
3154 wxString outPath = resolveJobOutputPath( job, brd );
3155
3156 if( !PATHS::EnsurePathExists( outPath, true ) )
3157 {
3158 m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
3160 }
3161
3163
3164 if( !resolver )
3165 {
3166 m_reporter->Report( _( "Internal error\n" ), RPT_SEVERITY_ERROR );
3168 }
3169
3170 IDF_EXPORTER exporter( brd, resolver, job, m_reporter );
3171
3172 if( !exporter.Export( outPath ) )
3174
3175 m_reporter->Report( _( "Successfully created IDFv3 files\n" ), RPT_SEVERITY_INFO );
3176
3178}
3179
3180
3182{
3183 JOB_EXPORT_PCB_ODB* job = dynamic_cast<JOB_EXPORT_PCB_ODB*>( aJob );
3184
3185 if( job == nullptr )
3187
3188 BOARD* brd = getBoard( job->m_filename );
3189
3190 if( !brd )
3192
3193 if( job->GetConfiguredOutputPath().IsEmpty() )
3194 {
3196 {
3197 // just basic folder name
3198 job->SetWorkingOutputPath( "odb" );
3199 }
3200 else
3201 {
3202 wxFileName fn( brd->GetFileName() );
3203 fn.SetName( fn.GetName() + wxS( "-odb" ) );
3204
3205 switch( job->m_compressionMode )
3206 {
3208
3209 case JOB_EXPORT_PCB_ODB::ODB_COMPRESSION::TGZ: fn.SetExt( "tgz" ); break;
3210
3211 default: break;
3212 };
3213
3214 job->SetWorkingOutputPath( fn.GetFullName() );
3215 }
3216 }
3217
3218 resolveJobOutputPath( job, brd );
3219
3220 // The helper handles output path creation, so hand it a job that already has fully-resolved
3221 // token context (title block and project overrides applied above).
3222 refillFabZones( *job, brd );
3223
3225
3226 if( result.m_ok )
3227 {
3228 for( const wxString& path : result.m_outputs )
3229 aJob->AddOutput( path );
3230 }
3231
3232 if( !result.m_ok || m_reporter->HasMessageOfSeverity( RPT_SEVERITY_ERROR ) )
3234
3236}
3237
3239{
3240 JOB_PCB_UPGRADE* job = dynamic_cast<JOB_PCB_UPGRADE*>( aJob );
3241
3242 if( job == nullptr )
3244
3245 bool shouldSave = job->m_force;
3246
3247 try
3248 {
3250 BOARD* brd = getBoard( job->m_filename );
3252 shouldSave = true;
3253
3254 // A chart is derived data, so the upgrade brings every one up to date and saves if
3255 // that changed anything
3256 if( RefreshGeneratedTables( *brd ) > 0 )
3257 shouldSave = true;
3258
3259 if( shouldSave )
3260 {
3261 pi->SaveBoard( brd->GetFileName(), *brd );
3262 m_reporter->Report( _( "Successfully saved board file using the latest format\n" ), RPT_SEVERITY_INFO );
3263 }
3264 else
3265 {
3266 m_reporter->Report( _( "Board file was not updated\n" ), RPT_SEVERITY_ERROR );
3267 }
3268 }
3269 catch( const IO_ERROR& ioe )
3270 {
3271 wxString msg =
3272 wxString::Format( _( "Error saving board file '%s'.\n%s" ), job->m_filename, ioe.What().GetData() );
3273 m_reporter->Report( msg, RPT_SEVERITY_ERROR );
3275 }
3276
3278}
3279
3280// Most job handlers need to align the running job with the board before resolving any
3281// output paths with variables in them like ${REVISION}.
3282wxString PCBNEW_JOBS_HANDLER::resolveJobOutputPath( JOB* aJob, BOARD* aBoard, const wxString* aDrawingSheet )
3283{
3284 aJob->SetTitleBlock( aBoard->GetTitleBlock() );
3285
3286 if( aDrawingSheet && !aDrawingSheet->IsEmpty() )
3287 loadOverrideDrawingSheet( aBoard, *aDrawingSheet );
3288
3289 PROJECT* project = aBoard->GetProject();
3290
3291 if( project )
3292 project->ApplyTextVars( aJob->GetVarOverrides() );
3293
3294 aBoard->SynchronizeProperties();
3295
3296 return aJob->GetFullOutputPath( project );
3297}
3298
3299
3301{
3302 DS_PROXY_VIEW_ITEM* drawingSheet = new DS_PROXY_VIEW_ITEM( pcbIUScale, &aBrd->GetPageSettings(), aBrd->GetProject(),
3303 &aBrd->GetTitleBlock(), &aBrd->GetProperties() );
3304
3305 drawingSheet->SetSheetName( std::string() );
3306 drawingSheet->SetSheetPath( std::string() );
3307 drawingSheet->SetIsFirstPage( true );
3308
3309 drawingSheet->SetFileName( TO_UTF8( aBrd->GetFileName() ) );
3310
3311 wxString currentVariant = aBrd->GetCurrentVariant();
3312 wxString variantDesc = aBrd->GetVariantDescription( currentVariant );
3313 drawingSheet->SetVariantName( TO_UTF8( currentVariant ) );
3314 drawingSheet->SetVariantDesc( TO_UTF8( variantDesc ) );
3315
3316 return drawingSheet;
3317}
3318
3319
3320void PCBNEW_JOBS_HANDLER::loadOverrideDrawingSheet( BOARD* aBrd, const wxString& aSheetPath )
3321{
3322 // dont bother attempting to load a empty path, if there was one
3323 if( aSheetPath.IsEmpty() )
3324 return;
3325
3326 auto loadSheet = [&]( const wxString& path ) -> bool
3327 {
3329 FILENAME_RESOLVER resolver;
3330 resolver.SetProject( aBrd->GetProject() );
3331 resolver.SetProgramBase( &Pgm() );
3332
3333 wxString filename = resolver.ResolvePath( BASE_SCREEN::m_DrawingSheetFileName,
3334 aBrd->GetProject()->GetProjectPath(), { aBrd->GetEmbeddedFiles() } );
3335 wxString msg;
3336
3337 if( !DS_DATA_MODEL::GetTheInstance().LoadDrawingSheet( filename, &msg ) )
3338 {
3339 m_reporter->Report( wxString::Format( _( "Error loading drawing sheet '%s'." ), path ) + wxS( "\n" ) + msg
3340 + wxS( "\n" ),
3342 return false;
3343 }
3344
3345 return true;
3346 };
3347
3348 if( loadSheet( aSheetPath ) )
3349 return;
3350
3351 // failed loading custom path, revert back to default
3352 loadSheet( aBrd->GetProject()->GetProjectFile().m_BoardDrawingSheetFile );
3353}
3354
3355
3356// Resolve a KiCad layer name (canonical board-file name such as "F.Cu", or the GUI display name)
3357// to its layer id. Returns UNDEFINED_LAYER when no layer matches.
3358static PCB_LAYER_ID resolveKiCadLayerName( const wxString& aName )
3359{
3360 for( PCB_LAYER_ID layer : LSET::AllLayersMask().Seq() )
3361 {
3362 if( LSET::Name( layer ) == aName || LayerName( layer ) == aName )
3363 return layer;
3364 }
3365
3366 return UNDEFINED_LAYER;
3367}
3368
3369
3371{
3372 JOB_PCB_IMPORT* job = dynamic_cast<JOB_PCB_IMPORT*>( aJob );
3373
3374 if( !job )
3376
3377 // Check that input file exists
3378 if( !wxFile::Exists( job->m_inputFile ) )
3379 {
3380 m_reporter->Report( wxString::Format( _( "Input file not found: '%s'\n" ),
3381 job->m_inputFile ),
3384 }
3385
3386 // Map job format to PCB_IO file type
3388
3389 switch( job->m_format )
3390 {
3392
3393 // "pads" names the vendor format, not the dialect: PADS writes both an ASCII export and a
3394 // binary PowerPCB database, and both use the .pcb extension. Resolve the dialect by content
3395 // so an explicit --format pads on a binary file imports it instead of handing it to the ASCII
3396 // reader, which parses nothing and silently yields an empty board.
3401 break;
3402
3404
3406
3408
3410
3412
3414
3416 }
3417
3418 // FindPluginTypeFromBoardPath returns FILE_TYPE_NONE (not PCB_FILE_UNKNOWN) when no plugin
3419 // claims the file. Quiet sentinel: lets the top-level `import` command try the schematic face.
3421 {
3422 m_reporter->Report( wxString::Format( _( "No PCB importer recognizes the file format of "
3423 "'%s'\n" ),
3424 job->m_inputFile ),
3427 }
3428
3429 if( job->m_probeOnly )
3430 {
3432
3433 if( !pi )
3434 {
3435 m_reporter->Report( wxString::Format( _( "No plugin found for file type '%s'\n" ),
3439 }
3440
3441 try
3442 {
3443 job->m_projectBoards = pi->EnumerateProjectBoards( job->m_inputFile );
3444 }
3445 catch( const IO_ERROR& ioe )
3446 {
3447 m_reporter->Report( wxString::Format( _( "Failed to inspect board project: %s\n" ), ioe.What() ),
3450 }
3451
3453 }
3454
3455
3456 // Determine output path
3457 wxString outputPath = job->GetConfiguredOutputPath();
3458
3459 if( outputPath.IsEmpty() )
3461
3462 // The generated footprint library and its table row belong to the *active* project, so an
3463 // import with no project loaded needs a transient active one at the output location (never
3464 // written to disk; LoadProject returns false yet still registers it, hence the GetProject()
3465 // check).
3467 PROJECT* projectPtr = nullptr;
3468 bool createdTransientProject = false;
3469
3470 if( mgr.IsProjectOpenNotDummy() )
3471 {
3472 projectPtr = &mgr.Prj();
3473 }
3474 else
3475 {
3476 wxFileName projectFn( outputPath );
3477 projectFn.SetExt( FILEEXT::ProjectFileExtension );
3478
3479 // PROJECT requires an absolute path; -o may be relative to the working directory
3480 projectFn.MakeAbsolute();
3481
3482 mgr.LoadProject( projectFn.GetFullPath(), true );
3483 projectPtr = mgr.GetProject( projectFn.GetFullPath() );
3484 createdTransientProject = ( projectPtr != nullptr );
3485 }
3486
3487 if( !projectPtr )
3488 {
3489 m_reporter->Report( _( "Could not establish a project for the import\n" ),
3492 }
3493
3494 // unloads the transient project on every exit path
3495 struct TRANSIENT_PROJECT_GUARD
3496 {
3497 SETTINGS_MANAGER& m_mgr;
3498 PROJECT* m_project;
3499 bool m_active;
3500
3501 ~TRANSIENT_PROJECT_GUARD()
3502 {
3503 if( m_active )
3504 m_mgr.UnloadProject( m_project, false );
3505 }
3506 } transientProjectGuard{ mgr, projectPtr, createdTransientProject };
3507
3508 std::unique_ptr<BOARD> board;
3509
3510 struct BOARD_PROJECT_GUARD
3511 {
3512 std::unique_ptr<BOARD>& board;
3513
3514 ~BOARD_PROJECT_GUARD()
3515 {
3516 if( board )
3517 board->ClearProject();
3518 }
3519 } boardProjectGuard{ board };
3520
3521 wxString formatName = PCB_IO_MGR::ShowType( fileType );
3522 std::vector<wxString> warnings;
3523
3524 // Real source-to-KiCad layer decisions, captured by our mapping callback so the report can
3525 // show them and so explicit overrides can be validated.
3526 struct CAPTURED_LAYER
3527 {
3528 wxString m_source;
3529 PCB_LAYER_ID m_target;
3530 wxString m_method;
3531 };
3532
3533 std::vector<CAPTURED_LAYER> capturedLayers;
3534 std::set<wxString> seenSourceLayers;
3535 bool layersCaptured = false;
3536
3537 try
3538 {
3539 // Declared before the plugin so it outlives the plugin's pointer to it
3540 IMPORT_MESSAGE_COLLECTOR importMessages( *m_reporter );
3542
3543 if( !pi )
3544 {
3545 m_reporter->Report( wxString::Format( _( "No plugin found for file type '%s'\n" ), formatName ),
3548 }
3549
3550 // Without this the importer's warnings never reach the import report
3551 pi->SetReporter( &importMessages );
3552
3553 // Replace the plugin's default best-guess callback so we can apply explicit overrides and
3554 // capture the resulting mapping. Only mappable importers expose their source layers; for
3555 // others the report falls back to listing the imported board's enabled layers.
3556 if( LAYER_MAPPABLE_PLUGIN* mappable = dynamic_cast<LAYER_MAPPABLE_PLUGIN*>( pi.get() ) )
3557 {
3558 if( !job->m_layerMap.empty() || job->m_reportFormat != IMPORT_REPORT_FORMAT::NONE )
3559 {
3560 mappable->RegisterCallback(
3561 [&]( const std::vector<INPUT_LAYER_DESC>& aDescs )
3562 -> std::map<wxString, PCB_LAYER_ID>
3563 {
3564 std::map<wxString, PCB_LAYER_ID> result;
3565
3566 for( const INPUT_LAYER_DESC& desc : aDescs )
3567 {
3568 PCB_LAYER_ID target = desc.AutoMapLayer;
3569 wxString method = wxS( "auto" );
3570
3571 if( auto it = job->m_layerMap.find( desc.Name );
3572 it != job->m_layerMap.end() )
3573 {
3574 PCB_LAYER_ID resolved = resolveKiCadLayerName( it->second );
3575
3576 if( resolved == UNDEFINED_LAYER )
3577 {
3578 warnings.push_back( wxString::Format(
3579 _( "Layer map entry '%s' -> '%s' names an unknown "
3580 "KiCad layer; using automatic mapping instead" ),
3581 desc.Name, it->second ) );
3582 }
3583 else if( !desc.PermittedLayers.Contains( resolved ) )
3584 {
3585 warnings.push_back( wxString::Format(
3586 _( "Layer map entry '%s' -> '%s' is not a permitted "
3587 "target for this layer; using automatic mapping "
3588 "instead" ),
3589 desc.Name, it->second ) );
3590 }
3591 else
3592 {
3593 target = resolved;
3594 method = wxS( "explicit" );
3595 }
3596 }
3597
3598 if( desc.Required && target == UNDEFINED_LAYER )
3599 {
3600 warnings.push_back( wxString::Format(
3601 _( "No KiCad layer mapping for required source layer "
3602 "'%s'; its items will not be imported" ),
3603 desc.Name ) );
3604 }
3605
3606 result.emplace( desc.Name, target );
3607 capturedLayers.push_back( { desc.Name, target, method } );
3608 seenSourceLayers.insert( desc.Name );
3609 }
3610
3611 layersCaptured = true;
3612 return result;
3613 } );
3614 }
3615 }
3616 else if( !job->m_layerMap.empty() )
3617 {
3618 warnings.push_back( _( "A layer map was provided, but this importer does not support "
3619 "layer remapping; it will be ignored" ) );
3620 }
3621
3622 m_reporter->Report(
3623 wxString::Format( _( "Importing '%s' using %s format...\n" ), job->m_inputFile, formatName ),
3625
3626 // LoadBoard reports load failures and user cancellations by throwing.
3627 try
3628 {
3629 // pcb_id selects a board only within an EasyEDA Pro project. Other importers must
3630 // continue receiving no properties so their existing design-settings import paths remain
3631 // unchanged.
3632 const std::map<std::string, UTF8>* importProperties = nullptr;
3633 std::map<std::string, UTF8> easyEdaProProperties;
3634
3635 if( !job->m_importPcbId.empty()
3637 {
3638 easyEdaProProperties.emplace( "pcb_id", std::string( job->m_importPcbId.ToUTF8() ) );
3639 importProperties = &easyEdaProProperties;
3640 }
3641
3642 board = pi->LoadBoard( job->m_inputFile, importProperties, projectPtr );
3643 }
3644 catch( const IO_CANCELLED& ioce )
3645 {
3646 // We should not be here, as the plugin should not have used an interactive dialog
3647 // in the CLI context.
3648 // But technically the file is not invalid
3649 m_reporter->Report( wxString::Format( _( "Unexpected cancellation: %s\n" ), ioce.What() ),
3652 }
3653 catch( const IO_ERROR& ioe )
3654 {
3655 m_reporter->Report( wxString::Format( _( "Failed to load board: %s\n" ), ioe.What() ), RPT_SEVERITY_ERROR );
3657 }
3658 board->SetProject( projectPtr, job->m_isPartOfMultiBoardProject );
3659
3660
3661
3662 if( !ApplyImportedNetNameMap( *board, job->m_netNameMap, *m_reporter ) )
3664
3665 // Extract a project footprint library and re-link FPIDs, as the board editor does.
3666 // Each EasyEDA Pro project board needs a distinct cache; using the archive name makes
3667 // every board compete to publish the same .pretty directory.
3669 {
3670 const wxString reconciliationPath =
3671 job->m_importPcbId.empty() ? job->m_inputFile : outputPath;
3672 ReconcileImportedFootprints( *pi, *board, *projectPtr, reconciliationPath, nullptr,
3673 *m_reporter );
3674 }
3675
3676 // Flag explicit map entries that never matched a source layer so typos do not pass silently.
3677 if( layersCaptured )
3678 {
3679 for( const auto& [source, target] : job->m_layerMap )
3680 {
3681 if( !seenSourceLayers.contains( source ) )
3682 {
3683 warnings.push_back( wxString::Format(
3684 _( "Layer map entry '%s' does not match any source layer in the "
3685 "imported file; it will be ignored" ),
3686 source ) );
3687 }
3688 }
3689 }
3690
3691 // Save as KiCad format
3692
3694 kicadPlugin->SaveBoard( outputPath, *board );
3695 wxString importedRules = pi->GetImportedDesignRules();
3696
3697 if( !importedRules.IsEmpty() )
3698 {
3699 wxFileName rulesPath( outputPath );
3700 rulesPath.SetExt( FILEEXT::DesignRulesFileExtension );
3701
3702 wxFile rulesFile( rulesPath.GetFullPath(), wxFile::write );
3703
3704 if( rulesFile.IsOpened() )
3705 {
3706 rulesFile.Write( importedRules );
3707 }
3708 else
3709 {
3710 m_reporter->Report( wxString::Format( _( "Could not write imported design rules to '%s'\n" ),
3711 rulesPath.GetFullPath() ),
3713 }
3714 }
3715
3716 m_reporter->Report( wxString::Format( _( "Successfully saved imported board to '%s'\n" ), outputPath ),
3718
3719 // Generate report if requested
3720 IMPORT_REPORT_DATA reportData;
3721 importMessages.Flush( job->m_reportFormat, reportData );
3722
3724 {
3725 reportData.m_sourceFile = wxFileName( job->m_inputFile ).GetFullName();
3726 reportData.m_sourceFormat = formatName;
3727 reportData.m_outputFile = wxFileName( outputPath ).GetFullName();
3728
3729 size_t trackCount = 0;
3730 size_t viaCount = 0;
3731
3732 for( PCB_TRACK* track : board->Tracks() )
3733 {
3734 if( track->Type() == PCB_VIA_T )
3735 viaCount++;
3736 else
3737 trackCount++;
3738 }
3739
3740 reportData.m_statistics = {
3741 { wxS( "footprints" ), board->Footprints().size() },
3742 { wxS( "tracks" ), trackCount },
3743 { wxS( "vias" ), viaCount },
3744 { wxS( "zones" ), board->Zones().size() }
3745 };
3746
3747 // Build layer mapping info, carried only in the JSON report. Prefer the real
3748 // source-to-KiCad decisions captured during load; fall back to the imported board's
3749 // enabled layers for importers that do not expose a mappable layer set.
3750 nlohmann::json layerMappings = nlohmann::json::object();
3751
3752 if( layersCaptured )
3753 {
3754 for( const CAPTURED_LAYER& mapped : capturedLayers )
3755 {
3756 std::string kicadLayer = mapped.m_target == UNDEFINED_LAYER
3757 ? std::string()
3758 : LSET::Name( mapped.m_target ).ToStdString();
3759
3760 layerMappings[mapped.m_source.ToStdString()] = {
3761 { "kicad_layer", kicadLayer },
3762 { "method", mapped.m_method.ToStdString() }
3763 };
3764 }
3765 }
3766 else
3767 {
3768 for( PCB_LAYER_ID layer : board->GetEnabledLayers().Seq() )
3769 {
3770 wxString layerName = board->GetLayerName( layer );
3771
3772 layerMappings[layerName.ToStdString()] = {
3773 { "kicad_layer", LSET::Name( layer ).ToStdString() },
3774 { "method", "auto" }
3775 };
3776 }
3777 }
3778
3779 reportData.m_extraJson["layer_mapping"] = layerMappings;
3780 reportData.m_warnings.insert( reportData.m_warnings.end(), warnings.begin(), warnings.end() );
3781
3782 if( !WriteImportReport( m_reporter, job->m_reportFormat, job->m_reportFile, reportData ) )
3784 }
3785 else
3786 {
3787 // No report requested, but explicit-mapping problems still need to surface.
3788 for( const wxString& warning : warnings )
3789 m_reporter->Report( warning + wxS( "\n" ), RPT_SEVERITY_WARNING );
3790 }
3791 }
3792 catch( const IO_ERROR& ioe )
3793 {
3794 m_reporter->Report( wxString::Format( _( "Error during import: %s\n" ), ioe.What() ), RPT_SEVERITY_ERROR );
3795
3797 }
3798
3800}
3801
3802
3803// ============================================================================
3804// JobDiff: pcb_diff implementation
3805// ============================================================================
3808#include <diff_merge/diff_scene.h>
3809#include <diff_merge/pcb_differ.h>
3811#include <jobs/job_pcb_diff.h>
3812
3813
3814// Load a board into a SCRATCH_DOC<BOARD> that keeps its project attached for
3815// the document's lifetime — the differ/applier read PROJECT_FILE-scoped fields
3816// (drawing-sheet path, DRC severities, net classes). The destructor severs the
3817// BOARD->project link in the right order. Used by every PCB diff/merge job.
3818static SCRATCH_DOC<BOARD> loadScratchBoard( SETTINGS_MANAGER& aMgr, const wxString& aPath,
3819 bool aInitializeAfterLoad = true )
3820{
3821 return LoadScratchDoc<BOARD>(
3822 aMgr, aPath,
3823 [aPath, aInitializeAfterLoad]( PROJECT* aProject ) -> std::unique_ptr<BOARD>
3824 {
3825 PCB_IO_MGR::PCB_FILE_T pluginType =
3827
3828 if( !aProject || pluginType == PCB_IO_MGR::FILE_TYPE_NONE )
3829 return nullptr;
3830
3832 opts.initialize_after_load = aInitializeAfterLoad;
3833
3834 try
3835 {
3836 return BOARD_LOADER::Load( aPath, pluginType, aProject, opts );
3837 }
3838 catch( ... )
3839 {
3840 return nullptr;
3841 }
3842 },
3843 []( BOARD* aBoard )
3844 {
3845 if( aBoard )
3846 aBoard->ClearProject();
3847 } );
3848}
3849
3850
3852{
3853 JOB_PCB_DIFF* diffJob = dynamic_cast<JOB_PCB_DIFF*>( aJob );
3854
3855 if( !diffJob )
3857
3858 // SCRATCH_DOC<BOARD> keeps each board's project attached for the lifetime
3859 // of the diff, which the differ needs to read project-file-scoped fields
3860 // (m_BoardDrawingSheetFile, etc). The previous loadStandaloneBoard +
3861 // ClearProject-up-front path would null those out before the differ ran.
3863
3864 SCRATCH_DOC<BOARD> aScratch = loadScratchBoard( diffMgr, diffJob->m_inputA );
3865 SCRATCH_DOC<BOARD> bScratch = loadScratchBoard( diffMgr, diffJob->m_inputB );
3866
3867 BOARD* boardA = aScratch.doc.get();
3868 BOARD* boardB = bScratch.doc.get();
3869
3870 if( !boardA )
3871 {
3872 m_reporter->Report( wxString::Format( _( "Failed to load %s\n" ), diffJob->m_inputA ), RPT_SEVERITY_ERROR );
3874 }
3875
3876 if( !boardB )
3877 {
3878 m_reporter->Report( wxString::Format( _( "Failed to load %s\n" ), diffJob->m_inputB ), RPT_SEVERITY_ERROR );
3880 }
3881
3882 KICAD_DIFF::PCB_DIFFER differ( boardA, boardB, diffJob->m_inputB );
3884
3885 int diffExitCode = KICAD_DIFF::DiffExitCode( result );
3886
3887 if( diffJob->m_exitCodeOnly )
3888 return diffExitCode;
3889
3890 // The board geometry rendered beneath the change overlay (PNG/SVG only)
3891 // matches what the interactive dialog draws.
3893 KICAD_DIFF::MakeEmitOptions( *diffJob, diffJob->m_inputA, diffJob->m_inputB );
3895 emitOpts.referenceGeometry = [&]( const KIGFX::COLOR4D& aColor )
3896 { return KICAD_DIFF::ExtractBoardGeometry( *boardA, aColor ); };
3897 emitOpts.comparisonGeometry = [&]( const KIGFX::COLOR4D& aColor )
3898 { return KICAD_DIFF::ExtractBoardGeometry( *boardB, aColor ); };
3899
3900 return KICAD_DIFF::EmitDiffResult( result, emitOpts, diffExitCode, *m_reporter );
3901}
3902
3903
3904// ============================================================================
3905// JobMerge: pcb_merge implementation
3906// ============================================================================
3911
3912
3913int PCBNEW_JOBS_HANDLER::RunMerge( KICAD_DIFF::DOC_KIND aKind, const wxString& aAncestor,
3914 const wxString& aOurs, const wxString& aTheirs,
3915 const wxString& aOutput, bool aInteractive, bool aSingleFile,
3916 REPORTER* aReporter )
3917{
3918 // Restore m_reporter on scope exit so a caller's transient (often
3919 // stack-local) reporter doesn't outlive this call as a dangling member.
3921 aReporter ? aReporter : m_reporter );
3922
3924 return runFpLibMerge( aAncestor, aOurs, aTheirs, aOutput, aSingleFile );
3925
3926 return runPcbMerge( aAncestor, aOurs, aTheirs, aOutput, aInteractive );
3927}
3928
3929
3930int PCBNEW_JOBS_HANDLER::runPcbMerge( const wxString& aAncestor, const wxString& aOurs,
3931 const wxString& aTheirs, const wxString& aOutput,
3932 bool aInteractive )
3933{
3934 // Use SCRATCH_DOC<BOARD> so each input keeps its project attached for the
3935 // life of the merge — necessary for any doc-level resolution that mutates
3936 // PROJECT_FILE-scoped state (DRC severities, net classes) and needs to be
3937 // saved as a sibling .kicad_pro. SCRATCH_DOC's destructor severs the
3938 // BOARD->project link in the right order and unloads the project from
3939 // the manager, avoiding the dangling-PROJECT_FILE::m_BoardSettings pointer
3940 // the previous up-front-ClearProject loadStandaloneBoard path had to
3941 // guard against.
3943
3944 SCRATCH_DOC<BOARD> ancestorScratch = loadScratchBoard( mgr, aAncestor );
3945 SCRATCH_DOC<BOARD> oursScratch = loadScratchBoard( mgr, aOurs );
3946 SCRATCH_DOC<BOARD> theirsScratch = loadScratchBoard( mgr, aTheirs );
3947
3948 BOARD* ancestor = ancestorScratch.doc.get();
3949 BOARD* ours = oursScratch.doc.get();
3950 BOARD* theirs = theirsScratch.doc.get();
3951
3952 if( !ancestor || !ours || !theirs )
3953 {
3954 m_reporter->Report( _( "Failed to load one or more input boards\n" ), RPT_SEVERITY_ERROR );
3956 }
3957
3958 KICAD_DIFF::PCB_DIFFER ourDiff( ancestor, ours );
3959 KICAD_DIFF::PCB_DIFFER theirDiff( ancestor, theirs );
3960
3961 KICAD_DIFF::DOCUMENT_DIFF ourDocDiff = ourDiff.Diff();
3962 KICAD_DIFF::DOCUMENT_DIFF theirDocDiff = theirDiff.Diff();
3963
3965 KICAD_DIFF::MERGE_PLAN plan = engine.Plan( ourDocDiff, theirDocDiff );
3966
3967 // A cancelled dialog leaves plan unresolved and falls through to the
3968 // marker flow below.
3969 if( aInteractive && !plan.Resolved() )
3970 {
3971 if( !Pgm().IsGUI() )
3972 {
3973 m_reporter->Report( _( "--interactive requires a GUI KiCad process; the console "
3974 "kicad-cli cannot open dialogs.\n" ),
3977 }
3978
3979 // Geometry context so the conflict viewer can render the actual
3980 // boards behind the conflict bbox highlight.
3981 const KICAD_DIFF::DIFF_COLOR_THEME theme;
3986
3987 // Build per-side bbox lookups so a "moved on theirs" item highlights
3988 // at its theirs-side coordinates when the user previews Theirs.
3990 KICAD_DIFF::CollectChangeBBoxes( theirDocDiff, ctx.theirsBBoxes );
3991
3992 DIALOG_KICAD_MERGE_3WAY dlg( wxTheApp->GetTopWindow(), plan, std::move( ctx ) );
3993
3994 if( dlg.ShowModal() == wxID_APPLY )
3995 plan = dlg.GetResolvedPlan();
3996 }
3997
3998 // Snapshot of the plan before the applier moves it; drives the
3999 // unresolved-conflict report below.
4000 const KICAD_DIFF::MERGE_PLAN planSnapshot = plan;
4001
4002 KICAD_DIFF::PCB_MERGE_APPLIER applier( ancestor, ours, theirs, std::move( plan ) );
4003 std::unique_ptr<BOARD> merged = applier.Apply();
4004
4005 if( !merged )
4006 {
4007 m_reporter->Report( _( "Merge applier failed to produce a board\n" ), RPT_SEVERITY_ERROR );
4009 }
4010
4011 // Serialize to the output path using the canonical PCB IO.
4012 PCB_IO_KICAD_SEXPR pcbIO;
4013
4014 try
4015 {
4016 pcbIO.SaveBoard( aOutput, *merged );
4017 }
4018 catch( const IO_ERROR& ioe )
4019 {
4020 m_reporter->Report( wxString::Format( _( "Failed to save merged board: %s\n" ), ioe.What() ),
4023 }
4024
4025 // BOARD_DESIGN_SETTINGS fields like m_DRCSeverities serialize to
4026 // .kicad_pro, not .kicad_pcb. Mirror only those specific fields onto
4027 // ancestor (still linked to its project via BOARD::SetProject) then
4028 // save ancestor's project alongside the merged board file. Whole-
4029 // BOARD_DESIGN_SETTINGS copy would alias shared_ptr<NET_SETTINGS>
4030 // across BOARDs and crash on ClearProject during SCRATCH_DOC release;
4031 // single-field mirror avoids that.
4032 if( applier.GetReport().projectFileTouched && ancestor && ancestor->GetProject() )
4033 {
4034 ancestor->GetDesignSettings().m_DRCSeverities = merged->GetDesignSettings().m_DRCSeverities;
4035
4036 // Mirror net settings (the applier copied them onto the result via
4037 // NET_SETTINGS::CopyFrom; do the same here to ancestor, which still
4038 // owns the project's nested-settings registration so SaveProjectCopy
4039 // walks the right entry).
4040 if( ancestor->GetDesignSettings().m_NetSettings && merged->GetDesignSettings().m_NetSettings )
4041 {
4042 ancestor->GetDesignSettings().m_NetSettings->CopyFrom( *merged->GetDesignSettings().m_NetSettings );
4043 }
4044
4045 // The applier stages drawing-sheet resolutions on the report (the
4046 // result BOARD is project-less). Mirror onto ancestor's project here
4047 // before SaveProjectCopy walks the PROJECT_FILE.
4048 if( applier.GetReport().drawingSheetFileSet )
4049 {
4051 }
4052
4053 wxFileName proFn( aOutput );
4054 proFn.SetExt( FILEEXT::ProjectFileExtension );
4055
4056 // JSON-patch path: flush ancestor's in-memory project to its JSON
4057 // cache, then patch only the diffed DOC_PROP fields onto the output
4058 // file. This preserves any non-diffed fields the user had at the
4059 // output path (text variables, last paths, layer presets etc.) that
4060 // a full SaveProjectCopy would silently overwrite.
4061 PROJECT_FILE& ancProj = ancestor->GetProject()->GetProjectFile();
4062 ancProj.Store();
4063
4064 const KICAD_DIFF::PCB_MERGE_APPLIER::REPORT& mergeReport = applier.GetReport();
4065
4066 // PROJECT_FILE::Store() flushes the project file's own params but not
4067 // its registered NESTED_SETTINGS. Flush only the nested settings the
4068 // merge resolved so the surgical patch does not overwrite unrelated
4069 // project subtrees.
4070 if( mergeReport.drcSeveritiesTouched )
4071 ancestor->GetDesignSettings().SaveToFile( wxEmptyString, true );
4072
4073 if( mergeReport.netClassesTouched && ancestor->GetDesignSettings().m_NetSettings )
4074 ancestor->GetDesignSettings().m_NetSettings->SaveToFile( wxEmptyString, true );
4075
4076 std::set<wxString> touched;
4077 if( mergeReport.drcSeveritiesTouched )
4078 touched.insert( KICAD_DIFF::DOC_PROP_DRC_SEVERITIES );
4079
4080 if( mergeReport.netClassesTouched )
4081 touched.insert( KICAD_DIFF::DOC_PROP_NET_CLASSES );
4082
4083 if( applier.GetReport().drawingSheetFileSet )
4084 touched.insert( KICAD_DIFF::DOC_PROP_DRAWING_SHEET );
4085
4086 if( !KICAD_DIFF::ApplyProjectFilePatches( proFn.GetFullPath(), *ancProj.Internals(), touched ) )
4087 {
4088 // Patch failed (existing output unparseable or write error).
4089 // Fall back to the legacy full-copy path so the user still gets
4090 // a project file even if it overwrites non-diffed customisations.
4091 if( !mgr.SaveProjectCopy( proFn.GetFullPath(), ancestor->GetProject() ) )
4092 {
4093 m_reporter->Report(
4094 wxString::Format( _( "Failed to save merged project file: %s\n" ), proFn.GetFullPath() ),
4097 }
4098 }
4099
4100 // Write a project-dir sibling file from staged report content. Empty
4101 // content removes the file so a TAKE_ANCESTOR resolution against an
4102 // ancestor with no file clears stale content at the output path.
4103 auto writeStagedFile = [&]( const wxString& aPath, const wxString& aContent, const wxString& aLabel ) -> bool
4104 {
4105 if( aContent.IsEmpty() )
4106 {
4107 if( wxFileExists( aPath ) )
4108 wxRemoveFile( aPath );
4109
4110 return true;
4111 }
4112
4113 wxFile out;
4114
4115 if( !out.Open( aPath, wxFile::write ) || !out.Write( aContent ) )
4116 {
4117 m_reporter->Report( wxString::Format( _( "Failed to save merged %s: %s\n" ), aLabel, aPath ),
4119 return false;
4120 }
4121
4122 return true;
4123 };
4124
4125 // Custom DRC rules: write the applier's staged content next to the
4126 // merged board so the chosen side's rules apply at next DRC run.
4127 if( applier.GetReport().customDrcRulesSet )
4128 {
4129 wxFileName druFn( aOutput );
4130 druFn.SetExt( FILEEXT::DesignRulesFileExtension );
4131
4132 if( !writeStagedFile( druFn.GetFullPath(), applier.GetReport().customDrcRules, _( "custom DRC rules" ) ) )
4133 {
4135 }
4136 }
4137
4138 // Footprint / symbol library tables: write into the merged project
4139 // directory. Both files have no extension.
4140 if( applier.GetReport().fpLibTableSet )
4141 {
4142 wxFileName fpFn( aOutput );
4143 fpFn.SetFullName( wxString::FromUTF8( FILEEXT::FootprintLibraryTableFileName ) );
4144
4145 if( !writeStagedFile( fpFn.GetFullPath(), applier.GetReport().fpLibTable, _( "footprint library table" ) ) )
4146 {
4148 }
4149 }
4150
4151 if( applier.GetReport().symLibTableSet )
4152 {
4153 wxFileName symFn( aOutput );
4154 symFn.SetFullName( wxString::FromUTF8( FILEEXT::SymbolLibraryTableFileName ) );
4155
4156 if( !writeStagedFile( symFn.GetFullPath(), applier.GetReport().symLibTable, _( "symbol library table" ) ) )
4157 {
4159 }
4160 }
4161 }
4162
4163 // Surface post-apply validator findings (refdes collisions, schema
4164 // mismatch, missed connectivity rebuild). Advisory — they do not change the
4165 // exit code, only the merge's resolved/unresolved status does.
4167 m_reporter->Report( wxString::Format( wxS( "%s: %s\n" ), f.validator, f.message ), f.severity );
4168
4169 // The merged board was written to m_outputPath above, so the output is
4170 // always a valid file. Unresolved conflicts are reported and signalled via
4171 // the exit code; the user resolves them with the interactive mergetool.
4172 if( !planSnapshot.Resolved() )
4173 {
4174 m_reporter->Report( wxString::Format( _( "Merge completed with %zu unresolved conflict(s) in %s\n" ),
4175 planSnapshot.ConflictCount(), aOutput ),
4178 }
4179
4181}
4182
4183
4184// ============================================================================
4185// JobFpDiff: fp_diff implementation
4186// ============================================================================
4188#include <jobs/job_fp_diff.h>
4189
4190
4191// Load one side of a footprint-library diff into its owner vector and name map.
4192// When aAllowEmpty is set an empty path resolves to a clean (empty) side; the
4193// non-interactive job path leaves it unset so a missing path is an input error.
4194static int loadFootprintLibrarySide( const wxString& aPath,
4195 std::vector<std::unique_ptr<FOOTPRINT>>& aOwners,
4196 KICAD_DIFF::FP_LIB_DIFFER::FOOTPRINT_MAP& aMap, bool aAllowEmpty,
4197 REPORTER& aReporter )
4198{
4199 if( aAllowEmpty && aPath.IsEmpty() )
4201
4202 try
4203 {
4204 auto loaded = KICAD_DIFF::FP_LIB_DIFFER::LoadLibrary( aPath );
4205 aOwners = std::move( loaded.first );
4206 aMap = std::move( loaded.second );
4208 }
4209 catch( const IO_ERROR& ioe )
4210 {
4211 aReporter.Report( wxString::Format( _( "Failed to load %s: %s\n" ), aPath, ioe.What() ),
4213 }
4214 catch( const std::exception& e )
4215 {
4216 aReporter.Report(
4217 wxString::Format( _( "Failed to load %s: %s\n" ), aPath, wxString::FromUTF8( e.what() ) ),
4219 }
4220
4222}
4223
4224
4225// Flatten a footprint-library name map into a single DOCUMENT_GEOMETRY tinted
4226// with the supplied per-side theme colour.
4229{
4231
4232 for( const auto& [name, footprint] : aMap )
4233 {
4234 if( footprint )
4235 KICAD_DIFF::AppendGeometry( geometry, KICAD_DIFF::ExtractFootprintGeometry( *footprint, aColor ) );
4236 }
4237
4238 return geometry;
4239}
4240
4241
4243{
4244 JOB_FP_DIFF* diffJob = dynamic_cast<JOB_FP_DIFF*>( aJob );
4245
4246 if( !diffJob )
4248
4249 wxFileName dirA( diffJob->m_inputA );
4250 dirA.MakeAbsolute();
4251 wxFileName dirB( diffJob->m_inputB );
4252 dirB.MakeAbsolute();
4253
4254 std::vector<std::unique_ptr<FOOTPRINT>> ownersA;
4255 std::vector<std::unique_ptr<FOOTPRINT>> ownersB;
4258
4259 if( int rc = loadFootprintLibrarySide( dirA.GetFullPath(), ownersA, mapA, false, *m_reporter );
4261 {
4262 return rc;
4263 }
4264
4265 if( int rc = loadFootprintLibrarySide( dirB.GetFullPath(), ownersB, mapB, false, *m_reporter );
4267 {
4268 return rc;
4269 }
4270
4271 KICAD_DIFF::FP_LIB_DIFFER differ( mapA, mapB, diffJob->m_inputB );
4273
4274 int diffExitCode = KICAD_DIFF::DiffExitCode( result );
4275
4276 if( diffJob->m_exitCodeOnly )
4277 return diffExitCode;
4278
4280 KICAD_DIFF::MakeEmitOptions( *diffJob, diffJob->m_inputA, diffJob->m_inputB );
4282 emitOpts.referenceGeometry = [&]( const KIGFX::COLOR4D& aColor )
4283 { return footprintLibraryGeometry( mapA, aColor ); };
4284 emitOpts.comparisonGeometry = [&]( const KIGFX::COLOR4D& aColor )
4285 { return footprintLibraryGeometry( mapB, aColor ); };
4286
4287 return KICAD_DIFF::EmitDiffResult( result, emitOpts, diffExitCode, *m_reporter );
4288}
4289
4290
4291// ============================================================================
4292// JobOpenDiffDialog: load two on-disk files and open DIALOG_KICAD_DIFF.
4293// Dispatched from the project manager / PR-review dialog via KIWAY.
4294// ============================================================================
4298#include <jobs/scratch_doc.h>
4299
4300
4302 const wxString& aFileB, const wxString& aLabelA,
4303 const wxString& aLabelB, wxWindow* aParent,
4304 REPORTER* aReporter )
4305{
4306 // Restore m_reporter on scope exit so a caller's transient (often
4307 // stack-local) reporter doesn't outlive this call as a dangling member.
4309 aReporter ? aReporter : m_reporter );
4310
4311 wxWindow* parent = aParent ? aParent : ( wxTheApp ? wxTheApp->GetTopWindow() : nullptr );
4312
4314
4315 auto loadBoardScratch = [&]( const wxString& aPath )
4316 {
4317 return loadScratchBoard( mgr, aPath, /* aInitializeAfterLoad */ false );
4318 };
4319
4322 KICAD_DIFF::DOCUMENT_GEOMETRY compGeometry;
4323
4324 auto loadFootprintFile = [&]( const wxString& aPath ) -> std::unique_ptr<FOOTPRINT>
4325 {
4326 if( aPath.IsEmpty() )
4327 return nullptr;
4328
4329 wxFileName fn( aPath );
4330 fn.MakeAbsolute();
4331
4332 // A single .kicad_mod's internal (footprint ...) name need not match its
4333 // filename, so load the file's sole footprint via ImportFootprint rather
4334 // than FootprintLoad (which treats the directory as a .pretty library and
4335 // keys by basename), matching runFpLibMerge's single-file path.
4337 wxString name;
4338 return io.ImportFootprint( fn.GetFullPath(), name );
4339 };
4340
4341 switch( aKind )
4342 {
4344 {
4345 SCRATCH_DOC<BOARD> a = loadBoardScratch( aFileA );
4346 SCRATCH_DOC<BOARD> b = loadBoardScratch( aFileB );
4347
4348 // Synthesize empty boards for ADDED / REMOVED sides so the differ
4349 // can still produce a meaningful per-item list rather than failing
4350 // on an empty input file.
4351 BOARD emptyA;
4352 BOARD emptyB;
4353
4354 if( !a.doc && !aFileA.IsEmpty() )
4355 {
4356 m_reporter->Report( wxString::Format( _( "Failed to load %s\n" ), aFileA ), RPT_SEVERITY_ERROR );
4358 }
4359
4360 if( !b.doc && !aFileB.IsEmpty() )
4361 {
4362 m_reporter->Report( wxString::Format( _( "Failed to load %s\n" ), aFileB ), RPT_SEVERITY_ERROR );
4364 }
4365
4366 BOARD* boardA = a.doc ? a.doc.get() : &emptyA;
4367 BOARD* boardB = b.doc ? b.doc.get() : &emptyB;
4368
4369 KICAD_DIFF::PCB_DIFFER differ( boardA, boardB, aFileB );
4370 result = differ.Diff();
4371
4372 // Extract background geometry so the dialog's canvas shows the
4373 // actual board outline + footprint footprints beneath the diff
4374 // bbox rectangles. Theme defaults: muted blue (ref) / gold (comp).
4375 const KICAD_DIFF::DIFF_COLOR_THEME theme;
4376 refGeometry = KICAD_DIFF::ExtractBoardGeometry( *boardA, theme.reference );
4377 compGeometry = KICAD_DIFF::ExtractBoardGeometry( *boardB, theme.comparison );
4378
4379 const wxString labelA = aLabelA.IsEmpty() ? aFileA : aLabelA;
4380 const wxString labelB = aLabelB.IsEmpty() ? aFileB : aLabelB;
4381
4383 parent, labelA, labelB, result, std::move( refGeometry ), std::move( compGeometry ),
4384 [boardA, boardB, color = theme.reference]( WIDGET_DIFF_CANVAS& aCanvas, const KIID_PATH& )
4385 {
4386 KICAD_DIFF::ConfigurePcbDiffCanvasContext( aCanvas, boardA, boardB, color );
4387 } );
4388 dlg.ShowModal();
4389
4391 }
4393 {
4394 std::vector<std::unique_ptr<FOOTPRINT>> ownersA;
4395 std::vector<std::unique_ptr<FOOTPRINT>> ownersB;
4398
4399 if( int rc = loadFootprintLibrarySide( aFileA, ownersA, mapA, true, *m_reporter );
4401 {
4402 return rc;
4403 }
4404
4405 if( int rc = loadFootprintLibrarySide( aFileB, ownersB, mapB, true, *m_reporter );
4407 {
4408 return rc;
4409 }
4410
4411 KICAD_DIFF::FP_LIB_DIFFER differ( mapA, mapB, aFileB );
4412 result = differ.Diff();
4413
4414 const KICAD_DIFF::DIFF_COLOR_THEME theme;
4415 refGeometry = footprintLibraryGeometry( mapA, theme.reference );
4416 compGeometry = footprintLibraryGeometry( mapB, theme.comparison );
4417 break;
4418 }
4420 {
4421 std::unique_ptr<FOOTPRINT> footprintA;
4422 std::unique_ptr<FOOTPRINT> footprintB;
4423
4424 try
4425 {
4426 footprintA = loadFootprintFile( aFileA );
4427 }
4428 catch( const IO_ERROR& ioe )
4429 {
4430 m_reporter->Report( wxString::Format( _( "Failed to load %s: %s\n" ), aFileA, ioe.What() ),
4433 }
4434
4435 try
4436 {
4437 footprintB = loadFootprintFile( aFileB );
4438 }
4439 catch( const IO_ERROR& ioe )
4440 {
4441 m_reporter->Report( wxString::Format( _( "Failed to load %s: %s\n" ), aFileB, ioe.What() ),
4444 }
4445
4448 const wxString nameA = wxFileName( aFileA ).GetName();
4449 const wxString nameB = wxFileName( aFileB ).GetName();
4450 const wxString itemName = !nameB.IsEmpty() ? nameB : nameA;
4451
4452 if( footprintA )
4453 mapA[itemName] = footprintA.get();
4454
4455 if( footprintB )
4456 mapB[itemName] = footprintB.get();
4457
4458 KICAD_DIFF::FP_LIB_DIFFER differ( mapA, mapB, aFileB );
4459 result = differ.Diff();
4460
4461 const KICAD_DIFF::DIFF_COLOR_THEME theme;
4462
4463 if( footprintA )
4464 refGeometry = KICAD_DIFF::ExtractFootprintGeometry( *footprintA, theme.reference );
4465
4466 if( footprintB )
4467 compGeometry = KICAD_DIFF::ExtractFootprintGeometry( *footprintB, theme.comparison );
4468
4469 break;
4470 }
4471 default:
4472 m_reporter->Report( _( "Unsupported document kind for this dispatcher.\n" ), RPT_SEVERITY_ERROR );
4474 }
4475
4476 const wxString labelA = aLabelA.IsEmpty() ? aFileA : aLabelA;
4477 const wxString labelB = aLabelB.IsEmpty() ? aFileB : aLabelB;
4478
4479 DIALOG_KICAD_DIFF dlg( parent, labelA, labelB, result, std::move( refGeometry ), std::move( compGeometry ) );
4480 dlg.ShowModal();
4481
4483}
4484
4485
4486// ============================================================================
4487// JobFpLibMerge: 3-way merge of .pretty footprint libraries.
4488// ============================================================================
4491
4492
4493int PCBNEW_JOBS_HANDLER::runFpLibMerge( const wxString& aAncestor, const wxString& aOurs,
4494 const wxString& aTheirs, const wxString& aOutput,
4495 bool aSingleFile )
4496{
4497 if( aOutput.IsEmpty() )
4498 {
4499 m_reporter->Report( _( "--output is required\n" ), RPT_SEVERITY_ERROR );
4501 }
4502
4503 struct LIB_SIDE
4504 {
4505 std::vector<std::unique_ptr<FOOTPRINT>> owners;
4507 };
4508
4509 LIB_SIDE ancestor, ours, theirs;
4510
4511 // Accept either a `.pretty` directory (library mode) or a single `.kicad_
4512 // mod` file (git's per-file driver mode). Extension autodetection works
4513 // for native invocations, but git's external driver passes temp paths
4514 // (`.merge_file_XXX`) with no extension, so the `--single-file` flag
4515 // overrides on demand.
4516 auto isSingleFile = [&]( const wxString& aPath )
4517 {
4518 if( aSingleFile )
4519 return true;
4520
4521 return wxFileName( aPath ).GetExt() == FILEEXT::KiCadFootprintFileExtension;
4522 };
4523
4524 auto loadSide = [&]( const wxString& aPath, LIB_SIDE& aSide ) -> int
4525 {
4526 try
4527 {
4528 if( isSingleFile( aPath ) )
4529 {
4531 wxString name;
4532 std::unique_ptr<FOOTPRINT> fp = io.ImportFootprint( aPath, name );
4533
4534 if( !fp )
4536
4537 // Use the footprint's own item-name (LIB_ID) if set,
4538 // falling back to the file basename. Both sides must
4539 // agree for the differ/applier to align them.
4540 const UTF8& itemName = fp->GetFPID().GetLibItemName();
4541 const wxString key = itemName.empty() ? name : itemName.wx_str();
4542
4543 aSide.map[key] = fp.get();
4544 aSide.owners.push_back( std::move( fp ) );
4546 }
4547
4548 auto loaded = KICAD_DIFF::FP_LIB_DIFFER::LoadLibrary( aPath );
4549 aSide.owners = std::move( loaded.first );
4550 aSide.map = std::move( loaded.second );
4552 }
4553 catch( const IO_ERROR& ioe )
4554 {
4555 m_reporter->Report( wxString::Format( _( "Failed to load %s: %s\n" ), aPath, ioe.What() ),
4557 }
4558 catch( const std::exception& e )
4559 {
4560 m_reporter->Report(
4561 wxString::Format( _( "Failed to load %s: %s\n" ), aPath, wxString::FromUTF8( e.what() ) ),
4563 }
4564
4566 };
4567
4568 if( int rc = loadSide( aAncestor, ancestor ); rc != CLI::EXIT_CODES::SUCCESS )
4569 return rc;
4570
4571 if( int rc = loadSide( aOurs, ours ); rc != CLI::EXIT_CODES::SUCCESS )
4572 return rc;
4573
4574 if( int rc = loadSide( aTheirs, theirs ); rc != CLI::EXIT_CODES::SUCCESS )
4575 return rc;
4576
4577 KICAD_DIFF::FP_LIB_DIFFER ourDiff( ancestor.map, ours.map, aOurs );
4578 KICAD_DIFF::FP_LIB_DIFFER theirDiff( ancestor.map, theirs.map, aTheirs );
4579
4580 KICAD_DIFF::DOCUMENT_DIFF ourDocDiff = ourDiff.Diff();
4581 KICAD_DIFF::DOCUMENT_DIFF theirDocDiff = theirDiff.Diff();
4582
4584 KICAD_DIFF::MERGE_PLAN plan = engine.Plan( ourDocDiff, theirDocDiff );
4585
4586 const KICAD_DIFF::MERGE_PLAN planSnapshot = plan;
4587
4588 KICAD_DIFF::FP_LIB_MERGE_APPLIER applier( ancestor.map, ours.map, theirs.map, std::move( plan ) );
4589 std::vector<std::unique_ptr<FOOTPRINT>> merged = applier.Apply();
4590
4591 // Per-property footprint merge isn't implemented; MERGE_PROPS resolutions
4592 // are downgraded to TAKE_OURS. Surface that as unresolved so the user sees
4593 // a marker instead of silent partial-merge.
4594 const bool hadSilentFallback = applier.GetReport().mergePropsFallback > 0;
4595
4596 const bool singleFileOutput = isSingleFile( aOutput );
4597
4598 // .pretty is a directory; .kicad_mod is a single file. wxFileName parses
4599 // a path ending in `.pretty` as a file with that extension, so library
4600 // mode uses DirName(); single-file mode keeps the file path as-is and
4601 // hands it directly to FootprintSave, which auto-detects .kicad_mod via
4602 // its own extension check.
4603 wxFileName outFn;
4604
4605 if( singleFileOutput )
4606 outFn = wxFileName( aOutput );
4607 else
4608 outFn = wxFileName::DirName( aOutput );
4609
4610 outFn.MakeAbsolute();
4611
4612 // In library mode wxFileName::DirName treats `foo.pretty` as a directory,
4613 // so GetPath() returns the .pretty itself. In single-file mode GetPath()
4614 // returns the file's parent dir. Either way it's the directory we Mkdir
4615 // into.
4616 const wxString outDir = outFn.GetPath();
4617
4618 if( !wxFileName::DirExists( outDir ) && !wxFileName::Mkdir( outDir, wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL ) )
4619 {
4620 m_reporter->Report( wxString::Format( _( "Cannot create output directory %s\n" ), outDir ),
4623 }
4624
4625 try
4626 {
4628
4629 if( singleFileOutput )
4630 {
4631 // Git per-file driver mode: one merged footprint -> one .kicad_mod.
4632 // Multiple survivors would lose data; flag that as an error since
4633 // single-file input by definition has at most one footprint per
4634 // side.
4635 if( merged.size() > 1 )
4636 {
4637 m_reporter->Report( _( "Single-file fp merge produced multiple footprints; refusing to "
4638 "collapse into one .kicad_mod\n" ),
4641 }
4642
4643 if( merged.empty() )
4644 {
4645 // All sides deleted the footprint. Remove the output file if
4646 // it existed, leaving nothing where the merged content would
4647 // have gone.
4648 if( wxFileName::FileExists( outFn.GetFullPath() ) )
4649 wxRemoveFile( outFn.GetFullPath() );
4650 }
4651 else if( wxFileName( outFn.GetFullPath() ).GetExt() == FILEEXT::KiCadFootprintFileExtension )
4652 {
4653 // FootprintSave's .kicad_mod extension autodetection handles
4654 // the write to the path as-given.
4655 io.FootprintSave( outFn.GetFullPath(), merged.front().get(), nullptr );
4656 }
4657 else
4658 {
4659 // Git driver mode: output is an extension-less temp path
4660 // (typically `.merge_file_XXX`). FootprintSave's
4661 // autodetection would treat it as a library directory.
4662 // Format directly via PRETTIFIED_FILE_OUTPUTFORMATTER, the
4663 // same writer the sexpr lib cache uses.
4664 PRETTIFIED_FILE_OUTPUTFORMATTER formatter( outFn.GetFullPath() );
4665 io.SetOutputFormatter( &formatter );
4666 io.Format( merged.front().get() );
4667 formatter.Finish();
4668 }
4669 }
4670 else
4671 {
4672 // Library mode. Footprints in `merged` are the survivors. Any
4673 // footprint already in the output `.pretty` but absent from
4674 // `merged` is a stale leftover from a previous invocation (or a
4675 // resolved DELETE / TAKE_ANCESTOR-with-no-ancestor case). Delete
4676 // those before saving the survivors, otherwise the resolved
4677 // DELETE never propagates to disk.
4678 std::set<wxString> mergedNames;
4679
4680 for( const auto& fp : merged )
4681 {
4682 if( fp )
4683 mergedNames.insert( fp->GetFPID().GetLibItemName() );
4684 }
4685
4686 wxArrayString existing;
4687 io.FootprintEnumerate( existing, outDir, false, nullptr );
4688
4689 for( const wxString& name : existing )
4690 {
4691 if( !mergedNames.count( name ) )
4692 io.FootprintDelete( outDir, name, nullptr );
4693 }
4694
4695 for( const auto& fp : merged )
4696 {
4697 if( !fp )
4698 continue;
4699
4700 const wxString name = fp->GetFPID().GetLibItemName();
4701
4702 if( io.FootprintExists( outDir, name, nullptr ) )
4703 io.FootprintDelete( outDir, name, nullptr );
4704
4705 io.FootprintSave( outDir, fp.get(), nullptr );
4706 }
4707 }
4708 }
4709 catch( const IO_ERROR& ioe )
4710 {
4711 m_reporter->Report( wxString::Format( _( "Failed to save merged footprint library: %s\n" ), ioe.What() ),
4714 }
4715
4716 // The merged library was saved above, so the output is always valid.
4717 if( !planSnapshot.Resolved() || hadSilentFallback )
4718 {
4719 // Conflict count = engine-unresolved ∪ applier-downgraded (deduped, so
4720 // an item that was both unresolved and silently downgraded counts once).
4721 std::set<KIID_PATH> conflicts( planSnapshot.unresolved.begin(), planSnapshot.unresolved.end() );
4722
4723 for( const KIID_PATH& id : applier.GetReport().mergePropsFallbackIds )
4724 conflicts.insert( id );
4725
4726 m_reporter->Report( wxString::Format( _( "Footprint library merge completed with %zu unresolved "
4727 "conflict(s) in %s\n" ),
4728 conflicts.size(), aOutput ),
4731 }
4732
4734}
@ VIEW3D_BOTTOM
Definition 3d_enums.h:77
const char * name
constexpr EDA_IU_SCALE pcbIUScale
Definition base_units.h:128
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 BuildStackupCsv(BOARD_STACKUP &aStackup, EDA_UNITS aUnits, const STACKUP_CSV_OPTIONS &aOptions)
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:914
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
BASE_SET & set(size_t pos)
Definition base_set.h:126
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.
void Push(const wxString &aMessage=wxEmptyString, int aCommitFlags=0) override
Execute the changes.
Container for design settings for a BOARD object.
std::shared_ptr< NET_SETTINGS > m_NetSettings
std::map< int, SEVERITY > m_DRCSeverities
std::shared_ptr< DRC_ENGINE > m_DRCEngine
const VECTOR2I & GetAuxOrigin() const
BOARD_STACKUP & GetStackupDescriptor()
static bool SaveBoard(wxString &aFileName, BOARD &aBoard, PCB_IO_MGR::PCB_FILE_T aFormat)
static std::unique_ptr< BOARD > Load(const wxString &aFileName, PCB_IO_MGR::PCB_FILE_T aFormat, PROJECT *aProject, const OPTIONS &aOptions)
Manage one layer needed to make a physical board.
Manage layers needed to make a physical board.
const std::vector< BOARD_STACKUP_ITEM * > & GetList() const
bool SynchronizeWithBoard(const BOARD_DESIGN_SETTINGS *aSettings)
Synchronize the BOARD_STACKUP_ITEM* list with the board.
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:410
void SetCurrentVariant(const wxString &aVariant)
Definition board.cpp:3173
const PAGE_INFO & GetPageSettings() const
Definition board.h:1018
void RecordDRCExclusions()
Scan existing markers and record data from any that are Excluded.
Definition board.cpp:570
TITLE_BLOCK & GetTitleBlock()
Definition board.h:1024
const std::map< wxString, wxString > & GetProperties() const
Definition board.h:525
const FOOTPRINTS & Footprints() const
Definition board.h:464
const wxString & GetFileName() const
Definition board.h:453
std::vector< PCB_MARKER * > ResolveDRCExclusions(bool aCreateMarkers)
Rebuild DRC markers from the serialized data in BOARD_DESIGN_SETTINGS.
Definition board.cpp:596
wxString GetVariantDescription(const wxString &aVariantName) const
Definition board.cpp:3327
int GetFileFormatVersionAtLoad() const
Definition board.h:584
const PCB_PLOT_PARAMS & GetPlotOptions() const
Definition board.h:1021
const wxString GetLayerName(PCB_LAYER_ID aLayer) const
Return the name of a aLayer.
Definition board.cpp:943
wxString GetCurrentVariant() const
Definition board.h:529
PROJECT * GetProject() const
Definition board.h:775
wxString GetDesignRulesPath() const
Return the absolute path to the design rules file for this board.
Definition board.cpp:438
BOARD_DESIGN_SETTINGS & GetDesignSettings() const
Definition board.cpp:1301
const LSET & GetEnabledLayers() const
A proxy function that calls the corresponding function in m_BoardSettings.
Definition board.cpp:1185
void SynchronizeProperties()
Copy the current project's text variables into the boards property cache.
Definition board.cpp:3153
BOX2I ComputeBoundingBox(bool aBoardEdgesOnly=false, bool aPhysicalLayersOnly=false) const
Calculate the bounding box containing all board items (or board edge segments).
Definition board.cpp:2742
void DeleteMARKERs()
Delete all MARKERS from the board.
Definition board.cpp:2067
constexpr const Vec GetCenter() const
Definition box2.h:227
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
COMMIT & Add(EDA_ITEM *aItem, BASE_SCREEN *aScreen=nullptr)
Add a new item to the model.
Definition commit.h:74
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
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.
void SetVariantNames(const std::vector< wxString > &aVariantNames)
std::vector< BOM_FIELD > GetFieldsOrdered()
static const wxString ITEM_NUMBER_VARIABLE
wxString Export(const BOM_FMT_PRESET &aSettings)
int GetFieldNameCol(const wxString &aFieldName) const
void ApplyBomPreset(const BOM_PRESET &aPreset)
void AddColumn(const wxString &aFieldName, const wxString &aLabel, bool aAddedByUser) override
void SetCurrentVariant(const wxString &aVariantName) override
Select the displayed variant, retaining un-applied edits in every variant.
Provide an extensible class to resolve 3D model paths.
wxString ResolvePath(const wxString &aFileName, const wxString &aWorkingPath, std::vector< const EMBEDDED_FILES * > aEmbeddedFilesStack)
Determine the full path of the given file name.
void SetProgramBase(PGM_BASE *aBase)
Set a pointer to the application's PGM_BASE instance used to extract the local env vars.
bool SetProject(const PROJECT *aProject, bool *flgChanged=nullptr)
Set the current KiCad project directory as the first entry in the model path list.
const FOOTPRINT_REFERENCE_LIST & GetReferenceList() const
void UpdateReferences(const FOOTPRINT_REFERENCE_LIST &aRefs)
An interface to the global shared library manager that is schematic-specific and linked to one projec...
This is the minimal equivalent to the SCH_REFERENCE so we can provide similar non-null guarantees thr...
const LIB_ID & GetFPID() const
Definition footprint.h:474
void GetFields(std::vector< PCB_FIELD * > &aVector, bool aVisibleOnly) const
Populate a std::vector with PCB_TEXTs.
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.
bool Export(const wxString &aFullFileName) const
Generate IDFv3 compliant board (*.emn) and library (*.emp) files for aPcb.
Reporter handed to an importer during an import job.
void Flush(IMPORT_REPORT_FORMAT aFormat, IMPORT_REPORT_DATA &aData)
Move the collected messages into aData when a report will be written, otherwise forward them to the j...
An exception saying that the user cancelled an interactive part of a load, import,...
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:60
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
wxString GetSelectedVariant() const
std::vector< wxString > m_variantNames
wxString m_stringDelimiter
wxString m_fieldDelimiter
bool m_includeByteOrderMark
wxString m_filename
wxString m_filterString
std::vector< wxString > m_fieldsOrdered
wxString m_refRangeDelimiter
std::vector< wxString > m_fieldsLabels
wxString m_refDelimiter
std::vector< wxString > m_fieldsGroupBy
wxString m_bomFmtPresetName
wxString m_sortField
wxString m_bomPresetName
BOM_FILTER_SCOPE m_filterScope
JOB_EXPORT_PCB_3D::FORMAT m_format
EXPORTER_STEP_PARAMS m_3dparams
Despite the name; also used for other formats.
std::vector< wxString > m_variantNames
wxString GetSettingsDialogTitle() const override
ODB_COMPRESSION m_compressionMode
@ ALL_LAYERS_ONE_FILE
DEPRECATED MODE.
GEN_MODE m_pdfGenMode
uused by the cli, will be removed when the other behavior is deprecated
LSEQ m_plotOnAllLayersSequence
Layers to include on all individual layer prints.
std::optional< wxString > m_argLayers
std::optional< wxString > m_argCommonLayers
wxString m_variant
Variant name for variant-aware filtering.
void SetDefaultOutputPath(const wxString &aReferenceName)
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.
wxString m_importPcbId
EasyEDA Pro PCB document identifier selected by project-level import.
std::map< wxString, wxString > m_layerMap
Explicit overrides from source layer name to KiCad layer name (canonical board-file name,...
std::map< wxString, wxString > m_netNameMap
bool m_isPartOfMultiBoardProject
Avoid promoting an individual board's source settings into shared project settings.
std::vector< std::pair< wxString, wxString > > m_projectBoards
PCB document identifier and source name pairs found during a probe.
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
void SetConfiguredOutputPath(const wxString &aPath)
Sets the configured output path for the job, this path is always saved to file.
Definition job.cpp:157
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()
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:62
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:348
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:355
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
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.
static bool EnsurePathExists(const wxString &aPath, bool aPathToFile=false)
Attempts to create a given path if it does not exist.
Definition paths.cpp:508
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...
bool preparePlotLayers(JOB_EXPORT_PCB_PLOT *aJob, BOARD *aBoard, TOOL_MANAGER *aToolManager)
void refillFabZones(const JOB_EXPORT_PCB_FAB &aJob, BOARD *aBoard)
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)
wxString resolvePlotOutputPath(JOB_EXPORT_PCB_PLOT *aJob, BOARD *aBoard, PLOT_FORMAT aFormat, bool aSingleOutput)
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 PCB_IO derivation for saving and loading Pcbnew s-expression formatted files.
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.
std::unique_ptr< 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.
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
@ EASYPC
Easy-PC / DesignSpark PCB.
Definition pcb_io_mgr.h:76
@ 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 PCB_IO 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)
True when the board's out-of-date-chart policy regenerated charts during the last Plot().
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 SetLayerSelection(const LSET &aSelection)
void SetPlotOnAllLayersSequence(LSEQ aSeq)
void SetPlotPadNumbers(bool aFlag)
LSET GetLayerSelection() const
bool GetSketchPadsOnFabLayers() const
bool GetUseGerberProtelExtensions() const
virtual SETTINGS_MANAGER & GetSettingsManager() const
Definition pgm_base.h:123
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:136
virtual bool EndPlot()=0
bool Finish() override
Runs prettification over the buffered bytes, writes them to the sibling temp file,...
Definition richio.cpp:710
The backing store for a PROJECT, in JSON format.
wxString m_BoardDrawingSheetFile
PcbNew params.
bool Store() override
Stores the current parameters into the JSON document represented by this object Note: this doesn't do...
static S3D_CACHE * Get3DCacheManager(PROJECT *aProject, bool updateProjDir=false)
Return a pointer to an instance of the 3D cache manager.
static FILENAME_RESOLVER * Get3DFilenameResolver(PROJECT *aProject)
Accessor for 3D path resolver.
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 void ApplyTextVars(const std::map< wxString, wxString > &aVarsMap)
Applies the given var map, it will create or update existing vars.
Definition project.cpp:132
virtual PROJECT_FILE & GetProjectFile() const
Definition project.h:201
void SetReporters(std::shared_ptr< REPORTER > aActivityReporter, std::shared_ptr< REPORTER > aWarningReporter)
Set the reporters for activity and warning messages.
bool Redraw(bool aIsMoving) 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:73
virtual REPORTER & Report(const wxString &aText, SEVERITY aSeverity=RPT_SEVERITY_UNDEFINED)
Report a string with a given severity.
Definition reporter.h:102
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:225
TOOL_MANAGER * GetToolManager() const
Return the MVC controller.
Master controller class:
T * GetTool()
Return the tool of given type or nullptr if there is no such tool registered.
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 GetGeneratedFieldDisplayName(const wxString &aSource)
Returns any variables unexpanded, e.g.
Definition common.cpp:481
bool IsGeneratedField(const wxString &aFieldName)
Returns true if the entire string is generated, e.g is a single text var reference.
Definition common.cpp:494
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
EDA_UNITS
Definition eda_units.h:44
std::vector< FOOTPRINT_REF > FOOTPRINT_REFERENCE_LIST
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.
int RefreshGeneratedTables(BOARD &aBoard)
Bring every generated table on the board up to date and return how many were rebuilt.
Classes used to generate a Gerber job file in JSON.
Classes used in place file generation.
nlohmann::json json
Definition gerbview.cpp:50
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 IdfV3BoardFileExtension
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::string source
#define TO_UTF8(wxstring)
Convert a wxString to a UTF8 encoded C string for all wxWidgets build modes.
Definition idf_helpers.h:37
bool ApplyImportedNetNameMap(BOARD &aBoard, const std::map< wxString, wxString > &aNames, REPORTER &aReporter)
Apply caller-selected imported net names without changing net identity or connectivity.
std::unique_ptr< T > IO_RELEASER
Helper to hold and release an IO_BASE object when exceptions are thrown.
Definition io_mgr.h:33
bool GenerateIpc2581File(JOB_EXPORT_PCB_IPC2581 &aJob, BOARD *aBoard, PROGRESS_REPORTER *aProgressReporter, REPORTER *aReporter)
Write the IPC-2581 file or archive of aJob and record it as a job output.
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...
bool 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...
@ KIFACE_NETLIST_SCHEMATIC
Definition kiface_ids.h:48
#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:575
@ LAYER_3D_BACKGROUND_BOTTOM
Definition layer_ids.h:574
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:45
#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:68
static std::vector< PENDING_PROPERTY > plan(const EDA_ITEM &aSource, const EDA_ITEM &aTarget, const std::set< wxString > &aEnabledKeys)
Target properties to write, paired with values read off the source.
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.
void PackBoardStackup(const BOARD &aBoard, BoardStackup &aOut)
ODB_EXPORT_RESULT GenerateODBPPFiles(const JOB_EXPORT_PCB_ODB &aJob, BOARD *aBoard, PCB_EDIT_FRAME *aParentFrame, PROGRESS_REPORTER *aProgressReporter, REPORTER *aReporter)
Generate the ODB++ tree or archive and return the write status and the paths written.
#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.
bool PlotFootprintToSVG(const FOOTPRINT &aFootprint, PROJECT &aProject, const std::map< wxString, wxString > *aVarOverrides, PCB_PLOT_PARAMS &aPlotOpts, const LSEQ &aLayersToPlot, const LSEQ &aLayersOnAll, const wxString &aFileName, REPORTER *aReporter)
Plot a footprint to an SVG file, with the footprint origin at the SVG origin and the page/viewBox siz...
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 int plotJob(JOB_EXPORT_PCB_PLOT *aJob, PCB_PLOTTER &aPlotter, const wxString &aOutPath, bool aSingleOutput, bool aUseOverrides)
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:63
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:40
#define SKIP_UNDO
Definition sch_commit.h:38
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 OPEN_OSTREAM(var, name)
#define CLOSE_STREAM(var)
wxString ExpandVariantOutputPath(const wxString &aPath, const wxString &aVariantToken)
wxString label
wxString name
wxString fieldDelimiter
bool includeByteOrderMark
static std::vector< BOM_FMT_PRESET > BuiltInPresets()
wxString stringDelimiter
wxString refRangeDelimiter
wxString refDelimiter
Phase 8 context for the conflict canvas.
std::vector< KIGFX::COLOR4D > raytrace_lightColor
std::vector< BOM_PRESET > m_BomPresets
std::vector< BOM_FMT_PRESET > m_BomFmtPresets
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:153
A filename or source description, a problem input line, a line number, a byte offset,...
Move-only RAII wrapper for "load a KiCad document into a non-active scratch PROJECT and clean up afte...
std::unique_ptr< DOC > doc
Options controlling which stackup fields are included in CSV exports.
wxString GetDefaultFieldName(FIELD_T aFieldId, TRANSLATION aTranslation)
Return a default symbol field name for a mandatory field type.
#define MANDATORY_FIELDS
FIELD_T
The set of all field indices assuming an array like sequence that a SCH_COMPONENT or LIB_PART can hol...
@ UNTRANSLATED
@ TRANSLATED
static void checkParity(CREEPAGE_PARITY_FIXTURE &aFixture, const std::string &aBoard)
std::string netlist
std::string path
IbisParser parser & reporter
VECTOR3I res
wxString result
Test unit parsing edge cases and error handling.
wxLogTrace helper definitions.
Declaration for a track ball camera.
double DEG2RAD(double deg)
Definition trigo.h:172
@ PCB_VIA_T
class PCB_VIA, a via (like a track segment on a copper layer)
Definition typeinfo.h:89
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:708
Definition of file extensions used in Kicad.
glm::vec3 SFVEC3F
Definition xv3d_types.h:40
#define ZONE_FILLER_TOOL_NAME