There are two netclass name methods, which previously were not obvious in their uses. These have been renamed to now have: GetName() : Used for internal or tooling (e.g. netlist export) usage GetHumanReadableName() : Used for display to users (e.g. in infobars) Fixing the previous unclear naming will result in fewer bugs when users start using the multiple netclass functionality, as the incorrect usage had started creeping in to new code. Also this will help authors of new code select the correct name method.
2166 lines
73 KiB
C++
2166 lines
73 KiB
C++
/*
|
|
* This program source code file is part of KiCad, a free EDA CAD application.
|
|
*
|
|
* Copyright The 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 <bitmaps.h>
|
|
#include <pcb_group.h>
|
|
#include <tool/tool_manager.h>
|
|
#include <tools/pcb_selection_tool.h>
|
|
#include <tools/pcb_picker_tool.h>
|
|
#include <tools/edit_tool.h>
|
|
#include <pcb_painter.h>
|
|
#include <connectivity/connectivity_data.h>
|
|
#include <drc/drc_engine.h>
|
|
#include <dialogs/dialog_board_statistics.h>
|
|
#include <dialogs/dialog_book_reporter.h>
|
|
#include <dialogs/panel_setup_rules_base.h>
|
|
#include <dialogs/dialog_footprint_associations.h>
|
|
#include <string_utils.h>
|
|
#include <tools/board_inspection_tool.h>
|
|
#include <fp_lib_table.h>
|
|
#include <pcb_shape.h>
|
|
#include <pcbnew_settings.h>
|
|
#include <widgets/appearance_controls.h>
|
|
#include <widgets/wx_html_report_box.h>
|
|
#include <widgets/footprint_diff_widget.h>
|
|
#include <drc/drc_item.h>
|
|
#include <pad.h>
|
|
#include <project_pcb.h>
|
|
#include <view/view_controls.h>
|
|
|
|
|
|
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<PCB_SELECTION_TOOL>();
|
|
|
|
std::shared_ptr<NET_CONTEXT_MENU> netSubMenu = std::make_shared<NET_CONTEXT_MENU>();
|
|
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<const PCB_SHAPE*>( 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<PCB_EDIT_FRAME>();
|
|
}
|
|
|
|
|
|
int BOARD_INSPECTION_TOOL::ShowBoardStatistics( const TOOL_EVENT& aEvent )
|
|
{
|
|
DIALOG_BOARD_STATISTICS dialog( m_frame );
|
|
dialog.ShowModal();
|
|
return 0;
|
|
}
|
|
|
|
|
|
std::unique_ptr<DRC_ENGINE> BOARD_INSPECTION_TOOL::makeDRCEngine( bool* aCompileError,
|
|
bool* aCourtyardError )
|
|
{
|
|
auto engine = std::make_unique<DRC_ENGINE>( 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<PAD*>( 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, true );
|
|
|
|
if( aItem->IsConnected() && !isNPTHPad( aItem ) )
|
|
{
|
|
BOARD_CONNECTED_ITEM* cItem = static_cast<BOARD_CONNECTED_ITEM*>( aItem );
|
|
|
|
msg += wxS( " " )
|
|
+ wxString::Format( _( "[netclass %s]" ),
|
|
cItem->GetEffectiveNetClass()->GetHumanReadableName() );
|
|
}
|
|
|
|
return msg;
|
|
};
|
|
|
|
|
|
void BOARD_INSPECTION_TOOL::reportCompileError( REPORTER* r )
|
|
{
|
|
r->Report( "" );
|
|
r->Report( _( "Report incomplete: could not compile custom design rules." )
|
|
+ wxS( " " )
|
|
+ wxS( "<a href='$CUSTOM_RULES'>" ) + _( "Show design rules." ) + wxS( "</a>" ) );
|
|
}
|
|
|
|
|
|
void BOARD_INSPECTION_TOOL::reportHeader( const wxString& aTitle, BOARD_ITEM* a, REPORTER* r )
|
|
{
|
|
r->Report( wxT( "<h7>" ) + EscapeHTML( aTitle ) + wxT( "</h7>" ) );
|
|
r->Report( wxT( "<ul><li>" ) + EscapeHTML( getItemDescription( a ) ) + wxT( "</li></ul>" ) );
|
|
}
|
|
|
|
|
|
void BOARD_INSPECTION_TOOL::reportHeader( const wxString& aTitle, BOARD_ITEM* a, BOARD_ITEM* b,
|
|
REPORTER* r )
|
|
{
|
|
r->Report( wxT( "<h7>" ) + EscapeHTML( aTitle ) + wxT( "</h7>" ) );
|
|
r->Report( wxT( "<ul><li>" ) + EscapeHTML( getItemDescription( a ) ) + wxT( "</li>" )
|
|
+ wxT( "<li>" ) + EscapeHTML( getItemDescription( b ) ) + wxT( "</li></ul>" ) );
|
|
}
|
|
|
|
|
|
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( "<h7>" ) + EscapeHTML( aTitle ) + wxT( "</h7>" ) );
|
|
r->Report( wxT( "<ul><li>" ) + EscapeHTML( layerStr ) + wxT( "</li>" )
|
|
+ wxT( "<li>" ) + EscapeHTML( getItemDescription( a ) ) + wxT( "</li>" )
|
|
+ wxT( "<li>" ) + EscapeHTML( getItemDescription( b ) ) + wxT( "</li></ul>" ) );
|
|
}
|
|
|
|
|
|
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( "<i>" ) + _( "undefined" ) + wxT( "</i>" );
|
|
}
|
|
|
|
|
|
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( "<i>" ) + _( "undefined" ) + wxT( "</i>" );
|
|
}
|
|
|
|
|
|
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( "<i>" ) + _( "undefined" ) + wxT( "</i>" );
|
|
}
|
|
|
|
|
|
wxString BOARD_INSPECTION_TOOL::InspectDRCErrorMenuText( const std::shared_ptr<RC_ITEM>& 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_TRACK_ANGLE
|
|
|| aDRCItem->GetErrorCode() == DRCE_TRACK_SEGMENT_LENGTH
|
|
|| 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<RC_ITEM>& 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<BOARD_CONNECTED_ITEM*>( a );
|
|
BOARD_CONNECTED_ITEM* bc = dynamic_cast<BOARD_CONNECTED_ITEM*>( b );
|
|
PCB_LAYER_ID layer = m_frame->GetActiveLayer();
|
|
|
|
if( aDRCItem->GetErrorCode() == DRCE_LIB_FOOTPRINT_MISMATCH )
|
|
{
|
|
if( FOOTPRINT* footprint = dynamic_cast<FOOTPRINT*>( 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<DRC_ENGINE> 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<BOARD_CONNECTED_ITEM*>( 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_TRACK_ANGLE:
|
|
r = dialog->AddHTMLPage( _( "Track Angle" ) );
|
|
reportHeader( _( "Track Angle resolution for:" ), a, r );
|
|
|
|
if( compileError )
|
|
reportCompileError( r );
|
|
|
|
constraint = drcEngine->EvalRules( TRACK_ANGLE_CONSTRAINT, a, b, layer, r );
|
|
|
|
r->Report( "" );
|
|
r->Report( wxString::Format( _( "Resolved angle constraints: min %s; max %s." ),
|
|
reportMin( m_frame, constraint ),
|
|
reportMax( m_frame, constraint ) ) );
|
|
break;
|
|
|
|
case DRCE_TRACK_SEGMENT_LENGTH:
|
|
r = dialog->AddHTMLPage( _( "Track Segment Length" ) );
|
|
reportHeader( _( "Track segment length resolution for:" ), a, r );
|
|
|
|
if( compileError )
|
|
reportCompileError( r );
|
|
|
|
constraint = drcEngine->EvalRules( TRACK_SEGMENT_LENGTH_CONSTRAINT, a, b, layer, r );
|
|
|
|
r->Report( "" );
|
|
r->Report( wxString::Format( _( "Resolved segment length 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<PAD*>( a )->GetAttribute() == PAD_ATTRIB::SMD )
|
|
{
|
|
PAD* pad = static_cast<PAD*>( a );
|
|
|
|
if( pad->IsOnLayer( F_Cu ) )
|
|
layer = F_Cu;
|
|
else
|
|
layer = B_Cu;
|
|
}
|
|
else if( b->Type() == PCB_PAD_T
|
|
&& static_cast<PAD*>( a )->GetAttribute() == PAD_ATTRIB::SMD )
|
|
{
|
|
PAD* pad = static_cast<PAD*>( 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<PCB_SELECTION_TOOL>();
|
|
|
|
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;
|
|
}
|
|
|
|
if( !selection.GetItem( 0 )->IsBOARD_ITEM() || !selection.GetItem( 1 )->IsBOARD_ITEM() )
|
|
return 0;
|
|
|
|
BOARD_ITEM* a = static_cast<BOARD_ITEM*>( selection.GetItem( 0 ) );
|
|
BOARD_ITEM* b = static_cast<BOARD_ITEM*>( selection.GetItem( 1 ) );
|
|
|
|
if( a->Type() == PCB_GROUP_T )
|
|
{
|
|
PCB_GROUP* ag = static_cast<PCB_GROUP*>( 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<PCB_GROUP*>( 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<FOOTPRINT*>( a ) );
|
|
|
|
if( b->Type() == PCB_FOOTPRINT_T )
|
|
b = checkFootprint( static_cast<FOOTPRINT*>( 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<BOARD_CONNECTED_ITEM*>( a );
|
|
BOARD_CONNECTED_ITEM* bc = dynamic_cast<BOARD_CONNECTED_ITEM*>( b );
|
|
ZONE* zone = dynamic_cast<ZONE*>( a );
|
|
PAD* pad = dynamic_cast<PAD*>( b );
|
|
FOOTPRINT* aFP = dynamic_cast<FOOTPRINT*>( a );
|
|
FOOTPRINT* bFP = dynamic_cast<FOOTPRINT*>( b );
|
|
DRC_CONSTRAINT constraint;
|
|
int clearance = 0;
|
|
|
|
bool compileError = false;
|
|
bool courtyardError = false;
|
|
std::unique_ptr<DRC_ENGINE> 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_DATA> 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<PCB_SELECTION_TOOL>();
|
|
|
|
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();
|
|
|
|
if( !selection.GetItem( 0 )->IsBOARD_ITEM() )
|
|
return 0;
|
|
|
|
BOARD_ITEM* item = static_cast<BOARD_ITEM*>( selection.GetItem( 0 ) );
|
|
DRC_CONSTRAINT constraint;
|
|
|
|
bool compileError = false;
|
|
bool courtyardError = false;
|
|
std::unique_ptr<DRC_ENGINE> drcEngine = makeDRCEngine( &compileError, &courtyardError );
|
|
|
|
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<PAD*>( 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( "<a href='$DRC'>" ) + _( "Run DRC for a full analysis." )
|
|
+ wxS( "</a>" ) );
|
|
}
|
|
|
|
r->Report( "" );
|
|
|
|
if( constraint.m_DisallowFlags )
|
|
r->Report( _( "Item <b>disallowed</b> 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( "<a href='$DRC'>" ) + _( "Run DRC for a full analysis." )
|
|
+ wxS( "</a>" ) );
|
|
}
|
|
|
|
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<PCB_SELECTION_TOOL>();
|
|
|
|
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<FOOTPRINT*>( item ) )
|
|
aCollector.Remove( item );
|
|
}
|
|
},
|
|
false /* ignore locked flag */ );
|
|
|
|
if( selection.Size() == 1 )
|
|
DiffFootprint( static_cast<FOOTPRINT*>( 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<PCB_SELECTION_TOOL>();
|
|
|
|
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<FOOTPRINT*>( 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( "<h7>" ) + _( "Board vs library diff for:" ) + wxS( "</h7>" ) );
|
|
r->Report( wxS( "<ul><li>" ) + EscapeHTML( getItemDescription( aFootprint ) ) + wxS( "</li>" )
|
|
+ wxS( "<li>" ) + _( "Library: " ) + EscapeHTML( libName ) + wxS( "</li>" )
|
|
+ wxS( "<li>" ) + _( "Library item: " ) + EscapeHTML( fpName ) + wxS( "</li></ul>" ) );
|
|
|
|
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( "<a href='$CONFIG'>" ) + _( "Manage Footprint Libraries" )
|
|
+ wxS( "</a>" ) );
|
|
|
|
}
|
|
else if( !libTable->HasLibrary( libName, true ) )
|
|
{
|
|
r->Report( _( "The library is not enabled in the current configuration." )
|
|
+ wxS( "  " )
|
|
+ wxS( "<a href='$CONFIG'>" ) + _( "Manage Footprint Libraries" )
|
|
+ wxS( "</a>" ) );
|
|
|
|
}
|
|
else
|
|
{
|
|
std::shared_ptr<FOOTPRINT> 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<BOARD_ITEM*>();
|
|
|
|
m_frame->m_probingSchToPcb = true; // recursion guard
|
|
{
|
|
m_toolMgr->RunAction( PCB_ACTIONS::selectionClear );
|
|
|
|
if( item )
|
|
m_toolMgr->RunAction<EDA_ITEM*>( 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<BOARD*>( m_toolMgr->GetModel() );
|
|
KIGFX::RENDER_SETTINGS* settings = getView()->GetPainter()->GetSettings();
|
|
PCB_SELECTION_TOOL* selectionTool = m_toolMgr->GetTool<PCB_SELECTION_TOOL>();
|
|
|
|
int net = -1;
|
|
bool enableHighlight = false;
|
|
|
|
if( aUseSelection )
|
|
{
|
|
const PCB_SELECTION& selection = selectionTool->GetSelection();
|
|
std::set<int> netcodes;
|
|
|
|
for( EDA_ITEM* item : selection )
|
|
{
|
|
if( BOARD_CONNECTED_ITEM* ci = dynamic_cast<BOARD_CONNECTED_ITEM*>( 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<PCB_LAYER_ID>( 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<BOARD_CONNECTED_ITEM*>( collector[0] );
|
|
|
|
if( targetItem->Type() == PCB_PAD_T )
|
|
m_frame->SendCrossProbeItem( targetItem );
|
|
|
|
net = targetItem->GetNetCode();
|
|
}
|
|
}
|
|
|
|
const std::set<int>& 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<MSG_PANEL_ITEM> 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<int>();
|
|
KIGFX::RENDER_SETTINGS* settings = m_toolMgr->GetView()->GetPainter()->GetSettings();
|
|
const std::set<int>& 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<int> 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<BOARD*>( 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<PCB_PICKER_TOOL>();
|
|
BOARD* board = getModel<BOARD>();
|
|
|
|
// 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<PCB_SELECTION_TOOL>();
|
|
|
|
m_toolMgr->RunAction( PCB_ACTIONS::selectionClear );
|
|
m_toolMgr->RunAction<CLIENT_SELECTION_FILTER>( PCB_ACTIONS::selectionCursor,
|
|
EDIT_TOOL::PadFilter );
|
|
|
|
PCB_SELECTION& selection = selectionTool->GetSelection();
|
|
|
|
if( selection.Empty() )
|
|
{
|
|
m_toolMgr->RunAction<CLIENT_SELECTION_FILTER>( 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<PAD*>( item) )
|
|
{
|
|
pad->SetLocalRatsnestVisible( !pad->GetLocalRatsnestVisible() );
|
|
}
|
|
else if( FOOTPRINT* fp = dyn_cast<FOOTPRINT*>( 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<VECTOR2I>();
|
|
|
|
if( delta == VECTOR2I() )
|
|
{
|
|
// We can delete the existing map to force a recalculation
|
|
delete m_dynamicData;
|
|
m_dynamicData = nullptr;
|
|
}
|
|
|
|
auto selectionTool = m_toolMgr->GetTool<PCB_SELECTION_TOOL>();
|
|
auto& selection = selectionTool->GetSelection();
|
|
auto connectivity = getModel<BOARD>()->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<BOARD>()->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<PCB_SELECTION_TOOL>();
|
|
SELECTION& selection = selectionTool->GetSelection();
|
|
std::shared_ptr<CONNECTIVITY_DATA> connectivity = board()->GetConnectivity();
|
|
std::vector<BOARD_ITEM*> items;
|
|
std::deque<EDA_ITEM*> queued_items( selection.begin(), selection.end() );
|
|
|
|
for( std::size_t i = 0; i < queued_items.size(); ++i )
|
|
{
|
|
if( !queued_items[i]->IsBOARD_ITEM() )
|
|
continue;
|
|
|
|
BOARD_ITEM* item = static_cast<BOARD_ITEM*>( queued_items[i] );
|
|
|
|
wxCHECK2( item, continue );
|
|
|
|
if( item->Type() == PCB_FOOTPRINT_T )
|
|
{
|
|
for( PAD* pad : static_cast<FOOTPRINT*>( 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<BOARD_CONNECTED_ITEM*>( 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<int>(), true );
|
|
return 0;
|
|
}
|
|
|
|
|
|
int BOARD_INSPECTION_TOOL::ShowNetInRatsnest( const TOOL_EVENT& aEvent )
|
|
{
|
|
doHideRatsnestNet( aEvent.Parameter<int>(), false );
|
|
return 0;
|
|
}
|
|
|
|
|
|
void BOARD_INSPECTION_TOOL::doHideRatsnestNet( int aNetCode, bool aHide )
|
|
{
|
|
KIGFX::PCB_RENDER_SETTINGS* rs = static_cast<KIGFX::PCB_RENDER_SETTINGS*>(
|
|
m_toolMgr->GetView()->GetPainter()->GetSettings() );
|
|
|
|
PCB_SELECTION_TOOL* selectionTool = m_toolMgr->GetTool<PCB_SELECTION_TOOL>();
|
|
SELECTION& selection = selectionTool->GetSelection();
|
|
|
|
if( aNetCode <= 0 && !selection.Empty() )
|
|
{
|
|
for( EDA_ITEM* item : selection )
|
|
{
|
|
if( BOARD_CONNECTED_ITEM* bci = dynamic_cast<BOARD_CONNECTED_ITEM*>( 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() );
|
|
}
|