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 EDA_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 std::vector<double> slopes;
579 int lineWidth = GetHatchLineWidth();
580 int spacing = GetHatchLineSpacing();
581 SHAPE_POLY_SET shapeBuffer;
582
583 // Validate state before clearing cached hatching. If we can't regenerate, keep existing cache.
584 if( isMoving() )
585 return;
586
588 slopes = { 1.0, -1.0 };
589 else if( GetFillMode() == FILL_T::HATCH )
590 slopes = { -1.0 };
591 else if( GetFillMode() == FILL_T::REVERSE_HATCH )
592 slopes = { 1.0 };
593 else
594 return;
595
596 if( spacing == 0 )
597 return;
598
599 switch( m_shape )
600 {
601 case SHAPE_T::ARC:
602 case SHAPE_T::SEGMENT:
603 case SHAPE_T::BEZIER:
604 return;
605
607 {
609 GetCornerRadius() );
610 rr.TransformToPolygon( shapeBuffer, getMaxError() );
611 }
612 break;
613
614 case SHAPE_T::CIRCLE:
616 break;
617
618 case SHAPE_T::POLY:
619 if( !IsClosed() )
620 return;
621
622 shapeBuffer = m_poly.CloneDropTriangulation();
623 break;
624
625 default:
627 return;
628 }
629
630 // Clear cached hatching only after all validation passes.
631 // This prevents flickering when early returns would otherwise leave empty hatching.
632 m_hatching.RemoveAllContours();
633 m_hatchLines.clear();
634
635 BOX2I extents = shapeBuffer.BBox();
636 int majorAxis = std::max( extents.GetWidth(), extents.GetHeight() );
637
638 if( majorAxis / spacing > 100 )
639 spacing = majorAxis / 100;
640
641 // Generate hatch lines for stroke-based rendering. All hatch types use line segments.
642 std::vector<SEG> hatchSegs = shapeBuffer.GenerateHatchLines( slopes, spacing, -1 );
643 m_hatchLines = hatchSegs;
644
645 // Also generate polygon representation for exports, 3D viewer, and hit testing
647 {
648 for( const SEG& seg : hatchSegs )
649 {
650 // We don't really need the rounded ends at all, so don't spend any extra time on them
651 int maxError = lineWidth;
652
653 TransformOvalToPolygon( m_hatching, seg.A, seg.B, lineWidth, maxError, ERROR_INSIDE );
654 }
655
656 m_hatching.Fracture();
657 m_hatchingDirty = false;
658 }
659 else
660 {
661 // Generate a grid of holes for a cross-hatch polygon representation.
662 // This is used for exports, 3D viewer, and hit testing.
663
664 int gridsize = spacing;
665 int hole_size = gridsize - GetHatchLineWidth();
666
667 m_hatching = shapeBuffer.CloneDropTriangulation();
668 m_hatching.Rotate( -ANGLE_45 );
669
670 // Build hole shape
671 SHAPE_LINE_CHAIN hole_base;
672 VECTOR2I corner( 0, 0 );;
673 hole_base.Append( corner );
674 corner.x += hole_size;
675 hole_base.Append( corner );
676 corner.y += hole_size;
677 hole_base.Append( corner );
678 corner.x = 0;
679 hole_base.Append( corner );
680 hole_base.SetClosed( true );
681
682 // Build holes
683 BOX2I bbox = m_hatching.BBox( 0 );
684 SHAPE_POLY_SET holes;
685
686 int x_offset = bbox.GetX() - ( bbox.GetX() ) % gridsize - gridsize;
687 int y_offset = bbox.GetY() - ( bbox.GetY() ) % gridsize - gridsize;
688
689 for( int xx = x_offset; xx <= bbox.GetRight(); xx += gridsize )
690 {
691 for( int yy = y_offset; yy <= bbox.GetBottom(); yy += gridsize )
692 {
693 SHAPE_LINE_CHAIN hole( hole_base );
694 hole.Move( VECTOR2I( xx, yy ) );
695 holes.AddOutline( hole );
696 }
697 }
698
699 m_hatching.BooleanSubtract( holes );
700 m_hatching.Fracture();
701
702 // Must re-rotate after Fracture(). Clipper struggles mightily with fracturing
703 // 45-degree holes.
704 m_hatching.Rotate( ANGLE_45 );
705 m_hatchingDirty = false;
706 }
707}
708
709
710void EDA_SHAPE::move( const VECTOR2I& aMoveVector )
711{
712 switch ( m_shape )
713 {
714 case SHAPE_T::ARC:
715 m_arcCenter += aMoveVector;
716 m_arcMidData.center += aMoveVector;
717 m_arcMidData.start += aMoveVector;
718 m_arcMidData.end += aMoveVector;
719 m_arcMidData.mid += aMoveVector;
721
722 case SHAPE_T::SEGMENT:
724 case SHAPE_T::CIRCLE:
725 m_start += aMoveVector;
726 m_end += aMoveVector;
727 break;
728
729 case SHAPE_T::POLY:
730 m_poly.Move( aMoveVector );
731 break;
732
733 case SHAPE_T::BEZIER:
734 m_start += aMoveVector;
735 m_end += aMoveVector;
736 m_bezierC1 += aMoveVector;
737 m_bezierC2 += aMoveVector;
738
739 for( VECTOR2I& pt : m_bezierPoints )
740 pt += aMoveVector;
741
742 break;
743
744 default:
746 break;
747 }
748
749 m_hatchingDirty = true;
750}
751
752
753void EDA_SHAPE::scale( double aScale )
754{
755 auto scalePt =
756 [&]( VECTOR2I& pt )
757 {
758 pt.x = KiROUND( pt.x * aScale );
759 pt.y = KiROUND( pt.y * aScale );
760 };
761
762 switch( m_shape )
763 {
764 case SHAPE_T::ARC:
765 scalePt( m_arcCenter );
767
768 case SHAPE_T::SEGMENT:
770 case SHAPE_T::CIRCLE:
771 scalePt( m_start );
772 scalePt( m_end );
773 break;
774
775 case SHAPE_T::POLY: // polygon
776 {
777 std::vector<VECTOR2I> pts;
778
779 for( int ii = 0; ii < m_poly.OutlineCount(); ++ ii )
780 {
781 for( const VECTOR2I& pt : m_poly.Outline( ii ).CPoints() )
782 {
783 pts.emplace_back( pt );
784 scalePt( pts.back() );
785 }
786 }
787
788 SetPolyPoints( pts );
789 }
790 break;
791
792 case SHAPE_T::BEZIER:
793 scalePt( m_start );
794 scalePt( m_end );
795 scalePt( m_bezierC1 );
796 scalePt( m_bezierC2 );
798 break;
799
800 default:
802 break;
803 }
804
805 m_hatchingDirty = true;
806}
807
808
809void EDA_SHAPE::rotate( const VECTOR2I& aRotCentre, const EDA_ANGLE& aAngle )
810{
811 switch( m_shape )
812 {
813 case SHAPE_T::SEGMENT:
814 case SHAPE_T::CIRCLE:
815 RotatePoint( m_start, aRotCentre, aAngle );
816 RotatePoint( m_end, aRotCentre, aAngle );
817 break;
818
819 case SHAPE_T::ARC:
820 RotatePoint( m_start, aRotCentre, aAngle );
821 RotatePoint( m_end, aRotCentre, aAngle );
822 RotatePoint( m_arcCenter, aRotCentre, aAngle );
823 RotatePoint( m_arcMidData.start, aRotCentre, aAngle );
824 RotatePoint( m_arcMidData.end, aRotCentre, aAngle );
825 RotatePoint( m_arcMidData.mid, aRotCentre, aAngle );
826 RotatePoint( m_arcMidData.center, aRotCentre, aAngle );
827 break;
828
830 if( aAngle.IsCardinal() )
831 {
832 RotatePoint( m_start, aRotCentre, aAngle );
833 RotatePoint( m_end, aRotCentre, aAngle );
834 }
835 else
836 {
837 // Convert non-cardinally-rotated rect to a diamond
841 m_poly.Rotate( aAngle, aRotCentre );
842 }
843
844 break;
845
846 case SHAPE_T::POLY:
847 m_poly.Rotate( aAngle, aRotCentre );
848 break;
849
850 case SHAPE_T::BEZIER:
851 RotatePoint( m_start, aRotCentre, aAngle );
852 RotatePoint( m_end, aRotCentre, aAngle );
853 RotatePoint( m_bezierC1, aRotCentre, aAngle );
854 RotatePoint( m_bezierC2, aRotCentre, aAngle );
855
856 for( VECTOR2I& pt : m_bezierPoints )
857 RotatePoint( pt, aRotCentre, aAngle);
858
859 break;
860
861 default:
863 break;
864 }
865
866 m_hatchingDirty = true;
867}
868
869
870void EDA_SHAPE::flip( const VECTOR2I& aCentre, FLIP_DIRECTION aFlipDirection )
871{
872 switch ( m_shape )
873 {
874 case SHAPE_T::SEGMENT:
876 MIRROR( m_start, aCentre, aFlipDirection );
877 MIRROR( m_end, aCentre, aFlipDirection );
878 break;
879
880 case SHAPE_T::CIRCLE:
881 MIRROR( m_start, aCentre, aFlipDirection );
882 MIRROR( m_end, aCentre, aFlipDirection );
883 break;
884
885 case SHAPE_T::ARC:
886 MIRROR( m_start, aCentre, aFlipDirection );
887 MIRROR( m_end, aCentre, aFlipDirection );
888 MIRROR( m_arcCenter, aCentre, aFlipDirection );
889
890 std::swap( m_start, m_end );
891 break;
892
893 case SHAPE_T::POLY:
894 m_poly.Mirror( aCentre, aFlipDirection );
895 break;
896
897 case SHAPE_T::BEZIER:
898 MIRROR( m_start, aCentre, aFlipDirection );
899 MIRROR( m_end, aCentre, aFlipDirection );
900 MIRROR( m_bezierC1, aCentre, aFlipDirection );
901 MIRROR( m_bezierC2, aCentre, aFlipDirection );
902
904 break;
905
906 default:
908 break;
909 }
910
911 m_hatchingDirty = true;
912}
913
914
916{
917 // Has meaning only for SHAPE_T::BEZIER
918 if( m_shape != SHAPE_T::BEZIER )
919 {
920 m_bezierPoints.clear();
921 return;
922 }
923
924 // Rebuild the m_BezierPoints vertex list that approximate the Bezier curve
926}
927
928
929const std::vector<VECTOR2I> EDA_SHAPE::buildBezierToSegmentsPointsList( int aMaxError ) const
930{
931 std::vector<VECTOR2I> bezierPoints;
932
933 // Rebuild the m_BezierPoints vertex list that approximate the Bezier curve
934 std::vector<VECTOR2I> ctrlPoints = { m_start, m_bezierC1, m_bezierC2, m_end };
935 BEZIER_POLY converter( ctrlPoints );
936 converter.GetPoly( bezierPoints, aMaxError );
937
938 return bezierPoints;
939}
940
941
943{
944 switch( m_shape )
945 {
946 case SHAPE_T::ARC:
947 return m_arcCenter;
948
949 case SHAPE_T::CIRCLE:
950 return m_start;
951
952 case SHAPE_T::SEGMENT:
953 // Midpoint of the line
954 return ( m_start + m_end ) / 2;
955
956 case SHAPE_T::POLY:
958 case SHAPE_T::BEZIER:
959 return getBoundingBox().Centre();
960
961 default:
963 return VECTOR2I();
964 }
965}
966
967
968void EDA_SHAPE::SetCenter( const VECTOR2I& aCenter )
969{
970 switch( m_shape )
971 {
972 case SHAPE_T::ARC:
973 m_arcCenter = aCenter;
974 break;
975
976 case SHAPE_T::CIRCLE:
977 m_start = aCenter;
978 m_hatchingDirty = true;
979 break;
980
981 default:
983 }
984}
985
986
988{
989 // If none of the input data have changed since we loaded the arc, keep the original mid point data
990 // to minimize churn
991 if( m_arcMidData.start == m_start && m_arcMidData.end == m_end && m_arcMidData.center == m_arcCenter )
992 return m_arcMidData.mid;
993
994 VECTOR2I mid = m_start;
995 RotatePoint( mid, m_arcCenter, -GetArcAngle() / 2.0 );
996 return mid;
997}
998
999
1000void EDA_SHAPE::CalcArcAngles( EDA_ANGLE& aStartAngle, EDA_ANGLE& aEndAngle ) const
1001{
1002 VECTOR2D startRadial( GetStart() - getCenter() );
1003 VECTOR2D endRadial( GetEnd() - getCenter() );
1004
1005 aStartAngle = EDA_ANGLE( startRadial );
1006 aEndAngle = EDA_ANGLE( endRadial );
1007
1008 if( aEndAngle == aStartAngle )
1009 aEndAngle = aStartAngle + ANGLE_360; // ring, not null
1010
1011 while( aEndAngle < aStartAngle )
1012 aEndAngle += ANGLE_360;
1013}
1014
1015
1017{
1018 double radius = 0.0;
1019
1020 switch( m_shape )
1021 {
1022 case SHAPE_T::ARC:
1023 radius = m_arcCenter.Distance( m_start );
1024 break;
1025
1026 case SHAPE_T::CIRCLE:
1027 radius = m_start.Distance( m_end );
1028 break;
1029
1030 default:
1032 }
1033
1034 // don't allow degenerate circles/arcs
1035 if( radius > (double) INT_MAX / 2.0 )
1036 radius = (double) INT_MAX / 2.0;
1037
1038 return std::max( 1, KiROUND( radius ) );
1039}
1040
1041
1042void EDA_SHAPE::SetCachedArcData( const VECTOR2I& aStart, const VECTOR2I& aMid,
1043 const VECTOR2I& aEnd, const VECTOR2I& aCenter )
1044{
1045 m_arcMidData.start = aStart;
1046 m_arcMidData.end = aEnd;
1047 m_arcMidData.center = aCenter;
1048 m_arcMidData.mid = aMid;
1049}
1050
1051
1052void EDA_SHAPE::SetArcGeometry( const VECTOR2I& aStart, const VECTOR2I& aMid, const VECTOR2I& aEnd )
1053{
1054 m_arcMidData = {};
1055 m_start = aStart;
1056 m_end = aEnd;
1057 m_arcCenter = CalcArcCenter( aStart, aMid, aEnd );
1058 VECTOR2I new_mid = GetArcMid();
1059
1060 m_endsSwapped = false;
1061
1062 // Watch the ordering here. GetArcMid above needs to be called prior to initializing the
1063 // m_arcMidData structure in order to ensure we get the calculated variant, not the cached
1064 SetCachedArcData( aStart, aMid, aEnd, m_arcCenter );
1065
1066 /*
1067 * If the input winding doesn't match our internal winding, the calculated midpoint will end
1068 * up on the other side of the arc. In this case, we need to flip the start/end points and
1069 * flag this change for the system.
1070 */
1071 VECTOR2D dist( new_mid - aMid );
1072 VECTOR2D dist2( new_mid - m_arcCenter );
1073
1074 if( dist.SquaredEuclideanNorm() > dist2.SquaredEuclideanNorm() )
1075 {
1076 std::swap( m_start, m_end );
1077 m_endsSwapped = true;
1078 }
1079}
1080
1081
1083{
1084 EDA_ANGLE angle( atan2( static_cast<double>( GetStart().y - GetEnd().y ),
1085 static_cast<double>( GetEnd().x - GetStart().x ) ), RADIANS_T );
1086
1087 return angle;
1088}
1089
1090
1092{
1093 EDA_ANGLE startAngle;
1094 EDA_ANGLE endAngle;
1095
1096 CalcArcAngles( startAngle, endAngle );
1097
1098 return endAngle - startAngle;
1099}
1100
1101
1103{
1104 if( m_shape == SHAPE_T::ARC )
1105 {
1106 VECTOR2D mid = GetArcMid();
1107
1108 double orient = ( mid.x - m_start.x ) * ( m_end.y - m_start.y )
1109 - ( mid.y - m_start.y ) * ( m_end.x - m_start.x );
1110
1111 return orient < 0;
1112 }
1113
1115 return false;
1116}
1117
1118
1119void EDA_SHAPE::SetArcAngleAndEnd( const EDA_ANGLE& aAngle, bool aCheckNegativeAngle )
1120{
1121 EDA_ANGLE angle( aAngle );
1122
1123 m_end = m_start;
1125
1126 if( aCheckNegativeAngle && aAngle < ANGLE_0 )
1127 {
1128 std::swap( m_start, m_end );
1129 m_endsSwapped = true;
1130 }
1131}
1132
1133
1135{
1136 if( IsProxyItem() )
1137 {
1138 switch( m_shape )
1139 {
1140 case SHAPE_T::RECTANGLE: return _( "Pad Number Box" );
1141 case SHAPE_T::SEGMENT: return _( "Thermal Spoke Template" );
1142 default: return _( "Unrecognized" );
1143 }
1144 }
1145 else
1146 {
1147 switch( m_shape )
1148 {
1149 case SHAPE_T::CIRCLE: return _( "Circle" );
1150 case SHAPE_T::ARC: return _( "Arc" );
1151 case SHAPE_T::BEZIER: return _( "Curve" );
1152 case SHAPE_T::POLY: return _( "Polygon" );
1153 case SHAPE_T::RECTANGLE: return _( "Rectangle" );
1154 case SHAPE_T::SEGMENT: return _( "Segment" );
1155 default: return _( "Unrecognized" );
1156 }
1157 }
1158}
1159
1160
1161void EDA_SHAPE::ShapeGetMsgPanelInfo( EDA_DRAW_FRAME* aFrame, std::vector<MSG_PANEL_ITEM>& aList )
1162{
1163 wxString msg;
1164
1165 wxString shape = _( "Shape" );
1166 aList.emplace_back( shape, getFriendlyName() );
1167
1168 switch( m_shape )
1169 {
1170 case SHAPE_T::CIRCLE:
1171 aList.emplace_back( _( "Radius" ), aFrame->MessageTextFromValue( GetRadius() ) );
1172 break;
1173
1174 case SHAPE_T::ARC:
1175 aList.emplace_back( _( "Length" ), aFrame->MessageTextFromValue( GetLength() ) );
1176
1178 aList.emplace_back( _( "Angle" ), msg );
1179
1180 aList.emplace_back( _( "Radius" ), aFrame->MessageTextFromValue( GetRadius() ) );
1181 break;
1182
1183 case SHAPE_T::BEZIER:
1184 aList.emplace_back( _( "Length" ), aFrame->MessageTextFromValue( GetLength() ) );
1185 break;
1186
1187 case SHAPE_T::POLY:
1188 msg.Printf( wxS( "%d" ), GetPolyShape().Outline(0).PointCount() );
1189 aList.emplace_back( _( "Points" ), msg );
1190 break;
1191
1192 case SHAPE_T::RECTANGLE:
1193 aList.emplace_back( _( "Width" ), aFrame->MessageTextFromValue( std::abs( GetEnd().x - GetStart().x ) ) );
1194 aList.emplace_back( _( "Height" ), aFrame->MessageTextFromValue( std::abs( GetEnd().y - GetStart().y ) ) );
1195 break;
1196
1197 case SHAPE_T::SEGMENT:
1198 {
1199 aList.emplace_back( _( "Length" ), aFrame->MessageTextFromValue( GetStart().Distance( GetEnd() ) ));
1200
1201 // angle counter-clockwise from 3'o-clock
1202 EDA_ANGLE angle( atan2( (double)( GetStart().y - GetEnd().y ), (double)( GetEnd().x - GetStart().x ) ),
1203 RADIANS_T );
1204 aList.emplace_back( _( "Angle" ), EDA_UNIT_UTILS::UI::MessageTextFromValue( angle ) );
1205 break;
1206 }
1207
1208 default:
1209 break;
1210 }
1211
1212 m_stroke.GetMsgPanelInfo( aFrame, aList );
1213}
1214
1215
1217{
1218 BOX2I bbox;
1219
1220 switch( m_shape )
1221 {
1222 case SHAPE_T::RECTANGLE:
1223 for( VECTOR2I& pt : GetRectCorners() )
1224 bbox.Merge( pt );
1225
1226 break;
1227
1228 case SHAPE_T::SEGMENT:
1229 bbox.SetOrigin( GetStart() );
1230 bbox.SetEnd( GetEnd() );
1231 break;
1232
1233 case SHAPE_T::CIRCLE:
1234 bbox.SetOrigin( GetStart() );
1235 bbox.Inflate( GetRadius() );
1236 break;
1237
1238 case SHAPE_T::ARC:
1239 computeArcBBox( bbox );
1240 break;
1241
1242 case SHAPE_T::POLY:
1243 if( m_poly.IsEmpty() )
1244 break;
1245
1246 for( auto iter = m_poly.CIterate(); iter; iter++ )
1247 bbox.Merge( *iter );
1248
1249 break;
1250
1251 case SHAPE_T::BEZIER:
1252 // Bezier BBoxes are not trivial to compute, so we approximate it by
1253 // using the bounding box of the curve (not control!) points.
1254 for( const VECTOR2I& pt : m_bezierPoints )
1255 bbox.Merge( pt );
1256
1257 break;
1258
1259 default:
1261 break;
1262 }
1263
1264 bbox.Inflate( std::max( 0, GetWidth() ) / 2 );
1265 bbox.Normalize();
1266
1267 return bbox;
1268}
1269
1270
1271bool EDA_SHAPE::hitTest( const VECTOR2I& aPosition, int aAccuracy ) const
1272{
1273 double maxdist = aAccuracy;
1274
1275 if( GetWidth() > 0 )
1276 maxdist += GetWidth() / 2.0;
1277
1278 switch( m_shape )
1279 {
1280 case SHAPE_T::CIRCLE:
1281 {
1282 double radius = GetRadius();
1283 double dist = aPosition.Distance( getCenter() );
1284
1285 if( IsFilledForHitTesting() )
1286 return dist <= radius + maxdist; // Filled circle hit-test
1287 else if( abs( radius - dist ) <= maxdist ) // Ring hit-test
1288 return true;
1289
1290 if( IsHatchedFill() && GetHatching().Collide( aPosition, maxdist ) )
1291 return true;
1292
1293 return false;
1294 }
1295
1296 case SHAPE_T::ARC:
1297 {
1298 if( aPosition.Distance( m_start ) <= maxdist )
1299 return true;
1300
1301 if( aPosition.Distance( m_end ) <= maxdist )
1302 return true;
1303
1304 double radius = GetRadius();
1305 VECTOR2D relPos( VECTOR2D( aPosition ) - getCenter() );
1306 double dist = relPos.EuclideanNorm();
1307
1308 if( IsFilledForHitTesting() )
1309 {
1310 // Check distance from arc center
1311 if( dist > radius + maxdist )
1312 return false;
1313 }
1314 else
1315 {
1316 // Check distance from arc circumference
1317 if( abs( radius - dist ) > maxdist )
1318 return false;
1319 }
1320
1321 // Finally, check to see if it's within arc's swept angle.
1322 EDA_ANGLE startAngle;
1323 EDA_ANGLE endAngle;
1324 CalcArcAngles( startAngle, endAngle );
1325
1326 EDA_ANGLE relPosAngle( relPos );
1327
1328 startAngle.Normalize();
1329 endAngle.Normalize();
1330 relPosAngle.Normalize();
1331
1332 if( endAngle > startAngle )
1333 return relPosAngle >= startAngle && relPosAngle <= endAngle;
1334 else
1335 return relPosAngle >= startAngle || relPosAngle <= endAngle;
1336 }
1337
1338 case SHAPE_T::BEZIER:
1339 {
1340 const std::vector<VECTOR2I>* pts = &m_bezierPoints;
1341 std::vector<VECTOR2I> updatedBezierPoints;
1342
1343 if( m_bezierPoints.empty() )
1344 {
1346 converter.GetPoly( updatedBezierPoints, aAccuracy / 2 );
1347 pts = &updatedBezierPoints;
1348 }
1349
1350 for( unsigned int i = 1; i < pts->size(); i++ )
1351 {
1352 if( TestSegmentHit( aPosition, ( *pts )[i - 1], ( *pts )[i], maxdist ) )
1353 return true;
1354 }
1355
1356 return false;
1357 }
1358 case SHAPE_T::SEGMENT:
1359 return TestSegmentHit( aPosition, GetStart(), GetEnd(), maxdist );
1360
1361 case SHAPE_T::RECTANGLE:
1362 if( IsProxyItem() || IsFilledForHitTesting() ) // Filled rect hit-test
1363 {
1364 SHAPE_POLY_SET poly;
1365 poly.NewOutline();
1366
1367 for( const VECTOR2I& pt : GetRectCorners() )
1368 poly.Append( pt );
1369
1370 return poly.Collide( aPosition, maxdist );
1371 }
1372 else if( m_cornerRadius > 0 )
1373 {
1375 SHAPE_POLY_SET poly;
1376 rr.TransformToPolygon( poly, getMaxError() );
1377
1378 if( poly.CollideEdge( aPosition, nullptr, maxdist ) )
1379 return true;
1380 }
1381 else
1382 {
1383 std::vector<VECTOR2I> pts = GetRectCorners();
1384
1385 if( TestSegmentHit( aPosition, pts[0], pts[1], maxdist )
1386 || TestSegmentHit( aPosition, pts[1], pts[2], maxdist )
1387 || TestSegmentHit( aPosition, pts[2], pts[3], maxdist )
1388 || TestSegmentHit( aPosition, pts[3], pts[0], maxdist ) )
1389 {
1390 return true;
1391 }
1392 }
1393
1394 if( IsHatchedFill() && GetHatching().Collide( aPosition, maxdist ) )
1395 return true;
1396
1397 return false;
1398
1399 case SHAPE_T::POLY:
1400 if( m_poly.OutlineCount() < 1 ) // empty poly
1401 return false;
1402
1403 if( IsFilledForHitTesting() )
1404 {
1405 if( !m_poly.COutline( 0 ).IsClosed() )
1406 {
1407 // Only one outline is expected
1408 SHAPE_LINE_CHAIN copy( m_poly.COutline( 0 ) );
1409 copy.SetClosed( true );
1410 return copy.Collide( aPosition, maxdist );
1411 }
1412 else
1413 {
1414 return m_poly.Collide( aPosition, maxdist );
1415 }
1416 }
1417 else
1418 {
1419 if( m_poly.CollideEdge( aPosition, nullptr, maxdist ) )
1420 return true;
1421
1422 if( IsHatchedFill() && GetHatching().Collide( aPosition, maxdist ) )
1423 return true;
1424
1425 return false;
1426 }
1427
1428 default:
1430 return false;
1431 }
1432}
1433
1434
1435bool EDA_SHAPE::hitTest( const BOX2I& aRect, bool aContained, int aAccuracy ) const
1436{
1437 BOX2I arect = aRect;
1438 arect.Normalize();
1439 arect.Inflate( aAccuracy );
1440
1441 BOX2I bbox = getBoundingBox();
1442
1443 auto checkOutline =
1444 [&]( const SHAPE_LINE_CHAIN& outline )
1445 {
1446 int count = (int) outline.GetPointCount();
1447
1448 for( int ii = 0; ii < count; ii++ )
1449 {
1450 VECTOR2I vertex = outline.GetPoint( ii );
1451
1452 // Test if the point is within aRect
1453 if( arect.Contains( vertex ) )
1454 return true;
1455
1456 if( ii + 1 < count )
1457 {
1458 VECTOR2I vertexNext = outline.GetPoint( ii + 1 );
1459
1460 // Test if this edge intersects aRect
1461 if( arect.Intersects( vertex, vertexNext ) )
1462 return true;
1463 }
1464 else if( outline.IsClosed() )
1465 {
1466 VECTOR2I vertexNext = outline.GetPoint( 0 );
1467
1468 // Test if this edge intersects aRect
1469 if( arect.Intersects( vertex, vertexNext ) )
1470 return true;
1471 }
1472 }
1473
1474 return false;
1475 };
1476
1477 switch( m_shape )
1478 {
1479 case SHAPE_T::CIRCLE:
1480 // Test if area intersects or contains the circle:
1481 if( aContained )
1482 {
1483 return arect.Contains( bbox );
1484 }
1485 else
1486 {
1487 // If the rectangle does not intersect the bounding box, this is a much quicker test
1488 if( !arect.Intersects( bbox ) )
1489 return false;
1490 else
1491 return arect.IntersectsCircleEdge( getCenter(), GetRadius(), GetWidth() );
1492 }
1493
1494 case SHAPE_T::ARC:
1495 // Test for full containment of this arc in the rect
1496 if( aContained )
1497 {
1498 return arect.Contains( bbox );
1499 }
1500 // Test if the rect crosses the arc
1501 else
1502 {
1503 if( !arect.Intersects( bbox ) )
1504 return false;
1505
1506 if( IsAnyFill() )
1507 {
1508 return ( arect.Intersects( getCenter(), GetStart() )
1509 || arect.Intersects( getCenter(), GetEnd() )
1510 || arect.IntersectsCircleEdge( getCenter(), GetRadius(), GetWidth() ) );
1511 }
1512 else
1513 {
1514 return arect.IntersectsCircleEdge( getCenter(), GetRadius(), GetWidth() );
1515 }
1516 }
1517
1518 case SHAPE_T::RECTANGLE:
1519 if( aContained )
1520 {
1521 return arect.Contains( bbox );
1522 }
1523 else if( m_cornerRadius > 0 )
1524 {
1526 SHAPE_POLY_SET poly;
1527 rr.TransformToPolygon( poly, getMaxError() );
1528
1529 // Account for the width of the line
1530 arect.Inflate( GetWidth() / 2 );
1531
1532 return checkOutline( poly.Outline( 0 ) );
1533 }
1534 else
1535 {
1536 std::vector<VECTOR2I> pts = GetRectCorners();
1537
1538 // Account for the width of the lines
1539 arect.Inflate( GetWidth() / 2 );
1540 return ( arect.Intersects( pts[0], pts[1] )
1541 || arect.Intersects( pts[1], pts[2] )
1542 || arect.Intersects( pts[2], pts[3] )
1543 || arect.Intersects( pts[3], pts[0] ) );
1544 }
1545
1546 case SHAPE_T::SEGMENT:
1547 if( aContained )
1548 {
1549 return arect.Contains( GetStart() ) && aRect.Contains( GetEnd() );
1550 }
1551 else
1552 {
1553 // Account for the width of the line
1554 arect.Inflate( GetWidth() / 2 );
1555 return arect.Intersects( GetStart(), GetEnd() );
1556 }
1557
1558 case SHAPE_T::POLY:
1559 if( aContained )
1560 {
1561 return arect.Contains( bbox );
1562 }
1563 else
1564 {
1565 // Fast test: if aRect is outside the polygon bounding box,
1566 // rectangles cannot intersect
1567 if( !arect.Intersects( bbox ) )
1568 return false;
1569
1570 // Account for the width of the line
1571 arect.Inflate( GetWidth() / 2 );
1572
1573 for( int ii = 0; ii < m_poly.OutlineCount(); ++ii )
1574 {
1575 if( checkOutline( m_poly.Outline( ii ) ) )
1576 return true;
1577 }
1578
1579 return false;
1580 }
1581
1582 case SHAPE_T::BEZIER:
1583 if( aContained )
1584 {
1585 return arect.Contains( bbox );
1586 }
1587 else
1588 {
1589 // Fast test: if aRect is outside the polygon bounding box,
1590 // rectangles cannot intersect
1591 if( !arect.Intersects( bbox ) )
1592 return false;
1593
1594 // Account for the width of the line
1595 arect.Inflate( GetWidth() / 2 );
1596 const std::vector<VECTOR2I>* pts = &m_bezierPoints;
1597 std::vector<VECTOR2I> updatedBezierPoints;
1598
1599 if( m_bezierPoints.empty() )
1600 {
1602 converter.GetPoly( updatedBezierPoints, aAccuracy / 2 );
1603 pts = &updatedBezierPoints;
1604 }
1605
1606 for( unsigned ii = 1; ii < pts->size(); ii++ )
1607 {
1608 VECTOR2I vertex = ( *pts )[ii - 1];
1609 VECTOR2I vertexNext = ( *pts )[ii];
1610
1611 // Test if the point is within aRect
1612 if( arect.Contains( vertex ) )
1613 return true;
1614
1615 // Test if this edge intersects aRect
1616 if( arect.Intersects( vertex, vertexNext ) )
1617 return true;
1618 }
1619
1620 return false;
1621 }
1622
1623 default:
1625 return false;
1626 }
1627}
1628
1629
1630bool EDA_SHAPE::hitTest( const SHAPE_LINE_CHAIN& aPoly, bool aContained ) const
1631{
1633
1634 return KIGEOM::ShapeHitTest( aPoly, shape, aContained );
1635}
1636
1637
1638std::vector<VECTOR2I> EDA_SHAPE::GetRectCorners() const
1639{
1640 std::vector<VECTOR2I> pts;
1641 VECTOR2I topLeft = GetStart();
1642 VECTOR2I botRight = GetEnd();
1643
1644 pts.emplace_back( topLeft );
1645 pts.emplace_back( botRight.x, topLeft.y );
1646 pts.emplace_back( botRight );
1647 pts.emplace_back( topLeft.x, botRight.y );
1648
1649 return pts;
1650}
1651
1652
1653std::vector<VECTOR2I> EDA_SHAPE::GetCornersInSequence( EDA_ANGLE angle ) const
1654{
1655 std::vector<VECTOR2I> pts;
1656
1657 angle.Normalize();
1658
1659 BOX2I bbox = getBoundingBox();
1660 bbox.Normalize();
1661
1662 if( angle.IsCardinal() )
1663 {
1664 if( angle == ANGLE_0 )
1665 {
1666 pts.emplace_back( VECTOR2I( bbox.GetLeft(), bbox.GetTop() ) );
1667 pts.emplace_back( VECTOR2I( bbox.GetRight(), bbox.GetTop() ) );
1668 pts.emplace_back( VECTOR2I( bbox.GetRight(), bbox.GetBottom() ) );
1669 pts.emplace_back( VECTOR2I( bbox.GetLeft(), bbox.GetBottom() ) );
1670 }
1671 else if( angle == ANGLE_90 )
1672 {
1673 pts.emplace_back( VECTOR2I( bbox.GetLeft(), bbox.GetBottom() ) );
1674 pts.emplace_back( VECTOR2I( bbox.GetLeft(), bbox.GetTop() ) );
1675 pts.emplace_back( VECTOR2I( bbox.GetRight(), bbox.GetTop() ) );
1676 pts.emplace_back( VECTOR2I( bbox.GetRight(), bbox.GetBottom() ) );
1677 }
1678 else if( angle == ANGLE_180 )
1679 {
1680 pts.emplace_back( VECTOR2I( bbox.GetRight(), bbox.GetBottom() ) );
1681 pts.emplace_back( VECTOR2I( bbox.GetLeft(), bbox.GetBottom() ) );
1682 pts.emplace_back( VECTOR2I( bbox.GetLeft(), bbox.GetTop() ) );
1683 pts.emplace_back( VECTOR2I( bbox.GetRight(), bbox.GetTop() ) );
1684 }
1685 else if( angle == ANGLE_270 )
1686 {
1687 pts.emplace_back( VECTOR2I( bbox.GetRight(), bbox.GetTop() ) );
1688 pts.emplace_back( VECTOR2I( bbox.GetRight(), bbox.GetBottom() ) );
1689 pts.emplace_back( VECTOR2I( bbox.GetLeft(), bbox.GetBottom() ) );
1690 pts.emplace_back( VECTOR2I( bbox.GetLeft(), bbox.GetTop() ) );
1691 }
1692 }
1693 else
1694 {
1695 // This function was originally located in pcb_textbox.cpp and was later moved to eda_shape.cpp.
1696 // As a result of this move, access to getCorners was lost, since it is defined in the PCB_SHAPE
1697 // class within pcb_shape.cpp and is not available in the current context.
1698 //
1699 // Additionally, GetRectCorners() cannot be used here, as it assumes the rectangle is rotated by
1700 // a cardinal angle. In non-cardinal cases, it returns incorrect values (e.g., (0, 0)).
1701 //
1702 // To address this, a portion of the getCorners implementation for SHAPE_T::POLY elements
1703 // has been replicated here to restore the correct behavior.
1704 std::vector<VECTOR2I> corners;
1705
1706 for( int ii = 0; ii < GetPolyShape().OutlineCount(); ++ii )
1707 {
1708 for( const VECTOR2I& pt : GetPolyShape().Outline( ii ).CPoints() )
1709 corners.emplace_back( pt );
1710 }
1711
1712 while( corners.size() < 4 )
1713 corners.emplace_back( corners.back() + VECTOR2I( 10, 10 ) );
1714
1715 VECTOR2I minX = corners[0];
1716 VECTOR2I maxX = corners[0];
1717 VECTOR2I minY = corners[0];
1718 VECTOR2I maxY = corners[0];
1719
1720 for( const VECTOR2I& corner : corners )
1721 {
1722 if( corner.x < minX.x )
1723 minX = corner;
1724
1725 if( corner.x > maxX.x )
1726 maxX = corner;
1727
1728 if( corner.y < minY.y )
1729 minY = corner;
1730
1731 if( corner.y > maxY.y )
1732 maxY = corner;
1733 }
1734
1735 if( angle < ANGLE_90 )
1736 {
1737 pts.emplace_back( minX );
1738 pts.emplace_back( minY );
1739 pts.emplace_back( maxX );
1740 pts.emplace_back( maxY );
1741 }
1742 else if( angle < ANGLE_180 )
1743 {
1744 pts.emplace_back( maxY );
1745 pts.emplace_back( minX );
1746 pts.emplace_back( minY );
1747 pts.emplace_back( maxX );
1748 }
1749 else if( angle < ANGLE_270 )
1750 {
1751 pts.emplace_back( maxX );
1752 pts.emplace_back( maxY );
1753 pts.emplace_back( minX );
1754 pts.emplace_back( minY );
1755 }
1756 else
1757 {
1758 pts.emplace_back( minY );
1759 pts.emplace_back( maxX );
1760 pts.emplace_back( maxY );
1761 pts.emplace_back( minX );
1762 }
1763 }
1764
1765 return pts;
1766}
1767
1768
1770{
1771 // Start, end, and each inflection point the arc crosses will enclose the entire arc.
1772 // Only include the center when filled; it's not necessarily inside the BB of an unfilled
1773 // arc with a small included angle.
1774 aBBox.SetOrigin( m_start );
1775 aBBox.Merge( m_end );
1776
1777 if( IsAnyFill() )
1778 aBBox.Merge( m_arcCenter );
1779
1780 int radius = GetRadius();
1781 EDA_ANGLE t1, t2;
1782
1783 CalcArcAngles( t1, t2 );
1784
1785 t1.Normalize();
1786 t2.Normalize();
1787
1788 if( t2 > t1 )
1789 {
1790 if( t1 < ANGLE_0 && t2 > ANGLE_0 )
1791 aBBox.Merge( VECTOR2I( m_arcCenter.x + radius, m_arcCenter.y ) ); // right
1792
1793 if( t1 < ANGLE_90 && t2 > ANGLE_90 )
1794 aBBox.Merge( VECTOR2I( m_arcCenter.x, m_arcCenter.y + radius ) ); // down
1795
1796 if( t1 < ANGLE_180 && t2 > ANGLE_180 )
1797 aBBox.Merge( VECTOR2I( m_arcCenter.x - radius, m_arcCenter.y ) ); // left
1798
1799 if( t1 < ANGLE_270 && t2 > ANGLE_270 )
1800 aBBox.Merge( VECTOR2I( m_arcCenter.x, m_arcCenter.y - radius ) ); // up
1801 }
1802 else
1803 {
1804 if( t1 < ANGLE_0 || t2 > ANGLE_0 )
1805 aBBox.Merge( VECTOR2I( m_arcCenter.x + radius, m_arcCenter.y ) ); // right
1806
1807 if( t1 < ANGLE_90 || t2 > ANGLE_90 )
1808 aBBox.Merge( VECTOR2I( m_arcCenter.x, m_arcCenter.y + radius ) ); // down
1809
1810 if( t1 < ANGLE_180 || t2 > ANGLE_180 )
1811 aBBox.Merge( VECTOR2I( m_arcCenter.x - radius, m_arcCenter.y ) ); // left
1812
1813 if( t1 < ANGLE_270 || t2 > ANGLE_270 )
1814 aBBox.Merge( VECTOR2I( m_arcCenter.x, m_arcCenter.y - radius ) ); // up
1815 }
1816}
1817
1818
1819void EDA_SHAPE::SetPolyPoints( const std::vector<VECTOR2I>& aPoints )
1820{
1821 m_poly.RemoveAllContours();
1822 m_poly.NewOutline();
1823
1824 for( const VECTOR2I& p : aPoints )
1825 m_poly.Append( p.x, p.y );
1826}
1827
1828
1829std::vector<SHAPE*> EDA_SHAPE::makeEffectiveShapes( bool aEdgeOnly, bool aLineChainOnly, bool aHittesting ) const
1830{
1831 std::vector<SHAPE*> effectiveShapes;
1832 int width = GetEffectiveWidth();
1833 bool solidFill = IsSolidFill()
1834 || IsHatchedFill()
1835 || IsProxyItem()
1836 || ( aHittesting && IsFilledForHitTesting() );
1837
1838 if( aEdgeOnly )
1839 solidFill = false;
1840
1841 switch( m_shape )
1842 {
1843 case SHAPE_T::ARC:
1844 effectiveShapes.emplace_back( new SHAPE_ARC( m_arcCenter, m_start, GetArcAngle(), width ) );
1845 break;
1846
1847 case SHAPE_T::SEGMENT:
1848 effectiveShapes.emplace_back( new SHAPE_SEGMENT( m_start, m_end, width ) );
1849 break;
1850
1851 case SHAPE_T::RECTANGLE:
1852 {
1853 if( m_cornerRadius > 0 )
1854 {
1856 SHAPE_POLY_SET poly;
1857 rr.TransformToPolygon( poly, getMaxError() );
1858 SHAPE_LINE_CHAIN outline = poly.Outline( 0 );
1859
1860 if( solidFill )
1861 effectiveShapes.emplace_back( new SHAPE_SIMPLE( outline ) );
1862
1863 if( width > 0 || !solidFill )
1864 {
1865 std::set<size_t> arcsHandled;
1866
1867 for( int ii = 0; ii < outline.SegmentCount(); ++ii )
1868 {
1869 if( outline.IsArcSegment( ii ) )
1870 {
1871 size_t arcIndex = outline.ArcIndex( ii );
1872
1873 if( !arcsHandled.contains( arcIndex ) )
1874 {
1875 arcsHandled.insert( arcIndex );
1876 effectiveShapes.emplace_back( new SHAPE_ARC( outline.Arc( arcIndex ), width ) );
1877 }
1878 }
1879 else
1880 {
1881 effectiveShapes.emplace_back( new SHAPE_SEGMENT( outline.Segment( ii ), width ) );
1882 }
1883 }
1884 }
1885 }
1886 else
1887 {
1888 std::vector<VECTOR2I> pts = GetRectCorners();
1889
1890 if( solidFill )
1891 effectiveShapes.emplace_back( new SHAPE_SIMPLE( pts ) );
1892
1893 if( width > 0 || !solidFill )
1894 {
1895 effectiveShapes.emplace_back( new SHAPE_SEGMENT( pts[0], pts[1], width ) );
1896 effectiveShapes.emplace_back( new SHAPE_SEGMENT( pts[1], pts[2], width ) );
1897 effectiveShapes.emplace_back( new SHAPE_SEGMENT( pts[2], pts[3], width ) );
1898 effectiveShapes.emplace_back( new SHAPE_SEGMENT( pts[3], pts[0], width ) );
1899 }
1900 }
1901 break;
1902 }
1903
1904 case SHAPE_T::CIRCLE:
1905 {
1906 if( solidFill )
1907 effectiveShapes.emplace_back( new SHAPE_CIRCLE( getCenter(), GetRadius() ) );
1908
1909 if( width > 0 || !solidFill )
1910 effectiveShapes.emplace_back( new SHAPE_ARC( getCenter(), GetEnd(), ANGLE_360, width ) );
1911
1912 break;
1913 }
1914
1915 case SHAPE_T::BEZIER:
1916 {
1917 std::vector<VECTOR2I> bezierPoints = buildBezierToSegmentsPointsList( getMaxError() );
1918 VECTOR2I start_pt = bezierPoints[0];
1919
1920 for( unsigned int jj = 1; jj < bezierPoints.size(); jj++ )
1921 {
1922 VECTOR2I end_pt = bezierPoints[jj];
1923 effectiveShapes.emplace_back( new SHAPE_SEGMENT( start_pt, end_pt, width ) );
1924 start_pt = end_pt;
1925 }
1926
1927 break;
1928 }
1929
1930 case SHAPE_T::POLY:
1931 {
1932 if( GetPolyShape().OutlineCount() == 0 ) // malformed/empty polygon
1933 break;
1934
1935 for( int ii = 0; ii < GetPolyShape().OutlineCount(); ++ii )
1936 {
1937 const SHAPE_LINE_CHAIN& l = GetPolyShape().COutline( ii );
1938
1939 if( solidFill )
1940 effectiveShapes.emplace_back( new SHAPE_SIMPLE( l ) );
1941
1942 if( width > 0 || !IsSolidFill() || aEdgeOnly )
1943 {
1944 int segCount = l.SegmentCount();
1945
1946 if( aLineChainOnly && l.IsClosed() )
1947 segCount--; // Treat closed chain as open
1948
1949 for( int jj = 0; jj < segCount; jj++ )
1950 effectiveShapes.emplace_back( new SHAPE_SEGMENT( l.CSegment( jj ), width ) );
1951 }
1952 }
1953 }
1954 break;
1955
1956 default:
1958 break;
1959 }
1960
1961 return effectiveShapes;
1962}
1963
1964
1965std::vector<VECTOR2I> EDA_SHAPE::GetPolyPoints() const
1966{
1967 std::vector<VECTOR2I> points;
1968
1969 for( int ii = 0; ii < m_poly.OutlineCount(); ++ii )
1970 {
1971 const SHAPE_LINE_CHAIN& outline = m_poly.COutline( ii );
1972 int pointCount = outline.PointCount();
1973
1974 if( pointCount )
1975 {
1976 points.reserve( points.size() + pointCount );
1977
1978 for( const VECTOR2I& pt : outline.CPoints() )
1979 points.emplace_back( pt );
1980 }
1981 }
1982
1983 return points;
1984}
1985
1986
1988{
1989 // return true if the polygonal shape is valid (has more than 2 points)
1990 return GetPolyShape().OutlineCount() > 0 && GetPolyShape().Outline( 0 ).PointCount() > 2;
1991}
1992
1993
1995{
1996 // return the number of corners of the polygonal shape
1997 // this shape is expected to be only one polygon without hole
1998 return GetPolyShape().OutlineCount() ? GetPolyShape().VertexCount( 0 ) : 0;
1999}
2000
2001
2002void EDA_SHAPE::beginEdit( const VECTOR2I& aPosition )
2003{
2004 switch( GetShape() )
2005 {
2006 case SHAPE_T::SEGMENT:
2007 case SHAPE_T::CIRCLE:
2008 case SHAPE_T::RECTANGLE:
2009 SetStart( aPosition );
2010 SetEnd( aPosition );
2011 break;
2012
2013 case SHAPE_T::ARC:
2014 SetArcGeometry( aPosition, aPosition, aPosition );
2015 m_editState = 1;
2016 break;
2017
2018 case SHAPE_T::BEZIER:
2019 SetStart( aPosition );
2020 SetEnd( aPosition );
2021 SetBezierC1( aPosition );
2022 SetBezierC2( aPosition );
2023 m_editState = 1;
2024
2026 break;
2027
2028 case SHAPE_T::POLY:
2029 m_poly.NewOutline();
2030 m_poly.Outline( 0 ).SetClosed( false );
2031
2032 // Start and end of the first segment (co-located for now)
2033 m_poly.Outline( 0 ).Append( aPosition );
2034 m_poly.Outline( 0 ).Append( aPosition, true );
2035 break;
2036
2037 default:
2039 }
2040}
2041
2042
2043bool EDA_SHAPE::continueEdit( const VECTOR2I& aPosition )
2044{
2045 switch( GetShape() )
2046 {
2047 case SHAPE_T::ARC:
2048 case SHAPE_T::SEGMENT:
2049 case SHAPE_T::CIRCLE:
2050 case SHAPE_T::RECTANGLE:
2051 return false;
2052
2053 case SHAPE_T::BEZIER:
2054 if( m_editState == 3 )
2055 return false;
2056
2057 m_editState++;
2058 return true;
2059
2060 case SHAPE_T::POLY:
2061 {
2062 SHAPE_LINE_CHAIN& poly = m_poly.Outline( 0 );
2063
2064 // do not add zero-length segments
2065 if( poly.CPoint( (int) poly.GetPointCount() - 2 ) != poly.CLastPoint() )
2066 poly.Append( aPosition, true );
2067 }
2068 return true;
2069
2070 default:
2072 return false;
2073 }
2074}
2075
2076
2077void EDA_SHAPE::calcEdit( const VECTOR2I& aPosition )
2078{
2079#define sq( x ) pow( x, 2 )
2080
2081 switch( GetShape() )
2082 {
2083 case SHAPE_T::SEGMENT:
2084 case SHAPE_T::CIRCLE:
2085 case SHAPE_T::RECTANGLE:
2086 SetEnd( aPosition );
2087 break;
2088
2089 case SHAPE_T::BEZIER:
2090 {
2091 switch( m_editState )
2092 {
2093 case 0:
2094 SetStart( aPosition );
2095 SetEnd( aPosition );
2096 SetBezierC1( aPosition );
2097 SetBezierC2( aPosition );
2098 break;
2099
2100 case 1:
2101 SetBezierC2( aPosition );
2102 SetEnd( aPosition );
2103 break;
2104
2105 case 2:
2106 SetBezierC1( aPosition );
2107 break;
2108
2109 case 3:
2110 SetBezierC2( aPosition );
2111 break;
2112 }
2113
2115 }
2116 break;
2117
2118 case SHAPE_T::ARC:
2119 {
2120 double radius = GetRadius();
2121 EDA_ANGLE lastAngle = GetArcAngle();
2122
2123 // Edit state 0: drawing: place start
2124 // Edit state 1: drawing: place end (center calculated for 90-degree subtended angle)
2125 // Edit state 2: point edit: move start (center calculated for invariant subtended angle)
2126 // Edit state 3: point edit: move end (center calculated for invariant subtended angle)
2127 // Edit state 4: point edit: move center
2128 // Edit state 5: point edit: move arc-mid-point
2129
2130 switch( m_editState )
2131 {
2132 case 0:
2133 SetArcGeometry( aPosition, aPosition, aPosition );
2134 return;
2135
2136 case 1:
2137 m_end = aPosition;
2138 radius = m_start.Distance( m_end ) * M_SQRT1_2;
2139 break;
2140
2141 case 2:
2142 case 3:
2143 {
2144 VECTOR2I v = m_start - m_end;
2145 double chordBefore = v.SquaredEuclideanNorm();
2146
2147 if( m_editState == 2 )
2148 m_start = aPosition;
2149 else
2150 m_end = aPosition;
2151
2152 v = m_start - m_end;
2153
2154 double chordAfter = v.SquaredEuclideanNorm();
2155 double ratio = 0.0;
2156
2157 if( chordBefore > 0 )
2158 ratio = chordAfter / chordBefore;
2159
2160 if( ratio != 0 )
2161 radius = std::max( sqrt( sq( radius ) * ratio ), sqrt( chordAfter ) / 2 );
2162 break;
2163 }
2164
2165 case 4:
2166 {
2167 double radialA = m_start.Distance( aPosition );
2168 double radialB = m_end.Distance( aPosition );
2169 radius = ( radialA + radialB ) / 2.0;
2170 break;
2171 }
2172
2173 case 5:
2174 SetArcGeometry( GetStart(), aPosition, GetEnd() );
2175 return;
2176 }
2177
2178 // Calculate center based on start, end, and radius
2179 //
2180 // Let 'l' be the length of the chord and 'm' the middle point of the chord
2181 double l = m_start.Distance( m_end );
2182 VECTOR2D m = ( m_start + m_end ) / 2;
2183 double sqRadDiff = ( radius * radius ) - ( l * l ) / 4.0;
2184
2185 // Calculate 'd', the vector from the chord midpoint to the center
2186 VECTOR2D d;
2187
2188 if( l > 0 && sqRadDiff >= 0 )
2189 {
2190 d.x = sqrt( sqRadDiff ) * ( m_start.y - m_end.y ) / l;
2191 d.y = sqrt( sqRadDiff ) * ( m_end.x - m_start.x ) / l;
2192 }
2193
2194 VECTOR2I c1 = KiROUND( m + d );
2195 VECTOR2I c2 = KiROUND( m - d );
2196
2197 // Solution gives us 2 centers; we need to pick one:
2198 switch( m_editState )
2199 {
2200 case 1:
2201 // Keep arc clockwise while drawing i.e. arc angle = 90 deg.
2202 // it can be 90 or 270 deg depending on the arc center choice (c1 or c2)
2203 m_arcCenter = c1; // first trial
2204
2205 if( GetArcAngle() > ANGLE_180 )
2206 m_arcCenter = c2;
2207
2208 break;
2209
2210 case 2:
2211 case 3:
2212 // Pick the one of c1, c2 to keep arc on the same side
2213 m_arcCenter = c1; // first trial
2214
2215 if( ( lastAngle < ANGLE_180 ) != ( GetArcAngle() < ANGLE_180 ) )
2216 m_arcCenter = c2;
2217
2218 break;
2219
2220 case 4:
2221 // Pick the one closer to the mouse position
2222 m_arcCenter = c1.Distance( aPosition ) < c2.Distance( aPosition ) ? c1 : c2;
2223 break;
2224 }
2225
2226 break;
2227 }
2228
2229 case SHAPE_T::POLY:
2230 m_poly.Outline( 0 ).SetPoint( m_poly.Outline( 0 ).GetPointCount() - 1, aPosition );
2231 break;
2232
2233 default:
2235 }
2236}
2237
2238
2239void EDA_SHAPE::endEdit( bool aClosed )
2240{
2241 switch( GetShape() )
2242 {
2243 case SHAPE_T::ARC:
2244 case SHAPE_T::SEGMENT:
2245 case SHAPE_T::CIRCLE:
2246 case SHAPE_T::RECTANGLE:
2247 case SHAPE_T::BEZIER:
2248 break;
2249
2250 case SHAPE_T::POLY:
2251 {
2252 SHAPE_LINE_CHAIN& poly = m_poly.Outline( 0 );
2253
2254 // do not include last point twice
2255 if( poly.GetPointCount() > 2 )
2256 {
2257 if( poly.CPoint( poly.GetPointCount() - 2 ) == poly.CLastPoint() )
2258 {
2259 poly.SetClosed( aClosed );
2260 }
2261 else
2262 {
2263 poly.SetClosed( false );
2264 poly.Remove( poly.GetPointCount() - 1 );
2265 }
2266 }
2267
2268 break;
2269 }
2270
2271 default:
2273 }
2274}
2275
2276
2278{
2279 EDA_SHAPE* image = dynamic_cast<EDA_SHAPE*>( aImage );
2280 assert( image );
2281
2282 #define SWAPITEM( x ) std::swap( x, image->x )
2283 SWAPITEM( m_stroke );
2284 SWAPITEM( m_start );
2285 SWAPITEM( m_end );
2287 SWAPITEM( m_shape );
2291 SWAPITEM( m_poly );
2293 SWAPITEM( m_fill );
2297 #undef SWAPITEM
2298
2299 m_hatchingDirty = true;
2300}
2301
2302
2303int EDA_SHAPE::Compare( const EDA_SHAPE* aOther ) const
2304{
2305#define EPSILON 2 // Should be enough for rounding errors on calculated items
2306
2307#define TEST( a, b ) { if( a != b ) return a - b; }
2308#define TEST_E( a, b ) { if( abs( a - b ) > EPSILON ) return a - b; }
2309#define TEST_PT( a, b ) { TEST_E( a.x, b.x ); TEST_E( a.y, b.y ); }
2310
2311 TEST_PT( m_start, aOther->m_start );
2312 TEST_PT( m_end, aOther->m_end );
2313
2314 TEST( (int) m_shape, (int) aOther->m_shape );
2315
2317 {
2319 }
2320 else if( m_shape == SHAPE_T::ARC )
2321 {
2322 TEST_PT( GetArcMid(), aOther->GetArcMid() );
2323 }
2324 else if( m_shape == SHAPE_T::BEZIER )
2325 {
2326 TEST_PT( m_bezierC1, aOther->m_bezierC1 );
2327 TEST_PT( m_bezierC2, aOther->m_bezierC2 );
2328 }
2329 else if( m_shape == SHAPE_T::POLY )
2330 {
2331 TEST( m_poly.TotalVertices(), aOther->m_poly.TotalVertices() );
2332 }
2333
2334 for( size_t ii = 0; ii < m_bezierPoints.size(); ++ii )
2335 TEST_PT( m_bezierPoints[ii], aOther->m_bezierPoints[ii] );
2336
2337 for( int ii = 0; ii < m_poly.TotalVertices(); ++ii )
2338 TEST_PT( m_poly.CVertex( ii ), aOther->m_poly.CVertex( ii ) );
2339
2340 TEST_E( m_stroke.GetWidth(), aOther->m_stroke.GetWidth() );
2341 TEST( (int) m_stroke.GetLineStyle(), (int) aOther->m_stroke.GetLineStyle() );
2342 TEST( (int) m_fill, (int) aOther->m_fill );
2343
2344 return 0;
2345}
2346
2347
2348void EDA_SHAPE::TransformShapeToPolygon( SHAPE_POLY_SET& aBuffer, int aClearance, int aError,
2349 ERROR_LOC aErrorLoc, bool ignoreLineWidth, bool includeFill ) const
2350{
2351 bool solidFill = IsSolidFill() || ( IsHatchedFill() && !includeFill ) || IsProxyItem();
2352 int width = ignoreLineWidth ? 0 : GetWidth();
2353
2354 width += 2 * aClearance;
2355
2356 switch( m_shape )
2357 {
2358 case SHAPE_T::CIRCLE:
2359 {
2360 int r = GetRadius();
2361
2362 if( solidFill )
2363 TransformCircleToPolygon( aBuffer, getCenter(), r + width / 2, aError, aErrorLoc );
2364 else
2365 TransformRingToPolygon( aBuffer, getCenter(), r, width, aError, aErrorLoc );
2366
2367 break;
2368 }
2369
2370 case SHAPE_T::RECTANGLE:
2371 {
2372 if( GetCornerRadius() > 0 )
2373 {
2374 // Use specialized function for rounded rectangles
2376 VECTOR2I position = GetStart() + size / 2; // Center position
2377
2378 if( solidFill )
2379 {
2381 0.0, 0, width / 2, aError, aErrorLoc );
2382 }
2383 else
2384 {
2385 // Export outline as a set of thick segments:
2386 SHAPE_POLY_SET poly;
2388 0.0, 0, 0, aError, aErrorLoc );
2389 SHAPE_LINE_CHAIN& outline = poly.Outline( 0 );
2390 outline.SetClosed( true );
2391
2392 for( int ii = 0; ii < outline.PointCount(); ii++ )
2393 {
2394 TransformOvalToPolygon( aBuffer, outline.CPoint( ii ), outline.CPoint( ii+1 ), width,
2395 aError, aErrorLoc );
2396 }
2397 }
2398 }
2399 else
2400 {
2401 std::vector<VECTOR2I> pts = GetRectCorners();
2402
2403 if( solidFill )
2404 {
2405 aBuffer.NewOutline();
2406
2407 for( const VECTOR2I& pt : pts )
2408 aBuffer.Append( pt );
2409 }
2410
2411 if( width > 0 || !solidFill )
2412 {
2413 // Add in segments
2414 TransformOvalToPolygon( aBuffer, pts[0], pts[1], width, aError, aErrorLoc );
2415 TransformOvalToPolygon( aBuffer, pts[1], pts[2], width, aError, aErrorLoc );
2416 TransformOvalToPolygon( aBuffer, pts[2], pts[3], width, aError, aErrorLoc );
2417 TransformOvalToPolygon( aBuffer, pts[3], pts[0], width, aError, aErrorLoc );
2418 }
2419 }
2420
2421 break;
2422 }
2423
2424 case SHAPE_T::ARC:
2425 TransformArcToPolygon( aBuffer, GetStart(), GetArcMid(), GetEnd(), width, aError, aErrorLoc );
2426 break;
2427
2428 case SHAPE_T::SEGMENT:
2429 TransformOvalToPolygon( aBuffer, GetStart(), GetEnd(), width, aError, aErrorLoc );
2430 break;
2431
2432 case SHAPE_T::POLY:
2433 {
2434 if( !IsPolyShapeValid() )
2435 break;
2436
2437 if( solidFill )
2438 {
2439 for( int ii = 0; ii < m_poly.OutlineCount(); ++ii )
2440 {
2441 const SHAPE_LINE_CHAIN& poly = m_poly.Outline( ii );
2442 SHAPE_POLY_SET tmp;
2443 tmp.NewOutline();
2444
2445 for( int jj = 0; jj < (int) poly.GetPointCount(); ++jj )
2446 tmp.Append( poly.GetPoint( jj ) );
2447
2448 if( width > 0 )
2449 {
2450 int inflate = width / 2;
2451
2452 if( aErrorLoc == ERROR_OUTSIDE )
2453 inflate += aError;
2454
2455 tmp.Inflate( inflate, CORNER_STRATEGY::ROUND_ALL_CORNERS, aError );
2456 }
2457
2458 aBuffer.Append( tmp );
2459 }
2460 }
2461 else
2462 {
2463 for( int ii = 0; ii < m_poly.OutlineCount(); ++ii )
2464 {
2465 const SHAPE_LINE_CHAIN& poly = m_poly.Outline( ii );
2466
2467 for( int jj = 0; jj < (int) poly.SegmentCount(); ++jj )
2468 {
2469 const SEG& seg = poly.GetSegment( jj );
2470 TransformOvalToPolygon( aBuffer, seg.A, seg.B, width, aError, aErrorLoc );
2471 }
2472 }
2473 }
2474
2475 break;
2476 }
2477
2478 case SHAPE_T::BEZIER:
2479 {
2480 std::vector<VECTOR2I> ctrlPts = { GetStart(), GetBezierC1(), GetBezierC2(), GetEnd() };
2481 BEZIER_POLY converter( ctrlPts );
2482 std::vector<VECTOR2I> poly;
2483 converter.GetPoly( poly, aError );
2484
2485 for( unsigned ii = 1; ii < poly.size(); ii++ )
2486 TransformOvalToPolygon( aBuffer, poly[ii - 1], poly[ii], width, aError, aErrorLoc );
2487
2488 break;
2489 }
2490
2491 default:
2493 break;
2494 }
2495
2496 if( IsHatchedFill() && includeFill )
2497 {
2498 for( int ii = 0; ii < GetHatching().OutlineCount(); ++ii )
2499 aBuffer.AddOutline( GetHatching().COutline( ii ) );
2500 }
2501}
2502
2503
2504void EDA_SHAPE::SetWidth( int aWidth )
2505{
2506 m_stroke.SetWidth( aWidth );
2507 m_hatchingDirty = true;
2508}
2509
2510
2512{
2513 m_stroke.SetLineStyle( aStyle );
2514}
2515
2516
2518{
2519 if( m_stroke.GetLineStyle() != LINE_STYLE::DEFAULT )
2520 return m_stroke.GetLineStyle();
2521
2522 return LINE_STYLE::SOLID;
2523}
2524
2525
2526bool EDA_SHAPE::operator==( const EDA_SHAPE& aOther ) const
2527{
2528 if( GetShape() != aOther.GetShape() )
2529 return false;
2530
2531 if( m_fill != aOther.m_fill )
2532 return false;
2533
2534 if( m_stroke.GetWidth() != aOther.m_stroke.GetWidth() )
2535 return false;
2536
2537 if( m_stroke.GetLineStyle() != aOther.m_stroke.GetLineStyle() )
2538 return false;
2539
2540 if( m_fillColor != aOther.m_fillColor )
2541 return false;
2542
2543 if( m_start != aOther.m_start )
2544 return false;
2545
2546 if( m_end != aOther.m_end )
2547 return false;
2548
2549 if( m_arcCenter != aOther.m_arcCenter )
2550 return false;
2551
2552 if( m_bezierC1 != aOther.m_bezierC1 )
2553 return false;
2554
2555 if( m_bezierC2 != aOther.m_bezierC2 )
2556 return false;
2557
2558 if( m_bezierPoints != aOther.m_bezierPoints )
2559 return false;
2560
2561 for( int ii = 0; ii < m_poly.TotalVertices(); ++ii )
2562 {
2563 if( m_poly.CVertex( ii ) != aOther.m_poly.CVertex( ii ) )
2564 return false;
2565 }
2566
2567 return true;
2568}
2569
2570
2571double EDA_SHAPE::Similarity( const EDA_SHAPE& aOther ) const
2572{
2573 if( GetShape() != aOther.GetShape() )
2574 return 0.0;
2575
2576 double similarity = 1.0;
2577
2578 if( m_fill != aOther.m_fill )
2579 similarity *= 0.9;
2580
2581 if( m_stroke.GetWidth() != aOther.m_stroke.GetWidth() )
2582 similarity *= 0.9;
2583
2584 if( m_stroke.GetLineStyle() != aOther.m_stroke.GetLineStyle() )
2585 similarity *= 0.9;
2586
2587 if( m_fillColor != aOther.m_fillColor )
2588 similarity *= 0.9;
2589
2590 if( m_start != aOther.m_start )
2591 similarity *= 0.9;
2592
2593 if( m_end != aOther.m_end )
2594 similarity *= 0.9;
2595
2596 if( m_arcCenter != aOther.m_arcCenter )
2597 similarity *= 0.9;
2598
2599 if( m_bezierC1 != aOther.m_bezierC1 )
2600 similarity *= 0.9;
2601
2602 if( m_bezierC2 != aOther.m_bezierC2 )
2603 similarity *= 0.9;
2604
2605 {
2606 int m = m_bezierPoints.size();
2607 int n = aOther.m_bezierPoints.size();
2608
2609 size_t longest = alg::longest_common_subset( m_bezierPoints, aOther.m_bezierPoints );
2610
2611 similarity *= std::pow( 0.9, m + n - 2 * longest );
2612 }
2613
2614 {
2615 int m = m_poly.TotalVertices();
2616 int n = aOther.m_poly.TotalVertices();
2617 std::vector<VECTOR2I> poly;
2618 std::vector<VECTOR2I> otherPoly;
2619 VECTOR2I lastPt( 0, 0 );
2620
2621 // We look for the longest common subset of the two polygons, but we need to
2622 // offset each point because we're actually looking for overall similarity, not just
2623 // exact matches. So if the zone is moved by 1IU, we only want one point to be
2624 // considered "moved" rather than the entire polygon. In this case, the first point
2625 // will not be a match but the rest of the sequence will.
2626 for( int ii = 0; ii < m; ++ii )
2627 {
2628 poly.emplace_back( lastPt - m_poly.CVertex( ii ) );
2629 lastPt = m_poly.CVertex( ii );
2630 }
2631
2632 lastPt = VECTOR2I( 0, 0 );
2633
2634 for( int ii = 0; ii < n; ++ii )
2635 {
2636 otherPoly.emplace_back( lastPt - aOther.m_poly.CVertex( ii ) );
2637 lastPt = aOther.m_poly.CVertex( ii );
2638 }
2639
2640 size_t longest = alg::longest_common_subset( poly, otherPoly );
2641
2642 similarity *= std::pow( 0.9, m + n - 2 * longest );
2643 }
2644
2645 return similarity;
2646}
2647
2648
2652
2653
2654static struct EDA_SHAPE_DESC
2655{
2657 {
2659 .Map( SHAPE_T::SEGMENT, _HKI( "Segment" ) )
2660 .Map( SHAPE_T::RECTANGLE, _HKI( "Rectangle" ) )
2661 .Map( SHAPE_T::ARC, _HKI( "Arc" ) )
2662 .Map( SHAPE_T::CIRCLE, _HKI( "Circle" ) )
2663 .Map( SHAPE_T::POLY, _HKI( "Polygon" ) )
2664 .Map( SHAPE_T::BEZIER, _HKI( "Bezier" ) );
2665
2667
2668 if( lineStyleEnum.Choices().GetCount() == 0 )
2669 {
2670 lineStyleEnum.Map( LINE_STYLE::SOLID, _HKI( "Solid" ) )
2671 .Map( LINE_STYLE::DASH, _HKI( "Dashed" ) )
2672 .Map( LINE_STYLE::DOT, _HKI( "Dotted" ) )
2673 .Map( LINE_STYLE::DASHDOT, _HKI( "Dash-Dot" ) )
2674 .Map( LINE_STYLE::DASHDOTDOT, _HKI( "Dash-Dot-Dot" ) );
2675 }
2676
2678
2679 if( hatchModeEnum.Choices().GetCount() == 0 )
2680 {
2681 hatchModeEnum.Map( UI_FILL_MODE::NONE, _HKI( "None" ) );
2682 hatchModeEnum.Map( UI_FILL_MODE::SOLID, _HKI( "Solid" ) );
2683 hatchModeEnum.Map( UI_FILL_MODE::HATCH, _HKI( "Hatch" ) );
2684 hatchModeEnum.Map( UI_FILL_MODE::REVERSE_HATCH, _HKI( "Reverse Hatch" ) );
2685 hatchModeEnum.Map( UI_FILL_MODE::CROSS_HATCH, _HKI( "Cross-hatch" ) );
2686 }
2687
2690
2691 auto isNotPolygonOrCircle =
2692 []( INSPECTABLE* aItem ) -> bool
2693 {
2694 // Polygons, unlike other shapes, have no meaningful start or end coordinates
2695 if( EDA_SHAPE* shape = dynamic_cast<EDA_SHAPE*>( aItem ) )
2696 return shape->GetShape() != SHAPE_T::POLY && shape->GetShape() != SHAPE_T::CIRCLE;
2697
2698 return false;
2699 };
2700
2701 auto isCircle =
2702 []( INSPECTABLE* aItem ) -> bool
2703 {
2704 // Polygons, unlike other shapes, have no meaningful start or end coordinates
2705 if( EDA_SHAPE* shape = dynamic_cast<EDA_SHAPE*>( aItem ) )
2706 return shape->GetShape() == SHAPE_T::CIRCLE;
2707
2708 return false;
2709 };
2710
2711 auto isRectangle =
2712 []( INSPECTABLE* aItem ) -> bool
2713 {
2714 // Polygons, unlike other shapes, have no meaningful start or end coordinates
2715 if( EDA_SHAPE* shape = dynamic_cast<EDA_SHAPE*>( aItem ) )
2716 return shape->GetShape() == SHAPE_T::RECTANGLE;
2717
2718 return false;
2719 };
2720
2721 const wxString shapeProps = _HKI( "Shape Properties" );
2722
2723 auto shape = new PROPERTY_ENUM<EDA_SHAPE, SHAPE_T>( _HKI( "Shape" ),
2725 propMgr.AddProperty( shape, shapeProps );
2726
2727 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "Start X" ),
2730 shapeProps )
2731 .SetAvailableFunc( isNotPolygonOrCircle );
2732 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "Start Y" ),
2735 shapeProps )
2736 .SetAvailableFunc( isNotPolygonOrCircle );
2737
2738 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "Center X" ),
2741 shapeProps )
2742 .SetAvailableFunc( isCircle );
2743
2744 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "Center Y" ),
2747 shapeProps )
2748 .SetAvailableFunc( isCircle );
2749
2750 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "Radius" ),
2753 shapeProps )
2754 .SetAvailableFunc( isCircle );
2755
2756 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "End X" ),
2759 shapeProps )
2760 .SetAvailableFunc( isNotPolygonOrCircle );
2761
2762 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "End Y" ),
2765 shapeProps )
2766 .SetAvailableFunc( isNotPolygonOrCircle );
2767
2768 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "Width" ),
2771 shapeProps )
2772 .SetAvailableFunc( isRectangle );
2773
2774 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "Height" ),
2777 shapeProps )
2778 .SetAvailableFunc( isRectangle );
2779
2780 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "Corner Radius" ),
2783 shapeProps )
2784 .SetAvailableFunc( isRectangle )
2785 .SetValidator( []( const wxAny&& aValue, EDA_ITEM* aItem ) -> VALIDATOR_RESULT
2786 {
2787 wxASSERT_MSG( aValue.CheckType<int>(),
2788 "Expecting int-containing value" );
2789
2790 int radius = aValue.As<int>();
2791
2792 EDA_SHAPE* prop_shape = dynamic_cast<EDA_SHAPE*>( aItem );
2793
2794 if( !prop_shape )
2795 return std::nullopt;
2796
2797 int maxRadius = std::min( prop_shape->GetRectangleWidth(),
2798 prop_shape->GetRectangleHeight() ) / 2;
2799
2800 if( radius > maxRadius )
2801 return std::make_unique<VALIDATION_ERROR_TOO_LARGE<int>>( radius, maxRadius );
2802 else if( radius < 0 )
2803 return std::make_unique<VALIDATION_ERROR_TOO_SMALL<int>>( radius, 0 );
2804
2805 return std::nullopt;
2806 } );
2807
2808 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "Line Width" ),
2810 shapeProps );
2811
2812 propMgr.AddProperty( new PROPERTY_ENUM<EDA_SHAPE, LINE_STYLE>( _HKI( "Line Style" ),
2814 shapeProps );
2815
2816 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, COLOR4D>( _HKI( "Line Color" ),
2818 shapeProps )
2820
2821 auto angle = new PROPERTY<EDA_SHAPE, EDA_ANGLE>( _HKI( "Angle" ),
2824 angle->SetAvailableFunc(
2825 [=]( INSPECTABLE* aItem ) -> bool
2826 {
2827 if( EDA_SHAPE* curr_shape = dynamic_cast<EDA_SHAPE*>( aItem ) )
2828 return curr_shape->GetShape() == SHAPE_T::ARC;
2829
2830 return false;
2831 } );
2832 propMgr.AddProperty( angle, shapeProps );
2833
2834 auto fillAvailable =
2835 [=]( INSPECTABLE* aItem ) -> bool
2836 {
2837 if( EDA_ITEM* edaItem = dynamic_cast<EDA_ITEM*>( aItem ) )
2838 {
2839 // For some reason masking "Filled" and "Fill Color" at the
2840 // PCB_TABLECELL level doesn't work.
2841 if( edaItem->Type() == PCB_TABLECELL_T )
2842 return false;
2843 }
2844
2845 if( EDA_SHAPE* edaShape = dynamic_cast<EDA_SHAPE*>( aItem ) )
2846 {
2847 switch( edaShape->GetShape() )
2848 {
2849 case SHAPE_T::POLY:
2850 case SHAPE_T::RECTANGLE:
2851 case SHAPE_T::CIRCLE:
2852 case SHAPE_T::BEZIER:
2853 return true;
2854
2855 default:
2856 return false;
2857 }
2858 }
2859
2860 return false;
2861 };
2862
2865 shapeProps )
2866 .SetAvailableFunc( fillAvailable );
2867
2868 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, COLOR4D>( _HKI( "Fill Color" ),
2870 shapeProps )
2871 .SetAvailableFunc( fillAvailable )
2873 }
ERROR_LOC
When approximating an arc or circle, should the error be placed on the outside or inside of the curve...
@ ERROR_OUTSIDE
@ ERROR_INSIDE
BOX2< VECTOR2I > BOX2I
Definition box2.h:922
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:990
Bezier curves to polygon converter.
void GetPoly(std::vector< VECTOR2I > &aOutput, int aMaxError=10)
Convert a Bezier curve to a polygon.
constexpr BOX2< Vec > & Inflate(coord_type dx, coord_type dy)
Inflates the rectangle horizontally by dx and vertically by dy.
Definition box2.h:558
constexpr void SetOrigin(const Vec &pos)
Definition box2.h:237
constexpr BOX2< Vec > & Normalize()
Ensure that the height and width are positive.
Definition box2.h:146
constexpr coord_type GetY() const
Definition box2.h:208
constexpr size_type GetWidth() const
Definition box2.h:214
constexpr Vec Centre() const
Definition box2.h:97
constexpr coord_type GetX() const
Definition box2.h:207
bool IntersectsCircleEdge(const Vec &aCenter, const int aRadius, const int aWidth) const
Definition box2.h:523
constexpr BOX2< Vec > & Merge(const BOX2< Vec > &aRect)
Modify the position and size of the rectangle in order to contain aRect.
Definition box2.h:658
constexpr size_type GetHeight() const
Definition box2.h:215
constexpr coord_type GetLeft() const
Definition box2.h:228
constexpr bool Contains(const Vec &aPoint) const
Definition box2.h:168
constexpr coord_type GetRight() const
Definition box2.h:217
constexpr void SetEnd(coord_type x, coord_type y)
Definition box2.h:297
constexpr coord_type GetTop() const
Definition box2.h:229
constexpr bool Intersects(const BOX2< Vec > &aRect) const
Definition box2.h:311
constexpr coord_type GetBottom() const
Definition box2.h:222
EDA_ANGLE Normalize()
Definition eda_angle.h:229
bool IsCardinal() const
Definition eda_angle.cpp:40
EDA_ANGLE Normalize720()
Definition eda_angle.h:279
double AsRadians() const
Definition eda_angle.h:120
The base class for create windows for drawing purpose.
A base class for most all the KiCad significant classes used in schematics and boards.
Definition eda_item.h:98
UI_FILL_MODE GetFillModeProp() const
virtual int GetHatchLineSpacing() const
Definition eda_shape.h:160
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:192
bool m_proxyItem
Definition eda_shape.h:520
int m_cornerRadius
Definition eda_shape.h:505
bool m_hatchingDirty
Definition eda_shape.h:501
bool m_endsSwapped
Definition eda_shape.h:493
const VECTOR2I & GetBezierC2() const
Definition eda_shape.h:259
void SetBezierC2(const VECTOR2I &aPt)
Definition eda_shape.h:258
void move(const VECTOR2I &aMoveVector)
void SetCenter(const VECTOR2I &aCenter)
VECTOR2I getCenter() const
int GetStartY() const
Definition eda_shape.h:175
void SetFillModeProp(UI_FILL_MODE)
int m_editState
Definition eda_shape.h:519
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:503
void SetEndY(int aY)
Definition eda_shape.h:227
virtual int GetEffectiveWidth() const
Definition eda_shape.h:158
std::vector< VECTOR2I > GetPolyPoints() const
Duplicate the polygon outlines into a flat list of VECTOR2I points.
COLOR4D GetLineColor() const
Definition eda_shape.h:166
int GetEndX() const
Definition eda_shape.h:218
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:185
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:199
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:169
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
std::vector< SEG > m_hatchLines
Definition eda_shape.h:500
VECTOR2I m_arcCenter
Definition eda_shape.h:510
void SetCenterX(int x)
Definition eda_shape.h:206
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:154
int GetEndY() const
Definition eda_shape.h:217
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:234
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:159
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:216
bool IsClosed() const
void SetRadius(int aX)
Definition eda_shape.h:241
void SetStart(const VECTOR2I &aStart)
Definition eda_shape.h:178
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:174
void SetLineColor(const COLOR4D &aColor)
Definition eda_shape.h:165
COLOR4D GetFillColor() const
Definition eda_shape.h:153
void SetRectangle(const long long int &aHeight, const long long int &aWidth)
void SetShape(SHAPE_T aShape)
Definition eda_shape.h:168
void SwapShape(EDA_SHAPE *aImage)
std::vector< VECTOR2I > GetRectCorners() const
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:220
void SetRectangleWidth(const int &aWidth)
void SetBezierC1(const VECTOR2I &aPt)
Definition eda_shape.h:255
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:176
double Similarity(const EDA_SHAPE &aOther) const
const VECTOR2I & GetBezierC1() const
Definition eda_shape.h:256
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:157
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:513
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:504
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:727
static ENUM_MAP< T > & Instance()
Definition property.h:721
wxPGChoices & Choices()
Definition property.h:770
Class that other classes need to inherit from, in order to be inspectable.
Definition inspectable.h:37
A color representation with 4 components: red, green, blue, alpha.
Definition color4d.h:105
PROPERTY_BASE & SetAvailableFunc(std::function< bool(INSPECTABLE *)> aFunc)
Set a callback function to determine whether an object provides this property.
Definition property.h:262
PROPERTY_BASE & SetValidator(PROPERTY_VALIDATOR_FN &&aValidator)
Definition property.h:349
PROPERTY_BASE & SetIsHiddenFromRulesEditor(bool aHide=true)
Definition property.h:326
Provide class metadata.Helper macro to map type hashes to names.
static PROPERTY_MANAGER & Instance()
PROPERTY_BASE & AddProperty(PROPERTY_BASE *aProperty, const wxString &aGroup=wxEmptyString)
Register a property.
A round rectangle shape, based on a rectangle and a radius.
Definition roundrect.h:36
void TransformToPolygon(SHAPE_POLY_SET &aBuffer, int aMaxError) const
Get the polygonal representation of the roundrect.
Definition roundrect.cpp:83
Definition seg.h:42
VECTOR2I A
Definition seg.h:49
VECTOR2I B
Definition seg.h:50
SHAPE_TYPE Type() const
Return the type of the shape.
Definition shape.h:98
Represent a polyline containing arcs as well as line segments: A chain of connected line and/or arc s...
void Move(const VECTOR2I &aVector) override
const SHAPE_ARC & Arc(size_t aArc) const
bool IsClosed() const override
virtual const VECTOR2I GetPoint(int aIndex) const override
void SetClosed(bool aClosed)
Mark the line chain as closed (i.e.
int PointCount() const
Return the number of points (vertices) in this line chain.
ssize_t ArcIndex(size_t aSegment) const
Return the arc index for the given segment index.
SEG Segment(int aIndex) const
Return a copy of the aIndex-th segment in the line chain.
virtual size_t GetPointCount() const override
void Append(int aX, int aY, bool aAllowDuplication=false)
Append a new point at the end of the line chain.
virtual const SEG GetSegment(int aIndex) const override
const VECTOR2I & CPoint(int aIndex) const
Return a reference to a given point in the line chain.
int SegmentCount() const
Return the number of segments in this line chain.
const VECTOR2I & CLastPoint() const
Return the last point in the line chain.
void Remove(int aStartIndex, int aEndIndex)
Remove the range of points [start_index, end_index] from the line chain.
const SEG CSegment(int aIndex) const
Return a constant copy of the aIndex segment in the line chain.
bool IsArcSegment(size_t aSegment) const
const std::vector< VECTOR2I > & CPoints() const
Represent a set of closed polygons.
bool CollideEdge(const VECTOR2I &aPoint, VERTEX_INDEX *aClosestVertex=nullptr, int aClearance=0) const
Check whether aPoint collides with any edge of any of the contours of the polygon.
int AddOutline(const SHAPE_LINE_CHAIN &aOutline)
Adds a new outline to the set and returns its index.
int VertexCount(int aOutline=-1, int aHole=-1) const
Return the number of vertices in a given outline/hole.
bool Collide(const SHAPE *aShape, int aClearance=0, int *aActual=nullptr, VECTOR2I *aLocation=nullptr) const override
Check if the boundary of shape (this) lies closer to the shape aShape than aClearance,...
int TotalVertices() const
Return total number of vertices stored in the set.
void Inflate(int aAmount, CORNER_STRATEGY aCornerStrategy, int aMaxError, bool aSimplify=false)
Perform outline inflation/deflation.
int Append(int x, int y, int aOutline=-1, int aHole=-1, bool aAllowDuplication=false)
Appends a vertex at the end of the given outline/hole (default: the last outline)
const std::vector< SEG > GenerateHatchLines(const std::vector< double > &aSlopes, int aSpacing, int aLineLength) const
SHAPE_LINE_CHAIN & Outline(int aIndex)
Return the reference to aIndex-th outline in the set.
int NewOutline()
Creates a new empty polygon in the set and returns its index.
const VECTOR2I & CVertex(int aIndex, int aOutline, int aHole) const
Return the index-th vertex in a given hole outline within a given outline.
int OutlineCount() const
Return the number of outlines in the set.
SHAPE_POLY_SET CloneDropTriangulation() const
const SHAPE_LINE_CHAIN & COutline(int aIndex) const
const BOX2I BBox(int aClearance=0) const override
Compute a bounding box of the shape, with a margin of aClearance a collision.
Represent a simple polygon consisting of a zero-thickness closed chain of connected line segments.
An abstract shape on 2D plane.
Definition shape.h:126
int GetWidth() const
LINE_STYLE GetLineStyle() const
wxString MessageTextFromValue(double aValue, bool aAddUnitLabel=true, EDA_DATA_TYPE aType=EDA_DATA_TYPE::DISTANCE) const
A lower-precision version of StringFromValue().
double Distance(const VECTOR2< extended_type > &aVector) const
Compute the distance between two vectors.
Definition vector2d.h:561
constexpr extended_type SquaredEuclideanNorm() const
Compute the squared euclidean norm of the vector, which is defined as (x ** 2 + y ** 2).
Definition vector2d.h:307
T EuclideanNorm() const
Compute the Euclidean norm of the vector, which is defined as sqrt(x ** 2 + y ** 2).
Definition vector2d.h:283
void TransformRingToPolygon(SHAPE_POLY_SET &aBuffer, const VECTOR2I &aCentre, int aRadius, int aWidth, int aError, ERROR_LOC aErrorLoc)
Convert arcs to multiple straight segments.
void TransformCircleToPolygon(SHAPE_LINE_CHAIN &aBuffer, const VECTOR2I &aCenter, int aRadius, int aError, ERROR_LOC aErrorLoc, int aMinSegCount=0)
Convert a circle to a polygon, using multiple straight lines.
void TransformArcToPolygon(SHAPE_POLY_SET &aBuffer, const VECTOR2I &aStart, const VECTOR2I &aMid, const VECTOR2I &aEnd, int aWidth, int aError, ERROR_LOC aErrorLoc)
Convert arc to multiple straight segments.
void TransformRoundChamferedRectToPolygon(SHAPE_POLY_SET &aBuffer, const VECTOR2I &aPosition, const VECTOR2I &aSize, const EDA_ANGLE &aRotation, int aCornerRadius, double aChamferRatio, int aChamferCorners, int aInflate, int aError, ERROR_LOC aErrorLoc)
Convert a rectangle with rounded corners and/or chamfered corners to a polygon.
void TransformOvalToPolygon(SHAPE_POLY_SET &aBuffer, const VECTOR2I &aStart, const VECTOR2I &aEnd, int aWidth, int aError, ERROR_LOC aErrorLoc, int aMinSegCount=0)
Convert a oblong shape to a polygon, using multiple segments.
@ ROUND_ALL_CORNERS
All angles are rounded.
#define _(s)
static constexpr EDA_ANGLE ANGLE_0
Definition eda_angle.h:411
static constexpr EDA_ANGLE ANGLE_90
Definition eda_angle.h:413
@ RADIANS_T
Definition eda_angle.h:32
static constexpr EDA_ANGLE ANGLE_45
Definition eda_angle.h:412
static constexpr EDA_ANGLE ANGLE_270
Definition eda_angle.h:416
static constexpr EDA_ANGLE ANGLE_360
Definition eda_angle.h:417
static constexpr EDA_ANGLE ANGLE_180
Definition eda_angle.h:415
#define TEST_PT(a, b)
#define TEST(a, b)
#define TEST_E(a, b)
static struct EDA_SHAPE_DESC _EDA_SHAPE_DESC
#define SWAPITEM(x)
#define sq(x)
SHAPE_T
Definition eda_shape.h:43
@ UNDEFINED
Definition eda_shape.h:44
@ SEGMENT
Definition eda_shape.h:45
@ RECTANGLE
Use RECTANGLE instead of RECT to avoid collision in a Windows header.
Definition eda_shape.h:46
UI_FILL_MODE
Definition eda_shape.h:68
@ REVERSE_HATCH
Definition eda_shape.h:72
@ SOLID
Definition eda_shape.h:70
@ HATCH
Definition eda_shape.h:71
@ NONE
Definition eda_shape.h:69
@ CROSS_HATCH
Definition eda_shape.h:73
FILL_T
Definition eda_shape.h:56
@ NO_FILL
Definition eda_shape.h:57
@ REVERSE_HATCH
Definition eda_shape.h:62
@ HATCH
Definition eda_shape.h:61
@ FILLED_SHAPE
Fill with object color.
Definition eda_shape.h:58
@ CROSS_HATCH
Definition eda_shape.h:63
a few functions useful in geometry calculations.
This file contains miscellaneous commonly used macros and functions.
#define KI_FALLTHROUGH
The KI_FALLTHROUGH macro is to be used when switch statement cases should purposely fallthrough from ...
Definition macros.h:83
#define UNIMPLEMENTED_FOR(type)
Definition macros.h:96
constexpr void MIRROR(T &aPoint, const T &aMirrorRef)
Updates aPoint with the mirror of aPoint relative to the aMirrorRef.
Definition mirror.h:45
FLIP_DIRECTION
Definition mirror.h:27
KICOMMON_API wxString MessageTextFromValue(const EDA_IU_SCALE &aIuScale, EDA_UNITS aUnits, double aValue, bool aAddUnitsText=true, EDA_DATA_TYPE aType=EDA_DATA_TYPE::DISTANCE)
A helper to convert the double length aValue to a string in inches, millimeters, or unscaled units.
bool ShapeHitTest(const SHAPE_LINE_CHAIN &aHitter, const SHAPE &aHittee, bool aHitteeContained)
Perform a shape-to-shape hit test.
size_t longest_common_subset(const _Container &__c1, const _Container &__c2)
Returns the length of the longest common subset of values between two containers.
Definition kicad_algo.h:186
KICOMMON_API VECTOR2I UnpackVector2(const types::Vector2 &aInput)
Definition api_utils.cpp:86
KICOMMON_API SHAPE_POLY_SET UnpackPolySet(const types::PolySet &aInput)
KICOMMON_API void PackVector2(types::Vector2 &aOutput, const VECTOR2I &aInput)
Definition api_utils.cpp:79
KICOMMON_API void PackPolySet(types::PolySet &aOutput, const SHAPE_POLY_SET &aInput)
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:400
#define _HKI(x)
Definition page_info.cpp:44
#define IMPLEMENT_ENUM_TO_WXANY(type)
Definition property.h:821
#define NO_SETTER(owner, type)
Definition property.h:828
@ PT_COORD
Coordinate expressed in distance units (mm/inch)
Definition property.h:65
@ PT_DECIDEGREE
Angle expressed in decidegrees.
Definition property.h:67
@ PT_SIZE
Size expressed in distance units (mm/inch)
Definition property.h:63
#define REGISTER_TYPE(x)
std::optional< std::unique_ptr< VALIDATION_ERROR > > VALIDATOR_RESULT
Null optional means validation succeeded.
@ SH_POLY_SET
set of polygons (with holes, etc.)
Definition shape.h:52
@ SH_CIRCLE
circle
Definition shape.h:50
@ SH_SIMPLE
simple polygon
Definition shape.h:51
@ SH_NULL
empty shape (no shape...),
Definition shape.h:55
@ SH_SEGMENT
line segment
Definition shape.h:48
@ SH_ARC
circular arc
Definition shape.h:54
@ SH_POLY_SET_TRIANGLE
a single triangle belonging to a POLY_SET triangulation
Definition shape.h:56
@ SH_LINE_CHAIN
line chain (polyline)
Definition shape.h:49
@ SH_COMPOUND
compound shape, consisting of multiple simple shapes
Definition shape.h:53
static bool Collide(const SHAPE_CIRCLE &aA, const SHAPE_CIRCLE &aB, int aClearance, int *aActual, VECTOR2I *aLocation, VECTOR2I *aMTV)
LINE_STYLE
Dashed line types.
int radius
SHAPE_CIRCLE circle(c.m_circle_center, c.m_circle_radius)
bool TestSegmentHit(const VECTOR2I &aRefPoint, const VECTOR2I &aStart, const VECTOR2I &aEnd, int aDist)
Test if aRefPoint is with aDistance on the line defined by aStart and aEnd.
Definition trigo.cpp:175
void RotatePoint(int *pX, int *pY, const EDA_ANGLE &aAngle)
Calculate the new point of coord coord pX, pY, for a rotation center 0, 0.
Definition trigo.cpp:229
const VECTOR2I CalcArcCenter(const VECTOR2I &aStart, const VECTOR2I &aMid, const VECTOR2I &aEnd)
Determine the center of an arc or circle given three points on its circumference.
Definition trigo.cpp:521
@ PCB_TABLECELL_T
class PCB_TABLECELL, PCB_TEXTBOX for use in tables
Definition typeinfo.h:95
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:695
VECTOR2< double > VECTOR2D
Definition vector2d.h:694