/* * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2021 Jean-Pierre Charras, jp.charras at wanadoo.fr * Copyright (C) 2023 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 "teardrop.h" #include #include #include #include #include // The first priority level of a teardrop area (arbitrary value) #define MAGIC_TEARDROP_ZONE_ID 30000 TEARDROP_MANAGER::TEARDROP_MANAGER( BOARD* aBoard, PCB_EDIT_FRAME* aFrame ) { m_board = aBoard; m_prmsList = m_board->GetDesignSettings().GetTeardropParamsList(); m_tolerance = 0; } // Build a zone teardrop static ZONE_SETTINGS s_default_settings; // Use zone default settings for teardrop ZONE* TEARDROP_MANAGER::createTeardrop( TEARDROP_VARIANT aTeardropVariant, std::vector& aPoints, PCB_TRACK* aTrack) const { ZONE* teardrop = new ZONE( m_board ); // teardrop settings are the last zone settings used by a zone dialog. // override them by default. s_default_settings.ExportSetting( *teardrop, false ); // Add zone properties (priority will be fixed later) teardrop->SetTeardropAreaType( aTeardropVariant == TD_TYPE_PADVIA ? TEARDROP_TYPE::TD_VIAPAD : TEARDROP_TYPE::TD_TRACKEND ); teardrop->SetLayer( aTrack->GetLayer() ); teardrop->SetNetCode( aTrack->GetNetCode() ); teardrop->SetLocalClearance( 0 ); teardrop->SetMinThickness( pcbIUScale.mmToIU( 0.0254 ) ); // The minimum zone thickness teardrop->SetPadConnection( ZONE_CONNECTION::FULL ); teardrop->SetIsFilled( false ); teardrop->SetZoneName( aTeardropVariant == TD_TYPE_PADVIA ? MAGIC_TEARDROP_PADVIA_NAME : MAGIC_TEARDROP_TRACK_NAME ); teardrop->SetIslandRemovalMode( ISLAND_REMOVAL_MODE::NEVER ); SHAPE_POLY_SET* outline = teardrop->Outline(); outline->NewOutline(); for( VECTOR2I pt: aPoints ) outline->Append(pt.x, pt.y); // Used in priority calculations: teardrop->CalculateFilledArea(); return teardrop; } int TEARDROP_MANAGER::SetTeardrops( BOARD_COMMIT* aCommitter, bool aFollowTracks ) { // Init parameters: m_tolerance = pcbIUScale.mmToIU( 0.01 ); int count = 0; // Number of created teardrop // Old teardrops must be removed, to ensure a clean teardrop rebuild int removed_cnt = RemoveTeardrops( aCommitter, false ); // get vias, PAD_ATTRIB_PTH and others if aIncludeNotDrilled == true // (custom pads are not collected) std::vector< VIAPAD > viapad_list; if( m_prmsList->m_TargetViasPads ) collectVias( viapad_list ); collectPadsCandidate( viapad_list, m_prmsList->m_TargetViasPads, m_prmsList->m_UseRoundShapesOnly, m_prmsList->m_TargetPadsWithNoHole ); TRACK_BUFFER trackLookupList; if( aFollowTracks ) { // Build the track list (only straight lines) for( PCB_TRACK* track: m_board->Tracks() ) { if( track->Type() == PCB_TRACE_T || track->Type() == PCB_ARC_T) { int netcode = track->GetNetCode(); int layer = track->GetLayer(); trackLookupList.AddTrack( track, layer, netcode ); } } } std::vector< ZONE*> teardrops; collectTeardrops( teardrops ); for( PCB_TRACK* track : m_board->Tracks() ) { if( ! (track->Type() == PCB_TRACE_T || track->Type() == PCB_ARC_T ) ) continue; // Search for a padvia connected to track, with one end inside and one end outside // if both track ends are inside or outside, one cannot build a teadrop for( VIAPAD& viapad: viapad_list ) { // Pad and track must be on the same layer if( !viapad.IsOnLayer( track->GetLayer() ) ) continue; bool start_in_pad = viapad.m_Parent->HitTest( track->GetStart() ); bool end_in_pad = viapad.m_Parent->HitTest( track->GetEnd() ); if( end_in_pad == start_in_pad ) // the track is inside or outside the via pad. Cannot create a teardrop continue; // A pointer to one of available m_Parameters items TEARDROP_PARAMETERS* currParams; if( viapad.m_IsRound ) currParams = m_prmsList->GetParameters( TARGET_ROUND ); else currParams = m_prmsList->GetParameters( TARGET_RECT ); // Ensure a teardrop shape can be built: // The track width must be < teardrop height if( track->GetWidth() >= currParams->m_TdMaxHeight || track->GetWidth() >= viapad.m_Width * currParams->m_HeightRatio ) continue; // Ensure also it is not filtered by a too high track->GetWidth()/viapad.m_Width ratio if( track->GetWidth() >= viapad.m_Width * currParams->m_WidthtoSizeFilterRatio ) continue; // Skip case where pad/via and the track is within a copper zone with the same net // (and the pad can be connected by the zone thermal relief ) if( !m_prmsList->m_TdOnPadsInZones && isViaAndTrackInSameZone( viapad, track ) ) continue; std::vector points; bool success = computeTeardropPolygonPoints( currParams, points, track, viapad, aFollowTracks, trackLookupList ); if( success ) { ZONE* new_teardrop = createTeardrop( TD_TYPE_PADVIA, points, track ); m_board->Add( new_teardrop, ADD_MODE::BULK_INSERT ); m_createdTdList.push_back( new_teardrop ); if( aCommitter ) aCommitter->Added( new_teardrop ); count += 1; } } } int track2trackCount = 0; if( m_prmsList->m_TargetTrack2Track ) track2trackCount = addTeardropsOnTracks( aCommitter ); // Now set priority of teardrops now all teardrops are added setTeardropPriorities(); // Fill teardrop shapes. This is a rough calculation, just to show a filled // shape on screen, but most of time this is a good shape. // Exact shapes can be calculated only on a full zone refill, **much more** time consuming if( m_createdTdList.size() ) { int epsilon = pcbIUScale.mmToIU( 0.001 ); for( ZONE* zone: m_createdTdList ) { int half_min_width = zone->GetMinThickness() / 2; int numSegs = GetArcToSegmentCount( half_min_width, pcbIUScale.mmToIU( 0.005 ), FULL_CIRCLE ); SHAPE_POLY_SET filledPolys = *zone->Outline(); filledPolys.Deflate( half_min_width - epsilon, numSegs ); // Re-inflate after pruning of areas that don't meet minimum-width criteria if( half_min_width - epsilon > epsilon ) filledPolys.Inflate( half_min_width - epsilon, numSegs ); zone->SetFilledPolysList( zone->GetFirstLayer(), filledPolys ); } } if( count || removed_cnt || track2trackCount ) { if( aCommitter ) aCommitter->Push( _( "Add teardrops" ) ); // Note: // Refill zones can be made only with clean data, especially connectivity data, // therefore only after changes are pushed to avoid crashes in some cases } return count + track2trackCount; } void TEARDROP_MANAGER::setTeardropPriorities() { // Note: a teardrop area is on only one layer, so using GetFirstLayer() is OK // to know the zone layer of a teardrop int priority_base = MAGIC_TEARDROP_ZONE_ID; // The sort function to sort by increasing copper layers. Group by layers. // For same layers sort by decreasing areas struct { bool operator()(ZONE* a, ZONE* b) const { if( a->GetFirstLayer() == b->GetFirstLayer() ) return a->GetOutlineArea() > b->GetOutlineArea(); return a->GetFirstLayer() < b->GetFirstLayer(); } } compareLess; for( ZONE* td: m_createdTdList ) td->CalculateOutlineArea(); std::sort( m_createdTdList.begin(), m_createdTdList.end(), compareLess ); int curr_layer = -1; for( ZONE* td: m_createdTdList ) { if( td->GetFirstLayer() != curr_layer ) { curr_layer = td->GetFirstLayer(); priority_base = MAGIC_TEARDROP_ZONE_ID; } td->SetAssignedPriority( priority_base++ ); } } int TEARDROP_MANAGER::addTeardropsOnTracks( BOARD_COMMIT* aCommitter ) { TRACK_BUFFER trackLookupList; int count = 0; // Build the track list (only straight lines) for( PCB_TRACK* track: m_board->Tracks() ) { if( track->Type() == PCB_TRACE_T || track->Type() == PCB_ARC_T ) { int netcode = track->GetNetCode(); int layer = track->GetLayer(); trackLookupList.AddTrack( track, layer, netcode ); } } // get vias and pads (custom pads are not collected). We do not create a track to track // teardrop inside a pad or via area std::vector< VIAPAD > viapad_list; collectVias( viapad_list ); collectPadsCandidate( viapad_list, true, true, true ); TEARDROP_PARAMETERS* currParams = m_prmsList->GetParameters( TARGET_TRACK ); // Explore groups (a group is a set of tracks on the same layer and the same net): for( auto& grp : trackLookupList.GetBuffer() ) { int layer, netcode; TRACK_BUFFER::GetNetcodeAndLayerFromIndex( grp.first, &layer, &netcode ); std::vector* sublist = grp.second; if( sublist->size() <= 1 ) // We need at least 2 track segments continue; // The sort function to sort by increasing track widths struct { bool operator()(PCB_TRACK* a, PCB_TRACK* b) const { return a->GetWidth() < b->GetWidth(); } } compareLess; std::sort( sublist->begin(), sublist->end(), compareLess ); int min_width = sublist->front()->GetWidth(); int max_width = sublist->back()->GetWidth(); // Skip groups having the same track thickness if( max_width == min_width ) continue; for( unsigned ii = 0; ii < sublist->size()-1; ii++ ) { PCB_TRACK* track = (*sublist)[ii]; int track_len = track->GetLength(); min_width = track->GetWidth(); // to avoid creating a teardrop between 2 tracks having similar widths // give a threshold const double th = currParams->m_WidthtoSizeFilterRatio > 0.1 ? 1.0 / currParams->m_WidthtoSizeFilterRatio : 10.0; min_width = min_width * th; for( unsigned jj = ii+1; jj < sublist->size(); jj++ ) { // Search candidates with thickness > curr thickness PCB_TRACK* candidate = (*sublist)[jj]; if( min_width >= candidate->GetWidth() ) continue; // Cannot build a teardrop on a too short track segment. // The min len is > candidate radius if( track_len <= candidate->GetWidth() /2 ) continue; // Now test end to end connection: EDA_ITEM_FLAGS match_points; // to return the end point EDA_ITEM_FLAGS: // 0, STARTPOINT, ENDPOINT VECTOR2I roundshape_pos = candidate->GetStart(); ENDPOINT_T endPointCandidate = ENDPOINT_START; match_points = track->IsPointOnEnds( roundshape_pos, m_tolerance ); if( !match_points ) { roundshape_pos = candidate->GetEnd(); match_points = track->IsPointOnEnds( roundshape_pos, m_tolerance ); endPointCandidate = ENDPOINT_END; } // Ensure a pad or via is not on test_pos point before creating a teardrop // at this location for( VIAPAD& viapad : viapad_list ) { if( viapad.IsOnLayer( track->GetLayer() ) && viapad.m_Parent->HitTest( roundshape_pos, 0 ) ) { match_points = 0; break; } } if( match_points ) { VIAPAD viatrack( candidate, endPointCandidate ); std::vector points; bool success = computeTeardropPolygonPoints( currParams, points, track, viatrack, false, trackLookupList ); if( success ) { ZONE* new_teardrop = createTeardrop( TD_TYPE_TRACKEND, points, track ); m_board->Add( new_teardrop, ADD_MODE::BULK_INSERT ); m_createdTdList.push_back( new_teardrop ); if( aCommitter ) aCommitter->Added( new_teardrop ); count += 1; } } } } } return count; } int TEARDROP_MANAGER::RemoveTeardrops( BOARD_COMMIT* aCommitter, bool aCommitAfterRemove ) { int count = 0; std::vector< ZONE*> teardrops; collectTeardrops( teardrops ); for( ZONE* teardrop : teardrops ) { m_board->Remove( teardrop, REMOVE_MODE::BULK ); if( aCommitter ) aCommitter->Removed( teardrop ); count += 1; } if( count ) { if( aCommitter && aCommitAfterRemove ) aCommitter->Push( _( "Remove teardrops" ), SKIP_CONNECTIVITY ); m_board->BuildConnectivity(); } return count; }