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>
37#include <geometry/shape_rect.h>
39#include <geometry/roundrect.h>
41#include <geometry/roundrect.h>
42#include <macros.h>
43#include <algorithm>
44#include <iterator>
46#include <properties/property.h>
48#include <math/util.h> // for KiROUND
49#include <eda_item.h>
50#include <plotters/plotter.h>
51#include <api/api_enums.h>
52#include <api/api_utils.h>
53#include <api/common/types/base_types.pb.h>
54
55
56EDA_SHAPE::EDA_SHAPE( SHAPE_T aType, int aLineWidth, FILL_T aFill ) :
57 m_endsSwapped( false ),
58 m_shape( aType ),
62 m_fill( aFill ),
64 m_hatchingDirty( true ),
67 m_cornerRadius( 0 ),
68 m_editState( 0 ),
69 m_proxyItem( false )
70{
71}
72
73
77
78
79EDA_SHAPE::EDA_SHAPE( const SHAPE& aShape ) :
80 m_endsSwapped( false ),
84 m_fill(),
85 m_hatchingDirty( true ),
88 m_cornerRadius( 0 ),
89 m_editState( 0 ),
90 m_proxyItem( false )
91{
92 switch( aShape.Type() )
93 {
94 case SH_RECT:
95 {
96 auto rect = static_cast<const SHAPE_RECT&>( aShape );
97 m_shape = SHAPE_T::RECTANGLE;
98 SetStart( rect.GetPosition() );
99 SetEnd( rect.GetPosition() + rect.GetSize() );
100 break;
101 }
102
103 case SH_SEGMENT:
104 {
105 auto seg = static_cast<const SHAPE_SEGMENT&>( aShape );
106 m_shape = SHAPE_T::SEGMENT;
107 SetStart( seg.GetSeg().A );
108 SetEnd( seg.GetSeg().B );
109 SetWidth( seg.GetWidth() );
110 break;
111 }
112
113 case SH_LINE_CHAIN:
114 {
115 auto line = static_cast<const SHAPE_LINE_CHAIN&>( aShape );
116 m_shape = SHAPE_T::POLY;
117 GetPolyShape() = SHAPE_POLY_SET();
118 GetPolyShape().AddOutline( line );
119 SetWidth( line.Width() );
120 break;
121 }
122
123 case SH_CIRCLE:
124 {
125 auto circle = static_cast<const SHAPE_CIRCLE&>( aShape );
126 m_shape = SHAPE_T::CIRCLE;
127 SetStart( circle.GetCenter() );
128 SetEnd( circle.GetCenter() + circle.GetRadius() );
129 break;
130 }
131
132 case SH_ARC:
133 {
134 auto arc = static_cast<const SHAPE_ARC&>( aShape );
135 m_shape = SHAPE_T::ARC;
136 SetArcGeometry( arc.GetP0(), arc.GetArcMid(), arc.GetP1() );
137 SetWidth( arc.GetWidth() );
138 break;
139 }
140
141 case SH_SIMPLE:
142 {
143 auto poly = static_cast<const SHAPE_SIMPLE&>( aShape );
144 m_shape = SHAPE_T::POLY;
145 poly.TransformToPolygon( GetPolyShape(), 0, ERROR_INSIDE );
146 break;
147 }
148
149 case SH_ELLIPSE:
150 {
151 auto ellipse = static_cast<const SHAPE_ELLIPSE&>( aShape );
152 m_shape = ellipse.IsArc() ? SHAPE_T::ELLIPSE_ARC : SHAPE_T::ELLIPSE;
153 SetEllipseCenter( ellipse.GetCenter() );
154 SetEllipseMajorRadius( ellipse.GetMajorRadius() );
155 SetEllipseMinorRadius( ellipse.GetMinorRadius() );
156 SetEllipseRotation( ellipse.GetRotation() );
157
158 if( ellipse.IsArc() )
159 {
160 SetEllipseStartAngle( ellipse.GetStartAngle() );
161 SetEllipseEndAngle( ellipse.GetEndAngle() );
162 }
163 break;
164 }
165
166 // currently unhandled
167 case SH_POLY_SET:
168 case SH_COMPOUND:
169 case SH_NULL:
171 default:
172 m_shape = SHAPE_T::UNDEFINED;
173 break;
174 }
175}
176
177
180 m_shape( aOther.m_shape ),
181 m_stroke( aOther.m_stroke ),
183 m_endEnding( aOther.m_endEnding ),
184 m_fill( aOther.m_fill ),
185 m_fillColor( aOther.m_fillColor ),
186 m_hatchingDirty( true ),
190 m_start( aOther.m_start ),
191 m_end( aOther.m_end ),
192 m_arcCenter( aOther.m_arcCenter ),
193 m_arcMidData( aOther.m_arcMidData ),
194 m_bezierC1( aOther.m_bezierC1 ),
195 m_bezierC2( aOther.m_bezierC2 ),
197 m_ellipse( aOther.m_ellipse ),
198 m_editState( aOther.m_editState ),
199 m_proxyItem( aOther.m_proxyItem )
200{
201 if( aOther.m_poly )
202 m_poly = std::make_unique<SHAPE_POLY_SET>( *aOther.m_poly );
203}
204
205
207{
208 if( this == &aOther )
209 return *this;
210
212 m_shape = aOther.m_shape;
213 m_stroke = aOther.m_stroke;
214 m_fill = aOther.m_fill;
215 m_fillColor = aOther.m_fillColor;
216 m_hatchingCache.reset();
217 m_hatchingDirty = true;
221 m_start = aOther.m_start;
222 m_end = aOther.m_end;
223 m_arcCenter = aOther.m_arcCenter;
224 m_arcMidData = aOther.m_arcMidData;
225 m_bezierC1 = aOther.m_bezierC1;
226 m_bezierC2 = aOther.m_bezierC2;
228 m_ellipse = aOther.m_ellipse;
229 if( aOther.m_poly )
230 m_poly = std::make_unique<SHAPE_POLY_SET>( *aOther.m_poly );
231 else
232 m_poly.reset();
234 m_endEnding = aOther.m_endEnding;
235 m_editState = aOther.m_editState;
236 m_proxyItem = aOther.m_proxyItem;
237
238 return *this;
239}
240
241
242void EDA_SHAPE::Serialize( google::protobuf::Any &aContainer ) const
243{
244 Serialize( aContainer, pcbIUScale );
245}
246
247
248void EDA_SHAPE::Serialize( kiapi::common::types::GraphicShape& shape, const EDA_IU_SCALE &aScale ) const
249{
250 using namespace kiapi::common;
251
252 types::GraphicFillAttributes* fill = shape.mutable_attributes()->mutable_fill();
253
254 PackStroke( *shape.mutable_attributes()->mutable_stroke(), m_stroke, aScale );
255
257
259 PackColor( *fill->mutable_color(), m_fillColor );
260
261 switch( GetShape() )
262 {
263 case SHAPE_T::SEGMENT:
264 {
265 types::GraphicSegmentAttributes* segment = shape.mutable_segment();
266 PackVector2( *segment->mutable_start(), GetStart(), aScale );
267 PackVector2( *segment->mutable_end(), GetEnd(), aScale );
268 break;
269 }
270
272 {
273 types::GraphicRectangleAttributes* rectangle = shape.mutable_rectangle();
274 PackVector2( *rectangle->mutable_top_left(), GetStart(), aScale );
275 PackVector2( *rectangle->mutable_bottom_right(), GetEnd(), aScale );
276 PackDistance( *rectangle->mutable_corner_radius(), GetCornerRadius(), aScale );
277 break;
278 }
279
280 case SHAPE_T::ARC:
281 {
282 types::GraphicArcAttributes* arc = shape.mutable_arc();
283 PackVector2( *arc->mutable_start(), GetStart(), aScale );
284 PackVector2( *arc->mutable_mid(), GetArcMid(), aScale );
285 PackVector2( *arc->mutable_end(), GetEnd(), aScale );
286 break;
287 }
288
289 case SHAPE_T::CIRCLE:
290 {
291 types::GraphicCircleAttributes* circle = shape.mutable_circle();
292 PackVector2( *circle->mutable_center(), GetStart(), aScale );
293 PackVector2( *circle->mutable_radius_point(), GetEnd(), aScale );
294 break;
295 }
296
297 case SHAPE_T::POLY:
298 {
299 PackPolySet( *shape.mutable_polygon(), GetPolyShape(), aScale );
300 break;
301 }
302
303 case SHAPE_T::BEZIER:
304 {
305 types::GraphicBezierAttributes* bezier = shape.mutable_bezier();
306 PackVector2( *bezier->mutable_start(), GetStart(), aScale );
307 PackVector2( *bezier->mutable_control1(), GetBezierC1(), aScale );
308 PackVector2( *bezier->mutable_control2(), GetBezierC2(), aScale );
309 PackVector2( *bezier->mutable_end(), GetEnd(), aScale );
310 break;
311 }
312
313 case SHAPE_T::ELLIPSE:
314 {
315 types::GraphicEllipseAttributes* ellipse = shape.mutable_ellipse();
316 PackVector2( *ellipse->mutable_center(), GetEllipseCenter(), aScale );
317 PackDistance( *ellipse->mutable_major_radius(), GetEllipseMajorRadius(), aScale );
318 PackDistance( *ellipse->mutable_minor_radius(), GetEllipseMinorRadius(), aScale );
319 ellipse->mutable_rotation()->set_value_degrees( GetEllipseRotation().AsDegrees() );
320 break;
321 }
322
324 {
325 types::GraphicEllipseArcAttributes* arc = shape.mutable_ellipse_arc();
326 PackVector2( *arc->mutable_center(), GetEllipseCenter(), aScale );
327 PackDistance( *arc->mutable_major_radius(), GetEllipseMajorRadius(), aScale );
328 PackDistance( *arc->mutable_minor_radius(), GetEllipseMinorRadius(), aScale );
329 arc->mutable_rotation()->set_value_degrees( GetEllipseRotation().AsDegrees() );
330 arc->mutable_start_angle()->set_value_degrees( GetEllipseStartAngle().AsDegrees() );
331 arc->mutable_end_angle()->set_value_degrees( GetEllipseEndAngle().AsDegrees() );
332 break;
333 }
334
335 default:
336 wxASSERT_MSG( false, "Unhandled shape in EDA_SHAPE::Serialize" );
337 }
338
339 if( m_startEnding.GetStyle() != LINE_ENDING_STYLE::NONE )
340 PackLineEnding( *shape.mutable_start_ending(), m_startEnding, aScale );
341
342 if( m_endEnding.GetStyle() != LINE_ENDING_STYLE::NONE )
343 PackLineEnding( *shape.mutable_end_ending(), m_endEnding, aScale );
344
345 // TODO m_hasSolderMask and m_solderMaskMargin
346
347}
348
349
350void EDA_SHAPE::Serialize( google::protobuf::Any &aContainer, const EDA_IU_SCALE &aScale ) const
351{
352 kiapi::common::types::GraphicShape shape;
353 Serialize( shape, aScale );
354 aContainer.PackFrom( shape );
355}
356
357
358bool EDA_SHAPE::Deserialize( const google::protobuf::Any &aContainer )
359{
360 return Deserialize( aContainer, pcbIUScale );
361}
362
363
364bool EDA_SHAPE::Deserialize( const kiapi::common::types::GraphicShape& shape, const EDA_IU_SCALE &aScale )
365{
366 using namespace kiapi::common;
367
368 // Initialize everything to a known state that doesn't get touched by every
369 // codepath below, to make sure the equality operator is consistent
370 m_start = {};
371 m_end = {};
372 m_arcCenter = {};
373 m_arcMidData = {};
374 m_startEnding = {};
375 m_endEnding = {};
376 m_bezierC1 = {};
377 m_bezierC2 = {};
378 m_editState = 0;
379 m_proxyItem = false;
380 m_endsSwapped = false;
382
383 UnpackStroke( m_stroke, shape.attributes().stroke(), aScale );
384
385 if( shape.attributes().has_fill() )
386 {
387 SetFillMode( FromProtoEnum<FILL_T, types::GraphicFillType>( shape.attributes().fill().fill_type() ) );
388
389 if( shape.attributes().fill().has_color() )
390 SetFillColor( UnpackColor( shape.attributes().fill().color() ) );
391 }
392
393 if( shape.has_segment() )
394 {
396 SetStart( UnpackVector2( shape.segment().start(), aScale ) );
397 SetEnd( UnpackVector2( shape.segment().end(), aScale ) );
398 }
399 else if( shape.has_rectangle() )
400 {
402 SetStart( UnpackVector2( shape.rectangle().top_left(), aScale ) );
403 SetEnd( UnpackVector2( shape.rectangle().bottom_right(), aScale ) );
404 SetCornerRadius( UnpackDistance( shape.rectangle().corner_radius(), aScale ) );
405 }
406 else if( shape.has_arc() )
407 {
409 SetArcGeometry( UnpackVector2( shape.arc().start(), aScale ),
410 UnpackVector2( shape.arc().mid(), aScale ),
411 UnpackVector2( shape.arc().end(), aScale ) );
412 }
413 else if( shape.has_circle() )
414 {
416 SetStart( UnpackVector2( shape.circle().center(), aScale ) );
417 SetEnd( UnpackVector2( shape.circle().radius_point(), aScale ) );
418 }
419 else if( shape.has_polygon() )
420 {
422 SetPolyShape( UnpackPolySet( shape.polygon(), aScale ) );
423 }
424 else if( shape.has_bezier() )
425 {
427 SetStart( UnpackVector2( shape.bezier().start(), aScale ) );
428 SetBezierC1( UnpackVector2( shape.bezier().control1(), aScale ) );
429 SetBezierC2( UnpackVector2( shape.bezier().control2(), aScale ) );
430 SetEnd( UnpackVector2( shape.bezier().end(), aScale ) );
432 }
433 else if( shape.has_ellipse() )
434 {
436 SetEllipseCenter( UnpackVector2( shape.ellipse().center(), aScale ) );
437 SetEllipseMajorRadius( UnpackDistance( shape.ellipse().major_radius(), aScale ) );
438 SetEllipseMinorRadius( UnpackDistance( shape.ellipse().minor_radius(), aScale ) );
439 SetEllipseRotation( EDA_ANGLE( shape.ellipse().rotation().value_degrees(), DEGREES_T ) );
440 }
441 else if( shape.has_ellipse_arc() )
442 {
444 SetEllipseCenter( UnpackVector2( shape.ellipse_arc().center(), aScale ) );
445 SetEllipseMajorRadius( UnpackDistance( shape.ellipse_arc().major_radius(), aScale ) );
446 SetEllipseMinorRadius( UnpackDistance( shape.ellipse_arc().minor_radius(), aScale ) );
447 SetEllipseRotation( EDA_ANGLE( shape.ellipse_arc().rotation().value_degrees(), DEGREES_T ) );
448 SetEllipseStartAngle( EDA_ANGLE( shape.ellipse_arc().start_angle().value_degrees(), DEGREES_T ) );
449 SetEllipseEndAngle( EDA_ANGLE( shape.ellipse_arc().end_angle().value_degrees(), DEGREES_T ) );
450 }
451
452 if( shape.has_start_ending() )
453 m_startEnding = UnpackLineEnding( shape.start_ending(), aScale );
454
455 if( shape.has_end_ending() )
456 m_endEnding = UnpackLineEnding( shape.end_ending(), aScale );
457
458 return true;
459}
460
461bool EDA_SHAPE::Deserialize( const google::protobuf::Any &aContainer, const EDA_IU_SCALE &aScale )
462{
463 kiapi::common::types::GraphicShape shape;
464
465 if( !aContainer.UnpackTo( &shape ) )
466 return false;
467
468 return Deserialize( shape, aScale );
469}
470
471
472wxString EDA_SHAPE::ShowShape() const
473{
474 if( IsProxyItem() )
475 {
476 switch( m_shape )
477 {
478 case SHAPE_T::SEGMENT: return _( "Thermal Spoke" );
479 case SHAPE_T::RECTANGLE: return _( "Number Box" );
480 default: return wxT( "??" );
481 }
482 }
483 else
484 {
485 switch( m_shape )
486 {
487 case SHAPE_T::SEGMENT: return _( "Line" );
488 case SHAPE_T::RECTANGLE: return _( "Rect" );
489 case SHAPE_T::ARC: return _( "Arc" );
490 case SHAPE_T::CIRCLE: return _( "Circle" );
491 case SHAPE_T::BEZIER: return _( "Bezier Curve" );
492 case SHAPE_T::POLY: return _( "Polygon" );
493 case SHAPE_T::ELLIPSE: return _( "Ellipse" );
494 case SHAPE_T::ELLIPSE_ARC: return _( "Elliptical Arc" );
495 default: return wxT( "??" );
496 }
497 }
498}
499
500
502{
503 switch( m_shape )
504 {
505 case SHAPE_T::SEGMENT: return wxS( "S_SEGMENT" );
506 case SHAPE_T::RECTANGLE: return wxS( "S_RECT" );
507 case SHAPE_T::ARC: return wxS( "S_ARC" );
508 case SHAPE_T::CIRCLE: return wxS( "S_CIRCLE" );
509 case SHAPE_T::POLY: return wxS( "S_POLYGON" );
510 case SHAPE_T::BEZIER: return wxS( "S_CURVE" );
511 case SHAPE_T::ELLIPSE: return wxS( "S_ELLIPSE" );
512 case SHAPE_T::ELLIPSE_ARC: return wxS( "S_ELLIPSE_ARC" );
513 case SHAPE_T::UNDEFINED: return wxS( "UNDEFINED" );
514 }
515
516 return wxEmptyString; // Just to quiet GCC.
517}
518
519
521{
522 move( aPos - getPosition() );
523}
524
525
527{
529 return getCenter();
530 else if( m_shape == SHAPE_T::POLY )
531 return GetPolyShape().CVertex( 0 );
532 else
533 return m_start;
534}
535
536
538{
539 double length = 0.0;
540
541 switch( m_shape )
542 {
543 case SHAPE_T::BEZIER:
544 for( size_t ii = 1; ii < m_bezierPoints.size(); ++ii )
545 length += m_bezierPoints[ ii - 1].Distance( m_bezierPoints[ii] );
546
547 return length;
548
549 case SHAPE_T::SEGMENT:
550 return GetStart().Distance( GetEnd() );
551
552 case SHAPE_T::POLY:
553 for( int ii = 0; ii < GetPolyShape().COutline( 0 ).SegmentCount(); ii++ )
554 length += GetPolyShape().COutline( 0 ).CSegment( ii ).Length();
555
556 return length;
557
558 case SHAPE_T::ARC:
559 return GetRadius() * GetArcAngle().AsRadians();
560
561 case SHAPE_T::ELLIPSE:
563
564 default:
566 return 0.0;
567 }
568}
569
570
572{
573 switch( m_shape )
574 {
576 return GetEndY() - GetStartY();
577
578 default:
580 return 0;
581 }
582}
583
584
586{
587 switch( m_shape )
588 {
590 return GetEndX() - GetStartX();
591
592 default:
594 return 0;
595 }
596}
597
598
600{
601 return m_cornerRadius;
602}
603
604
605void EDA_SHAPE::SetCornerRadius( int aRadius )
606{
608 {
609 int width = std::abs( GetRectangleWidth() );
610 int height = std::abs( GetRectangleHeight() );
611 int maxRadius = std::min( width, height ) / 2;
612
613 m_cornerRadius = std::clamp( aRadius, 0, maxRadius );
614 }
615 else
616 {
617 m_cornerRadius = aRadius;
618 }
619
620 m_hatchingDirty = true;
621}
622
623
624void EDA_SHAPE::SetRectangleHeight( const int& aHeight )
625{
626 switch ( m_shape )
627 {
629 m_rectangleHeight = aHeight;
631 break;
632
633 default:
635 }
636}
637
638
639void EDA_SHAPE::SetRectangleWidth( const int& aWidth )
640{
641 switch ( m_shape )
642 {
644 m_rectangleWidth = aWidth;
646 break;
647
648 default:
650 }
651}
652
653
654void EDA_SHAPE::SetRectangle( const long long int& aHeight, const long long int& aWidth )
655{
656 switch ( m_shape )
657 {
659 SetRectangleHeight( aHeight );
660 SetRectangleWidth( aWidth );
661 break;
662
663 default:
665 }
666}
667
668
670{
671 switch( m_shape )
672 {
673 case SHAPE_T::CIRCLE:
675 case SHAPE_T::ELLIPSE: return true;
676
677 case SHAPE_T::ARC:
678 case SHAPE_T::SEGMENT:
679 case SHAPE_T::ELLIPSE_ARC: return false;
680
681 case SHAPE_T::POLY:
682 if( GetPolyShape().IsEmpty() )
683 return false;
684 else
685 return GetPolyShape().Outline( 0 ).IsClosed();
686
687 case SHAPE_T::BEZIER:
688 if( m_bezierPoints.size() < 3 )
689 return false;
690 else
691 return m_bezierPoints[0] == m_bezierPoints[ m_bezierPoints.size() - 1 ];
692
693 default:
695 return false;
696 }
697}
698
699
701{
702 m_fill = aFill;
703 m_hatchingDirty = true;
704}
705
706
708{
709 switch( aFill )
710 {
715 default: SetFilled( true ); break;
716 }
717}
718
719
731
732
734{
736
737 if( !m_hatchingCache )
738 m_hatchingCache = std::make_unique<EDA_SHAPE_HATCH_CACHE_DATA>();
739
740 return m_hatchingCache->hatching;
741}
742
743
744const std::vector<SEG>& EDA_SHAPE::GetHatchLines() const
745{
747
748 if( !m_hatchingCache )
749 m_hatchingCache = std::make_unique<EDA_SHAPE_HATCH_CACHE_DATA>();
750
751 return m_hatchingCache->hatchLines;
752}
753
754
756{
757 if( !m_hatchingCache )
758 m_hatchingCache = std::make_unique<EDA_SHAPE_HATCH_CACHE_DATA>();
759
760 return m_hatchingCache->hatching;
761}
762
763
764std::vector<SEG>& EDA_SHAPE::hatchLines() const
765{
766 if( !m_hatchingCache )
767 m_hatchingCache = std::make_unique<EDA_SHAPE_HATCH_CACHE_DATA>();
768
769 return m_hatchingCache->hatchLines;
770}
771
772
774{
775 if( !m_hatchingDirty )
776 return;
777
778 std::vector<double> slopes;
779 int lineWidth = GetHatchLineWidth();
780 int spacing = GetHatchLineSpacing();
781 SHAPE_POLY_SET shapeBuffer;
782
783 // Validate state before clearing cached hatching. If we can't regenerate, keep existing cache.
784 if( isMoving() )
785 return;
786
788 slopes = { 1.0, -1.0 };
789 else if( GetFillMode() == FILL_T::HATCH )
790 slopes = { -1.0 };
791 else if( GetFillMode() == FILL_T::REVERSE_HATCH )
792 slopes = { 1.0 };
793 else
794 return;
795
796 if( spacing == 0 )
797 return;
798
799 switch( m_shape )
800 {
801 case SHAPE_T::ARC:
802 case SHAPE_T::SEGMENT:
803 case SHAPE_T::BEZIER:
804 case SHAPE_T::ELLIPSE_ARC: return;
805
807 {
809 rr.TransformToPolygon( shapeBuffer, getMaxError() );
810 }
811 break;
812
813 case SHAPE_T::CIRCLE:
815 break;
816
817 case SHAPE_T::POLY:
818 if( GetPolyShape().OutlineCount() == 0 )
819 return;
820
821 shapeBuffer = GetPolyShape().CloneDropTriangulation();
822
823 for( int ii = 0; ii < shapeBuffer.OutlineCount(); ++ii )
824 {
825 SHAPE_LINE_CHAIN& outline = shapeBuffer.Outline( ii );
826
827 if( outline.IsClosed() )
828 continue;
829
830 if( outline.PointCount() < 3 )
831 continue;
832
833 outline.SetClosed( true );
834 }
835
836 break;
837
838 case SHAPE_T::ELLIPSE:
839 {
840 // Hatching only applies to closed, fillable shapes.
843 chain.SetClosed( true );
844 shapeBuffer.AddOutline( chain );
845 break;
846 }
847
848 default:
850 return;
851 }
852
853 shapeBuffer.ClearArcs();
854
855 // Clear cached hatching only after all validation passes.
856 // This prevents flickering when early returns would otherwise leave empty hatching.
858 hatchLines().clear();
859
860 BOX2I extents = shapeBuffer.BBox();
861 int majorAxis = std::max( extents.GetWidth(), extents.GetHeight() );
862
863 if( majorAxis / spacing > 100 )
864 spacing = majorAxis / 100;
865
867
868 if( !knockouts.IsEmpty() )
869 {
870 shapeBuffer.BooleanSubtract( knockouts );
871 shapeBuffer.Fracture();
872 }
873
874 // Generate hatch lines for stroke-based rendering. All hatch types use line segments.
875 std::vector<SEG> hatchSegs = shapeBuffer.GenerateHatchLines( slopes, spacing, -1 );
876 hatchLines() = hatchSegs;
877
878 // Also generate polygon representation for exports, 3D viewer, and hit testing
880 {
881 for( const SEG& seg : hatchSegs )
882 {
883 // We don't really need the rounded ends at all, so don't spend any extra time on them
884 int maxError = lineWidth;
885
886 TransformOvalToPolygon( hatching(), seg.A, seg.B, lineWidth, maxError,
887 ERROR_INSIDE );
888 }
889
890 hatching().Fracture();
891 m_hatchingDirty = false;
892 }
893 else
894 {
895 // Generate a grid of holes for a cross-hatch polygon representation.
896 // This is used for exports, 3D viewer, and hit testing.
897
898 int gridsize = spacing;
899 int hole_size = gridsize - GetHatchLineWidth();
900
901 hatching() = shapeBuffer.CloneDropTriangulation();
903
904 // Build hole shape
905 SHAPE_LINE_CHAIN hole_base;
906 VECTOR2I corner( 0, 0 );;
907 hole_base.Append( corner );
908 corner.x += hole_size;
909 hole_base.Append( corner );
910 corner.y += hole_size;
911 hole_base.Append( corner );
912 corner.x = 0;
913 hole_base.Append( corner );
914 hole_base.SetClosed( true );
915
916 // Build holes
917 BOX2I bbox = hatching().BBox( 0 );
918 SHAPE_POLY_SET holes;
919
920 int x_offset = bbox.GetX() - ( bbox.GetX() ) % gridsize - gridsize;
921 int y_offset = bbox.GetY() - ( bbox.GetY() ) % gridsize - gridsize;
922
923 for( int xx = x_offset; xx <= bbox.GetRight(); xx += gridsize )
924 {
925 for( int yy = y_offset; yy <= bbox.GetBottom(); yy += gridsize )
926 {
927 SHAPE_LINE_CHAIN hole( hole_base );
928 hole.Move( VECTOR2I( xx, yy ) );
929 holes.AddOutline( hole );
930 }
931 }
932
933 hatching().BooleanSubtract( holes );
934 hatching().Fracture();
935
936 // Must re-rotate after Fracture(). Clipper struggles mightily with fracturing
937 // 45-degree holes.
939
940 if( !knockouts.IsEmpty() )
941 {
942 hatching().BooleanSubtract( knockouts );
943 hatching().Fracture();
944 }
945
946 m_hatchingDirty = false;
947 }
948}
949
950
951void EDA_SHAPE::move( const VECTOR2I& aMoveVector )
952{
953 switch ( m_shape )
954 {
955 case SHAPE_T::ARC:
956 m_arcCenter += aMoveVector;
957 m_arcMidData.center += aMoveVector;
958 m_arcMidData.start += aMoveVector;
959 m_arcMidData.end += aMoveVector;
960 m_arcMidData.mid += aMoveVector;
962
963 case SHAPE_T::SEGMENT:
965 case SHAPE_T::CIRCLE:
966 m_start += aMoveVector;
967 m_end += aMoveVector;
968 break;
969
970 case SHAPE_T::POLY:
971 GetPolyShape().Move( aMoveVector );
972 break;
973
974 case SHAPE_T::BEZIER:
975 m_start += aMoveVector;
976 m_end += aMoveVector;
977 m_bezierC1 += aMoveVector;
978 m_bezierC2 += aMoveVector;
979
980 for( VECTOR2I& pt : m_bezierPoints )
981 pt += aMoveVector;
982
983 break;
984
985 case SHAPE_T::ELLIPSE:
987 m_ellipse.Center += aMoveVector;
988 m_start += aMoveVector;
989 m_end += aMoveVector;
990 break;
991
992 default:
994 break;
995 }
996
997 // Translate the cached hatch geometry instead of leaving it stale. The hatch pattern is
998 // invariant under translation, so shifting line endpoints is sufficient and keeps the
999 // display correct during interactive moves without hitting GenerateHatchLines().
1000 if( m_hatchingCache )
1001 {
1002 for( SEG& seg : m_hatchingCache->hatchLines )
1003 {
1004 seg.A += aMoveVector;
1005 seg.B += aMoveVector;
1006 }
1007
1008 m_hatchingCache->hatching.Move( aMoveVector );
1009 }
1010
1011 m_hatchingDirty = true;
1012}
1013
1014
1015void EDA_SHAPE::scale( double aScale )
1016{
1017 auto scalePt =
1018 [&]( VECTOR2I& pt )
1019 {
1020 pt.x = KiROUND( pt.x * aScale );
1021 pt.y = KiROUND( pt.y * aScale );
1022 };
1023
1024 switch( m_shape )
1025 {
1026 case SHAPE_T::ARC:
1027 scalePt( m_arcCenter );
1029
1030 case SHAPE_T::SEGMENT:
1031 case SHAPE_T::RECTANGLE:
1032 case SHAPE_T::CIRCLE:
1033 scalePt( m_start );
1034 scalePt( m_end );
1035 break;
1036
1037 case SHAPE_T::POLY: // polygon
1038 {
1039 std::vector<VECTOR2I> pts;
1040
1041 for( int ii = 0; ii < GetPolyShape().OutlineCount(); ++ ii )
1042 {
1043 for( const VECTOR2I& pt : GetPolyShape().Outline( ii ).CPoints() )
1044 {
1045 pts.emplace_back( pt );
1046 scalePt( pts.back() );
1047 }
1048 }
1049
1050 SetPolyPoints( pts );
1051 }
1052 break;
1053
1054 case SHAPE_T::BEZIER:
1055 scalePt( m_start );
1056 scalePt( m_end );
1057 scalePt( m_bezierC1 );
1058 scalePt( m_bezierC2 );
1060 break;
1061
1062 case SHAPE_T::ELLIPSE:
1064 scalePt( m_ellipse.Center );
1065 m_ellipse.MajorRadius = KiROUND( std::abs( m_ellipse.MajorRadius * aScale ) );
1066 m_ellipse.MinorRadius = KiROUND( std::abs( m_ellipse.MinorRadius * aScale ) );
1068 break;
1069
1070 default:
1072 break;
1073 }
1074
1075 m_hatchingDirty = true;
1076}
1077
1078
1079void EDA_SHAPE::rotate( const VECTOR2I& aRotCentre, const EDA_ANGLE& aAngle )
1080{
1081 switch( m_shape )
1082 {
1083 case SHAPE_T::SEGMENT:
1084 case SHAPE_T::CIRCLE:
1085 RotatePoint( m_start, aRotCentre, aAngle );
1086 RotatePoint( m_end, aRotCentre, aAngle );
1087 break;
1088
1089 case SHAPE_T::ARC:
1090 RotatePoint( m_start, aRotCentre, aAngle );
1091 RotatePoint( m_end, aRotCentre, aAngle );
1092 RotatePoint( m_arcCenter, aRotCentre, aAngle );
1093 RotatePoint( m_arcMidData.start, aRotCentre, aAngle );
1094 RotatePoint( m_arcMidData.end, aRotCentre, aAngle );
1095 RotatePoint( m_arcMidData.mid, aRotCentre, aAngle );
1096 RotatePoint( m_arcMidData.center, aRotCentre, aAngle );
1097 break;
1098
1099 case SHAPE_T::RECTANGLE:
1100 if( aAngle.IsCardinal() )
1101 {
1102 RotatePoint( m_start, aRotCentre, aAngle );
1103 RotatePoint( m_end, aRotCentre, aAngle );
1104 }
1105 else
1106 {
1107 // Convert non-cardinally-rotated rect to a polygon.
1111 GetPolyShape().Rotate( aAngle, aRotCentre );
1112 }
1113
1114 break;
1115
1116 case SHAPE_T::POLY:
1117 GetPolyShape().Rotate( aAngle, aRotCentre );
1118 break;
1119
1120 case SHAPE_T::BEZIER:
1121 RotatePoint( m_start, aRotCentre, aAngle );
1122 RotatePoint( m_end, aRotCentre, aAngle );
1123 RotatePoint( m_bezierC1, aRotCentre, aAngle );
1124 RotatePoint( m_bezierC2, aRotCentre, aAngle );
1125
1126 for( VECTOR2I& pt : m_bezierPoints )
1127 RotatePoint( pt, aRotCentre, aAngle);
1128
1129 break;
1130
1131 case SHAPE_T::ELLIPSE:
1133 RotatePoint( m_ellipse.Center, aRotCentre, aAngle );
1134
1135 // Ellipse rotation is the CCW angle of the major axis in standard math
1136 // coordinates (Y-up). RotatePoint uses KiCad's Y-down screen convention,
1137 // so a positive aAngle rotates visually CCW on screen but corresponds to
1138 // a negative rotation in the math frame. Hence -= rather than +=.
1139 m_ellipse.Rotation -= aAngle;
1141 break;
1142
1143 default:
1145 break;
1146 }
1147
1148 m_hatchingDirty = true;
1149}
1150
1151
1152void EDA_SHAPE::flip( const VECTOR2I& aCentre, FLIP_DIRECTION aFlipDirection )
1153{
1154 switch ( m_shape )
1155 {
1156 case SHAPE_T::SEGMENT:
1157 case SHAPE_T::RECTANGLE:
1158 MIRROR( m_start, aCentre, aFlipDirection );
1159 MIRROR( m_end, aCentre, aFlipDirection );
1160 break;
1161
1162 case SHAPE_T::CIRCLE:
1163 MIRROR( m_start, aCentre, aFlipDirection );
1164 MIRROR( m_end, aCentre, aFlipDirection );
1165 break;
1166
1167 case SHAPE_T::ARC:
1168 MIRROR( m_start, aCentre, aFlipDirection );
1169 MIRROR( m_end, aCentre, aFlipDirection );
1170 MIRROR( m_arcCenter, aCentre, aFlipDirection );
1171
1172 std::swap( m_start, m_end );
1173 break;
1174
1175 case SHAPE_T::POLY:
1176 GetPolyShape().Mirror( aCentre, aFlipDirection );
1177 break;
1178
1179 case SHAPE_T::BEZIER:
1180 MIRROR( m_start, aCentre, aFlipDirection );
1181 MIRROR( m_end, aCentre, aFlipDirection );
1182 MIRROR( m_bezierC1, aCentre, aFlipDirection );
1183 MIRROR( m_bezierC2, aCentre, aFlipDirection );
1184
1186 break;
1187
1188 case SHAPE_T::ELLIPSE:
1190 m_ellipse.Mirror( aCentre, aFlipDirection );
1192 break;
1193
1194 default:
1196 break;
1197 }
1198
1199 m_hatchingDirty = true;
1200}
1201
1202
1204{
1205 // Has meaning only for SHAPE_T::BEZIER
1206 if( m_shape != SHAPE_T::BEZIER )
1207 {
1208 m_bezierPoints.clear();
1209 return;
1210 }
1211
1212 // Rebuild the m_BezierPoints vertex list that approximate the Bezier curve
1214}
1215
1216
1217const std::vector<VECTOR2I> EDA_SHAPE::buildBezierToSegmentsPointsList( int aMaxError ) const
1218{
1219 std::vector<VECTOR2I> bezierPoints;
1220
1221 // Rebuild the m_BezierPoints vertex list that approximate the Bezier curve
1222 std::vector<VECTOR2I> ctrlPoints = { m_start, m_bezierC1, m_bezierC2, m_end };
1223 BEZIER_POLY converter( ctrlPoints );
1224 converter.GetPoly( bezierPoints, aMaxError );
1225
1226 return bezierPoints;
1227}
1228
1229
1231{
1233 return SHAPE_ELLIPSE( m_ellipse.Center, m_ellipse.MajorRadius, m_ellipse.MinorRadius, m_ellipse.Rotation,
1234 m_ellipse.StartAngle, m_ellipse.EndAngle );
1235
1236 return SHAPE_ELLIPSE( m_ellipse.Center, m_ellipse.MajorRadius, m_ellipse.MinorRadius, m_ellipse.Rotation );
1237}
1238
1239
1241{
1242 if( m_editState != 0 )
1243 return;
1244
1245 if( m_shape == SHAPE_T::ELLIPSE )
1246 {
1247 const double phi = m_ellipse.Rotation.AsRadians();
1248 m_start = m_ellipse.Center;
1249 m_end = m_start
1250 + VECTOR2I( KiROUND( m_ellipse.MajorRadius * std::cos( phi ) ),
1251 KiROUND( m_ellipse.MajorRadius * std::sin( phi ) ) );
1252 return;
1253 }
1254
1256 return;
1257
1258 m_arcCenter = m_ellipse.Center;
1259
1260 const double a = m_ellipse.MajorRadius;
1261 const double b = m_ellipse.MinorRadius;
1262 const double phi = m_ellipse.Rotation.AsRadians();
1263 const double cosPhi = std::cos( phi );
1264 const double sinPhi = std::sin( phi );
1265 const VECTOR2I c = m_ellipse.Center;
1266
1267 auto eval = [&]( double theta ) -> VECTOR2I
1268 {
1269 const double lx = a * std::cos( theta );
1270 const double ly = b * std::sin( theta );
1271 return c + VECTOR2I( KiROUND( lx * cosPhi - ly * sinPhi ), KiROUND( lx * sinPhi + ly * cosPhi ) );
1272 };
1273
1274 m_start = eval( m_ellipse.StartAngle.AsRadians() );
1275 m_end = eval( m_ellipse.EndAngle.AsRadians() );
1276}
1277
1278
1280{
1281 switch( m_shape )
1282 {
1283 case SHAPE_T::ARC:
1284 return m_arcCenter;
1285
1286 case SHAPE_T::CIRCLE:
1287 return m_start;
1288
1289 case SHAPE_T::SEGMENT:
1290 // Midpoint of the line
1291 return ( m_start + m_end ) / 2;
1292
1293 case SHAPE_T::POLY:
1294 case SHAPE_T::RECTANGLE:
1295 case SHAPE_T::BEZIER:
1296 return getBoundingBox().Centre();
1297
1298 case SHAPE_T::ELLIPSE:
1299 case SHAPE_T::ELLIPSE_ARC: return m_ellipse.Center;
1300
1301 default:
1303 return VECTOR2I();
1304 }
1305}
1306
1307
1308void EDA_SHAPE::SetCenter( const VECTOR2I& aCenter )
1309{
1310 switch( m_shape )
1311 {
1312 case SHAPE_T::ARC:
1313 m_arcCenter = aCenter;
1314 break;
1315
1316 case SHAPE_T::CIRCLE:
1317 {
1318 // Route through SetStart / SetEnd so subclasses sync lib coords.
1319 const VECTOR2I delta = aCenter - m_start;
1320 SetEnd( m_end + delta );
1321 SetStart( aCenter );
1322 m_hatchingDirty = true;
1323 break;
1324 }
1325
1326 case SHAPE_T::ELLIPSE:
1328 m_ellipse.Center = aCenter;
1329 m_hatchingDirty = true;
1331 break;
1332
1333 default:
1335 }
1336}
1337
1338
1340{
1341 // If none of the input data have changed since we loaded the arc, keep the original mid point data
1342 // to minimize churn
1343 if( m_arcMidData.start == m_start && m_arcMidData.end == m_end && m_arcMidData.center == m_arcCenter )
1344 return m_arcMidData.mid;
1345
1346 VECTOR2I mid = m_start;
1347 RotatePoint( mid, m_arcCenter, -GetArcAngle() / 2.0 );
1348 return mid;
1349}
1350
1351
1352void EDA_SHAPE::CalcArcAngles( EDA_ANGLE& aStartAngle, EDA_ANGLE& aEndAngle ) const
1353{
1354 VECTOR2D startRadial( GetStart() - getCenter() );
1355 VECTOR2D endRadial( GetEnd() - getCenter() );
1356
1357 aStartAngle = EDA_ANGLE( startRadial );
1358 aEndAngle = EDA_ANGLE( endRadial );
1359
1360 if( aEndAngle == aStartAngle )
1361 aEndAngle = aStartAngle + ANGLE_360; // ring, not null
1362
1363 while( aEndAngle < aStartAngle )
1364 aEndAngle += ANGLE_360;
1365}
1366
1367
1369{
1370 double radius = 0.0;
1371
1372 switch( m_shape )
1373 {
1374 case SHAPE_T::ARC:
1375 radius = m_arcCenter.Distance( m_start );
1376 break;
1377
1378 case SHAPE_T::CIRCLE:
1379 radius = m_start.Distance( m_end );
1380 break;
1381
1382 default:
1384 }
1385
1386 // don't allow degenerate circles/arcs
1387 if( radius > (double) INT_MAX / 2.0 )
1388 radius = (double) INT_MAX / 2.0;
1389
1390 return std::max( 1, KiROUND( radius ) );
1391}
1392
1393
1394void EDA_SHAPE::SetCachedArcData( const VECTOR2I& aStart, const VECTOR2I& aMid,
1395 const VECTOR2I& aEnd, const VECTOR2I& aCenter )
1396{
1397 m_arcMidData.start = aStart;
1398 m_arcMidData.end = aEnd;
1399 m_arcMidData.center = aCenter;
1400 m_arcMidData.mid = aMid;
1401}
1402
1403
1404void EDA_SHAPE::SetArcGeometry( const VECTOR2I& aStart, const VECTOR2I& aMid, const VECTOR2I& aEnd )
1405{
1406 m_arcMidData = {};
1407 m_start = aStart;
1408 m_end = aEnd;
1409 m_arcCenter = CalcArcCenter( aStart, aMid, aEnd );
1410 VECTOR2I new_mid = GetArcMid();
1411
1412 m_endsSwapped = false;
1413
1414 /*
1415 * If the input winding doesn't match our internal winding, the calculated midpoint will end
1416 * up on the other side of the arc. In this case, we need to flip the start/end points and
1417 * flag this change for the system.
1418 */
1419 VECTOR2D dist( new_mid - aMid );
1420 VECTOR2D dist2( new_mid - m_arcCenter );
1421
1422 if( dist.SquaredEuclideanNorm() > dist2.SquaredEuclideanNorm() )
1423 {
1424 std::swap( m_start, m_end );
1425 m_endsSwapped = true;
1426 }
1427
1428 // Watch the ordering here. GetArcMid above needs to be called prior to initializing the
1429 // m_arcMidData structure in order to ensure we get the calculated variant, not the cached.
1430 // The cache is keyed on the stored ends, so it must follow any swap or GetArcMid never hits it
1432}
1433
1434
1436{
1437 EDA_ANGLE angle( atan2( static_cast<double>( GetStart().y - GetEnd().y ),
1438 static_cast<double>( GetEnd().x - GetStart().x ) ), RADIANS_T );
1439
1440 return angle;
1441}
1442
1443
1445{
1446 EDA_ANGLE startAngle;
1447 EDA_ANGLE endAngle;
1448
1449 CalcArcAngles( startAngle, endAngle );
1450
1451 return endAngle - startAngle;
1452}
1453
1454
1456{
1457 if( m_shape == SHAPE_T::ARC )
1458 {
1459 VECTOR2D mid = GetArcMid();
1460
1461 double orient = ( mid.x - m_start.x ) * ( m_end.y - m_start.y )
1462 - ( mid.y - m_start.y ) * ( m_end.x - m_start.x );
1463
1464 return orient < 0;
1465 }
1466
1468 return false;
1469}
1470
1471
1472void EDA_SHAPE::SetArcAngleAndEnd( const EDA_ANGLE& aAngle, bool aCheckNegativeAngle )
1473{
1474 EDA_ANGLE angle( aAngle );
1475
1476 m_end = m_start;
1478
1479 if( aCheckNegativeAngle && aAngle < ANGLE_0 )
1480 {
1481 std::swap( m_start, m_end );
1482 m_endsSwapped = true;
1483 }
1484}
1485
1486
1487wxString EDA_SHAPE::getFriendlyName( FRAME_T aFrameType ) const
1488{
1489 if( IsProxyItem() )
1490 {
1491 switch( m_shape )
1492 {
1493 case SHAPE_T::RECTANGLE: return _( "Pad Number Box" );
1494 case SHAPE_T::SEGMENT: return _( "Thermal Spoke Template" );
1495 default: return _( "Unrecognized" );
1496 }
1497 }
1498 else if( aFrameType == FRAME_SCH_SYMBOL_EDITOR && m_shape == SHAPE_T::POLY )
1499 {
1500 return _( "Connected Lines" );
1501 }
1502 else
1503 {
1504 switch( m_shape )
1505 {
1506 case SHAPE_T::CIRCLE: return _( "Circle" );
1507 case SHAPE_T::ARC: return _( "Arc" );
1508 case SHAPE_T::BEZIER: return _( "Curve" );
1509 case SHAPE_T::POLY: return _( "Polygon" );
1510 case SHAPE_T::RECTANGLE: return _( "Rectangle" );
1511 case SHAPE_T::SEGMENT: return _( "Segment" );
1512 case SHAPE_T::ELLIPSE: return _( "Ellipse" );
1513 case SHAPE_T::ELLIPSE_ARC: return _( "Elliptical Arc" );
1514 default: return _( "Unrecognized" );
1515 }
1516 }
1517}
1518
1519
1520void EDA_SHAPE::ShapeGetMsgPanelInfo( EDA_DRAW_FRAME* aFrame, std::vector<MSG_PANEL_ITEM>& aList )
1521{
1522 wxString msg;
1523
1524 wxString shape = _( "Shape" );
1525 aList.emplace_back( shape, getFriendlyName( aFrame->GetFrameType() ) );
1526
1527 switch( m_shape )
1528 {
1529 case SHAPE_T::CIRCLE:
1530 aList.emplace_back( _( "Radius" ), aFrame->MessageTextFromValue( GetRadius() ) );
1531 break;
1532
1533 case SHAPE_T::ARC:
1534 aList.emplace_back( _( "Length" ), aFrame->MessageTextFromValue( GetLength() ) );
1535
1537 aList.emplace_back( _( "Angle" ), msg );
1538
1539 aList.emplace_back( _( "Radius" ), aFrame->MessageTextFromValue( GetRadius() ) );
1540 break;
1541
1542 case SHAPE_T::BEZIER:
1543 aList.emplace_back( _( "Length" ), aFrame->MessageTextFromValue( GetLength() ) );
1544 break;
1545
1546 case SHAPE_T::ELLIPSE:
1547 aList.emplace_back( _( "Length" ), aFrame->MessageTextFromValue( GetLength() ) );
1548 aList.emplace_back( _( "Major Radius" ), aFrame->MessageTextFromValue( GetEllipseMajorRadius() ) );
1549 aList.emplace_back( _( "Minor Radius" ), aFrame->MessageTextFromValue( GetEllipseMinorRadius() ) );
1550 aList.emplace_back( _( "Rotation" ), EDA_UNIT_UTILS::UI::MessageTextFromValue( GetEllipseRotation() ) );
1551 break;
1552
1554 aList.emplace_back( _( "Length" ), aFrame->MessageTextFromValue( GetLength() ) );
1555 aList.emplace_back( _( "Major Radius" ), aFrame->MessageTextFromValue( GetEllipseMajorRadius() ) );
1556 aList.emplace_back( _( "Minor Radius" ), aFrame->MessageTextFromValue( GetEllipseMinorRadius() ) );
1557 aList.emplace_back( _( "Rotation" ), EDA_UNIT_UTILS::UI::MessageTextFromValue( GetEllipseRotation() ) );
1558 aList.emplace_back( _( "Start Angle" ), EDA_UNIT_UTILS::UI::MessageTextFromValue( GetEllipseStartAngle() ) );
1559 aList.emplace_back( _( "End Angle" ), EDA_UNIT_UTILS::UI::MessageTextFromValue( GetEllipseEndAngle() ) );
1560 break;
1561
1562 case SHAPE_T::POLY:
1563 {
1564 int pointCount = 0;
1565
1566 if( GetPolyShape().OutlineCount() > 0 )
1567 pointCount = GetPolyShape().Outline( 0 ).PointCount();
1568
1569 msg.Printf( wxS( "%d" ), pointCount );
1570 aList.emplace_back( _( "Points" ), msg );
1571 break;
1572 }
1573 case SHAPE_T::RECTANGLE:
1574 aList.emplace_back( _( "Width" ), aFrame->MessageTextFromValue( std::abs( GetEnd().x - GetStart().x ) ) );
1575 aList.emplace_back( _( "Height" ), aFrame->MessageTextFromValue( std::abs( GetEnd().y - GetStart().y ) ) );
1576 break;
1577
1578 case SHAPE_T::SEGMENT:
1579 {
1580 aList.emplace_back( _( "Length" ), aFrame->MessageTextFromValue( GetStart().Distance( GetEnd() ) ));
1581
1582 // angle counter-clockwise from 3'o-clock
1583 EDA_ANGLE angle( atan2( (double)( GetStart().y - GetEnd().y ), (double)( GetEnd().x - GetStart().x ) ),
1584 RADIANS_T );
1585 aList.emplace_back( _( "Angle" ), EDA_UNIT_UTILS::UI::MessageTextFromValue( angle ) );
1586 break;
1587 }
1588
1589 default:
1590 break;
1591 }
1592
1593 m_stroke.GetMsgPanelInfo( aFrame, aList );
1594}
1595
1596
1598{
1599 BOX2I bbox;
1600
1601 switch( m_shape )
1602 {
1603 case SHAPE_T::RECTANGLE:
1604 for( VECTOR2I& pt : GetRectCorners() )
1605 bbox.Merge( pt );
1606
1607 break;
1608
1609 case SHAPE_T::SEGMENT:
1610 bbox.SetOrigin( GetStart() );
1611 bbox.SetEnd( GetEnd() );
1612 break;
1613
1614 case SHAPE_T::CIRCLE:
1615 bbox.SetOrigin( GetStart() );
1616 bbox.Inflate( GetRadius() );
1617 break;
1618
1619 case SHAPE_T::ARC:
1620 computeArcBBox( bbox );
1621 break;
1622
1623 case SHAPE_T::ELLIPSE:
1624 case SHAPE_T::ELLIPSE_ARC: bbox = buildShapeEllipse().BBox( 0 ); break;
1625
1626 case SHAPE_T::POLY:
1627 if( GetPolyShape().IsEmpty() )
1628 break;
1629
1630 for( auto iter = GetPolyShape().CIterate(); iter; iter++ )
1631 bbox.Merge( *iter );
1632
1633 break;
1634
1635 case SHAPE_T::BEZIER:
1636 // Bezier BBoxes are not trivial to compute, so we approximate it by
1637 // using the bounding box of the curve (not control!) points.
1638 for( const VECTOR2I& pt : m_bezierPoints )
1639 bbox.Merge( pt );
1640
1641 break;
1642
1643 default:
1645 break;
1646 }
1647
1648 bbox.Inflate( std::max( 0, GetWidth() ) / 2 );
1649 bbox.Normalize();
1650
1651 return bbox;
1652}
1653
1654
1655static bool hasLineEnding( const LINE_ENDING& aStartEnding, const LINE_ENDING& aEndEnding );
1656
1657
1658bool EDA_SHAPE::hitTest( const VECTOR2I& aPosition, int aAccuracy ) const
1659{
1661 {
1663 const SHAPE& hitShape = shape;
1664
1665 return hitShape.Collide( aPosition, std::max( 0, aAccuracy ) );
1666 }
1667
1668 double maxdist = aAccuracy;
1669
1670 if( GetWidth() > 0 )
1671 maxdist += GetWidth() / 2.0;
1672
1673 switch( m_shape )
1674 {
1675 case SHAPE_T::CIRCLE:
1676 {
1677 double radius = GetRadius();
1678 double dist = aPosition.Distance( getCenter() );
1679
1680 if( IsFilledForHitTesting() )
1681 return dist <= radius + maxdist; // Filled circle hit-test
1682 else if( abs( radius - dist ) <= maxdist ) // Ring hit-test
1683 return true;
1684
1685 if( IsHatchedFill() && GetHatching().Collide( aPosition, maxdist ) )
1686 return true;
1687
1688 return false;
1689 }
1690
1691 case SHAPE_T::ARC:
1692 {
1693 if( aPosition.Distance( m_start ) <= maxdist )
1694 return true;
1695
1696 if( aPosition.Distance( m_end ) <= maxdist )
1697 return true;
1698
1699 double radius = GetRadius();
1700 VECTOR2D relPos( VECTOR2D( aPosition ) - getCenter() );
1701 double dist = relPos.EuclideanNorm();
1702
1703 if( IsFilledForHitTesting() )
1704 {
1705 // Check distance from arc center
1706 if( dist > radius + maxdist )
1707 return false;
1708 }
1709 else
1710 {
1711 // Check distance from arc circumference
1712 if( abs( radius - dist ) > maxdist )
1713 return false;
1714 }
1715
1716 // Finally, check to see if it's within arc's swept angle.
1717 EDA_ANGLE startAngle;
1718 EDA_ANGLE endAngle;
1719 CalcArcAngles( startAngle, endAngle );
1720
1721 EDA_ANGLE relPosAngle( relPos );
1722
1723 startAngle.Normalize();
1724 endAngle.Normalize();
1725 relPosAngle.Normalize();
1726
1727 if( endAngle > startAngle )
1728 return relPosAngle >= startAngle && relPosAngle <= endAngle;
1729 else
1730 return relPosAngle >= startAngle || relPosAngle <= endAngle;
1731 }
1732
1733 case SHAPE_T::BEZIER:
1734 {
1735 const std::vector<VECTOR2I>* pts = &m_bezierPoints;
1736 std::vector<VECTOR2I> updatedBezierPoints;
1737
1738 if( m_bezierPoints.empty() )
1739 {
1741 converter.GetPoly( updatedBezierPoints, aAccuracy / 2 );
1742 pts = &updatedBezierPoints;
1743 }
1744
1745 for( unsigned int i = 1; i < pts->size(); i++ )
1746 {
1747 if( TestSegmentHit( aPosition, ( *pts )[i - 1], ( *pts )[i], maxdist ) )
1748 return true;
1749 }
1750
1751 return false;
1752 }
1753 case SHAPE_T::SEGMENT:
1754 return TestSegmentHit( aPosition, GetStart(), GetEnd(), maxdist );
1755
1756 case SHAPE_T::RECTANGLE:
1757 if( IsProxyItem() || IsFilledForHitTesting() ) // Filled rect hit-test
1758 {
1759 SHAPE_POLY_SET poly;
1760 poly.NewOutline();
1761
1762 for( const VECTOR2I& pt : GetRectCorners() )
1763 poly.Append( pt );
1764
1765 return poly.Collide( aPosition, maxdist );
1766 }
1767 else if( m_cornerRadius > 0 )
1768 {
1770 SHAPE_POLY_SET poly;
1771 rr.TransformToPolygon( poly, getMaxError() );
1772
1773 if( poly.CollideEdge( aPosition, nullptr, maxdist ) )
1774 return true;
1775 }
1776 else
1777 {
1778 std::vector<VECTOR2I> pts = GetRectCorners();
1779
1780 if( TestSegmentHit( aPosition, pts[0], pts[1], maxdist )
1781 || TestSegmentHit( aPosition, pts[1], pts[2], maxdist )
1782 || TestSegmentHit( aPosition, pts[2], pts[3], maxdist )
1783 || TestSegmentHit( aPosition, pts[3], pts[0], maxdist ) )
1784 {
1785 return true;
1786 }
1787 }
1788
1789 if( IsHatchedFill() && GetHatching().Collide( aPosition, maxdist ) )
1790 return true;
1791
1792 return false;
1793
1794 case SHAPE_T::POLY:
1795 if( GetPolyShape().OutlineCount() < 1 ) // empty poly
1796 return false;
1797
1798 if( IsFilledForHitTesting() )
1799 {
1800 if( !GetPolyShape().COutline( 0 ).IsClosed() )
1801 {
1802 // Only one outline is expected
1803 SHAPE_LINE_CHAIN copy( GetPolyShape().COutline( 0 ) );
1804 copy.SetClosed( true );
1805 return copy.Collide( aPosition, maxdist );
1806 }
1807 else
1808 {
1809 return GetPolyShape().Collide( aPosition, maxdist );
1810 }
1811 }
1812 else
1813 {
1814 if( GetPolyShape().CollideEdge( aPosition, nullptr, maxdist ) )
1815 return true;
1816
1817 if( IsHatchedFill() && GetHatching().Collide( aPosition, maxdist ) )
1818 return true;
1819
1820 return false;
1821 }
1822
1823 case SHAPE_T::ELLIPSE:
1825 {
1827
1828 const double maxdistSq = maxdist * maxdist;
1829
1831 {
1832 // Filled closed ellipse
1833 if( static_cast<double>( e.SquaredDistance( aPosition, false ) ) <= maxdistSq )
1834 return true;
1835 }
1836 else
1837 {
1838 // Unfilled ring or arc
1839 if( static_cast<double>( e.SquaredDistance( aPosition, true ) ) <= maxdistSq )
1840 return true;
1841 }
1842
1843 if( IsHatchedFill() && GetHatching().Collide( aPosition, maxdist ) )
1844 return true;
1845
1846 return false;
1847 }
1848
1849 default:
1851 return false;
1852 }
1853}
1854
1855
1856bool EDA_SHAPE::hitTest( const BOX2I& aRect, bool aContained, int aAccuracy ) const
1857{
1858 BOX2I rect = aRect;
1859 rect.Normalize();
1860 rect.Inflate( aAccuracy );
1861
1863 {
1864 SHAPE_LINE_CHAIN selection = KIGEOM::BoxToLineChain( rect );
1866
1867 return KIGEOM::ShapeHitTest( selection, shape, aContained );
1868 }
1869
1870 BOX2I bbox = getBoundingBox();
1871
1872 auto checkOutline =
1873 [&]( const SHAPE_LINE_CHAIN& outline )
1874 {
1875 int count = (int) outline.GetPointCount();
1876
1877 for( int ii = 0; ii < count; ii++ )
1878 {
1879 VECTOR2I vertex = outline.GetPoint( ii );
1880
1881 // Test if the point is within rect
1882 if( rect.Contains( vertex ) )
1883 return true;
1884
1885 if( ii + 1 < count )
1886 {
1887 VECTOR2I vertexNext = outline.GetPoint( ii + 1 );
1888
1889 // Test if this edge intersects rect
1890 if( rect.Intersects( vertex, vertexNext ) )
1891 return true;
1892 }
1893 else if( outline.IsClosed() )
1894 {
1895 VECTOR2I vertexNext = outline.GetPoint( 0 );
1896
1897 // Test if this edge intersects rect
1898 if( rect.Intersects( vertex, vertexNext ) )
1899 return true;
1900 }
1901 }
1902
1903 return false;
1904 };
1905
1906 switch( m_shape )
1907 {
1908 case SHAPE_T::CIRCLE:
1909 // Test if area intersects or contains the circle:
1910 if( aContained )
1911 {
1912 return rect.Contains( bbox );
1913 }
1914 else
1915 {
1916 // If the rectangle does not intersect the bounding box, this is a much quicker test
1917 if( !rect.Intersects( bbox ) )
1918 return false;
1919 else
1920 return rect.IntersectsCircleEdge( getCenter(), GetRadius(), GetWidth() );
1921 }
1922
1923 case SHAPE_T::ARC:
1924 // Test for full containment of this arc in the rect
1925 if( aContained )
1926 {
1927 return rect.Contains( bbox );
1928 }
1929 // Test if the rect crosses the arc
1930 else
1931 {
1932 if( !rect.Intersects( bbox ) )
1933 return false;
1934
1935 if( IsAnyFill() )
1936 {
1937 return ( rect.Intersects( getCenter(), GetStart() )
1938 || rect.Intersects( getCenter(), GetEnd() )
1939 || rect.IntersectsCircleEdge( getCenter(), GetRadius(), GetWidth() ) );
1940 }
1941 else
1942 {
1943 return rect.IntersectsCircleEdge( getCenter(), GetRadius(), GetWidth() );
1944 }
1945 }
1946
1947 case SHAPE_T::RECTANGLE:
1948 if( aContained )
1949 {
1950 return rect.Contains( bbox );
1951 }
1952 else if( m_cornerRadius > 0 )
1953 {
1955 SHAPE_POLY_SET poly;
1956 rr.TransformToPolygon( poly, getMaxError() );
1957
1958 // Account for the width of the line
1959 rect.Inflate( GetWidth() / 2 );
1960
1961 return checkOutline( poly.Outline( 0 ) );
1962 }
1963 else
1964 {
1965 std::vector<VECTOR2I> pts = GetRectCorners();
1966
1967 // Account for the width of the lines
1968 rect.Inflate( GetWidth() / 2 );
1969 return ( rect.Intersects( pts[0], pts[1] )
1970 || rect.Intersects( pts[1], pts[2] )
1971 || rect.Intersects( pts[2], pts[3] )
1972 || rect.Intersects( pts[3], pts[0] ) );
1973 }
1974
1975 case SHAPE_T::SEGMENT:
1976 if( aContained )
1977 {
1978 return rect.Contains( GetStart() ) && rect.Contains( GetEnd() );
1979 }
1980 else
1981 {
1982 // Account for the width of the line
1983 rect.Inflate( GetWidth() / 2 );
1984 return rect.Intersects( GetStart(), GetEnd() );
1985 }
1986
1987 case SHAPE_T::POLY:
1988 if( aContained )
1989 {
1990 return rect.Contains( bbox );
1991 }
1992 else
1993 {
1994 // Fast test: if rect is outside the polygon bounding box,
1995 // rectangles cannot intersect
1996 if( !rect.Intersects( bbox ) )
1997 return false;
1998
1999 // Account for the width of the line
2000 rect.Inflate( GetWidth() / 2 );
2001
2002 for( int ii = 0; ii < GetPolyShape().OutlineCount(); ++ii )
2003 {
2004 if( checkOutline( GetPolyShape().Outline( ii ) ) )
2005 return true;
2006 }
2007
2008 return false;
2009 }
2010
2011 case SHAPE_T::BEZIER:
2012 if( aContained )
2013 {
2014 return rect.Contains( bbox );
2015 }
2016 else
2017 {
2018 // Fast test: if rect is outside the polygon bounding box,
2019 // rectangles cannot intersect
2020 if( !rect.Intersects( bbox ) )
2021 return false;
2022
2023 // Account for the width of the line
2024 rect.Inflate( GetWidth() / 2 );
2025 const std::vector<VECTOR2I>* pts = &m_bezierPoints;
2026 std::vector<VECTOR2I> updatedBezierPoints;
2027
2028 if( m_bezierPoints.empty() )
2029 {
2031 converter.GetPoly( updatedBezierPoints, aAccuracy / 2 );
2032 pts = &updatedBezierPoints;
2033 }
2034
2035 for( unsigned ii = 1; ii < pts->size(); ii++ )
2036 {
2037 VECTOR2I vertex = ( *pts )[ii - 1];
2038 VECTOR2I vertexNext = ( *pts )[ii];
2039
2040 // Test if the point is within rect
2041 if( rect.Contains( vertex ) )
2042 return true;
2043
2044 // Test if this edge intersects rect
2045 if( rect.Intersects( vertex, vertexNext ) )
2046 return true;
2047 }
2048
2049 return false;
2050 }
2051
2052 case SHAPE_T::ELLIPSE:
2054 {
2055 if( aContained )
2056 return rect.Contains( bbox );
2057
2058 if( !rect.Intersects( bbox ) )
2059 return false;
2060
2062
2063 const int tessError = std::max( 1, aAccuracy / 2 );
2064 const SHAPE_LINE_CHAIN chain = e.ConvertToPolyline( tessError );
2065
2066 // Account for the width of the line
2067 rect.Inflate( GetWidth() / 2 );
2068 return checkOutline( chain );
2069 }
2070
2071 default:
2073 return false;
2074 }
2075}
2076
2077
2078bool EDA_SHAPE::hitTest( const SHAPE_LINE_CHAIN& aPoly, bool aContained ) const
2079{
2082 : MakeEffectiveShapes() );
2083
2084 return KIGEOM::ShapeHitTest( aPoly, shape, aContained );
2085}
2086
2087
2088std::vector<VECTOR2I> EDA_SHAPE::GetRectCorners() const
2089{
2090 std::vector<VECTOR2I> pts;
2091 VECTOR2I topLeft = GetStart();
2092 VECTOR2I botRight = GetEnd();
2093
2094 pts.emplace_back( topLeft );
2095 pts.emplace_back( botRight.x, topLeft.y );
2096 pts.emplace_back( botRight );
2097 pts.emplace_back( topLeft.x, botRight.y );
2098
2099 return pts;
2100}
2101
2102
2103std::vector<VECTOR2I> EDA_SHAPE::GetCornersInSequence( EDA_ANGLE angle ) const
2104{
2105 std::vector<VECTOR2I> pts;
2106
2107 angle.Normalize();
2108
2109 BOX2I bbox = getBoundingBox();
2110 bbox.Normalize();
2111
2112 if( angle.IsCardinal() )
2113 {
2114 if( angle == ANGLE_0 )
2115 {
2116 pts.emplace_back( VECTOR2I( bbox.GetLeft(), bbox.GetTop() ) );
2117 pts.emplace_back( VECTOR2I( bbox.GetRight(), bbox.GetTop() ) );
2118 pts.emplace_back( VECTOR2I( bbox.GetRight(), bbox.GetBottom() ) );
2119 pts.emplace_back( VECTOR2I( bbox.GetLeft(), bbox.GetBottom() ) );
2120 }
2121 else if( angle == ANGLE_90 )
2122 {
2123 pts.emplace_back( VECTOR2I( bbox.GetLeft(), bbox.GetBottom() ) );
2124 pts.emplace_back( VECTOR2I( bbox.GetLeft(), bbox.GetTop() ) );
2125 pts.emplace_back( VECTOR2I( bbox.GetRight(), bbox.GetTop() ) );
2126 pts.emplace_back( VECTOR2I( bbox.GetRight(), bbox.GetBottom() ) );
2127 }
2128 else if( angle == ANGLE_180 )
2129 {
2130 pts.emplace_back( VECTOR2I( bbox.GetRight(), bbox.GetBottom() ) );
2131 pts.emplace_back( VECTOR2I( bbox.GetLeft(), bbox.GetBottom() ) );
2132 pts.emplace_back( VECTOR2I( bbox.GetLeft(), bbox.GetTop() ) );
2133 pts.emplace_back( VECTOR2I( bbox.GetRight(), bbox.GetTop() ) );
2134 }
2135 else if( angle == ANGLE_270 )
2136 {
2137 pts.emplace_back( VECTOR2I( bbox.GetRight(), bbox.GetTop() ) );
2138 pts.emplace_back( VECTOR2I( bbox.GetRight(), bbox.GetBottom() ) );
2139 pts.emplace_back( VECTOR2I( bbox.GetLeft(), bbox.GetBottom() ) );
2140 pts.emplace_back( VECTOR2I( bbox.GetLeft(), bbox.GetTop() ) );
2141 }
2142 }
2143 else if( m_shape == SHAPE_T::RECTANGLE )
2144 {
2145 // Axis-aligned rectangle with non-cardinal rotation (used by textboxes).
2146 VECTOR2I center = bbox.GetCenter();
2147
2148 VECTOR2I tl( bbox.GetLeft(), bbox.GetTop() );
2149 VECTOR2I tr( bbox.GetRight(), bbox.GetTop() );
2150 VECTOR2I br( bbox.GetRight(), bbox.GetBottom() );
2151 VECTOR2I bl( bbox.GetLeft(), bbox.GetBottom() );
2152
2153 RotatePoint( tl, center, angle );
2154 RotatePoint( tr, center, angle );
2155 RotatePoint( br, center, angle );
2156 RotatePoint( bl, center, angle );
2157
2158 pts.emplace_back( tl );
2159 pts.emplace_back( tr );
2160 pts.emplace_back( br );
2161 pts.emplace_back( bl );
2162 }
2163 else
2164 {
2165 // This function was originally located in pcb_textbox.cpp and was later moved to eda_shape.cpp.
2166 // As a result of this move, access to getCorners was lost, since it is defined in the PCB_SHAPE
2167 // class within pcb_shape.cpp and is not available in the current context.
2168 //
2169 // Additionally, GetRectCorners() cannot be used here, as it assumes the rectangle is rotated by
2170 // a cardinal angle. In non-cardinal cases, it returns incorrect values (e.g., (0, 0)).
2171 //
2172 // To address this, a portion of the getCorners implementation for SHAPE_T::POLY elements
2173 // has been replicated here to restore the correct behavior.
2174 std::vector<VECTOR2I> corners;
2175
2176 for( int ii = 0; ii < GetPolyShape().OutlineCount(); ++ii )
2177 {
2178 for( const VECTOR2I& pt : GetPolyShape().Outline( ii ).CPoints() )
2179 corners.emplace_back( pt );
2180 }
2181
2182 if( corners.empty() )
2183 return pts;
2184
2185 while( corners.size() < 4 )
2186 corners.emplace_back( corners.back() + VECTOR2I( 10, 10 ) );
2187
2188 VECTOR2I minX = corners[0];
2189 VECTOR2I maxX = corners[0];
2190 VECTOR2I minY = corners[0];
2191 VECTOR2I maxY = corners[0];
2192
2193 for( const VECTOR2I& corner : corners )
2194 {
2195 if( corner.x < minX.x )
2196 minX = corner;
2197
2198 if( corner.x > maxX.x )
2199 maxX = corner;
2200
2201 if( corner.y < minY.y )
2202 minY = corner;
2203
2204 if( corner.y > maxY.y )
2205 maxY = corner;
2206 }
2207
2208 if( angle < ANGLE_90 )
2209 {
2210 pts.emplace_back( minX );
2211 pts.emplace_back( minY );
2212 pts.emplace_back( maxX );
2213 pts.emplace_back( maxY );
2214 }
2215 else if( angle < ANGLE_180 )
2216 {
2217 pts.emplace_back( maxY );
2218 pts.emplace_back( minX );
2219 pts.emplace_back( minY );
2220 pts.emplace_back( maxX );
2221 }
2222 else if( angle < ANGLE_270 )
2223 {
2224 pts.emplace_back( maxX );
2225 pts.emplace_back( maxY );
2226 pts.emplace_back( minX );
2227 pts.emplace_back( minY );
2228 }
2229 else
2230 {
2231 pts.emplace_back( minY );
2232 pts.emplace_back( maxX );
2233 pts.emplace_back( maxY );
2234 pts.emplace_back( minX );
2235 }
2236 }
2237
2238 return pts;
2239}
2240
2241
2243{
2244 // Start, end, and each inflection point the arc crosses will enclose the entire arc.
2245 // Only include the center when filled; it's not necessarily inside the BB of an unfilled
2246 // arc with a small included angle.
2247 aBBox.SetOrigin( m_start );
2248 aBBox.Merge( m_end );
2249
2250 if( IsAnyFill() )
2251 aBBox.Merge( m_arcCenter );
2252
2253 int radius = GetRadius();
2254 EDA_ANGLE t1, t2;
2255
2256 CalcArcAngles( t1, t2 );
2257
2258 t1.Normalize();
2259 t2.Normalize();
2260
2261 if( t2 > t1 )
2262 {
2263 if( t1 < ANGLE_0 && t2 > ANGLE_0 )
2264 aBBox.Merge( VECTOR2I( m_arcCenter.x + radius, m_arcCenter.y ) ); // right
2265
2266 if( t1 < ANGLE_90 && t2 > ANGLE_90 )
2267 aBBox.Merge( VECTOR2I( m_arcCenter.x, m_arcCenter.y + radius ) ); // down
2268
2269 if( t1 < ANGLE_180 && t2 > ANGLE_180 )
2270 aBBox.Merge( VECTOR2I( m_arcCenter.x - radius, m_arcCenter.y ) ); // left
2271
2272 if( t1 < ANGLE_270 && t2 > ANGLE_270 )
2273 aBBox.Merge( VECTOR2I( m_arcCenter.x, m_arcCenter.y - radius ) ); // up
2274 }
2275 else
2276 {
2277 if( t1 < ANGLE_0 || t2 > ANGLE_0 )
2278 aBBox.Merge( VECTOR2I( m_arcCenter.x + radius, m_arcCenter.y ) ); // right
2279
2280 if( t1 < ANGLE_90 || t2 > ANGLE_90 )
2281 aBBox.Merge( VECTOR2I( m_arcCenter.x, m_arcCenter.y + radius ) ); // down
2282
2283 if( t1 < ANGLE_180 || t2 > ANGLE_180 )
2284 aBBox.Merge( VECTOR2I( m_arcCenter.x - radius, m_arcCenter.y ) ); // left
2285
2286 if( t1 < ANGLE_270 || t2 > ANGLE_270 )
2287 aBBox.Merge( VECTOR2I( m_arcCenter.x, m_arcCenter.y - radius ) ); // up
2288 }
2289}
2290
2291
2292void EDA_SHAPE::SetPolyPoints( const std::vector<VECTOR2I>& aPoints )
2293{
2296
2297 for( const VECTOR2I& p : aPoints )
2298 GetPolyShape().Append( p.x, p.y );
2299
2300 m_hatchingDirty = true;
2301}
2302
2303
2304std::vector<SHAPE*> EDA_SHAPE::MakeEffectiveShapesForStroking( int aLineWidth ) const
2305{
2306 if( aLineWidth < 0 )
2307 aLineWidth = GetEffectiveWidth();
2308
2309 if( m_startEnding.GetShortenDepth( aLineWidth ) > 0 || m_endEnding.GetShortenDepth( aLineWidth ) > 0 )
2310 {
2311 switch( m_shape )
2312 {
2313 // Stroke() has no Bezier primitive, so it gets the flattened polyline. One chain,
2314 // not loose segments, or the pattern restarts at every vertex (same reason as the
2315 // unshortened case below).
2316 case SHAPE_T::BEZIER:
2317 {
2318 std::vector<VECTOR2I> pts;
2319
2320 for( const VECTOR2D& pt : ShortenedBezierPolyline( aLineWidth ) )
2321 pts.emplace_back( VECTOR2I( pt ) );
2322
2323 return { new SHAPE_LINE_CHAIN( pts ) };
2324 }
2325
2326 case SHAPE_T::SEGMENT:
2327 case SHAPE_T::ARC:
2328 case SHAPE_T::POLY: return makeShortenedBodyShapes( aLineWidth, true );
2329
2330 // Other shapes have no body shortening; fall through to the standard cases.
2331 default: break;
2332 }
2333 }
2334
2335 switch( m_shape )
2336 {
2337 // Stroke() has no Bezier primitive, so it gets the flattened polyline. One chain, not
2338 // loose segments, or the pattern restarts at every vertex. This case goes away if
2339 // Bezier ever becomes a SHAPE of its own.
2341
2342 case SHAPE_T::ELLIPSE:
2343 case SHAPE_T::ELLIPSE_ARC: return { new SHAPE_ELLIPSE( buildShapeEllipse() ) };
2344
2345 default: return MakeEffectiveShapes( true );
2346 }
2347}
2348
2349
2350std::vector<SHAPE*> EDA_SHAPE::makeEffectiveShapes( bool aEdgeOnly, bool aLineChainOnly, bool aHittesting ) const
2351{
2352 std::vector<SHAPE*> effectiveShapes;
2353 int width = GetEffectiveWidth();
2354 bool solidFill = IsSolidFill()
2355 || IsHatchedFill()
2356 || IsProxyItem()
2357 || ( aHittesting && IsFilledForHitTesting() );
2358
2359 if( aEdgeOnly )
2360 solidFill = false;
2361
2362 switch( m_shape )
2363 {
2364 case SHAPE_T::ARC:
2365 effectiveShapes.emplace_back( new SHAPE_ARC( m_arcCenter, m_start, GetArcAngle(), width ) );
2366 break;
2367
2368 case SHAPE_T::SEGMENT:
2369 effectiveShapes.emplace_back( new SHAPE_SEGMENT( m_start, m_end, width ) );
2370 break;
2371
2372 case SHAPE_T::RECTANGLE:
2373 {
2374 if( m_cornerRadius > 0 )
2375 {
2377 SHAPE_POLY_SET poly;
2378 rr.TransformToPolygon( poly, getMaxError() );
2379 SHAPE_LINE_CHAIN outline = poly.Outline( 0 );
2380
2381 if( solidFill )
2382 effectiveShapes.emplace_back( new SHAPE_SIMPLE( outline ) );
2383
2384 if( width > 0 || !solidFill )
2385 {
2386 std::set<size_t> arcsHandled;
2387
2388 for( int ii = 0; ii < outline.SegmentCount(); ++ii )
2389 {
2390 if( outline.IsArcSegment( ii ) )
2391 {
2392 size_t arcIndex = outline.ArcIndex( ii );
2393
2394 if( !arcsHandled.contains( arcIndex ) )
2395 {
2396 arcsHandled.insert( arcIndex );
2397 effectiveShapes.emplace_back( new SHAPE_ARC( outline.Arc( arcIndex ), width ) );
2398 }
2399 }
2400 else
2401 {
2402 effectiveShapes.emplace_back( new SHAPE_SEGMENT( outline.Segment( ii ), width ) );
2403 }
2404 }
2405 }
2406 }
2407 else
2408 {
2409 std::vector<VECTOR2I> pts = GetRectCorners();
2410
2411 if( solidFill )
2412 effectiveShapes.emplace_back( new SHAPE_SIMPLE( pts ) );
2413
2414 if( width > 0 || !solidFill )
2415 {
2416 effectiveShapes.emplace_back( new SHAPE_SEGMENT( pts[0], pts[1], width ) );
2417 effectiveShapes.emplace_back( new SHAPE_SEGMENT( pts[1], pts[2], width ) );
2418 effectiveShapes.emplace_back( new SHAPE_SEGMENT( pts[2], pts[3], width ) );
2419 effectiveShapes.emplace_back( new SHAPE_SEGMENT( pts[3], pts[0], width ) );
2420 }
2421 }
2422 break;
2423 }
2424
2425 case SHAPE_T::CIRCLE:
2426 {
2427 if( solidFill )
2428 effectiveShapes.emplace_back( new SHAPE_CIRCLE( getCenter(), GetRadius() ) );
2429
2430 if( width > 0 || !solidFill )
2431 effectiveShapes.emplace_back( new SHAPE_ARC( getCenter(), GetEnd(), ANGLE_360, width ) );
2432
2433 break;
2434 }
2435
2436 case SHAPE_T::BEZIER:
2437 {
2438 std::vector<VECTOR2I> bezierPoints = buildBezierToSegmentsPointsList( getMaxError() );
2439 VECTOR2I start_pt = bezierPoints[0];
2440
2441 for( unsigned int jj = 1; jj < bezierPoints.size(); jj++ )
2442 {
2443 VECTOR2I end_pt = bezierPoints[jj];
2444 effectiveShapes.emplace_back( new SHAPE_SEGMENT( start_pt, end_pt, width ) );
2445 start_pt = end_pt;
2446 }
2447
2448 break;
2449 }
2450
2451 case SHAPE_T::POLY:
2452 {
2453 if( GetPolyShape().OutlineCount() == 0 ) // malformed/empty polygon
2454 break;
2455
2456 for( int ii = 0; ii < GetPolyShape().OutlineCount(); ++ii )
2457 {
2458 const SHAPE_LINE_CHAIN& l = GetPolyShape().COutline( ii );
2459
2460 if( solidFill )
2461 effectiveShapes.emplace_back( new SHAPE_SIMPLE( l ) );
2462
2463 if( width > 0 || !IsSolidFill() || aEdgeOnly )
2464 {
2465 int segCount = l.SegmentCount();
2466
2467 if( aLineChainOnly && l.IsClosed() )
2468 segCount--; // Treat closed chain as open
2469
2470 for( int jj = 0; jj < segCount; jj++ )
2471 effectiveShapes.emplace_back( new SHAPE_SEGMENT( l.CSegment( jj ), width ) );
2472 }
2473 }
2474 }
2475 break;
2476
2477 case SHAPE_T::ELLIPSE:
2479 {
2480 if( solidFill && m_shape == SHAPE_T::ELLIPSE )
2481 {
2482 // Filled closed ellipse: emit a SHAPE_SIMPLE for the filled interior.
2485 std::vector<VECTOR2I> pts;
2486
2487 for( int ii = 0; ii < chain.PointCount(); ++ii )
2488 pts.emplace_back( chain.CPoint( ii ) );
2489
2490 effectiveShapes.emplace_back( new SHAPE_SIMPLE( pts ) );
2491 }
2492
2493 if( width > 0 || !solidFill )
2494 {
2497
2498 for( int ii = 0; ii < chain.SegmentCount(); ++ii )
2499 effectiveShapes.emplace_back( new SHAPE_SEGMENT( chain.CSegment( ii ), width ) );
2500 }
2501
2502 break;
2503 }
2504
2505 default:
2507 break;
2508 }
2509
2510 return effectiveShapes;
2511}
2512
2513
2514static SHAPE_LINE_CHAIN lineEndingClosedChain( const std::vector<VECTOR2I>& aPolygon )
2515{
2517
2518 for( size_t ii = 0; ii < aPolygon.size(); ++ii )
2519 {
2520 if( ii == aPolygon.size() - 1 && aPolygon[ii] == aPolygon.front() )
2521 continue;
2522
2523 chain.Append( aPolygon[ii] );
2524 }
2525
2526 chain.SetClosed( true );
2527 return chain;
2528}
2529
2530
2531static bool hasLineEnding( const LINE_ENDING& aStartEnding, const LINE_ENDING& aEndEnding )
2532{
2533 return aStartEnding.GetStyle() != LINE_ENDING_STYLE::NONE || aEndEnding.GetStyle() != LINE_ENDING_STYLE::NONE;
2534}
2535
2536
2537static void addLineEndingEffectiveShapes( std::vector<SHAPE*>& aShapes, const LINE_ENDING& aEnding,
2538 const VECTOR2I& aPoint, const EDA_ANGLE& aTangent, int aLineWidth )
2539{
2540 if( aEnding.GetStyle() == LINE_ENDING_STYLE::NONE )
2541 return;
2542
2543 std::vector<VECTOR2I> polygon;
2544 aEnding.GetShapes( aPoint, aTangent, aLineWidth, polygon );
2545
2546 if( polygon.empty() )
2547 return;
2548
2549 if( aEnding.GetStyle() == LINE_ENDING_STYLE::ARROW_OPEN )
2550 {
2551 int width = aEnding.GetStrokeWidth() > 0 ? aEnding.GetStrokeWidth() : aLineWidth;
2552
2553 if( polygon.size() >= 3 )
2554 {
2555 aShapes.emplace_back( new SHAPE_SEGMENT( polygon[0], polygon[1], std::max( 0, width ) ) );
2556 aShapes.emplace_back( new SHAPE_SEGMENT( polygon[1], polygon[2], std::max( 0, width ) ) );
2557 }
2558
2559 return;
2560 }
2561
2562 if( polygon.size() < 3 )
2563 return;
2564
2565 SHAPE_LINE_CHAIN outline = lineEndingClosedChain( polygon );
2566
2567 if( outline.PointCount() >= 3 )
2568 aShapes.emplace_back( new SHAPE_SIMPLE( outline ) );
2569
2570 if( aEnding.GetStrokeWidth() > 0 )
2571 {
2572 for( int ii = 0; ii < outline.SegmentCount(); ++ii )
2573 aShapes.emplace_back( new SHAPE_SEGMENT( outline.CSegment( ii ), aEnding.GetStrokeWidth() ) );
2574 }
2575}
2576
2577
2578std::vector<SHAPE*> EDA_SHAPE::MakeLineEndingEffectiveShapes( int aLineWidth ) const
2579{
2580 std::vector<SHAPE*> effectiveShapes;
2581 VECTOR2I startPoint;
2582 VECTOR2I endPoint;
2583
2585 return effectiveShapes;
2586
2587 if( !GetLineEndingEndpoints( startPoint, endPoint ) )
2588 return effectiveShapes;
2589
2590 EDA_ANGLE startTangent;
2591 EDA_ANGLE endTangent;
2592
2593 GetEndingTangents( startTangent, endTangent, aLineWidth );
2594
2595 addLineEndingEffectiveShapes( effectiveShapes, m_startEnding, startPoint, startTangent, aLineWidth );
2596 addLineEndingEffectiveShapes( effectiveShapes, m_endEnding, endPoint, endTangent, aLineWidth );
2597
2598 return effectiveShapes;
2599}
2600
2601
2602std::vector<SHAPE*> EDA_SHAPE::makeShortenedBodyShapes( int aLineWidth, bool aEdgeOnly ) const
2603{
2604 std::vector<SHAPE*> effectiveShapes;
2605 bool shortenBody = m_startEnding.GetShortenDepth( aLineWidth ) > 0 || m_endEnding.GetShortenDepth( aLineWidth ) > 0;
2606
2607 if( !shortenBody )
2608 {
2609 effectiveShapes = makeEffectiveShapes( aEdgeOnly );
2610 }
2611 else
2612 {
2613 int width = GetEffectiveWidth();
2614
2615 switch( m_shape )
2616 {
2617 case SHAPE_T::SEGMENT:
2618 {
2619 VECTOR2I start = GetStart();
2620 VECTOR2I end = GetEnd();
2621
2622 if( ShortenSegmentForEndings( start, end, m_startEnding, m_endEnding, aLineWidth ) )
2623 effectiveShapes.emplace_back( new SHAPE_SEGMENT( start, end, width ) );
2624
2625 break;
2626 }
2627
2628 case SHAPE_T::ARC:
2629 {
2630 EDA_ANGLE startAngle;
2631 EDA_ANGLE endAngle;
2632 CalcArcAngles( startAngle, endAngle );
2633
2634 EDA_ANGLE originalStartAngle = startAngle;
2635 EDA_ANGLE arcAngle = endAngle - startAngle;
2636
2637 if( ShortenArcForEndings( startAngle, arcAngle, GetRadius(), aLineWidth ) )
2638 {
2639 VECTOR2I startPoint = GetStart();
2640 RotatePoint( startPoint, m_arcCenter, -( startAngle - originalStartAngle ) );
2641 effectiveShapes.emplace_back( new SHAPE_ARC( m_arcCenter, startPoint, arcAngle, width ) );
2642 }
2643
2644 break;
2645 }
2646
2647 case SHAPE_T::BEZIER:
2648 {
2649 std::vector<VECTOR2D> pts = ShortenedBezierPolyline( aLineWidth );
2650
2651 for( size_t ii = 1; ii < pts.size(); ++ii )
2652 {
2653 effectiveShapes.emplace_back(
2654 new SHAPE_SEGMENT( VECTOR2I( pts[ii - 1] ), VECTOR2I( pts[ii] ), width ) );
2655 }
2656
2657 break;
2658 }
2659
2660 case SHAPE_T::POLY:
2661 {
2662 if( ( !aEdgeOnly && ( IsSolidFill() || IsHatchedFill() || IsProxyItem() ) )
2663 || GetPolyShape().OutlineCount() == 0 )
2664 {
2665 effectiveShapes = makeEffectiveShapes( aEdgeOnly );
2666 break;
2667 }
2668
2669 for( int ii = 0; ii < GetPolyShape().OutlineCount(); ++ii )
2670 {
2671 const SHAPE_LINE_CHAIN& outline = GetPolyShape().COutline( ii );
2672
2673 if( outline.PointCount() < 2 )
2674 continue;
2675
2676 std::vector<VECTOR2I> pts;
2677
2678 if( !GetShortenedBodyPolyPoints( outline, ii, pts, aLineWidth ) )
2679 continue;
2680
2681 for( size_t jj = 1; jj < pts.size(); ++jj )
2682 effectiveShapes.emplace_back( new SHAPE_SEGMENT( pts[jj - 1], pts[jj], width ) );
2683
2684 if( outline.IsClosed() )
2685 effectiveShapes.emplace_back( new SHAPE_SEGMENT( pts.back(), pts.front(), width ) );
2686 }
2687
2688 break;
2689 }
2690
2691 default: effectiveShapes = makeEffectiveShapes( aEdgeOnly ); break;
2692 }
2693 }
2694
2695 return effectiveShapes;
2696}
2697
2698
2699std::vector<SHAPE*> EDA_SHAPE::MakeEffectiveShapesWithLineEndings( int aLineWidth ) const
2700{
2701 std::vector<SHAPE*> effectiveShapes = makeShortenedBodyShapes( aLineWidth );
2702
2703 for( SHAPE* shape : MakeLineEndingEffectiveShapes( aLineWidth ) )
2704 effectiveShapes.emplace_back( shape );
2705
2706 return effectiveShapes;
2707}
2708
2709
2710static void addLineEndingPolygon( SHAPE_POLY_SET& aBuffer, const LINE_ENDING& aEnding, const VECTOR2I& aPoint,
2711 const EDA_ANGLE& aTangent, int aClearance, int aError, ERROR_LOC aErrorLoc,
2712 int aLineWidth )
2713{
2714 if( aEnding.GetStyle() == LINE_ENDING_STYLE::NONE )
2715 return;
2716
2717 std::vector<VECTOR2I> polygon;
2718 aEnding.GetShapes( aPoint, aTangent, aLineWidth, polygon );
2719
2720 if( polygon.empty() )
2721 return;
2722
2723 auto addStrokedSegment = [&]( const VECTOR2I& aStart, const VECTOR2I& aEnd, int aStrokeWidth )
2724 {
2725 int width = std::max( 0, aStrokeWidth ) + 2 * aClearance;
2726
2727 if( width > 0 )
2728 TransformOvalToPolygon( aBuffer, aStart, aEnd, width, aError, aErrorLoc );
2729 };
2730
2731 if( aEnding.GetStyle() == LINE_ENDING_STYLE::ARROW_OPEN )
2732 {
2733 int strokeWidth = aEnding.GetStrokeWidth() > 0 ? aEnding.GetStrokeWidth() : aLineWidth;
2734
2735 if( polygon.size() >= 3 )
2736 {
2737 addStrokedSegment( polygon[0], polygon[1], strokeWidth );
2738 addStrokedSegment( polygon[1], polygon[2], strokeWidth );
2739 }
2740
2741 return;
2742 }
2743
2744 if( polygon.size() < 3 )
2745 return;
2746
2747 SHAPE_LINE_CHAIN outline = lineEndingClosedChain( polygon );
2748
2749 if( outline.PointCount() >= 3 )
2750 {
2751 SHAPE_POLY_SET fill;
2752 fill.NewOutline();
2753
2754 for( int ii = 0; ii < outline.PointCount(); ++ii )
2755 fill.Append( outline.CPoint( ii ) );
2756
2757 if( aClearance > 0 )
2758 {
2759 int inflate = aClearance;
2760
2761 if( aErrorLoc == ERROR_OUTSIDE )
2762 inflate += aError;
2763
2764 fill.Inflate( inflate, CORNER_STRATEGY::ROUND_ALL_CORNERS, aError );
2765 }
2766
2767 aBuffer.Append( fill );
2768 }
2769
2770 if( aEnding.GetStrokeWidth() > 0 )
2771 {
2772 for( int ii = 0; ii < outline.SegmentCount(); ++ii )
2773 {
2774 const SEG& seg = outline.CSegment( ii );
2775 addStrokedSegment( seg.A, seg.B, aEnding.GetStrokeWidth() );
2776 }
2777 }
2778}
2779
2780
2781void EDA_SHAPE::TransformLineEndingsToPolygon( SHAPE_POLY_SET& aBuffer, int aClearance, int aError, ERROR_LOC aErrorLoc,
2782 int aLineWidth ) const
2783{
2784 VECTOR2I startPoint;
2785 VECTOR2I endPoint;
2786
2788 return;
2789
2790 if( !GetLineEndingEndpoints( startPoint, endPoint ) )
2791 return;
2792
2793 EDA_ANGLE startTangent;
2794 EDA_ANGLE endTangent;
2795
2796 GetEndingTangents( startTangent, endTangent, aLineWidth );
2797
2798 addLineEndingPolygon( aBuffer, m_startEnding, startPoint, startTangent, aClearance, aError, aErrorLoc, aLineWidth );
2799 addLineEndingPolygon( aBuffer, m_endEnding, endPoint, endTangent, aClearance, aError, aErrorLoc, aLineWidth );
2800}
2801
2802
2803void EDA_SHAPE::TransformWithLineEndingsToPolygon( SHAPE_POLY_SET& aBuffer, int aClearance, int aError,
2804 ERROR_LOC aErrorLoc, bool ignoreLineWidth ) const
2805{
2806 int lineWidth = ignoreLineWidth ? 0 : GetEffectiveWidth();
2807 bool shortenBody = m_startEnding.GetShortenDepth( lineWidth ) > 0 || m_endEnding.GetShortenDepth( lineWidth ) > 0;
2808
2809 if( !shortenBody )
2810 {
2811 TransformShapeToPolygon( aBuffer, aClearance, aError, aErrorLoc, ignoreLineWidth );
2812 }
2813 else
2814 {
2815 int width = ignoreLineWidth ? 0 : GetWidth();
2816 width += 2 * aClearance;
2817
2818 switch( m_shape )
2819 {
2820 case SHAPE_T::SEGMENT:
2821 {
2822 VECTOR2I start = GetStart();
2823 VECTOR2I end = GetEnd();
2824
2825 if( ShortenSegmentForEndings( start, end, m_startEnding, m_endEnding, lineWidth ) )
2826 TransformOvalToPolygon( aBuffer, start, end, width, aError, aErrorLoc );
2827
2828 break;
2829 }
2830
2831 case SHAPE_T::ARC:
2832 {
2833 EDA_ANGLE startAngle;
2834 EDA_ANGLE endAngle;
2835 CalcArcAngles( startAngle, endAngle );
2836
2837 EDA_ANGLE originalStartAngle = startAngle;
2838 EDA_ANGLE arcAngle = endAngle - startAngle;
2839
2840 if( ShortenArcForEndings( startAngle, arcAngle, GetRadius(), lineWidth ) )
2841 {
2842 VECTOR2I startPoint = GetStart();
2843 RotatePoint( startPoint, m_arcCenter, -( startAngle - originalStartAngle ) );
2844 SHAPE_ARC arc( m_arcCenter, startPoint, arcAngle, width );
2845
2846 TransformArcToPolygon( aBuffer, arc.GetP0(), arc.GetArcMid(), arc.GetP1(), width, aError, aErrorLoc );
2847 }
2848
2849 break;
2850 }
2851
2852 case SHAPE_T::BEZIER:
2853 {
2854 std::vector<VECTOR2D> pts = ShortenedBezierPolyline( lineWidth );
2855
2856 for( size_t ii = 1; ii < pts.size(); ++ii )
2857 {
2858 TransformOvalToPolygon( aBuffer, VECTOR2I( pts[ii - 1] ), VECTOR2I( pts[ii] ), width, aError,
2859 aErrorLoc );
2860 }
2861
2862 break;
2863 }
2864
2865 case SHAPE_T::POLY:
2866 {
2867 bool solidFill = IsSolidFill() || IsHatchedFill() || IsProxyItem();
2868
2869 if( solidFill || !IsPolyShapeValid() )
2870 {
2871 TransformShapeToPolygon( aBuffer, aClearance, aError, aErrorLoc, ignoreLineWidth );
2872 break;
2873 }
2874
2875 for( int ii = 0; ii < GetPolyShape().OutlineCount(); ++ii )
2876 {
2877 const SHAPE_LINE_CHAIN& outline = GetPolyShape().COutline( ii );
2878
2879 if( outline.PointCount() < 2 )
2880 continue;
2881
2882 std::vector<VECTOR2I> pts;
2883
2884 if( !GetShortenedBodyPolyPoints( outline, ii, pts, lineWidth ) )
2885 continue;
2886
2887 for( size_t jj = 1; jj < pts.size(); ++jj )
2888 {
2889 TransformOvalToPolygon( aBuffer, pts[jj - 1], pts[jj], width, aError, aErrorLoc );
2890 }
2891
2892 if( outline.IsClosed() )
2893 {
2894 TransformOvalToPolygon( aBuffer, pts.back(), pts.front(), width, aError, aErrorLoc );
2895 }
2896 }
2897
2898 break;
2899 }
2900
2901 default: TransformShapeToPolygon( aBuffer, aClearance, aError, aErrorLoc, ignoreLineWidth ); break;
2902 }
2903 }
2904
2905 TransformLineEndingsToPolygon( aBuffer, aClearance, aError, aErrorLoc, lineWidth );
2906}
2907
2908
2909bool EDA_SHAPE::GetLineEndingsBoundingBox( BOX2I& aBBox, int aLineWidth ) const
2910{
2911 auto mergeEnding =
2912 [&]( const LINE_ENDING& aEnding, const VECTOR2I& aPoint, const EDA_ANGLE& aTangent, bool& aHasBox )
2913 {
2914 if( aEnding.GetStyle() == LINE_ENDING_STYLE::NONE )
2915 return;
2916
2917 std::vector<VECTOR2I> polygon;
2918 aEnding.GetShapes( aPoint, aTangent, aLineWidth, polygon );
2919
2920 if( polygon.empty() )
2921 return;
2922
2923 BOX2I endingBBox( polygon.front(), VECTOR2I( 0, 0 ) );
2924
2925 for( const VECTOR2I& point : polygon )
2926 endingBBox.Merge( point );
2927
2928 int stroke = aEnding.GetStrokeWidth();
2929
2930 if( aEnding.GetStyle() == LINE_ENDING_STYLE::ARROW_OPEN && stroke <= 0 )
2931 stroke = aLineWidth;
2932
2933 endingBBox.Inflate( std::max( 0, stroke ) / 2 );
2934
2935 if( !aHasBox )
2936 {
2937 aBBox = endingBBox;
2938 aHasBox = true;
2939 }
2940 else
2941 {
2942 aBBox.Merge( endingBBox );
2943 }
2944 };
2945
2946 VECTOR2I startPoint;
2947 VECTOR2I endPoint;
2948
2950 return false;
2951
2952 if( !GetLineEndingEndpoints( startPoint, endPoint ) )
2953 return false;
2954
2955 bool hasBox = false;
2956 EDA_ANGLE startTangent;
2957 EDA_ANGLE endTangent;
2958
2959 GetEndingTangents( startTangent, endTangent, aLineWidth );
2960
2961 mergeEnding( m_startEnding, startPoint, startTangent, hasBox );
2962 mergeEnding( m_endEnding, endPoint, endTangent, hasBox );
2963
2964 if( hasBox )
2965 {
2966 aBBox.Normalize();
2967 }
2968
2969 return hasBox;
2970}
2971
2972
2973std::vector<VECTOR2I> EDA_SHAPE::GetPolyPoints() const
2974{
2975 const SHAPE_POLY_SET& poly = GetPolyShape();
2976 std::vector<VECTOR2I> points;
2977 int totalCount = 0;
2978
2979 for( int ii = 0; ii < poly.OutlineCount(); ++ii )
2980 totalCount += poly.COutline( ii ).PointCount();
2981
2982 points.reserve( totalCount );
2983
2984 for( int ii = 0; ii < poly.OutlineCount(); ++ii )
2985 {
2986 for( const VECTOR2I& pt : poly.COutline( ii ).CPoints() )
2987 points.emplace_back( pt );
2988 }
2989
2990 return points;
2991}
2992
2993
2995{
2996 if( !m_poly )
2997 m_poly = std::make_unique<SHAPE_POLY_SET>();
2998
2999 return *m_poly;
3000}
3001
3003{
3004 if( !m_poly )
3005 m_poly = std::make_unique<SHAPE_POLY_SET>();
3006
3007 return *m_poly;
3008}
3009
3010
3012{
3013 // return true if the polygonal shape is valid (has more than 2 points)
3014 return GetPolyShape().OutlineCount() > 0 && GetPolyShape().Outline( 0 ).PointCount() > 2;
3015}
3016
3017
3019{
3020 // return the number of corners of the polygonal shape
3021 // this shape is expected to be only one polygon without hole
3022 return GetPolyShape().OutlineCount() ? GetPolyShape().VertexCount( 0 ) : 0;
3023}
3024
3025
3026void EDA_SHAPE::beginEdit( const VECTOR2I& aPosition )
3027{
3028 switch( GetShape() )
3029 {
3030 case SHAPE_T::SEGMENT:
3031 case SHAPE_T::CIRCLE:
3032 case SHAPE_T::RECTANGLE:
3033 SetStart( aPosition );
3034 SetEnd( aPosition );
3035 break;
3036
3037 case SHAPE_T::ARC:
3038 SetArcGeometry( aPosition, aPosition, aPosition );
3039 m_editState = 1;
3040 break;
3041
3042 case SHAPE_T::BEZIER:
3043 SetStart( aPosition );
3044 SetEnd( aPosition );
3045 SetBezierC1( aPosition );
3046 SetBezierC2( aPosition );
3047 m_editState = 1;
3048
3050 break;
3051
3052 case SHAPE_T::POLY:
3054 GetPolyShape().Outline( 0 ).SetClosed( false );
3055
3056 // Start and end of the first segment (co-located for now)
3057 GetPolyShape().Outline( 0 ).Append( aPosition );
3058 GetPolyShape().Outline( 0 ).Append( aPosition, true );
3059 break;
3060
3061 case SHAPE_T::ELLIPSE:
3062 // m_start holds the first bbox corner and calcEdit derives the ellipse from it.
3063 m_editState = 1;
3064 SetStart( aPosition );
3065 SetEnd( aPosition );
3066 SetEllipseCenter( aPosition );
3070 break;
3071
3073 // State 1: drag bbox. States 2-3: pick start then end angle.
3074 SetStart( aPosition );
3075 SetEnd( aPosition );
3076 SetEllipseCenter( aPosition );
3082 m_editState = 1;
3083 break;
3084
3085 default: UNIMPLEMENTED_FOR( SHAPE_T_asString() );
3086 }
3087}
3088
3089
3090bool EDA_SHAPE::continueEdit( const VECTOR2I& aPosition )
3091{
3092 switch( GetShape() )
3093 {
3094 case SHAPE_T::ARC:
3095 case SHAPE_T::SEGMENT:
3096 case SHAPE_T::CIRCLE:
3097 case SHAPE_T::RECTANGLE:
3098 case SHAPE_T::ELLIPSE: return false;
3099
3100 case SHAPE_T::BEZIER:
3101 if( m_editState == 3 )
3102 return false;
3103
3104 m_editState++;
3105 return true;
3106
3108 if( m_editState == 3 )
3109 return false;
3110
3111 m_editState++;
3112 return true;
3113
3114 case SHAPE_T::POLY:
3115 {
3116 SHAPE_LINE_CHAIN& poly = GetPolyShape().Outline( 0 );
3117
3118 // do not add zero-length segments
3119 if( poly.CPoint( (int) poly.GetPointCount() - 2 ) != poly.CLastPoint() )
3120 poly.Append( aPosition, true );
3121 }
3122 return true;
3123
3124 default: UNIMPLEMENTED_FOR( SHAPE_T_asString() ); return false;
3125 }
3126}
3127
3128
3129void EDA_SHAPE::calcEdit( const VECTOR2I& aPosition )
3130{
3131#define sq( x ) pow( x, 2 )
3132
3133 switch( GetShape() )
3134 {
3135 case SHAPE_T::SEGMENT:
3136 case SHAPE_T::CIRCLE:
3137 case SHAPE_T::RECTANGLE: SetEnd( aPosition ); break;
3138
3139 case SHAPE_T::BEZIER:
3140 {
3141 switch( m_editState )
3142 {
3143 case 0:
3144 SetStart( aPosition );
3145 SetEnd( aPosition );
3146 SetBezierC1( aPosition );
3147 SetBezierC2( aPosition );
3148 break;
3149
3150 case 1:
3151 SetBezierC2( aPosition );
3152 SetEnd( aPosition );
3153 break;
3154
3155 case 2: SetBezierC1( aPosition ); break;
3156
3157 case 3: SetBezierC2( aPosition ); break;
3158 }
3159
3161 }
3162 break;
3163
3164 case SHAPE_T::ARC:
3165 {
3166 double radius = GetRadius();
3167 EDA_ANGLE lastAngle = GetArcAngle();
3168
3169 // Edit state 0: drawing: place start
3170 // Edit state 1: drawing: place end (center calculated for 90-degree subtended angle)
3171 // Edit state 2: point edit: move start (center calculated for invariant subtended angle)
3172 // Edit state 3: point edit: move end (center calculated for invariant subtended angle)
3173 // Edit state 4: point edit: move center
3174 // Edit state 5: point edit: move arc-mid-point
3175
3176 switch( m_editState )
3177 {
3178 case 0: SetArcGeometry( aPosition, aPosition, aPosition ); return;
3179
3180 case 1:
3181 m_end = aPosition;
3182 radius = m_start.Distance( m_end ) * M_SQRT1_2;
3183 break;
3184
3185 case 2:
3186 case 3:
3187 {
3188 VECTOR2I v = m_start - m_end;
3189 double chordBefore = v.SquaredEuclideanNorm();
3190
3191 if( m_editState == 2 )
3192 m_start = aPosition;
3193 else
3194 m_end = aPosition;
3195
3196 v = m_start - m_end;
3197
3198 double chordAfter = v.SquaredEuclideanNorm();
3199 double ratio = 0.0;
3200
3201 if( chordBefore > 0 )
3202 ratio = chordAfter / chordBefore;
3203
3204 if( ratio != 0 )
3205 radius = std::max( sqrt( sq( radius ) * ratio ), sqrt( chordAfter ) / 2 );
3206 break;
3207 }
3208
3209 case 4:
3210 {
3211 double radialA = m_start.Distance( aPosition );
3212 double radialB = m_end.Distance( aPosition );
3213 radius = ( radialA + radialB ) / 2.0;
3214 break;
3215 }
3216
3217 case 5: SetArcGeometry( GetStart(), aPosition, GetEnd() ); return;
3218 }
3219
3220 // Calculate center based on start, end, and radius
3221 //
3222 // Let 'l' be the length of the chord and 'm' the middle point of the chord
3223 double l = m_start.Distance( m_end );
3224 VECTOR2D m = ( m_start + m_end ) / 2;
3225 double sqRadDiff = ( radius * radius ) - ( l * l ) / 4.0;
3226
3227 // Calculate 'd', the vector from the chord midpoint to the center
3228 VECTOR2D d;
3229
3230 if( l > 0 && sqRadDiff >= 0 )
3231 {
3232 d.x = sqrt( sqRadDiff ) * ( m_start.y - m_end.y ) / l;
3233 d.y = sqrt( sqRadDiff ) * ( m_end.x - m_start.x ) / l;
3234 }
3235
3236 VECTOR2I c1 = KiROUND( m + d );
3237 VECTOR2I c2 = KiROUND( m - d );
3238
3239 // Solution gives us 2 centers; we need to pick one:
3240 switch( m_editState )
3241 {
3242 case 1:
3243 // Keep arc clockwise while drawing i.e. arc angle = 90 deg.
3244 // it can be 90 or 270 deg depending on the arc center choice (c1 or c2)
3245 m_arcCenter = c1; // first trial
3246
3247 if( GetArcAngle() > ANGLE_180 )
3248 m_arcCenter = c2;
3249
3250 break;
3251
3252 case 2:
3253 case 3:
3254 // Pick the one of c1, c2 to keep arc on the same side
3255 m_arcCenter = c1; // first trial
3256
3257 if( ( lastAngle < ANGLE_180 ) != ( GetArcAngle() < ANGLE_180 ) )
3258 m_arcCenter = c2;
3259
3260 break;
3261
3262 case 4:
3263 // Pick the one closer to the mouse position
3264 m_arcCenter = c1.Distance( aPosition ) < c2.Distance( aPosition ) ? c1 : c2;
3265 break;
3266 }
3267
3268 break;
3269 }
3270
3271 case SHAPE_T::POLY:
3272 GetPolyShape().Outline( 0 ).SetPoint( GetPolyShape().Outline( 0 ).GetPointCount() - 1, aPosition );
3273 break;
3274
3275 case SHAPE_T::ELLIPSE:
3276 {
3277 const VECTOR2I firstCorner = GetStart();
3278 const VECTOR2I secondCorner = aPosition;
3279 const VECTOR2I center = ( firstCorner + secondCorner ) / 2;
3280 const int halfW = std::abs( secondCorner.x - firstCorner.x ) / 2;
3281 const int halfH = std::abs( secondCorner.y - firstCorner.y ) / 2;
3282
3283 int majorRadius;
3284 int minorRadius;
3285 EDA_ANGLE rotation;
3286
3287 if( halfW >= halfH )
3288 {
3289 majorRadius = std::max( halfW, 1 );
3290 minorRadius = std::max( halfH, 1 );
3291 rotation = ANGLE_0;
3292 }
3293 else
3294 {
3295 majorRadius = std::max( halfH, 1 );
3296 minorRadius = std::max( halfW, 1 );
3297 rotation = ANGLE_90;
3298 }
3299
3301 SetEllipseMajorRadius( majorRadius );
3302 SetEllipseMinorRadius( minorRadius );
3303 SetEllipseRotation( rotation );
3304 SetEnd( aPosition );
3305 break;
3306 }
3307
3309 {
3310 switch( m_editState )
3311 {
3312 case 0:
3313 case 1:
3314 {
3315 // Bbox
3316 const VECTOR2I firstCorner = GetStart();
3317 const VECTOR2I secondCorner = aPosition;
3318 const VECTOR2I center = ( firstCorner + secondCorner ) / 2;
3319 const int halfW = std::abs( secondCorner.x - firstCorner.x ) / 2;
3320 const int halfH = std::abs( secondCorner.y - firstCorner.y ) / 2;
3321
3322 int majorRadius;
3323 int minorRadius;
3324 EDA_ANGLE rotation;
3325
3326 if( halfW >= halfH )
3327 {
3328 majorRadius = std::max( halfW, 1 );
3329 minorRadius = std::max( halfH, 1 );
3330 rotation = ANGLE_0;
3331 }
3332 else
3333 {
3334 majorRadius = std::max( halfH, 1 );
3335 minorRadius = std::max( halfW, 1 );
3336 rotation = ANGLE_90;
3337 }
3338
3340 SetEllipseMajorRadius( majorRadius );
3341 SetEllipseMinorRadius( minorRadius );
3342 SetEllipseRotation( rotation );
3343 SetEnd( aPosition );
3344
3345 // Keep the preview rendering as a full closed ellipse during bbox build.
3348
3349 break;
3350 }
3351
3352 case 2:
3353 case 3:
3354 {
3355 // Project cursor onto the parametric form (a * cos t, b * sin t) to get t.
3356 const VECTOR2I center = m_ellipse.Center;
3357 const double a = std::max( 1, m_ellipse.MajorRadius );
3358 const double b = std::max( 1, m_ellipse.MinorRadius );
3359 const EDA_ANGLE rotation = m_ellipse.Rotation;
3360
3361 const double dx = aPosition.x - center.x;
3362 const double dy = aPosition.y - center.y;
3363
3364 const double cosRot = rotation.Cos();
3365 const double sinRot = rotation.Sin();
3366 const double lx = dx * cosRot + dy * sinRot;
3367 const double ly = -dx * sinRot + dy * cosRot;
3368
3369 const EDA_ANGLE paramAngle( std::atan2( ly / b, lx / a ), RADIANS_T );
3370
3371 if( m_editState == 2 )
3372 {
3373 SetEllipseStartAngle( paramAngle );
3374 SetEllipseEndAngle( paramAngle + ANGLE_360 );
3375 }
3376 else
3377 {
3378 // Force end > start
3379 EDA_ANGLE cursorAngle = paramAngle;
3380
3381 while( cursorAngle <= m_ellipse.StartAngle )
3382 cursorAngle = cursorAngle + ANGLE_360;
3383
3384 SetEllipseEndAngle( cursorAngle );
3385 }
3386
3387 break;
3388 }
3389 }
3390
3391 break;
3392 }
3393
3394 default: UNIMPLEMENTED_FOR( SHAPE_T_asString() );
3395 }
3396}
3397
3398
3399void EDA_SHAPE::endEdit( bool aClosed )
3400{
3401 switch( GetShape() )
3402 {
3403 case SHAPE_T::ARC:
3404 case SHAPE_T::SEGMENT:
3405 case SHAPE_T::CIRCLE:
3406 case SHAPE_T::RECTANGLE:
3407 case SHAPE_T::BEZIER: break;
3408
3409 case SHAPE_T::ELLIPSE:
3411 m_editState = 0;
3413 break;
3414
3415 case SHAPE_T::POLY:
3416 {
3417 SHAPE_LINE_CHAIN& poly = GetPolyShape().Outline( 0 );
3418
3419 // do not include last point twice
3420 if( poly.GetPointCount() > 2 && poly.CPoint( (int) poly.GetPointCount() - 2 ) == poly.CLastPoint() )
3421 poly.Remove( (int) poly.GetPointCount() - 1 );
3422
3423 poly.SetClosed( aClosed );
3424
3425 break;
3426 }
3427
3428 default: UNIMPLEMENTED_FOR( SHAPE_T_asString() );
3429 }
3430}
3431
3432
3434{
3435 EDA_SHAPE* image = dynamic_cast<EDA_SHAPE*>( aImage );
3436 assert( image );
3437
3438#define SWAPITEM( x ) std::swap( x, image->x )
3439 SWAPITEM( m_stroke );
3440 SWAPITEM( m_start );
3441 SWAPITEM( m_end );
3443 SWAPITEM( m_shape );
3447 SWAPITEM( m_poly );
3450 SWAPITEM( m_fill );
3456#undef SWAPITEM
3457
3458 m_hatchingDirty = true;
3459}
3460
3461
3462int EDA_SHAPE::Compare( const EDA_SHAPE* aOther ) const
3463{
3464#define EPSILON 2 // Should be enough for rounding errors on calculated items
3465
3466#define TEST( a, b ) \
3467 { \
3468 if( a != b ) \
3469 return a - b; \
3470 }
3471#define TEST_E( a, b ) \
3472 { \
3473 if( abs( a - b ) > EPSILON ) \
3474 return a - b; \
3475 }
3476#define TEST_PT( a, b ) \
3477 { \
3478 TEST_E( a.x, b.x ); \
3479 TEST_E( a.y, b.y ); \
3480 }
3481
3482 TEST_PT( m_start, aOther->m_start );
3483 TEST_PT( m_end, aOther->m_end );
3484
3485 TEST( (int) m_shape, (int) aOther->m_shape );
3486
3488 {
3490 }
3491 else if( m_shape == SHAPE_T::ARC )
3492 {
3493 TEST_PT( GetArcMid(), aOther->GetArcMid() );
3494 }
3495 else if( m_shape == SHAPE_T::BEZIER )
3496 {
3497 TEST_PT( m_bezierC1, aOther->m_bezierC1 );
3498 TEST_PT( m_bezierC2, aOther->m_bezierC2 );
3499 }
3501 {
3502 TEST_PT( m_ellipse.Center, aOther->m_ellipse.Center );
3503 TEST_E( m_ellipse.MajorRadius, aOther->m_ellipse.MajorRadius );
3504 TEST_E( m_ellipse.MinorRadius, aOther->m_ellipse.MinorRadius );
3505 TEST_E( m_ellipse.Rotation.AsTenthsOfADegree(), aOther->m_ellipse.Rotation.AsTenthsOfADegree() );
3506
3508 {
3509 TEST_E( m_ellipse.StartAngle.AsTenthsOfADegree(), aOther->m_ellipse.StartAngle.AsTenthsOfADegree() );
3510 TEST_E( m_ellipse.EndAngle.AsTenthsOfADegree(), aOther->m_ellipse.EndAngle.AsTenthsOfADegree() );
3511 }
3512 }
3513 else if( m_shape == SHAPE_T::POLY )
3514 {
3515 TEST( GetPolyShape().TotalVertices(), aOther->GetPolyShape().TotalVertices() );
3516 }
3517
3518 if( m_bezierPoints.size() != aOther->m_bezierPoints.size() )
3519 return m_bezierPoints.size() < aOther->m_bezierPoints.size() ? -1 : 1;
3520
3521 for( size_t ii = 0; ii < m_bezierPoints.size(); ++ii )
3522 TEST_PT( m_bezierPoints[ii], aOther->m_bezierPoints[ii] );
3523
3524 for( int ii = 0; ii < GetPolyShape().TotalVertices(); ++ii )
3525 TEST_PT( GetPolyShape().CVertex( ii ), aOther->GetPolyShape().CVertex( ii ) );
3526
3527 TEST_E( m_stroke.GetWidth(), aOther->m_stroke.GetWidth() );
3528 TEST( (int) m_stroke.GetLineStyle(), (int) aOther->m_stroke.GetLineStyle() );
3529 TEST( (int) m_fill, (int) aOther->m_fill );
3530
3531 return 0;
3532}
3533
3534
3535void EDA_SHAPE::TransformShapeToPolygon( SHAPE_POLY_SET& aBuffer, int aClearance, int aError, ERROR_LOC aErrorLoc,
3536 bool ignoreLineWidth, bool includeFill ) const
3537{
3538 bool solidFill = IsSolidFill() || ( IsHatchedFill() && !includeFill ) || IsProxyItem();
3539 int width = ignoreLineWidth ? 0 : GetWidth();
3540
3541 width += 2 * aClearance;
3542
3543 switch( m_shape )
3544 {
3545 case SHAPE_T::CIRCLE:
3546 {
3547 int r = GetRadius();
3548
3549 if( solidFill )
3550 TransformCircleToPolygon( aBuffer, getCenter(), r + width / 2, aError, aErrorLoc );
3551 else
3552 TransformRingToPolygon( aBuffer, getCenter(), r, width, aError, aErrorLoc );
3553
3554 break;
3555 }
3556
3557 case SHAPE_T::RECTANGLE:
3558 {
3559 if( GetCornerRadius() > 0 )
3560 {
3562 BOX2I bbox = getBoundingBox();
3563 VECTOR2I position = bbox.GetCenter();
3564
3565 if( solidFill )
3566 {
3567 TransformRoundChamferedRectToPolygon( aBuffer, position, size, ANGLE_0, GetCornerRadius(), 0.0, 0,
3568 width / 2, aError, aErrorLoc );
3569 }
3570 else
3571 {
3573 SHAPE_POLY_SET poly;
3574 rr.TransformToPolygon( poly, aError );
3575 SHAPE_LINE_CHAIN& outline = poly.Outline( 0 );
3576 outline.SetClosed( true );
3577
3578 std::set<size_t> arcsHandled;
3579
3580 for( int ii = 0; ii < outline.SegmentCount(); ++ii )
3581 {
3582 if( outline.IsArcSegment( ii ) )
3583 {
3584 size_t arcIndex = outline.ArcIndex( ii );
3585
3586 if( arcsHandled.contains( arcIndex ) )
3587 continue;
3588
3589 arcsHandled.insert( arcIndex );
3590
3591 const SHAPE_ARC& arc = outline.Arc( arcIndex );
3592 TransformArcToPolygon( aBuffer, arc.GetP0(), arc.GetArcMid(), arc.GetP1(), width, aError,
3593 aErrorLoc );
3594 }
3595 else
3596 {
3597 const SEG& seg = outline.GetSegment( ii );
3598 TransformOvalToPolygon( aBuffer, seg.A, seg.B, width, aError, aErrorLoc );
3599 }
3600 }
3601 }
3602 }
3603 else
3604 {
3605 std::vector<VECTOR2I> pts = GetRectCorners();
3606
3607 if( solidFill )
3608 {
3609 aBuffer.NewOutline();
3610
3611 for( const VECTOR2I& pt : pts )
3612 aBuffer.Append( pt );
3613 }
3614
3615 if( width > 0 || !solidFill )
3616 {
3617 // Add in segments
3618 TransformOvalToPolygon( aBuffer, pts[0], pts[1], width, aError, aErrorLoc );
3619 TransformOvalToPolygon( aBuffer, pts[1], pts[2], width, aError, aErrorLoc );
3620 TransformOvalToPolygon( aBuffer, pts[2], pts[3], width, aError, aErrorLoc );
3621 TransformOvalToPolygon( aBuffer, pts[3], pts[0], width, aError, aErrorLoc );
3622 }
3623 }
3624
3625 break;
3626 }
3627
3628 case SHAPE_T::ARC:
3629 TransformArcToPolygon( aBuffer, GetStart(), GetArcMid(), GetEnd(), width, aError, aErrorLoc );
3630 break;
3631
3632 case SHAPE_T::SEGMENT: TransformOvalToPolygon( aBuffer, GetStart(), GetEnd(), width, aError, aErrorLoc ); break;
3633
3634 case SHAPE_T::POLY:
3635 {
3636 if( !IsPolyShapeValid() )
3637 break;
3638
3639 if( solidFill )
3640 {
3641 for( int ii = 0; ii < GetPolyShape().OutlineCount(); ++ii )
3642 {
3643 const SHAPE_LINE_CHAIN& poly = GetPolyShape().Outline( ii );
3644 SHAPE_POLY_SET tmp;
3645 tmp.NewOutline();
3646
3647 for( int jj = 0; jj < (int) poly.GetPointCount(); ++jj )
3648 tmp.Append( poly.GetPoint( jj ) );
3649
3650 if( width > 0 )
3651 {
3652 int inflate = width / 2;
3653
3654 if( aErrorLoc == ERROR_OUTSIDE )
3655 inflate += aError;
3656
3657 tmp.Inflate( inflate, CORNER_STRATEGY::ROUND_ALL_CORNERS, aError );
3658 }
3659
3660 aBuffer.Append( tmp );
3661 }
3662 }
3663 else
3664 {
3665 for( int ii = 0; ii < GetPolyShape().OutlineCount(); ++ii )
3666 {
3667 const SHAPE_LINE_CHAIN& poly = GetPolyShape().Outline( ii );
3668
3669 for( int jj = 0; jj < (int) poly.SegmentCount(); ++jj )
3670 {
3671 const SEG& seg = poly.GetSegment( jj );
3672 TransformOvalToPolygon( aBuffer, seg.A, seg.B, width, aError, aErrorLoc );
3673 }
3674 }
3675 }
3676
3677 break;
3678 }
3679
3680 case SHAPE_T::BEZIER:
3681 {
3682 std::vector<VECTOR2I> ctrlPts = { GetStart(), GetBezierC1(), GetBezierC2(), GetEnd() };
3683 BEZIER_POLY converter( ctrlPts );
3684 std::vector<VECTOR2I> poly;
3685 converter.GetPoly( poly, aError );
3686
3687 for( unsigned ii = 1; ii < poly.size(); ii++ )
3688 TransformOvalToPolygon( aBuffer, poly[ii - 1], poly[ii], width, aError, aErrorLoc );
3689
3690 break;
3691 }
3692
3693 case SHAPE_T::ELLIPSE:
3695 {
3697
3699
3700 if( solidFill && m_shape == SHAPE_T::ELLIPSE )
3701 {
3702 // Filled closed ellipse, build the outline, inflate for stroke width.
3703 SHAPE_POLY_SET tmp;
3704 tmp.NewOutline();
3705
3706 for( int ii = 0; ii < chain.PointCount(); ++ii )
3707 tmp.Append( chain.CPoint( ii ) );
3708
3709 if( width > 0 )
3710 {
3711 int inflate = width / 2;
3712
3713 if( aErrorLoc == ERROR_OUTSIDE )
3714 inflate += aError;
3715
3716 tmp.Inflate( inflate, CORNER_STRATEGY::ROUND_ALL_CORNERS, aError );
3717 }
3718
3719 aBuffer.Append( tmp );
3720 }
3721 else
3722 {
3723 // stroke each tessellated segment as an oval.
3724 for( int ii = 0; ii < chain.SegmentCount(); ++ii )
3725 {
3726 const SEG& seg = chain.CSegment( ii );
3727 TransformOvalToPolygon( aBuffer, seg.A, seg.B, width, aError, aErrorLoc );
3728 }
3729 }
3730
3731 break;
3732 }
3733
3734 default: UNIMPLEMENTED_FOR( SHAPE_T_asString() ); break;
3735 }
3736
3737 if( IsHatchedFill() && includeFill )
3738 {
3739 for( int ii = 0; ii < GetHatching().OutlineCount(); ++ii )
3740 aBuffer.AddOutline( GetHatching().COutline( ii ) );
3741 }
3742}
3743
3744
3745void EDA_SHAPE::SetWidth( int aWidth )
3746{
3747 m_stroke.SetWidth( aWidth );
3748 m_hatchingDirty = true;
3749}
3750
3751
3753{
3754 m_stroke.SetLineStyle( aStyle );
3755}
3756
3757
3759{
3760 if( m_stroke.GetLineStyle() != LINE_STYLE::DEFAULT )
3761 return m_stroke.GetLineStyle();
3762
3763 return LINE_STYLE::SOLID;
3764}
3765
3766
3767bool EDA_SHAPE::operator==( const EDA_SHAPE& aOther ) const
3768{
3769 if( GetShape() != aOther.GetShape() )
3770 return false;
3771
3772 if( m_fill != aOther.m_fill )
3773 return false;
3774
3775 if( m_stroke.GetWidth() != aOther.m_stroke.GetWidth() )
3776 return false;
3777
3778 if( m_stroke.GetLineStyle() != aOther.m_stroke.GetLineStyle() )
3779 return false;
3780
3781 if( m_fillColor != aOther.m_fillColor )
3782 return false;
3783
3784 switch( GetShape() )
3785 {
3786 case SHAPE_T::SEGMENT:
3787 case SHAPE_T::RECTANGLE:
3788 case SHAPE_T::CIRCLE:
3789 if( m_start != aOther.m_start )
3790 return false;
3791
3792 if( m_end != aOther.m_end )
3793 return false;
3794
3795 break;
3796
3797 case SHAPE_T::ARC:
3798 if( m_start != aOther.m_start )
3799 return false;
3800
3801 if( m_end != aOther.m_end )
3802 return false;
3803
3804 if( m_arcCenter != aOther.m_arcCenter )
3805 return false;
3806
3807 break;
3808
3809 case SHAPE_T::POLY:
3810 if( GetPolyShape().TotalVertices() != aOther.GetPolyShape().TotalVertices() )
3811 return false;
3812
3813 for( int ii = 0; ii < GetPolyShape().TotalVertices(); ++ii )
3814 {
3815 if( GetPolyShape().CVertex( ii ) != aOther.GetPolyShape().CVertex( ii ) )
3816 return false;
3817 }
3818
3819 break;
3820
3821 case SHAPE_T::BEZIER:
3822 if( m_start != aOther.m_start )
3823 return false;
3824
3825 if( m_end != aOther.m_end )
3826 return false;
3827
3828 if( m_bezierC1 != aOther.m_bezierC1 )
3829 return false;
3830
3831 if( m_bezierC2 != aOther.m_bezierC2 )
3832 return false;
3833
3834 if( m_bezierPoints != aOther.m_bezierPoints )
3835 return false;
3836
3837 break;
3838
3839 case SHAPE_T::ELLIPSE:
3841 if( m_ellipse.Center != aOther.m_ellipse.Center )
3842 return false;
3843
3844 if( m_ellipse.MajorRadius != aOther.m_ellipse.MajorRadius )
3845 return false;
3846 if( m_ellipse.MinorRadius != aOther.m_ellipse.MinorRadius )
3847 return false;
3848
3849 if( m_ellipse.Rotation != aOther.m_ellipse.Rotation )
3850 return false;
3851
3853 {
3854 if( m_ellipse.StartAngle != aOther.m_ellipse.StartAngle )
3855 return false;
3856
3857 if( m_ellipse.EndAngle != aOther.m_ellipse.EndAngle )
3858 return false;
3859 }
3860
3861 break;
3862
3863 default: return false;
3864 }
3865
3866 if( m_startEnding != aOther.m_startEnding )
3867 return false;
3868
3869 if( m_endEnding != aOther.m_endEnding )
3870 return false;
3871
3872 return true;
3873}
3874
3875
3876double EDA_SHAPE::Similarity( const EDA_SHAPE& aOther ) const
3877{
3878 if( GetShape() != aOther.GetShape() )
3879 return 0.0;
3880
3881 double similarity = 1.0;
3882
3883 if( m_fill != aOther.m_fill )
3884 similarity *= 0.9;
3885
3886 if( m_stroke.GetWidth() != aOther.m_stroke.GetWidth() )
3887 similarity *= 0.9;
3888
3889 if( m_stroke.GetLineStyle() != aOther.m_stroke.GetLineStyle() )
3890 similarity *= 0.9;
3891
3892 if( m_fillColor != aOther.m_fillColor )
3893 similarity *= 0.9;
3894
3895 if( m_start != aOther.m_start )
3896 similarity *= 0.9;
3897
3898 if( m_end != aOther.m_end )
3899 similarity *= 0.9;
3900
3901 if( m_arcCenter != aOther.m_arcCenter )
3902 similarity *= 0.9;
3903
3904 if( m_bezierC1 != aOther.m_bezierC1 )
3905 similarity *= 0.9;
3906
3907 if( m_bezierC2 != aOther.m_bezierC2 )
3908 similarity *= 0.9;
3909
3910 {
3911 int m = m_bezierPoints.size();
3912 int n = aOther.m_bezierPoints.size();
3913
3914 size_t longest = alg::longest_common_subset( m_bezierPoints, aOther.m_bezierPoints );
3915
3916 similarity *= std::pow( 0.9, m + n - 2 * longest );
3917 }
3918
3919 {
3920 int m = GetPolyShape().TotalVertices();
3921 int n = aOther.GetPolyShape().TotalVertices();
3922 std::vector<VECTOR2I> poly;
3923 std::vector<VECTOR2I> otherPoly;
3924 VECTOR2I lastPt( 0, 0 );
3925
3926 // We look for the longest common subset of the two polygons, but we need to
3927 // offset each point because we're actually looking for overall similarity, not just
3928 // exact matches. So if the zone is moved by 1IU, we only want one point to be
3929 // considered "moved" rather than the entire polygon. In this case, the first point
3930 // will not be a match but the rest of the sequence will.
3931 for( int ii = 0; ii < m; ++ii )
3932 {
3933 poly.emplace_back( lastPt - GetPolyShape().CVertex( ii ) );
3934 lastPt = GetPolyShape().CVertex( ii );
3935 }
3936
3937 lastPt = VECTOR2I( 0, 0 );
3938
3939 for( int ii = 0; ii < n; ++ii )
3940 {
3941 otherPoly.emplace_back( lastPt - aOther.GetPolyShape().CVertex( ii ) );
3942 lastPt = aOther.GetPolyShape().CVertex( ii );
3943 }
3944
3945 size_t longest = alg::longest_common_subset( poly, otherPoly );
3946
3947 similarity *= std::pow( 0.9, m + n - 2 * longest );
3948 }
3949
3950 if( m_startEnding != aOther.m_startEnding )
3951 similarity *= 0.9;
3952
3953 if( m_endEnding != aOther.m_endEnding )
3954 similarity *= 0.9;
3955
3956 return similarity;
3957}
3958
3959
3960static double bezierLength( const BEZIER<double>& aBezier, double aT0, double aT1 );
3961
3962static double findBezierTAtLength( const BEZIER<double>& aBezier, double aTargetLength, double aTotalLength );
3963
3964
3965void EDA_SHAPE::GetEndingTangents( EDA_ANGLE& aStartTangent, EDA_ANGLE& aEndTangent, int aLineWidth ) const
3966{
3967 aStartTangent = ANGLE_0;
3968 aEndTangent = ANGLE_0;
3969
3970 switch( m_shape )
3971 {
3972 case SHAPE_T::SEGMENT:
3973 {
3974 EDA_ANGLE lineAngle( GetEnd() - GetStart() );
3975 aStartTangent = lineAngle + ANGLE_180;
3976 aEndTangent = lineAngle;
3977 break;
3978 }
3979
3980 case SHAPE_T::ARC:
3981 {
3982 SHAPE_ARC arc( GetStart(), GetArcMid(), GetEnd(), 0 );
3983 double radius = arc.GetRadius();
3984 EDA_ANGLE startRadius = arc.GetStartAngle();
3985 EDA_ANGLE endRadius = arc.GetEndAngle();
3986 bool cw = arc.GetCentralAngle() < ANGLE_0;
3987
3988 // Offset tangent angle by half the shortening depth for better visual alignment.
3989 if( aLineWidth > 0 && radius > 0 )
3990 {
3991 int startDepth = m_startEnding.GetCurveOrientationDepth( aLineWidth );
3992
3993 if( startDepth > 0 )
3994 {
3995 EDA_ANGLE offset( RAD2DEG( ( startDepth / 2.0 ) / radius ), DEGREES_T );
3996
3997 if( cw )
3998 startRadius -= offset;
3999 else
4000 startRadius += offset;
4001 }
4002
4003 int endDepth = m_endEnding.GetCurveOrientationDepth( aLineWidth );
4004
4005 if( endDepth > 0 )
4006 {
4007 EDA_ANGLE offset( RAD2DEG( ( endDepth / 2.0 ) / radius ), DEGREES_T );
4008
4009 if( cw )
4010 endRadius += offset;
4011 else
4012 endRadius -= offset;
4013 }
4014 }
4015
4016 if( cw )
4017 {
4018 aStartTangent = startRadius + ANGLE_90;
4019 aEndTangent = endRadius - ANGLE_90;
4020 }
4021 else
4022 {
4023 aStartTangent = startRadius - ANGLE_90;
4024 aEndTangent = endRadius + ANGLE_90;
4025 }
4026
4027 break;
4028 }
4029
4030 case SHAPE_T::BEZIER:
4031 {
4032 // Tangent = (original endpoint - shortened endpoint) so that the
4033 // end shape aligns with the visible curve endpoint.
4035 VECTOR2D( m_end ) };
4036 std::optional<double> totalLength;
4037 std::optional<BEZIER<double>> shortened = ShortenedBezierCurve( aLineWidth );
4038 const std::vector<VECTOR2I>& bpts = GetBezierPoints();
4039
4040 auto fallbackStartTangent = [&]() -> EDA_ANGLE
4041 {
4042 if( GetStart() != GetBezierC1() )
4043 return EDA_ANGLE( GetStart() - GetBezierC1() );
4044
4045 if( bpts.size() >= 2 )
4046 return EDA_ANGLE( bpts.front() - bpts[1] );
4047
4048 return ANGLE_0;
4049 };
4050
4051 auto fallbackEndTangent = [&]() -> EDA_ANGLE
4052 {
4053 if( GetEnd() != GetBezierC2() )
4054 return EDA_ANGLE( GetEnd() - GetBezierC2() );
4055
4056 if( bpts.size() >= 2 )
4057 return EDA_ANGLE( bpts.back() - bpts[bpts.size() - 2] );
4058
4059 return ANGLE_0;
4060 };
4061
4062 auto openArrowFlexTangent = [&]( const LINE_ENDING& aEnding, bool aStart ) -> std::optional<EDA_ANGLE>
4063 {
4064 if( aEnding.GetStyle() != LINE_ENDING_STYLE::ARROW_OPEN )
4065 return std::nullopt;
4066
4067 int depth = aEnding.GetCurveOrientationDepth( aLineWidth );
4068
4069 if( depth <= 0 )
4070 return std::nullopt;
4071
4072 if( !totalLength )
4073 totalLength = bezierLength( sourceCurve, 0.0, 1.0 );
4074
4075 if( *totalLength <= 0.0 )
4076 return std::nullopt;
4077
4078 double distanceFromStart = std::min<double>( depth, *totalLength );
4079
4080 if( !aStart )
4081 distanceFromStart = *totalLength - distanceFromStart;
4082
4083 double t = findBezierTAtLength( sourceCurve, distanceFromStart, *totalLength );
4084 VECTOR2D sample = sourceCurve.PointAt( t );
4085 VECTOR2D endpoint = aStart ? VECTOR2D( GetStart() ) : VECTOR2D( GetEnd() );
4086 VECTOR2D delta = endpoint - sample;
4087
4088 if( delta.EuclideanNorm() <= 0.0 )
4089 return std::nullopt;
4090
4091 return EDA_ANGLE( delta );
4092 };
4093
4094 if( shortened )
4095 {
4096 VECTOR2D startDelta = VECTOR2D( GetStart() ) - shortened->Start;
4097
4098 if( startDelta.EuclideanNorm() > 0 )
4099 aStartTangent = EDA_ANGLE( startDelta );
4100 else
4101 aStartTangent = fallbackStartTangent();
4102
4103 VECTOR2D endDelta = VECTOR2D( GetEnd() ) - shortened->End;
4104
4105 if( endDelta.EuclideanNorm() > 0 )
4106 aEndTangent = EDA_ANGLE( endDelta );
4107 else
4108 aEndTangent = fallbackEndTangent();
4109 }
4110 else
4111 {
4112 aStartTangent = fallbackStartTangent();
4113 aEndTangent = fallbackEndTangent();
4114 }
4115
4116 if( std::optional<EDA_ANGLE> startFlex = openArrowFlexTangent( m_startEnding, true ) )
4117 aStartTangent = *startFlex;
4118
4119 if( std::optional<EDA_ANGLE> endFlex = openArrowFlexTangent( m_endEnding, false ) )
4120 aEndTangent = *endFlex;
4121
4122 break;
4123 }
4124
4125 case SHAPE_T::POLY:
4126 {
4127 const SHAPE_POLY_SET& poly = GetPolyShape();
4128
4129 if( poly.OutlineCount() > 0 )
4130 {
4131 const SHAPE_LINE_CHAIN& outline = poly.Outline( 0 );
4132 int ptCount = outline.PointCount();
4133
4134 if( ptCount >= 2 )
4135 {
4136 aStartTangent = EDA_ANGLE( outline.CPoint( 0 ) - outline.CPoint( 1 ) );
4137 aEndTangent = EDA_ANGLE( outline.CPoint( ptCount - 1 ) - outline.CPoint( ptCount - 2 ) );
4138 }
4139 }
4140
4141 break;
4142 }
4143
4144 default:
4145 aStartTangent = ANGLE_0;
4146 aEndTangent = ANGLE_0;
4147 break;
4148 }
4149}
4150
4151
4152bool EDA_SHAPE::GetLineEndingEndpoints( VECTOR2I& aStartPoint, VECTOR2I& aEndPoint ) const
4153{
4154 switch( m_shape )
4155 {
4156 case SHAPE_T::SEGMENT:
4157 case SHAPE_T::ARC:
4158 aStartPoint = GetStart();
4159 aEndPoint = GetEnd();
4160 return true;
4161
4162 case SHAPE_T::POLY:
4163 {
4164 const SHAPE_POLY_SET& poly = GetPolyShape();
4165
4166 if( poly.OutlineCount() == 0 )
4167 return false;
4168
4169 const SHAPE_LINE_CHAIN& outline = poly.COutline( 0 );
4170
4171 if( outline.PointCount() < 2 )
4172 return false;
4173
4174 aStartPoint = outline.CPoint( 0 );
4175 aEndPoint = outline.CPoint( outline.PointCount() - 1 );
4176 return true;
4177 }
4178
4179 case SHAPE_T::BEZIER:
4180 {
4181 const std::vector<VECTOR2I>& bpts = GetBezierPoints();
4182
4183 if( bpts.size() < 2 )
4184 return false;
4185
4186 aStartPoint = bpts.front();
4187 aEndPoint = bpts.back();
4188 return true;
4189 }
4190
4191 default: return false;
4192 }
4193}
4194
4195
4196bool EDA_SHAPE::ShortenSegmentForEndings( VECTOR2I& aStart, VECTOR2I& aEnd, const LINE_ENDING& aStartEnding,
4197 const LINE_ENDING& aEndEnding, int aLineWidth )
4198{
4199 VECTOR2I delta = aEnd - aStart;
4200 double len = delta.EuclideanNorm();
4201
4202 if( len == 0 )
4203 return true;
4204
4205 int startDepth = aStartEnding.GetShortenDepth( aLineWidth );
4206 int endDepth = aEndEnding.GetShortenDepth( aLineWidth );
4207 int totalDepth = std::max( 0, startDepth ) + std::max( 0, endDepth );
4208
4209 if( totalDepth <= 0 )
4210 return true;
4211
4212 if( totalDepth >= len )
4213 {
4214 aEnd = aStart;
4215 return false;
4216 }
4217
4218 VECTOR2D dir( delta.x / len, delta.y / len );
4219
4220 if( startDepth > 0 )
4221 {
4222 aStart.x += KiROUND( dir.x * startDepth );
4223 aStart.y += KiROUND( dir.y * startDepth );
4224 }
4225
4226 if( endDepth > 0 )
4227 {
4228 aEnd.x -= KiROUND( dir.x * endDepth );
4229 aEnd.y -= KiROUND( dir.y * endDepth );
4230 }
4231
4232 return true;
4233}
4234
4235
4236bool EDA_SHAPE::ShortenArcForEndings( EDA_ANGLE& aStartAngle, EDA_ANGLE& aArcAngle, double aRadius,
4237 int aLineWidth ) const
4238{
4239 if( aRadius <= 0 )
4240 return true;
4241
4242 int startDepth = m_startEnding.GetShortenDepth( aLineWidth );
4243 int endDepth = m_endEnding.GetShortenDepth( aLineWidth );
4244 int totalDepth = std::max( 0, startDepth ) + std::max( 0, endDepth );
4245
4246 if( totalDepth <= 0 )
4247 return true;
4248
4249 double arcLength = std::abs( aRadius * aArcAngle.AsRadians() );
4250
4251 if( totalDepth >= arcLength )
4252 {
4253 aArcAngle = ANGLE_0;
4254 return false;
4255 }
4256
4257 EDA_ANGLE startOffset = EDA_ANGLE( RAD2DEG( std::max( 0, startDepth ) / aRadius ), DEGREES_T );
4258 EDA_ANGLE totalOffset = EDA_ANGLE( RAD2DEG( totalDepth / aRadius ), DEGREES_T );
4259
4260 if( aArcAngle > ANGLE_0 )
4261 {
4262 aStartAngle += startOffset;
4263 aArcAngle -= totalOffset;
4264 }
4265 else
4266 {
4267 aStartAngle -= startOffset;
4268 aArcAngle += totalOffset;
4269 }
4270
4271 return true;
4272}
4273
4274
4275static double bezierSpeedAt( const BEZIER<double>& aBezier, double aT )
4276{
4277 double tInv = 1.0 - aT;
4278
4279 VECTOR2D derivative = 3.0 * tInv * tInv * ( aBezier.C1 - aBezier.Start )
4280 + 6.0 * tInv * aT * ( aBezier.C2 - aBezier.C1 )
4281 + 3.0 * aT * aT * ( aBezier.End - aBezier.C2 );
4282
4283 return derivative.EuclideanNorm();
4284}
4285
4286
4287static double bezierLength( const BEZIER<double>& aBezier, double aT0, double aT1 )
4288{
4289 if( aT1 <= aT0 )
4290 return 0.0;
4291
4292 // 16-point Gauss-Legendre integration. This avoids allocating and
4293 // flattening a sub-curve on every binary-search step during redraw.
4294 static constexpr double nodes[] = {
4295 0.0950125098376374, 0.2816035507792590, 0.4580167776572274, 0.6178762444026438,
4296 0.7554044083550030, 0.8656312023878318, 0.9445750230732326, 0.9894009349916499
4297 };
4298
4299 static constexpr double weights[] = { 0.1894506104550685, 0.1826034150449236, 0.1691565193950025,
4300 0.1495959888165767, 0.1246289712555339, 0.0951585116824928,
4301 0.0622535239386479, 0.0271524594117541 };
4302
4303 static_assert( std::size( nodes ) == std::size( weights ) );
4304
4305 double halfWidth = ( aT1 - aT0 ) / 2.0;
4306 double center = ( aT0 + aT1 ) / 2.0;
4307 double length = 0.0;
4308
4309 for( size_t ii = 0; ii < std::size( nodes ); ++ii )
4310 {
4311 double offset = halfWidth * nodes[ii];
4312 length +=
4313 weights[ii] * ( bezierSpeedAt( aBezier, center - offset ) + bezierSpeedAt( aBezier, center + offset ) );
4314 }
4315
4316 return halfWidth * length;
4317}
4318
4319
4320static double findBezierTAtLength( const BEZIER<double>& aBezier, double aTargetLength, double aTotalLength )
4321{
4322 if( aTargetLength <= 0.0 )
4323 return 0.0;
4324
4325 if( aTargetLength >= aTotalLength )
4326 return 1.0;
4327
4328 double low = 0.0;
4329 double high = 1.0;
4330
4331 // Keep the parameter search tighter than the flattened-length tolerance.
4332 static constexpr int paramSearchIterations = 24;
4333
4334 for( int ii = 0; ii < paramSearchIterations; ++ii )
4335 {
4336 double mid = ( low + high ) / 2.0;
4337 double len = bezierLength( aBezier, 0.0, mid );
4338
4339 if( len < aTargetLength )
4340 low = mid;
4341 else
4342 high = mid;
4343 }
4344
4345 return ( low + high ) / 2.0;
4346}
4347
4348
4349std::optional<BEZIER<double>> EDA_SHAPE::ShortenedBezierCurve( int aLineWidth ) const
4350{
4351 if( m_shape != SHAPE_T::BEZIER )
4352 return std::nullopt;
4353
4355
4356 int startDepth = m_startEnding.GetShortenDepth( aLineWidth );
4357 int endDepth = m_endEnding.GetShortenDepth( aLineWidth );
4358
4359 if( startDepth <= 0 && endDepth <= 0 )
4360 return curve;
4361
4362 double totalLength = bezierLength( curve, 0.0, 1.0 );
4363
4364 if( totalLength <= 0.0 || startDepth + endDepth >= totalLength )
4365 return std::nullopt;
4366
4367 double t0 = findBezierTAtLength( curve, startDepth, totalLength );
4368 double t1 = findBezierTAtLength( curve, totalLength - endDepth, totalLength );
4369
4370 if( t1 <= t0 )
4371 return std::nullopt;
4372
4373 return curve.SubCurve( t0, t1 );
4374}
4375
4376
4377std::vector<VECTOR2D> EDA_SHAPE::ShortenedBezierPolyline( int aLineWidth ) const
4378{
4379 std::vector<VECTOR2D> pts;
4380 std::optional<BEZIER<double>> curve = ShortenedBezierCurve( aLineWidth );
4381
4382 if( !curve )
4383 {
4384 return pts;
4385 }
4386
4387 std::vector<VECTOR2D> ctrlPts = { curve->Start, curve->C1, curve->C2, curve->End };
4388 BEZIER_POLY converter( ctrlPts );
4389
4390 converter.GetPoly( pts, std::max( 1, getMaxError() ) );
4391
4392 return pts;
4393}
4394
4395
4396bool EDA_SHAPE::ShortenPolyForEndings( VECTOR2D& aFirst, VECTOR2D& aLast, const VECTOR2D& aSecond,
4397 const VECTOR2D& aPenultimate, int aLineWidth ) const
4398{
4399 int startDepth = m_startEnding.GetShortenDepth( aLineWidth );
4400 int endDepth = m_endEnding.GetShortenDepth( aLineWidth );
4401 int totalDepth = std::max( 0, startDepth ) + std::max( 0, endDepth );
4402
4403 if( totalDepth <= 0 )
4404 return true;
4405
4406 double startSegLen = ( aSecond - aFirst ).EuclideanNorm();
4407 double endSegLen = ( aLast - aPenultimate ).EuclideanNorm();
4408 bool twoPointPoly = aSecond == aLast && aPenultimate == aFirst;
4409
4410 if( twoPointPoly && startSegLen <= totalDepth )
4411 {
4412 aLast = aFirst;
4413 return false;
4414 }
4415
4416 if( startDepth > 0 && startSegLen <= startDepth )
4417 {
4418 aLast = aFirst;
4419 return false;
4420 }
4421
4422 if( endDepth > 0 && endSegLen <= endDepth )
4423 {
4424 aLast = aFirst;
4425 return false;
4426 }
4427
4428 if( startDepth > 0 )
4429 {
4430 VECTOR2D dir = aSecond - aFirst;
4431
4432 if( startSegLen > 0 )
4433 aFirst = aFirst + dir * ( startDepth / startSegLen );
4434 }
4435
4436 if( endDepth > 0 )
4437 {
4438 VECTOR2D dir = aLast - aPenultimate;
4439
4440 if( endSegLen > 0 )
4441 aLast = aLast - dir * ( endDepth / endSegLen );
4442 }
4443
4444 return true;
4445}
4446
4447
4448bool EDA_SHAPE::ShortenBodyPolyPoints( std::vector<VECTOR2I>& aPoints, bool aClosed, int aOutlineIdx,
4449 int aLineWidth ) const
4450{
4451 if( aPoints.size() < 2 )
4452 return false;
4453
4454 if( aClosed || aOutlineIdx != 0 )
4455 return true;
4456
4457 VECTOR2D first = aPoints.front();
4458 VECTOR2D last = aPoints.back();
4459
4460 if( !ShortenPolyForEndings( first, last, VECTOR2D( aPoints[1] ), VECTOR2D( aPoints[aPoints.size() - 2] ),
4461 aLineWidth ) )
4462 {
4463 aPoints.clear();
4464 return false;
4465 }
4466
4467 aPoints.front() = VECTOR2I( first );
4468 aPoints.back() = VECTOR2I( last );
4469
4470 return true;
4471}
4472
4473
4474bool EDA_SHAPE::GetShortenedBodyPolyPoints( const SHAPE_LINE_CHAIN& aOutline, int aOutlineIdx,
4475 std::vector<VECTOR2I>& aPoints, int aLineWidth ) const
4476{
4477 aPoints.clear();
4478
4479 if( aOutline.PointCount() < 2 )
4480 return false;
4481
4482 aPoints.reserve( aOutline.PointCount() );
4483
4484 for( const VECTOR2I& pt : aOutline.CPoints() )
4485 aPoints.emplace_back( pt );
4486
4487 return ShortenBodyPolyPoints( aPoints, aOutline.IsClosed(), aOutlineIdx, aLineWidth );
4488}
4489
4490
4495
4496
4497static struct EDA_SHAPE_DESC
4498{
4500 {
4502 .Map( SHAPE_T::SEGMENT, _HKI( "Segment" ) )
4503 .Map( SHAPE_T::RECTANGLE, _HKI( "Rectangle" ) )
4504 .Map( SHAPE_T::ARC, _HKI( "Arc" ) )
4505 .Map( SHAPE_T::CIRCLE, _HKI( "Circle" ) )
4506 .Map( SHAPE_T::POLY, _HKI( "Polygon" ) )
4507 .Map( SHAPE_T::BEZIER, _HKI( "Bezier" ) )
4508 .Map( SHAPE_T::ELLIPSE, _HKI( "Ellipse" ) )
4509 .Map( SHAPE_T::ELLIPSE_ARC, _HKI( "Elliptical Arc" ) );
4510
4512
4513 if( lineStyleEnum.Choices().GetCount() == 0 )
4514 {
4515 lineStyleEnum.Map( LINE_STYLE::SOLID, _HKI( "Solid" ) )
4516 .Map( LINE_STYLE::DASH, _HKI( "Dashed" ) )
4517 .Map( LINE_STYLE::DOT, _HKI( "Dotted" ) )
4518 .Map( LINE_STYLE::DASHDOT, _HKI( "Dash-Dot" ) )
4519 .Map( LINE_STYLE::DASHDOTDOT, _HKI( "Dash-Dot-Dot" ) );
4520 }
4521
4523
4524 if( hatchModeEnum.Choices().GetCount() == 0 )
4525 {
4526 hatchModeEnum.Map( UI_FILL_MODE::NONE, _HKI( "None" ) );
4527 hatchModeEnum.Map( UI_FILL_MODE::SOLID, _HKI( "Solid" ) );
4528 hatchModeEnum.Map( UI_FILL_MODE::HATCH, _HKI( "Hatch" ) );
4529 hatchModeEnum.Map( UI_FILL_MODE::REVERSE_HATCH, _HKI( "Reverse Hatch" ) );
4530 hatchModeEnum.Map( UI_FILL_MODE::CROSS_HATCH, _HKI( "Cross-hatch" ) );
4531 }
4532
4535
4536 auto isNotPolygonOrCircle =
4537 []( INSPECTABLE* aItem ) -> bool
4538 {
4539 // Polygons, unlike other shapes, have no meaningful start or end coordinates
4540 if( EDA_SHAPE* shape = dynamic_cast<EDA_SHAPE*>( aItem ) )
4541 return shape->GetShape() != SHAPE_T::POLY && shape->GetShape() != SHAPE_T::CIRCLE;
4542
4543 return false;
4544 };
4545
4546 auto isCircle =
4547 []( INSPECTABLE* aItem ) -> bool
4548 {
4549 // Polygons, unlike other shapes, have no meaningful start or end coordinates
4550 if( EDA_SHAPE* shape = dynamic_cast<EDA_SHAPE*>( aItem ) )
4551 return shape->GetShape() == SHAPE_T::CIRCLE;
4552
4553 return false;
4554 };
4555
4556 auto isRectangle =
4557 []( INSPECTABLE* aItem ) -> bool
4558 {
4559 // Polygons, unlike other shapes, have no meaningful start or end coordinates
4560 if( EDA_SHAPE* shape = dynamic_cast<EDA_SHAPE*>( aItem ) )
4561 return shape->GetShape() == SHAPE_T::RECTANGLE;
4562
4563 return false;
4564 };
4565
4566 auto isEllipseOrEllipseArc =
4567 []( INSPECTABLE* aItem ) -> bool
4568 {
4569 if( EDA_SHAPE* shape = dynamic_cast<EDA_SHAPE*>( aItem ) )
4570 {
4571 return shape->GetShape() == SHAPE_T::ELLIPSE || shape->GetShape() == SHAPE_T::ELLIPSE_ARC;
4572 }
4573
4574 return false;
4575 };
4576
4577 auto isEllipseArc =
4578 []( INSPECTABLE* aItem ) -> bool
4579 {
4580 if( EDA_SHAPE* shape = dynamic_cast<EDA_SHAPE*>( aItem ) )
4581 return shape->GetShape() == SHAPE_T::ELLIPSE_ARC;
4582
4583 return false;
4584 };
4585
4586 const wxString shapeProps = _HKI( "Shape Properties" );
4587
4588 propMgr.AddProperty( new PROPERTY_ENUM<EDA_SHAPE, SHAPE_T>( _HKI( "Shape" ),
4590 shapeProps );
4591
4592 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "Start X" ),
4595 shapeProps )
4596 .SetAvailableFunc( isNotPolygonOrCircle );
4597 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "Start Y" ),
4600 shapeProps )
4601 .SetAvailableFunc( isNotPolygonOrCircle );
4602
4603 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "Center X" ),
4606 shapeProps )
4607 .SetAvailableFunc( isCircle );
4608
4609 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "Center Y" ),
4612 shapeProps )
4613 .SetAvailableFunc( isCircle );
4614
4615 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "Radius" ),
4618 shapeProps )
4619 .SetAvailableFunc( isCircle );
4620
4621 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "End X" ),
4624 shapeProps )
4625 .SetAvailableFunc( isNotPolygonOrCircle );
4626
4627 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "End Y" ),
4630 shapeProps )
4631 .SetAvailableFunc( isNotPolygonOrCircle );
4632
4633 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "Width" ),
4636 shapeProps )
4637 .SetAvailableFunc( isRectangle );
4638
4639 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "Height" ),
4642 shapeProps )
4643 .SetAvailableFunc( isRectangle );
4644
4645 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "Corner Radius" ),
4648 shapeProps )
4649 .SetAvailableFunc( isRectangle )
4650 .SetValidator( []( const wxAny&& aValue, EDA_ITEM* aItem ) -> VALIDATOR_RESULT
4651 {
4652 wxASSERT_MSG( aValue.CheckType<int>(),
4653 "Expecting int-containing value" );
4654
4655 int radius = aValue.As<int>();
4656
4657 EDA_SHAPE* prop_shape = dynamic_cast<EDA_SHAPE*>( aItem );
4658
4659 if( !prop_shape )
4660 return std::nullopt;
4661
4662 int maxRadius = std::min( prop_shape->GetRectangleWidth(),
4663 prop_shape->GetRectangleHeight() ) / 2;
4664
4665 if( radius > maxRadius )
4666 return std::make_unique<VALIDATION_ERROR_TOO_LARGE<int>>( radius, maxRadius );
4667 else if( radius < 0 )
4668 return std::make_unique<VALIDATION_ERROR_TOO_SMALL<int>>( radius, 0 );
4669
4670 return std::nullopt;
4671 } );
4672
4673 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "Major Radius" ),
4676 shapeProps )
4677 .SetAvailableFunc( isEllipseOrEllipseArc );
4678
4679 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "Minor Radius" ),
4682 shapeProps )
4683 .SetAvailableFunc( isEllipseOrEllipseArc );
4684
4685 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, EDA_ANGLE>( _HKI( "Ellipse Rotation" ),
4687 shapeProps )
4688 .SetAvailableFunc( isEllipseOrEllipseArc );
4689
4690 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, EDA_ANGLE>( _HKI( "Arc Start Angle" ),
4693 shapeProps )
4694 .SetAvailableFunc( isEllipseArc );
4695
4696 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, EDA_ANGLE>( _HKI( "Arc End Angle" ),
4698 shapeProps )
4699 .SetAvailableFunc( isEllipseArc );
4700
4701 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "Line Width" ),
4703 shapeProps ).SetIsCopyable();
4704
4705 propMgr.AddProperty( new PROPERTY_ENUM<EDA_SHAPE, LINE_STYLE>( _HKI( "Line Style" ),
4707 shapeProps ).SetIsCopyable();
4708
4709 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, COLOR4D>( _HKI( "Line Color" ),
4711 shapeProps )
4713
4717 shapeProps )
4719 [=]( INSPECTABLE* aItem ) -> bool
4720 {
4721 if( EDA_SHAPE* curr_shape = dynamic_cast<EDA_SHAPE*>( aItem ) )
4722 return curr_shape->GetShape() == SHAPE_T::ARC;
4723
4724 return false;
4725 } )
4726 .SetValidator(
4727 []( const wxAny&& aValue, EDA_ITEM* aItem ) -> VALIDATOR_RESULT
4728 {
4729 double degrees = 0.0;
4730
4731 if( aValue.GetAs( &degrees ) && degrees == 0.0 )
4732 {
4733 return std::make_unique<VALIDATION_ERROR_MSG>( _( "Arc angle must not be zero." ) );
4734 }
4735
4736 return std::nullopt;
4737 } );
4738
4739 auto fillAvailable =
4740 [=]( INSPECTABLE* aItem ) -> bool
4741 {
4742 if( EDA_ITEM* edaItem = dynamic_cast<EDA_ITEM*>( aItem ) )
4743 {
4744 // For some reason masking "Filled" and "Fill Color" at the
4745 // PCB_TABLECELL level doesn't work.
4746 if( edaItem->Type() == PCB_TABLECELL_T || edaItem->Type() == PCB_TEXTBOX_T )
4747 return false;
4748 }
4749
4750 if( EDA_SHAPE* edaShape = dynamic_cast<EDA_SHAPE*>( aItem ) )
4751 {
4752 switch( edaShape->GetShape() )
4753 {
4754 case SHAPE_T::POLY:
4755 case SHAPE_T::RECTANGLE:
4756 case SHAPE_T::CIRCLE:
4757 case SHAPE_T::BEZIER:
4758 case SHAPE_T::ELLIPSE: return true;
4759
4760 default:
4761 return false;
4762 }
4763 }
4764
4765 return false;
4766 };
4767
4770 shapeProps )
4771 .SetAvailableFunc( fillAvailable ).SetIsCopyable();
4772
4773 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, COLOR4D>( _HKI( "Fill Color" ),
4775 shapeProps )
4776 .SetAvailableFunc( fillAvailable )
4778
4779 auto isOpenShape =
4780 []( INSPECTABLE* aItem ) -> bool
4781 {
4782 if( EDA_SHAPE* shape = dynamic_cast<EDA_SHAPE*>( aItem ) )
4783 return !shape->IsClosed();
4784
4785 return false;
4786 };
4787
4789
4790 if( endingStyleEnum.Choices().GetCount() == 0 )
4791 {
4792 endingStyleEnum.Map( LINE_ENDING_STYLE::NONE, _HKI( "None" ) )
4793 .Map( LINE_ENDING_STYLE::ARROW, _HKI( "Arrow" ) )
4794 .Map( LINE_ENDING_STYLE::CIRCLE, _HKI( "Circle" ) )
4795 .Map( LINE_ENDING_STYLE::SQUARE, _HKI( "Square" ) )
4796 .Map( LINE_ENDING_STYLE::ARROW_OPEN, _HKI( "Open Arrow" ) );
4797 }
4798
4799 propMgr.AddProperty( new PROPERTY_ENUM<EDA_SHAPE, LINE_ENDING_STYLE>( _HKI( "Start Shape" ),
4801 shapeProps )
4802 .SetAvailableFunc( isOpenShape );
4803
4804 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "Start Length" ),
4806 shapeProps )
4807 .SetAvailableFunc( isOpenShape );
4808
4809 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "Start Width" ),
4811 shapeProps )
4812 .SetAvailableFunc( isOpenShape );
4813
4814 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "Start Stroke Width" ),
4817 shapeProps )
4818 .SetAvailableFunc( isOpenShape );
4819
4822 shapeProps )
4823 .SetAvailableFunc( isOpenShape );
4824
4825 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "End Length" ),
4827 shapeProps )
4828 .SetAvailableFunc( isOpenShape );
4829
4830 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "End Width" ),
4832 shapeProps )
4833 .SetAvailableFunc( isOpenShape );
4834
4835 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "End Stroke Width" ),
4838 shapeProps )
4839 .SetAvailableFunc( isOpenShape );
4840 }
KICOMMON_API types::KiCadObjectType ToProtoEnum(KICAD_T aValue)
KICOMMON_API KICAD_T FromProtoEnum(types::KiCadObjectType aValue)
Definition api_enums.cpp:55
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:128
BOX2< VECTOR2I > BOX2I
Definition box2.h:914
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:982
Bezier curves to polygon converter.
void GetPoly(std::vector< VECTOR2I > &aOutput, int aMaxError=10)
Convert a Bezier curve to a polygon.
Generic cubic Bezier representation.
VECTOR2< NumericType > Start
VECTOR2< NumericType > C1
VECTOR2< NumericType > C2
VECTOR2< NumericType > End
constexpr BOX2< Vec > & Inflate(coord_type dx, coord_type dy)
Inflates the rectangle horizontally by dx and vertically by dy.
Definition box2.h:553
constexpr void SetOrigin(const Vec &pos)
Definition box2.h:234
constexpr BOX2< Vec > & Normalize()
Ensure that the height and width are positive.
Definition box2.h:143
constexpr coord_type GetY() const
Definition box2.h:205
constexpr size_type GetWidth() const
Definition box2.h:211
constexpr Vec Centre() const
Definition box2.h:94
constexpr coord_type GetX() const
Definition box2.h:204
bool IntersectsCircleEdge(const Vec &aCenter, const int aRadius, const int aWidth) const
Definition box2.h:518
constexpr BOX2< Vec > & Merge(const BOX2< Vec > &aRect)
Modify the position and size of the rectangle in order to contain aRect.
Definition box2.h:594
constexpr const Vec GetCenter() const
Definition box2.h:227
constexpr size_type GetHeight() const
Definition box2.h:212
constexpr coord_type GetLeft() const
Definition box2.h:225
constexpr bool Contains(const Vec &aPoint) const
Definition box2.h:165
constexpr coord_type GetRight() const
Definition box2.h:214
constexpr void SetEnd(coord_type x, coord_type y)
Definition box2.h:294
constexpr coord_type GetTop() const
Definition box2.h:226
constexpr bool Intersects(const BOX2< Vec > &aRect) const
Definition box2.h:308
constexpr coord_type GetBottom() const
Definition box2.h:219
static const COLOR4D UNSPECIFIED
For legacy support; used as a value to indicate color hasn't been set yet.
Definition color4d.h:400
EDA_ANGLE Normalize()
Definition eda_angle.h:228
double Sin() const
Definition eda_angle.h:177
int AsTenthsOfADegree() const
Definition eda_angle.h:117
bool IsCardinal() const
Definition eda_angle.cpp:40
EDA_ANGLE Normalize720()
Definition eda_angle.h:286
double AsRadians() const
Definition eda_angle.h:119
double Cos() const
Definition eda_angle.h:196
FRAME_T GetFrameType() const
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:98
UI_FILL_MODE GetFillModeProp() const
static bool ShortenSegmentForEndings(VECTOR2I &aStart, VECTOR2I &aEnd, const LINE_ENDING &aStartEnding, const LINE_ENDING &aEndEnding, int aLineWidth)
Shorten a segment body for line endings.
virtual int GetHatchLineSpacing() const
Definition eda_shape.h:166
EDA_ANGLE GetArcAngle() const
int GetStartEndingLength() const
Definition eda_shape.h:189
SHAPE_T m_shape
Definition eda_shape.h:738
virtual void SetEnd(const VECTOR2I &aEnd)
Definition eda_shape.h:329
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:293
int GetEllipseMinorRadius() const
Definition eda_shape.h:395
bool m_proxyItem
Definition eda_shape.h:766
int m_cornerRadius
Definition eda_shape.h:750
bool m_hatchingDirty
Definition eda_shape.h:746
bool m_endsSwapped
Definition eda_shape.h:737
const VECTOR2I & GetBezierC2() const
Definition eda_shape.h:368
const VECTOR2I & GetEllipseCenter() const
Definition eda_shape.h:377
bool GetLineEndingsBoundingBox(BOX2I &aBBox, int aLineWidth) const
void move(const VECTOR2I &aMoveVector)
void SetCenter(const VECTOR2I &aCenter)
VECTOR2I getCenter() const
int GetStartY() const
Definition eda_shape.h:276
void SetFillModeProp(UI_FILL_MODE)
int m_editState
Definition eda_shape.h:765
virtual int getMaxError() const
Definition eda_shape.h:730
void rotate(const VECTOR2I &aRotCentre, const EDA_ANGLE &aAngle)
std::vector< SHAPE * > MakeEffectiveShapesWithLineEndings(int aLineWidth) const
Make effective geometry for the shape body shortened for line endings plus the line-ending geometry i...
const std::vector< VECTOR2I > buildBezierToSegmentsPointsList(int aMaxError) const
void SetEndEndingLength(int aLength)
Definition eda_shape.h:195
void SetStartEndingStyle(LINE_ENDING_STYLE aStyle)
Definition eda_shape.h:184
const SHAPE_POLY_SET & GetHatching() const
EDA_ANGLE GetEllipseEndAngle() const
Definition eda_shape.h:423
FILL_T GetFillMode() const
Definition eda_shape.h:148
virtual ~EDA_SHAPE()
Definition eda_shape.cpp:74
void SetEndEndingWidth(int aWidth)
Definition eda_shape.h:197
virtual void SetCornerRadius(int aRadius)
long long int m_rectangleHeight
Definition eda_shape.h:748
int GetEllipseMajorRadius() const
Definition eda_shape.h:386
std::unique_ptr< EDA_SHAPE_HATCH_CACHE_DATA > m_hatchingCache
Definition eda_shape.h:745
void SetEndY(int aY)
Definition eda_shape.h:336
virtual int GetEffectiveWidth() const
Definition eda_shape.h:164
std::vector< VECTOR2I > GetPolyPoints() const
Duplicate the polygon outlines into a flat list of VECTOR2I points.
ELLIPSE< int > m_ellipse
Definition eda_shape.h:762
void TransformWithLineEndingsToPolygon(SHAPE_POLY_SET &aBuffer, int aClearance, int aError, ERROR_LOC aErrorLoc, bool ignoreLineWidth=false) const
Convert the shape body shortened for line endings plus line-ending geometry to polygons.
COLOR4D GetLineColor() const
Definition eda_shape.h:172
int GetEndX() const
Definition eda_shape.h:327
SHAPE_ELLIPSE buildShapeEllipse() const
void TransformLineEndingsToPolygon(SHAPE_POLY_SET &aBuffer, int aClearance, int aError, ERROR_LOC aErrorLoc, int aLineWidth) const
Append only the line-ending geometry associated with this shape to a polygon set.
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)
std::vector< SHAPE * > makeShortenedBodyShapes(int aLineWidth, bool aEdgeOnly=false) const
Make effective geometry for the shape body shortened for line endings, without the line-ending geomet...
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:286
virtual std::vector< SHAPE * > MakeEffectiveShapes(bool aEdgeOnly=false) const
Make a set of SHAPE objects representing the EDA_SHAPE.
Definition eda_shape.h:551
bool ShortenPolyForEndings(VECTOR2D &aFirst, VECTOR2D &aLast, const VECTOR2D &aSecond, const VECTOR2D &aPenultimate, int aLineWidth) const
SHAPE_POLY_SET & GetPolyShape()
void SetCenterY(int y)
Definition eda_shape.h:300
void GetEndingTangents(EDA_ANGLE &aStartTangent, EDA_ANGLE &aEndTangent, int aLineWidth=0) const
Compute outward-facing tangent angles at the start and end of the shape.
int GetStartEndingStrokeWidth() const
Definition eda_shape.h:199
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:404
void ShapeGetMsgPanelInfo(EDA_DRAW_FRAME *aFrame, std::vector< MSG_PANEL_ITEM > &aList)
void SetEndEndingStyle(LINE_ENDING_STYLE aStyle)
Definition eda_shape.h:187
virtual bool isMoving() const
Definition eda_shape.h:698
virtual void SetEllipseEndAngle(const EDA_ANGLE &aA)
Definition eda_shape.h:416
int GetEndEndingStrokeWidth() const
Definition eda_shape.h:201
bool operator==(const EDA_SHAPE &aOther) const
int GetRadius() const
SHAPE_T GetShape() const
Definition eda_shape.h:175
std::vector< SHAPE * > MakeEffectiveShapesForStroking(int aLineWidth=-1) const
Make a set of SHAPE objects to hand to STROKE_PARAMS::Stroke().
virtual void SetBezierC2(const VECTOR2I &aPt)
Definition eda_shape.h:367
bool GetShortenedBodyPolyPoints(const SHAPE_LINE_CHAIN &aOutline, int aOutlineIdx, std::vector< VECTOR2I > &aPoints, int aLineWidth) const
Copy an outline and apply line-ending body shortening when applicable.
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:130
virtual SHAPE_POLY_SET getHatchingKnockouts() const
Definition eda_shape.h:664
VECTOR2I m_arcCenter
Definition eda_shape.h:755
void SetCenterX(int x)
Definition eda_shape.h:311
virtual void SetBezierC1(const VECTOR2I &aPt)
Definition eda_shape.h:364
int GetStartEndingWidth() const
Definition eda_shape.h:191
bool GetLineEndingEndpoints(VECTOR2I &aStartPoint, VECTOR2I &aEndPoint) const
Return the source endpoints used to place line endings.
virtual void SetFilled(bool aFlag)
Definition eda_shape.h:142
virtual bool IsFilledForHitTesting() const
Definition eda_shape.h:137
virtual void SetEllipseRotation(const EDA_ANGLE &aA)
Definition eda_shape.h:397
bool continueEdit(const VECTOR2I &aPosition)
virtual void SetArcAngle(const EDA_ANGLE &aAngle)
Definition eda_shape.h:441
wxString ShowShape() const
LINE_ENDING_STYLE GetStartEndingStyle() const
Definition eda_shape.h:183
ARC_MID m_arcMidData
Definition eda_shape.h:756
void SetFillColor(const COLOR4D &aColor)
Definition eda_shape.h:160
int GetEndY() const
Definition eda_shape.h:326
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:343
virtual int GetHatchLineWidth() const
Definition eda_shape.h:165
bool IsSolidFill() const
Definition eda_shape.h:123
void flip(const VECTOR2I &aCentre, FLIP_DIRECTION aFlipDirection)
int GetEndEndingWidth() const
Definition eda_shape.h:196
EDA_SHAPE(SHAPE_T aType, int aLineWidth, FILL_T aFill)
Definition eda_shape.cpp:56
std::vector< SEG > & hatchLines() const
void beginEdit(const VECTOR2I &aStartPoint)
int GetEndEndingLength() const
Definition eda_shape.h:194
VECTOR2I m_start
Definition eda_shape.h:752
int GetPointCount() const
const VECTOR2I & GetEnd() const
Return the ending point of the graphic.
Definition eda_shape.h:325
bool IsClosed() const
void SetRadius(int aX)
Definition eda_shape.h:350
LINE_STYLE GetLineStyle() const
void endEdit(bool aClosed=true)
Finish editing the shape.
virtual void SetEllipseCenter(const VECTOR2I &aPt)
Definition eda_shape.h:370
void SetStartEndingLength(int aLength)
Definition eda_shape.h:190
void SetEndEndingStrokeWidth(int aWidth)
Definition eda_shape.h:202
virtual void SetEllipseMinorRadius(int aR)
Definition eda_shape.h:388
const VECTOR2I & GetStart() const
Return the starting point of the graphic.
Definition eda_shape.h:275
virtual void SetEllipseMajorRadius(int aR)
Definition eda_shape.h:379
void SetLineColor(const COLOR4D &aColor)
Definition eda_shape.h:171
COLOR4D GetFillColor() const
Definition eda_shape.h:159
void SetRectangle(const long long int &aHeight, const long long int &aWidth)
virtual void SetShape(SHAPE_T aShape)
Definition eda_shape.h:174
void SwapShape(EDA_SHAPE *aImage)
std::vector< VECTOR2I > GetRectCorners() const
std::vector< SHAPE * > MakeLineEndingEffectiveShapes(int aLineWidth) const
Make the line-ending geometry associated with this shape.
void SetStartEndingWidth(int aWidth)
Definition eda_shape.h:192
std::vector< VECTOR2I > m_bezierPoints
Definition eda_shape.h:761
bool IsAnyFill() const
Definition eda_shape.h:118
void setPosition(const VECTOR2I &aPos)
EDA_ANGLE GetEllipseStartAngle() const
Definition eda_shape.h:414
const std::vector< VECTOR2I > & GetBezierPoints() const
Definition eda_shape.h:491
virtual bool IsProxyItem() const
Definition eda_shape.h:115
void computeArcBBox(BOX2I &aBBox) const
virtual void UpdateHatching() const
LINE_ENDING m_startEnding
Definition eda_shape.h:740
void SetRectangleWidth(const int &aWidth)
virtual void SetEllipseStartAngle(const EDA_ANGLE &aA)
Definition eda_shape.h:407
virtual 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:277
double Similarity(const EDA_SHAPE &aOther) const
const VECTOR2I & GetBezierC1() const
Definition eda_shape.h:365
std::optional< BEZIER< double > > ShortenedBezierCurve(int aLineWidth) const
Return the cubic Bezier curve shortened for line endings.
VECTOR2I m_end
Definition eda_shape.h:753
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:163
bool ShortenBodyPolyPoints(std::vector< VECTOR2I > &aPoints, bool aClosed, int aOutlineIdx, int aLineWidth) const
Apply line-ending body shortening to copied/generated polyline points.
bool ShortenArcForEndings(EDA_ANGLE &aStartAngle, EDA_ANGLE &aArcAngle, double aRadius, int aLineWidth) const
Shorten an arc body for line endings.
std::vector< VECTOR2D > ShortenedBezierPolyline(int aLineWidth) const
Return the flattened Bezier polyline after line-ending shortening.
VECTOR2I getPosition() const
bool IsClockwiseArc() const
STROKE_PARAMS m_stroke
Definition eda_shape.h:739
LINE_ENDING m_endEnding
Definition eda_shape.h:741
LINE_ENDING_STYLE GetEndEndingStyle() const
Definition eda_shape.h:186
void RebuildBezierToSegmentsPointsList()
Definition eda_shape.h:541
void SetPolyPoints(const std::vector< VECTOR2I > &aPoints)
EDA_SHAPE & operator=(const EDA_SHAPE &aOther)
VECTOR2I m_bezierC1
Definition eda_shape.h:758
FILL_T m_fill
Definition eda_shape.h:742
COLOR4D m_fillColor
Definition eda_shape.h:743
virtual void SetWidth(int aWidth)
EDA_ANGLE GetSegmentAngle() const
void SetStartEndingStrokeWidth(int aWidth)
Definition eda_shape.h:200
int GetCornerRadius() const
void SetFillMode(FILL_T aFill)
std::unique_ptr< SHAPE_POLY_SET > m_poly
Definition eda_shape.h:763
virtual void SetPolyShape(const SHAPE_POLY_SET &aShape)
Definition eda_shape.h:514
virtual void SetStart(const VECTOR2I &aStart)
Definition eda_shape.h:279
wxString getFriendlyName(FRAME_T aFrameType) const
long long int m_rectangleWidth
Definition eda_shape.h:749
VECTOR2I m_bezierC2
Definition eda_shape.h:759
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:789
static ENUM_MAP< T > & Instance()
Definition property.h:783
wxPGChoices & Choices()
Definition property.h:834
Class that other classes need to inherit from, in order to be inspectable.
Definition inspectable.h:39
A color representation with 4 components: red, green, blue, alpha.
Definition color4d.h:101
Decorative shape (arrowhead, circle, square) at the start or end of a graphic line,...
Definition line_ending.h:62
int GetShortenDepth(int aLineWidth) const
Return how far the line should be shortened at this ending.
int GetCurveOrientationDepth(int aLineWidth) const
Return how far along a curved body to look when orienting this ending.
LINE_ENDING_STYLE GetStyle() const
Definition line_ending.h:81
void GetShapes(const VECTOR2I &aPoint, const EDA_ANGLE &aTangent, int aLineWidth, std::vector< VECTOR2I > &aPolygon) const
Generate ending geometry as polygon vertices at the given point and direction.
int GetStrokeWidth() const
Outline stroke width.
PROPERTY_BASE & SetAvailableFunc(std::function< bool(INSPECTABLE *)> aFunc)
Set a callback function to determine whether an object provides this property.
Definition property.h:263
PROPERTY_BASE & SetValidator(PROPERTY_VALIDATOR_FN &&aValidator)
Definition property.h:368
PROPERTY_BASE & SetIsCopyable(bool aIsCopyable=true)
Definition property.h:359
PROPERTY_BASE & SetIsHiddenFromRulesEditor(bool aHide=true)
Definition property.h:332
Provide class metadata.
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:340
EDA_ANGLE GetCentralAngle() const
Get the "central angle" of the arc - this is the angle at the point of the "pie slice".
const VECTOR2I & GetArcMid() const
Definition shape_arc.h:116
EDA_ANGLE GetEndAngle() const
const VECTOR2I & GetP1() const
Definition shape_arc.h:115
double GetRadius() const
EDA_ANGLE GetStartAngle() const
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
virtual bool Collide(const VECTOR2I &aP, int aClearance=0, int *aActual=nullptr, VECTOR2I *aLocation=nullptr) const
Check if the boundary of shape (this) lies closer to the point aP than aClearance,...
Definition shape.h:181
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:574
constexpr extended_type SquaredEuclideanNorm() const
Compute the squared euclidean norm of the vector, which is defined as (x ** 2 + y ** 2).
Definition vector2d.h:312
T EuclideanNorm() const
Compute the Euclidean norm of the vector, which is defined as sqrt(x ** 2 + y ** 2).
Definition vector2d.h:281
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:448
static constexpr EDA_ANGLE ANGLE_90
Definition eda_angle.h:450
@ RADIANS_T
Definition eda_angle.h:31
@ DEGREES_T
Definition eda_angle.h:30
static constexpr EDA_ANGLE ANGLE_45
Definition eda_angle.h:449
static constexpr EDA_ANGLE ANGLE_270
Definition eda_angle.h:453
static constexpr EDA_ANGLE ANGLE_360
Definition eda_angle.h:454
static constexpr EDA_ANGLE ANGLE_180
Definition eda_angle.h:452
UI_FILL_MODE
Definition eda_fill.h:41
@ REVERSE_HATCH
Definition eda_fill.h:45
@ SOLID
Definition eda_fill.h:43
@ HATCH
Definition eda_fill.h:44
@ NONE
Definition eda_fill.h:42
@ CROSS_HATCH
Definition eda_fill.h:46
FILL_T
Definition eda_fill.h:29
@ NO_FILL
Definition eda_fill.h:30
@ REVERSE_HATCH
Definition eda_fill.h:35
@ HATCH
Definition eda_fill.h:34
@ CROSS_HATCH
Definition eda_fill.h:36
static void addLineEndingEffectiveShapes(std::vector< SHAPE * > &aShapes, const LINE_ENDING &aEnding, const VECTOR2I &aPoint, const EDA_ANGLE &aTangent, int aLineWidth)
static void addLineEndingPolygon(SHAPE_POLY_SET &aBuffer, const LINE_ENDING &aEnding, const VECTOR2I &aPoint, const EDA_ANGLE &aTangent, int aClearance, int aError, ERROR_LOC aErrorLoc, int aLineWidth)
#define TEST_PT(a, b)
static bool hasLineEnding(const LINE_ENDING &aStartEnding, const LINE_ENDING &aEndEnding)
#define TEST(a, b)
#define TEST_E(a, b)
static double bezierSpeedAt(const BEZIER< double > &aBezier, double aT)
static double findBezierTAtLength(const BEZIER< double > &aBezier, double aTargetLength, double aTotalLength)
static struct EDA_SHAPE_DESC _EDA_SHAPE_DESC
static double bezierLength(const BEZIER< double > &aBezier, double aT0, double aT1)
#define SWAPITEM(x)
#define sq(x)
static SHAPE_LINE_CHAIN lineEndingClosedChain(const std::vector< VECTOR2I > &aPolygon)
SHAPE_T
Definition eda_shape.h:54
@ UNDEFINED
Definition eda_shape.h:55
@ ELLIPSE
Definition eda_shape.h:62
@ SEGMENT
Definition eda_shape.h:56
@ RECTANGLE
Use RECTANGLE instead of RECT to avoid collision in a Windows header.
Definition eda_shape.h:57
@ ELLIPSE_ARC
Definition eda_shape.h:63
FRAME_T
The set of EDA_BASE_FRAME derivatives, typically stored in EDA_BASE_FRAME::m_Ident.
Definition frame_type.h:29
@ FRAME_SCH_SYMBOL_EDITOR
Definition frame_type.h:31
a few functions useful in geometry calculations.
LINE_ENDING_STYLE
Line ending styles for graphic lines, arcs, and beziers.
Definition line_ending.h:44
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.
SHAPE_LINE_CHAIN BoxToLineChain(const BOX2I &aBox)
Get a SHAPE_LINE_CHAIN representing the outline of a box.
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 PackLineEnding(types::LineEnding &aOutput, const LINE_ENDING &aInput, const EDA_IU_SCALE &aScale)
KICOMMON_API void PackColor(types::Color &aOutput, const KIGFX::COLOR4D &aInput)
KICOMMON_API void UnpackStroke(STROKE_PARAMS &aOutput, const types::StrokeAttributes &aInput, const EDA_IU_SCALE &aScale)
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 LINE_ENDING UnpackLineEnding(const types::LineEnding &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 void PackStroke(types::StrokeAttributes &aOutput, const STROKE_PARAMS &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:437
#define _HKI(x)
Definition page_info.cpp:40
#define IMPLEMENT_ENUM_TO_WXANY(type)
Definition property.h:888
#define NO_SETTER(owner, type)
Macro to define read-only fields (no setter method available)
Definition property.h:895
@ 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)
Helper macro to map type hashes to names.
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)
Utility functions for working with shapes.
LINE_STYLE
Dashed line types.
bool cw
VECTOR2I center
const SHAPE_LINE_CHAIN chain
int radius
VECTOR2I end
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:173
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:227
double RAD2DEG(double rad)
Definition trigo.h:173
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:506
@ PCB_TEXTBOX_T
class PCB_TEXTBOX, wrapped text on a layer
Definition typeinfo.h:85
@ PCB_TABLECELL_T
class PCB_TABLECELL, PCB_TEXTBOX for use in tables
Definition typeinfo.h:87
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:708
VECTOR2< double > VECTOR2D
Definition vector2d.h:707