From 83c7e7fc65ced19cb93c5388a447447730d44fa1 Mon Sep 17 00:00:00 2001 From: Jon Evans Date: Mon, 11 Mar 2019 17:32:05 -0400 Subject: [PATCH] New connectivity algorithm and bus upgrades Bus upgrades: core new connectivity code Bus upgrades: eeschema integration and modifications Bus upgrades: eeschema dialogs Bus upgrades: netlist export Bus upgrades: file format changes --- CMakeLists.txt | 2 +- common/validators.cpp | 55 +- eeschema/CMakeLists.txt | 7 + eeschema/annotate.cpp | 12 +- eeschema/block.cpp | 2 +- eeschema/bus-wire-junction.cpp | 155 +- eeschema/bus_alias.cpp | 41 + eeschema/bus_alias.h | 100 + eeschema/connection_graph.cpp | 1644 +++++++++++++++++ eeschema/connection_graph.h | 327 ++++ eeschema/controle.cpp | 3 - eeschema/dialogs/dialog_bus_manager.cpp | 517 ++++++ eeschema/dialogs/dialog_bus_manager.h | 90 + eeschema/dialogs/dialog_edit_label.cpp | 11 +- eeschema/dialogs/dialog_edit_label_base.cpp | 4 +- eeschema/dialogs/dialog_edit_label_base.fbp | 11 +- eeschema/dialogs/dialog_edit_label_base.h | 5 +- eeschema/dialogs/dialog_erc.cpp | 103 +- eeschema/dialogs/dialog_erc.h | 9 +- eeschema/dialogs/dialog_erc_base.cpp | 20 +- eeschema/dialogs/dialog_erc_base.fbp | 367 ++++ eeschema/dialogs/dialog_erc_base.h | 6 +- eeschema/dialogs/dialog_migrate_buses.cpp | 229 +++ eeschema/dialogs/dialog_migrate_buses.h | 71 + .../dialogs/dialog_migrate_buses_base.cpp | 70 + .../dialogs/dialog_migrate_buses_base.fbp | 669 +++++++ eeschema/dialogs/dialog_migrate_buses_base.h | 57 + .../dialogs/dialog_sch_edit_sheet_pin.cpp | 1 + eeschema/dialogs/dialog_sch_sheet_props.cpp | 1 + eeschema/drc_erc_item.cpp | 17 +- eeschema/edit_component_in_schematic.cpp | 3 +- eeschema/eeschema_config.cpp | 21 +- eeschema/eeschema_id.h | 6 + eeschema/erc.cpp | 70 +- eeschema/erc.h | 49 +- eeschema/erc_settings.h | 82 + eeschema/files-io.cpp | 31 +- eeschema/find.cpp | 26 +- eeschema/general.h | 14 + eeschema/getpart.cpp | 5 +- eeschema/help_common_strings.h | 1 + eeschema/hierarch.cpp | 13 +- eeschema/highlight_connection.cpp | 76 +- eeschema/hotkeys.cpp | 5 + eeschema/hotkeys.h | 1 + eeschema/invoke_sch_dialog.h | 3 + eeschema/lib_draw_item.h | 1 + eeschema/lib_pin.cpp | 14 + eeschema/menubar.cpp | 6 + .../netlist_exporter_generic.cpp | 119 +- .../netlist_exporter_generic.h | 15 +- .../netlist_exporter_kicad.cpp | 131 ++ .../netlist_exporter_kicad.h | 6 +- eeschema/netlist_generator.cpp | 6 +- eeschema/netlist_object.cpp | 169 +- eeschema/netlist_object.h | 15 + eeschema/onleftclick.cpp | 2 +- eeschema/onrightclick.cpp | 58 +- eeschema/sch_base_frame.cpp | 54 +- eeschema/sch_base_frame.h | 9 +- eeschema/sch_bus_entry.cpp | 31 + eeschema/sch_bus_entry.h | 16 + eeschema/sch_component.cpp | 60 +- eeschema/sch_component.h | 33 +- eeschema/sch_connection.cpp | 469 +++++ eeschema/sch_connection.h | 347 ++++ eeschema/sch_edit_frame.cpp | 79 +- eeschema/sch_edit_frame.h | 94 +- eeschema/sch_item_struct.cpp | 44 + eeschema/sch_item_struct.h | 49 +- eeschema/sch_junction.cpp | 6 +- eeschema/sch_legacy_plugin.cpp | 47 + eeschema/sch_legacy_plugin.h | 4 + eeschema/sch_line.cpp | 35 + eeschema/sch_line.h | 2 + eeschema/sch_painter.cpp | 37 + eeschema/sch_pin_connection.cpp | 87 + eeschema/sch_pin_connection.h | 77 + eeschema/sch_screen.cpp | 77 +- eeschema/sch_screen.h | 39 + eeschema/sch_sheet.cpp | 3 +- eeschema/sch_sheet.h | 2 +- eeschema/sch_sheet_path.cpp | 16 + eeschema/sch_sheet_path.h | 13 +- eeschema/sch_text.cpp | 50 +- eeschema/sch_text.h | 3 + eeschema/schedit.cpp | 161 +- include/core/typeinfo.h | 1 + include/validators.h | 62 + 89 files changed, 7147 insertions(+), 414 deletions(-) create mode 100644 eeschema/bus_alias.cpp create mode 100644 eeschema/bus_alias.h create mode 100644 eeschema/connection_graph.cpp create mode 100644 eeschema/connection_graph.h create mode 100644 eeschema/dialogs/dialog_bus_manager.cpp create mode 100644 eeschema/dialogs/dialog_bus_manager.h create mode 100644 eeschema/dialogs/dialog_migrate_buses.cpp create mode 100644 eeschema/dialogs/dialog_migrate_buses.h create mode 100644 eeschema/dialogs/dialog_migrate_buses_base.cpp create mode 100644 eeschema/dialogs/dialog_migrate_buses_base.fbp create mode 100644 eeschema/dialogs/dialog_migrate_buses_base.h create mode 100644 eeschema/erc_settings.h create mode 100644 eeschema/sch_connection.cpp create mode 100644 eeschema/sch_connection.h create mode 100644 eeschema/sch_pin_connection.cpp create mode 100644 eeschema/sch_pin_connection.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 28b262d802..2e09e18902 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -585,7 +585,7 @@ find_package( Pixman 0.30 REQUIRED ) # # Find Boost headers, required. -find_package( Boost 1.54.0 REQUIRED ) +find_package( Boost 1.54.0 REQUIRED COMPONENTS regex ) # Include MinGW resource compiler. include( MinGWResourceCompiler ) diff --git a/common/validators.cpp b/common/validators.cpp index f0529bdd9e..ec7a3d98ba 100644 --- a/common/validators.cpp +++ b/common/validators.cpp @@ -3,6 +3,7 @@ * * Copyright (C) 2013 Wayne Stambaugh * Copyright (C) 2004-2013 KiCad Developers, see change_log.txt for contributors. + * Copyright (C) 2018 CERN * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -28,6 +29,7 @@ */ #include +#include #include #include @@ -220,6 +222,57 @@ void ENV_VAR_NAME_VALIDATOR::OnTextChanged( wxCommandEvent& event ) } +bool REGEX_VALIDATOR::Validate( wxWindow* aParent ) +{ + // If window is disabled, simply return + if( !m_validatorWindow->IsEnabled() ) + return true; + + wxTextEntry* const textEntry = GetTextEntry(); + + if( !textEntry ) + return false; + + bool valid = true; + const wxString& value = textEntry->GetValue(); + + if( m_regEx.Matches( value ) ) + { + size_t start, len; + m_regEx.GetMatch( &start, &len ); + + if( start != 0 || len != value.Length() ) // whole string must match + valid = false; + } + else // no match at all + { + valid = false; + } + + if( !valid ) + { + m_validatorWindow->SetFocus(); + DisplayError( aParent, wxString::Format( _( "Incorrect value: %s" ), value ) ); + return false; + } + + return true; +} + + +void REGEX_VALIDATOR::compileRegEx( const wxString& aRegEx, int aFlags ) +{ + if( !m_regEx.Compile( aRegEx, aFlags ) ) + { + throw std::runtime_error( "REGEX_VALIDATOR: Invalid regular expression: " + + aRegEx.ToStdString() ); + } + + m_regExString = aRegEx; + m_regExFlags = aFlags; +} + + void KIUI::ValidatorTransferToWindowWithoutEvents( wxValidator& aValidator ) { wxWindow* ctrl = aValidator.GetWindow(); @@ -228,4 +281,4 @@ void KIUI::ValidatorTransferToWindowWithoutEvents( wxValidator& aValidator ) wxEventBlocker orient_update_blocker( ctrl, wxEVT_ANY ); aValidator.TransferToWindow(); -} \ No newline at end of file +} diff --git a/eeschema/CMakeLists.txt b/eeschema/CMakeLists.txt index c521c8f926..39df4db97b 100644 --- a/eeschema/CMakeLists.txt +++ b/eeschema/CMakeLists.txt @@ -37,6 +37,7 @@ set( EESCHEMA_DLGS dialogs/dialog_bom.cpp dialogs/dialog_bom_base.cpp dialogs/dialog_bom_cfg_keywords.cpp + dialogs/dialog_bus_manager.cpp dialogs/dialog_fields_editor_global.cpp dialogs/dialog_fields_editor_global_base.cpp dialogs/dialog_choose_component.cpp @@ -64,6 +65,8 @@ set( EESCHEMA_DLGS dialogs/dialog_lib_edit_text_base.cpp dialogs/dialog_lib_new_component.cpp dialogs/dialog_lib_new_component_base.cpp + dialogs/dialog_migrate_buses.cpp + dialogs/dialog_migrate_buses_base.cpp dialogs/dialog_netlist.cpp dialogs/dialog_netlist_base.cpp dialogs/dialog_plot_schematic.cpp @@ -136,6 +139,7 @@ set( EESCHEMA_SRCS autoplace_fields.cpp backanno.cpp block.cpp + bus_alias.cpp bus-wire-junction.cpp busentry.cpp class_libentry.cpp @@ -144,6 +148,7 @@ set( EESCHEMA_SRCS cmp_library_lexer.cpp component_references_lister.cpp controle.cpp + connection_graph.cpp cross-probing.cpp drc_erc_item.cpp edit_bitmap.cpp @@ -195,6 +200,7 @@ set( EESCHEMA_SRCS sch_bus_entry.cpp sch_collectors.cpp sch_component.cpp + sch_connection.cpp sch_eagle_plugin.cpp sch_field.cpp sch_io_mgr.cpp @@ -204,6 +210,7 @@ set( EESCHEMA_SRCS sch_line.cpp sch_marker.cpp sch_no_connect.cpp + sch_pin_connection.cpp sch_plugin.cpp sch_preview_panel.cpp sch_screen.cpp diff --git a/eeschema/annotate.cpp b/eeschema/annotate.cpp index af92eabf3f..353f40f4ab 100644 --- a/eeschema/annotate.cpp +++ b/eeschema/annotate.cpp @@ -62,7 +62,7 @@ void SCH_EDIT_FRAME::DeleteAnnotation( bool aCurrentSheetOnly ) { SCH_SCREEN* screen = GetScreen(); wxCHECK_RET( screen != NULL, wxT( "Attempt to clear annotation of a NULL screen." ) ); - screen->ClearAnnotation( m_CurrentSheet ); + screen->ClearAnnotation( g_CurrentSheet ); } else { @@ -71,7 +71,7 @@ void SCH_EDIT_FRAME::DeleteAnnotation( bool aCurrentSheetOnly ) } // Update the references for the sheet that is currently being displayed. - m_CurrentSheet->UpdateAllScreenReferences(); + g_CurrentSheet->UpdateAllScreenReferences(); SyncView(); GetCanvas()->Refresh(); @@ -125,7 +125,7 @@ void SCH_EDIT_FRAME::AnnotateComponents( bool aAnnotateSchematic, } else { - m_CurrentSheet->GetMultiUnitComponents( lockedComponents ); + g_CurrentSheet->GetMultiUnitComponents( lockedComponents ); } } @@ -146,7 +146,7 @@ void SCH_EDIT_FRAME::AnnotateComponents( bool aAnnotateSchematic, } else { - m_CurrentSheet->GetComponents( references ); + g_CurrentSheet->GetComponents( references ); } // Break full components reference in name (prefix) and number: @@ -233,7 +233,7 @@ void SCH_EDIT_FRAME::AnnotateComponents( bool aAnnotateSchematic, aReporter.ReportTail( _( "Annotation complete." ), REPORTER::RPT_ACTION ); // Update on screen references, that can be modified by previous calculations: - m_CurrentSheet->UpdateAllScreenReferences(); + g_CurrentSheet->UpdateAllScreenReferences(); SetSheetNumberAndCount(); SyncView(); @@ -252,7 +252,7 @@ int SCH_EDIT_FRAME::CheckAnnotate( REPORTER& aReporter, bool aOneSheetOnly ) if( !aOneSheetOnly ) sheetList.GetComponents( componentsList ); else - m_CurrentSheet->GetComponents( componentsList ); + g_CurrentSheet->GetComponents( componentsList ); return componentsList.CheckAnnotation( aReporter ); } diff --git a/eeschema/block.cpp b/eeschema/block.cpp index 9a56299d39..e28c792f51 100644 --- a/eeschema/block.cpp +++ b/eeschema/block.cpp @@ -455,7 +455,7 @@ void SCH_EDIT_FRAME::PasteListOfItems( wxDC* DC ) return; } - wxFileName destFn = m_CurrentSheet->Last()->GetFileName(); + wxFileName destFn = g_CurrentSheet->Last()->GetFileName(); if( destFn.IsRelative() ) destFn.MakeAbsolute( Prj().GetProjectPath() ); diff --git a/eeschema/bus-wire-junction.cpp b/eeschema/bus-wire-junction.cpp index 3d0fb0d61e..7ac2c3a927 100644 --- a/eeschema/bus-wire-junction.cpp +++ b/eeschema/bus-wire-junction.cpp @@ -42,6 +42,7 @@ #include #include #include +#include #include #include @@ -53,7 +54,6 @@ static void ComputeBreakPoint( SCH_SCREEN* aScreen, SCH_LINE* aSegment, wxPoint& static DLIST< SCH_ITEM > s_wires; // when creating a new set of wires, // stores here the new wires. - /** * In a contiguous list of wires, remove wires that backtrack over the previous * wire. Example: @@ -130,15 +130,49 @@ static void DrawSegment( EDA_DRAW_PANEL* aPanel, wxDC* aDC, const wxPoint& aPosi wxPoint endpos = frame->GetCrossHairPosition(); + auto view = static_cast( aPanel )->GetView(); + view->ClearPreview(); + + // Update the bus unfold posture based on the mouse movement + if( frame->m_busUnfold.in_progress && !frame->m_busUnfold.label_placed ) + { + auto cursor_delta = frame->GetCursorPosition( false ) - frame->m_busUnfold.origin; + auto entry = frame->m_busUnfold.entry; + + bool offset = ( cursor_delta.x < 0 ); + char shape = ( offset ? ( ( cursor_delta.y >= 0 ) ? '/' : '\\' ) + : ( ( cursor_delta.y >= 0 ) ? '\\' : '/' ) ); + + // Erase and redraw if necessary + if( shape != entry->GetBusEntryShape() || + offset != frame->m_busUnfold.offset ) + { + entry->SetBusEntryShape( shape ); + wxPoint entry_pos = frame->m_busUnfold.origin; + + if( offset ) + entry_pos -= entry->GetSize(); + + entry->SetPosition( entry_pos ); + frame->m_busUnfold.offset = offset; + + frame->RefreshItem( entry ); + + wxPoint wire_start = ( offset ? entry->GetPosition() : entry->m_End() ); + ( (SCH_LINE*) s_wires.begin() )->SetStartPoint( wire_start ); + } + + // Update the label "ghost" position + auto label = frame->m_busUnfold.label; + label->SetPosition( endpos ); + view->AddToPreview( label->Clone() ); + } + if( frame->GetForceHVLines() ) /* Coerce the line to vertical or horizontal one: */ ComputeBreakPoint( frame->GetScreen(), (SCH_LINE*) s_wires.GetLast()->Back(), endpos ); else ( (SCH_LINE*) s_wires.GetLast() )->SetEndPoint( endpos ); - auto view = static_cast( aPanel )->GetView(); - - view->ClearPreview(); - for( SCH_LINE* segment = (SCH_LINE*) s_wires.begin(); segment; segment = segment->Next() ) { if( !segment->IsNull() ) // Add to preview if segment length != 0 @@ -206,6 +240,30 @@ void SCH_EDIT_FRAME::BeginSegment( int type ) } else // A segment is in progress: terminates the current segment and add a new segment. { + // Place the label for bus unfolding if needed + if( IsBusUnfoldInProgress() && !m_busUnfold.label_placed ) + { + wxASSERT( type == LAYER_WIRE ); + + AddToScreen( m_busUnfold.label ); + m_busUnfold.label_placed = true; + + nextSegment = new SCH_LINE( cursorpos, LAYER_WIRE ); + + segment->ClearFlags( IS_NEW ); + segment->SetFlags( SELECTED ); + + nextSegment->SetStartPoint( cursorpos ); + nextSegment->SetFlags( IS_NEW ); + + s_wires.PushBack( nextSegment ); + GetScreen()->SetCurItem( nextSegment ); + + m_canvas->SetMouseCapture( DrawSegment, AbortCreateNewLine ); + SetRepeatItem( NULL ); + return; + } + SCH_LINE* prevSegment = segment->Back(); // Be aware prevSegment can be null when the horizontal and vertical lines only switch @@ -230,7 +288,8 @@ void SCH_EDIT_FRAME::BeginSegment( int type ) m_canvas->CallMouseCapture( nullptr, wxDefaultPosition, false ); // Terminate the command if the end point is on a pin, junction, or another wire or bus. - if( GetScreen()->IsTerminalPoint( cursorpos, segment->GetLayer() ) ) + if( !IsBusUnfoldInProgress() && + GetScreen()->IsTerminalPoint( cursorpos, segment->GetLayer() ) ) { EndSegment(); return; @@ -328,6 +387,18 @@ void SCH_EDIT_FRAME::EndSegment() itemList.PushItem( ITEM_PICKER( wire, UR_NEW ) ); } + if( IsBusUnfoldInProgress() && m_busUnfold.label_placed ) + { + wxASSERT( m_busUnfold.entry && m_busUnfold.label ); + + PICKED_ITEMS_LIST bus_items; + + bus_items.PushItem( ITEM_PICKER( m_busUnfold.entry, UR_NEW ) ); + bus_items.PushItem( ITEM_PICKER( m_busUnfold.label, UR_NEW ) ); + + SaveCopyInUndoList( bus_items, UR_NEW, false ); + } + // Get the last non-null wire (this is the last created segment). SetRepeatItem( segment = (SCH_LINE*) s_wires.GetLast() ); @@ -366,7 +437,13 @@ void SCH_EDIT_FRAME::EndSegment() AddJunction( i, true ); } + if( IsBusUnfoldInProgress() ) + { + FinishBusUnfold(); + } + TestDanglingEnds(); + screen->ClearDrawingState(); screen->SetCurItem( NULL ); m_canvas->EndMouseCapture( -1, -1, wxEmptyString, false ); @@ -564,12 +641,14 @@ bool SCH_EDIT_FRAME::TrimWire( const wxPoint& aStart, const wxPoint& aEnd, bool } -bool SCH_EDIT_FRAME::SchematicCleanUp( bool aAppend ) +bool SCH_EDIT_FRAME::SchematicCleanUp( bool aAppend, SCH_SCREEN* aScreen ) { SCH_ITEM* item = NULL; SCH_ITEM* secondItem = NULL; PICKED_ITEMS_LIST itemList; - SCH_SCREEN* screen = GetScreen(); + + if( aScreen == nullptr ) + aScreen = GetScreen(); auto remove_item = [ &itemList ]( SCH_ITEM* aItem ) -> void { @@ -577,9 +656,9 @@ bool SCH_EDIT_FRAME::SchematicCleanUp( bool aAppend ) itemList.PushItem( ITEM_PICKER( aItem, UR_DELETED ) ); }; - BreakSegmentsOnJunctions( true ); + BreakSegmentsOnJunctions( true, aScreen ); - for( item = screen->GetDrawItems(); item; item = item->Next() ) + for( item = aScreen->GetDrawItems(); item; item = item->Next() ) { if( ( item->Type() != SCH_LINE_T ) && ( item->Type() != SCH_JUNCTION_T ) @@ -591,7 +670,7 @@ bool SCH_EDIT_FRAME::SchematicCleanUp( bool aAppend ) // Remove unneeded junctions if( ( item->Type() == SCH_JUNCTION_T ) - && ( !screen->IsJunctionNeeded( item->GetPosition() ) ) ) + && ( !aScreen->IsJunctionNeeded( item->GetPosition() ) ) ) { remove_item( item ); continue; @@ -634,16 +713,16 @@ bool SCH_EDIT_FRAME::SchematicCleanUp( bool aAppend ) // If the end points overlap, check if we still need the junction if( secondLine->IsEndPoint( firstLine->GetStartPoint() ) ) - needed = screen->IsJunctionNeeded( firstLine->GetStartPoint() ); + needed = aScreen->IsJunctionNeeded( firstLine->GetStartPoint() ); else if( secondLine->IsEndPoint( firstLine->GetEndPoint() ) ) - needed = screen->IsJunctionNeeded( firstLine->GetEndPoint() ); + needed = aScreen->IsJunctionNeeded( firstLine->GetEndPoint() ); if( !needed && ( line = (SCH_LINE*) secondLine->MergeOverlap( firstLine ) ) ) { remove_item( item ); remove_item( secondItem ); itemList.PushItem( ITEM_PICKER( line, UR_NEW ) ); - AddToScreen( line ); + AddToScreen( line, aScreen ); break; } } @@ -653,33 +732,33 @@ bool SCH_EDIT_FRAME::SchematicCleanUp( bool aAppend ) } } - for( item = screen->GetDrawItems(); item; item = secondItem ) + for( item = aScreen->GetDrawItems(); item; item = secondItem ) { secondItem = item->Next(); if( item->GetFlags() & STRUCT_DELETED ) - RemoveFromScreen( item ); + RemoveFromScreen( item, aScreen ); } - SaveCopyInUndoList( itemList, UR_CHANGED, aAppend ); - return itemList.GetCount() > 0; } -bool SCH_EDIT_FRAME::BreakSegment( SCH_LINE* aSegment, const wxPoint& aPoint, bool aAppend, - SCH_LINE** aNewSegment ) +bool SCH_EDIT_FRAME::BreakSegment( SCH_LINE* aSegment, const wxPoint& aPoint, + bool aAppend, SCH_LINE** aNewSegment, + SCH_SCREEN* aScreen ) { if( !IsPointOnSegment( aSegment->GetStartPoint(), aSegment->GetEndPoint(), aPoint ) || aSegment->IsEndPoint( aPoint ) ) return false; - SaveCopyInUndoList( aSegment, UR_CHANGED, aAppend ); + if( aScreen == nullptr ) + aScreen = GetScreen(); + SCH_LINE* newSegment = new SCH_LINE( *aSegment ); - SaveCopyInUndoList( newSegment, UR_NEW, true ); newSegment->SetStartPoint( aPoint ); - AddToScreen( newSegment ); + AddToScreen( newSegment, aScreen ); RefreshItem( aSegment ); aSegment->SetEndPoint( aPoint ); @@ -691,33 +770,41 @@ bool SCH_EDIT_FRAME::BreakSegment( SCH_LINE* aSegment, const wxPoint& aPoint, bo } -bool SCH_EDIT_FRAME::BreakSegments( const wxPoint& aPoint, bool aAppend ) +bool SCH_EDIT_FRAME::BreakSegments( const wxPoint& aPoint, bool aAppend, + SCH_SCREEN* aScreen ) { + if( aScreen == nullptr ) + aScreen = GetScreen(); + bool brokenSegments = false; - for( SCH_ITEM* segment = GetScreen()->GetDrawItems(); segment; segment = segment->Next() ) + for( SCH_ITEM* segment = aScreen->GetDrawItems(); segment; segment = segment->Next() ) { if( ( segment->Type() != SCH_LINE_T ) || ( segment->GetLayer() == LAYER_NOTES ) ) continue; - brokenSegments |= BreakSegment( (SCH_LINE*) segment, aPoint, aAppend || brokenSegments ); + brokenSegments |= BreakSegment( (SCH_LINE*) segment, aPoint, + aAppend || brokenSegments, NULL, aScreen ); } return brokenSegments; } -bool SCH_EDIT_FRAME::BreakSegmentsOnJunctions( bool aAppend ) +bool SCH_EDIT_FRAME::BreakSegmentsOnJunctions( bool aAppend, SCH_SCREEN* aScreen ) { + if( aScreen == nullptr ) + aScreen = GetScreen(); + bool brokenSegments = false; - for( SCH_ITEM* item = GetScreen()->GetDrawItems(); item; item = item->Next() ) + for( SCH_ITEM* item = aScreen->GetDrawItems(); item; item = item->Next() ) { if( item->Type() == SCH_JUNCTION_T ) { SCH_JUNCTION* junction = ( SCH_JUNCTION* ) item; - if( BreakSegments( junction->GetPosition(), brokenSegments || aAppend ) ) + if( BreakSegments( junction->GetPosition(), brokenSegments || aAppend, aScreen ) ) brokenSegments = true; } else @@ -725,8 +812,8 @@ bool SCH_EDIT_FRAME::BreakSegmentsOnJunctions( bool aAppend ) SCH_BUS_ENTRY_BASE* busEntry = dynamic_cast( item ); if( busEntry ) { - if( BreakSegments( busEntry->GetPosition(), brokenSegments || aAppend ) - || BreakSegments( busEntry->m_End(), brokenSegments || aAppend ) ) + if( BreakSegments( busEntry->GetPosition(), brokenSegments || aAppend, aScreen ) + || BreakSegments( busEntry->m_End(), brokenSegments || aAppend, aScreen ) ) brokenSegments = true; } } @@ -846,6 +933,7 @@ SCH_NO_CONNECT* SCH_EDIT_FRAME::AddNoConnect( const wxPoint& aPosition ) static void AbortCreateNewLine( EDA_DRAW_PANEL* aPanel, wxDC* aDC ) { SCH_SCREEN* screen = (SCH_SCREEN*) aPanel->GetScreen(); + SCH_EDIT_FRAME* parent = ( SCH_EDIT_FRAME* ) aPanel->GetParent(); if( screen->GetCurItem() ) @@ -858,6 +946,11 @@ static void AbortCreateNewLine( EDA_DRAW_PANEL* aPanel, wxDC* aDC ) parent->SetRepeatItem( NULL ); } + if( parent->IsBusUnfoldInProgress() ) + { + parent->CancelBusUnfold(); + } + auto view = static_cast(aPanel)->GetView(); view->ClearPreview(); view->ShowPreview( false ); diff --git a/eeschema/bus_alias.cpp b/eeschema/bus_alias.cpp new file mode 100644 index 0000000000..b7eb0f5d45 --- /dev/null +++ b/eeschema/bus_alias.cpp @@ -0,0 +1,41 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2018 CERN + * @author Jon Evans + * + * 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, see . + */ + +#include + +#include "bus_alias.h" + + +BUS_ALIAS::BUS_ALIAS( SCH_SCREEN* aParent ) : + m_parent( aParent ) +{ +} + + +BUS_ALIAS::~BUS_ALIAS() +{ +} + + +bool BUS_ALIAS::Contains( const wxString& aName ) +{ + return ( std::find( m_members.begin(), m_members.end(), aName ) + != m_members.end() ); +} diff --git a/eeschema/bus_alias.h b/eeschema/bus_alias.h new file mode 100644 index 0000000000..43073277f4 --- /dev/null +++ b/eeschema/bus_alias.h @@ -0,0 +1,100 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2018 CERN + * @author Jon Evans + * + * 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, see . + */ + +#ifndef _BUS_ALIAS_H +#define _BUS_ALIAS_H + +#include +#include +#include + + +class SCH_SCREEN; + + +class BUS_ALIAS +{ +public: + BUS_ALIAS( SCH_SCREEN* aParent = NULL ); + + ~BUS_ALIAS(); + + std::shared_ptr< BUS_ALIAS > Clone() const + { + return std::make_shared< BUS_ALIAS >( *this ); + } + + wxString GetName() + { + return m_name; + } + + void SetName( const wxString& aName ) + { + m_name = aName; + } + + void ClearMembers() + { + m_members.clear(); + } + + void AddMember( const wxString& aName ) + { + m_members.push_back( aName ); + } + + int GetMemberCount() + { + return m_members.size(); + } + + std::vector< wxString >& Members() + { + return m_members; + } + + bool Contains( const wxString& aName ); + + SCH_SCREEN* GetParent() + { + return m_parent; + } + + void SetParent( SCH_SCREEN* aParent ) + { + m_parent = aParent; + } + +protected: + + wxString m_name; + + std::vector< wxString > m_members; + + /** + * The bus alias editor dialog can edit aliases from all open sheets. + * This means we have to store a reference back to our parent so that + * the dialog can update the parent if aliases are changed or removed. + */ + SCH_SCREEN* m_parent; +}; + +#endif diff --git a/eeschema/connection_graph.cpp b/eeschema/connection_graph.cpp new file mode 100644 index 0000000000..ccf73f192b --- /dev/null +++ b/eeschema/connection_graph.cpp @@ -0,0 +1,1644 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2018 CERN + * @author Jon Evans + * + * 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, see . + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +using std::map; +using std::unordered_map; +using std::unordered_set; +using std::vector; + + +bool CONNECTION_SUBGRAPH::ResolveDrivers( bool aCreateMarkers ) +{ + int highest_priority = -1; + vector candidates; + + m_driver = nullptr; + + for( auto item : m_drivers ) + { + int item_priority = 0; + + switch( item->Type() ) + { + case SCH_SHEET_PIN_T: item_priority = 2; break; + case SCH_LABEL_T: item_priority = 3; break; + case SCH_HIERARCHICAL_LABEL_T: item_priority = 4; break; + case SCH_PIN_CONNECTION_T: + { + auto pin_connection = static_cast( item ); + + if( pin_connection->m_pin->IsPowerConnection() ) + item_priority = 5; + else + item_priority = 1; + + // Skip power flags, etc + if( item_priority == 1 && !pin_connection->m_comp->IsInNetlist() ) + continue; + + break; + } + case SCH_GLOBAL_LABEL_T: item_priority = 6; break; + default: break; + } + + if( item_priority > highest_priority ) + { + candidates.clear(); + candidates.push_back( item ); + highest_priority = item_priority; + } + else if( candidates.size() && ( item_priority == highest_priority ) ) + { + candidates.push_back( item ); + } + } + + if( candidates.size() ) + { + if( candidates.size() > 1 ) + { + if( highest_priority == 1 || highest_priority == 5 ) + { + // We have multiple options and they are all component pins. + std::sort( candidates.begin(), candidates.end(), + [this]( SCH_ITEM* a, SCH_ITEM* b) -> bool + { + auto pin_a = static_cast( a ); + auto pin_b = static_cast( b ); + + auto name_a = pin_a->GetDefaultNetName( m_sheet ); + auto name_b = pin_b->GetDefaultNetName( m_sheet ); + + return name_a < name_b; + } ); + } + } + + m_driver = candidates[0]; + } + + // For power connections, we allow multiple drivers + if( highest_priority == 5 && candidates.size() > 1 && + candidates[0]->Type() == SCH_PIN_CONNECTION_T ) + { + auto pc = static_cast( candidates[0] ); + + wxASSERT( pc->m_pin->IsPowerConnection() ); + + m_multiple_power_ports = true; + } + + if( aCreateMarkers && !m_multiple_power_ports && + candidates.size() > 1 && highest_priority > 1 ) + { + wxString msg; + msg.Printf( _( "%s and %s are both attached to the same wires. " + "%s was picked as the label to use for netlisting." ), + candidates[0]->GetSelectMenuText( m_frame->GetUserUnits() ), + candidates[1]->GetSelectMenuText( m_frame->GetUserUnits() ), + candidates[0]->Connection( m_sheet )->Name() ); + + wxASSERT( candidates[0] != candidates[1] ); + + auto marker = new SCH_MARKER(); + marker->SetTimeStamp( GetNewTimeStamp() ); + marker->SetMarkerType( MARKER_BASE::MARKER_ERC ); + marker->SetErrorLevel( MARKER_BASE::MARKER_SEVERITY_WARNING ); + marker->SetData( ERCE_DRIVER_CONFLICT, + candidates[0]->GetPosition(), msg, + candidates[1]->GetPosition() ); + + m_sheet.LastScreen()->Append( marker ); + + // If aCreateMarkers is true, then this is part of ERC check, so we + // should return false even if the driver was assigned + return false; + } + + return aCreateMarkers || ( m_driver != nullptr ); +} + + +wxString CONNECTION_SUBGRAPH::GetNetName() +{ + if( !m_driver || m_dirty ) + return ""; + + if( !m_driver->Connection( m_sheet ) ) + { + #ifdef CONNECTIVITY_DEBUG + wxASSERT_MSG( false, "Tried to get the net name of an item with no connection" ); + #endif + + return ""; + } + + return m_driver->Connection( m_sheet )->Name(); +} + + +std::vector CONNECTION_SUBGRAPH::GetBusLabels() +{ + vector labels; + + for( auto item : m_drivers ) + { + switch( item->Type() ) + { + case SCH_LABEL_T: + case SCH_GLOBAL_LABEL_T: + { + auto label_conn = item->Connection( m_sheet ); + + // Only consider bus vectors + if( label_conn->Type() == CONNECTION_BUS ) + labels.push_back( item ); + } + default: break; + } + } + + return labels; +} + + +void CONNECTION_GRAPH::Reset() +{ + for( auto sg : m_subgraphs ) + delete sg; + + m_items.clear(); + m_subgraphs.clear(); + m_invisible_power_pins.clear(); + m_bus_alias_cache.clear(); + m_net_name_to_code_map.clear(); + m_bus_name_to_code_map.clear(); + m_subgraph_code_map.clear(); + m_net_code_to_subgraphs_map.clear(); + m_last_net_code = 1; + m_last_bus_code = 1; + m_last_subgraph_code = 1; +} + + +void CONNECTION_GRAPH::Recalculate( SCH_SHEET_LIST aSheetList ) +{ +#ifdef CONNECTIVITY_DEBUG + PROF_COUNTER phase1; +#endif + + Reset(); + + for( const auto& sheet : aSheetList ) + { + std::vector items; + + for( auto item = sheet.LastScreen()->GetDrawItems(); + item; item = item->Next() ) + { + if( item->IsConnectable() ) + { + items.push_back( item ); + } + } + + updateItemConnectivity( sheet, items ); + } + +#ifdef CONNECTIVITY_DEBUG + phase1.Stop(); + std::cout << "UpdateItemConnectivity() " << phase1.msecs() << " ms" << std::endl; +#endif + + // IsDanglingStateChanged() also adds connected items for things like SCH_TEXT + SCH_SCREENS schematic; + schematic.TestDanglingEnds(); + + buildConnectionGraph(); +} + + +void CONNECTION_GRAPH::updateItemConnectivity( SCH_SHEET_PATH aSheet, + vector aItemList ) +{ + unordered_map< wxPoint, vector > connection_map; + + for( auto item : aItemList ) + { + vector< wxPoint > points; + item->GetConnectionPoints( points ); + item->ConnectedItems().clear(); + + if( item->Type() == SCH_SHEET_T ) + { + for( auto& pin : static_cast( item )->GetPins() ) + { + if( !pin.Connection( aSheet ) ) + { + pin.InitializeConnection( aSheet ); + } + + pin.ConnectedItems().clear(); + pin.Connection( aSheet )->Reset(); + + connection_map[ pin.GetTextPos() ].push_back( &pin ); + m_items.push_back( &pin ); + } + } + else if( item->Type() == SCH_COMPONENT_T ) + { + auto component = static_cast( item ); + + component->UpdatePinConnections( aSheet ); + + for( auto it : component->PinConnections() ) + { + auto pin_connection = it.second; + + // TODO(JE) use cached location from m_Pins + auto pin_pos = pin_connection->m_pin->GetPosition(); + auto pos = component->GetTransform().TransformCoordinate( pin_pos ) + + component->GetPosition(); + + // because calling the first time is not thread-safe + pin_connection->GetDefaultNetName( aSheet ); + pin_connection->ConnectedItems().clear(); + + connection_map[ pos ].push_back( pin_connection ); + m_items.push_back( pin_connection ); + } + } + else + { + m_items.push_back( item ); + + if( !item->Connection( aSheet ) ) + { + item->InitializeConnection( aSheet ); + } + + auto conn = item->Connection( aSheet ); + + conn->Reset(); + + // Set bus/net property here so that the propagation code uses it + switch( item->Type() ) + { + case SCH_LINE_T: + conn->SetType( ( item->GetLayer() == LAYER_BUS ) ? + CONNECTION_BUS : CONNECTION_NET ); + break; + + case SCH_BUS_BUS_ENTRY_T: + conn->SetType( CONNECTION_BUS ); + break; + + case SCH_PIN_CONNECTION_T: + case SCH_BUS_WIRE_ENTRY_T: + conn->SetType( CONNECTION_NET ); + break; + + default: + break; + } + + for( auto point : points ) + { + connection_map[ point ].push_back( item ); + } + } + } + + for( auto it : connection_map ) + { + auto connection_vec = it.second; + SCH_ITEM* junction = nullptr; + + for( auto connected_item : connection_vec ) + { + // Look for junctions. For points that have a junction, we want all + // items to connect to the junction but not to each other. + + if( connected_item->Type() == SCH_JUNCTION_T ) + { + junction = connected_item; + } + + // Bus entries are special: they can have connection points in the + // middle of a wire segment, because the junction algo doesn't split + // the segment in two where you place a bus entry. This means that + // bus entries that don't land on the end of a line segment need to + // have "virtual" connection points to the segments they graphically + // touch. + if( connected_item->Type() == SCH_BUS_WIRE_ENTRY_T ) + { + // If this location only has the connection point of the bus + // entry itself, this means that either the bus entry is not + // connected to anything graphically, or that it is connected to + // a segment at some point other than at one of the endpoints. + if( connection_vec.size() == 1 ) + { + auto screen = aSheet.LastScreen(); + auto bus = screen->GetBus( it.first ); + + if( bus ) + { + auto bus_entry = static_cast( connected_item ); + bus_entry->m_connected_bus_item = bus; + } + } + } + + // Bus-to-bus entries are treated just like bus wires + if( connected_item->Type() == SCH_BUS_BUS_ENTRY_T ) + { + if( connection_vec.size() < 2 ) + { + auto screen = aSheet.LastScreen(); + auto bus = screen->GetBus( it.first ); + + if( bus ) + { + auto bus_entry = static_cast( connected_item ); + + if( it.first == bus_entry->GetPosition() ) + bus_entry->m_connected_bus_items[0] = bus; + else + bus_entry->m_connected_bus_items[1] = bus; + + bus_entry->ConnectedItems().insert( bus ); + bus->ConnectedItems().insert( bus_entry ); + } + } + } + + for( auto test_item : connection_vec ) + { + if( !junction && test_item->Type() == SCH_JUNCTION_T ) + { + junction = test_item; + } + + if( connected_item != test_item && + connected_item != junction && + connected_item->ConnectionPropagatesTo( test_item ) && + test_item->ConnectionPropagatesTo( connected_item ) ) + { + connected_item->ConnectedItems().insert( test_item ); + test_item->ConnectedItems().insert( connected_item ); + } + + if( connected_item->Type() == SCH_BUS_WIRE_ENTRY_T ) + { + if( test_item->Connection( aSheet )->IsBus() ) + { + auto bus_entry = static_cast( connected_item ); + bus_entry->m_connected_bus_item = test_item; + } + } + } + } + } +} + + +// TODO(JE) This won't give the same subgraph IDs (and eventually net/graph codes) +// to the same subgraph necessarily if it runs over and over again on the same +// sheet. We need: +// +// a) a cache of net/bus codes, like used before +// b) to persist the CONNECTION_GRAPH globally so the cache is persistent, +// c) some way of trying to avoid changing net names. so we should keep track +// of the previous driver of a net, and if it comes down to choosing between +// equally-prioritized drivers, choose the one that already exists as a driver +// on some portion of the items. + + +void CONNECTION_GRAPH::buildConnectionGraph() +{ +#ifdef CONNECTIVITY_DEBUG + PROF_COUNTER phase2; +#endif + + // Recache all bus aliases for later use + + SCH_SHEET_LIST all_sheets( g_RootSheet ); + + for( unsigned i = 0; i < all_sheets.size(); i++ ) + { + for( auto alias : all_sheets[i].LastScreen()->GetBusAliases() ) + { + m_bus_alias_cache[ alias->GetName() ] = alias; + } + } + + // Build subgraphs from items (on a per-sheet basis) + + for( auto item : m_items ) + { + for( auto it : item->m_connection_map ) + { + const auto sheet = it.first; + auto connection = it.second; + + if( connection->SubgraphCode() == 0 ) + { + auto subgraph = new CONNECTION_SUBGRAPH( m_frame ); + + subgraph->m_code = m_last_subgraph_code++; + subgraph->m_sheet = sheet; + + subgraph->m_items.push_back( item ); + + if( connection->IsDriver() ) + subgraph->m_drivers.push_back( item ); + + if( item->Type() == SCH_NO_CONNECT_T ) + { + subgraph->m_no_connect = item; + } + else if( item->Type() == SCH_PIN_CONNECTION_T ) + { + auto pc = static_cast( item ); + + if( pc->m_pin->GetType() == PIN_NC ) + subgraph->m_no_connect = item; + + // Invisible power pins need to be post-processed later + + if( pc->m_pin->IsPowerConnection() && + !pc->m_pin->IsVisible() ) + { + m_invisible_power_pins.push_back( pc ); + } + } + + connection->SetSubgraphCode( subgraph->m_code ); + + std::list members( item->ConnectedItems().begin(), + item->ConnectedItems().end() ); + + for( auto connected_item : members ) + { + if( !connected_item->Connection( sheet ) ) + connected_item->InitializeConnection( sheet ); + + if( connected_item->Type() == SCH_NO_CONNECT_T ) + subgraph->m_no_connect = connected_item; + + auto connected_conn = connected_item->Connection( sheet ); + + wxASSERT( connected_conn ); + + if( connected_conn->SubgraphCode() == 0 ) + { + connected_conn->SetSubgraphCode( subgraph->m_code ); + subgraph->m_items.push_back( connected_item ); + + if( connected_conn->IsDriver() ) + subgraph->m_drivers.push_back( connected_item ); + + members.insert( members.end(), + connected_item->ConnectedItems().begin(), + connected_item->ConnectedItems().end() ); + } + } + + subgraph->m_dirty = true; + m_subgraphs.push_back( subgraph ); + } + } + } + + /** + * TODO(JE) + * + * It would be good if net codes were preserved as much as possible when + * generating netlists, so that unnamed nets don't keep shifting around when + * you regenerate. + * + * Right now, we are clearing out the old connections up in + * UpdateItemConnectivity(), but that is useful information, so maybe we + * need to just set the dirty flag or something. + * + * That way, ResolveDrivers() can check what the driver of the subgraph was + * previously, and if it is in the situation of choosing between equal + * candidates for an auto-generated net name, pick the previous one. + * + * N.B. the old algorithm solves this by sorting the possible net names + * alphabetically, so as long as the same refdes components are involved, + * the net will be the same. + */ + + // Resolve drivers for subgraphs and propagate connectivity info + + std::atomic nextSubgraph( 0 ); + std::atomic threadsFinished( 0 ); + size_t parallelThreadCount = std::max( std::thread::hardware_concurrency(), 2 ); + + for( size_t ii = 0; ii < parallelThreadCount; ++ii ) + { + auto t = std::thread( [&]() + { + for( size_t subgraphId = nextSubgraph.fetch_add( 1 ); + subgraphId < static_cast( m_subgraphs.size() ); + subgraphId = nextSubgraph.fetch_add( 1 ) ) + { + auto subgraph = m_subgraphs[subgraphId]; + + if( !subgraph->m_dirty ) + continue; + + if( !subgraph->ResolveDrivers() ) + { + subgraph->m_dirty = false; + } + else + { + // Now the subgraph has only one driver + auto driver = subgraph->m_driver; + auto sheet = subgraph->m_sheet; + auto connection = driver->Connection( sheet ); + + // TODO(JE) This should live in SCH_CONNECTION probably + switch( driver->Type() ) + { + case SCH_LABEL_T: + case SCH_GLOBAL_LABEL_T: + case SCH_HIERARCHICAL_LABEL_T: + case SCH_PIN_CONNECTION_T: + case SCH_SHEET_PIN_T: + case SCH_SHEET_T: + { + if( driver->Type() == SCH_PIN_CONNECTION_T ) + { + auto pin = static_cast( driver ); + + // NOTE(JE) GetDefaultNetName is not thread-safe. + connection->ConfigureFromLabel( pin->GetDefaultNetName( sheet ) ); + } + else + { + auto text = static_cast( driver ); + connection->ConfigureFromLabel( text->GetText() ); + } + + connection->SetDriver( driver ); + connection->ClearDirty(); + break; + } + default: + break; + } + + subgraph->m_dirty = false; + } + } + + threadsFinished++; + } ); + + t.detach(); + } + + while( threadsFinished < parallelThreadCount ) + std::this_thread::sleep_for( std::chrono::milliseconds( 10 ) ); + + // Generate net codes + + for( auto subgraph : m_subgraphs ) + { + if( !subgraph->m_driver ) + continue; + + auto connection = subgraph->m_driver->Connection( subgraph->m_sheet ); + int code; + + auto name = subgraph->GetNetName(); + + if( connection->IsBus() ) + { + try + { + code = m_bus_name_to_code_map.at( name ); + } + catch( const std::out_of_range& oor ) + { + code = m_last_bus_code++; + m_bus_name_to_code_map[ name ] = code; + } + + connection->SetBusCode( code ); + } + else + { + assignNewNetCode( *connection ); + } + + for( auto item : subgraph->m_items ) + { + auto item_conn = item->Connection( subgraph->m_sheet ); + + if( ( connection->IsBus() && item_conn->IsNet() ) || + ( connection->IsNet() && item_conn->IsBus() ) ) + { + continue; + } + + if( item != subgraph->m_driver ) + { + item_conn->Clone( *connection ); + item_conn->ClearDirty(); + } + } + + // Reset the flag for the next loop below + subgraph->m_dirty = true; + + auto sheet = subgraph->m_sheet; + + auto candidate_subgraphs( m_subgraphs ); + auto connections_to_check( connection->Members() ); + + bool contains_hier_stuff = false; + + for( auto item : subgraph->m_items ) + { + if( item->Type() == SCH_HIERARCHICAL_LABEL_T || + item->Type() == SCH_SHEET_PIN_T ) + { + contains_hier_stuff = true; + break; + } + } + + // TODO(JE) maybe it will be better to form these links eventually, + // but for now let's only include subgraphs that contain hierarchical + // links in one direction or another + if( !contains_hier_stuff ) + continue; + + // For plain nets, just link based on the driver + if( !connection->IsBus() ) + { + connections_to_check.push_back( std::make_shared( *connection ) ); + } + + for( unsigned i = 0; i < connections_to_check.size(); i++ ) + { + auto member = connections_to_check[i]; + + if( member->IsBus() ) + { + connections_to_check.insert( connections_to_check.end(), + member->Members().begin(), + member->Members().end() ); + continue; + } + + for( auto candidate : candidate_subgraphs ) + { + if( candidate->m_sheet != sheet || !candidate->m_driver || candidate == subgraph ) + continue; + + auto candidate_connection = candidate->m_driver->Connection( sheet ); + + if( !candidate_connection->IsNet() ) + continue; + + if( candidate_connection->Name( false ) == member->Name( false ) ) + subgraph->m_neighbor_map[ member ].push_back( candidate ); + } + } + } + + // Generate subgraphs for invisible power pins + + for( auto pc : m_invisible_power_pins ) + { + if( pc->ConnectedItems().size() > 0 && !pc->m_pin->GetParent()->IsPower() ) + { + // ERC will warn about this: user has wired up an invisible pin + continue; + } + + auto name = pc->m_pin->GetName(); + int code = -1; + auto sheet = all_sheets[0]; + + auto connection = pc->Connection( sheet ); + + if( !connection ) + { + pc->InitializeConnection( sheet ); + connection = pc->Connection( sheet ); + } + else + { + continue; + } + + try + { + code = m_net_name_to_code_map.at( name ); + } + catch( const std::out_of_range& oor ) + { + code = assignNewNetCode( *connection ); + } + + // Find a subgraph with the same net and just throw this pin on to it. + // TODO(JE) should there be a dedicated subgraph for invisible pins? + // Since this is currently done at the very end, the fact that some + // subgraph will be getting random pins added shouldn't be a problem, + // but this could be a gotcha if subgraph data is used after the end + // of this method at some point in the future. + + CONNECTION_SUBGRAPH* subgraph = nullptr; + + try + { + auto subgraphs = m_net_code_to_subgraphs_map.at( code ); + subgraph = subgraphs[0]; + } + catch( const std::out_of_range& oor ) + { + } + + if( subgraph && subgraph->m_driver ) + { + auto parent = subgraph->m_driver->Connection( subgraph->m_sheet ); + pc->Connection( sheet )->Clone( *parent ); + } + else + { + subgraph = new CONNECTION_SUBGRAPH( m_frame ); + m_net_code_to_subgraphs_map[ code ].push_back( subgraph ); + + subgraph->m_code = m_last_subgraph_code++; + subgraph->m_sheet = sheet; + subgraph->m_items.push_back( pc ); + subgraph->m_drivers.push_back( pc ); + + subgraph->ResolveDrivers(); + + connection->SetSubgraphCode( subgraph->m_code ); + } + } + + // Collapse net codes between hierarchical sheets + + for( auto it = m_subgraphs.begin(); it < m_subgraphs.end(); it++ ) + { + auto subgraph = *it; + + if( !subgraph->m_driver || !subgraph->m_dirty ) + continue; + + auto sheet = subgraph->m_sheet; + auto connection = std::make_shared( *subgraph->m_driver->Connection( sheet ) ); + + // Collapse power nets that are shorted together + + if( subgraph->m_multiple_power_ports ) + { + for( auto obj : subgraph->m_drivers ) + { + if( obj == subgraph->m_driver ) + continue; + + auto power_object = dynamic_cast( obj ); + + wxASSERT( power_object ); + + auto name = power_object->GetDefaultNetName( subgraph->m_sheet ); + int code = -1; + + try + { + code = m_net_name_to_code_map.at( name ); + } + catch( const std::out_of_range& oor ) + { + continue; + } + + for( auto subgraph_to_update : m_subgraphs ) + { + if( !subgraph_to_update->m_driver ) + continue; + + auto subsheet = subgraph_to_update->m_sheet; + auto conn = subgraph_to_update->m_driver->Connection( subsheet ); + + if( conn->IsBus() || conn->NetCode() != code ) + continue; + + for( auto item : subgraph_to_update->m_items ) + { + auto item_conn = item->Connection( subsheet ); + item_conn->Clone( *connection ); + } + } + } + } + + /** + * Is this bus in the highest level of hierarchy? That is, does it + * contain no hierarchical ports to parent sheets? If so, we process it + * here. If not, we continue, since the bus will be reached from one in + * a higher level sheet. + */ + + bool contains_hier_labels = false; + + for( auto item : subgraph->m_drivers ) + { + if( item->Type() == SCH_HIERARCHICAL_LABEL_T ) + { + contains_hier_labels = true; + break; + } + } + + if( contains_hier_labels ) + continue; + + // On the top level sheet, copy the neighbors onto the bus members + // because the members won't have net codes yet. Then recurse into the + // child sheets and propagate those net codes down. + + // TODO(JE) should we assign bus member net codes in the bus first, and + // then reverse this operation so we overwrite the net codes generated + // for the neighbors earlier rather than pulling them in? + + if( connection->IsBus() ) + { + for( auto& kv : subgraph->m_neighbor_map ) + { + auto member = kv.first; + + int candidate_net_code = 0; + + for( auto neighbor : kv.second ) + { + auto neighbor_conn = neighbor->m_driver->Connection( sheet ); + + try + { + int c = m_net_name_to_code_map.at( neighbor_conn->Name() ); + + if( candidate_net_code == 0 ) + candidate_net_code = c; + else + { + #ifdef CONNECTIVITY_DEBUG + wxASSERT_MSG( false, "More than one net code for a neighbor!" ); + #endif + } + } + catch( const std::out_of_range& oor ) + { + #ifdef CONNECTIVITY_DEBUG + wxASSERT_MSG( false, "No net code found for an existing net" ); + #endif + } + + member->SetNetCode( candidate_net_code ); + } + } + + // Some bus members might not have a neighbor to establish a net + // code, so generate new ones as needed. + for( auto& member : connection->Members() ) + { + if( member->IsNet() && member->NetCode() == 0 ) + { + assignNewNetCode( *member ); + } + else if( member->IsBus() ) + { + for( auto& sub_member : member->Members() ) + { + if( sub_member->NetCode() == 0 ) + assignNewNetCode( *sub_member ); + } + } + } + } + + /** + * The general plan: + * + * Find subsheet subgraphs that match this one (because the driver is a + * hierarchical label with the same name as a sheet pin on this one). + * + * Iterate over the bus members of the subsheet subgraph: + * + * 1) Find the matching bus member of the top level subgraph. + * For bus groups this is just a name match (minus path). + * For bus vectors the names *don't have to match*, just + * the vector index! + * + * 2) Clone the connection of the top level subgraph onto all + * the neighbor subgraphs. + * + * 3) Recurse down onto any subsheets connected to the SSSG. + */ + + for( auto item : subgraph->m_items ) + { + if( item->Type() == SCH_SHEET_PIN_T ) + { + auto sp = static_cast( item ); + auto sp_name = sp->GetText(); + auto subsheet = sheet; + subsheet.push_back( sp->GetParent() ); + + for( auto candidate : m_subgraphs ) + { + if( !candidate->m_dirty ) + continue; + + if( candidate->m_sheet == subsheet && candidate->m_driver ) + { + auto driver = candidate->m_driver; + + if( ( driver->Type() == SCH_HIERARCHICAL_LABEL_T ) && + ( static_cast( driver )->GetText() == sp_name ) ) + { + // We found a subgraph that is a subsheet child of + // our top-level subgraph, so let's mark it + + candidate->m_dirty = false; + + auto type = driver->Connection( subsheet )->Type(); + + // Directly update subsheet net connections + + for( auto c_item : candidate->m_items ) + { + auto c = c_item->Connection( subsheet ); + + wxASSERT( c ); + + if( ( connection->IsBus() && c->IsNet() ) || + ( connection->IsNet() && c->IsBus() ) ) + { + continue; + } + + c->Clone( *connection ); + } + + // Now propagate to subsheet neighbors + for( auto& kv : candidate->m_neighbor_map ) + { + auto member = kv.first; + std::shared_ptr top_level_conn; + + if( type == CONNECTION_BUS_GROUP ) + { + // Bus group: match parent by name + for( auto parent_member : connection->Members() ) + { + if( parent_member->IsNet() && + parent_member->Name( true ) == member->Name( true ) ) + { + top_level_conn = parent_member; + } + else if( parent_member->IsBus() ) + { + for( auto& sub_member : parent_member->Members() ) + { + if( sub_member->Name( true ) == member->Name( true ) ) + top_level_conn = sub_member; + } + } + } + } + else if( type == CONNECTION_BUS ) + { + // Bus vector: match parent by index + for( auto parent_member : connection->Members() ) + { + if( parent_member->VectorIndex() == member->VectorIndex() ) + top_level_conn = parent_member; + } + } + else + { + top_level_conn = connection; + } + + // If top_level_conn was not found, probably it's + // an ERC error and will be caught by ERC + + if( !top_level_conn ) + { + continue; + } + + for( auto neighbor : kv.second ) + { + for( auto n_item : neighbor->m_items ) + { + auto c = n_item->Connection( subsheet ); + + wxASSERT( c ); + + c->Clone( *top_level_conn ); + } + } + } + } + } + } + } + } + + subgraph->m_dirty = false; + } + + m_net_code_to_subgraphs_map.clear(); + + for( auto subgraph : m_subgraphs ) + { + if( !subgraph->m_driver ) + continue; + + if( subgraph->m_dirty ) + { + subgraph->m_dirty = false; + } + + if( subgraph->m_driver->Connection( subgraph->m_sheet )->IsBus() ) + continue; + + int code = subgraph->m_driver->Connection( subgraph->m_sheet )->NetCode(); + m_net_code_to_subgraphs_map[ code ].push_back( subgraph ); + } + +#ifdef CONNECTIVITY_DEBUG + phase2.Stop(); + std::cout << "BuildConnectionGraph() " << phase2.msecs() << " ms" << std::endl; +#endif +} + + +int CONNECTION_GRAPH::assignNewNetCode( SCH_CONNECTION& aConnection ) +{ + int code; + + try + { + code = m_net_name_to_code_map.at( aConnection.Name() ); + } + catch( const std::out_of_range& oor ) + { + code = m_last_net_code++; + m_net_name_to_code_map[ aConnection.Name() ] = code; + } + + aConnection.SetNetCode( code ); + + return code; +} + + +std::shared_ptr CONNECTION_GRAPH::GetBusAlias( wxString aName ) +{ + try + { + return m_bus_alias_cache.at( aName ); + } + catch( const std::out_of_range& oor ) + { + return nullptr; + } +} + + +std::vector CONNECTION_GRAPH::GetBusesNeedingMigration() +{ + std::vector ret; + + for( auto it = m_subgraphs.begin(); it < m_subgraphs.end(); it++ ) + { + auto subgraph = *it; + + // Graph is supposed to be up-to-date before calling this + wxASSERT( !subgraph->m_dirty ); + + if( !subgraph->m_driver ) + continue; + + auto sheet = subgraph->m_sheet; + auto connection = subgraph->m_driver->Connection( sheet ); + + if( !connection->IsBus() ) + continue; + + if( subgraph->GetBusLabels().size() > 1 ) + ret.push_back( subgraph ); + } + + return ret; +} + + +bool CONNECTION_GRAPH::UsesNewBusFeatures() const +{ + for( auto it = m_subgraphs.begin(); it < m_subgraphs.end(); it++ ) + { + auto subgraph = *it; + + + if( !subgraph->m_driver ) + continue; + + auto sheet = subgraph->m_sheet; + auto connection = subgraph->m_driver->Connection( sheet ); + + if( !connection->IsBus() ) + continue; + + if( connection->Type() == CONNECTION_BUS_GROUP ) + return true; + } + + return false; +} + + +int CONNECTION_GRAPH::RunERC( const ERC_SETTINGS& aSettings, bool aCreateMarkers ) +{ + int error_count = 0; + + for( auto it = m_subgraphs.begin(); it < m_subgraphs.end(); it++ ) + { + auto subgraph = *it; + + // Graph is supposed to be up-to-date before calling RunERC() + wxASSERT( !subgraph->m_dirty ); + + /** + * NOTE: + * + * We could check that labels attached to bus subgraphs follow the + * proper format (i.e. actually define a bus). + * + * This check doesn't need to be here right now because labels + * won't actually be connected to bus wires if they aren't in the right + * format due to their TestDanglingEnds() implementation. + */ + + if( aSettings.check_bus_driver_conflicts && + !subgraph->ResolveDrivers( aCreateMarkers ) ) + error_count++; + + if( aSettings.check_bus_to_net_conflicts && + !ercCheckBusToNetConflicts( subgraph, aCreateMarkers ) ) + error_count++; + + if( aSettings.check_bus_entry_conflicts && + !ercCheckBusToBusEntryConflicts( subgraph, aCreateMarkers ) ) + error_count++; + + if( aSettings.check_bus_to_bus_conflicts && + !ercCheckBusToBusConflicts( subgraph, aCreateMarkers ) ) + error_count++; + + // The following checks are always performed since they don't currently + // have an option exposed to the user + + if( !ercCheckNoConnects( subgraph, aCreateMarkers ) ) + error_count++; + + if( !ercCheckLabels( subgraph, aCreateMarkers ) ) + error_count++; + } + + return error_count; +} + + +bool CONNECTION_GRAPH::ercCheckBusToNetConflicts( CONNECTION_SUBGRAPH* aSubgraph, + bool aCreateMarkers ) +{ + wxString msg; + auto sheet = aSubgraph->m_sheet; + auto screen = sheet.LastScreen(); + + SCH_ITEM* net_item = nullptr; + SCH_ITEM* bus_item = nullptr; + SCH_CONNECTION conn; + + for( auto item : aSubgraph->m_items ) + { + switch( item->Type() ) + { + case SCH_LINE_T: + { + if( item->GetLayer() == LAYER_BUS ) + bus_item = ( !bus_item ) ? item : bus_item; + else + net_item = ( !net_item ) ? item : net_item; + break; + } + + case SCH_GLOBAL_LABEL_T: + case SCH_SHEET_PIN_T: + case SCH_HIERARCHICAL_LABEL_T: + { + auto text = static_cast( item )->GetText(); + conn.ConfigureFromLabel( text ); + + if( conn.IsBus() ) + bus_item = ( !bus_item ) ? item : bus_item; + else + net_item = ( !net_item ) ? item : net_item; + break; + } + + default: + break; + } + } + + if( net_item && bus_item ) + { + if( aCreateMarkers ) + { + msg.Printf( _( "%s and %s are graphically connected but cannot" + " electrically connect because one is a bus and" + " the other is a net." ), + bus_item->GetSelectMenuText( m_frame->GetUserUnits() ), + net_item->GetSelectMenuText( m_frame->GetUserUnits() ) ); + + auto marker = new SCH_MARKER(); + marker->SetTimeStamp( GetNewTimeStamp() ); + marker->SetMarkerType( MARKER_BASE::MARKER_ERC ); + marker->SetErrorLevel( MARKER_BASE::MARKER_SEVERITY_ERROR ); + marker->SetData( ERCE_BUS_TO_NET_CONFLICT, + net_item->GetPosition(), msg, + bus_item->GetPosition() ); + + screen->Append( marker ); + } + + return false; + } + + return true; +} + + +bool CONNECTION_GRAPH::ercCheckBusToBusConflicts( CONNECTION_SUBGRAPH* aSubgraph, + bool aCreateMarkers ) +{ + wxString msg; + auto sheet = aSubgraph->m_sheet; + auto screen = sheet.LastScreen(); + + SCH_ITEM* label = nullptr; + SCH_ITEM* port = nullptr; + + for( auto item : aSubgraph->m_items ) + { + switch( item->Type() ) + { + case SCH_TEXT_T: + case SCH_GLOBAL_LABEL_T: + { + if( !label && item->Connection( sheet )->IsBus() ) + label = item; + break; + } + + case SCH_SHEET_PIN_T: + case SCH_HIERARCHICAL_LABEL_T: + { + if( !port && item->Connection( sheet )->IsBus() ) + port = item; + break; + } + + default: + break; + } + } + + if( label && port ) + { + bool match = false; + + for( auto member : label->Connection( sheet )->Members() ) + { + for( auto test : port->Connection( sheet )->Members() ) + { + if( test != member && member->Name() == test->Name() ) + { + match = true; + break; + } + } + + if( match ) + break; + } + + if( !match ) + { + if( aCreateMarkers ) + { + msg.Printf( _( "%s and %s are graphically connected but do " + "not share any bus members" ), + label->GetSelectMenuText( m_frame->GetUserUnits() ), + port->GetSelectMenuText( m_frame->GetUserUnits() ) ); + + auto marker = new SCH_MARKER(); + marker->SetTimeStamp( GetNewTimeStamp() ); + marker->SetMarkerType( MARKER_BASE::MARKER_ERC ); + marker->SetErrorLevel( MARKER_BASE::MARKER_SEVERITY_ERROR ); + marker->SetData( ERCE_BUS_TO_BUS_CONFLICT, + label->GetPosition(), msg, + port->GetPosition() ); + + screen->Append( marker ); + } + + return false; + } + } + + return true; +} + + +bool CONNECTION_GRAPH::ercCheckBusToBusEntryConflicts( CONNECTION_SUBGRAPH* aSubgraph, + bool aCreateMarkers ) +{ + wxString msg; + bool conflict = false; + auto sheet = aSubgraph->m_sheet; + auto screen = sheet.LastScreen(); + + SCH_BUS_WIRE_ENTRY* bus_entry = nullptr; + SCH_ITEM* bus_wire = nullptr; + + for( auto item : aSubgraph->m_items ) + { + switch( item->Type() ) + { + case SCH_BUS_WIRE_ENTRY_T: + { + if( !bus_entry ) + bus_entry = static_cast( item ); + break; + } + + default: + break; + } + } + + if( bus_entry && bus_entry->m_connected_bus_item ) + { + bus_wire = bus_entry->m_connected_bus_item; + conflict = true; + + auto test_name = bus_entry->Connection( sheet )->Name(); + + for( auto member : bus_wire->Connection( sheet )->Members() ) + { + if( member->Type() == CONNECTION_BUS ) + { + for( const auto& sub_member : member->Members() ) + if( sub_member->Name() == test_name ) + conflict = false; + } + else if( member->Name() == test_name ) + { + conflict = false; + } + } + } + + if( conflict ) + { + if( aCreateMarkers ) + { + msg.Printf( _( "%s (%s) is connected to %s (%s) but is not a member of the bus" ), + bus_entry->GetSelectMenuText( m_frame->GetUserUnits() ), + bus_entry->Connection( sheet )->Name(), + bus_wire->GetSelectMenuText( m_frame->GetUserUnits() ), + bus_wire->Connection( sheet )->Name() ); + + auto marker = new SCH_MARKER(); + marker->SetTimeStamp( GetNewTimeStamp() ); + marker->SetMarkerType( MARKER_BASE::MARKER_ERC ); + marker->SetErrorLevel( MARKER_BASE::MARKER_SEVERITY_WARNING ); + marker->SetData( ERCE_BUS_ENTRY_CONFLICT, + bus_entry->GetPosition(), msg, + bus_entry->GetPosition() ); + + screen->Append( marker ); + } + + return false; + } + + return true; +} + + +// TODO(JE) Check sheet pins here too? +bool CONNECTION_GRAPH::ercCheckNoConnects( CONNECTION_SUBGRAPH* aSubgraph, + bool aCreateMarkers ) +{ + wxString msg; + auto sheet = aSubgraph->m_sheet; + auto screen = sheet.LastScreen(); + + if( aSubgraph->m_no_connect != nullptr ) + { + bool has_invalid_items = false; + SCH_PIN_CONNECTION* pin = nullptr; + std::vector invalid_items; + + // Any subgraph that contains both a pin and a no-connect should not + // contain any other connectable items. + + for( auto item : aSubgraph->m_items ) + { + switch( item->Type() ) + { + case SCH_PIN_CONNECTION_T: + pin = static_cast( item ); + break; + + case SCH_NO_CONNECT_T: + break; + + default: + has_invalid_items = true; + invalid_items.push_back( item ); + } + } + + // TODO: Should it be an error to have a NC item but no pin? + if( pin && has_invalid_items ) + { + auto pos = pin->GetPosition(); + msg.Printf( _( "Pin %s of component %s has a no-connect marker but is connected" ), + GetChars( pin->m_pin->GetName() ), + GetChars( pin->m_comp->GetRef( &aSubgraph->m_sheet ) ) ); + + auto marker = new SCH_MARKER(); + marker->SetTimeStamp( GetNewTimeStamp() ); + marker->SetMarkerType( MARKER_BASE::MARKER_ERC ); + marker->SetErrorLevel( MARKER_BASE::MARKER_SEVERITY_WARNING ); + marker->SetData( ERCE_NOCONNECT_CONNECTED, pos, msg, pos ); + + screen->Append( marker ); + + return false; + } + } + else + { + bool has_other_connections = false; + SCH_PIN_CONNECTION* pin = nullptr; + + // Any subgraph that lacks a no-connect and contains a pin should also + // contain at least one other connectable item. + + for( auto item : aSubgraph->m_items ) + { + switch( item->Type() ) + { + case SCH_PIN_CONNECTION_T: + if( !pin ) + pin = static_cast( item ); + else + has_other_connections = true; + break; + + default: + if( item->IsConnectable() ) + has_other_connections = true; + break; + } + } + + if( pin && !has_other_connections && + pin->m_pin->GetType() != PIN_NC ) + { + auto pos = pin->GetPosition(); + msg.Printf( _( "Pin %s of component %s is unconnected." ), + GetChars( pin->m_pin->GetName() ), + GetChars( pin->m_comp->GetRef( &aSubgraph->m_sheet ) ) ); + + auto marker = new SCH_MARKER(); + marker->SetTimeStamp( GetNewTimeStamp() ); + marker->SetMarkerType( MARKER_BASE::MARKER_ERC ); + marker->SetErrorLevel( MARKER_BASE::MARKER_SEVERITY_WARNING ); + marker->SetData( ERCE_PIN_NOT_CONNECTED, pos, msg, pos ); + + screen->Append( marker ); + + return false; + } + } + + return true; +} + + +bool CONNECTION_GRAPH::ercCheckLabels( CONNECTION_SUBGRAPH* aSubgraph, + bool aCreateMarkers ) +{ + wxString msg; + auto sheet = aSubgraph->m_sheet; + auto screen = sheet.LastScreen(); + + SCH_TEXT* text = nullptr; + bool has_other_connections = false; + + // Any subgraph that contains a label should also contain at least one other + // connectable item. + + for( auto item : aSubgraph->m_items ) + { + switch( item->Type() ) + { + case SCH_LABEL_T: + case SCH_GLOBAL_LABEL_T: + case SCH_HIERARCHICAL_LABEL_T: + text = static_cast( item ); + break; + + default: + if( item->IsConnectable() ) + has_other_connections = true; + break; + } + } + + if( text && !has_other_connections ) + { + auto pos = text->GetPosition(); + msg.Printf( _( "Label %s is unconnected." ), + GetChars( text->ShortenedShownText() ) ); + + auto marker = new SCH_MARKER(); + marker->SetTimeStamp( GetNewTimeStamp() ); + marker->SetMarkerType( MARKER_BASE::MARKER_ERC ); + marker->SetErrorLevel( MARKER_BASE::MARKER_SEVERITY_WARNING ); + marker->SetData( ERCE_LABEL_NOT_CONNECTED, pos, msg, pos ); + + screen->Append( marker ); + + return false; + } + + return true; +} diff --git a/eeschema/connection_graph.h b/eeschema/connection_graph.h new file mode 100644 index 0000000000..05eaaf90e3 --- /dev/null +++ b/eeschema/connection_graph.h @@ -0,0 +1,327 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2018 CERN + * @author Jon Evans + * + * 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, see . + */ + +#ifndef _CONNECTION_GRAPH_H +#define _CONNECTION_GRAPH_H + +#include +#include + +#include +#include +#include +#include + + +#ifdef DEBUG +// Uncomment this line to enable connectivity debugging features +// #define CONNECTIVITY_DEBUG +#endif + +class SCH_PIN_CONNECTION; + +class SCH_EDIT_FRAME; + + +/** + * A subgraph is a set of items that are "physically" connected in the schematic. + * + * For example, a label connected to a wire and so on. + * A net is composed of one or more subgraphs. + * + * A set of items that appears to be physically connected may actually be more + * than one subgraph, because some items don't connect electrically. + * + * For example, multiple bus wires can come together at a junction but have + * different labels on each branch. Each label+wire branch is its own subgraph. + * + */ +class CONNECTION_SUBGRAPH +{ +public: + CONNECTION_SUBGRAPH( SCH_EDIT_FRAME* aFrame ) : + m_dirty( false ), m_code( -1 ), m_multiple_power_ports( false ), + m_no_connect( nullptr ), m_driver( nullptr ), m_frame( aFrame ) + {} + /** + * Determines which potential driver should drive the subgraph. + * + * If multiple possible drivers exist, picks one according to the priority. + * If multiple "winners" exist, returns false and sets m_driver to nullptr. + * + * @param aCreateMarkers controls whether ERC markers should be added for conflicts + * @return true if m_driver was set, or false if a conflict occurred + */ + bool ResolveDrivers( bool aCreateMarkers = false ); + + /** + * Returns the fully-qualified net name for this subgraph (if one exists) + */ + wxString GetNetName(); + + /// Returns all the bus labels attached to this subgraph (if any) + std::vector GetBusLabels(); + + bool m_dirty; + + long m_code; + + /// True if this subgraph contains multiple power ports to join in one net + bool m_multiple_power_ports; + + /// No-connect item in graph, if any + SCH_ITEM* m_no_connect; + + std::vector m_items; + + std::vector m_drivers; + + SCH_ITEM* m_driver; + + SCH_SHEET_PATH m_sheet; + + // Needed for m_UserUnits for now; maybe refactor later + SCH_EDIT_FRAME* m_frame; + + /** + * This map stores pointers to other subgraphs on the same sheet as this one + * which should be connected to this one. + * + * For example, if this subgraph is part of the bus D[7..0] and there is + * another subgraph on this sheet with connection D7, this map will include + * a pointer to that subgraph under the key D7 (where the key comes from + * the m_members list of the SCH_CONNECTION that drives this subgraph) + */ + std::unordered_map< std::shared_ptr, + std::vector > m_neighbor_map; +}; + + +/** + * Calculates the connectivity of a schematic and generates netlists + */ +class CONNECTION_GRAPH +{ +public: + CONNECTION_GRAPH( SCH_EDIT_FRAME* aFrame) : + m_frame( aFrame ) + {} + + void Reset(); + + /** + * Updates the connection graph for the given list of sheets. + * + * @param aSheetList should be the whole schematic for now + */ + void Recalculate( SCH_SHEET_LIST aSheetList ); + + /** + * Updates the connectivity graph based on a single item + */ + void RebuildGraphForItem( SCH_ITEM* aItem ); + + /** + * Returns a bus alias pointer for the given name if it exists (from cache) + * + * CONNECTION_GRAPH caches these, they are owned by the SCH_SCREEN that + * the alias was defined on. The cache is only used to update the graph. + */ + std::shared_ptr GetBusAlias( wxString aName ); + + /** + * Determines which subgraphs have more than one conflicting bus label. + * + * @see DIALOG_MIGRATE_BUSES + * @return a list of subgraphs that need migration + */ + + std::vector GetBusesNeedingMigration(); + + /** + * Returns true if the graph makes use of any of the new bus features + * + * For quality control during rollout of new bus features: + * - Aliases + * - Bus groups + */ + bool UsesNewBusFeatures() const; + + /** + * Runs electrical rule checks on the connectivity graph. + * + * Precondition: graph is up-to-date + * + * @param aSettings is used to control which tests to run + * @param aCreateMarkers controls whether error markers are created + * @return the number of errors found + */ + int RunERC( const ERC_SETTINGS& aSettings, bool aCreateMarkers = true ); + + // TODO(JE) firm up API and move to private + std::map > m_net_code_to_subgraphs_map; + +private: + + std::vector m_items; + + std::vector m_subgraphs; + + std::vector m_invisible_power_pins; + + std::map> m_bus_alias_cache; + + std::map m_net_name_to_code_map; + + std::map m_bus_name_to_code_map; + + std::unordered_map m_subgraph_code_map; + + int m_last_net_code; + + int m_last_bus_code; + + int m_last_subgraph_code; + + std::mutex m_item_mutex; + + // Needed for m_UserUnits for now; maybe refactor later + SCH_EDIT_FRAME* m_frame; + + /** + * Updates the graphical connectivity between items (i.e. where they touch) + * The items passed in must be on the same sheet. + * + * In the first phase, all items in aItemList have their connections + * initialized for the given sheet (since they may have connections on more + * than one sheet, and each needs to be calculated individually). The + * graphical connection points for the item are added to a map that stores + * (x, y) -> [list of items]. + * + * Any item that is stored in the list of items that have a connection point + * at a given (x, y) location will eventually be electrically connected. + * This means that we can't store SCH_COMPONENTs in this map -- we must store + * a structure that links a specific pin on a component back to that + * component: a SCH_PIN_CONNECTION. This wrapper class is a convenience for + * linking a pin and component to a specific (x, y) point. + * + * In the second phase, we iterate over each value in the map, which is a + * vector of items that have overlapping connection points. After some + * checks to ensure that the items should actually connect, the items are + * linked together using ConnectedItems(). + * + * As a side effect, items are loaded into m_items for BuildConnectionGraph() + * + * @param aSheet is the path to the sheet of all items in the list + * @param aItemList is a list of items to consider + */ + void updateItemConnectivity( SCH_SHEET_PATH aSheet, + std::vector aItemList ); + + /** + * Generates the connection graph (after all item connectivity has been updated) + * + * In the first phase, the algorithm iterates over all items, and then over + * all items that are connected (graphically) to each item, placing them into + * CONNECTION_SUBGRAPHs. Items that can potentially drive connectivity (i.e. + * labels, pins, etc.) are added to the m_drivers vector of the subgraph. + * + * In the second phase, each subgraph is resolved. To resolve a subgraph, + * the driver is first selected by CONNECTION_SUBGRAPH::ResolveDrivers(), + * and then the connection for the chosen driver is propagated to all the + * other items in the subgraph. + */ + void buildConnectionGraph(); + + /** + * Helper to assign a new net code to a connection + * + * @return the assigned code + */ + int assignNewNetCode( SCH_CONNECTION& aConnection ); + + /** + * Checks one subgraph for conflicting connections between net and bus labels + * + * For example, a net wire connected to a bus port/pin, or vice versa + * + * @param aSubgraph is the subgraph to examine + * @param aCreateMarkers controls whether error markers are created + * @return true for no errors, false for errors + */ + bool ercCheckBusToNetConflicts( CONNECTION_SUBGRAPH* aSubgraph, + bool aCreateMarkers ); + + /** + * Checks one subgraph for conflicting connections between two bus items + * + * For example, a labeled bus wire connected to a hierarchical sheet pin + * where the labeled bus doesn't contain any of the same bus members as the + * sheet pin + * + * @param aSubgraph is the subgraph to examine + * @param aCreateMarkers controls whether error markers are created + * @return true for no errors, false for errors + */ + bool ercCheckBusToBusConflicts( CONNECTION_SUBGRAPH* aSubgraph, + bool aCreateMarkers ); + + /** + * Checks one subgraph for conflicting bus entry to bus connections + * + * For example, a wire with label "A0" is connected to a bus labeled "D[8..0]" + * + * Will also check for mistakes related to bus group names, for example: + * A bus group named "USB{DP DM}" should have bus entry connections like + * "USB.DP" but someone might accidentally just enter "DP" + * + * @param aSubgraph is the subgraph to examine + * @param aCreateMarkers controls whether error markers are created + * @return true for no errors, false for errors + */ + bool ercCheckBusToBusEntryConflicts( CONNECTION_SUBGRAPH* aSubgraph, + bool aCreateMarkers ); + + /** + * Checks one subgraph for proper presence or absence of no-connect symbols + * + * A pin with a no-connect symbol should not have any connections + * A pin without a no-connect symbol should have at least one connection + * + * @param aSubgraph is the subgraph to examine + * @param aCreateMarkers controls whether error markers are created + * @return true for no errors, false for errors + */ + bool ercCheckNoConnects( CONNECTION_SUBGRAPH* aSubgraph, + bool aCreateMarkers ); + + /** + * Checks one subgraph for proper connection of labels + * + * Labels should be connected to something + * + * @param aSubgraph is the subgraph to examine + * @param aCreateMarkers controls whether error markers are created + * @return true for no errors, false for errors + */ + bool ercCheckLabels( CONNECTION_SUBGRAPH* aSubgraph, bool aCreateMarkers ); +}; + +#endif diff --git a/eeschema/controle.cpp b/eeschema/controle.cpp index eab621a48e..aa96d083d8 100644 --- a/eeschema/controle.cpp +++ b/eeschema/controle.cpp @@ -228,9 +228,6 @@ SCH_ITEM* SCH_EDIT_FRAME::LocateItem( const wxPoint& aPosition, const KICAD_T aF if( item ) { - if( item->Type() == SCH_COMPONENT_T ) - ( (SCH_COMPONENT*) item )->SetCurrentSheetPath( &GetCurrentSheet() ); - MSG_PANEL_ITEMS items; item->GetMsgPanelInfo( m_UserUnits, items ); SetMsgPanel( items ); diff --git a/eeschema/dialogs/dialog_bus_manager.cpp b/eeschema/dialogs/dialog_bus_manager.cpp new file mode 100644 index 0000000000..4144d1bc3c --- /dev/null +++ b/eeschema/dialogs/dialog_bus_manager.cpp @@ -0,0 +1,517 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2018 CERN + * @author Jon Evans + * + * 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 . + */ + +#include + +#include +#include + +#include "dialog_bus_manager.h" + + +BEGIN_EVENT_TABLE( DIALOG_BUS_MANAGER, DIALOG_SHIM ) + EVT_BUTTON( wxID_OK, DIALOG_BUS_MANAGER::OnOkClick ) + EVT_BUTTON( wxID_CANCEL, DIALOG_BUS_MANAGER::OnCancelClick ) +END_EVENT_TABLE() + + +DIALOG_BUS_MANAGER::DIALOG_BUS_MANAGER( SCH_EDIT_FRAME* aParent ) + : DIALOG_SHIM( aParent, wxID_ANY, _( "Bus Definitions" ), + wxDefaultPosition, wxSize( 640, 480 ), + wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER ), + m_parent( aParent ) +{ + auto sizer = new wxBoxSizer( wxVERTICAL ); + auto buttons = new wxStdDialogButtonSizer(); + + buttons->AddButton( new wxButton( this, wxID_OK ) ); + buttons->AddButton( new wxButton( this, wxID_CANCEL ) ); + buttons->Realize(); + + auto top_container = new wxBoxSizer( wxHORIZONTAL ); + auto left_pane = new wxBoxSizer( wxVERTICAL ); + auto right_pane = new wxBoxSizer( wxVERTICAL ); + + // Left pane: alias list + auto lbl_aliases = new wxStaticText( this, wxID_ANY, _( "Bus Aliases" ), + wxDefaultPosition, wxDefaultSize, + wxALIGN_LEFT ); + + m_bus_list_view = new wxListView( this, wxID_ANY, wxDefaultPosition, + wxSize( 300, 300 ), wxLC_ALIGN_LEFT | + wxLC_NO_HEADER | wxLC_REPORT | + wxLC_SINGLE_SEL ); + m_bus_list_view->InsertColumn( 0, "" ); + + auto lbl_alias_edit = new wxStaticText( this, wxID_ANY, _( "Alias Name" ), + wxDefaultPosition, wxDefaultSize, + wxALIGN_LEFT ); + + m_bus_edit = new wxTextCtrl( this, wxID_ANY, wxEmptyString, + wxDefaultPosition, wxDefaultSize, wxTE_PROCESS_ENTER ); + + auto left_button_sizer = new wxBoxSizer( wxHORIZONTAL ); + + m_btn_add_bus = new wxButton( this, wxID_ANY, _( "Add" ) ); + m_btn_rename_bus = new wxButton( this, wxID_ANY, _( "Rename" ) ); + m_btn_remove_bus = new wxButton( this, wxID_ANY, _( "Remove" ) ); + + left_button_sizer->Add( m_btn_add_bus ); + left_button_sizer->Add( m_btn_rename_bus ); + left_button_sizer->Add( m_btn_remove_bus ); + + left_pane->Add( lbl_aliases, 0, wxEXPAND | wxALL, 5 ); + left_pane->Add( m_bus_list_view, 1, wxEXPAND | wxALL, 5 ); + left_pane->Add( lbl_alias_edit, 0, wxEXPAND | wxALL, 5 ); + left_pane->Add( m_bus_edit, 0, wxEXPAND | wxALL, 5 ); + left_pane->Add( left_button_sizer, 0, wxEXPAND | wxALL, 5 ); + + // Right pane: signal list + auto lbl_signals = new wxStaticText( this, wxID_ANY, _( "Alias Members" ), + wxDefaultPosition, wxDefaultSize, + wxALIGN_LEFT ); + + m_signal_list_view = new wxListView( this, wxID_ANY, wxDefaultPosition, + wxSize( 300, 300 ), wxLC_ALIGN_LEFT | + wxLC_NO_HEADER | wxLC_REPORT | + wxLC_SINGLE_SEL ); + m_signal_list_view->InsertColumn( 0, "" ); + + auto lbl_signal_edit = new wxStaticText( this, wxID_ANY, _( "Member Name" ), + wxDefaultPosition, wxDefaultSize, + wxALIGN_LEFT ); + + m_signal_edit = new wxTextCtrl( this, wxID_ANY, wxEmptyString, + wxDefaultPosition, wxDefaultSize, wxTE_PROCESS_ENTER ); + + auto right_button_sizer = new wxBoxSizer( wxHORIZONTAL ); + + m_btn_add_signal = new wxButton( this, wxID_ANY, _( "Add" ) ); + m_btn_rename_signal = new wxButton( this, wxID_ANY, _( "Rename" ) ); + m_btn_remove_signal = new wxButton( this, wxID_ANY, _( "Remove" ) ); + + right_button_sizer->Add( m_btn_add_signal ); + right_button_sizer->Add( m_btn_rename_signal ); + right_button_sizer->Add( m_btn_remove_signal ); + + right_pane->Add( lbl_signals, 0, wxEXPAND | wxALL, 5 ); + right_pane->Add( m_signal_list_view, 1, wxEXPAND | wxALL, 5 ); + right_pane->Add( lbl_signal_edit, 0, wxEXPAND | wxALL, 5 ); + right_pane->Add( m_signal_edit, 0, wxEXPAND | wxALL, 5 ); + right_pane->Add( right_button_sizer, 0, wxEXPAND | wxALL, 5 ); + + top_container->Add( left_pane, 1, wxEXPAND ); + top_container->Add( right_pane, 1, wxEXPAND ); + + sizer->Add( top_container, 1, wxEXPAND | wxALL, 5 ); + sizer->Add( buttons, 0, wxEXPAND | wxBOTTOM, 10 ); + SetSizer( sizer ); + + // Setup validators + + wxTextValidator validator; + validator.SetStyle( wxFILTER_EXCLUDE_CHAR_LIST ); + validator.SetCharExcludes( "\r\n\t " ); + m_bus_edit->SetValidator( validator ); + + // Allow spaces in the signal edit, so that you can type in a list of + // signals in the box and it can automatically split them when you add. + validator.SetCharExcludes( "\r\n\t" ); + m_signal_edit->SetValidator( validator ); + + // Setup events + + Bind( wxEVT_INIT_DIALOG, &DIALOG_BUS_MANAGER::OnInitDialog, this ); + m_bus_list_view->Connect( wxEVT_COMMAND_LIST_ITEM_DESELECTED, + wxListEventHandler( DIALOG_BUS_MANAGER::OnSelectBus ), NULL, this ); + m_bus_list_view->Connect( wxEVT_COMMAND_LIST_ITEM_SELECTED, + wxListEventHandler( DIALOG_BUS_MANAGER::OnSelectBus ), NULL, this ); + m_signal_list_view->Connect( wxEVT_COMMAND_LIST_ITEM_DESELECTED, + wxListEventHandler( DIALOG_BUS_MANAGER::OnSelectSignal ), NULL, this ); + m_signal_list_view->Connect( wxEVT_COMMAND_LIST_ITEM_SELECTED, + wxListEventHandler( DIALOG_BUS_MANAGER::OnSelectSignal ), NULL, this ); + + m_btn_add_bus->Connect( wxEVT_COMMAND_BUTTON_CLICKED, + wxCommandEventHandler( DIALOG_BUS_MANAGER::OnAddBus ), NULL, this ); + m_btn_rename_bus->Connect( wxEVT_COMMAND_BUTTON_CLICKED, + wxCommandEventHandler( DIALOG_BUS_MANAGER::OnRenameBus ), NULL, this ); + m_btn_remove_bus->Connect( wxEVT_COMMAND_BUTTON_CLICKED, + wxCommandEventHandler( DIALOG_BUS_MANAGER::OnRemoveBus ), NULL, this ); + m_signal_edit->Connect( wxEVT_TEXT_ENTER, + wxCommandEventHandler( DIALOG_BUS_MANAGER::OnAddSignal ), NULL, this ); + + m_btn_add_signal->Connect( wxEVT_COMMAND_BUTTON_CLICKED, + wxCommandEventHandler( DIALOG_BUS_MANAGER::OnAddSignal ), NULL, this ); + m_btn_rename_signal->Connect( wxEVT_COMMAND_BUTTON_CLICKED, + wxCommandEventHandler( DIALOG_BUS_MANAGER::OnRenameSignal ), NULL, this ); + m_btn_remove_signal->Connect( wxEVT_COMMAND_BUTTON_CLICKED, + wxCommandEventHandler( DIALOG_BUS_MANAGER::OnRemoveSignal ), NULL, this ); + m_bus_edit->Connect( wxEVT_TEXT_ENTER, + wxCommandEventHandler( DIALOG_BUS_MANAGER::OnAddBus ), NULL, this ); + + // Set initial UI state + + m_btn_rename_bus->Disable(); + m_btn_remove_bus->Disable(); + + m_btn_add_signal->Disable(); + m_btn_rename_signal->Disable(); + m_btn_remove_signal->Disable(); + + m_bus_edit->SetHint( _T( "Bus Alias Name" ) ); + m_signal_edit->SetHint( _T( "Net or Bus Name" ) ); + + FinishDialogSettings(); +} + + +void DIALOG_BUS_MANAGER::OnInitDialog( wxInitDialogEvent& aEvent ) +{ + TransferDataToWindow(); +} + + +bool DIALOG_BUS_MANAGER::TransferDataToWindow() +{ + m_aliases.clear(); + + SCH_SHEET_LIST aSheets( g_RootSheet ); + std::vector< std::shared_ptr< BUS_ALIAS > > original_aliases; + + // collect aliases from each open sheet + for( unsigned i = 0; i < aSheets.size(); i++ ) + { + auto sheet_aliases = aSheets[i].LastScreen()->GetBusAliases(); + original_aliases.insert( original_aliases.end(), sheet_aliases.begin(), + sheet_aliases.end() ); + } + + original_aliases.erase( std::unique( original_aliases.begin(), + original_aliases.end() ), + original_aliases.end() ); + + // clone into a temporary working set + int idx = 0; + for( auto alias : original_aliases ) + { + m_aliases.push_back( alias->Clone() ); + auto text = getAliasDisplayText( alias ); + m_bus_list_view->InsertItem( idx, text ); + m_bus_list_view->SetItemPtrData( idx, wxUIntPtr( m_aliases[idx].get() ) ); + idx++; + } + + m_bus_list_view->SetColumnWidth( 0, -1 ); + + return true; +} + + +void DIALOG_BUS_MANAGER::OnOkClick( wxCommandEvent& aEvent ) +{ + if( TransferDataFromWindow() ) + { + ( ( SCH_EDIT_FRAME* )GetParent() )->OnModify(); + EndModal( wxID_OK ); + } +} + + +void DIALOG_BUS_MANAGER::OnCancelClick( wxCommandEvent& aEvent ) +{ + EndModal( wxID_CANCEL ); +} + + +bool DIALOG_BUS_MANAGER::TransferDataFromWindow() +{ + // Since we have a clone of all the data, and it is from potentially + // multiple screens, the way this works is to rebuild each screen's aliases. + // A list of screens is stored here so that the screen's alias list is only + // cleared once. + + std::unordered_set< SCH_SCREEN* > cleared_list; + + for( auto alias : m_aliases ) + { + auto screen = alias->GetParent(); + + if( cleared_list.count( screen ) == 0 ) + { + screen->ClearBusAliases(); + cleared_list.insert( screen ); + } + + screen->AddBusAlias( alias ); + } + + return true; +} + + +void DIALOG_BUS_MANAGER::OnSelectBus( wxListEvent& event ) +{ + if( event.GetEventType() == wxEVT_COMMAND_LIST_ITEM_SELECTED ) + { + auto alias = m_aliases[ event.GetIndex() ]; + + if( m_active_alias != alias ) + { + m_active_alias = alias; + + m_bus_edit->ChangeValue( alias->GetName() ); + + m_btn_add_bus->Disable(); + m_btn_rename_bus->Enable(); + m_btn_remove_bus->Enable(); + + auto members = alias->Members(); + + // TODO(JE) Clear() seems to be clearing the hint, contrary to + // the wx documentation. + m_signal_edit->Clear(); + m_signal_list_view->DeleteAllItems(); + + for( unsigned i = 0; i < members.size(); i++ ) + { + m_signal_list_view->InsertItem( i, members[i] ); + } + + m_signal_list_view->SetColumnWidth( 0, -1 ); + + m_btn_add_signal->Enable(); + m_btn_rename_signal->Disable(); + m_btn_remove_signal->Disable(); + } + } + else + { + m_active_alias = NULL; + m_bus_edit->Clear(); + m_signal_edit->Clear(); + m_signal_list_view->DeleteAllItems(); + + m_btn_add_bus->Enable(); + m_btn_rename_bus->Disable(); + m_btn_remove_bus->Disable(); + + m_btn_add_signal->Disable(); + m_btn_rename_signal->Disable(); + m_btn_remove_signal->Disable(); + } +} + + +void DIALOG_BUS_MANAGER::OnSelectSignal( wxListEvent& event ) +{ + if( event.GetEventType() == wxEVT_COMMAND_LIST_ITEM_SELECTED ) + { + m_signal_edit->ChangeValue( event.GetText() ); + m_btn_rename_signal->Enable(); + m_btn_remove_signal->Enable(); + } + else + { + m_signal_edit->Clear(); + m_btn_rename_signal->Disable(); + m_btn_remove_signal->Disable(); + } +} + + +void DIALOG_BUS_MANAGER::OnAddBus( wxCommandEvent& aEvent ) +{ + // If there is an active alias, then check that the user actually + // changed the text in the edit box (we can't have duplicate aliases) + + auto new_name = m_bus_edit->GetValue(); + + if( new_name.Length() == 0 ) + { + return; + } + + for( auto alias : m_aliases ) + { + if( alias->GetName() == new_name ) + { + // TODO(JE) display error? + return; + } + } + + if( !m_active_alias || + ( m_active_alias && m_active_alias->GetName().Cmp( new_name ) ) ) + { + // The values are different; create a new alias + auto alias = std::make_shared(); + alias->SetName( new_name ); + + // New aliases get stored on the currently visible sheet + alias->SetParent( static_cast( GetParent() )->GetScreen() ); + auto text = getAliasDisplayText( alias ); + + m_aliases.push_back( alias ); + long idx = m_bus_list_view->InsertItem( m_aliases.size() - 1, text ); + m_bus_list_view->SetColumnWidth( 0, -1 ); + m_bus_list_view->Select( idx ); + m_bus_edit->Clear(); + } + else + { + // TODO(JE) Check about desired result here. + // Maybe warn the user? Or just do nothing + } +} + + +void DIALOG_BUS_MANAGER::OnRenameBus( wxCommandEvent& aEvent ) +{ + // We should only get here if there is an active alias + wxASSERT( m_active_alias ); + + m_active_alias->SetName( m_bus_edit->GetValue() ); + long idx = m_bus_list_view->FindItem( -1, wxUIntPtr( m_active_alias.get() ) ); + + wxASSERT( idx >= 0 ); + + m_bus_list_view->SetItemText( idx, getAliasDisplayText( m_active_alias ) ); +} + + +void DIALOG_BUS_MANAGER::OnRemoveBus( wxCommandEvent& aEvent ) +{ + // We should only get here if there is an active alias + wxASSERT( m_active_alias ); + long i = m_bus_list_view->GetFirstSelected(); + wxASSERT( m_active_alias == m_aliases[ i ] ); + + m_bus_list_view->DeleteItem( i ); + m_aliases.erase( m_aliases.begin() + i ); + m_bus_edit->Clear(); + + m_active_alias = NULL; + + auto evt = wxListEvent( wxEVT_COMMAND_LIST_ITEM_DESELECTED ); + OnSelectBus( evt ); +} + + +void DIALOG_BUS_MANAGER::OnAddSignal( wxCommandEvent& aEvent ) +{ + auto name_list = m_signal_edit->GetValue(); + + if( !m_active_alias || name_list.Length() == 0 ) + { + return; + } + + // String collecting net names that were not added to the bus + wxString notAdded; + + // Parse a space-separated list and add each one + wxStringTokenizer tok( name_list, " " ); + while( tok.HasMoreTokens() ) + { + auto name = tok.GetNextToken(); + + if( !m_active_alias->Contains( name ) ) + { + m_active_alias->AddMember( name ); + + long idx = m_signal_list_view->InsertItem( + m_active_alias->GetMemberCount() - 1, name ); + + m_signal_list_view->SetColumnWidth( 0, -1 ); + m_signal_list_view->Select( idx ); + } + else + { + // Some of the requested net names were not added to the list, so keep them for editing + notAdded = notAdded.IsEmpty() ? name : notAdded + " " + name; + } + } + + m_signal_edit->SetValue( notAdded ); + m_signal_edit->SetInsertionPointEnd(); +} + + +void DIALOG_BUS_MANAGER::OnRenameSignal( wxCommandEvent& aEvent ) +{ + // We should only get here if there is an active alias + wxASSERT( m_active_alias ); + + auto new_name = m_signal_edit->GetValue(); + long idx = m_signal_list_view->GetFirstSelected(); + + wxASSERT( idx >= 0 ); + + auto old_name = m_active_alias->Members()[ idx ]; + + // User could have typed a space here, so check first + if( new_name.Find( " " ) != wxNOT_FOUND ) + { + // TODO(JE) error feedback + m_signal_edit->ChangeValue( old_name ); + return; + } + + m_active_alias->Members()[ idx ] = new_name; + m_signal_list_view->SetItemText( idx, new_name ); + m_signal_list_view->SetColumnWidth( 0, -1 ); +} + + +void DIALOG_BUS_MANAGER::OnRemoveSignal( wxCommandEvent& aEvent ) +{ + // We should only get here if there is an active alias + wxASSERT( m_active_alias ); + + long idx = m_signal_list_view->GetFirstSelected(); + + wxASSERT( idx >= 0 ); + + m_active_alias->Members().erase( m_active_alias->Members().begin() + idx ); + + m_signal_list_view->DeleteItem( idx ); + m_signal_edit->Clear(); + m_btn_rename_signal->Disable(); + m_btn_remove_signal->Disable(); +} + + +wxString DIALOG_BUS_MANAGER::getAliasDisplayText( std::shared_ptr< BUS_ALIAS > aAlias ) +{ + wxString name = aAlias->GetName(); + wxFileName sheet_name( aAlias->GetParent()->GetFileName() ); + + name += _T( " (" ) + sheet_name.GetFullName() + _T( ")" ); + + return name; +} + + +// see invoke_sch_dialog.h +void InvokeDialogBusManager( SCH_EDIT_FRAME* aCaller ) +{ + DIALOG_BUS_MANAGER dlg( aCaller ); + dlg.ShowModal(); +} diff --git a/eeschema/dialogs/dialog_bus_manager.h b/eeschema/dialogs/dialog_bus_manager.h new file mode 100644 index 0000000000..4f7bdbb543 --- /dev/null +++ b/eeschema/dialogs/dialog_bus_manager.h @@ -0,0 +1,90 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2018 CERN + * @author Jon Evans + * + * 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 . + */ + +#ifndef _DIALOG_BUS_MANAGER_H_ +#define _DIALOG_BUS_MANAGER_H_ + +#include "dialog_shim.h" + +#include +#include + +#include + +class DIALOG_BUS_MANAGER : public DIALOG_SHIM +{ +public: + DIALOG_BUS_MANAGER( SCH_EDIT_FRAME* aParent ); + + ~DIALOG_BUS_MANAGER() {} + + bool TransferDataFromWindow() override; + + bool TransferDataToWindow() override; + + void OnAddBus( wxCommandEvent& aEvent ); + void OnRenameBus( wxCommandEvent& aEvent ); + void OnRemoveBus( wxCommandEvent& aEvent ); + + void OnAddSignal( wxCommandEvent& aEvent ); + void OnRenameSignal( wxCommandEvent& aEvent ); + void OnRemoveSignal( wxCommandEvent& aEvent ); + +protected: + + void OnInitDialog( wxInitDialogEvent& aEvent ); + + void OnSelectBus( wxListEvent& event ); + + void OnSelectSignal( wxListEvent& event ); + + SCH_EDIT_FRAME* m_parent; + + wxListView* m_bus_list_view; + wxListView* m_signal_list_view; + wxTextCtrl* m_bus_edit; + wxTextCtrl* m_signal_edit; + + wxButton* m_btn_add_bus; + wxButton* m_btn_rename_bus; + wxButton* m_btn_remove_bus; + + wxButton* m_btn_add_signal; + wxButton* m_btn_rename_signal; + wxButton* m_btn_remove_signal; + +private: + virtual void OnOkClick( wxCommandEvent& aEvent ); + + virtual void OnCancelClick( wxCommandEvent& aEvent ); + + wxString getAliasDisplayText( std::shared_ptr< BUS_ALIAS > aAlias ); + + std::vector< std::shared_ptr< BUS_ALIAS > > m_aliases; + + std::shared_ptr< BUS_ALIAS > m_active_alias; + + DECLARE_EVENT_TABLE() +}; + + +#endif + +// _DIALOG_BUS_MANAGER_H_ diff --git a/eeschema/dialogs/dialog_edit_label.cpp b/eeschema/dialogs/dialog_edit_label.cpp index 3e68a279ae..8bf27b3e0e 100644 --- a/eeschema/dialogs/dialog_edit_label.cpp +++ b/eeschema/dialogs/dialog_edit_label.cpp @@ -29,10 +29,9 @@ */ #include -#include -#include #include #include +#include #include #include @@ -87,6 +86,7 @@ private: wxWindow* m_activeTextCtrl; wxTextEntry* m_activeTextEntry; UNIT_BINDER m_textSize; + REGEX_VALIDATOR m_netNameValidator; }; @@ -108,7 +108,9 @@ const int MAX_TEXTSIZE = INT_MAX; DIALOG_LABEL_EDITOR::DIALOG_LABEL_EDITOR( SCH_EDIT_FRAME* aParent, SCH_TEXT* aTextItem ) : DIALOG_LABEL_EDITOR_BASE( aParent ), - m_textSize( aParent, m_textSizeLabel, m_textSizeCtrl, m_textSizeUnits, false ) + m_textSize( aParent, m_textSizeLabel, m_textSizeCtrl, m_textSizeUnits, false ), + // first part matches single nets and bus vector, the second part matches complex buses + m_netNameValidator( "([^/ ]+)|({[^/]+})" ) { m_Parent = aParent; m_CurrentText = aTextItem; @@ -151,8 +153,9 @@ DIALOG_LABEL_EDITOR::DIALOG_LABEL_EDITOR( SCH_EDIT_FRAME* aParent, SCH_TEXT* aTe SetInitialFocus( m_activeTextCtrl ); + // Enable validator for net names if( m_CurrentText->Type() != SCH_TEXT_T ) - ( (wxTextValidator*) m_activeTextCtrl->GetValidator() )->SetCharExcludes( wxT( " /" ) ); + m_activeTextCtrl->SetValidator( m_netNameValidator ); m_TextShape->Show( m_CurrentText->Type() == SCH_GLOBAL_LABEL_T || m_CurrentText->Type() == SCH_HIERARCHICAL_LABEL_T ); diff --git a/eeschema/dialogs/dialog_edit_label_base.cpp b/eeschema/dialogs/dialog_edit_label_base.cpp index b1585627a2..1f77fbf8b4 100644 --- a/eeschema/dialogs/dialog_edit_label_base.cpp +++ b/eeschema/dialogs/dialog_edit_label_base.cpp @@ -1,5 +1,5 @@ /////////////////////////////////////////////////////////////////////////// -// C++ code generated with wxFormBuilder (version Aug 4 2017) +// C++ code generated with wxFormBuilder (version Oct 17 2016) // http://www.wxformbuilder.org/ // // PLEASE DO "NOT" EDIT THIS FILE! @@ -31,8 +31,6 @@ DIALOG_LABEL_EDITOR_BASE::DIALOG_LABEL_EDITOR_BASE( wxWindow* parent, wxWindowID m_valueSingleLine = new wxTextCtrl( this, wxID_VALUESINGLE, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_PROCESS_ENTER|wxTE_RICH ); m_valueSingleLine->SetMinSize( wxSize( 360,-1 ) ); - m_valueSingleLine->SetValidator( wxTextValidator( wxFILTER_EXCLUDE_CHAR_LIST, &m_labelText ) ); - m_textEntrySizer->Add( m_valueSingleLine, 0, wxEXPAND|wxALIGN_CENTER_VERTICAL|wxBOTTOM|wxLEFT, 3 ); m_labelMultiLine = new wxStaticText( this, wxID_ANY, _("Text:"), wxDefaultPosition, wxDefaultSize, 0 ); diff --git a/eeschema/dialogs/dialog_edit_label_base.fbp b/eeschema/dialogs/dialog_edit_label_base.fbp index 6e8bb77a3d..f718adde2a 100644 --- a/eeschema/dialogs/dialog_edit_label_base.fbp +++ b/eeschema/dialogs/dialog_edit_label_base.fbp @@ -14,7 +14,6 @@ dialog_edit_label_base 1000 none - 1 dialog_edit_label_base @@ -247,10 +246,10 @@ 0 - wxString - wxFILTER_EXCLUDE_CHAR_LIST - wxTextValidator - m_labelText + + wxFILTER_NONE + wxDefaultValidator + @@ -606,8 +605,6 @@ - - diff --git a/eeschema/dialogs/dialog_edit_label_base.h b/eeschema/dialogs/dialog_edit_label_base.h index 719d6cecf3..5f5d1f080a 100644 --- a/eeschema/dialogs/dialog_edit_label_base.h +++ b/eeschema/dialogs/dialog_edit_label_base.h @@ -1,5 +1,5 @@ /////////////////////////////////////////////////////////////////////////// -// C++ code generated with wxFormBuilder (version Aug 4 2017) +// C++ code generated with wxFormBuilder (version Oct 17 2016) // http://www.wxformbuilder.org/ // // PLEASE DO "NOT" EDIT THIS FILE! @@ -21,8 +21,8 @@ class DIALOG_SHIM; #include #include #include -#include #include +#include #include #include #include @@ -67,7 +67,6 @@ class DIALOG_LABEL_EDITOR_BASE : public DIALOG_SHIM public: - wxString m_labelText; wxString m_comboText; DIALOG_LABEL_EDITOR_BASE( wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = _("Text Editor"), const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxSize( -1,-1 ), long style = wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER ); diff --git a/eeschema/dialogs/dialog_erc.cpp b/eeschema/dialogs/dialog_erc.cpp index 26f83b4c8f..654ba44010 100644 --- a/eeschema/dialogs/dialog_erc.cpp +++ b/eeschema/dialogs/dialog_erc.cpp @@ -48,6 +48,7 @@ #include #include #include +#include #include #include @@ -58,10 +59,7 @@ extern int DiagErc[PINTYPE_COUNT][PINTYPE_COUNT]; extern int DefaultDiagErc[PINTYPE_COUNT][PINTYPE_COUNT]; -bool DIALOG_ERC::m_writeErcFile = false; // saved only for the current session -bool DIALOG_ERC::m_TestSimilarLabels = true; // Save in project config bool DIALOG_ERC::m_diagErcTableInit = false; // saved only for the current session -bool DIALOG_ERC::m_tstUniqueGlobalLabels = true; // saved only for the current session // Control identifiers for events #define ID_MATRIX_0 1800 @@ -102,8 +100,13 @@ DIALOG_ERC::DIALOG_ERC( SCH_EDIT_FRAME* parent ) : DIALOG_ERC::~DIALOG_ERC() { - m_TestSimilarLabels = m_cbTestSimilarLabels->GetValue(); - m_tstUniqueGlobalLabels = m_cbTestUniqueGlbLabels->GetValue(); + transferControlsToSettings(); + + if( m_settings != m_parent->GetErcSettings() ) + { + m_parent->UpdateErcSettings( m_settings ); + m_parent->SaveProjectSettings( false ); + } } @@ -117,9 +120,8 @@ void DIALOG_ERC::Init() m_buttonList[ii][jj] = NULL; } - m_WriteResultOpt->SetValue( m_writeErcFile ); - m_cbTestSimilarLabels->SetValue( m_TestSimilarLabels ); - m_cbTestUniqueGlbLabels->SetValue( m_tstUniqueGlobalLabels ); + m_settings = m_parent->GetErcSettings(); + transferSettingsToControls(); SCH_SCREENS screens; updateMarkerCounts( &screens ); @@ -131,6 +133,30 @@ void DIALOG_ERC::Init() } +void DIALOG_ERC::transferSettingsToControls() +{ + m_WriteResultOpt->SetValue( m_settings.write_erc_file ); + m_cbTestSimilarLabels->SetValue( m_settings.check_similar_labels ); + m_cbTestUniqueGlbLabels->SetValue( m_settings.check_unique_global_labels ); + m_cbCheckBusDriverConflicts->SetValue( m_settings.check_bus_driver_conflicts ); + m_cbCheckBusEntries->SetValue( m_settings.check_bus_entry_conflicts ); + m_cbCheckBusToBusConflicts->SetValue( m_settings.check_bus_to_bus_conflicts ); + m_cbCheckBusToNetConflicts->SetValue( m_settings.check_bus_to_net_conflicts ); +} + + +void DIALOG_ERC::transferControlsToSettings() +{ + m_settings.write_erc_file = m_WriteResultOpt->GetValue(); + m_settings.check_similar_labels = m_cbTestSimilarLabels->GetValue(); + m_settings.check_unique_global_labels = m_cbTestUniqueGlbLabels->GetValue(); + m_settings.check_bus_driver_conflicts = m_cbCheckBusDriverConflicts->GetValue(); + m_settings.check_bus_entry_conflicts = m_cbCheckBusEntries->GetValue(); + m_settings.check_bus_to_bus_conflicts = m_cbCheckBusToBusConflicts->GetValue(); + m_settings.check_bus_to_net_conflicts = m_cbCheckBusToNetConflicts->GetValue(); +} + + void DIALOG_ERC::updateMarkerCounts( SCH_SCREENS *screens ) { int markers = screens->GetMarkerCount( MARKER_BASE::MARKER_ERC, @@ -436,10 +462,8 @@ void DIALOG_ERC::ResetDefaultERCDiag( wxCommandEvent& event ) { memcpy( DiagErc, DefaultDiagErc, sizeof( DiagErc ) ); ReBuildMatrixPanel(); - m_TestSimilarLabels = true; - m_cbTestSimilarLabels->SetValue( m_TestSimilarLabels ); - m_tstUniqueGlobalLabels = true; - m_cbTestUniqueGlbLabels->SetValue( m_tstUniqueGlobalLabels ); + m_settings.LoadDefaults(); + transferSettingsToControls(); } @@ -483,9 +507,7 @@ void DIALOG_ERC::TestErc( REPORTER& aReporter ) { wxFileName fn; - m_writeErcFile = m_WriteResultOpt->GetValue(); - m_TestSimilarLabels = m_cbTestSimilarLabels->GetValue(); - m_tstUniqueGlobalLabels = m_cbTestUniqueGlbLabels->GetValue(); + transferControlsToSettings(); // Build the whole sheet list in hierarchy (sheet, not screen) SCH_SHEET_LIST sheets( g_RootSheet ); @@ -509,6 +531,12 @@ void DIALOG_ERC::TestErc( REPORTER& aReporter ) */ TestDuplicateSheetNames( true ); + TestConflictingBusAliases(); + + // The connection graph has a whole set of ERC checks it can run + m_parent->RecalculateConnections(); + g_ConnectionGraph->RunERC( m_settings ); + /* Test is all units of each multiunit component have the same footprint assigned. */ TestMultiunitFootprints( sheets ); @@ -518,9 +546,7 @@ void DIALOG_ERC::TestErc( REPORTER& aReporter ) // Reset the connection type indicator objectsConnectedList->ResetConnectionsType(); - unsigned lastItemIdx; - unsigned nextItemIdx = lastItemIdx = 0; - int MinConn = NOC; + unsigned lastItemIdx = 0; /* Check that a pin appears in only one net. This check is necessary * because multi-unit components that have shared pins can be wired to @@ -547,13 +573,6 @@ void DIALOG_ERC::TestErc( REPORTER& aReporter ) wxASSERT_MSG( lastNet <= net, wxT( "Netlist not correctly ordered" ) ); - if( lastNet != net ) - { - // New net found: - MinConn = NOC; - nextItemIdx = itemIdx; - } - switch( item->m_Type ) { // These items do not create erc problems @@ -567,30 +586,7 @@ void DIALOG_ERC::TestErc( REPORTER& aReporter ) case NET_GLOBBUSLABELMEMBER: break; - case NET_HIERLABEL: - case NET_HIERBUSLABELMEMBER: - case NET_SHEETLABEL: - case NET_SHEETBUSLABELMEMBER: - // ERC problems when pin sheets do not match hierarchical labels. - // Each pin sheet must match a hierarchical label - // Each hierarchical label must match a pin sheet - objectsConnectedList->TestforNonOrphanLabel( itemIdx, nextItemIdx ); - break; - case NET_GLOBLABEL: - if( m_tstUniqueGlobalLabels ) - objectsConnectedList->TestforNonOrphanLabel( itemIdx, nextItemIdx ); - break; - - case NET_NOCONNECT: - - // ERC problems when a noconnect symbol is connected to more than one pin. - MinConn = NET_NC; - - if( objectsConnectedList->CountPinsInNet( nextItemIdx ) > 1 ) - Diagnose( item, NULL, MinConn, UNC ); - - break; - + // TODO(JE) Port this to the new system case NET_PIN: { // Check if this pin has appeared before on a different net @@ -619,10 +615,13 @@ void DIALOG_ERC::TestErc( REPORTER& aReporter ) } } + // TODO(JE) Remove this if new system is finished // Look for ERC problems between pins: - TestOthersItems( objectsConnectedList.get(), itemIdx, nextItemIdx, &MinConn ); + //TestOthersItems( objectsConnectedList.get(), itemIdx, nextItemIdx, &MinConn ); break; } + default: + break; } lastItemIdx = itemIdx; @@ -630,7 +629,7 @@ void DIALOG_ERC::TestErc( REPORTER& aReporter ) // Test similar labels (i;e. labels which are identical when // using case insensitive comparisons) - if( m_TestSimilarLabels ) + if( m_settings.check_similar_labels ) objectsConnectedList->TestforSimilarLabels(); // Displays global results: @@ -653,7 +652,7 @@ void DIALOG_ERC::TestErc( REPORTER& aReporter ) // Display message aReporter.ReportTail( _( "Finished" ), REPORTER::RPT_INFO ); - if( m_writeErcFile ) + if( m_settings.write_erc_file ) { fn = g_RootSheet->GetScreen()->GetFileName(); fn.SetExt( wxT( "erc" ) ); diff --git a/eeschema/dialogs/dialog_erc.h b/eeschema/dialogs/dialog_erc.h index b0c13352c2..a7736bfaf7 100644 --- a/eeschema/dialogs/dialog_erc.h +++ b/eeschema/dialogs/dialog_erc.h @@ -30,6 +30,7 @@ #include // For PINTYPE_COUNT definition #include +#include #include "dialog_erc_listbox.h" // DIALOG_ERC class declaration @@ -43,12 +44,8 @@ private: wxBitmapButton* m_buttonList[PINTYPE_COUNT][PINTYPE_COUNT]; bool m_initialized; const SCH_MARKER* m_lastMarkerFound; - static bool m_writeErcFile; static bool m_diagErcTableInit; // go to true after DiagErc init - static bool m_tstUniqueGlobalLabels; - -public: - static bool m_TestSimilarLabels; + ERC_SETTINGS m_settings; public: DIALOG_ERC( SCH_EDIT_FRAME* parent ); @@ -79,6 +76,8 @@ private: void ReBuildMatrixPanel(); void setDRCMatrixButtonState( wxBitmapButton *aButton, int aState ); void updateMarkerCounts( SCH_SCREENS *screens ); + void transferSettingsToControls(); + void transferControlsToSettings(); }; diff --git a/eeschema/dialogs/dialog_erc_base.cpp b/eeschema/dialogs/dialog_erc_base.cpp index 0d39fcad14..e896b5f030 100644 --- a/eeschema/dialogs/dialog_erc_base.cpp +++ b/eeschema/dialogs/dialog_erc_base.cpp @@ -1,5 +1,5 @@ /////////////////////////////////////////////////////////////////////////// -// C++ code generated with wxFormBuilder (version Jan 17 2019) +// C++ code generated with wxFormBuilder (version Feb 17 2019) // http://www.wxformbuilder.org/ // // PLEASE DO *NOT* EDIT THIS FILE! @@ -131,6 +131,24 @@ DIALOG_ERC_BASE::DIALOG_ERC_BASE( wxWindow* parent, wxWindowID id, const wxStrin m_panelMatrixSizer->Add( sbSizer3, 0, wxALL|wxEXPAND, 5 ); + wxStaticBoxSizer* sbSizer31; + sbSizer31 = new wxStaticBoxSizer( new wxStaticBox( m_PanelERCOptions, wxID_ANY, _("Bus Connections") ), wxVERTICAL ); + + m_cbCheckBusToNetConflicts = new wxCheckBox( sbSizer31->GetStaticBox(), wxID_ANY, _("Check that bus wires are not connected to hierarchical net pins and vice versa"), wxDefaultPosition, wxDefaultSize, 0 ); + sbSizer31->Add( m_cbCheckBusToNetConflicts, 0, wxALL, 5 ); + + m_cbCheckBusToBusConflicts = new wxCheckBox( sbSizer31->GetStaticBox(), wxID_ANY, _("Check that bus-to-bus connections have shared members"), wxDefaultPosition, wxDefaultSize, 0 ); + sbSizer31->Add( m_cbCheckBusToBusConflicts, 0, wxALL, 5 ); + + m_cbCheckBusEntries = new wxCheckBox( sbSizer31->GetStaticBox(), wxID_ANY, _("Check that nets are members of buses they graphically connect to"), wxDefaultPosition, wxDefaultSize, 0 ); + sbSizer31->Add( m_cbCheckBusEntries, 0, wxALL, 5 ); + + m_cbCheckBusDriverConflicts = new wxCheckBox( sbSizer31->GetStaticBox(), wxID_ANY, _("Check buses for conflicting drivers"), wxDefaultPosition, wxDefaultSize, 0 ); + sbSizer31->Add( m_cbCheckBusDriverConflicts, 0, wxALL, 5 ); + + + m_panelMatrixSizer->Add( sbSizer31, 0, wxALL|wxEXPAND, 5 ); + m_PanelERCOptions->SetSizer( m_panelMatrixSizer ); m_PanelERCOptions->Layout(); diff --git a/eeschema/dialogs/dialog_erc_base.fbp b/eeschema/dialogs/dialog_erc_base.fbp index fa6ef7b126..fcab33b323 100644 --- a/eeschema/dialogs/dialog_erc_base.fbp +++ b/eeschema/dialogs/dialog_erc_base.fbp @@ -1318,6 +1318,373 @@ + + 5 + wxALL|wxEXPAND + 0 + + wxID_ANY + Bus Connections + + sbSizer31 + wxVERTICAL + 1 + none + + + 5 + wxALL + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + Check that bus wires are not connected to hierarchical net pins and vice versa + + 0 + + + 0 + + 1 + m_cbCheckBusToNetConflicts + 1 + + + protected + 1 + + Resizable + 1 + + + + 0 + + + wxFILTER_NONE + wxDefaultValidator + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxALL + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + Check that bus-to-bus connections have shared members + + 0 + + + 0 + + 1 + m_cbCheckBusToBusConflicts + 1 + + + protected + 1 + + Resizable + 1 + + + + 0 + + + wxFILTER_NONE + wxDefaultValidator + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxALL + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + Check that nets are members of buses they graphically connect to + + 0 + + + 0 + + 1 + m_cbCheckBusEntries + 1 + + + protected + 1 + + Resizable + 1 + + + + 0 + + + wxFILTER_NONE + wxDefaultValidator + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxALL + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + Check buses for conflicting drivers + + 0 + + + 0 + + 1 + m_cbCheckBusDriverConflicts + 1 + + + protected + 1 + + Resizable + 1 + + + + 0 + + + wxFILTER_NONE + wxDefaultValidator + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/eeschema/dialogs/dialog_erc_base.h b/eeschema/dialogs/dialog_erc_base.h index 1620170e25..e98944f574 100644 --- a/eeschema/dialogs/dialog_erc_base.h +++ b/eeschema/dialogs/dialog_erc_base.h @@ -1,5 +1,5 @@ /////////////////////////////////////////////////////////////////////////// -// C++ code generated with wxFormBuilder (version Jan 17 2019) +// C++ code generated with wxFormBuilder (version Feb 17 2019) // http://www.wxformbuilder.org/ // // PLEASE DO *NOT* EDIT THIS FILE! @@ -65,6 +65,10 @@ class DIALOG_ERC_BASE : public DIALOG_SHIM wxCheckBox* m_cbTestUniqueGlbLabels; wxPanel* m_matrixPanel; wxButton* m_ResetOptButton; + wxCheckBox* m_cbCheckBusToNetConflicts; + wxCheckBox* m_cbCheckBusToBusConflicts; + wxCheckBox* m_cbCheckBusEntries; + wxCheckBox* m_cbCheckBusDriverConflicts; wxButton* m_buttondelmarkers; wxStdDialogButtonSizer* m_sdbSizer1; wxButton* m_sdbSizer1OK; diff --git a/eeschema/dialogs/dialog_migrate_buses.cpp b/eeschema/dialogs/dialog_migrate_buses.cpp new file mode 100644 index 0000000000..c6d63f65af --- /dev/null +++ b/eeschema/dialogs/dialog_migrate_buses.cpp @@ -0,0 +1,229 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2018 CERN + * @author Jon Evans + * + * 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, see . + */ + +#include +#include + +#include + +/** + * Migrates buses using legacy multi-label joining behavior. + * + * In KiCad verions before 6.0, you were allowed to place multiple labels + * on a given bus subgraph, and that would have the effect of making those + * bus descriptions equivalent according to the bus vector number. + * + * For example, if the labels PCA[0..15], ADR[0.7], and BUS[5..10] are all + * attached to the same subgraph, the intention is that there is connectivity + * between PCA0 and ADR0, between PCA10 and BUS10, and between PCA5, ADR5, + * and BUS5 (basically connect all the prefix names where the vector numbers + * line up). + * + * This is no longer allowed, because it doesn't map well onto the new + * bus groups feature and because it is confusing (the netlist will take on + * one of the possible names and it's impossible to control which one is + * used). + * + * This dialog identifies all of the subgraphs that have this behavior, + * and corrects them by determining a new name for the subgraph and removing + * all but one label. The name is determined by choosing a prefix and bus + * vector notation that can capture all the attached buses. In the above + * example, the result would need to have the vector notation [0..15] to + * capture all of the attached buses, and the name could be any of PCA, ADR, + * or BUS. We present a dialog to the user for them to select which name + * they want to use. + */ + + +DIALOG_MIGRATE_BUSES::DIALOG_MIGRATE_BUSES( SCH_EDIT_FRAME* aParent ) : + DIALOG_MIGRATE_BUSES_BASE( aParent ), + m_frame( aParent ) +{ + m_migration_list->Bind( wxEVT_LIST_ITEM_SELECTED, + &DIALOG_MIGRATE_BUSES::onItemSelected, this ); + + m_btn_accept->Bind( wxEVT_COMMAND_BUTTON_CLICKED, + &DIALOG_MIGRATE_BUSES::onAcceptClicked, this ); + + loadGraphData(); + updateUi(); + + m_frame->Zoom_Automatique( false ); +} + + +void DIALOG_MIGRATE_BUSES::loadGraphData() +{ + m_items.clear(); + auto subgraphs = g_ConnectionGraph->GetBusesNeedingMigration(); + + for( auto subgraph : subgraphs ) + { + BUS_MIGRATION_STATUS status; + + status.subgraph = subgraph; + status.approved = false; + + auto labels = subgraph->GetBusLabels(); + wxASSERT( labels.size() > 1 ); + + for( auto label : labels ) + status.labels.push_back( static_cast( label )->GetText() ); + + status.possible_labels = getProposedLabels( status.labels ); + m_items.push_back( status ); + } +} + + +void DIALOG_MIGRATE_BUSES::updateUi() +{ + m_migration_list->DeleteAllItems(); + + m_migration_list->InsertColumn( 0, _( "Sheet" ) ); + m_migration_list->InsertColumn( 1, _( "Conflicting Labels" ) ); + m_migration_list->InsertColumn( 2, _( "New Label" ) ); + m_migration_list->InsertColumn( 3, _( "Status" ) ); + + for( auto item : m_items ) + { + wxString old = item.labels[0]; + for( unsigned j = 1; j < item.labels.size(); j++ ) + old << ", " << item.labels[j]; + + auto i = m_migration_list->InsertItem( m_migration_list->GetItemCount(), + wxEmptyString ); + + m_migration_list->SetItem( i, 0, item.subgraph->m_sheet.PathHumanReadable() ); + m_migration_list->SetItem( i, 1, old ); + m_migration_list->SetItem( i, 2, item.possible_labels[0] ); + m_migration_list->SetItem( i, 3, "" ); + } + + m_migration_list->Select( 0 ); + m_migration_list->SetColumnWidth( 1, -1 ); +} + + +std::vector DIALOG_MIGRATE_BUSES::getProposedLabels( std::vector aLabelList ) +{ + int lowest_start = INT_MAX; + int highest_end = -1; + int widest_bus = -1; + + for( auto label : aLabelList ) + { + SCH_CONNECTION conn; + conn.ConfigureFromLabel( label ); + + int start = conn.VectorStart(); + int end = conn.VectorEnd(); + + if( start < lowest_start ) + lowest_start = start; + + if( end > highest_end ) + highest_end = end; + + if( end - start + 1 > widest_bus ) + widest_bus = end - start + 1; + } + + SCH_CONNECTION conn; + std::vector proposals; + + for( auto label : aLabelList ) + { + conn.ConfigureFromLabel( label ); + wxString proposal = conn.VectorPrefix(); + proposal << "[" << highest_end << ".." << lowest_start << "]"; + proposals.push_back( proposal ); + } + + return proposals; +} + + +void DIALOG_MIGRATE_BUSES::onItemSelected( wxListEvent& aEvent ) +{ + unsigned sel = aEvent.GetIndex(); + wxASSERT( sel < m_items.size() ); + + m_selected_index = sel; + + auto subgraph = m_items[sel].subgraph; + + auto sheet = subgraph->m_sheet; + auto driver = subgraph->m_driver; + + if( sheet != *g_CurrentSheet ) + { + sheet.LastScreen()->SetZoom( m_frame->GetScreen()->GetZoom() ); + *g_CurrentSheet = sheet; + g_CurrentSheet->UpdateAllScreenReferences(); + sheet.LastScreen()->TestDanglingEnds(); + } + + auto pos = driver->GetPosition(); + + m_frame->SetCrossHairPosition( pos ); + m_frame->RedrawScreen( pos, false ); + + m_cb_new_name->Clear(); + + for( auto option : m_items[sel].possible_labels ) + m_cb_new_name->Append( option ); + + m_cb_new_name->Select( 0 ); +} + + +void DIALOG_MIGRATE_BUSES::onAcceptClicked( wxCommandEvent& aEvent ) +{ + wxASSERT( m_selected_index < m_items.size() ); + + auto sel = m_selected_index; + + m_items[sel].approved_label = m_cb_new_name->GetStringSelection(); + m_items[sel].approved = true; + + auto sheet = m_items[sel].subgraph->m_sheet; + auto screen = sheet.LastScreen(); + + auto labels = m_items[sel].subgraph->GetBusLabels(); + + static_cast( labels[0] )->SetText( m_items[sel].approved_label ); + + labels.erase( labels.begin() ); + + for( auto label : labels ) + { + label->SetFlags( STRUCT_DELETED ); + screen->Remove( label ); + } + + m_migration_list->SetItem( sel, 2, m_items[sel].approved_label ); + m_migration_list->SetItem( sel, 3, _( "Updated" ) ); + + if( sel < m_items.size() - 1 ) + { + m_migration_list->Select( sel + 1 ); + } +} diff --git a/eeschema/dialogs/dialog_migrate_buses.h b/eeschema/dialogs/dialog_migrate_buses.h new file mode 100644 index 0000000000..4038010605 --- /dev/null +++ b/eeschema/dialogs/dialog_migrate_buses.h @@ -0,0 +1,71 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2018 CERN + * @author Jon Evans + * + * 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, see . + */ + +#ifndef _DIALOG_MIGRATE_BUSES_H +#define _DIALOG_MIGRATE_BUSES_H + +#include + +#include + +#include + +class CONNECTION_SUBGRAPH; + + +struct BUS_MIGRATION_STATUS +{ + CONNECTION_SUBGRAPH* subgraph; + + std::vector labels; + + std::vector possible_labels; + + wxString approved_label; + + bool approved; +}; + +class DIALOG_MIGRATE_BUSES : public DIALOG_MIGRATE_BUSES_BASE +{ +public: + + DIALOG_MIGRATE_BUSES( SCH_EDIT_FRAME* aParent ); + +private: + + SCH_EDIT_FRAME* m_frame; + + unsigned m_selected_index; + + void loadGraphData(); + + void updateUi(); + + std::vector getProposedLabels( std::vector aLabelList ); + + void onItemSelected( wxListEvent& aEvent ); + + void onAcceptClicked( wxCommandEvent& aEvent ); + + std::vector m_items; +}; + +#endif diff --git a/eeschema/dialogs/dialog_migrate_buses_base.cpp b/eeschema/dialogs/dialog_migrate_buses_base.cpp new file mode 100644 index 0000000000..46cdf22fae --- /dev/null +++ b/eeschema/dialogs/dialog_migrate_buses_base.cpp @@ -0,0 +1,70 @@ +/////////////////////////////////////////////////////////////////////////// +// C++ code generated with wxFormBuilder (version Oct 17 2016) +// http://www.wxformbuilder.org/ +// +// PLEASE DO "NOT" EDIT THIS FILE! +/////////////////////////////////////////////////////////////////////////// + +#include "dialog_migrate_buses_base.h" + +/////////////////////////////////////////////////////////////////////////// + +DIALOG_MIGRATE_BUSES_BASE::DIALOG_MIGRATE_BUSES_BASE( wxWindow* parent, wxWindowID id, const wxString& title, const wxPoint& pos, const wxSize& size, long style ) : DIALOG_SHIM( parent, id, title, pos, size, style ) +{ + this->SetSizeHints( wxDefaultSize, wxDefaultSize ); + + wxBoxSizer* main_sizer; + main_sizer = new wxBoxSizer( wxVERTICAL ); + + m_staticText5 = new wxStaticText( this, wxID_ANY, _("This schematic has one or more buses with more than one label. This was allowed in previous KiCad versions but is no longer permitted."), wxDefaultPosition, wxDefaultSize, 0 ); + m_staticText5->Wrap( 480 ); + main_sizer->Add( m_staticText5, 0, wxALL|wxEXPAND, 5 ); + + m_staticText7 = new wxStaticText( this, wxID_ANY, _("Please select a new name for each of the buses below.\nA name has been suggested for you based on the labels attached to the bus."), wxDefaultPosition, wxDefaultSize, 0 ); + m_staticText7->Wrap( 480 ); + m_staticText7->SetMinSize( wxSize( -1,60 ) ); + + main_sizer->Add( m_staticText7, 0, wxALL|wxEXPAND, 5 ); + + m_migration_list = new wxListView( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLC_HRULES|wxLC_REPORT|wxLC_SINGLE_SEL|wxLC_VRULES ); + m_migration_list->SetMinSize( wxSize( 460,100 ) ); + + main_sizer->Add( m_migration_list, 1, wxALL|wxEXPAND, 5 ); + + m_staticText6 = new wxStaticText( this, wxID_ANY, _("Proposed new name:"), wxDefaultPosition, wxDefaultSize, 0 ); + m_staticText6->Wrap( -1 ); + main_sizer->Add( m_staticText6, 0, wxALL, 5 ); + + wxBoxSizer* bSizer7; + bSizer7 = new wxBoxSizer( wxHORIZONTAL ); + + m_cb_new_name = new wxComboBox( this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0, NULL, 0 ); + m_cb_new_name->SetMinSize( wxSize( 300,-1 ) ); + m_cb_new_name->SetMaxSize( wxSize( 460,-1 ) ); + + bSizer7->Add( m_cb_new_name, 1, wxALL|wxEXPAND, 5 ); + + m_btn_accept = new wxButton( this, wxID_ANY, _("Accept Name"), wxDefaultPosition, wxDefaultSize, 0 ); + bSizer7->Add( m_btn_accept, 0, wxALL, 5 ); + + + main_sizer->Add( bSizer7, 0, wxEXPAND, 5 ); + + m_sdbSizer1 = new wxStdDialogButtonSizer(); + m_sdbSizer1OK = new wxButton( this, wxID_OK ); + m_sdbSizer1->AddButton( m_sdbSizer1OK ); + m_sdbSizer1->Realize(); + + main_sizer->Add( m_sdbSizer1, 0, wxEXPAND, 5 ); + + + this->SetSizer( main_sizer ); + this->Layout(); + main_sizer->Fit( this ); + + this->Centre( wxBOTH ); +} + +DIALOG_MIGRATE_BUSES_BASE::~DIALOG_MIGRATE_BUSES_BASE() +{ +} diff --git a/eeschema/dialogs/dialog_migrate_buses_base.fbp b/eeschema/dialogs/dialog_migrate_buses_base.fbp new file mode 100644 index 0000000000..02524c026e --- /dev/null +++ b/eeschema/dialogs/dialog_migrate_buses_base.fbp @@ -0,0 +1,669 @@ + + + + + + C++ + 1 + source_name + 0 + 0 + res + UTF-8 + connect + dialog_migrate_buses_base + 1000 + none + 1 + DIALOG_MIGRATE_BUSES_BASE + + . + + 1 + 1 + 1 + 1 + UI + 0 + 0 + + 0 + wxAUI_MGR_DEFAULT + + wxBOTH + + 1 + 1 + impl_virtual + + + + 0 + wxID_ANY + + + DIALOG_MIGRATE_BUSES_BASE + + + wxDEFAULT_DIALOG_STYLE + DIALOG_SHIM; dialog_shim.h + Migrate Buses + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + main_sizer + wxVERTICAL + none + + 5 + wxALL|wxEXPAND + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + This schematic has one or more buses with more than one label. This was allowed in previous KiCad versions but is no longer permitted. + + 0 + + + 0 + + 1 + m_staticText5 + 1 + + + protected + 1 + + Resizable + 1 + + + + 0 + + + + + 480 + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxALL|wxEXPAND + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + Please select a new name for each of the buses below. A name has been suggested for you based on the labels attached to the bus. + + 0 + + + 0 + -1,60 + 1 + m_staticText7 + 1 + + + protected + 1 + + Resizable + 1 + + + + 0 + + + + + 480 + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxALL|wxEXPAND + 1 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + + 0 + + + 0 + 460,100 + 1 + m_migration_list + 1 + + + protected + 1 + + Resizable + 1 + + wxLC_HRULES|wxLC_REPORT|wxLC_SINGLE_SEL|wxLC_VRULES + wxListView; + 0 + + + wxFILTER_NONE + wxDefaultValidator + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxALL + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + Proposed new name: + + 0 + + + 0 + + 1 + m_staticText6 + 1 + + + protected + 1 + + Resizable + 1 + + + + 0 + + + + + -1 + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxEXPAND + 0 + + + bSizer7 + wxHORIZONTAL + none + + 5 + wxALL|wxEXPAND + 1 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + + 0 + 460,-1 + + 0 + 300,-1 + 1 + m_cb_new_name + 1 + + + protected + 1 + + Resizable + -1 + 1 + + + + 0 + + + wxFILTER_NONE + wxDefaultValidator + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxALL + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + Accept Name + + 0 + + + 0 + + 1 + m_btn_accept + 1 + + + protected + 1 + + Resizable + 1 + + + + 0 + + + wxFILTER_NONE + wxDefaultValidator + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxEXPAND + 0 + + 0 + 0 + 0 + 0 + 0 + 1 + 0 + 0 + + m_sdbSizer1 + protected + + + + + + + + + + + + + + diff --git a/eeschema/dialogs/dialog_migrate_buses_base.h b/eeschema/dialogs/dialog_migrate_buses_base.h new file mode 100644 index 0000000000..a64731c471 --- /dev/null +++ b/eeschema/dialogs/dialog_migrate_buses_base.h @@ -0,0 +1,57 @@ +/////////////////////////////////////////////////////////////////////////// +// C++ code generated with wxFormBuilder (version Oct 17 2016) +// http://www.wxformbuilder.org/ +// +// PLEASE DO "NOT" EDIT THIS FILE! +/////////////////////////////////////////////////////////////////////////// + +#ifndef __DIALOG_MIGRATE_BUSES_BASE_H__ +#define __DIALOG_MIGRATE_BUSES_BASE_H__ + +#include +#include +#include +class DIALOG_SHIM; +class wxListView; + +#include "dialog_shim.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/////////////////////////////////////////////////////////////////////////// + + +/////////////////////////////////////////////////////////////////////////////// +/// Class DIALOG_MIGRATE_BUSES_BASE +/////////////////////////////////////////////////////////////////////////////// +class DIALOG_MIGRATE_BUSES_BASE : public DIALOG_SHIM +{ + private: + + protected: + wxStaticText* m_staticText5; + wxStaticText* m_staticText7; + wxListView* m_migration_list; + wxStaticText* m_staticText6; + wxComboBox* m_cb_new_name; + wxButton* m_btn_accept; + wxStdDialogButtonSizer* m_sdbSizer1; + wxButton* m_sdbSizer1OK; + + public: + + DIALOG_MIGRATE_BUSES_BASE( wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = _("Migrate Buses"), const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize, long style = wxDEFAULT_DIALOG_STYLE ); + ~DIALOG_MIGRATE_BUSES_BASE(); + +}; + +#endif //__DIALOG_MIGRATE_BUSES_BASE_H__ diff --git a/eeschema/dialogs/dialog_sch_edit_sheet_pin.cpp b/eeschema/dialogs/dialog_sch_edit_sheet_pin.cpp index e6c14d74aa..9789232536 100644 --- a/eeschema/dialogs/dialog_sch_edit_sheet_pin.cpp +++ b/eeschema/dialogs/dialog_sch_edit_sheet_pin.cpp @@ -23,6 +23,7 @@ */ #include +#include #include diff --git a/eeschema/dialogs/dialog_sch_sheet_props.cpp b/eeschema/dialogs/dialog_sch_sheet_props.cpp index af85a7c9a5..d7df5f36c7 100644 --- a/eeschema/dialogs/dialog_sch_sheet_props.cpp +++ b/eeschema/dialogs/dialog_sch_sheet_props.cpp @@ -33,6 +33,7 @@ #include #include +#include DIALOG_SCH_SHEET_PROPS::DIALOG_SCH_SHEET_PROPS( SCH_EDIT_FRAME* parent, SCH_SHEET* aSheet ) : diff --git a/eeschema/drc_erc_item.cpp b/eeschema/drc_erc_item.cpp index f9460a7c8f..0dc3839c79 100644 --- a/eeschema/drc_erc_item.cpp +++ b/eeschema/drc_erc_item.cpp @@ -52,8 +52,8 @@ wxString DRC_ITEM::GetErrorText() const return wxString( _("Mismatch between hierarchical labels and pins sheets")); case ERCE_NOCONNECT_CONNECTED: return wxString( _("A pin with a \"no connection\" flag is connected")); - case ERCE_GLOBLABEL: - return wxString( _("Global label not connected to any other global label") ); + case ERCE_LABEL_NOT_CONNECTED: + return wxString( _("A label not connected to any other item") ); case ERCE_SIMILAR_LABELS: return wxString( _("Labels are similar (lower/upper case difference only)") ); case ERCE_SIMILAR_GLBL_LABELS: @@ -62,7 +62,18 @@ wxString DRC_ITEM::GetErrorText() const return wxString( _("Different footprint assigned in another unit of the same component") ); case ERCE_DIFFERENT_UNIT_NET: return wxString( _("Different net assigned to a shared pin in another unit of the same component" ) ); - + case ERCE_BUS_ALIAS_CONFLICT: + return wxString( _("Conflict between bus alias definitions across schematic sheets") ); + case ERCE_DRIVER_CONFLICT: + return wxString( _( "More than one name given to this bus or net" ) ); + case ERCE_BUS_ENTRY_CONFLICT: + return wxString( _( "Net is graphically connected to a bus but not a bus member" ) ); + case ERCE_BUS_LABEL_ERROR: + return wxString( _( "Label attached to bus item does not describe a bus" ) ); + case ERCE_BUS_TO_BUS_CONFLICT: + return wxString( _( "No nets are shared between two bus items" ) ); + case ERCE_BUS_TO_NET_CONFLICT: + return wxString( _( "Invalid connection between bus and net items" ) ); default: wxFAIL_MSG( "Missing ERC error description" ); return wxString( wxT("Unknown.") ); diff --git a/eeschema/edit_component_in_schematic.cpp b/eeschema/edit_component_in_schematic.cpp index 7e4c7fda98..860be76bd4 100644 --- a/eeschema/edit_component_in_schematic.cpp +++ b/eeschema/edit_component_in_schematic.cpp @@ -77,7 +77,7 @@ void SCH_EDIT_FRAME::EditComponentFieldText( SCH_FIELD* aField ) return; } - dlg.UpdateField( aField, m_CurrentSheet ); + dlg.UpdateField( aField, g_CurrentSheet ); m_canvas->MoveCursorToCrossHair(); m_canvas->SetIgnoreMouseEvents( false ); @@ -88,7 +88,6 @@ void SCH_EDIT_FRAME::EditComponentFieldText( SCH_FIELD* aField ) OnModify(); MSG_PANEL_ITEMS items; - component->SetCurrentSheetPath( &GetCurrentSheet() ); component->GetMsgPanelInfo( m_UserUnits, items ); SetMsgPanel( items ); } diff --git a/eeschema/eeschema_config.cpp b/eeschema/eeschema_config.cpp index 46f56a4d29..0710b5f956 100644 --- a/eeschema/eeschema_config.cpp +++ b/eeschema/eeschema_config.cpp @@ -32,6 +32,7 @@ #include #include #include +#include #include #include #include @@ -249,8 +250,26 @@ PARAM_CFG_ARRAY& SCH_EDIT_FRAME::GetProjectFileParametersList() &s_defaultTextSize, DEFAULT_SIZE_TEXT, 5, 1000 ) ); + m_projectFileParams.push_back( new PARAM_CFG_BOOL( wxT( "ERC_WriteFile" ), + &m_ercSettings.write_erc_file, false ) ); + m_projectFileParams.push_back( new PARAM_CFG_BOOL( wxT( "ERC_TestSimilarLabels" ), - &DIALOG_ERC::m_TestSimilarLabels, true ) ); + &m_ercSettings.check_similar_labels, true ) ); + + m_projectFileParams.push_back( new PARAM_CFG_BOOL( wxT( "ERC_CheckUniqueGlobalLabels" ), + &m_ercSettings.check_unique_global_labels, true ) ); + + m_projectFileParams.push_back( new PARAM_CFG_BOOL( wxT( "ERC_CheckBusDriverConflicts" ), + &m_ercSettings.check_bus_driver_conflicts, true ) ); + + m_projectFileParams.push_back( new PARAM_CFG_BOOL( wxT( "ERC_CheckBusEntryConflicts" ), + &m_ercSettings.check_bus_entry_conflicts, true ) ); + + m_projectFileParams.push_back( new PARAM_CFG_BOOL( wxT( "ERC_CheckBusToBusConflicts" ), + &m_ercSettings.check_bus_to_bus_conflicts, true ) ); + + m_projectFileParams.push_back( new PARAM_CFG_BOOL( wxT( "ERC_CheckBusToNetConflicts" ), + &m_ercSettings.check_bus_to_net_conflicts, true ) ); return m_projectFileParams; } diff --git a/eeschema/eeschema_id.h b/eeschema/eeschema_id.h index 30818ee164..3ae29f62da 100644 --- a/eeschema/eeschema_id.h +++ b/eeschema/eeschema_id.h @@ -66,6 +66,7 @@ enum id_eeschema_frm /* Schematic editor main menubar IDs. */ ID_RESCUE_CACHED, ID_EDIT_SYM_LIB_TABLE, + ID_BUS_MANAGER, ID_REMAP_SYMBOLS, ID_EDIT_COMPONENTS_TO_SYMBOLS_LIB_ID, ID_GRID_SETTINGS, @@ -166,6 +167,10 @@ enum id_eeschema_frm ID_POPUP_SCH_GETINFO_MARKER, ID_POPUP_END_RANGE, + // Dynamically bound in AddMenusForBus() + ID_POPUP_SCH_UNFOLD_BUS, + ID_POPUP_SCH_UNFOLD_BUS_END = ID_POPUP_SCH_UNFOLD_BUS + 64, + ID_POPUP_SCH_DISPLAYDOC_CMP, ID_POPUP_SCH_CALL_LIBEDIT_AND_LOAD_CMP, @@ -200,6 +205,7 @@ enum id_eeschema_frm ID_SCH_EDIT_COMPONENT_FOOTPRINT, ID_SCH_MOVE_ITEM, ID_SCH_DRAG_ITEM, + ID_SCH_UNFOLD_BUS, ID_AUTOPLACE_FIELDS, diff --git a/eeschema/erc.cpp b/eeschema/erc.cpp index 11e8284113..769193d70b 100644 --- a/eeschema/erc.cpp +++ b/eeschema/erc.cpp @@ -225,6 +225,73 @@ int TestDuplicateSheetNames( bool aCreateMarker ) } +int TestConflictingBusAliases( bool aCreateMarker ) +{ + using std::pair; + using std::shared_ptr; + using std::vector; + + int err_count = 0; + SCH_SCREENS screens; + vector< shared_ptr > aliases; + vector< pair< shared_ptr, shared_ptr > > conflicts; + + for( auto screen = screens.GetFirst(); screen != NULL; screen = screens.GetNext() ) + { + auto screen_aliases = screen->GetBusAliases(); + + for( auto alias : screen_aliases ) + { + for( auto test : aliases ) + { + if( alias->GetName() == test->GetName() && + alias->Members() != test->Members() ) + { + conflicts.push_back( std::make_pair( alias, test ) ); + } + } + } + + aliases.insert( aliases.end(), + screen_aliases.begin(), screen_aliases.end() ); + } + + if( !conflicts.empty() ) + { + if( aCreateMarker ) + { + wxString msg; + + for( auto conflict : conflicts ) + { + auto marker = new SCH_MARKER(); + auto a1 = conflict.first; + auto a2 = conflict.second; + + msg.Printf( _( "Bus alias %s has conflicting definitions on multiple sheets: " ), + GetChars( a1->GetName() ) ); + + wxFileName f1 = a1->GetParent()->GetFileName(); + wxFileName f2 = a2->GetParent()->GetFileName(); + + msg << f1.GetFullName() << " and " << f2.GetFullName(); + + marker->SetData( ERCE_BUS_ALIAS_CONFLICT, wxPoint( 0, 0 ), + msg, wxPoint( 0, 0 ) ); + marker->SetMarkerType( MARKER_BASE::MARKER_ERC ); + marker->SetErrorLevel( MARKER_BASE::MARKER_SEVERITY_ERROR ); + + a2->GetParent()->Append( marker ); + + ++err_count; + } + } + } + + return err_count; +} + + int TestMultiunitFootprints( SCH_SHEET_LIST& aSheetList ) { int errors = 0; @@ -314,6 +381,7 @@ void Diagnose( NETLIST_OBJECT* aNetItemRef, NETLIST_OBJECT* aNetItemTst, if( aMinConn < 0 ) { +#if 0 if( aNetItemRef->m_Type == NET_HIERLABEL || aNetItemRef->m_Type == NET_HIERBUSLABELMEMBER ) { msg.Printf( _( "Hierarchical label %s is not connected to a sheet label." ), @@ -341,7 +409,7 @@ void Diagnose( NETLIST_OBJECT* aNetItemRef, NETLIST_OBJECT* aNetItemTst, msg, aNetItemRef->m_Start ); } - +#endif return; } diff --git a/eeschema/erc.h b/eeschema/erc.h index 16ffeac2fc..ea0b48a8e5 100644 --- a/eeschema/erc.h +++ b/eeschema/erc.h @@ -49,21 +49,28 @@ extern const wxString CommentERC_H[]; extern const wxString CommentERC_V[]; /// DRC error codes: -#define ERCE_UNSPECIFIED 0 -#define ERCE_DUPLICATE_SHEET_NAME 1 // duplicate sheet names within a given sheet -#define ERCE_PIN_NOT_CONNECTED 2 // pin not connected and not no connect symbol -#define ERCE_PIN_NOT_DRIVEN 3 // pin connected to some others pins but no pin to drive it -#define ERCE_PIN_TO_PIN_WARNING 4 // pin connected to another pin: warning level -#define ERCE_PIN_TO_PIN_ERROR 5 // pin connected to another pin: error level -#define ERCE_HIERACHICAL_LABEL 6 // mismatch between hierarchical labels and pins sheets -#define ERCE_NOCONNECT_CONNECTED 7 // a no connect symbol is connected to more than 1 pin -#define ERCE_GLOBLABEL 8 // global label not connected to any other global label -#define ERCE_SIMILAR_LABELS 9 // 2 labels are equal fir case insensitive comparisons -#define ERCE_SIMILAR_GLBL_LABELS 10 // 2 labels are equal fir case insensitive comparisons -#define ERCE_DIFFERENT_UNIT_FP 11 // different units of the same component have different - // footprints assigned -#define ERCE_DIFFERENT_UNIT_NET 12 // a shared pin in a multi-unit component is connected - // to more than one net +enum ERCE_T +{ + ERCE_UNSPECIFIED = 0, + ERCE_DUPLICATE_SHEET_NAME, // duplicate sheet names within a given sheet + ERCE_PIN_NOT_CONNECTED, // pin not connected and not no connect symbol + ERCE_PIN_NOT_DRIVEN, // pin connected to some others pins but no pin to drive it + ERCE_PIN_TO_PIN_WARNING, // pin connected to an other pin: warning level + ERCE_PIN_TO_PIN_ERROR, // pin connected to an other pin: error level + ERCE_HIERACHICAL_LABEL, // mismatch between hierarchical labels and pins sheets + ERCE_NOCONNECT_CONNECTED, // a no connect symbol is connected to more than 1 pin + ERCE_LABEL_NOT_CONNECTED, // label not connected to anything + ERCE_SIMILAR_LABELS, // 2 labels are equal fir case insensitive comparisons + ERCE_SIMILAR_GLBL_LABELS, // 2 labels are equal fir case insensitive comparisons + ERCE_DIFFERENT_UNIT_FP, // different units of the same component have different footprints assigned + ERCE_DIFFERENT_UNIT_NET, // a shared pin in a multi-unit component is connected to more than one net + ERCE_BUS_ALIAS_CONFLICT, // conflicting bus alias definitions across sheets + ERCE_DRIVER_CONFLICT, // conflicting drivers (labels, etc) on a subgraph + ERCE_BUS_ENTRY_CONFLICT, // a wire connected to a bus doesn't match the bus + ERCE_BUS_LABEL_ERROR, // a label attached to a bus isn't in bus format + ERCE_BUS_TO_BUS_CONFLICT, // a connection between bus objects doesn't share at least one net + ERCE_BUS_TO_NET_CONFLICT, // a bus wire is graphically connected to a net port/pin (or vice versa) +}; /* Minimal connection table */ #define NPI 4 // Net with Pin isolated, this pin has type Not Connected and must be left N.C. @@ -112,6 +119,18 @@ void TestOthersItems( NETLIST_OBJECT_LIST* aList, */ int TestDuplicateSheetNames( bool aCreateMarker ); +/** + * Checks that there are not conflicting bus alias definitions in the schematic + * + * (for example, two hierarchical sub-sheets contain different definitions for + * the same bus alias) + * + * @param aCreateMarker: true = create error markers in schematic, + * false = calculate error count only + * @return the error count + */ +int TestConflictingBusAliases( bool aCreateMarker = true ); + /** * Test if all units of each multiunit component have the same footprint assigned. * @param aSheetList contains components to be validated. diff --git a/eeschema/erc_settings.h b/eeschema/erc_settings.h new file mode 100644 index 0000000000..d4c2ba19e3 --- /dev/null +++ b/eeschema/erc_settings.h @@ -0,0 +1,82 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2018 CERN + * @author Jon Evans + * + * 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, see . + */ + +#ifndef _ERC_SETTINGS_H +#define _ERC_SETTINGS_H + +/** + * Container for ERC settings + * + * Currently only stores flags about checks to run, but could later be expanded + * to contain the matrix of electrical pin types. + */ +class ERC_SETTINGS +{ +public: + void LoadDefaults() + { + write_erc_file = false; + check_similar_labels = true; + check_unique_global_labels = true; + check_bus_driver_conflicts = true; + check_bus_entry_conflicts = true; + check_bus_to_bus_conflicts = true; + check_bus_to_net_conflicts = true; + } + + bool operator==( const ERC_SETTINGS& other ) const + { + return ( other.write_erc_file == write_erc_file && + other.check_similar_labels == check_similar_labels && + other.check_unique_global_labels == check_unique_global_labels && + other.check_bus_driver_conflicts == check_bus_driver_conflicts && + other.check_bus_entry_conflicts == check_bus_entry_conflicts && + other.check_bus_to_bus_conflicts == check_bus_to_bus_conflicts && + other.check_bus_to_net_conflicts == check_bus_to_net_conflicts ); + } + + bool operator!=( const ERC_SETTINGS& other ) const + { + return !( other == *this ); + } + + /// If true, write ERC results to a file + bool write_erc_file; + + /// If true, check each sheet for labels that differ only by letter case + bool check_similar_labels; + + /// If true, check to ensure that each global label apperas more than once + bool check_unique_global_labels; + + /// If true, check that buses don't have conflicting drivers + bool check_bus_driver_conflicts; + + /// If true, check that wires connecting to buses actually exist in the bus + bool check_bus_entry_conflicts; + + /// If true, check that bus-to-bus connections share at least one member + bool check_bus_to_bus_conflicts; + + /// If true, check that bus wires don't graphically connect to net objects (or vice versa) + bool check_bus_to_net_conflicts; +}; + +#endif diff --git a/eeschema/files-io.cpp b/eeschema/files-io.cpp index fd564f5bbd..60aa9f3ae0 100644 --- a/eeschema/files-io.cpp +++ b/eeschema/files-io.cpp @@ -51,7 +51,9 @@ #include #include #include +#include #include +#include bool SCH_EDIT_FRAME::SaveEEFile( SCH_SCREEN* aScreen, bool aSaveUnderNewName, @@ -287,6 +289,7 @@ bool SCH_EDIT_FRAME::OpenProjectFiles( const std::vector& aFileSet, in SetScreen( nullptr ); delete g_RootSheet; // Delete the current project. g_RootSheet = NULL; // Force CreateScreens() to build new empty project on load failure. + SCH_PLUGIN::SCH_PLUGIN_RELEASER pi( SCH_IO_MGR::FindPlugin( SCH_IO_MGR::SCH_LEGACY ) ); // This will rename the file if there is an autosave and the user want to recover @@ -295,8 +298,10 @@ bool SCH_EDIT_FRAME::OpenProjectFiles( const std::vector& aFileSet, in try { g_RootSheet = pi->Load( fullFileName, &Kiway() ); - m_CurrentSheet->clear(); - m_CurrentSheet->push_back( g_RootSheet ); + + g_CurrentSheet = new SCH_SHEET_PATH(); + g_CurrentSheet->clear(); + g_CurrentSheet->push_back( g_RootSheet ); if( !pi->GetError().IsEmpty() ) { @@ -362,13 +367,27 @@ bool SCH_EDIT_FRAME::OpenProjectFiles( const std::vector& aFileSet, in } schematic.UpdateSymbolLinks(); // Update all symbol library links for all sheets. - SetScreen( m_CurrentSheet->LastScreen() ); + SetScreen( g_CurrentSheet->LastScreen() ); // Ensure the schematic is fully segmented on first display BreakSegmentsOnJunctions(); SchematicCleanUp( true ); GetScreen()->ClearUndoORRedoList( GetScreen()->m_UndoList, 1 ); GetScreen()->TestDanglingEnds(); // Only perform the dangling end test on root sheet. + + RecalculateConnections(); // Update connectivity graph + + // Migrate conflicting bus definitions + // TODO(JE) This should only run once based on schematic file version + if( g_ConnectionGraph->GetBusesNeedingMigration().size() > 0 ) + { + DIALOG_MIGRATE_BUSES dlg( this ); + dlg.ShowQuasiModal(); + + RecalculateConnections(); + OnModify(); + } + GetScreen()->m_Initialized = true; } @@ -816,9 +835,9 @@ bool SCH_EDIT_FRAME::importFile( const wxString& aFileName, int aFileType ) newfilename.SetName( Prj().GetProjectName() ); newfilename.SetExt( SchematicFileExtension ); - m_CurrentSheet->clear(); - m_CurrentSheet->push_back( g_RootSheet ); - SetScreen( m_CurrentSheet->LastScreen() ); + g_CurrentSheet->clear(); + g_CurrentSheet->push_back( g_RootSheet ); + SetScreen( g_CurrentSheet->LastScreen() ); g_RootSheet->SetFileName( newfilename.GetFullPath() ); GetScreen()->SetFileName( newfilename.GetFullPath() ); diff --git a/eeschema/find.cpp b/eeschema/find.cpp index 30b41d5c3a..6bdcb3d878 100644 --- a/eeschema/find.cpp +++ b/eeschema/find.cpp @@ -70,8 +70,8 @@ void SCH_EDIT_FRAME::OnFindDrcMarker( wxFindDialogEvent& event ) if( event.GetFlags() & FR_CURRENT_SHEET_ONLY ) { - sheetFoundIn = m_CurrentSheet; - lastMarker = (SCH_MARKER*) m_CurrentSheet->FindNextItem( SCH_MARKER_T, lastMarker, wrap ); + sheetFoundIn = g_CurrentSheet; + lastMarker = (SCH_MARKER*) g_CurrentSheet->FindNextItem( SCH_MARKER_T, lastMarker, wrap ); } else { @@ -81,11 +81,11 @@ void SCH_EDIT_FRAME::OnFindDrcMarker( wxFindDialogEvent& event ) if( lastMarker != NULL ) { - if( *sheetFoundIn != *m_CurrentSheet ) + if( *sheetFoundIn != *g_CurrentSheet ) { sheetFoundIn->LastScreen()->SetZoom( GetScreen()->GetZoom() ); - *m_CurrentSheet = *sheetFoundIn; - m_CurrentSheet->UpdateAllScreenReferences(); + *g_CurrentSheet = *sheetFoundIn; + g_CurrentSheet->UpdateAllScreenReferences(); } SetCrossHairPosition( lastMarker->GetPosition() ); @@ -122,7 +122,7 @@ SCH_ITEM* SCH_EDIT_FRAME::FindComponentAndItem( const wxString& aReference, EDA_ITEM* foundItem = nullptr; if( !aSearchHierarchy ) - sheetList.push_back( *m_CurrentSheet ); + sheetList.push_back( *g_CurrentSheet ); else sheetList.BuildSheetList( g_RootSheet ); @@ -193,10 +193,10 @@ SCH_ITEM* SCH_EDIT_FRAME::FindComponentAndItem( const wxString& aReference, { sheet = sheetWithComponentFound; - if( *sheet != *m_CurrentSheet ) + if( *sheet != *g_CurrentSheet ) { sheet->LastScreen()->SetZoom( GetScreen()->GetZoom() ); - *m_CurrentSheet = *sheet; + *g_CurrentSheet = *sheet; DisplayCurrentSheet(); } @@ -279,7 +279,7 @@ void SCH_EDIT_FRAME::OnFindSchematicItem( wxFindDialogEvent& aEvent ) { if( aEvent.GetFlags() & FR_CURRENT_SHEET_ONLY && g_RootSheet->CountSheets() > 1 ) { - m_foundItems.Collect( searchCriteria, m_CurrentSheet ); + m_foundItems.Collect( searchCriteria, g_CurrentSheet ); } else { @@ -317,7 +317,7 @@ void SCH_EDIT_FRAME::OnFindReplace( wxFindDialogEvent& aEvent ) { if( aEvent.GetFlags() & FR_CURRENT_SHEET_ONLY && g_RootSheet->CountSheets() > 1 ) { - m_foundItems.Collect( searchCriteria, m_CurrentSheet ); + m_foundItems.Collect( searchCriteria, g_CurrentSheet ); } else { @@ -423,11 +423,11 @@ void SCH_EDIT_FRAME::updateFindReplaceView( wxFindDialogEvent& aEvent ) item->SetForceVisible( true ); } - if( sheet->PathHumanReadable() != m_CurrentSheet->PathHumanReadable() ) + if( sheet->PathHumanReadable() != g_CurrentSheet->PathHumanReadable() ) { sheet->LastScreen()->SetZoom( GetScreen()->GetZoom() ); - *m_CurrentSheet = *sheet; - m_CurrentSheet->UpdateAllScreenReferences(); + *g_CurrentSheet = *sheet; + g_CurrentSheet->UpdateAllScreenReferences(); SetScreen( sheet->LastScreen() ); sheet->LastScreen()->TestDanglingEnds(); } diff --git a/eeschema/general.h b/eeschema/general.h index c147204081..bd5b034bbd 100644 --- a/eeschema/general.h +++ b/eeschema/general.h @@ -34,8 +34,10 @@ using KIGFX::COLOR4D; +class CONNECTION_GRAPH; class TRANSFORM; class SCH_SHEET; +class SCH_SHEET_PATH; #define EESCHEMA_VERSION 4 #define SCHEMATIC_HEAD_STRING "Schematic File Version" @@ -83,6 +85,18 @@ extern TRANSFORM DefaultTransform; /* First and main (root) screen */ extern SCH_SHEET* g_RootSheet; +/** + * With the new connectivity algorithm, many more places than before want to + * know what the current sheet is. This was moved here from SCH_EDIT_FRAME + * but we could refactor things to get rid of this global. + */ +extern SCH_SHEET_PATH* g_CurrentSheet; ///< which sheet we are presently working on. + +/** + * This also wants to live in the eventual SCHEMATIC object + */ +extern CONNECTION_GRAPH* g_ConnectionGraph; + /** * Default line thickness used to draw/plot items having a * default thickness line value (i.e. = 0 ). diff --git a/eeschema/getpart.cpp b/eeschema/getpart.cpp index 13951995e5..367652c3ed 100644 --- a/eeschema/getpart.cpp +++ b/eeschema/getpart.cpp @@ -243,7 +243,7 @@ SCH_COMPONENT* SCH_EDIT_FRAME::Load_Component( const SCHLIB_FILTER* aF if( !part ) return NULL; - SCH_COMPONENT* component = new SCH_COMPONENT( *part, libId, m_CurrentSheet, + SCH_COMPONENT* component = new SCH_COMPONENT( *part, libId, g_CurrentSheet, sel.Unit, sel.Convert, GetCrossHairPosition(), true ); @@ -261,7 +261,6 @@ SCH_COMPONENT* SCH_EDIT_FRAME::Load_Component( const SCHLIB_FILTER* aF MSG_PANEL_ITEMS items; - component->SetCurrentSheetPath( &GetCurrentSheet() ); component->GetMsgPanelInfo( m_UserUnits, items ); SetMsgPanel( items ); @@ -333,7 +332,7 @@ void SCH_EDIT_FRAME::OnSelectUnit( wxCommandEvent& aEvent ) SaveCopyInUndoList( component, UR_CHANGED ); /* Update the unit number. */ - component->SetUnitSelection( m_CurrentSheet, unit ); + component->SetUnitSelection( g_CurrentSheet, unit ); component->SetUnit( unit ); component->ClearFlags(); component->SetFlags( flags ); // Restore m_Flag modified by SetUnit() diff --git a/eeschema/help_common_strings.h b/eeschema/help_common_strings.h index 2e2f56c70c..94ee4bd113 100644 --- a/eeschema/help_common_strings.h +++ b/eeschema/help_common_strings.h @@ -79,6 +79,7 @@ #define HELP_RUN_LIB_EDITOR _( "Create, delete, and edit symbols" ) #define HELP_RUN_LIB_VIEWER _( "Browse symbol libraries" ) #define HELP_GENERATE_BOM _( "Generate bill of materials" ) +#define HELP_BUS_MANAGER _( "Manage bus definitions" ) #define HELP_IMPORT_FOOTPRINTS \ _( "Back-import symbol footprint association fields from the .cmp back import file created by Pcbnew" ) diff --git a/eeschema/hierarch.cpp b/eeschema/hierarch.cpp index 02938993c1..d410f66362 100644 --- a/eeschema/hierarch.cpp +++ b/eeschema/hierarch.cpp @@ -267,7 +267,7 @@ void SCH_EDIT_FRAME::DisplayCurrentSheet() SetRepeatItem( NULL ); ClearMsgPanel(); - SCH_SCREEN* screen = m_CurrentSheet->LastScreen(); + SCH_SCREEN* screen = g_CurrentSheet->LastScreen(); // Switch to current sheet, // and update the grid size, because it can be modified in latest screen @@ -275,7 +275,7 @@ void SCH_EDIT_FRAME::DisplayCurrentSheet() GetScreen()->SetGrid( m_LastGridSizeId + ID_POPUP_GRID_LEVEL_1000 ); // update the References - m_CurrentSheet->UpdateAllScreenReferences(); + g_CurrentSheet->UpdateAllScreenReferences(); SetSheetNumberAndCount(); m_canvas->SetCanStartBlock( -1 ); @@ -284,12 +284,11 @@ void SCH_EDIT_FRAME::DisplayCurrentSheet() Zoom_Automatique( false ); screen->m_Initialized = true; - // Ensure the schematic is fully segmented on first display - BreakSegmentsOnJunctions(); - SchematicCleanUp( true ); - screen->ClearUndoORRedoList( screen->m_UndoList, 1 ); + // TODO(JE) should be able to just recalculate the current sheet path + // RecalculateConnections() handles cleanup and dangling ends tests + RecalculateConnections(); - screen->TestDanglingEnds(); + screen->ClearUndoORRedoList( screen->m_UndoList, 1 ); } else { diff --git a/eeschema/highlight_connection.cpp b/eeschema/highlight_connection.cpp index bc6713d267..37d4980701 100644 --- a/eeschema/highlight_connection.cpp +++ b/eeschema/highlight_connection.cpp @@ -35,42 +35,43 @@ #include #include +#include +#include // List of items having the highlight option modified, therefore need to be redrawn +// TODO(JE) Probably use netcode rather than connection name here eventually bool SCH_EDIT_FRAME::HighlightConnectionAtPosition( wxPoint aPosition ) { std::vector itemsToRedraw; m_SelectedNetName = ""; bool buildNetlistOk = false; + SetStatusText( "" ); + // find which connected item is selected EDA_ITEMS nodeList; wxPoint gridPosition = GetGridPosition( aPosition ); - if( GetScreen()->GetNode( gridPosition,nodeList ) ) + if( GetScreen()->GetNode( gridPosition, nodeList ) ) { if( TestDuplicateSheetNames( false ) > 0 ) wxMessageBox( _( "Error: duplicate sub-sheet names found in current sheet. Fix it" ) ); else { - // Build netlist info to get the proper netnames of connected items - std::unique_ptr objectsConnectedList( BuildNetListBase() ); - buildNetlistOk = true; - - for( auto obj : *objectsConnectedList ) + if( auto item = dynamic_cast( nodeList[0] ) ) { - if( obj->m_SheetPath == *m_CurrentSheet && obj->m_Comp == nodeList[0] ) + if( item->Connection( *g_CurrentSheet ) ) { - m_SelectedNetName = obj->GetNetName( true ); - break; + m_SelectedNetName = item->Connection( *g_CurrentSheet )->Name(); + SetStatusText( _( "Highlighted net: " ) + m_SelectedNetName ); } } } } SendCrossProbeNetName( m_SelectedNetName ); - SetStatusText( "selected net: " + m_SelectedNetName ); + SetStatusText( _( "Selected net: " ) + m_SelectedNetName ); SetCurrentSheetHighlightFlags( &itemsToRedraw ); // Be sure hightlight change will be redrawn @@ -87,7 +88,7 @@ bool SCH_EDIT_FRAME::HighlightConnectionAtPosition( wxPoint aPosition ) bool SCH_EDIT_FRAME::SetCurrentSheetHighlightFlags( std::vector* aItemsToRedrawList ) { - SCH_SCREEN* screen = m_CurrentSheet->LastScreen(); + SCH_SCREEN* screen = g_CurrentSheet->LastScreen(); if( !screen ) return true; @@ -95,20 +96,33 @@ bool SCH_EDIT_FRAME::SetCurrentSheetHighlightFlags( std::vector* aIte // Disable highlight flag on all items in the current screen for( SCH_ITEM* ptr = screen->GetDrawItems(); ptr; ptr = ptr->Next() ) { - if( ptr->GetState( BRIGHTENED ) && aItemsToRedrawList ) + auto conn = ptr->Connection( *g_CurrentSheet ); + bool bright = ptr->GetState( BRIGHTENED ); + + if( bright && aItemsToRedrawList ) aItemsToRedrawList->push_back( ptr ); - ptr->SetState( BRIGHTENED, false ); + ptr->SetState( BRIGHTENED, ( conn && conn->Name() == m_SelectedNetName ) ); + if( !bright && ptr->GetState( BRIGHTENED ) && aItemsToRedrawList ) + aItemsToRedrawList->push_back( ptr ); if( ptr->Type() == SCH_SHEET_T ) { for( SCH_SHEET_PIN& pin : static_cast( ptr )->GetPins() ) { - if( ptr->GetState( BRIGHTENED ) && aItemsToRedrawList ) + auto pin_conn = pin.Connection( *g_CurrentSheet ); + + bright = pin.GetState( BRIGHTENED ); + + if( bright && aItemsToRedrawList ) aItemsToRedrawList->push_back( &pin ); - pin.SetState( BRIGHTENED, false ); + pin.SetState( BRIGHTENED, ( pin_conn && + pin_conn->Name() == m_SelectedNetName ) ); + + if( !bright && pin.GetState( BRIGHTENED ) && aItemsToRedrawList ) + aItemsToRedrawList->push_back( &pin ); } } } @@ -119,37 +133,5 @@ bool SCH_EDIT_FRAME::SetCurrentSheetHighlightFlags( std::vector* aIte if( TestDuplicateSheetNames( false ) > 0 ) return false; - // Build netlist info to get the proper netnames - std::unique_ptr objectsConnectedList( BuildNetListBase( false ) ); - - // highlight the items belonging to this net - for( auto obj1 : *objectsConnectedList ) - { - if( obj1->m_SheetPath == *m_CurrentSheet && - obj1->GetNetName( true ) == m_SelectedNetName && obj1->m_Comp ) - { - obj1->m_Comp->SetState( BRIGHTENED, true ); - - if( aItemsToRedrawList ) - aItemsToRedrawList->push_back( obj1->m_Comp ); - - //if a bus is associated with this net highlight it as well - if( obj1->m_BusNetCode ) - { - for( auto obj2 : *objectsConnectedList ) - { - if( obj2 && obj2->m_Comp && obj2->m_SheetPath == *m_CurrentSheet && - obj1->m_BusNetCode == obj2->m_BusNetCode ) - { - if( aItemsToRedrawList ) - aItemsToRedrawList->push_back( obj2->m_Comp ); - - obj2->m_Comp->SetState( BRIGHTENED, true ); - } - } - } - } - } - return true; } diff --git a/eeschema/hotkeys.cpp b/eeschema/hotkeys.cpp index cd8a72fbf9..9278fbf603 100644 --- a/eeschema/hotkeys.cpp +++ b/eeschema/hotkeys.cpp @@ -220,6 +220,9 @@ static EDA_HOTKEY HkUpdatePcbFromSch( _HKI( "Update PCB from Schematic" ), HK_UP static EDA_HOTKEY HkHighlightConnection( _HKI( "Highlight Connection" ), ID_HOTKEY_HIGHLIGHT, 'B' + GR_KB_CTRL ); +static EDA_HOTKEY HkUnfoldBus( _HKI( "Unfold Bus" ), HK_UNFOLD_BUS, 'D', + ID_SCH_UNFOLD_BUS ); + // Common: hotkeys_basic.h static EDA_HOTKEY HkNew( _HKI( "New" ), HK_NEW, GR_KB_CTRL + 'N', (int) wxID_NEW ); static EDA_HOTKEY HkOpen( _HKI( "Open" ), HK_OPEN, GR_KB_CTRL + 'O', (int) wxID_OPEN ); @@ -332,6 +335,7 @@ static EDA_HOTKEY* schematic_Hotkey_List[] = &HkLeaveSheet, &HkDeleteNode, &HkHighlightConnection, + &HkUnfoldBus, &HkCanvasCairo, &HkCanvasOpenGL, NULL @@ -639,6 +643,7 @@ bool SCH_EDIT_FRAME::OnHotKey( wxDC* aDC, int aHotKey, const wxPoint& aPosition, case HK_ROTATE: // Rotate schematic item. case HK_EDIT_COMPONENT_WITH_LIBEDIT: // Call Libedit and load the current component case HK_AUTOPLACE_FIELDS: // Autoplace all fields around component + case HK_UNFOLD_BUS: // Unfold a bus wire case HK_CANVAS_CAIRO: case HK_CANVAS_OPENGL: { diff --git a/eeschema/hotkeys.h b/eeschema/hotkeys.h index d3b068b63f..825a28d258 100644 --- a/eeschema/hotkeys.h +++ b/eeschema/hotkeys.h @@ -80,6 +80,7 @@ enum hotkey_id_commnand { HK_AUTOPLACE_FIELDS, HK_UPDATE_PCB_FROM_SCH, HK_SELECT_ITEMS_ON_PCB, + HK_UNFOLD_BUS, HK_CANVAS_OPENGL, HK_CANVAS_CAIRO, }; diff --git a/eeschema/invoke_sch_dialog.h b/eeschema/invoke_sch_dialog.h index 587186051d..9376a87abf 100644 --- a/eeschema/invoke_sch_dialog.h +++ b/eeschema/invoke_sch_dialog.h @@ -81,6 +81,9 @@ int InvokeDialogPrintUsingPrinter( SCH_EDIT_FRAME* aCaller ); /// DIALOG_BOM::ShowModal() returns. int InvokeDialogCreateBOM( SCH_EDIT_FRAME* aCaller ); +/// Create and show DIALOG_BUS_MANAGER +void InvokeDialogBusManager( SCH_EDIT_FRAME* aCaller ); + /// Update symbol fields int InvokeDialogUpdateFields( SCH_EDIT_FRAME* aCaller, const std::list aComponents, bool aCreateUndoEntry ); diff --git a/eeschema/lib_draw_item.h b/eeschema/lib_draw_item.h index 2d0e4f03f7..6b02c705f2 100644 --- a/eeschema/lib_draw_item.h +++ b/eeschema/lib_draw_item.h @@ -35,6 +35,7 @@ #include #include #include +#include class LINE_READER; diff --git a/eeschema/lib_pin.cpp b/eeschema/lib_pin.cpp index adc3262b98..7c63a54026 100644 --- a/eeschema/lib_pin.cpp +++ b/eeschema/lib_pin.cpp @@ -47,6 +47,7 @@ #include #include #include +#include #include @@ -1770,6 +1771,19 @@ void LIB_PIN::GetMsgPanelInfo( EDA_UNITS_T aUnits, std::vector< MSG_PANEL_ITEM > aList.push_back( MSG_PANEL_ITEM( aComponent->GetField( REFERENCE )->GetShownText(), aComponent->GetField( VALUE )->GetShownText(), DARKCYAN ) ); + +#if defined(DEBUG) + + if( auto pin_connection = aComponent->GetConnectionForPin( this ) ) + { + auto conn = pin_connection->Connection( *g_CurrentSheet ); + + wxASSERT( conn ); + + conn->AppendDebugInfoToMsgPanel( aList ); + } + +#endif } const EDA_RECT LIB_PIN::GetBoundingBox( bool aIncludeInvisibles ) const diff --git a/eeschema/menubar.cpp b/eeschema/menubar.cpp index c209a40385..363f227456 100644 --- a/eeschema/menubar.cpp +++ b/eeschema/menubar.cpp @@ -571,6 +571,12 @@ void prepareToolsMenu( wxMenu* aParentMenu ) HELP_GENERATE_BOM, KiBitmap( bom_xpm ) ); + AddMenuItem( aParentMenu, + ID_BUS_MANAGER, + _( "Bus &Definitions" ), + HELP_BUS_MANAGER, + KiBitmap( bom_xpm ) ); // TODO(JE) new icon? + aParentMenu->AppendSeparator(); // Run CvPcb diff --git a/eeschema/netlist_exporters/netlist_exporter_generic.cpp b/eeschema/netlist_exporters/netlist_exporter_generic.cpp index b0e6bf2943..895ac52926 100644 --- a/eeschema/netlist_exporters/netlist_exporter_generic.cpp +++ b/eeschema/netlist_exporters/netlist_exporter_generic.cpp @@ -26,6 +26,9 @@ #include "netlist_exporter_generic.h" #include +#include +#include +#include #include #include @@ -499,44 +502,94 @@ XNODE* NETLIST_EXPORTER_GENERIC::makeListOfNets() m_LibParts.clear(); // must call this function before using m_LibParts. - for( unsigned ii = 0; ii < m_masterList->size(); ii++ ) + if( m_use_graph && m_graph ) { - NETLIST_OBJECT* nitem = m_masterList->GetItem( ii ); - SCH_COMPONENT* comp; - - // New net found, write net id; - if( ( netCode = nitem->GetNet() ) != lastNetCode ) + for( auto it : m_graph->m_net_code_to_subgraphs_map ) { - sameNetcodeCount = 0; // item count for this net - netName = nitem->GetNetName(); - lastNetCode = netCode; + bool added = false; + + auto code = it.first; + auto subgraphs = it.second; + auto net_name = subgraphs[0]->GetNetName(); + + XNODE* xnode; + + for( auto subgraph : subgraphs ) + { + auto sheet = subgraph->m_sheet; + + for( auto item : subgraph->m_items ) + { + if( item->Type() == SCH_PIN_CONNECTION_T ) + { + auto pc = static_cast( item ); + + // Skip power symbols + if( (LIB_PART*)( pc->m_pin->GetParent() )->IsPower() ) + continue; + + if( !added ) + { + xnets->AddChild( xnet = node( "net" ) ); + netCodeTxt.Printf( "%d", code ); + xnet->AddAttribute( "code", netCodeTxt ); + xnet->AddAttribute( "name", net_name ); + + added = true; + } + + auto refText = pc->m_comp->GetRef( &sheet ); + auto pinText = pc->m_pin->GetNumber(); + + xnet->AddChild( xnode = node( "node" ) ); + xnode->AddAttribute( "ref", refText ); + xnode->AddAttribute( "pin", pinText ); + } + } + } } - - if( nitem->m_Type != NET_PIN ) - continue; - - if( nitem->m_Flag != 0 ) // Redundant pin, skip it - continue; - - comp = nitem->GetComponentParent(); - - // Get the reference for the net name and the main parent component - ref = comp->GetRef( &nitem->m_SheetPath ); - if( ref[0] == wxChar( '#' ) ) - continue; - - if( ++sameNetcodeCount == 1 ) + } + else + { + for( unsigned ii = 0; ii < m_masterList->size(); ii++ ) { - xnets->AddChild( xnet = node( "net" ) ); - netCodeTxt.Printf( "%d", netCode ); - xnet->AddAttribute( "code", netCodeTxt ); - xnet->AddAttribute( "name", netName ); - } + NETLIST_OBJECT* nitem = m_masterList->GetItem( ii ); + SCH_COMPONENT* comp; - XNODE* xnode; - xnet->AddChild( xnode = node( "node" ) ); - xnode->AddAttribute( "ref", ref ); - xnode->AddAttribute( "pin", nitem->GetPinNumText() ); + // New net found, write net id; + if( ( netCode = nitem->GetNet() ) != lastNetCode ) + { + sameNetcodeCount = 0; // item count for this net + netName = nitem->GetNetName(); + lastNetCode = netCode; + } + + if( nitem->m_Type != NET_PIN ) + continue; + + if( nitem->m_Flag != 0 ) // Redundant pin, skip it + continue; + + comp = nitem->GetComponentParent(); + + // Get the reference for the net name and the main parent component + ref = comp->GetRef( &nitem->m_SheetPath ); + if( ref[0] == wxChar( '#' ) ) + continue; + + if( ++sameNetcodeCount == 1 ) + { + xnets->AddChild( xnet = node( "net" ) ); + netCodeTxt.Printf( "%d", netCode ); + xnet->AddAttribute( "code", netCodeTxt ); + xnet->AddAttribute( "name", netName ); + } + + XNODE* xnode; + xnet->AddChild( xnode = node( "node" ) ); + xnode->AddAttribute( "ref", ref ); + xnode->AddAttribute( "pin", nitem->GetPinNumText() ); + } } return xnets; diff --git a/eeschema/netlist_exporters/netlist_exporter_generic.h b/eeschema/netlist_exporters/netlist_exporter_generic.h index bbf4305b2f..6bf91c6955 100644 --- a/eeschema/netlist_exporters/netlist_exporter_generic.h +++ b/eeschema/netlist_exporters/netlist_exporter_generic.h @@ -33,6 +33,7 @@ #include +class CONNECTION_GRAPH; class SYMBOL_LIB_TABLE; #define GENERIC_INTERMEDIATE_NETLIST_EXT wxT( "xml" ) @@ -63,10 +64,20 @@ private: SYMBOL_LIB_TABLE* m_libTable; +protected: + CONNECTION_GRAPH* m_graph; + + // TODO(JE) Remove if not needed + bool m_use_graph; + public: - NETLIST_EXPORTER_GENERIC( SCH_EDIT_FRAME* aFrame, NETLIST_OBJECT_LIST* aMasterList ) : + NETLIST_EXPORTER_GENERIC( SCH_EDIT_FRAME* aFrame, + NETLIST_OBJECT_LIST* aMasterList, + CONNECTION_GRAPH* aGraph = nullptr ) : NETLIST_EXPORTER( aMasterList ), - m_libTable( aFrame->Prj().SchSymbolLibTable() ) + m_libTable( aFrame->Prj().SchSymbolLibTable() ), + m_graph( aGraph ), + m_use_graph( true ) {} /** diff --git a/eeschema/netlist_exporters/netlist_exporter_kicad.cpp b/eeschema/netlist_exporters/netlist_exporter_kicad.cpp index f000f8bcc1..b1326a7f70 100644 --- a/eeschema/netlist_exporters/netlist_exporter_kicad.cpp +++ b/eeschema/netlist_exporters/netlist_exporter_kicad.cpp @@ -24,15 +24,22 @@ */ +#include #include #include #include #include +#include +#include #include "netlist_exporter_kicad.h" bool NETLIST_EXPORTER_KICAD::WriteNetlist( const wxString& aOutFileName, unsigned aNetlistOptions ) { + wxASSERT( m_graph ); + + m_use_graph = true; + try { FILE_OUTPUTFORMATTER formatter( aOutFileName ); @@ -45,6 +52,130 @@ bool NETLIST_EXPORTER_KICAD::WriteNetlist( const wxString& aOutFileName, unsigne return false; } + /** + * Temporary QC measure: + * Generate the netlist again using the old algorithm and compare. + * In theory, if the schematic does not use any of the new bus techniques + * (bus aliases, bus groups, etc) they should match. If not, we can throw + * a warning and generate some debug output to fix the new netlister. + * + * This whole block can be removed once we are confident in the new code. + */ + + if( !m_graph->UsesNewBusFeatures() ) + { + m_use_graph = false; + auto old_nets = makeListOfNets(); + + bool different = false; + + for( auto it : m_graph->m_net_code_to_subgraphs_map ) + { + // auto code = it.first; + auto subgraphs = it.second; + auto net_name = subgraphs[0]->GetNetName(); + + std::set net_pins; + + for( auto subgraph : subgraphs ) + { + auto sheet = subgraph->m_sheet; + + for( auto item : subgraph->m_items ) + { + if( item->Type() == SCH_PIN_CONNECTION_T ) + { + auto pc = static_cast( item ); + + if( pc->m_pin->IsPowerConnection() || + (LIB_PART*)( pc->m_pin->GetParent() )->IsPower() ) + continue; + + wxString refText = pc->m_comp->GetRef( &sheet ); + wxString pinText = pc->m_pin->GetNumber(); + + net_pins.insert( refText + "-" + pinText ); + } + } + } + + bool found = false; + + // Yes this is slow, but it's a temporary debugging thing. + for( auto kid = old_nets->GetChildren(); kid; kid = kid->GetNext() ) + { + for( auto attr = kid->GetAttributes(); attr; attr = attr->GetNext() ) + { + if( attr->GetName() == "name" && attr->GetValue() == net_name ) + { + found = true; + + // Check members of this net + std::set old_net_pins; + + for( auto pin_node = kid->GetChildren(); + pin_node; pin_node = pin_node->GetNext() ) + { + wxString ref, pin; + + for( auto pin_attr = pin_node->GetAttributes(); + pin_attr; pin_attr = pin_attr->GetNext() ) + { + if( pin_attr->GetName() == "ref" ) + ref = pin_attr->GetValue(); + + if( pin_attr->GetName() == "pin" ) + pin = pin_attr->GetValue(); + } + + old_net_pins.insert( ref + "-" + pin ); + } + + std::vector difference( std::max( net_pins.size(), + old_net_pins.size() ) ); + + auto end = std::set_symmetric_difference( net_pins.begin(), + net_pins.end(), + old_net_pins.begin(), + old_net_pins.end(), + difference.begin() ); + + difference.resize( end - difference.begin() ); + + if( difference.size() > 0 ) + { + different = true; + } + } + } + } + + if( !found ) + { + different = true; + } + } + + if( different ) + { + wxLogDebug( "NOTE: New netlist algorithm is inconsistent with old! " + "Please contact Jon Evans " + "to help debug this issue" ); + + try + { + FILE_OUTPUTFORMATTER formatter( aOutFileName + ".old_algo" ); + Format( &formatter, GNL_ALL ); + } + + catch( const IO_ERROR& ioe ) + { + DisplayError( NULL, ioe.What() ); + return false; + } + } + } + return true; } diff --git a/eeschema/netlist_exporters/netlist_exporter_kicad.h b/eeschema/netlist_exporters/netlist_exporter_kicad.h index 2a4e8cfe8c..47aaa39f70 100644 --- a/eeschema/netlist_exporters/netlist_exporter_kicad.h +++ b/eeschema/netlist_exporters/netlist_exporter_kicad.h @@ -38,8 +38,10 @@ class OUTPUTFORMATTER; class NETLIST_EXPORTER_KICAD : public NETLIST_EXPORTER_GENERIC { public: - NETLIST_EXPORTER_KICAD( SCH_EDIT_FRAME* aFrame, NETLIST_OBJECT_LIST* aMasterList ) : - NETLIST_EXPORTER_GENERIC( aFrame, aMasterList ) + NETLIST_EXPORTER_KICAD( SCH_EDIT_FRAME* aFrame, + NETLIST_OBJECT_LIST* aMasterList, + CONNECTION_GRAPH* aGraph = nullptr ) : + NETLIST_EXPORTER_GENERIC( aFrame, aMasterList, aGraph ) {} /** diff --git a/eeschema/netlist_generator.cpp b/eeschema/netlist_generator.cpp index 6d3d3690ce..f3d3c0d057 100644 --- a/eeschema/netlist_generator.cpp +++ b/eeschema/netlist_generator.cpp @@ -61,7 +61,8 @@ bool SCH_EDIT_FRAME::WriteNetListFile( NETLIST_OBJECT_LIST* aConnectedItemsList, switch( aFormat ) { case NET_TYPE_PCBNEW: - helper = new NETLIST_EXPORTER_KICAD( this, aConnectedItemsList ); + helper = new NETLIST_EXPORTER_KICAD( this, aConnectedItemsList, + g_ConnectionGraph ); break; case NET_TYPE_ORCADPCB2: @@ -82,7 +83,8 @@ bool SCH_EDIT_FRAME::WriteNetListFile( NETLIST_OBJECT_LIST* aConnectedItemsList, tmpFile.SetExt( GENERIC_INTERMEDIATE_NETLIST_EXT ); fileName = tmpFile.GetFullPath(); - helper = new NETLIST_EXPORTER_GENERIC( this, aConnectedItemsList ); + helper = new NETLIST_EXPORTER_GENERIC( this, aConnectedItemsList, + g_ConnectionGraph ); executeCommandLine = true; } break; diff --git a/eeschema/netlist_object.cpp b/eeschema/netlist_object.cpp index ef8992707b..c800401210 100644 --- a/eeschema/netlist_object.cpp +++ b/eeschema/netlist_object.cpp @@ -30,30 +30,12 @@ #include #include -#include +#include #include +#include #include - -#include - - -/** - * The regular expression string for label bus notation. Valid bus labels are defined as - * one or more non-whitespace characters from the beginning of the string followed by the - * bus notation [nn...mm] with no characters after the closing bracket. - */ -static wxRegEx busLabelRe( wxT( "^([^[:space:]]+)(\\[[\\d]+\\.+[\\d]+\\])$" ), wxRE_ADVANCED ); - - -bool IsBusLabel( const wxString& aLabel ) -{ - wxCHECK_MSG( busLabelRe.IsValid(), false, - wxT( "Invalid regular expression in IsBusLabel()." ) ); - - return busLabelRe.Matches( aLabel ); -} - +#include #if defined(DEBUG) @@ -240,7 +222,8 @@ bool NETLIST_OBJECT::IsLabelConnected( NETLIST_OBJECT* aNetItem ) void NETLIST_OBJECT::ConvertBusToNetListItems( NETLIST_OBJECT_LIST& aNetListItems ) { - wxCHECK_RET( IsBusLabel( m_Label ), + SCH_CONNECTION conn; + wxCHECK_RET( conn.IsBusLabel( m_Label ), wxT( "<" ) + m_Label + wxT( "> is not a valid bus label." ) ); if( m_Type == NET_HIERLABEL ) @@ -254,61 +237,109 @@ void NETLIST_OBJECT::ConvertBusToNetListItems( NETLIST_OBJECT_LIST& aNetListItem else wxCHECK_RET( false, wxT( "Net list object type is not valid." ) ); - unsigned i; - wxString tmp, busName, busNumber; - long begin, end, member; + // NOTE: all netlist objects generated from a single bus definition need to have different + // member codes set. For bus vectors, the member code matches the vector index, but for + // bus groups (including with nested vectors) the code is something arbitrary. + long member_offset = 0; - busName = busLabelRe.GetMatch( m_Label, 1 ); - busNumber = busLabelRe.GetMatch( m_Label, 2 ); - - /* Search for '[' because a bus label is like "busname[nn..mm]" */ - i = busNumber.Find( '[' ); - i++; - - while( i < busNumber.Len() && busNumber[i] != '.' ) + auto alias = SCH_SCREEN::GetBusAlias( m_Label ); + if( alias || conn.IsBusGroupLabel( m_Label ) ) { - tmp.Append( busNumber[i] ); - i++; + wxString group_name; + bool self_set = false; + std::vector bus_contents_vec; + + if( alias ) + { + bus_contents_vec = alias->Members(); + } + else + { + wxCHECK_RET( conn.ParseBusGroup( m_Label, &group_name, bus_contents_vec ), + _( "Failed to parse bus group " ) + m_Label ); + } + + // For named bus groups, like "USB{DP DM}" + auto group_prefix = ( group_name != "" ) ? ( group_name + "." ) : ""; + + std::list bus_contents( bus_contents_vec.begin(), + bus_contents_vec.end() ); + + for( auto bus_member : bus_contents ) + { + // Nested bus vector inside a bus group + if( conn.IsBusVectorLabel( bus_member ) ) + { + wxString prefix; + long begin, end; + + conn.ParseBusVector( bus_member, &prefix, &begin, &end ); + prefix = group_prefix + prefix; + + if( !self_set ) + { + m_Label = prefix; + m_Label << begin; + m_Member = ( begin++ ) + ( member_offset++ ); + + self_set = true; + begin++; + } + + fillBusVector( aNetListItems, prefix, begin, end, member_offset ); + member_offset += std::abs( end - begin ); + } + else if( auto nested_alias = SCH_SCREEN::GetBusAlias( bus_member ) ) + { + // Nested alias inside a group + for( auto alias_member : nested_alias->Members() ) + { + bus_contents.push_back( alias_member ); + } + } + else + { + if( !self_set ) + { + m_Label = group_prefix + bus_member; + m_Member = member_offset++; + self_set = true; + } + else + { + auto item = new NETLIST_OBJECT( *this ); + item->m_Label = group_prefix + bus_member; + item->m_Member = member_offset++; + aNetListItems.push_back( item ); + } + } + } } - - tmp.ToLong( &begin ); - - while( i < busNumber.Len() && busNumber[i] == '.' ) - i++; - - tmp.Empty(); - - while( i < busNumber.Len() && busNumber[i] != ']' ) + else { - tmp.Append( busNumber[i] ); - i++; + // Plain bus vector + wxString prefix; + long begin, end; + + conn.ParseBusVector( m_Label, &prefix, &begin, &end ); + + m_Label = prefix; + m_Label << begin; + m_Member = begin; + + fillBusVector( aNetListItems, prefix, begin + 1, end, 0 ); } +} - tmp.ToLong( &end ); - - if( begin < 0 ) - begin = 0; - - if( end < 0 ) - end = 0; - - if( begin > end ) - std::swap( begin, end ); - - member = begin; - tmp = busName; - tmp << member; - m_Label = tmp; - m_Member = member; - - for( member++; member <= end; member++ ) +void NETLIST_OBJECT::fillBusVector( NETLIST_OBJECT_LIST& aNetListItems, + wxString aName, long aBegin, long aEnd, long aOffset ) +{ + for( long member = aBegin; member <= aEnd; member++ ) { - NETLIST_OBJECT* item = new NETLIST_OBJECT( *this ); + auto item = new NETLIST_OBJECT( *this ); - // Conversion of bus label to the root name + the current member id. - tmp = busName; - tmp << member; - item->m_Label = tmp; + item->m_Label = aName; + item->m_Label << member; item->m_Member = member; aNetListItems.push_back( item ); diff --git a/eeschema/netlist_object.h b/eeschema/netlist_object.h index 867c32c827..7dd542d3a7 100644 --- a/eeschema/netlist_object.h +++ b/eeschema/netlist_object.h @@ -264,6 +264,21 @@ public: * the bus label NETLIST_OBJECTs. */ void ConvertBusToNetListItems( NETLIST_OBJECT_LIST& aNetListItems ); + +private: + /** + * Given a bus vector, append the appropriate members into the list + * If given something like "DATA", 7, 0, will append "DATA7", "DATA6", etc. + * + * @param aNetListItems is the list to append to + * @param aName is the prefix for the vector, like "DATA" + * @param aBegin is the first entry in the vector + * @param aEnd is the last entry in the vector + * @param aOffset is an offset to add to the member code for each member + */ + void fillBusVector( NETLIST_OBJECT_LIST& aNetListItems, wxString aName, + long aBegin, long aEnd, long aOffset ); + }; diff --git a/eeschema/onleftclick.cpp b/eeschema/onleftclick.cpp index c15ee7898b..0cb0ebc6ed 100644 --- a/eeschema/onleftclick.cpp +++ b/eeschema/onleftclick.cpp @@ -408,7 +408,7 @@ void SCH_EDIT_FRAME::OnLeftDClick( wxDC* aDC, const wxPoint& aPosition ) switch( item->Type() ) { case SCH_SHEET_T: - m_CurrentSheet->push_back( (SCH_SHEET*) item ); + g_CurrentSheet->push_back( (SCH_SHEET*) item ); DisplayCurrentSheet(); break; diff --git a/eeschema/onrightclick.cpp b/eeschema/onrightclick.cpp index 273547635a..cb4d940977 100644 --- a/eeschema/onrightclick.cpp +++ b/eeschema/onrightclick.cpp @@ -34,9 +34,10 @@ #include #include +#include #include #include -#include +#include #include #include #include @@ -48,6 +49,7 @@ #include #include #include +#include #include #include @@ -208,7 +210,7 @@ bool SCH_EDIT_FRAME::OnRightClick( const wxPoint& aPosition, wxMenu* PopMenu ) PopMenu->AppendSeparator(); } - if( m_CurrentSheet->Last() != g_RootSheet ) + if( g_CurrentSheet->Last() != g_RootSheet ) { msg = AddHotkeyName( _( "Leave Sheet" ), g_Schematic_Hokeys_Descr, HK_LEAVE_SHEET ); AddMenuItem( PopMenu, ID_POPUP_SCH_LEAVE_SHEET, msg, @@ -766,6 +768,58 @@ void AddMenusForBus( wxMenu* PopMenu, SCH_LINE* Bus, SCH_EDIT_FRAME* frame ) AddMenuItem( PopMenu, ID_POPUP_SCH_BREAK_WIRE, _( "Break Bus" ), KiBitmap( break_bus_xpm ) ); + // Bus unfolding menu (only available if bus is properly defined) + auto connection = Bus->Connection( *g_CurrentSheet ); + + if( connection && connection->IsBus() && connection->Members().size() > 0 ) + { + int idx = 0; + wxMenu* bus_unfolding_menu = new wxMenu; + + PopMenu->AppendSubMenu( bus_unfolding_menu, _( "Unfold Bus" ) ); + + for( const auto& member : connection->Members() ) + { + int id = ID_POPUP_SCH_UNFOLD_BUS + ( idx++ ); + auto name = member->Name( true ); + + if( member->Type() == CONNECTION_BUS ) + { + wxMenu* submenu = new wxMenu; + bus_unfolding_menu->AppendSubMenu( submenu, _( name ) ); + + for( const auto& sub_member : member->Members() ) + { + id = ID_POPUP_SCH_UNFOLD_BUS + ( idx++ ); + + submenu->Append( id, sub_member->Name( true ), wxEmptyString ); + + // See comment in else clause below + auto sub_item_clone = new wxMenuItem(); + sub_item_clone->SetItemLabel( sub_member->Name( true ) ); + + frame->Bind( wxEVT_COMMAND_MENU_SELECTED, &SCH_EDIT_FRAME::OnUnfoldBus, + frame, id, id, sub_item_clone ); + } + } + else + { + bus_unfolding_menu->Append( id, name, wxEmptyString ); + + // Because Bind() takes ownership of the user data item, we + // make a new menu item here and set its label. Why create a + // menu item instead of just a wxString or something? Because + // Bind() requires a pointer to wxObject rather than a void + // pointer. Maybe at some point I'll think of a better way... + auto item_clone = new wxMenuItem(); + item_clone->SetItemLabel( name ); + + frame->Bind( wxEVT_COMMAND_MENU_SELECTED, &SCH_EDIT_FRAME::OnUnfoldBus, + frame, id, id, item_clone ); + } + } + } + PopMenu->AppendSeparator(); msg = AddHotkeyName( _( "Add Junction" ), g_Schematic_Hokeys_Descr, HK_ADD_JUNCTION ); AddMenuItem( PopMenu, ID_POPUP_SCH_ADD_JUNCTION, msg, KiBitmap( add_junction_xpm ) ); diff --git a/eeschema/sch_base_frame.cpp b/eeschema/sch_base_frame.cpp index 741ccaa593..5c323470a4 100644 --- a/eeschema/sch_base_frame.cpp +++ b/eeschema/sch_base_frame.cpp @@ -441,6 +441,8 @@ void SCH_BASE_FRAME::RedrawScreen( const wxPoint& aCenterPoint, bool aWarpPointe else GetCanvas()->GetView()->SetScale( scale ); + GetCanvas()->GetView()->SetCenter( aCenterPoint ); + if( aWarpPointer ) GetCanvas()->GetViewControls()->CenterOnCursor(); @@ -623,31 +625,57 @@ void SCH_BASE_FRAME::RefreshItem( SCH_ITEM* aItem, bool isAddOrDelete ) } -void SCH_BASE_FRAME::AddToScreen( SCH_ITEM* aItem ) +void SCH_BASE_FRAME::AddToScreen( SCH_ITEM* aItem, SCH_SCREEN* aScreen ) { - GetScreen()->Append( aItem ); - GetCanvas()->GetView()->Add( aItem ); - RefreshItem( aItem, true ); // handle any additional parent semantics + auto screen = aScreen; + + if( aScreen == nullptr ) + screen = GetScreen(); + + screen->Append( aItem ); + + if( screen == GetScreen() ) + { + GetCanvas()->GetView()->Add( aItem ); + RefreshItem( aItem, true ); // handle any additional parent semantics + } } -void SCH_BASE_FRAME::AddToScreen( DLIST& aItems ) +void SCH_BASE_FRAME::AddToScreen( DLIST& aItems, SCH_SCREEN* aScreen ) { - for( SCH_ITEM* item = aItems.begin(); item; item = item->Next() ) + auto screen = aScreen; + + if( aScreen == nullptr ) + screen = GetScreen(); + + if( screen == GetScreen() ) { - GetCanvas()->GetView()->Add( item ); - RefreshItem( item, true ); // handle any additional parent semantics + for( SCH_ITEM* item = aItems.begin(); item; item = item->Next() ) + { + GetCanvas()->GetView()->Add( item ); + RefreshItem( item, true ); // handle any additional parent semantics + } } - GetScreen()->Append( aItems ); + screen->Append( aItems ); } -void SCH_BASE_FRAME::RemoveFromScreen( SCH_ITEM* aItem ) +void SCH_BASE_FRAME::RemoveFromScreen( SCH_ITEM* aItem, SCH_SCREEN* aScreen ) { - GetCanvas()->GetView()->Remove( aItem ); - GetScreen()->Remove( aItem ); - RefreshItem( aItem, true ); // handle any additional parent semantics + auto screen = aScreen; + + if( aScreen == nullptr ) + screen = GetScreen(); + + if( screen == GetScreen() ) + GetCanvas()->GetView()->Remove( aItem ); + + screen->Remove( aItem ); + + if( screen == GetScreen() ) + RefreshItem( aItem, true ); // handle any additional parent semantics } diff --git a/eeschema/sch_base_frame.h b/eeschema/sch_base_frame.h index 876303963b..d49f40558e 100644 --- a/eeschema/sch_base_frame.h +++ b/eeschema/sch_base_frame.h @@ -298,18 +298,21 @@ public: /** * Add an item to the screen (and view) + * aScreen is the screen the item is located on, if not the current screen */ - void AddToScreen( SCH_ITEM* aItem ); + void AddToScreen( SCH_ITEM* aItem, SCH_SCREEN* aScreen = nullptr ); /** * Add a list of items to the screen (and view) + * aScreen is the screen the item is located on, if not the current screen */ - void AddToScreen( DLIST& aItems ); + void AddToScreen( DLIST& aItems, SCH_SCREEN* aScreen = nullptr ); /** * Remove an item from the screen (and view) + * aScreen is the screen the item is located on, if not the current screen */ - void RemoveFromScreen( SCH_ITEM* aItem ); + void RemoveFromScreen( SCH_ITEM* aItem, SCH_SCREEN* aScreen = nullptr ); /** * Mark an item for refresh. diff --git a/eeschema/sch_bus_entry.cpp b/eeschema/sch_bus_entry.cpp index 5faa48e861..e07bf632e4 100644 --- a/eeschema/sch_bus_entry.cpp +++ b/eeschema/sch_bus_entry.cpp @@ -40,6 +40,7 @@ #include #include #include +#include SCH_BUS_ENTRY_BASE::SCH_BUS_ENTRY_BASE( KICAD_T aType, const wxPoint& pos, char shape ) : @@ -59,12 +60,15 @@ SCH_BUS_WIRE_ENTRY::SCH_BUS_WIRE_ENTRY( const wxPoint& pos, char shape ) : SCH_BUS_ENTRY_BASE( SCH_BUS_WIRE_ENTRY_T, pos, shape ) { m_Layer = LAYER_WIRE; + m_connected_bus_item = nullptr; } SCH_BUS_BUS_ENTRY::SCH_BUS_BUS_ENTRY( const wxPoint& pos, char shape ) : SCH_BUS_ENTRY_BASE( SCH_BUS_BUS_ENTRY_T, pos, shape ) { m_Layer = LAYER_BUS; + m_connected_bus_items[0] = nullptr; + m_connected_bus_items[1] = nullptr; } EDA_ITEM* SCH_BUS_WIRE_ENTRY::Clone() const @@ -416,3 +420,30 @@ char SCH_BUS_ENTRY_BASE::GetBusEntryShape() const else return '\\'; } + + +void SCH_BUS_ENTRY_BASE::GetMsgPanelInfo( EDA_UNITS_T aUnits, MSG_PANEL_ITEMS& aList ) +{ + if( auto conn = Connection( *g_CurrentSheet ) ) + { +#if defined(DEBUG) + conn->AppendDebugInfoToMsgPanel( aList ); +#else + conn->AppendInfoToMsgPanel( aList ); +#endif + } +} + + +bool SCH_BUS_WIRE_ENTRY::ConnectionPropagatesTo( const EDA_ITEM* aItem ) const +{ + // Don't generate connections between bus entries and buses, since there is + // a connectivity change at that point (e.g. A[7..0] to A7) + if( ( aItem->Type() == SCH_LINE_T ) && + ( static_cast( aItem )->GetLayer() == LAYER_BUS ) ) + { + return false; + } + + return true; +} diff --git a/eeschema/sch_bus_entry.h b/eeschema/sch_bus_entry.h index 967f49bf0d..8d4fafc3f9 100644 --- a/eeschema/sch_bus_entry.h +++ b/eeschema/sch_bus_entry.h @@ -120,6 +120,8 @@ public: void Plot( PLOTTER* aPlotter ) override; + void GetMsgPanelInfo( EDA_UNITS_T aUnits, std::vector< MSG_PANEL_ITEM >& aList ) override; + #if defined(DEBUG) void Show( int nestLevel, std::ostream& os ) const override { ShowDummy( os ); } #endif @@ -157,9 +159,17 @@ public: EDA_ITEM* Clone() const override; + virtual bool ConnectionPropagatesTo( const EDA_ITEM* aItem ) const override; + BITMAP_DEF GetMenuImage() const override; bool UpdateDanglingState( std::vector& aItemList ) override; + + /** + * Pointer to the bus item (usually a bus wire) connected to this bus-wire + * entry, if it is connected to one. + */ + SCH_ITEM* m_connected_bus_item; }; /** @@ -193,6 +203,12 @@ public: BITMAP_DEF GetMenuImage() const override; bool UpdateDanglingState( std::vector& aItemList ) override; + + /** + * Pointer to the bus items (usually bus wires) connected to this bus-bus + * entry (either or both may be nullptr) + */ + SCH_ITEM* m_connected_bus_items[2]; }; #endif // _SCH_BUS_ENTRY_H_ diff --git a/eeschema/sch_component.cpp b/eeschema/sch_component.cpp index 028ba32c6b..e15e2b5ba8 100644 --- a/eeschema/sch_component.cpp +++ b/eeschema/sch_component.cpp @@ -123,7 +123,6 @@ SCH_COMPONENT::SCH_COMPONENT( const wxPoint& aPos, SCH_ITEM* aParent ) : SCH_ITEM( aParent, SCH_COMPONENT_T ) { Init( aPos ); - m_currentSheetPath = NULL; m_fieldsAutoplaced = AUTOPLACED_NO; } @@ -138,7 +137,6 @@ SCH_COMPONENT::SCH_COMPONENT( LIB_PART& aPart, LIB_ID aLibId, SCH_SHEET_PATH* sh m_convert = convert; m_lib_id = aLibId; m_part = aPart.SharedPtr(); - m_currentSheetPath = NULL; m_fieldsAutoplaced = AUTOPLACED_NO; SetTimeStamp( GetNewTimeStamp() ); @@ -163,7 +161,6 @@ SCH_COMPONENT::SCH_COMPONENT( LIB_PART& aPart, LIB_ID aLibId, SCH_SHEET_PATH* sh SCH_COMPONENT::SCH_COMPONENT( const SCH_COMPONENT& aComponent ) : SCH_ITEM( aComponent ) { - m_currentSheetPath = NULL; m_Parent = aComponent.m_Parent; m_Pos = aComponent.m_Pos; m_unit = aComponent.m_unit; @@ -471,6 +468,7 @@ void SCH_COMPONENT::UpdatePinCache() if( PART_SPTR part = m_part.lock() ) { m_Pins.clear(); + for( LIB_PIN* pin = part->GetNextPin(); pin; pin = part->GetNextPin( pin ) ) { wxASSERT( pin->Type() == LIB_PIN_T ); @@ -534,6 +532,54 @@ void SCH_COMPONENT::UpdateAllPinCaches( const SCH_COLLECTOR& aComponents ) } +void SCH_COMPONENT::UpdatePinConnections( SCH_SHEET_PATH aSheet ) +{ + if( PART_SPTR part = m_part.lock() ) + { + for( LIB_PIN* pin = part->GetNextPin(); pin; pin = part->GetNextPin( pin ) ) + { + wxASSERT( pin->Type() == LIB_PIN_T ); + + if( pin->GetUnit() && m_unit && ( m_unit != pin->GetUnit() ) ) + continue; + + if( pin->GetConvert() && m_convert && ( m_convert != pin->GetConvert() ) ) + continue; + + SCH_PIN_CONNECTION* connection = nullptr; + + try + { + connection = m_pin_connections.at( pin ); + } + catch( const std::out_of_range& oor ) + { + connection = new SCH_PIN_CONNECTION(); + m_pin_connections[ pin ] = connection; + } + + connection->m_pin = pin; + connection->m_comp = this; + connection->InitializeConnection( aSheet ); + } + } +} + + +SCH_PIN_CONNECTION* SCH_COMPONENT::GetConnectionForPin( LIB_PIN* aPin ) +{ + try + { + return m_pin_connections.at( aPin ); + } + catch( const std::out_of_range& oor ) + { + return nullptr; + } + +} + + void SCH_COMPONENT::SetUnit( int aUnit ) { if( m_unit != aUnit ) @@ -1436,9 +1482,9 @@ void SCH_COMPONENT::GetMsgPanelInfo( EDA_UNITS_T aUnits, MSG_PANEL_ITEMS& aList if( !alias ) return; - if( m_currentSheetPath ) + if( g_CurrentSheet ) aList.push_back( MSG_PANEL_ITEM( _( "Reference" ), - GetRef( m_currentSheetPath ), + GetRef( g_CurrentSheet ), DARKCYAN ) ); msg = part->IsPower() ? _( "Power symbol" ) : _( "Value" ); @@ -1476,8 +1522,8 @@ void SCH_COMPONENT::GetMsgPanelInfo( EDA_UNITS_T aUnits, MSG_PANEL_ITEMS& aList } else { - if( m_currentSheetPath ) - aList.push_back( MSG_PANEL_ITEM( _( "Reference" ), GetRef( m_currentSheetPath ), + if( g_CurrentSheet ) + aList.push_back( MSG_PANEL_ITEM( _( "Reference" ), GetRef( g_CurrentSheet ), DARKCYAN ) ); aList.push_back( MSG_PANEL_ITEM( _( "Value" ), GetField( VALUE )->GetShownText(), diff --git a/eeschema/sch_component.h b/eeschema/sch_component.h index 5219821bca..db24c58bc2 100644 --- a/eeschema/sch_component.h +++ b/eeschema/sch_component.h @@ -40,6 +40,7 @@ #include #include #include +#include class SCH_SCREEN; class SCH_SHEET_PATH; @@ -71,6 +72,7 @@ class SCH_COMPONENT : public SCH_ITEM { public: enum AUTOPLACED { AUTOPLACED_NO = 0, AUTOPLACED_AUTO, AUTOPLACED_MANUAL }; + private: wxPoint m_Pos; @@ -97,14 +99,6 @@ private: AUTOPLACED m_fieldsAutoplaced; ///< indicates status of field autoplacement - /** - * A temporary sheet path is required to generate the correct reference designator string - * in complex hierarchies. Hopefully this is only a temporary hack to decouple schematic - * objects from the drawing window until a better design for handling complex hierarchies - * can be implemented. - */ - const SCH_SHEET_PATH* m_currentSheetPath; - /** * Defines the hierarchical path and reference of the component. This allows support * for hierarchical sheets that reference the same schematic. The format for the path @@ -113,6 +107,8 @@ private: */ wxArrayString m_PathsAndReferences; + std::unordered_map m_pin_connections; + void Init( const wxPoint& pos = wxPoint( 0, 0 ) ); public: @@ -219,6 +215,21 @@ public: */ static void UpdateAllPinCaches( const SCH_COLLECTOR& aComponents ); + /** + * Updates the local cache of SCH_PIN_CONNECTION objects for each pin + */ + void UpdatePinConnections( SCH_SHEET_PATH aSheet ); + + /** + * Retrieves the pin connection for a given pin of the component + */ + SCH_PIN_CONNECTION* GetConnectionForPin( LIB_PIN* aPin ); + + const std::unordered_map& PinConnections() + { + return m_pin_connections; + } + /** * Change the unit number to \a aUnit * @@ -513,11 +524,6 @@ public: */ static bool IsReferenceStringValid( const wxString& aReferenceString ); - void SetCurrentSheetPath( const SCH_SHEET_PATH* aSheetPath ) - { - m_currentSheetPath = aSheetPath; - } - /** * Return the reference for the given sheet path. * @@ -666,5 +672,4 @@ private: bool doIsConnected( const wxPoint& aPosition ) const override; }; - #endif /* COMPONENT_CLASS_H */ diff --git a/eeschema/sch_connection.cpp b/eeschema/sch_connection.cpp new file mode 100644 index 0000000000..bc8ade6b9f --- /dev/null +++ b/eeschema/sch_connection.cpp @@ -0,0 +1,469 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2018 CERN + * @author Jon Evans + * + * 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, see . + */ + +#include +#include + +#include +#include +#include + +#include + + +/** + * + * Buses can be defined in multiple ways. A bus vector consists of a prefix and + * a numeric range of suffixes: + * + * BUS_NAME[M..N] + * + * For example, the bus A[3..0] will contain nets A3, A2, A1, and A0. + * The BUS_NAME is required. M and N must be integers but do not need to be in + * any particular order -- A[0..3] produces the same result. + * + * Like net names, bus names cannot contain whitespace. + * + * A bus group is just a grouping of signals, separated by spaces, some + * of which may be bus vectors. Bus groups can have names, but do not need to. + * + * MEMORY{A[15..0] D[7..0] RW CE OE} + * + * In named bus groups, the net names are expanded as . + * In the above example, the nets would be named like MEMORY.A15, MEMORY.D0, etc. + * + * {USB_DP USB_DN} + * + * In the above example, the bus is unnamed and so the underlying net names are + * just USB_DP and USB_DN. + * + */ +static boost::regex bus_label_re( "^([^[:space:]]+)(\\[[\\d]+\\.+[\\d]+\\])$" ); + +static boost::regex bus_group_label_re( "^([^[:space:]]+)?\\{((?:[^[:space:]]+(?:\\[[\\d]+\\.+[\\d]+\\])? ?)+)\\}$" ); + + +SCH_CONNECTION::SCH_CONNECTION( SCH_ITEM* aParent, SCH_SHEET_PATH aPath ) : + m_sheet( aPath ), + m_parent( aParent ) +{ + Reset(); +} + + +bool SCH_CONNECTION::operator==( const SCH_CONNECTION& aOther ) const +{ + // NOTE: Not comparing m_dirty or net/bus/subgraph codes + if( ( aOther.m_driver == m_driver ) && + ( aOther.m_type == m_type ) && + ( aOther.m_name == m_name ) && + ( aOther.m_sheet == m_sheet ) ) + { + return true; + } + + return false; +} + + +void SCH_CONNECTION::SetDriver( SCH_ITEM* aItem ) +{ + m_driver = aItem; + + for( auto member : m_members ) + member->SetDriver( aItem ); +} + + +void SCH_CONNECTION::SetSheet( SCH_SHEET_PATH aSheet ) +{ + m_sheet = aSheet; + + for( auto member : m_members ) + member->SetSheet( aSheet ); +} + + +bool SCH_CONNECTION::operator!=( const SCH_CONNECTION& aOther ) const +{ + return !( aOther == *this ); +} + + +void SCH_CONNECTION::ConfigureFromLabel( wxString aLabel ) +{ + if( IsBusVectorLabel( aLabel ) ) + { + m_name = aLabel; + m_type = CONNECTION_BUS; + + ParseBusVector( aLabel, &m_vector_prefix, &m_vector_start, &m_vector_end ); + + for( long i = m_vector_start; i <= m_vector_end; ++i ) + { + auto member = std::make_shared< SCH_CONNECTION >( m_parent, m_sheet ); + wxString name = m_vector_prefix; + name << i; + member->m_type = CONNECTION_NET; + member->m_name = m_prefix + name; + member->m_vector_index = i; + m_members.push_back( member ); + } + } + else if( IsBusGroupLabel( aLabel ) ) + { + m_type = CONNECTION_BUS_GROUP; + m_name = aLabel; + + std::vector members; + wxString group_name; + + if( ParseBusGroup( aLabel, &group_name, members ) ) + { + // Named bus groups generate a net prefix, unnamed ones don't + auto prefix = ( group_name != "" ) ? ( group_name + "." ) : ""; + + for( auto group_member : members ) + { + // Handle alias inside bus group member list + if( auto alias = g_ConnectionGraph->GetBusAlias( group_member ) ) + { + for( auto alias_member : alias->Members() ) + { + auto member = std::make_shared< SCH_CONNECTION >( m_parent, m_sheet ); + member->SetPrefix( prefix ); + member->ConfigureFromLabel( alias_member ); + m_members.push_back( member ); + } + } + else + { + auto member = std::make_shared< SCH_CONNECTION >( m_parent, m_sheet ); + member->SetPrefix( prefix ); + member->ConfigureFromLabel( group_member ); + m_members.push_back( member ); + } + } + } + } + else if( auto alias = g_ConnectionGraph->GetBusAlias( aLabel ) ) + { + m_type = CONNECTION_BUS_GROUP; + m_name = aLabel; + + for( auto alias_member : alias->Members() ) + { + auto member = std::make_shared< SCH_CONNECTION >( m_parent ); + member->ConfigureFromLabel( alias_member ); + m_members.push_back( member ); + } + } + else + { + m_name = m_prefix + aLabel; + m_type = CONNECTION_NET; + } +} + + +void SCH_CONNECTION::Reset() +{ + m_type = CONNECTION_NONE; + m_name = ""; + m_prefix = ""; + m_driver = nullptr; + m_members.clear(); + m_dirty = true; + m_net_code = 0; + m_bus_code = 0; + m_subgraph_code = 0; + m_vector_start = 0; + m_vector_end = 0; + m_vector_index = 0; + m_vector_prefix = ""; +} + + +void SCH_CONNECTION::Clone( SCH_CONNECTION& aOther ) +{ + m_type = aOther.Type(); + m_driver = aOther.Driver(); + m_sheet = aOther.Sheet(); + m_name = aOther.Name( true ); + m_prefix = aOther.Prefix(); + m_members = aOther.Members(); + m_net_code = aOther.NetCode(); + m_bus_code = aOther.BusCode(); + //m_subgraph_code = aOther.SubgraphCode(); + m_vector_start = aOther.VectorStart(); + m_vector_end = aOther.VectorEnd(); + m_vector_index = aOther.VectorIndex(); + m_vector_prefix = aOther.VectorPrefix(); +} + + +bool SCH_CONNECTION::IsDriver() const +{ + wxASSERT( Parent() ); + + switch( Parent()->Type() ) + { + case SCH_LABEL_T: + case SCH_GLOBAL_LABEL_T: + case SCH_HIERARCHICAL_LABEL_T: + case SCH_PIN_CONNECTION_T: + case SCH_SHEET_PIN_T: + case SCH_SHEET_T: + case LIB_PIN_T: + return true; + + default: + return false; + } +} + + +wxString SCH_CONNECTION::Name( bool aIgnoreSheet ) const +{ + wxString ret = m_name; + + if( !Parent() || m_type == CONNECTION_NONE ) + return ret; + + bool prepend_path = true; + + switch( Parent()->Type() ) + { + case SCH_PIN_CONNECTION_T: + // Pins are either power connections or belong to a uniquely-annotated + // component, so they don't need a path + prepend_path = false; + break; + + case SCH_GLOBAL_LABEL_T: + prepend_path = false; + break; + + default: + break; + } + + if( prepend_path && !aIgnoreSheet ) + ret = m_sheet.PathHumanReadable() + ret; + + return ret; +} + + +void SCH_CONNECTION::AppendInfoToMsgPanel( MSG_PANEL_ITEMS& aList ) const +{ + wxString msg, group_name; + std::vector group_members; + + aList.push_back( MSG_PANEL_ITEM( _( "Connection Name" ), m_name, BROWN ) ); + + msg.Printf( "%d", m_net_code ); + aList.push_back( MSG_PANEL_ITEM( _( "Net Code" ), msg, BROWN ) ); + + if( auto alias = g_ConnectionGraph->GetBusAlias( m_name ) ) + { + msg.Printf( _( "Bus Alias %s Members" ), m_name ); + + wxString members; + + for( auto member : alias->Members() ) + members << member << " "; + + aList.push_back( MSG_PANEL_ITEM( msg, members, RED ) ); + } + else if( ParseBusGroup( m_name, &group_name, group_members ) ) + { + for( auto group_member : group_members ) + { + if( auto group_alias = g_ConnectionGraph->GetBusAlias( group_member ) ) + { + msg.Printf( _( "Bus Alias %s Members" ), group_alias->GetName() ); + + wxString members; + + for( auto member : group_alias->Members() ) + members << member << " "; + + aList.push_back( MSG_PANEL_ITEM( msg, members, RED ) ); + } + } + } +} + + +void SCH_CONNECTION::AppendDebugInfoToMsgPanel( MSG_PANEL_ITEMS& aList ) const +{ + wxString msg; + + AppendInfoToMsgPanel( aList ); + + msg.Printf( "%d", m_bus_code ); + aList.push_back( MSG_PANEL_ITEM( _( "Bus Code" ), msg, BROWN ) ); + + msg.Printf( "%d", m_subgraph_code ); + aList.push_back( MSG_PANEL_ITEM( _( "Subgraph Code" ), msg, BROWN ) ); + + if( auto driver = Driver() ) + { + msg.Printf( "%s at %p", driver->GetSelectMenuText( MILLIMETRES ), driver ); + aList.push_back( MSG_PANEL_ITEM( _( "Connection Source" ), msg, RED ) ); + } + + msg.Printf( "%s at %p", Parent()->GetSelectMenuText( MILLIMETRES ), Parent() ); + aList.push_back( MSG_PANEL_ITEM( _( "Attached To" ), msg, RED ) ); +} + + +bool SCH_CONNECTION::IsBusLabel( const wxString& aLabel ) +{ + return IsBusVectorLabel( aLabel ) || IsBusGroupLabel( aLabel ); +} + + +bool SCH_CONNECTION::IsBusVectorLabel( const wxString& aLabel ) +{ + return boost::regex_match( std::string( aLabel.mb_str() ), bus_label_re ); +} + + +bool SCH_CONNECTION::IsBusGroupLabel( const wxString& aLabel ) +{ + return boost::regex_match( std::string( aLabel.mb_str() ), bus_group_label_re ); +} + + +void SCH_CONNECTION::ParseBusVector( wxString aVector, wxString* aName, + long* begin, long* end ) const +{ + auto ss_vector = std::string( aVector.mb_str() ); + boost::smatch matches; + + if( !boost::regex_match( ss_vector, matches, bus_label_re ) ) + { + wxFAIL_MSG( wxT( "<" ) + aVector + wxT( "> is not a valid bus vector." ) ); + return; + } + + *aName = wxString( matches[1] ); + wxString numberString( matches[2] ); + + // numberString will include the brackets, e.g. [5..0] so skip the first one + size_t i = 1, len = numberString.Len(); + wxString tmp; + + while( i < len && numberString[i] != '.' ) + { + tmp.Append( numberString[i] ); + i++; + } + + tmp.ToLong( begin ); + + while( i < len && numberString[i] == '.' ) + i++; + + tmp.Empty(); + + while( i < len && numberString[i] != ']' ) + { + tmp.Append( numberString[i] ); + i++; + } + + tmp.ToLong( end ); + + if( *begin < 0 ) + *begin = 0; + + if( *end < 0 ) + *end = 0; + + if( *begin > *end ) + std::swap( *begin, *end ); +} + + +bool SCH_CONNECTION::ParseBusGroup( wxString aGroup, wxString* aName, + std::vector& aMemberList ) const +{ + auto ss_group = std::string( aGroup.mb_str() ); + boost::smatch matches; + + if( !boost::regex_match( ss_group, matches, bus_group_label_re ) ) + { + return false; + } + + *aName = wxString( matches[1] ); + + wxStringTokenizer tokenizer( wxString( matches[2] ), " " ); + while( tokenizer.HasMoreTokens() ) + { + aMemberList.push_back( tokenizer.GetNextToken() ); + } + + return true; +} + + +bool SCH_CONNECTION::IsSubsetOf( SCH_CONNECTION* aOther ) const +{ + if( aOther->IsNet() ) + return IsNet() ? ( aOther->Name( true ) == Name( true ) ) : false; + + if( !IsBus() ) + return false; + + std::vector mine, theirs; + + for( auto m : Members() ) + mine.push_back( m->Name( true ) ); + + for( auto m : aOther->Members() ) + theirs.push_back( m->Name( true ) ); + + std::set subset; + + std::set_intersection( mine.begin(), mine.end(), + theirs.begin(), theirs.end(), + std::inserter(subset, subset.begin() ) ); + + return ( !subset.empty() ); +} + + +bool SCH_CONNECTION::IsMemberOfBus( SCH_CONNECTION* aOther ) const +{ + if( !aOther->IsBus() ) + return false; + + auto me = Name( true ); + + for( auto m : aOther->Members() ) + if( m->Name( true ) == me ) + return true; + + return false; +} diff --git a/eeschema/sch_connection.h b/eeschema/sch_connection.h new file mode 100644 index 0000000000..b2e626b6e1 --- /dev/null +++ b/eeschema/sch_connection.h @@ -0,0 +1,347 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2018 CERN + * @author Jon Evans + * + * 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, see . + */ + +#ifndef _SCH_CONNECTION_H +#define _SCH_CONNECTION_H + +#include +#include + +#include +#include + +#include +#include +#include + + +class SCH_ITEM; +class SCH_SHEET_PATH; + + +enum CONNECTION_TYPE +{ + CONNECTION_NONE, ///< No connection to this item + CONNECTION_NET, ///< This item represents a net + CONNECTION_BUS, ///< This item represents a bus vector + CONNECTION_BUS_GROUP, ///< This item represents a bus group +}; + +/** + * Each graphical item can have a SCH_CONNECTION describing its logical + * connection (to a bus or net). These are generated when netlisting, or when + * editing operations that can change the netlist are performed. + * + * In hierarchical schematics, a single SCH_ITEM object can refer to multiple + * distinct parts of a design (in the case of a sub-sheet that is instanced + * more than once in a higher level sheet). Because of this, a single item may + * contain more than one SCH_CONNECTION -- each is specific to a sheet. + * + * Components contain connections for each of their pins (and for each sheet + * they exist on) but don't use their own connection object. + */ +class SCH_CONNECTION +{ +public: + SCH_CONNECTION( SCH_ITEM* aParent = nullptr, SCH_SHEET_PATH aPath = SCH_SHEET_PATH() ); + + ~SCH_CONNECTION() + {} + + /** + * Note: the equality operator for SCH_CONNECTION only tests the net + * properties, not the ownership / sheet location! + */ + bool operator==( const SCH_CONNECTION& aOther ) const; + + bool operator!=( const SCH_CONNECTION& aOther ) const; + + /** + * Configures the connection given a label. + * For CONNECTION_NET, this just sets the name. + * For CONNECTION_BUS, this will deduce the correct BUS_TYPE and also + * generate a correct list of members. + */ + void ConfigureFromLabel( wxString aLabel ); + + /** + * Clears connectivity information + */ + void Reset(); + + /** + * Copies connectivity information (but not parent) from another connection + * + * @param aOther is the connection to clone + */ + void Clone( SCH_CONNECTION& aOther ); + + SCH_ITEM* Parent() const + { + return m_parent; + } + + SCH_ITEM* Driver() const + { + return m_driver; + } + + SCH_SHEET_PATH Sheet() const + { + return m_sheet; + } + + void SetDriver( SCH_ITEM* aItem ); + + void SetSheet( SCH_SHEET_PATH aSheet ); + + /** + * Checks if the SCH_ITEM this connection is attached to can drive connections + * Drivers can be labels, sheet pins, or component pins. + * + * @return true if the attached items is a driver + */ + bool IsDriver() const; + + bool IsBus() const + { + return ( m_type == CONNECTION_BUS || m_type == CONNECTION_BUS_GROUP ); + } + + bool IsNet() const + { + return ( m_type == CONNECTION_NET ); + } + + bool IsDirty() const + { + return m_dirty; + } + + void SetDirty() + { + m_dirty = true; + } + + void ClearDirty() + { + m_dirty = false; + } + + wxString Name( bool aIgnoreSheet = false ) const; + + wxString Prefix() const + { + return m_prefix; + } + + void SetPrefix( wxString aPrefix ) + { + m_prefix = aPrefix; + } + + CONNECTION_TYPE Type() const + { + return m_type; + } + + void SetType( CONNECTION_TYPE aType ) + { + m_type = aType; + } + + int NetCode() const + { + return m_net_code; + } + + void SetNetCode( int aCode ) + { + m_net_code = aCode; + } + + int BusCode() const + { + return m_bus_code; + } + + void SetBusCode( int aCode ) + { + m_bus_code = aCode; + } + + int SubgraphCode() const + { + return m_subgraph_code; + } + + void SetSubgraphCode( int aCode ) + { + m_subgraph_code = aCode; + } + + long VectorStart() const + { + return m_vector_start; + } + + long VectorEnd() const + { + return m_vector_end; + } + + long VectorIndex() const + { + return m_vector_index; + } + + wxString VectorPrefix() const + { + return m_vector_prefix; + } + + std::vector< std::shared_ptr< SCH_CONNECTION > >& Members() + { + return m_members; + } + + const std::vector< std::shared_ptr< SCH_CONNECTION > >& Members() const + { + return m_members; + } + + /** + * Returns true if aOther is a subset of this connection or vice versa. + * + * For plain nets, this just tests whether or not the connectio names are + * the same. For buses, this tests whether the two have any shared members. + * + * Will always return false if one connection is a bus and the other a net. + */ + bool IsSubsetOf( SCH_CONNECTION* aOther ) const; + + /** + * Returns true if this connection is a member of bus connection aOther + * + * Will always return false if aOther is not a bus connection + */ + bool IsMemberOfBus( SCH_CONNECTION* aOther ) const; + + /** + * Parses a bus vector (e.g. A[7..0]) into name, begin, and end. + * Ensures that begin and end are positive and that end > begin. + * + * @param vector is a bus vector label string + * @param name output of the name portion of the label + * @param begin is the first entry in the vector + * @param end is the last entry in the vector + */ + void ParseBusVector( wxString vector, wxString* name, + long* begin, long* end ) const; + + /** + * Parses a bus group label into the name and a list of components + * + * @param aGroup is the input label, e.g. "USB{DP DM}" + * @param name is the output group name, e.g. "USB" + * @param aMemberList is a list of member strings, e.g. "DP", "DM" + * @return true if aGroup was successfully parsed + */ + bool ParseBusGroup( wxString aGroup, wxString* name, + std::vector& aMemberList ) const; + + /** + * Adds information about the connection object to aList + */ + void AppendInfoToMsgPanel( MSG_PANEL_ITEMS& aList ) const; + + /** + * Adds extended debug information about the connection object to aList + */ + void AppendDebugInfoToMsgPanel( MSG_PANEL_ITEMS& aList ) const; + + /** + * Test if \a aLabel has a bus notation. + * + * @param aLabel A wxString object containing the label to test. + * @return true if text is a bus notation format otherwise false is returned. + */ + bool IsBusLabel( const wxString& aLabel ); + + /** + * Test if \a aLabel has a bus vector notation (simple bus, e.g. A[7..0]) + * + * @param aLabel A wxString object containing the label to test. + * @return true if text is a bus notation format otherwise false is returned. + */ + bool IsBusVectorLabel( const wxString& aLabel ); + + /** + * Test if \a aLabel has a bus group notation. + * + * @param aLabel A wxString object containing the label to test. + * @return true if text is a bus group notation format + */ + bool IsBusGroupLabel( const wxString& aLabel ); + +private: + + bool m_dirty; + + SCH_SHEET_PATH m_sheet; ///< The hierarchical sheet this connection is on + + SCH_ITEM* m_parent; ///< The SCH_ITEM this connection is owned by + + SCH_ITEM* m_driver; ///< The SCH_ITEM that drives this connection's net + + CONNECTION_TYPE m_type; ///< @see enum CONNECTION_TYPE + + wxString m_name; ///< Name of the bus. + + ///< Prefix if connection is member of a labeled bus group (or "" if not) + wxString m_prefix; + + int m_net_code; // TODO(JE) remove if unused + + int m_bus_code; // TODO(JE) remove if unused + + int m_subgraph_code; ///< Groups directly-connected items + + long m_vector_index; ///< Index of bus vector member nets + + long m_vector_start; ///< Highest member of a vector bus + + long m_vector_end; ///< Lowest member of a vector bus + + ///< Prefix name of the vector, if m_type == CONNECTION_BUS (or "" if not) + wxString m_vector_prefix; + + /** + * For bus connections, store a list of member connections + * + * NOTE: All connections that Clone() others share the list of member + * pointers. This seems fine at the moment. + */ + std::vector< std::shared_ptr< SCH_CONNECTION > > m_members; + +}; + +#endif + diff --git a/eeschema/sch_edit_frame.cpp b/eeschema/sch_edit_frame.cpp index e4b47ece92..6bc32b9614 100644 --- a/eeschema/sch_edit_frame.cpp +++ b/eeschema/sch_edit_frame.cpp @@ -69,6 +69,7 @@ #include #include +#include #include #include @@ -77,6 +78,9 @@ #include +SCH_SHEET_PATH* g_CurrentSheet = nullptr; // declared in general.h +CONNECTION_GRAPH* g_ConnectionGraph = nullptr; + // non-member so it can be moved easily, and kept REALLY private. // Do NOT Clear() in here. static void add_search_paths( SEARCH_STACK* aDst, const SEARCH_STACK& aSrc, int aIndex ) @@ -284,6 +288,7 @@ BEGIN_EVENT_TABLE( SCH_EDIT_FRAME, EDA_DRAW_FRAME ) EVT_TOOL( ID_UPDATE_PCB_FROM_SCH, SCH_EDIT_FRAME::OnUpdatePCB ) EVT_TOOL( ID_GET_TOOLS, SCH_EDIT_FRAME::OnCreateBillOfMaterials ) EVT_TOOL( ID_OPEN_CMP_TABLE, SCH_EDIT_FRAME::OnLaunchBomManager ) + EVT_TOOL( ID_BUS_MANAGER, SCH_EDIT_FRAME::OnLaunchBusManager ) EVT_TOOL( ID_FIND_ITEMS, SCH_EDIT_FRAME::OnFindItems ) EVT_TOOL( wxID_REPLACE, SCH_EDIT_FRAME::OnFindItems ) EVT_TOOL( ID_BACKANNO_ITEMS, SCH_EDIT_FRAME::OnLoadCmpToFootprintLinkFile ) @@ -321,6 +326,7 @@ BEGIN_EVENT_TABLE( SCH_EDIT_FRAME, EDA_DRAW_FRAME ) EVT_MENU_RANGE( ID_SCH_MIRROR_X, ID_SCH_ORIENT_NORMAL, SCH_EDIT_FRAME::OnOrient ) EVT_MENU_RANGE( ID_POPUP_START_RANGE, ID_POPUP_END_RANGE, SCH_EDIT_FRAME::Process_Special_Functions ) + EVT_MENU( ID_SCH_UNFOLD_BUS, SCH_EDIT_FRAME::OnUnfoldBusHotkey ) EVT_MENU( ID_POPUP_SCH_DISPLAYDOC_CMP, SCH_EDIT_FRAME::OnEditItem ) EVT_MENU( ID_MENU_CANVAS_CAIRO, SCH_EDIT_FRAME::OnSwitchCanvas ) @@ -371,9 +377,11 @@ SCH_EDIT_FRAME::SCH_EDIT_FRAME( KIWAY* aKiway, wxWindow* aParent ): wxDefaultPosition, wxDefaultSize, KICAD_DEFAULT_DRAWFRAME_STYLE, SCH_EDIT_FRAME_NAME ), m_item_to_repeat( 0 ) { + g_CurrentSheet = new SCH_SHEET_PATH(); + g_ConnectionGraph = new CONNECTION_GRAPH( this ); + m_showAxis = false; // true to show axis m_showBorderAndTitleBlock = true; // true to show sheet references - m_CurrentSheet = new SCH_SHEET_PATH; m_DefaultSchematicFileName = NAMELESS_PROJECT; m_DefaultSchematicFileName += wxT( ".sch" ); m_showAllPins = false; @@ -386,6 +394,7 @@ SCH_EDIT_FRAME::SCH_EDIT_FRAME( KIWAY* aKiway, wxWindow* aParent ): m_findReplaceStatus = new wxString( wxEmptyString ); m_undoItem = NULL; m_hasAutoSave = true; + m_busUnfold = {}; m_FrameSize = ConvertDialogToPixels( wxSize( 500, 350 ) ); // default in case of no prefs m_toolManager = new TOOL_MANAGER; @@ -458,12 +467,15 @@ SCH_EDIT_FRAME::~SCH_EDIT_FRAME() SetScreen( NULL ); - delete m_CurrentSheet; // a SCH_SHEET_PATH, on the heap. + delete g_CurrentSheet; // a SCH_SHEET_PATH, on the heap. + delete g_ConnectionGraph; delete m_undoItem; delete m_findReplaceData; delete m_findReplaceStatus; - delete g_RootSheet; + + g_CurrentSheet = nullptr; + g_ConnectionGraph = nullptr; g_RootSheet = NULL; } @@ -503,7 +515,7 @@ void SCH_EDIT_FRAME::SetSheetNumberAndCount() */ int sheet_count = g_RootSheet->CountSheets(); int SheetNumber = 1; - wxString current_sheetpath = m_CurrentSheet->Path(); + wxString current_sheetpath = g_CurrentSheet->Path(); SCH_SHEET_LIST sheetList( g_RootSheet ); // Examine all sheets path to find the current sheets path, @@ -520,7 +532,7 @@ void SCH_EDIT_FRAME::SetSheetNumberAndCount() * path */ } - m_CurrentSheet->SetPageNumber( SheetNumber ); + g_CurrentSheet->SetPageNumber( SheetNumber ); for( screen = s_list.GetFirst(); screen != NULL; screen = s_list.GetNext() ) { @@ -533,13 +545,13 @@ void SCH_EDIT_FRAME::SetSheetNumberAndCount() SCH_SCREEN* SCH_EDIT_FRAME::GetScreen() const { - return m_CurrentSheet->LastScreen(); + return g_CurrentSheet->LastScreen(); } wxString SCH_EDIT_FRAME::GetScreenDesc() const { - wxString s = m_CurrentSheet->PathHumanReadable(); + wxString s = g_CurrentSheet->PathHumanReadable(); return s; } @@ -562,8 +574,9 @@ void SCH_EDIT_FRAME::CreateScreens() g_RootSheet->GetScreen()->SetFileName( m_DefaultSchematicFileName ); - m_CurrentSheet->clear(); - m_CurrentSheet->push_back( g_RootSheet ); + g_CurrentSheet->clear(); + g_CurrentSheet->push_back( g_RootSheet ); + g_ConnectionGraph->Reset(); if( GetScreen() == NULL ) { @@ -578,26 +591,26 @@ void SCH_EDIT_FRAME::CreateScreens() SCH_SHEET_PATH& SCH_EDIT_FRAME::GetCurrentSheet() { - wxASSERT_MSG( m_CurrentSheet != NULL, wxT( "SCH_EDIT_FRAME m_CurrentSheet member is NULL." ) ); + wxASSERT_MSG( g_CurrentSheet != NULL, wxT( "SCH_EDIT_FRAME g_CurrentSheet member is NULL." ) ); - return *m_CurrentSheet; + return *g_CurrentSheet; } void SCH_EDIT_FRAME::SetCurrentSheet( const SCH_SHEET_PATH& aSheet ) { - if( aSheet != *m_CurrentSheet ) + if( aSheet != *g_CurrentSheet ) { - *m_CurrentSheet = aSheet; + *g_CurrentSheet = aSheet; - static_cast( m_canvas )->DisplaySheet( m_CurrentSheet->LastScreen() ); + static_cast( m_canvas )->DisplaySheet( g_CurrentSheet->LastScreen() ); } } void SCH_EDIT_FRAME::HardRedraw() { - static_cast( m_canvas )->DisplaySheet( m_CurrentSheet->LastScreen() ); + static_cast( m_canvas )->DisplaySheet( g_CurrentSheet->LastScreen() ); GetCanvas()->Refresh(); } @@ -709,7 +722,7 @@ void SCH_EDIT_FRAME::OnCloseWindow( wxCloseEvent& aEvent ) g_RootSheet->GetScreen()->Clear(); // all sub sheets are deleted, only the main sheet is usable - m_CurrentSheet->clear(); + g_CurrentSheet->clear(); Destroy(); } @@ -739,7 +752,7 @@ wxString SCH_EDIT_FRAME::GetUniqueFilenameForCurrentSheet() #define FN_LEN_MAX 80 // A reasonable value for the short filename len wxString filename = fn.GetName(); - wxString sheetFullName = m_CurrentSheet->PathHumanReadable(); + wxString sheetFullName = g_CurrentSheet->PathHumanReadable(); // Remove the last '/' of the path human readable // (and for the root sheet, make sheetFullName empty): @@ -767,6 +780,9 @@ void SCH_EDIT_FRAME::OnModify() m_foundItems.SetForceSearch(); + //RecalculateConnections( SCH_SHEET_LIST( g_CurrentSheet->Last() ) ); + RecalculateConnections(); + m_canvas->Refresh(); } @@ -824,7 +840,7 @@ void SCH_EDIT_FRAME::OnUpdateSaveSheet( wxUpdateUIEvent& aEvent ) void SCH_EDIT_FRAME::OnUpdateHierarchySheet( wxUpdateUIEvent& aEvent ) { - aEvent.Enable( m_CurrentSheet->Last() != g_RootSheet ); + aEvent.Enable( g_CurrentSheet->Last() != g_RootSheet ); } @@ -910,7 +926,7 @@ void SCH_EDIT_FRAME::doUpdatePcb( const wxString& aUpdateOptions ) } NETLIST_OBJECT_LIST* net_atoms = BuildNetListBase(); - NETLIST_EXPORTER_KICAD exporter( this, net_atoms ); + NETLIST_EXPORTER_KICAD exporter( this, net_atoms, g_ConnectionGraph ); STRING_FORMATTER formatter; exporter.Format( &formatter, GNL_ALL ); @@ -951,6 +967,12 @@ void SCH_EDIT_FRAME::OnLaunchBomManager( wxCommandEvent& event ) } +void SCH_EDIT_FRAME::OnLaunchBusManager( wxCommandEvent& ) +{ + InvokeDialogBusManager( this ); +} + + void SCH_EDIT_FRAME::OnFindItems( wxCommandEvent& aEvent ) { wxCHECK_RET( m_findReplaceData != NULL, @@ -1393,7 +1415,7 @@ void SCH_EDIT_FRAME::addCurrentItemToScreen() // the m_mouseCaptureCallback function. m_canvas->SetMouseCapture( NULL, NULL ); - if( !EditSheet( (SCH_SHEET*)item, m_CurrentSheet, &doClearAnnotation ) ) + if( !EditSheet( (SCH_SHEET*)item, g_CurrentSheet, &doClearAnnotation ) ) { screen->SetCurItem( NULL ); delete item; @@ -1438,6 +1460,9 @@ void SCH_EDIT_FRAME::addCurrentItemToScreen() SCH_SCREENS screensList( g_RootSheet ); screensList.ClearAnnotationOfNewSheetPaths( initial_sheetpathList ); } + + // Update connectivity info for new item + RecalculateConnections(); } else { @@ -1488,7 +1513,7 @@ void SCH_EDIT_FRAME::UpdateTitle() wxFileName fn = fileName; title.Printf( _( "Eeschema" ) + wxT( " \u2014 %s [%s] \u2014 %s" ), - fn.GetFullName(), m_CurrentSheet->PathHumanReadable(), + fn.GetFullName(), g_CurrentSheet->PathHumanReadable(), fn.GetPath() ); if( fn.FileExists() ) @@ -1504,6 +1529,18 @@ void SCH_EDIT_FRAME::UpdateTitle() } +void SCH_EDIT_FRAME::RecalculateConnections() +{ + SCH_SHEET_LIST list( g_RootSheet ); + + // Ensure schematic graph is accurate + for( const auto& sheet : list ) + SchematicCleanUp( true, sheet.LastScreen() ); + + g_ConnectionGraph->Recalculate( list ); +} + + void SCH_EDIT_FRAME::CommonSettingsChanged() { SCH_BASE_FRAME::CommonSettingsChanged(); diff --git a/eeschema/sch_edit_frame.h b/eeschema/sch_edit_frame.h index 36edeebab7..6bbd42a3f5 100644 --- a/eeschema/sch_edit_frame.h +++ b/eeschema/sch_edit_frame.h @@ -36,6 +36,7 @@ #include #include #include +#include #include // enum PINSHEETLABEL_SHAPE @@ -66,6 +67,7 @@ class wxFindDialogEvent; class wxFindReplaceData; class SCHLIB_FILTER; class RESCUER; +class CONNECTION_GRAPH; /// enum used in RotationMiroir() @@ -111,18 +113,36 @@ enum SCH_SEARCH_T { }; +/// Collection of data related to the bus unfolding tool +struct BUS_UNFOLDING_T { + bool in_progress; ///< True if bus unfold operation is running + + bool offset; ///< True if the bus entry should be offset from origin + + bool label_placed; ///< True if user has placed the net label + + wxPoint origin; ///< Origin (on the bus) of the unfold + + wxString net_name; ///< Net label for the unfolding operation + + SCH_BUS_WIRE_ENTRY* entry; + + SCH_LABEL* label; +}; + + /** * Schematic editor (Eeschema) main window. */ class SCH_EDIT_FRAME : public SCH_BASE_FRAME { private: - SCH_SHEET_PATH* m_CurrentSheet; ///< which sheet we are presently working on. wxString m_DefaultSchematicFileName; wxString m_SelectedNetName; PARAM_CFG_ARRAY m_projectFileParams; PARAM_CFG_ARRAY m_configSettings; + ERC_SETTINGS m_ercSettings; wxPageSetupDialogData m_pageSetupData; wxFindReplaceData* m_findReplaceData; wxString* m_findReplaceStatus; @@ -166,6 +186,18 @@ private: /// Use netcodes (net number) as net names when generating spice net lists. bool m_spiceAjustPassiveValues; +public: // TODO(JE) Make private + + /// Data related to bus unfolding tool + BUS_UNFOLDING_T m_busUnfold; + +private: + + /* these are PROJECT specific, not schematic editor specific + wxString m_userLibraryPath; + wxArrayString m_componentLibFiles; + */ + static PINSHEETLABEL_SHAPE m_lastSheetPinType; ///< Last sheet pin type. static wxSize m_lastSheetPinTextSize; ///< Last sheet pin text size. static wxPoint m_lastSheetPinPosition; ///< Last sheet pin position. @@ -251,6 +283,13 @@ public: void Process_Config( wxCommandEvent& event ); void OnSelectTool( wxCommandEvent& aEvent ); + /** + * Processes an "Unfold Bus" command from the right-click menu. + * Depending on what the user clicked, this can result in the creation + * of one or more new objects. + */ + void OnUnfoldBus( wxCommandEvent& event ); + bool GeneralControl( wxDC* aDC, const wxPoint& aPosition, EDA_KEY aHotKey ) override; /** @@ -277,6 +316,10 @@ public: */ bool LoadProjectFile(); + const ERC_SETTINGS& GetErcSettings() { return m_ercSettings; } + + void UpdateErcSettings( const ERC_SETTINGS& aSettings ) { m_ercSettings = aSettings; } + /** * Return a default symbol field name for field \a aFieldNdx for all components. * @@ -447,10 +490,12 @@ public: * @param aPoint Point at which to break the segment * @param aAppend Add the changes to the previous undo state * @param aNewSegment Pointer to the newly created segment (if given and created) + * @param aScreen is the screen to examine, or nullptr to examine the current screen * @return True if any wires or buses were broken. */ - bool BreakSegment( SCH_LINE* aSegment, const wxPoint& aPoint, bool aAppend = false, - SCH_LINE** aNewSegment = NULL ); + bool BreakSegment( SCH_LINE* aSegment, const wxPoint& aPoint, + bool aAppend = false, SCH_LINE** aNewSegment = NULL, + SCH_SCREEN* aScreen = nullptr ); /** * Checks every wire and bus for a intersection at \a aPoint and break into two segments @@ -458,18 +503,22 @@ public: * * @param aPoint Test this point for an intersection. * @param aAppend Add the changes to the previous undo state + * @param aScreen is the screen to examine, or nullptr to examine the current screen * @return True if any wires or buses were broken. */ - bool BreakSegments( const wxPoint& aPoint, bool aAppend = false ); + bool BreakSegments( const wxPoint& aPoint, bool aAppend = false, + SCH_SCREEN* aScreen = nullptr ); /** * Tests all junctions and bus entries in the schematic for intersections with wires and * buses and breaks any intersections into multiple segments. * * @param aAppend Add the changes to the previous undo state + * @param aScreen is the screen to examine, or nullptr to examine the current screen * @return True if any wires or buses were broken. */ - bool BreakSegmentsOnJunctions( bool aApped = false ); + bool BreakSegmentsOnJunctions( bool aApped = false, + SCH_SCREEN* aScreen = nullptr ); /** * Test all of the connectable objects in the schematic for unused connection points. @@ -795,6 +844,27 @@ public: */ bool AskToSaveChanges(); + /** + * Checks if a bus unfolding operation is in progress, so that it can be + * properly canceled / commited along with the wire draw operation. + */ + bool IsBusUnfoldInProgress() + { + return m_busUnfold.in_progress; + } + + /** + * Cancels a bus unfolding operation, cleaning up the bus entry and label + * that were created + */ + void CancelBusUnfold(); + + /** + * Completes a bus unfolding operation after the user finishes drawing the + * unfolded wire + */ + void FinishBusUnfold(); + private: /** @@ -835,6 +905,11 @@ private: */ void OnOrient( wxCommandEvent& aEvent ); + /** + * Handles the keyboard hotkey for unfolding a bus + */ + void OnUnfoldBusHotkey( wxCommandEvent& event ); + void OnExit( wxCommandEvent& event ); void OnAnnotate( wxCommandEvent& event ); void OnErc( wxCommandEvent& event ); @@ -843,6 +918,7 @@ private: void OnSimulate( wxCommandEvent& event ); void OnCreateBillOfMaterials( wxCommandEvent& event ); void OnLaunchBomManager( wxCommandEvent& event ); + void OnLaunchBusManager( wxCommandEvent& event ); void OnFindItems( wxCommandEvent& event ); void OnFindDialogClose( wxFindDialogEvent& event ); void OnFindDrcMarker( wxFindDialogEvent& event ); @@ -952,9 +1028,10 @@ private: * deleting identical objects superimposed on top of each other. * * @param aAppend The changes to the schematic should be appended to the previous undo + * @param aScreen is the screen to examine, or nullptr to examine the current screen * @return True if any schematic clean up was performed. */ - bool SchematicCleanUp( bool aAppend = false ); + bool SchematicCleanUp( bool aAppend = false, SCH_SCREEN* aScreen = nullptr ); /** * If any single wire passes through _both points_, remove the portion between the two points, @@ -1475,6 +1552,11 @@ public: wxString GetNetListerCommand() const { return m_netListerCommand; } + /** + * Generates the connection data for the entire schematic hierarchy. + */ + void RecalculateConnections(); + /** * Updates netlist and sends it to pcbnew. * @param aUpdateOptions is a string defining update options: diff --git a/eeschema/sch_item_struct.cpp b/eeschema/sch_item_struct.cpp index d0a31c364d..b4df58c42c 100644 --- a/eeschema/sch_item_struct.cpp +++ b/eeschema/sch_item_struct.cpp @@ -33,6 +33,7 @@ #include #include #include +#include #include #include @@ -85,6 +86,49 @@ bool SCH_ITEM::IsConnected( const wxPoint& aPosition ) const } +SCH_CONNECTION* SCH_ITEM::Connection( const SCH_SHEET_PATH& aSheet ) const +{ + SCH_CONNECTION* conn = nullptr; + + try + { + conn = m_connection_map.at( aSheet ); + } + catch( const std::out_of_range& oor ) + { + // TODO(JE) should we just call InitializeConnection here? + } + + return conn; +} + + +std::unordered_set& SCH_ITEM::ConnectedItems() +{ + return m_connected_items; +} + + +void SCH_ITEM::AddConnectionTo( SCH_ITEM* aItem ) +{ + m_connected_items.insert( aItem ); +} + + +void SCH_ITEM::InitializeConnection( const SCH_SHEET_PATH& aSheet ) +{ + if( Connection( aSheet ) ) + { + Connection( aSheet )->Reset(); + return; + } + + auto connection = new SCH_CONNECTION( this ); + connection->SetSheet( aSheet ); + m_connection_map.insert( std::make_pair( aSheet, connection ) ); +} + + void SCH_ITEM::SwapData( SCH_ITEM* aItem ) { wxFAIL_MSG( wxT( "SwapData() method not implemented for class " ) + GetClass() ); diff --git a/eeschema/sch_item_struct.h b/eeschema/sch_item_struct.h index b8ca4c923a..b5778955ab 100644 --- a/eeschema/sch_item_struct.h +++ b/eeschema/sch_item_struct.h @@ -30,12 +30,16 @@ #ifndef SCH_ITEM_STRUCT_H #define SCH_ITEM_STRUCT_H +#include #include + #include #include +#include +#include class SCH_ITEM; -class SCH_SHEET_PATH; +//class SCH_SHEET_PATH; class LINE_READER; class SCH_EDIT_FRAME; class wxFindReplaceData; @@ -69,7 +73,7 @@ class DANGLING_END_ITEM { private: /// A pointer to the connectable object. - const EDA_ITEM* m_item; + EDA_ITEM* m_item; /// The position of the connection point. wxPoint m_pos; @@ -81,7 +85,7 @@ private: const EDA_ITEM* m_parent; public: - DANGLING_END_ITEM( DANGLING_END_T aType, const EDA_ITEM* aItem, const wxPoint& aPosition ) + DANGLING_END_ITEM( DANGLING_END_T aType, EDA_ITEM* aItem, const wxPoint& aPosition ) { m_item = aItem; m_type = aType; @@ -89,7 +93,7 @@ public: m_parent = aItem; } - DANGLING_END_ITEM( DANGLING_END_T aType, const EDA_ITEM* aItem, + DANGLING_END_ITEM( DANGLING_END_T aType, EDA_ITEM* aItem, const wxPoint& aPosition, const EDA_ITEM* aParent ) { m_item = aItem; @@ -99,7 +103,7 @@ public: } wxPoint GetPosition() const { return m_pos; } - const EDA_ITEM* GetItem() const { return m_item; } + EDA_ITEM* GetItem() const { return m_item; } const EDA_ITEM* GetParent() const { return m_parent; } DANGLING_END_T GetType() const { return m_type; } }; @@ -114,12 +118,20 @@ public: */ class SCH_ITEM : public EDA_ITEM { + friend class CONNECTION_GRAPH; + protected: SCH_LAYER_ID m_Layer; EDA_ITEMS m_connections; ///< List of items connected to this item. wxPoint m_storedPos; ///< a temporary variable used in some move commands ///> to store a initial pos (of the item or mouse cursor) + /// Stores pointers to other items that are connected to this one (schematic only) + std::unordered_set m_connected_items; + + /// Stores connectivity information, per sheet + std::unordered_map m_connection_map; + public: SCH_ITEM( EDA_ITEM* aParent, KICAD_T aType ); @@ -312,6 +324,33 @@ public: */ bool IsConnected( const wxPoint& aPoint ) const; + /** + * Retrieves the connection associated with this object in the given sheet + */ + SCH_CONNECTION* Connection( const SCH_SHEET_PATH& aPath ) const; + + /** + * Retrieves the set of items connected to this item (schematic only) + */ + std::unordered_set& ConnectedItems(); + + /** + * Adds a connection link between this item and another + */ + void AddConnectionTo( SCH_ITEM* aItem ); + + /** + * Creates a new connection object associated with this object + * + * @param aPath is the sheet path to initialize + */ + void InitializeConnection( const SCH_SHEET_PATH& aPath ); + + /** + * Returns true if this item should propagate connection info to aItem + */ + virtual bool ConnectionPropagatesTo( const EDA_ITEM* aItem ) const { return true; } + virtual bool HitTest( const wxPoint& aPosition ) const override { return HitTest( aPosition, 0 ); diff --git a/eeschema/sch_junction.cpp b/eeschema/sch_junction.cpp index 3810b83039..a250f71cbd 100644 --- a/eeschema/sch_junction.cpp +++ b/eeschema/sch_junction.cpp @@ -96,10 +96,14 @@ void SCH_JUNCTION::Draw( EDA_DRAW_PANEL* aPanel, wxDC* aDC, const wxPoint& aOffs { COLOR4D color; + auto conn = Connection( *g_CurrentSheet ); + if( aColor != COLOR4D::UNSPECIFIED ) color = aColor; else - color = GetLayerColor( GetState( BRIGHTENED ) ? LAYER_BRIGHTENED : m_Layer ); + color = GetLayerColor( GetState( BRIGHTENED ) ? LAYER_BRIGHTENED : + ( conn && conn->IsBus() ) ? + LAYER_BUS : m_Layer ); GRSetDrawMode( aDC, aDrawMode ); diff --git a/eeschema/sch_legacy_plugin.cpp b/eeschema/sch_legacy_plugin.cpp index bbbeb067b1..3ba1228941 100644 --- a/eeschema/sch_legacy_plugin.cpp +++ b/eeschema/sch_legacy_plugin.cpp @@ -22,6 +22,7 @@ #include #include +#include #include #include @@ -45,6 +46,8 @@ #include #include #include +#include +#include #include #include #include @@ -759,6 +762,8 @@ void SCH_LEGACY_PLUGIN::loadFile( const wxString& aFileName, SCH_SCREEN* aScreen aScreen->Append( loadBusEntry( reader ) ); else if( strCompare( "Text", line ) ) aScreen->Append( loadText( reader ) ); + else if( strCompare( "BusAlias", line ) ) + aScreen->AddBusAlias( loadBusAlias( reader, aScreen ) ); else if( strCompare( "$EndSCHEMATC", line ) ) return; } @@ -1718,6 +1723,32 @@ SCH_COMPONENT* SCH_LEGACY_PLUGIN::loadComponent( FILE_LINE_READER& aReader ) } +std::shared_ptr< BUS_ALIAS > SCH_LEGACY_PLUGIN::loadBusAlias( FILE_LINE_READER& aReader, + SCH_SCREEN* aScreen ) +{ + auto busAlias = std::make_shared< BUS_ALIAS >( aScreen ); + const char* line = aReader.Line(); + + wxCHECK( strCompare( "BusAlias", line, &line ), NULL ); + + wxString buf; + parseUnquotedString( buf, aReader, line, &line ); + busAlias->SetName( buf ); + + while( *line != '\0' ) + { + buf.clear(); + parseUnquotedString( buf, aReader, line, &line, true ); + if( buf.Len() > 0 ) + { + busAlias->AddMember( buf ); + } + } + + return busAlias; +} + + void SCH_LEGACY_PLUGIN::Save( const wxString& aFileName, SCH_SCREEN* aScreen, KIWAY* aKiway, const PROPERTIES* aProperties ) { @@ -1782,6 +1813,11 @@ void SCH_LEGACY_PLUGIN::Format( SCH_SCREEN* aScreen ) m_out->Print( 0, "Comment4 %s\n", EscapedUTF8( tb.GetComment4() ).c_str() ); m_out->Print( 0, "$EndDescr\n" ); + for( auto alias : aScreen->GetBusAliases() ) + { + saveBusAlias( alias ); + } + for( SCH_ITEM* item = aScreen->GetDrawItems(); item; item = item->Next() ) { switch( item->Type() ) @@ -2206,6 +2242,17 @@ void SCH_LEGACY_PLUGIN::saveText( SCH_TEXT* aText ) } +void SCH_LEGACY_PLUGIN::saveBusAlias( std::shared_ptr< BUS_ALIAS > aAlias ) +{ + wxCHECK_RET( aAlias != NULL, "BUS_ALIAS* is NULL" ); + + wxString members = boost::algorithm::join( aAlias->Members(), " " ); + + m_out->Print( 0, "BusAlias %s %s\n", + TO_UTF8( aAlias->GetName() ), TO_UTF8( members ) ); +} + + int SCH_LEGACY_PLUGIN_CACHE::m_modHash = 1; // starts at 1 and goes up diff --git a/eeschema/sch_legacy_plugin.h b/eeschema/sch_legacy_plugin.h index 558348446b..34f9da2e0a 100644 --- a/eeschema/sch_legacy_plugin.h +++ b/eeschema/sch_legacy_plugin.h @@ -23,6 +23,7 @@ * with this program. If not, see . */ +#include #include #include @@ -44,6 +45,7 @@ class SCH_LEGACY_PLUGIN_CACHE; class LIB_PART; class PART_LIB; class LIB_ALIAS; +class BUS_ALIAS; /** @@ -145,6 +147,7 @@ private: SCH_BUS_ENTRY_BASE* loadBusEntry( FILE_LINE_READER& aReader ); SCH_TEXT* loadText( FILE_LINE_READER& aReader ); SCH_COMPONENT* loadComponent( FILE_LINE_READER& aReader ); + std::shared_ptr< BUS_ALIAS > loadBusAlias( FILE_LINE_READER& aReader, SCH_SCREEN* aScreen ); void saveComponent( SCH_COMPONENT* aComponent ); void saveField( SCH_FIELD* aField ); @@ -155,6 +158,7 @@ private: void saveBusEntry( SCH_BUS_ENTRY_BASE* aBusEntry ); void saveLine( SCH_LINE* aLine ); void saveText( SCH_TEXT* aText ); + void saveBusAlias( std::shared_ptr< BUS_ALIAS > aAlias ); void cacheLib( const wxString& aLibraryFileName ); bool writeDocFile( const PROPERTIES* aProperties ); diff --git a/eeschema/sch_line.cpp b/eeschema/sch_line.cpp index cf72f49caa..0125fda46c 100644 --- a/eeschema/sch_line.cpp +++ b/eeschema/sch_line.cpp @@ -781,6 +781,41 @@ wxPoint SCH_LINE::MidPoint() } +void SCH_LINE::GetMsgPanelInfo( EDA_UNITS_T aUnits, MSG_PANEL_ITEMS& aList ) +{ + wxString msg; + + switch( GetLayer() ) + { + case LAYER_WIRE: + msg = _( "Net Wire" ); + break; + + case LAYER_BUS: + msg = _( "Bus Wire" ); + break; + + default: + msg = _( "Graphical" ); + return; + } + + aList.push_back( MSG_PANEL_ITEM( _( "Line Type" ), msg, DARKCYAN ) ); + + if( auto conn = Connection( *g_CurrentSheet ) ) + { +#if defined(DEBUG) + conn->AppendDebugInfoToMsgPanel( aList ); + + msg.Printf( "%zu", m_connected_items.size() ); + aList.push_back( MSG_PANEL_ITEM( _( "Connections" ), msg, BROWN ) ); +#else + conn->AppendInfoToMsgPanel( aList ); +#endif + } +} + + int SCH_EDIT_FRAME::EditLine( SCH_LINE* aLine, bool aRedraw ) { if( aLine == NULL ) diff --git a/eeschema/sch_line.h b/eeschema/sch_line.h index d4d8870e7f..92560d2a11 100644 --- a/eeschema/sch_line.h +++ b/eeschema/sch_line.h @@ -195,6 +195,8 @@ public: void SwapData( SCH_ITEM* aItem ) override; + void GetMsgPanelInfo( EDA_UNITS_T aUnits, std::vector< MSG_PANEL_ITEM >& aList ) override; + #if defined(DEBUG) void Show( int nestLevel, std::ostream& os ) const override; #endif diff --git a/eeschema/sch_painter.cpp b/eeschema/sch_painter.cpp index 97900c7b8a..2c4d013ab7 100644 --- a/eeschema/sch_painter.cpp +++ b/eeschema/sch_painter.cpp @@ -43,6 +43,7 @@ #include #include #include +#include #include #include #include @@ -54,6 +55,7 @@ #include #include #include +#include #include #include @@ -167,6 +169,26 @@ bool SCH_PAINTER::Draw( const VIEW_ITEM *aItem, int aLayer ) m_schSettings.ImportLegacyColors( nullptr ); +#ifdef CONNECTIVITY_DEBUG + + auto sch_item = dynamic_cast( item ); + auto conn = sch_item ? sch_item->Connection( *g_CurrentSheet ) : nullptr; + + if( conn ) + { + auto pos = item->GetBoundingBox().Centre(); + auto label = conn->Name( true ); + + m_gal->SetHorizontalJustify( GR_TEXT_HJUSTIFY_CENTER ); + m_gal->SetVerticalJustify( GR_TEXT_VJUSTIFY_CENTER ); + m_gal->SetStrokeColor( COLOR4D( LIGHTRED ) ); + m_gal->SetLineWidth( 2 ); + m_gal->SetGlyphSize( VECTOR2D( 20, 20 ) ); + m_gal->StrokeText( conn->Name( true ), pos, 0.0 ); + } + +#endif + switch( item->Type() ) { HANDLE_ITEM(LIB_ALIAS_T, LIB_ALIAS); @@ -903,6 +925,10 @@ static void drawDanglingSymbol( GAL* aGal, const wxPoint& aPos ) void SCH_PAINTER::draw( SCH_JUNCTION *aJct, int aLayer ) { COLOR4D color = m_schSettings.GetLayerColor( LAYER_JUNCTION ); + auto conn = aJct->Connection( *g_CurrentSheet ); + + if( conn && conn->IsBus() ) + color = m_schSettings.GetLayerColor( LAYER_BUS ); if( aJct->GetState( BRIGHTENED ) ) color = m_schSettings.GetLayerColor( LAYER_BRIGHTENED ); @@ -1003,6 +1029,13 @@ void SCH_PAINTER::draw( SCH_TEXT *aText, int aLayer ) default: color = m_schSettings.GetLayerColor( LAYER_NOTES ); break; } + auto conn = aText->Connection( *g_CurrentSheet ); + + if( conn && conn->IsBus() && + ( aText->Type() == SCH_SHEET_PIN_T || + aText->Type() == SCH_HIERARCHICAL_LABEL_T ) ) + color = m_schSettings.GetLayerColor( LAYER_BUS ); + if( aText->GetState( BRIGHTENED ) ) color = m_schSettings.GetLayerColor( LAYER_BRIGHTENED ); @@ -1243,6 +1276,10 @@ void SCH_PAINTER::draw( SCH_HIERLABEL *aLabel, int aLayer ) auto back_color = m_schSettings.GetLayerColor( LAYER_SCHEMATIC_BACKGROUND ); int width = aLabel->GetThickness() ? aLabel->GetThickness() : GetDefaultLineThickness(); + auto conn = aLabel->Connection( *g_CurrentSheet ); + + if( conn && conn->IsBus() ) + color = m_schSettings.GetLayerColor( LAYER_BUS ); if( aLabel->GetState( BRIGHTENED ) ) color = m_schSettings.GetLayerColor( LAYER_BRIGHTENED ); diff --git a/eeschema/sch_pin_connection.cpp b/eeschema/sch_pin_connection.cpp new file mode 100644 index 0000000000..5e434f2265 --- /dev/null +++ b/eeschema/sch_pin_connection.cpp @@ -0,0 +1,87 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2018 CERN + * @author Jon Evans + * + * 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, see . + */ + +#include +#include +#include +#include + + +SCH_PIN_CONNECTION::SCH_PIN_CONNECTION( EDA_ITEM* aParent ) : + SCH_ITEM( aParent, SCH_PIN_CONNECTION_T ) +{ +} + + +wxString SCH_PIN_CONNECTION::GetSelectMenuText( EDA_UNITS_T aUnits ) const +{ + wxString tmp; + +#ifdef DEBUG + tmp.Printf( _( "SCH_PIN_CONNECTION for %s %s" ), + GetChars( m_comp->GetSelectMenuText( aUnits ) ), + GetChars( m_pin->GetSelectMenuText( aUnits ) ) ); +#else + tmp.Printf( _( "%s %s" ), + GetChars( m_comp->GetSelectMenuText( aUnits ) ), + GetChars( m_pin->GetSelectMenuText( aUnits ) ) ); +#endif + + return tmp; +} + + +wxString SCH_PIN_CONNECTION::GetDefaultNetName( const SCH_SHEET_PATH aPath ) +{ + if( m_pin->IsPowerConnection() ) + return m_pin->GetName(); + + wxString name; + + try + { + name = m_net_name_map.at( aPath ); + } + catch( const std::out_of_range& oor ) + { + name = wxT( "Net-(" ); + + name << m_comp->GetRef( &aPath ); + + // TODO(JE) do we need adoptTimestamp? + if( /* adoptTimestamp && */ name.Last() == '?' ) + name << m_comp->GetTimeStamp(); + + name << _( "-Pad" ) << m_pin->GetNumber() << _( ")" ); + + m_net_name_map[ aPath ] = name; + } + + return name; +} + + +wxPoint SCH_PIN_CONNECTION::GetPosition() const +{ + auto pos = m_comp->GetPosition(); + auto transform = m_comp->GetTransform(); + + return pos + transform.TransformCoordinate( m_pin->GetPosition() ); +} diff --git a/eeschema/sch_pin_connection.h b/eeschema/sch_pin_connection.h new file mode 100644 index 0000000000..0a23ef6335 --- /dev/null +++ b/eeschema/sch_pin_connection.h @@ -0,0 +1,77 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2018 CERN + * @author Jon Evans + * + * 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, see . + */ + +#ifndef _SCH_PIN_CONNECTION_H +#define _SCH_PIN_CONNECTION_H + +#include +#include +#include +#include + +class SCH_COMPONENT; + +/** + * Container to describe the net connectivity of a specific pin on a component. + */ +class SCH_PIN_CONNECTION : public SCH_ITEM +{ +public: + SCH_PIN_CONNECTION( EDA_ITEM* aParent = nullptr ); + + wxString GetClass() const override + { + return wxT( "SCH_PIN_CONNECTION" ); + } + + wxString GetDefaultNetName( const SCH_SHEET_PATH aPath ); + + wxString GetSelectMenuText( EDA_UNITS_T aUnits ) const override; + + void Draw( EDA_DRAW_PANEL* aPanel, wxDC* aDC, const wxPoint& aOffset, + GR_DRAWMODE aDrawMode, COLOR4D aColor = COLOR4D::UNSPECIFIED ) override + { + } + + void Move( const wxPoint& aMoveVector ) override {} + + void MirrorY( int aYaxis_position ) override {} + + void MirrorX( int aXaxis_position ) override {} + + void Rotate( wxPoint aPosition ) override {} + + wxPoint GetPosition() const override; + + void SetPosition( const wxPoint& aPosition ) override {} + +#if defined(DEBUG) + void Show( int nestLevel, std::ostream& os ) const override {} +#endif + + LIB_PIN* m_pin; + + SCH_COMPONENT* m_comp; + + /// The name that this pin connection will drive onto a net + std::map m_net_name_map; +}; + +#endif diff --git a/eeschema/sch_screen.cpp b/eeschema/sch_screen.cpp index a79d398fad..b035db2f9b 100644 --- a/eeschema/sch_screen.cpp +++ b/eeschema/sch_screen.cpp @@ -38,6 +38,7 @@ #include #include #include +#include #include #include @@ -56,6 +57,11 @@ #include #include +// TODO(JE) Debugging only +#include + +#include + #define EESCHEMA_FILE_STAMP "EESchema" /* Default zoom values. Limited to these values to keep a decent size @@ -198,6 +204,9 @@ void SCH_SCREEN::DeleteItem( SCH_ITEM* aItem ) } else { + if( GetCurItem() == aItem ) + SetCurItem( nullptr ); + m_drawList.Remove( aItem ); delete aItem; } @@ -411,6 +420,7 @@ bool SCH_SCREEN::IsTerminalPoint( const wxPoint& aPosition, int aLayer ) SCH_SHEET_PIN* label; SCH_TEXT* text; + SCH_CONNECTION conn; switch( aLayer ) { @@ -421,12 +431,12 @@ bool SCH_SCREEN::IsTerminalPoint( const wxPoint& aPosition, int aLayer ) label = GetSheetLabel( aPosition ); - if( label && IsBusLabel( label->GetText() ) && label->IsConnected( aPosition ) ) + if( label && conn.IsBusLabel( label->GetText() ) && label->IsConnected( aPosition ) ) return true; text = GetLabel( aPosition ); - if( text && IsBusLabel( text->GetText() ) && text->IsConnected( aPosition ) + if( text && conn.IsBusLabel( text->GetText() ) && text->IsConnected( aPosition ) && (text->Type() != SCH_LABEL_T) ) return true; @@ -457,12 +467,12 @@ bool SCH_SCREEN::IsTerminalPoint( const wxPoint& aPosition, int aLayer ) text = GetLabel( aPosition ); - if( text && text->IsConnected( aPosition ) && !IsBusLabel( text->GetText() ) ) + if( text && text->IsConnected( aPosition ) && !conn.IsBusLabel( text->GetText() ) ) return true; label = GetSheetLabel( aPosition ); - if( label && label->IsConnected( aPosition ) && !IsBusLabel( label->GetText() ) ) + if( label && label->IsConnected( aPosition ) && !conn.IsBusLabel( label->GetText() ) ) return true; break; @@ -525,6 +535,23 @@ void SCH_SCREEN::Draw( EDA_DRAW_PANEL* aCanvas, wxDC* aDC, GR_DRAWMODE aDrawMode // uncomment line below when there is a virtual EDA_ITEM::GetBoundingBox() // if( panel->GetClipBox().Intersects( item->GetBoundingBox() ) ) item->Draw( aCanvas, aDC, wxPoint( 0, 0 ), aDrawMode, aColor ); + + // TODO(JE) Remove debugging code +#ifdef DEBUG + + auto conn = item->Connection( *g_CurrentSheet ); + + if( conn ) + { + auto pos = item->GetBoundingBox().Centre(); + int sz = Mils2iu( 15 ); + auto label = conn->Name( true ); + + auto text = SCH_TEXT( pos, label, SCH_TEXT_T ); + text.SetTextSize( wxSize( sz, sz ) ); + text.Draw( aCanvas, aDC, wxPoint( 10, 10 ), aDrawMode, COLOR4D( LIGHTRED ) ); + } +#endif } for( auto item : junctions ) @@ -1209,6 +1236,48 @@ int SCH_SCREEN::GetConnection( const wxPoint& aPosition, PICKED_ITEMS_LIST& aLis } +void SCH_SCREEN::AddBusAlias( std::shared_ptr aAlias ) +{ + m_aliases.insert( aAlias ); +} + + +bool SCH_SCREEN::IsBusAlias( const wxString& aLabel ) +{ + SCH_SHEET_LIST aSheets( g_RootSheet ); + for( unsigned i = 0; i < aSheets.size(); i++ ) + { + for( auto alias : aSheets[i].LastScreen()->GetBusAliases() ) + { + if( alias->GetName() == aLabel ) + { + return true; + } + } + } + + return false; +} + + +std::shared_ptr SCH_SCREEN::GetBusAlias( const wxString& aLabel ) +{ + SCH_SHEET_LIST aSheets( g_RootSheet ); + for( unsigned i = 0; i < aSheets.size(); i++ ) + { + for( auto alias : aSheets[i].LastScreen()->GetBusAliases() ) + { + if( alias->GetName() == aLabel ) + { + return alias; + } + } + } + + return NULL; +} + + #if defined(DEBUG) void SCH_SCREEN::Show( int nestLevel, std::ostream& os ) const { diff --git a/eeschema/sch_screen.h b/eeschema/sch_screen.h index f42b74cddb..a248049101 100644 --- a/eeschema/sch_screen.h +++ b/eeschema/sch_screen.h @@ -30,6 +30,8 @@ #ifndef SCREEN_H #define SCREEN_H +#include + #include #include #include @@ -39,12 +41,14 @@ #include #include #include +#include #include <../eeschema/general.h> class LIB_PIN; class SCH_COMPONENT; +class SCH_SHEET_LIST; class SCH_SHEET_PATH; class SCH_SHEET_PIN; class SCH_LINE; @@ -95,6 +99,9 @@ private: int m_modification_sync; ///< inequality with PART_LIBS::GetModificationHash() ///< will trigger ResolveAll(). + /// List of bus aliases stored in this screen + std::unordered_set< std::shared_ptr< BUS_ALIAS > > m_aliases; + /** * Add items connected at \a aPosition to the block pick list. *

@@ -509,6 +516,38 @@ public: */ int UpdatePickList(); + /** + * Adds a bus alias definition (and transfers ownership of the pointer) + */ + void AddBusAlias( std::shared_ptr aAlias ); + + /** + * Removes all bus alias definitions + */ + void ClearBusAliases() + { + m_aliases.clear(); + } + + /** + * Returns a list of bus aliases defined in this screen + */ + std::unordered_set< std::shared_ptr > GetBusAliases() + { + return m_aliases; + } + + /** + * Returns true if the given string is a valid bus alias in a loaded screen + */ + static bool IsBusAlias( const wxString& aLabel ); + + /** + * Returns a pointer to a bus alias object for the given label, + * or null if one doesn't exist + */ + static std::shared_ptr GetBusAlias( const wxString& aLabel ); + #if defined(DEBUG) void Show( int nestLevel, std::ostream& os ) const override; #endif diff --git a/eeschema/sch_sheet.cpp b/eeschema/sch_sheet.cpp index 21e58e2afc..61d223da0c 100644 --- a/eeschema/sch_sheet.cpp +++ b/eeschema/sch_sheet.cpp @@ -926,7 +926,8 @@ void SCH_SHEET::GetNetListItem( NETLIST_OBJECT_LIST& aNetListItems, item->m_Start = item->m_End = m_pins[i].GetPosition(); aNetListItems.push_back( item ); - if( IsBusLabel( m_pins[i].GetText() ) ) + SCH_CONNECTION conn; + if( conn.IsBusLabel( m_pins[i].GetText() ) ) item->ConvertBusToNetListItems( aNetListItems ); } } diff --git a/eeschema/sch_sheet.h b/eeschema/sch_sheet.h index 0529d19e6a..7d54a18a2c 100644 --- a/eeschema/sch_sheet.h +++ b/eeschema/sch_sheet.h @@ -573,6 +573,6 @@ protected: }; -typedef std::vector< SCH_SHEET* > SCH_SHEETS; // no ownership over contained SCH_SHEETs +//typedef std::vector< SCH_SHEET* > SCH_SHEETS; // no ownership over contained SCH_SHEETs #endif // SCH_SHEEET_H diff --git a/eeschema/sch_sheet_path.cpp b/eeschema/sch_sheet_path.cpp index 2be088f7bd..ef88063cd3 100644 --- a/eeschema/sch_sheet_path.cpp +++ b/eeschema/sch_sheet_path.cpp @@ -38,13 +38,29 @@ #include #include #include +#include #include #include +#include #include +namespace std +{ + size_t hash::operator()( const SCH_SHEET_PATH& path ) const + { + size_t seed = 0; + + for( auto sheet : path ) + boost::hash_combine( seed, sheet->GetTimeStamp() ); + + return seed; + } +} + + SCH_SHEET_PATH::SCH_SHEET_PATH() { m_pageNumber = 0; diff --git a/eeschema/sch_sheet_path.h b/eeschema/sch_sheet_path.h index 9ad30ce34d..94b1f56f21 100644 --- a/eeschema/sch_sheet_path.h +++ b/eeschema/sch_sheet_path.h @@ -78,9 +78,9 @@ * (usable in flat and simple hierarchies). */ -#include "sch_sheet.h" // SCH_SHEETS class wxFindReplaceData; +class SCH_SHEET; class SCH_SCREEN; class SCH_MARKER; class SCH_ITEM; @@ -89,6 +89,8 @@ class SCH_REFERENCE_LIST; #define SHEET_NOT_FOUND -1 +typedef std::vector< SCH_SHEET* > SCH_SHEETS; // no ownership over contained SCH_SHEETs + /** * Type SCH_MULTI_UNIT_REFERENCE_MAP @@ -304,6 +306,15 @@ public: }; +namespace std +{ + template<> struct hash + { + size_t operator()( const SCH_SHEET_PATH& path ) const; + }; +} + + typedef std::vector< SCH_SHEET_PATH > SCH_SHEET_PATHS; typedef SCH_SHEET_PATHS::iterator SCH_SHEET_PATHS_ITER; typedef SCH_SHEET_PATHS::const_iterator SCH_SHEET_PATHS_CITER; diff --git a/eeschema/sch_text.cpp b/eeschema/sch_text.cpp index 416a2fd2e2..0a797c8ce6 100644 --- a/eeschema/sch_text.cpp +++ b/eeschema/sch_text.cpp @@ -113,6 +113,7 @@ SCH_TEXT::SCH_TEXT( const wxPoint& pos, const wxString& text, KICAD_T aType ) : m_Layer = LAYER_NOTES; SetTextPos( pos ); m_isDangling = false; + m_connectionType = CONNECTION_NONE; m_spin_style = 0; SetMultilineAllowed( true ); @@ -361,6 +362,7 @@ bool SCH_TEXT::UpdateDanglingState( std::vector& aItemList ) bool previousState = m_isDangling; m_isDangling = true; + m_connectionType = CONNECTION_NONE; for( unsigned ii = 0; ii < aItemList.size(); ii++ ) { @@ -376,12 +378,21 @@ bool SCH_TEXT::UpdateDanglingState( std::vector& aItemList ) case SHEET_LABEL_END: case NO_CONNECT_END: if( GetTextPos() == item.GetPosition() ) + { m_isDangling = false; + if( item.GetType() != PIN_END ) + m_connected_items.insert( static_cast< SCH_ITEM* >( item.GetItem() ) ); + } + break; - case WIRE_START_END: + case BUS_START_END: + m_connectionType = CONNECTION_BUS; + // fall through + + case WIRE_START_END: { // These schematic items have created 2 DANGLING_END_ITEM one per end. But being // a paranoid programmer, I'll check just in case. @@ -392,6 +403,18 @@ bool SCH_TEXT::UpdateDanglingState( std::vector& aItemList ) DANGLING_END_ITEM & nextItem = aItemList[ii]; m_isDangling = !IsPointOnSegment( item.GetPosition(), nextItem.GetPosition(), GetTextPos() ); + + if( !m_isDangling ) + { + if( m_connectionType != CONNECTION_BUS ) + m_connectionType = CONNECTION_NET; + + // Add the line to the connected items, since it won't be picked + // up by a search of intersecting connection points + auto sch_item = static_cast< SCH_ITEM* >( item.GetItem() ); + AddConnectionTo( sch_item ); + sch_item->AddConnectionTo( this ); + } } break; @@ -403,6 +426,9 @@ bool SCH_TEXT::UpdateDanglingState( std::vector& aItemList ) break; } + if( m_isDangling ) + m_connectionType = CONNECTION_NONE; + return previousState != m_isDangling; } @@ -492,8 +518,10 @@ void SCH_TEXT::GetNetListItem( NETLIST_OBJECT_LIST& aNetListItems, aNetListItems.push_back( item ); // If a bus connects to label - if( IsBusLabel( m_Text ) ) + if( Connection( *aSheetPath )->IsBusLabel( m_Text ) ) + { item->ConvertBusToNetListItems( aNetListItems ); + } } @@ -629,6 +657,18 @@ void SCH_TEXT::GetMsgPanelInfo( EDA_UNITS_T aUnits, MSG_PANEL_ITEMS& aList ) // Display text size (X or Y value, with are the same value in Eeschema) msg = MessageTextFromValue( aUnits, GetTextWidth(), true ); aList.push_back( MSG_PANEL_ITEM( _( "Size" ), msg, RED ) ); + +#if defined(DEBUG) + + if( auto conn = Connection( *g_CurrentSheet ) ) + { + conn->AppendDebugInfoToMsgPanel( aList ); + } + + msg.Printf( "%p", this ); + aList.push_back( MSG_PANEL_ITEM( _( "Object Address" ), msg, RED ) ); + +#endif } #if defined(DEBUG) @@ -1066,10 +1106,14 @@ void SCH_HIERLABEL::Draw( EDA_DRAW_PANEL* panel, linewidth = Clamp_Text_PenSize( linewidth, GetTextSize(), IsBold() ); + auto conn = Connection( *g_CurrentSheet ); + if( Color != COLOR4D::UNSPECIFIED ) color = Color; else - color = GetLayerColor( GetState( BRIGHTENED ) ? LAYER_BRIGHTENED : m_Layer ); + color = GetLayerColor( GetState( BRIGHTENED ) ? LAYER_BRIGHTENED : + ( conn && conn->IsBus() ) ? + LAYER_BUS : m_Layer ); GRSetDrawMode( DC, DrawMode ); diff --git a/eeschema/sch_text.h b/eeschema/sch_text.h index 381a19ffd5..4da3cc5045 100644 --- a/eeschema/sch_text.h +++ b/eeschema/sch_text.h @@ -34,6 +34,7 @@ #include #include #include +#include // for CONNECTION_TYPE class LINE_READER; @@ -64,6 +65,8 @@ protected: /// supports connections. bool m_isDangling; + CONNECTION_TYPE m_connectionType; + /** * The orientation of text and any associated drawing elements of derived objects. * 0 is the horizontal and left justified. diff --git a/eeschema/schedit.cpp b/eeschema/schedit.cpp index c83b110d8a..802398cf16 100644 --- a/eeschema/schedit.cpp +++ b/eeschema/schedit.cpp @@ -279,16 +279,16 @@ void SCH_EDIT_FRAME::Process_Special_Functions( wxCommandEvent& event ) if( item && (item->Type() == SCH_SHEET_T) ) { - m_CurrentSheet->push_back( (SCH_SHEET*) item ); + g_CurrentSheet->push_back( (SCH_SHEET*) item ); DisplayCurrentSheet(); } break; case ID_POPUP_SCH_LEAVE_SHEET: - if( m_CurrentSheet->Last() != g_RootSheet ) + if( g_CurrentSheet->Last() != g_RootSheet ) { - m_CurrentSheet->pop_back(); + g_CurrentSheet->pop_back(); DisplayCurrentSheet(); } @@ -376,6 +376,81 @@ void SCH_EDIT_FRAME::Process_Special_Functions( wxCommandEvent& event ) } +void SCH_EDIT_FRAME::OnUnfoldBus( wxCommandEvent& event ) +{ + auto screen = GetScreen(); + auto item = static_cast< wxMenuItem* >( event.GetEventUserData() ); + auto net = item->GetItemLabelText(); + + auto pos = GetCrossHairPosition(); + + /** + * Unfolding a bus consists of the following user inputs: + * 1) User selects a bus to unfold (see AddMenusForBus()) + * We land in this event handler. + * + * 2) User clicks to set the net label location (handled by BeginSegment()) + * Before this first click, the posture of the bus entry follows the + * mouse cursor in X and Y (handled by DrawSegment()) + * + * 3) User is now in normal wiring mode and can exit in any normal way. + */ + + wxASSERT( !m_busUnfold.in_progress ); + + m_busUnfold.entry = new SCH_BUS_WIRE_ENTRY( pos, '\\' ); + + SetSchItemParent( m_busUnfold.entry, screen ); + AddToScreen( m_busUnfold.entry ); + + m_busUnfold.label = new SCH_LABEL( m_busUnfold.entry->m_End(), net ); + + m_busUnfold.label->SetTextSize( wxSize( GetDefaultTextSize(), + GetDefaultTextSize() ) ); + m_busUnfold.label->SetLabelSpinStyle( 0 ); + + SetSchItemParent( m_busUnfold.label, screen ); + + m_busUnfold.in_progress = true; + m_busUnfold.origin = pos; + m_busUnfold.net_name = net; + + SetToolID( ID_WIRE_BUTT, wxCURSOR_PENCIL, _( "Add wire" ) ); + + SetCrossHairPosition( m_busUnfold.entry->m_End() ); + BeginSegment( LAYER_WIRE ); + m_canvas->SetAutoPanRequest( true ); +} + + +void SCH_EDIT_FRAME::CancelBusUnfold() +{ + if( m_busUnfold.entry ) + { + RemoveFromScreen( m_busUnfold.entry ); + delete m_busUnfold.entry; + } + + if( m_busUnfold.label ) + { + if( m_busUnfold.label_placed ) + RemoveFromScreen( m_busUnfold.label ); + + delete m_busUnfold.label; + } + + FinishBusUnfold(); +} + + +void SCH_EDIT_FRAME::FinishBusUnfold() +{ + m_busUnfold = {}; + + SetToolID( ID_NO_TOOL_SELECTED, GetGalCanvas()->GetDefaultCursor(), wxEmptyString ); +} + + void SCH_EDIT_FRAME::OnMoveItem( wxCommandEvent& aEvent ) { SCH_SCREEN* screen = GetScreen(); @@ -1058,17 +1133,17 @@ void SCH_EDIT_FRAME::OnEditItem( wxCommandEvent& aEvent ) // Keep trace of existing sheet paths. EditSheet() can modify this list SCH_SHEET_LIST initial_sheetpathList( g_RootSheet ); - doRefresh = EditSheet( (SCH_SHEET*) item, m_CurrentSheet, &doClearAnnotation ); + doRefresh = EditSheet( (SCH_SHEET*) item, g_CurrentSheet, &doClearAnnotation ); if( doClearAnnotation ) // happens when the current sheet load a existing file { // we must clear "new" components annotation SCH_SCREENS screensList( g_RootSheet ); // We clear annotation of new sheet paths here: screensList.ClearAnnotationOfNewSheetPaths( initial_sheetpathList ); - // Clear annotation of m_CurrentSheet itself, because its sheetpath + // Clear annotation of g_CurrentSheet itself, because its sheetpath // is not a new path, but components managed by its sheet path must have // their annotation cleared, becuase they are new: - ((SCH_SHEET*) item)->GetScreen()->ClearAnnotation( m_CurrentSheet ); + ((SCH_SHEET*) item)->GetScreen()->ClearAnnotation( g_CurrentSheet ); } if( doRefresh ) @@ -1331,3 +1406,77 @@ void SCH_EDIT_FRAME::OnOrient( wxCommandEvent& aEvent ) if( item->GetFlags() == 0 ) screen->SetCurItem( NULL ); } + + +void SCH_EDIT_FRAME::OnUnfoldBusHotkey( wxCommandEvent& aEvent ) +{ + auto data = (EDA_HOTKEY_CLIENT_DATA*) aEvent.GetClientObject(); + auto item = GetScreen()->GetCurItem(); + + wxCHECK_RET( data != NULL, wxT( "Invalid hot key client object." ) ); + + if( item == NULL ) + { + // If we didn't get here by a hot key, then something has gone wrong. + if( aEvent.GetInt() == 0 ) + return; + + item = LocateAndShowItem( data->GetPosition(), SCH_COLLECTOR::EditableItems, + aEvent.GetInt() ); + + // Exit if no item found at the current location or the item is already being edited. + if( (item == NULL) || (item->GetFlags() != 0) ) + return; + } + + auto connection = item->Connection( *g_CurrentSheet ); + + if( connection && connection->IsBus() ) + { + int idx = 0; + wxMenu* bus_unfolding_menu = new wxMenu; + + for( const auto& member : connection->Members() ) + { + int id = ID_POPUP_SCH_UNFOLD_BUS + ( idx++ ); + + if( member->Type() == CONNECTION_BUS ) + { + wxMenu* submenu = new wxMenu; + bus_unfolding_menu->AppendSubMenu( submenu, _( member->Name() ) ); + + for( const auto& sub_member : member->Members() ) + { + id = ID_POPUP_SCH_UNFOLD_BUS + ( idx++ ); + + submenu->Append( id, sub_member->Name(), wxEmptyString ); + + // See comment in else clause below + auto sub_item_clone = new wxMenuItem(); + sub_item_clone->SetItemLabel( sub_member->Name() ); + + Bind( wxEVT_COMMAND_MENU_SELECTED, &SCH_EDIT_FRAME::OnUnfoldBus, + this, id, id, sub_item_clone ); + } + } + else + { + bus_unfolding_menu->Append( id, member->Name(), + wxEmptyString ); + + // Because Bind() takes ownership of the user data item, we + // make a new menu item here and set its label. Why create a + // menu item instead of just a wxString or something? Because + // Bind() requires a pointer to wxObject rather than a void + // pointer. Maybe at some point I'll think of a better way... + auto item_clone = new wxMenuItem(); + item_clone->SetItemLabel( member->Name() ); + + Bind( wxEVT_COMMAND_MENU_SELECTED, &SCH_EDIT_FRAME::OnUnfoldBus, + this, id, id, item_clone ); + } + } + + GetGalCanvas()->PopupMenu( bus_unfolding_menu, GetCrossHairScreenPosition() ); + } +} diff --git a/include/core/typeinfo.h b/include/core/typeinfo.h index 178e522b59..451b3dae6d 100644 --- a/include/core/typeinfo.h +++ b/include/core/typeinfo.h @@ -121,6 +121,7 @@ enum KICAD_T SCH_COMPONENT_T, SCH_SHEET_PIN_T, SCH_SHEET_T, + SCH_PIN_CONNECTION_T, // Be prudent with these 3 types: // they should be used only to locate a specific field type diff --git a/include/validators.h b/include/validators.h index c472b40e1e..7c829062c7 100644 --- a/include/validators.h +++ b/include/validators.h @@ -3,6 +3,7 @@ * * Copyright (C) 2013 Wayne Stambaugh * Copyright (C) 2004-2013 KiCad Developers, see change_log.txt for contributors. + * Copyright (C) 2018 CERN * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -32,6 +33,7 @@ #include #include +#include /** * Class GRID_CELL_TEXT_EDITOR @@ -112,6 +114,66 @@ public: void OnTextChanged( wxCommandEvent& event ); }; + +/** + * Custom validator that checks verifies that a string *exactly* matches a + * regular expression. + */ +class REGEX_VALIDATOR : public wxTextValidator +{ +public: + /** + * Constructor. + * + * @param aRegEx is a regular expression to validate strings. + * @param aValue is a pointer to a wxString containing the value to validate. + */ + REGEX_VALIDATOR( const wxString& aRegEx, wxString* aValue = NULL ) + : wxTextValidator( wxFILTER_NONE, aValue ) + { + compileRegEx( aRegEx, wxRE_DEFAULT ); + } + + /** + * Constructor. + * + * @param aRegEx is a regular expression to validate strings. + * @param aFlags are compilation flags (normally wxRE_DEFAULT). + * @param aValue is a pointer to a wxString containing the value to validate. + */ + REGEX_VALIDATOR( const wxString& aRegEx, int aFlags, wxString* aValue = NULL ) + : wxTextValidator( wxFILTER_NONE, aValue ) + { + compileRegEx( aRegEx, aFlags ); + } + + REGEX_VALIDATOR( const REGEX_VALIDATOR& aOther ) + { + wxValidator::Copy( aOther ); + compileRegEx( aOther.m_regExString, aOther.m_regExFlags ); + } + + virtual wxObject* Clone() const override + { + return new REGEX_VALIDATOR( *this ); + } + + bool Validate( wxWindow* aParent ) override; + +protected: + ///> Compiles and stores a regular expression + void compileRegEx( const wxString& aRegEx, int aFlags ); + + ///> Original regular expression (for copy constructor) + wxString m_regExString; + + ///> Original compilation flags (for copy constructor) + int m_regExFlags; + + ///> Compiled regex + wxRegEx m_regEx; +}; + namespace KIUI { /**