/* * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2022 KiCad Developers. * * 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 3 * 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/gpl-3.0.html * or you may search the http://www.gnu.org website for the version 3 license, * or you may write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* Checks for copper connections that are less than the specified minimum width Errors generated: - DRCE_CONNECTION_WIDTH */ class DRC_TEST_PROVIDER_CONNECTION_WIDTH : public DRC_TEST_PROVIDER { public: DRC_TEST_PROVIDER_CONNECTION_WIDTH() { } virtual ~DRC_TEST_PROVIDER_CONNECTION_WIDTH() { } virtual bool Run() override; virtual const wxString GetName() const override { return wxT( "copper width" ); }; virtual const wxString GetDescription() const override { return wxT( "Checks copper nets for connections less than a specified minimum" ); } private: wxString layerDesc( PCB_LAYER_ID aLayer ); }; class POLYGON_TEST { public: POLYGON_TEST( int aLimit ) : m_limit( aLimit ) { }; bool FindPairs( const SHAPE_LINE_CHAIN& aPoly ) { m_hits.clear(); m_vertices.clear(); m_bbox = aPoly.BBox(); createList( aPoly ); m_vertices.front().updateList(); Vertex* p = m_vertices.front().next; std::set all_hits; while( p != &m_vertices.front() ) { Vertex* match = nullptr; // Only run the expensive search if we don't already have a match for the point if( ( all_hits.empty() || all_hits.count( p ) == 0 ) && ( match = getKink( p ) ) ) { if( !all_hits.count( match ) && m_hits.emplace( p->i, match->i ).second ) { all_hits.emplace( p ); all_hits.emplace( match ); all_hits.emplace( p->next ); all_hits.emplace( p->prev ); all_hits.emplace( match->next ); all_hits.emplace( match->prev ); } } p = p->next; } return !m_hits.empty(); } std::set>& GetVertices() { return m_hits; } private: struct Vertex { Vertex( int aIndex, double aX, double aY, POLYGON_TEST* aParent ) : i( aIndex ), x( aX ), y( aY ), parent( aParent ) { } Vertex& operator=( const Vertex& ) = delete; Vertex& operator=( Vertex&& ) = delete; bool operator==( const Vertex& rhs ) const { return this->x == rhs.x && this->y == rhs.y; } bool operator!=( const Vertex& rhs ) const { return !( *this == rhs ); } /** * Remove the node from the linked list and z-ordered linked list. */ void remove() { next->prev = prev; prev->next = next; if( prevZ ) prevZ->nextZ = nextZ; if( nextZ ) nextZ->prevZ = prevZ; next = nullptr; prev = nullptr; nextZ = nullptr; prevZ = nullptr; } void updateOrder() { if( !z ) z = parent->zOrder( x, y ); } /** * After inserting or changing nodes, this function should be called to * remove duplicate vertices and ensure z-ordering is correct. */ void updateList() { Vertex* p = next; while( p != this ) { /** * Remove duplicates */ if( *p == *p->next ) { p = p->prev; p->next->remove(); if( p == p->next ) break; } p->updateOrder(); p = p->next; }; updateOrder(); zSort(); } /** * Sort all vertices in this vertex's list by their Morton code. */ void zSort() { std::deque queue; queue.push_back( this ); for( Vertex* p = next; p && p != this; p = p->next ) queue.push_back( p ); std::sort( queue.begin(), queue.end(), []( const Vertex* a, const Vertex* b ) { return a->z < b->z; } ); Vertex* prev_elem = nullptr; for( Vertex* elem : queue ) { if( prev_elem ) prev_elem->nextZ = elem; elem->prevZ = prev_elem; prev_elem = elem; } prev_elem->nextZ = nullptr; } const int i; const double x; const double y; POLYGON_TEST* parent; // previous and next vertices nodes in a polygon ring Vertex* prev = nullptr; Vertex* next = nullptr; // z-order curve value int32_t z = 0; // previous and next nodes in z-order Vertex* prevZ = nullptr; Vertex* nextZ = nullptr; }; /** * Calculate the Morton code of the Vertex * http://www.graphics.stanford.edu/~seander/bithacks.html#InterleaveBMN * */ int32_t zOrder( const double aX, const double aY ) const { int32_t x = static_cast( 32767.0 * ( aX - m_bbox.GetX() ) / m_bbox.GetWidth() ); int32_t y = static_cast( 32767.0 * ( aY - m_bbox.GetY() ) / m_bbox.GetHeight() ); x = ( x | ( x << 8 ) ) & 0x00FF00FF; x = ( x | ( x << 4 ) ) & 0x0F0F0F0F; x = ( x | ( x << 2 ) ) & 0x33333333; x = ( x | ( x << 1 ) ) & 0x55555555; y = ( y | ( y << 8 ) ) & 0x00FF00FF; y = ( y | ( y << 4 ) ) & 0x0F0F0F0F; y = ( y | ( y << 2 ) ) & 0x33333333; y = ( y | ( y << 1 ) ) & 0x55555555; return x | ( y << 1 ); } /** * Checks to see if there is a "substantial" protrusion in each polygon produced by the cut from * aA to aB. Substantial in this case means that the polygon bulges out to a wider cross-section * than the distance from aA to aB * @param aA Starting point in the polygon * @param aB Ending point in the polygon * @return True if the two polygons are both "substantial" */ bool isSubstantial( Vertex* aA, Vertex* aB ) const { // `directions` is a bitfield where // bit 0 = pos y // bit 1 = neg y // bit 2 = pos x // bit 3 = neg x // So, once directions = 15, we have all directions int directions = 0; // This is a failsafe in case of invalid lists. Never check // more than the total number of points in m_vertices size_t checked = 0; size_t total_pts = m_vertices.size(); Vertex* p0 = aA; Vertex* p; Vertex* nz = p0->nextZ; Vertex* pz = p0->prevZ; auto same_point = []( const Vertex* a, const Vertex* b ) -> bool { return a && b && a->x == b->x && a->y == b->y; }; // If we hit a fracture point, we want to continue around the // edge we are working on and not switch to the pair edge // However, this will depend on which direction the initial // fracture hit is. If we find that we skip directly to // a new fracture point, then we know that we are proceeding // in the wrong direction from the fracture and should // fall through to the next point if( same_point( p0, nz ) && !( same_point( nz->next, nz->next->prevZ ) || same_point( nz->next, nz->next->nextZ ) ) ) p = nz->next; else if( same_point( p0, pz ) && !( same_point( pz->next, pz->next->prevZ ) || same_point( pz->next, pz->next->nextZ ) ) ) p = pz->next; else p = p0->next; while( p0 != aB && checked < total_pts && directions != 15 ) { int bit2x = std::signbit( p->x - p0->x ); int bit2y = std::signbit( p->y - p0->y ); directions |= ( 1 << ( 2 + bit2x ) ) + ( 1 << bit2y ); p0 = p; if( same_point( p0, p0->nextZ ) ) p = p->nextZ->next; else if( same_point( p0, p0->prevZ ) ) p = p->prevZ->next; else p = p->next; ++checked; } wxCHECK_MSG( checked < total_pts, false, wxT( "Invalid polygon detected. Missing points to check" ) ); if( directions != 15 ) return false; p0 = aA; nz = p0->nextZ; pz = p0->prevZ; if( nz && same_point( p0, nz ) && !( same_point( nz->prev, nz->prev->nextZ ) || same_point( nz->prev, nz->prev->prevZ ) ) ) p = nz->prev; else if( pz && same_point( p0, pz ) && !( same_point( pz->prev, pz->prev->nextZ ) || same_point( pz->prev, pz->prev->prevZ ) ) ) p = pz->prev; else p = p0->prev; directions = 0; checked = 0; while( p0 != aB && checked < total_pts && directions != 15 ) { int bit2x = std::signbit( p->x - p0->x ); int bit2y = std::signbit( p->y - p0->y ); directions |= ( 1 << ( 2 + bit2x ) ) + ( 1 << bit2y ); p0 = p; if( same_point( p, p->nextZ ) ) p = p->nextZ->prev; else if( same_point( p, p->prevZ ) ) p = p->prevZ->prev; else p = p->prev; ++checked; } wxCHECK_MSG( checked < total_pts, false, wxT( "Invalid polygon detected. Missing points to check" ) ); return ( directions == 15 ); } /** * Take a #SHAPE_LINE_CHAIN and links each point into a circular, doubly-linked list. */ Vertex* createList( const SHAPE_LINE_CHAIN& points ) { Vertex* tail = nullptr; double sum = 0.0; // Check for winding order for( int i = 0; i < points.PointCount(); i++ ) { VECTOR2D p1 = points.CPoint( i ); VECTOR2D p2 = points.CPoint( i + 1 ); sum += ( ( p2.x - p1.x ) * ( p2.y + p1.y ) ); } if( sum > 0.0 ) for( int i = points.PointCount() - 1; i >= 0; i--) tail = insertVertex( i, points.CPoint( i ), tail ); else for( int i = 0; i < points.PointCount(); i++ ) tail = insertVertex( i, points.CPoint( i ), tail ); if( tail && ( *tail == *tail->next ) ) { tail->next->remove(); } return tail; } Vertex* getKink( Vertex* aPt ) const { // The point needs to be at a concave surface if( locallyInside( aPt->prev, aPt->next ) ) return nullptr; // z-order range for the current point ± limit bounding box const int32_t maxZ = zOrder( aPt->x + m_limit, aPt->y + m_limit ); const int32_t minZ = zOrder( aPt->x - m_limit, aPt->y - m_limit ); const SEG::ecoord limit2 = SEG::Square( m_limit ); // first look for points in increasing z-order Vertex* p = aPt->nextZ; long min_dist = std::numeric_limits::max(); Vertex* retval = nullptr; while( p && p->z <= maxZ ) { int delta_i = std::abs( p->i - aPt->i ); VECTOR2D diff( p->x - aPt->x, p->y - aPt->y ); SEG::ecoord dist2 = diff.SquaredEuclideanNorm(); if( delta_i > 1 && dist2 < limit2 && dist2 < min_dist && dist2 > 0.0 && locallyInside( p, aPt ) && isSubstantial( p, aPt ) ) { min_dist = dist2; retval = p; } p = p->nextZ; } p = aPt->prevZ; while( p && p->z >= minZ ) { int delta_i = std::abs( p->i - aPt->i ); VECTOR2D diff( p->x - aPt->x, p->y - aPt->y ); SEG::ecoord dist2 = diff.SquaredEuclideanNorm(); if( delta_i > 1 && dist2 < limit2 && dist2 < min_dist && dist2 > 0.0 && locallyInside( p, aPt ) && isSubstantial( p, aPt ) ) { min_dist = dist2; retval = p; } p = p->prevZ; } return retval; } /** * Return the twice the signed area of the triangle formed by vertices p, q, and r. */ double area( const Vertex* p, const Vertex* q, const Vertex* r ) const { return ( q->y - p->y ) * ( r->x - q->x ) - ( q->x - p->x ) * ( r->y - q->y ); } /** * Check whether the segment from vertex a -> vertex b is inside the polygon * around the immediate area of vertex a. * * We don't define the exact area over which the segment is inside but it is guaranteed to * be inside the polygon immediately adjacent to vertex a. * * @return true if the segment from a->b is inside a's polygon next to vertex a. */ bool locallyInside( const Vertex* a, const Vertex* b ) const { if( area( a->prev, a, a->next ) < 0 ) return area( a, b, a->next ) >= 0 && area( a, a->prev, b ) >= 0; else return area( a, b, a->prev ) < 0 || area( a, a->next, b ) < 0; } /** * Create an entry in the vertices lookup and optionally inserts the newly created vertex * into an existing linked list. * * @return a pointer to the newly created vertex. */ Vertex* insertVertex( int aIndex, const VECTOR2I& pt, Vertex* last ) { m_vertices.emplace_back( aIndex, pt.x, pt.y, this ); Vertex* p = &m_vertices.back(); if( !last ) { p->prev = p; p->next = p; } else { p->next = last->next; p->prev = last; last->next->prev = p; last->next = p; } return p; } private: int m_limit; BOX2I m_bbox; std::deque m_vertices; std::set> m_hits; }; wxString DRC_TEST_PROVIDER_CONNECTION_WIDTH::layerDesc( PCB_LAYER_ID aLayer ) { return wxString::Format( wxT( "(%s)" ), m_drcEngine->GetBoard()->GetLayerName( aLayer ) ); } bool DRC_TEST_PROVIDER_CONNECTION_WIDTH::Run() { if( m_drcEngine->IsErrorLimitExceeded( DRCE_CONNECTION_WIDTH ) ) return true; // Continue with other tests if( !reportPhase( _( "Checking nets for minimum connection width..." ) ) ) return false; // DRC cancelled LSET copperLayerSet = m_drcEngine->GetBoard()->GetEnabledLayers() & LSET::AllCuMask(); LSEQ copperLayers = copperLayerSet.Seq(); BOARD* board = m_drcEngine->GetBoard(); BOARD_DESIGN_SETTINGS& bds = board->GetDesignSettings(); PROGRESS_REPORTER* reporter = m_drcEngine->GetProgressReporter(); if( reporter && reporter->IsCancelled() ) return false; // DRC cancelled std::map>> net_items; DRC_RTREE* tree = board->m_CopperItemRTreeCache.get(); auto min_checker = [&](const std::set& aItems, PCB_LAYER_ID aLayer ) -> size_t { if( reporter && reporter->IsCancelled() ) return 0; SHAPE_POLY_SET poly; for( BOARD_CONNECTED_ITEM* item : aItems ) item->TransformShapeWithClearanceToPolygon( poly, aLayer, 0, ARC_HIGH_DEF, ERROR_OUTSIDE); poly.Fracture(SHAPE_POLY_SET::PM_FAST); int minimum_width = bds.m_MinConn; POLYGON_TEST test( minimum_width ); for( int ii = 0; ii < poly.OutlineCount(); ++ii ) { const SHAPE_LINE_CHAIN& chain = poly.COutline( ii ); test.FindPairs( chain ); auto& ret = test.GetVertices(); for( const std::pair& pt : ret ) { VECTOR2I location = ( chain.CPoint( pt.first ) + chain.CPoint( pt.second ) ) / 2; int dist = ( chain.CPoint( pt.first ) - chain.CPoint( pt.second ) ).EuclideanNorm(); std::set items = tree->GetObjectsAt( location, aLayer, minimum_width ); std::shared_ptr drce = DRC_ITEM::Create( DRCE_CONNECTION_WIDTH ); wxString msg; msg.Printf( _( "Minimum connection width %s; actual %s" ), MessageTextFromValue( userUnits(), minimum_width ), MessageTextFromValue( userUnits(), dist ) ); drce->SetErrorMessage( msg + wxS( " " ) + layerDesc( aLayer ) ); for( BOARD_ITEM* item : items ) { if( item->HitTest( location, minimum_width ) ) drce->AddItem( item ); } reportViolation( drce, location, aLayer ); } } if( reporter ) reporter->AdvanceProgress(); return 1; }; for( PCB_LAYER_ID layer : copperLayers ) { auto& layer_items = net_items[layer]; for( ZONE* zone : board->Zones() ) { if( !zone->GetIsRuleArea() && zone->IsOnLayer( layer ) ) layer_items[zone->GetNetCode()].emplace( zone ); } for( PCB_TRACK* track : board->Tracks() ) { if( PCB_VIA* via = dyn_cast( track ) ) { if( via->FlashLayer( static_cast( layer ) ) ) layer_items[via->GetNetCode()].emplace( via ); } else if( track->IsOnLayer( layer ) ) { layer_items[track->GetNetCode()].emplace( track ); } } for( FOOTPRINT* fp : board->Footprints() ) { for( PAD* pad : fp->Pads() ) { if( pad->FlashLayer( static_cast( layer ) ) ) layer_items[pad->GetNetCode()].emplace( pad ); } for( FP_ZONE* zone : fp->Zones() ) { if( !zone->GetIsRuleArea() && zone->IsOnLayer( layer ) ) layer_items[zone->GetNetCode()].emplace( zone ); } } } thread_pool& tp = GetKiCadThreadPool(); std::vector> returns; size_t return_count = 0; for( auto& layer_items : net_items ) return_count += layer_items.second.size(); returns.reserve( return_count ); if( reporter ) reporter->SetMaxProgress( return_count ); for( auto& layer_items : net_items ) { for( const auto& items : layer_items.second ) { returns.emplace_back( tp.submit( min_checker, items.second, layer_items.first ) ); } } for( auto& retval : returns ) { std::future_status status; do { status = retval.wait_for( std::chrono::milliseconds( 100 ) ); } while( status != std::future_status::ready ); } return true; } namespace detail { static DRC_REGISTER_TEST_PROVIDER dummy; }