7
mirror of https://gitlab.com/kicad/code/kicad.git synced 2025-03-30 05:56:55 +00:00

new feature: Schematic Design Blocks

Added to advanced config, default to off.
EnableDesignBlocks=1 in kicad_advanced to test

Fixes: https://gitlab.com/kicad/code/kicad/-/issues/2263
This commit is contained in:
Mike Williams 2024-02-21 11:35:09 -05:00
parent 43177b7554
commit 2c99bc6c6d
89 changed files with 8262 additions and 237 deletions
common
eeschema
include
kicad
pcbnew

View File

@ -14,6 +14,7 @@ include_directories(
${CMAKE_SOURCE_DIR}/resources/bitmaps_png
${CMAKE_SOURCE_DIR}/3d-viewer
${CMAKE_SOURCE_DIR}/pcbnew
${CMAKE_SOURCE_DIR}/kicad
${INC_AFTER}
)
@ -117,6 +118,11 @@ set( KICOMMON_SRCS
database/database_lib_settings.cpp
design_block_lib_table.cpp
design_block_io.cpp
design_block_info.cpp
design_block_info_impl.cpp
advanced_config.cpp
asset_archive.cpp
array_axis.cpp
@ -173,6 +179,8 @@ set( KICOMMON_SRCS
../scripting/python_scripting.cpp
io/kicad/kicad_io_utils.cpp # needed by richio
io/io_base.cpp
io/io_utils.cpp
)
if( KICAD_IPC_API )

View File

@ -99,6 +99,7 @@ static const wxChar UpdateUIEventInterval[] = wxT( "UpdateUIEventInterval" );
static const wxChar V3DRT_BevelHeight_um[] = wxT( "V3DRT_BevelHeight_um" );
static const wxChar V3DRT_BevelExtentFactor[] = wxT( "V3DRT_BevelExtentFactor" );
static const wxChar UseClipper2[] = wxT( "UseClipper2" );
static const wxChar EnableDesignBlocks[] = wxT( "EnableDesignBlocks" );
static const wxChar EnableGenerators[] = wxT( "EnableGenerators" );
static const wxChar EnableGit[] = wxT( "EnableGit" );
static const wxChar EnableLibWithText[] = wxT( "EnableLibWithText" );
@ -241,6 +242,7 @@ ADVANCED_CFG::ADVANCED_CFG()
m_CompactSave = false;
m_UpdateUIEventInterval = 0;
m_ShowRepairSchematic = false;
m_EnableDesignBlocks = false;
m_EnableGenerators = false;
m_EnableGit = false;
m_EnableLibWithText = false;
@ -459,6 +461,9 @@ void ADVANCED_CFG::loadSettings( wxConfigBase& aCfg )
m_DisambiguationMenuDelay,
50, 10000 ) );
configParams.push_back( new PARAM_CFG_BOOL( true, AC_KEYS::EnableDesignBlocks,
&m_EnableDesignBlocks, m_EnableDesignBlocks ) );
configParams.push_back( new PARAM_CFG_BOOL( true, AC_KEYS::EnableGenerators,
&m_EnableGenerators, m_EnableGenerators ) );

54
common/design_block.h Normal file
View File

@ -0,0 +1,54 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2024 Mike Williams <mike@mikebwilliams.com>
* Copyright (C) 1992-2024 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 2
* 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, you may find one here:
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
* or you may search the http://www.gnu.org website for the version 2 license,
* or you may write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
#include <kicommon.h>
#include <lib_id.h>
class DESIGN_BLOCK
{
public:
void SetLibId( const LIB_ID& aName ) { m_lib_id = aName; }
const LIB_ID& GetLibId() const { return m_lib_id; }
wxString GetLibIdAsString() const { return m_lib_id.Format(); }
wxString GetLibDescription() const { return m_libDescription; }
void SetLibDescription( const wxString& aDesc ) { m_libDescription = aDesc; }
wxString GetKeywords() const { return m_keywords; }
void SetKeywords( const wxString& aKeywords ) { m_keywords = aKeywords; }
wxString GetDocumentationUrl() const { return m_doc_url; }
void SetDocumentationUrl( const wxString& aDocumentationUrl ) { m_doc_url = aDocumentationUrl; }
wxString GetSchematicFile() const { return m_schematicFile; }
void SetSchematicFile( const wxString& aFile ) { m_schematicFile = aFile; }
private:
LIB_ID m_lib_id;
wxString m_schematicFile; // File name and path for schematic symbol.
wxString m_libDescription; // File name and path for documentation file.
wxString m_keywords; // Search keywords to find footprint in library.
wxString m_doc_url; // URL of external documentation
};

View File

@ -0,0 +1,108 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 1992-2024 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 2
* 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, you may find one here:
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
* or you may search the http://www.gnu.org website for the version 2 license,
* or you may write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
/*
* Functions to read design block libraries and fill m_design_blocks by available design blocks names
* and their documentation (comments and keywords)
*/
#include <design_block_info.h>
#include <fp_lib_table.h>
#include <dialogs/html_message_box.h>
#include <string_utils.h>
#include <kiface_ids.h>
#include <kiway.h>
#include <lib_id.h>
#include <thread>
#include <utility>
#include <wx/tokenzr.h>
#include <kiface_base.h>
DESIGN_BLOCK_INFO* DESIGN_BLOCK_LIST::GetDesignBlockInfo( const wxString& aLibNickname,
const wxString& aDesignBlockName )
{
if( aDesignBlockName.IsEmpty() )
return nullptr;
for( std::unique_ptr<DESIGN_BLOCK_INFO>& db : m_list )
{
if( aLibNickname == db->GetLibNickname() && aDesignBlockName == db->GetDesignBlockName() )
return db.get();
}
return nullptr;
}
DESIGN_BLOCK_INFO* DESIGN_BLOCK_LIST::GetDesignBlockInfo( const wxString& aDesignBlockName )
{
if( aDesignBlockName.IsEmpty() )
return nullptr;
LIB_ID dbid;
wxCHECK_MSG( dbid.Parse( aDesignBlockName ) < 0, nullptr,
wxString::Format( wxT( "'%s' is not a valid LIB_ID." ), aDesignBlockName ) );
return GetDesignBlockInfo( dbid.GetLibNickname(), dbid.GetLibItemName() );
}
std::vector<SEARCH_TERM> DESIGN_BLOCK_INFO::GetSearchTerms()
{
std::vector<SEARCH_TERM> terms;
terms.emplace_back( SEARCH_TERM( GetName(), 8 ) );
wxStringTokenizer keywordTokenizer( GetKeywords(), wxS( " " ), wxTOKEN_STRTOK );
while( keywordTokenizer.HasMoreTokens() )
terms.emplace_back( SEARCH_TERM( keywordTokenizer.GetNextToken(), 4 ) );
// Also include keywords as one long string, just in case
terms.emplace_back( SEARCH_TERM( GetKeywords(), 1 ) );
terms.emplace_back( SEARCH_TERM( GetDesc(), 1 ) );
return terms;
}
bool DESIGN_BLOCK_INFO::InLibrary( const wxString& aLibrary ) const
{
return aLibrary == m_nickname;
}
bool operator<( const DESIGN_BLOCK_INFO& lhs, const DESIGN_BLOCK_INFO& rhs )
{
int retv = StrNumCmp( lhs.m_nickname, rhs.m_nickname, false );
if( retv != 0 )
return retv < 0;
// Technically design block names are not case sensitive because the file name is used
// as the design block name. On windows this would be problematic because windows does
// not support case sensitive file names by default. This should not cause any issues
// and allow for a future change to use the name defined in the design block file.
return StrNumCmp( lhs.m_dbname, rhs.m_dbname, false ) < 0;
}

219
common/design_block_info.h Normal file
View File

@ -0,0 +1,219 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 1992-2024 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 2
* 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, you may find one here:
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
* or you may search the http://www.gnu.org website for the version 2 license,
* or you may write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
/*
* @file design_block_info.h
*/
#ifndef DESIGN_BLOCK_INFO_H_
#define DESIGN_BLOCK_INFO_H_
#include <kicommon.h>
#include <boost/ptr_container/ptr_vector.hpp>
#include <import_export.h>
#include <ki_exception.h>
#include <core/sync_queue.h>
#include <lib_tree_item.h>
#include <atomic>
#include <functional>
#include <memory>
class DESIGN_BLOCK_LIB_TABLE;
class DESIGN_BLOCK_LIST;
class DESIGN_BLOCK_LIST_IMPL;
class PROGRESS_REPORTER;
class wxTopLevelWindow;
class KIWAY;
class wxTextFile;
/*
* Helper class to handle the list of design blocks available in libraries. It stores
* design block names, doc and keywords.
*
* This is a virtual class; its implementation lives in common/design_block_info_impl.cpp.
* To get instances of these classes, see DESIGN_BLOCK_LIST::GetInstance().
*/
class KICOMMON_API DESIGN_BLOCK_INFO : public LIB_TREE_ITEM
{
public:
virtual ~DESIGN_BLOCK_INFO() {}
// These two accessors do not have to call ensure_loaded(), because constructor
// fills in these fields:
const wxString& GetDesignBlockName() const { return m_dbname; }
wxString GetLibNickname() const override { return m_nickname; }
wxString GetName() const override { return m_dbname; }
LIB_ID GetLIB_ID() const override { return LIB_ID( m_nickname, m_dbname ); }
wxString GetDesc() override
{
ensure_loaded();
return m_doc;
}
wxString GetKeywords()
{
ensure_loaded();
return m_keywords;
}
std::vector<SEARCH_TERM> GetSearchTerms() override;
int GetOrderNum()
{
ensure_loaded();
return m_num;
}
/**
* Test if the #DESIGN_BLOCK_INFO object was loaded from \a aLibrary.
*
* @param aLibrary is the nickname of the library to test.
*
* @return true if the #DESIGN_BLOCK_INFO object was loaded from \a aLibrary. Otherwise
* false.
*/
bool InLibrary( const wxString& aLibrary ) const;
/**
* Less than comparison operator, intended for sorting DESIGN_BLOCK_INFO objects
*/
friend bool operator<( const DESIGN_BLOCK_INFO& lhs, const DESIGN_BLOCK_INFO& rhs );
protected:
void ensure_loaded()
{
if( !m_loaded )
load();
}
/// lazily load stuff not filled in by constructor. This may throw IO_ERRORS.
virtual void load(){};
DESIGN_BLOCK_LIST* m_owner; ///< provides access to DESIGN_BLOCK_LIB_TABLE
bool m_loaded;
wxString m_nickname; ///< library as known in DESIGN_BLOCK_LIB_TABLE
wxString m_dbname; ///< Module name.
int m_num; ///< Order number in the display list.
wxString m_doc; ///< Design block description.
wxString m_keywords; ///< Design block keywords.
};
/**
* Holds a list of #DESIGN_BLOCK_INFO objects, along with a list of IO_ERRORs or
* PARSE_ERRORs that were thrown acquiring the DESIGN_BLOCK_INFOs.
*
* This is a virtual class; its implementation lives in common/design_block_info_impl.cpp.
* To get instances of these classes, see DESIGN_BLOCK_LIST::GetInstance().
*/
class KICOMMON_API DESIGN_BLOCK_LIST
{
public:
typedef std::vector<std::unique_ptr<DESIGN_BLOCK_INFO>> DBILIST;
typedef SYNC_QUEUE<std::unique_ptr<IO_ERROR>> ERRLIST;
DESIGN_BLOCK_LIST() : m_lib_table( nullptr ) {}
virtual ~DESIGN_BLOCK_LIST() {}
virtual void WriteCacheToFile( const wxString& aFilePath ){};
virtual void ReadCacheFromFile( const wxString& aFilePath ){};
/**
* @return the number of items stored in list
*/
unsigned GetCount() const { return m_list.size(); }
/// Was forced to add this by modview_frame.cpp
const DBILIST& GetList() const { return m_list; }
/**
* @return Clears the design block info cache
*/
void Clear() { m_list.clear(); }
/**
* Get info for a design block by id.
*/
DESIGN_BLOCK_INFO* GetDesignBlockInfo( const wxString& aDesignBlockName );
/**
* Get info for a design block by libNickname/designBlockName
*/
DESIGN_BLOCK_INFO* GetDesignBlockInfo( const wxString& aLibNickname,
const wxString& aDesignBlockName );
/**
* Get info for a design block by index.
*
* @param aIdx index of the given item.
* @return the aIdx item in list.
*/
DESIGN_BLOCK_INFO& GetItem( unsigned aIdx ) const { return *m_list[aIdx]; }
unsigned GetErrorCount() const { return m_errors.size(); }
std::unique_ptr<IO_ERROR> PopError()
{
std::unique_ptr<IO_ERROR> error;
m_errors.pop( error );
return error;
}
/**
* Read all the design blocks provided by the combination of aTable and aNickname.
*
* @param aTable defines all the libraries.
* @param aNickname is the library to read from, or if NULL means read all design blocks
* from all known libraries in aTable.
* @param aProgressReporter is an optional progress reporter. ReadDesignBlockFiles() will
* use 2 phases within the reporter.
* @return true if it ran to completion, else false if it aborted after some number of
* errors. If true, it does not mean there were no errors, check GetErrorCount()
* for that, should be zero to indicate success.
*/
virtual bool ReadDesignBlockFiles( DESIGN_BLOCK_LIB_TABLE* aTable,
const wxString* aNickname = nullptr,
PROGRESS_REPORTER* aProgressReporter = nullptr ) = 0;
DESIGN_BLOCK_LIB_TABLE* GetTable() const { return m_lib_table; }
protected:
DESIGN_BLOCK_LIB_TABLE* m_lib_table = nullptr; ///< no ownership
DBILIST m_list;
ERRLIST m_errors; ///< some can be PARSE_ERRORs also
};
#endif // DESIGN_BLOCK_INFO_H_

View File

@ -0,0 +1,376 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 1992-2024 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 <design_block_info_impl.h>
#include <design_block.h>
#include <design_block_info.h>
#include <design_block_lib_table.h>
#include <kiway.h>
#include <locale_io.h>
#include <lib_id.h>
#include <progress_reporter.h>
#include <string_utils.h>
#include <core/thread_pool.h>
#include <wildcards_and_files_ext.h>
#include <kiplatform/io.h>
#include <wx/textfile.h>
#include <wx/txtstrm.h>
#include <wx/wfstream.h>
void DESIGN_BLOCK_INFO_IMPL::load()
{
DESIGN_BLOCK_LIB_TABLE* dbtable = m_owner->GetTable();
wxASSERT( dbtable );
const DESIGN_BLOCK* design_block = dbtable->GetEnumeratedDesignBlock( m_nickname, m_dbname );
if( design_block )
{
m_keywords = design_block->GetKeywords();
m_doc = design_block->GetLibDescription();
}
m_loaded = true;
}
bool DESIGN_BLOCK_LIST_IMPL::CatchErrors( const std::function<void()>& aFunc )
{
try
{
aFunc();
}
catch( const IO_ERROR& ioe )
{
m_errors.move_push( std::make_unique<IO_ERROR>( ioe ) );
return false;
}
catch( const std::exception& se )
{
// This is a round about way to do this, but who knows what THROW_IO_ERROR()
// may be tricked out to do someday, keep it in the game.
try
{
THROW_IO_ERROR( se.what() );
}
catch( const IO_ERROR& ioe )
{
m_errors.move_push( std::make_unique<IO_ERROR>( ioe ) );
}
return false;
}
return true;
}
bool DESIGN_BLOCK_LIST_IMPL::ReadDesignBlockFiles( DESIGN_BLOCK_LIB_TABLE* aTable,
const wxString* aNickname,
PROGRESS_REPORTER* aProgressReporter )
{
long long int generatedTimestamp = 0;
if( !CatchErrors(
[&]()
{
generatedTimestamp = aTable->GenerateTimestamp( aNickname );
} ) )
{
return false;
}
if( generatedTimestamp == m_list_timestamp )
return true;
// Disable KIID generation: not needed for library parts; sometimes very slow
KIID_NIL_SET_RESET reset_kiid;
m_progress_reporter = aProgressReporter;
if( m_progress_reporter )
{
m_progress_reporter->SetMaxProgress( m_queue_in.size() );
m_progress_reporter->Report( _( "Fetching design_block libraries..." ) );
}
m_cancelled = false;
m_lib_table = aTable;
// Clear data before reading files
m_errors.clear();
m_list.clear();
m_queue_in.clear();
m_queue_out.clear();
if( aNickname )
{
m_queue_in.push( *aNickname );
}
else
{
for( const wxString& nickname : aTable->GetLogicalLibs() )
m_queue_in.push( nickname );
}
loadLibs();
if( !m_cancelled )
{
if( m_progress_reporter )
{
m_progress_reporter->SetMaxProgress( m_queue_out.size() );
m_progress_reporter->AdvancePhase();
m_progress_reporter->Report( _( "Loading design_blocks..." ) );
}
loadDesignBlocks();
if( m_progress_reporter )
m_progress_reporter->AdvancePhase();
}
if( m_cancelled )
m_list_timestamp = 0; // God knows what we got before we were canceled
else
m_list_timestamp = generatedTimestamp;
return m_errors.empty();
}
void DESIGN_BLOCK_LIST_IMPL::loadLibs()
{
thread_pool& tp = GetKiCadThreadPool();
size_t num_returns = m_queue_in.size();
std::vector<std::future<size_t>> returns( num_returns );
auto loader_job =
[this]() -> size_t
{
wxString nickname;
size_t retval = 0;
if( !m_cancelled && m_queue_in.pop( nickname ) )
{
if( CatchErrors( [this, &nickname]()
{
m_lib_table->PrefetchLib( nickname );
m_queue_out.push( nickname );
} ) && m_progress_reporter )
{
m_progress_reporter->AdvanceProgress();
}
++retval;
}
return retval;
};
for( size_t ii = 0; ii < num_returns; ++ii )
returns[ii] = tp.submit( loader_job );
for( const std::future<size_t>& ret : returns )
{
std::future_status status = ret.wait_for( std::chrono::milliseconds( 250 ) );
while( status != std::future_status::ready )
{
if( m_progress_reporter && !m_progress_reporter->KeepRefreshing() )
m_cancelled = true;
status = ret.wait_for( std::chrono::milliseconds( 250 ) );
}
}
}
void DESIGN_BLOCK_LIST_IMPL::loadDesignBlocks()
{
LOCALE_IO toggle_locale;
// Parse the design_blocks in parallel. WARNING! This requires changing the locale, which is
// GLOBAL. It is only thread safe to construct the LOCALE_IO before the threads are created,
// destroy it after they finish, and block the main (GUI) thread while they work. Any deviation
// from this will cause nasal demons.
//
// TODO: blast LOCALE_IO into the sun
SYNC_QUEUE<std::unique_ptr<DESIGN_BLOCK_INFO>> queue_parsed;
thread_pool& tp = GetKiCadThreadPool();
size_t num_elements = m_queue_out.size();
std::vector<std::future<size_t>> returns( num_elements );
auto db_thread =
[ this, &queue_parsed ]() -> size_t
{
wxString nickname;
if( m_cancelled || !m_queue_out.pop( nickname ) )
return 0;
wxArrayString dbnames;
CatchErrors(
[&]()
{
m_lib_table->DesignBlockEnumerate( dbnames, nickname, false );
} );
for( wxString dbname : dbnames )
{
CatchErrors(
[&]()
{
auto* dbinfo = new DESIGN_BLOCK_INFO_IMPL( this, nickname, dbname );
queue_parsed.move_push( std::unique_ptr<DESIGN_BLOCK_INFO>( dbinfo ) );
} );
if( m_cancelled )
return 0;
}
if( m_progress_reporter )
m_progress_reporter->AdvanceProgress();
return 1;
};
for( size_t ii = 0; ii < num_elements; ++ii )
returns[ii] = tp.submit( db_thread );
for( const std::future<size_t>& ret : returns )
{
std::future_status status = ret.wait_for( std::chrono::milliseconds( 250 ) );
while( status != std::future_status::ready )
{
if( m_progress_reporter )
m_progress_reporter->KeepRefreshing();
status = ret.wait_for( std::chrono::milliseconds( 250 ) );
}
}
std::unique_ptr<DESIGN_BLOCK_INFO> dbi;
while( queue_parsed.pop( dbi ) )
m_list.push_back( std::move( dbi ) );
std::sort( m_list.begin(), m_list.end(),
[]( std::unique_ptr<DESIGN_BLOCK_INFO> const& lhs,
std::unique_ptr<DESIGN_BLOCK_INFO> const& rhs ) -> bool
{
return *lhs < *rhs;
} );
}
DESIGN_BLOCK_LIST_IMPL::DESIGN_BLOCK_LIST_IMPL() :
m_list_timestamp( 0 ), m_progress_reporter( nullptr ), m_cancelled( false )
{
}
void DESIGN_BLOCK_LIST_IMPL::WriteCacheToFile( const wxString& aFilePath )
{
wxFileName tmpFileName = wxFileName::CreateTempFileName( aFilePath );
wxFFileOutputStream outStream( tmpFileName.GetFullPath() );
wxTextOutputStream txtStream( outStream );
if( !outStream.IsOk() )
{
return;
}
txtStream << wxString::Format( wxT( "%lld" ), m_list_timestamp ) << endl;
for( std::unique_ptr<DESIGN_BLOCK_INFO>& dbinfo : m_list )
{
txtStream << dbinfo->GetLibNickname() << endl;
txtStream << dbinfo->GetName() << endl;
txtStream << EscapeString( dbinfo->GetDesc(), CTX_LINE ) << endl;
txtStream << EscapeString( dbinfo->GetKeywords(), CTX_LINE ) << endl;
txtStream << wxString::Format( wxT( "%d" ), dbinfo->GetOrderNum() ) << endl;
}
txtStream.Flush();
outStream.Close();
// Preserve the permissions of the current file
KIPLATFORM::IO::DuplicatePermissions( aFilePath, tmpFileName.GetFullPath() );
if( !wxRenameFile( tmpFileName.GetFullPath(), aFilePath, true ) )
{
// cleanup in case rename failed
// its also not the end of the world since this is just a cache file
wxRemoveFile( tmpFileName.GetFullPath() );
}
}
void DESIGN_BLOCK_LIST_IMPL::ReadCacheFromFile( const wxString& aFilePath )
{
wxTextFile cacheFile( aFilePath );
m_list_timestamp = 0;
m_list.clear();
try
{
if( cacheFile.Exists() && cacheFile.Open() )
{
cacheFile.GetFirstLine().ToLongLong( &m_list_timestamp );
while( cacheFile.GetCurrentLine() + 6 < cacheFile.GetLineCount() )
{
wxString libNickname = cacheFile.GetNextLine();
wxString name = cacheFile.GetNextLine();
wxString desc = UnescapeString( cacheFile.GetNextLine() );
wxString keywords = UnescapeString( cacheFile.GetNextLine() );
int orderNum = wxAtoi( cacheFile.GetNextLine() );
DESIGN_BLOCK_INFO_IMPL* dbinfo =
new DESIGN_BLOCK_INFO_IMPL( libNickname, name, desc, keywords, orderNum );
m_list.emplace_back( std::unique_ptr<DESIGN_BLOCK_INFO>( dbinfo ) );
}
}
}
catch( ... )
{
// whatever went wrong, invalidate the cache
m_list_timestamp = 0;
}
// Sanity check: an empty list is very unlikely to be correct.
if( m_list.size() == 0 )
m_list_timestamp = 0;
if( cacheFile.IsOpened() )
cacheFile.Close();
}

View File

@ -0,0 +1,112 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 1992-2021 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/>.
*/
#ifndef DESIGN_BLOCK_INFO_IMPL_H
#define DESIGN_BLOCK_INFO_IMPL_H
#include <atomic>
#include <functional>
#include <memory>
#include <thread>
#include <vector>
#include <kicommon.h>
#include <design_block_info.h>
#include <core/sync_queue.h>
class LOCALE_IO;
class KICOMMON_API DESIGN_BLOCK_INFO_IMPL : public DESIGN_BLOCK_INFO
{
public:
DESIGN_BLOCK_INFO_IMPL( DESIGN_BLOCK_LIST* aOwner, const wxString& aNickname,
const wxString& aDesignBlockName )
{
m_nickname = aNickname;
m_dbname = aDesignBlockName;
m_num = 0;
m_owner = aOwner;
m_loaded = false;
load();
}
// A constructor for cached items
DESIGN_BLOCK_INFO_IMPL( const wxString& aNickname, const wxString& aDesignBlockName,
const wxString& aDescription, const wxString& aKeywords, int aOrderNum )
{
m_nickname = aNickname;
m_dbname = aDesignBlockName;
m_num = aOrderNum;
m_doc = aDescription;
m_keywords = aKeywords;
m_owner = nullptr;
m_loaded = true;
}
// A dummy constructor for use as a target in a binary search
DESIGN_BLOCK_INFO_IMPL( const wxString& aNickname, const wxString& aDesignBlockName )
{
m_nickname = aNickname;
m_dbname = aDesignBlockName;
m_owner = nullptr;
m_loaded = true;
}
protected:
virtual void load() override;
};
class KICOMMON_API DESIGN_BLOCK_LIST_IMPL : public DESIGN_BLOCK_LIST
{
public:
DESIGN_BLOCK_LIST_IMPL();
virtual ~DESIGN_BLOCK_LIST_IMPL(){};
void WriteCacheToFile( const wxString& aFilePath ) override;
void ReadCacheFromFile( const wxString& aFilePath ) override;
bool ReadDesignBlockFiles( DESIGN_BLOCK_LIB_TABLE* aTable, const wxString* aNickname = nullptr,
PROGRESS_REPORTER* aProgressReporter = nullptr ) override;
protected:
void loadLibs();
void loadDesignBlocks();
private:
/**
* Call aFunc, pushing any IO_ERRORs and std::exceptions it throws onto m_errors.
*
* @return true if no error occurred.
*/
bool CatchErrors( const std::function<void()>& aFunc );
SYNC_QUEUE<wxString> m_queue_in;
SYNC_QUEUE<wxString> m_queue_out;
long long m_list_timestamp;
PROGRESS_REPORTER* m_progress_reporter;
std::atomic_bool m_cancelled;
std::mutex m_join;
};
#endif // DESIGN_BLOCK_INFO_IMPL_H

387
common/design_block_io.cpp Normal file
View File

@ -0,0 +1,387 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2024 Mike Williams <mike@mikebwilliams.com>
* Copyright (C) 1992-2024 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 2
* 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, you may find one here:
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
* or you may search the http://www.gnu.org website for the version 2 license,
* or you may write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
#include <i18n_utility.h>
#include <wx/dir.h>
#include <wx/filename.h>
#include <wx/log.h>
#include <wx/translation.h>
#include <wx/string.h>
#include <wx/arrstr.h>
#include <wx/datetime.h>
#include <wildcards_and_files_ext.h>
#include <kiway_player.h>
#include <design_block_io.h>
#include <design_block.h>
#include <ki_exception.h>
#include <trace_helpers.h>
#include <fstream>
const wxString DESIGN_BLOCK_IO_MGR::ShowType( DESIGN_BLOCK_FILE_T aFileType )
{
switch( aFileType )
{
case KICAD_SEXP: return _( "KiCad" );
default: return wxString::Format( _( "UNKNOWN (%d)" ), aFileType );
}
}
DESIGN_BLOCK_IO_MGR::DESIGN_BLOCK_FILE_T
DESIGN_BLOCK_IO_MGR::EnumFromStr( const wxString& aFileType )
{
if( aFileType == _( "KiCad" ) )
return DESIGN_BLOCK_FILE_T( KICAD_SEXP );
return DESIGN_BLOCK_FILE_T( DESIGN_BLOCK_FILE_UNKNOWN );
}
DESIGN_BLOCK_IO* DESIGN_BLOCK_IO_MGR::FindPlugin( DESIGN_BLOCK_FILE_T aFileType )
{
switch( aFileType )
{
case KICAD_SEXP: return new DESIGN_BLOCK_IO();
default: return nullptr;
}
}
DESIGN_BLOCK_IO_MGR::DESIGN_BLOCK_FILE_T
DESIGN_BLOCK_IO_MGR::GuessPluginTypeFromLibPath( const wxString& aLibPath, int aCtl )
{
if( IO_RELEASER<DESIGN_BLOCK_IO>( FindPlugin( KICAD_SEXP ) )->CanReadLibrary( aLibPath ) && aCtl != KICTL_NONKICAD_ONLY )
return KICAD_SEXP;
return DESIGN_BLOCK_IO_MGR::FILE_TYPE_NONE;
}
bool DESIGN_BLOCK_IO_MGR::ConvertLibrary( std::map<std::string, UTF8>* aOldFileProps,
const wxString& aOldFilePath,
const wxString& aNewFilePath )
{
DESIGN_BLOCK_IO_MGR::DESIGN_BLOCK_FILE_T oldFileType =
DESIGN_BLOCK_IO_MGR::GuessPluginTypeFromLibPath( aOldFilePath );
if( oldFileType == DESIGN_BLOCK_IO_MGR::FILE_TYPE_NONE )
return false;
IO_RELEASER<DESIGN_BLOCK_IO> oldFilePI( DESIGN_BLOCK_IO_MGR::FindPlugin( oldFileType ) );
IO_RELEASER<DESIGN_BLOCK_IO> kicadPI(
DESIGN_BLOCK_IO_MGR::FindPlugin( DESIGN_BLOCK_IO_MGR::KICAD_SEXP ) );
wxArrayString dbNames;
wxFileName newFileName( aNewFilePath );
if( newFileName.HasExt() )
{
wxString extraDir = newFileName.GetFullName();
newFileName.ClearExt();
newFileName.SetName( "" );
newFileName.AppendDir( extraDir );
}
if( !newFileName.DirExists() && !wxFileName::Mkdir( aNewFilePath, wxS_DIR_DEFAULT ) )
return false;
try
{
bool bestEfforts = false; // throw on first error
oldFilePI->DesignBlockEnumerate( dbNames, aOldFilePath, bestEfforts, aOldFileProps );
for( const wxString& dbName : dbNames )
{
std::unique_ptr<const DESIGN_BLOCK> db(
oldFilePI->GetEnumeratedDesignBlock( aOldFilePath, dbName, aOldFileProps ) );
kicadPI->DesignBlockSave( aNewFilePath, db.get() );
}
}
catch( ... )
{
return false;
}
return true;
}
const DESIGN_BLOCK_IO::IO_FILE_DESC DESIGN_BLOCK_IO::GetLibraryDesc() const
{
return IO_BASE::IO_FILE_DESC( _HKI( "KiCad Design Block folders" ), {},
{ FILEEXT::KiCadDesignBlockLibPathExtension }, false );
}
long long DESIGN_BLOCK_IO::GetLibraryTimestamp( const wxString& aLibraryPath ) const
{
wxFileName fn( aLibraryPath );
if( fn.IsFileReadable() && fn.GetModificationTime().IsValid() )
return fn.GetModificationTime().GetValue().GetValue();
else
return wxDateTime( 0.0 ).GetValue().GetValue();
}
void DESIGN_BLOCK_IO::CreateLibrary( const wxString& aLibraryPath,
const std::map<std::string, UTF8>* aProperties )
{
if( wxDir::Exists( aLibraryPath ) )
{
THROW_IO_ERROR( wxString::Format( _( "Cannot overwrite library path '%s'." ),
aLibraryPath.GetData() ) );
}
wxFileName dir;
dir.SetPath( aLibraryPath );
if( !dir.Mkdir() )
{
THROW_IO_ERROR(
wxString::Format( _( "Library path '%s' could not be created.\n\n"
"Make sure you have write permissions and try again." ),
dir.GetPath() ) );
}
}
bool DESIGN_BLOCK_IO::DeleteLibrary( const wxString& aLibraryPath,
const std::map<std::string, UTF8>* aProperties )
{
wxFileName fn;
fn.SetPath( aLibraryPath );
// Return if there is no library path to delete.
if( !fn.DirExists() )
return false;
if( !fn.IsDirWritable() )
{
THROW_IO_ERROR( wxString::Format( _( "Insufficient permissions to delete folder '%s'." ),
aLibraryPath.GetData() ) );
}
wxDir dir( aLibraryPath );
// Design block folders should only contain sub-folders for each design block
if( dir.HasFiles() )
{
THROW_IO_ERROR( wxString::Format( _( "Library folder '%s' has unexpected files." ),
aLibraryPath.GetData() ) );
}
// Must delete all sub-directories before deleting the library directory
if( dir.HasSubDirs() )
{
wxArrayString dirs;
// Get all sub-directories in the library path
dir.GetAllFiles( aLibraryPath, &dirs, wxEmptyString, wxDIR_DIRS );
for( size_t i = 0; i < dirs.GetCount(); i++ )
{
wxFileName tmp = dirs[i];
if( tmp.GetExt() != FILEEXT::KiCadDesignBlockLibPathExtension )
{
THROW_IO_ERROR( wxString::Format( _( "Unexpected folder '%s' found in library "
"path '%s'." ),
dirs[i].GetData(), aLibraryPath.GetData() ) );
}
}
for( size_t i = 0; i < dirs.GetCount(); i++ )
wxRemoveFile( dirs[i] );
}
wxLogTrace( traceDesignBlocks, wxT( "Removing design block library '%s'." ),
aLibraryPath.GetData() );
// Some of the more elaborate wxRemoveFile() crap puts up its own wxLog dialog
// we don't want that. we want bare metal portability with no UI here.
if( !wxFileName::Rmdir( aLibraryPath, wxPATH_RMDIR_RECURSIVE ) )
{
THROW_IO_ERROR( wxString::Format( _( "Design block library '%s' cannot be deleted." ),
aLibraryPath.GetData() ) );
}
// For some reason removing a directory in Windows is not immediately updated. This delay
// prevents an error when attempting to immediately recreate the same directory when over
// writing an existing library.
#ifdef __WINDOWS__
wxMilliSleep( 250L );
#endif
return true;
}
void DESIGN_BLOCK_IO::DesignBlockEnumerate( wxArrayString& aDesignBlockNames,
const wxString& aLibraryPath, bool aBestEfforts,
const std::map<std::string, UTF8>* aProperties )
{
// From the starting directory, look for all directories ending in the .block extension
wxDir dir( aLibraryPath );
if( !dir.IsOpened() )
return;
wxString dirname;
wxString fileSpec = wxT( "*." ) + wxString( FILEEXT::KiCadDesignBlockPathExtension );
bool cont = dir.GetFirst( &dirname, fileSpec, wxDIR_DIRS );
while( cont )
{
aDesignBlockNames.Add( dirname.Before( wxT( '.' ) ) );
cont = dir.GetNext( &dirname );
}
}
DESIGN_BLOCK* DESIGN_BLOCK_IO::DesignBlockLoad( const wxString& aLibraryPath,
const wxString& aDesignBlockName, bool aKeepUUID,
const std::map<std::string, UTF8>* aProperties )
{
DESIGN_BLOCK* newDB = new DESIGN_BLOCK();
wxString dbPath = aLibraryPath + wxFileName::GetPathSeparator() +
aDesignBlockName + wxT( "." ) + FILEEXT::KiCadDesignBlockPathExtension + wxFileName::GetPathSeparator();
wxString dbSchPath = dbPath + aDesignBlockName + wxT( "." ) + FILEEXT::KiCadSchematicFileExtension;
wxString dbMetadataPath = dbPath + aDesignBlockName + wxT( "." ) + FILEEXT::JsonFileExtension;
// Library name needs to be empty for when we fill it in with the correct library nickname
// one layer above
newDB->SetLibId( LIB_ID( wxEmptyString, aDesignBlockName ) );
newDB->SetSchematicFile(
// Library path
aLibraryPath + wxFileName::GetPathSeparator() +
// Design block name (project folder)
aDesignBlockName + +wxT( "." ) + FILEEXT::KiCadDesignBlockPathExtension + wxT( "/" ) +
// Schematic file
aDesignBlockName + wxT( "." ) + FILEEXT::KiCadSchematicFileExtension );
// Parse the JSON file if it exists
if( wxFileExists( dbMetadataPath ) )
{
try
{
nlohmann::json dbMetadata;
std::ifstream dbMetadataFile( dbMetadataPath.fn_str() );
dbMetadataFile >> dbMetadata;
if( dbMetadata.contains( "description" ) )
newDB->SetLibDescription( dbMetadata["description"] );
if( dbMetadata.contains( "keywords" ) )
newDB->SetKeywords( dbMetadata["keywords"] );
if( dbMetadata.contains( "documentation_url" ) )
newDB->SetDocumentationUrl( dbMetadata["documentation_url"] );
}
catch( ... )
{
THROW_IO_ERROR( wxString::Format(
_( "Design block metadata file '%s' could not be read." ), dbMetadataPath ) );
}
}
return newDB;
}
void DESIGN_BLOCK_IO::DesignBlockSave( const wxString& aLibraryPath,
const DESIGN_BLOCK* aDesignBlock,
const std::map<std::string, UTF8>* aProperties )
{
// Make sure we have a valid LIB_ID or we can't save the design block
if( !aDesignBlock->GetLibId().IsValid() )
{
THROW_IO_ERROR( _( "Design block does not have a valid library ID." ) );
}
if( !wxFileExists( aDesignBlock->GetSchematicFile() ) )
{
THROW_IO_ERROR( wxString::Format( _( "Schematic source file '%s' does not exist." ),
aDesignBlock->GetSchematicFile() ) );
}
// Create the design block folder
wxFileName dbFolder( aLibraryPath + wxFileName::GetPathSeparator()
+ aDesignBlock->GetLibId().GetLibItemName() + wxT( "." )
+ FILEEXT::KiCadDesignBlockPathExtension
+ wxFileName::GetPathSeparator() );
if( !dbFolder.DirExists() )
{
if( !dbFolder.Mkdir() )
{
THROW_IO_ERROR( wxString::Format( _( "Design block folder '%s' could not be created." ),
dbFolder.GetFullPath().GetData() ) );
}
}
// The new schematic file name is based on the design block name, not the source sheet name
wxString dbSchematicFile = dbFolder.GetFullPath() + aDesignBlock->GetLibId().GetLibItemName()
+ wxT( "." ) + FILEEXT::KiCadSchematicFileExtension;
// Copy the source sheet file to the design block folder, under the design block name
if( !wxCopyFile( aDesignBlock->GetSchematicFile(), dbSchematicFile ) )
{
THROW_IO_ERROR( wxString::Format(
_( "Schematic file '%s' could not be saved as design block at '%s'." ),
aDesignBlock->GetSchematicFile().GetData(), dbSchematicFile ) );
}
}
void DESIGN_BLOCK_IO::DesignBlockDelete( const wxString& aLibPath, const wxString& aDesignBlockName,
const std::map<std::string, UTF8>* aProperties )
{
wxFileName dbDir = wxFileName( aLibPath + wxFileName::GetPathSeparator() + aDesignBlockName
+ wxT( "." ) + FILEEXT::KiCadDesignBlockPathExtension );
if( !dbDir.DirExists() )
{
THROW_IO_ERROR(
wxString::Format( _( "Design block '%s' does not exist." ), dbDir.GetFullName() ) );
}
// Delete the whole design block folder
if( !wxFileName::Rmdir( dbDir.GetFullPath(), wxPATH_RMDIR_RECURSIVE ) )
{
THROW_IO_ERROR( wxString::Format( _( "Design block folder '%s' could not be deleted." ),
dbDir.GetFullPath().GetData() ) );
}
}
bool DESIGN_BLOCK_IO::IsLibraryWritable( const wxString& aLibraryPath )
{
wxFileName path( aLibraryPath );
return path.IsOk() && path.IsDirWritable();
}

115
common/design_block_io.h Normal file
View File

@ -0,0 +1,115 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2024 Mike Williams <mike@mikebwilliams.com>
* Copyright (C) 1992-2024 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 2
* 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, you may find one here:
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
* or you may search the http://www.gnu.org website for the version 2 license,
* or you may write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
#ifndef DESIGN_BLOCK_IO_H
#define DESIGN_BLOCK_IO_H
#include <kicommon.h>
#include <io/io_base.h>
#include <io/io_mgr.h>
class DESIGN_BLOCK;
class DESIGN_BLOCK_IO;
class wxArrayString;
class KICOMMON_API DESIGN_BLOCK_IO_MGR : public IO_MGR
{
public:
enum DESIGN_BLOCK_FILE_T
{
DESIGN_BLOCK_FILE_UNKNOWN = 0, ///< 0 is not a legal menu id on Mac
KICAD_SEXP, ///< S-expression KiCad file format.
FILE_TYPE_NONE
};
static const wxString ShowType( DESIGN_BLOCK_FILE_T aFileType );
static DESIGN_BLOCK_IO* FindPlugin( DESIGN_BLOCK_FILE_T aFileType );
static DESIGN_BLOCK_FILE_T EnumFromStr( const wxString& aFileType );
static DESIGN_BLOCK_FILE_T GuessPluginTypeFromLibPath( const wxString& aLibPath, int aCtl = 0 );
/**
* Convert a design block library to the latest KiCad format
*/
static bool ConvertLibrary( std::map<std::string, UTF8>* aOldFileProps,
const wxString& aOldFilePath, const wxString& aNewFilePath );
};
class KICOMMON_API DESIGN_BLOCK_IO : public IO_BASE
{
public:
DESIGN_BLOCK_IO() : IO_BASE( wxS( "KiCad" ) ) {}
const IO_BASE::IO_FILE_DESC GetLibraryDesc() const override;
long long GetLibraryTimestamp( const wxString& aLibraryPath ) const;
void DesignBlockEnumerate( wxArrayString& aDesignBlockNames, const wxString& aLibraryPath,
bool aBestEfforts,
const std::map<std::string, UTF8>* aProperties = nullptr );
const DESIGN_BLOCK*
GetEnumeratedDesignBlock( const wxString& aLibraryPath, const wxString& aDesignBlockName,
const std::map<std::string, UTF8>* aProperties = nullptr )
{
return DesignBlockLoad( aLibraryPath, aDesignBlockName, false, aProperties );
}
bool DesignBlockExists( const wxString& aLibraryPath, const wxString& aDesignBlockName,
const std::map<std::string, UTF8>* aProperties = nullptr )
{
return DesignBlockLoad( aLibraryPath, aDesignBlockName, true, aProperties ) != nullptr;
}
DESIGN_BLOCK* ImportDesignBlock( const wxString& aDesignBlockPath,
wxString& aDesignBlockNameOut,
const std::map<std::string, UTF8>* aProperties = nullptr )
{
return nullptr;
}
void CreateLibrary( const wxString& aLibraryPath,
const std::map<std::string, UTF8>* aProperties = nullptr ) override;
virtual bool DeleteLibrary( const wxString& aLibraryPath,
const std::map<std::string, UTF8>* aProperties = nullptr ) override;
bool IsLibraryWritable( const wxString& aLibraryPath ) override;
DESIGN_BLOCK* DesignBlockLoad( const wxString& aLibraryPath, const wxString& aDesignBlockName,
bool aKeepUUID = false,
const std::map<std::string, UTF8>* aProperties = nullptr );
void DesignBlockSave( const wxString& aLibraryPath, const DESIGN_BLOCK* aDesignBlock,
const std::map<std::string, UTF8>* aProperties = nullptr );
void DesignBlockDelete( const wxString& aLibraryPath, const wxString& aDesignBlockName,
const std::map<std::string, UTF8>* aProperties = nullptr );
virtual void PrefetchLib( const wxString& aLibraryPath,
const std::map<std::string, UTF8>* aProperties = nullptr )
{
}
};
#endif

View File

@ -0,0 +1,683 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2012-2024 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 2
* 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, you may find one here:
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
* or you may search the http://www.gnu.org website for the version 2 license,
* or you may write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
#include <kiface_base.h>
#include <env_vars.h>
#include <design_block_info.h>
#include <lib_id.h>
#include <lib_table_lexer.h>
#include <paths.h>
#include <pgm_base.h>
#include <search_stack.h>
#include <settings/kicad_settings.h>
#include <settings/settings_manager.h>
#include <systemdirsappend.h>
#include <design_block_lib_table.h>
#include <design_block.h>
#include <wx/dir.h>
#include <wx/hash.h>
#define OPT_SEP '|' ///< options separator character
/// The global design block library table. This is not dynamically allocated because
/// in a multiple project environment we must keep its address constant (since it is
/// the fallback table for multiple projects).
DESIGN_BLOCK_LIB_TABLE GDesignBlockTable;
/// The global footprint info table. This is performance-intensive to build so we
/// keep a hash-stamped global version. Any deviation from the request vs. stored
/// hash will result in it being rebuilt.
DESIGN_BLOCK_LIST_IMPL GDesignBlockList;
using namespace LIB_TABLE_T;
static const wxChar global_tbl_name[] = wxT( "design-block-lib-table" );
bool DESIGN_BLOCK_LIB_TABLE_ROW::operator==( const DESIGN_BLOCK_LIB_TABLE_ROW& aRow ) const
{
return LIB_TABLE_ROW::operator==( aRow ) && type == aRow.type;
}
void DESIGN_BLOCK_LIB_TABLE_ROW::SetType( const wxString& aType )
{
type = DESIGN_BLOCK_IO_MGR::EnumFromStr( aType );
if( DESIGN_BLOCK_IO_MGR::DESIGN_BLOCK_FILE_T( -1 ) == type )
type = DESIGN_BLOCK_IO_MGR::KICAD_SEXP;
plugin.reset();
}
DESIGN_BLOCK_LIB_TABLE::DESIGN_BLOCK_LIB_TABLE( DESIGN_BLOCK_LIB_TABLE* aFallBackTable ) :
LIB_TABLE( aFallBackTable )
{
// not copying fall back, simply search aFallBackTable separately
// if "nickName not found".
}
void DESIGN_BLOCK_LIB_TABLE::Parse( LIB_TABLE_LEXER* in )
{
T tok;
wxString errMsg; // to collect error messages
// This table may be nested within a larger s-expression, or not.
// Allow for parser of that optional containing s-epression to have looked ahead.
if( in->CurTok() != T_design_block_lib_table )
{
in->NeedLEFT();
if( ( tok = in->NextTok() ) != T_design_block_lib_table )
in->Expecting( T_design_block_lib_table );
}
while( ( tok = in->NextTok() ) != T_RIGHT )
{
std::unique_ptr<DESIGN_BLOCK_LIB_TABLE_ROW> row =
std::make_unique<DESIGN_BLOCK_LIB_TABLE_ROW>();
if( tok == T_EOF )
in->Expecting( T_RIGHT );
if( tok != T_LEFT )
in->Expecting( T_LEFT );
// in case there is a "row integrity" error, tell where later.
int lineNum = in->CurLineNumber();
tok = in->NextTok();
// Optionally parse the current version number
if( tok == T_version )
{
in->NeedNUMBER( "version" );
m_version = std::stoi( in->CurText() );
in->NeedRIGHT();
continue;
}
if( tok != T_lib )
in->Expecting( T_lib );
// (name NICKNAME)
in->NeedLEFT();
if( ( tok = in->NextTok() ) != T_name )
in->Expecting( T_name );
in->NeedSYMBOLorNUMBER();
row->SetNickName( in->FromUTF8() );
in->NeedRIGHT();
// After (name), remaining (lib) elements are order independent, and in
// some cases optional.
bool sawType = false;
bool sawOpts = false;
bool sawDesc = false;
bool sawUri = false;
bool sawDisabled = false;
while( ( tok = in->NextTok() ) != T_RIGHT )
{
if( tok == T_EOF )
in->Unexpected( T_EOF );
if( tok != T_LEFT )
in->Expecting( T_LEFT );
tok = in->NeedSYMBOLorNUMBER();
switch( tok )
{
case T_uri:
if( sawUri )
in->Duplicate( tok );
sawUri = true;
in->NeedSYMBOLorNUMBER();
row->SetFullURI( in->FromUTF8() );
break;
case T_type:
if( sawType )
in->Duplicate( tok );
sawType = true;
in->NeedSYMBOLorNUMBER();
row->SetType( in->FromUTF8() );
break;
case T_options:
if( sawOpts )
in->Duplicate( tok );
sawOpts = true;
in->NeedSYMBOLorNUMBER();
row->SetOptions( in->FromUTF8() );
break;
case T_descr:
if( sawDesc )
in->Duplicate( tok );
sawDesc = true;
in->NeedSYMBOLorNUMBER();
row->SetDescr( in->FromUTF8() );
break;
case T_disabled:
if( sawDisabled )
in->Duplicate( tok );
sawDisabled = true;
row->SetEnabled( false );
break;
case T_hidden:
// Hiding design block libraries is not yet supported. Unclear what path can set this
// attribute, but clear it on load.
row->SetVisible();
break;
default: in->Unexpected( tok );
}
in->NeedRIGHT();
}
if( !sawType )
in->Expecting( T_type );
if( !sawUri )
in->Expecting( T_uri );
// All nickNames within this table fragment must be unique, so we do not use doReplace
// in doInsertRow(). (However a fallBack table can have a conflicting nickName and ours
// will supercede that one since in FindLib() we search this table before any fall back.)
wxString nickname = row->GetNickName(); // store it to be able to used it
// after row deletion if an error occurs
bool doReplace = false;
LIB_TABLE_ROW* tmp = row.release();
if( !doInsertRow( tmp, doReplace ) )
{
delete tmp; // The table did not take ownership of the row.
wxString msg = wxString::Format( _( "Duplicate library nickname '%s' found in "
"design block library table file line %d." ),
nickname, lineNum );
if( !errMsg.IsEmpty() )
errMsg << '\n';
errMsg << msg;
}
}
if( !errMsg.IsEmpty() )
THROW_IO_ERROR( errMsg );
}
bool DESIGN_BLOCK_LIB_TABLE::operator==( const DESIGN_BLOCK_LIB_TABLE& aDesignBlockTable ) const
{
if( m_rows.size() == aDesignBlockTable.m_rows.size() )
{
for( unsigned i = 0; i < m_rows.size(); ++i )
{
if( (DESIGN_BLOCK_LIB_TABLE_ROW&) m_rows[i]
!= (DESIGN_BLOCK_LIB_TABLE_ROW&) aDesignBlockTable.m_rows[i] )
return false;
}
return true;
}
return false;
}
void DESIGN_BLOCK_LIB_TABLE::Format( OUTPUTFORMATTER* aOutput, int aIndentLevel ) const
{
aOutput->Print( aIndentLevel, "(design_block_lib_table\n" );
aOutput->Print( aIndentLevel + 1, "(version %d)\n", m_version );
for( LIB_TABLE_ROWS_CITER it = m_rows.begin(); it != m_rows.end(); ++it )
it->Format( aOutput, aIndentLevel + 1 );
aOutput->Print( aIndentLevel, ")\n" );
}
long long DESIGN_BLOCK_LIB_TABLE::GenerateTimestamp( const wxString* aNickname )
{
long long hash = 0;
if( aNickname )
{
const DESIGN_BLOCK_LIB_TABLE_ROW* row = FindRow( *aNickname, true );
wxCHECK( row && row->plugin, hash );
return row->plugin->GetLibraryTimestamp( row->GetFullURI( true ) )
+ wxHashTable::MakeKey( *aNickname );
}
for( const wxString& nickname : GetLogicalLibs() )
{
const DESIGN_BLOCK_LIB_TABLE_ROW* row = nullptr;
try
{
row = FindRow( nickname, true );
}
catch( ... )
{
// Do nothing if not found: just skip.
}
wxCHECK2( row && row->plugin, continue );
hash += row->plugin->GetLibraryTimestamp( row->GetFullURI( true ) )
+ wxHashTable::MakeKey( nickname );
}
return hash;
}
void DESIGN_BLOCK_LIB_TABLE::DesignBlockEnumerate( wxArrayString& aDesignBlockNames,
const wxString& aNickname, bool aBestEfforts )
{
const DESIGN_BLOCK_LIB_TABLE_ROW* row = FindRow( aNickname, true );
wxASSERT( row->plugin );
row->plugin->DesignBlockEnumerate( aDesignBlockNames, row->GetFullURI( true ), aBestEfforts,
row->GetProperties() );
}
void DESIGN_BLOCK_LIB_TABLE::PrefetchLib( const wxString& aNickname )
{
const DESIGN_BLOCK_LIB_TABLE_ROW* row = FindRow( aNickname, true );
wxASSERT( row->plugin );
row->plugin->PrefetchLib( row->GetFullURI( true ), row->GetProperties() );
}
const DESIGN_BLOCK_LIB_TABLE_ROW* DESIGN_BLOCK_LIB_TABLE::FindRow( const wxString& aNickname,
bool aCheckIfEnabled )
{
DESIGN_BLOCK_LIB_TABLE_ROW* row =
static_cast<DESIGN_BLOCK_LIB_TABLE_ROW*>( findRow( aNickname, aCheckIfEnabled ) );
if( !row )
{
wxString msg = wxString::Format(
_( "design-block-lib-table files contain no library named '%s'." ), aNickname );
THROW_IO_ERROR( msg );
}
if( !row->plugin )
row->setPlugin( DESIGN_BLOCK_IO_MGR::FindPlugin( row->type ) );
return row;
}
static void setLibNickname( DESIGN_BLOCK* aModule, const wxString& aNickname,
const wxString& aDesignBlockName )
{
// The library cannot know its own name, because it might have been renamed or moved.
// Therefore design blocks cannot know their own library nickname when residing in
// a design block library.
// Only at this API layer can we tell the design block about its actual library nickname.
if( aModule )
{
// remove "const"-ness, I really do want to set nickname without
// having to copy the LIB_ID and its two strings, twice each.
LIB_ID& dbid = (LIB_ID&) aModule->GetLibId();
// Catch any misbehaving plugin, which should be setting internal design block name properly:
wxASSERT( aDesignBlockName == dbid.GetLibItemName().wx_str() );
// and clearing nickname
wxASSERT( !dbid.GetLibNickname().size() );
dbid.SetLibNickname( aNickname );
}
}
const DESIGN_BLOCK*
DESIGN_BLOCK_LIB_TABLE::GetEnumeratedDesignBlock( const wxString& aNickname,
const wxString& aDesignBlockName )
{
const DESIGN_BLOCK_LIB_TABLE_ROW* row = FindRow( aNickname, true );
wxASSERT( row->plugin );
return row->plugin->GetEnumeratedDesignBlock( row->GetFullURI( true ), aDesignBlockName,
row->GetProperties() );
}
bool DESIGN_BLOCK_LIB_TABLE::DesignBlockExists( const wxString& aNickname,
const wxString& aDesignBlockName )
{
try
{
const DESIGN_BLOCK_LIB_TABLE_ROW* row = FindRow( aNickname, true );
wxASSERT( row->plugin );
return row->plugin->DesignBlockExists( row->GetFullURI( true ), aDesignBlockName,
row->GetProperties() );
}
catch( ... )
{
return false;
}
}
DESIGN_BLOCK* DESIGN_BLOCK_LIB_TABLE::DesignBlockLoad( const wxString& aNickname,
const wxString& aDesignBlockName,
bool aKeepUUID )
{
const DESIGN_BLOCK_LIB_TABLE_ROW* row = FindRow( aNickname, true );
wxASSERT( row->plugin );
DESIGN_BLOCK* ret = row->plugin->DesignBlockLoad( row->GetFullURI( true ), aDesignBlockName,
aKeepUUID, row->GetProperties() );
setLibNickname( ret, row->GetNickName(), aDesignBlockName );
return ret;
}
DESIGN_BLOCK_LIB_TABLE::SAVE_T
DESIGN_BLOCK_LIB_TABLE::DesignBlockSave( const wxString& aNickname,
const DESIGN_BLOCK* aDesignBlock, bool aOverwrite )
{
const DESIGN_BLOCK_LIB_TABLE_ROW* row = FindRow( aNickname, true );
wxASSERT( row->plugin );
if( !aOverwrite )
{
// Try loading the design block to see if it already exists, caller wants overwrite
// protection, which is atypical, not the default.
wxString DesignBlockname = aDesignBlock->GetLibId().GetLibItemName();
std::unique_ptr<DESIGN_BLOCK> design_block( row->plugin->DesignBlockLoad(
row->GetFullURI( true ), DesignBlockname, row->GetProperties() ) );
if( design_block.get() )
return SAVE_SKIPPED;
}
row->plugin->DesignBlockSave( row->GetFullURI( true ), aDesignBlock, row->GetProperties() );
return SAVE_OK;
}
void DESIGN_BLOCK_LIB_TABLE::DesignBlockDelete( const wxString& aNickname,
const wxString& aDesignBlockName )
{
const DESIGN_BLOCK_LIB_TABLE_ROW* row = FindRow( aNickname, true );
wxASSERT( row->plugin );
return row->plugin->DesignBlockDelete( row->GetFullURI( true ), aDesignBlockName,
row->GetProperties() );
}
bool DESIGN_BLOCK_LIB_TABLE::IsDesignBlockLibWritable( const wxString& aNickname )
{
const DESIGN_BLOCK_LIB_TABLE_ROW* row = FindRow( aNickname, true );
wxASSERT( row->plugin );
return row->plugin->IsLibraryWritable( row->GetFullURI( true ) );
}
void DESIGN_BLOCK_LIB_TABLE::DesignBlockLibDelete( const wxString& aNickname )
{
const DESIGN_BLOCK_LIB_TABLE_ROW* row = FindRow( aNickname, true );
wxASSERT( row->plugin );
row->plugin->DeleteLibrary( row->GetFullURI( true ), row->GetProperties() );
}
void DESIGN_BLOCK_LIB_TABLE::DesignBlockLibCreate( const wxString& aNickname )
{
const DESIGN_BLOCK_LIB_TABLE_ROW* row = FindRow( aNickname, true );
wxASSERT( row->plugin );
row->plugin->CreateLibrary( row->GetFullURI( true ), row->GetProperties() );
}
DESIGN_BLOCK*
DESIGN_BLOCK_LIB_TABLE::DesignBlockLoadWithOptionalNickname( const LIB_ID& aDesignBlockId,
bool aKeepUUID )
{
wxString nickname = aDesignBlockId.GetLibNickname();
wxString DesignBlockname = aDesignBlockId.GetLibItemName();
if( nickname.size() )
{
return DesignBlockLoad( nickname, DesignBlockname, aKeepUUID );
}
// nickname is empty, sequentially search (alphabetically) all libs/nicks for first match:
else
{
std::vector<wxString> nicks = GetLogicalLibs();
// Search each library going through libraries alphabetically.
for( unsigned i = 0; i < nicks.size(); ++i )
{
// DesignBlockLoad() returns NULL on not found, does not throw exception
// unless there's an IO_ERROR.
DESIGN_BLOCK* ret = DesignBlockLoad( nicks[i], DesignBlockname, aKeepUUID );
if( ret )
return ret;
}
return nullptr;
}
}
const wxString DESIGN_BLOCK_LIB_TABLE::GlobalPathEnvVariableName()
{
return ENV_VAR::GetVersionedEnvVarName( wxS( "DESIGN_BLOCK_DIR" ) );
}
class PCM_DESIGN_BLOCK_LIB_TRAVERSER final : public wxDirTraverser
{
public:
explicit PCM_DESIGN_BLOCK_LIB_TRAVERSER( const wxString& aPath, DESIGN_BLOCK_LIB_TABLE& aTable,
const wxString& aPrefix ) :
m_lib_table( aTable ),
m_path_prefix( aPath ), m_lib_prefix( aPrefix )
{
wxFileName f( aPath, wxS( "" ) );
m_prefix_dir_count = f.GetDirCount();
}
wxDirTraverseResult OnFile( const wxString& aFilePath ) override { return wxDIR_CONTINUE; }
wxDirTraverseResult OnDir( const wxString& dirPath ) override
{
wxFileName dir = wxFileName::DirName( dirPath );
// consider a directory to be a lib if it's name ends with .pretty and
// it is under $KICADn_3RD_PARTY/design_blocks/<pkgid>/ i.e. has nested level of at least +3
if( dirPath.EndsWith( wxS( ".pretty" ) ) && dir.GetDirCount() >= m_prefix_dir_count + 3 )
{
wxString versionedPath = wxString::Format(
wxS( "${%s}" ), ENV_VAR::GetVersionedEnvVarName( wxS( "3RD_PARTY" ) ) );
wxArrayString parts = dir.GetDirs();
parts.RemoveAt( 0, m_prefix_dir_count );
parts.Insert( versionedPath, 0 );
wxString libPath = wxJoin( parts, '/' );
if( !m_lib_table.HasLibraryWithPath( libPath ) )
{
wxString name = parts.Last().substr( 0, parts.Last().length() - 7 );
wxString nickname = wxString::Format( wxS( "%s%s" ), m_lib_prefix, name );
if( m_lib_table.HasLibrary( nickname ) )
{
int increment = 1;
do
{
nickname =
wxString::Format( wxS( "%s%s_%d" ), m_lib_prefix, name, increment );
increment++;
} while( m_lib_table.HasLibrary( nickname ) );
}
m_lib_table.InsertRow( new DESIGN_BLOCK_LIB_TABLE_ROW(
nickname, libPath, wxT( "KiCad" ), wxEmptyString,
_( "Added by Plugin and Content Manager" ) ) );
}
}
return wxDIR_CONTINUE;
}
private:
DESIGN_BLOCK_LIB_TABLE& m_lib_table;
wxString m_path_prefix;
wxString m_lib_prefix;
size_t m_prefix_dir_count;
};
bool DESIGN_BLOCK_LIB_TABLE::LoadGlobalTable( DESIGN_BLOCK_LIB_TABLE& aTable )
{
bool tableExists = true;
wxFileName fn = GetGlobalTableFileName();
if( !fn.FileExists() )
{
tableExists = false;
if( !fn.DirExists() && !fn.Mkdir( 0x777, wxPATH_MKDIR_FULL ) )
{
THROW_IO_ERROR( wxString::Format( _( "Cannot create global library table path '%s'." ),
fn.GetPath() ) );
}
// Attempt to copy the default global file table from the KiCad
// template folder to the user's home configuration path.
SEARCH_STACK ss;
SystemDirsAppend( &ss );
const ENV_VAR_MAP& envVars = Pgm().GetLocalEnvVariables();
std::optional<wxString> v =
ENV_VAR::GetVersionedEnvVarValue( envVars, wxT( "TEMPLATE_DIR" ) );
if( v && !v->IsEmpty() )
ss.AddPaths( *v, 0 );
wxString fileName = ss.FindValidPath( global_tbl_name );
// The fallback is to create an empty global design block table for the user to populate.
if( fileName.IsEmpty() || !::wxCopyFile( fileName, fn.GetFullPath(), false ) )
{
DESIGN_BLOCK_LIB_TABLE emptyTable;
emptyTable.Save( fn.GetFullPath() );
}
}
aTable.clear();
aTable.Load( fn.GetFullPath() );
SETTINGS_MANAGER& mgr = Pgm().GetSettingsManager();
KICAD_SETTINGS* settings = mgr.GetAppSettings<KICAD_SETTINGS>();
const ENV_VAR_MAP& env = Pgm().GetLocalEnvVariables();
wxString packagesPath;
if( std::optional<wxString> v = ENV_VAR::GetVersionedEnvVarValue( env, wxT( "3RD_PARTY" ) ) )
packagesPath = *v;
if( settings->m_PcmLibAutoAdd )
{
// Scan for libraries in PCM packages directory
wxFileName d( packagesPath, wxS( "" ) );
d.AppendDir( wxS( "design_blocks" ) );
if( d.DirExists() )
{
PCM_DESIGN_BLOCK_LIB_TRAVERSER traverser( packagesPath, aTable,
settings->m_PcmLibPrefix );
wxDir dir( d.GetPath() );
dir.Traverse( traverser );
}
}
if( settings->m_PcmLibAutoRemove )
{
// Remove PCM libraries that no longer exist
std::vector<wxString> to_remove;
for( size_t i = 0; i < aTable.GetCount(); i++ )
{
LIB_TABLE_ROW& row = aTable.At( i );
wxString path = row.GetFullURI( true );
if( path.StartsWith( packagesPath ) && !wxDir::Exists( path ) )
to_remove.push_back( row.GetNickName() );
}
for( const wxString& nickName : to_remove )
aTable.RemoveRow( aTable.FindRow( nickName ) );
}
return tableExists;
}
wxString DESIGN_BLOCK_LIB_TABLE::GetGlobalTableFileName()
{
wxFileName fn;
fn.SetPath( PATHS::GetUserSettingsPath() );
fn.SetName( global_tbl_name );
return fn.GetFullPath();
}

View File

@ -1,3 +1,4 @@
design_block_lib_table
fp_lib_table
sym_lib_table
version
@ -8,4 +9,4 @@ uri
options
descr
disabled
hidden
hidden

View File

@ -359,6 +359,23 @@ LIB_TREE_NODE_LIBRARY& LIB_TREE_NODE_ROOT::AddLib( wxString const& aName, wxStri
}
void LIB_TREE_NODE_ROOT::RemoveLib( wxString const& aName )
{
m_Children.erase( std::remove_if( m_Children.begin(), m_Children.end(),
[&]( std::unique_ptr<LIB_TREE_NODE>& aNode )
{
return aNode->m_Name == aName;
} ),
m_Children.end() );
}
void LIB_TREE_NODE_ROOT::Clear()
{
m_Children.clear();
}
void LIB_TREE_NODE_ROOT::UpdateScore( EDA_COMBINED_MATCHER* aMatcher, const wxString& aLib,
std::function<bool( LIB_TREE_NODE& aNode )>* aFilter )
{

View File

@ -247,6 +247,12 @@ void LIB_TREE_MODEL_ADAPTER::DoAddLibrary( const wxString& aNodeName, const wxSt
}
void LIB_TREE_MODEL_ADAPTER::DoRemoveLibrary( const wxString& aNodeName )
{
m_tree.RemoveLib( aNodeName );
}
void LIB_TREE_MODEL_ADAPTER::UpdateSearchString( const wxString& aSearch, bool aState )
{
{
@ -383,7 +389,7 @@ void LIB_TREE_MODEL_ADAPTER::resortTree()
void LIB_TREE_MODEL_ADAPTER::PinLibrary( LIB_TREE_NODE* aTreeNode )
{
m_parent->Prj().PinLibrary( aTreeNode->m_LibId.GetLibNickname(), isSymbolModel() );
m_parent->Prj().PinLibrary( aTreeNode->m_LibId.GetLibNickname(), getLibType() );
aTreeNode->m_Pinned = true;
resortTree();
@ -393,7 +399,7 @@ void LIB_TREE_MODEL_ADAPTER::PinLibrary( LIB_TREE_NODE* aTreeNode )
void LIB_TREE_MODEL_ADAPTER::UnpinLibrary( LIB_TREE_NODE* aTreeNode )
{
m_parent->Prj().UnpinLibrary( aTreeNode->m_LibId.GetLibNickname(), isSymbolModel() );
m_parent->Prj().UnpinLibrary( aTreeNode->m_LibId.GetLibNickname(), getLibType() );
aTreeNode->m_Pinned = false;
resortTree();

View File

@ -106,6 +106,17 @@ wxString PATHS::GetDefaultUserFootprintsPath()
}
wxString PATHS::GetDefaultUserDesignBlocksPath()
{
wxFileName tmp;
getUserDocumentPath( tmp );
tmp.AppendDir( wxT( "blocks" ) );
return tmp.GetPath();
}
wxString PATHS::GetDefaultUser3DModelsPath()
{
wxFileName tmp;
@ -224,6 +235,16 @@ wxString PATHS::GetStockFootprintsPath()
}
wxString PATHS::GetStockDesignBlocksPath()
{
wxString path;
path = GetStockEDALibraryPath() + wxT( "/blocks" );
return path;
}
wxString PATHS::GetStock3dmodelsPath()
{
wxString path;

View File

@ -28,6 +28,7 @@
#include <pgm_base.h>
#include <confirm.h>
#include <core/kicad_algo.h>
#include <design_block_lib_table.h>
#include <fp_lib_table.h>
#include <string_utils.h>
#include <kiface_ids.h>
@ -167,40 +168,78 @@ const wxString PROJECT::FootprintLibTblName() const
}
void PROJECT::PinLibrary( const wxString& aLibrary, bool isSymbolLibrary )
const wxString PROJECT::DesignBlockLibTblName() const
{
return libTableName( wxS( "design-block-lib-table" ) );
}
void PROJECT::PinLibrary( const wxString& aLibrary, enum LIB_TYPE_T aLibType )
{
COMMON_SETTINGS* cfg = Pgm().GetCommonSettings();
std::vector<wxString>* pinnedLibs = isSymbolLibrary ? &m_projectFile->m_PinnedSymbolLibs
: &m_projectFile->m_PinnedFootprintLibs;
std::vector<wxString>* pinnedLibsCfg = nullptr;
std::vector<wxString>* pinnedLibsFile = nullptr;
if( !alg::contains( *pinnedLibs, aLibrary ) )
pinnedLibs->push_back( aLibrary );
switch( aLibType )
{
case LIB_TYPE_T::SYMBOL_LIB:
pinnedLibsFile = &m_projectFile->m_PinnedSymbolLibs;
pinnedLibsCfg = &cfg->m_Session.pinned_symbol_libs;
break;
case LIB_TYPE_T::FOOTPRINT_LIB:
pinnedLibsFile = &m_projectFile->m_PinnedFootprintLibs;
pinnedLibsCfg = &cfg->m_Session.pinned_fp_libs;
break;
case LIB_TYPE_T::DESIGN_BLOCK_LIB:
pinnedLibsFile = &m_projectFile->m_PinnedDesignBlockLibs;
pinnedLibsCfg = &cfg->m_Session.pinned_design_block_libs;
break;
default:
wxFAIL_MSG( "Cannot pin library: invalid library type" );
return;
}
if( !alg::contains( *pinnedLibsFile, aLibrary ) )
pinnedLibsFile->push_back( aLibrary );
Pgm().GetSettingsManager().SaveProject();
pinnedLibs = isSymbolLibrary ? &cfg->m_Session.pinned_symbol_libs
: &cfg->m_Session.pinned_fp_libs;
if( !alg::contains( *pinnedLibs, aLibrary ) )
pinnedLibs->push_back( aLibrary );
if( !alg::contains( *pinnedLibsCfg, aLibrary ) )
pinnedLibsCfg->push_back( aLibrary );
cfg->SaveToFile( Pgm().GetSettingsManager().GetPathForSettingsFile( cfg ) );
}
void PROJECT::UnpinLibrary( const wxString& aLibrary, bool isSymbolLibrary )
void PROJECT::UnpinLibrary( const wxString& aLibrary, enum LIB_TYPE_T aLibType )
{
COMMON_SETTINGS* cfg = Pgm().GetCommonSettings();
std::vector<wxString>* pinnedLibs = isSymbolLibrary ? &m_projectFile->m_PinnedSymbolLibs
: &m_projectFile->m_PinnedFootprintLibs;
std::vector<wxString>* pinnedLibsCfg = nullptr;
std::vector<wxString>* pinnedLibsFile = nullptr;
alg::delete_matching( *pinnedLibs, aLibrary );
switch( aLibType )
{
case LIB_TYPE_T::SYMBOL_LIB:
pinnedLibsFile = &m_projectFile->m_PinnedSymbolLibs;
pinnedLibsCfg = &cfg->m_Session.pinned_symbol_libs;
break;
case LIB_TYPE_T::FOOTPRINT_LIB:
pinnedLibsFile = &m_projectFile->m_PinnedFootprintLibs;
pinnedLibsCfg = &cfg->m_Session.pinned_fp_libs;
break;
case LIB_TYPE_T::DESIGN_BLOCK_LIB:
pinnedLibsFile = &m_projectFile->m_PinnedDesignBlockLibs;
pinnedLibsCfg = &cfg->m_Session.pinned_design_block_libs;
break;
default:
wxFAIL_MSG( "Cannot unpin library: invalid library type" );
return;
}
alg::delete_matching( *pinnedLibsFile, aLibrary );
Pgm().GetSettingsManager().SaveProject();
pinnedLibs = isSymbolLibrary ? &cfg->m_Session.pinned_symbol_libs
: &cfg->m_Session.pinned_fp_libs;
alg::delete_matching( *pinnedLibs, aLibrary );
alg::delete_matching( *pinnedLibsCfg, aLibrary );
cfg->SaveToFile( Pgm().GetSettingsManager().GetPathForSettingsFile( cfg ) );
}
@ -374,3 +413,39 @@ FP_LIB_TABLE* PROJECT::PcbFootprintLibs( KIWAY& aKiway )
return tbl;
}
DESIGN_BLOCK_LIB_TABLE* PROJECT::DesignBlockLibs()
{
// This is a lazy loading function, it loads the project specific table when
// that table is asked for, not before.
DESIGN_BLOCK_LIB_TABLE* tbl = (DESIGN_BLOCK_LIB_TABLE*) GetElem( ELEM::DESIGN_BLOCK_LIB_TABLE );
if( tbl )
{
wxASSERT( tbl->ProjectElementType() == PROJECT::ELEM::DESIGN_BLOCK_LIB_TABLE );
}
else
{
try
{
tbl = new DESIGN_BLOCK_LIB_TABLE( &GDesignBlockTable );
tbl->Load( DesignBlockLibTblName() );
SetElem( ELEM::DESIGN_BLOCK_LIB_TABLE, tbl );
}
catch( const IO_ERROR& ioe )
{
DisplayErrorMessage( nullptr, _( "Error loading project design block library table." ),
ioe.What() );
}
catch( ... )
{
DisplayErrorMessage( nullptr,
_( "Error loading project design block library table." ) );
}
}
return tbl;
}

View File

@ -346,6 +346,9 @@ COMMON_SETTINGS::COMMON_SETTINGS() :
m_params.emplace_back( new PARAM_LIST<wxString>( "session.pinned_fp_libs",
&m_Session.pinned_fp_libs, {} ) );
m_params.emplace_back( new PARAM_LIST<wxString>( "session.pinned_design_block_libs",
&m_Session.pinned_design_block_libs, {} ) );
m_params.emplace_back( new PARAM<int>( "netclass_panel.sash_pos",
&m_NetclassPanel.sash_pos, 160 ) );
@ -666,6 +669,10 @@ void COMMON_SETTINGS::InitializeEnvironment()
path = basePath;
path.AppendDir( wxT( "symbols" ) );
addVar( ENV_VAR::GetVersionedEnvVarName( wxS( "SYMBOL_DIR" ) ), path.GetFullPath() );
path = basePath;
path.AppendDir( wxT( "blocks" ) );
addVar( ENV_VAR::GetVersionedEnvVarName( wxS( "DESIGN_BLOCK_DIR" ) ), path.GetFullPath() );
}

View File

@ -43,6 +43,9 @@ KICAD_SETTINGS::KICAD_SETTINGS() :
m_params.emplace_back(
new PARAM_LIST<wxString>( "system.open_projects", &m_OpenProjects, {} ) );
m_params.emplace_back( new PARAM<wxString>( "system.last_design_block_lib_dir",
&m_lastDesignBlockLibDir, "" ) );
m_params.emplace_back(
new PARAM<wxString>( "system.last_update_check_time", &m_lastUpdateCheckTime, "" ) );

View File

@ -1049,7 +1049,11 @@ std::vector<wxString> SETTINGS_MANAGER::GetOpenProjects() const
std::vector<wxString> ret;
for( const std::pair<const wxString, PROJECT*>& pair : m_projects )
ret.emplace_back( pair.first );
{
// Don't save empty projects (these are the default project settings)
if( !pair.first.IsEmpty() )
ret.emplace_back( pair.first );
}
return ret;
}

View File

@ -1104,6 +1104,13 @@ TOOL_ACTION ACTIONS::showFootprintLibTable( TOOL_ACTION_ARGS()
.Tooltip( _( "Edit the global and project footprint library lists" ) )
.Icon( BITMAPS::library_table ) );
TOOL_ACTION ACTIONS::showDesignBlockLibTable( TOOL_ACTION_ARGS()
.Name( "common.SuiteControl.showDesignBLockLibTable" )
.Scope( AS_GLOBAL )
.FriendlyName( _( "Manage Design Block Libraries..." ) )
.Tooltip( _( "Edit the global and project design block library lists" ) )
.Icon( BITMAPS::library_table ) );
TOOL_ACTION ACTIONS::gettingStarted( TOOL_ACTION_ARGS()
.Name( "common.SuiteControl.gettingStarted" )
.Scope( AS_GLOBAL )

View File

@ -82,7 +82,8 @@ int LIBRARY_EDITOR_CONTROL::PinLibrary( const TOOL_EVENT& aEvent )
if( current && !current->m_Pinned )
{
m_frame->Prj().PinLibrary( current->m_LibId.GetLibNickname(), false );
m_frame->Prj().PinLibrary( current->m_LibId.GetLibNickname(),
PROJECT::LIB_TYPE_T::FOOTPRINT_LIB );
current->m_Pinned = true;
regenerateLibraryTree();
}
@ -98,7 +99,8 @@ int LIBRARY_EDITOR_CONTROL::UnpinLibrary( const TOOL_EVENT& aEvent )
if( current && current->m_Pinned )
{
m_frame->Prj().UnpinLibrary( current->m_LibId.GetLibNickname(), false );
m_frame->Prj().UnpinLibrary( current->m_LibId.GetLibNickname(),
PROJECT::LIB_TYPE_T::FOOTPRINT_LIB );
current->m_Pinned = false;
regenerateLibraryTree();
}

View File

@ -58,6 +58,7 @@ const wxChar* const traceKiCad2Step = wxT( "KICAD2STEP" );
const wxChar* const traceUiProfile = wxT( "KICAD_UI_PROFILE" );
const wxChar* const traceGit = wxT( "KICAD_GIT" );
const wxChar* const traceEagleIo = wxT( "KICAD_EAGLE_IO" );
const wxChar* const traceDesignBlocks = wxT( "KICAD_DESIGN_BLOCK" );
wxString dump( const wxArrayString& aArray )

View File

@ -186,6 +186,9 @@ const std::string FILEEXT::IpcD356FileExtension( "d356" );
const std::string FILEEXT::Ipc2581FileExtension( "xml" );
const std::string FILEEXT::WorkbookFileExtension( "wbk" );
const std::string FILEEXT::KiCadDesignBlockLibPathExtension( "blocks" ); // this is a directory
const std::string FILEEXT::KiCadDesignBlockPathExtension( "block" ); // this is a directory
const std::string FILEEXT::PngFileExtension( "png" );
const std::string FILEEXT::JpegFileExtension( "jpg" );
const std::string FILEEXT::TextFileExtension( "txt" );
@ -337,6 +340,20 @@ wxString FILEEXT::KiCadFootprintLibPathWildcard()
}
wxString FILEEXT::KiCadDesignBlockPathWildcard()
{
return _( "KiCad design block path" )
+ AddFileExtListToFilter( { KiCadDesignBlockPathExtension } );
}
wxString FILEEXT::KiCadDesignBlockLibPathWildcard()
{
return _( "KiCad design block library paths" )
+ AddFileExtListToFilter( { KiCadDesignBlockLibPathExtension } );
}
wxString FILEEXT::DrawingSheetFileWildcard()
{
return _( "Drawing sheet files" )

View File

@ -272,7 +272,10 @@ set( EESCHEMA_SIM_SRCS
)
set( EESCHEMA_WIDGETS
widgets/design_block_pane.cpp
widgets/design_block_preview_widget.cpp
widgets/hierarchy_pane.cpp
widgets/panel_design_block_chooser.cpp
widgets/panel_sch_selection_filter_base.cpp
widgets/panel_sch_selection_filter.cpp
widgets/panel_symbol_chooser.cpp
@ -347,6 +350,8 @@ set( EESCHEMA_SRCS
bus-wire-junction.cpp
connection_graph.cpp
cross-probing.cpp
design_block_tree_model_adapter.cpp
design_block_utils.cpp
ee_collectors.cpp
eeschema_config.cpp
eeschema_helpers.cpp
@ -441,6 +446,7 @@ set( EESCHEMA_SRCS
tools/ee_selection_tool.cpp
tools/rule_area_create_helper.cpp
tools/sch_drawing_tools.cpp
tools/sch_design_block_control.cpp
tools/sch_edit_table_tool.cpp
tools/sch_edit_tool.cpp
tools/sch_editor_control.cpp

View File

@ -37,6 +37,7 @@
#include <tools/ee_actions.h>
#include <tools/sch_editor_control.h>
#include <advanced_config.h>
#include <widgets/design_block_pane.h>
#include <wx/log.h>
SCH_ITEM* SCH_EDITOR_CONTROL::FindSymbolAndItem( const wxString* aPath, const wxString* aReference,
@ -998,6 +999,7 @@ void SCH_EDIT_FRAME::KiwayMailIn( KIWAY_EXPRESS& mail )
break;
case MAIL_RELOAD_LIB:
m_designBlocksPane->RefreshLibs();
SyncView();
break;

View File

@ -0,0 +1,199 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2018-2024 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 <pgm_base.h>
#include <eda_base_frame.h>
#include <core/kicad_algo.h>
#include <settings/common_settings.h>
#include <project/project_file.h>
#include <wx/log.h>
#include <wx/tokenzr.h>
#include <string_utils.h>
#include <eda_pattern_match.h>
#include <design_block.h>
#include <design_block_lib_table.h>
#include <design_block_info.h>
#include <footprint_info.h>
#include <wx/settings.h>
#include <design_block_tree_model_adapter.h>
#include <tools/sch_design_block_control.h>
wxObjectDataPtr<LIB_TREE_MODEL_ADAPTER>
DESIGN_BLOCK_TREE_MODEL_ADAPTER::Create( EDA_BASE_FRAME* aParent, LIB_TABLE* aLibs )
{
auto* adapter = new DESIGN_BLOCK_TREE_MODEL_ADAPTER( aParent, aLibs );
adapter->m_frame = aParent;
return wxObjectDataPtr<LIB_TREE_MODEL_ADAPTER>( adapter );
}
DESIGN_BLOCK_TREE_MODEL_ADAPTER::DESIGN_BLOCK_TREE_MODEL_ADAPTER( EDA_BASE_FRAME* aParent,
LIB_TABLE* aLibs ) :
LIB_TREE_MODEL_ADAPTER( aParent, wxT( "pinned_design_block_libs" ) ),
m_libs( (DESIGN_BLOCK_LIB_TABLE*) aLibs )
{
}
void DESIGN_BLOCK_TREE_MODEL_ADAPTER::AddLibraries( EDA_BASE_FRAME* aParent )
{
COMMON_SETTINGS* cfg = Pgm().GetCommonSettings();
PROJECT_FILE& project = aParent->Prj().GetProjectFile();
for( const wxString& libName : m_libs->GetLogicalLibs() )
{
const DESIGN_BLOCK_LIB_TABLE_ROW* library = nullptr;
try
{
library = m_libs->FindRow( libName, true );
}
catch( ... )
{
// Skip loading this library, if not exists/ not found
continue;
}
bool pinned = alg::contains( cfg->m_Session.pinned_design_block_libs, libName )
|| alg::contains( project.m_PinnedDesignBlockLibs, libName );
DoAddLibrary( libName, library->GetDescr(), getDesignBlocks( aParent, libName ), pinned,
true );
}
m_tree.AssignIntrinsicRanks();
}
void DESIGN_BLOCK_TREE_MODEL_ADAPTER::ClearLibraries()
{
m_tree.Clear();
}
std::vector<LIB_TREE_ITEM*>
DESIGN_BLOCK_TREE_MODEL_ADAPTER::getDesignBlocks( EDA_BASE_FRAME* aParent,
const wxString& aLibName )
{
std::vector<LIB_TREE_ITEM*> libList;
auto fullListStart = GDesignBlockList.GetList().begin();
auto fullListEnd = GDesignBlockList.GetList().end();
std::unique_ptr<DESIGN_BLOCK_INFO> dummy =
std::make_unique<DESIGN_BLOCK_INFO_IMPL>( aLibName, wxEmptyString );
// List is sorted, so use a binary search to find the range of footnotes for our library
auto libBounds = std::equal_range(
fullListStart, fullListEnd, dummy,
[]( const std::unique_ptr<DESIGN_BLOCK_INFO>& a,
const std::unique_ptr<DESIGN_BLOCK_INFO>& b )
{
return StrNumCmp( a->GetLibNickname(), b->GetLibNickname(), false ) < 0;
} );
for( auto i = libBounds.first; i != libBounds.second; ++i )
libList.push_back( i->get() );
return libList;
}
wxString DESIGN_BLOCK_TREE_MODEL_ADAPTER::GenerateInfo( LIB_ID const& aLibId, int aUnit )
{
const wxString DescriptionFormat = wxT(
"<b>__NAME__</b>"
"<br>__DESC__"
"<hr><table border=0>"
"__FIELDS__"
"</table>" );
const wxString KeywordsFormat = wxT(
"<tr>"
" <td><b>" + _( "Keywords" ) + "</b></td>"
" <td>__KEYWORDS__</td>"
"</tr>" );
const wxString DocFormat = wxT(
"<tr>"
" <td><b>" + _( "Documentation" ) + "</b></td>"
" <td><a href=\"__HREF__\">__TEXT__</a></td>"
"</tr>" );
if( !aLibId.IsValid() )
return wxEmptyString;
const DESIGN_BLOCK* db = nullptr;
try
{
db = m_libs->GetEnumeratedDesignBlock( aLibId.GetLibNickname(), aLibId.GetLibItemName() );
}
catch( const IO_ERROR& ioe )
{
wxLogError( _( "Error loading design block %s from library '%s'." ) + wxS( "\n%s" ),
aLibId.GetLibItemName().wx_str(), aLibId.GetLibNickname().wx_str(),
ioe.What() );
return wxEmptyString;
}
wxString html = DescriptionFormat;
if( db )
{
wxString name = aLibId.GetLibItemName();
wxString desc = db->GetLibDescription();
wxString keywords = db->GetKeywords();
wxString doc = db->GetDocumentationUrl();
wxString esc_desc = EscapeHTML( UnescapeString( desc ) );
// Add line breaks
esc_desc.Replace( wxS( "\n" ), wxS( "<br>" ) );
// Add links
esc_desc = LinkifyHTML( esc_desc );
html.Replace( "__DESC__", esc_desc );
html.Replace( "__NAME__", EscapeHTML( name ) );
wxString keywordsHtml = KeywordsFormat;
keywordsHtml.Replace( "__KEYWORDS__", EscapeHTML( keywords ) );
wxString docHtml = DocFormat;
docHtml.Replace( "__HREF__", doc );
if( doc.Length() > 75 )
doc = doc.Left( 72 ) + wxT( "..." );
docHtml.Replace( "__TEXT__", EscapeHTML( doc ) );
html.Replace( "__FIELDS__", keywordsHtml + docHtml );
}
return html;
}
TOOL_INTERACTIVE* DESIGN_BLOCK_TREE_MODEL_ADAPTER::GetContextMenuTool()
{
return m_frame->GetToolManager()->GetTool<SCH_DESIGN_BLOCK_CONTROL>();
}

Some files were not shown because too many files have changed in this diff Show More