From eb21502d0fd916b5449ff36d4a091e999ca12a87 Mon Sep 17 00:00:00 2001 From: vocx-fc Date: Fri, 3 Apr 2020 22:11:50 -0600 Subject: [PATCH] Draft: move Offset GuiCommand to gui_offset module Include Yorik's commit 97c8eff825. --- src/Mod/Draft/CMakeLists.txt | 1 + src/Mod/Draft/DraftTools.py | 196 +------------ src/Mod/Draft/draftguitools/gui_offset.py | 317 ++++++++++++++++++++++ 3 files changed, 319 insertions(+), 195 deletions(-) create mode 100644 src/Mod/Draft/draftguitools/gui_offset.py diff --git a/src/Mod/Draft/CMakeLists.txt b/src/Mod/Draft/CMakeLists.txt index 20784b1efc..5ed416af38 100644 --- a/src/Mod/Draft/CMakeLists.txt +++ b/src/Mod/Draft/CMakeLists.txt @@ -105,6 +105,7 @@ SET(Modifier_tools draftguitools/gui_move.py draftguitools/gui_styles.py draftguitools/gui_rotate.py + draftguitools/gui_offset.py ) SET(Draft_GUI_tools diff --git a/src/Mod/Draft/DraftTools.py b/src/Mod/Draft/DraftTools.py index 720b054ded..14c906d040 100644 --- a/src/Mod/Draft/DraftTools.py +++ b/src/Mod/Draft/DraftTools.py @@ -177,200 +177,7 @@ from draftguitools.gui_subelements import SubelementHighlight from draftguitools.gui_move import Move from draftguitools.gui_styles import ApplyStyle from draftguitools.gui_rotate import Rotate - - -class Offset(Modifier): - """The Draft_Offset FreeCAD command definition""" - - def GetResources(self): - return {'Pixmap' : 'Draft_Offset', - 'Accel' : "O, S", - 'MenuText': QtCore.QT_TRANSLATE_NOOP("Draft_Offset", "Offset"), - 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Draft_Offset", "Offsets the active object. CTRL to snap, SHIFT to constrain, ALT to copy")} - - def Activated(self): - self.running = False - Modifier.Activated(self,"Offset") - self.ghost = None - self.linetrack = None - self.arctrack = None - if self.ui: - if not FreeCADGui.Selection.getSelection(): - self.ui.selectUi() - FreeCAD.Console.PrintMessage(translate("draft", "Select an object to offset")+"\n") - self.call = self.view.addEventCallback("SoEvent",selectObject) - elif len(FreeCADGui.Selection.getSelection()) > 1: - FreeCAD.Console.PrintWarning(translate("draft", "Offset only works on one object at a time")+"\n") - else: - self.proceed() - - def proceed(self): - if self.call: self.view.removeEventCallback("SoEvent",self.call) - self.sel = FreeCADGui.Selection.getSelection()[0] - if not self.sel.isDerivedFrom("Part::Feature"): - FreeCAD.Console.PrintWarning(translate("draft", "Cannot offset this object type")+"\n") - self.finish() - else: - self.step = 0 - self.dvec = None - self.npts = None - self.constrainSeg = None - self.ui.offsetUi() - self.linetrack = trackers.lineTracker() - self.faces = False - self.shape = self.sel.Shape - self.mode = None - if Draft.getType(self.sel) in ["Circle","Arc"]: - self.ghost = trackers.arcTracker() - self.mode = "Circle" - self.center = self.shape.Edges[0].Curve.Center - self.ghost.setCenter(self.center) - self.ghost.setStartAngle(math.radians(self.sel.FirstAngle)) - self.ghost.setEndAngle(math.radians(self.sel.LastAngle)) - elif Draft.getType(self.sel) == "BSpline": - self.ghost = trackers.bsplineTracker(points=self.sel.Points) - self.mode = "BSpline" - elif Draft.getType(self.sel) == "BezCurve": - FreeCAD.Console.PrintWarning(translate("draft", "Sorry, offset of Bezier curves is currently still not supported")+"\n") - self.finish() - return - else: - if len(self.sel.Shape.Edges) == 1: - import Part - if isinstance(self.sel.Shape.Edges[0].Curve,Part.Circle): - self.ghost = trackers.arcTracker() - self.mode = "Circle" - self.center = self.shape.Edges[0].Curve.Center - self.ghost.setCenter(self.center) - if len(self.sel.Shape.Vertexes) > 1: - self.ghost.setStartAngle(self.sel.Shape.Edges[0].FirstParameter) - self.ghost.setEndAngle(self.sel.Shape.Edges[0].LastParameter) - if not self.ghost: - self.ghost = trackers.wireTracker(self.shape) - self.mode = "Wire" - self.call = self.view.addEventCallback("SoEvent",self.action) - FreeCAD.Console.PrintMessage(translate("draft", "Pick distance")+"\n") - if self.planetrack: - self.planetrack.set(self.shape.Vertexes[0].Point) - self.running = True - - def action(self,arg): - """scene event handler""" - import DraftGeomUtils - if arg["Type"] == "SoKeyboardEvent": - if arg["Key"] == "ESCAPE": - self.finish() - elif arg["Type"] == "SoLocation2Event": - self.point,ctrlPoint,info = getPoint(self,arg) - if hasMod(arg,MODCONSTRAIN) and self.constrainSeg: - dist = DraftGeomUtils.findPerpendicular(self.point,self.shape,self.constrainSeg[1]) - else: - dist = DraftGeomUtils.findPerpendicular(self.point,self.shape.Edges) - if dist: - self.ghost.on() - if self.mode == "Wire": - d = dist[0].negative() - v1 = DraftGeomUtils.getTangent(self.shape.Edges[0],self.point) - v2 = DraftGeomUtils.getTangent(self.shape.Edges[dist[1]],self.point) - a = -DraftVecUtils.angle(v1,v2) - self.dvec = DraftVecUtils.rotate(d,a,plane.axis) - occmode = self.ui.occOffset.isChecked() - self.ghost.update(DraftGeomUtils.offsetWire(self.shape,self.dvec,occ=occmode),forceclosed=occmode) - elif self.mode == "BSpline": - d = dist[0].negative() - e = self.shape.Edges[0] - basetan = DraftGeomUtils.getTangent(e,self.point) - self.npts = [] - for p in self.sel.Points: - currtan = DraftGeomUtils.getTangent(e,p) - a = -DraftVecUtils.angle(currtan,basetan) - self.dvec = DraftVecUtils.rotate(d,a,plane.axis) - self.npts.append(p.add(self.dvec)) - self.ghost.update(self.npts) - elif self.mode == "Circle": - self.dvec = self.point.sub(self.center).Length - self.ghost.setRadius(self.dvec) - self.constrainSeg = dist - self.linetrack.on() - self.linetrack.p1(self.point) - self.linetrack.p2(self.point.add(dist[0])) - self.ui.setRadiusValue(dist[0].Length,unit="Length") - else: - self.dvec = None - self.ghost.off() - self.constrainSeg = None - self.linetrack.off() - self.ui.radiusValue.setText("off") - self.ui.radiusValue.setFocus() - self.ui.radiusValue.selectAll() - if self.extendedCopy: - if not hasMod(arg,MODALT): self.finish() - redraw3DView() - - elif arg["Type"] == "SoMouseButtonEvent": - if (arg["State"] == "DOWN") and (arg["Button"] == "BUTTON1"): - copymode = False - occmode = self.ui.occOffset.isChecked() - if hasMod(arg,MODALT) or self.ui.isCopy.isChecked(): copymode = True - FreeCADGui.addModule("Draft") - if self.npts: - print("offset:npts=",self.npts) - self.commit(translate("draft","Offset"), - ['Draft.offset(FreeCAD.ActiveDocument.'+self.sel.Name+','+DraftVecUtils.toString(self.npts)+',copy='+str(copymode)+')', - 'FreeCAD.ActiveDocument.recompute()']) - elif self.dvec: - if isinstance(self.dvec,float): - d = str(self.dvec) - else: - d = DraftVecUtils.toString(self.dvec) - self.commit(translate("draft","Offset"), - ['Draft.offset(FreeCAD.ActiveDocument.'+self.sel.Name+','+d+',copy='+str(copymode)+',occ='+str(occmode)+')', - 'FreeCAD.ActiveDocument.recompute()']) - if hasMod(arg,MODALT): - self.extendedCopy = True - else: - self.finish() - - def finish(self,closed=False): - if self.running: - if self.linetrack: - self.linetrack.finalize() - if self.ghost: - self.ghost.finalize() - Modifier.finish(self) - - def numericRadius(self,rad): - '''this function gets called by the toolbar when - valid radius have been entered there''' - #print("dvec:",self.dvec) - #print("rad:",rad) - if self.dvec: - if isinstance(self.dvec,float): - if self.mode == "Circle": - r1 = self.shape.Edges[0].Curve.Radius - r2 = self.ghost.getRadius() - if r2 >= r1: - rad = r1 + rad - else: - rad = r1 - rad - d = str(rad) - else: - print("Draft.Offset error: Unhandled case") - else: - self.dvec.normalize() - self.dvec.multiply(rad) - d = DraftVecUtils.toString(self.dvec) - copymode = False - occmode = self.ui.occOffset.isChecked() - if self.ui.isCopy.isChecked(): - copymode = True - FreeCADGui.addModule("Draft") - self.commit(translate("draft","Offset"), - ['Draft.offset(FreeCAD.ActiveDocument.'+self.sel.Name+','+d+',copy='+str(copymode)+',occ='+str(occmode)+')', - 'FreeCAD.ActiveDocument.recompute()']) - self.finish() - else: - FreeCAD.Console.PrintError(translate("draft","Offset direction is not defined. Please move the mouse on either side of the object first to indicate a direction")+"/n") +from draftguitools.gui_offset import Offset class Stretch(Modifier): @@ -2043,7 +1850,6 @@ from draftguitools.gui_snaps import ShowSnapBar # drawing commands # modification commands -FreeCADGui.addCommand('Draft_Offset',Offset()) FreeCADGui.addCommand('Draft_Join',Join()) FreeCADGui.addCommand('Draft_Split',Split()) FreeCADGui.addCommand('Draft_Upgrade',Upgrade()) diff --git a/src/Mod/Draft/draftguitools/gui_offset.py b/src/Mod/Draft/draftguitools/gui_offset.py new file mode 100644 index 0000000000..50f22b75a9 --- /dev/null +++ b/src/Mod/Draft/draftguitools/gui_offset.py @@ -0,0 +1,317 @@ +# *************************************************************************** +# * (c) 2009, 2010 Yorik van Havre * +# * (c) 2009, 2010 Ken Cline * +# * (c) 2020 Eliud Cabrera Castillo * +# * * +# * This file is part of the FreeCAD CAx development system. * +# * * +# * This program is free software; you can redistribute it and/or modify * +# * it under the terms of the GNU Lesser General Public License (LGPL) * +# * as published by the Free Software Foundation; either version 2 of * +# * the License, or (at your option) any later version. * +# * for detail see the LICENCE text file. * +# * * +# * FreeCAD 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 Library General Public License for more details. * +# * * +# * You should have received a copy of the GNU Library General Public * +# * License along with FreeCAD; if not, write to the Free Software * +# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * +# * USA * +# * * +# *************************************************************************** +"""Provides tools for offseting objects with the Draft Workbench. + +It mostly works on lines, polylines, and similar objects with +regular geometrical shapes, like rectangles. +""" +## @package gui_offset +# \ingroup DRAFT +# \brief Provides tools for offseting objects with the Draft Workbench. + +import math +from PySide.QtCore import QT_TRANSLATE_NOOP + +import FreeCAD as App +import FreeCADGui as Gui +import Draft_rc +import DraftVecUtils +import draftutils.utils as utils +import draftguitools.gui_base_original as gui_base_original +import draftguitools.gui_tool_utils as gui_tool_utils +import draftguitools.gui_trackers as trackers +from draftutils.messages import _msg, _wrn, _err +from draftutils.translate import translate, _tr + +# The module is used to prevent complaints from code checkers (flake8) +True if Draft_rc.__name__ else False + + +class Offset(gui_base_original.Modifier): + """Gui Command for the Offset tool.""" + + def GetResources(self): + """Set icon, menu and tooltip.""" + _tip = ("Offsets of the selected object.\n" + "It can also create an offset copy of the original object.\n" + "CTRL to snap, SHIFT to constrain. " + "Hold ALT and click to create a copy with each click.") + + return {'Pixmap': 'Draft_Offset', + 'Accel': "O, S", + 'MenuText': QT_TRANSLATE_NOOP("Draft_Offset", "Offset"), + 'ToolTip': QT_TRANSLATE_NOOP("Draft_Offset", _tip)} + + def Activated(self): + """Execute when the command is called.""" + self.running = False + super().Activated(name=_tr("Offset")) + self.ghost = None + self.linetrack = None + self.arctrack = None + if self.ui: + if not Gui.Selection.getSelection(): + self.ui.selectUi() + _msg(translate("draft", "Select an object to offset")) + self.call = \ + self.view.addEventCallback("SoEvent", + gui_tool_utils.selectObject) + elif len(Gui.Selection.getSelection()) > 1: + _wrn(translate("draft", "Offset only works " + "on one object at a time.")) + else: + self.proceed() + + def proceed(self): + """Proceed with the command if one object was selected.""" + if self.call: + self.view.removeEventCallback("SoEvent", self.call) + self.sel = Gui.Selection.getSelection()[0] + if not self.sel.isDerivedFrom("Part::Feature"): + _wrn(translate("draft", "Cannot offset this object type")) + self.finish() + else: + self.step = 0 + self.dvec = None + self.npts = None + self.constrainSeg = None + self.ui.offsetUi() + self.linetrack = trackers.lineTracker() + self.faces = False + self.shape = self.sel.Shape + self.mode = None + if utils.getType(self.sel) in ("Circle", "Arc"): + self.ghost = trackers.arcTracker() + self.mode = "Circle" + self.center = self.shape.Edges[0].Curve.Center + self.ghost.setCenter(self.center) + self.ghost.setStartAngle(math.radians(self.sel.FirstAngle)) + self.ghost.setEndAngle(math.radians(self.sel.LastAngle)) + elif utils.getType(self.sel) == "BSpline": + self.ghost = trackers.bsplineTracker(points=self.sel.Points) + self.mode = "BSpline" + elif utils.getType(self.sel) == "BezCurve": + _wrn(translate("draft", "Offset of Bezier curves " + "is currently not supported")) + self.finish() + return + else: + if len(self.sel.Shape.Edges) == 1: + import Part + + if isinstance(self.sel.Shape.Edges[0].Curve, Part.Circle): + self.ghost = trackers.arcTracker() + self.mode = "Circle" + self.center = self.shape.Edges[0].Curve.Center + self.ghost.setCenter(self.center) + if len(self.sel.Shape.Vertexes) > 1: + _edge = self.sel.Shape.Edges[0] + self.ghost.setStartAngle(_edge.FirstParameter) + self.ghost.setEndAngle(_edge.LastParameter) + if not self.ghost: + self.ghost = trackers.wireTracker(self.shape) + self.mode = "Wire" + self.call = self.view.addEventCallback("SoEvent", self.action) + _msg(translate("draft", "Pick distance")) + if self.planetrack: + self.planetrack.set(self.shape.Vertexes[0].Point) + self.running = True + + def action(self, arg): + """Handle the 3D scene events. + + This is installed as an EventCallback in the Inventor view. + + Parameters + ---------- + arg: dict + Dictionary with strings that indicates the type of event received + from the 3D view. + """ + import DraftGeomUtils + plane = App.DraftWorkingPlane + + if arg["Type"] == "SoKeyboardEvent": + if arg["Key"] == "ESCAPE": + self.finish() + elif arg["Type"] == "SoLocation2Event": + self.point, ctrlPoint, info = gui_tool_utils.getPoint(self, arg) + if (gui_tool_utils.hasMod(arg, gui_tool_utils.MODCONSTRAIN) + and self.constrainSeg): + dist = DraftGeomUtils.findPerpendicular(self.point, + self.shape, + self.constrainSeg[1]) + else: + dist = DraftGeomUtils.findPerpendicular(self.point, + self.shape.Edges) + if dist: + self.ghost.on() + if self.mode == "Wire": + d = dist[0].negative() + v1 = DraftGeomUtils.getTangent(self.shape.Edges[0], + self.point) + v2 = DraftGeomUtils.getTangent(self.shape.Edges[dist[1]], + self.point) + a = -DraftVecUtils.angle(v1, v2) + self.dvec = DraftVecUtils.rotate(d, a, plane.axis) + occmode = self.ui.occOffset.isChecked() + _wire = DraftGeomUtils.offsetWire(self.shape, + self.dvec, + occ=occmode) + self.ghost.update(_wire, forceclosed=occmode) + elif self.mode == "BSpline": + d = dist[0].negative() + e = self.shape.Edges[0] + basetan = DraftGeomUtils.getTangent(e, self.point) + self.npts = [] + for p in self.sel.Points: + currtan = DraftGeomUtils.getTangent(e, p) + a = -DraftVecUtils.angle(currtan, basetan) + self.dvec = DraftVecUtils.rotate(d, a, plane.axis) + self.npts.append(p.add(self.dvec)) + self.ghost.update(self.npts) + elif self.mode == "Circle": + self.dvec = self.point.sub(self.center).Length + self.ghost.setRadius(self.dvec) + self.constrainSeg = dist + self.linetrack.on() + self.linetrack.p1(self.point) + self.linetrack.p2(self.point.add(dist[0])) + self.ui.setRadiusValue(dist[0].Length, unit="Length") + else: + self.dvec = None + self.ghost.off() + self.constrainSeg = None + self.linetrack.off() + self.ui.radiusValue.setText("off") + self.ui.radiusValue.setFocus() + self.ui.radiusValue.selectAll() + if self.extendedCopy: + if not gui_tool_utils.hasMod(arg, gui_tool_utils.MODALT): + self.finish() + gui_tool_utils.redraw3DView() + + elif arg["Type"] == "SoMouseButtonEvent": + if (arg["State"] == "DOWN") and (arg["Button"] == "BUTTON1"): + copymode = False + occmode = self.ui.occOffset.isChecked() + if (gui_tool_utils.hasMod(arg, gui_tool_utils.MODALT) + or self.ui.isCopy.isChecked()): + copymode = True + Gui.addModule("Draft") + if self.npts: + # _msg("offset:npts= " + str(self.npts)) + _cmd = 'Draft.offset' + _cmd += '(' + _cmd += 'FreeCAD.ActiveDocument.' + _cmd += self.sel.Name + ', ' + _cmd += DraftVecUtils.toString(self.npts) + ', ' + _cmd += 'copy=' + str(copymode) + _cmd += ')' + _cmd_list = ['offst = ' + _cmd, + 'FreeCAD.ActiveDocument.recompute()'] + self.commit(translate("draft", "Offset"), + _cmd_list) + elif self.dvec: + if isinstance(self.dvec, float): + delta = str(self.dvec) + else: + delta = DraftVecUtils.toString(self.dvec) + _cmd = 'Draft.offset' + _cmd += '(' + _cmd += 'FreeCAD.ActiveDocument.' + _cmd += self.sel.Name + ', ' + _cmd += delta + ', ' + _cmd += 'copy=' + str(copymode) + ', ' + _cmd += 'occ=' + str(occmode) + _cmd += ')' + _cmd_list = ['offst = ' + _cmd, + 'FreeCAD.ActiveDocument.recompute()'] + self.commit(translate("draft", "Offset"), + _cmd_list) + if gui_tool_utils.hasMod(arg, gui_tool_utils.MODALT): + self.extendedCopy = True + else: + self.finish() + + def finish(self, closed=False): + """Finish the offset operation.""" + if self.running: + if self.linetrack: + self.linetrack.finalize() + if self.ghost: + self.ghost.finalize() + super().finish() + + def numericRadius(self, rad): + """Validate the radius entry field in the user interface. + + This function is called by the toolbar or taskpanel interface + when a valid radius has been entered in the input field. + """ + # print("dvec:", self.dvec) + # print("rad:", rad) + if self.dvec: + if isinstance(self.dvec, float): + if self.mode == "Circle": + r1 = self.shape.Edges[0].Curve.Radius + r2 = self.ghost.getRadius() + if r2 >= r1: + rad = r1 + rad + else: + rad = r1 - rad + delta = str(rad) + else: + _err("Draft.Offset error: Unhandled case") + else: + self.dvec.normalize() + self.dvec.multiply(rad) + delta = DraftVecUtils.toString(self.dvec) + copymode = False + occmode = self.ui.occOffset.isChecked() + if self.ui.isCopy.isChecked(): + copymode = True + Gui.addModule("Draft") + _cmd = 'Draft.offset' + _cmd += '(' + _cmd += 'FreeCAD.ActiveDocument.' + _cmd += self.sel.Name + ', ' + _cmd += delta + ', ' + _cmd += 'copy=' + str(copymode) + ', ' + _cmd += 'occ=' + str(occmode) + _cmd += ')' + _cmd_list = ['offst = ' + _cmd, + 'FreeCAD.ActiveDocument.recompute()'] + self.commit(translate("draft", "Offset"), + _cmd_list) + self.finish() + else: + _err(translate("Draft", + "Offset direction is not defined. " + "Please move the mouse on either side " + "of the object first to indicate a direction")) + + +Gui.addCommand('Draft_Offset', Offset())