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>
82#include <downgrade_scan.h>
83#include <downgrade_copy.h>
84#include <eda_units.h>
86#include <lset.h>
87#include <cli/exit_codes.h>
93#include <tool/tool_manager.h>
94#include <tools/drc_tool.h>
95#include <filename_resolver.h>
100#include <kiface_base.h>
101#include <macros.h>
102#include <pad.h>
103#include <pcb_marker.h>
104#include <project/project_file.h>
105#include <exporters/export_idf.h>
108#include <kiface_ids.h>
111#include <pcbnew_settings.h>
112#include <pcbplot.h>
113#include <pcb_plotter.h>
114#include <pcb_edit_frame.h>
115#include <pcb_track.h>
116#include <pgm_base.h>
119#include <project_pcb.h>
122#include <reporter.h>
123#include <scoped_set_reset.h>
124#include <progress_reporter.h>
126#include <export_vrml.h>
127#include <kiplatform/io.h>
138#include <dialogs/dialog_plot.h>
146#include <api/api_pcb_utils.h>
147#include <api/board/board.pb.h>
148#include <google/protobuf/util/json_util.h>
149#include <fstream>
150#include <paths.h>
151#include <streamwrapper.h>
153
154#include <locale_io.h>
155#include <confirm.h>
156
157
158#ifdef _WIN32
159#ifdef TRANSPARENT
160#undef TRANSPARENT
161#endif
162#endif
163
164
166 JOB_DISPATCHER( aKiway ),
167 m_cliBoard( nullptr ),
168 m_toolManager( nullptr )
169{
170 Register( "bom", std::bind( &PCBNEW_JOBS_HANDLER::JobExportBom, this, std::placeholders::_1 ),
171 [aKiway]( JOB* job, wxWindow* aParent ) -> bool
172 {
173 JOB_EXPORT_BOM* bomJob = dynamic_cast<JOB_EXPORT_BOM*>( job );
174
175 PCB_EDIT_FRAME* editFrame = static_cast<PCB_EDIT_FRAME*>( aKiway->Player( FRAME_PCB_EDITOR, false ) );
176
177 wxCHECK( bomJob && editFrame, false );
178
179 DIALOG_FOOTPRINT_FIELDS_TABLE dlg( editFrame, bomJob );
180
181 if( dlg.WasAborted() )
182 return false;
183
184 return dlg.ShowModal() == wxID_OK;
185 } );
186 Register( "3d", std::bind( &PCBNEW_JOBS_HANDLER::JobExportStep, this, std::placeholders::_1 ),
187 [aKiway]( JOB* job, wxWindow* aParent ) -> bool
188 {
189 JOB_EXPORT_PCB_3D* svgJob = dynamic_cast<JOB_EXPORT_PCB_3D*>( job );
190
191 PCB_EDIT_FRAME* editFrame =
192 dynamic_cast<PCB_EDIT_FRAME*>( aKiway->Player( FRAME_PCB_EDITOR, false ) );
193
194 wxCHECK( svgJob && editFrame, false );
195
196 DIALOG_EXPORT_STEP dlg( editFrame, aParent, "", svgJob );
197 return dlg.ShowModal() == wxID_OK;
198 } );
199 Register( "render", std::bind( &PCBNEW_JOBS_HANDLER::JobExportRender, this, std::placeholders::_1 ),
200 []( JOB* job, wxWindow* aParent ) -> bool
201 {
202 JOB_PCB_RENDER* renderJob = dynamic_cast<JOB_PCB_RENDER*>( job );
203
204 wxCHECK( renderJob, false );
205
206 DIALOG_RENDER_JOB dlg( aParent, renderJob );
207 return dlg.ShowModal() == wxID_OK;
208 } );
209 Register( "upgrade", std::bind( &PCBNEW_JOBS_HANDLER::JobUpgrade, this, std::placeholders::_1 ),
210 []( JOB* job, wxWindow* aParent ) -> bool
211 {
212 return true;
213 } );
214 Register( "downgrade", std::bind( &PCBNEW_JOBS_HANDLER::JobDowngrade, this, std::placeholders::_1 ),
215 []( JOB* job, wxWindow* aParent ) -> bool
216 {
217 return true;
218 } );
219 Register( "pcb_import", std::bind( &PCBNEW_JOBS_HANDLER::JobImport, this, std::placeholders::_1 ),
220 []( JOB* job, wxWindow* aParent ) -> bool
221 {
222 return true;
223 } );
224 Register( "svg", std::bind( &PCBNEW_JOBS_HANDLER::JobExportSvg, this, std::placeholders::_1 ),
225 [aKiway]( JOB* job, wxWindow* aParent ) -> bool
226 {
227 JOB_EXPORT_PCB_SVG* svgJob = dynamic_cast<JOB_EXPORT_PCB_SVG*>( job );
228
229 PCB_EDIT_FRAME* editFrame =
230 dynamic_cast<PCB_EDIT_FRAME*>( aKiway->Player( FRAME_PCB_EDITOR, false ) );
231
232 wxCHECK( svgJob && editFrame, false );
233
234 DIALOG_PLOT dlg( editFrame, aParent, svgJob );
235 return dlg.ShowModal() == wxID_OK;
236 } );
237 Register( "gencad", std::bind( &PCBNEW_JOBS_HANDLER::JobExportGencad, this, std::placeholders::_1 ),
238 [aKiway]( JOB* job, wxWindow* aParent ) -> bool
239 {
240 JOB_EXPORT_PCB_GENCAD* gencadJob = dynamic_cast<JOB_EXPORT_PCB_GENCAD*>( job );
241
242 PCB_EDIT_FRAME* editFrame =
243 dynamic_cast<PCB_EDIT_FRAME*>( aKiway->Player( FRAME_PCB_EDITOR, false ) );
244
245 wxCHECK( gencadJob && editFrame, false );
246
247 DIALOG_GENCAD_EXPORT_OPTIONS dlg( editFrame, gencadJob->GetSettingsDialogTitle(), gencadJob );
248 return dlg.ShowModal() == wxID_OK;
249 } );
250 Register( "dxf", std::bind( &PCBNEW_JOBS_HANDLER::JobExportDxf, this, std::placeholders::_1 ),
251 [aKiway]( JOB* job, wxWindow* aParent ) -> bool
252 {
253 JOB_EXPORT_PCB_DXF* dxfJob = dynamic_cast<JOB_EXPORT_PCB_DXF*>( job );
254
255 PCB_EDIT_FRAME* editFrame =
256 dynamic_cast<PCB_EDIT_FRAME*>( aKiway->Player( FRAME_PCB_EDITOR, false ) );
257
258 wxCHECK( dxfJob && editFrame, false );
259
260 DIALOG_PLOT dlg( editFrame, aParent, dxfJob );
261 return dlg.ShowModal() == wxID_OK;
262 } );
263 Register( "pdf", std::bind( &PCBNEW_JOBS_HANDLER::JobExportPdf, this, std::placeholders::_1 ),
264 [aKiway]( JOB* job, wxWindow* aParent ) -> bool
265 {
266 JOB_EXPORT_PCB_PDF* pdfJob = dynamic_cast<JOB_EXPORT_PCB_PDF*>( job );
267
268 PCB_EDIT_FRAME* editFrame =
269 dynamic_cast<PCB_EDIT_FRAME*>( aKiway->Player( FRAME_PCB_EDITOR, false ) );
270
271 wxCHECK( pdfJob && editFrame, false );
272
273 DIALOG_PLOT dlg( editFrame, aParent, pdfJob );
274 return dlg.ShowModal() == wxID_OK;
275 } );
276 Register( "png", std::bind( &PCBNEW_JOBS_HANDLER::JobExportPng, this, std::placeholders::_1 ),
277 [aKiway]( JOB* job, wxWindow* aParent ) -> bool
278 {
279 JOB_EXPORT_PCB_PNG* pngJob = dynamic_cast<JOB_EXPORT_PCB_PNG*>( job );
280
281 PCB_EDIT_FRAME* editFrame =
282 dynamic_cast<PCB_EDIT_FRAME*>( aKiway->Player( FRAME_PCB_EDITOR, false ) );
283
284 wxCHECK( pngJob && editFrame, false );
285
286 DIALOG_PLOT dlg( editFrame, aParent, pngJob );
287 return dlg.ShowModal() == wxID_OK;
288 } );
289 Register( "ps", std::bind( &PCBNEW_JOBS_HANDLER::JobExportPs, this, std::placeholders::_1 ),
290 [aKiway]( JOB* job, wxWindow* aParent ) -> bool
291 {
292 JOB_EXPORT_PCB_PS* psJob = dynamic_cast<JOB_EXPORT_PCB_PS*>( job );
293
294 PCB_EDIT_FRAME* editFrame =
295 dynamic_cast<PCB_EDIT_FRAME*>( aKiway->Player( FRAME_PCB_EDITOR, false ) );
296
297 wxCHECK( psJob && editFrame, false );
298
299 DIALOG_PLOT dlg( editFrame, aParent, psJob );
300 return dlg.ShowModal() == wxID_OK;
301 } );
302 Register( "stats", std::bind( &PCBNEW_JOBS_HANDLER::JobExportStats, this, std::placeholders::_1 ),
303 [aKiway]( JOB* job, wxWindow* aParent ) -> bool
304 {
305 JOB_EXPORT_PCB_STATS* statsJob = dynamic_cast<JOB_EXPORT_PCB_STATS*>( job );
306
307 PCB_EDIT_FRAME* editFrame =
308 dynamic_cast<PCB_EDIT_FRAME*>( aKiway->Player( FRAME_PCB_EDITOR, false ) );
309
310 wxCHECK( statsJob && editFrame, false );
311
312 if( statsJob->m_filename.IsEmpty() && editFrame->GetBoard() )
313 {
314 wxFileName boardName = editFrame->GetBoard()->GetFileName();
315 statsJob->m_filename = boardName.GetFullPath();
316 }
317
318 wxWindow* parent = aParent ? aParent : static_cast<wxWindow*>( editFrame );
319
320 DIALOG_BOARD_STATS_JOB dlg( parent, statsJob );
321
322 return dlg.ShowModal() == wxID_OK;
323 } );
324 Register( "stackup", std::bind( &PCBNEW_JOBS_HANDLER::JobExportStackup, this, std::placeholders::_1 ),
325 [aKiway]( JOB* job, wxWindow* aParent ) -> bool
326 {
327 JOB_EXPORT_PCB_STACKUP* stackupJob = dynamic_cast<JOB_EXPORT_PCB_STACKUP*>( job );
328
329 PCB_EDIT_FRAME* editFrame =
330 dynamic_cast<PCB_EDIT_FRAME*>( aKiway->Player( FRAME_PCB_EDITOR, false ) );
331
332 wxCHECK( stackupJob && editFrame, false );
333
334 if( stackupJob->m_filename.IsEmpty() && editFrame->GetBoard() )
335 {
336 wxFileName boardName = editFrame->GetBoard()->GetFileName();
337 stackupJob->m_filename = boardName.GetFullPath();
338 }
339
340 wxWindow* parent = aParent ? aParent : static_cast<wxWindow*>( editFrame );
341
342 DIALOG_BOARD_STACKUP_JOB dlg( parent, stackupJob );
343
344 return dlg.ShowModal() == wxID_OK;
345 } );
346 Register( "gerber", std::bind( &PCBNEW_JOBS_HANDLER::JobExportGerber, this, std::placeholders::_1 ),
347 [aKiway]( JOB* job, wxWindow* aParent ) -> bool
348 {
349 JOB_EXPORT_PCB_GERBER* gJob = dynamic_cast<JOB_EXPORT_PCB_GERBER*>( job );
350
351 PCB_EDIT_FRAME* editFrame =
352 dynamic_cast<PCB_EDIT_FRAME*>( aKiway->Player( FRAME_PCB_EDITOR, false ) );
353
354 wxCHECK( gJob && editFrame, false );
355
356 DIALOG_PLOT dlg( editFrame, aParent, gJob );
357 return dlg.ShowModal() == wxID_OK;
358 } );
359 Register( "gerbers", std::bind( &PCBNEW_JOBS_HANDLER::JobExportGerbers, this, std::placeholders::_1 ),
360 [aKiway]( JOB* job, wxWindow* aParent ) -> bool
361 {
362 JOB_EXPORT_PCB_GERBERS* gJob = dynamic_cast<JOB_EXPORT_PCB_GERBERS*>( job );
363
364 PCB_EDIT_FRAME* editFrame =
365 dynamic_cast<PCB_EDIT_FRAME*>( aKiway->Player( FRAME_PCB_EDITOR, false ) );
366
367 wxCHECK( gJob && editFrame, false );
368
369 DIALOG_PLOT dlg( editFrame, aParent, gJob );
370 return dlg.ShowModal() == wxID_OK;
371 } );
372 Register(
373 "hpgl",
374 [&]( JOB* aJob )
375 {
376 m_reporter->Report( _( "Plotting to HPGL is no longer supported as of KiCad 10.0.\n" ),
379 },
380 [aKiway]( JOB* job, wxWindow* aParent ) -> bool
381 {
382 PCB_EDIT_FRAME* editFrame = dynamic_cast<PCB_EDIT_FRAME*>( aKiway->Player( FRAME_PCB_EDITOR, false ) );
383
384 wxCHECK( editFrame, false );
385
386 DisplayErrorMessage( editFrame, _( "Plotting to HPGL is no longer supported as of KiCad 10.0." ) );
387 return false;
388 } );
389 Register( "drill", std::bind( &PCBNEW_JOBS_HANDLER::JobExportDrill, this, std::placeholders::_1 ),
390 [aKiway]( JOB* job, wxWindow* aParent ) -> bool
391 {
392 JOB_EXPORT_PCB_DRILL* drillJob = dynamic_cast<JOB_EXPORT_PCB_DRILL*>( job );
393
394 PCB_EDIT_FRAME* editFrame =
395 dynamic_cast<PCB_EDIT_FRAME*>( aKiway->Player( FRAME_PCB_EDITOR, false ) );
396
397 wxCHECK( drillJob && editFrame, false );
398
399 DIALOG_GENDRILL dlg( editFrame, drillJob, aParent );
400 return dlg.ShowModal() == wxID_OK;
401 } );
402 Register( "pos", std::bind( &PCBNEW_JOBS_HANDLER::JobExportPos, this, std::placeholders::_1 ),
403 [aKiway]( JOB* job, wxWindow* aParent ) -> bool
404 {
405 JOB_EXPORT_PCB_POS* posJob = dynamic_cast<JOB_EXPORT_PCB_POS*>( job );
406
407 PCB_EDIT_FRAME* editFrame =
408 dynamic_cast<PCB_EDIT_FRAME*>( aKiway->Player( FRAME_PCB_EDITOR, false ) );
409
410 wxCHECK( posJob && editFrame, false );
411
412 DIALOG_GEN_FOOTPRINT_POSITION dlg( posJob, editFrame, aParent );
413 return dlg.ShowModal() == wxID_OK;
414 } );
415 Register( "fpupgrade", std::bind( &PCBNEW_JOBS_HANDLER::JobExportFpUpgrade, this, std::placeholders::_1 ),
416 []( JOB* job, wxWindow* aParent ) -> bool
417 {
418 return true;
419 } );
420 Register( "fpsvg", std::bind( &PCBNEW_JOBS_HANDLER::JobExportFpSvg, this, std::placeholders::_1 ),
421 []( JOB* job, wxWindow* aParent ) -> bool
422 {
423 return true;
424 } );
425 Register( "pcb_diff", std::bind( &PCBNEW_JOBS_HANDLER::JobDiff, this, std::placeholders::_1 ),
426 []( JOB* job, wxWindow* aParent ) -> bool
427 {
428 return true;
429 } );
430 Register( "fp_diff", std::bind( &PCBNEW_JOBS_HANDLER::JobFpDiff, this, std::placeholders::_1 ),
431 []( JOB* job, wxWindow* aParent ) -> bool
432 {
433 return true;
434 } );
435 Register( "drc", std::bind( &PCBNEW_JOBS_HANDLER::JobExportDrc, this, std::placeholders::_1 ),
436 []( JOB* job, wxWindow* aParent ) -> bool
437 {
438 JOB_PCB_DRC* drcJob = dynamic_cast<JOB_PCB_DRC*>( job );
439
440 wxCHECK( drcJob, false );
441
442 DIALOG_DRC_JOB_CONFIG dlg( aParent, drcJob );
443 return dlg.ShowModal() == wxID_OK;
444 } );
445 Register( "ipc2581", std::bind( &PCBNEW_JOBS_HANDLER::JobExportIpc2581, this, std::placeholders::_1 ),
446 [aKiway]( JOB* job, wxWindow* aParent ) -> bool
447 {
448 JOB_EXPORT_PCB_IPC2581* ipcJob = dynamic_cast<JOB_EXPORT_PCB_IPC2581*>( job );
449
450 PCB_EDIT_FRAME* editFrame =
451 dynamic_cast<PCB_EDIT_FRAME*>( aKiway->Player( FRAME_PCB_EDITOR, false ) );
452
453 wxCHECK( ipcJob && editFrame, false );
454
455 DIALOG_EXPORT_2581 dlg( ipcJob, editFrame, aParent );
456 return dlg.ShowModal() == wxID_OK;
457 } );
458 Register( "ipcd356", std::bind( &PCBNEW_JOBS_HANDLER::JobExportIpcD356, this, std::placeholders::_1 ),
459 []( JOB* job, wxWindow* aParent ) -> bool
460 {
461 return true;
462 } );
463 Register( "odb", std::bind( &PCBNEW_JOBS_HANDLER::JobExportOdb, this, std::placeholders::_1 ),
464 [aKiway]( JOB* job, wxWindow* aParent ) -> bool
465 {
466 JOB_EXPORT_PCB_ODB* odbJob = dynamic_cast<JOB_EXPORT_PCB_ODB*>( job );
467
468 PCB_EDIT_FRAME* editFrame =
469 dynamic_cast<PCB_EDIT_FRAME*>( aKiway->Player( FRAME_PCB_EDITOR, false ) );
470
471 wxCHECK( odbJob && editFrame, false );
472
473 DIALOG_EXPORT_ODBPP dlg( odbJob, editFrame, aParent );
474 return dlg.ShowModal() == wxID_OK;
475 } );
476 Register( "idf", std::bind( &PCBNEW_JOBS_HANDLER::JobExportIdf, this, std::placeholders::_1 ),
477 [aKiway]( JOB* job, wxWindow* aParent ) -> bool
478 {
479 JOB_EXPORT_PCB_IDF* idfJob = dynamic_cast<JOB_EXPORT_PCB_IDF*>( job );
480
481 PCB_EDIT_FRAME* editFrame =
482 dynamic_cast<PCB_EDIT_FRAME*>( aKiway->Player( FRAME_PCB_EDITOR, false ) );
483
484 wxCHECK( idfJob && editFrame, false );
485
486 DIALOG_EXPORT_IDF3 dlg( idfJob, editFrame, aParent );
487 return dlg.ShowModal() == wxID_OK;
488 } );
489}
490
491
495
496
498{
499 m_cliBoard.reset();
500 m_toolManager.reset();
501}
502
503
505{
506 TOOL_MANAGER* toolManager = nullptr;
507 if( Pgm().IsGUI() )
508 {
509 // we assume the PCB we are working on here is the one in the frame
510 // so use the frame's tool manager
511 PCB_EDIT_FRAME* editFrame = (PCB_EDIT_FRAME*) m_kiway->Player( FRAME_PCB_EDITOR, false );
512 if( editFrame )
513 toolManager = editFrame->GetToolManager();
514 }
515 else
516 {
517 if( m_toolManager == nullptr )
518 {
519 m_toolManager = std::make_unique<TOOL_MANAGER>();
520 }
521
522 toolManager = m_toolManager.get();
523
524 toolManager->SetEnvironment( aBrd, nullptr, nullptr, Kiface().KifaceSettings(), nullptr );
525 }
526 return toolManager;
527}
528
529
530BOARD* PCBNEW_JOBS_HANDLER::getBoard( const wxString& aPath )
531{
532 BOARD* brd = nullptr;
533 SETTINGS_MANAGER& settingsManager = Pgm().GetSettingsManager();
534 wxString loadError;
535
536 auto getProjectForBoard = [&]( const wxString& aBoardPath ) -> PROJECT*
537 {
538 wxFileName pro = aBoardPath;
539 pro.SetExt( FILEEXT::ProjectFileExtension );
540 pro.MakeAbsolute();
541
542 PROJECT* project = settingsManager.GetProject( pro.GetFullPath() );
543
544 if( !project )
545 {
546 settingsManager.LoadProject( pro.GetFullPath(), true );
547 project = settingsManager.GetProject( pro.GetFullPath() );
548 }
549
550 return project;
551 };
552
553 auto loadBoardFromPath = [&]( const wxString& aBoardPath ) -> BOARD*
554 {
555 PROJECT* project = getProjectForBoard( aBoardPath );
556
558
559 if( !project || pluginType == PCB_IO_MGR::FILE_TYPE_NONE )
560 return nullptr;
561
562 try
563 {
564 std::unique_ptr<BOARD> loadedBoard = BOARD_LOADER::Load( aBoardPath, pluginType, project );
565 return loadedBoard.release();
566 }
567 catch( const IO_ERROR& ioe )
568 {
569 loadError = ioe.What();
570 return nullptr;
571 }
572 catch( ... )
573 {
574 return nullptr;
575 }
576 };
577
578 if( !Pgm().IsGUI() && Pgm().GetSettingsManager().IsProjectOpen() )
579 {
580 wxString pcbPath = aPath;
581
582 if( pcbPath.IsEmpty() )
583 {
584 wxFileName path = Pgm().GetSettingsManager().Prj().GetProjectFullName();
586 path.MakeAbsolute();
587 pcbPath = path.GetFullPath();
588 }
589
590 if( !m_cliBoard )
591 m_cliBoard.reset( loadBoardFromPath( pcbPath ) );
592
593 brd = m_cliBoard.get();
594 }
595 else if( Pgm().IsGUI() && Pgm().GetSettingsManager().IsProjectOpen() )
596 {
597 PCB_EDIT_FRAME* editFrame = (PCB_EDIT_FRAME*) m_kiway->Player( FRAME_PCB_EDITOR, false );
598
599 if( editFrame )
600 brd = editFrame->GetBoard();
601 }
602 else
603 {
604 m_cliBoard.reset( loadBoardFromPath( aPath ) );
605 brd = m_cliBoard.get();
606 }
607
608 if( !brd )
609 {
610 wxString msg = _( "Failed to load board" );
611
612 if( !loadError.IsEmpty() )
613 msg += wxString::Format( wxS( ": %s" ), loadError );
614
615 m_reporter->Report( msg + '\n', RPT_SEVERITY_ERROR );
616 }
617
618 return brd;
619}
620
621
622LSEQ PCBNEW_JOBS_HANDLER::convertLayerArg( wxString& aLayerString, BOARD* aBoard ) const
623{
624 std::map<wxString, LSET> layerUserMasks;
625 std::map<wxString, LSET> layerMasks;
626 std::map<wxString, LSET> layerGuiMasks;
627
628 // Build list of layer names and their layer mask:
629 for( PCB_LAYER_ID layer : LSET::AllLayersMask() )
630 {
631 // Add user layer name
632 if( aBoard )
633 layerUserMasks[aBoard->GetLayerName( layer )] = LSET( { layer } );
634
635 // Add layer name used in pcb files
636 layerMasks[LSET::Name( layer )] = LSET( { layer } );
637 // Add layer name using GUI canonical layer name
638 layerGuiMasks[LayerName( layer )] = LSET( { layer } );
639 }
640
641 // Add list of grouped layer names used in pcb files
642 layerMasks[wxT( "*" )] = LSET::AllLayersMask();
643 layerMasks[wxT( "*.Cu" )] = LSET::AllCuMask();
644 layerMasks[wxT( "*In.Cu" )] = LSET::InternalCuMask();
645 layerMasks[wxT( "F&B.Cu" )] = LSET( { F_Cu, B_Cu } );
646 layerMasks[wxT( "*.Adhes" )] = LSET( { B_Adhes, F_Adhes } );
647 layerMasks[wxT( "*.Paste" )] = LSET( { B_Paste, F_Paste } );
648 layerMasks[wxT( "*.Mask" )] = LSET( { B_Mask, F_Mask } );
649 layerMasks[wxT( "*.SilkS" )] = LSET( { B_SilkS, F_SilkS } );
650 layerMasks[wxT( "*.Fab" )] = LSET( { B_Fab, F_Fab } );
651 layerMasks[wxT( "*.CrtYd" )] = LSET( { B_CrtYd, F_CrtYd } );
652
653 // Add list of grouped layer names using GUI canonical layer names
654 layerGuiMasks[wxT( "*.Adhesive" )] = LSET( { B_Adhes, F_Adhes } );
655 layerGuiMasks[wxT( "*.Silkscreen" )] = LSET( { B_SilkS, F_SilkS } );
656 layerGuiMasks[wxT( "*.Courtyard" )] = LSET( { B_CrtYd, F_CrtYd } );
657
658 LSEQ layerMask;
659
660 auto pushLayers = [&]( const LSET& layerSet )
661 {
662 for( PCB_LAYER_ID layer : layerSet.Seq() )
663 layerMask.push_back( layer );
664 };
665
666 if( !aLayerString.IsEmpty() )
667 {
668 wxStringTokenizer layerTokens( aLayerString, "," );
669
670 while( layerTokens.HasMoreTokens() )
671 {
672 std::string token = TO_UTF8( layerTokens.GetNextToken().Trim( true ).Trim( false ) );
673
674 if( layerUserMasks.contains( token ) )
675 pushLayers( layerUserMasks.at( token ) );
676 else if( layerMasks.count( token ) )
677 pushLayers( layerMasks.at( token ) );
678 else if( layerGuiMasks.count( token ) )
679 pushLayers( layerGuiMasks.at( token ) );
680 else
681 m_reporter->Report( wxString::Format( _( "Invalid layer name '%s'\n" ), token ) );
682 }
683 }
684
685 return layerMask;
686}
687
688
690{
691 JOB_EXPORT_BOM* aBomJob = dynamic_cast<JOB_EXPORT_BOM*>( aJob );
692
693 wxCHECK( aBomJob, CLI::EXIT_CODES::ERR_UNKNOWN );
694
695 BOARD* board = getBoard( aBomJob->m_filename );
696
697 if( !board )
699
700 aJob->SetTitleBlock( board->GetTitleBlock() );
701 board->GetProject()->ApplyTextVars( aJob->GetVarOverrides() );
702
703 wxString currentVariant = aBomJob->GetSelectedVariant();
704
705 if( !currentVariant.IsEmpty() && currentVariant != wxS( "all" ) )
706 board->SetCurrentVariant( currentVariant );
707
708 FOOTPRINT_REFERENCE_LIST referenceList;
709
710 // Annotation warning check (and gather footprints for data model)
711 bool hasWarned = false;
712 for( FOOTPRINT* fp : board->Footprints() )
713 {
714 referenceList.push_back( FOOTPRINT_REF( *fp ) );
715
716 if( !fp->IsAnnotated() && !hasWarned )
717 {
718 m_reporter->Report( _( "Warning: board has unannotated footprints, please use the PCB "
719 "editor to annotate them\n" ),
721 hasWarned = true;
722 }
723 }
724
725 // Build our data model
726 FOOTPRINT_FIELDS_EDITOR_GRID_DATA_MODEL dataModel( referenceList );
727 dataModel.SetCurrentVariant( currentVariant );
728
729 // Mandatory fields first
730 for( FIELD_T fieldId : MANDATORY_FIELDS )
731 dataModel.AddColumn( GetDefaultFieldName( fieldId, UNTRANSLATED ),
732 GetDefaultFieldName( fieldId, TRANSLATED ), false );
733
734 // Generated/virtual fields (e.g. ${QUANTITY}, ${ITEM_NUMBER}) present only in the fields table
737 false );
740 false );
741
742 // Attribute fields (boolean flags on footprints)
743 dataModel.AddColumn( wxS( "${DNP}" ), GetGeneratedFieldDisplayName( wxS( "${DNP}" ) ), false );
744 dataModel.AddColumn( wxS( "${EXCLUDE_FROM_BOM}" ), GetGeneratedFieldDisplayName( wxS( "${EXCLUDE_FROM_BOM}" ) ),
745 false );
746 dataModel.AddColumn( wxS( "${EXCLUDE_FROM_BOARD}" ), GetGeneratedFieldDisplayName( wxS( "${EXCLUDE_FROM_BOARD}" ) ),
747 false );
748 dataModel.AddColumn( wxS( "${EXCLUDE_FROM_SIM}" ), GetGeneratedFieldDisplayName( wxS( "${EXCLUDE_FROM_SIM}" ) ),
749 false );
750
751 // User field names in footprints second
752 std::set<wxString> userFieldNames;
753
754 for( size_t i = 0; i < referenceList.size(); ++i )
755 {
756 FOOTPRINT& footprint = referenceList[i].GetFootprint();
757
758 for( PCB_FIELD* field : footprint.GetFields() )
759 {
760 if( !field->IsMandatory() && !field->IsPrivate() )
761 userFieldNames.insert( field->GetName() );
762 }
763 }
764
765 for( const wxString& fieldName : userFieldNames )
766 dataModel.AddColumn( fieldName, GetGeneratedFieldDisplayName( fieldName ), true );
767
768 // Add any templateFieldNames which aren't already present in the userFieldNames
769 // TODO: template field names not implemented in board editor
770
771 BOM_PRESET preset;
772
773 // Load a preset if one is specified
774 if( !aBomJob->m_bomPresetName.IsEmpty() )
775 {
776 // Find the preset
777 std::optional<BOM_PRESET> boardPreset;
778
779 for( const BOM_PRESET& p : BOM_PRESET::BuiltInPresets() )
780 {
781 if( p.name == aBomJob->m_bomPresetName )
782 {
783 boardPreset = p;
784 break;
785 }
786 }
787
788 for( const BOM_PRESET& p : board->GetDesignSettings().m_BomPresets )
789 {
790 if( p.name == aBomJob->m_bomPresetName )
791 {
792 boardPreset = p;
793 break;
794 }
795 }
796
797 if( !boardPreset )
798 {
799 m_reporter->Report(
800 wxString::Format( _( "BOM preset '%s' not found" ) + wxS( "\n" ), aBomJob->m_bomPresetName ),
802
804 }
805
806 preset = *boardPreset;
807 }
808 else
809 {
810 // Normalize field names so that bare generated-field tokens (e.g. "QUANTITY") are
811 // accepted alongside the canonical "${QUANTITY}" form. Shell expansion of ${VAR}
812 // inside double quotes silently produces an empty string, so this also guards against
813 // that common CLI pitfall.
814 auto normalizeFieldName = [&dataModel]( const wxString& aName ) -> wxString
815 {
816 if( aName.IsEmpty() )
817 return wxEmptyString;
818
819 if( IsGeneratedField( aName ) )
820 return aName;
821
822 wxString wrapped = wxS( "${" ) + aName + wxS( "}" );
823
824 if( IsGeneratedField( wrapped ) && dataModel.GetFieldNameCol( wrapped ) != -1 )
825 return wrapped;
826
827 return aName;
828 };
829
830 size_t i = 0;
831
832 for( const wxString& rawFieldName : aBomJob->m_fieldsOrdered )
833 {
834 wxString fieldName = normalizeFieldName( rawFieldName );
835
836 if( fieldName.IsEmpty() )
837 {
838 i++;
839 continue;
840 }
841
842 // Handle wildcard. We allow the wildcard anywhere in the list, but it needs to respect
843 // fields that come before and after the wildcard.
844 if( fieldName == wxS( "*" ) )
845 {
846 for( const BOM_FIELD& modelField : dataModel.GetFieldsOrdered() )
847 {
848 struct BOM_FIELD field;
849
850 field.name = modelField.name;
851 field.show = true;
852 field.groupBy = false;
853 field.label = field.name;
854
855 bool fieldAlreadyPresent = false;
856
857 for( BOM_FIELD& presetField : preset.fieldsOrdered )
858 {
859 if( presetField.name == field.name )
860 {
861 fieldAlreadyPresent = true;
862 break;
863 }
864 }
865
866 bool fieldLaterInList = false;
867
868 for( const wxString& fieldInList : aBomJob->m_fieldsOrdered )
869 {
870 if( normalizeFieldName( fieldInList ) == field.name )
871 {
872 fieldLaterInList = true;
873 break;
874 }
875 }
876
877 if( !fieldAlreadyPresent && !fieldLaterInList )
878 preset.fieldsOrdered.emplace_back( field );
879 }
880
881 continue;
882 }
883
884 struct BOM_FIELD field;
885
886 field.name = fieldName;
887 field.show = !fieldName.StartsWith( wxT( "__" ), &field.name );
888 field.name = normalizeFieldName( field.name );
889
890 if( ( aBomJob->m_fieldsLabels.size() > i ) && !aBomJob->m_fieldsLabels[i].IsEmpty() )
891 field.label = aBomJob->m_fieldsLabels[i];
892 else if( IsGeneratedField( field.name ) )
893 field.label = GetGeneratedFieldDisplayName( field.name );
894 else
895 field.label = field.name;
896
897 preset.fieldsOrdered.emplace_back( field );
898 i++;
899 }
900
901 for( const wxString& rawFieldName : aBomJob->m_fieldsGroupBy )
902 {
903 wxString fieldName = rawFieldName;
904 fieldName.StartsWith( wxT( "__" ), &fieldName );
905 fieldName = normalizeFieldName( fieldName );
906
907 if( fieldName.IsEmpty() )
908 continue;
909
910 bool fieldAlreadyPresent = false;
911
912 for( BOM_FIELD& field : preset.fieldsOrdered )
913 {
914 if( field.name == fieldName )
915 {
916 field.groupBy = true;
917 fieldAlreadyPresent = true;
918 break;
919 }
920 }
921
922 if( !fieldAlreadyPresent )
923 preset.fieldsOrdered.push_back( { fieldName, GetGeneratedFieldDisplayName( fieldName ), false, true } );
924 }
925
926 preset.sortAsc = aBomJob->m_sortAsc;
927 preset.sortField = normalizeFieldName( aBomJob->m_sortField );
928 preset.filterString = aBomJob->m_filterString;
929 preset.filterScope = aBomJob->m_filterScope;
930 preset.groupSymbols = aBomJob->m_groupSymbols;
931 preset.excludeDNP = aBomJob->m_excludeDNP;
932 }
933
934 BOM_FMT_PRESET fmt;
935
936 // Load a format preset if one is specified
937 if( !aBomJob->m_bomFmtPresetName.IsEmpty() )
938 {
939 std::optional<BOM_FMT_PRESET> boardFmtPreset;
940
942 {
943 if( p.name == aBomJob->m_bomFmtPresetName )
944 {
945 boardFmtPreset = p;
946 break;
947 }
948 }
949
950 for( const BOM_FMT_PRESET& p : board->GetDesignSettings().m_BomFmtPresets )
951 {
952 if( p.name == aBomJob->m_bomFmtPresetName )
953 {
954 boardFmtPreset = p;
955 break;
956 }
957 }
958
959 if( !boardFmtPreset )
960 {
961 m_reporter->Report( wxString::Format( _( "BOM format preset '%s' not found" ) + wxS( "\n" ),
962 aBomJob->m_bomFmtPresetName ),
964
966 }
967
968 fmt = *boardFmtPreset;
969 }
970 else
971 {
972 fmt.fieldDelimiter = aBomJob->m_fieldDelimiter;
973 fmt.stringDelimiter = aBomJob->m_stringDelimiter;
974 fmt.refDelimiter = aBomJob->m_refDelimiter;
976 fmt.keepTabs = aBomJob->m_keepTabs;
977 fmt.keepLineBreaks = aBomJob->m_keepLineBreaks;
979 }
980
981 if( aBomJob->GetConfiguredOutputPath().IsEmpty() )
982 {
983 wxFileName fn = board->GetFileName();
984 fn.SetName( fn.GetName() );
985 fn.SetExt( FILEEXT::CsvFileExtension );
986
987 aBomJob->SetConfiguredOutputPath( fn.GetFullName() );
988 }
989
990 wxString configuredPath = aBomJob->GetConfiguredOutputPath();
991 bool hasVariantPlaceholder = configuredPath.Contains( wxS( "${VARIANT}" ) );
992
993 // Determine which variants to process
994 std::vector<wxString> variantsToProcess;
995
996 if( aBomJob->m_variantNames.size() > 1 && hasVariantPlaceholder )
997 {
998 variantsToProcess = aBomJob->m_variantNames;
999 }
1000 else
1001 {
1002 variantsToProcess.push_back( currentVariant );
1003 }
1004
1005 for( const wxString& variantName : variantsToProcess )
1006 {
1007 std::vector<wxString> singleVariant = { variantName };
1008 dataModel.SetVariantNames( singleVariant );
1009 dataModel.SetCurrentVariant( variantName );
1010 dataModel.UpdateReferences( dataModel.GetReferenceList() );
1011 dataModel.ApplyBomPreset( preset );
1012
1013 wxString outPath;
1014
1015 if( hasVariantPlaceholder )
1016 {
1017 wxString variantPath = ExpandVariantOutputPath( configuredPath, variantName );
1018 aBomJob->SetConfiguredOutputPath( variantPath );
1019 outPath = aBomJob->GetFullOutputPath( board->GetProject() );
1020 aBomJob->SetConfiguredOutputPath( configuredPath );
1021 }
1022 else
1023 {
1024 outPath = aBomJob->GetFullOutputPath( board->GetProject() );
1025 }
1026
1027 if( !PATHS::EnsurePathExists( outPath, true ) )
1028 {
1029 m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
1031 }
1032
1033 wxFile f;
1034
1035 if( !f.Open( outPath, wxFile::write ) )
1036 {
1037 m_reporter->Report( wxString::Format( _( "Unable to open destination '%s'" ), outPath ),
1039
1041 }
1042
1043 bool res = f.Write( dataModel.Export( fmt ) );
1044
1045 if( !res )
1047
1048 aJob->AddOutput( outPath );
1049
1050 m_reporter->Report( wxString::Format( _( "Wrote bill of materials to '%s'." ), outPath ), RPT_SEVERITY_ACTION );
1051 }
1052
1053 return CLI::EXIT_CODES::OK;
1054}
1055
1056
1058{
1059 JOB_EXPORT_PCB_3D* aStepJob = dynamic_cast<JOB_EXPORT_PCB_3D*>( aJob );
1060
1061 if( aStepJob == nullptr )
1063
1064 BOARD* brd = getBoard( aStepJob->m_filename );
1065
1066 if( !brd )
1068
1069 if( !aStepJob->m_variant.IsEmpty() )
1070 brd->SetCurrentVariant( aStepJob->m_variant );
1071
1072 if( aStepJob->GetConfiguredOutputPath().IsEmpty() )
1073 {
1074 wxFileName fn = brd->GetFileName();
1075 fn.SetName( fn.GetName() );
1076
1077 switch( aStepJob->m_format )
1078 {
1088 default:
1089 m_reporter->Report( _( "Unknown export format" ), RPT_SEVERITY_ERROR );
1090 return CLI::EXIT_CODES::ERR_UNKNOWN; // shouldnt have gotten here
1091 }
1092
1093 aStepJob->SetWorkingOutputPath( fn.GetFullName() );
1094 }
1095
1096 wxString outPath = resolveJobOutputPath( aJob, brd );
1097
1098 if( !PATHS::EnsurePathExists( outPath, true ) )
1099 {
1100 m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
1102 }
1103
1104 if( aStepJob->m_format == JOB_EXPORT_PCB_3D::FORMAT::VRML )
1105 {
1106 double scale = 0.0;
1107 switch( aStepJob->m_vrmlUnits )
1108 {
1109 case JOB_EXPORT_PCB_3D::VRML_UNITS::MM: scale = 1.0; break;
1110 case JOB_EXPORT_PCB_3D::VRML_UNITS::METERS: scale = 0.001; break;
1111 case JOB_EXPORT_PCB_3D::VRML_UNITS::TENTHS: scale = 10.0 / 25.4; break;
1112 case JOB_EXPORT_PCB_3D::VRML_UNITS::INCH: scale = 1.0 / 25.4; break;
1113 }
1114
1115 EXPORTER_VRML vrmlExporter( brd );
1116 wxString messages;
1117
1118 double originX = pcbIUScale.IUTomm( aStepJob->m_3dparams.m_Origin.x );
1119 double originY = pcbIUScale.IUTomm( aStepJob->m_3dparams.m_Origin.y );
1120
1121 if( !aStepJob->m_hasUserOrigin )
1122 {
1123 BOX2I bbox = brd->ComputeBoundingBox( true, true );
1124 originX = pcbIUScale.IUTomm( bbox.GetCenter().x );
1125 originY = pcbIUScale.IUTomm( bbox.GetCenter().y );
1126 }
1127
1128 bool success = vrmlExporter.ExportVRML_File(
1129 brd->GetProject(), &messages, outPath, scale, aStepJob->m_3dparams.m_IncludeUnspecified,
1130 aStepJob->m_3dparams.m_IncludeDNP, !aStepJob->m_vrmlModelDir.IsEmpty(), aStepJob->m_vrmlRelativePaths,
1131 aStepJob->m_vrmlModelDir, originX, originY );
1132
1133 if( success )
1134 {
1135 m_reporter->Report( wxString::Format( _( "Successfully exported VRML to %s" ), outPath ),
1137 }
1138 else
1139 {
1140 m_reporter->Report( _( "Error exporting VRML" ), RPT_SEVERITY_ERROR );
1142 }
1143 }
1144 else
1145 {
1146 EXPORTER_STEP_PARAMS params = aStepJob->m_3dparams;
1147
1148 switch( aStepJob->m_format )
1149 {
1159 default:
1160 m_reporter->Report( _( "Unknown export format" ), RPT_SEVERITY_ERROR );
1161 return CLI::EXIT_CODES::ERR_UNKNOWN; // shouldnt have gotten here
1162 }
1163
1164 EXPORTER_STEP stepExporter( brd, params, m_reporter );
1165 stepExporter.m_outputFile = aStepJob->GetFullOutputPath( brd->GetProject() );
1166
1167 if( !stepExporter.Export() )
1169 }
1170
1171 return CLI::EXIT_CODES::OK;
1172}
1173
1174
1176{
1177 JOB_PCB_RENDER* aRenderJob = dynamic_cast<JOB_PCB_RENDER*>( aJob );
1178
1179 if( aRenderJob == nullptr )
1181
1182 // Reject width and height being invalid
1183 // Final bit of sanity because this can blow things up
1184 if( aRenderJob->m_width <= 0 || aRenderJob->m_height <= 0 )
1185 {
1186 m_reporter->Report( _( "Invalid image dimensions" ), RPT_SEVERITY_ERROR );
1188 }
1189
1190 BOARD* brd = getBoard( aRenderJob->m_filename );
1191
1192 if( !brd )
1194
1195 if( !aRenderJob->m_variant.IsEmpty() )
1196 brd->SetCurrentVariant( aRenderJob->m_variant );
1197
1198 if( aRenderJob->GetConfiguredOutputPath().IsEmpty() )
1199 {
1200 wxFileName fn = brd->GetFileName();
1201
1202 switch( aRenderJob->m_format )
1203 {
1206 default:
1207 m_reporter->Report( _( "Unknown export format" ), RPT_SEVERITY_ERROR );
1208 return CLI::EXIT_CODES::ERR_UNKNOWN; // shouldnt have gotten here
1209 }
1210
1211 // set the name to board name + "side", its lazy but its hard to generate anything truely unique
1212 // incase someone is doing this in a jobset with multiple jobs, they should be setting the output themselves
1213 // or we do a hash based on all the options
1214 fn.SetName( wxString::Format( "%s-%d", fn.GetName(), static_cast<int>( aRenderJob->m_side ) ) );
1215
1216 aRenderJob->SetWorkingOutputPath( fn.GetFullName() );
1217 }
1218
1219 wxString outPath = resolveJobOutputPath( aJob, brd );
1220
1221 if( !PATHS::EnsurePathExists( outPath, true ) )
1222 {
1223 m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
1225 }
1226
1227 BOARD_ADAPTER boardAdapter;
1228
1229 boardAdapter.SetBoard( brd );
1230 boardAdapter.m_IsBoardView = false;
1231
1233
1235 {
1236 cfg.m_Render = userCfg->m_Render;
1237 cfg.m_Camera = userCfg->m_Camera;
1238 cfg.m_LayerPresets = userCfg->m_LayerPresets;
1239 }
1240
1241 if( aRenderJob->m_appearancePreset.empty() )
1242 {
1243 // Force display 3D models
1245 cfg.m_Render.show_footprints_dnp = true;
1249 }
1250
1251 if( aRenderJob->m_quality == JOB_PCB_RENDER::QUALITY::BASIC )
1252 {
1253 // Silkscreen is pixelated without antialiasing
1255
1256 cfg.m_Render.raytrace_backfloor = aRenderJob->m_floor;
1257 cfg.m_Render.raytrace_post_processing = aRenderJob->m_floor;
1258
1260 cfg.m_Render.raytrace_reflections = false;
1261 cfg.m_Render.raytrace_shadows = aRenderJob->m_floor;
1262
1263 // Better colors
1265
1266 // Tracks below soldermask are not visible without refractions
1267 cfg.m_Render.raytrace_refractions = true;
1269 }
1270 else if( aRenderJob->m_quality == JOB_PCB_RENDER::QUALITY::HIGH )
1271 {
1273 cfg.m_Render.raytrace_backfloor = true;
1276 cfg.m_Render.raytrace_reflections = true;
1277 cfg.m_Render.raytrace_shadows = true;
1278 cfg.m_Render.raytrace_refractions = true;
1280 }
1281 else if( aRenderJob->m_quality == JOB_PCB_RENDER::QUALITY::JOB_SETTINGS )
1282 {
1284 cfg.m_Render.raytrace_backfloor = aRenderJob->m_floor;
1287 }
1288
1290 aRenderJob->m_lightTopIntensity.z, 1.0 );
1291
1293 COLOR4D( aRenderJob->m_lightBottomIntensity.x, aRenderJob->m_lightBottomIntensity.y,
1294 aRenderJob->m_lightBottomIntensity.z, 1.0 );
1295
1297 COLOR4D( aRenderJob->m_lightCameraIntensity.x, aRenderJob->m_lightCameraIntensity.y,
1298 aRenderJob->m_lightCameraIntensity.z, 1.0 );
1299
1300 COLOR4D lightColor( aRenderJob->m_lightSideIntensity.x, aRenderJob->m_lightSideIntensity.y,
1301 aRenderJob->m_lightSideIntensity.z, 1.0 );
1302
1304 lightColor, lightColor, lightColor, lightColor, lightColor, lightColor, lightColor, lightColor,
1305 };
1306
1307 int sideElevation = aRenderJob->m_lightSideElevation;
1308
1310 sideElevation, sideElevation, sideElevation, sideElevation,
1311 -sideElevation, -sideElevation, -sideElevation, -sideElevation,
1312 };
1313
1315 45, 135, 225, 315, 45, 135, 225, 315,
1316 };
1317
1318 cfg.m_CurrentPreset = aRenderJob->m_appearancePreset;
1320 boardAdapter.m_Cfg = &cfg;
1321
1322 // Apply the preset's layer visibility and colors to the render settings
1323 if( !aRenderJob->m_appearancePreset.empty() )
1324 {
1325 wxString presetName = wxString::FromUTF8( aRenderJob->m_appearancePreset );
1326
1327 if( presetName == FOLLOW_PCB || presetName == FOLLOW_PLOT_SETTINGS )
1328 {
1329 boardAdapter.SetVisibleLayers( boardAdapter.GetVisibleLayers() );
1330 }
1331 else if( LAYER_PRESET_3D* preset = cfg.FindPreset( presetName ) )
1332 {
1333 boardAdapter.SetVisibleLayers( preset->layers );
1334 boardAdapter.SetLayerColors( preset->colors );
1335
1336 if( preset->name.Lower() == _( "legacy colors" ) )
1337 cfg.m_UseStackupColors = false;
1338 }
1339 }
1340
1343 && aRenderJob->m_format == JOB_PCB_RENDER::FORMAT::PNG ) )
1344 {
1345 boardAdapter.m_ColorOverrides[LAYER_3D_BACKGROUND_TOP] = COLOR4D( 1.0, 1.0, 1.0, 0.0 );
1346 boardAdapter.m_ColorOverrides[LAYER_3D_BACKGROUND_BOTTOM] = COLOR4D( 1.0, 1.0, 1.0, 0.0 );
1347 }
1348
1350
1351 static std::map<JOB_PCB_RENDER::SIDE, VIEW3D_TYPE> s_viewCmdMap = {
1358 };
1359
1361
1362 wxSize windowSize( aRenderJob->m_width, aRenderJob->m_height );
1363 TRACK_BALL camera( 2 * RANGE_SCALE_3D );
1364
1365 camera.SetProjection( projection );
1366 camera.SetCurWindowSize( windowSize );
1367
1368 RENDER_3D_RAYTRACE_RAM raytrace( boardAdapter, camera );
1369 raytrace.SetCurWindowSize( windowSize );
1370
1371 std::shared_ptr<REPORTER> reporter( m_reporter, []( REPORTER* ) {} );
1372 raytrace.SetReporters( reporter, reporter );
1373
1374 for( bool first = true; raytrace.Redraw( false ); first = false )
1375 {
1376 if( first )
1377 {
1378 const float cmTo3D = boardAdapter.BiuTo3dUnits() * pcbIUScale.mmToIU( 10.0 );
1379
1380 // First redraw resets lookat point to the board center, so set up the camera here
1381 camera.ViewCommand_T1( s_viewCmdMap[aRenderJob->m_side] );
1382
1383 camera.SetLookAtPos_T1( camera.GetLookAtPos_T1()
1384 + SFVEC3F( aRenderJob->m_pivot.x, aRenderJob->m_pivot.y, aRenderJob->m_pivot.z )
1385 * cmTo3D );
1386
1387 camera.Pan_T1( SFVEC3F( aRenderJob->m_pan.x, aRenderJob->m_pan.y, aRenderJob->m_pan.z ) );
1388
1389 camera.Zoom_T1( aRenderJob->m_zoom );
1390
1391 camera.RotateX_T1( DEG2RAD( aRenderJob->m_rotation.x ) );
1392 camera.RotateY_T1( DEG2RAD( aRenderJob->m_rotation.y ) );
1393 camera.RotateZ_T1( DEG2RAD( aRenderJob->m_rotation.z ) );
1394
1395 camera.Interpolate( 1.0f );
1396 camera.SetT0_and_T1_current_T();
1397 camera.ParametersChanged();
1398 }
1399 }
1400
1401 uint8_t* rgbaBuffer = raytrace.GetBuffer();
1402 wxSize realSize = raytrace.GetRealBufferSize();
1403 bool success = !!rgbaBuffer;
1404
1405 if( rgbaBuffer )
1406 {
1407 const unsigned int wxh = realSize.x * realSize.y;
1408
1409 unsigned char* rgbBuffer = (unsigned char*) malloc( wxh * 3 );
1410 unsigned char* alphaBuffer = (unsigned char*) malloc( wxh );
1411
1412 unsigned char* rgbaPtr = rgbaBuffer;
1413 unsigned char* rgbPtr = rgbBuffer;
1414 unsigned char* alphaPtr = alphaBuffer;
1415
1416 for( int y = 0; y < realSize.y; y++ )
1417 {
1418 for( int x = 0; x < realSize.x; x++ )
1419 {
1420 rgbPtr[0] = rgbaPtr[0];
1421 rgbPtr[1] = rgbaPtr[1];
1422 rgbPtr[2] = rgbaPtr[2];
1423 alphaPtr[0] = rgbaPtr[3];
1424
1425 rgbaPtr += 4;
1426 rgbPtr += 3;
1427 alphaPtr += 1;
1428 }
1429 }
1430
1431 wxImage image( realSize );
1432 image.SetData( rgbBuffer );
1433 image.SetAlpha( alphaBuffer );
1434 image = image.Mirror( false );
1435
1436 image.SetOption( wxIMAGE_OPTION_QUALITY, 90 );
1437 image.SaveFile( outPath,
1438 aRenderJob->m_format == JOB_PCB_RENDER::FORMAT::PNG ? wxBITMAP_TYPE_PNG : wxBITMAP_TYPE_JPEG );
1439 }
1440
1441 if( success )
1442 {
1443 m_reporter->Report( _( "Successfully created 3D render image" ) + wxS( "\n" ), RPT_SEVERITY_INFO );
1444 return CLI::EXIT_CODES::OK;
1445 }
1446 else
1447 {
1448 m_reporter->Report( _( "Error creating 3D render image" ) + wxS( "\n" ), RPT_SEVERITY_ERROR );
1450 }
1451}
1452
1453
1455 TOOL_MANAGER* aToolManager )
1456{
1457 if( aJob->m_checkZonesBeforePlot )
1458 {
1459 if( !aToolManager->FindTool( ZONE_FILLER_TOOL_NAME ) )
1460 aToolManager->RegisterTool( new ZONE_FILLER_TOOL );
1461
1462 aToolManager->GetTool<ZONE_FILLER_TOOL>()->FillAllZones( nullptr, m_progressReporter, true );
1463 }
1464
1465 if( aJob->m_argLayers )
1466 aJob->m_plotLayerSequence = convertLayerArg( aJob->m_argLayers.value(), aBoard );
1467
1468 if( aJob->m_argCommonLayers )
1469 aJob->m_plotOnAllLayersSequence = convertLayerArg( aJob->m_argCommonLayers.value(), aBoard );
1470
1471 if( aJob->m_plotLayerSequence.empty() )
1472 {
1473 m_reporter->Report( _( "At least one layer must be specified\n" ), RPT_SEVERITY_ERROR );
1474 return false;
1475 }
1476
1477 return true;
1478}
1479
1480
1482 PLOT_FORMAT aFormat, bool aSingleOutput )
1483{
1484 if( aSingleOutput && aJob->GetConfiguredOutputPath().IsEmpty() )
1485 {
1486 wxFileName fn = aBoard->GetFileName();
1487 fn.SetExt( GetDefaultPlotExtension( aFormat ) );
1488 aJob->SetWorkingOutputPath( fn.GetFullName() );
1489 }
1490
1491 return resolveJobOutputPath( aJob, aBoard, &aJob->m_drawingSheet );
1492}
1493
1494
1495// PDF single-document output need not use overrides; PNG never uses them.
1496static int plotJob( JOB_EXPORT_PCB_PLOT* aJob, PCB_PLOTTER& aPlotter, const wxString& aOutPath,
1497 bool aSingleOutput, bool aUseOverrides )
1498{
1499 std::optional<wxString> layerName;
1500 std::optional<wxString> sheetName;
1501 std::optional<wxString> sheetPath;
1502
1503 if( aUseOverrides )
1504 {
1505 if( aJob->GetVarOverrides().contains( wxT( "LAYER" ) ) )
1506 layerName = aJob->GetVarOverrides().at( wxT( "LAYER" ) );
1507
1508 if( aJob->GetVarOverrides().contains( wxT( "SHEETNAME" ) ) )
1509 sheetName = aJob->GetVarOverrides().at( wxT( "SHEETNAME" ) );
1510
1511 if( aJob->GetVarOverrides().contains( wxT( "SHEETPATH" ) ) )
1512 sheetPath = aJob->GetVarOverrides().at( wxT( "SHEETPATH" ) );
1513 }
1514
1515 std::vector<wxString> outputPaths;
1516
1517 if( !aPlotter.Plot( aOutPath, aJob->m_plotLayerSequence, aJob->m_plotOnAllLayersSequence, false,
1518 aSingleOutput, layerName, sheetName, sheetPath, &outputPaths ) )
1519 {
1521 }
1522
1523 for( const wxString& outputPath : outputPaths )
1524 aJob->AddOutput( outputPath );
1525
1526 return CLI::EXIT_CODES::OK;
1527}
1528
1529
1531{
1532 JOB_EXPORT_PCB_SVG* aSvgJob = dynamic_cast<JOB_EXPORT_PCB_SVG*>( aJob );
1533
1534 if( aSvgJob == nullptr )
1536
1537 BOARD* brd = getBoard( aSvgJob->m_filename );
1538 TOOL_MANAGER* toolManager = getToolManager( brd );
1539
1540 if( !brd )
1542
1543 if( !aSvgJob->m_variant.IsEmpty() )
1544 brd->SetCurrentVariant( aSvgJob->m_variant );
1545
1546 const bool isSingle = aSvgJob->m_genMode == JOB_EXPORT_PCB_SVG::GEN_MODE::SINGLE;
1547
1548 wxString outPath = resolvePlotOutputPath( aSvgJob, brd, PLOT_FORMAT::SVG, isSingle );
1549
1550 if( !PATHS::EnsurePathExists( outPath, isSingle ) )
1551 {
1552 m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
1554 }
1555
1556 if( !preparePlotLayers( aSvgJob, brd, toolManager ) )
1558
1559 PCB_PLOT_PARAMS plotOpts;
1560 PCB_PLOTTER::PlotJobToPlotOpts( plotOpts, aSvgJob, *m_reporter );
1561
1562 PCB_PLOTTER plotter( brd, m_reporter, plotOpts );
1563
1564 return plotJob( aSvgJob, plotter, outPath, isSingle, isSingle );
1565}
1566
1567
1569{
1570 JOB_EXPORT_PCB_DXF* aDxfJob = dynamic_cast<JOB_EXPORT_PCB_DXF*>( aJob );
1571
1572 if( aDxfJob == nullptr )
1574
1575 BOARD* brd = getBoard( aDxfJob->m_filename );
1576
1577 if( !brd )
1579
1580 if( !aDxfJob->m_variant.IsEmpty() )
1581 brd->SetCurrentVariant( aDxfJob->m_variant );
1582
1583 TOOL_MANAGER* toolManager = getToolManager( brd );
1584
1585 if( !preparePlotLayers( aDxfJob, brd, toolManager ) )
1587
1588 const bool isSingle = aDxfJob->m_genMode == JOB_EXPORT_PCB_DXF::GEN_MODE::SINGLE;
1589
1590 wxString outPath = resolvePlotOutputPath( aDxfJob, brd, PLOT_FORMAT::DXF, isSingle );
1591
1592 if( !PATHS::EnsurePathExists( outPath, isSingle ) )
1593 {
1594 m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
1596 }
1597
1598 PCB_PLOT_PARAMS plotOpts;
1599 PCB_PLOTTER::PlotJobToPlotOpts( plotOpts, aDxfJob, *m_reporter );
1600
1601 PCB_PLOTTER plotter( brd, m_reporter, plotOpts );
1602
1603 return plotJob( aDxfJob, plotter, outPath, isSingle, isSingle );
1604}
1605
1606
1608{
1609 bool plotAllLayersOneFile = false;
1610 JOB_EXPORT_PCB_PDF* pdfJob = dynamic_cast<JOB_EXPORT_PCB_PDF*>( aJob );
1611
1612 if( pdfJob == nullptr )
1614
1615 BOARD* brd = getBoard( pdfJob->m_filename );
1616
1617 if( !brd )
1619
1620 if( !pdfJob->m_variant.IsEmpty() )
1621 brd->SetCurrentVariant( pdfJob->m_variant );
1622
1623 TOOL_MANAGER* toolManager = getToolManager( brd );
1624
1625 if( !preparePlotLayers( pdfJob, brd, toolManager ) )
1627
1629 plotAllLayersOneFile = true;
1630
1631 const bool outputIsSingle = plotAllLayersOneFile || pdfJob->m_pdfSingle;
1632
1633 wxString outPath = resolvePlotOutputPath( pdfJob, brd, PLOT_FORMAT::PDF, outputIsSingle );
1634
1635 PCB_PLOT_PARAMS plotOpts;
1636 PCB_PLOTTER::PlotJobToPlotOpts( plotOpts, pdfJob, *m_reporter );
1637
1638 PCB_PLOTTER pcbPlotter( brd, m_reporter, plotOpts );
1639
1640 if( !PATHS::EnsurePathExists( outPath, outputIsSingle ) )
1641 {
1642 m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
1644 }
1645
1646 return plotJob( pdfJob, pcbPlotter, outPath, outputIsSingle, plotAllLayersOneFile );
1647}
1648
1649
1651{
1652 JOB_EXPORT_PCB_PNG* pngJob = dynamic_cast<JOB_EXPORT_PCB_PNG*>( aJob );
1653
1654 if( pngJob == nullptr )
1656
1657 BOARD* brd = getBoard( pngJob->m_filename );
1658
1659 if( !brd )
1661
1662 if( !pngJob->m_variant.IsEmpty() )
1663 brd->SetCurrentVariant( pngJob->m_variant );
1664
1665 TOOL_MANAGER* toolManager = getToolManager( brd );
1666
1667 if( !preparePlotLayers( pngJob, brd, toolManager ) )
1669
1670 bool isSingle = pngJob->m_genMode == JOB_EXPORT_PCB_PNG::GEN_MODE::SINGLE;
1671
1672 wxString outPath = resolvePlotOutputPath( pngJob, brd, PLOT_FORMAT::PNG, isSingle );
1673
1674 PCB_PLOT_PARAMS plotOpts;
1675 PCB_PLOTTER::PlotJobToPlotOpts( plotOpts, pngJob, *m_reporter );
1676
1677 PCB_PLOTTER pcbPlotter( brd, m_reporter, plotOpts );
1678
1679 if( !PATHS::EnsurePathExists( outPath, isSingle ) )
1680 {
1681 m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
1683 }
1684
1685 return plotJob( pngJob, pcbPlotter, outPath, isSingle, false );
1686}
1687
1688
1690{
1691 JOB_EXPORT_PCB_PS* psJob = dynamic_cast<JOB_EXPORT_PCB_PS*>( aJob );
1692
1693 if( psJob == nullptr )
1695
1696 BOARD* brd = getBoard( psJob->m_filename );
1697
1698 if( !brd )
1700
1701 if( !psJob->m_variant.IsEmpty() )
1702 brd->SetCurrentVariant( psJob->m_variant );
1703
1704 TOOL_MANAGER* toolManager = getToolManager( brd );
1705
1706 if( !preparePlotLayers( psJob, brd, toolManager ) )
1708
1709 bool isSingle = psJob->m_genMode == JOB_EXPORT_PCB_PS::GEN_MODE::SINGLE;
1710
1711 wxString outPath = resolvePlotOutputPath( psJob, brd, PLOT_FORMAT::POST, isSingle );
1712
1713 if( !PATHS::EnsurePathExists( outPath, isSingle ) )
1714 {
1715 m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
1717 }
1718
1719 PCB_PLOT_PARAMS plotOpts;
1720 PCB_PLOTTER::PlotJobToPlotOpts( plotOpts, psJob, *m_reporter );
1721
1722 PCB_PLOTTER pcbPlotter( brd, m_reporter, plotOpts );
1723
1724 return plotJob( psJob, pcbPlotter, outPath, isSingle, isSingle );
1725}
1726
1727
1729{
1730 int exitCode = CLI::EXIT_CODES::OK;
1731 JOB_EXPORT_PCB_GERBERS* aGerberJob = dynamic_cast<JOB_EXPORT_PCB_GERBERS*>( aJob );
1732
1733 if( aGerberJob == nullptr )
1735
1736 BOARD* brd = getBoard( aGerberJob->m_filename );
1737
1738 if( !brd )
1740
1741 if( !aGerberJob->m_variant.IsEmpty() )
1742 brd->SetCurrentVariant( aGerberJob->m_variant );
1743
1744 wxString outPath = resolveJobOutputPath( aJob, brd, &aGerberJob->m_drawingSheet );
1745
1746 if( !PATHS::EnsurePathExists( outPath, false ) )
1747 {
1748 m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
1750 }
1751
1752 TOOL_MANAGER* toolManager = getToolManager( brd );
1753
1754 if( aGerberJob->m_checkZonesBeforePlot )
1755 {
1756 if( !toolManager->FindTool( ZONE_FILLER_TOOL_NAME ) )
1757 toolManager->RegisterTool( new ZONE_FILLER_TOOL );
1758
1759 toolManager->GetTool<ZONE_FILLER_TOOL>()->FillAllZones( nullptr, m_progressReporter, true );
1760 }
1761
1762 bool hasLayerListSpecified = false; // will be true if the user layer list is not empty
1763
1764 if( aGerberJob->m_argLayers )
1765 {
1766 if( !aGerberJob->m_argLayers.value().empty() )
1767 {
1768 aGerberJob->m_plotLayerSequence = convertLayerArg( aGerberJob->m_argLayers.value(), brd );
1769 hasLayerListSpecified = true;
1770 }
1771 else
1772 {
1774 }
1775 }
1776
1777 if( aGerberJob->m_argCommonLayers )
1778 aGerberJob->m_plotOnAllLayersSequence = convertLayerArg( aGerberJob->m_argCommonLayers.value(), brd );
1779
1780 PCB_PLOT_PARAMS boardPlotOptions = brd->GetPlotOptions();
1781 GERBER_JOBFILE_WRITER jobfile_writer( brd );
1782
1783 wxString fileExt;
1784
1785 if( aGerberJob->m_useBoardPlotParams )
1786 {
1787 // The board plot options are saved with all copper layers enabled, even those that don't
1788 // exist in the current stackup. This is done so the layers are automatically enabled in the plot
1789 // dialog when the user enables them. We need to filter out these not-enabled layers here so
1790 // we don't plot 32 layers when we only have 4, etc.
1791 LSET plotLayers = ( boardPlotOptions.GetLayerSelection() & LSET::AllNonCuMask() )
1792 | ( brd->GetEnabledLayers() & LSET::AllCuMask() );
1793 aGerberJob->m_plotLayerSequence = plotLayers.SeqStackupForPlotting();
1794 aGerberJob->m_plotOnAllLayersSequence = boardPlotOptions.GetPlotOnAllLayersSequence();
1795 }
1796 else
1797 {
1798 // default to the board enabled layers, but only if the user has not specifed a layer list
1799 // ( m_plotLayerSequence can be empty with a broken user layer list)
1800 if( aGerberJob->m_plotLayerSequence.empty() && !hasLayerListSpecified )
1802 }
1803
1804 // Ensure layers to plot are restricted to enabled layers of the board to plot
1805 LSET layersToPlot = LSET( { aGerberJob->m_plotLayerSequence } ) & brd->GetEnabledLayers();
1806
1807 // Once for the run, not per file, so an update rebuilds each chart once. Common layers
1808 // included or a chart plotted as one is never looked at
1809 LSET preflightLayers = layersToPlot;
1810
1811 for( PCB_LAYER_ID commonLayer : aGerberJob->m_plotOnAllLayersSequence )
1812 preflightLayers.set( commonLayer );
1813
1814 RefreshGeneratedTables( *brd );
1815
1816 for( PCB_LAYER_ID layer : layersToPlot.UIOrder() )
1817 {
1818 LSEQ plotSequence;
1819
1820 // Base layer always gets plotted first.
1821 plotSequence.push_back( layer );
1822
1823 // Now all the "include on all" layers
1824 for( PCB_LAYER_ID layer_all : aGerberJob->m_plotOnAllLayersSequence )
1825 {
1826 // Don't plot the same layer more than once;
1827 if( find( plotSequence.begin(), plotSequence.end(), layer_all ) != plotSequence.end() )
1828 continue;
1829
1830 plotSequence.push_back( layer_all );
1831 }
1832
1833 // Pick the basename from the board file
1834 wxFileName fn( brd->GetFileName() );
1835 wxString layerName = brd->GetLayerName( layer );
1836 wxString sheetName;
1837 wxString sheetPath;
1838 PCB_PLOT_PARAMS plotOpts;
1839
1840 if( aGerberJob->m_useBoardPlotParams )
1841 plotOpts = boardPlotOptions;
1842 else
1843 PCB_PLOTTER::PlotJobToPlotOpts( plotOpts, aGerberJob, *m_reporter );
1844
1845 if( plotOpts.GetUseGerberProtelExtensions() )
1846 fileExt = GetGerberProtelExtension( layer );
1847 else
1849
1850 PCB_PLOTTER::BuildPlotFileName( &fn, outPath, layerName, fileExt );
1851 wxString fullname = fn.GetFullName();
1852
1853 if( m_progressReporter )
1854 {
1855 m_progressReporter->AdvancePhase( wxString::Format( _( "Exporting %s" ), fullname ) );
1856 m_progressReporter->KeepRefreshing();
1857 }
1858
1859 jobfile_writer.AddGbrFile( layer, fullname );
1860
1861 if( aJob->GetVarOverrides().contains( wxT( "LAYER" ) ) )
1862 layerName = aJob->GetVarOverrides().at( wxT( "LAYER" ) );
1863
1864 if( aJob->GetVarOverrides().contains( wxT( "SHEETNAME" ) ) )
1865 sheetName = aJob->GetVarOverrides().at( wxT( "SHEETNAME" ) );
1866
1867 if( aJob->GetVarOverrides().contains( wxT( "SHEETPATH" ) ) )
1868 sheetPath = aJob->GetVarOverrides().at( wxT( "SHEETPATH" ) );
1869
1870 // We are feeding it one layer at the start here to silence a logic check
1871 GERBER_PLOTTER* plotter;
1872 plotter = (GERBER_PLOTTER*) StartPlotBoard( brd, &plotOpts, layer, layerName, fn.GetFullPath(), sheetName,
1873 sheetPath );
1874
1875 if( plotter )
1876 {
1877 m_reporter->Report( wxString::Format( _( "Plotted to '%s'.\n" ), fn.GetFullPath() ), RPT_SEVERITY_ACTION );
1878
1879 PlotBoardLayers( brd, plotter, plotSequence, plotOpts );
1880 plotter->EndPlot();
1881 aJob->AddOutput( fn.GetFullPath() );
1882 }
1883 else
1884 {
1885 m_reporter->Report( wxString::Format( _( "Failed to plot to '%s'.\n" ), fn.GetFullPath() ),
1888 }
1889
1890 delete plotter;
1891 }
1892
1893 if( aGerberJob->m_createJobsFile )
1894 {
1895 wxFileName fn( brd->GetFileName() );
1896
1897 // Build gerber job file from basename
1899 jobfile_writer.CreateJobFile( fn.GetFullPath() );
1900 aJob->AddOutput( fn.GetFullPath() );
1901 }
1902
1903 return exitCode;
1904}
1905
1906
1908{
1909 JOB_EXPORT_PCB_GENCAD* aGencadJob = dynamic_cast<JOB_EXPORT_PCB_GENCAD*>( aJob );
1910
1911 if( aGencadJob == nullptr )
1913
1914 BOARD* brd = getBoard( aGencadJob->m_filename );
1915
1916 if( !brd )
1918
1919 GENCAD_EXPORTER exporter( brd );
1920
1921 VECTOR2I GencadOffset;
1922 VECTOR2I auxOrigin = brd->GetDesignSettings().GetAuxOrigin();
1923 GencadOffset.x = aGencadJob->m_useDrillOrigin ? auxOrigin.x : 0;
1924 GencadOffset.y = aGencadJob->m_useDrillOrigin ? auxOrigin.y : 0;
1925
1926 exporter.FlipBottomPads( aGencadJob->m_flipBottomPads );
1927 exporter.UsePinNamesUnique( aGencadJob->m_useUniquePins );
1928 exporter.UseIndividualShapes( aGencadJob->m_useIndividualShapes );
1929 exporter.SetPlotOffet( GencadOffset );
1930 exporter.StoreOriginCoordsInFile( aGencadJob->m_storeOriginCoords );
1931
1932 if( aGencadJob->GetConfiguredOutputPath().IsEmpty() )
1933 {
1934 wxFileName fn = brd->GetFileName();
1935 fn.SetName( fn.GetName() );
1936 fn.SetExt( FILEEXT::GencadFileExtension );
1937
1938 aGencadJob->SetWorkingOutputPath( fn.GetFullName() );
1939 }
1940
1941 wxString outPath = resolveJobOutputPath( aJob, brd );
1942
1943 if( !PATHS::EnsurePathExists( outPath, true ) )
1944 {
1945 m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
1947 }
1948
1949 if( !exporter.WriteFile( outPath ) )
1950 {
1951 m_reporter->Report( wxString::Format( _( "Failed to create file '%s'.\n" ), outPath ), RPT_SEVERITY_ERROR );
1952
1954 }
1955
1956 aJob->AddOutput( outPath );
1957 m_reporter->Report( _( "Successfully created genCAD file\n" ), RPT_SEVERITY_INFO );
1958
1959 return CLI::EXIT_CODES::OK;
1960}
1961
1962
1964{
1965 JOB_EXPORT_PCB_STATS* statsJob = dynamic_cast<JOB_EXPORT_PCB_STATS*>( aJob );
1966
1967 if( statsJob == nullptr )
1969
1970 BOARD* brd = getBoard( statsJob->m_filename );
1971
1972 if( !brd )
1974
1977
1982
1983 ComputeBoardStatistics( brd, options, data );
1984
1985 wxString projectName;
1986
1987 if( brd->GetProject() )
1988 projectName = brd->GetProject()->GetProjectName();
1989
1990 wxFileName boardFile = brd->GetFileName();
1991
1992 if( boardFile.GetName().IsEmpty() )
1993 boardFile = wxFileName( statsJob->m_filename );
1994
1996 UNITS_PROVIDER unitsProvider( pcbIUScale, unitsForReport );
1997
1998 wxString report;
1999
2001 report = FormatBoardStatisticsJson( data, brd, unitsProvider, projectName, boardFile.GetName() );
2002 else
2003 report = FormatBoardStatisticsReport( data, brd, unitsProvider, projectName, boardFile.GetName() );
2004
2005 if( statsJob->GetConfiguredOutputPath().IsEmpty() && statsJob->GetWorkingOutputPath().IsEmpty() )
2006 statsJob->SetDefaultOutputPath( boardFile.GetFullPath() );
2007
2008 wxString outPath = resolveJobOutputPath( aJob, brd );
2009
2010 if( !PATHS::EnsurePathExists( outPath, true ) )
2011 {
2012 m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
2014 }
2015
2016 FILE* outFile = wxFopen( outPath, wxS( "wt" ) );
2017
2018 if( !outFile )
2019 {
2020 m_reporter->Report( wxString::Format( _( "Failed to create file '%s'.\n" ), outPath ), RPT_SEVERITY_ERROR );
2022 }
2023
2024 if( fprintf( outFile, "%s", TO_UTF8( report ) ) < 0 )
2025 {
2026 fclose( outFile );
2027 m_reporter->Report( wxString::Format( _( "Error writing file '%s'.\n" ), outPath ), RPT_SEVERITY_ERROR );
2029 }
2030
2031 fclose( outFile );
2032
2033 m_reporter->Report( wxString::Format( _( "Wrote board statistics to '%s'.\n" ), outPath ), RPT_SEVERITY_ACTION );
2034
2035 statsJob->AddOutput( outPath );
2036
2037 return CLI::EXIT_CODES::OK;
2038}
2039
2040
2042{
2043 JOB_EXPORT_PCB_STACKUP* stackupJob = dynamic_cast<JOB_EXPORT_PCB_STACKUP*>( aJob );
2044
2045 if( stackupJob == nullptr )
2047
2048 BOARD* brd = getBoard( stackupJob->m_filename );
2049
2050 if( !brd )
2052
2053 wxFileName boardFile = brd->GetFileName();
2054
2055 if( boardFile.GetName().IsEmpty() )
2056 boardFile = wxFileName( stackupJob->m_filename );
2057
2058 wxString output;
2059
2060 switch( stackupJob->m_format )
2061 {
2063 {
2064 kiapi::board::BoardStackup stackupMsg;
2065 kiapi::board::PackBoardStackup( *brd, stackupMsg );
2066
2067 google::protobuf::util::JsonPrintOptions jsonOptions;
2068 jsonOptions.add_whitespace = true;
2069
2070 std::string json;
2071
2072 if( !google::protobuf::util::MessageToJsonString( stackupMsg, &json, jsonOptions ).ok() )
2073 {
2074 m_reporter->Report( _( "Failed to serialize board stackup\n" ), RPT_SEVERITY_ERROR );
2076 }
2077
2078 output = wxString::FromUTF8( json );
2079 break;
2080 }
2081
2083 {
2085 BOARD_STACKUP stackup = bds.GetStackupDescriptor();
2086 stackup.SynchronizeWithBoard( &bds );
2087
2088 for( BOARD_STACKUP_ITEM* item : stackup.GetList() )
2089 {
2090 if( item->GetBrdLayerId() != UNDEFINED_LAYER )
2091 item->SetLayerName( brd->GetLayerName( item->GetBrdLayerId() ) );
2092 }
2093
2094 EDA_UNITS unitsForReport = stackupJob->m_units == JOB_EXPORT_PCB_STACKUP::UNITS::MM ? EDA_UNITS::MM
2096
2097 STACKUP_CSV_OPTIONS options;
2098 options.includeColor = stackupJob->m_includeColor;
2099 options.includeMaterial = stackupJob->m_includeMaterial;
2100 options.includeThickness = stackupJob->m_includeThickness;
2101 options.includeEpsilonR = stackupJob->m_includeEpsilonR;
2102 options.includeLossTangent = stackupJob->m_includeLossTangent;
2103 options.includeFinish = stackupJob->m_includeFinish;
2104 options.includeBoardOptions = stackupJob->m_includeBoardOptions;
2105
2106 output = BuildStackupCsv( stackup, unitsForReport, options );
2107 break;
2108 }
2109 }
2110
2111 if( stackupJob->GetConfiguredOutputPath().IsEmpty() && stackupJob->GetWorkingOutputPath().IsEmpty() )
2112 stackupJob->SetDefaultOutputPath( boardFile.GetFullPath() );
2113
2114 wxString outPath = resolveJobOutputPath( aJob, brd );
2115
2116 if( !PATHS::EnsurePathExists( outPath, true ) )
2117 {
2118 m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
2120 }
2121
2122 OPEN_OSTREAM( outFile, TO_UTF8( outPath ) );
2123
2124 if( !outFile )
2125 {
2126 m_reporter->Report( wxString::Format( _( "Failed to create file '%s'.\n" ), outPath ), RPT_SEVERITY_ERROR );
2128 }
2129
2130 outFile << TO_UTF8( output );
2131
2132 const bool writeOk = static_cast<bool>( outFile );
2133 CLOSE_STREAM( outFile );
2134
2135 if( !writeOk )
2136 {
2137 m_reporter->Report( wxString::Format( _( "Error writing file '%s'.\n" ), outPath ), RPT_SEVERITY_ERROR );
2139 }
2140
2141 m_reporter->Report( wxString::Format( _( "Wrote board stackup to '%s'.\n" ), outPath ), RPT_SEVERITY_ACTION );
2142
2143 stackupJob->AddOutput( outPath );
2144
2145 return CLI::EXIT_CODES::OK;
2146}
2147
2148
2150{
2151 int exitCode = CLI::EXIT_CODES::OK;
2152 JOB_EXPORT_PCB_GERBER* aGerberJob = dynamic_cast<JOB_EXPORT_PCB_GERBER*>( aJob );
2153
2154 if( aGerberJob == nullptr )
2156
2157 BOARD* brd = getBoard( aGerberJob->m_filename );
2158
2159 if( !brd )
2161
2162 if( !aGerberJob->m_variant.IsEmpty() )
2163 brd->SetCurrentVariant( aGerberJob->m_variant );
2164
2165 TOOL_MANAGER* toolManager = getToolManager( brd );
2166
2167 if( aGerberJob->m_argLayers )
2168 aGerberJob->m_plotLayerSequence = convertLayerArg( aGerberJob->m_argLayers.value(), brd );
2169
2170 if( aGerberJob->m_argCommonLayers )
2171 aGerberJob->m_plotOnAllLayersSequence = convertLayerArg( aGerberJob->m_argCommonLayers.value(), brd );
2172
2173 if( aGerberJob->m_plotLayerSequence.size() < 1 )
2174 {
2175 m_reporter->Report( _( "At least one layer must be specified\n" ), RPT_SEVERITY_ERROR );
2177 }
2178
2179 if( aGerberJob->GetConfiguredOutputPath().IsEmpty() )
2180 {
2181 wxFileName fn = brd->GetFileName();
2182 fn.SetName( fn.GetName() );
2184
2185 aGerberJob->SetWorkingOutputPath( fn.GetFullName() );
2186 }
2187
2188 wxString outPath = resolveJobOutputPath( aJob, brd, &aGerberJob->m_drawingSheet );
2189
2190 if( aGerberJob->m_checkZonesBeforePlot )
2191 {
2192 if( !toolManager->FindTool( ZONE_FILLER_TOOL_NAME ) )
2193 toolManager->RegisterTool( new ZONE_FILLER_TOOL );
2194
2195 toolManager->GetTool<ZONE_FILLER_TOOL>()->FillAllZones( nullptr, m_progressReporter, true );
2196 }
2197
2198 PCB_PLOT_PARAMS plotOpts;
2199 PCB_PLOTTER::PlotJobToPlotOpts( plotOpts, aGerberJob, *m_reporter );
2200 plotOpts.SetLayerSelection( aGerberJob->m_plotLayerSequence );
2202
2204 wxString layerName;
2205 wxString sheetName;
2206 wxString sheetPath;
2207
2208 // The first layer will be treated as the layer name for the gerber header,
2209 // the other layers will be treated equivalent to the "Plot on All Layers" option
2210 // in the GUI
2211 if( aGerberJob->m_plotLayerSequence.size() >= 1 )
2212 {
2213 layer = aGerberJob->m_plotLayerSequence.front();
2214 layerName = brd->GetLayerName( layer );
2215 }
2216
2217 if( aJob->GetVarOverrides().contains( wxT( "LAYER" ) ) )
2218 layerName = aJob->GetVarOverrides().at( wxT( "LAYER" ) );
2219
2220 if( aJob->GetVarOverrides().contains( wxT( "SHEETNAME" ) ) )
2221 sheetName = aJob->GetVarOverrides().at( wxT( "SHEETNAME" ) );
2222
2223 if( aJob->GetVarOverrides().contains( wxT( "SHEETPATH" ) ) )
2224 sheetPath = aJob->GetVarOverrides().at( wxT( "SHEETPATH" ) );
2225
2226 // We are feeding it one layer at the start here to silence a logic check
2227
2228 // It drives StartPlotBoard directly rather than PCB_PLOTTER::Plot, so it needs its own
2229 // preflight or a FAIL policy would still emit a stale chart
2230 {
2231 LSET preflightLayers( { aGerberJob->m_plotLayerSequence } );
2232
2233 RefreshGeneratedTables( *brd );
2234 }
2235
2236 PLOTTER* plotter = StartPlotBoard( brd, &plotOpts, layer, layerName, outPath, sheetName, sheetPath );
2237
2238 if( plotter )
2239 {
2240 PlotBoardLayers( brd, plotter, aGerberJob->m_plotLayerSequence, plotOpts );
2241 plotter->EndPlot();
2242 }
2243 else
2244 {
2245 m_reporter->Report( wxString::Format( _( "Failed to plot to '%s'.\n" ), outPath ), RPT_SEVERITY_ERROR );
2247 }
2248
2249 delete plotter;
2250
2251 return exitCode;
2252}
2253
2256
2257
2259{
2260 JOB_EXPORT_PCB_DRILL* aDrillJob = dynamic_cast<JOB_EXPORT_PCB_DRILL*>( aJob );
2261
2262 if( aDrillJob == nullptr )
2264
2265 BOARD* brd = getBoard( aDrillJob->m_filename );
2266
2267 if( !brd )
2269
2270 wxString outPath = resolveJobOutputPath( aJob, brd );
2271
2272 if( !PATHS::EnsurePathExists( outPath ) )
2273 {
2274 m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
2276 }
2277
2278 std::unique_ptr<GENDRILL_WRITER_BASE> drillWriter;
2279
2281 drillWriter = std::make_unique<EXCELLON_WRITER>( brd );
2282 else
2283 drillWriter = std::make_unique<GERBER_WRITER>( brd );
2284
2285 VECTOR2I offset;
2286
2288 offset = VECTOR2I( 0, 0 );
2289 else
2290 offset = brd->GetDesignSettings().GetAuxOrigin();
2291
2292 PLOT_FORMAT mapFormat = PLOT_FORMAT::PDF;
2293
2294 switch( aDrillJob->m_mapFormat )
2295 {
2300 default:
2302 }
2303
2304
2305 if( aDrillJob->m_generateReport && aDrillJob->m_reportPath.IsEmpty() )
2306 {
2307 wxFileName fn = outPath;
2308 fn.SetFullName( brd->GetFileName() );
2309 fn.SetName( fn.GetName() + "-drill" );
2310 fn.SetExt( FILEEXT::ReportFileExtension );
2311
2312 aDrillJob->m_reportPath = fn.GetFullPath();
2313 }
2314
2316 {
2318
2319 switch( aDrillJob->m_zeroFormat )
2320 {
2322
2324
2326
2328 default: zeroFmt = EXCELLON_WRITER::DECIMAL_FORMAT; break;
2329 }
2330
2331 DRILL_PRECISION precision;
2332
2334 precision = precisionListForInches;
2335 else
2336 precision = precisionListForMetric;
2337
2338 EXCELLON_WRITER* excellonWriter = dynamic_cast<EXCELLON_WRITER*>( drillWriter.get() );
2339
2340 if( excellonWriter == nullptr )
2342
2343 excellonWriter->SetFormat( aDrillJob->m_drillUnits == JOB_EXPORT_PCB_DRILL::DRILL_UNITS::MM, zeroFmt,
2344 precision.m_Lhs, precision.m_Rhs );
2345 excellonWriter->SetOptions( aDrillJob->m_excellonMirrorY, aDrillJob->m_excellonMinimalHeader, offset,
2346 aDrillJob->m_excellonCombinePTHNPTH );
2347 excellonWriter->SetRouteModeForOvalHoles( aDrillJob->m_excellonOvalDrillRoute );
2348 excellonWriter->SetMapFileFormat( mapFormat );
2349
2350 if( !excellonWriter->CreateDrillandMapFilesSet( outPath, true, aDrillJob->m_generateMap, m_reporter ) )
2351 {
2353 }
2354
2355 for( const wxString& outputFile : drillWriter->GetCreatedFiles() )
2356 aDrillJob->AddOutput( outputFile );
2357
2358 if( aDrillJob->m_generateReport )
2359 {
2360 wxString reportPath = aDrillJob->ResolveOutputPath( aDrillJob->m_reportPath, true, brd->GetProject() );
2361
2362 if( !excellonWriter->GenDrillReportFile( reportPath ) )
2363 {
2365 }
2366
2367 aDrillJob->AddOutput( reportPath );
2368 }
2369 }
2371 {
2372 GERBER_WRITER* gerberWriter = dynamic_cast<GERBER_WRITER*>( drillWriter.get() );
2373
2374 if( gerberWriter == nullptr )
2376
2377 // Set gerber precision: only 5 or 6 digits for mantissa are allowed
2378 // (SetFormat() accept 5 or 6, and any other value set the precision to 5)
2379 // the integer part precision is always 4, and units always mm
2380 gerberWriter->SetFormat( aDrillJob->m_gerberPrecision );
2381 gerberWriter->SetOptions( offset );
2382 gerberWriter->SetMapFileFormat( mapFormat );
2383
2384 if( !gerberWriter->CreateDrillandMapFilesSet( outPath, true, aDrillJob->m_generateMap,
2385 aDrillJob->m_generateTenting, m_reporter ) )
2386 {
2388 }
2389
2390 for( const wxString& outputFile : drillWriter->GetCreatedFiles() )
2391 aDrillJob->AddOutput( outputFile );
2392
2393 if( aDrillJob->m_generateReport )
2394 {
2395 wxString reportPath = aDrillJob->ResolveOutputPath( aDrillJob->m_reportPath, true, brd->GetProject() );
2396
2397 if( !gerberWriter->GenDrillReportFile( reportPath ) )
2398 {
2400 }
2401
2402 aDrillJob->AddOutput( reportPath );
2403 }
2404 }
2405
2406 return CLI::EXIT_CODES::OK;
2407}
2408
2409
2411{
2412 JOB_EXPORT_PCB_POS* aPosJob = dynamic_cast<JOB_EXPORT_PCB_POS*>( aJob );
2413
2414 if( aPosJob == nullptr )
2416
2417 BOARD* brd = getBoard( aPosJob->m_filename );
2418
2419 if( !brd )
2421
2422 if( aPosJob->GetConfiguredOutputPath().IsEmpty() )
2423 {
2424 wxFileName fn = brd->GetFileName();
2425 fn.SetName( fn.GetName() );
2426
2429 else if( aPosJob->m_format == JOB_EXPORT_PCB_POS::FORMAT::CSV )
2430 fn.SetExt( FILEEXT::CsvFileExtension );
2431 else if( aPosJob->m_format == JOB_EXPORT_PCB_POS::FORMAT::GERBER )
2432 fn.SetExt( FILEEXT::GerberFileExtension );
2433
2434 aPosJob->SetWorkingOutputPath( fn.GetFullName() );
2435 }
2436
2437 wxString outPath = resolveJobOutputPath( aJob, brd );
2438
2439 if( !PATHS::EnsurePathExists( outPath, true ) )
2440 {
2441 m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
2443 }
2444
2446 {
2447 wxFileName fn( outPath );
2448 wxString baseName = fn.GetName();
2449
2450 auto exportPlaceFile = [&]( bool frontSide, bool backSide, const wxString& curr_outPath ) -> bool
2451 {
2452 FILE* file = wxFopen( curr_outPath, wxS( "wt" ) );
2453 wxCHECK( file, false );
2454
2455 PLACE_FILE_EXPORTER exporter( brd, aPosJob->m_units == JOB_EXPORT_PCB_POS::UNITS::MM, aPosJob->m_smdOnly,
2456 aPosJob->m_excludeFootprintsWithTh, aPosJob->m_excludeDNP,
2457 aPosJob->m_excludeBOM, frontSide, backSide,
2459 aPosJob->m_useDrillPlaceFileOrigin, aPosJob->m_negateBottomX );
2460
2461 // Set variant for variant-aware DNP/BOM/position file filtering
2462 exporter.SetVariant( aPosJob->m_variant );
2463
2464 std::string data = exporter.GenPositionData();
2465 fputs( data.c_str(), file );
2466 fclose( file );
2467
2468 return true;
2469 };
2470
2471 if( aPosJob->m_side == JOB_EXPORT_PCB_POS::SIDE::BOTH && !aPosJob->m_singleFile )
2472 {
2473 fn.SetName( PLACE_FILE_EXPORTER::DecorateFilename( baseName, true, false ) );
2474
2475 if( aPosJob->m_format == JOB_EXPORT_PCB_POS::FORMAT::CSV && !aPosJob->m_nakedFilename )
2476 fn.SetName( fn.GetName() + wxT( "-" ) + FILEEXT::FootprintPlaceFileExtension );
2477
2478 if( exportPlaceFile( true, false, fn.GetFullPath() ) )
2479 {
2480 m_reporter->Report( wxString::Format( _( "Wrote front position data to '%s'.\n" ), fn.GetFullPath() ),
2482
2483 aPosJob->AddOutput( fn.GetFullPath() );
2484 }
2485 else
2486 {
2488 }
2489
2490 fn.SetName( PLACE_FILE_EXPORTER::DecorateFilename( baseName, false, true ) );
2491
2492 if( aPosJob->m_format == JOB_EXPORT_PCB_POS::FORMAT::CSV && !aPosJob->m_nakedFilename )
2493 fn.SetName( fn.GetName() + wxT( "-" ) + FILEEXT::FootprintPlaceFileExtension );
2494
2495 if( exportPlaceFile( false, true, fn.GetFullPath() ) )
2496 {
2497 m_reporter->Report( wxString::Format( _( "Wrote back position data to '%s'.\n" ), fn.GetFullPath() ),
2499
2500 aPosJob->AddOutput( fn.GetFullPath() );
2501 }
2502 else
2503 {
2505 }
2506 }
2507 else
2508 {
2509 bool front = aPosJob->m_side == JOB_EXPORT_PCB_POS::SIDE::FRONT
2511
2512 bool back = aPosJob->m_side == JOB_EXPORT_PCB_POS::SIDE::BACK
2514
2515 if( !aPosJob->m_nakedFilename )
2516 {
2517 fn.SetName( PLACE_FILE_EXPORTER::DecorateFilename( fn.GetName(), front, back ) );
2518
2520 fn.SetName( fn.GetName() + wxT( "-" ) + FILEEXT::FootprintPlaceFileExtension );
2521 }
2522
2523 if( exportPlaceFile( front, back, fn.GetFullPath() ) )
2524 {
2525 m_reporter->Report( wxString::Format( _( "Wrote position data to '%s'.\n" ), fn.GetFullPath() ),
2527
2528 aPosJob->AddOutput( fn.GetFullPath() );
2529 }
2530 else
2531 {
2533 }
2534 }
2535 }
2536 else if( aPosJob->m_format == JOB_EXPORT_PCB_POS::FORMAT::GERBER )
2537 {
2538 PLACEFILE_GERBER_WRITER exporter( brd );
2539
2540 // Set variant for variant-aware DNP/BOM/position file filtering
2541 exporter.SetVariant( aPosJob->m_variant );
2542
2543 PCB_LAYER_ID gbrLayer = F_Cu;
2544 wxString outPath_base = outPath;
2545
2547 {
2548 if( aPosJob->m_side == JOB_EXPORT_PCB_POS::SIDE::BOTH || !aPosJob->m_nakedFilename )
2549 outPath = exporter.GetPlaceFileName( outPath, gbrLayer );
2550
2551 if( exporter.CreatePlaceFile( outPath, gbrLayer, aPosJob->m_gerberBoardEdge, aPosJob->m_excludeDNP,
2552 aPosJob->m_excludeBOM )
2553 >= 0 )
2554 {
2555 m_reporter->Report( wxString::Format( _( "Wrote front position data to '%s'.\n" ), outPath ),
2557
2558 aPosJob->AddOutput( outPath );
2559 }
2560 else
2561 {
2563 }
2564 }
2565
2567 {
2568 gbrLayer = B_Cu;
2569
2570 outPath = outPath_base;
2571
2572 if( aPosJob->m_side == JOB_EXPORT_PCB_POS::SIDE::BOTH || !aPosJob->m_nakedFilename )
2573 outPath = exporter.GetPlaceFileName( outPath, gbrLayer );
2574
2575 if( exporter.CreatePlaceFile( outPath, gbrLayer, aPosJob->m_gerberBoardEdge, aPosJob->m_excludeDNP,
2576 aPosJob->m_excludeBOM )
2577 >= 0 )
2578 {
2579 m_reporter->Report( wxString::Format( _( "Wrote back position data to '%s'.\n" ), outPath ),
2581
2582 aPosJob->AddOutput( outPath );
2583 }
2584 else
2585 {
2587 }
2588 }
2589 }
2590
2591 return CLI::EXIT_CODES::OK;
2592}
2593
2594
2596{
2597 JOB_FP_UPGRADE* upgradeJob = dynamic_cast<JOB_FP_UPGRADE*>( aJob );
2598
2599 if( upgradeJob == nullptr )
2601
2603
2604 if( !upgradeJob->m_outputLibraryPath.IsEmpty() )
2605 {
2606 if( wxFile::Exists( upgradeJob->m_outputLibraryPath ) || wxDir::Exists( upgradeJob->m_outputLibraryPath ) )
2607 {
2608 m_reporter->Report( _( "Output path must not conflict with existing path\n" ), RPT_SEVERITY_ERROR );
2610 }
2611 }
2612 else if( fileType != PCB_IO_MGR::KICAD_SEXP )
2613 {
2614 m_reporter->Report( _( "Output path must be specified to convert legacy and non-KiCad libraries\n" ),
2616
2618 }
2619
2621 {
2622 if( !wxDir::Exists( upgradeJob->m_libraryPath ) )
2623 {
2624 m_reporter->Report( _( "Footprint library path does not exist or is not accessible\n" ),
2627 }
2628
2630 FP_CACHE fpLib( &pcb_io, upgradeJob->m_libraryPath );
2631
2632 try
2633 {
2634 fpLib.Load();
2635 }
2636 catch( ... )
2637 {
2638 m_reporter->Report( _( "Unable to load library\n" ), RPT_SEVERITY_ERROR );
2640 }
2641
2642 if( m_progressReporter )
2643 m_progressReporter->KeepRefreshing();
2644
2645 bool shouldSave = upgradeJob->m_force;
2646
2647 for( const auto& footprint : fpLib.GetFootprints() )
2648 {
2649 if( footprint.second->GetFootprint()->GetFileFormatVersionAtLoad() < SEXPR_BOARD_FILE_VERSION )
2650 shouldSave = true;
2651 }
2652
2653 if( shouldSave )
2654 {
2655 try
2656 {
2657 if( !upgradeJob->m_outputLibraryPath.IsEmpty() )
2658 fpLib.SetPath( upgradeJob->m_outputLibraryPath );
2659
2660 fpLib.Save();
2661 }
2662 catch( ... )
2663 {
2664 m_reporter->Report( _( "Unable to save library\n" ), RPT_SEVERITY_ERROR );
2666 }
2667 }
2668 else
2669 {
2670 m_reporter->Report( _( "Footprint library was not updated\n" ), RPT_SEVERITY_ERROR );
2671 }
2672 }
2673 else
2674 {
2675 if( !PCB_IO_MGR::ConvertLibrary( {}, upgradeJob->m_libraryPath, upgradeJob->m_outputLibraryPath,
2676 nullptr /* REPORTER */ ) )
2677 {
2678 m_reporter->Report( ( "Unable to convert library\n" ), RPT_SEVERITY_ERROR );
2680 }
2681 }
2682
2683 return CLI::EXIT_CODES::OK;
2684}
2685
2686
2688{
2689 JOB_FP_EXPORT_SVG* svgJob = dynamic_cast<JOB_FP_EXPORT_SVG*>( aJob );
2690
2691 if( svgJob == nullptr )
2693
2695 FP_CACHE fpLib( &pcb_io, svgJob->m_libraryPath );
2696
2697 if( svgJob->m_argLayers )
2698 {
2699 if( !svgJob->m_argLayers.value().empty() )
2700 svgJob->m_plotLayerSequence = convertLayerArg( svgJob->m_argLayers.value(), nullptr );
2701 else
2703 }
2704
2705 try
2706 {
2707 fpLib.Load();
2708 }
2709 catch( ... )
2710 {
2711 m_reporter->Report( _( "Unable to load library\n" ), RPT_SEVERITY_ERROR );
2713 }
2714
2715 wxString outPath = svgJob->GetFullOutputPath( nullptr );
2716
2717 if( !PATHS::EnsurePathExists( outPath, true ) )
2718 {
2719 m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
2721 }
2722
2723 int exitCode = CLI::EXIT_CODES::OK;
2724 bool singleFpPlotted = false;
2725
2726 for( const auto& [fpName, fpCacheEntry] : fpLib.GetFootprints() )
2727 {
2728 if( m_progressReporter )
2729 {
2730 m_progressReporter->AdvancePhase( wxString::Format( _( "Exporting %s" ), fpName ) );
2731 m_progressReporter->KeepRefreshing();
2732 }
2733
2734 if( !svgJob->m_footprint.IsEmpty() )
2735 {
2736 // skip until we find the right footprint
2737 if( fpName != svgJob->m_footprint )
2738 continue;
2739 else
2740 singleFpPlotted = true;
2741 }
2742
2743 exitCode = doFpExportSvg( svgJob, fpCacheEntry->GetFootprint().get() );
2744
2745 if( exitCode != CLI::EXIT_CODES::OK )
2746 break;
2747 }
2748
2749 if( !svgJob->m_footprint.IsEmpty() && !singleFpPlotted )
2750 {
2751 m_reporter->Report( _( "The given footprint could not be found to export." ) + wxS( "\n" ),
2753 }
2754
2755 return CLI::EXIT_CODES::OK;
2756}
2757
2758
2760{
2761 wxFileName outputFile;
2762 outputFile.SetPath( aSvgJob->GetFullOutputPath( nullptr ) );
2763 outputFile.SetName( aFootprint->GetFPID().GetLibItemName().wx_str() );
2764 outputFile.SetExt( FILEEXT::SVGFileExtension );
2765
2766 m_reporter->Report( wxString::Format( _( "Plotting footprint '%s' to '%s'\n" ),
2767 aFootprint->GetFPID().GetLibItemName().wx_str(), outputFile.GetFullPath() ),
2769
2770 PCB_PLOT_PARAMS plotOpts;
2771 PCB_PLOTTER::PlotJobToPlotOpts( plotOpts, aSvgJob, *m_reporter );
2772
2773 if( plotOpts.GetSketchPadsOnFabLayers() )
2774 {
2775 plotOpts.SetPlotPadNumbers( true );
2776 }
2777
2778 if( !PlotFootprintToSVG( *aFootprint, *Pgm().GetSettingsManager().GetProject( "" ), &aSvgJob->GetVarOverrides(),
2779 plotOpts, aSvgJob->m_plotLayerSequence, aSvgJob->m_plotOnAllLayersSequence,
2780 outputFile.GetFullPath(), m_reporter ) )
2781 {
2782 m_reporter->Report( _( "Error creating svg file" ) + wxS( "\n" ), RPT_SEVERITY_ERROR );
2784 }
2785
2786 aSvgJob->AddOutput( outputFile.GetFullPath() );
2787
2788 return CLI::EXIT_CODES::OK;
2789}
2790
2791
2793{
2794 JOB_PCB_DRC* drcJob = dynamic_cast<JOB_PCB_DRC*>( aJob );
2795
2796 if( drcJob == nullptr )
2798
2799 BOARD* brd = getBoard( drcJob->m_filename );
2800
2801 if( !brd )
2803
2804 // Running DRC requires libraries be loaded, so make sure they have been
2806 adapter->AsyncLoad();
2807 adapter->BlockUntilLoaded();
2808
2809 if( drcJob->GetConfiguredOutputPath().IsEmpty() )
2810 {
2811 wxFileName fn = brd->GetFileName();
2812 fn.SetName( fn.GetName() + wxS( "-drc" ) );
2813
2815 fn.SetExt( FILEEXT::JsonFileExtension );
2816 else
2817 fn.SetExt( FILEEXT::ReportFileExtension );
2818
2819 drcJob->SetWorkingOutputPath( fn.GetFullName() );
2820 }
2821
2822 wxString outPath = resolveJobOutputPath( aJob, brd );
2823
2824 if( !PATHS::EnsurePathExists( outPath, true ) )
2825 {
2826 m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
2828 }
2829
2830 EDA_UNITS units;
2831
2832 switch( drcJob->m_units )
2833 {
2834 case JOB_PCB_DRC::UNITS::INCH: units = EDA_UNITS::INCH; break;
2835 case JOB_PCB_DRC::UNITS::MILS: units = EDA_UNITS::MILS; break;
2836 case JOB_PCB_DRC::UNITS::MM: units = EDA_UNITS::MM; break;
2837 default: units = EDA_UNITS::MM; break;
2838 }
2839
2840 std::shared_ptr<DRC_ENGINE> drcEngine = brd->GetDesignSettings().m_DRCEngine;
2841 std::unique_ptr<NETLIST> netlist = std::make_unique<NETLIST>();
2842
2843 if( !drcEngine->RulesValid() )
2844 {
2845 try
2846 {
2847 drcEngine->InitEngine( brd->GetDesignRulesPath() );
2848 }
2849 catch( const PARSE_ERROR& error )
2850 {
2851 m_reporter->Report( _( "DRC incomplete: could not compile custom design rules." )
2852 + wxS( "\n" ) + error.What() + wxS( "\n" ),
2855 }
2856 }
2857
2858 drcEngine->SetDrawingSheet( getDrawingSheetProxyView( brd ) );
2859
2860 // BOARD_COMMIT uses TOOL_MANAGER to grab the board internally so we must give it one
2861 TOOL_MANAGER* toolManager = getToolManager( brd );
2862
2863 BOARD_COMMIT commit( toolManager );
2864 bool checkParity = drcJob->m_parity;
2865 std::string netlist_str;
2866
2867 if( checkParity )
2868 {
2869 wxString annotateMsg = _( "Schematic parity tests require a fully annotated schematic." );
2870 netlist_str = annotateMsg;
2871
2872 // The KIFACE_NETLIST_SCHEMATIC function has some broken-ness that the schematic
2873 // frame's version does not, but it is the only one that works in CLI, so we use it
2874 // if we don't have the sch frame open.
2875 // TODO: clean this up, see https://gitlab.com/kicad/code/kicad/-/issues/19929
2876 if( m_kiway->Player( FRAME_SCH, false ) )
2877 {
2878 m_kiway->ExpressMail( FRAME_SCH, MAIL_SCH_GET_NETLIST, netlist_str );
2879 }
2880 else
2881 {
2882 wxFileName schematicPath( drcJob->m_filename );
2883 schematicPath.MakeAbsolute();
2884 schematicPath.SetExt( FILEEXT::KiCadSchematicFileExtension );
2885
2886 if( !schematicPath.Exists() )
2887 schematicPath.SetExt( FILEEXT::LegacySchematicFileExtension );
2888
2889 if( !schematicPath.Exists() )
2890 {
2891 m_reporter->Report( _( "Failed to fetch schematic netlist for parity tests.\n" ), RPT_SEVERITY_ERROR );
2892 checkParity = false;
2893 }
2894 else
2895 {
2896 typedef bool ( *NETLIST_FN_PTR )( const wxString&, std::string&, KIWAY* );
2897 KIFACE* eeschema = m_kiway->KiFACE( KIWAY::FACE_SCH );
2898 NETLIST_FN_PTR netlister = (NETLIST_FN_PTR) eeschema->IfaceOrAddress( KIFACE_NETLIST_SCHEMATIC );
2899 ( *netlister )( schematicPath.GetFullPath(), netlist_str, m_kiway );
2900 }
2901 }
2902
2903 if( netlist_str == MAIL_SCH_GET_NETLIST_CANCELLED )
2904 {
2905 checkParity = false;
2906 }
2907 else if( netlist_str == annotateMsg )
2908 {
2909 m_reporter->Report( annotateMsg + wxT( "\n" ), RPT_SEVERITY_ERROR );
2910 checkParity = false;
2911 }
2912 }
2913
2914 if( checkParity )
2915 {
2916 try
2917 {
2918 STRING_LINE_READER* lineReader = new STRING_LINE_READER( netlist_str, _( "Eeschema netlist" ) );
2919 KICAD_NETLIST_READER netlistReader( lineReader, netlist.get() );
2920
2921 netlistReader.LoadNetlist();
2922 }
2923 catch( const IO_ERROR& )
2924 {
2925 m_reporter->Report( _( "Failed to fetch schematic netlist for parity tests.\n" ), RPT_SEVERITY_ERROR );
2926 checkParity = false;
2927 }
2928
2929 drcEngine->SetSchematicNetlist( netlist.get() );
2930 }
2931
2932 if( drcJob->m_refillZones )
2933 {
2934 if( !toolManager->FindTool( ZONE_FILLER_TOOL_NAME ) )
2935 toolManager->RegisterTool( new ZONE_FILLER_TOOL );
2936
2937 toolManager->GetTool<ZONE_FILLER_TOOL>()->FillAllZones( nullptr, m_progressReporter, true );
2938 }
2939
2940 drcEngine->SetProgressReporter( m_progressReporter );
2941 drcEngine->SetViolationHandler(
2942 [&]( const std::shared_ptr<DRC_ITEM>& aItem, const VECTOR2I& aPos, int aLayer,
2943 const std::function<void( PCB_MARKER* )>& aPathGenerator )
2944 {
2945 PCB_MARKER* marker = new PCB_MARKER( aItem, aPos, aLayer );
2946 aPathGenerator( marker );
2947 commit.Add( marker );
2948 } );
2949
2950 brd->RecordDRCExclusions();
2951 brd->DeleteMARKERs( true, true );
2952 drcEngine->RunTests( units, drcJob->m_reportAllTrackErrors, checkParity );
2953 drcEngine->ClearViolationHandler();
2954
2955 commit.Push( _( "DRC" ), SKIP_UNDO | SKIP_SET_DIRTY );
2956
2957 // Update the exclusion status on any excluded markers that still exist.
2958 brd->ResolveDRCExclusions( false );
2959
2960 std::shared_ptr<DRC_ITEMS_PROVIDER> markersProvider =
2961 std::make_shared<DRC_ITEMS_PROVIDER>( brd, MARKER_BASE::MARKER_DRC, MARKER_BASE::MARKER_DRAWING_SHEET );
2962
2963 std::shared_ptr<DRC_ITEMS_PROVIDER> ratsnestProvider =
2964 std::make_shared<DRC_ITEMS_PROVIDER>( brd, MARKER_BASE::MARKER_RATSNEST );
2965
2966 std::shared_ptr<DRC_ITEMS_PROVIDER> fpWarningsProvider =
2967 std::make_shared<DRC_ITEMS_PROVIDER>( brd, MARKER_BASE::MARKER_PARITY );
2968
2969 markersProvider->SetSeverities( drcJob->m_severity );
2970 ratsnestProvider->SetSeverities( drcJob->m_severity );
2971 fpWarningsProvider->SetSeverities( drcJob->m_severity );
2972
2973 m_reporter->Report( wxString::Format( _( "Found %d violations\n" ), markersProvider->GetCount() ),
2975 m_reporter->Report( wxString::Format( _( "Found %d unconnected items\n" ), ratsnestProvider->GetCount() ),
2977
2978 if( checkParity )
2979 {
2980 m_reporter->Report(
2981 wxString::Format( _( "Found %d schematic parity issues\n" ), fpWarningsProvider->GetCount() ),
2983 }
2984
2985 DRC_REPORT reportWriter( brd, units, markersProvider, ratsnestProvider, fpWarningsProvider );
2986
2987 bool wroteReport = false;
2988
2990 wroteReport = reportWriter.WriteJsonReport( outPath );
2991 else
2992 wroteReport = reportWriter.WriteTextReport( outPath );
2993
2994 if( !wroteReport )
2995 {
2996 m_reporter->Report( wxString::Format( _( "Unable to save DRC report to %s\n" ), outPath ), RPT_SEVERITY_ERROR );
2998 }
2999
3000 drcJob->AddOutput( outPath );
3001
3002 m_reporter->Report( wxString::Format( _( "Saved DRC Report to %s\n" ), outPath ), RPT_SEVERITY_ACTION );
3003
3004 if( drcJob->m_refillZones && drcJob->m_saveBoard )
3005 {
3006 if( BOARD_LOADER::SaveBoard( drcJob->m_filename, *brd ) )
3007 {
3008 m_reporter->Report( _( "Saved board\n" ), RPT_SEVERITY_ACTION );
3009 }
3010 else
3011 {
3012 m_reporter->Report( _( "Failed to save board.\n" ), RPT_SEVERITY_ERROR );
3013
3015 }
3016 }
3017
3018 if( drcJob->m_exitCodeViolations )
3019 {
3020 if( markersProvider->GetCount() > 0 || ratsnestProvider->GetCount() > 0 || fpWarningsProvider->GetCount() > 0 )
3021 {
3023 }
3024 }
3025
3027}
3028
3029
3031{
3032 if( !aJob.m_checkZonesBeforeExport )
3033 {
3034 return;
3035 }
3036
3037 TOOL_MANAGER* toolManager = getToolManager( aBoard );
3038
3039 if( !toolManager->FindTool( ZONE_FILLER_TOOL_NAME ) )
3040 {
3041 toolManager->RegisterTool( new ZONE_FILLER_TOOL );
3042 }
3043
3044 toolManager->GetTool<ZONE_FILLER_TOOL>()->FillAllZones( nullptr, m_progressReporter, true );
3045}
3046
3047
3049{
3050 JOB_EXPORT_PCB_IPC2581* job = dynamic_cast<JOB_EXPORT_PCB_IPC2581*>( aJob );
3051
3052 if( job == nullptr )
3054
3055 BOARD* brd = getBoard( job->m_filename );
3056
3057 if( !brd )
3059
3060 struct VARIANT_RESTORE
3061 {
3062 BOARD* m_board;
3063 wxString m_variant;
3064
3065 ~VARIANT_RESTORE() { m_board->SetCurrentVariant( m_variant ); }
3066 } restore{ brd, brd->GetCurrentVariant() };
3067
3068 if( !job->m_variantNames.empty() )
3069 brd->SetCurrentVariant( job->m_variantNames.front() );
3070
3071 if( job->GetConfiguredOutputPath().IsEmpty() )
3072 {
3073 wxFileName fn = brd->GetFileName();
3074 fn.SetExt( job->m_compress ? std::string( "zip" ) : FILEEXT::Ipc2581FileExtension );
3075
3076 job->SetWorkingOutputPath( fn.GetFullName() );
3077 }
3078
3079 wxString outPath = resolveJobOutputPath( aJob, brd );
3080
3081 if( !PATHS::EnsurePathExists( outPath, true ) )
3082 {
3083 m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
3085 }
3086
3087 refillFabZones( *job, brd );
3088
3091
3093}
3094
3095
3097{
3098 JOB_EXPORT_PCB_IPCD356* job = dynamic_cast<JOB_EXPORT_PCB_IPCD356*>( aJob );
3099
3100 if( job == nullptr )
3102
3103 BOARD* brd = getBoard( job->m_filename );
3104
3105 if( !brd )
3107
3108 if( job->GetConfiguredOutputPath().IsEmpty() )
3109 {
3110 wxFileName fn = brd->GetFileName();
3111 fn.SetName( fn.GetName() );
3112 fn.SetExt( FILEEXT::IpcD356FileExtension );
3113
3114 job->SetWorkingOutputPath( fn.GetFullName() );
3115 }
3116
3117 wxString outPath = resolveJobOutputPath( aJob, brd );
3118
3119 if( !PATHS::EnsurePathExists( outPath, true ) )
3120 {
3121 m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
3123 }
3124
3125 IPC356D_WRITER exporter( brd );
3126
3127 bool success = exporter.Write( outPath );
3128
3129 if( success )
3130 {
3131 aJob->AddOutput( outPath );
3132 m_reporter->Report( _( "Successfully created IPC-D-356 file\n" ), RPT_SEVERITY_INFO );
3134 }
3135 else
3136 {
3137 m_reporter->Report( _( "Failed to create IPC-D-356 file\n" ), RPT_SEVERITY_ERROR );
3139 }
3140}
3141
3142
3144{
3145 JOB_EXPORT_PCB_IDF* job = dynamic_cast<JOB_EXPORT_PCB_IDF*>( aJob );
3146
3147 if( job == nullptr )
3149
3150 BOARD* brd = getBoard( job->m_filename );
3151
3152 if( !brd )
3154
3155 if( job->GetConfiguredOutputPath().IsEmpty() )
3156 {
3157 wxFileName fn = brd->GetFileName();
3159
3160 job->SetWorkingOutputPath( fn.GetFullName() );
3161 }
3162
3163 wxString outPath = resolveJobOutputPath( job, brd );
3164
3165 if( !PATHS::EnsurePathExists( outPath, true ) )
3166 {
3167 m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
3169 }
3170
3172
3173 if( !resolver )
3174 {
3175 m_reporter->Report( _( "Internal error\n" ), RPT_SEVERITY_ERROR );
3177 }
3178
3179 IDF_EXPORTER exporter( brd, resolver, job, m_reporter );
3180
3181 if( !exporter.Export( outPath ) )
3183
3184 m_reporter->Report( _( "Successfully created IDFv3 files\n" ), RPT_SEVERITY_INFO );
3185
3187}
3188
3189
3191{
3192 JOB_EXPORT_PCB_ODB* job = dynamic_cast<JOB_EXPORT_PCB_ODB*>( aJob );
3193
3194 if( job == nullptr )
3196
3197 BOARD* brd = getBoard( job->m_filename );
3198
3199 if( !brd )
3201
3202 if( job->GetConfiguredOutputPath().IsEmpty() )
3203 {
3205 {
3206 // just basic folder name
3207 job->SetWorkingOutputPath( "odb" );
3208 }
3209 else
3210 {
3211 wxFileName fn( brd->GetFileName() );
3212 fn.SetName( fn.GetName() + wxS( "-odb" ) );
3213
3214 switch( job->m_compressionMode )
3215 {
3217
3218 case JOB_EXPORT_PCB_ODB::ODB_COMPRESSION::TGZ: fn.SetExt( "tgz" ); break;
3219
3220 default: break;
3221 };
3222
3223 job->SetWorkingOutputPath( fn.GetFullName() );
3224 }
3225 }
3226
3227 resolveJobOutputPath( job, brd );
3228
3229 // The helper handles output path creation, so hand it a job that already has fully-resolved
3230 // token context (title block and project overrides applied above).
3231 refillFabZones( *job, brd );
3232
3234
3235 if( result.m_ok )
3236 {
3237 for( const wxString& path : result.m_outputs )
3238 aJob->AddOutput( path );
3239 }
3240
3241 if( !result.m_ok || m_reporter->HasMessageOfSeverity( RPT_SEVERITY_ERROR ) )
3243
3245}
3246
3248{
3249 JOB_PCB_UPGRADE* job = dynamic_cast<JOB_PCB_UPGRADE*>( aJob );
3250
3251 if( job == nullptr )
3253
3254 bool shouldSave = job->m_force;
3255
3256 try
3257 {
3259 BOARD* brd = getBoard( job->m_filename );
3260
3261 if( !brd )
3263
3265 shouldSave = true;
3266
3267 // A chart is derived data, so the upgrade brings every one up to date and saves if
3268 // that changed anything
3269 if( RefreshGeneratedTables( *brd ) > 0 )
3270 shouldSave = true;
3271
3272 if( shouldSave )
3273 {
3274 pi->SaveBoard( brd->GetFileName(), *brd );
3275 m_reporter->Report( _( "Successfully saved board file using the latest format\n" ), RPT_SEVERITY_INFO );
3276 }
3277 else
3278 {
3279 m_reporter->Report( _( "Board file was not updated\n" ), RPT_SEVERITY_ERROR );
3280 }
3281 }
3282 catch( const IO_ERROR& ioe )
3283 {
3284 wxString msg =
3285 wxString::Format( _( "Error saving board file '%s'.\n%s" ), job->m_filename, ioe.What().GetData() );
3286 m_reporter->Report( msg, RPT_SEVERITY_ERROR );
3288 }
3289
3291}
3292
3293
3294// Classify library footprints so their losses appear in the report.
3295static void classifyFootprintLibraries( const wxString& aDir, const DOWNGRADE_TARGET& aTarget,
3296 COMPATIBILITY_REPORT& aReport, bool aDropInsteadOfApproximate )
3297{
3298 wxArrayString files;
3299 wxDir::GetAllFiles( aDir, &files, wxT( "*.kicad_mod" ) );
3300
3301 for( const wxString& file : files )
3302 {
3303 if( IsDowngradeWorkingArtifact( file ) )
3304 continue;
3305
3306 try
3307 {
3308 PCB_IO_KICAD_SEXPR loader;
3309 wxString nameOut;
3310 std::unique_ptr<FOOTPRINT> fp = loader.ImportFootprint( file, nameOut );
3311
3312 if( !fp )
3313 continue;
3314
3315 BOARD scratch;
3316 scratch.Add( fp.release() );
3317
3318 aReport.Merge( ClassifyBoardForDowngrade( &scratch, aTarget, aDropInsteadOfApproximate ) );
3319 }
3320 catch( const IO_ERROR& )
3321 {
3322 // A footprint that fails to load is caught later by the downgrade pass itself.
3323 }
3324 }
3325}
3326
3327
3328// Downgrade every .kicad_mod under the folder in place. Returns the first file the target could
3329// not open, or empty when all are safe.
3330static wxString downgradeFootprintLibraries( const wxString& aDir, const DOWNGRADE_TARGET& aTarget,
3331 bool aDropInsteadOfApproximate,
3332 DOWNGRADE_FILE_TRANSACTION* aTransaction = nullptr,
3333 wxString* aRecoveryError = nullptr )
3334{
3335 auto convert = [&]( const wxString& aFile, const wxString& aTmp ) -> DOWNGRADE_FILE_RESULT
3336 {
3337 return DowngradeFootprintFileToTemp( aFile, aTmp, aTarget, aDropInsteadOfApproximate );
3338 };
3339
3341 aDir, wxT( "*.kicad_mod" ), aTarget.m_boardVersion,
3342 [&]( const wxString& aText )
3343 {
3344 return FindUnsupportedBoardToken( aText, aTarget );
3345 },
3346 convert, aTransaction, aRecoveryError );
3347}
3348
3349
3350static wxString footprintRefusedMessage( const wxString& aFile, const DOWNGRADE_TARGET& aTarget )
3351{
3352 return wxString::Format( _( "Cannot export: footprint '%s' uses a feature %s cannot open.\n" ), aFile,
3353 aTarget.m_name );
3354}
3355
3356
3358{
3359 JOB_PCB_DOWNGRADE* job = dynamic_cast<JOB_PCB_DOWNGRADE*>( aJob );
3360
3361 if( job == nullptr )
3363
3364 const DOWNGRADE_TARGET* target =
3365 job->m_target.IsEmpty() ? &GetDowngradeTargets().front() : FindDowngradeTarget( job->m_target );
3366
3367 if( target == nullptr )
3368 {
3369 m_reporter->Report( wxString::Format( _( "Unknown target version '%s'. Valid targets: %s\n" ), job->m_target,
3373 }
3374
3375 if( !job->m_libraryDir.IsEmpty() && !wxDirExists( job->m_libraryDir ) )
3376 {
3377 m_reporter->Report( wxString::Format( _( "The library folder '%s' does not exist.\n" ), job->m_libraryDir ),
3380 }
3381
3382 if( !job->m_variant.IsEmpty() && target->m_boardVersion >= FIRST_PCB_VARIANTS )
3383 {
3384 m_reporter->Report( _( "The target supports variants, so there is nothing to flatten.\n" ),
3387 }
3388
3389 wxString pathError;
3390 wxString libraryCopy;
3391
3392 if( !job->m_libraryDir.IsEmpty() && !ValidateDowngradeLibrarySource( job->m_libraryDir, pathError ) )
3393 {
3394 m_reporter->Report( pathError + wxT( "\n" ), RPT_SEVERITY_ERROR );
3396 }
3397
3398 if( !job->m_inPlace && !job->m_dryRun )
3399 {
3400 wxFileName output( job->m_outputFile );
3401
3402 if( !job->m_outputFile.IsEmpty() && output.MakeAbsolute() )
3403 job->m_outputFile = output.GetFullPath();
3404
3405 if( !ValidateFreshDowngradeOutput( job->m_outputFile, pathError ) )
3406 {
3407 m_reporter->Report( pathError + wxT( "\n" ), RPT_SEVERITY_ERROR );
3409 }
3410
3411 if( !job->m_libraryDir.IsEmpty() )
3412 {
3413 libraryCopy = DowngradeLibraryCopyPath( job->m_libraryDir, output.GetPath() );
3414
3415 if( libraryCopy.IsEmpty() || !ValidateDowngradeLibraryCopy( job->m_libraryDir, libraryCopy, pathError ) )
3416 {
3417 m_reporter->Report( pathError + wxT( "\n" ), RPT_SEVERITY_ERROR );
3419 }
3420 }
3421 }
3422
3423 try
3424 {
3425 if( !job->m_filename.IsEmpty() )
3426 {
3427 // Load a private copy, never the live editor board.
3428 PCB_IO_KICAD_SEXPR loader;
3429 std::unique_ptr<BOARD> brd( loader.LoadBoard( job->m_filename, nullptr ) );
3430
3431 if( !brd )
3433
3434 if( !job->m_variant.IsEmpty() )
3435 {
3436 const std::vector<wxString>& names = brd->GetVariantNames();
3437
3438 if( names.empty() )
3439 {
3440 // A board only learns variants from the netlist updater. Without any, the
3441 // flatten is vacuous, so a project export must not fail here after the
3442 // schematic side accepted the name.
3443 m_reporter->Report( _( "The board has no variant data. The base board is exported.\n" ),
3445 }
3446 else if( std::find( names.begin(), names.end(), job->m_variant ) == names.end() )
3447 {
3448 m_reporter->Report(
3449 wxString::Format( _( "The board has no variant named '%s'.\n" ), job->m_variant ),
3452 }
3453 else
3454 {
3455 FlattenBoardVariant( brd.get(), job->m_variant );
3456 }
3457 }
3458
3459 COMPATIBILITY_REPORT report =
3460 ClassifyBoardForDowngrade( brd.get(), *target, job->m_dropInsteadOfApproximate );
3461
3462 // Library losses belong in the report the user approves too.
3463 if( !job->m_libraryDir.IsEmpty() )
3465
3466 job->m_report = report;
3467
3468 report.Print( m_reporter );
3469
3470 if( job->m_dryRun )
3472
3473 if( report.IsBlocked() )
3474 {
3475 m_reporter->Report( _( "Cannot export: the board uses features the target cannot represent.\n" ),
3478 }
3479
3480 if( report.IsLossy() && !job->m_force )
3481 {
3482 m_reporter->Report( _( "Export would lose data. Re-run with --force to export anyway.\n" ),
3485 }
3486
3487 DOWNGRADE_OUTPUT_DIRECTORIES outputDirectories;
3488 wxString libraryDir = job->m_libraryDir;
3489
3490 if( !job->m_inPlace )
3491 {
3492 wxFileName output( job->m_outputFile );
3493 output.MakeAbsolute();
3494
3495 if( !outputDirectories.Create( output.GetPath() ) )
3496 {
3497 m_reporter->Report( _( "Could not create the output folder.\n" ), RPT_SEVERITY_ERROR );
3499 }
3500
3501 if( !libraryDir.IsEmpty() )
3502 {
3503 if( !CopyDowngradeLibraryDirectory( libraryDir, libraryCopy, outputDirectories, pathError ) )
3504 {
3505 m_reporter->Report( pathError + wxT( "\n" ), RPT_SEVERITY_ERROR );
3507 }
3508
3509 libraryDir = libraryCopy;
3510 }
3511 }
3512
3513 DOWNGRADE_FILE_TRANSACTION transaction;
3514 wxString stagedBoard = transaction.Stage( job->m_outputFile );
3515
3516 if( stagedBoard.IsEmpty() )
3517 {
3518 m_reporter->Report( _( "Could not export the downgraded board.\n" ), RPT_SEVERITY_ERROR );
3520 }
3521
3522 // The real export loads its own fresh copy and fails closed on any uncovered token.
3523 wxString badToken;
3524
3525 if( !ExportBoardToOlderVersion( job->m_filename, stagedBoard, *target, report, &badToken, job->m_variant,
3527 {
3528 if( !badToken.IsEmpty() )
3529 m_reporter->Report( wxString::Format( _( "Cannot export: the result still uses '%s', which %s "
3530 "cannot open. This feature has no downgrade rule yet.\n" ),
3531 badToken, target->m_name ),
3533 else
3534 m_reporter->Report( _( "Could not export the downgraded board.\n" ), RPT_SEVERITY_ERROR );
3535
3537 }
3538
3539 if( !job->m_libraryDir.IsEmpty() )
3540 {
3541 wxString badLib = downgradeFootprintLibraries( libraryDir, *target, job->m_dropInsteadOfApproximate,
3542 &transaction );
3543
3544 if( !badLib.IsEmpty() )
3545 {
3546 m_reporter->Report( footprintRefusedMessage( badLib, *target ), RPT_SEVERITY_ERROR );
3548 }
3549 }
3550
3551 wxString failedDestination = transaction.Commit();
3552
3553 if( !failedDestination.IsEmpty() )
3554 {
3555 m_reporter->Report( wxString::Format( _( "Could not replace '%s'.\n" ), failedDestination ),
3557
3558 if( !transaction.GetRecoveryError().IsEmpty() )
3559 m_reporter->Report( transaction.GetRecoveryError(), RPT_SEVERITY_ERROR );
3560
3562 }
3563
3564 outputDirectories.Commit();
3565 m_reporter->Report( wxString::Format( _( "Saved downgraded board to '%s'\n" ), job->m_outputFile ),
3567 }
3568 // Library-only mode. Fails closed if any library still uses a token the target cannot open.
3569 else if( !job->m_libraryDir.IsEmpty() )
3570 {
3571 if( !job->m_inPlace && !job->m_dryRun )
3572 {
3573 m_reporter->Report( _( "A board input is required for a library copy export.\n" ), RPT_SEVERITY_ERROR );
3575 }
3576
3577 if( !job->m_variant.IsEmpty() )
3578 {
3579 m_reporter->Report( _( "Variants do not apply to a library downgrade.\n" ), RPT_SEVERITY_ERROR );
3581 }
3582
3583 COMPATIBILITY_REPORT report;
3585 job->m_report = report;
3586 report.Print( m_reporter );
3587
3588 if( job->m_dryRun )
3590
3591 if( report.IsBlocked() )
3592 {
3593 m_reporter->Report( _( "Cannot export: the libraries use features the target cannot represent.\n" ),
3596 }
3597
3598 if( report.IsLossy() && !job->m_force )
3599 {
3600 m_reporter->Report( _( "Export would lose data. Re-run with --force to export anyway.\n" ),
3603 }
3604
3605 wxString recoveryError;
3606 wxString badLib = downgradeFootprintLibraries( job->m_libraryDir, *target, job->m_dropInsteadOfApproximate,
3607 nullptr, &recoveryError );
3608
3609 if( !badLib.IsEmpty() )
3610 {
3611 m_reporter->Report( footprintRefusedMessage( badLib, *target ), RPT_SEVERITY_ERROR );
3612
3613 if( !recoveryError.IsEmpty() )
3614 m_reporter->Report( recoveryError, RPT_SEVERITY_ERROR );
3615
3617 }
3618
3619 m_reporter->Report( _( "Downgraded footprint libraries\n" ), RPT_SEVERITY_INFO );
3620 }
3621 }
3622 catch( const IO_ERROR& ioe )
3623 {
3624 wxString where = job->m_filename.IsEmpty() ? _( "the footprint libraries" ) : job->m_filename;
3625
3626 m_reporter->Report( wxString::Format( _( "Error exporting '%s'.\n%s" ), where, ioe.What().GetData() ),
3629 }
3630
3632}
3633
3634
3635// Most job handlers need to align the running job with the board before resolving any
3636// output paths with variables in them like ${REVISION}.
3637wxString PCBNEW_JOBS_HANDLER::resolveJobOutputPath( JOB* aJob, BOARD* aBoard, const wxString* aDrawingSheet )
3638{
3639 aJob->SetTitleBlock( aBoard->GetTitleBlock() );
3640
3641 if( aDrawingSheet && !aDrawingSheet->IsEmpty() )
3642 loadOverrideDrawingSheet( aBoard, *aDrawingSheet );
3643
3644 PROJECT* project = aBoard->GetProject();
3645
3646 if( project )
3647 project->ApplyTextVars( aJob->GetVarOverrides() );
3648
3649 aBoard->SynchronizeProperties();
3650
3651 return aJob->GetFullOutputPath( project );
3652}
3653
3654
3656{
3657 DS_PROXY_VIEW_ITEM* drawingSheet = new DS_PROXY_VIEW_ITEM( pcbIUScale, &aBrd->GetPageSettings(), aBrd->GetProject(),
3658 &aBrd->GetTitleBlock(), &aBrd->GetProperties() );
3659
3660 drawingSheet->SetSheetName( std::string() );
3661 drawingSheet->SetSheetPath( std::string() );
3662 drawingSheet->SetIsFirstPage( true );
3663
3664 drawingSheet->SetFileName( TO_UTF8( aBrd->GetFileName() ) );
3665
3666 wxString currentVariant = aBrd->GetCurrentVariant();
3667 wxString variantDesc = aBrd->GetVariantDescription( currentVariant );
3668 drawingSheet->SetVariantName( TO_UTF8( currentVariant ) );
3669 drawingSheet->SetVariantDesc( TO_UTF8( variantDesc ) );
3670
3671 return drawingSheet;
3672}
3673
3674
3675void PCBNEW_JOBS_HANDLER::loadOverrideDrawingSheet( BOARD* aBrd, const wxString& aSheetPath )
3676{
3677 // dont bother attempting to load a empty path, if there was one
3678 if( aSheetPath.IsEmpty() )
3679 return;
3680
3681 auto loadSheet = [&]( const wxString& path ) -> bool
3682 {
3684 FILENAME_RESOLVER resolver;
3685 resolver.SetProject( aBrd->GetProject() );
3686 resolver.SetProgramBase( &Pgm() );
3687
3688 wxString filename = resolver.ResolvePath( BASE_SCREEN::m_DrawingSheetFileName,
3689 aBrd->GetProject()->GetProjectPath(), { aBrd->GetEmbeddedFiles() } );
3690 wxString msg;
3691
3692 if( !DS_DATA_MODEL::GetTheInstance().LoadDrawingSheet( filename, &msg ) )
3693 {
3694 m_reporter->Report( wxString::Format( _( "Error loading drawing sheet '%s'." ), path ) + wxS( "\n" ) + msg
3695 + wxS( "\n" ),
3697 return false;
3698 }
3699
3700 return true;
3701 };
3702
3703 if( loadSheet( aSheetPath ) )
3704 return;
3705
3706 // failed loading custom path, revert back to default
3707 loadSheet( aBrd->GetProject()->GetProjectFile().m_BoardDrawingSheetFile );
3708}
3709
3710
3711// Resolve a KiCad layer name (canonical board-file name such as "F.Cu", or the GUI display name)
3712// to its layer id. Returns UNDEFINED_LAYER when no layer matches.
3713static PCB_LAYER_ID resolveKiCadLayerName( const wxString& aName )
3714{
3715 for( PCB_LAYER_ID layer : LSET::AllLayersMask().Seq() )
3716 {
3717 if( LSET::Name( layer ) == aName || LayerName( layer ) == aName )
3718 return layer;
3719 }
3720
3721 return UNDEFINED_LAYER;
3722}
3723
3724
3726{
3727 JOB_PCB_IMPORT* job = dynamic_cast<JOB_PCB_IMPORT*>( aJob );
3728
3729 if( !job )
3731
3732 // Check that input file exists
3733 if( !wxFile::Exists( job->m_inputFile ) )
3734 {
3735 m_reporter->Report( wxString::Format( _( "Input file not found: '%s'\n" ),
3736 job->m_inputFile ),
3739 }
3740
3741 // Map job format to PCB_IO file type
3743
3744 switch( job->m_format )
3745 {
3747
3748 // "pads" names the vendor format, not the dialect: PADS writes both an ASCII export and a
3749 // binary PowerPCB database, and both use the .pcb extension. Resolve the dialect by content
3750 // so an explicit --format pads on a binary file imports it instead of handing it to the ASCII
3751 // reader, which parses nothing and silently yields an empty board.
3756 break;
3757
3759
3761
3763
3765
3767
3769
3771 }
3772
3773 // FindPluginTypeFromBoardPath returns FILE_TYPE_NONE (not PCB_FILE_UNKNOWN) when no plugin
3774 // claims the file. Quiet sentinel: lets the top-level `import` command try the schematic face.
3776 {
3777 m_reporter->Report( wxString::Format( _( "No PCB importer recognizes the file format of "
3778 "'%s'\n" ),
3779 job->m_inputFile ),
3782 }
3783
3784 if( job->m_probeOnly )
3785 {
3787
3788 if( !pi )
3789 {
3790 m_reporter->Report( wxString::Format( _( "No plugin found for file type '%s'\n" ),
3794 }
3795
3796 try
3797 {
3798 job->m_projectBoards = pi->EnumerateProjectBoards( job->m_inputFile );
3799 }
3800 catch( const IO_ERROR& ioe )
3801 {
3802 m_reporter->Report( wxString::Format( _( "Failed to inspect board project: %s\n" ), ioe.What() ),
3805 }
3806
3808 }
3809
3810
3811 // Determine output path
3812 wxString outputPath = job->GetConfiguredOutputPath();
3813
3814 if( outputPath.IsEmpty() )
3816
3817 // The generated footprint library and its table row belong to the *active* project, so an
3818 // import with no project loaded needs a transient active one at the output location (never
3819 // written to disk; LoadProject returns false yet still registers it, hence the GetProject()
3820 // check).
3822 PROJECT* projectPtr = nullptr;
3823 bool createdTransientProject = false;
3824
3825 if( mgr.IsProjectOpenNotDummy() )
3826 {
3827 projectPtr = &mgr.Prj();
3828 }
3829 else
3830 {
3831 wxFileName projectFn( outputPath );
3832 projectFn.SetExt( FILEEXT::ProjectFileExtension );
3833
3834 // PROJECT requires an absolute path; -o may be relative to the working directory
3835 projectFn.MakeAbsolute();
3836
3837 mgr.LoadProject( projectFn.GetFullPath(), true );
3838 projectPtr = mgr.GetProject( projectFn.GetFullPath() );
3839 createdTransientProject = ( projectPtr != nullptr );
3840 }
3841
3842 if( !projectPtr )
3843 {
3844 m_reporter->Report( _( "Could not establish a project for the import\n" ),
3847 }
3848
3849 // unloads the transient project on every exit path
3851 {
3852 SETTINGS_MANAGER& m_mgr;
3853 PROJECT* m_project;
3854 bool m_active;
3855
3857 {
3858 if( m_active )
3859 m_mgr.UnloadProject( m_project, false );
3860 }
3861 } transientProjectGuard{ mgr, projectPtr, createdTransientProject };
3862
3863 std::unique_ptr<BOARD> board;
3864
3865 struct BOARD_PROJECT_GUARD
3866 {
3867 std::unique_ptr<BOARD>& board;
3868
3869 ~BOARD_PROJECT_GUARD()
3870 {
3871 if( board )
3872 board->ClearProject();
3873 }
3874 } boardProjectGuard{ board };
3875
3876 wxString formatName = PCB_IO_MGR::ShowType( fileType );
3877 std::vector<wxString> warnings;
3878
3879 // Real source-to-KiCad layer decisions, captured by our mapping callback so the report can
3880 // show them and so explicit overrides can be validated.
3881 struct CAPTURED_LAYER
3882 {
3883 wxString m_source;
3884 PCB_LAYER_ID m_target;
3885 wxString m_method;
3886 };
3887
3888 std::vector<CAPTURED_LAYER> capturedLayers;
3889 std::set<wxString> seenSourceLayers;
3890 bool layersCaptured = false;
3891
3892 try
3893 {
3894 // Declared before the plugin so it outlives the plugin's pointer to it
3895 IMPORT_MESSAGE_COLLECTOR importMessages( *m_reporter );
3897
3898 if( !pi )
3899 {
3900 m_reporter->Report( wxString::Format( _( "No plugin found for file type '%s'\n" ), formatName ),
3903 }
3904
3905 // Without this the importer's warnings never reach the import report
3906 pi->SetReporter( &importMessages );
3907
3908 // Replace the plugin's default best-guess callback so we can apply explicit overrides and
3909 // capture the resulting mapping. Only mappable importers expose their source layers; for
3910 // others the report falls back to listing the imported board's enabled layers.
3911 if( LAYER_MAPPABLE_PLUGIN* mappable = dynamic_cast<LAYER_MAPPABLE_PLUGIN*>( pi.get() ) )
3912 {
3913 if( !job->m_layerMap.empty() || job->m_reportFormat != IMPORT_REPORT_FORMAT::NONE )
3914 {
3915 mappable->RegisterCallback(
3916 [&]( const std::vector<INPUT_LAYER_DESC>& aDescs )
3917 -> std::map<wxString, PCB_LAYER_ID>
3918 {
3919 std::map<wxString, PCB_LAYER_ID> result;
3920
3921 for( const INPUT_LAYER_DESC& desc : aDescs )
3922 {
3923 PCB_LAYER_ID target = desc.AutoMapLayer;
3924 wxString method = wxS( "auto" );
3925
3926 if( auto it = job->m_layerMap.find( desc.Name );
3927 it != job->m_layerMap.end() )
3928 {
3929 PCB_LAYER_ID resolved = resolveKiCadLayerName( it->second );
3930
3931 if( resolved == UNDEFINED_LAYER )
3932 {
3933 warnings.push_back( wxString::Format(
3934 _( "Layer map entry '%s' -> '%s' names an unknown "
3935 "KiCad layer; using automatic mapping instead" ),
3936 desc.Name, it->second ) );
3937 }
3938 else if( !desc.PermittedLayers.Contains( resolved ) )
3939 {
3940 warnings.push_back( wxString::Format(
3941 _( "Layer map entry '%s' -> '%s' is not a permitted "
3942 "target for this layer; using automatic mapping "
3943 "instead" ),
3944 desc.Name, it->second ) );
3945 }
3946 else
3947 {
3948 target = resolved;
3949 method = wxS( "explicit" );
3950 }
3951 }
3952
3953 if( desc.Required && target == UNDEFINED_LAYER )
3954 {
3955 warnings.push_back( wxString::Format(
3956 _( "No KiCad layer mapping for required source layer "
3957 "'%s'; its items will not be imported" ),
3958 desc.Name ) );
3959 }
3960
3961 result.emplace( desc.Name, target );
3962 capturedLayers.push_back( { desc.Name, target, method } );
3963 seenSourceLayers.insert( desc.Name );
3964 }
3965
3966 layersCaptured = true;
3967 return result;
3968 } );
3969 }
3970 }
3971 else if( !job->m_layerMap.empty() )
3972 {
3973 warnings.push_back( _( "A layer map was provided, but this importer does not support "
3974 "layer remapping; it will be ignored" ) );
3975 }
3976
3977 m_reporter->Report(
3978 wxString::Format( _( "Importing '%s' using %s format...\n" ), job->m_inputFile, formatName ),
3980
3981 // LoadBoard reports load failures and user cancellations by throwing.
3982 try
3983 {
3984 // pcb_id selects a board only within an EasyEDA Pro project. Other importers must
3985 // continue receiving no properties so their existing design-settings import paths remain
3986 // unchanged.
3987 const std::map<std::string, UTF8>* importProperties = nullptr;
3988 std::map<std::string, UTF8> easyEdaProProperties;
3989
3990 if( !job->m_importPcbId.empty()
3992 {
3993 easyEdaProProperties.emplace( "pcb_id", std::string( job->m_importPcbId.ToUTF8() ) );
3994 importProperties = &easyEdaProProperties;
3995 }
3996
3997 board = pi->LoadBoard( job->m_inputFile, importProperties, projectPtr );
3998 }
3999 catch( const IO_CANCELLED& ioce )
4000 {
4001 // We should not be here, as the plugin should not have used an interactive dialog
4002 // in the CLI context.
4003 // But technically the file is not invalid
4004 m_reporter->Report( wxString::Format( _( "Unexpected cancellation: %s\n" ), ioce.What() ),
4007 }
4008 catch( const IO_ERROR& ioe )
4009 {
4010 m_reporter->Report( wxString::Format( _( "Failed to load board: %s\n" ), ioe.What() ), RPT_SEVERITY_ERROR );
4012 }
4013 board->SetProject( projectPtr, job->m_isPartOfMultiBoardProject );
4014
4015
4016
4017 if( !ApplyImportedNetNameMap( *board, job->m_netNameMap, *m_reporter ) )
4019
4020 // Extract a project footprint library and re-link FPIDs, as the board editor does.
4021 // Each EasyEDA Pro project board needs a distinct cache; using the archive name makes
4022 // every board compete to publish the same .pretty directory.
4024 {
4025 const wxString reconciliationPath =
4026 job->m_importPcbId.empty() ? job->m_inputFile : outputPath;
4027 ReconcileImportedFootprints( *pi, *board, *projectPtr, reconciliationPath, nullptr,
4028 *m_reporter );
4029 }
4030
4031 // Flag explicit map entries that never matched a source layer so typos do not pass silently.
4032 if( layersCaptured )
4033 {
4034 for( const auto& [source, target] : job->m_layerMap )
4035 {
4036 if( !seenSourceLayers.contains( source ) )
4037 {
4038 warnings.push_back( wxString::Format(
4039 _( "Layer map entry '%s' does not match any source layer in the "
4040 "imported file; it will be ignored" ),
4041 source ) );
4042 }
4043 }
4044 }
4045
4046 // Save as KiCad format
4047
4049 kicadPlugin->SaveBoard( outputPath, *board );
4050 wxString importedRules = pi->GetImportedDesignRules();
4051
4052 if( !importedRules.IsEmpty() )
4053 {
4054 wxFileName rulesPath( outputPath );
4055 rulesPath.SetExt( FILEEXT::DesignRulesFileExtension );
4056
4057 wxFile rulesFile( rulesPath.GetFullPath(), wxFile::write );
4058
4059 if( rulesFile.IsOpened() )
4060 {
4061 rulesFile.Write( importedRules );
4062 }
4063 else
4064 {
4065 m_reporter->Report( wxString::Format( _( "Could not write imported design rules to '%s'\n" ),
4066 rulesPath.GetFullPath() ),
4068 }
4069 }
4070
4071 m_reporter->Report( wxString::Format( _( "Successfully saved imported board to '%s'\n" ), outputPath ),
4073
4074 // Generate report if requested
4075 IMPORT_REPORT_DATA reportData;
4076 importMessages.Flush( job->m_reportFormat, reportData );
4077
4079 {
4080 reportData.m_sourceFile = wxFileName( job->m_inputFile ).GetFullName();
4081 reportData.m_sourceFormat = formatName;
4082 reportData.m_outputFile = wxFileName( outputPath ).GetFullName();
4083
4084 size_t trackCount = 0;
4085 size_t viaCount = 0;
4086
4087 for( PCB_TRACK* track : board->Tracks() )
4088 {
4089 if( track->Type() == PCB_VIA_T )
4090 viaCount++;
4091 else
4092 trackCount++;
4093 }
4094
4095 reportData.m_statistics = {
4096 { wxS( "footprints" ), board->Footprints().size() },
4097 { wxS( "tracks" ), trackCount },
4098 { wxS( "vias" ), viaCount },
4099 { wxS( "zones" ), board->Zones().size() }
4100 };
4101
4102 // Build layer mapping info, carried only in the JSON report. Prefer the real
4103 // source-to-KiCad decisions captured during load; fall back to the imported board's
4104 // enabled layers for importers that do not expose a mappable layer set.
4105 nlohmann::json layerMappings = nlohmann::json::object();
4106
4107 if( layersCaptured )
4108 {
4109 for( const CAPTURED_LAYER& mapped : capturedLayers )
4110 {
4111 std::string kicadLayer = mapped.m_target == UNDEFINED_LAYER
4112 ? std::string()
4113 : LSET::Name( mapped.m_target ).ToStdString();
4114
4115 layerMappings[mapped.m_source.ToStdString()] = {
4116 { "kicad_layer", kicadLayer },
4117 { "method", mapped.m_method.ToStdString() }
4118 };
4119 }
4120 }
4121 else
4122 {
4123 for( PCB_LAYER_ID layer : board->GetEnabledLayers().Seq() )
4124 {
4125 wxString layerName = board->GetLayerName( layer );
4126
4127 layerMappings[layerName.ToStdString()] = {
4128 { "kicad_layer", LSET::Name( layer ).ToStdString() },
4129 { "method", "auto" }
4130 };
4131 }
4132 }
4133
4134 reportData.m_extraJson["layer_mapping"] = layerMappings;
4135 reportData.m_warnings.insert( reportData.m_warnings.end(), warnings.begin(), warnings.end() );
4136
4137 if( !WriteImportReport( m_reporter, job->m_reportFormat, job->m_reportFile, reportData ) )
4139 }
4140 else
4141 {
4142 // No report requested, but explicit-mapping problems still need to surface.
4143 for( const wxString& warning : warnings )
4144 m_reporter->Report( warning + wxS( "\n" ), RPT_SEVERITY_WARNING );
4145 }
4146 }
4147 catch( const IO_ERROR& ioe )
4148 {
4149 m_reporter->Report( wxString::Format( _( "Error during import: %s\n" ), ioe.What() ), RPT_SEVERITY_ERROR );
4150
4152 }
4153
4155}
4156
4157
4158// ============================================================================
4159// JobDiff: pcb_diff implementation
4160// ============================================================================
4163#include <diff_merge/diff_scene.h>
4164#include <diff_merge/pcb_differ.h>
4166#include <jobs/job_pcb_diff.h>
4167
4168
4169// Load a board into a SCRATCH_DOC<BOARD> that keeps its project attached for
4170// the document's lifetime — the differ/applier read PROJECT_FILE-scoped fields
4171// (drawing-sheet path, DRC severities, net classes). The destructor severs the
4172// BOARD->project link in the right order. Used by every PCB diff/merge job.
4173static SCRATCH_DOC<BOARD> loadScratchBoard( SETTINGS_MANAGER& aMgr, const wxString& aPath,
4174 bool aInitializeAfterLoad = true )
4175{
4176 return LoadScratchDoc<BOARD>(
4177 aMgr, aPath,
4178 [aPath, aInitializeAfterLoad]( PROJECT* aProject ) -> std::unique_ptr<BOARD>
4179 {
4180 PCB_IO_MGR::PCB_FILE_T pluginType =
4182
4183 if( !aProject || pluginType == PCB_IO_MGR::FILE_TYPE_NONE )
4184 return nullptr;
4185
4187 opts.initialize_after_load = aInitializeAfterLoad;
4188
4189 try
4190 {
4191 return BOARD_LOADER::Load( aPath, pluginType, aProject, opts );
4192 }
4193 catch( ... )
4194 {
4195 return nullptr;
4196 }
4197 },
4198 []( BOARD* aBoard )
4199 {
4200 if( aBoard )
4201 aBoard->ClearProject();
4202 } );
4203}
4204
4205
4207{
4208 JOB_PCB_DIFF* diffJob = dynamic_cast<JOB_PCB_DIFF*>( aJob );
4209
4210 if( !diffJob )
4212
4213 // SCRATCH_DOC<BOARD> keeps each board's project attached for the lifetime
4214 // of the diff, which the differ needs to read project-file-scoped fields
4215 // (m_BoardDrawingSheetFile, etc). The previous loadStandaloneBoard +
4216 // ClearProject-up-front path would null those out before the differ ran.
4218
4219 SCRATCH_DOC<BOARD> aScratch = loadScratchBoard( diffMgr, diffJob->m_inputA );
4220 SCRATCH_DOC<BOARD> bScratch = loadScratchBoard( diffMgr, diffJob->m_inputB );
4221
4222 BOARD* boardA = aScratch.doc.get();
4223 BOARD* boardB = bScratch.doc.get();
4224
4225 if( !boardA )
4226 {
4227 m_reporter->Report( wxString::Format( _( "Failed to load %s\n" ), diffJob->m_inputA ), RPT_SEVERITY_ERROR );
4229 }
4230
4231 if( !boardB )
4232 {
4233 m_reporter->Report( wxString::Format( _( "Failed to load %s\n" ), diffJob->m_inputB ), RPT_SEVERITY_ERROR );
4235 }
4236
4237 KICAD_DIFF::PCB_DIFFER differ( boardA, boardB, diffJob->m_inputB );
4239
4240 int diffExitCode = KICAD_DIFF::DiffExitCode( result );
4241
4242 if( diffJob->m_exitCodeOnly )
4243 return diffExitCode;
4244
4245 // The board geometry rendered beneath the change overlay (PNG/SVG only)
4246 // matches what the interactive dialog draws.
4248 KICAD_DIFF::MakeEmitOptions( *diffJob, diffJob->m_inputA, diffJob->m_inputB );
4250 emitOpts.referenceGeometry = [&]( const KIGFX::COLOR4D& aColor )
4251 { return KICAD_DIFF::ExtractBoardGeometry( *boardA, aColor ); };
4252 emitOpts.comparisonGeometry = [&]( const KIGFX::COLOR4D& aColor )
4253 { return KICAD_DIFF::ExtractBoardGeometry( *boardB, aColor ); };
4254
4255 return KICAD_DIFF::EmitDiffResult( result, emitOpts, diffExitCode, *m_reporter );
4256}
4257
4258
4259// ============================================================================
4260// JobMerge: pcb_merge implementation
4261// ============================================================================
4266
4267
4268int PCBNEW_JOBS_HANDLER::RunMerge( KICAD_DIFF::DOC_KIND aKind, const wxString& aAncestor,
4269 const wxString& aOurs, const wxString& aTheirs,
4270 const wxString& aOutput, bool aInteractive, bool aSingleFile,
4271 REPORTER* aReporter )
4272{
4273 // Restore m_reporter on scope exit so a caller's transient (often
4274 // stack-local) reporter doesn't outlive this call as a dangling member.
4276 aReporter ? aReporter : m_reporter );
4277
4279 return runFpLibMerge( aAncestor, aOurs, aTheirs, aOutput, aSingleFile );
4280
4281 return runPcbMerge( aAncestor, aOurs, aTheirs, aOutput, aInteractive );
4282}
4283
4284
4285int PCBNEW_JOBS_HANDLER::runPcbMerge( const wxString& aAncestor, const wxString& aOurs,
4286 const wxString& aTheirs, const wxString& aOutput,
4287 bool aInteractive )
4288{
4289 // Use SCRATCH_DOC<BOARD> so each input keeps its project attached for the
4290 // life of the merge — necessary for any doc-level resolution that mutates
4291 // PROJECT_FILE-scoped state (DRC severities, net classes) and needs to be
4292 // saved as a sibling .kicad_pro. SCRATCH_DOC's destructor severs the
4293 // BOARD->project link in the right order and unloads the project from
4294 // the manager, avoiding the dangling-PROJECT_FILE::m_BoardSettings pointer
4295 // the previous up-front-ClearProject loadStandaloneBoard path had to
4296 // guard against.
4298
4299 SCRATCH_DOC<BOARD> ancestorScratch = loadScratchBoard( mgr, aAncestor );
4300 SCRATCH_DOC<BOARD> oursScratch = loadScratchBoard( mgr, aOurs );
4301 SCRATCH_DOC<BOARD> theirsScratch = loadScratchBoard( mgr, aTheirs );
4302
4303 BOARD* ancestor = ancestorScratch.doc.get();
4304 BOARD* ours = oursScratch.doc.get();
4305 BOARD* theirs = theirsScratch.doc.get();
4306
4307 if( !ancestor || !ours || !theirs )
4308 {
4309 m_reporter->Report( _( "Failed to load one or more input boards\n" ), RPT_SEVERITY_ERROR );
4311 }
4312
4313 KICAD_DIFF::PCB_DIFFER ourDiff( ancestor, ours );
4314 KICAD_DIFF::PCB_DIFFER theirDiff( ancestor, theirs );
4315
4316 KICAD_DIFF::DOCUMENT_DIFF ourDocDiff = ourDiff.Diff();
4317 KICAD_DIFF::DOCUMENT_DIFF theirDocDiff = theirDiff.Diff();
4318
4320 KICAD_DIFF::MERGE_PLAN plan = engine.Plan( ourDocDiff, theirDocDiff );
4321
4322 // A cancelled dialog leaves plan unresolved and falls through to the
4323 // marker flow below.
4324 if( aInteractive && !plan.Resolved() )
4325 {
4326 if( !Pgm().IsGUI() )
4327 {
4328 m_reporter->Report( _( "--interactive requires a GUI KiCad process; the console "
4329 "kicad-cli cannot open dialogs.\n" ),
4332 }
4333
4334 // Geometry context so the conflict viewer can render the actual
4335 // boards behind the conflict bbox highlight.
4336 const KICAD_DIFF::DIFF_COLOR_THEME theme;
4341
4342 // Build per-side bbox lookups so a "moved on theirs" item highlights
4343 // at its theirs-side coordinates when the user previews Theirs.
4345 KICAD_DIFF::CollectChangeBBoxes( theirDocDiff, ctx.theirsBBoxes );
4346
4347 DIALOG_KICAD_MERGE_3WAY dlg( wxTheApp->GetTopWindow(), plan, std::move( ctx ) );
4348
4349 if( dlg.ShowModal() == wxID_APPLY )
4350 plan = dlg.GetResolvedPlan();
4351 }
4352
4353 // Snapshot of the plan before the applier moves it; drives the
4354 // unresolved-conflict report below.
4355 const KICAD_DIFF::MERGE_PLAN planSnapshot = plan;
4356
4357 KICAD_DIFF::PCB_MERGE_APPLIER applier( ancestor, ours, theirs, std::move( plan ) );
4358 std::unique_ptr<BOARD> merged = applier.Apply();
4359
4360 if( !merged )
4361 {
4362 m_reporter->Report( _( "Merge applier failed to produce a board\n" ), RPT_SEVERITY_ERROR );
4364 }
4365
4366 // Serialize to the output path using the canonical PCB IO.
4367 PCB_IO_KICAD_SEXPR pcbIO;
4368
4369 try
4370 {
4371 pcbIO.SaveBoard( aOutput, *merged );
4372 }
4373 catch( const IO_ERROR& ioe )
4374 {
4375 m_reporter->Report( wxString::Format( _( "Failed to save merged board: %s\n" ), ioe.What() ),
4378 }
4379
4380 // BOARD_DESIGN_SETTINGS fields like m_DRCSeverities serialize to
4381 // .kicad_pro, not .kicad_pcb. Mirror only those specific fields onto
4382 // ancestor (still linked to its project via BOARD::SetProject) then
4383 // save ancestor's project alongside the merged board file. Whole-
4384 // BOARD_DESIGN_SETTINGS copy would alias shared_ptr<NET_SETTINGS>
4385 // across BOARDs and crash on ClearProject during SCRATCH_DOC release;
4386 // single-field mirror avoids that.
4387 if( applier.GetReport().projectFileTouched && ancestor && ancestor->GetProject() )
4388 {
4389 ancestor->GetDesignSettings().m_DRCSeverities = merged->GetDesignSettings().m_DRCSeverities;
4390
4391 // Mirror net settings (the applier copied them onto the result via
4392 // NET_SETTINGS::CopyFrom; do the same here to ancestor, which still
4393 // owns the project's nested-settings registration so SaveProjectCopy
4394 // walks the right entry).
4395 if( ancestor->GetDesignSettings().m_NetSettings && merged->GetDesignSettings().m_NetSettings )
4396 {
4397 ancestor->GetDesignSettings().m_NetSettings->CopyFrom( *merged->GetDesignSettings().m_NetSettings );
4398 }
4399
4400 // The applier stages drawing-sheet resolutions on the report (the
4401 // result BOARD is project-less). Mirror onto ancestor's project here
4402 // before SaveProjectCopy walks the PROJECT_FILE.
4403 if( applier.GetReport().drawingSheetFileSet )
4404 {
4406 }
4407
4408 wxFileName proFn( aOutput );
4409 proFn.SetExt( FILEEXT::ProjectFileExtension );
4410
4411 // JSON-patch path: flush ancestor's in-memory project to its JSON
4412 // cache, then patch only the diffed DOC_PROP fields onto the output
4413 // file. This preserves any non-diffed fields the user had at the
4414 // output path (text variables, last paths, layer presets etc.) that
4415 // a full SaveProjectCopy would silently overwrite.
4416 PROJECT_FILE& ancProj = ancestor->GetProject()->GetProjectFile();
4417 ancProj.Store();
4418
4419 const KICAD_DIFF::PCB_MERGE_APPLIER::REPORT& mergeReport = applier.GetReport();
4420
4421 // PROJECT_FILE::Store() flushes the project file's own params but not
4422 // its registered NESTED_SETTINGS. Flush only the nested settings the
4423 // merge resolved so the surgical patch does not overwrite unrelated
4424 // project subtrees.
4425 if( mergeReport.drcSeveritiesTouched )
4426 ancestor->GetDesignSettings().SaveToFile( wxEmptyString, true );
4427
4428 if( mergeReport.netClassesTouched && ancestor->GetDesignSettings().m_NetSettings )
4429 ancestor->GetDesignSettings().m_NetSettings->SaveToFile( wxEmptyString, true );
4430
4431 std::set<wxString> touched;
4432 if( mergeReport.drcSeveritiesTouched )
4433 touched.insert( KICAD_DIFF::DOC_PROP_DRC_SEVERITIES );
4434
4435 if( mergeReport.netClassesTouched )
4436 touched.insert( KICAD_DIFF::DOC_PROP_NET_CLASSES );
4437
4438 if( applier.GetReport().drawingSheetFileSet )
4439 touched.insert( KICAD_DIFF::DOC_PROP_DRAWING_SHEET );
4440
4441 if( !KICAD_DIFF::ApplyProjectFilePatches( proFn.GetFullPath(), *ancProj.Internals(), touched ) )
4442 {
4443 // Patch failed (existing output unparseable or write error).
4444 // Fall back to the legacy full-copy path so the user still gets
4445 // a project file even if it overwrites non-diffed customisations.
4446 if( !mgr.SaveProjectCopy( proFn.GetFullPath(), ancestor->GetProject() ) )
4447 {
4448 m_reporter->Report(
4449 wxString::Format( _( "Failed to save merged project file: %s\n" ), proFn.GetFullPath() ),
4452 }
4453 }
4454
4455 // Write a project-dir sibling file from staged report content. Empty
4456 // content removes the file so a TAKE_ANCESTOR resolution against an
4457 // ancestor with no file clears stale content at the output path.
4458 auto writeStagedFile = [&]( const wxString& aPath, const wxString& aContent, const wxString& aLabel ) -> bool
4459 {
4460 if( aContent.IsEmpty() )
4461 {
4462 if( wxFileExists( aPath ) )
4463 wxRemoveFile( aPath );
4464
4465 return true;
4466 }
4467
4468 wxFile out;
4469
4470 if( !out.Open( aPath, wxFile::write ) || !out.Write( aContent ) )
4471 {
4472 m_reporter->Report( wxString::Format( _( "Failed to save merged %s: %s\n" ), aLabel, aPath ),
4474 return false;
4475 }
4476
4477 return true;
4478 };
4479
4480 // Custom DRC rules: write the applier's staged content next to the
4481 // merged board so the chosen side's rules apply at next DRC run.
4482 if( applier.GetReport().customDrcRulesSet )
4483 {
4484 wxFileName druFn( aOutput );
4485 druFn.SetExt( FILEEXT::DesignRulesFileExtension );
4486
4487 if( !writeStagedFile( druFn.GetFullPath(), applier.GetReport().customDrcRules, _( "custom DRC rules" ) ) )
4488 {
4490 }
4491 }
4492
4493 // Footprint / symbol library tables: write into the merged project
4494 // directory. Both files have no extension.
4495 if( applier.GetReport().fpLibTableSet )
4496 {
4497 wxFileName fpFn( aOutput );
4498 fpFn.SetFullName( wxString::FromUTF8( FILEEXT::FootprintLibraryTableFileName ) );
4499
4500 if( !writeStagedFile( fpFn.GetFullPath(), applier.GetReport().fpLibTable, _( "footprint library table" ) ) )
4501 {
4503 }
4504 }
4505
4506 if( applier.GetReport().symLibTableSet )
4507 {
4508 wxFileName symFn( aOutput );
4509 symFn.SetFullName( wxString::FromUTF8( FILEEXT::SymbolLibraryTableFileName ) );
4510
4511 if( !writeStagedFile( symFn.GetFullPath(), applier.GetReport().symLibTable, _( "symbol library table" ) ) )
4512 {
4514 }
4515 }
4516 }
4517
4518 // Surface post-apply validator findings (refdes collisions, schema
4519 // mismatch, missed connectivity rebuild). Advisory — they do not change the
4520 // exit code, only the merge's resolved/unresolved status does.
4522 m_reporter->Report( wxString::Format( wxS( "%s: %s\n" ), f.validator, f.message ), f.severity );
4523
4524 // The merged board was written to m_outputPath above, so the output is
4525 // always a valid file. Unresolved conflicts are reported and signalled via
4526 // the exit code; the user resolves them with the interactive mergetool.
4527 if( !planSnapshot.Resolved() )
4528 {
4529 m_reporter->Report( wxString::Format( _( "Merge completed with %zu unresolved conflict(s) in %s\n" ),
4530 planSnapshot.ConflictCount(), aOutput ),
4533 }
4534
4536}
4537
4538
4539// ============================================================================
4540// JobFpDiff: fp_diff implementation
4541// ============================================================================
4543#include <jobs/job_fp_diff.h>
4544
4545
4546// Load one side of a footprint-library diff into its owner vector and name map.
4547// When aAllowEmpty is set an empty path resolves to a clean (empty) side; the
4548// non-interactive job path leaves it unset so a missing path is an input error.
4549static int loadFootprintLibrarySide( const wxString& aPath,
4550 std::vector<std::unique_ptr<FOOTPRINT>>& aOwners,
4551 KICAD_DIFF::FP_LIB_DIFFER::FOOTPRINT_MAP& aMap, bool aAllowEmpty,
4552 REPORTER& aReporter )
4553{
4554 if( aAllowEmpty && aPath.IsEmpty() )
4556
4557 try
4558 {
4559 auto loaded = KICAD_DIFF::FP_LIB_DIFFER::LoadLibrary( aPath );
4560 aOwners = std::move( loaded.first );
4561 aMap = std::move( loaded.second );
4563 }
4564 catch( const IO_ERROR& ioe )
4565 {
4566 aReporter.Report( wxString::Format( _( "Failed to load %s: %s\n" ), aPath, ioe.What() ),
4568 }
4569 catch( const std::exception& e )
4570 {
4571 aReporter.Report(
4572 wxString::Format( _( "Failed to load %s: %s\n" ), aPath, wxString::FromUTF8( e.what() ) ),
4574 }
4575
4577}
4578
4579
4580// Flatten a footprint-library name map into a single DOCUMENT_GEOMETRY tinted
4581// with the supplied per-side theme colour.
4584{
4586
4587 for( const auto& [name, footprint] : aMap )
4588 {
4589 if( footprint )
4590 KICAD_DIFF::AppendGeometry( geometry, KICAD_DIFF::ExtractFootprintGeometry( *footprint, aColor ) );
4591 }
4592
4593 return geometry;
4594}
4595
4596
4598{
4599 JOB_FP_DIFF* diffJob = dynamic_cast<JOB_FP_DIFF*>( aJob );
4600
4601 if( !diffJob )
4603
4604 wxFileName dirA( diffJob->m_inputA );
4605 dirA.MakeAbsolute();
4606 wxFileName dirB( diffJob->m_inputB );
4607 dirB.MakeAbsolute();
4608
4609 std::vector<std::unique_ptr<FOOTPRINT>> ownersA;
4610 std::vector<std::unique_ptr<FOOTPRINT>> ownersB;
4613
4614 if( int rc = loadFootprintLibrarySide( dirA.GetFullPath(), ownersA, mapA, false, *m_reporter );
4616 {
4617 return rc;
4618 }
4619
4620 if( int rc = loadFootprintLibrarySide( dirB.GetFullPath(), ownersB, mapB, false, *m_reporter );
4622 {
4623 return rc;
4624 }
4625
4626 KICAD_DIFF::FP_LIB_DIFFER differ( mapA, mapB, diffJob->m_inputB );
4628
4629 int diffExitCode = KICAD_DIFF::DiffExitCode( result );
4630
4631 if( diffJob->m_exitCodeOnly )
4632 return diffExitCode;
4633
4635 KICAD_DIFF::MakeEmitOptions( *diffJob, diffJob->m_inputA, diffJob->m_inputB );
4637 emitOpts.referenceGeometry = [&]( const KIGFX::COLOR4D& aColor )
4638 { return footprintLibraryGeometry( mapA, aColor ); };
4639 emitOpts.comparisonGeometry = [&]( const KIGFX::COLOR4D& aColor )
4640 { return footprintLibraryGeometry( mapB, aColor ); };
4641
4642 return KICAD_DIFF::EmitDiffResult( result, emitOpts, diffExitCode, *m_reporter );
4643}
4644
4645
4646// ============================================================================
4647// JobOpenDiffDialog: load two on-disk files and open DIALOG_KICAD_DIFF.
4648// Dispatched from the project manager / PR-review dialog via KIWAY.
4649// ============================================================================
4653#include <jobs/scratch_doc.h>
4654
4655
4657 const wxString& aFileB, const wxString& aLabelA,
4658 const wxString& aLabelB, wxWindow* aParent,
4659 REPORTER* aReporter )
4660{
4661 // Restore m_reporter on scope exit so a caller's transient (often
4662 // stack-local) reporter doesn't outlive this call as a dangling member.
4664 aReporter ? aReporter : m_reporter );
4665
4666 wxWindow* parent = aParent ? aParent : ( wxTheApp ? wxTheApp->GetTopWindow() : nullptr );
4667
4669
4670 auto loadBoardScratch = [&]( const wxString& aPath )
4671 {
4672 return loadScratchBoard( mgr, aPath, /* aInitializeAfterLoad */ false );
4673 };
4674
4677 KICAD_DIFF::DOCUMENT_GEOMETRY compGeometry;
4678
4679 auto loadFootprintFile = [&]( const wxString& aPath ) -> std::unique_ptr<FOOTPRINT>
4680 {
4681 if( aPath.IsEmpty() )
4682 return nullptr;
4683
4684 wxFileName fn( aPath );
4685 fn.MakeAbsolute();
4686
4687 // A single .kicad_mod's internal (footprint ...) name need not match its
4688 // filename, so load the file's sole footprint via ImportFootprint rather
4689 // than FootprintLoad (which treats the directory as a .pretty library and
4690 // keys by basename), matching runFpLibMerge's single-file path.
4692 wxString name;
4693 return io.ImportFootprint( fn.GetFullPath(), name );
4694 };
4695
4696 switch( aKind )
4697 {
4699 {
4700 SCRATCH_DOC<BOARD> a = loadBoardScratch( aFileA );
4701 SCRATCH_DOC<BOARD> b = loadBoardScratch( aFileB );
4702
4703 // Synthesize empty boards for ADDED / REMOVED sides so the differ
4704 // can still produce a meaningful per-item list rather than failing
4705 // on an empty input file.
4706 BOARD emptyA;
4707 BOARD emptyB;
4708
4709 if( !a.doc && !aFileA.IsEmpty() )
4710 {
4711 m_reporter->Report( wxString::Format( _( "Failed to load %s\n" ), aFileA ), RPT_SEVERITY_ERROR );
4713 }
4714
4715 if( !b.doc && !aFileB.IsEmpty() )
4716 {
4717 m_reporter->Report( wxString::Format( _( "Failed to load %s\n" ), aFileB ), RPT_SEVERITY_ERROR );
4719 }
4720
4721 BOARD* boardA = a.doc ? a.doc.get() : &emptyA;
4722 BOARD* boardB = b.doc ? b.doc.get() : &emptyB;
4723
4724 KICAD_DIFF::PCB_DIFFER differ( boardA, boardB, aFileB );
4725 result = differ.Diff();
4726
4727 // Extract background geometry so the dialog's canvas shows the
4728 // actual board outline + footprint footprints beneath the diff
4729 // bbox rectangles. Theme defaults: muted blue (ref) / gold (comp).
4730 const KICAD_DIFF::DIFF_COLOR_THEME theme;
4731 refGeometry = KICAD_DIFF::ExtractBoardGeometry( *boardA, theme.reference );
4732 compGeometry = KICAD_DIFF::ExtractBoardGeometry( *boardB, theme.comparison );
4733
4734 const wxString labelA = aLabelA.IsEmpty() ? aFileA : aLabelA;
4735 const wxString labelB = aLabelB.IsEmpty() ? aFileB : aLabelB;
4736
4738 parent, labelA, labelB, result, std::move( refGeometry ), std::move( compGeometry ),
4739 [boardA, boardB, color = theme.reference]( WIDGET_DIFF_CANVAS& aCanvas, const KIID_PATH& )
4740 {
4741 KICAD_DIFF::ConfigurePcbDiffCanvasContext( aCanvas, boardA, boardB, color );
4742 } );
4743 dlg.ShowModal();
4744
4746 }
4748 {
4749 std::vector<std::unique_ptr<FOOTPRINT>> ownersA;
4750 std::vector<std::unique_ptr<FOOTPRINT>> ownersB;
4753
4754 if( int rc = loadFootprintLibrarySide( aFileA, ownersA, mapA, true, *m_reporter );
4756 {
4757 return rc;
4758 }
4759
4760 if( int rc = loadFootprintLibrarySide( aFileB, ownersB, mapB, true, *m_reporter );
4762 {
4763 return rc;
4764 }
4765
4766 KICAD_DIFF::FP_LIB_DIFFER differ( mapA, mapB, aFileB );
4767 result = differ.Diff();
4768
4769 const KICAD_DIFF::DIFF_COLOR_THEME theme;
4770 refGeometry = footprintLibraryGeometry( mapA, theme.reference );
4771 compGeometry = footprintLibraryGeometry( mapB, theme.comparison );
4772 break;
4773 }
4775 {
4776 std::unique_ptr<FOOTPRINT> footprintA;
4777 std::unique_ptr<FOOTPRINT> footprintB;
4778
4779 try
4780 {
4781 footprintA = loadFootprintFile( aFileA );
4782 }
4783 catch( const IO_ERROR& ioe )
4784 {
4785 m_reporter->Report( wxString::Format( _( "Failed to load %s: %s\n" ), aFileA, ioe.What() ),
4788 }
4789
4790 try
4791 {
4792 footprintB = loadFootprintFile( aFileB );
4793 }
4794 catch( const IO_ERROR& ioe )
4795 {
4796 m_reporter->Report( wxString::Format( _( "Failed to load %s: %s\n" ), aFileB, ioe.What() ),
4799 }
4800
4803 const wxString nameA = wxFileName( aFileA ).GetName();
4804 const wxString nameB = wxFileName( aFileB ).GetName();
4805 const wxString itemName = !nameB.IsEmpty() ? nameB : nameA;
4806
4807 if( footprintA )
4808 mapA[itemName] = footprintA.get();
4809
4810 if( footprintB )
4811 mapB[itemName] = footprintB.get();
4812
4813 KICAD_DIFF::FP_LIB_DIFFER differ( mapA, mapB, aFileB );
4814 result = differ.Diff();
4815
4816 const KICAD_DIFF::DIFF_COLOR_THEME theme;
4817
4818 if( footprintA )
4819 refGeometry = KICAD_DIFF::ExtractFootprintGeometry( *footprintA, theme.reference );
4820
4821 if( footprintB )
4822 compGeometry = KICAD_DIFF::ExtractFootprintGeometry( *footprintB, theme.comparison );
4823
4824 break;
4825 }
4826 default:
4827 m_reporter->Report( _( "Unsupported document kind for this dispatcher.\n" ), RPT_SEVERITY_ERROR );
4829 }
4830
4831 const wxString labelA = aLabelA.IsEmpty() ? aFileA : aLabelA;
4832 const wxString labelB = aLabelB.IsEmpty() ? aFileB : aLabelB;
4833
4834 DIALOG_KICAD_DIFF dlg( parent, labelA, labelB, result, std::move( refGeometry ), std::move( compGeometry ) );
4835 dlg.ShowModal();
4836
4838}
4839
4840
4841// ============================================================================
4842// JobFpLibMerge: 3-way merge of .pretty footprint libraries.
4843// ============================================================================
4846
4847
4848int PCBNEW_JOBS_HANDLER::runFpLibMerge( const wxString& aAncestor, const wxString& aOurs,
4849 const wxString& aTheirs, const wxString& aOutput,
4850 bool aSingleFile )
4851{
4852 if( aOutput.IsEmpty() )
4853 {
4854 m_reporter->Report( _( "--output is required\n" ), RPT_SEVERITY_ERROR );
4856 }
4857
4858 struct LIB_SIDE
4859 {
4860 std::vector<std::unique_ptr<FOOTPRINT>> owners;
4862 };
4863
4864 LIB_SIDE ancestor, ours, theirs;
4865
4866 // Accept either a `.pretty` directory (library mode) or a single `.kicad_
4867 // mod` file (git's per-file driver mode). Extension autodetection works
4868 // for native invocations, but git's external driver passes temp paths
4869 // (`.merge_file_XXX`) with no extension, so the `--single-file` flag
4870 // overrides on demand.
4871 auto isSingleFile = [&]( const wxString& aPath )
4872 {
4873 if( aSingleFile )
4874 return true;
4875
4876 return wxFileName( aPath ).GetExt() == FILEEXT::KiCadFootprintFileExtension;
4877 };
4878
4879 auto loadSide = [&]( const wxString& aPath, LIB_SIDE& aSide ) -> int
4880 {
4881 try
4882 {
4883 if( isSingleFile( aPath ) )
4884 {
4886 wxString name;
4887 std::unique_ptr<FOOTPRINT> fp = io.ImportFootprint( aPath, name );
4888
4889 if( !fp )
4891
4892 // Use the footprint's own item-name (LIB_ID) if set,
4893 // falling back to the file basename. Both sides must
4894 // agree for the differ/applier to align them.
4895 const UTF8& itemName = fp->GetFPID().GetLibItemName();
4896 const wxString key = itemName.empty() ? name : itemName.wx_str();
4897
4898 aSide.map[key] = fp.get();
4899 aSide.owners.push_back( std::move( fp ) );
4901 }
4902
4903 auto loaded = KICAD_DIFF::FP_LIB_DIFFER::LoadLibrary( aPath );
4904 aSide.owners = std::move( loaded.first );
4905 aSide.map = std::move( loaded.second );
4907 }
4908 catch( const IO_ERROR& ioe )
4909 {
4910 m_reporter->Report( wxString::Format( _( "Failed to load %s: %s\n" ), aPath, ioe.What() ),
4912 }
4913 catch( const std::exception& e )
4914 {
4915 m_reporter->Report(
4916 wxString::Format( _( "Failed to load %s: %s\n" ), aPath, wxString::FromUTF8( e.what() ) ),
4918 }
4919
4921 };
4922
4923 if( int rc = loadSide( aAncestor, ancestor ); rc != CLI::EXIT_CODES::SUCCESS )
4924 return rc;
4925
4926 if( int rc = loadSide( aOurs, ours ); rc != CLI::EXIT_CODES::SUCCESS )
4927 return rc;
4928
4929 if( int rc = loadSide( aTheirs, theirs ); rc != CLI::EXIT_CODES::SUCCESS )
4930 return rc;
4931
4932 KICAD_DIFF::FP_LIB_DIFFER ourDiff( ancestor.map, ours.map, aOurs );
4933 KICAD_DIFF::FP_LIB_DIFFER theirDiff( ancestor.map, theirs.map, aTheirs );
4934
4935 KICAD_DIFF::DOCUMENT_DIFF ourDocDiff = ourDiff.Diff();
4936 KICAD_DIFF::DOCUMENT_DIFF theirDocDiff = theirDiff.Diff();
4937
4939 KICAD_DIFF::MERGE_PLAN plan = engine.Plan( ourDocDiff, theirDocDiff );
4940
4941 const KICAD_DIFF::MERGE_PLAN planSnapshot = plan;
4942
4943 KICAD_DIFF::FP_LIB_MERGE_APPLIER applier( ancestor.map, ours.map, theirs.map, std::move( plan ) );
4944 std::vector<std::unique_ptr<FOOTPRINT>> merged = applier.Apply();
4945
4946 // Per-property footprint merge isn't implemented; MERGE_PROPS resolutions
4947 // are downgraded to TAKE_OURS. Surface that as unresolved so the user sees
4948 // a marker instead of silent partial-merge.
4949 const bool hadSilentFallback = applier.GetReport().mergePropsFallback > 0;
4950
4951 const bool singleFileOutput = isSingleFile( aOutput );
4952
4953 // .pretty is a directory; .kicad_mod is a single file. wxFileName parses
4954 // a path ending in `.pretty` as a file with that extension, so library
4955 // mode uses DirName(); single-file mode keeps the file path as-is and
4956 // hands it directly to FootprintSave, which auto-detects .kicad_mod via
4957 // its own extension check.
4958 wxFileName outFn;
4959
4960 if( singleFileOutput )
4961 outFn = wxFileName( aOutput );
4962 else
4963 outFn = wxFileName::DirName( aOutput );
4964
4965 outFn.MakeAbsolute();
4966
4967 // In library mode wxFileName::DirName treats `foo.pretty` as a directory,
4968 // so GetPath() returns the .pretty itself. In single-file mode GetPath()
4969 // returns the file's parent dir. Either way it's the directory we Mkdir
4970 // into.
4971 const wxString outDir = outFn.GetPath();
4972
4973 if( !wxFileName::DirExists( outDir ) && !wxFileName::Mkdir( outDir, wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL ) )
4974 {
4975 m_reporter->Report( wxString::Format( _( "Cannot create output directory %s\n" ), outDir ),
4978 }
4979
4980 try
4981 {
4983
4984 if( singleFileOutput )
4985 {
4986 // Git per-file driver mode: one merged footprint -> one .kicad_mod.
4987 // Multiple survivors would lose data; flag that as an error since
4988 // single-file input by definition has at most one footprint per
4989 // side.
4990 if( merged.size() > 1 )
4991 {
4992 m_reporter->Report( _( "Single-file fp merge produced multiple footprints; refusing to "
4993 "collapse into one .kicad_mod\n" ),
4996 }
4997
4998 if( merged.empty() )
4999 {
5000 // All sides deleted the footprint. Remove the output file if
5001 // it existed, leaving nothing where the merged content would
5002 // have gone.
5003 if( wxFileName::FileExists( outFn.GetFullPath() ) )
5004 wxRemoveFile( outFn.GetFullPath() );
5005 }
5006 else if( wxFileName( outFn.GetFullPath() ).GetExt() == FILEEXT::KiCadFootprintFileExtension )
5007 {
5008 // FootprintSave's .kicad_mod extension autodetection handles
5009 // the write to the path as-given.
5010 io.FootprintSave( outFn.GetFullPath(), merged.front().get(), nullptr );
5011 }
5012 else
5013 {
5014 // Git driver mode: output is an extension-less temp path
5015 // (typically `.merge_file_XXX`). FootprintSave's
5016 // autodetection would treat it as a library directory.
5017 // Format directly via PRETTIFIED_FILE_OUTPUTFORMATTER, the
5018 // same writer the sexpr lib cache uses.
5019 PRETTIFIED_FILE_OUTPUTFORMATTER formatter( outFn.GetFullPath() );
5020 io.SetOutputFormatter( &formatter );
5021 io.Format( merged.front().get() );
5022 formatter.Finish();
5023 }
5024 }
5025 else
5026 {
5027 // Library mode. Footprints in `merged` are the survivors. Any
5028 // footprint already in the output `.pretty` but absent from
5029 // `merged` is a stale leftover from a previous invocation (or a
5030 // resolved DELETE / TAKE_ANCESTOR-with-no-ancestor case). Delete
5031 // those before saving the survivors, otherwise the resolved
5032 // DELETE never propagates to disk.
5033 std::set<wxString> mergedNames;
5034
5035 for( const auto& fp : merged )
5036 {
5037 if( fp )
5038 mergedNames.insert( fp->GetFPID().GetLibItemName() );
5039 }
5040
5041 wxArrayString existing;
5042 io.FootprintEnumerate( existing, outDir, false, nullptr );
5043
5044 for( const wxString& name : existing )
5045 {
5046 if( !mergedNames.count( name ) )
5047 io.FootprintDelete( outDir, name, nullptr );
5048 }
5049
5050 for( const auto& fp : merged )
5051 {
5052 if( !fp )
5053 continue;
5054
5055 const wxString name = fp->GetFPID().GetLibItemName();
5056
5057 if( io.FootprintExists( outDir, name, nullptr ) )
5058 io.FootprintDelete( outDir, name, nullptr );
5059
5060 io.FootprintSave( outDir, fp.get(), nullptr );
5061 }
5062 }
5063 }
5064 catch( const IO_ERROR& ioe )
5065 {
5066 m_reporter->Report( wxString::Format( _( "Failed to save merged footprint library: %s\n" ), ioe.What() ),
5069 }
5070
5071 // The merged library was saved above, so the output is always valid.
5072 if( !planSnapshot.Resolved() || hadSilentFallback )
5073 {
5074 // Conflict count = engine-unresolved ∪ applier-downgraded (deduped, so
5075 // an item that was both unresolved and silently downgraded counts once).
5076 std::set<KIID_PATH> conflicts( planSnapshot.unresolved.begin(), planSnapshot.unresolved.end() );
5077
5078 for( const KIID_PATH& id : applier.GetReport().mergePropsFallbackIds )
5079 conflicts.insert( id );
5080
5081 m_reporter->Report( wxString::Format( _( "Footprint library merge completed with %zu unresolved "
5082 "conflict(s) in %s\n" ),
5083 conflicts.size(), aOutput ),
5086 }
5087
5089}
@ 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.
COMPATIBILITY_REPORT ClassifyBoardForDowngrade(const BOARD *aBoard, const DOWNGRADE_TARGET &aTarget, bool aDropInsteadOfApproximate)
Report what a board would lose if exported to an older KiCad.
void FlattenBoardVariant(BOARD *aBoard, const wxString &aVariantName)
Bake a variant's overrides into the base footprint attributes.
bool ExportBoardToOlderVersion(const wxString &aSrcFile, const wxString &aDestFile, const DOWNGRADE_TARGET &aTarget, COMPATIBILITY_REPORT &aReport, wxString *aUnsupportedToken, const wxString &aVariant, bool aDropInsteadOfApproximate)
Load a board, downgrade the copy, and write it stamped for the target.
DOWNGRADE_FILE_RESULT DowngradeFootprintFileToTemp(const wxString &aSrcFile, const wxString &aTmpFile, const DOWNGRADE_TARGET &aTarget, bool aDropInsteadOfApproximate)
Load one .kicad_mod, classify it against the rule table, and write the downgraded copy to aTmpFile.
constexpr int FIRST_PCB_VARIANTS
First board format that stores variants.
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:3167
void Add(BOARD_ITEM *aItem, ADD_MODE aMode=ADD_MODE::INSERT, bool aSkipConnectivity=false) override
Removes an item from the container.
Definition board.cpp:1515
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:567
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:593
wxString GetVariantDescription(const wxString &aVariantName) const
Definition board.cpp:3321
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:934
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:435
BOARD_DESIGN_SETTINGS & GetDesignSettings() const
Definition board.cpp:1292
const LSET & GetEnabledLayers() const
A proxy function that calls the corresponding function in m_BoardSettings.
Definition board.cpp:1176
void SynchronizeProperties()
Copy the current project's text variables into the boards property cache.
Definition board.cpp:3147
BOX2I ComputeBoundingBox(bool aBoardEdgesOnly=false, bool aPhysicalLayersOnly=false) const
Calculate the bounding box containing all board items (or board edge segments).
Definition board.cpp:2736
void DeleteMARKERs()
Delete all MARKERS from the board.
Definition board.cpp:2061
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
What an export to a given target will keep, approximate, drop, or block.
void Print(REPORTER *aReporter) const
void Merge(const COMPATIBILITY_REPORT &aOther)
bool IsLossy() const
True if anything is approximated or dropped. This is what triggers the lossy warning.
bool IsBlocked() const
True if some feature makes the target impossible. The user cannot export anyway.
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
Stage related design and library files together, retaining originals until every write is ready.
const wxString & GetRecoveryError() const
Recovery details are nonempty only if restoring an original or removing a new output failed.
wxString Commit()
Replace all destinations, rolling back earlier replacements if a rename fails.
wxString Stage(const wxString &aDestination)
Return an exclusively-created sibling staging file, or empty if it cannot be created.
bool Create(const wxString &aDirectory, bool aOwnContents=false)
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.
wxString m_outputFile
Where to write the downgraded copy.
bool m_dropInsteadOfApproximate
Omit features that would otherwise be approximated.
wxString m_filename
Input board file (may be empty to downgrade only libraries)
wxString m_libraryDir
If set, downgrade every .kicad_mod footprint under this folder too.
COMPATIBILITY_REPORT m_report
Filled by the handler so the caller can show it.
wxString m_variant
Flatten this variant into the export. Empty means the base design.
bool m_inPlace
Internal project export operating only on its prepared copy.
wxString m_target
Target release name, e.g. "KiCad 10.0". Empty means the newest.
bool m_dryRun
Only classify and fill m_report, do not write anything.
bool m_force
Export even when features must be dropped or approximated.
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.
std::unique_ptr< BOARD > LoadBoard(const wxString &aFileName, const std::map< std::string, UTF8 > *aProperties=nullptr, PROJECT *aProject=nullptr)
Load information from some input file format that this PCB_IO implementation knows about into new BOA...
Definition pcb_io.cpp:72
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:138
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)
wxString DowngradeLibraryCopyPath(const wxString &aLibrary, const wxString &aOutputDirectory)
bool ValidateDowngradeLibrarySource(const wxString &aSource, wxString &aError)
bool ValidateDowngradeLibraryCopy(const wxString &aSource, const wxString &aDestination, wxString &aError)
bool ValidateFreshDowngradeOutput(const wxString &aOutput, wxString &aError)
bool CopyDowngradeLibraryDirectory(const wxString &aSource, const wxString &aDestination, DOWNGRADE_OUTPUT_DIRECTORIES &aDirectories, wxString &aError)
wxString DowngradeLibraryFilesInPlace(const wxString &aDir, const wxString &aGlob, int aStampVersion, const std::function< wxString(const wxString &)> &aFindForbidden, const std::function< DOWNGRADE_FILE_RESULT(const wxString &, const wxString &)> &aConvert, DOWNGRADE_FILE_TRANSACTION *aTransaction=nullptr, wxString *aRecoveryError=nullptr)
Downgrade every library file matching aGlob under aDir in place.
bool IsDowngradeWorkingArtifact(const wxString &aPath)
Editor working state (autosaves, local history, backups) holds current-format copies nothing downgrad...
DOWNGRADE_FILE_RESULT
What a per-file converter did with one library file.
const std::vector< DOWNGRADE_TARGET > & GetDowngradeTargets()
Supported downgrade targets, newest first.
const DOWNGRADE_TARGET * FindDowngradeTarget(const wxString &aNameOrId)
Match a user-supplied target string against name or stable id, case-insensitively.
wxString DowngradeTargetNames()
The valid target choices as one user-facing list, for error messages.
#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:63
#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:69
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 wxString footprintRefusedMessage(const wxString &aFile, const DOWNGRADE_TARGET &aTarget)
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)
static wxString downgradeFootprintLibraries(const wxString &aDir, const DOWNGRADE_TARGET &aTarget, bool aDropInsteadOfApproximate, DOWNGRADE_FILE_TRANSACTION *aTransaction=nullptr, wxString *aRecoveryError=nullptr)
static void classifyFootprintLibraries(const wxString &aDir, const DOWNGRADE_TARGET &aTarget, COMPATIBILITY_REPORT &aReport, bool aDropInsteadOfApproximate)
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:65
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.
An older KiCad release we can export a project back to.
int m_boardVersion
SEXPR_BOARD_FILE_VERSION for the target.
wxString m_name
User-facing name, e.g. "KiCad 10.0".
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