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