7
mirror of https://gitlab.com/kicad/code/kicad.git synced 2025-04-21 00:21:25 +00:00

Pcbnew: add bezier editing tool

Adds the initial implementation of bezier overlay
(assistant) and geometry manager. This is only
implemented in Pcbnew - the code is common, but
eeschema doesn't currently use it for any shape.

Relates-To: https://gitlab.com/kicad/code/kicad/-/issues/8828
This commit is contained in:
John Beard 2024-09-25 21:41:32 +01:00
parent 336c87b4c0
commit b3248095e8
14 changed files with 796 additions and 2 deletions

View File

@ -459,6 +459,8 @@ set( COMMON_PREVIEW_ITEMS_SRCS
preview_items/anchor_debug.cpp
preview_items/arc_assistant.cpp
preview_items/arc_geom_manager.cpp
preview_items/bezier_assistant.cpp
preview_items/bezier_geom_manager.cpp
preview_items/centreline_rect_item.cpp
preview_items/construction_geom.cpp
preview_items/draw_context.cpp

View File

@ -0,0 +1,110 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2017-2022 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/bezier_assistant.h"
#include <preview_items/draw_context.h>
#include <preview_items/preview_utils.h>
#include <gal/graphics_abstraction_layer.h>
#include <view/view.h>
#include <base_units.h>
#include <trigo.h>
using namespace KIGFX::PREVIEW;
BEZIER_ASSISTANT::BEZIER_ASSISTANT( const BEZIER_GEOM_MANAGER& aManager,
const EDA_IU_SCALE& aIuScale, EDA_UNITS aUnits ) :
EDA_ITEM( NOT_USED ), m_constructMan( aManager ), m_iuScale( aIuScale ), m_units( aUnits )
{
}
const BOX2I BEZIER_ASSISTANT::ViewBBox() const
{
BOX2I tmp;
// no bounding box when no graphic shown
if( m_constructMan.IsReset() )
return tmp;
// this is an edit-time artefact; no reason to try and be smart with the bounding box
// (besides, we can't tell the text extents without a view to know what the scale is)
tmp.SetMaximum();
return tmp;
}
void BEZIER_ASSISTANT::ViewDraw( int aLayer, KIGFX::VIEW* aView ) const
{
KIGFX::GAL& gal = *aView->GetGAL();
// not in a position to draw anything
if( m_constructMan.IsReset() )
return;
gal.ResetTextAttributes();
const VECTOR2I start = m_constructMan.GetStart();
const BEZIER_GEOM_MANAGER::BEZIER_STEPS step = m_constructMan.GetStep();
KIGFX::PREVIEW::DRAW_CONTEXT preview_ctx( *aView );
int dashSize = KiROUND( aView->ToWorld( 12 ) );
if( step >= BEZIER_GEOM_MANAGER::BEZIER_STEPS::SET_CONTROL1 )
{
// Draw the first control point control line
preview_ctx.DrawLineDashed( start, m_constructMan.GetControlC1(), dashSize, dashSize / 2,
false );
}
if( step >= BEZIER_GEOM_MANAGER::BEZIER_STEPS::SET_CONTROL2 )
{
const VECTOR2I c2vec = m_constructMan.GetControlC2() - m_constructMan.GetEnd();
// Draw the second control point control line as a double length line
// centred on the end point
preview_ctx.DrawLineDashed( m_constructMan.GetEnd() - c2vec, m_constructMan.GetControlC2(),
dashSize, dashSize / 2, false );
}
std::vector<wxString> cursorStrings;
if( step >= BEZIER_GEOM_MANAGER::BEZIER_STEPS::SET_END )
{
// Going to need a better way to get a length here
// const int length = m_constructMan.GetBezierLength();
// Have enough points to report a bezier length
// cursorStrings.push_back( DimensionLabel( wxString::FromUTF8( "L" ), 12300000,
// m_iuScale, m_units ) );
}
if( !cursorStrings.empty() )
{
// place the text next to cursor, on opposite side from radius
DrawTextNextToCursor( aView, m_constructMan.GetLastPoint(),
start - m_constructMan.GetLastPoint(), cursorStrings,
aLayer == LAYER_SELECT_OVERLAY );
}
}

View File

@ -0,0 +1,98 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2024 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/bezier_geom_manager.h"
using namespace KIGFX::PREVIEW;
bool BEZIER_GEOM_MANAGER::acceptPoint( const VECTOR2I& aPt )
{
switch( getStep() )
{
case SET_START: return setStart( aPt );
case SET_CONTROL1: return setControlC1( aPt );
case SET_END: return setEnd( aPt );
case SET_CONTROL2: return setControlC2( aPt );
case COMPLETE: return false;
}
return false;
}
VECTOR2I BEZIER_GEOM_MANAGER::GetStart() const
{
return m_start;
}
VECTOR2I BEZIER_GEOM_MANAGER::GetControlC1() const
{
return m_controlC1;
}
VECTOR2I BEZIER_GEOM_MANAGER::GetEnd() const
{
return m_end;
}
VECTOR2I BEZIER_GEOM_MANAGER::GetControlC2() const
{
// The actual bezier C2 point is the reflection over the end point
// so that the cursor will be on the C1 point of the next bezier.
return m_end - ( m_controlC2 - m_end );
}
bool BEZIER_GEOM_MANAGER::setStart( const VECTOR2I& aStart )
{
m_start = aStart;
// Prevents wierd-looking loops if the control points aren't initialized
m_end = aStart;
m_controlC1 = aStart;
m_controlC2 = aStart;
return true;
}
bool BEZIER_GEOM_MANAGER::setControlC1( const VECTOR2I& aControlC1 )
{
m_controlC1 = aControlC1;
m_end = m_controlC1;
m_controlC2 = m_controlC1;
// It's possible to set the control 1 point to the same as the start point
return true;
}
bool BEZIER_GEOM_MANAGER::setEnd( const VECTOR2I& aEnd )
{
m_end = aEnd;
m_controlC2 = m_end;
return m_end != m_start;
}
bool BEZIER_GEOM_MANAGER::setControlC2( const VECTOR2I& aControlC2 )
{
m_controlC2 = aControlC2;
// It's possible to set the control 2 point to the same as the end point
return true;
}

View File

@ -0,0 +1,71 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2024 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
*/
#pragma once
#include <eda_item.h>
#include <preview_items/bezier_geom_manager.h>
#include <layer_ids.h>
namespace KIGFX
{
namespace PREVIEW
{
/**
* Represents an assistant draw when interactively drawing a bezier on a canvas.
*/
class BEZIER_ASSISTANT : public EDA_ITEM
{
public:
BEZIER_ASSISTANT( const BEZIER_GEOM_MANAGER& aManager, const EDA_IU_SCALE& aIuScale,
EDA_UNITS aUnits );
const BOX2I ViewBBox() const override;
void ViewGetLayers( int aLayers[], int& aCount ) const override
{
aLayers[0] = LAYER_SELECT_OVERLAY; // Assistant graphics
aLayers[1] = LAYER_GP_OVERLAY; // Drop shadows
aCount = 2;
}
/**
* Draw the assistance (with reference to the construction manager
*/
void ViewDraw( int aLayer, KIGFX::VIEW* aView ) const override final;
#if defined( DEBUG )
void Show( int x, std::ostream& st ) const override {}
#endif
wxString GetClass() const override { return "BEZIER_ASSISTANT"; }
void SetUnits( EDA_UNITS aUnits ) { m_units = aUnits; }
private:
const BEZIER_GEOM_MANAGER& m_constructMan;
const EDA_IU_SCALE& m_iuScale;
EDA_UNITS m_units;
};
} // namespace PREVIEW
} // namespace KIGFX

View File

@ -0,0 +1,102 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2024 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
*/
#pragma once
#include <preview_items/multistep_geom_manager.h>
#include <geometry/eda_angle.h>
#include <geometry/seg.h>
namespace KIGFX
{
namespace PREVIEW
{
/**
* Manage the construction of a bezier through a series of steps.
*
* See also @ref KIGFX::PREVIEW::ARC_GEOM_MANAGER.
*
* Interfaces are provided to return both arc geometry (can be used to set up real beziers on
* PCBs, for example) as well as important control points for informational overlays.
*/
class BEZIER_GEOM_MANAGER : public MULTISTEP_GEOM_MANAGER
{
public:
BEZIER_GEOM_MANAGER() {}
enum BEZIER_STEPS
{
SET_START = 0, ///< Waiting to lock in the start point
SET_CONTROL1, ///< Waiting to lock in the first control point
SET_END, ///< Waiting to lock in the end point
SET_CONTROL2, ///< Waiting to lock in the second control point
COMPLETE
};
int getMaxStep() const override { return COMPLETE; }
/**
* Get the current step the manager is on (useful when drawing
* something depends on the current state)
*/
BEZIER_STEPS GetStep() const { return static_cast<BEZIER_STEPS>( getStep() ); }
bool acceptPoint( const VECTOR2I& aPt ) override;
/*
* Geometry query interface - used by clients of the manager
*/
///< Get the center point of the arc (valid when state > SET_ORIGIN)
VECTOR2I GetStart() const;
///< Get the coordinates of the arc start
VECTOR2I GetControlC1() const;
VECTOR2I GetControlC2() const;
///< Get the coordinates of the arc end point
VECTOR2I GetEnd() const;
private:
/*
* Point acceptor functions
*/
///< Set the center point of the arc
bool setStart( const VECTOR2I& aOrigin );
bool setControlC1( const VECTOR2I& aControl );
bool setEnd( const VECTOR2I& aCursor );
bool setControlC2( const VECTOR2I& aControl );
/*
* Bezier geometry
*/
VECTOR2I m_start;
VECTOR2I m_controlC1;
VECTOR2I m_end;
VECTOR2I m_controlC2;
};
} // namespace PREVIEW
} // namespace KIGFX

View File

@ -171,6 +171,7 @@ void FOOTPRINT_EDIT_FRAME::doReCreateMenuBar()
placeMenu->Add( PCB_ACTIONS::drawRectangle );
placeMenu->Add( PCB_ACTIONS::drawCircle );
placeMenu->Add( PCB_ACTIONS::drawPolygon );
placeMenu->Add( PCB_ACTIONS::drawBezier );
placeMenu->Add( PCB_ACTIONS::placeReferenceImage );
placeMenu->Add( PCB_ACTIONS::placeText );
placeMenu->Add( PCB_ACTIONS::drawTextBox );

View File

@ -316,6 +316,7 @@ void PCB_EDIT_FRAME::doReCreateMenuBar()
placeMenu->Add( PCB_ACTIONS::drawRectangle );
placeMenu->Add( PCB_ACTIONS::drawCircle );
placeMenu->Add( PCB_ACTIONS::drawPolygon );
placeMenu->Add( PCB_ACTIONS::drawBezier );
placeMenu->Add( PCB_ACTIONS::placeReferenceImage );
placeMenu->Add( PCB_ACTIONS::placeText );
placeMenu->Add( PCB_ACTIONS::drawTextBox );

View File

@ -1044,6 +1044,7 @@ void PCB_EDIT_FRAME::setupUIConditions()
CURRENT_EDIT_TOOL( PCB_ACTIONS::drawCircle );
CURRENT_EDIT_TOOL( PCB_ACTIONS::drawArc );
CURRENT_EDIT_TOOL( PCB_ACTIONS::drawPolygon );
CURRENT_EDIT_TOOL( PCB_ACTIONS::drawBezier );
CURRENT_EDIT_TOOL( PCB_ACTIONS::placeReferenceImage );
CURRENT_EDIT_TOOL( PCB_ACTIONS::placeText );
CURRENT_EDIT_TOOL( PCB_ACTIONS::drawTextBox );

View File

@ -173,6 +173,7 @@ void FOOTPRINT_EDIT_FRAME::ReCreateVToolbar()
&PCB_ACTIONS::drawLeader } );
}
// clang-format off
m_drawToolBar->Add( ACTIONS::selectionTool, ACTION_TOOLBAR::TOGGLE );
m_drawToolBar->AddScaledSeparator( this );
@ -185,6 +186,7 @@ void FOOTPRINT_EDIT_FRAME::ReCreateVToolbar()
m_drawToolBar->Add( PCB_ACTIONS::drawRectangle, ACTION_TOOLBAR::TOGGLE );
m_drawToolBar->Add( PCB_ACTIONS::drawCircle, ACTION_TOOLBAR::TOGGLE );
m_drawToolBar->Add( PCB_ACTIONS::drawPolygon, ACTION_TOOLBAR::TOGGLE );
m_drawToolBar->Add( PCB_ACTIONS::drawBezier, ACTION_TOOLBAR::TOGGLE );
m_drawToolBar->Add( PCB_ACTIONS::placeReferenceImage, ACTION_TOOLBAR::TOGGLE );
m_drawToolBar->Add( PCB_ACTIONS::placeText, ACTION_TOOLBAR::TOGGLE );
m_drawToolBar->Add( PCB_ACTIONS::drawTextBox, ACTION_TOOLBAR::TOGGLE );
@ -196,6 +198,7 @@ void FOOTPRINT_EDIT_FRAME::ReCreateVToolbar()
m_drawToolBar->Add( PCB_ACTIONS::setAnchor, ACTION_TOOLBAR::TOGGLE );
m_drawToolBar->Add( PCB_ACTIONS::gridSetOrigin, ACTION_TOOLBAR::TOGGLE );
m_drawToolBar->Add( ACTIONS::measureTool, ACTION_TOOLBAR::TOGGLE );
// clang-format on
PCB_SELECTION_TOOL* selTool = m_toolManager->GetTool<PCB_SELECTION_TOOL>();

View File

@ -356,6 +356,7 @@ void PCB_EDIT_FRAME::ReCreateVToolbar()
&PCB_ACTIONS::tuneSkew } );
}
// clang-format off
m_drawToolBar->Add( ACTIONS::selectionTool, ACTION_TOOLBAR::TOGGLE );
m_drawToolBar->Add( PCB_ACTIONS::localRatsnestTool, ACTION_TOOLBAR::TOGGLE );
@ -373,6 +374,7 @@ void PCB_EDIT_FRAME::ReCreateVToolbar()
m_drawToolBar->Add( PCB_ACTIONS::drawRectangle, ACTION_TOOLBAR::TOGGLE );
m_drawToolBar->Add( PCB_ACTIONS::drawCircle, ACTION_TOOLBAR::TOGGLE );
m_drawToolBar->Add( PCB_ACTIONS::drawPolygon, ACTION_TOOLBAR::TOGGLE );
m_drawToolBar->Add( PCB_ACTIONS::drawBezier, ACTION_TOOLBAR::TOGGLE );
m_drawToolBar->Add( PCB_ACTIONS::placeReferenceImage, ACTION_TOOLBAR::TOGGLE );
m_drawToolBar->Add( PCB_ACTIONS::placeText, ACTION_TOOLBAR::TOGGLE );
m_drawToolBar->Add( PCB_ACTIONS::drawTextBox, ACTION_TOOLBAR::TOGGLE );
@ -383,6 +385,7 @@ void PCB_EDIT_FRAME::ReCreateVToolbar()
m_drawToolBar->AddScaledSeparator( this );
m_drawToolBar->AddGroup( originGroup, ACTION_TOOLBAR::TOGGLE );
m_drawToolBar->Add( ACTIONS::measureTool, ACTION_TOOLBAR::TOGGLE );
// clang-format on
PCB_SELECTION_TOOL* selTool = m_toolManager->GetTool<PCB_SELECTION_TOOL>();

View File

@ -37,6 +37,8 @@
#include <geometry/geometry_utils.h>
#include <geometry/shape_segment.h>
#include <import_gfx/dialog_import_graphics.h>
#include <preview_items/arc_assistant.h>
#include <preview_items/bezier_assistant.h>
#include <preview_items/two_point_assistant.h>
#include <preview_items/two_point_geom_manager.h>
#include <ratsnest/ratsnest_data.h>
@ -72,7 +74,6 @@
#include <pcb_tablecell.h>
#include <pcb_dimension.h>
#include <pcbnew_id.h>
#include <preview_items/arc_assistant.h>
#include <scoped_set_reset.h>
#include <string_utils.h>
#include <zone.h>
@ -543,6 +544,45 @@ int DRAWING_TOOL::DrawArc( const TOOL_EVENT& aEvent )
}
int DRAWING_TOOL::DrawBezier( const TOOL_EVENT& aEvent )
{
if( m_isFootprintEditor && !m_frame->GetModel() )
return 0;
if( m_inDrawingTool )
return 0;
REENTRANCY_GUARD guard( &m_inDrawingTool );
BOARD_COMMIT commit( m_frame );
SCOPED_DRAW_MODE scopedDrawMode( m_mode, MODE::BEZIER );
OPT_VECTOR2I startingPoint, startingC1;
m_frame->PushTool( aEvent );
Activate();
if( aEvent.HasPosition() )
startingPoint = aEvent.Position();
while( std::unique_ptr<PCB_SHAPE> bezier = drawOneBezier( aEvent, startingPoint, startingC1 ) )
{
PCB_SHAPE& bezierRef = *bezier;
commit.Add( bezier.release() );
commit.Push( _( "Draw Bezier" ) );
startingPoint = bezierRef.GetEnd();
// Mirror the control point across the starting point to get
// a tangent control point
startingC1 = *startingPoint - ( bezierRef.GetBezierC2() - *startingPoint );
m_toolMgr->RunAction<EDA_ITEM*>( PCB_ACTIONS::selectItem, &bezierRef );
}
return 0;
}
int DRAWING_TOOL::PlaceReferenceImage( const TOOL_EVENT& aEvent )
{
if( m_inDrawingTool )
@ -2746,6 +2786,333 @@ bool DRAWING_TOOL::drawArc( const TOOL_EVENT& aTool, PCB_SHAPE** aGraphic,
}
/**
* Update a bezier PCB_SHAPE from the current state of a Bezier Geometry Manager.
*/
static void updateBezierFromConstructionMgr( const KIGFX::PREVIEW::BEZIER_GEOM_MANAGER& aMgr,
PCB_SHAPE& aBezier )
{
VECTOR2I vec = aMgr.GetStart();
aBezier.SetStart( vec );
aBezier.SetBezierC1( aMgr.GetControlC1() );
aBezier.SetEnd( aMgr.GetEnd() );
aBezier.SetBezierC2( aMgr.GetControlC2() );
// Need this for the length preview to work
aBezier.RebuildBezierToSegmentsPointsList( ARC_HIGH_DEF );
}
std::unique_ptr<PCB_SHAPE> DRAWING_TOOL::drawOneBezier( const TOOL_EVENT& aTool,
const OPT_VECTOR2I& aStartingPoint,
const OPT_VECTOR2I& aStartingControl1Point )
{
std::unique_ptr<PCB_SHAPE> bezier = std::make_unique<PCB_SHAPE>( m_frame->GetModel() );
bezier->SetShape( SHAPE_T::BEZIER );
bezier->SetFlags( IS_NEW );
if( m_layer != m_frame->GetActiveLayer() )
{
m_layer = m_frame->GetActiveLayer();
m_stroke.SetWidth( m_frame->GetDesignSettings().GetLineThickness( m_layer ) );
m_stroke.SetLineStyle( LINE_STYLE::DEFAULT );
m_stroke.SetColor( COLOR4D::UNSPECIFIED );
}
// Turn shapes on if they are off, so that the created object will be visible after completion
m_frame->SetObjectVisible( LAYER_SHAPES );
// Arc geometric construction manager
KIGFX::PREVIEW::BEZIER_GEOM_MANAGER bezierManager;
// Arc drawing assistant overlay
KIGFX::PREVIEW::BEZIER_ASSISTANT bezierAsst( bezierManager, pcbIUScale,
m_frame->GetUserUnits() );
// Add a VIEW_GROUP that serves as a preview for the new item
PCB_SELECTION preview;
m_view->Add( &preview );
m_view->Add( &bezierAsst );
PCB_GRID_HELPER grid( m_toolMgr, m_frame->GetMagneticItemsSettings() );
auto setCursor = [&]()
{
m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::PENCIL );
};
auto cleanup = [&]()
{
preview.Clear();
bezier.reset();
};
m_controls->ShowCursor( true );
m_controls->ForceCursorPosition( false );
// Set initial cursor
setCursor();
// We need to know when the bezier manager has started actually adding points
// but which point it started with depends on whether we were passed a starting point
KIGFX::PREVIEW::BEZIER_GEOM_MANAGER::BEZIER_STEPS startedAfterStep =
KIGFX::PREVIEW::BEZIER_GEOM_MANAGER::SET_START;
const auto started = [&]()
{
return bezierManager.GetStep() > startedAfterStep;
};
bool cancelled = false;
bool priming = false;
m_toolMgr->PostAction( ACTIONS::refreshPreview );
// Load in one or two points if they were passed in
if( aStartingPoint )
{
priming = true;
if( aStartingControl1Point )
{
bezierManager.AddPoint( *aStartingPoint, true );
bezierManager.AddPoint( *aStartingControl1Point, true );
m_toolMgr->PrimeTool( *aStartingControl1Point );
startedAfterStep = KIGFX::PREVIEW::BEZIER_GEOM_MANAGER::SET_END;
}
else
{
m_toolMgr->PrimeTool( *aStartingPoint );
startedAfterStep = KIGFX::PREVIEW::BEZIER_GEOM_MANAGER::SET_CONTROL1;
}
}
// Main loop: keep receiving events
while( TOOL_EVENT* evt = Wait() )
{
if( started() )
m_frame->SetMsgPanel( bezier.get() );
setCursor();
bezier->SetLayer( m_layer );
grid.SetSnap( !evt->Modifier( MD_SHIFT ) );
grid.SetUseGrid( getView()->GetGAL()->GetGridSnapping() && !evt->DisableGridSnapping() );
VECTOR2I cursorPos = GetClampedCoords(
grid.BestSnapAnchor( m_controls->GetMousePosition(), bezier.get(), GRID_GRAPHICS ),
COORDS_PADDING );
m_controls->ForceCursorPosition( true, cursorPos );
if( evt->IsCancelInteractive() || ( started() && evt->IsAction( &ACTIONS::undo ) ) )
{
cleanup();
if( !started() )
{
// We've handled the cancel event. Don't cancel other tools
evt->SetPassEvent( false );
m_frame->PopTool( aTool );
cancelled = true;
}
break;
}
else if( evt->IsActivate() )
{
if( evt->IsPointEditor() )
{
// don't exit (the point editor runs in the background)
}
else if( evt->IsMoveTool() )
{
cleanup();
// leave ourselves on the stack so we come back after the move
cancelled = true;
break;
}
else
{
cleanup();
m_frame->PopTool( aTool );
cancelled = true;
break;
}
}
else if( evt->IsClick( BUT_LEFT ) )
{
if( !started() )
{
m_toolMgr->RunAction( PCB_ACTIONS::selectionClear );
m_controls->SetAutoPan( true );
m_controls->CaptureCursor( true );
// Init the new item attributes
// (non-geometric, those are handled by the manager)
bezier->SetShape( SHAPE_T::BEZIER );
bezier->SetStroke( m_stroke );
if( !m_view->IsLayerVisible( m_layer ) )
{
m_frame->GetAppearancePanel()->SetLayerVisible( m_layer, true );
m_frame->GetCanvas()->Refresh();
}
frame()->SetMsgPanel( bezier.get() );
}
if( !priming )
bezierManager.AddPoint( cursorPos, true );
else
priming = false;
if( bezierManager.GetStep() == KIGFX::PREVIEW::BEZIER_GEOM_MANAGER::SET_END )
{
preview.Add( bezier.get() );
}
}
else if( evt->IsAction( &PCB_ACTIONS::deleteLastPoint ) )
{
bezierManager.RemoveLastPoint();
if( bezierManager.GetStep() < KIGFX::PREVIEW::BEZIER_GEOM_MANAGER::SET_END )
{
preview.Remove( bezier.get() );
}
}
else if( evt->IsMotion() )
{
// set angle snap
// bezierManager.SetAngleSnap( Is45Limited() );
// update, but don't step the manager state
bezierManager.AddPoint( cursorPos, false );
}
else if( evt->IsAction( &PCB_ACTIONS::layerChanged ) )
{
if( m_layer != m_frame->GetActiveLayer() )
{
m_layer = m_frame->GetActiveLayer();
m_stroke.SetWidth( m_frame->GetDesignSettings().GetLineThickness( m_layer ) );
m_stroke.SetLineStyle( LINE_STYLE::DEFAULT );
m_stroke.SetColor( COLOR4D::UNSPECIFIED );
}
if( bezier )
{
if( !m_view->IsLayerVisible( m_layer ) )
{
m_frame->GetAppearancePanel()->SetLayerVisible( m_layer, true );
m_frame->GetCanvas()->Refresh();
}
bezier->SetLayer( m_layer );
bezier->SetStroke( m_stroke );
m_view->Update( &preview );
frame()->SetMsgPanel( bezier.get() );
}
else
{
evt->SetPassEvent();
}
}
else if( evt->IsAction( &PCB_ACTIONS::properties ) )
{
// Don't show the edit panel if we can't represent the arc with it
if( ( bezierManager.GetStep() >= KIGFX::PREVIEW::BEZIER_GEOM_MANAGER::SET_END ) )
{
frame()->OnEditItemRequest( bezier.get() );
m_view->Update( &preview );
frame()->SetMsgPanel( bezier.get() );
break;
}
else
{
evt->SetPassEvent();
}
}
else if( evt->IsClick( BUT_RIGHT ) )
{
if( !bezier )
m_toolMgr->VetoContextMenuMouseWarp();
m_menu->ShowContextMenu( selection() );
}
else if( evt->IsAction( &PCB_ACTIONS::incWidth ) )
{
m_stroke.SetWidth( m_stroke.GetWidth() + WIDTH_STEP );
if( bezier )
{
bezier->SetStroke( m_stroke );
m_view->Update( &preview );
frame()->SetMsgPanel( bezier.get() );
}
}
else if( evt->IsAction( &PCB_ACTIONS::decWidth ) )
{
if( (unsigned) m_stroke.GetWidth() > WIDTH_STEP )
{
m_stroke.SetWidth( m_stroke.GetWidth() - WIDTH_STEP );
if( bezier )
{
bezier->SetStroke( m_stroke );
m_view->Update( &preview );
frame()->SetMsgPanel( bezier.get() );
}
}
}
else if( evt->IsAction( &ACTIONS::updateUnits ) )
{
bezierAsst.SetUnits( frame()->GetUserUnits() );
m_view->Update( &bezierAsst );
evt->SetPassEvent();
}
else if( started()
&& ( ZONE_FILLER_TOOL::IsZoneFillAction( evt )
|| evt->IsAction( &ACTIONS::redo ) ) )
{
wxBell();
}
else
{
evt->SetPassEvent();
}
if( bezierManager.IsComplete() )
{
break;
}
else if( bezierManager.HasGeometryChanged() )
{
updateBezierFromConstructionMgr( bezierManager, *bezier );
m_view->Update( &preview );
m_view->Update( &bezierAsst );
// Once we are receiving end points, we can show the bezier in the preview
if( bezierManager.GetStep() >= KIGFX::PREVIEW::BEZIER_GEOM_MANAGER::SET_END )
frame()->SetMsgPanel( bezier.get() );
else
frame()->SetMsgPanel( board() );
}
}
preview.Remove( bezier.get() );
m_view->Remove( &bezierAsst );
m_view->Remove( &preview );
if( selection().Empty() )
m_frame->SetMsgPanel( board() );
m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::ARROW );
m_controls->SetAutoPan( false );
m_controls->CaptureCursor( false );
m_controls->ForceCursorPosition( false );
if( cancelled )
return nullptr;
return bezier;
};
bool DRAWING_TOOL::getSourceZoneForAction( ZONE_MODE aMode, ZONE** aZone )
{
bool clearSelection = false;
@ -3681,7 +4048,7 @@ const unsigned int DRAWING_TOOL::WIDTH_STEP = pcbIUScale.mmToIU( 0.1 );
void DRAWING_TOOL::setTransitions()
{
// clang-format off
Go( &DRAWING_TOOL::PlaceStackup, PCB_ACTIONS::placeStackup.MakeEvent() );
Go( &DRAWING_TOOL::PlaceCharacteristics, PCB_ACTIONS::placeCharacteristics.MakeEvent() );
Go( &DRAWING_TOOL::DrawLine, PCB_ACTIONS::drawLine.MakeEvent() );
@ -3689,6 +4056,7 @@ void DRAWING_TOOL::setTransitions()
Go( &DRAWING_TOOL::DrawRectangle, PCB_ACTIONS::drawRectangle.MakeEvent() );
Go( &DRAWING_TOOL::DrawCircle, PCB_ACTIONS::drawCircle.MakeEvent() );
Go( &DRAWING_TOOL::DrawArc, PCB_ACTIONS::drawArc.MakeEvent() );
Go( &DRAWING_TOOL::DrawBezier, PCB_ACTIONS::drawBezier.MakeEvent() );
Go( &DRAWING_TOOL::DrawDimension, PCB_ACTIONS::drawAlignedDimension.MakeEvent() );
Go( &DRAWING_TOOL::DrawDimension, PCB_ACTIONS::drawOrthogonalDimension.MakeEvent() );
Go( &DRAWING_TOOL::DrawDimension, PCB_ACTIONS::drawCenterDimension.MakeEvent() );
@ -3711,4 +4079,5 @@ void DRAWING_TOOL::setTransitions()
Go( &DRAWING_TOOL::PlaceTuningPattern, PCB_ACTIONS::tuneSingleTrack.MakeEvent() );
Go( &DRAWING_TOOL::PlaceTuningPattern, PCB_ACTIONS::tuneDiffPair.MakeEvent() );
Go( &DRAWING_TOOL::PlaceTuningPattern, PCB_ACTIONS::tuneSkew.MakeEvent() );
// clang-format on
}

View File

@ -71,6 +71,7 @@ public:
RECTANGLE,
CIRCLE,
ARC,
BEZIER,
IMAGE,
TEXT,
ANCHOR,
@ -145,6 +146,13 @@ public:
*/
int DrawArc( const TOOL_EVENT& aEvent );
/**
* Start interactively drawing a bezier curve.
*
* An interactive geometry manager will handle adding/editing the control points.
*/
int DrawBezier( const TOOL_EVENT& aEvent );
/**
* Display a dialog that allows one to select a reference image and then decide where to
* place the image in the editor.
@ -258,6 +266,22 @@ private:
bool drawArc( const TOOL_EVENT& aTool, PCB_SHAPE** aGraphic,
std::optional<VECTOR2D> aStartingPoint );
/**
* Draw a bezier curve.
*
* @param aTool is the event that triggered the drawing.
* @param aStartingPoint is the starting point of the curve (e.g. the end point of the
* previous curve).
* @param aStartingControl1Point is the previous control point of the curve (which can
* be used to create a smooth transition between two curves).
*
* @return A new PCB_SHAPE object representing the bezier curve, or nullptr if
* the tool was canceled.
*/
std::unique_ptr<PCB_SHAPE> drawOneBezier( const TOOL_EVENT& aTool,
const OPT_VECTOR2I& aStartingPoint,
const OPT_VECTOR2I& aStartingControl1Point );
/**
* Draw a polygon, that is added as a zone or a keepout area.
*

View File

@ -142,6 +142,14 @@ TOOL_ACTION PCB_ACTIONS::drawArc( TOOL_ACTION_ARGS()
.Icon( BITMAPS::add_arc )
.Flags( AF_ACTIVATE ) );
TOOL_ACTION PCB_ACTIONS::drawBezier( TOOL_ACTION_ARGS()
.Name( "pcbnew.InteractiveDrawing.bezier" )
.Scope( AS_GLOBAL )
.DefaultHotkey( MD_SHIFT + MD_CTRL + 'B' )
.FriendlyName( _( "Draw Bezier Curve" ) )
.Icon( BITMAPS::add_bezier )
.Flags( AF_ACTIVATE ) );
TOOL_ACTION PCB_ACTIONS::placeCharacteristics( TOOL_ACTION_ARGS()
.Name( "pcbnew.InteractiveDrawing.placeCharacteristics" )
.Scope( AS_GLOBAL )

View File

@ -203,6 +203,7 @@ public:
static TOOL_ACTION drawRectangle;
static TOOL_ACTION drawCircle;
static TOOL_ACTION drawArc;
static TOOL_ACTION drawBezier;
static TOOL_ACTION placeReferenceImage;
static TOOL_ACTION placeText;
static TOOL_ACTION drawTextBox;