From 84b8f1c7219aa5566c7874e763f62e9c9ebb456f Mon Sep 17 00:00:00 2001 From: DeepSOIC Date: Sun, 19 Jun 2016 22:13:13 +0300 Subject: [PATCH] New module: Show A module for gathering code related to visibility automation and the like. Contains DepGraphTools.py and TempoVis.py, to be withdrawn from Part/AttachmentEditor. Also: changes to TempoVis: * keep TempoVis from from raising (TempoVis is non-essential functionality, so breaking execution of unsuspecting routines that use it seems like a bad idea. * implement saving/restoring camera (Unfortunately, I failed to implement nice animated restoration.) * almost proper support of saving to file (I still couldn't figure out a way to restore reference to document and viewer. So, falling to using active ones instead (might cause bugs).) --- CMakeLists.txt | 1 + src/Mod/CMakeLists.txt | 4 + src/Mod/Show/CMakeLists.txt | 21 ++++ src/Mod/Show/DepGraphTools.py | 82 ++++++++++++++++ src/Mod/Show/FrozenClass.py | 37 ++++++++ src/Mod/Show/TempoVis.py | 174 ++++++++++++++++++++++++++++++++++ src/Mod/Show/__init__.py | 4 + 7 files changed, 323 insertions(+) create mode 100644 src/Mod/Show/CMakeLists.txt create mode 100644 src/Mod/Show/DepGraphTools.py create mode 100644 src/Mod/Show/FrozenClass.py create mode 100644 src/Mod/Show/TempoVis.py create mode 100644 src/Mod/Show/__init__.py diff --git a/CMakeLists.txt b/CMakeLists.txt index e45c4f7634..b4aab7f404 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -198,6 +198,7 @@ OPTION(BUILD_RAYTRACING "Build the FreeCAD ray tracing module" ON) OPTION(BUILD_REVERSEENGINEERING "Build the FreeCAD reverse engineering module" ON) OPTION(BUILD_ROBOT "Build the FreeCAD robot module" ON) OPTION(BUILD_SHIP "Build the FreeCAD ship module" ON) +OPTION(BUILD_SHOW "Build the FreeCAD Show module (helper module for visibility automation)" ON) OPTION(BUILD_SKETCHER "Build the FreeCAD sketcher module" ON) OPTION(BUILD_SPREADSHEET "Build the FreeCAD spreadsheet module" ON) OPTION(BUILD_START "Build the FreeCAD start module" ON) diff --git a/src/Mod/CMakeLists.txt b/src/Mod/CMakeLists.txt index a0b7b3b62f..e310a30f32 100644 --- a/src/Mod/CMakeLists.txt +++ b/src/Mod/CMakeLists.txt @@ -125,3 +125,7 @@ endif(BUILD_JTREADER) if(BUILD_PATH) add_subdirectory(Path) endif(BUILD_PATH) + +if(BUILD_SHOW) + add_subdirectory(Show) +endif(BUILD_SHOW) diff --git a/src/Mod/Show/CMakeLists.txt b/src/Mod/Show/CMakeLists.txt new file mode 100644 index 0000000000..8f8602d4f8 --- /dev/null +++ b/src/Mod/Show/CMakeLists.txt @@ -0,0 +1,21 @@ + +SET(Show_SRCS + __init__.py + DepGraphTools.py + FrozenClass.py + TempoVis.py +) + +SOURCE_GROUP("" FILES ${Show_SRCS}) + +ADD_CUSTOM_TARGET(Show ALL + SOURCES ${Show_SRCS} +) + +fc_copy_sources(Show "${CMAKE_BINARY_DIR}/Mod/Show" ${Show_SRCS}) + +INSTALL( + FILES + ${Show_SRCS} + DESTINATION Mod/Show +) \ No newline at end of file diff --git a/src/Mod/Show/DepGraphTools.py b/src/Mod/Show/DepGraphTools.py new file mode 100644 index 0000000000..f5d36b541e --- /dev/null +++ b/src/Mod/Show/DepGraphTools.py @@ -0,0 +1,82 @@ +#/*************************************************************************** +# * Copyright (c) Victor Titov (DeepSOIC) * +# * (vv.titov@gmail.com) 2016 * +# * * +# * This file is part of the FreeCAD CAx development system. * +# * * +# * This library is free software; you can redistribute it and/or * +# * modify it under the terms of the GNU Library General Public * +# * License as published by the Free Software Foundation; either * +# * version 2 of the License, or (at your option) any later version. * +# * * +# * This library 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 this library; see the file COPYING.LIB. If not, * +# * write to the Free Software Foundation, Inc., 59 Temple Place, * +# * Suite 330, Boston, MA 02111-1307, USA * +# * * +# ***************************************************************************/ + +import FreeCAD as App + +def getAllDependencies(feat): + '''getAllDependencies(feat): gets all features feat depends on, directly or indirectly. + Returns a list, with deepest dependencies last. feat is not included in the list, except + if the feature depends on itself (dependency loop).''' + list_traversing_now = [feat] + set_of_deps = set() + list_of_deps = [] + + while len(list_traversing_now) > 0: + list_to_be_traversed_next = [] + for feat in list_traversing_now: + for dep in feat.OutList: + if not (dep in set_of_deps): + set_of_deps.add(dep) + list_of_deps.append(dep) + list_to_be_traversed_next.append(dep) + + list_traversing_now = list_to_be_traversed_next + + return list_of_deps + +def getAllDependent(feat): + '''getAllDependent(feat): gets all features that depend on feat, directly or indirectly. + Returns a list, with deepest dependencies last. feat is not included in the list, except + if the feature depends on itself (dependency loop).''' + list_traversing_now = [feat] + set_of_deps = set() + list_of_deps = [] + + while len(list_traversing_now) > 0: + list_to_be_traversed_next = [] + for feat in list_traversing_now: + for dep in feat.InList: + if not (dep in set_of_deps): + set_of_deps.add(dep) + list_of_deps.append(dep) + list_to_be_traversed_next.append(dep) + + list_traversing_now = list_to_be_traversed_next + + return list_of_deps + +def isContainer(obj): + '''isContainer(obj): returns True if obj is an object container, such as + Group, Part, Body. The important characterisic of an object being a + container is its action on visibility of linked objects. E.g. a + Part::Compound is not a group, because it does not affect visibility + of originals. Documents are considered containers, too.''' + + if obj.isDerivedFrom("App::DocumentObjectGroup"): + return True + if obj.isDerivedFrom("PartDesign::Body"): + return True + if obj.isDerivedFrom("App::Origin"): + return True + if obj.isDerivedFrom('App::Document'): + return True diff --git a/src/Mod/Show/FrozenClass.py b/src/Mod/Show/FrozenClass.py new file mode 100644 index 0000000000..1fa76fb5fc --- /dev/null +++ b/src/Mod/Show/FrozenClass.py @@ -0,0 +1,37 @@ +#/*************************************************************************** +# * Copyright (c) Victor Titov (DeepSOIC) * +# * (vv.titov@gmail.com) 2016 * +# * * +# * This file is part of the FreeCAD CAx development system. * +# * * +# * This library is free software; you can redistribute it and/or * +# * modify it under the terms of the GNU Library General Public * +# * License as published by the Free Software Foundation; either * +# * version 2 of the License, or (at your option) any later version. * +# * * +# * This library 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 this library; see the file COPYING.LIB. If not, * +# * write to the Free Software Foundation, Inc., 59 Temple Place, * +# * Suite 330, Boston, MA 02111-1307, USA * +# * * +# ***************************************************************************/ + +# adapted from http://stackoverflow.com/a/3603824/6285007 +class FrozenClass(object): + '''FrozenClass: prevents adding new attributes to class outside of __init__''' + __isfrozen = False + def __setattr__(self, key, value): + if self.__isfrozen and not hasattr(self, key): + raise TypeError( "{cls} has no attribute {attr}".format(cls= self.__class__.__name__, attr= key) ) + object.__setattr__(self, key, value) + + def _freeze(self): + self.__isfrozen = True + + def _unfreeze(self): + self.__isfrozen = False diff --git a/src/Mod/Show/TempoVis.py b/src/Mod/Show/TempoVis.py new file mode 100644 index 0000000000..823530be8b --- /dev/null +++ b/src/Mod/Show/TempoVis.py @@ -0,0 +1,174 @@ +#/*************************************************************************** +# * Copyright (c) Victor Titov (DeepSOIC) * +# * (vv.titov@gmail.com) 2016 * +# * * +# * This file is part of the FreeCAD CAx development system. * +# * * +# * This library is free software; you can redistribute it and/or * +# * modify it under the terms of the GNU Library General Public * +# * License as published by the Free Software Foundation; either * +# * version 2 of the License, or (at your option) any later version. * +# * * +# * This library 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 this library; see the file COPYING.LIB. If not, * +# * write to the Free Software Foundation, Inc., 59 Temple Place, * +# * Suite 330, Boston, MA 02111-1307, USA * +# * * +# ***************************************************************************/ + +import FreeCAD as App +if App.GuiUp: + import FreeCADGui as Gui + +from Show.FrozenClass import FrozenClass + +from Show.DepGraphTools import getAllDependencies, getAllDependent, isContainer + +class TempoVis(FrozenClass): + '''TempoVis - helper object to save visibilities of objects before doing + some GUI editing, hiding or showing relevant stuff during edit, and + then restoring all visibilities after editing. + + Constructors: + TempoVis(document): creates a new TempoVis. Supplying document is mandatory. Objects not belonging to the document can't be modified via TempoVis.''' + + def __define_attributes(self): + self.data = {} # dict. key = ("Object","Property"), value = original value of the property + + self.cam_string = "" # inventor ASCII string representing the camera + self.viewer = None # viewer the camera is saved from + + self.document = None + self.restore_on_delete = False # if true, restore() gets called upon object deletion. It becomes False after explicit call to Restore, and set to true by many methods. + + self.links_are_lost = False # set to true after restore from JSON. Indictes to attempt to use ActiveDocument/ActiveViewer instead. + + self._freeze() + + def __init__(self, document): + self.__define_attributes() + + self.document = document + + def modifyVPProperty(self, doc_obj_or_list, prop_name, new_value): + '''modifyVPProperty(self, doc_obj_or_list, prop_name, new_value): modifies + prop_name property of ViewProvider of doc_obj_or_list, and remembers + original value of the property. Original values will be restored upon + TempoVis deletion, or call to restore().''' + + if App.GuiUp: + if type(doc_obj_or_list) is not list: + doc_obj_or_list = [doc_obj_or_list] + for doc_obj in doc_obj_or_list: + if not hasattr(doc_obj.ViewObject, prop_name): + App.Console.PrintWarning("TempoVis: object {obj} has no attribute {attr}. Skipped.\n" + .format(obj= doc_obj.Name, attr= prop_name)) + continue # silently ignore if object doesn't have the property... + + if doc_obj.Document is not self.document: #ignore objects from other documents + raise ValueError("Document object to be modified does not belong to document TempoVis was made for.") + oldval = getattr(doc_obj.ViewObject, prop_name) + setattr(doc_obj.ViewObject, prop_name, new_value) + if not self.data.has_key((doc_obj.Name,prop_name)): + self.data[(doc_obj.Name,prop_name)] = oldval + self.restore_on_delete = True + + def show(self, doc_obj_or_list): + '''show(doc_obj_or_list): shows objects (sets their Visibility to True). doc_obj_or_list can be a document object, or a list of document objects''' + self.modifyVPProperty(doc_obj_or_list, "Visibility", True) + + def hide(self, doc_obj_or_list): + '''hide(doc_obj_or_list): hides objects (sets their Visibility to False). doc_obj_or_list can be a document object, or a list of document objects''' + self.modifyVPProperty(doc_obj_or_list, "Visibility", False) + + def hide_all_dependent(self, doc_obj): + '''hide_all_dependent(doc_obj): hides all objects that depend on doc_obj. Groups, Parts and Bodies are not hidden by this.''' + self.hide( [o for o in getAllDependent(doc_obj) if not isContainer(o)]) + + def show_all_dependent(self, doc_obj): + '''show_all_dependent(doc_obj): shows all objects that depend on doc_obj. This method is probably useless.''' + self.show( getAllDependent(doc_obj) ) + + def hide_all_dependencies(self, doc_obj): + '''hide_all_dependencies(doc_obj): hides all objects that doc_obj depends on (directly and indirectly).''' + self.hide( getAllDependencies(doc_obj) ) + + def show_all_dependencies(self, doc_obj): + '''show_all_dependencies(doc_obj): shows all objects that doc_obj depends on (directly and indirectly). This method is probably useless.''' + self.show( getAllDependencies(doc_obj) ) + + def saveCamera(self): + vw = Gui.ActiveDocument.ActiveView + self.cam_string = vw.getCamera() + self.viewer = vw + + self.restore_on_delete = True + + def restoreCamera(self): + if not self.cam_string: + return + vw = self.viewer + if self.links_are_lost: # can happen after save-restore + import FreeCADGui as Gui + vw = Gui.ActiveDocument.ActiveView + + vw.setCamera(self.cam_string) + + def restore(self): + '''restore(): restore all ViewProvider properties modified via TempoVis to their + original values, and saved camera, if any. Called automatically when instance is + destroyed, unless it was called explicitly. Should not raise exceptions.''' + + if self.links_are_lost: + self.document = App.ActiveDocument + self.viewer = Gui.ActiveDocument.ActiveView + self.links_are_lost = False + + for obj_name, prop_name in self.data: + try: + setattr(self.document.getObject(obj_name).ViewObject, prop_name, self.data[(obj_name, prop_name)]) + except Exception as err: + App.Console.PrintWarning("TempoVis: failed to restore {obj}.{prop}. {err}\n" + .format(err= err.message, + obj= obj_name, + prop= prop_name)) + try: + self.restoreCamera() + except Exception as err: + App.Console.PrintWarning("TempoVis: failed to restore camera. {err}\n" + .format(err= err.message)) + self.restore_on_delete = False + + def forget(self): + '''forget(): resets TempoVis''' + self.data = {} + + self.cam_string = "" + self.viewer = None + + self.restore_on_delete = False + + def __del__(self): + if self.restore_on_delete: + self.restore() + + def __getstate__(self): + return (self.data.items(), + self.cam_string, + self.restore_on_delete) + + def __setstate__(self, state): + self.__define_attributes() + + items, self.cam_string, self.restore_on_delete = state + + # need to convert keys to tuples (dict doesn't accept list as key; tuples are converted to lists by json) + items = [(tuple(item[0]), item[1]) for item in items] + self.data = dict(items) + self.links_are_lost = True + \ No newline at end of file diff --git a/src/Mod/Show/__init__.py b/src/Mod/Show/__init__.py new file mode 100644 index 0000000000..44b15b8d02 --- /dev/null +++ b/src/Mod/Show/__init__.py @@ -0,0 +1,4 @@ +__doc__ = "Show module: helper code for visibility automation." + +from Show.TempoVis import TempoVis +import Show.DepGraphTools as DepGraphTools \ No newline at end of file