void TransformOvalClearanceToPolygon( SHAPE_POLY_SET& aCornerBuffer, wxPoint aStart, wxPoint aEnd, int aWidth, int aCircleToSegmentsCount, double aCorrectionFactor ) { // To build the polygonal shape outside the actual shape, we use a bigger // radius to build rounded ends. // However, the width of the segment is too big. // so, later, we will clamp the polygonal shape with the bounding box // of the segment. int radius = aWidth / 2; // Note if we want to compensate the radius reduction of a circle due to // the segment approx, aCorrectionFactor must be calculated like this: // For a circle the min radius is radius * cos( 2PI / s_CircleToSegmentsCount / 2) // aCorrectionFactor is 1 /cos( PI/s_CircleToSegmentsCount ) radius = radius * aCorrectionFactor; // make segments outside the circles // end point is the coordinate relative to aStart wxPoint endp = aEnd - aStart; wxPoint startp = aStart; wxPoint corner; SHAPE_POLY_SET polyshape; polyshape.NewOutline(); // normalize the position in order to have endp.x >= 0 // it makes calculations more easy to understand if( endp.x < 0 ) { endp = aStart - aEnd; startp = aEnd; } // delta_angle is in radian double delta_angle = atan2( (double)endp.y, (double)endp.x ); int seg_len = KiROUND( EuclideanNorm( endp ) ); double delta = 3600.0 / aCircleToSegmentsCount; // rot angle in 0.1 degree // Compute the outlines of the segment, and creates a polygon // Note: the polygonal shape is built from the equivalent horizontal // segment starting ar 0,0, and ending at seg_len,0 // add right rounded end: for( int ii = 0; ii < aCircleToSegmentsCount/2; ii++ ) { corner = wxPoint( 0, radius ); RotatePoint( &corner, delta*ii ); corner.x += seg_len; polyshape.Append( corner.x, corner.y ); } // Finish arc: corner = wxPoint( seg_len, -radius ); polyshape.Append( corner.x, corner.y ); // add left rounded end: for( int ii = 0; ii < aCircleToSegmentsCount/2; ii++ ) { corner = wxPoint( 0, -radius ); RotatePoint( &corner, delta*ii ); polyshape.Append( corner.x, corner.y ); } // Finish arc: corner = wxPoint( 0, radius ); polyshape.Append( corner.x, corner.y ); // Now, clamp the polygonal shape (too big) with the segment bounding box // the polygonal shape bbox equivalent to the segment has a too big height, // and the right width if( aCorrectionFactor > 1.0 ) { SHAPE_POLY_SET bbox; bbox.NewOutline(); // Build the bbox (a horizontal rectangle). int halfwidth = aWidth / 2; // Use the exact segment width for the bbox height corner.x = -radius - 2; // use a bbox width slightly bigger to avoid // creating useless corner at segment ends corner.y = halfwidth; bbox.Append( corner.x, corner.y ); corner.y = -halfwidth; bbox.Append( corner.x, corner.y ); corner.x = radius + seg_len + 2; bbox.Append( corner.x, corner.y ); corner.y = halfwidth; bbox.Append( corner.x, corner.y ); // Now, clamp the shape polyshape.BooleanIntersection( bbox, SHAPE_POLY_SET::PM_STRICTLY_SIMPLE ); // Note the final polygon is a simple, convex polygon with no hole // due to the shape of initial polygons } // Rotate and move the polygon to its right location polyshape.Rotate( delta_angle, VECTOR2I( 0, 0 ) ); polyshape.Move( startp ); aCornerBuffer.Append( polyshape); }
bool GERBER_DRAW_ITEM::HitTest( const wxPoint& aRefPos ) const { // In case the item has a very tiny width defined, allow it to be selected const int MIN_HIT_TEST_RADIUS = Millimeter2iu( 0.01 ); // calculate aRefPos in XY gerber axis: wxPoint ref_pos = GetXYPosition( aRefPos ); SHAPE_POLY_SET poly; switch( m_Shape ) { case GBR_POLYGON: poly = m_Polygon; return poly.Contains( VECTOR2I( ref_pos ), 0 ); case GBR_SPOT_POLY: poly = GetDcodeDescr()->m_Polygon; poly.Move( m_Start ); return poly.Contains( VECTOR2I( ref_pos ), 0 ); case GBR_SPOT_RECT: return GetBoundingBox().Contains( aRefPos ); case GBR_ARC: { double radius = GetLineLength( m_Start, m_ArcCentre ); VECTOR2D test_radius = VECTOR2D( ref_pos ) - VECTOR2D( m_ArcCentre ); int size = ( ( m_Size.x < MIN_HIT_TEST_RADIUS ) ? MIN_HIT_TEST_RADIUS : m_Size.x ); // Are we close enough to the radius? bool radius_hit = ( std::fabs( test_radius.EuclideanNorm() - radius) < size ); if( radius_hit ) { // Now check that we are within the arc angle VECTOR2D start = VECTOR2D( m_Start ) - VECTOR2D( m_ArcCentre ); VECTOR2D end = VECTOR2D( m_End ) - VECTOR2D( m_ArcCentre ); double start_angle = NormalizeAngleRadiansPos( start.Angle() ); double end_angle = NormalizeAngleRadiansPos( end.Angle() ); if( m_Start == m_End ) { start_angle = 0; end_angle = 2 * M_PI; } else if( end_angle < start_angle ) { end_angle += 2 * M_PI; } double test_angle = NormalizeAngleRadiansPos( test_radius.Angle() ); return ( test_angle > start_angle && test_angle < end_angle ); } return false; } case GBR_SPOT_MACRO: // Aperture macro polygons are already in absolute coordinates auto p = GetDcodeDescr()->GetMacro()->GetApertureMacroShape( this, m_Start ); for( int i = 0; i < p->OutlineCount(); ++i ) { if( p->Contains( VECTOR2I( aRefPos ), i ) ) return true; } return false; } // TODO: a better analyze of the shape (perhaps create a D_CODE::HitTest for flashed items) int radius = std::min( m_Size.x, m_Size.y ) >> 1; if( radius < MIN_HIT_TEST_RADIUS ) radius = MIN_HIT_TEST_RADIUS; if( m_Flashed ) return HitTestPoints( m_Start, ref_pos, radius ); else return TestSegmentHit( ref_pos, m_Start, m_End, radius ); }
void TransformRoundChamferedRectToPolygon( SHAPE_POLY_SET& aCornerBuffer, const wxPoint& aPosition, const wxSize& aSize, double aRotation, int aCornerRadius, double aChamferRatio, int aChamferCorners, int aCircleToSegmentsCount ) { // Build the basic shape in orientation 0.0, position 0,0 for chamfered corners // or in actual position/orientation for round rect only wxPoint corners[4]; GetRoundRectCornerCenters( corners, aCornerRadius, aChamferCorners ? wxPoint( 0, 0 ) : aPosition, aSize, aChamferCorners ? 0.0 : aRotation ); SHAPE_POLY_SET outline; outline.NewOutline(); for( int ii = 0; ii < 4; ++ii ) outline.Append( corners[ii].x, corners[ii].y ); outline.Inflate( aCornerRadius, aCircleToSegmentsCount ); if( aChamferCorners == RECT_NO_CHAMFER ) // no chamfer { // Add the outline: aCornerBuffer.Append( outline ); return; } // Now we have the round rect outline, in position 0,0 orientation 0.0. // Chamfer the corner(s). int chamfer_value = aChamferRatio * std::min( aSize.x, aSize.y ); SHAPE_POLY_SET chamfered_corner; // corner shape for the current corner to chamfer int corner_id[4] = { RECT_CHAMFER_TOP_LEFT, RECT_CHAMFER_TOP_RIGHT, RECT_CHAMFER_BOTTOM_LEFT, RECT_CHAMFER_BOTTOM_RIGHT }; // Depending on the corner position, signX[] and signY[] give the sign of chamfer // coordinates relative to the corner position // The first corner is the top left corner, then top right, bottom left and bottom right int signX[4] = {1, -1, 1,-1 }; int signY[4] = {1, 1, -1,-1 }; for( int ii = 0; ii < 4; ii++ ) { if( (corner_id[ii] & aChamferCorners) == 0 ) continue; VECTOR2I corner_pos( -signX[ii]*aSize.x/2, -signY[ii]*aSize.y/2 ); if( aCornerRadius ) { // We recreate a rectangular area covering the full rounded corner (max size = aSize/2) // to rebuild the corner before chamfering, to be sure the rounded corner shape does not // overlap the chamfered corner shape: chamfered_corner.RemoveAllContours(); chamfered_corner.NewOutline(); chamfered_corner.Append( 0, 0 ); chamfered_corner.Append( 0, signY[ii]*aSize.y/2 ); chamfered_corner.Append( signX[ii]*aSize.x/2, signY[ii]*aSize.y/2 ); chamfered_corner.Append( signX[ii]*aSize.x/2, 0 ); chamfered_corner.Move( corner_pos ); outline.BooleanAdd( chamfered_corner, SHAPE_POLY_SET::PM_STRICTLY_SIMPLE ); } // Now chamfer this corner chamfered_corner.RemoveAllContours(); chamfered_corner.NewOutline(); chamfered_corner.Append( 0, 0 ); chamfered_corner.Append( 0, signY[ii]*chamfer_value ); chamfered_corner.Append( signX[ii]*chamfer_value, 0 ); chamfered_corner.Move( corner_pos ); outline.BooleanSubtract( chamfered_corner, SHAPE_POLY_SET::PM_STRICTLY_SIMPLE ); } // Rotate and move the outline: if( aRotation != 0.0 ) outline.Rotate( DECIDEG2RAD( -aRotation ), VECTOR2I( 0, 0 ) ); outline.Move( VECTOR2I( aPosition ) ); // Add the outline: aCornerBuffer.Append( outline ); }