438 lines
15 KiB
C++
438 lines
15 KiB
C++
/*
|
|
* 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 <confirm.h>
|
|
|
|
#include <board_design_settings.h>
|
|
#include <pcb_track.h>
|
|
#include <pad.h>
|
|
#include <zone_filler.h>
|
|
#include <board_commit.h>
|
|
|
|
#include "teardrop.h"
|
|
#include <geometry/convex_hull.h>
|
|
#include <geometry/shape_line_chain.h>
|
|
#include <convert_basic_shapes_to_polygon.h>
|
|
#include <bezier_curves.h>
|
|
|
|
#include <wx/log.h>
|
|
|
|
// 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().GetTeadropParamsList();
|
|
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<VECTOR2I>& 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<VECTOR2I> 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<PCB_TRACK*>* 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<VECTOR2I> 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;
|
|
}
|
|
|