From 085698d17c54876b1fd8fc9428538344071dd082 Mon Sep 17 00:00:00 2001 From: Tomasz Wlostowski Date: Sun, 14 Jun 2020 01:28:08 +0200 Subject: [PATCH] drc_proto: wip --- qa/drc_proto/CMakeLists.txt | 111 ++ qa/drc_proto/drc.cpp | 1433 +++++++++++++++++ qa/drc_proto/drc.h | 356 ++++ qa/drc_proto/drc_clearance_test.cpp | 758 +++++++++ qa/drc_proto/drc_clearance_test_functions.cpp | 995 ++++++++++++ qa/drc_proto/drc_courtyard_tester.cpp | 189 +++ qa/drc_proto/drc_courtyard_tester.h | 43 + qa/drc_proto/drc_drilled_hole_tester.cpp | 283 ++++ qa/drc_proto/drc_drilled_hole_tester.h | 68 + qa/drc_proto/drc_engine.cpp | 1419 ++++++++++++++++ qa/drc_proto/drc_engine.h | 232 +++ qa/drc_proto/drc_item.cpp | 207 +++ qa/drc_proto/drc_item.h | 69 + qa/drc_proto/drc_keepout_tester.cpp | 351 ++++ qa/drc_proto/drc_keepout_tester.h | 61 + qa/drc_proto/drc_marker_pcb.h | 117 ++ qa/drc_proto/drc_netclass_tester.cpp | 162 ++ qa/drc_proto/drc_netclass_tester.h | 54 + qa/drc_proto/drc_proto_test.cpp | 29 + qa/drc_proto/drc_provider.h | 286 ++++ qa/drc_proto/drc_rule.cpp | 82 + qa/drc_proto/drc_rule.h | 139 ++ qa/drc_proto/drc_rule_parser.cpp | 258 +++ qa/drc_proto/drc_rule_parser.h | 80 + qa/drc_proto/drc_rules_keywords.cpp | 50 + qa/drc_proto/drc_rules_lexer.h | 195 +++ qa/drc_proto/drc_rules_proto.keywords | 17 + qa/drc_proto/drc_test_provider.cpp | 31 + qa/drc_proto/drc_test_provider.h | 290 ++++ qa/drc_proto/drc_textvar_tester.cpp | 116 ++ qa/drc_proto/drc_textvar_tester.h | 49 + qa/drc_proto/footprint_tester.cpp | 90 ++ qa/drc_proto/footprint_tester.h | 34 + 33 files changed, 8654 insertions(+) create mode 100644 qa/drc_proto/CMakeLists.txt create mode 100644 qa/drc_proto/drc.cpp create mode 100644 qa/drc_proto/drc.h create mode 100644 qa/drc_proto/drc_clearance_test.cpp create mode 100644 qa/drc_proto/drc_clearance_test_functions.cpp create mode 100644 qa/drc_proto/drc_courtyard_tester.cpp create mode 100644 qa/drc_proto/drc_courtyard_tester.h create mode 100644 qa/drc_proto/drc_drilled_hole_tester.cpp create mode 100644 qa/drc_proto/drc_drilled_hole_tester.h create mode 100644 qa/drc_proto/drc_engine.cpp create mode 100644 qa/drc_proto/drc_engine.h create mode 100644 qa/drc_proto/drc_item.cpp create mode 100644 qa/drc_proto/drc_item.h create mode 100644 qa/drc_proto/drc_keepout_tester.cpp create mode 100644 qa/drc_proto/drc_keepout_tester.h create mode 100644 qa/drc_proto/drc_marker_pcb.h create mode 100644 qa/drc_proto/drc_netclass_tester.cpp create mode 100644 qa/drc_proto/drc_netclass_tester.h create mode 100644 qa/drc_proto/drc_proto_test.cpp create mode 100644 qa/drc_proto/drc_provider.h create mode 100644 qa/drc_proto/drc_rule.cpp create mode 100644 qa/drc_proto/drc_rule.h create mode 100644 qa/drc_proto/drc_rule_parser.cpp create mode 100644 qa/drc_proto/drc_rule_parser.h create mode 100644 qa/drc_proto/drc_rules_keywords.cpp create mode 100644 qa/drc_proto/drc_rules_lexer.h create mode 100644 qa/drc_proto/drc_rules_proto.keywords create mode 100644 qa/drc_proto/drc_test_provider.cpp create mode 100644 qa/drc_proto/drc_test_provider.h create mode 100644 qa/drc_proto/drc_textvar_tester.cpp create mode 100644 qa/drc_proto/drc_textvar_tester.h create mode 100644 qa/drc_proto/footprint_tester.cpp create mode 100644 qa/drc_proto/footprint_tester.h diff --git a/qa/drc_proto/CMakeLists.txt b/qa/drc_proto/CMakeLists.txt new file mode 100644 index 0000000000..61d4944317 --- /dev/null +++ b/qa/drc_proto/CMakeLists.txt @@ -0,0 +1,111 @@ +# +# This program source code file is part of KiCad, a free EDA CAD application. +# +# Copyright (C) 2017 CERN +# @author Alejandro GarcĂ­a Montoro +# +# 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 + +find_package(Boost COMPONENTS unit_test_framework REQUIRED) +find_package( wxWidgets 3.0.0 COMPONENTS gl aui adv html core net base xml stc REQUIRED ) + + +add_definitions(-DBOOST_TEST_DYN_LINK -DPCBNEW -DDRC_PROTO) + +if( BUILD_GITHUB_PLUGIN ) + set( GITHUB_PLUGIN_LIBRARIES github_plugin ) +endif() + + +add_dependencies( pnsrouter pcbcommon pcad2kicadpcb ${GITHUB_PLUGIN_LIBRARIES} ) + +add_executable( drc_proto + drc_proto_test.cpp + drc_rule.cpp + drc_rule_parser.cpp + drc_rules_proto_keywords.cpp + drc_test_provider.cpp + drc_clearance_test.cpp + drc_engine.cpp + ../qa_utils/mocks.cpp + ../pcbnew_utils/board_file_utils.cpp + ../qa_utils/stdstream_line_reader.cpp + ../../common/base_units.cpp + ../../3d-viewer/3d_viewer/3d_viewer_settings.cpp +) + +include_directories( BEFORE ${INC_BEFORE} ) +include_directories( + ${CMAKE_SOURCE_DIR} + ${CMAKE_SOURCE_DIR}/include + ${CMAKE_SOURCE_DIR}/3d-viewer + ${CMAKE_SOURCE_DIR}/common + ${CMAKE_SOURCE_DIR}/pcbnew + ${CMAKE_SOURCE_DIR}/pcbnew/router + ${CMAKE_SOURCE_DIR}/pcbnew/tools + ${CMAKE_SOURCE_DIR}/pcbnew/dialogs + ${CMAKE_SOURCE_DIR}/polygon + ${CMAKE_SOURCE_DIR}/common/geometry + ${CMAKE_SOURCE_DIR}/qa/common + ${CMAKE_SOURCE_DIR}/qa + ${CMAKE_SOURCE_DIR}/qa/qa_utils + ${CMAKE_SOURCE_DIR}/qa/qa_utils/include + ${CMAKE_SOURCE_DIR}/qa/pcbnew_utils/include + ${Boost_INCLUDE_DIR} + ${INC_AFTER} +) + +target_link_libraries( drc_proto + pnsrouter + common + pcbcommon + bitmaps + pnsrouter + common + pcbcommon + bitmaps + pnsrouter + common + pcbcommon + bitmaps + pnsrouter + common + pcbcommon + bitmaps + gal + pcad2kicadpcb + altium2kicadpcb + common + pcbcommon + ${GITHUB_PLUGIN_LIBRARIES} + common + pcbcommon + ${Boost_FILESYSTEM_LIBRARY} + ${Boost_SYSTEM_LIBRARY} + ${Boost_UNIT_TEST_FRAMEWORK_LIBRARY} + ${wxWidgets_LIBRARIES} +) + +# auto-generate drc_rules_lexer.h and drc_rules_keywords.cpp +make_lexer( + drc_proto + drc_rules_proto.keywords + drc_rules_proto_lexer.h + drc_rules_proto_keywords.cpp + DRCRULEPROTO_T +) \ No newline at end of file diff --git a/qa/drc_proto/drc.cpp b/qa/drc_proto/drc.cpp new file mode 100644 index 0000000000..055aaac81d --- /dev/null +++ b/qa/drc_proto/drc.cpp @@ -0,0 +1,1433 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * 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. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include // for KiROUND +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +DRC::DRC() : + PCB_TOOL_BASE( "pcbnew.DRCTool" ), + m_editFrame( nullptr ), + 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() +{ + for( DRC_ITEM* unconnectedItem : m_unconnected ) + delete unconnectedItem; + + for( DRC_ITEM* footprintItem : m_footprints ) + delete footprintItem; +} + + +void DRC::Reset( RESET_REASON aReason ) +{ + m_editFrame = getEditFrame(); + + if( m_pcb != m_editFrame->GetBoard() ) + { + if( m_drcDialog ) + DestroyDRCDialog( wxID_OK ); + + m_pcb = m_editFrame->GetBoard(); + } +} + + +void DRC::ShowDRCDialog( wxWindow* aParent ) +{ + 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 ) + { + m_drcDialog = new DIALOG_DRC( this, m_editFrame, aParent ); + updatePointers(); + + if( show_dlg_modal ) + m_drcDialog->ShowModal(); + else + m_drcDialog->Show( true ); + } + else // The dialog is just not visible (because the user has double clicked on an error item) + { + updatePointers(); + m_drcDialog->Show( true ); + } +} + + +int DRC::ShowDRCDialog( const TOOL_EVENT& aEvent ) +{ + ShowDRCDialog( nullptr ); + return 0; +} + + +bool DRC::IsDRCDialogShown() +{ + if( m_drcDialog ) + return m_drcDialog->IsShown(); + + return false; +} + + +void DRC::addMarkerToPcb( BOARD_COMMIT& aCommit, MARKER_PCB* aMarker ) +{ + if( m_pcb->GetDesignSettings().Ignore( aMarker->GetRCItem()->GetErrorCode() ) ) + { + delete aMarker; + return; + } + + aCommit.Add( aMarker ); +} + + +void DRC::DestroyDRCDialog( int aReason ) +{ + if( m_drcDialog ) + { + m_drcDialog->Destroy(); + m_drcDialog = nullptr; + } +} + + +bool DRC::LoadRules() +{ + wxString rulesFilepath = m_editFrame->Prj().AbsolutePath( "drc-rules" ); + wxFileName rulesFile( rulesFilepath ); + + if( rulesFile.FileExists() ) + { + m_ruleSelectors.clear(); + m_rules.clear(); + + FILE* fp = wxFopen( rulesFilepath, wxT( "rt" ) ); + + if( fp ) + { + try + { + DRC_RULES_PARSER parser( m_pcb, fp, rulesFilepath ); + parser.Parse( m_ruleSelectors, m_rules ); + } + catch( PARSE_ERROR& pe ) + { + // Don't leave possibly malformed stuff around for us to trip over + m_ruleSelectors.clear(); + 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_ruleSelectors ), std::end( m_ruleSelectors ) ); + + BOARD_DESIGN_SETTINGS& bds = m_pcb->GetDesignSettings(); + bds.m_DRCRuleSelectors = m_ruleSelectors; + bds.m_DRCRules = m_rules; + + return true; +} + + +void DRC::RunTests( wxTextCtrl* aMessages ) +{ + // Make absolutely sure these are up-to-date + if( !LoadRules() ) + return; + + wxASSERT( m_pcb == m_editFrame->GetBoard() ); + + BOARD_COMMIT commit( m_editFrame ); + BOARD_DESIGN_SETTINGS& bds = m_pcb->GetDesignSettings(); + + m_largestClearance = bds.GetBiggestClearanceValue(); + + if( !bds.Ignore( DRCE_INVALID_OUTLINE ) + || !bds.Ignore( DRCE_TRACK_NEAR_EDGE ) + || !bds.Ignore( DRCE_VIA_NEAR_EDGE ) + || !bds.Ignore( DRCE_PAD_NEAR_EDGE ) ) + { + if( aMessages ) + { + aMessages->AppendText( _( "Board Outline...\n" ) ); + wxSafeYield(); + } + + testOutline( commit ); + } + + if( aMessages ) + { + aMessages->AppendText( _( "Netclasses...\n" ) ); + wxSafeYield(); + } + + DRC_NETCLASS_TESTER netclassTester( [&]( MARKER_PCB* aMarker ) + { + addMarkerToPcb( commit, aMarker ); + } ); + + if( !netclassTester.RunDRC( userUnits(), *m_pcb ) ) + { + // testing the netclasses is a special case because if the netclasses + // do not pass the BOARD_DESIGN_SETTINGS checks, then every member of a net + // class (a NET) will cause its items such as tracks, vias, and pads + // to also fail. So quit after *all* netclass errors have been reported. + if( aMessages ) + aMessages->AppendText( _( "NETCLASS VIOLATIONS: Aborting DRC\n" ) ); + + commit.Push( wxEmptyString, false, false ); + + // update the m_drcDialog listboxes + updatePointers(); + + return; + } + + // test pad to pad clearances, nothing to do with tracks, vias or zones. + if( !bds.Ignore( DRCE_PAD_NEAR_EDGE ) + || !bds.Ignore( DRCE_PAD_NEAR_PAD ) + || !bds.Ignore( DRCE_HOLE_NEAR_PAD ) ) + { + if( aMessages ) + { + aMessages->AppendText( _( "Pad clearances...\n" ) ); + wxSafeYield(); + } + + testPadClearances( commit ); + } + + // test drilled holes + if( !bds.Ignore( DRCE_DRILLED_HOLES_TOO_CLOSE ) + || !bds.Ignore( DRCE_TOO_SMALL_PAD_DRILL ) + || !bds.Ignore( DRCE_TOO_SMALL_VIA_DRILL ) + || !bds.Ignore( DRCE_TOO_SMALL_MICROVIA_DRILL ) ) + { + if( aMessages ) + { + aMessages->AppendText( _( "Drill sizes and clearances...\n" ) ); + wxSafeYield(); + } + + 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()->FillAllZones( caller ); + } + else + { + if( aMessages ) + aMessages->AppendText( _( "Checking zone fills...\n" ) ); + + m_toolMgr->GetTool()->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 ) + { + aMessages->AppendText( _( "Zone to zone clearances...\n" ) ); + wxSafeYield(); + } + + testZones( commit ); + + // find and gather unconnected pads. + if( m_doUnconnectedTest + && !bds.Ignore( DRCE_UNCONNECTED_ITEMS ) ) + { + if( aMessages ) + { + aMessages->AppendText( _( "Unconnected pads...\n" ) ); + aMessages->Refresh(); + } + + testUnconnected(); + } + + // find and gather vias, tracks, pads inside keepout areas. + if( m_doKeepoutTest ) + { + if( aMessages ) + { + aMessages->AppendText( _( "Keepout areas ...\n" ) ); + aMessages->Refresh(); + } + + 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_VIA_NEAR_COPPER ) + || !bds.Ignore( DRCE_TRACK_NEAR_COPPER ) ) + { + if( aMessages ) + { + aMessages->AppendText( _( "Text and graphic clearances...\n" ) ); + wxSafeYield(); + } + + testCopperTextAndGraphics( commit ); + } + + // test courtyards + 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(); + } + + DRC_COURTYARD_TESTER tester( [&]( MARKER_PCB* aMarker ) + { + addMarkerToPcb( commit, aMarker ); + } ); + + tester.RunDRC( userUnits(), *m_pcb ); + } + + for( DRC_ITEM* footprintItem : m_footprints ) + delete footprintItem; + + 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 + if( !bds.Ignore( DRCE_DISABLED_LAYER_ITEM ) ) + { + if( aMessages ) + { + aMessages->AppendText( _( "Items on disabled layers...\n" ) ); + aMessages->Refresh(); + } + + testDisabledLayers( commit ); + } + + 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() ); + + tester.RunDRC( userUnits(), *m_pcb ); + } + + commit.Push( wxEmptyString, false, false ); + m_drcRun = true; + + // update the m_drcDialog listboxes + updatePointers(); + + if( aMessages ) + { + // no newline on this one because it is last, don't want the window + // to unnecessarily scroll. + aMessages->AppendText( _( "Finished" ) ); + } +} + + +void DRC::updatePointers() +{ + // update my pointers, m_editFrame is the only unchangeable one + m_pcb = m_editFrame->GetBoard(); + + m_editFrame->ResolveDRCExclusions(); + + if( m_drcDialog ) // Use diag list boxes only in DRC dialog + { + 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 ) ); + } +} + + +void DRC::testPadClearances( BOARD_COMMIT& aCommit ) +{ + BOARD_DESIGN_SETTINGS& bds = m_pcb->GetDesignSettings(); + std::vector sortedPads; + + m_pcb->GetSortedPadListByXthenYCoord( sortedPads ); + + if( sortedPads.empty() ) + return; + + // find the max size of the pads (used to stop the pad-to-pad tests) + int max_size = 0; + + for( D_PAD* pad : sortedPads ) + { + // GetBoundingRadius() is the radius of the minimum sized circle fully containing the pad + int radius = pad->GetBoundingRadius(); + + if( radius > max_size ) + max_size = radius; + } + + // 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(); + + // Test the pads + for( auto& pad : sortedPads ) + { + if( !bds.Ignore( DRCE_PAD_NEAR_EDGE ) && m_board_outline_valid ) + { + int minClearance = bds.m_CopperEdgeClearance; + m_clearanceSource = _( "board edge" ); + + static DRAWSEGMENT dummyEdge; + dummyEdge.SetLayer( Edge_Cuts ); + + if( pad->GetRuleClearance( &dummyEdge, &minClearance, &m_clearanceSource ) ) + /* minClearance and m_clearanceSource set in GetRuleClearance() */; + + for( auto it = m_board_outlines.IterateSegmentsWithHoles(); it; it++ ) + { + int actual; + + if( !checkClearanceSegmToPad( *it, 0, pad, minClearance, &actual ) ) + { + actual = std::max( 0, actual ); + DRC_ITEM* drcItem = new DRC_ITEM( DRCE_PAD_NEAR_EDGE ); + + 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_PAD_NEAR_PAD ) || !bds.Ignore( DRCE_HOLE_NEAR_PAD ) ) + { + int x_limit = pad->GetPosition().x + pad->GetBoundingRadius() + max_size; + + doPadToPadsDrc( aCommit, pad, &pad, listEnd, x_limit ); + } + } +} + + +void DRC::testTracks( BOARD_COMMIT& aCommit, wxWindow *aActiveWindow, bool aShowProgressBar ) +{ + wxProgressDialog* 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 wxProgressDialog( _( "Track clearances" ), wxEmptyString, + deltamax, aActiveWindow, + wxPD_AUTO_HIDE | wxPD_CAN_ABORT | wxPD_ELAPSED_TIME ); + progressDialog->Update( 0, wxEmptyString ); + } + + std::shared_ptr 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; + + 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 + doTrackDrc( aCommit, *seg_it, seg_it + 1, m_pcb->Tracks().end(), m_testTracksAgainstZones ); + + // 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 ) ) + { + DRC_ITEM* drcItem = new DRC_ITEM( code ); + drcItem->SetItems( *seg_it ); + + MARKER_PCB* marker = new MARKER_PCB( drcItem, pos ); + addMarkerToPcb( aCommit, marker ); + } + } + + if( progressDialog ) + progressDialog->Destroy(); +} + + +void DRC::testUnconnected() +{ + for( DRC_ITEM* unconnectedItem : m_unconnected ) + delete unconnectedItem; + + m_unconnected.clear(); + + auto connectivity = m_pcb->GetConnectivity(); + + connectivity->Clear(); + connectivity->Build( m_pcb ); // just in case. This really needs to be reliable. + connectivity->RecalculateRatsnest(); + + std::vector edges; + connectivity->GetUnconnectedEdges( edges ); + + for( const CN_EDGE& edge : edges ) + { + DRC_ITEM* item = new DRC_ITEM( DRCE_UNCONNECTED_ITEMS ); + item->SetItems( edge.GetSourceNode()->Parent(), edge.GetTargetNode()->Parent() ); + m_unconnected.push_back( item ); + } +} + + +void DRC::testZones( BOARD_COMMIT& aCommit ) +{ + BOARD_DESIGN_SETTINGS& bds = m_pcb->GetDesignSettings(); + + // Test copper areas for valid netcodes + // 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 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 ) + { + DRC_ITEM* drcItem = new DRC_ITEM( DRCE_ZONE_HAS_EMPTY_NET ); + drcItem->SetItems( zone ); + + MARKER_PCB* marker = new MARKER_PCB( drcItem, zone->GetPosition() ); + addMarkerToPcb( aCommit, marker ); + } + } + + ZONE_CONTAINER* zoneRef = m_pcb->GetArea( ii ); + std::set colinearCorners; + zoneRef->GetColinearCorners( m_pcb, colinearCorners ); + + zoneRef->BuildSmoothedPoly( smoothed_polys[ii], &colinearCorners ); + } + + // 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( 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 ) ) + { + DRC_ITEM* drcItem = new DRC_ITEM( 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 ) ) + { + DRC_ITEM* drcItem = new DRC_ITEM( 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 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& conflict : conflictPoints ) + { + int actual = conflict.second; + DRC_ITEM* drcItem; + + if( actual <= 0 ) + { + drcItem = new DRC_ITEM( DRCE_ZONES_INTERSECT ); + } + else + { + drcItem = new DRC_ITEM( DRCE_ZONES_TOO_CLOSE ); + + 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 ); + } + } + } +} + + +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() ) ) + 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() ) ) + testCopperDrawItem( aCommit, &ref ); + + if( val.IsVisible() && IsCopperLayer( val.GetLayer() ) ) + 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() ) + testCopperDrawItem( aCommit, item ); + else if( item->Type() == PCB_MODULE_EDGE_T ) + testCopperDrawItem( aCommit, item ); + } + } + } +} + + +void DRC::testCopperDrawItem( BOARD_COMMIT& aCommit, BOARD_ITEM* aItem ) +{ + EDA_RECT bbox; + std::vector itemShape; + int itemWidth; + DRAWSEGMENT* drawItem = dynamic_cast( aItem ); + EDA_TEXT* textItem = dynamic_cast( aItem ); + + if( drawItem ) + { + bbox = drawItem->GetBoundingBox(); + itemWidth = drawItem->GetWidth(); + + switch( drawItem->GetShape() ) + { + case S_ARC: + { + SHAPE_ARC arc( drawItem->GetCenter(), drawItem->GetArcStart(), + (double) drawItem->GetAngle() / 10.0 ); + + SHAPE_LINE_CHAIN l = arc.ConvertToPolyline(); + + for( int i = 0; i < l.SegmentCount(); i++ ) + itemShape.push_back( l.Segment( i ) ); + + break; + } + + case S_SEGMENT: + itemShape.emplace_back( SEG( drawItem->GetStart(), drawItem->GetEnd() ) ); + break; + + case S_CIRCLE: + { + // SHAPE_CIRCLE has no ConvertToPolyline() method, so use a 360.0 SHAPE_ARC + SHAPE_ARC circle( drawItem->GetCenter(), drawItem->GetEnd(), 360.0 ); + + SHAPE_LINE_CHAIN l = circle.ConvertToPolyline(); + + for( int i = 0; i < l.SegmentCount(); i++ ) + itemShape.push_back( l.Segment( i ) ); + + break; + } + + case S_CURVE: + { + drawItem->RebuildBezierToSegmentsPointsList( drawItem->GetWidth() ); + wxPoint start_pt = drawItem->GetBezierPoints()[0]; + + for( unsigned int jj = 1; jj < drawItem->GetBezierPoints().size(); jj++ ) + { + wxPoint end_pt = drawItem->GetBezierPoints()[jj]; + itemShape.emplace_back( SEG( start_pt, end_pt ) ); + start_pt = end_pt; + } + + break; + } + + case S_POLYGON: + { + SHAPE_LINE_CHAIN l = drawItem->GetPolyShape().Outline( 0 ); + + for( int i = 0; i < l.SegmentCount(); i++ ) + itemShape.push_back( l.Segment( i ) ); + } + break; + + default: + wxFAIL_MSG( "unknown shape type" ); + break; + } + } + else if( textItem ) + { + bbox = textItem->GetTextBox(); + itemWidth = textItem->GetEffectiveTextPenWidth(); + + std::vector textShape; + textItem->TransformTextShapeToSegmentList( textShape ); + + for( unsigned jj = 0; jj < textShape.size(); jj += 2 ) + itemShape.emplace_back( SEG( textShape[jj], textShape[jj+1] ) ); + } + else + { + wxFAIL_MSG( "unknown item type in testCopperDrawItem()" ); + return; + } + + SHAPE_RECT rect_area( bbox.GetX(), bbox.GetY(), bbox.GetWidth(), bbox.GetHeight() ); + + if( itemShape.empty() ) + return; + + // Test tracks and vias + for( auto track : m_pcb->Tracks() ) + { + if( !track->IsOnLayer( aItem->GetLayer() ) ) + continue; + + int minClearance = track->GetClearance( aItem, &m_clearanceSource ); + int widths = ( track->GetWidth() + itemWidth ) / 2; + int center2centerAllowed = minClearance + widths; + + SEG trackSeg( track->GetStart(), track->GetEnd() ); + + // Fast test to detect a track segment candidate inside the text bounding box + if( !rect_area.Collide( trackSeg, center2centerAllowed ) ) + continue; + + OPT minSeg; + SEG::ecoord center2center_squared = 0; + + for( const SEG& itemSeg : itemShape ) + { + SEG::ecoord thisDist_squared = trackSeg.SquaredDistance( itemSeg ); + + if( !minSeg || thisDist_squared < center2center_squared ) + { + minSeg = itemSeg; + center2center_squared = thisDist_squared; + } + } + + if( center2center_squared < SEG::Square( center2centerAllowed ) ) + { + int actual = std::max( 0.0, sqrt( center2center_squared ) - widths ); + int errorCode = ( track->Type() == PCB_VIA_T ) ? DRCE_VIA_NEAR_COPPER + : DRCE_TRACK_NEAR_COPPER; + DRC_ITEM* drcItem = new DRC_ITEM( errorCode ); + + 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( track, aItem ); + + wxPoint pos = GetLocation( track, minSeg.get() ); + MARKER_PCB* marker = new MARKER_PCB( drcItem, pos ); + addMarkerToPcb( aCommit, marker ); + } + } + + // Test pads + for( auto pad : m_pcb->GetPads() ) + { + if( !pad->IsOnLayer( aItem->GetLayer() ) ) + continue; + + // Graphic items are allowed to act as net-ties within their own footprint + if( drawItem && pad->GetParent() == drawItem->GetParent() ) + continue; + + int minClearance = pad->GetClearance( aItem, &m_clearanceSource ); + int widths = itemWidth / 2; + int center2centerAllowed = minClearance + widths; + + // 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; + VECTOR2I shape_pos( pad->ShapePos() ); + + if( !rect_area.Collide( SEG( shape_pos, shape_pos ), bb_radius ) ) + continue; + + SHAPE_POLY_SET padOutline; + pad->TransformShapeWithClearanceToPolygon( padOutline, 0 ); + + OPT minSeg; + SEG::ecoord center2center_squared = 0; + + for( const SEG& itemSeg : itemShape ) + { + SEG::ecoord thisCenter2center_squared = padOutline.SquaredDistance( itemSeg ); + + if( !minSeg || thisCenter2center_squared < center2center_squared ) + { + minSeg = itemSeg; + center2center_squared = thisCenter2center_squared; + } + } + + if( center2center_squared < SEG::Square( center2centerAllowed ) ) + { + int actual = std::max( 0.0, sqrt( center2center_squared ) - widths ); + DRC_ITEM* drcItem = new DRC_ITEM( DRCE_PAD_NEAR_COPPER ); + + 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, aItem ); + + MARKER_PCB* marker = new MARKER_PCB( drcItem, pad->GetPosition() ); + addMarkerToPcb( aCommit, marker ); + } + } +} + + +void DRC::testOutline( BOARD_COMMIT& aCommit ) +{ + 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 + { + DRC_ITEM* drcItem = new DRC_ITEM( DRCE_INVALID_OUTLINE ); + + m_msg.Printf( drcItem->GetErrorText() + _( " (not a closed shape)" ) ); + + drcItem->SetErrorMessage( m_msg ); + drcItem->SetItems( m_pcb ); + + MARKER_PCB* marker = new MARKER_PCB( drcItem, error_loc ); + addMarkerToPcb( aCommit, marker ); + } +} + + +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() ) ) + { + DRC_ITEM* drcItem = new DRC_ITEM( DRCE_DISABLED_LAYER_ITEM ); + + m_msg.Printf( drcItem->GetErrorText() + _( "layer %s" ), + track->GetLayerName() ); + + drcItem->SetErrorMessage( m_msg ); + drcItem->SetItems( track ); + + MARKER_PCB* marker = new MARKER_PCB( drcItem, track->GetPosition() ); + addMarkerToPcb( aCommit, marker ); + } + } + + for( MODULE* module : m_pcb->Modules() ) + { + module->RunOnChildren( + [&]( BOARD_ITEM* child ) + { + if( disabledLayers.test( child->GetLayer() ) ) + { + DRC_ITEM* drcItem = new DRC_ITEM( DRCE_DISABLED_LAYER_ITEM ); + + m_msg.Printf( drcItem->GetErrorText() + _( "layer %s" ), + child->GetLayerName() ); + + drcItem->SetErrorMessage( m_msg ); + drcItem->SetItems( child ); + + MARKER_PCB* marker = new MARKER_PCB( drcItem, child->GetPosition() ); + addMarkerToPcb( aCommit, marker ); + } + } ); + } + + for( ZONE_CONTAINER* zone : m_pcb->Zones() ) + { + if( disabledLayers.test( zone->GetLayer() ) ) + { + DRC_ITEM* drcItem = new DRC_ITEM( DRCE_DISABLED_LAYER_ITEM ); + + m_msg.Printf( drcItem->GetErrorText() + _( "layer %s" ), + zone->GetLayerName() ); + + drcItem->SetErrorMessage( m_msg ); + drcItem->SetItems( zone ); + + MARKER_PCB* marker = new MARKER_PCB( drcItem, zone->GetPosition() ); + addMarkerToPcb( aCommit, marker ); + } + } +} + + +bool DRC::doPadToPadsDrc( BOARD_COMMIT& aCommit, D_PAD* aRefPad, D_PAD** aStart, D_PAD** aEnd, + int x_limit ) +{ + const static LSET all_cu = LSET::AllCuMask(); + + LSET layerMask = aRefPad->GetLayerSet() & all_cu; + + // For hole testing we use a dummy pad which is given the shape of the hole. Note that + // this pad must have a parent because some functions expect a non-null parent to find + // the pad's board. + MODULE dummymodule( m_pcb ); // Creates a dummy parent + D_PAD dummypad( &dummymodule ); + + // Ensure the hole is on all copper layers + dummypad.SetLayerSet( all_cu | dummypad.GetLayerSet() ); + + for( D_PAD** pad_list = aStart; pad_listGetPosition().x > x_limit + // because the list is sorted by X values + if( pad->GetPosition().x > x_limit ) + 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 + // copper layers, so we test them + if( ( pad->GetLayerSet() & layerMask ) == 0 && + ( pad->GetLayerSet() & all_cu ) != 0 && + ( aRefPad->GetLayerSet() & all_cu ) != 0 ) + { + // if holes are in the same location and have the same size and shape, + // this can be accepted + if( pad->GetPosition() == aRefPad->GetPosition() + && 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 + if( pad->GetOrientation() == aRefPad->GetOrientation() ) + continue; + } + + /* Here, we must test clearance between holes and pads + * dummy pad size and shape is adjusted to pad drill size and shape + */ + if( pad->GetDrillSize().x ) + { + // pad under testing has a hole, test this hole against pad reference + dummypad.SetPosition( pad->GetPosition() ); + dummypad.SetSize( pad->GetDrillSize() ); + dummypad.SetShape( pad->GetDrillShape() == PAD_DRILL_SHAPE_OBLONG ? + PAD_SHAPE_OVAL : PAD_SHAPE_CIRCLE ); + dummypad.SetOrientation( pad->GetOrientation() ); + + int minClearance = aRefPad->GetClearance( nullptr, &m_clearanceSource ); + int actual; + + if( !checkClearancePadToPad( aRefPad, &dummypad, minClearance, &actual ) ) + { + DRC_ITEM* drcItem = new DRC_ITEM( DRCE_HOLE_NEAR_PAD ); + + 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, aRefPad ); + + MARKER_PCB* marker = new MARKER_PCB( drcItem, pad->GetPosition() ); + addMarkerToPcb( aCommit, marker ); + return false; + } + } + + if( aRefPad->GetDrillSize().x ) // pad reference has a hole + { + dummypad.SetPosition( aRefPad->GetPosition() ); + dummypad.SetSize( aRefPad->GetDrillSize() ); + dummypad.SetShape( aRefPad->GetDrillShape() == PAD_DRILL_SHAPE_OBLONG ? + PAD_SHAPE_OVAL : PAD_SHAPE_CIRCLE ); + dummypad.SetOrientation( aRefPad->GetOrientation() ); + + int minClearance = pad->GetClearance( nullptr, &m_clearanceSource ); + int actual; + + if( !checkClearancePadToPad( pad, &dummypad, minClearance, &actual ) ) + { + DRC_ITEM* drcItem = new DRC_ITEM( DRCE_HOLE_NEAR_PAD ); + + 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( aRefPad, pad ); + + MARKER_PCB* marker = new MARKER_PCB( drcItem, aRefPad->GetPosition() ); + addMarkerToPcb( aCommit, marker ); + return false; + } + } + + continue; + } + + // 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() ) ) + continue; + + // if pads are from the same footprint + if( pad->GetParent() == aRefPad->GetParent() ) + { + // and have the same pad number ( equivalent pads ) + + // one can argue that this 2nd test is not necessary, that any + // two pads from a single module are acceptable. This 2nd test + // should eventually be a configuration option. + if( pad->PadNameEqual( aRefPad ) ) + continue; + } + + // 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; + } + + int minClearance = aRefPad->GetClearance( pad, &m_clearanceSource ); + int clearanceAllowed = minClearance - m_pcb->GetDesignSettings().GetDRCEpsilon(); + int actual; + + if( !checkClearancePadToPad( aRefPad, pad, clearanceAllowed, &actual ) ) + { + DRC_ITEM* drcItem = new DRC_ITEM( DRCE_PAD_NEAR_PAD ); + + 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( aRefPad, pad ); + + MARKER_PCB* marker = new MARKER_PCB( drcItem, aRefPad->GetPosition() ); + addMarkerToPcb( aCommit, marker ); + return false; + } + } + + return true; +} + + +void DRC::setTransitions() +{ + Go( &DRC::ShowDRCDialog, PCB_ACTIONS::runDRC.MakeEvent() ); +} + + +const int UI_EPSILON = Mils2iu( 5 ); + + +wxPoint DRC::GetLocation( TRACK* aTrack, ZONE_CONTAINER* aConflictZone ) +{ + SHAPE_POLY_SET* conflictOutline; + + if( aConflictZone->IsFilled() ) + conflictOutline = const_cast( &aConflictZone->GetFilledPolysList() ); + else + conflictOutline = aConflictZone->Outline(); + + wxPoint pt1 = aTrack->GetPosition(); + wxPoint pt2 = aTrack->GetEnd(); + + // If the mid-point is in the zone, then that's a fine place for the marker + if( conflictOutline->SquaredDistance( ( pt1 + pt2 ) / 2 ) == 0 ) + return ( pt1 + pt2 ) / 2; + + // Otherwise do a binary search for a "good enough" marker location + else + { + while( GetLineLength( pt1, pt2 ) > UI_EPSILON ) + { + if( conflictOutline->SquaredDistance( pt1 ) < conflictOutline->SquaredDistance( pt2 ) ) + pt2 = ( pt1 + pt2 ) / 2; + else + pt1 = ( pt1 + pt2 ) / 2; + } + + // Once we're within UI_EPSILON pt1 and pt2 are "equivalent" + return pt1; + } +} + + +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; +} + + diff --git a/qa/drc_proto/drc.h b/qa/drc_proto/drc.h new file mode 100644 index 0000000000..8c2ac13a7f --- /dev/null +++ b/qa/drc_proto/drc.h @@ -0,0 +1,356 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2007-2016 Dick Hollenbeck, dick@softplc.com + * Copyright (C) 2017-2019 KiCad Developers, see change_log.txt for contributors. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#ifndef DRC_H +#define DRC_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/// DRC error codes: +enum PCB_DRC_CODE { + DRCE_FIRST = 1, + DRCE_UNCONNECTED_ITEMS = DRCE_FIRST, ///< items are unconnected + DRCE_TRACK_NEAR_HOLE, ///< thru hole is too close to track + DRCE_TRACK_NEAR_PAD, ///< pad too close to track + DRCE_TRACK_NEAR_VIA, ///< track too close to via + DRCE_TRACK_NEAR_ZONE, ///< track & zone collide or are too close together + DRCE_TRACK_NEAR_COPPER, ///< track & copper graphic collide or are too close + DRCE_VIA_NEAR_VIA, ///< via too close to via + DRCE_VIA_NEAR_TRACK, ///< via too close to track + DRCE_VIA_NEAR_COPPER, ///< via and copper graphic collide or are too close + DRCE_TRACK_ENDS, ///< track ends are too close + DRCE_TRACK_SEGMENTS_TOO_CLOSE, ///< 2 parallel track segments too close: segm ends between segref ends + DRCE_TRACKS_CROSSING, ///< tracks are crossing + DRCE_TRACK_NEAR_EDGE, ///< track too close to board edge + DRCE_VIA_NEAR_EDGE, ///< via too close to board edge + DRCE_PAD_NEAR_EDGE, ///< pad too close to board edge + DRCE_PAD_NEAR_PAD, ///< pad too close to pad + DRCE_PAD_NEAR_COPPER, ///< pad and copper graphic collide or are too close + DRCE_ZONES_INTERSECT, ///< copper area outlines intersect + DRCE_ZONES_TOO_CLOSE, ///< copper area outlines are too close + DRCE_ZONE_HAS_EMPTY_NET, ///< copper area has a net but no pads in nets, which is suspicious + DRCE_DANGLING_VIA, ///< via which isn't connected to anything + DRCE_DANGLING_TRACK, ///< track with at least one end not connected to anything + DRCE_HOLE_NEAR_PAD, ///< hole too close to pad + DRCE_HOLE_NEAR_TRACK, ///< hole too close to track + DRCE_DRILLED_HOLES_TOO_CLOSE, ///< overlapping drilled holes break drill bits + DRCE_TOO_SMALL_TRACK_WIDTH, ///< Too small track width + DRCE_TOO_LARGE_TRACK_WIDTH, ///< Too small track width + DRCE_TOO_SMALL_VIA, ///< Too small via size + DRCE_TOO_SMALL_VIA_ANNULUS, ///< Via size and drill leave annulus too small + DRCE_TOO_SMALL_VIA_DRILL, ///< Too small via drill + DRCE_TOO_SMALL_PAD_DRILL, ///< Too small via drill + DRCE_VIA_HOLE_BIGGER, ///< via's hole is bigger than its diameter + DRCE_MICROVIA_NOT_ALLOWED, ///< micro vias are not allowed + DRCE_MICROVIA_TOO_MANY_LAYERS, ///< micro via's layer pair incorrect (layers must be adjacent) + DRCE_TOO_SMALL_MICROVIA, ///< Too small micro via size + DRCE_TOO_SMALL_MICROVIA_DRILL, ///< Too small micro via drill + DRCE_BURIED_VIA_NOT_ALLOWED, ///< buried vias are not allowed + DRCE_NETCLASS_TRACKWIDTH, ///< netclass has TrackWidth < board.m_designSettings->m_TrackMinWidth + DRCE_NETCLASS_CLEARANCE, ///< netclass has Clearance < board.m_designSettings->m_TrackClearance + DRCE_NETCLASS_VIAANNULUS, ///< netclass ViaSize & ViaDrill leave annulus < board.m_designSettings->m_ViasMinAnnulus + DRCE_NETCLASS_VIASIZE, ///< netclass has ViaSize < board.m_designSettings->m_ViasMinSize + DRCE_NETCLASS_VIADRILLSIZE, ///< netclass has ViaDrillSize < board.m_designSettings->m_MinThroughDrill + DRCE_NETCLASS_uVIASIZE, ///< netclass has ViaSize < board.m_designSettings->m_MicroViasMinSize + DRCE_NETCLASS_uVIADRILLSIZE, ///< netclass has ViaSize < board.m_designSettings->m_MicroViasMinDrill + DRCE_VIA_INSIDE_KEEPOUT, + DRCE_MICROVIA_INSIDE_KEEPOUT, + DRCE_BBVIA_INSIDE_KEEPOUT, + DRCE_TRACK_INSIDE_KEEPOUT, + DRCE_PAD_INSIDE_KEEPOUT, + DRCE_FOOTPRINT_INSIDE_KEEPOUT, + DRCE_HOLE_INSIDE_KEEPOUT, + DRCE_TEXT_INSIDE_KEEPOUT, + DRCE_GRAPHICS_INSIDE_KEEPOUT, + DRCE_OVERLAPPING_FOOTPRINTS, ///< footprint courtyards overlap + DRCE_MISSING_COURTYARD, ///< footprint has no courtyard defined + DRCE_MALFORMED_COURTYARD, ///< footprint has a courtyard but malformed + ///< (not convertible to a closed polygon with holes) + DRCE_PTH_IN_COURTYARD, + DRCE_NPTH_IN_COURTYARD, + DRCE_DISABLED_LAYER_ITEM, ///< item on a disabled layer + DRCE_INVALID_OUTLINE, ///< invalid board outline + DRCE_MISSING_FOOTPRINT, ///< footprint not found for netlist item + DRCE_DUPLICATE_FOOTPRINT, ///< more than one footprints found for netlist item + DRCE_EXTRA_FOOTPRINT, ///< netlist item not found for footprint + + DRCE_UNRESOLVED_VARIABLE, + + DRCE_LAST = DRCE_UNRESOLVED_VARIABLE, + + // These are actually Cleanup Tracks and Vias actions, not DRCE errors + CLEANUP_SHORT, + CLEANUP_REDUNDANT_VIA, + CLEANUP_DUPLICATE_TRACK, + CLEANUP_MERGE_TRACKS, + CLEANUP_DANGLING_TRACK, + CLEANUP_DANGLING_VIA, + CLEANUP_ZERO_LENGTH_TRACK, + CLEANUP_TRACK_IN_PAD +}; + + +class PCB_EDIT_FRAME; +class DIALOG_DRC; +class BOARD_ITEM; +class BOARD; +class D_PAD; +class ZONE_CONTAINER; +class TRACK; +class MARKER_PCB; +class DRC_ITEM; +class NETCLASS; +class EDA_TEXT; +class DRAWSEGMENT; +class NETLIST; +class wxWindow; +class wxString; +class wxTextCtrl; + + +class DRC_ENGINE +{ + +} + +/** + * Design Rule Checker object that performs all the DRC tests. The output of + * the checking goes to the BOARD file in the form of two MARKER lists. Those + * two lists are displayable in the drc dialog box. And they can optionally + * be sent to a text file on disk. + * This class is given access to the windows and the BOARD + * that it needs via its constructor or public access functions. + */ +class DRC : public PCB_TOOL_BASE +{ + friend class DIALOG_DRC; + +public: + DRC(); + ~DRC(); + + /// @copydoc TOOL_INTERACTIVE::Reset() + void Reset( RESET_REASON aReason ) override; + +private: + bool m_doUnconnectedTest; // enable unconnected tests + bool m_testTracksAgainstZones; // enable zone to items clearance tests + bool m_doKeepoutTest; // enable keepout areas to items clearance tests + bool m_refillZones; // refill zones if requested (by user). + bool m_reportAllTrackErrors; // Report all tracks errors (or only 4 first errors) + bool m_testFootprints; // Test footprints against schematic + + PCB_EDIT_FRAME* m_editFrame; // The pcb frame editor which owns the board + BOARD* m_pcb; + SHAPE_POLY_SET m_board_outlines; // The board outline including cutouts + bool m_board_outline_valid; + DIALOG_DRC* m_drcDialog; + + std::vector m_unconnected; // list of unconnected pads + std::vector m_footprints; // list of footprint warnings + bool m_drcRun; // indicates DRC has been run at least once + bool m_footprintsTested; // indicates footprints were tested in last run + + std::vector m_ruleSelectors; + std::vector m_rules; + + // Temp variables for performance during a single DRC run + // + // wxString's c'tor is surprisingly expensive, and in the world of DRC everything matters + // + wxString m_msg; + wxString m_clearanceSource; + int m_largestClearance; + +private: + ///> Sets up handlers for various events. + void setTransitions() override; + + /** + * Update needed pointers from the one pointer which is known not to change. + */ + void updatePointers(); + + EDA_UNITS userUnits() const { return m_editFrame->GetUserUnits(); } + + /** + * Adds a DRC marker to the PCB through the COMMIT mechanism. + */ + void addMarkerToPcb( BOARD_COMMIT& aCommit, MARKER_PCB* aMarker ); + + //---------------------------------------------- + + /** + * Perform the DRC on all tracks. + * + * This test can take a while, a progress bar can be displayed + * @param aActiveWindow = the active window ued as parent for the progress bar + * @param aShowProgressBar = true to show a progress bar + * (Note: it is shown only if there are many tracks) + */ + void testTracks( BOARD_COMMIT& aCommit, wxWindow * aActiveWindow, bool aShowProgressBar ); + + void testPadClearances( BOARD_COMMIT& aCommit ); + + void testUnconnected(); + + void testZones( BOARD_COMMIT& aCommit ); + + void testCopperDrawItem( BOARD_COMMIT& aCommit, BOARD_ITEM* aDrawing ); + + void testCopperTextAndGraphics( BOARD_COMMIT& aCommit ); + + // Tests for items placed on disabled layers (causing false connections). + void testDisabledLayers( BOARD_COMMIT& aCommit ); + + /** + * Test that the board outline is contiguous and composed of valid elements + */ + void testOutline( BOARD_COMMIT& aCommit ); + + //---------------------------------------------- + + /** + * Test the clearance between aRefPad and other pads. + * + * The pad list must be sorted by x coordinate. + * + * @param aRefPad is the pad to test + * @param aStart is the first pad of the list to test against aRefPad + * @param aEnd is the end of the list and is not included + * @param x_limit is used to stop the test + * (i.e. when the current pad pos X in list exceeds this limit, because the list + * is sorted by X coordinate) + */ + bool doPadToPadsDrc( BOARD_COMMIT& aCommit, D_PAD* aRefPad, D_PAD** aStart, D_PAD** aEnd, + int x_limit ); + + /** + * Test the current segment. + * + * @param aRefSeg The segment to test + * @param aStartIt the iterator to the first track to test + * @param aEndIt the marker for the iterator end + * @param aTestZones true if should do copper zones test. This can be very time consumming + * @return bool - true if no problems, else false and m_currentMarker is + * filled in with the problem information. + */ + void doTrackDrc( BOARD_COMMIT& aCommit, TRACK* aRefSeg, TRACKS::iterator aStartIt, + TRACKS::iterator aEndIt, bool aTestZones ); + + //--------------------------------------------------- + + /** + * @param aRefPad The reference pad to check + * @param aPad Another pad to check against + * @param aMinClearance is the minimum allowed distance between the pads + * @param aActual [out] it the actual distance (only guaranteed to be set for violations) + * @return bool - true if clearance between aRefPad and aPad is >= aMinClearance, else false + */ + bool checkClearancePadToPad( D_PAD* aRefPad, D_PAD* aPad, int aMinClearance, int* aActual ); + + + /** + * Check the distance from a pad to segment. This function uses several + * instance variable not passed in: + * @param aPad Is the pad involved in the check + * @param aSegmentWidth width of the segment to test + * @param aMinDist Is the minimum clearance needed + * @param aActualDist [out] Is the actual clearance (only guarantted to be set on violations) + * + * @return true distance >= dist_min, + * false if distance < dist_min + */ + bool checkClearanceSegmToPad( const SEG& seg, int segWidth, const D_PAD* pad, + int minClearance, int* aActualDist ); + + + + //-------------------------------------------------- + +public: + /** + * Load the DRC rules. Must be called after the netclasses have been read. + */ + bool LoadRules(); + + /** + * Fetches a reasonable point for marking a violoation between two non-point objects. + */ + static wxPoint GetLocation( TRACK* aTrack, ZONE_CONTAINER* aConflictZone ); + static wxPoint GetLocation( TRACK* aTrack, const SEG& aConflictSeg ); + + /** + * Open a dialog and prompts the user, then if a test run button is + * clicked, runs the test(s) and creates the MARKERS. The dialog is only + * created if it is not already in existence. + * + * @param aParent is the parent window for wxWidgets. Usually the PCB editor frame + * but can be another dialog + * if aParent == NULL (default), the parent will be the PCB editor frame + * and the dialog will be not modal (just float on parent + * if aParent is specified, the dialog will be modal. + * The modal mode is mandatory if the dialog is created from another dialog, not + * from the PCB editor frame + */ + void ShowDRCDialog( wxWindow* aParent ); + + int ShowDRCDialog( const TOOL_EVENT& aEvent ); + + /** + * Check to see if the DRC dialog is currently shown + * + * @return true if the dialog is shown + */ + bool IsDRCDialogShown(); + + /** + * Deletes this ui dialog box and zeros out its pointer to remember + * the state of the dialog's existence. + * + * @param aReason Indication of which button was clicked to cause the destruction. + * if aReason == wxID_OK, design parameters values which can be entered from the dialog + * will bbe saved in design parameters list + */ + void DestroyDRCDialog( int aReason ); + + /** + * Run all the tests specified with a previous call to + * SetSettings() + * @param aMessages = a wxTextControl where to display some activity messages. Can be NULL + */ + void RunTests( wxTextCtrl* aMessages = NULL ); +}; + + +#endif // DRC_H diff --git a/qa/drc_proto/drc_clearance_test.cpp b/qa/drc_proto/drc_clearance_test.cpp new file mode 100644 index 0000000000..1db010e23a --- /dev/null +++ b/qa/drc_proto/drc_clearance_test.cpp @@ -0,0 +1,758 @@ +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +namespace test { + +class DRC_TEST_PROVIDER_CLEARANCE : public DRC_TEST_PROVIDER +{ +public: + DRC_TEST_PROVIDER_CLEARANCE ( DRC_ENGINE *aDrc ) : + DRC_TEST_PROVIDER( aDrc ) + { + + } + + virtual ~DRC_TEST_PROVIDER_CLEARANCE() + { + + } + + virtual bool Run() override; + + virtual const wxString GetName() const override { return "clearance"; }; + virtual const wxString GetDescription() const override { return "Tests copper item clearance"; } + virtual std::set GetMatchingRuleIds() const override; + +private: + void testPadClearances( ); + bool doPadToPadsDrc( D_PAD* aRefPad, D_PAD** aStart, D_PAD** aEnd, int x_limit ); + + bool checkClearanceSegmToPad( const SEG& refSeg, int refSegWidth, const D_PAD* pad, + int minClearance, int* aActualDist ); + bool checkClearancePadToPad( D_PAD* aRefPad, D_PAD* aPad, int aMinClearance, int* aActual ); + bool poly2segmentDRC( wxPoint* aTref, int aTrefCount, wxPoint aSegStart, wxPoint aSegEnd, + int aDist, int* aActual ); + bool poly2polyDRC( wxPoint* aTref, int aTrefCount, wxPoint* aTtest, int aTtestCount, + int aAllowedDist, int* actualDist ); + + BOARD* m_board; + int m_largestClearance; + SHAPE_POLY_SET m_boardOutline; // The board outline including cutouts + bool m_boardOutlineValid; + + +}; + +}; + +bool test::DRC_TEST_PROVIDER_CLEARANCE::Run() +{ + auto bds = m_drcEngine->GetDesignSettings(); + + m_board = m_drcEngine->GetBoard(); + m_largestClearance = bds->GetBiggestClearanceValue(); + + testPadClearances(); + + return false; +} + + + +void test::DRC_TEST_PROVIDER_CLEARANCE::testPadClearances( ) +{ + auto bds = m_drcEngine->GetDesignSettings(); + std::vector sortedPads; + + m_board->GetSortedPadListByXthenYCoord( sortedPads ); + + if( sortedPads.empty() ) + return; + + // find the max size of the pads (used to stop the pad-to-pad tests) + int max_size = 0; + + for( D_PAD* pad : sortedPads ) + { + // GetBoundingRadius() is the radius of the minimum sized circle fully containing the pad + int radius = pad->GetBoundingRadius(); + + if( radius > max_size ) + max_size = radius; + } + + // 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(); + + // Test the pads + for( auto& pad : sortedPads ) + { + +#if 0 + // fixme: move Board outline clearance to separate provider + if( m_boardOutlineValid ) + { + //int minClearance = bds->m_CopperEdgeClearance; + //m_clearanceSource = _( "board edge" ); + + static DRAWSEGMENT dummyEdge; + dummyEdge.SetLayer( Edge_Cuts ); + + //if( pad->GetRuleClearance( &dummyEdge, &minClearance, &m_clearanceSource ) ) + // /* minClearance and m_clearanceSource set in GetRuleClearance() */; + // FIXME + // auto rule = m_drcEngine->EvalRuleForItems( pad, dummyEdge, DRC_RULE_CLEARANCE ); + // min_clearance = rule->Constraint().Min(); + int minClearance; + + for( auto it = m_boardOutline.IterateSegmentsWithHoles(); it; it++ ) + { + int actual; + + if( !checkClearanceSegmToPad( *it, 0, pad, minClearance, &actual ) ) + { + actual = std::max( 0, actual ); + DRC_ITEM* drcItem = new DRC_ITEM( DRCE_PAD_NEAR_EDGE ); + wxString msg; + + msg.Printf( drcItem->GetErrorText() + _( " (%s clearance %s; actual %s)" ), + /*m_clearanceSource FIXME*/ "" , + MessageTextFromValue( userUnits(), minClearance, true ), + MessageTextFromValue( userUnits(), actual, true ) ); + + drcItem->SetErrorMessage( msg ); + drcItem->SetItems( pad ); + + MARKER_PCB* marker = nullptr; // fixme new MARKER_PCB( drcItem, pad->GetPosition() ); + AddMarkerToPcb( marker ); + + break; + } + } + } +#endif + //if( !bds->Ignore( DRCE_PAD_NEAR_PAD ) || !bds->Ignore( DRCE_HOLE_NEAR_PAD ) ) + { + int x_limit = pad->GetPosition().x + pad->GetBoundingRadius() + max_size; + + doPadToPadsDrc( pad, &pad, listEnd, x_limit ); + } + } +} + +bool test::DRC_TEST_PROVIDER_CLEARANCE::doPadToPadsDrc( D_PAD* aRefPad, D_PAD** aStart, D_PAD** aEnd, + int x_limit ) +{ + const static LSET all_cu = LSET::AllCuMask(); + + LSET layerMask = aRefPad->GetLayerSet() & all_cu; + + + // For hole testing we use a dummy pad which is given the shape of the hole. Note that + // this pad must have a parent because some functions expect a non-null parent to find + // the pad's board. + MODULE dummymodule( m_drcEngine->GetBoard() ); // Creates a dummy parent + D_PAD dummypad( &dummymodule ); + + // Ensure the hole is on all copper layers + dummypad.SetLayerSet( all_cu | dummypad.GetLayerSet() ); + + for( D_PAD** pad_list = aStart; pad_listGetPosition().x > x_limit + // because the list is sorted by X values + if( pad->GetPosition().x > x_limit ) + break; + +#if 0 + +// fixme move hole clearance check to another provider + + // 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 + // copper layers, so we test them + if( ( pad->GetLayerSet() & layerMask ) == 0 && + ( pad->GetLayerSet() & all_cu ) != 0 && + ( aRefPad->GetLayerSet() & all_cu ) != 0 ) + { + // if holes are in the same location and have the same size and shape, + // this can be accepted + if( pad->GetPosition() == aRefPad->GetPosition() + && 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 + if( pad->GetOrientation() == aRefPad->GetOrientation() ) + continue; + } + + + // fixme move hole clearance check to another providers + + /* Here, we must test clearance between holes and pads + * dummy pad size and shape is adjusted to pad drill size and shape + */ + if( pad->GetDrillSize().x ) + { + // pad under testing has a hole, test this hole against pad reference + dummypad.SetPosition( pad->GetPosition() ); + dummypad.SetSize( pad->GetDrillSize() ); + dummypad.SetShape( pad->GetDrillShape() == PAD_DRILL_SHAPE_OBLONG ? + PAD_SHAPE_OVAL : PAD_SHAPE_CIRCLE ); + dummypad.SetOrientation( pad->GetOrientation() ); + + int minClearance = 0; // fixme aRefPad->GetClearance( nullptr, &m_clearanceSource ); + + auto rule = m_drcEngine->MatchRulesForItems( DRC_RULE_ID_CLEARANCE, pad, &dummypad ); + int minClearance = rule->m_Value.Min(); + + int actual; + + if( !checkClearancePadToPad( aRefPad, &dummypad, minClearance, &actual ) ) + { + DRC_ITEM* drcItem = new DRC_ITEM( DRCE_HOLE_NEAR_PAD ); + wxString msg; + + msg.Printf( drcItem->GetErrorText() + _( " (%s clearance %s; actual %s)" ), + /* fixme m_clearanceSource */"", + MessageTextFromValue( userUnits(), minClearance, true ), + MessageTextFromValue( userUnits(), actual, true ) ); + + drcItem->SetErrorMessage( msg ); + drcItem->SetItems( pad, aRefPad ); + + MARKER_PCB* marker = nullptr; // fixme new MARKER_PCB( drcItem, pad->GetPosition() ); + AddMarkerToPcb( marker ); + return false; + } + } + + if( aRefPad->GetDrillSize().x ) // pad reference has a hole + { + dummypad.SetPosition( aRefPad->GetPosition() ); + dummypad.SetSize( aRefPad->GetDrillSize() ); + dummypad.SetShape( aRefPad->GetDrillShape() == PAD_DRILL_SHAPE_OBLONG ? + PAD_SHAPE_OVAL : PAD_SHAPE_CIRCLE ); + dummypad.SetOrientation( aRefPad->GetOrientation() ); + + // FIXME min_clearance = rule->Constraint().Min(); + //int minClearance = pad->GetClearance( nullptr, &m_clearanceSource ); + int minClearance; + int actual; + + if( !checkClearancePadToPad( pad, &dummypad, minClearance, &actual ) ) + { + DRC_ITEM* drcItem = new DRC_ITEM( DRCE_HOLE_NEAR_PAD ); + wxString msg; + + msg.Printf( drcItem->GetErrorText() + _( " (%s clearance %s; actual %s)" ), + /*m_clearanceSource FIXME */ "", + MessageTextFromValue( userUnits(), minClearance, true ), + MessageTextFromValue( userUnits(), actual, true ) ); + + drcItem->SetErrorMessage( msg ); + drcItem->SetItems( aRefPad, pad ); + + MARKER_PCB* marker = nullptr; // fixme new MARKER_PCB( drcItem, aRefPad->GetPosition() ); + AddMarkerToPcb( marker ); + return false; + } + } + + continue; + } +#endif + + // 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() ) ) + continue; + + // if pads are from the same footprint + if( pad->GetParent() == aRefPad->GetParent() ) + { + // and have the same pad number ( equivalent pads ) + + // one can argue that this 2nd test is not necessary, that any + // two pads from a single module are acceptable. This 2nd test + // should eventually be a configuration option. + if( pad->PadNameEqual( aRefPad ) ) + continue; + } + + // 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; + } + + auto constraint = m_drcEngine->EvalRulesForItems( test::DRC_RULE_ID_T::DRC_RULE_ID_CLEARANCE, aRefPad, pad ); + + //int minClearance = aRefPad->GetClearance( pad, *&m_clearanceSource ); + int minClearance; // fixme + + int clearanceAllowed = minClearance - m_drcEngine->GetDesignSettings()->GetDRCEpsilon(); + int actual; + + if( !checkClearancePadToPad( aRefPad, pad, clearanceAllowed, &actual ) ) + { + DRC_ITEM* drcItem = new DRC_ITEM( DRCE_PAD_NEAR_PAD ); + wxString msg; + msg.Printf( drcItem->GetErrorText() + _( " (%s clearance %s; actual %s)" ), + /*m_clearanceSource fixme*/ "", + MessageTextFromValue( userUnits(), minClearance, true ), + MessageTextFromValue( userUnits(), actual, true ) ); + + drcItem->SetErrorMessage( msg ); + drcItem->SetItems( aRefPad, pad ); + + MARKER_PCB* marker = nullptr; // fixme new MARKER_PCB( drcItem, aRefPad->GetPosition() ); + AddMarkerToPcb( marker ); + + return false; + } + } + + return true; +} + + +/* + * Test if distance between a segment and a pad is > minClearance. Return the actual + * distance if it is less. + */ +bool test::DRC_TEST_PROVIDER_CLEARANCE::checkClearanceSegmToPad( const SEG& refSeg, int refSegWidth, const D_PAD* pad, + int minClearance, int* aActualDist ) +{ + if( ( pad->GetShape() == PAD_SHAPE_CIRCLE || pad->GetShape() == PAD_SHAPE_OVAL ) ) + { + /* Treat an oval pad as a line segment along the hole's major axis, + * shortened by half its minor axis. + * A circular pad is just a degenerate case of an oval hole. + */ + wxPoint padStart, padEnd; + int padWidth; + + pad->GetOblongGeometry( pad->GetSize(), &padStart, &padEnd, &padWidth ); + padStart += pad->ShapePos(); + padEnd += pad->ShapePos(); + + SEG padSeg( padStart, padEnd ); + int widths = ( padWidth + refSegWidth ) / 2; + int center2centerAllowed = minClearance + widths; + + // Avoid square-roots if possible (for performance) + SEG::ecoord center2center_squared = refSeg.SquaredDistance( padSeg ); + + if( center2center_squared < SEG::Square( center2centerAllowed ) ) + { + *aActualDist = std::max( 0.0, sqrt( center2center_squared ) - widths ); + return false; + } + } + else if( ( pad->GetShape() == PAD_SHAPE_RECT || pad->GetShape() == PAD_SHAPE_ROUNDRECT ) + && ( (int) pad->GetOrientation() % 900 == 0 ) ) + { + EDA_RECT padBBox = pad->GetBoundingBox(); + int widths = refSegWidth / 2; + + // Note a ROUNDRECT pad with a corner radius = r can be treated as a smaller + // RECT (size - 2*r) with a clearance increased by r + if( pad->GetShape() == PAD_SHAPE_ROUNDRECT ) + { + padBBox.Inflate( - pad->GetRoundRectCornerRadius() ); + widths += pad->GetRoundRectCornerRadius(); + } + + SHAPE_RECT padShape( padBBox.GetPosition(), padBBox.GetWidth(), padBBox.GetHeight() ); + int actual; + + if( padShape.DoCollide( refSeg, minClearance + widths, &actual ) ) + { + *aActualDist = std::max( 0, actual - widths ); + return false; + } + } + else // Convert the rest to polygons + { + SHAPE_POLY_SET polyset; + + BOARD* board = pad->GetBoard(); + int maxError = board ? board->GetDesignSettings().m_MaxError : ARC_HIGH_DEF; + + pad->TransformShapeWithClearanceToPolygon( polyset, 0, maxError ); + + const SHAPE_LINE_CHAIN& refpoly = polyset.COutline( 0 ); + int widths = refSegWidth / 2; + int actual; + + if( !poly2segmentDRC( (wxPoint*) &refpoly.CPoint( 0 ), refpoly.PointCount(), + (wxPoint) refSeg.A, (wxPoint) refSeg.B, + minClearance + widths, &actual ) ) + { + *aActualDist = std::max( 0, actual - widths ); + return false; + } + } + + return true; +} + + +bool test::DRC_TEST_PROVIDER_CLEARANCE::checkClearancePadToPad( D_PAD* aRefPad, D_PAD* aPad, int aMinClearance, int* aActual ) +{ + // relativePadPos is the aPad shape position relative to the aRefPad shape position + wxPoint relativePadPos = aPad->ShapePos() - aRefPad->ShapePos(); + + int center2center = KiROUND( EuclideanNorm( relativePadPos ) ); + + // Quick test: Clearance is OK if the bounding circles are further away than aMinClearance + if( center2center - aRefPad->GetBoundingRadius() - aPad->GetBoundingRadius() >= aMinClearance ) + return true; + + /* Here, pads are near and DRC depends on the pad shapes. We must compare distance using + * a fine shape analysis. + * Because a circle or oval shape is the easier shape to test, swap pads to have aRefPad be + * a PAD_SHAPE_CIRCLE or PAD_SHAPE_OVAL. If aRefPad = TRAPEZOID and aPad = RECT, also swap. + */ + bool swap_pads; + swap_pads = false; + + // swap pads to make comparisons easier + // Note also a ROUNDRECT pad with a corner radius = r can be considered as + // a smaller RECT (size - 2*r) with a clearance increased by r + // priority is aRefPad = ROUND then OVAL then RECT/ROUNDRECT then other + if( aRefPad->GetShape() != aPad->GetShape() && aRefPad->GetShape() != PAD_SHAPE_CIRCLE ) + { + // pad ref shape is here oval, rect, roundrect, chamfered rect, trapezoid or custom + switch( aPad->GetShape() ) + { + case PAD_SHAPE_CIRCLE: + swap_pads = true; + break; + + case PAD_SHAPE_OVAL: + swap_pads = true; + break; + + case PAD_SHAPE_RECT: + case PAD_SHAPE_ROUNDRECT: + if( aRefPad->GetShape() != PAD_SHAPE_OVAL ) + swap_pads = true; + break; + + case PAD_SHAPE_TRAPEZOID: + case PAD_SHAPE_CHAMFERED_RECT: + case PAD_SHAPE_CUSTOM: + break; + } + } + + if( swap_pads ) + { + std::swap( aRefPad, aPad ); + relativePadPos = -relativePadPos; + } + + bool diag = true; + + if( ( aRefPad->GetShape() == PAD_SHAPE_CIRCLE || aRefPad->GetShape() == PAD_SHAPE_OVAL ) ) + { + /* Treat an oval pad as a line segment along the hole's major axis, + * shortened by half its minor axis. + * A circular pad is just a degenerate case of an oval hole. + */ + wxPoint refPadStart, refPadEnd; + int refPadWidth; + + aRefPad->GetOblongGeometry( aRefPad->GetSize(), &refPadStart, &refPadEnd, &refPadWidth ); + refPadStart += aRefPad->ShapePos(); + refPadEnd += aRefPad->ShapePos(); + + SEG refPadSeg( refPadStart, refPadEnd ); + diag = checkClearanceSegmToPad( refPadSeg, refPadWidth, aPad, aMinClearance, aActual ); + } + else + { + int dist_extra = 0; + + // corners of aRefPad (used only for rect/roundrect/trap pad) + wxPoint polyref[4]; + // corners of aRefPad (used only for custom pad) + SHAPE_POLY_SET polysetref; + + if( aRefPad->GetShape() == PAD_SHAPE_ROUNDRECT ) + { + int padRadius = aRefPad->GetRoundRectCornerRadius(); + dist_extra = padRadius; + GetRoundRectCornerCenters( polyref, padRadius, wxPoint( 0, 0 ), aRefPad->GetSize(), + aRefPad->GetOrientation() ); + } + else if( aRefPad->GetShape() == PAD_SHAPE_CHAMFERED_RECT ) + { + BOARD* board = aRefPad->GetBoard(); + int maxError = board ? board->GetDesignSettings().m_MaxError : ARC_HIGH_DEF; + + // The reference pad can be rotated. Calculate the rotated coordinates. + // (note, the ref pad position is the origin of coordinates for this drc test) + int padRadius = aRefPad->GetRoundRectCornerRadius(); + + TransformRoundChamferedRectToPolygon( polysetref, wxPoint( 0, 0 ), aRefPad->GetSize(), + aRefPad->GetOrientation(), + padRadius, aRefPad->GetChamferRectRatio(), + aRefPad->GetChamferPositions(), maxError ); + } + else if( aRefPad->GetShape() == PAD_SHAPE_CUSTOM ) + { + polysetref.Append( aRefPad->GetCustomShapeAsPolygon() ); + + // The reference pad can be rotated. Calculate the rotated coordinates. + // (note, the ref pad position is the origin of coordinates for this drc test) + aRefPad->CustomShapeAsPolygonToBoardPosition( &polysetref, wxPoint( 0, 0 ), + aRefPad->GetOrientation() ); + } + else + { + // BuildPadPolygon has meaning for rect a trapeziod shapes and returns the 4 corners. + aRefPad->BuildPadPolygon( polyref, wxSize( 0, 0 ), aRefPad->GetOrientation() ); + } + + // corners of aPad (used only for rect/roundrect/trap pad) + wxPoint polycompare[4]; + // corners of aPad (used only custom pad) + SHAPE_POLY_SET polysetcompare; + + switch( aPad->GetShape() ) + { + case PAD_SHAPE_ROUNDRECT: + case PAD_SHAPE_RECT: + case PAD_SHAPE_CHAMFERED_RECT: + case PAD_SHAPE_TRAPEZOID: + case PAD_SHAPE_CUSTOM: + if( aPad->GetShape() == PAD_SHAPE_ROUNDRECT ) + { + int padRadius = aPad->GetRoundRectCornerRadius(); + dist_extra = padRadius; + GetRoundRectCornerCenters( polycompare, padRadius, relativePadPos, aPad->GetSize(), + aPad->GetOrientation() ); + } + else if( aPad->GetShape() == PAD_SHAPE_CHAMFERED_RECT ) + { + BOARD* board = aRefPad->GetBoard(); + int maxError = board ? board->GetDesignSettings().m_MaxError : ARC_HIGH_DEF; + + // The pad to compare can be rotated. Calculate the rotated coordinates. + // ( note, the pad to compare position is the relativePadPos for this drc test) + int padRadius = aPad->GetRoundRectCornerRadius(); + + TransformRoundChamferedRectToPolygon( polysetcompare, relativePadPos, + aPad->GetSize(), aPad->GetOrientation(), + padRadius, aPad->GetChamferRectRatio(), + aPad->GetChamferPositions(), maxError ); + } + else if( aPad->GetShape() == PAD_SHAPE_CUSTOM ) + { + polysetcompare.Append( aPad->GetCustomShapeAsPolygon() ); + + // The pad to compare can be rotated. Calculate the rotated coordinates. + // ( note, the pad to compare position is the relativePadPos for this drc test) + aPad->CustomShapeAsPolygonToBoardPosition( &polysetcompare, relativePadPos, + aPad->GetOrientation() ); + } + else + { + aPad->BuildPadPolygon( polycompare, wxSize( 0, 0 ), aPad->GetOrientation() ); + + // Move aPad shape to relativePadPos + for( int ii = 0; ii < 4; ii++ ) + polycompare[ii] += relativePadPos; + } + + // And now test polygons: We have 3 cases: + // one poly is complex and the other is basic (has only 4 corners) + // both polys are complex + // both polys are basic (have only 4 corners) the most usual case + if( polysetref.OutlineCount() && polysetcompare.OutlineCount() == 0) + { + const SHAPE_LINE_CHAIN& refpoly = polysetref.COutline( 0 ); + // And now test polygons: + if( !poly2polyDRC( (wxPoint*) &refpoly.CPoint( 0 ), refpoly.PointCount(), + polycompare, 4, aMinClearance + dist_extra, aActual ) ) + { + *aActual = std::max( 0, *aActual - dist_extra ); + diag = false; + } + } + else if( polysetref.OutlineCount() == 0 && polysetcompare.OutlineCount()) + { + const SHAPE_LINE_CHAIN& cmppoly = polysetcompare.COutline( 0 ); + // And now test polygons: + if( !poly2polyDRC((wxPoint*) &cmppoly.CPoint( 0 ), cmppoly.PointCount(), + polyref, 4, aMinClearance + dist_extra, aActual ) ) + { + *aActual = std::max( 0, *aActual - dist_extra ); + diag = false; + } + } + else if( polysetref.OutlineCount() && polysetcompare.OutlineCount() ) + { + const SHAPE_LINE_CHAIN& refpoly = polysetref.COutline( 0 ); + const SHAPE_LINE_CHAIN& cmppoly = polysetcompare.COutline( 0 ); + + // And now test polygons: + if( !poly2polyDRC((wxPoint*) &refpoly.CPoint( 0 ), refpoly.PointCount(), + (wxPoint*) &cmppoly.CPoint( 0 ), cmppoly.PointCount(), + aMinClearance + dist_extra, aActual ) ) + { + *aActual = std::max( 0, *aActual - dist_extra ); + diag = false; + } + } + else + { + if( !poly2polyDRC( polyref, 4, polycompare, 4, aMinClearance + dist_extra, aActual ) ) + { + *aActual = std::max( 0, *aActual - dist_extra ); + diag = false; + } + } + break; + + default: + wxLogDebug( wxT( "DRC::checkClearancePadToPad: unexpected pad shape %d" ), aPad->GetShape() ); + break; + } + } + + return diag; +} + + +bool test::DRC_TEST_PROVIDER_CLEARANCE::poly2segmentDRC( wxPoint* aTref, int aTrefCount, wxPoint aSegStart, wxPoint aSegEnd, + int aDist, int* aActual ) +{ + /* Test if the segment is contained in the polygon. + * This case is not covered by the following check if the segment is + * completely contained in the polygon (because edges don't intersect)! + */ + if( TestPointInsidePolygon( aTref, aTrefCount, aSegStart ) ) + { + *aActual = 0; + return false; + } + + for( int ii = 0, jj = aTrefCount-1; ii < aTrefCount; jj = ii, ii++ ) + { // for all edges in polygon + double d; + + if( TestForIntersectionOfStraightLineSegments( aTref[ii].x, aTref[ii].y, aTref[jj].x, + aTref[jj].y, aSegStart.x, aSegStart.y, + aSegEnd.x, aSegEnd.y, NULL, NULL, &d ) ) + { + *aActual = 0; + return false; + } + + if( d < aDist ) + { + *aActual = KiROUND( d ); + return false; + } + } + + return true; +} + + +/** + * compare 2 convex polygons and return true if distance > aDist (if no error DRC) + * i.e if for each edge of the first polygon distance from each edge of the other polygon + * is >= aDist + */ +bool test::DRC_TEST_PROVIDER_CLEARANCE::poly2polyDRC( wxPoint* aTref, int aTrefCount, wxPoint* aTtest, int aTtestCount, + int aAllowedDist, int* actualDist ) +{ + /* Test if one polygon is contained in the other and thus the polygon overlap. + * This case is not covered by the following check if one polygone is + * completely contained in the other (because edges don't intersect)! + */ + if( TestPointInsidePolygon( aTref, aTrefCount, aTtest[0] ) ) + { + *actualDist = 0; + return false; + } + + if( TestPointInsidePolygon( aTtest, aTtestCount, aTref[0] ) ) + { + *actualDist = 0; + return false; + } + + for( int ii = 0, jj = aTrefCount - 1; ii < aTrefCount; jj = ii, ii++ ) + { + // for all edges in aTref + for( int kk = 0, ll = aTtestCount - 1; kk < aTtestCount; ll = kk, kk++ ) + { + // for all edges in aTtest + double d; + int intersect = TestForIntersectionOfStraightLineSegments( + aTref[ii].x, aTref[ii].y, aTref[jj].x, aTref[jj].y, + aTtest[kk].x, aTtest[kk].y, aTtest[ll].x, aTtest[ll].y, + nullptr, nullptr, &d ); + + if( intersect ) + { + *actualDist = 0; + return false; + } + + if( d < aAllowedDist ) + { + *actualDist = KiROUND( d ); + return false; + } + } + } + + return true; +} + + + +std::set test::DRC_TEST_PROVIDER_CLEARANCE::GetMatchingRuleIds() const +{ + return { DRC_RULE_ID_T::DRC_RULE_ID_CLEARANCE }; +} + +test::DRC_TEST_PROVIDER *drcCreateClearanceTestProvider( test::DRC_ENGINE *engine ) +{ + return new test::DRC_TEST_PROVIDER_CLEARANCE( engine ); +} \ No newline at end of file diff --git a/qa/drc_proto/drc_clearance_test_functions.cpp b/qa/drc_proto/drc_clearance_test_functions.cpp new file mode 100644 index 0000000000..736d9a3e39 --- /dev/null +++ b/qa/drc_proto/drc_clearance_test_functions.cpp @@ -0,0 +1,995 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2004-2019 Jean-Pierre Charras, jp.charras at wanadoo.fr + * Copyright (C) 2007 Dick Hollenbeck, dick@softplc.com + * Copyright (C) 2019 KiCad Developers, see AUTHORS.txt for contributors. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include // for KiROUND +#include +#include + + +/** + * compare 2 convex polygons and return true if distance > aDist (if no error DRC) + * i.e if for each edge of the first polygon distance from each edge of the other polygon + * is >= aDist + */ +bool poly2polyDRC( wxPoint* aTref, int aTrefCount, wxPoint* aTtest, int aTtestCount, + int aAllowedDist, int* actualDist ) +{ + /* Test if one polygon is contained in the other and thus the polygon overlap. + * This case is not covered by the following check if one polygone is + * completely contained in the other (because edges don't intersect)! + */ + if( TestPointInsidePolygon( aTref, aTrefCount, aTtest[0] ) ) + { + *actualDist = 0; + return false; + } + + if( TestPointInsidePolygon( aTtest, aTtestCount, aTref[0] ) ) + { + *actualDist = 0; + return false; + } + + for( int ii = 0, jj = aTrefCount - 1; ii < aTrefCount; jj = ii, ii++ ) + { + // for all edges in aTref + for( int kk = 0, ll = aTtestCount - 1; kk < aTtestCount; ll = kk, kk++ ) + { + // for all edges in aTtest + double d; + int intersect = TestForIntersectionOfStraightLineSegments( + aTref[ii].x, aTref[ii].y, aTref[jj].x, aTref[jj].y, + aTtest[kk].x, aTtest[kk].y, aTtest[ll].x, aTtest[ll].y, + nullptr, nullptr, &d ); + + if( intersect ) + { + *actualDist = 0; + return false; + } + + if( d < aAllowedDist ) + { + *actualDist = KiROUND( d ); + return false; + } + } + } + + return true; +} + + +/* + * compare a trapezoid (can be rectangle) and a segment and return true if distance > aDist + */ +bool poly2segmentDRC( wxPoint* aTref, int aTrefCount, wxPoint aSegStart, wxPoint aSegEnd, + int aDist, int* aActual ) +{ + /* Test if the segment is contained in the polygon. + * This case is not covered by the following check if the segment is + * completely contained in the polygon (because edges don't intersect)! + */ + if( TestPointInsidePolygon( aTref, aTrefCount, aSegStart ) ) + { + *aActual = 0; + return false; + } + + for( int ii = 0, jj = aTrefCount-1; ii < aTrefCount; jj = ii, ii++ ) + { // for all edges in polygon + double d; + + if( TestForIntersectionOfStraightLineSegments( aTref[ii].x, aTref[ii].y, aTref[jj].x, + aTref[jj].y, aSegStart.x, aSegStart.y, + aSegEnd.x, aSegEnd.y, NULL, NULL, &d ) ) + { + *aActual = 0; + return false; + } + + if( d < aDist ) + { + *aActual = KiROUND( d ); + return false; + } + } + + return true; +} + + +void DRC::doTrackDrc( BOARD_COMMIT& aCommit, TRACK* aRefSeg, TRACKS::iterator aStartIt, + TRACKS::iterator aEndIt, bool aTestZones ) +{ + BOARD_DESIGN_SETTINGS& bds = m_pcb->GetDesignSettings(); + + SEG refSeg( aRefSeg->GetStart(), aRefSeg->GetEnd() ); + PCB_LAYER_ID refLayer = aRefSeg->GetLayer(); + LSET refLayerSet = aRefSeg->GetLayerSet(); + + EDA_RECT refSegBB = aRefSeg->GetBoundingBox(); + int refSegWidth = aRefSeg->GetWidth(); + + + /******************************************/ + /* Phase 0 : via DRC tests : */ + /******************************************/ + + if( aRefSeg->Type() == PCB_VIA_T ) + { + VIA *refvia = static_cast( aRefSeg ); + int viaAnnulus = ( refvia->GetWidth() - refvia->GetDrill() ) / 2; + int minAnnulus = refvia->GetMinAnnulus( &m_clearanceSource ); + + // test if the via size is smaller than minimum + if( refvia->GetViaType() == VIATYPE::MICROVIA ) + { + if( viaAnnulus < minAnnulus ) + { + DRC_ITEM* drcItem = new DRC_ITEM( DRCE_TOO_SMALL_VIA_ANNULUS ); + + m_msg.Printf( drcItem->GetErrorText() + _( " (%s %s; actual %s)" ), + m_clearanceSource, + MessageTextFromValue( userUnits(), minAnnulus, true ), + MessageTextFromValue( userUnits(), viaAnnulus, true ) ); + + drcItem->SetErrorMessage( m_msg ); + drcItem->SetItems( refvia ); + + MARKER_PCB* marker = new MARKER_PCB( drcItem, refvia->GetPosition() ); + addMarkerToPcb( aCommit, marker ); + } + + if( refvia->GetWidth() < bds.m_MicroViasMinSize ) + { + DRC_ITEM* drcItem = new DRC_ITEM( DRCE_TOO_SMALL_MICROVIA ); + + m_msg.Printf( drcItem->GetErrorText() + _( " (board minimum %s; actual %s)" ), + MessageTextFromValue( userUnits(), bds.m_MicroViasMinSize, true ), + MessageTextFromValue( userUnits(), refvia->GetWidth(), true ) ); + + drcItem->SetErrorMessage( m_msg ); + drcItem->SetItems( refvia ); + + MARKER_PCB* marker = new MARKER_PCB( drcItem, refvia->GetPosition() ); + addMarkerToPcb( aCommit, marker ); + } + } + else + { + if( bds.m_ViasMinAnnulus > minAnnulus ) + { + minAnnulus = bds.m_ViasMinAnnulus; + m_clearanceSource = _( "board minimum" ); + } + + if( viaAnnulus < minAnnulus ) + { + DRC_ITEM* drcItem = new DRC_ITEM( DRCE_TOO_SMALL_VIA_ANNULUS ); + + m_msg.Printf( drcItem->GetErrorText() + _( " (%s %s; actual %s)" ), + m_clearanceSource, + MessageTextFromValue( userUnits(), minAnnulus, true ), + MessageTextFromValue( userUnits(), viaAnnulus, true ) ); + + drcItem->SetErrorMessage( m_msg ); + drcItem->SetItems( refvia ); + + MARKER_PCB* marker = new MARKER_PCB( drcItem, refvia->GetPosition() ); + addMarkerToPcb( aCommit, marker ); + } + + if( refvia->GetWidth() < bds.m_ViasMinSize ) + { + DRC_ITEM* drcItem = new DRC_ITEM( DRCE_TOO_SMALL_VIA ); + + m_msg.Printf( drcItem->GetErrorText() + _( " (board minimum %s; actual %s)" ), + MessageTextFromValue( userUnits(), bds.m_ViasMinSize, true ), + MessageTextFromValue( userUnits(), refvia->GetWidth(), true ) ); + + drcItem->SetErrorMessage( m_msg ); + drcItem->SetItems( refvia ); + + MARKER_PCB* marker = new MARKER_PCB( drcItem, refvia->GetPosition() ); + addMarkerToPcb( aCommit, marker ); + } + } + + // test if via's hole is bigger than its diameter + // This test is necessary since the via hole size and width can be modified + // and a default via hole can be bigger than some vias sizes + if( refvia->GetDrillValue() > refvia->GetWidth() ) + { + DRC_ITEM* drcItem = new DRC_ITEM( DRCE_VIA_HOLE_BIGGER ); + + m_msg.Printf( drcItem->GetErrorText() + _( " (diameter %s; drill %s)" ), + MessageTextFromValue( userUnits(), refvia->GetWidth(), true ), + MessageTextFromValue( userUnits(), refvia->GetDrillValue(), true ) ); + + drcItem->SetErrorMessage( m_msg ); + drcItem->SetItems( refvia ); + + MARKER_PCB* marker = new MARKER_PCB( drcItem, refvia->GetPosition() ); + addMarkerToPcb( aCommit, marker ); + } + + // test if the type of via is allowed due to design rules + if( refvia->GetViaType() == VIATYPE::MICROVIA && !bds.m_MicroViasAllowed ) + { + DRC_ITEM* drcItem = new DRC_ITEM( DRCE_MICROVIA_NOT_ALLOWED ); + + m_msg.Printf( drcItem->GetErrorText() + _( " (board design rule constraints)" ) ); + drcItem->SetErrorMessage( m_msg ); + drcItem->SetItems( refvia ); + + MARKER_PCB* marker = new MARKER_PCB( drcItem, refvia->GetPosition() ); + addMarkerToPcb( aCommit, marker ); + } + + // test if the type of via is allowed due to design rules + if( refvia->GetViaType() == VIATYPE::BLIND_BURIED && !bds.m_BlindBuriedViaAllowed ) + { + DRC_ITEM* drcItem = new DRC_ITEM( DRCE_BURIED_VIA_NOT_ALLOWED ); + + m_msg.Printf( drcItem->GetErrorText() + _( " (board design rule constraints)" ) ); + drcItem->SetErrorMessage( m_msg ); + drcItem->SetItems( refvia ); + + MARKER_PCB* marker = new MARKER_PCB( drcItem, refvia->GetPosition() ); + addMarkerToPcb( aCommit, marker ); + } + + // For microvias: test if they are blind vias and only between 2 layers + // because they are used for very small drill size and are drill by laser + // and **only one layer** can be drilled + if( refvia->GetViaType() == VIATYPE::MICROVIA ) + { + PCB_LAYER_ID layer1, layer2; + bool err = true; + + refvia->LayerPair( &layer1, &layer2 ); + + if( layer1 > layer2 ) + std::swap( layer1, layer2 ); + + if( layer2 == B_Cu && layer1 == bds.GetCopperLayerCount() - 2 ) + err = false; + else if( layer1 == F_Cu && layer2 == In1_Cu ) + err = false; + + if( err ) + { + DRC_ITEM* drcItem = new DRC_ITEM( DRCE_MICROVIA_TOO_MANY_LAYERS ); + + m_msg.Printf( drcItem->GetErrorText() + _( " (%s and %s not adjacent)" ), + m_pcb->GetLayerName( layer1 ), + m_pcb->GetLayerName( layer2 ) ); + + drcItem->SetErrorMessage( m_msg ); + drcItem->SetItems( refvia ); + + MARKER_PCB* marker = new MARKER_PCB( drcItem, refvia->GetPosition() ); + addMarkerToPcb( aCommit, marker ); + } + } + + } + else // This is a track segment + { + int minWidth, maxWidth; + aRefSeg->GetWidthConstraints( &minWidth, &maxWidth, &m_clearanceSource ); + + int errorCode = 0; + int constraintWidth; + + if( refSegWidth < minWidth ) + { + errorCode = DRCE_TOO_SMALL_TRACK_WIDTH; + constraintWidth = minWidth; + } + else if( refSegWidth > maxWidth ) + { + errorCode = DRCE_TOO_LARGE_TRACK_WIDTH; + constraintWidth = maxWidth; + } + + if( errorCode ) + { + wxPoint refsegMiddle = ( aRefSeg->GetStart() + aRefSeg->GetEnd() ) / 2; + + DRC_ITEM* drcItem = new DRC_ITEM( errorCode ); + + m_msg.Printf( drcItem->GetErrorText() + _( " (%s %s; actual %s)" ), + m_clearanceSource, + MessageTextFromValue( userUnits(), constraintWidth, true ), + MessageTextFromValue( userUnits(), refSegWidth, true ) ); + + drcItem->SetErrorMessage( m_msg ); + drcItem->SetItems( aRefSeg ); + + MARKER_PCB* marker = new MARKER_PCB( drcItem, refsegMiddle ); + addMarkerToPcb( aCommit, marker ); + } + } + + + /******************************************/ + /* Phase 1 : test DRC track to pads : */ + /******************************************/ + + // Compute the min distance to pads + for( MODULE* mod : m_pcb->Modules() ) + { + // Don't preflight at the module level. Getting a module's bounding box goes + // through all its pads anyway (so it's no faster), and also all its drawings + // (so it's in fact slower). + + for( D_PAD* pad : mod->Pads() ) + { + // Preflight based on bounding boxes. + EDA_RECT inflatedBB = refSegBB; + inflatedBB.Inflate( pad->GetBoundingRadius() + m_largestClearance ); + + if( !inflatedBB.Contains( pad->GetPosition() ) ) + continue; + + if( !( pad->GetLayerSet() & refLayerSet ).any() ) + continue; + + // No need to check pads with the same net as the refSeg. + if( pad->GetNetCode() && aRefSeg->GetNetCode() == pad->GetNetCode() ) + continue; + + if( pad->GetDrillSize().x > 0 ) + { + // For hole testing we use a dummy pad which is a copy of the current pad + // shrunk down to nothing but its hole. + D_PAD dummypad( *pad ); + dummypad.SetSize( pad->GetDrillSize() ); + dummypad.SetShape( pad->GetDrillShape() == PAD_DRILL_SHAPE_OBLONG ? + PAD_SHAPE_OVAL : PAD_SHAPE_CIRCLE ); + // Ensure the hole is on all copper layers + const static LSET all_cu = LSET::AllCuMask(); + dummypad.SetLayerSet( all_cu | dummypad.GetLayerSet() ); + + int minClearance; + DRC_RULE* rule = GetRule( aRefSeg, &dummypad, CLEARANCE_CONSTRAINT ); + + if( rule ) + { + m_clearanceSource = wxString::Format( _( "'%s' rule" ), rule->m_Name ); + minClearance = rule->m_Clearance.Min; + } + else + { + minClearance = aRefSeg->GetClearance( nullptr, &m_clearanceSource ); + } + + /* Treat an oval hole as a line segment along the hole's major axis, + * shortened by half its minor axis. + * A circular hole is just a degenerate case of an oval hole. + */ + wxPoint slotStart, slotEnd; + int slotWidth; + + pad->GetOblongGeometry( pad->GetDrillSize(), &slotStart, &slotEnd, &slotWidth ); + slotStart += pad->GetPosition(); + slotEnd += pad->GetPosition(); + + SEG slotSeg( slotStart, slotEnd ); + int widths = ( slotWidth + refSegWidth ) / 2; + int center2centerAllowed = minClearance + widths + bds.GetDRCEpsilon(); + + // Avoid square-roots if possible (for performance) + SEG::ecoord center2center_squared = refSeg.SquaredDistance( slotSeg ); + + if( center2center_squared < SEG::Square( center2centerAllowed ) ) + { + int actual = std::max( 0.0, sqrt( center2center_squared ) - widths ); + DRC_ITEM* drcItem = new DRC_ITEM( DRCE_TRACK_NEAR_HOLE ); + + 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( aRefSeg, pad ); + + MARKER_PCB* marker = new MARKER_PCB( drcItem, GetLocation( aRefSeg, slotSeg ) ); + addMarkerToPcb( aCommit, marker ); + + if( !m_reportAllTrackErrors ) + return; + } + } + + int minClearance = aRefSeg->GetClearance( pad, &m_clearanceSource ); + int clearanceAllowed = minClearance - bds.GetDRCEpsilon(); + int actual; + + if( !checkClearanceSegmToPad( refSeg, refSegWidth, pad, clearanceAllowed, &actual ) ) + { + actual = std::max( 0, actual ); + SEG padSeg( pad->GetPosition(), pad->GetPosition() ); + DRC_ITEM* drcItem = new DRC_ITEM( DRCE_TRACK_NEAR_PAD ); + + 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( aRefSeg, pad ); + + MARKER_PCB* marker = new MARKER_PCB( drcItem, GetLocation( aRefSeg, padSeg ) ); + addMarkerToPcb( aCommit, marker ); + + if( !m_reportAllTrackErrors ) + return; + } + } + } + + /***********************************************/ + /* Phase 2: test DRC with other track segments */ + /***********************************************/ + + // Test the reference segment with other track segments + for( auto it = aStartIt; it != aEndIt; it++ ) + { + TRACK* track = *it; + + // No problem if segments have the same net code: + if( aRefSeg->GetNetCode() == track->GetNetCode() ) + continue; + + // No problem if tracks are on different layers: + // Note that while the general case of GetLayerSet intersection always works, + // the others are much faster. + bool sameLayers; + + if( aRefSeg->Type() == PCB_VIA_T ) + { + if( track->Type() == PCB_VIA_T ) + sameLayers = ( refLayerSet & track->GetLayerSet() ).any(); + else + sameLayers = refLayerSet.test( track->GetLayer() ); + } + else + { + if( track->Type() == PCB_VIA_T ) + sameLayers = track->GetLayerSet().test( refLayer ); + else + sameLayers = track->GetLayer() == refLayer; + } + + if( !sameLayers ) + continue; + + // Preflight based on worst-case inflated bounding boxes: + EDA_RECT trackBB = track->GetBoundingBox(); + trackBB.Inflate( m_largestClearance ); + + if( !trackBB.Intersects( refSegBB ) ) + continue; + + int minClearance = aRefSeg->GetClearance( track, &m_clearanceSource ); + SEG trackSeg( track->GetStart(), track->GetEnd() ); + int widths = ( refSegWidth + track->GetWidth() ) / 2; + int center2centerAllowed = minClearance + widths; + + // Avoid square-roots if possible (for performance) + SEG::ecoord center2center_squared = refSeg.SquaredDistance( trackSeg ); + OPT_VECTOR2I intersection = refSeg.Intersect( trackSeg ); + + // Check two tracks crossing first as it reports a DRCE without distances + if( intersection ) + { + DRC_ITEM* drcItem = new DRC_ITEM( DRCE_TRACKS_CROSSING ); + drcItem->SetErrorMessage( m_msg ); + drcItem->SetItems( aRefSeg, track ); + + MARKER_PCB* marker = new MARKER_PCB( drcItem, (wxPoint) intersection.get() ); + addMarkerToPcb( aCommit, marker ); + + if( !m_reportAllTrackErrors ) + return; + } + else if( center2center_squared < SEG::Square( center2centerAllowed ) ) + { + int errorCode = DRCE_TRACK_ENDS; + + if( aRefSeg->Type() == PCB_VIA_T && track->Type() == PCB_VIA_T ) + errorCode = DRCE_VIA_NEAR_VIA; + else if( aRefSeg->Type() == PCB_VIA_T || track->Type() == PCB_VIA_T ) + errorCode = DRCE_VIA_NEAR_TRACK; + else if( refSeg.ApproxParallel( trackSeg ) ) + errorCode = DRCE_TRACK_SEGMENTS_TOO_CLOSE; + + int actual = std::max( 0.0, sqrt( center2center_squared ) - widths ); + DRC_ITEM* drcItem = new DRC_ITEM( errorCode ); + + 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( aRefSeg, track ); + + MARKER_PCB* marker = new MARKER_PCB( drcItem, GetLocation( aRefSeg, trackSeg ) ); + addMarkerToPcb( aCommit, marker ); + + if( !m_reportAllTrackErrors ) + return; + } + } + + /***************************************/ + /* Phase 3: test DRC with copper zones */ + /***************************************/ + // Can be *very* time consumming. + if( aTestZones ) + { + SEG testSeg( aRefSeg->GetStart(), aRefSeg->GetEnd() ); + + for( ZONE_CONTAINER* zone : m_pcb->Zones() ) + { + if( zone->GetFilledPolysList().IsEmpty() || zone->GetIsKeepout() ) + continue; + + if( !( refLayerSet & zone->GetLayerSet() ).any() ) + continue; + + if( zone->GetNetCode() && zone->GetNetCode() == aRefSeg->GetNetCode() ) + continue; + + int minClearance = aRefSeg->GetClearance( zone, &m_clearanceSource ); + int widths = refSegWidth / 2; + int center2centerAllowed = minClearance + widths; + SHAPE_POLY_SET* outline = const_cast( &zone->GetFilledPolysList() ); + + SEG::ecoord center2center_squared = outline->SquaredDistance( testSeg ); + + // to avoid false positive, due to rounding issues and approxiamtions + // in distance and clearance calculations, use a small threshold for distance + // (1 micron) + #define THRESHOLD_DIST Millimeter2iu( 0.001 ) + + if( center2center_squared + THRESHOLD_DIST < SEG::Square( center2centerAllowed ) ) + { + int actual = std::max( 0.0, sqrt( center2center_squared ) - widths ); + DRC_ITEM* drcItem = new DRC_ITEM( DRCE_TRACK_NEAR_ZONE ); + + 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( aRefSeg, zone ); + + MARKER_PCB* marker = new MARKER_PCB( drcItem, GetLocation( aRefSeg, zone ) ); + addMarkerToPcb( aCommit, marker ); + } + } + } + + /***********************************************/ + /* Phase 4: test DRC with to board edge */ + /***********************************************/ + if( m_board_outline_valid ) + { + int minClearance = bds.m_CopperEdgeClearance; + m_clearanceSource = _( "board edge" ); + + static DRAWSEGMENT dummyEdge; + dummyEdge.SetLayer( Edge_Cuts ); + + if( aRefSeg->GetRuleClearance( &dummyEdge, &minClearance, &m_clearanceSource ) ) + /* minClearance and m_clearanceSource set in GetRuleClearance() */; + + SEG testSeg( aRefSeg->GetStart(), aRefSeg->GetEnd() ); + int halfWidth = refSegWidth / 2; + int center2centerAllowed = minClearance + halfWidth; + + for( auto it = m_board_outlines.IterateSegmentsWithHoles(); it; it++ ) + { + SEG::ecoord center2center_squared = testSeg.SquaredDistance( *it ); + + if( center2center_squared < SEG::Square( center2centerAllowed ) ) + { + VECTOR2I pt = testSeg.NearestPoint( *it ); + + KICAD_T types[] = { PCB_LINE_T, EOT }; + DRAWSEGMENT* edge = nullptr; + INSPECTOR_FUNC inspector = + [&] ( EDA_ITEM* item, void* testData ) + { + DRAWSEGMENT* test_edge = dynamic_cast( item ); + + if( !test_edge || test_edge->GetLayer() != Edge_Cuts ) + return SEARCH_RESULT::CONTINUE; + + if( test_edge->HitTest( (wxPoint) pt, minClearance + halfWidth ) ) + { + edge = test_edge; + return SEARCH_RESULT::QUIT; + } + + return SEARCH_RESULT::CONTINUE; + }; + + // Best-efforts search for edge segment + BOARD::IterateForward( m_pcb->Drawings(), inspector, nullptr, types ); + + int actual = std::max( 0.0, sqrt( center2center_squared ) - halfWidth ); + int errorCode = ( aRefSeg->Type() == PCB_VIA_T ) ? DRCE_VIA_NEAR_EDGE + : DRCE_TRACK_NEAR_EDGE; + DRC_ITEM* drcItem = new DRC_ITEM( errorCode ); + + 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( aRefSeg, edge ); + + MARKER_PCB* marker = new MARKER_PCB( drcItem, (wxPoint) pt ); + addMarkerToPcb( aCommit, marker ); + } + } + } +} + + +bool DRC::checkClearancePadToPad( D_PAD* aRefPad, D_PAD* aPad, int aMinClearance, int* aActual ) +{ + // relativePadPos is the aPad shape position relative to the aRefPad shape position + wxPoint relativePadPos = aPad->ShapePos() - aRefPad->ShapePos(); + + int center2center = KiROUND( EuclideanNorm( relativePadPos ) ); + + // Quick test: Clearance is OK if the bounding circles are further away than aMinClearance + if( center2center - aRefPad->GetBoundingRadius() - aPad->GetBoundingRadius() >= aMinClearance ) + return true; + + /* Here, pads are near and DRC depends on the pad shapes. We must compare distance using + * a fine shape analysis. + * Because a circle or oval shape is the easier shape to test, swap pads to have aRefPad be + * a PAD_SHAPE_CIRCLE or PAD_SHAPE_OVAL. If aRefPad = TRAPEZOID and aPad = RECT, also swap. + */ + bool swap_pads; + swap_pads = false; + + // swap pads to make comparisons easier + // Note also a ROUNDRECT pad with a corner radius = r can be considered as + // a smaller RECT (size - 2*r) with a clearance increased by r + // priority is aRefPad = ROUND then OVAL then RECT/ROUNDRECT then other + if( aRefPad->GetShape() != aPad->GetShape() && aRefPad->GetShape() != PAD_SHAPE_CIRCLE ) + { + // pad ref shape is here oval, rect, roundrect, chamfered rect, trapezoid or custom + switch( aPad->GetShape() ) + { + case PAD_SHAPE_CIRCLE: + swap_pads = true; + break; + + case PAD_SHAPE_OVAL: + swap_pads = true; + break; + + case PAD_SHAPE_RECT: + case PAD_SHAPE_ROUNDRECT: + if( aRefPad->GetShape() != PAD_SHAPE_OVAL ) + swap_pads = true; + break; + + case PAD_SHAPE_TRAPEZOID: + case PAD_SHAPE_CHAMFERED_RECT: + case PAD_SHAPE_CUSTOM: + break; + } + } + + if( swap_pads ) + { + std::swap( aRefPad, aPad ); + relativePadPos = -relativePadPos; + } + + bool diag = true; + + if( ( aRefPad->GetShape() == PAD_SHAPE_CIRCLE || aRefPad->GetShape() == PAD_SHAPE_OVAL ) ) + { + /* Treat an oval pad as a line segment along the hole's major axis, + * shortened by half its minor axis. + * A circular pad is just a degenerate case of an oval hole. + */ + wxPoint refPadStart, refPadEnd; + int refPadWidth; + + aRefPad->GetOblongGeometry( aRefPad->GetSize(), &refPadStart, &refPadEnd, &refPadWidth ); + refPadStart += aRefPad->ShapePos(); + refPadEnd += aRefPad->ShapePos(); + + SEG refPadSeg( refPadStart, refPadEnd ); + diag = checkClearanceSegmToPad( refPadSeg, refPadWidth, aPad, aMinClearance, aActual ); + } + else + { + int dist_extra = 0; + + // corners of aRefPad (used only for rect/roundrect/trap pad) + wxPoint polyref[4]; + // corners of aRefPad (used only for custom pad) + SHAPE_POLY_SET polysetref; + + if( aRefPad->GetShape() == PAD_SHAPE_ROUNDRECT ) + { + int padRadius = aRefPad->GetRoundRectCornerRadius(); + dist_extra = padRadius; + GetRoundRectCornerCenters( polyref, padRadius, wxPoint( 0, 0 ), aRefPad->GetSize(), + aRefPad->GetOrientation() ); + } + else if( aRefPad->GetShape() == PAD_SHAPE_CHAMFERED_RECT ) + { + BOARD* board = aRefPad->GetBoard(); + int maxError = board ? board->GetDesignSettings().m_MaxError : ARC_HIGH_DEF; + + // The reference pad can be rotated. Calculate the rotated coordinates. + // (note, the ref pad position is the origin of coordinates for this drc test) + int padRadius = aRefPad->GetRoundRectCornerRadius(); + + TransformRoundChamferedRectToPolygon( polysetref, wxPoint( 0, 0 ), aRefPad->GetSize(), + aRefPad->GetOrientation(), + padRadius, aRefPad->GetChamferRectRatio(), + aRefPad->GetChamferPositions(), maxError ); + } + else if( aRefPad->GetShape() == PAD_SHAPE_CUSTOM ) + { + polysetref.Append( aRefPad->GetCustomShapeAsPolygon() ); + + // The reference pad can be rotated. Calculate the rotated coordinates. + // (note, the ref pad position is the origin of coordinates for this drc test) + aRefPad->CustomShapeAsPolygonToBoardPosition( &polysetref, wxPoint( 0, 0 ), + aRefPad->GetOrientation() ); + } + else + { + // BuildPadPolygon has meaning for rect a trapeziod shapes and returns the 4 corners. + aRefPad->BuildPadPolygon( polyref, wxSize( 0, 0 ), aRefPad->GetOrientation() ); + } + + // corners of aPad (used only for rect/roundrect/trap pad) + wxPoint polycompare[4]; + // corners of aPad (used only custom pad) + SHAPE_POLY_SET polysetcompare; + + switch( aPad->GetShape() ) + { + case PAD_SHAPE_ROUNDRECT: + case PAD_SHAPE_RECT: + case PAD_SHAPE_CHAMFERED_RECT: + case PAD_SHAPE_TRAPEZOID: + case PAD_SHAPE_CUSTOM: + if( aPad->GetShape() == PAD_SHAPE_ROUNDRECT ) + { + int padRadius = aPad->GetRoundRectCornerRadius(); + dist_extra = padRadius; + GetRoundRectCornerCenters( polycompare, padRadius, relativePadPos, aPad->GetSize(), + aPad->GetOrientation() ); + } + else if( aPad->GetShape() == PAD_SHAPE_CHAMFERED_RECT ) + { + BOARD* board = aRefPad->GetBoard(); + int maxError = board ? board->GetDesignSettings().m_MaxError : ARC_HIGH_DEF; + + // The pad to compare can be rotated. Calculate the rotated coordinates. + // ( note, the pad to compare position is the relativePadPos for this drc test) + int padRadius = aPad->GetRoundRectCornerRadius(); + + TransformRoundChamferedRectToPolygon( polysetcompare, relativePadPos, + aPad->GetSize(), aPad->GetOrientation(), + padRadius, aPad->GetChamferRectRatio(), + aPad->GetChamferPositions(), maxError ); + } + else if( aPad->GetShape() == PAD_SHAPE_CUSTOM ) + { + polysetcompare.Append( aPad->GetCustomShapeAsPolygon() ); + + // The pad to compare can be rotated. Calculate the rotated coordinates. + // ( note, the pad to compare position is the relativePadPos for this drc test) + aPad->CustomShapeAsPolygonToBoardPosition( &polysetcompare, relativePadPos, + aPad->GetOrientation() ); + } + else + { + aPad->BuildPadPolygon( polycompare, wxSize( 0, 0 ), aPad->GetOrientation() ); + + // Move aPad shape to relativePadPos + for( int ii = 0; ii < 4; ii++ ) + polycompare[ii] += relativePadPos; + } + + // And now test polygons: We have 3 cases: + // one poly is complex and the other is basic (has only 4 corners) + // both polys are complex + // both polys are basic (have only 4 corners) the most usual case + if( polysetref.OutlineCount() && polysetcompare.OutlineCount() == 0) + { + const SHAPE_LINE_CHAIN& refpoly = polysetref.COutline( 0 ); + // And now test polygons: + if( !poly2polyDRC( (wxPoint*) &refpoly.CPoint( 0 ), refpoly.PointCount(), + polycompare, 4, aMinClearance + dist_extra, aActual ) ) + { + *aActual = std::max( 0, *aActual - dist_extra ); + diag = false; + } + } + else if( polysetref.OutlineCount() == 0 && polysetcompare.OutlineCount()) + { + const SHAPE_LINE_CHAIN& cmppoly = polysetcompare.COutline( 0 ); + // And now test polygons: + if( !poly2polyDRC((wxPoint*) &cmppoly.CPoint( 0 ), cmppoly.PointCount(), + polyref, 4, aMinClearance + dist_extra, aActual ) ) + { + *aActual = std::max( 0, *aActual - dist_extra ); + diag = false; + } + } + else if( polysetref.OutlineCount() && polysetcompare.OutlineCount() ) + { + const SHAPE_LINE_CHAIN& refpoly = polysetref.COutline( 0 ); + const SHAPE_LINE_CHAIN& cmppoly = polysetcompare.COutline( 0 ); + + // And now test polygons: + if( !poly2polyDRC((wxPoint*) &refpoly.CPoint( 0 ), refpoly.PointCount(), + (wxPoint*) &cmppoly.CPoint( 0 ), cmppoly.PointCount(), + aMinClearance + dist_extra, aActual ) ) + { + *aActual = std::max( 0, *aActual - dist_extra ); + diag = false; + } + } + else + { + if( !poly2polyDRC( polyref, 4, polycompare, 4, aMinClearance + dist_extra, aActual ) ) + { + *aActual = std::max( 0, *aActual - dist_extra ); + diag = false; + } + } + break; + + default: + wxLogDebug( wxT( "DRC::checkClearancePadToPad: unexpected pad shape %d" ), aPad->GetShape() ); + break; + } + } + + return diag; +} + + +/* + * Test if distance between a segment and a pad is > minClearance. Return the actual + * distance if it is less. + */ +bool DRC::checkClearanceSegmToPad( const SEG& refSeg, int refSegWidth, const D_PAD* pad, + int minClearance, int* aActualDist ) +{ + if( ( pad->GetShape() == PAD_SHAPE_CIRCLE || pad->GetShape() == PAD_SHAPE_OVAL ) ) + { + /* Treat an oval pad as a line segment along the hole's major axis, + * shortened by half its minor axis. + * A circular pad is just a degenerate case of an oval hole. + */ + wxPoint padStart, padEnd; + int padWidth; + + pad->GetOblongGeometry( pad->GetSize(), &padStart, &padEnd, &padWidth ); + padStart += pad->ShapePos(); + padEnd += pad->ShapePos(); + + SEG padSeg( padStart, padEnd ); + int widths = ( padWidth + refSegWidth ) / 2; + int center2centerAllowed = minClearance + widths; + + // Avoid square-roots if possible (for performance) + SEG::ecoord center2center_squared = refSeg.SquaredDistance( padSeg ); + + if( center2center_squared < SEG::Square( center2centerAllowed ) ) + { + *aActualDist = std::max( 0.0, sqrt( center2center_squared ) - widths ); + return false; + } + } + else if( ( pad->GetShape() == PAD_SHAPE_RECT || pad->GetShape() == PAD_SHAPE_ROUNDRECT ) + && ( (int) pad->GetOrientation() % 900 == 0 ) ) + { + EDA_RECT padBBox = pad->GetBoundingBox(); + int widths = refSegWidth / 2; + + // Note a ROUNDRECT pad with a corner radius = r can be treated as a smaller + // RECT (size - 2*r) with a clearance increased by r + if( pad->GetShape() == PAD_SHAPE_ROUNDRECT ) + { + padBBox.Inflate( - pad->GetRoundRectCornerRadius() ); + widths += pad->GetRoundRectCornerRadius(); + } + + SHAPE_RECT padShape( padBBox.GetPosition(), padBBox.GetWidth(), padBBox.GetHeight() ); + int actual; + + if( padShape.DoCollide( refSeg, minClearance + widths, &actual ) ) + { + *aActualDist = std::max( 0, actual - widths ); + return false; + } + } + else // Convert the rest to polygons + { + SHAPE_POLY_SET polyset; + + BOARD* board = pad->GetBoard(); + int maxError = board ? board->GetDesignSettings().m_MaxError : ARC_HIGH_DEF; + + pad->TransformShapeWithClearanceToPolygon( polyset, 0, maxError ); + + const SHAPE_LINE_CHAIN& refpoly = polyset.COutline( 0 ); + int widths = refSegWidth / 2; + int actual; + + if( !poly2segmentDRC( (wxPoint*) &refpoly.CPoint( 0 ), refpoly.PointCount(), + (wxPoint) refSeg.A, (wxPoint) refSeg.B, + minClearance + widths, &actual ) ) + { + *aActualDist = std::max( 0, actual - widths ); + return false; + } + } + + return true; +} + diff --git a/qa/drc_proto/drc_courtyard_tester.cpp b/qa/drc_proto/drc_courtyard_tester.cpp new file mode 100644 index 0000000000..36f148d492 --- /dev/null +++ b/qa/drc_proto/drc_courtyard_tester.cpp @@ -0,0 +1,189 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2010 Dick Hollenbeck, dick@softplc.com + * Copyright (C) 2004-2017 Jean-Pierre Charras, jp.charras at wanadoo.fr + * Copyright (C) 2018-2020 KiCad Developers, see AUTHORS.txt for contributors. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + + +#include + +#include +#include + +#include + +#include + + +DRC_COURTYARD_TESTER::DRC_COURTYARD_TESTER( MARKER_HANDLER aMarkerHandler ) : + DRC_TEST_PROVIDER( std::move( aMarkerHandler ) ) +{ +} + + +bool DRC_COURTYARD_TESTER::RunDRC( EDA_UNITS aUnits, BOARD& aBoard ) +{ + // Detects missing (or malformed) footprint courtyards and courtyard incursions (for those + // with a courtyard). + wxString msg; + bool success = true; + + // Update courtyard polygons, and test for missing courtyard definition: + for( MODULE* footprint : aBoard.Modules() ) + { + if( footprint->BuildPolyCourtyard() ) + { + if( !aBoard.GetDesignSettings().Ignore( DRCE_MISSING_COURTYARD ) + && footprint->GetPolyCourtyardFront().OutlineCount() == 0 + && footprint->GetPolyCourtyardBack().OutlineCount() == 0 ) + { + DRC_ITEM* drcItem = new DRC_ITEM( DRCE_MISSING_COURTYARD ); + drcItem->SetItems( footprint ); + HandleMarker( new MARKER_PCB( drcItem, footprint->GetPosition() ) ); + success = false; + } + else + { + footprint->GetPolyCourtyardFront().BuildBBoxCaches(); + footprint->GetPolyCourtyardBack().BuildBBoxCaches(); + } + } + else + { + if( !aBoard.GetDesignSettings().Ignore( DRCE_MALFORMED_COURTYARD ) ) + { + DRC_ITEM* drcItem = new DRC_ITEM( DRCE_MALFORMED_COURTYARD ); + + msg.Printf( drcItem->GetErrorText() + _( " (not a closed shape)" ) ); + + drcItem->SetErrorMessage( msg ); + drcItem->SetItems( footprint ); + HandleMarker( new MARKER_PCB( drcItem, footprint->GetPosition() ) ); + success = false; + } + } + } + + if( !aBoard.GetDesignSettings().Ignore( DRCE_OVERLAPPING_FOOTPRINTS ) ) + { + for( auto it1 = aBoard.Modules().begin(); it1 != aBoard.Modules().end(); it1++ ) + { + MODULE* footprint = *it1; + SHAPE_POLY_SET& footprintFront = footprint->GetPolyCourtyardFront(); + SHAPE_POLY_SET& footprintBack = footprint->GetPolyCourtyardBack(); + + if( footprintFront.OutlineCount() == 0 && footprintBack.OutlineCount() == 0 ) + continue; // No courtyards defined + + for( auto it2 = it1 + 1; it2 != aBoard.Modules().end(); it2++ ) + { + MODULE* test = *it2; + SHAPE_POLY_SET& testFront = test->GetPolyCourtyardFront(); + SHAPE_POLY_SET& testBack = test->GetPolyCourtyardBack(); + SHAPE_POLY_SET intersection; + bool overlap = false; + wxPoint pos; + + if( footprintFront.OutlineCount() > 0 && testFront.OutlineCount() > 0 + && footprintFront.BBoxFromCaches().Intersects( testFront.BBoxFromCaches() ) ) + { + intersection.RemoveAllContours(); + intersection.Append( footprintFront ); + + // Build the common area between footprint and the test: + intersection.BooleanIntersection( testFront, SHAPE_POLY_SET::PM_FAST ); + + // If the intersection exists then they overlap + if( intersection.OutlineCount() > 0 ) + { + overlap = true; + pos = (wxPoint) intersection.CVertex( 0, 0, -1 ); + } + } + + if( footprintBack.OutlineCount() > 0 && testBack.OutlineCount() > 0 + && footprintBack.BBoxFromCaches().Intersects( testBack.BBoxFromCaches() ) ) + { + intersection.RemoveAllContours(); + intersection.Append( footprintBack ); + + intersection.BooleanIntersection( testBack, SHAPE_POLY_SET::PM_FAST ); + + if( intersection.OutlineCount() > 0 ) + { + overlap = true; + pos = (wxPoint) intersection.CVertex( 0, 0, -1 ); + } + } + + if( overlap ) + { + DRC_ITEM* drcItem = new DRC_ITEM( DRCE_OVERLAPPING_FOOTPRINTS ); + drcItem->SetItems( footprint, test ); + HandleMarker( new MARKER_PCB( drcItem, pos ) ); + success = false; + } + } + } + } + + if( !aBoard.GetDesignSettings().Ignore( DRCE_PTH_IN_COURTYARD ) + || !aBoard.GetDesignSettings().Ignore( DRCE_NPTH_IN_COURTYARD ) ) + { + for( MODULE* footprint : aBoard.Modules() ) + { + SHAPE_POLY_SET& footprintFront = footprint->GetPolyCourtyardFront(); + SHAPE_POLY_SET& footprintBack = footprint->GetPolyCourtyardBack(); + + if( footprintFront.OutlineCount() == 0 && footprintBack.OutlineCount() == 0 ) + continue; // No courtyards defined + + for( MODULE* candidate : aBoard.Modules() ) + { + if( footprint == candidate ) + continue; + + for( D_PAD* pad : candidate->Pads() ) + { + if( pad->GetDrillSize().x == 0 || pad->GetDrillSize().y == 0 ) + continue; + + wxPoint pos = pad->GetPosition(); + + if( footprintFront.Contains( pos, -1, 0, true /* use bbox caches */ ) + || footprintBack.Contains( pos, -1, 0, true /* use bbox caches */ ) ) + { + int code = pad->GetAttribute() == PAD_ATTRIB_HOLE_NOT_PLATED ? + DRCE_NPTH_IN_COURTYARD : + DRCE_PTH_IN_COURTYARD; + DRC_ITEM* drcItem = new DRC_ITEM( code ); + drcItem->SetItems( footprint, pad ); + HandleMarker( new MARKER_PCB( drcItem, pos ) ); + success = false; + } + } + } + } + } + + return success; +} diff --git a/qa/drc_proto/drc_courtyard_tester.h b/qa/drc_proto/drc_courtyard_tester.h new file mode 100644 index 0000000000..4ba70b09d3 --- /dev/null +++ b/qa/drc_proto/drc_courtyard_tester.h @@ -0,0 +1,43 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2018 KiCad Developers, see change_log.txt for contributors. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + + +#ifndef DRC_COURTYARD_OVERLAP__H +#define DRC_COURTYARD_OVERLAP__H + +#include + + +class BOARD; + +class DRC_COURTYARD_TESTER : public DRC_TEST_PROVIDER +{ +public: + DRC_COURTYARD_TESTER( MARKER_HANDLER aMarkerHandler ); + + virtual ~DRC_COURTYARD_TESTER() {}; + + bool RunDRC( EDA_UNITS aUnits, BOARD& aBoard ) override; +}; + +#endif // DRC_COURTYARD_OVERLAP__H diff --git a/qa/drc_proto/drc_drilled_hole_tester.cpp b/qa/drc_proto/drc_drilled_hole_tester.cpp new file mode 100644 index 0000000000..eeca399e5e --- /dev/null +++ b/qa/drc_proto/drc_drilled_hole_tester.cpp @@ -0,0 +1,283 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2020 KiCad Developers, see AUTHORS.txt for contributors. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + + +#include + +#include +#include + +#include + + +DRC_DRILLED_HOLE_TESTER::DRC_DRILLED_HOLE_TESTER( MARKER_HANDLER aMarkerHandler ) : + DRC_TEST_PROVIDER( std::move( aMarkerHandler ) ), + m_units( EDA_UNITS::MILLIMETRES ), + m_board( nullptr ), + m_largestRadius( 0 ) +{ +} + + +bool DRC_DRILLED_HOLE_TESTER::RunDRC( EDA_UNITS aUnits, BOARD& aBoard ) +{ + bool success = true; + + // Test drilled holes to minimize drill bit breakage. + // + // Check pad & std. via circular holes for hole-to-hole-min (non-circular holes are milled) + // Check pad & std. via holes for via-min-drill (minimum hole classification) + // Check uvia holes for uvia-min-drill (laser drill classification) + + m_units = aUnits; + m_board = &aBoard; + m_holes.clear(); + m_largestRadius = 0; + + for( MODULE* mod : aBoard.Modules() ) + { + for( D_PAD* pad : mod->Pads( ) ) + success &= checkPad( pad ); + } + + for( TRACK* track : aBoard.Tracks() ) + { + VIA* via = dynamic_cast( track ); + + if( via ) + { + if( via->GetViaType() == VIATYPE::MICROVIA ) + success &= checkMicroVia( via ); + else + success &= checkVia( via ); + } + } + + success &= checkHoles(); + + return success; +} + + +bool DRC_DRILLED_HOLE_TESTER::checkPad( D_PAD* aPad ) +{ + bool success = true; + BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings(); + + int holeSize = std::min( aPad->GetDrillSize().x, aPad->GetDrillSize().y ); + + if( holeSize == 0 ) + return true; + + if( !bds.Ignore( DRCE_TOO_SMALL_PAD_DRILL ) ) + { + int minHole = bds.m_MinThroughDrill; + wxString minHoleSource = _( "board minimum" ); + DRC_RULE* rule = GetRule( aPad, nullptr, HOLE_CONSTRAINT ); + + if( rule ) + { + minHole = rule->m_MinHole; + minHoleSource = wxString::Format( _( "'%s' rule" ), rule->m_Name ); + } + + if( holeSize < minHole ) + { + DRC_ITEM* drcItem = new DRC_ITEM( DRCE_TOO_SMALL_PAD_DRILL ); + + m_msg.Printf( drcItem->GetErrorText() + _( " (%s %s; actual %s)" ), + minHoleSource, + MessageTextFromValue( m_units, minHole, true ), + MessageTextFromValue( m_units, holeSize, true ) ); + + drcItem->SetErrorMessage( m_msg ); + drcItem->SetItems( aPad ); + + HandleMarker( new MARKER_PCB( drcItem, aPad->GetPosition() ) ); + success = false; + } + } + + if( !bds.Ignore( DRCE_DRILLED_HOLES_TOO_CLOSE ) && bds.m_HoleToHoleMin != 0 ) + { + if( aPad->GetDrillShape() == PAD_DRILL_SHAPE_CIRCLE ) + addHole( aPad->GetPosition(), aPad->GetDrillSize().x / 2, aPad ); + } + + return success; +} + +bool DRC_DRILLED_HOLE_TESTER::checkVia( VIA* via ) +{ + bool success = true; + BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings(); + + if( !bds.Ignore( DRCE_TOO_SMALL_VIA_DRILL ) ) + { + int minHole = bds.m_MinThroughDrill; + wxString minHoleSource = _( "board minimum" ); + DRC_RULE* rule = GetRule( via, nullptr, HOLE_CONSTRAINT ); + + if( rule ) + { + minHole = rule->m_MinHole; + minHoleSource = wxString::Format( _( "'%s' rule" ), rule->m_Name ); + } + + if( via->GetDrillValue() < minHole ) + { + DRC_ITEM* drcItem = new DRC_ITEM( DRCE_TOO_SMALL_VIA_DRILL ); + + m_msg.Printf( drcItem->GetErrorText() + _( " (%s %s; actual %s)" ), + minHoleSource, + MessageTextFromValue( m_units, minHole, true ), + MessageTextFromValue( m_units, via->GetDrillValue(), true ) ); + + drcItem->SetErrorMessage( m_msg ); + drcItem->SetItems( via ); + + HandleMarker( new MARKER_PCB( drcItem, via->GetPosition() ) ); + success = false; + } + } + + if( !bds.Ignore( DRCE_DRILLED_HOLES_TOO_CLOSE ) && bds.m_HoleToHoleMin != 0 ) + { + addHole( via->GetPosition(), via->GetDrillValue() / 2, via ); + } + + return success; +} + + +bool DRC_DRILLED_HOLE_TESTER::checkMicroVia( VIA* via ) +{ + bool success = true; + BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings(); + + if( !bds.Ignore( DRCE_TOO_SMALL_MICROVIA_DRILL ) ) + { + int minHole = bds.m_MicroViasMinDrill; + wxString minHoleSource = _( "board minimum" ); + DRC_RULE* rule = GetRule( via, nullptr, HOLE_CONSTRAINT ); + + if( rule ) + { + minHole = rule->m_MinHole; + minHoleSource = wxString::Format( _( "'%s' rule" ), rule->m_Name ); + } + + if( via->GetDrillValue() < minHole ) + { + DRC_ITEM* drcItem = new DRC_ITEM( DRCE_TOO_SMALL_MICROVIA_DRILL ); + + m_msg.Printf( drcItem->GetErrorText() + _( " (%s %s; actual %s)" ), + minHoleSource, + MessageTextFromValue( m_units, minHole, true ), + MessageTextFromValue( m_units, via->GetDrillValue(), true ) ); + + drcItem->SetErrorMessage( m_msg ); + drcItem->SetItems( via ); + + HandleMarker( new MARKER_PCB( drcItem, via->GetPosition() ) ); + success = false; + } + } + + return success; +} + + +void DRC_DRILLED_HOLE_TESTER::addHole( const wxPoint& aLocation, int aRadius, BOARD_ITEM* aOwner ) +{ + DRILLED_HOLE hole; + + hole.m_location = aLocation; + hole.m_drillRadius = aRadius; + hole.m_owner = aOwner; + + m_largestRadius = std::max( m_largestRadius, aRadius ); + + m_holes.push_back( hole ); +} + + +bool DRC_DRILLED_HOLE_TESTER::checkHoles() +{ + bool success = true; + BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings(); + + // No need to check if we're ignoring DRCE_DRILLED_HOLES_TOO_CLOSE; if we are then we + // won't have collected any holes to test. + + // Sort holes by X for performance. In the nested iteration we then need to look at + // following holes only while they are within the refHole's neighborhood as defined by + // the refHole radius + the minimum hole-to-hole clearance + the largest radius any of + // the following holes can have. + std::sort( m_holes.begin(), m_holes.end(), + []( const DRILLED_HOLE& a, const DRILLED_HOLE& b ) + { + if( a.m_location.x == b.m_location.x ) + return a.m_location.y < b.m_location.y; + else + return a.m_location.x < b.m_location.x; + } ); + + for( size_t ii = 0; ii < m_holes.size(); ++ii ) + { + const DRILLED_HOLE& refHole = m_holes[ ii ]; + int neighborhood = refHole.m_drillRadius + bds.m_HoleToHoleMin + m_largestRadius; + + for( size_t jj = ii + 1; jj < m_holes.size(); ++jj ) + { + const DRILLED_HOLE& checkHole = m_holes[ jj ]; + + if( refHole.m_location.x + neighborhood < checkHole.m_location.x ) + break; + + // Holes with identical locations are allowable + if( checkHole.m_location == refHole.m_location ) + continue; + + int actual = KiROUND( GetLineLength( checkHole.m_location, refHole.m_location ) ); + actual = std::max( 0, actual - checkHole.m_drillRadius - refHole.m_drillRadius ); + + if( actual < bds.m_HoleToHoleMin ) + { + DRC_ITEM* drcItem = new DRC_ITEM( DRCE_DRILLED_HOLES_TOO_CLOSE ); + + m_msg.Printf( drcItem->GetErrorText() + _( " (board minimum %s; actual %s)" ), + MessageTextFromValue( m_units, bds.m_HoleToHoleMin, true ), + MessageTextFromValue( m_units, actual, true ) ); + + drcItem->SetErrorMessage( m_msg ); + drcItem->SetItems( refHole.m_owner, checkHole.m_owner ); + + HandleMarker( new MARKER_PCB( drcItem, refHole.m_location ) ); + success = false; + } + } + } + + return success; +} diff --git a/qa/drc_proto/drc_drilled_hole_tester.h b/qa/drc_proto/drc_drilled_hole_tester.h new file mode 100644 index 0000000000..a23e6843eb --- /dev/null +++ b/qa/drc_proto/drc_drilled_hole_tester.h @@ -0,0 +1,68 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2020 KiCad Developers, see change_log.txt for contributors. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + + +#ifndef DRC_DRILLED_HOLE_TESTER__H +#define DRC_DRILLED_HOLE_TESTER__H + +#include + + +class BOARD; +class BOARD_ITEM; + + +class DRC_DRILLED_HOLE_TESTER : public DRC_TEST_PROVIDER +{ +public: + DRC_DRILLED_HOLE_TESTER( MARKER_HANDLER aMarkerHandler ); + + virtual ~DRC_DRILLED_HOLE_TESTER() {}; + + bool RunDRC( EDA_UNITS aUnits, BOARD& aBoard ) override; + +private: + bool checkPad( D_PAD* aPad ); + bool checkVia( VIA* aVia ); + bool checkMicroVia( VIA* aVia ); + + void addHole( const wxPoint& aLocation, int aRadius, BOARD_ITEM* aOwner ); + bool checkHoles(); + +private: + struct DRILLED_HOLE + { + wxPoint m_location; + int m_drillRadius = 0; + BOARD_ITEM* m_owner = nullptr; + }; + + EDA_UNITS m_units; + BOARD* m_board; + std::vector m_holes; + int m_largestRadius; + + wxString m_msg; // Construct only once for performance +}; + +#endif // DRC_DRILLED_HOLE_TESTER__H diff --git a/qa/drc_proto/drc_engine.cpp b/qa/drc_proto/drc_engine.cpp new file mode 100644 index 0000000000..afd067f347 --- /dev/null +++ b/qa/drc_proto/drc_engine.cpp @@ -0,0 +1,1419 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * 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. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include // for KiROUND +#include +#include +#include + +#include +#include +#include +#include +#include + + +test::DRC_ENGINE::DRC_ENGINE( BOARD* aBoard, BOARD_DESIGN_SETTINGS *aSettings ) : + m_board( aBoard ), + m_designSettings ( aSettings ) + { + + } + +test::DRC_ENGINE::~DRC_ENGINE() +{ + for( DRC_ITEM* item : m_drcItems ) + delete item; +} + +/*void test::DRC_ENGINE::AddMarker( MARKER_PCB* aMarker ) +{ + if( m_designSettings->Ignore( aMarker->GetRCItem()->GetErrorCode() ) ) + { + delete aMarker; + return; + } + + m_markers.push_back( aMarker ); +}*/ + +bool test::DRC_ENGINE::LoadRules( wxFileName aPath ) +{ + + if( aPath.FileExists() ) + { + m_ruleConditions.clear(); + m_rules.clear(); + + FILE* fp = wxFopen( aPath.GetFullPath(), wxT( "rt" ) ); + + if( fp ) + { + try + { + DRC_RULES_PARSER parser( m_board, fp, aPath.GetFullPath() ); + parser.Parse( m_ruleConditions, m_rules ); + } + catch( PARSE_ERROR& pe ) + { + // Don't leave possibly malformed stuff around for us to trip over + m_ruleConditions.clear(); + m_rules.clear(); + + //wxSafeYield( m_editFrame ); + //m_editFrame->ShowBoardSetupDialog( _( "Rules" ), pe.What(), ID_RULES_EDITOR, + // pe.lineNumber, pe.byteIndex ); + + throw; + + return false; + } + } + } + + return true; +} + + +test::DRC_TEST_PROVIDER *drcCreateClearanceTestProvider( test::DRC_ENGINE *engine ); + +void test::DRC_ENGINE::loadTestProviders() +{ + m_testProviders.push_back( drcCreateClearanceTestProvider( this ) ); +} + + +void test::DRC_ENGINE::inferImplicitRules() +{ + +} + + +static void drc_dbg( int level, const char* fmt, ... ) +{ +#ifdef DEBUG + if(level < 10) // fixme: tom's debugging. + { + va_list ap; + va_start( ap, fmt ); + fprintf( stderr, "drc: " ); + vfprintf( stderr, fmt, ap ); + va_end( ap ); + } +#endif +} + + +bool test::DRC_ENGINE::CompileRules() +{ + drc_dbg(0,"compiling rules\n"); + + for( auto provider : m_testProviders ) + { + drc_dbg(1, "provider %p name '%s' -> ID %d\n", provider, (const char*) provider->GetName().c_str(), 0 ); + for ( auto id : provider->GetMatchingRuleIds() ) + { + if( m_ruleMap.find(id) == m_ruleMap.end() ) + m_ruleMap[id] = new RULE_SET; + + m_ruleMap[ id ]->provider = provider; + + for( auto rule : m_rules ) + { + if( rule->GetTestProviderName() == provider->GetName() ) + { + drc_dbg(1, "rule %p name '%s' -> provider %s\n", rule, (const char*) rule->m_Name.c_str(), (const char*) rule->m_TestProviderName.c_str() ); + if( rule->IsEnabled() ) + { + auto rcons = new RULE_WITH_CONDITIONS; + + for( auto condition : m_ruleConditions ) + { + if( condition->m_TargetRuleName == rule->GetName() ) + { + rcons->conditions.push_back( condition ); + + bool compileOk = condition->Compile(); + + + drc_dbg(1," - condition: '%s' compile: %s\n", (const char* ) condition->m_TargetRuleName.c_str(), compileOk ? "OK" : "ERROR"); + + rcons->rule = rule; + m_ruleMap[ id ]->sortedRules.push_back( rcons ); + } + } + } + } + } + } + } + + return true; +} + + +void test::DRC_ENGINE::RunTests( ) +{ + //m_largestClearance = m_designSettings->GetBiggestClearanceValue(); + + loadTestProviders(); + inferImplicitRules(); + + CompileRules(); +} + + +test::DRC_CONSTRAINT test::DRC_ENGINE::EvalRulesForItems( test::DRC_RULE_ID_T ruleID, const BOARD_ITEM* a, const BOARD_ITEM* b ) +{ + test::DRC_CONSTRAINT rv; + auto ruleset = m_ruleMap[ ruleID ]; + + + return rv; +} + + +#if 0 + + + if( !bds.Ignore( DRCE_INVALID_OUTLINE ) + || !bds.Ignore( DRCE_TRACK_NEAR_EDGE ) + || !bds.Ignore( DRCE_VIA_NEAR_EDGE ) + || !bds.Ignore( DRCE_PAD_NEAR_EDGE ) ) + { + if( aMessages ) + { + aMessages->AppendText( _( "Board Outline...\n" ) ); + wxSafeYield(); + } + + testOutline( commit ); + } + + if( aMessages ) + { + aMessages->AppendText( _( "Netclasses...\n" ) ); + wxSafeYield(); + } + + DRC_NETCLASS_TESTER netclassTester( [&]( MARKER_PCB* aMarker ) + { + addMarkerToPcb( commit, aMarker ); + } ); + + if( !netclassTester.RunDRC( userUnits(), *m_pcb ) ) + { + // testing the netclasses is a special case because if the netclasses + // do not pass the BOARD_DESIGN_SETTINGS checks, then every member of a net + // class (a NET) will cause its items such as tracks, vias, and pads + // to also fail. So quit after *all* netclass errors have been reported. + if( aMessages ) + aMessages->AppendText( _( "NETCLASS VIOLATIONS: Aborting DRC\n" ) ); + + commit.Push( wxEmptyString, false, false ); + + // update the m_drcDialog listboxes + updatePointers(); + + return; + } + + // test pad to pad clearances, nothing to do with tracks, vias or zones. + if( !bds.Ignore( DRCE_PAD_NEAR_EDGE ) + || !bds.Ignore( DRCE_PAD_NEAR_PAD ) + || !bds.Ignore( DRCE_HOLE_NEAR_PAD ) ) + { + if( aMessages ) + { + aMessages->AppendText( _( "Pad clearances...\n" ) ); + wxSafeYield(); + } + + testPadClearances( commit ); + } + + // test drilled holes + if( !bds.Ignore( DRCE_DRILLED_HOLES_TOO_CLOSE ) + || !bds.Ignore( DRCE_TOO_SMALL_PAD_DRILL ) + || !bds.Ignore( DRCE_TOO_SMALL_VIA_DRILL ) + || !bds.Ignore( DRCE_TOO_SMALL_MICROVIA_DRILL ) ) + { + if( aMessages ) + { + aMessages->AppendText( _( "Drill sizes and clearances...\n" ) ); + wxSafeYield(); + } + + 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()->FillAllZones( caller ); + } + else + { + if( aMessages ) + aMessages->AppendText( _( "Checking zone fills...\n" ) ); + + m_toolMgr->GetTool()->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 ) + { + aMessages->AppendText( _( "Zone to zone clearances...\n" ) ); + wxSafeYield(); + } + + testZones( commit ); + + // find and gather unconnected pads. + if( m_doUnconnectedTest + && !bds.Ignore( DRCE_UNCONNECTED_ITEMS ) ) + { + if( aMessages ) + { + aMessages->AppendText( _( "Unconnected pads...\n" ) ); + aMessages->Refresh(); + } + + testUnconnected(); + } + + // find and gather vias, tracks, pads inside keepout areas. + if( m_doKeepoutTest ) + { + if( aMessages ) + { + aMessages->AppendText( _( "Keepout areas ...\n" ) ); + aMessages->Refresh(); + } + + 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_VIA_NEAR_COPPER ) + || !bds.Ignore( DRCE_TRACK_NEAR_COPPER ) ) + { + if( aMessages ) + { + aMessages->AppendText( _( "Text and graphic clearances...\n" ) ); + wxSafeYield(); + } + + testCopperTextAndGraphics( commit ); + } + + // test courtyards + 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(); + } + + DRC_COURTYARD_TESTER tester( [&]( MARKER_PCB* aMarker ) + { + addMarkerToPcb( commit, aMarker ); + } ); + + tester.RunDRC( userUnits(), *m_pcb ); + } + + for( DRC_ITEM* footprintItem : m_footprints ) + delete footprintItem; + + 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 + if( !bds.Ignore( DRCE_DISABLED_LAYER_ITEM ) ) + { + if( aMessages ) + { + aMessages->AppendText( _( "Items on disabled layers...\n" ) ); + aMessages->Refresh(); + } + + testDisabledLayers( commit ); + } + + 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() ); + + tester.RunDRC( userUnits(), *m_pcb ); + } + + commit.Push( wxEmptyString, false, false ); + m_drcRun = true; + + // update the m_drcDialog listboxes + updatePointers(); + + if( aMessages ) + { + // no newline on this one because it is last, don't want the window + // to unnecessarily scroll. + aMessages->AppendText( _( "Finished" ) ); + } +} + + +void DRC::updatePointers() +{ + // update my pointers, m_editFrame is the only unchangeable one + m_pcb = m_editFrame->GetBoard(); + + m_editFrame->ResolveDRCExclusions(); + + if( m_drcDialog ) // Use diag list boxes only in DRC dialog + { + 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 ) ); + } +} + + +void DRC::testPadClearances( BOARD_COMMIT& aCommit ) +{ + BOARD_DESIGN_SETTINGS& bds = m_pcb->GetDesignSettings(); + std::vector sortedPads; + + m_pcb->GetSortedPadListByXthenYCoord( sortedPads ); + + if( sortedPads.empty() ) + return; + + // find the max size of the pads (used to stop the pad-to-pad tests) + int max_size = 0; + + for( D_PAD* pad : sortedPads ) + { + // GetBoundingRadius() is the radius of the minimum sized circle fully containing the pad + int radius = pad->GetBoundingRadius(); + + if( radius > max_size ) + max_size = radius; + } + + // 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(); + + // Test the pads + for( auto& pad : sortedPads ) + { + if( !bds.Ignore( DRCE_PAD_NEAR_EDGE ) && m_board_outline_valid ) + { + int minClearance = bds.m_CopperEdgeClearance; + m_clearanceSource = _( "board edge" ); + + static DRAWSEGMENT dummyEdge; + dummyEdge.SetLayer( Edge_Cuts ); + + if( pad->GetRuleClearance( &dummyEdge, &minClearance, &m_clearanceSource ) ) + /* minClearance and m_clearanceSource set in GetRuleClearance() */; + + for( auto it = m_board_outlines.IterateSegmentsWithHoles(); it; it++ ) + { + int actual; + + if( !checkClearanceSegmToPad( *it, 0, pad, minClearance, &actual ) ) + { + actual = std::max( 0, actual ); + DRC_ITEM* drcItem = new DRC_ITEM( DRCE_PAD_NEAR_EDGE ); + + 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_PAD_NEAR_PAD ) || !bds.Ignore( DRCE_HOLE_NEAR_PAD ) ) + { + int x_limit = pad->GetPosition().x + pad->GetBoundingRadius() + max_size; + + doPadToPadsDrc( aCommit, pad, &pad, listEnd, x_limit ); + } + } +} + + +void DRC::testTracks( BOARD_COMMIT& aCommit, wxWindow *aActiveWindow, bool aShowProgressBar ) +{ + wxProgressDialog* 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 wxProgressDialog( _( "Track clearances" ), wxEmptyString, + deltamax, aActiveWindow, + wxPD_AUTO_HIDE | wxPD_CAN_ABORT | wxPD_ELAPSED_TIME ); + progressDialog->Update( 0, wxEmptyString ); + } + + std::shared_ptr 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; + + 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 + doTrackDrc( aCommit, *seg_it, seg_it + 1, m_pcb->Tracks().end(), m_testTracksAgainstZones ); + + // 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 ) ) + { + DRC_ITEM* drcItem = new DRC_ITEM( code ); + drcItem->SetItems( *seg_it ); + + MARKER_PCB* marker = new MARKER_PCB( drcItem, pos ); + addMarkerToPcb( aCommit, marker ); + } + } + + if( progressDialog ) + progressDialog->Destroy(); +} + + +void DRC::testUnconnected() +{ + for( DRC_ITEM* unconnectedItem : m_unconnected ) + delete unconnectedItem; + + m_unconnected.clear(); + + auto connectivity = m_pcb->GetConnectivity(); + + connectivity->Clear(); + connectivity->Build( m_pcb ); // just in case. This really needs to be reliable. + connectivity->RecalculateRatsnest(); + + std::vector edges; + connectivity->GetUnconnectedEdges( edges ); + + for( const CN_EDGE& edge : edges ) + { + DRC_ITEM* item = new DRC_ITEM( DRCE_UNCONNECTED_ITEMS ); + item->SetItems( edge.GetSourceNode()->Parent(), edge.GetTargetNode()->Parent() ); + m_unconnected.push_back( item ); + } +} + + +void DRC::testZones( BOARD_COMMIT& aCommit ) +{ + BOARD_DESIGN_SETTINGS& bds = m_pcb->GetDesignSettings(); + + // Test copper areas for valid netcodes + // 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 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 ) + { + DRC_ITEM* drcItem = new DRC_ITEM( DRCE_ZONE_HAS_EMPTY_NET ); + drcItem->SetItems( zone ); + + MARKER_PCB* marker = new MARKER_PCB( drcItem, zone->GetPosition() ); + addMarkerToPcb( aCommit, marker ); + } + } + + ZONE_CONTAINER* zoneRef = m_pcb->GetArea( ii ); + std::set colinearCorners; + zoneRef->GetColinearCorners( m_pcb, colinearCorners ); + + zoneRef->BuildSmoothedPoly( smoothed_polys[ii], &colinearCorners ); + } + + // 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( 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 ) ) + { + DRC_ITEM* drcItem = new DRC_ITEM( 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 ) ) + { + DRC_ITEM* drcItem = new DRC_ITEM( 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 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& conflict : conflictPoints ) + { + int actual = conflict.second; + DRC_ITEM* drcItem; + + if( actual <= 0 ) + { + drcItem = new DRC_ITEM( DRCE_ZONES_INTERSECT ); + } + else + { + drcItem = new DRC_ITEM( DRCE_ZONES_TOO_CLOSE ); + + 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 ); + } + } + } +} + + +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() ) ) + 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() ) ) + testCopperDrawItem( aCommit, &ref ); + + if( val.IsVisible() && IsCopperLayer( val.GetLayer() ) ) + 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() ) + testCopperDrawItem( aCommit, item ); + else if( item->Type() == PCB_MODULE_EDGE_T ) + testCopperDrawItem( aCommit, item ); + } + } + } +} + + +void DRC::testCopperDrawItem( BOARD_COMMIT& aCommit, BOARD_ITEM* aItem ) +{ + EDA_RECT bbox; + std::vector itemShape; + int itemWidth; + DRAWSEGMENT* drawItem = dynamic_cast( aItem ); + EDA_TEXT* textItem = dynamic_cast( aItem ); + + if( drawItem ) + { + bbox = drawItem->GetBoundingBox(); + itemWidth = drawItem->GetWidth(); + + switch( drawItem->GetShape() ) + { + case S_ARC: + { + SHAPE_ARC arc( drawItem->GetCenter(), drawItem->GetArcStart(), + (double) drawItem->GetAngle() / 10.0 ); + + SHAPE_LINE_CHAIN l = arc.ConvertToPolyline(); + + for( int i = 0; i < l.SegmentCount(); i++ ) + itemShape.push_back( l.Segment( i ) ); + + break; + } + + case S_SEGMENT: + itemShape.emplace_back( SEG( drawItem->GetStart(), drawItem->GetEnd() ) ); + break; + + case S_CIRCLE: + { + // SHAPE_CIRCLE has no ConvertToPolyline() method, so use a 360.0 SHAPE_ARC + SHAPE_ARC circle( drawItem->GetCenter(), drawItem->GetEnd(), 360.0 ); + + SHAPE_LINE_CHAIN l = circle.ConvertToPolyline(); + + for( int i = 0; i < l.SegmentCount(); i++ ) + itemShape.push_back( l.Segment( i ) ); + + break; + } + + case S_CURVE: + { + drawItem->RebuildBezierToSegmentsPointsList( drawItem->GetWidth() ); + wxPoint start_pt = drawItem->GetBezierPoints()[0]; + + for( unsigned int jj = 1; jj < drawItem->GetBezierPoints().size(); jj++ ) + { + wxPoint end_pt = drawItem->GetBezierPoints()[jj]; + itemShape.emplace_back( SEG( start_pt, end_pt ) ); + start_pt = end_pt; + } + + break; + } + + case S_POLYGON: + { + SHAPE_LINE_CHAIN l = drawItem->GetPolyShape().Outline( 0 ); + + for( int i = 0; i < l.SegmentCount(); i++ ) + itemShape.push_back( l.Segment( i ) ); + } + break; + + default: + wxFAIL_MSG( "unknown shape type" ); + break; + } + } + else if( textItem ) + { + bbox = textItem->GetTextBox(); + itemWidth = textItem->GetEffectiveTextPenWidth(); + + std::vector textShape; + textItem->TransformTextShapeToSegmentList( textShape ); + + for( unsigned jj = 0; jj < textShape.size(); jj += 2 ) + itemShape.emplace_back( SEG( textShape[jj], textShape[jj+1] ) ); + } + else + { + wxFAIL_MSG( "unknown item type in testCopperDrawItem()" ); + return; + } + + SHAPE_RECT rect_area( bbox.GetX(), bbox.GetY(), bbox.GetWidth(), bbox.GetHeight() ); + + if( itemShape.empty() ) + return; + + // Test tracks and vias + for( auto track : m_pcb->Tracks() ) + { + if( !track->IsOnLayer( aItem->GetLayer() ) ) + continue; + + int minClearance = track->GetClearance( aItem, &m_clearanceSource ); + int widths = ( track->GetWidth() + itemWidth ) / 2; + int center2centerAllowed = minClearance + widths; + + SEG trackSeg( track->GetStart(), track->GetEnd() ); + + // Fast test to detect a track segment candidate inside the text bounding box + if( !rect_area.Collide( trackSeg, center2centerAllowed ) ) + continue; + + OPT minSeg; + SEG::ecoord center2center_squared = 0; + + for( const SEG& itemSeg : itemShape ) + { + SEG::ecoord thisDist_squared = trackSeg.SquaredDistance( itemSeg ); + + if( !minSeg || thisDist_squared < center2center_squared ) + { + minSeg = itemSeg; + center2center_squared = thisDist_squared; + } + } + + if( center2center_squared < SEG::Square( center2centerAllowed ) ) + { + int actual = std::max( 0.0, sqrt( center2center_squared ) - widths ); + int errorCode = ( track->Type() == PCB_VIA_T ) ? DRCE_VIA_NEAR_COPPER + : DRCE_TRACK_NEAR_COPPER; + DRC_ITEM* drcItem = new DRC_ITEM( errorCode ); + + 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( track, aItem ); + + wxPoint pos = GetLocation( track, minSeg.get() ); + MARKER_PCB* marker = new MARKER_PCB( drcItem, pos ); + addMarkerToPcb( aCommit, marker ); + } + } + + // Test pads + for( auto pad : m_pcb->GetPads() ) + { + if( !pad->IsOnLayer( aItem->GetLayer() ) ) + continue; + + // Graphic items are allowed to act as net-ties within their own footprint + if( drawItem && pad->GetParent() == drawItem->GetParent() ) + continue; + + int minClearance = pad->GetClearance( aItem, &m_clearanceSource ); + int widths = itemWidth / 2; + int center2centerAllowed = minClearance + widths; + + // 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; + VECTOR2I shape_pos( pad->ShapePos() ); + + if( !rect_area.Collide( SEG( shape_pos, shape_pos ), bb_radius ) ) + continue; + + SHAPE_POLY_SET padOutline; + pad->TransformShapeWithClearanceToPolygon( padOutline, 0 ); + + OPT minSeg; + SEG::ecoord center2center_squared = 0; + + for( const SEG& itemSeg : itemShape ) + { + SEG::ecoord thisCenter2center_squared = padOutline.SquaredDistance( itemSeg ); + + if( !minSeg || thisCenter2center_squared < center2center_squared ) + { + minSeg = itemSeg; + center2center_squared = thisCenter2center_squared; + } + } + + if( center2center_squared < SEG::Square( center2centerAllowed ) ) + { + int actual = std::max( 0.0, sqrt( center2center_squared ) - widths ); + DRC_ITEM* drcItem = new DRC_ITEM( DRCE_PAD_NEAR_COPPER ); + + 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, aItem ); + + MARKER_PCB* marker = new MARKER_PCB( drcItem, pad->GetPosition() ); + addMarkerToPcb( aCommit, marker ); + } + } +} + + +void DRC::testOutline( BOARD_COMMIT& aCommit ) +{ + 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 + { + DRC_ITEM* drcItem = new DRC_ITEM( DRCE_INVALID_OUTLINE ); + + m_msg.Printf( drcItem->GetErrorText() + _( " (not a closed shape)" ) ); + + drcItem->SetErrorMessage( m_msg ); + drcItem->SetItems( m_pcb ); + + MARKER_PCB* marker = new MARKER_PCB( drcItem, error_loc ); + addMarkerToPcb( aCommit, marker ); + } +} + + +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() ) ) + { + DRC_ITEM* drcItem = new DRC_ITEM( DRCE_DISABLED_LAYER_ITEM ); + + m_msg.Printf( drcItem->GetErrorText() + _( "layer %s" ), + track->GetLayerName() ); + + drcItem->SetErrorMessage( m_msg ); + drcItem->SetItems( track ); + + MARKER_PCB* marker = new MARKER_PCB( drcItem, track->GetPosition() ); + addMarkerToPcb( aCommit, marker ); + } + } + + for( MODULE* module : m_pcb->Modules() ) + { + module->RunOnChildren( + [&]( BOARD_ITEM* child ) + { + if( disabledLayers.test( child->GetLayer() ) ) + { + DRC_ITEM* drcItem = new DRC_ITEM( DRCE_DISABLED_LAYER_ITEM ); + + m_msg.Printf( drcItem->GetErrorText() + _( "layer %s" ), + child->GetLayerName() ); + + drcItem->SetErrorMessage( m_msg ); + drcItem->SetItems( child ); + + MARKER_PCB* marker = new MARKER_PCB( drcItem, child->GetPosition() ); + addMarkerToPcb( aCommit, marker ); + } + } ); + } + + for( ZONE_CONTAINER* zone : m_pcb->Zones() ) + { + if( disabledLayers.test( zone->GetLayer() ) ) + { + DRC_ITEM* drcItem = new DRC_ITEM( DRCE_DISABLED_LAYER_ITEM ); + + m_msg.Printf( drcItem->GetErrorText() + _( "layer %s" ), + zone->GetLayerName() ); + + drcItem->SetErrorMessage( m_msg ); + drcItem->SetItems( zone ); + + MARKER_PCB* marker = new MARKER_PCB( drcItem, zone->GetPosition() ); + addMarkerToPcb( aCommit, marker ); + } + } +} + + +bool DRC::doPadToPadsDrc( BOARD_COMMIT& aCommit, D_PAD* aRefPad, D_PAD** aStart, D_PAD** aEnd, + int x_limit ) +{ + const static LSET all_cu = LSET::AllCuMask(); + + LSET layerMask = aRefPad->GetLayerSet() & all_cu; + + // For hole testing we use a dummy pad which is given the shape of the hole. Note that + // this pad must have a parent because some functions expect a non-null parent to find + // the pad's board. + MODULE dummymodule( m_pcb ); // Creates a dummy parent + D_PAD dummypad( &dummymodule ); + + // Ensure the hole is on all copper layers + dummypad.SetLayerSet( all_cu | dummypad.GetLayerSet() ); + + for( D_PAD** pad_list = aStart; pad_listGetPosition().x > x_limit + // because the list is sorted by X values + if( pad->GetPosition().x > x_limit ) + 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 + // copper layers, so we test them + if( ( pad->GetLayerSet() & layerMask ) == 0 && + ( pad->GetLayerSet() & all_cu ) != 0 && + ( aRefPad->GetLayerSet() & all_cu ) != 0 ) + { + // if holes are in the same location and have the same size and shape, + // this can be accepted + if( pad->GetPosition() == aRefPad->GetPosition() + && 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 + if( pad->GetOrientation() == aRefPad->GetOrientation() ) + continue; + } + + /* Here, we must test clearance between holes and pads + * dummy pad size and shape is adjusted to pad drill size and shape + */ + if( pad->GetDrillSize().x ) + { + // pad under testing has a hole, test this hole against pad reference + dummypad.SetPosition( pad->GetPosition() ); + dummypad.SetSize( pad->GetDrillSize() ); + dummypad.SetShape( pad->GetDrillShape() == PAD_DRILL_SHAPE_OBLONG ? + PAD_SHAPE_OVAL : PAD_SHAPE_CIRCLE ); + dummypad.SetOrientation( pad->GetOrientation() ); + + int minClearance = aRefPad->GetClearance( nullptr, &m_clearanceSource ); + int actual; + + if( !checkClearancePadToPad( aRefPad, &dummypad, minClearance, &actual ) ) + { + DRC_ITEM* drcItem = new DRC_ITEM( DRCE_HOLE_NEAR_PAD ); + + 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, aRefPad ); + + MARKER_PCB* marker = new MARKER_PCB( drcItem, pad->GetPosition() ); + addMarkerToPcb( aCommit, marker ); + return false; + } + } + + if( aRefPad->GetDrillSize().x ) // pad reference has a hole + { + dummypad.SetPosition( aRefPad->GetPosition() ); + dummypad.SetSize( aRefPad->GetDrillSize() ); + dummypad.SetShape( aRefPad->GetDrillShape() == PAD_DRILL_SHAPE_OBLONG ? + PAD_SHAPE_OVAL : PAD_SHAPE_CIRCLE ); + dummypad.SetOrientation( aRefPad->GetOrientation() ); + + int minClearance = pad->GetClearance( nullptr, &m_clearanceSource ); + int actual; + + if( !checkClearancePadToPad( pad, &dummypad, minClearance, &actual ) ) + { + DRC_ITEM* drcItem = new DRC_ITEM( DRCE_HOLE_NEAR_PAD ); + + 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( aRefPad, pad ); + + MARKER_PCB* marker = new MARKER_PCB( drcItem, aRefPad->GetPosition() ); + addMarkerToPcb( aCommit, marker ); + return false; + } + } + + continue; + } + + // 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() ) ) + continue; + + // if pads are from the same footprint + if( pad->GetParent() == aRefPad->GetParent() ) + { + // and have the same pad number ( equivalent pads ) + + // one can argue that this 2nd test is not necessary, that any + // two pads from a single module are acceptable. This 2nd test + // should eventually be a configuration option. + if( pad->PadNameEqual( aRefPad ) ) + continue; + } + + // 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; + } + + int minClearance = aRefPad->GetClearance( pad, &m_clearanceSource ); + int clearanceAllowed = minClearance - m_pcb->GetDesignSettings().GetDRCEpsilon(); + int actual; + + if( !checkClearancePadToPad( aRefPad, pad, clearanceAllowed, &actual ) ) + { + DRC_ITEM* drcItem = new DRC_ITEM( DRCE_PAD_NEAR_PAD ); + + 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( aRefPad, pad ); + + MARKER_PCB* marker = new MARKER_PCB( drcItem, aRefPad->GetPosition() ); + addMarkerToPcb( aCommit, marker ); + return false; + } + } + + return true; +} + + +void DRC::setTransitions() +{ + Go( &DRC::ShowDRCDialog, PCB_ACTIONS::runDRC.MakeEvent() ); +} + + +const int UI_EPSILON = Mils2iu( 5 ); + + +wxPoint DRC::GetLocation( TRACK* aTrack, ZONE_CONTAINER* aConflictZone ) +{ + SHAPE_POLY_SET* conflictOutline; + + if( aConflictZone->IsFilled() ) + conflictOutline = const_cast( &aConflictZone->GetFilledPolysList() ); + else + conflictOutline = aConflictZone->Outline(); + + wxPoint pt1 = aTrack->GetPosition(); + wxPoint pt2 = aTrack->GetEnd(); + + // If the mid-point is in the zone, then that's a fine place for the marker + if( conflictOutline->SquaredDistance( ( pt1 + pt2 ) / 2 ) == 0 ) + return ( pt1 + pt2 ) / 2; + + // Otherwise do a binary search for a "good enough" marker location + else + { + while( GetLineLength( pt1, pt2 ) > UI_EPSILON ) + { + if( conflictOutline->SquaredDistance( pt1 ) < conflictOutline->SquaredDistance( pt2 ) ) + pt2 = ( pt1 + pt2 ) / 2; + else + pt1 = ( pt1 + pt2 ) / 2; + } + + // Once we're within UI_EPSILON pt1 and pt2 are "equivalent" + return pt1; + } +} + + +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; +} + + +#endif + diff --git a/qa/drc_proto/drc_engine.h b/qa/drc_proto/drc_engine.h new file mode 100644 index 0000000000..58f316f4b2 --- /dev/null +++ b/qa/drc_proto/drc_engine.h @@ -0,0 +1,232 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2007-2016 Dick Hollenbeck, dick@softplc.com + * Copyright (C) 2017-2019 KiCad Developers, see change_log.txt for contributors. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#ifndef DRC_ENGINE_H +#define DRC_ENGINE_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#include + +class BOARD_DESIGN_SETTINGS; +class PCB_EDIT_FRAME; +class DIALOG_DRC; +class BOARD_ITEM; +class BOARD; +class D_PAD; +class ZONE_CONTAINER; +class TRACK; +class MARKER_PCB; +class NETCLASS; +class EDA_TEXT; +class DRAWSEGMENT; +class NETLIST; +class wxWindow; +class wxString; +class wxTextCtrl; +class PROGRESS_REPORTER; + +namespace test +{ + +class DRC_RULE_CONDITION; +class DRC_ITEM; +class DRC_RULE; +class DRC_TEST_PROVIDER; +class DRC_CONSTRAINT; + +/// DRC error codes: +enum PCB_DRC_CODE +{ + DRCE_FIRST = 1, + DRCE_UNCONNECTED_ITEMS = DRCE_FIRST, ///< items are unconnected + DRCE_TRACK_NEAR_HOLE, ///< thru hole is too close to track + DRCE_TRACK_NEAR_PAD, ///< pad too close to track + DRCE_TRACK_NEAR_VIA, ///< track too close to via + DRCE_TRACK_NEAR_ZONE, ///< track & zone collide or are too close together + DRCE_TRACK_NEAR_COPPER, ///< track & copper graphic collide or are too close + DRCE_VIA_NEAR_VIA, ///< via too close to via + DRCE_VIA_NEAR_TRACK, ///< via too close to track + DRCE_VIA_NEAR_COPPER, ///< via and copper graphic collide or are too close + DRCE_TRACK_ENDS, ///< track ends are too close + DRCE_TRACK_SEGMENTS_TOO_CLOSE, ///< 2 parallel track segments too close: segm ends between segref ends + DRCE_TRACKS_CROSSING, ///< tracks are crossing + DRCE_TRACK_NEAR_EDGE, ///< track too close to board edge + DRCE_VIA_NEAR_EDGE, ///< via too close to board edge + DRCE_PAD_NEAR_EDGE, ///< pad too close to board edge + DRCE_PAD_NEAR_PAD, ///< pad too close to pad + DRCE_PAD_NEAR_COPPER, ///< pad and copper graphic collide or are too close + DRCE_ZONES_INTERSECT, ///< copper area outlines intersect + DRCE_ZONES_TOO_CLOSE, ///< copper area outlines are too close + DRCE_ZONE_HAS_EMPTY_NET, ///< copper area has a net but no pads in nets, which is suspicious + DRCE_DANGLING_VIA, ///< via which isn't connected to anything + DRCE_DANGLING_TRACK, ///< track with at least one end not connected to anything + DRCE_HOLE_NEAR_PAD, ///< hole too close to pad + DRCE_HOLE_NEAR_TRACK, ///< hole too close to track + DRCE_DRILLED_HOLES_TOO_CLOSE, ///< overlapping drilled holes break drill bits + DRCE_TOO_SMALL_TRACK_WIDTH, ///< Too small track width + DRCE_TOO_LARGE_TRACK_WIDTH, ///< Too small track width + DRCE_TOO_SMALL_VIA, ///< Too small via size + DRCE_TOO_SMALL_VIA_ANNULUS, ///< Via size and drill leave annulus too small + DRCE_TOO_SMALL_VIA_DRILL, ///< Too small via drill + DRCE_TOO_SMALL_PAD_DRILL, ///< Too small via drill + DRCE_VIA_HOLE_BIGGER, ///< via's hole is bigger than its diameter + DRCE_MICROVIA_NOT_ALLOWED, ///< micro vias are not allowed + DRCE_MICROVIA_TOO_MANY_LAYERS, ///< micro via's layer pair incorrect (layers must be adjacent) + DRCE_TOO_SMALL_MICROVIA, ///< Too small micro via size + DRCE_TOO_SMALL_MICROVIA_DRILL, ///< Too small micro via drill + DRCE_BURIED_VIA_NOT_ALLOWED, ///< buried vias are not allowed + DRCE_NETCLASS_TRACKWIDTH, ///< netclass has TrackWidth < board.m_designSettings->m_TrackMinWidth + DRCE_NETCLASS_CLEARANCE, ///< netclass has Clearance < board.m_designSettings->m_TrackClearance + DRCE_NETCLASS_VIAANNULUS, ///< netclass ViaSize & ViaDrill leave annulus < board.m_designSettings->m_ViasMinAnnulus + DRCE_NETCLASS_VIASIZE, ///< netclass has ViaSize < board.m_designSettings->m_ViasMinSize + DRCE_NETCLASS_VIADRILLSIZE, ///< netclass has ViaDrillSize < board.m_designSettings->m_MinThroughDrill + DRCE_NETCLASS_uVIASIZE, ///< netclass has ViaSize < board.m_designSettings->m_MicroViasMinSize + DRCE_NETCLASS_uVIADRILLSIZE, ///< netclass has ViaSize < board.m_designSettings->m_MicroViasMinDrill + DRCE_VIA_INSIDE_KEEPOUT, + DRCE_MICROVIA_INSIDE_KEEPOUT, + DRCE_BBVIA_INSIDE_KEEPOUT, + DRCE_TRACK_INSIDE_KEEPOUT, + DRCE_PAD_INSIDE_KEEPOUT, + DRCE_FOOTPRINT_INSIDE_KEEPOUT, + DRCE_HOLE_INSIDE_KEEPOUT, + DRCE_TEXT_INSIDE_KEEPOUT, + DRCE_GRAPHICS_INSIDE_KEEPOUT, + DRCE_OVERLAPPING_FOOTPRINTS, ///< footprint courtyards overlap + DRCE_MISSING_COURTYARD, ///< footprint has no courtyard defined + DRCE_MALFORMED_COURTYARD, ///< footprint has a courtyard but malformed + ///< (not convertible to a closed polygon with holes) + DRCE_PTH_IN_COURTYARD, + DRCE_NPTH_IN_COURTYARD, + DRCE_DISABLED_LAYER_ITEM, ///< item on a disabled layer + DRCE_INVALID_OUTLINE, ///< invalid board outline + DRCE_MISSING_FOOTPRINT, ///< footprint not found for netlist item + DRCE_DUPLICATE_FOOTPRINT, ///< more than one footprints found for netlist item + DRCE_EXTRA_FOOTPRINT, ///< netlist item not found for footprint + + DRCE_UNRESOLVED_VARIABLE, + + DRCE_LAST = DRCE_UNRESOLVED_VARIABLE, + + // These are actually Cleanup Tracks and Vias actions, not DRCE errors + CLEANUP_SHORT, + CLEANUP_REDUNDANT_VIA, + CLEANUP_DUPLICATE_TRACK, + CLEANUP_MERGE_TRACKS, + CLEANUP_DANGLING_TRACK, + CLEANUP_DANGLING_VIA, + CLEANUP_ZERO_LENGTH_TRACK, + CLEANUP_TRACK_IN_PAD +}; + + +/** + * Design Rule Checker object that performs all the DRC tests. The output of + * the checking goes to the BOARD file in the form of two MARKER lists. Those + * two lists are displayable in the drc dialog box. And they can optionally + * be sent to a text file on disk. + * This class is given access to the windows and the BOARD + * that it needs via its constructor or public access functions. + */ +class DRC_ENGINE +{ + +public: + DRC_ENGINE( BOARD* aBoard, BOARD_DESIGN_SETTINGS* aSettings ); + ~DRC_ENGINE(); + + void SetProgressReporter( PROGRESS_REPORTER* aProgRep ); + + bool LoadRules( wxFileName aPath ); + void RunTests(); + + void SetErrorLimit( int aLimit ); + + BOARD_DESIGN_SETTINGS* GetDesignSettings() const + { + return m_designSettings; + } + BOARD* GetBoard() const + { + return m_board; + } + + DRC_CONSTRAINT EvalRulesForItems( + DRC_RULE_ID_T ruleID, const BOARD_ITEM* a, const BOARD_ITEM* b = nullptr ); + + //void IterateOverMatchingRules( DRC_RULE_ID_IT ruleID ); + + EDA_UNITS UserUnits() const + { + return EDA_UNITS::MILLIMETRES; + } + + bool CompileRules(); + +private: + + struct RULE_WITH_CONDITIONS + { + std::vector conditions; + test::DRC_RULE* rule; + }; + + struct RULE_SET + { + std::vector sortedRules; + DRC_TEST_PROVIDER* provider; + }; + + typedef std::unordered_map RULE_MAP; + + + void inferImplicitRules(); + void loadTestProviders(); + + BOARD_DESIGN_SETTINGS* m_designSettings; + BOARD* m_board; + std::vector m_drcItems; + + std::vector m_ruleConditions; + std::vector m_rules; + std::vector m_testProviders; + std::vector<::MARKER_PCB*> m_markers; + RULE_MAP m_ruleMap; + + // condition -> rule -> provider +}; + + +}; // namespace test + +#endif // DRC_H diff --git a/qa/drc_proto/drc_item.cpp b/qa/drc_proto/drc_item.cpp new file mode 100644 index 0000000000..ccefd4b977 --- /dev/null +++ b/qa/drc_proto/drc_item.cpp @@ -0,0 +1,207 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2007 Dick Hollenbeck, dick@softplc.com + * Copyright (C) 2015-2020 KiCad Developers, see change_log.txt for contributors. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + + +#include +#include +#include "wx/html/m_templ.h" +#include "wx/html/styleparams.h" +#include +#include +#include + + +DRC_ITEM::DRC_ITEM( int aErrorCode ) +{ + m_errorCode = aErrorCode; +} + + +DRC_ITEM::DRC_ITEM( const wxString& aErrorText ) +{ + for( int errorCode = DRCE_FIRST; errorCode <= DRCE_LAST; ++errorCode ) + { + if( aErrorText == GetErrorText( errorCode, false ) ) + { + m_errorCode = errorCode; + break; + } + } +} + + +wxString DRC_ITEM::GetErrorText( int aCode, bool aTranslate ) const +{ + wxString msg; + + if( aCode < 0 ) + aCode = m_errorCode; + + switch( aCode ) + { + case DRCE_UNCONNECTED_ITEMS: msg = _HKI( "Unconnected items" ); break; + case DRCE_TRACK_NEAR_HOLE: msg = _HKI( "Track too close to hole" ); break; + case DRCE_TRACK_NEAR_PAD: msg = _HKI( "Track too close to pad" ); break; + case DRCE_TRACK_NEAR_VIA: msg = _HKI( "Track too close to via" ); break; + case DRCE_VIA_NEAR_VIA: msg = _HKI( "Vias too close" ); break; + case DRCE_VIA_NEAR_TRACK: msg = _HKI( "Via too close to track" ); break; + case DRCE_TRACK_ENDS: msg = _HKI( "Track ends too close" ); break; + case DRCE_TRACK_SEGMENTS_TOO_CLOSE: msg = _HKI( "Parallel tracks too close" ); break; + case DRCE_TRACKS_CROSSING: msg = _HKI( "Tracks crossing" ); break; + case DRCE_TRACK_NEAR_ZONE: msg = _HKI( "Track too close to copper area" ); break; + case DRCE_PAD_NEAR_PAD: msg = _HKI( "Pads too close" ); break; + case DRCE_VIA_HOLE_BIGGER: msg = _HKI( "Via hole larger than diameter" ); break; + case DRCE_MICROVIA_TOO_MANY_LAYERS: msg = _HKI( "Micro via through too many layers" ); break; + case DRCE_MICROVIA_NOT_ALLOWED: msg = _HKI( "Micro via not allowed" ); break; + case DRCE_BURIED_VIA_NOT_ALLOWED: msg = _HKI( "Buried via not allowed" ); break; + case DRCE_DISABLED_LAYER_ITEM: msg = _HKI( "Item on a disabled layer" ); break; + case DRCE_ZONES_INTERSECT: msg = _HKI( "Copper areas intersect" ); break; + case DRCE_ZONES_TOO_CLOSE: msg = _HKI( "Copper areas too close" ); break; + case DRCE_ZONE_HAS_EMPTY_NET: msg = _HKI( "Copper zone net has no pads" ); break; + case DRCE_DANGLING_VIA: msg = _HKI( "Via is not connected" ); break; + case DRCE_DANGLING_TRACK: msg = _HKI( "Track has unconnected end" ); break; + case DRCE_HOLE_NEAR_PAD: msg = _HKI( "Hole too close to pad" ); break; + case DRCE_HOLE_NEAR_TRACK: msg = _HKI( "Hole too close to track" ); break; + case DRCE_TOO_SMALL_TRACK_WIDTH: msg = _HKI( "Track width too small" ); break; + case DRCE_TOO_LARGE_TRACK_WIDTH: msg = _HKI( "Track width too large" ); break; + case DRCE_TOO_SMALL_VIA: msg = _HKI( "Via size too small" ); break; + case DRCE_TOO_SMALL_VIA_ANNULUS: msg = _HKI( "Via annulus too small" ); break; + case DRCE_TOO_SMALL_MICROVIA: msg = _HKI( "Micro via size too small" ); break; + case DRCE_TOO_SMALL_VIA_DRILL: msg = _HKI( "Via drill too small" ); break; + case DRCE_TOO_SMALL_PAD_DRILL: msg = _HKI( "Pad drill too small" ); break; + case DRCE_TOO_SMALL_MICROVIA_DRILL: msg = _HKI( "Micro via drill too small" ); break; + case DRCE_DRILLED_HOLES_TOO_CLOSE: msg = _HKI( "Drilled holes too close together" ); break; + case DRCE_TRACK_NEAR_EDGE: msg = _HKI( "Track too close to board edge" ); break; + case DRCE_VIA_NEAR_EDGE: msg = _HKI( "Via too close to board edge" ); break; + case DRCE_PAD_NEAR_EDGE: msg = _HKI( "Pad too close to board edge" ); break; + case DRCE_INVALID_OUTLINE: msg = _HKI( "Board has malformed outline" ); break; + + case DRCE_NETCLASS_TRACKWIDTH: msg = _HKI( "NetClass Track Width too small" ); break; + case DRCE_NETCLASS_CLEARANCE: msg = _HKI( "NetClass Clearance too small" ); break; + case DRCE_NETCLASS_VIAANNULUS: msg = _HKI( "NetClass via annulus too small" ); break; + case DRCE_NETCLASS_VIASIZE: msg = _HKI( "NetClass Via Dia too small" ); break; + case DRCE_NETCLASS_VIADRILLSIZE: msg = _HKI( "NetClass Via Drill too small" ); break; + case DRCE_NETCLASS_uVIASIZE: msg = _HKI( "NetClass uVia Dia too small" ); break; + case DRCE_NETCLASS_uVIADRILLSIZE: msg = _HKI( "NetClass uVia Drill too small" ); break; + + case DRCE_VIA_INSIDE_KEEPOUT: msg = _HKI( "Via inside keepout area" ); break; + case DRCE_MICROVIA_INSIDE_KEEPOUT: msg = _HKI( "Micro via inside keepout area" ); break; + case DRCE_BBVIA_INSIDE_KEEPOUT: msg = _HKI( "Buried via inside keepout area" ); break; + case DRCE_TRACK_INSIDE_KEEPOUT: msg = _HKI( "Track inside keepout area" ); break; + case DRCE_PAD_INSIDE_KEEPOUT: msg = _HKI( "Pad inside keepout area" ); break; + case DRCE_FOOTPRINT_INSIDE_KEEPOUT: msg = _HKI( "Footprint inside keepout area" ); break; + case DRCE_HOLE_INSIDE_KEEPOUT: msg = _HKI( "Hole inside keepout area" ); break; + case DRCE_TEXT_INSIDE_KEEPOUT: msg = _HKI( "Text inside keepout area" ); break; + case DRCE_GRAPHICS_INSIDE_KEEPOUT: msg = _HKI( "Graphic inside keepout area" ); break; + + case DRCE_VIA_NEAR_COPPER: msg = _HKI( "Via too close to copper item" ); break; + case DRCE_TRACK_NEAR_COPPER: msg = _HKI( "Track too close to copper item" ); break; + case DRCE_PAD_NEAR_COPPER: msg = _HKI( "Pad too close to copper item" ); break; + + case DRCE_OVERLAPPING_FOOTPRINTS: msg = _HKI( "Courtyards overlap" ); break; + case DRCE_MISSING_COURTYARD: msg = _HKI( "Footprint has no courtyard defined" ); break; + case DRCE_MALFORMED_COURTYARD: msg = _HKI( "Footprint has malformed courtyard" ); break; + case DRCE_PTH_IN_COURTYARD: msg = _HKI( "PTH inside courtyard" ); break; + case DRCE_NPTH_IN_COURTYARD: msg = _HKI( "NPTH inside courtyard" ); break; + + case DRCE_DUPLICATE_FOOTPRINT: msg = _HKI( "Duplicate footprints" ); break; + case DRCE_MISSING_FOOTPRINT: msg = _HKI( "Missing footprint" ); break; + case DRCE_EXTRA_FOOTPRINT: msg = _HKI( "Extra footprint" ); break; + + // For cleanup tracks and vias: + case CLEANUP_SHORT: msg = _HKI( "Remove track shorting two nets" ); break; + case CLEANUP_REDUNDANT_VIA: msg = _HKI( "Remove redundant via" ); break; + case CLEANUP_DUPLICATE_TRACK: msg = _HKI( "Remove duplicate track" ); break; + case CLEANUP_MERGE_TRACKS: msg = _HKI( "Merge co-linear tracks" ); break; + case CLEANUP_DANGLING_TRACK: msg = _HKI( "Remove dangling track" ); break; + case CLEANUP_DANGLING_VIA: msg = _HKI( "Remove dangling via" ); break; + case CLEANUP_ZERO_LENGTH_TRACK: msg = _HKI( "Remove zero-length track" ); break; + case CLEANUP_TRACK_IN_PAD: msg = _HKI( "Remove track inside pad" ); break; + + case DRCE_UNRESOLVED_VARIABLE: msg = _HKI( "Unresolved text variable" ); break; + + default: + wxFAIL_MSG( "Missing DRC error description" ); + msg = _HKI( "Unknown DRC violation" ); + break; + } + + if( aTranslate ) + return wxGetTranslation( msg ); + else + return msg; +} + + +wxString escapeHtml( wxString aString ) +{ + aString.Replace( wxT("<"), wxT("<") ); + aString.Replace( wxT(">"), wxT(">") ); + return aString; +} + + +wxString DRC_ITEM::ShowHtml( PCB_BASE_FRAME* aFrame ) const +{ + BOARD_ITEM* mainItem = nullptr; + BOARD_ITEM* auxItem = nullptr; + wxString msg = m_errorMessage.IsEmpty() ? GetErrorText( m_errorCode ) : m_errorMessage; + wxString mainText; + wxString auxText; + + if( m_mainItemUuid != niluuid ) + mainItem = aFrame->GetBoard()->GetItem( m_mainItemUuid ); + + if( m_auxItemUuid != niluuid ) + auxItem = aFrame->GetBoard()->GetItem( m_auxItemUuid ); + + if( mainItem ) + mainText = mainItem->GetSelectMenuText( aFrame->GetUserUnits() ); + + if( auxItem ) + auxText = auxItem->GetSelectMenuText( aFrame->GetUserUnits() ); + + if( mainItem && auxItem ) + { + // an html fragment for the entire message in the listbox. feel free + // to add color if you want: + return wxString::Format( wxT( "%s
   %s
   %s" ), + escapeHtml( msg ), + escapeHtml( mainText ), + escapeHtml( auxText ) ); + } + else if( mainItem ) + { + return wxString::Format( wxT( "%s
   %s" ), + escapeHtml( msg ), + escapeHtml( mainText ) ); + } + else + { + return wxString::Format( wxT( "%s" ), + escapeHtml( msg ) ); + } +} + + diff --git a/qa/drc_proto/drc_item.h b/qa/drc_proto/drc_item.h new file mode 100644 index 0000000000..a4ca49d0c1 --- /dev/null +++ b/qa/drc_proto/drc_item.h @@ -0,0 +1,69 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2007 Dick Hollenbeck, dick@softplc.com + * Copyright (C) 2018-2020 KiCad Developers, see AUTHORS.txt for contributors. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#ifndef DRC_ITEM_PROTO_H +#define DRC_ITEM_PROTO_H + +namespace test { + +#include +#include +#include +#include +#include +#include + +#include "drc_engine.h" + +class DRC_RULE; + +class DRC_ITEM : public RC_ITEM +{ +public: + DRC_ITEM( int aErrorCode ) {}// fixme + + DRC_ITEM( const wxString& aErrorText ) {} // fixme + + /** + * Function GetErrorText + * returns the string form of a drc error code. + */ + ::wxString GetErrorText( int aErrorCode = -1, bool aTranslate = true ) const override { return ""; } // fixme + + /** + * Function ShowHtml + * translates this object into a fragment of HTML suitable for the wxHtmlListBox class. + * @return wxString - the html text. + */ + ::wxString FormatHtml( ) const { return ""; } // fixme + +protected: + + DRC_RULE *m_violatingRule; + //std::vector m_violatingObjects; +}; + +}; + +#endif // DRC_ITEM_H diff --git a/qa/drc_proto/drc_keepout_tester.cpp b/qa/drc_proto/drc_keepout_tester.cpp new file mode 100644 index 0000000000..fb36df85d0 --- /dev/null +++ b/qa/drc_proto/drc_keepout_tester.cpp @@ -0,0 +1,351 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2020 KiCad Developers, see AUTHORS.txt for contributors. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + + +#include + +#include +#include + + +DRC_KEEPOUT_TESTER::DRC_KEEPOUT_TESTER( MARKER_HANDLER aMarkerHandler ) : + DRC_TEST_PROVIDER( std::move( aMarkerHandler ) ), + m_units( EDA_UNITS::MILLIMETRES ), + m_board( nullptr ), + m_zone( nullptr ), + m_keepoutFlags( 0 ) +{ +} + + +bool DRC_KEEPOUT_TESTER::RunDRC( EDA_UNITS aUnits, BOARD& aBoard ) +{ + bool success = true; + + m_units = aUnits; + m_board = &aBoard; + + // Get a list of all zones to inspect, from both board and footprints + std::list areasToInspect = m_board->GetZoneList( true ); + + // Test keepout areas for vias, tracks and pads inside keepout areas + for( ZONE_CONTAINER* area : areasToInspect ) + { + m_keepoutFlags = area->GetKeepouts( &m_sources ); + + if( m_keepoutFlags > 0 ) + { + m_zone = area; + m_zoneBBox = area->GetBoundingBox(); + + success &= checkTracksAndVias(); + success &= checkFootprints(); + success &= checkDrawings(); + } + } + + return success; +} + + +bool DRC_KEEPOUT_TESTER::checkTracksAndVias() +{ + constexpr int VIA_MASK = DISALLOW_VIAS | DISALLOW_MICRO_VIAS | DISALLOW_BB_VIAS; + constexpr int CHECK_VIAS_MASK = VIA_MASK | DISALLOW_HOLES; + constexpr int CHECK_TRACKS_AND_VIAS_MASK = CHECK_VIAS_MASK | DISALLOW_TRACKS; + + if(( m_keepoutFlags & CHECK_TRACKS_AND_VIAS_MASK ) == 0 ) + return true; + + bool success = true; + + for( TRACK* segm : m_board->Tracks() ) + { + if( !m_zoneBBox.Intersects( segm->GetBoundingBox() ) ) + continue; + + if( segm->Type() == PCB_TRACE_T && ( m_keepoutFlags & DISALLOW_TRACKS ) != 0 ) + { + // Ignore if the keepout zone is not on the same layer + if( !m_zone->IsOnLayer( segm->GetLayer() ) ) + continue; + + int widths = segm->GetWidth() / 2; + SEG trackSeg( segm->GetStart(), segm->GetEnd() ); + SEG::ecoord center2center_squared = m_zone->Outline()->SquaredDistance( trackSeg ); + + if( center2center_squared <= SEG::Square( widths) ) + { + DRC_ITEM* drcItem = new DRC_ITEM( DRCE_TRACK_INSIDE_KEEPOUT ); + + m_msg.Printf( drcItem->GetErrorText() + _( " (%s)" ), + m_sources.at(DISALLOW_TRACKS ) ); + + drcItem->SetErrorMessage( m_msg ); + drcItem->SetItems( segm, m_zone ); + + HandleMarker( new MARKER_PCB( drcItem, DRC::GetLocation( segm, m_zone ) ) ); + success = false; + } + } + else if( segm->Type() == PCB_VIA_T && ( m_keepoutFlags & CHECK_VIAS_MASK ) != 0 ) + { + VIA* via = static_cast( segm ); + int errorCode = 0; + int sourceId = 0; + + if( ( m_keepoutFlags & DISALLOW_VIAS ) > 0 ) + { + errorCode = DRCE_VIA_INSIDE_KEEPOUT; + sourceId = DISALLOW_VIAS; + } + else if( via->GetViaType() == VIATYPE::MICROVIA + && ( m_keepoutFlags & DISALLOW_MICRO_VIAS ) > 0 ) + { + errorCode = DRCE_MICROVIA_INSIDE_KEEPOUT; + sourceId = DISALLOW_MICRO_VIAS; + } + else if( via->GetViaType() == VIATYPE::BLIND_BURIED + && ( m_keepoutFlags & DISALLOW_BB_VIAS ) > 0 ) + { + errorCode = DRCE_BBVIA_INSIDE_KEEPOUT; + sourceId = DISALLOW_BB_VIAS; + } + else if( ( m_keepoutFlags & DISALLOW_HOLES ) > 0 ) + { + errorCode = DRCE_HOLE_INSIDE_KEEPOUT; + sourceId = DISALLOW_HOLES; + } + else + continue; + + int widths = via->GetWidth() / 2; + wxPoint viaPos = via->GetPosition(); + + if( errorCode == DRCE_HOLE_INSIDE_KEEPOUT ) + widths = via->GetDrillValue() / 2; + + SEG::ecoord center2center_squared = m_zone->Outline()->SquaredDistance( viaPos ); + + if( center2center_squared <= SEG::Square( widths ) ) + { + DRC_ITEM* drcItem = new DRC_ITEM( errorCode ); + m_msg.Printf( drcItem->GetErrorText() + _( " (%s)" ), m_sources.at( sourceId ) ); + drcItem->SetErrorMessage( m_msg ); + drcItem->SetItems( segm, m_zone ); + + HandleMarker( new MARKER_PCB( drcItem, DRC::GetLocation( segm, m_zone ) ) ); + success = false; + } + } + } + + return success; +} + + +bool DRC_KEEPOUT_TESTER::checkFootprints() +{ + constexpr int CHECK_PADS_MASK = DISALLOW_PADS | DISALLOW_HOLES; + constexpr int CHECK_FOOTPRINTS_MASK = CHECK_PADS_MASK | DISALLOW_FOOTPRINTS; + + if(( m_keepoutFlags & CHECK_FOOTPRINTS_MASK ) == 0 ) + return true; + + bool success = true; + + for( MODULE* fp : m_board->Modules() ) + { + if( !m_zoneBBox.Intersects( fp->GetBoundingBox() ) ) + continue; + + if( ( m_keepoutFlags & DISALLOW_FOOTPRINTS ) > 0 + && ( fp->IsFlipped() ? m_zone->CommonLayerExists( LSET::BackMask() ) + : m_zone->CommonLayerExists( LSET::FrontMask() ) ) ) + { + SHAPE_POLY_SET poly; + + if( fp->BuildPolyCourtyard() ) + poly = fp->IsFlipped() ? fp->GetPolyCourtyardBack() : fp->GetPolyCourtyardFront(); + + if( poly.OutlineCount() == 0 ) + poly = fp->GetBoundingPoly(); + + // Build the common area between footprint and the keepout area: + poly.BooleanIntersection( *m_zone->Outline(), SHAPE_POLY_SET::PM_FAST ); + + // If it's not empty then we have a violation + if( poly.OutlineCount() ) + { + const VECTOR2I& pt = poly.CVertex( 0, 0, -1 ); + DRC_ITEM* drcItem = new DRC_ITEM( DRCE_FOOTPRINT_INSIDE_KEEPOUT ); + + m_msg.Printf( drcItem->GetErrorText() + _( " (%s)" ), + m_sources.at( DISALLOW_FOOTPRINTS ) ); + + drcItem->SetErrorMessage( m_msg ); + drcItem->SetItems( fp, m_zone ); + + HandleMarker( new MARKER_PCB( drcItem, (wxPoint) pt ) ); + success = false; + } + } + + if( ( m_keepoutFlags & CHECK_PADS_MASK ) > 0 ) + { + success &= checkPads( fp ); + } + } + + return success; +} + + +bool DRC_KEEPOUT_TESTER::checkPads( MODULE* aModule ) +{ + bool success = true; + + for( D_PAD* pad : aModule->Pads() ) + { + if( !m_zone->CommonLayerExists( pad->GetLayerSet() ) ) + continue; + + // Fast test to detect a pad inside the keepout area bounding box. + EDA_RECT padBBox( pad->ShapePos(), wxSize() ); + padBBox.Inflate( pad->GetBoundingRadius() ); + + if( !m_zoneBBox.Intersects( padBBox ) ) + continue; + + if( ( m_keepoutFlags & DISALLOW_PADS ) > 0 ) + { + SHAPE_POLY_SET outline; + pad->TransformShapeWithClearanceToPolygon( outline, 0 ); + + // Build the common area between pad and the keepout area: + outline.BooleanIntersection( *m_zone->Outline(), SHAPE_POLY_SET::PM_FAST ); + + // If it's not empty then we have a violation + if( outline.OutlineCount() ) + { + const VECTOR2I& pt = outline.CVertex( 0, 0, -1 ); + DRC_ITEM* drcItem = new DRC_ITEM( DRCE_PAD_INSIDE_KEEPOUT ); + + m_msg.Printf( drcItem->GetErrorText() + _( " (%s)" ), + m_sources.at( DISALLOW_PADS ) ); + + drcItem->SetErrorMessage( m_msg ); + drcItem->SetItems( pad, m_zone ); + + HandleMarker( new MARKER_PCB( drcItem, (wxPoint) pt ) ); + success = false; + } + } + else if( ( m_keepoutFlags & DISALLOW_HOLES ) > 0 ) + { + wxPoint slotStart, slotEnd; + int slotWidth; + + pad->GetOblongGeometry( pad->GetDrillSize(), &slotStart, &slotEnd, &slotWidth ); + slotStart += pad->GetPosition(); + slotEnd += pad->GetPosition(); + + SEG slotSeg( slotStart, slotEnd ); + SHAPE_POLY_SET* outline = const_cast( &m_zone->GetFilledPolysList() ); + SEG::ecoord center2center_sq = outline->SquaredDistance( slotSeg ); + + if( center2center_sq <= SEG::Square( slotWidth) ) + { + DRC_ITEM* drcItem = new DRC_ITEM( DRCE_HOLE_INSIDE_KEEPOUT ); + + m_msg.Printf( drcItem->GetErrorText() + _( " (%s)" ), + m_sources.at( DISALLOW_HOLES ) ); + + drcItem->SetErrorMessage( m_msg ); + drcItem->SetItems( pad, m_zone ); + + HandleMarker( new MARKER_PCB( drcItem, pad->GetPosition() ) ); + success = false; + } + } + } + + return success; +} + + +bool DRC_KEEPOUT_TESTER::checkDrawings() +{ + constexpr int CHECK_DRAWINGS_MASK = DISALLOW_TEXTS | DISALLOW_GRAPHICS; + constexpr KICAD_T graphicTypes[] = { PCB_LINE_T, PCB_DIMENSION_T, PCB_TARGET_T, EOT }; + + if(( m_keepoutFlags & CHECK_DRAWINGS_MASK ) == 0 ) + return true; + + bool success = true; + + for( BOARD_ITEM* drawing : m_board->Drawings() ) + { + if( !m_zoneBBox.Intersects( drawing->GetBoundingBox() ) ) + continue; + + int errorCode = 0; + int sourceId = 0; + + if( drawing->IsType( graphicTypes ) && ( m_keepoutFlags & DISALLOW_GRAPHICS ) > 0 ) + { + errorCode = DRCE_GRAPHICS_INSIDE_KEEPOUT; + sourceId = DISALLOW_GRAPHICS; + } + else if( drawing->Type() == PCB_TEXT_T && ( m_keepoutFlags & DISALLOW_TEXTS ) > 0 ) + { + errorCode = DRCE_TEXT_INSIDE_KEEPOUT; + sourceId = DISALLOW_TEXTS; + } + else + continue; + + SHAPE_POLY_SET poly; + drawing->TransformShapeWithClearanceToPolygon( poly, 0 ); + + // Build the common area between footprint and the keepout area: + poly.BooleanIntersection( *m_zone->Outline(), SHAPE_POLY_SET::PM_FAST ); + + // If it's not empty then we have a violation + if( poly.OutlineCount() ) + { + const VECTOR2I& pt = poly.CVertex( 0, 0, -1 ); + DRC_ITEM* drcItem = new DRC_ITEM( errorCode ); + m_msg.Printf( drcItem->GetErrorText() + _( " (%s)" ), m_sources.at( sourceId ) ); + drcItem->SetErrorMessage( m_msg ); + drcItem->SetItems( drawing, m_zone ); + + HandleMarker( new MARKER_PCB( drcItem, (wxPoint) pt ) ); + success = false; + } + } + + return success; +} + + diff --git a/qa/drc_proto/drc_keepout_tester.h b/qa/drc_proto/drc_keepout_tester.h new file mode 100644 index 0000000000..c7cdf93019 --- /dev/null +++ b/qa/drc_proto/drc_keepout_tester.h @@ -0,0 +1,61 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2020 KiCad Developers, see change_log.txt for contributors. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + + +#ifndef DRC_KEEPOUT_TESTER__H +#define DRC_KEEPOUT_TESTER__H + +#include + + +class BOARD; + + +class DRC_KEEPOUT_TESTER : public DRC_TEST_PROVIDER +{ +public: + DRC_KEEPOUT_TESTER( MARKER_HANDLER aMarkerHandler ); + + virtual ~DRC_KEEPOUT_TESTER() {}; + + bool RunDRC( EDA_UNITS aUnits, BOARD& aBoard ) override; + +private: + bool checkTracksAndVias(); + bool checkFootprints(); + bool checkPads( MODULE* aModule ); + bool checkDrawings(); + +private: + EDA_UNITS m_units; + BOARD* m_board; + + // Temp variables for use while testing: + ZONE_CONTAINER* m_zone; + EDA_RECT m_zoneBBox; + int m_keepoutFlags; // bitset of DISALLOW_* flags + std::map m_sources; // map of DISALLOW_* flag to source + wxString m_msg; // avoid lots of calls to wxString's c'tor. +}; + +#endif // DRC_KEEPOUT_TESTER__H diff --git a/qa/drc_proto/drc_marker_pcb.h b/qa/drc_proto/drc_marker_pcb.h new file mode 100644 index 0000000000..54339c5310 --- /dev/null +++ b/qa/drc_proto/drc_marker_pcb.h @@ -0,0 +1,117 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2009-2018 Jean-Pierre Charras, jean-pierre.charras@ujf-grenoble.fr + * Copyright (C) 1992-2018 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 + */ + +/** + * @file class_marker_pcb.h + * @brief Markers used to show a drc problem on boards. + */ + +#ifndef CLASS_MARKER_PCB_H +#define CLASS_MARKER_PCB_H + + +#include +#include + +class DRC_ITEM; + +// Coordinates count for the basic shape marker +#define MARKER_SHAPE_POINT_COUNT 9 + +class MSG_PANEL_ITEM; + + +class MARKER_PCB : public BOARD_ITEM, public MARKER_BASE +{ +public: + MARKER_PCB( DRC_ITEM* aItem, const wxPoint& aPosition ); + + ~MARKER_PCB(); + + static inline bool ClassOf( const EDA_ITEM* aItem ) + { + return aItem && PCB_MARKER_T == aItem->Type(); + } + + const KIID GetUUID() const override { return m_Uuid; } + + wxString Serialize() const; + + static MARKER_PCB* Deserialize( const wxString& data ); + + void Move(const wxPoint& aMoveVector) override + { + m_Pos += aMoveVector; + } + + void Rotate( const wxPoint& aRotCentre, double aAngle ) override; + + void Flip( const wxPoint& aCentre, bool aFlipLeftRight ) override; + + wxPoint GetPosition() const override { return m_Pos; } + void SetPosition( const wxPoint& aPos ) override { m_Pos = aPos; } + + bool HitTest( const wxPoint& aPosition, int aAccuracy = 0 ) const override + { + return HitTestMarker( aPosition, aAccuracy ); + } + + bool IsOnLayer( PCB_LAYER_ID aLayer ) const override; + + GAL_LAYER_ID GetColorLayer() const; + + void GetMsgPanelInfo( EDA_DRAW_FRAME* aFrame, std::vector& aList ) override; + + bool Matches( wxFindReplaceData& aSearchData, void* aAuxData ) override + { + return BOARD_ITEM::Matches( m_rcItem->GetErrorMessage(), aSearchData ); + } + + wxString GetSelectMenuText( EDA_UNITS aUnits ) const override; + + BITMAP_DEF GetMenuImage() const override; + + const BOX2I ViewBBox() const override; + + const EDA_RECT GetBoundingBox() const override; + + void ViewGetLayers( int aLayers[], int& aCount ) const override; + +#if defined(DEBUG) + void Show( int nestLevel, std::ostream& os ) const override { ShowDummy( os ); } +#endif + + /** Get class name + * @return string "MARKER_PCB" + */ + virtual wxString GetClass() const override + { + return wxT( "MARKER_PCB" ); + } + +protected: + KIGFX::COLOR4D getColor() const override; +}; + +#endif // CLASS_MARKER_PCB_H diff --git a/qa/drc_proto/drc_netclass_tester.cpp b/qa/drc_proto/drc_netclass_tester.cpp new file mode 100644 index 0000000000..6d2ad68078 --- /dev/null +++ b/qa/drc_proto/drc_netclass_tester.cpp @@ -0,0 +1,162 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2020 KiCad Developers, see AUTHORS.txt for contributors. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + + +#include + + +DRC_NETCLASS_TESTER::DRC_NETCLASS_TESTER( MARKER_HANDLER aMarkerHandler ) : + DRC_TEST_PROVIDER( std::move( aMarkerHandler ) ), + m_units( EDA_UNITS::MILLIMETRES ), + m_board( nullptr ) +{ +} + + +bool DRC_NETCLASS_TESTER::RunDRC( EDA_UNITS aUnits, BOARD& aBoard ) +{ + m_units = aUnits; + m_board = &aBoard; + + bool success = true; + NETCLASSES& netclasses = m_board->GetDesignSettings().m_NetClasses; + + success &= checkNetClass( netclasses.GetDefault() ); + + for( NETCLASSES::const_iterator i = netclasses.begin(); i != netclasses.end(); ++i ) + success &= checkNetClass( i->second ); + + return success; +} + + +bool DRC_NETCLASS_TESTER::checkNetClass( const NETCLASSPTR& nc ) +{ + bool ret = true; + + const BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings(); + + if( nc->GetClearance() < bds.m_MinClearance ) + { + DRC_ITEM* drcItem = new DRC_ITEM( DRCE_NETCLASS_CLEARANCE ); + + m_msg.Printf( drcItem->GetErrorText() + _( " (board minimum %s; %s netclass %s)" ), + MessageTextFromValue( m_units, bds.m_MinClearance, true ), + nc->GetName(), + MessageTextFromValue( m_units, nc->GetClearance(), true ) ); + + drcItem->SetErrorMessage( m_msg ); + HandleMarker( new MARKER_PCB( drcItem, wxPoint() ) ); + ret = false; + } + + if( nc->GetTrackWidth() < bds.m_TrackMinWidth ) + { + DRC_ITEM* drcItem = new DRC_ITEM( DRCE_NETCLASS_TRACKWIDTH ); + + m_msg.Printf( drcItem->GetErrorText() + _( " (board minimum %s; %s netclass %s)" ), + MessageTextFromValue( m_units, bds.m_TrackMinWidth, true ), + nc->GetName(), + MessageTextFromValue( m_units, nc->GetTrackWidth(), true ) ); + + drcItem->SetErrorMessage( m_msg ); + HandleMarker( new MARKER_PCB( drcItem, wxPoint() ) ); + ret = false; + } + + if( nc->GetViaDiameter() < bds.m_ViasMinSize ) + { + DRC_ITEM* drcItem = new DRC_ITEM( DRCE_NETCLASS_VIASIZE ); + + m_msg.Printf( drcItem->GetErrorText() + _( " (board minimum %s; %s netclass %s)" ), + MessageTextFromValue( m_units, bds.m_ViasMinSize, true ), + nc->GetName(), + MessageTextFromValue( m_units, nc->GetViaDiameter(), true ) ); + + drcItem->SetErrorMessage( m_msg ); + HandleMarker( new MARKER_PCB( drcItem, wxPoint() ) ); + ret = false; + } + + if( nc->GetViaDrill() < bds.m_MinThroughDrill ) + { + DRC_ITEM* drcItem = new DRC_ITEM( DRCE_NETCLASS_VIADRILLSIZE ); + + m_msg.Printf( drcItem->GetErrorText() + _( " (board min through hole %s; %s netclass %s)" ), + MessageTextFromValue( m_units, bds.m_MinThroughDrill, true ), + nc->GetName(), + MessageTextFromValue( m_units, nc->GetViaDrill(), true ) ); + + drcItem->SetErrorMessage( m_msg ); + HandleMarker( new MARKER_PCB( drcItem, wxPoint() ) ); + ret = false; + } + + int ncViaAnnulus = ( nc->GetViaDiameter() - nc->GetViaDrill() ) / 2; + + if( ncViaAnnulus < bds.m_ViasMinAnnulus ) + { + DRC_ITEM* drcItem = new DRC_ITEM( DRCE_NETCLASS_VIAANNULUS ); + + m_msg.Printf( drcItem->GetErrorText() + _( " (board minimum %s; %s netclass %s)" ), + MessageTextFromValue( m_units, bds.m_ViasMinAnnulus, true ), + nc->GetName(), + MessageTextFromValue( m_units, ncViaAnnulus, true ) ); + + drcItem->SetErrorMessage( m_msg ); + HandleMarker( new MARKER_PCB( drcItem, wxPoint() ) ); + ret = false; + } + + if( nc->GetuViaDiameter() < bds.m_MicroViasMinSize ) + { + DRC_ITEM* drcItem = new DRC_ITEM( DRCE_NETCLASS_uVIASIZE ); + + m_msg.Printf( drcItem->GetErrorText() + _( " (board minimum %s; %s netclass %s)" ), + MessageTextFromValue( m_units, bds.m_MicroViasMinSize, true ), + nc->GetName(), + MessageTextFromValue( m_units, nc->GetuViaDiameter(), true ) ); + + drcItem->SetErrorMessage( m_msg ); + HandleMarker( new MARKER_PCB( drcItem, wxPoint() ) ); + ret = false; + } + + if( nc->GetuViaDrill() < bds.m_MicroViasMinDrill ) + { + DRC_ITEM* drcItem = new DRC_ITEM( DRCE_NETCLASS_uVIADRILLSIZE ); + + m_msg.Printf( drcItem->GetErrorText() + _( " (board minimum %s; %s netclass %s)" ), + MessageTextFromValue( m_units, bds.m_MicroViasMinDrill, true ), + nc->GetName(), + MessageTextFromValue( m_units, nc->GetuViaDrill(), true ) ); + + drcItem->SetErrorMessage( m_msg ); + HandleMarker( new MARKER_PCB( drcItem, wxPoint() ) ); + ret = false; + } + + return ret; +} + + diff --git a/qa/drc_proto/drc_netclass_tester.h b/qa/drc_proto/drc_netclass_tester.h new file mode 100644 index 0000000000..ed345df6cc --- /dev/null +++ b/qa/drc_proto/drc_netclass_tester.h @@ -0,0 +1,54 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2020 KiCad Developers, see change_log.txt for contributors. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + + +#ifndef DRC_NETCLASS_TESTER__H +#define DRC_NETCLASS_TESTER__H + +#include + + +class BOARD; +class BOARD_ITEM; + + +class DRC_NETCLASS_TESTER : public DRC_TEST_PROVIDER +{ +public: + DRC_NETCLASS_TESTER( MARKER_HANDLER aMarkerHandler ); + + virtual ~DRC_NETCLASS_TESTER() {}; + + bool RunDRC( EDA_UNITS aUnits, BOARD& aBoard ) override; + +private: + bool checkNetClass( const NETCLASSPTR& nc ); + +private: + EDA_UNITS m_units; + BOARD* m_board; + + wxString m_msg; // Construct only once for performance +}; + +#endif // DRC_NETCLASS_TESTER__H diff --git a/qa/drc_proto/drc_proto_test.cpp b/qa/drc_proto/drc_proto_test.cpp new file mode 100644 index 0000000000..7f9304a0fd --- /dev/null +++ b/qa/drc_proto/drc_proto_test.cpp @@ -0,0 +1,29 @@ +#include + +#include +#include + +#include + +#include +#include + +//#include + +int main( int argc, char *argv[] ) +{ + auto brd = KI_TEST::ReadBoardFromFileOrStream(argv[1]); + + test::DRC_ENGINE drcEngine( brd.get(), &brd->GetDesignSettings() ); + + try { + drcEngine.LoadRules( wxString( argv[2] ) ); + } catch( PARSE_ERROR& err ) + { + printf("Exception %s\n", (const char*) err.What().c_str() ); + } + + drcEngine.RunTests(); + + return 0; +} \ No newline at end of file diff --git a/qa/drc_proto/drc_provider.h b/qa/drc_proto/drc_provider.h new file mode 100644 index 0000000000..ebd67faa75 --- /dev/null +++ b/qa/drc_proto/drc_provider.h @@ -0,0 +1,286 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2018 KiCad Developers, see change_log.txt for contributors. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + + +#ifndef DRC_PROVIDER__H +#define DRC_PROVIDER__H + +#include +#include + +#include + +namespace test { + +class DRC_ENGINE; + +/** + * DRC_TEST_PROVIDER + * is a base class that represents a DRC "provider" which runs some DRC functions over a + * #BOARD and spits out #PCB_MARKERs as needed. + */ +class DRC_TEST_PROVIDER +{ +public: + DRC_TEST_PROVIDER ( DRC_ENGINE *aDrc ); + virtual ~DRC_TEST_PROVIDER() {} + + /** + * Runs this provider against the given PCB with configured options (if any). + * + * Note: Board is non-const, as some DRC functions modify the board (e.g. zone fill + * or polygon coalescing) + */ + virtual bool Run() = 0; + + virtual void Enable( bool enable ); + virtual bool IsEnabled() const; + + virtual const wxString GetName() const; + virtual const wxString GetDescription() const; + + virtual std::set GetMatchingRuleIds() const; + + /** + * Pass a given marker to the marker handler + */ + virtual void HandleMarker( MARKER_PCB* aMarker ) const; + +private: + + DRC_ENGINE *m_drcEngine; + +}; + + +#if 0 +/** + * BOARD_DRC_ITEMS_PROVIDER + * is an implementation of the RC_ITEMS_PROVIDER interface which uses a BOARD instance + * to fulfill the interface. + */ +class BOARD_DRC_ITEMS_PROVIDER : public RC_ITEMS_PROVIDER +{ +private: + BOARD* m_board; + + int m_severities; + std::vector m_filteredMarkers; + +public: + BOARD_DRC_ITEMS_PROVIDER( BOARD* aBoard ) : + m_board( aBoard ), + m_severities( 0 ) + { + } + + void SetSeverities( int aSeverities ) override + { + m_severities = aSeverities; + + BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings(); + + m_filteredMarkers.clear(); + + for( MARKER_PCB* marker : m_board->Markers() ) + { + int markerSeverity; + + if( marker->IsExcluded() ) + markerSeverity = RPT_SEVERITY_EXCLUSION; + else + markerSeverity = bds.GetSeverity( marker->GetRCItem()->GetErrorCode() ); + + if( markerSeverity & m_severities ) + m_filteredMarkers.push_back( marker ); + } + } + + int GetCount( int aSeverity = -1 ) override + { + if( aSeverity < 0 ) + return m_filteredMarkers.size(); + + BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings(); + + int count = 0; + + for( MARKER_PCB* marker : m_board->Markers() ) + { + int markerSeverity; + + if( marker->IsExcluded() ) + markerSeverity = RPT_SEVERITY_EXCLUSION; + else + markerSeverity = bds.GetSeverity( marker->GetRCItem()->GetErrorCode() ); + + if( markerSeverity == aSeverity ) + count++; + } + + return count; + } + + DRC_ITEM* GetItem( int aIndex ) override + { + MARKER_PCB* marker = m_filteredMarkers[ aIndex ]; + + return marker ? static_cast( marker->GetRCItem() ) : nullptr; + } + + void DeleteItem( int aIndex, bool aDeep ) override + { + MARKER_PCB* marker = m_filteredMarkers[ aIndex ]; + m_filteredMarkers.erase( m_filteredMarkers.begin() + aIndex ); + + if( aDeep ) + m_board->Delete( marker ); + } + + void DeleteAllItems() override + { + m_board->DeleteMARKERs(); + m_filteredMarkers.clear(); + } +}; + + +/** + * VECTOR_DRC_ITEMS_PROVIDER + * is an implementation of the interface named DRC_ITEMS_PROVIDER which uses a vector + * of pointers to DRC_ITEMs to fulfill the interface. No ownership is taken of the + * vector. + */ +class VECTOR_DRC_ITEMS_PROVIDER : public RC_ITEMS_PROVIDER +{ + PCB_BASE_FRAME* m_frame; + std::vector* m_sourceVector; // owns its DRC_ITEMs + + int m_severities; + std::vector m_filteredVector; // does not own its DRC_ITEMs + +public: + + VECTOR_DRC_ITEMS_PROVIDER( PCB_BASE_FRAME* aFrame, std::vector* aList ) : + m_frame( aFrame ), + m_sourceVector( aList ), + m_severities( 0 ) + { + } + + void SetSeverities( int aSeverities ) override + { + m_severities = aSeverities; + + BOARD_DESIGN_SETTINGS& bds = m_frame->GetBoard()->GetDesignSettings(); + + m_filteredVector.clear(); + + if( m_sourceVector ) + { + for( DRC_ITEM* item : *m_sourceVector ) + { + if( bds.GetSeverity( item->GetErrorCode() ) & aSeverities ) + m_filteredVector.push_back( item ); + } + } + } + + int GetCount( int aSeverity = -1 ) override + { + if( aSeverity < 0 ) + return m_filteredVector.size(); + + int count = 0; + BOARD_DESIGN_SETTINGS& bds = m_frame->GetBoard()->GetDesignSettings(); + + if( m_sourceVector ) + { + for( DRC_ITEM* item : *m_sourceVector ) + { + if( bds.GetSeverity( item->GetErrorCode() ) == aSeverity ) + count++; + } + } + + return count; + } + + DRC_ITEM* GetItem( int aIndex ) override + { + return (m_filteredVector)[aIndex]; + } + + void DeleteItem( int aIndex, bool aDeep ) override + { + DRC_ITEM* item = m_filteredVector[aIndex]; + m_filteredVector.erase( m_filteredVector.begin() + aIndex ); + + if( aDeep ) + { + for( size_t i = 0; i < m_sourceVector->size(); ++i ) + { + if( m_sourceVector->at( i ) == item ) + { + delete item; + m_sourceVector->erase( m_sourceVector->begin() + i ); + break; + } + } + } + } + + void DeleteAllItems() override + { + if( m_sourceVector ) + { + for( DRC_ITEM* item : *m_sourceVector ) + delete item; + + m_sourceVector->clear(); + } + + m_filteredVector.clear(); // no ownership of DRC_ITEM pointers + } +}; + + +/** + * RATSNEST_DRC_ITEMS_PROVIDER + */ +class RATSNEST_DRC_ITEMS_PROVIDER : public VECTOR_DRC_ITEMS_PROVIDER +{ + // TODO: for now this is just a vector, but we need to map it to some board-level + // data-structure so that deleting/excluding things can do a deep delete/exclusion + // which will be reflected in the ratsnest.... +public: + RATSNEST_DRC_ITEMS_PROVIDER( PCB_BASE_FRAME* aFrame, std::vector* aList ) : + VECTOR_DRC_ITEMS_PROVIDER( aFrame, aList ) + { } +}; + +#endif + +}; + +#endif // DRC_PROVIDER__H diff --git a/qa/drc_proto/drc_rule.cpp b/qa/drc_proto/drc_rule.cpp new file mode 100644 index 0000000000..b6fafb8a83 --- /dev/null +++ b/qa/drc_proto/drc_rule.cpp @@ -0,0 +1,82 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2020 KiCad Developers, see change_log.txt for contributors. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + + +#include +#include +#include +#include + + +#include +#include + + +test::DRC_RULE::DRC_RULE() +{ + +} + +test::DRC_RULE::~DRC_RULE() +{ + +} + +test::DRC_RULE_CONDITION::DRC_RULE_CONDITION() +{ + if( m_ucode ) + delete m_ucode; +} + +test::DRC_RULE_CONDITION::~DRC_RULE_CONDITION() +{ + +} + +bool test::DRC_RULE_CONDITION::EvaluateFor( BOARD_ITEM* aItemA, BOARD_ITEM* aItemB ) +{ + +} + +bool test::DRC_RULE_CONDITION::Compile() +{ + PCB_EXPR_COMPILER compiler; + if (!m_ucode) + m_ucode = new PCB_EXPR_UCODE; + + bool ok = compiler.Compile( (const char*) m_Expression.c_str(), m_ucode ); + + if( ok ) + return true; + + m_compileError = compiler.GetErrorStatus(); + + printf("Fail: %s", m_compileError.Format().c_str() ); + + return false; +} + +LIBEVAL::ERROR_STATUS test::DRC_RULE_CONDITION::GetCompilationError() +{ + return m_compileError; +} diff --git a/qa/drc_proto/drc_rule.h b/qa/drc_proto/drc_rule.h new file mode 100644 index 0000000000..b65fd67497 --- /dev/null +++ b/qa/drc_proto/drc_rule.h @@ -0,0 +1,139 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2020 KiCad Developers, see change_log.txt for contributors. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#ifndef DRC_RULE_PROTO_H +#define DRC_RULE_PROTO_H + +#include +#include +#include +#include + +class BOARD_ITEM; + +namespace LIBEVAL +{ +class UCODE; +class ERROR_STATUS; +}; + +namespace test +{ + +enum class DRC_RULE_ID_T { + DRC_RULE_ID_CLEARANCE = 0 +}; + +enum class DRC_RULE_SEVERITY_T { + DRC_SEVERITY_IGNORE = 0, + DRC_SEVERITY_WARNING, + DRC_SEVERITY_ERROR +}; + +template +class MINOPTMAX +{ +public: + T Min() const { assert( m_hasMin ); return m_min; }; + T Max() const { assert( m_hasMax ); return m_max; }; + T Opt() const { assert( m_hasOpt ); return m_opt; }; + + void SetMin( T v ) { m_min = v; m_hasMin = true; } + void SetMax( T v ) { m_max = v; m_hasMax = true; } + void SetOpt( T v ) { m_opt = v; m_hasOpt = true; } + +private: + T m_min; + T m_opt; + T m_max; + bool m_hasMin = false; + bool m_hasOpt = false; + bool m_hasMax = false; +}; + +class DRC_CONSTRAINT +{ + public: + MINOPTMAX m_Value; + bool m_Allow; +}; + +class DRC_RULE +{ +public: + DRC_RULE(); + virtual ~DRC_RULE(); + + virtual bool IsImplicit() const { return false; }; + virtual bool AppliesTo( const BOARD_ITEM* a, const BOARD_ITEM* b = nullptr ) const { return true; }; + virtual bool IsEnabled() const { return m_Enabled; } + + virtual bool HasSpecificItemSet() const { return false; }; + virtual void FillSpecificItemSet( std::vector specificItems ) { }; + + int GetPriority() const { return m_Priority; } + DRC_RULE_SEVERITY_T GetSeverity() const { return m_Severity; } + + const wxString GetName() const { return m_Name; } + const wxString GetTestProviderName() const { return m_TestProviderName; } + + const DRC_CONSTRAINT& GetConstraint() const { return m_Constraint; } + +public: + bool m_Unary; + + wxString m_Name; + wxString m_TestProviderName; + DRC_RULE_SEVERITY_T m_Severity; + bool m_Enabled; + bool m_Conditional; + int m_Priority; // 0 indicates automatic priority generation + + DRC_CONSTRAINT m_Constraint; +}; + +class DRC_RULE_CONDITION +{ +public: + DRC_RULE_CONDITION(); + ~DRC_RULE_CONDITION(); + + bool EvaluateFor( BOARD_ITEM* aItemA, BOARD_ITEM* aItemB ); + bool Compile(); + LIBEVAL::ERROR_STATUS GetCompilationError(); + +public: + wxString m_Expression; + wxString m_TargetRuleName; + +private: + LIBEVAL::ERROR_STATUS m_compileError; + LIBEVAL::UCODE* m_ucode; +}; + + +//DRC_RULE* GetRule( const BOARD_ITEM* aItem, const BOARD_ITEM* bItem, int aConstraint ); + +}; // namespace test + +#endif // DRC_RULE_H diff --git a/qa/drc_proto/drc_rule_parser.cpp b/qa/drc_proto/drc_rule_parser.cpp new file mode 100644 index 0000000000..8909e2fe5d --- /dev/null +++ b/qa/drc_proto/drc_rule_parser.cpp @@ -0,0 +1,258 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2020 KiCad Developers, see change_log.txt for contributors. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + + +#include +#include + +#include +#include +#include + +#include + +#include + +using namespace DRCRULE_T; + + +test::DRC_RULES_PARSER::DRC_RULES_PARSER( BOARD* aBoard, FILE* aFile, const wxString& aFilename ) + : DRC_RULES_LEXER( aFile, aFilename ), + m_board( aBoard ), + m_requiredVersion( 0 ), + m_tooRecent( false ) +{ + for( LAYER_NUM layer = 0; layer < PCB_LAYER_ID_COUNT; ++layer ) + { + std::string untranslated = TO_UTF8( wxString( LSET::Name( PCB_LAYER_ID( layer ) ) ) ); + m_layerMap[untranslated] = PCB_LAYER_ID( layer ); + } +} + + +void test::DRC_RULES_PARSER::Parse( + std::vector& aConditions, std::vector& aRules ) +{ + bool haveVersion = false; + + for( T token = NextTok(); token != T_EOF; token = NextTok() ) + { + if( token != T_LEFT ) + Expecting( T_LEFT ); + + token = NextTok(); + + if( !haveVersion && token != T_version ) + Expecting( "version" ); + + switch( token ) + { + case T_version: + NeedNUMBER( "version" ); + m_requiredVersion = (int) strtol( CurText(), NULL, 10 ); + m_tooRecent = ( m_requiredVersion > DRC_RULE_FILE_VERSION ); + haveVersion = true; + NeedRIGHT(); + break; + + case T_condition: + aConditions.push_back( parseCONDITION() ); + break; + + case T_rule: + aRules.push_back( parseRULE() ); + break; + + default: + Expecting( "condition or rule" ); + } + } + + +#if 0 + // Hook up the selectors to their rules + std::map ruleMap; + + for( DRC_RULE* rule : aRules ) + ruleMap[ rule->m_Name ] = rule; + + for( const std::pair& entry : conditionsRules ) + { + if( ruleMap.count( entry.second ) ) + { + entry.first->m_Rule = ruleMap[ entry.second ]; + } + else + { + wxString errText = wxString::Format( _( "Rule \"%s\" not found." ), entry.second ); + THROW_PARSE_ERROR( errText, CurSource(), "", 0, 0 ); + } + } +#endif +} + + +test::DRC_RULE_CONDITION* test::DRC_RULES_PARSER::parseCONDITION() +{ + test::DRC_RULE_CONDITION* cond = new test::DRC_RULE_CONDITION(); + T token; + + printf( "parsecondition\n" ); + + for( token = NextTok(); token != T_RIGHT; token = NextTok() ) + { + if( token != T_LEFT ) + Expecting( T_LEFT ); + + token = NextTok(); + + printf( "Do token xxx %d '%s'\n", token, (const char*) FromUTF8().c_str() ); + + switch( token ) + { + case T_expression: + NeedSYMBOL(); + cond->m_Expression = FromUTF8(); + break; + + case T_rule: + NeedSYMBOL(); + cond->m_TargetRuleName = FromUTF8(); + break; + + default: + Expecting( "rule or expression" ); + break; + } + NeedRIGHT(); + } + + //NeedRIGHT() + + return cond; +} + + +test::DRC_RULE* test::DRC_RULES_PARSER::parseRULE() +{ + DRC_RULE* rule = new DRC_RULE(); + T token = NextTok(); + int val; + + if( !IsSymbol( token ) ) + Expecting( "rule name" ); + + rule->m_Name = FromUTF8(); + + for( token = NextTok(); token != T_RIGHT; token = NextTok() ) + { + if( token != T_LEFT ) + Expecting( T_LEFT ); + + token = NextTok(); + + //printf( "Do token xxx %d '%s'\n", token, (const char*) FromUTF8().c_str() ); + + switch( token ) + { + case T_type: + NeedSYMBOL(); + rule->m_TestProviderName = FromUTF8(); + break; + + case T_min: + NeedSYMBOL(); + parseValueWithUnits ( FromUTF8(), val ); + rule->m_Constraint.m_Value.SetMin( val ); + break; + + case T_opt: + NeedSYMBOL(); + parseValueWithUnits ( FromUTF8(), val ); + rule->m_Constraint.m_Value.SetOpt( val ); + break; + + case T_max: + NeedSYMBOL(); + parseValueWithUnits ( FromUTF8(), val ); + rule->m_Constraint.m_Value.SetMax( val ); + break; + + case T_allow: + rule->m_Constraint.m_Allow = parseInt("allowed"); + break; + + case T_enable: + rule->m_Enabled = parseInt("enabled"); + break; + + case T_severity: + token = NextTok(); + switch( token ) + { + case T_error: + case T_warning: + case T_ignore: break; // fixme + default: + Expecting( "error, warning or ignore" ); + break; + } + + + break; + + case T_priority: + rule->m_Priority = parseInt("priotity"); + break; + + + + default: + Expecting( "type, min, opt, max, allow, enable, priority or severity" ); + break; + } + + NeedRIGHT(); + } + + return rule; +} + + +void test::DRC_RULES_PARSER::parseValueWithUnits( const wxString& aExpr, int& aResult ) +{ + PCB_EXPR_EVALUATOR evaluator; + + bool ok = evaluator.Evaluate( aExpr ); + + if( !ok ) + { + auto err = evaluator.GetErrorString(); + printf( "eval error: '%s'\n", (const char*) err.c_str() ); + + THROW_PARSE_ERROR( err, "", "", 0, 0 ); + } + + aResult = evaluator.Result(); + printf("parseValueWithUnits: value %d\n", aResult ); +}; diff --git a/qa/drc_proto/drc_rule_parser.h b/qa/drc_proto/drc_rule_parser.h new file mode 100644 index 0000000000..9c54f04c17 --- /dev/null +++ b/qa/drc_proto/drc_rule_parser.h @@ -0,0 +1,80 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2020 KiCad Developers, see change_log.txt for contributors. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#ifndef DRC_RULE_PARSER_H +#define DRC_RULE_PARSER_H + +#include +#include +#include +#include +#include + +class BOARD_ITEM; + +namespace test { + +class DRC_RULE_CONDITION; +class DRC_RULE; + + +#define DRC_RULE_FILE_VERSION 20200610 + +class DRC_RULES_PARSER : public DRC_RULES_LEXER +{ +public: + DRC_RULES_PARSER( BOARD* aBoard, FILE* aFile, const wxString& aFilename ); + + void Parse( std::vector& aConditions, std::vector& aRules ); + +private: + DRC_RULE_CONDITION* parseCONDITION(); + DRC_RULE* parseRULE(); + void parseValueWithUnits( const wxString& aExpr, int& aResult ); + + inline int parseInt() + { + return (int)strtol( CurText(), NULL, 10 ); + } + + inline int parseInt( const char* aExpected ) + { + NeedNUMBER( aExpected ); + return parseInt(); + } + +// void parseConstraint( DRC_RULE* aRule ); + //int parseValue( DRCRULE_T::T aToken ); + +private: + + BOARD* m_board; + int m_requiredVersion; + bool m_tooRecent; + + std::unordered_map m_layerMap; +}; + +}; + +#endif // DRC_RULE_PARSER_H diff --git a/qa/drc_proto/drc_rules_keywords.cpp b/qa/drc_proto/drc_rules_keywords.cpp new file mode 100644 index 0000000000..391dff5341 --- /dev/null +++ b/qa/drc_proto/drc_rules_keywords.cpp @@ -0,0 +1,50 @@ + +/* Do not modify this file it was automatically generated by the + * TokenList2DsnLexer CMake script. + * + * Include this file in your lexer class to provide the keywords for + * your DSN lexer. + */ + +#include + +using namespace DRCRULE_T; + +#define TOKDEF(x) { #x, T_##x } + +const KEYWORD DRC_RULES_LEXER::keywords[] = { + TOKDEF( allow ), + TOKDEF( condition ), + TOKDEF( enable ), + TOKDEF( error ), + TOKDEF( expression ), + TOKDEF( ignore ), + TOKDEF( info ), + TOKDEF( max ), + TOKDEF( min ), + TOKDEF( name ), + TOKDEF( opt ), + TOKDEF( priority ), + TOKDEF( rule ), + TOKDEF( severity ), + TOKDEF( type ), + TOKDEF( version ), + TOKDEF( warning ) +}; + +const unsigned DRC_RULES_LEXER::keyword_count = unsigned( sizeof( DRC_RULES_LEXER::keywords )/sizeof( DRC_RULES_LEXER::keywords[0] ) ); + + +const char* DRC_RULES_LEXER::TokenName( T aTok ) +{ + const char* ret; + + if( aTok < 0 ) + ret = DSNLEXER::Syntax( aTok ); + else if( (unsigned) aTok < keyword_count ) + ret = keywords[aTok].name; + else + ret = "token too big"; + + return ret; +} diff --git a/qa/drc_proto/drc_rules_lexer.h b/qa/drc_proto/drc_rules_lexer.h new file mode 100644 index 0000000000..3a99221643 --- /dev/null +++ b/qa/drc_proto/drc_rules_lexer.h @@ -0,0 +1,195 @@ + +/* Do not modify this file it was automatically generated by the + * TokenList2DsnLexer CMake script. + */ + +#ifndef DRC_RULES_LEXER_H_ +#define DRC_RULES_LEXER_H_ + +#include + +/** + * C++ does not put enum _values_ in separate namespaces unless the enum itself + * is in a separate namespace. All the token enums must be in separate namespaces + * otherwise the C++ compiler will eventually complain if it sees more than one + * DSNLEXER in the same compilation unit, say by mutliple header file inclusion. + * Plus this also enables re-use of the same enum name T. A typedef can always be used + * to clarify which enum T is in play should that ever be a problem. This is + * unlikely since Parse() functions will usually only be exposed to one header + * file like this one. But if there is a problem, then use: + * typedef DRCRULE_T::T T; + * within that problem area. + */ +namespace DRCRULE_T +{ + /// enum T contains all this lexer's tokens. + enum T + { + // these first few are negative special ones for syntax, and are + // inherited from DSNLEXER. + T_NONE = DSN_NONE, + T_COMMENT = DSN_COMMENT, + T_STRING_QUOTE = DSN_STRING_QUOTE, + T_QUOTE_DEF = DSN_QUOTE_DEF, + T_DASH = DSN_DASH, + T_SYMBOL = DSN_SYMBOL, + T_NUMBER = DSN_NUMBER, + T_RIGHT = DSN_RIGHT, // right bracket: ')' + T_LEFT = DSN_LEFT, // left bracket: '(' + T_STRING = DSN_STRING, // a quoted string, stripped of the quotes + T_EOF = DSN_EOF, // special case for end of file + + T_allow = 0, + T_condition, + T_enable, + T_error, + T_expression, + T_ignore, + T_info, + T_max, + T_min, + T_name, + T_opt, + T_priority, + T_rule, + T_severity, + T_type, + T_version, + T_warning + }; +} // namespace DRCRULE_T + + +/** + * Class DRC_RULES_LEXER + * is an automatically generated class using the TokenList2DnsLexer.cmake + * technology, based on keywords provided by file: + * /home/twl/Kicad-dev/kicad-git-dev/common/drc_rules.keywords + */ +class DRC_RULES_LEXER : public DSNLEXER +{ + /// Auto generated lexer keywords table and length: + static const KEYWORD keywords[]; + static const unsigned keyword_count; + +public: + /** + * Constructor ( const std::string&, const wxString& ) + * @param aSExpression is (utf8) text possibly from the clipboard that you want to parse. + * @param aSource is a description of the origin of @a aSExpression, such as a filename. + * If left empty, then _("clipboard") is used. + */ + DRC_RULES_LEXER( const std::string& aSExpression, const wxString& aSource = wxEmptyString ) : + DSNLEXER( keywords, keyword_count, aSExpression, aSource ) + { + } + + /** + * Constructor ( FILE* ) + * takes @a aFile already opened for reading and @a aFilename as parameters. + * The opened file is assumed to be positioned at the beginning of the file + * for purposes of accurate line number reporting in error messages. The + * FILE is closed by this instance when its destructor is called. + * @param aFile is a FILE already opened for reading. + * @param aFilename is the name of the opened file, needed for error reporting. + */ + DRC_RULES_LEXER( FILE* aFile, const wxString& aFilename ) : + DSNLEXER( keywords, keyword_count, aFile, aFilename ) + { + } + + /** + * Constructor ( LINE_READER* ) + * initializes a lexer and prepares to read from @a aLineReader which + * is assumed ready, and may be in use by other DSNLEXERs also. No ownership + * is taken of @a aLineReader. This enables it to be used by other lexers also. + * The transition between grammars in such a case, must happen on a text + * line boundary, not within the same line of text. + * + * @param aLineReader is any subclassed instance of LINE_READER, such as + * STRING_LINE_READER or FILE_LINE_READER. No ownership is taken of aLineReader. + */ + DRC_RULES_LEXER( LINE_READER* aLineReader ) : + DSNLEXER( keywords, keyword_count, aLineReader ) + { + } + + /** + * Function TokenName + * returns the name of the token in ASCII form. + */ + static const char* TokenName( DRCRULE_T::T aTok ); + + /** + * Function NextTok + * returns the next token found in the input file or T_EOF when reaching + * the end of file. Users should wrap this function to return an enum + * to aid in grammar debugging while running under a debugger, but leave + * this lower level function returning an int (so the enum does not collide + * with another usage). + * @return DRCRULE_T::T - the type of token found next. + * @throw IO_ERROR - only if the LINE_READER throws it. + */ + DRCRULE_T::T NextTok() + { + return (DRCRULE_T::T) DSNLEXER::NextTok(); + } + + /** + * Function NeedSYMBOL + * calls NextTok() and then verifies that the token read in + * satisfies bool IsSymbol(). + * If not, an IO_ERROR is thrown. + * @return int - the actual token read in. + * @throw IO_ERROR, if the next token does not satisfy IsSymbol() + */ + DRCRULE_T::T NeedSYMBOL() + { + return (DRCRULE_T::T) DSNLEXER::NeedSYMBOL(); + } + + /** + * Function NeedSYMBOLorNUMBER + * calls NextTok() and then verifies that the token read in + * satisfies bool IsSymbol() or tok==T_NUMBER. + * If not, an IO_ERROR is thrown. + * @return int - the actual token read in. + * @throw IO_ERROR, if the next token does not satisfy the above test + */ + DRCRULE_T::T NeedSYMBOLorNUMBER() + { + return (DRCRULE_T::T) DSNLEXER::NeedSYMBOLorNUMBER(); + } + + /** + * Function CurTok + * returns whatever NextTok() returned the last time it was called. + */ + DRCRULE_T::T CurTok() + { + return (DRCRULE_T::T) DSNLEXER::CurTok(); + } + + /** + * Function PrevTok + * returns whatever NextTok() returned the 2nd to last time it was called. + */ + DRCRULE_T::T PrevTok() + { + return (DRCRULE_T::T) DSNLEXER::PrevTok(); + } +}; + +// example usage + +/** + * Class _PARSER + * holds data and functions pertinent to parsing a S-expression file . + * +class DRC_RULES_PARSER : public DRC_RULES_LEXER +{ + +}; +*/ + +#endif // DRC_RULES_LEXER_H_ diff --git a/qa/drc_proto/drc_rules_proto.keywords b/qa/drc_proto/drc_rules_proto.keywords new file mode 100644 index 0000000000..8075c026c1 --- /dev/null +++ b/qa/drc_proto/drc_rules_proto.keywords @@ -0,0 +1,17 @@ +condition +max +min +opt +allow +rule +version +name +priority +expression +enable +severity +type +error +warning +info +ignore \ No newline at end of file diff --git a/qa/drc_proto/drc_test_provider.cpp b/qa/drc_proto/drc_test_provider.cpp new file mode 100644 index 0000000000..582486d600 --- /dev/null +++ b/qa/drc_proto/drc_test_provider.cpp @@ -0,0 +1,31 @@ +#include +#include + +test::DRC_TEST_PROVIDER::DRC_TEST_PROVIDER ( DRC_ENGINE *aDrc ) : + m_drcEngine( aDrc ) +{ + +} + +void test::DRC_TEST_PROVIDER::Enable( bool enable ) +{ + m_enable = enable; +} + +bool test::DRC_TEST_PROVIDER::IsEnabled() const +{ + return m_enable; +} + +const wxString test::DRC_TEST_PROVIDER::GetName() const { return ""; } +const wxString test::DRC_TEST_PROVIDER::GetDescription() const { return ""; } + +void test::DRC_TEST_PROVIDER::AddMarkerToPcb( MARKER_PCB* aMarker ) +{ + +} + +EDA_UNITS test::DRC_TEST_PROVIDER::userUnits() const +{ + return m_drcEngine->UserUnits(); +} \ No newline at end of file diff --git a/qa/drc_proto/drc_test_provider.h b/qa/drc_proto/drc_test_provider.h new file mode 100644 index 0000000000..041ff6dff1 --- /dev/null +++ b/qa/drc_proto/drc_test_provider.h @@ -0,0 +1,290 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2018 KiCad Developers, see change_log.txt for contributors. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + + +#ifndef DRC_PROVIDER__H +#define DRC_PROVIDER__H + +#include +#include + +#include +#include + +namespace test { + +class DRC_ENGINE; + +/** + * DRC_TEST_PROVIDER + * is a base class that represents a DRC "provider" which runs some DRC functions over a + * #BOARD and spits out #PCB_MARKERs as needed. + */ +class DRC_TEST_PROVIDER +{ +public: + DRC_TEST_PROVIDER ( DRC_ENGINE *aDrc ); + virtual ~DRC_TEST_PROVIDER() {} + + /** + * Runs this provider against the given PCB with configured options (if any). + * + * Note: Board is non-const, as some DRC functions modify the board (e.g. zone fill + * or polygon coalescing) + */ + + void SetRule ( DRC_RULE *aRule ); + + virtual bool Run() = 0; + + virtual void Enable( bool enable ); + virtual bool IsEnabled() const; + + virtual const wxString GetName() const; + virtual const wxString GetDescription() const; + + virtual void AddMarkerToPcb( MARKER_PCB* aMarker ); + + virtual std::set GetMatchingRuleIds() const = 0; + +protected: + + EDA_UNITS userUnits() const; + + DRC_RULE *m_rule; + DRC_ENGINE *m_drcEngine; + bool m_enable; +}; + + +#if 0 +/** + * BOARD_DRC_ITEMS_PROVIDER + * is an implementation of the RC_ITEMS_PROVIDER interface which uses a BOARD instance + * to fulfill the interface. + */ +class BOARD_DRC_ITEMS_PROVIDER : public RC_ITEMS_PROVIDER +{ +private: + BOARD* m_board; + + int m_severities; + std::vector m_filteredMarkers; + +public: + BOARD_DRC_ITEMS_PROVIDER( BOARD* aBoard ) : + m_board( aBoard ), + m_severities( 0 ) + { + } + + void SetSeverities( int aSeverities ) override + { + m_severities = aSeverities; + + BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings(); + + m_filteredMarkers.clear(); + + for( MARKER_PCB* marker : m_board->Markers() ) + { + int markerSeverity; + + if( marker->IsExcluded() ) + markerSeverity = RPT_SEVERITY_EXCLUSION; + else + markerSeverity = bds.GetSeverity( marker->GetRCItem()->GetErrorCode() ); + + if( markerSeverity & m_severities ) + m_filteredMarkers.push_back( marker ); + } + } + + int GetCount( int aSeverity = -1 ) override + { + if( aSeverity < 0 ) + return m_filteredMarkers.size(); + + BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings(); + + int count = 0; + + for( MARKER_PCB* marker : m_board->Markers() ) + { + int markerSeverity; + + if( marker->IsExcluded() ) + markerSeverity = RPT_SEVERITY_EXCLUSION; + else + markerSeverity = bds.GetSeverity( marker->GetRCItem()->GetErrorCode() ); + + if( markerSeverity == aSeverity ) + count++; + } + + return count; + } + + DRC_ITEM* GetItem( int aIndex ) override + { + MARKER_PCB* marker = m_filteredMarkers[ aIndex ]; + + return marker ? static_cast( marker->GetRCItem() ) : nullptr; + } + + void DeleteItem( int aIndex, bool aDeep ) override + { + MARKER_PCB* marker = m_filteredMarkers[ aIndex ]; + m_filteredMarkers.erase( m_filteredMarkers.begin() + aIndex ); + + if( aDeep ) + m_board->Delete( marker ); + } + + void DeleteAllItems() override + { + m_board->DeleteMARKERs(); + m_filteredMarkers.clear(); + } +}; + + +/** + * VECTOR_DRC_ITEMS_PROVIDER + * is an implementation of the interface named DRC_ITEMS_PROVIDER which uses a vector + * of pointers to DRC_ITEMs to fulfill the interface. No ownership is taken of the + * vector. + */ +class VECTOR_DRC_ITEMS_PROVIDER : public RC_ITEMS_PROVIDER +{ + PCB_BASE_FRAME* m_frame; + std::vector* m_sourceVector; // owns its DRC_ITEMs + + int m_severities; + std::vector m_filteredVector; // does not own its DRC_ITEMs + +public: + + VECTOR_DRC_ITEMS_PROVIDER( PCB_BASE_FRAME* aFrame, std::vector* aList ) : + m_frame( aFrame ), + m_sourceVector( aList ), + m_severities( 0 ) + { + } + + void SetSeverities( int aSeverities ) override + { + m_severities = aSeverities; + + BOARD_DESIGN_SETTINGS& bds = m_frame->GetBoard()->GetDesignSettings(); + + m_filteredVector.clear(); + + if( m_sourceVector ) + { + for( DRC_ITEM* item : *m_sourceVector ) + { + if( bds.GetSeverity( item->GetErrorCode() ) & aSeverities ) + m_filteredVector.push_back( item ); + } + } + } + + int GetCount( int aSeverity = -1 ) override + { + if( aSeverity < 0 ) + return m_filteredVector.size(); + + int count = 0; + BOARD_DESIGN_SETTINGS& bds = m_frame->GetBoard()->GetDesignSettings(); + + if( m_sourceVector ) + { + for( DRC_ITEM* item : *m_sourceVector ) + { + if( bds.GetSeverity( item->GetErrorCode() ) == aSeverity ) + count++; + } + } + + return count; + } + + DRC_ITEM* GetItem( int aIndex ) override + { + return (m_filteredVector)[aIndex]; + } + + void DeleteItem( int aIndex, bool aDeep ) override + { + DRC_ITEM* item = m_filteredVector[aIndex]; + m_filteredVector.erase( m_filteredVector.begin() + aIndex ); + + if( aDeep ) + { + for( size_t i = 0; i < m_sourceVector->size(); ++i ) + { + if( m_sourceVector->at( i ) == item ) + { + delete item; + m_sourceVector->erase( m_sourceVector->begin() + i ); + break; + } + } + } + } + + void DeleteAllItems() override + { + if( m_sourceVector ) + { + for( DRC_ITEM* item : *m_sourceVector ) + delete item; + + m_sourceVector->clear(); + } + + m_filteredVector.clear(); // no ownership of DRC_ITEM pointers + } +}; + + +/** + * RATSNEST_DRC_ITEMS_PROVIDER + */ +class RATSNEST_DRC_ITEMS_PROVIDER : public VECTOR_DRC_ITEMS_PROVIDER +{ + // TODO: for now this is just a vector, but we need to map it to some board-level + // data-structure so that deleting/excluding things can do a deep delete/exclusion + // which will be reflected in the ratsnest.... +public: + RATSNEST_DRC_ITEMS_PROVIDER( PCB_BASE_FRAME* aFrame, std::vector* aList ) : + VECTOR_DRC_ITEMS_PROVIDER( aFrame, aList ) + { } +}; + +#endif + +}; + +#endif // DRC_PROVIDER__H diff --git a/qa/drc_proto/drc_textvar_tester.cpp b/qa/drc_proto/drc_textvar_tester.cpp new file mode 100644 index 0000000000..c845f299b8 --- /dev/null +++ b/qa/drc_proto/drc_textvar_tester.cpp @@ -0,0 +1,116 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2020 KiCad Developers, see AUTHORS.txt for contributors. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + + +#include + +#include +#include +#include +#include + +DRC_TEXTVAR_TESTER::DRC_TEXTVAR_TESTER( MARKER_HANDLER aMarkerHandler, + KIGFX::WS_PROXY_VIEW_ITEM* aWorksheet ) : + DRC_TEST_PROVIDER( std::move( aMarkerHandler ) ), + m_units( EDA_UNITS::MILLIMETRES ), + m_board( nullptr ), + m_worksheet( aWorksheet ) +{ +} + + +bool DRC_TEXTVAR_TESTER::RunDRC( EDA_UNITS aUnits, BOARD& aBoard ) +{ + bool success = true; + + m_units = aUnits; + m_board = &aBoard; + + for( MODULE* module : m_board->Modules() ) + { + module->RunOnChildren( + [&]( BOARD_ITEM* child ) + { + if( child->Type() == PCB_MODULE_TEXT_T ) + { + TEXTE_MODULE* text = static_cast( child ); + + if( text->GetShownText().Matches( wxT( "*${*}*" ) ) ) + { + DRC_ITEM* drcItem = new DRC_ITEM( DRCE_UNRESOLVED_VARIABLE ); + drcItem->SetItems( text ); + + HandleMarker( new MARKER_PCB( drcItem, text->GetPosition() ) ); + success = false; + } + } + } ); + } + + for( BOARD_ITEM* drawing : m_board->Drawings() ) + { + if( drawing->Type() == PCB_TEXT_T ) + { + TEXTE_PCB* text = static_cast( drawing ); + + if( text->GetShownText().Matches( wxT( "*${*}*" ) ) ) + { + DRC_ITEM* drcItem = new DRC_ITEM( DRCE_UNRESOLVED_VARIABLE ); + drcItem->SetItems( text ); + + HandleMarker( new MARKER_PCB( drcItem, text->GetPosition() ) ); + success = false; + } + } + } + + WS_DRAW_ITEM_LIST wsItems; + + if( m_worksheet ) + { + wsItems.SetMilsToIUfactor( IU_PER_MILS ); + wsItems.BuildWorkSheetGraphicList( m_worksheet->GetPageInfo(), + m_worksheet->GetTitleBlock() ); + + for( WS_DRAW_ITEM_BASE* item = wsItems.GetFirst(); item; item = wsItems.GetNext() ) + { + if( WS_DRAW_ITEM_TEXT* text = dynamic_cast( item ) ) + { + if( text->GetShownText().Matches( wxT( "*${*}*" ) ) ) + { + DRC_ITEM* drcItem = new DRC_ITEM( DRCE_UNRESOLVED_VARIABLE ); + drcItem->SetErrorMessage( _( "Unresolved text variable in worksheet." ) ); + + HandleMarker( new MARKER_PCB( drcItem, text->GetPosition() ) ); + success = false; + } + } + } + } + + // JEY TODO: Test text vars in worksheet... + + return success; +} + + diff --git a/qa/drc_proto/drc_textvar_tester.h b/qa/drc_proto/drc_textvar_tester.h new file mode 100644 index 0000000000..678d5b878f --- /dev/null +++ b/qa/drc_proto/drc_textvar_tester.h @@ -0,0 +1,49 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2020 KiCad Developers, see change_log.txt for contributors. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + + +#ifndef DRC_TEXTVAR_TESTER__H +#define DRC_TEXTVAR_TESTER__H + +#include + + +class BOARD; + + +class DRC_TEXTVAR_TESTER : public DRC_TEST_PROVIDER +{ +public: + DRC_TEXTVAR_TESTER( MARKER_HANDLER aMarkerHandler, KIGFX::WS_PROXY_VIEW_ITEM* aWorksheet ); + + virtual ~DRC_TEXTVAR_TESTER() {}; + + bool RunDRC( EDA_UNITS aUnits, BOARD& aBoard ) override; + +private: + EDA_UNITS m_units; + BOARD* m_board; + KIGFX::WS_PROXY_VIEW_ITEM* m_worksheet; +}; + +#endif // DRC_TEXTVAR_TESTER__H diff --git a/qa/drc_proto/footprint_tester.cpp b/qa/drc_proto/footprint_tester.cpp new file mode 100644 index 0000000000..14a2f70193 --- /dev/null +++ b/qa/drc_proto/footprint_tester.cpp @@ -0,0 +1,90 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2020 KiCad Developers, see change_log.txt for contributors. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include +#include +#include + +void TestFootprints( NETLIST& aNetlist, BOARD* aBoard, std::vector& aDRCList ) +{ + wxString msg; + + auto comp = []( const MODULE* x, const MODULE* y ) + { + return x->GetReference().CmpNoCase( y->GetReference() ) < 0; + }; + auto mods = std::set( comp ); + + if( !aBoard->GetDesignSettings().Ignore( DRCE_DUPLICATE_FOOTPRINT ) ) + { + // Search for duplicate footprints on the board + for( MODULE* mod : aBoard->Modules() ) + { + auto ins = mods.insert( mod ); + + if( !ins.second ) + { + DRC_ITEM* item = new DRC_ITEM( DRCE_DUPLICATE_FOOTPRINT ); + item->SetItems( mod, *ins.first ); + aDRCList.push_back( item ); + } + } + } + + if( !aBoard->GetDesignSettings().Ignore( DRCE_MISSING_FOOTPRINT ) ) + { + // Search for component footprints in the netlist but not on the board. + for( unsigned ii = 0; ii < aNetlist.GetCount(); ii++ ) + { + COMPONENT* component = aNetlist.GetComponent( ii ); + MODULE* module = aBoard->FindModuleByReference( component->GetReference() ); + + if( module == NULL ) + { + msg.Printf( _( "Missing footprint %s (%s)" ), + component->GetReference(), + component->GetValue() ); + + DRC_ITEM* item = new DRC_ITEM( DRCE_MISSING_FOOTPRINT ); + item->SetErrorMessage( msg ); + aDRCList.push_back( item ); + } + } + } + + if( !aBoard->GetDesignSettings().Ignore( DRCE_EXTRA_FOOTPRINT ) ) + { + // Search for component footprints found on board but not in netlist. + for( auto module : mods ) + { + COMPONENT* component = aNetlist.GetComponentByReference( module->GetReference() ); + + if( component == NULL ) + { + DRC_ITEM* item = new DRC_ITEM( DRCE_EXTRA_FOOTPRINT ); + item->SetItems( module ); + aDRCList.push_back( item ); + } + } + } +} diff --git a/qa/drc_proto/footprint_tester.h b/qa/drc_proto/footprint_tester.h new file mode 100644 index 0000000000..bff20988a2 --- /dev/null +++ b/qa/drc_proto/footprint_tester.h @@ -0,0 +1,34 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2020 KiCad Developers, see change_log.txt for contributors. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#ifndef FOOTPRINT_TESTER_H +#define FOOTPRINT_TESTER_H + +#include + +class BOARD; + + +void TestFootprints( NETLIST& aNetlist, BOARD* aBoard, std::vector& aDRCList ); + +#endif // FOOTPRINT_TESTER_H