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 );
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
631 {
632 for( const SEG& seg : shapeBuffer.GenerateHatchLines( slopes, spacing, -1 ) )
633 {
634 // We don't really need the rounded ends at all, so don't spend any extra time on them
635 int maxError = lineWidth;
636
637 TransformOvalToPolygon( m_hatching, seg.A, seg.B, lineWidth, maxError, ERROR_INSIDE );
638 }
639
640 m_hatching.Fracture();
641 }
642 else
643 {
644 // Generate a grid of holes for a cross-hatch. This is about 3X the speed of the above
645 // algorithm, even when modified for the 45-degree fracture problem.
646
647 int gridsize = GetHatchLineSpacing();
648 int hole_size = gridsize - GetHatchLineWidth();
649
650 m_hatching = shapeBuffer.CloneDropTriangulation();
651 m_hatching.Rotate( -ANGLE_45 );
652
653 // Build hole shape
654 SHAPE_LINE_CHAIN hole_base;
655 VECTOR2I corner( 0, 0 );;
656 hole_base.Append( corner );
657 corner.x += hole_size;
658 hole_base.Append( corner );
659 corner.y += hole_size;
660 hole_base.Append( corner );
661 corner.x = 0;
662 hole_base.Append( corner );
663 hole_base.SetClosed( true );
664
665 // Build holes
666 BOX2I bbox = m_hatching.BBox( 0 );
667 SHAPE_POLY_SET holes;
668
669 int x_offset = bbox.GetX() - ( bbox.GetX() ) % gridsize - gridsize;
670 int y_offset = bbox.GetY() - ( bbox.GetY() ) % gridsize - gridsize;
671
672 for( int xx = x_offset; xx <= bbox.GetRight(); xx += gridsize )
673 {
674 for( int yy = y_offset; yy <= bbox.GetBottom(); yy += gridsize )
675 {
676 SHAPE_LINE_CHAIN hole( hole_base );
677 hole.Move( VECTOR2I( xx, yy ) );
678 holes.AddOutline( hole );
679 }
680 }
681
682 m_hatching.BooleanSubtract( holes );
683 m_hatching.Fracture();
684
685 // Must re-rotate after Fracture(). Clipper struggles mightily with fracturing
686 // 45-degree holes.
687 m_hatching.Rotate( ANGLE_45 );
688 }
689}
690
691
692void EDA_SHAPE::move( const VECTOR2I& aMoveVector )
693{
694 switch ( m_shape )
695 {
696 case SHAPE_T::ARC:
697 m_arcCenter += aMoveVector;
698 m_arcMidData.center += aMoveVector;
699 m_arcMidData.start += aMoveVector;
700 m_arcMidData.end += aMoveVector;
701 m_arcMidData.mid += aMoveVector;
703
704 case SHAPE_T::SEGMENT:
706 case SHAPE_T::CIRCLE:
707 m_start += aMoveVector;
708 m_end += aMoveVector;
709 break;
710
711 case SHAPE_T::POLY:
712 m_poly.Move( aMoveVector );
713 break;
714
715 case SHAPE_T::BEZIER:
716 m_start += aMoveVector;
717 m_end += aMoveVector;
718 m_bezierC1 += aMoveVector;
719 m_bezierC2 += aMoveVector;
720
721 for( VECTOR2I& pt : m_bezierPoints )
722 pt += aMoveVector;
723
724 break;
725
726 default:
728 break;
729 }
730
731 m_hatchingDirty = true;
732}
733
734
735void EDA_SHAPE::scale( double aScale )
736{
737 auto scalePt = [&]( VECTOR2I& pt )
738 {
739 pt.x = KiROUND( pt.x * aScale );
740 pt.y = KiROUND( pt.y * aScale );
741 };
742
743 switch( m_shape )
744 {
745 case SHAPE_T::ARC:
746 scalePt( m_arcCenter );
748
749 case SHAPE_T::SEGMENT:
751 case SHAPE_T::CIRCLE:
752 scalePt( m_start );
753 scalePt( m_end );
754 break;
755
756 case SHAPE_T::POLY: // polygon
757 {
758 std::vector<VECTOR2I> pts;
759
760 for( int ii = 0; ii < m_poly.OutlineCount(); ++ ii )
761 {
762 for( const VECTOR2I& pt : m_poly.Outline( ii ).CPoints() )
763 {
764 pts.emplace_back( pt );
765 scalePt( pts.back() );
766 }
767 }
768
769 SetPolyPoints( pts );
770 }
771 break;
772
773 case SHAPE_T::BEZIER:
774 scalePt( m_start );
775 scalePt( m_end );
776 scalePt( m_bezierC1 );
777 scalePt( m_bezierC2 );
779 break;
780
781 default:
783 break;
784 }
785
786 m_hatchingDirty = true;
787}
788
789
790void EDA_SHAPE::rotate( const VECTOR2I& aRotCentre, const EDA_ANGLE& aAngle )
791{
792 switch( m_shape )
793 {
794 case SHAPE_T::SEGMENT:
795 case SHAPE_T::CIRCLE:
796 RotatePoint( m_start, aRotCentre, aAngle );
797 RotatePoint( m_end, aRotCentre, aAngle );
798 break;
799
800 case SHAPE_T::ARC:
801 RotatePoint( m_start, aRotCentre, aAngle );
802 RotatePoint( m_end, aRotCentre, aAngle );
803 RotatePoint( m_arcCenter, aRotCentre, aAngle );
804 RotatePoint( m_arcMidData.start, aRotCentre, aAngle );
805 RotatePoint( m_arcMidData.end, aRotCentre, aAngle );
806 RotatePoint( m_arcMidData.mid, aRotCentre, aAngle );
807 RotatePoint( m_arcMidData.center, aRotCentre, aAngle );
808 break;
809
811 if( aAngle.IsCardinal() )
812 {
813 RotatePoint( m_start, aRotCentre, aAngle );
814 RotatePoint( m_end, aRotCentre, aAngle );
815 }
816 else
817 {
818 // Convert non-cardinally-rotated rect to a diamond
822 m_poly.Rotate( aAngle, aRotCentre );
823 }
824
825 break;
826
827 case SHAPE_T::POLY:
828 m_poly.Rotate( aAngle, aRotCentre );
829 break;
830
831 case SHAPE_T::BEZIER:
832 RotatePoint( m_start, aRotCentre, aAngle );
833 RotatePoint( m_end, aRotCentre, aAngle );
834 RotatePoint( m_bezierC1, aRotCentre, aAngle );
835 RotatePoint( m_bezierC2, aRotCentre, aAngle );
836
837 for( VECTOR2I& pt : m_bezierPoints )
838 RotatePoint( pt, aRotCentre, aAngle);
839
840 break;
841
842 default:
844 break;
845 }
846
847 m_hatchingDirty = true;
848}
849
850
851void EDA_SHAPE::flip( const VECTOR2I& aCentre, FLIP_DIRECTION aFlipDirection )
852{
853 switch ( m_shape )
854 {
855 case SHAPE_T::SEGMENT:
857 MIRROR( m_start, aCentre, aFlipDirection );
858 MIRROR( m_end, aCentre, aFlipDirection );
859 break;
860
861 case SHAPE_T::CIRCLE:
862 MIRROR( m_start, aCentre, aFlipDirection );
863 MIRROR( m_end, aCentre, aFlipDirection );
864 break;
865
866 case SHAPE_T::ARC:
867 MIRROR( m_start, aCentre, aFlipDirection );
868 MIRROR( m_end, aCentre, aFlipDirection );
869 MIRROR( m_arcCenter, aCentre, aFlipDirection );
870
871 std::swap( m_start, m_end );
872 break;
873
874 case SHAPE_T::POLY:
875 m_poly.Mirror( aCentre, aFlipDirection );
876 break;
877
878 case SHAPE_T::BEZIER:
879 MIRROR( m_start, aCentre, aFlipDirection );
880 MIRROR( m_end, aCentre, aFlipDirection );
881 MIRROR( m_bezierC1, aCentre, aFlipDirection );
882 MIRROR( m_bezierC2, aCentre, aFlipDirection );
883
885 break;
886
887 default:
889 break;
890 }
891
892 m_hatchingDirty = true;
893}
894
895
897{
898 // Has meaning only for SHAPE_T::BEZIER
899 if( m_shape != SHAPE_T::BEZIER )
900 {
901 m_bezierPoints.clear();
902 return;
903 }
904
905 // Rebuild the m_BezierPoints vertex list that approximate the Bezier curve
907}
908
909
910const std::vector<VECTOR2I> EDA_SHAPE::buildBezierToSegmentsPointsList( int aMaxError ) const
911{
912 std::vector<VECTOR2I> bezierPoints;
913
914 // Rebuild the m_BezierPoints vertex list that approximate the Bezier curve
915 std::vector<VECTOR2I> ctrlPoints = { m_start, m_bezierC1, m_bezierC2, m_end };
916 BEZIER_POLY converter( ctrlPoints );
917 converter.GetPoly( bezierPoints, aMaxError );
918
919 return bezierPoints;
920}
921
922
924{
925 switch( m_shape )
926 {
927 case SHAPE_T::ARC:
928 return m_arcCenter;
929
930 case SHAPE_T::CIRCLE:
931 return m_start;
932
933 case SHAPE_T::SEGMENT:
934 // Midpoint of the line
935 return ( m_start + m_end ) / 2;
936
937 case SHAPE_T::POLY:
939 case SHAPE_T::BEZIER:
940 return getBoundingBox().Centre();
941
942 default:
944 return VECTOR2I();
945 }
946}
947
948
949void EDA_SHAPE::SetCenter( const VECTOR2I& aCenter )
950{
951 switch( m_shape )
952 {
953 case SHAPE_T::ARC:
954 m_arcCenter = aCenter;
955 break;
956
957 case SHAPE_T::CIRCLE:
958 m_start = aCenter;
959 m_hatchingDirty = true;
960 break;
961
962 default:
964 }
965}
966
967
969{
970 // If none of the input data have changed since we loaded the arc, keep the original mid point data
971 // to minimize churn
972 if( m_arcMidData.start == m_start && m_arcMidData.end == m_end && m_arcMidData.center == m_arcCenter )
973 return m_arcMidData.mid;
974
975 VECTOR2I mid = m_start;
976 RotatePoint( mid, m_arcCenter, -GetArcAngle() / 2.0 );
977 return mid;
978}
979
980
981void EDA_SHAPE::CalcArcAngles( EDA_ANGLE& aStartAngle, EDA_ANGLE& aEndAngle ) const
982{
983 VECTOR2D startRadial( GetStart() - getCenter() );
984 VECTOR2D endRadial( GetEnd() - getCenter() );
985
986 aStartAngle = EDA_ANGLE( startRadial );
987 aEndAngle = EDA_ANGLE( endRadial );
988
989 if( aEndAngle == aStartAngle )
990 aEndAngle = aStartAngle + ANGLE_360; // ring, not null
991
992 while( aEndAngle < aStartAngle )
993 aEndAngle += ANGLE_360;
994}
995
996
998{
999 double radius = 0.0;
1000
1001 switch( m_shape )
1002 {
1003 case SHAPE_T::ARC:
1004 radius = m_arcCenter.Distance( m_start );
1005 break;
1006
1007 case SHAPE_T::CIRCLE:
1008 radius = m_start.Distance( m_end );
1009 break;
1010
1011 default:
1013 }
1014
1015 // don't allow degenerate circles/arcs
1016 if( radius > (double) INT_MAX / 2.0 )
1017 radius = (double) INT_MAX / 2.0;
1018
1019 return std::max( 1, KiROUND( radius ) );
1020}
1021
1022
1023void EDA_SHAPE::SetCachedArcData( const VECTOR2I& aStart, const VECTOR2I& aMid,
1024 const VECTOR2I& aEnd, const VECTOR2I& aCenter )
1025{
1026 m_arcMidData.start = aStart;
1027 m_arcMidData.end = aEnd;
1028 m_arcMidData.center = aCenter;
1029 m_arcMidData.mid = aMid;
1030}
1031
1032
1033void EDA_SHAPE::SetArcGeometry( const VECTOR2I& aStart, const VECTOR2I& aMid, const VECTOR2I& aEnd )
1034{
1035 m_arcMidData = {};
1036 m_start = aStart;
1037 m_end = aEnd;
1038 m_arcCenter = CalcArcCenter( aStart, aMid, aEnd );
1039 VECTOR2I new_mid = GetArcMid();
1040
1041 m_endsSwapped = false;
1042
1043 // Watch the ordering here. GetArcMid above needs to be called prior to initializing the
1044 // m_arcMidData structure in order to ensure we get the calculated variant, not the cached
1045 SetCachedArcData( aStart, aMid, aEnd, m_arcCenter );
1046
1047 /*
1048 * If the input winding doesn't match our internal winding, the calculated midpoint will end
1049 * up on the other side of the arc. In this case, we need to flip the start/end points and
1050 * flag this change for the system.
1051 */
1052 VECTOR2D dist( new_mid - aMid );
1053 VECTOR2D dist2( new_mid - m_arcCenter );
1054
1055 if( dist.SquaredEuclideanNorm() > dist2.SquaredEuclideanNorm() )
1056 {
1057 std::swap( m_start, m_end );
1058 m_endsSwapped = true;
1059 }
1060}
1061
1062
1064{
1065 EDA_ANGLE angle( atan2( static_cast<double>( GetStart().y - GetEnd().y ),
1066 static_cast<double>( GetEnd().x - GetStart().x ) ), RADIANS_T );
1067
1068 return angle;
1069}
1070
1071
1073{
1074 EDA_ANGLE startAngle;
1075 EDA_ANGLE endAngle;
1076
1077 CalcArcAngles( startAngle, endAngle );
1078
1079 return endAngle - startAngle;
1080}
1081
1082
1084{
1085 if( m_shape == SHAPE_T::ARC )
1086 {
1087 VECTOR2D mid = GetArcMid();
1088
1089 double orient = ( mid.x - m_start.x ) * ( m_end.y - m_start.y )
1090 - ( mid.y - m_start.y ) * ( m_end.x - m_start.x );
1091
1092 return orient < 0;
1093 }
1094
1096 return false;
1097}
1098
1099
1100void EDA_SHAPE::SetArcAngleAndEnd( const EDA_ANGLE& aAngle, bool aCheckNegativeAngle )
1101{
1102 EDA_ANGLE angle( aAngle );
1103
1104 m_end = m_start;
1106
1107 if( aCheckNegativeAngle && aAngle < ANGLE_0 )
1108 {
1109 std::swap( m_start, m_end );
1110 m_endsSwapped = true;
1111 }
1112}
1113
1114
1116{
1117 if( IsProxyItem() )
1118 {
1119 switch( m_shape )
1120 {
1121 case SHAPE_T::RECTANGLE: return _( "Pad Number Box" );
1122 case SHAPE_T::SEGMENT: return _( "Thermal Spoke Template" );
1123 default: return _( "Unrecognized" );
1124 }
1125 }
1126 else
1127 {
1128 switch( m_shape )
1129 {
1130 case SHAPE_T::CIRCLE: return _( "Circle" );
1131 case SHAPE_T::ARC: return _( "Arc" );
1132 case SHAPE_T::BEZIER: return _( "Curve" );
1133 case SHAPE_T::POLY: return _( "Polygon" );
1134 case SHAPE_T::RECTANGLE: return _( "Rectangle" );
1135 case SHAPE_T::SEGMENT: return _( "Segment" );
1136 default: return _( "Unrecognized" );
1137 }
1138 }
1139}
1140
1141
1142void EDA_SHAPE::ShapeGetMsgPanelInfo( EDA_DRAW_FRAME* aFrame, std::vector<MSG_PANEL_ITEM>& aList )
1143{
1144 wxString msg;
1145
1146 wxString shape = _( "Shape" );
1147 aList.emplace_back( shape, getFriendlyName() );
1148
1149 switch( m_shape )
1150 {
1151 case SHAPE_T::CIRCLE:
1152 aList.emplace_back( _( "Radius" ), aFrame->MessageTextFromValue( GetRadius() ) );
1153 break;
1154
1155 case SHAPE_T::ARC:
1156 aList.emplace_back( _( "Length" ), aFrame->MessageTextFromValue( GetLength() ) );
1157
1159 aList.emplace_back( _( "Angle" ), msg );
1160
1161 aList.emplace_back( _( "Radius" ), aFrame->MessageTextFromValue( GetRadius() ) );
1162 break;
1163
1164 case SHAPE_T::BEZIER:
1165 aList.emplace_back( _( "Length" ), aFrame->MessageTextFromValue( GetLength() ) );
1166 break;
1167
1168 case SHAPE_T::POLY:
1169 msg.Printf( wxS( "%d" ), GetPolyShape().Outline(0).PointCount() );
1170 aList.emplace_back( _( "Points" ), msg );
1171 break;
1172
1173 case SHAPE_T::RECTANGLE:
1174 aList.emplace_back( _( "Width" ), aFrame->MessageTextFromValue( std::abs( GetEnd().x - GetStart().x ) ) );
1175 aList.emplace_back( _( "Height" ), aFrame->MessageTextFromValue( std::abs( GetEnd().y - GetStart().y ) ) );
1176 break;
1177
1178 case SHAPE_T::SEGMENT:
1179 {
1180 aList.emplace_back( _( "Length" ), aFrame->MessageTextFromValue( GetStart().Distance( GetEnd() ) ));
1181
1182 // angle counter-clockwise from 3'o-clock
1183 EDA_ANGLE angle( atan2( (double)( GetStart().y - GetEnd().y ), (double)( GetEnd().x - GetStart().x ) ),
1184 RADIANS_T );
1185 aList.emplace_back( _( "Angle" ), EDA_UNIT_UTILS::UI::MessageTextFromValue( angle ) );
1186 break;
1187 }
1188
1189 default:
1190 break;
1191 }
1192
1193 m_stroke.GetMsgPanelInfo( aFrame, aList );
1194}
1195
1196
1198{
1199 BOX2I bbox;
1200
1201 switch( m_shape )
1202 {
1203 case SHAPE_T::RECTANGLE:
1204 for( VECTOR2I& pt : GetRectCorners() )
1205 bbox.Merge( pt );
1206
1207 break;
1208
1209 case SHAPE_T::SEGMENT:
1210 bbox.SetOrigin( GetStart() );
1211 bbox.SetEnd( GetEnd() );
1212 break;
1213
1214 case SHAPE_T::CIRCLE:
1215 bbox.SetOrigin( GetStart() );
1216 bbox.Inflate( GetRadius() );
1217 break;
1218
1219 case SHAPE_T::ARC:
1220 computeArcBBox( bbox );
1221 break;
1222
1223 case SHAPE_T::POLY:
1224 if( m_poly.IsEmpty() )
1225 break;
1226
1227 for( auto iter = m_poly.CIterate(); iter; iter++ )
1228 bbox.Merge( *iter );
1229
1230 break;
1231
1232 case SHAPE_T::BEZIER:
1233 // Bezier BBoxes are not trivial to compute, so we approximate it by
1234 // using the bounding box of the curve (not control!) points.
1235 for( const VECTOR2I& pt : m_bezierPoints )
1236 bbox.Merge( pt );
1237
1238 break;
1239
1240 default:
1242 break;
1243 }
1244
1245 bbox.Inflate( std::max( 0, GetWidth() ) / 2 );
1246 bbox.Normalize();
1247
1248 return bbox;
1249}
1250
1251
1252bool EDA_SHAPE::hitTest( const VECTOR2I& aPosition, int aAccuracy ) const
1253{
1254 double maxdist = aAccuracy;
1255
1256 if( GetWidth() > 0 )
1257 maxdist += GetWidth() / 2.0;
1258
1259 switch( m_shape )
1260 {
1261 case SHAPE_T::CIRCLE:
1262 {
1263 double radius = GetRadius();
1264 double dist = aPosition.Distance( getCenter() );
1265
1266 if( IsFilledForHitTesting() )
1267 return dist <= radius + maxdist; // Filled circle hit-test
1268 else if( abs( radius - dist ) <= maxdist ) // Ring hit-test
1269 return true;
1270
1271 if( IsHatchedFill() && GetHatching().Collide( aPosition, maxdist ) )
1272 return true;
1273
1274 return false;
1275 }
1276
1277 case SHAPE_T::ARC:
1278 {
1279 if( aPosition.Distance( m_start ) <= maxdist )
1280 return true;
1281
1282 if( aPosition.Distance( m_end ) <= maxdist )
1283 return true;
1284
1285 double radius = GetRadius();
1286 VECTOR2D relPos( VECTOR2D( aPosition ) - getCenter() );
1287 double dist = relPos.EuclideanNorm();
1288
1289 if( IsFilledForHitTesting() )
1290 {
1291 // Check distance from arc center
1292 if( dist > radius + maxdist )
1293 return false;
1294 }
1295 else
1296 {
1297 // Check distance from arc circumference
1298 if( abs( radius - dist ) > maxdist )
1299 return false;
1300 }
1301
1302 // Finally, check to see if it's within arc's swept angle.
1303 EDA_ANGLE startAngle;
1304 EDA_ANGLE endAngle;
1305 CalcArcAngles( startAngle, endAngle );
1306
1307 EDA_ANGLE relPosAngle( relPos );
1308
1309 startAngle.Normalize();
1310 endAngle.Normalize();
1311 relPosAngle.Normalize();
1312
1313 if( endAngle > startAngle )
1314 return relPosAngle >= startAngle && relPosAngle <= endAngle;
1315 else
1316 return relPosAngle >= startAngle || relPosAngle <= endAngle;
1317 }
1318
1319 case SHAPE_T::BEZIER:
1320 {
1321 const std::vector<VECTOR2I>* pts = &m_bezierPoints;
1322 std::vector<VECTOR2I> updatedBezierPoints;
1323
1324 if( m_bezierPoints.empty() )
1325 {
1327 converter.GetPoly( updatedBezierPoints, aAccuracy / 2 );
1328 pts = &updatedBezierPoints;
1329 }
1330
1331 for( unsigned int i = 1; i < pts->size(); i++ )
1332 {
1333 if( TestSegmentHit( aPosition, ( *pts )[i - 1], ( *pts )[i], maxdist ) )
1334 return true;
1335 }
1336
1337 return false;
1338 }
1339 case SHAPE_T::SEGMENT:
1340 return TestSegmentHit( aPosition, GetStart(), GetEnd(), maxdist );
1341
1342 case SHAPE_T::RECTANGLE:
1343 if( IsProxyItem() || IsFilledForHitTesting() ) // Filled rect hit-test
1344 {
1345 SHAPE_POLY_SET poly;
1346 poly.NewOutline();
1347
1348 for( const VECTOR2I& pt : GetRectCorners() )
1349 poly.Append( pt );
1350
1351 return poly.Collide( aPosition, maxdist );
1352 }
1353 else if( m_cornerRadius > 0 )
1354 {
1356 SHAPE_POLY_SET poly;
1357 rr.TransformToPolygon( poly );
1358
1359 if( poly.CollideEdge( aPosition, nullptr, maxdist ) )
1360 return true;
1361 }
1362 else
1363 {
1364 std::vector<VECTOR2I> pts = GetRectCorners();
1365
1366 if( TestSegmentHit( aPosition, pts[0], pts[1], maxdist )
1367 || TestSegmentHit( aPosition, pts[1], pts[2], maxdist )
1368 || TestSegmentHit( aPosition, pts[2], pts[3], maxdist )
1369 || TestSegmentHit( aPosition, pts[3], pts[0], maxdist ) )
1370 {
1371 return true;
1372 }
1373 }
1374
1375 if( IsHatchedFill() && GetHatching().Collide( aPosition, maxdist ) )
1376 return true;
1377
1378 return false;
1379
1380 case SHAPE_T::POLY:
1381 if( IsFilledForHitTesting() )
1382 {
1383 if( !m_poly.COutline( 0 ).IsClosed() )
1384 {
1385 // Only one outline is expected
1386 SHAPE_LINE_CHAIN copy( m_poly.COutline( 0 ) );
1387 copy.SetClosed( true );
1388 return copy.Collide( aPosition, maxdist );
1389 }
1390 else
1391 {
1392 return m_poly.Collide( aPosition, maxdist );
1393 }
1394 }
1395 else
1396 {
1397 if( m_poly.CollideEdge( aPosition, nullptr, maxdist ) )
1398 return true;
1399
1400 if( IsHatchedFill() && GetHatching().Collide( aPosition, maxdist ) )
1401 return true;
1402
1403 return false;
1404 }
1405
1406 default:
1408 return false;
1409 }
1410}
1411
1412
1413bool EDA_SHAPE::hitTest( const BOX2I& aRect, bool aContained, int aAccuracy ) const
1414{
1415 BOX2I arect = aRect;
1416 arect.Normalize();
1417 arect.Inflate( aAccuracy );
1418
1419 BOX2I bbox = getBoundingBox();
1420
1421 auto checkOutline =
1422 [&]( const SHAPE_LINE_CHAIN& outline )
1423 {
1424 int count = (int) outline.GetPointCount();
1425
1426 for( int ii = 0; ii < count; ii++ )
1427 {
1428 VECTOR2I vertex = outline.GetPoint( ii );
1429
1430 // Test if the point is within aRect
1431 if( arect.Contains( vertex ) )
1432 return true;
1433
1434 if( ii + 1 < count )
1435 {
1436 VECTOR2I vertexNext = outline.GetPoint( ii + 1 );
1437
1438 // Test if this edge intersects aRect
1439 if( arect.Intersects( vertex, vertexNext ) )
1440 return true;
1441 }
1442 else if( outline.IsClosed() )
1443 {
1444 VECTOR2I vertexNext = outline.GetPoint( 0 );
1445
1446 // Test if this edge intersects aRect
1447 if( arect.Intersects( vertex, vertexNext ) )
1448 return true;
1449 }
1450 }
1451
1452 return false;
1453 };
1454
1455 switch( m_shape )
1456 {
1457 case SHAPE_T::CIRCLE:
1458 // Test if area intersects or contains the circle:
1459 if( aContained )
1460 {
1461 return arect.Contains( bbox );
1462 }
1463 else
1464 {
1465 // If the rectangle does not intersect the bounding box, this is a much quicker test
1466 if( !arect.Intersects( bbox ) )
1467 return false;
1468 else
1469 return arect.IntersectsCircleEdge( getCenter(), GetRadius(), GetWidth() );
1470 }
1471
1472 case SHAPE_T::ARC:
1473 // Test for full containment of this arc in the rect
1474 if( aContained )
1475 {
1476 return arect.Contains( bbox );
1477 }
1478 // Test if the rect crosses the arc
1479 else
1480 {
1481 if( !arect.Intersects( bbox ) )
1482 return false;
1483
1484 if( IsAnyFill() )
1485 {
1486 return ( arect.Intersects( getCenter(), GetStart() )
1487 || arect.Intersects( getCenter(), GetEnd() )
1488 || arect.IntersectsCircleEdge( getCenter(), GetRadius(), GetWidth() ) );
1489 }
1490 else
1491 {
1492 return arect.IntersectsCircleEdge( getCenter(), GetRadius(), GetWidth() );
1493 }
1494 }
1495
1496 case SHAPE_T::RECTANGLE:
1497 if( aContained )
1498 {
1499 return arect.Contains( bbox );
1500 }
1501 else if( m_cornerRadius > 0 )
1502 {
1504 SHAPE_POLY_SET poly;
1505 rr.TransformToPolygon( poly );
1506
1507 // Account for the width of the line
1508 arect.Inflate( GetWidth() / 2 );
1509
1510 return checkOutline( poly.Outline( 0 ) );
1511 }
1512 else
1513 {
1514 std::vector<VECTOR2I> pts = GetRectCorners();
1515
1516 // Account for the width of the lines
1517 arect.Inflate( GetWidth() / 2 );
1518 return ( arect.Intersects( pts[0], pts[1] )
1519 || arect.Intersects( pts[1], pts[2] )
1520 || arect.Intersects( pts[2], pts[3] )
1521 || arect.Intersects( pts[3], pts[0] ) );
1522 }
1523
1524 case SHAPE_T::SEGMENT:
1525 if( aContained )
1526 {
1527 return arect.Contains( GetStart() ) && aRect.Contains( GetEnd() );
1528 }
1529 else
1530 {
1531 // Account for the width of the line
1532 arect.Inflate( GetWidth() / 2 );
1533 return arect.Intersects( GetStart(), GetEnd() );
1534 }
1535
1536 case SHAPE_T::POLY:
1537 if( aContained )
1538 {
1539 return arect.Contains( bbox );
1540 }
1541 else
1542 {
1543 // Fast test: if aRect is outside the polygon bounding box,
1544 // rectangles cannot intersect
1545 if( !arect.Intersects( bbox ) )
1546 return false;
1547
1548 // Account for the width of the line
1549 arect.Inflate( GetWidth() / 2 );
1550
1551 for( int ii = 0; ii < m_poly.OutlineCount(); ++ii )
1552 {
1553 if( checkOutline( m_poly.Outline( ii ) ) )
1554 return true;
1555 }
1556
1557 return false;
1558 }
1559
1560 case SHAPE_T::BEZIER:
1561 if( aContained )
1562 {
1563 return arect.Contains( bbox );
1564 }
1565 else
1566 {
1567 // Fast test: if aRect is outside the polygon bounding box,
1568 // rectangles cannot intersect
1569 if( !arect.Intersects( bbox ) )
1570 return false;
1571
1572 // Account for the width of the line
1573 arect.Inflate( GetWidth() / 2 );
1574 const std::vector<VECTOR2I>* pts = &m_bezierPoints;
1575 std::vector<VECTOR2I> updatedBezierPoints;
1576
1577 if( m_bezierPoints.empty() )
1578 {
1580 converter.GetPoly( updatedBezierPoints, aAccuracy / 2 );
1581 pts = &updatedBezierPoints;
1582 }
1583
1584 for( unsigned ii = 1; ii < pts->size(); ii++ )
1585 {
1586 VECTOR2I vertex = ( *pts )[ii - 1];
1587 VECTOR2I vertexNext = ( *pts )[ii];
1588
1589 // Test if the point is within aRect
1590 if( arect.Contains( vertex ) )
1591 return true;
1592
1593 // Test if this edge intersects aRect
1594 if( arect.Intersects( vertex, vertexNext ) )
1595 return true;
1596 }
1597
1598 return false;
1599 }
1600
1601 default:
1603 return false;
1604 }
1605}
1606
1607
1608bool EDA_SHAPE::hitTest( const SHAPE_LINE_CHAIN& aPoly, bool aContained ) const
1609{
1611
1612 return KIGEOM::ShapeHitTest( aPoly, shape, aContained );
1613}
1614
1615
1616std::vector<VECTOR2I> EDA_SHAPE::GetRectCorners() const
1617{
1618 std::vector<VECTOR2I> pts;
1619 VECTOR2I topLeft = GetStart();
1620 VECTOR2I botRight = GetEnd();
1621
1622 pts.emplace_back( topLeft );
1623 pts.emplace_back( botRight.x, topLeft.y );
1624 pts.emplace_back( botRight );
1625 pts.emplace_back( topLeft.x, botRight.y );
1626
1627 return pts;
1628}
1629
1630
1631std::vector<VECTOR2I> EDA_SHAPE::GetCornersInSequence( EDA_ANGLE angle ) const
1632{
1633 std::vector<VECTOR2I> pts;
1634
1635 angle.Normalize();
1636
1637 BOX2I bbox = getBoundingBox();
1638 bbox.Normalize();
1639
1640 if( angle.IsCardinal() )
1641 {
1642 if( angle == ANGLE_0 )
1643 {
1644 pts.emplace_back( VECTOR2I( bbox.GetLeft(), bbox.GetTop() ) );
1645 pts.emplace_back( VECTOR2I( bbox.GetRight(), bbox.GetTop() ) );
1646 pts.emplace_back( VECTOR2I( bbox.GetRight(), bbox.GetBottom() ) );
1647 pts.emplace_back( VECTOR2I( bbox.GetLeft(), bbox.GetBottom() ) );
1648 }
1649 else if( angle == ANGLE_90 )
1650 {
1651 pts.emplace_back( VECTOR2I( bbox.GetLeft(), bbox.GetBottom() ) );
1652 pts.emplace_back( VECTOR2I( bbox.GetLeft(), bbox.GetTop() ) );
1653 pts.emplace_back( VECTOR2I( bbox.GetRight(), bbox.GetTop() ) );
1654 pts.emplace_back( VECTOR2I( bbox.GetRight(), bbox.GetBottom() ) );
1655 }
1656 else if( angle == ANGLE_180 )
1657 {
1658 pts.emplace_back( VECTOR2I( bbox.GetRight(), bbox.GetBottom() ) );
1659 pts.emplace_back( VECTOR2I( bbox.GetLeft(), bbox.GetBottom() ) );
1660 pts.emplace_back( VECTOR2I( bbox.GetLeft(), bbox.GetTop() ) );
1661 pts.emplace_back( VECTOR2I( bbox.GetRight(), bbox.GetTop() ) );
1662 }
1663 else if( angle == ANGLE_270 )
1664 {
1665 pts.emplace_back( VECTOR2I( bbox.GetRight(), bbox.GetTop() ) );
1666 pts.emplace_back( VECTOR2I( bbox.GetRight(), bbox.GetBottom() ) );
1667 pts.emplace_back( VECTOR2I( bbox.GetLeft(), bbox.GetBottom() ) );
1668 pts.emplace_back( VECTOR2I( bbox.GetLeft(), bbox.GetTop() ) );
1669 }
1670 }
1671 else
1672 {
1673 // This function was originally located in pcb_textbox.cpp and was later moved to eda_shape.cpp.
1674 // As a result of this move, access to getCorners was lost, since it is defined in the PCB_SHAPE
1675 // class within pcb_shape.cpp and is not available in the current context.
1676 //
1677 // Additionally, GetRectCorners() cannot be used here, as it assumes the rectangle is rotated by
1678 // a cardinal angle. In non-cardinal cases, it returns incorrect values (e.g., (0, 0)).
1679 //
1680 // To address this, a portion of the getCorners implementation for SHAPE_T::POLY elements
1681 // has been replicated here to restore the correct behavior.
1682 std::vector<VECTOR2I> corners;
1683
1684 for( int ii = 0; ii < GetPolyShape().OutlineCount(); ++ii )
1685 {
1686 for( const VECTOR2I& pt : GetPolyShape().Outline( ii ).CPoints() )
1687 corners.emplace_back( pt );
1688 }
1689
1690 while( corners.size() < 4 )
1691 corners.emplace_back( corners.back() + VECTOR2I( 10, 10 ) );
1692
1693 VECTOR2I minX = corners[0];
1694 VECTOR2I maxX = corners[0];
1695 VECTOR2I minY = corners[0];
1696 VECTOR2I maxY = corners[0];
1697
1698 for( const VECTOR2I& corner : corners )
1699 {
1700 if( corner.x < minX.x )
1701 minX = corner;
1702
1703 if( corner.x > maxX.x )
1704 maxX = corner;
1705
1706 if( corner.y < minY.y )
1707 minY = corner;
1708
1709 if( corner.y > maxY.y )
1710 maxY = corner;
1711 }
1712
1713 if( angle < ANGLE_90 )
1714 {
1715 pts.emplace_back( minX );
1716 pts.emplace_back( minY );
1717 pts.emplace_back( maxX );
1718 pts.emplace_back( maxY );
1719 }
1720 else if( angle < ANGLE_180 )
1721 {
1722 pts.emplace_back( maxY );
1723 pts.emplace_back( minX );
1724 pts.emplace_back( minY );
1725 pts.emplace_back( maxX );
1726 }
1727 else if( angle < ANGLE_270 )
1728 {
1729 pts.emplace_back( maxX );
1730 pts.emplace_back( maxY );
1731 pts.emplace_back( minX );
1732 pts.emplace_back( minY );
1733 }
1734 else
1735 {
1736 pts.emplace_back( minY );
1737 pts.emplace_back( maxX );
1738 pts.emplace_back( maxY );
1739 pts.emplace_back( minX );
1740 }
1741 }
1742
1743 return pts;
1744}
1745
1746
1748{
1749 // Start, end, and each inflection point the arc crosses will enclose the entire arc.
1750 // Only include the center when filled; it's not necessarily inside the BB of an unfilled
1751 // arc with a small included angle.
1752 aBBox.SetOrigin( m_start );
1753 aBBox.Merge( m_end );
1754
1755 if( IsAnyFill() )
1756 aBBox.Merge( m_arcCenter );
1757
1758 int radius = GetRadius();
1759 EDA_ANGLE t1, t2;
1760
1761 CalcArcAngles( t1, t2 );
1762
1763 t1.Normalize();
1764 t2.Normalize();
1765
1766 if( t2 > t1 )
1767 {
1768 if( t1 < ANGLE_0 && t2 > ANGLE_0 )
1769 aBBox.Merge( VECTOR2I( m_arcCenter.x + radius, m_arcCenter.y ) ); // right
1770
1771 if( t1 < ANGLE_90 && t2 > ANGLE_90 )
1772 aBBox.Merge( VECTOR2I( m_arcCenter.x, m_arcCenter.y + radius ) ); // down
1773
1774 if( t1 < ANGLE_180 && t2 > ANGLE_180 )
1775 aBBox.Merge( VECTOR2I( m_arcCenter.x - radius, m_arcCenter.y ) ); // left
1776
1777 if( t1 < ANGLE_270 && t2 > ANGLE_270 )
1778 aBBox.Merge( VECTOR2I( m_arcCenter.x, m_arcCenter.y - radius ) ); // up
1779 }
1780 else
1781 {
1782 if( t1 < ANGLE_0 || t2 > ANGLE_0 )
1783 aBBox.Merge( VECTOR2I( m_arcCenter.x + radius, m_arcCenter.y ) ); // right
1784
1785 if( t1 < ANGLE_90 || t2 > ANGLE_90 )
1786 aBBox.Merge( VECTOR2I( m_arcCenter.x, m_arcCenter.y + radius ) ); // down
1787
1788 if( t1 < ANGLE_180 || t2 > ANGLE_180 )
1789 aBBox.Merge( VECTOR2I( m_arcCenter.x - radius, m_arcCenter.y ) ); // left
1790
1791 if( t1 < ANGLE_270 || t2 > ANGLE_270 )
1792 aBBox.Merge( VECTOR2I( m_arcCenter.x, m_arcCenter.y - radius ) ); // up
1793 }
1794}
1795
1796
1797void EDA_SHAPE::SetPolyPoints( const std::vector<VECTOR2I>& aPoints )
1798{
1799 m_poly.RemoveAllContours();
1800 m_poly.NewOutline();
1801
1802 for( const VECTOR2I& p : aPoints )
1803 m_poly.Append( p.x, p.y );
1804}
1805
1806
1807std::vector<SHAPE*> EDA_SHAPE::makeEffectiveShapes( bool aEdgeOnly, bool aLineChainOnly, bool aHittesting ) const
1808{
1809 std::vector<SHAPE*> effectiveShapes;
1810 int width = GetEffectiveWidth();
1811 bool solidFill = IsSolidFill()
1812 || IsHatchedFill()
1813 || IsProxyItem()
1814 || ( aHittesting && IsFilledForHitTesting() );
1815
1816 if( aEdgeOnly )
1817 solidFill = false;
1818
1819 switch( m_shape )
1820 {
1821 case SHAPE_T::ARC:
1822 effectiveShapes.emplace_back( new SHAPE_ARC( m_arcCenter, m_start, GetArcAngle(), width ) );
1823 break;
1824
1825 case SHAPE_T::SEGMENT:
1826 effectiveShapes.emplace_back( new SHAPE_SEGMENT( m_start, m_end, width ) );
1827 break;
1828
1829 case SHAPE_T::RECTANGLE:
1830 {
1831 if( m_cornerRadius > 0 )
1832 {
1834 SHAPE_POLY_SET poly;
1835 rr.TransformToPolygon( poly );
1836 SHAPE_LINE_CHAIN outline = poly.Outline( 0 );
1837
1838 if( solidFill )
1839 effectiveShapes.emplace_back( new SHAPE_SIMPLE( outline ) );
1840
1841 if( width > 0 || !solidFill )
1842 {
1843 for( int i = 0; i < outline.PointCount() - 1; ++i )
1844 {
1845 effectiveShapes.emplace_back( new SHAPE_SEGMENT( outline.CPoint( i ), outline.CPoint( i + 1 ),
1846 width ) );
1847 }
1848
1849 effectiveShapes.emplace_back( new SHAPE_SEGMENT( outline.CPoint( outline.PointCount() - 1 ),
1850 outline.CPoint( 0 ), width ) );
1851 }
1852 }
1853 else
1854 {
1855 std::vector<VECTOR2I> pts = GetRectCorners();
1856
1857 if( solidFill )
1858 effectiveShapes.emplace_back( new SHAPE_SIMPLE( pts ) );
1859
1860 if( width > 0 || !solidFill )
1861 {
1862 effectiveShapes.emplace_back( new SHAPE_SEGMENT( pts[0], pts[1], width ) );
1863 effectiveShapes.emplace_back( new SHAPE_SEGMENT( pts[1], pts[2], width ) );
1864 effectiveShapes.emplace_back( new SHAPE_SEGMENT( pts[2], pts[3], width ) );
1865 effectiveShapes.emplace_back( new SHAPE_SEGMENT( pts[3], pts[0], width ) );
1866 }
1867 }
1868 break;
1869 }
1870
1871 case SHAPE_T::CIRCLE:
1872 {
1873 if( solidFill )
1874 effectiveShapes.emplace_back( new SHAPE_CIRCLE( getCenter(), GetRadius() ) );
1875
1876 if( width > 0 || !solidFill )
1877 effectiveShapes.emplace_back( new SHAPE_ARC( getCenter(), GetEnd(), ANGLE_360, width ) );
1878
1879 break;
1880 }
1881
1882 case SHAPE_T::BEZIER:
1883 {
1884 std::vector<VECTOR2I> bezierPoints = buildBezierToSegmentsPointsList( getMaxError() );
1885 VECTOR2I start_pt = bezierPoints[0];
1886
1887 for( unsigned int jj = 1; jj < bezierPoints.size(); jj++ )
1888 {
1889 VECTOR2I end_pt = bezierPoints[jj];
1890 effectiveShapes.emplace_back( new SHAPE_SEGMENT( start_pt, end_pt, width ) );
1891 start_pt = end_pt;
1892 }
1893
1894 break;
1895 }
1896
1897 case SHAPE_T::POLY:
1898 {
1899 if( GetPolyShape().OutlineCount() == 0 ) // malformed/empty polygon
1900 break;
1901
1902 for( int ii = 0; ii < GetPolyShape().OutlineCount(); ++ii )
1903 {
1904 const SHAPE_LINE_CHAIN& l = GetPolyShape().COutline( ii );
1905
1906 if( solidFill )
1907 effectiveShapes.emplace_back( new SHAPE_SIMPLE( l ) );
1908
1909 if( width > 0 || !IsSolidFill() || aEdgeOnly )
1910 {
1911 int segCount = l.SegmentCount();
1912
1913 if( aLineChainOnly && l.IsClosed() )
1914 segCount--; // Treat closed chain as open
1915
1916 for( int jj = 0; jj < segCount; jj++ )
1917 effectiveShapes.emplace_back( new SHAPE_SEGMENT( l.CSegment( jj ), width ) );
1918 }
1919 }
1920 }
1921 break;
1922
1923 default:
1925 break;
1926 }
1927
1928 return effectiveShapes;
1929}
1930
1931
1932void EDA_SHAPE::DupPolyPointsList( std::vector<VECTOR2I>& aBuffer ) const
1933{
1934 for( int ii = 0; ii < m_poly.OutlineCount(); ++ii )
1935 {
1936 int pointCount = m_poly.COutline( ii ).PointCount();
1937
1938 if( pointCount )
1939 {
1940 aBuffer.reserve( pointCount );
1941
1942 for ( auto iter = m_poly.CIterate(); iter; iter++ )
1943 aBuffer.emplace_back( iter->x, iter->y );
1944 }
1945 }
1946}
1947
1948
1950{
1951 // return true if the polygonal shape is valid (has more than 2 points)
1952 return GetPolyShape().OutlineCount() > 0 && GetPolyShape().Outline( 0 ).PointCount() > 2;
1953}
1954
1955
1957{
1958 // return the number of corners of the polygonal shape
1959 // this shape is expected to be only one polygon without hole
1960 return GetPolyShape().OutlineCount() ? GetPolyShape().VertexCount( 0 ) : 0;
1961}
1962
1963
1964void EDA_SHAPE::beginEdit( const VECTOR2I& aPosition )
1965{
1966 switch( GetShape() )
1967 {
1968 case SHAPE_T::SEGMENT:
1969 case SHAPE_T::CIRCLE:
1970 case SHAPE_T::RECTANGLE:
1971 SetStart( aPosition );
1972 SetEnd( aPosition );
1973 break;
1974
1975 case SHAPE_T::ARC:
1976 SetArcGeometry( aPosition, aPosition, aPosition );
1977 m_editState = 1;
1978 break;
1979
1980 case SHAPE_T::BEZIER:
1981 SetStart( aPosition );
1982 SetEnd( aPosition );
1983 SetBezierC1( aPosition );
1984 SetBezierC2( aPosition );
1985 m_editState = 1;
1986
1988 break;
1989
1990 case SHAPE_T::POLY:
1991 m_poly.NewOutline();
1992 m_poly.Outline( 0 ).SetClosed( false );
1993
1994 // Start and end of the first segment (co-located for now)
1995 m_poly.Outline( 0 ).Append( aPosition );
1996 m_poly.Outline( 0 ).Append( aPosition, true );
1997 break;
1998
1999 default:
2001 }
2002}
2003
2004
2005bool EDA_SHAPE::continueEdit( const VECTOR2I& aPosition )
2006{
2007 switch( GetShape() )
2008 {
2009 case SHAPE_T::ARC:
2010 case SHAPE_T::SEGMENT:
2011 case SHAPE_T::CIRCLE:
2012 case SHAPE_T::RECTANGLE:
2013 return false;
2014
2015 case SHAPE_T::BEZIER:
2016 if( m_editState == 3 )
2017 return false;
2018
2019 m_editState++;
2020 return true;
2021
2022 case SHAPE_T::POLY:
2023 {
2024 SHAPE_LINE_CHAIN& poly = m_poly.Outline( 0 );
2025
2026 // do not add zero-length segments
2027 if( poly.CPoint( (int) poly.GetPointCount() - 2 ) != poly.CLastPoint() )
2028 poly.Append( aPosition, true );
2029 }
2030 return true;
2031
2032 default:
2034 return false;
2035 }
2036}
2037
2038
2039void EDA_SHAPE::calcEdit( const VECTOR2I& aPosition )
2040{
2041#define sq( x ) pow( x, 2 )
2042
2043 switch( GetShape() )
2044 {
2045 case SHAPE_T::SEGMENT:
2046 case SHAPE_T::CIRCLE:
2047 case SHAPE_T::RECTANGLE:
2048 SetEnd( aPosition );
2049 break;
2050
2051 case SHAPE_T::BEZIER:
2052 {
2053 switch( m_editState )
2054 {
2055 case 0:
2056 SetStart( aPosition );
2057 SetEnd( aPosition );
2058 SetBezierC1( aPosition );
2059 SetBezierC2( aPosition );
2060 break;
2061
2062 case 1:
2063 SetBezierC2( aPosition );
2064 SetEnd( aPosition );
2065 break;
2066
2067 case 2:
2068 SetBezierC1( aPosition );
2069 break;
2070
2071 case 3:
2072 SetBezierC2( aPosition );
2073 break;
2074 }
2075
2077 }
2078 break;
2079
2080 case SHAPE_T::ARC:
2081 {
2082 double radius = GetRadius();
2083 EDA_ANGLE lastAngle = GetArcAngle();
2084
2085 // Edit state 0: drawing: place start
2086 // Edit state 1: drawing: place end (center calculated for 90-degree subtended angle)
2087 // Edit state 2: point edit: move start (center calculated for invariant subtended angle)
2088 // Edit state 3: point edit: move end (center calculated for invariant subtended angle)
2089 // Edit state 4: point edit: move center
2090 // Edit state 5: point edit: move arc-mid-point
2091
2092 switch( m_editState )
2093 {
2094 case 0:
2095 SetArcGeometry( aPosition, aPosition, aPosition );
2096 return;
2097
2098 case 1:
2099 m_end = aPosition;
2100 radius = m_start.Distance( m_end ) * M_SQRT1_2;
2101 break;
2102
2103 case 2:
2104 case 3:
2105 {
2106 VECTOR2I v = m_start - m_end;
2107 double chordBefore = v.SquaredEuclideanNorm();
2108
2109 if( m_editState == 2 )
2110 m_start = aPosition;
2111 else
2112 m_end = aPosition;
2113
2114 v = m_start - m_end;
2115
2116 double chordAfter = v.SquaredEuclideanNorm();
2117 double ratio = 0.0;
2118
2119 if( chordBefore > 0 )
2120 ratio = chordAfter / chordBefore;
2121
2122 if( ratio != 0 )
2123 radius = std::max( sqrt( sq( radius ) * ratio ), sqrt( chordAfter ) / 2 );
2124 break;
2125 }
2126
2127 case 4:
2128 {
2129 double radialA = m_start.Distance( aPosition );
2130 double radialB = m_end.Distance( aPosition );
2131 radius = ( radialA + radialB ) / 2.0;
2132 break;
2133 }
2134
2135 case 5:
2136 SetArcGeometry( GetStart(), aPosition, GetEnd() );
2137 return;
2138 }
2139
2140 // Calculate center based on start, end, and radius
2141 //
2142 // Let 'l' be the length of the chord and 'm' the middle point of the chord
2143 double l = m_start.Distance( m_end );
2144 VECTOR2D m = ( m_start + m_end ) / 2;
2145 double sqRadDiff = ( radius * radius ) - ( l * l ) / 4.0;
2146
2147 // Calculate 'd', the vector from the chord midpoint to the center
2148 VECTOR2D d;
2149
2150 if( l > 0 && sqRadDiff >= 0 )
2151 {
2152 d.x = sqrt( sqRadDiff ) * ( m_start.y - m_end.y ) / l;
2153 d.y = sqrt( sqRadDiff ) * ( m_end.x - m_start.x ) / l;
2154 }
2155
2156 VECTOR2I c1 = KiROUND( m + d );
2157 VECTOR2I c2 = KiROUND( m - d );
2158
2159 // Solution gives us 2 centers; we need to pick one:
2160 switch( m_editState )
2161 {
2162 case 1:
2163 // Keep arc clockwise while drawing i.e. arc angle = 90 deg.
2164 // it can be 90 or 270 deg depending on the arc center choice (c1 or c2)
2165 m_arcCenter = c1; // first trial
2166
2167 if( GetArcAngle() > ANGLE_180 )
2168 m_arcCenter = c2;
2169
2170 break;
2171
2172 case 2:
2173 case 3:
2174 // Pick the one of c1, c2 to keep arc on the same side
2175 m_arcCenter = c1; // first trial
2176
2177 if( ( lastAngle < ANGLE_180 ) != ( GetArcAngle() < ANGLE_180 ) )
2178 m_arcCenter = c2;
2179
2180 break;
2181
2182 case 4:
2183 // Pick the one closer to the mouse position
2184 m_arcCenter = c1.Distance( aPosition ) < c2.Distance( aPosition ) ? c1 : c2;
2185 break;
2186 }
2187
2188 break;
2189 }
2190
2191 case SHAPE_T::POLY:
2192 m_poly.Outline( 0 ).SetPoint( m_poly.Outline( 0 ).GetPointCount() - 1, aPosition );
2193 break;
2194
2195 default:
2197 }
2198}
2199
2200
2201void EDA_SHAPE::endEdit( bool aClosed )
2202{
2203 switch( GetShape() )
2204 {
2205 case SHAPE_T::ARC:
2206 case SHAPE_T::SEGMENT:
2207 case SHAPE_T::CIRCLE:
2208 case SHAPE_T::RECTANGLE:
2209 case SHAPE_T::BEZIER:
2210 break;
2211
2212 case SHAPE_T::POLY:
2213 {
2214 SHAPE_LINE_CHAIN& poly = m_poly.Outline( 0 );
2215
2216 // do not include last point twice
2217 if( poly.GetPointCount() > 2 )
2218 {
2219 if( poly.CPoint( poly.GetPointCount() - 2 ) == poly.CLastPoint() )
2220 {
2221 poly.SetClosed( aClosed );
2222 }
2223 else
2224 {
2225 poly.SetClosed( false );
2226 poly.Remove( poly.GetPointCount() - 1 );
2227 }
2228 }
2229
2230 break;
2231 }
2232
2233 default:
2235 }
2236}
2237
2238
2240{
2241 EDA_SHAPE* image = dynamic_cast<EDA_SHAPE*>( aImage );
2242 assert( image );
2243
2244 #define SWAPITEM( x ) std::swap( x, image->x )
2245 SWAPITEM( m_stroke );
2246 SWAPITEM( m_start );
2247 SWAPITEM( m_end );
2249 SWAPITEM( m_shape );
2253 SWAPITEM( m_poly );
2255 SWAPITEM( m_fill );
2259 #undef SWAPITEM
2260
2261 m_hatchingDirty = true;
2262}
2263
2264
2265int EDA_SHAPE::Compare( const EDA_SHAPE* aOther ) const
2266{
2267#define EPSILON 2 // Should be enough for rounding errors on calculated items
2268
2269#define TEST( a, b ) { if( a != b ) return a - b; }
2270#define TEST_E( a, b ) { if( abs( a - b ) > EPSILON ) return a - b; }
2271#define TEST_PT( a, b ) { TEST_E( a.x, b.x ); TEST_E( a.y, b.y ); }
2272
2273 TEST_PT( m_start, aOther->m_start );
2274 TEST_PT( m_end, aOther->m_end );
2275
2276 TEST( (int) m_shape, (int) aOther->m_shape );
2277
2279 {
2281 }
2282 else if( m_shape == SHAPE_T::ARC )
2283 {
2284 TEST_PT( GetArcMid(), aOther->GetArcMid() );
2285 }
2286 else if( m_shape == SHAPE_T::BEZIER )
2287 {
2288 TEST_PT( m_bezierC1, aOther->m_bezierC1 );
2289 TEST_PT( m_bezierC2, aOther->m_bezierC2 );
2290 }
2291 else if( m_shape == SHAPE_T::POLY )
2292 {
2293 TEST( m_poly.TotalVertices(), aOther->m_poly.TotalVertices() );
2294 }
2295
2296 for( size_t ii = 0; ii < m_bezierPoints.size(); ++ii )
2297 TEST_PT( m_bezierPoints[ii], aOther->m_bezierPoints[ii] );
2298
2299 for( int ii = 0; ii < m_poly.TotalVertices(); ++ii )
2300 TEST_PT( m_poly.CVertex( ii ), aOther->m_poly.CVertex( ii ) );
2301
2302 TEST_E( m_stroke.GetWidth(), aOther->m_stroke.GetWidth() );
2303 TEST( (int) m_stroke.GetLineStyle(), (int) aOther->m_stroke.GetLineStyle() );
2304 TEST( (int) m_fill, (int) aOther->m_fill );
2305
2306 return 0;
2307}
2308
2309
2310void EDA_SHAPE::TransformShapeToPolygon( SHAPE_POLY_SET& aBuffer, int aClearance, int aError,
2311 ERROR_LOC aErrorLoc, bool ignoreLineWidth, bool includeFill ) const
2312{
2313 bool solidFill = IsSolidFill() || ( IsHatchedFill() && !includeFill ) || IsProxyItem();
2314 int width = ignoreLineWidth ? 0 : GetWidth();
2315
2316 width += 2 * aClearance;
2317
2318 switch( m_shape )
2319 {
2320 case SHAPE_T::CIRCLE:
2321 {
2322 int r = GetRadius();
2323
2324 if( solidFill )
2325 TransformCircleToPolygon( aBuffer, getCenter(), r + width / 2, aError, aErrorLoc );
2326 else
2327 TransformRingToPolygon( aBuffer, getCenter(), r, width, aError, aErrorLoc );
2328
2329 break;
2330 }
2331
2332 case SHAPE_T::RECTANGLE:
2333 {
2334 if( GetCornerRadius() > 0 )
2335 {
2336 // Use specialized function for rounded rectangles
2338 VECTOR2I position = GetStart() + size / 2; // Center position
2339
2340 if( solidFill )
2341 {
2343 0.0, 0, width / 2, aError, aErrorLoc );
2344 }
2345 else
2346 {
2347 // Export outline as a set of thick segments:
2348 SHAPE_POLY_SET poly;
2350 0.0, 0, 0, aError, aErrorLoc );
2351 SHAPE_LINE_CHAIN& outline = poly.Outline( 0 );
2352 outline.SetClosed( true );
2353
2354 for( int ii = 0; ii < outline.PointCount(); ii++ )
2355 {
2356 TransformOvalToPolygon( aBuffer, outline.CPoint( ii ), outline.CPoint( ii+1 ), width,
2357 aError, aErrorLoc );
2358 }
2359 }
2360 }
2361 else
2362 {
2363 std::vector<VECTOR2I> pts = GetRectCorners();
2364
2365 if( solidFill )
2366 {
2367 aBuffer.NewOutline();
2368
2369 for( const VECTOR2I& pt : pts )
2370 aBuffer.Append( pt );
2371 }
2372
2373 if( width > 0 || !solidFill )
2374 {
2375 // Add in segments
2376 TransformOvalToPolygon( aBuffer, pts[0], pts[1], width, aError, aErrorLoc );
2377 TransformOvalToPolygon( aBuffer, pts[1], pts[2], width, aError, aErrorLoc );
2378 TransformOvalToPolygon( aBuffer, pts[2], pts[3], width, aError, aErrorLoc );
2379 TransformOvalToPolygon( aBuffer, pts[3], pts[0], width, aError, aErrorLoc );
2380 }
2381 }
2382
2383 break;
2384 }
2385
2386 case SHAPE_T::ARC:
2387 TransformArcToPolygon( aBuffer, GetStart(), GetArcMid(), GetEnd(), width, aError, aErrorLoc );
2388 break;
2389
2390 case SHAPE_T::SEGMENT:
2391 TransformOvalToPolygon( aBuffer, GetStart(), GetEnd(), width, aError, aErrorLoc );
2392 break;
2393
2394 case SHAPE_T::POLY:
2395 {
2396 if( !IsPolyShapeValid() )
2397 break;
2398
2399 if( solidFill )
2400 {
2401 for( int ii = 0; ii < m_poly.OutlineCount(); ++ii )
2402 {
2403 const SHAPE_LINE_CHAIN& poly = m_poly.Outline( ii );
2404 SHAPE_POLY_SET tmp;
2405 tmp.NewOutline();
2406
2407 for( int jj = 0; jj < (int) poly.GetPointCount(); ++jj )
2408 tmp.Append( poly.GetPoint( jj ) );
2409
2410 if( width > 0 )
2411 {
2412 int inflate = width / 2;
2413
2414 if( aErrorLoc == ERROR_OUTSIDE )
2415 inflate += aError;
2416
2417 tmp.Inflate( inflate, CORNER_STRATEGY::ROUND_ALL_CORNERS, aError );
2418 }
2419
2420 aBuffer.Append( tmp );
2421 }
2422 }
2423 else
2424 {
2425 for( int ii = 0; ii < m_poly.OutlineCount(); ++ii )
2426 {
2427 const SHAPE_LINE_CHAIN& poly = m_poly.Outline( ii );
2428
2429 for( int jj = 0; jj < (int) poly.SegmentCount(); ++jj )
2430 {
2431 const SEG& seg = poly.GetSegment( jj );
2432 TransformOvalToPolygon( aBuffer, seg.A, seg.B, width, aError, aErrorLoc );
2433 }
2434 }
2435 }
2436
2437 break;
2438 }
2439
2440 case SHAPE_T::BEZIER:
2441 {
2442 std::vector<VECTOR2I> ctrlPts = { GetStart(), GetBezierC1(), GetBezierC2(), GetEnd() };
2443 BEZIER_POLY converter( ctrlPts );
2444 std::vector<VECTOR2I> poly;
2445 converter.GetPoly( poly, aError );
2446
2447 for( unsigned ii = 1; ii < poly.size(); ii++ )
2448 TransformOvalToPolygon( aBuffer, poly[ii - 1], poly[ii], width, aError, aErrorLoc );
2449
2450 break;
2451 }
2452
2453 default:
2455 break;
2456 }
2457
2458 if( IsHatchedFill() && includeFill )
2459 {
2460 for( int ii = 0; ii < GetHatching().OutlineCount(); ++ii )
2461 aBuffer.AddOutline( GetHatching().COutline( ii ) );
2462 }
2463}
2464
2465
2466void EDA_SHAPE::SetWidth( int aWidth )
2467{
2468 m_stroke.SetWidth( aWidth );
2469 m_hatchingDirty = true;
2470}
2471
2472
2474{
2475 m_stroke.SetLineStyle( aStyle );
2476}
2477
2478
2480{
2481 if( m_stroke.GetLineStyle() != LINE_STYLE::DEFAULT )
2482 return m_stroke.GetLineStyle();
2483
2484 return LINE_STYLE::SOLID;
2485}
2486
2487
2488bool EDA_SHAPE::operator==( const EDA_SHAPE& aOther ) const
2489{
2490 if( GetShape() != aOther.GetShape() )
2491 return false;
2492
2493 if( m_fill != aOther.m_fill )
2494 return false;
2495
2496 if( m_stroke.GetWidth() != aOther.m_stroke.GetWidth() )
2497 return false;
2498
2499 if( m_stroke.GetLineStyle() != aOther.m_stroke.GetLineStyle() )
2500 return false;
2501
2502 if( m_fillColor != aOther.m_fillColor )
2503 return false;
2504
2505 if( m_start != aOther.m_start )
2506 return false;
2507
2508 if( m_end != aOther.m_end )
2509 return false;
2510
2511 if( m_arcCenter != aOther.m_arcCenter )
2512 return false;
2513
2514 if( m_bezierC1 != aOther.m_bezierC1 )
2515 return false;
2516
2517 if( m_bezierC2 != aOther.m_bezierC2 )
2518 return false;
2519
2520 if( m_bezierPoints != aOther.m_bezierPoints )
2521 return false;
2522
2523 for( int ii = 0; ii < m_poly.TotalVertices(); ++ii )
2524 {
2525 if( m_poly.CVertex( ii ) != aOther.m_poly.CVertex( ii ) )
2526 return false;
2527 }
2528
2529 return true;
2530}
2531
2532
2533double EDA_SHAPE::Similarity( const EDA_SHAPE& aOther ) const
2534{
2535 if( GetShape() != aOther.GetShape() )
2536 return 0.0;
2537
2538 double similarity = 1.0;
2539
2540 if( m_fill != aOther.m_fill )
2541 similarity *= 0.9;
2542
2543 if( m_stroke.GetWidth() != aOther.m_stroke.GetWidth() )
2544 similarity *= 0.9;
2545
2546 if( m_stroke.GetLineStyle() != aOther.m_stroke.GetLineStyle() )
2547 similarity *= 0.9;
2548
2549 if( m_fillColor != aOther.m_fillColor )
2550 similarity *= 0.9;
2551
2552 if( m_start != aOther.m_start )
2553 similarity *= 0.9;
2554
2555 if( m_end != aOther.m_end )
2556 similarity *= 0.9;
2557
2558 if( m_arcCenter != aOther.m_arcCenter )
2559 similarity *= 0.9;
2560
2561 if( m_bezierC1 != aOther.m_bezierC1 )
2562 similarity *= 0.9;
2563
2564 if( m_bezierC2 != aOther.m_bezierC2 )
2565 similarity *= 0.9;
2566
2567 {
2568 int m = m_bezierPoints.size();
2569 int n = aOther.m_bezierPoints.size();
2570
2571 size_t longest = alg::longest_common_subset( m_bezierPoints, aOther.m_bezierPoints );
2572
2573 similarity *= std::pow( 0.9, m + n - 2 * longest );
2574 }
2575
2576 {
2577 int m = m_poly.TotalVertices();
2578 int n = aOther.m_poly.TotalVertices();
2579 std::vector<VECTOR2I> poly;
2580 std::vector<VECTOR2I> otherPoly;
2581 VECTOR2I lastPt( 0, 0 );
2582
2583 // We look for the longest common subset of the two polygons, but we need to
2584 // offset each point because we're actually looking for overall similarity, not just
2585 // exact matches. So if the zone is moved by 1IU, we only want one point to be
2586 // considered "moved" rather than the entire polygon. In this case, the first point
2587 // will not be a match but the rest of the sequence will.
2588 for( int ii = 0; ii < m; ++ii )
2589 {
2590 poly.emplace_back( lastPt - m_poly.CVertex( ii ) );
2591 lastPt = m_poly.CVertex( ii );
2592 }
2593
2594 lastPt = VECTOR2I( 0, 0 );
2595
2596 for( int ii = 0; ii < n; ++ii )
2597 {
2598 otherPoly.emplace_back( lastPt - aOther.m_poly.CVertex( ii ) );
2599 lastPt = aOther.m_poly.CVertex( ii );
2600 }
2601
2602 size_t longest = alg::longest_common_subset( poly, otherPoly );
2603
2604 similarity *= std::pow( 0.9, m + n - 2 * longest );
2605 }
2606
2607 return similarity;
2608}
2609
2610
2614
2615
2616static struct EDA_SHAPE_DESC
2617{
2619 {
2621 .Map( SHAPE_T::SEGMENT, _HKI( "Segment" ) )
2622 .Map( SHAPE_T::RECTANGLE, _HKI( "Rectangle" ) )
2623 .Map( SHAPE_T::ARC, _HKI( "Arc" ) )
2624 .Map( SHAPE_T::CIRCLE, _HKI( "Circle" ) )
2625 .Map( SHAPE_T::POLY, _HKI( "Polygon" ) )
2626 .Map( SHAPE_T::BEZIER, _HKI( "Bezier" ) );
2627
2629
2630 if( lineStyleEnum.Choices().GetCount() == 0 )
2631 {
2632 lineStyleEnum.Map( LINE_STYLE::SOLID, _HKI( "Solid" ) )
2633 .Map( LINE_STYLE::DASH, _HKI( "Dashed" ) )
2634 .Map( LINE_STYLE::DOT, _HKI( "Dotted" ) )
2635 .Map( LINE_STYLE::DASHDOT, _HKI( "Dash-Dot" ) )
2636 .Map( LINE_STYLE::DASHDOTDOT, _HKI( "Dash-Dot-Dot" ) );
2637 }
2638
2640
2641 if( hatchModeEnum.Choices().GetCount() == 0 )
2642 {
2643 hatchModeEnum.Map( UI_FILL_MODE::NONE, _HKI( "None" ) );
2644 hatchModeEnum.Map( UI_FILL_MODE::SOLID, _HKI( "Solid" ) );
2645 hatchModeEnum.Map( UI_FILL_MODE::HATCH, _HKI( "Hatch" ) );
2646 hatchModeEnum.Map( UI_FILL_MODE::REVERSE_HATCH, _HKI( "Reverse Hatch" ) );
2647 hatchModeEnum.Map( UI_FILL_MODE::CROSS_HATCH, _HKI( "Cross-hatch" ) );
2648 }
2649
2652
2653 auto isNotPolygonOrCircle =
2654 []( INSPECTABLE* aItem ) -> bool
2655 {
2656 // Polygons, unlike other shapes, have no meaningful start or end coordinates
2657 if( EDA_SHAPE* shape = dynamic_cast<EDA_SHAPE*>( aItem ) )
2658 return shape->GetShape() != SHAPE_T::POLY && shape->GetShape() != SHAPE_T::CIRCLE;
2659
2660 return false;
2661 };
2662
2663 auto isCircle =
2664 []( INSPECTABLE* aItem ) -> bool
2665 {
2666 // Polygons, unlike other shapes, have no meaningful start or end coordinates
2667 if( EDA_SHAPE* shape = dynamic_cast<EDA_SHAPE*>( aItem ) )
2668 return shape->GetShape() == SHAPE_T::CIRCLE;
2669
2670 return false;
2671 };
2672
2673 auto isRectangle =
2674 []( INSPECTABLE* aItem ) -> bool
2675 {
2676 // Polygons, unlike other shapes, have no meaningful start or end coordinates
2677 if( EDA_SHAPE* shape = dynamic_cast<EDA_SHAPE*>( aItem ) )
2678 return shape->GetShape() == SHAPE_T::RECTANGLE;
2679
2680 return false;
2681 };
2682
2683 const wxString shapeProps = _HKI( "Shape Properties" );
2684
2685 auto shape = new PROPERTY_ENUM<EDA_SHAPE, SHAPE_T>( _HKI( "Shape" ),
2687 propMgr.AddProperty( shape, shapeProps );
2688
2689 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "Start X" ),
2692 shapeProps )
2693 .SetAvailableFunc( isNotPolygonOrCircle );
2694 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "Start Y" ),
2697 shapeProps )
2698 .SetAvailableFunc( isNotPolygonOrCircle );
2699
2700 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "Center X" ),
2703 shapeProps )
2704 .SetAvailableFunc( isCircle );
2705
2706 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "Center Y" ),
2709 shapeProps )
2710 .SetAvailableFunc( isCircle );
2711
2712 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "Radius" ),
2715 shapeProps )
2716 .SetAvailableFunc( isCircle );
2717
2718 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "End X" ),
2721 shapeProps )
2722 .SetAvailableFunc( isNotPolygonOrCircle );
2723
2724 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "End Y" ),
2727 shapeProps )
2728 .SetAvailableFunc( isNotPolygonOrCircle );
2729
2730 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "Width" ),
2733 shapeProps )
2734 .SetAvailableFunc( isRectangle );
2735
2736 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "Height" ),
2739 shapeProps )
2740 .SetAvailableFunc( isRectangle );
2741
2742 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "Corner Radius" ),
2745 shapeProps )
2746 .SetAvailableFunc( isRectangle )
2747 .SetValidator( []( const wxAny&& aValue, EDA_ITEM* aItem ) -> VALIDATOR_RESULT
2748 {
2749 wxASSERT_MSG( aValue.CheckType<int>(),
2750 "Expecting int-containing value" );
2751
2752 int radius = aValue.As<int>();
2753
2754 EDA_SHAPE* prop_shape = dynamic_cast<EDA_SHAPE*>( aItem );
2755
2756 if( !prop_shape )
2757 {
2758 wxLogDebug( wxT( "Corner Radius Validator: Not an EDA_SHAPE" ) );
2759 return std::nullopt;
2760 }
2761
2762 int maxRadius = std::min( prop_shape->GetRectangleWidth(),
2763 prop_shape->GetRectangleHeight() ) / 2;
2764
2765 if( radius > maxRadius )
2766 return std::make_unique<VALIDATION_ERROR_TOO_LARGE<int>>( radius, maxRadius );
2767 else if( radius < 0 )
2768 return std::make_unique<VALIDATION_ERROR_TOO_SMALL<int>>( radius, 0 );
2769
2770 return std::nullopt;
2771 } );
2772
2773 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "Line Width" ),
2775 shapeProps );
2776
2777 propMgr.AddProperty( new PROPERTY_ENUM<EDA_SHAPE, LINE_STYLE>( _HKI( "Line Style" ),
2779 shapeProps );
2780
2781 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, COLOR4D>( _HKI( "Line Color" ),
2783 shapeProps )
2785
2786 auto angle = new PROPERTY<EDA_SHAPE, EDA_ANGLE>( _HKI( "Angle" ),
2789 angle->SetAvailableFunc(
2790 [=]( INSPECTABLE* aItem ) -> bool
2791 {
2792 if( EDA_SHAPE* curr_shape = dynamic_cast<EDA_SHAPE*>( aItem ) )
2793 return curr_shape->GetShape() == SHAPE_T::ARC;
2794
2795 return false;
2796 } );
2797 propMgr.AddProperty( angle, shapeProps );
2798
2799 auto fillAvailable =
2800 [=]( INSPECTABLE* aItem ) -> bool
2801 {
2802 if( EDA_ITEM* edaItem = dynamic_cast<EDA_ITEM*>( aItem ) )
2803 {
2804 // For some reason masking "Filled" and "Fill Color" at the
2805 // PCB_TABLECELL level doesn't work.
2806 if( edaItem->Type() == PCB_TABLECELL_T )
2807 return false;
2808 }
2809
2810 if( EDA_SHAPE* edaShape = dynamic_cast<EDA_SHAPE*>( aItem ) )
2811 {
2812 switch( edaShape->GetShape() )
2813 {
2814 case SHAPE_T::POLY:
2815 case SHAPE_T::RECTANGLE:
2816 case SHAPE_T::CIRCLE:
2817 case SHAPE_T::BEZIER:
2818 return true;
2819
2820 default:
2821 return false;
2822 }
2823 }
2824
2825 return false;
2826 };
2827
2830 shapeProps )
2831 .SetAvailableFunc( fillAvailable );
2832
2833 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, COLOR4D>( _HKI( "Fill Color" ),
2835 shapeProps )
2836 .SetAvailableFunc( fillAvailable )
2838 }
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 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 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:494
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:519
int m_cornerRadius
Definition eda_shape.h:504
bool m_hatchingDirty
Definition eda_shape.h:500
bool m_endsSwapped
Definition eda_shape.h:493
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:518
virtual int getMaxError() const
Definition eda_shape.h:490
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:502
void SetEndY(int aY)
Definition eda_shape.h:226
virtual int GetEffectiveWidth() const
Definition eda_shape.h:157
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:499
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:379
SHAPE_POLY_SET & GetPolyShape()
Definition eda_shape.h:337
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:474
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:345
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:509
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:510
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:506
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
void DupPolyPointsList(std::vector< VECTOR2I > &aBuffer) const
Duplicate the list of corners in a std::vector<VECTOR2I>.
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:515
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:507
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:516
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:495
void SetPolyPoints(const std::vector< VECTOR2I > &aPoints)
wxString getFriendlyName() const
VECTOR2I m_bezierC1
Definition eda_shape.h:512
FILL_T m_fill
Definition eda_shape.h:496
COLOR4D m_fillColor
Definition eda_shape.h:497
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:503
VECTOR2I m_bezierC2
Definition eda_shape.h:513
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) 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
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.
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.
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
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