From 1391774f64e043f2b9af06cd76935666a323f061 Mon Sep 17 00:00:00 2001 From: Jeff Young Date: Sat, 21 Nov 2020 20:42:27 +0000 Subject: [PATCH] Re-factor boundary/courtyard checker error handling. ADDED Footprint Checker dialog to display the results in. Fixes https://gitlab.com/kicad/code/kicad/issues/6446 --- 3d-viewer/3d_canvas/board_adapter.cpp | 21 +- common/rc_item.cpp | 3 + .../symbol_editor/menubar_symbol_editor.cpp | 4 +- .../symbol_editor/toolbars_symbol_editor.cpp | 2 +- eeschema/tools/ee_actions.cpp | 5 + eeschema/tools/ee_actions.h | 1 + eeschema/tools/ee_inspection_tool.cpp | 33 +-- eeschema/tools/ee_inspection_tool.h | 4 +- pcbnew/CMakeLists.txt | 2 + pcbnew/board.cpp | 6 +- pcbnew/board.h | 8 +- .../convert_drawsegment_list_to_polygon.cpp | 182 ++++++++++----- pcbnew/convert_drawsegment_list_to_polygon.h | 15 +- pcbnew/dialogs/dialog_footprint_checker.cpp | 186 ++++++++++++++++ pcbnew/dialogs/dialog_footprint_checker.h | 60 +++++ .../dialogs/dialog_footprint_checker_base.cpp | 73 ++++++ .../dialogs/dialog_footprint_checker_base.fbp | 209 ++++++++++++++++++ .../dialogs/dialog_footprint_checker_base.h | 59 +++++ .../drc_test_provider_courtyard_clearance.cpp | 38 +--- pcbnew/drc/drc_test_provider_misc.cpp | 53 ++--- pcbnew/footprint.cpp | 15 +- pcbnew/footprint.h | 6 +- pcbnew/menubar_footprint_editor.cpp | 3 + pcbnew/toolbars_footprint_editor.cpp | 1 + pcbnew/tools/footprint_editor_tools.cpp | 38 +++- pcbnew/tools/footprint_editor_tools.h | 6 +- pcbnew/tools/pcb_actions.cpp | 4 + pcbnew/tools/pcb_actions.h | 2 + 28 files changed, 855 insertions(+), 184 deletions(-) create mode 100644 pcbnew/dialogs/dialog_footprint_checker.cpp create mode 100644 pcbnew/dialogs/dialog_footprint_checker.h create mode 100644 pcbnew/dialogs/dialog_footprint_checker_base.cpp create mode 100644 pcbnew/dialogs/dialog_footprint_checker_base.fbp create mode 100644 pcbnew/dialogs/dialog_footprint_checker_base.h diff --git a/3d-viewer/3d_canvas/board_adapter.cpp b/3d-viewer/3d_canvas/board_adapter.cpp index 2145fb6d18..29fbe5708c 100644 --- a/3d-viewer/3d_canvas/board_adapter.cpp +++ b/3d-viewer/3d_canvas/board_adapter.cpp @@ -452,9 +452,8 @@ void BOARD_ADAPTER::InitSettings( REPORTER* aStatusReporter, REPORTER* aWarningR extern bool BuildFootprintPolygonOutlines( BOARD* aBoard, SHAPE_POLY_SET& aOutlines, - unsigned int aTolerance, wxString* aErrorText, - std::vector* aDiscontinuities = nullptr, - std::vector* aIntersections = nullptr ); + int aTolerance, + OUTLINE_ERROR_HANDLER* aErrorHandler = nullptr ); bool BOARD_ADAPTER::createBoardPolygon( wxString* aErrorMsg ) @@ -465,18 +464,28 @@ bool BOARD_ADAPTER::createBoardPolygon( wxString* aErrorMsg ) if( m_board->IsFootprintHolder() ) { + if( !m_board->GetFirstFootprint() ) + { + if( aErrorMsg ) + *aErrorMsg = _( "No footprint loaded." ); + + return false; + } + success = BuildFootprintPolygonOutlines( m_board, m_board_poly, - m_board->GetDesignSettings().m_MaxError, nullptr ); + m_board->GetDesignSettings().m_MaxError ); // Make polygon strictly simple to avoid issues (especially in 3D viewer) m_board_poly.Simplify( SHAPE_POLY_SET::PM_STRICTLY_SIMPLE ); if( !success && aErrorMsg ) - *aErrorMsg = _( "Footprint outline is malformed. Run DRC for a full analysis." ); + { + *aErrorMsg = _( "Footprint outline is malformed. Run Footprint Checker for a " + "full analysis." ); + } } else { - success = m_board->GetBoardPolygonOutlines( m_board_poly ); if( !success && aErrorMsg ) diff --git a/common/rc_item.cpp b/common/rc_item.cpp index 4ceaf24de7..98248f1bf8 100644 --- a/common/rc_item.cpp +++ b/common/rc_item.cpp @@ -439,6 +439,9 @@ void RC_TREE_MODEL::DeleteItems( bool aCurrentOnly, bool aIncludeExclusions, boo return; } + if( !m_rcItemsProvider ) + return; + int lastGood = -1; bool found = false; diff --git a/eeschema/symbol_editor/menubar_symbol_editor.cpp b/eeschema/symbol_editor/menubar_symbol_editor.cpp index ed91af6511..a3cda92d57 100644 --- a/eeschema/symbol_editor/menubar_symbol_editor.cpp +++ b/eeschema/symbol_editor/menubar_symbol_editor.cpp @@ -149,7 +149,9 @@ void SYMBOL_EDIT_FRAME::ReCreateMenuBar() ACTION_MENU* inspectMenu = new ACTION_MENU( false, selTool ); inspectMenu->Add( EE_ACTIONS::showDatasheet ); - inspectMenu->Add( EE_ACTIONS::runERC ); + + inspectMenu->AppendSeparator(); + inspectMenu->Add( EE_ACTIONS::checkSymbol ); //-- Preferences menu ----------------------------------------------- diff --git a/eeschema/symbol_editor/toolbars_symbol_editor.cpp b/eeschema/symbol_editor/toolbars_symbol_editor.cpp index a5ab2129ea..09ab97b775 100644 --- a/eeschema/symbol_editor/toolbars_symbol_editor.cpp +++ b/eeschema/symbol_editor/toolbars_symbol_editor.cpp @@ -118,7 +118,7 @@ void SYMBOL_EDIT_FRAME::ReCreateHToolbar() m_mainToolBar->AddScaledSeparator( this ); m_mainToolBar->Add( EE_ACTIONS::showDatasheet ); - m_mainToolBar->Add( EE_ACTIONS::runERC ); + m_mainToolBar->Add( EE_ACTIONS::checkSymbol ); m_mainToolBar->AddScaledSeparator( this ); m_mainToolBar->Add( EE_ACTIONS::showDeMorganStandard, ACTION_TOOLBAR::TOGGLE ); diff --git a/eeschema/tools/ee_actions.cpp b/eeschema/tools/ee_actions.cpp index 5ff2f27be5..ffb4712092 100644 --- a/eeschema/tools/ee_actions.cpp +++ b/eeschema/tools/ee_actions.cpp @@ -45,6 +45,11 @@ TOOL_ACTION EE_ACTIONS::runERC( "eeschema.InspectionTool.runERC", _( "Electrical Rules Checker" ), _( "Perform electrical rules check" ), erc_xpm ); +TOOL_ACTION EE_ACTIONS::checkSymbol( "eeschema.InspectionTool.checkSymbol", + AS_GLOBAL, 0, "", + _( "Symbol Checker" ), _( "Show the symbol checker window" ), + erc_xpm ); + TOOL_ACTION EE_ACTIONS::runSimulation( "eeschema.EditorControl.runSimulation", AS_GLOBAL, 0, "", _( "Simulator..." ), _( "Simulate circuit in SPICE" ), diff --git a/eeschema/tools/ee_actions.h b/eeschema/tools/ee_actions.h index ff2f0afbc7..7efb09925b 100644 --- a/eeschema/tools/ee_actions.h +++ b/eeschema/tools/ee_actions.h @@ -153,6 +153,7 @@ public: static TOOL_ACTION showBusManager; static TOOL_ACTION schematicSetup; static TOOL_ACTION editPageNumber; + static TOOL_ACTION checkSymbol; static TOOL_ACTION rescueSymbols; static TOOL_ACTION remapSymbols; diff --git a/eeschema/tools/ee_inspection_tool.cpp b/eeschema/tools/ee_inspection_tool.cpp index 36f3386123..88b8239bb5 100644 --- a/eeschema/tools/ee_inspection_tool.cpp +++ b/eeschema/tools/ee_inspection_tool.cpp @@ -82,11 +82,7 @@ void EE_INSPECTION_TOOL::Reset( RESET_REASON aReason ) int EE_INSPECTION_TOOL::RunERC( const TOOL_EVENT& aEvent ) { - if( m_frame->IsType( FRAME_SCH_SYMBOL_EDITOR ) ) - { - checkPart( static_cast( m_frame )->GetCurPart() ); - } - else if( m_frame->IsType( FRAME_SCH ) ) + if( m_frame->IsType( FRAME_SCH ) ) { if( m_ercDialog ) { @@ -135,10 +131,12 @@ bool sort_by_pin_number( const LIB_PIN* ref, const LIB_PIN* tst ) } -void EE_INSPECTION_TOOL::checkPart( LIB_PART* aPart ) +int EE_INSPECTION_TOOL::CheckSymbol( const TOOL_EVENT& aEvent ) { - if( !aPart ) - return; + LIB_PART* part = static_cast( m_frame )->GetCurPart(); + + if( !part ) + return 0; wxString msg; const int min_grid_size = 25; @@ -146,12 +144,12 @@ void EE_INSPECTION_TOOL::checkPart( LIB_PART* aPart ) const int clamped_grid_size = ( grid_size < min_grid_size ) ? min_grid_size : grid_size; LIB_PINS pinList; - aPart->GetPins( pinList ); + part->GetPins( pinList ); if( pinList.empty() ) { DisplayInfoMessage( m_frame, _( "No pins!" ) ); - return; + return 0; } // Sort pins by pin num, so 2 duplicate pins @@ -176,9 +174,9 @@ void EE_INSPECTION_TOOL::checkPart( LIB_PART* aPart ) dup_error++; - if( aPart->HasConversion() && next->GetConvert() ) + if( part->HasConversion() && next->GetConvert() ) { - if( aPart->GetUnitCount() <= 1 ) + if( part->GetUnitCount() <= 1 ) { msg = wxString::Format( _( "Duplicate pin %s \"%s\" at location (%.3f, %.3f)" " conflicts with pin %s \"%s\" at location (%.3f, %.3f) of converted" ), @@ -206,7 +204,7 @@ void EE_INSPECTION_TOOL::checkPart( LIB_PART* aPart ) } else { - if( aPart->GetUnitCount() <= 1 ) + if( part->GetUnitCount() <= 1 ) { msg = wxString::Format( _( "Duplicate pin %s \"%s\" at location (%.3f, %.3f)" " conflicts with pin %s \"%s\" at location (%.3f, %.3f)" ), @@ -250,9 +248,9 @@ void EE_INSPECTION_TOOL::checkPart( LIB_PART* aPart ) // "pin" is off grid here. offgrid_error++; - if( aPart->HasConversion() && pin->GetConvert() ) + if( part->HasConversion() && pin->GetConvert() ) { - if( aPart->GetUnitCount() <= 1 ) + if( part->GetUnitCount() <= 1 ) { msg = wxString::Format( _( "Off grid pin %s \"%s\" at location " "(%.3f, %.3f) of converted.
" ), @@ -272,7 +270,7 @@ void EE_INSPECTION_TOOL::checkPart( LIB_PART* aPart ) } else { - if( aPart->GetUnitCount() <= 1 ) + if( part->GetUnitCount() <= 1 ) { msg = wxString::Format( _( "Off grid pin %s \"%s\" at location " "(%.3f, %.3f).
" ), @@ -312,6 +310,8 @@ void EE_INSPECTION_TOOL::checkPart( LIB_PART* aPart ) error_display.m_htmlWindow->SetPage( outmsg ); error_display.ShowModal(); } + + return 0; } @@ -400,6 +400,7 @@ int EE_INSPECTION_TOOL::UpdateMessagePanel( const TOOL_EVENT& aEvent ) void EE_INSPECTION_TOOL::setTransitions() { Go( &EE_INSPECTION_TOOL::RunERC, EE_ACTIONS::runERC.MakeEvent() ); + Go( &EE_INSPECTION_TOOL::CheckSymbol, EE_ACTIONS::checkSymbol.MakeEvent() ); Go( &EE_INSPECTION_TOOL::RunSimulation, EE_ACTIONS::runSimulation.MakeEvent() ); Go( &EE_INSPECTION_TOOL::ShowDatasheet, EE_ACTIONS::showDatasheet.MakeEvent() ); diff --git a/eeschema/tools/ee_inspection_tool.h b/eeschema/tools/ee_inspection_tool.h index 8534f4b1df..cedc4e4925 100644 --- a/eeschema/tools/ee_inspection_tool.h +++ b/eeschema/tools/ee_inspection_tool.h @@ -49,6 +49,8 @@ public: int RunERC( const TOOL_EVENT& aEvent ); void DestroyERCDialog(); + int CheckSymbol( const TOOL_EVENT& aEvent ); + int RunSimulation( const TOOL_EVENT& aEvent ); int ShowDatasheet( const TOOL_EVENT& aEvent ); @@ -56,8 +58,6 @@ public: int UpdateMessagePanel( const TOOL_EVENT& aEvent ); private: - void checkPart( LIB_PART* aPart ); - ///> @copydoc TOOL_INTERACTIVE::setTransitions(); void setTransitions() override; diff --git a/pcbnew/CMakeLists.txt b/pcbnew/CMakeLists.txt index b0e33e2fe5..045c5c048f 100644 --- a/pcbnew/CMakeLists.txt +++ b/pcbnew/CMakeLists.txt @@ -63,6 +63,8 @@ set( PCBNEW_DIALOGS dialogs/dialog_dimension_properties_base.cpp dialogs/dialog_drc.cpp dialogs/dialog_drc_base.cpp + dialogs/dialog_footprint_checker.cpp + dialogs/dialog_footprint_checker_base.cpp dialogs/dialog_footprint_properties.cpp dialogs/dialog_footprint_properties_base.cpp dialogs/dialog_edit_footprint_for_fp_editor.cpp diff --git a/pcbnew/board.cpp b/pcbnew/board.cpp index 9ad0d12e9a..c1c2a5d085 100644 --- a/pcbnew/board.cpp +++ b/pcbnew/board.cpp @@ -1842,12 +1842,10 @@ bool BOARD::NormalizeAreaPolygon( PICKED_ITEMS_LIST * aNewZonesList, ZONE* aCurr } -bool BOARD::GetBoardPolygonOutlines( SHAPE_POLY_SET& aOutlines, - std::vector* aDiscontinuities, - std::vector* aIntersections ) +bool BOARD::GetBoardPolygonOutlines( SHAPE_POLY_SET& aOutlines, OUTLINE_ERROR_HANDLER* aErrorHandler ) { bool success = BuildBoardPolygonOutlines( this, aOutlines, GetDesignSettings().m_MaxError, - aDiscontinuities, aIntersections ); + aErrorHandler ); // Make polygon strictly simple to avoid issues (especially in 3D viewer) aOutlines.Simplify( SHAPE_POLY_SET::PM_STRICTLY_SIMPLE ); diff --git a/pcbnew/board.h b/pcbnew/board.h index 58166c4ee8..dd7ba219e0 100644 --- a/pcbnew/board.h +++ b/pcbnew/board.h @@ -588,16 +588,12 @@ public: * Any closed outline inside the main outline is a hole * All contours should be closed, i.e. have valid vertices to build a closed polygon * @param aOutlines The SHAPE_POLY_SET to fill in with outlines/holes. - * @param aDiscontinuities = an optional array of wxPoint giving the locations of - * discontinuities in the outline - * @param aIntersections = an optional array of wxPoint giving the locations of self- - * intersections in the outline + * @param aErrorHandler = an optional DRC_ITEM error handler * * @return true if success, false if a contour is not valid */ bool GetBoardPolygonOutlines( SHAPE_POLY_SET& aOutlines, - std::vector* aDiscontinuities = nullptr, - std::vector* aIntersections = nullptr ); + OUTLINE_ERROR_HANDLER* aErrorHandler = nullptr ); /** * Build a set of polygons which are the outlines of copper items (pads, tracks, vias, texts, diff --git a/pcbnew/convert_drawsegment_list_to_polygon.cpp b/pcbnew/convert_drawsegment_list_to_polygon.cpp index 52cb881590..46d596aa7d 100644 --- a/pcbnew/convert_drawsegment_list_to_polygon.cpp +++ b/pcbnew/convert_drawsegment_list_to_polygon.cpp @@ -23,12 +23,6 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ -/** - * @file convert_drawsegment_list_to_polygon.cpp - * @brief functions to convert a shape built with DRAWSEGMENTS to a polygon. - * expecting the shape describes shape similar to a polygon - */ - #include #include @@ -192,14 +186,10 @@ static PCB_SHAPE* findNext( PCB_SHAPE* aShape, const wxPoint& aPoint, * @param aSegList the initial list of drawsegments (only lines, circles and arcs). * @param aPolygons will contain the complex polygon. * @param aTolerance is the max error distance when polygonizing a curve (internal units) - * @param aDiscontinuities = an optional array of wxPoint giving the locations of - * discontinuities in the outline - * @param aIntersections = an optional array of wxPoint giving the locations of self- - * intersections in the outline + * @param aErrorHandler = an optional error handler */ bool ConvertOutlineToPolygon( std::vector& aSegList, SHAPE_POLY_SET& aPolygons, - int aTolerance, std::vector* aDiscontinuities, - std::vector* aIntersections ) + int aTolerance, OUTLINE_ERROR_HANDLER* aErrorHandler ) { if( aSegList.size() == 0 ) return true; @@ -331,6 +321,15 @@ bool ConvertOutlineToPolygon( std::vector& aSegList, SHAPE_POLY_SET& } } + std::map, PCB_SHAPE*> segOwners; + + auto fetchOwner = + [&]( const SEG& seg ) -> PCB_SHAPE* + { + auto it = segOwners.find( std::make_pair( seg.A, seg.B ) ); + return it == segOwners.end() ? nullptr : it->second; + }; + // Grab the left most point, assume its on the board's perimeter, and see if we // can put enough graphics together by matching endpoints to formulate a cohesive // polygon. @@ -370,6 +369,7 @@ bool ConvertOutlineToPolygon( std::vector& aSegList, SHAPE_POLY_SET& } aPolygons.NewOutline(); + bool first = true; for( auto it = graphic->GetPolyShape().CIterate( 0 ); it; it++ ) { @@ -377,6 +377,13 @@ bool ConvertOutlineToPolygon( std::vector& aSegList, SHAPE_POLY_SET& RotatePoint( pt, orientation ); pt += offset; aPolygons.Append( pt ); + + if( first ) + first = false; + else + segOwners[ std::make_pair( prevPt, pt ) ] = graphic; + + prevPt = (wxPoint) pt; } } else @@ -411,6 +418,7 @@ bool ConvertOutlineToPolygon( std::vector& aSegList, SHAPE_POLY_SET& nextPt = graphic->GetStart(); aPolygons.Append( nextPt ); + segOwners[ std::make_pair( prevPt, nextPt ) ] = graphic; prevPt = nextPt; } break; @@ -442,11 +450,13 @@ bool ConvertOutlineToPolygon( std::vector& aSegList, SHAPE_POLY_SET& RotatePoint( &pt, pcenter, rotation ); aPolygons.Append( pt ); + segOwners[ std::make_pair( prevPt, pt ) ] = graphic; + prevPt = pt; } // Append the last arc end point aPolygons.Append( pend ); - + segOwners[ std::make_pair( prevPt, pend ) ] = graphic; prevPt = pend; } break; @@ -463,7 +473,9 @@ bool ConvertOutlineToPolygon( std::vector& aSegList, SHAPE_POLY_SET& // very close to it. if( close_st( prevPt, graphic->GetStart(), graphic->GetEnd() ) ) + { nextPt = graphic->GetEnd(); + } else { nextPt = graphic->GetStart(); @@ -473,12 +485,21 @@ bool ConvertOutlineToPolygon( std::vector& aSegList, SHAPE_POLY_SET& if( reverse ) { for( int jj = graphic->GetBezierPoints().size()-1; jj >= 0; jj-- ) - aPolygons.Append( graphic->GetBezierPoints()[jj] ); + { + const wxPoint& pt = graphic->GetBezierPoints()[jj]; + aPolygons.Append( pt ); + segOwners[ std::make_pair( prevPt, pt ) ] = graphic; + prevPt = pt; + } } else { - for( size_t jj = 0; jj < graphic->GetBezierPoints().size(); jj++ ) - aPolygons.Append( graphic->GetBezierPoints()[jj] ); + for( const wxPoint& pt : graphic->GetBezierPoints() ) + { + aPolygons.Append( pt ); + segOwners[ std::make_pair( prevPt, pt ) ] = graphic; + prevPt = pt; + } } prevPt = nextPt; @@ -493,10 +514,11 @@ bool ConvertOutlineToPolygon( std::vector& aSegList, SHAPE_POLY_SET& // Get next closest segment. - graphic = findNext( graphic, prevPt, aSegList, aTolerance ); + PCB_SHAPE* nextGraphic = findNext( graphic, prevPt, aSegList, aTolerance ); - if( graphic && !( graphic->GetFlags() & SKIP_STRUCT ) ) + if( nextGraphic && !( nextGraphic->GetFlags() & SKIP_STRUCT ) ) { + graphic = nextGraphic; graphic->SetFlags( SKIP_STRUCT ); startCandidates.erase( graphic ); continue; @@ -509,15 +531,15 @@ bool ConvertOutlineToPolygon( std::vector& aSegList, SHAPE_POLY_SET& polygonComplete = true; break; } - else if( graphic ) // encountered already-used segment, but not at the start + else if( nextGraphic ) // encountered already-used segment, but not at the start { polygonComplete = false; break; } - else // encountered discontinuity + else // encountered discontinuity { - if( aDiscontinuities ) - aDiscontinuities->emplace_back( prevPt ); + if( aErrorHandler ) + (*aErrorHandler)( _( "(not a closed shape)" ), graphic, nullptr, prevPt ); polygonComplete = false; break; @@ -529,7 +551,8 @@ bool ConvertOutlineToPolygon( std::vector& aSegList, SHAPE_POLY_SET& while( startCandidates.size() ) { - int hole = aPolygons.NewHole(); + int hole = aPolygons.NewHole(); + bool firstPt = true; holeNum++; graphic = (PCB_SHAPE*) *startCandidates.begin(); @@ -551,11 +574,18 @@ bool ConvertOutlineToPolygon( std::vector& aSegList, SHAPE_POLY_SET& for( auto it = graphic->GetPolyShape().CIterate(); it; it++ ) { - VECTOR2I val = *it; - RotatePoint( val, orientation ); - val += offset; + VECTOR2I pt = *it; + RotatePoint( pt, orientation ); + pt += offset; - aPolygons.Append( val, -1, hole ); + aPolygons.Append( pt, -1, hole ); + + if( firstPt ) + firstPt = false; + else + segOwners[ std::make_pair( prevPt, pt ) ] = graphic; + + prevPt = (wxPoint) pt; } } else if( graphic->GetShape() == S_CIRCLE ) @@ -576,6 +606,13 @@ bool ConvertOutlineToPolygon( std::vector& aSegList, SHAPE_POLY_SET& nextPt = start; RotatePoint( &nextPt.x, &nextPt.y, center.x, center.y, rotation ); aPolygons.Append( nextPt, -1, hole ); + + if( firstPt ) + firstPt = false; + else + segOwners[ std::make_pair( prevPt, nextPt ) ] = graphic; + + prevPt = nextPt; } } else if( graphic->GetShape() == S_RECT ) @@ -583,7 +620,16 @@ bool ConvertOutlineToPolygon( std::vector& aSegList, SHAPE_POLY_SET& std::vector pts = graphic->GetRectCorners(); for( const wxPoint& pt : pts ) + { aPolygons.Append( pt, -1, hole ); + + if( firstPt ) + firstPt = false; + else + segOwners[ std::make_pair( prevPt, pt ) ] = graphic; + + prevPt = (wxPoint) pt; + } } else { @@ -612,8 +658,9 @@ bool ConvertOutlineToPolygon( std::vector& aSegList, SHAPE_POLY_SET& else nextPt = graphic->GetStart(); + aPolygons.Append( nextPt, -1, hole ); + segOwners[ std::make_pair( prevPt, nextPt ) ] = graphic; prevPt = nextPt; - aPolygons.Append( prevPt, -1, hole ); } break; @@ -644,11 +691,13 @@ bool ConvertOutlineToPolygon( std::vector& aSegList, SHAPE_POLY_SET& RotatePoint( &pt, pcenter, rotation ); aPolygons.Append( pt, -1, hole ); + segOwners[ std::make_pair( prevPt, pt ) ] = graphic; + prevPt = pt; } // Append the last arc end point aPolygons.Append( pend, -1, hole ); - + segOwners[ std::make_pair( prevPt, pend ) ] = graphic; prevPt = pend; } break; @@ -658,15 +707,17 @@ bool ConvertOutlineToPolygon( std::vector& aSegList, SHAPE_POLY_SET& // with a series of short lines and put those // line segments into the !same! PATH. { - wxPoint nextPt; - bool reverse = false; + wxPoint nextPt; + bool reverse = false; // Use the end point furthest away from // prevPt as we assume the other end to be ON prevPt or // very close to it. if( close_st( prevPt, graphic->GetStart(), graphic->GetEnd() ) ) + { nextPt = graphic->GetEnd(); + } else { nextPt = graphic->GetStart(); @@ -676,12 +727,21 @@ bool ConvertOutlineToPolygon( std::vector& aSegList, SHAPE_POLY_SET& if( reverse ) { for( int jj = graphic->GetBezierPoints().size()-1; jj >= 0; jj-- ) - aPolygons.Append( graphic->GetBezierPoints()[jj], -1, hole ); + { + const wxPoint& pt = graphic->GetBezierPoints()[jj]; + aPolygons.Append( pt, -1, hole ); + segOwners[ std::make_pair( prevPt, pt ) ] = graphic; + prevPt = pt; + } } else { - for( const wxPoint& pt : graphic->GetBezierPoints()) + for( const wxPoint& pt : graphic->GetBezierPoints() ) + { aPolygons.Append( pt, -1, hole ); + segOwners[ std::make_pair( prevPt, pt ) ] = graphic; + prevPt = pt; + } } prevPt = nextPt; @@ -697,10 +757,11 @@ bool ConvertOutlineToPolygon( std::vector& aSegList, SHAPE_POLY_SET& // Get next closest segment. - graphic = findNext( graphic, prevPt, aSegList, aTolerance ); + PCB_SHAPE* nextGraphic = findNext( graphic, prevPt, aSegList, aTolerance ); - if( graphic && !( graphic->GetFlags() & SKIP_STRUCT ) ) + if( nextGraphic && !( nextGraphic->GetFlags() & SKIP_STRUCT ) ) { + graphic = nextGraphic; graphic->SetFlags( SKIP_STRUCT ); startCandidates.erase( graphic ); continue; @@ -712,15 +773,15 @@ bool ConvertOutlineToPolygon( std::vector& aSegList, SHAPE_POLY_SET& { break; } - else if( graphic ) // encountered already-used segment, but not at the start + else if( nextGraphic ) // encountered already-used segment, but not at the start { polygonComplete = false; break; } - else // encountered discontinuity + else // encountered discontinuity { - if( aDiscontinuities ) - aDiscontinuities->emplace_back( prevPt ); + if( aErrorHandler ) + (*aErrorHandler)( _( "(not a closed shape)" ), graphic, nullptr, prevPt ); polygonComplete = false; break; @@ -743,21 +804,27 @@ bool ConvertOutlineToPolygon( std::vector& aSegList, SHAPE_POLY_SET& for( ++seg2; seg2; seg2++ ) { - // Check for exact overlapping segments. This is not viewed - // as an intersection below - if( *seg1 == *seg2 || - ( ( *seg1 ).A == ( *seg2 ).B && ( *seg1 ).B == ( *seg2 ).A ) ) + // Check for exact overlapping segments. + if( *seg1 == *seg2 || ( ( *seg1 ).A == ( *seg2 ).B && ( *seg1 ).B == ( *seg2 ).A ) ) { - if( aIntersections ) - aIntersections->emplace_back( ( *seg1 ).A.x, ( *seg1 ).A.y ); + if( aErrorHandler ) + { + BOARD_ITEM* a = fetchOwner( *seg1 ); + BOARD_ITEM* b = fetchOwner( *seg2 ); + (*aErrorHandler)( _( "(self-intersecting)" ), a, b, (wxPoint) ( *seg1 ).A ); + } selfIntersecting = true; } if( boost::optional pt = seg1.Get().Intersect( seg2.Get(), true ) ) { - if( aIntersections ) - aIntersections->emplace_back( (wxPoint) pt.get() ); + if( aErrorHandler ) + { + BOARD_ITEM* a = fetchOwner( *seg1 ); + BOARD_ITEM* b = fetchOwner( *seg2 ); + (*aErrorHandler)( _( "(self-intersecting)" ), a, b, (wxPoint) pt.get() ); + } selfIntersecting = true; } @@ -767,6 +834,7 @@ bool ConvertOutlineToPolygon( std::vector& aSegList, SHAPE_POLY_SET& return !selfIntersecting; } + #include #include @@ -775,8 +843,7 @@ bool ConvertOutlineToPolygon( std::vector& aSegList, SHAPE_POLY_SET& * All contours should be closed, i.e. valid closed polygon vertices */ bool BuildBoardPolygonOutlines( BOARD* aBoard, SHAPE_POLY_SET& aOutlines, int aTolerance, - std::vector* aDiscontinuities, - std::vector* aIntersections ) + OUTLINE_ERROR_HANDLER* aErrorHandler ) { PCB_TYPE_COLLECTOR items; bool success = false; @@ -796,8 +863,7 @@ bool BuildBoardPolygonOutlines( BOARD* aBoard, SHAPE_POLY_SET& aOutlines, int aT if( segList.size() ) { - success = ConvertOutlineToPolygon( segList, aOutlines, aTolerance, aDiscontinuities, - aIntersections ); + success = ConvertOutlineToPolygon( segList, aOutlines, aTolerance, aErrorHandler ); } if( !success || !aOutlines.OutlineCount() ) @@ -989,10 +1055,9 @@ int findEndSegments( SHAPE_LINE_CHAIN& aChain, SEG& aStartSeg, SEG& aEndSeg ) * * If copper is located outside a closed outline, then that outline will be treated * as a hole, and the outer edge will be formed using the bounding box. */ -bool BuildFootprintPolygonOutlines( BOARD* aBoard, SHAPE_POLY_SET& aOutlines, - unsigned int aTolerance, wxString* aErrorText, - std::vector* aDiscontinuities, - std::vector* aIntersections ) +bool BuildFootprintPolygonOutlines( BOARD* aBoard, SHAPE_POLY_SET& aOutlines, int aTolerance, + OUTLINE_ERROR_HANDLER* aErrorHandler ) + { PCB_TYPE_COLLECTOR items; SHAPE_POLY_SET outlines; @@ -1010,8 +1075,7 @@ bool BuildFootprintPolygonOutlines( BOARD* aBoard, SHAPE_POLY_SET& aOutlines, segList.push_back( static_cast( items[ii] ) ); } - bool success = ConvertOutlineToPolygon( segList, outlines, aTolerance, aDiscontinuities, - aIntersections ); + bool success = ConvertOutlineToPolygon( segList, outlines, aTolerance, aErrorHandler ); FOOTPRINT* footprint = aBoard->GetFirstFootprint(); @@ -1019,10 +1083,6 @@ bool BuildFootprintPolygonOutlines( BOARD* aBoard, SHAPE_POLY_SET& aOutlines, if( !footprint ) { wxLogTrace( traceBoardOutline, "No footprint found on board" ); - - if( aErrorText ) - *aErrorText = _( "No footprint loaded" ); - return false; } diff --git a/pcbnew/convert_drawsegment_list_to_polygon.h b/pcbnew/convert_drawsegment_list_to_polygon.h index be0b635ace..029642802c 100644 --- a/pcbnew/convert_drawsegment_list_to_polygon.h +++ b/pcbnew/convert_drawsegment_list_to_polygon.h @@ -34,6 +34,10 @@ class SHAPE_POLY_SET; class wxString; class wxPoint; +typedef +const std::function OUTLINE_ERROR_HANDLER; + /** * Function ConvertOutlineToPolygon * build a polygon (with holes) from a PCB_SHAPE list, which is expected to be @@ -42,14 +46,10 @@ class wxPoint; * @param aSegList the initial list of drawsegments (only lines, circles and arcs). * @param aPolygons will contain the complex polygon. * @param aTolerance is the max error distance when polygonizing a curve (internal units) - * @param aDiscontinuities = an optional array of wxPoint giving the locations of - * discontinuities in the outline - * @param aIntersections = an optional array of wxPoint giving the locations of self- - * intersections in the outline + * @param aErrorHandler = an optional error handler */ bool ConvertOutlineToPolygon( std::vector& aSegList, SHAPE_POLY_SET& aPolygons, - int aTolerance, std::vector* aDiscontinuities = nullptr, - std::vector* aIntersections = nullptr ); + int aTolerance, OUTLINE_ERROR_HANDLER* aErrorHandler = nullptr ); /** @@ -60,6 +60,5 @@ bool ConvertOutlineToPolygon( std::vector& aSegList, SHAPE_POLY_SET& * return true if success, false if a contour is not valid */ extern bool BuildBoardPolygonOutlines( BOARD* aBoard, SHAPE_POLY_SET& aOutlines, int aTolerance, - std::vector* aDiscontinuities = nullptr, - std::vector* aIntersections = nullptr ); + OUTLINE_ERROR_HANDLER* aErrorHandler = nullptr ); diff --git a/pcbnew/dialogs/dialog_footprint_checker.cpp b/pcbnew/dialogs/dialog_footprint_checker.cpp new file mode 100644 index 0000000000..5b1da10c39 --- /dev/null +++ b/pcbnew/dialogs/dialog_footprint_checker.cpp @@ -0,0 +1,186 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2020 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 + + +DIALOG_FOOTPRINT_CHECKER::DIALOG_FOOTPRINT_CHECKER( FOOTPRINT_EDIT_FRAME* aParent ) : + DIALOG_FOOTPRINT_CHECKER_BASE( aParent ), + m_frame( aParent ) +{ + m_markersTreeModel = new RC_TREE_MODEL( m_frame, m_markersDataView ); + m_markersDataView->AssociateModel( m_markersTreeModel ); + + m_markersTreeModel->SetSeverities( -1 ); + + // We use a sdbSizer to get platform-dependent ordering of the action buttons, but + // that requires us to correct the button labels here. + m_sdbSizerOK->SetLabel( _( "Run Checks" ) ); + m_sdbSizerCancel->SetLabel( _( "Close" ) ); + + m_sdbSizerOK->SetDefault(); + GetSizer()->SetSizeHints(this); + Centre(); +} + + +DIALOG_FOOTPRINT_CHECKER::~DIALOG_FOOTPRINT_CHECKER() +{ + m_markersTreeModel->DecRef(); +} + + +bool DIALOG_FOOTPRINT_CHECKER::TransferDataToWindow() +{ + runChecks(); + + return true; +} + + +bool DIALOG_FOOTPRINT_CHECKER::TransferDataFromWindow() +{ + return true; +} + + +void DIALOG_FOOTPRINT_CHECKER::runChecks() +{ + BOARD* board = m_frame->GetBoard(); + FOOTPRINT* footprint = board->GetFirstFootprint(); + wxString msg; + + deleteAllMarkers(); + + if( !footprint ) + { + msg = _( "No footprint loaded." ); + return; + } + + OUTLINE_ERROR_HANDLER errorHandler = + [&]( const wxString& msg, BOARD_ITEM* itemA, BOARD_ITEM* itemB, const wxPoint& pt ) + { + std::shared_ptr drcItem = DRC_ITEM::Create( DRCE_MALFORMED_COURTYARD ); + + drcItem->SetErrorMessage( drcItem->GetErrorText() + wxS( " " ) + msg ); + drcItem->SetItems( itemA, itemB ); + + PCB_MARKER* marker = new PCB_MARKER( drcItem, pt ); + board->Add( marker ); + m_frame->GetCanvas()->GetView()->Add( marker ); + }; + + footprint->BuildPolyCourtyards( &errorHandler ); + + SetMarkersProvider( new BOARD_DRC_ITEMS_PROVIDER( m_frame->GetBoard() ) ); + + WINDOW_THAWER thawer( m_frame ); + + m_frame->GetCanvas()->Refresh(); +} + + +void DIALOG_FOOTPRINT_CHECKER::SetMarkersProvider( RC_ITEMS_PROVIDER* aProvider ) +{ + m_markersTreeModel->SetProvider( aProvider ); +} + + +void DIALOG_FOOTPRINT_CHECKER::OnRunChecksClick( wxCommandEvent& aEvent ) +{ + runChecks(); +} + + +void DIALOG_FOOTPRINT_CHECKER::OnCancelClick( wxCommandEvent& aEvent ) +{ + m_frame->FocusOnItem( nullptr ); + + SetReturnCode( wxID_CANCEL ); + + // Leave the tool to destroy (or not) the dialog + FOOTPRINT_EDITOR_TOOLS* tool = m_frame->GetToolManager()->GetTool(); + tool->DestroyCheckerDialog(); +} + + +void DIALOG_FOOTPRINT_CHECKER::OnClose( wxCloseEvent& aEvent ) +{ + wxCommandEvent dummy; + OnCancelClick( dummy ); +} + + +void DIALOG_FOOTPRINT_CHECKER::OnSelectItem( wxDataViewEvent& aEvent ) +{ + const KIID& itemID = RC_TREE_MODEL::ToUUID( aEvent.GetItem() ); + BOARD_ITEM* item = m_frame->GetBoard()->GetItem( itemID ); + WINDOW_THAWER thawer( m_frame ); + + m_frame->FocusOnItem( item ); + m_frame->GetCanvas()->Refresh(); + + aEvent.Skip(); +} + + +void DIALOG_FOOTPRINT_CHECKER::OnLeftDClickItem( wxMouseEvent& event ) +{ + event.Skip(); + + if( m_markersDataView->GetCurrentItem().IsOk() ) + { + if( !IsModal() ) + Show( false ); + } +} + + +void DIALOG_FOOTPRINT_CHECKER::OnDeleteAllClick( wxCommandEvent& event ) +{ + deleteAllMarkers(); + + WINDOW_THAWER thawer( m_frame ); + + m_frame->GetCanvas()->Refresh(); +} + + +void DIALOG_FOOTPRINT_CHECKER::deleteAllMarkers() +{ + // Clear current selection list to avoid selection of deleted items + m_frame->GetToolManager()->RunAction( PCB_ACTIONS::selectionClear, true ); + + m_markersTreeModel->DeleteItems( false, true, true ); +} + + diff --git a/pcbnew/dialogs/dialog_footprint_checker.h b/pcbnew/dialogs/dialog_footprint_checker.h new file mode 100644 index 0000000000..5afff28ea4 --- /dev/null +++ b/pcbnew/dialogs/dialog_footprint_checker.h @@ -0,0 +1,60 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2010-2014 Jean-Pierre Charras, jean-pierre.charras at wanadoo.fr + * Copyright (C) 1992-2019 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 DIALOG_FOOTPRINT_CHECKER_H +#define DIALOG_FOOTPRINT_CHECKER_H + +#include +#include + +class FOOTPRINT_EDIT_FRAME; + + +class DIALOG_FOOTPRINT_CHECKER: public DIALOG_FOOTPRINT_CHECKER_BASE +{ + FOOTPRINT_EDIT_FRAME* m_frame; + RC_TREE_MODEL* m_markersTreeModel; + + void runChecks(); + void deleteAllMarkers(); + + void OnRunChecksClick( wxCommandEvent& aEvent ) override; + void OnCancelClick( wxCommandEvent& aEvent ) override; + void OnClose( wxCloseEvent& event ) override; + + void OnSelectItem( wxDataViewEvent& event ) override; + void OnLeftDClickItem( wxMouseEvent& event ) override; + void OnDeleteAllClick( wxCommandEvent& event ) override; + + bool TransferDataToWindow() override; + bool TransferDataFromWindow() override; + +public: + DIALOG_FOOTPRINT_CHECKER( FOOTPRINT_EDIT_FRAME* aParent ); + ~DIALOG_FOOTPRINT_CHECKER(); + + void SetMarkersProvider( RC_ITEMS_PROVIDER* aProvider ); +}; + +#endif // DIALOG_FOOTPRINT_CHECKER_H diff --git a/pcbnew/dialogs/dialog_footprint_checker_base.cpp b/pcbnew/dialogs/dialog_footprint_checker_base.cpp new file mode 100644 index 0000000000..3fa0b21110 --- /dev/null +++ b/pcbnew/dialogs/dialog_footprint_checker_base.cpp @@ -0,0 +1,73 @@ +/////////////////////////////////////////////////////////////////////////// +// C++ code generated with wxFormBuilder (version Oct 26 2018) +// http://www.wxformbuilder.org/ +// +// PLEASE DO *NOT* EDIT THIS FILE! +/////////////////////////////////////////////////////////////////////////// + +#include "dialog_footprint_checker_base.h" + +/////////////////////////////////////////////////////////////////////////// + +DIALOG_FOOTPRINT_CHECKER_BASE::DIALOG_FOOTPRINT_CHECKER_BASE( wxWindow* parent, wxWindowID id, const wxString& title, const wxPoint& pos, const wxSize& size, long style ) : DIALOG_SHIM( parent, id, title, pos, size, style ) +{ + this->SetSizeHints( wxDefaultSize, wxDefaultSize ); + + wxBoxSizer* bSizerMain; + bSizerMain = new wxBoxSizer( wxVERTICAL ); + + wxBoxSizer* bUpperSizer; + bUpperSizer = new wxBoxSizer( wxVERTICAL ); + + bUpperSizer->SetMinSize( wxSize( 660,250 ) ); + m_markersDataView = new wxDataViewCtrl( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxDV_NO_HEADER ); + bUpperSizer->Add( m_markersDataView, 1, wxALL|wxEXPAND, 5 ); + + + bSizerMain->Add( bUpperSizer, 1, wxEXPAND|wxTOP|wxRIGHT|wxLEFT, 10 ); + + wxBoxSizer* bLowerSizer; + bLowerSizer = new wxBoxSizer( wxHORIZONTAL ); + + m_DeleteAllMarkersButton = new wxButton( this, wxID_ANY, _("Delete All Markers"), wxDefaultPosition, wxDefaultSize, 0 ); + bLowerSizer->Add( m_DeleteAllMarkersButton, 0, wxALL|wxALIGN_CENTER_VERTICAL, 5 ); + + m_sdbSizer = new wxStdDialogButtonSizer(); + m_sdbSizerOK = new wxButton( this, wxID_OK ); + m_sdbSizer->AddButton( m_sdbSizerOK ); + m_sdbSizerCancel = new wxButton( this, wxID_CANCEL ); + m_sdbSizer->AddButton( m_sdbSizerCancel ); + m_sdbSizer->Realize(); + + bLowerSizer->Add( m_sdbSizer, 1, wxEXPAND|wxALL, 5 ); + + + bSizerMain->Add( bLowerSizer, 0, wxEXPAND|wxLEFT, 10 ); + + + this->SetSizer( bSizerMain ); + this->Layout(); + bSizerMain->Fit( this ); + + this->Centre( wxBOTH ); + + // Connect Events + this->Connect( wxEVT_CLOSE_WINDOW, wxCloseEventHandler( DIALOG_FOOTPRINT_CHECKER_BASE::OnClose ) ); + m_markersDataView->Connect( wxEVT_COMMAND_DATAVIEW_SELECTION_CHANGED, wxDataViewEventHandler( DIALOG_FOOTPRINT_CHECKER_BASE::OnSelectItem ), NULL, this ); + m_markersDataView->Connect( wxEVT_LEFT_DCLICK, wxMouseEventHandler( DIALOG_FOOTPRINT_CHECKER_BASE::OnLeftDClickItem ), NULL, this ); + m_DeleteAllMarkersButton->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( DIALOG_FOOTPRINT_CHECKER_BASE::OnDeleteAllClick ), NULL, this ); + m_sdbSizerCancel->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( DIALOG_FOOTPRINT_CHECKER_BASE::OnCancelClick ), NULL, this ); + m_sdbSizerOK->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( DIALOG_FOOTPRINT_CHECKER_BASE::OnRunChecksClick ), NULL, this ); +} + +DIALOG_FOOTPRINT_CHECKER_BASE::~DIALOG_FOOTPRINT_CHECKER_BASE() +{ + // Disconnect Events + this->Disconnect( wxEVT_CLOSE_WINDOW, wxCloseEventHandler( DIALOG_FOOTPRINT_CHECKER_BASE::OnClose ) ); + m_markersDataView->Disconnect( wxEVT_COMMAND_DATAVIEW_SELECTION_CHANGED, wxDataViewEventHandler( DIALOG_FOOTPRINT_CHECKER_BASE::OnSelectItem ), NULL, this ); + m_markersDataView->Disconnect( wxEVT_LEFT_DCLICK, wxMouseEventHandler( DIALOG_FOOTPRINT_CHECKER_BASE::OnLeftDClickItem ), NULL, this ); + m_DeleteAllMarkersButton->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( DIALOG_FOOTPRINT_CHECKER_BASE::OnDeleteAllClick ), NULL, this ); + m_sdbSizerCancel->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( DIALOG_FOOTPRINT_CHECKER_BASE::OnCancelClick ), NULL, this ); + m_sdbSizerOK->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( DIALOG_FOOTPRINT_CHECKER_BASE::OnRunChecksClick ), NULL, this ); + +} diff --git a/pcbnew/dialogs/dialog_footprint_checker_base.fbp b/pcbnew/dialogs/dialog_footprint_checker_base.fbp new file mode 100644 index 0000000000..5da807adc3 --- /dev/null +++ b/pcbnew/dialogs/dialog_footprint_checker_base.fbp @@ -0,0 +1,209 @@ + + + + + + C++ + 1 + source_name + 0 + 0 + res + UTF-8 + connect + dialog_footprint_checker_base + 1000 + none + + 1 + dialog_footprint_checker + + . + + 1 + 1 + 1 + 1 + UI + 0 + 0 + + 0 + wxAUI_MGR_DEFAULT + + wxBOTH + + 1 + 1 + impl_virtual + + + + 0 + wxID_ANY + + + DIALOG_FOOTPRINT_CHECKER_BASE + + -1,-1 + wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER + DIALOG_SHIM; dialog_shim.h + Footprint Checker + + + + + OnClose + + + bSizerMain + wxVERTICAL + none + + 10 + wxEXPAND|wxTOP|wxRIGHT|wxLEFT + 1 + + 660,250 + bUpperSizer + wxVERTICAL + none + + 5 + wxALL|wxEXPAND + 1 + + + + 1 + 1 + + + 0 + wxID_ANY + + + m_markersDataView + protected + + + wxDV_NO_HEADER + ; ; forward_declare + + + + + OnSelectItem + OnLeftDClickItem + + + + + + 10 + wxEXPAND|wxLEFT + 0 + + + bLowerSizer + wxHORIZONTAL + none + + 5 + wxALL|wxALIGN_CENTER_VERTICAL + 0 + + 1 + 1 + 1 + 1 + + + + + + + + + 1 + 0 + 1 + + 1 + + 0 + 0 + + Dock + 0 + Left + 1 + + 1 + + + 0 + 0 + wxID_ANY + Delete All Markers + + 0 + + 0 + + + 0 + + 1 + m_DeleteAllMarkersButton + 1 + + + protected + 1 + + + + Resizable + 1 + + + ; forward_declare + 0 + + + wxFILTER_NONE + wxDefaultValidator + + + + + OnDeleteAllClick + + + + 5 + wxEXPAND|wxALL + 1 + + 0 + 1 + 0 + 0 + 0 + 1 + 0 + 0 + + m_sdbSizer + protected + OnCancelClick + OnRunChecksClick + + + + + + + + diff --git a/pcbnew/dialogs/dialog_footprint_checker_base.h b/pcbnew/dialogs/dialog_footprint_checker_base.h new file mode 100644 index 0000000000..8c7acb5f27 --- /dev/null +++ b/pcbnew/dialogs/dialog_footprint_checker_base.h @@ -0,0 +1,59 @@ +/////////////////////////////////////////////////////////////////////////// +// C++ code generated with wxFormBuilder (version Oct 26 2018) +// http://www.wxformbuilder.org/ +// +// PLEASE DO *NOT* EDIT THIS FILE! +/////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include +#include +#include +#include "dialog_shim.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/////////////////////////////////////////////////////////////////////////// + + +/////////////////////////////////////////////////////////////////////////////// +/// Class DIALOG_FOOTPRINT_CHECKER_BASE +/////////////////////////////////////////////////////////////////////////////// +class DIALOG_FOOTPRINT_CHECKER_BASE : public DIALOG_SHIM +{ + private: + + protected: + wxDataViewCtrl* m_markersDataView; + wxButton* m_DeleteAllMarkersButton; + wxStdDialogButtonSizer* m_sdbSizer; + wxButton* m_sdbSizerOK; + wxButton* m_sdbSizerCancel; + + // Virtual event handlers, overide them in your derived class + virtual void OnClose( wxCloseEvent& event ) { event.Skip(); } + virtual void OnSelectItem( wxDataViewEvent& event ) { event.Skip(); } + virtual void OnLeftDClickItem( wxMouseEvent& event ) { event.Skip(); } + virtual void OnDeleteAllClick( wxCommandEvent& event ) { event.Skip(); } + virtual void OnCancelClick( wxCommandEvent& event ) { event.Skip(); } + virtual void OnRunChecksClick( wxCommandEvent& event ) { event.Skip(); } + + + public: + + DIALOG_FOOTPRINT_CHECKER_BASE( wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = _("Footprint Checker"), const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxSize( -1,-1 ), long style = wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER ); + ~DIALOG_FOOTPRINT_CHECKER_BASE(); + +}; + diff --git a/pcbnew/drc/drc_test_provider_courtyard_clearance.cpp b/pcbnew/drc/drc_test_provider_courtyard_clearance.cpp index f4ea65924c..b1eee21371 100644 --- a/pcbnew/drc/drc_test_provider_courtyard_clearance.cpp +++ b/pcbnew/drc/drc_test_provider_courtyard_clearance.cpp @@ -92,35 +92,17 @@ void DRC_TEST_PROVIDER_COURTYARD_CLEARANCE::testFootprintCourtyardDefinitions() if( m_drcEngine->IsErrorLimitExceeded( DRCE_MALFORMED_COURTYARD) ) continue; - std::vector discontinuities; - std::vector intersections; + OUTLINE_ERROR_HANDLER errorHandler = + [&]( const wxString& msg, BOARD_ITEM* , BOARD_ITEM* , const wxPoint& pt ) + { + std::shared_ptr drcItem = DRC_ITEM::Create( DRCE_MALFORMED_COURTYARD ); + drcItem->SetErrorMessage( drcItem->GetErrorText() + wxS( " " ) + msg ); + drcItem->SetItems( footprint ); + reportViolation( drcItem, pt ); + }; - // Run courtyard test again to get error locations - footprint->BuildPolyCourtyards( &discontinuities, &intersections); - - for( wxPoint pt : discontinuities ) - { - std::shared_ptr drcItem = DRC_ITEM::Create( DRCE_MALFORMED_COURTYARD ); - - m_msg.Printf( _( "(not a closed shape)" ) ); - - drcItem->SetErrorMessage( drcItem->GetErrorText() + wxS( " " ) + m_msg ); - drcItem->SetItems( footprint ); - - reportViolation( drcItem, pt ); - } - - for( wxPoint pt : intersections ) - { - std::shared_ptr drcItem = DRC_ITEM::Create( DRCE_MALFORMED_COURTYARD ); - - m_msg.Printf( _( "(self-intersecting)" ) ); - - drcItem->SetErrorMessage( drcItem->GetErrorText() + wxS( " " ) + m_msg ); - drcItem->SetItems( footprint ); - - reportViolation( drcItem, pt ); - } + // Re-run courtyard tests to generate DRC_ITEMs + footprint->BuildPolyCourtyards( &errorHandler ); } else if( footprint->GetPolyCourtyardFront().OutlineCount() == 0 && footprint->GetPolyCourtyardBack().OutlineCount() == 0 ) diff --git a/pcbnew/drc/drc_test_provider_misc.cpp b/pcbnew/drc/drc_test_provider_misc.cpp index adb8774c92..d6f16d3057 100644 --- a/pcbnew/drc/drc_test_provider_misc.cpp +++ b/pcbnew/drc/drc_test_provider_misc.cpp @@ -80,48 +80,31 @@ private: void DRC_TEST_PROVIDER_MISC::testOutline() { - std::vector discontinuities; - std::vector intersections; + OUTLINE_ERROR_HANDLER errorHandler = + [&]( const wxString& msg, BOARD_ITEM* itemA, BOARD_ITEM* itemB, const wxPoint& pt ) + { + std::shared_ptr drcItem = DRC_ITEM::Create( DRCE_INVALID_OUTLINE ); + + drcItem->SetErrorMessage( drcItem->GetErrorText() + wxS( " " ) + msg ); + drcItem->SetItems( itemA, itemB ); + + reportViolation( drcItem, pt ); + }; SHAPE_POLY_SET boardOutlines; - if(! m_board->GetBoardPolygonOutlines( boardOutlines, &discontinuities, &intersections ) ) + m_board->GetBoardPolygonOutlines( boardOutlines, &errorHandler ); + + if( boardOutlines.IsEmpty() ) { - if( boardOutlines.IsEmpty() ) - { - std::shared_ptr drcItem = DRC_ITEM::Create( DRCE_INVALID_OUTLINE ); + std::shared_ptr drcItem = DRC_ITEM::Create( DRCE_INVALID_OUTLINE ); - m_msg.Printf( _( "(no edges found on Edge.Cuts layer)" ) ); + m_msg.Printf( _( "(no edges found on Edge.Cuts layer)" ) ); - drcItem->SetErrorMessage( drcItem->GetErrorText() + wxS( " " ) + m_msg ); - drcItem->SetItems( m_board ); + drcItem->SetErrorMessage( drcItem->GetErrorText() + wxS( " " ) + m_msg ); + drcItem->SetItems( m_board ); - reportViolation( drcItem, m_board->GetBoundingBox().Centre() ); - } - - for( wxPoint pt : discontinuities ) - { - std::shared_ptr drcItem = DRC_ITEM::Create( DRCE_INVALID_OUTLINE ); - - m_msg.Printf( _( "(not a closed shape)" ) ); - - drcItem->SetErrorMessage( drcItem->GetErrorText() + wxS( " " ) + m_msg ); - drcItem->SetItems( m_board ); - - reportViolation( drcItem, pt ); - } - - for( wxPoint pt : intersections ) - { - std::shared_ptr drcItem = DRC_ITEM::Create( DRCE_INVALID_OUTLINE ); - - m_msg.Printf( _( "(self-intersecting)" ) ); - - drcItem->SetErrorMessage( drcItem->GetErrorText() + wxS( " " ) + m_msg ); - drcItem->SetItems( m_board ); - - reportViolation( drcItem, pt ); - } + reportViolation( drcItem, m_board->GetBoundingBox().Centre() ); } } diff --git a/pcbnew/footprint.cpp b/pcbnew/footprint.cpp index f6691a8244..3fa06da7c5 100644 --- a/pcbnew/footprint.cpp +++ b/pcbnew/footprint.cpp @@ -1693,8 +1693,7 @@ std::shared_ptr FOOTPRINT::GetEffectiveShape( PCB_LAYER_ID aLayer ) const } -void FOOTPRINT::BuildPolyCourtyards( std::vector* aDiscontinuities, - std::vector* aIntersections ) +void FOOTPRINT::BuildPolyCourtyards( OUTLINE_ERROR_HANDLER* aErrorHandler ) { m_poly_courtyard_front.RemoveAllContours(); m_poly_courtyard_back.RemoveAllContours(); @@ -1718,19 +1717,13 @@ void FOOTPRINT::BuildPolyCourtyards( std::vector* aDiscontinuities, if( !list_front.size() && !list_back.size() ) return; - #define ARC_ERROR_MAX 0.02 /* error max in mm to approximate a arc by segments */ + constexpr int errorMax = Millimeter2iu( 0.02 ); /* error max for polygonization */ - if( !ConvertOutlineToPolygon( list_front, m_poly_courtyard_front, Millimeter2iu( ARC_ERROR_MAX ), - aDiscontinuities, aIntersections ) ) - { + if( !ConvertOutlineToPolygon( list_front, m_poly_courtyard_front, errorMax, aErrorHandler ) ) SetFlags( MALFORMED_F_COURTYARD ); - } - if( !ConvertOutlineToPolygon( list_back, m_poly_courtyard_back, Millimeter2iu( ARC_ERROR_MAX ), - aDiscontinuities, aIntersections ) ) - { + if( !ConvertOutlineToPolygon( list_back, m_poly_courtyard_back, errorMax, aErrorHandler ) ) SetFlags( MALFORMED_B_COURTYARD ); - } } diff --git a/pcbnew/footprint.h b/pcbnew/footprint.h index ac5e61f8f7..a54771fd34 100644 --- a/pcbnew/footprint.h +++ b/pcbnew/footprint.h @@ -35,7 +35,8 @@ #include #include -#include "zones.h" +#include +#include #include #include #include @@ -669,8 +670,7 @@ public: * @remark sets the MALFORMED_F_COURTYARD and MALFORMED_B_COURTYARD status flags if the given * courtyard layer does not contain a (single) closed shape */ - void BuildPolyCourtyards( std::vector* aDiscontinuities = nullptr, - std::vector* aIntersections = nullptr ); + void BuildPolyCourtyards( OUTLINE_ERROR_HANDLER* aErrorHandler = nullptr ); virtual std::shared_ptr GetEffectiveShape( PCB_LAYER_ID aLayer = UNDEFINED_LAYER ) const override; diff --git a/pcbnew/menubar_footprint_editor.cpp b/pcbnew/menubar_footprint_editor.cpp index 4ef9c65ce8..31cbe5a6a4 100644 --- a/pcbnew/menubar_footprint_editor.cpp +++ b/pcbnew/menubar_footprint_editor.cpp @@ -206,6 +206,9 @@ void FOOTPRINT_EDIT_FRAME::ReCreateMenuBar() inspectMenu->Add( ACTIONS::measureTool ); + inspectMenu->AppendSeparator(); + inspectMenu->Add( PCB_ACTIONS::checkFootprint ); + //-- Tools menu -------------------------------------------------------- // diff --git a/pcbnew/toolbars_footprint_editor.cpp b/pcbnew/toolbars_footprint_editor.cpp index 17fa64a912..207ec34b70 100644 --- a/pcbnew/toolbars_footprint_editor.cpp +++ b/pcbnew/toolbars_footprint_editor.cpp @@ -82,6 +82,7 @@ void FOOTPRINT_EDIT_FRAME::ReCreateHToolbar() m_mainToolBar->AddScaledSeparator( this ); m_mainToolBar->Add( PCB_ACTIONS::footprintProperties ); m_mainToolBar->Add( PCB_ACTIONS::defaultPadProperties ); + m_mainToolBar->Add( PCB_ACTIONS::checkFootprint ); m_mainToolBar->AddScaledSeparator( this ); m_mainToolBar->AddTool( ID_LOAD_FOOTPRINT_FROM_BOARD, wxEmptyString, diff --git a/pcbnew/tools/footprint_editor_tools.cpp b/pcbnew/tools/footprint_editor_tools.cpp index d8c34fbe5f..c4c730da23 100644 --- a/pcbnew/tools/footprint_editor_tools.cpp +++ b/pcbnew/tools/footprint_editor_tools.cpp @@ -36,12 +36,16 @@ #include #include #include +#include #include #include +#include + FOOTPRINT_EDITOR_TOOLS::FOOTPRINT_EDITOR_TOOLS() : PCB_TOOL_BASE( "pcbnew.ModuleEditor" ), - m_frame( nullptr ) + m_frame( nullptr ), + m_checkerDialog( nullptr ) { } @@ -54,6 +58,9 @@ FOOTPRINT_EDITOR_TOOLS::~FOOTPRINT_EDITOR_TOOLS() void FOOTPRINT_EDITOR_TOOLS::Reset( RESET_REASON aReason ) { m_frame = getEditFrame(); + + if( m_checkerDialog ) + DestroyCheckerDialog(); } @@ -454,6 +461,33 @@ int FOOTPRINT_EDITOR_TOOLS::CleanupGraphics( const TOOL_EVENT& aEvent ) } +int FOOTPRINT_EDITOR_TOOLS::CheckFootprint( const TOOL_EVENT& aEvent ) +{ + if( !m_checkerDialog ) + { + m_checkerDialog = new DIALOG_FOOTPRINT_CHECKER( m_frame ); + + m_checkerDialog->Show( true ); + } + else // The dialog is just not visible (because the user has double clicked on an error item) + { + m_checkerDialog->SetMarkersProvider( new BOARD_DRC_ITEMS_PROVIDER( m_frame->GetBoard() ) ); + m_checkerDialog->Show( true ); + } + return 0; +} + + +void FOOTPRINT_EDITOR_TOOLS::DestroyCheckerDialog() +{ + if( m_checkerDialog ) + { + m_checkerDialog->Destroy(); + m_checkerDialog = nullptr; + } +} + + void FOOTPRINT_EDITOR_TOOLS::setTransitions() { Go( &FOOTPRINT_EDITOR_TOOLS::NewFootprint, PCB_ACTIONS::newFootprint.MakeEvent() ); @@ -476,6 +510,8 @@ void FOOTPRINT_EDITOR_TOOLS::setTransitions() Go( &FOOTPRINT_EDITOR_TOOLS::CleanupGraphics, PCB_ACTIONS::cleanupGraphics.MakeEvent() ); + Go( &FOOTPRINT_EDITOR_TOOLS::CheckFootprint, PCB_ACTIONS::checkFootprint.MakeEvent() ); + Go( &FOOTPRINT_EDITOR_TOOLS::PinLibrary, ACTIONS::pinLibrary.MakeEvent() ); Go( &FOOTPRINT_EDITOR_TOOLS::UnpinLibrary, ACTIONS::unpinLibrary.MakeEvent() ); Go( &FOOTPRINT_EDITOR_TOOLS::ToggleFootprintTree, PCB_ACTIONS::toggleFootprintTree.MakeEvent() ); diff --git a/pcbnew/tools/footprint_editor_tools.h b/pcbnew/tools/footprint_editor_tools.h index ace47f911a..002f137b43 100644 --- a/pcbnew/tools/footprint_editor_tools.h +++ b/pcbnew/tools/footprint_editor_tools.h @@ -29,7 +29,7 @@ class FOOTPRINT_EDIT_FRAME; - +class DIALOG_FOOTPRINT_CHECKER; /** * FOOTPRINT_EDITOR_TOOLS @@ -69,6 +69,9 @@ public: int CleanupGraphics( const TOOL_EVENT& aEvent ); + int CheckFootprint( const TOOL_EVENT& aEvent ); + void DestroyCheckerDialog(); + /** * Edit the properties used for new pad creation. */ @@ -80,6 +83,7 @@ private: private: FOOTPRINT_EDIT_FRAME* m_frame; + DIALOG_FOOTPRINT_CHECKER* m_checkerDialog; // A private clipboard for cut/copy/past of an entire footprint std::unique_ptr m_copiedFootprint; diff --git a/pcbnew/tools/pcb_actions.cpp b/pcbnew/tools/pcb_actions.cpp index 5dd362e8fe..d34d67f5df 100644 --- a/pcbnew/tools/pcb_actions.cpp +++ b/pcbnew/tools/pcb_actions.cpp @@ -382,6 +382,10 @@ TOOL_ACTION PCB_ACTIONS::footprintProperties( "pcbnew.ModuleEditor.footprintProp _( "Footprint Properties..." ), "", module_options_xpm ); +TOOL_ACTION PCB_ACTIONS::checkFootprint( "pcbnew.ModuleEditor.checkFootprint", + AS_GLOBAL, 0, "", + _( "Footprint Checker" ), _( "Show the footprint checker window" ), + erc_xpm ); // GLOBAL_EDIT_TOOL // diff --git a/pcbnew/tools/pcb_actions.h b/pcbnew/tools/pcb_actions.h index 1ee97590fb..b020cfe85b 100644 --- a/pcbnew/tools/pcb_actions.h +++ b/pcbnew/tools/pcb_actions.h @@ -368,6 +368,8 @@ public: static TOOL_ACTION footprintProperties; static TOOL_ACTION defaultPadProperties; + static TOOL_ACTION checkFootprint; + /// Activation of the drawing tool (placing a PAD) static TOOL_ACTION placePad;