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 {
1515 int pointCount = 0;
1516 if( GetPolyShape().OutlineCount() > 0 )
1517 pointCount = GetPolyShape().Outline( 0 ).PointCount();
1518
1519 msg.Printf( wxS( "%d" ), pointCount );
1520 aList.emplace_back( _( "Points" ), msg );
1521 break;
1522 }
1523 case SHAPE_T::RECTANGLE:
1524 aList.emplace_back( _( "Width" ), aFrame->MessageTextFromValue( std::abs( GetEnd().x - GetStart().x ) ) );
1525 aList.emplace_back( _( "Height" ), aFrame->MessageTextFromValue( std::abs( GetEnd().y - GetStart().y ) ) );
1526 break;
1527
1528 case SHAPE_T::SEGMENT:
1529 {
1530 aList.emplace_back( _( "Length" ), aFrame->MessageTextFromValue( GetStart().Distance( GetEnd() ) ));
1531
1532 // angle counter-clockwise from 3'o-clock
1533 EDA_ANGLE angle( atan2( (double)( GetStart().y - GetEnd().y ), (double)( GetEnd().x - GetStart().x ) ),
1534 RADIANS_T );
1535 aList.emplace_back( _( "Angle" ), EDA_UNIT_UTILS::UI::MessageTextFromValue( angle ) );
1536 break;
1537 }
1538
1539 default:
1540 break;
1541 }
1542
1543 m_stroke.GetMsgPanelInfo( aFrame, aList );
1544}
1545
1546
1548{
1549 BOX2I bbox;
1550
1551 switch( m_shape )
1552 {
1553 case SHAPE_T::RECTANGLE:
1554 for( VECTOR2I& pt : GetRectCorners() )
1555 bbox.Merge( pt );
1556
1557 break;
1558
1559 case SHAPE_T::SEGMENT:
1560 bbox.SetOrigin( GetStart() );
1561 bbox.SetEnd( GetEnd() );
1562 break;
1563
1564 case SHAPE_T::CIRCLE:
1565 bbox.SetOrigin( GetStart() );
1566 bbox.Inflate( GetRadius() );
1567 break;
1568
1569 case SHAPE_T::ARC:
1570 computeArcBBox( bbox );
1571 break;
1572
1573 case SHAPE_T::ELLIPSE:
1574 case SHAPE_T::ELLIPSE_ARC: bbox = buildShapeEllipse().BBox( 0 ); break;
1575
1576 case SHAPE_T::POLY:
1577 if( GetPolyShape().IsEmpty() )
1578 break;
1579
1580 for( auto iter = GetPolyShape().CIterate(); iter; iter++ )
1581 bbox.Merge( *iter );
1582
1583 break;
1584
1585 case SHAPE_T::BEZIER:
1586 // Bezier BBoxes are not trivial to compute, so we approximate it by
1587 // using the bounding box of the curve (not control!) points.
1588 for( const VECTOR2I& pt : m_bezierPoints )
1589 bbox.Merge( pt );
1590
1591 break;
1592
1593 default:
1595 break;
1596 }
1597
1598 bbox.Inflate( std::max( 0, GetWidth() ) / 2 );
1599 bbox.Normalize();
1600
1601 return bbox;
1602}
1603
1604
1605bool EDA_SHAPE::hitTest( const VECTOR2I& aPosition, int aAccuracy ) const
1606{
1607 double maxdist = aAccuracy;
1608
1609 if( GetWidth() > 0 )
1610 maxdist += GetWidth() / 2.0;
1611
1612 switch( m_shape )
1613 {
1614 case SHAPE_T::CIRCLE:
1615 {
1616 double radius = GetRadius();
1617 double dist = aPosition.Distance( getCenter() );
1618
1619 if( IsFilledForHitTesting() )
1620 return dist <= radius + maxdist; // Filled circle hit-test
1621 else if( abs( radius - dist ) <= maxdist ) // Ring hit-test
1622 return true;
1623
1624 if( IsHatchedFill() && GetHatching().Collide( aPosition, maxdist ) )
1625 return true;
1626
1627 return false;
1628 }
1629
1630 case SHAPE_T::ARC:
1631 {
1632 if( aPosition.Distance( m_start ) <= maxdist )
1633 return true;
1634
1635 if( aPosition.Distance( m_end ) <= maxdist )
1636 return true;
1637
1638 double radius = GetRadius();
1639 VECTOR2D relPos( VECTOR2D( aPosition ) - getCenter() );
1640 double dist = relPos.EuclideanNorm();
1641
1642 if( IsFilledForHitTesting() )
1643 {
1644 // Check distance from arc center
1645 if( dist > radius + maxdist )
1646 return false;
1647 }
1648 else
1649 {
1650 // Check distance from arc circumference
1651 if( abs( radius - dist ) > maxdist )
1652 return false;
1653 }
1654
1655 // Finally, check to see if it's within arc's swept angle.
1656 EDA_ANGLE startAngle;
1657 EDA_ANGLE endAngle;
1658 CalcArcAngles( startAngle, endAngle );
1659
1660 EDA_ANGLE relPosAngle( relPos );
1661
1662 startAngle.Normalize();
1663 endAngle.Normalize();
1664 relPosAngle.Normalize();
1665
1666 if( endAngle > startAngle )
1667 return relPosAngle >= startAngle && relPosAngle <= endAngle;
1668 else
1669 return relPosAngle >= startAngle || relPosAngle <= endAngle;
1670 }
1671
1672 case SHAPE_T::BEZIER:
1673 {
1674 const std::vector<VECTOR2I>* pts = &m_bezierPoints;
1675 std::vector<VECTOR2I> updatedBezierPoints;
1676
1677 if( m_bezierPoints.empty() )
1678 {
1680 converter.GetPoly( updatedBezierPoints, aAccuracy / 2 );
1681 pts = &updatedBezierPoints;
1682 }
1683
1684 for( unsigned int i = 1; i < pts->size(); i++ )
1685 {
1686 if( TestSegmentHit( aPosition, ( *pts )[i - 1], ( *pts )[i], maxdist ) )
1687 return true;
1688 }
1689
1690 return false;
1691 }
1692 case SHAPE_T::SEGMENT:
1693 return TestSegmentHit( aPosition, GetStart(), GetEnd(), maxdist );
1694
1695 case SHAPE_T::RECTANGLE:
1696 if( IsProxyItem() || IsFilledForHitTesting() ) // Filled rect hit-test
1697 {
1698 SHAPE_POLY_SET poly;
1699 poly.NewOutline();
1700
1701 for( const VECTOR2I& pt : GetRectCorners() )
1702 poly.Append( pt );
1703
1704 return poly.Collide( aPosition, maxdist );
1705 }
1706 else if( m_cornerRadius > 0 )
1707 {
1709 SHAPE_POLY_SET poly;
1710 rr.TransformToPolygon( poly, getMaxError() );
1711
1712 if( poly.CollideEdge( aPosition, nullptr, maxdist ) )
1713 return true;
1714 }
1715 else
1716 {
1717 std::vector<VECTOR2I> pts = GetRectCorners();
1718
1719 if( TestSegmentHit( aPosition, pts[0], pts[1], maxdist )
1720 || TestSegmentHit( aPosition, pts[1], pts[2], maxdist )
1721 || TestSegmentHit( aPosition, pts[2], pts[3], maxdist )
1722 || TestSegmentHit( aPosition, pts[3], pts[0], maxdist ) )
1723 {
1724 return true;
1725 }
1726 }
1727
1728 if( IsHatchedFill() && GetHatching().Collide( aPosition, maxdist ) )
1729 return true;
1730
1731 return false;
1732
1733 case SHAPE_T::POLY:
1734 if( GetPolyShape().OutlineCount() < 1 ) // empty poly
1735 return false;
1736
1737 if( IsFilledForHitTesting() )
1738 {
1739 if( !GetPolyShape().COutline( 0 ).IsClosed() )
1740 {
1741 // Only one outline is expected
1742 SHAPE_LINE_CHAIN copy( GetPolyShape().COutline( 0 ) );
1743 copy.SetClosed( true );
1744 return copy.Collide( aPosition, maxdist );
1745 }
1746 else
1747 {
1748 return GetPolyShape().Collide( aPosition, maxdist );
1749 }
1750 }
1751 else
1752 {
1753 if( GetPolyShape().CollideEdge( aPosition, nullptr, maxdist ) )
1754 return true;
1755
1756 if( IsHatchedFill() && GetHatching().Collide( aPosition, maxdist ) )
1757 return true;
1758
1759 return false;
1760 }
1761
1762 case SHAPE_T::ELLIPSE:
1764 {
1766
1767 const double maxdistSq = maxdist * maxdist;
1768
1770 {
1771 // Filled closed ellipse
1772 if( static_cast<double>( e.SquaredDistance( aPosition, false ) ) <= maxdistSq )
1773 return true;
1774 }
1775 else
1776 {
1777 // Unfilled ring or arc
1778 if( static_cast<double>( e.SquaredDistance( aPosition, true ) ) <= maxdistSq )
1779 return true;
1780 }
1781
1782 if( IsHatchedFill() && GetHatching().Collide( aPosition, maxdist ) )
1783 return true;
1784
1785 return false;
1786 }
1787
1788 default:
1790 return false;
1791 }
1792}
1793
1794
1795bool EDA_SHAPE::hitTest( const BOX2I& aRect, bool aContained, int aAccuracy ) const
1796{
1797 BOX2I arect = aRect;
1798 arect.Normalize();
1799 arect.Inflate( aAccuracy );
1800
1801 BOX2I bbox = getBoundingBox();
1802
1803 auto checkOutline =
1804 [&]( const SHAPE_LINE_CHAIN& outline )
1805 {
1806 int count = (int) outline.GetPointCount();
1807
1808 for( int ii = 0; ii < count; ii++ )
1809 {
1810 VECTOR2I vertex = outline.GetPoint( ii );
1811
1812 // Test if the point is within aRect
1813 if( arect.Contains( vertex ) )
1814 return true;
1815
1816 if( ii + 1 < count )
1817 {
1818 VECTOR2I vertexNext = outline.GetPoint( ii + 1 );
1819
1820 // Test if this edge intersects aRect
1821 if( arect.Intersects( vertex, vertexNext ) )
1822 return true;
1823 }
1824 else if( outline.IsClosed() )
1825 {
1826 VECTOR2I vertexNext = outline.GetPoint( 0 );
1827
1828 // Test if this edge intersects aRect
1829 if( arect.Intersects( vertex, vertexNext ) )
1830 return true;
1831 }
1832 }
1833
1834 return false;
1835 };
1836
1837 switch( m_shape )
1838 {
1839 case SHAPE_T::CIRCLE:
1840 // Test if area intersects or contains the circle:
1841 if( aContained )
1842 {
1843 return arect.Contains( bbox );
1844 }
1845 else
1846 {
1847 // If the rectangle does not intersect the bounding box, this is a much quicker test
1848 if( !arect.Intersects( bbox ) )
1849 return false;
1850 else
1851 return arect.IntersectsCircleEdge( getCenter(), GetRadius(), GetWidth() );
1852 }
1853
1854 case SHAPE_T::ARC:
1855 // Test for full containment of this arc in the rect
1856 if( aContained )
1857 {
1858 return arect.Contains( bbox );
1859 }
1860 // Test if the rect crosses the arc
1861 else
1862 {
1863 if( !arect.Intersects( bbox ) )
1864 return false;
1865
1866 if( IsAnyFill() )
1867 {
1868 return ( arect.Intersects( getCenter(), GetStart() )
1869 || arect.Intersects( getCenter(), GetEnd() )
1870 || arect.IntersectsCircleEdge( getCenter(), GetRadius(), GetWidth() ) );
1871 }
1872 else
1873 {
1874 return arect.IntersectsCircleEdge( getCenter(), GetRadius(), GetWidth() );
1875 }
1876 }
1877
1878 case SHAPE_T::RECTANGLE:
1879 if( aContained )
1880 {
1881 return arect.Contains( bbox );
1882 }
1883 else if( m_cornerRadius > 0 )
1884 {
1886 SHAPE_POLY_SET poly;
1887 rr.TransformToPolygon( poly, getMaxError() );
1888
1889 // Account for the width of the line
1890 arect.Inflate( GetWidth() / 2 );
1891
1892 return checkOutline( poly.Outline( 0 ) );
1893 }
1894 else
1895 {
1896 std::vector<VECTOR2I> pts = GetRectCorners();
1897
1898 // Account for the width of the lines
1899 arect.Inflate( GetWidth() / 2 );
1900 return ( arect.Intersects( pts[0], pts[1] )
1901 || arect.Intersects( pts[1], pts[2] )
1902 || arect.Intersects( pts[2], pts[3] )
1903 || arect.Intersects( pts[3], pts[0] ) );
1904 }
1905
1906 case SHAPE_T::SEGMENT:
1907 if( aContained )
1908 {
1909 return arect.Contains( GetStart() ) && aRect.Contains( GetEnd() );
1910 }
1911 else
1912 {
1913 // Account for the width of the line
1914 arect.Inflate( GetWidth() / 2 );
1915 return arect.Intersects( GetStart(), GetEnd() );
1916 }
1917
1918 case SHAPE_T::POLY:
1919 if( aContained )
1920 {
1921 return arect.Contains( bbox );
1922 }
1923 else
1924 {
1925 // Fast test: if aRect is outside the polygon bounding box,
1926 // rectangles cannot intersect
1927 if( !arect.Intersects( bbox ) )
1928 return false;
1929
1930 // Account for the width of the line
1931 arect.Inflate( GetWidth() / 2 );
1932
1933 for( int ii = 0; ii < GetPolyShape().OutlineCount(); ++ii )
1934 {
1935 if( checkOutline( GetPolyShape().Outline( ii ) ) )
1936 return true;
1937 }
1938
1939 return false;
1940 }
1941
1942 case SHAPE_T::BEZIER:
1943 if( aContained )
1944 {
1945 return arect.Contains( bbox );
1946 }
1947 else
1948 {
1949 // Fast test: if aRect is outside the polygon bounding box,
1950 // rectangles cannot intersect
1951 if( !arect.Intersects( bbox ) )
1952 return false;
1953
1954 // Account for the width of the line
1955 arect.Inflate( GetWidth() / 2 );
1956 const std::vector<VECTOR2I>* pts = &m_bezierPoints;
1957 std::vector<VECTOR2I> updatedBezierPoints;
1958
1959 if( m_bezierPoints.empty() )
1960 {
1962 converter.GetPoly( updatedBezierPoints, aAccuracy / 2 );
1963 pts = &updatedBezierPoints;
1964 }
1965
1966 for( unsigned ii = 1; ii < pts->size(); ii++ )
1967 {
1968 VECTOR2I vertex = ( *pts )[ii - 1];
1969 VECTOR2I vertexNext = ( *pts )[ii];
1970
1971 // Test if the point is within aRect
1972 if( arect.Contains( vertex ) )
1973 return true;
1974
1975 // Test if this edge intersects aRect
1976 if( arect.Intersects( vertex, vertexNext ) )
1977 return true;
1978 }
1979
1980 return false;
1981 }
1982
1983 case SHAPE_T::ELLIPSE:
1985 {
1986 if( aContained )
1987 return arect.Contains( bbox );
1988
1989 if( !arect.Intersects( bbox ) )
1990 return false;
1991
1993
1994 const int tessError = std::max( 1, aAccuracy / 2 );
1995 const SHAPE_LINE_CHAIN chain = e.ConvertToPolyline( tessError );
1996
1997 // Account for the width of the line
1998 arect.Inflate( GetWidth() / 2 );
1999 return checkOutline( chain );
2000 }
2001
2002 default:
2004 return false;
2005 }
2006}
2007
2008
2009bool EDA_SHAPE::hitTest( const SHAPE_LINE_CHAIN& aPoly, bool aContained ) const
2010{
2012
2013 return KIGEOM::ShapeHitTest( aPoly, shape, aContained );
2014}
2015
2016
2017std::vector<VECTOR2I> EDA_SHAPE::GetRectCorners() const
2018{
2019 std::vector<VECTOR2I> pts;
2020 VECTOR2I topLeft = GetStart();
2021 VECTOR2I botRight = GetEnd();
2022
2023 pts.emplace_back( topLeft );
2024 pts.emplace_back( botRight.x, topLeft.y );
2025 pts.emplace_back( botRight );
2026 pts.emplace_back( topLeft.x, botRight.y );
2027
2028 return pts;
2029}
2030
2031
2032std::vector<VECTOR2I> EDA_SHAPE::GetCornersInSequence( EDA_ANGLE angle ) const
2033{
2034 std::vector<VECTOR2I> pts;
2035
2036 angle.Normalize();
2037
2038 BOX2I bbox = getBoundingBox();
2039 bbox.Normalize();
2040
2041 if( angle.IsCardinal() )
2042 {
2043 if( angle == ANGLE_0 )
2044 {
2045 pts.emplace_back( VECTOR2I( bbox.GetLeft(), bbox.GetTop() ) );
2046 pts.emplace_back( VECTOR2I( bbox.GetRight(), bbox.GetTop() ) );
2047 pts.emplace_back( VECTOR2I( bbox.GetRight(), bbox.GetBottom() ) );
2048 pts.emplace_back( VECTOR2I( bbox.GetLeft(), bbox.GetBottom() ) );
2049 }
2050 else if( angle == ANGLE_90 )
2051 {
2052 pts.emplace_back( VECTOR2I( bbox.GetLeft(), bbox.GetBottom() ) );
2053 pts.emplace_back( VECTOR2I( bbox.GetLeft(), bbox.GetTop() ) );
2054 pts.emplace_back( VECTOR2I( bbox.GetRight(), bbox.GetTop() ) );
2055 pts.emplace_back( VECTOR2I( bbox.GetRight(), bbox.GetBottom() ) );
2056 }
2057 else if( angle == ANGLE_180 )
2058 {
2059 pts.emplace_back( VECTOR2I( bbox.GetRight(), bbox.GetBottom() ) );
2060 pts.emplace_back( VECTOR2I( bbox.GetLeft(), bbox.GetBottom() ) );
2061 pts.emplace_back( VECTOR2I( bbox.GetLeft(), bbox.GetTop() ) );
2062 pts.emplace_back( VECTOR2I( bbox.GetRight(), bbox.GetTop() ) );
2063 }
2064 else if( angle == ANGLE_270 )
2065 {
2066 pts.emplace_back( VECTOR2I( bbox.GetRight(), bbox.GetTop() ) );
2067 pts.emplace_back( VECTOR2I( bbox.GetRight(), bbox.GetBottom() ) );
2068 pts.emplace_back( VECTOR2I( bbox.GetLeft(), bbox.GetBottom() ) );
2069 pts.emplace_back( VECTOR2I( bbox.GetLeft(), bbox.GetTop() ) );
2070 }
2071 }
2072 else if( m_shape == SHAPE_T::RECTANGLE )
2073 {
2074 // Axis-aligned rectangle with non-cardinal rotation (used by textboxes).
2075 VECTOR2I center = bbox.GetCenter();
2076
2077 VECTOR2I tl( bbox.GetLeft(), bbox.GetTop() );
2078 VECTOR2I tr( bbox.GetRight(), bbox.GetTop() );
2079 VECTOR2I br( bbox.GetRight(), bbox.GetBottom() );
2080 VECTOR2I bl( bbox.GetLeft(), bbox.GetBottom() );
2081
2082 RotatePoint( tl, center, angle );
2083 RotatePoint( tr, center, angle );
2084 RotatePoint( br, center, angle );
2085 RotatePoint( bl, center, angle );
2086
2087 pts.emplace_back( tl );
2088 pts.emplace_back( tr );
2089 pts.emplace_back( br );
2090 pts.emplace_back( bl );
2091 }
2092 else
2093 {
2094 // This function was originally located in pcb_textbox.cpp and was later moved to eda_shape.cpp.
2095 // As a result of this move, access to getCorners was lost, since it is defined in the PCB_SHAPE
2096 // class within pcb_shape.cpp and is not available in the current context.
2097 //
2098 // Additionally, GetRectCorners() cannot be used here, as it assumes the rectangle is rotated by
2099 // a cardinal angle. In non-cardinal cases, it returns incorrect values (e.g., (0, 0)).
2100 //
2101 // To address this, a portion of the getCorners implementation for SHAPE_T::POLY elements
2102 // has been replicated here to restore the correct behavior.
2103 std::vector<VECTOR2I> corners;
2104
2105 for( int ii = 0; ii < GetPolyShape().OutlineCount(); ++ii )
2106 {
2107 for( const VECTOR2I& pt : GetPolyShape().Outline( ii ).CPoints() )
2108 corners.emplace_back( pt );
2109 }
2110
2111 if( corners.empty() )
2112 return pts;
2113
2114 while( corners.size() < 4 )
2115 corners.emplace_back( corners.back() + VECTOR2I( 10, 10 ) );
2116
2117 VECTOR2I minX = corners[0];
2118 VECTOR2I maxX = corners[0];
2119 VECTOR2I minY = corners[0];
2120 VECTOR2I maxY = corners[0];
2121
2122 for( const VECTOR2I& corner : corners )
2123 {
2124 if( corner.x < minX.x )
2125 minX = corner;
2126
2127 if( corner.x > maxX.x )
2128 maxX = corner;
2129
2130 if( corner.y < minY.y )
2131 minY = corner;
2132
2133 if( corner.y > maxY.y )
2134 maxY = corner;
2135 }
2136
2137 if( angle < ANGLE_90 )
2138 {
2139 pts.emplace_back( minX );
2140 pts.emplace_back( minY );
2141 pts.emplace_back( maxX );
2142 pts.emplace_back( maxY );
2143 }
2144 else if( angle < ANGLE_180 )
2145 {
2146 pts.emplace_back( maxY );
2147 pts.emplace_back( minX );
2148 pts.emplace_back( minY );
2149 pts.emplace_back( maxX );
2150 }
2151 else if( angle < ANGLE_270 )
2152 {
2153 pts.emplace_back( maxX );
2154 pts.emplace_back( maxY );
2155 pts.emplace_back( minX );
2156 pts.emplace_back( minY );
2157 }
2158 else
2159 {
2160 pts.emplace_back( minY );
2161 pts.emplace_back( maxX );
2162 pts.emplace_back( maxY );
2163 pts.emplace_back( minX );
2164 }
2165 }
2166
2167 return pts;
2168}
2169
2170
2172{
2173 // Start, end, and each inflection point the arc crosses will enclose the entire arc.
2174 // Only include the center when filled; it's not necessarily inside the BB of an unfilled
2175 // arc with a small included angle.
2176 aBBox.SetOrigin( m_start );
2177 aBBox.Merge( m_end );
2178
2179 if( IsAnyFill() )
2180 aBBox.Merge( m_arcCenter );
2181
2182 int radius = GetRadius();
2183 EDA_ANGLE t1, t2;
2184
2185 CalcArcAngles( t1, t2 );
2186
2187 t1.Normalize();
2188 t2.Normalize();
2189
2190 if( t2 > t1 )
2191 {
2192 if( t1 < ANGLE_0 && t2 > ANGLE_0 )
2193 aBBox.Merge( VECTOR2I( m_arcCenter.x + radius, m_arcCenter.y ) ); // right
2194
2195 if( t1 < ANGLE_90 && t2 > ANGLE_90 )
2196 aBBox.Merge( VECTOR2I( m_arcCenter.x, m_arcCenter.y + radius ) ); // down
2197
2198 if( t1 < ANGLE_180 && t2 > ANGLE_180 )
2199 aBBox.Merge( VECTOR2I( m_arcCenter.x - radius, m_arcCenter.y ) ); // left
2200
2201 if( t1 < ANGLE_270 && t2 > ANGLE_270 )
2202 aBBox.Merge( VECTOR2I( m_arcCenter.x, m_arcCenter.y - radius ) ); // up
2203 }
2204 else
2205 {
2206 if( t1 < ANGLE_0 || t2 > ANGLE_0 )
2207 aBBox.Merge( VECTOR2I( m_arcCenter.x + radius, m_arcCenter.y ) ); // right
2208
2209 if( t1 < ANGLE_90 || t2 > ANGLE_90 )
2210 aBBox.Merge( VECTOR2I( m_arcCenter.x, m_arcCenter.y + radius ) ); // down
2211
2212 if( t1 < ANGLE_180 || t2 > ANGLE_180 )
2213 aBBox.Merge( VECTOR2I( m_arcCenter.x - radius, m_arcCenter.y ) ); // left
2214
2215 if( t1 < ANGLE_270 || t2 > ANGLE_270 )
2216 aBBox.Merge( VECTOR2I( m_arcCenter.x, m_arcCenter.y - radius ) ); // up
2217 }
2218}
2219
2220
2221void EDA_SHAPE::SetPolyPoints( const std::vector<VECTOR2I>& aPoints )
2222{
2225
2226 for( const VECTOR2I& p : aPoints )
2227 GetPolyShape().Append( p.x, p.y );
2228}
2229
2230
2231std::vector<SHAPE*> EDA_SHAPE::makeEffectiveShapes( bool aEdgeOnly, bool aLineChainOnly, bool aHittesting ) const
2232{
2233 std::vector<SHAPE*> effectiveShapes;
2234 int width = GetEffectiveWidth();
2235 bool solidFill = IsSolidFill()
2236 || IsHatchedFill()
2237 || IsProxyItem()
2238 || ( aHittesting && IsFilledForHitTesting() );
2239
2240 if( aEdgeOnly )
2241 solidFill = false;
2242
2243 switch( m_shape )
2244 {
2245 case SHAPE_T::ARC:
2246 effectiveShapes.emplace_back( new SHAPE_ARC( m_arcCenter, m_start, GetArcAngle(), width ) );
2247 break;
2248
2249 case SHAPE_T::SEGMENT:
2250 effectiveShapes.emplace_back( new SHAPE_SEGMENT( m_start, m_end, width ) );
2251 break;
2252
2253 case SHAPE_T::RECTANGLE:
2254 {
2255 if( m_cornerRadius > 0 )
2256 {
2258 SHAPE_POLY_SET poly;
2259 rr.TransformToPolygon( poly, getMaxError() );
2260 SHAPE_LINE_CHAIN outline = poly.Outline( 0 );
2261
2262 if( solidFill )
2263 effectiveShapes.emplace_back( new SHAPE_SIMPLE( outline ) );
2264
2265 if( width > 0 || !solidFill )
2266 {
2267 std::set<size_t> arcsHandled;
2268
2269 for( int ii = 0; ii < outline.SegmentCount(); ++ii )
2270 {
2271 if( outline.IsArcSegment( ii ) )
2272 {
2273 size_t arcIndex = outline.ArcIndex( ii );
2274
2275 if( !arcsHandled.contains( arcIndex ) )
2276 {
2277 arcsHandled.insert( arcIndex );
2278 effectiveShapes.emplace_back( new SHAPE_ARC( outline.Arc( arcIndex ), width ) );
2279 }
2280 }
2281 else
2282 {
2283 effectiveShapes.emplace_back( new SHAPE_SEGMENT( outline.Segment( ii ), width ) );
2284 }
2285 }
2286 }
2287 }
2288 else
2289 {
2290 std::vector<VECTOR2I> pts = GetRectCorners();
2291
2292 if( solidFill )
2293 effectiveShapes.emplace_back( new SHAPE_SIMPLE( pts ) );
2294
2295 if( width > 0 || !solidFill )
2296 {
2297 effectiveShapes.emplace_back( new SHAPE_SEGMENT( pts[0], pts[1], width ) );
2298 effectiveShapes.emplace_back( new SHAPE_SEGMENT( pts[1], pts[2], width ) );
2299 effectiveShapes.emplace_back( new SHAPE_SEGMENT( pts[2], pts[3], width ) );
2300 effectiveShapes.emplace_back( new SHAPE_SEGMENT( pts[3], pts[0], width ) );
2301 }
2302 }
2303 break;
2304 }
2305
2306 case SHAPE_T::CIRCLE:
2307 {
2308 if( solidFill )
2309 effectiveShapes.emplace_back( new SHAPE_CIRCLE( getCenter(), GetRadius() ) );
2310
2311 if( width > 0 || !solidFill )
2312 effectiveShapes.emplace_back( new SHAPE_ARC( getCenter(), GetEnd(), ANGLE_360, width ) );
2313
2314 break;
2315 }
2316
2317 case SHAPE_T::BEZIER:
2318 {
2319 std::vector<VECTOR2I> bezierPoints = buildBezierToSegmentsPointsList( getMaxError() );
2320 VECTOR2I start_pt = bezierPoints[0];
2321
2322 for( unsigned int jj = 1; jj < bezierPoints.size(); jj++ )
2323 {
2324 VECTOR2I end_pt = bezierPoints[jj];
2325 effectiveShapes.emplace_back( new SHAPE_SEGMENT( start_pt, end_pt, width ) );
2326 start_pt = end_pt;
2327 }
2328
2329 break;
2330 }
2331
2332 case SHAPE_T::POLY:
2333 {
2334 if( GetPolyShape().OutlineCount() == 0 ) // malformed/empty polygon
2335 break;
2336
2337 for( int ii = 0; ii < GetPolyShape().OutlineCount(); ++ii )
2338 {
2339 const SHAPE_LINE_CHAIN& l = GetPolyShape().COutline( ii );
2340
2341 if( solidFill )
2342 effectiveShapes.emplace_back( new SHAPE_SIMPLE( l ) );
2343
2344 if( width > 0 || !IsSolidFill() || aEdgeOnly )
2345 {
2346 int segCount = l.SegmentCount();
2347
2348 if( aLineChainOnly && l.IsClosed() )
2349 segCount--; // Treat closed chain as open
2350
2351 for( int jj = 0; jj < segCount; jj++ )
2352 effectiveShapes.emplace_back( new SHAPE_SEGMENT( l.CSegment( jj ), width ) );
2353 }
2354 }
2355 }
2356 break;
2357
2358 case SHAPE_T::ELLIPSE:
2360 {
2361 if( solidFill && m_shape == SHAPE_T::ELLIPSE )
2362 {
2363 // Filled closed ellipse: emit a SHAPE_SIMPLE for the filled interior.
2366 std::vector<VECTOR2I> pts;
2367
2368 for( int ii = 0; ii < chain.PointCount(); ++ii )
2369 pts.emplace_back( chain.CPoint( ii ) );
2370
2371 effectiveShapes.emplace_back( new SHAPE_SIMPLE( pts ) );
2372 }
2373
2374 if( width > 0 || !solidFill )
2375 {
2378
2379 for( int ii = 0; ii < chain.SegmentCount(); ++ii )
2380 effectiveShapes.emplace_back( new SHAPE_SEGMENT( chain.CSegment( ii ), width ) );
2381 }
2382
2383 break;
2384 }
2385
2386 default:
2388 break;
2389 }
2390
2391 return effectiveShapes;
2392}
2393
2394
2395std::vector<VECTOR2I> EDA_SHAPE::GetPolyPoints() const
2396{
2397 const SHAPE_POLY_SET& poly = GetPolyShape();
2398 std::vector<VECTOR2I> points;
2399 int totalCount = 0;
2400
2401 for( int ii = 0; ii < poly.OutlineCount(); ++ii )
2402 totalCount += poly.COutline( ii ).PointCount();
2403
2404 points.reserve( totalCount );
2405
2406 for( int ii = 0; ii < poly.OutlineCount(); ++ii )
2407 {
2408 for( const VECTOR2I& pt : poly.COutline( ii ).CPoints() )
2409 points.emplace_back( pt );
2410 }
2411
2412 return points;
2413}
2414
2415
2417{
2418 if( !m_poly )
2419 m_poly = std::make_unique<SHAPE_POLY_SET>();
2420
2421 return *m_poly;
2422}
2423
2425{
2426 if( !m_poly )
2427 m_poly = std::make_unique<SHAPE_POLY_SET>();
2428
2429 return *m_poly;
2430}
2431
2432
2434{
2435 // return true if the polygonal shape is valid (has more than 2 points)
2436 return GetPolyShape().OutlineCount() > 0 && GetPolyShape().Outline( 0 ).PointCount() > 2;
2437}
2438
2439
2441{
2442 // return the number of corners of the polygonal shape
2443 // this shape is expected to be only one polygon without hole
2444 return GetPolyShape().OutlineCount() ? GetPolyShape().VertexCount( 0 ) : 0;
2445}
2446
2447
2448void EDA_SHAPE::beginEdit( const VECTOR2I& aPosition )
2449{
2450 switch( GetShape() )
2451 {
2452 case SHAPE_T::SEGMENT:
2453 case SHAPE_T::CIRCLE:
2454 case SHAPE_T::RECTANGLE:
2455 SetStart( aPosition );
2456 SetEnd( aPosition );
2457 break;
2458
2459 case SHAPE_T::ARC:
2460 SetArcGeometry( aPosition, aPosition, aPosition );
2461 m_editState = 1;
2462 break;
2463
2464 case SHAPE_T::BEZIER:
2465 SetStart( aPosition );
2466 SetEnd( aPosition );
2467 SetBezierC1( aPosition );
2468 SetBezierC2( aPosition );
2469 m_editState = 1;
2470
2472 break;
2473
2474 case SHAPE_T::POLY:
2476 GetPolyShape().Outline( 0 ).SetClosed( false );
2477
2478 // Start and end of the first segment (co-located for now)
2479 GetPolyShape().Outline( 0 ).Append( aPosition );
2480 GetPolyShape().Outline( 0 ).Append( aPosition, true );
2481 break;
2482
2483 case SHAPE_T::ELLIPSE:
2484 // m_start holds the first bbox corner and calcEdit derives the ellipse from it.
2485 m_editState = 1;
2486 SetStart( aPosition );
2487 SetEnd( aPosition );
2488 SetEllipseCenter( aPosition );
2492 break;
2493
2495 // State 1: drag bbox. States 2-3: pick start then end angle.
2496 SetStart( aPosition );
2497 SetEnd( aPosition );
2498 SetEllipseCenter( aPosition );
2504 m_editState = 1;
2505 break;
2506
2507 default:
2509 }
2510}
2511
2512
2513bool EDA_SHAPE::continueEdit( const VECTOR2I& aPosition )
2514{
2515 switch( GetShape() )
2516 {
2517 case SHAPE_T::ARC:
2518 case SHAPE_T::SEGMENT:
2519 case SHAPE_T::CIRCLE:
2520 case SHAPE_T::RECTANGLE:
2521 case SHAPE_T::ELLIPSE: return false;
2522
2523 case SHAPE_T::BEZIER:
2524 if( m_editState == 3 )
2525 return false;
2526
2527 m_editState++;
2528 return true;
2529
2531 if( m_editState == 3 )
2532 return false;
2533
2534 m_editState++;
2535 return true;
2536
2537 case SHAPE_T::POLY:
2538 {
2539 SHAPE_LINE_CHAIN& poly = GetPolyShape().Outline( 0 );
2540
2541 // do not add zero-length segments
2542 if( poly.CPoint( (int) poly.GetPointCount() - 2 ) != poly.CLastPoint() )
2543 poly.Append( aPosition, true );
2544 }
2545 return true;
2546
2547 default:
2549 return false;
2550 }
2551}
2552
2553
2554void EDA_SHAPE::calcEdit( const VECTOR2I& aPosition )
2555{
2556#define sq( x ) pow( x, 2 )
2557
2558 switch( GetShape() )
2559 {
2560 case SHAPE_T::SEGMENT:
2561 case SHAPE_T::CIRCLE:
2562 case SHAPE_T::RECTANGLE:
2563 SetEnd( aPosition );
2564 break;
2565
2566 case SHAPE_T::BEZIER:
2567 {
2568 switch( m_editState )
2569 {
2570 case 0:
2571 SetStart( aPosition );
2572 SetEnd( aPosition );
2573 SetBezierC1( aPosition );
2574 SetBezierC2( aPosition );
2575 break;
2576
2577 case 1:
2578 SetBezierC2( aPosition );
2579 SetEnd( aPosition );
2580 break;
2581
2582 case 2:
2583 SetBezierC1( aPosition );
2584 break;
2585
2586 case 3:
2587 SetBezierC2( aPosition );
2588 break;
2589 }
2590
2592 }
2593 break;
2594
2595 case SHAPE_T::ARC:
2596 {
2597 double radius = GetRadius();
2598 EDA_ANGLE lastAngle = GetArcAngle();
2599
2600 // Edit state 0: drawing: place start
2601 // Edit state 1: drawing: place end (center calculated for 90-degree subtended angle)
2602 // Edit state 2: point edit: move start (center calculated for invariant subtended angle)
2603 // Edit state 3: point edit: move end (center calculated for invariant subtended angle)
2604 // Edit state 4: point edit: move center
2605 // Edit state 5: point edit: move arc-mid-point
2606
2607 switch( m_editState )
2608 {
2609 case 0:
2610 SetArcGeometry( aPosition, aPosition, aPosition );
2611 return;
2612
2613 case 1:
2614 m_end = aPosition;
2615 radius = m_start.Distance( m_end ) * M_SQRT1_2;
2616 break;
2617
2618 case 2:
2619 case 3:
2620 {
2621 VECTOR2I v = m_start - m_end;
2622 double chordBefore = v.SquaredEuclideanNorm();
2623
2624 if( m_editState == 2 )
2625 m_start = aPosition;
2626 else
2627 m_end = aPosition;
2628
2629 v = m_start - m_end;
2630
2631 double chordAfter = v.SquaredEuclideanNorm();
2632 double ratio = 0.0;
2633
2634 if( chordBefore > 0 )
2635 ratio = chordAfter / chordBefore;
2636
2637 if( ratio != 0 )
2638 radius = std::max( sqrt( sq( radius ) * ratio ), sqrt( chordAfter ) / 2 );
2639 break;
2640 }
2641
2642 case 4:
2643 {
2644 double radialA = m_start.Distance( aPosition );
2645 double radialB = m_end.Distance( aPosition );
2646 radius = ( radialA + radialB ) / 2.0;
2647 break;
2648 }
2649
2650 case 5:
2651 SetArcGeometry( GetStart(), aPosition, GetEnd() );
2652 return;
2653 }
2654
2655 // Calculate center based on start, end, and radius
2656 //
2657 // Let 'l' be the length of the chord and 'm' the middle point of the chord
2658 double l = m_start.Distance( m_end );
2659 VECTOR2D m = ( m_start + m_end ) / 2;
2660 double sqRadDiff = ( radius * radius ) - ( l * l ) / 4.0;
2661
2662 // Calculate 'd', the vector from the chord midpoint to the center
2663 VECTOR2D d;
2664
2665 if( l > 0 && sqRadDiff >= 0 )
2666 {
2667 d.x = sqrt( sqRadDiff ) * ( m_start.y - m_end.y ) / l;
2668 d.y = sqrt( sqRadDiff ) * ( m_end.x - m_start.x ) / l;
2669 }
2670
2671 VECTOR2I c1 = KiROUND( m + d );
2672 VECTOR2I c2 = KiROUND( m - d );
2673
2674 // Solution gives us 2 centers; we need to pick one:
2675 switch( m_editState )
2676 {
2677 case 1:
2678 // Keep arc clockwise while drawing i.e. arc angle = 90 deg.
2679 // it can be 90 or 270 deg depending on the arc center choice (c1 or c2)
2680 m_arcCenter = c1; // first trial
2681
2682 if( GetArcAngle() > ANGLE_180 )
2683 m_arcCenter = c2;
2684
2685 break;
2686
2687 case 2:
2688 case 3:
2689 // Pick the one of c1, c2 to keep arc on the same side
2690 m_arcCenter = c1; // first trial
2691
2692 if( ( lastAngle < ANGLE_180 ) != ( GetArcAngle() < ANGLE_180 ) )
2693 m_arcCenter = c2;
2694
2695 break;
2696
2697 case 4:
2698 // Pick the one closer to the mouse position
2699 m_arcCenter = c1.Distance( aPosition ) < c2.Distance( aPosition ) ? c1 : c2;
2700 break;
2701 }
2702
2703 break;
2704 }
2705
2706 case SHAPE_T::POLY:
2707 GetPolyShape().Outline( 0 ).SetPoint( GetPolyShape().Outline( 0 ).GetPointCount() - 1,
2708 aPosition );
2709 break;
2710
2711 case SHAPE_T::ELLIPSE:
2712 {
2713 const VECTOR2I firstCorner = GetStart();
2714 const VECTOR2I secondCorner = aPosition;
2715 const VECTOR2I center = ( firstCorner + secondCorner ) / 2;
2716 const int halfW = std::abs( secondCorner.x - firstCorner.x ) / 2;
2717 const int halfH = std::abs( secondCorner.y - firstCorner.y ) / 2;
2718
2719 int majorRadius;
2720 int minorRadius;
2721 EDA_ANGLE rotation;
2722
2723 if( halfW >= halfH )
2724 {
2725 majorRadius = std::max( halfW, 1 );
2726 minorRadius = std::max( halfH, 1 );
2727 rotation = ANGLE_0;
2728 }
2729 else
2730 {
2731 majorRadius = std::max( halfH, 1 );
2732 minorRadius = std::max( halfW, 1 );
2733 rotation = ANGLE_90;
2734 }
2735
2737 SetEllipseMajorRadius( majorRadius );
2738 SetEllipseMinorRadius( minorRadius );
2739 SetEllipseRotation( rotation );
2740 SetEnd( aPosition );
2741 break;
2742 }
2743
2745 {
2746 switch( m_editState )
2747 {
2748 case 0:
2749 case 1:
2750 {
2751 // Bbox
2752 const VECTOR2I firstCorner = GetStart();
2753 const VECTOR2I secondCorner = aPosition;
2754 const VECTOR2I center = ( firstCorner + secondCorner ) / 2;
2755 const int halfW = std::abs( secondCorner.x - firstCorner.x ) / 2;
2756 const int halfH = std::abs( secondCorner.y - firstCorner.y ) / 2;
2757
2758 int majorRadius;
2759 int minorRadius;
2760 EDA_ANGLE rotation;
2761
2762 if( halfW >= halfH )
2763 {
2764 majorRadius = std::max( halfW, 1 );
2765 minorRadius = std::max( halfH, 1 );
2766 rotation = ANGLE_0;
2767 }
2768 else
2769 {
2770 majorRadius = std::max( halfH, 1 );
2771 minorRadius = std::max( halfW, 1 );
2772 rotation = ANGLE_90;
2773 }
2774
2776 SetEllipseMajorRadius( majorRadius );
2777 SetEllipseMinorRadius( minorRadius );
2778 SetEllipseRotation( rotation );
2779 SetEnd( aPosition );
2780
2781 // Keep the preview rendering as a full closed ellipse during bbox build.
2784
2785 break;
2786 }
2787
2788 case 2:
2789 case 3:
2790 {
2791 // Project cursor onto the parametric form (a * cos t, b * sin t) to get t.
2792 const VECTOR2I center = m_ellipse.Center;
2793 const double a = std::max( 1, m_ellipse.MajorRadius );
2794 const double b = std::max( 1, m_ellipse.MinorRadius );
2795 const EDA_ANGLE rotation = m_ellipse.Rotation;
2796
2797 const double dx = aPosition.x - center.x;
2798 const double dy = aPosition.y - center.y;
2799
2800 const double cosRot = rotation.Cos();
2801 const double sinRot = rotation.Sin();
2802 const double lx = dx * cosRot + dy * sinRot;
2803 const double ly = -dx * sinRot + dy * cosRot;
2804
2805 const EDA_ANGLE paramAngle( std::atan2( ly / b, lx / a ), RADIANS_T );
2806
2807 if( m_editState == 2 )
2808 {
2809 SetEllipseStartAngle( paramAngle );
2810 SetEllipseEndAngle( paramAngle + ANGLE_360 );
2811 }
2812 else
2813 {
2814 // Force end > start
2815 EDA_ANGLE cursorAngle = paramAngle;
2816
2817 while( cursorAngle <= m_ellipse.StartAngle )
2818 cursorAngle = cursorAngle + ANGLE_360;
2819
2820 SetEllipseEndAngle( cursorAngle );
2821 }
2822
2823 break;
2824 }
2825 }
2826
2827 break;
2828 }
2829
2830 default:
2832 }
2833}
2834
2835
2836void EDA_SHAPE::endEdit( bool aClosed )
2837{
2838 switch( GetShape() )
2839 {
2840 case SHAPE_T::ARC:
2841 case SHAPE_T::SEGMENT:
2842 case SHAPE_T::CIRCLE:
2843 case SHAPE_T::RECTANGLE:
2844 case SHAPE_T::BEZIER: break;
2845
2846 case SHAPE_T::ELLIPSE:
2848 m_editState = 0;
2850 break;
2851
2852 case SHAPE_T::POLY:
2853 {
2854 SHAPE_LINE_CHAIN& poly = GetPolyShape().Outline( 0 );
2855
2856 // do not include last point twice
2857 if( poly.GetPointCount() > 2 )
2858 {
2859 if( poly.CPoint( poly.GetPointCount() - 2 ) == poly.CLastPoint() )
2860 {
2861 poly.SetClosed( aClosed );
2862 }
2863 else
2864 {
2865 poly.SetClosed( false );
2866 poly.Remove( poly.GetPointCount() - 1 );
2867 }
2868 }
2869
2870 break;
2871 }
2872
2873 default:
2875 }
2876}
2877
2878
2880{
2881 EDA_SHAPE* image = dynamic_cast<EDA_SHAPE*>( aImage );
2882 assert( image );
2883
2884 #define SWAPITEM( x ) std::swap( x, image->x )
2885 SWAPITEM( m_stroke );
2886 SWAPITEM( m_start );
2887 SWAPITEM( m_end );
2889 SWAPITEM( m_shape );
2893 SWAPITEM( m_poly );
2896 SWAPITEM( m_fill );
2900 #undef SWAPITEM
2901
2902 m_hatchingDirty = true;
2903}
2904
2905
2906int EDA_SHAPE::Compare( const EDA_SHAPE* aOther ) const
2907{
2908#define EPSILON 2 // Should be enough for rounding errors on calculated items
2909
2910#define TEST( a, b ) { if( a != b ) return a - b; }
2911#define TEST_E( a, b ) { if( abs( a - b ) > EPSILON ) return a - b; }
2912#define TEST_PT( a, b ) { TEST_E( a.x, b.x ); TEST_E( a.y, b.y ); }
2913
2914 TEST_PT( m_start, aOther->m_start );
2915 TEST_PT( m_end, aOther->m_end );
2916
2917 TEST( (int) m_shape, (int) aOther->m_shape );
2918
2920 {
2922 }
2923 else if( m_shape == SHAPE_T::ARC )
2924 {
2925 TEST_PT( GetArcMid(), aOther->GetArcMid() );
2926 }
2927 else if( m_shape == SHAPE_T::BEZIER )
2928 {
2929 TEST_PT( m_bezierC1, aOther->m_bezierC1 );
2930 TEST_PT( m_bezierC2, aOther->m_bezierC2 );
2931 }
2933 {
2934 TEST_PT( m_ellipse.Center, aOther->m_ellipse.Center );
2935 TEST_E( m_ellipse.MajorRadius, aOther->m_ellipse.MajorRadius );
2936 TEST_E( m_ellipse.MinorRadius, aOther->m_ellipse.MinorRadius );
2937 TEST_E( m_ellipse.Rotation.AsTenthsOfADegree(), aOther->m_ellipse.Rotation.AsTenthsOfADegree() );
2938
2940 {
2941 TEST_E( m_ellipse.StartAngle.AsTenthsOfADegree(), aOther->m_ellipse.StartAngle.AsTenthsOfADegree() );
2942 TEST_E( m_ellipse.EndAngle.AsTenthsOfADegree(), aOther->m_ellipse.EndAngle.AsTenthsOfADegree() );
2943 }
2944 }
2945 else if( m_shape == SHAPE_T::POLY )
2946 {
2947 TEST( GetPolyShape().TotalVertices(), aOther->GetPolyShape().TotalVertices() );
2948 }
2949
2950 for( size_t ii = 0; ii < m_bezierPoints.size(); ++ii )
2951 TEST_PT( m_bezierPoints[ii], aOther->m_bezierPoints[ii] );
2952
2953 for( int ii = 0; ii < GetPolyShape().TotalVertices(); ++ii )
2954 TEST_PT( GetPolyShape().CVertex( ii ), aOther->GetPolyShape().CVertex( ii ) );
2955
2956 TEST_E( m_stroke.GetWidth(), aOther->m_stroke.GetWidth() );
2957 TEST( (int) m_stroke.GetLineStyle(), (int) aOther->m_stroke.GetLineStyle() );
2958 TEST( (int) m_fill, (int) aOther->m_fill );
2959
2960 return 0;
2961}
2962
2963
2964void EDA_SHAPE::TransformShapeToPolygon( SHAPE_POLY_SET& aBuffer, int aClearance, int aError,
2965 ERROR_LOC aErrorLoc, bool ignoreLineWidth, bool includeFill ) const
2966{
2967 bool solidFill = IsSolidFill() || ( IsHatchedFill() && !includeFill ) || IsProxyItem();
2968 int width = ignoreLineWidth ? 0 : GetWidth();
2969
2970 width += 2 * aClearance;
2971
2972 switch( m_shape )
2973 {
2974 case SHAPE_T::CIRCLE:
2975 {
2976 int r = GetRadius();
2977
2978 if( solidFill )
2979 TransformCircleToPolygon( aBuffer, getCenter(), r + width / 2, aError, aErrorLoc );
2980 else
2981 TransformRingToPolygon( aBuffer, getCenter(), r, width, aError, aErrorLoc );
2982
2983 break;
2984 }
2985
2986 case SHAPE_T::RECTANGLE:
2987 {
2988 if( GetCornerRadius() > 0 )
2989 {
2991 BOX2I bbox = getBoundingBox();
2992 VECTOR2I position = bbox.GetCenter();
2993
2994 if( solidFill )
2995 {
2997 0.0, 0, width / 2, aError, aErrorLoc );
2998 }
2999 else
3000 {
3002 SHAPE_POLY_SET poly;
3003 rr.TransformToPolygon( poly, aError );
3004 SHAPE_LINE_CHAIN& outline = poly.Outline( 0 );
3005 outline.SetClosed( true );
3006
3007 std::set<size_t> arcsHandled;
3008
3009 for( int ii = 0; ii < outline.SegmentCount(); ++ii )
3010 {
3011 if( outline.IsArcSegment( ii ) )
3012 {
3013 size_t arcIndex = outline.ArcIndex( ii );
3014
3015 if( arcsHandled.contains( arcIndex ) )
3016 continue;
3017
3018 arcsHandled.insert( arcIndex );
3019
3020 const SHAPE_ARC& arc = outline.Arc( arcIndex );
3021 TransformArcToPolygon( aBuffer, arc.GetP0(), arc.GetArcMid(), arc.GetP1(), width, aError,
3022 aErrorLoc );
3023 }
3024 else
3025 {
3026 const SEG& seg = outline.GetSegment( ii );
3027 TransformOvalToPolygon( aBuffer, seg.A, seg.B, width, aError, aErrorLoc );
3028 }
3029 }
3030 }
3031 }
3032 else
3033 {
3034 std::vector<VECTOR2I> pts = GetRectCorners();
3035
3036 if( solidFill )
3037 {
3038 aBuffer.NewOutline();
3039
3040 for( const VECTOR2I& pt : pts )
3041 aBuffer.Append( pt );
3042 }
3043
3044 if( width > 0 || !solidFill )
3045 {
3046 // Add in segments
3047 TransformOvalToPolygon( aBuffer, pts[0], pts[1], width, aError, aErrorLoc );
3048 TransformOvalToPolygon( aBuffer, pts[1], pts[2], width, aError, aErrorLoc );
3049 TransformOvalToPolygon( aBuffer, pts[2], pts[3], width, aError, aErrorLoc );
3050 TransformOvalToPolygon( aBuffer, pts[3], pts[0], width, aError, aErrorLoc );
3051 }
3052 }
3053
3054 break;
3055 }
3056
3057 case SHAPE_T::ARC:
3058 TransformArcToPolygon( aBuffer, GetStart(), GetArcMid(), GetEnd(), width, aError, aErrorLoc );
3059 break;
3060
3061 case SHAPE_T::SEGMENT:
3062 TransformOvalToPolygon( aBuffer, GetStart(), GetEnd(), width, aError, aErrorLoc );
3063 break;
3064
3065 case SHAPE_T::POLY:
3066 {
3067 if( !IsPolyShapeValid() )
3068 break;
3069
3070 if( solidFill )
3071 {
3072 for( int ii = 0; ii < GetPolyShape().OutlineCount(); ++ii )
3073 {
3074 const SHAPE_LINE_CHAIN& poly = GetPolyShape().Outline( ii );
3075 SHAPE_POLY_SET tmp;
3076 tmp.NewOutline();
3077
3078 for( int jj = 0; jj < (int) poly.GetPointCount(); ++jj )
3079 tmp.Append( poly.GetPoint( jj ) );
3080
3081 if( width > 0 )
3082 {
3083 int inflate = width / 2;
3084
3085 if( aErrorLoc == ERROR_OUTSIDE )
3086 inflate += aError;
3087
3088 tmp.Inflate( inflate, CORNER_STRATEGY::ROUND_ALL_CORNERS, aError );
3089 }
3090
3091 aBuffer.Append( tmp );
3092 }
3093 }
3094 else
3095 {
3096 for( int ii = 0; ii < GetPolyShape().OutlineCount(); ++ii )
3097 {
3098 const SHAPE_LINE_CHAIN& poly = GetPolyShape().Outline( ii );
3099
3100 for( int jj = 0; jj < (int) poly.SegmentCount(); ++jj )
3101 {
3102 const SEG& seg = poly.GetSegment( jj );
3103 TransformOvalToPolygon( aBuffer, seg.A, seg.B, width, aError, aErrorLoc );
3104 }
3105 }
3106 }
3107
3108 break;
3109 }
3110
3111 case SHAPE_T::BEZIER:
3112 {
3113 std::vector<VECTOR2I> ctrlPts = { GetStart(), GetBezierC1(), GetBezierC2(), GetEnd() };
3114 BEZIER_POLY converter( ctrlPts );
3115 std::vector<VECTOR2I> poly;
3116 converter.GetPoly( poly, aError );
3117
3118 for( unsigned ii = 1; ii < poly.size(); ii++ )
3119 TransformOvalToPolygon( aBuffer, poly[ii - 1], poly[ii], width, aError, aErrorLoc );
3120
3121 break;
3122 }
3123
3124 case SHAPE_T::ELLIPSE:
3126 {
3128
3130
3131 if( solidFill && m_shape == SHAPE_T::ELLIPSE )
3132 {
3133 // Filled closed ellipse, build the outline, inflate for stroke width.
3134 SHAPE_POLY_SET tmp;
3135 tmp.NewOutline();
3136
3137 for( int ii = 0; ii < chain.PointCount(); ++ii )
3138 tmp.Append( chain.CPoint( ii ) );
3139
3140 if( width > 0 )
3141 {
3142 int inflate = width / 2;
3143
3144 if( aErrorLoc == ERROR_OUTSIDE )
3145 inflate += aError;
3146
3147 tmp.Inflate( inflate, CORNER_STRATEGY::ROUND_ALL_CORNERS, aError );
3148 }
3149
3150 aBuffer.Append( tmp );
3151 }
3152 else
3153 {
3154 // stroke each tessellated segment as an oval.
3155 for( int ii = 0; ii < chain.SegmentCount(); ++ii )
3156 {
3157 const SEG& seg = chain.CSegment( ii );
3158 TransformOvalToPolygon( aBuffer, seg.A, seg.B, width, aError, aErrorLoc );
3159 }
3160 }
3161
3162 break;
3163 }
3164
3165 default:
3167 break;
3168 }
3169
3170 if( IsHatchedFill() && includeFill )
3171 {
3172 for( int ii = 0; ii < GetHatching().OutlineCount(); ++ii )
3173 aBuffer.AddOutline( GetHatching().COutline( ii ) );
3174 }
3175}
3176
3177
3178void EDA_SHAPE::SetWidth( int aWidth )
3179{
3180 m_stroke.SetWidth( aWidth );
3181 m_hatchingDirty = true;
3182}
3183
3184
3186{
3187 m_stroke.SetLineStyle( aStyle );
3188}
3189
3190
3192{
3193 if( m_stroke.GetLineStyle() != LINE_STYLE::DEFAULT )
3194 return m_stroke.GetLineStyle();
3195
3196 return LINE_STYLE::SOLID;
3197}
3198
3199
3200bool EDA_SHAPE::operator==( const EDA_SHAPE& aOther ) const
3201{
3202 if( GetShape() != aOther.GetShape() )
3203 return false;
3204
3205 if( m_fill != aOther.m_fill )
3206 return false;
3207
3208 if( m_stroke.GetWidth() != aOther.m_stroke.GetWidth() )
3209 return false;
3210
3211 if( m_stroke.GetLineStyle() != aOther.m_stroke.GetLineStyle() )
3212 return false;
3213
3214 if( m_fillColor != aOther.m_fillColor )
3215 return false;
3216
3217 switch( GetShape() )
3218 {
3219 case SHAPE_T::SEGMENT:
3220 case SHAPE_T::RECTANGLE:
3221 case SHAPE_T::CIRCLE:
3222 if( m_start != aOther.m_start )
3223 return false;
3224
3225 if( m_end != aOther.m_end )
3226 return false;
3227
3228 break;
3229
3230 case SHAPE_T::ARC:
3231 if( m_start != aOther.m_start )
3232 return false;
3233
3234 if( m_end != aOther.m_end )
3235 return false;
3236
3237 if( m_arcCenter != aOther.m_arcCenter )
3238 return false;
3239
3240 break;
3241
3242 case SHAPE_T::POLY:
3243 if( GetPolyShape().TotalVertices() != aOther.GetPolyShape().TotalVertices() )
3244 return false;
3245
3246 for( int ii = 0; ii < GetPolyShape().TotalVertices(); ++ii )
3247 {
3248 if( GetPolyShape().CVertex( ii ) != aOther.GetPolyShape().CVertex( ii ) )
3249 return false;
3250 }
3251
3252 break;
3253
3254 case SHAPE_T::BEZIER:
3255 if( m_start != aOther.m_start )
3256 return false;
3257
3258 if( m_end != aOther.m_end )
3259 return false;
3260
3261 if( m_bezierC1 != aOther.m_bezierC1 )
3262 return false;
3263
3264 if( m_bezierC2 != aOther.m_bezierC2 )
3265 return false;
3266
3267 if( m_bezierPoints != aOther.m_bezierPoints )
3268 return false;
3269
3270 break;
3271
3272 case SHAPE_T::ELLIPSE:
3274 if( m_ellipse.Center != aOther.m_ellipse.Center )
3275 return false;
3276
3277 if( m_ellipse.MajorRadius != aOther.m_ellipse.MajorRadius )
3278 return false;
3279 if( m_ellipse.MinorRadius != aOther.m_ellipse.MinorRadius )
3280 return false;
3281
3282 if( m_ellipse.Rotation != aOther.m_ellipse.Rotation )
3283 return false;
3284
3286 {
3287 if( m_ellipse.StartAngle != aOther.m_ellipse.StartAngle )
3288 return false;
3289
3290 if( m_ellipse.EndAngle != aOther.m_ellipse.EndAngle )
3291 return false;
3292 }
3293
3294 break;
3295
3296 default:
3297 return false;
3298 }
3299
3300 return true;
3301}
3302
3303
3304double EDA_SHAPE::Similarity( const EDA_SHAPE& aOther ) const
3305{
3306 if( GetShape() != aOther.GetShape() )
3307 return 0.0;
3308
3309 double similarity = 1.0;
3310
3311 if( m_fill != aOther.m_fill )
3312 similarity *= 0.9;
3313
3314 if( m_stroke.GetWidth() != aOther.m_stroke.GetWidth() )
3315 similarity *= 0.9;
3316
3317 if( m_stroke.GetLineStyle() != aOther.m_stroke.GetLineStyle() )
3318 similarity *= 0.9;
3319
3320 if( m_fillColor != aOther.m_fillColor )
3321 similarity *= 0.9;
3322
3323 if( m_start != aOther.m_start )
3324 similarity *= 0.9;
3325
3326 if( m_end != aOther.m_end )
3327 similarity *= 0.9;
3328
3329 if( m_arcCenter != aOther.m_arcCenter )
3330 similarity *= 0.9;
3331
3332 if( m_bezierC1 != aOther.m_bezierC1 )
3333 similarity *= 0.9;
3334
3335 if( m_bezierC2 != aOther.m_bezierC2 )
3336 similarity *= 0.9;
3337
3338 {
3339 int m = m_bezierPoints.size();
3340 int n = aOther.m_bezierPoints.size();
3341
3342 size_t longest = alg::longest_common_subset( m_bezierPoints, aOther.m_bezierPoints );
3343
3344 similarity *= std::pow( 0.9, m + n - 2 * longest );
3345 }
3346
3347 {
3348 int m = GetPolyShape().TotalVertices();
3349 int n = aOther.GetPolyShape().TotalVertices();
3350 std::vector<VECTOR2I> poly;
3351 std::vector<VECTOR2I> otherPoly;
3352 VECTOR2I lastPt( 0, 0 );
3353
3354 // We look for the longest common subset of the two polygons, but we need to
3355 // offset each point because we're actually looking for overall similarity, not just
3356 // exact matches. So if the zone is moved by 1IU, we only want one point to be
3357 // considered "moved" rather than the entire polygon. In this case, the first point
3358 // will not be a match but the rest of the sequence will.
3359 for( int ii = 0; ii < m; ++ii )
3360 {
3361 poly.emplace_back( lastPt - GetPolyShape().CVertex( ii ) );
3362 lastPt = GetPolyShape().CVertex( ii );
3363 }
3364
3365 lastPt = VECTOR2I( 0, 0 );
3366
3367 for( int ii = 0; ii < n; ++ii )
3368 {
3369 otherPoly.emplace_back( lastPt - aOther.GetPolyShape().CVertex( ii ) );
3370 lastPt = aOther.GetPolyShape().CVertex( ii );
3371 }
3372
3373 size_t longest = alg::longest_common_subset( poly, otherPoly );
3374
3375 similarity *= std::pow( 0.9, m + n - 2 * longest );
3376 }
3377
3378 return similarity;
3379}
3380
3381
3385
3386
3387static struct EDA_SHAPE_DESC
3388{
3390 {
3392 .Map( SHAPE_T::SEGMENT, _HKI( "Segment" ) )
3393 .Map( SHAPE_T::RECTANGLE, _HKI( "Rectangle" ) )
3394 .Map( SHAPE_T::ARC, _HKI( "Arc" ) )
3395 .Map( SHAPE_T::CIRCLE, _HKI( "Circle" ) )
3396 .Map( SHAPE_T::POLY, _HKI( "Polygon" ) )
3397 .Map( SHAPE_T::BEZIER, _HKI( "Bezier" ) )
3398 .Map( SHAPE_T::ELLIPSE, _HKI( "Ellipse" ) )
3399 .Map( SHAPE_T::ELLIPSE_ARC, _HKI( "Elliptical Arc" ) );
3400
3402
3403 if( lineStyleEnum.Choices().GetCount() == 0 )
3404 {
3405 lineStyleEnum.Map( LINE_STYLE::SOLID, _HKI( "Solid" ) )
3406 .Map( LINE_STYLE::DASH, _HKI( "Dashed" ) )
3407 .Map( LINE_STYLE::DOT, _HKI( "Dotted" ) )
3408 .Map( LINE_STYLE::DASHDOT, _HKI( "Dash-Dot" ) )
3409 .Map( LINE_STYLE::DASHDOTDOT, _HKI( "Dash-Dot-Dot" ) );
3410 }
3411
3413
3414 if( hatchModeEnum.Choices().GetCount() == 0 )
3415 {
3416 hatchModeEnum.Map( UI_FILL_MODE::NONE, _HKI( "None" ) );
3417 hatchModeEnum.Map( UI_FILL_MODE::SOLID, _HKI( "Solid" ) );
3418 hatchModeEnum.Map( UI_FILL_MODE::HATCH, _HKI( "Hatch" ) );
3419 hatchModeEnum.Map( UI_FILL_MODE::REVERSE_HATCH, _HKI( "Reverse Hatch" ) );
3420 hatchModeEnum.Map( UI_FILL_MODE::CROSS_HATCH, _HKI( "Cross-hatch" ) );
3421 }
3422
3425
3426 auto isNotPolygonOrCircle =
3427 []( INSPECTABLE* aItem ) -> bool
3428 {
3429 // Polygons, unlike other shapes, have no meaningful start or end coordinates
3430 if( EDA_SHAPE* shape = dynamic_cast<EDA_SHAPE*>( aItem ) )
3431 return shape->GetShape() != SHAPE_T::POLY && shape->GetShape() != SHAPE_T::CIRCLE;
3432
3433 return false;
3434 };
3435
3436 auto isCircle =
3437 []( INSPECTABLE* aItem ) -> bool
3438 {
3439 // Polygons, unlike other shapes, have no meaningful start or end coordinates
3440 if( EDA_SHAPE* shape = dynamic_cast<EDA_SHAPE*>( aItem ) )
3441 return shape->GetShape() == SHAPE_T::CIRCLE;
3442
3443 return false;
3444 };
3445
3446 auto isRectangle =
3447 []( INSPECTABLE* aItem ) -> bool
3448 {
3449 // Polygons, unlike other shapes, have no meaningful start or end coordinates
3450 if( EDA_SHAPE* shape = dynamic_cast<EDA_SHAPE*>( aItem ) )
3451 return shape->GetShape() == SHAPE_T::RECTANGLE;
3452
3453 return false;
3454 };
3455
3456 auto isEllipseOrEllipseArc = []( INSPECTABLE* aItem ) -> bool
3457 {
3458 if( EDA_SHAPE* shape = dynamic_cast<EDA_SHAPE*>( aItem ) )
3459 {
3460 return shape->GetShape() == SHAPE_T::ELLIPSE || shape->GetShape() == SHAPE_T::ELLIPSE_ARC;
3461 }
3462
3463 return false;
3464 };
3465
3466 auto isEllipseArc = []( INSPECTABLE* aItem ) -> bool
3467 {
3468 if( EDA_SHAPE* shape = dynamic_cast<EDA_SHAPE*>( aItem ) )
3469 return shape->GetShape() == SHAPE_T::ELLIPSE_ARC;
3470
3471 return false;
3472 };
3473
3474 const wxString shapeProps = _HKI( "Shape Properties" );
3475
3476 auto shape = new PROPERTY_ENUM<EDA_SHAPE, SHAPE_T>( _HKI( "Shape" ),
3478 propMgr.AddProperty( shape, shapeProps );
3479
3480 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "Start X" ),
3483 shapeProps )
3484 .SetAvailableFunc( isNotPolygonOrCircle );
3485 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "Start Y" ),
3488 shapeProps )
3489 .SetAvailableFunc( isNotPolygonOrCircle );
3490
3491 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "Center X" ),
3494 shapeProps )
3495 .SetAvailableFunc( isCircle );
3496
3497 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "Center Y" ),
3500 shapeProps )
3501 .SetAvailableFunc( isCircle );
3502
3503 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "Radius" ),
3506 shapeProps )
3507 .SetAvailableFunc( isCircle );
3508
3509 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "End X" ),
3512 shapeProps )
3513 .SetAvailableFunc( isNotPolygonOrCircle );
3514
3515 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "End Y" ),
3518 shapeProps )
3519 .SetAvailableFunc( isNotPolygonOrCircle );
3520
3521 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "Width" ),
3524 shapeProps )
3525 .SetAvailableFunc( isRectangle );
3526
3527 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "Height" ),
3530 shapeProps )
3531 .SetAvailableFunc( isRectangle );
3532
3533 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "Corner Radius" ),
3536 shapeProps )
3537 .SetAvailableFunc( isRectangle )
3538 .SetValidator( []( const wxAny&& aValue, EDA_ITEM* aItem ) -> VALIDATOR_RESULT
3539 {
3540 wxASSERT_MSG( aValue.CheckType<int>(),
3541 "Expecting int-containing value" );
3542
3543 int radius = aValue.As<int>();
3544
3545 EDA_SHAPE* prop_shape = dynamic_cast<EDA_SHAPE*>( aItem );
3546
3547 if( !prop_shape )
3548 return std::nullopt;
3549
3550 int maxRadius = std::min( prop_shape->GetRectangleWidth(),
3551 prop_shape->GetRectangleHeight() ) / 2;
3552
3553 if( radius > maxRadius )
3554 return std::make_unique<VALIDATION_ERROR_TOO_LARGE<int>>( radius, maxRadius );
3555 else if( radius < 0 )
3556 return std::make_unique<VALIDATION_ERROR_TOO_SMALL<int>>( radius, 0 );
3557
3558 return std::nullopt;
3559 } );
3560
3564 shapeProps )
3565 .SetAvailableFunc( isEllipseOrEllipseArc );
3566
3570 shapeProps )
3571 .SetAvailableFunc( isEllipseOrEllipseArc );
3572
3574 _HKI( "Ellipse Rotation" ), &EDA_SHAPE::SetEllipseRotation,
3576 shapeProps )
3577 .SetAvailableFunc( isEllipseOrEllipseArc );
3578
3580 _HKI( "Arc Start Angle" ), &EDA_SHAPE::SetEllipseStartAngle,
3582 shapeProps )
3583 .SetAvailableFunc( isEllipseArc );
3584
3586 _HKI( "Arc End Angle" ), &EDA_SHAPE::SetEllipseEndAngle,
3588 shapeProps )
3589 .SetAvailableFunc( isEllipseArc );
3590
3591 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "Line Width" ),
3593 shapeProps );
3594
3595 propMgr.AddProperty( new PROPERTY_ENUM<EDA_SHAPE, LINE_STYLE>( _HKI( "Line Style" ),
3597 shapeProps );
3598
3599 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, COLOR4D>( _HKI( "Line Color" ),
3601 shapeProps )
3603
3604 auto angle = new PROPERTY<EDA_SHAPE, EDA_ANGLE>( _HKI( "Angle" ),
3607 angle->SetAvailableFunc(
3608 [=]( INSPECTABLE* aItem ) -> bool
3609 {
3610 if( EDA_SHAPE* curr_shape = dynamic_cast<EDA_SHAPE*>( aItem ) )
3611 return curr_shape->GetShape() == SHAPE_T::ARC;
3612
3613 return false;
3614 } );
3615 propMgr.AddProperty( angle, shapeProps );
3616
3617 auto fillAvailable =
3618 [=]( INSPECTABLE* aItem ) -> bool
3619 {
3620 if( EDA_ITEM* edaItem = dynamic_cast<EDA_ITEM*>( aItem ) )
3621 {
3622 // For some reason masking "Filled" and "Fill Color" at the
3623 // PCB_TABLECELL level doesn't work.
3624 if( edaItem->Type() == PCB_TABLECELL_T || edaItem->Type() == PCB_TEXTBOX_T )
3625 return false;
3626 }
3627
3628 if( EDA_SHAPE* edaShape = dynamic_cast<EDA_SHAPE*>( aItem ) )
3629 {
3630 switch( edaShape->GetShape() )
3631 {
3632 case SHAPE_T::POLY:
3633 case SHAPE_T::RECTANGLE:
3634 case SHAPE_T::CIRCLE:
3635 case SHAPE_T::BEZIER:
3636 case SHAPE_T::ELLIPSE: return true;
3637
3638 default:
3639 return false;
3640 }
3641 }
3642
3643 return false;
3644 };
3645
3648 shapeProps )
3649 .SetAvailableFunc( fillAvailable );
3650
3651 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, COLOR4D>( _HKI( "Fill Color" ),
3653 shapeProps )
3654 .SetAvailableFunc( fillAvailable )
3656 }
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:103
EDA_ANGLE Rotation
Definition ellipse.h:104
EDA_ANGLE StartAngle
Definition ellipse.h:105
NumericType MajorRadius
Definition ellipse.h:102
EDA_ANGLE EndAngle
Definition ellipse.h:106
VECTOR2< NumericType > Center
Definition ellipse.h:101
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:544
@ 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