Update
This commit is contained in:
870
2023/scripts/animation_tools/dwpicker/main.py
Normal file
870
2023/scripts/animation_tools/dwpicker/main.py
Normal file
@@ -0,0 +1,870 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import os
|
||||
import json
|
||||
import webbrowser
|
||||
from copy import deepcopy
|
||||
from functools import partial
|
||||
|
||||
from .pyside import QtWidgets, QtCore, QtGui
|
||||
|
||||
from maya import cmds
|
||||
import maya.OpenMaya as om
|
||||
|
||||
from .appinfos import (
|
||||
VERSION, RELEASE_DATE, DW_GITHUB, DW_WEBSITE, PICKER_DOCUMENTATION)
|
||||
from .compatibility import ensure_retro_compatibility
|
||||
from .document import PickerDocument
|
||||
from .designer.editor import PickerEditor
|
||||
from .dialog import (
|
||||
question, get_image_path, NamespaceDialog)
|
||||
from .ingest import animschool
|
||||
from .hotkeys import get_hotkeys_config
|
||||
from .namespace import (
|
||||
switch_namespace, selected_namespace, detect_picker_namespace,
|
||||
pickers_namespaces)
|
||||
from .optionvar import (
|
||||
AUTO_FOCUS_BEHAVIOR, AUTO_SWITCH_TAB, AUTO_RESIZE_NAMESPACE_COMBO,
|
||||
CHECK_IMAGES_PATHS, AUTO_SET_NAMESPACE, DISABLE_IMPORT_CALLBACKS,
|
||||
DISPLAY_HIERARCHY_IN_PICKER, DISPLAY_QUICK_OPTIONS,
|
||||
INSERT_TAB_AFTER_CURRENT, LAST_OPEN_DIRECTORY, LAST_IMPORT_DIRECTORY,
|
||||
LAST_SAVE_DIRECTORY, NAMESPACE_TOOLBAR, USE_ICON_FOR_UNSAVED_TAB,
|
||||
WARN_ON_TAB_CLOSED, save_optionvar, append_recent_filename,
|
||||
save_opened_filenames)
|
||||
from .path import get_import_directory, get_open_directory, format_path
|
||||
from .picker import PickerStackedView, list_targets
|
||||
from .preference import PreferencesWindow
|
||||
from .qtutils import set_shortcut, icon, maya_main_window, DockableBase
|
||||
from .quick import QuickOptions
|
||||
from .references import ensure_images_path_exists
|
||||
from .scenedata import (
|
||||
load_local_picker_data, store_local_picker_data,
|
||||
clean_stray_picker_holder_nodes)
|
||||
from .templates import PICKER, BACKGROUND
|
||||
|
||||
|
||||
ABOUT = """\
|
||||
DreamWall Picker
|
||||
Licence MIT
|
||||
Version: {version}
|
||||
Release date: {release}
|
||||
Authors: Lionel Brouyère, Olivier Evers
|
||||
Contributor(s):
|
||||
Herizo Ran, fabiencollet, c-morten, kalemas (Konstantin Maslyuk),
|
||||
Markus Ng, Jerome Drese, Renaud Lessard Larouche
|
||||
|
||||
Features:
|
||||
Animation picker widget.
|
||||
Quick picker creation.
|
||||
Advanced picker editing.
|
||||
Read AnimSchoolPicker files (december 2021 version and latest)
|
||||
Free and open source, today and forever.
|
||||
|
||||
This tool is a fork of Hotbox Designer (Lionel Brouyère).
|
||||
A menus, markmenu and hotbox designer cross DCC.
|
||||
https://github.com/luckylyk/hotbox_designer
|
||||
""".format(
|
||||
version=".".join(str(n) for n in VERSION),
|
||||
release=RELEASE_DATE)
|
||||
WINDOW_TITLE = "DreamWall - Picker"
|
||||
WINDOW_CONTROL_NAME = "dwPickerWindow"
|
||||
CLOSE_CALLBACK_COMMAND = "import dwpicker;dwpicker._dwpicker.close_event()"
|
||||
CLOSE_TAB_WARNING = """\
|
||||
Close the tab will remove completely the picker data from the scene.
|
||||
Are you sure to continue ?"""
|
||||
|
||||
|
||||
class DwPicker(DockableBase, QtWidgets.QWidget):
|
||||
def __init__(
|
||||
self,
|
||||
replace_namespace_function=None,
|
||||
list_namespaces_function=None):
|
||||
super(DwPicker, self).__init__(control_name=WINDOW_CONTROL_NAME)
|
||||
self.setWindowTitle(WINDOW_TITLE)
|
||||
self.shortcuts = {}
|
||||
self.replace_namespace_custom_function = replace_namespace_function
|
||||
self.list_namespaces_function = list_namespaces_function
|
||||
|
||||
self.editable = True
|
||||
self.callbacks = []
|
||||
self.stored_focus = None
|
||||
self.editors = []
|
||||
self.pickers = []
|
||||
self.preferences_window = PreferencesWindow(
|
||||
callback=self.load_ui_states, parent=maya_main_window())
|
||||
self.preferences_window.need_update_callbacks.connect(
|
||||
self.reload_callbacks)
|
||||
self.preferences_window.hotkey_changed.connect(self.register_shortcuts)
|
||||
|
||||
self.sub_panels_view = QtWidgets.QToolButton()
|
||||
self.sub_panels_view.setCheckable(True)
|
||||
self.sub_panels_view.setChecked(True)
|
||||
self.sub_panels_view.setIcon(icon("panels.png"))
|
||||
self.sub_panels_view.setFixedSize(17, 17)
|
||||
self.sub_panels_view.released.connect(self.update_panels_display_mode)
|
||||
self.sub_tabs_view = QtWidgets.QToolButton()
|
||||
self.sub_tabs_view.setCheckable(True)
|
||||
self.sub_tabs_view.setIcon(icon("tabs.png"))
|
||||
self.sub_tabs_view.setFixedSize(17, 17)
|
||||
self.sub_tabs_view.released.connect(self.update_panels_display_mode)
|
||||
|
||||
self.panel_buttons = QtWidgets.QButtonGroup()
|
||||
self.panel_buttons.addButton(self.sub_panels_view, 0)
|
||||
self.panel_buttons.addButton(self.sub_tabs_view, 1)
|
||||
|
||||
self.namespace_label = QtWidgets.QLabel("Namespace: ")
|
||||
self.namespace_combo = QtWidgets.QComboBox()
|
||||
self.namespace_combo.setMinimumWidth(200)
|
||||
method = self.change_namespace_combo
|
||||
self.namespace_combo.currentIndexChanged.connect(method)
|
||||
self.namespace_refresh = QtWidgets.QPushButton("")
|
||||
self.namespace_refresh.setIcon(icon("reload.png"))
|
||||
self.namespace_refresh.setFixedSize(17, 17)
|
||||
self.namespace_refresh.setIconSize(QtCore.QSize(15, 15))
|
||||
self.namespace_refresh.released.connect(self.update_namespaces)
|
||||
self.namespace_picker = QtWidgets.QPushButton("")
|
||||
self.namespace_picker.setIcon(icon("picker.png"))
|
||||
self.namespace_picker.setFixedSize(17, 17)
|
||||
self.namespace_picker.setIconSize(QtCore.QSize(15, 15))
|
||||
self.namespace_picker.released.connect(self.pick_namespace)
|
||||
self.namespace_widget = QtWidgets.QWidget()
|
||||
self.namespace_layout = QtWidgets.QHBoxLayout(self.namespace_widget)
|
||||
self.namespace_layout.setContentsMargins(10, 2, 2, 2)
|
||||
self.namespace_layout.setSpacing(0)
|
||||
self.namespace_layout.addWidget(self.namespace_label)
|
||||
self.namespace_layout.addSpacing(4)
|
||||
self.namespace_layout.addWidget(self.namespace_combo)
|
||||
self.namespace_layout.addSpacing(2)
|
||||
self.namespace_layout.addWidget(self.namespace_refresh)
|
||||
self.namespace_layout.addWidget(self.namespace_picker)
|
||||
self.namespace_layout.addStretch(1)
|
||||
self.namespace_layout.addWidget(self.sub_panels_view)
|
||||
self.namespace_layout.addWidget(self.sub_tabs_view)
|
||||
|
||||
self.tab = QtWidgets.QTabWidget()
|
||||
self.tab.setTabsClosable(True)
|
||||
self.tab.setMovable(True)
|
||||
self.tab.tabBar().tabMoved.connect(self.tab_moved)
|
||||
self.tab.tabBar().tabBarDoubleClicked.connect(self.change_title)
|
||||
self.tab.currentChanged.connect(self.tab_index_changed)
|
||||
method = partial(self.close_tab, store=True)
|
||||
self.tab.tabCloseRequested.connect(method)
|
||||
|
||||
self.quick_options = QuickOptions()
|
||||
|
||||
self.menubar = DwPickerMenu(parent=self)
|
||||
self.menubar.new.triggered.connect(self.call_new)
|
||||
self.menubar.open.triggered.connect(self.call_open)
|
||||
self.menubar.save.triggered.connect(self.call_save)
|
||||
self.menubar.save_as.triggered.connect(self.call_save_as)
|
||||
self.menubar.exit.triggered.connect(self.close)
|
||||
self.menubar.import_.triggered.connect(self.call_import)
|
||||
self.menubar.undo.triggered.connect(self.call_undo)
|
||||
self.menubar.redo.triggered.connect(self.call_redo)
|
||||
self.menubar.advanced_edit.triggered.connect(self.call_edit)
|
||||
self.menubar.preferences.triggered.connect(self.call_preferences)
|
||||
self.menubar.change_title.triggered.connect(self.change_title)
|
||||
self.menubar.toggle_display.triggered.connect(self.toggle_display_mode)
|
||||
method = self.toggle_hierarchy_display
|
||||
self.menubar.toggle_hierarchy_display.triggered.connect(method)
|
||||
method = self.change_namespace_dialog
|
||||
self.menubar.change_namespace.triggered.connect(method)
|
||||
self.menubar.add_background.triggered.connect(self.add_background)
|
||||
self.menubar.tools.triggered.connect(self.call_tools)
|
||||
self.menubar.documentation.triggered.connect(self.call_documentation)
|
||||
self.menubar.dw.triggered.connect(self.call_dreamwall)
|
||||
self.menubar.about.triggered.connect(self.call_about)
|
||||
|
||||
self.layout = QtWidgets.QVBoxLayout(self)
|
||||
self.layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.layout.setSpacing(0)
|
||||
self.layout.setMenuBar(self.menubar)
|
||||
self.layout.addWidget(self.namespace_widget)
|
||||
self.layout.addWidget(self.tab)
|
||||
self.layout.addWidget(self.quick_options)
|
||||
|
||||
self.load_ui_states()
|
||||
self.register_shortcuts()
|
||||
|
||||
def register_shortcuts(self):
|
||||
# Unregister all shortcuts before create new ones
|
||||
function_names_actions = {
|
||||
'focus': (self.reset, None),
|
||||
'new': (self.call_new, self.menubar.new),
|
||||
'open': (self.call_open, self.menubar.open),
|
||||
'save': (self.call_save, self.menubar.save),
|
||||
'close': (self.close, self.menubar.exit),
|
||||
'undo': (self.call_undo, self.menubar.undo),
|
||||
'redo': (self.call_redo, self.menubar.redo),
|
||||
'edit': (self.call_edit, self.menubar.advanced_edit),
|
||||
'next_tab': (self.call_next_tab, None),
|
||||
'previous_tab': (self.call_previous_tab, None),
|
||||
'toggle_display': (
|
||||
self.toggle_display_mode, self.menubar.toggle_display),
|
||||
'display_hierarchy': (
|
||||
self.toggle_hierarchy_display,
|
||||
self.menubar.toggle_hierarchy_display)
|
||||
}
|
||||
for function_name, sc in self.shortcuts.items():
|
||||
sc.activated.disconnect(function_names_actions[function_name][0])
|
||||
seq = QtGui.QKeySequence()
|
||||
action = function_names_actions[function_name][1]
|
||||
if not action:
|
||||
continue
|
||||
action.setShortcut(seq)
|
||||
|
||||
self.shortcuts = {}
|
||||
shortcut_context = QtCore.Qt.WidgetWithChildrenShortcut
|
||||
for function_name, data in get_hotkeys_config().items():
|
||||
if not data['enabled']:
|
||||
continue
|
||||
method = function_names_actions[function_name][0]
|
||||
ks = data['key_sequence']
|
||||
if ks is None:
|
||||
continue
|
||||
sc = set_shortcut(ks, self, method, shortcut_context)
|
||||
self.shortcuts[function_name] = sc
|
||||
# HACK: Need to implement twice the shortcut to display key
|
||||
# sequence in the menu and keep it active when the view is docked.
|
||||
action = function_names_actions[function_name][1]
|
||||
if action is None:
|
||||
continue
|
||||
action.setShortcut(ks)
|
||||
action.setShortcutContext(shortcut_context)
|
||||
|
||||
def show(self, *args, **kwargs):
|
||||
super(DwPicker, self).show(
|
||||
closeCallback=CLOSE_CALLBACK_COMMAND, *args, **kwargs)
|
||||
self.register_callbacks()
|
||||
|
||||
def close_event(self):
|
||||
self.preferences_window.close()
|
||||
|
||||
def list_scene_namespaces(self):
|
||||
if self.list_namespaces_function:
|
||||
ns = self.list_namespaces_function()
|
||||
else:
|
||||
ns = cmds.namespaceInfo(listOnlyNamespaces=True, recurse=True)
|
||||
ns = ns or []
|
||||
namespaces = ns + pickers_namespaces(self.pickers)
|
||||
return sorted(list(set(namespaces)))
|
||||
|
||||
def update_namespaces(self, *_):
|
||||
self.namespace_combo.blockSignals(True)
|
||||
self.namespace_combo.clear()
|
||||
self.namespace_combo.addItem("*Root*")
|
||||
namespaces = self.list_scene_namespaces()
|
||||
self.namespace_combo.addItems(namespaces)
|
||||
self.namespace_combo.blockSignals(False)
|
||||
|
||||
# Auto update namespace combo to namespace size.
|
||||
if not cmds.optionVar(query=AUTO_RESIZE_NAMESPACE_COMBO):
|
||||
self.namespace_combo.setSizePolicy(
|
||||
QtWidgets.QSizePolicy.MinimumExpanding,
|
||||
QtWidgets.QSizePolicy.Minimum)
|
||||
self.namespace_combo.setMinimumWidth(200)
|
||||
return
|
||||
max_width = 0
|
||||
for i in range(self.namespace_combo.count()):
|
||||
t = self.namespace_combo.itemText(i)
|
||||
width = self.namespace_combo.fontMetrics().horizontalAdvance(t)
|
||||
max_width = max(max_width, width)
|
||||
width = max_width + 20 # padding
|
||||
self.namespace_combo.setFixedWidth(max((200, width)))
|
||||
|
||||
def toggle_display_mode(self):
|
||||
index = int(not bool(self.panel_buttons.checkedId()))
|
||||
self.panel_buttons.button(index).setChecked(True)
|
||||
self.update_panels_display_mode()
|
||||
self.setFocus()
|
||||
|
||||
def toggle_hierarchy_display(self):
|
||||
state = not bool(cmds.optionVar(query=DISPLAY_HIERARCHY_IN_PICKER))
|
||||
save_optionvar(DISPLAY_HIERARCHY_IN_PICKER, int(state))
|
||||
self.update()
|
||||
self.setFocus()
|
||||
|
||||
def update_panels_display_mode(self, *_):
|
||||
state = bool(self.panel_buttons.checkedId())
|
||||
picker = self.tab.currentWidget()
|
||||
if picker is None:
|
||||
return
|
||||
focused_panel = picker.reset()
|
||||
picker.as_sub_tab = state
|
||||
picker.create_pickers()
|
||||
picker.create_panels(panel=focused_panel)
|
||||
QtCore.QTimer.singleShot(10, partial(picker.reset, force_all=True))
|
||||
picker.update()
|
||||
|
||||
def tab_index_changed(self, index):
|
||||
if not self.pickers:
|
||||
return
|
||||
picker = self.pickers[index]
|
||||
index = int(picker.as_sub_tab)
|
||||
self.panel_buttons.button(index).setChecked(True)
|
||||
if not picker:
|
||||
return
|
||||
namespace = detect_picker_namespace(picker.document.shapes)
|
||||
self.namespace_combo.blockSignals(True)
|
||||
if self.namespace_combo.findText(namespace) == -1 and namespace:
|
||||
self.namespace_combo.addItem(namespace)
|
||||
if namespace:
|
||||
self.namespace_combo.setCurrentText(namespace)
|
||||
else:
|
||||
self.namespace_combo.setCurrentIndex(0)
|
||||
self.namespace_combo.blockSignals(False)
|
||||
|
||||
def tab_moved(self, newindex, oldindex):
|
||||
|
||||
for l in (self.editors, self.pickers):
|
||||
l.insert(newindex, l.pop(oldindex))
|
||||
|
||||
self.store_local_pickers_data()
|
||||
|
||||
def leaveEvent(self, _):
|
||||
mode = cmds.optionVar(query=AUTO_FOCUS_BEHAVIOR)
|
||||
if mode == 'off':
|
||||
return
|
||||
cmds.setFocus("MayaWindow")
|
||||
|
||||
def enterEvent(self, _):
|
||||
mode = cmds.optionVar(query=AUTO_FOCUS_BEHAVIOR)
|
||||
if mode == 'bilateral':
|
||||
cmds.setFocus(self.objectName())
|
||||
|
||||
def dockCloseEventTriggered(self):
|
||||
save_opened_filenames([
|
||||
p.document.filename for p in self.pickers
|
||||
if p.document.filename])
|
||||
|
||||
modified = [p.document.modified_state for p in self.pickers]
|
||||
if not any(modified):
|
||||
return super(DwPicker, self).dockCloseEventTriggered()
|
||||
|
||||
msg = (
|
||||
'Some picker have unsaved modification. \n'
|
||||
'Would you like to save them ?')
|
||||
result = QtWidgets.QMessageBox.question(
|
||||
None, 'Save ?', msg,
|
||||
buttons=(
|
||||
QtWidgets.QMessageBox.SaveAll |
|
||||
QtWidgets.QMessageBox.Close),
|
||||
defaultButton=QtWidgets.QMessageBox.SaveAll)
|
||||
|
||||
if result == QtWidgets.QMessageBox.Close:
|
||||
return
|
||||
|
||||
for i in range(self.tab.count()-1, -1, -1):
|
||||
self.save_tab(i)
|
||||
|
||||
save_opened_filenames([p.document.filename for p in self.pickers])
|
||||
return super(DwPicker, self).dockCloseEventTriggered()
|
||||
|
||||
def reload_callbacks(self):
|
||||
self.unregister_callbacks()
|
||||
self.register_callbacks()
|
||||
|
||||
def register_callbacks(self):
|
||||
self.unregister_callbacks()
|
||||
callbacks = {
|
||||
om.MSceneMessage.kBeforeNew: [
|
||||
self.close_tabs, self.update_namespaces],
|
||||
om.MSceneMessage.kAfterOpen: [
|
||||
self.load_saved_pickers, self.update_namespaces],
|
||||
om.MSceneMessage.kAfterCreateReference: [
|
||||
self.load_saved_pickers, self.update_namespaces]}
|
||||
if not cmds.optionVar(query=DISABLE_IMPORT_CALLBACKS):
|
||||
callbacks[om.MSceneMessage.kAfterImport] = [
|
||||
self.load_saved_pickers, self.update_namespaces]
|
||||
|
||||
for event, methods in callbacks.items():
|
||||
for method in methods:
|
||||
callback = om.MSceneMessage.addCallback(event, method)
|
||||
self.callbacks.append(callback)
|
||||
|
||||
method = self.auto_switch_tab
|
||||
cb = om.MEventMessage.addEventCallback('SelectionChanged', method)
|
||||
self.callbacks.append(cb)
|
||||
method = self.auto_switch_namespace
|
||||
cb = om.MEventMessage.addEventCallback('SelectionChanged', method)
|
||||
self.callbacks.append(cb)
|
||||
|
||||
for picker in self.pickers:
|
||||
picker.register_callbacks()
|
||||
|
||||
def unregister_callbacks(self):
|
||||
for cb in self.callbacks:
|
||||
om.MMessage.removeCallback(cb)
|
||||
self.callbacks.remove(cb)
|
||||
for picker in self.pickers:
|
||||
picker.unregister_callbacks()
|
||||
|
||||
def auto_switch_namespace(self, *_, **__):
|
||||
if not cmds.optionVar(query=AUTO_SET_NAMESPACE):
|
||||
return
|
||||
self.pick_namespace()
|
||||
|
||||
def auto_switch_tab(self, *_, **__):
|
||||
if not cmds.optionVar(query=AUTO_SWITCH_TAB):
|
||||
return
|
||||
nodes = cmds.ls(selection=True)
|
||||
if not nodes:
|
||||
return
|
||||
picker = self.tab.currentWidget()
|
||||
if not picker:
|
||||
return
|
||||
targets = list_targets(picker.document.shapes)
|
||||
if nodes[-1] in targets:
|
||||
return
|
||||
for i, picker in enumerate(self.pickers):
|
||||
if nodes[-1] in list_targets(picker.document.shapes):
|
||||
self.tab.setCurrentIndex(i)
|
||||
return
|
||||
|
||||
def load_saved_pickers(self, *_, **__):
|
||||
self.clear()
|
||||
pickers = load_local_picker_data()
|
||||
if cmds.optionVar(query=CHECK_IMAGES_PATHS):
|
||||
ensure_images_path_exists(pickers)
|
||||
for picker in pickers:
|
||||
self.add_picker(picker)
|
||||
clean_stray_picker_holder_nodes()
|
||||
|
||||
def store_local_pickers_data(self):
|
||||
if not self.editable:
|
||||
return
|
||||
|
||||
if not self.tab.count():
|
||||
store_local_picker_data([])
|
||||
return
|
||||
|
||||
pickers = [self.document(i).data for i in range(self.tab.count())]
|
||||
store_local_picker_data(pickers)
|
||||
|
||||
def save_tab(self, index):
|
||||
msg = (
|
||||
'Picker contain unsaved modification !\n'
|
||||
'Woud you like to continue ?')
|
||||
result = QtWidgets.QMessageBox.question(
|
||||
None, 'Save ?', msg,
|
||||
buttons=(
|
||||
QtWidgets.QMessageBox.Save |
|
||||
QtWidgets.QMessageBox.Yes |
|
||||
QtWidgets.QMessageBox.Cancel),
|
||||
defaultButton=QtWidgets.QMessageBox.Cancel)
|
||||
|
||||
if result == QtWidgets.QMessageBox.Cancel:
|
||||
return False
|
||||
elif result == QtWidgets.QMessageBox.Save and not self.call_save(index):
|
||||
return False
|
||||
return True
|
||||
|
||||
def close_tabs(self, *_):
|
||||
for i in range(self.tab.count()-1, -1, -1):
|
||||
self.close_tab(i)
|
||||
self.store_local_pickers_data()
|
||||
|
||||
def clear(self):
|
||||
for i in range(self.tab.count()-1, -1, -1):
|
||||
self.close_tab(i, force=True)
|
||||
|
||||
def close_tab(self, index, force=False, store=False):
|
||||
if self.document(index).modified_state and force is False:
|
||||
if not self.save_tab(index):
|
||||
return
|
||||
|
||||
elif (cmds.optionVar(query=WARN_ON_TAB_CLOSED) and
|
||||
not question('Warning', CLOSE_TAB_WARNING)):
|
||||
return
|
||||
|
||||
editor = self.editors.pop(index)
|
||||
if editor:
|
||||
editor.close()
|
||||
picker = self.pickers.pop(index)
|
||||
picker.unregister_callbacks()
|
||||
picker.close()
|
||||
self.tab.removeTab(index)
|
||||
if store:
|
||||
self.store_local_pickers_data()
|
||||
|
||||
def load_ui_states(self):
|
||||
value = bool(cmds.optionVar(query=DISPLAY_QUICK_OPTIONS))
|
||||
self.quick_options.setVisible(value)
|
||||
value = bool(cmds.optionVar(query=NAMESPACE_TOOLBAR))
|
||||
self.namespace_widget.setVisible(value)
|
||||
self.update_namespaces()
|
||||
self.update_modified_states()
|
||||
|
||||
def add_picker_from_file(self, filename):
|
||||
with open(filename, "r") as f:
|
||||
data = ensure_retro_compatibility(json.load(f))
|
||||
ensure_images_path_exists([data])
|
||||
self.add_picker(data, filename=filename)
|
||||
append_recent_filename(filename)
|
||||
|
||||
def reset(self):
|
||||
picker = self.tab.currentWidget()
|
||||
if picker:
|
||||
picker.reset()
|
||||
|
||||
def general_changed(self, origin, option):
|
||||
if option == 'name' and origin != 'main_window':
|
||||
self.update_names()
|
||||
if option == 'panels.as_sub_tab' and origin != 'main_window':
|
||||
index = int(self.document().data['general']['panels.as_sub_tab'])
|
||||
self.panel_buttons.button(index).setChecked(True)
|
||||
|
||||
def update_names(self):
|
||||
for i in range(self.tab.count()):
|
||||
self.set_title(i, self.document(i).data['general']['name'])
|
||||
|
||||
def create_picker(self, data):
|
||||
document = PickerDocument(data)
|
||||
document.changed.connect(self.store_local_pickers_data)
|
||||
document.general_option_changed.connect(self.general_changed)
|
||||
document.data_changed.connect(self.update_names)
|
||||
document.changed.connect(self.update_modified_states)
|
||||
picker = PickerStackedView(document, self.editable)
|
||||
picker.register_callbacks()
|
||||
return picker
|
||||
|
||||
def add_picker(self, data, filename=None, modified_state=False):
|
||||
picker = self.create_picker(data)
|
||||
picker.document.filename = filename
|
||||
picker.document.modified_state = modified_state
|
||||
insert = cmds.optionVar(query=INSERT_TAB_AFTER_CURRENT)
|
||||
if not insert or self.tab.currentIndex() == self.tab.count() - 1:
|
||||
self.pickers.append(picker)
|
||||
self.editors.append(None)
|
||||
self.tab.addTab(picker, data['general']['name'])
|
||||
self.tab.setCurrentIndex(self.tab.count() - 1)
|
||||
else:
|
||||
index = self.tab.currentIndex() + 1
|
||||
self.pickers.insert(index, picker)
|
||||
self.editors.insert(index, None)
|
||||
self.tab.insertTab(index, picker, data['general']['name'])
|
||||
self.tab.setCurrentIndex(index)
|
||||
picker.reset(force_all=True)
|
||||
|
||||
def call_open(self):
|
||||
filenames = QtWidgets.QFileDialog.getOpenFileNames(
|
||||
None, "Open a picker...",
|
||||
get_open_directory(),
|
||||
filter="Dreamwall Picker (*.json)")[0]
|
||||
if not filenames:
|
||||
return
|
||||
save_optionvar(LAST_OPEN_DIRECTORY, os.path.dirname(filenames[0]))
|
||||
for filename in filenames:
|
||||
self.add_picker_from_file(filename)
|
||||
self.store_local_pickers_data()
|
||||
|
||||
def call_preferences(self):
|
||||
self.preferences_window.show()
|
||||
|
||||
def call_save(self, index=None):
|
||||
index = self.tab.currentIndex() if type(index) is not int else index
|
||||
filename = self.document(index).filename
|
||||
if not filename:
|
||||
return self.call_save_as(index=index)
|
||||
return self.save_picker(index, filename)
|
||||
|
||||
def call_save_as(self, index=None):
|
||||
index = self.tab.currentIndex() if type(index) is not int else index
|
||||
filename = QtWidgets.QFileDialog.getSaveFileName(
|
||||
None, "Save a picker ...",
|
||||
cmds.optionVar(query=LAST_SAVE_DIRECTORY),
|
||||
filter="Dreamwall Picker (*.json)")[0]
|
||||
|
||||
if not filename:
|
||||
return False
|
||||
|
||||
if os.path.exists(filename):
|
||||
msg = '{} already, exists. Do you want to erase it ?'
|
||||
if not question('File exist', msg.format(filename)):
|
||||
return False
|
||||
|
||||
self.save_picker(index, filename)
|
||||
|
||||
def call_undo(self):
|
||||
index = self.tab.currentIndex()
|
||||
if index < 0:
|
||||
return
|
||||
self.document(index).undo()
|
||||
self.document(index).changed.emit()
|
||||
|
||||
def call_redo(self):
|
||||
index = self.tab.currentIndex()
|
||||
if index < 0:
|
||||
return
|
||||
self.document(index).redo()
|
||||
self.document(index).changed.emit()
|
||||
|
||||
def save_picker(self, index, filename):
|
||||
self.document(index).filename = filename
|
||||
save_optionvar(LAST_SAVE_DIRECTORY, os.path.dirname(filename))
|
||||
append_recent_filename(filename)
|
||||
with open(filename, 'w') as f:
|
||||
json.dump(self.document(index).data, f, indent=2)
|
||||
|
||||
self.set_modified_state(index, False)
|
||||
return True
|
||||
|
||||
def call_import(self):
|
||||
sources = QtWidgets.QFileDialog.getOpenFileNames(
|
||||
None, "Import a picker...",
|
||||
get_import_directory(),
|
||||
filter="Anim School Picker (*.pkr)")[0]
|
||||
if not sources:
|
||||
return
|
||||
|
||||
dst = QtWidgets.QFileDialog.getExistingDirectory(
|
||||
None,
|
||||
"Conversion destination",
|
||||
os.path.dirname(sources[0]),
|
||||
options=QtWidgets.QFileDialog.ShowDirsOnly)
|
||||
if not dst:
|
||||
return
|
||||
|
||||
save_optionvar(LAST_IMPORT_DIRECTORY, os.path.dirname(sources[0]))
|
||||
for src in sources:
|
||||
filename = animschool.convert(src, dst)
|
||||
self.add_picker_from_file(filename)
|
||||
|
||||
def call_new(self):
|
||||
self.add_picker({
|
||||
'general': deepcopy(PICKER),
|
||||
'shapes': []})
|
||||
self.store_local_pickers_data()
|
||||
|
||||
def document(self, index=None):
|
||||
index = self.tab.currentIndex() if type(index) is not int else index
|
||||
if index < 0:
|
||||
return None
|
||||
picker = self.tab.widget(index)
|
||||
return picker.document
|
||||
|
||||
def call_edit(self):
|
||||
index = self.tab.currentIndex()
|
||||
if index < 0:
|
||||
QtWidgets.QMessageBox.warning(self, "Warning", "No picker set")
|
||||
return
|
||||
if self.editors[index] is None:
|
||||
document = self.document()
|
||||
editor = PickerEditor(
|
||||
document,
|
||||
parent=self)
|
||||
self.editors[index] = editor
|
||||
|
||||
self.editors[index].show()
|
||||
self.editors[index].shape_canvas.focus()
|
||||
|
||||
def call_next_tab(self):
|
||||
index = self.tab.currentIndex() + 1
|
||||
if index == self.tab.count():
|
||||
index = 0
|
||||
self.tab.setCurrentIndex(index)
|
||||
|
||||
def call_previous_tab(self):
|
||||
index = self.tab.currentIndex() - 1
|
||||
if index < 0:
|
||||
index = self.tab.count() - 1
|
||||
self.tab.setCurrentIndex(index)
|
||||
|
||||
def set_editable(self, state):
|
||||
self.editable = state
|
||||
self.menubar.set_editable(state)
|
||||
for picker in self.pickers:
|
||||
picker.editable = state
|
||||
|
||||
def update_modified_states(self):
|
||||
for index, picker in enumerate(self.pickers):
|
||||
state = picker.document.modified_state
|
||||
use_icon = cmds.optionVar(query=USE_ICON_FOR_UNSAVED_TAB)
|
||||
icon_ = icon('save.png') if state and use_icon else QtGui.QIcon()
|
||||
self.tab.setTabIcon(index, icon_)
|
||||
title = self.document(index).data['general']['name']
|
||||
title = "*" + title if state and not use_icon else title
|
||||
self.tab.setTabText(index, title)
|
||||
|
||||
def set_modified_state(self, index, state):
|
||||
"""
|
||||
Update the tab icon. Add a "save" icon if tab contains unsaved
|
||||
modifications.
|
||||
"""
|
||||
if not self.document(index).filename:
|
||||
return
|
||||
self.document(index).modified_state = state
|
||||
use_icon = cmds.optionVar(query=USE_ICON_FOR_UNSAVED_TAB)
|
||||
icon_ = icon('save.png') if state and use_icon else QtGui.QIcon()
|
||||
self.tab.setTabIcon(index, icon_)
|
||||
title = self.document(index).data['general']['name']
|
||||
title = "*" + title if state and not use_icon else title
|
||||
self.tab.setTabText(index, title)
|
||||
|
||||
def call_tools(self):
|
||||
webbrowser.open(DW_GITHUB)
|
||||
|
||||
def call_dreamwall(self):
|
||||
webbrowser.open(DW_WEBSITE)
|
||||
|
||||
def call_documentation(self):
|
||||
webbrowser.open(PICKER_DOCUMENTATION)
|
||||
|
||||
def call_about(self):
|
||||
QtWidgets.QMessageBox.about(self, 'About', ABOUT)
|
||||
|
||||
def sizeHint(self):
|
||||
return QtCore.QSize(500, 800)
|
||||
|
||||
def change_title(self, index=None):
|
||||
if not self.editable:
|
||||
return
|
||||
index = (
|
||||
self.tab.currentIndex() if not isinstance(index, int) else index)
|
||||
if index < 0:
|
||||
return
|
||||
title, operate = QtWidgets.QInputDialog.getText(
|
||||
None, 'Change picker title', 'New title',
|
||||
text=self.document(index).data['general']['name'])
|
||||
|
||||
if not operate:
|
||||
return
|
||||
self.set_title(index, title)
|
||||
index = (
|
||||
self.tab.currentIndex() if not isinstance(index, int) else index)
|
||||
if index < 0:
|
||||
return
|
||||
document = self.document(index)
|
||||
document.data['general']['name'] = title
|
||||
document.general_option_changed.emit('main_window', 'name')
|
||||
self.document(index).record_undo()
|
||||
self.set_title(index, title)
|
||||
|
||||
def set_title(self, index=None, title=''):
|
||||
use_icon = cmds.optionVar(query=USE_ICON_FOR_UNSAVED_TAB)
|
||||
if not use_icon and self.document(index).modified_state:
|
||||
title = "*" + title
|
||||
self.tab.setTabText(index, title)
|
||||
|
||||
def change_namespace_dialog(self):
|
||||
dialog = NamespaceDialog()
|
||||
if not dialog.exec_():
|
||||
return
|
||||
namespace = dialog.namespace
|
||||
self.change_namespace(namespace)
|
||||
|
||||
def change_namespace_combo(self):
|
||||
index = self.namespace_combo.currentIndex()
|
||||
text = self.namespace_combo.currentText()
|
||||
namespace = text if index else ":"
|
||||
self.change_namespace(namespace)
|
||||
|
||||
def pick_namespace(self):
|
||||
namespace = selected_namespace()
|
||||
self.namespace_combo.setCurrentText(namespace)
|
||||
|
||||
def change_namespace(self, namespace):
|
||||
document = self.document()
|
||||
if not document:
|
||||
return
|
||||
switch_namespace_function = (
|
||||
self.replace_namespace_custom_function or switch_namespace)
|
||||
for shape in document.shapes:
|
||||
if not shape.targets():
|
||||
continue
|
||||
targets = [
|
||||
switch_namespace_function(t, namespace)
|
||||
for t in shape.targets()]
|
||||
shape.options['action.targets'] = targets
|
||||
document.record_undo()
|
||||
document.shapes_changed.emit()
|
||||
|
||||
def add_background(self):
|
||||
filename = get_image_path(self)
|
||||
if not filename:
|
||||
return
|
||||
|
||||
filename = format_path(filename)
|
||||
shape = deepcopy(BACKGROUND)
|
||||
shape['image.path'] = filename
|
||||
image = QtGui.QImage(filename)
|
||||
shape['image.width'] = image.size().width()
|
||||
shape['image.height'] = image.size().height()
|
||||
shape['shape.width'] = image.size().width()
|
||||
shape['shape.height'] = image.size().height()
|
||||
shape['bgcolor.transparency'] = 255
|
||||
self.document().add_shapes([shape], prepend=True)
|
||||
self.document().record_undo()
|
||||
self.document().shapes_changed.emit()
|
||||
|
||||
|
||||
class DwPickerMenu(QtWidgets.QMenuBar):
|
||||
def __init__(self, parent=None):
|
||||
super(DwPickerMenu, self).__init__(parent)
|
||||
self.new = QtWidgets.QAction('&New', parent)
|
||||
self.open = QtWidgets.QAction('&Open', parent)
|
||||
self.import_ = QtWidgets.QAction('&Import', parent)
|
||||
self.save = QtWidgets.QAction('&Save', parent)
|
||||
self.save_as = QtWidgets.QAction('&Save as', parent)
|
||||
self.exit = QtWidgets.QAction('Exit', parent)
|
||||
|
||||
self.undo = QtWidgets.QAction('Undo', parent)
|
||||
self.redo = QtWidgets.QAction('Redo', parent)
|
||||
|
||||
self.advanced_edit = QtWidgets.QAction('Advanced &editing', parent)
|
||||
self.preferences = QtWidgets.QAction('Preferences', parent)
|
||||
text = 'Toggle panel display mode'
|
||||
self.toggle_display = QtWidgets.QAction(text, parent)
|
||||
text = 'Toggle hierarchy display'
|
||||
self.toggle_hierarchy_display = QtWidgets.QAction(text, parent)
|
||||
self.change_title = QtWidgets.QAction('Change picker title', parent)
|
||||
self.change_namespace = QtWidgets.QAction('Change namespace', parent)
|
||||
self.add_background = QtWidgets.QAction('Add background item', parent)
|
||||
|
||||
self.documentation = QtWidgets.QAction('Documentation', parent)
|
||||
self.tools = QtWidgets.QAction('Other DreamWall &tools', parent)
|
||||
self.dw = QtWidgets.QAction('&About DreamWall', parent)
|
||||
self.about = QtWidgets.QAction('&About DwPicker', parent)
|
||||
|
||||
self.file = QtWidgets.QMenu('&File', parent)
|
||||
self.file.addAction(self.new)
|
||||
self.file.addAction(self.open)
|
||||
self.file.addAction(self.import_)
|
||||
self.file.addSeparator()
|
||||
self.file.addAction(self.save)
|
||||
self.file.addAction(self.save_as)
|
||||
self.file.addSeparator()
|
||||
self.file.addAction(self.exit)
|
||||
|
||||
self.edit = QtWidgets.QMenu('&Edit', parent)
|
||||
self.edit.addAction(self.undo)
|
||||
self.edit.addAction(self.redo)
|
||||
self.edit.addSeparator()
|
||||
self.edit.addAction(self.advanced_edit)
|
||||
self.edit.addAction(self.preferences)
|
||||
self.edit.addSeparator()
|
||||
self.edit.addAction(self.toggle_display)
|
||||
self.edit.addAction(self.toggle_hierarchy_display)
|
||||
self.edit.addSeparator()
|
||||
self.edit.addAction(self.change_title)
|
||||
self.edit.addSeparator()
|
||||
self.edit.addAction(self.change_namespace)
|
||||
self.edit.addAction(self.add_background)
|
||||
|
||||
self.help = QtWidgets.QMenu('&Help', parent)
|
||||
self.help.addAction(self.documentation)
|
||||
self.help.addSeparator()
|
||||
self.help.addAction(self.tools)
|
||||
self.help.addAction(self.dw)
|
||||
self.help.addSeparator()
|
||||
self.help.addAction(self.about)
|
||||
|
||||
self.addMenu(self.file)
|
||||
self.addMenu(self.edit)
|
||||
self.addMenu(self.help)
|
||||
|
||||
def set_editable(self, state):
|
||||
self.undo.setEnabled(state)
|
||||
self.redo.setEnabled(state)
|
||||
self.change_title.setEnabled(state)
|
||||
self.advanced_edit.setEnabled(state)
|
||||
self.add_background.setEnabled(state)
|
||||
Reference in New Issue
Block a user