7
mirror of https://gitlab.com/kicad/code/kicad.git synced 2025-04-19 23:21:41 +00:00

Unify length calculation between router, board / frame, and DRC

This commit is contained in:
JamesJCode 2025-03-27 21:26:37 +00:00
parent 12861345d3
commit 906c24bc6d
24 changed files with 6620 additions and 974 deletions

View File

@ -861,6 +861,8 @@ set( PCB_COMMON_SRCS
${CMAKE_SOURCE_DIR}/pcbnew/tools/pcb_editor_conditions.cpp
${CMAKE_SOURCE_DIR}/pcbnew/tools/pcb_viewer_tools.cpp
${CMAKE_SOURCE_DIR}/pcbnew/length_calculation.cpp
widgets/net_selector.cpp
)

View File

@ -301,7 +301,7 @@ ADVANCED_CFG::ADVANCED_CFG()
m_MinimumMarkerSeparationDistance = 0.15;
m_NetInspectorBulkUpdateOptimisationThreshold = 25;
m_NetInspectorBulkUpdateOptimisationThreshold = 100;
m_ExcludeFromSimulationLineWidth = 25;

View File

@ -409,10 +409,7 @@ public:
return m_points[aIndex];
}
const std::vector<VECTOR2I>& CPoints() const
{
return m_points;
}
const std::vector<VECTOR2I>& CPoints() const { return m_points; }
/**
* Return the last point in the line chain.

View File

@ -75,12 +75,11 @@ VECTOR2I BOARD_ITEM::ZeroOffset( 0, 0 );
BOARD::BOARD() :
BOARD_ITEM_CONTAINER( (BOARD_ITEM*) nullptr, PCB_T ), m_LegacyDesignSettingsLoaded( false ),
m_LegacyCopperEdgeClearanceLoaded( false ), m_LegacyNetclassesLoaded( false ),
m_boardUse( BOARD_USE::NORMAL ), m_timeStamp( 1 ), m_paper( PAGE_INFO::A4 ),
m_project( nullptr ), m_userUnits( EDA_UNITS::MM ),
m_designSettings( new BOARD_DESIGN_SETTINGS( nullptr, "board.design_settings" ) ),
m_NetInfo( this ), m_embedFonts( false ),
m_componentClassManager( std::make_unique<COMPONENT_CLASS_MANAGER>( this ) )
m_LegacyCopperEdgeClearanceLoaded( false ), m_LegacyNetclassesLoaded( false ), m_boardUse( BOARD_USE::NORMAL ),
m_timeStamp( 1 ), m_paper( PAGE_INFO::A4 ), m_project( nullptr ), m_userUnits( EDA_UNITS::MM ),
m_designSettings( new BOARD_DESIGN_SETTINGS( nullptr, "board.design_settings" ) ), m_NetInfo( this ),
m_embedFonts( false ), m_componentClassManager( std::make_unique<COMPONENT_CLASS_MANAGER>( this ) ),
m_lengthCalc( std::make_unique<LENGTH_CALCULATION>( this ) )
{
// A too small value do not allow connecting 2 shapes (i.e. segments) not exactly connected
// A too large value do not allow safely connecting 2 shapes like very short segments.
@ -2399,82 +2398,24 @@ BOARD_STACKUP BOARD::GetStackupOrDefault() const
std::tuple<int, double, double> BOARD::GetTrackLength( const PCB_TRACK& aTrack ) const
{
int count = 0;
double length = 0.0;
double package_length = 0.0;
auto connectivity = GetBoard()->GetConnectivity();
auto connectivity = GetBoard()->GetConnectivity();
BOARD_STACKUP& stackup = GetDesignSettings().GetStackupDescriptor();
bool useHeight = GetDesignSettings().m_UseHeightForLengthCalcs;
std::vector<LENGTH_CALCULATION_ITEM> items;
for( BOARD_CONNECTED_ITEM* item : connectivity->GetConnectedItems( &aTrack, EXCLUDE_ZONES ) )
for( BOARD_CONNECTED_ITEM* boardItem : connectivity->GetConnectedItems( &aTrack, EXCLUDE_ZONES ) )
{
count++;
LENGTH_CALCULATION_ITEM item = GetLengthCalculation()->GetLengthCalculationItem( boardItem );
if( PCB_TRACK* track = dynamic_cast<PCB_TRACK*>( item ) )
{
if( track->Type() == PCB_VIA_T && useHeight )
{
PCB_VIA* via = static_cast<PCB_VIA*>( track );
length += stackup.GetLayerDistance( via->TopLayer(), via->BottomLayer() );
continue;
}
else if( track->Type() == PCB_ARC_T )
{
// Note: we don't apply the clip-to-pad optimization if an arc ends in a pad
// Room for future improvement.
length += track->GetLength();
continue;
}
bool inPad = false;
SEG trackSeg( track->GetStart(), track->GetEnd() );
double segLen = trackSeg.Length();
double segInPadLen = 0;
for( auto pad_it : connectivity->GetConnectedPads( item ) )
{
PAD* pad = static_cast<PAD*>( pad_it );
bool hitStart = pad->HitTest( track->GetStart(), track->GetWidth() / 2 );
bool hitEnd = pad->HitTest( track->GetEnd(), track->GetWidth() / 2 );
if( hitStart && hitEnd )
{
inPad = true;
break;
}
else if( hitStart || hitEnd )
{
VECTOR2I loc;
// We may not collide even if we passed the bounding-box hit test
if( pad->GetEffectivePolygon( track->GetLayer(), ERROR_INSIDE )->Collide( trackSeg, 0, nullptr, &loc ) )
{
// Part 1: length of the seg to the intersection with the pad poly
if( hitStart )
trackSeg.A = loc;
else
trackSeg.B = loc;
segLen = trackSeg.Length();
// Part 2: length from the intersection to the pad anchor
segInPadLen += ( loc - pad->GetPosition() ).EuclideanNorm();
}
}
}
if( !inPad )
length += segLen + segInPadLen;
}
else if( PAD* pad = dynamic_cast<PAD*>( item ) )
{
package_length += pad->GetPadToDieLength();
}
if( item.Type() != LENGTH_CALCULATION_ITEM::TYPE::UNKNOWN )
items.push_back( item );
}
return std::make_tuple( count, length, package_length );
constexpr PATH_OPTIMISATIONS opts = {
.OptimiseViaLayers = true, .MergeTracks = true, .OptimiseTracesInPads = true, .InferViaInPad = false
};
LENGTH_DETAILS details = GetLengthCalculation()->CalculateLengthDetails( items, opts );
return std::make_tuple( items.size(), details.TrackLength + details.ViaLength, details.PadToDieLength );
}

View File

@ -33,6 +33,7 @@
#include <convert_shape_list_to_polygon.h> // for OUTLINE_ERROR_HANDLER
#include <hash.h>
#include <layer_ids.h>
#include <length_calculation.h>
#include <lset.h>
#include <netinfo.h>
#include <pcb_item_containers.h>
@ -1304,6 +1305,11 @@ public:
*/
void EmbedFonts() override;
/**
* Returns the track length calculator
*/
LENGTH_CALCULATION* GetLengthCalculation() const { return m_lengthCalc.get(); }
/**
* Gets the component class manager
*/
@ -1428,6 +1434,8 @@ private:
bool m_embedFonts;
std::unique_ptr<COMPONENT_CLASS_MANAGER> m_componentClassManager;
std::unique_ptr<LENGTH_CALCULATION> m_lengthCalc;
};

View File

@ -345,20 +345,25 @@ bool DRC_TEST_PROVIDER_MATCHED_LENGTH::runInternal( bool aDelayReportMode )
return true;
} );
LENGTH_CALCULATION* calc = m_board->GetLengthCalculation();
std::map< DRC_RULE*, std::vector<CONNECTION> > matches;
for( const std::pair< DRC_RULE* const, std::set<BOARD_CONNECTED_ITEM*> >& it : itemSets )
for( const auto& [rule, ruleItems] : itemSets )
{
std::map<int, std::set<BOARD_CONNECTED_ITEM*> > netMap;
for( BOARD_CONNECTED_ITEM* citem : it.second )
netMap[ citem->GetNetCode() ].insert( citem );
for( BOARD_CONNECTED_ITEM* item : ruleItems )
netMap[item->GetNetCode()].insert( item );
for( const std::pair< const int, std::set<BOARD_CONNECTED_ITEM*> >& nitem : netMap )
for( const auto& [netCode, netItems] : netMap )
{
std::vector<LENGTH_CALCULATION_ITEM> lengthItems;
lengthItems.reserve( netItems.size() );
CONNECTION ent;
ent.items = nitem.second;
ent.netcode = nitem.first;
ent.items = netItems;
ent.netcode = netCode;
ent.netname = m_board->GetNetInfo().GetNetItem( ent.netcode )->GetNetname();
ent.netinfo = m_board->GetNetInfo().GetNetItem( ent.netcode );
@ -369,43 +374,24 @@ bool DRC_TEST_PROVIDER_MATCHED_LENGTH::runInternal( bool aDelayReportMode )
ent.fromItem = nullptr;
ent.toItem = nullptr;
for( BOARD_CONNECTED_ITEM* citem : nitem.second )
for( BOARD_CONNECTED_ITEM* item : netItems )
{
if( citem->Type() == PCB_VIA_T )
{
const BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings();
const BOARD_STACKUP& stackup = bds.GetStackupDescriptor();
LENGTH_CALCULATION_ITEM lengthItem = calc->GetLengthCalculationItem( item );
ent.viaCount++;
if( bds.m_UseHeightForLengthCalcs )
{
const PCB_VIA* v = static_cast<PCB_VIA*>( citem );
PCB_LAYER_ID topmost;
PCB_LAYER_ID bottommost;
v->GetOutermostConnectedLayers( &topmost, &bottommost );
if( topmost != UNDEFINED_LAYER && topmost != bottommost )
ent.totalVia += stackup.GetLayerDistance( topmost, bottommost );
}
}
else if( citem->Type() == PCB_TRACE_T )
{
ent.totalRoute += static_cast<PCB_TRACK*>( citem )->GetLength();
}
else if ( citem->Type() == PCB_ARC_T )
{
ent.totalRoute += static_cast<PCB_ARC*>( citem )->GetLength();
}
else if( citem->Type() == PCB_PAD_T )
{
ent.totalPadToDie += static_cast<PAD*>( citem )->GetPadToDieLength();
}
if( lengthItem.Type() != LENGTH_CALCULATION_ITEM::TYPE::UNKNOWN )
lengthItems.emplace_back( lengthItem );
}
constexpr PATH_OPTIMISATIONS opts = {
.OptimiseViaLayers = true, .MergeTracks = true, .OptimiseTracesInPads = true, .InferViaInPad = false
};
LENGTH_DETAILS details = calc->CalculateLengthDetails( lengthItems, opts );
ent.viaCount = details.NumVias;
ent.totalVia = details.ViaLength;
ent.totalRoute = static_cast<double>( details.TrackLength );
ent.totalPadToDie = details.PadToDieLength;
ent.total = ent.totalRoute + ent.totalVia + ent.totalPadToDie;
ent.matchingRule = it.first;
ent.matchingRule = rule;
// fixme: doesn't seem to work ;-)
auto ftPath = ftCache->QueryFromToPath( ent.items );
@ -421,7 +407,7 @@ bool DRC_TEST_PROVIDER_MATCHED_LENGTH::runInternal( bool aDelayReportMode )
}
m_report.Add( ent );
matches[ it.first ].push_back(ent);
matches[rule].push_back( ent );
}
}
@ -452,19 +438,14 @@ bool DRC_TEST_PROVIDER_MATCHED_LENGTH::runInternal( bool aDelayReportMode )
for( const DRC_LENGTH_REPORT::ENTRY& ent : matchedConnections )
{
reportAux(wxString::Format( wxT( " - net: %s, from: %s, to: %s, "
"%d matching items, "
"total: %s (tracks: %s, vias: %s, pad-to-die: %s), "
"vias: %d" ),
ent.netname,
ent.from,
ent.to,
(int) ent.items.size(),
MessageTextFromValue( ent.total ),
MessageTextFromValue( ent.totalRoute ),
MessageTextFromValue( ent.totalVia ),
MessageTextFromValue( ent.totalPadToDie ),
ent.viaCount ) );
reportAux( wxString::Format( wxT( " - net: %s, from: %s, to: %s, "
"%d matching items, "
"total: %s (tracks: %s, vias: %s, pad-to-die: %s), "
"vias: %d" ),
ent.netname, ent.from, ent.to, static_cast<int>( ent.items.size() ),
MessageTextFromValue( ent.total ), MessageTextFromValue( ent.totalRoute ),
MessageTextFromValue( ent.totalVia ),
MessageTextFromValue( ent.totalPadToDie ), ent.viaCount ) );
}

View File

@ -0,0 +1,556 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright The 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 <length_calculation.h>
#include <board.h>
#include <board_design_settings.h>
#include <geometry/geometry_utils.h>
void LENGTH_CALCULATION_ITEM::CalculateViaLayers( const BOARD* aBoard )
{
static std::initializer_list<KICAD_T> traceAndPadTypes = { PCB_TRACE_T, PCB_ARC_T, PCB_PAD_T };
PCB_LAYER_ID top_layer = UNDEFINED_LAYER;
PCB_LAYER_ID bottom_layer = UNDEFINED_LAYER;
const LSET layers = aBoard->GetDesignSettings().GetEnabledLayers();
for( auto layer_it = layers.copper_layers_begin(); layer_it != layers.copper_layers_end(); ++layer_it )
{
if( aBoard->GetConnectivity()->IsConnectedOnLayer( m_via, *layer_it, traceAndPadTypes ) )
{
if( top_layer == UNDEFINED_LAYER )
top_layer = *layer_it;
else
bottom_layer = *layer_it;
}
}
if( top_layer == UNDEFINED_LAYER )
top_layer = m_via->TopLayer();
if( bottom_layer == UNDEFINED_LAYER )
bottom_layer = m_via->BottomLayer();
SetLayers( bottom_layer, top_layer );
}
void LENGTH_CALCULATION::clipLineToPad( SHAPE_LINE_CHAIN& aLine, const PAD* aPad, PCB_LAYER_ID aLayer, bool aForward )
{
const int start = aForward ? 0 : aLine.PointCount() - 1;
const int delta = aForward ? 1 : -1;
// Note: we don't apply the clip-to-pad optimization if an arc ends in a pad
// Room for future improvement.
if( aLine.IsPtOnArc( start ) )
return;
const auto& shape = aPad->GetEffectivePolygon( aLayer, ERROR_INSIDE );
// Skip the "first" (or last) vertex, we already know it's contained in the pad
int clip = start;
for( int vertex = start + delta; aForward ? vertex < aLine.PointCount() : vertex >= 0; vertex += delta )
{
SEG seg( aLine.GetPoint( vertex ), aLine.GetPoint( vertex - delta ) );
bool containsA = shape->Contains( seg.A );
bool containsB = shape->Contains( seg.B );
if( containsA && containsB )
{
// Whole segment is inside: clip out this segment
clip = vertex;
}
else if( containsB )
{
// Only one point inside: Find the intersection
VECTOR2I loc;
if( shape->Collide( seg, 0, nullptr, &loc ) )
{
aLine.Replace( vertex - delta, vertex - delta, loc );
}
}
}
if( !aForward && clip < start )
aLine.Remove( clip + 1, start );
else if( clip > start )
aLine.Remove( start, clip - 1 );
// Now connect the dots
aLine.Insert( aForward ? 0 : aLine.PointCount(), aPad->GetPosition() );
}
LENGTH_DETAILS LENGTH_CALCULATION::CalculateLengthDetails( std::vector<LENGTH_CALCULATION_ITEM>& aItems,
const PATH_OPTIMISATIONS aOptimisations,
const PAD* aStartPad, const PAD* aEndPad,
const bool aWithLayerLengths ) const
{
// If this set of items has not been optimised, optimise for shortest electrical path
if( aOptimisations.OptimiseViaLayers || aOptimisations.MergeTracks || aOptimisations.MergeTracks )
{
std::vector<LENGTH_CALCULATION_ITEM*> pads;
std::vector<LENGTH_CALCULATION_ITEM*> lines;
std::vector<LENGTH_CALCULATION_ITEM*> vias;
// Map of line endpoints to line objects
std::map<VECTOR2I, std::unordered_set<LENGTH_CALCULATION_ITEM*>> linesPositionMap;
// Map of pad positions to pad objects
std::map<VECTOR2I, std::unordered_set<LENGTH_CALCULATION_ITEM*>> padsPositionMap;
for( LENGTH_CALCULATION_ITEM& item : aItems )
{
if( item.Type() == LENGTH_CALCULATION_ITEM::TYPE::PAD )
{
pads.emplace_back( &item );
padsPositionMap[item.GetPad()->GetPosition()].insert( &item );
}
else if( item.Type() == LENGTH_CALCULATION_ITEM::TYPE::VIA )
{
vias.emplace_back( &item );
}
else if( item.Type() == LENGTH_CALCULATION_ITEM::TYPE::LINE )
{
lines.emplace_back( &item );
linesPositionMap[item.GetLine().CPoint( 0 )].insert( &item );
linesPositionMap[item.GetLine().CLastPoint()].insert( &item );
}
}
if( aOptimisations.OptimiseViaLayers )
optimiseViaLayers( vias, lines, linesPositionMap, padsPositionMap );
if( aOptimisations.MergeTracks )
mergeLines( lines, linesPositionMap );
if( aOptimisations.OptimiseTracesInPads )
optimiseTracesInPads( pads, lines );
}
LENGTH_DETAILS details;
if( aWithLayerLengths )
details.LayerLengths = std::make_unique<std::map<PCB_LAYER_ID, int64_t>>();
const bool useHeight = m_board->GetDesignSettings().m_UseHeightForLengthCalcs;
// If this is a contiguous set of items, check if we have an inferred fanout via at either end. Note that this
// condition only arises as a result of how PNS assembles tuning paths - for DRC / net inspector calculations these
// fanout vias will be present in the object set and therefore do not need to be inferred
if( aOptimisations.InferViaInPad && useHeight )
{
inferViaInPad( aStartPad, aItems.front(), details );
inferViaInPad( aEndPad, aItems.back(), details );
}
// Add stats for each item
for( const LENGTH_CALCULATION_ITEM& item : aItems )
{
// Don't include merged items
if( item.GetMergeStatus() == LENGTH_CALCULATION_ITEM::MERGE_STATUS::MERGED_RETIRED
|| item.Type() == LENGTH_CALCULATION_ITEM::TYPE::UNKNOWN )
{
continue;
}
if( item.Type() == LENGTH_CALCULATION_ITEM::TYPE::LINE )
{
const int64_t length = item.GetLine().Length();
details.TrackLength += length;
if( details.LayerLengths )
( *details.LayerLengths )[item.GetStartLayer()] += length;
}
else if( item.Type() == LENGTH_CALCULATION_ITEM::TYPE::VIA && useHeight )
{
const auto [layerStart, layerEnd] = item.GetLayers();
details.ViaLength += stackupHeight( layerStart, layerEnd );
details.NumVias += 1;
}
else if( item.Type() == LENGTH_CALCULATION_ITEM::TYPE::PAD )
{
details.PadToDieLength += item.GetPad()->GetPadToDieLength();
details.NumPads += 1;
}
}
return details;
}
void LENGTH_CALCULATION::inferViaInPad( const PAD* aPad, const LENGTH_CALCULATION_ITEM& aItem,
LENGTH_DETAILS& aDetails ) const
{
if( aPad && aItem.Type() == LENGTH_CALCULATION_ITEM::TYPE::LINE )
{
const PCB_LAYER_ID startBottomLayer = aItem.GetStartLayer();
const LSET padLayers = aPad->Padstack().LayerSet();
if( !padLayers.Contains( startBottomLayer ) )
{
// This must be either F_Cu or B_Cu
const PCB_LAYER_ID padLayer = padLayers.Contains( F_Cu ) ? F_Cu : B_Cu;
aDetails.NumVias += 1;
aDetails.ViaLength += stackupHeight( startBottomLayer, padLayer );
}
}
}
int64_t LENGTH_CALCULATION::CalculateLength( std::vector<LENGTH_CALCULATION_ITEM>& aItems,
const PATH_OPTIMISATIONS aOptimisations, const PAD* aStartPad,
const PAD* aEndPad ) const
{
return CalculateLengthDetails( aItems, aOptimisations, aStartPad, aEndPad ).TotalLength();
}
int LENGTH_CALCULATION::stackupHeight( const PCB_LAYER_ID aFirstLayer, const PCB_LAYER_ID aSecondLayer ) const
{
if( !m_board || !m_board->GetDesignSettings().m_UseHeightForLengthCalcs )
return 0;
if( m_board->GetDesignSettings().m_HasStackup )
{
const BOARD_STACKUP& stackup = m_board->GetDesignSettings().GetStackupDescriptor();
return stackup.GetLayerDistance( aFirstLayer, aSecondLayer );
}
else
{
BOARD_STACKUP stackup;
stackup.BuildDefaultStackupList( &m_board->GetDesignSettings(), m_board->GetCopperLayerCount() );
return stackup.GetLayerDistance( aFirstLayer, aSecondLayer );
}
}
void LENGTH_CALCULATION::mergeLines(
std::vector<LENGTH_CALCULATION_ITEM*>& aLines,
std::map<VECTOR2I, std::unordered_set<LENGTH_CALCULATION_ITEM*>>& aLinesPositionMap )
{
// Vector of pads, and an associated flag to indicate whether they have been visited by the clustering algorithm
std::vector<LENGTH_CALCULATION_ITEM*> pads;
auto removeFromPositionMap = [&aLinesPositionMap]( LENGTH_CALCULATION_ITEM* line )
{
aLinesPositionMap[line->GetLine().CPoint( 0 )].erase( line );
aLinesPositionMap[line->GetLine().CLastPoint()].erase( line );
};
// Attempts to merge unmerged lines in to aPrimaryLine
auto tryMerge = [&removeFromPositionMap, &aLinesPositionMap]( const MERGE_POINT aMergePoint,
const VECTOR2I& aMergePos,
const LENGTH_CALCULATION_ITEM* aPrimaryItem,
SHAPE_LINE_CHAIN& aPrimaryLine, bool* aDidMerge )
{
const auto startItr = aLinesPositionMap.find( aMergePos );
if( startItr == aLinesPositionMap.end() )
return;
std::unordered_set<LENGTH_CALCULATION_ITEM*>& startItems = startItr->second;
if( startItems.size() != 1 )
return;
LENGTH_CALCULATION_ITEM* lineToMerge = *startItems.begin();
// Don't merge if line is an arc
if( !lineToMerge->GetLine().CArcs().empty() )
return;
// Don't merge if lines are on different layers
if( aPrimaryItem->GetStartLayer() != lineToMerge->GetStartLayer() )
return;
// Merge the lines
lineToMerge->SetMergeStatus( LENGTH_CALCULATION_ITEM::MERGE_STATUS::MERGED_RETIRED );
mergeShapeLineChains( aPrimaryLine, lineToMerge->GetLine(), aMergePoint );
removeFromPositionMap( lineToMerge );
*aDidMerge = true;
};
// Cluster all lines in to contiguous entities
for( LENGTH_CALCULATION_ITEM* primaryItem : aLines )
{
// Don't start with an already merged line
if( primaryItem->GetMergeStatus() != LENGTH_CALCULATION_ITEM::MERGE_STATUS::UNMERGED )
continue;
// Remove starting line from the position map
removeFromPositionMap( primaryItem );
SHAPE_LINE_CHAIN& primaryLine = primaryItem->GetLine();
// Merge all endpoints
primaryItem->SetMergeStatus( LENGTH_CALCULATION_ITEM::MERGE_STATUS::MERGED_IN_USE );
bool mergeComplete = false;
while( !mergeComplete )
{
bool startMerged = false;
bool endMerged = false;
VECTOR2I startPos = primaryLine.CPoint( 0 );
VECTOR2I endPos = primaryLine.CLastPoint();
tryMerge( MERGE_POINT::START, startPos, primaryItem, primaryLine, &startMerged );
tryMerge( MERGE_POINT::END, endPos, primaryItem, primaryLine, &endMerged );
mergeComplete = !startMerged && !endMerged;
}
}
}
void LENGTH_CALCULATION::mergeShapeLineChains( SHAPE_LINE_CHAIN& aPrimary, const SHAPE_LINE_CHAIN& aSecondary,
const MERGE_POINT aMergePoint )
{
if( aMergePoint == MERGE_POINT::START )
{
if( aSecondary.GetPoint( 0 ) == aPrimary.GetPoint( 0 ) )
{
for( auto itr = aSecondary.CPoints().begin() + 1; itr != aSecondary.CPoints().end(); ++itr )
aPrimary.Insert( 0, *itr );
}
else
{
wxASSERT( aSecondary.CLastPoint() == aPrimary.GetPoint( 0 ) );
for( auto itr = aSecondary.CPoints().rbegin() + 1; itr != aSecondary.CPoints().rend(); ++itr )
aPrimary.Insert( 0, *itr );
}
}
else
{
if( aSecondary.GetPoint( 0 ) == aPrimary.CLastPoint() )
{
for( auto itr = aSecondary.CPoints().begin() + 1; itr != aSecondary.CPoints().end(); ++itr )
aPrimary.Append( *itr );
}
else
{
wxASSERT( aSecondary.CLastPoint() == aPrimary.CLastPoint() );
for( auto itr = aSecondary.CPoints().rbegin() + 1; itr != aSecondary.CPoints().rend(); ++itr )
aPrimary.Append( *itr );
}
}
}
void LENGTH_CALCULATION::optimiseTracesInPads( const std::vector<LENGTH_CALCULATION_ITEM*>& aPads,
const std::vector<LENGTH_CALCULATION_ITEM*>& aLines )
{
for( LENGTH_CALCULATION_ITEM* padItem : aPads )
{
PAD* pad = padItem->GetPad();
for( LENGTH_CALCULATION_ITEM* lineItem : aLines )
{
// Ignore merged lines
if( lineItem->GetMergeStatus() != LENGTH_CALCULATION_ITEM::MERGE_STATUS::MERGED_IN_USE )
continue;
const PCB_LAYER_ID pcbLayer = lineItem->GetStartLayer();
SHAPE_LINE_CHAIN& line = lineItem->GetLine();
OptimiseTraceInPad( line, pad, pcbLayer );
}
}
}
void LENGTH_CALCULATION::optimiseViaLayers(
const std::vector<LENGTH_CALCULATION_ITEM*>& aVias, std::vector<LENGTH_CALCULATION_ITEM*>& aLines,
std::map<VECTOR2I, std::unordered_set<LENGTH_CALCULATION_ITEM*>>& aLinesPositionMap,
const std::map<VECTOR2I, std::unordered_set<LENGTH_CALCULATION_ITEM*>>& aPadsPositionMap )
{
for( LENGTH_CALCULATION_ITEM* via : aVias )
{
auto lineItr = aLinesPositionMap.find( via->GetVia()->GetPosition() );
if( lineItr == aLinesPositionMap.end() )
continue;
std::unordered_set<LENGTH_CALCULATION_ITEM*>& connectedLines = lineItr->second;
if( connectedLines.empty() )
{
// No connected lines - this via is floating. Set both layers to the same
via->SetLayers( via->GetVia()->GetLayer(), via->GetVia()->GetLayer() );
}
else if( connectedLines.size() == 1 )
{
// This is either a via stub, or a via-in-pad
bool isViaInPad = false;
const PCB_LAYER_ID lineLayer = ( *connectedLines.begin() )->GetStartLayer();
auto padItr = aPadsPositionMap.find( via->GetVia()->GetPosition() );
if( padItr != aLinesPositionMap.end() )
{
// This could be a via-in-pad - check for overlapping pads which are not on the line layer
const std::unordered_set<LENGTH_CALCULATION_ITEM*>& pads = padItr->second;
if( pads.size() == 1 )
{
const LENGTH_CALCULATION_ITEM* padItem = *pads.begin();
if( !padItem->GetPad()->Padstack().LayerSet().Contains( lineLayer ) )
{
// This is probably a via-in-pad
isViaInPad = true;
via->SetLayers( lineLayer, padItem->GetStartLayer() );
}
}
}
if( !isViaInPad )
{
// This is a via stub - make its electrical length 0
via->SetLayers( lineLayer, lineLayer );
}
}
else
{
// This via has more than one track ending at it. Calculate the connected layer span (which may be shorter
// than the overall via span)
LSET layers;
for( const LENGTH_CALCULATION_ITEM* lineItem : connectedLines )
layers.set( lineItem->GetStartLayer() );
LSEQ cuStack = layers.CuStack();
PCB_LAYER_ID firstLayer = UNDEFINED_LAYER;
PCB_LAYER_ID lastLayer = UNDEFINED_LAYER;
for( PCB_LAYER_ID layer : cuStack )
{
if( firstLayer == UNDEFINED_LAYER )
firstLayer = layer;
else
lastLayer = layer;
}
if( lastLayer == UNDEFINED_LAYER )
via->SetLayers( firstLayer, firstLayer );
else
via->SetLayers( firstLayer, lastLayer );
}
}
}
void LENGTH_CALCULATION::OptimiseTraceInPad( SHAPE_LINE_CHAIN& aLine, const PAD* aPad, const PCB_LAYER_ID aPcbLayer )
{
// Only consider lines which terminate in the pad
if( aLine.CPoint( 0 ) != aPad->GetPosition() && aLine.CLastPoint() != aPad->GetPosition() )
return;
if( !aPad->FlashLayer( aPcbLayer ) )
return;
const auto& shape = aPad->GetEffectivePolygon( aPcbLayer, ERROR_INSIDE );
if( shape->Contains( aLine.CPoint( 0 ) ) )
clipLineToPad( aLine, aPad, aPcbLayer, true );
else if( shape->Contains( aLine.CPoint( -1 ) ) )
clipLineToPad( aLine, aPad, aPcbLayer, false );
}
LENGTH_CALCULATION_ITEM LENGTH_CALCULATION::GetLengthCalculationItem( BOARD_CONNECTED_ITEM* aBoardItem ) const
{
if( PCB_TRACK* track = dynamic_cast<PCB_TRACK*>( aBoardItem ) )
{
if( track->Type() == PCB_VIA_T )
{
PCB_VIA* via = static_cast<PCB_VIA*>( track );
LENGTH_CALCULATION_ITEM item;
item.SetVia( via );
item.CalculateViaLayers( m_board );
return item;
}
if( track->Type() == PCB_ARC_T )
{
PCB_ARC* arcParent = static_cast<PCB_ARC*>( track );
SHAPE_ARC shapeArc( arcParent->GetStart(), arcParent->GetMid(), arcParent->GetEnd(),
arcParent->GetWidth() );
SHAPE_LINE_CHAIN chainArc( shapeArc );
LENGTH_CALCULATION_ITEM item;
item.SetLine( chainArc );
item.SetLayers( track->GetLayer() );
return item;
}
if( track->Type() == PCB_TRACE_T )
{
std::vector<VECTOR2I> points{ track->GetStart(), track->GetEnd() };
SHAPE_LINE_CHAIN shape( points );
LENGTH_CALCULATION_ITEM item;
item.SetLine( shape );
item.SetLayers( track->GetLayer() );
return item;
}
}
else if( PAD* pad = dynamic_cast<PAD*>( aBoardItem ) )
{
LENGTH_CALCULATION_ITEM item;
item.SetPad( pad );
LSET& layers = pad->Padstack().LayerSet();
PCB_LAYER_ID firstLayer = UNDEFINED_LAYER;
PCB_LAYER_ID secondLayer = UNDEFINED_LAYER;
for( auto itr = layers.copper_layers_begin(); itr != layers.copper_layers_end(); ++itr )
{
if( firstLayer == UNDEFINED_LAYER )
firstLayer = *itr;
else
secondLayer = *itr;
}
item.SetLayers( firstLayer, secondLayer );
return item;
}
return {};
}

288
pcbnew/length_calculation.h Normal file
View File

@ -0,0 +1,288 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright The 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
*/
#ifndef PCBNEW_LENGTH_CALCULATION_H
#define PCBNEW_LENGTH_CALCULATION_H
#include <board_design_settings.h>
#include <geometry/shape_line_chain.h>
#include <pad.h>
#include <pcb_track.h>
#include <layer_ids.h>
#include <unordered_set>
#include <connectivity/connectivity_data.h>
class BOARD;
/**
* Lightweight class which holds a pad, via, or a routed trace outline. Proxied objects passed by pointer are not
* owned by this container.
*/
class LENGTH_CALCULATION_ITEM
{
public:
/// The type of routing object this item proxies
enum class TYPE
{
UNKNOWN,
PAD,
LINE,
VIA
};
/// Whether this item is UNMERGED, it has been merged and should be used (MERGED_IN_USE), or it has been merged
/// and has been retired from use (MERGED_RETIRED). MERGED_RETIRED essentially means the object has been merged
/// in to a MERGED_IN_USE item.
enum class MERGE_STATUS
{
UNMERGED,
MERGED_IN_USE,
MERGED_RETIRED
};
/// Gets the routing item type
TYPE Type() const { return m_type; };
/// Sets the parent PAD associated with this item
void SetPad( PAD* aPad )
{
m_type = TYPE::PAD;
m_pad = aPad;
}
/// Gets the parent PAD associated with this item
PAD* GetPad() const { return m_pad; }
/// Sets the source SHAPE_LINE_CHAIN of this item
void SetLine( const SHAPE_LINE_CHAIN& aLine )
{
m_type = TYPE::LINE;
m_line = aLine;
}
/// Gets the SHAPE_LINE_CHAIN associated with this item
SHAPE_LINE_CHAIN& GetLine() const { return m_line; }
/// Sets the VIA associated with this item
void SetVia( PCB_VIA* aVia )
{
m_type = TYPE::VIA;
m_via = aVia;
}
/// Gets the VIA associated with this item
PCB_VIA* GetVia() const { return m_via; }
/// Sets the first and last layers associated with this item
void SetLayers( const PCB_LAYER_ID aStart, const PCB_LAYER_ID aEnd = PCB_LAYER_ID::UNDEFINED_LAYER )
{
m_layerStart = aStart;
m_layerEnd = aEnd;
}
/// Sets the MERGE_STATUS of this item. MERGED_RETIRED essentially means the object has been merged
/// in to a MERGED_IN_USE item.
void SetMergeStatus( const MERGE_STATUS aStatus ) { m_mergeStatus = aStatus; }
/// Gets the MERGE_STATUS of this item
MERGE_STATUS GetMergeStatus() const { return m_mergeStatus; }
/// Gets the upper and lower layers for the proxied item
std::tuple<PCB_LAYER_ID, PCB_LAYER_ID> GetLayers() const { return { m_layerStart, m_layerEnd }; }
/// Gets the start board layer for the proxied item
PCB_LAYER_ID GetStartLayer() const { return m_layerStart; }
/// Gets the end board layer for the proxied item.
PCB_LAYER_ID GetEndLayer() const { return m_layerEnd; }
/// Calculates active via payers for a proxied VIA object
void CalculateViaLayers( const BOARD* aBoard );
protected:
/// A proxied PAD object. Set to nullptr if not proxying a PAD.
PAD* m_pad{ nullptr };
/// A proxied SHAPE_LINE_CHAIN object. Line is empty if not proxying a SHAPE_LINE_CHAIN.
mutable SHAPE_LINE_CHAIN m_line;
/// A proxied PVIAAD object. Set to nullptr if not proxying a VIA.
PCB_VIA* m_via{ nullptr };
/// The start board layer for the proxied object
PCB_LAYER_ID m_layerStart{ PCB_LAYER_ID::UNDEFINED_LAYER };
/// The end board layer for the proxied object
PCB_LAYER_ID m_layerEnd{ PCB_LAYER_ID::UNDEFINED_LAYER };
/// Flags whether this item has already been merged with another
MERGE_STATUS m_mergeStatus{ MERGE_STATUS::UNMERGED };
/// The routing object type of the proxied parent
TYPE m_type{ TYPE::UNKNOWN };
};
/**
* Holds length measurement result details and statistics
*/
struct LENGTH_DETAILS
{
int NumPads{ 0 };
int NumVias{ 0 };
int ViaLength{ 0 };
int64_t TrackLength{ 0 };
int PadToDieLength{ 0 };
std::unique_ptr<std::map<PCB_LAYER_ID, int64_t>> LayerLengths;
int64_t TotalLength() const { return ViaLength + TrackLength + PadToDieLength; }
};
/**
* Struct to control which optimisations the length calculation code runs on
* the given path objects. This is required as some call sites (e.g. PNS) run
* their own path optimisation, whereas others (e.g. Net Inspector) do not.
*/
struct PATH_OPTIMISATIONS
{
/// Optimise via layers for height calculations, ensuring only the distance
/// between routed segments is considered
bool OptimiseViaLayers = false;
/// Merges all contiguous (end-to-end, same layer) tracks
bool MergeTracks = false;
/// Optimises the electrical length of tracks within pads. Note that the track
/// must terminate at the trace anchor point to be considered for
/// optimisation. Will require MergeTracks if used with a non-contiguous item
/// set.
bool OptimiseTracesInPads = false;
/// Determines if there is a via-in-pad present on the board but not in the
/// item set. This condition can arise from the PNS meander placer.
/// TODO (JJ): This can be fixed in the router
bool InferViaInPad = false;
};
/**
* Class which calculates lengths (and associated routing statistics) in a BOARD context
*/
class LENGTH_CALCULATION
{
public:
/// Construct the calculator in the given BOARD context
explicit LENGTH_CALCULATION( BOARD* aBoard ) : m_board( aBoard ) {}
/**
* @brief Calculates the electrical length of the given items
* @param aItems is the vector of items making up the route
* @param aPathType indicates whether this is an ordered route, or an unordered collection
* @param aOptimised indicates whether this has been optimised for electrical length (e.g. clipping within pads)
* @param aStartPad is the starting pad of the route
* @param aEndPad is the ending pad of the route
*/
int64_t CalculateLength( std::vector<LENGTH_CALCULATION_ITEM>& aItems, PATH_OPTIMISATIONS aOptimisations,
const PAD* aStartPad = nullptr, const PAD* aEndPad = nullptr ) const;
/**
* @brief Calculates the electrical length of the given items
* @param aItems is the vector of items making up the route
* @param aPathType indicates whether this is an ordered route, or an unordered collection
* @param aOptimised indicates whether this has been optimised for electrical length (e.g. clipping within pads)
* @param aStartPad is the starting pad of the route
* @param aEndPad is the ending pad of the route
* @param aWithLayerLengths indicates whether the layer length structure should be populated
*/
LENGTH_DETAILS CalculateLengthDetails( std::vector<LENGTH_CALCULATION_ITEM>& aItems,
PATH_OPTIMISATIONS aOptimisations, const PAD* aStartPad = nullptr,
const PAD* aEndPad = nullptr, bool aWithLayerLengths = false ) const;
/// Optimises the given trace / line to minimise the electrical path length within the given pad
static void OptimiseTraceInPad( SHAPE_LINE_CHAIN& aLine, const PAD* aPad, PCB_LAYER_ID aPcbLayer );
/// Return a LENGTH_CALCULATION_ITEM constructed from the given BOARD_CONNECTED_ITEM
LENGTH_CALCULATION_ITEM GetLengthCalculationItem( BOARD_CONNECTED_ITEM* aBoardItem ) const;
protected:
/// The parent board for all items
BOARD* m_board;
/// Enum to describe whether track merging is attempted from the start or end of a track segment
enum class MERGE_POINT
{
START,
END
};
/**
* Returns the stackup distance between the two given layers.
*
* Note: Can return 0 if the board design settings disallow stackup height calculations
*/
int stackupHeight( PCB_LAYER_ID aFirstLayer, PCB_LAYER_ID aSecondLayer ) const;
/**
* Optimises the given set of items to minimise the electrical path length. At the moment
* only optimises lines attached to pads, future work could optimise paths through pads
*
* Assumes that any polylines are only connected at either end, and not at midpoints
*/
static void optimiseTracesInPads( const std::vector<LENGTH_CALCULATION_ITEM*>& aPads,
const std::vector<LENGTH_CALCULATION_ITEM*>& aLines );
/// Clips the given line to the minimal direct electrical length within the pad
static void clipLineToPad( SHAPE_LINE_CHAIN& aLine, const PAD* aPad, PCB_LAYER_ID aLayer, bool aForward = true );
/**
* Optimises the via layers. Ensures that vias that are routed through only on one layer do not count towards total
* length calculations.
*/
static void
optimiseViaLayers( const std::vector<LENGTH_CALCULATION_ITEM*>& aVias,
std::vector<LENGTH_CALCULATION_ITEM*>& aLines,
std::map<VECTOR2I, std::unordered_set<LENGTH_CALCULATION_ITEM*>>& aLinesPositionMap,
const std::map<VECTOR2I, std::unordered_set<LENGTH_CALCULATION_ITEM*>>& aPadsPositionMap );
/**
* Merges any lines (traces) that are contiguous, on one layer, and with no junctions
*/
static void mergeLines( std::vector<LENGTH_CALCULATION_ITEM*>& aLines,
std::map<VECTOR2I, std::unordered_set<LENGTH_CALCULATION_ITEM*>>& aLinesPositionMap );
/**
* Merges two SHAPE_LINE_CHAINs where there is a shared endpoing.
*
* aSecondary is merged in to aPrimary
*/
static void mergeShapeLineChains( SHAPE_LINE_CHAIN& aPrimary, const SHAPE_LINE_CHAIN& aSecondary,
MERGE_POINT aMergePoint );
/**
* Infers if there is a via in the given pad. Adds via details to the length details data structure if found.
*/
void inferViaInPad( const PAD* aPad, const LENGTH_CALCULATION_ITEM& aItem, LENGTH_DETAILS& aDetails ) const;
};
#endif //PCBNEW_LENGTH_CALCULATION_H

View File

@ -2322,3 +2322,56 @@ PNS_LAYER_RANGE PNS_KICAD_IFACE_BASE::SetLayersFromPCBNew( PCB_LAYER_ID aStartLa
return PNS_LAYER_RANGE( GetPNSLayerFromBoardLayer( aStartLayer ),
GetPNSLayerFromBoardLayer( aEndLayer ) );
}
long long int PNS_KICAD_IFACE_BASE::CalculateRoutedPathLength( const PNS::ITEM_SET& aLine, const PNS::SOLID* aStartPad,
const PNS::SOLID* aEndPad )
{
std::vector<LENGTH_CALCULATION_ITEM> lengthItems;
for( int idx = 0; idx < aLine.Size(); idx++ )
{
const PNS::ITEM* lineItem = aLine[idx];
if( const PNS::LINE* l = dyn_cast<const PNS::LINE*>( lineItem ) )
{
LENGTH_CALCULATION_ITEM item;
item.SetLine( l->CLine() );
const PCB_LAYER_ID layer = GetBoardLayerFromPNSLayer( lineItem->Layer() );
item.SetLayers( layer );
lengthItems.emplace_back( std::move( item ) );
}
else if( lineItem->OfKind( PNS::ITEM::VIA_T ) && idx > 0 && idx < aLine.Size() - 1 )
{
const int layerPrev = aLine[idx - 1]->Layer();
const int layerNext = aLine[idx + 1]->Layer();
const PCB_LAYER_ID pcbLayerPrev = GetBoardLayerFromPNSLayer( layerPrev );
const PCB_LAYER_ID pcbLayerNext = GetBoardLayerFromPNSLayer( layerNext );
if( layerPrev != layerNext )
{
LENGTH_CALCULATION_ITEM item;
item.SetVia( static_cast<PCB_VIA*>( lineItem->GetSourceItem() ) );
item.SetLayers( pcbLayerPrev, pcbLayerNext );
lengthItems.emplace_back( std::move( item ) );
}
}
}
const PAD* startPad = nullptr;
const PAD* endPad = nullptr;
if( aStartPad )
startPad = static_cast<PAD*>( aStartPad->Parent() );
if( aEndPad )
endPad = static_cast<PAD*>( aEndPad->Parent() );
constexpr PATH_OPTIMISATIONS opts = {
.OptimiseViaLayers = false, .MergeTracks = false, .OptimiseTracesInPads = false, .InferViaInPad = true
};
const BOARD* board = GetBoard();
return board->GetLengthCalculation()->CalculateLength( lengthItems, opts, startPad, endPad );
}

View File

@ -83,6 +83,8 @@ public:
void SetDebugDecorator( PNS::DEBUG_DECORATOR* aDec );
long long int CalculateRoutedPathLength( const PNS::ITEM_SET& aLine, const PNS::SOLID* aStartPad,
const PNS::SOLID* aEndPad ) override;
PCB_LAYER_ID GetBoardLayerFromPNSLayer( int aLayer ) const override;
int GetPNSLayerFromBoardLayer( PCB_LAYER_ID aLayer ) const override;

View File

@ -321,60 +321,10 @@ VECTOR2I MEANDER_PLACER_BASE::getSnappedStartPoint( LINKED_ITEM* aStartItem, VEC
long long int MEANDER_PLACER_BASE::lineLength( const ITEM_SET& aLine, const SOLID* aStartPad, const SOLID* aEndPad ) const
{
long long int total = 0;
if( aLine.Empty() )
return 0;
const ITEM* start_item = aLine[0];
const ITEM* end_item = aLine[aLine.Size() - 1];
bool start_via = false;
bool end_via = false;
/**
* If there is a start pad but the pad's layers do not overlap the first track layer, then there must be a
* fanout via on the line. If there isn't, we still need to have the via back to the pad, so count the distance
* in the line tuning
*/
start_via = aStartPad && ( !aStartPad->LayersOverlap( start_item ) );
end_via = aEndPad && ( !aEndPad->LayersOverlap( end_item ) );
for( int idx = 0; idx < aLine.Size(); idx++ )
{
const ITEM* item = aLine[idx];
if( const LINE* l = dyn_cast<const LINE*>( item ) )
{
total += l->CLine().Length();
}
else if( item->OfKind( ITEM::VIA_T ) && idx > 0 && idx < aLine.Size() - 1 )
{
int layerPrev = aLine[idx - 1]->Layer();
int layerNext = aLine[idx + 1]->Layer();
if( layerPrev != layerNext )
total += m_router->GetInterface()->StackupHeight( layerPrev, layerNext );
}
}
if( start_via )
{
int layerPrev = aStartPad->Layer();
int layerNext = start_item->Layer();
total += m_router->GetInterface()->StackupHeight( layerPrev, layerNext );
}
if( end_via )
{
int layerPrev = end_item->Layer();
int layerNext = aEndPad->Layer();
total += m_router->GetInterface()->StackupHeight( layerPrev, layerNext );
}
return total;
ROUTER_IFACE* iface = Router()->GetInterface();
return iface->CalculateRoutedPathLength( aLine, aStartPad, aEndPad );
}
}

View File

@ -117,6 +117,8 @@ enum DRAG_MODE
virtual RULE_RESOLVER* GetRuleResolver() = 0;
virtual DEBUG_DECORATOR* GetDebugDecorator() = 0;
virtual long long int CalculateRoutedPathLength( const ITEM_SET& aLine, const SOLID* aStartPad,
const SOLID* aEndPad ) = 0;
virtual PCB_LAYER_ID GetBoardLayerFromPNSLayer( int aLayer ) const = 0;
virtual int GetPNSLayerFromBoardLayer( PCB_LAYER_ID aLayer ) const = 0;
};

View File

@ -29,6 +29,7 @@
#include <geometry/shape_line_chain.h>
#include "pns_item.h"
#include "pns_hole.h"
namespace PNS {

View File

@ -371,53 +371,7 @@ const ITEM_SET TOPOLOGY::AssembleTuningPath( ROUTER_IFACE* aRouterIface, ITEM* a
if( !padA && !padB )
return initialPath;
auto clipLineToPad =
[]( SHAPE_LINE_CHAIN& aLine, PAD* aPad, PCB_LAYER_ID aLayer, bool aForward = true )
{
const auto& shape = aPad->GetEffectivePolygon( aLayer, ERROR_INSIDE );
int start = aForward ? 0 : aLine.PointCount() - 1;
int delta = aForward ? 1 : -1;
// Skip the "first" (or last) vertex, we already know it's contained in the pad
int clip = start;
for( int vertex = start + delta;
aForward ? vertex < aLine.PointCount() : vertex >= 0;
vertex += delta )
{
SEG seg( aLine.GetPoint( vertex ), aLine.GetPoint( vertex - delta ) );
bool containsA = shape->Contains( seg.A );
bool containsB = shape->Contains( seg.B );
if( containsA && containsB )
{
// Whole segment is inside: clip out this segment
clip = vertex;
}
else if( containsB )
{
// Only one point inside: Find the intersection
VECTOR2I loc;
if( shape->Collide( seg, 0, nullptr, &loc ) )
{
aLine.Replace( vertex - delta, vertex - delta, loc );
}
}
}
if( !aForward && clip < start )
aLine.Remove( clip + 1, start );
else if( clip > start )
aLine.Remove( start, clip - 1 );
// Now connect the dots
aLine.Insert( aForward ? 0 : aLine.PointCount(), aPad->GetPosition() );
};
auto processPad = [&]( const JOINT* aJoint, PAD* aPad, int aLayer )
auto processPad = [&]( PAD* aPad, int aLayer )
{
for( int idx = 0; idx < initialPath.Size(); idx++ )
{
@ -425,32 +379,18 @@ const ITEM_SET TOPOLOGY::AssembleTuningPath( ROUTER_IFACE* aRouterIface, ITEM* a
continue;
LINE* line = static_cast<LINE*>( initialPath[idx] );
PCB_LAYER_ID pcbLayer = aRouterIface->GetBoardLayerFromPNSLayer( line->Layer() );
SHAPE_LINE_CHAIN& slc = line->Line();
const PCB_LAYER_ID pcbLayer = aRouterIface->GetBoardLayerFromPNSLayer( line->Layer() );
if( !aPad->FlashLayer( pcbLayer ) )
continue;
const std::vector<VECTOR2I>& points = line->CLine().CPoints();
if( points.front() != aJoint->Pos() && points.back() != aJoint->Pos() )
continue;
const auto& shape = aPad->GetEffectivePolygon( pcbLayer, ERROR_INSIDE );
SHAPE_LINE_CHAIN& slc = line->Line();
if( shape->Contains( slc.CPoint( 0 ) ) )
clipLineToPad( slc, aPad, pcbLayer, true );
else if( shape->Contains( slc.CPoint( -1 ) ) )
clipLineToPad( slc, aPad, pcbLayer, false );
LENGTH_CALCULATION::OptimiseTraceInPad( slc, aPad, pcbLayer );
}
};
if( padA )
processPad( joints.first, padA, joints.first->Layer() );
processPad( padA, joints.first->Layer() );
if( padB )
processPad( joints.second, padB, joints.second->Layer() );
processPad( padB, joints.second->Layer() );
return initialPath;
}

File diff suppressed because it is too large Load Diff

View File

@ -37,25 +37,10 @@ class PCB_TRACK;
class EDA_COMBINED_MATCHER;
/**
* Net inspection panel for pcbnew
* PCB net inspection panel
*
* Provides a read-only view of net information, such as routed lengths. Data is updated after
* every change of board items. Note that there is not always a 1:1 relationship between Nets and
* displayed items in the inspector.. This can be the case where there is a constraint which
* selects sub-sections of nets, for example consider a netclass used for a fly-by-routing
* adddress bus. There could be two constraints, e.g.:
* <p>
* FROM/TO=IC1-IC2, Netclass=DDR_ADDR, Net=ADDR_0
* FROM/TO=IC2-IC3, Netclass=DDR_ADDR, Net=ADDR_0
* <p>
* In this instance, a single address net within the DDR_ADDR netclass could have three entries in
* the inspector, each tracking a different set of net statistics:
* <p>
* 1. The whole net
* 2. IC1-IC2
* 3. IC2-IC3
* <p>
* In this instance, all sub-nets as a result of a constraint will be grouped by the constraint.
* Provides a read-only view of net information, such as routed lengths. Data is updated after every change of board
* items.
*/
class PCB_NET_INSPECTOR_PANEL : public NET_INSPECTOR_PANEL, public BOARD_LISTENER
{
@ -68,50 +53,41 @@ public:
*
* Called by PCB_EDIT_FRAME after displaying the Board Setup dialog
*/
virtual void OnParentSetupChanged() override;
void OnParentSetupChanged() override;
/*
* BOARD_LISTENER implementation
*/
virtual void OnBoardItemAdded( BOARD& aBoard, BOARD_ITEM* aBoardItem ) override;
virtual void OnBoardItemsAdded( BOARD& aBoard, std::vector<BOARD_ITEM*>& aBoardItems ) override;
virtual void OnBoardItemRemoved( BOARD& aBoard, BOARD_ITEM* aBoardItem ) override;
virtual void OnBoardItemsRemoved( BOARD& aBoard,
std::vector<BOARD_ITEM*>& aBoardItems ) override;
virtual void OnBoardNetSettingsChanged( BOARD& aBoard ) override;
virtual void OnBoardItemChanged( BOARD& aBoard, BOARD_ITEM* aBoardItem ) override;
virtual void OnBoardItemsChanged( BOARD& aBoard,
std::vector<BOARD_ITEM*>& aBoardItems ) override;
virtual void OnBoardHighlightNetChanged( BOARD& aBoard ) override;
virtual void OnBoardCompositeUpdate( BOARD& aBoard, std::vector<BOARD_ITEM*>& aAddedItems,
std::vector<BOARD_ITEM*>& aRemovedItems,
std::vector<BOARD_ITEM*>& aChangedItems ) override;
/**
* Update panel when board is changed
*/
virtual void OnBoardChanged() override;
void OnBoardItemAdded( BOARD& aBoard, BOARD_ITEM* aBoardItem ) override;
void OnBoardItemsAdded( BOARD& aBoard, std::vector<BOARD_ITEM*>& aBoardItems ) override;
void OnBoardItemRemoved( BOARD& aBoard, BOARD_ITEM* aBoardItem ) override;
void OnBoardItemsRemoved( BOARD& aBoard, std::vector<BOARD_ITEM*>& aBoardItems ) override;
void OnBoardNetSettingsChanged( BOARD& aBoard ) override;
void OnBoardItemChanged( BOARD& aBoard, BOARD_ITEM* aBoardItem ) override;
void OnBoardItemsChanged( BOARD& aBoard, std::vector<BOARD_ITEM*>& aBoardItems ) override;
void OnBoardHighlightNetChanged( BOARD& aBoard ) override;
void OnBoardCompositeUpdate( BOARD& aBoard, std::vector<BOARD_ITEM*>& aAddedItems,
std::vector<BOARD_ITEM*>& aRemovedItems,
std::vector<BOARD_ITEM*>& aChangedItems ) override;
/**
* Prepare the panel when shown in the editor
*/
virtual void OnShowPanel() override;
/// Update panel when board is changed
void OnBoardChanged() override;
/**
* Persist the net inspector configuration to project / global settings
*/
virtual void SaveSettings() override;
/// Prepare the panel when shown in the editor
void OnShowPanel() override;
/// Persist the net inspector configuration to project / global settings
void SaveSettings() override;
protected:
/**
* Reloads strings on an application language change
*/
virtual void OnLanguageChangedImpl() override;
/// Reloads strings on an application language change
void OnLanguageChangedImpl() override;
/*
* UI events
*/
virtual void OnSearchTextChanged( wxCommandEvent& event ) override;
virtual void OnConfigButton( wxCommandEvent& event ) override;
void OnSearchTextChanged( wxCommandEvent& event ) override;
void OnConfigButton( wxCommandEvent& event ) override;
void OnExpandCollapseRow( wxCommandEvent& event );
void OnHeaderContextMenu( wxCommandEvent& event );
void OnNetsListContextMenu( wxDataViewEvent& event );
@ -119,42 +95,36 @@ protected:
void OnColumnSorted( wxDataViewEvent& event );
private:
/// Updates displayed statistics for the given nets
void updateNets( const std::vector<NETINFO_ITEM*>& aNets ) const;
/// Unified handling of added / deleted / modified board items
void updateBoardItems( const std::vector<BOARD_ITEM*>& aBoardItems );
/*
* Helper methods for returning fornatted data
* Helper methods for returning formatted data
*/
wxString formatNetCode( const NETINFO_ITEM* aNet ) const;
wxString formatNetName( const NETINFO_ITEM* aNet ) const;
wxString formatCount( unsigned int aValue ) const;
static wxString formatNetCode( const NETINFO_ITEM* aNet );
static wxString formatNetName( const NETINFO_ITEM* aNet );
static wxString formatCount( unsigned int aValue );
wxString formatLength( int64_t aValue ) const;
/**
* Generates a sub-menu for the show / hide columns submenu
*/
/// Generates a sub-menu for the show / hide columns submenu
void generateShowHideColumnMenu( wxMenu* target );
/**
* Filters connectivity items from a board update to remove those not related to
* net / track metrics
*/
/// Fetches an ordered (by NetCode) list of all board connectivity items
std::vector<CN_ITEM*> relevantConnectivityItems() const;
/**
* Filter to determine whether a board net should be included in the net inspector
*/
/// Filter to determine whether a board net should be included in the net inspector
bool netFilterMatches( NETINFO_ITEM* aNet, PANEL_NET_INSPECTOR_SETTINGS* cfg = nullptr ) const;
/**
* Updates the stored LIST_ITEMs for a given updated board net item
*/
void updateNet( NETINFO_ITEM* aNet );
/**
* Calculates the length of a via from the board stackup
*/
unsigned int calculateViaLength( const PCB_TRACK* ) const;
/// Rebuilds the net inspector list, removing all previous entries
void buildNetsList( bool rebuildColumns = false );
/// Build the required columns in the net inspector grid
void buildColumns();
/// Set sensible default column widths
void setColumnWidths();
/**
@ -162,7 +132,7 @@ private:
*
* @param cfg the PANEL_NET_INSPECTOR_SETTINGS from which to read column widths
*/
void adjustListColumnSizes( PANEL_NET_INSPECTOR_SETTINGS* cfg );
void adjustListColumnSizes( PANEL_NET_INSPECTOR_SETTINGS* cfg ) const;
/**
* Sets the sort column in the grid to that showing the given model ID column
@ -171,7 +141,7 @@ private:
* @param sortOrderAsc True for ascending sort, False for descending sort
* @returns true if the column was found
*/
bool restoreSortColumn( int sortingColumnId, bool sortOrderAsc );
bool restoreSortColumn( int sortingColumnId, bool sortOrderAsc ) const;
/**
* Fetches the displayed grid view column for the given model column ID
@ -179,81 +149,84 @@ private:
* @param columnId The ID (from column static IDs enum) to find
* @returns Pointer to the wxDataViewColumn, or nullptr if not found
*/
wxDataViewColumn* getDisplayedColumnForModelField( int columnId );
wxDataViewColumn* getDisplayedColumnForModelField( int columnId ) const;
/**
* Generates a CSV report from currently disaplyed data
*/
/// Generates a CSV report from currently disaplyed data
void generateReport();
/**
* Highlight the currently selected net
*/
/// Highlight the currently selected net
void highlightSelectedNets();
/// Handle an application-level change of units
void onUnitsChanged( wxCommandEvent& event );
void onSettingsMenu( wxCommandEvent& event );
/// Handle a net row(s) context menu selection
void onContextMenuSelection( wxCommandEvent& event );
/// Display a new row(s) context menu
void onItemContextMenu( wxCommandEvent& event );
/// Adds a new user-specified net to the board
void onAddNet();
/// Renames a selected net
void onRenameSelectedNet();
/// Deletes a selected net
void onDeleteSelectedNet();
void onRemoveSelectedGroup();
/// Adds a custom display grouping of nets
void onAddGroup();
/// Removes a custom display grouping
void onRemoveSelectedGroup();
/// Clears highlighting from nets
void onClearHighlighting();
/**
* Container class for a set of net data
*/
/// Forward declaration: container class for a set of net data
class LIST_ITEM;
/**
* Ordered comparison of LIST_ITEMs by net code
*/
* Calculates the length statistics for each given netcode
*
* @param aNetCodes is the list of netcodes to calculate statistics for. This must be sorted in ascending netcode order
* @param aIncludeZeroPadNets determines whether the results should include nets with no pads
* @returns a map of net code to net length detail objects
*/
std::vector<std::unique_ptr<LIST_ITEM>> calculateNets( const std::vector<NETINFO_ITEM*>& aNetCodes,
bool aIncludeZeroPadNets ) const;
/// Ordered comparison of LIST_ITEMs by net code
struct LIST_ITEM_NETCODE_CMP_LESS;
/**
* Ordered comparison of LIST_ITEMs by group number
*/
/// Ordered comparison of LIST_ITEMs by group number
struct LIST_ITEM_GROUP_NUMBER_CMP_LESS;
using LIST_ITEM_ITER = std::vector<std::unique_ptr<LIST_ITEM>>::iterator;
using LIST_ITEM_CONST_ITER = std::vector<std::unique_ptr<LIST_ITEM>>::const_iterator;
/**
* Constructs a LIST_ITEM for storage in the data model from a board net item
*/
std::unique_ptr<LIST_ITEM> buildNewItem( NETINFO_ITEM* aNet, unsigned int aPadCount,
const std::vector<CN_ITEM*>& aCNItems );
/// Refreshes displayed data for the given rows
void updateDisplayedRowValues( const std::optional<LIST_ITEM_ITER>& aRow ) const;
void updateDisplayedRowValues( const std::optional<LIST_ITEM_ITER>& aRow );
/// Parent BOARD
BOARD* m_board = nullptr;
// special zero-netcode item. Unconnected pads etc might use different
// (dummy) NETINFO_ITEM. Redirect all of them to this item, which we get
// from the board object in buildNetsList.
NETINFO_ITEM* m_zero_netitem;
/*
* Current board and parent edit frame
*/
BOARD* m_brd = nullptr;
/// Owning edit frame
PCB_EDIT_FRAME* m_frame = nullptr;
/**
* Data model which holds LIST_ITEMs
*/
/// Data model which holds LIST_ITEMs
class DATA_MODEL;
/*
* The bound data model to display
*/
wxObjectDataPtr<DATA_MODEL> m_data_model;
/// The bound data model to display
wxObjectDataPtr<DATA_MODEL> m_dataModel;
friend DATA_MODEL;
/*
* Status flags set during reporting and net rebuild operations
*/
bool m_in_reporting = false;
bool m_in_build_nets_list = false;
bool m_inReporting = false;
bool m_inBuildNetsList = false;
/*
* Status flags to indicate whether a board has been loaded in this control's
@ -264,41 +237,36 @@ private:
* settings calculated from an empty board. We do not save settings until the first
* board load operation has occured.
*/
bool m_board_loaded = false;
bool m_board_loading = false;
bool m_boardLoaded = false;
bool m_boardLoading = false;
/*
* Flags to indicate whether certain events should be disabled during programmatic updates
*/
bool m_row_expanding = false;
bool m_highlighting_nets = false;
bool m_rowExpanding = false;
bool m_highlightingNets = false;
/*
* Configuration flags - these are all persisted to the project storage
*/
bool m_filter_by_net_name = true;
bool m_filter_by_netclass = true;
bool m_show_zero_pad_nets = false;
bool m_show_unconnected_nets = false;
bool m_group_by_netclass = false;
bool m_group_by_constraint = false;
int m_num_copper_layers = 0;
bool m_filterByNetName = true;
bool m_filterByNetclass = true;
bool m_showZeroPadNets = false;
bool m_showUnconnectedNets = false;
bool m_groupByNetclass = false;
bool m_groupByConstraint = false;
/// Custom net grouping rules
std::vector<std::unique_ptr<EDA_COMBINED_MATCHER>> m_custom_group_rules;
/**
* CSV output control
*/
/// CSV output control
enum class CSV_COLUMN_DESC : int
{
CSV_NONE = 0,
CSV_QUOTE = 1 << 0
};
/**
* Column metadata
*/
/// Column metadata
struct COLUMN_DESC
{
COLUMN_DESC( unsigned aNum, PCB_LAYER_ID aLayer, const wxString& aDisp,
@ -324,9 +292,7 @@ private:
*/
std::vector<COLUMN_DESC> m_columns;
/*
* Column static IDs. Used to refer to columns as use re-ordering can occur.
*/
/// Column static IDs. Used to refer to columns as user re-ordering can occur
enum
{
COLUMN_NAME = 0,
@ -340,9 +306,7 @@ private:
COLUMN_LAST_STATIC_COL = COLUMN_PAD_COUNT
};
/*
* Popup menu item IDs
*/
/// Popup menu item IDs
enum POPUP_MENU_OPTIONS
{
ID_ADD_NET = ID_POPUP_MENU_START,

View File

@ -152,7 +152,7 @@ public:
m_via_count -= aValue;
}
uint64_t GetViaLength() const { return m_via_length; }
int64_t GetViaLength() const { return m_via_length; }
bool ViaLengthChanged() const { return m_column_changed[COLUMN_VIA_LENGTH]; }
@ -174,7 +174,7 @@ public:
m_via_length += aValue;
}
void SubViaLength( uint64_t aValue )
void SubViaLength( int64_t aValue )
{
if( m_parent )
m_parent->SubViaLength( aValue );
@ -183,9 +183,9 @@ public:
m_via_length -= aValue;
}
uint64_t GetBoardWireLength() const
int64_t GetBoardWireLength() const
{
uint64_t retval = 0;
int64_t retval = 0;
for( auto& [layer, length] : m_layer_wire_length )
retval += length;
@ -193,7 +193,7 @@ public:
return retval;
}
uint64_t GetLayerWireLength( PCB_LAYER_ID aLayer ) const
int64_t GetLayerWireLength( PCB_LAYER_ID aLayer ) const
{
auto it = m_layer_wire_length.find( aLayer );
return it != m_layer_wire_length.end() ? it->second : 0;
@ -201,7 +201,7 @@ public:
bool BoardWireLengthChanged() const { return m_column_changed[COLUMN_BOARD_LENGTH]; }
void SetLayerWireLength( const uint64_t aValue, PCB_LAYER_ID aLayer )
void SetLayerWireLength( const int64_t aValue, PCB_LAYER_ID aLayer )
{
auto it = m_layer_wire_length.find( aLayer );
@ -219,14 +219,23 @@ public:
length = aValue;
}
std::map<PCB_LAYER_ID, uint64_t> GetLayerWireLength() const { return m_layer_wire_length; }
std::map<PCB_LAYER_ID, int64_t> GetLayerWireLengths() const { return m_layer_wire_length; }
void SetLayerWireLength( const std::map<PCB_LAYER_ID, uint64_t>& aValue )
void SetLayerWireLengths( const std::map<PCB_LAYER_ID, int64_t>& aValue )
{
if( m_parent )
{
for( auto& [oldLayer, oldLength] : m_layer_wire_length )
m_parent->SubLayerWireLength( oldLength, oldLayer );
for( auto& [newLayer, newLength] : aValue )
m_parent->AddLayerWireLength( newLength, newLayer );
}
m_layer_wire_length = aValue;
}
void AddLayerWireLength( const uint64_t aValue, PCB_LAYER_ID aLayer )
void AddLayerWireLength( const int64_t aValue, PCB_LAYER_ID aLayer )
{
if( m_parent )
m_parent->AddLayerWireLength( aValue, aLayer );
@ -235,7 +244,7 @@ public:
m_layer_wire_length[aLayer] += aValue;
}
void SubLayerWireLength( const uint64_t aValue, PCB_LAYER_ID aLayer )
void SubLayerWireLength( const int64_t aValue, PCB_LAYER_ID aLayer )
{
if( m_parent )
m_parent->SubLayerWireLength( aValue, aLayer );
@ -244,11 +253,11 @@ public:
m_layer_wire_length[aLayer] -= aValue;
}
uint64_t GetPadDieLength() const { return m_pad_die_length; }
int64_t GetPadDieLength() const { return m_pad_die_length; }
bool PadDieLengthChanged() const { return m_column_changed[COLUMN_PAD_DIE_LENGTH]; }
void SetPadDieLength( uint64_t aValue )
void SetPadDieLength( int64_t aValue )
{
if( m_parent )
m_parent->SetPadDieLength( m_parent->GetPadDieLength() - m_pad_die_length + aValue );
@ -257,7 +266,7 @@ public:
m_pad_die_length = aValue;
}
void AddPadDieLength( uint64_t aValue )
void AddPadDieLength( int64_t aValue )
{
if( m_parent )
m_parent->AddPadDieLength( aValue );
@ -266,7 +275,7 @@ public:
m_pad_die_length += aValue;
}
void SubPadDieLength( uint64_t aValue )
void SubPadDieLength( int64_t aValue )
{
if( m_parent )
m_parent->SubPadDieLength( aValue );
@ -334,10 +343,10 @@ private:
NETINFO_ITEM* m_net = nullptr;
unsigned int m_pad_count = 0;
unsigned int m_via_count = 0;
uint64_t m_via_length = 0;
uint64_t m_pad_die_length = 0;
int64_t m_via_length = 0;
int64_t m_pad_die_length = 0;
std::map<PCB_LAYER_ID, uint64_t> m_layer_wire_length{};
std::map<PCB_LAYER_ID, int64_t> m_layer_wire_length{};
// Dirty bits to record when some attribute has changed, in order to avoid unnecessary sort
// operations.
@ -514,7 +523,7 @@ public:
}
// Then add any netclass groups required by this item
if( m_parent.m_group_by_netclass && !groupMatched )
if( m_parent.m_groupByNetclass && !groupMatched )
{
LIST_ITEM_ITER groups_begin = m_items.begin();
LIST_ITEM_ITER groups_end = std::find_if_not( m_items.begin(), m_items.end(),
@ -544,7 +553,7 @@ public:
return { new_iter };
}
void addItems( std::vector<std::unique_ptr<LIST_ITEM>> aItems )
void addItems( std::vector<std::unique_ptr<LIST_ITEM>>& aItems )
{
m_items.reserve( m_items.size() + aItems.size() );
@ -569,7 +578,7 @@ public:
{
ItemChanged( wxDataViewItem( parent ) );
if( m_parent.m_group_by_netclass && parent != nullptr && parent->ChildrenCount() == 0 )
if( m_parent.m_groupByNetclass && parent != nullptr && parent->ChildrenCount() == 0 )
{
auto p = std::find_if( m_items.begin(), m_items.end(),
[&]( std::unique_ptr<LIST_ITEM>& x )
@ -604,7 +613,7 @@ public:
std::unique_ptr<LIST_ITEM>& group = m_items.emplace_back( std::make_unique<LIST_ITEM>(
groupId, rule->GetPattern(), LIST_ITEM::GROUP_TYPE::USER_DEFINED ) );
m_custom_group_map[ rule->GetPattern() ] = group.get();
group->SetLayerCount( m_parent.m_brd->GetCopperLayerCount() );
group->SetLayerCount( m_parent.m_board->GetCopperLayerCount() );
ItemAdded( wxDataViewItem( group->Parent() ), wxDataViewItem( group.get() ) );
++groupId;
}
@ -750,7 +759,7 @@ protected:
}
}
static int compareUInt( uint64_t aValue1, uint64_t aValue2, bool aAsc )
static int compareUInt( int64_t aValue1, int64_t aValue2, bool aAsc )
{
if( aAsc )
return aValue1 < aValue2 ? -1 : 1;

View File

@ -0,0 +1,31 @@
(version 1)
(rule "CASE_1"
(condition "A.hasNetclass('CASE_1')")
(constraint length (min 13.3345mm) (max 13.3555mm))
)
(rule "CASE_2"
(condition "A.hasNetclass('CASE_2')")
(constraint length (min 14.8795mm) (max 14.8805mm))
)
(rule "CASE_3"
(condition "A.hasNetclass('CASE_3')")
(constraint length (min 14.8795mm) (max 14.8805mm))
)
(rule "CASE_4"
(condition "A.hasNetclass('CASE_4')")
(constraint length (min 16.4245mm) (max 16.4250mm))
)
(rule "CASE_5"
(condition "A.hasNetclass('CASE_5')")
(constraint length (min 13.5470mm) (max 13.5480mm))
)
(rule "CASE_6"
(condition "A.hasNetclass('CASE_6')")
(constraint length (min 13.3345mm) (max 13.3555mm))
)

View File

LOADING design file

View File

@ -0,0 +1,658 @@
{
"board": {
"3dviewports": [],
"design_settings": {
"defaults": {
"apply_defaults_to_fp_fields": false,
"apply_defaults_to_fp_shapes": false,
"apply_defaults_to_fp_text": false,
"board_outline_line_width": 0.05,
"copper_line_width": 0.2,
"copper_text_italic": false,
"copper_text_size_h": 1.5,
"copper_text_size_v": 1.5,
"copper_text_thickness": 0.3,
"copper_text_upright": false,
"courtyard_line_width": 0.05,
"dimension_precision": 4,
"dimension_units": 3,
"dimensions": {
"arrow_length": 1270000,
"extension_offset": 500000,
"keep_text_aligned": true,
"suppress_zeroes": true,
"text_position": 0,
"units_format": 0
},
"fab_line_width": 0.1,
"fab_text_italic": false,
"fab_text_size_h": 1.0,
"fab_text_size_v": 1.0,
"fab_text_thickness": 0.15,
"fab_text_upright": false,
"other_line_width": 0.1,
"other_text_italic": false,
"other_text_size_h": 1.0,
"other_text_size_v": 1.0,
"other_text_thickness": 0.15,
"other_text_upright": false,
"pads": {
"drill": 0.8,
"height": 1.27,
"width": 2.54
},
"silk_line_width": 0.1,
"silk_text_italic": false,
"silk_text_size_h": 1.0,
"silk_text_size_v": 1.0,
"silk_text_thickness": 0.1,
"silk_text_upright": false,
"zones": {
"min_clearance": 0.5
}
},
"diff_pair_dimensions": [],
"drc_exclusions": [],
"meta": {
"version": 2
},
"rule_severities": {
"annular_width": "error",
"clearance": "error",
"connection_width": "warning",
"copper_edge_clearance": "error",
"copper_sliver": "warning",
"courtyards_overlap": "error",
"creepage": "error",
"diff_pair_gap_out_of_range": "error",
"diff_pair_uncoupled_length_too_long": "error",
"drill_out_of_range": "error",
"duplicate_footprints": "warning",
"extra_footprint": "warning",
"footprint": "error",
"footprint_filters_mismatch": "ignore",
"footprint_symbol_mismatch": "warning",
"footprint_type_mismatch": "ignore",
"hole_clearance": "error",
"hole_to_hole": "warning",
"holes_co_located": "warning",
"invalid_outline": "error",
"isolated_copper": "warning",
"item_on_disabled_layer": "error",
"items_not_allowed": "error",
"length_out_of_range": "error",
"lib_footprint_issues": "warning",
"lib_footprint_mismatch": "warning",
"malformed_courtyard": "error",
"microvia_drill_out_of_range": "error",
"mirrored_text_on_front_layer": "warning",
"missing_courtyard": "ignore",
"missing_footprint": "warning",
"net_conflict": "warning",
"nonmirrored_text_on_back_layer": "warning",
"npth_inside_courtyard": "ignore",
"padstack": "warning",
"pth_inside_courtyard": "ignore",
"shorting_items": "error",
"silk_edge_clearance": "warning",
"silk_over_copper": "warning",
"silk_overlap": "warning",
"skew_out_of_range": "error",
"solder_mask_bridge": "error",
"starved_thermal": "error",
"text_height": "warning",
"text_on_edge_cuts": "error",
"text_thickness": "warning",
"through_hole_pad_without_hole": "error",
"too_many_vias": "error",
"track_angle": "error",
"track_dangling": "warning",
"track_segment_length": "error",
"track_width": "error",
"tracks_crossing": "error",
"unconnected_items": "error",
"unresolved_variable": "error",
"via_dangling": "warning",
"zones_intersect": "error"
},
"rules": {
"max_error": 0.005,
"min_clearance": 0.0,
"min_connection": 0.0,
"min_copper_edge_clearance": 0.5,
"min_groove_width": 0.0,
"min_hole_clearance": 0.25,
"min_hole_to_hole": 0.25,
"min_microvia_diameter": 0.2,
"min_microvia_drill": 0.1,
"min_resolved_spokes": 2,
"min_silk_clearance": 0.0,
"min_text_height": 0.8,
"min_text_thickness": 0.08,
"min_through_hole_diameter": 0.3,
"min_track_width": 0.0,
"min_via_annular_width": 0.1,
"min_via_diameter": 0.5,
"solder_mask_to_copper_clearance": 0.0,
"use_height_for_length_calcs": true
},
"teardrop_options": [
{
"td_onpthpad": true,
"td_onroundshapesonly": false,
"td_onsmdpad": true,
"td_ontrackend": false,
"td_onvia": true
}
],
"teardrop_parameters": [
{
"td_allow_use_two_tracks": true,
"td_curve_segcount": 0,
"td_height_ratio": 1.0,
"td_length_ratio": 0.5,
"td_maxheight": 2.0,
"td_maxlen": 1.0,
"td_on_pad_in_zone": false,
"td_target_name": "td_round_shape",
"td_width_to_size_filter_ratio": 0.9
},
{
"td_allow_use_two_tracks": true,
"td_curve_segcount": 0,
"td_height_ratio": 1.0,
"td_length_ratio": 0.5,
"td_maxheight": 2.0,
"td_maxlen": 1.0,
"td_on_pad_in_zone": false,
"td_target_name": "td_rect_shape",
"td_width_to_size_filter_ratio": 0.9
},
{
"td_allow_use_two_tracks": true,
"td_curve_segcount": 0,
"td_height_ratio": 1.0,
"td_length_ratio": 0.5,
"td_maxheight": 2.0,
"td_maxlen": 1.0,
"td_on_pad_in_zone": false,
"td_target_name": "td_track_end",
"td_width_to_size_filter_ratio": 0.9
}
],
"track_widths": [],
"tuning_pattern_settings": {
"diff_pair_defaults": {
"corner_radius_percentage": 80,
"corner_style": 1,
"max_amplitude": 1.0,
"min_amplitude": 0.2,
"single_sided": false,
"spacing": 1.0
},
"diff_pair_skew_defaults": {
"corner_radius_percentage": 80,
"corner_style": 1,
"max_amplitude": 1.0,
"min_amplitude": 0.2,
"single_sided": false,
"spacing": 0.6
},
"single_track_defaults": {
"corner_radius_percentage": 80,
"corner_style": 1,
"max_amplitude": 1.0,
"min_amplitude": 0.2,
"single_sided": false,
"spacing": 0.6
}
},
"via_dimensions": [],
"zones_allow_external_fillets": false
},
"ipc2581": {
"dist": "",
"distpn": "",
"internal_id": "",
"mfg": "",
"mpn": ""
},
"layer_pairs": [],
"layer_presets": [],
"viewports": []
},
"boards": [],
"component_class_settings": {
"assignments": [],
"meta": {
"version": 0
},
"sheet_component_classes": {
"enabled": false
}
},
"cvpcb": {
"equivalence_files": []
},
"erc": {
"erc_exclusions": [],
"meta": {
"version": 0
},
"pin_map": [
[
0,
0,
0,
0,
0,
0,
1,
0,
0,
0,
0,
2
],
[
0,
2,
0,
1,
0,
0,
1,
0,
2,
2,
2,
2
],
[
0,
0,
0,
0,
0,
0,
1,
0,
1,
0,
1,
2
],
[
0,
1,
0,
0,
0,
0,
1,
1,
2,
1,
1,
2
],
[
0,
0,
0,
0,
0,
0,
1,
0,
0,
0,
0,
2
],
[
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
2
],
[
1,
1,
1,
1,
1,
0,
1,
1,
1,
1,
1,
2
],
[
0,
0,
0,
1,
0,
0,
1,
0,
0,
0,
0,
2
],
[
0,
2,
1,
2,
0,
0,
1,
0,
2,
2,
2,
2
],
[
0,
2,
0,
1,
0,
0,
1,
0,
2,
0,
0,
2
],
[
0,
2,
1,
1,
0,
0,
1,
0,
2,
0,
0,
2
],
[
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2
]
],
"rule_severities": {
"bus_definition_conflict": "error",
"bus_entry_needed": "error",
"bus_to_bus_conflict": "error",
"bus_to_net_conflict": "error",
"different_unit_footprint": "error",
"different_unit_net": "error",
"duplicate_reference": "error",
"duplicate_sheet_names": "error",
"endpoint_off_grid": "warning",
"extra_units": "error",
"footprint_filter": "ignore",
"footprint_link_issues": "warning",
"four_way_junction": "ignore",
"global_label_dangling": "warning",
"hier_label_mismatch": "error",
"label_dangling": "error",
"label_multiple_wires": "warning",
"lib_symbol_issues": "warning",
"lib_symbol_mismatch": "warning",
"missing_bidi_pin": "warning",
"missing_input_pin": "warning",
"missing_power_pin": "error",
"missing_unit": "warning",
"multiple_net_names": "warning",
"net_not_bus_member": "warning",
"no_connect_connected": "warning",
"no_connect_dangling": "warning",
"pin_not_connected": "error",
"pin_not_driven": "error",
"pin_to_pin": "warning",
"power_pin_not_driven": "error",
"same_local_global_label": "warning",
"similar_label_and_power": "warning",
"similar_labels": "warning",
"similar_power": "warning",
"simulation_model_issue": "ignore",
"single_global_label": "ignore",
"unannotated": "error",
"unconnected_wire_endpoint": "warning",
"unit_value_mismatch": "error",
"unresolved_variable": "error",
"wire_dangling": "error"
}
},
"libraries": {
"pinned_footprint_libs": [],
"pinned_symbol_libs": []
},
"meta": {
"filename": "length_calculations.kicad_pro",
"version": 3
},
"net_settings": {
"classes": [
{
"bus_width": 12,
"clearance": 0.2,
"diff_pair_gap": 0.25,
"diff_pair_via_gap": 0.25,
"diff_pair_width": 0.2,
"line_style": 0,
"microvia_diameter": 0.3,
"microvia_drill": 0.1,
"name": "Default",
"pcb_color": "rgba(0, 0, 0, 0.000)",
"priority": 2147483647,
"schematic_color": "rgba(0, 0, 0, 0.000)",
"track_width": 0.2,
"via_diameter": 0.6,
"via_drill": 0.3,
"wire_width": 6
}
],
"meta": {
"version": 4
},
"net_colors": null,
"netclass_assignments": {
"Net-(R1-Pad2)": [
"CASE_1"
],
"Net-(R10-Pad1)": [
"CASE_5"
],
"Net-(R11-Pad2)": [
"CASE_6"
],
"Net-(R3-Pad2)": [
"CASE_2"
],
"Net-(R5-Pad2)": [
"CASE_3"
],
"Net-(R7-Pad2)": [
"CASE_4"
]
},
"netclass_patterns": []
},
"pcbnew": {
"last_paths": {
"gencad": "",
"idf": "",
"netlist": "",
"plot": "",
"pos_files": "",
"specctra_dsn": "",
"step": "",
"svg": "",
"vrml": ""
},
"page_layout_descr_file": ""
},
"schematic": {
"annotate_start_num": 0,
"bom_export_filename": "${PROJECTNAME}.csv",
"bom_fmt_presets": [],
"bom_fmt_settings": {
"field_delimiter": ",",
"keep_line_breaks": false,
"keep_tabs": false,
"name": "CSV",
"ref_delimiter": ",",
"ref_range_delimiter": "",
"string_delimiter": "\""
},
"bom_presets": [],
"bom_settings": {
"exclude_dnp": false,
"fields_ordered": [
{
"group_by": false,
"label": "Reference",
"name": "Reference",
"show": true
},
{
"group_by": true,
"label": "Value",
"name": "Value",
"show": true
},
{
"group_by": true,
"label": "Footprint",
"name": "Footprint",
"show": true
},
{
"group_by": false,
"label": "Datasheet",
"name": "Datasheet",
"show": true
},
{
"group_by": false,
"label": "Description",
"name": "Description",
"show": false
},
{
"group_by": false,
"label": "Qty",
"name": "${QUANTITY}",
"show": true
},
{
"group_by": false,
"label": "#",
"name": "${ITEM_NUMBER}",
"show": false
},
{
"group_by": true,
"label": "DNP",
"name": "${DNP}",
"show": true
},
{
"group_by": true,
"label": "Exclude from BOM",
"name": "${EXCLUDE_FROM_BOM}",
"show": true
},
{
"group_by": true,
"label": "Exclude from Board",
"name": "${EXCLUDE_FROM_BOARD}",
"show": true
}
],
"filter_string": "",
"group_symbols": true,
"include_excluded_from_bom": true,
"name": "",
"sort_asc": true,
"sort_field": "Reference"
},
"connection_grid_size": 50.0,
"drawing": {
"dashed_lines_dash_length_ratio": 12.0,
"dashed_lines_gap_length_ratio": 3.0,
"default_line_thickness": 6.0,
"default_text_size": 50.0,
"field_names": [],
"intersheets_ref_own_page": false,
"intersheets_ref_prefix": "",
"intersheets_ref_short": false,
"intersheets_ref_show": false,
"intersheets_ref_suffix": "",
"junction_size_choice": 3,
"label_size_ratio": 0.375,
"operating_point_overlay_i_precision": 3,
"operating_point_overlay_i_range": "~A",
"operating_point_overlay_v_precision": 3,
"operating_point_overlay_v_range": "~V",
"overbar_offset_ratio": 1.23,
"pin_symbol_size": 25.0,
"text_offset_ratio": 0.15
},
"legacy_lib_dir": "",
"legacy_lib_list": [],
"meta": {
"version": 1
},
"net_format_name": "",
"page_layout_descr_file": "",
"plot_directory": "",
"space_save_all_events": true,
"spice_current_sheet_as_root": false,
"spice_external_command": "spice \"%I\"",
"spice_model_current_sheet_as_root": true,
"spice_save_all_currents": false,
"spice_save_all_dissipations": false,
"spice_save_all_voltages": false,
"subpart_first_id": 65,
"subpart_id_separator": 0
},
"sheets": [
[
"a4e4d485-017a-4720-8992-147a6425c6a8",
"Root"
]
],
"text_variables": {}
}

View File

LOADING design file

View File

@ -68,6 +68,7 @@ set( QA_PCBNEW_SRCS
drc/test_drc_incorrect_text_mirror.cpp
drc/test_drc_starved_thermal.cpp
drc/test_drc_orientation.cpp
drc/test_drc_lengths.cpp
pcb_io/altium/test_altium_rule_transformer.cpp
pcb_io/altium/test_altium_pcblib_import.cpp

View File

@ -0,0 +1,108 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright The 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 <qa_utils/wx_utils/unit_test_utils.h>
#include <pcbnew_utils/board_test_utils.h>
#include <board.h>
#include <board_design_settings.h>
#include <pad.h>
#include <pcb_track.h>
#include <pcb_marker.h>
#include <footprint.h>
#include <drc/drc_item.h>
#include <settings/settings_manager.h>
struct DRC_REGRESSION_TEST_FIXTURE
{
DRC_REGRESSION_TEST_FIXTURE() : m_settingsManager( true /* headless */ ) {}
SETTINGS_MANAGER m_settingsManager;
std::unique_ptr<BOARD> m_board;
};
BOOST_FIXTURE_TEST_CASE( DRCLengths, DRC_REGRESSION_TEST_FIXTURE )
{
// Check for minimum copper connection errors
std::vector<std::pair<wxString, int>> tests = {
{ "length_calculations", 0 } // Exclude warnings on unconnected pads
};
for( const std::pair<wxString, int>& test : tests )
{
KI_TEST::LoadBoard( m_settingsManager, test.first, m_board );
KI_TEST::FillZones( m_board.get() );
std::vector<DRC_ITEM> violations;
BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings();
// Disable DRC tests not useful or not handled in this testcase
bds.m_DRCSeverities[DRCE_INVALID_OUTLINE] = SEVERITY::RPT_SEVERITY_IGNORE;
bds.m_DRCSeverities[DRCE_UNCONNECTED_ITEMS] = SEVERITY::RPT_SEVERITY_IGNORE;
bds.m_DRCSeverities[DRCE_COPPER_SLIVER] = SEVERITY::RPT_SEVERITY_IGNORE;
bds.m_DRCSeverities[DRCE_STARVED_THERMAL] = SEVERITY::RPT_SEVERITY_IGNORE;
bds.m_DRCSeverities[DRCE_DRILL_OUT_OF_RANGE] = SEVERITY::RPT_SEVERITY_IGNORE;
bds.m_DRCSeverities[DRCE_VIA_DIAMETER] = SEVERITY::RPT_SEVERITY_IGNORE;
// These DRC tests are not useful and do not work because they need a footprint library
// associated to the board
bds.m_DRCSeverities[DRCE_LIB_FOOTPRINT_ISSUES] = SEVERITY::RPT_SEVERITY_IGNORE;
bds.m_DRCSeverities[DRCE_LIB_FOOTPRINT_MISMATCH] = SEVERITY::RPT_SEVERITY_IGNORE;
bds.m_DRCSeverities[DRCE_DANGLING_VIA] = SEVERITY::RPT_SEVERITY_IGNORE;
// Ensure that our desired error is fired
bds.m_DRCSeverities[DRCE_LENGTH_OUT_OF_RANGE] = SEVERITY::RPT_SEVERITY_ERROR;
bds.m_DRCEngine->SetViolationHandler(
[&]( const std::shared_ptr<DRC_ITEM>& aItem, VECTOR2I aPos, int aLayer,
DRC_CUSTOM_MARKER_HANDLER* aCustomHandler )
{
if( bds.GetSeverity( aItem->GetErrorCode() ) == SEVERITY::RPT_SEVERITY_ERROR )
violations.push_back( *aItem );
} );
bds.m_DRCEngine->RunTests( EDA_UNITS::MM, true, false );
if( violations.size() == test.second )
{
BOOST_CHECK_EQUAL( 1, 1 ); // quiet "did not check any assertions" warning
BOOST_TEST_MESSAGE( wxString::Format( "DRC lengths: %s, passed", test.first ) );
}
else
{
UNITS_PROVIDER unitsProvider( pcbIUScale, EDA_UNITS::INCH );
std::map<KIID, EDA_ITEM*> itemMap;
m_board->FillItemMap( itemMap );
for( const DRC_ITEM& item : violations )
{
BOOST_TEST_MESSAGE( item.ShowReport( &unitsProvider, RPT_SEVERITY_ERROR, itemMap ) );
}
BOOST_ERROR( wxString::Format( "DRC skew: %s, failed (violations found %d expected %d)", test.first,
(int) violations.size(), test.second ) );
}
}
}

View File

@ -28,10 +28,12 @@
#ifndef __PNS_LOG_VIEWER_FRAME_H
#define __PNS_LOG_VIEWER_FRAME_H
#include <length_calculation.h>
#include <pcb_painter.h>
#include <pcb_test_frame.h>
#include <pcbnew_utils/board_test_utils.h>
#include <reporter.h>
#include <router/pns_solid.h>
#include "pns_log_file.h"
#include "pns_log_player.h"
@ -105,6 +107,58 @@ public:
return ( aLayer / 2 ) - 1;
}
long long int CalculateRoutedPathLength( const PNS::ITEM_SET& aLine, const PNS::SOLID* aStartPad,
const PNS::SOLID* aEndPad ) override
{
std::vector<LENGTH_CALCULATION_ITEM> lengthItems;
for( int idx = 0; idx < aLine.Size(); idx++ )
{
const PNS::ITEM* lineItem = aLine[idx];
if( const PNS::LINE* l = dyn_cast<const PNS::LINE*>( lineItem ) )
{
LENGTH_CALCULATION_ITEM item;
item.SetLine( l->CLine() );
const PCB_LAYER_ID layer = GetBoardLayerFromPNSLayer( lineItem->Layer() );
item.SetLayers( layer );
lengthItems.emplace_back( std::move( item ) );
}
else if( lineItem->OfKind( PNS::ITEM::VIA_T ) && idx > 0 && idx < aLine.Size() - 1 )
{
const int layerPrev = aLine[idx - 1]->Layer();
const int layerNext = aLine[idx + 1]->Layer();
const PCB_LAYER_ID pcbLayerPrev = GetBoardLayerFromPNSLayer( layerPrev );
const PCB_LAYER_ID pcbLayerNext = GetBoardLayerFromPNSLayer( layerNext );
if( layerPrev != layerNext )
{
LENGTH_CALCULATION_ITEM item;
item.SetVia( static_cast<PCB_VIA*>( lineItem->GetSourceItem() ) );
item.SetLayers( pcbLayerPrev, pcbLayerNext );
lengthItems.emplace_back( std::move( item ) );
}
}
}
const PAD* startPad = nullptr;
const PAD* endPad = nullptr;
if( aStartPad )
startPad = static_cast<PAD*>( aStartPad->Parent() );
if( aEndPad )
endPad = static_cast<PAD*>( aEndPad->Parent() );
constexpr PATH_OPTIMISATIONS opts = {
.OptimiseViaLayers = false, .MergeTracks = false, .OptimiseTracesInPads = false, .InferViaInPad = true
};
return m_board->GetLengthCalculation()->CalculateLength( lengthItems, opts, startPad, endPad );
}
private:
std::shared_ptr<BOARD> m_board;
};