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