| | |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | |
| |
|
| | from . import Containers |
| |
|
| | from . import TVStack |
| |
|
| | import FreeCAD as App |
| |
|
| | if App.GuiUp: |
| | import FreeCADGui as Gui |
| | Wrn = lambda msg: App.Console.PrintWarning(msg + "\n") |
| | Err = lambda msg: App.Console.PrintError(msg + "\n") |
| | Log = lambda msg: App.Console.PrintLog(msg + "\n") |
| |
|
| | from copy import copy |
| |
|
| | S_EMPTY = 0 |
| | S_ACTIVE = 1 |
| | S_RESTORED = 2 |
| | S_INTERNAL = 3 |
| |
|
| |
|
| | def _printTraceback(err): |
| | import sys |
| |
|
| | if err is sys.exc_info()[1]: |
| | import traceback |
| |
|
| | tb = traceback.format_exc() |
| | Log(tb) |
| |
|
| |
|
| | class MAINSTACK(object): |
| | """it's just a default value definition for TV constructor""" |
| |
|
| | pass |
| |
|
| |
|
| | class JUST_SAVE(object): |
| | '''it's just a default value meaning "save current scene value but don't modify anything"''' |
| |
|
| | pass |
| |
|
| |
|
| | class TempoVis(object): |
| | """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, stack = MAINSTACK, **kwargs): creates a new TempoVis. |
| | |
| | document: required. Objects not belonging to the document can't be modified via TempoVis. |
| | |
| | stack: optional. Which stack to insert this new TV into. Can be: |
| | a TVStack instance (then, the new TV is added to the top of the stack), |
| | MAINSTACK special value (a global stack for the document will be used), or |
| | None (then, the TV is not in any stack, and can be manually instertd into one if desired). |
| | |
| | Any additional keyword args are assigned as attributes. You can use it to immediately set a tag, for example. |
| | """ |
| |
|
| | document = None |
| | stack = None |
| |
|
| | data = None |
| | data_requested = None |
| |
|
| | state = S_EMPTY |
| |
|
| | tag = "" |
| |
|
| | def _init_attrs(self): |
| | """initialize member variables to empty values (needed because we can't use mutable initial values when initializing member variables in class definition)""" |
| | self.data = {} |
| | self.data_requested = {} |
| |
|
| | |
| | def __init__(self, document, stack=MAINSTACK, **kwargs): |
| | self._init_attrs() |
| | self.document = document |
| |
|
| | if stack is MAINSTACK: |
| | stack = TVStack.mainStack(document) |
| |
|
| | if stack is None: |
| | pass |
| | else: |
| | stack.insert(self) |
| |
|
| | for key, val in kwargs.items(): |
| | setattr(self, key, val) |
| |
|
| | def __del__(self): |
| | if self.state == S_ACTIVE: |
| | self.restore(ultimate=True) |
| |
|
| | def has(self, detail): |
| | """has(self, detail): returns True if this TV has this detail value saved. |
| | example: tv.has(VProperty(obj, "Visibility"))""" |
| | return detail.full_key in self.data |
| |
|
| | def stored_val(self, detail): |
| | """stored_val(self, detail): returns value of detail remembered by this TV. If not, raises KeyError.""" |
| | return self.data[detail.full_key].data |
| |
|
| | def save(self, detail, mild_restore=False): |
| | """save(detail, mild_restore = False):saves the scene detail to be restored. |
| | The detail is saved only once; repeated calls are ignored. |
| | mild_restore: internal, do not use.""" |
| | self._change() |
| | if not detail.full_key in self.data: |
| | |
| | tv1, curr = self._value_after(detail, query_scene=True) |
| | self.data[detail.full_key] = copy(curr) |
| | self.data[detail.full_key].mild_restore = mild_restore |
| | else: |
| | |
| | stored_dt = self.data[detail.full_key] |
| | if not mild_restore: |
| | stored_dt.mild_restore = False |
| |
|
| | def modify(self, detail, mild_restore=None): |
| | """modify(detail, mild_restore = True): modifies scene detail through this TV. |
| | The value is provided as an instance of SceneDetail implementation. |
| | The procedure takes care to account for the stack - that is, if in a TV applied |
| | later than this one this detail was changed too, the value saved therein is altered, |
| | rather than applied to the scene. |
| | |
| | mild_restore: if True, when restoring later, checks if the value was changed |
| | by user after last call to modify(), and doesn't restore if it was changed. |
| | |
| | Example: tv.modify(VProperty(obj, "Visibility", True))""" |
| |
|
| | self._change() |
| |
|
| | if mild_restore is not None: |
| | detail.mild_restore = mild_restore |
| |
|
| | |
| | self.save(detail, detail.mild_restore) |
| |
|
| | |
| | tv1, curr = self._value_after(detail) |
| | if tv1 is not None: |
| | tv1.data[detail.full_key].data = detail.data |
| | else: |
| | detail.apply_data(detail.data) |
| |
|
| | |
| | if detail.mild_restore: |
| | self.data_requested[detail.full_key] = copy(detail) |
| |
|
| | def restoreDetail(self, detail, ultimate=False): |
| | """restoreDetail(detail, ultimate = False): restores a specific scene detail. |
| | ultimate: if true, the saved value is cleaned out. |
| | If the detail is not found, nothing is done. |
| | """ |
| | if not self.has(detail): |
| | return |
| | self._restore_detail(detail) |
| | if ultimate: |
| | self.forgetDetail(detail) |
| |
|
| | def forgetDetail(self, detail): |
| | """forgetDetail(detail): ditches a saved detail value, making the change done through this TV permanent.""" |
| | self.data.pop(detail.full_key, None) |
| | self.data_requested.pop(detail.full_key, None) |
| |
|
| | def forget(self): |
| | """forget(self): clears this TV, making all changes done through it permanent. |
| | Also, withdraws the TV from the stack.""" |
| | self.state = S_EMPTY |
| | self.data = {} |
| | if self.is_in_stack: |
| | self.stack.withdraw(self) |
| |
|
| | def restore(self, ultimate=True): |
| | """restore(ultimate = True): undoes all changes done through this tempovis / restores saved scene details. |
| | ultimate: if true, the saved values are cleaned out, and the TV is withdrawn from |
| | the stack. If false, the TV will still remember stuff, and restore can be called again. |
| | """ |
| | if self.state == S_RESTORED: |
| | return |
| |
|
| | if self.state != S_INTERNAL and ultimate: |
| | self.state = S_RESTORED |
| |
|
| | for key, detail in self.data.items(): |
| | try: |
| | self._restoreDetail(detail) |
| | except Exception as err: |
| | Err( |
| | "TempoVis.restore: failed to restore detail {key}: {err}".format( |
| | key=key, err=str(err) |
| | ) |
| | ) |
| | _printTraceback(err) |
| | if ultimate: |
| | self.data = {} |
| | if self.is_in_stack: |
| | self.stack.withdraw(self) |
| |
|
| | |
| |
|
| | |
| | def _inserted(self, stack, index): |
| | """called when this tv is inserted into a stack""" |
| | self.stack = stack |
| |
|
| | def _withdrawn(self, stack, index): |
| | """called when this tv is withdrawn from a stack""" |
| | self.stack = None |
| |
|
| | @property |
| | def is_in_stack(self): |
| | return self.stack is not None |
| |
|
| | |
| |
|
| | |
| | def modifyVPProperty(self, doc_obj_or_list, prop_names, new_value=JUST_SAVE, mild_restore=None): |
| | """modifyVPProperty(doc_obj_or_list, prop_names, new_value = JUST_SAVE, mild_restore = None): 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(). |
| | |
| | mild_restore: test if user changed the value manually when restoring the TV.""" |
| |
|
| | if self.state == S_RESTORED: |
| | Wrn("Attempting to use a TV that has been restored. There must be a problem with code.") |
| | return |
| |
|
| | if not hasattr(doc_obj_or_list, "__iter__"): |
| | doc_obj_or_list = [doc_obj_or_list] |
| | if not isinstance(prop_names, (list, tuple)): |
| | prop_names = [prop_names] |
| | for doc_obj in doc_obj_or_list: |
| | for prop_name in prop_names: |
| | if not hasattr(doc_obj.ViewObject, prop_name): |
| | Wrn( |
| | "TempoVis: object {obj} has no attribute {attr}. Skipped.".format( |
| | obj=doc_obj.Name, attr=prop_name |
| | ) |
| | ) |
| | continue |
| |
|
| | |
| | |
| | |
| | |
| | |
| | from .SceneDetails.VProperty import VProperty |
| |
|
| | if new_value is JUST_SAVE: |
| | if mild_restore: |
| | Wrn( |
| | "TempoVis: can't just save a value for mild restore. Saving for hard restore." |
| | ) |
| | self.save(VProperty(doc_obj, prop_name, new_value)) |
| | else: |
| | self.modify(VProperty(doc_obj, prop_name, new_value), mild_restore) |
| |
|
| | def restoreVPProperty(self, doc_obj_or_list, prop_names): |
| | """restoreVPProperty(doc_obj_or_list, prop_name, new_value): restores specific property changes.""" |
| | from .SceneDetails.VProperty import VProperty |
| |
|
| | if not hasattr(doc_obj_or_list, "__iter__"): |
| | doc_obj_or_list = [doc_obj_or_list] |
| | if not isinstance(prop_names, (tuple, list)): |
| | prop_names = [prop_names] |
| | for doc_obj in doc_obj_or_list: |
| | for prop_name in prop_names: |
| | try: |
| | self.restoreDetail(VProperty(doc_obj, prop_name)) |
| | except Exception as err: |
| | Err( |
| | "TempoVis.restore: failed to restore detail {key}: {err}".format( |
| | key=key, err=str(err) |
| | ) |
| | ) |
| | _printTraceback(err) |
| |
|
| | def saveBodyVisibleFeature(self, doc_obj_or_list): |
| | """saveBodyVisibleFeature(self, doc_obj_or_list): saves Visibility of currently |
| | visible feature, for every body of PartDesign features in the provided list.""" |
| | if not hasattr(doc_obj_or_list, "__iter__"): |
| | doc_obj_or_list = [doc_obj_or_list] |
| | objs = [] |
| | bodies = set() |
| | for obj in doc_obj_or_list: |
| | body = getattr(obj, "_Body", None) |
| | if not body or body in bodies: |
| | continue |
| | bodies.add(body) |
| | feature = getattr(body, "VisibleFeature", None) |
| | if feature: |
| | objs.append(feature) |
| | self.modifyVPProperty(objs, "Visibility", JUST_SAVE) |
| | return objs |
| |
|
| | def show(self, doc_obj_or_list, links_too=True, mild_restore=None): |
| | """show(doc_obj_or_list, links_too = True): shows objects (sets their Visibility to True). |
| | doc_obj_or_list can be a document object, or a list of document objects. |
| | If links_too is True, all Links of the objects are also hidden, by setting LinkVisibility attribute of each object. |
| | """ |
| | doc_obj_or_list = self._3D_objects(doc_obj_or_list) |
| | self.saveBodyVisibleFeature( |
| | doc_obj_or_list |
| | ) |
| | self.modifyVPProperty(doc_obj_or_list, "Visibility", True, mild_restore) |
| | if links_too: |
| | self.modifyVPProperty(doc_obj_or_list, "LinkVisibility", True, mild_restore) |
| |
|
| | def hide(self, doc_obj_or_list, links_too=True, mild_restore=None): |
| | """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""" |
| | doc_obj_or_list = self._3D_objects(doc_obj_or_list) |
| | |
| | self.modifyVPProperty(doc_obj_or_list, "Visibility", False, mild_restore) |
| | if links_too: |
| | self.modifyVPProperty(doc_obj_or_list, "LinkVisibility", False, mild_restore) |
| |
|
| | def get_all_dependent(self, doc_obj, subname=None): |
| | """get_all_dependent(doc_obj, subname = None): gets all objects that depend on doc_obj. Containers and Links (if subname) required for visibility of the object are excluded from the list.""" |
| | from . import Containers |
| | from .Containers import isAContainer |
| | from .DepGraphTools import getAllDependencies, getAllDependent |
| |
|
| | if subname: |
| | |
| | |
| | cnt_chain = doc_obj.getSubObjectList(subname) |
| | doc_obj = cnt_chain[-1].getLinkedObject() |
| | |
| | |
| | |
| | |
| | |
| | |
| | cnt_chain = [ |
| | o for o in cnt_chain if o == cnt_chain[-1] or isAContainer(o, links_too=True) |
| | ] |
| | else: |
| | cnt_chain = Containers.ContainerChain(doc_obj) |
| | return [o for o in getAllDependent(doc_obj) if not o in cnt_chain] |
| |
|
| | 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(self._3D_objects(self.get_all_dependent(doc_obj))) |
| |
|
| | 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.""" |
| | from .DepGraphTools import getAllDependencies, getAllDependent |
| |
|
| | self.show(self._3D_objects(getAllDependent(doc_obj))) |
| |
|
| | def restore_all_dependent(self, doc_obj): |
| | """show_all_dependent(doc_obj): restores original visibilities of all dependent objects.""" |
| | from .DepGraphTools import getAllDependencies, getAllDependent |
| |
|
| | self.restoreVPProperty(getAllDependent(doc_obj), ("Visibility", "LinkVisibility")) |
| |
|
| | def hide_all_dependencies(self, doc_obj): |
| | """hide_all_dependencies(doc_obj): hides all objects that doc_obj depends on (directly and indirectly).""" |
| | from .DepGraphTools import getAllDependencies, getAllDependent |
| |
|
| | self.hide(self._3D_objects(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.""" |
| | from .DepGraphTools import getAllDependencies, getAllDependent |
| |
|
| | self.show(self._3D_objects(getAllDependencies(doc_obj))) |
| |
|
| | def saveCamera(self, vw=None): |
| | self._change() |
| | from .SceneDetails.Camera import Camera |
| |
|
| | self.save(Camera(self.document)) |
| |
|
| | def restoreCamera(self, ultimate=False): |
| | from .SceneDetails.Camera import Camera |
| |
|
| | dt = Camera(self.document) |
| | self.restoreDetail(dt, ultimate) |
| |
|
| | def setUnpickable( |
| | self, doc_obj_or_list, actual_pick_style=2 |
| | ): |
| | """setUnpickable(doc_obj_or_list, actual_pick_style = 2): sets object unpickable (transparent to clicks). |
| | doc_obj_or_list: object or list of objects to alter (App) |
| | actual_pick_style: optional parameter, specifying the actual pick style: |
| | 0 = regular, 1 = bounding box, 2 (default) = unpickable. |
| | |
| | Implementation detail: uses SoPickStyle node. If viewprovider already has a node |
| | of this type as direct child, one is used. Otherwise, new one is created and |
| | inserted as the very first node, and remains there even after restore()/deleting |
| | tempovis.""" |
| |
|
| | from .SceneDetails.Pickability import Pickability |
| | from .ShowUtils import is3DObject |
| |
|
| | if not hasattr(doc_obj_or_list, "__iter__"): |
| | doc_obj_or_list = [doc_obj_or_list] |
| | for doc_obj in doc_obj_or_list: |
| | if not is3DObject(doc_obj): |
| | continue |
| | dt = Pickability(doc_obj, actual_pick_style) |
| | self.modify(dt) |
| |
|
| | def clipPlane(self, doc_obj_or_list, enable, placement, offset=0.02): |
| | """clipPlane(doc_obj_or_list, enable, placement, offset): slices off the object with a clipping plane. |
| | doc_obj_or_list: object or list of objects to alter (App) |
| | enable: True if you want clipping, False if you want to remove clipping: |
| | placement: XY plane of local coordinates of the placement is the clipping plane. The placement must be in document's global coordinate system. |
| | offset: shifts the plane. Positive offset reveals more of the object. |
| | |
| | Implementation detail: uses SoClipPlane node. If viewprovider already has a node |
| | of this type as direct child, one is used. Otherwise, new one is created and |
| | inserted as the very first node. The node is left, but disabled when tempovis is restoring. |
| | """ |
| |
|
| | from .SceneDetails.ObjectClipPlane import ObjectClipPlane |
| | from .ShowUtils import is3DObject |
| |
|
| | if not hasattr(doc_obj_or_list, "__iter__"): |
| | doc_obj_or_list = [doc_obj_or_list] |
| | for doc_obj in doc_obj_or_list: |
| | if not is3DObject(doc_obj): |
| | continue |
| | dt = ObjectClipPlane(doc_obj, enable, placement, offset) |
| | self.modify(dt) |
| |
|
| | @staticmethod |
| | def allVisibleObjects(aroundObject): |
| | """allVisibleObjects(aroundObject): returns list of objects that have to be toggled invisible for only aroundObject to remain. |
| | If a whole container can be made invisible, it is returned, instead of its child objects.""" |
| | from .ShowUtils import is3DObject |
| | from . import Containers |
| |
|
| | chain = Containers.VisGroupChain(aroundObject) |
| | result = [] |
| | for i in range(len(chain)): |
| | cnt = chain[i] |
| | cnt_next = chain[i + 1] if i + 1 < len(chain) else aroundObject |
| | container = Containers.Container(cnt) |
| | for obj in container.getVisGroupChildren(): |
| | if not is3DObject(obj): |
| | continue |
| | if obj is not cnt_next: |
| | if container.isChildVisible(obj): |
| | result.append(obj) |
| | return result |
| |
|
| | def sketchClipPlane(self, sketch, enable=None, reverted=False): |
| | """sketchClipPlane(sketch, enable = None): Clips all objects by plane of sketch. |
| | If enable argument is omitted, calling the routine repeatedly will toggle clipping plane.""" |
| |
|
| | from .SceneDetails.ClipPlane import ClipPlane |
| |
|
| | editDoc = Gui.editDocument() |
| | if editDoc is None: |
| | doc = sketch.Document |
| | pla = sketch.getGlobalPlacement() |
| | else: |
| | doc = editDoc.Document |
| | pla = App.Placement(editDoc.EditingTransform) |
| | toggle = {False: 0, True: 1, None: -1}[enable] |
| |
|
| | if reverted: |
| | pla = pla * App.Rotation(0, 1, 0, 0) |
| |
|
| | if enable: |
| | self.modify(ClipPlane(doc, 0)) |
| |
|
| | self.modify(ClipPlane(doc, toggle, pla, 0.001)) |
| | sketch.ViewObject.SectionView = ( |
| | enable if enable is not None else not sketch.ViewObject.SectionView |
| | ) |
| |
|
| | def activateWorkbench(self, wb_name): |
| | from .SceneDetails.Workbench import Workbench |
| |
|
| | self.modify(Workbench(wb_name)) |
| |
|
| | |
| |
|
| | |
| | def _restoreDetail(self, detail): |
| | p = self.data[detail.full_key] |
| | tv1, curr = self._value_after(detail, query_scene=p.mild_restore) |
| | if p.mild_restore: |
| | if self.data_requested[detail.full_key] != curr: |
| | |
| | self._purge_milds(detail) |
| | return |
| | if tv1 is None: |
| | |
| | detail.apply_data(p.data) |
| | else: |
| | |
| | tv1.data[detail.full_key].data = p.data |
| |
|
| | def _purge_milds(self, detail): |
| | """_purge_milds(detail): wipes out detail from earlier TVs if the detail is mild-restore.""" |
| | if not self.is_in_stack: |
| | return |
| | seq_before, seq_after = self.stack.getSplitSequence(self) |
| | for tv in reversed(seq_before): |
| | if tv.has(detail): |
| | if tv.data[detail.full_key].mild_restore: |
| | tv.forgetDetail(detail) |
| | else: |
| | |
| | break |
| |
|
| | def _change(self): |
| | """to be called whenever anything is done that is to be restored later.""" |
| | if self.state == S_EMPTY: |
| | self.state = S_ACTIVE |
| | if self.state == S_RESTORED: |
| | Wrn("Attempting to use a TV that has been restored. There must be a problem with code.") |
| | self.tv_redo = None |
| |
|
| | def _value_after(self, detail, query_scene=False): |
| | """_value_current(detail): returns (tv, detail1). SceneDetail instance holds "current" value of |
| | scene detail (current from the context of this TV; i.e. either the current scene |
| | status, or the saved state from upper TVs). |
| | If no upper TV has saved the detail value, returns either (None, None), or |
| | (None, detail1) if query_scene is True, where detail1 holds value from the scene.""" |
| |
|
| | def scene_value(): |
| | if query_scene: |
| | cpy = copy(detail) |
| | cpy.data = cpy.scene_value() |
| | return (None, cpy) |
| | else: |
| | return (None, None) |
| |
|
| | if self.is_in_stack: |
| | va = self.stack.value_after(self, detail) |
| | if va is None: |
| | return scene_value() |
| | else: |
| | return va |
| | else: |
| | return scene_value() |
| |
|
| | def _3D_objects(self, doc_obj_or_list): |
| | """_3D_objects(doc_obj_or_list): returns list of objects that are in 3d view.""" |
| | from .ShowUtils import is3DObject |
| |
|
| | if not hasattr(doc_obj_or_list, "__iter__"): |
| | doc_obj_or_list = [doc_obj_or_list] |
| |
|
| | return [obj for obj in doc_obj_or_list if is3DObject(obj)] |
| |
|
| | def dumps(self): |
| | return None |
| |
|
| | def loads(self, state): |
| | self._init_attrs() |
| |
|