From be8744176cee04c8e3b76b80788393076ff6e31c Mon Sep 17 00:00:00 2001 From: James J <13408010-JamesJCode@users.noreply.gitlab.com> Date: Thu, 25 Apr 2024 14:24:46 +0000 Subject: [PATCH] Add SCH_RULE_AREA shapes to eeschema Includes: - Fix GAL to draw closed polygons in eeschema - Add functionality to eeschema to draw arbitary polygons - Update polygon item previews to have customisable edge colour - Add new SCH_RULE_AREA class, derived from a poly SCH_SHAPE - Add SCH_RULE_AREA to paint and plot methods - Add new rule area color preference to themes --- common/eda_item.cpp | 1 + common/layer_id.cpp | 1 + common/preview_items/polygon_item.cpp | 25 +- common/settings/builtin_color_themes.h | 2 + common/settings/color_settings.cpp | 1 + eeschema/CMakeLists.txt | 2 + eeschema/connection_graph.cpp | 102 ++-- eeschema/connection_graph.h | 8 +- eeschema/ee_collectors.cpp | 6 +- eeschema/erc.cpp | 76 +++ eeschema/erc.h | 15 + eeschema/erc_item.cpp | 8 +- eeschema/erc_item.h | 1 + eeschema/erc_settings.h | 1 + eeschema/menubar.cpp | 3 +- eeschema/sch_commit.cpp | 32 +- eeschema/sch_edit_frame.cpp | 147 +++++- eeschema/sch_file_versions.h | 3 +- .../sch_io/kicad_sexpr/sch_io_kicad_sexpr.cpp | 19 + .../sch_io/kicad_sexpr/sch_io_kicad_sexpr.h | 2 + .../kicad_sexpr/sch_io_kicad_sexpr_parser.cpp | 44 +- .../kicad_sexpr/sch_io_kicad_sexpr_parser.h | 2 + eeschema/sch_item.h | 23 + eeschema/sch_label.cpp | 24 + eeschema/sch_label.h | 17 + eeschema/sch_painter.cpp | 38 +- eeschema/sch_plotter.h | 1 + eeschema/sch_rule_area.cpp | 442 ++++++++++++++++++ eeschema/sch_rule_area.h | 125 +++++ eeschema/sch_view.h | 2 +- eeschema/schematic.keywords | 1 + eeschema/schematic_undo_redo.cpp | 55 ++- eeschema/toolbars_sch_editor.cpp | 1 + eeschema/tools/ee_actions.cpp | 23 + eeschema/tools/ee_actions.h | 3 + eeschema/tools/ee_grid_helper.cpp | 1 + eeschema/tools/ee_point_editor.cpp | 49 +- eeschema/tools/rule_area_create_helper.cpp | 175 +++++++ eeschema/tools/rule_area_create_helper.h | 93 ++++ eeschema/tools/sch_drawing_tools.cpp | 185 ++++++++ eeschema/tools/sch_drawing_tools.h | 2 + eeschema/tools/sch_edit_tool.cpp | 20 + include/core/typeinfo.h | 2 + include/layer_ids.h | 1 + include/preview_items/polygon_item.h | 13 + ...RuleAreaNetclassConflictOnWire_1.kicad_sch | 376 +++++++++++++++ ...RuleAreaNetclassConflictOnWire_2.kicad_sch | 365 +++++++++++++++ qa/data/eeschema/RuleAreaNoOverlap.kicad_sch | 45 ++ .../RuleAreaOneNetclassDirective.kicad_sch | 327 +++++++++++++ qa/data/eeschema/RuleAreaOneOverlap.kicad_sch | 43 ++ .../RuleAreaOneOverlapTwice.kicad_sch | 75 +++ .../RuleAreaThreeNetclassDirectives.kicad_sch | 373 +++++++++++++++ .../RuleAreaTwoNetclassDirectives.kicad_sch | 350 ++++++++++++++ .../eeschema/RuleAreaTwoOverlaps.kicad_sch | 58 +++ qa/tests/eeschema/CMakeLists.txt | 1 + qa/tests/eeschema/erc/test_erc_rule_areas.cpp | 139 ++++++ qa/tests/eeschema/test_ee_item.cpp | 19 + 57 files changed, 3858 insertions(+), 110 deletions(-) create mode 100644 eeschema/sch_rule_area.cpp create mode 100644 eeschema/sch_rule_area.h create mode 100644 eeschema/tools/rule_area_create_helper.cpp create mode 100644 eeschema/tools/rule_area_create_helper.h create mode 100644 qa/data/eeschema/RuleAreaNetclassConflictOnWire_1.kicad_sch create mode 100644 qa/data/eeschema/RuleAreaNetclassConflictOnWire_2.kicad_sch create mode 100644 qa/data/eeschema/RuleAreaNoOverlap.kicad_sch create mode 100644 qa/data/eeschema/RuleAreaOneNetclassDirective.kicad_sch create mode 100644 qa/data/eeschema/RuleAreaOneOverlap.kicad_sch create mode 100644 qa/data/eeschema/RuleAreaOneOverlapTwice.kicad_sch create mode 100644 qa/data/eeschema/RuleAreaThreeNetclassDirectives.kicad_sch create mode 100644 qa/data/eeschema/RuleAreaTwoNetclassDirectives.kicad_sch create mode 100644 qa/data/eeschema/RuleAreaTwoOverlaps.kicad_sch create mode 100644 qa/tests/eeschema/erc/test_erc_rule_areas.cpp diff --git a/common/eda_item.cpp b/common/eda_item.cpp index 682e337882..aa4c400fd8 100644 --- a/common/eda_item.cpp +++ b/common/eda_item.cpp @@ -374,6 +374,7 @@ static struct EDA_ITEM_DESC .Map( SCH_LINE_T, _HKI( "Line" ) ) .Map( SCH_BITMAP_T, _HKI( "Bitmap" ) ) .Map( SCH_SHAPE_T, _HKI( "Graphic" ) ) + .Map( SCH_RULE_AREA_T, _HKI( "Rule Area" ) ) .Map( SCH_TEXT_T, _HKI( "Text" ) ) .Map( SCH_TEXTBOX_T, _HKI( "Text Box" ) ) .Map( SCH_TABLE_T, _HKI( "Table" ) ) diff --git a/common/layer_id.cpp b/common/layer_id.cpp index ce88182a44..04e5db6cb8 100644 --- a/common/layer_id.cpp +++ b/common/layer_id.cpp @@ -122,6 +122,7 @@ wxString LayerName( int aLayer ) case LAYER_FIELDS: return _( "Symbol fields" ); case LAYER_INTERSHEET_REFS: return _( "Sheet references" ); case LAYER_NETCLASS_REFS: return _( "Net class references" ); + case LAYER_RULE_AREAS: return _( "Rule areas" ); case LAYER_DEVICE: return _( "Symbol body outlines" ); case LAYER_DEVICE_BACKGROUND: return _( "Symbol body fills" ); case LAYER_NOTES: return _( "Schematic text && graphics" ); diff --git a/common/preview_items/polygon_item.cpp b/common/preview_items/polygon_item.cpp index 107dcbd40d..45436c0342 100644 --- a/common/preview_items/polygon_item.cpp +++ b/common/preview_items/polygon_item.cpp @@ -33,6 +33,20 @@ const double POLYGON_ITEM::POLY_LINE_WIDTH = 1; POLYGON_ITEM::POLYGON_ITEM() : SIMPLE_OVERLAY_ITEM() { + m_lineColor = KIGFX::COLOR4D::UNSPECIFIED; + m_leaderColor = KIGFX::COLOR4D::UNSPECIFIED; +} + + +void POLYGON_ITEM::SetLineColor( KIGFX::COLOR4D lineColor ) +{ + m_lineColor = lineColor; +} + + +void POLYGON_ITEM::SetLeaderColor( KIGFX::COLOR4D leaderColor ) +{ + m_leaderColor = leaderColor; } @@ -62,8 +76,13 @@ void POLYGON_ITEM::drawPreviewShape( KIGFX::VIEW* aView ) const KIGFX::GAL& gal = *aView->GetGAL(); RENDER_SETTINGS* renderSettings = aView->GetPainter()->GetSettings(); + gal.SetIsStroke( true ); + if( m_lockedChain.PointCount() >= 2 ) { + if( m_lineColor != KIGFX::COLOR4D::UNSPECIFIED ) + gal.SetStrokeColor( m_lineColor ); + gal.SetLineWidth( (float) aView->ToWorld( POLY_LINE_WIDTH ) ); gal.DrawPolyline( m_lockedChain ); } @@ -71,7 +90,11 @@ void POLYGON_ITEM::drawPreviewShape( KIGFX::VIEW* aView ) const // draw the leader line in a different color if( m_leaderChain.PointCount() >= 2 ) { - gal.SetStrokeColor( renderSettings->GetLayerColor( LAYER_AUX_ITEMS ) ); + if( m_leaderColor != KIGFX::COLOR4D::UNSPECIFIED ) + gal.SetStrokeColor( m_leaderColor ); + else + gal.SetStrokeColor( renderSettings->GetLayerColor( LAYER_AUX_ITEMS ) ); + gal.DrawPolyline( m_leaderChain ); } diff --git a/common/settings/builtin_color_themes.h b/common/settings/builtin_color_themes.h index 481ab4db13..250deab90f 100644 --- a/common/settings/builtin_color_themes.h +++ b/common/settings/builtin_color_themes.h @@ -50,6 +50,7 @@ static const std::map s_defaultTheme = { LAYER_HIERLABEL, CSS_COLOR( 114, 86, 0, 1 ) }, { LAYER_LOCLABEL, CSS_COLOR( 15, 15, 15, 1 ) }, { LAYER_NETCLASS_REFS, CSS_COLOR( 72, 72, 72, 1 ) }, + { LAYER_RULE_AREAS, CSS_COLOR( 255, 0, 0, 1 ) }, { LAYER_NOCONNECT, CSS_COLOR( 0, 0, 132, 1 ) }, { LAYER_NOTES, CSS_COLOR( 0, 0, 194, 1 ) }, { LAYER_PRIVATE_NOTES, CSS_COLOR( 72, 72, 255, 1 ) }, @@ -268,6 +269,7 @@ static const std::map s_classicTheme = { LAYER_HIERLABEL, COLOR4D( BROWN ) }, { LAYER_LOCLABEL, COLOR4D( BLACK ) }, { LAYER_NETCLASS_REFS, COLOR4D( BLACK ) }, + { LAYER_RULE_AREAS, COLOR4D( RED ) }, { LAYER_NOCONNECT, COLOR4D( BLUE ) }, { LAYER_NOTES, COLOR4D( LIGHTBLUE ) }, { LAYER_PRIVATE_NOTES, COLOR4D( LIGHTBLUE ) }, diff --git a/common/settings/color_settings.cpp b/common/settings/color_settings.cpp index 3990af624c..a2cc245527 100644 --- a/common/settings/color_settings.cpp +++ b/common/settings/color_settings.cpp @@ -75,6 +75,7 @@ COLOR_SETTINGS::COLOR_SETTINGS( const wxString& aFilename, bool aAbsolutePath ) CLR( "schematic.label_hier", LAYER_HIERLABEL ); CLR( "schematic.label_local", LAYER_LOCLABEL ); CLR( "schematic.netclass_flag", LAYER_NETCLASS_REFS ); + CLR( "schematic.rule_area", LAYER_RULE_AREAS ); CLR( "schematic.no_connect", LAYER_NOCONNECT ); CLR( "schematic.note", LAYER_NOTES ); CLR( "schematic.private_note", LAYER_PRIVATE_NOTES ); diff --git a/eeschema/CMakeLists.txt b/eeschema/CMakeLists.txt index 7ace801cd1..893efd8fc5 100644 --- a/eeschema/CMakeLists.txt +++ b/eeschema/CMakeLists.txt @@ -383,6 +383,7 @@ set( EESCHEMA_SRCS sch_render_settings.cpp sch_screen.cpp sch_shape.cpp + sch_rule_area.cpp sch_sheet.cpp sch_sheet_path.cpp sch_sheet_pin.cpp @@ -431,6 +432,7 @@ set( EESCHEMA_SRCS tools/ee_point_editor.cpp tools/ee_selection.cpp tools/ee_selection_tool.cpp + tools/rule_area_create_helper.cpp tools/sch_drawing_tools.cpp tools/sch_edit_table_tool.cpp tools/sch_edit_tool.cpp diff --git a/eeschema/connection_graph.cpp b/eeschema/connection_graph.cpp index 2e63d19e81..d003ada009 100644 --- a/eeschema/connection_graph.cpp +++ b/eeschema/connection_graph.cpp @@ -36,6 +36,7 @@ #include #include #include +#include #include #include #include @@ -430,10 +431,35 @@ const wxString& CONNECTION_SUBGRAPH::GetNameForDriver( SCH_ITEM* aItem ) const } -const wxString CONNECTION_SUBGRAPH::GetNetclassForDriver( SCH_ITEM* aItem ) const +const std::vector> +CONNECTION_SUBGRAPH::GetNetclassesForDriver( SCH_ITEM* aItem, bool returnAll ) const { - wxString netclass; + std::vector> foundNetclasses; + const std::unordered_set& ruleAreaCache = aItem->GetRuleAreaCache(); + + // Get netclasses on attached rule areas + for( SCH_RULE_AREA* ruleArea : ruleAreaCache ) + { + const std::vector> ruleNetclasses = + ruleArea->GetResolvedNetclasses(); + + if( ruleNetclasses.size() > 0 ) + { + if( returnAll ) + { + foundNetclasses.insert( foundNetclasses.end(), ruleNetclasses.begin(), + ruleNetclasses.end() ); + } + else + { + foundNetclasses.push_back( ruleNetclasses[0] ); + return foundNetclasses; + } + } + } + + // Get netclasses on child fields aItem->RunOnChildren( [&]( SCH_ITEM* aChild ) { @@ -443,15 +469,19 @@ const wxString CONNECTION_SUBGRAPH::GetNetclassForDriver( SCH_ITEM* aItem ) cons if( field->GetCanonicalName() == wxT( "Netclass" ) ) { - netclass = field->GetText(); - return false; + wxString netclass = field->GetText(); + + if( netclass != wxEmptyString ) + foundNetclasses.push_back( { field->GetText(), aItem } ); + + return returnAll; } } return true; } ); - return netclass; + return foundNetclasses; } @@ -742,7 +772,9 @@ void CONNECTION_GRAPH::Recalculate( const SCH_SHEET_LIST& aSheetList, bool aUnco } else if( item->Type() == SCH_SHEET_T ) { - for( SCH_SHEET_PIN* pin : static_cast( item )->GetPins() ) + SCH_SHEET* sheetItem = static_cast( item ); + + for( SCH_SHEET_PIN* pin : sheetItem->GetPins() ) { if( pin->IsConnectivityDirty() ) { @@ -2347,10 +2379,14 @@ void CONNECTION_GRAPH::buildConnectionGraph( std::function* a { for( SCH_ITEM* item : subgraph->m_items ) { - netclass = subgraph->GetNetclassForDriver( item ); + const std::vector> netclassesWithProviders = + subgraph->GetNetclassesForDriver( item, false ); - if( !netclass.IsEmpty() ) + if( netclassesWithProviders.size() > 0 ) + { + netclass = netclassesWithProviders[0].first; break; + } } if( !netclass.IsEmpty() ) @@ -3188,40 +3224,52 @@ bool CONNECTION_GRAPH::ercCheckNetclassConflicts( const std::vectorm_items ) { - const wxString netclass = subgraph->GetNetclassForDriver( item ); + const std::vector> netclassesWithProvider = + subgraph->GetNetclassesForDriver( item, true ); - if( netclass.IsEmpty() ) + if( netclassesWithProvider.size() == 0 ) continue; - if( netclass != firstNetclass ) + auto checkNetclass = [&]( const std::pair& netclass ) { - if( !firstNetclassDriver ) + if( netclass.first != firstNetclass ) { - firstNetclass = netclass; - firstNetclassDriver = item; - firstNetclassDriverSheet = &subgraph->GetSheet(); - continue; + if( !firstNetclassDriver ) + { + firstNetclass = netclass.first; + firstNetclassDriver = netclass.second; + firstNetclassDriverSheet = &subgraph->GetSheet(); + } + else + { + conflictFound = true; + + std::shared_ptr ercItem = + ERC_ITEM::Create( ERCE_NETCLASS_CONFLICT ); + ercItem->SetItems( firstNetclassDriver, netclass.second ); + ercItem->SetSheetSpecificPath( subgraph->GetSheet() ); + ercItem->SetItemsSheetPaths( *firstNetclassDriverSheet, + subgraph->GetSheet() ); + + SCH_MARKER* marker = + new SCH_MARKER( ercItem, netclass.second->GetPosition() ); + subgraph->m_sheet.LastScreen()->Append( marker ); + } } + }; - std::shared_ptr ercItem = ERC_ITEM::Create( ERCE_NETCLASS_CONFLICT ); - ercItem->SetItems( firstNetclassDriver, item ); - ercItem->SetSheetSpecificPath( subgraph->GetSheet() ); - ercItem->SetItemsSheetPaths( *firstNetclassDriverSheet, subgraph->GetSheet() ); - - SCH_MARKER* marker = new SCH_MARKER( ercItem, item->GetPosition() ); - subgraph->m_sheet.LastScreen()->Append( marker ); - - return false; - } + for( const std::pair& netclass : netclassesWithProvider ) + checkNetclass( netclass ); } } - return true; + return conflictFound; } diff --git a/eeschema/connection_graph.h b/eeschema/connection_graph.h index 91975ecc85..d9e87bd0ec 100644 --- a/eeschema/connection_graph.h +++ b/eeschema/connection_graph.h @@ -23,6 +23,7 @@ #define _CONNECTION_GRAPH_H #include +#include #include #include @@ -122,7 +123,12 @@ public: /// Return the candidate net name for a driver. const wxString& GetNameForDriver( SCH_ITEM* aItem ) const; - const wxString GetNetclassForDriver( SCH_ITEM* aItem ) const; + /// Return the resolved netclasses for the item, and the source item providing the netclass + /// @param aItem the item to query for netclass assignments + /// @param returnAll If true, return all assigned netclasses (for ERC). If false, stop on first + /// netclass (for connectivity). + const std::vector> + GetNetclassesForDriver( SCH_ITEM* aItem, bool returnAll ) const; /// Combine another subgraph on the same sheet into this one. void Absorb( CONNECTION_SUBGRAPH* aOther ); diff --git a/eeschema/ee_collectors.cpp b/eeschema/ee_collectors.cpp index a4194c7129..aa08ae79f3 100644 --- a/eeschema/ee_collectors.cpp +++ b/eeschema/ee_collectors.cpp @@ -51,7 +51,8 @@ const std::vector EE_COLLECTOR::EditableItems = { SCH_BITMAP_T, SCH_LINE_T, SCH_BUS_WIRE_ENTRY_T, - SCH_JUNCTION_T + SCH_JUNCTION_T, + SCH_RULE_AREA_T }; @@ -76,7 +77,8 @@ const std::vector EE_COLLECTOR::MovableItems = SCH_FIELD_T, SCH_SYMBOL_T, SCH_SHEET_PIN_T, - SCH_SHEET_T + SCH_SHEET_T, + SCH_RULE_AREA_T }; diff --git a/eeschema/erc.cpp b/eeschema/erc.cpp index c8243321e9..1a8e20a208 100644 --- a/eeschema/erc.cpp +++ b/eeschema/erc.cpp @@ -41,6 +41,7 @@ #include #include #include +#include #include #include #include @@ -1229,6 +1230,73 @@ int ERC_TESTER::TestSimModelIssues() } +int ERC_TESTER::RunRuleAreaERC() +{ + int numErrors = 0; + ERC_SETTINGS& settings = m_schematic->ErcSettings(); + + if( !settings.IsTestEnabled( ERCE_OVERLAPPING_RULE_AREAS ) ) + return 0; + + std::map> allScreenRuleAreas; + + SCH_SCREENS screens( m_schematic->Root() ); + + for( SCH_SCREEN* screen = screens.GetFirst(); screen != nullptr; screen = screens.GetNext() ) + { + for( SCH_ITEM* item : screen->Items().OfType( SCH_RULE_AREA_T ) ) + { + allScreenRuleAreas[screen].push_back( static_cast( item ) ); + } + } + + if( settings.IsTestEnabled( ERCE_OVERLAPPING_RULE_AREAS ) ) + numErrors += TestRuleAreaOverlappingRuleAreasERC( allScreenRuleAreas ); + + return numErrors; +} + + +int ERC_TESTER::TestRuleAreaOverlappingRuleAreasERC( + std::map>& allScreenRuleAreas ) +{ + int numErrors = 0; + + for( auto screenRuleAreas : allScreenRuleAreas ) + { + std::vector& ruleAreas = screenRuleAreas.second; + + for( std::size_t i = 0; i < ruleAreas.size(); ++i ) + { + SHAPE_POLY_SET& polyFirst = ruleAreas[i]->GetPolyShape(); + + for( std::size_t j = i + 1; j < ruleAreas.size(); ++j ) + { + SHAPE_POLY_SET polySecond = ruleAreas[j]->GetPolyShape(); + if( polyFirst.Collide( &polySecond ) ) + { + numErrors++; + + SCH_SCREEN* screen = screenRuleAreas.first; + SCH_SHEET_PATH firstSheet = screen->GetClientSheetPaths()[0]; + + std::shared_ptr ercItem = + ERC_ITEM::Create( ERCE_OVERLAPPING_RULE_AREAS ); + ercItem->SetItems( ruleAreas[i], ruleAreas[j] ); + ercItem->SetSheetSpecificPath( firstSheet ); + ercItem->SetItemsSheetPaths( firstSheet, firstSheet ); + + SCH_MARKER* marker = new SCH_MARKER( ercItem, ruleAreas[i]->GetPosition() ); + screen->Append( marker ); + } + } + } + } + + return numErrors; +} + + void ERC_TESTER::RunTests( DS_PROXY_VIEW_ITEM* aDrawingSheet, SCH_EDIT_FRAME* aEditFrame, KIFACE* aCvPcb, PROJECT* aProject, PROGRESS_REPORTER* aProgressReporter ) { @@ -1267,6 +1335,14 @@ void ERC_TESTER::RunTests( DS_PROXY_VIEW_ITEM* aDrawingSheet, SCH_EDIT_FRAME* aE m_schematic->ConnectionGraph()->RunERC(); + if( aProgressReporter ) + aProgressReporter->AdvancePhase( _( "Checking rule areas..." ) ); + + if( settings.IsTestEnabled( ERCE_OVERLAPPING_RULE_AREAS ) ) + { + RunRuleAreaERC(); + } + if( aProgressReporter ) aProgressReporter->AdvancePhase( _( "Checking units..." ) ); diff --git a/eeschema/erc.h b/eeschema/erc.h index 8177130698..7d3fa2d22c 100644 --- a/eeschema/erc.h +++ b/eeschema/erc.h @@ -27,6 +27,8 @@ #define ERC_H #include +#include +#include class SCH_SHEET_LIST; @@ -36,6 +38,8 @@ class SCH_EDIT_FRAME; class PROGRESS_REPORTER; struct KIFACE; class PROJECT; +class SCREEN; +class SCH_RULE_AREA; extern const wxString CommentERC_H[]; @@ -141,6 +145,17 @@ public: */ int TestMissingNetclasses(); + /** + * Tests for rule area ERC issues + */ + int RunRuleAreaERC(); + + /** + * Runs ERC to check for overlapping rule areas + */ + int TestRuleAreaOverlappingRuleAreasERC( + std::map>& allScreenRuleAreas ); + void RunTests( DS_PROXY_VIEW_ITEM* aDrawingSheet, SCH_EDIT_FRAME* aEditFrame, KIFACE* aCvPcb, PROJECT* aProject, PROGRESS_REPORTER* aProgressReporter ); diff --git a/eeschema/erc_item.cpp b/eeschema/erc_item.cpp index 372bf4196d..caf806b075 100644 --- a/eeschema/erc_item.cpp +++ b/eeschema/erc_item.cpp @@ -119,6 +119,10 @@ ERC_ITEM ERC_ITEM::netclassConflict( ERCE_NETCLASS_CONFLICT, _( "Conflicting netclass assignments" ), wxT( "conflicting_netclasses" ) ); +ERC_ITEM ERC_ITEM::overlappingRuleAreas( ERCE_OVERLAPPING_RULE_AREAS, + _( "Overlapping rule areas" ), + wxT( "overlapping_rule_areas" ) ); + ERC_ITEM ERC_ITEM::netNotBusMember( ERCE_BUS_ENTRY_CONFLICT, _( "Net is graphically connected to a bus but not a bus member" ), wxT( "net_not_bus_member" ) ); @@ -239,7 +243,8 @@ std::vector> ERC_ITEM::allItemTypes( { ERC_ITEM::missingUnits, ERC_ITEM::missingInputPin, ERC_ITEM::missingBidiPin, - ERC_ITEM::missingPowerInputPin + ERC_ITEM::missingPowerInputPin, + ERC_ITEM::overlappingRuleAreas } ); @@ -269,6 +274,7 @@ std::shared_ptr ERC_ITEM::Create( int aErrorCode ) case ERCE_BUS_TO_BUS_CONFLICT: return std::make_shared( busToBusConflict ); case ERCE_BUS_TO_NET_CONFLICT: return std::make_shared( busToNetConflict ); case ERCE_NETCLASS_CONFLICT: return std::make_shared( netclassConflict ); + case ERCE_OVERLAPPING_RULE_AREAS: return std::make_shared( overlappingRuleAreas ); case ERCE_GLOBLABEL: return std::make_shared( globalLabelDangling ); case ERCE_UNRESOLVED_VARIABLE: return std::make_shared( unresolvedVariable ); case ERCE_UNDEFINED_NETCLASS: return std::make_shared( undefinedNetclass ); diff --git a/eeschema/erc_item.h b/eeschema/erc_item.h index 6d400e24b3..e25a3a5689 100644 --- a/eeschema/erc_item.h +++ b/eeschema/erc_item.h @@ -202,6 +202,7 @@ private: static ERC_ITEM busDefinitionConflict; static ERC_ITEM multipleNetNames; static ERC_ITEM netclassConflict; + static ERC_ITEM overlappingRuleAreas; static ERC_ITEM netNotBusMember; static ERC_ITEM busToBusConflict; static ERC_ITEM busToNetConflict; diff --git a/eeschema/erc_settings.h b/eeschema/erc_settings.h index c4aeb64046..5e0fc6cae3 100644 --- a/eeschema/erc_settings.h +++ b/eeschema/erc_settings.h @@ -67,6 +67,7 @@ enum ERCE_T ERCE_BUS_TO_NET_CONFLICT, ///< A bus wire is graphically connected to a net port/pin ///< (or vice versa). ERCE_NETCLASS_CONFLICT, ///< Multiple labels assign different netclasses to same net. + ERCE_OVERLAPPING_RULE_AREAS, ///< Rule areas are overlapping ERCE_GLOBLABEL, ///< A global label is unique. ERCE_UNRESOLVED_VARIABLE, ///< A text variable could not be resolved. ERCE_UNDEFINED_NETCLASS, ///< A netclass was referenced but not defined. diff --git a/eeschema/menubar.cpp b/eeschema/menubar.cpp index 554d08d5a8..86413df09c 100644 --- a/eeschema/menubar.cpp +++ b/eeschema/menubar.cpp @@ -248,8 +248,9 @@ void SCH_EDIT_FRAME::doReCreateMenuBar() placeMenu->Add( EE_ACTIONS::placeNoConnect ); placeMenu->Add( EE_ACTIONS::placeJunction ); placeMenu->Add( EE_ACTIONS::placeLabel ); - placeMenu->Add( EE_ACTIONS::placeClassLabel ); placeMenu->Add( EE_ACTIONS::placeGlobalLabel ); + placeMenu->Add( EE_ACTIONS::placeClassLabel ); + placeMenu->Add( EE_ACTIONS::drawRuleArea ); placeMenu->AppendSeparator(); placeMenu->Add( EE_ACTIONS::placeHierLabel ); diff --git a/eeschema/sch_commit.cpp b/eeschema/sch_commit.cpp index 8f7a637e99..76aec856d5 100644 --- a/eeschema/sch_commit.cpp +++ b/eeschema/sch_commit.cpp @@ -221,22 +221,21 @@ void SCH_COMMIT::pushSchEdit( const wxString& aMessage, int aCommitFlags ) } ); } - auto updateConnectivityFlag = - [&]() - { - if( schItem->IsConnectable() ) - { - dirtyConnectivity = true; + auto updateConnectivityFlag = [&]() + { + if( schItem->IsConnectable() || ( schItem->Type() == SCH_RULE_AREA_T ) ) + { + dirtyConnectivity = true; - // Do a local clean up if there are any connectable objects in the commit. - if( connectivityCleanUp == NO_CLEANUP ) - connectivityCleanUp = LOCAL_CLEANUP; + // Do a local clean up if there are any connectable objects in the commit. + if( connectivityCleanUp == NO_CLEANUP ) + connectivityCleanUp = LOCAL_CLEANUP; - // Do a full rebauild of the connectivity if there is a sheet in the commit. - if( schItem->Type() == SCH_SHEET_T ) - connectivityCleanUp = GLOBAL_CLEANUP; - } - }; + // Do a full rebauild of the connectivity if there is a sheet in the commit. + if( schItem->Type() == SCH_SHEET_T ) + connectivityCleanUp = GLOBAL_CLEANUP; + } + }; switch( changeType ) { @@ -318,8 +317,11 @@ void SCH_COMMIT::pushSchEdit( const wxString& aMessage, int aCommitFlags ) if( frame ) currentSheet = frame->GetCurrentSheet(); - if( itemCopy->HasConnectivityChanges( schItem, ¤tSheet ) ) + if( itemCopy->HasConnectivityChanges( schItem, ¤tSheet ) + || ( itemCopy->Type() == SCH_RULE_AREA_T ) ) + { updateConnectivityFlag(); + } undoList.PushItem( itemWrapper ); ent.m_copy = nullptr; // We've transferred ownership to the undo list diff --git a/eeschema/sch_edit_frame.cpp b/eeschema/sch_edit_frame.cpp index e126eccccf..8571f7531e 100644 --- a/eeschema/sch_edit_frame.cpp +++ b/eeschema/sch_edit_frame.cpp @@ -22,6 +22,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ +#include #include #include #include @@ -36,6 +37,7 @@ #include #include #include +#include #include #include #include @@ -59,6 +61,7 @@ #include #include #include +#include #include #include #include @@ -85,6 +88,7 @@ #include #include #include +#include #include #include #include @@ -756,6 +760,7 @@ void SCH_EDIT_FRAME::setupUIConditions() CURRENT_TOOL( EE_ACTIONS::placeClassLabel ); CURRENT_TOOL( EE_ACTIONS::placeGlobalLabel ); CURRENT_TOOL( EE_ACTIONS::placeHierLabel ); + CURRENT_TOOL( EE_ACTIONS::drawRuleArea ); CURRENT_TOOL( EE_ACTIONS::drawSheet ); CURRENT_TOOL( EE_ACTIONS::placeSheetPin ); CURRENT_TOOL( EE_ACTIONS::syncSheetPins ); @@ -1763,52 +1768,150 @@ void SCH_EDIT_FRAME::RecalculateConnections( SCH_COMMIT* aCommit, SCH_CLEANUP_FL if( !ADVANCED_CFG::GetCfg().m_IncrementalConnectivity || aCleanupFlags == GLOBAL_CLEANUP || m_undoList.m_CommandsList.empty() ) { + // Update all rule areas so we can cascade implied connectivity changes + std::unordered_set all_screens; + + for( const SCH_SHEET_PATH& path : list ) + all_screens.insert( path.LastScreen() ); + + SCH_RULE_AREA::UpdateRuleAreasInScreens( all_screens, GetCanvas()->GetView() ); + + // Recalculate all connectivity Schematic().ConnectionGraph()->Recalculate( list, true, &changeHandler ); } else { + struct CHANGED_ITEM + { + SCH_ITEM* item; + SCH_ITEM* linked_item; + SCH_SCREEN* screen; + }; + PICKED_ITEMS_LIST* changed_list = m_undoList.m_CommandsList.back(); - std::set changed_items; - std::set pts; - std::set> item_paths; - for( unsigned ii = 0; ii < changed_list->GetCount(); ++ii ) - { - SCH_ITEM* item = dynamic_cast( changed_list->GetPickedItem( ii ) ); + // Final change sets + std::set changed_items; + std::set pts; + std::set> item_paths; - // Ignore objects that are not connectable. - if( !item || !item->IsConnectable() ) - continue; + // Working change sets + std::unordered_set changed_screens; + std::set> changed_rule_areas; + std::vector changed_connectable_items; - SCH_SCREEN* screen = static_cast( changed_list->GetScreenForItem( ii ) ); - SCH_SHEET_PATHS& paths = screen->GetClientSheetPaths(); + // Lambda to add an item to the connectivity update sets + auto addItemToChangeSet = + [&changed_items, &pts, &item_paths, &changed_screens]( CHANGED_ITEM itemData ) + { + SCH_SHEET_PATHS& paths = itemData.screen->GetClientSheetPaths(); - std::vector tmp_pts = item->GetConnectionPoints(); + std::vector tmp_pts = itemData.item->GetConnectionPoints(); pts.insert( tmp_pts.begin(), tmp_pts.end() ); - changed_items.insert( item ); + changed_items.insert( itemData.item ); for( SCH_SHEET_PATH& path : paths ) - item_paths.insert( std::make_pair( path, item ) ); - - item = dynamic_cast( changed_list->GetPickedItemLink( ii ) ); + item_paths.insert( std::make_pair( path, itemData.item ) ); - if( !item || !item->IsConnectable() ) - continue; + if( !itemData.linked_item || !itemData.linked_item->IsConnectable() ) + return; - tmp_pts = item->GetConnectionPoints(); + tmp_pts = itemData.linked_item->GetConnectionPoints(); pts.insert( tmp_pts.begin(), tmp_pts.end() ); - changed_items.insert( item ); + changed_items.insert( itemData.linked_item ); // We have to directly add the pins here because the link may not exist on the schematic // anymore and so won't be picked up by GetScreen()->Items().Overlapping() below. - if( SCH_SYMBOL* symbol = dynamic_cast( item ) ) + if( SCH_SYMBOL* symbol = dynamic_cast( itemData.linked_item ) ) { std::vector pins = symbol->GetPins(); changed_items.insert( pins.begin(), pins.end() ); } for( SCH_SHEET_PATH& path : paths ) - item_paths.insert( std::make_pair( path, item ) ); + item_paths.insert( std::make_pair( path, itemData.linked_item ) ); + }; + + // Get all changed connectable items and determine all changed screens + for( unsigned ii = 0; ii < changed_list->GetCount(); ++ii ) + { + SCH_ITEM* item = dynamic_cast( changed_list->GetPickedItem( ii ) ); + + if( item ) + { + SCH_SCREEN* screen = + static_cast( changed_list->GetScreenForItem( ii ) ); + changed_screens.insert( screen ); + + if( item->Type() == SCH_RULE_AREA_T ) + { + SCH_RULE_AREA* ruleArea = static_cast( item ); + + // Clear item and directive associations for this rule area + ruleArea->ResetDirectivesAndItems( GetCanvas()->GetView() ); + + changed_rule_areas.insert( { ruleArea, screen } ); + } + else if( item->IsConnectable() ) + { + SCH_ITEM* linked_item = + dynamic_cast( changed_list->GetPickedItemLink( ii ) ); + changed_connectable_items.push_back( { item, linked_item, screen } ); + } + } + } + + // Update rule areas in changed screens to propagate any directive connectivity changes + std::vector> forceUpdateRuleAreas = + SCH_RULE_AREA::UpdateRuleAreasInScreens( changed_screens, GetCanvas()->GetView() ); + + std::for_each( forceUpdateRuleAreas.begin(), forceUpdateRuleAreas.end(), + [&]( std::pair& updatedRuleArea ) + { + changed_rule_areas.insert( updatedRuleArea ); + } ); + + // If a SCH_RULE_AREA was changed, we need to add all past and present contained items to + // update their connectivity + for( const std::pair& changedRuleArea : changed_rule_areas ) + { + for( SCH_ITEM* containedItem : + changedRuleArea.first->GetPastAndPresentContainedItems() ) + { + addItemToChangeSet( { containedItem, nullptr, changedRuleArea.second } ); + } + } + + // Add all changed items, and associated items, to the change set + for( CHANGED_ITEM& changed_item_data : changed_connectable_items ) + { + addItemToChangeSet( changed_item_data ); + + // If a SCH_DIRECTIVE_LABEL was changed which is attached to a SCH_RULE_AREA, we need + // to add the contained items to the change set to force update of their connectivity + if( changed_item_data.item->Type() == SCH_DIRECTIVE_LABEL_T ) + { + const std::vector labelConnectionPoints = + changed_item_data.item->GetConnectionPoints(); + + EE_RTREE::EE_TYPE candidateRuleAreas = + changed_item_data.screen->Items().Overlapping( + SCH_RULE_AREA_T, changed_item_data.item->GetBoundingBox() ); + + for( SCH_ITEM* candidateRuleArea : candidateRuleAreas ) + { + SCH_RULE_AREA* ruleArea = static_cast( candidateRuleArea ); + std::vector borderShapes = ruleArea->MakeEffectiveShapes( true ); + + if( ruleArea->GetPolyShape().CollideEdge( labelConnectionPoints[0], nullptr, + 5 ) ) + { + for( SCH_ITEM* containedItem : ruleArea->GetPastAndPresentContainedItems() ) + addItemToChangeSet( + { containedItem, nullptr, changed_item_data.screen } ); + } + } + } } for( const VECTOR2I& pt: pts ) diff --git a/eeschema/sch_file_versions.h b/eeschema/sch_file_versions.h index 3c9c791588..3f79516871 100644 --- a/eeschema/sch_file_versions.h +++ b/eeschema/sch_file_versions.h @@ -103,4 +103,5 @@ //#define SEXPR_SCHEMATIC_FILE_VERSION 20230808 // Move Sim.Enable field to exclude_from_sim attr //#define SEXPR_SCHEMATIC_FILE_VERSION 20230819 // Allow multiple library symbol inheritance depth. //#define SEXPR_SCHEMATIC_FILE_VERSION 20231120 // generator_version; V8 cleanups -#define SEXPR_SCHEMATIC_FILE_VERSION 20240101 // Tables. +//#define SEXPR_SCHEMATIC_FILE_VERSION 20240101 // Tables. +#define SEXPR_SCHEMATIC_FILE_VERSION 20240417 // Rule areas diff --git a/eeschema/sch_io/kicad_sexpr/sch_io_kicad_sexpr.cpp b/eeschema/sch_io/kicad_sexpr/sch_io_kicad_sexpr.cpp index 87f33f42b1..7e3412603b 100644 --- a/eeschema/sch_io/kicad_sexpr/sch_io_kicad_sexpr.cpp +++ b/eeschema/sch_io/kicad_sexpr/sch_io_kicad_sexpr.cpp @@ -42,6 +42,7 @@ #include #include #include +#include #include #include #include @@ -470,6 +471,10 @@ void SCH_IO_KICAD_SEXPR::Format( SCH_SHEET* aSheet ) case SCH_SHAPE_T: saveShape( static_cast( item ), 1 ); break; + + case SCH_RULE_AREA_T: + saveRuleArea( static_cast( item ), 1 ); + break; case SCH_TEXT_T: case SCH_LABEL_T: @@ -599,6 +604,10 @@ void SCH_IO_KICAD_SEXPR::Format( EE_SELECTION* aSelection, SCH_SHEET_PATH* aSele saveShape( static_cast( item ), 0 ); break; + case SCH_RULE_AREA_T: + saveRuleArea( static_cast( item ), 0 ); + break; + case SCH_TEXT_T: case SCH_LABEL_T: case SCH_GLOBAL_LABEL_T: @@ -1234,6 +1243,16 @@ void SCH_IO_KICAD_SEXPR::saveShape( SCH_SHAPE* aShape, int aNestLevel ) } +void SCH_IO_KICAD_SEXPR::saveRuleArea( SCH_RULE_AREA* aRuleArea, int aNestLevel ) +{ + wxCHECK_RET( aRuleArea != nullptr && m_out != nullptr, "" ); + + m_out->Print( aNestLevel, "(rule_area " ); + saveShape( aRuleArea, aNestLevel + 1 ); + m_out->Print( aNestLevel, ")\n" ); +} + + void SCH_IO_KICAD_SEXPR::saveLine( SCH_LINE* aLine, int aNestLevel ) { wxCHECK_RET( aLine != nullptr && m_out != nullptr, "" ); diff --git a/eeschema/sch_io/kicad_sexpr/sch_io_kicad_sexpr.h b/eeschema/sch_io/kicad_sexpr/sch_io_kicad_sexpr.h index 3f7f95a386..0918339f6e 100644 --- a/eeschema/sch_io/kicad_sexpr/sch_io_kicad_sexpr.h +++ b/eeschema/sch_io/kicad_sexpr/sch_io_kicad_sexpr.h @@ -43,6 +43,7 @@ class SCH_JUNCTION; class SCH_NO_CONNECT; class SCH_LINE; class SCH_SHAPE; +class SCH_RULE_AREA; class SCH_BUS_ENTRY_BASE; class SCH_TEXT; class SCH_TEXTBOX; @@ -151,6 +152,7 @@ private: void saveBusEntry( SCH_BUS_ENTRY_BASE* aBusEntry, int aNestLevel ); void saveLine( SCH_LINE* aLine, int aNestLevel ); void saveShape( SCH_SHAPE* aShape, int aNestLevel ); + void saveRuleArea( SCH_RULE_AREA* aRuleArea, int aNestLevel ); void saveText( SCH_TEXT* aText, int aNestLevel ); void saveTextBox( SCH_TEXTBOX* aText, int aNestLevel ); void saveTable( SCH_TABLE* aTable, int aNestLevel ); diff --git a/eeschema/sch_io/kicad_sexpr/sch_io_kicad_sexpr_parser.cpp b/eeschema/sch_io/kicad_sexpr/sch_io_kicad_sexpr_parser.cpp index 942a088536..54db6948b0 100644 --- a/eeschema/sch_io/kicad_sexpr/sch_io_kicad_sexpr_parser.cpp +++ b/eeschema/sch_io/kicad_sexpr/sch_io_kicad_sexpr_parser.cpp @@ -46,6 +46,7 @@ #include // SYM_ORIENT_XXX #include #include +#include #include #include #include @@ -2745,6 +2746,10 @@ void SCH_IO_KICAD_SEXPR_PARSER::ParseSchematic( SCH_SHEET* aSheet, bool aIsCopya screen->Append( parseSchBezier() ); break; + case T_rule_area: + screen->Append( parseSchRuleArea() ); + break; + case T_netclass_flag: // present only during early development of 7.0 KI_FALLTHROUGH; @@ -2782,7 +2787,7 @@ void SCH_IO_KICAD_SEXPR_PARSER::ParseSchematic( SCH_SHEET* aSheet, bool aIsCopya default: Expecting( "symbol, paper, page, title_block, bitmap, sheet, junction, no_connect, " "bus_entry, line, bus, text, label, class_label, global_label, " - "hierarchical_label, symbol_instances, or bus_alias" ); + "hierarchical_label, symbol_instances, rule_area, or bus_alias" ); } } @@ -3927,6 +3932,43 @@ SCH_SHAPE* SCH_IO_KICAD_SEXPR_PARSER::parseSchRectangle() } +SCH_RULE_AREA* SCH_IO_KICAD_SEXPR_PARSER::parseSchRuleArea() +{ + wxCHECK_MSG( CurTok() == T_rule_area, nullptr, + wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as a rule area." ) ); + + T token; + STROKE_PARAMS stroke( schIUScale.MilsToIU( DEFAULT_LINE_WIDTH_MILS ), LINE_STYLE::DEFAULT ); + FILL_PARAMS fill; + std::unique_ptr ruleArea = std::make_unique(); + + for( token = NextTok(); token != T_RIGHT; token = NextTok() ) + { + if( token != T_LEFT ) + Expecting( T_LEFT ); + + token = NextTok(); + + switch( token ) + { + case T_polyline: + { + std::unique_ptr poly( parseSchPolyLine() ); + ruleArea->SetPolyShape( poly->GetPolyShape() ); + ruleArea->SetStroke( poly->GetStroke() ); + ruleArea->SetFillMode( poly->GetFillMode() ); + ruleArea->SetFillColor( poly->GetFillColor() ); + break; + } + default: + Expecting( "polyline" ); + } + } + + return ruleArea.release(); +} + + SCH_SHAPE* SCH_IO_KICAD_SEXPR_PARSER::parseSchBezier() { wxCHECK_MSG( CurTok() == T_bezier, nullptr, diff --git a/eeschema/sch_io/kicad_sexpr/sch_io_kicad_sexpr_parser.h b/eeschema/sch_io/kicad_sexpr/sch_io_kicad_sexpr_parser.h index bcac0b91ab..62d9a823b0 100644 --- a/eeschema/sch_io/kicad_sexpr/sch_io_kicad_sexpr_parser.h +++ b/eeschema/sch_io/kicad_sexpr/sch_io_kicad_sexpr_parser.h @@ -45,6 +45,7 @@ class SCH_SYMBOL; class SCH_FIELD; class SCH_ITEM; class SCH_SHAPE; +class SCH_RULE_AREA; class SCH_JUNCTION; class SCH_LINE; class SCH_NO_CONNECT; @@ -221,6 +222,7 @@ private: SCH_SHAPE* parseSchCircle(); SCH_SHAPE* parseSchRectangle(); SCH_SHAPE* parseSchBezier(); + SCH_RULE_AREA* parseSchRuleArea(); SCH_TEXT* parseSchText(); SCH_TEXTBOX* parseSchTextBox(); void parseSchTextBoxContent( SCH_TEXTBOX* aTextBox ); diff --git a/eeschema/sch_item.h b/eeschema/sch_item.h index 310b51bdc9..97a6affb19 100644 --- a/eeschema/sch_item.h +++ b/eeschema/sch_item.h @@ -26,6 +26,7 @@ #define SCH_ITEM_H #include +#include #include #include @@ -44,6 +45,7 @@ class SCHEMATIC; class SYMBOL; class LINE_READER; class SCH_EDIT_FRAME; +class SCH_RULE_AREA; struct SCH_PLOT_OPTS; namespace KIFONT @@ -628,6 +630,24 @@ public: wxCHECK_MSG( false, /*void*/, wxT( "Plot not implemented in " ) + GetClass() ); } + /** + * Reset the cache of rule areas (called prior to schematic connectivity recomputation) + */ + void ClearRuleAreasCache() { m_rule_areas_cache.clear(); } + + /** + * Adds a rule area to the item's cache + */ + void AddRuleAreaToCache( SCH_RULE_AREA* aRuleArea ) { m_rule_areas_cache.insert( aRuleArea ); } + + /** + * Gets the cache of rule areas enclosing this item + */ + const std::unordered_set& GetRuleAreaCache() const + { + return m_rule_areas_cache; + } + /** * The list of flags used by the #compare function. * @@ -721,6 +741,9 @@ protected: bool m_connectivity_dirty; + /// Store pointers to rule areas which this item is contained within + std::unordered_set m_rule_areas_cache; + private: friend class LIB_SYMBOL; }; diff --git a/eeschema/sch_label.cpp b/eeschema/sch_label.cpp index 16445f49e4..ba57c68b6d 100644 --- a/eeschema/sch_label.cpp +++ b/eeschema/sch_label.cpp @@ -1808,6 +1808,30 @@ wxString SCH_DIRECTIVE_LABEL::GetItemDescription( UNITS_PROVIDER* aUnitsProvider } +void SCH_DIRECTIVE_LABEL::AddConnectedRuleArea( SCH_RULE_AREA* aRuleArea ) +{ + m_connected_rule_areas.insert( aRuleArea ); +} + + +void SCH_DIRECTIVE_LABEL::ClearConnectedRuleAreas() +{ + m_connected_rule_areas.clear(); +} + + +void SCH_DIRECTIVE_LABEL::RemoveConnectedRuleArea( SCH_RULE_AREA* aRuleArea ) +{ + m_connected_rule_areas.erase( aRuleArea ); +} + + +bool SCH_DIRECTIVE_LABEL::IsDangling() const +{ + return m_isDangling && m_connected_rule_areas.empty(); +} + + SCH_GLOBALLABEL::SCH_GLOBALLABEL( const VECTOR2I& pos, const wxString& text ) : SCH_LABEL_BASE( pos, text, SCH_GLOBAL_LABEL_T ) { diff --git a/eeschema/sch_label.h b/eeschema/sch_label.h index 01d97d5a51..b1f593532d 100644 --- a/eeschema/sch_label.h +++ b/eeschema/sch_label.h @@ -30,6 +30,8 @@ #include #include // for CONNECTION_TYPE +class SCH_RULE_AREA; + /* * Spin style for labels of all kinds on schematics @@ -474,9 +476,24 @@ public: void MirrorHorizontally( int aCenter ) override; void MirrorVertically( int aCenter ) override; + /// @brief Adds an entry to the connected rule area cache + void AddConnectedRuleArea( SCH_RULE_AREA* aRuleArea ); + + /// @brief Removes all rule areas from the cache + void ClearConnectedRuleAreas(); + + /// @brief Removes a specific rule area from the cache + void RemoveConnectedRuleArea( SCH_RULE_AREA* aRuleArea ); + + /// @brief Determines dangling state from connectivity and cached connected rule areas + virtual bool IsDangling() const override; + private: int m_pinLength; int m_symbolSize; + + /// Cache of any rule areas with borders which this label connects to + std::unordered_set m_connected_rule_areas; }; diff --git a/eeschema/sch_painter.cpp b/eeschema/sch_painter.cpp index 7888b56d7f..91e24a7786 100644 --- a/eeschema/sch_painter.cpp +++ b/eeschema/sch_painter.cpp @@ -79,6 +79,7 @@ std::vector SCH_PAINTER::g_ScaledSelectionTypes = { SCH_BUS_BUS_ENTRY_T, SCH_LINE_T, SCH_SHAPE_T, + SCH_RULE_AREA_T, SCH_BITMAP_T, SCH_TEXT_T, SCH_GLOBAL_LABEL_T, @@ -194,6 +195,9 @@ void SCH_PAINTER::draw( const EDA_ITEM* aItem, int aLayer, bool aDimmed ) case SCH_SHAPE_T: draw( static_cast( aItem ), aLayer, aDimmed ); break; + case SCH_RULE_AREA_T: + draw( static_cast( aItem ), aLayer, aDimmed ); + break; case SCH_TEXT_T: draw( static_cast( aItem ), aLayer, aDimmed ); break; @@ -352,7 +356,7 @@ COLOR4D SCH_PAINTER::getRenderColor( const SCH_ITEM* aItem, int aLayer, bool aDr else color = sheet->GetBorderColor(); } - else if( aItem->Type() == SCH_SHAPE_T ) + else if( aItem->Type() == SCH_SHAPE_T || aItem->Type() == SCH_RULE_AREA_T ) { const SCH_SHAPE* shape = static_cast( aItem ); @@ -373,7 +377,17 @@ COLOR4D SCH_PAINTER::getRenderColor( const SCH_ITEM* aItem, int aLayer, bool aDr // A filled shape means filled; if they didn't specify a fill colour then use the // border colour. if( color == COLOR4D::UNSPECIFIED ) - color = m_schSettings.GetLayerColor( isSymbolChild ? LAYER_DEVICE : LAYER_NOTES ); + { + if( aItem->Type() == SCH_RULE_AREA_T ) + { + color = m_schSettings.GetLayerColor( LAYER_RULE_AREAS ); + } + else + { + color = m_schSettings.GetLayerColor( isSymbolChild ? LAYER_DEVICE + : LAYER_NOTES ); + } + } } else if( aItem->IsType( { SCH_LABEL_LOCATE_ANY_T } ) ) { @@ -1566,11 +1580,20 @@ void SCH_PAINTER::draw( const SCH_SHAPE* aShape, int aLayer, bool aDimmed ) case SHAPE_T::POLY: { - const SHAPE_LINE_CHAIN poly = shape->GetPolyShape().Outline( 0 ); - std::deque mappedPts; + const std::vector polySegments = shape->MakeEffectiveShapes( true ); + std::deque mappedPts; - for( const VECTOR2I& pt : poly.CPoints() ) - mappedPts.push_back( MAP_COORDS( pt ) ); + for( SHAPE* polySegment : polySegments ) + { + mappedPts.push_back( MAP_COORDS( + static_cast( polySegment )->GetSeg().A ) ); + } + + mappedPts.push_back( MAP_COORDS( + static_cast( polySegments.back() )->GetSeg().B ) ); + + for( SHAPE* polySegment : polySegments ) + delete polySegment; m_gal->DrawPolygon( mappedPts ); break; @@ -1629,7 +1652,8 @@ void SCH_PAINTER::draw( const SCH_SHAPE* aShape, int aLayer, bool aDimmed ) drawShape( aShape ); } } - else if( aLayer == LAYER_DEVICE || aLayer == LAYER_NOTES || aLayer == LAYER_PRIVATE_NOTES ) + else if( aLayer == LAYER_DEVICE || aLayer == LAYER_NOTES || aLayer == LAYER_PRIVATE_NOTES + || aLayer == LAYER_RULE_AREAS ) { float lineWidth = getLineWidth( aShape, drawingShadows ); diff --git a/eeschema/sch_plotter.h b/eeschema/sch_plotter.h index 48930344ee..68e25edae4 100644 --- a/eeschema/sch_plotter.h +++ b/eeschema/sch_plotter.h @@ -27,6 +27,7 @@ #ifndef SCH_PLOTTER_H #define SCH_PLOTTER_H +#include #include #include #include diff --git a/eeschema/sch_rule_area.cpp b/eeschema/sch_rule_area.cpp new file mode 100644 index 0000000000..36958e3f02 --- /dev/null +++ b/eeschema/sch_rule_area.cpp @@ -0,0 +1,442 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2024 KiCad Developers, see AUTHORS.txt for contributors. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +wxString SCH_RULE_AREA::GetClass() const +{ + return wxT( "SCH_RULE_AREA" ); +} + + +wxString SCH_RULE_AREA::GetFriendlyName() const +{ + return _( "Rule Area" ); +} + + +EDA_ITEM* SCH_RULE_AREA::Clone() const +{ + return new SCH_RULE_AREA( *this ); +} + + +void SCH_RULE_AREA::ViewGetLayers( int aLayers[], int& aCount ) const +{ + aCount = 3; + aLayers[0] = LAYER_RULE_AREAS; + aLayers[1] = LAYER_NOTES_BACKGROUND; + aLayers[2] = LAYER_SELECTION_SHADOWS; +} + + +std::vector SCH_RULE_AREA::MakeEffectiveShapes( bool aEdgeOnly ) const +{ + std::vector effectiveShapes; + int width = GetEffectiveWidth(); + + switch( m_shape ) + { + case SHAPE_T::POLY: + { + if( GetPolyShape().OutlineCount() == 0 ) // malformed/empty polygon + break; + + for( int ii = 0; ii < GetPolyShape().OutlineCount(); ++ii ) + { + const SHAPE_LINE_CHAIN& l = GetPolyShape().COutline( ii ); + + if( IsFilled() && !aEdgeOnly ) + effectiveShapes.emplace_back( new SHAPE_SIMPLE( l ) ); + + if( width > 0 || !IsFilled() || aEdgeOnly ) + { + int segCount = l.SegmentCount(); + + for( int jj = 0; jj < segCount; jj++ ) + effectiveShapes.emplace_back( new SHAPE_SEGMENT( l.CSegment( jj ), width ) ); + } + } + } + break; + + default: + return SCH_SHAPE::MakeEffectiveShapes( aEdgeOnly ); + break; + } + + return effectiveShapes; +} + + +void SCH_RULE_AREA::Plot( PLOTTER* aPlotter, bool aBackground, const SCH_PLOT_OPTS& aPlotOpts, + int aUnit, int aBodyStyle, const VECTOR2I& aOffset, bool aDimmed ) +{ + if( IsPrivate() ) + return; + + SCH_RENDER_SETTINGS* renderSettings = getRenderSettings( aPlotter ); + int pen_size = GetEffectivePenWidth( renderSettings ); + + if( GetShape() != SHAPE_T::POLY ) + SCH_SHAPE::Plot( aPlotter, aBackground, aPlotOpts, aUnit, aBodyStyle, aOffset, aDimmed ); + + static std::vector ptList; + + ptList.clear(); + + const std::vector& polyPoints = m_poly.Outline( 0 ).CPoints(); + + for( const VECTOR2I& pt : polyPoints ) + { + ptList.push_back( pt ); + } + + ptList.push_back( polyPoints[0] ); + + COLOR4D color = GetStroke().GetColor(); + COLOR4D bg = renderSettings->GetBackgroundColor(); + LINE_STYLE lineStyle = GetStroke().GetLineStyle(); + FILL_T fill = m_fill; + + if( aBackground ) + { + if( !aPlotter->GetColorMode() ) + return; + + switch( m_fill ) + { + case FILL_T::FILLED_SHAPE: + return; + + case FILL_T::FILLED_WITH_COLOR: + color = GetFillColor(); + break; + + case FILL_T::FILLED_WITH_BG_BODYCOLOR: + color = renderSettings->GetLayerColor( LAYER_DEVICE_BACKGROUND ); + break; + + default: + return; + } + + pen_size = 0; + lineStyle = LINE_STYLE::SOLID; + } + else /* if( aForeground ) */ + { + if( !aPlotter->GetColorMode() || color == COLOR4D::UNSPECIFIED ) + color = renderSettings->GetLayerColor( m_layer ); + + if( lineStyle == LINE_STYLE::DEFAULT ) + lineStyle = LINE_STYLE::SOLID; + + if( m_fill == FILL_T::FILLED_SHAPE ) + fill = m_fill; + else + fill = FILL_T::NO_FILL; + + pen_size = GetEffectivePenWidth( renderSettings ); + } + + if( bg == COLOR4D::UNSPECIFIED || !aPlotter->GetColorMode() ) + bg = COLOR4D::WHITE; + + if( aDimmed ) + { + color.Desaturate(); + color = color.Mix( bg, 0.5f ); + } + + aPlotter->SetColor( color ); + aPlotter->SetCurrentLineWidth( pen_size ); + aPlotter->SetDash( pen_size, lineStyle ); + + aPlotter->PlotPoly( ptList, fill, pen_size ); + + aPlotter->SetDash( pen_size, LINE_STYLE::SOLID ); +} + + +wxString SCH_RULE_AREA::GetItemDescription( UNITS_PROVIDER* aUnitsProvider ) const +{ + return _( "Schematic rule area" ); +} + + +void SCH_RULE_AREA::ResetCaches( KIGFX::SCH_VIEW* view ) +{ + // Save the current state + m_prev_items = m_items; + m_prev_directives = m_directives; + + // Reset the rule area + clearContainedItems(); + clearDirectives( view ); +} + + +void SCH_RULE_AREA::RefreshContainedItemsAndDirectives( + SCH_SCREEN* screen, KIGFX::SCH_VIEW* view, + std::vector>& forceUpdateRuleAreas ) +{ + EE_RTREE& items = screen->Items(); + const BOX2I boundingBox = GetBoundingBox(); + + // Get any SCH_DIRECTIVE_LABELs which are attached to the rule area border + std::unordered_set attachedDirectives; + EE_RTREE::EE_TYPE candidateDirectives = items.Overlapping( SCH_DIRECTIVE_LABEL_T, boundingBox ); + + for( SCH_ITEM* candidateDirective : candidateDirectives ) + { + SCH_DIRECTIVE_LABEL* label = static_cast( candidateDirective ); + const std::vector labelConnectionPoints = label->GetConnectionPoints(); + assert( labelConnectionPoints.size() == 1 ); + + if( GetPolyShape().CollideEdge( labelConnectionPoints[0], nullptr, 5 ) ) + { + addDirective( label, view ); + } + } + + // If directives have changed, we need to force an update of the contained items connectivity + if( m_directives != m_prev_directives ) + forceUpdateRuleAreas.push_back( { this, screen } ); + + // Next find any connectable items which lie within the rule area + EE_RTREE::EE_TYPE ruleAreaItems = items.Overlapping( boundingBox ); + + for( SCH_ITEM* areaItem : ruleAreaItems ) + { + if( areaItem->IsType( { SCH_ITEM_LOCATE_WIRE_T, SCH_ITEM_LOCATE_BUS_T } ) ) + { + SCH_LINE* lineItem = static_cast( areaItem ); + SHAPE_SEGMENT lineSeg( lineItem->GetStartPoint(), lineItem->GetEndPoint(), + lineItem->GetLineWidth() ); + + if( GetPolyShape().Collide( &lineSeg ) ) + { + addContainedItem( areaItem ); + } + } + else if( areaItem->IsType( + { SCH_PIN_T, SCH_LABEL_T, SCH_GLOBAL_LABEL_T, SCH_HIER_LABEL_T } ) ) + { + std::vector connectionPoints = areaItem->GetConnectionPoints(); + assert( connectionPoints.size() == 1 ); + + if( GetPolyShape().Collide( connectionPoints[0] ) ) + { + addContainedItem( areaItem ); + } + } + } +} + + +std::unordered_set SCH_RULE_AREA::GetPastAndPresentContainedItems() const +{ + std::unordered_set items = m_items; + + for( SCH_ITEM* item : m_prev_items ) + items.insert( item ); + + return items; +} + + +std::vector> +SCH_RULE_AREA::UpdateRuleAreasInScreens( std::unordered_set& screens, + KIGFX::SCH_VIEW* view ) +{ + std::vector> forceUpdateRuleAreas; + + for( SCH_SCREEN* screen : screens ) + { + // First reset all item caches - must be done first to ensure two rule areas + // on the same item don't overwrite each other's caches + for( SCH_ITEM* ruleAreaAsItem : screen->Items().OfType( SCH_RULE_AREA_T ) ) + { + SCH_RULE_AREA* ruleArea = static_cast( ruleAreaAsItem ); + ruleArea->ResetCaches( view ); + } + + // Secondly refresh the contained items + for( SCH_ITEM* ruleAreaAsItem : screen->Items().OfType( SCH_RULE_AREA_T ) ) + { + SCH_RULE_AREA* ruleArea = static_cast( ruleAreaAsItem ); + ruleArea->RefreshContainedItemsAndDirectives( screen, view, forceUpdateRuleAreas ); + } + } + + return forceUpdateRuleAreas; +} + + +const std::unordered_set& SCH_RULE_AREA::GetContainedItems() const +{ + return m_items; +} + + +const std::unordered_set SCH_RULE_AREA::GetDirectives() const +{ + return m_directives; +} + + +const std::vector> SCH_RULE_AREA::GetResolvedNetclasses() const +{ + std::vector> resolvedNetclasses; + + for( SCH_DIRECTIVE_LABEL* directive : m_directives ) + { + directive->RunOnChildren( + [&]( SCH_ITEM* aChild ) + { + if( aChild->Type() == SCH_FIELD_T ) + { + SCH_FIELD* field = static_cast( aChild ); + + if( field->GetCanonicalName() == wxT( "Netclass" ) ) + { + wxString netclass = field->GetText(); + + if( netclass != wxEmptyString ) + resolvedNetclasses.push_back( { netclass, directive } ); + } + } + + return true; + } ); + } + + return resolvedNetclasses; +} + + +void SCH_RULE_AREA::ResetDirectivesAndItems( KIGFX::SCH_VIEW* view ) +{ + for( SCH_DIRECTIVE_LABEL* label : m_directives ) + { + label->ClearConnectedRuleAreas(); + view->Update( label, KIGFX::REPAINT ); + } + + for( SCH_ITEM* item : m_items ) + item->ClearRuleAreasCache(); +} + + +void SCH_RULE_AREA::GetMsgPanelInfo( EDA_DRAW_FRAME* aFrame, std::vector& aList ) +{ + aList.emplace_back( _( "Rule Area" ), wxEmptyString ); + + wxString msg; + msg.Printf( wxS( "%d" ), GetPolyShape().Outline( 0 ).PointCount() ); + aList.emplace_back( _( "Points" ), msg ); + + m_stroke.GetMsgPanelInfo( aFrame, aList ); + + const std::vector> netclasses = + SCH_RULE_AREA::GetResolvedNetclasses(); + wxString resolvedNetclass = _( "" ); + + if( netclasses.size() > 0 ) + resolvedNetclass = netclasses[0].first; + + aList.emplace_back( _( "Resolved netclass" ), resolvedNetclass ); +} + + +void SCH_RULE_AREA::addDirective( SCH_DIRECTIVE_LABEL* label, KIGFX::SCH_VIEW* view ) +{ + label->AddConnectedRuleArea( this ); + m_directives.insert( label ); + + if( view ) + view->Update( label, KIGFX::REPAINT ); +} + + +void SCH_RULE_AREA::clearDirectives( KIGFX::SCH_VIEW* view ) +{ + for( SCH_DIRECTIVE_LABEL* label : m_directives ) + { + label->ClearConnectedRuleAreas(); + + if( view ) + view->Update( label, KIGFX::REPAINT ); + } + + m_directives.clear(); +} + + +void SCH_RULE_AREA::addContainedItem( SCH_ITEM* item ) +{ + item->AddRuleAreaToCache( this ); + m_items.insert( item ); +} + + +void SCH_RULE_AREA::clearContainedItems() +{ + for( SCH_ITEM* item : m_items ) + item->ClearRuleAreasCache(); + + m_items.clear(); +} + + +static struct SCH_RULE_AREA_DESC +{ + SCH_RULE_AREA_DESC() + { + PROPERTY_MANAGER& propMgr = PROPERTY_MANAGER::Instance(); + REGISTER_TYPE( SCH_RULE_AREA ); + propMgr.AddTypeCast( new TYPE_CAST ); + propMgr.AddTypeCast( new TYPE_CAST ); + propMgr.AddTypeCast( new TYPE_CAST ); + propMgr.InheritsAfter( TYPE_HASH( SCH_RULE_AREA ), TYPE_HASH( SCH_SHAPE ) ); + propMgr.InheritsAfter( TYPE_HASH( SCH_RULE_AREA ), TYPE_HASH( SCH_ITEM ) ); + propMgr.InheritsAfter( TYPE_HASH( SCH_RULE_AREA ), TYPE_HASH( EDA_SHAPE ) ); + } +} _SCH_RULE_AREA_DESC; diff --git a/eeschema/sch_rule_area.h b/eeschema/sch_rule_area.h new file mode 100644 index 0000000000..d8c4d9a48e --- /dev/null +++ b/eeschema/sch_rule_area.h @@ -0,0 +1,125 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2024 KiCad Developers, see AUTHORS.txt for contributors. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#ifndef SCH_RULE_AREA_H +#define SCH_RULE_AREA_H + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +class SCHEMATIC; + +class SCH_RULE_AREA : public SCH_SHAPE +{ +public: + SCH_RULE_AREA() : + SCH_SHAPE( SHAPE_T::POLY, LAYER_RULE_AREAS, 0 /* line width */, FILL_T::NO_FILL, + SCH_RULE_AREA_T ) + { + SetLayer( LAYER_RULE_AREAS ); + } + + virtual ~SCH_RULE_AREA() {} + + wxString GetClass() const override; + + wxString GetFriendlyName() const override; + + EDA_ITEM* Clone() const override; + + void ViewGetLayers( int aLayers[], int& aCount ) const override; + + virtual std::vector MakeEffectiveShapes( bool aEdgeOnly = false ) const override; + + virtual void Plot( PLOTTER* aPlotter, bool aBackground, const SCH_PLOT_OPTS& aPlotOpts, + int aUnit, int aBodyStyle, const VECTOR2I& aOffset, bool aDimmed ) override; + + wxString GetItemDescription( UNITS_PROVIDER* aUnitsProvider ) const override; + + /// @brief Resets all item and directive caches, saving the current state first + void ResetCaches( KIGFX::SCH_VIEW* view ); + + /// @brief Refreshes the list of items which this rule area affects + void RefreshContainedItemsAndDirectives( + SCH_SCREEN* screen, KIGFX::SCH_VIEW* view, + std::vector>& forceUpdateRuleAreas ); + + /// @brief Fetches all items which were, or are, within the rule area + std::unordered_set GetPastAndPresentContainedItems() const; + + /// @brief Updates all rule area connectvity / caches in the given sheet paths + /// @returns A map of all updated rule areas and their owning screen + static std::vector> + UpdateRuleAreasInScreens( std::unordered_set& screens, KIGFX::SCH_VIEW* view ); + + /// @brief Returns a set of all items contained within the rule area + const std::unordered_set& GetContainedItems() const; + + /// @brief Returns the set of all directive labels attached to the rule area border + const std::unordered_set GetDirectives() const; + + /// @brief Resolves the netclass of this rule area from connected directive labels + /// @returns The resolved netclass (if any), and the SCH_ITEM providing the declaration + const std::vector> GetResolvedNetclasses() const; + + /// @brief Clears and resets items and directives attached to this rule area + void ResetDirectivesAndItems( KIGFX::SCH_VIEW* view ); + + /// @brief Gets the message panel info for the rule area + void GetMsgPanelInfo( EDA_DRAW_FRAME* aFrame, std::vector& aList ) override; + +protected: + /// @brief Adds a directive label which applies to items within ths rule area + void addDirective( SCH_DIRECTIVE_LABEL* label, KIGFX::SCH_VIEW* view ); + + /// @brief Clears the list of directives + void clearDirectives( KIGFX::SCH_VIEW* view ); + + /// @briefs Adds an item to the list of items which this rule area affects + void addContainedItem( SCH_ITEM* item ); + + /// @brief Clears the list of items which this rule area affects + void clearContainedItems(); + + /// All SCH_ITEMs currently contained or intersecting the rule area + std::unordered_set m_items; + + /// All SCH_DIRECTIVE_LABELs attached to the rule area border + std::unordered_set m_directives; + + /// All SCH_ITEMs contained or intersecting the rule area in the previous update + std::unordered_set m_prev_items; + + /// All SCH_DIRECTIVE_LABELs attached to the rule area border in the previous update + std::unordered_set m_prev_directives; +}; + +#endif diff --git a/eeschema/sch_view.h b/eeschema/sch_view.h index 6559e99d51..ddf78b2bab 100644 --- a/eeschema/sch_view.h +++ b/eeschema/sch_view.h @@ -49,7 +49,7 @@ static const int SCH_LAYER_ORDER[] = LAYER_OP_VOLTAGES, LAYER_OP_CURRENTS, LAYER_REFERENCEPART, LAYER_VALUEPART, LAYER_FIELDS, LAYER_PINNUM, LAYER_PINNAM, - LAYER_INTERSHEET_REFS, LAYER_NETCLASS_REFS, + LAYER_INTERSHEET_REFS, LAYER_NETCLASS_REFS, LAYER_RULE_AREAS, LAYER_BUS_JUNCTION, LAYER_JUNCTION, LAYER_NOCONNECT, LAYER_HIERLABEL, LAYER_GLOBLABEL, LAYER_LOCLABEL, LAYER_SHEETFILENAME, LAYER_SHEETNAME, LAYER_SHEETLABEL, LAYER_SHEETFIELDS, diff --git a/eeschema/schematic.keywords b/eeschema/schematic.keywords index e00b2c68e6..fb05e65b46 100644 --- a/eeschema/schematic.keywords +++ b/eeschema/schematic.keywords @@ -128,6 +128,7 @@ right round rows row_heights +rule_area scale separators shape diff --git a/eeschema/schematic_undo_redo.cpp b/eeschema/schematic_undo_redo.cpp index 68d0e92a48..3688325d19 100644 --- a/eeschema/schematic_undo_redo.cpp +++ b/eeschema/schematic_undo_redo.cpp @@ -294,39 +294,46 @@ void SCH_EDIT_FRAME::PutDataInPreviousState( PICKED_ITEMS_LIST* aList ) // Set connectable object connectivity status. auto updateConnectivityFlag = [&, this]() { - if( schItem && schItem->IsConnectable() ) + if( schItem ) { - schItem->SetConnectivityDirty(); - - if( schItem->Type() == SCH_SYMBOL_T ) + if( schItem->IsConnectable() ) { - SCH_SYMBOL* symbol = static_cast( schItem ); + schItem->SetConnectivityDirty(); - wxCHECK( symbol, /* void */ ); + if( schItem->Type() == SCH_SYMBOL_T ) + { + SCH_SYMBOL* symbol = static_cast( schItem ); - for( SCH_PIN* pin : symbol->GetPins() ) - pin->SetConnectivityDirty(); - } - else if( schItem->Type() == SCH_SHEET_T ) - { - SCH_SHEET* sheet = static_cast( schItem ); + wxCHECK( symbol, /* void */ ); - wxCHECK( sheet, /* void */ ); + for( SCH_PIN* pin : symbol->GetPins() ) + pin->SetConnectivityDirty(); + } + else if( schItem->Type() == SCH_SHEET_T ) + { + SCH_SHEET* sheet = static_cast( schItem ); - for( SCH_SHEET_PIN* pin : sheet->GetPins() ) - pin->SetConnectivityDirty(); - } + wxCHECK( sheet, /* void */ ); - m_highlightedConnChanged = true; - dirtyConnectivity = true; + for( SCH_SHEET_PIN* pin : sheet->GetPins() ) + pin->SetConnectivityDirty(); + } - // Do a local clean up if there are any connectable objects in the commit. - if( connectivityCleanUp == NO_CLEANUP ) - connectivityCleanUp = LOCAL_CLEANUP; + m_highlightedConnChanged = true; + dirtyConnectivity = true; - // Do a full rebauild of the connectivity if there is a sheet in the commit. - if( schItem->Type() == SCH_SHEET_T ) - connectivityCleanUp = GLOBAL_CLEANUP; + // Do a local clean up if there are any connectable objects in the commit. + if( connectivityCleanUp == NO_CLEANUP ) + connectivityCleanUp = LOCAL_CLEANUP; + + // Do a full rebauild of the connectivity if there is a sheet in the commit. + if( schItem->Type() == SCH_SHEET_T ) + connectivityCleanUp = GLOBAL_CLEANUP; + } + else if( schItem->Type() == SCH_RULE_AREA_T ) + { + dirtyConnectivity = true; + } } }; diff --git a/eeschema/toolbars_sch_editor.cpp b/eeschema/toolbars_sch_editor.cpp index 4ee819e8d7..9220424bb6 100644 --- a/eeschema/toolbars_sch_editor.cpp +++ b/eeschema/toolbars_sch_editor.cpp @@ -175,6 +175,7 @@ void SCH_EDIT_FRAME::ReCreateVToolbar() m_drawToolBar->Add( EE_ACTIONS::placeJunction, ACTION_TOOLBAR::TOGGLE ); m_drawToolBar->Add( EE_ACTIONS::placeLabel, ACTION_TOOLBAR::TOGGLE ); m_drawToolBar->Add( EE_ACTIONS::placeClassLabel, ACTION_TOOLBAR::TOGGLE ); + m_drawToolBar->Add( EE_ACTIONS::drawRuleArea, ACTION_TOOLBAR::TOGGLE ); m_drawToolBar->Add( EE_ACTIONS::placeGlobalLabel, ACTION_TOOLBAR::TOGGLE ); m_drawToolBar->Add( EE_ACTIONS::placeHierLabel, ACTION_TOOLBAR::TOGGLE ); m_drawToolBar->Add( EE_ACTIONS::drawSheet, ACTION_TOOLBAR::TOGGLE ); diff --git a/eeschema/tools/ee_actions.cpp b/eeschema/tools/ee_actions.cpp index 8f486e7f25..c727c5eff7 100644 --- a/eeschema/tools/ee_actions.cpp +++ b/eeschema/tools/ee_actions.cpp @@ -607,6 +607,29 @@ TOOL_ACTION EE_ACTIONS::placeImage( TOOL_ACTION_ARGS() .Flags( AF_ACTIVATE ) .Parameter( nullptr ) ); +TOOL_ACTION EE_ACTIONS::drawRuleArea( TOOL_ACTION_ARGS() + .Name( "eeschema.InteractiveDrawing.drawRuleArea" ) + .Scope( AS_GLOBAL ) + .FriendlyName( _( "Add Rule Area" ) ) + .Tooltip( _( "Draw rule area" ) ) + .Icon( BITMAPS::add_keepout_area ) + .Flags( AF_ACTIVATE ) + .Parameter( SHAPE_T::RECTANGLE ) ); + +TOOL_ACTION EE_ACTIONS::deleteLastPoint( TOOL_ACTION_ARGS() + .Name( "eeschema.InteractiveDrawing.deleteLastPoint" ) + .Scope( AS_CONTEXT ) + .FriendlyName( _( "Delete Last Point" ) ) + .Tooltip( _( "Delete the last point added to the current item" ) ) + .Icon( BITMAPS::undo ) ); + +TOOL_ACTION EE_ACTIONS::closeOutline( TOOL_ACTION_ARGS() + .Name( "eeschema.InteractiveDrawing.closeOutline" ) + .Scope( AS_CONTEXT ) + .FriendlyName( _( "Close Outline" ) ) + .Tooltip( _( "Close the in progress outline" ) ) + .Icon( BITMAPS::checked_ok ) ); + // SCH_EDIT_TOOL // diff --git a/eeschema/tools/ee_actions.h b/eeschema/tools/ee_actions.h index f915cc3688..db4feb9032 100644 --- a/eeschema/tools/ee_actions.h +++ b/eeschema/tools/ee_actions.h @@ -104,6 +104,9 @@ public: static TOOL_ACTION placeImage; static TOOL_ACTION undoLastSegment; static TOOL_ACTION switchSegmentPosture; + static TOOL_ACTION drawRuleArea; + static TOOL_ACTION deleteLastPoint; + static TOOL_ACTION closeOutline; // Symbol Tools static TOOL_ACTION placeSymbolPin; diff --git a/eeschema/tools/ee_grid_helper.cpp b/eeschema/tools/ee_grid_helper.cpp index 6620d41896..16fea65888 100644 --- a/eeschema/tools/ee_grid_helper.cpp +++ b/eeschema/tools/ee_grid_helper.cpp @@ -373,6 +373,7 @@ GRID_HELPER_GRIDS EE_GRID_HELPER::GetItemGrid( const EDA_ITEM* aItem ) const case SCH_HIER_LABEL_T: case SCH_LABEL_T: case SCH_DIRECTIVE_LABEL_T: + case SCH_RULE_AREA_T: return GRID_CONNECTABLE; case SCH_FIELD_T: diff --git a/eeschema/tools/ee_point_editor.cpp b/eeschema/tools/ee_point_editor.cpp index 2e3854a2cb..2762bc354f 100644 --- a/eeschema/tools/ee_point_editor.cpp +++ b/eeschema/tools/ee_point_editor.cpp @@ -164,6 +164,16 @@ public: break; } + case SCH_RULE_AREA_T: + { + SCH_SHAPE* shape = static_cast( aItem ); + + for( const VECTOR2I& pt : shape->GetPolyShape().Outline( 0 ).CPoints() ) + points->AddPoint( pt ); + + break; + } + case SCH_TEXTBOX_T: { SCH_TEXTBOX* textbox = static_cast( aItem ); @@ -373,6 +383,7 @@ int EE_POINT_EDITOR::Main( const TOOL_EVENT& aEvent ) const EE_SELECTION& selection = m_selectionTool->GetSelection(); if( selection.Size() != 1 || !selection.Front()->IsType( { SCH_SHAPE_T, + SCH_RULE_AREA_T, SCH_TEXTBOX_T, SCH_TABLECELL_T, SCH_SHEET_T, @@ -632,6 +643,7 @@ void EE_POINT_EDITOR::updateParentItem( bool aSnapToGrid ) const switch( item->Type() ) { + case SCH_RULE_AREA_T: case SCH_SHAPE_T: { SCH_SHAPE* shape = static_cast( item ); @@ -1149,8 +1161,16 @@ void EE_POINT_EDITOR::setEditedPoint( EDIT_POINT* aPoint ) bool EE_POINT_EDITOR::removeCornerCondition( const SELECTION& ) { - if( !m_editPoints || !m_editedPoint || m_editPoints->GetParent()->Type() != SCH_SHAPE_T ) + bool isRuleArea = false; + + if( m_editPoints ) + isRuleArea = m_editPoints->GetParent()->Type() == SCH_RULE_AREA_T; + + if( !m_editPoints || !m_editedPoint + || !( m_editPoints->GetParent()->Type() == SCH_SHAPE_T || isRuleArea ) ) + { return false; + } SCH_SHAPE* shape = static_cast( m_editPoints->GetParent() ); bool invertY = shape->GetLayer() == LAYER_DEVICE; @@ -1165,7 +1185,10 @@ bool EE_POINT_EDITOR::removeCornerCondition( const SELECTION& ) for( const VECTOR2I& pt : poly.CPoints() ) { - if( pt == mapCoords( m_editedPoint->GetPosition(), invertY ) ) + VECTOR2I adjustedPos = isRuleArea ? m_editedPoint->GetPosition() + : mapCoords( m_editedPoint->GetPosition(), invertY ); + + if( pt == adjustedPos ) return true; } @@ -1175,8 +1198,12 @@ bool EE_POINT_EDITOR::removeCornerCondition( const SELECTION& ) bool EE_POINT_EDITOR::addCornerCondition( const SELECTION& ) { - if( !m_editPoints || m_editPoints->GetParent()->Type() != SCH_SHAPE_T ) + if( !m_editPoints + || !( m_editPoints->GetParent()->Type() == SCH_SHAPE_T + || m_editPoints->GetParent()->Type() == SCH_RULE_AREA_T ) ) + { return false; + } SCH_SHAPE* shape = static_cast( m_editPoints->GetParent() ); @@ -1192,7 +1219,9 @@ bool EE_POINT_EDITOR::addCornerCondition( const SELECTION& ) int EE_POINT_EDITOR::addCorner( const TOOL_EVENT& aEvent ) { - if( !m_editPoints || m_editPoints->GetParent()->Type() != SCH_SHAPE_T ) + if( !m_editPoints + || !( m_editPoints->GetParent()->Type() == SCH_SHAPE_T + || m_editPoints->GetParent()->Type() == SCH_RULE_AREA_T ) ) return 0; SCH_SHAPE* shape = static_cast( m_editPoints->GetParent() ); @@ -1206,8 +1235,12 @@ int EE_POINT_EDITOR::addCorner( const TOOL_EVENT& aEvent ) VECTOR2I pos = mapCoords( cursor, invertY ); int currentMinDistance = INT_MAX; int closestLineStart = 0; + unsigned numPoints = poly.GetPointCount(); + + if( !shape->IsClosed() ) + numPoints -= 1; - for( unsigned i = 0; i < poly.GetPointCount() - 1; ++i ) + for( unsigned i = 0; i < numPoints; ++i ) { int distance = (int) DistanceLinePoint( poly.CPoint( i ), poly.CPoint( i + 1 ), pos ); @@ -1231,8 +1264,12 @@ int EE_POINT_EDITOR::addCorner( const TOOL_EVENT& aEvent ) int EE_POINT_EDITOR::removeCorner( const TOOL_EVENT& aEvent ) { - if( !m_editPoints || !m_editedPoint || m_editPoints->GetParent()->Type() != SCH_SHAPE_T ) + if( !m_editPoints || !m_editedPoint + || !( m_editPoints->GetParent()->Type() == SCH_SHAPE_T + || m_editPoints->GetParent()->Type() == SCH_RULE_AREA_T ) ) + { return 0; + } SCH_SHAPE* shape = static_cast( m_editPoints->GetParent() ); SHAPE_LINE_CHAIN& poly = shape->GetPolyShape().Outline( 0 ); diff --git a/eeschema/tools/rule_area_create_helper.cpp b/eeschema/tools/rule_area_create_helper.cpp new file mode 100644 index 0000000000..01b1814361 --- /dev/null +++ b/eeschema/tools/rule_area_create_helper.cpp @@ -0,0 +1,175 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2017-2023 KiCad Developers, see AUTHORS.txt for contributors. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +RULE_AREA_CREATE_HELPER::RULE_AREA_CREATE_HELPER( KIGFX::VIEW& aView, SCH_EDIT_FRAME* aFrame, + TOOL_MANAGER* aMgr ) : + m_parentView( aView ), + m_frame( aFrame ), m_toolManager( aMgr ) +{ + m_parentView.Add( &m_previewItem ); +} + + +RULE_AREA_CREATE_HELPER::~RULE_AREA_CREATE_HELPER() +{ + // remove the preview from the view + m_parentView.SetVisible( &m_previewItem, false ); + m_parentView.Remove( &m_previewItem ); +} + + +std::unique_ptr RULE_AREA_CREATE_HELPER::createNewRuleArea() +{ + std::unique_ptr ruleArea = std::make_unique(); + ruleArea->SetLineStyle( LINE_STYLE::DASH ); + ruleArea->SetLineColor( COLOR4D::UNSPECIFIED ); + + return ruleArea; +} + + +void RULE_AREA_CREATE_HELPER::commitRuleArea( std::unique_ptr aRuleArea ) +{ + SCH_COMMIT commit( m_toolManager ); + + SCH_RULE_AREA* ruleArea = aRuleArea.release(); + + commit.Add( ruleArea, m_frame->GetScreen() ); + commit.Push( _( "Draw Rule Area" ) ); + + m_toolManager->RunAction( EE_ACTIONS::addItemToSel, ruleArea ); + + m_parentView.ClearPreview(); +} + + +bool RULE_AREA_CREATE_HELPER::OnFirstPoint( POLYGON_GEOM_MANAGER& aMgr ) +{ + m_rule_area = createNewRuleArea(); + + if( m_rule_area ) + { + m_toolManager->RunAction( EE_ACTIONS::clearSelection ); + + SCH_RENDER_SETTINGS renderSettings; + COLOR_SETTINGS* colorSettings = m_frame->GetColorSettings(); + renderSettings.LoadColors( colorSettings ); + + COLOR4D color = renderSettings.GetLayerColor( LAYER_RULE_AREAS ); + m_previewItem.SetLineColor( color ); + m_previewItem.SetLeaderColor( color ); + m_previewItem.SetFillColor( color.WithAlpha( 0.2 ) ); + + m_parentView.SetVisible( &m_previewItem, true ); + + aMgr.SetLeaderMode( POLYGON_GEOM_MANAGER::LEADER_MODE::DEG45 ); + } + + return m_rule_area != nullptr; +} + + +void RULE_AREA_CREATE_HELPER::OnGeometryChange( const POLYGON_GEOM_MANAGER& aMgr ) +{ + // Handle a cancel-interactive + if( m_rule_area && !aMgr.IsPolygonInProgress() ) + { + m_rule_area = nullptr; + m_parentView.SetVisible( &m_previewItem, false ); + return; + } + + // send the points to the preview item + m_previewItem.SetPoints( aMgr.GetLockedInPoints(), aMgr.GetLeaderLinePoints(), + aMgr.GetLoopLinePoints() ); + m_parentView.Update( &m_previewItem, KIGFX::GEOMETRY ); +} + + +void RULE_AREA_CREATE_HELPER::OnComplete( const POLYGON_GEOM_MANAGER& aMgr ) +{ + auto& finalPoints = aMgr.GetLockedInPoints(); + + if( finalPoints.PointCount() < 3 ) + { + // Just scrap the rule area in progress + m_rule_area = nullptr; + } + else + { + SHAPE_POLY_SET ruleShape; + + ruleShape.NewOutline(); + auto& outline = ruleShape.Outline( 0 ); + + for( int i = 0; i < finalPoints.PointCount(); ++i ) + outline.Append( finalPoints.CPoint( i ) ); + + // In DEG45 mode, we may have intermediate points in the leader that should be included + // as they are shown in the preview. These typically maintain the 45 constraint + if( aMgr.GetLeaderMode() == POLYGON_GEOM_MANAGER::LEADER_MODE::DEG45 ) + { + const SHAPE_LINE_CHAIN leaderPts = aMgr.GetLeaderLinePoints(); + for( int i = 1; i < leaderPts.PointCount(); i++ ) + outline.Append( leaderPts.CPoint( i ) ); + + const SHAPE_LINE_CHAIN loopPts = aMgr.GetLoopLinePoints(); + for( int i = 1; i < loopPts.PointCount() - 1; i++ ) + outline.Append( loopPts.CPoint( i ) ); + } + + outline.SetClosed( true ); + outline.Simplify( true ); + + // Remove the start point if it lies on the line between neighbouring points. + // Simplify doesn't handle that currently. + if( outline.PointCount() >= 3 ) + { + SEG seg( outline.CPoint( -1 ), outline.CPoint( 1 ) ); + + if( seg.LineDistance( outline.CPoint( 0 ) ) <= 1 ) + outline.Remove( 0 ); + } + + m_rule_area->SetPolyShape( ruleShape ); + + // hand the rule area over to the committer + commitRuleArea( std::move( m_rule_area ) ); + m_rule_area = nullptr; + } + + m_parentView.SetVisible( &m_previewItem, false ); +} + + diff --git a/eeschema/tools/rule_area_create_helper.h b/eeschema/tools/rule_area_create_helper.h new file mode 100644 index 0000000000..055fd05de0 --- /dev/null +++ b/eeschema/tools/rule_area_create_helper.h @@ -0,0 +1,93 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2024 Kicad Developers, see AUTHORS.txt for contributors. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#ifndef EESCHEMA_TOOLS_RULE_CREATE_HELPER__H_ +#define EESCHEMA_TOOLS_RULE_CREATE_HELPER__H_ + +#include +#include +#include "sch_rule_area.h" +#include + +namespace KIGFX +{ +class VIEW; +} + +class TOOL_MANAGER; + +/** + * An adjunct helper to the DRAWING_TOOL interactive tool, which handles incoming geometry + * changes from a #POLYGON_GEOM_MANAGER and translates that into a SCH_RULE_AREA based on given + * parameters. + */ +class RULE_AREA_CREATE_HELPER : public POLYGON_GEOM_MANAGER::CLIENT +{ +public: + RULE_AREA_CREATE_HELPER( KIGFX::VIEW& aView, SCH_EDIT_FRAME* aFrame, TOOL_MANAGER* aMgr ); + + virtual ~RULE_AREA_CREATE_HELPER(); + + SCH_RULE_AREA* GetRuleArea() const { return m_rule_area.get(); } + + /* + * Interface for receiving #POLYGON_GEOM_MANAGER update + */ + void OnGeometryChange( const POLYGON_GEOM_MANAGER& aMgr ) override; + + bool OnFirstPoint( POLYGON_GEOM_MANAGER& aMgr ) override; + + void OnComplete( const POLYGON_GEOM_MANAGER& aMgr ) override; + + /** + * Create a new SCH_RULE_AREA + * + * @return the new rule area, can be null if the user aborted + */ + std::unique_ptr createNewRuleArea(); + + /** + * Commit the current rule area in progress to the schematic. + * + * @param aRuleArea is the drawn rule area outline to commit. + */ + void commitRuleArea( std::unique_ptr aRuleArea ); + +private: + ///< The preview item to display + KIGFX::PREVIEW::POLYGON_ITEM m_previewItem; + + ///< view that show the preview item + KIGFX::VIEW& m_parentView; + + ///< The active schematic edit frame + SCH_EDIT_FRAME* m_frame; + + ///< The rule area in progress + std::unique_ptr m_rule_area; + + ///< The TOOL_MANAGER running the tool + TOOL_MANAGER* m_toolManager; +}; + +#endif // EESCHEMA_TOOLS_RULE_CREATE_HELPER__H_ diff --git a/eeschema/tools/sch_drawing_tools.cpp b/eeschema/tools/sch_drawing_tools.cpp index 9f4606e245..a92a2641ae 100644 --- a/eeschema/tools/sch_drawing_tools.cpp +++ b/eeschema/tools/sch_drawing_tools.cpp @@ -32,6 +32,7 @@ #include #include #include +#include #include #include #include @@ -45,6 +46,7 @@ #include #include #include +#include #include #include #include @@ -55,6 +57,7 @@ #include #include #include +#include #include #include #include @@ -90,6 +93,7 @@ SCH_DRAWING_TOOLS::SCH_DRAWING_TOOLS() : m_lastTextboxStroke( 0, LINE_STYLE::DEFAULT, COLOR4D::UNSPECIFIED ), m_mruPath( wxEmptyString ), m_lastAutoLabelRotateOnPlacement( false ), + m_drawingRuleArea( false ), m_inDrawingTool( false ) { } @@ -105,8 +109,16 @@ bool SCH_DRAWING_TOOLS::Init() return m_frame->GetCurrentSheet().Last() != &m_frame->Schematic().Root(); }; + auto inDrawingRuleArea = + [&]( const SELECTION& aSel ) + { + return m_drawingRuleArea; + }; + CONDITIONAL_MENU& ctxMenu = m_menu.GetMenu(); ctxMenu.AddItem( EE_ACTIONS::leaveSheet, belowRootSheetCondition, 150 ); + ctxMenu.AddItem( EE_ACTIONS::closeOutline, inDrawingRuleArea, 200 ); + ctxMenu.AddItem( EE_ACTIONS::deleteLastPoint, inDrawingRuleArea, 200 ); return true; } @@ -2033,6 +2045,178 @@ int SCH_DRAWING_TOOLS::DrawShape( const TOOL_EVENT& aEvent ) } +int SCH_DRAWING_TOOLS::DrawRuleArea( const TOOL_EVENT& aEvent ) +{ + if( m_inDrawingTool ) + return 0; + + REENTRANCY_GUARD guard( &m_inDrawingTool ); + SCOPED_SET_RESET scopedDrawMode( m_drawingRuleArea, true ); + + KIGFX::VIEW_CONTROLS* controls = getViewControls(); + EE_GRID_HELPER grid( m_toolMgr ); + VECTOR2I cursorPos; + + RULE_AREA_CREATE_HELPER ruleAreaTool( *getView(), m_frame, m_toolMgr ); + POLYGON_GEOM_MANAGER polyGeomMgr( ruleAreaTool ); + bool started = false; + + // We might be running as the same shape in another co-routine. Make sure that one + // gets whacked. + m_toolMgr->DeactivateTool(); + + m_toolMgr->RunAction( EE_ACTIONS::clearSelection ); + + m_frame->PushTool( aEvent ); + + auto setCursor = + [&]() + { + m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::PENCIL ); + }; + + auto cleanup = [&]() + { + polyGeomMgr.Reset(); + started = false; + getViewControls()->SetAutoPan( false ); + getViewControls()->CaptureCursor( false ); + m_toolMgr->RunAction( EE_ACTIONS::clearSelection ); + }; + + Activate(); + + // Must be done after Activate() so that it gets set into the correct context + getViewControls()->ShowCursor( true ); + //m_controls->ForceCursorPosition( false ); + + // Set initial cursor + setCursor(); + + if( aEvent.HasPosition() ) + m_toolMgr->PrimeTool( aEvent.Position() ); + + // Main loop: keep receiving events + while( TOOL_EVENT* evt = Wait() ) + { + setCursor(); + + grid.SetSnap( !evt->Modifier( MD_SHIFT ) ); + grid.SetUseGrid( getView()->GetGAL()->GetGridSnapping() && !evt->DisableGridSnapping() ); + + cursorPos = grid.Align( controls->GetMousePosition(), GRID_HELPER_GRIDS::GRID_CONNECTABLE ); + controls->ForceCursorPosition( true, cursorPos ); + + polyGeomMgr.SetLeaderMode( m_frame->eeconfig()->m_Drawing.line_mode == LINE_MODE_FREE + ? POLYGON_GEOM_MANAGER::LEADER_MODE::DIRECT + : POLYGON_GEOM_MANAGER::LEADER_MODE::DEG45 ); + + if( evt->IsCancelInteractive() ) + { + if( started ) + { + cleanup(); + } + else + { + m_frame->PopTool( aEvent ); + + // We've handled the cancel event. Don't cancel other tools + evt->SetPassEvent( false ); + break; + } + } + else if( evt->IsActivate() ) + { + if( started ) + cleanup(); + + if( evt->IsPointEditor() ) + { + // don't exit (the point editor runs in the background) + } + else if( evt->IsMoveTool() ) + { + // leave ourselves on the stack so we come back after the move + break; + } + else + { + m_frame->PopTool( aEvent ); + break; + } + } + else if( evt->IsClick( BUT_RIGHT ) ) + { + if( !started ) + m_toolMgr->VetoContextMenuMouseWarp(); + + m_menu.ShowContextMenu( m_selectionTool->GetSelection() ); + } + // events that lock in nodes + else if( evt->IsClick( BUT_LEFT ) || evt->IsDblClick( BUT_LEFT ) + || evt->IsAction( &EE_ACTIONS::closeOutline ) ) + { + // Check if it is double click / closing line (so we have to finish the zone) + const bool endPolygon = evt->IsDblClick( BUT_LEFT ) + || evt->IsAction( &EE_ACTIONS::closeOutline ) + || polyGeomMgr.NewPointClosesOutline( cursorPos ); + + if( endPolygon ) + { + polyGeomMgr.SetFinished(); + polyGeomMgr.Reset(); + + started = false; + getViewControls()->SetAutoPan( false ); + getViewControls()->CaptureCursor( false ); + } + // adding a corner + else if( polyGeomMgr.AddPoint( cursorPos ) ) + { + if( !started ) + { + started = true; + + getViewControls()->SetAutoPan( true ); + getViewControls()->CaptureCursor( true ); + } + } + } + else if( started + && ( evt->IsAction( &EE_ACTIONS::deleteLastPoint ) + || evt->IsAction( &ACTIONS::doDelete ) || evt->IsAction( &ACTIONS::undo ) ) ) + { + if( std::optional last = polyGeomMgr.DeleteLastCorner() ) + { + cursorPos = last.value(); + getViewControls()->WarpMouseCursor( cursorPos, true ); + getViewControls()->ForceCursorPosition( true, cursorPos ); + polyGeomMgr.SetCursorPosition( cursorPos ); + } + else + { + cleanup(); + } + } + else if( started && ( evt->IsMotion() || evt->IsDrag( BUT_LEFT ) ) ) + { + polyGeomMgr.SetCursorPosition( cursorPos ); + } + else + { + evt->SetPassEvent(); + } + + } // end while + + getViewControls()->SetAutoPan( false ); + getViewControls()->CaptureCursor( false ); + m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::ARROW ); + return 0; +} + + int SCH_DRAWING_TOOLS::DrawTable( const TOOL_EVENT& aEvent ) { SCHEMATIC* schematic = getModel(); @@ -2640,6 +2824,7 @@ void SCH_DRAWING_TOOLS::setTransitions() Go( &SCH_DRAWING_TOOLS::DrawShape, EE_ACTIONS::drawCircle.MakeEvent() ); Go( &SCH_DRAWING_TOOLS::DrawShape, EE_ACTIONS::drawArc.MakeEvent() ); Go( &SCH_DRAWING_TOOLS::DrawShape, EE_ACTIONS::drawTextBox.MakeEvent() ); + Go( &SCH_DRAWING_TOOLS::DrawRuleArea, EE_ACTIONS::drawRuleArea.MakeEvent() ); Go( &SCH_DRAWING_TOOLS::DrawTable, EE_ACTIONS::drawTable.MakeEvent() ); Go( &SCH_DRAWING_TOOLS::PlaceImage, EE_ACTIONS::placeImage.MakeEvent() ); Go( &SCH_DRAWING_TOOLS::ImportGraphics, EE_ACTIONS::importGraphics.MakeEvent() ); diff --git a/eeschema/tools/sch_drawing_tools.h b/eeschema/tools/sch_drawing_tools.h index 11279f45fc..3b516361d4 100644 --- a/eeschema/tools/sch_drawing_tools.h +++ b/eeschema/tools/sch_drawing_tools.h @@ -56,6 +56,7 @@ public: int SingleClickPlace( const TOOL_EVENT& aEvent ); int TwoClickPlace( const TOOL_EVENT& aEvent ); int DrawShape( const TOOL_EVENT& aEvent ); + int DrawRuleArea( const TOOL_EVENT& aEvent ); int DrawTable( const TOOL_EVENT& aEvent ); int DrawSheet( const TOOL_EVENT& aEvent ); int PlaceImage( const TOOL_EVENT& aEvent ); @@ -103,6 +104,7 @@ private: STROKE_PARAMS m_lastTextboxStroke; wxString m_mruPath; bool m_lastAutoLabelRotateOnPlacement; + bool m_drawingRuleArea; bool m_inDrawingTool; // Re-entrancy guard std::unique_ptr m_statusPopup; diff --git a/eeschema/tools/sch_edit_tool.cpp b/eeschema/tools/sch_edit_tool.cpp index 50dfe6dd0c..75657df0a8 100644 --- a/eeschema/tools/sch_edit_tool.cpp +++ b/eeschema/tools/sch_edit_tool.cpp @@ -41,6 +41,7 @@ #include #include #include +#include #include #include #include @@ -314,6 +315,7 @@ bool SCH_EDIT_TOOL::Init() case SCH_GLOBAL_LABEL_T: case SCH_HIER_LABEL_T: case SCH_DIRECTIVE_LABEL_T: + case SCH_RULE_AREA_T: case SCH_FIELD_T: case SCH_SHAPE_T: case SCH_BITMAP_T: @@ -619,6 +621,7 @@ bool SCH_EDIT_TOOL::Init() const std::vector SCH_EDIT_TOOL::RotatableItems = { SCH_SHAPE_T, + SCH_RULE_AREA_T, SCH_TEXT_T, SCH_TEXTBOX_T, SCH_TABLE_T, @@ -769,6 +772,7 @@ int SCH_EDIT_TOOL::Rotate( const TOOL_EVENT& aEvent ) break; } + case SCH_RULE_AREA_T: case SCH_SHAPE_T: case SCH_TEXTBOX_T: head->Rotate( rotPoint, !clockwise ); @@ -1116,6 +1120,7 @@ int SCH_EDIT_TOOL::Mirror( const TOOL_EVENT& aEvent ) const std::vector swappableItems = { SCH_SHAPE_T, + SCH_RULE_AREA_T, SCH_TEXT_T, SCH_TEXTBOX_T, SCH_LABEL_T, @@ -1359,6 +1364,7 @@ static std::vector deletableItems = SCH_BUS_BUS_ENTRY_T, SCH_BUS_WIRE_ENTRY_T, SCH_SHAPE_T, + SCH_RULE_AREA_T, SCH_TEXT_T, SCH_TEXTBOX_T, SCH_TABLECELL_T, // Clear contents @@ -1434,6 +1440,11 @@ int SCH_EDIT_TOOL::DoDelete( const TOOL_EVENT& aEvent ) commit.Modify( item, m_frame->GetScreen() ); static_cast( sch_item )->SetText( wxEmptyString ); } + else if( sch_item->Type() == SCH_RULE_AREA_T ) + { + sch_item->SetFlags( STRUCT_DELETED ); + commit.Remove( item, m_frame->GetScreen() ); + } else { sch_item->SetFlags( STRUCT_DELETED ); @@ -1997,6 +2008,15 @@ int SCH_EDIT_TOOL::Properties( const TOOL_EVENT& aEvent ) break; } + case SCH_RULE_AREA_T: + { + DIALOG_SHAPE_PROPERTIES dlg( m_frame, static_cast( curr_item ) ); + dlg.SetTitle( _( "Rule Area Properties" ) ); + + dlg.ShowModal(); + break; + } + case SCH_LINE_T: case SCH_BUS_WIRE_ENTRY_T: case SCH_JUNCTION_T: diff --git a/include/core/typeinfo.h b/include/core/typeinfo.h index 103b9cc440..20d663cb75 100644 --- a/include/core/typeinfo.h +++ b/include/core/typeinfo.h @@ -167,6 +167,7 @@ enum KICAD_T SCH_LABEL_T, SCH_GLOBAL_LABEL_T, SCH_HIER_LABEL_T, + SCH_RULE_AREA_T, SCH_DIRECTIVE_LABEL_T, SCH_SYMBOL_T, SCH_SHEET_PIN_T, @@ -373,6 +374,7 @@ constexpr bool IsEeschemaType( const KICAD_T aType ) case SCH_BUS_BUS_ENTRY_T: case SCH_LINE_T: case SCH_SHAPE_T: + case SCH_RULE_AREA_T: case SCH_BITMAP_T: case SCH_TEXT_T: case SCH_TEXTBOX_T: diff --git a/include/layer_ids.h b/include/layer_ids.h index 671e962e48..9f80f2e19a 100644 --- a/include/layer_ids.h +++ b/include/layer_ids.h @@ -366,6 +366,7 @@ enum SCH_LAYER_ID: int LAYER_FIELDS, LAYER_INTERSHEET_REFS, LAYER_NETCLASS_REFS, + LAYER_RULE_AREAS, LAYER_DEVICE, LAYER_NOTES, LAYER_PRIVATE_NOTES, diff --git a/include/preview_items/polygon_item.h b/include/preview_items/polygon_item.h index b5e7376871..f123d35481 100644 --- a/include/preview_items/polygon_item.h +++ b/include/preview_items/polygon_item.h @@ -26,6 +26,7 @@ #include +#include #include #include @@ -47,6 +48,12 @@ class POLYGON_ITEM : public SIMPLE_OVERLAY_ITEM public: POLYGON_ITEM(); + ///< Sets the color of the preview outline + void SetLineColor( KIGFX::COLOR4D lineColor ); + + ///< Sets the color of the outline leader line + void SetLeaderColor( KIGFX::COLOR4D leaderColor ); + ///< Gets the bounding box of the polygon virtual const BOX2I ViewBBox() const override; @@ -71,6 +78,12 @@ private: ///< polygon fill SHAPE_POLY_SET m_polyfill; + ///< the preview outline color + KIGFX::COLOR4D m_lineColor; + + ///< the preview leader line color + KIGFX::COLOR4D m_leaderColor; + static const double POLY_LINE_WIDTH; }; diff --git a/qa/data/eeschema/RuleAreaNetclassConflictOnWire_1.kicad_sch b/qa/data/eeschema/RuleAreaNetclassConflictOnWire_1.kicad_sch new file mode 100644 index 0000000000..a0f2740c66 --- /dev/null +++ b/qa/data/eeschema/RuleAreaNetclassConflictOnWire_1.kicad_sch @@ -0,0 +1,376 @@ +(kicad_sch + (version 20240417) + (generator "eeschema") + (generator_version "8.99") + (uuid "0726e9d1-eea3-424d-8780-ca27e04db582") + (paper "A4") + (lib_symbols + (symbol "Device:R" + (pin_numbers hide) + (pin_names + (offset 0) + ) + (exclude_from_sim no) + (in_bom yes) + (on_board yes) + (property "Reference" "R" + (at 2.032 0 90) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Value" "R" + (at 0 0 90) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Footprint" "" + (at -1.778 0 90) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Datasheet" "~" + (at 0 0 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Description" "Resistor" + (at 0 0 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "ki_keywords" "R res resistor" + (at 0 0 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "ki_fp_filters" "R_*" + (at 0 0 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (symbol "R_0_1" + (rectangle + (start -1.016 -2.54) + (end 1.016 2.54) + (stroke + (width 0.254) + (type default) + ) + (fill + (type none) + ) + ) + ) + (symbol "R_1_1" + (pin passive line + (at 0 3.81 270) + (length 1.27) + (name "~" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "1" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin passive line + (at 0 -3.81 90) + (length 1.27) + (name "~" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "2" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + ) + ) + ) + (no_connect + (at 121.92 97.79) + (uuid "4236af9f-a6bf-4cf1-9924-15f3612b7e4e") + ) + (no_connect + (at 83.82 73.66) + (uuid "5e7b627b-1c2e-465a-84ed-c5ec27af46ab") + ) + (wire + (pts + (xy 91.44 73.66) (xy 121.92 73.66) + ) + (stroke + (width 0) + (type default) + ) + (uuid "73c537ad-8abb-4a42-9ec1-e67712c81b34") + ) + (wire + (pts + (xy 121.92 90.17) (xy 121.92 73.66) + ) + (stroke + (width 0) + (type default) + ) + (uuid "f443e7c4-f627-4cda-b5a1-cbc238e17fb4") + ) + (rule_area + (polyline + (pts + (xy 93.98 67.31) (xy 99.06 67.31) (xy 99.06 80.01) (xy 96.52 82.55) (xy 95.25 81.28) (xy 95.25 77.47) + (xy 93.98 76.2) + ) + (stroke + (width 0) + (type dash) + ) + (fill + (type none) + ) + (uuid 40e86e61-b17e-4a50-a591-d1015a45e6e4) + ) + ) + (rule_area + (polyline + (pts + (xy 133.35 77.47) (xy 133.35 83.82) (xy 114.3 83.82) (xy 111.76 81.28) (xy 111.76 77.47) + ) + (stroke + (width 0) + (type dash) + ) + (fill + (type none) + ) + (uuid ea7226cb-9108-48d9-9ca3-95d32a5d4077) + ) + ) + (netclass_flag "" + (length 2.54) + (shape round) + (at 95.25 67.31 0) + (fields_autoplaced yes) + (effects + (font + (size 1.27 1.27) + ) + (justify left bottom) + ) + (uuid "71111a86-8188-4247-ad10-36f2f6d838f8") + (property "Netclass" "CLASS1" + (at 95.9485 64.77 0) + (effects + (font + (size 1.27 1.27) + (italic yes) + ) + (justify left) + ) + ) + ) + (netclass_flag "" + (length 2.54) + (shape round) + (at 133.35 80.01 270) + (fields_autoplaced yes) + (effects + (font + (size 1.27 1.27) + ) + (justify right bottom) + ) + (uuid "e8b709aa-42c8-45a8-8cd0-dd3110e4048f") + (property "Netclass" "CLASS2" + (at 135.89 79.3115 90) + (effects + (font + (size 1.27 1.27) + (italic yes) + ) + (justify left) + ) + ) + ) + (symbol + (lib_id "Device:R") + (at 121.92 93.98 180) + (unit 1) + (exclude_from_sim no) + (in_bom yes) + (on_board yes) + (dnp no) + (fields_autoplaced yes) + (uuid "e294c19e-e287-4924-9e89-aea9a1a5c8e1") + (property "Reference" "R2" + (at 128.27 93.98 90) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Value" "R" + (at 125.73 93.98 90) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Footprint" "" + (at 123.698 93.98 90) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Datasheet" "~" + (at 121.92 93.98 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Description" "Resistor" + (at 121.92 93.98 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (pin "1" + (uuid "6e8901c6-d0c5-455a-a42b-f31a4c09e3db") + ) + (pin "2" + (uuid "fcf2f54f-8f15-40c7-882e-02cb8d2d987f") + ) + (instances + (project "RuleAreaNetclassConflictOnWire" + (path "/0726e9d1-eea3-424d-8780-ca27e04db582" + (reference "R2") + (unit 1) + ) + ) + ) + ) + (symbol + (lib_id "Device:R") + (at 87.63 73.66 270) + (unit 1) + (exclude_from_sim no) + (in_bom yes) + (on_board yes) + (dnp no) + (fields_autoplaced yes) + (uuid "e33b52ff-7e35-442f-8c36-1e6677209b8c") + (property "Reference" "R1" + (at 87.63 67.31 90) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Value" "R" + (at 87.63 69.85 90) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Footprint" "" + (at 87.63 71.882 90) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Datasheet" "~" + (at 87.63 73.66 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Description" "Resistor" + (at 87.63 73.66 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (pin "1" + (uuid "c87ab3da-a973-4c8f-8e38-3156b231d8af") + ) + (pin "2" + (uuid "5d2f6407-4aee-4cc1-850a-883ae2f37307") + ) + (instances + (project "" + (path "/0726e9d1-eea3-424d-8780-ca27e04db582" + (reference "R1") + (unit 1) + ) + ) + ) + ) + (sheet_instances + (path "/" + (page "1") + ) + ) +) diff --git a/qa/data/eeschema/RuleAreaNetclassConflictOnWire_2.kicad_sch b/qa/data/eeschema/RuleAreaNetclassConflictOnWire_2.kicad_sch new file mode 100644 index 0000000000..1aa17a9159 --- /dev/null +++ b/qa/data/eeschema/RuleAreaNetclassConflictOnWire_2.kicad_sch @@ -0,0 +1,365 @@ +(kicad_sch + (version 20240417) + (generator "eeschema") + (generator_version "8.99") + (uuid "0726e9d1-eea3-424d-8780-ca27e04db582") + (paper "A4") + (lib_symbols + (symbol "Device:R" + (pin_numbers hide) + (pin_names + (offset 0) + ) + (exclude_from_sim no) + (in_bom yes) + (on_board yes) + (property "Reference" "R" + (at 2.032 0 90) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Value" "R" + (at 0 0 90) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Footprint" "" + (at -1.778 0 90) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Datasheet" "~" + (at 0 0 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Description" "Resistor" + (at 0 0 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "ki_keywords" "R res resistor" + (at 0 0 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "ki_fp_filters" "R_*" + (at 0 0 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (symbol "R_0_1" + (rectangle + (start -1.016 -2.54) + (end 1.016 2.54) + (stroke + (width 0.254) + (type default) + ) + (fill + (type none) + ) + ) + ) + (symbol "R_1_1" + (pin passive line + (at 0 3.81 270) + (length 1.27) + (name "~" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "1" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin passive line + (at 0 -3.81 90) + (length 1.27) + (name "~" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "2" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + ) + ) + ) + (no_connect + (at 127 73.66) + (uuid "4236af9f-a6bf-4cf1-9924-15f3612b7e4e") + ) + (no_connect + (at 83.82 73.66) + (uuid "5e7b627b-1c2e-465a-84ed-c5ec27af46ab") + ) + (wire + (pts + (xy 91.44 73.66) (xy 119.38 73.66) + ) + (stroke + (width 0) + (type default) + ) + (uuid "73c537ad-8abb-4a42-9ec1-e67712c81b34") + ) + (rule_area + (polyline + (pts + (xy 106.68 67.31) (xy 110.49 67.31) (xy 110.49 77.47) (xy 106.68 77.47) + ) + (stroke + (width 0) + (type dash) + ) + (fill + (type none) + ) + (uuid 03801fed-03b2-45ff-96c8-aae8ef0df963) + ) + ) + (rule_area + (polyline + (pts + (xy 93.98 67.31) (xy 97.79 67.31) (xy 97.79 80.01) (xy 93.98 80.01) + ) + (stroke + (width 0) + (type dash) + ) + (fill + (type none) + ) + (uuid 6fd89307-6035-4c4e-8858-d37921436305) + ) + ) + (netclass_flag "" + (length 2.54) + (shape round) + (at 95.25 67.31 0) + (fields_autoplaced yes) + (effects + (font + (size 1.27 1.27) + ) + (justify left bottom) + ) + (uuid "71111a86-8188-4247-ad10-36f2f6d838f8") + (property "Netclass" "CLASS1" + (at 95.9485 64.77 0) + (effects + (font + (size 1.27 1.27) + (italic yes) + ) + (justify left) + ) + ) + ) + (netclass_flag "" + (length 2.54) + (shape round) + (at 107.95 67.31 0) + (fields_autoplaced yes) + (effects + (font + (size 1.27 1.27) + ) + (justify left bottom) + ) + (uuid "e8b709aa-42c8-45a8-8cd0-dd3110e4048f") + (property "Netclass" "CLASS2" + (at 108.6485 64.77 0) + (effects + (font + (size 1.27 1.27) + (italic yes) + ) + (justify left) + ) + ) + ) + (symbol + (lib_id "Device:R") + (at 123.19 73.66 270) + (unit 1) + (exclude_from_sim no) + (in_bom yes) + (on_board yes) + (dnp no) + (fields_autoplaced yes) + (uuid "e294c19e-e287-4924-9e89-aea9a1a5c8e1") + (property "Reference" "R2" + (at 123.19 67.31 90) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Value" "R" + (at 123.19 69.85 90) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Footprint" "" + (at 123.19 71.882 90) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Datasheet" "~" + (at 123.19 73.66 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Description" "Resistor" + (at 123.19 73.66 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (pin "1" + (uuid "6e8901c6-d0c5-455a-a42b-f31a4c09e3db") + ) + (pin "2" + (uuid "fcf2f54f-8f15-40c7-882e-02cb8d2d987f") + ) + (instances + (project "RuleAreaNetclassConflictOnWire" + (path "/0726e9d1-eea3-424d-8780-ca27e04db582" + (reference "R2") + (unit 1) + ) + ) + ) + ) + (symbol + (lib_id "Device:R") + (at 87.63 73.66 270) + (unit 1) + (exclude_from_sim no) + (in_bom yes) + (on_board yes) + (dnp no) + (fields_autoplaced yes) + (uuid "e33b52ff-7e35-442f-8c36-1e6677209b8c") + (property "Reference" "R1" + (at 87.63 67.31 90) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Value" "R" + (at 87.63 69.85 90) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Footprint" "" + (at 87.63 71.882 90) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Datasheet" "~" + (at 87.63 73.66 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Description" "Resistor" + (at 87.63 73.66 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (pin "1" + (uuid "c87ab3da-a973-4c8f-8e38-3156b231d8af") + ) + (pin "2" + (uuid "5d2f6407-4aee-4cc1-850a-883ae2f37307") + ) + (instances + (project "" + (path "/0726e9d1-eea3-424d-8780-ca27e04db582" + (reference "R1") + (unit 1) + ) + ) + ) + ) + (sheet_instances + (path "/" + (page "1") + ) + ) +) diff --git a/qa/data/eeschema/RuleAreaNoOverlap.kicad_sch b/qa/data/eeschema/RuleAreaNoOverlap.kicad_sch new file mode 100644 index 0000000000..e5625a1336 --- /dev/null +++ b/qa/data/eeschema/RuleAreaNoOverlap.kicad_sch @@ -0,0 +1,45 @@ +(kicad_sch + (version 20240417) + (generator "eeschema") + (generator_version "8.99") + (uuid "e77939c4-758d-4d0c-a6e5-dc441c8c54c9") + (paper "A4") + (lib_symbols) + (rule_area + (polyline + (pts + (xy 144.78 63.5) (xy 177.8 63.5) (xy 180.34 66.04) (xy 180.34 110.49) (xy 171.45 110.49) (xy 157.48 96.52) + (xy 148.59 105.41) (xy 148.59 107.95) (xy 133.35 92.71) (xy 133.35 85.09) (xy 139.7 78.74) (xy 139.7 68.58) + ) + (stroke + (width 0) + (type dash) + ) + (fill + (type none) + ) + (uuid 4f0aecc0-f3b3-4645-a47e-6a29fb5ff671) + ) + ) + (rule_area + (polyline + (pts + (xy 69.85 49.53) (xy 101.6 49.53) (xy 101.6 73.66) (xy 95.25 80.01) (xy 86.36 80.01) (xy 76.2 69.85) + (xy 76.2 66.04) (xy 78.74 63.5) (xy 69.85 54.61) + ) + (stroke + (width 0) + (type dash) + ) + (fill + (type none) + ) + (uuid 839de646-0221-4c81-b248-242a273d8ed3) + ) + ) + (sheet_instances + (path "/" + (page "1") + ) + ) +) diff --git a/qa/data/eeschema/RuleAreaOneNetclassDirective.kicad_sch b/qa/data/eeschema/RuleAreaOneNetclassDirective.kicad_sch new file mode 100644 index 0000000000..74fb302963 --- /dev/null +++ b/qa/data/eeschema/RuleAreaOneNetclassDirective.kicad_sch @@ -0,0 +1,327 @@ +(kicad_sch + (version 20240417) + (generator "eeschema") + (generator_version "8.99") + (uuid "8a90b8e2-28c2-4028-bcb6-cef3be983f1e") + (paper "A4") + (lib_symbols + (symbol "Device:R" + (pin_numbers hide) + (pin_names + (offset 0) + ) + (exclude_from_sim no) + (in_bom yes) + (on_board yes) + (property "Reference" "R" + (at 2.032 0 90) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Value" "R" + (at 0 0 90) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Footprint" "" + (at -1.778 0 90) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Datasheet" "~" + (at 0 0 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Description" "Resistor" + (at 0 0 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "ki_keywords" "R res resistor" + (at 0 0 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "ki_fp_filters" "R_*" + (at 0 0 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (symbol "R_0_1" + (rectangle + (start -1.016 -2.54) + (end 1.016 2.54) + (stroke + (width 0.254) + (type default) + ) + (fill + (type none) + ) + ) + ) + (symbol "R_1_1" + (pin passive line + (at 0 3.81 270) + (length 1.27) + (name "~" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "1" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin passive line + (at 0 -3.81 90) + (length 1.27) + (name "~" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "2" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + ) + ) + ) + (no_connect + (at 132.08 53.34) + (uuid "45692886-21e1-4796-b1b4-634d6c7163d5") + ) + (no_connect + (at 83.82 53.34) + (uuid "c283f30d-202c-4a0d-9d0a-ea95c5656841") + ) + (wire + (pts + (xy 91.44 53.34) (xy 124.46 53.34) + ) + (stroke + (width 0) + (type default) + ) + (uuid "6f61af9e-a21c-4ea8-aac1-554407fe006a") + ) + (rule_area + (polyline + (pts + (xy 99.06 44.45) (xy 105.41 44.45) (xy 105.41 60.96) (xy 99.06 60.96) + ) + (stroke + (width 0) + (type dash) + ) + (fill + (type none) + ) + (uuid 01d7705f-5c3d-4abe-b2c7-9e8f5b00caa8) + ) + ) + (netclass_flag "" + (length 2.54) + (shape round) + (at 101.6 44.45 0) + (fields_autoplaced yes) + (effects + (font + (size 1.27 1.27) + ) + (justify left bottom) + ) + (uuid "655ca652-695a-4f6a-93fa-4347d69a2f15") + (property "Netclass" "CLASS1" + (at 102.2985 41.91 0) + (effects + (font + (size 1.27 1.27) + (italic yes) + ) + (justify left) + ) + ) + ) + (symbol + (lib_id "Device:R") + (at 87.63 53.34 270) + (unit 1) + (exclude_from_sim no) + (in_bom yes) + (on_board yes) + (dnp no) + (fields_autoplaced yes) + (uuid "57d8460e-6d78-4dc9-8734-201edb21294a") + (property "Reference" "R1" + (at 87.63 46.99 90) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Value" "R" + (at 87.63 49.53 90) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Footprint" "" + (at 87.63 51.562 90) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Datasheet" "~" + (at 87.63 53.34 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Description" "Resistor" + (at 87.63 53.34 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (pin "1" + (uuid "5d7b48cb-ccac-454c-b8ae-d8106594e160") + ) + (pin "2" + (uuid "e59c83d5-3e67-4d97-8414-98cf0d4cd0f7") + ) + (instances + (project "" + (path "/8a90b8e2-28c2-4028-bcb6-cef3be983f1e" + (reference "R1") + (unit 1) + ) + ) + ) + ) + (symbol + (lib_id "Device:R") + (at 128.27 53.34 270) + (unit 1) + (exclude_from_sim no) + (in_bom yes) + (on_board yes) + (dnp no) + (fields_autoplaced yes) + (uuid "758cfdea-5424-4d58-a623-ef6afa897cdc") + (property "Reference" "R2" + (at 128.27 46.99 90) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Value" "R" + (at 128.27 49.53 90) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Footprint" "" + (at 128.27 51.562 90) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Datasheet" "~" + (at 128.27 53.34 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Description" "Resistor" + (at 128.27 53.34 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (pin "1" + (uuid "fc325f46-cf90-477d-b24e-842cfcdab056") + ) + (pin "2" + (uuid "d48f9df0-ba21-474c-878e-433fd5cecebf") + ) + (instances + (project "RuleAreaOneNetclassDirective" + (path "/8a90b8e2-28c2-4028-bcb6-cef3be983f1e" + (reference "R2") + (unit 1) + ) + ) + ) + ) + (sheet_instances + (path "/" + (page "1") + ) + ) +) diff --git a/qa/data/eeschema/RuleAreaOneOverlap.kicad_sch b/qa/data/eeschema/RuleAreaOneOverlap.kicad_sch new file mode 100644 index 0000000000..9011dd7462 --- /dev/null +++ b/qa/data/eeschema/RuleAreaOneOverlap.kicad_sch @@ -0,0 +1,43 @@ +(kicad_sch + (version 20240417) + (generator "eeschema") + (generator_version "8.99") + (uuid "e77939c4-758d-4d0c-a6e5-dc441c8c54c9") + (paper "A4") + (lib_symbols) + (rule_area + (polyline + (pts + (xy 81.28 64.77) (xy 107.95 64.77) (xy 107.95 97.79) (xy 106.68 99.06) (xy 100.33 99.06) (xy 81.28 80.01) + ) + (stroke + (width 0) + (type dash) + ) + (fill + (type none) + ) + (uuid 29482117-50fb-4bf0-9227-b57a2a024196) + ) + ) + (rule_area + (polyline + (pts + (xy 63.5 48.26) (xy 95.25 48.26) (xy 95.25 76.2) (xy 92.71 78.74) (xy 67.31 78.74) (xy 63.5 74.93) + ) + (stroke + (width 0) + (type dash) + ) + (fill + (type none) + ) + (uuid fb9e0dd5-7b94-4714-b095-086e21d4076e) + ) + ) + (sheet_instances + (path "/" + (page "1") + ) + ) +) diff --git a/qa/data/eeschema/RuleAreaOneOverlapTwice.kicad_sch b/qa/data/eeschema/RuleAreaOneOverlapTwice.kicad_sch new file mode 100644 index 0000000000..dc53e6fa9f --- /dev/null +++ b/qa/data/eeschema/RuleAreaOneOverlapTwice.kicad_sch @@ -0,0 +1,75 @@ +(kicad_sch + (version 20240417) + (generator "eeschema") + (generator_version "8.99") + (uuid "e77939c4-758d-4d0c-a6e5-dc441c8c54c9") + (paper "A4") + (lib_symbols) + (rule_area + (polyline + (pts + (xy 193.04 80.01) (xy 229.87 80.01) (xy 231.14 81.28) (xy 231.14 111.76) (xy 205.74 111.76) (xy 199.39 105.41) + (xy 199.39 102.87) (xy 193.04 96.52) + ) + (stroke + (width 0) + (type dash) + ) + (fill + (type none) + ) + (uuid 205fcd89-40f4-4d53-8543-c72a408467cf) + ) + ) + (rule_area + (polyline + (pts + (xy 215.9 100.33) (xy 215.9 135.89) (xy 250.19 135.89) (xy 255.27 130.81) (xy 255.27 109.22) (xy 246.38 100.33) + ) + (stroke + (width 0) + (type dash) + ) + (fill + (type none) + ) + (uuid 4953ad1a-332e-4324-a6e9-44125580b5ce) + ) + ) + (rule_area + (polyline + (pts + (xy 90.17 52.07) (xy 64.77 52.07) (xy 64.77 71.12) (xy 73.66 80.01) (xy 77.47 80.01) (xy 90.17 67.31) + ) + (stroke + (width 0) + (type dash) + ) + (fill + (type none) + ) + (uuid 9e60651e-f0c3-4ab3-88c0-5b67a7585ce9) + ) + ) + (rule_area + (polyline + (pts + (xy 80.01 66.04) (xy 107.95 66.04) (xy 110.49 68.58) (xy 110.49 100.33) (xy 85.09 100.33) (xy 74.93 90.17) + (xy 74.93 71.12) + ) + (stroke + (width 0) + (type dash) + ) + (fill + (type none) + ) + (uuid a2990ee5-6b66-496f-a7b1-0402651214fa) + ) + ) + (sheet_instances + (path "/" + (page "1") + ) + ) +) diff --git a/qa/data/eeschema/RuleAreaThreeNetclassDirectives.kicad_sch b/qa/data/eeschema/RuleAreaThreeNetclassDirectives.kicad_sch new file mode 100644 index 0000000000..b64e842aed --- /dev/null +++ b/qa/data/eeschema/RuleAreaThreeNetclassDirectives.kicad_sch @@ -0,0 +1,373 @@ +(kicad_sch + (version 20240417) + (generator "eeschema") + (generator_version "8.99") + (uuid "3faa707b-1d78-466d-bafe-1ee08b43bb08") + (paper "A4") + (lib_symbols + (symbol "Device:R" + (pin_numbers hide) + (pin_names + (offset 0) + ) + (exclude_from_sim no) + (in_bom yes) + (on_board yes) + (property "Reference" "R" + (at 2.032 0 90) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Value" "R" + (at 0 0 90) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Footprint" "" + (at -1.778 0 90) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Datasheet" "~" + (at 0 0 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Description" "Resistor" + (at 0 0 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "ki_keywords" "R res resistor" + (at 0 0 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "ki_fp_filters" "R_*" + (at 0 0 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (symbol "R_0_1" + (rectangle + (start -1.016 -2.54) + (end 1.016 2.54) + (stroke + (width 0.254) + (type default) + ) + (fill + (type none) + ) + ) + ) + (symbol "R_1_1" + (pin passive line + (at 0 3.81 270) + (length 1.27) + (name "~" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "1" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin passive line + (at 0 -3.81 90) + (length 1.27) + (name "~" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "2" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + ) + ) + ) + (no_connect + (at 88.9 74.93) + (uuid "5833ab2c-0b21-4284-bda0-ec7a7f3886e3") + ) + (no_connect + (at 137.16 74.93) + (uuid "6a8eab8a-8b70-48aa-8775-bd58e38e6544") + ) + (wire + (pts + (xy 96.52 74.93) (xy 129.54 74.93) + ) + (stroke + (width 0) + (type default) + ) + (uuid "3c913039-c0ff-4276-9bfd-95aa6d609814") + ) + (rule_area + (polyline + (pts + (xy 102.87 66.04) (xy 119.38 66.04) (xy 119.38 78.74) (xy 113.03 85.09) (xy 107.95 85.09) (xy 102.87 80.01) + ) + (stroke + (width 0) + (type dash) + ) + (fill + (type none) + ) + (uuid 732edf9e-56d8-43fc-b7ec-e3df2ffeb182) + ) + ) + (netclass_flag "" + (length 2.54) + (shape round) + (at 106.68 66.04 0) + (fields_autoplaced yes) + (effects + (font + (size 1.27 1.27) + ) + (justify left bottom) + ) + (uuid "0e535a38-1e87-423a-9462-046eeca2761f") + (property "Netclass" "CLASS1" + (at 107.3785 63.5 0) + (effects + (font + (size 1.27 1.27) + (italic yes) + ) + (justify left) + ) + ) + ) + (netclass_flag "" + (length 2.54) + (shape round) + (at 115.57 66.04 0) + (fields_autoplaced yes) + (effects + (font + (size 1.27 1.27) + ) + (justify left bottom) + ) + (uuid "3651b727-06a6-4517-b594-14249bcb01ff") + (property "Netclass" "CLASS2" + (at 116.2685 63.5 0) + (effects + (font + (size 1.27 1.27) + (italic yes) + ) + (justify left) + ) + ) + ) + (netclass_flag "" + (length 2.54) + (shape round) + (at 111.76 85.09 180) + (fields_autoplaced yes) + (effects + (font + (size 1.27 1.27) + ) + (justify right bottom) + ) + (uuid "39c91c96-4582-476e-aaa8-0403610211a4") + (property "Netclass" "CLASS3" + (at 112.4585 87.63 0) + (effects + (font + (size 1.27 1.27) + (italic yes) + ) + (justify left) + ) + ) + ) + (symbol + (lib_id "Device:R") + (at 133.35 74.93 270) + (unit 1) + (exclude_from_sim no) + (in_bom yes) + (on_board yes) + (dnp no) + (fields_autoplaced yes) + (uuid "69906541-660f-49dd-b971-79ef911d51f2") + (property "Reference" "R2" + (at 133.35 68.58 90) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Value" "R" + (at 133.35 71.12 90) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Footprint" "" + (at 133.35 73.152 90) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Datasheet" "~" + (at 133.35 74.93 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Description" "Resistor" + (at 133.35 74.93 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (pin "1" + (uuid "5a4f2edd-e02e-4a2a-b273-2a79268c7b9b") + ) + (pin "2" + (uuid "92e081c4-e86d-400c-82e1-d46671606b34") + ) + (instances + (project "RuleAreaThreeNetclassDirectives" + (path "/3faa707b-1d78-466d-bafe-1ee08b43bb08" + (reference "R2") + (unit 1) + ) + ) + ) + ) + (symbol + (lib_id "Device:R") + (at 92.71 74.93 270) + (unit 1) + (exclude_from_sim no) + (in_bom yes) + (on_board yes) + (dnp no) + (fields_autoplaced yes) + (uuid "6c332818-966e-43a5-87c5-6aea83712c35") + (property "Reference" "R1" + (at 92.71 68.58 90) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Value" "R" + (at 92.71 71.12 90) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Footprint" "" + (at 92.71 73.152 90) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Datasheet" "~" + (at 92.71 74.93 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Description" "Resistor" + (at 92.71 74.93 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (pin "1" + (uuid "50fab8cc-532c-479e-a764-1688c768c001") + ) + (pin "2" + (uuid "0f5fce2a-437e-4928-b1e0-0469819dd910") + ) + (instances + (project "RuleAreaThreeNetclassDirectives" + (path "/3faa707b-1d78-466d-bafe-1ee08b43bb08" + (reference "R1") + (unit 1) + ) + ) + ) + ) + (sheet_instances + (path "/" + (page "1") + ) + ) +) diff --git a/qa/data/eeschema/RuleAreaTwoNetclassDirectives.kicad_sch b/qa/data/eeschema/RuleAreaTwoNetclassDirectives.kicad_sch new file mode 100644 index 0000000000..3c30f7825e --- /dev/null +++ b/qa/data/eeschema/RuleAreaTwoNetclassDirectives.kicad_sch @@ -0,0 +1,350 @@ +(kicad_sch + (version 20240417) + (generator "eeschema") + (generator_version "8.99") + (uuid "72af7883-b930-45a7-b8c1-7781d1afdd37") + (paper "A4") + (lib_symbols + (symbol "Device:R" + (pin_numbers hide) + (pin_names + (offset 0) + ) + (exclude_from_sim no) + (in_bom yes) + (on_board yes) + (property "Reference" "R" + (at 2.032 0 90) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Value" "R" + (at 0 0 90) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Footprint" "" + (at -1.778 0 90) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Datasheet" "~" + (at 0 0 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Description" "Resistor" + (at 0 0 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "ki_keywords" "R res resistor" + (at 0 0 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "ki_fp_filters" "R_*" + (at 0 0 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (symbol "R_0_1" + (rectangle + (start -1.016 -2.54) + (end 1.016 2.54) + (stroke + (width 0.254) + (type default) + ) + (fill + (type none) + ) + ) + ) + (symbol "R_1_1" + (pin passive line + (at 0 3.81 270) + (length 1.27) + (name "~" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "1" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin passive line + (at 0 -3.81 90) + (length 1.27) + (name "~" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "2" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + ) + ) + ) + (no_connect + (at 106.68 71.12) + (uuid "2f717211-60fa-4046-b14f-c148c1bebb31") + ) + (no_connect + (at 154.94 71.12) + (uuid "e58e3d56-33f2-47c1-8169-9b603a68c30f") + ) + (wire + (pts + (xy 114.3 71.12) (xy 147.32 71.12) + ) + (stroke + (width 0) + (type default) + ) + (uuid "2d14d2f0-6ae7-41c8-8699-c57748f8f4ae") + ) + (rule_area + (polyline + (pts + (xy 121.92 62.23) (xy 137.16 62.23) (xy 137.16 73.66) (xy 135.89 74.93) (xy 123.19 74.93) (xy 121.92 73.66) + ) + (stroke + (width 0) + (type dash) + ) + (fill + (type none) + ) + (uuid 826526a6-b643-4832-8cb3-7693aac87471) + ) + ) + (netclass_flag "" + (length 2.54) + (shape round) + (at 133.35 62.23 0) + (fields_autoplaced yes) + (effects + (font + (size 1.27 1.27) + ) + (justify left bottom) + ) + (uuid "0318db31-1e4c-4d69-8534-0e5f3ee9c7eb") + (property "Netclass" "CLASS2" + (at 134.0485 59.69 0) + (effects + (font + (size 1.27 1.27) + (italic yes) + ) + (justify left) + ) + ) + ) + (netclass_flag "" + (length 2.54) + (shape round) + (at 124.46 62.23 0) + (fields_autoplaced yes) + (effects + (font + (size 1.27 1.27) + ) + (justify left bottom) + ) + (uuid "74dc4bc7-6913-49ba-a2f3-bcdc39c3019f") + (property "Netclass" "CLASS1" + (at 125.1585 59.69 0) + (effects + (font + (size 1.27 1.27) + (italic yes) + ) + (justify left) + ) + ) + ) + (symbol + (lib_id "Device:R") + (at 151.13 71.12 270) + (unit 1) + (exclude_from_sim no) + (in_bom yes) + (on_board yes) + (dnp no) + (fields_autoplaced yes) + (uuid "4f5c367d-1c08-48f1-86dc-e95b0140a51b") + (property "Reference" "R2" + (at 151.13 64.77 90) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Value" "R" + (at 151.13 67.31 90) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Footprint" "" + (at 151.13 69.342 90) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Datasheet" "~" + (at 151.13 71.12 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Description" "Resistor" + (at 151.13 71.12 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (pin "1" + (uuid "dbe85168-ed53-4e8a-8c45-277d6c150cde") + ) + (pin "2" + (uuid "4c070690-f1aa-48d2-a083-8c7fe39df9c2") + ) + (instances + (project "RuleAreaTwoNetclassDirectives" + (path "/72af7883-b930-45a7-b8c1-7781d1afdd37" + (reference "R2") + (unit 1) + ) + ) + ) + ) + (symbol + (lib_id "Device:R") + (at 110.49 71.12 270) + (unit 1) + (exclude_from_sim no) + (in_bom yes) + (on_board yes) + (dnp no) + (fields_autoplaced yes) + (uuid "7848d115-602f-44c3-b980-c1e03934ea7e") + (property "Reference" "R1" + (at 110.49 64.77 90) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Value" "R" + (at 110.49 67.31 90) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Footprint" "" + (at 110.49 69.342 90) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Datasheet" "~" + (at 110.49 71.12 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Description" "Resistor" + (at 110.49 71.12 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (pin "1" + (uuid "84a25105-1263-4adf-a0a5-a2bdf51e736e") + ) + (pin "2" + (uuid "4d2f1a67-27d6-4ef0-a95f-9a34d0373e7d") + ) + (instances + (project "RuleAreaTwoNetclassDirectives" + (path "/72af7883-b930-45a7-b8c1-7781d1afdd37" + (reference "R1") + (unit 1) + ) + ) + ) + ) + (sheet_instances + (path "/" + (page "1") + ) + ) +) diff --git a/qa/data/eeschema/RuleAreaTwoOverlaps.kicad_sch b/qa/data/eeschema/RuleAreaTwoOverlaps.kicad_sch new file mode 100644 index 0000000000..11b789fe37 --- /dev/null +++ b/qa/data/eeschema/RuleAreaTwoOverlaps.kicad_sch @@ -0,0 +1,58 @@ +(kicad_sch + (version 20240417) + (generator "eeschema") + (generator_version "8.99") + (uuid "e77939c4-758d-4d0c-a6e5-dc441c8c54c9") + (paper "A4") + (lib_symbols) + (rule_area + (polyline + (pts + (xy 105.41 66.04) (xy 105.41 95.25) (xy 124.46 95.25) (xy 133.35 86.36) (xy 133.35 83.82) (xy 115.57 66.04) + ) + (stroke + (width 0) + (type dash) + ) + (fill + (type none) + ) + (uuid b2e29650-5d7b-46fa-b3ac-2da8c988ba4f) + ) + ) + (rule_area + (polyline + (pts + (xy 85.09 76.2) (xy 114.3 76.2) (xy 114.3 107.95) (xy 92.71 107.95) (xy 85.09 100.33) + ) + (stroke + (width 0) + (type dash) + ) + (fill + (type none) + ) + (uuid c2d18c36-f42f-4d71-b979-aec0f5842e3b) + ) + ) + (rule_area + (polyline + (pts + (xy 68.58 60.96) (xy 96.52 60.96) (xy 97.79 62.23) (xy 97.79 88.9) (xy 76.2 88.9) (xy 68.58 81.28) + ) + (stroke + (width 0) + (type dash) + ) + (fill + (type none) + ) + (uuid d4219527-beea-4857-99ad-9080af58cbc4) + ) + ) + (sheet_instances + (path "/" + (page "1") + ) + ) +) diff --git a/qa/tests/eeschema/CMakeLists.txt b/qa/tests/eeschema/CMakeLists.txt index 0a752bd38d..11455139a7 100644 --- a/qa/tests/eeschema/CMakeLists.txt +++ b/qa/tests/eeschema/CMakeLists.txt @@ -56,6 +56,7 @@ set( QA_EESCHEMA_SRCS erc/test_erc_global_labels.cpp erc/test_erc_no_connect.cpp erc/test_erc_hierarchical_schematics.cpp + erc/test_erc_rule_areas.cpp test_eagle_plugin.cpp test_lib_part.cpp diff --git a/qa/tests/eeschema/erc/test_erc_rule_areas.cpp b/qa/tests/eeschema/erc/test_erc_rule_areas.cpp new file mode 100644 index 0000000000..514b97d3ea --- /dev/null +++ b/qa/tests/eeschema/erc/test_erc_rule_areas.cpp @@ -0,0 +1,139 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2024 KiCad Developers, see AUTHORS.TXT for contributors. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 3 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, you may find one at + * http://www.gnu.org/licenses/ + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + + +struct ERC_REGRESSION_TEST_FIXTURE +{ + ERC_REGRESSION_TEST_FIXTURE() : m_settingsManager( true /* headless */ ) {} + + SETTINGS_MANAGER m_settingsManager; + std::unique_ptr m_schematic; +}; + + +BOOST_FIXTURE_TEST_CASE( ERCRuleAreaNetClasseDirectives, ERC_REGRESSION_TEST_FIXTURE ) +{ + LOCALE_IO dummy; + + // Check for Errors when using rule area netclass directives + std::vector> tests = { { "RuleAreaOneNetclassDirective", 0 }, + { "RuleAreaTwoNetclassDirectives", 1 }, + { "RuleAreaThreeNetclassDirectives", 2 }, + { "RuleAreaNetclassConflictOnWire_1", 1 }, + { "RuleAreaNetclassConflictOnWire_2", 1 } }; + + for( const std::pair& test : tests ) + { + KI_TEST::LoadSchematic( m_settingsManager, test.first, m_schematic ); + + ERC_SETTINGS& settings = m_schematic->ErcSettings(); + SHEETLIST_ERC_ITEMS_PROVIDER errors( m_schematic.get() ); + + // Skip the "Modified symbol" warning + settings.m_ERCSeverities[ERCE_LIB_SYMBOL_ISSUES] = RPT_SEVERITY_IGNORE; + settings.m_ERCSeverities[ERCE_LIB_SYMBOL_MISMATCH] = RPT_SEVERITY_IGNORE; + + // Configure the rules under test + settings.m_ERCSeverities[ERCE_NETCLASS_CONFLICT] = RPT_SEVERITY_ERROR; + + std::unordered_set allScreens; + SCH_SCREENS screens( m_schematic->Root() ); + screens.BuildClientSheetPathList(); + + for( SCH_SCREEN* screen = screens.GetFirst(); screen != nullptr; + screen = screens.GetNext() ) + allScreens.insert( screen ); + + SCH_RULE_AREA::UpdateRuleAreasInScreens( allScreens, nullptr ); + m_schematic->ConnectionGraph()->Recalculate( m_schematic->GetSheets(), true ); + m_schematic->ConnectionGraph()->RunERC(); + + errors.SetSeverities( RPT_SEVERITY_ERROR | RPT_SEVERITY_WARNING ); + + ERC_REPORT reportWriter( m_schematic.get(), EDA_UNITS::MILLIMETRES ); + + BOOST_CHECK_MESSAGE( errors.GetCount() == test.second, + "Expected " << test.second << " errors in " << test.first.ToStdString() + << " but got " << errors.GetCount() << "\n" + << reportWriter.GetTextReport() ); + } +} + + +BOOST_FIXTURE_TEST_CASE( ERCRuleAreaOverlaps, ERC_REGRESSION_TEST_FIXTURE ) +{ + LOCALE_IO dummy; + + // Check for Errors when using rule area netclass directives + std::vector> tests = { { "RuleAreaNoOverlap", 0 }, + { "RuleAreaOneOverlap", 1 }, + { "RuleAreaOneOverlapTwice", 2 }, + { "RuleAreaTwoOverlaps", 2 } }; + + for( const std::pair& test : tests ) + { + KI_TEST::LoadSchematic( m_settingsManager, test.first, m_schematic ); + + ERC_SETTINGS& settings = m_schematic->ErcSettings(); + SHEETLIST_ERC_ITEMS_PROVIDER errors( m_schematic.get() ); + + // Skip the "Modified symbol" warning + settings.m_ERCSeverities[ERCE_LIB_SYMBOL_ISSUES] = RPT_SEVERITY_IGNORE; + settings.m_ERCSeverities[ERCE_LIB_SYMBOL_MISMATCH] = RPT_SEVERITY_IGNORE; + + // Configure the rules under test + settings.m_ERCSeverities[ERCE_OVERLAPPING_RULE_AREAS] = RPT_SEVERITY_ERROR; + + std::unordered_set allScreens; + SCH_SCREENS screens( m_schematic->Root() ); + screens.BuildClientSheetPathList(); + + for( SCH_SCREEN* screen = screens.GetFirst(); screen != nullptr; + screen = screens.GetNext() ) + allScreens.insert( screen ); + + SCH_RULE_AREA::UpdateRuleAreasInScreens( allScreens, nullptr ); + m_schematic->ConnectionGraph()->Recalculate( m_schematic->GetSheets(), true ); + + ERC_TESTER tester( m_schematic.get() ); + tester.RunRuleAreaERC(); + + errors.SetSeverities( RPT_SEVERITY_ERROR | RPT_SEVERITY_WARNING ); + + ERC_REPORT reportWriter( m_schematic.get(), EDA_UNITS::MILLIMETRES ); + + BOOST_CHECK_MESSAGE( errors.GetCount() == test.second, + "Expected " << test.second << " errors in " << test.first.ToStdString() + << " but got " << errors.GetCount() << "\n" + << reportWriter.GetTextReport() ); + } +} diff --git a/qa/tests/eeschema/test_ee_item.cpp b/qa/tests/eeschema/test_ee_item.cpp index 78deba7e69..d1011672cd 100644 --- a/qa/tests/eeschema/test_ee_item.cpp +++ b/qa/tests/eeschema/test_ee_item.cpp @@ -33,6 +33,7 @@ #include #include #include +#include #include #include #include @@ -81,6 +82,24 @@ public: case SCH_BUS_WIRE_ENTRY_T: return new SCH_BUS_WIRE_ENTRY(); case SCH_BUS_BUS_ENTRY_T: return new SCH_BUS_BUS_ENTRY(); case SCH_LINE_T: return new SCH_LINE(); + case SCH_RULE_AREA_T: + { + SHAPE_POLY_SET ruleShape; + + ruleShape.NewOutline(); + auto& outline = ruleShape.Outline( 0 ); + outline.Append( VECTOR2I( 20000, 20000) ); + outline.Append( VECTOR2I( 22000, 20000) ); + outline.Append( VECTOR2I( 22000, 22000) ); + outline.Append( VECTOR2I( 20000, 22000) ); + outline.SetClosed( true ); + outline.Simplify( true ); + + SCH_RULE_AREA* ruleArea = new SCH_RULE_AREA(); + ruleArea->SetPolyShape( ruleShape ); + + return ruleArea; + } case SCH_SHAPE_T: return new SCH_SHAPE( SHAPE_T::ARC, LAYER_NOTES ); case SCH_BITMAP_T: return new SCH_BITMAP(); case SCH_TEXT_T: return new SCH_TEXT( VECTOR2I( 0, 0 ), "test text" );