kicad-source/pcbnew/dialogs/dialog_board_setup.cpp
Seth Hillbrand 77797103f7 Add ability to embed files in various elements
Schematics, symbols, boards and footprints all get the ability to store
files inside their file structures.  File lookups now have a
kicad-embed:// URI to allow various parts of KiCad to refer to files
stored in this manner.

kicad-embed://datasheet.pdf references the file named "datasheet.pdf"
embedded in the document.  Embeds are allowed in schematics, boards,
symbols and footprints.  Currently supported embeddings are Datasheets,
3D Models and drawingsheets

Fixes https://gitlab.com/kicad/code/kicad/-/issues/6918

Fixes https://gitlab.com/kicad/code/kicad/-/issues/2376

Fixes https://gitlab.com/kicad/code/kicad/-/issues/17827
2024-07-15 16:06:55 -07:00

431 lines
16 KiB
C++

/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2017-2023 KiCad Developers, see AUTHORS.txt for contributors.
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <pcb_edit_frame.h>
#include <panel_setup_layers.h>
#include <panel_setup_text_and_graphics.h>
#include <panel_setup_constraints.h>
#include <panel_setup_tracks_and_vias.h>
#include <panel_setup_mask_and_paste.h>
#include <../board_stackup_manager/panel_board_stackup.h>
#include <../board_stackup_manager/panel_board_finish.h>
#include <confirm.h>
#include <board_design_settings.h>
#include <kiface_base.h>
#include <drc/drc_item.h>
#include <dialog_import_settings.h>
#include <pcb_io/pcb_io.h>
#include <pcb_io/pcb_io_mgr.h>
#include <panel_embedded_files.h>
#include <dialogs/panel_setup_severities.h>
#include <dialogs/panel_setup_rules.h>
#include <dialogs/panel_setup_teardrops.h>
#include <dialogs/panel_setup_tuning_patterns.h>
#include <dialogs/panel_setup_netclasses.h>
#include <panel_text_variables.h>
#include <project.h>
#include <project/project_file.h>
#include <settings/settings_manager.h>
#include <widgets/resettable_panel.h>
#include <widgets/wx_progress_reporters.h>
#include <wildcards_and_files_ext.h>
#include "dialog_board_setup.h"
std::mutex DIALOG_BOARD_SETUP::g_Mutex;
#define RESOLVE_PAGE( T, pageIndex ) static_cast<T*>( m_treebook->ResolvePage( pageIndex ) )
DIALOG_BOARD_SETUP::DIALOG_BOARD_SETUP( PCB_EDIT_FRAME* aFrame ) :
PAGED_DIALOG( aFrame, _( "Board Setup" ), false, false,
_( "Import Settings from Another Board..." ), wxSize( 980, 600 ) ),
m_frame( aFrame ),
m_layers( nullptr ),
m_boardFinish( nullptr ),
m_physicalStackup( nullptr ),
m_currentPage( 0 ),
m_layersPage( 0 ),
m_physicalStackupPage( 0 ),
m_boardFinishPage( 0 ),
m_textAndGraphicsPage( 0 ),
m_formattingPage( 0 ),
m_maskAndPagePage( 0 ),
m_constraintsPage( 0 ),
m_tracksAndViasPage( 0 ),
m_teardropsPage( 0 ),
m_tuningPatternsPage( 0 ),
m_netclassesPage( 0 ),
m_customRulesPage( 0 ),
m_severitiesPage( 0 )
{
SetEvtHandlerEnabled( false );
/*
* WARNING: If you change page names you MUST update calls to ShowBoardSetupDialog().
*/
m_treebook->AddPage( new wxPanel( GetTreebook() ), _( "Board Stackup" ) );
/*
* WARNING: Code currently relies on the layers setup coming before the physical stackup panel,
* and thus transferring data to the board first. See comment in
* PANEL_SETUP_BOARD_STACKUP::TransferDataFromWindow and rework this logic if it is determined
* that the order of these pages should be changed.
*/
m_layersPage = m_treebook->GetPageCount();
m_treebook->AddLazySubPage(
[this]( wxWindow* aParent ) -> wxWindow*
{
return new PANEL_SETUP_LAYERS( aParent, m_frame );
}, _( "Board Editor Layers" ) );
m_physicalStackupPage = m_treebook->GetPageCount();
m_treebook->AddLazySubPage(
[this]( wxWindow* aParent ) -> wxWindow*
{
m_layers = RESOLVE_PAGE( PANEL_SETUP_LAYERS, m_layersPage );
m_boardFinish = RESOLVE_PAGE( PANEL_SETUP_BOARD_FINISH, m_boardFinishPage );
return new PANEL_SETUP_BOARD_STACKUP( aParent, m_frame, m_layers, m_boardFinish );
}, _( "Physical Stackup" ) );
m_boardFinishPage = m_treebook->GetPageCount();
m_treebook->AddLazySubPage(
[this]( wxWindow* aParent ) -> wxWindow*
{
return new PANEL_SETUP_BOARD_FINISH( aParent, m_frame );
}, _( "Board Finish" ) );
m_maskAndPagePage = m_treebook->GetPageCount();
m_treebook->AddLazySubPage(
[this]( wxWindow* aParent ) -> wxWindow*
{
return new PANEL_SETUP_MASK_AND_PASTE( aParent, m_frame );
}, _( "Solder Mask/Paste" ) );
m_treebook->AddPage( new wxPanel( GetTreebook() ), _( "Text & Graphics" ) );
m_textAndGraphicsPage = m_treebook->GetPageCount();
m_treebook->AddLazySubPage(
[this]( wxWindow* aParent ) -> wxWindow*
{
return new PANEL_SETUP_TEXT_AND_GRAPHICS( aParent, m_frame );
}, _( "Defaults" ) );
m_formattingPage = m_treebook->GetPageCount();
m_treebook->AddLazySubPage(
[this]( wxWindow* aParent ) -> wxWindow*
{
return new PANEL_SETUP_FORMATTING( aParent, m_frame );
}, _( "Formatting" ) );
m_treebook->AddLazySubPage(
[this]( wxWindow* aParent ) -> wxWindow*
{
return new PANEL_TEXT_VARIABLES( aParent, &Prj() );
}, _( "Text Variables" ) );
m_treebook->AddPage( new wxPanel( GetTreebook() ), _( "Design Rules" ) );
m_constraintsPage = m_treebook->GetPageCount();
m_treebook->AddLazySubPage(
[this]( wxWindow* aParent ) -> wxWindow*
{
return new PANEL_SETUP_CONSTRAINTS( aParent, m_frame );
}, _( "Constraints" ) );
m_tracksAndViasPage = m_treebook->GetPageCount();
m_treebook->AddLazySubPage(
[this]( wxWindow* aParent ) -> wxWindow*
{
return new PANEL_SETUP_TRACKS_AND_VIAS( aParent, m_frame );
}, _( "Pre-defined Sizes" ) );
m_teardropsPage = m_treebook->GetPageCount();
m_treebook->AddLazySubPage(
[this]( wxWindow* aParent ) -> wxWindow*
{
return new PANEL_SETUP_TEARDROPS( aParent, m_frame );
}, _( "Teardrops" ) );
m_tuningPatternsPage = m_treebook->GetPageCount();
m_treebook->AddLazySubPage(
[this]( wxWindow* aParent ) -> wxWindow*
{
BOARD_DESIGN_SETTINGS& bds = m_frame->GetBoard()->GetDesignSettings();
return new PANEL_SETUP_TUNING_PATTERNS( aParent, m_frame,
bds.m_SingleTrackMeanderSettings,
bds.m_DiffPairMeanderSettings,
bds.m_SkewMeanderSettings );
}, _( "Length-tuning Patterns" ) );
m_netclassesPage = m_treebook->GetPageCount();
m_treebook->AddLazySubPage(
[this]( wxWindow* aParent ) -> wxWindow*
{
BOARD* board = m_frame->GetBoard();
return new PANEL_SETUP_NETCLASSES( aParent, m_frame,
m_frame->Prj().GetProjectFile().NetSettings(),
board->GetNetClassAssignmentCandidates(),
false );
}, _( "Net Classes" ) );
m_customRulesPage = m_treebook->GetPageCount();
m_treebook->AddLazySubPage(
[this]( wxWindow* aParent ) -> wxWindow*
{
return new PANEL_SETUP_RULES( aParent, m_frame );
}, _( "Custom Rules" ) );
m_severitiesPage = m_treebook->GetPageCount();
m_treebook->AddLazySubPage(
[this]( wxWindow* aParent ) -> wxWindow*
{
BOARD* board = m_frame->GetBoard();
return new PANEL_SETUP_SEVERITIES( aParent, DRC_ITEM::GetItemsWithSeverities(),
board->GetDesignSettings().m_DRCSeverities );
}, _( "Violation Severity" ) );
m_treebook->AddPage( new wxPanel( GetTreebook() ), _( "Board Data" ) );
m_embeddedFilesPage = m_treebook->GetPageCount();
m_treebook->AddLazySubPage(
[this]( wxWindow* aParent ) -> wxWindow*
{
return new PANEL_EMBEDDED_FILES( aParent, m_frame->GetBoard() );
}, _( "Embedded Files" ) );
for( size_t i = 0; i < m_treebook->GetPageCount(); ++i )
m_treebook->ExpandNode( i );
SetEvtHandlerEnabled( true );
finishDialogSettings();
if( Prj().IsReadOnly() )
{
m_infoBar->ShowMessage( _( "Project is missing or read-only. Some settings will not "
"be editable." ), wxICON_WARNING );
}
wxBookCtrlEvent evt( wxEVT_TREEBOOK_PAGE_CHANGED, wxID_ANY, 0 );
wxQueueEvent( m_treebook, evt.Clone() );
}
DIALOG_BOARD_SETUP::~DIALOG_BOARD_SETUP()
{
}
void DIALOG_BOARD_SETUP::onPageChanged( wxBookCtrlEvent& aEvent )
{
PAGED_DIALOG::onPageChanged( aEvent );
size_t page = aEvent.GetSelection();
if( m_physicalStackupPage > 0 ) // Don't run this during initialization
{
if( m_currentPage == m_physicalStackupPage || page == m_physicalStackupPage )
{
m_layers = RESOLVE_PAGE( PANEL_SETUP_LAYERS, m_layersPage );
m_physicalStackup = RESOLVE_PAGE( PANEL_SETUP_BOARD_STACKUP, m_physicalStackupPage );
}
// Ensure layer page always gets updated even if we aren't moving towards it
if( m_currentPage == m_physicalStackupPage )
m_layers->SyncCopperLayers( m_physicalStackup->GetCopperLayerCount() );
if( page == m_physicalStackupPage )
m_physicalStackup->OnLayersOptionsChanged( m_layers->GetUILayerMask() );
else if( Prj().IsReadOnly() )
KIUI::Disable( m_treebook->GetPage( page ) );
}
m_currentPage = page;
}
void DIALOG_BOARD_SETUP::onAuxiliaryAction( wxCommandEvent& aEvent )
{
DIALOG_IMPORT_SETTINGS importDlg( this, m_frame );
if( importDlg.ShowModal() == wxID_CANCEL )
return;
wxFileName boardFn( importDlg.GetFilePath() );
wxFileName projectFn( boardFn );
projectFn.SetExt( FILEEXT::ProjectFileExtension );
if( !m_frame->GetSettingsManager()->LoadProject( projectFn.GetFullPath(), false ) )
{
wxString msg = wxString::Format( _( "Error importing settings from board:\n"
"Associated project file %s could not be loaded" ),
projectFn.GetFullPath() );
DisplayErrorMessage( this, msg );
return;
}
m_layers = RESOLVE_PAGE( PANEL_SETUP_LAYERS, m_layersPage );
m_physicalStackup = RESOLVE_PAGE( PANEL_SETUP_BOARD_STACKUP, m_physicalStackupPage );
m_boardFinish = RESOLVE_PAGE( PANEL_SETUP_BOARD_FINISH, m_boardFinishPage );
// Flag so user can stop work if it will result in deleted inner copper layers
// and still clean up this function properly.
bool okToProceed = true;
PROJECT* otherPrj = m_frame->GetSettingsManager()->GetProject( projectFn.GetFullPath() );
IO_RELEASER<PCB_IO> pi( PCB_IO_MGR::PluginFind( PCB_IO_MGR::KICAD_SEXP ) );
BOARD* otherBoard = nullptr;
try
{
WX_PROGRESS_REPORTER progressReporter( this, _( "Loading PCB" ), 1 );
pi->SetProgressReporter( &progressReporter );
otherBoard = pi->LoadBoard( boardFn.GetFullPath(), nullptr );
if( importDlg.m_LayersOpt->GetValue() )
{
BOARD* loadedBoard = m_frame->GetBoard();
// Check if "Import Settings" board has more layers than the current board.
okToProceed = m_layers->CheckCopperLayerCount( loadedBoard, otherBoard );
}
}
catch( const IO_ERROR& ioe )
{
// You wouldn't think boardFn.GetFullPath() would throw, but we get a stack buffer
// underflow from ASAN. While it's probably an ASAN error, a second try/catch doesn't
// cost us much.
try
{
if( ioe.Problem() != wxT( "CANCEL" ) )
{
wxString msg = wxString::Format( _( "Error loading board file:\n%s" ),
boardFn.GetFullPath() );
DisplayErrorMessage( this, msg, ioe.What() );
}
if( otherPrj != &m_frame->Prj() )
m_frame->GetSettingsManager()->UnloadProject( otherPrj, false );
}
catch(...)
{
// That was already our best-efforts
}
return;
}
if( okToProceed )
{
otherBoard->SetProject( otherPrj );
// If layers options are imported, import also the stackup
// layers options and stackup are linked, so they cannot be imported
// separately, and stackup can be imported only after layers options
if( importDlg.m_LayersOpt->GetValue() )
{
m_physicalStackup->ImportSettingsFrom( otherBoard );
m_layers->ImportSettingsFrom( otherBoard );
m_boardFinish->ImportSettingsFrom( otherBoard );
}
if( importDlg.m_TextAndGraphicsOpt->GetValue() )
{
RESOLVE_PAGE( PANEL_SETUP_TEXT_AND_GRAPHICS,
m_textAndGraphicsPage )->ImportSettingsFrom( otherBoard );
}
if( importDlg.m_FormattingOpt->GetValue() )
{
RESOLVE_PAGE( PANEL_SETUP_FORMATTING,
m_formattingPage )->ImportSettingsFrom( otherBoard );
}
if( importDlg.m_ConstraintsOpt->GetValue() )
{
RESOLVE_PAGE( PANEL_SETUP_CONSTRAINTS,
m_constraintsPage )->ImportSettingsFrom( otherBoard );
}
if( importDlg.m_NetclassesOpt->GetValue() )
{
PROJECT_FILE& otherProjectFile = otherPrj->GetProjectFile();
RESOLVE_PAGE( PANEL_SETUP_NETCLASSES,
m_netclassesPage )->ImportSettingsFrom( otherProjectFile.m_NetSettings );
}
if( importDlg.m_TracksAndViasOpt->GetValue() )
{
RESOLVE_PAGE( PANEL_SETUP_TRACKS_AND_VIAS,
m_tracksAndViasPage )->ImportSettingsFrom( otherBoard );
}
if( importDlg.m_TeardropsOpt->GetValue() )
{
RESOLVE_PAGE( PANEL_SETUP_TEARDROPS,
m_teardropsPage )->ImportSettingsFrom( otherBoard );
}
if( importDlg.m_TuningPatternsOpt->GetValue() )
{
RESOLVE_PAGE( PANEL_SETUP_TUNING_PATTERNS,
m_tuningPatternsPage )->ImportSettingsFrom( otherBoard );
}
if( importDlg.m_MaskAndPasteOpt->GetValue() )
{
RESOLVE_PAGE( PANEL_SETUP_MASK_AND_PASTE,
m_maskAndPagePage )->ImportSettingsFrom( otherBoard );
}
if( importDlg.m_CustomRulesOpt->GetValue() )
{
RESOLVE_PAGE( PANEL_SETUP_RULES,
m_customRulesPage )->ImportSettingsFrom( otherBoard );
}
if( importDlg.m_SeveritiesOpt->GetValue() )
{
BOARD_DESIGN_SETTINGS& otherSettings = otherBoard->GetDesignSettings();
RESOLVE_PAGE( PANEL_SETUP_SEVERITIES,
m_severitiesPage )->ImportSettingsFrom( otherSettings.m_DRCSeverities );
}
if( otherPrj != &m_frame->Prj() )
otherBoard->ClearProject();
}
// Clean up and free memory before leaving
if( otherPrj != &m_frame->Prj() )
m_frame->GetSettingsManager()->UnloadProject( otherPrj, false );
delete otherBoard;
}