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:
John Beard 2017-02-27 04:08:45 +08:00 committed by Maciej Suminski
parent db174862c8
commit 5f303f7b25
7 changed files with 876 additions and 112 deletions

View File

@ -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

View File

@ -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 );
}

View File

@ -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;
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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();
}