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