mirror of
https://gitlab.com/kicad/code/kicad.git
synced 2025-09-14 18:23:15 +02:00
Rather than skipping our endEdit() call in libedit, we need to parameterize whether we want the shape open/closed. Closed will remove the last segment if it lands on the first point. We don't want that but we do want to remove the last point if it duplicates the second to last (in the case of double-clicking) Fixes https://gitlab.com/kicad/code/kicad/issues/10334 (cherry picked from commit 55020c2b8950be14a46b438f6d080eefc040df19)
1639 lines
45 KiB
C++
1639 lines
45 KiB
C++
/*
|
|
* This program source code file is part of KiCad, a free EDA CAD application.
|
|
*
|
|
* Copyright (C) 2018 Jean-Pierre Charras, jp.charras at wanadoo.fr
|
|
* Copyright (C) 2012 SoftPLC Corporation, Dick Hollenbeck <dick@softplc.com>
|
|
* Copyright (C) 2011 Wayne Stambaugh <stambaughw@gmail.com>
|
|
* Copyright (C) 1992-2021 KiCad Developers, see AUTHORS.txt for contributors.
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 2
|
|
* of the License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, you may find one here:
|
|
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
|
|
* or you may search the http://www.gnu.org website for the version 2 license,
|
|
* or you may write to the Free Software Foundation, Inc.,
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
|
|
*/
|
|
|
|
#include <bezier_curves.h>
|
|
#include <base_units.h>
|
|
#include <convert_basic_shapes_to_polygon.h>
|
|
#include <eda_draw_frame.h>
|
|
#include <geometry/shape_simple.h>
|
|
#include <geometry/shape_segment.h>
|
|
#include <geometry/shape_circle.h>
|
|
#include <macros.h>
|
|
#include <math/util.h> // for KiROUND
|
|
#include <eda_shape.h>
|
|
#include <plotters/plotter.h>
|
|
|
|
|
|
EDA_SHAPE::EDA_SHAPE( SHAPE_T aType, int aLineWidth, FILL_T aFill, bool eeWinding ) :
|
|
m_endsSwapped( false ),
|
|
m_shape( aType ),
|
|
m_stroke( aLineWidth, PLOT_DASH_TYPE::DEFAULT, COLOR4D::UNSPECIFIED ),
|
|
m_fill( aFill ),
|
|
m_fillColor( COLOR4D::UNSPECIFIED ),
|
|
m_editState( 0 ),
|
|
m_eeWinding( eeWinding )
|
|
{
|
|
}
|
|
|
|
|
|
EDA_SHAPE::~EDA_SHAPE()
|
|
{
|
|
}
|
|
|
|
|
|
wxString EDA_SHAPE::ShowShape() const
|
|
{
|
|
switch( m_shape )
|
|
{
|
|
case SHAPE_T::SEGMENT: return _( "Line" );
|
|
case SHAPE_T::RECT: return _( "Rect" );
|
|
case SHAPE_T::ARC: return _( "Arc" );
|
|
case SHAPE_T::CIRCLE: return _( "Circle" );
|
|
case SHAPE_T::BEZIER: return _( "Bezier Curve" );
|
|
case SHAPE_T::POLY: return _( "Polygon" );
|
|
default: return wxT( "??" );
|
|
}
|
|
}
|
|
|
|
|
|
wxString EDA_SHAPE::SHAPE_T_asString() const
|
|
{
|
|
switch( m_shape )
|
|
{
|
|
case SHAPE_T::SEGMENT: return "S_SEGMENT";
|
|
case SHAPE_T::RECT: return "S_RECT";
|
|
case SHAPE_T::ARC: return "S_ARC";
|
|
case SHAPE_T::CIRCLE: return "S_CIRCLE";
|
|
case SHAPE_T::POLY: return "S_POLYGON";
|
|
case SHAPE_T::BEZIER: return "S_CURVE";
|
|
case SHAPE_T::LAST: return "!S_LAST!"; // Synthetic value, but if we come across it then
|
|
// we're going to want to know.
|
|
}
|
|
|
|
return wxEmptyString; // Just to quiet GCC.
|
|
}
|
|
|
|
|
|
void EDA_SHAPE::setPosition( const VECTOR2I& aPos )
|
|
{
|
|
move( aPos - getPosition() );
|
|
}
|
|
|
|
|
|
VECTOR2I EDA_SHAPE::getPosition() const
|
|
{
|
|
if( m_shape == SHAPE_T::ARC )
|
|
return getCenter();
|
|
else if( m_shape == SHAPE_T::POLY )
|
|
return m_poly.CVertex( 0 );
|
|
else
|
|
return m_start;
|
|
}
|
|
|
|
|
|
double EDA_SHAPE::GetLength() const
|
|
{
|
|
double length = 0.0;
|
|
|
|
switch( m_shape )
|
|
{
|
|
case SHAPE_T::BEZIER:
|
|
for( size_t ii = 1; ii < m_bezierPoints.size(); ++ii )
|
|
length += GetLineLength( m_bezierPoints[ ii - 1], m_bezierPoints[ii] );
|
|
|
|
return length;
|
|
|
|
case SHAPE_T::SEGMENT:
|
|
return GetLineLength( GetStart(), GetEnd() );
|
|
|
|
case SHAPE_T::POLY:
|
|
for( int ii = 0; ii < m_poly.COutline( 0 ).SegmentCount(); ii++ )
|
|
length += m_poly.COutline( 0 ).CSegment( ii ).Length();
|
|
|
|
return length;
|
|
|
|
case SHAPE_T::ARC:
|
|
return 2 * M_PI * GetRadius() * ( GetArcAngle() / 3600.0 );
|
|
|
|
default:
|
|
UNIMPLEMENTED_FOR( SHAPE_T_asString() );
|
|
return 0.0;
|
|
}
|
|
}
|
|
|
|
|
|
void EDA_SHAPE::move( const VECTOR2I& aMoveVector )
|
|
{
|
|
switch ( m_shape )
|
|
{
|
|
case SHAPE_T::ARC:
|
|
case SHAPE_T::SEGMENT:
|
|
case SHAPE_T::RECT:
|
|
case SHAPE_T::CIRCLE:
|
|
m_start += aMoveVector;
|
|
m_end += aMoveVector;
|
|
m_arcCenter += aMoveVector;
|
|
break;
|
|
|
|
case SHAPE_T::POLY:
|
|
m_poly.Move( VECTOR2I( aMoveVector ) );
|
|
break;
|
|
|
|
case SHAPE_T::BEZIER:
|
|
m_start += aMoveVector;
|
|
m_end += aMoveVector;
|
|
m_bezierC1 += aMoveVector;
|
|
m_bezierC2 += aMoveVector;
|
|
|
|
for( VECTOR2I& pt : m_bezierPoints )
|
|
pt += aMoveVector;
|
|
|
|
break;
|
|
|
|
default:
|
|
UNIMPLEMENTED_FOR( SHAPE_T_asString() );
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
void EDA_SHAPE::scale( double aScale )
|
|
{
|
|
auto scalePt = [&]( VECTOR2I& pt )
|
|
{
|
|
pt.x = KiROUND( pt.x * aScale );
|
|
pt.y = KiROUND( pt.y * aScale );
|
|
};
|
|
|
|
switch( m_shape )
|
|
{
|
|
case SHAPE_T::ARC:
|
|
case SHAPE_T::SEGMENT:
|
|
case SHAPE_T::RECT:
|
|
scalePt( m_start );
|
|
scalePt( m_end );
|
|
scalePt( m_arcCenter );
|
|
break;
|
|
|
|
case SHAPE_T::CIRCLE: // ring or circle
|
|
scalePt( m_start );
|
|
m_end.x = m_start.x + KiROUND( GetRadius() * aScale );
|
|
m_end.y = m_start.y;
|
|
break;
|
|
|
|
case SHAPE_T::POLY: // polygon
|
|
{
|
|
std::vector<VECTOR2I> pts;
|
|
|
|
for( const VECTOR2I& pt : m_poly.Outline( 0 ).CPoints() )
|
|
{
|
|
pts.emplace_back( pt );
|
|
scalePt( pts.back() );
|
|
}
|
|
|
|
SetPolyPoints( pts );
|
|
}
|
|
break;
|
|
|
|
case SHAPE_T::BEZIER:
|
|
scalePt( m_start );
|
|
scalePt( m_end );
|
|
scalePt( m_bezierC1 );
|
|
scalePt( m_bezierC2 );
|
|
break;
|
|
|
|
default:
|
|
UNIMPLEMENTED_FOR( SHAPE_T_asString() );
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
void EDA_SHAPE::rotate( const VECTOR2I& aRotCentre, double aAngle )
|
|
{
|
|
switch( m_shape )
|
|
{
|
|
case SHAPE_T::SEGMENT:
|
|
case SHAPE_T::CIRCLE:
|
|
RotatePoint( m_start, aRotCentre, aAngle );
|
|
RotatePoint( m_end, aRotCentre, aAngle );
|
|
break;
|
|
|
|
case SHAPE_T::ARC:
|
|
RotatePoint( m_start, aRotCentre, aAngle );
|
|
RotatePoint( m_end, aRotCentre, aAngle );
|
|
RotatePoint( m_arcCenter, aRotCentre, aAngle );
|
|
break;
|
|
|
|
case SHAPE_T::RECT:
|
|
if( KiROUND( aAngle ) % 900 == 0 )
|
|
{
|
|
RotatePoint( m_start, aRotCentre, aAngle );
|
|
RotatePoint( m_end, aRotCentre, aAngle );
|
|
break;
|
|
}
|
|
|
|
// Convert non-cartesian-rotated rect to a diamond
|
|
m_shape = SHAPE_T::POLY;
|
|
m_poly.RemoveAllContours();
|
|
m_poly.NewOutline();
|
|
m_poly.Append( m_start );
|
|
m_poly.Append( m_end.x, m_start.y );
|
|
m_poly.Append( m_end );
|
|
m_poly.Append( m_start.x, m_end.y );
|
|
|
|
KI_FALLTHROUGH;
|
|
|
|
case SHAPE_T::POLY:
|
|
m_poly.Rotate( -DECIDEG2RAD( aAngle ), VECTOR2I( aRotCentre ) );
|
|
break;
|
|
|
|
case SHAPE_T::BEZIER:
|
|
RotatePoint( m_start, aRotCentre, aAngle );
|
|
RotatePoint( m_end, aRotCentre, aAngle );
|
|
RotatePoint( m_bezierC1, aRotCentre, aAngle );
|
|
RotatePoint( m_bezierC2, aRotCentre, aAngle );
|
|
|
|
for( VECTOR2I& pt : m_bezierPoints )
|
|
RotatePoint( pt, aRotCentre, aAngle);
|
|
|
|
break;
|
|
|
|
default:
|
|
UNIMPLEMENTED_FOR( SHAPE_T_asString() );
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
void EDA_SHAPE::flip( const VECTOR2I& aCentre, bool aFlipLeftRight )
|
|
{
|
|
switch ( m_shape )
|
|
{
|
|
case SHAPE_T::SEGMENT:
|
|
case SHAPE_T::RECT:
|
|
if( aFlipLeftRight )
|
|
{
|
|
m_start.x = aCentre.x - ( m_start.x - aCentre.x );
|
|
m_end.x = aCentre.x - ( m_end.x - aCentre.x );
|
|
}
|
|
else
|
|
{
|
|
m_start.y = aCentre.y - ( m_start.y - aCentre.y );
|
|
m_end.y = aCentre.y - ( m_end.y - aCentre.y );
|
|
}
|
|
|
|
std::swap( m_start, m_end );
|
|
break;
|
|
|
|
case SHAPE_T::CIRCLE:
|
|
if( aFlipLeftRight )
|
|
{
|
|
m_start.x = aCentre.x - ( m_start.x - aCentre.x );
|
|
m_end.x = aCentre.x - ( m_end.x - aCentre.x );
|
|
}
|
|
else
|
|
{
|
|
m_start.y = aCentre.y - ( m_start.y - aCentre.y );
|
|
m_end.y = aCentre.y - ( m_end.y - aCentre.y );
|
|
}
|
|
break;
|
|
|
|
case SHAPE_T::ARC:
|
|
if( aFlipLeftRight )
|
|
{
|
|
m_start.x = aCentre.x - ( m_start.x - aCentre.x );
|
|
m_end.x = aCentre.x - ( m_end.x - aCentre.x );
|
|
m_arcCenter.x = aCentre.x - ( m_arcCenter.x - aCentre.x );
|
|
}
|
|
else
|
|
{
|
|
m_start.y = aCentre.y - ( m_start.y - aCentre.y );
|
|
m_end.y = aCentre.y - ( m_end.y - aCentre.y );
|
|
m_arcCenter.y = aCentre.y - ( m_arcCenter.y - aCentre.y );
|
|
}
|
|
|
|
std::swap( m_start, m_end );
|
|
break;
|
|
|
|
case SHAPE_T::POLY:
|
|
m_poly.Mirror( aFlipLeftRight, !aFlipLeftRight, aCentre );
|
|
break;
|
|
|
|
case SHAPE_T::BEZIER:
|
|
if( aFlipLeftRight )
|
|
{
|
|
m_start.x = aCentre.x - ( m_start.x - aCentre.x );
|
|
m_end.x = aCentre.x - ( m_end.x - aCentre.x );
|
|
m_bezierC1.x = aCentre.x - ( m_bezierC1.x - aCentre.x );
|
|
m_bezierC2.x = aCentre.x - ( m_bezierC2.x - aCentre.x );
|
|
}
|
|
else
|
|
{
|
|
m_start.y = aCentre.y - ( m_start.y - aCentre.y );
|
|
m_end.y = aCentre.y - ( m_end.y - aCentre.y );
|
|
m_bezierC1.y = aCentre.y - ( m_bezierC1.y - aCentre.y );
|
|
m_bezierC2.y = aCentre.y - ( m_bezierC2.y - aCentre.y );
|
|
}
|
|
|
|
// Rebuild the poly points shape
|
|
{
|
|
std::vector<VECTOR2I> ctrlPoints = { m_start, m_bezierC1, m_bezierC2, m_end };
|
|
BEZIER_POLY converter( ctrlPoints );
|
|
converter.GetPoly( m_bezierPoints, m_stroke.GetWidth() );
|
|
}
|
|
break;
|
|
|
|
default:
|
|
UNIMPLEMENTED_FOR( SHAPE_T_asString() );
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
void EDA_SHAPE::RebuildBezierToSegmentsPointsList( int aMinSegLen )
|
|
{
|
|
// Has meaning only for S_CURVE DRAW_SEGMENT shape
|
|
if( m_shape != SHAPE_T::BEZIER )
|
|
{
|
|
m_bezierPoints.clear();
|
|
return;
|
|
}
|
|
|
|
// Rebuild the m_BezierPoints vertex list that approximate the Bezier curve
|
|
m_bezierPoints = buildBezierToSegmentsPointsList( aMinSegLen );
|
|
}
|
|
|
|
|
|
const std::vector<VECTOR2I> EDA_SHAPE::buildBezierToSegmentsPointsList( int aMinSegLen ) const
|
|
{
|
|
std::vector<VECTOR2I> bezierPoints;
|
|
|
|
// Rebuild the m_BezierPoints vertex list that approximate the Bezier curve
|
|
std::vector<VECTOR2I> ctrlPoints = { m_start, m_bezierC1, m_bezierC2, m_end };
|
|
BEZIER_POLY converter( ctrlPoints );
|
|
converter.GetPoly( bezierPoints, aMinSegLen );
|
|
|
|
return bezierPoints;
|
|
}
|
|
|
|
|
|
VECTOR2I EDA_SHAPE::getCenter() const
|
|
{
|
|
switch( m_shape )
|
|
{
|
|
case SHAPE_T::ARC:
|
|
return m_arcCenter;
|
|
|
|
case SHAPE_T::CIRCLE:
|
|
return m_start;
|
|
|
|
case SHAPE_T::SEGMENT:
|
|
// Midpoint of the line
|
|
return ( m_start + m_end ) / 2;
|
|
|
|
case SHAPE_T::POLY:
|
|
case SHAPE_T::RECT:
|
|
case SHAPE_T::BEZIER:
|
|
return getBoundingBox().Centre();
|
|
|
|
default:
|
|
UNIMPLEMENTED_FOR( SHAPE_T_asString() );
|
|
return VECTOR2I();
|
|
}
|
|
}
|
|
|
|
|
|
void EDA_SHAPE::SetCenter( const VECTOR2I& aCenter )
|
|
{
|
|
switch( m_shape )
|
|
{
|
|
case SHAPE_T::ARC:
|
|
m_arcCenter = aCenter;
|
|
break;
|
|
|
|
case SHAPE_T::CIRCLE:
|
|
m_start = aCenter;
|
|
break;
|
|
|
|
default:
|
|
UNIMPLEMENTED_FOR( SHAPE_T_asString() );
|
|
}
|
|
}
|
|
|
|
|
|
VECTOR2I EDA_SHAPE::GetArcMid() const
|
|
{
|
|
VECTOR2I mid = m_start;
|
|
RotatePoint( mid, m_arcCenter, -GetArcAngle() / 2.0 );
|
|
return mid;
|
|
}
|
|
|
|
|
|
void EDA_SHAPE::CalcArcAngles( double& aStartAngle, double& aEndAngle ) const
|
|
{
|
|
VECTOR2D startRadial( GetStart() - getCenter() );
|
|
VECTOR2D endRadial( GetEnd() - getCenter() );
|
|
|
|
aStartAngle = 180.0 / M_PI * atan2( startRadial.y, startRadial.x );
|
|
aEndAngle = 180.0 / M_PI * atan2( endRadial.y, endRadial.x );
|
|
|
|
if( aEndAngle == aStartAngle )
|
|
aEndAngle = aStartAngle + 360.0; // ring, not null
|
|
|
|
if( aStartAngle > aEndAngle )
|
|
{
|
|
if( aEndAngle < 0 )
|
|
aEndAngle = NormalizeAngleDegrees( aEndAngle, 0.0, 360.0 );
|
|
else
|
|
aStartAngle = NormalizeAngleDegrees( aStartAngle, -360.0, 0.0 );
|
|
}
|
|
}
|
|
|
|
|
|
int EDA_SHAPE::GetRadius() const
|
|
{
|
|
double radius = 0.0;
|
|
|
|
switch( m_shape )
|
|
{
|
|
case SHAPE_T::ARC:
|
|
radius = GetLineLength( m_arcCenter, m_start );
|
|
break;
|
|
|
|
case SHAPE_T::CIRCLE:
|
|
radius = GetLineLength( m_start, m_end );
|
|
break;
|
|
|
|
default:
|
|
UNIMPLEMENTED_FOR( SHAPE_T_asString() );
|
|
}
|
|
|
|
// don't allow degenerate circles/arcs
|
|
return std::max( 1, KiROUND( radius ) );
|
|
}
|
|
|
|
|
|
void EDA_SHAPE::SetArcGeometry( const VECTOR2I& aStart, const VECTOR2I& aMid, const VECTOR2I& aEnd )
|
|
{
|
|
m_start = aStart;
|
|
m_end = aEnd;
|
|
m_arcCenter = CalcArcCenter( aStart, aMid, aEnd );
|
|
m_endsSwapped = false;
|
|
|
|
/**
|
|
* If the input winding doesn't match our internal winding, the calculated midpoint will end up
|
|
* on the other side of the arc. In this case, we need to flip the start/end points and flag this
|
|
* change for the system
|
|
*/
|
|
VECTOR2I new_mid = GetArcMid();
|
|
VECTOR2D dist( new_mid - aMid );
|
|
VECTOR2D dist2( new_mid - m_arcCenter );
|
|
|
|
if( dist.SquaredEuclideanNorm() > dist2.SquaredEuclideanNorm() )
|
|
{
|
|
std::swap( m_start, m_end );
|
|
m_endsSwapped = true;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
double EDA_SHAPE::GetArcAngle() const
|
|
{
|
|
double startAngle;
|
|
double endAngle;
|
|
|
|
CalcArcAngles( startAngle, endAngle );
|
|
|
|
return ( endAngle - startAngle ) * 10;
|
|
}
|
|
|
|
|
|
void EDA_SHAPE::SetArcAngleAndEnd( double aAngle, bool aCheckNegativeAngle )
|
|
{
|
|
m_end = m_start;
|
|
RotatePoint( m_end, m_arcCenter, -NormalizeAngle360Max( aAngle ) );
|
|
|
|
if( aCheckNegativeAngle && aAngle < 0 )
|
|
{
|
|
std::swap( m_start, m_end );
|
|
m_endsSwapped = true;
|
|
}
|
|
}
|
|
|
|
|
|
void EDA_SHAPE::ShapeGetMsgPanelInfo( EDA_DRAW_FRAME* aFrame, std::vector<MSG_PANEL_ITEM>& aList )
|
|
{
|
|
EDA_UNITS units = aFrame->GetUserUnits();
|
|
ORIGIN_TRANSFORMS originTransforms = aFrame->GetOriginTransforms();
|
|
wxString msg;
|
|
|
|
wxString shape = _( "Shape" );
|
|
|
|
switch( m_shape )
|
|
{
|
|
case SHAPE_T::CIRCLE:
|
|
aList.emplace_back( shape, _( "Circle" ) );
|
|
|
|
msg = MessageTextFromValue( units, GetRadius() );
|
|
aList.emplace_back( _( "Radius" ), msg );
|
|
break;
|
|
|
|
case SHAPE_T::ARC:
|
|
aList.emplace_back( shape, _( "Arc" ) );
|
|
|
|
msg.Printf( wxT( "%.1f" ), GetArcAngle() / 10.0 );
|
|
aList.emplace_back( _( "Angle" ), msg );
|
|
|
|
msg = MessageTextFromValue( units, GetRadius() );
|
|
aList.emplace_back( _( "Radius" ), msg );
|
|
break;
|
|
|
|
case SHAPE_T::BEZIER:
|
|
aList.emplace_back( shape, _( "Curve" ) );
|
|
|
|
msg = MessageTextFromValue( units, GetLength() );
|
|
aList.emplace_back( _( "Length" ), msg );
|
|
break;
|
|
|
|
case SHAPE_T::POLY:
|
|
aList.emplace_back( shape, _( "Polygon" ) );
|
|
|
|
msg.Printf( "%d", GetPolyShape().Outline(0).PointCount() );
|
|
aList.emplace_back( _( "Points" ), msg );
|
|
break;
|
|
|
|
case SHAPE_T::RECT:
|
|
aList.emplace_back( shape, _( "Rectangle" ) );
|
|
|
|
msg = MessageTextFromValue( units, std::abs( GetEnd().x - GetStart().x ) );
|
|
aList.emplace_back( _( "Width" ), msg );
|
|
|
|
msg = MessageTextFromValue( units, std::abs( GetEnd().y - GetStart().y ) );
|
|
aList.emplace_back( _( "Height" ), msg );
|
|
break;
|
|
|
|
case SHAPE_T::SEGMENT:
|
|
{
|
|
aList.emplace_back( shape, _( "Segment" ) );
|
|
|
|
msg = MessageTextFromValue( units, GetLineLength( GetStart(), GetEnd() ) );
|
|
aList.emplace_back( _( "Length" ), msg );
|
|
|
|
// angle counter-clockwise from 3'o-clock
|
|
const double deg = RAD2DEG( atan2( (double)( GetStart().y - GetEnd().y ),
|
|
(double)( GetEnd().x - GetStart().x ) ) );
|
|
aList.emplace_back( _( "Angle" ), wxString::Format( "%.1f", deg ) );
|
|
}
|
|
break;
|
|
|
|
default:
|
|
aList.emplace_back( shape, _( "Unrecognized" ) );
|
|
break;
|
|
}
|
|
|
|
aList.emplace_back( _( "Line width" ), MessageTextFromValue( units, GetWidth() ) );
|
|
}
|
|
|
|
|
|
const EDA_RECT EDA_SHAPE::getBoundingBox() const
|
|
{
|
|
EDA_RECT bbox;
|
|
|
|
switch( m_shape )
|
|
{
|
|
case SHAPE_T::RECT:
|
|
for( VECTOR2I& pt : GetRectCorners() )
|
|
bbox.Merge( pt );
|
|
|
|
break;
|
|
|
|
case SHAPE_T::SEGMENT:
|
|
bbox.SetOrigin( GetStart() );
|
|
bbox.SetEnd( GetEnd() );
|
|
break;
|
|
|
|
case SHAPE_T::CIRCLE:
|
|
bbox.SetOrigin( GetStart() );
|
|
bbox.Inflate( GetRadius() );
|
|
break;
|
|
|
|
case SHAPE_T::ARC:
|
|
computeArcBBox( bbox );
|
|
break;
|
|
|
|
case SHAPE_T::POLY:
|
|
if( m_poly.IsEmpty() )
|
|
break;
|
|
|
|
for( auto iter = m_poly.CIterate(); iter; iter++ )
|
|
{
|
|
VECTOR2I pt( iter->x, iter->y );
|
|
|
|
RotatePoint( pt, getParentOrientation() );
|
|
pt += getParentPosition();
|
|
|
|
bbox.Merge( pt );
|
|
}
|
|
|
|
break;
|
|
|
|
case SHAPE_T::BEZIER:
|
|
bbox.SetOrigin( GetStart() );
|
|
bbox.Merge( GetBezierC1() );
|
|
bbox.Merge( GetBezierC2() );
|
|
bbox.Merge( GetEnd() );
|
|
break;
|
|
|
|
default:
|
|
UNIMPLEMENTED_FOR( SHAPE_T_asString() );
|
|
break;
|
|
}
|
|
|
|
bbox.Inflate( std::max( 0, GetWidth() ) / 2 );
|
|
bbox.Normalize();
|
|
|
|
return bbox;
|
|
}
|
|
|
|
|
|
bool EDA_SHAPE::hitTest( const VECTOR2I& aPosition, int aAccuracy ) const
|
|
{
|
|
int maxdist = aAccuracy;
|
|
|
|
if( GetWidth() > 0 )
|
|
maxdist += GetWidth() / 2;
|
|
|
|
switch( m_shape )
|
|
{
|
|
case SHAPE_T::CIRCLE:
|
|
{
|
|
int radius = GetRadius();
|
|
int dist = KiROUND( EuclideanNorm( aPosition - getCenter() ) );
|
|
|
|
if( IsFilled() )
|
|
return dist <= radius + maxdist; // Filled circle hit-test
|
|
else
|
|
return abs( radius - dist ) <= maxdist; // Ring hit-test
|
|
}
|
|
|
|
case SHAPE_T::ARC:
|
|
{
|
|
if( EuclideanNorm( aPosition - m_start ) <= maxdist )
|
|
return true;
|
|
|
|
if( EuclideanNorm( aPosition - m_end ) <= maxdist )
|
|
return true;
|
|
|
|
VECTOR2I relPos = aPosition - getCenter();
|
|
int radius = GetRadius();
|
|
int dist = KiROUND( EuclideanNorm( relPos ) );
|
|
|
|
if( abs( radius - dist ) <= maxdist )
|
|
{
|
|
double startAngle;
|
|
double endAngle;
|
|
CalcArcAngles( startAngle, endAngle );
|
|
|
|
if( m_eeWinding && NormalizeAngleDegrees( startAngle - endAngle, -180.0, 180.0 ) > 0 )
|
|
std::swap( startAngle, endAngle );
|
|
|
|
double relPosAngle = 180.0 / M_PI * atan2( relPos.y, relPos.x );
|
|
|
|
startAngle = NormalizeAngleDegrees( startAngle, 0.0, 360.0 );
|
|
endAngle = NormalizeAngleDegrees( endAngle, 0.0, 360.0 );
|
|
relPosAngle = NormalizeAngleDegrees( relPosAngle, 0.0, 360.0 );
|
|
|
|
if( endAngle > startAngle )
|
|
return relPosAngle >= startAngle && relPosAngle <= endAngle;
|
|
else
|
|
return relPosAngle >= startAngle || relPosAngle <= endAngle;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
case SHAPE_T::BEZIER:
|
|
const_cast<EDA_SHAPE*>( this )->RebuildBezierToSegmentsPointsList( GetWidth() );
|
|
|
|
for( unsigned int i= 1; i < m_bezierPoints.size(); i++)
|
|
{
|
|
if( TestSegmentHit( aPosition, m_bezierPoints[ i - 1], m_bezierPoints[i], maxdist ) )
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
|
|
case SHAPE_T::SEGMENT:
|
|
return TestSegmentHit( aPosition, GetStart(), GetEnd(), maxdist );
|
|
|
|
case SHAPE_T::RECT:
|
|
if( IsFilled() ) // Filled rect hit-test
|
|
{
|
|
SHAPE_POLY_SET poly;
|
|
poly.NewOutline();
|
|
|
|
for( const VECTOR2I& pt : GetRectCorners() )
|
|
poly.Append( pt );
|
|
|
|
return poly.Collide( VECTOR2I( aPosition ), maxdist );
|
|
}
|
|
else // Open rect hit-test
|
|
{
|
|
std::vector<VECTOR2I> pts = GetRectCorners();
|
|
|
|
return TestSegmentHit( aPosition, pts[0], pts[1], maxdist )
|
|
|| TestSegmentHit( aPosition, pts[1], pts[2], maxdist )
|
|
|| TestSegmentHit( aPosition, pts[2], pts[3], maxdist )
|
|
|| TestSegmentHit( aPosition, pts[3], pts[0], maxdist );
|
|
}
|
|
|
|
case SHAPE_T::POLY:
|
|
if( IsFilled() )
|
|
{
|
|
return m_poly.Collide( VECTOR2I( aPosition ), maxdist );
|
|
}
|
|
else
|
|
{
|
|
SHAPE_POLY_SET::VERTEX_INDEX dummy;
|
|
return m_poly.CollideEdge( VECTOR2I( aPosition ), dummy, maxdist );
|
|
}
|
|
|
|
default:
|
|
UNIMPLEMENTED_FOR( SHAPE_T_asString() );
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
bool EDA_SHAPE::hitTest( const EDA_RECT& aRect, bool aContained, int aAccuracy ) const
|
|
{
|
|
EDA_RECT arect = aRect;
|
|
arect.Normalize();
|
|
arect.Inflate( aAccuracy );
|
|
|
|
EDA_RECT arcRect;
|
|
EDA_RECT bb = getBoundingBox();
|
|
|
|
switch( m_shape )
|
|
{
|
|
case SHAPE_T::CIRCLE:
|
|
// Test if area intersects or contains the circle:
|
|
if( aContained )
|
|
{
|
|
return arect.Contains( bb );
|
|
}
|
|
else
|
|
{
|
|
// If the rectangle does not intersect the bounding box, this is a much quicker test
|
|
if( !aRect.Intersects( bb ) )
|
|
{
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
return arect.IntersectsCircleEdge( getCenter(), GetRadius(), GetWidth() );
|
|
}
|
|
}
|
|
|
|
case SHAPE_T::ARC:
|
|
// Test for full containment of this arc in the rect
|
|
if( aContained )
|
|
{
|
|
return arect.Contains( bb );
|
|
}
|
|
// Test if the rect crosses the arc
|
|
else
|
|
{
|
|
arcRect = bb.Common( arect );
|
|
|
|
/* All following tests must pass:
|
|
* 1. Rectangle must intersect arc BoundingBox
|
|
* 2. Rectangle must cross the outside of the arc
|
|
*/
|
|
return arcRect.Intersects( arect ) &&
|
|
arcRect.IntersectsCircleEdge( getCenter(), GetRadius(), GetWidth() );
|
|
}
|
|
|
|
case SHAPE_T::RECT:
|
|
if( aContained )
|
|
{
|
|
return arect.Contains( bb );
|
|
}
|
|
else
|
|
{
|
|
std::vector<VECTOR2I> pts = GetRectCorners();
|
|
|
|
// Account for the width of the lines
|
|
arect.Inflate( GetWidth() / 2 );
|
|
return ( arect.Intersects( pts[0], pts[1] )
|
|
|| arect.Intersects( pts[1], pts[2] )
|
|
|| arect.Intersects( pts[2], pts[3] )
|
|
|| arect.Intersects( pts[3], pts[0] ) );
|
|
}
|
|
|
|
case SHAPE_T::SEGMENT:
|
|
if( aContained )
|
|
{
|
|
return arect.Contains( GetStart() ) && aRect.Contains( GetEnd() );
|
|
}
|
|
else
|
|
{
|
|
// Account for the width of the line
|
|
arect.Inflate( GetWidth() / 2 );
|
|
return arect.Intersects( GetStart(), GetEnd() );
|
|
}
|
|
|
|
case SHAPE_T::POLY:
|
|
if( aContained )
|
|
{
|
|
return arect.Contains( bb );
|
|
}
|
|
else
|
|
{
|
|
// Fast test: if aRect is outside the polygon bounding box,
|
|
// rectangles cannot intersect
|
|
if( !arect.Intersects( bb ) )
|
|
return false;
|
|
|
|
// Account for the width of the line
|
|
arect.Inflate( GetWidth() / 2 );
|
|
|
|
// Polygons in footprints use coordinates relative to the footprint.
|
|
// Therefore, instead of using m_poly, we make a copy which is translated
|
|
// to the actual location in the board.
|
|
double orientation = 0.0;
|
|
VECTOR2I offset = getParentPosition();
|
|
|
|
if( getParentOrientation() )
|
|
orientation = -DECIDEG2RAD( getParentOrientation() );
|
|
|
|
SHAPE_LINE_CHAIN poly = m_poly.Outline( 0 );
|
|
poly.Rotate( orientation );
|
|
poly.Move( offset );
|
|
|
|
int count = poly.GetPointCount();
|
|
|
|
for( int ii = 0; ii < count; ii++ )
|
|
{
|
|
VECTOR2I vertex = poly.GetPoint( ii );
|
|
|
|
// Test if the point is within aRect
|
|
if( arect.Contains( vertex ) )
|
|
return true;
|
|
|
|
if( ii + 1 < count )
|
|
{
|
|
VECTOR2I vertexNext = poly.GetPoint( ii + 1 );
|
|
|
|
// Test if this edge intersects aRect
|
|
if( arect.Intersects( vertex, vertexNext ) )
|
|
return true;
|
|
}
|
|
else if( poly.IsClosed() )
|
|
{
|
|
VECTOR2I vertexNext = poly.GetPoint( 0 );
|
|
|
|
// Test if this edge intersects aRect
|
|
if( arect.Intersects( vertex, vertexNext ) )
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
case SHAPE_T::BEZIER:
|
|
if( aContained )
|
|
{
|
|
return arect.Contains( bb );
|
|
}
|
|
else
|
|
{
|
|
// Fast test: if aRect is outside the polygon bounding box,
|
|
// rectangles cannot intersect
|
|
if( !arect.Intersects( bb ) )
|
|
return false;
|
|
|
|
// Account for the width of the line
|
|
arect.Inflate( GetWidth() / 2 );
|
|
unsigned count = m_bezierPoints.size();
|
|
|
|
for( unsigned ii = 1; ii < count; ii++ )
|
|
{
|
|
VECTOR2I vertex = m_bezierPoints[ii - 1];
|
|
VECTOR2I vertexNext = m_bezierPoints[ii];
|
|
|
|
// Test if the point is within aRect
|
|
if( arect.Contains( vertex ) )
|
|
return true;
|
|
|
|
// Test if this edge intersects aRect
|
|
if( arect.Intersects( vertex, vertexNext ) )
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
default:
|
|
UNIMPLEMENTED_FOR( SHAPE_T_asString() );
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
std::vector<VECTOR2I> EDA_SHAPE::GetRectCorners() const
|
|
{
|
|
std::vector<VECTOR2I> pts;
|
|
VECTOR2I topLeft = GetStart();
|
|
VECTOR2I botRight = GetEnd();
|
|
|
|
// Un-rotate rect topLeft and botRight
|
|
if( KiROUND( getParentOrientation() ) % 900 != 0 )
|
|
{
|
|
topLeft -= getParentPosition();
|
|
RotatePoint( topLeft, -getParentOrientation() );
|
|
|
|
botRight -= getParentPosition();
|
|
RotatePoint( botRight, -getParentOrientation() );
|
|
}
|
|
|
|
// Set up the un-rotated 4 corners
|
|
pts.emplace_back( topLeft );
|
|
pts.emplace_back( botRight.x, topLeft.y );
|
|
pts.emplace_back( botRight );
|
|
pts.emplace_back( topLeft.x, botRight.y );
|
|
|
|
// Now re-rotate the 4 corners to get a diamond
|
|
if( KiROUND( getParentOrientation() ) % 900 != 0 )
|
|
{
|
|
for( VECTOR2I& pt : pts )
|
|
{
|
|
RotatePoint( pt, getParentOrientation() );
|
|
pt += getParentPosition();
|
|
}
|
|
}
|
|
|
|
return pts;
|
|
}
|
|
|
|
|
|
void EDA_SHAPE::computeArcBBox( EDA_RECT& aBBox ) const
|
|
{
|
|
VECTOR2I start = m_start;
|
|
VECTOR2I end = m_end;
|
|
double t1, t2;
|
|
|
|
CalcArcAngles( t1, t2 );
|
|
|
|
if( m_eeWinding && NormalizeAngleDegrees( t1 - t2, -180.0, 180.0 ) > 0 )
|
|
std::swap( start, end );
|
|
|
|
// Do not include the center, which is not necessarily inside the BB of an arc with a small
|
|
// included angle
|
|
aBBox.SetOrigin( start );
|
|
aBBox.Merge( end );
|
|
|
|
// Determine the starting quarter
|
|
// 0 right-bottom
|
|
// 1 left-bottom
|
|
// 2 left-top
|
|
// 3 right-top
|
|
unsigned int quarter;
|
|
|
|
if( start.x < m_arcCenter.x )
|
|
{
|
|
if( start.y <= m_arcCenter.y )
|
|
quarter = 2;
|
|
else
|
|
quarter = 1;
|
|
}
|
|
else if( start.x == m_arcCenter.x )
|
|
{
|
|
if( start.y < m_arcCenter.y )
|
|
quarter = 3;
|
|
else
|
|
quarter = 1;
|
|
}
|
|
else
|
|
{
|
|
if( start.y < m_arcCenter.y )
|
|
quarter = 3;
|
|
else
|
|
quarter = 0;
|
|
}
|
|
|
|
int radius = GetRadius();
|
|
VECTOR2I startRadial = start - m_arcCenter;
|
|
VECTOR2I endRadial = end - m_arcCenter;
|
|
double angleStart = ArcTangente( startRadial.y, startRadial.x );
|
|
double arcAngle = RAD2DECIDEG( endRadial.Angle() - startRadial.Angle() );
|
|
int angle = (int) NormalizeAnglePos( angleStart ) % 900 + NormalizeAnglePos( arcAngle );
|
|
|
|
while( angle > 900 )
|
|
{
|
|
switch( quarter )
|
|
{
|
|
case 0: aBBox.Merge( VECTOR2I( m_arcCenter.x, m_arcCenter.y + radius ) ); break; // down
|
|
case 1: aBBox.Merge( VECTOR2I( m_arcCenter.x - radius, m_arcCenter.y ) ); break; // left
|
|
case 2: aBBox.Merge( VECTOR2I( m_arcCenter.x, m_arcCenter.y - radius ) ); break; // up
|
|
case 3: aBBox.Merge( VECTOR2I( m_arcCenter.x + radius, m_arcCenter.y ) ); break; // right
|
|
}
|
|
|
|
++quarter %= 4;
|
|
angle -= 900;
|
|
}
|
|
}
|
|
|
|
|
|
void EDA_SHAPE::SetPolyPoints( const std::vector<VECTOR2I>& aPoints )
|
|
{
|
|
m_poly.RemoveAllContours();
|
|
m_poly.NewOutline();
|
|
|
|
for( const VECTOR2I& p : aPoints )
|
|
m_poly.Append( p.x, p.y );
|
|
}
|
|
|
|
|
|
std::vector<SHAPE*> EDA_SHAPE::MakeEffectiveShapes( bool aEdgeOnly ) const
|
|
{
|
|
std::vector<SHAPE*> effectiveShapes;
|
|
|
|
switch( m_shape )
|
|
{
|
|
case SHAPE_T::ARC:
|
|
effectiveShapes.emplace_back( new SHAPE_ARC( m_arcCenter, m_start, GetArcAngle() / 10.0,
|
|
GetWidth() ) );
|
|
break;
|
|
|
|
case SHAPE_T::SEGMENT:
|
|
effectiveShapes.emplace_back( new SHAPE_SEGMENT( m_start, m_end, GetWidth() ) );
|
|
break;
|
|
|
|
case SHAPE_T::RECT:
|
|
{
|
|
std::vector<VECTOR2I> pts = GetRectCorners();
|
|
|
|
if( IsFilled() && !aEdgeOnly )
|
|
effectiveShapes.emplace_back( new SHAPE_SIMPLE( pts ) );
|
|
|
|
if( GetWidth() > 0 || !IsFilled() || aEdgeOnly )
|
|
{
|
|
effectiveShapes.emplace_back( new SHAPE_SEGMENT( pts[0], pts[1], GetWidth() ) );
|
|
effectiveShapes.emplace_back( new SHAPE_SEGMENT( pts[1], pts[2], GetWidth() ) );
|
|
effectiveShapes.emplace_back( new SHAPE_SEGMENT( pts[2], pts[3], GetWidth() ) );
|
|
effectiveShapes.emplace_back( new SHAPE_SEGMENT( pts[3], pts[0], GetWidth() ) );
|
|
}
|
|
}
|
|
break;
|
|
|
|
case SHAPE_T::CIRCLE:
|
|
{
|
|
if( IsFilled() && !aEdgeOnly )
|
|
effectiveShapes.emplace_back( new SHAPE_CIRCLE( getCenter(), GetRadius() ) );
|
|
|
|
if( GetWidth() > 0 || !IsFilled() || aEdgeOnly )
|
|
effectiveShapes.emplace_back( new SHAPE_ARC( getCenter(), GetEnd(), 360.0 ) );
|
|
|
|
break;
|
|
}
|
|
|
|
case SHAPE_T::BEZIER:
|
|
{
|
|
std::vector<VECTOR2I> bezierPoints = buildBezierToSegmentsPointsList( GetWidth() );
|
|
VECTOR2I start_pt = bezierPoints[0];
|
|
|
|
for( unsigned int jj = 1; jj < bezierPoints.size(); jj++ )
|
|
{
|
|
VECTOR2I end_pt = bezierPoints[jj];
|
|
effectiveShapes.emplace_back( new SHAPE_SEGMENT( start_pt, end_pt, GetWidth() ) );
|
|
start_pt = end_pt;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case SHAPE_T::POLY:
|
|
{
|
|
SHAPE_LINE_CHAIN l = GetPolyShape().COutline( 0 );
|
|
|
|
l.Rotate( -DECIDEG2RAD( getParentOrientation() ) );
|
|
l.Move( getParentPosition() );
|
|
|
|
if( IsFilled() && !aEdgeOnly )
|
|
effectiveShapes.emplace_back( new SHAPE_SIMPLE( l ) );
|
|
|
|
if( GetWidth() > 0 || !IsFilled() || aEdgeOnly )
|
|
{
|
|
for( int i = 0; i < l.SegmentCount(); i++ )
|
|
effectiveShapes.emplace_back( new SHAPE_SEGMENT( l.Segment( i ), GetWidth() ) );
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
UNIMPLEMENTED_FOR( SHAPE_T_asString() );
|
|
break;
|
|
}
|
|
|
|
return effectiveShapes;
|
|
}
|
|
|
|
|
|
void EDA_SHAPE::DupPolyPointsList( std::vector<VECTOR2I>& aBuffer ) const
|
|
{
|
|
if( m_poly.OutlineCount() )
|
|
{
|
|
int pointCount = m_poly.COutline( 0 ).PointCount();
|
|
|
|
if( pointCount )
|
|
{
|
|
aBuffer.reserve( pointCount );
|
|
|
|
for ( auto iter = m_poly.CIterate(); iter; iter++ )
|
|
aBuffer.emplace_back( iter->x, iter->y );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
bool EDA_SHAPE::IsPolyShapeValid() const
|
|
{
|
|
// return true if the polygonal shape is valid (has more than 2 points)
|
|
if( GetPolyShape().OutlineCount() == 0 )
|
|
return false;
|
|
|
|
const SHAPE_LINE_CHAIN& outline = ( (SHAPE_POLY_SET&)GetPolyShape() ).Outline( 0 );
|
|
|
|
return outline.PointCount() > 2;
|
|
}
|
|
|
|
|
|
int EDA_SHAPE::GetPointCount() const
|
|
{
|
|
// return the number of corners of the polygonal shape
|
|
// this shape is expected to be only one polygon without hole
|
|
if( GetPolyShape().OutlineCount() )
|
|
return GetPolyShape().VertexCount( 0 );
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
void EDA_SHAPE::beginEdit( const VECTOR2I& aPosition )
|
|
{
|
|
switch( GetShape() )
|
|
{
|
|
case SHAPE_T::SEGMENT:
|
|
case SHAPE_T::CIRCLE:
|
|
case SHAPE_T::RECT:
|
|
SetStart( aPosition );
|
|
SetEnd( aPosition );
|
|
break;
|
|
|
|
case SHAPE_T::ARC:
|
|
SetArcGeometry( aPosition, aPosition, aPosition );
|
|
m_editState = 1;
|
|
break;
|
|
|
|
case SHAPE_T::POLY:
|
|
m_poly.NewOutline();
|
|
m_poly.Outline( 0 ).SetClosed( false );
|
|
|
|
// Start and end of the first segment (co-located for now)
|
|
m_poly.Outline( 0 ).Append( aPosition );
|
|
m_poly.Outline( 0 ).Append( aPosition, true );
|
|
break;
|
|
|
|
default:
|
|
UNIMPLEMENTED_FOR( SHAPE_T_asString() );
|
|
}
|
|
}
|
|
|
|
|
|
bool EDA_SHAPE::continueEdit( const VECTOR2I& aPosition )
|
|
{
|
|
switch( GetShape() )
|
|
{
|
|
case SHAPE_T::ARC:
|
|
case SHAPE_T::SEGMENT:
|
|
case SHAPE_T::CIRCLE:
|
|
case SHAPE_T::RECT:
|
|
return false;
|
|
|
|
case SHAPE_T::POLY:
|
|
{
|
|
SHAPE_LINE_CHAIN& poly = m_poly.Outline( 0 );
|
|
|
|
// do not add zero-length segments
|
|
if( poly.CPoint( poly.GetPointCount() - 2 ) != poly.CLastPoint() )
|
|
poly.Append( aPosition, true );
|
|
}
|
|
return true;
|
|
|
|
default:
|
|
UNIMPLEMENTED_FOR( SHAPE_T_asString() );
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
void EDA_SHAPE::calcEdit( const VECTOR2I& aPosition )
|
|
{
|
|
#define sq( x ) pow( x, 2 )
|
|
|
|
switch( GetShape() )
|
|
{
|
|
case SHAPE_T::SEGMENT:
|
|
case SHAPE_T::CIRCLE:
|
|
case SHAPE_T::RECT:
|
|
SetEnd( aPosition );
|
|
break;
|
|
|
|
case SHAPE_T::ARC:
|
|
{
|
|
int radius = GetRadius();
|
|
|
|
// Edit state 0: drawing: place start
|
|
// Edit state 1: drawing: place end (center calculated for 90-degree subtended angle)
|
|
// Edit state 2: point edit: move start (center calculated for invariant subtended angle)
|
|
// Edit state 3: point edit: move end (center calculated for invariant subtended angle)
|
|
// Edit state 4: point edit: move center
|
|
// Edit state 5: point edit: move arc-mid-point
|
|
|
|
switch( m_editState )
|
|
{
|
|
case 0:
|
|
SetArcGeometry( aPosition, aPosition, aPosition );
|
|
return;
|
|
|
|
case 1:
|
|
m_end = aPosition;
|
|
radius = KiROUND( sqrt( sq( GetLineLength( m_start, m_end ) ) / 2.0 ) );
|
|
break;
|
|
|
|
case 2:
|
|
case 3:
|
|
{
|
|
VECTOR2I v = m_start - m_end;
|
|
double chordBefore = sq( v.x ) + sq( v.y );
|
|
|
|
if( m_editState == 2 )
|
|
m_start = aPosition;
|
|
else
|
|
m_end = aPosition;
|
|
|
|
v = m_start - m_end;
|
|
double chordAfter = sq( v.x ) + sq( v.y );
|
|
double ratio = chordAfter / chordBefore;
|
|
|
|
if( ratio != 0 )
|
|
{
|
|
radius = std::max( int( sqrt( sq( radius ) * ratio ) ) + 1,
|
|
int( sqrt( chordAfter ) / 2 ) + 1 );
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 4:
|
|
{
|
|
double radialA = GetLineLength( m_start, aPosition );
|
|
double radialB = GetLineLength( m_end, aPosition );
|
|
radius = int( ( radialA + radialB ) / 2.0 ) + 1;
|
|
}
|
|
break;
|
|
|
|
case 5:
|
|
SetArcGeometry( GetStart(), aPosition, GetEnd() );
|
|
return;
|
|
}
|
|
|
|
// Calculate center based on start, end, and radius
|
|
//
|
|
// Let 'l' be the length of the chord and 'm' the middle point of the chord
|
|
double l = GetLineLength( m_start, m_end );
|
|
VECTOR2I m = ( m_start + m_end ) / 2;
|
|
|
|
// Calculate 'd', the vector from the chord midpoint to the center
|
|
VECTOR2I d;
|
|
d.x = KiROUND( sqrt( sq( radius ) - sq( l/2 ) ) * ( m_start.y - m_end.y ) / l );
|
|
d.y = KiROUND( sqrt( sq( radius ) - sq( l/2 ) ) * ( m_end.x - m_start.x ) / l );
|
|
|
|
VECTOR2I c1 = m + d;
|
|
VECTOR2I c2 = m - d;
|
|
|
|
// Solution gives us 2 centers; we need to pick one:
|
|
switch( m_editState )
|
|
{
|
|
case 1:
|
|
{
|
|
// Keep center clockwise from chord while drawing
|
|
VECTOR2I chordVector = m_end - m_start;
|
|
double chordAngle = ArcTangente( chordVector.y, chordVector.x );
|
|
NORMALIZE_ANGLE_POS( chordAngle );
|
|
|
|
VECTOR2I c1Test = c1;
|
|
RotatePoint( c1Test, m_start, -chordAngle );
|
|
|
|
m_arcCenter = c1Test.x > 0 ? c2 : c1;
|
|
}
|
|
break;
|
|
|
|
case 2:
|
|
case 3:
|
|
// Pick the one closer to the old center
|
|
m_arcCenter = GetLineLength( c1, m_arcCenter ) < GetLineLength( c2, m_arcCenter ) ? c1 : c2;
|
|
break;
|
|
|
|
case 4:
|
|
// Pick the one closer to the mouse position
|
|
m_arcCenter = GetLineLength( c1, aPosition ) < GetLineLength( c2, aPosition ) ? c1 : c2;
|
|
|
|
if( GetArcAngle() > 1800 )
|
|
std::swap( m_start, m_end );
|
|
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case SHAPE_T::POLY:
|
|
m_poly.Outline( 0 ).SetPoint( m_poly.Outline( 0 ).GetPointCount() - 1, aPosition );
|
|
break;
|
|
|
|
default:
|
|
UNIMPLEMENTED_FOR( SHAPE_T_asString() );
|
|
}
|
|
}
|
|
|
|
|
|
void EDA_SHAPE::endEdit( bool aClosed )
|
|
{
|
|
switch( GetShape() )
|
|
{
|
|
case SHAPE_T::ARC:
|
|
case SHAPE_T::SEGMENT:
|
|
case SHAPE_T::CIRCLE:
|
|
case SHAPE_T::RECT:
|
|
break;
|
|
|
|
case SHAPE_T::POLY:
|
|
{
|
|
SHAPE_LINE_CHAIN& poly = m_poly.Outline( 0 );
|
|
|
|
// do not include last point twice
|
|
if( poly.GetPointCount() > 2 )
|
|
{
|
|
if( poly.CPoint( poly.GetPointCount() - 2 ) == poly.CLastPoint() )
|
|
{
|
|
poly.SetClosed( aClosed );
|
|
poly.Remove( poly.GetPointCount() - 1 );
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
UNIMPLEMENTED_FOR( SHAPE_T_asString() );
|
|
}
|
|
}
|
|
|
|
|
|
void EDA_SHAPE::SwapShape( EDA_SHAPE* aImage )
|
|
{
|
|
EDA_SHAPE* image = dynamic_cast<EDA_SHAPE*>( aImage );
|
|
assert( image );
|
|
|
|
std::swap( m_stroke, image->m_stroke );
|
|
std::swap( m_start, image->m_start );
|
|
std::swap( m_end, image->m_end );
|
|
std::swap( m_arcCenter, image->m_arcCenter );
|
|
std::swap( m_shape, image->m_shape );
|
|
std::swap( m_bezierC1, image->m_bezierC1 );
|
|
std::swap( m_bezierC2, image->m_bezierC2 );
|
|
std::swap( m_bezierPoints, image->m_bezierPoints );
|
|
std::swap( m_poly, image->m_poly );
|
|
}
|
|
|
|
|
|
int EDA_SHAPE::Compare( const EDA_SHAPE* aOther ) const
|
|
{
|
|
#define EPSILON 2 // Should be enough for rounding errors on calculated items
|
|
|
|
#define TEST( a, b ) { if( a != b ) return a - b; }
|
|
#define TEST_E( a, b ) { if( abs( a - b ) > EPSILON ) return a - b; }
|
|
#define TEST_PT( a, b ) { TEST_E( a.x, b.x ); TEST_E( a.y, b.y ); }
|
|
|
|
TEST_PT( m_start, aOther->m_start );
|
|
TEST_PT( m_end, aOther->m_end );
|
|
|
|
TEST( (int) m_shape, (int) aOther->m_shape );
|
|
|
|
if( m_shape == SHAPE_T::ARC )
|
|
{
|
|
TEST_PT( m_arcCenter, aOther->m_arcCenter );
|
|
}
|
|
else if( m_shape == SHAPE_T::BEZIER )
|
|
{
|
|
TEST_PT( m_bezierC1, aOther->m_bezierC1 );
|
|
TEST_PT( m_bezierC2, aOther->m_bezierC2 );
|
|
}
|
|
else if( m_shape == SHAPE_T::POLY )
|
|
{
|
|
TEST( m_poly.TotalVertices(), aOther->m_poly.TotalVertices() );
|
|
}
|
|
|
|
for( size_t ii = 0; ii < m_bezierPoints.size(); ++ii )
|
|
TEST_PT( m_bezierPoints[ii], aOther->m_bezierPoints[ii] );
|
|
|
|
for( int ii = 0; ii < m_poly.TotalVertices(); ++ii )
|
|
TEST_PT( m_poly.CVertex( ii ), aOther->m_poly.CVertex( ii ) );
|
|
|
|
TEST_E( m_stroke.GetWidth(), aOther->m_stroke.GetWidth() );
|
|
TEST( (int) m_stroke.GetPlotStyle(), (int) aOther->m_stroke.GetPlotStyle() );
|
|
TEST( (int) m_fill, (int) aOther->m_fill );
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
void EDA_SHAPE::TransformShapeWithClearanceToPolygon( SHAPE_POLY_SET& aCornerBuffer,
|
|
int aClearanceValue,
|
|
int aError, ERROR_LOC aErrorLoc,
|
|
bool ignoreLineWidth ) const
|
|
{
|
|
int width = ignoreLineWidth ? 0 : GetWidth();
|
|
|
|
width += 2 * aClearanceValue;
|
|
|
|
switch( m_shape )
|
|
{
|
|
case SHAPE_T::CIRCLE:
|
|
if( IsFilled() )
|
|
{
|
|
TransformCircleToPolygon( aCornerBuffer, getCenter(), GetRadius() + width / 2, aError,
|
|
aErrorLoc );
|
|
}
|
|
else
|
|
{
|
|
TransformRingToPolygon( aCornerBuffer, getCenter(), GetRadius(), width, aError,
|
|
aErrorLoc );
|
|
}
|
|
|
|
break;
|
|
|
|
case SHAPE_T::RECT:
|
|
{
|
|
std::vector<VECTOR2I> pts = GetRectCorners();
|
|
|
|
if( IsFilled() )
|
|
{
|
|
aCornerBuffer.NewOutline();
|
|
|
|
for( const VECTOR2I& pt : pts )
|
|
aCornerBuffer.Append( pt );
|
|
}
|
|
|
|
if( width > 0 || !IsFilled() )
|
|
{
|
|
// Add in segments
|
|
TransformOvalToPolygon( aCornerBuffer, pts[0], pts[1], width, aError, aErrorLoc );
|
|
TransformOvalToPolygon( aCornerBuffer, pts[1], pts[2], width, aError, aErrorLoc );
|
|
TransformOvalToPolygon( aCornerBuffer, pts[2], pts[3], width, aError, aErrorLoc );
|
|
TransformOvalToPolygon( aCornerBuffer, pts[3], pts[0], width, aError, aErrorLoc );
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case SHAPE_T::ARC:
|
|
TransformArcToPolygon( aCornerBuffer, GetStart(), GetArcMid(), GetEnd(), width, aError,
|
|
aErrorLoc );
|
|
break;
|
|
|
|
case SHAPE_T::SEGMENT:
|
|
TransformOvalToPolygon( aCornerBuffer, GetStart(), GetEnd(), width, aError, aErrorLoc );
|
|
break;
|
|
|
|
case SHAPE_T::POLY:
|
|
{
|
|
if( !IsPolyShapeValid() )
|
|
break;
|
|
|
|
// The polygon is expected to be a simple polygon; not self intersecting, no hole.
|
|
double orientation = getParentOrientation();
|
|
VECTOR2I offset = getParentPosition();
|
|
|
|
// Build the polygon with the actual position and orientation:
|
|
std::vector<VECTOR2I> poly;
|
|
DupPolyPointsList( poly );
|
|
|
|
for( VECTOR2I& point : poly )
|
|
{
|
|
RotatePoint( point, orientation );
|
|
point += offset;
|
|
}
|
|
|
|
if( IsFilled() )
|
|
{
|
|
aCornerBuffer.NewOutline();
|
|
|
|
for( const VECTOR2I& point : poly )
|
|
aCornerBuffer.Append( point.x, point.y );
|
|
}
|
|
|
|
if( width > 0 || !IsFilled() )
|
|
{
|
|
VECTOR2I pt1( poly[poly.size() - 1] );
|
|
|
|
for( const VECTOR2I& pt2 : poly )
|
|
{
|
|
if( pt2 != pt1 )
|
|
TransformOvalToPolygon( aCornerBuffer, pt1, pt2, width, aError, aErrorLoc );
|
|
|
|
pt1 = pt2;
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case SHAPE_T::BEZIER:
|
|
{
|
|
std::vector<VECTOR2I> ctrlPts = { GetStart(), GetBezierC1(), GetBezierC2(), GetEnd() };
|
|
BEZIER_POLY converter( ctrlPts );
|
|
std::vector<VECTOR2I> poly;
|
|
converter.GetPoly( poly, GetWidth() );
|
|
|
|
for( unsigned ii = 1; ii < poly.size(); ii++ )
|
|
{
|
|
TransformOvalToPolygon( aCornerBuffer, poly[ii - 1], poly[ii], width, aError,
|
|
aErrorLoc );
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
default:
|
|
UNIMPLEMENTED_FOR( SHAPE_T_asString() );
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
static struct EDA_SHAPE_DESC
|
|
{
|
|
EDA_SHAPE_DESC()
|
|
{
|
|
ENUM_MAP<SHAPE_T>::Instance()
|
|
.Map( SHAPE_T::SEGMENT, _HKI( "Segment" ) )
|
|
.Map( SHAPE_T::RECT, _HKI( "Rectangle" ) )
|
|
.Map( SHAPE_T::ARC, _HKI( "Arc" ) )
|
|
.Map( SHAPE_T::CIRCLE, _HKI( "Circle" ) )
|
|
.Map( SHAPE_T::POLY, _HKI( "Polygon" ) )
|
|
.Map( SHAPE_T::BEZIER, _HKI( "Bezier" ) );
|
|
ENUM_MAP<PLOT_DASH_TYPE>::Instance()
|
|
.Map( PLOT_DASH_TYPE::DEFAULT, _HKI( "Default" ) )
|
|
.Map( PLOT_DASH_TYPE::SOLID, _HKI( "Solid" ) )
|
|
.Map( PLOT_DASH_TYPE::DASH, _HKI( "Dashed" ) )
|
|
.Map( PLOT_DASH_TYPE::DOT, _HKI( "Dotted" ) )
|
|
.Map( PLOT_DASH_TYPE::DASHDOT, _HKI( "Dash-Dot" ) )
|
|
.Map( PLOT_DASH_TYPE::DASHDOTDOT, _HKI( "Dash-Dot-Dot" ) );
|
|
|
|
PROPERTY_MANAGER& propMgr = PROPERTY_MANAGER::Instance();
|
|
REGISTER_TYPE( EDA_SHAPE );
|
|
propMgr.AddProperty( new PROPERTY_ENUM<EDA_SHAPE, SHAPE_T>( _HKI( "Shape" ),
|
|
&EDA_SHAPE::SetShape, &EDA_SHAPE::GetShape ) );
|
|
propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "Start X" ),
|
|
&EDA_SHAPE::SetStartX, &EDA_SHAPE::GetStartX ) );
|
|
propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "Start Y" ),
|
|
&EDA_SHAPE::SetStartY, &EDA_SHAPE::GetStartY ) );
|
|
propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "End X" ),
|
|
&EDA_SHAPE::SetEndX, &EDA_SHAPE::GetEndX ) );
|
|
propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "End Y" ),
|
|
&EDA_SHAPE::SetEndY, &EDA_SHAPE::GetEndY ) );
|
|
// TODO: m_arcCenter, m_bezierC1, m_bezierC2, m_poly
|
|
propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "Line Width" ),
|
|
&EDA_SHAPE::SetWidth, &EDA_SHAPE::GetWidth ) );
|
|
}
|
|
} _EDA_SHAPE_DESC;
|
|
|
|
ENUM_TO_WXANY( SHAPE_T )
|
|
ENUM_TO_WXANY( PLOT_DASH_TYPE )
|