← Back to team overview

kicad-developers team mailing list archive

[PATCH] Finish GAL zone tool

 

Hi,

Here's a patch set to add the final pieces of the GAL zone tool: close
outline and delete last corner.

This takes the form of a major re-cast of the drawZone event loop,
which now uses a preview item to show the zone in progress and a
geometry manager to decouple the construction from the the preview and
the end result.

I chose this way, as the existing GAL event loop was very large and
unwieldy. Adding modifiers keys and additional line contraints (e.g.
90 degrees) will be much easier when the parts are separated. Also the
preview item and the geometry manager are reusable for other GAL tools
(and not only in Pcbnew).

This is preceded by moving the DIRECTION45 class into common, as this
is useful for the generic polygon preview, and has no dependencies
other than standard primitives.

Cheers,

John
From 79316c7e2b1492a13cf5f51ff5853d5ea1e96d4c Mon Sep 17 00:00:00 2001
From: John Beard <john.j.beard@xxxxxxxxx>
Date: Mon, 27 Feb 2017 01:45:52 +0800
Subject: [PATCH 2/2] 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
---
 common/CMakeLists.txt                         |   2 +
 common/preview_items/polygon_geom_manager.cpp | 158 ++++++++++++
 common/preview_items/polygon_item.cpp         |  76 ++++++
 include/preview_items/polygon_geom_manager.h  | 159 ++++++++++++
 include/preview_items/polygon_item.h          |  82 ++++++
 pcbnew/CMakeLists.txt                         |   1 +
 pcbnew/tools/drawing_tool.cpp                 | 353 ++++++++------------------
 pcbnew/tools/drawing_tool.h                   |  38 +--
 pcbnew/tools/zone_create_helper.cpp           | 238 +++++++++++++++++
 pcbnew/tools/zone_create_helper.h             | 138 ++++++++++
 10 files changed, 969 insertions(+), 276 deletions(-)
 create mode 100644 common/preview_items/polygon_geom_manager.cpp
 create mode 100644 common/preview_items/polygon_item.cpp
 create mode 100644 include/preview_items/polygon_geom_manager.h
 create mode 100644 include/preview_items/polygon_item.h
 create mode 100644 pcbnew/tools/zone_create_helper.cpp
 create mode 100644 pcbnew/tools/zone_create_helper.h

diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt
index 0f3de539f..48406dabc 100644
--- a/common/CMakeLists.txt
+++ b/common/CMakeLists.txt
@@ -193,6 +193,8 @@ set( COMMON_PREVIEW_ITEMS_SRCS
     preview_items/simple_overlay_item.cpp
     preview_items/selection_area.cpp
     preview_items/bright_box.cpp
+    preview_items/polygon_geom_manager.cpp
+    preview_items/polygon_item.cpp
     )
 
 set( COMMON_SRCS
diff --git a/common/preview_items/polygon_geom_manager.cpp b/common/preview_items/polygon_geom_manager.cpp
new file mode 100644
index 000000000..8eeee8e68
--- /dev/null
+++ b/common/preview_items/polygon_geom_manager.cpp
@@ -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;
+}
diff --git a/common/preview_items/polygon_item.cpp b/common/preview_items/polygon_item.cpp
new file mode 100644
index 000000000..08da8c70d
--- /dev/null
+++ b/common/preview_items/polygon_item.cpp
@@ -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();
+}
diff --git a/include/preview_items/polygon_geom_manager.h b/include/preview_items/polygon_geom_manager.h
new file mode 100644
index 000000000..a6b665c1d
--- /dev/null
+++ b/include/preview_items/polygon_geom_manager.h
@@ -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_
diff --git a/include/preview_items/polygon_item.h b/include/preview_items/polygon_item.h
new file mode 100644
index 000000000..6d489f6f7
--- /dev/null
+++ b/include/preview_items/polygon_item.h
@@ -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_
diff --git a/pcbnew/CMakeLists.txt b/pcbnew/CMakeLists.txt
index b466debea..c0ce9ad19 100644
--- a/pcbnew/CMakeLists.txt
+++ b/pcbnew/CMakeLists.txt
@@ -316,6 +316,7 @@ set( PCBNEW_CLASS_SRCS
     tools/pad_tool.cpp
     tools/picker_tool.cpp
     tools/zoom_tool.cpp
+    tools/zone_create_helper.cpp
     tools/tools_common.cpp
     tools/tool_event_utils.cpp
 
diff --git a/pcbnew/tools/drawing_tool.cpp b/pcbnew/tools/drawing_tool.cpp
index 45e92981a..9753198f1 100644
--- a/pcbnew/tools/drawing_tool.cpp
+++ b/pcbnew/tools/drawing_tool.cpp
@@ -46,6 +46,7 @@
 #include <scoped_set_reset.h>
 #include <bitmaps.h>
 #include <hotkeys.h>
+#include <painter.h>
 
 #include <preview_items/arc_assistant.h>
 
@@ -58,6 +59,7 @@
 
 #include <tools/selection_tool.h>
 #include <tools/tool_event_utils.h>
+#include <tools/zone_create_helper.h>
 
 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" ),
         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() :
     PCB_TOOL( "pcbnew.InteractiveDrawing" ),
@@ -152,8 +159,14 @@ bool DRAWING_TOOL::Init()
         return m_mode != MODE::NONE;
     };
 
+    // some interactive drawing tools can undo the last point
     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();
@@ -161,12 +174,16 @@ bool DRAWING_TOOL::Init()
     // 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
+    // tool-specific actions
+    ctxMenu.AddItem( closeZoneOutline, zoneActiveFunctor, 1000 );
     ctxMenu.AddItem( deleteLastPoint, canUndoPoint, 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>() );
 
     return true;
@@ -431,7 +448,6 @@ int DRAWING_TOOL::PlaceText( const TOOL_EVENT& aEvent )
                 text = NULL;
             }
         }
-
         else if( text && evt->IsMotion() )
         {
             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 )
 {
     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 )
-{
-    // 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();
-
-    m_board->OnAreaPolygonModified( nullptr, &aExistingZone );
-
-    // Re-fill if needed
-    if( aExistingZone.IsFilled() )
-    {
-        SELECTION_TOOL* selTool = m_toolMgr->GetTool<SELECTION_TOOL>();
-
-        auto& selection = selTool->GetSelection();
-
-        selection.Clear();
-        selection.Add( &aExistingZone );
-
-        m_toolMgr->RunAction( PCB_ACTIONS::zoneFill, true );
-    }
-}
-
-
-int DRAWING_TOOL::drawZone( bool aKeepout, ZONE_MODE aMode )
+void DRAWING_TOOL::runPolygonEventLoop( POLYGON_GEOM_MANAGER& polyGeomMgr )
 {
-    std::unique_ptr<ZONE_CONTAINER> zone;
-    DRAWSEGMENT line45;
-    DRAWSEGMENT* helperLine = NULL;  // we will need more than one helper line
-    BOARD_COMMIT commit( m_frame );
-    ZONE_CONTAINER* sourceZone = nullptr;
-
-    // get a source zone, if we need one
-    if( !getSourceZoneForAction( aMode, sourceZone ) )
-        return 0;
-
-    // Add a VIEW_GROUP that serves as a preview for the new item
-    SELECTION preview;
-    m_view->Add( &preview );
-
-    m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
-    m_controls->ShowCursor( true );
-    m_controls->SetSnapping( true );
-
-    Activate();
-
-    VECTOR2I origin;
-    int numPoints = 0;
-    bool direction45 = false;       // 45 degrees only mode
+    auto& controls = *getViewControls();
+    bool started = false;
 
-    // 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 );
-        }
+        VECTOR2I cursorPos = controls.GetCursorPosition();
 
         if( TOOL_EVT_UTILS::IsCancelInteractive( *evt ) )
         {
-            if( numPoints > 0 )         // cancel the current zone
+            // pre-empted by another tool, give up
+            // cancelled without an inprogress polygon, give up
+            if( !polyGeomMgr.IsPolygonInProgress() || evt->IsActivate() )
             {
-                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;
+            polyGeomMgr.Reset();
+            // start again
+            started = false;
+
+            controls.SetAutoPan( false );
+            controls.CaptureCursor( false );
         }
+
         else if( evt->IsClick( BUT_RIGHT ) )
         {
             m_menu.ShowContextMenu();
         }
-        else if( evt->IsClick( BUT_LEFT ) || evt->IsDblClick( BUT_LEFT ) )
+
+        // events that lock in nodes
+        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)
-            if( evt->IsDblClick( BUT_LEFT ) || ( numPoints > 0 && cursorPos == origin ) )
+            const bool endPolygon = evt->IsDblClick( BUT_LEFT )
+                            || evt->IsAction( &closeZoneOutline )
+                            || polyGeomMgr.NewPointClosesOutline( cursorPos );
+
+            if( endPolygon )
             {
-                if( numPoints > 2 )     // valid zone consists of more than 2 points
-                {
-                    assert( zone->GetNumCorners() > 2 );
+                polyGeomMgr.SetFinished();
+                polyGeomMgr.Reset();
 
-                    // Finish the zone
-                    if( direction45 )
-                        zone->AppendCorner( cursorPos == origin ? line45.GetStart() : line45.GetEnd() );
+                // ready to start again
+                started = false;
+                controls.SetAutoPan( false );
+                controls.CaptureCursor( false );
+            }
+            else // adding a corner
+            {
+                polyGeomMgr.AddPoint( cursorPos );
 
-                    zone->Outline()->CloseLastContour();
-                    zone->Outline()->RemoveNullSegments();
-                    zone->Outline()->Hatch();
+                if( !started )
+                {
+                    started = true;
+                    controls.SetAutoPan( true );
+                    controls.CaptureCursor( true );
+                }
+            }
+        }
 
-                    if( !aKeepout )
-                        static_cast<PCB_EDIT_FRAME*>( m_frame )->Fill_Zone( zone.get() );
+        else if( evt->IsAction( &deleteLastPoint ) )
+        {
+            polyGeomMgr.DeleteLastCorner();
 
-                    if( aMode == ZONE_MODE::CUTOUT )
-                    {
-                        // For cutouts, subtract from the source
-                        commit.Modify( sourceZone );
+            if( !polyGeomMgr.IsPolygonInProgress() )
+            {
+                // report finished as an empty shape
+                polyGeomMgr.SetFinished();
 
-                        performZoneCutout( *sourceZone, *zone );
+                // start again
+                started = false;
+                controls.SetAutoPan( false );
+                controls.CaptureCursor( false );
+            }
+        }
 
-                        commit.Push( _( "Add a zone cutout" ) );
-                    }
-                    else
-                    {
-                        // Add the zone as a new board item
-                        commit.Add( zone.release() );
-                        commit.Push( _( "Draw a zone" ) );
-                    }
-                }
+        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
+}
 
-                // 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 );
+int DRAWING_TOOL::drawZone( bool aKeepout, ZONE_MODE aMode )
+{
+    // get a source zone, if we need one
+    ZONE_CONTAINER* sourceZone = nullptr;
 
-                if( direction45 )
-                {
-                    preview.Remove( &line45 );
-                    direction45 = false;
-                }
+    if( !getSourceZoneForAction( aMode, sourceZone ) )
+        return 0;
 
-                preview.FreeItems();
-                m_view->Update( &preview );
-            }
-            else
-            {
-                if( numPoints == 0 )        // it's the first click
-                {
-                    if( sourceZone )
-                    {
-                        zone = createZoneFromExisting( *sourceZone );
-                    }
-                    else
-                    {
-                        zone = createNewZone( aKeepout );
-                    }
+    ZONE_CREATE_HELPER::PARAMS params;
 
-                    if( !zone )
-                    {
-                        continue;
-                    }
+    params.m_keepout = aKeepout;
+    params.m_mode = aMode;
+    params.m_sourceZone = sourceZone;
 
-                    m_frame->GetGalCanvas()->SetTopLayer( zone->GetLayer() );
+    ZONE_CREATE_HELPER zoneTool( *this, params );
 
-                    // Add the first point
-                    zone->Outline()->Start( zone->GetLayer(),
-                                            cursorPos.x, cursorPos.y,
-                                            zone->GetHatchStyle() );
-                    origin = cursorPos;
+    // the geometry manager which handles the zone geometry, and
+    // hands the calculated points over to the zone creator tool
+    POLYGON_GEOM_MANAGER polyGeomMgr( zoneTool );
 
-                    // 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;
+    Activate(); // register for events
 
-                    preview.Add( helperLine );
-                }
-                else
-                {
-                    zone->AppendCorner( helperLine->GetEnd() );
-                    helperLine = new DRAWSEGMENT( *helperLine );
-                    helperLine->SetStart( helperLine->GetEnd() );
-                    preview.Add( helperLine );
-                }
+    auto& controls = *getViewControls();
 
-                ++numPoints;
-                m_view->Update( &preview );
-            }
-        }
+    m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
 
-        else if( evt->IsMotion() && numPoints > 0 )
-        {
-            // 45 degree lines
-            if( direction45 )
-                make45DegLine( helperLine, &line45 );
-            else
-                helperLine->SetEnd( wxPoint( cursorPos.x, cursorPos.y ) );
+    controls.ShowCursor( true );
+    controls.SetSnapping( true );
 
-            m_view->Update( &preview );
-        }
-    }
+    runPolygonEventLoop( polyGeomMgr );
 
-    m_view->Remove( &preview );
     m_frame->SetToolID( ID_NO_TOOL_SELECTED, wxCURSOR_DEFAULT, wxEmptyString );
 
     return 0;
diff --git a/pcbnew/tools/drawing_tool.h b/pcbnew/tools/drawing_tool.h
index 6617d849d..3240f0cda 100644
--- a/pcbnew/tools/drawing_tool.h
+++ b/pcbnew/tools/drawing_tool.h
@@ -38,6 +38,7 @@ namespace KIGFX
 class BOARD;
 class PCB_BASE_EDIT_FRAME;
 class DRAWSEGMENT;
+class POLYGON_GEOM_MANAGER;
 
 /**
  * Class DRAWING_TOOL
@@ -216,27 +217,6 @@ private:
     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()
      *
      * Gets a source zone item for an action that takes an existing zone
@@ -252,15 +232,11 @@ private:
     bool getSourceZoneForAction( ZONE_MODE aMode, ZONE_CONTAINER*& aZone );
 
     /**
-     * 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
+     * Run the event loop for polygon creation, sending user input
+     * on to the given POLYGON_GEOM_MANAGER for processing into a
+     * complete polygon.
      */
-    void performZoneCutout( ZONE_CONTAINER& aExistingZone, ZONE_CONTAINER& aCutout );
+    void runPolygonEventLoop( POLYGON_GEOM_MANAGER& aPolyGeomMgr );
 
     /**
      * Function make45DegLine()
@@ -291,6 +267,10 @@ private:
 
     // How does line width change after one -/+ key press.
     static const unsigned int WIDTH_STEP;
+
+
+    // give internal access to drawing helper classes
+    friend class ZONE_CREATE_HELPER;
 };
 
 #endif /* __DRAWING_TOOL_H */
diff --git a/pcbnew/tools/zone_create_helper.cpp b/pcbnew/tools/zone_create_helper.cpp
new file mode 100644
index 000000000..b1def2080
--- /dev/null
+++ b/pcbnew/tools/zone_create_helper.cpp
@@ -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 );
+}
diff --git a/pcbnew/tools/zone_create_helper.h b/pcbnew/tools/zone_create_helper.h
new file mode 100644
index 000000000..f837fb20b
--- /dev/null
+++ b/pcbnew/tools/zone_create_helper.h
@@ -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 */
-- 
2.12.0

From 4fcce8a34e3911360df53d61e8a46a65cf9d072b Mon Sep 17 00:00:00 2001
From: John Beard <john.j.beard@xxxxxxxxx>
Date: Wed, 22 Mar 2017 20:35:07 +0800
Subject: [PATCH 1/2] Move DIRECTION45 to common/geometry

This class is useful for generic geometric layout of 45-deg constrained
line chains, not only in the PNS router.
---
 pcbnew/router/direction.h => include/geometry/direction45.h | 9 +++++----
 pcbnew/router/pns_diff_pair.cpp                             | 2 +-
 pcbnew/router/pns_line.h                                    | 2 +-
 pcbnew/router/pns_routing_settings.cpp                      | 3 ++-
 pcbnew/tools/drawing_tool.cpp                               | 2 +-
 5 files changed, 10 insertions(+), 8 deletions(-)
 rename pcbnew/router/direction.h => include/geometry/direction45.h (97%)

diff --git a/pcbnew/router/direction.h b/include/geometry/direction45.h
similarity index 97%
rename from pcbnew/router/direction.h
rename to include/geometry/direction45.h
index 402d4c991..50236eaff 100644
--- a/pcbnew/router/direction.h
+++ b/include/geometry/direction45.h
@@ -1,8 +1,9 @@
 /*
- * KiRouter - a push-and-(sometimes-)shove PCB router
+ * This program source code file is part of KiCad, a free EDA CAD application.
  *
  * Copyright (C) 2013-2015 CERN
  * Author: Tomasz Wlostowski <tomasz.wlostowski@xxxxxxx>
+ * 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
@@ -18,8 +19,8 @@
  * with this program.  If not, see <http://www.gnu.or/licenses/>.
  */
 
-#ifndef __DIRECTION_H
-#define __DIRECTION_H
+#ifndef DIRECTION45_H
+#define DIRECTION45_H
 
 #include <geometry/seg.h>
 #include <geometry/shape_line_chain.h>
@@ -354,4 +355,4 @@ private:
     Directions m_dir;
 };
 
-#endif    // __DIRECTION_H
+#endif    // DIRECTION45_H
diff --git a/pcbnew/router/pns_diff_pair.cpp b/pcbnew/router/pns_diff_pair.cpp
index 85814644e..0279e1bd9 100644
--- a/pcbnew/router/pns_diff_pair.cpp
+++ b/pcbnew/router/pns_diff_pair.cpp
@@ -24,12 +24,12 @@
 #include <cmath>
 #include <limits>
 
+#include <geometry/direction45.h>
 #include <geometry/shape.h>
 #include <geometry/shape_rect.h>
 #include <geometry/shape_circle.h>
 #include <geometry/shape_segment.h>
 
-#include "direction.h"
 
 #include "pns_diff_pair.h"
 #include "pns_router.h"
diff --git a/pcbnew/router/pns_line.h b/pcbnew/router/pns_line.h
index 5a0e2bfa7..076540ec5 100644
--- a/pcbnew/router/pns_line.h
+++ b/pcbnew/router/pns_line.h
@@ -24,11 +24,11 @@
 
 #include <math/vector2d.h>
 
+#include <geometry/direction45.h>
 #include <geometry/seg.h>
 #include <geometry/shape.h>
 #include <geometry/shape_line_chain.h>
 
-#include "direction.h"
 #include "pns_item.h"
 #include "pns_via.h"
 
diff --git a/pcbnew/router/pns_routing_settings.cpp b/pcbnew/router/pns_routing_settings.cpp
index b000eb4f8..62147a2f5 100644
--- a/pcbnew/router/pns_routing_settings.cpp
+++ b/pcbnew/router/pns_routing_settings.cpp
@@ -21,8 +21,9 @@
 
 #include <tool/tool_settings.h>
 
+#include <geometry/direction45.h>
+
 #include "pns_routing_settings.h"
-#include "direction.h"
 
 namespace PNS {
 
diff --git a/pcbnew/tools/drawing_tool.cpp b/pcbnew/tools/drawing_tool.cpp
index 9038e7f69..45e92981a 100644
--- a/pcbnew/tools/drawing_tool.cpp
+++ b/pcbnew/tools/drawing_tool.cpp
@@ -40,7 +40,7 @@
 #include <view/view.h>
 #include <gal/graphics_abstraction_layer.h>
 #include <tool/tool_manager.h>
-#include <router/direction.h>
+#include <geometry/direction45.h>
 #include <ratsnest_data.h>
 #include <board_commit.h>
 #include <scoped_set_reset.h>
-- 
2.12.0


Follow ups