Sch/PCB: allow back-updating schematic fields from PCB

Fixes: https://gitlab.com/kicad/code/kicad/-/issues/15285
This commit is contained in:
Mike Williams 2023-07-31 10:38:02 -04:00
parent b95b07e54d
commit 744452d092
12 changed files with 260 additions and 8 deletions

View File

@ -31,7 +31,7 @@
// Saved dialog settings
DIALOG_UPDATE_FROM_PCB::DIALOG_UPDATE_FROM_PCB_SAVED_STATE
DIALOG_UPDATE_FROM_PCB::s_savedDialogState{ false, true, true, true, false, true };
DIALOG_UPDATE_FROM_PCB::s_savedDialogState{ false, true, true, true, false, true, true };
DIALOG_UPDATE_FROM_PCB::DIALOG_UPDATE_FROM_PCB( SCH_EDIT_FRAME* aParent )
@ -62,6 +62,7 @@ DIALOG_UPDATE_FROM_PCB::DIALOG_UPDATE_FROM_PCB( SCH_EDIT_FRAME* aParent )
m_cbUpdateValues->SetValue( s_savedDialogState.UpdateValues );
m_cbUpdateNetNames->SetValue( s_savedDialogState.UpdateNetNames );
m_cbUpdateAttributes->SetValue( s_savedDialogState.UpdateAttributes );
m_cbUpdateOtherFields->SetValue( s_savedDialogState.UpdateOtherFields );
SetupStandardButtons( { { wxID_OK, _( "Update Schematic" ) },
{ wxID_CANCEL, _( "Close" ) } } );
@ -82,6 +83,7 @@ void DIALOG_UPDATE_FROM_PCB::updateData()
m_cbUpdateReferences->GetValue(),
m_cbUpdateNetNames->GetValue(),
m_cbUpdateAttributes->GetValue(),
m_cbUpdateOtherFields->GetValue(),
true );
std::string netlist;
@ -135,6 +137,8 @@ void DIALOG_UPDATE_FROM_PCB::OnOptionChanged( wxCommandEvent& event )
s_savedDialogState.UpdateNetNames = m_cbUpdateNetNames->GetValue();
else if( event.GetEventObject() == m_cbUpdateAttributes )
s_savedDialogState.UpdateAttributes = m_cbUpdateAttributes->GetValue();
else if( event.GetEventObject() == m_cbUpdateOtherFields )
s_savedDialogState.UpdateOtherFields = m_cbUpdateOtherFields->GetValue();
}
@ -150,6 +154,7 @@ void DIALOG_UPDATE_FROM_PCB::OnUpdateClick( wxCommandEvent& event )
m_cbUpdateReferences->GetValue(),
m_cbUpdateNetNames->GetValue(),
m_cbUpdateAttributes->GetValue(),
m_cbUpdateOtherFields->GetValue(),
false );
if( backAnno.FetchNetlistFromPCB( netlist ) && backAnno.BackAnnotateSymbols( netlist ) )

View File

@ -56,6 +56,7 @@ private:
bool UpdateValues;
bool UpdateNetNames;
bool UpdateAttributes;
bool UpdateOtherFields;
};
static DIALOG_UPDATE_FROM_PCB_SAVED_STATE s_savedDialogState;

View File

@ -72,6 +72,11 @@ DIALOG_UPDATE_FROM_PCB_BASE::DIALOG_UPDATE_FROM_PCB_BASE( wxWindow* parent, wxWi
m_cbUpdateAttributes = new wxCheckBox( sbSizer2->GetStaticBox(), wxID_ANY, _("Attributes"), wxDefaultPosition, wxDefaultSize, 0 );
fgSizer2->Add( m_cbUpdateAttributes, 0, wxBOTTOM|wxLEFT|wxRIGHT, 5 );
m_cbUpdateOtherFields = new wxCheckBox( sbSizer2->GetStaticBox(), wxID_ANY, _("Other fields"), wxDefaultPosition, wxDefaultSize, 0 );
m_cbUpdateOtherFields->SetToolTip( _("Update all other fields in the symbol from the footprint") );
fgSizer2->Add( m_cbUpdateOtherFields, 0, wxBOTTOM|wxLEFT|wxRIGHT, 5 );
sbSizer2->Add( fgSizer2, 1, wxEXPAND, 5 );
@ -112,6 +117,7 @@ DIALOG_UPDATE_FROM_PCB_BASE::DIALOG_UPDATE_FROM_PCB_BASE( wxWindow* parent, wxWi
m_cbUpdateValues->Connect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( DIALOG_UPDATE_FROM_PCB_BASE::OnOptionChanged ), NULL, this );
m_cbUpdateNetNames->Connect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( DIALOG_UPDATE_FROM_PCB_BASE::OnOptionChanged ), NULL, this );
m_cbUpdateAttributes->Connect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( DIALOG_UPDATE_FROM_PCB_BASE::OnOptionChanged ), NULL, this );
m_cbUpdateOtherFields->Connect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( DIALOG_UPDATE_FROM_PCB_BASE::OnOptionChanged ), NULL, this );
m_sdbSizerOK->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( DIALOG_UPDATE_FROM_PCB_BASE::OnUpdateClick ), NULL, this );
}
@ -124,6 +130,7 @@ DIALOG_UPDATE_FROM_PCB_BASE::~DIALOG_UPDATE_FROM_PCB_BASE()
m_cbUpdateValues->Disconnect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( DIALOG_UPDATE_FROM_PCB_BASE::OnOptionChanged ), NULL, this );
m_cbUpdateNetNames->Disconnect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( DIALOG_UPDATE_FROM_PCB_BASE::OnOptionChanged ), NULL, this );
m_cbUpdateAttributes->Disconnect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( DIALOG_UPDATE_FROM_PCB_BASE::OnOptionChanged ), NULL, this );
m_cbUpdateOtherFields->Disconnect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( DIALOG_UPDATE_FROM_PCB_BASE::OnOptionChanged ), NULL, this );
m_sdbSizerOK->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( DIALOG_UPDATE_FROM_PCB_BASE::OnUpdateClick ), NULL, this );
}

View File

@ -520,6 +520,71 @@
<event name="OnCheckBox">OnOptionChanged</event>
</object>
</object>
<object class="sizeritem" expanded="1">
<property name="border">5</property>
<property name="flag">wxBOTTOM|wxLEFT|wxRIGHT</property>
<property name="proportion">0</property>
<object class="wxCheckBox" expanded="1">
<property name="BottomDockable">1</property>
<property name="LeftDockable">1</property>
<property name="RightDockable">1</property>
<property name="TopDockable">1</property>
<property name="aui_layer"></property>
<property name="aui_name"></property>
<property name="aui_position"></property>
<property name="aui_row"></property>
<property name="best_size"></property>
<property name="bg"></property>
<property name="caption"></property>
<property name="caption_visible">1</property>
<property name="center_pane">0</property>
<property name="checked">0</property>
<property name="close_button">1</property>
<property name="context_help"></property>
<property name="context_menu">1</property>
<property name="default_pane">0</property>
<property name="dock">Dock</property>
<property name="dock_fixed">0</property>
<property name="docking">Left</property>
<property name="enabled">1</property>
<property name="fg"></property>
<property name="floatable">1</property>
<property name="font"></property>
<property name="gripper">0</property>
<property name="hidden">0</property>
<property name="id">wxID_ANY</property>
<property name="label">Other fields</property>
<property name="max_size"></property>
<property name="maximize_button">0</property>
<property name="maximum_size"></property>
<property name="min_size"></property>
<property name="minimize_button">0</property>
<property name="minimum_size"></property>
<property name="moveable">1</property>
<property name="name">m_cbUpdateOtherFields</property>
<property name="pane_border">1</property>
<property name="pane_position"></property>
<property name="pane_size"></property>
<property name="permission">protected</property>
<property name="pin_button">1</property>
<property name="pos"></property>
<property name="resize">Resizable</property>
<property name="show">1</property>
<property name="size"></property>
<property name="style"></property>
<property name="subclass">; forward_declare</property>
<property name="toolbar_pane">0</property>
<property name="tooltip">Update all other fields in the symbol from the footprint</property>
<property name="validator_data_type"></property>
<property name="validator_style">wxFILTER_NONE</property>
<property name="validator_type">wxDefaultValidator</property>
<property name="validator_variable"></property>
<property name="window_extra_style"></property>
<property name="window_name"></property>
<property name="window_style"></property>
<event name="OnCheckBox">OnOptionChanged</event>
</object>
</object>
</object>
</object>
</object>

View File

@ -41,6 +41,7 @@ class DIALOG_UPDATE_FROM_PCB_BASE : public DIALOG_SHIM
wxCheckBox* m_cbUpdateValues;
wxCheckBox* m_cbUpdateNetNames;
wxCheckBox* m_cbUpdateAttributes;
wxCheckBox* m_cbUpdateOtherFields;
WX_HTML_REPORT_PANEL* m_messagePanel;
wxStdDialogButtonSizer* m_sdbSizer;
wxButton* m_sdbSizerOK;

View File

@ -94,8 +94,9 @@ void NETLIST_EXPORTER_XML::addSymbolFields( XNODE* aNode, SCH_SYMBOL* aSymbol,
SCH_SHEET_PATH* aSheet )
{
wxString value;
wxString datasheet;
wxString footprint;
wxString datasheet;
wxString description;
wxString candidate;
std::map<wxString, wxString> userFields;
@ -127,16 +128,20 @@ void NETLIST_EXPORTER_XML::addSymbolFields( XNODE* aNode, SCH_SYMBOL* aSymbol,
int unit = symbol2->GetUnitSelection( aSheet );
// The lowest unit number wins. User should only set fields in any one unit.
// Value
candidate = symbol2->GetValueFieldText( m_resolveTextVars, &sheetList[i], false );
if( !candidate.IsEmpty() && ( unit < minUnit || value.IsEmpty() ) )
value = candidate;
// Footprint
candidate = symbol2->GetFootprintFieldText( m_resolveTextVars, &sheetList[i], false );
if( !candidate.IsEmpty() && ( unit < minUnit || footprint.IsEmpty() ) )
footprint = candidate;
// Datasheet
candidate = m_resolveTextVars
? symbol2->GetField( DATASHEET_FIELD )->GetShownText( &sheetList[i], false )
: symbol2->GetField( DATASHEET_FIELD )->GetText();
@ -144,6 +149,15 @@ void NETLIST_EXPORTER_XML::addSymbolFields( XNODE* aNode, SCH_SYMBOL* aSymbol,
if( !candidate.IsEmpty() && ( unit < minUnit || datasheet.IsEmpty() ) )
datasheet = candidate;
// Description
candidate = m_resolveTextVars
? symbol2->GetField( DESCRIPTION_FIELD )->GetShownText( &sheetList[i], false )
: symbol2->GetField( DESCRIPTION_FIELD )->GetText();
if( !candidate.IsEmpty() && ( unit < minUnit || description.IsEmpty() ) )
description = candidate;
// All non-mandatory fields
for( int ii = MANDATORY_FIELDS; ii < symbol2->GetFieldCount(); ++ii )
{
const SCH_FIELD& f = symbol2->GetFields()[ ii ];
@ -167,11 +181,18 @@ void NETLIST_EXPORTER_XML::addSymbolFields( XNODE* aNode, SCH_SYMBOL* aSymbol,
value = aSymbol->GetValueFieldText( m_resolveTextVars, aSheet, false );
footprint = aSymbol->GetFootprintFieldText( m_resolveTextVars, aSheet, false );
// Datasheet
if( m_resolveTextVars )
datasheet = aSymbol->GetField( DATASHEET_FIELD )->GetShownText( aSheet, false );
else
datasheet = aSymbol->GetField( DATASHEET_FIELD )->GetText();
// Description
if( m_resolveTextVars )
description = aSymbol->GetField( DESCRIPTION_FIELD )->GetShownText( aSheet, false );
else
description = aSymbol->GetField( DESCRIPTION_FIELD )->GetText();
for( int ii = MANDATORY_FIELDS; ii < aSymbol->GetFieldCount(); ++ii )
{
const SCH_FIELD& f = aSymbol->GetFields()[ ii ];
@ -198,11 +219,26 @@ void NETLIST_EXPORTER_XML::addSymbolFields( XNODE* aNode, SCH_SYMBOL* aSymbol,
if( datasheet.size() )
aNode->AddChild( node( wxT( "datasheet" ), UnescapeString( datasheet ) ) );
if( datasheet.size() )
aNode->AddChild( node( wxT( "description" ), UnescapeString( description ) ) );
if( userFields.size() )
{
XNODE* xfields;
aNode->AddChild( xfields = node( wxT( "fields" ) ) );
XNODE* datasheetField = node( wxT( "field" ), UnescapeString( datasheet ) );
datasheetField->AddAttribute(
wxT( "name" ),
UnescapeString( TEMPLATE_FIELDNAME::GetDefaultFieldName( DATASHEET_FIELD ) ) );
xfields->AddChild( datasheetField );
XNODE* descriptionField = node( wxT( "field" ), UnescapeString( description ) );
descriptionField->AddAttribute(
wxT( "name" ),
UnescapeString( TEMPLATE_FIELDNAME::GetDefaultFieldName( DESCRIPTION_FIELD ) ) );
xfields->AddChild( descriptionField );
// non MANDATORY fields are output alphabetically
for( const std::pair<const wxString, wxString>& f : userFields )
{

View File

@ -44,7 +44,8 @@
BACK_ANNOTATE::BACK_ANNOTATE( SCH_EDIT_FRAME* aFrame, REPORTER& aReporter, bool aRelinkFootprints,
bool aProcessFootprints, bool aProcessValues,
bool aProcessReferences, bool aProcessNetNames,
bool aProcessAttributes, bool aDryRun ) :
bool aProcessAttributes, bool aProcessOtherFields,
bool aDryRun ) :
m_reporter( aReporter ),
m_matchByReference( aRelinkFootprints ),
m_processFootprints( aProcessFootprints ),
@ -52,6 +53,7 @@ BACK_ANNOTATE::BACK_ANNOTATE( SCH_EDIT_FRAME* aFrame, REPORTER& aReporter, bool
m_processReferences( aProcessReferences ),
m_processNetNames( aProcessNetNames ),
m_processAttributes( aProcessAttributes ),
m_processOtherFields( aProcessOtherFields ),
m_dryRun( aDryRun ),
m_frame( aFrame ),
m_changesCount( 0 )
@ -150,7 +152,7 @@ void BACK_ANNOTATE::getPcbModulesFromString( const std::string& aPayload )
{
wxString path, value, footprint;
bool dnp = false, exBOM = false;
std::map<wxString, wxString> pinNetMap;
std::map<wxString, wxString> pinNetMap, fieldsMap;
wxASSERT( item.first == "ref" );
wxString ref = getStr( item.second );
@ -171,6 +173,29 @@ void BACK_ANNOTATE::getPcbModulesFromString( const std::string& aPayload )
footprint = getStr( item.second.get_child( "fpid" ) );
value = getStr( item.second.get_child( "value" ) );
// Get child PTREE of fields
boost::optional<const PTREE&> fields = item.second.get_child_optional( "fields" );
// Parse each field out of the fields string
if( fields )
{
for( const std::pair<const std::string, PTREE>& field : fields.get() )
{
if( field.first != "field" )
continue;
// Fields are of the format "(field (name "name") "12345")
auto fieldName = field.second.get_child_optional( "name" );
auto fieldValue = field.second.back().first;
if( !fieldName )
continue;
fieldsMap[getStr( fieldName.get() )] = fieldValue;
}
}
// Get DNP and Exclude from BOM out of the properties if they exist
for( const auto& child : item.second )
{
@ -226,7 +251,7 @@ void BACK_ANNOTATE::getPcbModulesFromString( const std::string& aPayload )
{
// Add footprint to the map
auto data = std::make_shared<PCB_FP_DATA>( ref, footprint, value, dnp, exBOM,
pinNetMap );
pinNetMap, fieldsMap );
m_pcbFootprints.insert( nearestItem, std::make_pair( path, data ) );
}
}
@ -465,6 +490,81 @@ void BACK_ANNOTATE::applyChangelist()
}
}
if( m_processOtherFields )
{
// Need to handle three cases: existing field, new field, deleted field
for( const std::pair<const wxString, wxString>& field : fpData.m_fieldsMap )
{
const wxString& fpFieldName = field.first;
const wxString& fpFieldValue = field.second;
SCH_FIELD* symField = symbol->FindField( fpFieldName );
// 1. Existing fields has changed value
// PCB Field value is checked against the shown text because this is the value
// with all the variables resolved. The footprints field value gets the symbol's
// resolved value when the PCB is updated from the schematic.
if( symField
&& symField->GetShownText( &ref.GetSheetPath(), false ) != fpFieldValue )
{
m_changesCount++;
msg.Printf( _( "Change field '%s' value to '%s'." ),
symField->GetCanonicalName(), fpFieldValue );
if( !m_dryRun )
{
commit.Modify( symbol, screen );
symField->SetText( fpFieldValue );
}
m_reporter.ReportHead( msg, RPT_SEVERITY_ACTION );
}
// 2. New field has been added to footprint and needs to be added to symbol
if( symField == nullptr )
{
m_changesCount++;
msg.Printf( _( "Add field '%s' with value '%s'." ), fpFieldName, fpFieldValue );
if( !m_dryRun )
{
commit.Modify( symbol, screen );
SCH_FIELD newField( VECTOR2I( 0, 0 ), symbol->GetFieldCount(), symbol,
fpFieldName );
newField.SetText( fpFieldValue );
symbol->AddField( newField );
}
m_reporter.ReportHead( msg, RPT_SEVERITY_ACTION );
}
}
// 3. Existing field has been deleted from footprint and needs to be deleted from symbol
// Check all symbol fields for existence in the footprint field map
for( SCH_FIELD& field : symbol->GetFields() )
{
// Never delete mandatory fields
if( field.GetId() < MANDATORY_FIELDS )
continue;
if( fpData.m_fieldsMap.find( field.GetCanonicalName() )
== fpData.m_fieldsMap.end() )
{
// Field not found in footprint field map, delete it
m_changesCount++;
msg.Printf( _( "Delete field '%s.'" ), field.GetCanonicalName() );
if( !m_dryRun )
{
commit.Modify( symbol, screen );
symbol->RemoveField( &field );
}
m_reporter.ReportHead( msg, RPT_SEVERITY_ACTION );
}
}
}
// TODO: back-annotate netclass changes?
}

View File

@ -62,13 +62,15 @@ public:
{
PCB_FP_DATA( const wxString& aRef, const wxString& aFootprint, const wxString& aValue,
bool aDNP, bool aExcludeFromBOM,
const std::map<wxString, wxString>& aPinMap ) :
const std::map<wxString, wxString>& aPinMap,
const std::map<wxString, wxString>& aFieldsMap ) :
m_ref( aRef ),
m_footprint( aFootprint ),
m_value( aValue ),
m_DNP( aDNP ),
m_excludeFromBOM( aExcludeFromBOM ),
m_pinMap( aPinMap )
m_pinMap( aPinMap ),
m_fieldsMap( aFieldsMap )
{}
wxString m_ref;
@ -77,6 +79,7 @@ public:
bool m_DNP;
bool m_excludeFromBOM;
std::map<wxString, wxString> m_pinMap;
std::map<wxString, wxString> m_fieldsMap;
};
///< Map to hold NETLIST footprints data
@ -86,7 +89,8 @@ public:
BACK_ANNOTATE( SCH_EDIT_FRAME* aFrame, REPORTER& aReporter, bool aRelinkFootprints,
bool aProcessFootprints, bool aProcessValues, bool aProcessReferences,
bool aProcessNetNames, bool aProcessAttributes, bool aDryRun );
bool aProcessNetNames, bool aProcessAttributes, bool aProcessOtherFields,
bool aDryRun );
~BACK_ANNOTATE();
/**
@ -143,6 +147,7 @@ private:
bool m_processReferences;
bool m_processNetNames;
bool m_processAttributes;
bool m_processOtherFields;
bool m_dryRun;
PCB_FOOTPRINTS_MAP m_pcbFootprints;

View File

@ -563,6 +563,12 @@ void PCB_EDIT_FRAME::KiwayMailIn( KIWAY_EXPRESS& mail )
}
}
std::map<wxString, wxString> fields;
for( PCB_FIELD* field : footprint->Fields() )
fields[field->GetCanonicalName()] = field->GetText();
component->SetFields( fields );
// Add DNP and Exclude from BOM properties
std::map<wxString, wxString> properties;

View File

@ -360,6 +360,7 @@ bool BOARD_NETLIST_UPDATER::updateFootprintParameters( FOOTPRINT* aPcbFootprint,
changed = true;
// Add or change field value
for( auto pair : aNetlistComponent->GetFields() )
{
if( aPcbFootprint->HasFieldByName( pair.first ) )
@ -386,6 +387,16 @@ bool BOARD_NETLIST_UPDATER::updateFootprintParameters( FOOTPRINT* aPcbFootprint,
}
}
}
// Remove fields that aren't present in the symbol
for( PCB_FIELD* field : aPcbFootprint->GetFields() )
{
if( field->IsMandatoryField() )
continue;
if( aNetlistComponent->GetFields().count( field->GetName() ) == 0 )
aPcbFootprint->RemoveField( field->GetCanonicalName() );
}
}
m_reporter->Report( msg, RPT_SEVERITY_ACTION );

View File

@ -93,6 +93,15 @@ void COMPONENT::Format( OUTPUTFORMATTER* aOut, int aNestLevel, int aCtl )
aOut->Print( nl+1, "(timestamp %s)\n", aOut->Quotew( path ).c_str() );
// Add all fields as a (field) under a (fields) node
aOut->Print( nl + 1, "(fields" );
for( std::pair<wxString, wxString> field : m_fields )
aOut->Print( nl + 2, "\n(field (name %s) %s)", aOut->Quotew( field.first ).c_str(),
aOut->Quotew( field.second ).c_str() );
aOut->Print( 0, ")\n" );
// Add DNP and Exclude from BOM properties if we have them
if( m_properties.count( "dnp" ) )
aOut->Print( nl + 1, "(property (name \"dnp\"))\n" );

View File

@ -473,6 +473,12 @@ int BOARD_EDITOR_CONTROL::ExportNetlist( const TOOL_EVENT& aEvent )
}
}
std::map<wxString, wxString> fields;
for( PCB_FIELD* field : footprint->Fields() )
fields[field->GetCanonicalName()] = field->GetText();
component->SetFields( fields );
netlist.AddComponent( component );
}