From 918666d308d996add250e529ab9823f14bde7f7c Mon Sep 17 00:00:00 2001 From: Konstantin Maslyuk Date: Tue, 5 Jul 2022 21:35:20 +0300 Subject: [PATCH 1/3] implement storage class for pickers data --- dwpicker/__init__.py | 6 ++- dwpicker/main.py | 16 +++--- dwpicker/scenedata.py | 121 +++++++++++++++++++++--------------------- 3 files changed, 72 insertions(+), 71 deletions(-) diff --git a/dwpicker/__init__.py b/dwpicker/__init__.py index a17eaa0..f65071b 100644 --- a/dwpicker/__init__.py +++ b/dwpicker/__init__.py @@ -8,12 +8,14 @@ _dwpicker = None -def show(editable=True, pickers=None, ignore_scene_pickers=False): +def show( + editable=True, pickers=None, ignore_scene_pickers=False, + storage_class=None): ensure_optionvars_exists() global _dwpicker if not _dwpicker: warn_if_update_available() - _dwpicker = DwPicker() + _dwpicker = DwPicker(storage_class=storage_class) try: _dwpicker.show(dockable=True) diff --git a/dwpicker/main.py b/dwpicker/main.py index e7355f9..988406d 100644 --- a/dwpicker/main.py +++ b/dwpicker/main.py @@ -31,9 +31,7 @@ from dwpicker.qtutils import set_shortcut, icon, maya_main_window, DockableBase from dwpicker.quick import QuickOptions from dwpicker.references import ensure_images_path_exists -from dwpicker.scenedata import ( - load_local_picker_data, store_local_picker_data, - clean_stray_picker_holder_nodes) +from dwpicker.scenedata import DefaultSceneStorage from dwpicker.templates import BUTTON, PICKER, BACKGROUND from dwpicker.undo import UndoManager @@ -77,7 +75,7 @@ def build_multiple_shapes(targets, override): class DwPicker(DockableBase, QtWidgets.QWidget): - def __init__(self): + def __init__(self, storage_class=None): super(DwPicker, self).__init__(control_name=WINDOW_CONTROL_NAME) self.setWindowTitle(WINDOW_TITLE) shortcut_context = QtCore.Qt.WidgetWithChildrenShortcut @@ -99,6 +97,8 @@ def __init__(self): self.pickers = [] self.filenames = [] self.modified_states = [] + self.storage = (storage_class or DefaultSceneStorage)() + self.preferences_window = PreferencesWindow( callback=self.load_ui_states, parent=maya_main_window()) self.preferences_window.disable_import_callbacks.released.connect( @@ -324,23 +324,23 @@ def auto_switch_tab(self, *_, **__): def load_saved_pickers(self, *_, **__): self.clear() - pickers = load_local_picker_data() + pickers = self.storage.load() if cmds.optionVar(query=CHECK_IMAGES_PATHS): picker = ensure_images_path_exists(pickers) for picker in pickers: self.add_picker(picker) - clean_stray_picker_holder_nodes() + self.storage.cleanup() def store_local_pickers_data(self): if not self.editable: return if not self.tab.count(): - store_local_picker_data([]) + self.storage.store([]) return pickers = [self.picker_data(i) for i in range(self.tab.count())] - store_local_picker_data(pickers) + self.storage.store(pickers) def save_tab(self, index): msg = ( diff --git a/dwpicker/scenedata.py b/dwpicker/scenedata.py index 0fdf083..028f27e 100644 --- a/dwpicker/scenedata.py +++ b/dwpicker/scenedata.py @@ -9,42 +9,6 @@ from dwpicker.namespace import maya_namespace -PICKER_HOLDER_NODE = '_dwpicker_data' -PICKER_HOLDER_ATTRIBUTE = '_dwpicker_data' -LS_EXP = ["*." + PICKER_HOLDER_ATTRIBUTE, "*:*." + PICKER_HOLDER_ATTRIBUTE] - - -def get_picker_holder_node(): - if cmds.objExists(PICKER_HOLDER_NODE): - return PICKER_HOLDER_NODE - return create_picker_holder_node() - - -def create_picker_holder_node(): - with maya_namespace(":"): - node = cmds.createNode('script', name=PICKER_HOLDER_NODE) - cmds.setAttr(node + '.nodeState', 1) - cmds.addAttr(node, longName=PICKER_HOLDER_ATTRIBUTE, dataType='string') - return node - - -def store_local_picker_data(pickers): - data = encode_data(pickers) - node = get_picker_holder_node() - cmds.setAttr(node + '.' + PICKER_HOLDER_ATTRIBUTE, data, type='string') - clean_stray_picker_holder_nodes() - - -def load_local_picker_data(): - nodes = list_picker_holder_nodes() - pickers = [] - for node in nodes: - data = cmds.getAttr(node + '.' + PICKER_HOLDER_ATTRIBUTE) - data = decode_data(data) - pickers.extend(ensure_retro_compatibility(p) for p in data) - return pickers - - def encode_data(pickers): data = json.dumps(pickers) if not cmds.optionVar(query=USE_BASE64_DATA_ENCODING): @@ -62,28 +26,63 @@ def decode_data(data): return json.loads(base64.b64decode(data)) -def list_picker_holder_nodes(): - """ - Look up in the scene all the nodes holding an attribute named - "_dwpicker_holder" which are not set on the "_dwpicker_holder" node. - This mignt happed if a node node is imported (creating a namespace or a - incrementation). - """ - return [node.split(".")[0] for node in cmds.ls(LS_EXP)] - - -def clean_stray_picker_holder_nodes(): - """ - If the scene contains multiple picker holder nodes, we remove them - automatically to avoid repeated pickers. - """ - for node in list_picker_holder_nodes(): - if node == PICKER_HOLDER_NODE: - continue - try: - cmds.delete(node) - except: - # Node is locked or in reference and cannot be removed. - # As we cant remove it, we reset his data to avoid double pickers. - cmds.setAttr( - node + "." + PICKER_HOLDER_ATTRIBUTE, "", dataType="string") +class DefaultSceneStorage: + PICKER_HOLDER_NODE = '_dwpicker_data' + PICKER_HOLDER_ATTRIBUTE = '_dwpicker_data' + LS_EXP = ["*." + PICKER_HOLDER_ATTRIBUTE, "*:*." + PICKER_HOLDER_ATTRIBUTE] + + def _get_picker_holder_node(self): + if cmds.objExists(self.PICKER_HOLDER_NODE): + return self.PICKER_HOLDER_NODE + return self._create_picker_holder_node() + + def _create_picker_holder_node(self): + with maya_namespace(":"): + node = cmds.createNode('script', name=self.PICKER_HOLDER_NODE) + cmds.setAttr(node + '.nodeState', 1) + cmds.addAttr( + node, longName=self.PICKER_HOLDER_ATTRIBUTE, dataType='string') + return node + + def _list_picker_holder_nodes(self): + """ + Look up in the scene all the nodes holding an attribute named + "_dwpicker_holder" which are not set on the "_dwpicker_holder" node. + This mignt happed if a node node is imported (creating a namespace or a + incrementation). + """ + return [node.split(".")[0] for node in cmds.ls(self.LS_EXP)] + + def load(self): + nodes = self._list_picker_holder_nodes() + pickers = [] + for node in nodes: + data = cmds.getAttr(node + '.' + self.PICKER_HOLDER_ATTRIBUTE) + data = decode_data(data) + pickers.extend(ensure_retro_compatibility(p) for p in data) + return pickers + + def store(self, pickers): + data = encode_data(pickers) + node = self._get_picker_holder_node() + cmds.setAttr( + node + '.' + self.PICKER_HOLDER_ATTRIBUTE, data, type='string') + self.cleanup() + + def cleanup(self): + """ + If the scene contains multiple picker holder nodes, we remove them + automatically to avoid repeated pickers. + """ + for node in self._list_picker_holder_nodes(): + if node == self.PICKER_HOLDER_NODE: + continue + try: + cmds.delete(node) + except: + # Node is locked or in reference and cannot be removed. + # As we cant remove it, we reset his data to avoid double + # pickers. + cmds.setAttr( + node + "." + self.PICKER_HOLDER_ATTRIBUTE, "", + dataType="string") From 378b1d2bf077960a8750c734057903bdea103815 Mon Sep 17 00:00:00 2001 From: Konstantin Maslyuk Date: Wed, 6 Jul 2022 02:24:20 +0300 Subject: [PATCH 2/3] add storage class for dag hierarchy (wip) --- dwpicker/scenedata_dag.py | 130 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 dwpicker/scenedata_dag.py diff --git a/dwpicker/scenedata_dag.py b/dwpicker/scenedata_dag.py new file mode 100644 index 0000000..29d7e3e --- /dev/null +++ b/dwpicker/scenedata_dag.py @@ -0,0 +1,130 @@ +import imp +import json +import time + +import dwpicker +imp.reload(dwpicker) +import dwpicker.scenedata +from maya import cmds + + +def shortname(fullname): + return fullname.split('|')[-1].split(':')[-1] + + +def timeit(func): + def wrapped(*args, **kwargs): + started = time.perf_counter() + value = func(*args, **kwargs) + print( + 'Time for', func.__qualname__, ':', time.perf_counter() - started) + return value + return wrapped + + +def ensure_holder(parent, type, identifier, default_name): + if parent: + if identifier: + for node in cmds.listRelatives( + parent, children=True, fullPath=True): + if shortname(node) == shortname(identifier): + identifier = node + break + else: + default_name = identifier + identifier = None + if not identifier: + identifier = cmds.createNode( + type, name=default_name, parent=parent) + if not cmds.objExists(identifier): + identifier = cmds.createNode(type, name=identifier) + if parent: + identifier = parent + '|' + identifier.split('|')[-1] + nodes = cmds.ls(identifier, long=True) + assert len(nodes) == 1, ( + 'Unexpected nodes found: {}, for {}'.format( + nodes, identifier)) + return nodes[0] + + +def ensure_data(node, attribute, value): + if not cmds.attributeQuery(attribute, node=node, exists=True): + cmds.addAttr(node, longName=attribute, dataType='string') + value = json.dumps(value) + attribute = node + '.' + attribute + if cmds.getAttr(attribute) != value: + cmds.setAttr(attribute, value, type='string') + + +class SceneDagStorage: + PICKER_HOLDER_NODE = 'DwPicker' + + def load(self): + data = [] + for holder in cmds.ls('::' + self.PICKER_HOLDER_NODE): + for picker_node in cmds.listRelatives( + holder, children=True, fullPath=True): + picker = { + 'general': { + # add corresponding scene node identifier, so storing + # picker data then will be into same node + 'id': picker_node, + }, 'shapes': []} + for attribute in cmds.listAttr( + picker_node, userDefined=True): + value = json.loads(cmds.getAttr( + picker_node + '.' + attribute)) + if attribute == 'version': + picker[attribute] = value + else: + picker['general'][attribute] = value + for shape in cmds.listRelatives( + picker_node, shapes=True, fullPath=True): + shape_data = {'id': shape} + for attribute in cmds.listAttr( + shape, userDefined=True): + value = json.loads(cmds.getAttr( + shape + '.' + attribute)) + shape_data[attribute.replace('_', '.')] = value + picker['shapes'].append(shape_data) + # some weird missing version in picker general section + picker['general']['version'] = picker['version'] + data.append(picker) + return data + + def store(self, pickers): + for picker in pickers: + picker_holder = picker.get('general', {}).get('id') + if not picker_holder or not cmds.objExists(picker_holder): + if not cmds.objExists(self.PICKER_HOLDER_NODE): + cmds.createNode( + 'dagContainer', name=self.PICKER_HOLDER_NODE) + picker_holder = ensure_holder( + self.PICKER_HOLDER_NODE, 'transform', picker_holder, + picker['general']['name']) + ensure_data(picker_holder, 'version', picker['version']) + for key, value in picker['general'].items(): + if key == 'id': + # scene identifier makes sense only in current scene + # context and should not be serialized + continue + ensure_data(picker_holder, key, value) + for shape in picker['shapes']: + shape_holder = ensure_holder( + picker_holder, 'nurbsSurface', shape.get('id'), 'shape') + for key, value in shape.items(): + if key == 'id': + continue + assert '_' not in key + ensure_data(shape_holder, key.replace('.', '_'), value) + + def cleanup(self): + pass + +#data = timeit(dwpicker.scenedata.DefaultSceneStorage().load)() +#data = timeit(SceneDagStorage().load)() +#SceneDagStorage().store(data) +#dwpicker.scenedata.DefaultSceneStorage().store(data) +#pprint.pprint(data) +#dwpicker.show(storage_class=SceneDagStorage) +#dwpicker.show(storage_class=dwpicker.scenedata.DefaultSceneStorage) From 7149c0d13aa2e2faa53f4e6d6562ec5cefa5ca12 Mon Sep 17 00:00:00 2001 From: Konstantin Date: Tue, 3 Oct 2023 01:15:24 +0300 Subject: [PATCH 3/3] Add storage implementation for referencing (wip) --- dwpicker/scenedata_dag.py | 89 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 82 insertions(+), 7 deletions(-) diff --git a/dwpicker/scenedata_dag.py b/dwpicker/scenedata_dag.py index 29d7e3e..7dce71d 100644 --- a/dwpicker/scenedata_dag.py +++ b/dwpicker/scenedata_dag.py @@ -6,6 +6,7 @@ imp.reload(dwpicker) import dwpicker.scenedata from maya import cmds +from dwpicker.namespace import maya_namespace def shortname(fullname): @@ -121,10 +122,84 @@ def store(self, pickers): def cleanup(self): pass -#data = timeit(dwpicker.scenedata.DefaultSceneStorage().load)() -#data = timeit(SceneDagStorage().load)() -#SceneDagStorage().store(data) -#dwpicker.scenedata.DefaultSceneStorage().store(data) -#pprint.pprint(data) -#dwpicker.show(storage_class=SceneDagStorage) -#dwpicker.show(storage_class=dwpicker.scenedata.DefaultSceneStorage) + +def node_namespace(name): + # TODO dwpicker.namespace.node_namespace do not handle + # nested namespaces + return (name.rsplit(':', 1)[:-1] or [None])[-1] + + +class SceneDictDiffStorage: + PICKER_HOLDER_NODE = 'DwPicker' + PICKER_HOLDER_ATTRIBUTE = '_dwpicker_data' + # LS_EXP = ["*." + PICKER_HOLDER_ATTRIBUTE, "*:*." + PICKER_HOLDER_ATTRIBUTE] + LS_EXP = [PICKER_HOLDER_NODE, "*:" + PICKER_HOLDER_NODE] + + def _get_picker_holder_node(self): + if cmds.objExists(self.PICKER_HOLDER_NODE): + return self.PICKER_HOLDER_NODE + return self._create_picker_holder_node() + + def _create_picker_holder_node(self): + with maya_namespace(":"): + node = cmds.createNode('dagContainer', name=self.PICKER_HOLDER_NODE, skipSelect=True) + return node + + def _list_picker_holder_nodes(self): + """ + Look up in the scene all the nodes holding an attribute named + "_dwpicker_holder" which are not set on the "_dwpicker_holder" node. + This mignt happed if a node node is imported (creating a namespace or a + incrementation). + """ + return [node.split(".")[0] for node in cmds.ls(self.LS_EXP)] + + def load(self): + nodes = self._list_picker_holder_nodes() + pickers = [] + added_pickers = set() + # start from root holders + for node in sorted(nodes, key=lambda x: len(x)): + for picker_node in cmds.listRelatives(node, children=True) or []: + if picker_node in added_pickers: + continue + namespace = node_namespace(picker_node) + data = cmds.getAttr(picker_node + '.' + self.PICKER_HOLDER_ATTRIBUTE) + data = dwpicker.scenedata.decode_data(data) + data = dwpicker.scenedata.ensure_retro_compatibility(data) + if namespace: + for s in data['shapes']: + s['action.targets'] = ['{}:{}'.format(namespace, n) + for n in s['action.targets']] + data['source_node'] = picker_node + pickers.append(data) + added_pickers.add(data['source_node']) + return pickers + + def store(self, pickers): + node = self._get_picker_holder_node() + for picker_node in cmds.listRelatives(node, children=True) or []: + cmds.delete(picker_node) + for p in pickers: + picker_node = p.get('source_node') or p.get('general', {}).get('name') or 'Picker' + picker_node = cmds.createNode('dagContainer', name=picker_node, parent=node, skipSelect=True) + if p.get('source_node') == picker_node: + del p['source_node'] + cmds.addAttr( + picker_node, longName=self.PICKER_HOLDER_ATTRIBUTE, dataType='string') + data = dwpicker.scenedata.encode_data(p) + cmds.setAttr(picker_node + '.' + self.PICKER_HOLDER_ATTRIBUTE, data, type='string') + + def cleanup(self): + pass + + +# data = timeit(dwpicker.scenedata.DefaultSceneStorage().load)() +# data = timeit(SceneDagStorage().load)() +# SceneDagStorage().store(data) +# dwpicker.scenedata.DefaultSceneStorage().store(data) +# pprint.pprint(data) +# dwpicker.show(storage_class=SceneDagStorage) +# dwpicker.show(storage_class=dwpicker.scenedata.DefaultSceneStorage) +# import dwpicker.scenedata_dag +# dwpicker.show(storage_class=dwpicker.scenedata_dag.SceneDictDiffStorage)