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
621
622void EDA_SHAPE::SetRectangleHeight( const int& aHeight )
623{
624 switch ( m_shape )
625 {
627 m_rectangleHeight = aHeight;
629 break;
630
631 default:
633 }
634}
635
636
637void EDA_SHAPE::SetRectangleWidth( const int& aWidth )
638{
639 switch ( m_shape )
640 {
642 m_rectangleWidth = aWidth;
644 break;
645
646 default:
648 }
649}
650
651
652void EDA_SHAPE::SetRectangle( const long long int& aHeight, const long long int& aWidth )
653{
654 switch ( m_shape )
655 {
657 m_rectangleHeight = aHeight;
658 m_rectangleWidth = aWidth;
659 break;
660
661 default:
663 }
664}
665
666
668{
669 switch( m_shape )
670 {
671 case SHAPE_T::CIRCLE:
673 case SHAPE_T::ELLIPSE: return true;
674
675 case SHAPE_T::ARC:
676 case SHAPE_T::SEGMENT:
677 case SHAPE_T::ELLIPSE_ARC: return false;
678
679 case SHAPE_T::POLY:
680 if( GetPolyShape().IsEmpty() )
681 return false;
682 else
683 return GetPolyShape().Outline( 0 ).IsClosed();
684
685 case SHAPE_T::BEZIER:
686 if( m_bezierPoints.size() < 3 )
687 return false;
688 else
689 return m_bezierPoints[0] == m_bezierPoints[ m_bezierPoints.size() - 1 ];
690
691 default:
693 return false;
694 }
695}
696
697
699{
700 m_fill = aFill;
701 m_hatchingDirty = true;
702}
703
704
706{
707 switch( aFill )
708 {
713 default: SetFilled( true ); break;
714 }
715}
716
717
729
730
732{
734
735 if( !m_hatchingCache )
736 m_hatchingCache = std::make_unique<EDA_SHAPE_HATCH_CACHE_DATA>();
737
738 return m_hatchingCache->hatching;
739}
740
741
742const std::vector<SEG>& EDA_SHAPE::GetHatchLines() const
743{
745
746 if( !m_hatchingCache )
747 m_hatchingCache = std::make_unique<EDA_SHAPE_HATCH_CACHE_DATA>();
748
749 return m_hatchingCache->hatchLines;
750}
751
752
754{
755 if( !m_hatchingCache )
756 m_hatchingCache = std::make_unique<EDA_SHAPE_HATCH_CACHE_DATA>();
757
758 return m_hatchingCache->hatching;
759}
760
761
762std::vector<SEG>& EDA_SHAPE::hatchLines() const
763{
764 if( !m_hatchingCache )
765 m_hatchingCache = std::make_unique<EDA_SHAPE_HATCH_CACHE_DATA>();
766
767 return m_hatchingCache->hatchLines;
768}
769
770
772{
773 if( !m_hatchingDirty )
774 return;
775
776 std::vector<double> slopes;
777 int lineWidth = GetHatchLineWidth();
778 int spacing = GetHatchLineSpacing();
779 SHAPE_POLY_SET shapeBuffer;
780
781 // Validate state before clearing cached hatching. If we can't regenerate, keep existing cache.
782 if( isMoving() )
783 return;
784
786 slopes = { 1.0, -1.0 };
787 else if( GetFillMode() == FILL_T::HATCH )
788 slopes = { -1.0 };
789 else if( GetFillMode() == FILL_T::REVERSE_HATCH )
790 slopes = { 1.0 };
791 else
792 return;
793
794 if( spacing == 0 )
795 return;
796
797 switch( m_shape )
798 {
799 case SHAPE_T::ARC:
800 case SHAPE_T::SEGMENT:
801 case SHAPE_T::BEZIER:
802 case SHAPE_T::ELLIPSE_ARC: return;
803
805 {
807 rr.TransformToPolygon( shapeBuffer, getMaxError() );
808 }
809 break;
810
811 case SHAPE_T::CIRCLE:
813 break;
814
815 case SHAPE_T::POLY:
816 if( GetPolyShape().OutlineCount() == 0 )
817 return;
818
819 shapeBuffer = GetPolyShape().CloneDropTriangulation();
820
821 for( int ii = 0; ii < shapeBuffer.OutlineCount(); ++ii )
822 {
823 SHAPE_LINE_CHAIN& outline = shapeBuffer.Outline( ii );
824
825 if( outline.IsClosed() )
826 continue;
827
828 if( outline.PointCount() < 3 )
829 continue;
830
831 outline.SetClosed( true );
832 }
833
834 break;
835
836 case SHAPE_T::ELLIPSE:
837 {
838 // Hatching only applies to closed, fillable shapes.
841 chain.SetClosed( true );
842 shapeBuffer.AddOutline( chain );
843 break;
844 }
845
846 default:
848 return;
849 }
850
851 shapeBuffer.ClearArcs();
852
853 // Clear cached hatching only after all validation passes.
854 // This prevents flickering when early returns would otherwise leave empty hatching.
856 hatchLines().clear();
857
858 BOX2I extents = shapeBuffer.BBox();
859 int majorAxis = std::max( extents.GetWidth(), extents.GetHeight() );
860
861 if( majorAxis / spacing > 100 )
862 spacing = majorAxis / 100;
863
865
866 if( !knockouts.IsEmpty() )
867 {
868 shapeBuffer.BooleanSubtract( knockouts );
869 shapeBuffer.Fracture();
870 }
871
872 // Generate hatch lines for stroke-based rendering. All hatch types use line segments.
873 std::vector<SEG> hatchSegs = shapeBuffer.GenerateHatchLines( slopes, spacing, -1 );
874 hatchLines() = hatchSegs;
875
876 // Also generate polygon representation for exports, 3D viewer, and hit testing
878 {
879 for( const SEG& seg : hatchSegs )
880 {
881 // We don't really need the rounded ends at all, so don't spend any extra time on them
882 int maxError = lineWidth;
883
884 TransformOvalToPolygon( hatching(), seg.A, seg.B, lineWidth, maxError,
885 ERROR_INSIDE );
886 }
887
888 hatching().Fracture();
889 m_hatchingDirty = false;
890 }
891 else
892 {
893 // Generate a grid of holes for a cross-hatch polygon representation.
894 // This is used for exports, 3D viewer, and hit testing.
895
896 int gridsize = spacing;
897 int hole_size = gridsize - GetHatchLineWidth();
898
899 hatching() = shapeBuffer.CloneDropTriangulation();
901
902 // Build hole shape
903 SHAPE_LINE_CHAIN hole_base;
904 VECTOR2I corner( 0, 0 );;
905 hole_base.Append( corner );
906 corner.x += hole_size;
907 hole_base.Append( corner );
908 corner.y += hole_size;
909 hole_base.Append( corner );
910 corner.x = 0;
911 hole_base.Append( corner );
912 hole_base.SetClosed( true );
913
914 // Build holes
915 BOX2I bbox = hatching().BBox( 0 );
916 SHAPE_POLY_SET holes;
917
918 int x_offset = bbox.GetX() - ( bbox.GetX() ) % gridsize - gridsize;
919 int y_offset = bbox.GetY() - ( bbox.GetY() ) % gridsize - gridsize;
920
921 for( int xx = x_offset; xx <= bbox.GetRight(); xx += gridsize )
922 {
923 for( int yy = y_offset; yy <= bbox.GetBottom(); yy += gridsize )
924 {
925 SHAPE_LINE_CHAIN hole( hole_base );
926 hole.Move( VECTOR2I( xx, yy ) );
927 holes.AddOutline( hole );
928 }
929 }
930
931 hatching().BooleanSubtract( holes );
932 hatching().Fracture();
933
934 // Must re-rotate after Fracture(). Clipper struggles mightily with fracturing
935 // 45-degree holes.
937
938 if( !knockouts.IsEmpty() )
939 {
940 hatching().BooleanSubtract( knockouts );
941 hatching().Fracture();
942 }
943
944 m_hatchingDirty = false;
945 }
946}
947
948
949void EDA_SHAPE::move( const VECTOR2I& aMoveVector )
950{
951 switch ( m_shape )
952 {
953 case SHAPE_T::ARC:
954 m_arcCenter += aMoveVector;
955 m_arcMidData.center += aMoveVector;
956 m_arcMidData.start += aMoveVector;
957 m_arcMidData.end += aMoveVector;
958 m_arcMidData.mid += aMoveVector;
960
961 case SHAPE_T::SEGMENT:
963 case SHAPE_T::CIRCLE:
964 m_start += aMoveVector;
965 m_end += aMoveVector;
966 break;
967
968 case SHAPE_T::POLY:
969 GetPolyShape().Move( aMoveVector );
970 break;
971
972 case SHAPE_T::BEZIER:
973 m_start += aMoveVector;
974 m_end += aMoveVector;
975 m_bezierC1 += aMoveVector;
976 m_bezierC2 += aMoveVector;
977
978 for( VECTOR2I& pt : m_bezierPoints )
979 pt += aMoveVector;
980
981 break;
982
983 case SHAPE_T::ELLIPSE:
985 m_ellipse.Center += aMoveVector;
986 m_start += aMoveVector;
987 m_end += aMoveVector;
988 break;
989
990 default:
992 break;
993 }
994
995 // Translate the cached hatch geometry instead of leaving it stale. The hatch pattern is
996 // invariant under translation, so shifting line endpoints is sufficient and keeps the
997 // display correct during interactive moves without hitting GenerateHatchLines().
998 if( m_hatchingCache )
999 {
1000 for( SEG& seg : m_hatchingCache->hatchLines )
1001 {
1002 seg.A += aMoveVector;
1003 seg.B += aMoveVector;
1004 }
1005
1006 m_hatchingCache->hatching.Move( aMoveVector );
1007 }
1008
1009 m_hatchingDirty = true;
1010}
1011
1012
1013void EDA_SHAPE::scale( double aScale )
1014{
1015 auto scalePt =
1016 [&]( VECTOR2I& pt )
1017 {
1018 pt.x = KiROUND( pt.x * aScale );
1019 pt.y = KiROUND( pt.y * aScale );
1020 };
1021
1022 switch( m_shape )
1023 {
1024 case SHAPE_T::ARC:
1025 scalePt( m_arcCenter );
1027
1028 case SHAPE_T::SEGMENT:
1029 case SHAPE_T::RECTANGLE:
1030 case SHAPE_T::CIRCLE:
1031 scalePt( m_start );
1032 scalePt( m_end );
1033 break;
1034
1035 case SHAPE_T::POLY: // polygon
1036 {
1037 std::vector<VECTOR2I> pts;
1038
1039 for( int ii = 0; ii < GetPolyShape().OutlineCount(); ++ ii )
1040 {
1041 for( const VECTOR2I& pt : GetPolyShape().Outline( ii ).CPoints() )
1042 {
1043 pts.emplace_back( pt );
1044 scalePt( pts.back() );
1045 }
1046 }
1047
1048 SetPolyPoints( pts );
1049 }
1050 break;
1051
1052 case SHAPE_T::BEZIER:
1053 scalePt( m_start );
1054 scalePt( m_end );
1055 scalePt( m_bezierC1 );
1056 scalePt( m_bezierC2 );
1058 break;
1059
1060 case SHAPE_T::ELLIPSE:
1062 scalePt( m_ellipse.Center );
1063 m_ellipse.MajorRadius = KiROUND( std::abs( m_ellipse.MajorRadius * aScale ) );
1064 m_ellipse.MinorRadius = KiROUND( std::abs( m_ellipse.MinorRadius * aScale ) );
1066 break;
1067
1068 default:
1070 break;
1071 }
1072
1073 m_hatchingDirty = true;
1074}
1075
1076
1077void EDA_SHAPE::rotate( const VECTOR2I& aRotCentre, const EDA_ANGLE& aAngle )
1078{
1079 switch( m_shape )
1080 {
1081 case SHAPE_T::SEGMENT:
1082 case SHAPE_T::CIRCLE:
1083 RotatePoint( m_start, aRotCentre, aAngle );
1084 RotatePoint( m_end, aRotCentre, aAngle );
1085 break;
1086
1087 case SHAPE_T::ARC:
1088 RotatePoint( m_start, aRotCentre, aAngle );
1089 RotatePoint( m_end, aRotCentre, aAngle );
1090 RotatePoint( m_arcCenter, aRotCentre, aAngle );
1091 RotatePoint( m_arcMidData.start, aRotCentre, aAngle );
1092 RotatePoint( m_arcMidData.end, aRotCentre, aAngle );
1093 RotatePoint( m_arcMidData.mid, aRotCentre, aAngle );
1094 RotatePoint( m_arcMidData.center, aRotCentre, aAngle );
1095 break;
1096
1097 case SHAPE_T::RECTANGLE:
1098 if( aAngle.IsCardinal() )
1099 {
1100 RotatePoint( m_start, aRotCentre, aAngle );
1101 RotatePoint( m_end, aRotCentre, aAngle );
1102 }
1103 else
1104 {
1105 // Convert non-cardinally-rotated rect to a polygon.
1109 GetPolyShape().Rotate( aAngle, aRotCentre );
1110 }
1111
1112 break;
1113
1114 case SHAPE_T::POLY:
1115 GetPolyShape().Rotate( aAngle, aRotCentre );
1116 break;
1117
1118 case SHAPE_T::BEZIER:
1119 RotatePoint( m_start, aRotCentre, aAngle );
1120 RotatePoint( m_end, aRotCentre, aAngle );
1121 RotatePoint( m_bezierC1, aRotCentre, aAngle );
1122 RotatePoint( m_bezierC2, aRotCentre, aAngle );
1123
1124 for( VECTOR2I& pt : m_bezierPoints )
1125 RotatePoint( pt, aRotCentre, aAngle);
1126
1127 break;
1128
1129 case SHAPE_T::ELLIPSE:
1131 RotatePoint( m_ellipse.Center, aRotCentre, aAngle );
1132
1133 // Ellipse rotation is the CCW angle of the major axis in standard math
1134 // coordinates (Y-up). RotatePoint uses KiCad's Y-down screen convention,
1135 // so a positive aAngle rotates visually CCW on screen but corresponds to
1136 // a negative rotation in the math frame. Hence -= rather than +=.
1137 m_ellipse.Rotation -= aAngle;
1139 break;
1140
1141 default:
1143 break;
1144 }
1145
1146 m_hatchingDirty = true;
1147}
1148
1149
1150void EDA_SHAPE::flip( const VECTOR2I& aCentre, FLIP_DIRECTION aFlipDirection )
1151{
1152 switch ( m_shape )
1153 {
1154 case SHAPE_T::SEGMENT:
1155 case SHAPE_T::RECTANGLE:
1156 MIRROR( m_start, aCentre, aFlipDirection );
1157 MIRROR( m_end, aCentre, aFlipDirection );
1158 break;
1159
1160 case SHAPE_T::CIRCLE:
1161 MIRROR( m_start, aCentre, aFlipDirection );
1162 MIRROR( m_end, aCentre, aFlipDirection );
1163 break;
1164
1165 case SHAPE_T::ARC:
1166 MIRROR( m_start, aCentre, aFlipDirection );
1167 MIRROR( m_end, aCentre, aFlipDirection );
1168 MIRROR( m_arcCenter, aCentre, aFlipDirection );
1169
1170 std::swap( m_start, m_end );
1171 break;
1172
1173 case SHAPE_T::POLY:
1174 GetPolyShape().Mirror( aCentre, aFlipDirection );
1175 break;
1176
1177 case SHAPE_T::BEZIER:
1178 MIRROR( m_start, aCentre, aFlipDirection );
1179 MIRROR( m_end, aCentre, aFlipDirection );
1180 MIRROR( m_bezierC1, aCentre, aFlipDirection );
1181 MIRROR( m_bezierC2, aCentre, aFlipDirection );
1182
1184 break;
1185
1186 case SHAPE_T::ELLIPSE:
1188 m_ellipse.Mirror( aCentre, aFlipDirection );
1190 break;
1191
1192 default:
1194 break;
1195 }
1196
1197 m_hatchingDirty = true;
1198}
1199
1200
1202{
1203 // Has meaning only for SHAPE_T::BEZIER
1204 if( m_shape != SHAPE_T::BEZIER )
1205 {
1206 m_bezierPoints.clear();
1207 return;
1208 }
1209
1210 // Rebuild the m_BezierPoints vertex list that approximate the Bezier curve
1212}
1213
1214
1215const std::vector<VECTOR2I> EDA_SHAPE::buildBezierToSegmentsPointsList( int aMaxError ) const
1216{
1217 std::vector<VECTOR2I> bezierPoints;
1218
1219 // Rebuild the m_BezierPoints vertex list that approximate the Bezier curve
1220 std::vector<VECTOR2I> ctrlPoints = { m_start, m_bezierC1, m_bezierC2, m_end };
1221 BEZIER_POLY converter( ctrlPoints );
1222 converter.GetPoly( bezierPoints, aMaxError );
1223
1224 return bezierPoints;
1225}
1226
1227
1229{
1231 return SHAPE_ELLIPSE( m_ellipse.Center, m_ellipse.MajorRadius, m_ellipse.MinorRadius, m_ellipse.Rotation,
1232 m_ellipse.StartAngle, m_ellipse.EndAngle );
1233
1234 return SHAPE_ELLIPSE( m_ellipse.Center, m_ellipse.MajorRadius, m_ellipse.MinorRadius, m_ellipse.Rotation );
1235}
1236
1237
1239{
1240 if( m_editState != 0 )
1241 return;
1242
1243 if( m_shape == SHAPE_T::ELLIPSE )
1244 {
1245 const double phi = m_ellipse.Rotation.AsRadians();
1246 m_start = m_ellipse.Center;
1247 m_end = m_start
1248 + VECTOR2I( KiROUND( m_ellipse.MajorRadius * std::cos( phi ) ),
1249 KiROUND( m_ellipse.MajorRadius * std::sin( phi ) ) );
1250 return;
1251 }
1252
1254 return;
1255
1256 m_arcCenter = m_ellipse.Center;
1257
1258 const double a = m_ellipse.MajorRadius;
1259 const double b = m_ellipse.MinorRadius;
1260 const double phi = m_ellipse.Rotation.AsRadians();
1261 const double cosPhi = std::cos( phi );
1262 const double sinPhi = std::sin( phi );
1263 const VECTOR2I c = m_ellipse.Center;
1264
1265 auto eval = [&]( double theta ) -> VECTOR2I
1266 {
1267 const double lx = a * std::cos( theta );
1268 const double ly = b * std::sin( theta );
1269 return c + VECTOR2I( KiROUND( lx * cosPhi - ly * sinPhi ), KiROUND( lx * sinPhi + ly * cosPhi ) );
1270 };
1271
1272 m_start = eval( m_ellipse.StartAngle.AsRadians() );
1273 m_end = eval( m_ellipse.EndAngle.AsRadians() );
1274}
1275
1276
1278{
1279 switch( m_shape )
1280 {
1281 case SHAPE_T::ARC:
1282 return m_arcCenter;
1283
1284 case SHAPE_T::CIRCLE:
1285 return m_start;
1286
1287 case SHAPE_T::SEGMENT:
1288 // Midpoint of the line
1289 return ( m_start + m_end ) / 2;
1290
1291 case SHAPE_T::POLY:
1292 case SHAPE_T::RECTANGLE:
1293 case SHAPE_T::BEZIER:
1294 return getBoundingBox().Centre();
1295
1296 case SHAPE_T::ELLIPSE:
1297 case SHAPE_T::ELLIPSE_ARC: return m_ellipse.Center;
1298
1299 default:
1301 return VECTOR2I();
1302 }
1303}
1304
1305
1306void EDA_SHAPE::SetCenter( const VECTOR2I& aCenter )
1307{
1308 switch( m_shape )
1309 {
1310 case SHAPE_T::ARC:
1311 m_arcCenter = aCenter;
1312 break;
1313
1314 case SHAPE_T::CIRCLE:
1315 {
1316 // Route through SetStart / SetEnd so subclasses sync lib coords.
1317 const VECTOR2I delta = aCenter - m_start;
1318 SetEnd( m_end + delta );
1319 SetStart( aCenter );
1320 m_hatchingDirty = true;
1321 break;
1322 }
1323
1324 case SHAPE_T::ELLIPSE:
1326 m_ellipse.Center = aCenter;
1327 m_hatchingDirty = true;
1329 break;
1330
1331 default:
1333 }
1334}
1335
1336
1338{
1339 // If none of the input data have changed since we loaded the arc, keep the original mid point data
1340 // to minimize churn
1341 if( m_arcMidData.start == m_start && m_arcMidData.end == m_end && m_arcMidData.center == m_arcCenter )
1342 return m_arcMidData.mid;
1343
1344 VECTOR2I mid = m_start;
1345 RotatePoint( mid, m_arcCenter, -GetArcAngle() / 2.0 );
1346 return mid;
1347}
1348
1349
1350void EDA_SHAPE::CalcArcAngles( EDA_ANGLE& aStartAngle, EDA_ANGLE& aEndAngle ) const
1351{
1352 VECTOR2D startRadial( GetStart() - getCenter() );
1353 VECTOR2D endRadial( GetEnd() - getCenter() );
1354
1355 aStartAngle = EDA_ANGLE( startRadial );
1356 aEndAngle = EDA_ANGLE( endRadial );
1357
1358 if( aEndAngle == aStartAngle )
1359 aEndAngle = aStartAngle + ANGLE_360; // ring, not null
1360
1361 while( aEndAngle < aStartAngle )
1362 aEndAngle += ANGLE_360;
1363}
1364
1365
1367{
1368 double radius = 0.0;
1369
1370 switch( m_shape )
1371 {
1372 case SHAPE_T::ARC:
1373 radius = m_arcCenter.Distance( m_start );
1374 break;
1375
1376 case SHAPE_T::CIRCLE:
1377 radius = m_start.Distance( m_end );
1378 break;
1379
1380 default:
1382 }
1383
1384 // don't allow degenerate circles/arcs
1385 if( radius > (double) INT_MAX / 2.0 )
1386 radius = (double) INT_MAX / 2.0;
1387
1388 return std::max( 1, KiROUND( radius ) );
1389}
1390
1391
1392void EDA_SHAPE::SetCachedArcData( const VECTOR2I& aStart, const VECTOR2I& aMid,
1393 const VECTOR2I& aEnd, const VECTOR2I& aCenter )
1394{
1395 m_arcMidData.start = aStart;
1396 m_arcMidData.end = aEnd;
1397 m_arcMidData.center = aCenter;
1398 m_arcMidData.mid = aMid;
1399}
1400
1401
1402void EDA_SHAPE::SetArcGeometry( const VECTOR2I& aStart, const VECTOR2I& aMid, const VECTOR2I& aEnd )
1403{
1404 m_arcMidData = {};
1405 m_start = aStart;
1406 m_end = aEnd;
1407 m_arcCenter = CalcArcCenter( aStart, aMid, aEnd );
1408 VECTOR2I new_mid = GetArcMid();
1409
1410 m_endsSwapped = false;
1411
1412 // Watch the ordering here. GetArcMid above needs to be called prior to initializing the
1413 // m_arcMidData structure in order to ensure we get the calculated variant, not the cached
1414 SetCachedArcData( aStart, aMid, aEnd, m_arcCenter );
1415
1416 /*
1417 * If the input winding doesn't match our internal winding, the calculated midpoint will end
1418 * up on the other side of the arc. In this case, we need to flip the start/end points and
1419 * flag this change for the system.
1420 */
1421 VECTOR2D dist( new_mid - aMid );
1422 VECTOR2D dist2( new_mid - m_arcCenter );
1423
1424 if( dist.SquaredEuclideanNorm() > dist2.SquaredEuclideanNorm() )
1425 {
1426 std::swap( m_start, m_end );
1427 m_endsSwapped = true;
1428 }
1429}
1430
1431
1433{
1434 EDA_ANGLE angle( atan2( static_cast<double>( GetStart().y - GetEnd().y ),
1435 static_cast<double>( GetEnd().x - GetStart().x ) ), RADIANS_T );
1436
1437 return angle;
1438}
1439
1440
1442{
1443 EDA_ANGLE startAngle;
1444 EDA_ANGLE endAngle;
1445
1446 CalcArcAngles( startAngle, endAngle );
1447
1448 return endAngle - startAngle;
1449}
1450
1451
1453{
1454 if( m_shape == SHAPE_T::ARC )
1455 {
1456 VECTOR2D mid = GetArcMid();
1457
1458 double orient = ( mid.x - m_start.x ) * ( m_end.y - m_start.y )
1459 - ( mid.y - m_start.y ) * ( m_end.x - m_start.x );
1460
1461 return orient < 0;
1462 }
1463
1465 return false;
1466}
1467
1468
1469void EDA_SHAPE::SetArcAngleAndEnd( const EDA_ANGLE& aAngle, bool aCheckNegativeAngle )
1470{
1471 EDA_ANGLE angle( aAngle );
1472
1473 m_end = m_start;
1475
1476 if( aCheckNegativeAngle && aAngle < ANGLE_0 )
1477 {
1478 std::swap( m_start, m_end );
1479 m_endsSwapped = true;
1480 }
1481}
1482
1483
1484wxString EDA_SHAPE::getFriendlyName( FRAME_T aFrameType ) const
1485{
1486 if( IsProxyItem() )
1487 {
1488 switch( m_shape )
1489 {
1490 case SHAPE_T::RECTANGLE: return _( "Pad Number Box" );
1491 case SHAPE_T::SEGMENT: return _( "Thermal Spoke Template" );
1492 default: return _( "Unrecognized" );
1493 }
1494 }
1495 else if( aFrameType == FRAME_SCH_SYMBOL_EDITOR && m_shape == SHAPE_T::POLY )
1496 {
1497 return _( "Connected Lines" );
1498 }
1499 else
1500 {
1501 switch( m_shape )
1502 {
1503 case SHAPE_T::CIRCLE: return _( "Circle" );
1504 case SHAPE_T::ARC: return _( "Arc" );
1505 case SHAPE_T::BEZIER: return _( "Curve" );
1506 case SHAPE_T::POLY: return _( "Polygon" );
1507 case SHAPE_T::RECTANGLE: return _( "Rectangle" );
1508 case SHAPE_T::SEGMENT: return _( "Segment" );
1509 case SHAPE_T::ELLIPSE: return _( "Ellipse" );
1510 case SHAPE_T::ELLIPSE_ARC: return _( "Elliptical Arc" );
1511 default: return _( "Unrecognized" );
1512 }
1513 }
1514}
1515
1516
1517void EDA_SHAPE::ShapeGetMsgPanelInfo( EDA_DRAW_FRAME* aFrame, std::vector<MSG_PANEL_ITEM>& aList )
1518{
1519 wxString msg;
1520
1521 wxString shape = _( "Shape" );
1522 aList.emplace_back( shape, getFriendlyName( aFrame->GetFrameType() ) );
1523
1524 switch( m_shape )
1525 {
1526 case SHAPE_T::CIRCLE:
1527 aList.emplace_back( _( "Radius" ), aFrame->MessageTextFromValue( GetRadius() ) );
1528 break;
1529
1530 case SHAPE_T::ARC:
1531 aList.emplace_back( _( "Length" ), aFrame->MessageTextFromValue( GetLength() ) );
1532
1534 aList.emplace_back( _( "Angle" ), msg );
1535
1536 aList.emplace_back( _( "Radius" ), aFrame->MessageTextFromValue( GetRadius() ) );
1537 break;
1538
1539 case SHAPE_T::BEZIER:
1540 aList.emplace_back( _( "Length" ), aFrame->MessageTextFromValue( GetLength() ) );
1541 break;
1542
1543 case SHAPE_T::ELLIPSE:
1544 aList.emplace_back( _( "Length" ), aFrame->MessageTextFromValue( GetLength() ) );
1545 aList.emplace_back( _( "Major Radius" ), aFrame->MessageTextFromValue( GetEllipseMajorRadius() ) );
1546 aList.emplace_back( _( "Minor Radius" ), aFrame->MessageTextFromValue( GetEllipseMinorRadius() ) );
1547 aList.emplace_back( _( "Rotation" ), EDA_UNIT_UTILS::UI::MessageTextFromValue( GetEllipseRotation() ) );
1548 break;
1549
1551 aList.emplace_back( _( "Length" ), aFrame->MessageTextFromValue( GetLength() ) );
1552 aList.emplace_back( _( "Major Radius" ), aFrame->MessageTextFromValue( GetEllipseMajorRadius() ) );
1553 aList.emplace_back( _( "Minor Radius" ), aFrame->MessageTextFromValue( GetEllipseMinorRadius() ) );
1554 aList.emplace_back( _( "Rotation" ), EDA_UNIT_UTILS::UI::MessageTextFromValue( GetEllipseRotation() ) );
1555 aList.emplace_back( _( "Start Angle" ), EDA_UNIT_UTILS::UI::MessageTextFromValue( GetEllipseStartAngle() ) );
1556 aList.emplace_back( _( "End Angle" ), EDA_UNIT_UTILS::UI::MessageTextFromValue( GetEllipseEndAngle() ) );
1557 break;
1558
1559 case SHAPE_T::POLY:
1560 {
1561 int pointCount = 0;
1562
1563 if( GetPolyShape().OutlineCount() > 0 )
1564 pointCount = GetPolyShape().Outline( 0 ).PointCount();
1565
1566 msg.Printf( wxS( "%d" ), pointCount );
1567 aList.emplace_back( _( "Points" ), msg );
1568 break;
1569 }
1570 case SHAPE_T::RECTANGLE:
1571 aList.emplace_back( _( "Width" ), aFrame->MessageTextFromValue( std::abs( GetEnd().x - GetStart().x ) ) );
1572 aList.emplace_back( _( "Height" ), aFrame->MessageTextFromValue( std::abs( GetEnd().y - GetStart().y ) ) );
1573 break;
1574
1575 case SHAPE_T::SEGMENT:
1576 {
1577 aList.emplace_back( _( "Length" ), aFrame->MessageTextFromValue( GetStart().Distance( GetEnd() ) ));
1578
1579 // angle counter-clockwise from 3'o-clock
1580 EDA_ANGLE angle( atan2( (double)( GetStart().y - GetEnd().y ), (double)( GetEnd().x - GetStart().x ) ),
1581 RADIANS_T );
1582 aList.emplace_back( _( "Angle" ), EDA_UNIT_UTILS::UI::MessageTextFromValue( angle ) );
1583 break;
1584 }
1585
1586 default:
1587 break;
1588 }
1589
1590 m_stroke.GetMsgPanelInfo( aFrame, aList );
1591}
1592
1593
1595{
1596 BOX2I bbox;
1597
1598 switch( m_shape )
1599 {
1600 case SHAPE_T::RECTANGLE:
1601 for( VECTOR2I& pt : GetRectCorners() )
1602 bbox.Merge( pt );
1603
1604 break;
1605
1606 case SHAPE_T::SEGMENT:
1607 bbox.SetOrigin( GetStart() );
1608 bbox.SetEnd( GetEnd() );
1609 break;
1610
1611 case SHAPE_T::CIRCLE:
1612 bbox.SetOrigin( GetStart() );
1613 bbox.Inflate( GetRadius() );
1614 break;
1615
1616 case SHAPE_T::ARC:
1617 computeArcBBox( bbox );
1618 break;
1619
1620 case SHAPE_T::ELLIPSE:
1621 case SHAPE_T::ELLIPSE_ARC: bbox = buildShapeEllipse().BBox( 0 ); break;
1622
1623 case SHAPE_T::POLY:
1624 if( GetPolyShape().IsEmpty() )
1625 break;
1626
1627 for( auto iter = GetPolyShape().CIterate(); iter; iter++ )
1628 bbox.Merge( *iter );
1629
1630 break;
1631
1632 case SHAPE_T::BEZIER:
1633 // Bezier BBoxes are not trivial to compute, so we approximate it by
1634 // using the bounding box of the curve (not control!) points.
1635 for( const VECTOR2I& pt : m_bezierPoints )
1636 bbox.Merge( pt );
1637
1638 break;
1639
1640 default:
1642 break;
1643 }
1644
1645 bbox.Inflate( std::max( 0, GetWidth() ) / 2 );
1646 bbox.Normalize();
1647
1648 return bbox;
1649}
1650
1651
1652static bool hasLineEnding( const LINE_ENDING& aStartEnding, const LINE_ENDING& aEndEnding );
1653
1654
1655bool EDA_SHAPE::hitTest( const VECTOR2I& aPosition, int aAccuracy ) const
1656{
1658 {
1660 const SHAPE& hitShape = shape;
1661
1662 return hitShape.Collide( aPosition, std::max( 0, aAccuracy ) );
1663 }
1664
1665 double maxdist = aAccuracy;
1666
1667 if( GetWidth() > 0 )
1668 maxdist += GetWidth() / 2.0;
1669
1670 switch( m_shape )
1671 {
1672 case SHAPE_T::CIRCLE:
1673 {
1674 double radius = GetRadius();
1675 double dist = aPosition.Distance( getCenter() );
1676
1677 if( IsFilledForHitTesting() )
1678 return dist <= radius + maxdist; // Filled circle hit-test
1679 else if( abs( radius - dist ) <= maxdist ) // Ring hit-test
1680 return true;
1681
1682 if( IsHatchedFill() && GetHatching().Collide( aPosition, maxdist ) )
1683 return true;
1684
1685 return false;
1686 }
1687
1688 case SHAPE_T::ARC:
1689 {
1690 if( aPosition.Distance( m_start ) <= maxdist )
1691 return true;
1692
1693 if( aPosition.Distance( m_end ) <= maxdist )
1694 return true;
1695
1696 double radius = GetRadius();
1697 VECTOR2D relPos( VECTOR2D( aPosition ) - getCenter() );
1698 double dist = relPos.EuclideanNorm();
1699
1700 if( IsFilledForHitTesting() )
1701 {
1702 // Check distance from arc center
1703 if( dist > radius + maxdist )
1704 return false;
1705 }
1706 else
1707 {
1708 // Check distance from arc circumference
1709 if( abs( radius - dist ) > maxdist )
1710 return false;
1711 }
1712
1713 // Finally, check to see if it's within arc's swept angle.
1714 EDA_ANGLE startAngle;
1715 EDA_ANGLE endAngle;
1716 CalcArcAngles( startAngle, endAngle );
1717
1718 EDA_ANGLE relPosAngle( relPos );
1719
1720 startAngle.Normalize();
1721 endAngle.Normalize();
1722 relPosAngle.Normalize();
1723
1724 if( endAngle > startAngle )
1725 return relPosAngle >= startAngle && relPosAngle <= endAngle;
1726 else
1727 return relPosAngle >= startAngle || relPosAngle <= endAngle;
1728 }
1729
1730 case SHAPE_T::BEZIER:
1731 {
1732 const std::vector<VECTOR2I>* pts = &m_bezierPoints;
1733 std::vector<VECTOR2I> updatedBezierPoints;
1734
1735 if( m_bezierPoints.empty() )
1736 {
1738 converter.GetPoly( updatedBezierPoints, aAccuracy / 2 );
1739 pts = &updatedBezierPoints;
1740 }
1741
1742 for( unsigned int i = 1; i < pts->size(); i++ )
1743 {
1744 if( TestSegmentHit( aPosition, ( *pts )[i - 1], ( *pts )[i], maxdist ) )
1745 return true;
1746 }
1747
1748 return false;
1749 }
1750 case SHAPE_T::SEGMENT:
1751 return TestSegmentHit( aPosition, GetStart(), GetEnd(), maxdist );
1752
1753 case SHAPE_T::RECTANGLE:
1754 if( IsProxyItem() || IsFilledForHitTesting() ) // Filled rect hit-test
1755 {
1756 SHAPE_POLY_SET poly;
1757 poly.NewOutline();
1758
1759 for( const VECTOR2I& pt : GetRectCorners() )
1760 poly.Append( pt );
1761
1762 return poly.Collide( aPosition, maxdist );
1763 }
1764 else if( m_cornerRadius > 0 )
1765 {
1767 SHAPE_POLY_SET poly;
1768 rr.TransformToPolygon( poly, getMaxError() );
1769
1770 if( poly.CollideEdge( aPosition, nullptr, maxdist ) )
1771 return true;
1772 }
1773 else
1774 {
1775 std::vector<VECTOR2I> pts = GetRectCorners();
1776
1777 if( TestSegmentHit( aPosition, pts[0], pts[1], maxdist )
1778 || TestSegmentHit( aPosition, pts[1], pts[2], maxdist )
1779 || TestSegmentHit( aPosition, pts[2], pts[3], maxdist )
1780 || TestSegmentHit( aPosition, pts[3], pts[0], maxdist ) )
1781 {
1782 return true;
1783 }
1784 }
1785
1786 if( IsHatchedFill() && GetHatching().Collide( aPosition, maxdist ) )
1787 return true;
1788
1789 return false;
1790
1791 case SHAPE_T::POLY:
1792 if( GetPolyShape().OutlineCount() < 1 ) // empty poly
1793 return false;
1794
1795 if( IsFilledForHitTesting() )
1796 {
1797 if( !GetPolyShape().COutline( 0 ).IsClosed() )
1798 {
1799 // Only one outline is expected
1800 SHAPE_LINE_CHAIN copy( GetPolyShape().COutline( 0 ) );
1801 copy.SetClosed( true );
1802 return copy.Collide( aPosition, maxdist );
1803 }
1804 else
1805 {
1806 return GetPolyShape().Collide( aPosition, maxdist );
1807 }
1808 }
1809 else
1810 {
1811 if( GetPolyShape().CollideEdge( aPosition, nullptr, maxdist ) )
1812 return true;
1813
1814 if( IsHatchedFill() && GetHatching().Collide( aPosition, maxdist ) )
1815 return true;
1816
1817 return false;
1818 }
1819
1820 case SHAPE_T::ELLIPSE:
1822 {
1824
1825 const double maxdistSq = maxdist * maxdist;
1826
1828 {
1829 // Filled closed ellipse
1830 if( static_cast<double>( e.SquaredDistance( aPosition, false ) ) <= maxdistSq )
1831 return true;
1832 }
1833 else
1834 {
1835 // Unfilled ring or arc
1836 if( static_cast<double>( e.SquaredDistance( aPosition, true ) ) <= maxdistSq )
1837 return true;
1838 }
1839
1840 if( IsHatchedFill() && GetHatching().Collide( aPosition, maxdist ) )
1841 return true;
1842
1843 return false;
1844 }
1845
1846 default:
1848 return false;
1849 }
1850}
1851
1852
1853bool EDA_SHAPE::hitTest( const BOX2I& aRect, bool aContained, int aAccuracy ) const
1854{
1855 BOX2I arect = aRect;
1856 arect.Normalize();
1857 arect.Inflate( aAccuracy );
1858
1860 {
1861 SHAPE_LINE_CHAIN selection = KIGEOM::BoxToLineChain( arect );
1863
1864 return KIGEOM::ShapeHitTest( selection, shape, aContained );
1865 }
1866
1867 BOX2I bbox = getBoundingBox();
1868
1869 auto checkOutline =
1870 [&]( const SHAPE_LINE_CHAIN& outline )
1871 {
1872 int count = (int) outline.GetPointCount();
1873
1874 for( int ii = 0; ii < count; ii++ )
1875 {
1876 VECTOR2I vertex = outline.GetPoint( ii );
1877
1878 // Test if the point is within aRect
1879 if( arect.Contains( vertex ) )
1880 return true;
1881
1882 if( ii + 1 < count )
1883 {
1884 VECTOR2I vertexNext = outline.GetPoint( ii + 1 );
1885
1886 // Test if this edge intersects aRect
1887 if( arect.Intersects( vertex, vertexNext ) )
1888 return true;
1889 }
1890 else if( outline.IsClosed() )
1891 {
1892 VECTOR2I vertexNext = outline.GetPoint( 0 );
1893
1894 // Test if this edge intersects aRect
1895 if( arect.Intersects( vertex, vertexNext ) )
1896 return true;
1897 }
1898 }
1899
1900 return false;
1901 };
1902
1903 switch( m_shape )
1904 {
1905 case SHAPE_T::CIRCLE:
1906 // Test if area intersects or contains the circle:
1907 if( aContained )
1908 {
1909 return arect.Contains( bbox );
1910 }
1911 else
1912 {
1913 // If the rectangle does not intersect the bounding box, this is a much quicker test
1914 if( !arect.Intersects( bbox ) )
1915 return false;
1916 else
1917 return arect.IntersectsCircleEdge( getCenter(), GetRadius(), GetWidth() );
1918 }
1919
1920 case SHAPE_T::ARC:
1921 // Test for full containment of this arc in the rect
1922 if( aContained )
1923 {
1924 return arect.Contains( bbox );
1925 }
1926 // Test if the rect crosses the arc
1927 else
1928 {
1929 if( !arect.Intersects( bbox ) )
1930 return false;
1931
1932 if( IsAnyFill() )
1933 {
1934 return ( arect.Intersects( getCenter(), GetStart() )
1935 || arect.Intersects( getCenter(), GetEnd() )
1936 || arect.IntersectsCircleEdge( getCenter(), GetRadius(), GetWidth() ) );
1937 }
1938 else
1939 {
1940 return arect.IntersectsCircleEdge( getCenter(), GetRadius(), GetWidth() );
1941 }
1942 }
1943
1944 case SHAPE_T::RECTANGLE:
1945 if( aContained )
1946 {
1947 return arect.Contains( bbox );
1948 }
1949 else if( m_cornerRadius > 0 )
1950 {
1952 SHAPE_POLY_SET poly;
1953 rr.TransformToPolygon( poly, getMaxError() );
1954
1955 // Account for the width of the line
1956 arect.Inflate( GetWidth() / 2 );
1957
1958 return checkOutline( poly.Outline( 0 ) );
1959 }
1960 else
1961 {
1962 std::vector<VECTOR2I> pts = GetRectCorners();
1963
1964 // Account for the width of the lines
1965 arect.Inflate( GetWidth() / 2 );
1966 return ( arect.Intersects( pts[0], pts[1] )
1967 || arect.Intersects( pts[1], pts[2] )
1968 || arect.Intersects( pts[2], pts[3] )
1969 || arect.Intersects( pts[3], pts[0] ) );
1970 }
1971
1972 case SHAPE_T::SEGMENT:
1973 if( aContained )
1974 {
1975 return arect.Contains( GetStart() ) && aRect.Contains( GetEnd() );
1976 }
1977 else
1978 {
1979 // Account for the width of the line
1980 arect.Inflate( GetWidth() / 2 );
1981 return arect.Intersects( GetStart(), GetEnd() );
1982 }
1983
1984 case SHAPE_T::POLY:
1985 if( aContained )
1986 {
1987 return arect.Contains( bbox );
1988 }
1989 else
1990 {
1991 // Fast test: if aRect is outside the polygon bounding box,
1992 // rectangles cannot intersect
1993 if( !arect.Intersects( bbox ) )
1994 return false;
1995
1996 // Account for the width of the line
1997 arect.Inflate( GetWidth() / 2 );
1998
1999 for( int ii = 0; ii < GetPolyShape().OutlineCount(); ++ii )
2000 {
2001 if( checkOutline( GetPolyShape().Outline( ii ) ) )
2002 return true;
2003 }
2004
2005 return false;
2006 }
2007
2008 case SHAPE_T::BEZIER:
2009 if( aContained )
2010 {
2011 return arect.Contains( bbox );
2012 }
2013 else
2014 {
2015 // Fast test: if aRect is outside the polygon bounding box,
2016 // rectangles cannot intersect
2017 if( !arect.Intersects( bbox ) )
2018 return false;
2019
2020 // Account for the width of the line
2021 arect.Inflate( GetWidth() / 2 );
2022 const std::vector<VECTOR2I>* pts = &m_bezierPoints;
2023 std::vector<VECTOR2I> updatedBezierPoints;
2024
2025 if( m_bezierPoints.empty() )
2026 {
2028 converter.GetPoly( updatedBezierPoints, aAccuracy / 2 );
2029 pts = &updatedBezierPoints;
2030 }
2031
2032 for( unsigned ii = 1; ii < pts->size(); ii++ )
2033 {
2034 VECTOR2I vertex = ( *pts )[ii - 1];
2035 VECTOR2I vertexNext = ( *pts )[ii];
2036
2037 // Test if the point is within aRect
2038 if( arect.Contains( vertex ) )
2039 return true;
2040
2041 // Test if this edge intersects aRect
2042 if( arect.Intersects( vertex, vertexNext ) )
2043 return true;
2044 }
2045
2046 return false;
2047 }
2048
2049 case SHAPE_T::ELLIPSE:
2051 {
2052 if( aContained )
2053 return arect.Contains( bbox );
2054
2055 if( !arect.Intersects( bbox ) )
2056 return false;
2057
2059
2060 const int tessError = std::max( 1, aAccuracy / 2 );
2061 const SHAPE_LINE_CHAIN chain = e.ConvertToPolyline( tessError );
2062
2063 // Account for the width of the line
2064 arect.Inflate( GetWidth() / 2 );
2065 return checkOutline( chain );
2066 }
2067
2068 default:
2070 return false;
2071 }
2072}
2073
2074
2075bool EDA_SHAPE::hitTest( const SHAPE_LINE_CHAIN& aPoly, bool aContained ) const
2076{
2079 : MakeEffectiveShapes() );
2080
2081 return KIGEOM::ShapeHitTest( aPoly, shape, aContained );
2082}
2083
2084
2085std::vector<VECTOR2I> EDA_SHAPE::GetRectCorners() const
2086{
2087 std::vector<VECTOR2I> pts;
2088 VECTOR2I topLeft = GetStart();
2089 VECTOR2I botRight = GetEnd();
2090
2091 pts.emplace_back( topLeft );
2092 pts.emplace_back( botRight.x, topLeft.y );
2093 pts.emplace_back( botRight );
2094 pts.emplace_back( topLeft.x, botRight.y );
2095
2096 return pts;
2097}
2098
2099
2100std::vector<VECTOR2I> EDA_SHAPE::GetCornersInSequence( EDA_ANGLE angle ) const
2101{
2102 std::vector<VECTOR2I> pts;
2103
2104 angle.Normalize();
2105
2106 BOX2I bbox = getBoundingBox();
2107 bbox.Normalize();
2108
2109 if( angle.IsCardinal() )
2110 {
2111 if( angle == ANGLE_0 )
2112 {
2113 pts.emplace_back( VECTOR2I( bbox.GetLeft(), bbox.GetTop() ) );
2114 pts.emplace_back( VECTOR2I( bbox.GetRight(), bbox.GetTop() ) );
2115 pts.emplace_back( VECTOR2I( bbox.GetRight(), bbox.GetBottom() ) );
2116 pts.emplace_back( VECTOR2I( bbox.GetLeft(), bbox.GetBottom() ) );
2117 }
2118 else if( angle == ANGLE_90 )
2119 {
2120 pts.emplace_back( VECTOR2I( bbox.GetLeft(), bbox.GetBottom() ) );
2121 pts.emplace_back( VECTOR2I( bbox.GetLeft(), bbox.GetTop() ) );
2122 pts.emplace_back( VECTOR2I( bbox.GetRight(), bbox.GetTop() ) );
2123 pts.emplace_back( VECTOR2I( bbox.GetRight(), bbox.GetBottom() ) );
2124 }
2125 else if( angle == ANGLE_180 )
2126 {
2127 pts.emplace_back( VECTOR2I( bbox.GetRight(), bbox.GetBottom() ) );
2128 pts.emplace_back( VECTOR2I( bbox.GetLeft(), bbox.GetBottom() ) );
2129 pts.emplace_back( VECTOR2I( bbox.GetLeft(), bbox.GetTop() ) );
2130 pts.emplace_back( VECTOR2I( bbox.GetRight(), bbox.GetTop() ) );
2131 }
2132 else if( angle == ANGLE_270 )
2133 {
2134 pts.emplace_back( VECTOR2I( bbox.GetRight(), bbox.GetTop() ) );
2135 pts.emplace_back( VECTOR2I( bbox.GetRight(), bbox.GetBottom() ) );
2136 pts.emplace_back( VECTOR2I( bbox.GetLeft(), bbox.GetBottom() ) );
2137 pts.emplace_back( VECTOR2I( bbox.GetLeft(), bbox.GetTop() ) );
2138 }
2139 }
2140 else if( m_shape == SHAPE_T::RECTANGLE )
2141 {
2142 // Axis-aligned rectangle with non-cardinal rotation (used by textboxes).
2143 VECTOR2I center = bbox.GetCenter();
2144
2145 VECTOR2I tl( bbox.GetLeft(), bbox.GetTop() );
2146 VECTOR2I tr( bbox.GetRight(), bbox.GetTop() );
2147 VECTOR2I br( bbox.GetRight(), bbox.GetBottom() );
2148 VECTOR2I bl( bbox.GetLeft(), bbox.GetBottom() );
2149
2150 RotatePoint( tl, center, angle );
2151 RotatePoint( tr, center, angle );
2152 RotatePoint( br, center, angle );
2153 RotatePoint( bl, center, angle );
2154
2155 pts.emplace_back( tl );
2156 pts.emplace_back( tr );
2157 pts.emplace_back( br );
2158 pts.emplace_back( bl );
2159 }
2160 else
2161 {
2162 // This function was originally located in pcb_textbox.cpp and was later moved to eda_shape.cpp.
2163 // As a result of this move, access to getCorners was lost, since it is defined in the PCB_SHAPE
2164 // class within pcb_shape.cpp and is not available in the current context.
2165 //
2166 // Additionally, GetRectCorners() cannot be used here, as it assumes the rectangle is rotated by
2167 // a cardinal angle. In non-cardinal cases, it returns incorrect values (e.g., (0, 0)).
2168 //
2169 // To address this, a portion of the getCorners implementation for SHAPE_T::POLY elements
2170 // has been replicated here to restore the correct behavior.
2171 std::vector<VECTOR2I> corners;
2172
2173 for( int ii = 0; ii < GetPolyShape().OutlineCount(); ++ii )
2174 {
2175 for( const VECTOR2I& pt : GetPolyShape().Outline( ii ).CPoints() )
2176 corners.emplace_back( pt );
2177 }
2178
2179 if( corners.empty() )
2180 return pts;
2181
2182 while( corners.size() < 4 )
2183 corners.emplace_back( corners.back() + VECTOR2I( 10, 10 ) );
2184
2185 VECTOR2I minX = corners[0];
2186 VECTOR2I maxX = corners[0];
2187 VECTOR2I minY = corners[0];
2188 VECTOR2I maxY = corners[0];
2189
2190 for( const VECTOR2I& corner : corners )
2191 {
2192 if( corner.x < minX.x )
2193 minX = corner;
2194
2195 if( corner.x > maxX.x )
2196 maxX = corner;
2197
2198 if( corner.y < minY.y )
2199 minY = corner;
2200
2201 if( corner.y > maxY.y )
2202 maxY = corner;
2203 }
2204
2205 if( angle < ANGLE_90 )
2206 {
2207 pts.emplace_back( minX );
2208 pts.emplace_back( minY );
2209 pts.emplace_back( maxX );
2210 pts.emplace_back( maxY );
2211 }
2212 else if( angle < ANGLE_180 )
2213 {
2214 pts.emplace_back( maxY );
2215 pts.emplace_back( minX );
2216 pts.emplace_back( minY );
2217 pts.emplace_back( maxX );
2218 }
2219 else if( angle < ANGLE_270 )
2220 {
2221 pts.emplace_back( maxX );
2222 pts.emplace_back( maxY );
2223 pts.emplace_back( minX );
2224 pts.emplace_back( minY );
2225 }
2226 else
2227 {
2228 pts.emplace_back( minY );
2229 pts.emplace_back( maxX );
2230 pts.emplace_back( maxY );
2231 pts.emplace_back( minX );
2232 }
2233 }
2234
2235 return pts;
2236}
2237
2238
2240{
2241 // Start, end, and each inflection point the arc crosses will enclose the entire arc.
2242 // Only include the center when filled; it's not necessarily inside the BB of an unfilled
2243 // arc with a small included angle.
2244 aBBox.SetOrigin( m_start );
2245 aBBox.Merge( m_end );
2246
2247 if( IsAnyFill() )
2248 aBBox.Merge( m_arcCenter );
2249
2250 int radius = GetRadius();
2251 EDA_ANGLE t1, t2;
2252
2253 CalcArcAngles( t1, t2 );
2254
2255 t1.Normalize();
2256 t2.Normalize();
2257
2258 if( t2 > t1 )
2259 {
2260 if( t1 < ANGLE_0 && t2 > ANGLE_0 )
2261 aBBox.Merge( VECTOR2I( m_arcCenter.x + radius, m_arcCenter.y ) ); // right
2262
2263 if( t1 < ANGLE_90 && t2 > ANGLE_90 )
2264 aBBox.Merge( VECTOR2I( m_arcCenter.x, m_arcCenter.y + radius ) ); // down
2265
2266 if( t1 < ANGLE_180 && t2 > ANGLE_180 )
2267 aBBox.Merge( VECTOR2I( m_arcCenter.x - radius, m_arcCenter.y ) ); // left
2268
2269 if( t1 < ANGLE_270 && t2 > ANGLE_270 )
2270 aBBox.Merge( VECTOR2I( m_arcCenter.x, m_arcCenter.y - radius ) ); // up
2271 }
2272 else
2273 {
2274 if( t1 < ANGLE_0 || t2 > ANGLE_0 )
2275 aBBox.Merge( VECTOR2I( m_arcCenter.x + radius, m_arcCenter.y ) ); // right
2276
2277 if( t1 < ANGLE_90 || t2 > ANGLE_90 )
2278 aBBox.Merge( VECTOR2I( m_arcCenter.x, m_arcCenter.y + radius ) ); // down
2279
2280 if( t1 < ANGLE_180 || t2 > ANGLE_180 )
2281 aBBox.Merge( VECTOR2I( m_arcCenter.x - radius, m_arcCenter.y ) ); // left
2282
2283 if( t1 < ANGLE_270 || t2 > ANGLE_270 )
2284 aBBox.Merge( VECTOR2I( m_arcCenter.x, m_arcCenter.y - radius ) ); // up
2285 }
2286}
2287
2288
2289void EDA_SHAPE::SetPolyPoints( const std::vector<VECTOR2I>& aPoints )
2290{
2293
2294 for( const VECTOR2I& p : aPoints )
2295 GetPolyShape().Append( p.x, p.y );
2296}
2297
2298
2299std::vector<SHAPE*> EDA_SHAPE::MakeEffectiveShapesForStroking( int aLineWidth ) const
2300{
2301 if( aLineWidth < 0 )
2302 aLineWidth = GetEffectiveWidth();
2303
2304 if( m_startEnding.GetShortenDepth( aLineWidth ) > 0 || m_endEnding.GetShortenDepth( aLineWidth ) > 0 )
2305 {
2306 switch( m_shape )
2307 {
2308 // Stroke() has no Bezier primitive, so it gets the flattened polyline. One chain,
2309 // not loose segments, or the pattern restarts at every vertex (same reason as the
2310 // unshortened case below).
2311 case SHAPE_T::BEZIER:
2312 {
2313 std::vector<VECTOR2I> pts;
2314
2315 for( const VECTOR2D& pt : ShortenedBezierPolyline( aLineWidth ) )
2316 pts.emplace_back( VECTOR2I( pt ) );
2317
2318 return { new SHAPE_LINE_CHAIN( pts ) };
2319 }
2320
2321 case SHAPE_T::SEGMENT:
2322 case SHAPE_T::ARC:
2323 case SHAPE_T::POLY: return makeShortenedBodyShapes( aLineWidth, true );
2324
2325 // Other shapes have no body shortening; fall through to the standard cases.
2326 default: break;
2327 }
2328 }
2329
2330 switch( m_shape )
2331 {
2332 // Stroke() has no Bezier primitive, so it gets the flattened polyline. One chain, not
2333 // loose segments, or the pattern restarts at every vertex. This case goes away if
2334 // Bezier ever becomes a SHAPE of its own.
2336
2337 case SHAPE_T::ELLIPSE:
2338 case SHAPE_T::ELLIPSE_ARC: return { new SHAPE_ELLIPSE( buildShapeEllipse() ) };
2339
2340 default: return MakeEffectiveShapes( true );
2341 }
2342}
2343
2344
2345std::vector<SHAPE*> EDA_SHAPE::makeEffectiveShapes( bool aEdgeOnly, bool aLineChainOnly, bool aHittesting ) const
2346{
2347 std::vector<SHAPE*> effectiveShapes;
2348 int width = GetEffectiveWidth();
2349 bool solidFill = IsSolidFill()
2350 || IsHatchedFill()
2351 || IsProxyItem()
2352 || ( aHittesting && IsFilledForHitTesting() );
2353
2354 if( aEdgeOnly )
2355 solidFill = false;
2356
2357 switch( m_shape )
2358 {
2359 case SHAPE_T::ARC:
2360 effectiveShapes.emplace_back( new SHAPE_ARC( m_arcCenter, m_start, GetArcAngle(), width ) );
2361 break;
2362
2363 case SHAPE_T::SEGMENT:
2364 effectiveShapes.emplace_back( new SHAPE_SEGMENT( m_start, m_end, width ) );
2365 break;
2366
2367 case SHAPE_T::RECTANGLE:
2368 {
2369 if( m_cornerRadius > 0 )
2370 {
2372 SHAPE_POLY_SET poly;
2373 rr.TransformToPolygon( poly, getMaxError() );
2374 SHAPE_LINE_CHAIN outline = poly.Outline( 0 );
2375
2376 if( solidFill )
2377 effectiveShapes.emplace_back( new SHAPE_SIMPLE( outline ) );
2378
2379 if( width > 0 || !solidFill )
2380 {
2381 std::set<size_t> arcsHandled;
2382
2383 for( int ii = 0; ii < outline.SegmentCount(); ++ii )
2384 {
2385 if( outline.IsArcSegment( ii ) )
2386 {
2387 size_t arcIndex = outline.ArcIndex( ii );
2388
2389 if( !arcsHandled.contains( arcIndex ) )
2390 {
2391 arcsHandled.insert( arcIndex );
2392 effectiveShapes.emplace_back( new SHAPE_ARC( outline.Arc( arcIndex ), width ) );
2393 }
2394 }
2395 else
2396 {
2397 effectiveShapes.emplace_back( new SHAPE_SEGMENT( outline.Segment( ii ), width ) );
2398 }
2399 }
2400 }
2401 }
2402 else
2403 {
2404 std::vector<VECTOR2I> pts = GetRectCorners();
2405
2406 if( solidFill )
2407 effectiveShapes.emplace_back( new SHAPE_SIMPLE( pts ) );
2408
2409 if( width > 0 || !solidFill )
2410 {
2411 effectiveShapes.emplace_back( new SHAPE_SEGMENT( pts[0], pts[1], width ) );
2412 effectiveShapes.emplace_back( new SHAPE_SEGMENT( pts[1], pts[2], width ) );
2413 effectiveShapes.emplace_back( new SHAPE_SEGMENT( pts[2], pts[3], width ) );
2414 effectiveShapes.emplace_back( new SHAPE_SEGMENT( pts[3], pts[0], width ) );
2415 }
2416 }
2417 break;
2418 }
2419
2420 case SHAPE_T::CIRCLE:
2421 {
2422 if( solidFill )
2423 effectiveShapes.emplace_back( new SHAPE_CIRCLE( getCenter(), GetRadius() ) );
2424
2425 if( width > 0 || !solidFill )
2426 effectiveShapes.emplace_back( new SHAPE_ARC( getCenter(), GetEnd(), ANGLE_360, width ) );
2427
2428 break;
2429 }
2430
2431 case SHAPE_T::BEZIER:
2432 {
2433 std::vector<VECTOR2I> bezierPoints = buildBezierToSegmentsPointsList( getMaxError() );
2434 VECTOR2I start_pt = bezierPoints[0];
2435
2436 for( unsigned int jj = 1; jj < bezierPoints.size(); jj++ )
2437 {
2438 VECTOR2I end_pt = bezierPoints[jj];
2439 effectiveShapes.emplace_back( new SHAPE_SEGMENT( start_pt, end_pt, width ) );
2440 start_pt = end_pt;
2441 }
2442
2443 break;
2444 }
2445
2446 case SHAPE_T::POLY:
2447 {
2448 if( GetPolyShape().OutlineCount() == 0 ) // malformed/empty polygon
2449 break;
2450
2451 for( int ii = 0; ii < GetPolyShape().OutlineCount(); ++ii )
2452 {
2453 const SHAPE_LINE_CHAIN& l = GetPolyShape().COutline( ii );
2454
2455 if( solidFill )
2456 effectiveShapes.emplace_back( new SHAPE_SIMPLE( l ) );
2457
2458 if( width > 0 || !IsSolidFill() || aEdgeOnly )
2459 {
2460 int segCount = l.SegmentCount();
2461
2462 if( aLineChainOnly && l.IsClosed() )
2463 segCount--; // Treat closed chain as open
2464
2465 for( int jj = 0; jj < segCount; jj++ )
2466 effectiveShapes.emplace_back( new SHAPE_SEGMENT( l.CSegment( jj ), width ) );
2467 }
2468 }
2469 }
2470 break;
2471
2472 case SHAPE_T::ELLIPSE:
2474 {
2475 if( solidFill && m_shape == SHAPE_T::ELLIPSE )
2476 {
2477 // Filled closed ellipse: emit a SHAPE_SIMPLE for the filled interior.
2480 std::vector<VECTOR2I> pts;
2481
2482 for( int ii = 0; ii < chain.PointCount(); ++ii )
2483 pts.emplace_back( chain.CPoint( ii ) );
2484
2485 effectiveShapes.emplace_back( new SHAPE_SIMPLE( pts ) );
2486 }
2487
2488 if( width > 0 || !solidFill )
2489 {
2492
2493 for( int ii = 0; ii < chain.SegmentCount(); ++ii )
2494 effectiveShapes.emplace_back( new SHAPE_SEGMENT( chain.CSegment( ii ), width ) );
2495 }
2496
2497 break;
2498 }
2499
2500 default:
2502 break;
2503 }
2504
2505 return effectiveShapes;
2506}
2507
2508
2509static SHAPE_LINE_CHAIN lineEndingClosedChain( const std::vector<VECTOR2I>& aPolygon )
2510{
2512
2513 for( size_t ii = 0; ii < aPolygon.size(); ++ii )
2514 {
2515 if( ii == aPolygon.size() - 1 && aPolygon[ii] == aPolygon.front() )
2516 continue;
2517
2518 chain.Append( aPolygon[ii] );
2519 }
2520
2521 chain.SetClosed( true );
2522 return chain;
2523}
2524
2525
2526static bool hasLineEnding( const LINE_ENDING& aStartEnding, const LINE_ENDING& aEndEnding )
2527{
2528 return aStartEnding.GetStyle() != LINE_ENDING_STYLE::NONE || aEndEnding.GetStyle() != LINE_ENDING_STYLE::NONE;
2529}
2530
2531
2532static void addLineEndingEffectiveShapes( std::vector<SHAPE*>& aShapes, const LINE_ENDING& aEnding,
2533 const VECTOR2I& aPoint, const EDA_ANGLE& aTangent, int aLineWidth )
2534{
2535 if( aEnding.GetStyle() == LINE_ENDING_STYLE::NONE )
2536 return;
2537
2538 std::vector<VECTOR2I> polygon;
2539 aEnding.GetShapes( aPoint, aTangent, aLineWidth, polygon );
2540
2541 if( polygon.empty() )
2542 return;
2543
2544 if( aEnding.GetStyle() == LINE_ENDING_STYLE::ARROW_OPEN )
2545 {
2546 int width = aEnding.GetStrokeWidth() > 0 ? aEnding.GetStrokeWidth() : aLineWidth;
2547
2548 if( polygon.size() >= 3 )
2549 {
2550 aShapes.emplace_back( new SHAPE_SEGMENT( polygon[0], polygon[1], std::max( 0, width ) ) );
2551 aShapes.emplace_back( new SHAPE_SEGMENT( polygon[1], polygon[2], std::max( 0, width ) ) );
2552 }
2553
2554 return;
2555 }
2556
2557 if( polygon.size() < 3 )
2558 return;
2559
2560 SHAPE_LINE_CHAIN outline = lineEndingClosedChain( polygon );
2561
2562 if( outline.PointCount() >= 3 )
2563 aShapes.emplace_back( new SHAPE_SIMPLE( outline ) );
2564
2565 if( aEnding.GetStrokeWidth() > 0 )
2566 {
2567 for( int ii = 0; ii < outline.SegmentCount(); ++ii )
2568 aShapes.emplace_back( new SHAPE_SEGMENT( outline.CSegment( ii ), aEnding.GetStrokeWidth() ) );
2569 }
2570}
2571
2572
2573std::vector<SHAPE*> EDA_SHAPE::MakeLineEndingEffectiveShapes( int aLineWidth ) const
2574{
2575 std::vector<SHAPE*> effectiveShapes;
2576 VECTOR2I startPoint;
2577 VECTOR2I endPoint;
2578
2580 return effectiveShapes;
2581
2582 if( !GetLineEndingEndpoints( startPoint, endPoint ) )
2583 return effectiveShapes;
2584
2585 EDA_ANGLE startTangent;
2586 EDA_ANGLE endTangent;
2587
2588 GetEndingTangents( startTangent, endTangent, aLineWidth );
2589
2590 addLineEndingEffectiveShapes( effectiveShapes, m_startEnding, startPoint, startTangent, aLineWidth );
2591 addLineEndingEffectiveShapes( effectiveShapes, m_endEnding, endPoint, endTangent, aLineWidth );
2592
2593 return effectiveShapes;
2594}
2595
2596
2597std::vector<SHAPE*> EDA_SHAPE::makeShortenedBodyShapes( int aLineWidth, bool aEdgeOnly ) const
2598{
2599 std::vector<SHAPE*> effectiveShapes;
2600 bool shortenBody = m_startEnding.GetShortenDepth( aLineWidth ) > 0 || m_endEnding.GetShortenDepth( aLineWidth ) > 0;
2601
2602 if( !shortenBody )
2603 {
2604 effectiveShapes = makeEffectiveShapes( aEdgeOnly );
2605 }
2606 else
2607 {
2608 int width = GetEffectiveWidth();
2609
2610 switch( m_shape )
2611 {
2612 case SHAPE_T::SEGMENT:
2613 {
2614 VECTOR2I start = GetStart();
2615 VECTOR2I end = GetEnd();
2616
2617 if( ShortenSegmentForEndings( start, end, m_startEnding, m_endEnding, aLineWidth ) )
2618 effectiveShapes.emplace_back( new SHAPE_SEGMENT( start, end, width ) );
2619
2620 break;
2621 }
2622
2623 case SHAPE_T::ARC:
2624 {
2625 EDA_ANGLE startAngle;
2626 EDA_ANGLE endAngle;
2627 CalcArcAngles( startAngle, endAngle );
2628
2629 EDA_ANGLE originalStartAngle = startAngle;
2630 EDA_ANGLE arcAngle = endAngle - startAngle;
2631
2632 if( ShortenArcForEndings( startAngle, arcAngle, GetRadius(), aLineWidth ) )
2633 {
2634 VECTOR2I startPoint = GetStart();
2635 RotatePoint( startPoint, m_arcCenter, -( startAngle - originalStartAngle ) );
2636 effectiveShapes.emplace_back( new SHAPE_ARC( m_arcCenter, startPoint, arcAngle, width ) );
2637 }
2638
2639 break;
2640 }
2641
2642 case SHAPE_T::BEZIER:
2643 {
2644 std::vector<VECTOR2D> pts = ShortenedBezierPolyline( aLineWidth );
2645
2646 for( size_t ii = 1; ii < pts.size(); ++ii )
2647 {
2648 effectiveShapes.emplace_back(
2649 new SHAPE_SEGMENT( VECTOR2I( pts[ii - 1] ), VECTOR2I( pts[ii] ), width ) );
2650 }
2651
2652 break;
2653 }
2654
2655 case SHAPE_T::POLY:
2656 {
2657 if( ( !aEdgeOnly && ( IsSolidFill() || IsHatchedFill() || IsProxyItem() ) )
2658 || GetPolyShape().OutlineCount() == 0 )
2659 {
2660 effectiveShapes = makeEffectiveShapes( aEdgeOnly );
2661 break;
2662 }
2663
2664 for( int ii = 0; ii < GetPolyShape().OutlineCount(); ++ii )
2665 {
2666 const SHAPE_LINE_CHAIN& outline = GetPolyShape().COutline( ii );
2667
2668 if( outline.PointCount() < 2 )
2669 continue;
2670
2671 std::vector<VECTOR2I> pts;
2672
2673 if( !GetShortenedBodyPolyPoints( outline, ii, pts, aLineWidth ) )
2674 continue;
2675
2676 for( size_t jj = 1; jj < pts.size(); ++jj )
2677 effectiveShapes.emplace_back( new SHAPE_SEGMENT( pts[jj - 1], pts[jj], width ) );
2678
2679 if( outline.IsClosed() )
2680 effectiveShapes.emplace_back( new SHAPE_SEGMENT( pts.back(), pts.front(), width ) );
2681 }
2682
2683 break;
2684 }
2685
2686 default: effectiveShapes = makeEffectiveShapes( aEdgeOnly ); break;
2687 }
2688 }
2689
2690 return effectiveShapes;
2691}
2692
2693
2694std::vector<SHAPE*> EDA_SHAPE::MakeEffectiveShapesWithLineEndings( int aLineWidth ) const
2695{
2696 std::vector<SHAPE*> effectiveShapes = makeShortenedBodyShapes( aLineWidth );
2697
2698 for( SHAPE* shape : MakeLineEndingEffectiveShapes( aLineWidth ) )
2699 effectiveShapes.emplace_back( shape );
2700
2701 return effectiveShapes;
2702}
2703
2704
2705static void addLineEndingPolygon( SHAPE_POLY_SET& aBuffer, const LINE_ENDING& aEnding, const VECTOR2I& aPoint,
2706 const EDA_ANGLE& aTangent, int aClearance, int aError, ERROR_LOC aErrorLoc,
2707 int aLineWidth )
2708{
2709 if( aEnding.GetStyle() == LINE_ENDING_STYLE::NONE )
2710 return;
2711
2712 std::vector<VECTOR2I> polygon;
2713 aEnding.GetShapes( aPoint, aTangent, aLineWidth, polygon );
2714
2715 if( polygon.empty() )
2716 return;
2717
2718 auto addStrokedSegment = [&]( const VECTOR2I& aStart, const VECTOR2I& aEnd, int aStrokeWidth )
2719 {
2720 int width = std::max( 0, aStrokeWidth ) + 2 * aClearance;
2721
2722 if( width > 0 )
2723 TransformOvalToPolygon( aBuffer, aStart, aEnd, width, aError, aErrorLoc );
2724 };
2725
2726 if( aEnding.GetStyle() == LINE_ENDING_STYLE::ARROW_OPEN )
2727 {
2728 int strokeWidth = aEnding.GetStrokeWidth() > 0 ? aEnding.GetStrokeWidth() : aLineWidth;
2729
2730 if( polygon.size() >= 3 )
2731 {
2732 addStrokedSegment( polygon[0], polygon[1], strokeWidth );
2733 addStrokedSegment( polygon[1], polygon[2], strokeWidth );
2734 }
2735
2736 return;
2737 }
2738
2739 if( polygon.size() < 3 )
2740 return;
2741
2742 SHAPE_LINE_CHAIN outline = lineEndingClosedChain( polygon );
2743
2744 if( outline.PointCount() >= 3 )
2745 {
2746 SHAPE_POLY_SET fill;
2747 fill.NewOutline();
2748
2749 for( int ii = 0; ii < outline.PointCount(); ++ii )
2750 fill.Append( outline.CPoint( ii ) );
2751
2752 if( aClearance > 0 )
2753 {
2754 int inflate = aClearance;
2755
2756 if( aErrorLoc == ERROR_OUTSIDE )
2757 inflate += aError;
2758
2759 fill.Inflate( inflate, CORNER_STRATEGY::ROUND_ALL_CORNERS, aError );
2760 }
2761
2762 aBuffer.Append( fill );
2763 }
2764
2765 if( aEnding.GetStrokeWidth() > 0 )
2766 {
2767 for( int ii = 0; ii < outline.SegmentCount(); ++ii )
2768 {
2769 const SEG& seg = outline.CSegment( ii );
2770 addStrokedSegment( seg.A, seg.B, aEnding.GetStrokeWidth() );
2771 }
2772 }
2773}
2774
2775
2776void EDA_SHAPE::TransformLineEndingsToPolygon( SHAPE_POLY_SET& aBuffer, int aClearance, int aError, ERROR_LOC aErrorLoc,
2777 int aLineWidth ) const
2778{
2779 VECTOR2I startPoint;
2780 VECTOR2I endPoint;
2781
2783 return;
2784
2785 if( !GetLineEndingEndpoints( startPoint, endPoint ) )
2786 return;
2787
2788 EDA_ANGLE startTangent;
2789 EDA_ANGLE endTangent;
2790
2791 GetEndingTangents( startTangent, endTangent, aLineWidth );
2792
2793 addLineEndingPolygon( aBuffer, m_startEnding, startPoint, startTangent, aClearance, aError, aErrorLoc, aLineWidth );
2794 addLineEndingPolygon( aBuffer, m_endEnding, endPoint, endTangent, aClearance, aError, aErrorLoc, aLineWidth );
2795}
2796
2797
2798void EDA_SHAPE::TransformWithLineEndingsToPolygon( SHAPE_POLY_SET& aBuffer, int aClearance, int aError,
2799 ERROR_LOC aErrorLoc, bool ignoreLineWidth ) const
2800{
2801 int lineWidth = ignoreLineWidth ? 0 : GetEffectiveWidth();
2802 bool shortenBody = m_startEnding.GetShortenDepth( lineWidth ) > 0 || m_endEnding.GetShortenDepth( lineWidth ) > 0;
2803
2804 if( !shortenBody )
2805 {
2806 TransformShapeToPolygon( aBuffer, aClearance, aError, aErrorLoc, ignoreLineWidth );
2807 }
2808 else
2809 {
2810 int width = ignoreLineWidth ? 0 : GetWidth();
2811 width += 2 * aClearance;
2812
2813 switch( m_shape )
2814 {
2815 case SHAPE_T::SEGMENT:
2816 {
2817 VECTOR2I start = GetStart();
2818 VECTOR2I end = GetEnd();
2819
2820 if( ShortenSegmentForEndings( start, end, m_startEnding, m_endEnding, lineWidth ) )
2821 TransformOvalToPolygon( aBuffer, start, end, width, aError, aErrorLoc );
2822
2823 break;
2824 }
2825
2826 case SHAPE_T::ARC:
2827 {
2828 EDA_ANGLE startAngle;
2829 EDA_ANGLE endAngle;
2830 CalcArcAngles( startAngle, endAngle );
2831
2832 EDA_ANGLE originalStartAngle = startAngle;
2833 EDA_ANGLE arcAngle = endAngle - startAngle;
2834
2835 if( ShortenArcForEndings( startAngle, arcAngle, GetRadius(), lineWidth ) )
2836 {
2837 VECTOR2I startPoint = GetStart();
2838 RotatePoint( startPoint, m_arcCenter, -( startAngle - originalStartAngle ) );
2839 SHAPE_ARC arc( m_arcCenter, startPoint, arcAngle, width );
2840
2841 TransformArcToPolygon( aBuffer, arc.GetP0(), arc.GetArcMid(), arc.GetP1(), width, aError, aErrorLoc );
2842 }
2843
2844 break;
2845 }
2846
2847 case SHAPE_T::BEZIER:
2848 {
2849 std::vector<VECTOR2D> pts = ShortenedBezierPolyline( lineWidth );
2850
2851 for( size_t ii = 1; ii < pts.size(); ++ii )
2852 {
2853 TransformOvalToPolygon( aBuffer, VECTOR2I( pts[ii - 1] ), VECTOR2I( pts[ii] ), width, aError,
2854 aErrorLoc );
2855 }
2856
2857 break;
2858 }
2859
2860 case SHAPE_T::POLY:
2861 {
2862 bool solidFill = IsSolidFill() || IsHatchedFill() || IsProxyItem();
2863
2864 if( solidFill || !IsPolyShapeValid() )
2865 {
2866 TransformShapeToPolygon( aBuffer, aClearance, aError, aErrorLoc, ignoreLineWidth );
2867 break;
2868 }
2869
2870 for( int ii = 0; ii < GetPolyShape().OutlineCount(); ++ii )
2871 {
2872 const SHAPE_LINE_CHAIN& outline = GetPolyShape().COutline( ii );
2873
2874 if( outline.PointCount() < 2 )
2875 continue;
2876
2877 std::vector<VECTOR2I> pts;
2878
2879 if( !GetShortenedBodyPolyPoints( outline, ii, pts, lineWidth ) )
2880 continue;
2881
2882 for( size_t jj = 1; jj < pts.size(); ++jj )
2883 {
2884 TransformOvalToPolygon( aBuffer, pts[jj - 1], pts[jj], width, aError, aErrorLoc );
2885 }
2886
2887 if( outline.IsClosed() )
2888 {
2889 TransformOvalToPolygon( aBuffer, pts.back(), pts.front(), width, aError, aErrorLoc );
2890 }
2891 }
2892
2893 break;
2894 }
2895
2896 default: TransformShapeToPolygon( aBuffer, aClearance, aError, aErrorLoc, ignoreLineWidth ); break;
2897 }
2898 }
2899
2900 TransformLineEndingsToPolygon( aBuffer, aClearance, aError, aErrorLoc, lineWidth );
2901}
2902
2903
2904bool EDA_SHAPE::GetLineEndingsBoundingBox( BOX2I& aBBox, int aLineWidth ) const
2905{
2906 auto mergeEnding =
2907 [&]( const LINE_ENDING& aEnding, const VECTOR2I& aPoint, const EDA_ANGLE& aTangent, bool& aHasBox )
2908 {
2909 if( aEnding.GetStyle() == LINE_ENDING_STYLE::NONE )
2910 return;
2911
2912 std::vector<VECTOR2I> polygon;
2913 aEnding.GetShapes( aPoint, aTangent, aLineWidth, polygon );
2914
2915 if( polygon.empty() )
2916 return;
2917
2918 BOX2I endingBBox( polygon.front(), VECTOR2I( 0, 0 ) );
2919
2920 for( const VECTOR2I& point : polygon )
2921 endingBBox.Merge( point );
2922
2923 int stroke = aEnding.GetStrokeWidth();
2924
2925 if( aEnding.GetStyle() == LINE_ENDING_STYLE::ARROW_OPEN && stroke <= 0 )
2926 stroke = aLineWidth;
2927
2928 endingBBox.Inflate( std::max( 0, stroke ) / 2 );
2929
2930 if( !aHasBox )
2931 {
2932 aBBox = endingBBox;
2933 aHasBox = true;
2934 }
2935 else
2936 {
2937 aBBox.Merge( endingBBox );
2938 }
2939 };
2940
2941 VECTOR2I startPoint;
2942 VECTOR2I endPoint;
2943
2945 return false;
2946
2947 if( !GetLineEndingEndpoints( startPoint, endPoint ) )
2948 return false;
2949
2950 bool hasBox = false;
2951 EDA_ANGLE startTangent;
2952 EDA_ANGLE endTangent;
2953
2954 GetEndingTangents( startTangent, endTangent, aLineWidth );
2955
2956 mergeEnding( m_startEnding, startPoint, startTangent, hasBox );
2957 mergeEnding( m_endEnding, endPoint, endTangent, hasBox );
2958
2959 if( hasBox )
2960 {
2961 aBBox.Normalize();
2962 }
2963
2964 return hasBox;
2965}
2966
2967
2968std::vector<VECTOR2I> EDA_SHAPE::GetPolyPoints() const
2969{
2970 const SHAPE_POLY_SET& poly = GetPolyShape();
2971 std::vector<VECTOR2I> points;
2972 int totalCount = 0;
2973
2974 for( int ii = 0; ii < poly.OutlineCount(); ++ii )
2975 totalCount += poly.COutline( ii ).PointCount();
2976
2977 points.reserve( totalCount );
2978
2979 for( int ii = 0; ii < poly.OutlineCount(); ++ii )
2980 {
2981 for( const VECTOR2I& pt : poly.COutline( ii ).CPoints() )
2982 points.emplace_back( pt );
2983 }
2984
2985 return points;
2986}
2987
2988
2990{
2991 if( !m_poly )
2992 m_poly = std::make_unique<SHAPE_POLY_SET>();
2993
2994 return *m_poly;
2995}
2996
2998{
2999 if( !m_poly )
3000 m_poly = std::make_unique<SHAPE_POLY_SET>();
3001
3002 return *m_poly;
3003}
3004
3005
3007{
3008 // return true if the polygonal shape is valid (has more than 2 points)
3009 return GetPolyShape().OutlineCount() > 0 && GetPolyShape().Outline( 0 ).PointCount() > 2;
3010}
3011
3012
3014{
3015 // return the number of corners of the polygonal shape
3016 // this shape is expected to be only one polygon without hole
3017 return GetPolyShape().OutlineCount() ? GetPolyShape().VertexCount( 0 ) : 0;
3018}
3019
3020
3021void EDA_SHAPE::beginEdit( const VECTOR2I& aPosition )
3022{
3023 switch( GetShape() )
3024 {
3025 case SHAPE_T::SEGMENT:
3026 case SHAPE_T::CIRCLE:
3027 case SHAPE_T::RECTANGLE:
3028 SetStart( aPosition );
3029 SetEnd( aPosition );
3030 break;
3031
3032 case SHAPE_T::ARC:
3033 SetArcGeometry( aPosition, aPosition, aPosition );
3034 m_editState = 1;
3035 break;
3036
3037 case SHAPE_T::BEZIER:
3038 SetStart( aPosition );
3039 SetEnd( aPosition );
3040 SetBezierC1( aPosition );
3041 SetBezierC2( aPosition );
3042 m_editState = 1;
3043
3045 break;
3046
3047 case SHAPE_T::POLY:
3049 GetPolyShape().Outline( 0 ).SetClosed( false );
3050
3051 // Start and end of the first segment (co-located for now)
3052 GetPolyShape().Outline( 0 ).Append( aPosition );
3053 GetPolyShape().Outline( 0 ).Append( aPosition, true );
3054 break;
3055
3056 case SHAPE_T::ELLIPSE:
3057 // m_start holds the first bbox corner and calcEdit derives the ellipse from it.
3058 m_editState = 1;
3059 SetStart( aPosition );
3060 SetEnd( aPosition );
3061 SetEllipseCenter( aPosition );
3065 break;
3066
3068 // State 1: drag bbox. States 2-3: pick start then end angle.
3069 SetStart( aPosition );
3070 SetEnd( aPosition );
3071 SetEllipseCenter( aPosition );
3077 m_editState = 1;
3078 break;
3079
3080 default: UNIMPLEMENTED_FOR( SHAPE_T_asString() );
3081 }
3082}
3083
3084
3085bool EDA_SHAPE::continueEdit( const VECTOR2I& aPosition )
3086{
3087 switch( GetShape() )
3088 {
3089 case SHAPE_T::ARC:
3090 case SHAPE_T::SEGMENT:
3091 case SHAPE_T::CIRCLE:
3092 case SHAPE_T::RECTANGLE:
3093 case SHAPE_T::ELLIPSE: return false;
3094
3095 case SHAPE_T::BEZIER:
3096 if( m_editState == 3 )
3097 return false;
3098
3099 m_editState++;
3100 return true;
3101
3103 if( m_editState == 3 )
3104 return false;
3105
3106 m_editState++;
3107 return true;
3108
3109 case SHAPE_T::POLY:
3110 {
3111 SHAPE_LINE_CHAIN& poly = GetPolyShape().Outline( 0 );
3112
3113 // do not add zero-length segments
3114 if( poly.CPoint( (int) poly.GetPointCount() - 2 ) != poly.CLastPoint() )
3115 poly.Append( aPosition, true );
3116 }
3117 return true;
3118
3119 default: UNIMPLEMENTED_FOR( SHAPE_T_asString() ); return false;
3120 }
3121}
3122
3123
3124void EDA_SHAPE::calcEdit( const VECTOR2I& aPosition )
3125{
3126#define sq( x ) pow( x, 2 )
3127
3128 switch( GetShape() )
3129 {
3130 case SHAPE_T::SEGMENT:
3131 case SHAPE_T::CIRCLE:
3132 case SHAPE_T::RECTANGLE: SetEnd( aPosition ); break;
3133
3134 case SHAPE_T::BEZIER:
3135 {
3136 switch( m_editState )
3137 {
3138 case 0:
3139 SetStart( aPosition );
3140 SetEnd( aPosition );
3141 SetBezierC1( aPosition );
3142 SetBezierC2( aPosition );
3143 break;
3144
3145 case 1:
3146 SetBezierC2( aPosition );
3147 SetEnd( aPosition );
3148 break;
3149
3150 case 2: SetBezierC1( aPosition ); break;
3151
3152 case 3: SetBezierC2( aPosition ); break;
3153 }
3154
3156 }
3157 break;
3158
3159 case SHAPE_T::ARC:
3160 {
3161 double radius = GetRadius();
3162 EDA_ANGLE lastAngle = GetArcAngle();
3163
3164 // Edit state 0: drawing: place start
3165 // Edit state 1: drawing: place end (center calculated for 90-degree subtended angle)
3166 // Edit state 2: point edit: move start (center calculated for invariant subtended angle)
3167 // Edit state 3: point edit: move end (center calculated for invariant subtended angle)
3168 // Edit state 4: point edit: move center
3169 // Edit state 5: point edit: move arc-mid-point
3170
3171 switch( m_editState )
3172 {
3173 case 0: SetArcGeometry( aPosition, aPosition, aPosition ); return;
3174
3175 case 1:
3176 m_end = aPosition;
3177 radius = m_start.Distance( m_end ) * M_SQRT1_2;
3178 break;
3179
3180 case 2:
3181 case 3:
3182 {
3183 VECTOR2I v = m_start - m_end;
3184 double chordBefore = v.SquaredEuclideanNorm();
3185
3186 if( m_editState == 2 )
3187 m_start = aPosition;
3188 else
3189 m_end = aPosition;
3190
3191 v = m_start - m_end;
3192
3193 double chordAfter = v.SquaredEuclideanNorm();
3194 double ratio = 0.0;
3195
3196 if( chordBefore > 0 )
3197 ratio = chordAfter / chordBefore;
3198
3199 if( ratio != 0 )
3200 radius = std::max( sqrt( sq( radius ) * ratio ), sqrt( chordAfter ) / 2 );
3201 break;
3202 }
3203
3204 case 4:
3205 {
3206 double radialA = m_start.Distance( aPosition );
3207 double radialB = m_end.Distance( aPosition );
3208 radius = ( radialA + radialB ) / 2.0;
3209 break;
3210 }
3211
3212 case 5: SetArcGeometry( GetStart(), aPosition, GetEnd() ); return;
3213 }
3214
3215 // Calculate center based on start, end, and radius
3216 //
3217 // Let 'l' be the length of the chord and 'm' the middle point of the chord
3218 double l = m_start.Distance( m_end );
3219 VECTOR2D m = ( m_start + m_end ) / 2;
3220 double sqRadDiff = ( radius * radius ) - ( l * l ) / 4.0;
3221
3222 // Calculate 'd', the vector from the chord midpoint to the center
3223 VECTOR2D d;
3224
3225 if( l > 0 && sqRadDiff >= 0 )
3226 {
3227 d.x = sqrt( sqRadDiff ) * ( m_start.y - m_end.y ) / l;
3228 d.y = sqrt( sqRadDiff ) * ( m_end.x - m_start.x ) / l;
3229 }
3230
3231 VECTOR2I c1 = KiROUND( m + d );
3232 VECTOR2I c2 = KiROUND( m - d );
3233
3234 // Solution gives us 2 centers; we need to pick one:
3235 switch( m_editState )
3236 {
3237 case 1:
3238 // Keep arc clockwise while drawing i.e. arc angle = 90 deg.
3239 // it can be 90 or 270 deg depending on the arc center choice (c1 or c2)
3240 m_arcCenter = c1; // first trial
3241
3242 if( GetArcAngle() > ANGLE_180 )
3243 m_arcCenter = c2;
3244
3245 break;
3246
3247 case 2:
3248 case 3:
3249 // Pick the one of c1, c2 to keep arc on the same side
3250 m_arcCenter = c1; // first trial
3251
3252 if( ( lastAngle < ANGLE_180 ) != ( GetArcAngle() < ANGLE_180 ) )
3253 m_arcCenter = c2;
3254
3255 break;
3256
3257 case 4:
3258 // Pick the one closer to the mouse position
3259 m_arcCenter = c1.Distance( aPosition ) < c2.Distance( aPosition ) ? c1 : c2;
3260 break;
3261 }
3262
3263 break;
3264 }
3265
3266 case SHAPE_T::POLY:
3267 GetPolyShape().Outline( 0 ).SetPoint( GetPolyShape().Outline( 0 ).GetPointCount() - 1, aPosition );
3268 break;
3269
3270 case SHAPE_T::ELLIPSE:
3271 {
3272 const VECTOR2I firstCorner = GetStart();
3273 const VECTOR2I secondCorner = aPosition;
3274 const VECTOR2I center = ( firstCorner + secondCorner ) / 2;
3275 const int halfW = std::abs( secondCorner.x - firstCorner.x ) / 2;
3276 const int halfH = std::abs( secondCorner.y - firstCorner.y ) / 2;
3277
3278 int majorRadius;
3279 int minorRadius;
3280 EDA_ANGLE rotation;
3281
3282 if( halfW >= halfH )
3283 {
3284 majorRadius = std::max( halfW, 1 );
3285 minorRadius = std::max( halfH, 1 );
3286 rotation = ANGLE_0;
3287 }
3288 else
3289 {
3290 majorRadius = std::max( halfH, 1 );
3291 minorRadius = std::max( halfW, 1 );
3292 rotation = ANGLE_90;
3293 }
3294
3296 SetEllipseMajorRadius( majorRadius );
3297 SetEllipseMinorRadius( minorRadius );
3298 SetEllipseRotation( rotation );
3299 SetEnd( aPosition );
3300 break;
3301 }
3302
3304 {
3305 switch( m_editState )
3306 {
3307 case 0:
3308 case 1:
3309 {
3310 // Bbox
3311 const VECTOR2I firstCorner = GetStart();
3312 const VECTOR2I secondCorner = aPosition;
3313 const VECTOR2I center = ( firstCorner + secondCorner ) / 2;
3314 const int halfW = std::abs( secondCorner.x - firstCorner.x ) / 2;
3315 const int halfH = std::abs( secondCorner.y - firstCorner.y ) / 2;
3316
3317 int majorRadius;
3318 int minorRadius;
3319 EDA_ANGLE rotation;
3320
3321 if( halfW >= halfH )
3322 {
3323 majorRadius = std::max( halfW, 1 );
3324 minorRadius = std::max( halfH, 1 );
3325 rotation = ANGLE_0;
3326 }
3327 else
3328 {
3329 majorRadius = std::max( halfH, 1 );
3330 minorRadius = std::max( halfW, 1 );
3331 rotation = ANGLE_90;
3332 }
3333
3335 SetEllipseMajorRadius( majorRadius );
3336 SetEllipseMinorRadius( minorRadius );
3337 SetEllipseRotation( rotation );
3338 SetEnd( aPosition );
3339
3340 // Keep the preview rendering as a full closed ellipse during bbox build.
3343
3344 break;
3345 }
3346
3347 case 2:
3348 case 3:
3349 {
3350 // Project cursor onto the parametric form (a * cos t, b * sin t) to get t.
3351 const VECTOR2I center = m_ellipse.Center;
3352 const double a = std::max( 1, m_ellipse.MajorRadius );
3353 const double b = std::max( 1, m_ellipse.MinorRadius );
3354 const EDA_ANGLE rotation = m_ellipse.Rotation;
3355
3356 const double dx = aPosition.x - center.x;
3357 const double dy = aPosition.y - center.y;
3358
3359 const double cosRot = rotation.Cos();
3360 const double sinRot = rotation.Sin();
3361 const double lx = dx * cosRot + dy * sinRot;
3362 const double ly = -dx * sinRot + dy * cosRot;
3363
3364 const EDA_ANGLE paramAngle( std::atan2( ly / b, lx / a ), RADIANS_T );
3365
3366 if( m_editState == 2 )
3367 {
3368 SetEllipseStartAngle( paramAngle );
3369 SetEllipseEndAngle( paramAngle + ANGLE_360 );
3370 }
3371 else
3372 {
3373 // Force end > start
3374 EDA_ANGLE cursorAngle = paramAngle;
3375
3376 while( cursorAngle <= m_ellipse.StartAngle )
3377 cursorAngle = cursorAngle + ANGLE_360;
3378
3379 SetEllipseEndAngle( cursorAngle );
3380 }
3381
3382 break;
3383 }
3384 }
3385
3386 break;
3387 }
3388
3389 default: UNIMPLEMENTED_FOR( SHAPE_T_asString() );
3390 }
3391}
3392
3393
3394void EDA_SHAPE::endEdit( bool aClosed )
3395{
3396 switch( GetShape() )
3397 {
3398 case SHAPE_T::ARC:
3399 case SHAPE_T::SEGMENT:
3400 case SHAPE_T::CIRCLE:
3401 case SHAPE_T::RECTANGLE:
3402 case SHAPE_T::BEZIER: break;
3403
3404 case SHAPE_T::ELLIPSE:
3406 m_editState = 0;
3408 break;
3409
3410 case SHAPE_T::POLY:
3411 {
3412 SHAPE_LINE_CHAIN& poly = GetPolyShape().Outline( 0 );
3413
3414 // do not include last point twice
3415 if( poly.GetPointCount() > 2 )
3416 {
3417 if( poly.CPoint( poly.GetPointCount() - 2 ) == poly.CLastPoint() )
3418 {
3419 poly.SetClosed( aClosed );
3420 }
3421 else
3422 {
3423 poly.SetClosed( false );
3424 poly.Remove( poly.GetPointCount() - 1 );
3425 }
3426 }
3427
3428 break;
3429 }
3430
3431 default: UNIMPLEMENTED_FOR( SHAPE_T_asString() );
3432 }
3433}
3434
3435
3437{
3438 EDA_SHAPE* image = dynamic_cast<EDA_SHAPE*>( aImage );
3439 assert( image );
3440
3441#define SWAPITEM( x ) std::swap( x, image->x )
3442 SWAPITEM( m_stroke );
3443 SWAPITEM( m_start );
3444 SWAPITEM( m_end );
3446 SWAPITEM( m_shape );
3450 SWAPITEM( m_poly );
3453 SWAPITEM( m_fill );
3459#undef SWAPITEM
3460
3461 m_hatchingDirty = true;
3462}
3463
3464
3465int EDA_SHAPE::Compare( const EDA_SHAPE* aOther ) const
3466{
3467#define EPSILON 2 // Should be enough for rounding errors on calculated items
3468
3469#define TEST( a, b ) \
3470 { \
3471 if( a != b ) \
3472 return a - b; \
3473 }
3474#define TEST_E( a, b ) \
3475 { \
3476 if( abs( a - b ) > EPSILON ) \
3477 return a - b; \
3478 }
3479#define TEST_PT( a, b ) \
3480 { \
3481 TEST_E( a.x, b.x ); \
3482 TEST_E( a.y, b.y ); \
3483 }
3484
3485 TEST_PT( m_start, aOther->m_start );
3486 TEST_PT( m_end, aOther->m_end );
3487
3488 TEST( (int) m_shape, (int) aOther->m_shape );
3489
3491 {
3493 }
3494 else if( m_shape == SHAPE_T::ARC )
3495 {
3496 TEST_PT( GetArcMid(), aOther->GetArcMid() );
3497 }
3498 else if( m_shape == SHAPE_T::BEZIER )
3499 {
3500 TEST_PT( m_bezierC1, aOther->m_bezierC1 );
3501 TEST_PT( m_bezierC2, aOther->m_bezierC2 );
3502 }
3504 {
3505 TEST_PT( m_ellipse.Center, aOther->m_ellipse.Center );
3506 TEST_E( m_ellipse.MajorRadius, aOther->m_ellipse.MajorRadius );
3507 TEST_E( m_ellipse.MinorRadius, aOther->m_ellipse.MinorRadius );
3508 TEST_E( m_ellipse.Rotation.AsTenthsOfADegree(), aOther->m_ellipse.Rotation.AsTenthsOfADegree() );
3509
3511 {
3512 TEST_E( m_ellipse.StartAngle.AsTenthsOfADegree(), aOther->m_ellipse.StartAngle.AsTenthsOfADegree() );
3513 TEST_E( m_ellipse.EndAngle.AsTenthsOfADegree(), aOther->m_ellipse.EndAngle.AsTenthsOfADegree() );
3514 }
3515 }
3516 else if( m_shape == SHAPE_T::POLY )
3517 {
3518 TEST( GetPolyShape().TotalVertices(), aOther->GetPolyShape().TotalVertices() );
3519 }
3520
3521 if( m_bezierPoints.size() != aOther->m_bezierPoints.size() )
3522 return m_bezierPoints.size() < aOther->m_bezierPoints.size() ? -1 : 1;
3523
3524 for( size_t ii = 0; ii < m_bezierPoints.size(); ++ii )
3525 TEST_PT( m_bezierPoints[ii], aOther->m_bezierPoints[ii] );
3526
3527 for( int ii = 0; ii < GetPolyShape().TotalVertices(); ++ii )
3528 TEST_PT( GetPolyShape().CVertex( ii ), aOther->GetPolyShape().CVertex( ii ) );
3529
3530 TEST_E( m_stroke.GetWidth(), aOther->m_stroke.GetWidth() );
3531 TEST( (int) m_stroke.GetLineStyle(), (int) aOther->m_stroke.GetLineStyle() );
3532 TEST( (int) m_fill, (int) aOther->m_fill );
3533
3534 return 0;
3535}
3536
3537
3538void EDA_SHAPE::TransformShapeToPolygon( SHAPE_POLY_SET& aBuffer, int aClearance, int aError, ERROR_LOC aErrorLoc,
3539 bool ignoreLineWidth, bool includeFill ) const
3540{
3541 bool solidFill = IsSolidFill() || ( IsHatchedFill() && !includeFill ) || IsProxyItem();
3542 int width = ignoreLineWidth ? 0 : GetWidth();
3543
3544 width += 2 * aClearance;
3545
3546 switch( m_shape )
3547 {
3548 case SHAPE_T::CIRCLE:
3549 {
3550 int r = GetRadius();
3551
3552 if( solidFill )
3553 TransformCircleToPolygon( aBuffer, getCenter(), r + width / 2, aError, aErrorLoc );
3554 else
3555 TransformRingToPolygon( aBuffer, getCenter(), r, width, aError, aErrorLoc );
3556
3557 break;
3558 }
3559
3560 case SHAPE_T::RECTANGLE:
3561 {
3562 if( GetCornerRadius() > 0 )
3563 {
3565 BOX2I bbox = getBoundingBox();
3566 VECTOR2I position = bbox.GetCenter();
3567
3568 if( solidFill )
3569 {
3570 TransformRoundChamferedRectToPolygon( aBuffer, position, size, ANGLE_0, GetCornerRadius(), 0.0, 0,
3571 width / 2, aError, aErrorLoc );
3572 }
3573 else
3574 {
3576 SHAPE_POLY_SET poly;
3577 rr.TransformToPolygon( poly, aError );
3578 SHAPE_LINE_CHAIN& outline = poly.Outline( 0 );
3579 outline.SetClosed( true );
3580
3581 std::set<size_t> arcsHandled;
3582
3583 for( int ii = 0; ii < outline.SegmentCount(); ++ii )
3584 {
3585 if( outline.IsArcSegment( ii ) )
3586 {
3587 size_t arcIndex = outline.ArcIndex( ii );
3588
3589 if( arcsHandled.contains( arcIndex ) )
3590 continue;
3591
3592 arcsHandled.insert( arcIndex );
3593
3594 const SHAPE_ARC& arc = outline.Arc( arcIndex );
3595 TransformArcToPolygon( aBuffer, arc.GetP0(), arc.GetArcMid(), arc.GetP1(), width, aError,
3596 aErrorLoc );
3597 }
3598 else
3599 {
3600 const SEG& seg = outline.GetSegment( ii );
3601 TransformOvalToPolygon( aBuffer, seg.A, seg.B, width, aError, aErrorLoc );
3602 }
3603 }
3604 }
3605 }
3606 else
3607 {
3608 std::vector<VECTOR2I> pts = GetRectCorners();
3609
3610 if( solidFill )
3611 {
3612 aBuffer.NewOutline();
3613
3614 for( const VECTOR2I& pt : pts )
3615 aBuffer.Append( pt );
3616 }
3617
3618 if( width > 0 || !solidFill )
3619 {
3620 // Add in segments
3621 TransformOvalToPolygon( aBuffer, pts[0], pts[1], width, aError, aErrorLoc );
3622 TransformOvalToPolygon( aBuffer, pts[1], pts[2], width, aError, aErrorLoc );
3623 TransformOvalToPolygon( aBuffer, pts[2], pts[3], width, aError, aErrorLoc );
3624 TransformOvalToPolygon( aBuffer, pts[3], pts[0], width, aError, aErrorLoc );
3625 }
3626 }
3627
3628 break;
3629 }
3630
3631 case SHAPE_T::ARC:
3632 TransformArcToPolygon( aBuffer, GetStart(), GetArcMid(), GetEnd(), width, aError, aErrorLoc );
3633 break;
3634
3635 case SHAPE_T::SEGMENT: TransformOvalToPolygon( aBuffer, GetStart(), GetEnd(), width, aError, aErrorLoc ); break;
3636
3637 case SHAPE_T::POLY:
3638 {
3639 if( !IsPolyShapeValid() )
3640 break;
3641
3642 if( solidFill )
3643 {
3644 for( int ii = 0; ii < GetPolyShape().OutlineCount(); ++ii )
3645 {
3646 const SHAPE_LINE_CHAIN& poly = GetPolyShape().Outline( ii );
3647 SHAPE_POLY_SET tmp;
3648 tmp.NewOutline();
3649
3650 for( int jj = 0; jj < (int) poly.GetPointCount(); ++jj )
3651 tmp.Append( poly.GetPoint( jj ) );
3652
3653 if( width > 0 )
3654 {
3655 int inflate = width / 2;
3656
3657 if( aErrorLoc == ERROR_OUTSIDE )
3658 inflate += aError;
3659
3660 tmp.Inflate( inflate, CORNER_STRATEGY::ROUND_ALL_CORNERS, aError );
3661 }
3662
3663 aBuffer.Append( tmp );
3664 }
3665 }
3666 else
3667 {
3668 for( int ii = 0; ii < GetPolyShape().OutlineCount(); ++ii )
3669 {
3670 const SHAPE_LINE_CHAIN& poly = GetPolyShape().Outline( ii );
3671
3672 for( int jj = 0; jj < (int) poly.SegmentCount(); ++jj )
3673 {
3674 const SEG& seg = poly.GetSegment( jj );
3675 TransformOvalToPolygon( aBuffer, seg.A, seg.B, width, aError, aErrorLoc );
3676 }
3677 }
3678 }
3679
3680 break;
3681 }
3682
3683 case SHAPE_T::BEZIER:
3684 {
3685 std::vector<VECTOR2I> ctrlPts = { GetStart(), GetBezierC1(), GetBezierC2(), GetEnd() };
3686 BEZIER_POLY converter( ctrlPts );
3687 std::vector<VECTOR2I> poly;
3688 converter.GetPoly( poly, aError );
3689
3690 for( unsigned ii = 1; ii < poly.size(); ii++ )
3691 TransformOvalToPolygon( aBuffer, poly[ii - 1], poly[ii], width, aError, aErrorLoc );
3692
3693 break;
3694 }
3695
3696 case SHAPE_T::ELLIPSE:
3698 {
3700
3702
3703 if( solidFill && m_shape == SHAPE_T::ELLIPSE )
3704 {
3705 // Filled closed ellipse, build the outline, inflate for stroke width.
3706 SHAPE_POLY_SET tmp;
3707 tmp.NewOutline();
3708
3709 for( int ii = 0; ii < chain.PointCount(); ++ii )
3710 tmp.Append( chain.CPoint( ii ) );
3711
3712 if( width > 0 )
3713 {
3714 int inflate = width / 2;
3715
3716 if( aErrorLoc == ERROR_OUTSIDE )
3717 inflate += aError;
3718
3719 tmp.Inflate( inflate, CORNER_STRATEGY::ROUND_ALL_CORNERS, aError );
3720 }
3721
3722 aBuffer.Append( tmp );
3723 }
3724 else
3725 {
3726 // stroke each tessellated segment as an oval.
3727 for( int ii = 0; ii < chain.SegmentCount(); ++ii )
3728 {
3729 const SEG& seg = chain.CSegment( ii );
3730 TransformOvalToPolygon( aBuffer, seg.A, seg.B, width, aError, aErrorLoc );
3731 }
3732 }
3733
3734 break;
3735 }
3736
3737 default: UNIMPLEMENTED_FOR( SHAPE_T_asString() ); break;
3738 }
3739
3740 if( IsHatchedFill() && includeFill )
3741 {
3742 for( int ii = 0; ii < GetHatching().OutlineCount(); ++ii )
3743 aBuffer.AddOutline( GetHatching().COutline( ii ) );
3744 }
3745}
3746
3747
3748void EDA_SHAPE::SetWidth( int aWidth )
3749{
3750 m_stroke.SetWidth( aWidth );
3751 m_hatchingDirty = true;
3752}
3753
3754
3756{
3757 m_stroke.SetLineStyle( aStyle );
3758}
3759
3760
3762{
3763 if( m_stroke.GetLineStyle() != LINE_STYLE::DEFAULT )
3764 return m_stroke.GetLineStyle();
3765
3766 return LINE_STYLE::SOLID;
3767}
3768
3769
3770bool EDA_SHAPE::operator==( const EDA_SHAPE& aOther ) const
3771{
3772 if( GetShape() != aOther.GetShape() )
3773 return false;
3774
3775 if( m_fill != aOther.m_fill )
3776 return false;
3777
3778 if( m_stroke.GetWidth() != aOther.m_stroke.GetWidth() )
3779 return false;
3780
3781 if( m_stroke.GetLineStyle() != aOther.m_stroke.GetLineStyle() )
3782 return false;
3783
3784 if( m_fillColor != aOther.m_fillColor )
3785 return false;
3786
3787 switch( GetShape() )
3788 {
3789 case SHAPE_T::SEGMENT:
3790 case SHAPE_T::RECTANGLE:
3791 case SHAPE_T::CIRCLE:
3792 if( m_start != aOther.m_start )
3793 return false;
3794
3795 if( m_end != aOther.m_end )
3796 return false;
3797
3798 break;
3799
3800 case SHAPE_T::ARC:
3801 if( m_start != aOther.m_start )
3802 return false;
3803
3804 if( m_end != aOther.m_end )
3805 return false;
3806
3807 if( m_arcCenter != aOther.m_arcCenter )
3808 return false;
3809
3810 break;
3811
3812 case SHAPE_T::POLY:
3813 if( GetPolyShape().TotalVertices() != aOther.GetPolyShape().TotalVertices() )
3814 return false;
3815
3816 for( int ii = 0; ii < GetPolyShape().TotalVertices(); ++ii )
3817 {
3818 if( GetPolyShape().CVertex( ii ) != aOther.GetPolyShape().CVertex( ii ) )
3819 return false;
3820 }
3821
3822 break;
3823
3824 case SHAPE_T::BEZIER:
3825 if( m_start != aOther.m_start )
3826 return false;
3827
3828 if( m_end != aOther.m_end )
3829 return false;
3830
3831 if( m_bezierC1 != aOther.m_bezierC1 )
3832 return false;
3833
3834 if( m_bezierC2 != aOther.m_bezierC2 )
3835 return false;
3836
3837 if( m_bezierPoints != aOther.m_bezierPoints )
3838 return false;
3839
3840 break;
3841
3842 case SHAPE_T::ELLIPSE:
3844 if( m_ellipse.Center != aOther.m_ellipse.Center )
3845 return false;
3846
3847 if( m_ellipse.MajorRadius != aOther.m_ellipse.MajorRadius )
3848 return false;
3849 if( m_ellipse.MinorRadius != aOther.m_ellipse.MinorRadius )
3850 return false;
3851
3852 if( m_ellipse.Rotation != aOther.m_ellipse.Rotation )
3853 return false;
3854
3856 {
3857 if( m_ellipse.StartAngle != aOther.m_ellipse.StartAngle )
3858 return false;
3859
3860 if( m_ellipse.EndAngle != aOther.m_ellipse.EndAngle )
3861 return false;
3862 }
3863
3864 break;
3865
3866 default: return false;
3867 }
3868
3869 if( m_startEnding != aOther.m_startEnding )
3870 return false;
3871
3872 if( m_endEnding != aOther.m_endEnding )
3873 return false;
3874
3875 return true;
3876}
3877
3878
3879double EDA_SHAPE::Similarity( const EDA_SHAPE& aOther ) const
3880{
3881 if( GetShape() != aOther.GetShape() )
3882 return 0.0;
3883
3884 double similarity = 1.0;
3885
3886 if( m_fill != aOther.m_fill )
3887 similarity *= 0.9;
3888
3889 if( m_stroke.GetWidth() != aOther.m_stroke.GetWidth() )
3890 similarity *= 0.9;
3891
3892 if( m_stroke.GetLineStyle() != aOther.m_stroke.GetLineStyle() )
3893 similarity *= 0.9;
3894
3895 if( m_fillColor != aOther.m_fillColor )
3896 similarity *= 0.9;
3897
3898 if( m_start != aOther.m_start )
3899 similarity *= 0.9;
3900
3901 if( m_end != aOther.m_end )
3902 similarity *= 0.9;
3903
3904 if( m_arcCenter != aOther.m_arcCenter )
3905 similarity *= 0.9;
3906
3907 if( m_bezierC1 != aOther.m_bezierC1 )
3908 similarity *= 0.9;
3909
3910 if( m_bezierC2 != aOther.m_bezierC2 )
3911 similarity *= 0.9;
3912
3913 {
3914 int m = m_bezierPoints.size();
3915 int n = aOther.m_bezierPoints.size();
3916
3917 size_t longest = alg::longest_common_subset( m_bezierPoints, aOther.m_bezierPoints );
3918
3919 similarity *= std::pow( 0.9, m + n - 2 * longest );
3920 }
3921
3922 {
3923 int m = GetPolyShape().TotalVertices();
3924 int n = aOther.GetPolyShape().TotalVertices();
3925 std::vector<VECTOR2I> poly;
3926 std::vector<VECTOR2I> otherPoly;
3927 VECTOR2I lastPt( 0, 0 );
3928
3929 // We look for the longest common subset of the two polygons, but we need to
3930 // offset each point because we're actually looking for overall similarity, not just
3931 // exact matches. So if the zone is moved by 1IU, we only want one point to be
3932 // considered "moved" rather than the entire polygon. In this case, the first point
3933 // will not be a match but the rest of the sequence will.
3934 for( int ii = 0; ii < m; ++ii )
3935 {
3936 poly.emplace_back( lastPt - GetPolyShape().CVertex( ii ) );
3937 lastPt = GetPolyShape().CVertex( ii );
3938 }
3939
3940 lastPt = VECTOR2I( 0, 0 );
3941
3942 for( int ii = 0; ii < n; ++ii )
3943 {
3944 otherPoly.emplace_back( lastPt - aOther.GetPolyShape().CVertex( ii ) );
3945 lastPt = aOther.GetPolyShape().CVertex( ii );
3946 }
3947
3948 size_t longest = alg::longest_common_subset( poly, otherPoly );
3949
3950 similarity *= std::pow( 0.9, m + n - 2 * longest );
3951 }
3952
3953 if( m_startEnding != aOther.m_startEnding )
3954 similarity *= 0.9;
3955
3956 if( m_endEnding != aOther.m_endEnding )
3957 similarity *= 0.9;
3958
3959 return similarity;
3960}
3961
3962
3963static double bezierLength( const BEZIER<double>& aBezier, double aT0, double aT1 );
3964
3965static double findBezierTAtLength( const BEZIER<double>& aBezier, double aTargetLength, double aTotalLength );
3966
3967
3968void EDA_SHAPE::GetEndingTangents( EDA_ANGLE& aStartTangent, EDA_ANGLE& aEndTangent, int aLineWidth ) const
3969{
3970 aStartTangent = ANGLE_0;
3971 aEndTangent = ANGLE_0;
3972
3973 switch( m_shape )
3974 {
3975 case SHAPE_T::SEGMENT:
3976 {
3977 EDA_ANGLE lineAngle( GetEnd() - GetStart() );
3978 aStartTangent = lineAngle + ANGLE_180;
3979 aEndTangent = lineAngle;
3980 break;
3981 }
3982
3983 case SHAPE_T::ARC:
3984 {
3985 SHAPE_ARC arc( GetStart(), GetArcMid(), GetEnd(), 0 );
3986 double radius = arc.GetRadius();
3987 EDA_ANGLE startRadius = arc.GetStartAngle();
3988 EDA_ANGLE endRadius = arc.GetEndAngle();
3989 bool cw = arc.GetCentralAngle() < ANGLE_0;
3990
3991 // Offset tangent angle by half the shortening depth for better visual alignment.
3992 if( aLineWidth > 0 && radius > 0 )
3993 {
3994 int startDepth = m_startEnding.GetCurveOrientationDepth( aLineWidth );
3995
3996 if( startDepth > 0 )
3997 {
3998 EDA_ANGLE offset( RAD2DEG( ( startDepth / 2.0 ) / radius ), DEGREES_T );
3999
4000 if( cw )
4001 startRadius -= offset;
4002 else
4003 startRadius += offset;
4004 }
4005
4006 int endDepth = m_endEnding.GetCurveOrientationDepth( aLineWidth );
4007
4008 if( endDepth > 0 )
4009 {
4010 EDA_ANGLE offset( RAD2DEG( ( endDepth / 2.0 ) / radius ), DEGREES_T );
4011
4012 if( cw )
4013 endRadius += offset;
4014 else
4015 endRadius -= offset;
4016 }
4017 }
4018
4019 if( cw )
4020 {
4021 aStartTangent = startRadius + ANGLE_90;
4022 aEndTangent = endRadius - ANGLE_90;
4023 }
4024 else
4025 {
4026 aStartTangent = startRadius - ANGLE_90;
4027 aEndTangent = endRadius + ANGLE_90;
4028 }
4029
4030 break;
4031 }
4032
4033 case SHAPE_T::BEZIER:
4034 {
4035 // Tangent = (original endpoint - shortened endpoint) so that the
4036 // end shape aligns with the visible curve endpoint.
4038 VECTOR2D( m_end ) };
4039 std::optional<double> totalLength;
4040 std::optional<BEZIER<double>> shortened = ShortenedBezierCurve( aLineWidth );
4041 const std::vector<VECTOR2I>& bpts = GetBezierPoints();
4042
4043 auto fallbackStartTangent = [&]() -> EDA_ANGLE
4044 {
4045 if( GetStart() != GetBezierC1() )
4046 return EDA_ANGLE( GetStart() - GetBezierC1() );
4047
4048 if( bpts.size() >= 2 )
4049 return EDA_ANGLE( bpts.front() - bpts[1] );
4050
4051 return ANGLE_0;
4052 };
4053
4054 auto fallbackEndTangent = [&]() -> EDA_ANGLE
4055 {
4056 if( GetEnd() != GetBezierC2() )
4057 return EDA_ANGLE( GetEnd() - GetBezierC2() );
4058
4059 if( bpts.size() >= 2 )
4060 return EDA_ANGLE( bpts.back() - bpts[bpts.size() - 2] );
4061
4062 return ANGLE_0;
4063 };
4064
4065 auto openArrowFlexTangent = [&]( const LINE_ENDING& aEnding, bool aStart ) -> std::optional<EDA_ANGLE>
4066 {
4067 if( aEnding.GetStyle() != LINE_ENDING_STYLE::ARROW_OPEN )
4068 return std::nullopt;
4069
4070 int depth = aEnding.GetCurveOrientationDepth( aLineWidth );
4071
4072 if( depth <= 0 )
4073 return std::nullopt;
4074
4075 if( !totalLength )
4076 totalLength = bezierLength( sourceCurve, 0.0, 1.0 );
4077
4078 if( *totalLength <= 0.0 )
4079 return std::nullopt;
4080
4081 double distanceFromStart = std::min<double>( depth, *totalLength );
4082
4083 if( !aStart )
4084 distanceFromStart = *totalLength - distanceFromStart;
4085
4086 double t = findBezierTAtLength( sourceCurve, distanceFromStart, *totalLength );
4087 VECTOR2D sample = sourceCurve.PointAt( t );
4088 VECTOR2D endpoint = aStart ? VECTOR2D( GetStart() ) : VECTOR2D( GetEnd() );
4089 VECTOR2D delta = endpoint - sample;
4090
4091 if( delta.EuclideanNorm() <= 0.0 )
4092 return std::nullopt;
4093
4094 return EDA_ANGLE( delta );
4095 };
4096
4097 if( shortened )
4098 {
4099 VECTOR2D startDelta = VECTOR2D( GetStart() ) - shortened->Start;
4100
4101 if( startDelta.EuclideanNorm() > 0 )
4102 aStartTangent = EDA_ANGLE( startDelta );
4103 else
4104 aStartTangent = fallbackStartTangent();
4105
4106 VECTOR2D endDelta = VECTOR2D( GetEnd() ) - shortened->End;
4107
4108 if( endDelta.EuclideanNorm() > 0 )
4109 aEndTangent = EDA_ANGLE( endDelta );
4110 else
4111 aEndTangent = fallbackEndTangent();
4112 }
4113 else
4114 {
4115 aStartTangent = fallbackStartTangent();
4116 aEndTangent = fallbackEndTangent();
4117 }
4118
4119 if( std::optional<EDA_ANGLE> startFlex = openArrowFlexTangent( m_startEnding, true ) )
4120 aStartTangent = *startFlex;
4121
4122 if( std::optional<EDA_ANGLE> endFlex = openArrowFlexTangent( m_endEnding, false ) )
4123 aEndTangent = *endFlex;
4124
4125 break;
4126 }
4127
4128 case SHAPE_T::POLY:
4129 {
4130 const SHAPE_POLY_SET& poly = GetPolyShape();
4131
4132 if( poly.OutlineCount() > 0 )
4133 {
4134 const SHAPE_LINE_CHAIN& outline = poly.Outline( 0 );
4135 int ptCount = outline.PointCount();
4136
4137 if( ptCount >= 2 )
4138 {
4139 aStartTangent = EDA_ANGLE( outline.CPoint( 0 ) - outline.CPoint( 1 ) );
4140 aEndTangent = EDA_ANGLE( outline.CPoint( ptCount - 1 ) - outline.CPoint( ptCount - 2 ) );
4141 }
4142 }
4143
4144 break;
4145 }
4146
4147 default:
4148 aStartTangent = ANGLE_0;
4149 aEndTangent = ANGLE_0;
4150 break;
4151 }
4152}
4153
4154
4155bool EDA_SHAPE::GetLineEndingEndpoints( VECTOR2I& aStartPoint, VECTOR2I& aEndPoint ) const
4156{
4157 switch( m_shape )
4158 {
4159 case SHAPE_T::SEGMENT:
4160 case SHAPE_T::ARC:
4161 aStartPoint = GetStart();
4162 aEndPoint = GetEnd();
4163 return true;
4164
4165 case SHAPE_T::POLY:
4166 {
4167 const SHAPE_POLY_SET& poly = GetPolyShape();
4168
4169 if( poly.OutlineCount() == 0 )
4170 return false;
4171
4172 const SHAPE_LINE_CHAIN& outline = poly.COutline( 0 );
4173
4174 if( outline.PointCount() < 2 )
4175 return false;
4176
4177 aStartPoint = outline.CPoint( 0 );
4178 aEndPoint = outline.CPoint( outline.PointCount() - 1 );
4179 return true;
4180 }
4181
4182 case SHAPE_T::BEZIER:
4183 {
4184 const std::vector<VECTOR2I>& bpts = GetBezierPoints();
4185
4186 if( bpts.size() < 2 )
4187 return false;
4188
4189 aStartPoint = bpts.front();
4190 aEndPoint = bpts.back();
4191 return true;
4192 }
4193
4194 default: return false;
4195 }
4196}
4197
4198
4199bool EDA_SHAPE::ShortenSegmentForEndings( VECTOR2I& aStart, VECTOR2I& aEnd, const LINE_ENDING& aStartEnding,
4200 const LINE_ENDING& aEndEnding, int aLineWidth )
4201{
4202 VECTOR2I delta = aEnd - aStart;
4203 double len = delta.EuclideanNorm();
4204
4205 if( len == 0 )
4206 return true;
4207
4208 int startDepth = aStartEnding.GetShortenDepth( aLineWidth );
4209 int endDepth = aEndEnding.GetShortenDepth( aLineWidth );
4210 int totalDepth = std::max( 0, startDepth ) + std::max( 0, endDepth );
4211
4212 if( totalDepth <= 0 )
4213 return true;
4214
4215 if( totalDepth >= len )
4216 {
4217 aEnd = aStart;
4218 return false;
4219 }
4220
4221 VECTOR2D dir( delta.x / len, delta.y / len );
4222
4223 if( startDepth > 0 )
4224 {
4225 aStart.x += KiROUND( dir.x * startDepth );
4226 aStart.y += KiROUND( dir.y * startDepth );
4227 }
4228
4229 if( endDepth > 0 )
4230 {
4231 aEnd.x -= KiROUND( dir.x * endDepth );
4232 aEnd.y -= KiROUND( dir.y * endDepth );
4233 }
4234
4235 return true;
4236}
4237
4238
4239bool EDA_SHAPE::ShortenArcForEndings( EDA_ANGLE& aStartAngle, EDA_ANGLE& aArcAngle, double aRadius,
4240 int aLineWidth ) const
4241{
4242 if( aRadius <= 0 )
4243 return true;
4244
4245 int startDepth = m_startEnding.GetShortenDepth( aLineWidth );
4246 int endDepth = m_endEnding.GetShortenDepth( aLineWidth );
4247 int totalDepth = std::max( 0, startDepth ) + std::max( 0, endDepth );
4248
4249 if( totalDepth <= 0 )
4250 return true;
4251
4252 double arcLength = std::abs( aRadius * aArcAngle.AsRadians() );
4253
4254 if( totalDepth >= arcLength )
4255 {
4256 aArcAngle = ANGLE_0;
4257 return false;
4258 }
4259
4260 EDA_ANGLE startOffset = EDA_ANGLE( RAD2DEG( std::max( 0, startDepth ) / aRadius ), DEGREES_T );
4261 EDA_ANGLE totalOffset = EDA_ANGLE( RAD2DEG( totalDepth / aRadius ), DEGREES_T );
4262
4263 if( aArcAngle > ANGLE_0 )
4264 {
4265 aStartAngle += startOffset;
4266 aArcAngle -= totalOffset;
4267 }
4268 else
4269 {
4270 aStartAngle -= startOffset;
4271 aArcAngle += totalOffset;
4272 }
4273
4274 return true;
4275}
4276
4277
4278static double bezierSpeedAt( const BEZIER<double>& aBezier, double aT )
4279{
4280 double tInv = 1.0 - aT;
4281
4282 VECTOR2D derivative = 3.0 * tInv * tInv * ( aBezier.C1 - aBezier.Start )
4283 + 6.0 * tInv * aT * ( aBezier.C2 - aBezier.C1 )
4284 + 3.0 * aT * aT * ( aBezier.End - aBezier.C2 );
4285
4286 return derivative.EuclideanNorm();
4287}
4288
4289
4290static double bezierLength( const BEZIER<double>& aBezier, double aT0, double aT1 )
4291{
4292 if( aT1 <= aT0 )
4293 return 0.0;
4294
4295 // 16-point Gauss-Legendre integration. This avoids allocating and
4296 // flattening a sub-curve on every binary-search step during redraw.
4297 static constexpr double nodes[] = {
4298 0.0950125098376374, 0.2816035507792590, 0.4580167776572274, 0.6178762444026438,
4299 0.7554044083550030, 0.8656312023878318, 0.9445750230732326, 0.9894009349916499
4300 };
4301
4302 static constexpr double weights[] = { 0.1894506104550685, 0.1826034150449236, 0.1691565193950025,
4303 0.1495959888165767, 0.1246289712555339, 0.0951585116824928,
4304 0.0622535239386479, 0.0271524594117541 };
4305
4306 static_assert( std::size( nodes ) == std::size( weights ) );
4307
4308 double halfWidth = ( aT1 - aT0 ) / 2.0;
4309 double center = ( aT0 + aT1 ) / 2.0;
4310 double length = 0.0;
4311
4312 for( size_t ii = 0; ii < std::size( nodes ); ++ii )
4313 {
4314 double offset = halfWidth * nodes[ii];
4315 length +=
4316 weights[ii] * ( bezierSpeedAt( aBezier, center - offset ) + bezierSpeedAt( aBezier, center + offset ) );
4317 }
4318
4319 return halfWidth * length;
4320}
4321
4322
4323static double findBezierTAtLength( const BEZIER<double>& aBezier, double aTargetLength, double aTotalLength )
4324{
4325 if( aTargetLength <= 0.0 )
4326 return 0.0;
4327
4328 if( aTargetLength >= aTotalLength )
4329 return 1.0;
4330
4331 double low = 0.0;
4332 double high = 1.0;
4333
4334 // Keep the parameter search tighter than the flattened-length tolerance.
4335 static constexpr int paramSearchIterations = 24;
4336
4337 for( int ii = 0; ii < paramSearchIterations; ++ii )
4338 {
4339 double mid = ( low + high ) / 2.0;
4340 double len = bezierLength( aBezier, 0.0, mid );
4341
4342 if( len < aTargetLength )
4343 low = mid;
4344 else
4345 high = mid;
4346 }
4347
4348 return ( low + high ) / 2.0;
4349}
4350
4351
4352std::optional<BEZIER<double>> EDA_SHAPE::ShortenedBezierCurve( int aLineWidth ) const
4353{
4354 if( m_shape != SHAPE_T::BEZIER )
4355 return std::nullopt;
4356
4358
4359 int startDepth = m_startEnding.GetShortenDepth( aLineWidth );
4360 int endDepth = m_endEnding.GetShortenDepth( aLineWidth );
4361
4362 if( startDepth <= 0 && endDepth <= 0 )
4363 return curve;
4364
4365 double totalLength = bezierLength( curve, 0.0, 1.0 );
4366
4367 if( totalLength <= 0.0 || startDepth + endDepth >= totalLength )
4368 return std::nullopt;
4369
4370 double t0 = findBezierTAtLength( curve, startDepth, totalLength );
4371 double t1 = findBezierTAtLength( curve, totalLength - endDepth, totalLength );
4372
4373 if( t1 <= t0 )
4374 return std::nullopt;
4375
4376 return curve.SubCurve( t0, t1 );
4377}
4378
4379
4380std::vector<VECTOR2D> EDA_SHAPE::ShortenedBezierPolyline( int aLineWidth ) const
4381{
4382 std::vector<VECTOR2D> pts;
4383 std::optional<BEZIER<double>> curve = ShortenedBezierCurve( aLineWidth );
4384
4385 if( !curve )
4386 {
4387 return pts;
4388 }
4389
4390 std::vector<VECTOR2D> ctrlPts = { curve->Start, curve->C1, curve->C2, curve->End };
4391 BEZIER_POLY converter( ctrlPts );
4392
4393 converter.GetPoly( pts, std::max( 1, getMaxError() ) );
4394
4395 return pts;
4396}
4397
4398
4399bool EDA_SHAPE::ShortenPolyForEndings( VECTOR2D& aFirst, VECTOR2D& aLast, const VECTOR2D& aSecond,
4400 const VECTOR2D& aPenultimate, int aLineWidth ) const
4401{
4402 int startDepth = m_startEnding.GetShortenDepth( aLineWidth );
4403 int endDepth = m_endEnding.GetShortenDepth( aLineWidth );
4404 int totalDepth = std::max( 0, startDepth ) + std::max( 0, endDepth );
4405
4406 if( totalDepth <= 0 )
4407 return true;
4408
4409 double startSegLen = ( aSecond - aFirst ).EuclideanNorm();
4410 double endSegLen = ( aLast - aPenultimate ).EuclideanNorm();
4411 bool twoPointPoly = aSecond == aLast && aPenultimate == aFirst;
4412
4413 if( twoPointPoly && startSegLen <= totalDepth )
4414 {
4415 aLast = aFirst;
4416 return false;
4417 }
4418
4419 if( startDepth > 0 && startSegLen <= startDepth )
4420 {
4421 aLast = aFirst;
4422 return false;
4423 }
4424
4425 if( endDepth > 0 && endSegLen <= endDepth )
4426 {
4427 aLast = aFirst;
4428 return false;
4429 }
4430
4431 if( startDepth > 0 )
4432 {
4433 VECTOR2D dir = aSecond - aFirst;
4434
4435 if( startSegLen > 0 )
4436 aFirst = aFirst + dir * ( startDepth / startSegLen );
4437 }
4438
4439 if( endDepth > 0 )
4440 {
4441 VECTOR2D dir = aLast - aPenultimate;
4442
4443 if( endSegLen > 0 )
4444 aLast = aLast - dir * ( endDepth / endSegLen );
4445 }
4446
4447 return true;
4448}
4449
4450
4451bool EDA_SHAPE::ShortenBodyPolyPoints( std::vector<VECTOR2I>& aPoints, bool aClosed, int aOutlineIdx,
4452 int aLineWidth ) const
4453{
4454 if( aPoints.size() < 2 )
4455 return false;
4456
4457 if( aClosed || aOutlineIdx != 0 )
4458 return true;
4459
4460 VECTOR2D first = aPoints.front();
4461 VECTOR2D last = aPoints.back();
4462
4463 if( !ShortenPolyForEndings( first, last, VECTOR2D( aPoints[1] ), VECTOR2D( aPoints[aPoints.size() - 2] ),
4464 aLineWidth ) )
4465 {
4466 aPoints.clear();
4467 return false;
4468 }
4469
4470 aPoints.front() = VECTOR2I( first );
4471 aPoints.back() = VECTOR2I( last );
4472
4473 return true;
4474}
4475
4476
4477bool EDA_SHAPE::GetShortenedBodyPolyPoints( const SHAPE_LINE_CHAIN& aOutline, int aOutlineIdx,
4478 std::vector<VECTOR2I>& aPoints, int aLineWidth ) const
4479{
4480 aPoints.clear();
4481
4482 if( aOutline.PointCount() < 2 )
4483 return false;
4484
4485 aPoints.reserve( aOutline.PointCount() );
4486
4487 for( const VECTOR2I& pt : aOutline.CPoints() )
4488 aPoints.emplace_back( pt );
4489
4490 return ShortenBodyPolyPoints( aPoints, aOutline.IsClosed(), aOutlineIdx, aLineWidth );
4491}
4492
4493
4498
4499
4500static struct EDA_SHAPE_DESC
4501{
4503 {
4505 .Map( SHAPE_T::SEGMENT, _HKI( "Segment" ) )
4506 .Map( SHAPE_T::RECTANGLE, _HKI( "Rectangle" ) )
4507 .Map( SHAPE_T::ARC, _HKI( "Arc" ) )
4508 .Map( SHAPE_T::CIRCLE, _HKI( "Circle" ) )
4509 .Map( SHAPE_T::POLY, _HKI( "Polygon" ) )
4510 .Map( SHAPE_T::BEZIER, _HKI( "Bezier" ) )
4511 .Map( SHAPE_T::ELLIPSE, _HKI( "Ellipse" ) )
4512 .Map( SHAPE_T::ELLIPSE_ARC, _HKI( "Elliptical Arc" ) );
4513
4515
4516 if( lineStyleEnum.Choices().GetCount() == 0 )
4517 {
4518 lineStyleEnum.Map( LINE_STYLE::SOLID, _HKI( "Solid" ) )
4519 .Map( LINE_STYLE::DASH, _HKI( "Dashed" ) )
4520 .Map( LINE_STYLE::DOT, _HKI( "Dotted" ) )
4521 .Map( LINE_STYLE::DASHDOT, _HKI( "Dash-Dot" ) )
4522 .Map( LINE_STYLE::DASHDOTDOT, _HKI( "Dash-Dot-Dot" ) );
4523 }
4524
4526
4527 if( hatchModeEnum.Choices().GetCount() == 0 )
4528 {
4529 hatchModeEnum.Map( UI_FILL_MODE::NONE, _HKI( "None" ) );
4530 hatchModeEnum.Map( UI_FILL_MODE::SOLID, _HKI( "Solid" ) );
4531 hatchModeEnum.Map( UI_FILL_MODE::HATCH, _HKI( "Hatch" ) );
4532 hatchModeEnum.Map( UI_FILL_MODE::REVERSE_HATCH, _HKI( "Reverse Hatch" ) );
4533 hatchModeEnum.Map( UI_FILL_MODE::CROSS_HATCH, _HKI( "Cross-hatch" ) );
4534 }
4535
4538
4539 auto isNotPolygonOrCircle =
4540 []( INSPECTABLE* aItem ) -> bool
4541 {
4542 // Polygons, unlike other shapes, have no meaningful start or end coordinates
4543 if( EDA_SHAPE* shape = dynamic_cast<EDA_SHAPE*>( aItem ) )
4544 return shape->GetShape() != SHAPE_T::POLY && shape->GetShape() != SHAPE_T::CIRCLE;
4545
4546 return false;
4547 };
4548
4549 auto isCircle =
4550 []( INSPECTABLE* aItem ) -> bool
4551 {
4552 // Polygons, unlike other shapes, have no meaningful start or end coordinates
4553 if( EDA_SHAPE* shape = dynamic_cast<EDA_SHAPE*>( aItem ) )
4554 return shape->GetShape() == SHAPE_T::CIRCLE;
4555
4556 return false;
4557 };
4558
4559 auto isRectangle =
4560 []( INSPECTABLE* aItem ) -> bool
4561 {
4562 // Polygons, unlike other shapes, have no meaningful start or end coordinates
4563 if( EDA_SHAPE* shape = dynamic_cast<EDA_SHAPE*>( aItem ) )
4564 return shape->GetShape() == SHAPE_T::RECTANGLE;
4565
4566 return false;
4567 };
4568
4569 auto isEllipseOrEllipseArc =
4570 []( INSPECTABLE* aItem ) -> bool
4571 {
4572 if( EDA_SHAPE* shape = dynamic_cast<EDA_SHAPE*>( aItem ) )
4573 {
4574 return shape->GetShape() == SHAPE_T::ELLIPSE || shape->GetShape() == SHAPE_T::ELLIPSE_ARC;
4575 }
4576
4577 return false;
4578 };
4579
4580 auto isEllipseArc =
4581 []( INSPECTABLE* aItem ) -> bool
4582 {
4583 if( EDA_SHAPE* shape = dynamic_cast<EDA_SHAPE*>( aItem ) )
4584 return shape->GetShape() == SHAPE_T::ELLIPSE_ARC;
4585
4586 return false;
4587 };
4588
4589 const wxString shapeProps = _HKI( "Shape Properties" );
4590
4591 propMgr.AddProperty( new PROPERTY_ENUM<EDA_SHAPE, SHAPE_T>( _HKI( "Shape" ),
4593 shapeProps );
4594
4595 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "Start X" ),
4598 shapeProps )
4599 .SetAvailableFunc( isNotPolygonOrCircle );
4600 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "Start Y" ),
4603 shapeProps )
4604 .SetAvailableFunc( isNotPolygonOrCircle );
4605
4606 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "Center X" ),
4609 shapeProps )
4610 .SetAvailableFunc( isCircle );
4611
4612 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "Center Y" ),
4615 shapeProps )
4616 .SetAvailableFunc( isCircle );
4617
4618 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "Radius" ),
4621 shapeProps )
4622 .SetAvailableFunc( isCircle );
4623
4624 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "End X" ),
4627 shapeProps )
4628 .SetAvailableFunc( isNotPolygonOrCircle );
4629
4630 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "End Y" ),
4633 shapeProps )
4634 .SetAvailableFunc( isNotPolygonOrCircle );
4635
4636 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "Width" ),
4639 shapeProps )
4640 .SetAvailableFunc( isRectangle );
4641
4642 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "Height" ),
4645 shapeProps )
4646 .SetAvailableFunc( isRectangle );
4647
4648 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "Corner Radius" ),
4651 shapeProps )
4652 .SetAvailableFunc( isRectangle )
4653 .SetValidator( []( const wxAny&& aValue, EDA_ITEM* aItem ) -> VALIDATOR_RESULT
4654 {
4655 wxASSERT_MSG( aValue.CheckType<int>(),
4656 "Expecting int-containing value" );
4657
4658 int radius = aValue.As<int>();
4659
4660 EDA_SHAPE* prop_shape = dynamic_cast<EDA_SHAPE*>( aItem );
4661
4662 if( !prop_shape )
4663 return std::nullopt;
4664
4665 int maxRadius = std::min( prop_shape->GetRectangleWidth(),
4666 prop_shape->GetRectangleHeight() ) / 2;
4667
4668 if( radius > maxRadius )
4669 return std::make_unique<VALIDATION_ERROR_TOO_LARGE<int>>( radius, maxRadius );
4670 else if( radius < 0 )
4671 return std::make_unique<VALIDATION_ERROR_TOO_SMALL<int>>( radius, 0 );
4672
4673 return std::nullopt;
4674 } );
4675
4676 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "Major Radius" ),
4679 shapeProps )
4680 .SetAvailableFunc( isEllipseOrEllipseArc );
4681
4682 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "Minor Radius" ),
4685 shapeProps )
4686 .SetAvailableFunc( isEllipseOrEllipseArc );
4687
4688 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, EDA_ANGLE>( _HKI( "Ellipse Rotation" ),
4690 shapeProps )
4691 .SetAvailableFunc( isEllipseOrEllipseArc );
4692
4693 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, EDA_ANGLE>( _HKI( "Arc Start Angle" ),
4696 shapeProps )
4697 .SetAvailableFunc( isEllipseArc );
4698
4699 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, EDA_ANGLE>( _HKI( "Arc End Angle" ),
4701 shapeProps )
4702 .SetAvailableFunc( isEllipseArc );
4703
4704 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "Line Width" ),
4706 shapeProps ).SetIsCopyable();
4707
4708 propMgr.AddProperty( new PROPERTY_ENUM<EDA_SHAPE, LINE_STYLE>( _HKI( "Line Style" ),
4710 shapeProps ).SetIsCopyable();
4711
4712 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, COLOR4D>( _HKI( "Line Color" ),
4714 shapeProps )
4716
4720 shapeProps )
4722 [=]( INSPECTABLE* aItem ) -> bool
4723 {
4724 if( EDA_SHAPE* curr_shape = dynamic_cast<EDA_SHAPE*>( aItem ) )
4725 return curr_shape->GetShape() == SHAPE_T::ARC;
4726
4727 return false;
4728 } )
4729 .SetValidator(
4730 []( const wxAny&& aValue, EDA_ITEM* aItem ) -> VALIDATOR_RESULT
4731 {
4732 double degrees = 0.0;
4733
4734 if( aValue.GetAs( &degrees ) && degrees == 0.0 )
4735 {
4736 return std::make_unique<VALIDATION_ERROR_MSG>( _( "Arc angle must not be zero." ) );
4737 }
4738
4739 return std::nullopt;
4740 } );
4741
4742 auto fillAvailable =
4743 [=]( INSPECTABLE* aItem ) -> bool
4744 {
4745 if( EDA_ITEM* edaItem = dynamic_cast<EDA_ITEM*>( aItem ) )
4746 {
4747 // For some reason masking "Filled" and "Fill Color" at the
4748 // PCB_TABLECELL level doesn't work.
4749 if( edaItem->Type() == PCB_TABLECELL_T || edaItem->Type() == PCB_TEXTBOX_T )
4750 return false;
4751 }
4752
4753 if( EDA_SHAPE* edaShape = dynamic_cast<EDA_SHAPE*>( aItem ) )
4754 {
4755 switch( edaShape->GetShape() )
4756 {
4757 case SHAPE_T::POLY:
4758 case SHAPE_T::RECTANGLE:
4759 case SHAPE_T::CIRCLE:
4760 case SHAPE_T::BEZIER:
4761 case SHAPE_T::ELLIPSE: return true;
4762
4763 default:
4764 return false;
4765 }
4766 }
4767
4768 return false;
4769 };
4770
4773 shapeProps )
4774 .SetAvailableFunc( fillAvailable ).SetIsCopyable();
4775
4776 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, COLOR4D>( _HKI( "Fill Color" ),
4778 shapeProps )
4779 .SetAvailableFunc( fillAvailable )
4781
4782 auto isOpenShape =
4783 []( INSPECTABLE* aItem ) -> bool
4784 {
4785 if( EDA_SHAPE* shape = dynamic_cast<EDA_SHAPE*>( aItem ) )
4786 return !shape->IsClosed();
4787
4788 return false;
4789 };
4790
4792
4793 if( endingStyleEnum.Choices().GetCount() == 0 )
4794 {
4795 endingStyleEnum.Map( LINE_ENDING_STYLE::NONE, _HKI( "None" ) )
4796 .Map( LINE_ENDING_STYLE::ARROW, _HKI( "Arrow" ) )
4797 .Map( LINE_ENDING_STYLE::CIRCLE, _HKI( "Circle" ) )
4798 .Map( LINE_ENDING_STYLE::SQUARE, _HKI( "Square" ) )
4799 .Map( LINE_ENDING_STYLE::ARROW_OPEN, _HKI( "Open Arrow" ) );
4800 }
4801
4802 propMgr.AddProperty( new PROPERTY_ENUM<EDA_SHAPE, LINE_ENDING_STYLE>( _HKI( "Start Shape" ),
4804 shapeProps )
4806
4807 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "Start Length" ),
4809 shapeProps )
4811
4812 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "Start Width" ),
4814 shapeProps )
4816
4817 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "Start Stroke Width" ),
4820 shapeProps )
4822
4825 shapeProps )
4827
4828 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "End Length" ),
4830 shapeProps )
4832
4833 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "End Width" ),
4835 shapeProps )
4837
4838 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "End Stroke Width" ),
4841 shapeProps )
4843 }
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:121
BOX2< VECTOR2I > BOX2I
Definition box2.h:927
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:995
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:653
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:399
EDA_ANGLE Normalize()
Definition eda_angle.h:229
double Sin() const
Definition eda_angle.h:178
int AsTenthsOfADegree() const
Definition eda_angle.h:118
bool IsCardinal() const
Definition eda_angle.cpp:40
EDA_ANGLE Normalize720()
Definition eda_angle.h:279
double AsRadians() const
Definition eda_angle.h:120
double Cos() const
Definition eda_angle.h:197
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:733
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:761
int m_cornerRadius
Definition eda_shape.h:745
bool m_hatchingDirty
Definition eda_shape.h:741
bool m_endsSwapped
Definition eda_shape.h:732
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:760
virtual int getMaxError() const
Definition eda_shape.h:725
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
void SetCornerRadius(int aRadius)
long long int m_rectangleHeight
Definition eda_shape.h:743
int GetEllipseMajorRadius() const
Definition eda_shape.h:386
std::unique_ptr< EDA_SHAPE_HATCH_CACHE_DATA > m_hatchingCache
Definition eda_shape.h:740
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:757
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:549
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:694
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:660
VECTOR2I m_arcCenter
Definition eda_shape.h:750
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:751
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:747
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:756
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:735
void SetRectangleWidth(const int &aWidth)
virtual void SetEllipseStartAngle(const EDA_ANGLE &aA)
Definition eda_shape.h:407
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:748
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:734
LINE_ENDING m_endEnding
Definition eda_shape.h:736
LINE_ENDING_STYLE GetEndEndingStyle() const
Definition eda_shape.h:186
void RebuildBezierToSegmentsPointsList()
Definition eda_shape.h:539
void SetPolyPoints(const std::vector< VECTOR2I > &aPoints)
EDA_SHAPE & operator=(const EDA_SHAPE &aOther)
VECTOR2I m_bezierC1
Definition eda_shape.h:753
FILL_T m_fill
Definition eda_shape.h:737
COLOR4D m_fillColor
Definition eda_shape.h:738
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:758
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:744
VECTOR2I m_bezierC2
Definition eda_shape.h:754
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:776
static ENUM_MAP< T > & Instance()
Definition property.h:770
wxPGChoices & Choices()
Definition property.h:821
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.Helper macro to map type hashes to names.
static PROPERTY_MANAGER & Instance()
PROPERTY_BASE & AddProperty(PROPERTY_BASE *aProperty, const wxString &aGroup=wxEmptyString)
Register a property.
A round rectangle shape, based on a rectangle and a radius.
Definition roundrect.h:32
void TransformToPolygon(SHAPE_POLY_SET &aBuffer, int aMaxError) const
Get the polygonal representation of the roundrect.
Definition roundrect.cpp:79
Definition seg.h:38
VECTOR2I A
Definition seg.h:45
VECTOR2I B
Definition seg.h:46
int Length() const
Return the length (this).
Definition seg.h:339
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:179
int GetWidth() const
LINE_STYLE GetLineStyle() const
wxString MessageTextFromValue(double aValue, bool aAddUnitLabel=true, EDA_DATA_TYPE aType=EDA_DATA_TYPE::DISTANCE) const
A lower-precision version of StringFromValue().
double Distance(const VECTOR2< extended_type > &aVector) const
Compute the distance between two vectors.
Definition vector2d.h:549
constexpr extended_type SquaredEuclideanNorm() const
Compute the squared euclidean norm of the vector, which is defined as (x ** 2 + y ** 2).
Definition vector2d.h:303
T EuclideanNorm() const
Compute the Euclidean norm of the vector, which is defined as sqrt(x ** 2 + y ** 2).
Definition vector2d.h:279
void TransformRingToPolygon(SHAPE_POLY_SET &aBuffer, const VECTOR2I &aCentre, int aRadius, int aWidth, int aError, ERROR_LOC aErrorLoc)
Convert arcs to multiple straight segments.
void TransformCircleToPolygon(SHAPE_LINE_CHAIN &aBuffer, const VECTOR2I &aCenter, int aRadius, int aError, ERROR_LOC aErrorLoc, int aMinSegCount=0)
Convert a circle to a polygon, using multiple straight lines.
void TransformArcToPolygon(SHAPE_POLY_SET &aBuffer, const VECTOR2I &aStart, const VECTOR2I &aMid, const VECTOR2I &aEnd, int aWidth, int aError, ERROR_LOC aErrorLoc)
Convert arc to multiple straight segments.
void TransformRoundChamferedRectToPolygon(SHAPE_POLY_SET &aBuffer, const VECTOR2I &aPosition, const VECTOR2I &aSize, const EDA_ANGLE &aRotation, int aCornerRadius, double aChamferRatio, int aChamferCorners, int aInflate, int aError, ERROR_LOC aErrorLoc)
Convert a rectangle with rounded corners and/or chamfered corners to a polygon.
void TransformOvalToPolygon(SHAPE_POLY_SET &aBuffer, const VECTOR2I &aStart, const VECTOR2I &aEnd, int aWidth, int aError, ERROR_LOC aErrorLoc, int aMinSegCount=0)
Convert a oblong shape to a polygon, using multiple segments.
@ ROUND_ALL_CORNERS
All angles are rounded.
#define _(s)
static constexpr EDA_ANGLE ANGLE_0
Definition eda_angle.h:422
static constexpr EDA_ANGLE ANGLE_90
Definition eda_angle.h:424
@ RADIANS_T
Definition eda_angle.h:32
@ DEGREES_T
Definition eda_angle.h:31
static constexpr EDA_ANGLE ANGLE_45
Definition eda_angle.h:423
static constexpr EDA_ANGLE ANGLE_270
Definition eda_angle.h:427
static constexpr EDA_ANGLE ANGLE_360
Definition eda_angle.h:428
static constexpr EDA_ANGLE ANGLE_180
Definition eda_angle.h:426
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
static bool isOpenShape(SHAPE_T aShape)
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)
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:411
#define _HKI(x)
Definition page_info.cpp:40
#define IMPLEMENT_ENUM_TO_WXANY(type)
Definition property.h:875
#define NO_SETTER(owner, type)
Definition property.h:882
@ PT_COORD
Coordinate expressed in distance units (mm/inch)
Definition property.h:65
@ PT_DECIDEGREE
Angle expressed in decidegrees.
Definition property.h:67
@ PT_SIZE
Size expressed in distance units (mm/inch)
Definition property.h:63
#define REGISTER_TYPE(x)
std::optional< std::unique_ptr< VALIDATION_ERROR > > VALIDATOR_RESULT
Null optional means validation succeeded.
@ SH_POLY_SET
set of polygons (with holes, etc.)
Definition shape.h:48
@ SH_CIRCLE
circle
Definition shape.h:46
@ SH_SIMPLE
simple polygon
Definition shape.h:47
@ SH_ELLIPSE
ellipse or elliptical arc
Definition shape.h:53
@ SH_NULL
empty shape (no shape...),
Definition shape.h:51
@ SH_SEGMENT
line segment
Definition shape.h:44
@ SH_ARC
circular arc
Definition shape.h:50
@ SH_POLY_SET_TRIANGLE
a single triangle belonging to a POLY_SET triangulation
Definition shape.h:52
@ SH_LINE_CHAIN
line chain (polyline)
Definition shape.h:45
@ SH_COMPOUND
compound shape, consisting of multiple simple shapes
Definition shape.h:49
static bool Collide(const SHAPE_CIRCLE &aA, const SHAPE_CIRCLE &aB, int aClearance, int *aActual, VECTOR2I *aLocation, VECTOR2I *aMTV)
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:171
void RotatePoint(int *pX, int *pY, const EDA_ANGLE &aAngle)
Calculate the new point of coord coord pX, pY, for a rotation center 0, 0.
Definition trigo.cpp:225
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:562
@ 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:683
VECTOR2< double > VECTOR2D
Definition vector2d.h:682