libs 0019: remote lib edit — invalidate, don't auto-reload; editor copy counted + re-read
- kicadLibsInvalidate: a peer's edit only drops the lib's plugin entry + pcbnew's PreloadedFootprints (the cache the old reload never cleared — tree/preview/ LoadFootprint/update-from-library kept serving the old body); the fat re-load now runs lazily or from Update-from-library (kicadLibsReload, which also clears the preloaded cache) - usage bridges count the Footprint/Symbol Editor's open copy; update re-opens an unmodified copy, reports a modified one - embind TU gets eeschema/symbol_editor on its include path; smoke probes - kicad → 27051b46e2 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012Wd1r3ewftpV1DBSEArpRa
This commit is contained in:
parent
d7cb570f07
commit
767f2abfc0
8 changed files with 307 additions and 10 deletions
2
kicad
2
kicad
|
|
@ -1 +1 @@
|
||||||
Subproject commit ab62afa4732bf2b4c26cf185e4b08b2912edae01
|
Subproject commit 27051b46e2b3662731f913355a2bd752ca5c6561
|
||||||
|
|
@ -602,6 +602,10 @@ compile_embind_tu() {
|
||||||
local _src="$1" _obj="$2" _subdir="$3" _defines="${4:-}"
|
local _src="$1" _obj="$2" _subdir="$3" _defines="${4:-}"
|
||||||
|
|
||||||
local _includes="-I${KICAD_BUILD} -I${KICAD_DIR}/include -I${KICAD_DIR}/${_subdir} -I${KICAD_DIR}/common"
|
local _includes="-I${KICAD_BUILD} -I${KICAD_DIR}/include -I${KICAD_DIR}/${_subdir} -I${KICAD_DIR}/common"
|
||||||
|
# The editor-frame headers the bindings reach into (libs 0019 F3: the
|
||||||
|
# Symbol Editor's open copy) live one level down, like eeschema's own
|
||||||
|
# CMake include list (./symbol_editor).
|
||||||
|
_includes+=" -I${KICAD_DIR}/${_subdir}/symbol_editor -I${KICAD_DIR}/${_subdir}/widgets"
|
||||||
# Generated DSN-lexer headers (e.g. pcb_lexer.h, used transitively via kicad_clipboard.h →
|
# Generated DSN-lexer headers (e.g. pcb_lexer.h, used transitively via kicad_clipboard.h →
|
||||||
# pcb_io_kicad_sexpr_parser.h) are emitted into the common build subdir by make_lexer.
|
# pcb_io_kicad_sexpr_parser.h) are emitted into the common build subdir by make_lexer.
|
||||||
_includes+=" -I${KICAD_BUILD}/common"
|
_includes+=" -I${KICAD_BUILD}/common"
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@
|
||||||
#include <project.h>
|
#include <project.h>
|
||||||
#include <schematic.h>
|
#include <schematic.h>
|
||||||
#include <sch_edit_frame.h>
|
#include <sch_edit_frame.h>
|
||||||
|
#include <symbol_edit_frame.h>
|
||||||
#include <sch_io/kicad_sexpr/sch_io_kicad_sexpr.h>
|
#include <sch_io/kicad_sexpr/sch_io_kicad_sexpr.h>
|
||||||
#include <sch_sheet.h>
|
#include <sch_sheet.h>
|
||||||
#include <richio.h>
|
#include <richio.h>
|
||||||
|
|
@ -1383,6 +1384,29 @@ std::string schUpdateFromLibrary( std::string aLibNickname, std::string aNamesJs
|
||||||
|
|
||||||
commit.Push( wxT( "Update symbols from library" ) );
|
commit.Push( wxT( "Update symbols from library" ) );
|
||||||
fr->GetCanvas()->Refresh();
|
fr->GetCanvas()->Refresh();
|
||||||
|
|
||||||
|
// The Symbol Editor's own copy (libs 0019 F3): re-open it from the
|
||||||
|
// fresh library when unmodified; report and leave it with local edits.
|
||||||
|
if( SYMBOL_EDIT_FRAME* se = dynamic_cast<SYMBOL_EDIT_FRAME*>(
|
||||||
|
fr->Kiway().Player( FRAME_SCH_SYMBOL_EDITOR, false ) ) )
|
||||||
|
{
|
||||||
|
if( LIB_SYMBOL* cur = se->GetCurSymbol() )
|
||||||
|
{
|
||||||
|
const wxString curName = cur->GetName();
|
||||||
|
|
||||||
|
if( se->GetCurLib() == lib && names.count( curName ) )
|
||||||
|
{
|
||||||
|
const LIB_ID id( lib, curName );
|
||||||
|
|
||||||
|
if( se->IsContentModified() )
|
||||||
|
missing.push_back( std::string( id.Format().c_str() )
|
||||||
|
+ " (open in the Symbol Editor with unsaved edits — save or revert it first)" );
|
||||||
|
else
|
||||||
|
se->LoadSymbol( id, 1, 1 );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
emitLibUpdateDone( updated, missing );
|
emitLibUpdateDone( updated, missing );
|
||||||
} );
|
} );
|
||||||
|
|
||||||
|
|
@ -1425,6 +1449,18 @@ int schLibsSymbolUsage( std::string aLibNickname, std::string aSymbolName )
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The Symbol Editor's open copy counts as "used" too (libs 0019 F3).
|
||||||
|
if( SYMBOL_EDIT_FRAME* se = dynamic_cast<SYMBOL_EDIT_FRAME*>(
|
||||||
|
fr->Kiway().Player( FRAME_SCH_SYMBOL_EDITOR, false ) ) )
|
||||||
|
{
|
||||||
|
if( LIB_SYMBOL* cur = se->GetCurSymbol() )
|
||||||
|
{
|
||||||
|
if( se->GetCurLib() == wxString( target.GetLibNickname() )
|
||||||
|
&& cur->GetName() == wxString( target.GetLibItemName() ) )
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,11 @@ using namespace emscripten;
|
||||||
bool pcbEditorActive();
|
bool pcbEditorActive();
|
||||||
// libs 0017 §2c/2d: placed-footprint usage + update-from-library.
|
// libs 0017 §2c/2d: placed-footprint usage + update-from-library.
|
||||||
int pcbLibsFootprintUsage( std::string aLib, std::string aName );
|
int pcbLibsFootprintUsage( std::string aLib, std::string aName );
|
||||||
|
void pcbLibsInvalidatePreloaded( std::string aLib );
|
||||||
|
int pcbLibsTestPreload( std::string aLib );
|
||||||
|
std::string pcbLibsTestLoadFootprint( std::string aLib, std::string aName );
|
||||||
|
std::string pcbLibsTestEditorFootprint();
|
||||||
|
bool pcbLibsTestEditorLoad( std::string aLib, std::string aName );
|
||||||
std::string pcbUpdateFromLibrary( std::string aLib, std::string aNamesJson );
|
std::string pcbUpdateFromLibrary( std::string aLib, std::string aNamesJson );
|
||||||
std::string schUpdateFromLibrary( std::string aLib, std::string aNamesJson );
|
std::string schUpdateFromLibrary( std::string aLib, std::string aNamesJson );
|
||||||
void pcbCollabApply( std::string aJson );
|
void pcbCollabApply( std::string aJson );
|
||||||
|
|
@ -413,6 +418,47 @@ static int libsSymbolUsage( std::string aLib, std::string aName )
|
||||||
return schEditorActive() ? schLibsSymbolUsage( aLib, aName ) : 0;
|
return schEditorActive() ? schLibsSymbolUsage( aLib, aName ) : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// libs 0019 F2: a remote lib edit only INVALIDATES (cheap: drop the plugin
|
||||||
|
// entry + pcbnew's preloaded footprint cache); the fat re-load runs lazily on
|
||||||
|
// the next access or explicitly via kicadLibsReload from "Update from library".
|
||||||
|
static void libsInvalidate( std::string aKind, std::string aNick )
|
||||||
|
{
|
||||||
|
if( aKind == "footprint" && pcbEditorActive() )
|
||||||
|
pcbLibsInvalidatePreloaded( aNick );
|
||||||
|
|
||||||
|
pcbjam_libs::invalidateLibrary( aKind, aNick );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Full refresh: the preloaded cache must go too, or the re-loaded plugin sits
|
||||||
|
// under stale parsed copies (libs 0019 F1).
|
||||||
|
static void libsReload( std::string aKind, std::string aNick )
|
||||||
|
{
|
||||||
|
if( aKind == "footprint" && pcbEditorActive() )
|
||||||
|
pcbLibsInvalidatePreloaded( aNick );
|
||||||
|
|
||||||
|
pcbjam_libs::reloadLibrary( aKind, aNick );
|
||||||
|
}
|
||||||
|
|
||||||
|
static int libsTestPreload( std::string aLib )
|
||||||
|
{
|
||||||
|
return pcbEditorActive() ? pcbLibsTestPreload( aLib ) : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string libsTestLoadFootprint( std::string aLib, std::string aName )
|
||||||
|
{
|
||||||
|
return pcbEditorActive() ? pcbLibsTestLoadFootprint( aLib, aName ) : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string libsTestEditorFootprint()
|
||||||
|
{
|
||||||
|
return pcbEditorActive() ? pcbLibsTestEditorFootprint() : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool libsTestEditorLoad( std::string aLib, std::string aName )
|
||||||
|
{
|
||||||
|
return pcbEditorActive() ? pcbLibsTestEditorLoad( aLib, aName ) : false;
|
||||||
|
}
|
||||||
|
|
||||||
// Placed-instance count for a library footprint — board frame only (libs 0017 §2d).
|
// Placed-instance count for a library footprint — board frame only (libs 0017 §2d).
|
||||||
static int libsFootprintUsage( std::string aLib, std::string aName )
|
static int libsFootprintUsage( std::string aLib, std::string aName )
|
||||||
{
|
{
|
||||||
|
|
@ -619,7 +665,13 @@ EMSCRIPTEN_BINDINGS(kicad_editor) {
|
||||||
function("kicadCollabTestSelectFirst", &collabTestSelectFirst);
|
function("kicadCollabTestSelectFirst", &collabTestSelectFirst);
|
||||||
function("kicadCollabTestClearSelection", &collabTestClearSelection);
|
function("kicadCollabTestClearSelection", &collabTestClearSelection);
|
||||||
// Library reload after a remote (synced) lib edit — r2-idb-sync realtime.
|
// Library reload after a remote (synced) lib edit — r2-idb-sync realtime.
|
||||||
function("kicadLibsReload", &pcbjam_libs::reloadLibrary PCBJAM_PARKER_POLICY);
|
function("kicadLibsReload", &libsReload PCBJAM_PARKER_POLICY);
|
||||||
|
// Cheap invalidation for remote edits (libs 0019 F2) + smoke probes.
|
||||||
|
function("kicadLibsInvalidate", &libsInvalidate PCBJAM_PARKER_POLICY);
|
||||||
|
function("kicadLibsTestPreload", &libsTestPreload PCBJAM_PARKER_POLICY);
|
||||||
|
function("kicadLibsTestLoadFootprint", &libsTestLoadFootprint PCBJAM_PARKER_POLICY);
|
||||||
|
function("kicadLibsTestEditorFootprint", &libsTestEditorFootprint);
|
||||||
|
function("kicadLibsTestEditorLoad", &libsTestEditorLoad);
|
||||||
// Runtime lib-table row insert + load (a new team library appeared
|
// Runtime lib-table row insert + load (a new team library appeared
|
||||||
// mid-session; the lib set is otherwise frozen at boot).
|
// mid-session; the lib set is otherwise frozen at boot).
|
||||||
function("kicadLibsAddEntry", &pcbjam_libs::addLibraryEntry PCBJAM_PARKER_POLICY);
|
function("kicadLibsAddEntry", &pcbjam_libs::addLibraryEntry PCBJAM_PARKER_POLICY);
|
||||||
|
|
|
||||||
|
|
@ -77,6 +77,33 @@ inline void reloadLibrary( std::string aKind, std::string aNickname )
|
||||||
} );
|
} );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cheap counterpart of reloadLibrary (libs 0019): drop the lib's LIB_DATA
|
||||||
|
* entry (plugin instance + parsed cache) so the NEXT access re-reads the
|
||||||
|
* provider — no eager LoadLibraryEntry fat-load, no tree mail. A remote edit
|
||||||
|
* calls this; the fat-load happens lazily when the user looks at the lib,
|
||||||
|
* or explicitly through reloadLibrary from "Update from library".
|
||||||
|
* (pcbnew's FOOTPRINT_LIBRARY_ADAPTER::PreloadedFootprints is invalidated by
|
||||||
|
* the pcbnew-side caller — this header stays common-code only.)
|
||||||
|
*/
|
||||||
|
inline void invalidateLibrary( std::string aKind, std::string aNickname )
|
||||||
|
{
|
||||||
|
KIWAY_PLAYER* top =
|
||||||
|
wxTheApp ? dynamic_cast<KIWAY_PLAYER*>( wxTheApp->GetTopWindow() ) : nullptr;
|
||||||
|
|
||||||
|
if( !top )
|
||||||
|
return;
|
||||||
|
|
||||||
|
const bool fp = aKind == "footprint";
|
||||||
|
const wxString nick = wxString::FromUTF8( aNickname.c_str() );
|
||||||
|
|
||||||
|
pcbjam_collab::runOnCoroutine( top, [fp, nick]()
|
||||||
|
{
|
||||||
|
Pgm().GetLibraryManager().ReloadLibraryEntry(
|
||||||
|
fp ? LIBRARY_TABLE_TYPE::FOOTPRINT : LIBRARY_TABLE_TYPE::SYMBOL, nick );
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add one PCBJAM lib-table row at RUNTIME and load it — the lib SET is
|
* Add one PCBJAM lib-table row at RUNTIME and load it — the lib SET is
|
||||||
* otherwise frozen at boot (sym/fp-lib-table are written once in preRun).
|
* otherwise frozen at boot (sym/fp-lib-table are written once in preRun).
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,9 @@
|
||||||
#include <zone.h>
|
#include <zone.h>
|
||||||
#include <eda_text.h>
|
#include <eda_text.h>
|
||||||
#include <pcb_edit_frame.h>
|
#include <pcb_edit_frame.h>
|
||||||
|
#include <footprint_edit_frame.h>
|
||||||
|
#include <footprint_library_adapter.h>
|
||||||
|
#include <project_pcb.h>
|
||||||
#include <lib_id.h>
|
#include <lib_id.h>
|
||||||
#include <kicad_clipboard.h>
|
#include <kicad_clipboard.h>
|
||||||
#include <io/kicad/kicad_io_utils.h>
|
#include <io/kicad/kicad_io_utils.h>
|
||||||
|
|
@ -2484,9 +2487,129 @@ static bool kicadCollabBusyProbe()
|
||||||
|
|
||||||
// ── libs 0017 §2d/§2c: placed-footprint usage + update-from-library ──────────
|
// ── libs 0017 §2d/§2c: placed-footprint usage + update-from-library ──────────
|
||||||
|
|
||||||
|
// The Footprint Editor frame opened from this board session (nullptr when
|
||||||
|
// none exists — Player(…, false) never creates one).
|
||||||
|
static FOOTPRINT_EDIT_FRAME* fpEditorFrame( PCB_EDIT_FRAME* aFrame )
|
||||||
|
{
|
||||||
|
if( !aFrame )
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
return dynamic_cast<FOOTPRINT_EDIT_FRAME*>(
|
||||||
|
aFrame->Kiway().Player( FRAME_FOOTPRINT_EDITOR, false ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
// libs 0019 F1: drop pcbnew's SECOND footprint cache for one library.
|
||||||
|
// FOOTPRINT_LIBRARY_ADAPTER::PreloadedFootprints is read before the plugin by
|
||||||
|
// the tree, the preview and LoadFootprint, and its only invalidation is
|
||||||
|
// timestamp-gated (PCB_IO_PCBJAM_FP pins the timestamp), so a remote edit
|
||||||
|
// never reached it — the tree/preview/update kept serving the old body.
|
||||||
|
void pcbLibsInvalidatePreloaded( std::string aLibNickname )
|
||||||
|
{
|
||||||
|
PCB_EDIT_FRAME* fr = pcbFrame();
|
||||||
|
|
||||||
|
if( !fr )
|
||||||
|
return;
|
||||||
|
|
||||||
|
PROJECT_PCB::FootprintLibAdapter( &fr->Prj() )
|
||||||
|
->InvalidatePreloaded( wxString::FromUTF8( aLibNickname.c_str() ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test probes (libs 0019 smoke): warm the adapter's preloaded cache the way the
|
||||||
|
// footprint editor's tree does, and read a footprint's pad sizes through the
|
||||||
|
// SAME LoadFootprint path the preview / update-from-library use.
|
||||||
|
int pcbLibsTestPreload( std::string aLibNickname )
|
||||||
|
{
|
||||||
|
PCB_EDIT_FRAME* fr = pcbFrame();
|
||||||
|
|
||||||
|
if( !fr )
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
FOOTPRINT_LIBRARY_ADAPTER* adapter = PROJECT_PCB::FootprintLibAdapter( &fr->Prj() );
|
||||||
|
adapter->AsyncLoad();
|
||||||
|
adapter->BlockUntilLoaded();
|
||||||
|
return (int) adapter->GetFootprints( wxString::FromUTF8( aLibNickname.c_str() ), true ).size();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string pcbLibsTestLoadFootprint( std::string aLibNickname, std::string aFootprintName )
|
||||||
|
{
|
||||||
|
PCB_EDIT_FRAME* fr = pcbFrame();
|
||||||
|
|
||||||
|
if( !fr )
|
||||||
|
return "";
|
||||||
|
|
||||||
|
FOOTPRINT_LIBRARY_ADAPTER* adapter = PROJECT_PCB::FootprintLibAdapter( &fr->Prj() );
|
||||||
|
std::unique_ptr<FOOTPRINT> fp;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
fp.reset( adapter->LoadFootprint( wxString::FromUTF8( aLibNickname.c_str() ),
|
||||||
|
wxString::FromUTF8( aFootprintName.c_str() ), false ) );
|
||||||
|
}
|
||||||
|
catch( ... )
|
||||||
|
{
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
if( !fp )
|
||||||
|
return "";
|
||||||
|
|
||||||
|
std::string out;
|
||||||
|
|
||||||
|
for( PAD* pad : fp->Pads() )
|
||||||
|
{
|
||||||
|
out += std::string( pad->GetNumber().utf8_str() ) + ":"
|
||||||
|
+ std::to_string( pad->GetSize( PADSTACK::ALL_LAYERS ).x ) + "x"
|
||||||
|
+ std::to_string( pad->GetSize( PADSTACK::ALL_LAYERS ).y ) + ";";
|
||||||
|
}
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Smoke plumbing: load a library footprint into the (already open) Footprint
|
||||||
|
// Editor frame — what a tree double-click does, without tree geometry.
|
||||||
|
bool pcbLibsTestEditorLoad( std::string aLibNickname, std::string aFootprintName )
|
||||||
|
{
|
||||||
|
FOOTPRINT_EDIT_FRAME* fe = fpEditorFrame( pcbFrame() );
|
||||||
|
|
||||||
|
if( !fe )
|
||||||
|
return false;
|
||||||
|
|
||||||
|
const LIB_ID id( wxString::FromUTF8( aLibNickname.c_str() ),
|
||||||
|
wxString::FromUTF8( aFootprintName.c_str() ) );
|
||||||
|
pcbjam_collab::runOnCoroutine( fe, [fe, id]() { fe->LoadFootprintFromLibrary( id ); } );
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Smoke probe: the Footprint Editor's OPEN copy — "<lib>:<name>|<pads>" or "".
|
||||||
|
std::string pcbLibsTestEditorFootprint()
|
||||||
|
{
|
||||||
|
FOOTPRINT_EDIT_FRAME* fe = fpEditorFrame( pcbFrame() );
|
||||||
|
|
||||||
|
if( !fe || !fe->GetBoard() )
|
||||||
|
return "";
|
||||||
|
|
||||||
|
FOOTPRINT* fp = fe->GetBoard()->GetFirstFootprint();
|
||||||
|
|
||||||
|
if( !fp )
|
||||||
|
return "";
|
||||||
|
|
||||||
|
std::string out = std::string( fe->GetLoadedFPID().Format().c_str() ) + "|";
|
||||||
|
|
||||||
|
for( PAD* pad : fp->Pads() )
|
||||||
|
{
|
||||||
|
out += std::string( pad->GetNumber().utf8_str() ) + ":"
|
||||||
|
+ std::to_string( pad->GetSize( PADSTACK::ALL_LAYERS ).x ) + "x"
|
||||||
|
+ std::to_string( pad->GetSize( PADSTACK::ALL_LAYERS ).y ) + ";";
|
||||||
|
}
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
// Placed-instance count for a library footprint (mirror of schLibsSymbolUsage):
|
// Placed-instance count for a library footprint (mirror of schLibsSymbolUsage):
|
||||||
// drives the "a footprint you placed was updated" notice after a remote lib
|
// drives the "a footprint you placed was updated" notice after a remote lib
|
||||||
// edit. 0 without a board frame.
|
// edit. Counts the board's placements PLUS the copy open in the Footprint
|
||||||
|
// Editor (libs 0019 F3 — an open-but-unplaced footprint is "used" too).
|
||||||
|
// 0 without a board frame.
|
||||||
int pcbLibsFootprintUsage( std::string aLibNickname, std::string aFootprintName )
|
int pcbLibsFootprintUsage( std::string aLibNickname, std::string aFootprintName )
|
||||||
{
|
{
|
||||||
PCB_EDIT_FRAME* fr = pcbFrame();
|
PCB_EDIT_FRAME* fr = pcbFrame();
|
||||||
|
|
@ -2504,6 +2627,12 @@ int pcbLibsFootprintUsage( std::string aLibNickname, std::string aFootprintName
|
||||||
count++;
|
count++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if( FOOTPRINT_EDIT_FRAME* fe = fpEditorFrame( fr ) )
|
||||||
|
{
|
||||||
|
if( fe->GetLoadedFPID() == target )
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2595,6 +2724,26 @@ std::string pcbUpdateFromLibrary( std::string aLibNickname, std::string aNamesJs
|
||||||
|
|
||||||
commit.Push( wxT( "Update footprints from library" ) );
|
commit.Push( wxT( "Update footprints from library" ) );
|
||||||
fr->GetCanvas()->Refresh();
|
fr->GetCanvas()->Refresh();
|
||||||
|
|
||||||
|
// The Footprint Editor's own copy (libs 0019 F3): re-open it from the
|
||||||
|
// (fresh) library when it is one of the named items and has no local
|
||||||
|
// edits; with edits pending it is left alone and reported, never
|
||||||
|
// clobbered.
|
||||||
|
if( FOOTPRINT_EDIT_FRAME* fe = fpEditorFrame( fr ) )
|
||||||
|
{
|
||||||
|
const LIB_ID loaded = fe->GetLoadedFPID();
|
||||||
|
|
||||||
|
if( wxString( loaded.GetLibNickname() ) == lib
|
||||||
|
&& names.count( wxString( loaded.GetLibItemName() ) ) )
|
||||||
|
{
|
||||||
|
if( fe->IsContentModified() )
|
||||||
|
missing.push_back( std::string( loaded.Format().c_str() )
|
||||||
|
+ " (open in the Footprint Editor with unsaved edits — save or revert it first)" );
|
||||||
|
else
|
||||||
|
fe->LoadFootprintFromLibrary( loaded );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
emitLibUpdateDone( updated, missing );
|
emitLibUpdateDone( updated, missing );
|
||||||
} );
|
} );
|
||||||
|
|
||||||
|
|
@ -2636,6 +2785,10 @@ EMSCRIPTEN_BINDINGS(pcbnew) {
|
||||||
// Placed-footprint usage + update-from-library (libs 0017 §2c/2d).
|
// Placed-footprint usage + update-from-library (libs 0017 §2c/2d).
|
||||||
function("kicadLibsFootprintUsage", &pcbLibsFootprintUsage);
|
function("kicadLibsFootprintUsage", &pcbLibsFootprintUsage);
|
||||||
function("kicadUpdateFromLibrary", &pcbUpdateFromLibraryShim);
|
function("kicadUpdateFromLibrary", &pcbUpdateFromLibraryShim);
|
||||||
|
function("kicadLibsTestPreload", &pcbLibsTestPreload);
|
||||||
|
function("kicadLibsTestLoadFootprint", &pcbLibsTestLoadFootprint);
|
||||||
|
function("kicadLibsTestEditorFootprint", &pcbLibsTestEditorFootprint);
|
||||||
|
function("kicadLibsTestEditorLoad", &pcbLibsTestEditorLoad);
|
||||||
#endif
|
#endif
|
||||||
// Layer bridge (viewer-panels) — pcbnew-only names, merged-image safe
|
// Layer bridge (viewer-panels) — pcbnew-only names, merged-image safe
|
||||||
// (null-frame no-op when eeschema is the live frame).
|
// (null-frame no-op when eeschema is the live frame).
|
||||||
|
|
|
||||||
|
|
@ -90,8 +90,11 @@ export function useLibNotices(opts: {
|
||||||
}, []);
|
}, []);
|
||||||
/** Update every placed instance of the stale items from the library (2c). */
|
/** Update every placed instance of the stale items from the library (2c). */
|
||||||
const updateStaleFromLibrary = React.useCallback(async () => {
|
const updateStaleFromLibrary = React.useCallback(async () => {
|
||||||
const mod = (window as { Module?: { kicadUpdateFromLibrary?: unknown } }).Module;
|
const mod = (window as {
|
||||||
|
Module?: { kicadUpdateFromLibrary?: unknown; kicadLibsReload?: unknown };
|
||||||
|
}).Module;
|
||||||
const fn = mod?.kicadUpdateFromLibrary;
|
const fn = mod?.kicadUpdateFromLibrary;
|
||||||
|
const reload = mod?.kicadLibsReload;
|
||||||
if (typeof fn !== "function") {
|
if (typeof fn !== "function") {
|
||||||
setLibError("This editor build can't update placed items from the library — reload to refresh them.");
|
setLibError("This editor build can't update placed items from the library — reload to refresh them.");
|
||||||
return;
|
return;
|
||||||
|
|
@ -102,10 +105,20 @@ export function useLibNotices(opts: {
|
||||||
// The bridge queues the edit on the frame's coroutine and answers
|
// The bridge queues the edit on the frame's coroutine and answers
|
||||||
// {queued:true}; the outcome arrives as a `pcbjam:lib-update-done`
|
// {queued:true}; the outcome arrives as a `pcbjam:lib-update-done`
|
||||||
// window event (or {ok:false,error} synchronously).
|
// window event (or {ok:false,error} synchronously).
|
||||||
const done = new Promise<{ ok: boolean; updated?: number; error?: string }>((resolve) => {
|
// libs 0019 F2: the remote edit only invalidated the caches — bring the
|
||||||
|
// lib back to LOADED first (same coroutine queue, so it lands before
|
||||||
|
// the exchange below).
|
||||||
|
if (typeof reload === "function") {
|
||||||
|
try {
|
||||||
|
(reload as (kind: string, lib: string) => void)(entry.kind, entry.lib);
|
||||||
|
} catch {
|
||||||
|
/* the update below falls back to the lazy load */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const done = new Promise<{ ok: boolean; updated?: number; missing?: string[]; error?: string }>((resolve) => {
|
||||||
const onDone = (e: Event) => {
|
const onDone = (e: Event) => {
|
||||||
window.removeEventListener("pcbjam:lib-update-done", onDone);
|
window.removeEventListener("pcbjam:lib-update-done", onDone);
|
||||||
resolve((e as CustomEvent<{ ok: boolean; updated?: number }>).detail);
|
resolve((e as CustomEvent<{ ok: boolean; updated?: number; missing?: string[] }>).detail);
|
||||||
};
|
};
|
||||||
window.addEventListener("pcbjam:lib-update-done", onDone);
|
window.addEventListener("pcbjam:lib-update-done", onDone);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
|
@ -131,6 +144,10 @@ export function useLibNotices(opts: {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
console.log(`[libs] updated ${outcome.updated ?? "?"} placed ${entry.kind}(s) from "${entry.lib}"`);
|
console.log(`[libs] updated ${outcome.updated ?? "?"} placed ${entry.kind}(s) from "${entry.lib}"`);
|
||||||
|
// An open editor copy with local edits is left alone (0019 F3) — say so.
|
||||||
|
if (outcome.missing && outcome.missing.length > 0) {
|
||||||
|
setLibError(`Not updated: ${outcome.missing.join("; ")}`);
|
||||||
|
}
|
||||||
clearStale(key);
|
clearStale(key);
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
|
|
|
||||||
|
|
@ -111,13 +111,21 @@ export function syncedLibsSource(
|
||||||
const names = [...(pendingNames.get(kind) ?? [])];
|
const names = [...(pendingNames.get(kind) ?? [])];
|
||||||
pendingNames.delete(kind);
|
pendingNames.delete(kind);
|
||||||
const mod = (globalThis as { Module?: Record<string, unknown> }).Module;
|
const mod = (globalThis as { Module?: Record<string, unknown> }).Module;
|
||||||
|
// libs 0019 F2: a remote edit only INVALIDATES the editor's caches
|
||||||
|
// (cheap); the fat re-load happens lazily when the user looks at the
|
||||||
|
// lib, or explicitly from "Update from library" (kicadLibsReload).
|
||||||
|
// Older builds without the invalidate export keep the eager reload.
|
||||||
|
const invalidate = mod?.kicadLibsInvalidate;
|
||||||
const reload = mod?.kicadLibsReload;
|
const reload = mod?.kicadLibsReload;
|
||||||
if (typeof reload !== "function") return;
|
const fn = typeof invalidate === "function" ? invalidate : reload;
|
||||||
log(`[synced] remote change → reload ${kind} lib "${info.name}"`);
|
if (typeof fn !== "function") return;
|
||||||
|
log(
|
||||||
|
`[synced] remote change → ${fn === invalidate ? "invalidate" : "reload"} ${kind} lib "${info.name}"`,
|
||||||
|
);
|
||||||
try {
|
try {
|
||||||
(reload as (kind: string, nickname: string) => void)(kind, info.name);
|
(fn as (kind: string, nickname: string) => void)(kind, info.name);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log(`[synced] editor reload failed: ${String(e)}`);
|
log(`[synced] editor cache invalidation failed: ${String(e)}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
emitItemUpdated(info, kind, names, mod);
|
emitItemUpdated(info, kind, names, mod);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue