Improve arc geometry manager to choose the direction which makes more sense

As long as the arc angle stays below <60°, it will automatically choose between
clockwise and counter clockwise. This allows the user to choose the direction
with a simple mouse movement.
This commit is contained in:
Thomas Pointhuber 2020-07-03 15:05:17 +00:00 committed by Seth Hillbrand
parent 0201cb4e7e
commit 7ba6a77c94
10 changed files with 378 additions and 52 deletions

View File

@ -47,6 +47,7 @@ Seth Hillbrand <hillbrand[at]ucdavis-dot-edu>
Jeff Young <jeff[at]rokeby-dot-ie>
Kevin Cozens <kevin[at]ve3syb-dot-ca>
Ian McInerney <ian.s.mcinerney[at]ieee-dot-org>
Thomas Pointhuber <thomas.pointhuber[at]gmx-dot-at>
See git repo on GitLab for contributors at
https://gitlab.com/kicad/code/kicad/-/graphs/master

View File

@ -240,6 +240,7 @@ set( COMMON_PREVIEW_ITEMS_SRCS
preview_items/ruler_item.cpp
preview_items/selection_area.cpp
preview_items/simple_overlay_item.cpp
preview_items/two_point_assistant.cpp
)
set( PLOTTERS_CONTROL_SRCS

View File

@ -56,6 +56,7 @@ bool ARC_GEOM_MANAGER::acceptPoint( const VECTOR2I& aPt )
void ARC_GEOM_MANAGER::SetClockwise( bool aCw )
{
m_clockwise = aCw;
m_directionLocked = true;
setGeometryChanged();
}
@ -63,6 +64,7 @@ void ARC_GEOM_MANAGER::SetClockwise( bool aCw )
void ARC_GEOM_MANAGER::ToggleClockwise()
{
m_clockwise = !m_clockwise;
m_directionLocked = true;
setGeometryChanged();
}
@ -159,6 +161,25 @@ bool ARC_GEOM_MANAGER::setEnd( const VECTOR2I& aCursor )
while( m_endAngle < 0 )
m_endAngle += M_PI * 2;
if( !m_directionLocked )
{
double ccwAngle = m_endAngle - m_startAngle;
if( m_endAngle <= m_startAngle )
ccwAngle += 2 * M_PI;
double cwAngle = std::abs( ccwAngle - 2 * M_PI );
if( std::min( ccwAngle, cwAngle ) >= M_PI_2 )
m_directionLocked = true;
else
m_clockwise = cwAngle < ccwAngle;
}
else if( std::abs( GetSubtended() ) < M_PI_2 )
{
m_directionLocked = false;
}
// if the end is the same as the start, this is a bad point
return m_endAngle != m_startAngle;
}

View File

@ -125,6 +125,7 @@ void KIGFX::PREVIEW::DrawTextNextToCursor( KIGFX::VIEW* aView,
textPos.x -= 15.0 / gal->GetWorldScale();
}
gal->SetLineWidth( 1.0f ); // TODO(ISM): Set to the minimum GAL linewidth for HiDPI compatibility
gal->SetStrokeColor( rs->GetLayerColor( LAYER_AUX_ITEMS ).WithAlpha(
PreviewOverlayDeemphAlpha( true ) ) );
gal->SetIsFill( false );

View File

@ -0,0 +1,114 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2020 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/two_point_assistant.h>
#include <preview_items/draw_context.h>
#include <preview_items/preview_utils.h>
#include <view/view.h>
#include <common.h>
#include <pcb_painter.h>
using namespace KIGFX::PREVIEW;
TWO_POINT_ASSISTANT::TWO_POINT_ASSISTANT(
const TWO_POINT_GEOMETRY_MANAGER& aManager, EDA_UNITS aUnits, GEOM_SHAPE aShape )
: EDA_ITEM( NOT_USED ), m_constructMan( aManager ), m_units( aUnits ), m_shape( aShape )
{
}
const BOX2I TWO_POINT_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 end = m_constructMan.GetEnd();
if( m_shape == GEOM_SHAPE::SEGMENT || m_shape == GEOM_SHAPE::RECT )
{
tmp.SetOrigin( origin );
tmp.SetEnd( end );
}
else
{
tmp.SetOrigin( origin + end );
tmp.SetEnd( origin - end );
}
tmp.Normalize();
return tmp;
}
void TWO_POINT_ASSISTANT::ViewDraw( int aLayer, KIGFX::VIEW* aView ) const
{
auto& gal = *aView->GetGAL();
// not in a position to draw anything
if( m_constructMan.IsReset() )
return;
const auto origin = m_constructMan.GetOrigin();
const auto end = m_constructMan.GetEnd();
const auto radVec = end - origin;
if( radVec.x == 0 && radVec.y == 0 )
{
return; // text next to cursor jumps alot around in this corner case
}
gal.ResetTextAttributes();
// constant text size on screen
SetConstantGlyphHeight( gal, 12.0 );
std::vector<wxString> cursorStrings;
if( m_shape == GEOM_SHAPE::SEGMENT )
{
cursorStrings.push_back( DimensionLabel( "l", radVec.EuclideanNorm(), m_units ) );
}
else if( m_shape == GEOM_SHAPE::RECT )
{
cursorStrings.push_back( DimensionLabel( "x", std::abs( radVec.x ), m_units ) );
cursorStrings.push_back( DimensionLabel( "y", std::abs( radVec.y ), m_units ) );
}
else if( m_shape == GEOM_SHAPE::CIRCLE )
{
KIGFX::PREVIEW::DRAW_CONTEXT preview_ctx( *aView );
preview_ctx.DrawLine( origin, end, false );
cursorStrings.push_back( DimensionLabel( "r", radVec.EuclideanNorm(), m_units ) );
}
// place the text next to cursor, on opposite side from drawing
DrawTextNextToCursor( aView, end, origin - end, cursorStrings );
}

View File

@ -27,47 +27,48 @@
#include <base_struct.h>
#include <preview_items/arc_geom_manager.h>
namespace KIGFX {
namespace PREVIEW {
/**
* SELECTION_AREA
*
* Represents an assitant draw when interactively drawing an
* arc on a canvas.
*/
class ARC_ASSISTANT : public EDA_ITEM
namespace KIGFX
{
namespace PREVIEW
{
public:
ARC_ASSISTANT( const ARC_GEOM_MANAGER& aManager, EDA_UNITS aUnits );
const BOX2I ViewBBox() const override;
/**
* Draw the assistance (with reference to the contstruction manager
* SELECTION_AREA
*
* Represents an assistant draw when interactively drawing an
* arc on a canvas.
*/
void ViewDraw( int aLayer, KIGFX::VIEW* aView ) const override final;
#if defined(DEBUG)
void Show( int x, std::ostream& st ) const override
class ARC_ASSISTANT : public EDA_ITEM
{
}
public:
ARC_ASSISTANT( const ARC_GEOM_MANAGER& aManager, EDA_UNITS aUnits );
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";
}
/**
* Get class name
* @return string "ARC_ASSISTANT"
*/
wxString GetClass() const override
{
return "ARC_ASSISTANT";
}
private:
private:
const ARC_GEOM_MANAGER& m_constructMan;
EDA_UNITS m_units;
};
} // namespace PREVIEW
} // namespace KIGFX
const ARC_GEOM_MANAGER& m_constructMan;
EDA_UNITS m_units;
};
} // PREVIEW
} // KIGFX
#endif // PREVIEW_ITEMS_ARC_ASSISTANT_H
#endif // PREVIEW_ITEMS_ARC_ASSISTANT_H

View File

@ -136,6 +136,7 @@ private:
* construction parameters
*/
bool m_angleSnap = false;
bool m_directionLocked = false;
};
} // PREVIEW
} // KIGFX

View File

@ -0,0 +1,85 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2020 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_TWO_POINT_ASSISTANT_H
#define PREVIEW_ITEMS_TWO_POINT_ASSISTANT_H
#include <base_struct.h>
#include <preview_items/two_point_geom_manager.h>
namespace KIGFX
{
namespace PREVIEW
{
// TODO: required until STROKE_T is either moved into commons or a better approach is found
enum class GEOM_SHAPE
{
SEGMENT = 0,
RECT,
CIRCLE,
};
/**
* SELECTION_AREA
*
* Represents an assistant draw when interactively drawing an
* line or circle on a canvas.
*/
class TWO_POINT_ASSISTANT : public EDA_ITEM
{
public:
TWO_POINT_ASSISTANT(
const TWO_POINT_GEOMETRY_MANAGER& aManager, EDA_UNITS aUnits, GEOM_SHAPE aShape );
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 "TWO_POINT_ASSISTANT"
*/
wxString GetClass() const override
{
return "TWO_POINT_ASSISTANT";
}
private:
const TWO_POINT_GEOMETRY_MANAGER& m_constructMan;
EDA_UNITS m_units;
GEOM_SHAPE m_shape;
};
} // namespace PREVIEW
} // namespace KIGFX
#endif // PREVIEW_ITEMS_TWO_POINT_ASSISTANT_H

View File

@ -48,6 +48,8 @@ public:
void SetOrigin( const VECTOR2I& aOrigin )
{
m_origin = aOrigin;
m_originSet = true;
setGeometryChanged();
}
VECTOR2I GetOrigin() const
@ -69,6 +71,7 @@ public:
{
m_end = aEnd;
}
setGeometryChanged();
}
VECTOR2I GetEnd() const
@ -81,10 +84,61 @@ public:
m_angleSnap = aSnap;
}
bool GetAngleSnap() const
{
return m_angleSnap;
}
/**
* @return true if the manager is in the initial state
*/
bool IsReset() const
{
return !m_originSet;
}
/**
* Reset the manager to the initial state
*/
void Reset()
{
m_originSet = false;
setGeometryChanged();
}
/**
* @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;
}
private:
VECTOR2I m_origin, m_end;
bool m_angleSnap = false;
///> Has the gemotry changed such that a client should redraw?
bool m_changed = false;
bool m_originSet = false;
};
} // PREVIEW

View File

@ -47,12 +47,14 @@
#include <class_zone.h>
#include <class_module.h>
#include <preview_items/two_point_assistant.h>
#include <preview_items/two_point_geom_manager.h>
#include <ratsnest/ratsnest_data.h>
#include <tools/grid_helper.h>
#include <tools/point_editor.h>
#include <tools/selection_tool.h>
#include <tools/tool_event_utils.h>
#include <tools/zone_create_helper.h>
#include <tools/point_editor.h>
#include <tools/grid_helper.h>
#include <ratsnest/ratsnest_data.h>
using SCOPED_DRAW_MODE = SCOPED_SET_RESET<DRAWING_TOOL::MODE>;
@ -951,6 +953,22 @@ int DRAWING_TOOL::SetAnchor( const TOOL_EVENT& aEvent )
}
/**
* Update an DRAWSEGMENT from the current state
* of an Two POINT Geometry Manager
*/
static void updateSegmentFromConstructionMgr(
const KIGFX::PREVIEW::TWO_POINT_GEOMETRY_MANAGER& aMgr, DRAWSEGMENT* aGraphic )
{
auto vec = aMgr.GetOrigin();
aGraphic->SetStart( { vec.x, vec.y } );
vec = aMgr.GetEnd();
aGraphic->SetEnd( { vec.x, vec.y } );
}
bool DRAWING_TOOL::drawSegment( const std::string& aTool, int aShape, DRAWSEGMENT** aGraphic,
OPT<VECTOR2D> aStartingPoint )
{
@ -962,9 +980,22 @@ bool DRAWING_TOOL::drawSegment( const std::string& aTool, int aShape, DRAWSEGMEN
m_lineWidth = getSegmentWidth( m_frame->GetActiveLayer() );
// geometric construction manager
KIGFX::PREVIEW::TWO_POINT_GEOMETRY_MANAGER twoPointManager;
// drawing assistant overlay
// TODO: workaround because STROKE_T is not visible from commons.
KIGFX::PREVIEW::GEOM_SHAPE geomShape =
( aShape == S_SEGMENT ) ? KIGFX::PREVIEW::GEOM_SHAPE::SEGMENT :
( aShape == S_CIRCLE ) ? KIGFX::PREVIEW::GEOM_SHAPE::CIRCLE :
KIGFX::PREVIEW::GEOM_SHAPE::RECT;
KIGFX::PREVIEW::TWO_POINT_ASSISTANT twoPointAsst(
twoPointManager, m_frame->GetUserUnits(), geomShape );
// Add a VIEW_GROUP that serves as a preview for the new item
PCBNEW_SELECTION preview;
m_view->Add( &preview );
m_view->Add( &twoPointAsst );
m_controls->ShowCursor( true );
@ -1004,19 +1035,24 @@ bool DRAWING_TOOL::drawSegment( const std::string& aTool, int aShape, DRAWSEGMEN
if( direction45 )
{
const VECTOR2I lineVector( cursorPos - VECTOR2I( graphic->GetStart() ) );
const VECTOR2I lineVector( cursorPos - VECTOR2I( twoPointManager.GetOrigin() ) );
// get a restricted 45/H/V line from the last fixed point to the cursor
auto newEnd = GetVectorSnapped45( lineVector );
graphic->SetEnd( graphic->GetStart() + (wxPoint) newEnd );
m_controls->ForceCursorPosition( true, VECTOR2I( graphic->GetEnd() ) );
m_controls->ForceCursorPosition( true, VECTOR2I( twoPointManager.GetEnd() ) );
twoPointManager.SetEnd( twoPointManager.GetOrigin() + (wxPoint) newEnd );
twoPointManager.SetAngleSnap( true );
}
else
{
graphic->SetEnd( (wxPoint) cursorPos );
twoPointManager.SetEnd( (wxPoint) cursorPos );
twoPointManager.SetAngleSnap( false );
}
updateSegmentFromConstructionMgr( twoPointManager, graphic );
m_view->Update( &preview );
m_view->Update( &twoPointAsst );
frame()->SetMsgPanel( graphic );
}
@ -1092,11 +1128,12 @@ bool DRAWING_TOOL::drawSegment( const std::string& aTool, int aShape, DRAWSEGMEN
// Init the new item attributes
graphic->SetShape( (STROKE_T) aShape );
graphic->SetWidth( m_lineWidth );
graphic->SetStart( (wxPoint) cursorPos );
graphic->SetEnd( (wxPoint) cursorPos );
graphic->SetLayer( m_frame->GetActiveLayer() );
grid.SetSkipPoint( cursorPos );
twoPointManager.SetOrigin( (wxPoint) cursorPos );
twoPointManager.SetEnd( (wxPoint) cursorPos );
if( !isLocalOriginSet )
m_frame->GetScreen()->m_LocalOrigin = cursorPos;
@ -1105,15 +1142,16 @@ bool DRAWING_TOOL::drawSegment( const std::string& aTool, int aShape, DRAWSEGMEN
m_controls->SetAutoPan( true );
m_controls->CaptureCursor( true );
updateSegmentFromConstructionMgr( twoPointManager, graphic );
started = true;
}
else
{
auto snapItem = dyn_cast<DRAWSEGMENT*>( grid.GetSnapped() );
if( graphic->GetEnd() == graphic->GetStart()
|| ( evt->IsDblClick( BUT_LEFT ) && aShape == S_SEGMENT )
|| snapItem )
if( twoPointManager.GetOrigin() == twoPointManager.GetEnd()
|| ( evt->IsDblClick( BUT_LEFT ) && aShape == S_SEGMENT ) || snapItem )
// User has clicked twice in the same spot
// or clicked on the end of an existing segment (closing a path)
{
@ -1138,23 +1176,31 @@ bool DRAWING_TOOL::drawSegment( const std::string& aTool, int aShape, DRAWSEGMEN
preview.Clear();
break;
}
twoPointManager.SetEnd( cursorPos );
}
else if( evt->IsMotion() )
{
// 45 degree lines
if( direction45 && aShape == S_SEGMENT )
{
const VECTOR2I lineVector( cursorPos - VECTOR2I( graphic->GetStart() ) );
const VECTOR2I lineVector( cursorPos - VECTOR2I( twoPointManager.GetOrigin() ) );
// get a restricted 45/H/V line from the last fixed point to the cursor
auto newEnd = GetVectorSnapped45( lineVector );
graphic->SetEnd( graphic->GetStart() + (wxPoint) newEnd );
m_controls->ForceCursorPosition( true, VECTOR2I( graphic->GetEnd() ) );
m_controls->ForceCursorPosition( true, VECTOR2I( twoPointManager.GetEnd() ) );
twoPointManager.SetEnd( twoPointManager.GetOrigin() + (wxPoint) newEnd );
twoPointManager.SetAngleSnap( true );
}
else
graphic->SetEnd( (wxPoint) cursorPos );
{
twoPointManager.SetEnd( (wxPoint) cursorPos );
twoPointManager.SetAngleSnap( false );
}
updateSegmentFromConstructionMgr( twoPointManager, graphic );
m_view->Update( &preview );
m_view->Update( &twoPointAsst );
if( started )
frame()->SetMsgPanel( graphic );
@ -1186,6 +1232,7 @@ bool DRAWING_TOOL::drawSegment( const std::string& aTool, int aShape, DRAWSEGMEN
if( !isLocalOriginSet ) // reset the relative coordinte if it was not set before
m_frame->GetScreen()->m_LocalOrigin = VECTOR2D( 0, 0 );
m_view->Remove( &twoPointAsst );
m_view->Remove( &preview );
frame()->SetMsgPanel( board() );
m_controls->SetAutoPan( false );