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>
23#include <richio.h>
24#include <string_utils.h>
25#include <wx/crt.h>
26#include <wx/dir.h>
27#include <wx/zipstrm.h>
28#include <wx/filename.h>
29#include <wx/tokenzr.h>
30#include <wx/wfstream.h>
31
32#include <nlohmann/json.hpp>
33
34#include "pcbnew_jobs_handler.h"
35#include <board_loader.h>
36#include <jobs/scratch_doc.h>
37#include <board_commit.h>
44#include <trace_helpers.h>
46#include <pcb_drill_chart.h>
47#include <drc/drc_engine.h>
49#include <drc/drc_item.h>
50#include <drc/drc_report.h>
53#include <footprint.h>
55#include <jobs/job_export_bom.h>
57#include <jobs/job_fp_upgrade.h>
77#include <jobs/job_pcb_render.h>
78#include <jobs/job_pcb_drc.h>
79#include <jobs/job_pcb_import.h>
84#include <downgrade_scan.h>
85#include <downgrade_copy.h>
86#include <eda_units.h>
88#include <lset.h>
89#include <cli/exit_codes.h>
95#include <tool/tool_manager.h>
96#include <tools/drc_tool.h>
97#include <filename_resolver.h>
102#include <kiface_base.h>
103#include <macros.h>
104#include <pad.h>
105#include <pcb_marker.h>
106#include <project/project_file.h>
107#include <exporters/export_idf.h>
110#include <kiface_ids.h>
113#include <pcbnew_settings.h>
114#include <pcbplot.h>
115#include <pcb_plotter.h>
116#include <pcb_edit_frame.h>
117#include <pcb_track.h>
118#include <pgm_base.h>
121#include <project_pcb.h>
124#include <reporter.h>
125#include <scoped_set_reset.h>
126#include <progress_reporter.h>
128#include <export_vrml.h>
129#include <kiplatform/io.h>
140#include <dialogs/dialog_plot.h>
148#include <api/api_pcb_utils.h>
149#include <api/board/board.pb.h>
150#include <google/protobuf/util/json_util.h>
151#include <fstream>
152#include <paths.h>
153#include <streamwrapper.h>
154
157
158#include <locale_io.h>
159#include <confirm.h>
160
161
162#ifdef _WIN32
163#ifdef TRANSPARENT
164#undef TRANSPARENT
165#endif
166#endif
167
168
170 JOB_DISPATCHER( aKiway ),
171 m_cliBoard( nullptr ),
172 m_toolManager( nullptr )
173{
174 Register( "bom", std::bind( &PCBNEW_JOBS_HANDLER::JobExportBom, this, std::placeholders::_1 ),
175 [aKiway]( JOB* job, wxWindow* aParent ) -> bool
176 {
177 JOB_EXPORT_BOM* bomJob = dynamic_cast<JOB_EXPORT_BOM*>( job );
178
179 PCB_EDIT_FRAME* editFrame = static_cast<PCB_EDIT_FRAME*>( aKiway->Player( FRAME_PCB_EDITOR, false ) );
180
181 wxCHECK( bomJob && editFrame, false );
182
183 DIALOG_FOOTPRINT_FIELDS_TABLE dlg( editFrame, bomJob );
184
185 if( dlg.WasAborted() )
186 return false;
187
188 return dlg.ShowModal() == wxID_OK;
189 } );
190 Register( "3d", std::bind( &PCBNEW_JOBS_HANDLER::JobExportStep, this, std::placeholders::_1 ),
191 [aKiway]( JOB* job, wxWindow* aParent ) -> bool
192 {
193 JOB_EXPORT_PCB_3D* svgJob = dynamic_cast<JOB_EXPORT_PCB_3D*>( job );
194
195 PCB_EDIT_FRAME* editFrame =
196 dynamic_cast<PCB_EDIT_FRAME*>( aKiway->Player( FRAME_PCB_EDITOR, false ) );
197
198 wxCHECK( svgJob && editFrame, false );
199
200 DIALOG_EXPORT_STEP dlg( editFrame, aParent, "", svgJob );
201 return dlg.ShowModal() == wxID_OK;
202 } );
203 Register( "render", std::bind( &PCBNEW_JOBS_HANDLER::JobExportRender, this, std::placeholders::_1 ),
204 []( JOB* job, wxWindow* aParent ) -> bool
205 {
206 JOB_PCB_RENDER* renderJob = dynamic_cast<JOB_PCB_RENDER*>( job );
207
208 wxCHECK( renderJob, false );
209
210 DIALOG_RENDER_JOB dlg( aParent, renderJob );
211 return dlg.ShowModal() == wxID_OK;
212 } );
213 Register( "upgrade", std::bind( &PCBNEW_JOBS_HANDLER::JobUpgrade, this, std::placeholders::_1 ),
214 []( JOB* job, wxWindow* aParent ) -> bool
215 {
216 return true;
217 } );
218 Register( "downgrade", std::bind( &PCBNEW_JOBS_HANDLER::JobDowngrade, this, std::placeholders::_1 ),
219 []( JOB* job, wxWindow* aParent ) -> bool
220 {
221 return true;
222 } );
223 Register( "pcb_import", std::bind( &PCBNEW_JOBS_HANDLER::JobImport, this, std::placeholders::_1 ),
224 []( JOB* job, wxWindow* aParent ) -> bool
225 {
226 return true;
227 } );
228 Register( "svg", std::bind( &PCBNEW_JOBS_HANDLER::JobExportSvg, this, std::placeholders::_1 ),
229 [aKiway]( JOB* job, wxWindow* aParent ) -> bool
230 {
231 JOB_EXPORT_PCB_SVG* svgJob = dynamic_cast<JOB_EXPORT_PCB_SVG*>( job );
232
233 PCB_EDIT_FRAME* editFrame =
234 dynamic_cast<PCB_EDIT_FRAME*>( aKiway->Player( FRAME_PCB_EDITOR, false ) );
235
236 wxCHECK( svgJob && editFrame, false );
237
238 DIALOG_PLOT dlg( editFrame, aParent, svgJob );
239 return dlg.ShowModal() == wxID_OK;
240 } );
241 Register( "gencad", std::bind( &PCBNEW_JOBS_HANDLER::JobExportGencad, this, std::placeholders::_1 ),
242 [aKiway]( JOB* job, wxWindow* aParent ) -> bool
243 {
244 JOB_EXPORT_PCB_GENCAD* gencadJob = dynamic_cast<JOB_EXPORT_PCB_GENCAD*>( job );
245
246 PCB_EDIT_FRAME* editFrame =
247 dynamic_cast<PCB_EDIT_FRAME*>( aKiway->Player( FRAME_PCB_EDITOR, false ) );
248
249 wxCHECK( gencadJob && editFrame, false );
250
251 DIALOG_GENCAD_EXPORT_OPTIONS dlg( editFrame, gencadJob->GetSettingsDialogTitle(), gencadJob );
252 return dlg.ShowModal() == wxID_OK;
253 } );
254 Register( "specctra_dsn",
255 std::bind( &PCBNEW_JOBS_HANDLER::JobExportSpecctraDsn, this, std::placeholders::_1 ),
256 []( JOB* job, wxWindow* aParent ) -> bool
257 {
258 return true;
259 } );
260 Register( "dxf", std::bind( &PCBNEW_JOBS_HANDLER::JobExportDxf, this, std::placeholders::_1 ),
261 [aKiway]( JOB* job, wxWindow* aParent ) -> bool
262 {
263 JOB_EXPORT_PCB_DXF* dxfJob = dynamic_cast<JOB_EXPORT_PCB_DXF*>( job );
264
265 PCB_EDIT_FRAME* editFrame =
266 dynamic_cast<PCB_EDIT_FRAME*>( aKiway->Player( FRAME_PCB_EDITOR, false ) );
267
268 wxCHECK( dxfJob && editFrame, false );
269
270 DIALOG_PLOT dlg( editFrame, aParent, dxfJob );
271 return dlg.ShowModal() == wxID_OK;
272 } );
273 Register( "pdf", std::bind( &PCBNEW_JOBS_HANDLER::JobExportPdf, this, std::placeholders::_1 ),
274 [aKiway]( JOB* job, wxWindow* aParent ) -> bool
275 {
276 JOB_EXPORT_PCB_PDF* pdfJob = dynamic_cast<JOB_EXPORT_PCB_PDF*>( job );
277
278 PCB_EDIT_FRAME* editFrame =
279 dynamic_cast<PCB_EDIT_FRAME*>( aKiway->Player( FRAME_PCB_EDITOR, false ) );
280
281 wxCHECK( pdfJob && editFrame, false );
282
283 DIALOG_PLOT dlg( editFrame, aParent, pdfJob );
284 return dlg.ShowModal() == wxID_OK;
285 } );
286 Register( "png", std::bind( &PCBNEW_JOBS_HANDLER::JobExportPng, this, std::placeholders::_1 ),
287 [aKiway]( JOB* job, wxWindow* aParent ) -> bool
288 {
289 JOB_EXPORT_PCB_PNG* pngJob = dynamic_cast<JOB_EXPORT_PCB_PNG*>( job );
290
291 PCB_EDIT_FRAME* editFrame =
292 dynamic_cast<PCB_EDIT_FRAME*>( aKiway->Player( FRAME_PCB_EDITOR, false ) );
293
294 wxCHECK( pngJob && editFrame, false );
295
296 DIALOG_PLOT dlg( editFrame, aParent, pngJob );
297 return dlg.ShowModal() == wxID_OK;
298 } );
299 Register( "ps", std::bind( &PCBNEW_JOBS_HANDLER::JobExportPs, this, std::placeholders::_1 ),
300 [aKiway]( JOB* job, wxWindow* aParent ) -> bool
301 {
302 JOB_EXPORT_PCB_PS* psJob = dynamic_cast<JOB_EXPORT_PCB_PS*>( job );
303
304 PCB_EDIT_FRAME* editFrame =
305 dynamic_cast<PCB_EDIT_FRAME*>( aKiway->Player( FRAME_PCB_EDITOR, false ) );
306
307 wxCHECK( psJob && editFrame, false );
308
309 DIALOG_PLOT dlg( editFrame, aParent, psJob );
310 return dlg.ShowModal() == wxID_OK;
311 } );
312 Register( "stats", std::bind( &PCBNEW_JOBS_HANDLER::JobExportStats, this, std::placeholders::_1 ),
313 [aKiway]( JOB* job, wxWindow* aParent ) -> bool
314 {
315 JOB_EXPORT_PCB_STATS* statsJob = dynamic_cast<JOB_EXPORT_PCB_STATS*>( job );
316
317 PCB_EDIT_FRAME* editFrame =
318 dynamic_cast<PCB_EDIT_FRAME*>( aKiway->Player( FRAME_PCB_EDITOR, false ) );
319
320 wxCHECK( statsJob && editFrame, false );
321
322 if( statsJob->m_filename.IsEmpty() && editFrame->GetBoard() )
323 {
324 wxFileName boardName = editFrame->GetBoard()->GetFileName();
325 statsJob->m_filename = boardName.GetFullPath();
326 }
327
328 wxWindow* parent = aParent ? aParent : static_cast<wxWindow*>( editFrame );
329
330 DIALOG_BOARD_STATS_JOB dlg( parent, statsJob );
331
332 return dlg.ShowModal() == wxID_OK;
333 } );
334 Register( "stackup", std::bind( &PCBNEW_JOBS_HANDLER::JobExportStackup, this, std::placeholders::_1 ),
335 [aKiway]( JOB* job, wxWindow* aParent ) -> bool
336 {
337 JOB_EXPORT_PCB_STACKUP* stackupJob = dynamic_cast<JOB_EXPORT_PCB_STACKUP*>( job );
338
339 PCB_EDIT_FRAME* editFrame =
340 dynamic_cast<PCB_EDIT_FRAME*>( aKiway->Player( FRAME_PCB_EDITOR, false ) );
341
342 wxCHECK( stackupJob && editFrame, false );
343
344 if( stackupJob->m_filename.IsEmpty() && editFrame->GetBoard() )
345 {
346 wxFileName boardName = editFrame->GetBoard()->GetFileName();
347 stackupJob->m_filename = boardName.GetFullPath();
348 }
349
350 wxWindow* parent = aParent ? aParent : static_cast<wxWindow*>( editFrame );
351
352 DIALOG_BOARD_STACKUP_JOB dlg( parent, stackupJob );
353
354 return dlg.ShowModal() == wxID_OK;
355 } );
356 Register( "gerber", std::bind( &PCBNEW_JOBS_HANDLER::JobExportGerber, this, std::placeholders::_1 ),
357 [aKiway]( JOB* job, wxWindow* aParent ) -> bool
358 {
359 JOB_EXPORT_PCB_GERBER* gJob = dynamic_cast<JOB_EXPORT_PCB_GERBER*>( job );
360
361 PCB_EDIT_FRAME* editFrame =
362 dynamic_cast<PCB_EDIT_FRAME*>( aKiway->Player( FRAME_PCB_EDITOR, false ) );
363
364 wxCHECK( gJob && editFrame, false );
365
366 DIALOG_PLOT dlg( editFrame, aParent, gJob );
367 return dlg.ShowModal() == wxID_OK;
368 } );
369 Register( "gerbers", std::bind( &PCBNEW_JOBS_HANDLER::JobExportGerbers, this, std::placeholders::_1 ),
370 [aKiway]( JOB* job, wxWindow* aParent ) -> bool
371 {
372 JOB_EXPORT_PCB_GERBERS* gJob = dynamic_cast<JOB_EXPORT_PCB_GERBERS*>( job );
373
374 PCB_EDIT_FRAME* editFrame =
375 dynamic_cast<PCB_EDIT_FRAME*>( aKiway->Player( FRAME_PCB_EDITOR, false ) );
376
377 wxCHECK( gJob && editFrame, false );
378
379 DIALOG_PLOT dlg( editFrame, aParent, gJob );
380 return dlg.ShowModal() == wxID_OK;
381 } );
382 Register(
383 "hpgl",
384 [&]( JOB* aJob )
385 {
386 m_reporter->Report( _( "Plotting to HPGL is no longer supported as of KiCad 10.0.\n" ),
389 },
390 [aKiway]( JOB* job, wxWindow* aParent ) -> bool
391 {
392 PCB_EDIT_FRAME* editFrame = dynamic_cast<PCB_EDIT_FRAME*>( aKiway->Player( FRAME_PCB_EDITOR, false ) );
393
394 wxCHECK( editFrame, false );
395
396 DisplayErrorMessage( editFrame, _( "Plotting to HPGL is no longer supported as of KiCad 10.0." ) );
397 return false;
398 } );
399 Register( "drill", std::bind( &PCBNEW_JOBS_HANDLER::JobExportDrill, this, std::placeholders::_1 ),
400 [aKiway]( JOB* job, wxWindow* aParent ) -> bool
401 {
402 JOB_EXPORT_PCB_DRILL* drillJob = dynamic_cast<JOB_EXPORT_PCB_DRILL*>( job );
403
404 PCB_EDIT_FRAME* editFrame =
405 dynamic_cast<PCB_EDIT_FRAME*>( aKiway->Player( FRAME_PCB_EDITOR, false ) );
406
407 wxCHECK( drillJob && editFrame, false );
408
409 DIALOG_GENDRILL dlg( editFrame, drillJob, aParent );
410 return dlg.ShowModal() == wxID_OK;
411 } );
412 Register( "pos", std::bind( &PCBNEW_JOBS_HANDLER::JobExportPos, this, std::placeholders::_1 ),
413 [aKiway]( JOB* job, wxWindow* aParent ) -> bool
414 {
415 JOB_EXPORT_PCB_POS* posJob = dynamic_cast<JOB_EXPORT_PCB_POS*>( job );
416
417 PCB_EDIT_FRAME* editFrame =
418 dynamic_cast<PCB_EDIT_FRAME*>( aKiway->Player( FRAME_PCB_EDITOR, false ) );
419
420 wxCHECK( posJob && editFrame, false );
421
422 DIALOG_GEN_FOOTPRINT_POSITION dlg( posJob, editFrame, aParent );
423 return dlg.ShowModal() == wxID_OK;
424 } );
425 Register( "fpupgrade", std::bind( &PCBNEW_JOBS_HANDLER::JobExportFpUpgrade, this, std::placeholders::_1 ),
426 []( JOB* job, wxWindow* aParent ) -> bool
427 {
428 return true;
429 } );
430 Register( "fpsvg", std::bind( &PCBNEW_JOBS_HANDLER::JobExportFpSvg, this, std::placeholders::_1 ),
431 []( JOB* job, wxWindow* aParent ) -> bool
432 {
433 return true;
434 } );
435 Register( "pcb_diff", std::bind( &PCBNEW_JOBS_HANDLER::JobDiff, this, std::placeholders::_1 ),
436 []( JOB* job, wxWindow* aParent ) -> bool
437 {
438 return true;
439 } );
440 Register( "fp_diff", std::bind( &PCBNEW_JOBS_HANDLER::JobFpDiff, this, std::placeholders::_1 ),
441 []( JOB* job, wxWindow* aParent ) -> bool
442 {
443 return true;
444 } );
445 Register( "drc", std::bind( &PCBNEW_JOBS_HANDLER::JobExportDrc, this, std::placeholders::_1 ),
446 []( JOB* job, wxWindow* aParent ) -> bool
447 {
448 JOB_PCB_DRC* drcJob = dynamic_cast<JOB_PCB_DRC*>( job );
449
450 wxCHECK( drcJob, false );
451
452 DIALOG_DRC_JOB_CONFIG dlg( aParent, drcJob );
453 return dlg.ShowModal() == wxID_OK;
454 } );
455 Register( "ipc2581", std::bind( &PCBNEW_JOBS_HANDLER::JobExportIpc2581, this, std::placeholders::_1 ),
456 [aKiway]( JOB* job, wxWindow* aParent ) -> bool
457 {
458 JOB_EXPORT_PCB_IPC2581* ipcJob = dynamic_cast<JOB_EXPORT_PCB_IPC2581*>( job );
459
460 PCB_EDIT_FRAME* editFrame =
461 dynamic_cast<PCB_EDIT_FRAME*>( aKiway->Player( FRAME_PCB_EDITOR, false ) );
462
463 wxCHECK( ipcJob && editFrame, false );
464
465 DIALOG_EXPORT_2581 dlg( ipcJob, editFrame, aParent );
466 return dlg.ShowModal() == wxID_OK;
467 } );
468 Register( "ipcd356", std::bind( &PCBNEW_JOBS_HANDLER::JobExportIpcD356, this, std::placeholders::_1 ),
469 []( JOB* job, wxWindow* aParent ) -> bool
470 {
471 return true;
472 } );
473 Register( "odb", std::bind( &PCBNEW_JOBS_HANDLER::JobExportOdb, this, std::placeholders::_1 ),
474 [aKiway]( JOB* job, wxWindow* aParent ) -> bool
475 {
476 JOB_EXPORT_PCB_ODB* odbJob = dynamic_cast<JOB_EXPORT_PCB_ODB*>( job );
477
478 PCB_EDIT_FRAME* editFrame =
479 dynamic_cast<PCB_EDIT_FRAME*>( aKiway->Player( FRAME_PCB_EDITOR, false ) );
480
481 wxCHECK( odbJob && editFrame, false );
482
483 DIALOG_EXPORT_ODBPP dlg( odbJob, editFrame, aParent );
484 return dlg.ShowModal() == wxID_OK;
485 } );
486 Register( "idf", std::bind( &PCBNEW_JOBS_HANDLER::JobExportIdf, this, std::placeholders::_1 ),
487 [aKiway]( JOB* job, wxWindow* aParent ) -> bool
488 {
489 JOB_EXPORT_PCB_IDF* idfJob = dynamic_cast<JOB_EXPORT_PCB_IDF*>( job );
490
491 PCB_EDIT_FRAME* editFrame =
492 dynamic_cast<PCB_EDIT_FRAME*>( aKiway->Player( FRAME_PCB_EDITOR, false ) );
493
494 wxCHECK( idfJob && editFrame, false );
495
496 DIALOG_EXPORT_IDF3 dlg( idfJob, editFrame, aParent );
497 return dlg.ShowModal() == wxID_OK;
498 } );
499}
500
501
505
506
508{
509 m_cliBoard.reset();
510 m_toolManager.reset();
511}
512
513
515{
516 TOOL_MANAGER* toolManager = nullptr;
517 if( Pgm().IsGUI() )
518 {
519 // we assume the PCB we are working on here is the one in the frame
520 // so use the frame's tool manager
521 PCB_EDIT_FRAME* editFrame = (PCB_EDIT_FRAME*) m_kiway->Player( FRAME_PCB_EDITOR, false );
522 if( editFrame )
523 toolManager = editFrame->GetToolManager();
524 }
525 else
526 {
527 if( m_toolManager == nullptr )
528 {
529 m_toolManager = std::make_unique<TOOL_MANAGER>();
530 }
531
532 toolManager = m_toolManager.get();
533
534 toolManager->SetEnvironment( aBrd, nullptr, nullptr, Kiface().KifaceSettings(), nullptr );
535 }
536 return toolManager;
537}
538
539
540BOARD* PCBNEW_JOBS_HANDLER::getBoard( const wxString& aPath )
541{
542 BOARD* brd = nullptr;
543 SETTINGS_MANAGER& settingsManager = Pgm().GetSettingsManager();
544 wxString loadError;
545
546 auto getProjectForBoard = [&]( const wxString& aBoardPath ) -> PROJECT*
547 {
548 wxFileName pro = aBoardPath;
549 pro.SetExt( FILEEXT::ProjectFileExtension );
550 pro.MakeAbsolute();
551
552 PROJECT* project = settingsManager.GetProject( pro.GetFullPath() );
553
554 if( !project )
555 {
556 settingsManager.LoadProject( pro.GetFullPath(), true );
557 project = settingsManager.GetProject( pro.GetFullPath() );
558 }
559
560 return project;
561 };
562
563 auto loadBoardFromPath = [&]( const wxString& aBoardPath ) -> BOARD*
564 {
565 PROJECT* project = getProjectForBoard( aBoardPath );
566
568
569 if( !project || pluginType == PCB_IO_MGR::FILE_TYPE_NONE )
570 return nullptr;
571
572 try
573 {
574 std::unique_ptr<BOARD> loadedBoard = BOARD_LOADER::Load( aBoardPath, pluginType, project );
575 return loadedBoard.release();
576 }
577 catch( const IO_ERROR& ioe )
578 {
579 loadError = ioe.What();
580 return nullptr;
581 }
582 catch( ... )
583 {
584 return nullptr;
585 }
586 };
587
588 if( !Pgm().IsGUI() && Pgm().GetSettingsManager().IsProjectOpen() )
589 {
590 wxString pcbPath = aPath;
591
592 if( pcbPath.IsEmpty() )
593 {
594 wxFileName path = Pgm().GetSettingsManager().Prj().GetProjectFullName();
596 path.MakeAbsolute();
597 pcbPath = path.GetFullPath();
598 }
599
600 if( !m_cliBoard )
601 m_cliBoard.reset( loadBoardFromPath( pcbPath ) );
602
603 brd = m_cliBoard.get();
604 }
605 else if( Pgm().IsGUI() && Pgm().GetSettingsManager().IsProjectOpen() )
606 {
607 PCB_EDIT_FRAME* editFrame = (PCB_EDIT_FRAME*) m_kiway->Player( FRAME_PCB_EDITOR, false );
608
609 if( editFrame )
610 brd = editFrame->GetBoard();
611 }
612 else
613 {
614 m_cliBoard.reset( loadBoardFromPath( aPath ) );
615 brd = m_cliBoard.get();
616 }
617
618 if( !brd )
619 {
620 wxString msg = _( "Failed to load board" );
621
622 if( !loadError.IsEmpty() )
623 msg += wxString::Format( wxS( ": %s" ), loadError );
624
625 m_reporter->Report( msg + '\n', RPT_SEVERITY_ERROR );
626 }
627
628 return brd;
629}
630
631
632LSEQ PCBNEW_JOBS_HANDLER::convertLayerArg( wxString& aLayerString, BOARD* aBoard ) const
633{
634 std::map<wxString, LSET> layerUserMasks;
635 std::map<wxString, LSET> layerMasks;
636 std::map<wxString, LSET> layerGuiMasks;
637
638 // Build list of layer names and their layer mask:
639 for( PCB_LAYER_ID layer : LSET::AllLayersMask() )
640 {
641 // Add user layer name
642 if( aBoard )
643 layerUserMasks[aBoard->GetLayerName( layer )] = LSET( { layer } );
644
645 // Add layer name used in pcb files
646 layerMasks[LSET::Name( layer )] = LSET( { layer } );
647 // Add layer name using GUI canonical layer name
648 layerGuiMasks[LayerName( layer )] = LSET( { layer } );
649 }
650
651 // Add list of grouped layer names used in pcb files
652 layerMasks[wxT( "*" )] = LSET::AllLayersMask();
653 layerMasks[wxT( "*.Cu" )] = LSET::AllCuMask();
654 layerMasks[wxT( "*In.Cu" )] = LSET::InternalCuMask();
655 layerMasks[wxT( "F&B.Cu" )] = LSET( { F_Cu, B_Cu } );
656 layerMasks[wxT( "*.Adhes" )] = LSET( { B_Adhes, F_Adhes } );
657 layerMasks[wxT( "*.Paste" )] = LSET( { B_Paste, F_Paste } );
658 layerMasks[wxT( "*.Mask" )] = LSET( { B_Mask, F_Mask } );
659 layerMasks[wxT( "*.SilkS" )] = LSET( { B_SilkS, F_SilkS } );
660 layerMasks[wxT( "*.Fab" )] = LSET( { B_Fab, F_Fab } );
661 layerMasks[wxT( "*.CrtYd" )] = LSET( { B_CrtYd, F_CrtYd } );
662
663 // Add list of grouped layer names using GUI canonical layer names
664 layerGuiMasks[wxT( "*.Adhesive" )] = LSET( { B_Adhes, F_Adhes } );
665 layerGuiMasks[wxT( "*.Silkscreen" )] = LSET( { B_SilkS, F_SilkS } );
666 layerGuiMasks[wxT( "*.Courtyard" )] = LSET( { B_CrtYd, F_CrtYd } );
667
668 LSEQ layerMask;
669
670 auto pushLayers = [&]( const LSET& layerSet )
671 {
672 for( PCB_LAYER_ID layer : layerSet.Seq() )
673 layerMask.push_back( layer );
674 };
675
676 if( !aLayerString.IsEmpty() )
677 {
678 wxStringTokenizer layerTokens( aLayerString, "," );
679
680 while( layerTokens.HasMoreTokens() )
681 {
682 std::string token = TO_UTF8( layerTokens.GetNextToken().Trim( true ).Trim( false ) );
683
684 if( layerUserMasks.contains( token ) )
685 pushLayers( layerUserMasks.at( token ) );
686 else if( layerMasks.count( token ) )
687 pushLayers( layerMasks.at( token ) );
688 else if( layerGuiMasks.count( token ) )
689 pushLayers( layerGuiMasks.at( token ) );
690 else
691 m_reporter->Report( wxString::Format( _( "Invalid layer name '%s'\n" ), token ) );
692 }
693 }
694
695 return layerMask;
696}
697
698
700{
701 JOB_EXPORT_BOM* aBomJob = dynamic_cast<JOB_EXPORT_BOM*>( aJob );
702
703 wxCHECK( aBomJob, CLI::EXIT_CODES::ERR_UNKNOWN );
704
705 BOARD* board = getBoard( aBomJob->m_filename );
706
707 if( !board )
709
710 aJob->SetTitleBlock( board->GetTitleBlock() );
711 board->GetProject()->ApplyTextVars( aJob->GetVarOverrides() );
712
713 wxString currentVariant = aBomJob->GetSelectedVariant();
714
715 if( !currentVariant.IsEmpty() && currentVariant != wxS( "all" ) )
716 board->SetCurrentVariant( currentVariant );
717
718 FOOTPRINT_REFERENCE_LIST referenceList;
719
720 // Annotation warning check (and gather footprints for data model)
721 bool hasWarned = false;
722 for( FOOTPRINT* fp : board->Footprints() )
723 {
724 referenceList.push_back( FOOTPRINT_REF( *fp ) );
725
726 if( !fp->IsAnnotated() && !hasWarned )
727 {
728 m_reporter->Report( _( "Warning: board has unannotated footprints, please use the PCB "
729 "editor to annotate them\n" ),
731 hasWarned = true;
732 }
733 }
734
735 // Build our data model
736 FOOTPRINT_FIELDS_EDITOR_GRID_DATA_MODEL dataModel( referenceList );
737 dataModel.SetCurrentVariant( currentVariant );
738
739 // Mandatory fields first
740 for( FIELD_T fieldId : MANDATORY_FIELDS )
741 dataModel.AddColumn( GetDefaultFieldName( fieldId, UNTRANSLATED ),
742 GetDefaultFieldName( fieldId, TRANSLATED ), false );
743
744 // Generated/virtual fields (e.g. ${QUANTITY}, ${ITEM_NUMBER}) present only in the fields table
747 false );
750 false );
751
752 // Attribute fields (boolean flags on footprints)
753 dataModel.AddColumn( wxS( "${DNP}" ), GetGeneratedFieldDisplayName( wxS( "${DNP}" ) ), false );
754 dataModel.AddColumn( wxS( "${EXCLUDE_FROM_BOM}" ), GetGeneratedFieldDisplayName( wxS( "${EXCLUDE_FROM_BOM}" ) ),
755 false );
756 dataModel.AddColumn( wxS( "${EXCLUDE_FROM_BOARD}" ), GetGeneratedFieldDisplayName( wxS( "${EXCLUDE_FROM_BOARD}" ) ),
757 false );
758 dataModel.AddColumn( wxS( "${EXCLUDE_FROM_SIM}" ), GetGeneratedFieldDisplayName( wxS( "${EXCLUDE_FROM_SIM}" ) ),
759 false );
760
761 // User field names in footprints second
762 std::set<wxString> userFieldNames;
763
764 for( size_t i = 0; i < referenceList.size(); ++i )
765 {
766 FOOTPRINT& footprint = referenceList[i].GetFootprint();
767
768 for( PCB_FIELD* field : footprint.GetFields() )
769 {
770 if( !field->IsMandatory() && !field->IsPrivate() )
771 userFieldNames.insert( field->GetName() );
772 }
773 }
774
775 for( const wxString& fieldName : userFieldNames )
776 dataModel.AddColumn( fieldName, GetGeneratedFieldDisplayName( fieldName ), true );
777
778 // Add any templateFieldNames which aren't already present in the userFieldNames
779 // TODO: template field names not implemented in board editor
780
781 BOM_PRESET preset;
782
783 // Load a preset if one is specified
784 if( !aBomJob->m_bomPresetName.IsEmpty() )
785 {
786 // Find the preset
787 std::optional<BOM_PRESET> boardPreset;
788
789 for( const BOM_PRESET& p : BOM_PRESET::BuiltInPresets() )
790 {
791 if( p.name == aBomJob->m_bomPresetName )
792 {
793 boardPreset = p;
794 break;
795 }
796 }
797
798 for( const BOM_PRESET& p : board->GetDesignSettings().m_BomPresets )
799 {
800 if( p.name == aBomJob->m_bomPresetName )
801 {
802 boardPreset = p;
803 break;
804 }
805 }
806
807 if( !boardPreset )
808 {
809 m_reporter->Report(
810 wxString::Format( _( "BOM preset '%s' not found" ) + wxS( "\n" ), aBomJob->m_bomPresetName ),
812
814 }
815
816 preset = *boardPreset;
817 }
818 else
819 {
820 // Normalize field names so that bare generated-field tokens (e.g. "QUANTITY") are
821 // accepted alongside the canonical "${QUANTITY}" form. Shell expansion of ${VAR}
822 // inside double quotes silently produces an empty string, so this also guards against
823 // that common CLI pitfall.
824 auto normalizeFieldName = [&dataModel]( const wxString& aName ) -> wxString
825 {
826 if( aName.IsEmpty() )
827 return wxEmptyString;
828
829 if( IsGeneratedField( aName ) )
830 return aName;
831
832 wxString wrapped = wxS( "${" ) + aName + wxS( "}" );
833
834 if( IsGeneratedField( wrapped ) && dataModel.GetFieldNameCol( wrapped ) != -1 )
835 return wrapped;
836
837 return aName;
838 };
839
840 size_t i = 0;
841
842 for( const wxString& rawFieldName : aBomJob->m_fieldsOrdered )
843 {
844 wxString fieldName = normalizeFieldName( rawFieldName );
845
846 if( fieldName.IsEmpty() )
847 {
848 i++;
849 continue;
850 }
851
852 // Handle wildcard. We allow the wildcard anywhere in the list, but it needs to respect
853 // fields that come before and after the wildcard.
854 if( fieldName == wxS( "*" ) )
855 {
856 for( const BOM_FIELD& modelField : dataModel.GetFieldsOrdered() )
857 {
858 struct BOM_FIELD field;
859
860 field.name = modelField.name;
861 field.show = true;
862 field.groupBy = false;
863 field.label = field.name;
864
865 bool fieldAlreadyPresent = false;
866
867 for( BOM_FIELD& presetField : preset.fieldsOrdered )
868 {
869 if( presetField.name == field.name )
870 {
871 fieldAlreadyPresent = true;
872 break;
873 }
874 }
875
876 bool fieldLaterInList = false;
877
878 for( const wxString& fieldInList : aBomJob->m_fieldsOrdered )
879 {
880 if( normalizeFieldName( fieldInList ) == field.name )
881 {
882 fieldLaterInList = true;
883 break;
884 }
885 }
886
887 if( !fieldAlreadyPresent && !fieldLaterInList )
888 preset.fieldsOrdered.emplace_back( field );
889 }
890
891 continue;
892 }
893
894 struct BOM_FIELD field;
895
896 field.name = fieldName;
897 field.show = !fieldName.StartsWith( wxT( "__" ), &field.name );
898 field.name = normalizeFieldName( field.name );
899
900 if( ( aBomJob->m_fieldsLabels.size() > i ) && !aBomJob->m_fieldsLabels[i].IsEmpty() )
901 field.label = aBomJob->m_fieldsLabels[i];
902 else if( IsGeneratedField( field.name ) )
903 field.label = GetGeneratedFieldDisplayName( field.name );
904 else
905 field.label = field.name;
906
907 preset.fieldsOrdered.emplace_back( field );
908 i++;
909 }
910
911 for( const wxString& rawFieldName : aBomJob->m_fieldsGroupBy )
912 {
913 wxString fieldName = rawFieldName;
914 fieldName.StartsWith( wxT( "__" ), &fieldName );
915 fieldName = normalizeFieldName( fieldName );
916
917 if( fieldName.IsEmpty() )
918 continue;
919
920 bool fieldAlreadyPresent = false;
921
922 for( BOM_FIELD& field : preset.fieldsOrdered )
923 {
924 if( field.name == fieldName )
925 {
926 field.groupBy = true;
927 fieldAlreadyPresent = true;
928 break;
929 }
930 }
931
932 if( !fieldAlreadyPresent )
933 preset.fieldsOrdered.push_back( { fieldName, GetGeneratedFieldDisplayName( fieldName ), false, true } );
934 }
935
936 preset.sortAsc = aBomJob->m_sortAsc;
937 preset.sortField = normalizeFieldName( aBomJob->m_sortField );
938 preset.filterString = aBomJob->m_filterString;
939 preset.filterScope = aBomJob->m_filterScope;
940 preset.groupSymbols = aBomJob->m_groupSymbols;
941 preset.excludeDNP = aBomJob->m_excludeDNP;
942 }
943
944 BOM_FMT_PRESET fmt;
945
946 // Load a format preset if one is specified
947 if( !aBomJob->m_bomFmtPresetName.IsEmpty() )
948 {
949 std::optional<BOM_FMT_PRESET> boardFmtPreset;
950
952 {
953 if( p.name == aBomJob->m_bomFmtPresetName )
954 {
955 boardFmtPreset = p;
956 break;
957 }
958 }
959
960 for( const BOM_FMT_PRESET& p : board->GetDesignSettings().m_BomFmtPresets )
961 {
962 if( p.name == aBomJob->m_bomFmtPresetName )
963 {
964 boardFmtPreset = p;
965 break;
966 }
967 }
968
969 if( !boardFmtPreset )
970 {
971 m_reporter->Report( wxString::Format( _( "BOM format preset '%s' not found" ) + wxS( "\n" ),
972 aBomJob->m_bomFmtPresetName ),
974
976 }
977
978 fmt = *boardFmtPreset;
979 }
980 else
981 {
982 fmt.fieldDelimiter = aBomJob->m_fieldDelimiter;
983 fmt.stringDelimiter = aBomJob->m_stringDelimiter;
984 fmt.refDelimiter = aBomJob->m_refDelimiter;
986 fmt.keepTabs = aBomJob->m_keepTabs;
987 fmt.keepLineBreaks = aBomJob->m_keepLineBreaks;
989 }
990
991 if( aBomJob->GetConfiguredOutputPath().IsEmpty() )
992 {
993 wxFileName fn = board->GetFileName();
994 fn.SetName( fn.GetName() );
995 fn.SetExt( FILEEXT::CsvFileExtension );
996
997 aBomJob->SetConfiguredOutputPath( fn.GetFullName() );
998 }
999
1000 wxString configuredPath = aBomJob->GetConfiguredOutputPath();
1001 bool hasVariantPlaceholder = configuredPath.Contains( wxS( "${VARIANT}" ) );
1002
1003 // Determine which variants to process
1004 std::vector<wxString> variantsToProcess;
1005
1006 if( aBomJob->m_variantNames.size() > 1 && hasVariantPlaceholder )
1007 {
1008 variantsToProcess = aBomJob->m_variantNames;
1009 }
1010 else
1011 {
1012 variantsToProcess.push_back( currentVariant );
1013 }
1014
1015 for( const wxString& variantName : variantsToProcess )
1016 {
1017 std::vector<wxString> singleVariant = { variantName };
1018 dataModel.SetVariantNames( singleVariant );
1019 dataModel.SetCurrentVariant( variantName );
1020 dataModel.UpdateReferences( dataModel.GetReferenceList() );
1021 dataModel.ApplyBomPreset( preset );
1022
1023 wxString outPath;
1024
1025 if( hasVariantPlaceholder )
1026 {
1027 wxString variantPath = ExpandVariantOutputPath( configuredPath, variantName );
1028 aBomJob->SetConfiguredOutputPath( variantPath );
1029 outPath = aBomJob->GetFullOutputPath( board->GetProject() );
1030 aBomJob->SetConfiguredOutputPath( configuredPath );
1031 }
1032 else
1033 {
1034 outPath = aBomJob->GetFullOutputPath( board->GetProject() );
1035 }
1036
1037 if( !PATHS::EnsurePathExists( outPath, true ) )
1038 {
1039 m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
1041 }
1042
1043 wxFile f;
1044
1045 if( !f.Open( outPath, wxFile::write ) )
1046 {
1047 m_reporter->Report( wxString::Format( _( "Unable to open destination '%s'" ), outPath ),
1049
1051 }
1052
1053 bool res = f.Write( dataModel.Export( fmt ) );
1054
1055 if( !res )
1057
1058 aJob->AddOutput( outPath );
1059
1060 m_reporter->Report( wxString::Format( _( "Wrote bill of materials to '%s'." ), outPath ), RPT_SEVERITY_ACTION );
1061 }
1062
1063 return CLI::EXIT_CODES::OK;
1064}
1065
1066
1068{
1069 JOB_EXPORT_PCB_3D* aStepJob = dynamic_cast<JOB_EXPORT_PCB_3D*>( aJob );
1070
1071 if( aStepJob == nullptr )
1073
1074 BOARD* brd = getBoard( aStepJob->m_filename );
1075
1076 if( !brd )
1078
1079 if( !aStepJob->m_variant.IsEmpty() )
1080 brd->SetCurrentVariant( aStepJob->m_variant );
1081
1082 if( aStepJob->GetConfiguredOutputPath().IsEmpty() )
1083 {
1084 wxFileName fn = brd->GetFileName();
1085 fn.SetName( fn.GetName() );
1086
1087 switch( aStepJob->m_format )
1088 {
1098 default:
1099 m_reporter->Report( _( "Unknown export format" ), RPT_SEVERITY_ERROR );
1100 return CLI::EXIT_CODES::ERR_UNKNOWN; // shouldnt have gotten here
1101 }
1102
1103 aStepJob->SetWorkingOutputPath( fn.GetFullName() );
1104 }
1105
1106 wxString outPath = resolveJobOutputPath( aJob, brd );
1107
1108 if( !PATHS::EnsurePathExists( outPath, true ) )
1109 {
1110 m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
1112 }
1113
1114 if( aStepJob->m_format == JOB_EXPORT_PCB_3D::FORMAT::VRML )
1115 {
1116 double scale = 0.0;
1117 switch( aStepJob->m_vrmlUnits )
1118 {
1119 case JOB_EXPORT_PCB_3D::VRML_UNITS::MM: scale = 1.0; break;
1120 case JOB_EXPORT_PCB_3D::VRML_UNITS::METERS: scale = 0.001; break;
1121 case JOB_EXPORT_PCB_3D::VRML_UNITS::TENTHS: scale = 10.0 / 25.4; break;
1122 case JOB_EXPORT_PCB_3D::VRML_UNITS::INCH: scale = 1.0 / 25.4; break;
1123 }
1124
1125 EXPORTER_VRML vrmlExporter( brd );
1126 wxString messages;
1127
1128 double originX = pcbIUScale.IUTomm( aStepJob->m_3dparams.m_Origin.x );
1129 double originY = pcbIUScale.IUTomm( aStepJob->m_3dparams.m_Origin.y );
1130
1131 if( !aStepJob->m_hasUserOrigin )
1132 {
1133 BOX2I bbox = brd->ComputeBoundingBox( true, true );
1134 originX = pcbIUScale.IUTomm( bbox.GetCenter().x );
1135 originY = pcbIUScale.IUTomm( bbox.GetCenter().y );
1136 }
1137
1138 bool success = vrmlExporter.ExportVRML_File(
1139 brd->GetProject(), &messages, outPath, scale, aStepJob->m_3dparams.m_IncludeUnspecified,
1140 aStepJob->m_3dparams.m_IncludeDNP, !aStepJob->m_vrmlModelDir.IsEmpty(), aStepJob->m_vrmlRelativePaths,
1141 aStepJob->m_vrmlModelDir, originX, originY );
1142
1143 if( success )
1144 {
1145 m_reporter->Report( wxString::Format( _( "Successfully exported VRML to %s" ), outPath ),
1147 }
1148 else
1149 {
1150 m_reporter->Report( _( "Error exporting VRML" ), RPT_SEVERITY_ERROR );
1152 }
1153 }
1154 else
1155 {
1156 EXPORTER_STEP_PARAMS params = aStepJob->m_3dparams;
1157
1158 switch( aStepJob->m_format )
1159 {
1169 default:
1170 m_reporter->Report( _( "Unknown export format" ), RPT_SEVERITY_ERROR );
1171 return CLI::EXIT_CODES::ERR_UNKNOWN; // shouldnt have gotten here
1172 }
1173
1174 EXPORTER_STEP stepExporter( brd, params, m_reporter );
1175 stepExporter.m_outputFile = aStepJob->GetFullOutputPath( brd->GetProject() );
1176
1177 if( !stepExporter.Export() )
1179 }
1180
1181 return CLI::EXIT_CODES::OK;
1182}
1183
1184
1186{
1187 JOB_PCB_RENDER* aRenderJob = dynamic_cast<JOB_PCB_RENDER*>( aJob );
1188
1189 if( aRenderJob == nullptr )
1191
1192 // Reject width and height being invalid
1193 // Final bit of sanity because this can blow things up
1194 if( aRenderJob->m_width <= 0 || aRenderJob->m_height <= 0 )
1195 {
1196 m_reporter->Report( _( "Invalid image dimensions" ), RPT_SEVERITY_ERROR );
1198 }
1199
1200 BOARD* brd = getBoard( aRenderJob->m_filename );
1201
1202 if( !brd )
1204
1205 if( !aRenderJob->m_variant.IsEmpty() )
1206 brd->SetCurrentVariant( aRenderJob->m_variant );
1207
1208 if( aRenderJob->GetConfiguredOutputPath().IsEmpty() )
1209 {
1210 wxFileName fn = brd->GetFileName();
1211
1212 switch( aRenderJob->m_format )
1213 {
1216 default:
1217 m_reporter->Report( _( "Unknown export format" ), RPT_SEVERITY_ERROR );
1218 return CLI::EXIT_CODES::ERR_UNKNOWN; // shouldnt have gotten here
1219 }
1220
1221 // set the name to board name + "side", its lazy but its hard to generate anything truely unique
1222 // incase someone is doing this in a jobset with multiple jobs, they should be setting the output themselves
1223 // or we do a hash based on all the options
1224 fn.SetName( wxString::Format( "%s-%d", fn.GetName(), static_cast<int>( aRenderJob->m_side ) ) );
1225
1226 aRenderJob->SetWorkingOutputPath( fn.GetFullName() );
1227 }
1228
1229 wxString outPath = resolveJobOutputPath( aJob, brd );
1230
1231 if( !PATHS::EnsurePathExists( outPath, true ) )
1232 {
1233 m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
1235 }
1236
1237 BOARD_ADAPTER boardAdapter;
1238
1239 boardAdapter.SetBoard( brd );
1240 boardAdapter.m_IsBoardView = false;
1241
1243
1245 {
1246 cfg.m_Render = userCfg->m_Render;
1247 cfg.m_Camera = userCfg->m_Camera;
1248 cfg.m_LayerPresets = userCfg->m_LayerPresets;
1249 }
1250
1251 if( aRenderJob->m_appearancePreset.empty() )
1252 {
1253 // Force display 3D models
1255 cfg.m_Render.show_footprints_dnp = true;
1259 }
1260
1261 if( aRenderJob->m_quality == JOB_PCB_RENDER::QUALITY::BASIC )
1262 {
1263 // Silkscreen is pixelated without antialiasing
1265
1266 cfg.m_Render.raytrace_backfloor = aRenderJob->m_floor;
1267 cfg.m_Render.raytrace_post_processing = aRenderJob->m_floor;
1268
1270 cfg.m_Render.raytrace_reflections = false;
1271 cfg.m_Render.raytrace_shadows = aRenderJob->m_floor;
1272
1273 // Better colors
1275
1276 // Tracks below soldermask are not visible without refractions
1277 cfg.m_Render.raytrace_refractions = true;
1279 }
1280 else if( aRenderJob->m_quality == JOB_PCB_RENDER::QUALITY::HIGH )
1281 {
1283 cfg.m_Render.raytrace_backfloor = true;
1286 cfg.m_Render.raytrace_reflections = true;
1287 cfg.m_Render.raytrace_shadows = true;
1288 cfg.m_Render.raytrace_refractions = true;
1290 }
1291 else if( aRenderJob->m_quality == JOB_PCB_RENDER::QUALITY::JOB_SETTINGS )
1292 {
1294 cfg.m_Render.raytrace_backfloor = aRenderJob->m_floor;
1297 }
1298
1300 aRenderJob->m_lightTopIntensity.z, 1.0 );
1301
1303 COLOR4D( aRenderJob->m_lightBottomIntensity.x, aRenderJob->m_lightBottomIntensity.y,
1304 aRenderJob->m_lightBottomIntensity.z, 1.0 );
1305
1307 COLOR4D( aRenderJob->m_lightCameraIntensity.x, aRenderJob->m_lightCameraIntensity.y,
1308 aRenderJob->m_lightCameraIntensity.z, 1.0 );
1309
1310 COLOR4D lightColor( aRenderJob->m_lightSideIntensity.x, aRenderJob->m_lightSideIntensity.y,
1311 aRenderJob->m_lightSideIntensity.z, 1.0 );
1312
1314 lightColor, lightColor, lightColor, lightColor, lightColor, lightColor, lightColor, lightColor,
1315 };
1316
1317 int sideElevation = aRenderJob->m_lightSideElevation;
1318
1320 sideElevation, sideElevation, sideElevation, sideElevation,
1321 -sideElevation, -sideElevation, -sideElevation, -sideElevation,
1322 };
1323
1325 45, 135, 225, 315, 45, 135, 225, 315,
1326 };
1327
1328 cfg.m_CurrentPreset = aRenderJob->m_appearancePreset;
1330 boardAdapter.m_Cfg = &cfg;
1331
1332 // Apply the preset's layer visibility and colors to the render settings
1333 if( !aRenderJob->m_appearancePreset.empty() )
1334 {
1335 wxString presetName = wxString::FromUTF8( aRenderJob->m_appearancePreset );
1336
1337 if( presetName == FOLLOW_PCB || presetName == FOLLOW_PLOT_SETTINGS )
1338 {
1339 boardAdapter.SetVisibleLayers( boardAdapter.GetVisibleLayers() );
1340 }
1341 else if( LAYER_PRESET_3D* preset = cfg.FindPreset( presetName ) )
1342 {
1343 boardAdapter.SetVisibleLayers( preset->layers );
1344 boardAdapter.SetLayerColors( preset->colors );
1345
1346 if( preset->name.Lower() == _( "legacy colors" ) )
1347 cfg.m_UseStackupColors = false;
1348 }
1349 }
1350
1353 && aRenderJob->m_format == JOB_PCB_RENDER::FORMAT::PNG ) )
1354 {
1355 boardAdapter.m_ColorOverrides[LAYER_3D_BACKGROUND_TOP] = COLOR4D( 1.0, 1.0, 1.0, 0.0 );
1356 boardAdapter.m_ColorOverrides[LAYER_3D_BACKGROUND_BOTTOM] = COLOR4D( 1.0, 1.0, 1.0, 0.0 );
1357 }
1358
1360
1361 static std::map<JOB_PCB_RENDER::SIDE, VIEW3D_TYPE> s_viewCmdMap = {
1368 };
1369
1371
1372 wxSize windowSize( aRenderJob->m_width, aRenderJob->m_height );
1373 TRACK_BALL camera( 2 * RANGE_SCALE_3D );
1374
1375 camera.SetProjection( projection );
1376 camera.SetCurWindowSize( windowSize );
1377
1378 RENDER_3D_RAYTRACE_RAM raytrace( boardAdapter, camera );
1379 raytrace.SetCurWindowSize( windowSize );
1380
1381 std::shared_ptr<REPORTER> reporter( m_reporter, []( REPORTER* ) {} );
1382 raytrace.SetReporters( reporter, reporter );
1383
1384 for( bool first = true; raytrace.Redraw( false ); first = false )
1385 {
1386 if( first )
1387 {
1388 const float cmTo3D = boardAdapter.BiuTo3dUnits() * pcbIUScale.mmToIU( 10.0 );
1389
1390 // First redraw resets lookat point to the board center, so set up the camera here
1391 camera.ViewCommand_T1( s_viewCmdMap[aRenderJob->m_side] );
1392
1393 camera.SetLookAtPos_T1( camera.GetLookAtPos_T1()
1394 + SFVEC3F( aRenderJob->m_pivot.x, aRenderJob->m_pivot.y, aRenderJob->m_pivot.z )
1395 * cmTo3D );
1396
1397 camera.Pan_T1( SFVEC3F( aRenderJob->m_pan.x, aRenderJob->m_pan.y, aRenderJob->m_pan.z ) );
1398
1399 camera.Zoom_T1( aRenderJob->m_zoom );
1400
1401 camera.RotateX_T1( DEG2RAD( aRenderJob->m_rotation.x ) );
1402 camera.RotateY_T1( DEG2RAD( aRenderJob->m_rotation.y ) );
1403 camera.RotateZ_T1( DEG2RAD( aRenderJob->m_rotation.z ) );
1404
1405 camera.Interpolate( 1.0f );
1406 camera.SetT0_and_T1_current_T();
1407 camera.ParametersChanged();
1408 }
1409 }
1410
1411 uint8_t* rgbaBuffer = raytrace.GetBuffer();
1412 wxSize realSize = raytrace.GetRealBufferSize();
1413 bool success = !!rgbaBuffer;
1414
1415 if( rgbaBuffer )
1416 {
1417 const unsigned int wxh = realSize.x * realSize.y;
1418
1419 unsigned char* rgbBuffer = (unsigned char*) malloc( wxh * 3 );
1420 unsigned char* alphaBuffer = (unsigned char*) malloc( wxh );
1421
1422 unsigned char* rgbaPtr = rgbaBuffer;
1423 unsigned char* rgbPtr = rgbBuffer;
1424 unsigned char* alphaPtr = alphaBuffer;
1425
1426 for( int y = 0; y < realSize.y; y++ )
1427 {
1428 for( int x = 0; x < realSize.x; x++ )
1429 {
1430 rgbPtr[0] = rgbaPtr[0];
1431 rgbPtr[1] = rgbaPtr[1];
1432 rgbPtr[2] = rgbaPtr[2];
1433 alphaPtr[0] = rgbaPtr[3];
1434
1435 rgbaPtr += 4;
1436 rgbPtr += 3;
1437 alphaPtr += 1;
1438 }
1439 }
1440
1441 wxImage image( realSize );
1442 image.SetData( rgbBuffer );
1443 image.SetAlpha( alphaBuffer );
1444 image = image.Mirror( false );
1445
1446 image.SetOption( wxIMAGE_OPTION_QUALITY, 90 );
1447 image.SaveFile( outPath,
1448 aRenderJob->m_format == JOB_PCB_RENDER::FORMAT::PNG ? wxBITMAP_TYPE_PNG : wxBITMAP_TYPE_JPEG );
1449 }
1450
1451 if( success )
1452 {
1453 m_reporter->Report( _( "Successfully created 3D render image" ) + wxS( "\n" ), RPT_SEVERITY_INFO );
1454 return CLI::EXIT_CODES::OK;
1455 }
1456 else
1457 {
1458 m_reporter->Report( _( "Error creating 3D render image" ) + wxS( "\n" ), RPT_SEVERITY_ERROR );
1460 }
1461}
1462
1463
1465 TOOL_MANAGER* aToolManager )
1466{
1467 if( aJob->m_checkZonesBeforePlot )
1468 {
1469 if( !aToolManager->FindTool( ZONE_FILLER_TOOL_NAME ) )
1470 aToolManager->RegisterTool( new ZONE_FILLER_TOOL );
1471
1472 aToolManager->GetTool<ZONE_FILLER_TOOL>()->FillAllZones( nullptr, m_progressReporter, true );
1473 }
1474
1475 if( aJob->m_argLayers )
1476 aJob->m_plotLayerSequence = convertLayerArg( aJob->m_argLayers.value(), aBoard );
1477
1478 if( aJob->m_argCommonLayers )
1479 aJob->m_plotOnAllLayersSequence = convertLayerArg( aJob->m_argCommonLayers.value(), aBoard );
1480
1481 if( aJob->m_plotLayerSequence.empty() )
1482 {
1483 m_reporter->Report( _( "At least one layer must be specified\n" ), RPT_SEVERITY_ERROR );
1484 return false;
1485 }
1486
1487 return true;
1488}
1489
1490
1492 PLOT_FORMAT aFormat, bool aSingleOutput )
1493{
1494 if( aSingleOutput && aJob->GetConfiguredOutputPath().IsEmpty() )
1495 {
1496 wxFileName fn = aBoard->GetFileName();
1497 fn.SetExt( GetDefaultPlotExtension( aFormat ) );
1498 aJob->SetWorkingOutputPath( fn.GetFullName() );
1499 }
1500
1501 return resolveJobOutputPath( aJob, aBoard, &aJob->m_drawingSheet );
1502}
1503
1504
1505// PDF single-document output need not use overrides; PNG never uses them.
1506static int plotJob( JOB_EXPORT_PCB_PLOT* aJob, PCB_PLOTTER& aPlotter, const wxString& aOutPath,
1507 bool aSingleOutput, bool aUseOverrides )
1508{
1509 std::optional<wxString> layerName;
1510 std::optional<wxString> sheetName;
1511 std::optional<wxString> sheetPath;
1512
1513 if( aUseOverrides )
1514 {
1515 if( aJob->GetVarOverrides().contains( wxT( "LAYER" ) ) )
1516 layerName = aJob->GetVarOverrides().at( wxT( "LAYER" ) );
1517
1518 if( aJob->GetVarOverrides().contains( wxT( "SHEETNAME" ) ) )
1519 sheetName = aJob->GetVarOverrides().at( wxT( "SHEETNAME" ) );
1520
1521 if( aJob->GetVarOverrides().contains( wxT( "SHEETPATH" ) ) )
1522 sheetPath = aJob->GetVarOverrides().at( wxT( "SHEETPATH" ) );
1523 }
1524
1525 std::vector<wxString> outputPaths;
1526
1527 if( !aPlotter.Plot( aOutPath, aJob->m_plotLayerSequence, aJob->m_plotOnAllLayersSequence, false,
1528 aSingleOutput, layerName, sheetName, sheetPath, &outputPaths ) )
1529 {
1531 }
1532
1533 for( const wxString& outputPath : outputPaths )
1534 aJob->AddOutput( outputPath );
1535
1536 return CLI::EXIT_CODES::OK;
1537}
1538
1539
1541{
1542 JOB_EXPORT_PCB_SVG* aSvgJob = dynamic_cast<JOB_EXPORT_PCB_SVG*>( aJob );
1543
1544 if( aSvgJob == nullptr )
1546
1547 BOARD* brd = getBoard( aSvgJob->m_filename );
1548 TOOL_MANAGER* toolManager = getToolManager( brd );
1549
1550 if( !brd )
1552
1553 if( !aSvgJob->m_variant.IsEmpty() )
1554 brd->SetCurrentVariant( aSvgJob->m_variant );
1555
1556 const bool isSingle = aSvgJob->m_genMode == JOB_EXPORT_PCB_SVG::GEN_MODE::SINGLE;
1557
1558 wxString outPath = resolvePlotOutputPath( aSvgJob, brd, PLOT_FORMAT::SVG, isSingle );
1559
1560 if( !PATHS::EnsurePathExists( outPath, isSingle ) )
1561 {
1562 m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
1564 }
1565
1566 if( !preparePlotLayers( aSvgJob, brd, toolManager ) )
1568
1569 PCB_PLOT_PARAMS plotOpts;
1570 PCB_PLOTTER::PlotJobToPlotOpts( plotOpts, aSvgJob, *m_reporter );
1571
1572 PCB_PLOTTER plotter( brd, m_reporter, plotOpts );
1573
1574 return plotJob( aSvgJob, plotter, outPath, isSingle, isSingle );
1575}
1576
1577
1579{
1580 JOB_EXPORT_PCB_DXF* aDxfJob = dynamic_cast<JOB_EXPORT_PCB_DXF*>( aJob );
1581
1582 if( aDxfJob == nullptr )
1584
1585 BOARD* brd = getBoard( aDxfJob->m_filename );
1586
1587 if( !brd )
1589
1590 if( !aDxfJob->m_variant.IsEmpty() )
1591 brd->SetCurrentVariant( aDxfJob->m_variant );
1592
1593 TOOL_MANAGER* toolManager = getToolManager( brd );
1594
1595 if( !preparePlotLayers( aDxfJob, brd, toolManager ) )
1597
1598 const bool isSingle = aDxfJob->m_genMode == JOB_EXPORT_PCB_DXF::GEN_MODE::SINGLE;
1599
1600 wxString outPath = resolvePlotOutputPath( aDxfJob, brd, PLOT_FORMAT::DXF, isSingle );
1601
1602 if( !PATHS::EnsurePathExists( outPath, isSingle ) )
1603 {
1604 m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
1606 }
1607
1608 PCB_PLOT_PARAMS plotOpts;
1609 PCB_PLOTTER::PlotJobToPlotOpts( plotOpts, aDxfJob, *m_reporter );
1610
1611 PCB_PLOTTER plotter( brd, m_reporter, plotOpts );
1612
1613 return plotJob( aDxfJob, plotter, outPath, isSingle, isSingle );
1614}
1615
1616
1618{
1619 bool plotAllLayersOneFile = false;
1620 JOB_EXPORT_PCB_PDF* pdfJob = dynamic_cast<JOB_EXPORT_PCB_PDF*>( aJob );
1621
1622 if( pdfJob == nullptr )
1624
1625 BOARD* brd = getBoard( pdfJob->m_filename );
1626
1627 if( !brd )
1629
1630 if( !pdfJob->m_variant.IsEmpty() )
1631 brd->SetCurrentVariant( pdfJob->m_variant );
1632
1633 TOOL_MANAGER* toolManager = getToolManager( brd );
1634
1635 if( !preparePlotLayers( pdfJob, brd, toolManager ) )
1637
1639 plotAllLayersOneFile = true;
1640
1641 const bool outputIsSingle = plotAllLayersOneFile || pdfJob->m_pdfSingle;
1642
1643 wxString outPath = resolvePlotOutputPath( pdfJob, brd, PLOT_FORMAT::PDF, outputIsSingle );
1644
1645 PCB_PLOT_PARAMS plotOpts;
1646 PCB_PLOTTER::PlotJobToPlotOpts( plotOpts, pdfJob, *m_reporter );
1647
1648 PCB_PLOTTER pcbPlotter( brd, m_reporter, plotOpts );
1649
1650 if( !PATHS::EnsurePathExists( outPath, outputIsSingle ) )
1651 {
1652 m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
1654 }
1655
1656 return plotJob( pdfJob, pcbPlotter, outPath, outputIsSingle, plotAllLayersOneFile );
1657}
1658
1659
1661{
1662 JOB_EXPORT_PCB_PNG* pngJob = dynamic_cast<JOB_EXPORT_PCB_PNG*>( aJob );
1663
1664 if( pngJob == nullptr )
1666
1667 BOARD* brd = getBoard( pngJob->m_filename );
1668
1669 if( !brd )
1671
1672 if( !pngJob->m_variant.IsEmpty() )
1673 brd->SetCurrentVariant( pngJob->m_variant );
1674
1675 TOOL_MANAGER* toolManager = getToolManager( brd );
1676
1677 if( !preparePlotLayers( pngJob, brd, toolManager ) )
1679
1680 bool isSingle = pngJob->m_genMode == JOB_EXPORT_PCB_PNG::GEN_MODE::SINGLE;
1681
1682 wxString outPath = resolvePlotOutputPath( pngJob, brd, PLOT_FORMAT::PNG, isSingle );
1683
1684 PCB_PLOT_PARAMS plotOpts;
1685 PCB_PLOTTER::PlotJobToPlotOpts( plotOpts, pngJob, *m_reporter );
1686
1687 PCB_PLOTTER pcbPlotter( brd, m_reporter, plotOpts );
1688
1689 if( !PATHS::EnsurePathExists( outPath, isSingle ) )
1690 {
1691 m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
1693 }
1694
1695 return plotJob( pngJob, pcbPlotter, outPath, isSingle, false );
1696}
1697
1698
1700{
1701 JOB_EXPORT_PCB_PS* psJob = dynamic_cast<JOB_EXPORT_PCB_PS*>( aJob );
1702
1703 if( psJob == nullptr )
1705
1706 BOARD* brd = getBoard( psJob->m_filename );
1707
1708 if( !brd )
1710
1711 if( !psJob->m_variant.IsEmpty() )
1712 brd->SetCurrentVariant( psJob->m_variant );
1713
1714 TOOL_MANAGER* toolManager = getToolManager( brd );
1715
1716 if( !preparePlotLayers( psJob, brd, toolManager ) )
1718
1719 bool isSingle = psJob->m_genMode == JOB_EXPORT_PCB_PS::GEN_MODE::SINGLE;
1720
1721 wxString outPath = resolvePlotOutputPath( psJob, brd, PLOT_FORMAT::POST, isSingle );
1722
1723 if( !PATHS::EnsurePathExists( outPath, isSingle ) )
1724 {
1725 m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
1727 }
1728
1729 PCB_PLOT_PARAMS plotOpts;
1730 PCB_PLOTTER::PlotJobToPlotOpts( plotOpts, psJob, *m_reporter );
1731
1732 PCB_PLOTTER pcbPlotter( brd, m_reporter, plotOpts );
1733
1734 return plotJob( psJob, pcbPlotter, outPath, isSingle, isSingle );
1735}
1736
1737
1739{
1740 int exitCode = CLI::EXIT_CODES::OK;
1741 JOB_EXPORT_PCB_GERBERS* aGerberJob = dynamic_cast<JOB_EXPORT_PCB_GERBERS*>( aJob );
1742
1743 if( aGerberJob == nullptr )
1745
1746 BOARD* brd = getBoard( aGerberJob->m_filename );
1747
1748 if( !brd )
1750
1751 if( !aGerberJob->m_variant.IsEmpty() )
1752 brd->SetCurrentVariant( aGerberJob->m_variant );
1753
1754 wxString outPath = resolveJobOutputPath( aJob, brd, &aGerberJob->m_drawingSheet );
1755
1756 if( !PATHS::EnsurePathExists( outPath, false ) )
1757 {
1758 m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
1760 }
1761
1762 TOOL_MANAGER* toolManager = getToolManager( brd );
1763
1764 if( aGerberJob->m_checkZonesBeforePlot )
1765 {
1766 if( !toolManager->FindTool( ZONE_FILLER_TOOL_NAME ) )
1767 toolManager->RegisterTool( new ZONE_FILLER_TOOL );
1768
1769 toolManager->GetTool<ZONE_FILLER_TOOL>()->FillAllZones( nullptr, m_progressReporter, true );
1770 }
1771
1772 bool hasLayerListSpecified = false; // will be true if the user layer list is not empty
1773
1774 if( aGerberJob->m_argLayers )
1775 {
1776 if( !aGerberJob->m_argLayers.value().empty() )
1777 {
1778 aGerberJob->m_plotLayerSequence = convertLayerArg( aGerberJob->m_argLayers.value(), brd );
1779 hasLayerListSpecified = true;
1780 }
1781 else
1782 {
1784 }
1785 }
1786
1787 if( aGerberJob->m_argCommonLayers )
1788 aGerberJob->m_plotOnAllLayersSequence = convertLayerArg( aGerberJob->m_argCommonLayers.value(), brd );
1789
1790 PCB_PLOT_PARAMS boardPlotOptions = brd->GetPlotOptions();
1791 GERBER_JOBFILE_WRITER jobfile_writer( brd );
1792
1793 wxString fileExt;
1794
1795 if( aGerberJob->m_useBoardPlotParams )
1796 {
1797 // The board plot options are saved with all copper layers enabled, even those that don't
1798 // exist in the current stackup. This is done so the layers are automatically enabled in the plot
1799 // dialog when the user enables them. We need to filter out these not-enabled layers here so
1800 // we don't plot 32 layers when we only have 4, etc.
1801 LSET plotLayers = ( boardPlotOptions.GetLayerSelection() & LSET::AllNonCuMask() )
1802 | ( brd->GetEnabledLayers() & LSET::AllCuMask() );
1803 aGerberJob->m_plotLayerSequence = plotLayers.SeqStackupForPlotting();
1804 aGerberJob->m_plotOnAllLayersSequence = boardPlotOptions.GetPlotOnAllLayersSequence();
1805 }
1806 else
1807 {
1808 // default to the board enabled layers, but only if the user has not specifed a layer list
1809 // ( m_plotLayerSequence can be empty with a broken user layer list)
1810 if( aGerberJob->m_plotLayerSequence.empty() && !hasLayerListSpecified )
1812 }
1813
1814 // Ensure layers to plot are restricted to enabled layers of the board to plot
1815 LSET layersToPlot = LSET( { aGerberJob->m_plotLayerSequence } ) & brd->GetEnabledLayers();
1816
1817 // Once for the run, not per file, so an update rebuilds each chart once. Common layers
1818 // included or a chart plotted as one is never looked at
1819 LSET preflightLayers = layersToPlot;
1820
1821 for( PCB_LAYER_ID commonLayer : aGerberJob->m_plotOnAllLayersSequence )
1822 preflightLayers.set( commonLayer );
1823
1824 RefreshGeneratedTables( *brd );
1825
1826 for( PCB_LAYER_ID layer : layersToPlot.UIOrder() )
1827 {
1828 LSEQ plotSequence;
1829
1830 // Base layer always gets plotted first.
1831 plotSequence.push_back( layer );
1832
1833 // Now all the "include on all" layers
1834 for( PCB_LAYER_ID layer_all : aGerberJob->m_plotOnAllLayersSequence )
1835 {
1836 // Don't plot the same layer more than once;
1837 if( find( plotSequence.begin(), plotSequence.end(), layer_all ) != plotSequence.end() )
1838 continue;
1839
1840 plotSequence.push_back( layer_all );
1841 }
1842
1843 // Pick the basename from the board file
1844 wxFileName fn( brd->GetFileName() );
1845 wxString layerName = brd->GetLayerName( layer );
1846 wxString sheetName;
1847 wxString sheetPath;
1848 PCB_PLOT_PARAMS plotOpts;
1849
1850 if( aGerberJob->m_useBoardPlotParams )
1851 plotOpts = boardPlotOptions;
1852 else
1853 PCB_PLOTTER::PlotJobToPlotOpts( plotOpts, aGerberJob, *m_reporter );
1854
1855 if( plotOpts.GetUseGerberProtelExtensions() )
1856 fileExt = GetGerberProtelExtension( layer );
1857 else
1859
1860 PCB_PLOTTER::BuildPlotFileName( &fn, outPath, layerName, fileExt );
1861 wxString fullname = fn.GetFullName();
1862
1863 if( m_progressReporter )
1864 {
1865 m_progressReporter->AdvancePhase( wxString::Format( _( "Exporting %s" ), fullname ) );
1866 m_progressReporter->KeepRefreshing();
1867 }
1868
1869 jobfile_writer.AddGbrFile( layer, fullname );
1870
1871 if( aJob->GetVarOverrides().contains( wxT( "LAYER" ) ) )
1872 layerName = aJob->GetVarOverrides().at( wxT( "LAYER" ) );
1873
1874 if( aJob->GetVarOverrides().contains( wxT( "SHEETNAME" ) ) )
1875 sheetName = aJob->GetVarOverrides().at( wxT( "SHEETNAME" ) );
1876
1877 if( aJob->GetVarOverrides().contains( wxT( "SHEETPATH" ) ) )
1878 sheetPath = aJob->GetVarOverrides().at( wxT( "SHEETPATH" ) );
1879
1880 // We are feeding it one layer at the start here to silence a logic check
1881 GERBER_PLOTTER* plotter;
1882 plotter = (GERBER_PLOTTER*) StartPlotBoard( brd, &plotOpts, layer, layerName, fn.GetFullPath(), sheetName,
1883 sheetPath );
1884
1885 if( plotter )
1886 {
1887 m_reporter->Report( wxString::Format( _( "Plotted to '%s'.\n" ), fn.GetFullPath() ), RPT_SEVERITY_ACTION );
1888
1889 PlotBoardLayers( brd, plotter, plotSequence, plotOpts );
1890 plotter->EndPlot();
1891 aJob->AddOutput( fn.GetFullPath() );
1892 }
1893 else
1894 {
1895 m_reporter->Report( wxString::Format( _( "Failed to plot to '%s'.\n" ), fn.GetFullPath() ),
1898 }
1899
1900 delete plotter;
1901 }
1902
1903 if( aGerberJob->m_createJobsFile )
1904 {
1905 wxFileName fn( brd->GetFileName() );
1906
1907 // Build gerber job file from basename
1909 jobfile_writer.CreateJobFile( fn.GetFullPath() );
1910 aJob->AddOutput( fn.GetFullPath() );
1911 }
1912
1913 return exitCode;
1914}
1915
1916
1918{
1919 JOB_EXPORT_PCB_GENCAD* aGencadJob = dynamic_cast<JOB_EXPORT_PCB_GENCAD*>( aJob );
1920
1921 if( aGencadJob == nullptr )
1923
1924 BOARD* brd = getBoard( aGencadJob->m_filename );
1925
1926 if( !brd )
1928
1929 GENCAD_EXPORTER exporter( brd );
1930
1931 VECTOR2I GencadOffset;
1932 VECTOR2I auxOrigin = brd->GetDesignSettings().GetAuxOrigin();
1933 GencadOffset.x = aGencadJob->m_useDrillOrigin ? auxOrigin.x : 0;
1934 GencadOffset.y = aGencadJob->m_useDrillOrigin ? auxOrigin.y : 0;
1935
1936 exporter.FlipBottomPads( aGencadJob->m_flipBottomPads );
1937 exporter.UsePinNamesUnique( aGencadJob->m_useUniquePins );
1938 exporter.UseIndividualShapes( aGencadJob->m_useIndividualShapes );
1939 exporter.SetPlotOffet( GencadOffset );
1940 exporter.StoreOriginCoordsInFile( aGencadJob->m_storeOriginCoords );
1941
1942 if( aGencadJob->GetConfiguredOutputPath().IsEmpty() )
1943 {
1944 wxFileName fn = brd->GetFileName();
1945 fn.SetName( fn.GetName() );
1946 fn.SetExt( FILEEXT::GencadFileExtension );
1947
1948 aGencadJob->SetWorkingOutputPath( fn.GetFullName() );
1949 }
1950
1951 wxString outPath = resolveJobOutputPath( aJob, brd );
1952
1953 if( !PATHS::EnsurePathExists( outPath, true ) )
1954 {
1955 m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
1957 }
1958
1959 if( !exporter.WriteFile( outPath ) )
1960 {
1961 m_reporter->Report( wxString::Format( _( "Failed to create file '%s'.\n" ), outPath ), RPT_SEVERITY_ERROR );
1962
1964 }
1965
1966 aJob->AddOutput( outPath );
1967 m_reporter->Report( _( "Successfully created genCAD file\n" ), RPT_SEVERITY_INFO );
1968
1969 return CLI::EXIT_CODES::OK;
1970}
1971
1972
1974{
1975 JOB_EXPORT_PCB_SPECCTRA_DSN* specctraJob = dynamic_cast<JOB_EXPORT_PCB_SPECCTRA_DSN*>( aJob );
1976
1977 if( !specctraJob )
1979
1980 BOARD* brd = getBoard( specctraJob->m_filename );
1981
1982 if( !brd )
1984
1985 if( specctraJob->GetConfiguredOutputPath().IsEmpty() )
1986 {
1987 wxFileName fn = brd->GetFileName();
1989
1990 specctraJob->SetWorkingOutputPath( fn.GetFullName() );
1991 }
1992
1993 wxString outPath = resolveJobOutputPath( aJob, brd );
1994
1995 if( !PATHS::EnsurePathExists( outPath, true ) )
1996 {
1997 m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
1999 }
2000
2001 try
2002 {
2004 }
2005 catch( const IO_ERROR& ioe )
2006 {
2007 m_reporter->Report( wxString::Format( _( "Export failed: %s\n" ), ioe.What() ),
2010 }
2011
2012 aJob->AddOutput( outPath );
2013 m_reporter->Report( _( "Successfully created Specctra DSN file\n" ), RPT_SEVERITY_INFO );
2014
2015 return CLI::EXIT_CODES::OK;
2016}
2017
2018
2020{
2021 JOB_EXPORT_PCB_STATS* statsJob = dynamic_cast<JOB_EXPORT_PCB_STATS*>( aJob );
2022
2023 if( statsJob == nullptr )
2025
2026 BOARD* brd = getBoard( statsJob->m_filename );
2027
2028 if( !brd )
2030
2033
2038
2039 ComputeBoardStatistics( brd, options, data );
2040
2041 wxString projectName;
2042
2043 if( brd->GetProject() )
2044 projectName = brd->GetProject()->GetProjectName();
2045
2046 wxFileName boardFile = brd->GetFileName();
2047
2048 if( boardFile.GetName().IsEmpty() )
2049 boardFile = wxFileName( statsJob->m_filename );
2050
2052 UNITS_PROVIDER unitsProvider( pcbIUScale, unitsForReport );
2053
2054 wxString report;
2055
2057 report = FormatBoardStatisticsJson( data, brd, unitsProvider, projectName, boardFile.GetName() );
2058 else
2059 report = FormatBoardStatisticsReport( data, brd, unitsProvider, projectName, boardFile.GetName() );
2060
2061 if( statsJob->GetConfiguredOutputPath().IsEmpty() && statsJob->GetWorkingOutputPath().IsEmpty() )
2062 statsJob->SetDefaultOutputPath( boardFile.GetFullPath() );
2063
2064 wxString outPath = resolveJobOutputPath( aJob, brd );
2065
2066 if( !PATHS::EnsurePathExists( outPath, true ) )
2067 {
2068 m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
2070 }
2071
2072 FILE* outFile = wxFopen( outPath, wxS( "wt" ) );
2073
2074 if( !outFile )
2075 {
2076 m_reporter->Report( wxString::Format( _( "Failed to create file '%s'.\n" ), outPath ), RPT_SEVERITY_ERROR );
2078 }
2079
2080 if( fprintf( outFile, "%s", TO_UTF8( report ) ) < 0 )
2081 {
2082 fclose( outFile );
2083 m_reporter->Report( wxString::Format( _( "Error writing file '%s'.\n" ), outPath ), RPT_SEVERITY_ERROR );
2085 }
2086
2087 fclose( outFile );
2088
2089 m_reporter->Report( wxString::Format( _( "Wrote board statistics to '%s'.\n" ), outPath ), RPT_SEVERITY_ACTION );
2090
2091 statsJob->AddOutput( outPath );
2092
2093 return CLI::EXIT_CODES::OK;
2094}
2095
2096
2098{
2099 JOB_EXPORT_PCB_STACKUP* stackupJob = dynamic_cast<JOB_EXPORT_PCB_STACKUP*>( aJob );
2100
2101 if( stackupJob == nullptr )
2103
2104 BOARD* brd = getBoard( stackupJob->m_filename );
2105
2106 if( !brd )
2108
2109 wxFileName boardFile = brd->GetFileName();
2110
2111 if( boardFile.GetName().IsEmpty() )
2112 boardFile = wxFileName( stackupJob->m_filename );
2113
2114 wxString output;
2115
2116 switch( stackupJob->m_format )
2117 {
2119 {
2120 kiapi::board::BoardStackup stackupMsg;
2121 kiapi::board::PackBoardStackup( *brd, stackupMsg );
2122
2123 google::protobuf::util::JsonPrintOptions jsonOptions;
2124 jsonOptions.add_whitespace = true;
2125
2126 std::string json;
2127
2128 if( !google::protobuf::util::MessageToJsonString( stackupMsg, &json, jsonOptions ).ok() )
2129 {
2130 m_reporter->Report( _( "Failed to serialize board stackup\n" ), RPT_SEVERITY_ERROR );
2132 }
2133
2134 output = wxString::FromUTF8( json );
2135 break;
2136 }
2137
2139 {
2141 BOARD_STACKUP stackup = bds.GetStackupDescriptor();
2142 stackup.SynchronizeWithBoard( &bds );
2143
2144 for( BOARD_STACKUP_ITEM* item : stackup.GetList() )
2145 {
2146 if( item->GetBrdLayerId() != UNDEFINED_LAYER )
2147 item->SetLayerName( brd->GetLayerName( item->GetBrdLayerId() ) );
2148 }
2149
2150 EDA_UNITS unitsForReport = stackupJob->m_units == JOB_EXPORT_PCB_STACKUP::UNITS::MM ? EDA_UNITS::MM
2152
2153 STACKUP_CSV_OPTIONS options;
2154 options.includeColor = stackupJob->m_includeColor;
2155 options.includeMaterial = stackupJob->m_includeMaterial;
2156 options.includeThickness = stackupJob->m_includeThickness;
2157 options.includeEpsilonR = stackupJob->m_includeEpsilonR;
2158 options.includeLossTangent = stackupJob->m_includeLossTangent;
2159 options.includeFinish = stackupJob->m_includeFinish;
2160 options.includeBoardOptions = stackupJob->m_includeBoardOptions;
2161
2162 output = BuildStackupCsv( stackup, unitsForReport, options );
2163 break;
2164 }
2165 }
2166
2167 if( stackupJob->GetConfiguredOutputPath().IsEmpty() && stackupJob->GetWorkingOutputPath().IsEmpty() )
2168 stackupJob->SetDefaultOutputPath( boardFile.GetFullPath() );
2169
2170 wxString outPath = resolveJobOutputPath( aJob, brd );
2171
2172 if( !PATHS::EnsurePathExists( outPath, true ) )
2173 {
2174 m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
2176 }
2177
2178 OPEN_OSTREAM( outFile, TO_UTF8( outPath ) );
2179
2180 if( !outFile )
2181 {
2182 m_reporter->Report( wxString::Format( _( "Failed to create file '%s'.\n" ), outPath ), RPT_SEVERITY_ERROR );
2184 }
2185
2186 outFile << TO_UTF8( output );
2187
2188 const bool writeOk = static_cast<bool>( outFile );
2189 CLOSE_STREAM( outFile );
2190
2191 if( !writeOk )
2192 {
2193 m_reporter->Report( wxString::Format( _( "Error writing file '%s'.\n" ), outPath ), RPT_SEVERITY_ERROR );
2195 }
2196
2197 m_reporter->Report( wxString::Format( _( "Wrote board stackup to '%s'.\n" ), outPath ), RPT_SEVERITY_ACTION );
2198
2199 stackupJob->AddOutput( outPath );
2200
2201 return CLI::EXIT_CODES::OK;
2202}
2203
2204
2206{
2207 int exitCode = CLI::EXIT_CODES::OK;
2208 JOB_EXPORT_PCB_GERBER* aGerberJob = dynamic_cast<JOB_EXPORT_PCB_GERBER*>( aJob );
2209
2210 if( aGerberJob == nullptr )
2212
2213 BOARD* brd = getBoard( aGerberJob->m_filename );
2214
2215 if( !brd )
2217
2218 if( !aGerberJob->m_variant.IsEmpty() )
2219 brd->SetCurrentVariant( aGerberJob->m_variant );
2220
2221 TOOL_MANAGER* toolManager = getToolManager( brd );
2222
2223 if( aGerberJob->m_argLayers )
2224 aGerberJob->m_plotLayerSequence = convertLayerArg( aGerberJob->m_argLayers.value(), brd );
2225
2226 if( aGerberJob->m_argCommonLayers )
2227 aGerberJob->m_plotOnAllLayersSequence = convertLayerArg( aGerberJob->m_argCommonLayers.value(), brd );
2228
2229 if( aGerberJob->m_plotLayerSequence.size() < 1 )
2230 {
2231 m_reporter->Report( _( "At least one layer must be specified\n" ), RPT_SEVERITY_ERROR );
2233 }
2234
2235 if( aGerberJob->GetConfiguredOutputPath().IsEmpty() )
2236 {
2237 wxFileName fn = brd->GetFileName();
2238 fn.SetName( fn.GetName() );
2240
2241 aGerberJob->SetWorkingOutputPath( fn.GetFullName() );
2242 }
2243
2244 wxString outPath = resolveJobOutputPath( aJob, brd, &aGerberJob->m_drawingSheet );
2245
2246 if( aGerberJob->m_checkZonesBeforePlot )
2247 {
2248 if( !toolManager->FindTool( ZONE_FILLER_TOOL_NAME ) )
2249 toolManager->RegisterTool( new ZONE_FILLER_TOOL );
2250
2251 toolManager->GetTool<ZONE_FILLER_TOOL>()->FillAllZones( nullptr, m_progressReporter, true );
2252 }
2253
2254 PCB_PLOT_PARAMS plotOpts;
2255 PCB_PLOTTER::PlotJobToPlotOpts( plotOpts, aGerberJob, *m_reporter );
2256 plotOpts.SetLayerSelection( aGerberJob->m_plotLayerSequence );
2258
2260 wxString layerName;
2261 wxString sheetName;
2262 wxString sheetPath;
2263
2264 // The first layer will be treated as the layer name for the gerber header,
2265 // the other layers will be treated equivalent to the "Plot on All Layers" option
2266 // in the GUI
2267 if( aGerberJob->m_plotLayerSequence.size() >= 1 )
2268 {
2269 layer = aGerberJob->m_plotLayerSequence.front();
2270 layerName = brd->GetLayerName( layer );
2271 }
2272
2273 if( aJob->GetVarOverrides().contains( wxT( "LAYER" ) ) )
2274 layerName = aJob->GetVarOverrides().at( wxT( "LAYER" ) );
2275
2276 if( aJob->GetVarOverrides().contains( wxT( "SHEETNAME" ) ) )
2277 sheetName = aJob->GetVarOverrides().at( wxT( "SHEETNAME" ) );
2278
2279 if( aJob->GetVarOverrides().contains( wxT( "SHEETPATH" ) ) )
2280 sheetPath = aJob->GetVarOverrides().at( wxT( "SHEETPATH" ) );
2281
2282 // We are feeding it one layer at the start here to silence a logic check
2283
2284 // It drives StartPlotBoard directly rather than PCB_PLOTTER::Plot, so it needs its own
2285 // preflight or a FAIL policy would still emit a stale chart
2286 {
2287 LSET preflightLayers( { aGerberJob->m_plotLayerSequence } );
2288
2289 RefreshGeneratedTables( *brd );
2290 }
2291
2292 PLOTTER* plotter = StartPlotBoard( brd, &plotOpts, layer, layerName, outPath, sheetName, sheetPath );
2293 bool success = false;
2294
2295 if( plotter )
2296 {
2297 PlotBoardLayers( brd, plotter, aGerberJob->m_plotLayerSequence, plotOpts );
2298 success = plotter->EndPlot();
2299 }
2300
2301 if( !success )
2302 {
2303 m_reporter->Report( wxString::Format( _( "Failed to plot to '%s'.\n" ), outPath ), RPT_SEVERITY_ERROR );
2305 }
2306
2307 delete plotter;
2308
2309 return exitCode;
2310}
2311
2314
2315
2317{
2318 JOB_EXPORT_PCB_DRILL* aDrillJob = dynamic_cast<JOB_EXPORT_PCB_DRILL*>( aJob );
2319
2320 if( aDrillJob == nullptr )
2322
2323 BOARD* brd = getBoard( aDrillJob->m_filename );
2324
2325 if( !brd )
2327
2328 wxString outPath = resolveJobOutputPath( aJob, brd );
2329
2330 if( !PATHS::EnsurePathExists( outPath ) )
2331 {
2332 m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
2334 }
2335
2336 std::unique_ptr<GENDRILL_WRITER_BASE> drillWriter;
2337
2339 drillWriter = std::make_unique<EXCELLON_WRITER>( brd );
2340 else
2341 drillWriter = std::make_unique<GERBER_WRITER>( brd );
2342
2343 VECTOR2I offset;
2344
2346 offset = VECTOR2I( 0, 0 );
2347 else
2348 offset = brd->GetDesignSettings().GetAuxOrigin();
2349
2350 PLOT_FORMAT mapFormat = PLOT_FORMAT::PDF;
2351
2352 switch( aDrillJob->m_mapFormat )
2353 {
2358 default:
2360 }
2361
2362
2363 if( aDrillJob->m_generateReport && aDrillJob->m_reportPath.IsEmpty() )
2364 {
2365 wxFileName fn = outPath;
2366 fn.SetFullName( brd->GetFileName() );
2367 fn.SetName( fn.GetName() + "-drill" );
2368 fn.SetExt( FILEEXT::ReportFileExtension );
2369
2370 aDrillJob->m_reportPath = fn.GetFullPath();
2371 }
2372
2374 {
2376
2377 switch( aDrillJob->m_zeroFormat )
2378 {
2380
2382
2384
2386 default: zeroFmt = EXCELLON_WRITER::DECIMAL_FORMAT; break;
2387 }
2388
2389 DRILL_PRECISION precision;
2390
2392 precision = precisionListForInches;
2393 else
2394 precision = precisionListForMetric;
2395
2396 EXCELLON_WRITER* excellonWriter = dynamic_cast<EXCELLON_WRITER*>( drillWriter.get() );
2397
2398 if( excellonWriter == nullptr )
2400
2401 excellonWriter->SetFormat( aDrillJob->m_drillUnits == JOB_EXPORT_PCB_DRILL::DRILL_UNITS::MM, zeroFmt,
2402 precision.m_Lhs, precision.m_Rhs );
2403 excellonWriter->SetOptions( aDrillJob->m_excellonMirrorY, aDrillJob->m_excellonMinimalHeader, offset,
2404 aDrillJob->m_excellonCombinePTHNPTH );
2405 excellonWriter->SetRouteModeForOvalHoles( aDrillJob->m_excellonOvalDrillRoute );
2406 excellonWriter->SetMapFileFormat( mapFormat );
2407
2408 if( !excellonWriter->CreateDrillandMapFilesSet( outPath, true, aDrillJob->m_generateMap, m_reporter ) )
2409 {
2411 }
2412
2413 for( const wxString& outputFile : drillWriter->GetCreatedFiles() )
2414 aDrillJob->AddOutput( outputFile );
2415
2416 if( aDrillJob->m_generateReport )
2417 {
2418 wxString reportPath = aDrillJob->ResolveOutputPath( aDrillJob->m_reportPath, true, brd->GetProject() );
2419
2420 if( !excellonWriter->GenDrillReportFile( reportPath ) )
2421 {
2423 }
2424
2425 aDrillJob->AddOutput( reportPath );
2426 }
2427 }
2429 {
2430 GERBER_WRITER* gerberWriter = dynamic_cast<GERBER_WRITER*>( drillWriter.get() );
2431
2432 if( gerberWriter == nullptr )
2434
2435 // Set gerber precision: only 5 or 6 digits for mantissa are allowed
2436 // (SetFormat() accept 5 or 6, and any other value set the precision to 5)
2437 // the integer part precision is always 4, and units always mm
2438 gerberWriter->SetFormat( aDrillJob->m_gerberPrecision );
2439 gerberWriter->SetOptions( offset );
2440 gerberWriter->SetMapFileFormat( mapFormat );
2441
2442 if( !gerberWriter->CreateDrillandMapFilesSet( outPath, true, aDrillJob->m_generateMap,
2443 aDrillJob->m_generateTenting, m_reporter ) )
2444 {
2446 }
2447
2448 for( const wxString& outputFile : drillWriter->GetCreatedFiles() )
2449 aDrillJob->AddOutput( outputFile );
2450
2451 if( aDrillJob->m_generateReport )
2452 {
2453 wxString reportPath = aDrillJob->ResolveOutputPath( aDrillJob->m_reportPath, true, brd->GetProject() );
2454
2455 if( !gerberWriter->GenDrillReportFile( reportPath ) )
2456 {
2458 }
2459
2460 aDrillJob->AddOutput( reportPath );
2461 }
2462 }
2463
2464 return CLI::EXIT_CODES::OK;
2465}
2466
2467
2469{
2470 JOB_EXPORT_PCB_POS* aPosJob = dynamic_cast<JOB_EXPORT_PCB_POS*>( aJob );
2471
2472 if( aPosJob == nullptr )
2474
2475 BOARD* brd = getBoard( aPosJob->m_filename );
2476
2477 if( !brd )
2479
2480 if( aPosJob->GetConfiguredOutputPath().IsEmpty() )
2481 {
2482 wxFileName fn = brd->GetFileName();
2483 fn.SetName( fn.GetName() );
2484
2487 else if( aPosJob->m_format == JOB_EXPORT_PCB_POS::FORMAT::CSV )
2488 fn.SetExt( FILEEXT::CsvFileExtension );
2489 else if( aPosJob->m_format == JOB_EXPORT_PCB_POS::FORMAT::GERBER )
2490 fn.SetExt( FILEEXT::GerberFileExtension );
2491
2492 aPosJob->SetWorkingOutputPath( fn.GetFullName() );
2493 }
2494
2495 wxString outPath = resolveJobOutputPath( aJob, brd );
2496
2497 if( !PATHS::EnsurePathExists( outPath, true ) )
2498 {
2499 m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
2501 }
2502
2504 {
2505 wxFileName fn( outPath );
2506 wxString baseName = fn.GetName();
2507
2508 auto exportPlaceFile = [&]( bool frontSide, bool backSide, const wxString& curr_outPath ) -> bool
2509 {
2510 FILE* file = wxFopen( curr_outPath, wxS( "wt" ) );
2511 wxCHECK( file, false );
2512
2513 PLACE_FILE_EXPORTER exporter( brd, aPosJob->m_units == JOB_EXPORT_PCB_POS::UNITS::MM, aPosJob->m_smdOnly,
2514 aPosJob->m_excludeFootprintsWithTh, aPosJob->m_excludeDNP,
2515 aPosJob->m_excludeBOM, frontSide, backSide,
2517 aPosJob->m_useDrillPlaceFileOrigin, aPosJob->m_negateBottomX );
2518
2519 // Set variant for variant-aware DNP/BOM/position file filtering
2520 exporter.SetVariant( aPosJob->m_variant );
2521
2522 std::string data = exporter.GenPositionData();
2523 fputs( data.c_str(), file );
2524 fclose( file );
2525
2526 return true;
2527 };
2528
2529 if( aPosJob->m_side == JOB_EXPORT_PCB_POS::SIDE::BOTH && !aPosJob->m_singleFile )
2530 {
2531 fn.SetName( PLACE_FILE_EXPORTER::DecorateFilename( baseName, true, false ) );
2532
2533 if( aPosJob->m_format == JOB_EXPORT_PCB_POS::FORMAT::CSV && !aPosJob->m_nakedFilename )
2534 fn.SetName( fn.GetName() + wxT( "-" ) + FILEEXT::FootprintPlaceFileExtension );
2535
2536 if( exportPlaceFile( true, false, fn.GetFullPath() ) )
2537 {
2538 m_reporter->Report( wxString::Format( _( "Wrote front position data to '%s'.\n" ), fn.GetFullPath() ),
2540
2541 aPosJob->AddOutput( fn.GetFullPath() );
2542 }
2543 else
2544 {
2546 }
2547
2548 fn.SetName( PLACE_FILE_EXPORTER::DecorateFilename( baseName, false, true ) );
2549
2550 if( aPosJob->m_format == JOB_EXPORT_PCB_POS::FORMAT::CSV && !aPosJob->m_nakedFilename )
2551 fn.SetName( fn.GetName() + wxT( "-" ) + FILEEXT::FootprintPlaceFileExtension );
2552
2553 if( exportPlaceFile( false, true, fn.GetFullPath() ) )
2554 {
2555 m_reporter->Report( wxString::Format( _( "Wrote back position data to '%s'.\n" ), fn.GetFullPath() ),
2557
2558 aPosJob->AddOutput( fn.GetFullPath() );
2559 }
2560 else
2561 {
2563 }
2564 }
2565 else
2566 {
2567 bool front = aPosJob->m_side == JOB_EXPORT_PCB_POS::SIDE::FRONT
2569
2570 bool back = aPosJob->m_side == JOB_EXPORT_PCB_POS::SIDE::BACK
2572
2573 if( !aPosJob->m_nakedFilename )
2574 {
2575 fn.SetName( PLACE_FILE_EXPORTER::DecorateFilename( fn.GetName(), front, back ) );
2576
2578 fn.SetName( fn.GetName() + wxT( "-" ) + FILEEXT::FootprintPlaceFileExtension );
2579 }
2580
2581 if( exportPlaceFile( front, back, fn.GetFullPath() ) )
2582 {
2583 m_reporter->Report( wxString::Format( _( "Wrote position data to '%s'.\n" ), fn.GetFullPath() ),
2585
2586 aPosJob->AddOutput( fn.GetFullPath() );
2587 }
2588 else
2589 {
2591 }
2592 }
2593 }
2594 else if( aPosJob->m_format == JOB_EXPORT_PCB_POS::FORMAT::GERBER )
2595 {
2596 PLACEFILE_GERBER_WRITER exporter( brd );
2597
2598 // Set variant for variant-aware DNP/BOM/position file filtering
2599 exporter.SetVariant( aPosJob->m_variant );
2600
2601 PCB_LAYER_ID gbrLayer = F_Cu;
2602 wxString outPath_base = outPath;
2603
2605 {
2606 if( aPosJob->m_side == JOB_EXPORT_PCB_POS::SIDE::BOTH || !aPosJob->m_nakedFilename )
2607 outPath = exporter.GetPlaceFileName( outPath, gbrLayer );
2608
2609 if( exporter.CreatePlaceFile( outPath, gbrLayer, aPosJob->m_gerberBoardEdge, aPosJob->m_excludeDNP,
2610 aPosJob->m_excludeBOM )
2611 >= 0 )
2612 {
2613 m_reporter->Report( wxString::Format( _( "Wrote front position data to '%s'.\n" ), outPath ),
2615
2616 aPosJob->AddOutput( outPath );
2617 }
2618 else
2619 {
2621 }
2622 }
2623
2625 {
2626 gbrLayer = B_Cu;
2627
2628 outPath = outPath_base;
2629
2630 if( aPosJob->m_side == JOB_EXPORT_PCB_POS::SIDE::BOTH || !aPosJob->m_nakedFilename )
2631 outPath = exporter.GetPlaceFileName( outPath, gbrLayer );
2632
2633 if( exporter.CreatePlaceFile( outPath, gbrLayer, aPosJob->m_gerberBoardEdge, aPosJob->m_excludeDNP,
2634 aPosJob->m_excludeBOM )
2635 >= 0 )
2636 {
2637 m_reporter->Report( wxString::Format( _( "Wrote back position data to '%s'.\n" ), outPath ),
2639
2640 aPosJob->AddOutput( outPath );
2641 }
2642 else
2643 {
2645 }
2646 }
2647 }
2648
2649 return CLI::EXIT_CODES::OK;
2650}
2651
2652
2654{
2655 JOB_FP_UPGRADE* upgradeJob = dynamic_cast<JOB_FP_UPGRADE*>( aJob );
2656
2657 if( upgradeJob == nullptr )
2659
2661
2662 if( !upgradeJob->m_outputLibraryPath.IsEmpty() )
2663 {
2664 if( wxFile::Exists( upgradeJob->m_outputLibraryPath ) || wxDir::Exists( upgradeJob->m_outputLibraryPath ) )
2665 {
2666 m_reporter->Report( _( "Output path must not conflict with existing path\n" ), RPT_SEVERITY_ERROR );
2668 }
2669 }
2670 else if( fileType != PCB_IO_MGR::KICAD_SEXP )
2671 {
2672 m_reporter->Report( _( "Output path must be specified to convert legacy and non-KiCad libraries\n" ),
2674
2676 }
2677
2679 {
2680 if( !wxDir::Exists( upgradeJob->m_libraryPath ) )
2681 {
2682 m_reporter->Report( _( "Footprint library path does not exist or is not accessible\n" ),
2685 }
2686
2688 FP_CACHE fpLib( &pcb_io, upgradeJob->m_libraryPath );
2689
2690 try
2691 {
2692 fpLib.Load();
2693 }
2694 catch( ... )
2695 {
2696 m_reporter->Report( _( "Unable to load library\n" ), RPT_SEVERITY_ERROR );
2698 }
2699
2700 if( m_progressReporter )
2701 m_progressReporter->KeepRefreshing();
2702
2703 bool shouldSave = upgradeJob->m_force;
2704
2705 for( const auto& footprint : fpLib.GetFootprints() )
2706 {
2707 if( footprint.second->GetFootprint()->GetFileFormatVersionAtLoad() < SEXPR_BOARD_FILE_VERSION )
2708 shouldSave = true;
2709 }
2710
2711 if( shouldSave )
2712 {
2713 try
2714 {
2715 if( !upgradeJob->m_outputLibraryPath.IsEmpty() )
2716 fpLib.SetPath( upgradeJob->m_outputLibraryPath );
2717
2718 fpLib.Save();
2719 }
2720 catch( ... )
2721 {
2722 m_reporter->Report( _( "Unable to save library\n" ), RPT_SEVERITY_ERROR );
2724 }
2725 }
2726 else
2727 {
2728 m_reporter->Report( _( "Footprint library was not updated\n" ), RPT_SEVERITY_ERROR );
2729 }
2730 }
2731 else
2732 {
2733 if( !PCB_IO_MGR::ConvertLibrary( {}, upgradeJob->m_libraryPath, upgradeJob->m_outputLibraryPath,
2734 nullptr /* REPORTER */ ) )
2735 {
2736 m_reporter->Report( ( "Unable to convert library\n" ), RPT_SEVERITY_ERROR );
2738 }
2739 }
2740
2741 return CLI::EXIT_CODES::OK;
2742}
2743
2744
2746{
2747 JOB_FP_EXPORT_SVG* svgJob = dynamic_cast<JOB_FP_EXPORT_SVG*>( aJob );
2748
2749 if( svgJob == nullptr )
2751
2753 FP_CACHE fpLib( &pcb_io, svgJob->m_libraryPath );
2754
2755 if( svgJob->m_argLayers )
2756 {
2757 if( !svgJob->m_argLayers.value().empty() )
2758 svgJob->m_plotLayerSequence = convertLayerArg( svgJob->m_argLayers.value(), nullptr );
2759 else
2761 }
2762
2763 try
2764 {
2765 fpLib.Load();
2766 }
2767 catch( ... )
2768 {
2769 m_reporter->Report( _( "Unable to load library\n" ), RPT_SEVERITY_ERROR );
2771 }
2772
2773 wxString outPath = svgJob->GetFullOutputPath( nullptr );
2774
2775 if( !PATHS::EnsurePathExists( outPath, true ) )
2776 {
2777 m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
2779 }
2780
2781 int exitCode = CLI::EXIT_CODES::OK;
2782 bool singleFpPlotted = false;
2783
2784 for( const auto& [fpName, fpCacheEntry] : fpLib.GetFootprints() )
2785 {
2786 if( m_progressReporter )
2787 {
2788 m_progressReporter->AdvancePhase( wxString::Format( _( "Exporting %s" ), fpName ) );
2789 m_progressReporter->KeepRefreshing();
2790 }
2791
2792 if( !svgJob->m_footprint.IsEmpty() )
2793 {
2794 // skip until we find the right footprint
2795 if( fpName != svgJob->m_footprint )
2796 continue;
2797 else
2798 singleFpPlotted = true;
2799 }
2800
2801 exitCode = doFpExportSvg( svgJob, fpCacheEntry->GetFootprint().get() );
2802
2803 if( exitCode != CLI::EXIT_CODES::OK )
2804 break;
2805 }
2806
2807 if( !svgJob->m_footprint.IsEmpty() && !singleFpPlotted )
2808 {
2809 m_reporter->Report( _( "The given footprint could not be found to export." ) + wxS( "\n" ),
2811 }
2812
2813 return CLI::EXIT_CODES::OK;
2814}
2815
2816
2818{
2819 wxFileName outputFile;
2820 outputFile.SetPath( aSvgJob->GetFullOutputPath( nullptr ) );
2821 outputFile.SetName( aFootprint->GetFPID().GetLibItemName().wx_str() );
2822 outputFile.SetExt( FILEEXT::SVGFileExtension );
2823
2824 m_reporter->Report( wxString::Format( _( "Plotting footprint '%s' to '%s'\n" ),
2825 aFootprint->GetFPID().GetLibItemName().wx_str(), outputFile.GetFullPath() ),
2827
2828 PCB_PLOT_PARAMS plotOpts;
2829 PCB_PLOTTER::PlotJobToPlotOpts( plotOpts, aSvgJob, *m_reporter );
2830
2831 if( !PlotFootprintToSVG( *aFootprint, *Pgm().GetSettingsManager().GetProject( "" ), &aSvgJob->GetVarOverrides(),
2832 plotOpts, aSvgJob->m_plotLayerSequence, aSvgJob->m_plotOnAllLayersSequence,
2833 outputFile.GetFullPath(), m_reporter ) )
2834 {
2835 m_reporter->Report( _( "Error creating svg file" ) + wxS( "\n" ), RPT_SEVERITY_ERROR );
2837 }
2838
2839 aSvgJob->AddOutput( outputFile.GetFullPath() );
2840
2841 return CLI::EXIT_CODES::OK;
2842}
2843
2844
2846{
2847 JOB_PCB_DRC* drcJob = dynamic_cast<JOB_PCB_DRC*>( aJob );
2848
2849 if( drcJob == nullptr )
2851
2852 BOARD* brd = getBoard( drcJob->m_filename );
2853
2854 if( !brd )
2856
2857 // Running DRC requires libraries be loaded, so make sure they have been
2859 adapter->AsyncLoad();
2860 adapter->BlockUntilLoaded();
2861
2862 if( drcJob->GetConfiguredOutputPath().IsEmpty() )
2863 {
2864 wxFileName fn = brd->GetFileName();
2865 fn.SetName( fn.GetName() + wxS( "-drc" ) );
2866
2868 fn.SetExt( FILEEXT::JsonFileExtension );
2869 else
2870 fn.SetExt( FILEEXT::ReportFileExtension );
2871
2872 drcJob->SetWorkingOutputPath( fn.GetFullName() );
2873 }
2874
2875 wxString outPath = resolveJobOutputPath( aJob, brd );
2876
2877 if( !PATHS::EnsurePathExists( outPath, true ) )
2878 {
2879 m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
2881 }
2882
2883 EDA_UNITS units;
2884
2885 switch( drcJob->m_units )
2886 {
2887 case JOB_PCB_DRC::UNITS::INCH: units = EDA_UNITS::INCH; break;
2888 case JOB_PCB_DRC::UNITS::MILS: units = EDA_UNITS::MILS; break;
2889 case JOB_PCB_DRC::UNITS::MM: units = EDA_UNITS::MM; break;
2890 default: units = EDA_UNITS::MM; break;
2891 }
2892
2893 std::shared_ptr<DRC_ENGINE> drcEngine = brd->GetDesignSettings().m_DRCEngine;
2894 std::unique_ptr<NETLIST> netlist = std::make_unique<NETLIST>();
2895
2896 if( !drcEngine->RulesValid() )
2897 {
2898 try
2899 {
2900 drcEngine->InitEngine( brd->GetDesignRulesPath() );
2901 }
2902 catch( const PARSE_ERROR& error )
2903 {
2904 m_reporter->Report( _( "DRC incomplete: could not compile custom design rules." )
2905 + wxS( "\n" ) + error.What() + wxS( "\n" ),
2908 }
2909 }
2910
2911 drcEngine->SetDrawingSheet( getDrawingSheetProxyView( brd ) );
2912
2913 // BOARD_COMMIT uses TOOL_MANAGER to grab the board internally so we must give it one
2914 TOOL_MANAGER* toolManager = getToolManager( brd );
2915
2916 BOARD_COMMIT commit( toolManager );
2917 bool checkParity = drcJob->m_parity;
2918 std::string netlist_str;
2919
2920 if( checkParity )
2921 {
2922 wxString annotateMsg = _( "Schematic parity tests require a fully annotated schematic." );
2923 netlist_str = annotateMsg;
2924
2925 // The KIFACE_NETLIST_SCHEMATIC function has some broken-ness that the schematic
2926 // frame's version does not, but it is the only one that works in CLI, so we use it
2927 // if we don't have the sch frame open.
2928 // TODO: clean this up, see https://gitlab.com/kicad/code/kicad/-/issues/19929
2929 if( m_kiway->Player( FRAME_SCH, false ) )
2930 {
2931 m_kiway->ExpressMail( FRAME_SCH, MAIL_SCH_GET_NETLIST, netlist_str );
2932 }
2933 else
2934 {
2935 wxFileName schematicPath( drcJob->m_filename );
2936 schematicPath.MakeAbsolute();
2937 schematicPath.SetExt( FILEEXT::KiCadSchematicFileExtension );
2938
2939 if( !schematicPath.Exists() )
2940 schematicPath.SetExt( FILEEXT::LegacySchematicFileExtension );
2941
2942 if( !schematicPath.Exists() )
2943 {
2944 m_reporter->Report( _( "Failed to fetch schematic netlist for parity tests.\n" ), RPT_SEVERITY_ERROR );
2945 checkParity = false;
2946 }
2947 else
2948 {
2949 typedef bool ( *NETLIST_FN_PTR )( const wxString&, std::string&, KIWAY* );
2950 KIFACE* eeschema = m_kiway->KiFACE( KIWAY::FACE_SCH );
2951 NETLIST_FN_PTR netlister = (NETLIST_FN_PTR) eeschema->IfaceOrAddress( KIFACE_NETLIST_SCHEMATIC );
2952 ( *netlister )( schematicPath.GetFullPath(), netlist_str, m_kiway );
2953 }
2954 }
2955
2956 if( netlist_str == MAIL_SCH_GET_NETLIST_CANCELLED )
2957 {
2958 checkParity = false;
2959 }
2960 else if( netlist_str == annotateMsg )
2961 {
2962 m_reporter->Report( annotateMsg + wxT( "\n" ), RPT_SEVERITY_ERROR );
2963 checkParity = false;
2964 }
2965 }
2966
2967 if( checkParity )
2968 {
2969 try
2970 {
2971 STRING_LINE_READER* lineReader = new STRING_LINE_READER( netlist_str, _( "Eeschema netlist" ) );
2972 KICAD_NETLIST_READER netlistReader( lineReader, netlist.get() );
2973
2974 netlistReader.LoadNetlist();
2975 }
2976 catch( const IO_ERROR& )
2977 {
2978 m_reporter->Report( _( "Failed to fetch schematic netlist for parity tests.\n" ), RPT_SEVERITY_ERROR );
2979 checkParity = false;
2980 }
2981
2982 drcEngine->SetSchematicNetlist( netlist.get() );
2983 }
2984
2985 if( drcJob->m_refillZones )
2986 {
2987 if( !toolManager->FindTool( ZONE_FILLER_TOOL_NAME ) )
2988 toolManager->RegisterTool( new ZONE_FILLER_TOOL );
2989
2990 toolManager->GetTool<ZONE_FILLER_TOOL>()->FillAllZones( nullptr, m_progressReporter, true );
2991 }
2992
2993 drcEngine->SetProgressReporter( m_progressReporter );
2994 drcEngine->SetViolationHandler(
2995 [&]( const std::shared_ptr<DRC_ITEM>& aItem, const VECTOR2I& aPos, int aLayer,
2996 const std::function<void( PCB_MARKER* )>& aPathGenerator )
2997 {
2998 PCB_MARKER* marker = new PCB_MARKER( aItem, aPos, aLayer );
2999 aPathGenerator( marker );
3000 commit.Add( marker );
3001 } );
3002
3003 brd->RecordDRCExclusions();
3004 brd->DeleteMARKERs( true, true );
3005 drcEngine->RunTests( units, drcJob->m_reportAllTrackErrors, checkParity );
3006 drcEngine->ClearViolationHandler();
3007
3008 commit.Push( _( "DRC" ), SKIP_UNDO | SKIP_SET_DIRTY );
3009
3010 // Update the exclusion status on any excluded markers that still exist.
3011 brd->ResolveDRCExclusions( false );
3012
3013 std::shared_ptr<DRC_ITEMS_PROVIDER> markersProvider =
3014 std::make_shared<DRC_ITEMS_PROVIDER>( brd, MARKER_BASE::MARKER_DRC, MARKER_BASE::MARKER_DRAWING_SHEET );
3015
3016 std::shared_ptr<DRC_ITEMS_PROVIDER> ratsnestProvider =
3017 std::make_shared<DRC_ITEMS_PROVIDER>( brd, MARKER_BASE::MARKER_RATSNEST );
3018
3019 std::shared_ptr<DRC_ITEMS_PROVIDER> fpWarningsProvider =
3020 std::make_shared<DRC_ITEMS_PROVIDER>( brd, MARKER_BASE::MARKER_PARITY );
3021
3022 markersProvider->SetSeverities( drcJob->m_severity );
3023 ratsnestProvider->SetSeverities( drcJob->m_severity );
3024 fpWarningsProvider->SetSeverities( drcJob->m_severity );
3025
3026 m_reporter->Report( wxString::Format( _( "Found %d violations\n" ), markersProvider->GetCount() ),
3028 m_reporter->Report( wxString::Format( _( "Found %d unconnected items\n" ), ratsnestProvider->GetCount() ),
3030
3031 if( checkParity )
3032 {
3033 m_reporter->Report(
3034 wxString::Format( _( "Found %d schematic parity issues\n" ), fpWarningsProvider->GetCount() ),
3036 }
3037
3038 DRC_REPORT reportWriter( brd, units, markersProvider, ratsnestProvider, fpWarningsProvider );
3039
3040 bool wroteReport = false;
3041
3043 wroteReport = reportWriter.WriteJsonReport( outPath );
3044 else
3045 wroteReport = reportWriter.WriteTextReport( outPath );
3046
3047 if( !wroteReport )
3048 {
3049 m_reporter->Report( wxString::Format( _( "Unable to save DRC report to %s\n" ), outPath ), RPT_SEVERITY_ERROR );
3051 }
3052
3053 drcJob->AddOutput( outPath );
3054
3055 m_reporter->Report( wxString::Format( _( "Saved DRC Report to %s\n" ), outPath ), RPT_SEVERITY_ACTION );
3056
3057 if( drcJob->m_refillZones && drcJob->m_saveBoard )
3058 {
3059 if( BOARD_LOADER::SaveBoard( drcJob->m_filename, *brd ) )
3060 {
3061 m_reporter->Report( _( "Saved board\n" ), RPT_SEVERITY_ACTION );
3062 }
3063 else
3064 {
3065 m_reporter->Report( _( "Failed to save board.\n" ), RPT_SEVERITY_ERROR );
3066
3068 }
3069 }
3070
3071 if( drcJob->m_exitCodeViolations )
3072 {
3073 if( markersProvider->GetCount() > 0 || ratsnestProvider->GetCount() > 0 || fpWarningsProvider->GetCount() > 0 )
3074 {
3076 }
3077 }
3078
3080}
3081
3082
3084{
3085 if( !aJob.m_checkZonesBeforeExport )
3086 {
3087 return;
3088 }
3089
3090 TOOL_MANAGER* toolManager = getToolManager( aBoard );
3091
3092 if( !toolManager->FindTool( ZONE_FILLER_TOOL_NAME ) )
3093 {
3094 toolManager->RegisterTool( new ZONE_FILLER_TOOL );
3095 }
3096
3097 toolManager->GetTool<ZONE_FILLER_TOOL>()->FillAllZones( nullptr, m_progressReporter, true );
3098}
3099
3100
3102{
3103 JOB_EXPORT_PCB_IPC2581* job = dynamic_cast<JOB_EXPORT_PCB_IPC2581*>( aJob );
3104
3105 if( job == nullptr )
3107
3108 BOARD* brd = getBoard( job->m_filename );
3109
3110 if( !brd )
3112
3113 struct VARIANT_RESTORE
3114 {
3115 BOARD* m_board;
3116 wxString m_variant;
3117
3118 ~VARIANT_RESTORE() { m_board->SetCurrentVariant( m_variant ); }
3119 } restore{ brd, brd->GetCurrentVariant() };
3120
3121 if( !job->m_variantNames.empty() )
3122 brd->SetCurrentVariant( job->m_variantNames.front() );
3123
3124 if( job->GetConfiguredOutputPath().IsEmpty() )
3125 {
3126 wxFileName fn = brd->GetFileName();
3127 fn.SetExt( job->m_compress ? std::string( "zip" ) : FILEEXT::Ipc2581FileExtension );
3128
3129 job->SetWorkingOutputPath( fn.GetFullName() );
3130 }
3131
3132 wxString outPath = resolveJobOutputPath( aJob, brd );
3133
3134 if( !PATHS::EnsurePathExists( outPath, true ) )
3135 {
3136 m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
3138 }
3139
3140 refillFabZones( *job, brd );
3141
3144
3146}
3147
3148
3150{
3151 JOB_EXPORT_PCB_IPCD356* job = dynamic_cast<JOB_EXPORT_PCB_IPCD356*>( aJob );
3152
3153 if( job == nullptr )
3155
3156 BOARD* brd = getBoard( job->m_filename );
3157
3158 if( !brd )
3160
3161 if( job->GetConfiguredOutputPath().IsEmpty() )
3162 {
3163 wxFileName fn = brd->GetFileName();
3164 fn.SetName( fn.GetName() );
3165 fn.SetExt( FILEEXT::IpcD356FileExtension );
3166
3167 job->SetWorkingOutputPath( fn.GetFullName() );
3168 }
3169
3170 wxString outPath = resolveJobOutputPath( aJob, brd );
3171
3172 if( !PATHS::EnsurePathExists( outPath, true ) )
3173 {
3174 m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
3176 }
3177
3178 IPC356D_WRITER exporter( brd );
3179
3180 bool success = exporter.Write( outPath );
3181
3182 if( success )
3183 {
3184 aJob->AddOutput( outPath );
3185 m_reporter->Report( _( "Successfully created IPC-D-356 file\n" ), RPT_SEVERITY_INFO );
3187 }
3188 else
3189 {
3190 m_reporter->Report( _( "Failed to create IPC-D-356 file\n" ), RPT_SEVERITY_ERROR );
3192 }
3193}
3194
3195
3197{
3198 JOB_EXPORT_PCB_IDF* job = dynamic_cast<JOB_EXPORT_PCB_IDF*>( 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();
3212
3213 job->SetWorkingOutputPath( fn.GetFullName() );
3214 }
3215
3216 wxString outPath = resolveJobOutputPath( job, brd );
3217
3218 if( !PATHS::EnsurePathExists( outPath, true ) )
3219 {
3220 m_reporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
3222 }
3223
3225
3226 if( !resolver )
3227 {
3228 m_reporter->Report( _( "Internal error\n" ), RPT_SEVERITY_ERROR );
3230 }
3231
3232 IDF_EXPORTER exporter( brd, resolver, job, m_reporter );
3233
3234 if( !exporter.Export( outPath ) )
3236
3237 m_reporter->Report( _( "Successfully created IDFv3 files\n" ), RPT_SEVERITY_INFO );
3238
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->GetConfiguredOutputPath().IsEmpty() )
3256 {
3258 {
3259 // just basic folder name
3260 job->SetWorkingOutputPath( "odb" );
3261 }
3262 else
3263 {
3264 wxFileName fn( brd->GetFileName() );
3265 fn.SetName( fn.GetName() + wxS( "-odb" ) );
3266
3267 switch( job->m_compressionMode )
3268 {
3270
3271 case JOB_EXPORT_PCB_ODB::ODB_COMPRESSION::TGZ: fn.SetExt( "tgz" ); break;
3272
3273 default: break;
3274 };
3275
3276 job->SetWorkingOutputPath( fn.GetFullName() );
3277 }
3278 }
3279
3280 resolveJobOutputPath( job, brd );
3281
3282 // The helper handles output path creation, so hand it a job that already has fully-resolved
3283 // token context (title block and project overrides applied above).
3284 refillFabZones( *job, brd );
3285
3287
3288 if( result.m_ok )
3289 {
3290 for( const wxString& path : result.m_outputs )
3291 aJob->AddOutput( path );
3292 }
3293
3294 if( !result.m_ok || m_reporter->HasMessageOfSeverity( RPT_SEVERITY_ERROR ) )
3296
3298}
3299
3301{
3302 JOB_PCB_UPGRADE* job = dynamic_cast<JOB_PCB_UPGRADE*>( aJob );
3303
3304 if( job == nullptr )
3306
3307 bool shouldSave = job->m_force;
3308
3309 try
3310 {
3312 BOARD* brd = getBoard( job->m_filename );
3313
3314 if( !brd )
3316
3318 shouldSave = true;
3319
3320 // A chart is derived data, so the upgrade brings every one up to date and saves if
3321 // that changed anything
3322 if( RefreshGeneratedTables( *brd ) > 0 )
3323 shouldSave = true;
3324
3325 if( shouldSave )
3326 {
3327 pi->SaveBoard( brd->GetFileName(), *brd );
3328 m_reporter->Report( _( "Successfully saved board file using the latest format\n" ), RPT_SEVERITY_INFO );
3329 }
3330 else
3331 {
3332 m_reporter->Report( _( "Board file was not updated\n" ), RPT_SEVERITY_ERROR );
3333 }
3334 }
3335 catch( const IO_ERROR& ioe )
3336 {
3337 wxString msg =
3338 wxString::Format( _( "Error saving board file '%s'.\n%s" ), job->m_filename, ioe.What().GetData() );
3339 m_reporter->Report( msg, RPT_SEVERITY_ERROR );
3341 }
3342
3344}
3345
3346
3347// Classify library footprints so their losses appear in the report.
3348static void classifyFootprintLibraries( const wxString& aDir, const DOWNGRADE_TARGET& aTarget,
3349 COMPATIBILITY_REPORT& aReport, bool aDropInsteadOfApproximate )
3350{
3351 wxArrayString files;
3352 wxDir::GetAllFiles( aDir, &files, wxT( "*.kicad_mod" ) );
3353
3354 for( const wxString& file : files )
3355 {
3356 if( IsDowngradeWorkingArtifact( file ) )
3357 continue;
3358
3359 try
3360 {
3361 PCB_IO_KICAD_SEXPR loader;
3362 wxString nameOut;
3363 std::unique_ptr<FOOTPRINT> fp = loader.ImportFootprint( file, nameOut );
3364
3365 if( !fp )
3366 continue;
3367
3368 BOARD scratch;
3369 scratch.Add( fp.release() );
3370
3371 aReport.Merge( ClassifyBoardForDowngrade( &scratch, aTarget, aDropInsteadOfApproximate ) );
3372 }
3373 catch( const IO_ERROR& )
3374 {
3375 // A footprint that fails to load is caught later by the downgrade pass itself.
3376 }
3377 }
3378}
3379
3380
3381// Downgrade every .kicad_mod under the folder in place. Returns the first file the target could
3382// not open, or empty when all are safe.
3383static wxString downgradeFootprintLibraries( const wxString& aDir, const DOWNGRADE_TARGET& aTarget,
3384 bool aDropInsteadOfApproximate,
3385 DOWNGRADE_FILE_TRANSACTION* aTransaction = nullptr,
3386 wxString* aRecoveryError = nullptr )
3387{
3388 auto convert = [&]( const wxString& aFile, const wxString& aTmp ) -> DOWNGRADE_FILE_RESULT
3389 {
3390 return DowngradeFootprintFileToTemp( aFile, aTmp, aTarget, aDropInsteadOfApproximate );
3391 };
3392
3394 aDir, wxT( "*.kicad_mod" ), aTarget.m_boardVersion,
3395 [&]( const wxString& aText )
3396 {
3397 return FindUnsupportedBoardToken( aText, aTarget );
3398 },
3399 convert, aTransaction, aRecoveryError );
3400}
3401
3402
3403static wxString footprintRefusedMessage( const wxString& aFile, const DOWNGRADE_TARGET& aTarget )
3404{
3405 return wxString::Format( _( "Cannot export: footprint '%s' uses a feature %s cannot open.\n" ), aFile,
3406 aTarget.m_name );
3407}
3408
3409
3411{
3412 JOB_PCB_DOWNGRADE* job = dynamic_cast<JOB_PCB_DOWNGRADE*>( aJob );
3413
3414 if( job == nullptr )
3416
3417 const DOWNGRADE_TARGET* target =
3418 job->m_target.IsEmpty() ? &GetDowngradeTargets().front() : FindDowngradeTarget( job->m_target );
3419
3420 if( target == nullptr )
3421 {
3422 m_reporter->Report( wxString::Format( _( "Unknown target version '%s'. Valid targets: %s\n" ), job->m_target,
3426 }
3427
3428 if( !job->m_libraryDir.IsEmpty() && !wxDirExists( job->m_libraryDir ) )
3429 {
3430 m_reporter->Report( wxString::Format( _( "The library folder '%s' does not exist.\n" ), job->m_libraryDir ),
3433 }
3434
3435 if( !job->m_variant.IsEmpty() && target->m_boardVersion >= FIRST_PCB_VARIANTS )
3436 {
3437 m_reporter->Report( _( "The target supports variants, so there is nothing to flatten.\n" ),
3440 }
3441
3442 wxString pathError;
3443 wxString libraryCopy;
3444
3445 if( !job->m_libraryDir.IsEmpty() && !ValidateDowngradeLibrarySource( job->m_libraryDir, pathError ) )
3446 {
3447 m_reporter->Report( pathError + wxT( "\n" ), RPT_SEVERITY_ERROR );
3449 }
3450
3451 if( !job->m_inPlace && !job->m_dryRun )
3452 {
3453 wxFileName output( job->m_outputFile );
3454
3455 if( !job->m_outputFile.IsEmpty() && output.MakeAbsolute() )
3456 job->m_outputFile = output.GetFullPath();
3457
3458 if( !ValidateFreshDowngradeOutput( job->m_outputFile, pathError ) )
3459 {
3460 m_reporter->Report( pathError + wxT( "\n" ), RPT_SEVERITY_ERROR );
3462 }
3463
3464 if( !job->m_libraryDir.IsEmpty() )
3465 {
3466 libraryCopy = DowngradeLibraryCopyPath( job->m_libraryDir, output.GetPath() );
3467
3468 if( libraryCopy.IsEmpty() || !ValidateDowngradeLibraryCopy( job->m_libraryDir, libraryCopy, pathError ) )
3469 {
3470 m_reporter->Report( pathError + wxT( "\n" ), RPT_SEVERITY_ERROR );
3472 }
3473 }
3474 }
3475
3476 try
3477 {
3478 if( !job->m_filename.IsEmpty() )
3479 {
3480 // Load a private copy, never the live editor board.
3481 PCB_IO_KICAD_SEXPR loader;
3482 std::unique_ptr<BOARD> brd( loader.LoadBoard( job->m_filename, nullptr ) );
3483
3484 if( !brd )
3486
3487 if( !job->m_variant.IsEmpty() )
3488 {
3489 const std::vector<wxString>& names = brd->GetVariantNames();
3490
3491 if( names.empty() )
3492 {
3493 // A board only learns variants from the netlist updater. Without any, the
3494 // flatten is vacuous, so a project export must not fail here after the
3495 // schematic side accepted the name.
3496 m_reporter->Report( _( "The board has no variant data. The base board is exported.\n" ),
3498 }
3499 else if( std::find( names.begin(), names.end(), job->m_variant ) == names.end() )
3500 {
3501 m_reporter->Report(
3502 wxString::Format( _( "The board has no variant named '%s'.\n" ), job->m_variant ),
3505 }
3506 else
3507 {
3508 FlattenBoardVariant( brd.get(), job->m_variant );
3509 }
3510 }
3511
3512 COMPATIBILITY_REPORT report =
3513 ClassifyBoardForDowngrade( brd.get(), *target, job->m_dropInsteadOfApproximate );
3514
3515 // Library losses belong in the report the user approves too.
3516 if( !job->m_libraryDir.IsEmpty() )
3518
3519 job->m_report = report;
3520
3521 report.Print( m_reporter );
3522
3523 if( job->m_dryRun )
3525
3526 if( report.IsBlocked() )
3527 {
3528 m_reporter->Report( _( "Cannot export: the board uses features the target cannot represent.\n" ),
3531 }
3532
3533 if( report.IsLossy() && !job->m_force )
3534 {
3535 m_reporter->Report( _( "Export would lose data. Re-run with --force to export anyway.\n" ),
3538 }
3539
3540 DOWNGRADE_OUTPUT_DIRECTORIES outputDirectories;
3541 wxString libraryDir = job->m_libraryDir;
3542
3543 if( !job->m_inPlace )
3544 {
3545 wxFileName output( job->m_outputFile );
3546 output.MakeAbsolute();
3547
3548 if( !outputDirectories.Create( output.GetPath() ) )
3549 {
3550 m_reporter->Report( _( "Could not create the output folder.\n" ), RPT_SEVERITY_ERROR );
3552 }
3553
3554 if( !libraryDir.IsEmpty() )
3555 {
3556 if( !CopyDowngradeLibraryDirectory( libraryDir, libraryCopy, outputDirectories, pathError ) )
3557 {
3558 m_reporter->Report( pathError + wxT( "\n" ), RPT_SEVERITY_ERROR );
3560 }
3561
3562 libraryDir = libraryCopy;
3563 }
3564 }
3565
3566 DOWNGRADE_FILE_TRANSACTION transaction;
3567 wxString stagedBoard = transaction.Stage( job->m_outputFile );
3568
3569 if( stagedBoard.IsEmpty() )
3570 {
3571 m_reporter->Report( _( "Could not export the downgraded board.\n" ), RPT_SEVERITY_ERROR );
3573 }
3574
3575 // The real export loads its own fresh copy and fails closed on any uncovered token.
3576 wxString badToken;
3577
3578 if( !ExportBoardToOlderVersion( job->m_filename, stagedBoard, *target, report, &badToken, job->m_variant,
3580 {
3581 if( !badToken.IsEmpty() )
3582 m_reporter->Report( wxString::Format( _( "Cannot export: the result still uses '%s', which %s "
3583 "cannot open. This feature has no downgrade rule yet.\n" ),
3584 badToken, target->m_name ),
3586 else
3587 m_reporter->Report( _( "Could not export the downgraded board.\n" ), RPT_SEVERITY_ERROR );
3588
3590 }
3591
3592 if( !job->m_libraryDir.IsEmpty() )
3593 {
3594 wxString badLib = downgradeFootprintLibraries( libraryDir, *target, job->m_dropInsteadOfApproximate,
3595 &transaction );
3596
3597 if( !badLib.IsEmpty() )
3598 {
3599 m_reporter->Report( footprintRefusedMessage( badLib, *target ), RPT_SEVERITY_ERROR );
3601 }
3602 }
3603
3604 wxString failedDestination = transaction.Commit();
3605
3606 if( !failedDestination.IsEmpty() )
3607 {
3608 m_reporter->Report( wxString::Format( _( "Could not replace '%s'.\n" ), failedDestination ),
3610
3611 if( !transaction.GetRecoveryError().IsEmpty() )
3612 m_reporter->Report( transaction.GetRecoveryError(), RPT_SEVERITY_ERROR );
3613
3615 }
3616
3617 outputDirectories.Commit();
3618 m_reporter->Report( wxString::Format( _( "Saved downgraded board to '%s'\n" ), job->m_outputFile ),
3620 }
3621 // Library-only mode. Fails closed if any library still uses a token the target cannot open.
3622 else if( !job->m_libraryDir.IsEmpty() )
3623 {
3624 if( !job->m_inPlace && !job->m_dryRun )
3625 {
3626 m_reporter->Report( _( "A board input is required for a library copy export.\n" ), RPT_SEVERITY_ERROR );
3628 }
3629
3630 if( !job->m_variant.IsEmpty() )
3631 {
3632 m_reporter->Report( _( "Variants do not apply to a library downgrade.\n" ), RPT_SEVERITY_ERROR );
3634 }
3635
3636 COMPATIBILITY_REPORT report;
3638 job->m_report = report;
3639 report.Print( m_reporter );
3640
3641 if( job->m_dryRun )
3643
3644 if( report.IsBlocked() )
3645 {
3646 m_reporter->Report( _( "Cannot export: the libraries use features the target cannot represent.\n" ),
3649 }
3650
3651 if( report.IsLossy() && !job->m_force )
3652 {
3653 m_reporter->Report( _( "Export would lose data. Re-run with --force to export anyway.\n" ),
3656 }
3657
3658 wxString recoveryError;
3659 wxString badLib = downgradeFootprintLibraries( job->m_libraryDir, *target, job->m_dropInsteadOfApproximate,
3660 nullptr, &recoveryError );
3661
3662 if( !badLib.IsEmpty() )
3663 {
3664 m_reporter->Report( footprintRefusedMessage( badLib, *target ), RPT_SEVERITY_ERROR );
3665
3666 if( !recoveryError.IsEmpty() )
3667 m_reporter->Report( recoveryError, RPT_SEVERITY_ERROR );
3668
3670 }
3671
3672 m_reporter->Report( _( "Downgraded footprint libraries\n" ), RPT_SEVERITY_INFO );
3673 }
3674 }
3675 catch( const IO_ERROR& ioe )
3676 {
3677 wxString where = job->m_filename.IsEmpty() ? _( "the footprint libraries" ) : job->m_filename;
3678
3679 m_reporter->Report( wxString::Format( _( "Error exporting '%s'.\n%s" ), where, ioe.What().GetData() ),
3682 }
3683
3685}
3686
3687
3688// Most job handlers need to align the running job with the board before resolving any
3689// output paths with variables in them like ${REVISION}.
3690wxString PCBNEW_JOBS_HANDLER::resolveJobOutputPath( JOB* aJob, BOARD* aBoard, const wxString* aDrawingSheet )
3691{
3692 aJob->SetTitleBlock( aBoard->GetTitleBlock() );
3693
3694 if( aDrawingSheet && !aDrawingSheet->IsEmpty() )
3695 loadOverrideDrawingSheet( aBoard, *aDrawingSheet );
3696
3697 PROJECT* project = aBoard->GetProject();
3698
3699 if( project )
3700 project->ApplyTextVars( aJob->GetVarOverrides() );
3701
3702 aBoard->SynchronizeProperties();
3703
3704 return aJob->GetFullOutputPath( project );
3705}
3706
3707
3709{
3710 DS_PROXY_VIEW_ITEM* drawingSheet = new DS_PROXY_VIEW_ITEM( pcbIUScale, &aBrd->GetPageSettings(), aBrd->GetProject(),
3711 &aBrd->GetTitleBlock(), &aBrd->GetProperties() );
3712
3713 drawingSheet->SetSheetName( std::string() );
3714 drawingSheet->SetSheetPath( std::string() );
3715 drawingSheet->SetIsFirstPage( true );
3716
3717 drawingSheet->SetFileName( TO_UTF8( aBrd->GetFileName() ) );
3718
3719 wxString currentVariant = aBrd->GetCurrentVariant();
3720 wxString variantDesc = aBrd->GetVariantDescription( currentVariant );
3721 drawingSheet->SetVariantName( TO_UTF8( currentVariant ) );
3722 drawingSheet->SetVariantDesc( TO_UTF8( variantDesc ) );
3723
3724 return drawingSheet;
3725}
3726
3727
3728void PCBNEW_JOBS_HANDLER::loadOverrideDrawingSheet( BOARD* aBrd, const wxString& aSheetPath )
3729{
3730 // dont bother attempting to load a empty path, if there was one
3731 if( aSheetPath.IsEmpty() )
3732 return;
3733
3734 auto loadSheet = [&]( const wxString& path ) -> bool
3735 {
3737 FILENAME_RESOLVER resolver;
3738 resolver.SetProject( aBrd->GetProject() );
3739 resolver.SetProgramBase( &Pgm() );
3740
3741 wxString filename = resolver.ResolvePath( BASE_SCREEN::m_DrawingSheetFileName,
3742 aBrd->GetProject()->GetProjectPath(), { aBrd->GetEmbeddedFiles() } );
3743 wxString msg;
3744
3745 if( !DS_DATA_MODEL::GetTheInstance().LoadDrawingSheet( filename, &msg ) )
3746 {
3747 m_reporter->Report( wxString::Format( _( "Error loading drawing sheet '%s'." ), path ) + wxS( "\n" ) + msg
3748 + wxS( "\n" ),
3750 return false;
3751 }
3752
3753 return true;
3754 };
3755
3756 if( loadSheet( aSheetPath ) )
3757 return;
3758
3759 // failed loading custom path, revert back to default
3760 loadSheet( aBrd->GetProject()->GetProjectFile().m_BoardDrawingSheetFile );
3761}
3762
3763
3764// Resolve a KiCad layer name (canonical board-file name such as "F.Cu", or the GUI display name)
3765// to its layer id. Returns UNDEFINED_LAYER when no layer matches.
3766static PCB_LAYER_ID resolveKiCadLayerName( const wxString& aName )
3767{
3768 for( PCB_LAYER_ID layer : LSET::AllLayersMask().Seq() )
3769 {
3770 if( LSET::Name( layer ) == aName || LayerName( layer ) == aName )
3771 return layer;
3772 }
3773
3774 return UNDEFINED_LAYER;
3775}
3776
3777
3779{
3780 JOB_PCB_IMPORT* job = dynamic_cast<JOB_PCB_IMPORT*>( aJob );
3781
3782 if( !job )
3784
3785 // Check that input file exists
3786 if( !wxFile::Exists( job->m_inputFile ) )
3787 {
3788 m_reporter->Report( wxString::Format( _( "Input file not found: '%s'\n" ),
3789 job->m_inputFile ),
3792 }
3793
3794 // Map job format to PCB_IO file type
3796
3797 switch( job->m_format )
3798 {
3800
3801 // "pads" names the vendor format, not the dialect: PADS writes both an ASCII export and a
3802 // binary PowerPCB database, and both use the .pcb extension. Resolve the dialect by content
3803 // so an explicit --format pads on a binary file imports it instead of handing it to the ASCII
3804 // reader, which parses nothing and silently yields an empty board.
3809 break;
3810
3812
3814
3816
3818
3820
3822
3824
3826 }
3827
3828 // FindPluginTypeFromBoardPath returns FILE_TYPE_NONE (not PCB_FILE_UNKNOWN) when no plugin
3829 // claims the file. Quiet sentinel: lets the top-level `import` command try the schematic face.
3831 {
3832 m_reporter->Report( wxString::Format( _( "No PCB importer recognizes the file format of "
3833 "'%s'\n" ),
3834 job->m_inputFile ),
3837 }
3838
3839 if( job->m_probeOnly )
3840 {
3842
3843 if( !pi )
3844 {
3845 m_reporter->Report( wxString::Format( _( "No plugin found for file type '%s'\n" ),
3849 }
3850
3851 try
3852 {
3853 job->m_projectBoards = pi->EnumerateProjectBoards( job->m_inputFile );
3854 }
3855 catch( const IO_ERROR& ioe )
3856 {
3857 m_reporter->Report( wxString::Format( _( "Failed to inspect board project: %s\n" ), ioe.What() ),
3860 }
3861
3863 }
3864
3865
3866 // Determine output path
3867 wxString outputPath = job->GetConfiguredOutputPath();
3868
3869 if( outputPath.IsEmpty() )
3871
3872 // The generated footprint library and its table row belong to the *active* project, so an
3873 // import with no project loaded needs a transient active one at the output location (never
3874 // written to disk; LoadProject returns false yet still registers it, hence the GetProject()
3875 // check).
3877 PROJECT* projectPtr = nullptr;
3878 bool createdTransientProject = false;
3879
3880 if( mgr.IsProjectOpenNotDummy() )
3881 {
3882 projectPtr = &mgr.Prj();
3883 }
3884 else
3885 {
3886 wxFileName projectFn( outputPath );
3887 projectFn.SetExt( FILEEXT::ProjectFileExtension );
3888
3889 // PROJECT requires an absolute path; -o may be relative to the working directory
3890 projectFn.MakeAbsolute();
3891
3892 mgr.LoadProject( projectFn.GetFullPath(), true );
3893 projectPtr = mgr.GetProject( projectFn.GetFullPath() );
3894 createdTransientProject = ( projectPtr != nullptr );
3895 }
3896
3897 if( !projectPtr )
3898 {
3899 m_reporter->Report( _( "Could not establish a project for the import\n" ),
3902 }
3903
3904 // unloads the transient project on every exit path
3906 {
3907 SETTINGS_MANAGER& m_mgr;
3908 PROJECT* m_project;
3909 bool m_active;
3910
3912 {
3913 if( m_active )
3914 m_mgr.UnloadProject( m_project, false );
3915 }
3916 } transientProjectGuard{ mgr, projectPtr, createdTransientProject };
3917
3918 std::unique_ptr<BOARD> board;
3919
3920 struct BOARD_PROJECT_GUARD
3921 {
3922 std::unique_ptr<BOARD>& board;
3923
3924 ~BOARD_PROJECT_GUARD()
3925 {
3926 if( board )
3927 board->ClearProject();
3928 }
3929 } boardProjectGuard{ board };
3930
3931 wxString formatName = PCB_IO_MGR::ShowType( fileType );
3932 std::vector<wxString> warnings;
3933
3934 // Real source-to-KiCad layer decisions, captured by our mapping callback so the report can
3935 // show them and so explicit overrides can be validated.
3936 struct CAPTURED_LAYER
3937 {
3938 wxString m_source;
3939 PCB_LAYER_ID m_target;
3940 wxString m_method;
3941 };
3942
3943 std::vector<CAPTURED_LAYER> capturedLayers;
3944 std::set<wxString> seenSourceLayers;
3945 bool layersCaptured = false;
3946
3947 try
3948 {
3949 // Declared before the plugin so it outlives the plugin's pointer to it
3950 IMPORT_MESSAGE_COLLECTOR importMessages( *m_reporter );
3952
3953 if( !pi )
3954 {
3955 m_reporter->Report( wxString::Format( _( "No plugin found for file type '%s'\n" ), formatName ),
3958 }
3959
3960 // Without this the importer's warnings never reach the import report
3961 pi->SetReporter( &importMessages );
3962
3963 // Replace the plugin's default best-guess callback so we can apply explicit overrides and
3964 // capture the resulting mapping. Only mappable importers expose their source layers; for
3965 // others the report falls back to listing the imported board's enabled layers.
3966 if( LAYER_MAPPABLE_PLUGIN* mappable = dynamic_cast<LAYER_MAPPABLE_PLUGIN*>( pi.get() ) )
3967 {
3968 if( !job->m_layerMap.empty() || job->m_reportFormat != IMPORT_REPORT_FORMAT::NONE )
3969 {
3970 mappable->RegisterCallback(
3971 [&]( const std::vector<INPUT_LAYER_DESC>& aDescs )
3972 -> std::map<wxString, PCB_LAYER_ID>
3973 {
3974 std::map<wxString, PCB_LAYER_ID> result;
3975
3976 for( const INPUT_LAYER_DESC& desc : aDescs )
3977 {
3978 PCB_LAYER_ID target = desc.AutoMapLayer;
3979 wxString method = wxS( "auto" );
3980
3981 if( auto it = job->m_layerMap.find( desc.Name );
3982 it != job->m_layerMap.end() )
3983 {
3984 PCB_LAYER_ID resolved = resolveKiCadLayerName( it->second );
3985
3986 if( resolved == UNDEFINED_LAYER )
3987 {
3988 warnings.push_back( wxString::Format(
3989 _( "Layer map entry '%s' -> '%s' names an unknown "
3990 "KiCad layer; using automatic mapping instead" ),
3991 desc.Name, it->second ) );
3992 }
3993 else if( !desc.PermittedLayers.Contains( resolved ) )
3994 {
3995 warnings.push_back( wxString::Format(
3996 _( "Layer map entry '%s' -> '%s' is not a permitted "
3997 "target for this layer; using automatic mapping "
3998 "instead" ),
3999 desc.Name, it->second ) );
4000 }
4001 else
4002 {
4003 target = resolved;
4004 method = wxS( "explicit" );
4005 }
4006 }
4007
4008 if( desc.Required && target == UNDEFINED_LAYER )
4009 {
4010 warnings.push_back( wxString::Format(
4011 _( "No KiCad layer mapping for required source layer "
4012 "'%s'; its items will not be imported" ),
4013 desc.Name ) );
4014 }
4015
4016 result.emplace( desc.Name, target );
4017 capturedLayers.push_back( { desc.Name, target, method } );
4018 seenSourceLayers.insert( desc.Name );
4019 }
4020
4021 layersCaptured = true;
4022 return result;
4023 } );
4024 }
4025 }
4026 else if( !job->m_layerMap.empty() )
4027 {
4028 warnings.push_back( _( "A layer map was provided, but this importer does not support "
4029 "layer remapping; it will be ignored" ) );
4030 }
4031
4032 m_reporter->Report(
4033 wxString::Format( _( "Importing '%s' using %s format...\n" ), job->m_inputFile, formatName ),
4035
4036 // LoadBoard reports load failures and user cancellations by throwing.
4037 try
4038 {
4039 // pcb_id selects a board only within an EasyEDA Pro project. Other importers must
4040 // continue receiving no properties so their existing design-settings import paths remain
4041 // unchanged.
4042 const std::map<std::string, UTF8>* importProperties = nullptr;
4043 std::map<std::string, UTF8> easyEdaProProperties;
4044
4045 if( !job->m_importPcbId.empty()
4047 {
4048 easyEdaProProperties.emplace( "pcb_id", std::string( job->m_importPcbId.ToUTF8() ) );
4049 importProperties = &easyEdaProProperties;
4050 }
4051
4052 board = pi->LoadBoard( job->m_inputFile, importProperties, projectPtr );
4053 }
4054 catch( const IO_CANCELLED& ioce )
4055 {
4056 // We should not be here, as the plugin should not have used an interactive dialog
4057 // in the CLI context.
4058 // But technically the file is not invalid
4059 m_reporter->Report( wxString::Format( _( "Unexpected cancellation: %s\n" ), ioce.What() ),
4062 }
4063 catch( const IO_ERROR& ioe )
4064 {
4065 m_reporter->Report( wxString::Format( _( "Failed to load board: %s\n" ), ioe.What() ), RPT_SEVERITY_ERROR );
4067 }
4068 board->SetProject( projectPtr, job->m_isPartOfMultiBoardProject );
4069
4070
4071
4072 if( !ApplyImportedNetNameMap( *board, job->m_netNameMap, *m_reporter ) )
4074
4075 // Extract a project footprint library and re-link FPIDs, as the board editor does.
4076 // Each EasyEDA Pro project board needs a distinct cache; using the archive name makes
4077 // every board compete to publish the same .pretty directory.
4079 {
4080 const wxString reconciliationPath =
4081 job->m_importPcbId.empty() ? job->m_inputFile : outputPath;
4082 ReconcileImportedFootprints( *pi, *board, *projectPtr, reconciliationPath, nullptr,
4083 *m_reporter );
4084 }
4085
4086 // Flag explicit map entries that never matched a source layer so typos do not pass silently.
4087 if( layersCaptured )
4088 {
4089 for( const auto& [source, target] : job->m_layerMap )
4090 {
4091 if( !seenSourceLayers.contains( source ) )
4092 {
4093 warnings.push_back( wxString::Format(
4094 _( "Layer map entry '%s' does not match any source layer in the "
4095 "imported file; it will be ignored" ),
4096 source ) );
4097 }
4098 }
4099 }
4100
4101 // Save as KiCad format
4102
4104 kicadPlugin->SaveBoard( outputPath, *board );
4105 wxString importedRules = pi->GetImportedDesignRules();
4106
4107 if( !importedRules.IsEmpty() )
4108 {
4109 wxFileName rulesPath( outputPath );
4110 rulesPath.SetExt( FILEEXT::DesignRulesFileExtension );
4111
4112 wxFile rulesFile( rulesPath.GetFullPath(), wxFile::write );
4113
4114 if( rulesFile.IsOpened() )
4115 {
4116 rulesFile.Write( importedRules );
4117 }
4118 else
4119 {
4120 m_reporter->Report( wxString::Format( _( "Could not write imported design rules to '%s'\n" ),
4121 rulesPath.GetFullPath() ),
4123 }
4124 }
4125
4126 m_reporter->Report( wxString::Format( _( "Successfully saved imported board to '%s'\n" ), outputPath ),
4128
4129 // Generate report if requested
4130 IMPORT_REPORT_DATA reportData;
4131 importMessages.Flush( job->m_reportFormat, reportData );
4132
4134 {
4135 reportData.m_sourceFile = wxFileName( job->m_inputFile ).GetFullName();
4136 reportData.m_sourceFormat = formatName;
4137 reportData.m_outputFile = wxFileName( outputPath ).GetFullName();
4138
4139 size_t trackCount = 0;
4140 size_t viaCount = 0;
4141
4142 for( PCB_TRACK* track : board->Tracks() )
4143 {
4144 if( track->Type() == PCB_VIA_T )
4145 viaCount++;
4146 else
4147 trackCount++;
4148 }
4149
4150 reportData.m_statistics = {
4151 { wxS( "footprints" ), board->Footprints().size() },
4152 { wxS( "tracks" ), trackCount },
4153 { wxS( "vias" ), viaCount },
4154 { wxS( "zones" ), board->Zones().size() }
4155 };
4156
4157 // Build layer mapping info, carried only in the JSON report. Prefer the real
4158 // source-to-KiCad decisions captured during load; fall back to the imported board's
4159 // enabled layers for importers that do not expose a mappable layer set.
4160 nlohmann::json layerMappings = nlohmann::json::object();
4161
4162 if( layersCaptured )
4163 {
4164 for( const CAPTURED_LAYER& mapped : capturedLayers )
4165 {
4166 std::string kicadLayer = mapped.m_target == UNDEFINED_LAYER
4167 ? std::string()
4168 : LSET::Name( mapped.m_target ).ToStdString();
4169
4170 layerMappings[mapped.m_source.ToStdString()] = {
4171 { "kicad_layer", kicadLayer },
4172 { "method", mapped.m_method.ToStdString() }
4173 };
4174 }
4175 }
4176 else
4177 {
4178 for( PCB_LAYER_ID layer : board->GetEnabledLayers().Seq() )
4179 {
4180 wxString layerName = board->GetLayerName( layer );
4181
4182 layerMappings[layerName.ToStdString()] = {
4183 { "kicad_layer", LSET::Name( layer ).ToStdString() },
4184 { "method", "auto" }
4185 };
4186 }
4187 }
4188
4189 reportData.m_extraJson["layer_mapping"] = layerMappings;
4190 reportData.m_warnings.insert( reportData.m_warnings.end(), warnings.begin(), warnings.end() );
4191
4192 if( !WriteImportReport( m_reporter, job->m_reportFormat, job->m_reportFile, reportData ) )
4194 }
4195 else
4196 {
4197 // No report requested, but explicit-mapping problems still need to surface.
4198 for( const wxString& warning : warnings )
4199 m_reporter->Report( warning + wxS( "\n" ), RPT_SEVERITY_WARNING );
4200 }
4201 }
4202 catch( const IO_ERROR& ioe )
4203 {
4204 m_reporter->Report( wxString::Format( _( "Error during import: %s\n" ), ioe.What() ), RPT_SEVERITY_ERROR );
4205
4207 }
4208
4210}
4211
4212
4213// ============================================================================
4214// JobDiff: pcb_diff implementation
4215// ============================================================================
4218#include <diff_merge/diff_scene.h>
4219#include <diff_merge/pcb_differ.h>
4221#include <jobs/job_pcb_diff.h>
4222
4223
4224// Load a board into a SCRATCH_DOC<BOARD> that keeps its project attached for
4225// the document's lifetime — the differ/applier read PROJECT_FILE-scoped fields
4226// (drawing-sheet path, DRC severities, net classes). The destructor severs the
4227// BOARD->project link in the right order. Used by every PCB diff/merge job.
4228static SCRATCH_DOC<BOARD> loadScratchBoard( SETTINGS_MANAGER& aMgr, const wxString& aPath,
4229 bool aInitializeAfterLoad = true )
4230{
4231 return LoadScratchDoc<BOARD>(
4232 aMgr, aPath,
4233 [aPath, aInitializeAfterLoad]( PROJECT* aProject ) -> std::unique_ptr<BOARD>
4234 {
4235 PCB_IO_MGR::PCB_FILE_T pluginType =
4237
4238 if( !aProject || pluginType == PCB_IO_MGR::FILE_TYPE_NONE )
4239 return nullptr;
4240
4242 opts.initialize_after_load = aInitializeAfterLoad;
4243
4244 try
4245 {
4246 return BOARD_LOADER::Load( aPath, pluginType, aProject, opts );
4247 }
4248 catch( ... )
4249 {
4250 return nullptr;
4251 }
4252 },
4253 []( BOARD* aBoard )
4254 {
4255 if( aBoard )
4256 aBoard->ClearProject();
4257 } );
4258}
4259
4260
4262{
4263 JOB_PCB_DIFF* diffJob = dynamic_cast<JOB_PCB_DIFF*>( aJob );
4264
4265 if( !diffJob )
4267
4268 // SCRATCH_DOC<BOARD> keeps each board's project attached for the lifetime
4269 // of the diff, which the differ needs to read project-file-scoped fields
4270 // (m_BoardDrawingSheetFile, etc). The previous loadStandaloneBoard +
4271 // ClearProject-up-front path would null those out before the differ ran.
4273
4274 SCRATCH_DOC<BOARD> aScratch = loadScratchBoard( diffMgr, diffJob->m_inputA );
4275 SCRATCH_DOC<BOARD> bScratch = loadScratchBoard( diffMgr, diffJob->m_inputB );
4276
4277 BOARD* boardA = aScratch.doc.get();
4278 BOARD* boardB = bScratch.doc.get();
4279
4280 if( !boardA )
4281 {
4282 m_reporter->Report( wxString::Format( _( "Failed to load %s\n" ), diffJob->m_inputA ), RPT_SEVERITY_ERROR );
4284 }
4285
4286 if( !boardB )
4287 {
4288 m_reporter->Report( wxString::Format( _( "Failed to load %s\n" ), diffJob->m_inputB ), RPT_SEVERITY_ERROR );
4290 }
4291
4292 KICAD_DIFF::PCB_DIFFER differ( boardA, boardB, diffJob->m_inputB );
4294
4295 int diffExitCode = KICAD_DIFF::DiffExitCode( result );
4296
4297 if( diffJob->m_exitCodeOnly )
4298 return diffExitCode;
4299
4300 // The board geometry rendered beneath the change overlay (PNG/SVG only)
4301 // matches what the interactive dialog draws.
4303 KICAD_DIFF::MakeEmitOptions( *diffJob, diffJob->m_inputA, diffJob->m_inputB );
4305 emitOpts.referenceGeometry = [&]( const KIGFX::COLOR4D& aColor )
4306 { return KICAD_DIFF::ExtractBoardGeometry( *boardA, aColor ); };
4307 emitOpts.comparisonGeometry = [&]( const KIGFX::COLOR4D& aColor )
4308 { return KICAD_DIFF::ExtractBoardGeometry( *boardB, aColor ); };
4309
4310 return KICAD_DIFF::EmitDiffResult( result, emitOpts, diffExitCode, *m_reporter );
4311}
4312
4313
4314// ============================================================================
4315// JobMerge: pcb_merge implementation
4316// ============================================================================
4324
4325
4326int PCBNEW_JOBS_HANDLER::RunMerge( KICAD_DIFF::DOC_KIND aKind, const wxString& aAncestor,
4327 const wxString& aOurs, const wxString& aTheirs,
4328 const wxString& aOutput, bool aInteractive, bool aSingleFile,
4329 REPORTER* aReporter )
4330{
4331 // Restore m_reporter on scope exit so a caller's transient (often
4332 // stack-local) reporter doesn't outlive this call as a dangling member.
4334 aReporter ? aReporter : m_reporter );
4335
4337 return runFpLibMerge( aAncestor, aOurs, aTheirs, aOutput, aSingleFile );
4338
4339 return runPcbMerge( aAncestor, aOurs, aTheirs, aOutput, aInteractive );
4340}
4341
4342
4343int PCBNEW_JOBS_HANDLER::runPcbMerge( const wxString& aAncestor, const wxString& aOurs,
4344 const wxString& aTheirs, const wxString& aOutput,
4345 bool aInteractive )
4346{
4347 // Use SCRATCH_DOC<BOARD> so each input keeps its project attached for the
4348 // life of the merge — necessary for any doc-level resolution that mutates
4349 // PROJECT_FILE-scoped state (DRC severities, net classes) and needs to be
4350 // saved as a sibling .kicad_pro. SCRATCH_DOC's destructor severs the
4351 // BOARD->project link in the right order and unloads the project from
4352 // the manager, avoiding the dangling-PROJECT_FILE::m_BoardSettings pointer
4353 // the previous up-front-ClearProject loadStandaloneBoard path had to
4354 // guard against.
4356
4357 SCRATCH_DOC<BOARD> ancestorScratch = loadScratchBoard( mgr, aAncestor );
4358 SCRATCH_DOC<BOARD> oursScratch = loadScratchBoard( mgr, aOurs );
4359 SCRATCH_DOC<BOARD> theirsScratch = loadScratchBoard( mgr, aTheirs );
4360
4361 BOARD* ancestor = ancestorScratch.doc.get();
4362 BOARD* ours = oursScratch.doc.get();
4363 BOARD* theirs = theirsScratch.doc.get();
4364
4365 if( !ancestor || !ours || !theirs )
4366 {
4367 m_reporter->Report( _( "Failed to load one or more input boards\n" ), RPT_SEVERITY_ERROR );
4369 }
4370
4371 const wxString formatNotice =
4374
4375 if( !formatNotice.IsEmpty() )
4376 m_reporter->Report( formatNotice + wxS( "\n" ), RPT_SEVERITY_WARNING );
4377
4378 KICAD_DIFF::PCB_DIFFER ourDiff( ancestor, ours );
4379 KICAD_DIFF::PCB_DIFFER theirDiff( ancestor, theirs );
4380
4381 KICAD_DIFF::DOCUMENT_DIFF ourDocDiff = ourDiff.Diff();
4382 KICAD_DIFF::DOCUMENT_DIFF theirDocDiff = theirDiff.Diff();
4383
4385 KICAD_DIFF::MERGE_PLAN plan = engine.Plan( ourDocDiff, theirDocDiff );
4386
4387 if( aInteractive && !KICAD_DIFF::BuildConflictList( plan ).empty() )
4388 {
4389 if( !Pgm().IsGUI() )
4390 {
4391 m_reporter->Report( _( "--interactive requires a GUI KiCad process; the console "
4392 "kicad-cli cannot open dialogs.\n" ),
4395 }
4396
4397 // Geometry context so the conflict viewer can render the actual
4398 // boards behind the conflict bbox highlight.
4399 const KICAD_DIFF::DIFF_COLOR_THEME theme;
4401 ctx.formatNotice = formatNotice;
4405
4406 // Build per-side bbox lookups so a "moved on theirs" item highlights
4407 // at its theirs-side coordinates when the user previews Theirs.
4409 KICAD_DIFF::CollectChangeBBoxes( theirDocDiff, ctx.theirsBBoxes );
4410
4411 ctx.describeItem = [ours, theirs, ancestor]( const KIID_PATH& aId ) -> wxString
4412 {
4413 if( aId.empty() )
4414 return wxEmptyString;
4415
4417
4418 for( BOARD* board : { ours, theirs, ancestor } )
4419 {
4420 if( !board )
4421 continue;
4422
4423 if( BOARD_ITEM* item = board->ResolveItem( aId.back(), true ) )
4424 return item->GetItemDescription( &units, false );
4425 }
4426
4427 return wxEmptyString;
4428 };
4429
4430 ctx.drawSide = [ancestor, ours, theirs, theme]( WIDGET_DIFF_CANVAS& aCanvas,
4432 const std::optional<KIID_PATH>& )
4433 {
4434 BOARD* board = nullptr;
4435 KIGFX::COLOR4D colour = theme.reference;
4436
4437 switch( aSide )
4438 {
4439 case DIALOG_KICAD_MERGE_3WAY::SIDE::OURS: board = ours; break;
4440
4442 board = theirs;
4443 colour = theme.comparison;
4444 break;
4445
4446 case DIALOG_KICAD_MERGE_3WAY::SIDE::ANCESTOR: board = ancestor; break;
4447 }
4448
4449 if( board )
4450 KICAD_DIFF::ConfigurePcbDiffCanvasContext( aCanvas, board, nullptr, colour );
4451 };
4452
4453 DIALOG_KICAD_MERGE_3WAY dlg( wxTheApp->GetTopWindow(), plan, std::move( ctx ) );
4454
4455 // Cancelling leaves the conflicts unresolved and the files untouched.
4456 if( dlg.ShowModal() != wxID_APPLY )
4458
4459 plan = dlg.GetResolvedPlan();
4460 }
4461
4462 // Snapshot of the plan before the applier moves it; drives the
4463 // unresolved-conflict report below.
4464 const KICAD_DIFF::MERGE_PLAN planSnapshot = plan;
4465
4466 KICAD_DIFF::PCB_MERGE_APPLIER applier( ancestor, ours, theirs, std::move( plan ) );
4467 std::unique_ptr<BOARD> merged = applier.Apply();
4468
4469 if( !merged )
4470 {
4471 m_reporter->Report( _( "Merge applier failed to produce a board\n" ), RPT_SEVERITY_ERROR );
4473 }
4474
4475 // Serialize to the output path using the canonical PCB IO.
4476 PCB_IO_KICAD_SEXPR pcbIO;
4477
4478 try
4479 {
4480 pcbIO.SaveBoard( aOutput, *merged );
4481 }
4482 catch( const IO_ERROR& ioe )
4483 {
4484 m_reporter->Report( wxString::Format( _( "Failed to save merged board: %s\n" ), ioe.What() ),
4487 }
4488
4489 // BOARD_DESIGN_SETTINGS fields like m_DRCSeverities serialize to
4490 // .kicad_pro, not .kicad_pcb. Mirror only those specific fields onto
4491 // ancestor (still linked to its project via BOARD::SetProject) then
4492 // save ancestor's project alongside the merged board file. Whole-
4493 // BOARD_DESIGN_SETTINGS copy would alias shared_ptr<NET_SETTINGS>
4494 // across BOARDs and crash on ClearProject during SCRATCH_DOC release;
4495 // single-field mirror avoids that.
4496 if( applier.GetReport().projectFileTouched && ancestor && ancestor->GetProject() )
4497 {
4498 ancestor->GetDesignSettings().m_DRCSeverities = merged->GetDesignSettings().m_DRCSeverities;
4499
4500 // Mirror net settings (the applier copied them onto the result via
4501 // NET_SETTINGS::CopyFrom; do the same here to ancestor, which still
4502 // owns the project's nested-settings registration so SaveProjectCopy
4503 // walks the right entry).
4504 if( ancestor->GetDesignSettings().m_NetSettings && merged->GetDesignSettings().m_NetSettings )
4505 {
4506 ancestor->GetDesignSettings().m_NetSettings->CopyFrom( *merged->GetDesignSettings().m_NetSettings );
4507 }
4508
4509 // The applier stages drawing-sheet resolutions on the report (the
4510 // result BOARD is project-less). Mirror onto ancestor's project here
4511 // before SaveProjectCopy walks the PROJECT_FILE.
4512 if( applier.GetReport().drawingSheetFileSet )
4513 {
4515 }
4516
4517 wxFileName proFn( aOutput );
4518 proFn.SetExt( FILEEXT::ProjectFileExtension );
4519
4520 // JSON-patch path: flush ancestor's in-memory project to its JSON
4521 // cache, then patch only the diffed DOC_PROP fields onto the output
4522 // file. This preserves any non-diffed fields the user had at the
4523 // output path (text variables, last paths, layer presets etc.) that
4524 // a full SaveProjectCopy would silently overwrite.
4525 PROJECT_FILE& ancProj = ancestor->GetProject()->GetProjectFile();
4526 ancProj.Store();
4527
4528 const KICAD_DIFF::PCB_MERGE_APPLIER::REPORT& mergeReport = applier.GetReport();
4529
4530 // PROJECT_FILE::Store() flushes the project file's own params but not
4531 // its registered NESTED_SETTINGS. Flush only the nested settings the
4532 // merge resolved so the surgical patch does not overwrite unrelated
4533 // project subtrees.
4534 if( mergeReport.drcSeveritiesTouched )
4535 ancestor->GetDesignSettings().SaveToFile( wxEmptyString, true );
4536
4537 if( mergeReport.netClassesTouched && ancestor->GetDesignSettings().m_NetSettings )
4538 ancestor->GetDesignSettings().m_NetSettings->SaveToFile( wxEmptyString, true );
4539
4540 std::set<wxString> touched;
4541 if( mergeReport.drcSeveritiesTouched )
4542 touched.insert( KICAD_DIFF::DOC_PROP_DRC_SEVERITIES );
4543
4544 if( mergeReport.netClassesTouched )
4545 touched.insert( KICAD_DIFF::DOC_PROP_NET_CLASSES );
4546
4547 if( applier.GetReport().drawingSheetFileSet )
4548 touched.insert( KICAD_DIFF::DOC_PROP_DRAWING_SHEET );
4549
4550 if( !KICAD_DIFF::ApplyProjectFilePatches( proFn.GetFullPath(), *ancProj.Internals(), touched ) )
4551 {
4552 // Patch failed (existing output unparseable or write error).
4553 // Fall back to the legacy full-copy path so the user still gets
4554 // a project file even if it overwrites non-diffed customisations.
4555 if( !mgr.SaveProjectCopy( proFn.GetFullPath(), ancestor->GetProject() ) )
4556 {
4557 m_reporter->Report(
4558 wxString::Format( _( "Failed to save merged project file: %s\n" ), proFn.GetFullPath() ),
4561 }
4562 }
4563
4564 // Write a project-dir sibling file from staged report content. Empty
4565 // content removes the file so a TAKE_ANCESTOR resolution against an
4566 // ancestor with no file clears stale content at the output path.
4567 auto writeStagedFile = [&]( const wxString& aPath, const wxString& aContent, const wxString& aLabel ) -> bool
4568 {
4569 if( aContent.IsEmpty() )
4570 {
4571 if( wxFileExists( aPath ) )
4572 wxRemoveFile( aPath );
4573
4574 return true;
4575 }
4576
4577 wxFile out;
4578
4579 if( !out.Open( aPath, wxFile::write ) || !out.Write( aContent ) )
4580 {
4581 m_reporter->Report( wxString::Format( _( "Failed to save merged %s: %s\n" ), aLabel, aPath ),
4583 return false;
4584 }
4585
4586 return true;
4587 };
4588
4589 // Custom DRC rules: write the applier's staged content next to the
4590 // merged board so the chosen side's rules apply at next DRC run.
4591 if( applier.GetReport().customDrcRulesSet )
4592 {
4593 wxFileName druFn( aOutput );
4594 druFn.SetExt( FILEEXT::DesignRulesFileExtension );
4595
4596 if( !writeStagedFile( druFn.GetFullPath(), applier.GetReport().customDrcRules, _( "custom DRC rules" ) ) )
4597 {
4599 }
4600 }
4601
4602 // Footprint / symbol library tables: write into the merged project
4603 // directory. Both files have no extension.
4604 if( applier.GetReport().fpLibTableSet )
4605 {
4606 wxFileName fpFn( aOutput );
4607 fpFn.SetFullName( wxString::FromUTF8( FILEEXT::FootprintLibraryTableFileName ) );
4608
4609 if( !writeStagedFile( fpFn.GetFullPath(), applier.GetReport().fpLibTable, _( "footprint library table" ) ) )
4610 {
4612 }
4613 }
4614
4615 if( applier.GetReport().symLibTableSet )
4616 {
4617 wxFileName symFn( aOutput );
4618 symFn.SetFullName( wxString::FromUTF8( FILEEXT::SymbolLibraryTableFileName ) );
4619
4620 if( !writeStagedFile( symFn.GetFullPath(), applier.GetReport().symLibTable, _( "symbol library table" ) ) )
4621 {
4623 }
4624 }
4625 }
4626
4627 // Surface post-apply validator findings (refdes collisions, schema
4628 // mismatch, missed connectivity rebuild). Advisory — they do not change the
4629 // exit code, only the merge's resolved/unresolved status does.
4631 m_reporter->Report( wxString::Format( wxS( "%s: %s\n" ), f.validator, f.message ), f.severity );
4632
4633 // The merged board was written to m_outputPath above, so the output is
4634 // always a valid file. Unresolved conflicts are reported and signalled via
4635 // the exit code; the user resolves them with the interactive mergetool.
4636 if( !planSnapshot.Resolved() )
4637 {
4638 m_reporter->Report( wxString::Format( _( "Merge completed with %zu unresolved conflict(s) in %s\n" ),
4639 planSnapshot.ConflictCount(), aOutput ),
4642 }
4643
4645}
4646
4647
4648// ============================================================================
4649// JobFpDiff: fp_diff implementation
4650// ============================================================================
4652#include <jobs/job_fp_diff.h>
4653
4654
4655// Load one side of a footprint-library diff into its owner vector and name map.
4656// When aAllowEmpty is set an empty path resolves to a clean (empty) side; the
4657// non-interactive job path leaves it unset so a missing path is an input error.
4658static int loadFootprintLibrarySide( const wxString& aPath,
4659 std::vector<std::unique_ptr<FOOTPRINT>>& aOwners,
4660 KICAD_DIFF::FP_LIB_DIFFER::FOOTPRINT_MAP& aMap, bool aAllowEmpty,
4661 REPORTER& aReporter )
4662{
4663 if( aAllowEmpty && aPath.IsEmpty() )
4665
4666 try
4667 {
4668 auto loaded = KICAD_DIFF::FP_LIB_DIFFER::LoadLibrary( aPath );
4669 aOwners = std::move( loaded.first );
4670 aMap = std::move( loaded.second );
4672 }
4673 catch( const IO_ERROR& ioe )
4674 {
4675 aReporter.Report( wxString::Format( _( "Failed to load %s: %s\n" ), aPath, ioe.What() ),
4677 }
4678 catch( const std::exception& e )
4679 {
4680 aReporter.Report(
4681 wxString::Format( _( "Failed to load %s: %s\n" ), aPath, wxString::FromUTF8( e.what() ) ),
4683 }
4684
4686}
4687
4688
4689// Flatten a footprint-library name map into a single DOCUMENT_GEOMETRY tinted
4690// with the supplied per-side theme colour.
4693{
4695
4696 for( const auto& [name, footprint] : aMap )
4697 {
4698 if( footprint )
4699 KICAD_DIFF::AppendGeometry( geometry, KICAD_DIFF::ExtractFootprintGeometry( *footprint, aColor ) );
4700 }
4701
4702 return geometry;
4703}
4704
4705
4707{
4708 JOB_FP_DIFF* diffJob = dynamic_cast<JOB_FP_DIFF*>( aJob );
4709
4710 if( !diffJob )
4712
4713 wxFileName dirA( diffJob->m_inputA );
4714 dirA.MakeAbsolute();
4715 wxFileName dirB( diffJob->m_inputB );
4716 dirB.MakeAbsolute();
4717
4718 std::vector<std::unique_ptr<FOOTPRINT>> ownersA;
4719 std::vector<std::unique_ptr<FOOTPRINT>> ownersB;
4722
4723 if( int rc = loadFootprintLibrarySide( dirA.GetFullPath(), ownersA, mapA, false, *m_reporter );
4725 {
4726 return rc;
4727 }
4728
4729 if( int rc = loadFootprintLibrarySide( dirB.GetFullPath(), ownersB, mapB, false, *m_reporter );
4731 {
4732 return rc;
4733 }
4734
4735 KICAD_DIFF::FP_LIB_DIFFER differ( mapA, mapB, diffJob->m_inputB );
4737
4738 int diffExitCode = KICAD_DIFF::DiffExitCode( result );
4739
4740 if( diffJob->m_exitCodeOnly )
4741 return diffExitCode;
4742
4744 KICAD_DIFF::MakeEmitOptions( *diffJob, diffJob->m_inputA, diffJob->m_inputB );
4746 emitOpts.referenceGeometry = [&]( const KIGFX::COLOR4D& aColor )
4747 { return footprintLibraryGeometry( mapA, aColor ); };
4748 emitOpts.comparisonGeometry = [&]( const KIGFX::COLOR4D& aColor )
4749 { return footprintLibraryGeometry( mapB, aColor ); };
4750
4751 return KICAD_DIFF::EmitDiffResult( result, emitOpts, diffExitCode, *m_reporter );
4752}
4753
4754
4755// ============================================================================
4756// JobOpenDiffDialog: load two on-disk files and open DIALOG_KICAD_DIFF.
4757// Dispatched from the project manager / PR-review dialog via KIWAY.
4758// ============================================================================
4763#include <jobs/scratch_doc.h>
4764
4765
4766// Both boards of a diff, kept while the caller draws them.
4777
4778
4779static int loadPcbDiffSides( SETTINGS_MANAGER& aMgr, REPORTER& aReporter, const wxString& aFileA,
4780 const wxString& aFileB, PCB_DIFF_SIDES& aSides )
4781{
4782 aSides.a = loadScratchBoard( aMgr, aFileA, /* aInitializeAfterLoad */ false );
4783 aSides.b = loadScratchBoard( aMgr, aFileB, /* aInitializeAfterLoad */ false );
4784
4785 if( !aSides.a.doc && !aFileA.IsEmpty() )
4786 {
4787 aReporter.Report( wxString::Format( _( "Failed to load %s\n" ), aFileA ), RPT_SEVERITY_ERROR );
4789 }
4790
4791 if( !aSides.b.doc && !aFileB.IsEmpty() )
4792 {
4793 aReporter.Report( wxString::Format( _( "Failed to load %s\n" ), aFileB ), RPT_SEVERITY_ERROR );
4795 }
4796
4797 // An ADDED or REMOVED side is an empty board, so the differ still produces a per-item
4798 // list rather than failing on an empty input file.
4799 aSides.boardA = aSides.a.doc ? aSides.a.doc.get() : &aSides.emptyA;
4800 aSides.boardB = aSides.b.doc ? aSides.b.doc.get() : &aSides.emptyB;
4801
4802 KICAD_DIFF::PCB_DIFFER differ( aSides.boardA, aSides.boardB, aFileB );
4803 aSides.diff = differ.Diff();
4804
4806}
4807
4808
4809// The two boards a window outside the kiface shows, taken off their canvases before they go.
4811{
4812public:
4814 m_before( aBefore ),
4815 m_after( aAfter )
4816 {
4817 }
4818
4820 {
4821 m_before->SetContextItems( {} );
4822 m_after->SetContextItems( {} );
4823 }
4824
4826
4827private:
4830};
4831
4832
4834PCBNEW_JOBS_HANDLER::AttachDiffCanvases( KICAD_DIFF::DOC_KIND aKind, const wxString& aFileA, const wxString& aFileB,
4835 WIDGET_DIFF_CANVAS* aBefore, WIDGET_DIFF_CANVAS* aAfter,
4836 KICAD_DIFF::DOCUMENT_DIFF& aDiff, REPORTER* aReporter )
4837{
4838 SCOPED_SET_RESET<REPORTER*> reporterGuard( m_reporter, aReporter ? aReporter : m_reporter );
4839
4840 if( aKind != KICAD_DIFF::DOC_KIND::PCB )
4841 return nullptr;
4842
4843 auto context = std::make_unique<PCB_DIFF_CANVAS_CONTEXT>( aBefore, aAfter );
4844
4845 if( loadPcbDiffSides( Pgm().GetSettingsManager(), *m_reporter, aFileA, aFileB, context->m_sides )
4847 {
4848 return nullptr;
4849 }
4850
4851 const KICAD_DIFF::DIFF_COLOR_THEME theme;
4852
4853 KICAD_DIFF::ConfigurePcbDiffCanvasContext( *aBefore, context->m_sides.boardA, nullptr, theme.reference, {}, {},
4854 {} );
4855 KICAD_DIFF::ConfigurePcbDiffCanvasContext( *aAfter, nullptr, context->m_sides.boardB, theme.reference, {}, {}, {} );
4856
4857 aDiff = context->m_sides.diff;
4858 return context.release();
4859}
4860
4861
4863 const wxString& aFileB, const wxString& aLabelA,
4864 const wxString& aLabelB, wxWindow* aParent,
4865 REPORTER* aReporter )
4866{
4867 // Restore m_reporter on scope exit so a caller's transient (often
4868 // stack-local) reporter doesn't outlive this call as a dangling member.
4870 aReporter ? aReporter : m_reporter );
4871
4872 wxWindow* parent = aParent ? aParent : ( wxTheApp ? wxTheApp->GetTopWindow() : nullptr );
4873
4875
4878 KICAD_DIFF::DOCUMENT_GEOMETRY compGeometry;
4879
4880 auto loadFootprintFile = [&]( const wxString& aPath ) -> std::unique_ptr<FOOTPRINT>
4881 {
4882 if( aPath.IsEmpty() )
4883 return nullptr;
4884
4885 wxFileName fn( aPath );
4886 fn.MakeAbsolute();
4887
4888 // A single .kicad_mod's internal (footprint ...) name need not match its
4889 // filename, so load the file's sole footprint via ImportFootprint rather
4890 // than FootprintLoad (which treats the directory as a .pretty library and
4891 // keys by basename), matching runFpLibMerge's single-file path.
4893 wxString name;
4894 return io.ImportFootprint( fn.GetFullPath(), name );
4895 };
4896
4897 switch( aKind )
4898 {
4900 {
4901 PCB_DIFF_SIDES sides;
4902
4903 if( int rc = loadPcbDiffSides( mgr, *m_reporter, aFileA, aFileB, sides ); rc != CLI::EXIT_CODES::SUCCESS )
4904 return rc;
4905
4906 BOARD* boardA = sides.boardA;
4907 BOARD* boardB = sides.boardB;
4908 result = std::move( sides.diff );
4909
4910 // Background geometry, so the dialog's canvas shows the board outline and footprints
4911 // beneath the change rectangles.
4912 const KICAD_DIFF::DIFF_COLOR_THEME theme;
4913 refGeometry = KICAD_DIFF::ExtractBoardGeometry( *boardA, theme.reference );
4914 compGeometry = KICAD_DIFF::ExtractBoardGeometry( *boardB, theme.comparison );
4915
4916 const wxString labelA = aLabelA.IsEmpty() ? aFileA : aLabelA;
4917 const wxString labelB = aLabelB.IsEmpty() ? aFileB : aLabelB;
4918
4920 parent, labelA, labelB, result, std::move( refGeometry ), std::move( compGeometry ),
4921 [boardA, boardB, color = theme.reference]( WIDGET_DIFF_CANVAS& aCanvas, const KIID_PATH& )
4922 {
4923 KICAD_DIFF::ConfigurePcbDiffCanvasContext( aCanvas, boardA, boardB, color );
4924 } );
4925 dlg.ShowModal();
4926
4928 }
4930 {
4931 std::vector<std::unique_ptr<FOOTPRINT>> ownersA;
4932 std::vector<std::unique_ptr<FOOTPRINT>> ownersB;
4935
4936 if( int rc = loadFootprintLibrarySide( aFileA, ownersA, mapA, true, *m_reporter );
4938 {
4939 return rc;
4940 }
4941
4942 if( int rc = loadFootprintLibrarySide( aFileB, ownersB, mapB, true, *m_reporter );
4944 {
4945 return rc;
4946 }
4947
4948 KICAD_DIFF::FP_LIB_DIFFER differ( mapA, mapB, aFileB );
4949 result = differ.Diff();
4950
4951 const KICAD_DIFF::DIFF_COLOR_THEME theme;
4952 refGeometry = footprintLibraryGeometry( mapA, theme.reference );
4953 compGeometry = footprintLibraryGeometry( mapB, theme.comparison );
4954 break;
4955 }
4957 {
4958 std::unique_ptr<FOOTPRINT> footprintA;
4959 std::unique_ptr<FOOTPRINT> footprintB;
4960
4961 try
4962 {
4963 footprintA = loadFootprintFile( aFileA );
4964 }
4965 catch( const IO_ERROR& ioe )
4966 {
4967 m_reporter->Report( wxString::Format( _( "Failed to load %s: %s\n" ), aFileA, ioe.What() ),
4970 }
4971
4972 try
4973 {
4974 footprintB = loadFootprintFile( aFileB );
4975 }
4976 catch( const IO_ERROR& ioe )
4977 {
4978 m_reporter->Report( wxString::Format( _( "Failed to load %s: %s\n" ), aFileB, ioe.What() ),
4981 }
4982
4985 const wxString nameA = wxFileName( aFileA ).GetName();
4986 const wxString nameB = wxFileName( aFileB ).GetName();
4987 const wxString itemName = !nameB.IsEmpty() ? nameB : nameA;
4988
4989 if( footprintA )
4990 mapA[itemName] = footprintA.get();
4991
4992 if( footprintB )
4993 mapB[itemName] = footprintB.get();
4994
4995 KICAD_DIFF::FP_LIB_DIFFER differ( mapA, mapB, aFileB );
4996 result = differ.Diff();
4997
4998 const KICAD_DIFF::DIFF_COLOR_THEME theme;
4999
5000 if( footprintA )
5001 refGeometry = KICAD_DIFF::ExtractFootprintGeometry( *footprintA, theme.reference );
5002
5003 if( footprintB )
5004 compGeometry = KICAD_DIFF::ExtractFootprintGeometry( *footprintB, theme.comparison );
5005
5006 break;
5007 }
5008 default:
5009 m_reporter->Report( _( "Unsupported document kind for this dispatcher.\n" ), RPT_SEVERITY_ERROR );
5011 }
5012
5013 const wxString labelA = aLabelA.IsEmpty() ? aFileA : aLabelA;
5014 const wxString labelB = aLabelB.IsEmpty() ? aFileB : aLabelB;
5015
5016 DIALOG_KICAD_DIFF dlg( parent, labelA, labelB, result, std::move( refGeometry ), std::move( compGeometry ) );
5017 dlg.ShowModal();
5018
5020}
5021
5022
5023// ============================================================================
5024// JobFpLibMerge: 3-way merge of .pretty footprint libraries.
5025// ============================================================================
5028
5029
5030int PCBNEW_JOBS_HANDLER::runFpLibMerge( const wxString& aAncestor, const wxString& aOurs,
5031 const wxString& aTheirs, const wxString& aOutput,
5032 bool aSingleFile )
5033{
5034 if( aOutput.IsEmpty() )
5035 {
5036 m_reporter->Report( _( "--output is required\n" ), RPT_SEVERITY_ERROR );
5038 }
5039
5040 struct LIB_SIDE
5041 {
5042 std::vector<std::unique_ptr<FOOTPRINT>> owners;
5044 };
5045
5046 LIB_SIDE ancestor, ours, theirs;
5047
5048 // Accept either a `.pretty` directory (library mode) or a single `.kicad_
5049 // mod` file (git's per-file driver mode). Extension autodetection works
5050 // for native invocations, but git's external driver passes temp paths
5051 // (`.merge_file_XXX`) with no extension, so the `--single-file` flag
5052 // overrides on demand.
5053 auto isSingleFile = [&]( const wxString& aPath )
5054 {
5055 if( aSingleFile )
5056 return true;
5057
5058 return wxFileName( aPath ).GetExt() == FILEEXT::KiCadFootprintFileExtension;
5059 };
5060
5061 auto loadSide = [&]( const wxString& aPath, LIB_SIDE& aSide ) -> int
5062 {
5063 try
5064 {
5065 if( isSingleFile( aPath ) )
5066 {
5068 wxString name;
5069 std::unique_ptr<FOOTPRINT> fp = io.ImportFootprint( aPath, name );
5070
5071 if( !fp )
5073
5074 // Use the footprint's own item-name (LIB_ID) if set,
5075 // falling back to the file basename. Both sides must
5076 // agree for the differ/applier to align them.
5077 const UTF8& itemName = fp->GetFPID().GetLibItemName();
5078 const wxString key = itemName.empty() ? name : itemName.wx_str();
5079
5080 aSide.map[key] = fp.get();
5081 aSide.owners.push_back( std::move( fp ) );
5083 }
5084
5085 auto loaded = KICAD_DIFF::FP_LIB_DIFFER::LoadLibrary( aPath );
5086 aSide.owners = std::move( loaded.first );
5087 aSide.map = std::move( loaded.second );
5089 }
5090 catch( const IO_ERROR& ioe )
5091 {
5092 m_reporter->Report( wxString::Format( _( "Failed to load %s: %s\n" ), aPath, ioe.What() ),
5094 }
5095 catch( const std::exception& e )
5096 {
5097 m_reporter->Report(
5098 wxString::Format( _( "Failed to load %s: %s\n" ), aPath, wxString::FromUTF8( e.what() ) ),
5100 }
5101
5103 };
5104
5105 if( int rc = loadSide( aAncestor, ancestor ); rc != CLI::EXIT_CODES::SUCCESS )
5106 return rc;
5107
5108 if( int rc = loadSide( aOurs, ours ); rc != CLI::EXIT_CODES::SUCCESS )
5109 return rc;
5110
5111 if( int rc = loadSide( aTheirs, theirs ); rc != CLI::EXIT_CODES::SUCCESS )
5112 return rc;
5113
5114 KICAD_DIFF::FP_LIB_DIFFER ourDiff( ancestor.map, ours.map, aOurs );
5115 KICAD_DIFF::FP_LIB_DIFFER theirDiff( ancestor.map, theirs.map, aTheirs );
5116
5117 KICAD_DIFF::DOCUMENT_DIFF ourDocDiff = ourDiff.Diff();
5118 KICAD_DIFF::DOCUMENT_DIFF theirDocDiff = theirDiff.Diff();
5119
5121 KICAD_DIFF::MERGE_PLAN plan = engine.Plan( ourDocDiff, theirDocDiff );
5122
5123 const KICAD_DIFF::MERGE_PLAN planSnapshot = plan;
5124
5125 KICAD_DIFF::FP_LIB_MERGE_APPLIER applier( ancestor.map, ours.map, theirs.map, std::move( plan ) );
5126 std::vector<std::unique_ptr<FOOTPRINT>> merged = applier.Apply();
5127
5128 // Per-property footprint merge isn't implemented; MERGE_PROPS resolutions
5129 // are downgraded to TAKE_OURS. Surface that as unresolved so the user sees
5130 // a marker instead of silent partial-merge.
5131 const bool hadSilentFallback = applier.GetReport().mergePropsFallback > 0;
5132
5133 const bool singleFileOutput = isSingleFile( aOutput );
5134
5135 // .pretty is a directory; .kicad_mod is a single file. wxFileName parses
5136 // a path ending in `.pretty` as a file with that extension, so library
5137 // mode uses DirName(); single-file mode keeps the file path as-is and
5138 // hands it directly to FootprintSave, which auto-detects .kicad_mod via
5139 // its own extension check.
5140 wxFileName outFn;
5141
5142 if( singleFileOutput )
5143 outFn = wxFileName( aOutput );
5144 else
5145 outFn = wxFileName::DirName( aOutput );
5146
5147 outFn.MakeAbsolute();
5148
5149 // In library mode wxFileName::DirName treats `foo.pretty` as a directory,
5150 // so GetPath() returns the .pretty itself. In single-file mode GetPath()
5151 // returns the file's parent dir. Either way it's the directory we Mkdir
5152 // into.
5153 const wxString outDir = outFn.GetPath();
5154
5155 if( !wxFileName::DirExists( outDir ) && !wxFileName::Mkdir( outDir, wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL ) )
5156 {
5157 m_reporter->Report( wxString::Format( _( "Cannot create output directory %s\n" ), outDir ),
5160 }
5161
5162 try
5163 {
5165
5166 if( singleFileOutput )
5167 {
5168 // Git per-file driver mode: one merged footprint -> one .kicad_mod.
5169 // Multiple survivors would lose data; flag that as an error since
5170 // single-file input by definition has at most one footprint per
5171 // side.
5172 if( merged.size() > 1 )
5173 {
5174 m_reporter->Report( _( "Single-file fp merge produced multiple footprints; refusing to "
5175 "collapse into one .kicad_mod\n" ),
5178 }
5179
5180 if( merged.empty() )
5181 {
5182 // All sides deleted the footprint. Remove the output file if
5183 // it existed, leaving nothing where the merged content would
5184 // have gone.
5185 if( wxFileName::FileExists( outFn.GetFullPath() ) )
5186 wxRemoveFile( outFn.GetFullPath() );
5187 }
5188 else if( wxFileName( outFn.GetFullPath() ).GetExt() == FILEEXT::KiCadFootprintFileExtension )
5189 {
5190 // FootprintSave's .kicad_mod extension autodetection handles
5191 // the write to the path as-given.
5192 io.FootprintSave( outFn.GetFullPath(), merged.front().get(), nullptr );
5193 }
5194 else
5195 {
5196 // Git driver mode: output is an extension-less temp path
5197 // (typically `.merge_file_XXX`). FootprintSave's
5198 // autodetection would treat it as a library directory.
5199 // Format directly via PRETTIFIED_FILE_OUTPUTFORMATTER, the
5200 // same writer the sexpr lib cache uses.
5201 PRETTIFIED_FILE_OUTPUTFORMATTER formatter( outFn.GetFullPath() );
5202 io.SetOutputFormatter( &formatter );
5203 io.Format( merged.front().get() );
5204 formatter.Finish();
5205 }
5206 }
5207 else
5208 {
5209 // Library mode. Footprints in `merged` are the survivors. Any
5210 // footprint already in the output `.pretty` but absent from
5211 // `merged` is a stale leftover from a previous invocation (or a
5212 // resolved DELETE / TAKE_ANCESTOR-with-no-ancestor case). Delete
5213 // those before saving the survivors, otherwise the resolved
5214 // DELETE never propagates to disk.
5215 std::set<wxString> mergedNames;
5216
5217 for( const auto& fp : merged )
5218 {
5219 if( fp )
5220 mergedNames.insert( fp->GetFPID().GetLibItemName() );
5221 }
5222
5223 wxArrayString existing;
5224 io.FootprintEnumerate( existing, outDir, false, nullptr );
5225
5226 for( const wxString& name : existing )
5227 {
5228 if( !mergedNames.count( name ) )
5229 io.FootprintDelete( outDir, name, nullptr );
5230 }
5231
5232 for( const auto& fp : merged )
5233 {
5234 if( !fp )
5235 continue;
5236
5237 const wxString name = fp->GetFPID().GetLibItemName();
5238
5239 if( io.FootprintExists( outDir, name, nullptr ) )
5240 io.FootprintDelete( outDir, name, nullptr );
5241
5242 io.FootprintSave( outDir, fp.get(), nullptr );
5243 }
5244 }
5245 }
5246 catch( const IO_ERROR& ioe )
5247 {
5248 m_reporter->Report( wxString::Format( _( "Failed to save merged footprint library: %s\n" ), ioe.What() ),
5251 }
5252
5253 // The merged library was saved above, so the output is always valid.
5254 if( !planSnapshot.Resolved() || hadSilentFallback )
5255 {
5256 // Conflict count = engine-unresolved ∪ applier-downgraded (deduped, so
5257 // an item that was both unresolved and silently downgraded counts once).
5258 std::set<KIID_PATH> conflicts( planSnapshot.unresolved.begin(), planSnapshot.unresolved.end() );
5259
5260 for( const KIID_PATH& id : applier.GetReport().mergePropsFallbackIds )
5261 conflicts.insert( id );
5262
5263 m_reporter->Report( wxString::Format( _( "Footprint library merge completed with %zu unresolved "
5264 "conflict(s) in %s\n" ),
5265 conflicts.size(), aOutput ),
5268 }
5269
5271}
@ VIEW3D_BOTTOM
Definition 3d_enums.h:84
PROJECTION_TYPE
Camera projection types.
Definition 3d_enums.h:50
const char * name
constexpr EDA_IU_SCALE pcbIUScale
Definition base_units.h:128
#define RANGE_SCALE_3D
This defines the range that all coord will have to be rendered.
COMPATIBILITY_REPORT ClassifyBoardForDowngrade(const BOARD *aBoard, const DOWNGRADE_TARGET &aTarget, bool aDropInsteadOfApproximate)
Report what a board would lose if exported to an older KiCad.
void FlattenBoardVariant(BOARD *aBoard, const wxString &aVariantName)
Bake a variant's overrides into the base footprint attributes.
bool ExportBoardToOlderVersion(const wxString &aSrcFile, const wxString &aDestFile, const DOWNGRADE_TARGET &aTarget, COMPATIBILITY_REPORT &aReport, wxString *aUnsupportedToken, const wxString &aVariant, bool aDropInsteadOfApproximate)
Load a board, downgrade the copy, and write it stamped for the target.
DOWNGRADE_FILE_RESULT DowngradeFootprintFileToTemp(const wxString &aSrcFile, const wxString &aTmpFile, const DOWNGRADE_TARGET &aTarget, bool aDropInsteadOfApproximate)
Load one .kicad_mod, classify it against the rule table, and write the downgraded copy to aTmpFile.
constexpr int FIRST_PCB_VARIANTS
First board format that stores variants.
wxString BuildStackupCsv(BOARD_STACKUP &aStackup, EDA_UNITS aUnits, const STACKUP_CSV_OPTIONS &aOptions)
wxString FormatBoardStatisticsJson(const BOARD_STATISTICS_DATA &aData, BOARD *aBoard, const UNITS_PROVIDER &aUnitsProvider, const wxString &aProjectName, const wxString &aBoardName)
void ComputeBoardStatistics(BOARD *aBoard, const BOARD_STATISTICS_OPTIONS &aOptions, BOARD_STATISTICS_DATA &aData)
wxString FormatBoardStatisticsReport(const BOARD_STATISTICS_DATA &aData, BOARD *aBoard, const UNITS_PROVIDER &aUnitsProvider, const wxString &aProjectName, const wxString &aBoardName)
void InitializeBoardStatisticsData(BOARD_STATISTICS_DATA &aData)
BOX2< VECTOR2I > BOX2I
Definition box2.h:914
static wxString m_DrawingSheetFileName
the name of the drawing sheet file, or empty to use the default drawing sheet
Definition base_screen.h:81
BASE_SET & set(size_t pos)
Definition base_set.h:126
Helper class to handle information needed to display 3D board.
double BiuTo3dUnits() const noexcept
Board integer units To 3D units.
void SetVisibleLayers(const std::bitset< LAYER_3D_END > &aLayers)
std::bitset< LAYER_3D_END > GetVisibleLayers() const
void SetBoard(BOARD *aBoard) noexcept
Set current board to be rendered.
void SetLayerColors(const std::map< int, COLOR4D > &aColors)
EDA_3D_VIEWER_SETTINGS * m_Cfg
std::map< int, COLOR4D > m_ColorOverrides
allows to override color scheme colors
void Set3dCacheManager(S3D_CACHE *aCacheMgr) noexcept
Update the cache manager pointer.
void Push(const wxString &aMessage=wxEmptyString, int aCommitFlags=0) override
Execute the changes.
Container for design settings for a BOARD object.
std::shared_ptr< NET_SETTINGS > m_NetSettings
std::map< int, SEVERITY > m_DRCSeverities
std::shared_ptr< DRC_ENGINE > m_DRCEngine
const VECTOR2I & GetAuxOrigin() const
BOARD_STACKUP & GetStackupDescriptor()
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition board_item.h:85
static bool SaveBoard(wxString &aFileName, BOARD &aBoard, PCB_IO_MGR::PCB_FILE_T aFormat)
static std::unique_ptr< BOARD > Load(const wxString &aFileName, PCB_IO_MGR::PCB_FILE_T aFormat, PROJECT *aProject, const OPTIONS &aOptions)
Manage one layer needed to make a physical board.
Manage layers needed to make a physical board.
const std::vector< BOARD_STACKUP_ITEM * > & GetList() const
bool SynchronizeWithBoard(const BOARD_DESIGN_SETTINGS *aSettings)
Synchronize the BOARD_STACKUP_ITEM* list with the board.
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:411
void SetCurrentVariant(const wxString &aVariant)
Definition board.cpp:2969
void Add(BOARD_ITEM *aItem, ADD_MODE aMode=ADD_MODE::INSERT, bool aSkipConnectivity=false) override
Removes an item from the container.
Definition board.cpp:1520
const PAGE_INFO & GetPageSettings() const
Definition board.h:1020
void RecordDRCExclusions()
Scan existing markers and record data from any that are Excluded.
Definition board.cpp:569
TITLE_BLOCK & GetTitleBlock()
Definition board.h:1026
const std::map< wxString, wxString > & GetProperties() const
Definition board.h:526
const FOOTPRINTS & Footprints() const
Definition board.h:465
const wxString & GetFileName() const
Definition board.h:454
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:3125
int GetFileFormatVersionAtLoad() const
Definition board.h:585
const PCB_PLOT_PARAMS & GetPlotOptions() const
Definition board.h:1023
const wxString GetLayerName(PCB_LAYER_ID aLayer) const
Return the name of a aLayer.
Definition board.cpp:932
wxString GetCurrentVariant() const
Definition board.h:530
PROJECT * GetProject() const
Definition board.h:774
wxString GetDesignRulesPath() const
Return the absolute path to the design rules file for this board.
Definition board.cpp:437
const LSET & GetEnabledLayers() const
A proxy function that calls the corresponding function in m_BoardSettings.
Definition board.cpp:1174
void SynchronizeProperties()
Copy the current project's text variables into the boards property cache.
Definition board.cpp:2949
BOX2I ComputeBoundingBox(bool aBoardEdgesOnly=false, bool aPhysicalLayersOnly=false) const
Calculate the bounding box containing all board items (or board edge segments).
Definition board.cpp:2538
void DeleteMARKERs()
Delete all MARKERS from the board.
Definition board.cpp:2066
BOARD_DESIGN_SETTINGS & GetDesignSettings()
Definition board.cpp:1290
constexpr const Vec GetCenter() const
Definition box2.h:227
void SetProjection(PROJECTION_TYPE aProjection)
Definition camera.h:197
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:153
const SFVEC3F & GetLookAtPos_T1() const
Definition camera.h:158
void RotateZ_T1(float aAngleInRadians)
Definition camera.cpp:688
bool ParametersChanged()
Definition camera.cpp:726
COMMIT & Add(EDA_ITEM *aItem, BASE_SCREEN *aScreen=nullptr)
Add a new item to the model.
Definition commit.h:74
What an export to a given target will keep, approximate, drop, or block.
void Print(REPORTER *aReporter) const
void Merge(const COMPATIBILITY_REPORT &aOther)
bool IsLossy() const
True if anything is approximated or dropped. This is what triggers the lossy warning.
bool IsBlocked() const
True if some feature makes the target impossible. The user cannot export anyway.
The dialog to create footprint position files and choose options (one or 2 files, units and force all...
File-compare dialog (Phase 7).
Resolve a three-way merge by choosing a version for each conflicting item.
const KICAD_DIFF::MERGE_PLAN & GetResolvedPlan() const
Returns the plan with the user's resolutions applied.
SIDE
Which side's version the canvas should currently display.
A dialog to set the plot options and create plot files in various formats.
Definition dialog_plot.h:37
int ShowModal() override
Stage related design and library files together, retaining originals until every write is ready.
const wxString & GetRecoveryError() const
Recovery details are nonempty only if restoring an original or removing a new output failed.
wxString Commit()
Replace all destinations, rolling back earlier replacements if a rename fails.
wxString Stage(const wxString &aDestination)
Return an exclusively-created sibling staging file, or empty if it cannot be created.
bool Create(const wxString &aDirectory, bool aOwnContents=false)
bool WriteJsonReport(const wxString &aFullFileName)
bool WriteTextReport(const wxString &aFullFileName)
Helper to handle drill precision format in excellon files.
static DS_DATA_MODEL & GetTheInstance()
Return the instance of DS_DATA_MODEL used in the application.
void SetSheetPath(const std::string &aSheetPath)
Set the sheet path displayed in the title block.
void SetVariantName(const std::string &aVariant)
Set the current variant name and description to be shown on the drawing sheet.
void SetVariantDesc(const std::string &aVariantDesc)
void SetSheetName(const std::string &aSheetName)
Set the sheet name displayed in the title block.
void SetIsFirstPage(bool aIsFirstPage)
Change if this is first page.
void SetFileName(const std::string &aFileName)
Set the file name displayed in the title block.
LAYER_PRESET_3D * FindPreset(const wxString &aName)
std::vector< LAYER_PRESET_3D > m_LayerPresets
Create Excellon drill, drill map, and drill report files.
void SetFormat(bool aMetric, ZEROS_FMT aZerosFmt=DECIMAL_FORMAT, int aLeftDigits=0, int aRightDigits=0)
Initialize internal parameters to match the given format.
bool CreateDrillandMapFilesSet(const wxString &aPlotDirectory, bool aGenDrill, bool aGenMap, REPORTER *aReporter=nullptr)
Create the full set of Excellon drill file for the board.
void SetOptions(bool aMirror, bool aMinimalHeader, const VECTOR2I &aOffset, bool aMerge_PTH_NPTH)
Initialize internal parameters to match drill options.
void SetRouteModeForOvalHoles(bool aUseRouteModeForOvalHoles)
wxString m_outputFile
Wrapper to expose an API for writing VRML files, without exposing all the many structures used in the...
Definition export_vrml.h:33
bool ExportVRML_File(PROJECT *aProject, wxString *aMessages, const wxString &aFullFileName, double aMMtoWRMLunit, bool aIncludeUnspecified, bool aIncludeDNP, bool aExport3DFiles, bool aUseRelativePaths, const wxString &a3D_Subdir, double aXRef, double aYRef)
Exports the board and its footprint shapes 3D (vrml files only) as a vrml file.
void SetVariantNames(const std::vector< wxString > &aVariantNames)
std::vector< BOM_FIELD > GetFieldsOrdered()
static const wxString ITEM_NUMBER_VARIABLE
wxString Export(const BOM_FMT_PRESET &aSettings)
int GetFieldNameCol(const wxString &aFieldName) const
void ApplyBomPreset(const BOM_PRESET &aPreset)
void AddColumn(const wxString &aFieldName, const wxString &aLabel, bool aAddedByUser) override
void SetCurrentVariant(const wxString &aVariantName) override
Select the displayed variant, retaining un-applied edits in every variant.
Provide an extensible class to resolve 3D model paths.
wxString ResolvePath(const wxString &aFileName, const wxString &aWorkingPath, std::vector< const EMBEDDED_FILES * > aEmbeddedFilesStack)
Determine the full path of the given file name.
void SetProgramBase(PGM_BASE *aBase)
Set a pointer to the application's PGM_BASE instance used to extract the local env vars.
bool SetProject(const PROJECT *aProject, bool *flgChanged=nullptr)
Set the current KiCad project directory as the first entry in the model path list.
const FOOTPRINT_REFERENCE_LIST & GetReferenceList() const
void UpdateReferences(const FOOTPRINT_REFERENCE_LIST &aRefs)
An interface to the global shared library manager that is schematic-specific and linked to one projec...
This is the minimal equivalent to the SCH_REFERENCE so we can provide similar non-null guarantees thr...
const LIB_ID & GetFPID() const
Definition footprint.h:417
void GetFields(std::vector< PCB_FIELD * > &aVector, bool aVisibleOnly) const
Populate a std::vector with PCB_TEXTs.
void SetPath(const wxString &aPath)
void Save(FOOTPRINT *aFootprintFilter=nullptr)
Save the footprint cache or a single footprint from it to disk.
boost::ptr_map< wxString, FP_CACHE_ENTRY > & GetFootprints()
Export board to GenCAD file format.
void UseIndividualShapes(bool aUnique)
Make pad shapes unique.
void UsePinNamesUnique(bool aUnique)
Make pin names unique.
void StoreOriginCoordsInFile(bool aStore)
Store origin coordinate in GenCAD file.
void FlipBottomPads(bool aFlip)
Flip pad shapes on the bottom side.
void SetPlotOffet(VECTOR2I aOffset)
Set the coordinates offset when exporting items.
bool WriteFile(const wxString &aFullFileName)
Export a GenCAD file.
void SetMapFileFormat(PLOT_FORMAT aMapFmt)
Initialize the format for the drill map file.
bool GenDrillReportFile(const wxString &aFullFileName, REPORTER *aReporter=nullptr)
Create a plain text report file giving a list of drill values and drill count for through holes,...
GERBER_JOBFILE_WRITER is a class used to create Gerber job file a Gerber job file stores info to make...
bool CreateJobFile(const wxString &aFullFilename)
Creates a Gerber job file.
void AddGbrFile(PCB_LAYER_ID aLayer, wxString &aFilename)
add a gerber file name and type in job file list
virtual bool EndPlot() override
Used to create Gerber drill files.
bool CreateDrillandMapFilesSet(const wxString &aPlotDirectory, bool aGenDrill, bool aGenMap, bool aGenTenting, REPORTER *aReporter=nullptr)
Create the full set of Excellon drill file for the board filenames are computed from the board name,...
void SetOptions(const VECTOR2I &aOffset)
Initialize internal parameters to match drill options.
void SetFormat(int aRightDigits=6)
Initialize internal parameters to match the given format.
bool Export(const wxString &aFullFileName) const
Generate IDFv3 compliant board (*.emn) and library (*.emp) files for aPcb.
Reporter handed to an importer during an import job.
void Flush(IMPORT_REPORT_FORMAT aFormat, IMPORT_REPORT_DATA &aData)
Move the collected messages into aData when a report will be written, otherwise forward them to the j...
An exception saying that the user cancelled an interactive part of a load, import,...
Hold an error message and may be used when throwing exceptions containing meaningful error messages.
virtual const wxString What() const
A composite of Problem() and Where()
Wrapper to expose an API for writing IPC-D356 files.
Definition export_d356.h:60
bool Write(const wxString &aFilename)
Generates and writes the netlist to a given path.
wxString m_inputB
Comparison document (file or directory)
wxString m_inputA
Reference document (file or directory)
void Register(const std::string &aJobTypeName, std::function< int(JOB *job)> aHandler, std::function< bool(JOB *job, wxWindow *aParent)> aConfigHandler)
JOB_DISPATCHER(KIWAY *aKiway)
PROGRESS_REPORTER * m_progressReporter
REPORTER * m_reporter
wxString GetSelectedVariant() const
std::vector< wxString > m_variantNames
wxString m_stringDelimiter
wxString m_fieldDelimiter
bool m_includeByteOrderMark
wxString m_filename
wxString m_filterString
std::vector< wxString > m_fieldsOrdered
wxString m_refRangeDelimiter
std::vector< wxString > m_fieldsLabels
wxString m_refDelimiter
std::vector< wxString > m_fieldsGroupBy
wxString m_bomFmtPresetName
wxString m_sortField
wxString m_bomPresetName
BOM_FILTER_SCOPE m_filterScope
JOB_EXPORT_PCB_3D::FORMAT m_format
EXPORTER_STEP_PARAMS m_3dparams
Despite the name; also used for other formats.
std::vector< wxString > m_variantNames
wxString GetSettingsDialogTitle() const override
ODB_COMPRESSION m_compressionMode
@ ALL_LAYERS_ONE_FILE
DEPRECATED MODE.
GEN_MODE m_pdfGenMode
uused by the cli, will be removed when the other behavior is deprecated
LSEQ m_plotOnAllLayersSequence
Layers to include on all individual layer prints.
std::optional< wxString > m_argLayers
std::optional< wxString > m_argCommonLayers
wxString m_variant
Variant name for variant-aware filtering.
void SetDefaultOutputPath(const wxString &aReferenceName)
void SetDefaultOutputPath(const wxString &aReferenceName)
wxString m_libraryPath
wxString m_outputLibraryPath
Job: diff two PCB files end-to-end via PCB_DIFFER.
wxString m_outputFile
Where to write the downgraded copy.
bool m_dropInsteadOfApproximate
Omit features that would otherwise be approximated.
wxString m_filename
Input board file (may be empty to downgrade only libraries)
wxString m_libraryDir
If set, downgrade every .kicad_mod footprint under this folder too.
COMPATIBILITY_REPORT m_report
Filled by the handler so the caller can show it.
wxString m_variant
Flatten this variant into the export. Empty means the base design.
bool m_inPlace
Internal project export operating only on its prepared copy.
wxString m_target
Target release name, e.g. "KiCad 10.0". Empty means the newest.
bool m_dryRun
Only classify and fill m_report, do not write anything.
bool m_force
Export even when features must be dropped or approximated.
bool m_saveBoard
Definition job_pcb_drc.h:36
bool m_reportAllTrackErrors
Definition job_pcb_drc.h:32
bool m_refillZones
Definition job_pcb_drc.h:35
Job to import a non-KiCad PCB file to KiCad format.
wxString m_importPcbId
EasyEDA Pro PCB document identifier selected by project-level import.
std::map< wxString, wxString > m_layerMap
Explicit overrides from source layer name to KiCad layer name (canonical board-file name,...
std::map< wxString, wxString > m_netNameMap
bool m_isPartOfMultiBoardProject
Avoid promoting an individual board's source settings into shared project settings.
std::vector< std::pair< wxString, wxString > > m_projectBoards
PCB document identifier and source name pairs found during a probe.
wxString m_reportFile
IMPORT_REPORT_FORMAT m_reportFormat
wxString m_inputFile
VECTOR3D m_lightBottomIntensity
VECTOR3D m_lightTopIntensity
VECTOR3D m_lightCameraIntensity
VECTOR3D m_rotation
wxString m_filename
bool m_useBoardStackupColors
VECTOR3D m_lightSideIntensity
std::string m_appearancePreset
bool m_exitCodeViolations
Definition job_rc.h:52
int m_severity
Definition job_rc.h:49
UNITS m_units
Definition job_rc.h:48
OUTPUT_FORMAT m_format
Definition job_rc.h:50
wxString m_filename
Definition job_rc.h:47
An simple container class that lets us dispatch output jobs to kifaces.
Definition job.h:184
void SetConfiguredOutputPath(const wxString &aPath)
Sets the configured output path for the job, this path is always saved to file.
Definition job.cpp:156
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:149
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()
Keeps the documents drawn on a pair of diff canvases alive.
Diff two .pretty footprint library directories.
static std::pair< std::vector< std::unique_ptr< FOOTPRINT > >, FOOTPRINT_MAP > LoadLibrary(const wxString &aPrettyPath)
Load a .pretty directory into a FOOTPRINT_MAP.
DOCUMENT_DIFF Diff() override
Produce a DOCUMENT_DIFF of the inputs the concrete differ was constructed with.
std::map< wxString, const FOOTPRINT * > FOOTPRINT_MAP
Three-way merge plan generator.
MERGE_PLAN Plan(const DOCUMENT_DIFF &aAncestorOurs, const DOCUMENT_DIFF &aAncestorTheirs) const
Plan the merge given the canonical pair of diffs.
const REPORT & GetReport() const
std::vector< std::unique_ptr< ITEM > > Apply()
Diff two already-parsed BOARDs and produce a DOCUMENT_DIFF.
Definition pcb_differ.h:62
DOCUMENT_DIFF Diff() override
Produce a DOCUMENT_DIFF of the inputs the concrete differ was constructed with.
Materialize a MERGE_PLAN into a real merged BOARD.
std::unique_ptr< BOARD > Apply()
Produce the merged board.
const REPORT & GetReport() const
Read the new s-expression based KiCad netlist format.
virtual void LoadNetlist() override
Load the contents of the netlist file into aNetlist.
A color representation with 4 components: red, green, blue, alpha.
Definition color4d.h:101
A minimalistic software bus for communications between various DLLs/DSOs (DSOs) within the same KiCad...
Definition kiway.h:348
virtual KIWAY_PLAYER * Player(FRAME_T aFrameType, bool doCreate=true, wxTopLevelWindow *aParent=nullptr)
Return the KIWAY_PLAYER* given a FRAME_T.
Definition kiway.cpp:401
@ FACE_SCH
eeschema DSO
Definition kiway.h:355
Plugin class for import plugins that support remappable layers.
void AsyncLoad()
Loads all available libraries for this adapter type in the background.
const UTF8 & GetLibItemName() const
Definition lib_id.h:98
LSEQ is a sequence (and therefore also a set) of PCB_LAYER_IDs.
Definition lseq.h:47
LSET is a set of PCB_LAYER_IDs.
Definition lset.h:37
static const LSET & AllCuMask()
return AllCuMask( MAX_CU_LAYERS );
Definition lset.cpp:604
LSEQ UIOrder() const
Return the copper, technical and user layers in the order shown in layer widget.
Definition lset.cpp:739
LSEQ SeqStackupForPlotting() const
Return the sequence that is typical for a bottom-to-top stack-up.
Definition lset.cpp:400
static LSET AllNonCuMask()
Return a mask holding all layer minus CU layers.
Definition lset.cpp:623
static LSET AllCuMask(int aCuLayerCount)
Return a mask holding the requested number of Cu PCB_LAYER_IDs.
Definition lset.cpp:595
static const LSET & AllLayersMask()
Definition lset.cpp:637
static const LSET & InternalCuMask()
Return a complete set of internal copper layers which is all Cu layers except F_Cu and B_Cu.
Definition lset.cpp:573
static wxString Name(PCB_LAYER_ID aLayerId)
Return the fixed name association with aLayerId.
Definition lset.cpp:184
@ MARKER_DRAWING_SHEET
Definition marker_base.h:52
bool SaveToFile(const wxString &aDirectory="", bool aForce=false) override
Calls Store() and then saves the JSON document contents into the parent JSON_SETTINGS.
void CopyFrom(NET_SETTINGS &aOther)
Deep-copy the persisted contents of aOther into this instance.
static bool EnsurePathExists(const wxString &aPath, bool aPathToFile=false)
Attempts to create a given path if it does not exist.
Definition paths.cpp:470
int RunMerge(KICAD_DIFF::DOC_KIND aKind, const wxString &aAncestor, const wxString &aOurs, const wxString &aTheirs, const wxString &aOutput, bool aInteractive, bool aSingleFile, REPORTER *aReporter)
Non-job entry points (reached via the kiface KIFACE_MERGE_DOCUMENT / KIFACE_OPEN_DIFF_DIALOG function...
bool preparePlotLayers(JOB_EXPORT_PCB_PLOT *aJob, BOARD *aBoard, TOOL_MANAGER *aToolManager)
int JobExportSpecctraDsn(JOB *aJob)
void refillFabZones(const JOB_EXPORT_PCB_FAB &aJob, BOARD *aBoard)
DS_PROXY_VIEW_ITEM * getDrawingSheetProxyView(BOARD *aBrd)
wxString resolveJobOutputPath(JOB *aJob, BOARD *aBoard, const wxString *aDrawingSheet=nullptr)
int runPcbMerge(const wxString &aAncestor, const wxString &aOurs, const wxString &aTheirs, const wxString &aOutput, bool aInteractive)
void loadOverrideDrawingSheet(BOARD *brd, const wxString &aSheetPath)
PCBNEW_JOBS_HANDLER(KIWAY *aKiway)
TOOL_MANAGER * getToolManager(BOARD *aBrd)
std::unique_ptr< BOARD > m_cliBoard
BOARD * getBoard(const wxString &aPath=wxEmptyString)
wxString resolvePlotOutputPath(JOB_EXPORT_PCB_PLOT *aJob, BOARD *aBoard, PLOT_FORMAT aFormat, bool aSingleOutput)
int runFpLibMerge(const wxString &aAncestor, const wxString &aOurs, const wxString &aTheirs, const wxString &aOutput, bool aSingleFile)
std::unique_ptr< TOOL_MANAGER > m_toolManager
int OpenDiffDialog(KICAD_DIFF::DOC_KIND aKind, const wxString &aFileA, const wxString &aFileB, const wxString &aLabelA, const wxString &aLabelB, wxWindow *aParent, REPORTER *aReporter)
LSEQ convertLayerArg(wxString &aLayerString, BOARD *aBoard) const
void ClearCachedBoard()
Clear the cached CLI board so the next job reloads from the current project.
int doFpExportSvg(JOB_FP_EXPORT_SVG *aSvgJob, const FOOTPRINT *aFootprint)
KICAD_DIFF::DIFF_CANVAS_CONTEXT * AttachDiffCanvases(KICAD_DIFF::DOC_KIND aKind, const wxString &aFileA, const wxString &aFileB, WIDGET_DIFF_CANVAS *aBefore, WIDGET_DIFF_CANVAS *aAfter, KICAD_DIFF::DOCUMENT_DIFF &aDiff, REPORTER *aReporter)
Diff two boards for a window outside the kiface, drawing the first on aBefore and the second on aAfte...
BOARD * GetBoard() const
PCB_DIFF_CANVAS_CONTEXT(WIDGET_DIFF_CANVAS *aBefore, WIDGET_DIFF_CANVAS *aAfter)
The main frame for Pcbnew.
A PCB_IO derivation for saving and loading Pcbnew s-expression formatted files.
void FootprintDelete(const wxString &aLibraryPath, const wxString &aFootprintName, const std::map< std::string, UTF8 > *aProperties=nullptr) override
Delete aFootprintName from the library at aLibraryPath.
void FootprintEnumerate(wxArrayString &aFootprintNames, const wxString &aLibraryPath, bool aBestEfforts, const std::map< std::string, UTF8 > *aProperties=nullptr) override
Return a list of footprint names contained within the library at aLibraryPath.
std::unique_ptr< FOOTPRINT > ImportFootprint(const wxString &aFootprintPath, wxString &aFootprintNameOut, const std::map< std::string, UTF8 > *aProperties=nullptr) override
Load a single footprint from aFootprintPath and put its name in aFootprintNameOut.
bool FootprintExists(const wxString &aLibraryPath, const wxString &aFootprintName, const std::map< std::string, UTF8 > *aProperties=nullptr) override
Check for the existence of a footprint.
void FootprintSave(const wxString &aLibraryPath, const FOOTPRINT *aFootprint, const std::map< std::string, UTF8 > *aProperties=nullptr) override
Write aFootprint to an existing library located at aLibraryPath.
void SaveBoard(const wxString &aFileName, BOARD &aBoard, const std::map< std::string, UTF8 > *aProperties=nullptr) override
Write aBoard to a storage file in a format that this PCB_IO implementation knows about or it can be u...
void Format(const BOARD_ITEM *aItem) const
Output aItem to aFormatter in s-expression format.
void SetOutputFormatter(OUTPUTFORMATTER *aFormatter)
static bool ConvertLibrary(const std::map< std::string, UTF8 > &aOldFileProps, const wxString &aOldFilePath, const wxString &aNewFilePath, REPORTER *aReporter)
Convert a schematic symbol library to the latest KiCad format.
PCB_FILE_T
The set of file types that the PCB_IO_MGR knows about, and for which there has been a plugin written,...
Definition pcb_io_mgr.h:52
@ KICAD_SEXP
S-expression Pcbnew file format.
Definition pcb_io_mgr.h:54
@ EASYPC
Easy-PC / DesignSpark PCB.
Definition pcb_io_mgr.h:76
@ TARGET3001
TARGET 3001! project.
Definition pcb_io_mgr.h:77
@ ALTIUM_DESIGNER
Definition pcb_io_mgr.h:59
@ CADSTAR_PCB_ARCHIVE
Definition pcb_io_mgr.h:60
@ PCB_FILE_UNKNOWN
0 is not a legal menu id on Mac
Definition pcb_io_mgr.h:53
static PCB_IO * FindPlugin(PCB_FILE_T aFileType)
Return a PCB_IO which the caller can use to import, export, save, or load design documents.
static PCB_FILE_T FindPluginTypeFromBoardPath(const wxString &aFileName, int aCtl=0)
Return a plugin type given a path for a board file.
static PCB_FILE_T GuessPluginTypeFromLibPath(const wxString &aLibPath, int aCtl=0)
Return a plugin type given a footprint library's libPath.
static bool ImportGeneratesProjectLibrary(PCB_FILE_T aFileType)
Return true when importing aFileType should materialize a project footprint library.
static const wxString ShowType(PCB_FILE_T aFileType)
Return a brief name for a plugin given aFileType enum.
std::unique_ptr< BOARD > LoadBoard(const wxString &aFileName, const std::map< std::string, UTF8 > *aProperties=nullptr, PROJECT *aProject=nullptr)
Load information from some input file format that this PCB_IO implementation knows about into new BOA...
Definition pcb_io.cpp:72
static void PlotJobToPlotOpts(PCB_PLOT_PARAMS &aOpts, JOB_EXPORT_PCB_PLOT *aJob, REPORTER &aReporter)
True when the board's out-of-date-chart policy regenerated charts during the last Plot().
bool Plot(const wxString &aOutputPath, const LSEQ &aLayersToPlot, const LSEQ &aCommonLayers, bool aUseGerberFileExtensions, bool aOutputPathIsSingle=false, std::optional< wxString > aLayerName=std::nullopt, std::optional< wxString > aSheetName=std::nullopt, std::optional< wxString > aSheetPath=std::nullopt, std::vector< wxString > *aOutputFiles=nullptr)
static void BuildPlotFileName(wxFileName *aFilename, const wxString &aOutputDir, const wxString &aSuffix, const wxString &aExtension)
Complete a plot filename.
Parameters and options when plotting/printing a board.
LSEQ GetPlotOnAllLayersSequence() const
void SetLayerSelection(const LSET &aSelection)
void SetPlotOnAllLayersSequence(LSEQ aSeq)
LSET GetLayerSelection() const
bool GetUseGerberProtelExtensions() const
virtual SETTINGS_MANAGER & GetSettingsManager() const
Definition pgm_base.h:123
Used to create Gerber drill files.
const wxString GetPlaceFileName(const wxString &aFullBaseFilename, PCB_LAYER_ID aLayer) const
void SetVariant(const wxString &aVariant)
Set the variant name for variant-aware filtering.
int CreatePlaceFile(const wxString &aFullFilename, PCB_LAYER_ID aLayer, bool aIncludeBrdEdges, bool aExcludeDNP, bool aExcludeBOM)
Create an pnp gerber file.
The ASCII format of the kicad place file is:
static wxString DecorateFilename(const wxString &aBaseName, bool aFront, bool aBack)
std::string GenPositionData()
build a string filled with the position data
void SetVariant(const wxString &aVariant)
Set the variant name for variant-aware export.
Base plotter engine class.
Definition plotter.h:138
virtual bool EndPlot()=0
bool Finish() override
Runs prettification over the buffered bytes, writes them to the sibling temp file,...
Definition richio.cpp:710
The backing store for a PROJECT, in JSON format.
wxString m_BoardDrawingSheetFile
PcbNew params.
bool Store() override
Stores the current parameters into the JSON document represented by this object Note: this doesn't do...
static S3D_CACHE * Get3DCacheManager(PROJECT *aProject, bool updateProjDir=false)
Return a pointer to an instance of the 3D cache manager.
static FILENAME_RESOLVER * Get3DFilenameResolver(PROJECT *aProject)
Accessor for 3D path resolver.
static FOOTPRINT_LIBRARY_ADAPTER * FootprintLibAdapter(PROJECT *aProject)
Container for project specific data.
Definition project.h:63
virtual const wxString GetProjectFullName() const
Return the full path and name of the project.
Definition project.cpp:270
virtual const wxString GetProjectPath() const
Return the full path of the project.
Definition project.cpp:276
virtual const wxString GetProjectName() const
Return the short name of the project.
Definition project.cpp:288
virtual void ApplyTextVars(const std::map< wxString, wxString > &aVarsMap)
Applies the given var map, it will create or update existing vars.
Definition project.cpp:228
virtual PROJECT_FILE & GetProjectFile() const
Definition project.h:201
void SetReporters(std::shared_ptr< REPORTER > aActivityReporter, std::shared_ptr< REPORTER > aWarningReporter)
Set the reporters for activity and warning messages.
bool Redraw(bool aIsMoving) override
Redraw the view.
void SetCurWindowSize(const wxSize &aSize) override
Before each render, the canvas will tell the render what is the size of its windows,...
A pure virtual class used to derive REPORTER objects from.
Definition reporter.h:73
virtual REPORTER & Report(const wxString &aText, SEVERITY aSeverity=RPT_SEVERITY_UNDEFINED)
Report a string with a given severity.
Definition reporter.h:102
RAII class that sets an value at construction and resets it to the original value at destruction.
bool SaveProjectCopy(const wxString &aFullPath, PROJECT *aProject=nullptr)
Save a copy of the current project under the given path.
bool LoadProject(const wxString &aFullPath, bool aSetActive=true)
Load a project or sets up a new project with a specified path.
PROJECT * GetProject(const wxString &aFullPath) const
Retrieve a loaded project by name.
bool UnloadProject(PROJECT *aProject, bool aSave=true)
Save, unload and unregister the given PROJECT.
PROJECT & Prj() const
A helper while we are not MDI-capable – return the one and only project.
bool IsProjectOpenNotDummy() const
Helper for checking if we have a project open that is not a dummy project.
Is a LINE_READER that reads from a multiline 8 bit wide std::string.
Definition richio.h:225
TOOL_MANAGER * GetToolManager() const
Return the MVC controller.
Master controller class:
T * GetTool()
Return the tool of given type or nullptr if there is no such tool registered.
TOOL_BASE * FindTool(int aId) const
Search for a tool with given ID.
void RegisterTool(TOOL_BASE *aTool)
Add a tool to the manager set and sets it up.
void SetEnvironment(EDA_ITEM *aModel, KIGFX::VIEW *aView, KIGFX::VIEW_CONTROLS *aViewControls, APP_SETTINGS_BASE *aSettings, TOOLS_HOLDER *aFrame)
Set the work environment (model, view, view controls and the parent window).
void Pan_T1(const SFVEC3F &aDeltaOffsetInc) override
void SetT0_and_T1_current_T() override
This will set T0 and T1 with the current values.
void Interpolate(float t) override
It will update the matrix to interpolate between T0 and T1 values.
An 8 bit string that is assuredly encoded in UTF8, and supplies special conversion support to and fro...
Definition utf8.h:67
bool empty() const
Definition utf8.h:105
wxString wx_str() const
Definition utf8.cpp:41
GAL-backed canvas for visualizing a KICAD_DIFF::DIFF_SCENE.
Handle actions specific to filling copper zones.
wxString GetGeneratedFieldDisplayName(const wxString &aSource)
Returns any variables unexpanded, e.g.
Definition common.cpp:481
bool IsGeneratedField(const wxString &aFieldName)
Returns true if the entire string is generated, e.g is a single text var reference.
Definition common.cpp:494
wxString GetDefaultPlotExtension(PLOT_FORMAT aFormat)
Return the default plot extension for a format.
void DisplayErrorMessage(wxWindow *aParent, const wxString &aText, const wxString &aExtraInfo)
Display an error message with aMessage.
Definition confirm.cpp:217
This file is part of the common library.
KIFACE_BASE & Kiface()
Global KIFACE_BASE "get" accessor.
Definition cvpcb.cpp:165
static DRILL_PRECISION precisionListForInches(2, 4)
static DRILL_PRECISION precisionListForMetric(3, 3)
static bool empty(const wxTextEntryBase *aCtrl)
wxString DowngradeLibraryCopyPath(const wxString &aLibrary, const wxString &aOutputDirectory)
bool ValidateDowngradeLibrarySource(const wxString &aSource, wxString &aError)
bool ValidateDowngradeLibraryCopy(const wxString &aSource, const wxString &aDestination, wxString &aError)
bool ValidateFreshDowngradeOutput(const wxString &aOutput, wxString &aError)
bool CopyDowngradeLibraryDirectory(const wxString &aSource, const wxString &aDestination, DOWNGRADE_OUTPUT_DIRECTORIES &aDirectories, wxString &aError)
wxString DowngradeLibraryFilesInPlace(const wxString &aDir, const wxString &aGlob, int aStampVersion, const std::function< wxString(const wxString &)> &aFindForbidden, const std::function< DOWNGRADE_FILE_RESULT(const wxString &, const wxString &)> &aConvert, DOWNGRADE_FILE_TRANSACTION *aTransaction=nullptr, wxString *aRecoveryError=nullptr)
Downgrade every library file matching aGlob under aDir in place.
bool IsDowngradeWorkingArtifact(const wxString &aPath)
Editor working state (autosaves, local history, backups) holds current-format copies nothing downgrad...
DOWNGRADE_FILE_RESULT
What a per-file converter did with one library file.
const std::vector< DOWNGRADE_TARGET > & GetDowngradeTargets()
Supported downgrade targets, newest first.
const DOWNGRADE_TARGET * FindDowngradeTarget(const wxString &aNameOrId)
Match a user-supplied target string against name or stable id, case-insensitively.
wxString DowngradeTargetNames()
The valid target choices as one user-facing list, for error messages.
#define _(s)
#define FOLLOW_PLOT_SETTINGS
#define FOLLOW_PCB
EDA_UNITS
Definition eda_units.h:44
std::vector< FOOTPRINT_REF > FOOTPRINT_REFERENCE_LIST
FOOTPRINT_IMPORT_RECONCILE_RESULT ReconcileImportedFootprints(std::vector< std::unique_ptr< FOOTPRINT > > aDefinitions, BOARD &aBoard, PROJECT &aProject, const wxString &aBoardPath, const std::map< std::string, UTF8 > *aProperties, REPORTER &aReporter)
Reconcile aBoard against the definitions an importer retained while loading it.
@ FRAME_PCB_EDITOR
Definition frame_type.h:38
@ FRAME_SCH
Definition frame_type.h:30
Classes used in drill files, map files and report files generation.
Classes used in drill files, map files and report files generation.
int RefreshGeneratedTables(BOARD &aBoard)
Bring every generated table on the board up to date and return how many were rebuilt.
Classes used to generate a Gerber job file in JSON.
Classes used in place file generation.
nlohmann::json json
Definition gerbview.cpp:50
static const std::string LegacySchematicFileExtension
static const std::string BrepFileExtension
static const std::string SymbolLibraryTableFileName
static const std::string JpegFileExtension
static const std::string GerberJobFileExtension
static const std::string GerberFileExtension
static const std::string XaoFileExtension
static const std::string ReportFileExtension
static const std::string GltfBinaryFileExtension
static const std::string ProjectFileExtension
static const std::string PngFileExtension
static const std::string FootprintPlaceFileExtension
static const std::string JsonFileExtension
static const std::string IdfV3BoardFileExtension
static const std::string KiCadSchematicFileExtension
static const std::string CsvFileExtension
static const std::string U3DFileExtension
static const std::string PdfFileExtension
static const std::string Ipc2581FileExtension
static const std::string FootprintLibraryTableFileName
static const std::string GencadFileExtension
static const std::string StlFileExtension
static const std::string IpcD356FileExtension
static const std::string PlyFileExtension
static const std::string SpecctraDsnFileExtension
static const std::string StepFileExtension
static const std::string SVGFileExtension
static const std::string DesignRulesFileExtension
static const std::string VrmlFileExtension
static const std::string KiCadFootprintFileExtension
static const std::string ArchiveFileExtension
static const std::string KiCadPcbFileExtension
std::string source
#define TO_UTF8(wxstring)
Convert a wxString to a UTF8 encoded C string for all wxWidgets build modes.
Definition idf_helpers.h:37
bool ApplyImportedNetNameMap(BOARD &aBoard, const std::map< wxString, wxString > &aNames, REPORTER &aReporter)
Apply caller-selected imported net names without changing net identity or connectivity.
std::unique_ptr< T > IO_RELEASER
Helper to hold and release an IO_BASE object when exceptions are thrown.
Definition io_mgr.h:33
bool GenerateIpc2581File(JOB_EXPORT_PCB_IPC2581 &aJob, BOARD *aBoard, PROGRESS_REPORTER *aProgressReporter, REPORTER *aReporter)
Write the IPC-2581 file or archive of aJob and record it as a job output.
wxString DefaultImportOutputPath(const wxString &aInputFile, const wxString &aKiCadExt)
Build the default output path for an import by swapping the input file's extension for the given KiCa...
bool WriteImportReport(REPORTER *aReporter, IMPORT_REPORT_FORMAT aFormat, const wxString &aReportFile, const IMPORT_REPORT_DATA &aData)
Emit an import report in the requested format to aReportFile, or to aReporter (at INFO severity) when...
@ KIFACE_NETLIST_SCHEMATIC
Definition kiface_ids.h:63
#define KICTL_KICAD_ONLY
chosen file is from KiCad according to user
wxString LayerName(int aLayer)
Returns the default display name for a given layer.
Definition layer_id.cpp:31
@ LAYER_3D_BACKGROUND_TOP
Definition layer_ids.h:581
@ LAYER_3D_BACKGROUND_BOTTOM
Definition layer_ids.h:580
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:76
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_CANCELLED
The user cancelled the operation.
Definition exit_codes.h:45
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 ExportBoardToSpecctraFile(BOARD *aBoard, const wxString &aFullFilename, REPORTER *aReporter=nullptr)
Helper method to export board to DSN file.
std::vector< CONFLICT_LIST_ENTRY > BuildConflictList(const MERGE_PLAN &aPlan)
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.
wxString OlderFormatNotice(int aAncestorVersion, int aOursVersion, int aTheirsVersion, int aCurrentVersion)
Empty when every side is already in the current file format.
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...
void ConfigurePcbDiffCanvasContext(WIDGET_DIFF_CANVAS &aCanvas, BOARD *aReference, BOARD *aComparison, const KIGFX::COLOR4D &aColor, const std::map< KIID, KIGFX::COLOR4D > &aOverrides, const std::vector< KIGFX::VIEW_ITEM * > &aExtraItems, const std::map< KIID, KICAD_DIFF::CATEGORY > &aCategories)
const wxString DOC_PROP_DRAWING_SHEET
DOC_KIND
Document type a diff/merge entry point should route to, derived from a file path's extension.
int EmitDiffResult(const DOCUMENT_DIFF &aResult, const DIFF_EMIT_OPTIONS &aOptions, int aDiffExitCode, REPORTER &aReporter)
Emit a computed DOCUMENT_DIFF in the requested format.
const wxString DOC_PROP_DRC_SEVERITIES
DOCUMENT_GEOMETRY ExtractBoardGeometry(const BOARD &aBoard, const KIGFX::COLOR4D &aColor)
Extract a coarse outline of a BOARD into a DOCUMENT_GEOMETRY for use as background context in DIFF_SC...
bool ApplyProjectFilePatches(const wxString &aOutputProPath, const nlohmann::json &aSource, const std::set< wxString > &aDocProps, DOC_KIND aKind)
Higher-level orchestrator: load the existing aOutputProPath as JSON (or start from aSource if the fil...
int DiffExitCode(const DOCUMENT_DIFF &aResult)
Map a computed diff onto its CLI exit code – SUCCESS when empty, otherwise ERR_RC_VIOLATIONS.
void PackBoardStackup(const BOARD &aBoard, BoardStackup &aOut)
ODB_EXPORT_RESULT GenerateODBPPFiles(const JOB_EXPORT_PCB_ODB &aJob, BOARD *aBoard, PCB_EDIT_FRAME *aParentFrame, PROGRESS_REPORTER *aProgressReporter, REPORTER *aReporter)
Generate the ODB++ tree or archive and return the write status and the paths written.
#define SEXPR_BOARD_FILE_VERSION
Current s-expression file format version. 2 was the last legacy format version.
#define CTL_FOR_LIBRARY
Format output for a footprint library instead of clipboard or BOARD.
bool PlotFootprintToSVG(const FOOTPRINT &aFootprint, PROJECT &aProject, const std::map< wxString, wxString > *aVarOverrides, PCB_PLOT_PARAMS &aPlotOpts, const LSEQ &aLayersToPlot, const LSEQ &aLayersOnAll, const wxString &aFileName, REPORTER *aReporter)
Plot a footprint to an SVG file, with the footprint origin at the SVG origin and the page/viewBox siz...
static PCB_LAYER_ID resolveKiCadLayerName(const wxString &aName)
static int loadFootprintLibrarySide(const wxString &aPath, std::vector< std::unique_ptr< FOOTPRINT > > &aOwners, KICAD_DIFF::FP_LIB_DIFFER::FOOTPRINT_MAP &aMap, bool aAllowEmpty, REPORTER &aReporter)
static wxString footprintRefusedMessage(const wxString &aFile, const DOWNGRADE_TARGET &aTarget)
static DRILL_PRECISION precisionListForInches(2, 4)
static int plotJob(JOB_EXPORT_PCB_PLOT *aJob, PCB_PLOTTER &aPlotter, const wxString &aOutPath, bool aSingleOutput, bool aUseOverrides)
static DRILL_PRECISION precisionListForMetric(3, 3)
static SCRATCH_DOC< BOARD > loadScratchBoard(SETTINGS_MANAGER &aMgr, const wxString &aPath, bool aInitializeAfterLoad=true)
static int loadPcbDiffSides(SETTINGS_MANAGER &aMgr, REPORTER &aReporter, const wxString &aFileA, const wxString &aFileB, PCB_DIFF_SIDES &aSides)
static KICAD_DIFF::DOCUMENT_GEOMETRY footprintLibraryGeometry(const KICAD_DIFF::FP_LIB_DIFFER::FOOTPRINT_MAP &aMap, const KIGFX::COLOR4D &aColor)
static wxString downgradeFootprintLibraries(const wxString &aDir, const DOWNGRADE_TARGET &aTarget, bool aDropInsteadOfApproximate, DOWNGRADE_FILE_TRANSACTION *aTransaction=nullptr, wxString *aRecoveryError=nullptr)
static void classifyFootprintLibraries(const wxString &aDir, const DOWNGRADE_TARGET &aTarget, COMPATIBILITY_REPORT &aReport, bool aDropInsteadOfApproximate)
const wxString GetGerberProtelExtension(int aLayer)
Definition pcbplot.cpp:39
PLOTTER * StartPlotBoard(BOARD *aBoard, const PCB_PLOT_PARAMS *aPlotOpts, int aLayer, const wxString &aLayerName, const wxString &aFullFileName, const wxString &aSheetName, const wxString &aSheetPath, const wxString &aPageName=wxT("1"), const wxString &aPageNumber=wxEmptyString, const int aPageCount=1)
Open a new plotfile using the options (and especially the format) specified in the options and prepar...
void PlotBoardLayers(BOARD *aBoard, PLOTTER *aPlotter, const LSEQ &aLayerSequence, const PCB_PLOT_PARAMS &aPlotOptions)
Plot a sequence of board layer IDs.
PGM_BASE & Pgm()
The global program "get" accessor.
see class PGM_BASE
PLOT_FORMAT
The set of supported output plot formats.
Definition plotter.h:65
Plotting engines similar to ps (PostScript, Gerber, svg)
@ RPT_SEVERITY_WARNING
@ RPT_SEVERITY_ERROR
@ RPT_SEVERITY_INFO
@ RPT_SEVERITY_ACTION
#define SKIP_SET_DIRTY
Definition sch_commit.h:40
#define SKIP_UNDO
Definition sch_commit.h:38
SCRATCH_DOC< DOC > LoadScratchDoc(SETTINGS_MANAGER &aMgr, const wxString &aDocPath, Loader aLoader, ClearFn aClearFn)
Construct a SCRATCH_DOC by loading a project non-active and then handing it to the caller's document ...
T * GetAppSettings(const char *aFilename)
const int scale
MODEL3D_FORMAT_TYPE fileType(const char *aFileName)
#define OPEN_OSTREAM(var, name)
#define CLOSE_STREAM(var)
wxString ExpandVariantOutputPath(const wxString &aPath, const wxString &aVariantToken)
wxString label
wxString name
wxString fieldDelimiter
bool includeByteOrderMark
static std::vector< BOM_FMT_PRESET > BuiltInPresets()
wxString stringDelimiter
wxString refRangeDelimiter
wxString refDelimiter
Phase 8 context for the conflict canvas.
wxString formatNotice
Shown with the instructions when the merge cannot keep a side's file format.
std::function< void(WIDGET_DIFF_CANVAS &, SIDE, const std::optional< KIID_PATH > &)> drawSide
Draw a side on the canvas with the editor's own painter.
std::function< wxString(const KIID_PATH &)> describeItem
Name an item the way the editor would, so a conflict can be told from its neighbours.
An older KiCad release we can export a project back to.
int m_boardVersion
SEXPR_BOARD_FILE_VERSION for the target.
wxString m_name
User-facing name, e.g. "KiCad 10.0".
std::vector< KIGFX::COLOR4D > raytrace_lightColor
std::vector< BOM_PRESET > m_BomPresets
std::vector< BOM_FMT_PRESET > m_BomFmtPresets
Describes an imported layer and how it could be mapped to KiCad Layers.
Diff colors with defaults for light backgrounds.
Definition diff_scene.h:278
KIGFX::COLOR4D reference
Default color for source-document context geometry.
Definition diff_scene.h:292
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,...
KICAD_DIFF::DOCUMENT_DIFF diff
SCRATCH_DOC< BOARD > a
SCRATCH_DOC< BOARD > b
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:178
@ PCB_VIA_T
class PCB_VIA, a via (like a track segment on a copper layer)
Definition typeinfo.h:89
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:708
Definition of file extensions used in Kicad.
glm::vec3 SFVEC3F
Definition xv3d_types.h:40
#define ZONE_FILLER_TOOL_NAME