KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_drc_chain_length_trunk.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, you may find one here:
18 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
19 */
20
21#include <boost/test/unit_test.hpp>
22
23#include <filesystem>
24#include <fstream>
25
26#include <board.h>
29#include <drc/drc_engine.h>
30#include <drc/drc_item.h>
31#include <footprint.h>
32#include <netinfo.h>
33#include <pad.h>
34#include <pcb_marker.h>
35#include <pcb_track.h>
37
38
39// Two-net daisy chain through a single bridge, terminals set. The trunk
40// length should equal the sum of both routed segments + the bridge span.
41// A `(constraint net_chain_length (max 80mm))` rule passes when trunk == 50 mm.
42static const char* DAISY_PCB = R"(
43(kicad_pcb
44 (version 20250904)
45 (generator "pcbnew")
46 (generator_version "9.99")
47 (layers
48 (0 "F.Cu" signal)
49 (2 "B.Cu" signal)
50 (44 "Edge.Cuts" user)
51 )
52 (net 0 "")
53 (net 1 "/NET_A")
54 (net 2 "/NET_B")
55 (gr_line (start -5 -5) (end 60 -5) (layer "Edge.Cuts") (width 0.05))
56 (gr_line (start 60 -5) (end 60 5) (layer "Edge.Cuts") (width 0.05))
57 (gr_line (start 60 5) (end -5 5) (layer "Edge.Cuts") (width 0.05))
58 (gr_line (start -5 5) (end -5 -5) (layer "Edge.Cuts") (width 0.05))
59 (footprint "Term1" (layer "F.Cu") (uuid "00000000-0000-0000-0000-000000000a01")
60 (at 0 0)
61 (pad "1" smd rect (at 0 0) (size 0.8 0.8) (layers "F.Cu") (net 1 "/NET_A") (uuid "00000000-0000-0000-0000-000000000a02"))
62 )
63 (footprint "Bridge" (layer "F.Cu") (uuid "00000000-0000-0000-0000-000000000b01")
64 (at 22.5 0)
65 (pad "1" smd rect (at -2.5 0) (size 0.8 0.8) (layers "F.Cu") (net 1 "/NET_A") (uuid "00000000-0000-0000-0000-000000000b02"))
66 (pad "2" smd rect (at 2.5 0) (size 0.8 0.8) (layers "F.Cu") (net 2 "/NET_B") (uuid "00000000-0000-0000-0000-000000000b03"))
67 )
68 (footprint "Term2" (layer "F.Cu") (uuid "00000000-0000-0000-0000-000000000c01")
69 (at 50 0)
70 (pad "1" smd rect (at 0 0) (size 0.8 0.8) (layers "F.Cu") (net 2 "/NET_B") (uuid "00000000-0000-0000-0000-000000000c02"))
71 )
72 (segment (start 0 0) (end 20 0) (width 0.2) (layer "F.Cu") (net 1))
73 (segment (start 25 0) (end 50 0) (width 0.2) (layer "F.Cu") (net 2))
74)
75)";
76
77
78// Same trunk as DAISY_PCB but adding three perpendicular branches off the trunk
79// (T-junctions in the routed copper) all carrying the same chain so the trunk
80// stays unaffected — provides a regression check that branches don't add to
81// the trunk length when terminals are set.
82static const char* BRANCHED_PCB = R"(
83(kicad_pcb
84 (version 20250904)
85 (generator "pcbnew")
86 (generator_version "9.99")
87 (layers
88 (0 "F.Cu" signal)
89 (2 "B.Cu" signal)
90 (44 "Edge.Cuts" user)
91 )
92 (net 0 "")
93 (net 1 "/NET_A")
94 (gr_line (start -5 -25) (end 60 -25) (layer "Edge.Cuts") (width 0.05))
95 (gr_line (start 60 -25) (end 60 25) (layer "Edge.Cuts") (width 0.05))
96 (gr_line (start 60 25) (end -5 25) (layer "Edge.Cuts") (width 0.05))
97 (gr_line (start -5 25) (end -5 -25) (layer "Edge.Cuts") (width 0.05))
98 (footprint "Term1" (layer "F.Cu") (uuid "00000000-0000-0000-0000-000000000d01")
99 (at 0 0)
100 (pad "1" smd rect (at 0 0) (size 0.8 0.8) (layers "F.Cu") (net 1 "/NET_A") (uuid "00000000-0000-0000-0000-000000000d02"))
101 )
102 (footprint "Term2" (layer "F.Cu") (uuid "00000000-0000-0000-0000-000000000e01")
103 (at 50 0)
104 (pad "1" smd rect (at 0 0) (size 0.8 0.8) (layers "F.Cu") (net 1 "/NET_A") (uuid "00000000-0000-0000-0000-000000000e02"))
105 )
106 (segment (start 0 0) (end 50 0) (width 0.2) (layer "F.Cu") (net 1))
107 (segment (start 12.5 0) (end 12.5 15) (width 0.2) (layer "F.Cu") (net 1))
108 (segment (start 25 0) (end 25 -20) (width 0.2) (layer "F.Cu") (net 1))
109 (segment (start 37.5 0) (end 37.5 18) (width 0.2) (layer "F.Cu") (net 1))
110)
111)";
112
113
114namespace
115{
116std::unique_ptr<BOARD> loadBoard( const char* aText, const std::string& aSubdir )
117{
118 namespace fs = std::filesystem;
119 fs::path tmpDir = fs::temp_directory_path() / aSubdir;
120 fs::create_directories( tmpDir );
121 fs::path pcbPath = tmpDir / "trunk.kicad_pcb";
122
123 {
124 std::ofstream out( pcbPath );
125 out << aText;
126 }
127
128 PCB_IO_KICAD_SEXPR plugin;
129 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
130 plugin.LoadBoard( pcbPath.string(), board.get() );
131 board->BuildConnectivity();
132 fs::remove( pcbPath );
133 return board;
134}
135
136// Tag every "/NET_*" net into the named chain. Terminal pads come from the
137// footprints whose anchor matches the given X positions (in mm) — the inline
138// PCB strings here don't set explicit Reference properties.
139void tagAndSetTerminals( BOARD* aBoard, const wxString& aChain,
140 double aTermAxMm, double aTermBxMm )
141{
142 for( NETINFO_ITEM* n : aBoard->GetNetInfo() )
143 {
144 if( n && n->GetNetname().StartsWith( wxS( "/NET_" ) ) )
145 n->SetNetChain( aChain );
146 }
147
148 PAD* termA = nullptr;
149 PAD* termB = nullptr;
150 constexpr int EPS = 100;
151 const VECTOR2I targetA( static_cast<int>( aTermAxMm * 1000000 ), 0 );
152 const VECTOR2I targetB( static_cast<int>( aTermBxMm * 1000000 ), 0 );
153
154 for( FOOTPRINT* fp : aBoard->Footprints() )
155 {
156 if( fp->Pads().empty() )
157 continue;
158
159 VECTOR2I pos = fp->GetPosition();
160
161 if( std::abs( pos.x - targetA.x ) <= EPS && std::abs( pos.y - targetA.y ) <= EPS )
162 termA = fp->Pads().front();
163
164 if( std::abs( pos.x - targetB.x ) <= EPS && std::abs( pos.y - targetB.y ) <= EPS )
165 termB = fp->Pads().front();
166 }
167
168 for( NETINFO_ITEM* n : aBoard->GetNetInfo() )
169 {
170 if( n && n->GetNetChain() == aChain )
171 {
172 if( termA )
173 n->SetTerminalPad( 0, termA );
174 if( termB )
175 n->SetTerminalPad( 1, termB );
176 }
177 }
178}
179
180} // namespace
181
182
183BOOST_AUTO_TEST_SUITE( DRCChainLengthTrunk )
184
185
186// Two-net daisy with terminals: trunk = (20 + 25 + 5 bridge) = 50 mm.
187BOOST_AUTO_TEST_CASE( DaisyChainTrunkEqualsSumExplicit )
188{
189 auto board = loadBoard( DAISY_PCB, "kicad_drc_trunk_daisy" );
190 tagAndSetTerminals( board.get(), wxS( "DSY" ), 0.0, 50.0 );
191
192 std::set<BOARD_CONNECTED_ITEM*> items;
193
194 for( PCB_TRACK* t : board->Tracks() )
195 {
196 if( t->GetNet() && t->GetNet()->GetNetChain() == wxS( "DSY" ) )
197 items.insert( t );
198 }
199
200 for( FOOTPRINT* fp : board->Footprints() )
201 {
202 for( PAD* p : fp->Pads() )
203 {
204 if( p->GetNet() && p->GetNet()->GetNetChain() == wxS( "DSY" ) )
205 items.insert( p );
206 }
207 }
208
209 CHAIN_TOPOLOGY topo( board.get(), wxS( "DSY" ), items );
210
211 BOOST_REQUIRE( topo.IsValid() );
212 BOOST_CHECK_CLOSE( topo.TrunkLength(), 50.0e6, 5.0 );
213}
214
215
216// Trunk + three perpendicular branches: trunk should be 50 mm, branches
217// reported as stubs (3 of them).
218BOOST_AUTO_TEST_CASE( BranchedChainTrunkExcludesStubs )
219{
220 auto board = loadBoard( BRANCHED_PCB, "kicad_drc_trunk_branched" );
221 tagAndSetTerminals( board.get(), wxS( "BR" ), 0.0, 50.0 );
222
223 std::set<BOARD_CONNECTED_ITEM*> items;
224
225 for( PCB_TRACK* t : board->Tracks() )
226 {
227 if( t->GetNet() && t->GetNet()->GetNetChain() == wxS( "BR" ) )
228 items.insert( t );
229 }
230
231 for( FOOTPRINT* fp : board->Footprints() )
232 {
233 for( PAD* p : fp->Pads() )
234 {
235 if( p->GetNet() && p->GetNet()->GetNetChain() == wxS( "BR" ) )
236 items.insert( p );
237 }
238 }
239
240 CHAIN_TOPOLOGY topo( board.get(), wxS( "BR" ), items );
241
242 BOOST_REQUIRE( topo.IsValid() );
243 BOOST_CHECK_CLOSE( topo.TrunkLength(), 50.0e6, 5.0 );
244 BOOST_CHECK_EQUAL( topo.Stubs().size(), 3u );
245}
246
247
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:323
const NETINFO_LIST & GetNetInfo() const
Definition board.h:1004
const FOOTPRINTS & Footprints() const
Definition board.h:364
Build a topological view of a single named net chain's routed copper.
const std::vector< STUB > & Stubs() const
double TrunkLength() const
bool IsValid() const
Handle the data for a net.
Definition netinfo.h:50
Definition pad.h:55
A #PLUGIN derivation for saving and loading Pcbnew s-expression formatted files.
BOARD * LoadBoard(const wxString &aFileName, BOARD *aAppendToMe, const std::map< std::string, UTF8 > *aProperties=nullptr, PROJECT *aProject=nullptr) override
Load information from some input file format that this PCB_IO implementation knows about into either ...
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:400
BOOST_AUTO_TEST_CASE(HorizontalAlignment)
BOOST_AUTO_TEST_SUITE(CadstarPartParser)
BOOST_AUTO_TEST_CASE(DaisyChainTrunkEqualsSumExplicit)
static const char * BRANCHED_PCB
static const char * DAISY_PCB
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
BOOST_AUTO_TEST_SUITE_END()
BOOST_CHECK_EQUAL(result, "25.4")
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:687