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