kicad/pcbnew/drc/drc.cpp

1316 lines
43 KiB
C++
Raw Normal View History

2007-12-01 03:42:52 +00:00
/*
* This program source code file is part of KiCad, a free EDA CAD application.
2007-12-01 03:42:52 +00:00
*
* Copyright (C) 2004-2019 Jean-Pierre Charras, jp.charras at wanadoo.fr
* Copyright (C) 2014 Dick Hollenbeck, dick@softplc.com
* Copyright (C) 2017-2020 KiCad Developers, see change_log.txt for contributors.
2007-12-01 03:42:52 +00:00
*
* 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 <fctsys.h>
2018-01-29 20:58:58 +00:00
#include <pcb_edit_frame.h>
#include <trigo.h>
2018-02-02 20:57:12 +00:00
#include <board_design_settings.h>
#include <class_edge_mod.h>
#include <class_drawsegment.h>
#include <class_module.h>
#include <class_track.h>
#include <class_pad.h>
#include <class_zone.h>
#include <class_pcb_text.h>
#include <geometry/seg.h>
#include <math_for_graphics.h>
#include <connectivity/connectivity_algo.h>
#include <bitmaps.h>
#include <tool/tool_manager.h>
#include <tools/pcb_actions.h>
#include <tools/pcb_tool_base.h>
#include <tools/zone_filler_tool.h>
#include <kiface_i.h>
#include <pcbnew.h>
#include <netlist_reader/pcb_netlist.h>
#include <math/util.h> // for KiROUND
#include <dialog_drc.h>
2020-08-11 13:33:16 +00:00
#include <wx/progdlg.h>
#include <board_commit.h>
#include <geometry/shape_segment.h>
#include <geometry/shape_arc.h>
#include <drc/drc.h>
2020-05-18 00:20:16 +00:00
#include <drc/drc_rule_parser.h>
#include <drc/drc_item.h>
#include <drc/drc_courtyard_tester.h>
#include <drc/drc_drilled_hole_tester.h>
2020-05-18 00:20:16 +00:00
#include <drc/drc_keepout_tester.h>
#include <drc/drc_textvar_tester.h>
#include <drc/footprint_tester.h>
#include <dialogs/panel_setup_rules.h>
#include <reporter.h>
#include <widgets/app_progress_dialog.h>
DRC::DRC() :
2020-01-13 19:46:39 +00:00
PCB_TOOL_BASE( "pcbnew.DRCTool" ),
m_editFrame( nullptr ),
2020-01-13 19:46:39 +00:00
m_pcb( nullptr ),
m_board_outline_valid( false ),
m_drcDialog( nullptr ),
m_largestClearance( 0 )
{
// establish initial values for everything:
m_doUnconnectedTest = true; // enable unconnected tests
m_testTracksAgainstZones = false; // disable zone to items clearance tests
m_doKeepoutTest = true; // enable keepout areas to items clearance tests
m_refillZones = false; // Only fill zones if requested by user.
m_reportAllTrackErrors = false;
m_testFootprints = false;
m_drcRun = false;
m_footprintsTested = false;
}
DRC::~DRC()
{
}
void DRC::Reset( RESET_REASON aReason )
{
m_editFrame = getEditFrame<PCB_EDIT_FRAME>();
if( m_pcb != m_editFrame->GetBoard() )
{
if( m_drcDialog )
DestroyDRCDialog( wxID_OK );
m_pcb = m_editFrame->GetBoard();
}
}
void DRC::ShowDRCDialog( wxWindow* aParent )
2007-12-01 03:42:52 +00:00
{
bool show_dlg_modal = true;
// the dialog needs a parent frame. if it is not specified, this is
// the PCB editor frame specified in DRC class.
if( !aParent )
{
// if any parent is specified, the dialog is modal.
// if this is the default PCB editor frame, it is not modal
show_dlg_modal = false;
aParent = m_editFrame;
}
Activate();
m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
if( !m_drcDialog )
2007-12-01 03:42:52 +00:00
{
m_drcDialog = new DIALOG_DRC( this, m_editFrame, aParent );
2007-12-04 18:23:38 +00:00
updatePointers();
2008-02-09 08:34:45 +00:00
if( show_dlg_modal )
m_drcDialog->ShowModal();
else
m_drcDialog->Show( true );
2007-12-04 18:23:38 +00:00
}
else // The dialog is just not visible (because the user has double clicked on an error item)
{
2007-12-04 18:23:38 +00:00
updatePointers();
m_drcDialog->Show( true );
}
2007-12-01 03:42:52 +00:00
}
int DRC::ShowDRCDialog( const TOOL_EVENT& aEvent )
{
ShowDRCDialog( nullptr );
return 0;
}
bool DRC::IsDRCDialogShown()
{
if( m_drcDialog )
return m_drcDialog->IsShown();
return false;
}
2020-05-18 00:20:16 +00:00
void DRC::addMarkerToPcb( BOARD_COMMIT& aCommit, MARKER_PCB* aMarker )
{
if( m_pcb->GetDesignSettings().Ignore( aMarker->GetRCItem()->GetErrorCode() ) )
{
delete aMarker;
return;
}
2020-05-18 00:20:16 +00:00
aCommit.Add( aMarker );
}
2007-12-01 03:42:52 +00:00
void DRC::DestroyDRCDialog( int aReason )
2007-12-01 03:42:52 +00:00
{
if( m_drcDialog )
2007-12-01 03:42:52 +00:00
{
m_drcDialog->Destroy();
m_drcDialog = nullptr;
2007-12-01 03:42:52 +00:00
}
}
2007-11-27 01:34:35 +00:00
bool DRC::LoadRules()
{
wxString rulesFilepath = m_editFrame->Prj().AbsolutePath( "drc-rules" );
wxFileName rulesFile( rulesFilepath );
if( rulesFile.FileExists() )
{
m_rules.clear();
FILE* fp = wxFopen( rulesFilepath, wxT( "rt" ) );
if( fp )
{
try
{
DRC_RULES_PARSER parser( m_pcb, fp, rulesFilepath );
parser.Parse( m_rules, &NULL_REPORTER::GetInstance() );
}
catch( PARSE_ERROR& pe )
{
// Don't leave possibly malformed stuff around for us to trip over
m_rules.clear();
wxSafeYield( m_editFrame );
m_editFrame->ShowBoardSetupDialog( _( "Rules" ), pe.What(), ID_RULES_EDITOR,
pe.lineNumber, pe.byteIndex );
return false;
}
}
}
std::reverse( std::begin( m_rules ), std::end( m_rules ) );
BOARD_DESIGN_SETTINGS& bds = m_pcb->GetDesignSettings();
bds.m_DRCRules = m_rules;
return true;
}
void DRC::RunTests( wxTextCtrl* aMessages )
2007-12-01 03:42:52 +00:00
{
// Make absolutely sure these are up-to-date
if( !LoadRules() )
return;
wxASSERT( m_pcb == m_editFrame->GetBoard() );
BOARD_COMMIT commit( m_editFrame );
2020-05-18 00:20:16 +00:00
BOARD_DESIGN_SETTINGS& bds = m_pcb->GetDesignSettings();
m_largestClearance = bds.GetBiggestClearanceValue();
if( !bds.Ignore( DRCE_INVALID_OUTLINE )
|| !bds.Ignore( DRCE_COPPER_EDGE_CLEARANCE ) )
{
2020-05-18 00:20:16 +00:00
if( aMessages )
{
aMessages->AppendText( _( "Board Outline...\n" ) );
wxSafeYield();
}
2020-05-18 00:20:16 +00:00
testOutline( commit );
}
2008-02-09 08:34:45 +00:00
2007-12-01 03:42:52 +00:00
// test pad to pad clearances, nothing to do with tracks, vias or zones.
if( !bds.Ignore( DRCE_COPPER_EDGE_CLEARANCE ) )
{
2009-09-10 15:22:26 +00:00
if( aMessages )
{
aMessages->AppendText( _( "Pad clearances...\n" ) );
2009-09-10 15:22:26 +00:00
wxSafeYield();
}
testPadClearances( commit );
}
2007-12-01 03:42:52 +00:00
2020-05-18 00:20:16 +00:00
// test drilled holes
if( !bds.Ignore( DRCE_DRILLED_HOLES_TOO_CLOSE )
|| !bds.Ignore( DRCE_TOO_SMALL_DRILL )
2020-05-18 00:20:16 +00:00
|| !bds.Ignore( DRCE_TOO_SMALL_MICROVIA_DRILL ) )
{
2020-05-18 00:20:16 +00:00
if( aMessages )
{
aMessages->AppendText( _( "Drill sizes and clearances...\n" ) );
wxSafeYield();
}
2020-05-18 00:20:16 +00:00
DRC_DRILLED_HOLE_TESTER tester( [&]( MARKER_PCB* aMarker )
{
addMarkerToPcb( commit, aMarker );
} );
tester.RunDRC( userUnits(), *m_pcb );
}
// caller (a wxTopLevelFrame) is the wxDialog or the Pcb Editor frame that call DRC:
wxWindow* caller = aMessages ? aMessages->GetParent() : m_editFrame;
if( m_refillZones )
{
if( aMessages )
aMessages->AppendText( _( "Refilling all zones...\n" ) );
m_toolMgr->GetTool<ZONE_FILLER_TOOL>()->FillAllZones( caller );
}
else
{
if( aMessages )
aMessages->AppendText( _( "Checking zone fills...\n" ) );
m_toolMgr->GetTool<ZONE_FILLER_TOOL>()->CheckAllZones( caller );
}
// test track and via clearances to other tracks, pads, and vias
if( aMessages )
{
aMessages->AppendText( _( "Track clearances...\n" ) );
wxSafeYield();
}
testTracks( commit, aMessages ? aMessages->GetParent() : m_editFrame, true );
// test zone clearances to other zones
if( aMessages )
2009-09-10 15:22:26 +00:00
{
aMessages->AppendText( _( "Zone to zone clearances...\n" ) );
2009-09-10 15:22:26 +00:00
wxSafeYield();
}
2020-05-18 00:20:16 +00:00
testZones( commit );
2007-12-01 03:42:52 +00:00
2008-02-09 08:34:45 +00:00
// find and gather unconnected pads.
2020-05-18 00:20:16 +00:00
if( m_doUnconnectedTest
&& !bds.Ignore( DRCE_UNCONNECTED_ITEMS ) )
{
2009-09-10 15:22:26 +00:00
if( aMessages )
{
aMessages->AppendText( _( "Unconnected pads...\n" ) );
aMessages->Refresh();
2009-09-10 15:22:26 +00:00
}
2007-12-01 03:42:52 +00:00
testUnconnected();
}
2008-02-09 08:34:45 +00:00
// find and gather vias, tracks, pads inside keepout areas.
if( m_doKeepoutTest )
{
if( aMessages )
{
aMessages->AppendText( _( "Keepout areas ...\n" ) );
aMessages->Refresh();
}
2020-05-18 00:20:16 +00:00
DRC_KEEPOUT_TESTER tester( [&]( MARKER_PCB* aMarker )
{
addMarkerToPcb( commit, aMarker );
} );
tester.RunDRC( userUnits(), *m_pcb );
}
// find and gather vias, tracks, pads inside text boxes.
if( !bds.Ignore( DRCE_CLEARANCE ) )
{
2020-05-18 00:20:16 +00:00
if( aMessages )
{
aMessages->AppendText( _( "Text and graphic clearances...\n" ) );
wxSafeYield();
}
2020-05-18 00:20:16 +00:00
testCopperTextAndGraphics( commit );
}
// test courtyards
2020-05-18 00:20:16 +00:00
if( !bds.Ignore( DRCE_OVERLAPPING_FOOTPRINTS )
|| !bds.Ignore( DRCE_MISSING_COURTYARD )
|| !bds.Ignore( DRCE_MALFORMED_COURTYARD )
|| !bds.Ignore( DRCE_PTH_IN_COURTYARD )
|| !bds.Ignore( DRCE_NPTH_IN_COURTYARD ) )
{
if( aMessages )
{
aMessages->AppendText( _( "Courtyard areas...\n" ) );
aMessages->Refresh();
}
2020-05-18 00:20:16 +00:00
DRC_COURTYARD_TESTER tester( [&]( MARKER_PCB* aMarker )
{
addMarkerToPcb( commit, aMarker );
} );
tester.RunDRC( userUnits(), *m_pcb );
}
m_footprints.clear();
m_footprintsTested = false;
if( m_testFootprints && !Kiface().IsSingle() )
{
if( aMessages )
{
aMessages->AppendText( _( "Checking footprints against schematic...\n" ) );
aMessages->Refresh();
}
NETLIST netlist;
m_editFrame->FetchNetlistFromSchematic( netlist, PCB_EDIT_FRAME::ANNOTATION_DIALOG );
if( m_drcDialog )
m_drcDialog->Raise();
TestFootprints( netlist, m_pcb, m_footprints );
m_footprintsTested = true;
}
// Check if there are items on disabled layers
2020-05-18 00:20:16 +00:00
if( !bds.Ignore( DRCE_DISABLED_LAYER_ITEM ) )
{
2020-05-18 00:20:16 +00:00
if( aMessages )
{
aMessages->AppendText( _( "Items on disabled layers...\n" ) );
aMessages->Refresh();
}
testDisabledLayers( commit );
}
2020-05-18 00:20:16 +00:00
if( !bds.Ignore( DRCE_UNRESOLVED_VARIABLE ) )
{
if( aMessages )
{
aMessages->AppendText( _( "Unresolved text variables...\n" ) );
aMessages->Refresh();
}
DRC_TEXTVAR_TESTER tester( [&]( MARKER_PCB* aMarker )
{
addMarkerToPcb( commit, aMarker );
},
m_editFrame->GetCanvas()->GetWorksheet() );
2020-05-18 00:20:16 +00:00
tester.RunDRC( userUnits(), *m_pcb );
}
commit.Push( wxEmptyString, false, false );
m_drcRun = true;
// update the m_drcDialog listboxes
2007-12-03 05:14:51 +00:00
updatePointers();
2009-09-10 15:22:26 +00:00
if( aMessages )
{
// no newline on this one because it is last, don't want the window
// to unnecessarily scroll.
aMessages->AppendText( _( "Finished" ) );
2009-09-10 15:22:26 +00:00
}
2007-12-03 05:14:51 +00:00
}
void DRC::updatePointers()
{
// update my pointers, m_editFrame is the only unchangeable one
m_pcb = m_editFrame->GetBoard();
2007-12-04 18:23:38 +00:00
m_editFrame->ResolveDRCExclusions();
if( m_drcDialog ) // Use diag list boxes only in DRC dialog
2008-02-09 08:34:45 +00:00
{
m_drcDialog->SetMarkersProvider( new BOARD_DRC_ITEMS_PROVIDER( m_pcb ) );
m_drcDialog->SetUnconnectedProvider( new RATSNEST_DRC_ITEMS_PROVIDER( m_editFrame,
&m_unconnected ) );
m_drcDialog->SetFootprintsProvider( new VECTOR_DRC_ITEMS_PROVIDER( m_editFrame,
&m_footprints ) );
2008-02-09 08:34:45 +00:00
}
2007-12-03 05:14:51 +00:00
}
2008-02-09 08:34:45 +00:00
2007-12-03 05:14:51 +00:00
void DRC::testPadClearances( BOARD_COMMIT& aCommit )
2007-12-01 03:42:52 +00:00
{
BOARD_DESIGN_SETTINGS& bds = m_pcb->GetDesignSettings();
std::vector<D_PAD*> sortedPads;
2008-03-18 04:04:17 +00:00
m_pcb->GetSortedPadListByXthenYCoord( sortedPads );
2007-12-01 03:42:52 +00:00
2019-06-04 10:45:43 +00:00
if( sortedPads.empty() )
return;
// find the max size of the pads (used to stop the pad-to-pad tests)
int max_size = 0;
2008-03-18 04:04:17 +00:00
2019-06-04 10:45:43 +00:00
for( D_PAD* pad : sortedPads )
2007-12-01 03:42:52 +00:00
{
2012-02-19 04:02:19 +00:00
// GetBoundingRadius() is the radius of the minimum sized circle fully containing the pad
int radius = pad->GetBoundingRadius();
2012-02-19 04:02:19 +00:00
if( radius > max_size )
max_size = radius;
2007-12-01 03:42:52 +00:00
}
2020-05-18 00:20:16 +00:00
// Better to be fast than accurate; this keeps us from having to look up / calculate the
// actual clearances
max_size += m_largestClearance;
// Upper limit of pad list (limit not included)
D_PAD** listEnd = &sortedPads[0] + sortedPads.size();
2008-03-18 04:04:17 +00:00
// Test the pads
2019-06-04 10:45:43 +00:00
for( auto& pad : sortedPads )
2007-12-01 03:42:52 +00:00
{
if( !bds.Ignore( DRCE_COPPER_EDGE_CLEARANCE ) && m_board_outline_valid )
{
int minClearance = bds.m_CopperEdgeClearance;
m_clearanceSource = _( "board edge" );
static DRAWSEGMENT dummyEdge;
dummyEdge.SetParent( m_pcb );
dummyEdge.SetLayer( Edge_Cuts );
pad->GetRuleClearance( &dummyEdge, pad->GetLayer(), &minClearance, &m_clearanceSource );
for( auto it = m_board_outlines.IterateSegmentsWithHoles(); it; it++ )
{
SHAPE_SEGMENT edge( *it );
int actual;
if( pad->Collide( &edge, minClearance, &actual ) )
{
2020-08-11 13:33:16 +00:00
std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_COPPER_EDGE_CLEARANCE );
2008-02-09 08:34:45 +00:00
m_msg.Printf( drcItem->GetErrorText() + _( " (%s clearance %s; actual %s)" ),
m_clearanceSource,
MessageTextFromValue( userUnits(), minClearance, true ),
MessageTextFromValue( userUnits(), actual, true ) );
drcItem->SetErrorMessage( m_msg );
drcItem->SetItems( pad );
MARKER_PCB* marker = new MARKER_PCB( drcItem, pad->GetPosition() );
addMarkerToPcb( aCommit, marker );
break;
}
}
}
if( !bds.Ignore( DRCE_CLEARANCE ) )
{
int x_limit = pad->GetPosition().x + pad->GetBoundingRadius() + max_size;
doPadToPadsDrc( aCommit, pad, &pad, listEnd, x_limit );
}
2007-12-01 03:42:52 +00:00
}
}
2007-11-27 01:34:35 +00:00
2020-05-18 00:20:16 +00:00
void DRC::testTracks( BOARD_COMMIT& aCommit, wxWindow *aActiveWindow, bool aShowProgressBar )
{
APP_PROGRESS_DIALOG* progressDialog = NULL;
const int delta = 500; // This is the number of tests between 2 calls to the
// progress bar
int count = m_pcb->Tracks().size();
int deltamax = count/delta;
if( aShowProgressBar && deltamax > 3 )
{
// Do not use wxPD_APP_MODAL style here: it is not necessary and create issues
// on OSX
progressDialog = new APP_PROGRESS_DIALOG( _( "Track clearances" ), wxEmptyString,
deltamax, aActiveWindow, false,
2020-08-11 13:33:16 +00:00
wxPD_AUTO_HIDE | wxPD_CAN_ABORT | wxPD_ELAPSED_TIME );
progressDialog->Update( 0, wxEmptyString );
}
std::shared_ptr<CONNECTIVITY_DATA> connectivity = m_pcb->GetConnectivity();
BOARD_DESIGN_SETTINGS& settings = m_pcb->GetDesignSettings();
if( !m_pcb->GetDesignSettings().Ignore( DRCE_DANGLING_TRACK )
|| !m_pcb->GetDesignSettings().Ignore( DRCE_DANGLING_VIA ) )
{
connectivity->Clear();
connectivity->Build( m_pcb ); // just in case. This really needs to be reliable.
}
int ii = 0;
count = 0;
2019-05-31 02:30:28 +00:00
for( auto seg_it = m_pcb->Tracks().begin(); seg_it != m_pcb->Tracks().end(); seg_it++ )
{
if( ii++ > delta )
{
ii = 0;
count++;
if( progressDialog )
{
if( !progressDialog->Update( count, wxEmptyString ) )
break; // Aborted by user
#ifdef __WXMAC__
// Work around a dialog z-order issue on OS X
if( count == deltamax )
aActiveWindow->Raise();
#endif
}
}
// Test new segment against tracks and pads, optionally against copper zones
LSEQ layer_seq = ( *seg_it )->GetLayerSet().Seq();
if( ( *seg_it )->Type() == PCB_VIA_T )
doSingleViaDRC( aCommit, static_cast<VIA*>( *seg_it ) );
else
doSingleTrackDRC( aCommit, *seg_it );
for( PCB_LAYER_ID layer : layer_seq )
doTrackDrc( aCommit, *seg_it, seg_it + 1, m_pcb->Tracks().end(), m_testTracksAgainstZones, layer );
// Test for dangling items
int code = (*seg_it)->Type() == PCB_VIA_T ? DRCE_DANGLING_VIA : DRCE_DANGLING_TRACK;
wxPoint pos;
if( !settings.Ignore( code ) && connectivity->TestTrackEndpointDangling( *seg_it, &pos ) )
{
2020-08-11 13:33:16 +00:00
std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( code );
drcItem->SetItems( *seg_it );
MARKER_PCB* marker = new MARKER_PCB( drcItem, pos );
2020-05-18 00:20:16 +00:00
addMarkerToPcb( aCommit, marker );
}
}
if( progressDialog )
progressDialog->Destroy();
}
2007-12-01 03:42:52 +00:00
void DRC::testUnconnected()
{
m_unconnected.clear();
2017-03-22 13:43:10 +00:00
auto connectivity = m_pcb->GetConnectivity();
2017-03-22 15:47:15 +00:00
connectivity->Clear();
connectivity->Build( m_pcb ); // just in case. This really needs to be reliable.
2017-03-22 15:47:15 +00:00
connectivity->RecalculateRatsnest();
2007-11-27 01:34:35 +00:00
2017-03-22 15:47:15 +00:00
std::vector<CN_EDGE> edges;
connectivity->GetUnconnectedEdges( edges );
2020-05-18 00:20:16 +00:00
for( const CN_EDGE& edge : edges )
2007-12-01 03:42:52 +00:00
{
2020-08-11 13:33:16 +00:00
std::shared_ptr<DRC_ITEM> item = DRC_ITEM::Create( DRCE_UNCONNECTED_ITEMS );
item->SetItems( edge.GetSourceNode()->Parent(), edge.GetTargetNode()->Parent() );
m_unconnected.push_back( item );
2017-03-22 15:47:15 +00:00
}
2007-12-01 03:42:52 +00:00
}
2007-11-27 01:34:35 +00:00
2020-05-18 00:20:16 +00:00
void DRC::testZones( BOARD_COMMIT& aCommit )
2007-12-01 03:42:52 +00:00
{
BOARD_DESIGN_SETTINGS& bds = m_pcb->GetDesignSettings();
// Test copper areas for valid netcodes
2008-02-09 08:34:45 +00:00
// if a netcode is < 0 the netname was not found when reading a netlist
// if a netcode is == 0 the netname is void, and the zone is not connected.
// This is allowed, but i am not sure this is a good idea
//
// In recent Pcbnew versions, the netcode is always >= 0, but an internal net name
// is stored, and initialized from the file or the zone properties editor.
// if it differs from the net name from net code, there is a DRC issue
std::vector<SHAPE_POLY_SET> smoothed_polys;
smoothed_polys.resize( m_pcb->GetAreaCount() );
for( int ii = 0; ii < m_pcb->GetAreaCount(); ii++ )
{
ZONE_CONTAINER* zone = m_pcb->GetArea( ii );
if( !bds.Ignore( DRCE_ZONE_HAS_EMPTY_NET ) && zone->IsOnCopperLayer() )
{
int netcode = zone->GetNetCode();
// a netcode < 0 or > 0 and no pad in net is a error or strange
// perhaps a "dead" net, which happens when all pads in this net were removed
// Remark: a netcode < 0 should not happen (this is more a bug somewhere)
int pads_in_net = ( netcode > 0 ) ? m_pcb->GetConnectivity()->GetPadCount( netcode ) : 1;
if( ( netcode < 0 ) || pads_in_net == 0 )
{
2020-08-11 13:33:16 +00:00
std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_ZONE_HAS_EMPTY_NET );
drcItem->SetItems( zone );
MARKER_PCB* marker = new MARKER_PCB( drcItem, zone->GetPosition() );
2020-05-18 00:20:16 +00:00
addMarkerToPcb( aCommit, marker );
}
2008-02-09 08:34:45 +00:00
}
ZONE_CONTAINER* zoneRef = m_pcb->GetArea( ii );
zoneRef->BuildSmoothedPoly( smoothed_polys[ii], zoneRef->GetLayer() );
2008-02-09 08:34:45 +00:00
}
// iterate through all areas
for( int ia = 0; ia < m_pcb->GetAreaCount(); ia++ )
{
ZONE_CONTAINER* zoneRef = m_pcb->GetArea( ia );
if( !zoneRef->IsOnCopperLayer() )
continue;
// If we are testing a single zone, then iterate through all other zones
// Otherwise, we have already tested the zone combination
for( int ia2 = ia + 1; ia2 < m_pcb->GetAreaCount(); ia2++ )
{
ZONE_CONTAINER* zoneToTest = m_pcb->GetArea( ia2 );
if( zoneRef == zoneToTest )
continue;
// test for same layer
if( zoneRef->GetLayer() != zoneToTest->GetLayer() )
continue;
// Test for same net
if( zoneRef->GetNetCode() == zoneToTest->GetNetCode() && zoneRef->GetNetCode() >= 0 )
continue;
// test for different priorities
if( zoneRef->GetPriority() != zoneToTest->GetPriority() )
continue;
// test for different types
if( zoneRef->GetIsKeepout() != zoneToTest->GetIsKeepout() )
continue;
// Examine a candidate zone: compare zoneToTest to zoneRef
// Get clearance used in zone to zone test. The policy used to
// obtain that value is now part of the zone object itself by way of
// ZONE_CONTAINER::GetClearance().
int zone2zoneClearance = zoneRef->GetClearance( zoneRef->GetLayer(), zoneToTest,
&m_clearanceSource );
// Keepout areas have no clearance, so set zone2zoneClearance to 1
// ( zone2zoneClearance = 0 can create problems in test functions)
if( zoneRef->GetIsKeepout() )
zone2zoneClearance = 1;
// test for some corners of zoneRef inside zoneToTest
for( auto iterator = smoothed_polys[ia].IterateWithHoles(); iterator; iterator++ )
{
VECTOR2I currentVertex = *iterator;
wxPoint pt( currentVertex.x, currentVertex.y );
if( smoothed_polys[ia2].Contains( currentVertex ) )
{
2020-08-11 13:33:16 +00:00
std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_ZONES_INTERSECT );
drcItem->SetItems( zoneRef, zoneToTest );
MARKER_PCB* marker = new MARKER_PCB( drcItem, pt );
addMarkerToPcb( aCommit, marker );
}
}
// test for some corners of zoneToTest inside zoneRef
for( auto iterator = smoothed_polys[ia2].IterateWithHoles(); iterator; iterator++ )
{
VECTOR2I currentVertex = *iterator;
wxPoint pt( currentVertex.x, currentVertex.y );
if( smoothed_polys[ia].Contains( currentVertex ) )
{
2020-08-11 13:33:16 +00:00
std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_ZONES_INTERSECT );
drcItem->SetItems( zoneToTest, zoneRef );
MARKER_PCB* marker = new MARKER_PCB( drcItem, pt );
addMarkerToPcb( aCommit, marker );
}
}
// Iterate through all the segments of refSmoothedPoly
std::map<wxPoint, int> conflictPoints;
for( auto refIt = smoothed_polys[ia].IterateSegmentsWithHoles(); refIt; refIt++ )
{
// Build ref segment
SEG refSegment = *refIt;
// Iterate through all the segments in smoothed_polys[ia2]
for( auto testIt = smoothed_polys[ia2].IterateSegmentsWithHoles(); testIt; testIt++ )
{
// Build test segment
SEG testSegment = *testIt;
wxPoint pt;
int ax1, ay1, ax2, ay2;
ax1 = refSegment.A.x;
ay1 = refSegment.A.y;
ax2 = refSegment.B.x;
ay2 = refSegment.B.y;
int bx1, by1, bx2, by2;
bx1 = testSegment.A.x;
by1 = testSegment.A.y;
bx2 = testSegment.B.x;
by2 = testSegment.B.y;
int d = GetClearanceBetweenSegments( bx1, by1, bx2, by2,
0,
ax1, ay1, ax2, ay2,
0,
zone2zoneClearance,
&pt.x, &pt.y );
if( d < zone2zoneClearance )
{
if( conflictPoints.count( pt ) )
conflictPoints[ pt ] = std::min( conflictPoints[ pt ], d );
else
conflictPoints[ pt ] = d;
}
}
}
for( const std::pair<const wxPoint, int>& conflict : conflictPoints )
{
int actual = conflict.second;
2020-08-11 13:33:16 +00:00
std::shared_ptr<DRC_ITEM> drcItem;
if( actual <= 0 )
{
drcItem = DRC_ITEM::Create( DRCE_ZONES_INTERSECT );
}
else
{
drcItem = DRC_ITEM::Create( DRCE_CLEARANCE );
m_msg.Printf( drcItem->GetErrorText() + _( " (%s clearance %s; actual %s)" ),
m_clearanceSource,
MessageTextFromValue( userUnits(), zone2zoneClearance, true ),
MessageTextFromValue( userUnits(), conflict.second, true ) );
drcItem->SetErrorMessage( m_msg );
}
drcItem->SetItems( zoneRef, zoneToTest );
MARKER_PCB* marker = new MARKER_PCB( drcItem, conflict.first );
addMarkerToPcb( aCommit, marker );
}
}
}
2007-12-01 03:42:52 +00:00
}
2007-08-08 20:51:08 +00:00
2007-12-01 03:42:52 +00:00
2020-05-18 00:20:16 +00:00
void DRC::testCopperTextAndGraphics( BOARD_COMMIT& aCommit )
{
// Test copper items for clearance violations with vias, tracks and pads
for( BOARD_ITEM* brdItem : m_pcb->Drawings() )
{
if( IsCopperLayer( brdItem->GetLayer() ) )
2020-05-18 00:20:16 +00:00
testCopperDrawItem( aCommit, brdItem );
}
for( MODULE* module : m_pcb->Modules() )
{
TEXTE_MODULE& ref = module->Reference();
TEXTE_MODULE& val = module->Value();
if( ref.IsVisible() && IsCopperLayer( ref.GetLayer() ) )
2020-05-18 00:20:16 +00:00
testCopperDrawItem( aCommit, &ref );
if( val.IsVisible() && IsCopperLayer( val.GetLayer() ) )
2020-05-18 00:20:16 +00:00
testCopperDrawItem( aCommit, &val );
if( module->IsNetTie() )
continue;
for( BOARD_ITEM* item : module->GraphicalItems() )
{
if( IsCopperLayer( item->GetLayer() ) )
{
if( item->Type() == PCB_MODULE_TEXT_T && ( (TEXTE_MODULE*) item )->IsVisible() )
2020-05-18 00:20:16 +00:00
testCopperDrawItem( aCommit, item );
else if( item->Type() == PCB_MODULE_EDGE_T )
2020-05-18 00:20:16 +00:00
testCopperDrawItem( aCommit, item );
}
}
}
}
2020-05-18 00:20:16 +00:00
void DRC::testCopperDrawItem( BOARD_COMMIT& aCommit, BOARD_ITEM* aItem )
{
EDA_RECT bbox;
std::vector<SHAPE*> itemShapes;
DRAWSEGMENT* drawItem = dynamic_cast<DRAWSEGMENT*>( aItem );
EDA_TEXT* textItem = dynamic_cast<EDA_TEXT*>( aItem );
2020-05-18 00:20:16 +00:00
if( drawItem )
{
2020-05-18 00:20:16 +00:00
bbox = drawItem->GetBoundingBox();
itemShapes = drawItem->MakeEffectiveShapes();
}
else if( textItem )
2020-05-18 00:20:16 +00:00
{
bbox = textItem->GetTextBox();
int penWidth = textItem->GetEffectiveTextPenWidth();
std::vector<wxPoint> pts;
textItem->TransformTextShapeToSegmentList( pts );
2020-05-18 00:20:16 +00:00
for( unsigned jj = 0; jj < pts.size(); jj += 2 )
itemShapes.push_back( new SHAPE_SEGMENT( pts[jj], pts[jj+1], penWidth ) );
2020-05-18 00:20:16 +00:00
}
else
{
wxFAIL_MSG( "unknown item type in testCopperDrawItem()" );
return;
}
SHAPE_RECT bboxShape( bbox.GetX(), bbox.GetY(), bbox.GetWidth(), bbox.GetHeight() );
// Test tracks and vias
for( TRACK* track : m_pcb->Tracks() )
{
if( !track->IsOnLayer( aItem->GetLayer() ) )
continue;
int minClearance = track->GetClearance( track->GetLayer(), aItem, &m_clearanceSource );
int actual = INT_MAX;
wxPoint pos;
SHAPE_SEGMENT trackSeg( track->GetStart(), track->GetEnd(), track->GetWidth() );
// Fast test to detect a track segment candidate inside the text bounding box
if( !bboxShape.Collide( &trackSeg, 0 ) )
continue;
for( const SHAPE* shape : itemShapes )
{
int this_dist;
if( shape->Collide( &trackSeg, minClearance, &this_dist ) )
{
if( this_dist < actual )
{
actual = this_dist;
pos = (wxPoint) shape->Centre();
}
}
}
if( actual < INT_MAX )
{
2020-08-11 13:33:16 +00:00
std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_CLEARANCE );
m_msg.Printf( drcItem->GetErrorText() + _( " (%s clearance %s; actual %s)" ),
2020-05-18 00:20:16 +00:00
m_clearanceSource,
MessageTextFromValue( userUnits(), minClearance, true ),
MessageTextFromValue( userUnits(), std::max( 0, actual ), true ) );
2020-05-18 00:20:16 +00:00
drcItem->SetErrorMessage( m_msg );
drcItem->SetItems( track, aItem );
MARKER_PCB* marker = new MARKER_PCB( drcItem, pos );
2020-05-18 00:20:16 +00:00
addMarkerToPcb( aCommit, marker );
}
}
// Test pads
for( D_PAD* pad : m_pcb->GetPads() )
{
if( !pad->IsOnLayer( aItem->GetLayer() ) )
continue;
// Graphic items are allowed to act as net-ties within their own footprint
2020-05-18 00:20:16 +00:00
if( drawItem && pad->GetParent() == drawItem->GetParent() )
continue;
int minClearance = pad->GetClearance( aItem->GetLayer(), aItem, &m_clearanceSource );
int actual = INT_MAX;
2020-05-18 00:20:16 +00:00
// Fast test to detect a pad candidate inside the text bounding box
// Finer test (time consumming) is made only for pads near the text.
int bb_radius = pad->GetBoundingRadius() + minClearance;
if( !bboxShape.Collide( SEG( pad->GetPosition(), pad->GetPosition() ), bb_radius ) )
continue;
for( const SHAPE* bShape : itemShapes )
{
int this_dist;
if( pad->GetEffectiveShape()->Collide( bShape, minClearance, &this_dist ) )
actual = std::min( actual, this_dist );
}
if( actual < INT_MAX )
{
2020-08-11 13:33:16 +00:00
std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_CLEARANCE );
m_msg.Printf( drcItem->GetErrorText() + _( " (%s clearance %s; actual %s)" ),
2020-05-18 00:20:16 +00:00
m_clearanceSource,
MessageTextFromValue( userUnits(), minClearance, true ),
MessageTextFromValue( userUnits(), std::max( 0, actual ), true ) );
2020-05-18 00:20:16 +00:00
drcItem->SetErrorMessage( m_msg );
drcItem->SetItems( pad, aItem );
MARKER_PCB* marker = new MARKER_PCB( drcItem, pad->GetPosition() );
2020-05-18 00:20:16 +00:00
addMarkerToPcb( aCommit, marker );
}
}
for( SHAPE* shape : itemShapes )
delete shape;
}
2020-05-18 00:20:16 +00:00
void DRC::testOutline( BOARD_COMMIT& aCommit )
{
2020-05-18 00:20:16 +00:00
wxPoint error_loc( m_pcb->GetBoardEdgesBoundingBox().GetPosition() );
m_board_outlines.RemoveAllContours();
m_board_outline_valid = false;
if( m_pcb->GetBoardPolygonOutlines( m_board_outlines, nullptr, &error_loc ) )
{
m_board_outline_valid = true;
}
else
{
2020-08-11 13:33:16 +00:00
std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_INVALID_OUTLINE );
2020-05-18 00:20:16 +00:00
m_msg.Printf( drcItem->GetErrorText() + _( " (not a closed shape)" ) );
2020-05-18 00:20:16 +00:00
drcItem->SetErrorMessage( m_msg );
drcItem->SetItems( m_pcb );
MARKER_PCB* marker = new MARKER_PCB( drcItem, error_loc );
2020-05-18 00:20:16 +00:00
addMarkerToPcb( aCommit, marker );
}
}
2020-05-18 00:20:16 +00:00
void DRC::testDisabledLayers( BOARD_COMMIT& aCommit )
{
LSET disabledLayers = m_pcb->GetEnabledLayers().flip();
// Perform the test only for copper layers
disabledLayers &= LSET::AllCuMask();
for( TRACK* track : m_pcb->Tracks() )
{
if( disabledLayers.test( track->GetLayer() ) )
{
2020-08-11 13:33:16 +00:00
std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_DISABLED_LAYER_ITEM );
2020-05-18 00:20:16 +00:00
m_msg.Printf( drcItem->GetErrorText() + _( "layer %s" ),
track->GetLayerName() );
2020-05-18 00:20:16 +00:00
drcItem->SetErrorMessage( m_msg );
drcItem->SetItems( track );
MARKER_PCB* marker = new MARKER_PCB( drcItem, track->GetPosition() );
2020-05-18 00:20:16 +00:00
addMarkerToPcb( aCommit, marker );
}
}
for( MODULE* module : m_pcb->Modules() )
{
module->RunOnChildren(
[&]( BOARD_ITEM* child )
{
if( disabledLayers.test( child->GetLayer() ) )
{
2020-08-11 13:33:16 +00:00
std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_DISABLED_LAYER_ITEM );
2020-05-18 00:20:16 +00:00
m_msg.Printf( drcItem->GetErrorText() + _( "layer %s" ),
child->GetLayerName() );
2020-05-18 00:20:16 +00:00
drcItem->SetErrorMessage( m_msg );
drcItem->SetItems( child );
MARKER_PCB* marker = new MARKER_PCB( drcItem, child->GetPosition() );
2020-05-18 00:20:16 +00:00
addMarkerToPcb( aCommit, marker );
}
} );
}
for( ZONE_CONTAINER* zone : m_pcb->Zones() )
{
if( disabledLayers.test( zone->GetLayer() ) )
{
2020-08-11 13:33:16 +00:00
std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_DISABLED_LAYER_ITEM );
2020-05-18 00:20:16 +00:00
m_msg.Printf( drcItem->GetErrorText() + _( "layer %s" ),
zone->GetLayerName() );
2020-05-18 00:20:16 +00:00
drcItem->SetErrorMessage( m_msg );
drcItem->SetItems( zone );
MARKER_PCB* marker = new MARKER_PCB( drcItem, zone->GetPosition() );
2020-05-18 00:20:16 +00:00
addMarkerToPcb( aCommit, marker );
}
}
}
2020-05-18 00:20:16 +00:00
bool DRC::doPadToPadsDrc( BOARD_COMMIT& aCommit, D_PAD* aRefPad, D_PAD** aStart, D_PAD** aEnd,
int x_limit )
2007-12-01 03:42:52 +00:00
{
const static LSET all_cu = LSET::AllCuMask();
LSET layerMask = aRefPad->GetLayerSet() & all_cu;
for( D_PAD** pad_list = aStart; pad_list<aEnd; ++pad_list )
2007-12-01 03:42:52 +00:00
{
2020-05-18 00:20:16 +00:00
D_PAD* pad = *pad_list;
2007-12-01 03:42:52 +00:00
if( pad == aRefPad )
continue;
2012-02-19 04:02:19 +00:00
// We can stop the test when pad->GetPosition().x > x_limit
2009-09-10 15:22:26 +00:00
// because the list is sorted by X values
2012-02-19 04:02:19 +00:00
if( pad->GetPosition().x > x_limit )
2007-12-01 03:42:52 +00:00
break;
// No problem if pads which are on copper layers are on different copper layers,
// (pads can be only on a technical layer, to build complex pads)
// but their hole (if any ) can create DRC error because they are on all
2009-09-10 15:22:26 +00:00
// copper layers, so we test them
if( ( pad->GetLayerSet() & layerMask ) == 0 &&
( pad->GetLayerSet() & all_cu ) != 0 &&
( aRefPad->GetLayerSet() & all_cu ) != 0 )
{
2009-09-10 15:22:26 +00:00
// if holes are in the same location and have the same size and shape,
// this can be accepted
if( pad->GetPosition() == aRefPad->GetPosition()
2012-02-19 04:02:19 +00:00
&& pad->GetDrillSize() == aRefPad->GetDrillSize()
&& pad->GetDrillShape() == aRefPad->GetDrillShape() )
{
if( aRefPad->GetDrillShape() == PAD_DRILL_SHAPE_CIRCLE )
continue;
// for oval holes: must also have the same orientation
2012-02-19 04:02:19 +00:00
if( pad->GetOrientation() == aRefPad->GetOrientation() )
continue;
}
2012-02-19 04:02:19 +00:00
if( pad->GetDrillSize().x )
{
int minClearance = aRefPad->GetClearance( aRefPad->GetLayer(), nullptr,
&m_clearanceSource );
2020-05-18 00:20:16 +00:00
int actual;
if( aRefPad->Collide( pad->GetEffectiveHoleShape(), minClearance, &actual ) )
{
2020-08-11 13:33:16 +00:00
std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_CLEARANCE );
m_msg.Printf( drcItem->GetErrorText() + _( " (%s clearance %s; actual %s)" ),
2020-05-18 00:20:16 +00:00
m_clearanceSource,
MessageTextFromValue( userUnits(), minClearance, true ),
MessageTextFromValue( userUnits(), actual, true ) );
2020-05-18 00:20:16 +00:00
drcItem->SetErrorMessage( m_msg );
drcItem->SetItems( pad, aRefPad );
MARKER_PCB* marker = new MARKER_PCB( drcItem, pad->GetPosition() );
2020-05-18 00:20:16 +00:00
addMarkerToPcb( aCommit, marker );
return false;
}
}
2009-09-10 15:22:26 +00:00
if( aRefPad->GetDrillSize().x )
{
2020-08-11 22:16:20 +00:00
int minClearance = pad->GetClearance( pad->GetLayer(), nullptr,
&m_clearanceSource );
2020-05-18 00:20:16 +00:00
int actual;
if( pad->Collide( aRefPad->GetEffectiveHoleShape(), minClearance, &actual ) )
{
2020-08-11 13:33:16 +00:00
std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_CLEARANCE );
m_msg.Printf( drcItem->GetErrorText() + _( " (%s clearance %s; actual %s)" ),
2020-05-18 00:20:16 +00:00
m_clearanceSource,
MessageTextFromValue( userUnits(), minClearance, true ),
MessageTextFromValue( userUnits(), actual, true ) );
2020-05-18 00:20:16 +00:00
drcItem->SetErrorMessage( m_msg );
drcItem->SetItems( aRefPad, pad );
MARKER_PCB* marker = new MARKER_PCB( drcItem, aRefPad->GetPosition() );
2020-05-18 00:20:16 +00:00
addMarkerToPcb( aCommit, marker );
return false;
}
}
2007-12-01 03:42:52 +00:00
continue;
}
2007-12-01 03:42:52 +00:00
2009-09-10 15:22:26 +00:00
// The pad must be in a net (i.e pt_pad->GetNet() != 0 ),
// But no problem if pads have the same netcode (same net)
if( pad->GetNetCode() && ( aRefPad->GetNetCode() == pad->GetNetCode() ) )
2007-12-01 03:42:52 +00:00
continue;
// If pads are equivalent (ie: from the same footprint with the same pad number)...
if( pad->GetParent() == aRefPad->GetParent() && pad->PadNameEqual( aRefPad ) )
2009-09-10 15:22:26 +00:00
{
// ...and have nets, then they must be the same net
if( pad->GetNetCode() && aRefPad->GetNetCode()
&& pad->GetNetCode() != aRefPad->GetNetCode() )
{
2020-08-11 13:33:16 +00:00
std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_SHORTING_ITEMS );
m_msg.Printf( drcItem->GetErrorText() + _( " (nets %s and %s)" ),
pad->GetNetname(), aRefPad->GetNetname() );
drcItem->SetErrorMessage( m_msg );
drcItem->SetItems( pad, aRefPad );
MARKER_PCB* marker = new MARKER_PCB( drcItem, aRefPad->GetPosition() );
addMarkerToPcb( aCommit, marker );
}
2020-08-11 13:33:16 +00:00
continue;
2009-09-10 15:22:26 +00:00
}
// if either pad has no drill and is only on technical layers, not a clearance violation
if( ( ( pad->GetLayerSet() & layerMask ) == 0 && !pad->GetDrillSize().x ) ||
( ( aRefPad->GetLayerSet() & layerMask ) == 0 && !aRefPad->GetDrillSize().x ) )
{
continue;
}
2007-12-01 03:42:52 +00:00
for( PCB_LAYER_ID layer : aRefPad->GetLayerSet().Seq() )
2007-12-01 03:42:52 +00:00
{
int minClearance = aRefPad->GetClearance( layer, pad, &m_clearanceSource );
2020-09-02 20:21:13 +00:00
int clearanceAllowed = minClearance - m_pcb->GetDesignSettings().GetDRCEpsilon();
int actual;
2020-09-02 20:21:13 +00:00
if( aRefPad->Collide( pad, clearanceAllowed, &actual ) )
{
std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_CLEARANCE );
2020-09-02 20:21:13 +00:00
m_msg.Printf( drcItem->GetErrorText() + _( " (%s clearance %s; actual %s)" ),
m_clearanceSource,
MessageTextFromValue( userUnits(), minClearance, true ),
MessageTextFromValue( userUnits(), actual, true ) );
2020-09-02 20:21:13 +00:00
drcItem->SetErrorMessage( m_msg );
drcItem->SetItems( aRefPad, pad );
2020-09-02 20:21:13 +00:00
MARKER_PCB* marker = new MARKER_PCB( drcItem, aRefPad->GetPosition() );
addMarkerToPcb( aCommit, marker );
return false;
}
2007-12-01 03:42:52 +00:00
}
}
return true;
}
void DRC::setTransitions()
{
Go( &DRC::ShowDRCDialog, PCB_ACTIONS::runDRC.MakeEvent() );
}
const int UI_EPSILON = Mils2iu( 5 );
Clean up arc/circle polygonization. 1) For a while now we've been using a calculated seg count from a given maxError, and a correction factor to push the radius out so that all the error is outside the arc/circle. However, the second calculation (which pre-dates the first) is pretty much just the inverse of the first (and yields nothing more than maxError back). This is particularly sub-optimal given the cost of trig functions. 2) There are a lot of old optimizations to reduce segcounts in certain situations, someting that our error-based calculation compensates for anyway. (Smaller radii need fewer segments to meet the maxError condition.) But perhaps more importantly we now surface maxError in the UI and we don't really want to call it "Max deviation except when it's not". 3) We were also clamping the segCount twice: once in the calculation routine and once in most of it's callers. Furthermore, the caller clamping was inconsistent (both in being done and in the clamping value). We now clamp only in the calculation routine. 4) There's no reason to use the correction factors in the 3Dviewer; it's just a visualization and whether the polygonization error is inside or outside the shape isn't really material. 5) The arc-correction-disabling stuff (used for solder mask layer) was somewhat fragile in that it depended on the caller to turn it back on afterwards. It's now only exposed as a RAII object which automatically cleans up when it goes out of scope. 6) There were also bugs in a couple of the polygonization routines where we'd accumulate round-off error in adding up the segments and end up with an overly long last segment (which of course would voilate the error max). This was the cause of the linked bug and also some issues with vias that we had fudged in the past with extra clearance. Fixes https://gitlab.com/kicad/code/kicad/issues/5567
2020-09-10 23:05:20 +00:00
wxPoint DRC::GetLocation( PCB_LAYER_ID aLayer, TRACK* aTrack, ZONE_CONTAINER* aZone )
{
Clean up arc/circle polygonization. 1) For a while now we've been using a calculated seg count from a given maxError, and a correction factor to push the radius out so that all the error is outside the arc/circle. However, the second calculation (which pre-dates the first) is pretty much just the inverse of the first (and yields nothing more than maxError back). This is particularly sub-optimal given the cost of trig functions. 2) There are a lot of old optimizations to reduce segcounts in certain situations, someting that our error-based calculation compensates for anyway. (Smaller radii need fewer segments to meet the maxError condition.) But perhaps more importantly we now surface maxError in the UI and we don't really want to call it "Max deviation except when it's not". 3) We were also clamping the segCount twice: once in the calculation routine and once in most of it's callers. Furthermore, the caller clamping was inconsistent (both in being done and in the clamping value). We now clamp only in the calculation routine. 4) There's no reason to use the correction factors in the 3Dviewer; it's just a visualization and whether the polygonization error is inside or outside the shape isn't really material. 5) The arc-correction-disabling stuff (used for solder mask layer) was somewhat fragile in that it depended on the caller to turn it back on afterwards. It's now only exposed as a RAII object which automatically cleans up when it goes out of scope. 6) There were also bugs in a couple of the polygonization routines where we'd accumulate round-off error in adding up the segments and end up with an overly long last segment (which of course would voilate the error max). This was the cause of the linked bug and also some issues with vias that we had fudged in the past with extra clearance. Fixes https://gitlab.com/kicad/code/kicad/issues/5567
2020-09-10 23:05:20 +00:00
SHAPE_POLY_SET* zonePoly = nullptr;
Clean up arc/circle polygonization. 1) For a while now we've been using a calculated seg count from a given maxError, and a correction factor to push the radius out so that all the error is outside the arc/circle. However, the second calculation (which pre-dates the first) is pretty much just the inverse of the first (and yields nothing more than maxError back). This is particularly sub-optimal given the cost of trig functions. 2) There are a lot of old optimizations to reduce segcounts in certain situations, someting that our error-based calculation compensates for anyway. (Smaller radii need fewer segments to meet the maxError condition.) But perhaps more importantly we now surface maxError in the UI and we don't really want to call it "Max deviation except when it's not". 3) We were also clamping the segCount twice: once in the calculation routine and once in most of it's callers. Furthermore, the caller clamping was inconsistent (both in being done and in the clamping value). We now clamp only in the calculation routine. 4) There's no reason to use the correction factors in the 3Dviewer; it's just a visualization and whether the polygonization error is inside or outside the shape isn't really material. 5) The arc-correction-disabling stuff (used for solder mask layer) was somewhat fragile in that it depended on the caller to turn it back on afterwards. It's now only exposed as a RAII object which automatically cleans up when it goes out of scope. 6) There were also bugs in a couple of the polygonization routines where we'd accumulate round-off error in adding up the segments and end up with an overly long last segment (which of course would voilate the error max). This was the cause of the linked bug and also some issues with vias that we had fudged in the past with extra clearance. Fixes https://gitlab.com/kicad/code/kicad/issues/5567
2020-09-10 23:05:20 +00:00
if( aZone->IsFilled() && aZone->HasFilledPolysForLayer( aLayer ) )
zonePoly = const_cast<SHAPE_POLY_SET*>( &aZone->GetFilledPolysList( aLayer ) );
Clean up arc/circle polygonization. 1) For a while now we've been using a calculated seg count from a given maxError, and a correction factor to push the radius out so that all the error is outside the arc/circle. However, the second calculation (which pre-dates the first) is pretty much just the inverse of the first (and yields nothing more than maxError back). This is particularly sub-optimal given the cost of trig functions. 2) There are a lot of old optimizations to reduce segcounts in certain situations, someting that our error-based calculation compensates for anyway. (Smaller radii need fewer segments to meet the maxError condition.) But perhaps more importantly we now surface maxError in the UI and we don't really want to call it "Max deviation except when it's not". 3) We were also clamping the segCount twice: once in the calculation routine and once in most of it's callers. Furthermore, the caller clamping was inconsistent (both in being done and in the clamping value). We now clamp only in the calculation routine. 4) There's no reason to use the correction factors in the 3Dviewer; it's just a visualization and whether the polygonization error is inside or outside the shape isn't really material. 5) The arc-correction-disabling stuff (used for solder mask layer) was somewhat fragile in that it depended on the caller to turn it back on afterwards. It's now only exposed as a RAII object which automatically cleans up when it goes out of scope. 6) There were also bugs in a couple of the polygonization routines where we'd accumulate round-off error in adding up the segments and end up with an overly long last segment (which of course would voilate the error max). This was the cause of the linked bug and also some issues with vias that we had fudged in the past with extra clearance. Fixes https://gitlab.com/kicad/code/kicad/issues/5567
2020-09-10 23:05:20 +00:00
if( !zonePoly || zonePoly->IsEmpty() )
zonePoly = aZone->Outline();
Clean up arc/circle polygonization. 1) For a while now we've been using a calculated seg count from a given maxError, and a correction factor to push the radius out so that all the error is outside the arc/circle. However, the second calculation (which pre-dates the first) is pretty much just the inverse of the first (and yields nothing more than maxError back). This is particularly sub-optimal given the cost of trig functions. 2) There are a lot of old optimizations to reduce segcounts in certain situations, someting that our error-based calculation compensates for anyway. (Smaller radii need fewer segments to meet the maxError condition.) But perhaps more importantly we now surface maxError in the UI and we don't really want to call it "Max deviation except when it's not". 3) We were also clamping the segCount twice: once in the calculation routine and once in most of it's callers. Furthermore, the caller clamping was inconsistent (both in being done and in the clamping value). We now clamp only in the calculation routine. 4) There's no reason to use the correction factors in the 3Dviewer; it's just a visualization and whether the polygonization error is inside or outside the shape isn't really material. 5) The arc-correction-disabling stuff (used for solder mask layer) was somewhat fragile in that it depended on the caller to turn it back on afterwards. It's now only exposed as a RAII object which automatically cleans up when it goes out of scope. 6) There were also bugs in a couple of the polygonization routines where we'd accumulate round-off error in adding up the segments and end up with an overly long last segment (which of course would voilate the error max). This was the cause of the linked bug and also some issues with vias that we had fudged in the past with extra clearance. Fixes https://gitlab.com/kicad/code/kicad/issues/5567
2020-09-10 23:05:20 +00:00
SEG trackSeg( aTrack->GetStart(), aTrack->GetEnd() );
SEG::ecoord closestDist_sq = VECTOR2I::ECOORD_MAX;
SEG closestSeg;
Clean up arc/circle polygonization. 1) For a while now we've been using a calculated seg count from a given maxError, and a correction factor to push the radius out so that all the error is outside the arc/circle. However, the second calculation (which pre-dates the first) is pretty much just the inverse of the first (and yields nothing more than maxError back). This is particularly sub-optimal given the cost of trig functions. 2) There are a lot of old optimizations to reduce segcounts in certain situations, someting that our error-based calculation compensates for anyway. (Smaller radii need fewer segments to meet the maxError condition.) But perhaps more importantly we now surface maxError in the UI and we don't really want to call it "Max deviation except when it's not". 3) We were also clamping the segCount twice: once in the calculation routine and once in most of it's callers. Furthermore, the caller clamping was inconsistent (both in being done and in the clamping value). We now clamp only in the calculation routine. 4) There's no reason to use the correction factors in the 3Dviewer; it's just a visualization and whether the polygonization error is inside or outside the shape isn't really material. 5) The arc-correction-disabling stuff (used for solder mask layer) was somewhat fragile in that it depended on the caller to turn it back on afterwards. It's now only exposed as a RAII object which automatically cleans up when it goes out of scope. 6) There were also bugs in a couple of the polygonization routines where we'd accumulate round-off error in adding up the segments and end up with an overly long last segment (which of course would voilate the error max). This was the cause of the linked bug and also some issues with vias that we had fudged in the past with extra clearance. Fixes https://gitlab.com/kicad/code/kicad/issues/5567
2020-09-10 23:05:20 +00:00
for( auto it = zonePoly->CIterateSegments( 0, -1, true ); it; it++ )
{
Clean up arc/circle polygonization. 1) For a while now we've been using a calculated seg count from a given maxError, and a correction factor to push the radius out so that all the error is outside the arc/circle. However, the second calculation (which pre-dates the first) is pretty much just the inverse of the first (and yields nothing more than maxError back). This is particularly sub-optimal given the cost of trig functions. 2) There are a lot of old optimizations to reduce segcounts in certain situations, someting that our error-based calculation compensates for anyway. (Smaller radii need fewer segments to meet the maxError condition.) But perhaps more importantly we now surface maxError in the UI and we don't really want to call it "Max deviation except when it's not". 3) We were also clamping the segCount twice: once in the calculation routine and once in most of it's callers. Furthermore, the caller clamping was inconsistent (both in being done and in the clamping value). We now clamp only in the calculation routine. 4) There's no reason to use the correction factors in the 3Dviewer; it's just a visualization and whether the polygonization error is inside or outside the shape isn't really material. 5) The arc-correction-disabling stuff (used for solder mask layer) was somewhat fragile in that it depended on the caller to turn it back on afterwards. It's now only exposed as a RAII object which automatically cleans up when it goes out of scope. 6) There were also bugs in a couple of the polygonization routines where we'd accumulate round-off error in adding up the segments and end up with an overly long last segment (which of course would voilate the error max). This was the cause of the linked bug and also some issues with vias that we had fudged in the past with extra clearance. Fixes https://gitlab.com/kicad/code/kicad/issues/5567
2020-09-10 23:05:20 +00:00
SEG::ecoord dist_sq = trackSeg.SquaredDistance( *it );
if( dist_sq < closestDist_sq )
{
Clean up arc/circle polygonization. 1) For a while now we've been using a calculated seg count from a given maxError, and a correction factor to push the radius out so that all the error is outside the arc/circle. However, the second calculation (which pre-dates the first) is pretty much just the inverse of the first (and yields nothing more than maxError back). This is particularly sub-optimal given the cost of trig functions. 2) There are a lot of old optimizations to reduce segcounts in certain situations, someting that our error-based calculation compensates for anyway. (Smaller radii need fewer segments to meet the maxError condition.) But perhaps more importantly we now surface maxError in the UI and we don't really want to call it "Max deviation except when it's not". 3) We were also clamping the segCount twice: once in the calculation routine and once in most of it's callers. Furthermore, the caller clamping was inconsistent (both in being done and in the clamping value). We now clamp only in the calculation routine. 4) There's no reason to use the correction factors in the 3Dviewer; it's just a visualization and whether the polygonization error is inside or outside the shape isn't really material. 5) The arc-correction-disabling stuff (used for solder mask layer) was somewhat fragile in that it depended on the caller to turn it back on afterwards. It's now only exposed as a RAII object which automatically cleans up when it goes out of scope. 6) There were also bugs in a couple of the polygonization routines where we'd accumulate round-off error in adding up the segments and end up with an overly long last segment (which of course would voilate the error max). This was the cause of the linked bug and also some issues with vias that we had fudged in the past with extra clearance. Fixes https://gitlab.com/kicad/code/kicad/issues/5567
2020-09-10 23:05:20 +00:00
closestDist_sq = dist_sq;
closestSeg = *it;
}
Clean up arc/circle polygonization. 1) For a while now we've been using a calculated seg count from a given maxError, and a correction factor to push the radius out so that all the error is outside the arc/circle. However, the second calculation (which pre-dates the first) is pretty much just the inverse of the first (and yields nothing more than maxError back). This is particularly sub-optimal given the cost of trig functions. 2) There are a lot of old optimizations to reduce segcounts in certain situations, someting that our error-based calculation compensates for anyway. (Smaller radii need fewer segments to meet the maxError condition.) But perhaps more importantly we now surface maxError in the UI and we don't really want to call it "Max deviation except when it's not". 3) We were also clamping the segCount twice: once in the calculation routine and once in most of it's callers. Furthermore, the caller clamping was inconsistent (both in being done and in the clamping value). We now clamp only in the calculation routine. 4) There's no reason to use the correction factors in the 3Dviewer; it's just a visualization and whether the polygonization error is inside or outside the shape isn't really material. 5) The arc-correction-disabling stuff (used for solder mask layer) was somewhat fragile in that it depended on the caller to turn it back on afterwards. It's now only exposed as a RAII object which automatically cleans up when it goes out of scope. 6) There were also bugs in a couple of the polygonization routines where we'd accumulate round-off error in adding up the segments and end up with an overly long last segment (which of course would voilate the error max). This was the cause of the linked bug and also some issues with vias that we had fudged in the past with extra clearance. Fixes https://gitlab.com/kicad/code/kicad/issues/5567
2020-09-10 23:05:20 +00:00
}
Clean up arc/circle polygonization. 1) For a while now we've been using a calculated seg count from a given maxError, and a correction factor to push the radius out so that all the error is outside the arc/circle. However, the second calculation (which pre-dates the first) is pretty much just the inverse of the first (and yields nothing more than maxError back). This is particularly sub-optimal given the cost of trig functions. 2) There are a lot of old optimizations to reduce segcounts in certain situations, someting that our error-based calculation compensates for anyway. (Smaller radii need fewer segments to meet the maxError condition.) But perhaps more importantly we now surface maxError in the UI and we don't really want to call it "Max deviation except when it's not". 3) We were also clamping the segCount twice: once in the calculation routine and once in most of it's callers. Furthermore, the caller clamping was inconsistent (both in being done and in the clamping value). We now clamp only in the calculation routine. 4) There's no reason to use the correction factors in the 3Dviewer; it's just a visualization and whether the polygonization error is inside or outside the shape isn't really material. 5) The arc-correction-disabling stuff (used for solder mask layer) was somewhat fragile in that it depended on the caller to turn it back on afterwards. It's now only exposed as a RAII object which automatically cleans up when it goes out of scope. 6) There were also bugs in a couple of the polygonization routines where we'd accumulate round-off error in adding up the segments and end up with an overly long last segment (which of course would voilate the error max). This was the cause of the linked bug and also some issues with vias that we had fudged in the past with extra clearance. Fixes https://gitlab.com/kicad/code/kicad/issues/5567
2020-09-10 23:05:20 +00:00
VECTOR2I pt1 = closestSeg.A;
VECTOR2I pt2 = closestSeg.B;
// Do a binary search for a "good enough" marker location
while( GetLineLength( (wxPoint) pt1, (wxPoint) pt2 ) > UI_EPSILON )
{
if( trackSeg.SquaredDistance( pt1 ) < trackSeg.SquaredDistance( pt2 ) )
pt2 = ( pt1 + pt2 ) / 2;
else
pt1 = ( pt1 + pt2 ) / 2;
}
Clean up arc/circle polygonization. 1) For a while now we've been using a calculated seg count from a given maxError, and a correction factor to push the radius out so that all the error is outside the arc/circle. However, the second calculation (which pre-dates the first) is pretty much just the inverse of the first (and yields nothing more than maxError back). This is particularly sub-optimal given the cost of trig functions. 2) There are a lot of old optimizations to reduce segcounts in certain situations, someting that our error-based calculation compensates for anyway. (Smaller radii need fewer segments to meet the maxError condition.) But perhaps more importantly we now surface maxError in the UI and we don't really want to call it "Max deviation except when it's not". 3) We were also clamping the segCount twice: once in the calculation routine and once in most of it's callers. Furthermore, the caller clamping was inconsistent (both in being done and in the clamping value). We now clamp only in the calculation routine. 4) There's no reason to use the correction factors in the 3Dviewer; it's just a visualization and whether the polygonization error is inside or outside the shape isn't really material. 5) The arc-correction-disabling stuff (used for solder mask layer) was somewhat fragile in that it depended on the caller to turn it back on afterwards. It's now only exposed as a RAII object which automatically cleans up when it goes out of scope. 6) There were also bugs in a couple of the polygonization routines where we'd accumulate round-off error in adding up the segments and end up with an overly long last segment (which of course would voilate the error max). This was the cause of the linked bug and also some issues with vias that we had fudged in the past with extra clearance. Fixes https://gitlab.com/kicad/code/kicad/issues/5567
2020-09-10 23:05:20 +00:00
// Once we're within UI_EPSILON pt1 and pt2 are "equivalent"
return (wxPoint) pt1;
}
2020-05-18 00:20:16 +00:00
wxPoint DRC::GetLocation( TRACK* aTrack, const SEG& aConflictSeg )
{
wxPoint pt1 = aTrack->GetPosition();
wxPoint pt2 = aTrack->GetEnd();
// Do a binary search along the track for a "good enough" marker location
while( GetLineLength( pt1, pt2 ) > UI_EPSILON )
{
if( aConflictSeg.SquaredDistance( pt1 ) < aConflictSeg.SquaredDistance( pt2 ) )
pt2 = ( pt1 + pt2 ) / 2;
else
pt1 = ( pt1 + pt2 ) / 2;
}
// Once we're within UI_EPSILON pt1 and pt2 are "equivalent"
return pt1;
}