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 <base_units.h>
31#include <bezier_curves.h>
33#include <eda_draw_frame.h>
34#include <geometry/shape_arc.h>
40#include <geometry/shape_rect.h>
41#include <geometry/roundrect.h>
43#include <geometry/roundrect.h>
44#include <macros.h>
45#include <algorithm>
47#include <properties/property.h>
49#include <math/util.h> // for KiROUND
50#include <eda_item.h>
51#include <plotters/plotter.h>
52#include <api/api_enums.h>
53#include <api/api_utils.h>
54#include <api/common/types/base_types.pb.h>
55
56
57EDA_SHAPE::EDA_SHAPE( SHAPE_T aType, int aLineWidth, FILL_T aFill ) :
58 m_endsSwapped( false ),
59 m_shape( aType ),
61 m_fill( aFill ),
63 m_hatchingDirty( true ),
66 m_cornerRadius( 0 ),
67 m_editState( 0 ),
68 m_proxyItem( false )
69{
70}
71
72
76
77
78EDA_SHAPE::EDA_SHAPE( const SHAPE& aShape ) :
79 m_endsSwapped( false ),
81 m_fill(),
82 m_hatchingDirty( true ),
85 m_cornerRadius( 0 ),
86 m_editState( 0 ),
87 m_proxyItem( false )
88{
89 switch( aShape.Type() )
90 {
91 case SH_RECT:
92 {
93 auto rect = static_cast<const SHAPE_RECT&>( aShape );
94 m_shape = SHAPE_T::RECTANGLE;
95 SetStart( rect.GetPosition() );
96 SetEnd( rect.GetPosition() + rect.GetSize() );
97 break;
98 }
99
100 case SH_SEGMENT:
101 {
102 auto seg = static_cast<const SHAPE_SEGMENT&>( aShape );
103 m_shape = SHAPE_T::SEGMENT;
104 SetStart( seg.GetSeg().A );
105 SetEnd( seg.GetSeg().B );
106 SetWidth( seg.GetWidth() );
107 break;
108 }
109
110 case SH_LINE_CHAIN:
111 {
112 auto line = static_cast<const SHAPE_LINE_CHAIN&>( aShape );
113 m_shape = SHAPE_T::POLY;
114 GetPolyShape() = SHAPE_POLY_SET();
115 GetPolyShape().AddOutline( line );
116 SetWidth( line.Width() );
117 break;
118 }
119
120 case SH_CIRCLE:
121 {
122 auto circle = static_cast<const SHAPE_CIRCLE&>( aShape );
123 m_shape = SHAPE_T::CIRCLE;
124 SetStart( circle.GetCenter() );
125 SetEnd( circle.GetCenter() + circle.GetRadius() );
126 break;
127 }
128
129 case SH_ARC:
130 {
131 auto arc = static_cast<const SHAPE_ARC&>( aShape );
132 m_shape = SHAPE_T::ARC;
133 SetArcGeometry( arc.GetP0(), arc.GetArcMid(), arc.GetP1() );
134 SetWidth( arc.GetWidth() );
135 break;
136 }
137
138 case SH_SIMPLE:
139 {
140 auto poly = static_cast<const SHAPE_SIMPLE&>( aShape );
141 m_shape = SHAPE_T::POLY;
142 poly.TransformToPolygon( GetPolyShape(), 0, ERROR_INSIDE );
143 break;
144 }
145
146 case SH_ELLIPSE:
147 {
148 auto ellipse = static_cast<const SHAPE_ELLIPSE&>( aShape );
149 m_shape = ellipse.IsArc() ? SHAPE_T::ELLIPSE_ARC : SHAPE_T::ELLIPSE;
150 SetEllipseCenter( ellipse.GetCenter() );
151 SetEllipseMajorRadius( ellipse.GetMajorRadius() );
152 SetEllipseMinorRadius( ellipse.GetMinorRadius() );
153 SetEllipseRotation( ellipse.GetRotation() );
154
155 if( ellipse.IsArc() )
156 {
157 SetEllipseStartAngle( ellipse.GetStartAngle() );
158 SetEllipseEndAngle( ellipse.GetEndAngle() );
159 }
160 break;
161 }
162
163 // currently unhandled
164 case SH_POLY_SET:
165 case SH_COMPOUND:
166 case SH_NULL:
168 default:
169 m_shape = SHAPE_T::UNDEFINED;
170 break;
171 }
172}
173
174
177 m_shape( aOther.m_shape ),
178 m_stroke( aOther.m_stroke ),
179 m_fill( aOther.m_fill ),
180 m_fillColor( aOther.m_fillColor ),
181 m_hatchingDirty( true ),
185 m_start( aOther.m_start ),
186 m_end( aOther.m_end ),
187 m_arcCenter( aOther.m_arcCenter ),
188 m_arcMidData( aOther.m_arcMidData ),
189 m_bezierC1( aOther.m_bezierC1 ),
190 m_bezierC2( aOther.m_bezierC2 ),
192 m_ellipse( aOther.m_ellipse ),
193 m_editState( aOther.m_editState ),
194 m_proxyItem( aOther.m_proxyItem )
195{
196 if( aOther.m_poly )
197 m_poly = std::make_unique<SHAPE_POLY_SET>( *aOther.m_poly );
198}
199
200
202{
203 if( this == &aOther )
204 return *this;
205
207 m_shape = aOther.m_shape;
208 m_stroke = aOther.m_stroke;
209 m_fill = aOther.m_fill;
210 m_fillColor = aOther.m_fillColor;
211 m_hatchingCache.reset();
212 m_hatchingDirty = true;
216 m_start = aOther.m_start;
217 m_end = aOther.m_end;
218 m_arcCenter = aOther.m_arcCenter;
219 m_arcMidData = aOther.m_arcMidData;
220 m_bezierC1 = aOther.m_bezierC1;
221 m_bezierC2 = aOther.m_bezierC2;
223 m_ellipse = aOther.m_ellipse;
224 if( aOther.m_poly )
225 m_poly = std::make_unique<SHAPE_POLY_SET>( *aOther.m_poly );
226 else
227 m_poly.reset();
228 m_editState = aOther.m_editState;
229 m_proxyItem = aOther.m_proxyItem;
230
231 return *this;
232}
233
234
235void EDA_SHAPE::Serialize( google::protobuf::Any &aContainer ) const
236{
237 Serialize( aContainer, pcbIUScale );
238}
239
240
241void EDA_SHAPE::Serialize( google::protobuf::Any &aContainer, const EDA_IU_SCALE &aScale ) const
242{
243 using namespace kiapi::common;
244 types::GraphicShape shape;
245
246 types::StrokeAttributes* stroke = shape.mutable_attributes()->mutable_stroke();
247 types::GraphicFillAttributes* fill = shape.mutable_attributes()->mutable_fill();
248
249 PackDistance( *stroke->mutable_width(), GetWidth(), aScale );
250 stroke->set_style( ToProtoEnum<LINE_STYLE, types::StrokeLineStyle>( m_stroke.GetLineStyle() ) );
251
252 if( m_stroke.GetColor() != COLOR4D::UNSPECIFIED )
253 PackColor( *stroke->mutable_color(), m_stroke.GetColor() );
254
256
258 PackColor( *fill->mutable_color(), m_fillColor );
259
260 switch( GetShape() )
261 {
262 case SHAPE_T::SEGMENT:
263 {
264 types::GraphicSegmentAttributes* segment = shape.mutable_segment();
265 PackVector2( *segment->mutable_start(), GetStart(), aScale );
266 PackVector2( *segment->mutable_end(), GetEnd(), aScale );
267 break;
268 }
269
271 {
272 types::GraphicRectangleAttributes* rectangle = shape.mutable_rectangle();
273 PackVector2( *rectangle->mutable_top_left(), GetStart(), aScale );
274 PackVector2( *rectangle->mutable_bottom_right(), GetEnd(), aScale );
275 PackDistance( *rectangle->mutable_corner_radius(), GetCornerRadius(), aScale );
276 break;
277 }
278
279 case SHAPE_T::ARC:
280 {
281 types::GraphicArcAttributes* arc = shape.mutable_arc();
282 PackVector2( *arc->mutable_start(), GetStart(), aScale );
283 PackVector2( *arc->mutable_mid(), GetArcMid(), aScale );
284 PackVector2( *arc->mutable_end(), GetEnd(), aScale );
285 break;
286 }
287
288 case SHAPE_T::CIRCLE:
289 {
290 types::GraphicCircleAttributes* circle = shape.mutable_circle();
291 PackVector2( *circle->mutable_center(), GetStart(), aScale );
292 PackVector2( *circle->mutable_radius_point(), GetEnd(), aScale );
293 break;
294 }
295
296 case SHAPE_T::POLY:
297 {
298 PackPolySet( *shape.mutable_polygon(), GetPolyShape(), aScale );
299 break;
300 }
301
302 case SHAPE_T::BEZIER:
303 {
304 types::GraphicBezierAttributes* bezier = shape.mutable_bezier();
305 PackVector2( *bezier->mutable_start(), GetStart(), aScale );
306 PackVector2( *bezier->mutable_control1(), GetBezierC1(), aScale );
307 PackVector2( *bezier->mutable_control2(), GetBezierC2(), aScale );
308 PackVector2( *bezier->mutable_end(), GetEnd(), aScale );
309 break;
310 }
311
312 case SHAPE_T::ELLIPSE:
313 {
314 types::GraphicEllipseAttributes* ellipse = shape.mutable_ellipse();
315 PackVector2( *ellipse->mutable_center(), GetEllipseCenter(), aScale );
316 PackDistance( *ellipse->mutable_major_radius(), GetEllipseMajorRadius(), aScale );
317 PackDistance( *ellipse->mutable_minor_radius(), GetEllipseMinorRadius(), aScale );
318 ellipse->mutable_rotation()->set_value_degrees( GetEllipseRotation().AsDegrees() );
319 break;
320 }
321
323 {
324 types::GraphicEllipseArcAttributes* arc = shape.mutable_ellipse_arc();
325 PackVector2( *arc->mutable_center(), GetEllipseCenter(), aScale );
326 PackDistance( *arc->mutable_major_radius(), GetEllipseMajorRadius(), aScale );
327 PackDistance( *arc->mutable_minor_radius(), GetEllipseMinorRadius(), aScale );
328 arc->mutable_rotation()->set_value_degrees( GetEllipseRotation().AsDegrees() );
329 arc->mutable_start_angle()->set_value_degrees( GetEllipseStartAngle().AsDegrees() );
330 arc->mutable_end_angle()->set_value_degrees( GetEllipseEndAngle().AsDegrees() );
331 break;
332 }
333
334 default:
335 wxASSERT_MSG( false, "Unhandled shape in EDA_SHAPE::Serialize" );
336 }
337
338 // TODO m_hasSolderMask and m_solderMaskMargin
339
340 aContainer.PackFrom( shape );
341}
342
343
344bool EDA_SHAPE::Deserialize( const google::protobuf::Any &aContainer )
345{
346 return Deserialize( aContainer, pcbIUScale );
347}
348
349
350bool EDA_SHAPE::Deserialize( const google::protobuf::Any &aContainer, const EDA_IU_SCALE &aScale )
351{
352 using namespace kiapi::common;
353
354 types::GraphicShape shape;
355
356 if( !aContainer.UnpackTo( &shape ) )
357 return false;
358
359 // Initialize everything to a known state that doesn't get touched by every
360 // codepath below, to make sure the equality operator is consistent
361 m_start = {};
362 m_end = {};
363 m_arcCenter = {};
364 m_arcMidData = {};
365 m_bezierC1 = {};
366 m_bezierC2 = {};
367 m_editState = 0;
368 m_proxyItem = false;
369 m_endsSwapped = false;
371
372 if( shape.attributes().stroke().has_color() )
373 m_stroke.SetColor( UnpackColor( shape.attributes().stroke().color() ) );
374 else
375 m_stroke.SetColor( COLOR4D::UNSPECIFIED );
376
377 if( shape.attributes().fill().has_color() )
378 SetFillColor( UnpackColor( shape.attributes().fill().color() ) );
379
380 if( shape.attributes().has_stroke() )
381 {
382 SetWidth( UnpackDistance( shape.attributes().stroke().width(), aScale ) );
383 SetLineStyle( FromProtoEnum<LINE_STYLE, types::StrokeLineStyle>( shape.attributes().stroke().style() ) );
384 }
385
386 if( shape.attributes().has_fill() )
387 SetFillMode( FromProtoEnum<FILL_T, types::GraphicFillType>( shape.attributes().fill().fill_type() ) );
388
389 if( shape.has_segment() )
390 {
392 SetStart( UnpackVector2( shape.segment().start(), aScale ) );
393 SetEnd( UnpackVector2( shape.segment().end(), aScale ) );
394 }
395 else if( shape.has_rectangle() )
396 {
398 SetStart( UnpackVector2( shape.rectangle().top_left(), aScale ) );
399 SetEnd( UnpackVector2( shape.rectangle().bottom_right(), aScale ) );
400 SetCornerRadius( UnpackDistance( shape.rectangle().corner_radius(), aScale ) );
401 }
402 else if( shape.has_arc() )
403 {
405 SetArcGeometry( UnpackVector2( shape.arc().start(), aScale ),
406 UnpackVector2( shape.arc().mid(), aScale ),
407 UnpackVector2( shape.arc().end(), aScale ) );
408 }
409 else if( shape.has_circle() )
410 {
412 SetStart( UnpackVector2( shape.circle().center(), aScale ) );
413 SetEnd( UnpackVector2( shape.circle().radius_point(), aScale ) );
414 }
415 else if( shape.has_polygon() )
416 {
418 SetPolyShape( UnpackPolySet( shape.polygon(), aScale ) );
419 }
420 else if( shape.has_bezier() )
421 {
423 SetStart( UnpackVector2( shape.bezier().start(), aScale ) );
424 SetBezierC1( UnpackVector2( shape.bezier().control1(), aScale ) );
425 SetBezierC2( UnpackVector2( shape.bezier().control2(), aScale ) );
426 SetEnd( UnpackVector2( shape.bezier().end(), aScale ) );
428 }
429 else if( shape.has_ellipse() )
430 {
432 SetEllipseCenter( UnpackVector2( shape.ellipse().center(), aScale ) );
433 SetEllipseMajorRadius( UnpackDistance( shape.ellipse().major_radius(), aScale ) );
434 SetEllipseMinorRadius( UnpackDistance( shape.ellipse().minor_radius(), aScale ) );
435 SetEllipseRotation( EDA_ANGLE( shape.ellipse().rotation().value_degrees(), DEGREES_T ) );
436 }
437 else if( shape.has_ellipse_arc() )
438 {
440 SetEllipseCenter( UnpackVector2( shape.ellipse_arc().center(), aScale ) );
441 SetEllipseMajorRadius( UnpackDistance( shape.ellipse_arc().major_radius(), aScale ) );
442 SetEllipseMinorRadius( UnpackDistance( shape.ellipse_arc().minor_radius(), aScale ) );
443 SetEllipseRotation( EDA_ANGLE( shape.ellipse_arc().rotation().value_degrees(), DEGREES_T ) );
444 SetEllipseStartAngle( EDA_ANGLE( shape.ellipse_arc().start_angle().value_degrees(), DEGREES_T ) );
445 SetEllipseEndAngle( EDA_ANGLE( shape.ellipse_arc().end_angle().value_degrees(), DEGREES_T ) );
446 }
447
448 return true;
449}
450
451
452wxString EDA_SHAPE::ShowShape() const
453{
454 if( IsProxyItem() )
455 {
456 switch( m_shape )
457 {
458 case SHAPE_T::SEGMENT: return _( "Thermal Spoke" );
459 case SHAPE_T::RECTANGLE: return _( "Number Box" );
460 default: return wxT( "??" );
461 }
462 }
463 else
464 {
465 switch( m_shape )
466 {
467 case SHAPE_T::SEGMENT: return _( "Line" );
468 case SHAPE_T::RECTANGLE: return _( "Rect" );
469 case SHAPE_T::ARC: return _( "Arc" );
470 case SHAPE_T::CIRCLE: return _( "Circle" );
471 case SHAPE_T::BEZIER: return _( "Bezier Curve" );
472 case SHAPE_T::POLY: return _( "Polygon" );
473 case SHAPE_T::ELLIPSE: return _( "Ellipse" );
474 case SHAPE_T::ELLIPSE_ARC: return _( "Elliptical Arc" );
475 default: return wxT( "??" );
476 }
477 }
478}
479
480
482{
483 switch( m_shape )
484 {
485 case SHAPE_T::SEGMENT: return wxS( "S_SEGMENT" );
486 case SHAPE_T::RECTANGLE: return wxS( "S_RECT" );
487 case SHAPE_T::ARC: return wxS( "S_ARC" );
488 case SHAPE_T::CIRCLE: return wxS( "S_CIRCLE" );
489 case SHAPE_T::POLY: return wxS( "S_POLYGON" );
490 case SHAPE_T::BEZIER: return wxS( "S_CURVE" );
491 case SHAPE_T::ELLIPSE: return wxS( "S_ELLIPSE" );
492 case SHAPE_T::ELLIPSE_ARC: return wxS( "S_ELLIPSE_ARC" );
493 case SHAPE_T::UNDEFINED: return wxS( "UNDEFINED" );
494 }
495
496 return wxEmptyString; // Just to quiet GCC.
497}
498
499
501{
502 move( aPos - getPosition() );
503}
504
505
507{
509 return getCenter();
510 else if( m_shape == SHAPE_T::POLY )
511 return GetPolyShape().CVertex( 0 );
512 else
513 return m_start;
514}
515
516
518{
519 double length = 0.0;
520
521 switch( m_shape )
522 {
523 case SHAPE_T::BEZIER:
524 for( size_t ii = 1; ii < m_bezierPoints.size(); ++ii )
525 length += m_bezierPoints[ ii - 1].Distance( m_bezierPoints[ii] );
526
527 return length;
528
529 case SHAPE_T::SEGMENT:
530 return GetStart().Distance( GetEnd() );
531
532 case SHAPE_T::POLY:
533 for( int ii = 0; ii < GetPolyShape().COutline( 0 ).SegmentCount(); ii++ )
534 length += GetPolyShape().COutline( 0 ).CSegment( ii ).Length();
535
536 return length;
537
538 case SHAPE_T::ARC:
539 return GetRadius() * GetArcAngle().AsRadians();
540
541 case SHAPE_T::ELLIPSE:
543
544 default:
546 return 0.0;
547 }
548}
549
550
552{
553 switch( m_shape )
554 {
556 return GetEndY() - GetStartY();
557
558 default:
560 return 0;
561 }
562}
563
564
566{
567 switch( m_shape )
568 {
570 return GetEndX() - GetStartX();
571
572 default:
574 return 0;
575 }
576}
577
578
580{
581 return m_cornerRadius;
582}
583
584
585void EDA_SHAPE::SetCornerRadius( int aRadius )
586{
588 {
589 int width = std::abs( GetRectangleWidth() );
590 int height = std::abs( GetRectangleHeight() );
591 int maxRadius = std::min( width, height ) / 2;
592
593 m_cornerRadius = std::clamp( aRadius, 0, maxRadius );
594 }
595 else
596 {
597 m_cornerRadius = aRadius;
598 }
599}
600
601
602void EDA_SHAPE::SetRectangleHeight( const int& aHeight )
603{
604 switch ( m_shape )
605 {
607 m_rectangleHeight = aHeight;
609 break;
610
611 default:
613 }
614}
615
616
617void EDA_SHAPE::SetRectangleWidth( const int& aWidth )
618{
619 switch ( m_shape )
620 {
622 m_rectangleWidth = aWidth;
624 break;
625
626 default:
628 }
629}
630
631
632void EDA_SHAPE::SetRectangle( const long long int& aHeight, const long long int& aWidth )
633{
634 switch ( m_shape )
635 {
637 m_rectangleHeight = aHeight;
638 m_rectangleWidth = aWidth;
639 break;
640
641 default:
643 }
644}
645
646
648{
649 switch( m_shape )
650 {
651 case SHAPE_T::CIRCLE:
653 case SHAPE_T::ELLIPSE: return true;
654
655 case SHAPE_T::ARC:
656 case SHAPE_T::SEGMENT:
657 case SHAPE_T::ELLIPSE_ARC: return false;
658
659 case SHAPE_T::POLY:
660 if( GetPolyShape().IsEmpty() )
661 return false;
662 else
663 return GetPolyShape().Outline( 0 ).IsClosed();
664
665 case SHAPE_T::BEZIER:
666 if( m_bezierPoints.size() < 3 )
667 return false;
668 else
669 return m_bezierPoints[0] == m_bezierPoints[ m_bezierPoints.size() - 1 ];
670
671 default:
673 return false;
674 }
675}
676
677
679{
680 m_fill = aFill;
681 m_hatchingDirty = true;
682}
683
684
686{
687 switch( aFill )
688 {
693 default: SetFilled( true ); break;
694 }
695}
696
697
709
710
712{
713 if( !m_hatchingCache )
714 m_hatchingCache = std::make_unique<EDA_SHAPE_HATCH_CACHE_DATA>();
715
716 return m_hatchingCache->hatching;
717}
718
719
720const std::vector<SEG>& EDA_SHAPE::GetHatchLines() const
721{
722 if( !m_hatchingCache )
723 m_hatchingCache = std::make_unique<EDA_SHAPE_HATCH_CACHE_DATA>();
724
725 return m_hatchingCache->hatchLines;
726}
727
728
730{
731 if( !m_hatchingCache )
732 m_hatchingCache = std::make_unique<EDA_SHAPE_HATCH_CACHE_DATA>();
733
734 return m_hatchingCache->hatching;
735}
736
737
738std::vector<SEG>& EDA_SHAPE::hatchLines() const
739{
740 if( !m_hatchingCache )
741 m_hatchingCache = std::make_unique<EDA_SHAPE_HATCH_CACHE_DATA>();
742
743 return m_hatchingCache->hatchLines;
744}
745
746
748{
749 if( !m_hatchingDirty )
750 return;
751
752 std::vector<double> slopes;
753 int lineWidth = GetHatchLineWidth();
754 int spacing = GetHatchLineSpacing();
755 SHAPE_POLY_SET shapeBuffer;
756
757 // Validate state before clearing cached hatching. If we can't regenerate, keep existing cache.
758 if( isMoving() )
759 return;
760
762 slopes = { 1.0, -1.0 };
763 else if( GetFillMode() == FILL_T::HATCH )
764 slopes = { -1.0 };
765 else if( GetFillMode() == FILL_T::REVERSE_HATCH )
766 slopes = { 1.0 };
767 else
768 return;
769
770 if( spacing == 0 )
771 return;
772
773 switch( m_shape )
774 {
775 case SHAPE_T::ARC:
776 case SHAPE_T::SEGMENT:
777 case SHAPE_T::BEZIER:
778 case SHAPE_T::ELLIPSE_ARC: return;
779
781 {
783 rr.TransformToPolygon( shapeBuffer, getMaxError() );
784 }
785 break;
786
787 case SHAPE_T::CIRCLE:
789 break;
790
791 case SHAPE_T::POLY:
792 if( !IsClosed() )
793 return;
794
795 shapeBuffer = GetPolyShape().CloneDropTriangulation();
796 break;
797
798 case SHAPE_T::ELLIPSE:
799 {
800 // Hatching only applies to closed, fillable shapes.
803 chain.SetClosed( true );
804 shapeBuffer.AddOutline( chain );
805 break;
806 }
807
808 default:
810 return;
811 }
812
813 shapeBuffer.ClearArcs();
814
815 // Clear cached hatching only after all validation passes.
816 // This prevents flickering when early returns would otherwise leave empty hatching.
818 hatchLines().clear();
819
820 BOX2I extents = shapeBuffer.BBox();
821 int majorAxis = std::max( extents.GetWidth(), extents.GetHeight() );
822
823 if( majorAxis / spacing > 100 )
824 spacing = majorAxis / 100;
825
827
828 if( !knockouts.IsEmpty() )
829 {
830 shapeBuffer.BooleanSubtract( knockouts );
831 shapeBuffer.Fracture();
832 }
833
834 // Generate hatch lines for stroke-based rendering. All hatch types use line segments.
835 std::vector<SEG> hatchSegs = shapeBuffer.GenerateHatchLines( slopes, spacing, -1 );
836 hatchLines() = hatchSegs;
837
838 // Also generate polygon representation for exports, 3D viewer, and hit testing
840 {
841 for( const SEG& seg : hatchSegs )
842 {
843 // We don't really need the rounded ends at all, so don't spend any extra time on them
844 int maxError = lineWidth;
845
846 TransformOvalToPolygon( hatching(), seg.A, seg.B, lineWidth, maxError,
847 ERROR_INSIDE );
848 }
849
850 hatching().Fracture();
851 m_hatchingDirty = false;
852 }
853 else
854 {
855 // Generate a grid of holes for a cross-hatch polygon representation.
856 // This is used for exports, 3D viewer, and hit testing.
857
858 int gridsize = spacing;
859 int hole_size = gridsize - GetHatchLineWidth();
860
861 hatching() = shapeBuffer.CloneDropTriangulation();
863
864 // Build hole shape
865 SHAPE_LINE_CHAIN hole_base;
866 VECTOR2I corner( 0, 0 );;
867 hole_base.Append( corner );
868 corner.x += hole_size;
869 hole_base.Append( corner );
870 corner.y += hole_size;
871 hole_base.Append( corner );
872 corner.x = 0;
873 hole_base.Append( corner );
874 hole_base.SetClosed( true );
875
876 // Build holes
877 BOX2I bbox = GetHatching().BBox( 0 );
878 SHAPE_POLY_SET holes;
879
880 int x_offset = bbox.GetX() - ( bbox.GetX() ) % gridsize - gridsize;
881 int y_offset = bbox.GetY() - ( bbox.GetY() ) % gridsize - gridsize;
882
883 for( int xx = x_offset; xx <= bbox.GetRight(); xx += gridsize )
884 {
885 for( int yy = y_offset; yy <= bbox.GetBottom(); yy += gridsize )
886 {
887 SHAPE_LINE_CHAIN hole( hole_base );
888 hole.Move( VECTOR2I( xx, yy ) );
889 holes.AddOutline( hole );
890 }
891 }
892
893 hatching().BooleanSubtract( holes );
894 hatching().Fracture();
895
896 // Must re-rotate after Fracture(). Clipper struggles mightily with fracturing
897 // 45-degree holes.
899
900 if( !knockouts.IsEmpty() )
901 {
902 hatching().BooleanSubtract( knockouts );
903 hatching().Fracture();
904 }
905
906 m_hatchingDirty = false;
907 }
908}
909
910
911void EDA_SHAPE::move( const VECTOR2I& aMoveVector )
912{
913 switch ( m_shape )
914 {
915 case SHAPE_T::ARC:
916 m_arcCenter += aMoveVector;
917 m_arcMidData.center += aMoveVector;
918 m_arcMidData.start += aMoveVector;
919 m_arcMidData.end += aMoveVector;
920 m_arcMidData.mid += aMoveVector;
922
923 case SHAPE_T::SEGMENT:
925 case SHAPE_T::CIRCLE:
926 m_start += aMoveVector;
927 m_end += aMoveVector;
928 break;
929
930 case SHAPE_T::POLY:
931 GetPolyShape().Move( aMoveVector );
932 break;
933
934 case SHAPE_T::BEZIER:
935 m_start += aMoveVector;
936 m_end += aMoveVector;
937 m_bezierC1 += aMoveVector;
938 m_bezierC2 += aMoveVector;
939
940 for( VECTOR2I& pt : m_bezierPoints )
941 pt += aMoveVector;
942
943 break;
944
945 case SHAPE_T::ELLIPSE:
947 m_ellipse.Center += aMoveVector;
948 m_start += aMoveVector;
949 m_end += aMoveVector;
950 break;
951
952 default:
954 break;
955 }
956
957 // Translate the cached hatch geometry instead of leaving it stale. The hatch pattern is
958 // invariant under translation, so shifting line endpoints is sufficient and keeps the
959 // display correct during interactive moves without hitting GenerateHatchLines().
960 if( m_hatchingCache )
961 {
962 for( SEG& seg : m_hatchingCache->hatchLines )
963 {
964 seg.A += aMoveVector;
965 seg.B += aMoveVector;
966 }
967
968 m_hatchingCache->hatching.Move( aMoveVector );
969 }
970
971 m_hatchingDirty = true;
972}
973
974
975void EDA_SHAPE::scale( double aScale )
976{
977 auto scalePt =
978 [&]( VECTOR2I& pt )
979 {
980 pt.x = KiROUND( pt.x * aScale );
981 pt.y = KiROUND( pt.y * aScale );
982 };
983
984 switch( m_shape )
985 {
986 case SHAPE_T::ARC:
987 scalePt( m_arcCenter );
989
990 case SHAPE_T::SEGMENT:
992 case SHAPE_T::CIRCLE:
993 scalePt( m_start );
994 scalePt( m_end );
995 break;
996
997 case SHAPE_T::POLY: // polygon
998 {
999 std::vector<VECTOR2I> pts;
1000
1001 for( int ii = 0; ii < GetPolyShape().OutlineCount(); ++ ii )
1002 {
1003 for( const VECTOR2I& pt : GetPolyShape().Outline( ii ).CPoints() )
1004 {
1005 pts.emplace_back( pt );
1006 scalePt( pts.back() );
1007 }
1008 }
1009
1010 SetPolyPoints( pts );
1011 }
1012 break;
1013
1014 case SHAPE_T::BEZIER:
1015 scalePt( m_start );
1016 scalePt( m_end );
1017 scalePt( m_bezierC1 );
1018 scalePt( m_bezierC2 );
1020 break;
1021
1022 case SHAPE_T::ELLIPSE:
1024 scalePt( m_ellipse.Center );
1025 m_ellipse.MajorRadius = KiROUND( std::abs( m_ellipse.MajorRadius * aScale ) );
1026 m_ellipse.MinorRadius = KiROUND( std::abs( m_ellipse.MinorRadius * aScale ) );
1028 break;
1029
1030 default:
1032 break;
1033 }
1034
1035 m_hatchingDirty = true;
1036}
1037
1038
1039void EDA_SHAPE::rotate( const VECTOR2I& aRotCentre, const EDA_ANGLE& aAngle )
1040{
1041 switch( m_shape )
1042 {
1043 case SHAPE_T::SEGMENT:
1044 case SHAPE_T::CIRCLE:
1045 RotatePoint( m_start, aRotCentre, aAngle );
1046 RotatePoint( m_end, aRotCentre, aAngle );
1047 break;
1048
1049 case SHAPE_T::ARC:
1050 RotatePoint( m_start, aRotCentre, aAngle );
1051 RotatePoint( m_end, aRotCentre, aAngle );
1052 RotatePoint( m_arcCenter, aRotCentre, aAngle );
1053 RotatePoint( m_arcMidData.start, aRotCentre, aAngle );
1054 RotatePoint( m_arcMidData.end, aRotCentre, aAngle );
1055 RotatePoint( m_arcMidData.mid, aRotCentre, aAngle );
1056 RotatePoint( m_arcMidData.center, aRotCentre, aAngle );
1057 break;
1058
1059 case SHAPE_T::RECTANGLE:
1060 if( aAngle.IsCardinal() )
1061 {
1062 RotatePoint( m_start, aRotCentre, aAngle );
1063 RotatePoint( m_end, aRotCentre, aAngle );
1064 }
1065 else
1066 {
1067 // Convert non-cardinally-rotated rect to a diamond
1071 GetPolyShape().Rotate( aAngle, aRotCentre );
1072 }
1073
1074 break;
1075
1076 case SHAPE_T::POLY:
1077 GetPolyShape().Rotate( aAngle, aRotCentre );
1078 break;
1079
1080 case SHAPE_T::BEZIER:
1081 RotatePoint( m_start, aRotCentre, aAngle );
1082 RotatePoint( m_end, aRotCentre, aAngle );
1083 RotatePoint( m_bezierC1, aRotCentre, aAngle );
1084 RotatePoint( m_bezierC2, aRotCentre, aAngle );
1085
1086 for( VECTOR2I& pt : m_bezierPoints )
1087 RotatePoint( pt, aRotCentre, aAngle);
1088
1089 break;
1090
1091 case SHAPE_T::ELLIPSE:
1093 RotatePoint( m_ellipse.Center, aRotCentre, aAngle );
1094
1095 // Ellipse rotation is the CCW angle of the major axis in standard math
1096 // coordinates (Y-up). RotatePoint uses KiCad's Y-down screen convention,
1097 // so a positive aAngle rotates visually CCW on screen but corresponds to
1098 // a negative rotation in the math frame. Hence -= rather than +=.
1099 m_ellipse.Rotation -= aAngle;
1101 break;
1102
1103 default:
1105 break;
1106 }
1107
1108 m_hatchingDirty = true;
1109}
1110
1111
1112void EDA_SHAPE::flip( const VECTOR2I& aCentre, FLIP_DIRECTION aFlipDirection )
1113{
1114 switch ( m_shape )
1115 {
1116 case SHAPE_T::SEGMENT:
1117 case SHAPE_T::RECTANGLE:
1118 MIRROR( m_start, aCentre, aFlipDirection );
1119 MIRROR( m_end, aCentre, aFlipDirection );
1120 break;
1121
1122 case SHAPE_T::CIRCLE:
1123 MIRROR( m_start, aCentre, aFlipDirection );
1124 MIRROR( m_end, aCentre, aFlipDirection );
1125 break;
1126
1127 case SHAPE_T::ARC:
1128 MIRROR( m_start, aCentre, aFlipDirection );
1129 MIRROR( m_end, aCentre, aFlipDirection );
1130 MIRROR( m_arcCenter, aCentre, aFlipDirection );
1131
1132 std::swap( m_start, m_end );
1133 break;
1134
1135 case SHAPE_T::POLY:
1136 GetPolyShape().Mirror( aCentre, aFlipDirection );
1137 break;
1138
1139 case SHAPE_T::BEZIER:
1140 MIRROR( m_start, aCentre, aFlipDirection );
1141 MIRROR( m_end, aCentre, aFlipDirection );
1142 MIRROR( m_bezierC1, aCentre, aFlipDirection );
1143 MIRROR( m_bezierC2, aCentre, aFlipDirection );
1144
1146 break;
1147
1148 case SHAPE_T::ELLIPSE:
1150 m_ellipse.Mirror( aCentre, aFlipDirection );
1152 break;
1153
1154 default:
1156 break;
1157 }
1158
1159 m_hatchingDirty = true;
1160}
1161
1162
1164{
1165 // Has meaning only for SHAPE_T::BEZIER
1166 if( m_shape != SHAPE_T::BEZIER )
1167 {
1168 m_bezierPoints.clear();
1169 return;
1170 }
1171
1172 // Rebuild the m_BezierPoints vertex list that approximate the Bezier curve
1174}
1175
1176
1177const std::vector<VECTOR2I> EDA_SHAPE::buildBezierToSegmentsPointsList( int aMaxError ) const
1178{
1179 std::vector<VECTOR2I> bezierPoints;
1180
1181 // Rebuild the m_BezierPoints vertex list that approximate the Bezier curve
1182 std::vector<VECTOR2I> ctrlPoints = { m_start, m_bezierC1, m_bezierC2, m_end };
1183 BEZIER_POLY converter( ctrlPoints );
1184 converter.GetPoly( bezierPoints, aMaxError );
1185
1186 return bezierPoints;
1187}
1188
1189
1191{
1193 return SHAPE_ELLIPSE( m_ellipse.Center, m_ellipse.MajorRadius, m_ellipse.MinorRadius, m_ellipse.Rotation,
1194 m_ellipse.StartAngle, m_ellipse.EndAngle );
1195
1196 return SHAPE_ELLIPSE( m_ellipse.Center, m_ellipse.MajorRadius, m_ellipse.MinorRadius, m_ellipse.Rotation );
1197}
1198
1199
1201{
1202 if( m_editState != 0 )
1203 return;
1204
1205 if( m_shape == SHAPE_T::ELLIPSE )
1206 {
1207 const double phi = m_ellipse.Rotation.AsRadians();
1208 m_start = m_ellipse.Center;
1209 m_end = m_start
1210 + VECTOR2I( KiROUND( m_ellipse.MajorRadius * std::cos( phi ) ),
1211 KiROUND( m_ellipse.MajorRadius * std::sin( phi ) ) );
1212 return;
1213 }
1214
1216 return;
1217
1218 m_arcCenter = m_ellipse.Center;
1219
1220 const double a = m_ellipse.MajorRadius;
1221 const double b = m_ellipse.MinorRadius;
1222 const double phi = m_ellipse.Rotation.AsRadians();
1223 const double cosPhi = std::cos( phi );
1224 const double sinPhi = std::sin( phi );
1225 const VECTOR2I c = m_ellipse.Center;
1226
1227 auto eval = [&]( double theta ) -> VECTOR2I
1228 {
1229 const double lx = a * std::cos( theta );
1230 const double ly = b * std::sin( theta );
1231 return c + VECTOR2I( KiROUND( lx * cosPhi - ly * sinPhi ), KiROUND( lx * sinPhi + ly * cosPhi ) );
1232 };
1233
1234 m_start = eval( m_ellipse.StartAngle.AsRadians() );
1235 m_end = eval( m_ellipse.EndAngle.AsRadians() );
1236}
1237
1238
1240{
1241 switch( m_shape )
1242 {
1243 case SHAPE_T::ARC:
1244 return m_arcCenter;
1245
1246 case SHAPE_T::CIRCLE:
1247 return m_start;
1248
1249 case SHAPE_T::SEGMENT:
1250 // Midpoint of the line
1251 return ( m_start + m_end ) / 2;
1252
1253 case SHAPE_T::POLY:
1254 case SHAPE_T::RECTANGLE:
1255 case SHAPE_T::BEZIER:
1256 return getBoundingBox().Centre();
1257
1258 case SHAPE_T::ELLIPSE:
1259 case SHAPE_T::ELLIPSE_ARC: return m_ellipse.Center;
1260
1261 default:
1263 return VECTOR2I();
1264 }
1265}
1266
1267
1268void EDA_SHAPE::SetCenter( const VECTOR2I& aCenter )
1269{
1270 switch( m_shape )
1271 {
1272 case SHAPE_T::ARC:
1273 m_arcCenter = aCenter;
1274 break;
1275
1276 case SHAPE_T::CIRCLE:
1277 m_start = aCenter;
1278 m_hatchingDirty = true;
1279 break;
1280
1281 case SHAPE_T::ELLIPSE:
1283 m_ellipse.Center = aCenter;
1284 m_hatchingDirty = true;
1286 break;
1287
1288 default:
1290 }
1291}
1292
1293
1295{
1296 // If none of the input data have changed since we loaded the arc, keep the original mid point data
1297 // to minimize churn
1298 if( m_arcMidData.start == m_start && m_arcMidData.end == m_end && m_arcMidData.center == m_arcCenter )
1299 return m_arcMidData.mid;
1300
1301 VECTOR2I mid = m_start;
1302 RotatePoint( mid, m_arcCenter, -GetArcAngle() / 2.0 );
1303 return mid;
1304}
1305
1306
1307void EDA_SHAPE::CalcArcAngles( EDA_ANGLE& aStartAngle, EDA_ANGLE& aEndAngle ) const
1308{
1309 VECTOR2D startRadial( GetStart() - getCenter() );
1310 VECTOR2D endRadial( GetEnd() - getCenter() );
1311
1312 aStartAngle = EDA_ANGLE( startRadial );
1313 aEndAngle = EDA_ANGLE( endRadial );
1314
1315 if( aEndAngle == aStartAngle )
1316 aEndAngle = aStartAngle + ANGLE_360; // ring, not null
1317
1318 while( aEndAngle < aStartAngle )
1319 aEndAngle += ANGLE_360;
1320}
1321
1322
1324{
1325 double radius = 0.0;
1326
1327 switch( m_shape )
1328 {
1329 case SHAPE_T::ARC:
1330 radius = m_arcCenter.Distance( m_start );
1331 break;
1332
1333 case SHAPE_T::CIRCLE:
1334 radius = m_start.Distance( m_end );
1335 break;
1336
1337 default:
1339 }
1340
1341 // don't allow degenerate circles/arcs
1342 if( radius > (double) INT_MAX / 2.0 )
1343 radius = (double) INT_MAX / 2.0;
1344
1345 return std::max( 1, KiROUND( radius ) );
1346}
1347
1348
1349void EDA_SHAPE::SetCachedArcData( const VECTOR2I& aStart, const VECTOR2I& aMid,
1350 const VECTOR2I& aEnd, const VECTOR2I& aCenter )
1351{
1352 m_arcMidData.start = aStart;
1353 m_arcMidData.end = aEnd;
1354 m_arcMidData.center = aCenter;
1355 m_arcMidData.mid = aMid;
1356}
1357
1358
1359void EDA_SHAPE::SetArcGeometry( const VECTOR2I& aStart, const VECTOR2I& aMid, const VECTOR2I& aEnd )
1360{
1361 m_arcMidData = {};
1362 m_start = aStart;
1363 m_end = aEnd;
1364 m_arcCenter = CalcArcCenter( aStart, aMid, aEnd );
1365 VECTOR2I new_mid = GetArcMid();
1366
1367 m_endsSwapped = false;
1368
1369 // Watch the ordering here. GetArcMid above needs to be called prior to initializing the
1370 // m_arcMidData structure in order to ensure we get the calculated variant, not the cached
1371 SetCachedArcData( aStart, aMid, aEnd, m_arcCenter );
1372
1373 /*
1374 * If the input winding doesn't match our internal winding, the calculated midpoint will end
1375 * up on the other side of the arc. In this case, we need to flip the start/end points and
1376 * flag this change for the system.
1377 */
1378 VECTOR2D dist( new_mid - aMid );
1379 VECTOR2D dist2( new_mid - m_arcCenter );
1380
1381 if( dist.SquaredEuclideanNorm() > dist2.SquaredEuclideanNorm() )
1382 {
1383 std::swap( m_start, m_end );
1384 m_endsSwapped = true;
1385 }
1386}
1387
1388
1390{
1391 EDA_ANGLE angle( atan2( static_cast<double>( GetStart().y - GetEnd().y ),
1392 static_cast<double>( GetEnd().x - GetStart().x ) ), RADIANS_T );
1393
1394 return angle;
1395}
1396
1397
1399{
1400 EDA_ANGLE startAngle;
1401 EDA_ANGLE endAngle;
1402
1403 CalcArcAngles( startAngle, endAngle );
1404
1405 return endAngle - startAngle;
1406}
1407
1408
1410{
1411 if( m_shape == SHAPE_T::ARC )
1412 {
1413 VECTOR2D mid = GetArcMid();
1414
1415 double orient = ( mid.x - m_start.x ) * ( m_end.y - m_start.y )
1416 - ( mid.y - m_start.y ) * ( m_end.x - m_start.x );
1417
1418 return orient < 0;
1419 }
1420
1422 return false;
1423}
1424
1425
1426void EDA_SHAPE::SetArcAngleAndEnd( const EDA_ANGLE& aAngle, bool aCheckNegativeAngle )
1427{
1428 EDA_ANGLE angle( aAngle );
1429
1430 m_end = m_start;
1432
1433 if( aCheckNegativeAngle && aAngle < ANGLE_0 )
1434 {
1435 std::swap( m_start, m_end );
1436 m_endsSwapped = true;
1437 }
1438}
1439
1440
1442{
1443 if( IsProxyItem() )
1444 {
1445 switch( m_shape )
1446 {
1447 case SHAPE_T::RECTANGLE: return _( "Pad Number Box" );
1448 case SHAPE_T::SEGMENT: return _( "Thermal Spoke Template" );
1449 default: return _( "Unrecognized" );
1450 }
1451 }
1452 else
1453 {
1454 switch( m_shape )
1455 {
1456 case SHAPE_T::CIRCLE: return _( "Circle" );
1457 case SHAPE_T::ARC: return _( "Arc" );
1458 case SHAPE_T::BEZIER: return _( "Curve" );
1459 case SHAPE_T::POLY: return _( "Polygon" );
1460 case SHAPE_T::RECTANGLE: return _( "Rectangle" );
1461 case SHAPE_T::SEGMENT: return _( "Segment" );
1462 case SHAPE_T::ELLIPSE: return _( "Ellipse" );
1463 case SHAPE_T::ELLIPSE_ARC: return _( "Elliptical Arc" );
1464 default: return _( "Unrecognized" );
1465 }
1466 }
1467}
1468
1469
1470void EDA_SHAPE::ShapeGetMsgPanelInfo( EDA_DRAW_FRAME* aFrame, std::vector<MSG_PANEL_ITEM>& aList )
1471{
1472 wxString msg;
1473
1474 wxString shape = _( "Shape" );
1475 aList.emplace_back( shape, getFriendlyName() );
1476
1477 switch( m_shape )
1478 {
1479 case SHAPE_T::CIRCLE:
1480 aList.emplace_back( _( "Radius" ), aFrame->MessageTextFromValue( GetRadius() ) );
1481 break;
1482
1483 case SHAPE_T::ARC:
1484 aList.emplace_back( _( "Length" ), aFrame->MessageTextFromValue( GetLength() ) );
1485
1487 aList.emplace_back( _( "Angle" ), msg );
1488
1489 aList.emplace_back( _( "Radius" ), aFrame->MessageTextFromValue( GetRadius() ) );
1490 break;
1491
1492 case SHAPE_T::BEZIER:
1493 aList.emplace_back( _( "Length" ), aFrame->MessageTextFromValue( GetLength() ) );
1494 break;
1495
1496 case SHAPE_T::ELLIPSE:
1497 aList.emplace_back( _( "Length" ), aFrame->MessageTextFromValue( GetLength() ) );
1498 aList.emplace_back( _( "Major Radius" ), aFrame->MessageTextFromValue( GetEllipseMajorRadius() ) );
1499 aList.emplace_back( _( "Minor Radius" ), aFrame->MessageTextFromValue( GetEllipseMinorRadius() ) );
1500 aList.emplace_back( _( "Rotation" ), EDA_UNIT_UTILS::UI::MessageTextFromValue( GetEllipseRotation() ) );
1501 break;
1502
1504 aList.emplace_back( _( "Length" ), aFrame->MessageTextFromValue( GetLength() ) );
1505 aList.emplace_back( _( "Major Radius" ), aFrame->MessageTextFromValue( GetEllipseMajorRadius() ) );
1506 aList.emplace_back( _( "Minor Radius" ), aFrame->MessageTextFromValue( GetEllipseMinorRadius() ) );
1507 aList.emplace_back( _( "Rotation" ), EDA_UNIT_UTILS::UI::MessageTextFromValue( GetEllipseRotation() ) );
1508 aList.emplace_back( _( "Start Angle" ), EDA_UNIT_UTILS::UI::MessageTextFromValue( GetEllipseStartAngle() ) );
1509 aList.emplace_back( _( "End Angle" ), EDA_UNIT_UTILS::UI::MessageTextFromValue( GetEllipseEndAngle() ) );
1510 break;
1511
1512 case SHAPE_T::POLY:
1513 msg.Printf( wxS( "%d" ), GetPolyShape().Outline(0).PointCount() );
1514 aList.emplace_back( _( "Points" ), msg );
1515 break;
1516
1517 case SHAPE_T::RECTANGLE:
1518 aList.emplace_back( _( "Width" ), aFrame->MessageTextFromValue( std::abs( GetEnd().x - GetStart().x ) ) );
1519 aList.emplace_back( _( "Height" ), aFrame->MessageTextFromValue( std::abs( GetEnd().y - GetStart().y ) ) );
1520 break;
1521
1522 case SHAPE_T::SEGMENT:
1523 {
1524 aList.emplace_back( _( "Length" ), aFrame->MessageTextFromValue( GetStart().Distance( GetEnd() ) ));
1525
1526 // angle counter-clockwise from 3'o-clock
1527 EDA_ANGLE angle( atan2( (double)( GetStart().y - GetEnd().y ), (double)( GetEnd().x - GetStart().x ) ),
1528 RADIANS_T );
1529 aList.emplace_back( _( "Angle" ), EDA_UNIT_UTILS::UI::MessageTextFromValue( angle ) );
1530 break;
1531 }
1532
1533 default:
1534 break;
1535 }
1536
1537 m_stroke.GetMsgPanelInfo( aFrame, aList );
1538}
1539
1540
1542{
1543 BOX2I bbox;
1544
1545 switch( m_shape )
1546 {
1547 case SHAPE_T::RECTANGLE:
1548 for( VECTOR2I& pt : GetRectCorners() )
1549 bbox.Merge( pt );
1550
1551 break;
1552
1553 case SHAPE_T::SEGMENT:
1554 bbox.SetOrigin( GetStart() );
1555 bbox.SetEnd( GetEnd() );
1556 break;
1557
1558 case SHAPE_T::CIRCLE:
1559 bbox.SetOrigin( GetStart() );
1560 bbox.Inflate( GetRadius() );
1561 break;
1562
1563 case SHAPE_T::ARC:
1564 computeArcBBox( bbox );
1565 break;
1566
1567 case SHAPE_T::ELLIPSE:
1568 case SHAPE_T::ELLIPSE_ARC: bbox = buildShapeEllipse().BBox( 0 ); break;
1569
1570 case SHAPE_T::POLY:
1571 if( GetPolyShape().IsEmpty() )
1572 break;
1573
1574 for( auto iter = GetPolyShape().CIterate(); iter; iter++ )
1575 bbox.Merge( *iter );
1576
1577 break;
1578
1579 case SHAPE_T::BEZIER:
1580 // Bezier BBoxes are not trivial to compute, so we approximate it by
1581 // using the bounding box of the curve (not control!) points.
1582 for( const VECTOR2I& pt : m_bezierPoints )
1583 bbox.Merge( pt );
1584
1585 break;
1586
1587 default:
1589 break;
1590 }
1591
1592 bbox.Inflate( std::max( 0, GetWidth() ) / 2 );
1593 bbox.Normalize();
1594
1595 return bbox;
1596}
1597
1598
1599bool EDA_SHAPE::hitTest( const VECTOR2I& aPosition, int aAccuracy ) const
1600{
1601 double maxdist = aAccuracy;
1602
1603 if( GetWidth() > 0 )
1604 maxdist += GetWidth() / 2.0;
1605
1606 switch( m_shape )
1607 {
1608 case SHAPE_T::CIRCLE:
1609 {
1610 double radius = GetRadius();
1611 double dist = aPosition.Distance( getCenter() );
1612
1613 if( IsFilledForHitTesting() )
1614 return dist <= radius + maxdist; // Filled circle hit-test
1615 else if( abs( radius - dist ) <= maxdist ) // Ring hit-test
1616 return true;
1617
1618 if( IsHatchedFill() && GetHatching().Collide( aPosition, maxdist ) )
1619 return true;
1620
1621 return false;
1622 }
1623
1624 case SHAPE_T::ARC:
1625 {
1626 if( aPosition.Distance( m_start ) <= maxdist )
1627 return true;
1628
1629 if( aPosition.Distance( m_end ) <= maxdist )
1630 return true;
1631
1632 double radius = GetRadius();
1633 VECTOR2D relPos( VECTOR2D( aPosition ) - getCenter() );
1634 double dist = relPos.EuclideanNorm();
1635
1636 if( IsFilledForHitTesting() )
1637 {
1638 // Check distance from arc center
1639 if( dist > radius + maxdist )
1640 return false;
1641 }
1642 else
1643 {
1644 // Check distance from arc circumference
1645 if( abs( radius - dist ) > maxdist )
1646 return false;
1647 }
1648
1649 // Finally, check to see if it's within arc's swept angle.
1650 EDA_ANGLE startAngle;
1651 EDA_ANGLE endAngle;
1652 CalcArcAngles( startAngle, endAngle );
1653
1654 EDA_ANGLE relPosAngle( relPos );
1655
1656 startAngle.Normalize();
1657 endAngle.Normalize();
1658 relPosAngle.Normalize();
1659
1660 if( endAngle > startAngle )
1661 return relPosAngle >= startAngle && relPosAngle <= endAngle;
1662 else
1663 return relPosAngle >= startAngle || relPosAngle <= endAngle;
1664 }
1665
1666 case SHAPE_T::BEZIER:
1667 {
1668 const std::vector<VECTOR2I>* pts = &m_bezierPoints;
1669 std::vector<VECTOR2I> updatedBezierPoints;
1670
1671 if( m_bezierPoints.empty() )
1672 {
1674 converter.GetPoly( updatedBezierPoints, aAccuracy / 2 );
1675 pts = &updatedBezierPoints;
1676 }
1677
1678 for( unsigned int i = 1; i < pts->size(); i++ )
1679 {
1680 if( TestSegmentHit( aPosition, ( *pts )[i - 1], ( *pts )[i], maxdist ) )
1681 return true;
1682 }
1683
1684 return false;
1685 }
1686 case SHAPE_T::SEGMENT:
1687 return TestSegmentHit( aPosition, GetStart(), GetEnd(), maxdist );
1688
1689 case SHAPE_T::RECTANGLE:
1690 if( IsProxyItem() || IsFilledForHitTesting() ) // Filled rect hit-test
1691 {
1692 SHAPE_POLY_SET poly;
1693 poly.NewOutline();
1694
1695 for( const VECTOR2I& pt : GetRectCorners() )
1696 poly.Append( pt );
1697
1698 return poly.Collide( aPosition, maxdist );
1699 }
1700 else if( m_cornerRadius > 0 )
1701 {
1703 SHAPE_POLY_SET poly;
1704 rr.TransformToPolygon( poly, getMaxError() );
1705
1706 if( poly.CollideEdge( aPosition, nullptr, maxdist ) )
1707 return true;
1708 }
1709 else
1710 {
1711 std::vector<VECTOR2I> pts = GetRectCorners();
1712
1713 if( TestSegmentHit( aPosition, pts[0], pts[1], maxdist )
1714 || TestSegmentHit( aPosition, pts[1], pts[2], maxdist )
1715 || TestSegmentHit( aPosition, pts[2], pts[3], maxdist )
1716 || TestSegmentHit( aPosition, pts[3], pts[0], maxdist ) )
1717 {
1718 return true;
1719 }
1720 }
1721
1722 if( IsHatchedFill() && GetHatching().Collide( aPosition, maxdist ) )
1723 return true;
1724
1725 return false;
1726
1727 case SHAPE_T::POLY:
1728 if( GetPolyShape().OutlineCount() < 1 ) // empty poly
1729 return false;
1730
1731 if( IsFilledForHitTesting() )
1732 {
1733 if( !GetPolyShape().COutline( 0 ).IsClosed() )
1734 {
1735 // Only one outline is expected
1736 SHAPE_LINE_CHAIN copy( GetPolyShape().COutline( 0 ) );
1737 copy.SetClosed( true );
1738 return copy.Collide( aPosition, maxdist );
1739 }
1740 else
1741 {
1742 return GetPolyShape().Collide( aPosition, maxdist );
1743 }
1744 }
1745 else
1746 {
1747 if( GetPolyShape().CollideEdge( aPosition, nullptr, maxdist ) )
1748 return true;
1749
1750 if( IsHatchedFill() && GetHatching().Collide( aPosition, maxdist ) )
1751 return true;
1752
1753 return false;
1754 }
1755
1756 case SHAPE_T::ELLIPSE:
1758 {
1760
1761 const double maxdistSq = maxdist * maxdist;
1762
1764 {
1765 // Filled closed ellipse
1766 if( static_cast<double>( e.SquaredDistance( aPosition, false ) ) <= maxdistSq )
1767 return true;
1768 }
1769 else
1770 {
1771 // Unfilled ring or arc
1772 if( static_cast<double>( e.SquaredDistance( aPosition, true ) ) <= maxdistSq )
1773 return true;
1774 }
1775
1776 if( IsHatchedFill() && GetHatching().Collide( aPosition, maxdist ) )
1777 return true;
1778
1779 return false;
1780 }
1781
1782 default:
1784 return false;
1785 }
1786}
1787
1788
1789bool EDA_SHAPE::hitTest( const BOX2I& aRect, bool aContained, int aAccuracy ) const
1790{
1791 BOX2I arect = aRect;
1792 arect.Normalize();
1793 arect.Inflate( aAccuracy );
1794
1795 BOX2I bbox = getBoundingBox();
1796
1797 auto checkOutline =
1798 [&]( const SHAPE_LINE_CHAIN& outline )
1799 {
1800 int count = (int) outline.GetPointCount();
1801
1802 for( int ii = 0; ii < count; ii++ )
1803 {
1804 VECTOR2I vertex = outline.GetPoint( ii );
1805
1806 // Test if the point is within aRect
1807 if( arect.Contains( vertex ) )
1808 return true;
1809
1810 if( ii + 1 < count )
1811 {
1812 VECTOR2I vertexNext = outline.GetPoint( ii + 1 );
1813
1814 // Test if this edge intersects aRect
1815 if( arect.Intersects( vertex, vertexNext ) )
1816 return true;
1817 }
1818 else if( outline.IsClosed() )
1819 {
1820 VECTOR2I vertexNext = outline.GetPoint( 0 );
1821
1822 // Test if this edge intersects aRect
1823 if( arect.Intersects( vertex, vertexNext ) )
1824 return true;
1825 }
1826 }
1827
1828 return false;
1829 };
1830
1831 switch( m_shape )
1832 {
1833 case SHAPE_T::CIRCLE:
1834 // Test if area intersects or contains the circle:
1835 if( aContained )
1836 {
1837 return arect.Contains( bbox );
1838 }
1839 else
1840 {
1841 // If the rectangle does not intersect the bounding box, this is a much quicker test
1842 if( !arect.Intersects( bbox ) )
1843 return false;
1844 else
1845 return arect.IntersectsCircleEdge( getCenter(), GetRadius(), GetWidth() );
1846 }
1847
1848 case SHAPE_T::ARC:
1849 // Test for full containment of this arc in the rect
1850 if( aContained )
1851 {
1852 return arect.Contains( bbox );
1853 }
1854 // Test if the rect crosses the arc
1855 else
1856 {
1857 if( !arect.Intersects( bbox ) )
1858 return false;
1859
1860 if( IsAnyFill() )
1861 {
1862 return ( arect.Intersects( getCenter(), GetStart() )
1863 || arect.Intersects( getCenter(), GetEnd() )
1864 || arect.IntersectsCircleEdge( getCenter(), GetRadius(), GetWidth() ) );
1865 }
1866 else
1867 {
1868 return arect.IntersectsCircleEdge( getCenter(), GetRadius(), GetWidth() );
1869 }
1870 }
1871
1872 case SHAPE_T::RECTANGLE:
1873 if( aContained )
1874 {
1875 return arect.Contains( bbox );
1876 }
1877 else if( m_cornerRadius > 0 )
1878 {
1880 SHAPE_POLY_SET poly;
1881 rr.TransformToPolygon( poly, getMaxError() );
1882
1883 // Account for the width of the line
1884 arect.Inflate( GetWidth() / 2 );
1885
1886 return checkOutline( poly.Outline( 0 ) );
1887 }
1888 else
1889 {
1890 std::vector<VECTOR2I> pts = GetRectCorners();
1891
1892 // Account for the width of the lines
1893 arect.Inflate( GetWidth() / 2 );
1894 return ( arect.Intersects( pts[0], pts[1] )
1895 || arect.Intersects( pts[1], pts[2] )
1896 || arect.Intersects( pts[2], pts[3] )
1897 || arect.Intersects( pts[3], pts[0] ) );
1898 }
1899
1900 case SHAPE_T::SEGMENT:
1901 if( aContained )
1902 {
1903 return arect.Contains( GetStart() ) && aRect.Contains( GetEnd() );
1904 }
1905 else
1906 {
1907 // Account for the width of the line
1908 arect.Inflate( GetWidth() / 2 );
1909 return arect.Intersects( GetStart(), GetEnd() );
1910 }
1911
1912 case SHAPE_T::POLY:
1913 if( aContained )
1914 {
1915 return arect.Contains( bbox );
1916 }
1917 else
1918 {
1919 // Fast test: if aRect is outside the polygon bounding box,
1920 // rectangles cannot intersect
1921 if( !arect.Intersects( bbox ) )
1922 return false;
1923
1924 // Account for the width of the line
1925 arect.Inflate( GetWidth() / 2 );
1926
1927 for( int ii = 0; ii < GetPolyShape().OutlineCount(); ++ii )
1928 {
1929 if( checkOutline( GetPolyShape().Outline( ii ) ) )
1930 return true;
1931 }
1932
1933 return false;
1934 }
1935
1936 case SHAPE_T::BEZIER:
1937 if( aContained )
1938 {
1939 return arect.Contains( bbox );
1940 }
1941 else
1942 {
1943 // Fast test: if aRect is outside the polygon bounding box,
1944 // rectangles cannot intersect
1945 if( !arect.Intersects( bbox ) )
1946 return false;
1947
1948 // Account for the width of the line
1949 arect.Inflate( GetWidth() / 2 );
1950 const std::vector<VECTOR2I>* pts = &m_bezierPoints;
1951 std::vector<VECTOR2I> updatedBezierPoints;
1952
1953 if( m_bezierPoints.empty() )
1954 {
1956 converter.GetPoly( updatedBezierPoints, aAccuracy / 2 );
1957 pts = &updatedBezierPoints;
1958 }
1959
1960 for( unsigned ii = 1; ii < pts->size(); ii++ )
1961 {
1962 VECTOR2I vertex = ( *pts )[ii - 1];
1963 VECTOR2I vertexNext = ( *pts )[ii];
1964
1965 // Test if the point is within aRect
1966 if( arect.Contains( vertex ) )
1967 return true;
1968
1969 // Test if this edge intersects aRect
1970 if( arect.Intersects( vertex, vertexNext ) )
1971 return true;
1972 }
1973
1974 return false;
1975 }
1976
1977 case SHAPE_T::ELLIPSE:
1979 {
1980 if( aContained )
1981 return arect.Contains( bbox );
1982
1983 if( !arect.Intersects( bbox ) )
1984 return false;
1985
1987
1988 const int tessError = std::max( 1, aAccuracy / 2 );
1989 const SHAPE_LINE_CHAIN chain = e.ConvertToPolyline( tessError );
1990
1991 // Account for the width of the line
1992 arect.Inflate( GetWidth() / 2 );
1993 return checkOutline( chain );
1994 }
1995
1996 default:
1998 return false;
1999 }
2000}
2001
2002
2003bool EDA_SHAPE::hitTest( const SHAPE_LINE_CHAIN& aPoly, bool aContained ) const
2004{
2006
2007 return KIGEOM::ShapeHitTest( aPoly, shape, aContained );
2008}
2009
2010
2011std::vector<VECTOR2I> EDA_SHAPE::GetRectCorners() const
2012{
2013 std::vector<VECTOR2I> pts;
2014 VECTOR2I topLeft = GetStart();
2015 VECTOR2I botRight = GetEnd();
2016
2017 pts.emplace_back( topLeft );
2018 pts.emplace_back( botRight.x, topLeft.y );
2019 pts.emplace_back( botRight );
2020 pts.emplace_back( topLeft.x, botRight.y );
2021
2022 return pts;
2023}
2024
2025
2026std::vector<VECTOR2I> EDA_SHAPE::GetCornersInSequence( EDA_ANGLE angle ) const
2027{
2028 std::vector<VECTOR2I> pts;
2029
2030 angle.Normalize();
2031
2032 BOX2I bbox = getBoundingBox();
2033 bbox.Normalize();
2034
2035 if( angle.IsCardinal() )
2036 {
2037 if( angle == ANGLE_0 )
2038 {
2039 pts.emplace_back( VECTOR2I( bbox.GetLeft(), bbox.GetTop() ) );
2040 pts.emplace_back( VECTOR2I( bbox.GetRight(), bbox.GetTop() ) );
2041 pts.emplace_back( VECTOR2I( bbox.GetRight(), bbox.GetBottom() ) );
2042 pts.emplace_back( VECTOR2I( bbox.GetLeft(), bbox.GetBottom() ) );
2043 }
2044 else if( angle == ANGLE_90 )
2045 {
2046 pts.emplace_back( VECTOR2I( bbox.GetLeft(), bbox.GetBottom() ) );
2047 pts.emplace_back( VECTOR2I( bbox.GetLeft(), bbox.GetTop() ) );
2048 pts.emplace_back( VECTOR2I( bbox.GetRight(), bbox.GetTop() ) );
2049 pts.emplace_back( VECTOR2I( bbox.GetRight(), bbox.GetBottom() ) );
2050 }
2051 else if( angle == ANGLE_180 )
2052 {
2053 pts.emplace_back( VECTOR2I( bbox.GetRight(), bbox.GetBottom() ) );
2054 pts.emplace_back( VECTOR2I( bbox.GetLeft(), bbox.GetBottom() ) );
2055 pts.emplace_back( VECTOR2I( bbox.GetLeft(), bbox.GetTop() ) );
2056 pts.emplace_back( VECTOR2I( bbox.GetRight(), bbox.GetTop() ) );
2057 }
2058 else if( angle == ANGLE_270 )
2059 {
2060 pts.emplace_back( VECTOR2I( bbox.GetRight(), bbox.GetTop() ) );
2061 pts.emplace_back( VECTOR2I( bbox.GetRight(), bbox.GetBottom() ) );
2062 pts.emplace_back( VECTOR2I( bbox.GetLeft(), bbox.GetBottom() ) );
2063 pts.emplace_back( VECTOR2I( bbox.GetLeft(), bbox.GetTop() ) );
2064 }
2065 }
2066 else
2067 {
2068 // This function was originally located in pcb_textbox.cpp and was later moved to eda_shape.cpp.
2069 // As a result of this move, access to getCorners was lost, since it is defined in the PCB_SHAPE
2070 // class within pcb_shape.cpp and is not available in the current context.
2071 //
2072 // Additionally, GetRectCorners() cannot be used here, as it assumes the rectangle is rotated by
2073 // a cardinal angle. In non-cardinal cases, it returns incorrect values (e.g., (0, 0)).
2074 //
2075 // To address this, a portion of the getCorners implementation for SHAPE_T::POLY elements
2076 // has been replicated here to restore the correct behavior.
2077 std::vector<VECTOR2I> corners;
2078
2079 for( int ii = 0; ii < GetPolyShape().OutlineCount(); ++ii )
2080 {
2081 for( const VECTOR2I& pt : GetPolyShape().Outline( ii ).CPoints() )
2082 corners.emplace_back( pt );
2083 }
2084
2085 while( corners.size() < 4 )
2086 corners.emplace_back( corners.back() + VECTOR2I( 10, 10 ) );
2087
2088 VECTOR2I minX = corners[0];
2089 VECTOR2I maxX = corners[0];
2090 VECTOR2I minY = corners[0];
2091 VECTOR2I maxY = corners[0];
2092
2093 for( const VECTOR2I& corner : corners )
2094 {
2095 if( corner.x < minX.x )
2096 minX = corner;
2097
2098 if( corner.x > maxX.x )
2099 maxX = corner;
2100
2101 if( corner.y < minY.y )
2102 minY = corner;
2103
2104 if( corner.y > maxY.y )
2105 maxY = corner;
2106 }
2107
2108 if( angle < ANGLE_90 )
2109 {
2110 pts.emplace_back( minX );
2111 pts.emplace_back( minY );
2112 pts.emplace_back( maxX );
2113 pts.emplace_back( maxY );
2114 }
2115 else if( angle < ANGLE_180 )
2116 {
2117 pts.emplace_back( maxY );
2118 pts.emplace_back( minX );
2119 pts.emplace_back( minY );
2120 pts.emplace_back( maxX );
2121 }
2122 else if( angle < ANGLE_270 )
2123 {
2124 pts.emplace_back( maxX );
2125 pts.emplace_back( maxY );
2126 pts.emplace_back( minX );
2127 pts.emplace_back( minY );
2128 }
2129 else
2130 {
2131 pts.emplace_back( minY );
2132 pts.emplace_back( maxX );
2133 pts.emplace_back( maxY );
2134 pts.emplace_back( minX );
2135 }
2136 }
2137
2138 return pts;
2139}
2140
2141
2143{
2144 // Start, end, and each inflection point the arc crosses will enclose the entire arc.
2145 // Only include the center when filled; it's not necessarily inside the BB of an unfilled
2146 // arc with a small included angle.
2147 aBBox.SetOrigin( m_start );
2148 aBBox.Merge( m_end );
2149
2150 if( IsAnyFill() )
2151 aBBox.Merge( m_arcCenter );
2152
2153 int radius = GetRadius();
2154 EDA_ANGLE t1, t2;
2155
2156 CalcArcAngles( t1, t2 );
2157
2158 t1.Normalize();
2159 t2.Normalize();
2160
2161 if( t2 > t1 )
2162 {
2163 if( t1 < ANGLE_0 && t2 > ANGLE_0 )
2164 aBBox.Merge( VECTOR2I( m_arcCenter.x + radius, m_arcCenter.y ) ); // right
2165
2166 if( t1 < ANGLE_90 && t2 > ANGLE_90 )
2167 aBBox.Merge( VECTOR2I( m_arcCenter.x, m_arcCenter.y + radius ) ); // down
2168
2169 if( t1 < ANGLE_180 && t2 > ANGLE_180 )
2170 aBBox.Merge( VECTOR2I( m_arcCenter.x - radius, m_arcCenter.y ) ); // left
2171
2172 if( t1 < ANGLE_270 && t2 > ANGLE_270 )
2173 aBBox.Merge( VECTOR2I( m_arcCenter.x, m_arcCenter.y - radius ) ); // up
2174 }
2175 else
2176 {
2177 if( t1 < ANGLE_0 || t2 > ANGLE_0 )
2178 aBBox.Merge( VECTOR2I( m_arcCenter.x + radius, m_arcCenter.y ) ); // right
2179
2180 if( t1 < ANGLE_90 || t2 > ANGLE_90 )
2181 aBBox.Merge( VECTOR2I( m_arcCenter.x, m_arcCenter.y + radius ) ); // down
2182
2183 if( t1 < ANGLE_180 || t2 > ANGLE_180 )
2184 aBBox.Merge( VECTOR2I( m_arcCenter.x - radius, m_arcCenter.y ) ); // left
2185
2186 if( t1 < ANGLE_270 || t2 > ANGLE_270 )
2187 aBBox.Merge( VECTOR2I( m_arcCenter.x, m_arcCenter.y - radius ) ); // up
2188 }
2189}
2190
2191
2192void EDA_SHAPE::SetPolyPoints( const std::vector<VECTOR2I>& aPoints )
2193{
2196
2197 for( const VECTOR2I& p : aPoints )
2198 GetPolyShape().Append( p.x, p.y );
2199}
2200
2201
2202std::vector<SHAPE*> EDA_SHAPE::makeEffectiveShapes( bool aEdgeOnly, bool aLineChainOnly, bool aHittesting ) const
2203{
2204 std::vector<SHAPE*> effectiveShapes;
2205 int width = GetEffectiveWidth();
2206 bool solidFill = IsSolidFill()
2207 || IsHatchedFill()
2208 || IsProxyItem()
2209 || ( aHittesting && IsFilledForHitTesting() );
2210
2211 if( aEdgeOnly )
2212 solidFill = false;
2213
2214 switch( m_shape )
2215 {
2216 case SHAPE_T::ARC:
2217 effectiveShapes.emplace_back( new SHAPE_ARC( m_arcCenter, m_start, GetArcAngle(), width ) );
2218 break;
2219
2220 case SHAPE_T::SEGMENT:
2221 effectiveShapes.emplace_back( new SHAPE_SEGMENT( m_start, m_end, width ) );
2222 break;
2223
2224 case SHAPE_T::RECTANGLE:
2225 {
2226 if( m_cornerRadius > 0 )
2227 {
2229 SHAPE_POLY_SET poly;
2230 rr.TransformToPolygon( poly, getMaxError() );
2231 SHAPE_LINE_CHAIN outline = poly.Outline( 0 );
2232
2233 if( solidFill )
2234 effectiveShapes.emplace_back( new SHAPE_SIMPLE( outline ) );
2235
2236 if( width > 0 || !solidFill )
2237 {
2238 std::set<size_t> arcsHandled;
2239
2240 for( int ii = 0; ii < outline.SegmentCount(); ++ii )
2241 {
2242 if( outline.IsArcSegment( ii ) )
2243 {
2244 size_t arcIndex = outline.ArcIndex( ii );
2245
2246 if( !arcsHandled.contains( arcIndex ) )
2247 {
2248 arcsHandled.insert( arcIndex );
2249 effectiveShapes.emplace_back( new SHAPE_ARC( outline.Arc( arcIndex ), width ) );
2250 }
2251 }
2252 else
2253 {
2254 effectiveShapes.emplace_back( new SHAPE_SEGMENT( outline.Segment( ii ), width ) );
2255 }
2256 }
2257 }
2258 }
2259 else
2260 {
2261 std::vector<VECTOR2I> pts = GetRectCorners();
2262
2263 if( solidFill )
2264 effectiveShapes.emplace_back( new SHAPE_SIMPLE( pts ) );
2265
2266 if( width > 0 || !solidFill )
2267 {
2268 effectiveShapes.emplace_back( new SHAPE_SEGMENT( pts[0], pts[1], width ) );
2269 effectiveShapes.emplace_back( new SHAPE_SEGMENT( pts[1], pts[2], width ) );
2270 effectiveShapes.emplace_back( new SHAPE_SEGMENT( pts[2], pts[3], width ) );
2271 effectiveShapes.emplace_back( new SHAPE_SEGMENT( pts[3], pts[0], width ) );
2272 }
2273 }
2274 break;
2275 }
2276
2277 case SHAPE_T::CIRCLE:
2278 {
2279 if( solidFill )
2280 effectiveShapes.emplace_back( new SHAPE_CIRCLE( getCenter(), GetRadius() ) );
2281
2282 if( width > 0 || !solidFill )
2283 effectiveShapes.emplace_back( new SHAPE_ARC( getCenter(), GetEnd(), ANGLE_360, width ) );
2284
2285 break;
2286 }
2287
2288 case SHAPE_T::BEZIER:
2289 {
2290 std::vector<VECTOR2I> bezierPoints = buildBezierToSegmentsPointsList( getMaxError() );
2291 VECTOR2I start_pt = bezierPoints[0];
2292
2293 for( unsigned int jj = 1; jj < bezierPoints.size(); jj++ )
2294 {
2295 VECTOR2I end_pt = bezierPoints[jj];
2296 effectiveShapes.emplace_back( new SHAPE_SEGMENT( start_pt, end_pt, width ) );
2297 start_pt = end_pt;
2298 }
2299
2300 break;
2301 }
2302
2303 case SHAPE_T::POLY:
2304 {
2305 if( GetPolyShape().OutlineCount() == 0 ) // malformed/empty polygon
2306 break;
2307
2308 for( int ii = 0; ii < GetPolyShape().OutlineCount(); ++ii )
2309 {
2310 const SHAPE_LINE_CHAIN& l = GetPolyShape().COutline( ii );
2311
2312 if( solidFill )
2313 effectiveShapes.emplace_back( new SHAPE_SIMPLE( l ) );
2314
2315 if( width > 0 || !IsSolidFill() || aEdgeOnly )
2316 {
2317 int segCount = l.SegmentCount();
2318
2319 if( aLineChainOnly && l.IsClosed() )
2320 segCount--; // Treat closed chain as open
2321
2322 for( int jj = 0; jj < segCount; jj++ )
2323 effectiveShapes.emplace_back( new SHAPE_SEGMENT( l.CSegment( jj ), width ) );
2324 }
2325 }
2326 }
2327 break;
2328
2329 case SHAPE_T::ELLIPSE:
2331 {
2332 if( solidFill && m_shape == SHAPE_T::ELLIPSE )
2333 {
2334 // Filled closed ellipse: emit a SHAPE_SIMPLE for the filled interior.
2337 std::vector<VECTOR2I> pts;
2338
2339 for( int ii = 0; ii < chain.PointCount(); ++ii )
2340 pts.emplace_back( chain.CPoint( ii ) );
2341
2342 effectiveShapes.emplace_back( new SHAPE_SIMPLE( pts ) );
2343 }
2344
2345 if( width > 0 || !solidFill )
2346 {
2349
2350 for( int ii = 0; ii < chain.SegmentCount(); ++ii )
2351 effectiveShapes.emplace_back( new SHAPE_SEGMENT( chain.CSegment( ii ), width ) );
2352 }
2353
2354 break;
2355 }
2356
2357 default:
2359 break;
2360 }
2361
2362 return effectiveShapes;
2363}
2364
2365
2366std::vector<VECTOR2I> EDA_SHAPE::GetPolyPoints() const
2367{
2368 std::vector<VECTOR2I> points;
2369
2370 for( int ii = 0; ii < GetPolyShape().OutlineCount(); ++ii )
2371 {
2372 const SHAPE_LINE_CHAIN& outline = GetPolyShape().COutline( ii );
2373 int pointCount = outline.PointCount();
2374
2375 if( pointCount )
2376 {
2377 points.reserve( points.size() + pointCount );
2378
2379 for( const VECTOR2I& pt : outline.CPoints() )
2380 points.emplace_back( pt );
2381 }
2382 }
2383
2384 return points;
2385}
2386
2387
2389{
2390 if( !m_poly )
2391 m_poly = std::make_unique<SHAPE_POLY_SET>();
2392
2393 return *m_poly;
2394}
2395
2397{
2398 if( !m_poly )
2399 m_poly = std::make_unique<SHAPE_POLY_SET>();
2400
2401 return *m_poly;
2402}
2403
2404
2406{
2407 // return true if the polygonal shape is valid (has more than 2 points)
2408 return GetPolyShape().OutlineCount() > 0 && GetPolyShape().Outline( 0 ).PointCount() > 2;
2409}
2410
2411
2413{
2414 // return the number of corners of the polygonal shape
2415 // this shape is expected to be only one polygon without hole
2416 return GetPolyShape().OutlineCount() ? GetPolyShape().VertexCount( 0 ) : 0;
2417}
2418
2419
2420void EDA_SHAPE::beginEdit( const VECTOR2I& aPosition )
2421{
2422 switch( GetShape() )
2423 {
2424 case SHAPE_T::SEGMENT:
2425 case SHAPE_T::CIRCLE:
2426 case SHAPE_T::RECTANGLE:
2427 SetStart( aPosition );
2428 SetEnd( aPosition );
2429 break;
2430
2431 case SHAPE_T::ARC:
2432 SetArcGeometry( aPosition, aPosition, aPosition );
2433 m_editState = 1;
2434 break;
2435
2436 case SHAPE_T::BEZIER:
2437 SetStart( aPosition );
2438 SetEnd( aPosition );
2439 SetBezierC1( aPosition );
2440 SetBezierC2( aPosition );
2441 m_editState = 1;
2442
2444 break;
2445
2446 case SHAPE_T::POLY:
2448 GetPolyShape().Outline( 0 ).SetClosed( false );
2449
2450 // Start and end of the first segment (co-located for now)
2451 GetPolyShape().Outline( 0 ).Append( aPosition );
2452 GetPolyShape().Outline( 0 ).Append( aPosition, true );
2453 break;
2454
2455 case SHAPE_T::ELLIPSE:
2456 // m_start holds the first bbox corner and calcEdit derives the ellipse from it.
2457 m_editState = 1;
2458 SetStart( aPosition );
2459 SetEnd( aPosition );
2460 SetEllipseCenter( aPosition );
2464 break;
2465
2467 // State 1: drag bbox. States 2-3: pick start then end angle.
2468 SetStart( aPosition );
2469 SetEnd( aPosition );
2470 SetEllipseCenter( aPosition );
2476 m_editState = 1;
2477 break;
2478
2479 default:
2481 }
2482}
2483
2484
2485bool EDA_SHAPE::continueEdit( const VECTOR2I& aPosition )
2486{
2487 switch( GetShape() )
2488 {
2489 case SHAPE_T::ARC:
2490 case SHAPE_T::SEGMENT:
2491 case SHAPE_T::CIRCLE:
2492 case SHAPE_T::RECTANGLE:
2493 case SHAPE_T::ELLIPSE: return false;
2494
2495 case SHAPE_T::BEZIER:
2496 if( m_editState == 3 )
2497 return false;
2498
2499 m_editState++;
2500 return true;
2501
2503 if( m_editState == 3 )
2504 return false;
2505
2506 m_editState++;
2507 return true;
2508
2509 case SHAPE_T::POLY:
2510 {
2511 SHAPE_LINE_CHAIN& poly = GetPolyShape().Outline( 0 );
2512
2513 // do not add zero-length segments
2514 if( poly.CPoint( (int) poly.GetPointCount() - 2 ) != poly.CLastPoint() )
2515 poly.Append( aPosition, true );
2516 }
2517 return true;
2518
2519 default:
2521 return false;
2522 }
2523}
2524
2525
2526void EDA_SHAPE::calcEdit( const VECTOR2I& aPosition )
2527{
2528#define sq( x ) pow( x, 2 )
2529
2530 switch( GetShape() )
2531 {
2532 case SHAPE_T::SEGMENT:
2533 case SHAPE_T::CIRCLE:
2534 case SHAPE_T::RECTANGLE:
2535 SetEnd( aPosition );
2536 break;
2537
2538 case SHAPE_T::BEZIER:
2539 {
2540 switch( m_editState )
2541 {
2542 case 0:
2543 SetStart( aPosition );
2544 SetEnd( aPosition );
2545 SetBezierC1( aPosition );
2546 SetBezierC2( aPosition );
2547 break;
2548
2549 case 1:
2550 SetBezierC2( aPosition );
2551 SetEnd( aPosition );
2552 break;
2553
2554 case 2:
2555 SetBezierC1( aPosition );
2556 break;
2557
2558 case 3:
2559 SetBezierC2( aPosition );
2560 break;
2561 }
2562
2564 }
2565 break;
2566
2567 case SHAPE_T::ARC:
2568 {
2569 double radius = GetRadius();
2570 EDA_ANGLE lastAngle = GetArcAngle();
2571
2572 // Edit state 0: drawing: place start
2573 // Edit state 1: drawing: place end (center calculated for 90-degree subtended angle)
2574 // Edit state 2: point edit: move start (center calculated for invariant subtended angle)
2575 // Edit state 3: point edit: move end (center calculated for invariant subtended angle)
2576 // Edit state 4: point edit: move center
2577 // Edit state 5: point edit: move arc-mid-point
2578
2579 switch( m_editState )
2580 {
2581 case 0:
2582 SetArcGeometry( aPosition, aPosition, aPosition );
2583 return;
2584
2585 case 1:
2586 m_end = aPosition;
2587 radius = m_start.Distance( m_end ) * M_SQRT1_2;
2588 break;
2589
2590 case 2:
2591 case 3:
2592 {
2593 VECTOR2I v = m_start - m_end;
2594 double chordBefore = v.SquaredEuclideanNorm();
2595
2596 if( m_editState == 2 )
2597 m_start = aPosition;
2598 else
2599 m_end = aPosition;
2600
2601 v = m_start - m_end;
2602
2603 double chordAfter = v.SquaredEuclideanNorm();
2604 double ratio = 0.0;
2605
2606 if( chordBefore > 0 )
2607 ratio = chordAfter / chordBefore;
2608
2609 if( ratio != 0 )
2610 radius = std::max( sqrt( sq( radius ) * ratio ), sqrt( chordAfter ) / 2 );
2611 break;
2612 }
2613
2614 case 4:
2615 {
2616 double radialA = m_start.Distance( aPosition );
2617 double radialB = m_end.Distance( aPosition );
2618 radius = ( radialA + radialB ) / 2.0;
2619 break;
2620 }
2621
2622 case 5:
2623 SetArcGeometry( GetStart(), aPosition, GetEnd() );
2624 return;
2625 }
2626
2627 // Calculate center based on start, end, and radius
2628 //
2629 // Let 'l' be the length of the chord and 'm' the middle point of the chord
2630 double l = m_start.Distance( m_end );
2631 VECTOR2D m = ( m_start + m_end ) / 2;
2632 double sqRadDiff = ( radius * radius ) - ( l * l ) / 4.0;
2633
2634 // Calculate 'd', the vector from the chord midpoint to the center
2635 VECTOR2D d;
2636
2637 if( l > 0 && sqRadDiff >= 0 )
2638 {
2639 d.x = sqrt( sqRadDiff ) * ( m_start.y - m_end.y ) / l;
2640 d.y = sqrt( sqRadDiff ) * ( m_end.x - m_start.x ) / l;
2641 }
2642
2643 VECTOR2I c1 = KiROUND( m + d );
2644 VECTOR2I c2 = KiROUND( m - d );
2645
2646 // Solution gives us 2 centers; we need to pick one:
2647 switch( m_editState )
2648 {
2649 case 1:
2650 // Keep arc clockwise while drawing i.e. arc angle = 90 deg.
2651 // it can be 90 or 270 deg depending on the arc center choice (c1 or c2)
2652 m_arcCenter = c1; // first trial
2653
2654 if( GetArcAngle() > ANGLE_180 )
2655 m_arcCenter = c2;
2656
2657 break;
2658
2659 case 2:
2660 case 3:
2661 // Pick the one of c1, c2 to keep arc on the same side
2662 m_arcCenter = c1; // first trial
2663
2664 if( ( lastAngle < ANGLE_180 ) != ( GetArcAngle() < ANGLE_180 ) )
2665 m_arcCenter = c2;
2666
2667 break;
2668
2669 case 4:
2670 // Pick the one closer to the mouse position
2671 m_arcCenter = c1.Distance( aPosition ) < c2.Distance( aPosition ) ? c1 : c2;
2672 break;
2673 }
2674
2675 break;
2676 }
2677
2678 case SHAPE_T::POLY:
2679 GetPolyShape().Outline( 0 ).SetPoint( GetPolyShape().Outline( 0 ).GetPointCount() - 1,
2680 aPosition );
2681 break;
2682
2683 case SHAPE_T::ELLIPSE:
2684 {
2685 const VECTOR2I firstCorner = GetStart();
2686 const VECTOR2I secondCorner = aPosition;
2687 const VECTOR2I center = ( firstCorner + secondCorner ) / 2;
2688 const int halfW = std::abs( secondCorner.x - firstCorner.x ) / 2;
2689 const int halfH = std::abs( secondCorner.y - firstCorner.y ) / 2;
2690
2691 int majorRadius;
2692 int minorRadius;
2693 EDA_ANGLE rotation;
2694
2695 if( halfW >= halfH )
2696 {
2697 majorRadius = std::max( halfW, 1 );
2698 minorRadius = std::max( halfH, 1 );
2699 rotation = ANGLE_0;
2700 }
2701 else
2702 {
2703 majorRadius = std::max( halfH, 1 );
2704 minorRadius = std::max( halfW, 1 );
2705 rotation = ANGLE_90;
2706 }
2707
2709 SetEllipseMajorRadius( majorRadius );
2710 SetEllipseMinorRadius( minorRadius );
2711 SetEllipseRotation( rotation );
2712 SetEnd( aPosition );
2713 break;
2714 }
2715
2717 {
2718 switch( m_editState )
2719 {
2720 case 0:
2721 case 1:
2722 {
2723 // Bbox
2724 const VECTOR2I firstCorner = GetStart();
2725 const VECTOR2I secondCorner = aPosition;
2726 const VECTOR2I center = ( firstCorner + secondCorner ) / 2;
2727 const int halfW = std::abs( secondCorner.x - firstCorner.x ) / 2;
2728 const int halfH = std::abs( secondCorner.y - firstCorner.y ) / 2;
2729
2730 int majorRadius;
2731 int minorRadius;
2732 EDA_ANGLE rotation;
2733
2734 if( halfW >= halfH )
2735 {
2736 majorRadius = std::max( halfW, 1 );
2737 minorRadius = std::max( halfH, 1 );
2738 rotation = ANGLE_0;
2739 }
2740 else
2741 {
2742 majorRadius = std::max( halfH, 1 );
2743 minorRadius = std::max( halfW, 1 );
2744 rotation = ANGLE_90;
2745 }
2746
2748 SetEllipseMajorRadius( majorRadius );
2749 SetEllipseMinorRadius( minorRadius );
2750 SetEllipseRotation( rotation );
2751 SetEnd( aPosition );
2752
2753 // Keep the preview rendering as a full closed ellipse during bbox build.
2756
2757 break;
2758 }
2759
2760 case 2:
2761 case 3:
2762 {
2763 // Project cursor onto the parametric form (a * cos t, b * sin t) to get t.
2764 const VECTOR2I center = m_ellipse.Center;
2765 const double a = std::max( 1, m_ellipse.MajorRadius );
2766 const double b = std::max( 1, m_ellipse.MinorRadius );
2767 const EDA_ANGLE rotation = m_ellipse.Rotation;
2768
2769 const double dx = aPosition.x - center.x;
2770 const double dy = aPosition.y - center.y;
2771
2772 const double cosRot = rotation.Cos();
2773 const double sinRot = rotation.Sin();
2774 const double lx = dx * cosRot + dy * sinRot;
2775 const double ly = -dx * sinRot + dy * cosRot;
2776
2777 const EDA_ANGLE paramAngle( std::atan2( ly / b, lx / a ), RADIANS_T );
2778
2779 if( m_editState == 2 )
2780 {
2781 SetEllipseStartAngle( paramAngle );
2782 SetEllipseEndAngle( paramAngle + ANGLE_360 );
2783 }
2784 else
2785 {
2786 // Force end > start
2787 EDA_ANGLE cursorAngle = paramAngle;
2788
2789 while( cursorAngle <= m_ellipse.StartAngle )
2790 cursorAngle = cursorAngle + ANGLE_360;
2791
2792 SetEllipseEndAngle( cursorAngle );
2793 }
2794
2795 break;
2796 }
2797 }
2798
2799 break;
2800 }
2801
2802 default:
2804 }
2805}
2806
2807
2808void EDA_SHAPE::endEdit( bool aClosed )
2809{
2810 switch( GetShape() )
2811 {
2812 case SHAPE_T::ARC:
2813 case SHAPE_T::SEGMENT:
2814 case SHAPE_T::CIRCLE:
2815 case SHAPE_T::RECTANGLE:
2816 case SHAPE_T::BEZIER: break;
2817
2818 case SHAPE_T::ELLIPSE:
2820 m_editState = 0;
2822 break;
2823
2824 case SHAPE_T::POLY:
2825 {
2826 SHAPE_LINE_CHAIN& poly = GetPolyShape().Outline( 0 );
2827
2828 // do not include last point twice
2829 if( poly.GetPointCount() > 2 )
2830 {
2831 if( poly.CPoint( poly.GetPointCount() - 2 ) == poly.CLastPoint() )
2832 {
2833 poly.SetClosed( aClosed );
2834 }
2835 else
2836 {
2837 poly.SetClosed( false );
2838 poly.Remove( poly.GetPointCount() - 1 );
2839 }
2840 }
2841
2842 break;
2843 }
2844
2845 default:
2847 }
2848}
2849
2850
2852{
2853 EDA_SHAPE* image = dynamic_cast<EDA_SHAPE*>( aImage );
2854 assert( image );
2855
2856 #define SWAPITEM( x ) std::swap( x, image->x )
2857 SWAPITEM( m_stroke );
2858 SWAPITEM( m_start );
2859 SWAPITEM( m_end );
2861 SWAPITEM( m_shape );
2865 SWAPITEM( m_poly );
2868 SWAPITEM( m_fill );
2872 #undef SWAPITEM
2873
2874 m_hatchingDirty = true;
2875}
2876
2877
2878int EDA_SHAPE::Compare( const EDA_SHAPE* aOther ) const
2879{
2880#define EPSILON 2 // Should be enough for rounding errors on calculated items
2881
2882#define TEST( a, b ) { if( a != b ) return a - b; }
2883#define TEST_E( a, b ) { if( abs( a - b ) > EPSILON ) return a - b; }
2884#define TEST_PT( a, b ) { TEST_E( a.x, b.x ); TEST_E( a.y, b.y ); }
2885
2886 TEST_PT( m_start, aOther->m_start );
2887 TEST_PT( m_end, aOther->m_end );
2888
2889 TEST( (int) m_shape, (int) aOther->m_shape );
2890
2892 {
2894 }
2895 else if( m_shape == SHAPE_T::ARC )
2896 {
2897 TEST_PT( GetArcMid(), aOther->GetArcMid() );
2898 }
2899 else if( m_shape == SHAPE_T::BEZIER )
2900 {
2901 TEST_PT( m_bezierC1, aOther->m_bezierC1 );
2902 TEST_PT( m_bezierC2, aOther->m_bezierC2 );
2903 }
2905 {
2906 TEST_PT( m_ellipse.Center, aOther->m_ellipse.Center );
2907 TEST_E( m_ellipse.MajorRadius, aOther->m_ellipse.MajorRadius );
2908 TEST_E( m_ellipse.MinorRadius, aOther->m_ellipse.MinorRadius );
2909 TEST_E( m_ellipse.Rotation.AsTenthsOfADegree(), aOther->m_ellipse.Rotation.AsTenthsOfADegree() );
2910
2912 {
2913 TEST_E( m_ellipse.StartAngle.AsTenthsOfADegree(), aOther->m_ellipse.StartAngle.AsTenthsOfADegree() );
2914 TEST_E( m_ellipse.EndAngle.AsTenthsOfADegree(), aOther->m_ellipse.EndAngle.AsTenthsOfADegree() );
2915 }
2916 }
2917 else if( m_shape == SHAPE_T::POLY )
2918 {
2919 TEST( GetPolyShape().TotalVertices(), aOther->GetPolyShape().TotalVertices() );
2920 }
2921
2922 for( size_t ii = 0; ii < m_bezierPoints.size(); ++ii )
2923 TEST_PT( m_bezierPoints[ii], aOther->m_bezierPoints[ii] );
2924
2925 for( int ii = 0; ii < GetPolyShape().TotalVertices(); ++ii )
2926 TEST_PT( GetPolyShape().CVertex( ii ), aOther->GetPolyShape().CVertex( ii ) );
2927
2928 TEST_E( m_stroke.GetWidth(), aOther->m_stroke.GetWidth() );
2929 TEST( (int) m_stroke.GetLineStyle(), (int) aOther->m_stroke.GetLineStyle() );
2930 TEST( (int) m_fill, (int) aOther->m_fill );
2931
2932 return 0;
2933}
2934
2935
2936void EDA_SHAPE::TransformShapeToPolygon( SHAPE_POLY_SET& aBuffer, int aClearance, int aError,
2937 ERROR_LOC aErrorLoc, bool ignoreLineWidth, bool includeFill ) const
2938{
2939 bool solidFill = IsSolidFill() || ( IsHatchedFill() && !includeFill ) || IsProxyItem();
2940 int width = ignoreLineWidth ? 0 : GetWidth();
2941
2942 width += 2 * aClearance;
2943
2944 switch( m_shape )
2945 {
2946 case SHAPE_T::CIRCLE:
2947 {
2948 int r = GetRadius();
2949
2950 if( solidFill )
2951 TransformCircleToPolygon( aBuffer, getCenter(), r + width / 2, aError, aErrorLoc );
2952 else
2953 TransformRingToPolygon( aBuffer, getCenter(), r, width, aError, aErrorLoc );
2954
2955 break;
2956 }
2957
2958 case SHAPE_T::RECTANGLE:
2959 {
2960 if( GetCornerRadius() > 0 )
2961 {
2963 BOX2I bbox = getBoundingBox();
2964 VECTOR2I position = bbox.GetCenter();
2965
2966 if( solidFill )
2967 {
2969 0.0, 0, width / 2, aError, aErrorLoc );
2970 }
2971 else
2972 {
2974 SHAPE_POLY_SET poly;
2975 rr.TransformToPolygon( poly, aError );
2976 SHAPE_LINE_CHAIN& outline = poly.Outline( 0 );
2977 outline.SetClosed( true );
2978
2979 std::set<size_t> arcsHandled;
2980
2981 for( int ii = 0; ii < outline.SegmentCount(); ++ii )
2982 {
2983 if( outline.IsArcSegment( ii ) )
2984 {
2985 size_t arcIndex = outline.ArcIndex( ii );
2986
2987 if( arcsHandled.contains( arcIndex ) )
2988 continue;
2989
2990 arcsHandled.insert( arcIndex );
2991
2992 const SHAPE_ARC& arc = outline.Arc( arcIndex );
2993 TransformArcToPolygon( aBuffer, arc.GetP0(), arc.GetArcMid(), arc.GetP1(), width, aError,
2994 aErrorLoc );
2995 }
2996 else
2997 {
2998 const SEG& seg = outline.GetSegment( ii );
2999 TransformOvalToPolygon( aBuffer, seg.A, seg.B, width, aError, aErrorLoc );
3000 }
3001 }
3002 }
3003 }
3004 else
3005 {
3006 std::vector<VECTOR2I> pts = GetRectCorners();
3007
3008 if( solidFill )
3009 {
3010 aBuffer.NewOutline();
3011
3012 for( const VECTOR2I& pt : pts )
3013 aBuffer.Append( pt );
3014 }
3015
3016 if( width > 0 || !solidFill )
3017 {
3018 // Add in segments
3019 TransformOvalToPolygon( aBuffer, pts[0], pts[1], width, aError, aErrorLoc );
3020 TransformOvalToPolygon( aBuffer, pts[1], pts[2], width, aError, aErrorLoc );
3021 TransformOvalToPolygon( aBuffer, pts[2], pts[3], width, aError, aErrorLoc );
3022 TransformOvalToPolygon( aBuffer, pts[3], pts[0], width, aError, aErrorLoc );
3023 }
3024 }
3025
3026 break;
3027 }
3028
3029 case SHAPE_T::ARC:
3030 TransformArcToPolygon( aBuffer, GetStart(), GetArcMid(), GetEnd(), width, aError, aErrorLoc );
3031 break;
3032
3033 case SHAPE_T::SEGMENT:
3034 TransformOvalToPolygon( aBuffer, GetStart(), GetEnd(), width, aError, aErrorLoc );
3035 break;
3036
3037 case SHAPE_T::POLY:
3038 {
3039 if( !IsPolyShapeValid() )
3040 break;
3041
3042 if( solidFill )
3043 {
3044 for( int ii = 0; ii < GetPolyShape().OutlineCount(); ++ii )
3045 {
3046 const SHAPE_LINE_CHAIN& poly = GetPolyShape().Outline( ii );
3047 SHAPE_POLY_SET tmp;
3048 tmp.NewOutline();
3049
3050 for( int jj = 0; jj < (int) poly.GetPointCount(); ++jj )
3051 tmp.Append( poly.GetPoint( jj ) );
3052
3053 if( width > 0 )
3054 {
3055 int inflate = width / 2;
3056
3057 if( aErrorLoc == ERROR_OUTSIDE )
3058 inflate += aError;
3059
3060 tmp.Inflate( inflate, CORNER_STRATEGY::ROUND_ALL_CORNERS, aError );
3061 }
3062
3063 aBuffer.Append( tmp );
3064 }
3065 }
3066 else
3067 {
3068 for( int ii = 0; ii < GetPolyShape().OutlineCount(); ++ii )
3069 {
3070 const SHAPE_LINE_CHAIN& poly = GetPolyShape().Outline( ii );
3071
3072 for( int jj = 0; jj < (int) poly.SegmentCount(); ++jj )
3073 {
3074 const SEG& seg = poly.GetSegment( jj );
3075 TransformOvalToPolygon( aBuffer, seg.A, seg.B, width, aError, aErrorLoc );
3076 }
3077 }
3078 }
3079
3080 break;
3081 }
3082
3083 case SHAPE_T::BEZIER:
3084 {
3085 std::vector<VECTOR2I> ctrlPts = { GetStart(), GetBezierC1(), GetBezierC2(), GetEnd() };
3086 BEZIER_POLY converter( ctrlPts );
3087 std::vector<VECTOR2I> poly;
3088 converter.GetPoly( poly, aError );
3089
3090 for( unsigned ii = 1; ii < poly.size(); ii++ )
3091 TransformOvalToPolygon( aBuffer, poly[ii - 1], poly[ii], width, aError, aErrorLoc );
3092
3093 break;
3094 }
3095
3096 case SHAPE_T::ELLIPSE:
3098 {
3100
3102
3103 if( solidFill && m_shape == SHAPE_T::ELLIPSE )
3104 {
3105 // Filled closed ellipse, build the outline, inflate for stroke width.
3106 SHAPE_POLY_SET tmp;
3107 tmp.NewOutline();
3108
3109 for( int ii = 0; ii < chain.PointCount(); ++ii )
3110 tmp.Append( chain.CPoint( ii ) );
3111
3112 if( width > 0 )
3113 {
3114 int inflate = width / 2;
3115
3116 if( aErrorLoc == ERROR_OUTSIDE )
3117 inflate += aError;
3118
3119 tmp.Inflate( inflate, CORNER_STRATEGY::ROUND_ALL_CORNERS, aError );
3120 }
3121
3122 aBuffer.Append( tmp );
3123 }
3124 else
3125 {
3126 // stroke each tessellated segment as an oval.
3127 for( int ii = 0; ii < chain.SegmentCount(); ++ii )
3128 {
3129 const SEG& seg = chain.CSegment( ii );
3130 TransformOvalToPolygon( aBuffer, seg.A, seg.B, width, aError, aErrorLoc );
3131 }
3132 }
3133
3134 break;
3135 }
3136
3137 default:
3139 break;
3140 }
3141
3142 if( IsHatchedFill() && includeFill )
3143 {
3144 for( int ii = 0; ii < GetHatching().OutlineCount(); ++ii )
3145 aBuffer.AddOutline( GetHatching().COutline( ii ) );
3146 }
3147}
3148
3149
3150void EDA_SHAPE::SetWidth( int aWidth )
3151{
3152 m_stroke.SetWidth( aWidth );
3153 m_hatchingDirty = true;
3154}
3155
3156
3158{
3159 m_stroke.SetLineStyle( aStyle );
3160}
3161
3162
3164{
3165 if( m_stroke.GetLineStyle() != LINE_STYLE::DEFAULT )
3166 return m_stroke.GetLineStyle();
3167
3168 return LINE_STYLE::SOLID;
3169}
3170
3171
3172bool EDA_SHAPE::operator==( const EDA_SHAPE& aOther ) const
3173{
3174 if( GetShape() != aOther.GetShape() )
3175 return false;
3176
3177 if( m_fill != aOther.m_fill )
3178 return false;
3179
3180 if( m_stroke.GetWidth() != aOther.m_stroke.GetWidth() )
3181 return false;
3182
3183 if( m_stroke.GetLineStyle() != aOther.m_stroke.GetLineStyle() )
3184 return false;
3185
3186 if( m_fillColor != aOther.m_fillColor )
3187 return false;
3188
3189 switch( GetShape() )
3190 {
3191 case SHAPE_T::SEGMENT:
3192 case SHAPE_T::RECTANGLE:
3193 case SHAPE_T::CIRCLE:
3194 if( m_start != aOther.m_start )
3195 return false;
3196
3197 if( m_end != aOther.m_end )
3198 return false;
3199
3200 break;
3201
3202 case SHAPE_T::ARC:
3203 if( m_start != aOther.m_start )
3204 return false;
3205
3206 if( m_end != aOther.m_end )
3207 return false;
3208
3209 if( m_arcCenter != aOther.m_arcCenter )
3210 return false;
3211
3212 break;
3213
3214 case SHAPE_T::POLY:
3215 if( GetPolyShape().TotalVertices() != aOther.GetPolyShape().TotalVertices() )
3216 return false;
3217
3218 for( int ii = 0; ii < GetPolyShape().TotalVertices(); ++ii )
3219 {
3220 if( GetPolyShape().CVertex( ii ) != aOther.GetPolyShape().CVertex( ii ) )
3221 return false;
3222 }
3223
3224 break;
3225
3226 case SHAPE_T::BEZIER:
3227 if( m_start != aOther.m_start )
3228 return false;
3229
3230 if( m_end != aOther.m_end )
3231 return false;
3232
3233 if( m_bezierC1 != aOther.m_bezierC1 )
3234 return false;
3235
3236 if( m_bezierC2 != aOther.m_bezierC2 )
3237 return false;
3238
3239 if( m_bezierPoints != aOther.m_bezierPoints )
3240 return false;
3241
3242 break;
3243
3244 case SHAPE_T::ELLIPSE:
3246 if( m_ellipse.Center != aOther.m_ellipse.Center )
3247 return false;
3248
3249 if( m_ellipse.MajorRadius != aOther.m_ellipse.MajorRadius )
3250 return false;
3251 if( m_ellipse.MinorRadius != aOther.m_ellipse.MinorRadius )
3252 return false;
3253
3254 if( m_ellipse.Rotation != aOther.m_ellipse.Rotation )
3255 return false;
3256
3258 {
3259 if( m_ellipse.StartAngle != aOther.m_ellipse.StartAngle )
3260 return false;
3261
3262 if( m_ellipse.EndAngle != aOther.m_ellipse.EndAngle )
3263 return false;
3264 }
3265
3266 break;
3267
3268 default:
3269 return false;
3270 }
3271
3272 return true;
3273}
3274
3275
3276double EDA_SHAPE::Similarity( const EDA_SHAPE& aOther ) const
3277{
3278 if( GetShape() != aOther.GetShape() )
3279 return 0.0;
3280
3281 double similarity = 1.0;
3282
3283 if( m_fill != aOther.m_fill )
3284 similarity *= 0.9;
3285
3286 if( m_stroke.GetWidth() != aOther.m_stroke.GetWidth() )
3287 similarity *= 0.9;
3288
3289 if( m_stroke.GetLineStyle() != aOther.m_stroke.GetLineStyle() )
3290 similarity *= 0.9;
3291
3292 if( m_fillColor != aOther.m_fillColor )
3293 similarity *= 0.9;
3294
3295 if( m_start != aOther.m_start )
3296 similarity *= 0.9;
3297
3298 if( m_end != aOther.m_end )
3299 similarity *= 0.9;
3300
3301 if( m_arcCenter != aOther.m_arcCenter )
3302 similarity *= 0.9;
3303
3304 if( m_bezierC1 != aOther.m_bezierC1 )
3305 similarity *= 0.9;
3306
3307 if( m_bezierC2 != aOther.m_bezierC2 )
3308 similarity *= 0.9;
3309
3310 {
3311 int m = m_bezierPoints.size();
3312 int n = aOther.m_bezierPoints.size();
3313
3314 size_t longest = alg::longest_common_subset( m_bezierPoints, aOther.m_bezierPoints );
3315
3316 similarity *= std::pow( 0.9, m + n - 2 * longest );
3317 }
3318
3319 {
3320 int m = GetPolyShape().TotalVertices();
3321 int n = aOther.GetPolyShape().TotalVertices();
3322 std::vector<VECTOR2I> poly;
3323 std::vector<VECTOR2I> otherPoly;
3324 VECTOR2I lastPt( 0, 0 );
3325
3326 // We look for the longest common subset of the two polygons, but we need to
3327 // offset each point because we're actually looking for overall similarity, not just
3328 // exact matches. So if the zone is moved by 1IU, we only want one point to be
3329 // considered "moved" rather than the entire polygon. In this case, the first point
3330 // will not be a match but the rest of the sequence will.
3331 for( int ii = 0; ii < m; ++ii )
3332 {
3333 poly.emplace_back( lastPt - GetPolyShape().CVertex( ii ) );
3334 lastPt = GetPolyShape().CVertex( ii );
3335 }
3336
3337 lastPt = VECTOR2I( 0, 0 );
3338
3339 for( int ii = 0; ii < n; ++ii )
3340 {
3341 otherPoly.emplace_back( lastPt - aOther.GetPolyShape().CVertex( ii ) );
3342 lastPt = aOther.GetPolyShape().CVertex( ii );
3343 }
3344
3345 size_t longest = alg::longest_common_subset( poly, otherPoly );
3346
3347 similarity *= std::pow( 0.9, m + n - 2 * longest );
3348 }
3349
3350 return similarity;
3351}
3352
3353
3357
3358
3359static struct EDA_SHAPE_DESC
3360{
3362 {
3364 .Map( SHAPE_T::SEGMENT, _HKI( "Segment" ) )
3365 .Map( SHAPE_T::RECTANGLE, _HKI( "Rectangle" ) )
3366 .Map( SHAPE_T::ARC, _HKI( "Arc" ) )
3367 .Map( SHAPE_T::CIRCLE, _HKI( "Circle" ) )
3368 .Map( SHAPE_T::POLY, _HKI( "Polygon" ) )
3369 .Map( SHAPE_T::BEZIER, _HKI( "Bezier" ) )
3370 .Map( SHAPE_T::ELLIPSE, _HKI( "Ellipse" ) )
3371 .Map( SHAPE_T::ELLIPSE_ARC, _HKI( "Elliptical Arc" ) );
3372
3374
3375 if( lineStyleEnum.Choices().GetCount() == 0 )
3376 {
3377 lineStyleEnum.Map( LINE_STYLE::SOLID, _HKI( "Solid" ) )
3378 .Map( LINE_STYLE::DASH, _HKI( "Dashed" ) )
3379 .Map( LINE_STYLE::DOT, _HKI( "Dotted" ) )
3380 .Map( LINE_STYLE::DASHDOT, _HKI( "Dash-Dot" ) )
3381 .Map( LINE_STYLE::DASHDOTDOT, _HKI( "Dash-Dot-Dot" ) );
3382 }
3383
3385
3386 if( hatchModeEnum.Choices().GetCount() == 0 )
3387 {
3388 hatchModeEnum.Map( UI_FILL_MODE::NONE, _HKI( "None" ) );
3389 hatchModeEnum.Map( UI_FILL_MODE::SOLID, _HKI( "Solid" ) );
3390 hatchModeEnum.Map( UI_FILL_MODE::HATCH, _HKI( "Hatch" ) );
3391 hatchModeEnum.Map( UI_FILL_MODE::REVERSE_HATCH, _HKI( "Reverse Hatch" ) );
3392 hatchModeEnum.Map( UI_FILL_MODE::CROSS_HATCH, _HKI( "Cross-hatch" ) );
3393 }
3394
3397
3398 auto isNotPolygonOrCircle =
3399 []( INSPECTABLE* aItem ) -> bool
3400 {
3401 // Polygons, unlike other shapes, have no meaningful start or end coordinates
3402 if( EDA_SHAPE* shape = dynamic_cast<EDA_SHAPE*>( aItem ) )
3403 return shape->GetShape() != SHAPE_T::POLY && shape->GetShape() != SHAPE_T::CIRCLE;
3404
3405 return false;
3406 };
3407
3408 auto isCircle =
3409 []( INSPECTABLE* aItem ) -> bool
3410 {
3411 // Polygons, unlike other shapes, have no meaningful start or end coordinates
3412 if( EDA_SHAPE* shape = dynamic_cast<EDA_SHAPE*>( aItem ) )
3413 return shape->GetShape() == SHAPE_T::CIRCLE;
3414
3415 return false;
3416 };
3417
3418 auto isRectangle =
3419 []( INSPECTABLE* aItem ) -> bool
3420 {
3421 // Polygons, unlike other shapes, have no meaningful start or end coordinates
3422 if( EDA_SHAPE* shape = dynamic_cast<EDA_SHAPE*>( aItem ) )
3423 return shape->GetShape() == SHAPE_T::RECTANGLE;
3424
3425 return false;
3426 };
3427
3428 auto isEllipseOrEllipseArc = []( INSPECTABLE* aItem ) -> bool
3429 {
3430 if( EDA_SHAPE* shape = dynamic_cast<EDA_SHAPE*>( aItem ) )
3431 {
3432 return shape->GetShape() == SHAPE_T::ELLIPSE || shape->GetShape() == SHAPE_T::ELLIPSE_ARC;
3433 }
3434
3435 return false;
3436 };
3437
3438 auto isEllipseArc = []( INSPECTABLE* aItem ) -> bool
3439 {
3440 if( EDA_SHAPE* shape = dynamic_cast<EDA_SHAPE*>( aItem ) )
3441 return shape->GetShape() == SHAPE_T::ELLIPSE_ARC;
3442
3443 return false;
3444 };
3445
3446 const wxString shapeProps = _HKI( "Shape Properties" );
3447
3448 auto shape = new PROPERTY_ENUM<EDA_SHAPE, SHAPE_T>( _HKI( "Shape" ),
3450 propMgr.AddProperty( shape, shapeProps );
3451
3452 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "Start X" ),
3455 shapeProps )
3456 .SetAvailableFunc( isNotPolygonOrCircle );
3457 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "Start Y" ),
3460 shapeProps )
3461 .SetAvailableFunc( isNotPolygonOrCircle );
3462
3463 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "Center X" ),
3466 shapeProps )
3467 .SetAvailableFunc( isCircle );
3468
3469 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "Center Y" ),
3472 shapeProps )
3473 .SetAvailableFunc( isCircle );
3474
3475 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "Radius" ),
3478 shapeProps )
3479 .SetAvailableFunc( isCircle );
3480
3481 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "End X" ),
3484 shapeProps )
3485 .SetAvailableFunc( isNotPolygonOrCircle );
3486
3487 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "End Y" ),
3490 shapeProps )
3491 .SetAvailableFunc( isNotPolygonOrCircle );
3492
3493 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "Width" ),
3496 shapeProps )
3497 .SetAvailableFunc( isRectangle );
3498
3499 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "Height" ),
3502 shapeProps )
3503 .SetAvailableFunc( isRectangle );
3504
3505 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "Corner Radius" ),
3508 shapeProps )
3509 .SetAvailableFunc( isRectangle )
3510 .SetValidator( []( const wxAny&& aValue, EDA_ITEM* aItem ) -> VALIDATOR_RESULT
3511 {
3512 wxASSERT_MSG( aValue.CheckType<int>(),
3513 "Expecting int-containing value" );
3514
3515 int radius = aValue.As<int>();
3516
3517 EDA_SHAPE* prop_shape = dynamic_cast<EDA_SHAPE*>( aItem );
3518
3519 if( !prop_shape )
3520 return std::nullopt;
3521
3522 int maxRadius = std::min( prop_shape->GetRectangleWidth(),
3523 prop_shape->GetRectangleHeight() ) / 2;
3524
3525 if( radius > maxRadius )
3526 return std::make_unique<VALIDATION_ERROR_TOO_LARGE<int>>( radius, maxRadius );
3527 else if( radius < 0 )
3528 return std::make_unique<VALIDATION_ERROR_TOO_SMALL<int>>( radius, 0 );
3529
3530 return std::nullopt;
3531 } );
3532
3536 shapeProps )
3537 .SetAvailableFunc( isEllipseOrEllipseArc );
3538
3542 shapeProps )
3543 .SetAvailableFunc( isEllipseOrEllipseArc );
3544
3546 _HKI( "Ellipse Rotation" ), &EDA_SHAPE::SetEllipseRotation,
3548 shapeProps )
3549 .SetAvailableFunc( isEllipseOrEllipseArc );
3550
3552 _HKI( "Arc Start Angle" ), &EDA_SHAPE::SetEllipseStartAngle,
3554 shapeProps )
3555 .SetAvailableFunc( isEllipseArc );
3556
3558 _HKI( "Arc End Angle" ), &EDA_SHAPE::SetEllipseEndAngle,
3560 shapeProps )
3561 .SetAvailableFunc( isEllipseArc );
3562
3563 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "Line Width" ),
3565 shapeProps );
3566
3567 propMgr.AddProperty( new PROPERTY_ENUM<EDA_SHAPE, LINE_STYLE>( _HKI( "Line Style" ),
3569 shapeProps );
3570
3571 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, COLOR4D>( _HKI( "Line Color" ),
3573 shapeProps )
3575
3576 auto angle = new PROPERTY<EDA_SHAPE, EDA_ANGLE>( _HKI( "Angle" ),
3579 angle->SetAvailableFunc(
3580 [=]( INSPECTABLE* aItem ) -> bool
3581 {
3582 if( EDA_SHAPE* curr_shape = dynamic_cast<EDA_SHAPE*>( aItem ) )
3583 return curr_shape->GetShape() == SHAPE_T::ARC;
3584
3585 return false;
3586 } );
3587 propMgr.AddProperty( angle, shapeProps );
3588
3589 auto fillAvailable =
3590 [=]( INSPECTABLE* aItem ) -> bool
3591 {
3592 if( EDA_ITEM* edaItem = dynamic_cast<EDA_ITEM*>( aItem ) )
3593 {
3594 // For some reason masking "Filled" and "Fill Color" at the
3595 // PCB_TABLECELL level doesn't work.
3596 if( edaItem->Type() == PCB_TABLECELL_T || edaItem->Type() == PCB_TEXTBOX_T )
3597 return false;
3598 }
3599
3600 if( EDA_SHAPE* edaShape = dynamic_cast<EDA_SHAPE*>( aItem ) )
3601 {
3602 switch( edaShape->GetShape() )
3603 {
3604 case SHAPE_T::POLY:
3605 case SHAPE_T::RECTANGLE:
3606 case SHAPE_T::CIRCLE:
3607 case SHAPE_T::BEZIER:
3608 case SHAPE_T::ELLIPSE: return true;
3609
3610 default:
3611 return false;
3612 }
3613 }
3614
3615 return false;
3616 };
3617
3620 shapeProps )
3621 .SetAvailableFunc( fillAvailable );
3622
3623 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, COLOR4D>( _HKI( "Fill Color" ),
3625 shapeProps )
3626 .SetAvailableFunc( fillAvailable )
3628 }
types::KiCadObjectType ToProtoEnum(KICAD_T aValue)
KICAD_T FromProtoEnum(types::KiCadObjectType aValue)
Definition api_enums.cpp:44
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
constexpr EDA_IU_SCALE pcbIUScale
Definition base_units.h:125
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 const Vec GetCenter() const
Definition box2.h:230
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
static const COLOR4D UNSPECIFIED
For legacy support; used as a value to indicate color hasn't been set yet.
Definition color4d.h:402
EDA_ANGLE Normalize()
Definition eda_angle.h:229
double Sin() const
Definition eda_angle.h:178
int AsTenthsOfADegree() const
Definition eda_angle.h:118
bool IsCardinal() const
Definition eda_angle.cpp:40
EDA_ANGLE Normalize720()
Definition eda_angle.h:279
double AsRadians() const
Definition eda_angle.h:120
double Cos() const
Definition eda_angle.h:197
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
void SetEllipseRotation(const EDA_ANGLE &aA)
Definition eda_shape.h:308
virtual int GetHatchLineSpacing() const
Definition eda_shape.h:180
EDA_ANGLE GetArcAngle() const
SHAPE_T m_shape
Definition eda_shape.h:587
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:212
int GetEllipseMinorRadius() const
Definition eda_shape.h:306
bool m_proxyItem
Definition eda_shape.h:613
int m_cornerRadius
Definition eda_shape.h:597
bool m_hatchingDirty
Definition eda_shape.h:593
bool m_endsSwapped
Definition eda_shape.h:586
const VECTOR2I & GetBezierC2() const
Definition eda_shape.h:279
const VECTOR2I & GetEllipseCenter() const
Definition eda_shape.h:288
void SetBezierC2(const VECTOR2I &aPt)
Definition eda_shape.h:278
void move(const VECTOR2I &aMoveVector)
void SetCenter(const VECTOR2I &aCenter)
VECTOR2I getCenter() const
int GetStartY() const
Definition eda_shape.h:195
void SetFillModeProp(UI_FILL_MODE)
int m_editState
Definition eda_shape.h:612
virtual int getMaxError() const
Definition eda_shape.h:579
void rotate(const VECTOR2I &aRotCentre, const EDA_ANGLE &aAngle)
void SetEllipseCenter(const VECTOR2I &aPt)
Definition eda_shape.h:281
const std::vector< VECTOR2I > buildBezierToSegmentsPointsList(int aMaxError) const
const SHAPE_POLY_SET & GetHatching() const
EDA_ANGLE GetEllipseEndAngle() const
Definition eda_shape.h:334
FILL_T GetFillMode() const
Definition eda_shape.h:162
virtual ~EDA_SHAPE()
Definition eda_shape.cpp:73
void SetCornerRadius(int aRadius)
long long int m_rectangleHeight
Definition eda_shape.h:595
int GetEllipseMajorRadius() const
Definition eda_shape.h:297
void SetEllipseStartAngle(const EDA_ANGLE &aA)
Definition eda_shape.h:318
std::unique_ptr< EDA_SHAPE_HATCH_CACHE_DATA > m_hatchingCache
Definition eda_shape.h:592
void SetEndY(int aY)
Definition eda_shape.h:247
virtual int GetEffectiveWidth() const
Definition eda_shape.h:178
std::vector< VECTOR2I > GetPolyPoints() const
Duplicate the polygon outlines into a flat list of VECTOR2I points.
ELLIPSE< int > m_ellipse
Definition eda_shape.h:609
COLOR4D GetLineColor() const
Definition eda_shape.h:186
int GetEndX() const
Definition eda_shape.h:238
SHAPE_ELLIPSE buildShapeEllipse() const
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 recalcEllipseArcEndpoints()
When m_shape == ELLIPSE_ARC, recompute m_start/m_end from m_ellipse.
void calcEdit(const VECTOR2I &aPosition)
void SetStartY(int y)
Definition eda_shape.h:205
virtual std::vector< SHAPE * > MakeEffectiveShapes(bool aEdgeOnly=false) const
Make a set of SHAPE objects representing the EDA_SHAPE.
Definition eda_shape.h:457
SHAPE_POLY_SET & GetPolyShape()
void SetCenterY(int y)
Definition eda_shape.h:219
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
EDA_ANGLE GetEllipseRotation() const
Definition eda_shape.h:315
void ShapeGetMsgPanelInfo(EDA_DRAW_FRAME *aFrame, std::vector< MSG_PANEL_ITEM > &aList)
virtual bool isMoving() const
Definition eda_shape.h:557
bool operator==(const EDA_SHAPE &aOther) const
int GetRadius() const
SHAPE_T GetShape() const
Definition eda_shape.h:189
void SetPolyShape(const SHAPE_POLY_SET &aShape)
Definition eda_shape.h:423
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:144
virtual SHAPE_POLY_SET getHatchingKnockouts() const
Definition eda_shape.h:523
VECTOR2I m_arcCenter
Definition eda_shape.h:602
void SetCenterX(int x)
Definition eda_shape.h:226
virtual void SetFilled(bool aFlag)
Definition eda_shape.h:156
virtual bool IsFilledForHitTesting() const
Definition eda_shape.h:151
bool continueEdit(const VECTOR2I &aPosition)
wxString ShowShape() const
void SetEllipseEndAngle(const EDA_ANGLE &aA)
Definition eda_shape.h:327
ARC_MID m_arcMidData
Definition eda_shape.h:603
void SetFillColor(const COLOR4D &aColor)
Definition eda_shape.h:174
int GetEndY() const
Definition eda_shape.h:237
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:254
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:179
bool IsSolidFill() const
Definition eda_shape.h:137
void flip(const VECTOR2I &aCentre, FLIP_DIRECTION aFlipDirection)
EDA_SHAPE(SHAPE_T aType, int aLineWidth, FILL_T aFill)
Definition eda_shape.cpp:57
std::vector< SEG > & hatchLines() const
void beginEdit(const VECTOR2I &aStartPoint)
VECTOR2I m_start
Definition eda_shape.h:599
int GetPointCount() const
const VECTOR2I & GetEnd() const
Return the ending point of the graphic.
Definition eda_shape.h:236
bool IsClosed() const
void SetRadius(int aX)
Definition eda_shape.h:261
void SetStart(const VECTOR2I &aStart)
Definition eda_shape.h:198
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:194
void SetLineColor(const COLOR4D &aColor)
Definition eda_shape.h:185
COLOR4D GetFillColor() const
Definition eda_shape.h:173
void SetRectangle(const long long int &aHeight, const long long int &aWidth)
void SetShape(SHAPE_T aShape)
Definition eda_shape.h:188
void SwapShape(EDA_SHAPE *aImage)
std::vector< VECTOR2I > GetRectCorners() const
std::vector< VECTOR2I > m_bezierPoints
Definition eda_shape.h:608
bool IsAnyFill() const
Definition eda_shape.h:132
void setPosition(const VECTOR2I &aPos)
EDA_ANGLE GetEllipseStartAngle() const
Definition eda_shape.h:325
virtual bool IsProxyItem() const
Definition eda_shape.h:129
void computeArcBBox(BOX2I &aBBox) const
virtual void UpdateHatching() const
void SetEnd(const VECTOR2I &aEnd)
Definition eda_shape.h:240
void SetRectangleWidth(const int &aWidth)
void SetBezierC1(const VECTOR2I &aPt)
Definition eda_shape.h:275
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:196
double Similarity(const EDA_SHAPE &aOther) const
const VECTOR2I & GetBezierC1() const
Definition eda_shape.h:276
VECTOR2I m_end
Definition eda_shape.h:600
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:177
VECTOR2I getPosition() const
bool IsClockwiseArc() const
STROKE_PARAMS m_stroke
Definition eda_shape.h:588
void SetEllipseMajorRadius(int aR)
Definition eda_shape.h:290
void SetPolyPoints(const std::vector< VECTOR2I > &aPoints)
wxString getFriendlyName() const
EDA_SHAPE & operator=(const EDA_SHAPE &aOther)
VECTOR2I m_bezierC1
Definition eda_shape.h:605
FILL_T m_fill
Definition eda_shape.h:589
COLOR4D m_fillColor
Definition eda_shape.h:590
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:610
long long int m_rectangleWidth
Definition eda_shape.h:596
VECTOR2I m_bezierC2
Definition eda_shape.h:606
void SetEllipseMinorRadius(int aR)
Definition eda_shape.h:299
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
NumericType MinorRadius
Definition ellipse.h:76
EDA_ANGLE Rotation
Definition ellipse.h:77
EDA_ANGLE StartAngle
Definition ellipse.h:78
NumericType MajorRadius
Definition ellipse.h:75
EDA_ANGLE EndAngle
Definition ellipse.h:79
VECTOR2< NumericType > Center
Definition ellipse.h:74
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:100
SHAPE_LINE_CHAIN ConvertToPolyline(int aMaxError) const
Build a polyline approximation of the ellipse or arc.
SEG::ecoord SquaredDistance(const VECTOR2I &aP, bool aOutlineOnly=false) const override
double GetLength() 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 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.
void ClearArcs()
Removes all arc references from all the outlines and holes in the polyset.
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:128
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
@ DEGREES_T
Definition eda_angle.h:31
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:48
@ UNDEFINED
Definition eda_shape.h:49
@ ELLIPSE
Definition eda_shape.h:56
@ SEGMENT
Definition eda_shape.h:50
@ RECTANGLE
Use RECTANGLE instead of RECT to avoid collision in a Windows header.
Definition eda_shape.h:51
@ ELLIPSE_ARC
Definition eda_shape.h:57
UI_FILL_MODE
Definition eda_shape.h:75
@ REVERSE_HATCH
Definition eda_shape.h:79
@ SOLID
Definition eda_shape.h:77
@ HATCH
Definition eda_shape.h:78
@ NONE
Definition eda_shape.h:76
@ CROSS_HATCH
Definition eda_shape.h:80
FILL_T
Definition eda_shape.h:63
@ NO_FILL
Definition eda_shape.h:64
@ REVERSE_HATCH
Definition eda_shape.h:69
@ HATCH
Definition eda_shape.h:68
@ CROSS_HATCH
Definition eda_shape.h:70
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 void PackColor(types::Color &aOutput, const KIGFX::COLOR4D &aInput)
KICOMMON_API int UnpackDistance(const types::Distance &aInput, const EDA_IU_SCALE &aScale)
KICOMMON_API void PackPolySet(types::PolySet &aOutput, const SHAPE_POLY_SET &aInput, const EDA_IU_SCALE &aScale)
KICOMMON_API KIGFX::COLOR4D UnpackColor(const types::Color &aInput)
KICOMMON_API VECTOR2I UnpackVector2(const types::Vector2 &aInput, const EDA_IU_SCALE &aScale)
KICOMMON_API void PackDistance(types::Distance &aOutput, int aInput, const EDA_IU_SCALE &aScale)
KICOMMON_API void PackVector2(types::Vector2 &aOutput, const VECTOR2I &aInput, const EDA_IU_SCALE &aScale)
KICOMMON_API SHAPE_POLY_SET UnpackPolySet(const types::PolySet &aInput, const EDA_IU_SCALE &aScale)
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_ELLIPSE
ellipse or elliptical arc
Definition shape.h:57
@ 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.
VECTOR2I center
const SHAPE_LINE_CHAIN chain
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:534
@ 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