KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_connectivity_clusters.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18 */
19
21#include <boost/test/data/test_case.hpp>
22
24
25#include <board.h>
30
31#include <deque>
32#include <map>
33#include <set>
34
35/*
36 * SearchClusters() computes connected components and RN_NET builds a minimum spanning forest
37 * over them. Both are stated as properties here rather than as recorded numbers, so the
38 * checks stand on their own rather than on whatever the current implementation happens to
39 * produce.
40 */
41
42namespace
43{
44
45struct CONNECTIVITY_CLUSTER_FIXTURE
46{
47 SETTINGS_MANAGER m_settingsManager;
48 std::unique_ptr<BOARD> m_board;
49};
50
51
53const std::vector<wxString> c_boards = {
54 "issue22267",
55 "issue17429",
56 "issue16182",
57 "issue14559",
58 "issue8909",
59};
60
61} // namespace
62
63
64BOOST_FIXTURE_TEST_SUITE( ConnectivityClusters, CONNECTIVITY_CLUSTER_FIXTURE )
65
66
67
72BOOST_DATA_TEST_CASE( ClustersAreTheConnectedComponents, boost::unit_test::data::make( c_boards ),
73 boardName )
74{
75 KI_TEST::LoadBoard( m_settingsManager, boardName, m_board );
76 KI_TEST::FillZones( m_board.get() );
77 m_board->BuildConnectivity();
78
79 std::shared_ptr<CN_CONNECTIVITY_ALGO> algo =
80 m_board->GetConnectivity()->GetConnectivityAlgo();
81
83 algo->SearchClusters( CN_CONNECTIVITY_ALGO::CSM_RATSNEST );
84
85 BOOST_REQUIRE( !clusters.empty() );
86
87 std::map<CN_ITEM*, size_t> clusterOf;
88 size_t placed = 0;
89
90 for( size_t ii = 0; ii < clusters.size(); ++ii )
91 {
92 for( CN_ITEM* item : *clusters[ii] )
93 {
94 BOOST_REQUIRE_MESSAGE( clusterOf.emplace( item, ii ).second,
95 "item appears in more than one cluster" );
96 placed++;
97 }
98 }
99
100 BOOST_CHECK_EQUAL( placed, clusterOf.size() );
101
102 // No adjacency between two items that both took part may span two clusters, or they would
103 // be one component and the search split them.
104 for( const auto& [item, cluster] : clusterOf )
105 {
106 for( CN_ITEM* neighbour : item->ConnectedItems() )
107 {
108 auto it = clusterOf.find( neighbour );
109
110 if( it == clusterOf.end() )
111 continue;
112
113 if( neighbour->Net() != item->Net() )
114 continue;
115
116 BOOST_REQUIRE_MESSAGE( it->second == cluster,
117 "connected items landed in different clusters" );
118 }
119 }
120
121 // Conversely each cluster has to be reachable end to end, or the search merged components
122 // that share no path.
123 for( const std::shared_ptr<CN_CLUSTER>& cluster : clusters )
124 {
125 std::set<CN_ITEM*> members( cluster->begin(), cluster->end() );
126 std::set<CN_ITEM*> seen;
127 std::deque<CN_ITEM*> queue;
128
129 queue.push_back( *cluster->begin() );
130 seen.insert( *cluster->begin() );
131
132 while( !queue.empty() )
133 {
134 CN_ITEM* current = queue.front();
135 queue.pop_front();
136
137 for( CN_ITEM* neighbour : current->ConnectedItems() )
138 {
139 if( members.count( neighbour ) && !seen.count( neighbour ) )
140 {
141 seen.insert( neighbour );
142 queue.push_back( neighbour );
143 }
144 }
145 }
146
147 BOOST_REQUIRE_EQUAL( seen.size(), members.size() );
148 }
149}
150
151
158BOOST_DATA_TEST_CASE( RatsnestSpansTheClustersOfEachNet,
159 boost::unit_test::data::make( c_boards ), boardName )
160{
161 KI_TEST::LoadBoard( m_settingsManager, boardName, m_board );
162 KI_TEST::FillZones( m_board.get() );
163 m_board->BuildConnectivity();
164
165 std::shared_ptr<CONNECTIVITY_DATA> conn = m_board->GetConnectivity();
166 std::shared_ptr<CN_CONNECTIVITY_ALGO> algo = conn->GetConnectivityAlgo();
167
168 std::map<int, std::set<const CN_CLUSTER*>> clustersPerNet;
169
170 for( const std::shared_ptr<CN_CLUSTER>& cluster :
171 algo->SearchClusters( CN_CONNECTIVITY_ALGO::CSM_RATSNEST ) )
172 {
173 // A kept island contributes no anchor, matching what AddCluster() skips.
174 if( cluster->IsOrphaned() && cluster->Size() == 1
175 && dynamic_cast<CN_ZONE_LAYER*>( *cluster->begin() ) )
176 {
177 continue;
178 }
179
180 if( cluster->OriginNet() > 0 )
181 clustersPerNet[cluster->OriginNet()].insert( cluster.get() );
182 }
183
184 size_t netsChecked = 0;
185 size_t edgesSeen = 0;
186
187 for( const auto& [net, netClusters] : clustersPerNet )
188 {
189 RN_NET* rn = conn->GetRatsnestForNet( net );
190
191 if( !rn || rn->GetNodeCount() == 0 )
192 continue;
193
194 const std::vector<CN_EDGE>& edges = rn->GetEdges();
195
196 BOOST_REQUIRE_MESSAGE( edges.size() == netClusters.size() - 1,
197 "net " << net << " has " << netClusters.size() << " clusters but "
198 << edges.size() << " ratsnest edges" );
199
200 for( const CN_EDGE& edge : edges )
201 BOOST_REQUIRE( edge.GetSourceNode() && edge.GetTargetNode() );
202
203 netsChecked++;
204 edgesSeen += edges.size();
205 }
206
207 BOOST_REQUIRE_MESSAGE( netsChecked > 0, "no net on this board exercised the ratsnest" );
208 BOOST_TEST_MESSAGE( "net count " << netsChecked << ", ratsnest edges " << edgesSeen );
209}
210
211
std::vector< std::shared_ptr< CN_CLUSTER > > CLUSTERS
CN_EDGE represents a point-to-point connection, whether realized or unrealized (ie: tracks etc.
CN_ITEM represents a BOARD_CONNETED_ITEM in the connectivity system (ie: a pad, track/arc/via,...
const std::vector< CN_ITEM * > & ConnectedItems() const
Describe ratsnest for a single net.
unsigned int GetNodeCount() const
const std::vector< CN_EDGE > & GetEdges() const
void LoadBoard(SETTINGS_MANAGER &aSettingsManager, const wxString &aRelPath, std::unique_ptr< BOARD > &aBoard)
void FillZones(BOARD *m_board)
Class that computes missing connections on a PCB.
BOOST_DATA_TEST_CASE(ConvertToKicadUnit, boost::unit_test::data::make(altium_to_kicad_unit), input_value, expected_result)
Test conversation from Altium internal units into KiCad internal units.
BOOST_DATA_TEST_CASE(ClustersAreTheConnectedComponents, boost::unit_test::data::make(c_boards), boardName)
Every item must land in exactly one cluster, each cluster must be internally reachable over the adjac...
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
BOOST_AUTO_TEST_SUITE_END()
BOOST_TEST_MESSAGE("Polyline has "<< chain.PointCount()<< " points")
BOOST_CHECK_EQUAL(result, "25.4")