libs 0017: sync overrides indexed + stale-lib session menu + Cmd+S DOM-focus fix + WasmTool split
- kicadLibsFootprintUsage + kicadUpdateFromLibrary embinds (result via pcbjam:lib-update-done — runOnCoroutine is deferred) - standalone: stale-lib FAB triangle + session-menu Update-from-library row, save busy notice names the item, footprint placed-usage in the toast - WasmTool.tsx split: module helpers → components/wasm-tool/ - specs: save-cmd-key (Meta+S, mac UA), fpedit-cmd-save (DOM-focus repro) - wxwidgets → cdd5a5c (wxDomBlurActive) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012Wd1r3ewftpV1DBSEArpRa
This commit is contained in:
parent
9a19b96b6c
commit
866db5888c
18 changed files with 2084 additions and 1092 deletions
|
|
@ -1272,12 +1272,137 @@ std::string schCollabTestMoveFirst( int aDx, int aDy )
|
|||
}
|
||||
|
||||
|
||||
// Outcome of a deferred update-from-library run → window event
|
||||
// `pcbjam:lib-update-done` {ok, updated, missing[]} (libs 0017 §2c).
|
||||
static void emitLibUpdateDone( int aUpdated, const std::vector<std::string>& aMissing )
|
||||
{
|
||||
nlohmann::json j;
|
||||
j["ok"] = true;
|
||||
j["updated"] = aUpdated;
|
||||
j["missing"] = aMissing;
|
||||
std::string s = j.dump();
|
||||
EM_ASM( {
|
||||
if( typeof window !== 'undefined' )
|
||||
window.dispatchEvent( new CustomEvent( 'pcbjam:lib-update-done',
|
||||
{ detail: JSON.parse( UTF8ToString( $0 ) ) } ) );
|
||||
}, s.c_str() );
|
||||
}
|
||||
|
||||
// How many placed instances of a library symbol the open schematic holds —
|
||||
// the JS lib-sync bridge asks after a remote lib update so the editor chrome
|
||||
// can warn "a symbol you are using changed" (placed SCH_SYMBOLs keep their
|
||||
// embedded copy across a lib reload, so the user must update explicitly).
|
||||
// Counts across all unique screens of the hierarchy; 0 without a schematic
|
||||
// frame (symbol editor / viewer sessions).
|
||||
// Re-read the named library symbols into every placed instance — the headless
|
||||
// core of Tools ▸ Update Symbols from Library… (DIALOG_CHANGE_SYMBOLS::
|
||||
// processSymbols with the dialog's defaults: keep field text/positions, take
|
||||
// the new body/pins/attributes), scoped to `aNamesJson` of `aLibNickname`.
|
||||
// Runs as a normal SCH_COMMIT on the frame's coroutine so peers receive it as
|
||||
// an ordinary edit. Returns {ok, updated, missing[]}. (libs 0017 §2c)
|
||||
std::string schUpdateFromLibrary( std::string aLibNickname, std::string aNamesJson )
|
||||
{
|
||||
nlohmann::json out;
|
||||
SCH_EDIT_FRAME* fr = schFrame();
|
||||
|
||||
if( !fr )
|
||||
{
|
||||
out["ok"] = false;
|
||||
out["error"] = "no schematic frame";
|
||||
return out.dump();
|
||||
}
|
||||
|
||||
std::set<wxString> names;
|
||||
|
||||
try
|
||||
{
|
||||
for( const auto& n : nlohmann::json::parse( aNamesJson ) )
|
||||
names.insert( wxString::FromUTF8( n.get<std::string>().c_str() ) );
|
||||
}
|
||||
catch( ... )
|
||||
{
|
||||
out["ok"] = false;
|
||||
out["error"] = "bad names";
|
||||
return out.dump();
|
||||
}
|
||||
|
||||
const wxString lib = wxString::FromUTF8( aLibNickname.c_str() );
|
||||
pcbjam_collab::runOnCoroutine( fr, [fr, lib, names]()
|
||||
{
|
||||
int updated = 0;
|
||||
std::vector<std::string> missing;
|
||||
SCH_COMMIT commit( fr );
|
||||
SCH_SCREENS screens( fr->Schematic().Root() );
|
||||
|
||||
for( SCH_SCREEN* screen = screens.GetFirst(); screen; screen = screens.GetNext() )
|
||||
{
|
||||
std::vector<SCH_SYMBOL*> targets;
|
||||
|
||||
for( SCH_ITEM* item : screen->Items().OfType( SCH_SYMBOL_T ) )
|
||||
{
|
||||
SCH_SYMBOL* sym = static_cast<SCH_SYMBOL*>( item );
|
||||
const LIB_ID& id = sym->GetLibId();
|
||||
|
||||
if( wxString( id.GetLibNickname() ) == lib && names.count( wxString( id.GetLibItemName() ) ) )
|
||||
targets.push_back( sym );
|
||||
}
|
||||
|
||||
for( SCH_SYMBOL* sym : targets )
|
||||
{
|
||||
LIB_SYMBOL* libSymbol = fr->GetLibSymbol( sym->GetLibId() );
|
||||
|
||||
if( !libSymbol )
|
||||
{
|
||||
missing.push_back( std::string( sym->GetLibId().Format().c_str() ) );
|
||||
continue;
|
||||
}
|
||||
|
||||
std::unique_ptr<LIB_SYMBOL> flattened = libSymbol->Flatten();
|
||||
|
||||
if( flattened->GetUnitCount() < sym->GetUnit() )
|
||||
{
|
||||
missing.push_back( std::string( sym->GetLibId().Format().c_str() ) );
|
||||
continue;
|
||||
}
|
||||
|
||||
// Same order as the dialog: remove, record, swap the lib symbol,
|
||||
// re-append (the screen's RTree + connectivity re-index).
|
||||
screen->Remove( sym );
|
||||
commit.Modified( sym, static_cast<SCH_SYMBOL*>( sym->Clone() ), screen );
|
||||
sym->SetLibSymbol( flattened.release() );
|
||||
sym->SetExcludedFromSim( sym->GetLibSymbolRef()->GetExcludedFromSim() );
|
||||
sym->SetExcludedFromBOM( sym->GetLibSymbolRef()->GetExcludedFromBOM() );
|
||||
sym->SetExcludedFromBoard( sym->GetLibSymbolRef()->GetExcludedFromBoard() );
|
||||
sym->SetShowPinNames( sym->GetLibSymbolRef()->GetShowPinNames() );
|
||||
sym->SetShowPinNumbers( sym->GetLibSymbolRef()->GetShowPinNumbers() );
|
||||
sym->SetSchSymbolLibraryName( wxEmptyString );
|
||||
screen->Append( sym );
|
||||
updated++;
|
||||
}
|
||||
}
|
||||
|
||||
commit.Push( wxT( "Update symbols from library" ) );
|
||||
fr->GetCanvas()->Refresh();
|
||||
emitLibUpdateDone( updated, missing );
|
||||
} );
|
||||
|
||||
// The body runs deferred on the frame's coroutine (runOnCoroutine =
|
||||
// CallAfter): the caller awaits the `pcbjam:lib-update-done` window event
|
||||
// for the outcome.
|
||||
out["ok"] = true;
|
||||
out["queued"] = true;
|
||||
return out.dump();
|
||||
}
|
||||
|
||||
// Standalone-eeschema shape of kicadUpdateFromLibrary(kind, lib, namesJson).
|
||||
static std::string schUpdateFromLibraryShim( std::string aKind, std::string aLib, std::string aNames )
|
||||
{
|
||||
if( aKind != "symbol" )
|
||||
return "{\"ok\":false,\"error\":\"kind not handled by this editor\"}";
|
||||
|
||||
return schUpdateFromLibrary( aLib, aNames );
|
||||
}
|
||||
|
||||
int schLibsSymbolUsage( std::string aLibNickname, std::string aSymbolName )
|
||||
{
|
||||
SCH_EDIT_FRAME* fr = schFrame();
|
||||
|
|
@ -2110,6 +2235,8 @@ EMSCRIPTEN_BINDINGS(eeschema) {
|
|||
// Placed-instance count for a library symbol (drives the "a symbol you are
|
||||
// using was updated" toast after a remote lib edit).
|
||||
function("kicadLibsSymbolUsage", &schLibsSymbolUsage);
|
||||
// Update placed symbols from the library (libs 0017 §2c).
|
||||
function("kicadUpdateFromLibrary", &schUpdateFromLibraryShim);
|
||||
#endif // !KICAD_MERGED_EMBIND
|
||||
}
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -50,6 +50,10 @@ using namespace emscripten;
|
|||
// Per-editor entry points and frame probes — defined (with external linkage) in
|
||||
// pcbnew_embind.cpp / eeschema_embind.cpp.
|
||||
bool pcbEditorActive();
|
||||
// libs 0017 §2c/2d: placed-footprint usage + update-from-library.
|
||||
int pcbLibsFootprintUsage( std::string aLib, std::string aName );
|
||||
std::string pcbUpdateFromLibrary( std::string aLib, std::string aNamesJson );
|
||||
std::string schUpdateFromLibrary( std::string aLib, std::string aNamesJson );
|
||||
void pcbCollabApply( std::string aJson );
|
||||
void pcbCollabApplyItems( std::string aJson );
|
||||
std::string pcbCollabSnapshot();
|
||||
|
|
@ -409,6 +413,26 @@ static int libsSymbolUsage( std::string aLib, std::string aName )
|
|||
return schEditorActive() ? schLibsSymbolUsage( aLib, aName ) : 0;
|
||||
}
|
||||
|
||||
// Placed-instance count for a library footprint — board frame only (libs 0017 §2d).
|
||||
static int libsFootprintUsage( std::string aLib, std::string aName )
|
||||
{
|
||||
return pcbEditorActive() ? pcbLibsFootprintUsage( aLib, aName ) : 0;
|
||||
}
|
||||
|
||||
// Update placed instances of the named lib items from the library (libs 0017
|
||||
// §2c): `aKind` picks the editor — "footprint" needs the board frame,
|
||||
// "symbol" the schematic frame; a mismatch answers {ok:false}.
|
||||
static std::string updateFromLibrary( std::string aKind, std::string aLib, std::string aNamesJson )
|
||||
{
|
||||
if( aKind == "footprint" && pcbEditorActive() )
|
||||
return pcbUpdateFromLibrary( aLib, aNamesJson );
|
||||
|
||||
if( aKind == "symbol" && schEditorActive() )
|
||||
return schUpdateFromLibrary( aLib, aNamesJson );
|
||||
|
||||
return "{\"ok\":false,\"error\":\"no editor for this kind\"}";
|
||||
}
|
||||
|
||||
// Presence shims (collab-presence 0002 pcbnew / 0003 eeschema): route to the live
|
||||
// editor's implementation, same pattern as the collab bridge shims above.
|
||||
static void collabPresenceStart()
|
||||
|
|
@ -603,6 +627,9 @@ EMSCRIPTEN_BINDINGS(kicad_editor) {
|
|||
// 0 from any other frame; drives the "symbol you are using was updated"
|
||||
// toast after a remote lib edit).
|
||||
function("kicadLibsSymbolUsage", &libsSymbolUsage);
|
||||
// Placed-footprint usage + update-from-library (libs 0017 §2c/2d).
|
||||
function("kicadLibsFootprintUsage", &libsFootprintUsage);
|
||||
function("kicadUpdateFromLibrary", &updateFromLibrary);
|
||||
}
|
||||
|
||||
#endif // __EMSCRIPTEN__
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@
|
|||
#include <zone.h>
|
||||
#include <eda_text.h>
|
||||
#include <pcb_edit_frame.h>
|
||||
#include <lib_id.h>
|
||||
#include <kicad_clipboard.h>
|
||||
#include <io/kicad/kicad_io_utils.h>
|
||||
#include <richio.h>
|
||||
|
|
@ -2480,6 +2481,140 @@ static bool kicadCollabBusyProbe()
|
|||
return pcbjam_collab::applyBusy() || !pcbjam_collab::applyQueue().empty();
|
||||
}
|
||||
|
||||
|
||||
// ── libs 0017 §2d/§2c: placed-footprint usage + update-from-library ──────────
|
||||
|
||||
// Placed-instance count for a library footprint (mirror of schLibsSymbolUsage):
|
||||
// drives the "a footprint you placed was updated" notice after a remote lib
|
||||
// edit. 0 without a board frame.
|
||||
int pcbLibsFootprintUsage( std::string aLibNickname, std::string aFootprintName )
|
||||
{
|
||||
PCB_EDIT_FRAME* fr = pcbFrame();
|
||||
|
||||
if( !fr || !fr->GetBoard() )
|
||||
return 0;
|
||||
|
||||
const LIB_ID target( wxString::FromUTF8( aLibNickname.c_str() ),
|
||||
wxString::FromUTF8( aFootprintName.c_str() ) );
|
||||
int count = 0;
|
||||
|
||||
for( FOOTPRINT* fp : fr->GetBoard()->Footprints() )
|
||||
{
|
||||
if( fp->GetFPID() == target )
|
||||
count++;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
// Outcome of a deferred update-from-library run → window event
|
||||
// `pcbjam:lib-update-done` {ok, updated, missing[]} (libs 0017 §2c).
|
||||
static void emitLibUpdateDone( int aUpdated, const std::vector<std::string>& aMissing )
|
||||
{
|
||||
nlohmann::json j;
|
||||
j["ok"] = true;
|
||||
j["updated"] = aUpdated;
|
||||
j["missing"] = aMissing;
|
||||
std::string s = j.dump();
|
||||
EM_ASM( {
|
||||
if( typeof window !== 'undefined' )
|
||||
window.dispatchEvent( new CustomEvent( 'pcbjam:lib-update-done',
|
||||
{ detail: JSON.parse( UTF8ToString( $0 ) ) } ) );
|
||||
}, s.c_str() );
|
||||
}
|
||||
|
||||
// Re-read the named library footprints into every placed instance — the
|
||||
// headless core of Tools ▸ Update Footprints from Library… (DIALOG_EXCHANGE_
|
||||
// FOOTPRINTS::processFootprint with the dialog's defaults), scoped to
|
||||
// `aNamesJson` (a JSON array of footprint names) of `aLibNickname`. Runs as a
|
||||
// normal BOARD_COMMIT on the frame's coroutine so peers receive it as an
|
||||
// ordinary edit. Returns {ok, updated, missing[]}.
|
||||
std::string pcbUpdateFromLibrary( std::string aLibNickname, std::string aNamesJson )
|
||||
{
|
||||
nlohmann::json out;
|
||||
PCB_EDIT_FRAME* fr = pcbFrame();
|
||||
|
||||
if( !fr || !fr->GetBoard() )
|
||||
{
|
||||
out["ok"] = false;
|
||||
out["error"] = "no board frame";
|
||||
return out.dump();
|
||||
}
|
||||
|
||||
std::set<wxString> names;
|
||||
|
||||
try
|
||||
{
|
||||
for( const auto& n : nlohmann::json::parse( aNamesJson ) )
|
||||
names.insert( wxString::FromUTF8( n.get<std::string>().c_str() ) );
|
||||
}
|
||||
catch( ... )
|
||||
{
|
||||
out["ok"] = false;
|
||||
out["error"] = "bad names";
|
||||
return out.dump();
|
||||
}
|
||||
|
||||
const wxString lib = wxString::FromUTF8( aLibNickname.c_str() );
|
||||
pcbjam_collab::runOnCoroutine( fr, [fr, lib, names]()
|
||||
{
|
||||
int updated = 0;
|
||||
std::vector<std::string> missing;
|
||||
BOARD_COMMIT commit( fr );
|
||||
// Reverse: ExchangeFootprint appends the replacement at the end of the list.
|
||||
std::vector<FOOTPRINT*> targets;
|
||||
|
||||
for( FOOTPRINT* fp : fr->GetBoard()->Footprints() )
|
||||
{
|
||||
const LIB_ID& id = fp->GetFPID();
|
||||
|
||||
if( wxString( id.GetLibNickname() ) == lib && names.count( wxString( id.GetLibItemName() ) ) )
|
||||
targets.push_back( fp );
|
||||
}
|
||||
|
||||
for( auto it = targets.rbegin(); it != targets.rend(); ++it )
|
||||
{
|
||||
FOOTPRINT* fp = *it;
|
||||
FOOTPRINT* fresh = fr->LoadFootprint( fp->GetFPID() );
|
||||
|
||||
if( !fresh )
|
||||
{
|
||||
missing.push_back( std::string( fp->GetFPID().Format().c_str() ) );
|
||||
continue;
|
||||
}
|
||||
|
||||
bool changed = fp->FootprintNeedsUpdate( fresh );
|
||||
fr->ExchangeFootprint( fp, fresh, commit, /*deleteExtraTexts*/ true,
|
||||
/*resetTextLayers*/ true, /*resetTextEffects*/ true,
|
||||
/*resetTextPositions*/ true, /*resetTextContent*/ true,
|
||||
/*resetFabricationAttrs*/ true,
|
||||
/*resetClearanceOverrides*/ true, /*reset3DModels*/ true,
|
||||
&changed );
|
||||
updated++;
|
||||
}
|
||||
|
||||
commit.Push( wxT( "Update footprints from library" ) );
|
||||
fr->GetCanvas()->Refresh();
|
||||
emitLibUpdateDone( updated, missing );
|
||||
} );
|
||||
|
||||
// The body runs deferred on the frame's coroutine (runOnCoroutine =
|
||||
// CallAfter): the caller awaits the `pcbjam:lib-update-done` window event
|
||||
// for the outcome.
|
||||
out["ok"] = true;
|
||||
out["queued"] = true;
|
||||
return out.dump();
|
||||
}
|
||||
|
||||
// Standalone-pcbnew shape of kicadUpdateFromLibrary(kind, lib, namesJson).
|
||||
static std::string pcbUpdateFromLibraryShim( std::string aKind, std::string aLib, std::string aNames )
|
||||
{
|
||||
if( aKind != "footprint" )
|
||||
return "{\"ok\":false,\"error\":\"kind not handled by this editor\"}";
|
||||
|
||||
return pcbUpdateFromLibrary( aLib, aNames );
|
||||
}
|
||||
|
||||
EMSCRIPTEN_BINDINGS(pcbnew) {
|
||||
// Register vector types for iteration
|
||||
register_vector<FOOTPRINT*>("FootprintVector");
|
||||
|
|
@ -2497,6 +2632,11 @@ EMSCRIPTEN_BINDINGS(pcbnew) {
|
|||
|
||||
// Programmatic save of the in-memory board (round-trip tests, README §A).
|
||||
function("kicadSaveBoard", &kicadSaveBoard);
|
||||
#ifndef KICAD_MERGED_EMBIND
|
||||
// Placed-footprint usage + update-from-library (libs 0017 §2c/2d).
|
||||
function("kicadLibsFootprintUsage", &pcbLibsFootprintUsage);
|
||||
function("kicadUpdateFromLibrary", &pcbUpdateFromLibraryShim);
|
||||
#endif
|
||||
// Layer bridge (viewer-panels) — pcbnew-only names, merged-image safe
|
||||
// (null-frame no-op when eeschema is the live frame).
|
||||
function("kicadLayersGetState", &pcbLayersGetState);
|
||||
|
|
|
|||
Loading…
Reference in a new issue