KiCad PCB EDA Suite
Loading...
Searching...
No Matches
ratsnest_data.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 (C) 2013-2017 CERN
5 * Copyright (C) 2019-2023 KiCad Developers, see AUTHORS.txt for contributors.
6 *
7 * @author Maciej Suminski <[email protected]>
8 * @author Tomasz Wlostowski <[email protected]>
9 *
10 * This program is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU General Public License
12 * as published by the Free Software Foundation; either version 2
13 * of the License, or (at your option) any later version.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, you may find one here:
22 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
23 * or you may search the http://www.gnu.org website for the version 2 license,
24 * or you may write to the Free Software Foundation, Inc.,
25 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
26 */
27
33#ifdef PROFILE
34#include <core/profile.h>
35#endif
36
38#include <functional>
39using namespace std::placeholders;
40
41#include <algorithm>
42#include <cassert>
43#include <limits>
44
45#include <delaunator.hpp>
46
48{
49
50public:
51 disjoint_set( size_t size )
52 {
53 m_data.resize( size );
54 m_depth.resize( size, 0 );
55
56 for( size_t i = 0; i < size; i++ )
57 m_data[i] = i;
58 }
59
60 int find( int aVal )
61 {
62 int root = aVal;
63
64 while( m_data[root] != root )
65 root = m_data[root];
66
67 // Compress the path
68 while( m_data[aVal] != aVal )
69 {
70 auto& tmp = m_data[aVal];
71 aVal = tmp;
72 tmp = root;
73 }
74
75 return root;
76 }
77
78
79 bool unite( int aVal1, int aVal2 )
80 {
81 aVal1 = find( aVal1 );
82 aVal2 = find( aVal2 );
83
84 if( aVal1 != aVal2 )
85 {
86 if( m_depth[aVal1] < m_depth[aVal2] )
87 {
88 m_data[aVal1] = aVal2;
89 }
90 else
91 {
92 m_data[aVal2] = aVal1;
93
94 if( m_depth[aVal1] == m_depth[aVal2] )
95 m_depth[aVal1]++;
96 }
97
98 return true;
99 }
100
101 return false;
102 }
103
104private:
105 std::vector<int> m_data;
106 std::vector<int> m_depth;
107};
108
109
110void RN_NET::kruskalMST( const std::vector<CN_EDGE> &aEdges )
111{
112 disjoint_set dset( m_nodes.size() );
113
114 m_rnEdges.clear();
115
116 int i = 0;
117
118 for( const std::shared_ptr<CN_ANCHOR>& node : m_nodes )
119 node->SetTag( i++ );
120
121 for( const CN_EDGE& tmp : aEdges )
122 {
123 const std::shared_ptr<const CN_ANCHOR>& source = tmp.GetSourceNode();
124 const std::shared_ptr<const CN_ANCHOR>& target = tmp.GetTargetNode();
125
126 wxCHECK2( source && !source->Dirty() && target && !target->Dirty(), continue );
127
128 if( dset.unite( source->GetTag(), target->GetTag() ) )
129 {
130 if( tmp.GetWeight() > 0 )
131 m_rnEdges.push_back( tmp );
132 }
133 }
134}
135
136
138{
139private:
140 std::multiset<std::shared_ptr<CN_ANCHOR>, CN_PTR_CMP> m_allNodes;
141
142
143 // Checks if all nodes in aNodes lie on a single line. Requires the nodes to
144 // have unique coordinates!
145 bool areNodesColinear( const std::vector<std::shared_ptr<CN_ANCHOR>>& aNodes ) const
146 {
147 if ( aNodes.size() <= 2 )
148 return true;
149
150 const VECTOR2I p0( aNodes[0]->Pos() );
151 const VECTOR2I v0( aNodes[1]->Pos() - p0 );
152
153 for( unsigned i = 2; i < aNodes.size(); i++ )
154 {
155 const VECTOR2I v1 = aNodes[i]->Pos() - p0;
156
157 if( v0.Cross( v1 ) != 0 )
158 return false;
159 }
160
161 return true;
162 }
163
164public:
165
166 void Clear()
167 {
168 m_allNodes.clear();
169 }
170
171 void AddNode( const std::shared_ptr<CN_ANCHOR>& aNode )
172 {
173 m_allNodes.insert( aNode );
174 }
175
176 void Triangulate( std::vector<CN_EDGE>& mstEdges )
177 {
178 std::vector<double> node_pts;
179 std::vector<std::shared_ptr<CN_ANCHOR>> anchors;
180 std::vector< std::vector<std::shared_ptr<CN_ANCHOR>> > anchorChains( m_allNodes.size() );
181
182 node_pts.reserve( 2 * m_allNodes.size() );
183 anchors.reserve( m_allNodes.size() );
184
185 auto addEdge =
186 [&]( const std::shared_ptr<CN_ANCHOR>& src, const std::shared_ptr<CN_ANCHOR>& dst )
187 {
188 mstEdges.emplace_back( src, dst, src->Dist( *dst ) );
189 };
190
191 std::shared_ptr<CN_ANCHOR> prev = nullptr;
192
193 for( const std::shared_ptr<CN_ANCHOR>& n : m_allNodes )
194 {
195 if( !prev || prev->Pos() != n->Pos() )
196 {
197 node_pts.push_back( n->Pos().x );
198 node_pts.push_back( n->Pos().y );
199 anchors.push_back( n );
200 prev = n;
201 }
202
203 anchorChains[anchors.size() - 1].push_back( n );
204 }
205
206 if( anchors.empty() )
207 {
208 return;
209 }
210 else if( anchors.size() == 1 )
211 {
212 // The anchors all have the same position, but may not have overlapping layers.
213 prev = nullptr;
214
215 for( const std::shared_ptr<CN_ANCHOR>& n : m_allNodes )
216 {
217 if( prev && !( prev->Parent()->GetLayerSet() & n->Parent()->GetLayerSet() ).any() )
218 {
219 // Use a minimal but non-zero distance or the edge will be ignored
220 mstEdges.emplace_back( prev, n, 1 );
221 }
222
223 prev = n;
224 }
225
226 return;
227 }
228 else if( areNodesColinear( anchors ) )
229 {
230 // special case: all nodes are on the same line - there's no
231 // triangulation for such set. In this case, we sort along any coordinate
232 // and chain the nodes together.
233 for( size_t i = 0; i < anchors.size() - 1; i++ )
234 addEdge( anchors[i], anchors[i + 1] );
235 }
236 else
237 {
238 delaunator::Delaunator delaunator( node_pts );
239 auto& triangles = delaunator.triangles;
240
241 for( size_t i = 0; i < triangles.size(); i += 3 )
242 {
243 addEdge( anchors[triangles[i]], anchors[triangles[i + 1]] );
244 addEdge( anchors[triangles[i + 1]], anchors[triangles[i + 2]] );
245 addEdge( anchors[triangles[i + 2]], anchors[triangles[i]] );
246 }
247
248 for( size_t i = 0; i < delaunator.halfedges.size(); i++ )
249 {
250 if( delaunator.halfedges[i] == delaunator::INVALID_INDEX )
251 continue;
252
253 addEdge( anchors[triangles[i]], anchors[triangles[delaunator.halfedges[i]]] );
254 }
255 }
256
257 for( size_t i = 0; i < anchorChains.size(); i++ )
258 {
259 std::vector<std::shared_ptr<CN_ANCHOR>>& chain = anchorChains[i];
260
261 if( chain.size() < 2 )
262 continue;
263
264 std::sort( chain.begin(), chain.end(),
265 [] ( const std::shared_ptr<CN_ANCHOR>& a, const std::shared_ptr<CN_ANCHOR>& b )
266 {
267 return a->GetCluster().get() < b->GetCluster().get();
268 } );
269
270 for( unsigned int j = 1; j < chain.size(); j++ )
271 {
272 const std::shared_ptr<CN_ANCHOR>& prevNode = chain[j - 1];
273 const std::shared_ptr<CN_ANCHOR>& curNode = chain[j];
274 int weight = prevNode->GetCluster() != curNode->GetCluster() ? 1 : 0;
275 mstEdges.emplace_back( prevNode, curNode, weight );
276 }
277 }
278 }
279};
280
281
282RN_NET::RN_NET() : m_dirty( true )
283{
285}
286
287
289{
290 // Special cases do not need complicated algorithms (actually, it does not work well with
291 // the Delaunay triangulator)
292 if( m_nodes.size() <= 2 )
293 {
294 m_rnEdges.clear();
295
296 // Check if the only possible connection exists
297 if( m_boardEdges.size() == 0 && m_nodes.size() == 2 )
298 {
299 // There can be only one possible connection, but it is missing
300 auto it = m_nodes.begin();
301 const std::shared_ptr<CN_ANCHOR>& source = *it++;
302 const std::shared_ptr<CN_ANCHOR>& target = *it;
303
304 source->SetTag( 0 );
305 target->SetTag( 1 );
306 m_rnEdges.emplace_back( source, target );
307 }
308 else
309 {
310 // Set tags to m_nodes as connected
311 for( const std::shared_ptr<CN_ANCHOR>& node : m_nodes )
312 node->SetTag( 0 );
313 }
314
315 return;
316 }
317
318
319 m_triangulator->Clear();
320
321 for( const std::shared_ptr<CN_ANCHOR>& n : m_nodes )
322 m_triangulator->AddNode( n );
323
324 std::vector<CN_EDGE> triangEdges;
325 triangEdges.reserve( m_nodes.size() + m_boardEdges.size() );
326
327#ifdef PROFILE
328 PROF_TIMER cnt( "triangulate" );
329#endif
330 m_triangulator->Triangulate( triangEdges );
331#ifdef PROFILE
332 cnt.Show();
333#endif
334
335 for( const CN_EDGE& e : m_boardEdges )
336 triangEdges.emplace_back( e );
337
338 std::sort( triangEdges.begin(), triangEdges.end() );
339
340// Get the minimal spanning tree
341#ifdef PROFILE
342 PROF_TIMER cnt2( "mst" );
343#endif
344 kruskalMST( triangEdges );
345#ifdef PROFILE
346 cnt2.Show();
347#endif
348}
349
350
352{
353 auto optimizeZoneAnchor =
354 [&]( const VECTOR2I& aPos, const LSET& aLayerSet,
355 const std::shared_ptr<const CN_ANCHOR>& aAnchor,
356 const std::function<void( std::shared_ptr<const CN_ANCHOR> )>& setOptimizedTo )
357 {
358 SEG::ecoord closest_dist_sq = ( aAnchor->Pos() - aPos ).SquaredEuclideanNorm();
359 VECTOR2I closest_pt;
360 CN_ITEM* closest_item = nullptr;
361
362 for( CN_ITEM* item : aAnchor->Item()->ConnectedItems() )
363 {
364 // Don't consider shorted items
365 if( aAnchor->Item()->Net() != item->Net() )
366 continue;
367
368 CN_ZONE_LAYER* zoneLayer = dynamic_cast<CN_ZONE_LAYER*>( item );
369
370 if( zoneLayer && aLayerSet.test( zoneLayer->GetBoardLayer() ) )
371 {
372 const std::vector<VECTOR2I>& pts = zoneLayer->GetOutline().CPoints();
373
374 for( const VECTOR2I& pt : pts )
375 {
376 SEG::ecoord dist_sq = ( pt - aPos ).SquaredEuclideanNorm();
377
378 if( dist_sq < closest_dist_sq )
379 {
380 closest_pt = pt;
381 closest_item = zoneLayer;
382 closest_dist_sq = dist_sq;
383 }
384 }
385 }
386 }
387
388 if( closest_item )
389 setOptimizedTo( std::make_shared<CN_ANCHOR>( closest_pt, closest_item ) );
390 };
391
392 auto optimizeZoneToZoneAnchors =
393 [&]( const std::shared_ptr<const CN_ANCHOR>& a,
394 const std::shared_ptr<const CN_ANCHOR>& b,
395 const std::function<void( const std::shared_ptr<const CN_ANCHOR>& )>&
396 setOptimizedATo,
397 const std::function<void( const std::shared_ptr<const CN_ANCHOR>& )>&
398 setOptimizedBTo )
399 {
400 struct CENTER
401 {
402 VECTOR2I pt;
403 bool valid = false;
404 };
405
406 struct DIST_PAIR
407 {
408 DIST_PAIR( int64_t aDistSq, size_t aIdA, size_t aIdB )
409 : dist_sq( aDistSq ), idA( aIdA ), idB( aIdB )
410 {}
411
412 int64_t dist_sq;
413 size_t idA;
414 size_t idB;
415 };
416
417 const std::vector<CN_ITEM*>& connectedItemsA = a->Item()->ConnectedItems();
418 const std::vector<CN_ITEM*>& connectedItemsB = b->Item()->ConnectedItems();
419
420 std::vector<CENTER> centersA( connectedItemsA.size() );
421 std::vector<CENTER> centersB( connectedItemsB.size() );
422
423 for( size_t i = 0; i < connectedItemsA.size(); i++ )
424 {
425 CN_ITEM* itemA = connectedItemsA[i];
426 CN_ZONE_LAYER* zoneLayerA = dynamic_cast<CN_ZONE_LAYER*>( itemA );
427
428 if( !zoneLayerA )
429 continue;
430
431 const SHAPE_LINE_CHAIN& shapeA = zoneLayerA->GetOutline();
432 centersA[i].pt = shapeA.BBox().GetCenter();
433 centersA[i].valid = true;
434 }
435
436 for( size_t i = 0; i < connectedItemsB.size(); i++ )
437 {
438 CN_ITEM* itemB = connectedItemsB[i];
439 CN_ZONE_LAYER* zoneLayerB = dynamic_cast<CN_ZONE_LAYER*>( itemB );
440
441 if( !zoneLayerB )
442 continue;
443
444 const SHAPE_LINE_CHAIN& shapeB = zoneLayerB->GetOutline();
445 centersB[i].pt = shapeB.BBox().GetCenter();
446 centersB[i].valid = true;
447 }
448
449 std::vector<DIST_PAIR> pairsToTest;
450
451 for( size_t ia = 0; ia < centersA.size(); ia++ )
452 {
453 for( size_t ib = 0; ib < centersB.size(); ib++ )
454 {
455 const CENTER& ca = centersA[ia];
456 const CENTER& cb = centersB[ib];
457
458 if( !ca.valid || !cb.valid )
459 continue;
460
461 VECTOR2L pA( ca.pt );
462 VECTOR2L pB( cb.pt );
463
464 int64_t dist_sq = ( pB - pA ).SquaredEuclideanNorm();
465 pairsToTest.emplace_back( dist_sq, ia, ib );
466 }
467 }
468
469 std::sort( pairsToTest.begin(), pairsToTest.end(),
470 []( const DIST_PAIR& dp_a, const DIST_PAIR& dp_b )
471 {
472 return dp_a.dist_sq < dp_b.dist_sq;
473 } );
474
475 const int c_polyPairsLimit = 3;
476
477 for( size_t i = 0; i < pairsToTest.size() && i < c_polyPairsLimit; i++ )
478 {
479 const DIST_PAIR& pair = pairsToTest[i];
480
481 CN_ZONE_LAYER* zoneLayerA = static_cast<CN_ZONE_LAYER*>( connectedItemsA[pair.idA] );
482 CN_ZONE_LAYER* zoneLayerB = static_cast<CN_ZONE_LAYER*>( connectedItemsB[pair.idB] );
483
484 if( zoneLayerA == zoneLayerB )
485 continue;
486
487 const SHAPE_LINE_CHAIN& shapeA = zoneLayerA->GetOutline();
488 const SHAPE_LINE_CHAIN& shapeB = zoneLayerB->GetOutline();
489
490 VECTOR2I ptA;
491 VECTOR2I ptB;
492
493 if( shapeA.ClosestSegmentsFast( shapeB, ptA, ptB ) )
494 {
495 setOptimizedATo( std::make_shared<CN_ANCHOR>( ptA, zoneLayerA ) );
496 setOptimizedBTo( std::make_shared<CN_ANCHOR>( ptB, zoneLayerB ) );
497 }
498 }
499 };
500
501 for( CN_EDGE& edge : m_rnEdges )
502 {
503 const std::shared_ptr<const CN_ANCHOR>& source = edge.GetSourceNode();
504 const std::shared_ptr<const CN_ANCHOR>& target = edge.GetTargetNode();
505
506 wxCHECK2( source && !source->Dirty() && target && !target->Dirty(), continue );
507
508 if( source->ConnectedItemsCount() == 0 )
509 {
510 optimizeZoneAnchor( source->Pos(), source->Parent()->GetLayerSet(), target,
511 [&]( const std::shared_ptr<const CN_ANCHOR>& optimized )
512 {
513 edge.SetTargetNode( optimized );
514 } );
515 }
516 else if( target->ConnectedItemsCount() == 0 )
517 {
518 optimizeZoneAnchor( target->Pos(), target->Parent()->GetLayerSet(), source,
519 [&]( const std::shared_ptr<const CN_ANCHOR>& optimized )
520 {
521 edge.SetSourceNode( optimized );
522 } );
523 }
524 else
525 {
526 optimizeZoneToZoneAnchors( source, target,
527 [&]( const std::shared_ptr<const CN_ANCHOR>& optimized )
528 {
529 edge.SetSourceNode( optimized );
530 },
531 [&]( const std::shared_ptr<const CN_ANCHOR>& optimized )
532 {
533 edge.SetTargetNode( optimized );
534 } );
535 }
536 }
537}
538
539
541{
542 compute();
543
544 m_dirty = false;
545}
546
547
549{
550 for( CN_EDGE& edge : m_rnEdges )
551 edge.RemoveInvalidRefs();
552
553 for( CN_EDGE& edge : m_boardEdges )
554 edge.RemoveInvalidRefs();
555}
556
557
559{
560 m_rnEdges.clear();
561 m_boardEdges.clear();
562 m_nodes.clear();
563
564 m_dirty = true;
565}
566
567
568void RN_NET::AddCluster( std::shared_ptr<CN_CLUSTER> aCluster )
569{
570 std::shared_ptr<CN_ANCHOR> firstAnchor;
571
572 for( CN_ITEM* item : *aCluster )
573 {
574 std::vector<std::shared_ptr<CN_ANCHOR>>& anchors = item->Anchors();
575 unsigned int nAnchors = dynamic_cast<CN_ZONE_LAYER*>( item ) ? 1 : anchors.size();
576
577 if( nAnchors > anchors.size() )
578 nAnchors = anchors.size();
579
580 for( unsigned int i = 0; i < nAnchors; i++ )
581 {
582 anchors[i]->SetCluster( aCluster );
583 m_nodes.insert( anchors[i] );
584
585 if( firstAnchor )
586 {
587 if( firstAnchor != anchors[i] )
588 m_boardEdges.emplace_back( firstAnchor, anchors[i], 0 );
589 }
590 else
591 {
592 firstAnchor = anchors[i];
593 }
594 }
595 }
596}
597
598
599bool RN_NET::NearestBicoloredPair( RN_NET* aOtherNet, VECTOR2I& aPos1, VECTOR2I& aPos2 ) const
600{
601 bool rv = false;
602
604
605 auto verify =
606 [&]( const std::shared_ptr<CN_ANCHOR>& aTestNode1,
607 const std::shared_ptr<CN_ANCHOR>& aTestNode2 )
608 {
609 VECTOR2I diff = aTestNode1->Pos() - aTestNode2->Pos();
610 SEG::ecoord dist_sq = diff.SquaredEuclideanNorm();
611
612 if( dist_sq < distMax_sq )
613 {
614 rv = true;
615 distMax_sq = dist_sq;
616 aPos1 = aTestNode1->Pos();
617 aPos2 = aTestNode2->Pos();
618 }
619 };
620
624 for( const std::shared_ptr<CN_ANCHOR>& nodeA : aOtherNet->m_nodes )
625 {
626
627 if( nodeA->GetNoLine() )
628 continue;
629
633 auto fwd_it = m_nodes.lower_bound( nodeA );
634 auto rev_it = std::make_reverse_iterator( fwd_it );
635
636 for( ; fwd_it != m_nodes.end(); ++fwd_it )
637 {
638 const std::shared_ptr<CN_ANCHOR>& nodeB = *fwd_it;
639
640 if( nodeB->GetNoLine() )
641 continue;
642
643 SEG::ecoord distX_sq = SEG::Square( nodeA->Pos().x - nodeB->Pos().x );
644
647 if( distX_sq > distMax_sq )
648 break;
649
650 verify( nodeA, nodeB );
651 }
652
654 for( ; rev_it != m_nodes.rend(); ++rev_it )
655 {
656 const std::shared_ptr<CN_ANCHOR>& nodeB = *rev_it;
657
658 if( nodeB->GetNoLine() )
659 continue;
660
661 SEG::ecoord distX_sq = SEG::Square( nodeA->Pos().x - nodeB->Pos().x );
662
663 if( distX_sq > distMax_sq )
664 break;
665
666 verify( nodeA, nodeB );
667 }
668 }
669
670 return rv;
671}
672
constexpr const Vec GetCenter() const
Definition: box2.h:230
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
PCB_LAYER_ID GetBoardLayer() const
When using CN_ITEM layers to compare against board items, use this function which correctly remaps th...
const SHAPE_LINE_CHAIN & GetOutline() const
LSET is a set of PCB_LAYER_IDs.
Definition: lset.h:36
A small class to help profiling.
Definition: profile.h:49
void Show(std::ostream &aStream=std::cerr)
Print the elapsed time (in a suitable unit) to a stream.
Definition: profile.h:105
void AddNode(const std::shared_ptr< CN_ANCHOR > &aNode)
std::multiset< std::shared_ptr< CN_ANCHOR >, CN_PTR_CMP > m_allNodes
bool areNodesColinear(const std::vector< std::shared_ptr< CN_ANCHOR > > &aNodes) const
void Triangulate(std::vector< CN_EDGE > &mstEdges)
Describe ratsnest for a single net.
Definition: ratsnest_data.h:63
std::shared_ptr< TRIANGULATOR_STATE > m_triangulator
std::multiset< std::shared_ptr< CN_ANCHOR >, CN_PTR_CMP > m_nodes
< Vector of nodes
void RemoveInvalidRefs()
void kruskalMST(const std::vector< CN_EDGE > &aEdges)
void UpdateNet()
Recompute ratsnest for a net.
std::vector< CN_EDGE > m_rnEdges
Flag indicating necessity of recalculation of ratsnest for a net.
void OptimizeRNEdges()
Find optimal ends of RNEdges.
bool NearestBicoloredPair(RN_NET *aOtherNet, VECTOR2I &aPos1, VECTOR2I &aPos2) const
bool m_dirty
std::vector< CN_EDGE > m_boardEdges
Vector of edges that makes ratsnest for a given net.
void compute()
< Recompute ratsnest from scratch.
void Clear()
void AddCluster(std::shared_ptr< CN_CLUSTER > aCluster)
VECTOR2I::extended_type ecoord
Definition: seg.h:44
static SEG::ecoord Square(int a)
Definition: seg.h:123
Represent a polyline containing arcs as well as line segments: A chain of connected line and/or arc s...
bool ClosestSegmentsFast(const SHAPE_LINE_CHAIN &aOther, VECTOR2I &aPt0, VECTOR2I &aPt1) const
Finds closest points between segments of this and the other line chain.
const std::vector< VECTOR2I > & CPoints() const
const BOX2I BBox(int aClearance=0) const override
Compute a bounding box of the shape, with a margin of aClearance a collision.
constexpr extended_type Cross(const VECTOR2< T > &aVector) const
Compute cross product of self with aVector.
Definition: vector2d.h:542
constexpr extended_type SquaredEuclideanNorm() const
Compute the squared euclidean norm of the vector, which is defined as (x ** 2 + y ** 2).
Definition: vector2d.h:307
static constexpr extended_type ECOORD_MAX
Definition: vector2d.h:76
disjoint_set(size_t size)
std::vector< int > m_data
std::vector< int > m_depth
int find(int aVal)
bool unite(int aVal1, int aVal2)
Class that computes missing connections on a PCB.
VECTOR3I v1(5, 5, 5)