diff --git a/src/Mod/Draft/CMakeLists.txt b/src/Mod/Draft/CMakeLists.txt index 080a68063d..8cd5bcef7b 100644 --- a/src/Mod/Draft/CMakeLists.txt +++ b/src/Mod/Draft/CMakeLists.txt @@ -48,7 +48,6 @@ SET (Draft_geoutils SET(Draft_tests drafttests/__init__.py - drafttests/README.md drafttests/auxiliary.py drafttests/draft_test_objects.py drafttests/test_airfoildat.py @@ -65,22 +64,24 @@ SET(Draft_tests drafttests/test_oca.py drafttests/test_pivy.py drafttests/test_svg.py + drafttests/README.md ) SET(Draft_utilities draftutils/__init__.py + draftutils/doc_observer.py + draftutils/grid_observer.py draftutils/groups.py - draftutils/init_tools.py - draftutils/init_draft_statusbar.py - draftutils/params.py - draftutils/units.py - draftutils/utils.py draftutils/gui_utils.py + draftutils/init_draft_statusbar.py + draftutils/init_tools.py + draftutils/messages.py + draftutils/params.py draftutils/todo.py draftutils/translate.py - draftutils/messages.py + draftutils/units.py + draftutils/utils.py draftutils/README.md - draftutils/grid_observer.py ) SET(Draft_functions diff --git a/src/Mod/Draft/InitGui.py b/src/Mod/Draft/InitGui.py index ee5431fd0b..ee6c4de252 100644 --- a/src/Mod/Draft/InitGui.py +++ b/src/Mod/Draft/InitGui.py @@ -157,6 +157,8 @@ class DraftWorkbench(FreeCADGui.Workbench): WorkingPlane._view_observer_start() # Updates the draftToolBar when switching views. from draftutils import grid_observer grid_observer._view_observer_setup() + from draftutils import doc_observer + doc_observer._doc_observer_start() FreeCAD.Console.PrintLog("Draft workbench activated.\n") def Deactivated(self): @@ -171,6 +173,8 @@ class DraftWorkbench(FreeCADGui.Workbench): WorkingPlane._view_observer_stop() from draftutils import grid_observer grid_observer._view_observer_setup() + from draftutils import doc_observer + doc_observer._doc_observer_stop() FreeCAD.Console.PrintLog("Draft workbench deactivated.\n") def ContextMenu(self, recipient): diff --git a/src/Mod/Draft/draftguitools/gui_arcs.py b/src/Mod/Draft/draftguitools/gui_arcs.py index 44b5b0e5cc..6d5deb385c 100644 --- a/src/Mod/Draft/draftguitools/gui_arcs.py +++ b/src/Mod/Draft/draftguitools/gui_arcs.py @@ -470,19 +470,19 @@ Gui.addCommand('Draft_Arc', Arc()) class Arc_3Points(gui_base.GuiCommandBase): """GuiCommand for the Draft_Arc_3Points tool.""" + def __init__(self): + super().__init__(name="Arc_3Points") + def GetResources(self): """Set icon, menu and tooltip.""" return {"Pixmap": "Draft_Arc_3Points", - "Accel": "A,T", + "Accel": "A, T", "MenuText": QT_TRANSLATE_NOOP("Draft_Arc_3Points", "Arc by 3 points"), "ToolTip": QT_TRANSLATE_NOOP("Draft_Arc_3Points", "Creates a circular arc by picking 3 points.\nCTRL to snap, SHIFT to constrain.")} def Activated(self): """Execute when the command is called.""" - if App.activeDraftCommand: - App.activeDraftCommand.finish() - App.activeDraftCommand = self - self.featureName = "Arc_3Points" + super().Activated() # Reset the values self.points = [] @@ -498,10 +498,11 @@ class Arc_3Points(gui_base.GuiCommandBase): Gui.Snapper.getPoint(callback=self.getPoint, movecallback=self.drawArc) - Gui.Snapper.ui.sourceCmd = self - Gui.Snapper.ui.setTitle(title=translate("draft", "Arc by 3 points"), - icon="Draft_Arc_3Points") - Gui.Snapper.ui.continueCmd.show() + self.ui = Gui.Snapper.ui ## self must have a ui for _finish_command_on_doc_close in doc_observer.py. + self.ui.sourceCmd = self + self.ui.setTitle(title=translate("draft", "Arc by 3 points"), + icon="Draft_Arc_3Points") + self.ui.continueCmd.show() def getPoint(self, point, info): """Get the point by clicking on the 3D view. @@ -527,6 +528,8 @@ class Arc_3Points(gui_base.GuiCommandBase): # Avoid adding the same point twice if point not in self.points: self.points.append(point) + if self.planetrack and len(self.points) == 1: + self.planetrack.set(point) if len(self.points) < 3: # If one or two points were picked, set up again the Snapper @@ -540,10 +543,11 @@ class Arc_3Points(gui_base.GuiCommandBase): Gui.Snapper.getPoint(last=self.points[-1], callback=self.getPoint, movecallback=self.drawArc) - Gui.Snapper.ui.sourceCmd = self - Gui.Snapper.ui.setTitle(title=translate("draft", "Arc by 3 points"), - icon="Draft_Arc_3Points") - Gui.Snapper.ui.continueCmd.show() + self.ui = Gui.Snapper.ui + self.ui.sourceCmd = self + self.ui.setTitle(title=translate("draft", "Arc by 3 points"), + icon="Draft_Arc_3Points") + self.ui.continueCmd.show() else: # If three points were already picked in the 3D view @@ -592,7 +596,6 @@ class Arc_3Points(gui_base.GuiCommandBase): Restart (continue) the command if `True`, or if `None` and `ui.continueMode` is `True`. """ - App.activeDraftCommand = None self.tracker.finalize() super().finish() if cont or (cont is None and Gui.Snapper.ui and Gui.Snapper.ui.continueMode): diff --git a/src/Mod/Draft/draftguitools/gui_base.py b/src/Mod/Draft/draftguitools/gui_base.py index 82a1039f2f..3cdc4c8bce 100644 --- a/src/Mod/Draft/draftguitools/gui_base.py +++ b/src/Mod/Draft/draftguitools/gui_base.py @@ -31,7 +31,9 @@ # @{ import FreeCAD as App import FreeCADGui as Gui +from draftguitools import gui_trackers as trackers from draftutils import gui_utils +from draftutils import params from draftutils import todo from draftutils.messages import _toolmsg, _log @@ -60,30 +62,20 @@ class GuiCommandSimplest: for example, `'Heal'`, `'Flip dimensions'`, `'Line'`, `'Circle'`, etc. - doc: App::Document, optional - It defaults to the value of `App.activeDocument()`. - The document object itself, which indicates where the actions - of the command will be executed. - Attributes ---------- - command_name: str + featureName: str This is the command name, which is assigned by `name`. doc: App::Document - This is the document object itself, which is assigned by `doc`. - This attribute should be used by functions to make sure that the operations are performed in the correct document and not in other documents. - To set the active document we can use - - >>> App.setActiveDocument(self.doc.Name) """ - def __init__(self, name="None", doc=App.activeDocument()): - self.command_name = name - self.doc = doc + def __init__(self, name="None"): + self.doc = None + self.featureName = name def IsActive(self): """Return True when this command should be available.""" @@ -96,10 +88,8 @@ class GuiCommandSimplest: Also update the `doc` attribute. """ self.doc = App.activeDocument() - _log("Document: {}".format(self.doc.Label)) - _log("GuiCommand: {}".format(self.command_name)) _toolmsg("{}".format(16*"-")) - _toolmsg("GuiCommand: {}".format(self.command_name)) + _toolmsg("GuiCommand: {}".format(self.featureName)) class GuiCommandNeedsSelection(GuiCommandSimplest): @@ -153,18 +143,34 @@ class GuiCommandBase: >>> Draft.autogroup(obj) """ - def __init__(self): + def __init__(self, name="None"): + App.activeDraftCommand = None self.call = None self.commit_list = [] self.doc = None - App.activeDraftCommand = None - self.view = None + self.featureName = name self.planetrack = None + self.view = None def IsActive(self): """Return True when this command should be available.""" return bool(gui_utils.get_3d_view()) + def Activated(self): + self.doc = App.ActiveDocument + if not self.doc: + self.finish() + return + + App.activeDraftCommand = self + self.view = gui_utils.get_3d_view() + + if params.get_param("showPlaneTracker"): + self.planetrack = trackers.PlaneTracker() + + _toolmsg("{}".format(16*"-")) + _toolmsg("GuiCommand: {}".format(self.featureName)) + def finish(self): """Terminate the active command by committing the list of commands. @@ -174,6 +180,7 @@ class GuiCommandBase: App.activeDraftCommand = None if self.planetrack: self.planetrack.finalize() + self.planetrack = None if hasattr(Gui, "Snapper"): Gui.Snapper.off() if self.call: @@ -182,7 +189,7 @@ class GuiCommandBase: except RuntimeError: # the view has been deleted already pass - self.call = None + self.call = None if self.commit_list: todo.ToDo.delayCommit(self.commit_list) self.commit_list = [] diff --git a/src/Mod/Draft/draftguitools/gui_circulararray.py b/src/Mod/Draft/draftguitools/gui_circulararray.py index 3137c4beb6..e4bb3ab605 100644 --- a/src/Mod/Draft/draftguitools/gui_circulararray.py +++ b/src/Mod/Draft/draftguitools/gui_circulararray.py @@ -32,8 +32,6 @@ from PySide.QtCore import QT_TRANSLATE_NOOP import FreeCAD as App import FreeCADGui as Gui -import Draft -import Draft_rc # include resources, icons, ui files from draftguitools import gui_base from draftutils import gui_utils from draftutils import todo @@ -41,19 +39,14 @@ from draftutils.messages import _log from draftutils.translate import translate from drafttaskpanels import task_circulararray -# The module is used to prevent complaints from code checkers (flake8) -bool(Draft_rc.__name__) - class CircularArray(gui_base.GuiCommandBase): """Gui command for the CircularArray tool.""" def __init__(self): - super().__init__() - self.command_name = "Circular array" + super().__init__(name="CircularArray") self.location = None self.mouse_event = None - self.view = None self.callback_move = None self.callback_click = None self.ui = None @@ -61,9 +54,9 @@ class CircularArray(gui_base.GuiCommandBase): def GetResources(self): """Set icon, menu and tooltip.""" - return {'Pixmap': 'Draft_CircularArray', - 'MenuText': QT_TRANSLATE_NOOP("Draft_CircularArray", "Circular array"), - 'ToolTip': QT_TRANSLATE_NOOP("Draft_CircularArray", "Creates copies of the selected object, and places the copies in a radial pattern\ncreating various circular layers.\n\nThe array can be turned into an orthogonal or a polar array by changing its type.")} + return {"Pixmap": "Draft_CircularArray", + "MenuText": QT_TRANSLATE_NOOP("Draft_CircularArray", "Circular array"), + "ToolTip": QT_TRANSLATE_NOOP("Draft_CircularArray", "Creates copies of the selected object, and places the copies in a radial pattern\ncreating various circular layers.\n\nThe array can be turned into an orthogonal or a polar array by changing its type.")} def Activated(self): """Execute when the command is called. @@ -71,11 +64,10 @@ class CircularArray(gui_base.GuiCommandBase): We add callbacks that connect the 3D view with the widgets of the task panel. """ - _log("GuiCommand: {}".format(self.command_name)) + super().Activated() self.location = coin.SoLocation2Event.getClassTypeId() self.mouse_event = coin.SoMouseButtonEvent.getClassTypeId() - self.view = Draft.get3DView() self.callback_move = \ self.view.addEventCallbackPivy(self.location, self.move) self.callback_click = \ @@ -135,7 +127,7 @@ class CircularArray(gui_base.GuiCommandBase): self.callback_click = None if Gui.Control.activeDialog(): Gui.Control.closeDialog() - self.finish() + self.finish() Gui.addCommand('Draft_CircularArray', CircularArray()) diff --git a/src/Mod/Draft/draftguitools/gui_orthoarray.py b/src/Mod/Draft/draftguitools/gui_orthoarray.py index 5b6ca08aba..ff3ef853df 100644 --- a/src/Mod/Draft/draftguitools/gui_orthoarray.py +++ b/src/Mod/Draft/draftguitools/gui_orthoarray.py @@ -32,8 +32,6 @@ from PySide.QtCore import QT_TRANSLATE_NOOP import FreeCAD as App import FreeCADGui as Gui -import Draft -import Draft_rc # include resources, icons, ui files from draftguitools import gui_base from draftutils import gui_utils from draftutils import todo @@ -41,19 +39,14 @@ from draftutils.messages import _log from draftutils.translate import translate from drafttaskpanels import task_orthoarray -# The module is used to prevent complaints from code checkers (flake8) -bool(Draft_rc.__name__) - class OrthoArray(gui_base.GuiCommandBase): """Gui command for the OrthoArray tool.""" def __init__(self): - super().__init__() - self.command_name = "Orthogonal array" + super().__init__(name="OrthoArray") # self.location = None self.mouse_event = None - self.view = None # self.callback_move = None self.callback_click = None self.ui = None @@ -71,11 +64,10 @@ class OrthoArray(gui_base.GuiCommandBase): We add callbacks that connect the 3D view with the widgets of the task panel. """ - _log("GuiCommand: {}".format(self.command_name)) + super().Activated() # self.location = coin.SoLocation2Event.getClassTypeId() self.mouse_event = coin.SoMouseButtonEvent.getClassTypeId() - self.view = Draft.get3DView() # self.callback_move = \ # self.view.addEventCallbackPivy(self.location, self.move) self.callback_click = \ @@ -120,7 +112,7 @@ class OrthoArray(gui_base.GuiCommandBase): self.callback_click = None if Gui.Control.activeDialog(): Gui.Control.closeDialog() - self.finish() + self.finish() Gui.addCommand('Draft_OrthoArray', OrthoArray()) diff --git a/src/Mod/Draft/draftguitools/gui_polararray.py b/src/Mod/Draft/draftguitools/gui_polararray.py index 89046d4e0c..6d769878b0 100644 --- a/src/Mod/Draft/draftguitools/gui_polararray.py +++ b/src/Mod/Draft/draftguitools/gui_polararray.py @@ -32,8 +32,6 @@ from PySide.QtCore import QT_TRANSLATE_NOOP import FreeCAD as App import FreeCADGui as Gui -import Draft -import Draft_rc # include resources, icons, ui files from draftguitools import gui_base from draftutils import gui_utils from draftutils import todo @@ -41,19 +39,14 @@ from draftutils.messages import _log from draftutils.translate import translate from drafttaskpanels import task_polararray -# The module is used to prevent complaints from code checkers (flake8) -bool(Draft_rc.__name__) - class PolarArray(gui_base.GuiCommandBase): """Gui command for the PolarArray tool.""" def __init__(self): - super().__init__() - self.command_name = "Polar array" + super().__init__(name="PolarArray") self.location = None self.mouse_event = None - self.view = None self.callback_move = None self.callback_click = None self.ui = None @@ -61,9 +54,9 @@ class PolarArray(gui_base.GuiCommandBase): def GetResources(self): """Set icon, menu and tooltip.""" - return {'Pixmap': 'Draft_PolarArray', - 'MenuText': QT_TRANSLATE_NOOP("Draft_PolarArray", "Polar array"), - 'ToolTip': QT_TRANSLATE_NOOP("Draft_PolarArray", "Creates copies of the selected object, and places the copies in a polar pattern\ndefined by a center of rotation and its angle.\n\nThe array can be turned into an orthogonal or a circular array by changing its type.")} + return {"Pixmap": "Draft_PolarArray", + "MenuText": QT_TRANSLATE_NOOP("Draft_PolarArray", "Polar array"), + "ToolTip": QT_TRANSLATE_NOOP("Draft_PolarArray", "Creates copies of the selected object, and places the copies in a polar pattern\ndefined by a center of rotation and its angle.\n\nThe array can be turned into an orthogonal or a circular array by changing its type.")} def Activated(self): """Execute when the command is called. @@ -71,11 +64,10 @@ class PolarArray(gui_base.GuiCommandBase): We add callbacks that connect the 3D view with the widgets of the task panel. """ - _log("GuiCommand: {}".format(self.command_name)) + super().Activated() self.location = coin.SoLocation2Event.getClassTypeId() self.mouse_event = coin.SoMouseButtonEvent.getClassTypeId() - self.view = Draft.get3DView() self.callback_move = \ self.view.addEventCallbackPivy(self.location, self.move) self.callback_click = \ @@ -135,7 +127,7 @@ class PolarArray(gui_base.GuiCommandBase): self.callback_click = None if Gui.Control.activeDialog(): Gui.Control.closeDialog() - self.finish() + self.finish() Gui.addCommand('Draft_PolarArray', PolarArray()) diff --git a/src/Mod/Draft/draftguitools/gui_shapestrings.py b/src/Mod/Draft/draftguitools/gui_shapestrings.py index e092f8edf1..6229e05b17 100644 --- a/src/Mod/Draft/draftguitools/gui_shapestrings.py +++ b/src/Mod/Draft/draftguitools/gui_shapestrings.py @@ -39,46 +39,46 @@ They are more complex that simple text annotations. # @{ 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 draftutils.todo as todo - -from drafttaskpanels.task_shapestring import ShapeStringTaskPanelCmd +from draftguitools import gui_base +from draftutils import gui_utils +from draftutils import todo +from draftutils.messages import _toolmsg from draftutils.translate import translate -from draftutils.messages import _toolmsg, _err - -# The module is used to prevent complaints from code checkers (flake8) -True if Draft_rc.__name__ else False +from drafttaskpanels import task_shapestring -class ShapeString(gui_base_original.Creator): +class ShapeString(gui_base.GuiCommandBase): """Gui command for the ShapeString tool.""" + def __init__(self): + super().__init__(name="ShapeString") + def GetResources(self): """Set icon, menu and tooltip.""" - d = {'Pixmap': 'Draft_ShapeString', - 'MenuText': QT_TRANSLATE_NOOP("Draft_ShapeString", "Shape from text"), - 'ToolTip': QT_TRANSLATE_NOOP("Draft_ShapeString", "Creates a shape from a text string by choosing a specific font and a placement.\nThe closed shapes can be used for extrusions and boolean operations.")} - return d + return {"Pixmap": "Draft_ShapeString", + "MenuText": QT_TRANSLATE_NOOP("Draft_ShapeString", "Shape from text"), + "ToolTip": QT_TRANSLATE_NOOP("Draft_ShapeString", "Creates a shape from a text string by choosing a specific font and a placement.\nThe closed shapes can be used for extrusions and boolean operations.")} def Activated(self): """Execute when the command is called.""" - super().Activated(name="ShapeString") - if self.ui: - self.ui.sourceCmd = self - self.task = ShapeStringTaskPanelCmd(self) - self.call = self.view.addEventCallback("SoEvent", self.task.action) - _toolmsg(translate("draft", "Pick ShapeString location point")) - todo.ToDo.delay(Gui.Control.showDialog, self.task) + super().Activated() + self.ui = task_shapestring.ShapeStringTaskPanelCmd(self) + self.call = self.view.addEventCallback("SoEvent", self.ui.action) + _toolmsg(translate("draft", "Pick ShapeString location point")) + todo.ToDo.delay(Gui.Control.showDialog, self.ui) def finish(self): - self.end_callbacks(self.call) + try: + self.view.removeEventCallback("SoEvent", self.call) + gui_utils.end_all_events() + except RuntimeError: + # the view has been deleted already + pass + self.call = None + if Gui.Control.activeDialog(): + Gui.Control.closeDialog() super().finish() diff --git a/src/Mod/Draft/drafttaskpanels/task_shapestring.py b/src/Mod/Draft/drafttaskpanels/task_shapestring.py index a8121602c4..26d6616668 100644 --- a/src/Mod/Draft/drafttaskpanels/task_shapestring.py +++ b/src/Mod/Draft/drafttaskpanels/task_shapestring.py @@ -142,7 +142,6 @@ class ShapeStringTaskPanelCmd(ShapeStringTaskPanel): def reject(self): """Run when clicking the Cancel button.""" - Gui.ActiveDocument.resetEdit() self.sourceCmd.finish() self.platWinDialog("Restore") return True @@ -160,15 +159,14 @@ class ShapeStringTaskPanelCmd(ShapeStringTaskPanel): z = App.Units.Quantity(self.form.sbZ.text()).Value ssBase = App.Vector(x, y, z) try: - qr, sup, points, fil = self.sourceCmd.getStrings() Gui.addModule("Draft") + Gui.addModule("WorkingPlane") self.sourceCmd.commit(translate('draft', 'Create ShapeString'), ['ss = Draft.make_shapestring(String=' + String + ', FontFile=' + FFile + ', Size=' + Size + ', Tracking=' + Tracking + ')', - 'plm = FreeCAD.Placement()', - 'plm.Base = ' + toString(ssBase), - 'plm.Rotation.Q = ' + qr, - 'ss.Placement = plm', - 'ss.AttachmentSupport = ' + sup, + 'pl = FreeCAD.Placement()', + 'pl.Base = ' + toString(ssBase), + 'pl.Rotation = WorkingPlane.get_working_plane().get_placement().Rotation', + 'ss.Placement = pl', 'Draft.autogroup(ss)', 'FreeCAD.ActiveDocument.recompute()']) except Exception: diff --git a/src/Mod/Draft/draftutils/doc_observer.py b/src/Mod/Draft/draftutils/doc_observer.py new file mode 100644 index 0000000000..2ff93d27c5 --- /dev/null +++ b/src/Mod/Draft/draftutils/doc_observer.py @@ -0,0 +1,42 @@ +import FreeCAD as App + +if App.GuiUp: + import FreeCADGui as Gui + from draftutils.todo import ToDo + + class _Draft_DocObserver(): + + # See: /src/Gui/DocumentObserverPython.h + + def slotDeletedDocument(self, gui_doc): + _finish_command_on_doc_close(gui_doc) + + _doc_observer = None + + def _doc_observer_start(): + global _doc_observer + if _doc_observer is None: + _doc_observer = _Draft_DocObserver() + Gui.addDocumentObserver(_doc_observer) + + def _doc_observer_stop(): + global _doc_observer + try: + if _doc_observer is not None: + Gui.removeDocumentObserver(_doc_observer) + except: + pass + _doc_observer = None + + def _finish_command_on_doc_close(gui_doc): + """Finish the active Draft or BIM command if the related document has been + closed. Only works for commands that have set `App.activeDraftCommand.doc` + and use a task panel. + """ + if getattr(App, "activeDraftCommand", None) \ + and getattr(App.activeDraftCommand, "doc", None) == gui_doc.Document \ + and getattr(App.activeDraftCommand, "ui", None): + if hasattr(App.activeDraftCommand.ui, "reject"): + ToDo.delay(App.activeDraftCommand.ui.reject, None) + elif hasattr(App.activeDraftCommand.ui, "escape"): + ToDo.delay(App.activeDraftCommand.ui.escape, None)