jean-pierre charras 0bd83d3096 Footprint viewer: ensure the footprint is updated after new library selection
Previously, if the first fp of the new lib has the same name as the old
displayed fp, the old fp was not replace by the new fp.
2023-03-29 16:53:49 +02:00

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;
}