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