Enhance Pcbnew arc construction
This adds a richer overlay to the arc-by-three-points tool in Pcbnew, including more guide-lines and a live display of radius and angle. Also included is ability to go back to the previous step (if setting end angle, you can go back to setting start point, etc) using Backspace, and Ctrl snaps the start/end angles to 45 degree multiples. This adds new classes * MULTISTEP_GEOM_MANAGER: represents a generic "geometry manager" that builds up some geometrical construction based on a sequence of points. Used by: * ARC_GEOM_MANAGER: handles the logical flow of constructing an arc by centre-point, set radius, set angle. This moves the logic out of the Pcbnew DRAWING_TOOL event loop in drawArc(). * ARC_ASSISTANT: graphical overlay to communicate current arc shape to the user during the drawing process
This commit is contained in:
parent
db174862c8
commit
5f303f7b25
|
@ -185,6 +185,8 @@ set( COMMON_PAGE_LAYOUT_SRCS
|
|||
)
|
||||
|
||||
set( COMMON_PREVIEW_ITEMS_SRCS
|
||||
preview_items/arc_assistant.cpp
|
||||
preview_items/arc_geom_manager.cpp
|
||||
preview_items/preview_utils.cpp
|
||||
preview_items/ruler_item.cpp
|
||||
preview_items/simple_overlay_item.cpp
|
||||
|
|
|
@ -0,0 +1,198 @@
|
|||
/*
|
||||
* This program source code file is part of KiCad, a free EDA CAD application.
|
||||
*
|
||||
* Copyright (C) 2017 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 <preview_items/arc_assistant.h>
|
||||
|
||||
#include <preview_items/preview_utils.h>
|
||||
#include <gal/graphics_abstraction_layer.h>
|
||||
#include <view/view.h>
|
||||
|
||||
#include <common.h>
|
||||
#include <base_units.h>
|
||||
|
||||
using namespace KIGFX::PREVIEW;
|
||||
|
||||
ARC_ASSISTANT::ARC_ASSISTANT( const ARC_GEOM_MANAGER& aManager ) :
|
||||
EDA_ITEM( NOT_USED ),
|
||||
m_constructMan( aManager )
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
const BOX2I ARC_ASSISTANT::ViewBBox() const
|
||||
{
|
||||
BOX2I tmp;
|
||||
|
||||
// no bounding box when no graphic shown
|
||||
if( m_constructMan.IsReset() )
|
||||
return tmp;
|
||||
|
||||
// just enclose the whle circular area
|
||||
auto origin = m_constructMan.GetOrigin();
|
||||
auto radius = m_constructMan.GetRadius();
|
||||
VECTOR2D rVec( radius, radius );
|
||||
|
||||
tmp.SetOrigin( origin + rVec );
|
||||
tmp.SetEnd( origin - rVec );
|
||||
tmp.Normalize();
|
||||
return tmp;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get deci-degrees from radians, normalised to +/- 360.
|
||||
*
|
||||
* The normalisation is such that a negative angle will stay
|
||||
* negative.
|
||||
*/
|
||||
double getNormDeciDegFromRad( double aRadians )
|
||||
{
|
||||
double degs = RAD2DECIDEG( aRadians );
|
||||
|
||||
// normalise to +/- 360
|
||||
while( degs < -3600.0 )
|
||||
degs += 3600.0;
|
||||
|
||||
while( degs > 3600.0 )
|
||||
degs -= 3600.0;
|
||||
|
||||
return degs;
|
||||
}
|
||||
|
||||
|
||||
static const double ANGLE_EPSILON = 1e-9;
|
||||
|
||||
double angleIsSpecial( double aRadians )
|
||||
{
|
||||
return std::fabs( std::remainder( aRadians, M_PI_4 ) ) < ANGLE_EPSILON;
|
||||
}
|
||||
|
||||
|
||||
static void drawLineWithHilight( KIGFX::GAL& aGal,
|
||||
const VECTOR2I& aStart, const VECTOR2I& aEnd, bool aDim )
|
||||
{
|
||||
const auto vec = aEnd - aStart;
|
||||
COLOR4D strokeColor = PreviewOverlayDefaultColor();
|
||||
|
||||
if( angleIsSpecial( vec.Angle() ) )
|
||||
strokeColor = PreviewOverlaySpecialAngleColor();
|
||||
|
||||
aGal.SetStrokeColor( strokeColor.WithAlpha( PreviewOverlayDeemphAlpha( aDim ) ) );
|
||||
aGal.DrawLine( aStart, aEnd );
|
||||
}
|
||||
|
||||
|
||||
static void drawArcWithHilight( KIGFX::GAL& aGal,
|
||||
const VECTOR2I& aOrigin, double aRad, double aStartAngle,
|
||||
double aEndAngle )
|
||||
{
|
||||
COLOR4D color = PreviewOverlayDefaultColor();
|
||||
|
||||
if( angleIsSpecial( aStartAngle - aEndAngle ) )
|
||||
color = PreviewOverlaySpecialAngleColor();
|
||||
|
||||
aGal.SetStrokeColor( color );
|
||||
aGal.SetFillColor( color.WithAlpha( 0.2 ) );
|
||||
|
||||
// draw the angle reference arc
|
||||
aGal.DrawArc( aOrigin, aRad, -aStartAngle, -aEndAngle );
|
||||
}
|
||||
|
||||
|
||||
void ARC_ASSISTANT::ViewDraw( int aLayer, KIGFX::VIEW* aView ) const
|
||||
{
|
||||
auto& gal = *aView->GetGAL();
|
||||
|
||||
// not in a position to draw anything
|
||||
if( m_constructMan.IsReset() )
|
||||
return;
|
||||
|
||||
gal.SetLineWidth( 1.0 );
|
||||
gal.SetIsStroke( true );
|
||||
gal.SetIsFill( true );
|
||||
|
||||
// constant text size on screen
|
||||
SetConstantGlyphHeight( gal, 12.0 );
|
||||
|
||||
// angle reference arc size
|
||||
const double innerRad = 12.0 / gal.GetWorldScale();
|
||||
|
||||
const auto origin = m_constructMan.GetOrigin();
|
||||
|
||||
// draw first radius line
|
||||
bool dimFirstLine = m_constructMan.GetStep() > ARC_GEOM_MANAGER::SET_START;
|
||||
|
||||
drawLineWithHilight( gal, origin, m_constructMan.GetStartRadiusEnd(), dimFirstLine );
|
||||
|
||||
std::vector<wxString> cursorStrings;
|
||||
|
||||
if( m_constructMan.GetStep() == ARC_GEOM_MANAGER::SET_START )
|
||||
{
|
||||
// haven't started the angle selection phase yet
|
||||
|
||||
auto initAngle = m_constructMan.GetStartAngle();
|
||||
|
||||
const auto angleRefLineEnd = m_constructMan.GetOrigin() + VECTOR2D( innerRad * 1.5, 0.0 );
|
||||
|
||||
gal.SetStrokeColor( PreviewOverlayDefaultColor() );
|
||||
gal.DrawLine( origin, angleRefLineEnd );
|
||||
|
||||
// draw the angle reference arc
|
||||
drawArcWithHilight( gal, origin, innerRad, initAngle, 0.0 );
|
||||
|
||||
double degs = getNormDeciDegFromRad( initAngle );
|
||||
|
||||
cursorStrings.push_back( DimensionLabel( "r", m_constructMan.GetRadius(), g_UserUnit ) );
|
||||
cursorStrings.push_back( DimensionLabel( "θ", degs, DEGREES ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
drawLineWithHilight( gal, origin, m_constructMan.GetEndRadiusEnd(), false );
|
||||
|
||||
auto start = m_constructMan.GetStartAngle();
|
||||
auto subtended = m_constructMan.GetSubtended();
|
||||
|
||||
drawArcWithHilight( gal, origin, innerRad, start, start + subtended );
|
||||
|
||||
double subtendedDeg = getNormDeciDegFromRad( subtended );
|
||||
double endAngleDeg = getNormDeciDegFromRad( start + subtended );
|
||||
|
||||
// draw dimmed extender line to cursor
|
||||
drawLineWithHilight( gal, origin, m_constructMan.GetLastPoint(), true );
|
||||
|
||||
cursorStrings.push_back( DimensionLabel( "Δθ", subtendedDeg, DEGREES ) );
|
||||
cursorStrings.push_back( DimensionLabel( "θ", endAngleDeg, DEGREES ) );
|
||||
}
|
||||
|
||||
// FIXME: spaces choke OpenGL lp:1668455
|
||||
for( auto& str : cursorStrings )
|
||||
{
|
||||
str.erase( std::remove( str.begin(), str.end(), ' ' ),
|
||||
str.end() );
|
||||
}
|
||||
|
||||
// place the text next to cursor, on opposite side from radius
|
||||
DrawTextNextToCursor( gal, m_constructMan.GetLastPoint(),
|
||||
origin - m_constructMan.GetLastPoint(),
|
||||
cursorStrings );
|
||||
}
|
|
@ -0,0 +1,164 @@
|
|||
/*
|
||||
* This program source code file is part of KiCad, a free EDA CAD application.
|
||||
*
|
||||
* Copyright (C) 2017 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 <preview_items/arc_geom_manager.h>
|
||||
|
||||
#include <common.h> // KiROUND
|
||||
|
||||
using namespace KIGFX::PREVIEW;
|
||||
|
||||
|
||||
///> Snap an angle to the nearest 45 degrees
|
||||
static double snapAngle( double aAngle )
|
||||
{
|
||||
return KiROUND( aAngle / M_PI_4 ) * M_PI_4;
|
||||
}
|
||||
|
||||
|
||||
bool ARC_GEOM_MANAGER::acceptPoint( const VECTOR2I& aPt )
|
||||
{
|
||||
switch( getStep() )
|
||||
{
|
||||
case SET_ORIGIN:
|
||||
return setOrigin( aPt );
|
||||
case SET_START:
|
||||
return setStart( aPt );
|
||||
case SET_ANGLE:
|
||||
return setEnd( aPt );
|
||||
case COMPLETE:
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
void ARC_GEOM_MANAGER::SetClockwise( bool aCw )
|
||||
{
|
||||
m_clockwise = aCw;
|
||||
setGeometryChanged();
|
||||
}
|
||||
|
||||
|
||||
void ARC_GEOM_MANAGER::ToggleClockwise()
|
||||
{
|
||||
m_clockwise = !m_clockwise;
|
||||
setGeometryChanged();
|
||||
}
|
||||
|
||||
|
||||
VECTOR2I ARC_GEOM_MANAGER::GetOrigin() const
|
||||
{
|
||||
return m_origin;
|
||||
}
|
||||
|
||||
|
||||
VECTOR2I ARC_GEOM_MANAGER::GetStartRadiusEnd() const
|
||||
{
|
||||
return m_origin + VECTOR2I( m_radius, 0 ).Rotate( m_startAngle );
|
||||
}
|
||||
|
||||
|
||||
VECTOR2I ARC_GEOM_MANAGER::GetEndRadiusEnd() const
|
||||
{
|
||||
return m_origin + VECTOR2I( m_radius, 0 ).Rotate( m_endAngle );
|
||||
}
|
||||
|
||||
|
||||
double ARC_GEOM_MANAGER::GetRadius() const
|
||||
{
|
||||
return m_radius;
|
||||
}
|
||||
|
||||
|
||||
double ARC_GEOM_MANAGER::GetStartAngle() const
|
||||
{
|
||||
double angle = m_startAngle;
|
||||
|
||||
if( m_clockwise )
|
||||
angle -= 2 * M_PI;
|
||||
|
||||
return -angle;
|
||||
}
|
||||
|
||||
|
||||
double ARC_GEOM_MANAGER::GetSubtended() const
|
||||
{
|
||||
double angle = m_endAngle - m_startAngle;
|
||||
|
||||
if( m_endAngle <= m_startAngle )
|
||||
angle += 2 * M_PI;
|
||||
|
||||
if( m_clockwise )
|
||||
angle -= 2 * M_PI;
|
||||
|
||||
return -angle;
|
||||
}
|
||||
|
||||
|
||||
bool ARC_GEOM_MANAGER::setOrigin( const VECTOR2I& aOrigin )
|
||||
{
|
||||
m_origin = aOrigin;
|
||||
m_startAngle = 0.0;
|
||||
m_endAngle = 0.0;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool ARC_GEOM_MANAGER::setStart( const VECTOR2I& aEnd )
|
||||
{
|
||||
const auto radVec = aEnd - m_origin;
|
||||
|
||||
m_radius = radVec.EuclideanNorm();
|
||||
m_startAngle = radVec.Angle();
|
||||
|
||||
if( m_angleSnap )
|
||||
m_startAngle = snapAngle( m_startAngle );
|
||||
|
||||
// normalise into 0-2Pi
|
||||
while( m_startAngle < 0 )
|
||||
m_startAngle += M_PI * 2;
|
||||
|
||||
m_endAngle = m_startAngle;
|
||||
|
||||
return m_radius != 0.0;
|
||||
}
|
||||
|
||||
|
||||
bool ARC_GEOM_MANAGER::setEnd( const VECTOR2I& aCursor )
|
||||
{
|
||||
const auto radVec = aCursor - m_origin;
|
||||
|
||||
m_endAngle = radVec.Angle();
|
||||
|
||||
if( m_angleSnap )
|
||||
m_endAngle = snapAngle( m_endAngle );
|
||||
|
||||
// normalise into 0-2Pi
|
||||
while( m_endAngle < 0 )
|
||||
m_endAngle += M_PI * 2;
|
||||
|
||||
// if the end is the same as the start, this is a bad point
|
||||
return m_endAngle != m_startAngle;
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* This program source code file is part of KiCad, a free EDA CAD application.
|
||||
*
|
||||
* Copyright (C) 2017 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
|
||||
*/
|
||||
|
||||
#ifndef PREVIEW_ITEMS_ARC_ASSISTANT_H
|
||||
#define PREVIEW_ITEMS_ARC_ASSISTANT_H
|
||||
|
||||
#include <base_struct.h>
|
||||
#include <preview_items/arc_geom_manager.h>
|
||||
|
||||
namespace KIGFX {
|
||||
namespace PREVIEW {
|
||||
/**
|
||||
* Class SELECTION_AREA
|
||||
*
|
||||
* Represents an assitant draw when interactively drawing an
|
||||
* arc on a canvas.
|
||||
*/
|
||||
class ARC_ASSISTANT : public EDA_ITEM
|
||||
{
|
||||
public:
|
||||
|
||||
ARC_ASSISTANT( const ARC_GEOM_MANAGER& aManager );
|
||||
|
||||
const BOX2I ViewBBox() const override;
|
||||
|
||||
/**
|
||||
* Draw the assistance (with reference to the contstruction manager
|
||||
*/
|
||||
void ViewDraw( int aLayer, KIGFX::VIEW* aView ) const override final;
|
||||
|
||||
#if defined(DEBUG)
|
||||
void Show( int x, std::ostream& st ) const override
|
||||
{
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Get class name
|
||||
* @return string "ARC_ASSISTANT"
|
||||
*/
|
||||
wxString GetClass() const override
|
||||
{
|
||||
return "ARC_ASSISTANT";
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
const ARC_GEOM_MANAGER& m_constructMan;
|
||||
};
|
||||
} // PREVIEW
|
||||
} // KIGFX
|
||||
|
||||
#endif // PREVIEW_ITEMS_ARC_ASSISTANT_H
|
|
@ -0,0 +1,144 @@
|
|||
/*
|
||||
* This program source code file is part of KiCad, a free EDA CAD application.
|
||||
*
|
||||
* Copyright (C) 2017 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
|
||||
*/
|
||||
|
||||
#ifndef PREVIEW_ITEMS_ARC_GEOMETRY_MANAGER_H
|
||||
#define PREVIEW_ITEMS_ARC_GEOMETRY_MANAGER_H
|
||||
|
||||
#include <preview_items/multistep_geom_manager.h>
|
||||
|
||||
#include <common.h>
|
||||
|
||||
namespace KIGFX {
|
||||
namespace PREVIEW {
|
||||
|
||||
|
||||
/**
|
||||
* Class ARC_GEOM_MANAGER
|
||||
*
|
||||
* A class to manage the construction of a circular arc though
|
||||
* sequential setting of critical points: centre, arc start
|
||||
* and arc end. The manager is driven by setting cursor points, which
|
||||
* update the geometry, and optionally advance the manager state.
|
||||
*
|
||||
* Interfaces are provided to return both arc geometry (can be used
|
||||
* to set up real arcs on PCBs, for example) as well as important
|
||||
* control points for informational overlays.
|
||||
*/
|
||||
class ARC_GEOM_MANAGER: public MULTISTEP_GEOM_MANAGER
|
||||
{
|
||||
public:
|
||||
ARC_GEOM_MANAGER()
|
||||
{}
|
||||
|
||||
enum ARC_STEPS
|
||||
{
|
||||
SET_ORIGIN = 0, ///> Waiting to lock in origin point
|
||||
SET_START, ///> Waiting to lock in the arc start point
|
||||
SET_ANGLE, ///> Waiting to lock in the arc end point
|
||||
COMPLETE
|
||||
};
|
||||
|
||||
|
||||
int getMaxStep() const override
|
||||
{
|
||||
return COMPLETE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current step the mananger is on (useful when drawing
|
||||
* something depends on the current state)
|
||||
*/
|
||||
ARC_STEPS GetStep() const
|
||||
{
|
||||
return static_cast<ARC_STEPS>( getStep() );
|
||||
}
|
||||
|
||||
bool acceptPoint( const VECTOR2I& aPt ) override;
|
||||
|
||||
///> The the arc to be clockwise from start
|
||||
void SetClockwise( bool aCw );
|
||||
|
||||
///> Reverse the current are direction
|
||||
void ToggleClockwise();
|
||||
|
||||
///> Set angle snapping (for the next point)
|
||||
void SetAngleSnap( bool aSnap )
|
||||
{
|
||||
m_angleSnap = aSnap;
|
||||
}
|
||||
|
||||
/*
|
||||
* Geometry query interface - used by clients of the manager
|
||||
*/
|
||||
|
||||
///> Get the centre point of the arc (valid when state > SET_ORIGIN)
|
||||
VECTOR2I GetOrigin() const;
|
||||
|
||||
///> Get the coordinates of the arc start
|
||||
VECTOR2I GetStartRadiusEnd() const;
|
||||
|
||||
///> Get the coordinates of the arc end point
|
||||
VECTOR2I GetEndRadiusEnd() const;
|
||||
|
||||
///> Get the radius of the arc (valid if step >= SET_START)
|
||||
double GetRadius() const;
|
||||
|
||||
///> Get the angle of the vector leading to the start point (valid if step >= SET_START)
|
||||
double GetStartAngle() const;
|
||||
|
||||
///> Get the angle of the vector leading to the end point (valid if step >= SET_ANGLE)
|
||||
double GetSubtended() const;
|
||||
|
||||
private:
|
||||
|
||||
/*
|
||||
* Point acceptor functions
|
||||
*/
|
||||
|
||||
///> Set the centre point of the arc
|
||||
bool setOrigin( const VECTOR2I& aOrigin );
|
||||
|
||||
///> Set the end of the first radius line (arc start)
|
||||
bool setStart( const VECTOR2I& aEnd );
|
||||
|
||||
///> Set a point of the second radius line (collinear with arc end)
|
||||
bool setEnd( const VECTOR2I& aCursor );
|
||||
|
||||
/*
|
||||
* Arc geometry
|
||||
*/
|
||||
bool m_clockwise = true;
|
||||
VECTOR2I m_origin;
|
||||
double m_radius = 0.0;
|
||||
double m_startAngle = 0.0;
|
||||
double m_endAngle = 0.0;
|
||||
|
||||
/*
|
||||
* construction parameters
|
||||
*/
|
||||
bool m_angleSnap;
|
||||
};
|
||||
} // PREVIEW
|
||||
} // KIGFX
|
||||
|
||||
#endif // PREVIEW_ITEMS_ARC_GEOMETRY_MANAGER_H
|
|
@ -0,0 +1,195 @@
|
|||
/*
|
||||
* This program source code file is part of KiCad, a free EDA CAD application.
|
||||
*
|
||||
* Copyright (C) 2017 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
|
||||
*/
|
||||
|
||||
#ifndef PREVIEW_ITEMS_MULTISTEP_GEOMETRY_MANAGER_H
|
||||
#define PREVIEW_ITEMS_MULTISTEP_GEOMETRY_MANAGER_H
|
||||
|
||||
#include <math/vector2d.h>
|
||||
#include <algorithm>
|
||||
|
||||
namespace KIGFX {
|
||||
namespace PREVIEW {
|
||||
|
||||
/**
|
||||
* A geometry manager that works by accepting a sequence of points
|
||||
* and advancing though stages of geometric construction with each
|
||||
* point "locked in".
|
||||
*
|
||||
* For example, constructing an arc might go:
|
||||
* - Set origin
|
||||
* - Set start point
|
||||
* - Set end point
|
||||
*
|
||||
* The exact steps and how the points translate to geometry depends on
|
||||
* the subclass.
|
||||
*/
|
||||
class MULTISTEP_GEOM_MANAGER
|
||||
{
|
||||
public:
|
||||
virtual ~MULTISTEP_GEOM_MANAGER()
|
||||
{}
|
||||
|
||||
/**
|
||||
* Add a point to the construction manager
|
||||
*
|
||||
* @param aPt the new point
|
||||
* @param aLockIn whether to "lock in" the point, and move the
|
||||
* geometry manager to the next (or previous) step. False to
|
||||
* update geomtry and not affect manager state
|
||||
*/
|
||||
void AddPoint( const VECTOR2I& aPt, bool aLockIn )
|
||||
{
|
||||
// hold onto the raw point separately to the managed geometry
|
||||
m_lastPoint = aPt;
|
||||
|
||||
// update the geometry
|
||||
bool accepted = acceptPoint( aPt );
|
||||
|
||||
// advance or regress the manager
|
||||
if( aLockIn )
|
||||
performStep( accepted );
|
||||
|
||||
setGeometryChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* Undo the last point, and move the manager back to the previous
|
||||
* step
|
||||
*/
|
||||
void RemoveLastPoint()
|
||||
{
|
||||
performStep( false );
|
||||
|
||||
// process the last point again, but in the previous step mode
|
||||
// it doesn't matter if accepted or not, as long as the geometry
|
||||
// is regenerated if needed
|
||||
acceptPoint( GetLastPoint() );
|
||||
setGeometryChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if the manager is in the initial state
|
||||
*/
|
||||
bool IsReset() const
|
||||
{
|
||||
return m_step == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the manager to the initial state
|
||||
*/
|
||||
void Reset()
|
||||
{
|
||||
m_step = 0;
|
||||
setGeometryChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if the manager reached the final state
|
||||
*/
|
||||
bool IsComplete() const
|
||||
{
|
||||
return m_step == getMaxStep();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the last point added (locked in or not). This can
|
||||
* be useful when drawing previews, as the point given isn't always
|
||||
* what gets locked into the geometry, if that step doesn't have
|
||||
* full degrees of freedom
|
||||
*/
|
||||
VECTOR2I GetLastPoint() const
|
||||
{
|
||||
return m_lastPoint;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if the geoemtry has changed, eg such that a client
|
||||
* should redraw
|
||||
*/
|
||||
bool HasGeometryChanged() const
|
||||
{
|
||||
return m_changed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the geometry changed flag, call after the client code has
|
||||
* updated everything as needed.
|
||||
*/
|
||||
void ClearGeometryChanged()
|
||||
{
|
||||
m_changed = false;
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
///> Mark the geometry as changed for clients to notice
|
||||
void setGeometryChanged()
|
||||
{
|
||||
m_changed = true;
|
||||
}
|
||||
|
||||
///> Get the current stage of the manager
|
||||
int getStep() const
|
||||
{
|
||||
return m_step;
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
///> Function that accepts a point for a stage, or rejects it
|
||||
///> to return to the previous stage
|
||||
virtual bool acceptPoint( const VECTOR2I& aPt ) = 0;
|
||||
|
||||
/**
|
||||
* The highest step this manager has - used to recognise completion
|
||||
* and to clamp the step as it advances.
|
||||
*/
|
||||
virtual int getMaxStep() const = 0;
|
||||
|
||||
///> Moves the manager forward or backward through the stages
|
||||
void performStep( bool aForward )
|
||||
{
|
||||
m_step += aForward ? 1 : -1;
|
||||
|
||||
// clamp to allowed values
|
||||
m_step = std::min( std::max( m_step, 0 ), getMaxStep() );
|
||||
}
|
||||
|
||||
///> Has the gemotry changed such that a client should redraw?
|
||||
bool m_changed = false;
|
||||
|
||||
///> The last (raw) point added, which is usually the cursor position
|
||||
VECTOR2I m_lastPoint;
|
||||
|
||||
/**
|
||||
* The current manager step, from 0 to some highest number that
|
||||
* depends on the manager
|
||||
*/
|
||||
int m_step = 0;
|
||||
};
|
||||
|
||||
} // PREVIEW
|
||||
} // KIGFX
|
||||
|
||||
#endif // PREVIEW_ITEMS_MULTIPOINT_GEOMETRY_MANAGER_H
|
|
@ -47,6 +47,8 @@
|
|||
#include <bitmaps.h>
|
||||
#include <hotkeys.h>
|
||||
|
||||
#include <preview_items/arc_assistant.h>
|
||||
|
||||
#include <class_board.h>
|
||||
#include <class_edge_mod.h>
|
||||
#include <class_pcb_text.h>
|
||||
|
@ -119,6 +121,16 @@ TOOL_ACTION PCB_ACTIONS::arcPosture( "pcbnew.InteractiveDrawing.arcPosture",
|
|||
AS_CONTEXT, TOOL_ACTION::LegacyHotKey( HK_SWITCH_TRACK_POSTURE ),
|
||||
_( "Switch Arc Posture" ), _( "Switch the arc posture" ) );
|
||||
|
||||
/*
|
||||
* Contextual actions
|
||||
*/
|
||||
|
||||
static TOOL_ACTION deleteLastPoint( "pcbnew.InteractiveDrawing.deleteLastPoint",
|
||||
AS_CONTEXT, WXK_BACK,
|
||||
_( "Delete Last Point" ), _( "Delete the last point added to the current item" ),
|
||||
undo_xpm );
|
||||
|
||||
|
||||
DRAWING_TOOL::DRAWING_TOOL() :
|
||||
PCB_TOOL( "pcbnew.InteractiveDrawing" ),
|
||||
m_view( nullptr ), m_controls( nullptr ),
|
||||
|
@ -140,10 +152,18 @@ bool DRAWING_TOOL::Init()
|
|||
return m_mode != MODE::NONE;
|
||||
};
|
||||
|
||||
auto canUndoPoint = [ this ] ( const SELECTION& aSel ) {
|
||||
return m_mode == MODE::ARC;
|
||||
};
|
||||
|
||||
auto& ctxMenu = m_menu.GetMenu();
|
||||
|
||||
// cancel current toool goes in main context menu at the top if present
|
||||
ctxMenu.AddItem( ACTIONS::cancelInteractive, activeToolFunctor, 1000 );
|
||||
|
||||
// some interactive drawing tools can undo the last point
|
||||
ctxMenu.AddItem( deleteLastPoint, canUndoPoint, 1000 );
|
||||
|
||||
ctxMenu.AddSeparator( activeToolFunctor, 1000 );
|
||||
|
||||
// Drawing type-specific options will be added by the PCB control tool
|
||||
|
@ -1039,139 +1059,99 @@ bool DRAWING_TOOL::drawSegment( int aShape, DRAWSEGMENT*& aGraphic,
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* Update an arc DRAWSEGMENT from the current state
|
||||
* of an Arc Geoemetry Manager
|
||||
*/
|
||||
static void updateArcFromConstructionMgr(
|
||||
const KIGFX::PREVIEW::ARC_GEOM_MANAGER& aMgr,
|
||||
DRAWSEGMENT& aArc )
|
||||
{
|
||||
auto vec = aMgr.GetOrigin();
|
||||
|
||||
aArc.SetCenter( { vec.x, vec.y } );
|
||||
|
||||
vec = aMgr.GetStartRadiusEnd();
|
||||
aArc.SetArcStart( { vec.x, vec.y } );
|
||||
|
||||
aArc.SetAngle( RAD2DECIDEG( -aMgr.GetSubtended() ) );
|
||||
}
|
||||
|
||||
|
||||
bool DRAWING_TOOL::drawArc( DRAWSEGMENT*& aGraphic )
|
||||
{
|
||||
bool clockwise = true; // drawing direction of the arc
|
||||
double startAngle = 0.0f; // angle of the first arc line
|
||||
VECTOR2I cursorPos = m_controls->GetCursorPosition();
|
||||
m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
|
||||
|
||||
// Line from the arc center to its origin, to visualize its radius
|
||||
DRAWSEGMENT helperLine;
|
||||
helperLine.SetShape( S_SEGMENT );
|
||||
helperLine.SetLayer( Dwgs_User );
|
||||
helperLine.SetWidth( 1 );
|
||||
// Arc geometric construction manager
|
||||
KIGFX::PREVIEW::ARC_GEOM_MANAGER arcManager;
|
||||
|
||||
// Arc drawing assistant overlay
|
||||
KIGFX::PREVIEW::ARC_ASSISTANT arcAsst( arcManager );
|
||||
|
||||
// Add a VIEW_GROUP that serves as a preview for the new item
|
||||
SELECTION preview;
|
||||
m_view->Add( &preview );
|
||||
m_view->Add( &arcAsst );
|
||||
|
||||
m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
|
||||
m_controls->ShowCursor( true );
|
||||
m_controls->SetSnapping( true );
|
||||
|
||||
Activate();
|
||||
|
||||
enum ARC_STEPS
|
||||
{
|
||||
SET_ORIGIN = 0,
|
||||
SET_END,
|
||||
SET_ANGLE,
|
||||
FINISHED
|
||||
};
|
||||
int step = SET_ORIGIN;
|
||||
bool firstPoint = false;
|
||||
|
||||
// Main loop: keep receiving events
|
||||
while( OPT_TOOL_EVENT evt = Wait() )
|
||||
{
|
||||
cursorPos = m_controls->GetCursorPosition();
|
||||
const VECTOR2I cursorPos = m_controls->GetCursorPosition();
|
||||
|
||||
if( TOOL_EVT_UTILS::IsCancelInteractive( *evt ) )
|
||||
if( evt->IsClick( BUT_LEFT ) )
|
||||
{
|
||||
if( !firstPoint )
|
||||
{
|
||||
m_controls->SetAutoPan( true );
|
||||
m_controls->CaptureCursor( true );
|
||||
|
||||
LAYER_ID layer = getDrawingLayer();
|
||||
|
||||
// Init the new item attributes
|
||||
// (non-geometric, those are handled by the manager)
|
||||
aGraphic->SetShape( S_ARC );
|
||||
aGraphic->SetWidth( m_lineWidth );
|
||||
aGraphic->SetLayer( layer );
|
||||
|
||||
preview.Add( aGraphic );
|
||||
firstPoint = true;
|
||||
}
|
||||
|
||||
arcManager.AddPoint( cursorPos, true );
|
||||
}
|
||||
|
||||
else if( evt->IsAction( &deleteLastPoint ) )
|
||||
{
|
||||
arcManager.RemoveLastPoint();
|
||||
}
|
||||
|
||||
else if( evt->IsMotion() )
|
||||
{
|
||||
// set angle snap
|
||||
arcManager.SetAngleSnap( evt->Modifier( MD_CTRL ) );
|
||||
|
||||
// update, but don't step the manager state
|
||||
arcManager.AddPoint( cursorPos, false );
|
||||
}
|
||||
|
||||
else if( TOOL_EVT_UTILS::IsCancelInteractive( *evt ) )
|
||||
{
|
||||
preview.Clear();
|
||||
delete aGraphic;
|
||||
aGraphic = NULL;
|
||||
aGraphic = nullptr;
|
||||
break;
|
||||
}
|
||||
else if( evt->IsClick( BUT_RIGHT ) )
|
||||
{
|
||||
m_menu.ShowContextMenu();
|
||||
}
|
||||
else if( evt->IsClick( BUT_LEFT ) )
|
||||
{
|
||||
switch( step )
|
||||
{
|
||||
case SET_ORIGIN:
|
||||
{
|
||||
LAYER_ID layer = getDrawingLayer();
|
||||
|
||||
// Init the new item attributes
|
||||
aGraphic->SetShape( S_ARC );
|
||||
aGraphic->SetAngle( 0.0 );
|
||||
aGraphic->SetWidth( m_lineWidth );
|
||||
aGraphic->SetCenter( wxPoint( cursorPos.x, cursorPos.y ) );
|
||||
aGraphic->SetLayer( layer );
|
||||
|
||||
helperLine.SetStart( aGraphic->GetCenter() );
|
||||
helperLine.SetEnd( aGraphic->GetCenter() );
|
||||
|
||||
preview.Add( aGraphic );
|
||||
preview.Add( &helperLine );
|
||||
|
||||
m_controls->SetAutoPan( true );
|
||||
m_controls->CaptureCursor( true );
|
||||
}
|
||||
break;
|
||||
|
||||
case SET_END:
|
||||
{
|
||||
if( wxPoint( cursorPos.x, cursorPos.y ) != aGraphic->GetCenter() )
|
||||
{
|
||||
VECTOR2D startLine( aGraphic->GetArcStart() - aGraphic->GetCenter() );
|
||||
startAngle = startLine.Angle();
|
||||
aGraphic->SetArcStart( wxPoint( cursorPos.x, cursorPos.y ) );
|
||||
}
|
||||
else
|
||||
--step; // one another chance to draw a proper arc
|
||||
}
|
||||
break;
|
||||
|
||||
case SET_ANGLE:
|
||||
{
|
||||
if( wxPoint( cursorPos.x, cursorPos.y ) != aGraphic->GetArcStart() && aGraphic->GetAngle() != 0 )
|
||||
{
|
||||
assert( aGraphic->GetArcStart() != aGraphic->GetArcEnd() );
|
||||
assert( aGraphic->GetWidth() > 0 );
|
||||
|
||||
preview.Remove( aGraphic );
|
||||
preview.Remove( &helperLine );
|
||||
}
|
||||
else
|
||||
--step; // one another chance to draw a proper arc
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if( ++step == FINISHED )
|
||||
break;
|
||||
}
|
||||
|
||||
else if( evt->IsMotion() )
|
||||
{
|
||||
switch( step )
|
||||
{
|
||||
case SET_END:
|
||||
helperLine.SetEnd( wxPoint( cursorPos.x, cursorPos.y ) );
|
||||
aGraphic->SetArcStart( wxPoint( cursorPos.x, cursorPos.y ) );
|
||||
break;
|
||||
|
||||
case SET_ANGLE:
|
||||
{
|
||||
VECTOR2D endLine( wxPoint( cursorPos.x, cursorPos.y ) - aGraphic->GetCenter() );
|
||||
double newAngle = RAD2DECIDEG( endLine.Angle() - startAngle );
|
||||
|
||||
// Adjust the new angle to (counter)clockwise setting
|
||||
if( clockwise && newAngle < 0.0 )
|
||||
newAngle += 3600.0;
|
||||
else if( !clockwise && newAngle > 0.0 )
|
||||
newAngle -= 3600.0;
|
||||
|
||||
aGraphic->SetAngle( newAngle );
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
m_view->Update( &preview );
|
||||
}
|
||||
|
||||
else if( evt->IsAction( &PCB_ACTIONS::incWidth ) )
|
||||
{
|
||||
|
@ -1189,19 +1169,26 @@ bool DRAWING_TOOL::drawArc( DRAWSEGMENT*& aGraphic )
|
|||
|
||||
else if( evt->IsAction( &PCB_ACTIONS::arcPosture ) )
|
||||
{
|
||||
if( clockwise )
|
||||
aGraphic->SetAngle( aGraphic->GetAngle() - 3600.0 );
|
||||
else
|
||||
aGraphic->SetAngle( aGraphic->GetAngle() + 3600.0 );
|
||||
arcManager.ToggleClockwise();
|
||||
}
|
||||
|
||||
clockwise = !clockwise;
|
||||
if( arcManager.IsComplete() )
|
||||
{
|
||||
break;
|
||||
}
|
||||
else if( arcManager.HasGeometryChanged() )
|
||||
{
|
||||
updateArcFromConstructionMgr( arcManager, *aGraphic );
|
||||
m_view->Update( &preview );
|
||||
m_view->Update( &arcAsst );
|
||||
}
|
||||
}
|
||||
|
||||
preview.Remove( aGraphic );
|
||||
m_view->Remove( &arcAsst );
|
||||
m_view->Remove( &preview );
|
||||
|
||||
return ( step > SET_ORIGIN );
|
||||
return !arcManager.IsReset();
|
||||
}
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue