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