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 <geometry/roundrect.h>
41#include <macros.h>
42#include <algorithm>
44#include <properties/property.h>
46#include <math/util.h> // for KiROUND
47#include <eda_item.h>
48#include <plotters/plotter.h>
49#include <api/api_enums.h>
50#include <api/api_utils.h>
51#include <api/common/types/base_types.pb.h>
52
53
54EDA_SHAPE::EDA_SHAPE( SHAPE_T aType, int aLineWidth, FILL_T aFill ) :
55 m_endsSwapped( false ),
56 m_shape( aType ),
58 m_fill( aFill ),
60 m_hatchingDirty( true ),
63 m_cornerRadius( 0 ),
64 m_editState( 0 ),
65 m_proxyItem( false )
66{
67}
68
69
73
74
75EDA_SHAPE::EDA_SHAPE( const SHAPE& aShape ) :
76 m_endsSwapped( false ),
78 m_fill(),
79 m_hatchingDirty( true ),
82 m_cornerRadius( 0 ),
83 m_editState( 0 ),
84 m_proxyItem( false )
85{
86 switch( aShape.Type() )
87 {
88 case SH_RECT:
89 {
90 auto rect = static_cast<const SHAPE_RECT&>( aShape );
91 m_shape = SHAPE_T::RECTANGLE;
92 SetStart( rect.GetPosition() );
93 SetEnd( rect.GetPosition() + rect.GetSize() );
94 break;
95 }
96
97 case SH_SEGMENT:
98 {
99 auto seg = static_cast<const SHAPE_SEGMENT&>( aShape );
100 m_shape = SHAPE_T::SEGMENT;
101 SetStart( seg.GetSeg().A );
102 SetEnd( seg.GetSeg().B );
103 SetWidth( seg.GetWidth() );
104 break;
105 }
106
107 case SH_LINE_CHAIN:
108 {
109 auto line = static_cast<const SHAPE_LINE_CHAIN&>( aShape );
110 m_shape = SHAPE_T::POLY;
111 GetPolyShape() = SHAPE_POLY_SET();
112 GetPolyShape().AddOutline( line );
113 SetWidth( line.Width() );
114 break;
115 }
116
117 case SH_CIRCLE:
118 {
119 auto circle = static_cast<const SHAPE_CIRCLE&>( aShape );
120 m_shape = SHAPE_T::CIRCLE;
121 SetStart( circle.GetCenter() );
122 SetEnd( circle.GetCenter() + circle.GetRadius() );
123 break;
124 }
125
126 case SH_ARC:
127 {
128 auto arc = static_cast<const SHAPE_ARC&>( aShape );
129 m_shape = SHAPE_T::ARC;
130 SetArcGeometry( arc.GetP0(), arc.GetArcMid(), arc.GetP1() );
131 SetWidth( arc.GetWidth() );
132 break;
133 }
134
135 case SH_SIMPLE:
136 {
137 auto poly = static_cast<const SHAPE_SIMPLE&>( aShape );
138 m_shape = SHAPE_T::POLY;
139 poly.TransformToPolygon( GetPolyShape(), 0, ERROR_INSIDE );
140 break;
141 }
142
143 // currently unhandled
144 case SH_POLY_SET:
145 case SH_COMPOUND:
146 case SH_NULL:
148 default:
150 break;
151 }
152}
153
154
157 m_shape( aOther.m_shape ),
158 m_stroke( aOther.m_stroke ),
159 m_fill( aOther.m_fill ),
160 m_fillColor( aOther.m_fillColor ),
161 m_hatchingDirty( true ),
165 m_start( aOther.m_start ),
166 m_end( aOther.m_end ),
167 m_arcCenter( aOther.m_arcCenter ),
168 m_arcMidData( aOther.m_arcMidData ),
169 m_bezierC1( aOther.m_bezierC1 ),
170 m_bezierC2( aOther.m_bezierC2 ),
172 m_editState( aOther.m_editState ),
173 m_proxyItem( aOther.m_proxyItem )
174{
175 if( aOther.m_poly )
176 m_poly = std::make_unique<SHAPE_POLY_SET>( *aOther.m_poly );
177}
178
179
181{
182 if( this == &aOther )
183 return *this;
184
186 m_shape = aOther.m_shape;
187 m_stroke = aOther.m_stroke;
188 m_fill = aOther.m_fill;
189 m_fillColor = aOther.m_fillColor;
190 m_hatchingCache.reset();
191 m_hatchingDirty = true;
195 m_start = aOther.m_start;
196 m_end = aOther.m_end;
197 m_arcCenter = aOther.m_arcCenter;
198 m_arcMidData = aOther.m_arcMidData;
199 m_bezierC1 = aOther.m_bezierC1;
200 m_bezierC2 = aOther.m_bezierC2;
202 if( aOther.m_poly )
203 m_poly = std::make_unique<SHAPE_POLY_SET>( *aOther.m_poly );
204 else
205 m_poly.reset();
206 m_editState = aOther.m_editState;
207 m_proxyItem = aOther.m_proxyItem;
208
209 return *this;
210}
211
212
213void EDA_SHAPE::Serialize( google::protobuf::Any &aContainer ) const
214{
215 using namespace kiapi::common;
216 types::GraphicShape shape;
217
218 types::StrokeAttributes* stroke = shape.mutable_attributes()->mutable_stroke();
219 types::GraphicFillAttributes* fill = shape.mutable_attributes()->mutable_fill();
220
221 stroke->mutable_width()->set_value_nm( GetWidth() );
222
223 switch( GetLineStyle() )
224 {
225 case LINE_STYLE::DEFAULT: stroke->set_style( types::SLS_DEFAULT ); break;
226 case LINE_STYLE::SOLID: stroke->set_style( types::SLS_SOLID ); break;
227 case LINE_STYLE::DASH: stroke->set_style( types::SLS_DASH ); break;
228 case LINE_STYLE::DOT: stroke->set_style( types::SLS_DOT ); break;
229 case LINE_STYLE::DASHDOT: stroke->set_style( types::SLS_DASHDOT ); break;
230 case LINE_STYLE::DASHDOTDOT: stroke->set_style( types::SLS_DASHDOTDOT ); break;
231 default: break;
232 }
233
234 switch( GetFillMode() )
235 {
236 case FILL_T::FILLED_SHAPE: fill->set_fill_type( types::GFT_FILLED ); break;
237 default: fill->set_fill_type( types::GFT_UNFILLED ); break;
238 }
239
240 switch( GetShape() )
241 {
242 case SHAPE_T::SEGMENT:
243 {
244 types::GraphicSegmentAttributes* segment = shape.mutable_segment();
245 PackVector2( *segment->mutable_start(), GetStart() );
246 PackVector2( *segment->mutable_end(), GetEnd() );
247 break;
248 }
249
251 {
252 types::GraphicRectangleAttributes* rectangle = shape.mutable_rectangle();
253 PackVector2( *rectangle->mutable_top_left(), GetStart() );
254 PackVector2( *rectangle->mutable_bottom_right(), GetEnd() );
255 rectangle->mutable_corner_radius()->set_value_nm( GetCornerRadius() );
256 break;
257 }
258
259 case SHAPE_T::ARC:
260 {
261 types::GraphicArcAttributes* arc = shape.mutable_arc();
262 PackVector2( *arc->mutable_start(), GetStart() );
263 PackVector2( *arc->mutable_mid(), GetArcMid() );
264 PackVector2( *arc->mutable_end(), GetEnd() );
265 break;
266 }
267
268 case SHAPE_T::CIRCLE:
269 {
270 types::GraphicCircleAttributes* circle = shape.mutable_circle();
271 PackVector2( *circle->mutable_center(), GetStart() );
272 PackVector2( *circle->mutable_radius_point(), GetEnd() );
273 break;
274 }
275
276 case SHAPE_T::POLY:
277 {
278 PackPolySet( *shape.mutable_polygon(), GetPolyShape() );
279 break;
280 }
281
282 case SHAPE_T::BEZIER:
283 {
284 types::GraphicBezierAttributes* bezier = shape.mutable_bezier();
285 PackVector2( *bezier->mutable_start(), GetStart() );
286 PackVector2( *bezier->mutable_control1(), GetBezierC1() );
287 PackVector2( *bezier->mutable_control2(), GetBezierC2() );
288 PackVector2( *bezier->mutable_end(), GetEnd() );
289 break;
290 }
291
292 default:
293 wxASSERT_MSG( false, "Unhandled shape in EDA_SHAPE::Serialize" );
294 }
295
296 // TODO m_hasSolderMask and m_solderMaskMargin
297
298 aContainer.PackFrom( shape );
299}
300
301
302bool EDA_SHAPE::Deserialize( const google::protobuf::Any &aContainer )
303{
304 using namespace kiapi::common;
305
306 types::GraphicShape shape;
307
308 if( !aContainer.UnpackTo( &shape ) )
309 return false;
310
311 // Initialize everything to a known state that doesn't get touched by every
312 // codepath below, to make sure the equality operator is consistent
313 m_start = {};
314 m_end = {};
315 m_arcCenter = {};
316 m_arcMidData = {};
317 m_bezierC1 = {};
318 m_bezierC2 = {};
319 m_editState = 0;
320 m_proxyItem = false;
321 m_endsSwapped = false;
322
323 SetFilled( shape.attributes().fill().fill_type() == types::GFT_FILLED );
324 SetWidth( shape.attributes().stroke().width().value_nm() );
325
326 switch( shape.attributes().stroke().style() )
327 {
328 case types::SLS_DEFAULT: SetLineStyle( LINE_STYLE::DEFAULT ); break;
329 case types::SLS_SOLID: SetLineStyle( LINE_STYLE::SOLID ); break;
330 case types::SLS_DASH: SetLineStyle( LINE_STYLE::DASH ); break;
331 case types::SLS_DOT: SetLineStyle( LINE_STYLE::DOT ); break;
332 case types::SLS_DASHDOT: SetLineStyle( LINE_STYLE::DASHDOT ); break;
333 case types::SLS_DASHDOTDOT: SetLineStyle( LINE_STYLE::DASHDOTDOT ); break;
334 default: break;
335 }
336
337 if( shape.has_segment() )
338 {
340 SetStart( UnpackVector2( shape.segment().start() ) );
341 SetEnd( UnpackVector2( shape.segment().end() ) );
342 }
343 else if( shape.has_rectangle() )
344 {
346 SetStart( UnpackVector2( shape.rectangle().top_left() ) );
347 SetEnd( UnpackVector2( shape.rectangle().bottom_right() ) );
348 SetCornerRadius( shape.rectangle().corner_radius().value_nm() );
349 }
350 else if( shape.has_arc() )
351 {
353 SetArcGeometry( UnpackVector2( shape.arc().start() ),
354 UnpackVector2( shape.arc().mid() ),
355 UnpackVector2( shape.arc().end() ) );
356 }
357 else if( shape.has_circle() )
358 {
360 SetStart( UnpackVector2( shape.circle().center() ) );
361 SetEnd( UnpackVector2( shape.circle().radius_point() ) );
362 }
363 else if( shape.has_polygon() )
364 {
366 SetPolyShape( UnpackPolySet( shape.polygon() ) );
367 }
368 else if( shape.has_bezier() )
369 {
371 SetStart( UnpackVector2( shape.bezier().start() ) );
372 SetBezierC1( UnpackVector2( shape.bezier().control1() ) );
373 SetBezierC2( UnpackVector2( shape.bezier().control2() ) );
374 SetEnd( UnpackVector2( shape.bezier().end() ) );
376 }
377
378 return true;
379}
380
381
382wxString EDA_SHAPE::ShowShape() const
383{
384 if( IsProxyItem() )
385 {
386 switch( m_shape )
387 {
388 case SHAPE_T::SEGMENT: return _( "Thermal Spoke" );
389 case SHAPE_T::RECTANGLE: return _( "Number Box" );
390 default: return wxT( "??" );
391 }
392 }
393 else
394 {
395 switch( m_shape )
396 {
397 case SHAPE_T::SEGMENT: return _( "Line" );
398 case SHAPE_T::RECTANGLE: return _( "Rect" );
399 case SHAPE_T::ARC: return _( "Arc" );
400 case SHAPE_T::CIRCLE: return _( "Circle" );
401 case SHAPE_T::BEZIER: return _( "Bezier Curve" );
402 case SHAPE_T::POLY: return _( "Polygon" );
403 default: return wxT( "??" );
404 }
405 }
406}
407
408
410{
411 switch( m_shape )
412 {
413 case SHAPE_T::SEGMENT: return wxS( "S_SEGMENT" );
414 case SHAPE_T::RECTANGLE: return wxS( "S_RECT" );
415 case SHAPE_T::ARC: return wxS( "S_ARC" );
416 case SHAPE_T::CIRCLE: return wxS( "S_CIRCLE" );
417 case SHAPE_T::POLY: return wxS( "S_POLYGON" );
418 case SHAPE_T::BEZIER: return wxS( "S_CURVE" );
419 case SHAPE_T::UNDEFINED: return wxS( "UNDEFINED" );
420 }
421
422 return wxEmptyString; // Just to quiet GCC.
423}
424
425
427{
428 move( aPos - getPosition() );
429}
430
431
433{
434 if( m_shape == SHAPE_T::ARC )
435 return getCenter();
436 else if( m_shape == SHAPE_T::POLY )
437 return GetPolyShape().CVertex( 0 );
438 else
439 return m_start;
440}
441
442
444{
445 double length = 0.0;
446
447 switch( m_shape )
448 {
449 case SHAPE_T::BEZIER:
450 for( size_t ii = 1; ii < m_bezierPoints.size(); ++ii )
451 length += m_bezierPoints[ ii - 1].Distance( m_bezierPoints[ii] );
452
453 return length;
454
455 case SHAPE_T::SEGMENT:
456 return GetStart().Distance( GetEnd() );
457
458 case SHAPE_T::POLY:
459 for( int ii = 0; ii < GetPolyShape().COutline( 0 ).SegmentCount(); ii++ )
460 length += GetPolyShape().COutline( 0 ).CSegment( ii ).Length();
461
462 return length;
463
464 case SHAPE_T::ARC:
465 return GetRadius() * GetArcAngle().AsRadians();
466
467 default:
469 return 0.0;
470 }
471}
472
473
475{
476 switch( m_shape )
477 {
479 return GetEndY() - GetStartY();
480
481 default:
483 return 0;
484 }
485}
486
487
489{
490 switch( m_shape )
491 {
493 return GetEndX() - GetStartX();
494
495 default:
497 return 0;
498 }
499}
500
501
503{
504 return m_cornerRadius;
505}
506
507
508void EDA_SHAPE::SetCornerRadius( int aRadius )
509{
511 {
512 int width = std::abs( GetRectangleWidth() );
513 int height = std::abs( GetRectangleHeight() );
514 int maxRadius = std::min( width, height ) / 2;
515
516 m_cornerRadius = std::clamp( aRadius, 0, maxRadius );
517 }
518 else
519 {
520 m_cornerRadius = aRadius;
521 }
522}
523
524
525void EDA_SHAPE::SetRectangleHeight( const int& aHeight )
526{
527 switch ( m_shape )
528 {
530 m_rectangleHeight = aHeight;
532 break;
533
534 default:
536 }
537}
538
539
540void EDA_SHAPE::SetRectangleWidth( const int& aWidth )
541{
542 switch ( m_shape )
543 {
545 m_rectangleWidth = aWidth;
547 break;
548
549 default:
551 }
552}
553
554
555void EDA_SHAPE::SetRectangle( const long long int& aHeight, const long long int& aWidth )
556{
557 switch ( m_shape )
558 {
560 m_rectangleHeight = aHeight;
561 m_rectangleWidth = aWidth;
562 break;
563
564 default:
566 }
567}
568
569
571{
572 switch( m_shape )
573 {
574 case SHAPE_T::CIRCLE:
576 return true;
577
578 case SHAPE_T::ARC:
579 case SHAPE_T::SEGMENT:
580 return false;
581
582 case SHAPE_T::POLY:
583 if( GetPolyShape().IsEmpty() )
584 return false;
585 else
586 return GetPolyShape().Outline( 0 ).IsClosed();
587
588 case SHAPE_T::BEZIER:
589 if( m_bezierPoints.size() < 3 )
590 return false;
591 else
592 return m_bezierPoints[0] == m_bezierPoints[ m_bezierPoints.size() - 1 ];
593
594 default:
596 return false;
597 }
598}
599
600
602{
603 m_fill = aFill;
604 m_hatchingDirty = true;
605}
606
607
609{
610 switch( aFill )
611 {
616 default: SetFilled( true ); break;
617 }
618}
619
620
632
633
635{
636 if( !m_hatchingCache )
637 m_hatchingCache = std::make_unique<EDA_SHAPE_HATCH_CACHE_DATA>();
638
639 return m_hatchingCache->hatching;
640}
641
642
643const std::vector<SEG>& EDA_SHAPE::GetHatchLines() const
644{
645 if( !m_hatchingCache )
646 m_hatchingCache = std::make_unique<EDA_SHAPE_HATCH_CACHE_DATA>();
647
648 return m_hatchingCache->hatchLines;
649}
650
651
653{
654 if( !m_hatchingCache )
655 m_hatchingCache = std::make_unique<EDA_SHAPE_HATCH_CACHE_DATA>();
656
657 return m_hatchingCache->hatching;
658}
659
660
661std::vector<SEG>& EDA_SHAPE::hatchLines() const
662{
663 if( !m_hatchingCache )
664 m_hatchingCache = std::make_unique<EDA_SHAPE_HATCH_CACHE_DATA>();
665
666 return m_hatchingCache->hatchLines;
667}
668
669
671{
672 if( !m_hatchingDirty )
673 return;
674
675 std::vector<double> slopes;
676 int lineWidth = GetHatchLineWidth();
677 int spacing = GetHatchLineSpacing();
678 SHAPE_POLY_SET shapeBuffer;
679
680 // Validate state before clearing cached hatching. If we can't regenerate, keep existing cache.
681 if( isMoving() )
682 return;
683
685 slopes = { 1.0, -1.0 };
686 else if( GetFillMode() == FILL_T::HATCH )
687 slopes = { -1.0 };
688 else if( GetFillMode() == FILL_T::REVERSE_HATCH )
689 slopes = { 1.0 };
690 else
691 return;
692
693 if( spacing == 0 )
694 return;
695
696 switch( m_shape )
697 {
698 case SHAPE_T::ARC:
699 case SHAPE_T::SEGMENT:
700 case SHAPE_T::BEZIER:
701 return;
702
704 {
706 rr.TransformToPolygon( shapeBuffer, getMaxError() );
707 }
708 break;
709
710 case SHAPE_T::CIRCLE:
712 break;
713
714 case SHAPE_T::POLY:
715 if( !IsClosed() )
716 return;
717
718 shapeBuffer = GetPolyShape().CloneDropTriangulation();
719 break;
720
721 default:
723 return;
724 }
725
726 // Clear cached hatching only after all validation passes.
727 // This prevents flickering when early returns would otherwise leave empty hatching.
729 hatchLines().clear();
730
731 BOX2I extents = shapeBuffer.BBox();
732 int majorAxis = std::max( extents.GetWidth(), extents.GetHeight() );
733
734 if( majorAxis / spacing > 100 )
735 spacing = majorAxis / 100;
736
738
739 if( !knockouts.IsEmpty() )
740 {
741 shapeBuffer.BooleanSubtract( knockouts );
742 shapeBuffer.Fracture();
743 }
744
745 // Generate hatch lines for stroke-based rendering. All hatch types use line segments.
746 std::vector<SEG> hatchSegs = shapeBuffer.GenerateHatchLines( slopes, spacing, -1 );
747 hatchLines() = hatchSegs;
748
749 // Also generate polygon representation for exports, 3D viewer, and hit testing
751 {
752 for( const SEG& seg : hatchSegs )
753 {
754 // We don't really need the rounded ends at all, so don't spend any extra time on them
755 int maxError = lineWidth;
756
757 TransformOvalToPolygon( hatching(), seg.A, seg.B, lineWidth, maxError,
758 ERROR_INSIDE );
759 }
760
761 hatching().Fracture();
762 m_hatchingDirty = false;
763 }
764 else
765 {
766 // Generate a grid of holes for a cross-hatch polygon representation.
767 // This is used for exports, 3D viewer, and hit testing.
768
769 int gridsize = spacing;
770 int hole_size = gridsize - GetHatchLineWidth();
771
772 hatching() = shapeBuffer.CloneDropTriangulation();
774
775 // Build hole shape
776 SHAPE_LINE_CHAIN hole_base;
777 VECTOR2I corner( 0, 0 );;
778 hole_base.Append( corner );
779 corner.x += hole_size;
780 hole_base.Append( corner );
781 corner.y += hole_size;
782 hole_base.Append( corner );
783 corner.x = 0;
784 hole_base.Append( corner );
785 hole_base.SetClosed( true );
786
787 // Build holes
788 BOX2I bbox = GetHatching().BBox( 0 );
789 SHAPE_POLY_SET holes;
790
791 int x_offset = bbox.GetX() - ( bbox.GetX() ) % gridsize - gridsize;
792 int y_offset = bbox.GetY() - ( bbox.GetY() ) % gridsize - gridsize;
793
794 for( int xx = x_offset; xx <= bbox.GetRight(); xx += gridsize )
795 {
796 for( int yy = y_offset; yy <= bbox.GetBottom(); yy += gridsize )
797 {
798 SHAPE_LINE_CHAIN hole( hole_base );
799 hole.Move( VECTOR2I( xx, yy ) );
800 holes.AddOutline( hole );
801 }
802 }
803
804 hatching().BooleanSubtract( holes );
805 hatching().Fracture();
806
807 // Must re-rotate after Fracture(). Clipper struggles mightily with fracturing
808 // 45-degree holes.
810
811 if( !knockouts.IsEmpty() )
812 {
813 hatching().BooleanSubtract( knockouts );
814 hatching().Fracture();
815 }
816
817 m_hatchingDirty = false;
818 }
819}
820
821
822void EDA_SHAPE::move( const VECTOR2I& aMoveVector )
823{
824 switch ( m_shape )
825 {
826 case SHAPE_T::ARC:
827 m_arcCenter += aMoveVector;
828 m_arcMidData.center += aMoveVector;
829 m_arcMidData.start += aMoveVector;
830 m_arcMidData.end += aMoveVector;
831 m_arcMidData.mid += aMoveVector;
833
834 case SHAPE_T::SEGMENT:
836 case SHAPE_T::CIRCLE:
837 m_start += aMoveVector;
838 m_end += aMoveVector;
839 break;
840
841 case SHAPE_T::POLY:
842 GetPolyShape().Move( aMoveVector );
843 break;
844
845 case SHAPE_T::BEZIER:
846 m_start += aMoveVector;
847 m_end += aMoveVector;
848 m_bezierC1 += aMoveVector;
849 m_bezierC2 += aMoveVector;
850
851 for( VECTOR2I& pt : m_bezierPoints )
852 pt += aMoveVector;
853
854 break;
855
856 default:
858 break;
859 }
860
861 // Translate the cached hatch geometry instead of leaving it stale. The hatch pattern is
862 // invariant under translation, so shifting line endpoints is sufficient and keeps the
863 // display correct during interactive moves without hitting GenerateHatchLines().
864 if( m_hatchingCache )
865 {
866 for( SEG& seg : m_hatchingCache->hatchLines )
867 {
868 seg.A += aMoveVector;
869 seg.B += aMoveVector;
870 }
871
872 m_hatchingCache->hatching.Move( aMoveVector );
873 }
874
875 m_hatchingDirty = true;
876}
877
878
879void EDA_SHAPE::scale( double aScale )
880{
881 auto scalePt =
882 [&]( VECTOR2I& pt )
883 {
884 pt.x = KiROUND( pt.x * aScale );
885 pt.y = KiROUND( pt.y * aScale );
886 };
887
888 switch( m_shape )
889 {
890 case SHAPE_T::ARC:
891 scalePt( m_arcCenter );
893
894 case SHAPE_T::SEGMENT:
896 case SHAPE_T::CIRCLE:
897 scalePt( m_start );
898 scalePt( m_end );
899 break;
900
901 case SHAPE_T::POLY: // polygon
902 {
903 std::vector<VECTOR2I> pts;
904
905 for( int ii = 0; ii < GetPolyShape().OutlineCount(); ++ ii )
906 {
907 for( const VECTOR2I& pt : GetPolyShape().Outline( ii ).CPoints() )
908 {
909 pts.emplace_back( pt );
910 scalePt( pts.back() );
911 }
912 }
913
914 SetPolyPoints( pts );
915 }
916 break;
917
918 case SHAPE_T::BEZIER:
919 scalePt( m_start );
920 scalePt( m_end );
921 scalePt( m_bezierC1 );
922 scalePt( m_bezierC2 );
924 break;
925
926 default:
928 break;
929 }
930
931 m_hatchingDirty = true;
932}
933
934
935void EDA_SHAPE::rotate( const VECTOR2I& aRotCentre, const EDA_ANGLE& aAngle )
936{
937 switch( m_shape )
938 {
939 case SHAPE_T::SEGMENT:
940 case SHAPE_T::CIRCLE:
941 RotatePoint( m_start, aRotCentre, aAngle );
942 RotatePoint( m_end, aRotCentre, aAngle );
943 break;
944
945 case SHAPE_T::ARC:
946 RotatePoint( m_start, aRotCentre, aAngle );
947 RotatePoint( m_end, aRotCentre, aAngle );
948 RotatePoint( m_arcCenter, aRotCentre, aAngle );
949 RotatePoint( m_arcMidData.start, aRotCentre, aAngle );
950 RotatePoint( m_arcMidData.end, aRotCentre, aAngle );
951 RotatePoint( m_arcMidData.mid, aRotCentre, aAngle );
952 RotatePoint( m_arcMidData.center, aRotCentre, aAngle );
953 break;
954
956 if( aAngle.IsCardinal() )
957 {
958 RotatePoint( m_start, aRotCentre, aAngle );
959 RotatePoint( m_end, aRotCentre, aAngle );
960 }
961 else
962 {
963 // Convert non-cardinally-rotated rect to a diamond
967 GetPolyShape().Rotate( aAngle, aRotCentre );
968 }
969
970 break;
971
972 case SHAPE_T::POLY:
973 GetPolyShape().Rotate( aAngle, aRotCentre );
974 break;
975
976 case SHAPE_T::BEZIER:
977 RotatePoint( m_start, aRotCentre, aAngle );
978 RotatePoint( m_end, aRotCentre, aAngle );
979 RotatePoint( m_bezierC1, aRotCentre, aAngle );
980 RotatePoint( m_bezierC2, aRotCentre, aAngle );
981
982 for( VECTOR2I& pt : m_bezierPoints )
983 RotatePoint( pt, aRotCentre, aAngle);
984
985 break;
986
987 default:
989 break;
990 }
991
992 m_hatchingDirty = true;
993}
994
995
996void EDA_SHAPE::flip( const VECTOR2I& aCentre, FLIP_DIRECTION aFlipDirection )
997{
998 switch ( m_shape )
999 {
1000 case SHAPE_T::SEGMENT:
1001 case SHAPE_T::RECTANGLE:
1002 MIRROR( m_start, aCentre, aFlipDirection );
1003 MIRROR( m_end, aCentre, aFlipDirection );
1004 break;
1005
1006 case SHAPE_T::CIRCLE:
1007 MIRROR( m_start, aCentre, aFlipDirection );
1008 MIRROR( m_end, aCentre, aFlipDirection );
1009 break;
1010
1011 case SHAPE_T::ARC:
1012 MIRROR( m_start, aCentre, aFlipDirection );
1013 MIRROR( m_end, aCentre, aFlipDirection );
1014 MIRROR( m_arcCenter, aCentre, aFlipDirection );
1015
1016 std::swap( m_start, m_end );
1017 break;
1018
1019 case SHAPE_T::POLY:
1020 GetPolyShape().Mirror( aCentre, aFlipDirection );
1021 break;
1022
1023 case SHAPE_T::BEZIER:
1024 MIRROR( m_start, aCentre, aFlipDirection );
1025 MIRROR( m_end, aCentre, aFlipDirection );
1026 MIRROR( m_bezierC1, aCentre, aFlipDirection );
1027 MIRROR( m_bezierC2, aCentre, aFlipDirection );
1028
1030 break;
1031
1032 default:
1034 break;
1035 }
1036
1037 m_hatchingDirty = true;
1038}
1039
1040
1042{
1043 // Has meaning only for SHAPE_T::BEZIER
1044 if( m_shape != SHAPE_T::BEZIER )
1045 {
1046 m_bezierPoints.clear();
1047 return;
1048 }
1049
1050 // Rebuild the m_BezierPoints vertex list that approximate the Bezier curve
1052}
1053
1054
1055const std::vector<VECTOR2I> EDA_SHAPE::buildBezierToSegmentsPointsList( int aMaxError ) const
1056{
1057 std::vector<VECTOR2I> bezierPoints;
1058
1059 // Rebuild the m_BezierPoints vertex list that approximate the Bezier curve
1060 std::vector<VECTOR2I> ctrlPoints = { m_start, m_bezierC1, m_bezierC2, m_end };
1061 BEZIER_POLY converter( ctrlPoints );
1062 converter.GetPoly( bezierPoints, aMaxError );
1063
1064 return bezierPoints;
1065}
1066
1067
1069{
1070 switch( m_shape )
1071 {
1072 case SHAPE_T::ARC:
1073 return m_arcCenter;
1074
1075 case SHAPE_T::CIRCLE:
1076 return m_start;
1077
1078 case SHAPE_T::SEGMENT:
1079 // Midpoint of the line
1080 return ( m_start + m_end ) / 2;
1081
1082 case SHAPE_T::POLY:
1083 case SHAPE_T::RECTANGLE:
1084 case SHAPE_T::BEZIER:
1085 return getBoundingBox().Centre();
1086
1087 default:
1089 return VECTOR2I();
1090 }
1091}
1092
1093
1094void EDA_SHAPE::SetCenter( const VECTOR2I& aCenter )
1095{
1096 switch( m_shape )
1097 {
1098 case SHAPE_T::ARC:
1099 m_arcCenter = aCenter;
1100 break;
1101
1102 case SHAPE_T::CIRCLE:
1103 m_start = aCenter;
1104 m_hatchingDirty = true;
1105 break;
1106
1107 default:
1109 }
1110}
1111
1112
1114{
1115 // If none of the input data have changed since we loaded the arc, keep the original mid point data
1116 // to minimize churn
1117 if( m_arcMidData.start == m_start && m_arcMidData.end == m_end && m_arcMidData.center == m_arcCenter )
1118 return m_arcMidData.mid;
1119
1120 VECTOR2I mid = m_start;
1121 RotatePoint( mid, m_arcCenter, -GetArcAngle() / 2.0 );
1122 return mid;
1123}
1124
1125
1126void EDA_SHAPE::CalcArcAngles( EDA_ANGLE& aStartAngle, EDA_ANGLE& aEndAngle ) const
1127{
1128 VECTOR2D startRadial( GetStart() - getCenter() );
1129 VECTOR2D endRadial( GetEnd() - getCenter() );
1130
1131 aStartAngle = EDA_ANGLE( startRadial );
1132 aEndAngle = EDA_ANGLE( endRadial );
1133
1134 if( aEndAngle == aStartAngle )
1135 aEndAngle = aStartAngle + ANGLE_360; // ring, not null
1136
1137 while( aEndAngle < aStartAngle )
1138 aEndAngle += ANGLE_360;
1139}
1140
1141
1143{
1144 double radius = 0.0;
1145
1146 switch( m_shape )
1147 {
1148 case SHAPE_T::ARC:
1149 radius = m_arcCenter.Distance( m_start );
1150 break;
1151
1152 case SHAPE_T::CIRCLE:
1153 radius = m_start.Distance( m_end );
1154 break;
1155
1156 default:
1158 }
1159
1160 // don't allow degenerate circles/arcs
1161 if( radius > (double) INT_MAX / 2.0 )
1162 radius = (double) INT_MAX / 2.0;
1163
1164 return std::max( 1, KiROUND( radius ) );
1165}
1166
1167
1168void EDA_SHAPE::SetCachedArcData( const VECTOR2I& aStart, const VECTOR2I& aMid,
1169 const VECTOR2I& aEnd, const VECTOR2I& aCenter )
1170{
1171 m_arcMidData.start = aStart;
1172 m_arcMidData.end = aEnd;
1173 m_arcMidData.center = aCenter;
1174 m_arcMidData.mid = aMid;
1175}
1176
1177
1178void EDA_SHAPE::SetArcGeometry( const VECTOR2I& aStart, const VECTOR2I& aMid, const VECTOR2I& aEnd )
1179{
1180 m_arcMidData = {};
1181 m_start = aStart;
1182 m_end = aEnd;
1183 m_arcCenter = CalcArcCenter( aStart, aMid, aEnd );
1184 VECTOR2I new_mid = GetArcMid();
1185
1186 m_endsSwapped = false;
1187
1188 // Watch the ordering here. GetArcMid above needs to be called prior to initializing the
1189 // m_arcMidData structure in order to ensure we get the calculated variant, not the cached
1190 SetCachedArcData( aStart, aMid, aEnd, m_arcCenter );
1191
1192 /*
1193 * If the input winding doesn't match our internal winding, the calculated midpoint will end
1194 * up on the other side of the arc. In this case, we need to flip the start/end points and
1195 * flag this change for the system.
1196 */
1197 VECTOR2D dist( new_mid - aMid );
1198 VECTOR2D dist2( new_mid - m_arcCenter );
1199
1200 if( dist.SquaredEuclideanNorm() > dist2.SquaredEuclideanNorm() )
1201 {
1202 std::swap( m_start, m_end );
1203 m_endsSwapped = true;
1204 }
1205}
1206
1207
1209{
1210 EDA_ANGLE angle( atan2( static_cast<double>( GetStart().y - GetEnd().y ),
1211 static_cast<double>( GetEnd().x - GetStart().x ) ), RADIANS_T );
1212
1213 return angle;
1214}
1215
1216
1218{
1219 EDA_ANGLE startAngle;
1220 EDA_ANGLE endAngle;
1221
1222 CalcArcAngles( startAngle, endAngle );
1223
1224 return endAngle - startAngle;
1225}
1226
1227
1229{
1230 if( m_shape == SHAPE_T::ARC )
1231 {
1232 VECTOR2D mid = GetArcMid();
1233
1234 double orient = ( mid.x - m_start.x ) * ( m_end.y - m_start.y )
1235 - ( mid.y - m_start.y ) * ( m_end.x - m_start.x );
1236
1237 return orient < 0;
1238 }
1239
1241 return false;
1242}
1243
1244
1245void EDA_SHAPE::SetArcAngleAndEnd( const EDA_ANGLE& aAngle, bool aCheckNegativeAngle )
1246{
1247 EDA_ANGLE angle( aAngle );
1248
1249 m_end = m_start;
1251
1252 if( aCheckNegativeAngle && aAngle < ANGLE_0 )
1253 {
1254 std::swap( m_start, m_end );
1255 m_endsSwapped = true;
1256 }
1257}
1258
1259
1261{
1262 if( IsProxyItem() )
1263 {
1264 switch( m_shape )
1265 {
1266 case SHAPE_T::RECTANGLE: return _( "Pad Number Box" );
1267 case SHAPE_T::SEGMENT: return _( "Thermal Spoke Template" );
1268 default: return _( "Unrecognized" );
1269 }
1270 }
1271 else
1272 {
1273 switch( m_shape )
1274 {
1275 case SHAPE_T::CIRCLE: return _( "Circle" );
1276 case SHAPE_T::ARC: return _( "Arc" );
1277 case SHAPE_T::BEZIER: return _( "Curve" );
1278 case SHAPE_T::POLY: return _( "Polygon" );
1279 case SHAPE_T::RECTANGLE: return _( "Rectangle" );
1280 case SHAPE_T::SEGMENT: return _( "Segment" );
1281 default: return _( "Unrecognized" );
1282 }
1283 }
1284}
1285
1286
1287void EDA_SHAPE::ShapeGetMsgPanelInfo( EDA_DRAW_FRAME* aFrame, std::vector<MSG_PANEL_ITEM>& aList )
1288{
1289 wxString msg;
1290
1291 wxString shape = _( "Shape" );
1292 aList.emplace_back( shape, getFriendlyName() );
1293
1294 switch( m_shape )
1295 {
1296 case SHAPE_T::CIRCLE:
1297 aList.emplace_back( _( "Radius" ), aFrame->MessageTextFromValue( GetRadius() ) );
1298 break;
1299
1300 case SHAPE_T::ARC:
1301 aList.emplace_back( _( "Length" ), aFrame->MessageTextFromValue( GetLength() ) );
1302
1304 aList.emplace_back( _( "Angle" ), msg );
1305
1306 aList.emplace_back( _( "Radius" ), aFrame->MessageTextFromValue( GetRadius() ) );
1307 break;
1308
1309 case SHAPE_T::BEZIER:
1310 aList.emplace_back( _( "Length" ), aFrame->MessageTextFromValue( GetLength() ) );
1311 break;
1312
1313 case SHAPE_T::POLY:
1314 msg.Printf( wxS( "%d" ), GetPolyShape().Outline(0).PointCount() );
1315 aList.emplace_back( _( "Points" ), msg );
1316 break;
1317
1318 case SHAPE_T::RECTANGLE:
1319 aList.emplace_back( _( "Width" ), aFrame->MessageTextFromValue( std::abs( GetEnd().x - GetStart().x ) ) );
1320 aList.emplace_back( _( "Height" ), aFrame->MessageTextFromValue( std::abs( GetEnd().y - GetStart().y ) ) );
1321 break;
1322
1323 case SHAPE_T::SEGMENT:
1324 {
1325 aList.emplace_back( _( "Length" ), aFrame->MessageTextFromValue( GetStart().Distance( GetEnd() ) ));
1326
1327 // angle counter-clockwise from 3'o-clock
1328 EDA_ANGLE angle( atan2( (double)( GetStart().y - GetEnd().y ), (double)( GetEnd().x - GetStart().x ) ),
1329 RADIANS_T );
1330 aList.emplace_back( _( "Angle" ), EDA_UNIT_UTILS::UI::MessageTextFromValue( angle ) );
1331 break;
1332 }
1333
1334 default:
1335 break;
1336 }
1337
1338 m_stroke.GetMsgPanelInfo( aFrame, aList );
1339}
1340
1341
1343{
1344 BOX2I bbox;
1345
1346 switch( m_shape )
1347 {
1348 case SHAPE_T::RECTANGLE:
1349 for( VECTOR2I& pt : GetRectCorners() )
1350 bbox.Merge( pt );
1351
1352 break;
1353
1354 case SHAPE_T::SEGMENT:
1355 bbox.SetOrigin( GetStart() );
1356 bbox.SetEnd( GetEnd() );
1357 break;
1358
1359 case SHAPE_T::CIRCLE:
1360 bbox.SetOrigin( GetStart() );
1361 bbox.Inflate( GetRadius() );
1362 break;
1363
1364 case SHAPE_T::ARC:
1365 computeArcBBox( bbox );
1366 break;
1367
1368 case SHAPE_T::POLY:
1369 if( GetPolyShape().IsEmpty() )
1370 break;
1371
1372 for( auto iter = GetPolyShape().CIterate(); iter; iter++ )
1373 bbox.Merge( *iter );
1374
1375 break;
1376
1377 case SHAPE_T::BEZIER:
1378 // Bezier BBoxes are not trivial to compute, so we approximate it by
1379 // using the bounding box of the curve (not control!) points.
1380 for( const VECTOR2I& pt : m_bezierPoints )
1381 bbox.Merge( pt );
1382
1383 break;
1384
1385 default:
1387 break;
1388 }
1389
1390 bbox.Inflate( std::max( 0, GetWidth() ) / 2 );
1391 bbox.Normalize();
1392
1393 return bbox;
1394}
1395
1396
1397bool EDA_SHAPE::hitTest( const VECTOR2I& aPosition, int aAccuracy ) const
1398{
1399 double maxdist = aAccuracy;
1400
1401 if( GetWidth() > 0 )
1402 maxdist += GetWidth() / 2.0;
1403
1404 switch( m_shape )
1405 {
1406 case SHAPE_T::CIRCLE:
1407 {
1408 double radius = GetRadius();
1409 double dist = aPosition.Distance( getCenter() );
1410
1411 if( IsFilledForHitTesting() )
1412 return dist <= radius + maxdist; // Filled circle hit-test
1413 else if( abs( radius - dist ) <= maxdist ) // Ring hit-test
1414 return true;
1415
1416 if( IsHatchedFill() && GetHatching().Collide( aPosition, maxdist ) )
1417 return true;
1418
1419 return false;
1420 }
1421
1422 case SHAPE_T::ARC:
1423 {
1424 if( aPosition.Distance( m_start ) <= maxdist )
1425 return true;
1426
1427 if( aPosition.Distance( m_end ) <= maxdist )
1428 return true;
1429
1430 double radius = GetRadius();
1431 VECTOR2D relPos( VECTOR2D( aPosition ) - getCenter() );
1432 double dist = relPos.EuclideanNorm();
1433
1434 if( IsFilledForHitTesting() )
1435 {
1436 // Check distance from arc center
1437 if( dist > radius + maxdist )
1438 return false;
1439 }
1440 else
1441 {
1442 // Check distance from arc circumference
1443 if( abs( radius - dist ) > maxdist )
1444 return false;
1445 }
1446
1447 // Finally, check to see if it's within arc's swept angle.
1448 EDA_ANGLE startAngle;
1449 EDA_ANGLE endAngle;
1450 CalcArcAngles( startAngle, endAngle );
1451
1452 EDA_ANGLE relPosAngle( relPos );
1453
1454 startAngle.Normalize();
1455 endAngle.Normalize();
1456 relPosAngle.Normalize();
1457
1458 if( endAngle > startAngle )
1459 return relPosAngle >= startAngle && relPosAngle <= endAngle;
1460 else
1461 return relPosAngle >= startAngle || relPosAngle <= endAngle;
1462 }
1463
1464 case SHAPE_T::BEZIER:
1465 {
1466 const std::vector<VECTOR2I>* pts = &m_bezierPoints;
1467 std::vector<VECTOR2I> updatedBezierPoints;
1468
1469 if( m_bezierPoints.empty() )
1470 {
1472 converter.GetPoly( updatedBezierPoints, aAccuracy / 2 );
1473 pts = &updatedBezierPoints;
1474 }
1475
1476 for( unsigned int i = 1; i < pts->size(); i++ )
1477 {
1478 if( TestSegmentHit( aPosition, ( *pts )[i - 1], ( *pts )[i], maxdist ) )
1479 return true;
1480 }
1481
1482 return false;
1483 }
1484 case SHAPE_T::SEGMENT:
1485 return TestSegmentHit( aPosition, GetStart(), GetEnd(), maxdist );
1486
1487 case SHAPE_T::RECTANGLE:
1488 if( IsProxyItem() || IsFilledForHitTesting() ) // Filled rect hit-test
1489 {
1490 SHAPE_POLY_SET poly;
1491 poly.NewOutline();
1492
1493 for( const VECTOR2I& pt : GetRectCorners() )
1494 poly.Append( pt );
1495
1496 return poly.Collide( aPosition, maxdist );
1497 }
1498 else if( m_cornerRadius > 0 )
1499 {
1501 SHAPE_POLY_SET poly;
1502 rr.TransformToPolygon( poly, getMaxError() );
1503
1504 if( poly.CollideEdge( aPosition, nullptr, maxdist ) )
1505 return true;
1506 }
1507 else
1508 {
1509 std::vector<VECTOR2I> pts = GetRectCorners();
1510
1511 if( TestSegmentHit( aPosition, pts[0], pts[1], maxdist )
1512 || TestSegmentHit( aPosition, pts[1], pts[2], maxdist )
1513 || TestSegmentHit( aPosition, pts[2], pts[3], maxdist )
1514 || TestSegmentHit( aPosition, pts[3], pts[0], maxdist ) )
1515 {
1516 return true;
1517 }
1518 }
1519
1520 if( IsHatchedFill() && GetHatching().Collide( aPosition, maxdist ) )
1521 return true;
1522
1523 return false;
1524
1525 case SHAPE_T::POLY:
1526 if( GetPolyShape().OutlineCount() < 1 ) // empty poly
1527 return false;
1528
1529 if( IsFilledForHitTesting() )
1530 {
1531 if( !GetPolyShape().COutline( 0 ).IsClosed() )
1532 {
1533 // Only one outline is expected
1534 SHAPE_LINE_CHAIN copy( GetPolyShape().COutline( 0 ) );
1535 copy.SetClosed( true );
1536 return copy.Collide( aPosition, maxdist );
1537 }
1538 else
1539 {
1540 return GetPolyShape().Collide( aPosition, maxdist );
1541 }
1542 }
1543 else
1544 {
1545 if( GetPolyShape().CollideEdge( aPosition, nullptr, maxdist ) )
1546 return true;
1547
1548 if( IsHatchedFill() && GetHatching().Collide( aPosition, maxdist ) )
1549 return true;
1550
1551 return false;
1552 }
1553
1554 default:
1556 return false;
1557 }
1558}
1559
1560
1561bool EDA_SHAPE::hitTest( const BOX2I& aRect, bool aContained, int aAccuracy ) const
1562{
1563 BOX2I arect = aRect;
1564 arect.Normalize();
1565 arect.Inflate( aAccuracy );
1566
1567 BOX2I bbox = getBoundingBox();
1568
1569 auto checkOutline =
1570 [&]( const SHAPE_LINE_CHAIN& outline )
1571 {
1572 int count = (int) outline.GetPointCount();
1573
1574 for( int ii = 0; ii < count; ii++ )
1575 {
1576 VECTOR2I vertex = outline.GetPoint( ii );
1577
1578 // Test if the point is within aRect
1579 if( arect.Contains( vertex ) )
1580 return true;
1581
1582 if( ii + 1 < count )
1583 {
1584 VECTOR2I vertexNext = outline.GetPoint( ii + 1 );
1585
1586 // Test if this edge intersects aRect
1587 if( arect.Intersects( vertex, vertexNext ) )
1588 return true;
1589 }
1590 else if( outline.IsClosed() )
1591 {
1592 VECTOR2I vertexNext = outline.GetPoint( 0 );
1593
1594 // Test if this edge intersects aRect
1595 if( arect.Intersects( vertex, vertexNext ) )
1596 return true;
1597 }
1598 }
1599
1600 return false;
1601 };
1602
1603 switch( m_shape )
1604 {
1605 case SHAPE_T::CIRCLE:
1606 // Test if area intersects or contains the circle:
1607 if( aContained )
1608 {
1609 return arect.Contains( bbox );
1610 }
1611 else
1612 {
1613 // If the rectangle does not intersect the bounding box, this is a much quicker test
1614 if( !arect.Intersects( bbox ) )
1615 return false;
1616 else
1617 return arect.IntersectsCircleEdge( getCenter(), GetRadius(), GetWidth() );
1618 }
1619
1620 case SHAPE_T::ARC:
1621 // Test for full containment of this arc in the rect
1622 if( aContained )
1623 {
1624 return arect.Contains( bbox );
1625 }
1626 // Test if the rect crosses the arc
1627 else
1628 {
1629 if( !arect.Intersects( bbox ) )
1630 return false;
1631
1632 if( IsAnyFill() )
1633 {
1634 return ( arect.Intersects( getCenter(), GetStart() )
1635 || arect.Intersects( getCenter(), GetEnd() )
1636 || arect.IntersectsCircleEdge( getCenter(), GetRadius(), GetWidth() ) );
1637 }
1638 else
1639 {
1640 return arect.IntersectsCircleEdge( getCenter(), GetRadius(), GetWidth() );
1641 }
1642 }
1643
1644 case SHAPE_T::RECTANGLE:
1645 if( aContained )
1646 {
1647 return arect.Contains( bbox );
1648 }
1649 else if( m_cornerRadius > 0 )
1650 {
1652 SHAPE_POLY_SET poly;
1653 rr.TransformToPolygon( poly, getMaxError() );
1654
1655 // Account for the width of the line
1656 arect.Inflate( GetWidth() / 2 );
1657
1658 return checkOutline( poly.Outline( 0 ) );
1659 }
1660 else
1661 {
1662 std::vector<VECTOR2I> pts = GetRectCorners();
1663
1664 // Account for the width of the lines
1665 arect.Inflate( GetWidth() / 2 );
1666 return ( arect.Intersects( pts[0], pts[1] )
1667 || arect.Intersects( pts[1], pts[2] )
1668 || arect.Intersects( pts[2], pts[3] )
1669 || arect.Intersects( pts[3], pts[0] ) );
1670 }
1671
1672 case SHAPE_T::SEGMENT:
1673 if( aContained )
1674 {
1675 return arect.Contains( GetStart() ) && aRect.Contains( GetEnd() );
1676 }
1677 else
1678 {
1679 // Account for the width of the line
1680 arect.Inflate( GetWidth() / 2 );
1681 return arect.Intersects( GetStart(), GetEnd() );
1682 }
1683
1684 case SHAPE_T::POLY:
1685 if( aContained )
1686 {
1687 return arect.Contains( bbox );
1688 }
1689 else
1690 {
1691 // Fast test: if aRect is outside the polygon bounding box,
1692 // rectangles cannot intersect
1693 if( !arect.Intersects( bbox ) )
1694 return false;
1695
1696 // Account for the width of the line
1697 arect.Inflate( GetWidth() / 2 );
1698
1699 for( int ii = 0; ii < GetPolyShape().OutlineCount(); ++ii )
1700 {
1701 if( checkOutline( GetPolyShape().Outline( ii ) ) )
1702 return true;
1703 }
1704
1705 return false;
1706 }
1707
1708 case SHAPE_T::BEZIER:
1709 if( aContained )
1710 {
1711 return arect.Contains( bbox );
1712 }
1713 else
1714 {
1715 // Fast test: if aRect is outside the polygon bounding box,
1716 // rectangles cannot intersect
1717 if( !arect.Intersects( bbox ) )
1718 return false;
1719
1720 // Account for the width of the line
1721 arect.Inflate( GetWidth() / 2 );
1722 const std::vector<VECTOR2I>* pts = &m_bezierPoints;
1723 std::vector<VECTOR2I> updatedBezierPoints;
1724
1725 if( m_bezierPoints.empty() )
1726 {
1728 converter.GetPoly( updatedBezierPoints, aAccuracy / 2 );
1729 pts = &updatedBezierPoints;
1730 }
1731
1732 for( unsigned ii = 1; ii < pts->size(); ii++ )
1733 {
1734 VECTOR2I vertex = ( *pts )[ii - 1];
1735 VECTOR2I vertexNext = ( *pts )[ii];
1736
1737 // Test if the point is within aRect
1738 if( arect.Contains( vertex ) )
1739 return true;
1740
1741 // Test if this edge intersects aRect
1742 if( arect.Intersects( vertex, vertexNext ) )
1743 return true;
1744 }
1745
1746 return false;
1747 }
1748
1749 default:
1751 return false;
1752 }
1753}
1754
1755
1756bool EDA_SHAPE::hitTest( const SHAPE_LINE_CHAIN& aPoly, bool aContained ) const
1757{
1759
1760 return KIGEOM::ShapeHitTest( aPoly, shape, aContained );
1761}
1762
1763
1764std::vector<VECTOR2I> EDA_SHAPE::GetRectCorners() const
1765{
1766 std::vector<VECTOR2I> pts;
1767 VECTOR2I topLeft = GetStart();
1768 VECTOR2I botRight = GetEnd();
1769
1770 pts.emplace_back( topLeft );
1771 pts.emplace_back( botRight.x, topLeft.y );
1772 pts.emplace_back( botRight );
1773 pts.emplace_back( topLeft.x, botRight.y );
1774
1775 return pts;
1776}
1777
1778
1779std::vector<VECTOR2I> EDA_SHAPE::GetCornersInSequence( EDA_ANGLE angle ) const
1780{
1781 std::vector<VECTOR2I> pts;
1782
1783 angle.Normalize();
1784
1785 BOX2I bbox = getBoundingBox();
1786 bbox.Normalize();
1787
1788 if( angle.IsCardinal() )
1789 {
1790 if( angle == ANGLE_0 )
1791 {
1792 pts.emplace_back( VECTOR2I( bbox.GetLeft(), bbox.GetTop() ) );
1793 pts.emplace_back( VECTOR2I( bbox.GetRight(), bbox.GetTop() ) );
1794 pts.emplace_back( VECTOR2I( bbox.GetRight(), bbox.GetBottom() ) );
1795 pts.emplace_back( VECTOR2I( bbox.GetLeft(), bbox.GetBottom() ) );
1796 }
1797 else if( angle == ANGLE_90 )
1798 {
1799 pts.emplace_back( VECTOR2I( bbox.GetLeft(), bbox.GetBottom() ) );
1800 pts.emplace_back( VECTOR2I( bbox.GetLeft(), bbox.GetTop() ) );
1801 pts.emplace_back( VECTOR2I( bbox.GetRight(), bbox.GetTop() ) );
1802 pts.emplace_back( VECTOR2I( bbox.GetRight(), bbox.GetBottom() ) );
1803 }
1804 else if( angle == ANGLE_180 )
1805 {
1806 pts.emplace_back( VECTOR2I( bbox.GetRight(), bbox.GetBottom() ) );
1807 pts.emplace_back( VECTOR2I( bbox.GetLeft(), bbox.GetBottom() ) );
1808 pts.emplace_back( VECTOR2I( bbox.GetLeft(), bbox.GetTop() ) );
1809 pts.emplace_back( VECTOR2I( bbox.GetRight(), bbox.GetTop() ) );
1810 }
1811 else if( angle == ANGLE_270 )
1812 {
1813 pts.emplace_back( VECTOR2I( bbox.GetRight(), bbox.GetTop() ) );
1814 pts.emplace_back( VECTOR2I( bbox.GetRight(), bbox.GetBottom() ) );
1815 pts.emplace_back( VECTOR2I( bbox.GetLeft(), bbox.GetBottom() ) );
1816 pts.emplace_back( VECTOR2I( bbox.GetLeft(), bbox.GetTop() ) );
1817 }
1818 }
1819 else
1820 {
1821 // This function was originally located in pcb_textbox.cpp and was later moved to eda_shape.cpp.
1822 // As a result of this move, access to getCorners was lost, since it is defined in the PCB_SHAPE
1823 // class within pcb_shape.cpp and is not available in the current context.
1824 //
1825 // Additionally, GetRectCorners() cannot be used here, as it assumes the rectangle is rotated by
1826 // a cardinal angle. In non-cardinal cases, it returns incorrect values (e.g., (0, 0)).
1827 //
1828 // To address this, a portion of the getCorners implementation for SHAPE_T::POLY elements
1829 // has been replicated here to restore the correct behavior.
1830 std::vector<VECTOR2I> corners;
1831
1832 for( int ii = 0; ii < GetPolyShape().OutlineCount(); ++ii )
1833 {
1834 for( const VECTOR2I& pt : GetPolyShape().Outline( ii ).CPoints() )
1835 corners.emplace_back( pt );
1836 }
1837
1838 while( corners.size() < 4 )
1839 corners.emplace_back( corners.back() + VECTOR2I( 10, 10 ) );
1840
1841 VECTOR2I minX = corners[0];
1842 VECTOR2I maxX = corners[0];
1843 VECTOR2I minY = corners[0];
1844 VECTOR2I maxY = corners[0];
1845
1846 for( const VECTOR2I& corner : corners )
1847 {
1848 if( corner.x < minX.x )
1849 minX = corner;
1850
1851 if( corner.x > maxX.x )
1852 maxX = corner;
1853
1854 if( corner.y < minY.y )
1855 minY = corner;
1856
1857 if( corner.y > maxY.y )
1858 maxY = corner;
1859 }
1860
1861 if( angle < ANGLE_90 )
1862 {
1863 pts.emplace_back( minX );
1864 pts.emplace_back( minY );
1865 pts.emplace_back( maxX );
1866 pts.emplace_back( maxY );
1867 }
1868 else if( angle < ANGLE_180 )
1869 {
1870 pts.emplace_back( maxY );
1871 pts.emplace_back( minX );
1872 pts.emplace_back( minY );
1873 pts.emplace_back( maxX );
1874 }
1875 else if( angle < ANGLE_270 )
1876 {
1877 pts.emplace_back( maxX );
1878 pts.emplace_back( maxY );
1879 pts.emplace_back( minX );
1880 pts.emplace_back( minY );
1881 }
1882 else
1883 {
1884 pts.emplace_back( minY );
1885 pts.emplace_back( maxX );
1886 pts.emplace_back( maxY );
1887 pts.emplace_back( minX );
1888 }
1889 }
1890
1891 return pts;
1892}
1893
1894
1896{
1897 // Start, end, and each inflection point the arc crosses will enclose the entire arc.
1898 // Only include the center when filled; it's not necessarily inside the BB of an unfilled
1899 // arc with a small included angle.
1900 aBBox.SetOrigin( m_start );
1901 aBBox.Merge( m_end );
1902
1903 if( IsAnyFill() )
1904 aBBox.Merge( m_arcCenter );
1905
1906 int radius = GetRadius();
1907 EDA_ANGLE t1, t2;
1908
1909 CalcArcAngles( t1, t2 );
1910
1911 t1.Normalize();
1912 t2.Normalize();
1913
1914 if( t2 > t1 )
1915 {
1916 if( t1 < ANGLE_0 && t2 > ANGLE_0 )
1917 aBBox.Merge( VECTOR2I( m_arcCenter.x + radius, m_arcCenter.y ) ); // right
1918
1919 if( t1 < ANGLE_90 && t2 > ANGLE_90 )
1920 aBBox.Merge( VECTOR2I( m_arcCenter.x, m_arcCenter.y + radius ) ); // down
1921
1922 if( t1 < ANGLE_180 && t2 > ANGLE_180 )
1923 aBBox.Merge( VECTOR2I( m_arcCenter.x - radius, m_arcCenter.y ) ); // left
1924
1925 if( t1 < ANGLE_270 && t2 > ANGLE_270 )
1926 aBBox.Merge( VECTOR2I( m_arcCenter.x, m_arcCenter.y - radius ) ); // up
1927 }
1928 else
1929 {
1930 if( t1 < ANGLE_0 || t2 > ANGLE_0 )
1931 aBBox.Merge( VECTOR2I( m_arcCenter.x + radius, m_arcCenter.y ) ); // right
1932
1933 if( t1 < ANGLE_90 || t2 > ANGLE_90 )
1934 aBBox.Merge( VECTOR2I( m_arcCenter.x, m_arcCenter.y + radius ) ); // down
1935
1936 if( t1 < ANGLE_180 || t2 > ANGLE_180 )
1937 aBBox.Merge( VECTOR2I( m_arcCenter.x - radius, m_arcCenter.y ) ); // left
1938
1939 if( t1 < ANGLE_270 || t2 > ANGLE_270 )
1940 aBBox.Merge( VECTOR2I( m_arcCenter.x, m_arcCenter.y - radius ) ); // up
1941 }
1942}
1943
1944
1945void EDA_SHAPE::SetPolyPoints( const std::vector<VECTOR2I>& aPoints )
1946{
1949
1950 for( const VECTOR2I& p : aPoints )
1951 GetPolyShape().Append( p.x, p.y );
1952}
1953
1954
1955std::vector<SHAPE*> EDA_SHAPE::makeEffectiveShapes( bool aEdgeOnly, bool aLineChainOnly, bool aHittesting ) const
1956{
1957 std::vector<SHAPE*> effectiveShapes;
1958 int width = GetEffectiveWidth();
1959 bool solidFill = IsSolidFill()
1960 || IsHatchedFill()
1961 || IsProxyItem()
1962 || ( aHittesting && IsFilledForHitTesting() );
1963
1964 if( aEdgeOnly )
1965 solidFill = false;
1966
1967 switch( m_shape )
1968 {
1969 case SHAPE_T::ARC:
1970 effectiveShapes.emplace_back( new SHAPE_ARC( m_arcCenter, m_start, GetArcAngle(), width ) );
1971 break;
1972
1973 case SHAPE_T::SEGMENT:
1974 effectiveShapes.emplace_back( new SHAPE_SEGMENT( m_start, m_end, width ) );
1975 break;
1976
1977 case SHAPE_T::RECTANGLE:
1978 {
1979 if( m_cornerRadius > 0 )
1980 {
1982 SHAPE_POLY_SET poly;
1983 rr.TransformToPolygon( poly, getMaxError() );
1984 SHAPE_LINE_CHAIN outline = poly.Outline( 0 );
1985
1986 if( solidFill )
1987 effectiveShapes.emplace_back( new SHAPE_SIMPLE( outline ) );
1988
1989 if( width > 0 || !solidFill )
1990 {
1991 std::set<size_t> arcsHandled;
1992
1993 for( int ii = 0; ii < outline.SegmentCount(); ++ii )
1994 {
1995 if( outline.IsArcSegment( ii ) )
1996 {
1997 size_t arcIndex = outline.ArcIndex( ii );
1998
1999 if( !arcsHandled.contains( arcIndex ) )
2000 {
2001 arcsHandled.insert( arcIndex );
2002 effectiveShapes.emplace_back( new SHAPE_ARC( outline.Arc( arcIndex ), width ) );
2003 }
2004 }
2005 else
2006 {
2007 effectiveShapes.emplace_back( new SHAPE_SEGMENT( outline.Segment( ii ), width ) );
2008 }
2009 }
2010 }
2011 }
2012 else
2013 {
2014 std::vector<VECTOR2I> pts = GetRectCorners();
2015
2016 if( solidFill )
2017 effectiveShapes.emplace_back( new SHAPE_SIMPLE( pts ) );
2018
2019 if( width > 0 || !solidFill )
2020 {
2021 effectiveShapes.emplace_back( new SHAPE_SEGMENT( pts[0], pts[1], width ) );
2022 effectiveShapes.emplace_back( new SHAPE_SEGMENT( pts[1], pts[2], width ) );
2023 effectiveShapes.emplace_back( new SHAPE_SEGMENT( pts[2], pts[3], width ) );
2024 effectiveShapes.emplace_back( new SHAPE_SEGMENT( pts[3], pts[0], width ) );
2025 }
2026 }
2027 break;
2028 }
2029
2030 case SHAPE_T::CIRCLE:
2031 {
2032 if( solidFill )
2033 effectiveShapes.emplace_back( new SHAPE_CIRCLE( getCenter(), GetRadius() ) );
2034
2035 if( width > 0 || !solidFill )
2036 effectiveShapes.emplace_back( new SHAPE_ARC( getCenter(), GetEnd(), ANGLE_360, width ) );
2037
2038 break;
2039 }
2040
2041 case SHAPE_T::BEZIER:
2042 {
2043 std::vector<VECTOR2I> bezierPoints = buildBezierToSegmentsPointsList( getMaxError() );
2044 VECTOR2I start_pt = bezierPoints[0];
2045
2046 for( unsigned int jj = 1; jj < bezierPoints.size(); jj++ )
2047 {
2048 VECTOR2I end_pt = bezierPoints[jj];
2049 effectiveShapes.emplace_back( new SHAPE_SEGMENT( start_pt, end_pt, width ) );
2050 start_pt = end_pt;
2051 }
2052
2053 break;
2054 }
2055
2056 case SHAPE_T::POLY:
2057 {
2058 if( GetPolyShape().OutlineCount() == 0 ) // malformed/empty polygon
2059 break;
2060
2061 for( int ii = 0; ii < GetPolyShape().OutlineCount(); ++ii )
2062 {
2063 const SHAPE_LINE_CHAIN& l = GetPolyShape().COutline( ii );
2064
2065 if( solidFill )
2066 effectiveShapes.emplace_back( new SHAPE_SIMPLE( l ) );
2067
2068 if( width > 0 || !IsSolidFill() || aEdgeOnly )
2069 {
2070 int segCount = l.SegmentCount();
2071
2072 if( aLineChainOnly && l.IsClosed() )
2073 segCount--; // Treat closed chain as open
2074
2075 for( int jj = 0; jj < segCount; jj++ )
2076 effectiveShapes.emplace_back( new SHAPE_SEGMENT( l.CSegment( jj ), width ) );
2077 }
2078 }
2079 }
2080 break;
2081
2082 default:
2084 break;
2085 }
2086
2087 return effectiveShapes;
2088}
2089
2090
2091std::vector<VECTOR2I> EDA_SHAPE::GetPolyPoints() const
2092{
2093 std::vector<VECTOR2I> points;
2094
2095 for( int ii = 0; ii < GetPolyShape().OutlineCount(); ++ii )
2096 {
2097 const SHAPE_LINE_CHAIN& outline = GetPolyShape().COutline( ii );
2098 int pointCount = outline.PointCount();
2099
2100 if( pointCount )
2101 {
2102 points.reserve( points.size() + pointCount );
2103
2104 for( const VECTOR2I& pt : outline.CPoints() )
2105 points.emplace_back( pt );
2106 }
2107 }
2108
2109 return points;
2110}
2111
2112
2114{
2115 if( !m_poly )
2116 m_poly = std::make_unique<SHAPE_POLY_SET>();
2117
2118 return *m_poly;
2119}
2120
2122{
2123 if( !m_poly )
2124 m_poly = std::make_unique<SHAPE_POLY_SET>();
2125
2126 return *m_poly;
2127}
2128
2129
2131{
2132 // return true if the polygonal shape is valid (has more than 2 points)
2133 return GetPolyShape().OutlineCount() > 0 && GetPolyShape().Outline( 0 ).PointCount() > 2;
2134}
2135
2136
2138{
2139 // return the number of corners of the polygonal shape
2140 // this shape is expected to be only one polygon without hole
2141 return GetPolyShape().OutlineCount() ? GetPolyShape().VertexCount( 0 ) : 0;
2142}
2143
2144
2145void EDA_SHAPE::beginEdit( const VECTOR2I& aPosition )
2146{
2147 switch( GetShape() )
2148 {
2149 case SHAPE_T::SEGMENT:
2150 case SHAPE_T::CIRCLE:
2151 case SHAPE_T::RECTANGLE:
2152 SetStart( aPosition );
2153 SetEnd( aPosition );
2154 break;
2155
2156 case SHAPE_T::ARC:
2157 SetArcGeometry( aPosition, aPosition, aPosition );
2158 m_editState = 1;
2159 break;
2160
2161 case SHAPE_T::BEZIER:
2162 SetStart( aPosition );
2163 SetEnd( aPosition );
2164 SetBezierC1( aPosition );
2165 SetBezierC2( aPosition );
2166 m_editState = 1;
2167
2169 break;
2170
2171 case SHAPE_T::POLY:
2173 GetPolyShape().Outline( 0 ).SetClosed( false );
2174
2175 // Start and end of the first segment (co-located for now)
2176 GetPolyShape().Outline( 0 ).Append( aPosition );
2177 GetPolyShape().Outline( 0 ).Append( aPosition, true );
2178 break;
2179
2180 default:
2182 }
2183}
2184
2185
2186bool EDA_SHAPE::continueEdit( const VECTOR2I& aPosition )
2187{
2188 switch( GetShape() )
2189 {
2190 case SHAPE_T::ARC:
2191 case SHAPE_T::SEGMENT:
2192 case SHAPE_T::CIRCLE:
2193 case SHAPE_T::RECTANGLE:
2194 return false;
2195
2196 case SHAPE_T::BEZIER:
2197 if( m_editState == 3 )
2198 return false;
2199
2200 m_editState++;
2201 return true;
2202
2203 case SHAPE_T::POLY:
2204 {
2205 SHAPE_LINE_CHAIN& poly = GetPolyShape().Outline( 0 );
2206
2207 // do not add zero-length segments
2208 if( poly.CPoint( (int) poly.GetPointCount() - 2 ) != poly.CLastPoint() )
2209 poly.Append( aPosition, true );
2210 }
2211 return true;
2212
2213 default:
2215 return false;
2216 }
2217}
2218
2219
2220void EDA_SHAPE::calcEdit( const VECTOR2I& aPosition )
2221{
2222#define sq( x ) pow( x, 2 )
2223
2224 switch( GetShape() )
2225 {
2226 case SHAPE_T::SEGMENT:
2227 case SHAPE_T::CIRCLE:
2228 case SHAPE_T::RECTANGLE:
2229 SetEnd( aPosition );
2230 break;
2231
2232 case SHAPE_T::BEZIER:
2233 {
2234 switch( m_editState )
2235 {
2236 case 0:
2237 SetStart( aPosition );
2238 SetEnd( aPosition );
2239 SetBezierC1( aPosition );
2240 SetBezierC2( aPosition );
2241 break;
2242
2243 case 1:
2244 SetBezierC2( aPosition );
2245 SetEnd( aPosition );
2246 break;
2247
2248 case 2:
2249 SetBezierC1( aPosition );
2250 break;
2251
2252 case 3:
2253 SetBezierC2( aPosition );
2254 break;
2255 }
2256
2258 }
2259 break;
2260
2261 case SHAPE_T::ARC:
2262 {
2263 double radius = GetRadius();
2264 EDA_ANGLE lastAngle = GetArcAngle();
2265
2266 // Edit state 0: drawing: place start
2267 // Edit state 1: drawing: place end (center calculated for 90-degree subtended angle)
2268 // Edit state 2: point edit: move start (center calculated for invariant subtended angle)
2269 // Edit state 3: point edit: move end (center calculated for invariant subtended angle)
2270 // Edit state 4: point edit: move center
2271 // Edit state 5: point edit: move arc-mid-point
2272
2273 switch( m_editState )
2274 {
2275 case 0:
2276 SetArcGeometry( aPosition, aPosition, aPosition );
2277 return;
2278
2279 case 1:
2280 m_end = aPosition;
2281 radius = m_start.Distance( m_end ) * M_SQRT1_2;
2282 break;
2283
2284 case 2:
2285 case 3:
2286 {
2287 VECTOR2I v = m_start - m_end;
2288 double chordBefore = v.SquaredEuclideanNorm();
2289
2290 if( m_editState == 2 )
2291 m_start = aPosition;
2292 else
2293 m_end = aPosition;
2294
2295 v = m_start - m_end;
2296
2297 double chordAfter = v.SquaredEuclideanNorm();
2298 double ratio = 0.0;
2299
2300 if( chordBefore > 0 )
2301 ratio = chordAfter / chordBefore;
2302
2303 if( ratio != 0 )
2304 radius = std::max( sqrt( sq( radius ) * ratio ), sqrt( chordAfter ) / 2 );
2305 break;
2306 }
2307
2308 case 4:
2309 {
2310 double radialA = m_start.Distance( aPosition );
2311 double radialB = m_end.Distance( aPosition );
2312 radius = ( radialA + radialB ) / 2.0;
2313 break;
2314 }
2315
2316 case 5:
2317 SetArcGeometry( GetStart(), aPosition, GetEnd() );
2318 return;
2319 }
2320
2321 // Calculate center based on start, end, and radius
2322 //
2323 // Let 'l' be the length of the chord and 'm' the middle point of the chord
2324 double l = m_start.Distance( m_end );
2325 VECTOR2D m = ( m_start + m_end ) / 2;
2326 double sqRadDiff = ( radius * radius ) - ( l * l ) / 4.0;
2327
2328 // Calculate 'd', the vector from the chord midpoint to the center
2329 VECTOR2D d;
2330
2331 if( l > 0 && sqRadDiff >= 0 )
2332 {
2333 d.x = sqrt( sqRadDiff ) * ( m_start.y - m_end.y ) / l;
2334 d.y = sqrt( sqRadDiff ) * ( m_end.x - m_start.x ) / l;
2335 }
2336
2337 VECTOR2I c1 = KiROUND( m + d );
2338 VECTOR2I c2 = KiROUND( m - d );
2339
2340 // Solution gives us 2 centers; we need to pick one:
2341 switch( m_editState )
2342 {
2343 case 1:
2344 // Keep arc clockwise while drawing i.e. arc angle = 90 deg.
2345 // it can be 90 or 270 deg depending on the arc center choice (c1 or c2)
2346 m_arcCenter = c1; // first trial
2347
2348 if( GetArcAngle() > ANGLE_180 )
2349 m_arcCenter = c2;
2350
2351 break;
2352
2353 case 2:
2354 case 3:
2355 // Pick the one of c1, c2 to keep arc on the same side
2356 m_arcCenter = c1; // first trial
2357
2358 if( ( lastAngle < ANGLE_180 ) != ( GetArcAngle() < ANGLE_180 ) )
2359 m_arcCenter = c2;
2360
2361 break;
2362
2363 case 4:
2364 // Pick the one closer to the mouse position
2365 m_arcCenter = c1.Distance( aPosition ) < c2.Distance( aPosition ) ? c1 : c2;
2366 break;
2367 }
2368
2369 break;
2370 }
2371
2372 case SHAPE_T::POLY:
2373 GetPolyShape().Outline( 0 ).SetPoint( GetPolyShape().Outline( 0 ).GetPointCount() - 1,
2374 aPosition );
2375 break;
2376
2377 default:
2379 }
2380}
2381
2382
2383void EDA_SHAPE::endEdit( bool aClosed )
2384{
2385 switch( GetShape() )
2386 {
2387 case SHAPE_T::ARC:
2388 case SHAPE_T::SEGMENT:
2389 case SHAPE_T::CIRCLE:
2390 case SHAPE_T::RECTANGLE:
2391 case SHAPE_T::BEZIER:
2392 break;
2393
2394 case SHAPE_T::POLY:
2395 {
2396 SHAPE_LINE_CHAIN& poly = GetPolyShape().Outline( 0 );
2397
2398 // do not include last point twice
2399 if( poly.GetPointCount() > 2 )
2400 {
2401 if( poly.CPoint( poly.GetPointCount() - 2 ) == poly.CLastPoint() )
2402 {
2403 poly.SetClosed( aClosed );
2404 }
2405 else
2406 {
2407 poly.SetClosed( false );
2408 poly.Remove( poly.GetPointCount() - 1 );
2409 }
2410 }
2411
2412 break;
2413 }
2414
2415 default:
2417 }
2418}
2419
2420
2422{
2423 EDA_SHAPE* image = dynamic_cast<EDA_SHAPE*>( aImage );
2424 assert( image );
2425
2426 #define SWAPITEM( x ) std::swap( x, image->x )
2427 SWAPITEM( m_stroke );
2428 SWAPITEM( m_start );
2429 SWAPITEM( m_end );
2431 SWAPITEM( m_shape );
2435 SWAPITEM( m_poly );
2437 SWAPITEM( m_fill );
2441 #undef SWAPITEM
2442
2443 m_hatchingDirty = true;
2444}
2445
2446
2447int EDA_SHAPE::Compare( const EDA_SHAPE* aOther ) const
2448{
2449#define EPSILON 2 // Should be enough for rounding errors on calculated items
2450
2451#define TEST( a, b ) { if( a != b ) return a - b; }
2452#define TEST_E( a, b ) { if( abs( a - b ) > EPSILON ) return a - b; }
2453#define TEST_PT( a, b ) { TEST_E( a.x, b.x ); TEST_E( a.y, b.y ); }
2454
2455 TEST_PT( m_start, aOther->m_start );
2456 TEST_PT( m_end, aOther->m_end );
2457
2458 TEST( (int) m_shape, (int) aOther->m_shape );
2459
2461 {
2463 }
2464 else if( m_shape == SHAPE_T::ARC )
2465 {
2466 TEST_PT( GetArcMid(), aOther->GetArcMid() );
2467 }
2468 else if( m_shape == SHAPE_T::BEZIER )
2469 {
2470 TEST_PT( m_bezierC1, aOther->m_bezierC1 );
2471 TEST_PT( m_bezierC2, aOther->m_bezierC2 );
2472 }
2473 else if( m_shape == SHAPE_T::POLY )
2474 {
2475 TEST( GetPolyShape().TotalVertices(), aOther->GetPolyShape().TotalVertices() );
2476 }
2477
2478 for( size_t ii = 0; ii < m_bezierPoints.size(); ++ii )
2479 TEST_PT( m_bezierPoints[ii], aOther->m_bezierPoints[ii] );
2480
2481 for( int ii = 0; ii < GetPolyShape().TotalVertices(); ++ii )
2482 TEST_PT( GetPolyShape().CVertex( ii ), aOther->GetPolyShape().CVertex( ii ) );
2483
2484 TEST_E( m_stroke.GetWidth(), aOther->m_stroke.GetWidth() );
2485 TEST( (int) m_stroke.GetLineStyle(), (int) aOther->m_stroke.GetLineStyle() );
2486 TEST( (int) m_fill, (int) aOther->m_fill );
2487
2488 return 0;
2489}
2490
2491
2492void EDA_SHAPE::TransformShapeToPolygon( SHAPE_POLY_SET& aBuffer, int aClearance, int aError,
2493 ERROR_LOC aErrorLoc, bool ignoreLineWidth, bool includeFill ) const
2494{
2495 bool solidFill = IsSolidFill() || ( IsHatchedFill() && !includeFill ) || IsProxyItem();
2496 int width = ignoreLineWidth ? 0 : GetWidth();
2497
2498 width += 2 * aClearance;
2499
2500 switch( m_shape )
2501 {
2502 case SHAPE_T::CIRCLE:
2503 {
2504 int r = GetRadius();
2505
2506 if( solidFill )
2507 TransformCircleToPolygon( aBuffer, getCenter(), r + width / 2, aError, aErrorLoc );
2508 else
2509 TransformRingToPolygon( aBuffer, getCenter(), r, width, aError, aErrorLoc );
2510
2511 break;
2512 }
2513
2514 case SHAPE_T::RECTANGLE:
2515 {
2516 if( GetCornerRadius() > 0 )
2517 {
2518 // Use specialized function for rounded rectangles
2520 VECTOR2I position = GetStart() + size / 2; // Center position
2521
2522 if( solidFill )
2523 {
2525 0.0, 0, width / 2, aError, aErrorLoc );
2526 }
2527 else
2528 {
2530 SHAPE_POLY_SET poly;
2531 rr.TransformToPolygon( poly, aError );
2532 SHAPE_LINE_CHAIN& outline = poly.Outline( 0 );
2533 outline.SetClosed( true );
2534
2535 std::set<size_t> arcsHandled;
2536
2537 for( int ii = 0; ii < outline.SegmentCount(); ++ii )
2538 {
2539 if( outline.IsArcSegment( ii ) )
2540 {
2541 size_t arcIndex = outline.ArcIndex( ii );
2542
2543 if( arcsHandled.contains( arcIndex ) )
2544 continue;
2545
2546 arcsHandled.insert( arcIndex );
2547
2548 const SHAPE_ARC& arc = outline.Arc( arcIndex );
2549 TransformArcToPolygon( aBuffer, arc.GetP0(), arc.GetArcMid(), arc.GetP1(), width, aError,
2550 aErrorLoc );
2551 }
2552 else
2553 {
2554 const SEG& seg = outline.GetSegment( ii );
2555 TransformOvalToPolygon( aBuffer, seg.A, seg.B, width, aError, aErrorLoc );
2556 }
2557 }
2558 }
2559 }
2560 else
2561 {
2562 std::vector<VECTOR2I> pts = GetRectCorners();
2563
2564 if( solidFill )
2565 {
2566 aBuffer.NewOutline();
2567
2568 for( const VECTOR2I& pt : pts )
2569 aBuffer.Append( pt );
2570 }
2571
2572 if( width > 0 || !solidFill )
2573 {
2574 // Add in segments
2575 TransformOvalToPolygon( aBuffer, pts[0], pts[1], width, aError, aErrorLoc );
2576 TransformOvalToPolygon( aBuffer, pts[1], pts[2], width, aError, aErrorLoc );
2577 TransformOvalToPolygon( aBuffer, pts[2], pts[3], width, aError, aErrorLoc );
2578 TransformOvalToPolygon( aBuffer, pts[3], pts[0], width, aError, aErrorLoc );
2579 }
2580 }
2581
2582 break;
2583 }
2584
2585 case SHAPE_T::ARC:
2586 TransformArcToPolygon( aBuffer, GetStart(), GetArcMid(), GetEnd(), width, aError, aErrorLoc );
2587 break;
2588
2589 case SHAPE_T::SEGMENT:
2590 TransformOvalToPolygon( aBuffer, GetStart(), GetEnd(), width, aError, aErrorLoc );
2591 break;
2592
2593 case SHAPE_T::POLY:
2594 {
2595 if( !IsPolyShapeValid() )
2596 break;
2597
2598 if( solidFill )
2599 {
2600 for( int ii = 0; ii < GetPolyShape().OutlineCount(); ++ii )
2601 {
2602 const SHAPE_LINE_CHAIN& poly = GetPolyShape().Outline( ii );
2603 SHAPE_POLY_SET tmp;
2604 tmp.NewOutline();
2605
2606 for( int jj = 0; jj < (int) poly.GetPointCount(); ++jj )
2607 tmp.Append( poly.GetPoint( jj ) );
2608
2609 if( width > 0 )
2610 {
2611 int inflate = width / 2;
2612
2613 if( aErrorLoc == ERROR_OUTSIDE )
2614 inflate += aError;
2615
2616 tmp.Inflate( inflate, CORNER_STRATEGY::ROUND_ALL_CORNERS, aError );
2617 }
2618
2619 aBuffer.Append( tmp );
2620 }
2621 }
2622 else
2623 {
2624 for( int ii = 0; ii < GetPolyShape().OutlineCount(); ++ii )
2625 {
2626 const SHAPE_LINE_CHAIN& poly = GetPolyShape().Outline( ii );
2627
2628 for( int jj = 0; jj < (int) poly.SegmentCount(); ++jj )
2629 {
2630 const SEG& seg = poly.GetSegment( jj );
2631 TransformOvalToPolygon( aBuffer, seg.A, seg.B, width, aError, aErrorLoc );
2632 }
2633 }
2634 }
2635
2636 break;
2637 }
2638
2639 case SHAPE_T::BEZIER:
2640 {
2641 std::vector<VECTOR2I> ctrlPts = { GetStart(), GetBezierC1(), GetBezierC2(), GetEnd() };
2642 BEZIER_POLY converter( ctrlPts );
2643 std::vector<VECTOR2I> poly;
2644 converter.GetPoly( poly, aError );
2645
2646 for( unsigned ii = 1; ii < poly.size(); ii++ )
2647 TransformOvalToPolygon( aBuffer, poly[ii - 1], poly[ii], width, aError, aErrorLoc );
2648
2649 break;
2650 }
2651
2652 default:
2654 break;
2655 }
2656
2657 if( IsHatchedFill() && includeFill )
2658 {
2659 for( int ii = 0; ii < GetHatching().OutlineCount(); ++ii )
2660 aBuffer.AddOutline( GetHatching().COutline( ii ) );
2661 }
2662}
2663
2664
2665void EDA_SHAPE::SetWidth( int aWidth )
2666{
2667 m_stroke.SetWidth( aWidth );
2668 m_hatchingDirty = true;
2669}
2670
2671
2673{
2674 m_stroke.SetLineStyle( aStyle );
2675}
2676
2677
2679{
2680 if( m_stroke.GetLineStyle() != LINE_STYLE::DEFAULT )
2681 return m_stroke.GetLineStyle();
2682
2683 return LINE_STYLE::SOLID;
2684}
2685
2686
2687bool EDA_SHAPE::operator==( const EDA_SHAPE& aOther ) const
2688{
2689 if( GetShape() != aOther.GetShape() )
2690 return false;
2691
2692 if( m_fill != aOther.m_fill )
2693 return false;
2694
2695 if( m_stroke.GetWidth() != aOther.m_stroke.GetWidth() )
2696 return false;
2697
2698 if( m_stroke.GetLineStyle() != aOther.m_stroke.GetLineStyle() )
2699 return false;
2700
2701 if( m_fillColor != aOther.m_fillColor )
2702 return false;
2703
2704 if( m_start != aOther.m_start )
2705 return false;
2706
2707 if( m_end != aOther.m_end )
2708 return false;
2709
2710 if( m_arcCenter != aOther.m_arcCenter )
2711 return false;
2712
2713 if( m_bezierC1 != aOther.m_bezierC1 )
2714 return false;
2715
2716 if( m_bezierC2 != aOther.m_bezierC2 )
2717 return false;
2718
2719 if( m_bezierPoints != aOther.m_bezierPoints )
2720 return false;
2721
2722 for( int ii = 0; ii < GetPolyShape().TotalVertices(); ++ii )
2723 {
2724 if( GetPolyShape().CVertex( ii ) != aOther.GetPolyShape().CVertex( ii ) )
2725 return false;
2726 }
2727
2728 return true;
2729}
2730
2731
2732double EDA_SHAPE::Similarity( const EDA_SHAPE& aOther ) const
2733{
2734 if( GetShape() != aOther.GetShape() )
2735 return 0.0;
2736
2737 double similarity = 1.0;
2738
2739 if( m_fill != aOther.m_fill )
2740 similarity *= 0.9;
2741
2742 if( m_stroke.GetWidth() != aOther.m_stroke.GetWidth() )
2743 similarity *= 0.9;
2744
2745 if( m_stroke.GetLineStyle() != aOther.m_stroke.GetLineStyle() )
2746 similarity *= 0.9;
2747
2748 if( m_fillColor != aOther.m_fillColor )
2749 similarity *= 0.9;
2750
2751 if( m_start != aOther.m_start )
2752 similarity *= 0.9;
2753
2754 if( m_end != aOther.m_end )
2755 similarity *= 0.9;
2756
2757 if( m_arcCenter != aOther.m_arcCenter )
2758 similarity *= 0.9;
2759
2760 if( m_bezierC1 != aOther.m_bezierC1 )
2761 similarity *= 0.9;
2762
2763 if( m_bezierC2 != aOther.m_bezierC2 )
2764 similarity *= 0.9;
2765
2766 {
2767 int m = m_bezierPoints.size();
2768 int n = aOther.m_bezierPoints.size();
2769
2770 size_t longest = alg::longest_common_subset( m_bezierPoints, aOther.m_bezierPoints );
2771
2772 similarity *= std::pow( 0.9, m + n - 2 * longest );
2773 }
2774
2775 {
2776 int m = GetPolyShape().TotalVertices();
2777 int n = aOther.GetPolyShape().TotalVertices();
2778 std::vector<VECTOR2I> poly;
2779 std::vector<VECTOR2I> otherPoly;
2780 VECTOR2I lastPt( 0, 0 );
2781
2782 // We look for the longest common subset of the two polygons, but we need to
2783 // offset each point because we're actually looking for overall similarity, not just
2784 // exact matches. So if the zone is moved by 1IU, we only want one point to be
2785 // considered "moved" rather than the entire polygon. In this case, the first point
2786 // will not be a match but the rest of the sequence will.
2787 for( int ii = 0; ii < m; ++ii )
2788 {
2789 poly.emplace_back( lastPt - GetPolyShape().CVertex( ii ) );
2790 lastPt = GetPolyShape().CVertex( ii );
2791 }
2792
2793 lastPt = VECTOR2I( 0, 0 );
2794
2795 for( int ii = 0; ii < n; ++ii )
2796 {
2797 otherPoly.emplace_back( lastPt - aOther.GetPolyShape().CVertex( ii ) );
2798 lastPt = aOther.GetPolyShape().CVertex( ii );
2799 }
2800
2801 size_t longest = alg::longest_common_subset( poly, otherPoly );
2802
2803 similarity *= std::pow( 0.9, m + n - 2 * longest );
2804 }
2805
2806 return similarity;
2807}
2808
2809
2813
2814
2815static struct EDA_SHAPE_DESC
2816{
2818 {
2820 .Map( SHAPE_T::SEGMENT, _HKI( "Segment" ) )
2821 .Map( SHAPE_T::RECTANGLE, _HKI( "Rectangle" ) )
2822 .Map( SHAPE_T::ARC, _HKI( "Arc" ) )
2823 .Map( SHAPE_T::CIRCLE, _HKI( "Circle" ) )
2824 .Map( SHAPE_T::POLY, _HKI( "Polygon" ) )
2825 .Map( SHAPE_T::BEZIER, _HKI( "Bezier" ) );
2826
2828
2829 if( lineStyleEnum.Choices().GetCount() == 0 )
2830 {
2831 lineStyleEnum.Map( LINE_STYLE::SOLID, _HKI( "Solid" ) )
2832 .Map( LINE_STYLE::DASH, _HKI( "Dashed" ) )
2833 .Map( LINE_STYLE::DOT, _HKI( "Dotted" ) )
2834 .Map( LINE_STYLE::DASHDOT, _HKI( "Dash-Dot" ) )
2835 .Map( LINE_STYLE::DASHDOTDOT, _HKI( "Dash-Dot-Dot" ) );
2836 }
2837
2839
2840 if( hatchModeEnum.Choices().GetCount() == 0 )
2841 {
2842 hatchModeEnum.Map( UI_FILL_MODE::NONE, _HKI( "None" ) );
2843 hatchModeEnum.Map( UI_FILL_MODE::SOLID, _HKI( "Solid" ) );
2844 hatchModeEnum.Map( UI_FILL_MODE::HATCH, _HKI( "Hatch" ) );
2845 hatchModeEnum.Map( UI_FILL_MODE::REVERSE_HATCH, _HKI( "Reverse Hatch" ) );
2846 hatchModeEnum.Map( UI_FILL_MODE::CROSS_HATCH, _HKI( "Cross-hatch" ) );
2847 }
2848
2851
2852 auto isNotPolygonOrCircle =
2853 []( INSPECTABLE* aItem ) -> bool
2854 {
2855 // Polygons, unlike other shapes, have no meaningful start or end coordinates
2856 if( EDA_SHAPE* shape = dynamic_cast<EDA_SHAPE*>( aItem ) )
2857 return shape->GetShape() != SHAPE_T::POLY && shape->GetShape() != SHAPE_T::CIRCLE;
2858
2859 return false;
2860 };
2861
2862 auto isCircle =
2863 []( INSPECTABLE* aItem ) -> bool
2864 {
2865 // Polygons, unlike other shapes, have no meaningful start or end coordinates
2866 if( EDA_SHAPE* shape = dynamic_cast<EDA_SHAPE*>( aItem ) )
2867 return shape->GetShape() == SHAPE_T::CIRCLE;
2868
2869 return false;
2870 };
2871
2872 auto isRectangle =
2873 []( INSPECTABLE* aItem ) -> bool
2874 {
2875 // Polygons, unlike other shapes, have no meaningful start or end coordinates
2876 if( EDA_SHAPE* shape = dynamic_cast<EDA_SHAPE*>( aItem ) )
2877 return shape->GetShape() == SHAPE_T::RECTANGLE;
2878
2879 return false;
2880 };
2881
2882 const wxString shapeProps = _HKI( "Shape Properties" );
2883
2884 auto shape = new PROPERTY_ENUM<EDA_SHAPE, SHAPE_T>( _HKI( "Shape" ),
2886 propMgr.AddProperty( shape, shapeProps );
2887
2888 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "Start X" ),
2891 shapeProps )
2892 .SetAvailableFunc( isNotPolygonOrCircle );
2893 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "Start Y" ),
2896 shapeProps )
2897 .SetAvailableFunc( isNotPolygonOrCircle );
2898
2899 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "Center X" ),
2902 shapeProps )
2903 .SetAvailableFunc( isCircle );
2904
2905 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "Center Y" ),
2908 shapeProps )
2909 .SetAvailableFunc( isCircle );
2910
2911 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "Radius" ),
2914 shapeProps )
2915 .SetAvailableFunc( isCircle );
2916
2917 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "End X" ),
2920 shapeProps )
2921 .SetAvailableFunc( isNotPolygonOrCircle );
2922
2923 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "End Y" ),
2926 shapeProps )
2927 .SetAvailableFunc( isNotPolygonOrCircle );
2928
2929 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "Width" ),
2932 shapeProps )
2933 .SetAvailableFunc( isRectangle );
2934
2935 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "Height" ),
2938 shapeProps )
2939 .SetAvailableFunc( isRectangle );
2940
2941 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "Corner Radius" ),
2944 shapeProps )
2945 .SetAvailableFunc( isRectangle )
2946 .SetValidator( []( const wxAny&& aValue, EDA_ITEM* aItem ) -> VALIDATOR_RESULT
2947 {
2948 wxASSERT_MSG( aValue.CheckType<int>(),
2949 "Expecting int-containing value" );
2950
2951 int radius = aValue.As<int>();
2952
2953 EDA_SHAPE* prop_shape = dynamic_cast<EDA_SHAPE*>( aItem );
2954
2955 if( !prop_shape )
2956 return std::nullopt;
2957
2958 int maxRadius = std::min( prop_shape->GetRectangleWidth(),
2959 prop_shape->GetRectangleHeight() ) / 2;
2960
2961 if( radius > maxRadius )
2962 return std::make_unique<VALIDATION_ERROR_TOO_LARGE<int>>( radius, maxRadius );
2963 else if( radius < 0 )
2964 return std::make_unique<VALIDATION_ERROR_TOO_SMALL<int>>( radius, 0 );
2965
2966 return std::nullopt;
2967 } );
2968
2969 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "Line Width" ),
2971 shapeProps );
2972
2973 propMgr.AddProperty( new PROPERTY_ENUM<EDA_SHAPE, LINE_STYLE>( _HKI( "Line Style" ),
2975 shapeProps );
2976
2977 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, COLOR4D>( _HKI( "Line Color" ),
2979 shapeProps )
2981
2982 auto angle = new PROPERTY<EDA_SHAPE, EDA_ANGLE>( _HKI( "Angle" ),
2985 angle->SetAvailableFunc(
2986 [=]( INSPECTABLE* aItem ) -> bool
2987 {
2988 if( EDA_SHAPE* curr_shape = dynamic_cast<EDA_SHAPE*>( aItem ) )
2989 return curr_shape->GetShape() == SHAPE_T::ARC;
2990
2991 return false;
2992 } );
2993 propMgr.AddProperty( angle, shapeProps );
2994
2995 auto fillAvailable =
2996 [=]( INSPECTABLE* aItem ) -> bool
2997 {
2998 if( EDA_ITEM* edaItem = dynamic_cast<EDA_ITEM*>( aItem ) )
2999 {
3000 // For some reason masking "Filled" and "Fill Color" at the
3001 // PCB_TABLECELL level doesn't work.
3002 if( edaItem->Type() == PCB_TABLECELL_T || edaItem->Type() == PCB_TEXTBOX_T )
3003 return false;
3004 }
3005
3006 if( EDA_SHAPE* edaShape = dynamic_cast<EDA_SHAPE*>( aItem ) )
3007 {
3008 switch( edaShape->GetShape() )
3009 {
3010 case SHAPE_T::POLY:
3011 case SHAPE_T::RECTANGLE:
3012 case SHAPE_T::CIRCLE:
3013 case SHAPE_T::BEZIER:
3014 return true;
3015
3016 default:
3017 return false;
3018 }
3019 }
3020
3021 return false;
3022 };
3023
3026 shapeProps )
3027 .SetAvailableFunc( fillAvailable );
3028
3029 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, COLOR4D>( _HKI( "Fill Color" ),
3031 shapeProps )
3032 .SetAvailableFunc( fillAvailable )
3034 }
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:100
UI_FILL_MODE GetFillModeProp() const
virtual int GetHatchLineSpacing() const
Definition eda_shape.h:172
EDA_ANGLE GetArcAngle() const
SHAPE_T m_shape
Definition eda_shape.h:514
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:204
bool m_proxyItem
Definition eda_shape.h:539
int m_cornerRadius
Definition eda_shape.h:524
bool m_hatchingDirty
Definition eda_shape.h:520
bool m_endsSwapped
Definition eda_shape.h:513
const VECTOR2I & GetBezierC2() const
Definition eda_shape.h:271
void SetBezierC2(const VECTOR2I &aPt)
Definition eda_shape.h:270
void move(const VECTOR2I &aMoveVector)
void SetCenter(const VECTOR2I &aCenter)
VECTOR2I getCenter() const
int GetStartY() const
Definition eda_shape.h:187
void SetFillModeProp(UI_FILL_MODE)
int m_editState
Definition eda_shape.h:538
virtual int getMaxError() const
Definition eda_shape.h:506
void rotate(const VECTOR2I &aRotCentre, const EDA_ANGLE &aAngle)
const std::vector< VECTOR2I > buildBezierToSegmentsPointsList(int aMaxError) const
const SHAPE_POLY_SET & GetHatching() const
FILL_T GetFillMode() const
Definition eda_shape.h:154
virtual ~EDA_SHAPE()
Definition eda_shape.cpp:70
void SetCornerRadius(int aRadius)
long long int m_rectangleHeight
Definition eda_shape.h:522
std::unique_ptr< EDA_SHAPE_HATCH_CACHE_DATA > m_hatchingCache
Definition eda_shape.h:519
void SetEndY(int aY)
Definition eda_shape.h:239
virtual int GetEffectiveWidth() const
Definition eda_shape.h:170
std::vector< VECTOR2I > GetPolyPoints() const
Duplicate the polygon outlines into a flat list of VECTOR2I points.
COLOR4D GetLineColor() const
Definition eda_shape.h:178
int GetEndX() const
Definition eda_shape.h:230
std::vector< SHAPE * > makeEffectiveShapes(bool aEdgeOnly, bool aLineChainOnly=false, bool aHittesting=false) const
Make a set of SHAPE objects representing the EDA_SHAPE.
int GetRectangleWidth() const
void SetLineStyle(const LINE_STYLE aStyle)
void calcEdit(const VECTOR2I &aPosition)
void SetStartY(int y)
Definition eda_shape.h:197
virtual std::vector< SHAPE * > MakeEffectiveShapes(bool aEdgeOnly=false) const
Make a set of SHAPE objects representing the EDA_SHAPE.
Definition eda_shape.h:390
SHAPE_POLY_SET & GetPolyShape()
void SetCenterY(int y)
Definition eda_shape.h:211
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:490
bool operator==(const EDA_SHAPE &aOther) const
int GetRadius() const
SHAPE_T GetShape() const
Definition eda_shape.h:181
void SetPolyShape(const SHAPE_POLY_SET &aShape)
Definition eda_shape.h:356
bool Deserialize(const google::protobuf::Any &aContainer) override
Deserializes the given protobuf message into this object.
const std::vector< SEG > & GetHatchLines() const
void SetRectangleHeight(const int &aHeight)
SHAPE_POLY_SET & hatching() const
bool IsHatchedFill() const
Definition eda_shape.h:136
virtual SHAPE_POLY_SET getHatchingKnockouts() const
Definition eda_shape.h:456
VECTOR2I m_arcCenter
Definition eda_shape.h:529
void SetCenterX(int x)
Definition eda_shape.h:218
virtual void SetFilled(bool aFlag)
Definition eda_shape.h:148
virtual bool IsFilledForHitTesting() const
Definition eda_shape.h:143
bool continueEdit(const VECTOR2I &aPosition)
wxString ShowShape() const
ARC_MID m_arcMidData
Definition eda_shape.h:530
void SetFillColor(const COLOR4D &aColor)
Definition eda_shape.h:166
int GetEndY() const
Definition eda_shape.h:229
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:246
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:171
bool IsSolidFill() const
Definition eda_shape.h:129
void flip(const VECTOR2I &aCentre, FLIP_DIRECTION aFlipDirection)
EDA_SHAPE(SHAPE_T aType, int aLineWidth, FILL_T aFill)
Definition eda_shape.cpp:54
std::vector< SEG > & hatchLines() const
void beginEdit(const VECTOR2I &aStartPoint)
VECTOR2I m_start
Definition eda_shape.h:526
int GetPointCount() const
const VECTOR2I & GetEnd() const
Return the ending point of the graphic.
Definition eda_shape.h:228
bool IsClosed() const
void SetRadius(int aX)
Definition eda_shape.h:253
void SetStart(const VECTOR2I &aStart)
Definition eda_shape.h:190
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:186
void SetLineColor(const COLOR4D &aColor)
Definition eda_shape.h:177
COLOR4D GetFillColor() const
Definition eda_shape.h:165
void SetRectangle(const long long int &aHeight, const long long int &aWidth)
void SetShape(SHAPE_T aShape)
Definition eda_shape.h:180
void SwapShape(EDA_SHAPE *aImage)
std::vector< VECTOR2I > GetRectCorners() const
std::vector< VECTOR2I > m_bezierPoints
Definition eda_shape.h:535
bool IsAnyFill() const
Definition eda_shape.h:124
void setPosition(const VECTOR2I &aPos)
virtual bool IsProxyItem() const
Definition eda_shape.h:121
void computeArcBBox(BOX2I &aBBox) const
virtual void UpdateHatching() const
void SetEnd(const VECTOR2I &aEnd)
Definition eda_shape.h:232
void SetRectangleWidth(const int &aWidth)
void SetBezierC1(const VECTOR2I &aPt)
Definition eda_shape.h:267
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:188
double Similarity(const EDA_SHAPE &aOther) const
const VECTOR2I & GetBezierC1() const
Definition eda_shape.h:268
VECTOR2I m_end
Definition eda_shape.h:527
const BOX2I getBoundingBox() const
void SetArcAngleAndEnd(const EDA_ANGLE &aAngle, bool aCheckNegativeAngle=false)
Set the end point from the angle center and start.
int GetRectangleHeight() const
virtual int GetWidth() const
Definition eda_shape.h:169
VECTOR2I getPosition() const
bool IsClockwiseArc() const
STROKE_PARAMS m_stroke
Definition eda_shape.h:515
void SetPolyPoints(const std::vector< VECTOR2I > &aPoints)
wxString getFriendlyName() const
EDA_SHAPE & operator=(const EDA_SHAPE &aOther)
VECTOR2I m_bezierC1
Definition eda_shape.h:532
FILL_T m_fill
Definition eda_shape.h:516
COLOR4D m_fillColor
Definition eda_shape.h:517
void SetWidth(int aWidth)
EDA_ANGLE GetSegmentAngle() const
int GetCornerRadius() const
void SetFillMode(FILL_T aFill)
std::unique_ptr< SHAPE_POLY_SET > m_poly
Definition eda_shape.h:536
long long int m_rectangleWidth
Definition eda_shape.h:523
VECTOR2I m_bezierC2
Definition eda_shape.h:533
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
int Length() const
Return the length (this).
Definition seg.h:343
const VECTOR2I & GetArcMid() const
Definition shape_arc.h:120
const VECTOR2I & GetP1() const
Definition shape_arc.h:119
const VECTOR2I & GetP0() const
Definition shape_arc.h:118
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 SetPoint(int aIndex, const VECTOR2I &aPos)
Move a point to a specific location.
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.
void Rotate(const EDA_ANGLE &aAngle, const VECTOR2I &aCenter={ 0, 0 }) override
Rotate all vertices by a given angle.
void RemoveAllContours()
Remove all outlines & holes (clears) the polygon set.
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 IsEmpty() const
Return true if the set is empty (no polygons at all)
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.
void Mirror(const VECTOR2I &aRef, FLIP_DIRECTION aFlipDirection)
Mirror the line points about y or x (or both)
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.
void Move(const VECTOR2I &aVector) override
void Fracture(bool aSimplify=true)
Convert a set of polygons with holes to a single outline with "slits"/"fractures" connecting the oute...
SHAPE_POLY_SET CloneDropTriangulation() const
void BooleanSubtract(const SHAPE_POLY_SET &b)
Perform boolean polyset difference.
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:553
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:45
@ UNDEFINED
Definition eda_shape.h:46
@ SEGMENT
Definition eda_shape.h:47
@ RECTANGLE
Use RECTANGLE instead of RECT to avoid collision in a Windows header.
Definition eda_shape.h:48
UI_FILL_MODE
Definition eda_shape.h:70
@ REVERSE_HATCH
Definition eda_shape.h:74
@ SOLID
Definition eda_shape.h:72
@ HATCH
Definition eda_shape.h:73
@ NONE
Definition eda_shape.h:71
@ CROSS_HATCH
Definition eda_shape.h:75
FILL_T
Definition eda_shape.h:58
@ NO_FILL
Definition eda_shape.h:59
@ REVERSE_HATCH
Definition eda_shape.h:64
@ HATCH
Definition eda_shape.h:63
@ FILLED_SHAPE
Fill with object color.
Definition eda_shape.h:60
@ CROSS_HATCH
Definition eda_shape.h:65
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_TEXTBOX_T
class PCB_TEXTBOX, wrapped text on a layer
Definition typeinfo.h:90
@ PCB_TABLECELL_T
class PCB_TABLECELL, PCB_TEXTBOX for use in tables
Definition typeinfo.h:92
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:687
VECTOR2< double > VECTOR2D
Definition vector2d.h:686