/* * 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 void drcPrintDebugMessage( int level, const wxString& msg, const char *function, int line ) { wxString valueStr; if( wxGetEnv( "DRC_DEBUG", &valueStr ) ) { int setLevel = wxAtoi( valueStr ); if( level <= setLevel ) { printf("%-30s:%d | %s\n", function, line, (const char *) msg.c_str() ); } } } DRC_ENGINE::DRC_ENGINE( BOARD* aBoard, BOARD_DESIGN_SETTINGS *aSettings ) : m_designSettings ( aSettings ), m_board( aBoard ), m_worksheet( nullptr ), m_schematicNetlist( nullptr ), m_rulesValid( false ), m_userUnits( EDA_UNITS::MILLIMETRES ), m_reportAllTrackErrors( false ), m_testFootprints( false ), m_reporter( nullptr ), m_progressReporter( nullptr ) { m_errorLimits.resize( DRCE_LAST + 1 ); for( int ii = DRCE_FIRST; ii <= DRCE_LAST; ++ii ) m_errorLimits[ ii ] = INT_MAX; } DRC_ENGINE::~DRC_ENGINE() { for( DRC_RULE* rule : m_rules ) delete rule; for( std::pair< DRC_CONSTRAINT_TYPE_T, std::vector* > pair : m_constraintMap ) { for( CONSTRAINT_WITH_CONDITIONS* constraintWithCondition : *pair.second ) delete constraintWithCondition; delete pair.second; } } static bool isKeepoutZone( const BOARD_ITEM* aItem ) { if( aItem && ( aItem->Type() == PCB_ZONE_AREA_T || aItem->Type() == PCB_FP_ZONE_AREA_T ) ) { const ZONE_CONTAINER* zone = static_cast( aItem ); return zone->GetIsRuleArea() && ( zone->GetDoNotAllowTracks() || zone->GetDoNotAllowVias() || zone->GetDoNotAllowPads() || zone->GetDoNotAllowCopperPour() || zone->GetDoNotAllowFootprints() ); } return false; } DRC_RULE* DRC_ENGINE::createImplicitRule( const wxString& name ) { DRC_RULE *rule = new DRC_RULE; rule->m_Name = name; rule->m_Implicit = true; addRule( rule ); return rule; } void DRC_ENGINE::loadImplicitRules() { ReportAux( wxString::Format( "Building implicit rules (per-item/class overrides, etc...)" ) ); BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings(); // 1) global defaults DRC_RULE* rule = createImplicitRule( _( "board setup constraints" ) ); DRC_CONSTRAINT clearanceConstraint( CLEARANCE_CONSTRAINT ); clearanceConstraint.Value().SetMin( bds.m_MinClearance ); rule->AddConstraint( clearanceConstraint ); DRC_CONSTRAINT widthConstraint( TRACK_WIDTH_CONSTRAINT ); widthConstraint.Value().SetMin( bds.m_TrackMinWidth ); rule->AddConstraint( widthConstraint ); DRC_CONSTRAINT drillConstraint( HOLE_SIZE_CONSTRAINT ); drillConstraint.Value().SetMin( bds.m_MinThroughDrill ); rule->AddConstraint( drillConstraint ); DRC_CONSTRAINT annulusConstraint( ANNULAR_WIDTH_CONSTRAINT ); annulusConstraint.Value().SetMin( bds.m_ViasMinAnnulus ); rule->AddConstraint( annulusConstraint ); DRC_CONSTRAINT diameterConstraint( VIA_DIAMETER_CONSTRAINT ); diameterConstraint.Value().SetMin( bds.m_ViasMinSize ); rule->AddConstraint( diameterConstraint ); DRC_CONSTRAINT edgeClearanceConstraint( EDGE_CLEARANCE_CONSTRAINT ); edgeClearanceConstraint.Value().SetMin( bds.m_CopperEdgeClearance ); rule->AddConstraint( edgeClearanceConstraint ); DRC_CONSTRAINT holeClearanceConstraint( HOLE_CLEARANCE_CONSTRAINT ); holeClearanceConstraint.Value().SetMin( bds.m_HoleToHoleMin ); rule->AddConstraint( holeClearanceConstraint ); DRC_CONSTRAINT courtyardClearanceConstraint( COURTYARD_CLEARANCE_CONSTRAINT ); holeClearanceConstraint.Value().SetMin( 0 ); rule->AddConstraint( courtyardClearanceConstraint ); DRC_CONSTRAINT diffPairGapConstraint( DIFF_PAIR_GAP_CONSTRAINT ); diffPairGapConstraint.Value().SetMin( bds.GetDefault()->GetClearance() ); rule->AddConstraint( diffPairGapConstraint ); rule = createImplicitRule( _( "board setup constraints" ) ); rule->m_LayerCondition = LSET( 2, F_SilkS, B_SilkS ); DRC_CONSTRAINT silkClearanceConstraint( SILK_CLEARANCE_CONSTRAINT ); silkClearanceConstraint.Value().SetMin( bds.m_SilkClearance ); rule->AddConstraint( silkClearanceConstraint ); // 2) micro-via specific defaults (new DRC doesn't treat microvias in any special way) DRC_RULE* uViaRule = createImplicitRule( _( "board setup micro-via constraints" ) ); uViaRule->m_Condition = new DRC_RULE_CONDITION( "A.Via_Type == 'Micro'" ); DRC_CONSTRAINT uViaDrillConstraint( HOLE_SIZE_CONSTRAINT ); uViaDrillConstraint.Value().SetMin( bds.m_MicroViasMinDrill ); uViaRule->AddConstraint( uViaDrillConstraint ); DRC_CONSTRAINT uViaDiameterConstraint( VIA_DIAMETER_CONSTRAINT ); uViaDiameterConstraint.Value().SetMin( bds.m_MicroViasMinSize ); uViaRule->AddConstraint( uViaDiameterConstraint ); if( !bds.m_MicroViasAllowed ) { DRC_CONSTRAINT disallowConstraint( DISALLOW_CONSTRAINT ); disallowConstraint.m_DisallowFlags = DRC_DISALLOW_MICRO_VIAS; uViaRule->AddConstraint( disallowConstraint ); } if( !bds.m_BlindBuriedViaAllowed ) { DRC_RULE* bbViaRule = createImplicitRule( _( "board setup constraints" ) ); bbViaRule->m_Condition = new DRC_RULE_CONDITION( "A.Via_Type == 'Blind/buried'" ); DRC_CONSTRAINT disallowConstraint( DISALLOW_CONSTRAINT ); disallowConstraint.m_DisallowFlags = DRC_DISALLOW_BB_VIAS; bbViaRule->AddConstraint( disallowConstraint ); } // 3) per-netclass rules std::vector netclassClearanceRules; std::vector netclassItemSpecificRules; auto makeNetclassRules = [&]( const NETCLASSPTR& nc, bool isDefault ) { wxString ncName = nc->GetName(); DRC_RULE* rule; wxString expr; if( nc->GetClearance() || nc->GetTrackWidth() ) { rule = new DRC_RULE; rule->m_Name = wxString::Format( _( "netclass '%s'" ), ncName ); rule->m_Implicit = true; expr = wxString::Format( "A.NetClass == '%s'", ncName ); rule->m_Condition = new DRC_RULE_CONDITION( expr ); netclassClearanceRules.push_back( rule ); if( nc->GetClearance() ) { DRC_CONSTRAINT constraint( CLEARANCE_CONSTRAINT ); constraint.Value().SetMin( std::max( bds.m_MinClearance, nc->GetClearance() ) ); rule->AddConstraint( constraint ); } if( nc->GetTrackWidth() ) { DRC_CONSTRAINT constraint( TRACK_WIDTH_CONSTRAINT ); constraint.Value().SetMin( bds.m_TrackMinWidth ); constraint.Value().SetOpt( nc->GetTrackWidth() ); rule->AddConstraint( constraint ); } } if( nc->GetDiffPairWidth() || nc->GetDiffPairGap() ) { rule = new DRC_RULE; rule->m_Name = wxString::Format( _( "netclass '%s'" ), ncName ); rule->m_Implicit = true; expr = wxString::Format( "A.NetClass == '%s' && A.isDiffPair()", ncName ); rule->m_Condition = new DRC_RULE_CONDITION( expr ); netclassItemSpecificRules.push_back( rule ); if( nc->GetDiffPairWidth() ) { DRC_CONSTRAINT constraint( TRACK_WIDTH_CONSTRAINT ); constraint.Value().SetMin( bds.m_TrackMinWidth ); constraint.Value().SetOpt( nc->GetDiffPairWidth() ); rule->AddConstraint( constraint ); } if( nc->GetDiffPairGap() ) { DRC_CONSTRAINT constraint( DIFF_PAIR_GAP_CONSTRAINT ); constraint.Value().SetMin( std::max( bds.m_MinClearance, nc->GetClearance() ) ); constraint.Value().SetOpt( nc->GetDiffPairGap() ); rule->AddConstraint( constraint ); } } if( nc->GetViaDiameter() || nc->GetViaDrill() ) { rule = new DRC_RULE; rule->m_Name = wxString::Format( _( "netclass '%s'" ), ncName ); rule->m_Implicit = true; expr = wxString::Format( "A.NetClass == '%s' && A.Via_Type != 'Micro'", ncName ); rule->m_Condition = new DRC_RULE_CONDITION( expr ); netclassItemSpecificRules.push_back( rule ); if( nc->GetViaDiameter() ) { DRC_CONSTRAINT constraint( VIA_DIAMETER_CONSTRAINT ); constraint.Value().SetMin( bds.m_ViasMinSize ); constraint.Value().SetOpt( nc->GetViaDiameter() ); rule->AddConstraint( constraint ); } if( nc->GetViaDrill() ) { DRC_CONSTRAINT constraint( HOLE_SIZE_CONSTRAINT ); constraint.Value().SetMin( bds.m_MinThroughDrill ); constraint.Value().SetOpt( nc->GetViaDrill() ); rule->AddConstraint( constraint ); } } if( nc->GetuViaDiameter() || nc->GetuViaDrill() ) { rule = new DRC_RULE; rule->m_Name = wxString::Format( _( "netclass '%s'" ), ncName ); rule->m_Implicit = true; expr = wxString::Format( "A.NetClass == '%s' && A.Via_Type == 'Micro'", ncName ); rule->m_Condition = new DRC_RULE_CONDITION( expr ); netclassItemSpecificRules.push_back( rule ); if( nc->GetuViaDiameter() ) { DRC_CONSTRAINT constraint( VIA_DIAMETER_CONSTRAINT ); constraint.Value().SetMin( bds.m_MicroViasMinSize ); constraint.Value().SetMin( nc->GetuViaDiameter() ); rule->AddConstraint( constraint ); } if( nc->GetuViaDrill() ) { DRC_CONSTRAINT constraint( HOLE_SIZE_CONSTRAINT ); constraint.Value().SetMin( bds.m_MicroViasMinDrill ); constraint.Value().SetOpt( nc->GetuViaDrill() ); rule->AddConstraint( constraint ); } } }; m_board->SynchronizeNetsAndNetClasses(); makeNetclassRules( bds.GetNetClasses().GetDefault(), true ); for( const std::pair& netclass : bds.GetNetClasses() ) makeNetclassRules( netclass.second, false ); // The netclass clearance rules have to be sorted by min clearance so the right one fires // if 'A' and 'B' belong to two different netclasses. // // The item-specific netclass rules are all unary, so there's no 'A' vs 'B' issue. std::sort( netclassClearanceRules.begin(), netclassClearanceRules.end(), []( DRC_RULE* lhs, DRC_RULE* rhs ) { return lhs->m_Constraints[0].m_Value.Min() < rhs->m_Constraints[0].m_Value.Min(); } ); for( DRC_RULE* ncRule : netclassClearanceRules ) addRule( ncRule ); for( DRC_RULE* ncRule : netclassItemSpecificRules ) addRule( ncRule ); // 3) keepout area rules auto addKeepoutConstraint = [&rule]( int aConstraint ) { DRC_CONSTRAINT disallowConstraint( DISALLOW_CONSTRAINT ); disallowConstraint.m_DisallowFlags = aConstraint; rule->AddConstraint( disallowConstraint ); }; std::vector keepoutZones; for( ZONE_CONTAINER* zone : m_board->Zones() ) { if( isKeepoutZone( zone ) ) keepoutZones.push_back( zone ); } for( MODULE* footprint : m_board->Modules() ) { for( ZONE_CONTAINER* zone : footprint->Zones() ) { if( isKeepoutZone( zone ) ) keepoutZones.push_back( zone ); } } for( ZONE_CONTAINER* zone : keepoutZones ) { wxString name = zone->GetZoneName(); if( name.IsEmpty() ) { rule = createImplicitRule( _( "keepout area" ) ); name = zone->m_Uuid.AsString(); } else { rule = createImplicitRule( wxString::Format( _( "keepout area '%s'" ), name ) ); } rule->m_Condition = new DRC_RULE_CONDITION( wxString::Format( "A.insideArea('%s')", name ) ); rule->m_LayerCondition = zone->GetLayerSet(); if( zone->GetDoNotAllowTracks() ) addKeepoutConstraint( DRC_DISALLOW_TRACKS ); if( zone->GetDoNotAllowVias() ) addKeepoutConstraint( DRC_DISALLOW_VIAS ); if( zone->GetDoNotAllowPads() ) addKeepoutConstraint( DRC_DISALLOW_PADS ); if( zone->GetDoNotAllowCopperPour() ) addKeepoutConstraint( DRC_DISALLOW_ZONES ); if( zone->GetDoNotAllowFootprints() ) addKeepoutConstraint( DRC_DISALLOW_FOOTPRINTS ); } ReportAux( wxString::Format( "Building %d implicit netclass rules", (int) netclassClearanceRules.size() ) ); } static wxString formatConstraint( const DRC_CONSTRAINT& constraint ) { struct Formatter { DRC_CONSTRAINT_TYPE_T type; wxString name; std::function formatter; }; auto formatMinMax = []( const DRC_CONSTRAINT& c ) -> wxString { wxString str; const auto value = c.GetValue(); if ( value.HasMin() ) str += wxString::Format(" min: %d", value.Min() ); if ( value.HasOpt() ) str += wxString::Format(" opt: %d", value.Opt() ); if ( value.HasMax() ) str += wxString::Format(" max: %d", value.Max() ); return str; }; std::vector formats = { { CLEARANCE_CONSTRAINT, "clearance", formatMinMax }, { HOLE_CLEARANCE_CONSTRAINT, "hole_clearance", formatMinMax }, { EDGE_CLEARANCE_CONSTRAINT, "edge_clearance", formatMinMax }, { HOLE_SIZE_CONSTRAINT, "hole_size", formatMinMax }, { COURTYARD_CLEARANCE_CONSTRAINT, "courtyard_clearance", formatMinMax }, { SILK_CLEARANCE_CONSTRAINT, "silk_clearance", formatMinMax }, { TRACK_WIDTH_CONSTRAINT, "track_width", formatMinMax }, { ANNULAR_WIDTH_CONSTRAINT, "annular_width", formatMinMax }, { DISALLOW_CONSTRAINT, "disallow", nullptr }, { VIA_DIAMETER_CONSTRAINT, "via_diameter", formatMinMax }, { LENGTH_CONSTRAINT, "length", formatMinMax }, { SKEW_CONSTRAINT, "skew", formatMinMax }, { VIA_COUNT_CONSTRAINT, "via_count", formatMinMax } }; for( auto& fmt : formats ) { if( fmt.type == constraint.m_Type ) { wxString rv = fmt.name + " "; if( fmt.formatter ) rv += fmt.formatter( constraint ); return rv; } } return "?"; } /** * @throws PARSE_ERROR */ void DRC_ENGINE::loadRules( const wxFileName& aPath ) { if( aPath.FileExists() ) { std::vector rules; FILE* fp = wxFopen( aPath.GetFullPath(), wxT( "rt" ) ); if( fp ) { DRC_RULES_PARSER parser( fp, aPath.GetFullPath() ); parser.Parse( rules, m_reporter ); } // Copy the rules into the member variable afterwards so that if Parse() throws then // the possibly malformed rules won't contaminate the current ruleset. for( DRC_RULE* rule : rules ) m_rules.push_back( rule ); } } void DRC_ENGINE::compileRules() { ReportAux( wxString::Format( "Compiling Rules (%d rules): ", (int) m_rules.size() ) ); for( DRC_TEST_PROVIDER* provider : m_testProviders ) { ReportAux( wxString::Format( "- Provider: '%s': ", provider->GetName() ) ); drc_dbg( 7, "do prov %s", provider->GetName() ); for( DRC_CONSTRAINT_TYPE_T id : provider->GetConstraintTypes() ) { drc_dbg( 7, "do id %d", id ); if( m_constraintMap.find( id ) == m_constraintMap.end() ) m_constraintMap[ id ] = new std::vector(); for( DRC_RULE* rule : m_rules ) { DRC_RULE_CONDITION* condition = nullptr; bool compileOk = false; std::vector matchingConstraints; drc_dbg( 7, "Scan provider %s, rule %s", provider->GetName(), rule->m_Name ); if( rule->m_Condition && !rule->m_Condition->GetExpression().IsEmpty() ) { condition = rule->m_Condition; compileOk = condition->Compile( nullptr, 0, 0 ); // fixme } for( const DRC_CONSTRAINT& constraint : rule->m_Constraints ) { drc_dbg(7, "scan constraint id %d\n", constraint.m_Type ); if( constraint.m_Type != id ) continue; CONSTRAINT_WITH_CONDITIONS* rcons = new CONSTRAINT_WITH_CONDITIONS; rcons->layerTest = rule->m_LayerCondition; rcons->condition = condition; matchingConstraints.push_back( constraint ); rcons->constraint = constraint; rcons->parentRule = rule; m_constraintMap[ id ]->push_back( rcons ); } if( !matchingConstraints.empty() ) { ReportAux( wxString::Format( " |- Rule: '%s' ", rule->m_Name ) ); if( condition ) { ReportAux( wxString::Format( " |- condition: '%s' compile: %s", condition->GetExpression(), compileOk ? "OK" : "ERROR" ) ); } for (const DRC_CONSTRAINT& constraint : matchingConstraints ) { ReportAux( wxString::Format( " |- constraint: %s", formatConstraint( constraint ) ) ); } } } } } } /** * @throws PARSE_ERROR */ void DRC_ENGINE::InitEngine( const wxFileName& aRulePath ) { m_testProviders = DRC_TEST_PROVIDER_REGISTRY::Instance().GetTestProviders(); for( DRC_TEST_PROVIDER* provider : m_testProviders ) { ReportAux( wxString::Format( "Create DRC provider: '%s'", provider->GetName() ) ); provider->SetDRCEngine( this ); } for( DRC_RULE* rule : m_rules ) delete rule; m_rules.clear(); m_rulesValid = false; for( std::pair< DRC_CONSTRAINT_TYPE_T, std::vector* > pair : m_constraintMap ) { for( CONSTRAINT_WITH_CONDITIONS* constraintWithCondition : *pair.second ) delete constraintWithCondition; delete pair.second; } m_constraintMap.clear(); try // attempt to load full set of rules (implicit + user rules) { loadImplicitRules(); loadRules( aRulePath ); compileRules(); } catch( PARSE_ERROR& original_parse_error ) { try // try again with just our implicit rules { loadImplicitRules(); compileRules(); } catch( PARSE_ERROR& ignore ) { wxFAIL_MSG( "Compiling implict rules failed." ); } throw original_parse_error; } for( int ii = DRCE_FIRST; ii < DRCE_LAST; ++ii ) m_errorLimits[ ii ] = INT_MAX; m_rulesValid = true; } void DRC_ENGINE::RunTests( EDA_UNITS aUnits, bool aReportAllTrackErrors, bool aTestFootprints ) { m_userUnits = aUnits; // Note: set these first. The phase counts may be dependent on some of them. m_reportAllTrackErrors = aReportAllTrackErrors; m_testFootprints = aTestFootprints; if( m_progressReporter ) { int phases = 0; for( DRC_TEST_PROVIDER* provider : m_testProviders ) { if( provider->IsEnabled() ) phases += provider->GetNumPhases(); } m_progressReporter->AddPhases( phases ); } for( int ii = DRCE_FIRST; ii < DRCE_LAST; ++ii ) { if( m_designSettings->Ignore( ii ) ) m_errorLimits[ ii ] = 0; else m_errorLimits[ ii ] = INT_MAX; } for( ZONE_CONTAINER* zone : m_board->Zones() ) zone->CacheBoundingBox(); for( MODULE* module : m_board->Modules() ) { for( ZONE_CONTAINER* zone : module->Zones() ) zone->CacheBoundingBox(); module->BuildPolyCourtyards(); } for( DRC_TEST_PROVIDER* provider : m_testProviders ) { if( !provider->IsEnabled() ) continue; drc_dbg( 0, "Running test provider: '%s'\n", provider->GetName() ); ReportAux( wxString::Format( "Run DRC provider: '%s'", provider->GetName() ) ); if( !provider->Run() ) break; } } DRC_CONSTRAINT DRC_ENGINE::EvalRulesForItems( DRC_CONSTRAINT_TYPE_T aConstraintId, const BOARD_ITEM* a, const BOARD_ITEM* b, PCB_LAYER_ID aLayer, REPORTER* aReporter ) { #define REPORT( s ) { if( aReporter ) { aReporter->Report( s ); } } #define UNITS aReporter ? aReporter->GetUnits() : EDA_UNITS::MILLIMETRES /* * NOTE: all string manipulation MUST be kept inside the REPORT macro. It absolutely * kills performance when running bulk DRC tests (where aReporter is nullptr). */ if( aConstraintId == CLEARANCE_CONSTRAINT ) { // A PTH pad has a plated cylinder around the hole so copper clearances apply // whether or not there's a flashed pad. Not true for NPTHs. if( a->Type() == PCB_PAD_T ) { const D_PAD* pad = static_cast( a ); if( pad->GetAttribute() == PAD_ATTRIB_NPTH && !pad->FlashLayer( aLayer ) ) aConstraintId = HOLE_CLEARANCE_CONSTRAINT; } } const BOARD_CONNECTED_ITEM* connectedA = dynamic_cast( a ); const BOARD_CONNECTED_ITEM* connectedB = dynamic_cast( b ); const DRC_CONSTRAINT* constraintRef = nullptr; bool implicit = false; // Local overrides take precedence if( aConstraintId == CLEARANCE_CONSTRAINT ) { int overrideA = 0; int overrideB = 0; if( connectedA && connectedA->GetLocalClearanceOverrides( nullptr ) > 0 ) { overrideA = connectedA->GetLocalClearanceOverrides( &m_msg ); REPORT( "" ) REPORT( wxString::Format( _( "Local override on %s; clearance: %s." ), a->GetSelectMenuText( UNITS ), MessageTextFromValue( UNITS, overrideA ) ) ) } if( connectedB && connectedB->GetLocalClearanceOverrides( nullptr ) > 0 ) { overrideB = connectedB->GetLocalClearanceOverrides( &m_msg ); REPORT( "" ) REPORT( wxString::Format( _( "Local override on %s; clearance: %s." ), b->GetSelectMenuText( UNITS ), MessageTextFromValue( UNITS, overrideB ) ) ) } if( overrideA || overrideB ) { DRC_CONSTRAINT constraint( CLEARANCE_CONSTRAINT, m_msg ); constraint.m_Value.SetMin( std::max( overrideA, overrideB ) ); return constraint; } } auto processConstraint = [&]( const CONSTRAINT_WITH_CONDITIONS* c ) -> bool { implicit = c->parentRule && c->parentRule->m_Implicit; REPORT( "" ) if( aConstraintId == CLEARANCE_CONSTRAINT ) { int clearance = c->constraint.m_Value.Min(); REPORT( wxString::Format( _( "Checking %s; clearance: %s." ), c->constraint.GetName(), MessageTextFromValue( UNITS, clearance ) ) ) } else if( aConstraintId == COURTYARD_CLEARANCE_CONSTRAINT ) { int clearance = c->constraint.m_Value.Min(); REPORT( wxString::Format( _( "Checking %s; courtyard clearance: %s." ), c->constraint.GetName(), MessageTextFromValue( UNITS, clearance ) ) ) } else if( aConstraintId == SILK_CLEARANCE_CONSTRAINT ) { int clearance = c->constraint.m_Value.Min(); REPORT( wxString::Format( _( "Checking %s; silk clearance: %s." ), c->constraint.GetName(), MessageTextFromValue( UNITS, clearance ) ) ) } else if( aConstraintId == HOLE_CLEARANCE_CONSTRAINT ) { int clearance = c->constraint.m_Value.Min(); REPORT( wxString::Format( _( "Checking %s; hole clearance: %s." ), c->constraint.GetName(), MessageTextFromValue( UNITS, clearance ) ) ) } else if( aConstraintId == EDGE_CLEARANCE_CONSTRAINT ) { int clearance = c->constraint.m_Value.Min(); REPORT( wxString::Format( _( "Checking %s; edge clearance: %s." ), c->constraint.GetName(), MessageTextFromValue( UNITS, clearance ) ) ) } else { REPORT( wxString::Format( _( "Checking %s." ), c->constraint.GetName() ) ) } if( aConstraintId == CLEARANCE_CONSTRAINT ) { if( implicit && ( isKeepoutZone( a ) || isKeepoutZone( b ) ) ) { REPORT( _( "Board and netclass clearances not applied to keepout zones" ) ); return true; } } else if( aConstraintId == DISALLOW_CONSTRAINT ) { int mask; if( a->GetFlags() & HOLE_PROXY ) { mask = DRC_DISALLOW_HOLES; } else if( a->Type() == PCB_VIA_T ) { if( static_cast( a )->GetViaType() == VIATYPE::BLIND_BURIED ) mask = DRC_DISALLOW_VIAS | DRC_DISALLOW_BB_VIAS; else if( static_cast( a )->GetViaType() == VIATYPE::MICROVIA ) mask = DRC_DISALLOW_VIAS | DRC_DISALLOW_MICRO_VIAS; else mask = DRC_DISALLOW_VIAS; } else { switch( a->Type() ) { case PCB_TRACE_T: mask = DRC_DISALLOW_TRACKS; break; case PCB_ARC_T: mask = DRC_DISALLOW_TRACKS; break; case PCB_PAD_T: mask = DRC_DISALLOW_PADS; break; case PCB_MODULE_T: mask = DRC_DISALLOW_FOOTPRINTS; break; case PCB_SHAPE_T: mask = DRC_DISALLOW_GRAPHICS; break; case PCB_FP_SHAPE_T: mask = DRC_DISALLOW_GRAPHICS; break; case PCB_TEXT_T: mask = DRC_DISALLOW_TEXTS; break; case PCB_FP_TEXT_T: mask = DRC_DISALLOW_TEXTS; break; case PCB_ZONE_AREA_T: mask = DRC_DISALLOW_ZONES; break; case PCB_FP_ZONE_AREA_T: mask = DRC_DISALLOW_ZONES; break; case PCB_LOCATE_HOLE_T: mask = DRC_DISALLOW_HOLES; break; default: mask = 0; break; } } if( ( c->constraint.m_DisallowFlags & mask ) == 0 ) { if( implicit ) REPORT( _( "Keepout constraint not met." ) ) else REPORT( _( "Disallow constraint not met." ) ) return false; } if( !( c->layerTest & a->GetLayerSet() ).any() ) { if( implicit ) { REPORT( _( "Keepout layer(s) not matched." ) ) } else if( c->parentRule ) { REPORT( wxString::Format( _( "Rule layer \"%s\" not matched." ), c->parentRule->m_LayerSource ) ) REPORT( "Rule ignored." ) } else { REPORT( _( "Rule layer not matched." ) ) REPORT( "Rule ignored." ) } return false; } } if( aLayer != UNDEFINED_LAYER && !c->layerTest.test( aLayer ) ) { if( implicit ) { REPORT( "Constraint layer not matched." ) } else if( c->parentRule ) { REPORT( wxString::Format( _( "Rule layer \"%s\" not matched." ), c->parentRule->m_LayerSource ) ) REPORT( "Rule ignored." ) } else { REPORT( _( "Rule layer not matched." ) ) REPORT( "Rule ignored." ) } return false; } if( !c->condition || c->condition->GetExpression().IsEmpty() ) { REPORT( implicit ? _( "Unconditional constraint applied." ) : _( "Unconditional rule applied." ) ); constraintRef = &c->constraint; return true; } else { if( implicit ) { // Don't report on implicit rule conditions; they're synthetic. } else { REPORT( wxString::Format( _( "Checking rule condition \"%s\"." ), c->condition->GetExpression() ) ) } if( c->condition->EvaluateFor( a, b, aLayer, aReporter ) ) { REPORT( implicit ? _( "Constraint applied." ) : _( "Rule applied; overrides previous constraints." ) ) constraintRef = &c->constraint; return true; } else { REPORT( implicit ? _( "Membership not satisfied; constraint ignored." ) : _( "Condition not satisfied; rule ignored." ) ) return false; } } }; if( m_constraintMap.count( aConstraintId ) ) { std::vector* ruleset = m_constraintMap[ aConstraintId ]; if( aReporter ) { // We want to see all results so process in "natural" order for( int ii = 0; ii < (int) ruleset->size(); ++ii ) { processConstraint( ruleset->at( ii ) ); } } else { // Last matching rule wins, so process in reverse order and quit when match found for( int ii = (int) ruleset->size() - 1; ii >= 0; --ii ) { if( processConstraint( ruleset->at( ii ) ) ) break; } } } // Unfortunately implicit rules don't work for local clearances (such as zones) because // they have to be max'ed with netclass values (which are already implicit rules), and our // rule selection paradigm is "winner takes all". if( constraintRef && aConstraintId == CLEARANCE_CONSTRAINT && implicit ) { int global = constraintRef->m_Value.Min(); int localA = connectedA ? connectedA->GetLocalClearance( nullptr ) : 0; int localB = connectedB ? connectedB->GetLocalClearance( nullptr ) : 0; int clearance = global; if( localA > 0 ) { REPORT( "" ) REPORT( wxString::Format( _( "Local clearance on %s; clearance: %s." ), a->GetSelectMenuText( UNITS ), MessageTextFromValue( UNITS, localA ) ) ) if( localA > clearance ) clearance = connectedA->GetLocalClearance( &m_msg ); } if( localB > 0 ) { REPORT( "" ) REPORT( wxString::Format( _( "Local clearance on %s; clearance: %s." ), b->GetSelectMenuText( UNITS ), MessageTextFromValue( UNITS, localB ) ) ) if( localB > clearance ) clearance = connectedB->GetLocalClearance( &m_msg ); } if( localA > global || localB > global ) { DRC_CONSTRAINT constraint( CLEARANCE_CONSTRAINT, m_msg ); constraint.m_Value.SetMin( clearance ); return constraint; } } static DRC_CONSTRAINT nullConstraint( NULL_CONSTRAINT ); nullConstraint.m_DisallowFlags = 0; return constraintRef ? *constraintRef : nullConstraint; #undef REPORT #undef UNITS } bool DRC_ENGINE::IsErrorLimitExceeded( int error_code ) { assert( error_code >= 0 && error_code <= DRCE_LAST ); return m_errorLimits[ error_code ] <= 0; } void DRC_ENGINE::ReportViolation( const std::shared_ptr& aItem, wxPoint aPos ) { m_errorLimits[ aItem->GetErrorCode() ] -= 1; if( m_violationHandler ) m_violationHandler( aItem, aPos ); if( m_reporter ) { wxString msg = wxString::Format( "Test '%s': %s (code %d)", aItem->GetViolatingTest()->GetName(), aItem->GetErrorMessage(), aItem->GetErrorCode() ); DRC_RULE* rule = aItem->GetViolatingRule(); if( rule ) msg += wxString::Format( ", violating rule: '%s'", rule->m_Name ); m_reporter->Report( msg ); wxString violatingItemsStr = "Violating items: "; m_reporter->Report( wxString::Format( " |- violating position (%d, %d)", aPos.x, aPos.y ) ); } } void DRC_ENGINE::ReportAux ( const wxString& aStr ) { if( !m_reporter ) return; m_reporter->Report( aStr, RPT_SEVERITY_INFO ); } bool DRC_ENGINE::ReportProgress( double aProgress ) { if( !m_progressReporter ) return true; m_progressReporter->SetCurrentProgress( aProgress ); return m_progressReporter->KeepRefreshing( false ); } bool DRC_ENGINE::ReportPhase( const wxString& aMessage ) { if( !m_progressReporter ) return true; m_progressReporter->AdvancePhase( aMessage ); return m_progressReporter->KeepRefreshing( false ); } #if 0 DRC_CONSTRAINT DRC_ENGINE::GetWorstGlobalConstraint( DRC_CONSTRAINT_TYPE_T ruleID ) { DRC_CONSTRAINT rv; rv.m_Value.SetMin( std::numeric_limits::max() ); rv.m_Value.SetMax( std::numeric_limits::min() ); for( auto rule : QueryRulesById( ruleID ) ) { auto mm = rule->GetConstraint().m_Value; if( mm.HasMax() ) rv.m_Value.SetMax( std::max( mm.Max(), rv.m_Value.Max() ) ); if( mm.HasMin() ) rv.m_Value.SetMin( std::min( mm.Min(), rv.m_Value.Min() ) ); } return rv; } #endif std::vector DRC_ENGINE::QueryConstraintsById( DRC_CONSTRAINT_TYPE_T constraintID ) { std::vector rv; if( m_constraintMap.count( constraintID ) ) { for ( CONSTRAINT_WITH_CONDITIONS* c : *m_constraintMap[constraintID] ) rv.push_back( c->constraint ); } return rv; } bool DRC_ENGINE::HasRulesForConstraintType( DRC_CONSTRAINT_TYPE_T constraintID ) { //drc_dbg(10,"hascorrect id %d size %d\n", ruleID, m_ruleMap[ruleID]->sortedRules.size( ) ); if( m_constraintMap.count( constraintID ) ) return m_constraintMap[ constraintID ]->size() > 0; return false; } bool DRC_ENGINE::QueryWorstConstraint( DRC_CONSTRAINT_TYPE_T aConstraintId, DRC_CONSTRAINT& aConstraint ) { int worst = 0; for( const DRC_CONSTRAINT& constraint : QueryConstraintsById( aConstraintId ) ) { if( constraint.GetValue().HasMin() ) { int current = constraint.GetValue().Min(); if( current > worst ) { worst = current; aConstraint = constraint; } } } return worst > 0; } // fixme: move two functions below to pcbcommon? static int matchDpSuffix( const wxString& aNetName, wxString& aComplementNet, wxString& aBaseDpName ) { int rv = 0; if( aNetName.EndsWith( "+" ) ) { aComplementNet = "-"; rv = 1; } else if( aNetName.EndsWith( "P" ) ) { aComplementNet = "N"; rv = 1; } else if( aNetName.EndsWith( "-" ) ) { aComplementNet = "+"; rv = -1; } else if( aNetName.EndsWith( "N" ) ) { aComplementNet = "P"; rv = -1; } // Match P followed by 2 digits else if( aNetName.Right( 2 ).IsNumber() && aNetName.Right( 3 ).Left( 1 ) == "P" ) { aComplementNet = "N" + aNetName.Right( 2 ); rv = 1; } // Match P followed by 1 digit else if( aNetName.Right( 1 ).IsNumber() && aNetName.Right( 2 ).Left( 1 ) == "P" ) { aComplementNet = "N" + aNetName.Right( 1 ); rv = 1; } // Match N followed by 2 digits else if( aNetName.Right( 2 ).IsNumber() && aNetName.Right( 3 ).Left( 1 ) == "N" ) { aComplementNet = "P" + aNetName.Right( 2 ); rv = -1; } // Match N followed by 1 digit else if( aNetName.Right( 1 ).IsNumber() && aNetName.Right( 2 ).Left( 1 ) == "N" ) { aComplementNet = "P" + aNetName.Right( 1 ); rv = -1; } if( rv != 0 ) { aBaseDpName = aNetName.Left( aNetName.Length() - aComplementNet.Length() ); aComplementNet = aBaseDpName + aComplementNet; } return rv; } int DRC_ENGINE::IsNetADiffPair( BOARD* aBoard, NETINFO_ITEM* aNet, int& aNetP, int& aNetN ) { wxString refName = aNet->GetNetname(); wxString dummy, coupledNetName; if( int polarity = matchDpSuffix( refName, coupledNetName, dummy ) ) { NETINFO_ITEM* net = aBoard->FindNet( coupledNetName ); if( !net ) return false; if( polarity > 0 ) { aNetP = aNet->GetNet(); aNetN = net->GetNet(); } else { aNetP = net->GetNet(); aNetN = aNet->GetNet(); } return true; } return false; } DRC_TEST_PROVIDER* DRC_ENGINE::GetTestProvider( const wxString& name ) const { for( auto prov : m_testProviders ) if( name == prov->GetName() ) return prov; return nullptr; }