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
87{
88 // Estimate text dimensions based on character count and font size
89 int textHeight = textInfo.m_TextSize;
90 int textWidth = textInfo.m_Text.Length() * textInfo.m_TextSize * 6 / 10;
91
92 // Handle vertical text - swap width and height
93 if( textInfo.m_Angle == ANGLE_VERTICAL )
94 std::swap( textWidth, textHeight );
95
96 BOX2I bbox;
97 bbox.SetOrigin( textInfo.m_TextPosition.x - textWidth / 2,
98 textInfo.m_TextPosition.y - textHeight / 2 );
99 bbox.SetSize( textWidth, textHeight );
100
101 return bbox;
102}
103
104
110static int getPerpendicularDistance( const SCH_PIN* pin, const VECTOR2I& textPos,
111 const TRANSFORM& transform )
112{
113 VECTOR2I pinPos = pin->GetPosition();
114 PIN_ORIENTATION orient = pin->PinDrawOrient( transform );
115
116 if( orient == PIN_ORIENTATION::PIN_LEFT || orient == PIN_ORIENTATION::PIN_RIGHT )
117 {
118 // Horizontal pin - perpendicular distance is in Y
119 return std::abs( textPos.y - pinPos.y );
120 }
121 else
122 {
123 // Vertical pin - perpendicular distance is in X
124 return std::abs( textPos.x - pinPos.x );
125 }
126}
127
128
136BOOST_AUTO_TEST_CASE( AdjacentPinTextNoOverlap )
137{
138 // Create test symbol with adjacent pins
139 std::unique_ptr<LIB_SYMBOL> symbol = createAdjacentPinsSymbol();
140 BOOST_REQUIRE( symbol );
141
142 // Get the pins
143 std::vector<SCH_PIN*> pins;
144
145 for( SCH_ITEM& item : symbol->GetDrawItems() )
146 {
147 if( item.Type() == SCH_PIN_T )
148 pins.push_back( static_cast<SCH_PIN*>( &item ) );
149 }
150
151 BOOST_REQUIRE_GE( pins.size(), 2 );
152
153 // Test at 0° (identity transform) and 90° rotation
154 std::vector<TRANSFORM> rotations = {
155 TRANSFORM( 1, 0, 0, 1 ), // 0° (identity) - pins are horizontal
156 TRANSFORM( 0, -1, 1, 0 ), // 90° CCW - pins become vertical
157 };
158
159 std::vector<wxString> rotationNames = { wxT( "0°" ), wxT( "90°" ) };
160
161 for( size_t r = 0; r < rotations.size(); r++ )
162 {
163 const TRANSFORM& transform = rotations[r];
164 const wxString& rotName = rotationNames[r];
165
166 // Set global transform for this test
167 TRANSFORM oldTransform = DefaultTransform;
168 DefaultTransform = transform;
169
170 // Collect text bounding boxes for all pins
171 struct PinTextBoxes
172 {
173 wxString pinNumber;
174 BOX2I nameBBox;
175 BOX2I numberBBox;
176 bool hasName;
177 bool hasNumber;
178 };
179
180 std::vector<PinTextBoxes> pinBoxes;
181
182 for( SCH_PIN* pin : pins )
183 {
184 PinTextBoxes boxes;
185 boxes.pinNumber = pin->GetNumber();
186 boxes.hasName = false;
187 boxes.hasNumber = false;
188
189 PIN_LAYOUT_CACHE cache( *pin );
190
191 // Get name bounding box
192 std::optional<PIN_LAYOUT_CACHE::TEXT_INFO> nameInfo = cache.GetPinNameInfo( 0 );
193
194 if( nameInfo.has_value() && !nameInfo->m_Text.IsEmpty() )
195 {
196 boxes.nameBBox = getTextBoundingBox( *nameInfo );
197 boxes.hasName = true;
198 }
199
200 // Get number bounding box
201 std::optional<PIN_LAYOUT_CACHE::TEXT_INFO> numberInfo = cache.GetPinNumberInfo( 0 );
202
203 if( numberInfo.has_value() && !numberInfo->m_Text.IsEmpty() )
204 {
205 boxes.numberBBox = getTextBoundingBox( *numberInfo );
206 boxes.hasNumber = true;
207 }
208
209 pinBoxes.push_back( boxes );
210 }
211
212 // Check that names and numbers of different pins don't overlap
213 for( size_t i = 0; i < pinBoxes.size(); i++ )
214 {
215 for( size_t j = i + 1; j < pinBoxes.size(); j++ )
216 {
217 const PinTextBoxes& pinA = pinBoxes[i];
218 const PinTextBoxes& pinB = pinBoxes[j];
219
220 // Check name-to-name overlap
221 if( pinA.hasName && pinB.hasName )
222 {
223 bool namesOverlap = pinA.nameBBox.Intersects( pinB.nameBBox );
225 !namesOverlap,
226 "At " << rotName << ": Pin " << pinA.pinNumber << " name overlaps with pin "
227 << pinB.pinNumber << " name. "
228 << "BBoxA=(" << pinA.nameBBox.GetLeft() << ","
229 << pinA.nameBBox.GetTop() << ")-(" << pinA.nameBBox.GetRight()
230 << "," << pinA.nameBBox.GetBottom() << ") "
231 << "BBoxB=(" << pinB.nameBBox.GetLeft() << ","
232 << pinB.nameBBox.GetTop() << ")-(" << pinB.nameBBox.GetRight()
233 << "," << pinB.nameBBox.GetBottom() << ")" );
234 }
235
236 // Check number-to-number overlap
237 if( pinA.hasNumber && pinB.hasNumber )
238 {
239 bool numbersOverlap = pinA.numberBBox.Intersects( pinB.numberBBox );
241 !numbersOverlap,
242 "At " << rotName << ": Pin " << pinA.pinNumber
243 << " number overlaps with pin " << pinB.pinNumber << " number. "
244 << "BBoxA=(" << pinA.numberBBox.GetLeft() << ","
245 << pinA.numberBBox.GetTop() << ")-("
246 << pinA.numberBBox.GetRight() << ","
247 << pinA.numberBBox.GetBottom() << ") "
248 << "BBoxB=(" << pinB.numberBBox.GetLeft() << ","
249 << pinB.numberBBox.GetTop() << ")-("
250 << pinB.numberBBox.GetRight() << ","
251 << pinB.numberBBox.GetBottom() << ")" );
252 }
253
254 // Check name-to-number overlap between pins
255 if( pinA.hasName && pinB.hasNumber )
256 {
257 bool overlap = pinA.nameBBox.Intersects( pinB.numberBBox );
259 !overlap,
260 "At " << rotName << ": Pin " << pinA.pinNumber
261 << " name overlaps with pin " << pinB.pinNumber << " number." );
262 }
263
264 if( pinA.hasNumber && pinB.hasName )
265 {
266 bool overlap = pinA.numberBBox.Intersects( pinB.nameBBox );
268 !overlap,
269 "At " << rotName << ": Pin " << pinA.pinNumber
270 << " number overlaps with pin " << pinB.pinNumber << " name." );
271 }
272 }
273 }
274
275 // Restore original transform
276 DefaultTransform = oldTransform;
277 }
278}
279
280
287BOOST_AUTO_TEST_CASE( PerpendicularDistanceConsistentAcrossRotations )
288{
289 // Create test symbol with adjacent pins
290 std::unique_ptr<LIB_SYMBOL> symbol = createAdjacentPinsSymbol();
291 BOOST_REQUIRE( symbol );
292
293 // Get the first pin for testing
294 SCH_PIN* testPin = nullptr;
295
296 for( SCH_ITEM& item : symbol->GetDrawItems() )
297 {
298 if( item.Type() == SCH_PIN_T )
299 {
300 testPin = static_cast<SCH_PIN*>( &item );
301 break;
302 }
303 }
304
305 BOOST_REQUIRE( testPin );
306
307 // Transforms for 0° and 90°
308 TRANSFORM transform0( 1, 0, 0, 1 ); // 0° (identity)
309 TRANSFORM transform90( 0, -1, 1, 0 ); // 90° CCW
310
311 // Measure perpendicular distance at 0°
312 TRANSFORM oldTransform = DefaultTransform;
313 DefaultTransform = transform0;
314
315 PIN_LAYOUT_CACHE cache0( *testPin );
316 std::optional<PIN_LAYOUT_CACHE::TEXT_INFO> nameInfo0 = cache0.GetPinNameInfo( 0 );
317 std::optional<PIN_LAYOUT_CACHE::TEXT_INFO> numberInfo0 = cache0.GetPinNumberInfo( 0 );
318
319 int nameDistance0 = 0;
320 int numberDistance0 = 0;
321
322 if( nameInfo0.has_value() )
323 nameDistance0 = getPerpendicularDistance( testPin, nameInfo0->m_TextPosition, transform0 );
324
325 if( numberInfo0.has_value() )
326 numberDistance0 = getPerpendicularDistance( testPin, numberInfo0->m_TextPosition, transform0 );
327
328 // Measure perpendicular distance at 90°
329 DefaultTransform = transform90;
330
331 PIN_LAYOUT_CACHE cache90( *testPin );
332 std::optional<PIN_LAYOUT_CACHE::TEXT_INFO> nameInfo90 = cache90.GetPinNameInfo( 0 );
333 std::optional<PIN_LAYOUT_CACHE::TEXT_INFO> numberInfo90 = cache90.GetPinNumberInfo( 0 );
334
335 int nameDistance90 = 0;
336 int numberDistance90 = 0;
337
338 if( nameInfo90.has_value() )
339 nameDistance90 = getPerpendicularDistance( testPin, nameInfo90->m_TextPosition, transform90 );
340
341 if( numberInfo90.has_value() )
342 numberDistance90 = getPerpendicularDistance( testPin, numberInfo90->m_TextPosition, transform90 );
343
344 // Restore original transform
345 DefaultTransform = oldTransform;
346
347 // Allow some tolerance for rounding differences
348 const int tolerance = schIUScale.MilsToIU( 5 ); // 5 mils tolerance
349
350 // Check that name distances are consistent
351 if( nameInfo0.has_value() && nameInfo90.has_value() )
352 {
353 int nameDiff = std::abs( nameDistance0 - nameDistance90 );
355 nameDiff <= tolerance,
356 "Pin name perpendicular distance changed across rotation. "
357 << "At 0°: " << nameDistance0 << ", at 90°: " << nameDistance90
358 << ", difference: " << nameDiff << " (tolerance: " << tolerance << ")" );
359 }
360
361 // Check that number distances are consistent
362 if( numberInfo0.has_value() && numberInfo90.has_value() )
363 {
364 int numberDiff = std::abs( numberDistance0 - numberDistance90 );
366 numberDiff <= tolerance,
367 "Pin number perpendicular distance changed across rotation. "
368 << "At 0°: " << numberDistance0 << ", at 90°: " << numberDistance90
369 << ", difference: " << numberDiff << " (tolerance: " << tolerance << ")" );
370 }
371}
372
373
380BOOST_AUTO_TEST_CASE( NameAndNumberOnOppositeSides )
381{
382 // Create test symbol with adjacent pins
383 std::unique_ptr<LIB_SYMBOL> symbol = createAdjacentPinsSymbol();
384 BOOST_REQUIRE( symbol );
385
386 // Get the first pin for testing
387 SCH_PIN* testPin = nullptr;
388
389 for( SCH_ITEM& item : symbol->GetDrawItems() )
390 {
391 if( item.Type() == SCH_PIN_T )
392 {
393 testPin = static_cast<SCH_PIN*>( &item );
394 break;
395 }
396 }
397
398 BOOST_REQUIRE( testPin );
399
400 // Test at both 0° and 90°
401 std::vector<TRANSFORM> rotations = { TRANSFORM( 1, 0, 0, 1 ), TRANSFORM( 0, -1, 1, 0 ) };
402 std::vector<wxString> rotationNames = { wxT( "0°" ), wxT( "90°" ) };
403
404 for( size_t r = 0; r < rotations.size(); r++ )
405 {
406 const TRANSFORM& transform = rotations[r];
407 const wxString& rotName = rotationNames[r];
408
409 TRANSFORM oldTransform = DefaultTransform;
410 DefaultTransform = transform;
411
412 PIN_LAYOUT_CACHE cache( *testPin );
413 std::optional<PIN_LAYOUT_CACHE::TEXT_INFO> nameInfo = cache.GetPinNameInfo( 0 );
414 std::optional<PIN_LAYOUT_CACHE::TEXT_INFO> numberInfo = cache.GetPinNumberInfo( 0 );
415
416 BOOST_REQUIRE_MESSAGE( nameInfo.has_value(), "Pin should have name at " << rotName );
417 BOOST_REQUIRE_MESSAGE( numberInfo.has_value(), "Pin should have number at " << rotName );
418
419 VECTOR2I pinPos = testPin->GetPosition();
420 PIN_ORIENTATION orient = testPin->PinDrawOrient( transform );
421
422 if( orient == PIN_ORIENTATION::PIN_LEFT || orient == PIN_ORIENTATION::PIN_RIGHT )
423 {
424 // Horizontal pin - name and number should be on opposite Y sides
425 bool nameAbove = nameInfo->m_TextPosition.y < pinPos.y;
426 bool numberAbove = numberInfo->m_TextPosition.y < pinPos.y;
427
429 nameAbove != numberAbove,
430 "At " << rotName << " (horizontal pin): name and number should be on opposite "
431 << "Y sides of pin. Name Y=" << nameInfo->m_TextPosition.y
432 << ", Number Y=" << numberInfo->m_TextPosition.y
433 << ", Pin Y=" << pinPos.y );
434 }
435 else
436 {
437 // Vertical pin - name and number should be on opposite X sides
438 bool nameLeft = nameInfo->m_TextPosition.x < pinPos.x;
439 bool numberLeft = numberInfo->m_TextPosition.x < pinPos.x;
440
442 nameLeft != numberLeft,
443 "At " << rotName << " (vertical pin): name and number should be on opposite "
444 << "X sides of pin. Name X=" << nameInfo->m_TextPosition.x
445 << ", Number X=" << numberInfo->m_TextPosition.x
446 << ", Pin X=" << pinPos.x );
447 }
448
449 DefaultTransform = oldTransform;
450 }
451}
452
453
const char * name
constexpr EDA_IU_SCALE schIUScale
Definition base_units.h:123
BOX2< VECTOR2I > BOX2I
Definition box2.h:918
constexpr void SetOrigin(const Vec &pos)
Definition box2.h:233
constexpr void SetSize(const SizeVec &size)
Definition box2.h:244
Define a library symbol object.
Definition lib_symbol.h:79
A pin layout helper is a class that manages the layout of the parts of a pin on a schematic symbol:
std::optional< TEXT_INFO > GetPinNameInfo(int aShadowWidth)
Get the text info for the pin name.
std::optional< TEXT_INFO > GetPinNumberInfo(int aShadowWidth)
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:1291
VECTOR2I GetPosition() const override
Definition sch_pin.cpp:345
for transforming drawing coordinates for a wxDC device context.
Definition transform.h:42
STL class.
static constexpr EDA_ANGLE ANGLE_VERTICAL
Definition eda_angle.h:408
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 BOX2I getTextBoundingBox(const PIN_LAYOUT_CACHE::TEXT_INFO &textInfo)
Get the bounding box of pin text (name or number) for a given pin.
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°.
BOOST_CHECK_MESSAGE(totalMismatches==0, std::to_string(totalMismatches)+" board(s) with strategy disagreements")
@ SCH_PIN_T
Definition typeinfo.h:150
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683