diff --git a/src/Mod/Draft/CMakeLists.txt b/src/Mod/Draft/CMakeLists.txt index 900205eb74..b70e0c7049 100644 --- a/src/Mod/Draft/CMakeLists.txt +++ b/src/Mod/Draft/CMakeLists.txt @@ -83,6 +83,10 @@ SET(Draft_view_providers draftviewproviders/README.md ) +SET(Creator_tools + draftguitools/gui_lines.py +) + SET(Draft_GUI_tools draftguitools/__init__.py draftguitools/gui_base.py @@ -106,6 +110,7 @@ SET(Draft_GUI_tools draftguitools/gui_dimension_ops.py draftguitools/gui_lineslope.py draftguitools/gui_arcs.py + ${Creator_tools} draftguitools/README.md ) diff --git a/src/Mod/Draft/DraftTools.py b/src/Mod/Draft/DraftTools.py index be8990d108..4dcc117f7f 100644 --- a/src/Mod/Draft/DraftTools.py +++ b/src/Mod/Draft/DraftTools.py @@ -150,194 +150,8 @@ from draftguitools.gui_tool_utils import redraw3DView from draftguitools.gui_base_original import Creator +from draftguitools.gui_lines import Line -class Line(Creator): - """The Line FreeCAD command definition""" - - def __init__(self, wiremode=False): - Creator.__init__(self) - self.isWire = wiremode - - def GetResources(self): - return {'Pixmap': 'Draft_Line', - 'Accel': "L,I", - 'MenuText': QtCore.QT_TRANSLATE_NOOP("Draft_Line", "Line"), - 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Draft_Line", "Creates a 2-point line. CTRL to snap, SHIFT to constrain")} - - def Activated(self,name=translate("draft","Line")): - Creator.Activated(self,name) - if not self.doc: - return - self.obj = None # stores the temp shape - self.oldWP = None # stores the WP if we modify it - if self.isWire: - self.ui.wireUi(name) - else: - self.ui.lineUi(name) - self.ui.setTitle(translate("draft", "Line")) - if sys.version_info.major < 3: - if isinstance(self.featureName,unicode): - self.featureName = self.featureName.encode("utf8") - self.obj=self.doc.addObject("Part::Feature",self.featureName) - Draft.formatObject(self.obj) - self.call = self.view.addEventCallback("SoEvent", self.action) - FreeCAD.Console.PrintMessage(translate("draft", "Pick first point")+"\n") - - def action(self, arg): - """scene event handler""" - if arg["Type"] == "SoKeyboardEvent" and arg["Key"] == "ESCAPE": - self.finish() - elif arg["Type"] == "SoLocation2Event": - self.point, ctrlPoint, info = getPoint(self, arg) - redraw3DView() - elif arg["Type"] == "SoMouseButtonEvent" and \ - arg["State"] == "DOWN" and \ - arg["Button"] == "BUTTON1": - if (arg["Position"] == self.pos): - return self.finish(False,cont=True) - if (not self.node) and (not self.support): - getSupport(arg) - self.point,ctrlPoint,info = getPoint(self,arg) - if self.point: - self.ui.redraw() - self.pos = arg["Position"] - self.node.append(self.point) - self.drawSegment(self.point) - if (not self.isWire and len(self.node) == 2): - self.finish(False,cont=True) - if (len(self.node) > 2): - if ((self.point-self.node[0]).Length < Draft.tolerance()): - self.undolast() - self.finish(True,cont=True) - - def finish(self,closed=False,cont=False): - """terminates the operation and closes the poly if asked""" - self.removeTemporaryObject() - if self.oldWP: - FreeCAD.DraftWorkingPlane = self.oldWP - if hasattr(FreeCADGui,"Snapper"): - FreeCADGui.Snapper.setGrid() - FreeCADGui.Snapper.restack() - self.oldWP = None - if (len(self.node) > 1): - FreeCADGui.addModule("Draft") - if (len(self.node) == 2) and Draft.getParam("UsePartPrimitives",False): - # use Part primitive - p1 = self.node[0] - p2 = self.node[-1] - self.commit(translate("draft","Create Line"), - ['line = FreeCAD.ActiveDocument.addObject("Part::Line","Line")', - 'line.X1 = '+str(p1.x), - 'line.Y1 = '+str(p1.y), - 'line.Z1 = '+str(p1.z), - 'line.X2 = '+str(p2.x), - 'line.Y2 = '+str(p2.y), - 'line.Z2 = '+str(p2.z), - 'Draft.autogroup(line)', - 'FreeCAD.ActiveDocument.recompute()']) - else: - # building command string - rot,sup,pts,fil = self.getStrings() - self.commit(translate("draft","Create Wire"), - ['pl = FreeCAD.Placement()', - 'pl.Rotation.Q = '+rot, - 'pl.Base = '+DraftVecUtils.toString(self.node[0]), - 'points = '+pts, - 'line = Draft.makeWire(points,placement=pl,closed='+str(closed)+',face='+fil+',support='+sup+')', - 'Draft.autogroup(line)', - 'FreeCAD.ActiveDocument.recompute()']) - Creator.finish(self) - if self.ui and self.ui.continueMode: - self.Activated() - - def removeTemporaryObject(self): - if self.obj: - try: - old = self.obj.Name - except ReferenceError: - # object already deleted, for some reason - pass - else: - ToDo.delay(self.doc.removeObject, old) - self.obj = None - - def undolast(self): - """undoes last line segment""" - import Part - if (len(self.node) > 1): - self.node.pop() - last = self.node[-1] - if self.obj.Shape.Edges: - edges = self.obj.Shape.Edges - if len(edges) > 1: - newshape = Part.makePolygon(self.node) - self.obj.Shape = newshape - else: - self.obj.ViewObject.hide() - # DNC: report on removal - #FreeCAD.Console.PrintMessage(translate("draft", "Removing last point")+"\n") - FreeCAD.Console.PrintMessage(translate("draft", "Pick next point")+"\n") - - def drawSegment(self,point): - """draws a new segment""" - import Part - if self.planetrack and self.node: - self.planetrack.set(self.node[-1]) - if (len(self.node) == 1): - FreeCAD.Console.PrintMessage(translate("draft", "Pick next point")+"\n") - elif (len(self.node) == 2): - last = self.node[len(self.node)-2] - newseg = Part.LineSegment(last,point).toShape() - self.obj.Shape = newseg - self.obj.ViewObject.Visibility = True - if self.isWire: - FreeCAD.Console.PrintMessage(translate("draft", "Pick next point")+"\n") - else: - currentshape = self.obj.Shape.copy() - last = self.node[len(self.node)-2] - if not DraftVecUtils.equals(last,point): - newseg = Part.LineSegment(last,point).toShape() - newshape=currentshape.fuse(newseg) - self.obj.Shape = newshape - FreeCAD.Console.PrintMessage(translate("draft", "Pick next point")+"\n") - - def wipe(self): - """removes all previous segments and starts from last point""" - if len(self.node) > 1: - # self.obj.Shape.nullify() - for some reason this fails - self.obj.ViewObject.Visibility = False - self.node = [self.node[-1]] - if self.planetrack: - self.planetrack.set(self.node[0]) - FreeCAD.Console.PrintMessage(translate("draft", "Pick next point")+"\n") - - def orientWP(self): - if hasattr(FreeCAD,"DraftWorkingPlane"): - if (len(self.node) > 1) and self.obj: - import DraftGeomUtils - n = DraftGeomUtils.getNormal(self.obj.Shape) - if not n: - n = FreeCAD.DraftWorkingPlane.axis - p = self.node[-1] - v = self.node[-2].sub(self.node[-1]) - v = v.negative() - if not self.oldWP: - self.oldWP = FreeCAD.DraftWorkingPlane.copy() - FreeCAD.DraftWorkingPlane.alignToPointAndAxis(p,n,upvec=v) - if hasattr(FreeCADGui,"Snapper"): - FreeCADGui.Snapper.setGrid() - FreeCADGui.Snapper.restack() - if self.planetrack: - self.planetrack.set(self.node[-1]) - - def numericInput(self,numx,numy,numz): - """this function gets called by the toolbar when valid x, y, and z have been entered there""" - self.point = Vector(numx,numy,numz) - self.node.append(self.point) - self.drawSegment(self.point) - if (not self.isWire and len(self.node) == 2): - self.finish(False,cont=True) - self.ui.setNextFocus() class Wire(Line): """a FreeCAD command for creating a wire""" @@ -4806,8 +4620,6 @@ from draftguitools.gui_snaps import ShowSnapBar #--------------------------------------------------------------------------- # drawing commands - -FreeCADGui.addCommand('Draft_Line',Line()) FreeCADGui.addCommand('Draft_Wire',Wire()) FreeCADGui.addCommand('Draft_Circle',Circle()) class CommandArcGroup: diff --git a/src/Mod/Draft/draftguitools/gui_lines.py b/src/Mod/Draft/draftguitools/gui_lines.py new file mode 100644 index 0000000000..e0220b9e89 --- /dev/null +++ b/src/Mod/Draft/draftguitools/gui_lines.py @@ -0,0 +1,287 @@ +# *************************************************************************** +# * (c) 2009 Yorik van Havre * +# * (c) 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 creating straight lines with the Draft Workbench. + +The Line class is used by other Gui Commands that behave in a similar way +like Wire, BSpline, and BezCurve. +""" +## @package gui_lines +# \ingroup DRAFT +# \brief Provides tools for creating straight lines with the Draft Workbench. + +from PySide.QtCore import QT_TRANSLATE_NOOP +import sys + +import FreeCAD as App +import FreeCADGui as Gui +import DraftVecUtils +import draftutils.utils as utils +import draftutils.gui_utils as gui_utils +import draftutils.todo as todo +import draftguitools.gui_base_original as gui_base_original +import draftguitools.gui_tool_utils as gui_tool_utils +from draftutils.messages import _msg +from draftutils.translate import translate + + +class Line(gui_base_original.Creator): + """Gui command for the Line tool.""" + + def __init__(self, wiremode=False): + super().__init__() + self.isWire = wiremode + + def GetResources(self): + """Set icon, menu and tooltip.""" + _tip = "Creates a 2-point line. CTRL to snap, SHIFT to constrain." + + return {'Pixmap': 'Draft_Line', + 'Accel': "L,I", + 'MenuText': QT_TRANSLATE_NOOP("Draft_Line", "Line"), + 'ToolTip': QT_TRANSLATE_NOOP("Draft_Line", _tip)} + + def Activated(self, name=translate("draft", "Line")): + """Execute when the command is called.""" + super().Activated(name) + + if not self.doc: + return + self.obj = None # stores the temp shape + self.oldWP = None # stores the WP if we modify it + if self.isWire: + self.ui.wireUi(name) + else: + self.ui.lineUi(name) + self.ui.setTitle(translate("draft", "Line")) + + if sys.version_info.major < 3: + if isinstance(self.featureName, unicode): + self.featureName = self.featureName.encode("utf8") + + self.obj = self.doc.addObject("Part::Feature", self.featureName) + gui_utils.format_object(self.obj) + + self.call = self.view.addEventCallback("SoEvent", self.action) + _msg(translate("draft", "Pick first point")) + + 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. + """ + if arg["Type"] == "SoKeyboardEvent" and arg["Key"] == "ESCAPE": + self.finish() + elif arg["Type"] == "SoLocation2Event": + self.point, ctrlPoint, info = gui_tool_utils.getPoint(self, arg) + gui_tool_utils.redraw3DView() + elif (arg["Type"] == "SoMouseButtonEvent" + and arg["State"] == "DOWN" + and arg["Button"] == "BUTTON1"): + if arg["Position"] == self.pos: + return self.finish(False, cont=True) + if (not self.node) and (not self.support): + gui_tool_utils.getSupport(arg) + (self.point, + ctrlPoint, info) = gui_tool_utils.getPoint(self, arg) + if self.point: + self.ui.redraw() + self.pos = arg["Position"] + self.node.append(self.point) + self.drawSegment(self.point) + if not self.isWire and len(self.node) == 2: + self.finish(False, cont=True) + if len(self.node) > 2: + if (self.point - self.node[0]).Length < utils.tolerance(): + self.undolast() + self.finish(True, cont=True) + + def finish(self, closed=False, cont=False): + """Terminate the operation and close the polyline if asked. + + Parameters + ---------- + closed: bool, optional + Close the line if `True`. + """ + self.removeTemporaryObject() + if self.oldWP: + App.DraftWorkingPlane = self.oldWP + if hasattr(Gui, "Snapper"): + Gui.Snapper.setGrid() + Gui.Snapper.restack() + self.oldWP = None + + if len(self.node) > 1: + Gui.addModule("Draft") + # The command to run is built as a series of text strings + # to be commited through the `draftutils.todo.ToDo` class. + if (len(self.node) == 2 + and utils.getParam("UsePartPrimitives", False)): + # Insert a Part::Primitive object + p1 = self.node[0] + p2 = self.node[-1] + + _cmd = 'FreeCAD.ActiveDocument.' + _cmd += 'addObject("Part::Line", "Line")' + _cmd_list = ['line = ' + _cmd, + 'line.X1 = ' + str(p1.x), + 'line.Y1 = ' + str(p1.y), + 'line.Z1 = ' + str(p1.z), + 'line.X2 = ' + str(p2.x), + 'line.Y2 = ' + str(p2.y), + 'line.Z2 = ' + str(p2.z), + 'Draft.autogroup(line)', + 'FreeCAD.ActiveDocument.recompute()'] + self.commit(translate("draft", "Create Line"), + _cmd_list) + else: + # Insert a Draft line + rot, sup, pts, fil = self.getStrings() + + _base = DraftVecUtils.toString(self.node[0]) + _cmd = 'Draft.makeWire' + _cmd += '(' + _cmd += 'points, ' + _cmd += 'placement=pl, ' + _cmd += 'closed=' + str(closed) + ', ' + _cmd += 'face=' + fil + ', ' + _cmd += 'support=' + sup + _cmd += ')' + _cmd_list = ['pl = FreeCAD.Placement()', + 'pl.Rotation.Q = ' + rot, + 'pl.Base = ' + _base, + 'points = ' + pts, + 'line = ' + _cmd, + 'Draft.autogroup(line)', + 'FreeCAD.ActiveDocument.recompute()'] + self.commit(translate("draft", "Create Wire"), + _cmd_list) + super().finish() + if self.ui and self.ui.continueMode: + self.Activated() + + def removeTemporaryObject(self): + """Remove temporary object created.""" + if self.obj: + try: + old = self.obj.Name + except ReferenceError: + # object already deleted, for some reason + pass + else: + todo.ToDo.delay(self.doc.removeObject, old) + self.obj = None + + def undolast(self): + """Undoes last line segment.""" + import Part + if len(self.node) > 1: + self.node.pop() + # last = self.node[-1] + if self.obj.Shape.Edges: + edges = self.obj.Shape.Edges + if len(edges) > 1: + newshape = Part.makePolygon(self.node) + self.obj.Shape = newshape + else: + self.obj.ViewObject.hide() + # DNC: report on removal + # _msg(translate("draft", "Removing last point")) + _msg(translate("draft", "Pick next point")) + + def drawSegment(self, point): + """Draws new line segment.""" + import Part + if self.planetrack and self.node: + self.planetrack.set(self.node[-1]) + if len(self.node) == 1: + _msg(translate("draft", "Pick next point")) + elif len(self.node) == 2: + last = self.node[len(self.node) - 2] + newseg = Part.LineSegment(last, point).toShape() + self.obj.Shape = newseg + self.obj.ViewObject.Visibility = True + if self.isWire: + _msg(translate("draft", "Pick next point")) + else: + currentshape = self.obj.Shape.copy() + last = self.node[len(self.node) - 2] + if not DraftVecUtils.equals(last, point): + newseg = Part.LineSegment(last, point).toShape() + newshape = currentshape.fuse(newseg) + self.obj.Shape = newshape + _msg(translate("draft", "Pick next point")) + + def wipe(self): + """Remove all previous segments and starts from last point.""" + if len(self.node) > 1: + # self.obj.Shape.nullify() # For some reason this fails + self.obj.ViewObject.Visibility = False + self.node = [self.node[-1]] + if self.planetrack: + self.planetrack.set(self.node[0]) + _msg(translate("draft", "Pick next point")) + + def orientWP(self): + """Orient the working plane.""" + import DraftGeomUtils + if hasattr(App, "DraftWorkingPlane"): + if len(self.node) > 1 and self.obj: + n = DraftGeomUtils.getNormal(self.obj.Shape) + if not n: + n = App.DraftWorkingPlane.axis + p = self.node[-1] + v = self.node[-2].sub(self.node[-1]) + v = v.negative() + if not self.oldWP: + self.oldWP = App.DraftWorkingPlane.copy() + App.DraftWorkingPlane.alignToPointAndAxis(p, n, upvec=v) + if hasattr(Gui, "Snapper"): + Gui.Snapper.setGrid() + Gui.Snapper.restack() + if self.planetrack: + self.planetrack.set(self.node[-1]) + + def numericInput(self, numx, numy, numz): + """Validate the entry fields in the user interface. + + This function is called by the toolbar or taskpanel interface + when valid x, y, and z have been entered in the input fields. + """ + self.point = App.Vector(numx, numy, numz) + self.node.append(self.point) + self.drawSegment(self.point) + if not self.isWire and len(self.node) == 2: + self.finish(False, cont=True) + self.ui.setNextFocus() + + +Gui.addCommand('Draft_Line', Line())