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( m_poly.OutlineCount() < 1 ) // empty poly
1389 return false;
1390
1391 if( IsFilledForHitTesting() )
1392 {
1393 if( !m_poly.COutline( 0 ).IsClosed() )
1394 {
1395 // Only one outline is expected
1396 SHAPE_LINE_CHAIN copy( m_poly.COutline( 0 ) );
1397 copy.SetClosed( true );
1398 return copy.Collide( aPosition, maxdist );
1399 }
1400 else
1401 {
1402 return m_poly.Collide( aPosition, maxdist );
1403 }
1404 }
1405 else
1406 {
1407 if( m_poly.CollideEdge( aPosition, nullptr, maxdist ) )
1408 return true;
1409
1410 if( IsHatchedFill() && GetHatching().Collide( aPosition, maxdist ) )
1411 return true;
1412
1413 return false;
1414 }
1415
1416 default:
1418 return false;
1419 }
1420}
1421
1422
1423bool EDA_SHAPE::hitTest( const BOX2I& aRect, bool aContained, int aAccuracy ) const
1424{
1425 BOX2I arect = aRect;
1426 arect.Normalize();
1427 arect.Inflate( aAccuracy );
1428
1429 BOX2I bbox = getBoundingBox();
1430
1431 auto checkOutline =
1432 [&]( const SHAPE_LINE_CHAIN& outline )
1433 {
1434 int count = (int) outline.GetPointCount();
1435
1436 for( int ii = 0; ii < count; ii++ )
1437 {
1438 VECTOR2I vertex = outline.GetPoint( ii );
1439
1440 // Test if the point is within aRect
1441 if( arect.Contains( vertex ) )
1442 return true;
1443
1444 if( ii + 1 < count )
1445 {
1446 VECTOR2I vertexNext = outline.GetPoint( ii + 1 );
1447
1448 // Test if this edge intersects aRect
1449 if( arect.Intersects( vertex, vertexNext ) )
1450 return true;
1451 }
1452 else if( outline.IsClosed() )
1453 {
1454 VECTOR2I vertexNext = outline.GetPoint( 0 );
1455
1456 // Test if this edge intersects aRect
1457 if( arect.Intersects( vertex, vertexNext ) )
1458 return true;
1459 }
1460 }
1461
1462 return false;
1463 };
1464
1465 switch( m_shape )
1466 {
1467 case SHAPE_T::CIRCLE:
1468 // Test if area intersects or contains the circle:
1469 if( aContained )
1470 {
1471 return arect.Contains( bbox );
1472 }
1473 else
1474 {
1475 // If the rectangle does not intersect the bounding box, this is a much quicker test
1476 if( !arect.Intersects( bbox ) )
1477 return false;
1478 else
1479 return arect.IntersectsCircleEdge( getCenter(), GetRadius(), GetWidth() );
1480 }
1481
1482 case SHAPE_T::ARC:
1483 // Test for full containment of this arc in the rect
1484 if( aContained )
1485 {
1486 return arect.Contains( bbox );
1487 }
1488 // Test if the rect crosses the arc
1489 else
1490 {
1491 if( !arect.Intersects( bbox ) )
1492 return false;
1493
1494 if( IsAnyFill() )
1495 {
1496 return ( arect.Intersects( getCenter(), GetStart() )
1497 || arect.Intersects( getCenter(), GetEnd() )
1498 || arect.IntersectsCircleEdge( getCenter(), GetRadius(), GetWidth() ) );
1499 }
1500 else
1501 {
1502 return arect.IntersectsCircleEdge( getCenter(), GetRadius(), GetWidth() );
1503 }
1504 }
1505
1506 case SHAPE_T::RECTANGLE:
1507 if( aContained )
1508 {
1509 return arect.Contains( bbox );
1510 }
1511 else if( m_cornerRadius > 0 )
1512 {
1514 SHAPE_POLY_SET poly;
1515 rr.TransformToPolygon( poly, getMaxError() );
1516
1517 // Account for the width of the line
1518 arect.Inflate( GetWidth() / 2 );
1519
1520 return checkOutline( poly.Outline( 0 ) );
1521 }
1522 else
1523 {
1524 std::vector<VECTOR2I> pts = GetRectCorners();
1525
1526 // Account for the width of the lines
1527 arect.Inflate( GetWidth() / 2 );
1528 return ( arect.Intersects( pts[0], pts[1] )
1529 || arect.Intersects( pts[1], pts[2] )
1530 || arect.Intersects( pts[2], pts[3] )
1531 || arect.Intersects( pts[3], pts[0] ) );
1532 }
1533
1534 case SHAPE_T::SEGMENT:
1535 if( aContained )
1536 {
1537 return arect.Contains( GetStart() ) && aRect.Contains( GetEnd() );
1538 }
1539 else
1540 {
1541 // Account for the width of the line
1542 arect.Inflate( GetWidth() / 2 );
1543 return arect.Intersects( GetStart(), GetEnd() );
1544 }
1545
1546 case SHAPE_T::POLY:
1547 if( aContained )
1548 {
1549 return arect.Contains( bbox );
1550 }
1551 else
1552 {
1553 // Fast test: if aRect is outside the polygon bounding box,
1554 // rectangles cannot intersect
1555 if( !arect.Intersects( bbox ) )
1556 return false;
1557
1558 // Account for the width of the line
1559 arect.Inflate( GetWidth() / 2 );
1560
1561 for( int ii = 0; ii < m_poly.OutlineCount(); ++ii )
1562 {
1563 if( checkOutline( m_poly.Outline( ii ) ) )
1564 return true;
1565 }
1566
1567 return false;
1568 }
1569
1570 case SHAPE_T::BEZIER:
1571 if( aContained )
1572 {
1573 return arect.Contains( bbox );
1574 }
1575 else
1576 {
1577 // Fast test: if aRect is outside the polygon bounding box,
1578 // rectangles cannot intersect
1579 if( !arect.Intersects( bbox ) )
1580 return false;
1581
1582 // Account for the width of the line
1583 arect.Inflate( GetWidth() / 2 );
1584 const std::vector<VECTOR2I>* pts = &m_bezierPoints;
1585 std::vector<VECTOR2I> updatedBezierPoints;
1586
1587 if( m_bezierPoints.empty() )
1588 {
1590 converter.GetPoly( updatedBezierPoints, aAccuracy / 2 );
1591 pts = &updatedBezierPoints;
1592 }
1593
1594 for( unsigned ii = 1; ii < pts->size(); ii++ )
1595 {
1596 VECTOR2I vertex = ( *pts )[ii - 1];
1597 VECTOR2I vertexNext = ( *pts )[ii];
1598
1599 // Test if the point is within aRect
1600 if( arect.Contains( vertex ) )
1601 return true;
1602
1603 // Test if this edge intersects aRect
1604 if( arect.Intersects( vertex, vertexNext ) )
1605 return true;
1606 }
1607
1608 return false;
1609 }
1610
1611 default:
1613 return false;
1614 }
1615}
1616
1617
1618bool EDA_SHAPE::hitTest( const SHAPE_LINE_CHAIN& aPoly, bool aContained ) const
1619{
1621
1622 return KIGEOM::ShapeHitTest( aPoly, shape, aContained );
1623}
1624
1625
1626std::vector<VECTOR2I> EDA_SHAPE::GetRectCorners() const
1627{
1628 std::vector<VECTOR2I> pts;
1629 VECTOR2I topLeft = GetStart();
1630 VECTOR2I botRight = GetEnd();
1631
1632 pts.emplace_back( topLeft );
1633 pts.emplace_back( botRight.x, topLeft.y );
1634 pts.emplace_back( botRight );
1635 pts.emplace_back( topLeft.x, botRight.y );
1636
1637 return pts;
1638}
1639
1640
1641std::vector<VECTOR2I> EDA_SHAPE::GetCornersInSequence( EDA_ANGLE angle ) const
1642{
1643 std::vector<VECTOR2I> pts;
1644
1645 angle.Normalize();
1646
1647 BOX2I bbox = getBoundingBox();
1648 bbox.Normalize();
1649
1650 if( angle.IsCardinal() )
1651 {
1652 if( angle == ANGLE_0 )
1653 {
1654 pts.emplace_back( VECTOR2I( bbox.GetLeft(), bbox.GetTop() ) );
1655 pts.emplace_back( VECTOR2I( bbox.GetRight(), bbox.GetTop() ) );
1656 pts.emplace_back( VECTOR2I( bbox.GetRight(), bbox.GetBottom() ) );
1657 pts.emplace_back( VECTOR2I( bbox.GetLeft(), bbox.GetBottom() ) );
1658 }
1659 else if( angle == ANGLE_90 )
1660 {
1661 pts.emplace_back( VECTOR2I( bbox.GetLeft(), bbox.GetBottom() ) );
1662 pts.emplace_back( VECTOR2I( bbox.GetLeft(), bbox.GetTop() ) );
1663 pts.emplace_back( VECTOR2I( bbox.GetRight(), bbox.GetTop() ) );
1664 pts.emplace_back( VECTOR2I( bbox.GetRight(), bbox.GetBottom() ) );
1665 }
1666 else if( angle == ANGLE_180 )
1667 {
1668 pts.emplace_back( VECTOR2I( bbox.GetRight(), bbox.GetBottom() ) );
1669 pts.emplace_back( VECTOR2I( bbox.GetLeft(), bbox.GetBottom() ) );
1670 pts.emplace_back( VECTOR2I( bbox.GetLeft(), bbox.GetTop() ) );
1671 pts.emplace_back( VECTOR2I( bbox.GetRight(), bbox.GetTop() ) );
1672 }
1673 else if( angle == ANGLE_270 )
1674 {
1675 pts.emplace_back( VECTOR2I( bbox.GetRight(), bbox.GetTop() ) );
1676 pts.emplace_back( VECTOR2I( bbox.GetRight(), bbox.GetBottom() ) );
1677 pts.emplace_back( VECTOR2I( bbox.GetLeft(), bbox.GetBottom() ) );
1678 pts.emplace_back( VECTOR2I( bbox.GetLeft(), bbox.GetTop() ) );
1679 }
1680 }
1681 else
1682 {
1683 // This function was originally located in pcb_textbox.cpp and was later moved to eda_shape.cpp.
1684 // As a result of this move, access to getCorners was lost, since it is defined in the PCB_SHAPE
1685 // class within pcb_shape.cpp and is not available in the current context.
1686 //
1687 // Additionally, GetRectCorners() cannot be used here, as it assumes the rectangle is rotated by
1688 // a cardinal angle. In non-cardinal cases, it returns incorrect values (e.g., (0, 0)).
1689 //
1690 // To address this, a portion of the getCorners implementation for SHAPE_T::POLY elements
1691 // has been replicated here to restore the correct behavior.
1692 std::vector<VECTOR2I> corners;
1693
1694 for( int ii = 0; ii < GetPolyShape().OutlineCount(); ++ii )
1695 {
1696 for( const VECTOR2I& pt : GetPolyShape().Outline( ii ).CPoints() )
1697 corners.emplace_back( pt );
1698 }
1699
1700 while( corners.size() < 4 )
1701 corners.emplace_back( corners.back() + VECTOR2I( 10, 10 ) );
1702
1703 VECTOR2I minX = corners[0];
1704 VECTOR2I maxX = corners[0];
1705 VECTOR2I minY = corners[0];
1706 VECTOR2I maxY = corners[0];
1707
1708 for( const VECTOR2I& corner : corners )
1709 {
1710 if( corner.x < minX.x )
1711 minX = corner;
1712
1713 if( corner.x > maxX.x )
1714 maxX = corner;
1715
1716 if( corner.y < minY.y )
1717 minY = corner;
1718
1719 if( corner.y > maxY.y )
1720 maxY = corner;
1721 }
1722
1723 if( angle < ANGLE_90 )
1724 {
1725 pts.emplace_back( minX );
1726 pts.emplace_back( minY );
1727 pts.emplace_back( maxX );
1728 pts.emplace_back( maxY );
1729 }
1730 else if( angle < ANGLE_180 )
1731 {
1732 pts.emplace_back( maxY );
1733 pts.emplace_back( minX );
1734 pts.emplace_back( minY );
1735 pts.emplace_back( maxX );
1736 }
1737 else if( angle < ANGLE_270 )
1738 {
1739 pts.emplace_back( maxX );
1740 pts.emplace_back( maxY );
1741 pts.emplace_back( minX );
1742 pts.emplace_back( minY );
1743 }
1744 else
1745 {
1746 pts.emplace_back( minY );
1747 pts.emplace_back( maxX );
1748 pts.emplace_back( maxY );
1749 pts.emplace_back( minX );
1750 }
1751 }
1752
1753 return pts;
1754}
1755
1756
1758{
1759 // Start, end, and each inflection point the arc crosses will enclose the entire arc.
1760 // Only include the center when filled; it's not necessarily inside the BB of an unfilled
1761 // arc with a small included angle.
1762 aBBox.SetOrigin( m_start );
1763 aBBox.Merge( m_end );
1764
1765 if( IsAnyFill() )
1766 aBBox.Merge( m_arcCenter );
1767
1768 int radius = GetRadius();
1769 EDA_ANGLE t1, t2;
1770
1771 CalcArcAngles( t1, t2 );
1772
1773 t1.Normalize();
1774 t2.Normalize();
1775
1776 if( t2 > t1 )
1777 {
1778 if( t1 < ANGLE_0 && t2 > ANGLE_0 )
1779 aBBox.Merge( VECTOR2I( m_arcCenter.x + radius, m_arcCenter.y ) ); // right
1780
1781 if( t1 < ANGLE_90 && t2 > ANGLE_90 )
1782 aBBox.Merge( VECTOR2I( m_arcCenter.x, m_arcCenter.y + radius ) ); // down
1783
1784 if( t1 < ANGLE_180 && t2 > ANGLE_180 )
1785 aBBox.Merge( VECTOR2I( m_arcCenter.x - radius, m_arcCenter.y ) ); // left
1786
1787 if( t1 < ANGLE_270 && t2 > ANGLE_270 )
1788 aBBox.Merge( VECTOR2I( m_arcCenter.x, m_arcCenter.y - radius ) ); // up
1789 }
1790 else
1791 {
1792 if( t1 < ANGLE_0 || t2 > ANGLE_0 )
1793 aBBox.Merge( VECTOR2I( m_arcCenter.x + radius, m_arcCenter.y ) ); // right
1794
1795 if( t1 < ANGLE_90 || t2 > ANGLE_90 )
1796 aBBox.Merge( VECTOR2I( m_arcCenter.x, m_arcCenter.y + radius ) ); // down
1797
1798 if( t1 < ANGLE_180 || t2 > ANGLE_180 )
1799 aBBox.Merge( VECTOR2I( m_arcCenter.x - radius, m_arcCenter.y ) ); // left
1800
1801 if( t1 < ANGLE_270 || t2 > ANGLE_270 )
1802 aBBox.Merge( VECTOR2I( m_arcCenter.x, m_arcCenter.y - radius ) ); // up
1803 }
1804}
1805
1806
1807void EDA_SHAPE::SetPolyPoints( const std::vector<VECTOR2I>& aPoints )
1808{
1809 m_poly.RemoveAllContours();
1810 m_poly.NewOutline();
1811
1812 for( const VECTOR2I& p : aPoints )
1813 m_poly.Append( p.x, p.y );
1814}
1815
1816
1817std::vector<SHAPE*> EDA_SHAPE::makeEffectiveShapes( bool aEdgeOnly, bool aLineChainOnly, bool aHittesting ) const
1818{
1819 std::vector<SHAPE*> effectiveShapes;
1820 int width = GetEffectiveWidth();
1821 bool solidFill = IsSolidFill()
1822 || IsHatchedFill()
1823 || IsProxyItem()
1824 || ( aHittesting && IsFilledForHitTesting() );
1825
1826 if( aEdgeOnly )
1827 solidFill = false;
1828
1829 switch( m_shape )
1830 {
1831 case SHAPE_T::ARC:
1832 effectiveShapes.emplace_back( new SHAPE_ARC( m_arcCenter, m_start, GetArcAngle(), width ) );
1833 break;
1834
1835 case SHAPE_T::SEGMENT:
1836 effectiveShapes.emplace_back( new SHAPE_SEGMENT( m_start, m_end, width ) );
1837 break;
1838
1839 case SHAPE_T::RECTANGLE:
1840 {
1841 if( m_cornerRadius > 0 )
1842 {
1844 SHAPE_POLY_SET poly;
1845 rr.TransformToPolygon( poly, getMaxError() );
1846 SHAPE_LINE_CHAIN outline = poly.Outline( 0 );
1847
1848 if( solidFill )
1849 effectiveShapes.emplace_back( new SHAPE_SIMPLE( outline ) );
1850
1851 if( width > 0 || !solidFill )
1852 {
1853 std::set<size_t> arcsHandled;
1854
1855 for( int ii = 0; ii < outline.SegmentCount(); ++ii )
1856 {
1857 if( outline.IsArcSegment( ii ) )
1858 {
1859 size_t arcIndex = outline.ArcIndex( ii );
1860
1861 if( !arcsHandled.contains( arcIndex ) )
1862 {
1863 arcsHandled.insert( arcIndex );
1864 effectiveShapes.emplace_back( new SHAPE_ARC( outline.Arc( arcIndex ), width ) );
1865 }
1866 }
1867 else
1868 {
1869 effectiveShapes.emplace_back( new SHAPE_SEGMENT( outline.Segment( ii ), width ) );
1870 }
1871 }
1872 }
1873 }
1874 else
1875 {
1876 std::vector<VECTOR2I> pts = GetRectCorners();
1877
1878 if( solidFill )
1879 effectiveShapes.emplace_back( new SHAPE_SIMPLE( pts ) );
1880
1881 if( width > 0 || !solidFill )
1882 {
1883 effectiveShapes.emplace_back( new SHAPE_SEGMENT( pts[0], pts[1], width ) );
1884 effectiveShapes.emplace_back( new SHAPE_SEGMENT( pts[1], pts[2], width ) );
1885 effectiveShapes.emplace_back( new SHAPE_SEGMENT( pts[2], pts[3], width ) );
1886 effectiveShapes.emplace_back( new SHAPE_SEGMENT( pts[3], pts[0], width ) );
1887 }
1888 }
1889 break;
1890 }
1891
1892 case SHAPE_T::CIRCLE:
1893 {
1894 if( solidFill )
1895 effectiveShapes.emplace_back( new SHAPE_CIRCLE( getCenter(), GetRadius() ) );
1896
1897 if( width > 0 || !solidFill )
1898 effectiveShapes.emplace_back( new SHAPE_ARC( getCenter(), GetEnd(), ANGLE_360, width ) );
1899
1900 break;
1901 }
1902
1903 case SHAPE_T::BEZIER:
1904 {
1905 std::vector<VECTOR2I> bezierPoints = buildBezierToSegmentsPointsList( getMaxError() );
1906 VECTOR2I start_pt = bezierPoints[0];
1907
1908 for( unsigned int jj = 1; jj < bezierPoints.size(); jj++ )
1909 {
1910 VECTOR2I end_pt = bezierPoints[jj];
1911 effectiveShapes.emplace_back( new SHAPE_SEGMENT( start_pt, end_pt, width ) );
1912 start_pt = end_pt;
1913 }
1914
1915 break;
1916 }
1917
1918 case SHAPE_T::POLY:
1919 {
1920 if( GetPolyShape().OutlineCount() == 0 ) // malformed/empty polygon
1921 break;
1922
1923 for( int ii = 0; ii < GetPolyShape().OutlineCount(); ++ii )
1924 {
1925 const SHAPE_LINE_CHAIN& l = GetPolyShape().COutline( ii );
1926
1927 if( solidFill )
1928 effectiveShapes.emplace_back( new SHAPE_SIMPLE( l ) );
1929
1930 if( width > 0 || !IsSolidFill() || aEdgeOnly )
1931 {
1932 int segCount = l.SegmentCount();
1933
1934 if( aLineChainOnly && l.IsClosed() )
1935 segCount--; // Treat closed chain as open
1936
1937 for( int jj = 0; jj < segCount; jj++ )
1938 effectiveShapes.emplace_back( new SHAPE_SEGMENT( l.CSegment( jj ), width ) );
1939 }
1940 }
1941 }
1942 break;
1943
1944 default:
1946 break;
1947 }
1948
1949 return effectiveShapes;
1950}
1951
1952
1953std::vector<VECTOR2I> EDA_SHAPE::GetPolyPoints() const
1954{
1955 std::vector<VECTOR2I> points;
1956
1957 for( int ii = 0; ii < m_poly.OutlineCount(); ++ii )
1958 {
1959 const SHAPE_LINE_CHAIN& outline = m_poly.COutline( ii );
1960 int pointCount = outline.PointCount();
1961
1962 if( pointCount )
1963 {
1964 points.reserve( points.size() + pointCount );
1965
1966 for( const VECTOR2I& pt : outline.CPoints() )
1967 points.emplace_back( pt );
1968 }
1969 }
1970
1971 return points;
1972}
1973
1974
1976{
1977 // return true if the polygonal shape is valid (has more than 2 points)
1978 return GetPolyShape().OutlineCount() > 0 && GetPolyShape().Outline( 0 ).PointCount() > 2;
1979}
1980
1981
1983{
1984 // return the number of corners of the polygonal shape
1985 // this shape is expected to be only one polygon without hole
1986 return GetPolyShape().OutlineCount() ? GetPolyShape().VertexCount( 0 ) : 0;
1987}
1988
1989
1990void EDA_SHAPE::beginEdit( const VECTOR2I& aPosition )
1991{
1992 switch( GetShape() )
1993 {
1994 case SHAPE_T::SEGMENT:
1995 case SHAPE_T::CIRCLE:
1996 case SHAPE_T::RECTANGLE:
1997 SetStart( aPosition );
1998 SetEnd( aPosition );
1999 break;
2000
2001 case SHAPE_T::ARC:
2002 SetArcGeometry( aPosition, aPosition, aPosition );
2003 m_editState = 1;
2004 break;
2005
2006 case SHAPE_T::BEZIER:
2007 SetStart( aPosition );
2008 SetEnd( aPosition );
2009 SetBezierC1( aPosition );
2010 SetBezierC2( aPosition );
2011 m_editState = 1;
2012
2014 break;
2015
2016 case SHAPE_T::POLY:
2017 m_poly.NewOutline();
2018 m_poly.Outline( 0 ).SetClosed( false );
2019
2020 // Start and end of the first segment (co-located for now)
2021 m_poly.Outline( 0 ).Append( aPosition );
2022 m_poly.Outline( 0 ).Append( aPosition, true );
2023 break;
2024
2025 default:
2027 }
2028}
2029
2030
2031bool EDA_SHAPE::continueEdit( const VECTOR2I& aPosition )
2032{
2033 switch( GetShape() )
2034 {
2035 case SHAPE_T::ARC:
2036 case SHAPE_T::SEGMENT:
2037 case SHAPE_T::CIRCLE:
2038 case SHAPE_T::RECTANGLE:
2039 return false;
2040
2041 case SHAPE_T::BEZIER:
2042 if( m_editState == 3 )
2043 return false;
2044
2045 m_editState++;
2046 return true;
2047
2048 case SHAPE_T::POLY:
2049 {
2050 SHAPE_LINE_CHAIN& poly = m_poly.Outline( 0 );
2051
2052 // do not add zero-length segments
2053 if( poly.CPoint( (int) poly.GetPointCount() - 2 ) != poly.CLastPoint() )
2054 poly.Append( aPosition, true );
2055 }
2056 return true;
2057
2058 default:
2060 return false;
2061 }
2062}
2063
2064
2065void EDA_SHAPE::calcEdit( const VECTOR2I& aPosition )
2066{
2067#define sq( x ) pow( x, 2 )
2068
2069 switch( GetShape() )
2070 {
2071 case SHAPE_T::SEGMENT:
2072 case SHAPE_T::CIRCLE:
2073 case SHAPE_T::RECTANGLE:
2074 SetEnd( aPosition );
2075 break;
2076
2077 case SHAPE_T::BEZIER:
2078 {
2079 switch( m_editState )
2080 {
2081 case 0:
2082 SetStart( aPosition );
2083 SetEnd( aPosition );
2084 SetBezierC1( aPosition );
2085 SetBezierC2( aPosition );
2086 break;
2087
2088 case 1:
2089 SetBezierC2( aPosition );
2090 SetEnd( aPosition );
2091 break;
2092
2093 case 2:
2094 SetBezierC1( aPosition );
2095 break;
2096
2097 case 3:
2098 SetBezierC2( aPosition );
2099 break;
2100 }
2101
2103 }
2104 break;
2105
2106 case SHAPE_T::ARC:
2107 {
2108 double radius = GetRadius();
2109 EDA_ANGLE lastAngle = GetArcAngle();
2110
2111 // Edit state 0: drawing: place start
2112 // Edit state 1: drawing: place end (center calculated for 90-degree subtended angle)
2113 // Edit state 2: point edit: move start (center calculated for invariant subtended angle)
2114 // Edit state 3: point edit: move end (center calculated for invariant subtended angle)
2115 // Edit state 4: point edit: move center
2116 // Edit state 5: point edit: move arc-mid-point
2117
2118 switch( m_editState )
2119 {
2120 case 0:
2121 SetArcGeometry( aPosition, aPosition, aPosition );
2122 return;
2123
2124 case 1:
2125 m_end = aPosition;
2126 radius = m_start.Distance( m_end ) * M_SQRT1_2;
2127 break;
2128
2129 case 2:
2130 case 3:
2131 {
2132 VECTOR2I v = m_start - m_end;
2133 double chordBefore = v.SquaredEuclideanNorm();
2134
2135 if( m_editState == 2 )
2136 m_start = aPosition;
2137 else
2138 m_end = aPosition;
2139
2140 v = m_start - m_end;
2141
2142 double chordAfter = v.SquaredEuclideanNorm();
2143 double ratio = 0.0;
2144
2145 if( chordBefore > 0 )
2146 ratio = chordAfter / chordBefore;
2147
2148 if( ratio != 0 )
2149 radius = std::max( sqrt( sq( radius ) * ratio ), sqrt( chordAfter ) / 2 );
2150 break;
2151 }
2152
2153 case 4:
2154 {
2155 double radialA = m_start.Distance( aPosition );
2156 double radialB = m_end.Distance( aPosition );
2157 radius = ( radialA + radialB ) / 2.0;
2158 break;
2159 }
2160
2161 case 5:
2162 SetArcGeometry( GetStart(), aPosition, GetEnd() );
2163 return;
2164 }
2165
2166 // Calculate center based on start, end, and radius
2167 //
2168 // Let 'l' be the length of the chord and 'm' the middle point of the chord
2169 double l = m_start.Distance( m_end );
2170 VECTOR2D m = ( m_start + m_end ) / 2;
2171 double sqRadDiff = ( radius * radius ) - ( l * l ) / 4.0;
2172
2173 // Calculate 'd', the vector from the chord midpoint to the center
2174 VECTOR2D d;
2175
2176 if( l > 0 && sqRadDiff >= 0 )
2177 {
2178 d.x = sqrt( sqRadDiff ) * ( m_start.y - m_end.y ) / l;
2179 d.y = sqrt( sqRadDiff ) * ( m_end.x - m_start.x ) / l;
2180 }
2181
2182 VECTOR2I c1 = KiROUND( m + d );
2183 VECTOR2I c2 = KiROUND( m - d );
2184
2185 // Solution gives us 2 centers; we need to pick one:
2186 switch( m_editState )
2187 {
2188 case 1:
2189 // Keep arc clockwise while drawing i.e. arc angle = 90 deg.
2190 // it can be 90 or 270 deg depending on the arc center choice (c1 or c2)
2191 m_arcCenter = c1; // first trial
2192
2193 if( GetArcAngle() > ANGLE_180 )
2194 m_arcCenter = c2;
2195
2196 break;
2197
2198 case 2:
2199 case 3:
2200 // Pick the one of c1, c2 to keep arc on the same side
2201 m_arcCenter = c1; // first trial
2202
2203 if( ( lastAngle < ANGLE_180 ) != ( GetArcAngle() < ANGLE_180 ) )
2204 m_arcCenter = c2;
2205
2206 break;
2207
2208 case 4:
2209 // Pick the one closer to the mouse position
2210 m_arcCenter = c1.Distance( aPosition ) < c2.Distance( aPosition ) ? c1 : c2;
2211 break;
2212 }
2213
2214 break;
2215 }
2216
2217 case SHAPE_T::POLY:
2218 m_poly.Outline( 0 ).SetPoint( m_poly.Outline( 0 ).GetPointCount() - 1, aPosition );
2219 break;
2220
2221 default:
2223 }
2224}
2225
2226
2227void EDA_SHAPE::endEdit( bool aClosed )
2228{
2229 switch( GetShape() )
2230 {
2231 case SHAPE_T::ARC:
2232 case SHAPE_T::SEGMENT:
2233 case SHAPE_T::CIRCLE:
2234 case SHAPE_T::RECTANGLE:
2235 case SHAPE_T::BEZIER:
2236 break;
2237
2238 case SHAPE_T::POLY:
2239 {
2240 SHAPE_LINE_CHAIN& poly = m_poly.Outline( 0 );
2241
2242 // do not include last point twice
2243 if( poly.GetPointCount() > 2 )
2244 {
2245 if( poly.CPoint( poly.GetPointCount() - 2 ) == poly.CLastPoint() )
2246 {
2247 poly.SetClosed( aClosed );
2248 }
2249 else
2250 {
2251 poly.SetClosed( false );
2252 poly.Remove( poly.GetPointCount() - 1 );
2253 }
2254 }
2255
2256 break;
2257 }
2258
2259 default:
2261 }
2262}
2263
2264
2266{
2267 EDA_SHAPE* image = dynamic_cast<EDA_SHAPE*>( aImage );
2268 assert( image );
2269
2270 #define SWAPITEM( x ) std::swap( x, image->x )
2271 SWAPITEM( m_stroke );
2272 SWAPITEM( m_start );
2273 SWAPITEM( m_end );
2275 SWAPITEM( m_shape );
2279 SWAPITEM( m_poly );
2281 SWAPITEM( m_fill );
2285 #undef SWAPITEM
2286
2287 m_hatchingDirty = true;
2288}
2289
2290
2291int EDA_SHAPE::Compare( const EDA_SHAPE* aOther ) const
2292{
2293#define EPSILON 2 // Should be enough for rounding errors on calculated items
2294
2295#define TEST( a, b ) { if( a != b ) return a - b; }
2296#define TEST_E( a, b ) { if( abs( a - b ) > EPSILON ) return a - b; }
2297#define TEST_PT( a, b ) { TEST_E( a.x, b.x ); TEST_E( a.y, b.y ); }
2298
2299 TEST_PT( m_start, aOther->m_start );
2300 TEST_PT( m_end, aOther->m_end );
2301
2302 TEST( (int) m_shape, (int) aOther->m_shape );
2303
2305 {
2307 }
2308 else if( m_shape == SHAPE_T::ARC )
2309 {
2310 TEST_PT( GetArcMid(), aOther->GetArcMid() );
2311 }
2312 else if( m_shape == SHAPE_T::BEZIER )
2313 {
2314 TEST_PT( m_bezierC1, aOther->m_bezierC1 );
2315 TEST_PT( m_bezierC2, aOther->m_bezierC2 );
2316 }
2317 else if( m_shape == SHAPE_T::POLY )
2318 {
2319 TEST( m_poly.TotalVertices(), aOther->m_poly.TotalVertices() );
2320 }
2321
2322 for( size_t ii = 0; ii < m_bezierPoints.size(); ++ii )
2323 TEST_PT( m_bezierPoints[ii], aOther->m_bezierPoints[ii] );
2324
2325 for( int ii = 0; ii < m_poly.TotalVertices(); ++ii )
2326 TEST_PT( m_poly.CVertex( ii ), aOther->m_poly.CVertex( ii ) );
2327
2328 TEST_E( m_stroke.GetWidth(), aOther->m_stroke.GetWidth() );
2329 TEST( (int) m_stroke.GetLineStyle(), (int) aOther->m_stroke.GetLineStyle() );
2330 TEST( (int) m_fill, (int) aOther->m_fill );
2331
2332 return 0;
2333}
2334
2335
2336void EDA_SHAPE::TransformShapeToPolygon( SHAPE_POLY_SET& aBuffer, int aClearance, int aError,
2337 ERROR_LOC aErrorLoc, bool ignoreLineWidth, bool includeFill ) const
2338{
2339 bool solidFill = IsSolidFill() || ( IsHatchedFill() && !includeFill ) || IsProxyItem();
2340 int width = ignoreLineWidth ? 0 : GetWidth();
2341
2342 width += 2 * aClearance;
2343
2344 switch( m_shape )
2345 {
2346 case SHAPE_T::CIRCLE:
2347 {
2348 int r = GetRadius();
2349
2350 if( solidFill )
2351 TransformCircleToPolygon( aBuffer, getCenter(), r + width / 2, aError, aErrorLoc );
2352 else
2353 TransformRingToPolygon( aBuffer, getCenter(), r, width, aError, aErrorLoc );
2354
2355 break;
2356 }
2357
2358 case SHAPE_T::RECTANGLE:
2359 {
2360 if( GetCornerRadius() > 0 )
2361 {
2362 // Use specialized function for rounded rectangles
2364 VECTOR2I position = GetStart() + size / 2; // Center position
2365
2366 if( solidFill )
2367 {
2369 0.0, 0, width / 2, aError, aErrorLoc );
2370 }
2371 else
2372 {
2373 // Export outline as a set of thick segments:
2374 SHAPE_POLY_SET poly;
2376 0.0, 0, 0, aError, aErrorLoc );
2377 SHAPE_LINE_CHAIN& outline = poly.Outline( 0 );
2378 outline.SetClosed( true );
2379
2380 for( int ii = 0; ii < outline.PointCount(); ii++ )
2381 {
2382 TransformOvalToPolygon( aBuffer, outline.CPoint( ii ), outline.CPoint( ii+1 ), width,
2383 aError, aErrorLoc );
2384 }
2385 }
2386 }
2387 else
2388 {
2389 std::vector<VECTOR2I> pts = GetRectCorners();
2390
2391 if( solidFill )
2392 {
2393 aBuffer.NewOutline();
2394
2395 for( const VECTOR2I& pt : pts )
2396 aBuffer.Append( pt );
2397 }
2398
2399 if( width > 0 || !solidFill )
2400 {
2401 // Add in segments
2402 TransformOvalToPolygon( aBuffer, pts[0], pts[1], width, aError, aErrorLoc );
2403 TransformOvalToPolygon( aBuffer, pts[1], pts[2], width, aError, aErrorLoc );
2404 TransformOvalToPolygon( aBuffer, pts[2], pts[3], width, aError, aErrorLoc );
2405 TransformOvalToPolygon( aBuffer, pts[3], pts[0], width, aError, aErrorLoc );
2406 }
2407 }
2408
2409 break;
2410 }
2411
2412 case SHAPE_T::ARC:
2413 TransformArcToPolygon( aBuffer, GetStart(), GetArcMid(), GetEnd(), width, aError, aErrorLoc );
2414 break;
2415
2416 case SHAPE_T::SEGMENT:
2417 TransformOvalToPolygon( aBuffer, GetStart(), GetEnd(), width, aError, aErrorLoc );
2418 break;
2419
2420 case SHAPE_T::POLY:
2421 {
2422 if( !IsPolyShapeValid() )
2423 break;
2424
2425 if( solidFill )
2426 {
2427 for( int ii = 0; ii < m_poly.OutlineCount(); ++ii )
2428 {
2429 const SHAPE_LINE_CHAIN& poly = m_poly.Outline( ii );
2430 SHAPE_POLY_SET tmp;
2431 tmp.NewOutline();
2432
2433 for( int jj = 0; jj < (int) poly.GetPointCount(); ++jj )
2434 tmp.Append( poly.GetPoint( jj ) );
2435
2436 if( width > 0 )
2437 {
2438 int inflate = width / 2;
2439
2440 if( aErrorLoc == ERROR_OUTSIDE )
2441 inflate += aError;
2442
2443 tmp.Inflate( inflate, CORNER_STRATEGY::ROUND_ALL_CORNERS, aError );
2444 }
2445
2446 aBuffer.Append( tmp );
2447 }
2448 }
2449 else
2450 {
2451 for( int ii = 0; ii < m_poly.OutlineCount(); ++ii )
2452 {
2453 const SHAPE_LINE_CHAIN& poly = m_poly.Outline( ii );
2454
2455 for( int jj = 0; jj < (int) poly.SegmentCount(); ++jj )
2456 {
2457 const SEG& seg = poly.GetSegment( jj );
2458 TransformOvalToPolygon( aBuffer, seg.A, seg.B, width, aError, aErrorLoc );
2459 }
2460 }
2461 }
2462
2463 break;
2464 }
2465
2466 case SHAPE_T::BEZIER:
2467 {
2468 std::vector<VECTOR2I> ctrlPts = { GetStart(), GetBezierC1(), GetBezierC2(), GetEnd() };
2469 BEZIER_POLY converter( ctrlPts );
2470 std::vector<VECTOR2I> poly;
2471 converter.GetPoly( poly, aError );
2472
2473 for( unsigned ii = 1; ii < poly.size(); ii++ )
2474 TransformOvalToPolygon( aBuffer, poly[ii - 1], poly[ii], width, aError, aErrorLoc );
2475
2476 break;
2477 }
2478
2479 default:
2481 break;
2482 }
2483
2484 if( IsHatchedFill() && includeFill )
2485 {
2486 for( int ii = 0; ii < GetHatching().OutlineCount(); ++ii )
2487 aBuffer.AddOutline( GetHatching().COutline( ii ) );
2488 }
2489}
2490
2491
2492void EDA_SHAPE::SetWidth( int aWidth )
2493{
2494 m_stroke.SetWidth( aWidth );
2495 m_hatchingDirty = true;
2496}
2497
2498
2500{
2501 m_stroke.SetLineStyle( aStyle );
2502}
2503
2504
2506{
2507 if( m_stroke.GetLineStyle() != LINE_STYLE::DEFAULT )
2508 return m_stroke.GetLineStyle();
2509
2510 return LINE_STYLE::SOLID;
2511}
2512
2513
2514bool EDA_SHAPE::operator==( const EDA_SHAPE& aOther ) const
2515{
2516 if( GetShape() != aOther.GetShape() )
2517 return false;
2518
2519 if( m_fill != aOther.m_fill )
2520 return false;
2521
2522 if( m_stroke.GetWidth() != aOther.m_stroke.GetWidth() )
2523 return false;
2524
2525 if( m_stroke.GetLineStyle() != aOther.m_stroke.GetLineStyle() )
2526 return false;
2527
2528 if( m_fillColor != aOther.m_fillColor )
2529 return false;
2530
2531 if( m_start != aOther.m_start )
2532 return false;
2533
2534 if( m_end != aOther.m_end )
2535 return false;
2536
2537 if( m_arcCenter != aOther.m_arcCenter )
2538 return false;
2539
2540 if( m_bezierC1 != aOther.m_bezierC1 )
2541 return false;
2542
2543 if( m_bezierC2 != aOther.m_bezierC2 )
2544 return false;
2545
2546 if( m_bezierPoints != aOther.m_bezierPoints )
2547 return false;
2548
2549 for( int ii = 0; ii < m_poly.TotalVertices(); ++ii )
2550 {
2551 if( m_poly.CVertex( ii ) != aOther.m_poly.CVertex( ii ) )
2552 return false;
2553 }
2554
2555 return true;
2556}
2557
2558
2559double EDA_SHAPE::Similarity( const EDA_SHAPE& aOther ) const
2560{
2561 if( GetShape() != aOther.GetShape() )
2562 return 0.0;
2563
2564 double similarity = 1.0;
2565
2566 if( m_fill != aOther.m_fill )
2567 similarity *= 0.9;
2568
2569 if( m_stroke.GetWidth() != aOther.m_stroke.GetWidth() )
2570 similarity *= 0.9;
2571
2572 if( m_stroke.GetLineStyle() != aOther.m_stroke.GetLineStyle() )
2573 similarity *= 0.9;
2574
2575 if( m_fillColor != aOther.m_fillColor )
2576 similarity *= 0.9;
2577
2578 if( m_start != aOther.m_start )
2579 similarity *= 0.9;
2580
2581 if( m_end != aOther.m_end )
2582 similarity *= 0.9;
2583
2584 if( m_arcCenter != aOther.m_arcCenter )
2585 similarity *= 0.9;
2586
2587 if( m_bezierC1 != aOther.m_bezierC1 )
2588 similarity *= 0.9;
2589
2590 if( m_bezierC2 != aOther.m_bezierC2 )
2591 similarity *= 0.9;
2592
2593 {
2594 int m = m_bezierPoints.size();
2595 int n = aOther.m_bezierPoints.size();
2596
2597 size_t longest = alg::longest_common_subset( m_bezierPoints, aOther.m_bezierPoints );
2598
2599 similarity *= std::pow( 0.9, m + n - 2 * longest );
2600 }
2601
2602 {
2603 int m = m_poly.TotalVertices();
2604 int n = aOther.m_poly.TotalVertices();
2605 std::vector<VECTOR2I> poly;
2606 std::vector<VECTOR2I> otherPoly;
2607 VECTOR2I lastPt( 0, 0 );
2608
2609 // We look for the longest common subset of the two polygons, but we need to
2610 // offset each point because we're actually looking for overall similarity, not just
2611 // exact matches. So if the zone is moved by 1IU, we only want one point to be
2612 // considered "moved" rather than the entire polygon. In this case, the first point
2613 // will not be a match but the rest of the sequence will.
2614 for( int ii = 0; ii < m; ++ii )
2615 {
2616 poly.emplace_back( lastPt - m_poly.CVertex( ii ) );
2617 lastPt = m_poly.CVertex( ii );
2618 }
2619
2620 lastPt = VECTOR2I( 0, 0 );
2621
2622 for( int ii = 0; ii < n; ++ii )
2623 {
2624 otherPoly.emplace_back( lastPt - aOther.m_poly.CVertex( ii ) );
2625 lastPt = aOther.m_poly.CVertex( ii );
2626 }
2627
2628 size_t longest = alg::longest_common_subset( poly, otherPoly );
2629
2630 similarity *= std::pow( 0.9, m + n - 2 * longest );
2631 }
2632
2633 return similarity;
2634}
2635
2636
2640
2641
2642static struct EDA_SHAPE_DESC
2643{
2645 {
2647 .Map( SHAPE_T::SEGMENT, _HKI( "Segment" ) )
2648 .Map( SHAPE_T::RECTANGLE, _HKI( "Rectangle" ) )
2649 .Map( SHAPE_T::ARC, _HKI( "Arc" ) )
2650 .Map( SHAPE_T::CIRCLE, _HKI( "Circle" ) )
2651 .Map( SHAPE_T::POLY, _HKI( "Polygon" ) )
2652 .Map( SHAPE_T::BEZIER, _HKI( "Bezier" ) );
2653
2655
2656 if( lineStyleEnum.Choices().GetCount() == 0 )
2657 {
2658 lineStyleEnum.Map( LINE_STYLE::SOLID, _HKI( "Solid" ) )
2659 .Map( LINE_STYLE::DASH, _HKI( "Dashed" ) )
2660 .Map( LINE_STYLE::DOT, _HKI( "Dotted" ) )
2661 .Map( LINE_STYLE::DASHDOT, _HKI( "Dash-Dot" ) )
2662 .Map( LINE_STYLE::DASHDOTDOT, _HKI( "Dash-Dot-Dot" ) );
2663 }
2664
2666
2667 if( hatchModeEnum.Choices().GetCount() == 0 )
2668 {
2669 hatchModeEnum.Map( UI_FILL_MODE::NONE, _HKI( "None" ) );
2670 hatchModeEnum.Map( UI_FILL_MODE::SOLID, _HKI( "Solid" ) );
2671 hatchModeEnum.Map( UI_FILL_MODE::HATCH, _HKI( "Hatch" ) );
2672 hatchModeEnum.Map( UI_FILL_MODE::REVERSE_HATCH, _HKI( "Reverse Hatch" ) );
2673 hatchModeEnum.Map( UI_FILL_MODE::CROSS_HATCH, _HKI( "Cross-hatch" ) );
2674 }
2675
2678
2679 auto isNotPolygonOrCircle =
2680 []( INSPECTABLE* aItem ) -> bool
2681 {
2682 // Polygons, unlike other shapes, have no meaningful start or end coordinates
2683 if( EDA_SHAPE* shape = dynamic_cast<EDA_SHAPE*>( aItem ) )
2684 return shape->GetShape() != SHAPE_T::POLY && shape->GetShape() != SHAPE_T::CIRCLE;
2685
2686 return false;
2687 };
2688
2689 auto isCircle =
2690 []( INSPECTABLE* aItem ) -> bool
2691 {
2692 // Polygons, unlike other shapes, have no meaningful start or end coordinates
2693 if( EDA_SHAPE* shape = dynamic_cast<EDA_SHAPE*>( aItem ) )
2694 return shape->GetShape() == SHAPE_T::CIRCLE;
2695
2696 return false;
2697 };
2698
2699 auto isRectangle =
2700 []( INSPECTABLE* aItem ) -> bool
2701 {
2702 // Polygons, unlike other shapes, have no meaningful start or end coordinates
2703 if( EDA_SHAPE* shape = dynamic_cast<EDA_SHAPE*>( aItem ) )
2704 return shape->GetShape() == SHAPE_T::RECTANGLE;
2705
2706 return false;
2707 };
2708
2709 const wxString shapeProps = _HKI( "Shape Properties" );
2710
2711 auto shape = new PROPERTY_ENUM<EDA_SHAPE, SHAPE_T>( _HKI( "Shape" ),
2713 propMgr.AddProperty( shape, shapeProps );
2714
2715 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "Start X" ),
2718 shapeProps )
2719 .SetAvailableFunc( isNotPolygonOrCircle );
2720 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "Start Y" ),
2723 shapeProps )
2724 .SetAvailableFunc( isNotPolygonOrCircle );
2725
2726 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "Center X" ),
2729 shapeProps )
2730 .SetAvailableFunc( isCircle );
2731
2732 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "Center Y" ),
2735 shapeProps )
2736 .SetAvailableFunc( isCircle );
2737
2738 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "Radius" ),
2741 shapeProps )
2742 .SetAvailableFunc( isCircle );
2743
2744 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "End X" ),
2747 shapeProps )
2748 .SetAvailableFunc( isNotPolygonOrCircle );
2749
2750 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "End Y" ),
2753 shapeProps )
2754 .SetAvailableFunc( isNotPolygonOrCircle );
2755
2756 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "Width" ),
2759 shapeProps )
2760 .SetAvailableFunc( isRectangle );
2761
2762 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "Height" ),
2765 shapeProps )
2766 .SetAvailableFunc( isRectangle );
2767
2768 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "Corner Radius" ),
2771 shapeProps )
2772 .SetAvailableFunc( isRectangle )
2773 .SetValidator( []( const wxAny&& aValue, EDA_ITEM* aItem ) -> VALIDATOR_RESULT
2774 {
2775 wxASSERT_MSG( aValue.CheckType<int>(),
2776 "Expecting int-containing value" );
2777
2778 int radius = aValue.As<int>();
2779
2780 EDA_SHAPE* prop_shape = dynamic_cast<EDA_SHAPE*>( aItem );
2781
2782 if( !prop_shape )
2783 {
2784 wxLogDebug( wxT( "Corner Radius Validator: Not an EDA_SHAPE" ) );
2785 return std::nullopt;
2786 }
2787
2788 int maxRadius = std::min( prop_shape->GetRectangleWidth(),
2789 prop_shape->GetRectangleHeight() ) / 2;
2790
2791 if( radius > maxRadius )
2792 return std::make_unique<VALIDATION_ERROR_TOO_LARGE<int>>( radius, maxRadius );
2793 else if( radius < 0 )
2794 return std::make_unique<VALIDATION_ERROR_TOO_SMALL<int>>( radius, 0 );
2795
2796 return std::nullopt;
2797 } );
2798
2799 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "Line Width" ),
2801 shapeProps );
2802
2803 propMgr.AddProperty( new PROPERTY_ENUM<EDA_SHAPE, LINE_STYLE>( _HKI( "Line Style" ),
2805 shapeProps );
2806
2807 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, COLOR4D>( _HKI( "Line Color" ),
2809 shapeProps )
2811
2812 auto angle = new PROPERTY<EDA_SHAPE, EDA_ANGLE>( _HKI( "Angle" ),
2815 angle->SetAvailableFunc(
2816 [=]( INSPECTABLE* aItem ) -> bool
2817 {
2818 if( EDA_SHAPE* curr_shape = dynamic_cast<EDA_SHAPE*>( aItem ) )
2819 return curr_shape->GetShape() == SHAPE_T::ARC;
2820
2821 return false;
2822 } );
2823 propMgr.AddProperty( angle, shapeProps );
2824
2825 auto fillAvailable =
2826 [=]( INSPECTABLE* aItem ) -> bool
2827 {
2828 if( EDA_ITEM* edaItem = dynamic_cast<EDA_ITEM*>( aItem ) )
2829 {
2830 // For some reason masking "Filled" and "Fill Color" at the
2831 // PCB_TABLECELL level doesn't work.
2832 if( edaItem->Type() == PCB_TABLECELL_T )
2833 return false;
2834 }
2835
2836 if( EDA_SHAPE* edaShape = dynamic_cast<EDA_SHAPE*>( aItem ) )
2837 {
2838 switch( edaShape->GetShape() )
2839 {
2840 case SHAPE_T::POLY:
2841 case SHAPE_T::RECTANGLE:
2842 case SHAPE_T::CIRCLE:
2843 case SHAPE_T::BEZIER:
2844 return true;
2845
2846 default:
2847 return false;
2848 }
2849 }
2850
2851 return false;
2852 };
2853
2856 shapeProps )
2857 .SetAvailableFunc( fillAvailable );
2858
2859 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, COLOR4D>( _HKI( "Fill Color" ),
2861 shapeProps )
2862 .SetAvailableFunc( fillAvailable )
2864 }
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:105
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:83
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