KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_pin_text_overlap.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
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
25
27
28#include <sch_pin.h>
29#include <lib_symbol.h>
30#include <pin_layout_cache.h>
31#include <transform.h>
32#include <sch_io/sch_io_mgr.h>
33
34#include <wx/log.h>
35#include <boost/test/unit_test.hpp>
36
37BOOST_AUTO_TEST_SUITE( PinTextOverlap )
38
39
45{
46 auto symbol = std::make_unique<LIB_SYMBOL>( wxT( "TestAdjacentPins" ) );
47
48 // Set pin name offset to 0 so names are positioned outside (like numbers)
49 // This is the configuration that triggers the overlap issue
50 symbol->SetPinNameOffset( 0 );
51 symbol->SetShowPinNames( true );
52 symbol->SetShowPinNumbers( true );
53
54 // Pin spacing of 5.08mm (200 mils) - typical for connector symbols
55 const int pinSpacing = schIUScale.MilsToIU( 200 );
56 const int pinLength = schIUScale.MilsToIU( 100 );
57
58 // Create 4 adjacent pins pointing left (like connector J1 pins)
59 // These pins will have names and numbers that could potentially overlap
60 for( int i = 0; i < 4; i++ )
61 {
62 auto pin = std::make_unique<SCH_PIN>( symbol.get() );
63 pin->SetPosition( VECTOR2I( 0, i * pinSpacing ) );
64 pin->SetOrientation( PIN_ORIENTATION::PIN_LEFT );
65 pin->SetLength( pinLength );
66
67 // Use realistic names similar to the issue report
68 wxString name = wxString::Format( wxT( "GPIO%d" ), 28 + i );
69 wxString number = wxString::Format( wxT( "J1.%d" ), i + 1 );
70
71 pin->SetName( name );
72 pin->SetNumber( number );
74 pin->SetUnit( 1 );
75
76 symbol->AddDrawItem( pin.release() );
77 }
78
79 return symbol;
80}
81
82
88static int getPerpendicularDistance( const SCH_PIN* pin, const VECTOR2I& textPos,
89 const TRANSFORM& transform )
90{
91 VECTOR2I pinPos = pin->GetPosition();
92 PIN_ORIENTATION orient = pin->PinDrawOrient( transform );
93
94 if( orient == PIN_ORIENTATION::PIN_LEFT || orient == PIN_ORIENTATION::PIN_RIGHT )
95 {
96 // Horizontal pin - perpendicular distance is in Y
97 return std::abs( textPos.y - pinPos.y );
98 }
99 else
100 {
101 // Vertical pin - perpendicular distance is in X
102 return std::abs( textPos.x - pinPos.x );
103 }
104}
105
106
114BOOST_AUTO_TEST_CASE( AdjacentPinTextNoOverlap )
115{
116 // Create test symbol with adjacent pins
117 std::unique_ptr<LIB_SYMBOL> symbol = createAdjacentPinsSymbol();
118 BOOST_REQUIRE( symbol );
119
120 // Get the pins
121 std::vector<SCH_PIN*> pins;
122
123 for( SCH_ITEM& item : symbol->GetDrawItems() )
124 {
125 if( item.Type() == SCH_PIN_T )
126 pins.push_back( static_cast<SCH_PIN*>( &item ) );
127 }
128
129 BOOST_REQUIRE_GE( pins.size(), 2 );
130
131 // Test at 0° (identity transform) and 90° rotation
132 std::vector<TRANSFORM> rotations = {
133 TRANSFORM( 1, 0, 0, 1 ), // 0° (identity) - pins are horizontal
134 TRANSFORM( 0, -1, 1, 0 ), // 90° CCW - pins become vertical
135 };
136
137 std::vector<wxString> rotationNames = { wxT( "0°" ), wxT( "90°" ) };
138
139 for( size_t r = 0; r < rotations.size(); r++ )
140 {
141 const TRANSFORM& transform = rotations[r];
142 const wxString& rotName = rotationNames[r];
143
144 // Set global transform for this test
145 TRANSFORM oldTransform = DefaultTransform;
146 DefaultTransform = transform;
147
148 // Collect text bounding boxes for all pins
149 struct PinTextBoxes
150 {
151 wxString pinNumber;
152 BOX2I nameBBox;
153 BOX2I numberBBox;
154 bool hasName;
155 bool hasNumber;
156 };
157
158 std::vector<PinTextBoxes> pinBoxes;
159
160 for( SCH_PIN* pin : pins )
161 {
162 PinTextBoxes boxes;
163 boxes.pinNumber = pin->GetNumber();
164 boxes.hasName = false;
165 boxes.hasNumber = false;
166
167 PIN_LAYOUT_CACHE cache( *pin );
168
169 // The painter controls font rotation, so we need to transform here as well before
170 // comparison
171 const VECTOR2I shift = transform.TransformCoordinate( pin->GetPosition() ) - pin->GetPosition();
172
173 if( OPT_BOX2I nameBox = cache.GetPinNameBBox() )
174 {
175 boxes.nameBBox = *nameBox;
176 boxes.nameBBox.Move( shift );
177 boxes.hasName = true;
178 }
179
180 if( OPT_BOX2I numberBox = cache.GetPinNumberBBox() )
181 {
182 boxes.numberBBox = *numberBox;
183 boxes.numberBBox.Move( shift );
184 boxes.hasNumber = true;
185 }
186
187 pinBoxes.push_back( boxes );
188 }
189
190 BOOST_REQUIRE_GE( pinBoxes.size(), 2 );
191
192 // Control: the separation above is only meaningful if these boxes can collide at all, so
193 // slide the first name box onto its neighbour and require the overlap to be seen
194 {
195 BOX2I collided = pinBoxes[0].nameBBox;
196 collided.Move( pinBoxes[1].nameBBox.GetCenter() - pinBoxes[0].nameBBox.GetCenter() );
197
198 BOOST_CHECK_MESSAGE( collided.Intersects( pinBoxes[1].nameBBox ),
199 "At " << rotName << ": coincident name boxes were not reported as overlapping" );
200 }
201
202 // Check that names and numbers of different pins don't overlap
203 for( size_t i = 0; i < pinBoxes.size(); i++ )
204 {
205 for( size_t j = i + 1; j < pinBoxes.size(); j++ )
206 {
207 const PinTextBoxes& pinA = pinBoxes[i];
208 const PinTextBoxes& pinB = pinBoxes[j];
209
210 // Check name-to-name overlap
211 if( pinA.hasName && pinB.hasName )
212 {
213 bool namesOverlap = pinA.nameBBox.Intersects( pinB.nameBBox );
214 BOOST_CHECK_MESSAGE(
215 !namesOverlap,
216 "At " << rotName << ": Pin " << pinA.pinNumber << " name overlaps with pin "
217 << pinB.pinNumber << " name. "
218 << "BBoxA=(" << pinA.nameBBox.GetLeft() << ","
219 << pinA.nameBBox.GetTop() << ")-(" << pinA.nameBBox.GetRight()
220 << "," << pinA.nameBBox.GetBottom() << ") "
221 << "BBoxB=(" << pinB.nameBBox.GetLeft() << ","
222 << pinB.nameBBox.GetTop() << ")-(" << pinB.nameBBox.GetRight()
223 << "," << pinB.nameBBox.GetBottom() << ")" );
224 }
225
226 // Check number-to-number overlap
227 if( pinA.hasNumber && pinB.hasNumber )
228 {
229 bool numbersOverlap = pinA.numberBBox.Intersects( pinB.numberBBox );
230 BOOST_CHECK_MESSAGE(
231 !numbersOverlap,
232 "At " << rotName << ": Pin " << pinA.pinNumber
233 << " number overlaps with pin " << pinB.pinNumber << " number. "
234 << "BBoxA=(" << pinA.numberBBox.GetLeft() << ","
235 << pinA.numberBBox.GetTop() << ")-("
236 << pinA.numberBBox.GetRight() << ","
237 << pinA.numberBBox.GetBottom() << ") "
238 << "BBoxB=(" << pinB.numberBBox.GetLeft() << ","
239 << pinB.numberBBox.GetTop() << ")-("
240 << pinB.numberBBox.GetRight() << ","
241 << pinB.numberBBox.GetBottom() << ")" );
242 }
243
244 // Check name-to-number overlap between pins
245 if( pinA.hasName && pinB.hasNumber )
246 {
247 bool overlap = pinA.nameBBox.Intersects( pinB.numberBBox );
248 BOOST_CHECK_MESSAGE(
249 !overlap,
250 "At " << rotName << ": Pin " << pinA.pinNumber
251 << " name overlaps with pin " << pinB.pinNumber << " number." );
252 }
253
254 if( pinA.hasNumber && pinB.hasName )
255 {
256 bool overlap = pinA.numberBBox.Intersects( pinB.nameBBox );
257 BOOST_CHECK_MESSAGE(
258 !overlap,
259 "At " << rotName << ": Pin " << pinA.pinNumber
260 << " number overlaps with pin " << pinB.pinNumber << " name." );
261 }
262 }
263 }
264
265 // Restore original transform
266 DefaultTransform = oldTransform;
267 }
268}
269
270
277BOOST_AUTO_TEST_CASE( PerpendicularDistanceConsistentAcrossRotations )
278{
279 // Create test symbol with adjacent pins
280 std::unique_ptr<LIB_SYMBOL> symbol = createAdjacentPinsSymbol();
281 BOOST_REQUIRE( symbol );
282
283 // Get the first pin for testing
284 SCH_PIN* testPin = nullptr;
285
286 for( SCH_ITEM& item : symbol->GetDrawItems() )
287 {
288 if( item.Type() == SCH_PIN_T )
289 {
290 testPin = static_cast<SCH_PIN*>( &item );
291 break;
292 }
293 }
294
295 BOOST_REQUIRE( testPin );
296
297 // Transforms for 0° and 90°
298 TRANSFORM transform0( 1, 0, 0, 1 ); // 0° (identity)
299 TRANSFORM transform90( 0, -1, 1, 0 ); // 90° CCW
300
301 // Measure perpendicular distance at 0°
302 TRANSFORM oldTransform = DefaultTransform;
303 DefaultTransform = transform0;
304
305 PIN_LAYOUT_CACHE cache0( *testPin );
306 std::optional<PIN_LAYOUT_CACHE::TEXT_INFO> nameInfo0 = cache0.GetPinNameInfo( 0 );
307 std::optional<PIN_LAYOUT_CACHE::TEXT_INFO> numberInfo0 = cache0.GetPinNumberInfo( 0 );
308
309 int nameDistance0 = 0;
310 int numberDistance0 = 0;
311
312 if( nameInfo0.has_value() )
313 nameDistance0 = getPerpendicularDistance( testPin, nameInfo0->m_TextPosition, transform0 );
314
315 if( numberInfo0.has_value() )
316 numberDistance0 = getPerpendicularDistance( testPin, numberInfo0->m_TextPosition, transform0 );
317
318 // Measure perpendicular distance at 90°
319 DefaultTransform = transform90;
320
321 PIN_LAYOUT_CACHE cache90( *testPin );
322 std::optional<PIN_LAYOUT_CACHE::TEXT_INFO> nameInfo90 = cache90.GetPinNameInfo( 0 );
323 std::optional<PIN_LAYOUT_CACHE::TEXT_INFO> numberInfo90 = cache90.GetPinNumberInfo( 0 );
324
325 int nameDistance90 = 0;
326 int numberDistance90 = 0;
327
328 if( nameInfo90.has_value() )
329 nameDistance90 = getPerpendicularDistance( testPin, nameInfo90->m_TextPosition, transform90 );
330
331 if( numberInfo90.has_value() )
332 numberDistance90 = getPerpendicularDistance( testPin, numberInfo90->m_TextPosition, transform90 );
333
334 // Restore original transform
335 DefaultTransform = oldTransform;
336
337 // Allow some tolerance for rounding differences
338 const int tolerance = schIUScale.MilsToIU( 5 ); // 5 mils tolerance
339
340 // Check that name distances are consistent
341 if( nameInfo0.has_value() && nameInfo90.has_value() )
342 {
343 int nameDiff = std::abs( nameDistance0 - nameDistance90 );
344 BOOST_CHECK_MESSAGE(
345 nameDiff <= tolerance,
346 "Pin name perpendicular distance changed across rotation. "
347 << "At 0°: " << nameDistance0 << ", at 90°: " << nameDistance90
348 << ", difference: " << nameDiff << " (tolerance: " << tolerance << ")" );
349 }
350
351 // Check that number distances are consistent
352 if( numberInfo0.has_value() && numberInfo90.has_value() )
353 {
354 int numberDiff = std::abs( numberDistance0 - numberDistance90 );
355 BOOST_CHECK_MESSAGE(
356 numberDiff <= tolerance,
357 "Pin number perpendicular distance changed across rotation. "
358 << "At 0°: " << numberDistance0 << ", at 90°: " << numberDistance90
359 << ", difference: " << numberDiff << " (tolerance: " << tolerance << ")" );
360 }
361}
362
363
370BOOST_AUTO_TEST_CASE( NameAndNumberOnOppositeSides )
371{
372 // Create test symbol with adjacent pins
373 std::unique_ptr<LIB_SYMBOL> symbol = createAdjacentPinsSymbol();
374 BOOST_REQUIRE( symbol );
375
376 // Get the first pin for testing
377 SCH_PIN* testPin = nullptr;
378
379 for( SCH_ITEM& item : symbol->GetDrawItems() )
380 {
381 if( item.Type() == SCH_PIN_T )
382 {
383 testPin = static_cast<SCH_PIN*>( &item );
384 break;
385 }
386 }
387
388 BOOST_REQUIRE( testPin );
389
390 // Test at both 0° and 90°
391 std::vector<TRANSFORM> rotations = { TRANSFORM( 1, 0, 0, 1 ), TRANSFORM( 0, -1, 1, 0 ) };
392 std::vector<wxString> rotationNames = { wxT( "0°" ), wxT( "90°" ) };
393
394 for( size_t r = 0; r < rotations.size(); r++ )
395 {
396 const TRANSFORM& transform = rotations[r];
397 const wxString& rotName = rotationNames[r];
398
399 TRANSFORM oldTransform = DefaultTransform;
400 DefaultTransform = transform;
401
402 PIN_LAYOUT_CACHE cache( *testPin );
403 std::optional<PIN_LAYOUT_CACHE::TEXT_INFO> nameInfo = cache.GetPinNameInfo( 0 );
404 std::optional<PIN_LAYOUT_CACHE::TEXT_INFO> numberInfo = cache.GetPinNumberInfo( 0 );
405
406 BOOST_REQUIRE_MESSAGE( nameInfo.has_value(), "Pin should have name at " << rotName );
407 BOOST_REQUIRE_MESSAGE( numberInfo.has_value(), "Pin should have number at " << rotName );
408
409 VECTOR2I pinPos = testPin->GetPosition();
410 PIN_ORIENTATION orient = testPin->PinDrawOrient( transform );
411
412 if( orient == PIN_ORIENTATION::PIN_LEFT || orient == PIN_ORIENTATION::PIN_RIGHT )
413 {
414 // Horizontal pin - name and number should be on opposite Y sides
415 bool nameAbove = nameInfo->m_TextPosition.y < pinPos.y;
416 bool numberAbove = numberInfo->m_TextPosition.y < pinPos.y;
417
418 BOOST_CHECK_MESSAGE(
419 nameAbove != numberAbove,
420 "At " << rotName << " (horizontal pin): name and number should be on opposite "
421 << "Y sides of pin. Name Y=" << nameInfo->m_TextPosition.y
422 << ", Number Y=" << numberInfo->m_TextPosition.y
423 << ", Pin Y=" << pinPos.y );
424 }
425 else
426 {
427 // Vertical pin - name and number should be on opposite X sides
428 bool nameLeft = nameInfo->m_TextPosition.x < pinPos.x;
429 bool numberLeft = numberInfo->m_TextPosition.x < pinPos.x;
430
431 BOOST_CHECK_MESSAGE(
432 nameLeft != numberLeft,
433 "At " << rotName << " (vertical pin): name and number should be on opposite "
434 << "X sides of pin. Name X=" << nameInfo->m_TextPosition.x
435 << ", Number X=" << numberInfo->m_TextPosition.x
436 << ", Pin X=" << pinPos.x );
437 }
438
439 DefaultTransform = oldTransform;
440 }
441}
442
443
const char * name
constexpr EDA_IU_SCALE schIUScale
Definition base_units.h:123
BOX2< VECTOR2I > BOX2I
Definition box2.h:918
std::optional< BOX2I > OPT_BOX2I
Definition box2.h:922
constexpr void Move(const Vec &aMoveVector)
Move the rectangle by the aMoveVector.
Definition box2.h:134
constexpr bool Intersects(const BOX2< Vec > &aRect) const
Definition box2.h:307
Define a library symbol object.
Definition lib_symbol.h:114
A pin layout helper is a class that manages the layout of the parts of a pin on a schematic symbol:
OPT_BOX2I GetPinNumberBBox()
Get the bounding box of the pin number, if there is one.
std::optional< TEXT_INFO > GetPinNameInfo(int aShadowWidth)
Get the text info for the pin name.
std::optional< TEXT_INFO > GetPinNumberInfo(int aShadowWidth)
OPT_BOX2I GetPinNameBBox()
Get the bounding box of the pin name, if there is one.
Base class for any item which can be embedded within the SCHEMATIC container class,...
Definition sch_item.h:162
PIN_ORIENTATION PinDrawOrient(const TRANSFORM &aTransform) const
Return the pin real orientation (PIN_UP, PIN_DOWN, PIN_RIGHT, PIN_LEFT), according to its orientation...
Definition sch_pin.cpp:1399
VECTOR2I GetPosition() const override
Definition sch_pin.cpp:350
for transforming drawing coordinates for a wxDC device context.
Definition transform.h:42
VECTOR2I TransformCoordinate(const VECTOR2I &aPoint) const
Calculate a new coordinate according to the mirror/rotation transform.
Definition transform.cpp:40
STL class.
TRANSFORM DefaultTransform
Definition transform.cpp:28
STL namespace.
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:400
@ PT_BIDI
input or output (like port for a microprocessor)
Definition pin_type.h:35
PIN_ORIENTATION
The symbol library pin object orientations.
Definition pin_type.h:101
@ PIN_RIGHT
The pin extends rightwards from the connection point.
Definition pin_type.h:107
@ PIN_LEFT
The pin extends leftwards from the connection point: Probably on the right side of the symbol.
Definition pin_type.h:114
BOOST_AUTO_TEST_SUITE(CadstarPartParser)
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
BOOST_AUTO_TEST_SUITE_END()
KIBIS_PIN * pin
KIBIS_PIN * pinA
static std::unique_ptr< LIB_SYMBOL > createAdjacentPinsSymbol()
Create a test symbol with adjacent pins for testing overlap.
static int getPerpendicularDistance(const SCH_PIN *pin, const VECTOR2I &textPos, const TRANSFORM &transform)
Calculate perpendicular distance from pin line to text center.
BOOST_AUTO_TEST_CASE(AdjacentPinTextNoOverlap)
Test that pin names and numbers don't overlap with adjacent pins at 0° and 90°.
@ SCH_PIN_T
Definition typeinfo.h:150
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683