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_segmentLength( 0 ),
62 m_editState( 0 ),
63 m_proxyItem( false )
64{
65}
66
67
71
72
73EDA_SHAPE::EDA_SHAPE( const SHAPE& aShape ) :
74 m_endsSwapped( false ),
76 m_fill(),
77 m_hatchingDirty( true ),
80 m_cornerRadius( 0 ),
81 m_segmentLength( 0 ),
82 m_editState( 0 ),
83 m_proxyItem( false )
84{
85 switch( aShape.Type() )
86 {
87 case SH_RECT:
88 {
89 auto rect = static_cast<const SHAPE_RECT&>( aShape );
90 m_shape = SHAPE_T::RECTANGLE;
91 SetStart( rect.GetPosition() );
92 SetEnd( rect.GetPosition() + rect.GetSize() );
93 break;
94 }
95
96 case SH_SEGMENT:
97 {
98 auto seg = static_cast<const SHAPE_SEGMENT&>( aShape );
99 m_shape = SHAPE_T::SEGMENT;
100 SetStart( seg.GetSeg().A );
101 SetEnd( seg.GetSeg().B );
102 SetWidth( seg.GetWidth() );
103 break;
104 }
105
106 case SH_LINE_CHAIN:
107 {
108 auto line = static_cast<const SHAPE_LINE_CHAIN&>( aShape );
109 m_shape = SHAPE_T::POLY;
110 m_poly = SHAPE_POLY_SET();
111 m_poly.AddOutline( line );
112 SetWidth( line.Width() );
113 break;
114 }
115
116 case SH_CIRCLE:
117 {
118 auto circle = static_cast<const SHAPE_CIRCLE&>( aShape );
119 m_shape = SHAPE_T::CIRCLE;
120 SetStart( circle.GetCenter() );
121 SetEnd( circle.GetCenter() + circle.GetRadius() );
122 break;
123 }
124
125 case SH_ARC:
126 {
127 auto arc = static_cast<const SHAPE_ARC&>( aShape );
128 m_shape = SHAPE_T::ARC;
129 SetArcGeometry( arc.GetP0(), arc.GetArcMid(), arc.GetP1() );
130 SetWidth( arc.GetWidth() );
131 break;
132 }
133
134 case SH_SIMPLE:
135 {
136 auto poly = static_cast<const SHAPE_SIMPLE&>( aShape );
137 m_shape = SHAPE_T::POLY;
138 poly.TransformToPolygon( m_poly, 0, ERROR_INSIDE );
139 break;
140 }
141
142 // currently unhandled
143 case SH_POLY_SET:
144 case SH_COMPOUND:
145 case SH_NULL:
147 default:
149 break;
150 }
151}
152
153
154void EDA_SHAPE::Serialize( google::protobuf::Any &aContainer ) const
155{
156 using namespace kiapi::common;
157 types::GraphicShape shape;
158
159 types::StrokeAttributes* stroke = shape.mutable_attributes()->mutable_stroke();
160 types::GraphicFillAttributes* fill = shape.mutable_attributes()->mutable_fill();
161
162 stroke->mutable_width()->set_value_nm( GetWidth() );
163
164 switch( GetLineStyle() )
165 {
166 case LINE_STYLE::DEFAULT: stroke->set_style( types::SLS_DEFAULT ); break;
167 case LINE_STYLE::SOLID: stroke->set_style( types::SLS_SOLID ); break;
168 case LINE_STYLE::DASH: stroke->set_style( types::SLS_DASH ); break;
169 case LINE_STYLE::DOT: stroke->set_style( types::SLS_DOT ); break;
170 case LINE_STYLE::DASHDOT: stroke->set_style( types::SLS_DASHDOT ); break;
171 case LINE_STYLE::DASHDOTDOT: stroke->set_style( types::SLS_DASHDOTDOT ); break;
172 default: break;
173 }
174
175 switch( GetFillMode() )
176 {
177 case FILL_T::FILLED_SHAPE: fill->set_fill_type( types::GFT_FILLED ); break;
178 default: fill->set_fill_type( types::GFT_UNFILLED ); break;
179 }
180
181 switch( GetShape() )
182 {
183 case SHAPE_T::SEGMENT:
184 {
185 types::GraphicSegmentAttributes* segment = shape.mutable_segment();
186 PackVector2( *segment->mutable_start(), GetStart() );
187 PackVector2( *segment->mutable_end(), GetEnd() );
188 break;
189 }
190
192 {
193 types::GraphicRectangleAttributes* rectangle = shape.mutable_rectangle();
194 PackVector2( *rectangle->mutable_top_left(), GetStart() );
195 PackVector2( *rectangle->mutable_bottom_right(), GetEnd() );
196 rectangle->mutable_corner_radius()->set_value_nm( GetCornerRadius() );
197 break;
198 }
199
200 case SHAPE_T::ARC:
201 {
202 types::GraphicArcAttributes* arc = shape.mutable_arc();
203 PackVector2( *arc->mutable_start(), GetStart() );
204 PackVector2( *arc->mutable_mid(), GetArcMid() );
205 PackVector2( *arc->mutable_end(), GetEnd() );
206 break;
207 }
208
209 case SHAPE_T::CIRCLE:
210 {
211 types::GraphicCircleAttributes* circle = shape.mutable_circle();
212 PackVector2( *circle->mutable_center(), GetStart() );
213 PackVector2( *circle->mutable_radius_point(), GetEnd() );
214 break;
215 }
216
217 case SHAPE_T::POLY:
218 {
219 PackPolySet( *shape.mutable_polygon(), GetPolyShape() );
220 break;
221 }
222
223 case SHAPE_T::BEZIER:
224 {
225 types::GraphicBezierAttributes* bezier = shape.mutable_bezier();
226 PackVector2( *bezier->mutable_start(), GetStart() );
227 PackVector2( *bezier->mutable_control1(), GetBezierC1() );
228 PackVector2( *bezier->mutable_control2(), GetBezierC2() );
229 PackVector2( *bezier->mutable_end(), GetEnd() );
230 break;
231 }
232
233 default:
234 wxASSERT_MSG( false, "Unhandled shape in PCB_SHAPE::Serialize" );
235 }
236
237 // TODO m_hasSolderMask and m_solderMaskMargin
238
239 aContainer.PackFrom( shape );
240}
241
242
243bool EDA_SHAPE::Deserialize( const google::protobuf::Any &aContainer )
244{
245 using namespace kiapi::common;
246
247 types::GraphicShape shape;
248
249 if( !aContainer.UnpackTo( &shape ) )
250 return false;
251
252 // Initialize everything to a known state that doesn't get touched by every
253 // codepath below, to make sure the equality operator is consistent
254 m_start = {};
255 m_end = {};
256 m_arcCenter = {};
257 m_arcMidData = {};
258 m_bezierC1 = {};
259 m_bezierC2 = {};
260 m_editState = 0;
261 m_proxyItem = false;
262 m_endsSwapped = false;
263
264 SetFilled( shape.attributes().fill().fill_type() == types::GFT_FILLED );
265 SetWidth( shape.attributes().stroke().width().value_nm() );
266
267 switch( shape.attributes().stroke().style() )
268 {
269 case types::SLS_DEFAULT: SetLineStyle( LINE_STYLE::DEFAULT ); break;
270 case types::SLS_SOLID: SetLineStyle( LINE_STYLE::SOLID ); break;
271 case types::SLS_DASH: SetLineStyle( LINE_STYLE::DASH ); break;
272 case types::SLS_DOT: SetLineStyle( LINE_STYLE::DOT ); break;
273 case types::SLS_DASHDOT: SetLineStyle( LINE_STYLE::DASHDOT ); break;
274 case types::SLS_DASHDOTDOT: SetLineStyle( LINE_STYLE::DASHDOTDOT ); break;
275 default: break;
276 }
277
278 if( shape.has_segment() )
279 {
281 SetStart( UnpackVector2( shape.segment().start() ) );
282 SetEnd( UnpackVector2( shape.segment().end() ) );
283 }
284 else if( shape.has_rectangle() )
285 {
287 SetStart( UnpackVector2( shape.rectangle().top_left() ) );
288 SetEnd( UnpackVector2( shape.rectangle().bottom_right() ) );
289 SetCornerRadius( shape.rectangle().corner_radius().value_nm() );
290 }
291 else if( shape.has_arc() )
292 {
294 SetArcGeometry( UnpackVector2( shape.arc().start() ),
295 UnpackVector2( shape.arc().mid() ),
296 UnpackVector2( shape.arc().end() ) );
297 }
298 else if( shape.has_circle() )
299 {
301 SetStart( UnpackVector2( shape.circle().center() ) );
302 SetEnd( UnpackVector2( shape.circle().radius_point() ) );
303 }
304 else if( shape.has_polygon() )
305 {
307 SetPolyShape( UnpackPolySet( shape.polygon() ) );
308 }
309 else if( shape.has_bezier() )
310 {
312 SetStart( UnpackVector2( shape.bezier().start() ) );
313 SetBezierC1( UnpackVector2( shape.bezier().control1() ) );
314 SetBezierC2( UnpackVector2( shape.bezier().control2() ) );
315 SetEnd( UnpackVector2( shape.bezier().end() ) );
317 }
318
319 return true;
320}
321
322
323wxString EDA_SHAPE::ShowShape() const
324{
325 if( IsProxyItem() )
326 {
327 switch( m_shape )
328 {
329 case SHAPE_T::SEGMENT: return _( "Thermal Spoke" );
330 case SHAPE_T::RECTANGLE: return _( "Number Box" );
331 default: return wxT( "??" );
332 }
333 }
334 else
335 {
336 switch( m_shape )
337 {
338 case SHAPE_T::SEGMENT: return _( "Line" );
339 case SHAPE_T::RECTANGLE: return _( "Rect" );
340 case SHAPE_T::ARC: return _( "Arc" );
341 case SHAPE_T::CIRCLE: return _( "Circle" );
342 case SHAPE_T::BEZIER: return _( "Bezier Curve" );
343 case SHAPE_T::POLY: return _( "Polygon" );
344 default: return wxT( "??" );
345 }
346 }
347}
348
349
351{
352 switch( m_shape )
353 {
354 case SHAPE_T::SEGMENT: return wxS( "S_SEGMENT" );
355 case SHAPE_T::RECTANGLE: return wxS( "S_RECT" );
356 case SHAPE_T::ARC: return wxS( "S_ARC" );
357 case SHAPE_T::CIRCLE: return wxS( "S_CIRCLE" );
358 case SHAPE_T::POLY: return wxS( "S_POLYGON" );
359 case SHAPE_T::BEZIER: return wxS( "S_CURVE" );
360 case SHAPE_T::UNDEFINED: return wxS( "UNDEFINED" );
361 }
362
363 return wxEmptyString; // Just to quiet GCC.
364}
365
366
368{
369 move( aPos - getPosition() );
370}
371
372
374{
375 if( m_shape == SHAPE_T::ARC )
376 return getCenter();
377 else if( m_shape == SHAPE_T::POLY )
378 return m_poly.CVertex( 0 );
379 else
380 return m_start;
381}
382
383
385{
386 double length = 0.0;
387
388 switch( m_shape )
389 {
390 case SHAPE_T::BEZIER:
391 for( size_t ii = 1; ii < m_bezierPoints.size(); ++ii )
392 length += m_bezierPoints[ ii - 1].Distance( m_bezierPoints[ii] );
393
394 return length;
395
396 case SHAPE_T::SEGMENT:
397 return GetStart().Distance( GetEnd() );
398
399 case SHAPE_T::POLY:
400 for( int ii = 0; ii < m_poly.COutline( 0 ).SegmentCount(); ii++ )
401 length += m_poly.COutline( 0 ).CSegment( ii ).Length();
402
403 return length;
404
405 case SHAPE_T::ARC:
406 return GetRadius() * GetArcAngle().AsRadians();
407
408 default:
410 return 0.0;
411 }
412}
413
414
416{
417 switch( m_shape )
418 {
420 return GetEndY() - GetStartY();
421
422 default:
424 return 0;
425 }
426}
427
428
430{
431 switch( m_shape )
432 {
434 return GetEndX() - GetStartX();
435
436 default:
438 return 0;
439 }
440}
441
442
444{
445 return m_cornerRadius;
446}
447
448
449void EDA_SHAPE::SetCornerRadius( int aRadius )
450{
452 {
453 int width = std::abs( GetRectangleWidth() );
454 int height = std::abs( GetRectangleHeight() );
455 int maxRadius = std::min( width, height ) / 2;
456
457 m_cornerRadius = std::clamp( aRadius, 0, maxRadius );
458 }
459 else
460 {
461 m_cornerRadius = aRadius;
462 }
463}
464
465
466void EDA_SHAPE::SetLength( const double& aLength )
467{
468 switch( m_shape )
469 {
470 case SHAPE_T::SEGMENT:
471 m_segmentLength = aLength;
472 break;
473
474 default:
476 }
477}
478
479
480void EDA_SHAPE::SetRectangleHeight( const int& aHeight )
481{
482 switch ( m_shape )
483 {
485 m_rectangleHeight = aHeight;
487 break;
488
489 default:
491 }
492}
493
494
495void EDA_SHAPE::SetRectangleWidth( const int& aWidth )
496{
497 switch ( m_shape )
498 {
500 m_rectangleWidth = aWidth;
502 break;
503
504 default:
506 }
507}
508
509
510void EDA_SHAPE::SetRectangle( const long long int& aHeight, const long long int& aWidth )
511{
512 switch ( m_shape )
513 {
515 m_rectangleHeight = aHeight;
516 m_rectangleWidth = aWidth;
517 break;
518
519 default:
521 }
522}
523
524
526{
527 switch( m_shape )
528 {
529 case SHAPE_T::SEGMENT:
530 m_segmentAngle = aAngle;
531 break;
532
533 default:
535 }
536}
537
538
540{
541 switch( m_shape )
542 {
543 case SHAPE_T::CIRCLE:
545 return true;
546
547 case SHAPE_T::ARC:
548 case SHAPE_T::SEGMENT:
549 return false;
550
551 case SHAPE_T::POLY:
552 if( m_poly.IsEmpty() )
553 return false;
554 else
555 return m_poly.Outline( 0 ).IsClosed();
556
557 case SHAPE_T::BEZIER:
558 if( m_bezierPoints.size() < 3 )
559 return false;
560 else
561 return m_bezierPoints[0] == m_bezierPoints[ m_bezierPoints.size() - 1 ];
562
563 default:
565 return false;
566 }
567}
568
569
571{
572 m_fill = aFill;
573 m_hatchingDirty = true;
574}
575
576
578{
579 switch( aFill )
580 {
585 default: SetFilled( true ); break;
586 }
587}
588
589
601
602
604{
605 if( !m_hatchingDirty )
606 return;
607
608 m_hatching.RemoveAllContours();
609
610 std::vector<double> slopes;
611 int lineWidth = GetHatchLineWidth();
612 int spacing = GetHatchLineSpacing();
613 SHAPE_POLY_SET shapeBuffer;
614
615 if( isMoving() )
616 return;
617 else if( GetFillMode() == FILL_T::CROSS_HATCH )
618 slopes = { 1.0, -1.0 };
619 else if( GetFillMode() == FILL_T::HATCH )
620 slopes = { -1.0 };
621 else if( GetFillMode() == FILL_T::REVERSE_HATCH )
622 slopes = { 1.0 };
623 else
624 return;
625
626 if( spacing == 0 )
627 return;
628
629 switch( m_shape )
630 {
631 case SHAPE_T::ARC:
632 case SHAPE_T::SEGMENT:
633 case SHAPE_T::BEZIER:
634 return;
635
637 {
639 GetCornerRadius() );
640 rr.TransformToPolygon( shapeBuffer );
641 }
642 break;
643
644 case SHAPE_T::CIRCLE:
646 break;
647
648 case SHAPE_T::POLY:
649 if( !IsClosed() )
650 return;
651
652 shapeBuffer = m_poly.CloneDropTriangulation();
653 break;
654
655 default:
657 return;
658 }
659
661 {
662 for( const SEG& seg : shapeBuffer.GenerateHatchLines( slopes, spacing, -1 ) )
663 {
664 // We don't really need the rounded ends at all, so don't spend any extra time on them
665 int maxError = lineWidth;
666
667 TransformOvalToPolygon( m_hatching, seg.A, seg.B, lineWidth, maxError, ERROR_INSIDE );
668 }
669
670 m_hatching.Fracture();
671 }
672 else
673 {
674 // Generate a grid of holes for a cross-hatch. This is about 3X the speed of the above
675 // algorithm, even when modified for the 45-degree fracture problem.
676
677 int gridsize = GetHatchLineSpacing();
678 int hole_size = gridsize - GetHatchLineWidth();
679
680 m_hatching = shapeBuffer.CloneDropTriangulation();
681 m_hatching.Rotate( -ANGLE_45 );
682
683 // Build hole shape
684 SHAPE_LINE_CHAIN hole_base;
685 VECTOR2I corner( 0, 0 );;
686 hole_base.Append( corner );
687 corner.x += hole_size;
688 hole_base.Append( corner );
689 corner.y += hole_size;
690 hole_base.Append( corner );
691 corner.x = 0;
692 hole_base.Append( corner );
693 hole_base.SetClosed( true );
694
695 // Build holes
696 BOX2I bbox = m_hatching.BBox( 0 );
697 SHAPE_POLY_SET holes;
698
699 int x_offset = bbox.GetX() - ( bbox.GetX() ) % gridsize - gridsize;
700 int y_offset = bbox.GetY() - ( bbox.GetY() ) % gridsize - gridsize;
701
702 for( int xx = x_offset; xx <= bbox.GetRight(); xx += gridsize )
703 {
704 for( int yy = y_offset; yy <= bbox.GetBottom(); yy += gridsize )
705 {
706 SHAPE_LINE_CHAIN hole( hole_base );
707 hole.Move( VECTOR2I( xx, yy ) );
708 holes.AddOutline( hole );
709 }
710 }
711
712 m_hatching.BooleanSubtract( holes );
713 m_hatching.Fracture();
714
715 // Must re-rotate after Fracture(). Clipper struggles mightily with fracturing
716 // 45-degree holes.
717 m_hatching.Rotate( ANGLE_45 );
718 }
719}
720
721
722void EDA_SHAPE::move( const VECTOR2I& aMoveVector )
723{
724 switch ( m_shape )
725 {
726 case SHAPE_T::ARC:
727 m_arcCenter += aMoveVector;
728 m_arcMidData.center += aMoveVector;
729 m_arcMidData.start += aMoveVector;
730 m_arcMidData.end += aMoveVector;
731 m_arcMidData.mid += aMoveVector;
733
734 case SHAPE_T::SEGMENT:
736 case SHAPE_T::CIRCLE:
737 m_start += aMoveVector;
738 m_end += aMoveVector;
739 break;
740
741 case SHAPE_T::POLY:
742 m_poly.Move( aMoveVector );
743 break;
744
745 case SHAPE_T::BEZIER:
746 m_start += aMoveVector;
747 m_end += aMoveVector;
748 m_bezierC1 += aMoveVector;
749 m_bezierC2 += aMoveVector;
750
751 for( VECTOR2I& pt : m_bezierPoints )
752 pt += aMoveVector;
753
754 break;
755
756 default:
758 break;
759 }
760
761 m_hatchingDirty = true;
762}
763
764
765void EDA_SHAPE::scale( double aScale )
766{
767 auto scalePt = [&]( VECTOR2I& pt )
768 {
769 pt.x = KiROUND( pt.x * aScale );
770 pt.y = KiROUND( pt.y * aScale );
771 };
772
773 switch( m_shape )
774 {
775 case SHAPE_T::ARC:
776 scalePt( m_arcCenter );
778
779 case SHAPE_T::SEGMENT:
781 case SHAPE_T::CIRCLE:
782 scalePt( m_start );
783 scalePt( m_end );
784 break;
785
786 case SHAPE_T::POLY: // polygon
787 {
788 std::vector<VECTOR2I> pts;
789
790 for( int ii = 0; ii < m_poly.OutlineCount(); ++ ii )
791 {
792 for( const VECTOR2I& pt : m_poly.Outline( ii ).CPoints() )
793 {
794 pts.emplace_back( pt );
795 scalePt( pts.back() );
796 }
797 }
798
799 SetPolyPoints( pts );
800 }
801 break;
802
803 case SHAPE_T::BEZIER:
804 scalePt( m_start );
805 scalePt( m_end );
806 scalePt( m_bezierC1 );
807 scalePt( m_bezierC2 );
809 break;
810
811 default:
813 break;
814 }
815
816 m_hatchingDirty = true;
817}
818
819
820void EDA_SHAPE::rotate( const VECTOR2I& aRotCentre, const EDA_ANGLE& aAngle )
821{
822 switch( m_shape )
823 {
824 case SHAPE_T::SEGMENT:
825 case SHAPE_T::CIRCLE:
826 RotatePoint( m_start, aRotCentre, aAngle );
827 RotatePoint( m_end, aRotCentre, aAngle );
828 break;
829
830 case SHAPE_T::ARC:
831 RotatePoint( m_start, aRotCentre, aAngle );
832 RotatePoint( m_end, aRotCentre, aAngle );
833 RotatePoint( m_arcCenter, aRotCentre, aAngle );
834 RotatePoint( m_arcMidData.start, aRotCentre, aAngle );
835 RotatePoint( m_arcMidData.end, aRotCentre, aAngle );
836 RotatePoint( m_arcMidData.mid, aRotCentre, aAngle );
837 RotatePoint( m_arcMidData.center, aRotCentre, aAngle );
838 break;
839
841 if( aAngle.IsCardinal() )
842 {
843 RotatePoint( m_start, aRotCentre, aAngle );
844 RotatePoint( m_end, aRotCentre, aAngle );
845 break;
846 }
847
848 // Convert non-cardinally-rotated rect to a diamond
850 m_poly.RemoveAllContours();
851 m_poly.NewOutline();
852 m_poly.Append( m_start );
853 m_poly.Append( m_end.x, m_start.y );
854 m_poly.Append( m_end );
855 m_poly.Append( m_start.x, m_end.y );
856
858
859 case SHAPE_T::POLY:
860 m_poly.Rotate( aAngle, aRotCentre );
861 break;
862
863 case SHAPE_T::BEZIER:
864 RotatePoint( m_start, aRotCentre, aAngle );
865 RotatePoint( m_end, aRotCentre, aAngle );
866 RotatePoint( m_bezierC1, aRotCentre, aAngle );
867 RotatePoint( m_bezierC2, aRotCentre, aAngle );
868
869 for( VECTOR2I& pt : m_bezierPoints )
870 RotatePoint( pt, aRotCentre, aAngle);
871
872 break;
873
874 default:
876 break;
877 }
878
879 m_hatchingDirty = true;
880}
881
882
883void EDA_SHAPE::flip( const VECTOR2I& aCentre, FLIP_DIRECTION aFlipDirection )
884{
885 switch ( m_shape )
886 {
887 case SHAPE_T::SEGMENT:
889 MIRROR( m_start, aCentre, aFlipDirection );
890 MIRROR( m_end, aCentre, aFlipDirection );
891 break;
892
893 case SHAPE_T::CIRCLE:
894 MIRROR( m_start, aCentre, aFlipDirection );
895 MIRROR( m_end, aCentre, aFlipDirection );
896 break;
897
898 case SHAPE_T::ARC:
899 MIRROR( m_start, aCentre, aFlipDirection );
900 MIRROR( m_end, aCentre, aFlipDirection );
901 MIRROR( m_arcCenter, aCentre, aFlipDirection );
902
903 std::swap( m_start, m_end );
904 break;
905
906 case SHAPE_T::POLY:
907 m_poly.Mirror( aCentre, aFlipDirection );
908 break;
909
910 case SHAPE_T::BEZIER:
911 MIRROR( m_start, aCentre, aFlipDirection );
912 MIRROR( m_end, aCentre, aFlipDirection );
913 MIRROR( m_bezierC1, aCentre, aFlipDirection );
914 MIRROR( m_bezierC2, aCentre, aFlipDirection );
915
917 break;
918
919 default:
921 break;
922 }
923
924 m_hatchingDirty = true;
925}
926
927
929{
930 // Has meaning only for SHAPE_T::BEZIER
931 if( m_shape != SHAPE_T::BEZIER )
932 {
933 m_bezierPoints.clear();
934 return;
935 }
936
937 // Rebuild the m_BezierPoints vertex list that approximate the Bezier curve
939}
940
941
942const std::vector<VECTOR2I> EDA_SHAPE::buildBezierToSegmentsPointsList( int aMaxError ) const
943{
944 std::vector<VECTOR2I> bezierPoints;
945
946 // Rebuild the m_BezierPoints vertex list that approximate the Bezier curve
947 std::vector<VECTOR2I> ctrlPoints = { m_start, m_bezierC1, m_bezierC2, m_end };
948 BEZIER_POLY converter( ctrlPoints );
949 converter.GetPoly( bezierPoints, aMaxError );
950
951 return bezierPoints;
952}
953
954
956{
957 switch( m_shape )
958 {
959 case SHAPE_T::ARC:
960 return m_arcCenter;
961
962 case SHAPE_T::CIRCLE:
963 return m_start;
964
965 case SHAPE_T::SEGMENT:
966 // Midpoint of the line
967 return ( m_start + m_end ) / 2;
968
969 case SHAPE_T::POLY:
971 case SHAPE_T::BEZIER:
972 return getBoundingBox().Centre();
973
974 default:
976 return VECTOR2I();
977 }
978}
979
980
981void EDA_SHAPE::SetCenter( const VECTOR2I& aCenter )
982{
983 switch( m_shape )
984 {
985 case SHAPE_T::ARC:
986 m_arcCenter = aCenter;
987 break;
988
989 case SHAPE_T::CIRCLE:
990 m_start = aCenter;
991 m_hatchingDirty = true;
992 break;
993
994 default:
996 }
997}
998
999
1001{
1002 // If none of the input data have changed since we loaded the arc, keep the original mid point data
1003 // to minimize churn
1004 if( m_arcMidData.start == m_start && m_arcMidData.end == m_end && m_arcMidData.center == m_arcCenter )
1005 return m_arcMidData.mid;
1006
1007 VECTOR2I mid = m_start;
1008 RotatePoint( mid, m_arcCenter, -GetArcAngle() / 2.0 );
1009 return mid;
1010}
1011
1012
1013void EDA_SHAPE::CalcArcAngles( EDA_ANGLE& aStartAngle, EDA_ANGLE& aEndAngle ) const
1014{
1015 VECTOR2D startRadial( GetStart() - getCenter() );
1016 VECTOR2D endRadial( GetEnd() - getCenter() );
1017
1018 aStartAngle = EDA_ANGLE( startRadial );
1019 aEndAngle = EDA_ANGLE( endRadial );
1020
1021 if( aEndAngle == aStartAngle )
1022 aEndAngle = aStartAngle + ANGLE_360; // ring, not null
1023
1024 while( aEndAngle < aStartAngle )
1025 aEndAngle += ANGLE_360;
1026}
1027
1028
1030{
1031 double radius = 0.0;
1032
1033 switch( m_shape )
1034 {
1035 case SHAPE_T::ARC:
1036 radius = m_arcCenter.Distance( m_start );
1037 break;
1038
1039 case SHAPE_T::CIRCLE:
1040 radius = m_start.Distance( m_end );
1041 break;
1042
1043 default:
1045 }
1046
1047 // don't allow degenerate circles/arcs
1048 if( radius > (double) INT_MAX / 2.0 )
1049 radius = (double) INT_MAX / 2.0;
1050
1051 return std::max( 1, KiROUND( radius ) );
1052}
1053
1054
1055void EDA_SHAPE::SetCachedArcData( const VECTOR2I& aStart, const VECTOR2I& aMid,
1056 const VECTOR2I& aEnd, const VECTOR2I& aCenter )
1057{
1058 m_arcMidData.start = aStart;
1059 m_arcMidData.end = aEnd;
1060 m_arcMidData.center = aCenter;
1061 m_arcMidData.mid = aMid;
1062}
1063
1064
1065void EDA_SHAPE::SetArcGeometry( const VECTOR2I& aStart, const VECTOR2I& aMid, const VECTOR2I& aEnd )
1066{
1067 m_arcMidData = {};
1068 m_start = aStart;
1069 m_end = aEnd;
1070 m_arcCenter = CalcArcCenter( aStart, aMid, aEnd );
1071 VECTOR2I new_mid = GetArcMid();
1072
1073 m_endsSwapped = false;
1074
1075 // Watch the ordering here. GetArcMid above needs to be called prior to initializing the
1076 // m_arcMidData structure in order to ensure we get the calculated variant, not the cached
1077 SetCachedArcData( aStart, aMid, aEnd, m_arcCenter );
1078
1079 /*
1080 * If the input winding doesn't match our internal winding, the calculated midpoint will end
1081 * up on the other side of the arc. In this case, we need to flip the start/end points and
1082 * flag this change for the system.
1083 */
1084 VECTOR2D dist( new_mid - aMid );
1085 VECTOR2D dist2( new_mid - m_arcCenter );
1086
1087 if( dist.SquaredEuclideanNorm() > dist2.SquaredEuclideanNorm() )
1088 {
1089 std::swap( m_start, m_end );
1090 m_endsSwapped = true;
1091 }
1092}
1093
1094
1096{
1097 EDA_ANGLE angle( atan2( static_cast<double>( GetStart().y - GetEnd().y ),
1098 static_cast<double>( GetEnd().x - GetStart().x ) ), RADIANS_T );
1099
1100 return angle;
1101}
1102
1103
1105{
1106 EDA_ANGLE startAngle;
1107 EDA_ANGLE endAngle;
1108
1109 CalcArcAngles( startAngle, endAngle );
1110
1111 return endAngle - startAngle;
1112}
1113
1114
1116{
1117 if( m_shape == SHAPE_T::ARC )
1118 {
1119 VECTOR2D mid = GetArcMid();
1120
1121 double orient = ( mid.x - m_start.x ) * ( m_end.y - m_start.y )
1122 - ( mid.y - m_start.y ) * ( m_end.x - m_start.x );
1123
1124 return orient < 0;
1125 }
1126
1128 return false;
1129}
1130
1131
1132void EDA_SHAPE::SetArcAngleAndEnd( const EDA_ANGLE& aAngle, bool aCheckNegativeAngle )
1133{
1134 EDA_ANGLE angle( aAngle );
1135
1136 m_end = m_start;
1138
1139 if( aCheckNegativeAngle && aAngle < ANGLE_0 )
1140 {
1141 std::swap( m_start, m_end );
1142 m_endsSwapped = true;
1143 }
1144}
1145
1146
1148{
1149 if( IsProxyItem() )
1150 {
1151 switch( m_shape )
1152 {
1153 case SHAPE_T::RECTANGLE: return _( "Pad Number Box" );
1154 case SHAPE_T::SEGMENT: return _( "Thermal Spoke Template" );
1155 default: return _( "Unrecognized" );
1156 }
1157 }
1158 else
1159 {
1160 switch( m_shape )
1161 {
1162 case SHAPE_T::CIRCLE: return _( "Circle" );
1163 case SHAPE_T::ARC: return _( "Arc" );
1164 case SHAPE_T::BEZIER: return _( "Curve" );
1165 case SHAPE_T::POLY: return _( "Polygon" );
1166 case SHAPE_T::RECTANGLE: return _( "Rectangle" );
1167 case SHAPE_T::SEGMENT: return _( "Segment" );
1168 default: return _( "Unrecognized" );
1169 }
1170 }
1171}
1172
1173
1174void EDA_SHAPE::ShapeGetMsgPanelInfo( EDA_DRAW_FRAME* aFrame, std::vector<MSG_PANEL_ITEM>& aList )
1175{
1176 wxString msg;
1177
1178 wxString shape = _( "Shape" );
1179 aList.emplace_back( shape, getFriendlyName() );
1180
1181 switch( m_shape )
1182 {
1183 case SHAPE_T::CIRCLE:
1184 aList.emplace_back( _( "Radius" ), aFrame->MessageTextFromValue( GetRadius() ) );
1185 break;
1186
1187 case SHAPE_T::ARC:
1188 aList.emplace_back( _( "Length" ), aFrame->MessageTextFromValue( GetLength() ) );
1189
1191 aList.emplace_back( _( "Angle" ), msg );
1192
1193 aList.emplace_back( _( "Radius" ), aFrame->MessageTextFromValue( GetRadius() ) );
1194 break;
1195
1196 case SHAPE_T::BEZIER:
1197 aList.emplace_back( _( "Length" ), aFrame->MessageTextFromValue( GetLength() ) );
1198 break;
1199
1200 case SHAPE_T::POLY:
1201 msg.Printf( wxS( "%d" ), GetPolyShape().Outline(0).PointCount() );
1202 aList.emplace_back( _( "Points" ), msg );
1203 break;
1204
1205 case SHAPE_T::RECTANGLE:
1206 aList.emplace_back( _( "Width" ), aFrame->MessageTextFromValue( std::abs( GetEnd().x - GetStart().x ) ) );
1207 aList.emplace_back( _( "Height" ), aFrame->MessageTextFromValue( std::abs( GetEnd().y - GetStart().y ) ) );
1208 break;
1209
1210 case SHAPE_T::SEGMENT:
1211 {
1212 aList.emplace_back( _( "Length" ), aFrame->MessageTextFromValue( GetStart().Distance( GetEnd() ) ));
1213
1214 // angle counter-clockwise from 3'o-clock
1215 EDA_ANGLE angle( atan2( (double)( GetStart().y - GetEnd().y ), (double)( GetEnd().x - GetStart().x ) ),
1216 RADIANS_T );
1217 aList.emplace_back( _( "Angle" ), EDA_UNIT_UTILS::UI::MessageTextFromValue( angle ) );
1218 break;
1219 }
1220
1221 default:
1222 break;
1223 }
1224
1225 m_stroke.GetMsgPanelInfo( aFrame, aList );
1226}
1227
1228
1230{
1231 BOX2I bbox;
1232
1233 switch( m_shape )
1234 {
1235 case SHAPE_T::RECTANGLE:
1236 for( VECTOR2I& pt : GetRectCorners() )
1237 bbox.Merge( pt );
1238
1239 break;
1240
1241 case SHAPE_T::SEGMENT:
1242 bbox.SetOrigin( GetStart() );
1243 bbox.SetEnd( GetEnd() );
1244 break;
1245
1246 case SHAPE_T::CIRCLE:
1247 bbox.SetOrigin( GetStart() );
1248 bbox.Inflate( GetRadius() );
1249 break;
1250
1251 case SHAPE_T::ARC:
1252 computeArcBBox( bbox );
1253 break;
1254
1255 case SHAPE_T::POLY:
1256 if( m_poly.IsEmpty() )
1257 break;
1258
1259 for( auto iter = m_poly.CIterate(); iter; iter++ )
1260 bbox.Merge( *iter );
1261
1262 break;
1263
1264 case SHAPE_T::BEZIER:
1265 // Bezier BBoxes are not trivial to compute, so we approximate it by
1266 // using the bounding box of the curve (not control!) points.
1267 for( const VECTOR2I& pt : m_bezierPoints )
1268 bbox.Merge( pt );
1269
1270 break;
1271
1272 default:
1274 break;
1275 }
1276
1277 bbox.Inflate( std::max( 0, GetWidth() ) / 2 );
1278 bbox.Normalize();
1279
1280 return bbox;
1281}
1282
1283
1284bool EDA_SHAPE::hitTest( const VECTOR2I& aPosition, int aAccuracy ) const
1285{
1286 double maxdist = aAccuracy;
1287
1288 if( GetWidth() > 0 )
1289 maxdist += GetWidth() / 2.0;
1290
1291 switch( m_shape )
1292 {
1293 case SHAPE_T::CIRCLE:
1294 {
1295 double radius = GetRadius();
1296 double dist = aPosition.Distance( getCenter() );
1297
1298 if( IsFilledForHitTesting() )
1299 return dist <= radius + maxdist; // Filled circle hit-test
1300 else if( abs( radius - dist ) <= maxdist ) // Ring hit-test
1301 return true;
1302
1303 if( IsHatchedFill() && GetHatching().Collide( aPosition, maxdist ) )
1304 return true;
1305
1306 return false;
1307 }
1308
1309 case SHAPE_T::ARC:
1310 {
1311 if( aPosition.Distance( m_start ) <= maxdist )
1312 return true;
1313
1314 if( aPosition.Distance( m_end ) <= maxdist )
1315 return true;
1316
1317 double radius = GetRadius();
1318 VECTOR2D relPos( VECTOR2D( aPosition ) - getCenter() );
1319 double dist = relPos.EuclideanNorm();
1320
1321 if( IsFilledForHitTesting() )
1322 {
1323 // Check distance from arc center
1324 if( dist > radius + maxdist )
1325 return false;
1326 }
1327 else
1328 {
1329 // Check distance from arc circumference
1330 if( abs( radius - dist ) > maxdist )
1331 return false;
1332 }
1333
1334 // Finally, check to see if it's within arc's swept angle.
1335 EDA_ANGLE startAngle;
1336 EDA_ANGLE endAngle;
1337 CalcArcAngles( startAngle, endAngle );
1338
1339 EDA_ANGLE relPosAngle( relPos );
1340
1341 startAngle.Normalize();
1342 endAngle.Normalize();
1343 relPosAngle.Normalize();
1344
1345 if( endAngle > startAngle )
1346 return relPosAngle >= startAngle && relPosAngle <= endAngle;
1347 else
1348 return relPosAngle >= startAngle || relPosAngle <= endAngle;
1349 }
1350
1351 case SHAPE_T::BEZIER:
1352 {
1353 const std::vector<VECTOR2I>* pts = &m_bezierPoints;
1354 std::vector<VECTOR2I> updatedBezierPoints;
1355
1356 if( m_bezierPoints.empty() )
1357 {
1359 converter.GetPoly( updatedBezierPoints, aAccuracy / 2 );
1360 pts = &updatedBezierPoints;
1361 }
1362
1363 for( unsigned int i = 1; i < pts->size(); i++ )
1364 {
1365 if( TestSegmentHit( aPosition, ( *pts )[i - 1], ( *pts )[i], maxdist ) )
1366 return true;
1367 }
1368
1369 return false;
1370 }
1371 case SHAPE_T::SEGMENT:
1372 return TestSegmentHit( aPosition, GetStart(), GetEnd(), maxdist );
1373
1374 case SHAPE_T::RECTANGLE:
1375 if( IsProxyItem() || IsFilledForHitTesting() ) // Filled rect hit-test
1376 {
1377 SHAPE_POLY_SET poly;
1378 poly.NewOutline();
1379
1380 for( const VECTOR2I& pt : GetRectCorners() )
1381 poly.Append( pt );
1382
1383 return poly.Collide( aPosition, maxdist );
1384 }
1385 else if( m_cornerRadius > 0 )
1386 {
1388 SHAPE_POLY_SET poly;
1389 rr.TransformToPolygon( poly );
1390
1391 if( poly.CollideEdge( aPosition, nullptr, maxdist ) )
1392 return true;
1393 }
1394 else
1395 {
1396 std::vector<VECTOR2I> pts = GetRectCorners();
1397
1398 if( TestSegmentHit( aPosition, pts[0], pts[1], maxdist )
1399 || TestSegmentHit( aPosition, pts[1], pts[2], maxdist )
1400 || TestSegmentHit( aPosition, pts[2], pts[3], maxdist )
1401 || TestSegmentHit( aPosition, pts[3], pts[0], maxdist ) )
1402 {
1403 return true;
1404 }
1405 }
1406
1407 if( IsHatchedFill() && GetHatching().Collide( aPosition, maxdist ) )
1408 return true;
1409
1410 return false;
1411
1412 case SHAPE_T::POLY:
1413 if( IsFilledForHitTesting() )
1414 {
1415 if( !m_poly.COutline( 0 ).IsClosed() )
1416 {
1417 // Only one outline is expected
1418 SHAPE_LINE_CHAIN copy( m_poly.COutline( 0 ) );
1419 copy.SetClosed( true );
1420 return copy.Collide( aPosition, maxdist );
1421 }
1422 else
1423 {
1424 return m_poly.Collide( aPosition, maxdist );
1425 }
1426 }
1427 else
1428 {
1429 if( m_poly.CollideEdge( aPosition, nullptr, maxdist ) )
1430 return true;
1431
1432 if( IsHatchedFill() && GetHatching().Collide( aPosition, maxdist ) )
1433 return true;
1434
1435 return false;
1436 }
1437
1438 default:
1440 return false;
1441 }
1442}
1443
1444
1445bool EDA_SHAPE::hitTest( const BOX2I& aRect, bool aContained, int aAccuracy ) const
1446{
1447 BOX2I arect = aRect;
1448 arect.Normalize();
1449 arect.Inflate( aAccuracy );
1450
1451 BOX2I bbox = getBoundingBox();
1452
1453 auto checkOutline =
1454 [&]( const SHAPE_LINE_CHAIN& outline )
1455 {
1456 int count = (int) outline.GetPointCount();
1457
1458 for( int ii = 0; ii < count; ii++ )
1459 {
1460 VECTOR2I vertex = outline.GetPoint( ii );
1461
1462 // Test if the point is within aRect
1463 if( arect.Contains( vertex ) )
1464 return true;
1465
1466 if( ii + 1 < count )
1467 {
1468 VECTOR2I vertexNext = outline.GetPoint( ii + 1 );
1469
1470 // Test if this edge intersects aRect
1471 if( arect.Intersects( vertex, vertexNext ) )
1472 return true;
1473 }
1474 else if( outline.IsClosed() )
1475 {
1476 VECTOR2I vertexNext = outline.GetPoint( 0 );
1477
1478 // Test if this edge intersects aRect
1479 if( arect.Intersects( vertex, vertexNext ) )
1480 return true;
1481 }
1482 }
1483
1484 return false;
1485 };
1486
1487 switch( m_shape )
1488 {
1489 case SHAPE_T::CIRCLE:
1490 // Test if area intersects or contains the circle:
1491 if( aContained )
1492 {
1493 return arect.Contains( bbox );
1494 }
1495 else
1496 {
1497 // If the rectangle does not intersect the bounding box, this is a much quicker test
1498 if( !arect.Intersects( bbox ) )
1499 return false;
1500 else
1501 return arect.IntersectsCircleEdge( getCenter(), GetRadius(), GetWidth() );
1502 }
1503
1504 case SHAPE_T::ARC:
1505 // Test for full containment of this arc in the rect
1506 if( aContained )
1507 {
1508 return arect.Contains( bbox );
1509 }
1510 // Test if the rect crosses the arc
1511 else
1512 {
1513 if( !arect.Intersects( bbox ) )
1514 return false;
1515
1516 if( IsAnyFill() )
1517 {
1518 return ( arect.Intersects( getCenter(), GetStart() )
1519 || arect.Intersects( getCenter(), GetEnd() )
1520 || arect.IntersectsCircleEdge( getCenter(), GetRadius(), GetWidth() ) );
1521 }
1522 else
1523 {
1524 return arect.IntersectsCircleEdge( getCenter(), GetRadius(), GetWidth() );
1525 }
1526 }
1527
1528 case SHAPE_T::RECTANGLE:
1529 if( aContained )
1530 {
1531 return arect.Contains( bbox );
1532 }
1533 else if( m_cornerRadius > 0 )
1534 {
1536 SHAPE_POLY_SET poly;
1537 rr.TransformToPolygon( poly );
1538
1539 // Account for the width of the line
1540 arect.Inflate( GetWidth() / 2 );
1541
1542 return checkOutline( poly.Outline( 0 ) );
1543 }
1544 else
1545 {
1546 std::vector<VECTOR2I> pts = GetRectCorners();
1547
1548 // Account for the width of the lines
1549 arect.Inflate( GetWidth() / 2 );
1550 return ( arect.Intersects( pts[0], pts[1] )
1551 || arect.Intersects( pts[1], pts[2] )
1552 || arect.Intersects( pts[2], pts[3] )
1553 || arect.Intersects( pts[3], pts[0] ) );
1554 }
1555
1556 case SHAPE_T::SEGMENT:
1557 if( aContained )
1558 {
1559 return arect.Contains( GetStart() ) && aRect.Contains( GetEnd() );
1560 }
1561 else
1562 {
1563 // Account for the width of the line
1564 arect.Inflate( GetWidth() / 2 );
1565 return arect.Intersects( GetStart(), GetEnd() );
1566 }
1567
1568 case SHAPE_T::POLY:
1569 if( aContained )
1570 {
1571 return arect.Contains( bbox );
1572 }
1573 else
1574 {
1575 // Fast test: if aRect is outside the polygon bounding box,
1576 // rectangles cannot intersect
1577 if( !arect.Intersects( bbox ) )
1578 return false;
1579
1580 // Account for the width of the line
1581 arect.Inflate( GetWidth() / 2 );
1582
1583 for( int ii = 0; ii < m_poly.OutlineCount(); ++ii )
1584 {
1585 if( checkOutline( m_poly.Outline( ii ) ) )
1586 return true;
1587 }
1588
1589 return false;
1590 }
1591
1592 case SHAPE_T::BEZIER:
1593 if( aContained )
1594 {
1595 return arect.Contains( bbox );
1596 }
1597 else
1598 {
1599 // Fast test: if aRect is outside the polygon bounding box,
1600 // rectangles cannot intersect
1601 if( !arect.Intersects( bbox ) )
1602 return false;
1603
1604 // Account for the width of the line
1605 arect.Inflate( GetWidth() / 2 );
1606 const std::vector<VECTOR2I>* pts = &m_bezierPoints;
1607 std::vector<VECTOR2I> updatedBezierPoints;
1608
1609 if( m_bezierPoints.empty() )
1610 {
1612 converter.GetPoly( updatedBezierPoints, aAccuracy / 2 );
1613 pts = &updatedBezierPoints;
1614 }
1615
1616 for( unsigned ii = 1; ii < pts->size(); ii++ )
1617 {
1618 VECTOR2I vertex = ( *pts )[ii - 1];
1619 VECTOR2I vertexNext = ( *pts )[ii];
1620
1621 // Test if the point is within aRect
1622 if( arect.Contains( vertex ) )
1623 return true;
1624
1625 // Test if this edge intersects aRect
1626 if( arect.Intersects( vertex, vertexNext ) )
1627 return true;
1628 }
1629
1630 return false;
1631 }
1632
1633 default:
1635 return false;
1636 }
1637}
1638
1639
1640bool EDA_SHAPE::hitTest( const SHAPE_LINE_CHAIN& aPoly, bool aContained ) const
1641{
1643
1644 return KIGEOM::ShapeHitTest( aPoly, shape, aContained );
1645}
1646
1647
1648std::vector<VECTOR2I> EDA_SHAPE::GetRectCorners() const
1649{
1650 std::vector<VECTOR2I> pts;
1651 VECTOR2I topLeft = GetStart();
1652 VECTOR2I botRight = GetEnd();
1653
1654 pts.emplace_back( topLeft );
1655 pts.emplace_back( botRight.x, topLeft.y );
1656 pts.emplace_back( botRight );
1657 pts.emplace_back( topLeft.x, botRight.y );
1658
1659 return pts;
1660}
1661
1662
1663std::vector<VECTOR2I> EDA_SHAPE::GetCornersInSequence( EDA_ANGLE angle ) const
1664{
1665 std::vector<VECTOR2I> pts;
1666
1667 angle.Normalize();
1668
1669 BOX2I bbox = getBoundingBox();
1670 bbox.Normalize();
1671
1672 if( angle.IsCardinal() )
1673 {
1674 if( angle == ANGLE_0 )
1675 {
1676 pts.emplace_back( VECTOR2I( bbox.GetLeft(), bbox.GetTop() ) );
1677 pts.emplace_back( VECTOR2I( bbox.GetRight(), bbox.GetTop() ) );
1678 pts.emplace_back( VECTOR2I( bbox.GetRight(), bbox.GetBottom() ) );
1679 pts.emplace_back( VECTOR2I( bbox.GetLeft(), bbox.GetBottom() ) );
1680 }
1681 else if( angle == ANGLE_90 )
1682 {
1683 pts.emplace_back( VECTOR2I( bbox.GetLeft(), bbox.GetBottom() ) );
1684 pts.emplace_back( VECTOR2I( bbox.GetLeft(), bbox.GetTop() ) );
1685 pts.emplace_back( VECTOR2I( bbox.GetRight(), bbox.GetTop() ) );
1686 pts.emplace_back( VECTOR2I( bbox.GetRight(), bbox.GetBottom() ) );
1687 }
1688 else if( angle == ANGLE_180 )
1689 {
1690 pts.emplace_back( VECTOR2I( bbox.GetRight(), bbox.GetBottom() ) );
1691 pts.emplace_back( VECTOR2I( bbox.GetLeft(), bbox.GetBottom() ) );
1692 pts.emplace_back( VECTOR2I( bbox.GetLeft(), bbox.GetTop() ) );
1693 pts.emplace_back( VECTOR2I( bbox.GetRight(), bbox.GetTop() ) );
1694 }
1695 else if( angle == ANGLE_270 )
1696 {
1697 pts.emplace_back( VECTOR2I( bbox.GetRight(), bbox.GetTop() ) );
1698 pts.emplace_back( VECTOR2I( bbox.GetRight(), bbox.GetBottom() ) );
1699 pts.emplace_back( VECTOR2I( bbox.GetLeft(), bbox.GetBottom() ) );
1700 pts.emplace_back( VECTOR2I( bbox.GetLeft(), bbox.GetTop() ) );
1701 }
1702 }
1703 else
1704 {
1705 // This function was originally located in pcb_textbox.cpp and was later moved to eda_shape.cpp.
1706 // As a result of this move, access to getCorners was lost, since it is defined in the PCB_SHAPE
1707 // class within pcb_shape.cpp and is not available in the current context.
1708 //
1709 // Additionally, GetRectCorners() cannot be used here, as it assumes the rectangle is rotated by
1710 // a cardinal angle. In non-cardinal cases, it returns incorrect values (e.g., (0, 0)).
1711 //
1712 // To address this, a portion of the getCorners implementation for SHAPE_T::POLY elements
1713 // has been replicated here to restore the correct behavior.
1714 std::vector<VECTOR2I> corners;
1715
1716 for( int ii = 0; ii < GetPolyShape().OutlineCount(); ++ii )
1717 {
1718 for( const VECTOR2I& pt : GetPolyShape().Outline( ii ).CPoints() )
1719 corners.emplace_back( pt );
1720 }
1721
1722 while( corners.size() < 4 )
1723 corners.emplace_back( corners.back() + VECTOR2I( 10, 10 ) );
1724
1725 VECTOR2I minX = corners[0];
1726 VECTOR2I maxX = corners[0];
1727 VECTOR2I minY = corners[0];
1728 VECTOR2I maxY = corners[0];
1729
1730 for( const VECTOR2I& corner : corners )
1731 {
1732 if( corner.x < minX.x )
1733 minX = corner;
1734
1735 if( corner.x > maxX.x )
1736 maxX = corner;
1737
1738 if( corner.y < minY.y )
1739 minY = corner;
1740
1741 if( corner.y > maxY.y )
1742 maxY = corner;
1743 }
1744
1745 if( angle < ANGLE_90 )
1746 {
1747 pts.emplace_back( minX );
1748 pts.emplace_back( minY );
1749 pts.emplace_back( maxX );
1750 pts.emplace_back( maxY );
1751 }
1752 else if( angle < ANGLE_180 )
1753 {
1754 pts.emplace_back( maxY );
1755 pts.emplace_back( minX );
1756 pts.emplace_back( minY );
1757 pts.emplace_back( maxX );
1758 }
1759 else if( angle < ANGLE_270 )
1760 {
1761 pts.emplace_back( maxX );
1762 pts.emplace_back( maxY );
1763 pts.emplace_back( minX );
1764 pts.emplace_back( minY );
1765 }
1766 else
1767 {
1768 pts.emplace_back( minY );
1769 pts.emplace_back( maxX );
1770 pts.emplace_back( maxY );
1771 pts.emplace_back( minX );
1772 }
1773 }
1774
1775 return pts;
1776}
1777
1778
1780{
1781 // Start, end, and each inflection point the arc crosses will enclose the entire arc.
1782 // Only include the center when filled; it's not necessarily inside the BB of an unfilled
1783 // arc with a small included angle.
1784 aBBox.SetOrigin( m_start );
1785 aBBox.Merge( m_end );
1786
1787 if( IsAnyFill() )
1788 aBBox.Merge( m_arcCenter );
1789
1790 int radius = GetRadius();
1791 EDA_ANGLE t1, t2;
1792
1793 CalcArcAngles( t1, t2 );
1794
1795 t1.Normalize();
1796 t2.Normalize();
1797
1798 if( t2 > t1 )
1799 {
1800 if( t1 < ANGLE_0 && t2 > ANGLE_0 )
1801 aBBox.Merge( VECTOR2I( m_arcCenter.x + radius, m_arcCenter.y ) ); // right
1802
1803 if( t1 < ANGLE_90 && t2 > ANGLE_90 )
1804 aBBox.Merge( VECTOR2I( m_arcCenter.x, m_arcCenter.y + radius ) ); // down
1805
1806 if( t1 < ANGLE_180 && t2 > ANGLE_180 )
1807 aBBox.Merge( VECTOR2I( m_arcCenter.x - radius, m_arcCenter.y ) ); // left
1808
1809 if( t1 < ANGLE_270 && t2 > ANGLE_270 )
1810 aBBox.Merge( VECTOR2I( m_arcCenter.x, m_arcCenter.y - radius ) ); // up
1811 }
1812 else
1813 {
1814 if( t1 < ANGLE_0 || t2 > ANGLE_0 )
1815 aBBox.Merge( VECTOR2I( m_arcCenter.x + radius, m_arcCenter.y ) ); // right
1816
1817 if( t1 < ANGLE_90 || t2 > ANGLE_90 )
1818 aBBox.Merge( VECTOR2I( m_arcCenter.x, m_arcCenter.y + radius ) ); // down
1819
1820 if( t1 < ANGLE_180 || t2 > ANGLE_180 )
1821 aBBox.Merge( VECTOR2I( m_arcCenter.x - radius, m_arcCenter.y ) ); // left
1822
1823 if( t1 < ANGLE_270 || t2 > ANGLE_270 )
1824 aBBox.Merge( VECTOR2I( m_arcCenter.x, m_arcCenter.y - radius ) ); // up
1825 }
1826}
1827
1828
1829void EDA_SHAPE::SetPolyPoints( const std::vector<VECTOR2I>& aPoints )
1830{
1831 m_poly.RemoveAllContours();
1832 m_poly.NewOutline();
1833
1834 for( const VECTOR2I& p : aPoints )
1835 m_poly.Append( p.x, p.y );
1836}
1837
1838
1839std::vector<SHAPE*> EDA_SHAPE::makeEffectiveShapes( bool aEdgeOnly, bool aLineChainOnly ) const
1840{
1841 std::vector<SHAPE*> effectiveShapes;
1842 int width = GetEffectiveWidth();
1843 bool solidFill = ( IsSolidFill() || IsHatchedFill() || IsProxyItem() ) && !aEdgeOnly;
1844
1845 switch( m_shape )
1846 {
1847 case SHAPE_T::ARC:
1848 effectiveShapes.emplace_back( new SHAPE_ARC( m_arcCenter, m_start, GetArcAngle(), width ) );
1849 break;
1850
1851 case SHAPE_T::SEGMENT:
1852 effectiveShapes.emplace_back( new SHAPE_SEGMENT( m_start, m_end, width ) );
1853 break;
1854
1855 case SHAPE_T::RECTANGLE:
1856 {
1857 if( m_cornerRadius > 0 )
1858 {
1860 SHAPE_POLY_SET poly;
1861 rr.TransformToPolygon( poly );
1862 SHAPE_LINE_CHAIN outline = poly.Outline( 0 );
1863
1864 if( solidFill )
1865 effectiveShapes.emplace_back( new SHAPE_SIMPLE( outline ) );
1866
1867 if( width > 0 || !solidFill )
1868 {
1869 for( int i = 0; i < outline.PointCount() - 1; ++i )
1870 {
1871 effectiveShapes.emplace_back( new SHAPE_SEGMENT( outline.CPoint( i ), outline.CPoint( i + 1 ),
1872 width ) );
1873 }
1874
1875 effectiveShapes.emplace_back( new SHAPE_SEGMENT( outline.CPoint( outline.PointCount() - 1 ),
1876 outline.CPoint( 0 ), width ) );
1877 }
1878 }
1879 else
1880 {
1881 std::vector<VECTOR2I> pts = GetRectCorners();
1882
1883 if( solidFill )
1884 effectiveShapes.emplace_back( new SHAPE_SIMPLE( pts ) );
1885
1886 if( width > 0 || !solidFill )
1887 {
1888 effectiveShapes.emplace_back( new SHAPE_SEGMENT( pts[0], pts[1], width ) );
1889 effectiveShapes.emplace_back( new SHAPE_SEGMENT( pts[1], pts[2], width ) );
1890 effectiveShapes.emplace_back( new SHAPE_SEGMENT( pts[2], pts[3], width ) );
1891 effectiveShapes.emplace_back( new SHAPE_SEGMENT( pts[3], pts[0], width ) );
1892 }
1893 }
1894 break;
1895 }
1896
1897 case SHAPE_T::CIRCLE:
1898 {
1899 if( solidFill )
1900 effectiveShapes.emplace_back( new SHAPE_CIRCLE( getCenter(), GetRadius() ) );
1901
1902 if( width > 0 || !solidFill )
1903 effectiveShapes.emplace_back( new SHAPE_ARC( getCenter(), GetEnd(), ANGLE_360, width ) );
1904
1905 break;
1906 }
1907
1908 case SHAPE_T::BEZIER:
1909 {
1910 std::vector<VECTOR2I> bezierPoints = buildBezierToSegmentsPointsList( getMaxError() );
1911 VECTOR2I start_pt = bezierPoints[0];
1912
1913 for( unsigned int jj = 1; jj < bezierPoints.size(); jj++ )
1914 {
1915 VECTOR2I end_pt = bezierPoints[jj];
1916 effectiveShapes.emplace_back( new SHAPE_SEGMENT( start_pt, end_pt, width ) );
1917 start_pt = end_pt;
1918 }
1919
1920 break;
1921 }
1922
1923 case SHAPE_T::POLY:
1924 {
1925 if( GetPolyShape().OutlineCount() == 0 ) // malformed/empty polygon
1926 break;
1927
1928 for( int ii = 0; ii < GetPolyShape().OutlineCount(); ++ii )
1929 {
1930 const SHAPE_LINE_CHAIN& l = GetPolyShape().COutline( ii );
1931
1932 if( solidFill )
1933 effectiveShapes.emplace_back( new SHAPE_SIMPLE( l ) );
1934
1935 if( width > 0 || !IsSolidFill() || aEdgeOnly )
1936 {
1937 int segCount = l.SegmentCount();
1938
1939 if( aLineChainOnly && l.IsClosed() )
1940 segCount--; // Treat closed chain as open
1941
1942 for( int jj = 0; jj < segCount; jj++ )
1943 effectiveShapes.emplace_back( new SHAPE_SEGMENT( l.CSegment( jj ), width ) );
1944 }
1945 }
1946 }
1947 break;
1948
1949 default:
1951 break;
1952 }
1953
1954 return effectiveShapes;
1955}
1956
1957
1958void EDA_SHAPE::DupPolyPointsList( std::vector<VECTOR2I>& aBuffer ) const
1959{
1960 for( int ii = 0; ii < m_poly.OutlineCount(); ++ii )
1961 {
1962 int pointCount = m_poly.COutline( ii ).PointCount();
1963
1964 if( pointCount )
1965 {
1966 aBuffer.reserve( pointCount );
1967
1968 for ( auto iter = m_poly.CIterate(); iter; iter++ )
1969 aBuffer.emplace_back( iter->x, iter->y );
1970 }
1971 }
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 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:492
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:520
int m_cornerRadius
Definition eda_shape.h:502
bool m_hatchingDirty
Definition eda_shape.h:498
bool m_endsSwapped
Definition eda_shape.h:491
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)
void SetLength(const double &aLength)
int m_editState
Definition eda_shape.h:519
virtual int getMaxError() const
Definition eda_shape.h:488
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:68
void SetCornerRadius(int aRadius)
long long int m_rectangleHeight
Definition eda_shape.h:500
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
SHAPE_POLY_SET m_hatching
Definition eda_shape.h:497
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: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: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:510
double m_segmentLength
Definition eda_shape.h:504
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:511
void SetFillColor(const COLOR4D &aColor)
Definition eda_shape.h:153
int GetEndY() const
Definition eda_shape.h:216
std::vector< SHAPE * > makeEffectiveShapes(bool aEdgeOnly, bool aLineChainOnly=false) const
Make a set of SHAPE objects representing the EDA_SHAPE.
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:507
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
void SetSegmentAngle(const EDA_ANGLE &aAngle)
std::vector< VECTOR2I > m_bezierPoints
Definition eda_shape.h:516
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:508
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:517
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:493
EDA_ANGLE m_segmentAngle
Definition eda_shape.h:505
void SetPolyPoints(const std::vector< VECTOR2I > &aPoints)
wxString getFriendlyName() const
VECTOR2I m_bezierC1
Definition eda_shape.h:513
FILL_T m_fill
Definition eda_shape.h:494
COLOR4D m_fillColor
Definition eda_shape.h:495
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:501
VECTOR2I m_bezierC2
Definition eda_shape.h:514
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:705
static ENUM_MAP< T > & Instance()
Definition property.h:699
wxPGChoices & Choices()
Definition property.h:748
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:259
PROPERTY_BASE & SetValidator(PROPERTY_VALIDATOR_FN &&aValidator)
Definition property.h:346
PROPERTY_BASE & SetIsHiddenFromRulesEditor(bool aHide=true)
Definition property.h:323
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:85
KICOMMON_API SHAPE_POLY_SET UnpackPolySet(const types::PolySet &aInput)
KICOMMON_API void PackVector2(types::Vector2 &aOutput, const VECTOR2I &aInput)
Definition api_utils.cpp:78
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:799
#define NO_SETTER(owner, type)
Definition property.h:806
@ PT_COORD
Coordinate expressed in distance units (mm/inch)
Definition property.h:64
@ PT_DECIDEGREE
Angle expressed in decidegrees.
Definition property.h:66
@ PT_SIZE
Size expressed in distance units (mm/inch)
Definition property.h:62
#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