/* * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2019-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 #include #include #include #include #include #include #include #include #include #include BOARD_INSPECTION_TOOL::BOARD_INSPECTION_TOOL() : PCB_TOOL_BASE( "pcbnew.InspectionTool" ), m_frame( nullptr ) { m_dynamicData = nullptr; } class NET_CONTEXT_MENU : public ACTION_MENU { public: NET_CONTEXT_MENU() : ACTION_MENU( true ) { SetIcon( BITMAPS::show_ratsnest ); SetTitle( _( "Net Inspection Tools" ) ); Add( PCB_ACTIONS::showNetInRatsnest ); Add( PCB_ACTIONS::hideNetInRatsnest ); AppendSeparator(); Add( PCB_ACTIONS::highlightNetSelection ); Add( PCB_ACTIONS::clearHighlight ); } private: ACTION_MENU* create() const override { return new NET_CONTEXT_MENU(); } }; bool BOARD_INSPECTION_TOOL::Init() { PCB_SELECTION_TOOL* selectionTool = m_toolMgr->GetTool(); std::shared_ptr netSubMenu = std::make_shared(); netSubMenu->SetTool( this ); // Only show the net menu if all items in the selection are connectable auto showNetMenuFunc = []( const SELECTION& aSelection ) { if( aSelection.Empty() ) return false; for( const EDA_ITEM* item : aSelection ) { switch( item->Type() ) { case PCB_TRACE_T: case PCB_ARC_T: case PCB_VIA_T: case PCB_PAD_T: case PCB_ZONE_T: continue; case PCB_SHAPE_T: { if( !static_cast( item )->IsOnCopperLayer() ) return false; else continue; } default: return false; } } return true; }; CONDITIONAL_MENU& menu = selectionTool->GetToolMenu().GetMenu(); selectionTool->GetToolMenu().RegisterSubMenu( netSubMenu ); menu.AddMenu( netSubMenu.get(), showNetMenuFunc, 100 ); return true; } void BOARD_INSPECTION_TOOL::Reset( RESET_REASON aReason ) { m_frame = getEditFrame(); } int BOARD_INSPECTION_TOOL::ShowBoardStatistics( const TOOL_EVENT& aEvent ) { DIALOG_BOARD_STATISTICS dialog( m_frame ); dialog.ShowModal(); return 0; } std::unique_ptr BOARD_INSPECTION_TOOL::makeDRCEngine( bool* aCompileError, bool* aCourtyardError ) { auto engine = std::make_unique( m_frame->GetBoard(), &m_frame->GetBoard()->GetDesignSettings() ); try { engine->InitEngine( m_frame->GetDesignRulesPath() ); } catch( PARSE_ERROR& ) { if( aCompileError ) *aCompileError = true; } for( ZONE* zone : m_frame->GetBoard()->Zones() ) zone->CacheBoundingBox(); for( FOOTPRINT* footprint : m_frame->GetBoard()->Footprints() ) { for( ZONE* zone : footprint->Zones() ) zone->CacheBoundingBox(); footprint->BuildCourtyardCaches(); if( aCourtyardError && ( footprint->GetFlags() & MALFORMED_COURTYARDS ) != 0 ) *aCourtyardError = true; } return engine; } bool isNPTHPad( BOARD_ITEM* aItem ) { return aItem->Type() == PCB_PAD_T && static_cast( aItem )->GetAttribute() == PAD_ATTRIB::NPTH; } wxString BOARD_INSPECTION_TOOL::getItemDescription( BOARD_ITEM* aItem ) { // Null items have no description if( !aItem ) return wxString(); wxString msg = aItem->GetItemDescription( m_frame ); if( aItem->IsConnected() && !isNPTHPad( aItem ) ) { BOARD_CONNECTED_ITEM* cItem = static_cast( aItem ); msg += wxS( " " ) + wxString::Format( _( "[netclass %s]" ), cItem->GetEffectiveNetClass()->GetName() ); } return msg; }; void BOARD_INSPECTION_TOOL::reportCompileError( REPORTER* r ) { r->Report( "" ); r->Report( _( "Report incomplete: could not compile custom design rules." ) + wxS( "  " ) + wxS( "" ) + _( "Show design rules." ) + wxS( "" ) ); } void BOARD_INSPECTION_TOOL::reportHeader( const wxString& aTitle, BOARD_ITEM* a, REPORTER* r ) { r->Report( wxT( "" ) + EscapeHTML( aTitle ) + wxT( "" ) ); r->Report( wxT( "
  • " ) + EscapeHTML( getItemDescription( a ) ) + wxT( "
" ) ); } void BOARD_INSPECTION_TOOL::reportHeader( const wxString& aTitle, BOARD_ITEM* a, BOARD_ITEM* b, REPORTER* r ) { r->Report( wxT( "" ) + EscapeHTML( aTitle ) + wxT( "" ) ); r->Report( wxT( "
  • " ) + EscapeHTML( getItemDescription( a ) ) + wxT( "
  • " ) + wxT( "
  • " ) + EscapeHTML( getItemDescription( b ) ) + wxT( "
" ) ); } void BOARD_INSPECTION_TOOL::reportHeader( const wxString& aTitle, BOARD_ITEM* a, BOARD_ITEM* b, PCB_LAYER_ID aLayer, REPORTER* r ) { wxString layerStr = _( "Layer" ) + wxS( " " ) + m_frame->GetBoard()->GetLayerName( aLayer ); r->Report( wxT( "" ) + EscapeHTML( aTitle ) + wxT( "" ) ); r->Report( wxT( "
  • " ) + EscapeHTML( layerStr ) + wxT( "
  • " ) + wxT( "
  • " ) + EscapeHTML( getItemDescription( a ) ) + wxT( "
  • " ) + wxT( "
  • " ) + EscapeHTML( getItemDescription( b ) ) + wxT( "
" ) ); } wxString reportMin( PCB_BASE_FRAME* aFrame, DRC_CONSTRAINT& aConstraint ) { if( aConstraint.m_Value.HasMin() ) return aFrame->StringFromValue( aConstraint.m_Value.Min(), true ); else return wxT( "" ) + _( "undefined" ) + wxT( "" ); } wxString reportOpt( PCB_BASE_FRAME* aFrame, DRC_CONSTRAINT& aConstraint ) { if( aConstraint.m_Value.HasOpt() ) return aFrame->StringFromValue( aConstraint.m_Value.Opt(), true ); else return wxT( "" ) + _( "undefined" ) + wxT( "" ); } wxString reportMax( PCB_BASE_FRAME* aFrame, DRC_CONSTRAINT& aConstraint ) { if( aConstraint.m_Value.HasMax() ) return aFrame->StringFromValue( aConstraint.m_Value.Max(), true ); else return wxT( "" ) + _( "undefined" ) + wxT( "" ); } wxString BOARD_INSPECTION_TOOL::InspectDRCErrorMenuText( const std::shared_ptr& aDRCItem ) { auto menuDescription = [&]( const TOOL_ACTION& aAction ) { wxString menuItemLabel = aAction.GetMenuLabel(); wxMenuBar* menuBar = m_frame->GetMenuBar(); for( size_t ii = 0; ii < menuBar->GetMenuCount(); ++ii ) { for( wxMenuItem* menuItem : menuBar->GetMenu( ii )->GetMenuItems() ) { if( menuItem->GetItemLabelText() == menuItemLabel ) { wxString menuTitleLabel = menuBar->GetMenuLabelText( ii ); menuTitleLabel.Replace( wxS( "&" ), wxS( "&&" ) ); menuItemLabel.Replace( wxS( "&" ), wxS( "&&" ) ); return wxString::Format( _( "Run %s > %s" ), menuTitleLabel, menuItemLabel ); } } } return wxString::Format( _( "Run %s" ), aAction.GetFriendlyName() ); }; if( aDRCItem->GetErrorCode() == DRCE_CLEARANCE || aDRCItem->GetErrorCode() == DRCE_EDGE_CLEARANCE || aDRCItem->GetErrorCode() == DRCE_HOLE_CLEARANCE || aDRCItem->GetErrorCode() == DRCE_DRILLED_HOLES_TOO_CLOSE ) { return menuDescription( PCB_ACTIONS::inspectClearance ); } else if( aDRCItem->GetErrorCode() == DRCE_TEXT_HEIGHT || aDRCItem->GetErrorCode() == DRCE_TEXT_THICKNESS || aDRCItem->GetErrorCode() == DRCE_DIFF_PAIR_UNCOUPLED_LENGTH_TOO_LONG || aDRCItem->GetErrorCode() == DRCE_TRACK_WIDTH || aDRCItem->GetErrorCode() == DRCE_VIA_DIAMETER || aDRCItem->GetErrorCode() == DRCE_ANNULAR_WIDTH || aDRCItem->GetErrorCode() == DRCE_DRILL_OUT_OF_RANGE || aDRCItem->GetErrorCode() == DRCE_MICROVIA_DRILL_OUT_OF_RANGE || aDRCItem->GetErrorCode() == DRCE_CONNECTION_WIDTH || aDRCItem->GetErrorCode() == DRCE_ASSERTION_FAILURE ) { return menuDescription( PCB_ACTIONS::inspectConstraints ); } else if( aDRCItem->GetErrorCode() == DRCE_LIB_FOOTPRINT_MISMATCH ) { return menuDescription( PCB_ACTIONS::diffFootprint ); } else if( aDRCItem->GetErrorCode() == DRCE_DANGLING_TRACK || aDRCItem->GetErrorCode() == DRCE_DANGLING_VIA ) { return menuDescription( PCB_ACTIONS::cleanupTracksAndVias ); } return wxEmptyString; } void BOARD_INSPECTION_TOOL::InspectDRCError( const std::shared_ptr& aDRCItem ) { wxCHECK( m_frame, /* void */ ); BOARD_ITEM* a = m_frame->GetBoard()->GetItem( aDRCItem->GetMainItemID() ); BOARD_ITEM* b = m_frame->GetBoard()->GetItem( aDRCItem->GetAuxItemID() ); BOARD_CONNECTED_ITEM* ac = dynamic_cast( a ); BOARD_CONNECTED_ITEM* bc = dynamic_cast( b ); PCB_LAYER_ID layer = m_frame->GetActiveLayer(); if( aDRCItem->GetErrorCode() == DRCE_LIB_FOOTPRINT_MISMATCH ) { if( FOOTPRINT* footprint = dynamic_cast( a ) ) DiffFootprint( footprint ); return; } else if( aDRCItem->GetErrorCode() == DRCE_DANGLING_TRACK || aDRCItem->GetErrorCode() == DRCE_DANGLING_VIA ) { m_toolMgr->RunAction( PCB_ACTIONS::cleanupTracksAndVias ); return; } DIALOG_BOOK_REPORTER* dialog = m_frame->GetInspectDrcErrorDialog(); wxCHECK( dialog, /* void */ ); dialog->DeleteAllPages(); bool compileError = false; bool courtyardError = false; std::unique_ptr drcEngine = makeDRCEngine( &compileError, &courtyardError ); WX_HTML_REPORT_BOX* r = nullptr; DRC_CONSTRAINT constraint; int clearance = 0; wxString clearanceStr; switch( aDRCItem->GetErrorCode() ) { case DRCE_DIFF_PAIR_UNCOUPLED_LENGTH_TOO_LONG: { for( KIID id : aDRCItem->GetIDs() ) { bc = dynamic_cast( m_frame->GetBoard()->GetItem( id ) ); if( ac && bc && ac->GetNetCode() != bc->GetNetCode() ) break; } r = dialog->AddHTMLPage( _( "Uncoupled Length" ) ); reportHeader( _( "Diff pair uncoupled length resolution for:" ), ac, bc, r ); if( compileError ) reportCompileError( r ); constraint = drcEngine->EvalRules( MAX_UNCOUPLED_CONSTRAINT, a, b, layer, r ); r->Report( "" ); r->Report( wxString::Format( _( "Resolved max uncoupled length: %s." ), reportMax( m_frame, constraint ) ) ); break; } case DRCE_TEXT_HEIGHT: r = dialog->AddHTMLPage( _( "Text Height" ) ); reportHeader( _( "Text height resolution for:" ), a, r ); if( compileError ) reportCompileError( r ); constraint = drcEngine->EvalRules( TEXT_HEIGHT_CONSTRAINT, a, b, layer, r ); r->Report( "" ); r->Report( wxString::Format( _( "Resolved height constraints: min %s; max %s." ), reportMin( m_frame, constraint ), reportMax( m_frame, constraint ) ) ); break; case DRCE_TEXT_THICKNESS: r = dialog->AddHTMLPage( _( "Text Thickness" ) ); reportHeader( _( "Text thickness resolution for:" ), a, r ); if( compileError ) reportCompileError( r ); constraint = drcEngine->EvalRules( TEXT_THICKNESS_CONSTRAINT, a, b, layer, r ); r->Report( "" ); r->Report( wxString::Format( _( "Resolved thickness constraints: min %s; max %s." ), reportMin( m_frame, constraint ), reportMax( m_frame, constraint ) ) ); break; case DRCE_TRACK_WIDTH: r = dialog->AddHTMLPage( _( "Track Width" ) ); reportHeader( _( "Track width resolution for:" ), a, r ); if( compileError ) reportCompileError( r ); constraint = drcEngine->EvalRules( TRACK_WIDTH_CONSTRAINT, a, b, layer, r ); r->Report( "" ); r->Report( wxString::Format( _( "Resolved width constraints: min %s; max %s." ), reportMin( m_frame, constraint ), reportMax( m_frame, constraint ) ) ); break; case DRCE_CONNECTION_WIDTH: r = dialog->AddHTMLPage( _( "Connection Width" ) ); reportHeader( _( "Connection width resolution for:" ), a, b, r ); if( compileError ) reportCompileError( r ); constraint = drcEngine->EvalRules( CONNECTION_WIDTH_CONSTRAINT, a, b, layer, r ); r->Report( "" ); r->Report( wxString::Format( _( "Resolved min connection width: %s." ), reportMin( m_frame, constraint ) ) ); break; case DRCE_VIA_DIAMETER: r = dialog->AddHTMLPage( _( "Via Diameter" ) ); reportHeader( _( "Via diameter resolution for:" ), a, r ); if( compileError ) reportCompileError( r ); constraint = drcEngine->EvalRules( VIA_DIAMETER_CONSTRAINT, a, b, layer, r ); r->Report( "" ); r->Report( wxString::Format( _( "Resolved diameter constraints: min %s; max %s." ), reportMin( m_frame, constraint ), reportMax( m_frame, constraint ) ) ); break; case DRCE_ANNULAR_WIDTH: r = dialog->AddHTMLPage( _( "Via Annulus" ) ); reportHeader( _( "Via annular width resolution for:" ), a, r ); if( compileError ) reportCompileError( r ); constraint = drcEngine->EvalRules( ANNULAR_WIDTH_CONSTRAINT, a, b, layer, r ); r->Report( "" ); r->Report( wxString::Format( _( "Resolved annular width constraints: min %s; max %s." ), reportMin( m_frame, constraint ), reportMax( m_frame, constraint ) ) ); break; case DRCE_DRILL_OUT_OF_RANGE: case DRCE_MICROVIA_DRILL_OUT_OF_RANGE: r = dialog->AddHTMLPage( _( "Hole Size" ) ); reportHeader( _( "Hole size resolution for:" ), a, r ); if( compileError ) reportCompileError( r ); constraint = drcEngine->EvalRules( HOLE_SIZE_CONSTRAINT, a, b, layer, r ); r->Report( "" ); r->Report( wxString::Format( _( "Resolved hole size constraints: min %s; max %s." ), reportMin( m_frame, constraint ), reportMax( m_frame, constraint ) ) ); break; case DRCE_HOLE_CLEARANCE: r = dialog->AddHTMLPage( _( "Hole Clearance" ) ); reportHeader( _( "Hole clearance resolution for:" ), a, b, r ); if( compileError ) reportCompileError( r ); if( ac && bc && ac->GetNetCode() == bc->GetNetCode() ) { r->Report( "" ); r->Report( _( "Items belong to the same net. Clearance is 0." ) ); } else { constraint = drcEngine->EvalRules( HOLE_CLEARANCE_CONSTRAINT, a, b, layer, r ); clearance = constraint.m_Value.Min(); clearanceStr = m_frame->StringFromValue( clearance, true ); r->Report( "" ); r->Report( wxString::Format( _( "Resolved min clearance: %s." ), clearanceStr ) ); } r->Report( "" ); r->Report( "" ); r->Report( "" ); reportHeader( _( "Physical hole clearance resolution for:" ), a, b, layer, r ); constraint = drcEngine->EvalRules( PHYSICAL_HOLE_CLEARANCE_CONSTRAINT, a, b, layer, r ); clearance = constraint.m_Value.Min(); clearanceStr = m_frame->StringFromValue( clearance, true ); if( !drcEngine->HasRulesForConstraintType( PHYSICAL_HOLE_CLEARANCE_CONSTRAINT ) ) { r->Report( "" ); r->Report( _( "No 'physical_hole_clearance' constraints defined." ) ); } else { r->Report( "" ); r->Report( wxString::Format( _( "Resolved min clearance: %s." ), clearanceStr ) ); } break; case DRCE_DRILLED_HOLES_TOO_CLOSE: r = dialog->AddHTMLPage( _( "Hole to Hole" ) ); reportHeader( _( "Hole-to-hole clearance resolution for:" ), a, b, r ); if( compileError ) reportCompileError( r ); constraint = drcEngine->EvalRules( HOLE_TO_HOLE_CONSTRAINT, a, b, UNDEFINED_LAYER, r ); clearance = constraint.m_Value.Min(); clearanceStr = m_frame->StringFromValue( clearance, true ); r->Report( "" ); r->Report( wxString::Format( _( "Resolved min clearance: %s." ), clearanceStr ) ); break; case DRCE_EDGE_CLEARANCE: r = dialog->AddHTMLPage( _( "Edge Clearance" ) ); reportHeader( _( "Edge clearance resolution for:" ), a, b, r ); if( compileError ) reportCompileError( r ); constraint = drcEngine->EvalRules( EDGE_CLEARANCE_CONSTRAINT, a, b, layer, r ); clearance = constraint.m_Value.Min(); clearanceStr = m_frame->StringFromValue( clearance, true ); r->Report( "" ); r->Report( wxString::Format( _( "Resolved min clearance: %s." ), clearanceStr ) ); break; case DRCE_CLEARANCE: if( a->Type() == PCB_TRACE_T || a->Type() == PCB_ARC_T ) { layer = a->GetLayer(); } else if( b->Type() == PCB_TRACE_T || b->Type() == PCB_ARC_T ) { layer = b->GetLayer(); } else if( a->Type() == PCB_PAD_T && static_cast( a )->GetAttribute() == PAD_ATTRIB::SMD ) { PAD* pad = static_cast( a ); if( pad->IsOnLayer( F_Cu ) ) layer = F_Cu; else layer = B_Cu; } else if( b->Type() == PCB_PAD_T && static_cast( a )->GetAttribute() == PAD_ATTRIB::SMD ) { PAD* pad = static_cast( b ); if( pad->IsOnLayer( F_Cu ) ) layer = F_Cu; else layer = B_Cu; } r = dialog->AddHTMLPage( _( "Clearance" ) ); reportHeader( _( "Clearance resolution for:" ), a, b, layer, r ); if( compileError ) reportCompileError( r ); if( ac && bc && ac->GetNetCode() == bc->GetNetCode() ) { r->Report( "" ); r->Report( _( "Items belong to the same net. Clearance is 0." ) ); } else { constraint = drcEngine->EvalRules( CLEARANCE_CONSTRAINT, a, b, layer, r ); clearance = constraint.m_Value.Min(); clearanceStr = m_frame->StringFromValue( clearance, true ); r->Report( "" ); r->Report( wxString::Format( _( "Resolved min clearance: %s." ), clearanceStr ) ); } r->Report( "" ); r->Report( "" ); r->Report( "" ); reportHeader( _( "Physical clearance resolution for:" ), a, b, layer, r ); constraint = drcEngine->EvalRules( PHYSICAL_CLEARANCE_CONSTRAINT, a, b, layer, r ); clearance = constraint.m_Value.Min(); clearanceStr = m_frame->StringFromValue( clearance, true ); if( !drcEngine->HasRulesForConstraintType( PHYSICAL_CLEARANCE_CONSTRAINT ) ) { r->Report( "" ); r->Report( _( "No 'physical_clearance' constraints defined." ) ); } else { r->Report( "" ); r->Report( wxString::Format( _( "Resolved min clearance: %s." ), clearanceStr ) ); } break; case DRCE_ASSERTION_FAILURE: r = dialog->AddHTMLPage( _( "Assertions" ) ); reportHeader( _( "Assertions for:" ), a, r ); if( compileError ) reportCompileError( r ); drcEngine->ProcessAssertions( a, []( const DRC_CONSTRAINT* c ){}, r ); break; default: return; } r->Flush(); dialog->Raise(); dialog->Show( true ); } int BOARD_INSPECTION_TOOL::InspectClearance( const TOOL_EVENT& aEvent ) { wxCHECK( m_frame, 0 ); PCB_SELECTION_TOOL* selTool = m_toolMgr->GetTool(); wxCHECK( selTool, 0 ); const PCB_SELECTION& selection = selTool->GetSelection(); if( selection.Size() != 2 ) { m_frame->ShowInfoBarError( _( "Select two items for a clearance resolution report." ) ); return 0; } BOARD_ITEM* a = dynamic_cast( selection.GetItem( 0 ) ); BOARD_ITEM* b = dynamic_cast( selection.GetItem( 1 ) ); wxCHECK( a && b, 0 ); if( a->Type() == PCB_GROUP_T ) { PCB_GROUP* ag = static_cast( a ); if( ag->GetItems().empty() ) { m_frame->ShowInfoBarError( _( "Cannot generate clearance report on empty group." ) ); return 0; } a = *ag->GetItems().begin(); } if( b->Type() == PCB_GROUP_T ) { PCB_GROUP* bg = static_cast( b ); if( bg->GetItems().empty() ) { m_frame->ShowInfoBarError( _( "Cannot generate clearance report on empty group." ) ); return 0; } b = *bg->GetItems().begin(); } // a or b could be null after group tests above. if( !a || !b ) return 0; auto checkFootprint = [&]( FOOTPRINT* footprint ) -> BOARD_ITEM* { PAD* foundPad = nullptr; for( PAD* pad : footprint->Pads() ) { if( !foundPad || pad->SameLogicalPadAs( foundPad ) ) foundPad = pad; else return footprint; } if( !foundPad ) return footprint; return foundPad; }; if( a->Type() == PCB_FOOTPRINT_T ) a = checkFootprint( static_cast( a ) ); if( b->Type() == PCB_FOOTPRINT_T ) b = checkFootprint( static_cast( b ) ); // a or b could be null after footprint tests above. if( !a || !b ) return 0; DIALOG_BOOK_REPORTER* dialog = m_frame->GetInspectClearanceDialog(); wxCHECK( dialog, 0 ); dialog->DeleteAllPages(); if( a->Type() != PCB_ZONE_T && b->Type() == PCB_ZONE_T ) std::swap( a, b ); else if( !a->IsConnected() && b->IsConnected() ) std::swap( a, b ); WX_HTML_REPORT_BOX* r = nullptr; PCB_LAYER_ID active = m_frame->GetActiveLayer(); LSET layerIntersection = a->GetLayerSet() & b->GetLayerSet(); LSET copperIntersection = layerIntersection & LSET::AllCuMask(); BOARD_CONNECTED_ITEM* ac = dynamic_cast( a ); BOARD_CONNECTED_ITEM* bc = dynamic_cast( b ); ZONE* zone = dynamic_cast( a ); PAD* pad = dynamic_cast( b ); FOOTPRINT* aFP = dynamic_cast( a ); FOOTPRINT* bFP = dynamic_cast( b ); DRC_CONSTRAINT constraint; int clearance = 0; bool compileError = false; bool courtyardError = false; std::unique_ptr drcEngine = makeDRCEngine( &compileError, &courtyardError ); if( copperIntersection.any() && zone && pad && zone->GetNetCode() == pad->GetNetCode() ) { PCB_LAYER_ID layer = UNDEFINED_LAYER; if( zone->IsOnLayer( active ) ) layer = active; else if( zone->GetLayerSet().count() > 0 ) layer = zone->GetLayerSet().Seq().front(); r = dialog->AddHTMLPage( _( "Zone" ) ); reportHeader( _( "Zone connection resolution for:" ), a, b, layer, r ); constraint = drcEngine->EvalZoneConnection( pad, zone, layer, r ); if( constraint.m_ZoneConnection == ZONE_CONNECTION::THERMAL ) { r->Report( "" ); r->Report( "" ); reportHeader( _( "Thermal-relief gap resolution for:" ), a, b, layer, r ); constraint = drcEngine->EvalRules( THERMAL_RELIEF_GAP_CONSTRAINT, pad, zone, layer, r ); int gap = constraint.m_Value.Min(); if( compileError ) reportCompileError( r ); r->Report( "" ); r->Report( wxString::Format( _( "Resolved thermal relief gap: %s." ), m_frame->StringFromValue( gap, true ) ) ); r->Report( "" ); r->Report( "" ); reportHeader( _( "Thermal-relief spoke width resolution for:" ), a, b, layer, r ); constraint = drcEngine->EvalRules( THERMAL_SPOKE_WIDTH_CONSTRAINT, pad, zone, layer, r ); int width = constraint.m_Value.Opt(); if( compileError ) reportCompileError( r ); r->Report( "" ); r->Report( wxString::Format( _( "Resolved spoke width: %s." ), m_frame->StringFromValue( width, true ) ) ); r->Report( "" ); r->Report( "" ); reportHeader( _( "Thermal-relief min spoke count resolution for:" ), a, b, layer, r ); constraint = drcEngine->EvalRules( MIN_RESOLVED_SPOKES_CONSTRAINT, pad, zone, layer, r ); int minSpokes = constraint.m_Value.Min(); if( compileError ) reportCompileError( r ); r->Report( "" ); r->Report( wxString::Format( _( "Resolved min spoke count: %d." ), minSpokes ) ); std::shared_ptr connectivity = pad->GetBoard()->GetConnectivity(); } else if( constraint.m_ZoneConnection == ZONE_CONNECTION::NONE ) { r->Report( "" ); r->Report( "" ); reportHeader( _( "Zone clearance resolution for:" ), a, b, layer, r ); clearance = zone->GetLocalClearance().value(); r->Report( "" ); r->Report( wxString::Format( _( "Zone clearance: %s." ), m_frame->StringFromValue( clearance, true ) ) ); constraint = drcEngine->EvalRules( PHYSICAL_CLEARANCE_CONSTRAINT, pad, zone, layer, r ); if( constraint.m_Value.Min() > clearance ) { clearance = constraint.m_Value.Min(); r->Report( "" ); r->Report( wxString::Format( _( "Overridden by larger physical clearance from %s;" "clearance: %s." ), EscapeHTML( constraint.GetName() ), m_frame->StringFromValue( clearance, true ) ) ); } if( !pad->FlashLayer( layer ) ) { constraint = drcEngine->EvalRules( PHYSICAL_HOLE_CLEARANCE_CONSTRAINT, pad, zone, layer, r ); if( constraint.m_Value.Min() > clearance ) { clearance = constraint.m_Value.Min(); r->Report( "" ); r->Report( wxString::Format( _( "Overridden by larger physical hole clearance " "from %s; clearance: %s." ), EscapeHTML( constraint.GetName() ), m_frame->StringFromValue( clearance, true ) ) ); } } if( compileError ) reportCompileError( r ); r->Report( "" ); r->Report( wxString::Format( _( "Resolved min clearance: %s." ), m_frame->StringFromValue( clearance, true ) ) ); } else { r->Report( "" ); r->Report( "" ); reportHeader( _( "Zone clearance resolution for:" ), a, b, layer, r ); if( compileError ) reportCompileError( r ); // Report a 0 clearance for solid connections r->Report( "" ); r->Report( wxString::Format( _( "Resolved min clearance: %s." ), m_frame->StringFromValue( 0, true ) ) ); } r->Flush(); } else if( copperIntersection.any() && !aFP && !bFP ) { PCB_LAYER_ID layer = active; if( !copperIntersection.test( layer ) ) layer = copperIntersection.Seq().front(); r = dialog->AddHTMLPage( m_frame->GetBoard()->GetLayerName( layer ) ); reportHeader( _( "Clearance resolution for:" ), a, b, layer, r ); if( ac && bc && ac->GetNetCode() > 0 && ac->GetNetCode() == bc->GetNetCode() ) { // Same nets.... r->Report( _( "Items belong to the same net. Min clearance is 0." ) ); } else { // Different nets (or one or both unconnected).... constraint = drcEngine->EvalRules( CLEARANCE_CONSTRAINT, a, b, layer, r ); clearance = constraint.m_Value.Min(); if( compileError ) reportCompileError( r ); r->Report( "" ); if( constraint.IsNull() ) { r->Report( _( "Min clearance is 0." ) ); } else if( clearance < 0 ) { r->Report( wxString::Format( _( "Resolved clearance: %s; clearance will not be " "tested." ), m_frame->StringFromValue( clearance, true ) ) ); } else { r->Report( wxString::Format( _( "Resolved min clearance: %s." ), m_frame->StringFromValue( clearance, true ) ) ); } } r->Flush(); } if( ac && bc ) { NETINFO_ITEM* refNet = ac->GetNet(); wxString coupledNet; wxString dummy; if( DRC_ENGINE::MatchDpSuffix( refNet->GetNetname(), coupledNet, dummy ) && bc->GetNetname() == coupledNet ) { r = dialog->AddHTMLPage( _( "Diff Pair" ) ); reportHeader( _( "Diff-pair gap resolution for:" ), ac, bc, active, r ); constraint = drcEngine->EvalRules( DIFF_PAIR_GAP_CONSTRAINT, ac, bc, active, r ); r->Report( "" ); r->Report( wxString::Format( _( "Resolved gap constraints: min %s; opt %s; max %s." ), reportMin( m_frame, constraint ), reportOpt( m_frame, constraint ), reportMax( m_frame, constraint ) ) ); r->Report( "" ); r->Report( "" ); r->Report( "" ); reportHeader( _( "Diff-pair max uncoupled length resolution for:" ), ac, bc, active, r ); if( !drcEngine->HasRulesForConstraintType( MAX_UNCOUPLED_CONSTRAINT ) ) { r->Report( "" ); r->Report( _( "No 'diff_pair_uncoupled' constraints defined." ) ); } else { constraint = drcEngine->EvalRules( MAX_UNCOUPLED_CONSTRAINT, ac, bc, active, r ); r->Report( "" ); r->Report( wxString::Format( _( "Resolved max uncoupled length: %s." ), reportMax( m_frame, constraint ) ) ); } r->Flush(); } } auto isOnCorrespondingLayer= [&]( BOARD_ITEM* aItem, PCB_LAYER_ID aLayer, wxString* aWarning ) { if( aItem->IsOnLayer( aLayer ) ) return true; PCB_LAYER_ID correspondingMask = IsFrontLayer( aLayer ) ? F_Mask : B_Mask; PCB_LAYER_ID correspondingCopper = IsFrontLayer( aLayer ) ? F_Cu : B_Cu; if( aItem->IsOnLayer( aLayer ) ) return true; if( aItem->IsOnLayer( correspondingMask ) ) return true; if( aItem->IsTented( correspondingMask ) && aItem->IsOnLayer( correspondingCopper ) ) { *aWarning = wxString::Format( _( "Note: %s is tented; clearance will only be " "applied to holes." ), getItemDescription( aItem ) ); return true; } return false; }; for( PCB_LAYER_ID layer : { F_SilkS, B_SilkS } ) { wxString warning; if( ( a->IsOnLayer( layer ) && isOnCorrespondingLayer( b, layer, &warning ) ) || ( b->IsOnLayer( layer ) && isOnCorrespondingLayer( a, layer, &warning ) ) ) { r = dialog->AddHTMLPage( m_frame->GetBoard()->GetLayerName( layer ) ); reportHeader( _( "Silkscreen clearance resolution for:" ), a, b, layer, r ); constraint = drcEngine->EvalRules( SILK_CLEARANCE_CONSTRAINT, a, b, layer, r ); clearance = constraint.m_Value.Min(); if( compileError ) reportCompileError( r ); r->Report( "" ); if( !warning.IsEmpty() ) r->Report( warning ); r->Report( wxString::Format( _( "Resolved min clearance: %s." ), m_frame->StringFromValue( clearance, true ) ) ); r->Flush(); } } for( PCB_LAYER_ID layer : { F_CrtYd, B_CrtYd } ) { bool aCourtyard = aFP && !aFP->GetCourtyard( layer ).IsEmpty(); bool bCourtyard = bFP && !bFP->GetCourtyard( layer ).IsEmpty(); if( aCourtyard && bCourtyard ) { r = dialog->AddHTMLPage( m_frame->GetBoard()->GetLayerName( layer ) ); reportHeader( _( "Courtyard clearance resolution for:" ), a, b, layer, r ); constraint = drcEngine->EvalRules( COURTYARD_CLEARANCE_CONSTRAINT, a, b, layer, r ); clearance = constraint.m_Value.Min(); if( compileError ) reportCompileError( r ); r->Report( "" ); r->Report( wxString::Format( _( "Resolved min clearance: %s." ), m_frame->StringFromValue( clearance, true ) ) ); r->Flush(); } } if( a->HasHole() || b->HasHole() ) { PCB_LAYER_ID layer = UNDEFINED_LAYER; bool pageAdded = false; if( a->HasHole() && b->IsOnLayer( active ) && IsCopperLayer( active ) ) layer = active; else if( b->HasHole() && a->IsOnLayer( active ) && IsCopperLayer( active ) ) layer = active; else if( a->HasHole() && b->IsOnCopperLayer() ) layer = b->GetLayer(); else if( b->HasHole() && b->IsOnCopperLayer() ) layer = a->GetLayer(); if( layer >= 0 ) { if( !pageAdded ) { r = dialog->AddHTMLPage( _( "Hole" ) ); pageAdded = true; } else { r->Report( "" ); r->Report( "" ); r->Report( "" ); } reportHeader( _( "Hole clearance resolution for:" ), a, b, layer, r ); constraint = drcEngine->EvalRules( HOLE_CLEARANCE_CONSTRAINT, a, b, layer, r ); clearance = constraint.m_Value.Min(); if( compileError ) reportCompileError( r ); r->Report( "" ); r->Report( wxString::Format( _( "Resolved min clearance: %s." ), m_frame->StringFromValue( clearance, true ) ) ); r->Flush(); } if( a->HasDrilledHole() || b->HasDrilledHole() ) { if( !pageAdded ) { r = dialog->AddHTMLPage( _( "Hole" ) ); pageAdded = true; } else { r->Report( "" ); r->Report( "" ); r->Report( "" ); } reportHeader( _( "Hole-to-hole clearance resolution for:" ), a, b, r ); constraint = drcEngine->EvalRules( HOLE_TO_HOLE_CONSTRAINT, a, b, UNDEFINED_LAYER, r ); clearance = constraint.m_Value.Min(); if( compileError ) reportCompileError( r ); r->Report( "" ); r->Report( wxString::Format( _( "Resolved min clearance: %s." ), m_frame->StringFromValue( clearance, true ) ) ); r->Flush(); } } for( PCB_LAYER_ID edgeLayer : { Edge_Cuts, Margin } ) { PCB_LAYER_ID layer = UNDEFINED_LAYER; if( a->IsOnLayer( edgeLayer ) && b->Type() != PCB_FOOTPRINT_T ) { if( b->IsOnLayer( active ) && IsCopperLayer( active ) ) layer = active; else if( IsCopperLayer( b->GetLayer() ) ) layer = b->GetLayer(); } else if( b->IsOnLayer( edgeLayer ) && a->Type() != PCB_FOOTPRINT_T ) { if( a->IsOnLayer( active ) && IsCopperLayer( active ) ) layer = active; else if( IsCopperLayer( a->GetLayer() ) ) layer = a->GetLayer(); } if( layer >= 0 ) { wxString layerName = m_frame->GetBoard()->GetLayerName( edgeLayer ); r = dialog->AddHTMLPage( layerName + wxS( " " ) + _( "Clearance" ) ); reportHeader( _( "Edge clearance resolution for:" ), a, b, layer, r ); constraint = drcEngine->EvalRules( EDGE_CLEARANCE_CONSTRAINT, a, b, layer, r ); clearance = constraint.m_Value.Min(); if( compileError ) reportCompileError( r ); r->Report( "" ); r->Report( wxString::Format( _( "Resolved min clearance: %s." ), m_frame->StringFromValue( clearance, true ) ) ); r->Flush(); } } r = dialog->AddHTMLPage( _( "Physical Clearances" ) ); auto reportPhysicalClearance = [&]( PCB_LAYER_ID aLayer ) { reportHeader( _( "Physical clearance resolution for:" ), a, b, aLayer, r ); constraint = drcEngine->EvalRules( PHYSICAL_CLEARANCE_CONSTRAINT, a, b, aLayer, r ); clearance = constraint.m_Value.Min(); if( compileError ) { reportCompileError( r ); } else if( !drcEngine->HasRulesForConstraintType( PHYSICAL_CLEARANCE_CONSTRAINT ) ) { r->Report( "" ); r->Report( _( "No 'physical_clearance' constraints defined." ) ); } else { r->Report( "" ); r->Report( wxString::Format( _( "Resolved min clearance: %s." ), m_frame->StringFromValue( clearance, true ) ) ); } r->Report( "" ); r->Report( "" ); r->Report( "" ); }; if( layerIntersection.any() ) { PCB_LAYER_ID layer = active; if( !layerIntersection.test( layer ) ) layer = layerIntersection.Seq().front(); reportPhysicalClearance( layer ); } if( aFP && b->IsOnLayer( Edge_Cuts ) ) { if( !aFP->GetCourtyard( F_CrtYd ).IsEmpty() ) reportPhysicalClearance( F_CrtYd ); if( !aFP->GetCourtyard( B_CrtYd ).IsEmpty() ) reportPhysicalClearance( B_CrtYd ); } else if( bFP && a->IsOnLayer( Edge_Cuts ) ) { if( !bFP->GetCourtyard( F_CrtYd ).IsEmpty() ) reportPhysicalClearance( F_CrtYd ); if( !bFP->GetCourtyard( B_CrtYd ).IsEmpty() ) reportPhysicalClearance( B_CrtYd ); } if( a->HasHole() || b->HasHole() ) { PCB_LAYER_ID layer; if( a->HasHole() && b->IsOnLayer( active ) ) layer = active; else if( b->HasHole() && a->IsOnLayer( active ) ) layer = active; else if( a->HasHole() ) layer = b->GetLayer(); else layer = a->GetLayer(); reportHeader( _( "Physical hole clearance resolution for:" ), a, b, layer, r ); constraint = drcEngine->EvalRules( PHYSICAL_HOLE_CLEARANCE_CONSTRAINT, a, b, layer, r ); clearance = constraint.m_Value.Min(); if( compileError ) { reportCompileError( r ); } else if( !drcEngine->HasRulesForConstraintType( PHYSICAL_HOLE_CLEARANCE_CONSTRAINT ) ) { r->Report( "" ); r->Report( _( "No 'physical_hole_clearance' constraints defined." ) ); } else { r->Report( "" ); r->Report( wxString::Format( _( "Resolved min clearance: %s." ), m_frame->StringFromValue( clearance, true ) ) ); } } r->Flush(); dialog->Raise(); dialog->Show( true ); return 0; } int BOARD_INSPECTION_TOOL::InspectConstraints( const TOOL_EVENT& aEvent ) { #define EVAL_RULES( constraint, a, b, layer, r ) drcEngine->EvalRules( constraint, a, b, layer, r ) wxCHECK( m_frame, 0 ); PCB_SELECTION_TOOL* selTool = m_toolMgr->GetTool(); wxCHECK( selTool, 0 ); const PCB_SELECTION& selection = selTool->GetSelection(); if( selection.Size() != 1 ) { m_frame->ShowInfoBarError( _( "Select an item for a constraints resolution report." ) ); return 0; } DIALOG_BOOK_REPORTER* dialog = m_frame->GetInspectConstraintsDialog(); wxCHECK( dialog, 0 ); dialog->DeleteAllPages(); BOARD_ITEM* item = dynamic_cast( selection.GetItem( 0 ) ); DRC_CONSTRAINT constraint; bool compileError = false; bool courtyardError = false; std::unique_ptr drcEngine = makeDRCEngine( &compileError, &courtyardError ); wxCHECK( item, 0 ); WX_HTML_REPORT_BOX* r = nullptr; if( item->Type() == PCB_TRACE_T ) { r = dialog->AddHTMLPage( _( "Track Width" ) ); reportHeader( _( "Track width resolution for:" ), item, r ); constraint = EVAL_RULES( TRACK_WIDTH_CONSTRAINT, item, nullptr, item->GetLayer(), r ); if( compileError ) reportCompileError( r ); r->Report( "" ); r->Report( wxString::Format( _( "Resolved width constraints: min %s; opt %s; max %s." ), reportMin( m_frame, constraint ), reportOpt( m_frame, constraint ), reportMax( m_frame, constraint ) ) ); r->Flush(); } if( item->Type() == PCB_VIA_T ) { r = dialog->AddHTMLPage( _( "Via Diameter" ) ); reportHeader( _( "Via diameter resolution for:" ), item, r ); // PADSTACKS TODO: once we have padstacks we'll need to run this per-layer.... constraint = EVAL_RULES( VIA_DIAMETER_CONSTRAINT, item, nullptr, UNDEFINED_LAYER, r ); if( compileError ) reportCompileError( r ); r->Report( "" ); r->Report( wxString::Format( _( "Resolved diameter constraints: min %s; opt %s; max %s." ), reportMin( m_frame, constraint ), reportOpt( m_frame, constraint ), reportMax( m_frame, constraint ) ) ); r->Flush(); r = dialog->AddHTMLPage( _( "Via Annular Width" ) ); reportHeader( _( "Via annular width resolution for:" ), item, r ); // PADSTACKS TODO: once we have padstacks we'll need to run this per-layer.... constraint = EVAL_RULES( ANNULAR_WIDTH_CONSTRAINT, item, nullptr, UNDEFINED_LAYER, r ); if( compileError ) reportCompileError( r ); r->Report( "" ); r->Report( wxString::Format( _( "Resolved annular width constraints: min %s; opt %s; max %s." ), reportMin( m_frame, constraint ), reportOpt( m_frame, constraint ), reportMax( m_frame, constraint ) ) ); r->Flush(); } if( ( item->Type() == PCB_PAD_T && static_cast( item )->GetDrillSize().x > 0 ) || item->Type() == PCB_VIA_T ) { r = dialog->AddHTMLPage( _( "Hole Size" ) ); reportHeader( _( "Hole size resolution for:" ), item, r ); // PADSTACKS TODO: once we have padstacks we'll need to run this per-layer.... constraint = EVAL_RULES( HOLE_SIZE_CONSTRAINT, item, nullptr, UNDEFINED_LAYER, r ); if( compileError ) reportCompileError( r ); r->Report( "" ); r->Report( wxString::Format( _( "Resolved hole size constraints: min %s; opt %s; max %s." ), reportMin( m_frame, constraint ), reportOpt( m_frame, constraint ), reportMax( m_frame, constraint ) ) ); r->Flush(); } if( item->Type() == PCB_FIELD_T || item->Type() == PCB_TEXT_T || item->Type() == PCB_TEXTBOX_T ) { r = dialog->AddHTMLPage( _( "Text Size" ) ); reportHeader( _( "Text height resolution for:" ), item, r ); constraint = EVAL_RULES( TEXT_HEIGHT_CONSTRAINT, item, nullptr, UNDEFINED_LAYER, r ); if( compileError ) reportCompileError( r ); r->Report( "" ); r->Report( wxString::Format( _( "Resolved height constraints: min %s; opt %s; max %s." ), reportMin( m_frame, constraint ), reportOpt( m_frame, constraint ), reportMax( m_frame, constraint ) ) ); r->Report( "" ); r->Report( "" ); r->Report( "" ); reportHeader( _( "Text thickness resolution for:" ), item, r ); constraint = EVAL_RULES( TEXT_THICKNESS_CONSTRAINT, item, nullptr, UNDEFINED_LAYER, r ); if( compileError ) reportCompileError( r ); r->Report( "" ); r->Report( wxString::Format( _( "Resolved thickness constraints: min %s; opt %s; max %s." ), reportMin( m_frame, constraint ), reportOpt( m_frame, constraint ), reportMax( m_frame, constraint ) ) ); r->Flush(); } r = dialog->AddHTMLPage( _( "Keepouts" ) ); reportHeader( _( "Keepout resolution for:" ), item, r ); constraint = EVAL_RULES( DISALLOW_CONSTRAINT, item, nullptr, item->GetLayer(), r ); if( compileError ) reportCompileError( r ); if( courtyardError ) { r->Report( "" ); r->Report( _( "Report may be incomplete: some footprint courtyards are malformed." ) + wxS( "  " ) + wxS( "" ) + _( "Run DRC for a full analysis." ) + wxS( "" ) ); } r->Report( "" ); if( constraint.m_DisallowFlags ) r->Report( _( "Item disallowed at current location." ) ); else r->Report( _( "Item allowed at current location." ) ); r->Flush(); r = dialog->AddHTMLPage( _( "Assertions" ) ); reportHeader( _( "Assertions for:" ), item, r ); if( compileError ) reportCompileError( r ); if( courtyardError ) { r->Report( "" ); r->Report( _( "Report may be incomplete: some footprint courtyards are malformed." ) + wxS( "  " ) + wxS( "" ) + _( "Run DRC for a full analysis." ) + wxS( "" ) ); } drcEngine->ProcessAssertions( item, []( const DRC_CONSTRAINT* c ){}, r ); r->Flush(); dialog->Raise(); dialog->Show( true ); return 0; } int BOARD_INSPECTION_TOOL::DiffFootprint( const TOOL_EVENT& aEvent ) { wxCHECK( m_frame, 0 ); PCB_SELECTION_TOOL* selTool = m_toolMgr->GetTool(); wxCHECK( selTool, 0 ); const PCB_SELECTION& selection = selTool->RequestSelection( []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool ) { // Iterate from the back so we don't have to worry about removals. for( int i = aCollector.GetCount() - 1; i >= 0; --i ) { BOARD_ITEM* item = aCollector[ i ]; if( !dynamic_cast( item ) ) aCollector.Remove( item ); } }, false /* ignore locked flag */ ); if( selection.Size() == 1 ) DiffFootprint( static_cast( selection.GetItem( 0 ) ) ); else m_frame->ShowInfoBarError( _( "Select a footprint to diff with its library equivalent." ) ); return 0; } int BOARD_INSPECTION_TOOL::ShowFootprintLinks( const TOOL_EVENT& aEvent ) { wxCHECK( m_frame, 0 ); PCB_SELECTION_TOOL* selTool = m_toolMgr->GetTool(); wxCHECK( selTool, 0 ); const PCB_SELECTION& selection = selTool->GetSelection(); if( selection.Size() != 1 || selection.Front()->Type() != PCB_FOOTPRINT_T ) { m_frame->ShowInfoBarError( _( "Select a footprint for a footprint associations report." ) ); return 0; } DIALOG_FOOTPRINT_ASSOCIATIONS dlg( m_frame, static_cast( selection.Front() ) ); dlg.ShowModal(); return 0; } void BOARD_INSPECTION_TOOL::DiffFootprint( FOOTPRINT* aFootprint ) { DIALOG_BOOK_REPORTER* dialog = m_frame->GetFootprintDiffDialog(); wxCHECK( dialog, /* void */ ); dialog->DeleteAllPages(); LIB_ID fpID = aFootprint->GetFPID(); wxString libName = fpID.GetLibNickname(); wxString fpName = fpID.GetLibItemName(); WX_HTML_REPORT_BOX* r = nullptr; r = dialog->AddHTMLPage( _( "Summary" ) ); r->Report( wxS( "" ) + _( "Board vs library diff for:" ) + wxS( "" ) ); r->Report( wxS( "
  • " ) + EscapeHTML( getItemDescription( aFootprint ) ) + wxS( "
  • " ) + wxS( "
  • " ) + _( "Library: " ) + EscapeHTML( libName ) + wxS( "
  • " ) + wxS( "
  • " ) + _( "Library item: " ) + EscapeHTML( fpName ) + wxS( "
" ) ); r->Report( "" ); PROJECT* project = aFootprint->GetBoard()->GetProject(); FP_LIB_TABLE* libTable = PROJECT_PCB::PcbFootprintLibs( project ); const LIB_TABLE_ROW* libTableRow = nullptr; try { libTableRow = libTable->FindRow( libName ); } catch( const IO_ERROR& ) { } if( !libTableRow ) { r->Report( _( "The library is not included in the current configuration." ) + wxS( "   " ) + wxS( "" ) + _( "Manage Footprint Libraries" ) + wxS( "" ) ); } else if( !libTable->HasLibrary( libName, true ) ) { r->Report( _( "The library is not enabled in the current configuration." ) + wxS( "   " ) + wxS( "" ) + _( "Manage Footprint Libraries" ) + wxS( "" ) ); } else { std::shared_ptr libFootprint; try { libFootprint.reset( libTable->FootprintLoad( libName, fpName, true ) ); } catch( const IO_ERROR& ) { } if( !libFootprint ) { r->Report( wxString::Format( _( "The library no longer contains the item %s." ), fpName) ); } else { if( !aFootprint->FootprintNeedsUpdate( libFootprint.get(), 0, r ) ) r->Report( _( "No relevant differences detected." ) ); wxPanel* panel = dialog->AddBlankPage( _( "Visual" ) ); FOOTPRINT_DIFF_WIDGET* diff = constructDiffPanel( panel ); diff->DisplayDiff( aFootprint, libFootprint ); } } r->Flush(); dialog->Raise(); dialog->Show( true ); } FOOTPRINT_DIFF_WIDGET* BOARD_INSPECTION_TOOL::constructDiffPanel( wxPanel* aParentPanel ) { wxBoxSizer* sizer = new wxBoxSizer( wxVERTICAL ); FOOTPRINT_DIFF_WIDGET* diffWidget = new FOOTPRINT_DIFF_WIDGET( aParentPanel, m_frame->Kiway() ); sizer->Add( diffWidget, 1, wxEXPAND | wxALL, 5 ); aParentPanel->SetSizer( sizer ); aParentPanel->Layout(); return diffWidget; } int BOARD_INSPECTION_TOOL::HighlightItem( const TOOL_EVENT& aEvent ) { BOARD_ITEM* item = aEvent.Parameter(); m_frame->m_probingSchToPcb = true; // recursion guard { m_toolMgr->RunAction( PCB_ACTIONS::selectionClear ); if( item ) m_toolMgr->RunAction( PCB_ACTIONS::selectItem, item ); } m_frame->m_probingSchToPcb = false; bool request3DviewRedraw = frame()->GetPcbNewSettings()->m_Display.m_Live3DRefresh; if( item && item->Type() != PCB_FOOTPRINT_T ) request3DviewRedraw = false; // Update 3D viewer highlighting if( request3DviewRedraw ) m_frame->Update3DView( false, true ); return 0; } bool BOARD_INSPECTION_TOOL::highlightNet( const VECTOR2D& aPosition, bool aUseSelection ) { BOARD* board = static_cast( m_toolMgr->GetModel() ); KIGFX::RENDER_SETTINGS* settings = getView()->GetPainter()->GetSettings(); PCB_SELECTION_TOOL* selectionTool = m_toolMgr->GetTool(); int net = -1; bool enableHighlight = false; if( aUseSelection ) { const PCB_SELECTION& selection = selectionTool->GetSelection(); std::set netcodes; for( EDA_ITEM* item : selection ) { if( BOARD_CONNECTED_ITEM* ci = dynamic_cast( item ) ) netcodes.insert( ci->GetNetCode() ); } enableHighlight = !netcodes.empty(); if( enableHighlight && netcodes.size() > 1 ) { // If we are doing a multi-highlight, cross-probing back and other stuff is not // yet supported settings->SetHighlight( netcodes ); board->ResetNetHighLight(); for( int multiNet : netcodes ) board->SetHighLightNet( multiNet, true ); board->HighLightON(); m_toolMgr->GetView()->UpdateAllLayersColor(); m_currentlyHighlighted = netcodes; return true; } else if( enableHighlight ) { net = *netcodes.begin(); } } // If we didn't get a net to highlight from the selection, use the cursor if( net < 0 ) { GENERAL_COLLECTORS_GUIDE guide = m_frame->GetCollectorsGuide(); guide.SetIgnoreZoneFills( false ); PCB_LAYER_ID activeLayer = static_cast( view()->GetTopLayer() ); guide.SetPreferredLayer( activeLayer ); GENERAL_COLLECTOR collector; collector.Collect( board, { PCB_PAD_T, PCB_VIA_T, PCB_TRACE_T, PCB_ARC_T, PCB_SHAPE_T }, aPosition, guide ); if( collector.GetCount() == 0 ) collector.Collect( board, { PCB_ZONE_T }, aPosition, guide ); // Apply the active selection filter, except we want to allow picking locked items for // highlighting even if the user has disabled them for selection PCB_SELECTION_FILTER_OPTIONS& filter = selectionTool->GetFilter(); bool saved = filter.lockedItems; filter.lockedItems = true; selectionTool->FilterCollectedItems( collector, true ); filter.lockedItems = saved; // Clear the previous highlight //m_frame->SendMessageToEESCHEMA( nullptr ); bool highContrast = settings->GetHighContrast(); PCB_LAYER_ID contrastLayer = settings->GetPrimaryHighContrastLayer(); for( int i = collector.GetCount() - 1; i >= 0; i-- ) { LSET itemLayers = collector[i]->GetLayerSet(); if( ( itemLayers & LSET::AllCuMask() ).none() || ( highContrast && !itemLayers.Contains( contrastLayer ) ) ) { collector.Remove( i ); continue; } } enableHighlight = ( collector.GetCount() > 0 ); // Obtain net code for the clicked item if( enableHighlight ) { BOARD_CONNECTED_ITEM* targetItem = static_cast( collector[0] ); if( targetItem->Type() == PCB_PAD_T ) m_frame->SendCrossProbeItem( targetItem ); net = targetItem->GetNetCode(); } } const std::set& netcodes = settings->GetHighlightNetCodes(); // Toggle highlight when the same net was picked if( !aUseSelection && netcodes.size() == 1 && netcodes.contains( net ) ) enableHighlight = !settings->IsHighlightEnabled(); if( enableHighlight != settings->IsHighlightEnabled() || !netcodes.count( net ) ) { if( !netcodes.empty() ) m_lastHighlighted = netcodes; settings->SetHighlight( enableHighlight, net ); m_toolMgr->GetView()->UpdateAllLayersColor(); } // Store the highlighted netcode in the current board (for dialogs for instance) if( enableHighlight && net >= 0 ) { m_currentlyHighlighted = netcodes; board->SetHighLightNet( net ); board->HighLightON(); NETINFO_ITEM* netinfo = board->FindNet( net ); if( netinfo ) { std::vector items; netinfo->GetMsgPanelInfo( m_frame, items ); m_frame->SetMsgPanel( items ); m_frame->SendCrossProbeNetName( netinfo->GetNetname() ); } } else { m_currentlyHighlighted.clear(); board->ResetNetHighLight(); m_frame->SetMsgPanel( board ); m_frame->SendCrossProbeNetName( "" ); } return true; } int BOARD_INSPECTION_TOOL::HighlightNet( const TOOL_EVENT& aEvent ) { int netcode = aEvent.Parameter(); KIGFX::RENDER_SETTINGS* settings = m_toolMgr->GetView()->GetPainter()->GetSettings(); const std::set& highlighted = settings->GetHighlightNetCodes(); if( netcode > 0 ) { m_lastHighlighted = highlighted; settings->SetHighlight( true, netcode ); m_toolMgr->GetView()->UpdateAllLayersColor(); m_currentlyHighlighted.clear(); m_currentlyHighlighted.insert( netcode ); } else if( aEvent.IsAction( &PCB_ACTIONS::highlightNetSelection ) ) { // Highlight selection (cursor position will be ignored) highlightNet( getViewControls()->GetMousePosition(), true ); } else if( aEvent.IsAction( &PCB_ACTIONS::toggleLastNetHighlight ) ) { std::set temp = highlighted; settings->SetHighlight( m_lastHighlighted ); m_toolMgr->GetView()->UpdateAllLayersColor(); m_currentlyHighlighted = m_lastHighlighted; m_lastHighlighted = std::move( temp ); } else if( aEvent.IsAction( &PCB_ACTIONS::toggleNetHighlight ) ) { bool turnOn = highlighted.empty() && !m_currentlyHighlighted.empty(); settings->SetHighlight( m_currentlyHighlighted, turnOn ); m_toolMgr->GetView()->UpdateAllLayersColor(); } else // Highlight the net belonging to the item under the cursor { highlightNet( getViewControls()->GetMousePosition(), false ); } return 0; } int BOARD_INSPECTION_TOOL::ClearHighlight( const TOOL_EVENT& aEvent ) { BOARD* board = static_cast( m_toolMgr->GetModel() ); KIGFX::RENDER_SETTINGS* settings = m_toolMgr->GetView()->GetPainter()->GetSettings(); m_currentlyHighlighted.clear(); m_lastHighlighted.clear(); board->ResetNetHighLight(); settings->SetHighlight( false ); m_toolMgr->GetView()->UpdateAllLayersColor(); m_frame->SetMsgPanel( board ); m_frame->SendCrossProbeNetName( "" ); return 0; } int BOARD_INSPECTION_TOOL::LocalRatsnestTool( const TOOL_EVENT& aEvent ) { PCB_PICKER_TOOL* picker = m_toolMgr->GetTool(); BOARD* board = getModel(); // Deactivate other tools; particularly important if another PICKER is currently running Activate(); picker->SetCursor( KICURSOR::BULLSEYE ); picker->SetClickHandler( [this, board]( const VECTOR2D& pt ) -> bool { PCB_SELECTION_TOOL* selectionTool = m_toolMgr->GetTool(); m_toolMgr->RunAction( PCB_ACTIONS::selectionClear ); m_toolMgr->RunAction( PCB_ACTIONS::selectionCursor, EDIT_TOOL::PadFilter ); PCB_SELECTION& selection = selectionTool->GetSelection(); if( selection.Empty() ) { m_toolMgr->RunAction( PCB_ACTIONS::selectionCursor, EDIT_TOOL::FootprintFilter ); selection = selectionTool->GetSelection(); } if( selection.Empty() ) { // Clear the previous local ratsnest if we click off all items for( FOOTPRINT* fp : board->Footprints() ) { for( PAD* pad : fp->Pads() ) pad->SetLocalRatsnestVisible( displayOptions().m_ShowGlobalRatsnest ); } } else { for( EDA_ITEM* item : selection ) { if( PAD* pad = dyn_cast( item) ) { pad->SetLocalRatsnestVisible( !pad->GetLocalRatsnestVisible() ); } else if( FOOTPRINT* fp = dyn_cast( item) ) { if( !fp->Pads().empty() ) { bool enable = !fp->Pads()[0]->GetLocalRatsnestVisible(); for( PAD* childPad : fp->Pads() ) childPad->SetLocalRatsnestVisible( enable ); } } } } m_toolMgr->GetView()->MarkTargetDirty( KIGFX::TARGET_OVERLAY ); return true; } ); picker->SetFinalizeHandler( [this, board]( int aCondition ) { if( aCondition != PCB_PICKER_TOOL::END_ACTIVATE ) { for( FOOTPRINT* fp : board->Footprints() ) { for( PAD* pad : fp->Pads() ) pad->SetLocalRatsnestVisible( displayOptions().m_ShowGlobalRatsnest ); } } } ); m_toolMgr->RunAction( ACTIONS::pickerTool, &aEvent ); return 0; } int BOARD_INSPECTION_TOOL::UpdateLocalRatsnest( const TOOL_EVENT& aEvent ) { VECTOR2I delta = aEvent.Parameter(); if( delta == VECTOR2I() ) { // We can delete the existing map to force a recalculation delete m_dynamicData; m_dynamicData = nullptr; } auto selectionTool = m_toolMgr->GetTool(); auto& selection = selectionTool->GetSelection(); auto connectivity = getModel()->GetConnectivity(); if( selection.Empty() ) { connectivity->ClearLocalRatsnest(); delete m_dynamicData; m_dynamicData = nullptr; } else { calculateSelectionRatsnest( delta ); } return 0; } int BOARD_INSPECTION_TOOL::HideLocalRatsnest( const TOOL_EVENT& aEvent ) { getModel()->GetConnectivity()->ClearLocalRatsnest(); delete m_dynamicData; m_dynamicData = nullptr; return 0; } void BOARD_INSPECTION_TOOL::calculateSelectionRatsnest( const VECTOR2I& aDelta ) { PCB_SELECTION_TOOL* selectionTool = m_toolMgr->GetTool(); SELECTION& selection = selectionTool->GetSelection(); std::shared_ptr connectivity = board()->GetConnectivity(); std::vector items; std::deque queued_items( selection.begin(), selection.end() ); for( std::size_t i = 0; i < queued_items.size(); ++i ) { BOARD_ITEM* item = dynamic_cast( queued_items[i] ); wxCHECK2( item, continue ); if( item->Type() == PCB_FOOTPRINT_T ) { for( PAD* pad : static_cast( item )->Pads() ) { if( pad->GetLocalRatsnestVisible() || displayOptions().m_ShowModuleRatsnest ) items.push_back( pad ); } } else if( item->Type() == PCB_GROUP_T || item->Type() == PCB_GENERATOR_T ) { item->RunOnDescendants( [ &queued_items ]( BOARD_ITEM *aItem ) { queued_items.push_back( aItem ); } ); } else if( BOARD_CONNECTED_ITEM* boardItem = dyn_cast( item ) ) { if( boardItem->GetLocalRatsnestVisible() || displayOptions().m_ShowModuleRatsnest ) items.push_back( boardItem ); } } if( items.empty() || std::none_of( items.begin(), items.end(), []( const BOARD_ITEM* aItem ) { return( aItem->Type() == PCB_TRACE_T || aItem->Type() == PCB_PAD_T || aItem->Type() == PCB_ARC_T || aItem->Type() == PCB_ZONE_T || aItem->Type() == PCB_FOOTPRINT_T || aItem->Type() == PCB_VIA_T || aItem->Type() == PCB_SHAPE_T ); } ) ) { return; } if( !m_dynamicData ) { m_dynamicData = new CONNECTIVITY_DATA( board()->GetConnectivity(), items, true ); connectivity->BlockRatsnestItems( items ); } else { m_dynamicData->Move( aDelta ); } connectivity->ComputeLocalRatsnest( items, m_dynamicData ); } int BOARD_INSPECTION_TOOL::HideNetInRatsnest( const TOOL_EVENT& aEvent ) { doHideRatsnestNet( aEvent.Parameter(), true ); return 0; } int BOARD_INSPECTION_TOOL::ShowNetInRatsnest( const TOOL_EVENT& aEvent ) { doHideRatsnestNet( aEvent.Parameter(), false ); return 0; } void BOARD_INSPECTION_TOOL::doHideRatsnestNet( int aNetCode, bool aHide ) { KIGFX::PCB_RENDER_SETTINGS* rs = static_cast( m_toolMgr->GetView()->GetPainter()->GetSettings() ); PCB_SELECTION_TOOL* selectionTool = m_toolMgr->GetTool(); SELECTION& selection = selectionTool->GetSelection(); if( aNetCode <= 0 && !selection.Empty() ) { for( EDA_ITEM* item : selection ) { if( BOARD_CONNECTED_ITEM* bci = dynamic_cast( item ) ) { if( bci->GetNetCode() > 0 ) doHideRatsnestNet( bci->GetNetCode(), aHide ); } } return; } if( aHide ) rs->GetHiddenNets().insert( aNetCode ); else rs->GetHiddenNets().erase( aNetCode ); if( !m_frame->GetAppearancePanel()->IsTogglingNetclassRatsnestVisibility() ) { m_frame->GetCanvas()->RedrawRatsnest(); m_frame->GetCanvas()->Refresh(); m_frame->GetAppearancePanel()->OnNetVisibilityChanged( aNetCode, !aHide ); } } void BOARD_INSPECTION_TOOL::setTransitions() { Go( &BOARD_INSPECTION_TOOL::LocalRatsnestTool, PCB_ACTIONS::localRatsnestTool.MakeEvent() ); Go( &BOARD_INSPECTION_TOOL::HideLocalRatsnest, PCB_ACTIONS::hideLocalRatsnest.MakeEvent() ); Go( &BOARD_INSPECTION_TOOL::UpdateLocalRatsnest, PCB_ACTIONS::updateLocalRatsnest.MakeEvent() ); Go( &BOARD_INSPECTION_TOOL::ShowBoardStatistics, PCB_ACTIONS::boardStatistics.MakeEvent() ); Go( &BOARD_INSPECTION_TOOL::InspectClearance, PCB_ACTIONS::inspectClearance.MakeEvent() ); Go( &BOARD_INSPECTION_TOOL::InspectConstraints, PCB_ACTIONS::inspectConstraints.MakeEvent() ); Go( &BOARD_INSPECTION_TOOL::DiffFootprint, PCB_ACTIONS::diffFootprint.MakeEvent() ); Go( &BOARD_INSPECTION_TOOL::ShowFootprintLinks, PCB_ACTIONS::showFootprintAssociations.MakeEvent() ); Go( &BOARD_INSPECTION_TOOL::HighlightNet, PCB_ACTIONS::highlightNet.MakeEvent() ); Go( &BOARD_INSPECTION_TOOL::HighlightNet, PCB_ACTIONS::highlightNetSelection.MakeEvent() ); Go( &BOARD_INSPECTION_TOOL::HighlightNet, PCB_ACTIONS::toggleLastNetHighlight.MakeEvent() ); Go( &BOARD_INSPECTION_TOOL::ClearHighlight, PCB_ACTIONS::clearHighlight.MakeEvent() ); Go( &BOARD_INSPECTION_TOOL::HighlightNet, PCB_ACTIONS::toggleNetHighlight.MakeEvent() ); Go( &BOARD_INSPECTION_TOOL::HighlightItem, PCB_ACTIONS::highlightItem.MakeEvent() ); Go( &BOARD_INSPECTION_TOOL::HideNetInRatsnest, PCB_ACTIONS::hideNetInRatsnest.MakeEvent() ); Go( &BOARD_INSPECTION_TOOL::ShowNetInRatsnest, PCB_ACTIONS::showNetInRatsnest.MakeEvent() ); }