1 /*
2  * This program source code file is part of KiCad, a free EDA CAD application.
3  *
4  * Copyright (C) 2018 Jean-Pierre Charras, jp.charras at wanadoo.fr
5  * Copyright (C) 2012 SoftPLC Corporation, Dick Hollenbeck <dick@softplc.com>
6  * Copyright (C) 2011 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 <bezier_curves.h>
28 #include <base_units.h>
29 #include <convert_basic_shapes_to_polygon.h>
30 #include <eda_draw_frame.h>
31 #include <geometry/shape_simple.h>
32 #include <geometry/shape_segment.h>
33 #include <geometry/shape_circle.h>
34 #include <macros.h>
35 #include <math/util.h>      // for KiROUND
36 #include <eda_shape.h>
37 #include <plotters/plotter.h>
38 
39 
EDA_SHAPE(SHAPE_T aType,int aLineWidth,FILL_T aFill,bool eeWinding)40 EDA_SHAPE::EDA_SHAPE( SHAPE_T aType, int aLineWidth, FILL_T aFill, bool eeWinding ) :
41     m_endsSwapped( false ),
42     m_shape( aType ),
43     m_width( aLineWidth ),
44     m_fill( aFill ),
45     m_editState( 0 ),
46     m_eeWinding( eeWinding )
47 {
48 }
49 
50 
~EDA_SHAPE()51 EDA_SHAPE::~EDA_SHAPE()
52 {
53 }
54 
55 
ShowShape() const56 wxString EDA_SHAPE::ShowShape() const
57 {
58     switch( m_shape )
59     {
60     case SHAPE_T::SEGMENT: return _( "Line" );
61     case SHAPE_T::RECT:    return _( "Rect" );
62     case SHAPE_T::ARC:     return _( "Arc" );
63     case SHAPE_T::CIRCLE:  return _( "Circle" );
64     case SHAPE_T::BEZIER:  return _( "Bezier Curve" );
65     case SHAPE_T::POLY:    return _( "Polygon" );
66     default:               return wxT( "??" );
67     }
68 }
69 
70 
SHAPE_T_asString() const71 wxString EDA_SHAPE::SHAPE_T_asString() const
72 {
73     switch( m_shape )
74     {
75     case SHAPE_T::SEGMENT: return "S_SEGMENT";
76     case SHAPE_T::RECT:    return "S_RECT";
77     case SHAPE_T::ARC:     return "S_ARC";
78     case SHAPE_T::CIRCLE:  return "S_CIRCLE";
79     case SHAPE_T::POLY:    return "S_POLYGON";
80     case SHAPE_T::BEZIER:  return "S_CURVE";
81     case SHAPE_T::LAST:    return "!S_LAST!";  // Synthetic value, but if we come across it then
82                                                // we're going to want to know.
83     }
84 
85     return wxEmptyString;  // Just to quiet GCC.
86 }
87 
88 
setPosition(const wxPoint & aPos)89 void EDA_SHAPE::setPosition( const wxPoint& aPos )
90 {
91     move( aPos - getPosition() );
92 }
93 
94 
getPosition() const95 wxPoint EDA_SHAPE::getPosition() const
96 {
97     if( m_shape == SHAPE_T::ARC )
98         return getCenter();
99     else if( m_shape == SHAPE_T::POLY )
100         return (wxPoint) m_poly.CVertex( 0 );
101     else
102         return m_start;
103 }
104 
105 
GetLength() const106 double EDA_SHAPE::GetLength() const
107 {
108     double length = 0.0;
109 
110     switch( m_shape )
111     {
112     case SHAPE_T::BEZIER:
113         for( size_t ii = 1; ii < m_bezierPoints.size(); ++ii )
114             length += GetLineLength( m_bezierPoints[ ii - 1], m_bezierPoints[ii] );
115 
116         return length;
117 
118     case SHAPE_T::SEGMENT:
119         return GetLineLength( GetStart(), GetEnd() );
120 
121     case SHAPE_T::POLY:
122         for( int ii = 0; ii < m_poly.COutline( 0 ).SegmentCount(); ii++ )
123             length += m_poly.COutline( 0 ).CSegment( ii ).Length();
124 
125         return length;
126 
127     case SHAPE_T::ARC:
128         return 2 * M_PI * GetRadius() * ( GetArcAngle() / 3600.0 );
129 
130     default:
131         UNIMPLEMENTED_FOR( SHAPE_T_asString() );
132         return 0.0;
133     }
134 }
135 
136 
move(const wxPoint & aMoveVector)137 void EDA_SHAPE::move( const wxPoint& aMoveVector )
138 {
139     switch ( m_shape )
140     {
141     case SHAPE_T::ARC:
142     case SHAPE_T::SEGMENT:
143     case SHAPE_T::RECT:
144     case SHAPE_T::CIRCLE:
145         m_start += aMoveVector;
146         m_end += aMoveVector;
147         m_arcCenter += aMoveVector;
148         break;
149 
150     case SHAPE_T::POLY:
151         m_poly.Move( VECTOR2I( aMoveVector ) );
152         break;
153 
154     case SHAPE_T::BEZIER:
155         m_start += aMoveVector;
156         m_end += aMoveVector;
157         m_bezierC1 += aMoveVector;
158         m_bezierC2 += aMoveVector;
159 
160         for( wxPoint& pt : m_bezierPoints)
161             pt += aMoveVector;
162 
163         break;
164 
165     default:
166         UNIMPLEMENTED_FOR( SHAPE_T_asString() );
167         break;
168     }
169 }
170 
171 
scale(double aScale)172 void EDA_SHAPE::scale( double aScale )
173 {
174     auto scalePt = [&]( wxPoint& pt )
175                    {
176                        pt.x = KiROUND( pt.x * aScale );
177                        pt.y = KiROUND( pt.y * aScale );
178                    };
179 
180     switch( m_shape )
181     {
182     case SHAPE_T::ARC:
183     case SHAPE_T::SEGMENT:
184     case SHAPE_T::RECT:
185         scalePt( m_start );
186         scalePt( m_end );
187         scalePt( m_arcCenter );
188         break;
189 
190     case SHAPE_T::CIRCLE: //  ring or circle
191         scalePt( m_start );
192         m_end.x = m_start.x + KiROUND( GetRadius() * aScale );
193         m_end.y = m_start.y;
194         break;
195 
196     case SHAPE_T::POLY: // polygon
197     {
198         std::vector<wxPoint> pts;
199 
200         for( const VECTOR2I& pt : m_poly.Outline( 0 ).CPoints() )
201         {
202             pts.emplace_back( pt );
203             scalePt( pts.back() );
204         }
205 
206         SetPolyPoints( pts );
207     }
208         break;
209 
210     case SHAPE_T::BEZIER:
211         scalePt( m_start );
212         scalePt( m_end );
213         scalePt( m_bezierC1 );
214         scalePt( m_bezierC2 );
215         break;
216 
217     default:
218         UNIMPLEMENTED_FOR( SHAPE_T_asString() );
219         break;
220     }
221 }
222 
223 
rotate(const wxPoint & aRotCentre,double aAngle)224 void EDA_SHAPE::rotate( const wxPoint& aRotCentre, double aAngle )
225 {
226     switch( m_shape )
227     {
228     case SHAPE_T::SEGMENT:
229     case SHAPE_T::CIRCLE:
230         RotatePoint( &m_start, aRotCentre, aAngle );
231         RotatePoint( &m_end, aRotCentre, aAngle );
232         break;
233 
234     case SHAPE_T::ARC:
235         RotatePoint( &m_start, aRotCentre, aAngle );
236         RotatePoint( &m_end, aRotCentre, aAngle );
237         RotatePoint( &m_arcCenter, aRotCentre, aAngle );
238         break;
239 
240     case SHAPE_T::RECT:
241         if( KiROUND( aAngle ) % 900 == 0 )
242         {
243             RotatePoint( &m_start, aRotCentre, aAngle );
244             RotatePoint( &m_end, aRotCentre, aAngle );
245             break;
246         }
247 
248         // Convert non-cartesian-rotated rect to a diamond
249         m_shape = SHAPE_T::POLY;
250         m_poly.RemoveAllContours();
251         m_poly.NewOutline();
252         m_poly.Append( m_start );
253         m_poly.Append( m_end.x, m_start.y );
254         m_poly.Append( m_end );
255         m_poly.Append( m_start.x, m_end.y );
256 
257         KI_FALLTHROUGH;
258 
259     case SHAPE_T::POLY:
260         m_poly.Rotate( -DECIDEG2RAD( aAngle ), VECTOR2I( aRotCentre ) );
261         break;
262 
263     case SHAPE_T::BEZIER:
264         RotatePoint( &m_start, aRotCentre, aAngle);
265         RotatePoint( &m_end, aRotCentre, aAngle);
266         RotatePoint( &m_bezierC1, aRotCentre, aAngle);
267         RotatePoint( &m_bezierC2, aRotCentre, aAngle);
268 
269         for( wxPoint& pt : m_bezierPoints )
270             RotatePoint( &pt, aRotCentre, aAngle);
271 
272         break;
273 
274     default:
275         UNIMPLEMENTED_FOR( SHAPE_T_asString() );
276         break;
277     }
278 }
279 
280 
flip(const wxPoint & aCentre,bool aFlipLeftRight)281 void EDA_SHAPE::flip( const wxPoint& aCentre, bool aFlipLeftRight )
282 {
283     switch ( m_shape )
284     {
285     case SHAPE_T::SEGMENT:
286     case SHAPE_T::RECT:
287         if( aFlipLeftRight )
288         {
289             m_start.x = aCentre.x - ( m_start.x - aCentre.x );
290             m_end.x   = aCentre.x - ( m_end.x - aCentre.x );
291         }
292         else
293         {
294             m_start.y = aCentre.y - ( m_start.y - aCentre.y );
295             m_end.y   = aCentre.y - ( m_end.y - aCentre.y );
296         }
297 
298         std::swap( m_start, m_end );
299         break;
300 
301     case SHAPE_T::CIRCLE:
302         if( aFlipLeftRight )
303         {
304             m_start.x = aCentre.x - ( m_start.x - aCentre.x );
305             m_end.x   = aCentre.x - ( m_end.x - aCentre.x );
306         }
307         else
308         {
309             m_start.y = aCentre.y - ( m_start.y - aCentre.y );
310             m_end.y   = aCentre.y - ( m_end.y - aCentre.y );
311         }
312         break;
313 
314     case SHAPE_T::ARC:
315         if( aFlipLeftRight )
316         {
317             m_start.x     = aCentre.x - ( m_start.x - aCentre.x );
318             m_end.x       = aCentre.x - ( m_end.x - aCentre.x );
319             m_arcCenter.x = aCentre.x - ( m_arcCenter.x - aCentre.x );
320         }
321         else
322         {
323             m_start.y     = aCentre.y - ( m_start.y - aCentre.y );
324             m_end.y       = aCentre.y - ( m_end.y - aCentre.y );
325             m_arcCenter.y = aCentre.y - ( m_arcCenter.y - aCentre.y );
326         }
327 
328         std::swap( m_start, m_end );
329         break;
330 
331     case SHAPE_T::POLY:
332         m_poly.Mirror( aFlipLeftRight, !aFlipLeftRight, VECTOR2I( aCentre ) );
333         break;
334 
335     case SHAPE_T::BEZIER:
336         if( aFlipLeftRight )
337         {
338             m_start.x    = aCentre.x - ( m_start.x - aCentre.x );
339             m_end.x      = aCentre.x - ( m_end.x - aCentre.x );
340             m_bezierC1.x = aCentre.x - ( m_bezierC1.x - aCentre.x );
341             m_bezierC2.x = aCentre.x - ( m_bezierC2.x - aCentre.x );
342         }
343         else
344         {
345             m_start.y    = aCentre.y - ( m_start.y - aCentre.y );
346             m_end.y      = aCentre.y - ( m_end.y - aCentre.y );
347             m_bezierC1.y = aCentre.y - ( m_bezierC1.y - aCentre.y );
348             m_bezierC2.y = aCentre.y - ( m_bezierC2.y - aCentre.y );
349         }
350 
351         // Rebuild the poly points shape
352         {
353             std::vector<wxPoint> ctrlPoints = { m_start, m_bezierC1, m_bezierC2, m_end };
354             BEZIER_POLY converter( ctrlPoints );
355             converter.GetPoly( m_bezierPoints, m_width );
356         }
357         break;
358 
359     default:
360         UNIMPLEMENTED_FOR( SHAPE_T_asString() );
361         break;
362     }
363 }
364 
365 
RebuildBezierToSegmentsPointsList(int aMinSegLen)366 void EDA_SHAPE::RebuildBezierToSegmentsPointsList( int aMinSegLen )
367 {
368     // Has meaning only for S_CURVE DRAW_SEGMENT shape
369     if( m_shape != SHAPE_T::BEZIER )
370     {
371         m_bezierPoints.clear();
372         return;
373     }
374 
375     // Rebuild the m_BezierPoints vertex list that approximate the Bezier curve
376     m_bezierPoints = buildBezierToSegmentsPointsList( aMinSegLen );
377 }
378 
379 
buildBezierToSegmentsPointsList(int aMinSegLen) const380 const std::vector<wxPoint> EDA_SHAPE::buildBezierToSegmentsPointsList( int aMinSegLen  ) const
381 {
382     std::vector<wxPoint> bezierPoints;
383 
384     // Rebuild the m_BezierPoints vertex list that approximate the Bezier curve
385     std::vector<wxPoint> ctrlPoints = { m_start, m_bezierC1, m_bezierC2, m_end };
386     BEZIER_POLY converter( ctrlPoints );
387     converter.GetPoly( bezierPoints, aMinSegLen );
388 
389     return bezierPoints;
390 }
391 
392 
getCenter() const393 wxPoint EDA_SHAPE::getCenter() const
394 {
395     switch( m_shape )
396     {
397     case SHAPE_T::ARC:
398         return m_arcCenter;
399 
400     case SHAPE_T::CIRCLE:
401         return m_start;
402 
403     case SHAPE_T::SEGMENT:
404         // Midpoint of the line
405         return ( m_start + m_end ) / 2;
406 
407     case SHAPE_T::POLY:
408     case SHAPE_T::RECT:
409     case SHAPE_T::BEZIER:
410         return getBoundingBox().Centre();
411 
412     default:
413         UNIMPLEMENTED_FOR( SHAPE_T_asString() );
414         return wxPoint();
415     }
416 }
417 
418 
SetCenter(const wxPoint & aCenter)419 void EDA_SHAPE::SetCenter( const wxPoint& aCenter )
420 {
421     switch( m_shape )
422     {
423     case SHAPE_T::ARC:
424         m_arcCenter = aCenter;
425         break;
426 
427     case SHAPE_T::CIRCLE:
428         m_start = aCenter;
429         break;
430 
431     default:
432         UNIMPLEMENTED_FOR( SHAPE_T_asString() );
433     }
434 }
435 
436 
GetArcMid() const437 wxPoint EDA_SHAPE::GetArcMid() const
438 {
439     wxPoint mid = m_start;
440     RotatePoint( &mid, m_arcCenter, -GetArcAngle() / 2.0 );
441     return mid;
442 }
443 
444 
CalcArcAngles(double & aStartAngle,double & aEndAngle) const445 void EDA_SHAPE::CalcArcAngles( double& aStartAngle, double& aEndAngle ) const
446 {
447     VECTOR2D startRadial( GetStart() - getCenter() );
448     VECTOR2D endRadial( GetEnd() - getCenter() );
449 
450     aStartAngle = 180.0 / M_PI * atan2( startRadial.y, startRadial.x );
451     aEndAngle = 180.0 / M_PI * atan2( endRadial.y, endRadial.x );
452 
453     if( aEndAngle == aStartAngle )
454         aEndAngle = aStartAngle + 360.0;   // ring, not null
455 
456     if( aStartAngle > aEndAngle )
457     {
458         if( aEndAngle < 0 )
459             aEndAngle = NormalizeAngleDegrees( aEndAngle, 0.0, 360.0 );
460         else
461             aStartAngle = NormalizeAngleDegrees( aStartAngle, -360.0, 0.0 );
462     }
463 }
464 
465 
GetRadius() const466 int EDA_SHAPE::GetRadius() const
467 {
468     double radius = 0.0;
469 
470     switch( m_shape )
471     {
472     case SHAPE_T::ARC:
473         radius = GetLineLength( m_arcCenter, m_start );
474         break;
475 
476     case SHAPE_T::CIRCLE:
477         radius = GetLineLength( m_start, m_end );
478         break;
479 
480     default:
481         UNIMPLEMENTED_FOR( SHAPE_T_asString() );
482     }
483 
484     // don't allow degenerate circles/arcs
485     return std::max( 1, KiROUND( radius ) );
486 }
487 
488 
SetArcGeometry(const wxPoint & aStart,const wxPoint & aMid,const wxPoint & aEnd)489 void EDA_SHAPE::SetArcGeometry( const wxPoint& aStart, const wxPoint& aMid, const wxPoint& aEnd )
490 {
491     m_start = aStart;
492     m_end = aEnd;
493     m_arcCenter = CalcArcCenter( aStart, aMid, aEnd );
494     m_endsSwapped = false;
495 
496     /**
497      * If the input winding doesn't match our internal winding, the calculated midpoint will end up
498      * on the other side of the arc.  In this case, we need to flip the start/end points and flag this
499      * change for the system
500      */
501     wxPoint new_mid = GetArcMid();
502     VECTOR2D dist( new_mid - aMid );
503     VECTOR2D dist2( new_mid - m_arcCenter );
504 
505     if( dist.SquaredEuclideanNorm() > dist2.SquaredEuclideanNorm() )
506     {
507         std::swap( m_start, m_end );
508         m_endsSwapped = true;
509     }
510 
511 }
512 
513 
GetArcAngle() const514 double EDA_SHAPE::GetArcAngle() const
515 {
516     double startAngle;
517     double endAngle;
518 
519     CalcArcAngles( startAngle, endAngle );
520 
521     return ( endAngle - startAngle ) * 10;
522 }
523 
524 
SetArcAngleAndEnd(double aAngle,bool aCheckNegativeAngle)525 void EDA_SHAPE::SetArcAngleAndEnd( double aAngle, bool aCheckNegativeAngle )
526 {
527     m_end = m_start;
528     RotatePoint( &m_end, m_arcCenter, -NormalizeAngle360Max( aAngle ) );
529 
530     if( aCheckNegativeAngle && aAngle < 0 )
531     {
532         std::swap( m_start, m_end );
533         m_endsSwapped = true;
534     }
535 }
536 
537 
ShapeGetMsgPanelInfo(EDA_DRAW_FRAME * aFrame,std::vector<MSG_PANEL_ITEM> & aList)538 void EDA_SHAPE::ShapeGetMsgPanelInfo( EDA_DRAW_FRAME* aFrame, std::vector<MSG_PANEL_ITEM>& aList )
539 {
540     EDA_UNITS         units = aFrame->GetUserUnits();
541     ORIGIN_TRANSFORMS originTransforms = aFrame->GetOriginTransforms();
542     wxString          msg;
543 
544     wxString shape = _( "Shape" );
545 
546     switch( m_shape )
547     {
548     case SHAPE_T::CIRCLE:
549         aList.emplace_back( shape, _( "Circle" ) );
550 
551         msg = MessageTextFromValue( units, GetRadius() );
552         aList.emplace_back( _( "Radius" ), msg );
553         break;
554 
555     case SHAPE_T::ARC:
556         aList.emplace_back( shape, _( "Arc" ) );
557 
558         msg.Printf( wxT( "%.1f" ), GetArcAngle() / 10.0 );
559         aList.emplace_back( _( "Angle" ), msg );
560 
561         msg = MessageTextFromValue( units, GetRadius() );
562         aList.emplace_back( _( "Radius" ), msg );
563         break;
564 
565     case SHAPE_T::BEZIER:
566         aList.emplace_back( shape, _( "Curve" ) );
567 
568         msg = MessageTextFromValue( units, GetLength() );
569         aList.emplace_back( _( "Length" ), msg );
570         break;
571 
572     case SHAPE_T::POLY:
573         aList.emplace_back( shape, _( "Polygon" ) );
574 
575         msg.Printf( "%d", GetPolyShape().Outline(0).PointCount() );
576         aList.emplace_back( _( "Points" ), msg );
577         break;
578 
579     case SHAPE_T::RECT:
580         aList.emplace_back( shape, _( "Rectangle" ) );
581 
582         msg = MessageTextFromValue( units, std::abs( GetEnd().x - GetStart().x ) );
583         aList.emplace_back( _( "Width" ), msg );
584 
585         msg = MessageTextFromValue( units, std::abs( GetEnd().y - GetStart().y ) );
586         aList.emplace_back( _( "Height" ), msg );
587         break;
588 
589     case SHAPE_T::SEGMENT:
590     {
591         aList.emplace_back( shape, _( "Segment" ) );
592 
593         msg = MessageTextFromValue( units, GetLineLength( GetStart(), GetEnd() ) );
594         aList.emplace_back( _( "Length" ), msg );
595 
596         // angle counter-clockwise from 3'o-clock
597         const double deg = RAD2DEG( atan2( (double)( GetStart().y - GetEnd().y ),
598                                            (double)( GetEnd().x - GetStart().x ) ) );
599         aList.emplace_back( _( "Angle" ), wxString::Format( "%.1f", deg ) );
600     }
601         break;
602 
603     default:
604         aList.emplace_back( shape, _( "Unrecognized" ) );
605         break;
606     }
607 
608     aList.emplace_back( _( "Line width" ), MessageTextFromValue( units, m_width ) );
609 }
610 
611 
getBoundingBox() const612 const EDA_RECT EDA_SHAPE::getBoundingBox() const
613 {
614     EDA_RECT bbox;
615 
616     switch( m_shape )
617     {
618     case SHAPE_T::RECT:
619         for( wxPoint& pt : GetRectCorners() )
620             bbox.Merge( pt );
621 
622         break;
623 
624     case SHAPE_T::SEGMENT:
625         bbox.SetOrigin( GetStart() );
626         bbox.SetEnd( GetEnd() );
627         break;
628 
629     case SHAPE_T::CIRCLE:
630         bbox.SetOrigin( GetStart() );
631         bbox.Inflate( GetRadius() );
632         break;
633 
634     case SHAPE_T::ARC:
635         computeArcBBox( bbox );
636         break;
637 
638     case SHAPE_T::POLY:
639         if( m_poly.IsEmpty() )
640             break;
641 
642         for( auto iter = m_poly.CIterate(); iter; iter++ )
643         {
644             wxPoint pt( iter->x, iter->y );
645 
646             RotatePoint( &pt, getParentOrientation() );
647             pt += getParentPosition();
648 
649             bbox.Merge( pt );
650         }
651 
652         break;
653 
654     case SHAPE_T::BEZIER:
655         bbox.SetOrigin( GetStart() );
656         bbox.Merge( GetBezierC1() );
657         bbox.Merge( GetBezierC2() );
658         bbox.Merge( GetEnd() );
659         break;
660 
661     default:
662         UNIMPLEMENTED_FOR( SHAPE_T_asString() );
663         break;
664     }
665 
666     bbox.Inflate( std::max( 0, m_width / 2 ) );
667     bbox.Normalize();
668 
669     return bbox;
670 }
671 
672 
hitTest(const wxPoint & aPosition,int aAccuracy) const673 bool EDA_SHAPE::hitTest( const wxPoint& aPosition, int aAccuracy ) const
674 {
675     int maxdist = aAccuracy;
676 
677     if( m_width > 0 )
678         maxdist += m_width / 2;
679 
680     switch( m_shape )
681     {
682     case SHAPE_T::CIRCLE:
683     {
684         int radius = GetRadius();
685         int dist   = KiROUND( EuclideanNorm( aPosition - getCenter() ) );
686 
687         if( IsFilled() )
688             return dist <= radius + maxdist;          // Filled circle hit-test
689         else
690             return abs( radius - dist ) <= maxdist;   // Ring hit-test
691     }
692 
693     case SHAPE_T::ARC:
694     {
695         if( EuclideanNorm( aPosition - m_start ) <= maxdist )
696             return true;
697 
698         if( EuclideanNorm( aPosition - m_end ) <= maxdist )
699             return true;
700 
701         wxPoint relPos = aPosition - getCenter();
702         int     radius = GetRadius();
703         int     dist   = KiROUND( EuclideanNorm( relPos ) );
704 
705         if( abs( radius - dist ) <= maxdist )
706         {
707             double startAngle;
708             double endAngle;
709             CalcArcAngles( startAngle, endAngle );
710 
711             if( m_eeWinding && NormalizeAngleDegrees( startAngle - endAngle, -180.0, 180.0 ) > 0 )
712                 std::swap( startAngle, endAngle );
713 
714             double relPosAngle = 180.0 / M_PI * atan2( relPos.y, relPos.x );
715 
716             startAngle = NormalizeAngleDegrees( startAngle, 0.0, 360.0 );
717             endAngle = NormalizeAngleDegrees( endAngle, 0.0, 360.0 );
718             relPosAngle = NormalizeAngleDegrees( relPosAngle, 0.0, 360.0 );
719 
720             if( endAngle > startAngle )
721                 return relPosAngle >= startAngle && relPosAngle <= endAngle;
722             else
723                 return relPosAngle >= startAngle || relPosAngle <= endAngle;
724         }
725 
726         return false;
727     }
728 
729     case SHAPE_T::BEZIER:
730         const_cast<EDA_SHAPE*>( this )->RebuildBezierToSegmentsPointsList( m_width );
731 
732         for( unsigned int i= 1; i < m_bezierPoints.size(); i++)
733         {
734             if( TestSegmentHit( aPosition, m_bezierPoints[ i - 1], m_bezierPoints[i], maxdist ) )
735                 return true;
736         }
737 
738         return false;
739 
740     case SHAPE_T::SEGMENT:
741         return TestSegmentHit( aPosition, GetStart(), GetEnd(), maxdist );
742 
743     case SHAPE_T::RECT:
744         if( IsFilled() )            // Filled rect hit-test
745         {
746             SHAPE_POLY_SET poly;
747             poly.NewOutline();
748 
749             for( const wxPoint& pt : GetRectCorners() )
750                 poly.Append( pt );
751 
752             return poly.Collide( VECTOR2I( aPosition ), maxdist );
753         }
754         else                        // Open rect hit-test
755         {
756             std::vector<wxPoint> pts = GetRectCorners();
757 
758             return TestSegmentHit( aPosition, pts[0], pts[1], maxdist )
759                     || TestSegmentHit( aPosition, pts[1], pts[2], maxdist )
760                     || TestSegmentHit( aPosition, pts[2], pts[3], maxdist )
761                     || TestSegmentHit( aPosition, pts[3], pts[0], maxdist );
762         }
763 
764     case SHAPE_T::POLY:
765         if( IsFilled() )
766         {
767             return m_poly.Collide( VECTOR2I( aPosition ), maxdist );
768         }
769         else
770         {
771             SHAPE_POLY_SET::VERTEX_INDEX dummy;
772             return m_poly.CollideEdge( VECTOR2I( aPosition ), dummy, maxdist );
773         }
774 
775     default:
776         UNIMPLEMENTED_FOR( SHAPE_T_asString() );
777         return false;
778     }
779 }
780 
781 
hitTest(const EDA_RECT & aRect,bool aContained,int aAccuracy) const782 bool EDA_SHAPE::hitTest( const EDA_RECT& aRect, bool aContained, int aAccuracy ) const
783 {
784     EDA_RECT arect = aRect;
785     arect.Normalize();
786     arect.Inflate( aAccuracy );
787 
788     EDA_RECT arcRect;
789     EDA_RECT bb = getBoundingBox();
790 
791     switch( m_shape )
792     {
793     case SHAPE_T::CIRCLE:
794         // Test if area intersects or contains the circle:
795         if( aContained )
796         {
797             return arect.Contains( bb );
798         }
799         else
800         {
801             // If the rectangle does not intersect the bounding box, this is a much quicker test
802             if( !aRect.Intersects( bb ) )
803             {
804                 return false;
805             }
806             else
807             {
808                 return arect.IntersectsCircleEdge( getCenter(), GetRadius(), GetWidth() );
809             }
810         }
811 
812     case SHAPE_T::ARC:
813         // Test for full containment of this arc in the rect
814         if( aContained )
815         {
816             return arect.Contains( bb );
817         }
818         // Test if the rect crosses the arc
819         else
820         {
821             arcRect = bb.Common( arect );
822 
823             /* All following tests must pass:
824              * 1. Rectangle must intersect arc BoundingBox
825              * 2. Rectangle must cross the outside of the arc
826              */
827             return arcRect.Intersects( arect ) &&
828                    arcRect.IntersectsCircleEdge( getCenter(), GetRadius(), GetWidth() );
829         }
830 
831     case SHAPE_T::RECT:
832         if( aContained )
833         {
834             return arect.Contains( bb );
835         }
836         else
837         {
838             std::vector<wxPoint> pts = GetRectCorners();
839 
840             // Account for the width of the lines
841             arect.Inflate( GetWidth() / 2 );
842             return ( arect.Intersects( pts[0], pts[1] )
843                   || arect.Intersects( pts[1], pts[2] )
844                   || arect.Intersects( pts[2], pts[3] )
845                   || arect.Intersects( pts[3], pts[0] ) );
846         }
847 
848     case SHAPE_T::SEGMENT:
849         if( aContained )
850         {
851             return arect.Contains( GetStart() ) && aRect.Contains( GetEnd() );
852         }
853         else
854         {
855             // Account for the width of the line
856             arect.Inflate( GetWidth() / 2 );
857             return arect.Intersects( GetStart(), GetEnd() );
858         }
859 
860     case SHAPE_T::POLY:
861         if( aContained )
862         {
863             return arect.Contains( bb );
864         }
865         else
866         {
867             // Fast test: if aRect is outside the polygon bounding box,
868             // rectangles cannot intersect
869             if( !arect.Intersects( bb ) )
870                 return false;
871 
872             // Account for the width of the line
873             arect.Inflate( GetWidth() / 2 );
874 
875             // Polygons in footprints use coordinates relative to the footprint.
876             // Therefore, instead of using m_poly, we make a copy which is translated
877             // to the actual location in the board.
878             double  orientation = 0.0;
879             wxPoint offset = getParentPosition();
880 
881             if( getParentOrientation() )
882                 orientation = -DECIDEG2RAD( getParentOrientation() );
883 
884             SHAPE_LINE_CHAIN poly = m_poly.Outline( 0 );
885             poly.Rotate( orientation );
886             poly.Move( offset );
887 
888             int count = poly.GetPointCount();
889 
890             for( int ii = 0; ii < count; ii++ )
891             {
892                 VECTOR2I vertex = poly.GetPoint( ii );
893 
894                 // Test if the point is within aRect
895                 if( arect.Contains( ( wxPoint ) vertex ) )
896                     return true;
897 
898                 if( ii + 1 < count )
899                 {
900                     VECTOR2I vertexNext = poly.GetPoint( ii + 1 );
901 
902                     // Test if this edge intersects aRect
903                     if( arect.Intersects( ( wxPoint ) vertex, ( wxPoint ) vertexNext ) )
904                         return true;
905                 }
906                 else if( poly.IsClosed() )
907                 {
908                     VECTOR2I vertexNext = poly.GetPoint( 0 );
909 
910                     // Test if this edge intersects aRect
911                     if( arect.Intersects( ( wxPoint ) vertex, ( wxPoint ) vertexNext ) )
912                         return true;
913                 }
914             }
915 
916             return false;
917         }
918 
919     case SHAPE_T::BEZIER:
920         if( aContained )
921         {
922             return arect.Contains( bb );
923         }
924         else
925         {
926             // Fast test: if aRect is outside the polygon bounding box,
927             // rectangles cannot intersect
928             if( !arect.Intersects( bb ) )
929                 return false;
930 
931             // Account for the width of the line
932             arect.Inflate( GetWidth() / 2 );
933             unsigned count = m_bezierPoints.size();
934 
935             for( unsigned ii = 1; ii < count; ii++ )
936             {
937                 wxPoint vertex = m_bezierPoints[ ii - 1];
938                 wxPoint vertexNext = m_bezierPoints[ii];
939 
940                 // Test if the point is within aRect
941                 if( arect.Contains( ( wxPoint ) vertex ) )
942                     return true;
943 
944                 // Test if this edge intersects aRect
945                 if( arect.Intersects( vertex, vertexNext ) )
946                     return true;
947             }
948 
949             return false;
950         }
951 
952     default:
953         UNIMPLEMENTED_FOR( SHAPE_T_asString() );
954         return false;
955     }
956 }
957 
958 
GetRectCorners() const959 std::vector<wxPoint> EDA_SHAPE::GetRectCorners() const
960 {
961     std::vector<wxPoint> pts;
962     wxPoint              topLeft = GetStart();
963     wxPoint              botRight = GetEnd();
964 
965     // Un-rotate rect topLeft and botRight
966     if( KiROUND( getParentOrientation() ) % 900 != 0 )
967     {
968         topLeft -= getParentPosition();
969         RotatePoint( &topLeft, -getParentOrientation() );
970 
971         botRight -= getParentPosition();
972         RotatePoint( &botRight, -getParentOrientation() );
973     }
974 
975     // Set up the un-rotated 4 corners
976     pts.emplace_back( topLeft );
977     pts.emplace_back( botRight.x, topLeft.y );
978     pts.emplace_back( botRight );
979     pts.emplace_back( topLeft.x, botRight.y );
980 
981     // Now re-rotate the 4 corners to get a diamond
982     if( KiROUND( getParentOrientation() ) % 900 != 0 )
983     {
984         for( wxPoint& pt : pts )
985         {
986             RotatePoint( &pt, getParentOrientation() );
987             pt += getParentPosition();
988         }
989     }
990 
991     return pts;
992 }
993 
994 
computeArcBBox(EDA_RECT & aBBox) const995 void EDA_SHAPE::computeArcBBox( EDA_RECT& aBBox ) const
996 {
997     wxPoint start = m_start;
998     wxPoint end = m_end;
999     double  t1, t2;
1000 
1001     CalcArcAngles( t1, t2 );
1002 
1003     if( m_eeWinding && NormalizeAngleDegrees( t1 - t2, -180.0, 180.0 ) > 0 )
1004         std::swap( start, end );
1005 
1006     // Do not include the center, which is not necessarily inside the BB of an arc with a small
1007     // included angle
1008     aBBox.SetOrigin( start );
1009     aBBox.Merge( end );
1010 
1011     // Determine the starting quarter
1012     // 0 right-bottom
1013     // 1 left-bottom
1014     // 2 left-top
1015     // 3 right-top
1016     unsigned int quarter;
1017 
1018     if( start.x < m_arcCenter.x )
1019     {
1020         if( start.y <= m_arcCenter.y )
1021             quarter = 2;
1022         else
1023             quarter = 1;
1024     }
1025     else if( start.x == m_arcCenter.x )
1026     {
1027         if( start.y < m_arcCenter.y )
1028             quarter = 3;
1029         else
1030             quarter = 1;
1031     }
1032     else
1033     {
1034         if( start.y < m_arcCenter.y )
1035             quarter = 3;
1036         else
1037             quarter = 0;
1038     }
1039 
1040     int      radius = GetRadius();
1041     VECTOR2I startRadial = start - m_arcCenter;
1042     VECTOR2I endRadial = end - m_arcCenter;
1043     double   angleStart = ArcTangente( startRadial.y, startRadial.x );
1044     double   arcAngle = RAD2DECIDEG( endRadial.Angle() - startRadial.Angle() );
1045     int      angle = (int) NormalizeAnglePos( angleStart ) % 900 + NormalizeAnglePos( arcAngle );
1046 
1047     while( angle > 900 )
1048     {
1049         switch( quarter )
1050         {
1051         case 0: aBBox.Merge( wxPoint( m_arcCenter.x, m_arcCenter.y + radius ) ); break;  // down
1052         case 1: aBBox.Merge( wxPoint( m_arcCenter.x - radius, m_arcCenter.y ) ); break;  // left
1053         case 2: aBBox.Merge( wxPoint( m_arcCenter.x, m_arcCenter.y - radius ) ); break;  // up
1054         case 3: aBBox.Merge( wxPoint( m_arcCenter.x + radius, m_arcCenter.y ) ); break;  // right
1055         }
1056 
1057         ++quarter %= 4;
1058         angle -= 900;
1059     }
1060 }
1061 
1062 
SetPolyPoints(const std::vector<wxPoint> & aPoints)1063 void EDA_SHAPE::SetPolyPoints( const std::vector<wxPoint>& aPoints )
1064 {
1065     m_poly.RemoveAllContours();
1066     m_poly.NewOutline();
1067 
1068     for ( const wxPoint& p : aPoints )
1069         m_poly.Append( p.x, p.y );
1070 }
1071 
1072 
MakeEffectiveShapes() const1073 std::vector<SHAPE*> EDA_SHAPE::MakeEffectiveShapes() const
1074 {
1075     std::vector<SHAPE*> effectiveShapes;
1076 
1077     switch( m_shape )
1078     {
1079     case SHAPE_T::ARC:
1080         effectiveShapes.emplace_back( new SHAPE_ARC( m_arcCenter, m_start, GetArcAngle() / 10.0,
1081                                                      m_width ) );
1082         break;
1083 
1084     case SHAPE_T::SEGMENT:
1085         effectiveShapes.emplace_back( new SHAPE_SEGMENT( m_start, m_end, m_width ) );
1086         break;
1087 
1088     case SHAPE_T::RECT:
1089     {
1090         std::vector<wxPoint> pts = GetRectCorners();
1091 
1092         if( IsFilled() )
1093             effectiveShapes.emplace_back( new SHAPE_SIMPLE( pts ) );
1094 
1095         if( m_width > 0 || !IsFilled() )
1096         {
1097             effectiveShapes.emplace_back( new SHAPE_SEGMENT( pts[0], pts[1], m_width ) );
1098             effectiveShapes.emplace_back( new SHAPE_SEGMENT( pts[1], pts[2], m_width ) );
1099             effectiveShapes.emplace_back( new SHAPE_SEGMENT( pts[2], pts[3], m_width ) );
1100             effectiveShapes.emplace_back( new SHAPE_SEGMENT( pts[3], pts[0], m_width ) );
1101         }
1102     }
1103         break;
1104 
1105     case SHAPE_T::CIRCLE:
1106     {
1107         if( IsFilled() )
1108             effectiveShapes.emplace_back( new SHAPE_CIRCLE( getCenter(), GetRadius() ) );
1109 
1110         if( m_width > 0 || !IsFilled() )
1111         {
1112             // SHAPE_CIRCLE has no ConvertToPolyline() method, so use a 360.0 SHAPE_ARC
1113             SHAPE_ARC        circle( getCenter(), GetEnd(), 360.0 );
1114             SHAPE_LINE_CHAIN l = circle.ConvertToPolyline();
1115 
1116             for( int i = 0; i < l.SegmentCount(); i++ )
1117             {
1118                 effectiveShapes.emplace_back( new SHAPE_SEGMENT( l.Segment( i ).A, l.Segment( i ).B,
1119                                                                  m_width ) );
1120             }
1121         }
1122 
1123         break;
1124     }
1125 
1126     case SHAPE_T::BEZIER:
1127     {
1128         auto bezierPoints = buildBezierToSegmentsPointsList( GetWidth() );
1129         wxPoint start_pt = bezierPoints[0];
1130 
1131         for( unsigned int jj = 1; jj < bezierPoints.size(); jj++ )
1132         {
1133             wxPoint end_pt = bezierPoints[jj];
1134             effectiveShapes.emplace_back( new SHAPE_SEGMENT( start_pt, end_pt, m_width ) );
1135             start_pt = end_pt;
1136         }
1137 
1138         break;
1139     }
1140 
1141     case SHAPE_T::POLY:
1142     {
1143         SHAPE_LINE_CHAIN l = GetPolyShape().COutline( 0 );
1144 
1145         l.Rotate( -DECIDEG2RAD( getParentOrientation() ) );
1146         l.Move( getParentPosition() );
1147 
1148         if( IsFilled() )
1149             effectiveShapes.emplace_back( new SHAPE_SIMPLE( l ) );
1150 
1151         if( m_width > 0 || !IsFilled() )
1152         {
1153             for( int i = 0; i < l.SegmentCount(); i++ )
1154                 effectiveShapes.emplace_back( new SHAPE_SEGMENT( l.Segment( i ), m_width ) );
1155         }
1156     }
1157         break;
1158 
1159     default:
1160         UNIMPLEMENTED_FOR( SHAPE_T_asString() );
1161         break;
1162     }
1163 
1164     return effectiveShapes;
1165 }
1166 
1167 
DupPolyPointsList(std::vector<wxPoint> & aBuffer) const1168 void EDA_SHAPE::DupPolyPointsList( std::vector<wxPoint>& aBuffer ) const
1169 {
1170     if( m_poly.OutlineCount() )
1171     {
1172         int pointCount = m_poly.COutline( 0 ).PointCount();
1173 
1174         if( pointCount )
1175         {
1176             aBuffer.reserve( pointCount );
1177 
1178             for ( auto iter = m_poly.CIterate(); iter; iter++ )
1179                 aBuffer.emplace_back( iter->x, iter->y );
1180         }
1181     }
1182 }
1183 
1184 
IsPolyShapeValid() const1185 bool EDA_SHAPE::IsPolyShapeValid() const
1186 {
1187     // return true if the polygonal shape is valid (has more than 2 points)
1188     if( GetPolyShape().OutlineCount() == 0 )
1189         return false;
1190 
1191     const SHAPE_LINE_CHAIN& outline = ( (SHAPE_POLY_SET&)GetPolyShape() ).Outline( 0 );
1192 
1193     return outline.PointCount() > 2;
1194 }
1195 
1196 
GetPointCount() const1197 int EDA_SHAPE::GetPointCount() const
1198 {
1199     // return the number of corners of the polygonal shape
1200     // this shape is expected to be only one polygon without hole
1201     if( GetPolyShape().OutlineCount() )
1202         return GetPolyShape().VertexCount( 0 );
1203 
1204     return 0;
1205 }
1206 
1207 
beginEdit(const wxPoint & aPosition)1208 void EDA_SHAPE::beginEdit( const wxPoint& aPosition )
1209 {
1210     switch( GetShape() )
1211     {
1212     case SHAPE_T::SEGMENT:
1213     case SHAPE_T::CIRCLE:
1214     case SHAPE_T::RECT:
1215         SetStart( aPosition );
1216         SetEnd( aPosition );
1217         break;
1218 
1219     case SHAPE_T::ARC:
1220         SetArcGeometry( aPosition, aPosition, aPosition );
1221         m_editState = 1;
1222         break;
1223 
1224     case SHAPE_T::POLY:
1225         m_poly.NewOutline();
1226         m_poly.Outline( 0 ).SetClosed( false );
1227 
1228         // Start and end of the first segment (co-located for now)
1229         m_poly.Outline( 0 ).Append( aPosition );
1230         m_poly.Outline( 0 ).Append( aPosition, true );
1231         break;
1232 
1233     default:
1234         UNIMPLEMENTED_FOR( SHAPE_T_asString() );
1235     }
1236 }
1237 
1238 
continueEdit(const wxPoint & aPosition)1239 bool EDA_SHAPE::continueEdit( const wxPoint& aPosition )
1240 {
1241     switch( GetShape() )
1242     {
1243     case SHAPE_T::ARC:
1244     case SHAPE_T::SEGMENT:
1245     case SHAPE_T::CIRCLE:
1246     case SHAPE_T::RECT:
1247         return false;
1248 
1249     case SHAPE_T::POLY:
1250     {
1251         SHAPE_LINE_CHAIN& poly = m_poly.Outline( 0 );
1252 
1253         // do not add zero-length segments
1254         if( poly.CPoint( poly.GetPointCount() - 2 ) != poly.CLastPoint() )
1255             poly.Append( aPosition, true );
1256     }
1257         return true;
1258 
1259     default:
1260         UNIMPLEMENTED_FOR( SHAPE_T_asString() );
1261         return false;
1262     }
1263 }
1264 
1265 
calcEdit(const wxPoint & aPosition)1266 void EDA_SHAPE::calcEdit( const wxPoint& aPosition )
1267 {
1268 #define sq( x ) pow( x, 2 )
1269 
1270     switch( GetShape() )
1271     {
1272     case SHAPE_T::SEGMENT:
1273     case SHAPE_T::CIRCLE:
1274     case SHAPE_T::RECT:
1275         SetEnd( aPosition );
1276         break;
1277 
1278     case SHAPE_T::ARC:
1279     {
1280         int radius = GetRadius();
1281 
1282         // Edit state 0: drawing: place start
1283         // Edit state 1: drawing: place end (center calculated for 90-degree subtended angle)
1284         // Edit state 2: point edit: move start (center calculated for invariant subtended angle)
1285         // Edit state 3: point edit: move end (center calculated for invariant subtended angle)
1286         // Edit state 4: point edit: move center
1287         // Edit state 5: point edit: move arc-mid-point
1288 
1289         switch( m_editState )
1290         {
1291         case 0:
1292             SetArcGeometry( aPosition, aPosition, aPosition );
1293             return;
1294 
1295         case 1:
1296             m_end = aPosition;
1297             radius = KiROUND( sqrt( sq( GetLineLength( m_start, m_end ) ) / 2.0 ) );
1298             break;
1299 
1300         case 2:
1301         case 3:
1302         {
1303             wxPoint v = m_start - m_end;
1304             double chordBefore = sq( v.x ) + sq( v.y );
1305 
1306             if( m_editState == 2 )
1307                 m_start = aPosition;
1308             else
1309                 m_end = aPosition;
1310 
1311             v = m_start - m_end;
1312             double chordAfter = sq( v.x ) + sq( v.y );
1313             double ratio = chordAfter / chordBefore;
1314 
1315             if( ratio != 0 )
1316             {
1317                 radius = std::max( int( sqrt( sq( radius ) * ratio ) ) + 1,
1318                                    int( sqrt( chordAfter ) / 2 ) + 1 );
1319             }
1320         }
1321             break;
1322 
1323         case 4:
1324         {
1325             double chordA = GetLineLength( m_start, aPosition );
1326             double chordB = GetLineLength( m_end, aPosition );
1327             radius = int( ( chordA + chordB ) / 2.0 ) + 1;
1328         }
1329             break;
1330 
1331         case 5:
1332             SetArcGeometry( GetStart(), aPosition, GetEnd() );
1333             return;
1334         }
1335 
1336         // Calculate center based on start, end, and radius
1337         //
1338         // Let 'l' be the length of the chord and 'm' the middle point of the chord
1339         double  l = GetLineLength( m_start, m_end );
1340         wxPoint m = ( m_start + m_end ) / 2;
1341 
1342         // Calculate 'd', the vector from the chord midpoint to the center
1343         wxPoint d;
1344         d.x = KiROUND( sqrt( sq( radius ) - sq( l/2 ) ) * ( m_start.y - m_end.y ) / l );
1345         d.y = KiROUND( sqrt( sq( radius ) - sq( l/2 ) ) * ( m_end.x - m_start.x ) / l );
1346 
1347         wxPoint c1 = m + d;
1348         wxPoint c2 = m - d;
1349 
1350         // Solution gives us 2 centers; we need to pick one:
1351         switch( m_editState )
1352         {
1353         case 1:
1354         {
1355             // Keep center clockwise from chord while drawing
1356             wxPoint chordVector = m_end - m_start;
1357             double  chordAngle = ArcTangente( chordVector.y, chordVector.x );
1358             NORMALIZE_ANGLE_POS( chordAngle );
1359 
1360             wxPoint c1Test = c1;
1361             RotatePoint( &c1Test, m_start, -chordAngle );
1362 
1363             m_arcCenter = c1Test.x > 0 ? c2 : c1;
1364         }
1365             break;
1366 
1367         case 2:
1368         case 3:
1369             // Pick the one closer to the old center
1370             m_arcCenter = GetLineLength( c1, m_arcCenter ) < GetLineLength( c2, m_arcCenter ) ? c1 : c2;
1371             break;
1372 
1373         case 4:
1374             // Pick the one closer to the mouse position
1375             m_arcCenter = GetLineLength( c1, aPosition ) < GetLineLength( c2, aPosition ) ? c1 : c2;
1376             break;
1377         }
1378     }
1379         break;
1380 
1381     case SHAPE_T::POLY:
1382         m_poly.Outline( 0 ).SetPoint( m_poly.Outline( 0 ).GetPointCount() - 1, aPosition );
1383         break;
1384 
1385     default:
1386         UNIMPLEMENTED_FOR( SHAPE_T_asString() );
1387     }
1388 }
1389 
1390 
endEdit()1391 void EDA_SHAPE::endEdit()
1392 {
1393     switch( GetShape() )
1394     {
1395     case SHAPE_T::ARC:
1396     case SHAPE_T::SEGMENT:
1397     case SHAPE_T::CIRCLE:
1398     case SHAPE_T::RECT:
1399         break;
1400 
1401     case SHAPE_T::POLY:
1402     {
1403         SHAPE_LINE_CHAIN& poly = m_poly.Outline( 0 );
1404 
1405         // do not include last point twice
1406         if( poly.GetPointCount() > 2 )
1407         {
1408             if( poly.CPoint( poly.GetPointCount() - 2 ) == poly.CLastPoint() )
1409             {
1410                 poly.SetClosed( true );
1411                 poly.Remove( poly.GetPointCount() - 1 );
1412             }
1413         }
1414     }
1415         break;
1416 
1417     default:
1418         UNIMPLEMENTED_FOR( SHAPE_T_asString() );
1419     }
1420 }
1421 
1422 
SwapShape(EDA_SHAPE * aImage)1423 void EDA_SHAPE::SwapShape( EDA_SHAPE* aImage )
1424 {
1425     EDA_SHAPE* image = dynamic_cast<EDA_SHAPE*>( aImage );
1426     assert( image );
1427 
1428     std::swap( m_width, image->m_width );
1429     std::swap( m_start, image->m_start );
1430     std::swap( m_end, image->m_end );
1431     std::swap( m_arcCenter, image->m_arcCenter );
1432     std::swap( m_shape, image->m_shape );
1433     std::swap( m_bezierC1, image->m_bezierC1 );
1434     std::swap( m_bezierC2, image->m_bezierC2 );
1435     std::swap( m_bezierPoints, image->m_bezierPoints );
1436     std::swap( m_poly, image->m_poly );
1437 }
1438 
1439 
Compare(const EDA_SHAPE * aOther) const1440 int EDA_SHAPE::Compare( const EDA_SHAPE* aOther ) const
1441 {
1442 #define EPSILON 2       // Should be enough for rounding errors on calculated items
1443 
1444 #define TEST( a, b ) { if( a != b ) return a - b; }
1445 #define TEST_E( a, b ) { if( abs( a - b ) > EPSILON ) return a - b; }
1446 #define TEST_PT( a, b ) { TEST_E( a.x, b.x ); TEST_E( a.y, b.y ); }
1447 
1448     TEST_PT( m_start, aOther->m_start );
1449     TEST_PT( m_end, aOther->m_end );
1450 
1451     TEST( (int) m_shape, (int) aOther->m_shape );
1452 
1453     if( m_shape == SHAPE_T::ARC )
1454     {
1455         TEST_PT( m_arcCenter, aOther->m_arcCenter );
1456     }
1457     else if( m_shape == SHAPE_T::BEZIER )
1458     {
1459         TEST_PT( m_bezierC1, aOther->m_bezierC1 );
1460         TEST_PT( m_bezierC2, aOther->m_bezierC2 );
1461     }
1462     else if( m_shape == SHAPE_T::POLY )
1463     {
1464         TEST( m_poly.TotalVertices(), aOther->m_poly.TotalVertices() );
1465 
1466         for( int ii = 0; ii < m_poly.TotalVertices(); ++ii )
1467             TEST_PT( m_poly.CVertex( ii ), aOther->m_poly.CVertex( ii ) );
1468     }
1469 
1470     TEST_E( m_width, aOther->m_width );
1471     TEST( (int) m_fill, (int) aOther->m_fill );
1472 
1473     return 0;
1474 }
1475 
1476 
TransformShapeWithClearanceToPolygon(SHAPE_POLY_SET & aCornerBuffer,int aClearanceValue,int aError,ERROR_LOC aErrorLoc,bool ignoreLineWidth) const1477 void EDA_SHAPE::TransformShapeWithClearanceToPolygon( SHAPE_POLY_SET& aCornerBuffer,
1478                                                       int aClearanceValue,
1479                                                       int aError, ERROR_LOC aErrorLoc,
1480                                                       bool ignoreLineWidth ) const
1481 {
1482     int width = ignoreLineWidth ? 0 : m_width;
1483 
1484     width += 2 * aClearanceValue;
1485 
1486     switch( m_shape )
1487     {
1488     case SHAPE_T::CIRCLE:
1489         if( IsFilled() )
1490         {
1491             TransformCircleToPolygon( aCornerBuffer, getCenter(), GetRadius() + width / 2, aError,
1492                                       aErrorLoc );
1493         }
1494         else
1495         {
1496             TransformRingToPolygon( aCornerBuffer, getCenter(), GetRadius(), width, aError,
1497                                     aErrorLoc );
1498         }
1499 
1500         break;
1501 
1502     case SHAPE_T::RECT:
1503     {
1504         std::vector<wxPoint> pts = GetRectCorners();
1505 
1506         if( IsFilled() )
1507         {
1508             aCornerBuffer.NewOutline();
1509 
1510             for( const wxPoint& pt : pts )
1511                 aCornerBuffer.Append( pt );
1512         }
1513 
1514         if( width > 0 || !IsFilled() )
1515         {
1516             // Add in segments
1517             TransformOvalToPolygon( aCornerBuffer, pts[0], pts[1], width, aError, aErrorLoc );
1518             TransformOvalToPolygon( aCornerBuffer, pts[1], pts[2], width, aError, aErrorLoc );
1519             TransformOvalToPolygon( aCornerBuffer, pts[2], pts[3], width, aError, aErrorLoc );
1520             TransformOvalToPolygon( aCornerBuffer, pts[3], pts[0], width, aError, aErrorLoc );
1521         }
1522 
1523         break;
1524     }
1525 
1526     case SHAPE_T::ARC:
1527         TransformArcToPolygon( aCornerBuffer, GetStart(), GetArcMid(), GetEnd(), width, aError,
1528                                aErrorLoc );
1529         break;
1530 
1531     case SHAPE_T::SEGMENT:
1532         TransformOvalToPolygon( aCornerBuffer, GetStart(), GetEnd(), width, aError, aErrorLoc );
1533         break;
1534 
1535     case SHAPE_T::POLY:
1536     {
1537         if( !IsPolyShapeValid() )
1538             break;
1539 
1540         // The polygon is expected to be a simple polygon; not self intersecting, no hole.
1541         double  orientation = getParentOrientation();
1542         wxPoint offset = getParentPosition();
1543 
1544         // Build the polygon with the actual position and orientation:
1545         std::vector<wxPoint> poly;
1546         DupPolyPointsList( poly );
1547 
1548         for( wxPoint& point : poly )
1549         {
1550             RotatePoint( &point, orientation );
1551             point += offset;
1552         }
1553 
1554         if( IsFilled() )
1555         {
1556             aCornerBuffer.NewOutline();
1557 
1558             for( const wxPoint& point : poly )
1559                 aCornerBuffer.Append( point.x, point.y );
1560         }
1561 
1562         if( width > 0 || !IsFilled() )
1563         {
1564             wxPoint pt1( poly[ poly.size() - 1] );
1565 
1566             for( const wxPoint& pt2 : poly )
1567             {
1568                 if( pt2 != pt1 )
1569                     TransformOvalToPolygon( aCornerBuffer, pt1, pt2, width, aError, aErrorLoc );
1570 
1571                 pt1 = pt2;
1572             }
1573         }
1574 
1575         break;
1576     }
1577 
1578     case SHAPE_T::BEZIER:
1579     {
1580         std::vector<wxPoint> ctrlPts = { GetStart(), GetBezierC1(), GetBezierC2(), GetEnd() };
1581         BEZIER_POLY converter( ctrlPts );
1582         std::vector< wxPoint> poly;
1583         converter.GetPoly( poly, m_width );
1584 
1585         for( unsigned ii = 1; ii < poly.size(); ii++ )
1586         {
1587             TransformOvalToPolygon( aCornerBuffer, poly[ii - 1], poly[ii], width, aError,
1588                                     aErrorLoc );
1589         }
1590 
1591         break;
1592     }
1593 
1594     default:
1595         UNIMPLEMENTED_FOR( SHAPE_T_asString() );
1596         break;
1597     }
1598 }
1599 
1600 
1601 static struct EDA_SHAPE_DESC
1602 {
EDA_SHAPE_DESCEDA_SHAPE_DESC1603     EDA_SHAPE_DESC()
1604     {
1605         ENUM_MAP<SHAPE_T>::Instance()
1606                     .Map( SHAPE_T::SEGMENT, _HKI( "Segment" ) )
1607                     .Map( SHAPE_T::RECT,    _HKI( "Rectangle" ) )
1608                     .Map( SHAPE_T::ARC,     _HKI( "Arc" ) )
1609                     .Map( SHAPE_T::CIRCLE,  _HKI( "Circle" ) )
1610                     .Map( SHAPE_T::POLY,    _HKI( "Polygon" ) )
1611                     .Map( SHAPE_T::BEZIER,  _HKI( "Bezier" ) );
1612         ENUM_MAP<PLOT_DASH_TYPE>::Instance()
1613                     .Map( PLOT_DASH_TYPE::DEFAULT,    _HKI( "Default" ) )
1614                     .Map( PLOT_DASH_TYPE::SOLID,      _HKI( "Solid" ) )
1615                     .Map( PLOT_DASH_TYPE::DASH,       _HKI( "Dashed" ) )
1616                     .Map( PLOT_DASH_TYPE::DOT,        _HKI( "Dotted" ) )
1617                     .Map( PLOT_DASH_TYPE::DASHDOT,    _HKI( "Dash-Dot" ) );
1618 
1619         PROPERTY_MANAGER& propMgr = PROPERTY_MANAGER::Instance();
1620         REGISTER_TYPE( EDA_SHAPE );
1621         propMgr.AddProperty( new PROPERTY_ENUM<EDA_SHAPE, SHAPE_T>( _HKI( "Shape" ),
1622                     &EDA_SHAPE::SetShape, &EDA_SHAPE::GetShape ) );
1623         propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "Start X" ),
1624                     &EDA_SHAPE::SetStartX, &EDA_SHAPE::GetStartX ) );
1625         propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "Start Y" ),
1626                     &EDA_SHAPE::SetStartY, &EDA_SHAPE::GetStartY ) );
1627         propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "End X" ),
1628                     &EDA_SHAPE::SetEndX, &EDA_SHAPE::GetEndX ) );
1629         propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "End Y" ),
1630                     &EDA_SHAPE::SetEndY, &EDA_SHAPE::GetEndY ) );
1631         // TODO: m_arcCenter, m_bezierC1, m_bezierC2, m_poly
1632         propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "Line Width" ),
1633                     &EDA_SHAPE::SetWidth, &EDA_SHAPE::GetWidth ) );
1634     }
1635 } _EDA_SHAPE_DESC;
1636 
1637 ENUM_TO_WXANY( SHAPE_T )
1638 ENUM_TO_WXANY( PLOT_DASH_TYPE )
1639