diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index a9e017c5fc..98538fdbae 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 0000000000..cbd910b345 --- /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 + +#include + + +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& POLYGON_GEOM_MANAGER::GetLockedInPoints() const +{ + return m_lockedPoints; +} + + +const std::vector& 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 0000000000..08da8c70de --- /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 + +#include + +#include +#include + +using namespace KIGFX::PREVIEW; + + +POLYGON_ITEM::POLYGON_ITEM(): + SIMPLE_OVERLAY_ITEM() +{ +} + +void POLYGON_ITEM::SetPoints( const std::vector& aLockedPts, + const std::vector& 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 0000000000..a6b665c1da --- /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 +#include + +/** + * 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& 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& 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 m_lockedPoints; + + ///> Points in the temporary "leader" line(s) + std::vector 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 0000000000..6d489f6f7a --- /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 + +#include +#include + +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& aLockedInPts, + const std::vector& 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 b466debea7..c0ce9ad19e 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 45e92981a4..9753198f13 100644 --- a/pcbnew/tools/drawing_tool.cpp +++ b/pcbnew/tools/drawing_tool.cpp @@ -46,6 +46,7 @@ #include #include #include +#include #include @@ -58,6 +59,7 @@ #include #include +#include using SCOPED_DRAW_MODE = SCOPED_SET_RESET; @@ -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() ); 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 DRAWING_TOOL::createNewZone( bool aKeepout ) -{ - const auto& board = *getModel(); - - // 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( m_board ); - - // Apply the selected settings - zoneInfo.ExportSetting( *newZone ); - - return newZone; -} - - -std::unique_ptr DRAWING_TOOL::createZoneFromExisting( - const ZONE_CONTAINER& aSrcZone ) -{ - auto newZone = std::make_unique( 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 ) +void DRAWING_TOOL::runPolygonEventLoop( POLYGON_GEOM_MANAGER& polyGeomMgr ) { - // Copy cutout corners into existing zone - for( int ii = 0; ii < aCutout.GetNumCorners(); ii++ ) + auto& controls = *getViewControls(); + bool started = false; + + while( OPT_TOOL_EVENT evt = Wait() ) { - aExistingZone.AppendCorner( aCutout.GetCornerPosition( ii ) ); - } + VECTOR2I cursorPos = controls.GetCursorPosition(); - // Close the current corner list - aExistingZone.Outline()->CloseLastContour(); + if( TOOL_EVT_UTILS::IsCancelInteractive( *evt ) ) + { + // 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 - if( aExistingZone.IsFilled() ) - { - SELECTION_TOOL* selTool = m_toolMgr->GetTool(); + controls.SetAutoPan( false ); + controls.CaptureCursor( false ); + } - auto& selection = selTool->GetSelection(); + else if( evt->IsClick( BUT_RIGHT ) ) + { + m_menu.ShowContextMenu(); + } - selection.Clear(); - selection.Add( &aExistingZone ); + // 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) + 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 ) { - std::unique_ptr zone; - DRAWSEGMENT line45; - DRAWSEGMENT* helperLine = NULL; // we will need more than one helper line - BOARD_COMMIT commit( m_frame ); + // get a source zone, if we need one 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 ); + ZONE_CREATE_HELPER::PARAMS params; + + 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_controls->ShowCursor( true ); - m_controls->SetSnapping( true ); - Activate(); + controls.ShowCursor( true ); + controls.SetSnapping( true ); - VECTOR2I origin; - int numPoints = 0; - bool direction45 = false; // 45 degrees only mode + runPolygonEventLoop( polyGeomMgr ); - // 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( 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 ); return 0; diff --git a/pcbnew/tools/drawing_tool.h b/pcbnew/tools/drawing_tool.h index 6617d849dc..3240f0cdae 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 @@ -215,27 +216,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 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 createZoneFromExisting( const ZONE_CONTAINER& aSrcZone ); - /** * Function getSourceZoneForAction() * @@ -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 0000000000..b1def20802 --- /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 + +#include +#include +#include +#include +#include + +#include +#include + + +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_CREATE_HELPER::createNewZone( bool aKeepout ) +{ + auto& frame = *m_tool.getEditFrame(); + auto& board = *m_tool.getModel(); + + // 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( &board ); + + // Apply the selected settings + zoneInfo.ExportSetting( *newZone ); + + return newZone; +} + + +std::unique_ptr ZONE_CREATE_HELPER::createZoneFromExisting( + const ZONE_CONTAINER& aSrcZone ) +{ + auto& board = *m_tool.getModel(); + + auto newZone = std::make_unique( &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(); + 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(); + + auto& selection = selTool->GetSelection(); + + selection.Clear(); + selection.Add( &aExistingZone ); + + toolMgr.RunAction( PCB_ACTIONS::zoneFill, true ); + } +} + + +void ZONE_CREATE_HELPER::commitZone( std::unique_ptr aZone ) +{ + auto& frame = *m_tool.getEditFrame(); + + 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 0000000000..f837fb20bd --- /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 +#include + +#include + +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 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 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 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 m_zone; +}; + +#endif /* __DRAWING_TOOL_H */