Rework zone creation in GAL

The zone creation in DRAWING_TOOL was a complex event loop whici
simultaneously managed the event handling, construction of the preview
polygon and the creation and commiting of the zone, all in the same
scope. This has been broken into several pieces:

* POLYGON_ITEM preview item, used to preview the polygon in progress
* POLYGON_GEOM_MANAGER is a class that collects points from user input
  and used them to describe a polygon's geometry, including calculation
  of 45-degree constrained leader lines
* ZONE_CREATE_HELPER is a class which deals with creating zones based on
  geometry from a geometry manager and parameters from the DRAWING_TOOL
* The (much simpler) event loop in DRAWING_TOOL drives the
  POLYGON_GEOM_MANAGER. With a minor refactor, this loop can be reused
  in future for other polygonal tools if wanted.

The polygon preview now has a translucent fill which makes it easier to
visualise the zone.

This also adds the Close Zone Outline and Delete Last Corner actions as
part of the new event loop.

Fixes: lp:1663885
* https://bugs.launchpad.net/kicad/+bug/1663885

Fixes: lp:1667885
* https://bugs.launchpad.net/kicad/+bug/1667885
This commit is contained in:
John Beard 2017-02-27 01:45:52 +08:00 committed by Maciej Suminski
parent 1fd503bf58
commit 9fe780f1b3
10 changed files with 983 additions and 290 deletions

View File

@ -193,6 +193,8 @@ set( COMMON_PREVIEW_ITEMS_SRCS
preview_items/simple_overlay_item.cpp preview_items/simple_overlay_item.cpp
preview_items/selection_area.cpp preview_items/selection_area.cpp
preview_items/bright_box.cpp preview_items/bright_box.cpp
preview_items/polygon_geom_manager.cpp
preview_items/polygon_item.cpp
) )
set( COMMON_SRCS set( COMMON_SRCS

View File

@ -0,0 +1,158 @@
/*
* 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/polygon_geom_manager.h>
#include <geometry/direction45.h>
POLYGON_GEOM_MANAGER::POLYGON_GEOM_MANAGER( CLIENT& aClient ):
m_client( aClient ),
m_leaderMode( LEADER_MODE::DIRECT )
{}
void POLYGON_GEOM_MANAGER::AddPoint( const VECTOR2I& aPt )
{
// if this is the first point, make sure the client is happy
// for us to continue
if( !IsPolygonInProgress() && !m_client.OnFirstPoint() )
return;
if( m_leaderPts.size() > 1 )
{
// there are enough leader points - the next
// locked-in point is the end of the first leader
// segment
m_lockedPoints.push_back( m_leaderPts[1] );
}
else
{
// no leader lines, directly add the cursor
m_lockedPoints.push_back( aPt );
}
m_client.OnGeometryChange( *this );
}
void POLYGON_GEOM_MANAGER::SetFinished()
{
m_client.OnComplete( *this );
}
void POLYGON_GEOM_MANAGER::SetLeaderMode( LEADER_MODE aMode )
{
m_leaderMode = aMode;
}
void POLYGON_GEOM_MANAGER::SetCursorPosition( const VECTOR2I& aPos )
{
updateLeaderPoints( aPos );
}
bool POLYGON_GEOM_MANAGER::IsPolygonInProgress() const
{
return m_lockedPoints.size() > 0;
}
bool POLYGON_GEOM_MANAGER::NewPointClosesOutline( const VECTOR2I& aPt ) const
{
return m_lockedPoints.size() && m_lockedPoints[0] == aPt;
}
void POLYGON_GEOM_MANAGER::DeleteLastCorner()
{
if( m_lockedPoints.size() > 0 )
{
m_lockedPoints.pop_back();
}
// update the new last segment (was previously
// locked in), reusing last constraints
if( m_lockedPoints.size() > 0 )
{
updateLeaderPoints( m_leaderPts.back() );
}
m_client.OnGeometryChange( *this );
}
void POLYGON_GEOM_MANAGER::Reset()
{
m_lockedPoints.clear();
m_leaderPts.clear();
m_client.OnGeometryChange( *this );
}
void POLYGON_GEOM_MANAGER::updateLeaderPoints( const VECTOR2I& aEndPoint )
{
SHAPE_LINE_CHAIN newChain;
if( m_leaderMode == LEADER_MODE::DEG45 )
{
// get a restricted 45/H/V line from the last fixed point to the cursor
DIRECTION_45 direction( m_lockedPoints.back() - aEndPoint );
newChain = direction.BuildInitialTrace( m_lockedPoints.back(), aEndPoint );
// Can also add chain back to start, but this rearely produces
// usable result
//DIRECTION_45 directionToStart( aEndPoint - m_lockedPoints.front() );
//newChain.Append( directionToStart.BuildInitialTrace( aEndPoint, m_lockedPoints.front() ) );
}
else
{
// direct segment
newChain = SHAPE_LINE_CHAIN( m_lockedPoints.back(), aEndPoint );
}
// rebuild leader point list from the chain
m_leaderPts.clear();
for( int i = 0; i < newChain.PointCount(); ++i )
{
m_leaderPts.push_back( newChain.Point( i ) );
}
m_client.OnGeometryChange( *this );
}
const std::vector<VECTOR2I>& POLYGON_GEOM_MANAGER::GetLockedInPoints() const
{
return m_lockedPoints;
}
const std::vector<VECTOR2I>& POLYGON_GEOM_MANAGER::GetLeaderLinePoints() const
{
return m_leaderPts;
}

View File

@ -0,0 +1,76 @@
/*
* 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/polygon_item.h>
#include <preview_items/preview_utils.h>
#include <gal/graphics_abstraction_layer.h>
#include <view/view.h>
using namespace KIGFX::PREVIEW;
POLYGON_ITEM::POLYGON_ITEM():
SIMPLE_OVERLAY_ITEM()
{
}
void POLYGON_ITEM::SetPoints( const std::vector<VECTOR2I>& aLockedPts,
const std::vector<VECTOR2I>& aLeaderPts )
{
m_lockedChain.Clear();
m_leaderChain.Clear();
m_polyfill.RemoveAllContours();
m_polyfill.NewOutline();
for( auto& pt: aLockedPts )
{
m_lockedChain.Append( pt, false );
m_polyfill.Append( pt );
}
for( auto& pt: aLeaderPts )
{
m_leaderChain.Append( pt, false );
m_polyfill.Append( pt );
}
}
void POLYGON_ITEM::drawPreviewShape( KIGFX::GAL& aGal ) const
{
aGal.DrawPolyline( m_lockedChain );
aGal.DrawPolygon( m_polyfill );
// draw the leader line in a different color
aGal.SetStrokeColor( PreviewOverlayDefaultColor() );
aGal.DrawPolyline( m_leaderChain );
}
const BOX2I POLYGON_ITEM::ViewBBox() const
{
return m_polyfill.BBox();
}

View File

@ -0,0 +1,159 @@
/*
* 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_POLYGON_GEOM_MANAGER__H_
#define PREVIEW_POLYGON_GEOM_MANAGER__H_
#include <vector>
#include <math/vector2d.h>
/**
* Class that handles the drawing of a polygon, including
* management of last corner deletion and drawing of leader lines
* with various constraints (eg 45 deg only).
*
* This class handles only the geometry of the process.
*/
class POLYGON_GEOM_MANAGER
{
public:
/**
* "Listener" interface for a class that wants to be updated about
* polygon geometry changes
*/
class CLIENT
{
public:
/**
* Called before the first point is added - clients can do
* initialisation here, and can veto the start of the process
* (eg if user cancels a dialog)
*
* @return false to veto start of new polygon
*/
virtual bool OnFirstPoint() = 0;
///> Sent when the polygon geometry changes
virtual void OnGeometryChange( const POLYGON_GEOM_MANAGER& aMgr ) = 0;
///> Called when the polygon is complete
virtual void OnComplete( const POLYGON_GEOM_MANAGER& aMgr ) = 0;
};
/**
* The kind of the leader line
*/
enum class LEADER_MODE
{
DIRECT, ///> Unconstrained point-to-point
DEG45, ///> 45 Degree only
};
/**
* @param the client to pass the results onto
*/
POLYGON_GEOM_MANAGER( CLIENT& aClient );
/**
* Lock in a polygon point.
*/
void AddPoint( const VECTOR2I& aPt );
/**
* Mark the polygon finished and update the client
*/
void SetFinished();
/**
* Clear the manager state and start again
*/
void Reset();
/**
* Set the leader mode to use when calculating the leader/returner
* lines
*/
void SetLeaderMode( LEADER_MODE aMode );
/**
* Set the current cursor position
*/
void SetCursorPosition( const VECTOR2I& aPos );
/**
* @return true if the polygon in "in progress", i.e. it has at least
* one locked-in point
*/
bool IsPolygonInProgress() const;
/**
* @return true if locking in the given point would close the
* current polygon
*/
bool NewPointClosesOutline( const VECTOR2I& aPt ) const;
/**
* Remove the last-added point from the polygon
*/
void DeleteLastCorner();
/* =================================================================
* Interfaces for users of the geometry
*/
/**
* Get the "locked-in" points that describe the polygon itself
*/
const std::vector<VECTOR2I>& GetLockedInPoints() const;
/**
* Get the points comprising the leader line (the line from the
* last locked-in point to the current cursor position
*
* How this is drawn will depend on the LEADER_MODE
*/
const std::vector<VECTOR2I>& GetLeaderLinePoints() const;
private:
/**
* Update the leader line points based on a new endpoint (probably
* a cursor position)
*/
void updateLeaderPoints( const VECTOR2I& aEndPoint );
///> The "user" of the polygon data that is informed when the geometry changes
CLIENT& m_client;
///> The current mode of the leader line
LEADER_MODE m_leaderMode;
///> Point that have been "locked in"
std::vector<VECTOR2I> m_lockedPoints;
///> Points in the temporary "leader" line(s)
std::vector<VECTOR2I> m_leaderPts;
};
#endif // PREVIEW_POLYGON_GEOM_MANAGER__H_

View File

@ -0,0 +1,82 @@
/*
* This program source code file is part of KICAD, a free EDA CAD application.
*
* Copyright (C) 2017 Kicad Developers, see change_log.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_POLYGON_ITEM__H_
#define PREVIEW_POLYGON_ITEM__H_
#include <preview_items/simple_overlay_item.h>
#include <geometry/shape_poly_set.h>
#include <geometry/shape_line_chain.h>
namespace KIGFX
{
class GAL;
namespace PREVIEW
{
/**
* Class POLYGON_ITEM
*
* A preview item which shows an in-progress polygon, which
* can be used for zone outlines, etc
*/
class POLYGON_ITEM : public SIMPLE_OVERLAY_ITEM
{
public:
POLYGON_ITEM();
///> Gets the bounding box of the polygon
virtual const BOX2I ViewBBox() const override;
/**
* Set the polygon points
*
* @param locked in points - the "fixed point" of the outline
* @param leader line points - the lines from the last fixed point to
* another point, eg the cursor.
*/
void SetPoints( const std::vector<VECTOR2I>& aLockedInPts,
const std::vector<VECTOR2I>& aLeaderPts );
private:
///> Draw rectangle and centre line onto GAL
void drawPreviewShape( KIGFX::GAL& aGal ) const override;
///> complete polyline of locked in and leader points
SHAPE_LINE_CHAIN m_lockedChain, m_leaderChain;
///> polygon fill
SHAPE_POLY_SET m_polyfill;
};
} // PREVIEW
} // KIGFX
#endif // PREVIEW_POLYGON_ITEM__H_

View File

@ -316,6 +316,7 @@ set( PCBNEW_CLASS_SRCS
tools/pad_tool.cpp tools/pad_tool.cpp
tools/picker_tool.cpp tools/picker_tool.cpp
tools/zoom_tool.cpp tools/zoom_tool.cpp
tools/zone_create_helper.cpp
tools/tools_common.cpp tools/tools_common.cpp
tools/tool_event_utils.cpp tools/tool_event_utils.cpp

View File

@ -46,6 +46,7 @@
#include <scoped_set_reset.h> #include <scoped_set_reset.h>
#include <bitmaps.h> #include <bitmaps.h>
#include <hotkeys.h> #include <hotkeys.h>
#include <painter.h>
#include <preview_items/arc_assistant.h> #include <preview_items/arc_assistant.h>
@ -58,6 +59,7 @@
#include <tools/selection_tool.h> #include <tools/selection_tool.h>
#include <tools/tool_event_utils.h> #include <tools/tool_event_utils.h>
#include <tools/zone_create_helper.h>
using SCOPED_DRAW_MODE = SCOPED_SET_RESET<DRAWING_TOOL::MODE>; using SCOPED_DRAW_MODE = SCOPED_SET_RESET<DRAWING_TOOL::MODE>;
@ -130,6 +132,11 @@ static TOOL_ACTION deleteLastPoint( "pcbnew.InteractiveDrawing.deleteLastPoint",
_( "Delete Last Point" ), _( "Delete the last point added to the current item" ), _( "Delete Last Point" ), _( "Delete the last point added to the current item" ),
undo_xpm ); undo_xpm );
static TOOL_ACTION closeZoneOutline( "pcbnew.InteractiveDrawing.closeZoneOutline",
AS_CONTEXT, 0,
_( "Close Zone Outline" ), _( "Close the outline of a zone in progress" ),
checked_ok_xpm );
DRAWING_TOOL::DRAWING_TOOL() : DRAWING_TOOL::DRAWING_TOOL() :
PCB_TOOL( "pcbnew.InteractiveDrawing" ), PCB_TOOL( "pcbnew.InteractiveDrawing" ),
@ -152,8 +159,14 @@ bool DRAWING_TOOL::Init()
return m_mode != MODE::NONE; return m_mode != MODE::NONE;
}; };
// some interactive drawing tools can undo the last point
auto canUndoPoint = [ this ] ( const SELECTION& aSel ) { auto canUndoPoint = [ this ] ( const SELECTION& aSel ) {
return m_mode == MODE::ARC; return m_mode == MODE::ARC || m_mode == MODE::ZONE;
};
// functor for zone-only actions
auto zoneActiveFunctor = [this ] ( const SELECTION& aSel ) {
return m_mode == MODE::ZONE;
}; };
auto& ctxMenu = m_menu.GetMenu(); auto& ctxMenu = m_menu.GetMenu();
@ -161,12 +174,16 @@ bool DRAWING_TOOL::Init()
// cancel current toool goes in main context menu at the top if present // cancel current toool goes in main context menu at the top if present
ctxMenu.AddItem( ACTIONS::cancelInteractive, activeToolFunctor, 1000 ); ctxMenu.AddItem( ACTIONS::cancelInteractive, activeToolFunctor, 1000 );
// some interactive drawing tools can undo the last point // tool-specific actions
ctxMenu.AddItem( closeZoneOutline, zoneActiveFunctor, 1000 );
ctxMenu.AddItem( deleteLastPoint, canUndoPoint, 1000 ); ctxMenu.AddItem( deleteLastPoint, canUndoPoint, 1000 );
ctxMenu.AddSeparator( activeToolFunctor, 1000 ); ctxMenu.AddSeparator( activeToolFunctor, 1000 );
// Drawing type-specific options will be added by the PCB control tool // Type-specific sub-menus will be added for us by other tools
// For example, zone fill/unfill is provided by the PCB control tool
// Finally, add the standard zoom/grid items
m_menu.AddStandardSubMenus( *getEditFrame<PCB_BASE_FRAME>() ); m_menu.AddStandardSubMenus( *getEditFrame<PCB_BASE_FRAME>() );
return true; return true;
@ -431,7 +448,6 @@ int DRAWING_TOOL::PlaceText( const TOOL_EVENT& aEvent )
text = NULL; text = NULL;
} }
} }
else if( text && evt->IsMotion() ) else if( text && evt->IsMotion() )
{ {
text->SetPosition( wxPoint( cursorPos.x, cursorPos.y ) ); text->SetPosition( wxPoint( cursorPos.x, cursorPos.y ) );
@ -1192,62 +1208,6 @@ bool DRAWING_TOOL::drawArc( DRAWSEGMENT*& aGraphic )
} }
std::unique_ptr<ZONE_CONTAINER> DRAWING_TOOL::createNewZone( bool aKeepout )
{
const auto& board = *getModel<BOARD>();
// Get the current default settings for zones
ZONE_SETTINGS zoneInfo = m_frame->GetZoneSettings();
zoneInfo.m_CurrentZone_Layer = m_frame->GetScreen()->m_Active_Layer;
zoneInfo.m_NetcodeSelection = board.GetHighLightNetCode();
zoneInfo.SetIsKeepout( aKeepout );
m_controls->SetAutoPan( true );
m_controls->CaptureCursor( true );
// Show options dialog
ZONE_EDIT_T dialogResult;
if( aKeepout )
dialogResult = InvokeKeepoutAreaEditor( m_frame, &zoneInfo );
else
{
if( IsCopperLayer( zoneInfo.m_CurrentZone_Layer ) )
dialogResult = InvokeCopperZonesEditor( m_frame, &zoneInfo );
else
dialogResult = InvokeNonCopperZonesEditor( m_frame, NULL, &zoneInfo );
}
if( dialogResult == ZONE_ABORT )
{
m_controls->SetAutoPan( false );
m_controls->CaptureCursor( false );
return nullptr;
}
auto newZone = std::make_unique<ZONE_CONTAINER>( m_board );
// Apply the selected settings
zoneInfo.ExportSetting( *newZone );
return newZone;
}
std::unique_ptr<ZONE_CONTAINER> DRAWING_TOOL::createZoneFromExisting(
const ZONE_CONTAINER& aSrcZone )
{
auto newZone = std::make_unique<ZONE_CONTAINER>( m_board );
ZONE_SETTINGS zoneSettings;
zoneSettings << aSrcZone;
zoneSettings.ExportSetting( *newZone );
return newZone;
}
bool DRAWING_TOOL::getSourceZoneForAction( ZONE_MODE aMode, ZONE_CONTAINER*& aZone ) bool DRAWING_TOOL::getSourceZoneForAction( ZONE_MODE aMode, ZONE_CONTAINER*& aZone )
{ {
aZone = nullptr; aZone = nullptr;
@ -1276,230 +1236,129 @@ bool DRAWING_TOOL::getSourceZoneForAction( ZONE_MODE aMode, ZONE_CONTAINER*& aZo
} }
void DRAWING_TOOL::performZoneCutout( ZONE_CONTAINER& aExistingZone, ZONE_CONTAINER& aCutout ) void DRAWING_TOOL::runPolygonEventLoop( POLYGON_GEOM_MANAGER& polyGeomMgr )
{ {
// Copy cutout corners into existing zone auto& controls = *getViewControls();
for( int ii = 0; ii < aCutout.GetNumCorners(); ii++ ) bool started = false;
while( OPT_TOOL_EVENT evt = Wait() )
{ {
aExistingZone.AppendCorner( aCutout.GetCornerPosition( ii ) ); VECTOR2I cursorPos = controls.GetCursorPosition();
}
// Close the current corner list if( TOOL_EVT_UTILS::IsCancelInteractive( *evt ) )
aExistingZone.Outline()->CloseLastContour(); {
// pre-empted by another tool, give up
// cancelled without an inprogress polygon, give up
if( !polyGeomMgr.IsPolygonInProgress() || evt->IsActivate() )
{
break;
}
m_board->OnAreaPolygonModified( nullptr, &aExistingZone ); polyGeomMgr.Reset();
// start again
started = false;
// Re-fill if needed controls.SetAutoPan( false );
if( aExistingZone.IsFilled() ) controls.CaptureCursor( false );
{ }
SELECTION_TOOL* selTool = m_toolMgr->GetTool<SELECTION_TOOL>();
auto& selection = selTool->GetSelection(); else if( evt->IsClick( BUT_RIGHT ) )
{
m_menu.ShowContextMenu();
}
selection.Clear(); // events that lock in nodes
selection.Add( &aExistingZone ); else if( evt->IsClick( BUT_LEFT )
|| evt->IsDblClick( BUT_LEFT )
|| evt->IsAction( &closeZoneOutline ) )
{
// Check if it is double click / closing line (so we have to finish the zone)
const bool endPolygon = evt->IsDblClick( BUT_LEFT )
|| evt->IsAction( &closeZoneOutline )
|| polyGeomMgr.NewPointClosesOutline( cursorPos );
m_toolMgr->RunAction( PCB_ACTIONS::zoneFill, true ); if( endPolygon )
} {
polyGeomMgr.SetFinished();
polyGeomMgr.Reset();
// ready to start again
started = false;
controls.SetAutoPan( false );
controls.CaptureCursor( false );
}
else // adding a corner
{
polyGeomMgr.AddPoint( cursorPos );
if( !started )
{
started = true;
controls.SetAutoPan( true );
controls.CaptureCursor( true );
}
}
}
else if( evt->IsAction( &deleteLastPoint ) )
{
polyGeomMgr.DeleteLastCorner();
if( !polyGeomMgr.IsPolygonInProgress() )
{
// report finished as an empty shape
polyGeomMgr.SetFinished();
// start again
started = false;
controls.SetAutoPan( false );
controls.CaptureCursor( false );
}
}
else if( polyGeomMgr.IsPolygonInProgress()
&& ( evt->IsMotion() || evt->IsDrag( BUT_LEFT ) ) )
{
bool draw45 = evt->Modifier( MD_CTRL );
polyGeomMgr.SetLeaderMode( draw45 ? POLYGON_GEOM_MANAGER::LEADER_MODE::DEG45
: POLYGON_GEOM_MANAGER::LEADER_MODE::DIRECT );
polyGeomMgr.SetCursorPosition( cursorPos );
}
} // end while
} }
int DRAWING_TOOL::drawZone( bool aKeepout, ZONE_MODE aMode ) int DRAWING_TOOL::drawZone( bool aKeepout, ZONE_MODE aMode )
{ {
std::unique_ptr<ZONE_CONTAINER> zone; // get a source zone, if we need one
DRAWSEGMENT line45;
DRAWSEGMENT* helperLine = NULL; // we will need more than one helper line
BOARD_COMMIT commit( m_frame );
ZONE_CONTAINER* sourceZone = nullptr; ZONE_CONTAINER* sourceZone = nullptr;
// get a source zone, if we need one
if( !getSourceZoneForAction( aMode, sourceZone ) ) if( !getSourceZoneForAction( aMode, sourceZone ) )
return 0; return 0;
// Add a VIEW_GROUP that serves as a preview for the new item ZONE_CREATE_HELPER::PARAMS params;
SELECTION preview;
m_view->Add( &preview ); params.m_keepout = aKeepout;
params.m_mode = aMode;
params.m_sourceZone = sourceZone;
ZONE_CREATE_HELPER zoneTool( *this, params );
// the geometry manager which handles the zone geometry, and
// hands the calculated points over to the zone creator tool
POLYGON_GEOM_MANAGER polyGeomMgr( zoneTool );
Activate(); // register for events
auto& controls = *getViewControls();
m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true ); m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
m_controls->ShowCursor( true );
m_controls->SetSnapping( true );
Activate(); controls.ShowCursor( true );
controls.SetSnapping( true );
VECTOR2I origin; runPolygonEventLoop( polyGeomMgr );
int numPoints = 0;
bool direction45 = false; // 45 degrees only mode
// Main loop: keep receiving events
while( OPT_TOOL_EVENT evt = Wait() )
{
VECTOR2I cursorPos = m_controls->GetCursorPosition();
// Enable 45 degrees lines only mode by holding control
if( direction45 != ( evt->Modifier( MD_CTRL ) && numPoints > 0 ) )
{
direction45 = evt->Modifier( MD_CTRL );
if( direction45 )
{
preview.Add( &line45 );
make45DegLine( helperLine, &line45 );
}
else
{
preview.Remove( &line45 );
helperLine->SetEnd( wxPoint( cursorPos.x, cursorPos.y ) );
}
m_view->Update( &preview );
}
if( TOOL_EVT_UTILS::IsCancelInteractive( *evt ) )
{
if( numPoints > 0 ) // cancel the current zone
{
zone = nullptr;
m_controls->SetAutoPan( false );
m_controls->CaptureCursor( false );
if( direction45 )
{
preview.Remove( &line45 );
direction45 = false;
}
preview.FreeItems();
m_view->Update( &preview );
numPoints = 0;
}
else // there is no zone currently drawn - just stop the tool
break;
if( evt->IsActivate() ) // now finish unconditionally
break;
}
else if( evt->IsClick( BUT_RIGHT ) )
{
m_menu.ShowContextMenu();
}
else if( evt->IsClick( BUT_LEFT ) || evt->IsDblClick( BUT_LEFT ) )
{
// Check if it is double click / closing line (so we have to finish the zone)
if( evt->IsDblClick( BUT_LEFT ) || ( numPoints > 0 && cursorPos == origin ) )
{
if( numPoints > 2 ) // valid zone consists of more than 2 points
{
assert( zone->GetNumCorners() > 2 );
// Finish the zone
if( direction45 )
zone->AppendCorner( cursorPos == origin ? line45.GetStart() : line45.GetEnd() );
zone->Outline()->CloseLastContour();
zone->Outline()->RemoveNullSegments();
zone->Outline()->Hatch();
if( !aKeepout )
static_cast<PCB_EDIT_FRAME*>( m_frame )->Fill_Zone( zone.get() );
if( aMode == ZONE_MODE::CUTOUT )
{
// For cutouts, subtract from the source
commit.Modify( sourceZone );
performZoneCutout( *sourceZone, *zone );
commit.Push( _( "Add a zone cutout" ) );
}
else
{
// Add the zone as a new board item
commit.Add( zone.release() );
commit.Push( _( "Draw a zone" ) );
}
}
// if kept, this was released. if still not null,
// this zone is now unwanted and can be removed
zone = nullptr;
numPoints = 0;
m_controls->SetAutoPan( false );
m_controls->CaptureCursor( false );
if( direction45 )
{
preview.Remove( &line45 );
direction45 = false;
}
preview.FreeItems();
m_view->Update( &preview );
}
else
{
if( numPoints == 0 ) // it's the first click
{
if( sourceZone )
{
zone = createZoneFromExisting( *sourceZone );
}
else
{
zone = createNewZone( aKeepout );
}
if( !zone )
{
continue;
}
m_frame->GetGalCanvas()->SetTopLayer( zone->GetLayer() );
// Add the first point
zone->Outline()->Start( zone->GetLayer(),
cursorPos.x, cursorPos.y,
zone->GetHatchStyle() );
origin = cursorPos;
// Helper line represents the currently drawn line of the zone polygon
helperLine = new DRAWSEGMENT;
helperLine->SetShape( S_SEGMENT );
helperLine->SetWidth( 1 );
helperLine->SetLayer( zone->GetLayer() );
helperLine->SetStart( wxPoint( cursorPos.x, cursorPos.y ) );
helperLine->SetEnd( wxPoint( cursorPos.x, cursorPos.y ) );
line45 = *helperLine;
preview.Add( helperLine );
}
else
{
zone->AppendCorner( helperLine->GetEnd() );
helperLine = new DRAWSEGMENT( *helperLine );
helperLine->SetStart( helperLine->GetEnd() );
preview.Add( helperLine );
}
++numPoints;
m_view->Update( &preview );
}
}
else if( evt->IsMotion() && numPoints > 0 )
{
// 45 degree lines
if( direction45 )
make45DegLine( helperLine, &line45 );
else
helperLine->SetEnd( wxPoint( cursorPos.x, cursorPos.y ) );
m_view->Update( &preview );
}
}
m_view->Remove( &preview );
m_frame->SetToolID( ID_NO_TOOL_SELECTED, wxCURSOR_DEFAULT, wxEmptyString ); m_frame->SetToolID( ID_NO_TOOL_SELECTED, wxCURSOR_DEFAULT, wxEmptyString );
return 0; return 0;

View File

@ -38,6 +38,7 @@ namespace KIGFX
class BOARD; class BOARD;
class PCB_BASE_EDIT_FRAME; class PCB_BASE_EDIT_FRAME;
class DRAWSEGMENT; class DRAWSEGMENT;
class POLYGON_GEOM_MANAGER;
/** /**
* Class DRAWING_TOOL * Class DRAWING_TOOL
@ -215,27 +216,6 @@ private:
*/ */
int drawZone( bool aKeepout, ZONE_MODE aMode ); int drawZone( bool aKeepout, ZONE_MODE aMode );
/**
* Function createNewZone()
*
* Prompt the user for new zone settings, and create a new zone with
* those settings
*
* @param aKeepout should the zone be a keepout
* @return the new zone, can be null if the user aborted
*/
std::unique_ptr<ZONE_CONTAINER> createNewZone( bool aKeepout );
/**
* Function createZoneFromExisting
*
* Create a new zone with the settings from an existing zone
*
* @param aSrcZone the zone to copy settings from
* @return the new zone
*/
std::unique_ptr<ZONE_CONTAINER> createZoneFromExisting( const ZONE_CONTAINER& aSrcZone );
/** /**
* Function getSourceZoneForAction() * Function getSourceZoneForAction()
* *
@ -252,15 +232,11 @@ private:
bool getSourceZoneForAction( ZONE_MODE aMode, ZONE_CONTAINER*& aZone ); bool getSourceZoneForAction( ZONE_MODE aMode, ZONE_CONTAINER*& aZone );
/** /**
* Function performZoneCutout() * Run the event loop for polygon creation, sending user input
* * on to the given POLYGON_GEOM_MANAGER for processing into a
* Cut one zone out of another one (i.e. subtraction) and * complete polygon.
* update the zone.
*
* @param aExistingZone the zone to removed area from
* @param aCutout the area to remove
*/ */
void performZoneCutout( ZONE_CONTAINER& aExistingZone, ZONE_CONTAINER& aCutout ); void runPolygonEventLoop( POLYGON_GEOM_MANAGER& aPolyGeomMgr );
/** /**
* Function make45DegLine() * Function make45DegLine()
@ -291,6 +267,10 @@ private:
// How does line width change after one -/+ key press. // How does line width change after one -/+ key press.
static const unsigned int WIDTH_STEP; static const unsigned int WIDTH_STEP;
// give internal access to drawing helper classes
friend class ZONE_CREATE_HELPER;
}; };
#endif /* __DRAWING_TOOL_H */ #endif /* __DRAWING_TOOL_H */

View File

@ -0,0 +1,238 @@
/*
* 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 <tools/zone_create_helper.h>
#include <view/view.h>
#include <tool/tool_manager.h>
#include <class_zone.h>
#include <board_commit.h>
#include <pcb_painter.h>
#include <tools/pcb_actions.h>
#include <tools/selection_tool.h>
ZONE_CREATE_HELPER::ZONE_CREATE_HELPER( DRAWING_TOOL& aTool,
const PARAMS& aParams ):
m_tool( aTool ),
m_params( aParams ),
m_parentView( *aTool.getView() )
{
m_parentView.Add( &m_previewItem );
}
ZONE_CREATE_HELPER::~ZONE_CREATE_HELPER()
{
// remove the preview from the view
m_parentView.SetVisible( &m_previewItem, false );
m_parentView.Remove( &m_previewItem );
}
std::unique_ptr<ZONE_CONTAINER> ZONE_CREATE_HELPER::createNewZone( bool aKeepout )
{
auto& frame = *m_tool.getEditFrame<PCB_BASE_EDIT_FRAME>();
auto& board = *m_tool.getModel<BOARD>();
// Get the current default settings for zones
ZONE_SETTINGS zoneInfo = frame.GetZoneSettings();
zoneInfo.m_CurrentZone_Layer = frame.GetScreen()->m_Active_Layer;
zoneInfo.m_NetcodeSelection = board.GetHighLightNetCode();
zoneInfo.SetIsKeepout( m_params.m_keepout );
// Show options dialog
ZONE_EDIT_T dialogResult;
if( m_params.m_keepout )
dialogResult = InvokeKeepoutAreaEditor( &frame, &zoneInfo );
else
{
if( IsCopperLayer( zoneInfo.m_CurrentZone_Layer ) )
dialogResult = InvokeCopperZonesEditor( &frame, &zoneInfo );
else
dialogResult = InvokeNonCopperZonesEditor( &frame, nullptr, &zoneInfo );
}
if( dialogResult == ZONE_ABORT )
{
return nullptr;
}
auto newZone = std::make_unique<ZONE_CONTAINER>( &board );
// Apply the selected settings
zoneInfo.ExportSetting( *newZone );
return newZone;
}
std::unique_ptr<ZONE_CONTAINER> ZONE_CREATE_HELPER::createZoneFromExisting(
const ZONE_CONTAINER& aSrcZone )
{
auto& board = *m_tool.getModel<BOARD>();
auto newZone = std::make_unique<ZONE_CONTAINER>( &board );
ZONE_SETTINGS zoneSettings;
zoneSettings << aSrcZone;
zoneSettings.ExportSetting( *newZone );
return newZone;
}
void ZONE_CREATE_HELPER::performZoneCutout( ZONE_CONTAINER& aExistingZone,
ZONE_CONTAINER& aCutout )
{
auto& board = *m_tool.getModel<BOARD>();
auto& toolMgr = *m_tool.GetManager();
// Copy cutout corners into existing zone
for( int ii = 0; ii < aCutout.GetNumCorners(); ii++ )
{
aExistingZone.AppendCorner( aCutout.GetCornerPosition( ii ) );
}
// Close the current corner list
aExistingZone.Outline()->CloseLastContour();
board.OnAreaPolygonModified( nullptr, &aExistingZone );
// Re-fill if needed
if( aExistingZone.IsFilled() )
{
SELECTION_TOOL* selTool = toolMgr.GetTool<SELECTION_TOOL>();
auto& selection = selTool->GetSelection();
selection.Clear();
selection.Add( &aExistingZone );
toolMgr.RunAction( PCB_ACTIONS::zoneFill, true );
}
}
void ZONE_CREATE_HELPER::commitZone( std::unique_ptr<ZONE_CONTAINER> aZone )
{
auto& frame = *m_tool.getEditFrame<PCB_EDIT_FRAME>();
if( !m_params.m_keepout )
frame.Fill_Zone( aZone.get() );
BOARD_COMMIT bCommit( &m_tool );
if( m_params.m_mode == DRAWING_TOOL::ZONE_MODE::CUTOUT )
{
// For cutouts, subtract from the source
bCommit.Modify( m_params.m_sourceZone );
performZoneCutout( *m_params.m_sourceZone, *aZone );
bCommit.Push( _( "Add a zone cutout" ) );
}
else
{
// Add the zone as a new board item
bCommit.Add( aZone.release() );
bCommit.Push( _( "Add a zone" ) );
}
};
bool ZONE_CREATE_HELPER::OnFirstPoint()
{
// if we don't have a zone, create one
// the user's choice here can affect things like the colour
// of the preview
if( !m_zone )
{
if( m_params.m_sourceZone )
m_zone = createZoneFromExisting( *m_params.m_sourceZone );
else
m_zone = createNewZone( m_params.m_keepout );
if( m_zone )
{
// set up poperties from zone
const auto& settings = *m_parentView.GetPainter()->GetSettings();
COLOR4D color = settings.GetColor( nullptr, m_zone->GetLayer() );
m_previewItem.SetStrokeColor( color );
m_previewItem.SetFillColor( color.WithAlpha( 0.2 ) );
m_parentView.SetVisible( &m_previewItem, true );
}
}
if( !m_zone )
{
return false;
}
return true;
}
void ZONE_CREATE_HELPER::OnGeometryChange( const POLYGON_GEOM_MANAGER& aMgr )
{
// send the points to the preview item
m_previewItem.SetPoints( aMgr.GetLockedInPoints(), aMgr.GetLeaderLinePoints() );
m_parentView.Update( &m_previewItem, KIGFX::GEOMETRY );
}
void ZONE_CREATE_HELPER::OnComplete( const POLYGON_GEOM_MANAGER& aMgr )
{
auto& finalPoints = aMgr.GetLockedInPoints();
if( finalPoints.size() < 3 )
{
// just scrap the zone in progress
m_zone = nullptr;
}
else
{
m_zone->Outline()->Start( m_zone->GetLayer(),
finalPoints[0].x, finalPoints[0].y,
m_zone->GetHatchStyle() );
for( size_t i = 1; i < finalPoints.size(); ++i )
{
m_zone->AppendCorner( { finalPoints[i].x, finalPoints[i].y } );
}
m_zone->Outline()->CloseLastContour();
m_zone->Outline()->RemoveNullSegments();
m_zone->Outline()->Hatch();
// hand the zone over to the committer
commitZone( std::move( m_zone ) );
}
m_parentView.SetVisible( &m_previewItem, false );
}

View File

@ -0,0 +1,138 @@
/*
* 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 TOOLS_ZONE_CREATE_HELPER__H_
#define TOOLS_ZONE_CREATE_HELPER__H_
#include <preview_items/polygon_geom_manager.h>
#include <preview_items/polygon_item.h>
#include <tools/drawing_tool.h>
namespace KIGFX
{
class VIEW;
}
/**
* This class is an adjuct helper to the DRAWING_TOOL interactive
* tool, which handles incoming geometry changes from a
* POLYGON_GEOM_MANAGER and translates that into a ZONE_CONTAINER
* based on given parameters
*/
class ZONE_CREATE_HELPER : public POLYGON_GEOM_MANAGER::CLIENT
{
public:
/**
* Parameters used to fully describe a zone creation process
*/
struct PARAMS
{
///> Should create a keepout zone?
bool m_keepout;
///> The zone mode to operate in
DRAWING_TOOL::ZONE_MODE m_mode;
///> Zone settings source (for similar and cutout zones)
ZONE_CONTAINER* m_sourceZone;
};
/**
* @param aTool the DRAWING_TOOL to provide the zone tool to
* @param aParams the parameters to use to guide the zone creation
*/
ZONE_CREATE_HELPER( DRAWING_TOOL& aTool, const PARAMS& aParams );
~ZONE_CREATE_HELPER();
/*
* Interface for receiving POLYGON_GEOM_MANAGER update
*/
void OnGeometryChange( const POLYGON_GEOM_MANAGER& aMgr ) override;
bool OnFirstPoint() override;
void OnComplete( const POLYGON_GEOM_MANAGER& aMgr ) override;
/**
* Function createNewZone()
*
* Prompt the user for new zone settings, and create a new zone with
* those settings
*
* @param aKeepout should the zone be a keepout
* @return the new zone, can be null if the user aborted
*/
std::unique_ptr<ZONE_CONTAINER> createNewZone( bool aKeepout );
/**
* Function createZoneFromExisting
*
* Create a new zone with the settings from an existing zone
*
* @param aSrcZone the zone to copy settings from
* @return the new zone
*/
std::unique_ptr<ZONE_CONTAINER> createZoneFromExisting( const ZONE_CONTAINER& aSrcZone );
/**
* Function performZoneCutout()
*
* Cut one zone out of another one (i.e. subtraction) and
* update the zone.
*
* @param aExistingZone the zone to removed area from
* @param aCutout the area to remove
*/
void performZoneCutout( ZONE_CONTAINER& aExistingZone, ZONE_CONTAINER& aCutout );
/**
* Commit the current zone-in-progress to the BOARD. This might
* be adding a new zone, or modifying an existing zone with a
* cutout, depending on parameters.
*
* @param aZone - the drawn zone outline to commit
*/
void commitZone( std::unique_ptr<ZONE_CONTAINER> aZone );
private:
DRAWING_TOOL& m_tool;
///> Parameters of the zone to be drawn
const PARAMS& m_params;
///> The preview item to display
KIGFX::PREVIEW::POLYGON_ITEM m_previewItem;
///> view that show the preview item
KIGFX::VIEW& m_parentView;
///> The zone-in-progress
std::unique_ptr<ZONE_CONTAINER> m_zone;
};
#endif /* __DRAWING_TOOL_H */