KiCad PCB EDA Suite
Loading...
Searching...
No Matches
eda_shape.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright (C) 2018 Jean-Pierre Charras, jp.charras at wanadoo.fr
5 * Copyright (C) 2012 SoftPLC Corporation, Dick Hollenbeck <[email protected]>
6 * Copyright (C) 2011 Wayne Stambaugh <[email protected]>
7 * Copyright (C) 2023 CERN
8 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
9 *
10 * This program is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU General Public License
12 * as published by the Free Software Foundation; either version 2
13 * of the License, or (at your option) any later version.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, you may find one here:
22 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
23 * or you may search the http://www.gnu.org website for the version 2 license,
24 * or you may write to the Free Software Foundation, Inc.,
25 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
26 */
27
28#include <eda_shape.h>
29
30#include <bezier_curves.h>
32#include <eda_draw_frame.h>
33#include <geometry/shape_arc.h>
37#include <geometry/shape_rect.h>
38#include <geometry/roundrect.h>
40#include <macros.h>
41#include <algorithm>
43#include <math/util.h> // for KiROUND
44#include <eda_item.h>
45#include <plotters/plotter.h>
46#include <api/api_enums.h>
47#include <api/api_utils.h>
48#include <api/common/types/base_types.pb.h>
49
50
51EDA_SHAPE::EDA_SHAPE( SHAPE_T aType, int aLineWidth, FILL_T aFill ) :
52 m_endsSwapped( false ),
53 m_shape( aType ),
55 m_fill( aFill ),
57 m_hatchingDirty( true ),
60 m_cornerRadius( 0 ),
61 m_editState( 0 ),
62 m_proxyItem( false )
63{
64}
65
66
70
71
72EDA_SHAPE::EDA_SHAPE( const SHAPE& aShape ) :
73 m_endsSwapped( false ),
75 m_fill(),
76 m_hatchingDirty( true ),
79 m_cornerRadius( 0 ),
80 m_editState( 0 ),
81 m_proxyItem( false )
82{
83 switch( aShape.Type() )
84 {
85 case SH_RECT:
86 {
87 auto rect = static_cast<const SHAPE_RECT&>( aShape );
88 m_shape = SHAPE_T::RECTANGLE;
89 SetStart( rect.GetPosition() );
90 SetEnd( rect.GetPosition() + rect.GetSize() );
91 break;
92 }
93
94 case SH_SEGMENT:
95 {
96 auto seg = static_cast<const SHAPE_SEGMENT&>( aShape );
97 m_shape = SHAPE_T::SEGMENT;
98 SetStart( seg.GetSeg().A );
99 SetEnd( seg.GetSeg().B );
100 SetWidth( seg.GetWidth() );
101 break;
102 }
103
104 case SH_LINE_CHAIN:
105 {
106 auto line = static_cast<const SHAPE_LINE_CHAIN&>( aShape );
107 m_shape = SHAPE_T::POLY;
108 m_poly = SHAPE_POLY_SET();
109 m_poly.AddOutline( line );
110 SetWidth( line.Width() );
111 break;
112 }
113
114 case SH_CIRCLE:
115 {
116 auto circle = static_cast<const SHAPE_CIRCLE&>( aShape );
117 m_shape = SHAPE_T::CIRCLE;
118 SetStart( circle.GetCenter() );
119 SetEnd( circle.GetCenter() + circle.GetRadius() );
120 break;
121 }
122
123 case SH_ARC:
124 {
125 auto arc = static_cast<const SHAPE_ARC&>( aShape );
126 m_shape = SHAPE_T::ARC;
127 SetArcGeometry( arc.GetP0(), arc.GetArcMid(), arc.GetP1() );
128 SetWidth( arc.GetWidth() );
129 break;
130 }
131
132 case SH_SIMPLE:
133 {
134 auto poly = static_cast<const SHAPE_SIMPLE&>( aShape );
135 m_shape = SHAPE_T::POLY;
136 poly.TransformToPolygon( m_poly, 0, ERROR_INSIDE );
137 break;
138 }
139
140 // currently unhandled
141 case SH_POLY_SET:
142 case SH_COMPOUND:
143 case SH_NULL:
145 default:
147 break;
148 }
149}
150
151
152void EDA_SHAPE::Serialize( google::protobuf::Any &aContainer ) const
153{
154 using namespace kiapi::common;
155 types::GraphicShape shape;
156
157 types::StrokeAttributes* stroke = shape.mutable_attributes()->mutable_stroke();
158 types::GraphicFillAttributes* fill = shape.mutable_attributes()->mutable_fill();
159
160 stroke->mutable_width()->set_value_nm( GetWidth() );
161
162 switch( GetLineStyle() )
163 {
164 case LINE_STYLE::DEFAULT: stroke->set_style( types::SLS_DEFAULT ); break;
165 case LINE_STYLE::SOLID: stroke->set_style( types::SLS_SOLID ); break;
166 case LINE_STYLE::DASH: stroke->set_style( types::SLS_DASH ); break;
167 case LINE_STYLE::DOT: stroke->set_style( types::SLS_DOT ); break;
168 case LINE_STYLE::DASHDOT: stroke->set_style( types::SLS_DASHDOT ); break;
169 case LINE_STYLE::DASHDOTDOT: stroke->set_style( types::SLS_DASHDOTDOT ); break;
170 default: break;
171 }
172
173 switch( GetFillMode() )
174 {
175 case FILL_T::FILLED_SHAPE: fill->set_fill_type( types::GFT_FILLED ); break;
176 default: fill->set_fill_type( types::GFT_UNFILLED ); break;
177 }
178
179 switch( GetShape() )
180 {
181 case SHAPE_T::SEGMENT:
182 {
183 types::GraphicSegmentAttributes* segment = shape.mutable_segment();
184 PackVector2( *segment->mutable_start(), GetStart() );
185 PackVector2( *segment->mutable_end(), GetEnd() );
186 break;
187 }
188
190 {
191 types::GraphicRectangleAttributes* rectangle = shape.mutable_rectangle();
192 PackVector2( *rectangle->mutable_top_left(), GetStart() );
193 PackVector2( *rectangle->mutable_bottom_right(), GetEnd() );
194 rectangle->mutable_corner_radius()->set_value_nm( GetCornerRadius() );
195 break;
196 }
197
198 case SHAPE_T::ARC:
199 {
200 types::GraphicArcAttributes* arc = shape.mutable_arc();
201 PackVector2( *arc->mutable_start(), GetStart() );
202 PackVector2( *arc->mutable_mid(), GetArcMid() );
203 PackVector2( *arc->mutable_end(), GetEnd() );
204 break;
205 }
206
207 case SHAPE_T::CIRCLE:
208 {
209 types::GraphicCircleAttributes* circle = shape.mutable_circle();
210 PackVector2( *circle->mutable_center(), GetStart() );
211 PackVector2( *circle->mutable_radius_point(), GetEnd() );
212 break;
213 }
214
215 case SHAPE_T::POLY:
216 {
217 PackPolySet( *shape.mutable_polygon(), GetPolyShape() );
218 break;
219 }
220
221 case SHAPE_T::BEZIER:
222 {
223 types::GraphicBezierAttributes* bezier = shape.mutable_bezier();
224 PackVector2( *bezier->mutable_start(), GetStart() );
225 PackVector2( *bezier->mutable_control1(), GetBezierC1() );
226 PackVector2( *bezier->mutable_control2(), GetBezierC2() );
227 PackVector2( *bezier->mutable_end(), GetEnd() );
228 break;
229 }
230
231 default:
232 wxASSERT_MSG( false, "Unhandled shape in PCB_SHAPE::Serialize" );
233 }
234
235 // TODO m_hasSolderMask and m_solderMaskMargin
236
237 aContainer.PackFrom( shape );
238}
239
240
241bool EDA_SHAPE::Deserialize( const google::protobuf::Any &aContainer )
242{
243 using namespace kiapi::common;
244
245 types::GraphicShape shape;
246
247 if( !aContainer.UnpackTo( &shape ) )
248 return false;
249
250 // Initialize everything to a known state that doesn't get touched by every
251 // codepath below, to make sure the equality operator is consistent
252 m_start = {};
253 m_end = {};
254 m_arcCenter = {};
255 m_arcMidData = {};
256 m_bezierC1 = {};
257 m_bezierC2 = {};
258 m_editState = 0;
259 m_proxyItem = false;
260 m_endsSwapped = false;
261
262 SetFilled( shape.attributes().fill().fill_type() == types::GFT_FILLED );
263 SetWidth( shape.attributes().stroke().width().value_nm() );
264
265 switch( shape.attributes().stroke().style() )
266 {
267 case types::SLS_DEFAULT: SetLineStyle( LINE_STYLE::DEFAULT ); break;
268 case types::SLS_SOLID: SetLineStyle( LINE_STYLE::SOLID ); break;
269 case types::SLS_DASH: SetLineStyle( LINE_STYLE::DASH ); break;
270 case types::SLS_DOT: SetLineStyle( LINE_STYLE::DOT ); break;
271 case types::SLS_DASHDOT: SetLineStyle( LINE_STYLE::DASHDOT ); break;
272 case types::SLS_DASHDOTDOT: SetLineStyle( LINE_STYLE::DASHDOTDOT ); break;
273 default: break;
274 }
275
276 if( shape.has_segment() )
277 {
279 SetStart( UnpackVector2( shape.segment().start() ) );
280 SetEnd( UnpackVector2( shape.segment().end() ) );
281 }
282 else if( shape.has_rectangle() )
283 {
285 SetStart( UnpackVector2( shape.rectangle().top_left() ) );
286 SetEnd( UnpackVector2( shape.rectangle().bottom_right() ) );
287 SetCornerRadius( shape.rectangle().corner_radius().value_nm() );
288 }
289 else if( shape.has_arc() )
290 {
292 SetArcGeometry( UnpackVector2( shape.arc().start() ),
293 UnpackVector2( shape.arc().mid() ),
294 UnpackVector2( shape.arc().end() ) );
295 }
296 else if( shape.has_circle() )
297 {
299 SetStart( UnpackVector2( shape.circle().center() ) );
300 SetEnd( UnpackVector2( shape.circle().radius_point() ) );
301 }
302 else if( shape.has_polygon() )
303 {
305 SetPolyShape( UnpackPolySet( shape.polygon() ) );
306 }
307 else if( shape.has_bezier() )
308 {
310 SetStart( UnpackVector2( shape.bezier().start() ) );
311 SetBezierC1( UnpackVector2( shape.bezier().control1() ) );
312 SetBezierC2( UnpackVector2( shape.bezier().control2() ) );
313 SetEnd( UnpackVector2( shape.bezier().end() ) );
315 }
316
317 return true;
318}
319
320
321wxString EDA_SHAPE::ShowShape() const
322{
323 if( IsProxyItem() )
324 {
325 switch( m_shape )
326 {
327 case SHAPE_T::SEGMENT: return _( "Thermal Spoke" );
328 case SHAPE_T::RECTANGLE: return _( "Number Box" );
329 default: return wxT( "??" );
330 }
331 }
332 else
333 {
334 switch( m_shape )
335 {
336 case SHAPE_T::SEGMENT: return _( "Line" );
337 case SHAPE_T::RECTANGLE: return _( "Rect" );
338 case SHAPE_T::ARC: return _( "Arc" );
339 case SHAPE_T::CIRCLE: return _( "Circle" );
340 case SHAPE_T::BEZIER: return _( "Bezier Curve" );
341 case SHAPE_T::POLY: return _( "Polygon" );
342 default: return wxT( "??" );
343 }
344 }
345}
346
347
349{
350 switch( m_shape )
351 {
352 case SHAPE_T::SEGMENT: return wxS( "S_SEGMENT" );
353 case SHAPE_T::RECTANGLE: return wxS( "S_RECT" );
354 case SHAPE_T::ARC: return wxS( "S_ARC" );
355 case SHAPE_T::CIRCLE: return wxS( "S_CIRCLE" );
356 case SHAPE_T::POLY: return wxS( "S_POLYGON" );
357 case SHAPE_T::BEZIER: return wxS( "S_CURVE" );
358 case SHAPE_T::UNDEFINED: return wxS( "UNDEFINED" );
359 }
360
361 return wxEmptyString; // Just to quiet GCC.
362}
363
364
366{
367 move( aPos - getPosition() );
368}
369
370
372{
373 if( m_shape == SHAPE_T::ARC )
374 return getCenter();
375 else if( m_shape == SHAPE_T::POLY )
376 return m_poly.CVertex( 0 );
377 else
378 return m_start;
379}
380
381
383{
384 double length = 0.0;
385
386 switch( m_shape )
387 {
388 case SHAPE_T::BEZIER:
389 for( size_t ii = 1; ii < m_bezierPoints.size(); ++ii )
390 length += m_bezierPoints[ ii - 1].Distance( m_bezierPoints[ii] );
391
392 return length;
393
394 case SHAPE_T::SEGMENT:
395 return GetStart().Distance( GetEnd() );
396
397 case SHAPE_T::POLY:
398 for( int ii = 0; ii < m_poly.COutline( 0 ).SegmentCount(); ii++ )
399 length += m_poly.COutline( 0 ).CSegment( ii ).Length();
400
401 return length;
402
403 case SHAPE_T::ARC:
404 return GetRadius() * GetArcAngle().AsRadians();
405
406 default:
408 return 0.0;
409 }
410}
411
412
414{
415 switch( m_shape )
416 {
418 return GetEndY() - GetStartY();
419
420 default:
422 return 0;
423 }
424}
425
426
428{
429 switch( m_shape )
430 {
432 return GetEndX() - GetStartX();
433
434 default:
436 return 0;
437 }
438}
439
440
442{
443 return m_cornerRadius;
444}
445
446
447void EDA_SHAPE::SetCornerRadius( int aRadius )
448{
450 {
451 int width = std::abs( GetRectangleWidth() );
452 int height = std::abs( GetRectangleHeight() );
453 int maxRadius = std::min( width, height ) / 2;
454
455 m_cornerRadius = std::clamp( aRadius, 0, maxRadius );
456 }
457 else
458 {
459 m_cornerRadius = aRadius;
460 }
461}
462
463
464void EDA_SHAPE::SetRectangleHeight( const int& aHeight )
465{
466 switch ( m_shape )
467 {
469 m_rectangleHeight = aHeight;
471 break;
472
473 default:
475 }
476}
477
478
479void EDA_SHAPE::SetRectangleWidth( const int& aWidth )
480{
481 switch ( m_shape )
482 {
484 m_rectangleWidth = aWidth;
486 break;
487
488 default:
490 }
491}
492
493
494void EDA_SHAPE::SetRectangle( const long long int& aHeight, const long long int& aWidth )
495{
496 switch ( m_shape )
497 {
499 m_rectangleHeight = aHeight;
500 m_rectangleWidth = aWidth;
501 break;
502
503 default:
505 }
506}
507
508
510{
511 switch( m_shape )
512 {
513 case SHAPE_T::CIRCLE:
515 return true;
516
517 case SHAPE_T::ARC:
518 case SHAPE_T::SEGMENT:
519 return false;
520
521 case SHAPE_T::POLY:
522 if( m_poly.IsEmpty() )
523 return false;
524 else
525 return m_poly.Outline( 0 ).IsClosed();
526
527 case SHAPE_T::BEZIER:
528 if( m_bezierPoints.size() < 3 )
529 return false;
530 else
531 return m_bezierPoints[0] == m_bezierPoints[ m_bezierPoints.size() - 1 ];
532
533 default:
535 return false;
536 }
537}
538
539
541{
542 m_fill = aFill;
543 m_hatchingDirty = true;
544}
545
546
548{
549 switch( aFill )
550 {
555 default: SetFilled( true ); break;
556 }
557}
558
559
571
572
574{
575 if( !m_hatchingDirty )
576 return;
577
578 m_hatching.RemoveAllContours();
579
580 std::vector<double> slopes;
581 int lineWidth = GetHatchLineWidth();
582 int spacing = GetHatchLineSpacing();
583 SHAPE_POLY_SET shapeBuffer;
584
585 if( isMoving() )
586 return;
587 else if( GetFillMode() == FILL_T::CROSS_HATCH )
588 slopes = { 1.0, -1.0 };
589 else if( GetFillMode() == FILL_T::HATCH )
590 slopes = { -1.0 };
591 else if( GetFillMode() == FILL_T::REVERSE_HATCH )
592 slopes = { 1.0 };
593 else
594 return;
595
596 if( spacing == 0 )
597 return;
598
599 switch( m_shape )
600 {
601 case SHAPE_T::ARC:
602 case SHAPE_T::SEGMENT:
603 case SHAPE_T::BEZIER:
604 return;
605
607 {
609 GetCornerRadius() );
610 rr.TransformToPolygon( shapeBuffer, getMaxError() );
611 }
612 break;
613
614 case SHAPE_T::CIRCLE:
616 break;
617
618 case SHAPE_T::POLY:
619 if( !IsClosed() )
620 return;
621
622 shapeBuffer = m_poly.CloneDropTriangulation();
623 break;
624
625 default:
627 return;
628 }
629
630 BOX2I extents = shapeBuffer.BBox();
631 int majorAxis = std::max( extents.GetWidth(), extents.GetHeight() );
632
633 if( majorAxis / spacing > 100 )
634 spacing = majorAxis / 100;
635
637 {
638 for( const SEG& seg : shapeBuffer.GenerateHatchLines( slopes, spacing, -1 ) )
639 {
640 // We don't really need the rounded ends at all, so don't spend any extra time on them
641 int maxError = lineWidth;
642
643 TransformOvalToPolygon( m_hatching, seg.A, seg.B, lineWidth, maxError, ERROR_INSIDE );
644 }
645
646 m_hatching.Fracture();
647 }
648 else
649 {
650 // Generate a grid of holes for a cross-hatch. This is about 3X the speed of the above
651 // algorithm, even when modified for the 45-degree fracture problem.
652
653 int gridsize = spacing;
654 int hole_size = gridsize - GetHatchLineWidth();
655
656 m_hatching = shapeBuffer.CloneDropTriangulation();
657 m_hatching.Rotate( -ANGLE_45 );
658
659 // Build hole shape
660 SHAPE_LINE_CHAIN hole_base;
661 VECTOR2I corner( 0, 0 );;
662 hole_base.Append( corner );
663 corner.x += hole_size;
664 hole_base.Append( corner );
665 corner.y += hole_size;
666 hole_base.Append( corner );
667 corner.x = 0;
668 hole_base.Append( corner );
669 hole_base.SetClosed( true );
670
671 // Build holes
672 BOX2I bbox = m_hatching.BBox( 0 );
673 SHAPE_POLY_SET holes;
674
675 int x_offset = bbox.GetX() - ( bbox.GetX() ) % gridsize - gridsize;
676 int y_offset = bbox.GetY() - ( bbox.GetY() ) % gridsize - gridsize;
677
678 for( int xx = x_offset; xx <= bbox.GetRight(); xx += gridsize )
679 {
680 for( int yy = y_offset; yy <= bbox.GetBottom(); yy += gridsize )
681 {
682 SHAPE_LINE_CHAIN hole( hole_base );
683 hole.Move( VECTOR2I( xx, yy ) );
684 holes.AddOutline( hole );
685 }
686 }
687
688 m_hatching.BooleanSubtract( holes );
689 m_hatching.Fracture();
690
691 // Must re-rotate after Fracture(). Clipper struggles mightily with fracturing
692 // 45-degree holes.
693 m_hatching.Rotate( ANGLE_45 );
694 }
695}
696
697
698void EDA_SHAPE::move( const VECTOR2I& aMoveVector )
699{
700 switch ( m_shape )
701 {
702 case SHAPE_T::ARC:
703 m_arcCenter += aMoveVector;
704 m_arcMidData.center += aMoveVector;
705 m_arcMidData.start += aMoveVector;
706 m_arcMidData.end += aMoveVector;
707 m_arcMidData.mid += aMoveVector;
709
710 case SHAPE_T::SEGMENT:
712 case SHAPE_T::CIRCLE:
713 m_start += aMoveVector;
714 m_end += aMoveVector;
715 break;
716
717 case SHAPE_T::POLY:
718 m_poly.Move( aMoveVector );
719 break;
720
721 case SHAPE_T::BEZIER:
722 m_start += aMoveVector;
723 m_end += aMoveVector;
724 m_bezierC1 += aMoveVector;
725 m_bezierC2 += aMoveVector;
726
727 for( VECTOR2I& pt : m_bezierPoints )
728 pt += aMoveVector;
729
730 break;
731
732 default:
734 break;
735 }
736
737 m_hatchingDirty = true;
738}
739
740
741void EDA_SHAPE::scale( double aScale )
742{
743 auto scalePt =
744 [&]( VECTOR2I& pt )
745 {
746 pt.x = KiROUND( pt.x * aScale );
747 pt.y = KiROUND( pt.y * aScale );
748 };
749
750 switch( m_shape )
751 {
752 case SHAPE_T::ARC:
753 scalePt( m_arcCenter );
755
756 case SHAPE_T::SEGMENT:
758 case SHAPE_T::CIRCLE:
759 scalePt( m_start );
760 scalePt( m_end );
761 break;
762
763 case SHAPE_T::POLY: // polygon
764 {
765 std::vector<VECTOR2I> pts;
766
767 for( int ii = 0; ii < m_poly.OutlineCount(); ++ ii )
768 {
769 for( const VECTOR2I& pt : m_poly.Outline( ii ).CPoints() )
770 {
771 pts.emplace_back( pt );
772 scalePt( pts.back() );
773 }
774 }
775
776 SetPolyPoints( pts );
777 }
778 break;
779
780 case SHAPE_T::BEZIER:
781 scalePt( m_start );
782 scalePt( m_end );
783 scalePt( m_bezierC1 );
784 scalePt( m_bezierC2 );
786 break;
787
788 default:
790 break;
791 }
792
793 m_hatchingDirty = true;
794}
795
796
797void EDA_SHAPE::rotate( const VECTOR2I& aRotCentre, const EDA_ANGLE& aAngle )
798{
799 switch( m_shape )
800 {
801 case SHAPE_T::SEGMENT:
802 case SHAPE_T::CIRCLE:
803 RotatePoint( m_start, aRotCentre, aAngle );
804 RotatePoint( m_end, aRotCentre, aAngle );
805 break;
806
807 case SHAPE_T::ARC:
808 RotatePoint( m_start, aRotCentre, aAngle );
809 RotatePoint( m_end, aRotCentre, aAngle );
810 RotatePoint( m_arcCenter, aRotCentre, aAngle );
811 RotatePoint( m_arcMidData.start, aRotCentre, aAngle );
812 RotatePoint( m_arcMidData.end, aRotCentre, aAngle );
813 RotatePoint( m_arcMidData.mid, aRotCentre, aAngle );
814 RotatePoint( m_arcMidData.center, aRotCentre, aAngle );
815 break;
816
818 if( aAngle.IsCardinal() )
819 {
820 RotatePoint( m_start, aRotCentre, aAngle );
821 RotatePoint( m_end, aRotCentre, aAngle );
822 }
823 else
824 {
825 // Convert non-cardinally-rotated rect to a diamond
829 m_poly.Rotate( aAngle, aRotCentre );
830 }
831
832 break;
833
834 case SHAPE_T::POLY:
835 m_poly.Rotate( aAngle, aRotCentre );
836 break;
837
838 case SHAPE_T::BEZIER:
839 RotatePoint( m_start, aRotCentre, aAngle );
840 RotatePoint( m_end, aRotCentre, aAngle );
841 RotatePoint( m_bezierC1, aRotCentre, aAngle );
842 RotatePoint( m_bezierC2, aRotCentre, aAngle );
843
844 for( VECTOR2I& pt : m_bezierPoints )
845 RotatePoint( pt, aRotCentre, aAngle);
846
847 break;
848
849 default:
851 break;
852 }
853
854 m_hatchingDirty = true;
855}
856
857
858void EDA_SHAPE::flip( const VECTOR2I& aCentre, FLIP_DIRECTION aFlipDirection )
859{
860 switch ( m_shape )
861 {
862 case SHAPE_T::SEGMENT:
864 MIRROR( m_start, aCentre, aFlipDirection );
865 MIRROR( m_end, aCentre, aFlipDirection );
866 break;
867
868 case SHAPE_T::CIRCLE:
869 MIRROR( m_start, aCentre, aFlipDirection );
870 MIRROR( m_end, aCentre, aFlipDirection );
871 break;
872
873 case SHAPE_T::ARC:
874 MIRROR( m_start, aCentre, aFlipDirection );
875 MIRROR( m_end, aCentre, aFlipDirection );
876 MIRROR( m_arcCenter, aCentre, aFlipDirection );
877
878 std::swap( m_start, m_end );
879 break;
880
881 case SHAPE_T::POLY:
882 m_poly.Mirror( aCentre, aFlipDirection );
883 break;
884
885 case SHAPE_T::BEZIER:
886 MIRROR( m_start, aCentre, aFlipDirection );
887 MIRROR( m_end, aCentre, aFlipDirection );
888 MIRROR( m_bezierC1, aCentre, aFlipDirection );
889 MIRROR( m_bezierC2, aCentre, aFlipDirection );
890
892 break;
893
894 default:
896 break;
897 }
898
899 m_hatchingDirty = true;
900}
901
902
904{
905 // Has meaning only for SHAPE_T::BEZIER
906 if( m_shape != SHAPE_T::BEZIER )
907 {
908 m_bezierPoints.clear();
909 return;
910 }
911
912 // Rebuild the m_BezierPoints vertex list that approximate the Bezier curve
914}
915
916
917const std::vector<VECTOR2I> EDA_SHAPE::buildBezierToSegmentsPointsList( int aMaxError ) const
918{
919 std::vector<VECTOR2I> bezierPoints;
920
921 // Rebuild the m_BezierPoints vertex list that approximate the Bezier curve
922 std::vector<VECTOR2I> ctrlPoints = { m_start, m_bezierC1, m_bezierC2, m_end };
923 BEZIER_POLY converter( ctrlPoints );
924 converter.GetPoly( bezierPoints, aMaxError );
925
926 return bezierPoints;
927}
928
929
931{
932 switch( m_shape )
933 {
934 case SHAPE_T::ARC:
935 return m_arcCenter;
936
937 case SHAPE_T::CIRCLE:
938 return m_start;
939
940 case SHAPE_T::SEGMENT:
941 // Midpoint of the line
942 return ( m_start + m_end ) / 2;
943
944 case SHAPE_T::POLY:
946 case SHAPE_T::BEZIER:
947 return getBoundingBox().Centre();
948
949 default:
951 return VECTOR2I();
952 }
953}
954
955
956void EDA_SHAPE::SetCenter( const VECTOR2I& aCenter )
957{
958 switch( m_shape )
959 {
960 case SHAPE_T::ARC:
961 m_arcCenter = aCenter;
962 break;
963
964 case SHAPE_T::CIRCLE:
965 m_start = aCenter;
966 m_hatchingDirty = true;
967 break;
968
969 default:
971 }
972}
973
974
976{
977 // If none of the input data have changed since we loaded the arc, keep the original mid point data
978 // to minimize churn
979 if( m_arcMidData.start == m_start && m_arcMidData.end == m_end && m_arcMidData.center == m_arcCenter )
980 return m_arcMidData.mid;
981
982 VECTOR2I mid = m_start;
983 RotatePoint( mid, m_arcCenter, -GetArcAngle() / 2.0 );
984 return mid;
985}
986
987
988void EDA_SHAPE::CalcArcAngles( EDA_ANGLE& aStartAngle, EDA_ANGLE& aEndAngle ) const
989{
990 VECTOR2D startRadial( GetStart() - getCenter() );
991 VECTOR2D endRadial( GetEnd() - getCenter() );
992
993 aStartAngle = EDA_ANGLE( startRadial );
994 aEndAngle = EDA_ANGLE( endRadial );
995
996 if( aEndAngle == aStartAngle )
997 aEndAngle = aStartAngle + ANGLE_360; // ring, not null
998
999 while( aEndAngle < aStartAngle )
1000 aEndAngle += ANGLE_360;
1001}
1002
1003
1005{
1006 double radius = 0.0;
1007
1008 switch( m_shape )
1009 {
1010 case SHAPE_T::ARC:
1011 radius = m_arcCenter.Distance( m_start );
1012 break;
1013
1014 case SHAPE_T::CIRCLE:
1015 radius = m_start.Distance( m_end );
1016 break;
1017
1018 default:
1020 }
1021
1022 // don't allow degenerate circles/arcs
1023 if( radius > (double) INT_MAX / 2.0 )
1024 radius = (double) INT_MAX / 2.0;
1025
1026 return std::max( 1, KiROUND( radius ) );
1027}
1028
1029
1030void EDA_SHAPE::SetCachedArcData( const VECTOR2I& aStart, const VECTOR2I& aMid,
1031 const VECTOR2I& aEnd, const VECTOR2I& aCenter )
1032{
1033 m_arcMidData.start = aStart;
1034 m_arcMidData.end = aEnd;
1035 m_arcMidData.center = aCenter;
1036 m_arcMidData.mid = aMid;
1037}
1038
1039
1040void EDA_SHAPE::SetArcGeometry( const VECTOR2I& aStart, const VECTOR2I& aMid, const VECTOR2I& aEnd )
1041{
1042 m_arcMidData = {};
1043 m_start = aStart;
1044 m_end = aEnd;
1045 m_arcCenter = CalcArcCenter( aStart, aMid, aEnd );
1046 VECTOR2I new_mid = GetArcMid();
1047
1048 m_endsSwapped = false;
1049
1050 // Watch the ordering here. GetArcMid above needs to be called prior to initializing the
1051 // m_arcMidData structure in order to ensure we get the calculated variant, not the cached
1052 SetCachedArcData( aStart, aMid, aEnd, m_arcCenter );
1053
1054 /*
1055 * If the input winding doesn't match our internal winding, the calculated midpoint will end
1056 * up on the other side of the arc. In this case, we need to flip the start/end points and
1057 * flag this change for the system.
1058 */
1059 VECTOR2D dist( new_mid - aMid );
1060 VECTOR2D dist2( new_mid - m_arcCenter );
1061
1062 if( dist.SquaredEuclideanNorm() > dist2.SquaredEuclideanNorm() )
1063 {
1064 std::swap( m_start, m_end );
1065 m_endsSwapped = true;
1066 }
1067}
1068
1069
1071{
1072 EDA_ANGLE angle( atan2( static_cast<double>( GetStart().y - GetEnd().y ),
1073 static_cast<double>( GetEnd().x - GetStart().x ) ), RADIANS_T );
1074
1075 return angle;
1076}
1077
1078
1080{
1081 EDA_ANGLE startAngle;
1082 EDA_ANGLE endAngle;
1083
1084 CalcArcAngles( startAngle, endAngle );
1085
1086 return endAngle - startAngle;
1087}
1088
1089
1091{
1092 if( m_shape == SHAPE_T::ARC )
1093 {
1094 VECTOR2D mid = GetArcMid();
1095
1096 double orient = ( mid.x - m_start.x ) * ( m_end.y - m_start.y )
1097 - ( mid.y - m_start.y ) * ( m_end.x - m_start.x );
1098
1099 return orient < 0;
1100 }
1101
1103 return false;
1104}
1105
1106
1107void EDA_SHAPE::SetArcAngleAndEnd( const EDA_ANGLE& aAngle, bool aCheckNegativeAngle )
1108{
1109 EDA_ANGLE angle( aAngle );
1110
1111 m_end = m_start;
1113
1114 if( aCheckNegativeAngle && aAngle < ANGLE_0 )
1115 {
1116 std::swap( m_start, m_end );
1117 m_endsSwapped = true;
1118 }
1119}
1120
1121
1123{
1124 if( IsProxyItem() )
1125 {
1126 switch( m_shape )
1127 {
1128 case SHAPE_T::RECTANGLE: return _( "Pad Number Box" );
1129 case SHAPE_T::SEGMENT: return _( "Thermal Spoke Template" );
1130 default: return _( "Unrecognized" );
1131 }
1132 }
1133 else
1134 {
1135 switch( m_shape )
1136 {
1137 case SHAPE_T::CIRCLE: return _( "Circle" );
1138 case SHAPE_T::ARC: return _( "Arc" );
1139 case SHAPE_T::BEZIER: return _( "Curve" );
1140 case SHAPE_T::POLY: return _( "Polygon" );
1141 case SHAPE_T::RECTANGLE: return _( "Rectangle" );
1142 case SHAPE_T::SEGMENT: return _( "Segment" );
1143 default: return _( "Unrecognized" );
1144 }
1145 }
1146}
1147
1148
1149void EDA_SHAPE::ShapeGetMsgPanelInfo( EDA_DRAW_FRAME* aFrame, std::vector<MSG_PANEL_ITEM>& aList )
1150{
1151 wxString msg;
1152
1153 wxString shape = _( "Shape" );
1154 aList.emplace_back( shape, getFriendlyName() );
1155
1156 switch( m_shape )
1157 {
1158 case SHAPE_T::CIRCLE:
1159 aList.emplace_back( _( "Radius" ), aFrame->MessageTextFromValue( GetRadius() ) );
1160 break;
1161
1162 case SHAPE_T::ARC:
1163 aList.emplace_back( _( "Length" ), aFrame->MessageTextFromValue( GetLength() ) );
1164
1166 aList.emplace_back( _( "Angle" ), msg );
1167
1168 aList.emplace_back( _( "Radius" ), aFrame->MessageTextFromValue( GetRadius() ) );
1169 break;
1170
1171 case SHAPE_T::BEZIER:
1172 aList.emplace_back( _( "Length" ), aFrame->MessageTextFromValue( GetLength() ) );
1173 break;
1174
1175 case SHAPE_T::POLY:
1176 msg.Printf( wxS( "%d" ), GetPolyShape().Outline(0).PointCount() );
1177 aList.emplace_back( _( "Points" ), msg );
1178 break;
1179
1180 case SHAPE_T::RECTANGLE:
1181 aList.emplace_back( _( "Width" ), aFrame->MessageTextFromValue( std::abs( GetEnd().x - GetStart().x ) ) );
1182 aList.emplace_back( _( "Height" ), aFrame->MessageTextFromValue( std::abs( GetEnd().y - GetStart().y ) ) );
1183 break;
1184
1185 case SHAPE_T::SEGMENT:
1186 {
1187 aList.emplace_back( _( "Length" ), aFrame->MessageTextFromValue( GetStart().Distance( GetEnd() ) ));
1188
1189 // angle counter-clockwise from 3'o-clock
1190 EDA_ANGLE angle( atan2( (double)( GetStart().y - GetEnd().y ), (double)( GetEnd().x - GetStart().x ) ),
1191 RADIANS_T );
1192 aList.emplace_back( _( "Angle" ), EDA_UNIT_UTILS::UI::MessageTextFromValue( angle ) );
1193 break;
1194 }
1195
1196 default:
1197 break;
1198 }
1199
1200 m_stroke.GetMsgPanelInfo( aFrame, aList );
1201}
1202
1203
1205{
1206 BOX2I bbox;
1207
1208 switch( m_shape )
1209 {
1210 case SHAPE_T::RECTANGLE:
1211 for( VECTOR2I& pt : GetRectCorners() )
1212 bbox.Merge( pt );
1213
1214 break;
1215
1216 case SHAPE_T::SEGMENT:
1217 bbox.SetOrigin( GetStart() );
1218 bbox.SetEnd( GetEnd() );
1219 break;
1220
1221 case SHAPE_T::CIRCLE:
1222 bbox.SetOrigin( GetStart() );
1223 bbox.Inflate( GetRadius() );
1224 break;
1225
1226 case SHAPE_T::ARC:
1227 computeArcBBox( bbox );
1228 break;
1229
1230 case SHAPE_T::POLY:
1231 if( m_poly.IsEmpty() )
1232 break;
1233
1234 for( auto iter = m_poly.CIterate(); iter; iter++ )
1235 bbox.Merge( *iter );
1236
1237 break;
1238
1239 case SHAPE_T::BEZIER:
1240 // Bezier BBoxes are not trivial to compute, so we approximate it by
1241 // using the bounding box of the curve (not control!) points.
1242 for( const VECTOR2I& pt : m_bezierPoints )
1243 bbox.Merge( pt );
1244
1245 break;
1246
1247 default:
1249 break;
1250 }
1251
1252 bbox.Inflate( std::max( 0, GetWidth() ) / 2 );
1253 bbox.Normalize();
1254
1255 return bbox;
1256}
1257
1258
1259bool EDA_SHAPE::hitTest( const VECTOR2I& aPosition, int aAccuracy ) const
1260{
1261 double maxdist = aAccuracy;
1262
1263 if( GetWidth() > 0 )
1264 maxdist += GetWidth() / 2.0;
1265
1266 switch( m_shape )
1267 {
1268 case SHAPE_T::CIRCLE:
1269 {
1270 double radius = GetRadius();
1271 double dist = aPosition.Distance( getCenter() );
1272
1273 if( IsFilledForHitTesting() )
1274 return dist <= radius + maxdist; // Filled circle hit-test
1275 else if( abs( radius - dist ) <= maxdist ) // Ring hit-test
1276 return true;
1277
1278 if( IsHatchedFill() && GetHatching().Collide( aPosition, maxdist ) )
1279 return true;
1280
1281 return false;
1282 }
1283
1284 case SHAPE_T::ARC:
1285 {
1286 if( aPosition.Distance( m_start ) <= maxdist )
1287 return true;
1288
1289 if( aPosition.Distance( m_end ) <= maxdist )
1290 return true;
1291
1292 double radius = GetRadius();
1293 VECTOR2D relPos( VECTOR2D( aPosition ) - getCenter() );
1294 double dist = relPos.EuclideanNorm();
1295
1296 if( IsFilledForHitTesting() )
1297 {
1298 // Check distance from arc center
1299 if( dist > radius + maxdist )
1300 return false;
1301 }
1302 else
1303 {
1304 // Check distance from arc circumference
1305 if( abs( radius - dist ) > maxdist )
1306 return false;
1307 }
1308
1309 // Finally, check to see if it's within arc's swept angle.
1310 EDA_ANGLE startAngle;
1311 EDA_ANGLE endAngle;
1312 CalcArcAngles( startAngle, endAngle );
1313
1314 EDA_ANGLE relPosAngle( relPos );
1315
1316 startAngle.Normalize();
1317 endAngle.Normalize();
1318 relPosAngle.Normalize();
1319
1320 if( endAngle > startAngle )
1321 return relPosAngle >= startAngle && relPosAngle <= endAngle;
1322 else
1323 return relPosAngle >= startAngle || relPosAngle <= endAngle;
1324 }
1325
1326 case SHAPE_T::BEZIER:
1327 {
1328 const std::vector<VECTOR2I>* pts = &m_bezierPoints;
1329 std::vector<VECTOR2I> updatedBezierPoints;
1330
1331 if( m_bezierPoints.empty() )
1332 {
1334 converter.GetPoly( updatedBezierPoints, aAccuracy / 2 );
1335 pts = &updatedBezierPoints;
1336 }
1337
1338 for( unsigned int i = 1; i < pts->size(); i++ )
1339 {
1340 if( TestSegmentHit( aPosition, ( *pts )[i - 1], ( *pts )[i], maxdist ) )
1341 return true;
1342 }
1343
1344 return false;
1345 }
1346 case SHAPE_T::SEGMENT:
1347 return TestSegmentHit( aPosition, GetStart(), GetEnd(), maxdist );
1348
1349 case SHAPE_T::RECTANGLE:
1350 if( IsProxyItem() || IsFilledForHitTesting() ) // Filled rect hit-test
1351 {
1352 SHAPE_POLY_SET poly;
1353 poly.NewOutline();
1354
1355 for( const VECTOR2I& pt : GetRectCorners() )
1356 poly.Append( pt );
1357
1358 return poly.Collide( aPosition, maxdist );
1359 }
1360 else if( m_cornerRadius > 0 )
1361 {
1363 SHAPE_POLY_SET poly;
1364 rr.TransformToPolygon( poly, getMaxError() );
1365
1366 if( poly.CollideEdge( aPosition, nullptr, maxdist ) )
1367 return true;
1368 }
1369 else
1370 {
1371 std::vector<VECTOR2I> pts = GetRectCorners();
1372
1373 if( TestSegmentHit( aPosition, pts[0], pts[1], maxdist )
1374 || TestSegmentHit( aPosition, pts[1], pts[2], maxdist )
1375 || TestSegmentHit( aPosition, pts[2], pts[3], maxdist )
1376 || TestSegmentHit( aPosition, pts[3], pts[0], maxdist ) )
1377 {
1378 return true;
1379 }
1380 }
1381
1382 if( IsHatchedFill() && GetHatching().Collide( aPosition, maxdist ) )
1383 return true;
1384
1385 return false;
1386
1387 case SHAPE_T::POLY:
1388 if( IsFilledForHitTesting() )
1389 {
1390 if( !m_poly.COutline( 0 ).IsClosed() )
1391 {
1392 // Only one outline is expected
1393 SHAPE_LINE_CHAIN copy( m_poly.COutline( 0 ) );
1394 copy.SetClosed( true );
1395 return copy.Collide( aPosition, maxdist );
1396 }
1397 else
1398 {
1399 return m_poly.Collide( aPosition, maxdist );
1400 }
1401 }
1402 else
1403 {
1404 if( m_poly.CollideEdge( aPosition, nullptr, maxdist ) )
1405 return true;
1406
1407 if( IsHatchedFill() && GetHatching().Collide( aPosition, maxdist ) )
1408 return true;
1409
1410 return false;
1411 }
1412
1413 default:
1415 return false;
1416 }
1417}
1418
1419
1420bool EDA_SHAPE::hitTest( const BOX2I& aRect, bool aContained, int aAccuracy ) const
1421{
1422 BOX2I arect = aRect;
1423 arect.Normalize();
1424 arect.Inflate( aAccuracy );
1425
1426 BOX2I bbox = getBoundingBox();
1427
1428 auto checkOutline =
1429 [&]( const SHAPE_LINE_CHAIN& outline )
1430 {
1431 int count = (int) outline.GetPointCount();
1432
1433 for( int ii = 0; ii < count; ii++ )
1434 {
1435 VECTOR2I vertex = outline.GetPoint( ii );
1436
1437 // Test if the point is within aRect
1438 if( arect.Contains( vertex ) )
1439 return true;
1440
1441 if( ii + 1 < count )
1442 {
1443 VECTOR2I vertexNext = outline.GetPoint( ii + 1 );
1444
1445 // Test if this edge intersects aRect
1446 if( arect.Intersects( vertex, vertexNext ) )
1447 return true;
1448 }
1449 else if( outline.IsClosed() )
1450 {
1451 VECTOR2I vertexNext = outline.GetPoint( 0 );
1452
1453 // Test if this edge intersects aRect
1454 if( arect.Intersects( vertex, vertexNext ) )
1455 return true;
1456 }
1457 }
1458
1459 return false;
1460 };
1461
1462 switch( m_shape )
1463 {
1464 case SHAPE_T::CIRCLE:
1465 // Test if area intersects or contains the circle:
1466 if( aContained )
1467 {
1468 return arect.Contains( bbox );
1469 }
1470 else
1471 {
1472 // If the rectangle does not intersect the bounding box, this is a much quicker test
1473 if( !arect.Intersects( bbox ) )
1474 return false;
1475 else
1476 return arect.IntersectsCircleEdge( getCenter(), GetRadius(), GetWidth() );
1477 }
1478
1479 case SHAPE_T::ARC:
1480 // Test for full containment of this arc in the rect
1481 if( aContained )
1482 {
1483 return arect.Contains( bbox );
1484 }
1485 // Test if the rect crosses the arc
1486 else
1487 {
1488 if( !arect.Intersects( bbox ) )
1489 return false;
1490
1491 if( IsAnyFill() )
1492 {
1493 return ( arect.Intersects( getCenter(), GetStart() )
1494 || arect.Intersects( getCenter(), GetEnd() )
1495 || arect.IntersectsCircleEdge( getCenter(), GetRadius(), GetWidth() ) );
1496 }
1497 else
1498 {
1499 return arect.IntersectsCircleEdge( getCenter(), GetRadius(), GetWidth() );
1500 }
1501 }
1502
1503 case SHAPE_T::RECTANGLE:
1504 if( aContained )
1505 {
1506 return arect.Contains( bbox );
1507 }
1508 else if( m_cornerRadius > 0 )
1509 {
1511 SHAPE_POLY_SET poly;
1512 rr.TransformToPolygon( poly, getMaxError() );
1513
1514 // Account for the width of the line
1515 arect.Inflate( GetWidth() / 2 );
1516
1517 return checkOutline( poly.Outline( 0 ) );
1518 }
1519 else
1520 {
1521 std::vector<VECTOR2I> pts = GetRectCorners();
1522
1523 // Account for the width of the lines
1524 arect.Inflate( GetWidth() / 2 );
1525 return ( arect.Intersects( pts[0], pts[1] )
1526 || arect.Intersects( pts[1], pts[2] )
1527 || arect.Intersects( pts[2], pts[3] )
1528 || arect.Intersects( pts[3], pts[0] ) );
1529 }
1530
1531 case SHAPE_T::SEGMENT:
1532 if( aContained )
1533 {
1534 return arect.Contains( GetStart() ) && aRect.Contains( GetEnd() );
1535 }
1536 else
1537 {
1538 // Account for the width of the line
1539 arect.Inflate( GetWidth() / 2 );
1540 return arect.Intersects( GetStart(), GetEnd() );
1541 }
1542
1543 case SHAPE_T::POLY:
1544 if( aContained )
1545 {
1546 return arect.Contains( bbox );
1547 }
1548 else
1549 {
1550 // Fast test: if aRect is outside the polygon bounding box,
1551 // rectangles cannot intersect
1552 if( !arect.Intersects( bbox ) )
1553 return false;
1554
1555 // Account for the width of the line
1556 arect.Inflate( GetWidth() / 2 );
1557
1558 for( int ii = 0; ii < m_poly.OutlineCount(); ++ii )
1559 {
1560 if( checkOutline( m_poly.Outline( ii ) ) )
1561 return true;
1562 }
1563
1564 return false;
1565 }
1566
1567 case SHAPE_T::BEZIER:
1568 if( aContained )
1569 {
1570 return arect.Contains( bbox );
1571 }
1572 else
1573 {
1574 // Fast test: if aRect is outside the polygon bounding box,
1575 // rectangles cannot intersect
1576 if( !arect.Intersects( bbox ) )
1577 return false;
1578
1579 // Account for the width of the line
1580 arect.Inflate( GetWidth() / 2 );
1581 const std::vector<VECTOR2I>* pts = &m_bezierPoints;
1582 std::vector<VECTOR2I> updatedBezierPoints;
1583
1584 if( m_bezierPoints.empty() )
1585 {
1587 converter.GetPoly( updatedBezierPoints, aAccuracy / 2 );
1588 pts = &updatedBezierPoints;
1589 }
1590
1591 for( unsigned ii = 1; ii < pts->size(); ii++ )
1592 {
1593 VECTOR2I vertex = ( *pts )[ii - 1];
1594 VECTOR2I vertexNext = ( *pts )[ii];
1595
1596 // Test if the point is within aRect
1597 if( arect.Contains( vertex ) )
1598 return true;
1599
1600 // Test if this edge intersects aRect
1601 if( arect.Intersects( vertex, vertexNext ) )
1602 return true;
1603 }
1604
1605 return false;
1606 }
1607
1608 default:
1610 return false;
1611 }
1612}
1613
1614
1615bool EDA_SHAPE::hitTest( const SHAPE_LINE_CHAIN& aPoly, bool aContained ) const
1616{
1618
1619 return KIGEOM::ShapeHitTest( aPoly, shape, aContained );
1620}
1621
1622
1623std::vector<VECTOR2I> EDA_SHAPE::GetRectCorners() const
1624{
1625 std::vector<VECTOR2I> pts;
1626 VECTOR2I topLeft = GetStart();
1627 VECTOR2I botRight = GetEnd();
1628
1629 pts.emplace_back( topLeft );
1630 pts.emplace_back( botRight.x, topLeft.y );
1631 pts.emplace_back( botRight );
1632 pts.emplace_back( topLeft.x, botRight.y );
1633
1634 return pts;
1635}
1636
1637
1638std::vector<VECTOR2I> EDA_SHAPE::GetCornersInSequence( EDA_ANGLE angle ) const
1639{
1640 std::vector<VECTOR2I> pts;
1641
1642 angle.Normalize();
1643
1644 BOX2I bbox = getBoundingBox();
1645 bbox.Normalize();
1646
1647 if( angle.IsCardinal() )
1648 {
1649 if( angle == ANGLE_0 )
1650 {
1651 pts.emplace_back( VECTOR2I( bbox.GetLeft(), bbox.GetTop() ) );
1652 pts.emplace_back( VECTOR2I( bbox.GetRight(), bbox.GetTop() ) );
1653 pts.emplace_back( VECTOR2I( bbox.GetRight(), bbox.GetBottom() ) );
1654 pts.emplace_back( VECTOR2I( bbox.GetLeft(), bbox.GetBottom() ) );
1655 }
1656 else if( angle == ANGLE_90 )
1657 {
1658 pts.emplace_back( VECTOR2I( bbox.GetLeft(), bbox.GetBottom() ) );
1659 pts.emplace_back( VECTOR2I( bbox.GetLeft(), bbox.GetTop() ) );
1660 pts.emplace_back( VECTOR2I( bbox.GetRight(), bbox.GetTop() ) );
1661 pts.emplace_back( VECTOR2I( bbox.GetRight(), bbox.GetBottom() ) );
1662 }
1663 else if( angle == ANGLE_180 )
1664 {
1665 pts.emplace_back( VECTOR2I( bbox.GetRight(), bbox.GetBottom() ) );
1666 pts.emplace_back( VECTOR2I( bbox.GetLeft(), bbox.GetBottom() ) );
1667 pts.emplace_back( VECTOR2I( bbox.GetLeft(), bbox.GetTop() ) );
1668 pts.emplace_back( VECTOR2I( bbox.GetRight(), bbox.GetTop() ) );
1669 }
1670 else if( angle == ANGLE_270 )
1671 {
1672 pts.emplace_back( VECTOR2I( bbox.GetRight(), bbox.GetTop() ) );
1673 pts.emplace_back( VECTOR2I( bbox.GetRight(), bbox.GetBottom() ) );
1674 pts.emplace_back( VECTOR2I( bbox.GetLeft(), bbox.GetBottom() ) );
1675 pts.emplace_back( VECTOR2I( bbox.GetLeft(), bbox.GetTop() ) );
1676 }
1677 }
1678 else
1679 {
1680 // This function was originally located in pcb_textbox.cpp and was later moved to eda_shape.cpp.
1681 // As a result of this move, access to getCorners was lost, since it is defined in the PCB_SHAPE
1682 // class within pcb_shape.cpp and is not available in the current context.
1683 //
1684 // Additionally, GetRectCorners() cannot be used here, as it assumes the rectangle is rotated by
1685 // a cardinal angle. In non-cardinal cases, it returns incorrect values (e.g., (0, 0)).
1686 //
1687 // To address this, a portion of the getCorners implementation for SHAPE_T::POLY elements
1688 // has been replicated here to restore the correct behavior.
1689 std::vector<VECTOR2I> corners;
1690
1691 for( int ii = 0; ii < GetPolyShape().OutlineCount(); ++ii )
1692 {
1693 for( const VECTOR2I& pt : GetPolyShape().Outline( ii ).CPoints() )
1694 corners.emplace_back( pt );
1695 }
1696
1697 while( corners.size() < 4 )
1698 corners.emplace_back( corners.back() + VECTOR2I( 10, 10 ) );
1699
1700 VECTOR2I minX = corners[0];
1701 VECTOR2I maxX = corners[0];
1702 VECTOR2I minY = corners[0];
1703 VECTOR2I maxY = corners[0];
1704
1705 for( const VECTOR2I& corner : corners )
1706 {
1707 if( corner.x < minX.x )
1708 minX = corner;
1709
1710 if( corner.x > maxX.x )
1711 maxX = corner;
1712
1713 if( corner.y < minY.y )
1714 minY = corner;
1715
1716 if( corner.y > maxY.y )
1717 maxY = corner;
1718 }
1719
1720 if( angle < ANGLE_90 )
1721 {
1722 pts.emplace_back( minX );
1723 pts.emplace_back( minY );
1724 pts.emplace_back( maxX );
1725 pts.emplace_back( maxY );
1726 }
1727 else if( angle < ANGLE_180 )
1728 {
1729 pts.emplace_back( maxY );
1730 pts.emplace_back( minX );
1731 pts.emplace_back( minY );
1732 pts.emplace_back( maxX );
1733 }
1734 else if( angle < ANGLE_270 )
1735 {
1736 pts.emplace_back( maxX );
1737 pts.emplace_back( maxY );
1738 pts.emplace_back( minX );
1739 pts.emplace_back( minY );
1740 }
1741 else
1742 {
1743 pts.emplace_back( minY );
1744 pts.emplace_back( maxX );
1745 pts.emplace_back( maxY );
1746 pts.emplace_back( minX );
1747 }
1748 }
1749
1750 return pts;
1751}
1752
1753
1755{
1756 // Start, end, and each inflection point the arc crosses will enclose the entire arc.
1757 // Only include the center when filled; it's not necessarily inside the BB of an unfilled
1758 // arc with a small included angle.
1759 aBBox.SetOrigin( m_start );
1760 aBBox.Merge( m_end );
1761
1762 if( IsAnyFill() )
1763 aBBox.Merge( m_arcCenter );
1764
1765 int radius = GetRadius();
1766 EDA_ANGLE t1, t2;
1767
1768 CalcArcAngles( t1, t2 );
1769
1770 t1.Normalize();
1771 t2.Normalize();
1772
1773 if( t2 > t1 )
1774 {
1775 if( t1 < ANGLE_0 && t2 > ANGLE_0 )
1776 aBBox.Merge( VECTOR2I( m_arcCenter.x + radius, m_arcCenter.y ) ); // right
1777
1778 if( t1 < ANGLE_90 && t2 > ANGLE_90 )
1779 aBBox.Merge( VECTOR2I( m_arcCenter.x, m_arcCenter.y + radius ) ); // down
1780
1781 if( t1 < ANGLE_180 && t2 > ANGLE_180 )
1782 aBBox.Merge( VECTOR2I( m_arcCenter.x - radius, m_arcCenter.y ) ); // left
1783
1784 if( t1 < ANGLE_270 && t2 > ANGLE_270 )
1785 aBBox.Merge( VECTOR2I( m_arcCenter.x, m_arcCenter.y - radius ) ); // up
1786 }
1787 else
1788 {
1789 if( t1 < ANGLE_0 || t2 > ANGLE_0 )
1790 aBBox.Merge( VECTOR2I( m_arcCenter.x + radius, m_arcCenter.y ) ); // right
1791
1792 if( t1 < ANGLE_90 || t2 > ANGLE_90 )
1793 aBBox.Merge( VECTOR2I( m_arcCenter.x, m_arcCenter.y + radius ) ); // down
1794
1795 if( t1 < ANGLE_180 || t2 > ANGLE_180 )
1796 aBBox.Merge( VECTOR2I( m_arcCenter.x - radius, m_arcCenter.y ) ); // left
1797
1798 if( t1 < ANGLE_270 || t2 > ANGLE_270 )
1799 aBBox.Merge( VECTOR2I( m_arcCenter.x, m_arcCenter.y - radius ) ); // up
1800 }
1801}
1802
1803
1804void EDA_SHAPE::SetPolyPoints( const std::vector<VECTOR2I>& aPoints )
1805{
1806 m_poly.RemoveAllContours();
1807 m_poly.NewOutline();
1808
1809 for( const VECTOR2I& p : aPoints )
1810 m_poly.Append( p.x, p.y );
1811}
1812
1813
1814std::vector<SHAPE*> EDA_SHAPE::makeEffectiveShapes( bool aEdgeOnly, bool aLineChainOnly, bool aHittesting ) const
1815{
1816 std::vector<SHAPE*> effectiveShapes;
1817 int width = GetEffectiveWidth();
1818 bool solidFill = IsSolidFill()
1819 || IsHatchedFill()
1820 || IsProxyItem()
1821 || ( aHittesting && IsFilledForHitTesting() );
1822
1823 if( aEdgeOnly )
1824 solidFill = false;
1825
1826 switch( m_shape )
1827 {
1828 case SHAPE_T::ARC:
1829 effectiveShapes.emplace_back( new SHAPE_ARC( m_arcCenter, m_start, GetArcAngle(), width ) );
1830 break;
1831
1832 case SHAPE_T::SEGMENT:
1833 effectiveShapes.emplace_back( new SHAPE_SEGMENT( m_start, m_end, width ) );
1834 break;
1835
1836 case SHAPE_T::RECTANGLE:
1837 {
1838 if( m_cornerRadius > 0 )
1839 {
1841 SHAPE_POLY_SET poly;
1842 rr.TransformToPolygon( poly, getMaxError() );
1843 SHAPE_LINE_CHAIN outline = poly.Outline( 0 );
1844
1845 if( solidFill )
1846 effectiveShapes.emplace_back( new SHAPE_SIMPLE( outline ) );
1847
1848 if( width > 0 || !solidFill )
1849 {
1850 std::set<size_t> arcsHandled;
1851
1852 for( int ii = 0; ii < outline.SegmentCount(); ++ii )
1853 {
1854 if( outline.IsArcSegment( ii ) )
1855 {
1856 size_t arcIndex = outline.ArcIndex( ii );
1857
1858 if( !arcsHandled.contains( arcIndex ) )
1859 {
1860 arcsHandled.insert( arcIndex );
1861 effectiveShapes.emplace_back( new SHAPE_ARC( outline.Arc( arcIndex ), width ) );
1862 }
1863 }
1864 else
1865 {
1866 effectiveShapes.emplace_back( new SHAPE_SEGMENT( outline.Segment( ii ), width ) );
1867 }
1868 }
1869 }
1870 }
1871 else
1872 {
1873 std::vector<VECTOR2I> pts = GetRectCorners();
1874
1875 if( solidFill )
1876 effectiveShapes.emplace_back( new SHAPE_SIMPLE( pts ) );
1877
1878 if( width > 0 || !solidFill )
1879 {
1880 effectiveShapes.emplace_back( new SHAPE_SEGMENT( pts[0], pts[1], width ) );
1881 effectiveShapes.emplace_back( new SHAPE_SEGMENT( pts[1], pts[2], width ) );
1882 effectiveShapes.emplace_back( new SHAPE_SEGMENT( pts[2], pts[3], width ) );
1883 effectiveShapes.emplace_back( new SHAPE_SEGMENT( pts[3], pts[0], width ) );
1884 }
1885 }
1886 break;
1887 }
1888
1889 case SHAPE_T::CIRCLE:
1890 {
1891 if( solidFill )
1892 effectiveShapes.emplace_back( new SHAPE_CIRCLE( getCenter(), GetRadius() ) );
1893
1894 if( width > 0 || !solidFill )
1895 effectiveShapes.emplace_back( new SHAPE_ARC( getCenter(), GetEnd(), ANGLE_360, width ) );
1896
1897 break;
1898 }
1899
1900 case SHAPE_T::BEZIER:
1901 {
1902 std::vector<VECTOR2I> bezierPoints = buildBezierToSegmentsPointsList( getMaxError() );
1903 VECTOR2I start_pt = bezierPoints[0];
1904
1905 for( unsigned int jj = 1; jj < bezierPoints.size(); jj++ )
1906 {
1907 VECTOR2I end_pt = bezierPoints[jj];
1908 effectiveShapes.emplace_back( new SHAPE_SEGMENT( start_pt, end_pt, width ) );
1909 start_pt = end_pt;
1910 }
1911
1912 break;
1913 }
1914
1915 case SHAPE_T::POLY:
1916 {
1917 if( GetPolyShape().OutlineCount() == 0 ) // malformed/empty polygon
1918 break;
1919
1920 for( int ii = 0; ii < GetPolyShape().OutlineCount(); ++ii )
1921 {
1922 const SHAPE_LINE_CHAIN& l = GetPolyShape().COutline( ii );
1923
1924 if( solidFill )
1925 effectiveShapes.emplace_back( new SHAPE_SIMPLE( l ) );
1926
1927 if( width > 0 || !IsSolidFill() || aEdgeOnly )
1928 {
1929 int segCount = l.SegmentCount();
1930
1931 if( aLineChainOnly && l.IsClosed() )
1932 segCount--; // Treat closed chain as open
1933
1934 for( int jj = 0; jj < segCount; jj++ )
1935 effectiveShapes.emplace_back( new SHAPE_SEGMENT( l.CSegment( jj ), width ) );
1936 }
1937 }
1938 }
1939 break;
1940
1941 default:
1943 break;
1944 }
1945
1946 return effectiveShapes;
1947}
1948
1949
1950std::vector<VECTOR2I> EDA_SHAPE::GetPolyPoints() const
1951{
1952 std::vector<VECTOR2I> points;
1953
1954 for( int ii = 0; ii < m_poly.OutlineCount(); ++ii )
1955 {
1956 const SHAPE_LINE_CHAIN& outline = m_poly.COutline( ii );
1957 int pointCount = outline.PointCount();
1958
1959 if( pointCount )
1960 {
1961 points.reserve( points.size() + pointCount );
1962
1963 for( const VECTOR2I& pt : outline.CPoints() )
1964 points.emplace_back( pt );
1965 }
1966 }
1967
1968 return points;
1969}
1970
1971
1973{
1974 // return true if the polygonal shape is valid (has more than 2 points)
1975 return GetPolyShape().OutlineCount() > 0 && GetPolyShape().Outline( 0 ).PointCount() > 2;
1976}
1977
1978
1980{
1981 // return the number of corners of the polygonal shape
1982 // this shape is expected to be only one polygon without hole
1983 return GetPolyShape().OutlineCount() ? GetPolyShape().VertexCount( 0 ) : 0;
1984}
1985
1986
1987void EDA_SHAPE::beginEdit( const VECTOR2I& aPosition )
1988{
1989 switch( GetShape() )
1990 {
1991 case SHAPE_T::SEGMENT:
1992 case SHAPE_T::CIRCLE:
1993 case SHAPE_T::RECTANGLE:
1994 SetStart( aPosition );
1995 SetEnd( aPosition );
1996 break;
1997
1998 case SHAPE_T::ARC:
1999 SetArcGeometry( aPosition, aPosition, aPosition );
2000 m_editState = 1;
2001 break;
2002
2003 case SHAPE_T::BEZIER:
2004 SetStart( aPosition );
2005 SetEnd( aPosition );
2006 SetBezierC1( aPosition );
2007 SetBezierC2( aPosition );
2008 m_editState = 1;
2009
2011 break;
2012
2013 case SHAPE_T::POLY:
2014 m_poly.NewOutline();
2015 m_poly.Outline( 0 ).SetClosed( false );
2016
2017 // Start and end of the first segment (co-located for now)
2018 m_poly.Outline( 0 ).Append( aPosition );
2019 m_poly.Outline( 0 ).Append( aPosition, true );
2020 break;
2021
2022 default:
2024 }
2025}
2026
2027
2028bool EDA_SHAPE::continueEdit( const VECTOR2I& aPosition )
2029{
2030 switch( GetShape() )
2031 {
2032 case SHAPE_T::ARC:
2033 case SHAPE_T::SEGMENT:
2034 case SHAPE_T::CIRCLE:
2035 case SHAPE_T::RECTANGLE:
2036 return false;
2037
2038 case SHAPE_T::BEZIER:
2039 if( m_editState == 3 )
2040 return false;
2041
2042 m_editState++;
2043 return true;
2044
2045 case SHAPE_T::POLY:
2046 {
2047 SHAPE_LINE_CHAIN& poly = m_poly.Outline( 0 );
2048
2049 // do not add zero-length segments
2050 if( poly.CPoint( (int) poly.GetPointCount() - 2 ) != poly.CLastPoint() )
2051 poly.Append( aPosition, true );
2052 }
2053 return true;
2054
2055 default:
2057 return false;
2058 }
2059}
2060
2061
2062void EDA_SHAPE::calcEdit( const VECTOR2I& aPosition )
2063{
2064#define sq( x ) pow( x, 2 )
2065
2066 switch( GetShape() )
2067 {
2068 case SHAPE_T::SEGMENT:
2069 case SHAPE_T::CIRCLE:
2070 case SHAPE_T::RECTANGLE:
2071 SetEnd( aPosition );
2072 break;
2073
2074 case SHAPE_T::BEZIER:
2075 {
2076 switch( m_editState )
2077 {
2078 case 0:
2079 SetStart( aPosition );
2080 SetEnd( aPosition );
2081 SetBezierC1( aPosition );
2082 SetBezierC2( aPosition );
2083 break;
2084
2085 case 1:
2086 SetBezierC2( aPosition );
2087 SetEnd( aPosition );
2088 break;
2089
2090 case 2:
2091 SetBezierC1( aPosition );
2092 break;
2093
2094 case 3:
2095 SetBezierC2( aPosition );
2096 break;
2097 }
2098
2100 }
2101 break;
2102
2103 case SHAPE_T::ARC:
2104 {
2105 double radius = GetRadius();
2106 EDA_ANGLE lastAngle = GetArcAngle();
2107
2108 // Edit state 0: drawing: place start
2109 // Edit state 1: drawing: place end (center calculated for 90-degree subtended angle)
2110 // Edit state 2: point edit: move start (center calculated for invariant subtended angle)
2111 // Edit state 3: point edit: move end (center calculated for invariant subtended angle)
2112 // Edit state 4: point edit: move center
2113 // Edit state 5: point edit: move arc-mid-point
2114
2115 switch( m_editState )
2116 {
2117 case 0:
2118 SetArcGeometry( aPosition, aPosition, aPosition );
2119 return;
2120
2121 case 1:
2122 m_end = aPosition;
2123 radius = m_start.Distance( m_end ) * M_SQRT1_2;
2124 break;
2125
2126 case 2:
2127 case 3:
2128 {
2129 VECTOR2I v = m_start - m_end;
2130 double chordBefore = v.SquaredEuclideanNorm();
2131
2132 if( m_editState == 2 )
2133 m_start = aPosition;
2134 else
2135 m_end = aPosition;
2136
2137 v = m_start - m_end;
2138
2139 double chordAfter = v.SquaredEuclideanNorm();
2140 double ratio = 0.0;
2141
2142 if( chordBefore > 0 )
2143 ratio = chordAfter / chordBefore;
2144
2145 if( ratio != 0 )
2146 radius = std::max( sqrt( sq( radius ) * ratio ), sqrt( chordAfter ) / 2 );
2147 break;
2148 }
2149
2150 case 4:
2151 {
2152 double radialA = m_start.Distance( aPosition );
2153 double radialB = m_end.Distance( aPosition );
2154 radius = ( radialA + radialB ) / 2.0;
2155 break;
2156 }
2157
2158 case 5:
2159 SetArcGeometry( GetStart(), aPosition, GetEnd() );
2160 return;
2161 }
2162
2163 // Calculate center based on start, end, and radius
2164 //
2165 // Let 'l' be the length of the chord and 'm' the middle point of the chord
2166 double l = m_start.Distance( m_end );
2167 VECTOR2D m = ( m_start + m_end ) / 2;
2168 double sqRadDiff = ( radius * radius ) - ( l * l ) / 4.0;
2169
2170 // Calculate 'd', the vector from the chord midpoint to the center
2171 VECTOR2D d;
2172
2173 if( l > 0 && sqRadDiff >= 0 )
2174 {
2175 d.x = sqrt( sqRadDiff ) * ( m_start.y - m_end.y ) / l;
2176 d.y = sqrt( sqRadDiff ) * ( m_end.x - m_start.x ) / l;
2177 }
2178
2179 VECTOR2I c1 = KiROUND( m + d );
2180 VECTOR2I c2 = KiROUND( m - d );
2181
2182 // Solution gives us 2 centers; we need to pick one:
2183 switch( m_editState )
2184 {
2185 case 1:
2186 // Keep arc clockwise while drawing i.e. arc angle = 90 deg.
2187 // it can be 90 or 270 deg depending on the arc center choice (c1 or c2)
2188 m_arcCenter = c1; // first trial
2189
2190 if( GetArcAngle() > ANGLE_180 )
2191 m_arcCenter = c2;
2192
2193 break;
2194
2195 case 2:
2196 case 3:
2197 // Pick the one of c1, c2 to keep arc on the same side
2198 m_arcCenter = c1; // first trial
2199
2200 if( ( lastAngle < ANGLE_180 ) != ( GetArcAngle() < ANGLE_180 ) )
2201 m_arcCenter = c2;
2202
2203 break;
2204
2205 case 4:
2206 // Pick the one closer to the mouse position
2207 m_arcCenter = c1.Distance( aPosition ) < c2.Distance( aPosition ) ? c1 : c2;
2208 break;
2209 }
2210
2211 break;
2212 }
2213
2214 case SHAPE_T::POLY:
2215 m_poly.Outline( 0 ).SetPoint( m_poly.Outline( 0 ).GetPointCount() - 1, aPosition );
2216 break;
2217
2218 default:
2220 }
2221}
2222
2223
2224void EDA_SHAPE::endEdit( bool aClosed )
2225{
2226 switch( GetShape() )
2227 {
2228 case SHAPE_T::ARC:
2229 case SHAPE_T::SEGMENT:
2230 case SHAPE_T::CIRCLE:
2231 case SHAPE_T::RECTANGLE:
2232 case SHAPE_T::BEZIER:
2233 break;
2234
2235 case SHAPE_T::POLY:
2236 {
2237 SHAPE_LINE_CHAIN& poly = m_poly.Outline( 0 );
2238
2239 // do not include last point twice
2240 if( poly.GetPointCount() > 2 )
2241 {
2242 if( poly.CPoint( poly.GetPointCount() - 2 ) == poly.CLastPoint() )
2243 {
2244 poly.SetClosed( aClosed );
2245 }
2246 else
2247 {
2248 poly.SetClosed( false );
2249 poly.Remove( poly.GetPointCount() - 1 );
2250 }
2251 }
2252
2253 break;
2254 }
2255
2256 default:
2258 }
2259}
2260
2261
2263{
2264 EDA_SHAPE* image = dynamic_cast<EDA_SHAPE*>( aImage );
2265 assert( image );
2266
2267 #define SWAPITEM( x ) std::swap( x, image->x )
2268 SWAPITEM( m_stroke );
2269 SWAPITEM( m_start );
2270 SWAPITEM( m_end );
2272 SWAPITEM( m_shape );
2276 SWAPITEM( m_poly );
2278 SWAPITEM( m_fill );
2282 #undef SWAPITEM
2283
2284 m_hatchingDirty = true;
2285}
2286
2287
2288int EDA_SHAPE::Compare( const EDA_SHAPE* aOther ) const
2289{
2290#define EPSILON 2 // Should be enough for rounding errors on calculated items
2291
2292#define TEST( a, b ) { if( a != b ) return a - b; }
2293#define TEST_E( a, b ) { if( abs( a - b ) > EPSILON ) return a - b; }
2294#define TEST_PT( a, b ) { TEST_E( a.x, b.x ); TEST_E( a.y, b.y ); }
2295
2296 TEST_PT( m_start, aOther->m_start );
2297 TEST_PT( m_end, aOther->m_end );
2298
2299 TEST( (int) m_shape, (int) aOther->m_shape );
2300
2302 {
2304 }
2305 else if( m_shape == SHAPE_T::ARC )
2306 {
2307 TEST_PT( GetArcMid(), aOther->GetArcMid() );
2308 }
2309 else if( m_shape == SHAPE_T::BEZIER )
2310 {
2311 TEST_PT( m_bezierC1, aOther->m_bezierC1 );
2312 TEST_PT( m_bezierC2, aOther->m_bezierC2 );
2313 }
2314 else if( m_shape == SHAPE_T::POLY )
2315 {
2316 TEST( m_poly.TotalVertices(), aOther->m_poly.TotalVertices() );
2317 }
2318
2319 for( size_t ii = 0; ii < m_bezierPoints.size(); ++ii )
2320 TEST_PT( m_bezierPoints[ii], aOther->m_bezierPoints[ii] );
2321
2322 for( int ii = 0; ii < m_poly.TotalVertices(); ++ii )
2323 TEST_PT( m_poly.CVertex( ii ), aOther->m_poly.CVertex( ii ) );
2324
2325 TEST_E( m_stroke.GetWidth(), aOther->m_stroke.GetWidth() );
2326 TEST( (int) m_stroke.GetLineStyle(), (int) aOther->m_stroke.GetLineStyle() );
2327 TEST( (int) m_fill, (int) aOther->m_fill );
2328
2329 return 0;
2330}
2331
2332
2333void EDA_SHAPE::TransformShapeToPolygon( SHAPE_POLY_SET& aBuffer, int aClearance, int aError,
2334 ERROR_LOC aErrorLoc, bool ignoreLineWidth, bool includeFill ) const
2335{
2336 bool solidFill = IsSolidFill() || ( IsHatchedFill() && !includeFill ) || IsProxyItem();
2337 int width = ignoreLineWidth ? 0 : GetWidth();
2338
2339 width += 2 * aClearance;
2340
2341 switch( m_shape )
2342 {
2343 case SHAPE_T::CIRCLE:
2344 {
2345 int r = GetRadius();
2346
2347 if( solidFill )
2348 TransformCircleToPolygon( aBuffer, getCenter(), r + width / 2, aError, aErrorLoc );
2349 else
2350 TransformRingToPolygon( aBuffer, getCenter(), r, width, aError, aErrorLoc );
2351
2352 break;
2353 }
2354
2355 case SHAPE_T::RECTANGLE:
2356 {
2357 if( GetCornerRadius() > 0 )
2358 {
2359 // Use specialized function for rounded rectangles
2361 VECTOR2I position = GetStart() + size / 2; // Center position
2362
2363 if( solidFill )
2364 {
2366 0.0, 0, width / 2, aError, aErrorLoc );
2367 }
2368 else
2369 {
2370 // Export outline as a set of thick segments:
2371 SHAPE_POLY_SET poly;
2373 0.0, 0, 0, aError, aErrorLoc );
2374 SHAPE_LINE_CHAIN& outline = poly.Outline( 0 );
2375 outline.SetClosed( true );
2376
2377 for( int ii = 0; ii < outline.PointCount(); ii++ )
2378 {
2379 TransformOvalToPolygon( aBuffer, outline.CPoint( ii ), outline.CPoint( ii+1 ), width,
2380 aError, aErrorLoc );
2381 }
2382 }
2383 }
2384 else
2385 {
2386 std::vector<VECTOR2I> pts = GetRectCorners();
2387
2388 if( solidFill )
2389 {
2390 aBuffer.NewOutline();
2391
2392 for( const VECTOR2I& pt : pts )
2393 aBuffer.Append( pt );
2394 }
2395
2396 if( width > 0 || !solidFill )
2397 {
2398 // Add in segments
2399 TransformOvalToPolygon( aBuffer, pts[0], pts[1], width, aError, aErrorLoc );
2400 TransformOvalToPolygon( aBuffer, pts[1], pts[2], width, aError, aErrorLoc );
2401 TransformOvalToPolygon( aBuffer, pts[2], pts[3], width, aError, aErrorLoc );
2402 TransformOvalToPolygon( aBuffer, pts[3], pts[0], width, aError, aErrorLoc );
2403 }
2404 }
2405
2406 break;
2407 }
2408
2409 case SHAPE_T::ARC:
2410 TransformArcToPolygon( aBuffer, GetStart(), GetArcMid(), GetEnd(), width, aError, aErrorLoc );
2411 break;
2412
2413 case SHAPE_T::SEGMENT:
2414 TransformOvalToPolygon( aBuffer, GetStart(), GetEnd(), width, aError, aErrorLoc );
2415 break;
2416
2417 case SHAPE_T::POLY:
2418 {
2419 if( !IsPolyShapeValid() )
2420 break;
2421
2422 if( solidFill )
2423 {
2424 for( int ii = 0; ii < m_poly.OutlineCount(); ++ii )
2425 {
2426 const SHAPE_LINE_CHAIN& poly = m_poly.Outline( ii );
2427 SHAPE_POLY_SET tmp;
2428 tmp.NewOutline();
2429
2430 for( int jj = 0; jj < (int) poly.GetPointCount(); ++jj )
2431 tmp.Append( poly.GetPoint( jj ) );
2432
2433 if( width > 0 )
2434 {
2435 int inflate = width / 2;
2436
2437 if( aErrorLoc == ERROR_OUTSIDE )
2438 inflate += aError;
2439
2440 tmp.Inflate( inflate, CORNER_STRATEGY::ROUND_ALL_CORNERS, aError );
2441 }
2442
2443 aBuffer.Append( tmp );
2444 }
2445 }
2446 else
2447 {
2448 for( int ii = 0; ii < m_poly.OutlineCount(); ++ii )
2449 {
2450 const SHAPE_LINE_CHAIN& poly = m_poly.Outline( ii );
2451
2452 for( int jj = 0; jj < (int) poly.SegmentCount(); ++jj )
2453 {
2454 const SEG& seg = poly.GetSegment( jj );
2455 TransformOvalToPolygon( aBuffer, seg.A, seg.B, width, aError, aErrorLoc );
2456 }
2457 }
2458 }
2459
2460 break;
2461 }
2462
2463 case SHAPE_T::BEZIER:
2464 {
2465 std::vector<VECTOR2I> ctrlPts = { GetStart(), GetBezierC1(), GetBezierC2(), GetEnd() };
2466 BEZIER_POLY converter( ctrlPts );
2467 std::vector<VECTOR2I> poly;
2468 converter.GetPoly( poly, aError );
2469
2470 for( unsigned ii = 1; ii < poly.size(); ii++ )
2471 TransformOvalToPolygon( aBuffer, poly[ii - 1], poly[ii], width, aError, aErrorLoc );
2472
2473 break;
2474 }
2475
2476 default:
2478 break;
2479 }
2480
2481 if( IsHatchedFill() && includeFill )
2482 {
2483 for( int ii = 0; ii < GetHatching().OutlineCount(); ++ii )
2484 aBuffer.AddOutline( GetHatching().COutline( ii ) );
2485 }
2486}
2487
2488
2489void EDA_SHAPE::SetWidth( int aWidth )
2490{
2491 m_stroke.SetWidth( aWidth );
2492 m_hatchingDirty = true;
2493}
2494
2495
2497{
2498 m_stroke.SetLineStyle( aStyle );
2499}
2500
2501
2503{
2504 if( m_stroke.GetLineStyle() != LINE_STYLE::DEFAULT )
2505 return m_stroke.GetLineStyle();
2506
2507 return LINE_STYLE::SOLID;
2508}
2509
2510
2511bool EDA_SHAPE::operator==( const EDA_SHAPE& aOther ) const
2512{
2513 if( GetShape() != aOther.GetShape() )
2514 return false;
2515
2516 if( m_fill != aOther.m_fill )
2517 return false;
2518
2519 if( m_stroke.GetWidth() != aOther.m_stroke.GetWidth() )
2520 return false;
2521
2522 if( m_stroke.GetLineStyle() != aOther.m_stroke.GetLineStyle() )
2523 return false;
2524
2525 if( m_fillColor != aOther.m_fillColor )
2526 return false;
2527
2528 if( m_start != aOther.m_start )
2529 return false;
2530
2531 if( m_end != aOther.m_end )
2532 return false;
2533
2534 if( m_arcCenter != aOther.m_arcCenter )
2535 return false;
2536
2537 if( m_bezierC1 != aOther.m_bezierC1 )
2538 return false;
2539
2540 if( m_bezierC2 != aOther.m_bezierC2 )
2541 return false;
2542
2543 if( m_bezierPoints != aOther.m_bezierPoints )
2544 return false;
2545
2546 for( int ii = 0; ii < m_poly.TotalVertices(); ++ii )
2547 {
2548 if( m_poly.CVertex( ii ) != aOther.m_poly.CVertex( ii ) )
2549 return false;
2550 }
2551
2552 return true;
2553}
2554
2555
2556double EDA_SHAPE::Similarity( const EDA_SHAPE& aOther ) const
2557{
2558 if( GetShape() != aOther.GetShape() )
2559 return 0.0;
2560
2561 double similarity = 1.0;
2562
2563 if( m_fill != aOther.m_fill )
2564 similarity *= 0.9;
2565
2566 if( m_stroke.GetWidth() != aOther.m_stroke.GetWidth() )
2567 similarity *= 0.9;
2568
2569 if( m_stroke.GetLineStyle() != aOther.m_stroke.GetLineStyle() )
2570 similarity *= 0.9;
2571
2572 if( m_fillColor != aOther.m_fillColor )
2573 similarity *= 0.9;
2574
2575 if( m_start != aOther.m_start )
2576 similarity *= 0.9;
2577
2578 if( m_end != aOther.m_end )
2579 similarity *= 0.9;
2580
2581 if( m_arcCenter != aOther.m_arcCenter )
2582 similarity *= 0.9;
2583
2584 if( m_bezierC1 != aOther.m_bezierC1 )
2585 similarity *= 0.9;
2586
2587 if( m_bezierC2 != aOther.m_bezierC2 )
2588 similarity *= 0.9;
2589
2590 {
2591 int m = m_bezierPoints.size();
2592 int n = aOther.m_bezierPoints.size();
2593
2594 size_t longest = alg::longest_common_subset( m_bezierPoints, aOther.m_bezierPoints );
2595
2596 similarity *= std::pow( 0.9, m + n - 2 * longest );
2597 }
2598
2599 {
2600 int m = m_poly.TotalVertices();
2601 int n = aOther.m_poly.TotalVertices();
2602 std::vector<VECTOR2I> poly;
2603 std::vector<VECTOR2I> otherPoly;
2604 VECTOR2I lastPt( 0, 0 );
2605
2606 // We look for the longest common subset of the two polygons, but we need to
2607 // offset each point because we're actually looking for overall similarity, not just
2608 // exact matches. So if the zone is moved by 1IU, we only want one point to be
2609 // considered "moved" rather than the entire polygon. In this case, the first point
2610 // will not be a match but the rest of the sequence will.
2611 for( int ii = 0; ii < m; ++ii )
2612 {
2613 poly.emplace_back( lastPt - m_poly.CVertex( ii ) );
2614 lastPt = m_poly.CVertex( ii );
2615 }
2616
2617 lastPt = VECTOR2I( 0, 0 );
2618
2619 for( int ii = 0; ii < n; ++ii )
2620 {
2621 otherPoly.emplace_back( lastPt - aOther.m_poly.CVertex( ii ) );
2622 lastPt = aOther.m_poly.CVertex( ii );
2623 }
2624
2625 size_t longest = alg::longest_common_subset( poly, otherPoly );
2626
2627 similarity *= std::pow( 0.9, m + n - 2 * longest );
2628 }
2629
2630 return similarity;
2631}
2632
2633
2637
2638
2639static struct EDA_SHAPE_DESC
2640{
2642 {
2644 .Map( SHAPE_T::SEGMENT, _HKI( "Segment" ) )
2645 .Map( SHAPE_T::RECTANGLE, _HKI( "Rectangle" ) )
2646 .Map( SHAPE_T::ARC, _HKI( "Arc" ) )
2647 .Map( SHAPE_T::CIRCLE, _HKI( "Circle" ) )
2648 .Map( SHAPE_T::POLY, _HKI( "Polygon" ) )
2649 .Map( SHAPE_T::BEZIER, _HKI( "Bezier" ) );
2650
2652
2653 if( lineStyleEnum.Choices().GetCount() == 0 )
2654 {
2655 lineStyleEnum.Map( LINE_STYLE::SOLID, _HKI( "Solid" ) )
2656 .Map( LINE_STYLE::DASH, _HKI( "Dashed" ) )
2657 .Map( LINE_STYLE::DOT, _HKI( "Dotted" ) )
2658 .Map( LINE_STYLE::DASHDOT, _HKI( "Dash-Dot" ) )
2659 .Map( LINE_STYLE::DASHDOTDOT, _HKI( "Dash-Dot-Dot" ) );
2660 }
2661
2663
2664 if( hatchModeEnum.Choices().GetCount() == 0 )
2665 {
2666 hatchModeEnum.Map( UI_FILL_MODE::NONE, _HKI( "None" ) );
2667 hatchModeEnum.Map( UI_FILL_MODE::SOLID, _HKI( "Solid" ) );
2668 hatchModeEnum.Map( UI_FILL_MODE::HATCH, _HKI( "Hatch" ) );
2669 hatchModeEnum.Map( UI_FILL_MODE::REVERSE_HATCH, _HKI( "Reverse Hatch" ) );
2670 hatchModeEnum.Map( UI_FILL_MODE::CROSS_HATCH, _HKI( "Cross-hatch" ) );
2671 }
2672
2675
2676 auto isNotPolygonOrCircle =
2677 []( INSPECTABLE* aItem ) -> bool
2678 {
2679 // Polygons, unlike other shapes, have no meaningful start or end coordinates
2680 if( EDA_SHAPE* shape = dynamic_cast<EDA_SHAPE*>( aItem ) )
2681 return shape->GetShape() != SHAPE_T::POLY && shape->GetShape() != SHAPE_T::CIRCLE;
2682
2683 return false;
2684 };
2685
2686 auto isCircle =
2687 []( INSPECTABLE* aItem ) -> bool
2688 {
2689 // Polygons, unlike other shapes, have no meaningful start or end coordinates
2690 if( EDA_SHAPE* shape = dynamic_cast<EDA_SHAPE*>( aItem ) )
2691 return shape->GetShape() == SHAPE_T::CIRCLE;
2692
2693 return false;
2694 };
2695
2696 auto isRectangle =
2697 []( INSPECTABLE* aItem ) -> bool
2698 {
2699 // Polygons, unlike other shapes, have no meaningful start or end coordinates
2700 if( EDA_SHAPE* shape = dynamic_cast<EDA_SHAPE*>( aItem ) )
2701 return shape->GetShape() == SHAPE_T::RECTANGLE;
2702
2703 return false;
2704 };
2705
2706 const wxString shapeProps = _HKI( "Shape Properties" );
2707
2708 auto shape = new PROPERTY_ENUM<EDA_SHAPE, SHAPE_T>( _HKI( "Shape" ),
2710 propMgr.AddProperty( shape, shapeProps );
2711
2712 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "Start X" ),
2715 shapeProps )
2716 .SetAvailableFunc( isNotPolygonOrCircle );
2717 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "Start Y" ),
2720 shapeProps )
2721 .SetAvailableFunc( isNotPolygonOrCircle );
2722
2723 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "Center X" ),
2726 shapeProps )
2727 .SetAvailableFunc( isCircle );
2728
2729 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "Center Y" ),
2732 shapeProps )
2733 .SetAvailableFunc( isCircle );
2734
2735 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "Radius" ),
2738 shapeProps )
2739 .SetAvailableFunc( isCircle );
2740
2741 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "End X" ),
2744 shapeProps )
2745 .SetAvailableFunc( isNotPolygonOrCircle );
2746
2747 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "End Y" ),
2750 shapeProps )
2751 .SetAvailableFunc( isNotPolygonOrCircle );
2752
2753 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "Width" ),
2756 shapeProps )
2757 .SetAvailableFunc( isRectangle );
2758
2759 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "Height" ),
2762 shapeProps )
2763 .SetAvailableFunc( isRectangle );
2764
2765 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "Corner Radius" ),
2768 shapeProps )
2769 .SetAvailableFunc( isRectangle )
2770 .SetValidator( []( const wxAny&& aValue, EDA_ITEM* aItem ) -> VALIDATOR_RESULT
2771 {
2772 wxASSERT_MSG( aValue.CheckType<int>(),
2773 "Expecting int-containing value" );
2774
2775 int radius = aValue.As<int>();
2776
2777 EDA_SHAPE* prop_shape = dynamic_cast<EDA_SHAPE*>( aItem );
2778
2779 if( !prop_shape )
2780 {
2781 wxLogDebug( wxT( "Corner Radius Validator: Not an EDA_SHAPE" ) );
2782 return std::nullopt;
2783 }
2784
2785 int maxRadius = std::min( prop_shape->GetRectangleWidth(),
2786 prop_shape->GetRectangleHeight() ) / 2;
2787
2788 if( radius > maxRadius )
2789 return std::make_unique<VALIDATION_ERROR_TOO_LARGE<int>>( radius, maxRadius );
2790 else if( radius < 0 )
2791 return std::make_unique<VALIDATION_ERROR_TOO_SMALL<int>>( radius, 0 );
2792
2793 return std::nullopt;
2794 } );
2795
2796 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "Line Width" ),
2798 shapeProps );
2799
2800 propMgr.AddProperty( new PROPERTY_ENUM<EDA_SHAPE, LINE_STYLE>( _HKI( "Line Style" ),
2802 shapeProps );
2803
2804 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, COLOR4D>( _HKI( "Line Color" ),
2806 shapeProps )
2808
2809 auto angle = new PROPERTY<EDA_SHAPE, EDA_ANGLE>( _HKI( "Angle" ),
2812 angle->SetAvailableFunc(
2813 [=]( INSPECTABLE* aItem ) -> bool
2814 {
2815 if( EDA_SHAPE* curr_shape = dynamic_cast<EDA_SHAPE*>( aItem ) )
2816 return curr_shape->GetShape() == SHAPE_T::ARC;
2817
2818 return false;
2819 } );
2820 propMgr.AddProperty( angle, shapeProps );
2821
2822 auto fillAvailable =
2823 [=]( INSPECTABLE* aItem ) -> bool
2824 {
2825 if( EDA_ITEM* edaItem = dynamic_cast<EDA_ITEM*>( aItem ) )
2826 {
2827 // For some reason masking "Filled" and "Fill Color" at the
2828 // PCB_TABLECELL level doesn't work.
2829 if( edaItem->Type() == PCB_TABLECELL_T )
2830 return false;
2831 }
2832
2833 if( EDA_SHAPE* edaShape = dynamic_cast<EDA_SHAPE*>( aItem ) )
2834 {
2835 switch( edaShape->GetShape() )
2836 {
2837 case SHAPE_T::POLY:
2838 case SHAPE_T::RECTANGLE:
2839 case SHAPE_T::CIRCLE:
2840 case SHAPE_T::BEZIER:
2841 return true;
2842
2843 default:
2844 return false;
2845 }
2846 }
2847
2848 return false;
2849 };
2850
2853 shapeProps )
2854 .SetAvailableFunc( fillAvailable );
2855
2856 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, COLOR4D>( _HKI( "Fill Color" ),
2858 shapeProps )
2859 .SetAvailableFunc( fillAvailable )
2861 }
ERROR_LOC
When approximating an arc or circle, should the error be placed on the outside or inside of the curve...
@ ERROR_OUTSIDE
@ ERROR_INSIDE
BOX2< VECTOR2I > BOX2I
Definition box2.h:922
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:990
Bezier curves to polygon converter.
void GetPoly(std::vector< VECTOR2I > &aOutput, int aMaxError=10)
Convert a Bezier curve to a polygon.
constexpr BOX2< Vec > & Inflate(coord_type dx, coord_type dy)
Inflates the rectangle horizontally by dx and vertically by dy.
Definition box2.h:558
constexpr void SetOrigin(const Vec &pos)
Definition box2.h:237
constexpr BOX2< Vec > & Normalize()
Ensure that the height and width are positive.
Definition box2.h:146
constexpr coord_type GetY() const
Definition box2.h:208
constexpr size_type GetWidth() const
Definition box2.h:214
constexpr Vec Centre() const
Definition box2.h:97
constexpr coord_type GetX() const
Definition box2.h:207
bool IntersectsCircleEdge(const Vec &aCenter, const int aRadius, const int aWidth) const
Definition box2.h:523
constexpr BOX2< Vec > & Merge(const BOX2< Vec > &aRect)
Modify the position and size of the rectangle in order to contain aRect.
Definition box2.h:658
constexpr size_type GetHeight() const
Definition box2.h:215
constexpr coord_type GetLeft() const
Definition box2.h:228
constexpr bool Contains(const Vec &aPoint) const
Definition box2.h:168
constexpr coord_type GetRight() const
Definition box2.h:217
constexpr void SetEnd(coord_type x, coord_type y)
Definition box2.h:297
constexpr coord_type GetTop() const
Definition box2.h:229
constexpr bool Intersects(const BOX2< Vec > &aRect) const
Definition box2.h:311
constexpr coord_type GetBottom() const
Definition box2.h:222
EDA_ANGLE Normalize()
Definition eda_angle.h:229
bool IsCardinal() const
Definition eda_angle.cpp:40
EDA_ANGLE Normalize720()
Definition eda_angle.h:279
double AsRadians() const
Definition eda_angle.h:120
The base class for create windows for drawing purpose.
A base class for most all the KiCad significant classes used in schematics and boards.
Definition eda_item.h:98
UI_FILL_MODE GetFillModeProp() const
virtual int GetHatchLineSpacing() const
Definition eda_shape.h:159
EDA_ANGLE GetArcAngle() const
SHAPE_T m_shape
Definition eda_shape.h:493
void TransformShapeToPolygon(SHAPE_POLY_SET &aBuffer, int aClearance, int aError, ERROR_LOC aErrorLoc, bool ignoreLineWidth=false, bool includeFill=false) const
Convert the shape to a closed polygon.
void SetStartX(int x)
Definition eda_shape.h:191
bool m_proxyItem
Definition eda_shape.h:518
int m_cornerRadius
Definition eda_shape.h:503
bool m_hatchingDirty
Definition eda_shape.h:499
bool m_endsSwapped
Definition eda_shape.h:492
const VECTOR2I & GetBezierC2() const
Definition eda_shape.h:258
void SetBezierC2(const VECTOR2I &aPt)
Definition eda_shape.h:257
void move(const VECTOR2I &aMoveVector)
void SetCenter(const VECTOR2I &aCenter)
VECTOR2I getCenter() const
int GetStartY() const
Definition eda_shape.h:174
void SetFillModeProp(UI_FILL_MODE)
int m_editState
Definition eda_shape.h:517
virtual int getMaxError() const
Definition eda_shape.h:489
void rotate(const VECTOR2I &aRotCentre, const EDA_ANGLE &aAngle)
const std::vector< VECTOR2I > buildBezierToSegmentsPointsList(int aMaxError) const
const SHAPE_POLY_SET & GetHatching() const
Definition eda_shape.h:148
FILL_T GetFillMode() const
Definition eda_shape.h:142
virtual ~EDA_SHAPE()
Definition eda_shape.cpp:67
void SetCornerRadius(int aRadius)
long long int m_rectangleHeight
Definition eda_shape.h:501
void SetEndY(int aY)
Definition eda_shape.h:226
virtual int GetEffectiveWidth() const
Definition eda_shape.h:157
std::vector< VECTOR2I > GetPolyPoints() const
Duplicate the polygon outlines into a flat list of VECTOR2I points.
COLOR4D GetLineColor() const
Definition eda_shape.h:165
int GetEndX() const
Definition eda_shape.h:217
std::vector< SHAPE * > makeEffectiveShapes(bool aEdgeOnly, bool aLineChainOnly=false, bool aHittesting=false) const
Make a set of SHAPE objects representing the EDA_SHAPE.
SHAPE_POLY_SET m_hatching
Definition eda_shape.h:498
int GetRectangleWidth() const
void SetLineStyle(const LINE_STYLE aStyle)
void calcEdit(const VECTOR2I &aPosition)
void SetStartY(int y)
Definition eda_shape.h:184
virtual std::vector< SHAPE * > MakeEffectiveShapes(bool aEdgeOnly=false) const
Make a set of SHAPE objects representing the EDA_SHAPE.
Definition eda_shape.h:378
SHAPE_POLY_SET & GetPolyShape()
Definition eda_shape.h:336
void SetCenterY(int y)
Definition eda_shape.h:198
void CalcArcAngles(EDA_ANGLE &aStartAngle, EDA_ANGLE &aEndAngle) const
Calc arc start and end angles such that aStartAngle < aEndAngle.
std::vector< VECTOR2I > GetCornersInSequence(EDA_ANGLE angle) const
void ShapeGetMsgPanelInfo(EDA_DRAW_FRAME *aFrame, std::vector< MSG_PANEL_ITEM > &aList)
virtual bool isMoving() const
Definition eda_shape.h:473
bool operator==(const EDA_SHAPE &aOther) const
int GetRadius() const
SHAPE_T GetShape() const
Definition eda_shape.h:168
void SetPolyShape(const SHAPE_POLY_SET &aShape)
Definition eda_shape.h:344
bool Deserialize(const google::protobuf::Any &aContainer) override
Deserializes the given protobuf message into this object.
void SetRectangleHeight(const int &aHeight)
bool IsHatchedFill() const
Definition eda_shape.h:124
VECTOR2I m_arcCenter
Definition eda_shape.h:508
void SetCenterX(int x)
Definition eda_shape.h:205
virtual void SetFilled(bool aFlag)
Definition eda_shape.h:136
virtual bool IsFilledForHitTesting() const
Definition eda_shape.h:131
bool continueEdit(const VECTOR2I &aPosition)
wxString ShowShape() const
ARC_MID m_arcMidData
Definition eda_shape.h:509
void SetFillColor(const COLOR4D &aColor)
Definition eda_shape.h:153
int GetEndY() const
Definition eda_shape.h:216
bool hitTest(const VECTOR2I &aPosition, int aAccuracy=0) const
void SetCachedArcData(const VECTOR2I &aStart, const VECTOR2I &aMid, const VECTOR2I &aEnd, const VECTOR2I &aCenter)
Set the data used for mid point caching.
void SetEndX(int aX)
Definition eda_shape.h:233
void RebuildBezierToSegmentsPointsList(int aMaxError)
Rebuild the m_bezierPoints vertex list that approximate the Bezier curve by a list of segments.
virtual int GetHatchLineWidth() const
Definition eda_shape.h:158
bool IsSolidFill() const
Definition eda_shape.h:117
void flip(const VECTOR2I &aCentre, FLIP_DIRECTION aFlipDirection)
EDA_SHAPE(SHAPE_T aType, int aLineWidth, FILL_T aFill)
Definition eda_shape.cpp:51
void beginEdit(const VECTOR2I &aStartPoint)
VECTOR2I m_start
Definition eda_shape.h:505
int GetPointCount() const
const VECTOR2I & GetEnd() const
Return the ending point of the graphic.
Definition eda_shape.h:215
bool IsClosed() const
void SetRadius(int aX)
Definition eda_shape.h:240
void SetStart(const VECTOR2I &aStart)
Definition eda_shape.h:177
LINE_STYLE GetLineStyle() const
void endEdit(bool aClosed=true)
Finish editing the shape.
const VECTOR2I & GetStart() const
Return the starting point of the graphic.
Definition eda_shape.h:173
void SetLineColor(const COLOR4D &aColor)
Definition eda_shape.h:164
COLOR4D GetFillColor() const
Definition eda_shape.h:152
void SetRectangle(const long long int &aHeight, const long long int &aWidth)
void SetShape(SHAPE_T aShape)
Definition eda_shape.h:167
void SwapShape(EDA_SHAPE *aImage)
std::vector< VECTOR2I > GetRectCorners() const
std::vector< VECTOR2I > m_bezierPoints
Definition eda_shape.h:514
bool IsAnyFill() const
Definition eda_shape.h:112
void setPosition(const VECTOR2I &aPos)
virtual bool IsProxyItem() const
Definition eda_shape.h:109
void computeArcBBox(BOX2I &aBBox) const
virtual void UpdateHatching() const
void SetEnd(const VECTOR2I &aEnd)
Definition eda_shape.h:219
void SetRectangleWidth(const int &aWidth)
void SetBezierC1(const VECTOR2I &aPt)
Definition eda_shape.h:254
void SetArcGeometry(const VECTOR2I &aStart, const VECTOR2I &aMid, const VECTOR2I &aEnd)
Set the three controlling points for an arc.
double GetLength() const
wxString SHAPE_T_asString() const
void scale(double aScale)
int GetStartX() const
Definition eda_shape.h:175
double Similarity(const EDA_SHAPE &aOther) const
const VECTOR2I & GetBezierC1() const
Definition eda_shape.h:255
VECTOR2I m_end
Definition eda_shape.h:506
const BOX2I getBoundingBox() const
void SetArcAngleAndEnd(const EDA_ANGLE &aAngle, bool aCheckNegativeAngle=false)
Set the end point from the angle center and start.
SHAPE_POLY_SET m_poly
Definition eda_shape.h:515
int GetRectangleHeight() const
virtual int GetWidth() const
Definition eda_shape.h:156
VECTOR2I getPosition() const
bool IsClockwiseArc() const
STROKE_PARAMS m_stroke
Definition eda_shape.h:494
void SetPolyPoints(const std::vector< VECTOR2I > &aPoints)
wxString getFriendlyName() const
VECTOR2I m_bezierC1
Definition eda_shape.h:511
FILL_T m_fill
Definition eda_shape.h:495
COLOR4D m_fillColor
Definition eda_shape.h:496
void SetWidth(int aWidth)
EDA_ANGLE GetSegmentAngle() const
int GetCornerRadius() const
void SetFillMode(FILL_T aFill)
long long int m_rectangleWidth
Definition eda_shape.h:502
VECTOR2I m_bezierC2
Definition eda_shape.h:512
void Serialize(google::protobuf::Any &aContainer) const override
Serializes this object to the given Any message.
bool IsPolyShapeValid() const
int Compare(const EDA_SHAPE *aOther) const
VECTOR2I GetArcMid() const
ENUM_MAP & Map(T aValue, const wxString &aName)
Definition property.h:727
static ENUM_MAP< T > & Instance()
Definition property.h:721
wxPGChoices & Choices()
Definition property.h:770
Class that other classes need to inherit from, in order to be inspectable.
Definition inspectable.h:37
A color representation with 4 components: red, green, blue, alpha.
Definition color4d.h:104
PROPERTY_BASE & SetAvailableFunc(std::function< bool(INSPECTABLE *)> aFunc)
Set a callback function to determine whether an object provides this property.
Definition property.h:262
PROPERTY_BASE & SetValidator(PROPERTY_VALIDATOR_FN &&aValidator)
Definition property.h:349
PROPERTY_BASE & SetIsHiddenFromRulesEditor(bool aHide=true)
Definition property.h:326
Provide class metadata.Helper macro to map type hashes to names.
static PROPERTY_MANAGER & Instance()
PROPERTY_BASE & AddProperty(PROPERTY_BASE *aProperty, const wxString &aGroup=wxEmptyString)
Register a property.
A round rectangle shape, based on a rectangle and a radius.
Definition roundrect.h:36
void TransformToPolygon(SHAPE_POLY_SET &aBuffer, int aMaxError) const
Get the polygonal representation of the roundrect.
Definition roundrect.cpp:81
Definition seg.h:42
VECTOR2I A
Definition seg.h:49
VECTOR2I B
Definition seg.h:50
SHAPE_TYPE Type() const
Return the type of the shape.
Definition shape.h:98
Represent a polyline containing arcs as well as line segments: A chain of connected line and/or arc s...
void Move(const VECTOR2I &aVector) override
const SHAPE_ARC & Arc(size_t aArc) const
bool IsClosed() const override
virtual const VECTOR2I GetPoint(int aIndex) const override
void SetClosed(bool aClosed)
Mark the line chain as closed (i.e.
int PointCount() const
Return the number of points (vertices) in this line chain.
ssize_t ArcIndex(size_t aSegment) const
Return the arc index for the given segment index.
SEG Segment(int aIndex) const
Return a copy of the aIndex-th segment in the line chain.
virtual size_t GetPointCount() const override
void Append(int aX, int aY, bool aAllowDuplication=false)
Append a new point at the end of the line chain.
virtual const SEG GetSegment(int aIndex) const override
const VECTOR2I & CPoint(int aIndex) const
Return a reference to a given point in the line chain.
int SegmentCount() const
Return the number of segments in this line chain.
const VECTOR2I & CLastPoint() const
Return the last point in the line chain.
void Remove(int aStartIndex, int aEndIndex)
Remove the range of points [start_index, end_index] from the line chain.
const SEG CSegment(int aIndex) const
Return a constant copy of the aIndex segment in the line chain.
bool IsArcSegment(size_t aSegment) const
const std::vector< VECTOR2I > & CPoints() const
Represent a set of closed polygons.
bool CollideEdge(const VECTOR2I &aPoint, VERTEX_INDEX *aClosestVertex=nullptr, int aClearance=0) const
Check whether aPoint collides with any edge of any of the contours of the polygon.
int AddOutline(const SHAPE_LINE_CHAIN &aOutline)
Adds a new outline to the set and returns its index.
int VertexCount(int aOutline=-1, int aHole=-1) const
Return the number of vertices in a given outline/hole.
bool Collide(const SHAPE *aShape, int aClearance=0, int *aActual=nullptr, VECTOR2I *aLocation=nullptr) const override
Check if the boundary of shape (this) lies closer to the shape aShape than aClearance,...
int TotalVertices() const
Return total number of vertices stored in the set.
void Inflate(int aAmount, CORNER_STRATEGY aCornerStrategy, int aMaxError, bool aSimplify=false)
Perform outline inflation/deflation.
int Append(int x, int y, int aOutline=-1, int aHole=-1, bool aAllowDuplication=false)
Appends a vertex at the end of the given outline/hole (default: the last outline)
const std::vector< SEG > GenerateHatchLines(const std::vector< double > &aSlopes, int aSpacing, int aLineLength) const
SHAPE_LINE_CHAIN & Outline(int aIndex)
Return the reference to aIndex-th outline in the set.
int NewOutline()
Creates a new empty polygon in the set and returns its index.
const VECTOR2I & CVertex(int aIndex, int aOutline, int aHole) const
Return the index-th vertex in a given hole outline within a given outline.
int OutlineCount() const
Return the number of outlines in the set.
SHAPE_POLY_SET CloneDropTriangulation() const
const SHAPE_LINE_CHAIN & COutline(int aIndex) const
const BOX2I BBox(int aClearance=0) const override
Compute a bounding box of the shape, with a margin of aClearance a collision.
Represent a simple polygon consisting of a zero-thickness closed chain of connected line segments.
An abstract shape on 2D plane.
Definition shape.h:126
int GetWidth() const
LINE_STYLE GetLineStyle() const
wxString MessageTextFromValue(double aValue, bool aAddUnitLabel=true, EDA_DATA_TYPE aType=EDA_DATA_TYPE::DISTANCE) const
A lower-precision version of StringFromValue().
double Distance(const VECTOR2< extended_type > &aVector) const
Compute the distance between two vectors.
Definition vector2d.h:561
constexpr extended_type SquaredEuclideanNorm() const
Compute the squared euclidean norm of the vector, which is defined as (x ** 2 + y ** 2).
Definition vector2d.h:307
T EuclideanNorm() const
Compute the Euclidean norm of the vector, which is defined as sqrt(x ** 2 + y ** 2).
Definition vector2d.h:283
void TransformRingToPolygon(SHAPE_POLY_SET &aBuffer, const VECTOR2I &aCentre, int aRadius, int aWidth, int aError, ERROR_LOC aErrorLoc)
Convert arcs to multiple straight segments.
void TransformCircleToPolygon(SHAPE_LINE_CHAIN &aBuffer, const VECTOR2I &aCenter, int aRadius, int aError, ERROR_LOC aErrorLoc, int aMinSegCount=0)
Convert a circle to a polygon, using multiple straight lines.
void TransformArcToPolygon(SHAPE_POLY_SET &aBuffer, const VECTOR2I &aStart, const VECTOR2I &aMid, const VECTOR2I &aEnd, int aWidth, int aError, ERROR_LOC aErrorLoc)
Convert arc to multiple straight segments.
void TransformRoundChamferedRectToPolygon(SHAPE_POLY_SET &aBuffer, const VECTOR2I &aPosition, const VECTOR2I &aSize, const EDA_ANGLE &aRotation, int aCornerRadius, double aChamferRatio, int aChamferCorners, int aInflate, int aError, ERROR_LOC aErrorLoc)
Convert a rectangle with rounded corners and/or chamfered corners to a polygon.
void TransformOvalToPolygon(SHAPE_POLY_SET &aBuffer, const VECTOR2I &aStart, const VECTOR2I &aEnd, int aWidth, int aError, ERROR_LOC aErrorLoc, int aMinSegCount=0)
Convert a oblong shape to a polygon, using multiple segments.
@ ROUND_ALL_CORNERS
All angles are rounded.
#define _(s)
static constexpr EDA_ANGLE ANGLE_0
Definition eda_angle.h:411
static constexpr EDA_ANGLE ANGLE_90
Definition eda_angle.h:413
@ RADIANS_T
Definition eda_angle.h:32
static constexpr EDA_ANGLE ANGLE_45
Definition eda_angle.h:412
static constexpr EDA_ANGLE ANGLE_270
Definition eda_angle.h:416
static constexpr EDA_ANGLE ANGLE_360
Definition eda_angle.h:417
static constexpr EDA_ANGLE ANGLE_180
Definition eda_angle.h:415
#define TEST_PT(a, b)
#define TEST(a, b)
#define TEST_E(a, b)
static struct EDA_SHAPE_DESC _EDA_SHAPE_DESC
#define SWAPITEM(x)
#define sq(x)
SHAPE_T
Definition eda_shape.h:43
@ UNDEFINED
Definition eda_shape.h:44
@ SEGMENT
Definition eda_shape.h:45
@ RECTANGLE
Use RECTANGLE instead of RECT to avoid collision in a Windows header.
Definition eda_shape.h:46
UI_FILL_MODE
Definition eda_shape.h:68
@ REVERSE_HATCH
Definition eda_shape.h:72
@ SOLID
Definition eda_shape.h:70
@ HATCH
Definition eda_shape.h:71
@ NONE
Definition eda_shape.h:69
@ CROSS_HATCH
Definition eda_shape.h:73
FILL_T
Definition eda_shape.h:56
@ NO_FILL
Definition eda_shape.h:57
@ REVERSE_HATCH
Definition eda_shape.h:62
@ HATCH
Definition eda_shape.h:61
@ FILLED_SHAPE
Fill with object color.
Definition eda_shape.h:58
@ CROSS_HATCH
Definition eda_shape.h:63
a few functions useful in geometry calculations.
This file contains miscellaneous commonly used macros and functions.
#define KI_FALLTHROUGH
The KI_FALLTHROUGH macro is to be used when switch statement cases should purposely fallthrough from ...
Definition macros.h:83
#define UNIMPLEMENTED_FOR(type)
Definition macros.h:96
constexpr void MIRROR(T &aPoint, const T &aMirrorRef)
Updates aPoint with the mirror of aPoint relative to the aMirrorRef.
Definition mirror.h:45
FLIP_DIRECTION
Definition mirror.h:27
KICOMMON_API wxString MessageTextFromValue(const EDA_IU_SCALE &aIuScale, EDA_UNITS aUnits, double aValue, bool aAddUnitsText=true, EDA_DATA_TYPE aType=EDA_DATA_TYPE::DISTANCE)
A helper to convert the double length aValue to a string in inches, millimeters, or unscaled units.
bool ShapeHitTest(const SHAPE_LINE_CHAIN &aHitter, const SHAPE &aHittee, bool aHitteeContained)
Perform a shape-to-shape hit test.
size_t longest_common_subset(const _Container &__c1, const _Container &__c2)
Returns the length of the longest common subset of values between two containers.
Definition kicad_algo.h:186
KICOMMON_API VECTOR2I UnpackVector2(const types::Vector2 &aInput)
Definition api_utils.cpp:86
KICOMMON_API SHAPE_POLY_SET UnpackPolySet(const types::PolySet &aInput)
KICOMMON_API void PackVector2(types::Vector2 &aOutput, const VECTOR2I &aInput)
Definition api_utils.cpp:79
KICOMMON_API void PackPolySet(types::PolySet &aOutput, const SHAPE_POLY_SET &aInput)
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:400
#define _HKI(x)
Definition page_info.cpp:44
#define IMPLEMENT_ENUM_TO_WXANY(type)
Definition property.h:821
#define NO_SETTER(owner, type)
Definition property.h:828
@ PT_COORD
Coordinate expressed in distance units (mm/inch)
Definition property.h:65
@ PT_DECIDEGREE
Angle expressed in decidegrees.
Definition property.h:67
@ PT_SIZE
Size expressed in distance units (mm/inch)
Definition property.h:63
#define REGISTER_TYPE(x)
std::optional< std::unique_ptr< VALIDATION_ERROR > > VALIDATOR_RESULT
Null optional means validation succeeded.
@ SH_POLY_SET
set of polygons (with holes, etc.)
Definition shape.h:52
@ SH_CIRCLE
circle
Definition shape.h:50
@ SH_SIMPLE
simple polygon
Definition shape.h:51
@ SH_NULL
empty shape (no shape...),
Definition shape.h:55
@ SH_SEGMENT
line segment
Definition shape.h:48
@ SH_ARC
circular arc
Definition shape.h:54
@ SH_POLY_SET_TRIANGLE
a single triangle belonging to a POLY_SET triangulation
Definition shape.h:56
@ SH_LINE_CHAIN
line chain (polyline)
Definition shape.h:49
@ SH_COMPOUND
compound shape, consisting of multiple simple shapes
Definition shape.h:53
static bool Collide(const SHAPE_CIRCLE &aA, const SHAPE_CIRCLE &aB, int aClearance, int *aActual, VECTOR2I *aLocation, VECTOR2I *aMTV)
LINE_STYLE
Dashed line types.
int radius
SHAPE_CIRCLE circle(c.m_circle_center, c.m_circle_radius)
bool TestSegmentHit(const VECTOR2I &aRefPoint, const VECTOR2I &aStart, const VECTOR2I &aEnd, int aDist)
Test if aRefPoint is with aDistance on the line defined by aStart and aEnd.
Definition trigo.cpp:175
void RotatePoint(int *pX, int *pY, const EDA_ANGLE &aAngle)
Calculate the new point of coord coord pX, pY, for a rotation center 0, 0.
Definition trigo.cpp:229
const VECTOR2I CalcArcCenter(const VECTOR2I &aStart, const VECTOR2I &aMid, const VECTOR2I &aEnd)
Determine the center of an arc or circle given three points on its circumference.
Definition trigo.cpp:521
@ PCB_TABLECELL_T
class PCB_TABLECELL, PCB_TEXTBOX for use in tables
Definition typeinfo.h:95
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:695
VECTOR2< double > VECTOR2D
Definition vector2d.h:694