1 /*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright (C) 2012 Jean-Pierre Charras, jean-pierre.charras@ujf-grenoble.fr
5 * Copyright (C) 2012 SoftPLC Corporation, Dick Hollenbeck <dick@softplc.com>
6 * Copyright (C) 2012 Wayne Stambaugh <stambaughw@gmail.com>
7 * Copyright (C) 1992-2021 KiCad Developers, see AUTHORS.txt for contributors.
8 *
9 * This program is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU General Public License
11 * as published by the Free Software Foundation; either version 2
12 * of the License, or (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, you may find one here:
21 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
22 * or you may search the http://www.gnu.org website for the version 2 license,
23 * or you may write to the Free Software Foundation, Inc.,
24 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
25 */
26
27 #include <bitmaps.h>
28 #include <pcb_edit_frame.h>
29 #include <base_units.h>
30 #include <board.h>
31 #include <convert_basic_shapes_to_polygon.h>
32 #include <pcb_dimension.h>
33 #include <pcb_text.h>
34 #include <geometry/shape_compound.h>
35 #include <geometry/shape_circle.h>
36 #include <geometry/shape_segment.h>
37 #include <settings/color_settings.h>
38 #include <settings/settings_manager.h>
39 #include <trigo.h>
40 #include <i18n_utility.h>
41
42
PCB_DIMENSION_BASE(BOARD_ITEM * aParent,KICAD_T aType)43 PCB_DIMENSION_BASE::PCB_DIMENSION_BASE( BOARD_ITEM* aParent, KICAD_T aType ) :
44 BOARD_ITEM( aParent, aType ),
45 m_overrideTextEnabled( false ),
46 m_units( EDA_UNITS::INCHES ),
47 m_autoUnits( false ),
48 m_unitsFormat( DIM_UNITS_FORMAT::BARE_SUFFIX ),
49 m_precision( 4 ),
50 m_suppressZeroes( false ),
51 m_lineThickness( Millimeter2iu( 0.2 ) ),
52 m_arrowLength( Mils2iu( 50 ) ),
53 m_extensionOffset( 0 ),
54 m_textPosition( DIM_TEXT_POSITION::OUTSIDE ),
55 m_keepTextAligned( true ),
56 m_text( aParent ),
57 m_measuredValue( 0 )
58 {
59 m_layer = Dwgs_User;
60 }
61
62
SetParent(EDA_ITEM * aParent)63 void PCB_DIMENSION_BASE::SetParent( EDA_ITEM* aParent )
64 {
65 BOARD_ITEM::SetParent( aParent );
66 m_text.SetParent( aParent );
67 }
68
69
updateText()70 void PCB_DIMENSION_BASE::updateText()
71 {
72 wxString text = m_overrideTextEnabled ? m_valueString : GetValueText();
73
74 switch( m_unitsFormat )
75 {
76 case DIM_UNITS_FORMAT::NO_SUFFIX: // no units
77 break;
78
79 case DIM_UNITS_FORMAT::BARE_SUFFIX: // normal
80 text += " ";
81 text += GetAbbreviatedUnitsLabel( m_units );
82 break;
83
84 case DIM_UNITS_FORMAT::PAREN_SUFFIX: // parenthetical
85 text += " (";
86 text += GetAbbreviatedUnitsLabel( m_units );
87 text += ")";
88 break;
89 }
90
91 text.Prepend( m_prefix );
92 text.Append( m_suffix );
93
94 m_text.SetText( text );
95 }
96
97
98 template<typename ShapeType>
addShape(const ShapeType & aShape)99 void PCB_DIMENSION_BASE::addShape( const ShapeType& aShape )
100 {
101 m_shapes.push_back( std::make_shared<ShapeType>( aShape ) );
102 }
103
104
GetValueText() const105 wxString PCB_DIMENSION_BASE::GetValueText() const
106 {
107 struct lconv* lc = localeconv();
108 wxChar sep = lc->decimal_point[0];
109
110 int val = GetMeasuredValue();
111 wxString text;
112 wxString format = wxT( "%." ) + wxString::Format( "%i", m_precision ) + wxT( "f" );
113
114 text.Printf( format, To_User_Unit( m_units, val ) );
115
116 if( m_suppressZeroes )
117 {
118 while( text.Last() == '0' )
119 {
120 text.RemoveLast();
121
122 if( text.Last() == '.' || text.Last() == sep )
123 {
124 text.RemoveLast();
125 break;
126 }
127 }
128 }
129
130 return text;
131 }
132
133
SetPrefix(const wxString & aPrefix)134 void PCB_DIMENSION_BASE::SetPrefix( const wxString& aPrefix )
135 {
136 m_prefix = aPrefix;
137 }
138
139
SetSuffix(const wxString & aSuffix)140 void PCB_DIMENSION_BASE::SetSuffix( const wxString& aSuffix )
141 {
142 m_suffix = aSuffix;
143 }
144
145
SetUnits(EDA_UNITS aUnits)146 void PCB_DIMENSION_BASE::SetUnits( EDA_UNITS aUnits )
147 {
148 m_units = aUnits;
149 }
150
151
GetUnitsMode() const152 DIM_UNITS_MODE PCB_DIMENSION_BASE::GetUnitsMode() const
153 {
154 if( m_autoUnits )
155 {
156 return DIM_UNITS_MODE::AUTOMATIC;
157 }
158 else
159 {
160 switch( m_units )
161 {
162 case EDA_UNITS::MILLIMETRES:
163 return DIM_UNITS_MODE::MILLIMETRES;
164
165 case EDA_UNITS::MILS:
166 return DIM_UNITS_MODE::MILS;
167
168 default:
169 case EDA_UNITS::INCHES:
170 return DIM_UNITS_MODE::INCHES;
171 }
172 }
173 }
174
175
SetUnitsMode(DIM_UNITS_MODE aMode)176 void PCB_DIMENSION_BASE::SetUnitsMode( DIM_UNITS_MODE aMode )
177 {
178 m_autoUnits = false;
179
180 switch( aMode )
181 {
182 case DIM_UNITS_MODE::INCHES:
183 m_units = EDA_UNITS::INCHES;
184 break;
185
186 case DIM_UNITS_MODE::MILS:
187 m_units = EDA_UNITS::MILS;
188 break;
189
190 case DIM_UNITS_MODE::MILLIMETRES:
191 m_units = EDA_UNITS::MILLIMETRES;
192 break;
193
194 case DIM_UNITS_MODE::AUTOMATIC:
195 m_autoUnits = true;
196 break;
197 }
198 }
199
200
SetText(const wxString & aNewText)201 void PCB_DIMENSION_BASE::SetText( const wxString& aNewText )
202 {
203 m_valueString = aNewText;
204 updateText();
205 }
206
207
GetText() const208 const wxString PCB_DIMENSION_BASE::GetText() const
209 {
210 return m_text.GetText();
211 }
212
213
SetLayer(PCB_LAYER_ID aLayer)214 void PCB_DIMENSION_BASE::SetLayer( PCB_LAYER_ID aLayer )
215 {
216 m_layer = aLayer;
217 m_text.SetLayer( aLayer );
218 }
219
220
Move(const wxPoint & offset)221 void PCB_DIMENSION_BASE::Move( const wxPoint& offset )
222 {
223 m_text.Offset( offset );
224
225 m_start += offset;
226 m_end += offset;
227
228 Update();
229 }
230
231
Rotate(const wxPoint & aRotCentre,double aAngle)232 void PCB_DIMENSION_BASE::Rotate( const wxPoint& aRotCentre, double aAngle )
233 {
234 double newAngle = m_text.GetTextAngle() + aAngle;
235
236 if( newAngle >= 3600 )
237 newAngle -= 3600;
238
239 m_text.SetTextAngle( newAngle );
240
241 wxPoint pt = m_text.GetTextPos();
242 RotatePoint( &pt, aRotCentre, aAngle );
243 m_text.SetTextPos( pt );
244
245 RotatePoint( &m_start, aRotCentre, aAngle );
246 RotatePoint( &m_end, aRotCentre, aAngle );
247
248 Update();
249 }
250
251
Flip(const wxPoint & aCentre,bool aFlipLeftRight)252 void PCB_DIMENSION_BASE::Flip( const wxPoint& aCentre, bool aFlipLeftRight )
253 {
254 Mirror( aCentre );
255
256 SetLayer( FlipLayer( GetLayer(), GetBoard()->GetCopperLayerCount() ) );
257 }
258
259
Mirror(const wxPoint & axis_pos,bool aMirrorLeftRight)260 void PCB_DIMENSION_BASE::Mirror( const wxPoint& axis_pos, bool aMirrorLeftRight )
261 {
262 int axis = aMirrorLeftRight ? axis_pos.x : axis_pos.y;
263 wxPoint newPos = m_text.GetTextPos();
264
265 #define INVERT( pos ) ( ( pos ) = axis - ( ( pos ) - axis ) )
266 if( aMirrorLeftRight )
267 INVERT( newPos.x );
268 else
269 INVERT( newPos.y );
270
271 m_text.SetTextPos( newPos );
272
273 // invert angle
274 m_text.SetTextAngle( -m_text.GetTextAngle() );
275
276 if( aMirrorLeftRight )
277 {
278 INVERT( m_start.x );
279 INVERT( m_end.x );
280 }
281 else
282 {
283 INVERT( m_start.y );
284 INVERT( m_end.y );
285 }
286
287 m_text.SetMirrored( !m_text.IsMirrored() );
288
289 Update();
290 }
291
292
GetMsgPanelInfo(EDA_DRAW_FRAME * aFrame,std::vector<MSG_PANEL_ITEM> & aList)293 void PCB_DIMENSION_BASE::GetMsgPanelInfo( EDA_DRAW_FRAME* aFrame,
294 std::vector<MSG_PANEL_ITEM>& aList )
295 {
296 // for now, display only the text within the DIMENSION using class PCB_TEXT.
297 wxString msg;
298
299 wxCHECK_RET( m_parent != nullptr, wxT( "PCB_TEXT::GetMsgPanelInfo() m_Parent is NULL." ) );
300
301 aList.emplace_back( _( "Dimension" ), m_text.GetShownText() );
302
303 aList.emplace_back( _( "Prefix" ), GetPrefix() );
304
305 if( GetOverrideTextEnabled() )
306 {
307 aList.emplace_back( _( "Override Text" ), GetOverrideText() );
308 }
309 else
310 {
311 aList.emplace_back( _( "Value" ), GetValueText() );
312
313 msg = "%" + wxString::Format( "1.%df", GetPrecision() );
314 aList.emplace_back( _( "Precision" ), wxString::Format( msg, 0.0 ) );
315 }
316
317 aList.emplace_back( _( "Suffix" ), GetSuffix() );
318
319 EDA_UNITS units;
320
321 GetUnits( units );
322 aList.emplace_back( _( "Units" ), GetAbbreviatedUnitsLabel( units ) );
323
324 ORIGIN_TRANSFORMS originTransforms = aFrame->GetOriginTransforms();
325 units = aFrame->GetUserUnits();
326
327 if( Type() == PCB_DIM_CENTER_T )
328 {
329 wxPoint startCoord = originTransforms.ToDisplayAbs( GetStart() );
330 wxString start = wxString::Format( "@(%s, %s)",
331 MessageTextFromValue( units, startCoord.x ),
332 MessageTextFromValue( units, startCoord.y ) );
333
334 aList.emplace_back( start, wxEmptyString );
335 }
336 else
337 {
338 wxPoint startCoord = originTransforms.ToDisplayAbs( GetStart() );
339 wxString start = wxString::Format( "@(%s, %s)",
340 MessageTextFromValue( units, startCoord.x ),
341 MessageTextFromValue( units, startCoord.y ) );
342 wxPoint endCoord = originTransforms.ToDisplayAbs( GetEnd() );
343 wxString end = wxString::Format( "@(%s, %s)",
344 MessageTextFromValue( units, endCoord.x ),
345 MessageTextFromValue( units, endCoord.y ) );
346
347 aList.emplace_back( start, end );
348 }
349
350 if( aFrame->GetName() == PCB_EDIT_FRAME_NAME && IsLocked() )
351 aList.emplace_back( _( "Status" ), _( "Locked" ) );
352
353 aList.emplace_back( _( "Layer" ), GetLayerName() );
354 }
355
356
GetEffectiveShape(PCB_LAYER_ID aLayer) const357 std::shared_ptr<SHAPE> PCB_DIMENSION_BASE::GetEffectiveShape( PCB_LAYER_ID aLayer ) const
358 {
359 std::shared_ptr<SHAPE_COMPOUND> effectiveShape = std::make_shared<SHAPE_COMPOUND>();
360
361 effectiveShape->AddShape( Text().GetEffectiveTextShape()->Clone() );
362
363 for( const std::shared_ptr<SHAPE>& shape : GetShapes() )
364 effectiveShape->AddShape( shape->Clone() );
365
366 return effectiveShape;
367 }
368
369
HitTest(const wxPoint & aPosition,int aAccuracy) const370 bool PCB_DIMENSION_BASE::HitTest( const wxPoint& aPosition, int aAccuracy ) const
371 {
372 if( m_text.TextHitTest( aPosition ) )
373 return true;
374
375 int dist_max = aAccuracy + ( m_lineThickness / 2 );
376
377 // Locate SEGMENTS
378
379 for( const std::shared_ptr<SHAPE>& shape : GetShapes() )
380 {
381 if( shape->Collide( aPosition, dist_max ) )
382 return true;
383 }
384
385 return false;
386 }
387
388
HitTest(const EDA_RECT & aRect,bool aContained,int aAccuracy) const389 bool PCB_DIMENSION_BASE::HitTest( const EDA_RECT& aRect, bool aContained, int aAccuracy ) const
390 {
391 EDA_RECT arect = aRect;
392 arect.Inflate( aAccuracy );
393
394 EDA_RECT rect = GetBoundingBox();
395
396 if( aAccuracy )
397 rect.Inflate( aAccuracy );
398
399 if( aContained )
400 return arect.Contains( rect );
401
402 return arect.Intersects( rect );
403 }
404
405
GetBoundingBox() const406 const EDA_RECT PCB_DIMENSION_BASE::GetBoundingBox() const
407 {
408 EDA_RECT bBox;
409 int xmin, xmax, ymin, ymax;
410
411 bBox = m_text.GetTextBox();
412 xmin = bBox.GetX();
413 xmax = bBox.GetRight();
414 ymin = bBox.GetY();
415 ymax = bBox.GetBottom();
416
417 for( const std::shared_ptr<SHAPE>& shape : GetShapes() )
418 {
419 BOX2I shapeBox = shape->BBox();
420 shapeBox.Inflate( m_lineThickness / 2 );
421
422 xmin = std::min( xmin, shapeBox.GetOrigin().x );
423 xmax = std::max( xmax, shapeBox.GetEnd().x );
424 ymin = std::min( ymin, shapeBox.GetOrigin().y );
425 ymax = std::max( ymax, shapeBox.GetEnd().y );
426 }
427
428 bBox.SetX( xmin );
429 bBox.SetY( ymin );
430 bBox.SetWidth( xmax - xmin + 1 );
431 bBox.SetHeight( ymax - ymin + 1 );
432
433 bBox.Normalize();
434
435 return bBox;
436 }
437
438
GetSelectMenuText(EDA_UNITS aUnits) const439 wxString PCB_DIMENSION_BASE::GetSelectMenuText( EDA_UNITS aUnits ) const
440 {
441 return wxString::Format( _( "Dimension '%s' on %s" ), GetText(), GetLayerName() );
442 }
443
444
445
ViewBBox() const446 const BOX2I PCB_DIMENSION_BASE::ViewBBox() const
447 {
448 BOX2I dimBBox = BOX2I( VECTOR2I( GetBoundingBox().GetPosition() ),
449 VECTOR2I( GetBoundingBox().GetSize() ) );
450 dimBBox.Merge( m_text.ViewBBox() );
451
452 return dimBBox;
453 }
454
455
segPolyIntersection(const SHAPE_POLY_SET & aPoly,const SEG & aSeg,bool aStart)456 OPT_VECTOR2I PCB_DIMENSION_BASE::segPolyIntersection( const SHAPE_POLY_SET& aPoly, const SEG& aSeg,
457 bool aStart )
458 {
459 VECTOR2I start( aStart ? aSeg.A : aSeg.B );
460 VECTOR2I endpoint( aStart ? aSeg.B : aSeg.A );
461
462 if( aPoly.Contains( start ) )
463 return NULLOPT;
464
465 for( SHAPE_POLY_SET::CONST_SEGMENT_ITERATOR seg = aPoly.CIterateSegments(); seg; ++seg )
466 {
467 if( OPT_VECTOR2I intersection = ( *seg ).Intersect( aSeg ) )
468 {
469 if( ( *intersection - start ).SquaredEuclideanNorm() <
470 ( endpoint - start ).SquaredEuclideanNorm() )
471 endpoint = *intersection;
472 }
473 }
474
475 if( start == endpoint )
476 return NULLOPT;
477
478 return OPT_VECTOR2I( endpoint );
479 }
480
481
segCircleIntersection(CIRCLE & aCircle,SEG & aSeg,bool aStart)482 OPT_VECTOR2I PCB_DIMENSION_BASE::segCircleIntersection( CIRCLE& aCircle, SEG& aSeg, bool aStart )
483 {
484 VECTOR2I start( aStart ? aSeg.A : aSeg.B );
485 VECTOR2I endpoint( aStart ? aSeg.B : aSeg.A );
486
487 if( aCircle.Contains( start ) )
488 return NULLOPT;
489
490 std::vector<VECTOR2I> intersections = aCircle.Intersect( aSeg );
491
492 for( VECTOR2I& intersection : aCircle.Intersect( aSeg ) )
493 {
494 if( ( intersection - start ).SquaredEuclideanNorm() <
495 ( endpoint - start ).SquaredEuclideanNorm() )
496 endpoint = intersection;
497 }
498
499 if( start == endpoint )
500 return NULLOPT;
501
502 return OPT_VECTOR2I( endpoint );
503 }
504
505
TransformShapeWithClearanceToPolygon(SHAPE_POLY_SET & aCornerBuffer,PCB_LAYER_ID aLayer,int aClearance,int aError,ERROR_LOC aErrorLoc,bool aIgnoreLineWidth) const506 void PCB_DIMENSION_BASE::TransformShapeWithClearanceToPolygon( SHAPE_POLY_SET& aCornerBuffer,
507 PCB_LAYER_ID aLayer, int aClearance,
508 int aError, ERROR_LOC aErrorLoc,
509 bool aIgnoreLineWidth ) const
510 {
511 wxASSERT_MSG( !aIgnoreLineWidth, "IgnoreLineWidth has no meaning for dimensions." );
512
513 for( const std::shared_ptr<SHAPE>& shape : m_shapes )
514 {
515 const SHAPE_CIRCLE* circle = dynamic_cast<const SHAPE_CIRCLE*>( shape.get() );
516 const SHAPE_SEGMENT* seg = dynamic_cast<const SHAPE_SEGMENT*>( shape.get() );
517
518 if( circle )
519 {
520 TransformCircleToPolygon( aCornerBuffer, (wxPoint) circle->GetCenter(),
521 circle->GetRadius() + m_lineThickness / 2 + aClearance,
522 aError, aErrorLoc );
523 }
524 else if( seg )
525 {
526 TransformOvalToPolygon( aCornerBuffer, (wxPoint) seg->GetSeg().A,
527 (wxPoint) seg->GetSeg().B, m_lineThickness + 2 * aClearance,
528 aError, aErrorLoc );
529 }
530 else
531 {
532 wxFAIL_MSG( "PCB_DIMENSION_BASE::TransformShapeWithClearanceToPolygon unexpected "
533 "shape type." );
534 }
535 }
536 }
537
538
PCB_DIM_ALIGNED(BOARD_ITEM * aParent,KICAD_T aType)539 PCB_DIM_ALIGNED::PCB_DIM_ALIGNED( BOARD_ITEM* aParent, KICAD_T aType ) :
540 PCB_DIMENSION_BASE( aParent, aType ),
541 m_height( 0 )
542 {
543 // To preserve look of old dimensions, initialize extension height based on default arrow length
544 m_extensionHeight = static_cast<int>( m_arrowLength * std::sin( DEG2RAD( s_arrowAngle ) ) );
545 }
546
547
Clone() const548 EDA_ITEM* PCB_DIM_ALIGNED::Clone() const
549 {
550 return new PCB_DIM_ALIGNED( *this );
551 }
552
553
SwapData(BOARD_ITEM * aImage)554 void PCB_DIM_ALIGNED::SwapData( BOARD_ITEM* aImage )
555 {
556 assert( aImage->Type() == PCB_DIM_ALIGNED_T );
557
558 m_shapes.clear();
559 static_cast<PCB_DIM_ALIGNED*>( aImage )->m_shapes.clear();
560
561 std::swap( *static_cast<PCB_DIM_ALIGNED*>( this ), *static_cast<PCB_DIM_ALIGNED*>( aImage ) );
562
563 Update();
564 }
565
566
GetMenuImage() const567 BITMAPS PCB_DIM_ALIGNED::GetMenuImage() const
568 {
569 return BITMAPS::add_aligned_dimension;
570 }
571
572
UpdateHeight(const wxPoint & aCrossbarStart,const wxPoint & aCrossbarEnd)573 void PCB_DIM_ALIGNED::UpdateHeight( const wxPoint& aCrossbarStart, const wxPoint& aCrossbarEnd )
574 {
575 VECTOR2D height( aCrossbarStart - GetStart() );
576 VECTOR2D crossBar( aCrossbarEnd - aCrossbarStart );
577
578 if( height.Cross( crossBar ) > 0 )
579 m_height = -height.EuclideanNorm();
580 else
581 m_height = height.EuclideanNorm();
582
583 Update();
584 }
585
586
updateGeometry()587 void PCB_DIM_ALIGNED::updateGeometry()
588 {
589 m_shapes.clear();
590
591 VECTOR2I dimension( m_end - m_start );
592
593 m_measuredValue = KiROUND( dimension.EuclideanNorm() );
594
595 VECTOR2I extension;
596
597 if( m_height > 0 )
598 extension = VECTOR2I( -dimension.y, dimension.x );
599 else
600 extension = VECTOR2I( dimension.y, -dimension.x );
601
602 // Add extension lines
603 int extensionHeight = std::abs( m_height ) - m_extensionOffset + m_extensionHeight;
604
605 VECTOR2I extStart( m_start );
606 extStart += extension.Resize( m_extensionOffset );
607
608 addShape( SHAPE_SEGMENT( extStart, extStart + extension.Resize( extensionHeight ) ) );
609
610 extStart = VECTOR2I( m_end );
611 extStart += extension.Resize( m_extensionOffset );
612
613 addShape( SHAPE_SEGMENT( extStart, extStart + extension.Resize( extensionHeight ) ) );
614
615 // Add crossbar
616 VECTOR2I crossBarDistance = sign( m_height ) * extension.Resize( m_height );
617 m_crossBarStart = m_start + wxPoint( crossBarDistance );
618 m_crossBarEnd = m_end + wxPoint( crossBarDistance );
619
620 // Update text after calculating crossbar position but before adding crossbar lines
621 updateText();
622
623 // Now that we have the text updated, we can determine how to draw the crossbar.
624 // First we need to create an appropriate bounding polygon to collide with
625 EDA_RECT textBox = m_text.GetTextBox().Inflate( m_text.GetTextWidth() / 2,
626 m_text.GetEffectiveTextPenWidth() );
627
628 SHAPE_POLY_SET polyBox;
629 polyBox.NewOutline();
630 polyBox.Append( textBox.GetOrigin() );
631 polyBox.Append( textBox.GetOrigin().x, textBox.GetEnd().y );
632 polyBox.Append( textBox.GetEnd() );
633 polyBox.Append( textBox.GetEnd().x, textBox.GetOrigin().y );
634 polyBox.Rotate( -m_text.GetTextAngleRadians(), textBox.GetCenter() );
635
636 // The ideal crossbar, if the text doesn't collide
637 SEG crossbar( m_crossBarStart, m_crossBarEnd );
638
639 // Now we can draw 0, 1, or 2 crossbar lines depending on how the polygon collides
640 bool containsA = polyBox.Contains( crossbar.A );
641 bool containsB = polyBox.Contains( crossbar.B );
642
643 OPT_VECTOR2I endpointA = segPolyIntersection( polyBox, crossbar );
644 OPT_VECTOR2I endpointB = segPolyIntersection( polyBox, crossbar, false );
645
646 if( endpointA )
647 m_shapes.emplace_back( new SHAPE_SEGMENT( crossbar.A, *endpointA ) );
648
649 if( endpointB )
650 m_shapes.emplace_back( new SHAPE_SEGMENT( *endpointB, crossbar.B ) );
651
652 if( !containsA && !containsB && !endpointA && !endpointB )
653 m_shapes.emplace_back( new SHAPE_SEGMENT( crossbar ) );
654
655 // Add arrows
656 VECTOR2I arrowEnd( m_arrowLength, 0 );
657
658 double arrowRotPos = dimension.Angle() + DEG2RAD( s_arrowAngle );
659 double arrowRotNeg = dimension.Angle() - DEG2RAD( s_arrowAngle );
660
661 m_shapes.emplace_back( new SHAPE_SEGMENT( m_crossBarStart,
662 m_crossBarStart + wxPoint( arrowEnd.Rotate( arrowRotPos ) ) ) );
663
664 m_shapes.emplace_back( new SHAPE_SEGMENT( m_crossBarStart,
665 m_crossBarStart + wxPoint( arrowEnd.Rotate( arrowRotNeg ) ) ) );
666
667 m_shapes.emplace_back( new SHAPE_SEGMENT( m_crossBarEnd,
668 m_crossBarEnd - wxPoint( arrowEnd.Rotate( arrowRotPos ) ) ) );
669
670 m_shapes.emplace_back( new SHAPE_SEGMENT( m_crossBarEnd,
671 m_crossBarEnd - wxPoint( arrowEnd.Rotate( arrowRotNeg ) ) ) );
672 }
673
674
updateText()675 void PCB_DIM_ALIGNED::updateText()
676 {
677 VECTOR2I crossbarCenter( ( m_crossBarEnd - m_crossBarStart ) / 2 );
678
679 if( m_textPosition == DIM_TEXT_POSITION::OUTSIDE )
680 {
681 int textOffsetDistance = m_text.GetEffectiveTextPenWidth() + m_text.GetTextHeight();
682
683 double rotation;
684 if( crossbarCenter.x == 0 )
685 rotation = sign( crossbarCenter.y ) * DEG2RAD( 90 );
686 else
687 rotation = -std::copysign( DEG2RAD( 90 ), crossbarCenter.x );
688
689 VECTOR2I textOffset = crossbarCenter.Rotate( rotation ).Resize( textOffsetDistance );
690 textOffset += crossbarCenter;
691
692 m_text.SetTextPos( m_crossBarStart + wxPoint( textOffset ) );
693 }
694 else if( m_textPosition == DIM_TEXT_POSITION::INLINE )
695 {
696 m_text.SetTextPos( m_crossBarStart + wxPoint( crossbarCenter ) );
697 }
698
699 if( m_keepTextAligned )
700 {
701 double textAngle = 3600 - RAD2DECIDEG( crossbarCenter.Angle() );
702
703 NORMALIZE_ANGLE_POS( textAngle );
704
705 if( textAngle > 900 && textAngle <= 2700 )
706 textAngle -= 1800;
707
708 m_text.SetTextAngle( textAngle );
709 }
710
711 PCB_DIMENSION_BASE::updateText();
712 }
713
714
GetMsgPanelInfo(EDA_DRAW_FRAME * aFrame,std::vector<MSG_PANEL_ITEM> & aList)715 void PCB_DIM_ALIGNED::GetMsgPanelInfo( EDA_DRAW_FRAME* aFrame, std::vector<MSG_PANEL_ITEM>& aList )
716 {
717 PCB_DIMENSION_BASE::GetMsgPanelInfo( aFrame, aList );
718
719 aList.emplace_back( _( "Height" ), MessageTextFromValue( aFrame->GetUserUnits(), m_height ) );
720 }
721
722
PCB_DIM_ORTHOGONAL(BOARD_ITEM * aParent)723 PCB_DIM_ORTHOGONAL::PCB_DIM_ORTHOGONAL( BOARD_ITEM* aParent ) :
724 PCB_DIM_ALIGNED( aParent, PCB_DIM_ORTHOGONAL_T )
725 {
726 // To preserve look of old dimensions, initialize extension height based on default arrow length
727 m_extensionHeight = static_cast<int>( m_arrowLength * std::sin( DEG2RAD( s_arrowAngle ) ) );
728 m_orientation = DIR::HORIZONTAL;
729 }
730
731
Clone() const732 EDA_ITEM* PCB_DIM_ORTHOGONAL::Clone() const
733 {
734 return new PCB_DIM_ORTHOGONAL( *this );
735 }
736
737
SwapData(BOARD_ITEM * aImage)738 void PCB_DIM_ORTHOGONAL::SwapData( BOARD_ITEM* aImage )
739 {
740 assert( aImage->Type() == PCB_DIM_ORTHOGONAL_T );
741
742 m_shapes.clear();
743 static_cast<PCB_DIM_ORTHOGONAL*>( aImage )->m_shapes.clear();
744
745 std::swap( *static_cast<PCB_DIM_ORTHOGONAL*>( this ),
746 *static_cast<PCB_DIM_ORTHOGONAL*>( aImage ) );
747
748 Update();
749 }
750
751
GetMenuImage() const752 BITMAPS PCB_DIM_ORTHOGONAL::GetMenuImage() const
753 {
754 return BITMAPS::add_orthogonal_dimension;
755 }
756
757
updateGeometry()758 void PCB_DIM_ORTHOGONAL::updateGeometry()
759 {
760 m_shapes.clear();
761
762 int measurement = ( m_orientation == DIR::HORIZONTAL ? m_end.x - m_start.x :
763 m_end.y - m_start.y );
764 m_measuredValue = KiROUND( std::abs( measurement ) );
765
766 VECTOR2I extension;
767
768 if( m_orientation == DIR::HORIZONTAL )
769 extension = VECTOR2I( 0, m_height );
770 else
771 extension = VECTOR2I( m_height, 0 );
772
773 // Add first extension line
774 int extensionHeight = std::abs( m_height ) - m_extensionOffset + m_extensionHeight;
775
776 VECTOR2I extStart( m_start );
777 extStart += extension.Resize( m_extensionOffset );
778
779 addShape( SHAPE_SEGMENT( extStart, extStart + extension.Resize( extensionHeight ) ) );
780
781 // Add crossbar
782 VECTOR2I crossBarDistance = sign( m_height ) * extension.Resize( m_height );
783 m_crossBarStart = m_start + wxPoint( crossBarDistance );
784
785 if( m_orientation == DIR::HORIZONTAL )
786 m_crossBarEnd = wxPoint( m_end.x, m_crossBarStart.y );
787 else
788 m_crossBarEnd = wxPoint( m_crossBarStart.x, m_end.y );
789
790 // Add second extension line (m_end to crossbar end)
791 if( m_orientation == DIR::HORIZONTAL )
792 extension = VECTOR2I( 0, m_end.y - m_crossBarEnd.y );
793 else
794 extension = VECTOR2I( m_end.x - m_crossBarEnd.x, 0 );
795
796 extensionHeight = extension.EuclideanNorm() - m_extensionOffset + m_extensionHeight;
797
798 extStart = VECTOR2I( m_crossBarEnd );
799 extStart -= extension.Resize( m_extensionHeight );
800
801 addShape( SHAPE_SEGMENT( extStart, extStart + extension.Resize( extensionHeight ) ) );
802
803 // Update text after calculating crossbar position but before adding crossbar lines
804 updateText();
805
806 // Now that we have the text updated, we can determine how to draw the crossbar.
807 // First we need to create an appropriate bounding polygon to collide with
808 EDA_RECT textBox = m_text.GetTextBox().Inflate( m_text.GetTextWidth() / 2,
809 m_text.GetEffectiveTextPenWidth() );
810
811 SHAPE_POLY_SET polyBox;
812 polyBox.NewOutline();
813 polyBox.Append( textBox.GetOrigin() );
814 polyBox.Append( textBox.GetOrigin().x, textBox.GetEnd().y );
815 polyBox.Append( textBox.GetEnd() );
816 polyBox.Append( textBox.GetEnd().x, textBox.GetOrigin().y );
817 polyBox.Rotate( -m_text.GetTextAngleRadians(), textBox.GetCenter() );
818
819 // The ideal crossbar, if the text doesn't collide
820 SEG crossbar( m_crossBarStart, m_crossBarEnd );
821
822 // Now we can draw 0, 1, or 2 crossbar lines depending on how the polygon collides
823 bool containsA = polyBox.Contains( crossbar.A );
824 bool containsB = polyBox.Contains( crossbar.B );
825
826 OPT_VECTOR2I endpointA = segPolyIntersection( polyBox, crossbar );
827 OPT_VECTOR2I endpointB = segPolyIntersection( polyBox, crossbar, false );
828
829 if( endpointA )
830 m_shapes.emplace_back( new SHAPE_SEGMENT( crossbar.A, *endpointA ) );
831
832 if( endpointB )
833 m_shapes.emplace_back( new SHAPE_SEGMENT( *endpointB, crossbar.B ) );
834
835 if( !containsA && !containsB && !endpointA && !endpointB )
836 m_shapes.emplace_back( new SHAPE_SEGMENT( crossbar ) );
837
838 // Add arrows
839 VECTOR2I crossBarAngle( m_crossBarEnd - m_crossBarStart );
840 VECTOR2I arrowEnd( m_arrowLength, 0 );
841
842 double arrowRotPos = crossBarAngle.Angle() + DEG2RAD( s_arrowAngle );
843 double arrowRotNeg = crossBarAngle.Angle() - DEG2RAD( s_arrowAngle );
844
845 m_shapes.emplace_back( new SHAPE_SEGMENT( m_crossBarStart,
846 m_crossBarStart + wxPoint( arrowEnd.Rotate( arrowRotPos ) ) ) );
847
848 m_shapes.emplace_back( new SHAPE_SEGMENT( m_crossBarStart,
849 m_crossBarStart + wxPoint( arrowEnd.Rotate( arrowRotNeg ) ) ) );
850
851 m_shapes.emplace_back( new SHAPE_SEGMENT( m_crossBarEnd,
852 m_crossBarEnd - wxPoint( arrowEnd.Rotate( arrowRotPos ) ) ) );
853
854 m_shapes.emplace_back( new SHAPE_SEGMENT( m_crossBarEnd,
855 m_crossBarEnd - wxPoint( arrowEnd.Rotate( arrowRotNeg ) ) ) );
856 }
857
858
updateText()859 void PCB_DIM_ORTHOGONAL::updateText()
860 {
861 VECTOR2I crossbarCenter( ( m_crossBarEnd - m_crossBarStart ) / 2 );
862
863 if( m_textPosition == DIM_TEXT_POSITION::OUTSIDE )
864 {
865 int textOffsetDistance = m_text.GetEffectiveTextPenWidth() + m_text.GetTextHeight();
866
867 VECTOR2I textOffset;
868
869 if( m_orientation == DIR::HORIZONTAL )
870 textOffset.y = -textOffsetDistance;
871 else
872 textOffset.x = -textOffsetDistance;
873
874 textOffset += crossbarCenter;
875
876 m_text.SetTextPos( m_crossBarStart + wxPoint( textOffset ) );
877 }
878 else if( m_textPosition == DIM_TEXT_POSITION::INLINE )
879 {
880 m_text.SetTextPos( m_crossBarStart + wxPoint( crossbarCenter ) );
881 }
882
883 if( m_keepTextAligned )
884 {
885 double textAngle;
886
887 if( abs( crossbarCenter.x ) > abs( crossbarCenter.y ) )
888 textAngle = 0;
889 else
890 textAngle = 900;
891
892 m_text.SetTextAngle( textAngle );
893 }
894
895 PCB_DIMENSION_BASE::updateText();
896 }
897
898
Rotate(const wxPoint & aRotCentre,double aAngle)899 void PCB_DIM_ORTHOGONAL::Rotate( const wxPoint& aRotCentre, double aAngle )
900 {
901 // restrict angle to -179.9 to 180.0 degrees
902 if( aAngle > 1800 )
903 {
904 aAngle -= 3600;
905 }
906 else if( aAngle <= -1800 )
907 {
908 aAngle += 3600;
909 }
910
911 // adjust orientation and height to new angle
912 // we can only handle the cases of -90, 0, 90, 180 degrees exactly;
913 // in the other cases we will use the nearest 90 degree angle to
914 // choose at least an approximate axis for the target orientation
915 // In case of exactly 45 or 135 degrees, we will round towards zero for consistency
916 if( aAngle > 450 && aAngle <= 1350 )
917 {
918 // about 90 degree
919 if( m_orientation == DIR::HORIZONTAL )
920 {
921 m_orientation = DIR::VERTICAL;
922 }
923 else
924 {
925 m_orientation = DIR::HORIZONTAL;
926 m_height = -m_height;
927 }
928 }
929 else if( aAngle < -450 && aAngle >= -1350 )
930 {
931 // about -90 degree
932 if( m_orientation == DIR::HORIZONTAL )
933 {
934 m_orientation = DIR::VERTICAL;
935 m_height = -m_height;
936 }
937 else
938 {
939 m_orientation = DIR::HORIZONTAL;
940 }
941 }
942 else if( aAngle > 1350 || aAngle < -1350 )
943 {
944 // about 180 degree
945 m_height = -m_height;
946 }
947
948 // this will update m_crossBarStart and m_crossbarEnd
949 PCB_DIMENSION_BASE::Rotate( aRotCentre, aAngle );
950 }
951
952
PCB_DIM_LEADER(BOARD_ITEM * aParent)953 PCB_DIM_LEADER::PCB_DIM_LEADER( BOARD_ITEM* aParent ) :
954 PCB_DIMENSION_BASE( aParent, PCB_DIM_LEADER_T ),
955 m_textFrame( DIM_TEXT_FRAME::NONE )
956 {
957 m_unitsFormat = DIM_UNITS_FORMAT::NO_SUFFIX;
958 m_overrideTextEnabled = true;
959 m_keepTextAligned = false;
960 }
961
962
Clone() const963 EDA_ITEM* PCB_DIM_LEADER::Clone() const
964 {
965 return new PCB_DIM_LEADER( *this );
966 }
967
968
SwapData(BOARD_ITEM * aImage)969 void PCB_DIM_LEADER::SwapData( BOARD_ITEM* aImage )
970 {
971 assert( aImage->Type() == PCB_DIM_LEADER_T );
972
973 std::swap( *static_cast<PCB_DIM_LEADER*>( this ), *static_cast<PCB_DIM_LEADER*>( aImage ) );
974 }
975
976
GetMenuImage() const977 BITMAPS PCB_DIM_LEADER::GetMenuImage() const
978 {
979 return BITMAPS::add_leader;
980 }
981
982
updateGeometry()983 void PCB_DIM_LEADER::updateGeometry()
984 {
985 m_shapes.clear();
986
987 updateText();
988
989 // Now that we have the text updated, we can determine how to draw the second line
990 // First we need to create an appropriate bounding polygon to collide with
991 EDA_RECT textBox = m_text.GetTextBox().Inflate( m_text.GetTextWidth() / 2,
992 m_text.GetEffectiveTextPenWidth() );
993
994 SHAPE_POLY_SET polyBox;
995 polyBox.NewOutline();
996 polyBox.Append( textBox.GetOrigin() );
997 polyBox.Append( textBox.GetOrigin().x, textBox.GetEnd().y );
998 polyBox.Append( textBox.GetEnd() );
999 polyBox.Append( textBox.GetEnd().x, textBox.GetOrigin().y );
1000 polyBox.Rotate( -m_text.GetTextAngleRadians(), textBox.GetCenter() );
1001
1002 VECTOR2I firstLine( m_end - m_start );
1003 VECTOR2I start( m_start );
1004 start += firstLine.Resize( m_extensionOffset );
1005
1006 SEG arrowSeg( m_start, m_end );
1007 SEG textSeg( m_end, m_text.GetPosition() );
1008 OPT_VECTOR2I arrowSegEnd = boost::make_optional( false, VECTOR2I() );;
1009 OPT_VECTOR2I textSegEnd = boost::make_optional( false, VECTOR2I() );
1010
1011 if( m_textFrame == DIM_TEXT_FRAME::CIRCLE )
1012 {
1013 double penWidth = m_text.GetEffectiveTextPenWidth() / 2.0;
1014 double radius = ( textBox.GetWidth() / 2.0 ) - penWidth;
1015 CIRCLE circle( textBox.GetCenter(), radius );
1016
1017 arrowSegEnd = segCircleIntersection( circle, arrowSeg );
1018 textSegEnd = segCircleIntersection( circle, textSeg );
1019 }
1020 else
1021 {
1022 arrowSegEnd = segPolyIntersection( polyBox, arrowSeg );
1023 textSegEnd = segPolyIntersection( polyBox, textSeg );
1024 }
1025
1026 if( !arrowSegEnd )
1027 arrowSegEnd = m_end;
1028
1029 m_shapes.emplace_back( new SHAPE_SEGMENT( start, *arrowSegEnd ) );
1030
1031 // Add arrows
1032 VECTOR2I arrowEnd( m_arrowLength, 0 );
1033
1034 double arrowRotPos = firstLine.Angle() + DEG2RAD( s_arrowAngle );
1035 double arrowRotNeg = firstLine.Angle() - DEG2RAD( s_arrowAngle );
1036
1037 m_shapes.emplace_back( new SHAPE_SEGMENT( start,
1038 start + wxPoint( arrowEnd.Rotate( arrowRotPos ) ) ) );
1039 m_shapes.emplace_back( new SHAPE_SEGMENT( start,
1040 start + wxPoint( arrowEnd.Rotate( arrowRotNeg ) ) ) );
1041
1042
1043 if( !GetText().IsEmpty() )
1044 {
1045 switch( m_textFrame )
1046 {
1047 case DIM_TEXT_FRAME::RECTANGLE:
1048 {
1049 for( SHAPE_POLY_SET::SEGMENT_ITERATOR seg = polyBox.IterateSegments(); seg; seg++ )
1050 m_shapes.emplace_back( new SHAPE_SEGMENT( *seg ) );
1051
1052 break;
1053 }
1054
1055 case DIM_TEXT_FRAME::CIRCLE:
1056 {
1057 double penWidth = m_text.GetEffectiveTextPenWidth() / 2.0;
1058 double radius = ( textBox.GetWidth() / 2.0 ) - penWidth;
1059 m_shapes.emplace_back( new SHAPE_CIRCLE( textBox.GetCenter(), radius ) );
1060
1061 break;
1062 }
1063
1064 default:
1065 break;
1066 }
1067 }
1068
1069 if( textSegEnd && *arrowSegEnd == m_end )
1070 m_shapes.emplace_back( new SHAPE_SEGMENT( m_end, *textSegEnd ) );
1071 }
1072
1073
GetMsgPanelInfo(EDA_DRAW_FRAME * aFrame,std::vector<MSG_PANEL_ITEM> & aList)1074 void PCB_DIM_LEADER::GetMsgPanelInfo( EDA_DRAW_FRAME* aFrame, std::vector<MSG_PANEL_ITEM>& aList )
1075 {
1076 wxString msg;
1077
1078 aList.emplace_back( _( "Leader" ), m_text.GetShownText() );
1079
1080 ORIGIN_TRANSFORMS originTransforms = aFrame->GetOriginTransforms();
1081 EDA_UNITS units = aFrame->GetUserUnits();
1082
1083 wxPoint startCoord = originTransforms.ToDisplayAbs( GetStart() );
1084 wxString start = wxString::Format( "@(%s, %s)",
1085 MessageTextFromValue( units, startCoord.x ),
1086 MessageTextFromValue( units, startCoord.y ) );
1087
1088 aList.emplace_back( start, wxEmptyString );
1089
1090 aList.emplace_back( _( "Layer" ), GetLayerName() );
1091 }
1092
1093
PCB_DIM_CENTER(BOARD_ITEM * aParent)1094 PCB_DIM_CENTER::PCB_DIM_CENTER( BOARD_ITEM* aParent ) :
1095 PCB_DIMENSION_BASE( aParent, PCB_DIM_CENTER_T )
1096 {
1097 m_unitsFormat = DIM_UNITS_FORMAT::NO_SUFFIX;
1098 m_overrideTextEnabled = true;
1099 }
1100
1101
Clone() const1102 EDA_ITEM* PCB_DIM_CENTER::Clone() const
1103 {
1104 return new PCB_DIM_CENTER( *this );
1105 }
1106
1107
SwapData(BOARD_ITEM * aImage)1108 void PCB_DIM_CENTER::SwapData( BOARD_ITEM* aImage )
1109 {
1110 assert( aImage->Type() == PCB_DIM_CENTER_T );
1111
1112 std::swap( *static_cast<PCB_DIM_CENTER*>( this ), *static_cast<PCB_DIM_CENTER*>( aImage ) );
1113 }
1114
1115
GetMenuImage() const1116 BITMAPS PCB_DIM_CENTER::GetMenuImage() const
1117 {
1118 return BITMAPS::add_center_dimension;
1119 }
1120
1121
GetBoundingBox() const1122 const EDA_RECT PCB_DIM_CENTER::GetBoundingBox() const
1123 {
1124 int halfWidth = VECTOR2I( m_end - m_start ).x + ( m_lineThickness / 2.0 );
1125
1126 EDA_RECT bBox;
1127
1128 bBox.SetX( m_start.x - halfWidth );
1129 bBox.SetY( m_start.y - halfWidth );
1130 bBox.SetWidth( halfWidth * 2 );
1131 bBox.SetHeight( halfWidth * 2 );
1132
1133 bBox.Normalize();
1134
1135 return bBox;
1136 }
1137
1138
ViewBBox() const1139 const BOX2I PCB_DIM_CENTER::ViewBBox() const
1140 {
1141 return BOX2I( VECTOR2I( GetBoundingBox().GetPosition() ),
1142 VECTOR2I( GetBoundingBox().GetSize() ) );
1143 }
1144
1145
updateGeometry()1146 void PCB_DIM_CENTER::updateGeometry()
1147 {
1148 m_shapes.clear();
1149
1150 VECTOR2I center( m_start );
1151 VECTOR2I arm( m_end - m_start );
1152
1153 m_shapes.emplace_back( new SHAPE_SEGMENT( center - arm, center + arm ) );
1154
1155 arm = arm.Rotate( DEG2RAD( 90 ) );
1156
1157 m_shapes.emplace_back( new SHAPE_SEGMENT( center - arm, center + arm ) );
1158 }
1159
1160
1161 static struct DIMENSION_DESC
1162 {
DIMENSION_DESCDIMENSION_DESC1163 DIMENSION_DESC()
1164 {
1165 PROPERTY_MANAGER& propMgr = PROPERTY_MANAGER::Instance();
1166 REGISTER_TYPE( PCB_DIMENSION_BASE );
1167 propMgr.InheritsAfter( TYPE_HASH( PCB_DIMENSION_BASE ), TYPE_HASH( BOARD_ITEM ) );
1168 // TODO: add dimension properties:
1169 //propMgr.AddProperty( new PROPERTY<DIMENSION, int>( _HKI( "Height" ),
1170 //&DIMENSION::SetHeight, &DIMENSION::GetHeight, PROPERTY_DISPLAY::DISTANCE ) );
1171 }
1172 } _DIMENSION_DESC;
1173
1174
1175