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, you may find one here:
18 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
19 * or you may search the http://www.gnu.org website for the version 2 license,
20 * or you may write to the Free Software Foundation, Inc.,
21 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
22 */
23
29
31
32#include <sch_pin.h>
33#include <lib_symbol.h>
34#include <pin_layout_cache.h>
35#include <transform.h>
36#include <sch_io/sch_io_mgr.h>
37
38#include <wx/log.h>
39#include <boost/test/unit_test.hpp>
40
41BOOST_AUTO_TEST_SUITE( PinTextOverlap )
42
43
49{
50 auto symbol = std::make_unique<LIB_SYMBOL>( wxT( "TestAdjacentPins" ) );
51
52 // Set pin name offset to 0 so names are positioned outside (like numbers)
53 // This is the configuration that triggers the overlap issue
54 symbol->SetPinNameOffset( 0 );
55 symbol->SetShowPinNames( true );
56 symbol->SetShowPinNumbers( true );
57
58 // Pin spacing of 5.08mm (200 mils) - typical for connector symbols
59 const int pinSpacing = schIUScale.MilsToIU( 200 );
60 const int pinLength = schIUScale.MilsToIU( 100 );
61
62 // Create 4 adjacent pins pointing left (like connector J1 pins)
63 // These pins will have names and numbers that could potentially overlap
64 for( int i = 0; i < 4; i++ )
65 {
66 auto pin = std::make_unique<SCH_PIN>( symbol.get() );
67 pin->SetPosition( VECTOR2I( 0, i * pinSpacing ) );
68 pin->SetOrientation( PIN_ORIENTATION::PIN_LEFT );
69 pin->SetLength( pinLength );
70
71 // Use realistic names similar to the issue report
72 wxString name = wxString::Format( wxT( "GPIO%d" ), 28 + i );
73 wxString number = wxString::Format( wxT( "J1.%d" ), i + 1 );
74
75 pin->SetName( name );
76 pin->SetNumber( number );
78 pin->SetUnit( 1 );
79
80 symbol->AddDrawItem( pin.release() );
81 }
82
83 return symbol;
84}
85
86
91{
92 // Estimate text dimensions based on character count and font size
93 int textHeight = textInfo.m_TextSize;
94 int textWidth = textInfo.m_Text.Length() * textInfo.m_TextSize * 6 / 10;
95
96 // Handle vertical text - swap width and height
97 if( textInfo.m_Angle == ANGLE_VERTICAL )
98 std::swap( textWidth, textHeight );
99
100 BOX2I bbox;
101 bbox.SetOrigin( textInfo.m_TextPosition.x - textWidth / 2,
102 textInfo.m_TextPosition.y - textHeight / 2 );
103 bbox.SetSize( textWidth, textHeight );
104
105 return bbox;
106}
107
108
114static int getPerpendicularDistance( const SCH_PIN* pin, const VECTOR2I& textPos,
115 const TRANSFORM& transform )
116{
117 VECTOR2I pinPos = pin->GetPosition();
118 PIN_ORIENTATION orient = pin->PinDrawOrient( transform );
119
120 if( orient == PIN_ORIENTATION::PIN_LEFT || orient == PIN_ORIENTATION::PIN_RIGHT )
121 {
122 // Horizontal pin - perpendicular distance is in Y
123 return std::abs( textPos.y - pinPos.y );
124 }
125 else
126 {
127 // Vertical pin - perpendicular distance is in X
128 return std::abs( textPos.x - pinPos.x );
129 }
130}
131
132
140BOOST_AUTO_TEST_CASE( AdjacentPinTextNoOverlap )
141{
142 // Create test symbol with adjacent pins
143 std::unique_ptr<LIB_SYMBOL> symbol = createAdjacentPinsSymbol();
144 BOOST_REQUIRE( symbol );
145
146 // Get the pins
147 std::vector<SCH_PIN*> pins;
148
149 for( SCH_ITEM& item : symbol->GetDrawItems() )
150 {
151 if( item.Type() == SCH_PIN_T )
152 pins.push_back( static_cast<SCH_PIN*>( &item ) );
153 }
154
155 BOOST_REQUIRE_GE( pins.size(), 2 );
156
157 // Test at 0° (identity transform) and 90° rotation
158 std::vector<TRANSFORM> rotations = {
159 TRANSFORM( 1, 0, 0, 1 ), // 0° (identity) - pins are horizontal
160 TRANSFORM( 0, -1, 1, 0 ), // 90° CCW - pins become vertical
161 };
162
163 std::vector<wxString> rotationNames = { wxT( "0°" ), wxT( "90°" ) };
164
165 for( size_t r = 0; r < rotations.size(); r++ )
166 {
167 const TRANSFORM& transform = rotations[r];
168 const wxString& rotName = rotationNames[r];
169
170 // Set global transform for this test
171 TRANSFORM oldTransform = DefaultTransform;
172 DefaultTransform = transform;
173
174 // Collect text bounding boxes for all pins
175 struct PinTextBoxes
176 {
177 wxString pinNumber;
178 BOX2I nameBBox;
179 BOX2I numberBBox;
180 bool hasName;
181 bool hasNumber;
182 };
183
184 std::vector<PinTextBoxes> pinBoxes;
185
186 for( SCH_PIN* pin : pins )
187 {
188 PinTextBoxes boxes;
189 boxes.pinNumber = pin->GetNumber();
190 boxes.hasName = false;
191 boxes.hasNumber = false;
192
193 PIN_LAYOUT_CACHE cache( *pin );
194
195 // Get name bounding box
196 std::optional<PIN_LAYOUT_CACHE::TEXT_INFO> nameInfo = cache.GetPinNameInfo( 0 );
197
198 if( nameInfo.has_value() && !nameInfo->m_Text.IsEmpty() )
199 {
200 boxes.nameBBox = getTextBoundingBox( *nameInfo );
201 boxes.hasName = true;
202 }
203
204 // Get number bounding box
205 std::optional<PIN_LAYOUT_CACHE::TEXT_INFO> numberInfo = cache.GetPinNumberInfo( 0 );
206
207 if( numberInfo.has_value() && !numberInfo->m_Text.IsEmpty() )
208 {
209 boxes.numberBBox = getTextBoundingBox( *numberInfo );
210 boxes.hasNumber = true;
211 }
212
213 pinBoxes.push_back( boxes );
214 }
215
216 // Check that names and numbers of different pins don't overlap
217 for( size_t i = 0; i < pinBoxes.size(); i++ )
218 {
219 for( size_t j = i + 1; j < pinBoxes.size(); j++ )
220 {
221 const PinTextBoxes& pinA = pinBoxes[i];
222 const PinTextBoxes& pinB = pinBoxes[j];
223
224 // Check name-to-name overlap
225 if( pinA.hasName && pinB.hasName )
226 {
227 bool namesOverlap = pinA.nameBBox.Intersects( pinB.nameBBox );
228 BOOST_CHECK_MESSAGE(
229 !namesOverlap,
230 "At " << rotName << ": Pin " << pinA.pinNumber << " name overlaps with pin "
231 << pinB.pinNumber << " name. "
232 << "BBoxA=(" << pinA.nameBBox.GetLeft() << ","
233 << pinA.nameBBox.GetTop() << ")-(" << pinA.nameBBox.GetRight()
234 << "," << pinA.nameBBox.GetBottom() << ") "
235 << "BBoxB=(" << pinB.nameBBox.GetLeft() << ","
236 << pinB.nameBBox.GetTop() << ")-(" << pinB.nameBBox.GetRight()
237 << "," << pinB.nameBBox.GetBottom() << ")" );
238 }
239
240 // Check number-to-number overlap
241 if( pinA.hasNumber && pinB.hasNumber )
242 {
243 bool numbersOverlap = pinA.numberBBox.Intersects( pinB.numberBBox );
244 BOOST_CHECK_MESSAGE(
245 !numbersOverlap,
246 "At " << rotName << ": Pin " << pinA.pinNumber
247 << " number overlaps with pin " << pinB.pinNumber << " number. "
248 << "BBoxA=(" << pinA.numberBBox.GetLeft() << ","
249 << pinA.numberBBox.GetTop() << ")-("
250 << pinA.numberBBox.GetRight() << ","
251 << pinA.numberBBox.GetBottom() << ") "
252 << "BBoxB=(" << pinB.numberBBox.GetLeft() << ","
253 << pinB.numberBBox.GetTop() << ")-("
254 << pinB.numberBBox.GetRight() << ","
255 << pinB.numberBBox.GetBottom() << ")" );
256 }
257
258 // Check name-to-number overlap between pins
259 if( pinA.hasName && pinB.hasNumber )
260 {
261 bool overlap = pinA.nameBBox.Intersects( pinB.numberBBox );
262 BOOST_CHECK_MESSAGE(
263 !overlap,
264 "At " << rotName << ": Pin " << pinA.pinNumber
265 << " name overlaps with pin " << pinB.pinNumber << " number." );
266 }
267
268 if( pinA.hasNumber && pinB.hasName )
269 {
270 bool overlap = pinA.numberBBox.Intersects( pinB.nameBBox );
271 BOOST_CHECK_MESSAGE(
272 !overlap,
273 "At " << rotName << ": Pin " << pinA.pinNumber
274 << " number overlaps with pin " << pinB.pinNumber << " name." );
275 }
276 }
277 }
278
279 // Restore original transform
280 DefaultTransform = oldTransform;
281 }
282}
283
284
291BOOST_AUTO_TEST_CASE( PerpendicularDistanceConsistentAcrossRotations )
292{
293 // Create test symbol with adjacent pins
294 std::unique_ptr<LIB_SYMBOL> symbol = createAdjacentPinsSymbol();
295 BOOST_REQUIRE( symbol );
296
297 // Get the first pin for testing
298 SCH_PIN* testPin = nullptr;
299
300 for( SCH_ITEM& item : symbol->GetDrawItems() )
301 {
302 if( item.Type() == SCH_PIN_T )
303 {
304 testPin = static_cast<SCH_PIN*>( &item );
305 break;
306 }
307 }
308
309 BOOST_REQUIRE( testPin );
310
311 // Transforms for 0° and 90°
312 TRANSFORM transform0( 1, 0, 0, 1 ); // 0° (identity)
313 TRANSFORM transform90( 0, -1, 1, 0 ); // 90° CCW
314
315 // Measure perpendicular distance at 0°
316 TRANSFORM oldTransform = DefaultTransform;
317 DefaultTransform = transform0;
318
319 PIN_LAYOUT_CACHE cache0( *testPin );
320 std::optional<PIN_LAYOUT_CACHE::TEXT_INFO> nameInfo0 = cache0.GetPinNameInfo( 0 );
321 std::optional<PIN_LAYOUT_CACHE::TEXT_INFO> numberInfo0 = cache0.GetPinNumberInfo( 0 );
322
323 int nameDistance0 = 0;
324 int numberDistance0 = 0;
325
326 if( nameInfo0.has_value() )
327 nameDistance0 = getPerpendicularDistance( testPin, nameInfo0->m_TextPosition, transform0 );
328
329 if( numberInfo0.has_value() )
330 numberDistance0 = getPerpendicularDistance( testPin, numberInfo0->m_TextPosition, transform0 );
331
332 // Measure perpendicular distance at 90°
333 DefaultTransform = transform90;
334
335 PIN_LAYOUT_CACHE cache90( *testPin );
336 std::optional<PIN_LAYOUT_CACHE::TEXT_INFO> nameInfo90 = cache90.GetPinNameInfo( 0 );
337 std::optional<PIN_LAYOUT_CACHE::TEXT_INFO> numberInfo90 = cache90.GetPinNumberInfo( 0 );
338
339 int nameDistance90 = 0;
340 int numberDistance90 = 0;
341
342 if( nameInfo90.has_value() )
343 nameDistance90 = getPerpendicularDistance( testPin, nameInfo90->m_TextPosition, transform90 );
344
345 if( numberInfo90.has_value() )
346 numberDistance90 = getPerpendicularDistance( testPin, numberInfo90->m_TextPosition, transform90 );
347
348 // Restore original transform
349 DefaultTransform = oldTransform;
350
351 // Allow some tolerance for rounding differences
352 const int tolerance = schIUScale.MilsToIU( 5 ); // 5 mils tolerance
353
354 // Check that name distances are consistent
355 if( nameInfo0.has_value() && nameInfo90.has_value() )
356 {
357 int nameDiff = std::abs( nameDistance0 - nameDistance90 );
358 BOOST_CHECK_MESSAGE(
359 nameDiff <= tolerance,
360 "Pin name perpendicular distance changed across rotation. "
361 << "At 0°: " << nameDistance0 << ", at 90°: " << nameDistance90
362 << ", difference: " << nameDiff << " (tolerance: " << tolerance << ")" );
363 }
364
365 // Check that number distances are consistent
366 if( numberInfo0.has_value() && numberInfo90.has_value() )
367 {
368 int numberDiff = std::abs( numberDistance0 - numberDistance90 );
369 BOOST_CHECK_MESSAGE(
370 numberDiff <= tolerance,
371 "Pin number perpendicular distance changed across rotation. "
372 << "At 0°: " << numberDistance0 << ", at 90°: " << numberDistance90
373 << ", difference: " << numberDiff << " (tolerance: " << tolerance << ")" );
374 }
375}
376
377
384BOOST_AUTO_TEST_CASE( NameAndNumberOnOppositeSides )
385{
386 // Create test symbol with adjacent pins
387 std::unique_ptr<LIB_SYMBOL> symbol = createAdjacentPinsSymbol();
388 BOOST_REQUIRE( symbol );
389
390 // Get the first pin for testing
391 SCH_PIN* testPin = nullptr;
392
393 for( SCH_ITEM& item : symbol->GetDrawItems() )
394 {
395 if( item.Type() == SCH_PIN_T )
396 {
397 testPin = static_cast<SCH_PIN*>( &item );
398 break;
399 }
400 }
401
402 BOOST_REQUIRE( testPin );
403
404 // Test at both 0° and 90°
405 std::vector<TRANSFORM> rotations = { TRANSFORM( 1, 0, 0, 1 ), TRANSFORM( 0, -1, 1, 0 ) };
406 std::vector<wxString> rotationNames = { wxT( "0°" ), wxT( "90°" ) };
407
408 for( size_t r = 0; r < rotations.size(); r++ )
409 {
410 const TRANSFORM& transform = rotations[r];
411 const wxString& rotName = rotationNames[r];
412
413 TRANSFORM oldTransform = DefaultTransform;
414 DefaultTransform = transform;
415
416 PIN_LAYOUT_CACHE cache( *testPin );
417 std::optional<PIN_LAYOUT_CACHE::TEXT_INFO> nameInfo = cache.GetPinNameInfo( 0 );
418 std::optional<PIN_LAYOUT_CACHE::TEXT_INFO> numberInfo = cache.GetPinNumberInfo( 0 );
419
420 BOOST_REQUIRE_MESSAGE( nameInfo.has_value(), "Pin should have name at " << rotName );
421 BOOST_REQUIRE_MESSAGE( numberInfo.has_value(), "Pin should have number at " << rotName );
422
423 VECTOR2I pinPos = testPin->GetPosition();
424 PIN_ORIENTATION orient = testPin->PinDrawOrient( transform );
425
426 if( orient == PIN_ORIENTATION::PIN_LEFT || orient == PIN_ORIENTATION::PIN_RIGHT )
427 {
428 // Horizontal pin - name and number should be on opposite Y sides
429 bool nameAbove = nameInfo->m_TextPosition.y < pinPos.y;
430 bool numberAbove = numberInfo->m_TextPosition.y < pinPos.y;
431
432 BOOST_CHECK_MESSAGE(
433 nameAbove != numberAbove,
434 "At " << rotName << " (horizontal pin): name and number should be on opposite "
435 << "Y sides of pin. Name Y=" << nameInfo->m_TextPosition.y
436 << ", Number Y=" << numberInfo->m_TextPosition.y
437 << ", Pin Y=" << pinPos.y );
438 }
439 else
440 {
441 // Vertical pin - name and number should be on opposite X sides
442 bool nameLeft = nameInfo->m_TextPosition.x < pinPos.x;
443 bool numberLeft = numberInfo->m_TextPosition.x < pinPos.x;
444
445 BOOST_CHECK_MESSAGE(
446 nameLeft != numberLeft,
447 "At " << rotName << " (vertical pin): name and number should be on opposite "
448 << "X sides of pin. Name X=" << nameInfo->m_TextPosition.x
449 << ", Number X=" << numberInfo->m_TextPosition.x
450 << ", Pin X=" << pinPos.x );
451 }
452
453 DefaultTransform = oldTransform;
454 }
455}
456
457
const char * name
constexpr EDA_IU_SCALE schIUScale
Definition base_units.h:114
BOX2< VECTOR2I > BOX2I
Definition box2.h:922
constexpr void SetOrigin(const Vec &pos)
Definition box2.h:237
constexpr void SetSize(const SizeVec &size)
Definition box2.h:248
Define a library symbol object.
Definition lib_symbol.h:83
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:167
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:1194
VECTOR2I GetPosition() const override
Definition sch_pin.cpp:256
for transforming drawing coordinates for a wxDC device context.
Definition transform.h:46
STL class.
static constexpr EDA_ANGLE ANGLE_VERTICAL
Definition eda_angle.h:408
TRANSFORM DefaultTransform
Definition transform.cpp:32
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:39
PIN_ORIENTATION
The symbol library pin object orientations.
Definition pin_type.h:105
@ PIN_RIGHT
The pin extends rightwards from the connection point.
Definition pin_type.h:111
@ PIN_LEFT
The pin extends leftwards from the connection point: Probably on the right side of the symbol.
Definition pin_type.h:118
BOOST_AUTO_TEST_SUITE(CadstarPartParser)
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
BOOST_AUTO_TEST_SUITE_END()
KIBIS_PIN * pin
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°.
@ SCH_PIN_T
Definition typeinfo.h:157
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:695