Updated
21
Scripts/Animation/dwpicker/LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020 DreamWall
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
56
Scripts/Animation/dwpicker/README.md
Normal file
@ -0,0 +1,56 @@
|
||||
# Dreamwall Picker
|
||||
|
||||
适用于 Autodesk Maya 2017(或更高版本)的动画选择器
|
||||
|
||||
作者:Lionel Brouyère, Olivier Evers
|
||||
> 该工具是 Hotbox Designer(Lionel Brouyère)的一个分支。
|
||||
> 一个跨 DCC 的菜单、标记菜单和热盒设计器。
|
||||
> https://github.com/luckylyk/hotbox_designer
|
||||
|
||||
### 功能
|
||||
- 简单快速的选择器创建。
|
||||
- 导入 2022 年之前完成的 AnimSchool 选择器。
|
||||
- 将选择器存储在 Maya 场景中。
|
||||
- 高级选择器编辑器。
|
||||
- 执行 AnimSchool 选择器的所有功能以及更多功能...
|
||||
<center><img src="https://raw.githubusercontent.com/DreamWall-Animation/dwpicker/main/screenshots/picker.gif" alt="drawing" align="center" width="250"/> <img src="https://s10.gifyu.com/images/createbuttons.gif" alt="drawing" align="center" width="400"/>
|
||||
<img src="https://raw.githubusercontent.com/DreamWall-Animation/dwpicker/main/screenshots/editor.gif" alt="drawing" align="center" width="370"/>
|
||||
|
||||
### 安装
|
||||
将名为 "dwpicker" 的文件夹(不是 dwpicker-main)放入 Maya 脚本文件夹中
|
||||
|
||||
| 操作系统 | 路径 |
|
||||
| ------ | ------ |
|
||||
| Linux | ~/<用户名>/maya/scripts |
|
||||
| Windows | \Users\<用户名>\Documents\maya\scripts |
|
||||
| Mac OS X | ~<用户名>/Library/Preferences/Autodesk/maya/scripts |
|
||||
|
||||
### 如何运行
|
||||
|
||||
```python
|
||||
import dwpicker
|
||||
dwpicker.show()
|
||||
```
|
||||
|
||||
### 常见问题
|
||||
|
||||
#### 它能在 Maya 2025 上运行吗?
|
||||
可以!(自版本 0.11.2 起)
|
||||
|
||||
#### 我的绑定包含多个命名空间或嵌套命名空间。
|
||||
此功能当前不支持。选择器旨在提供单级命名空间的灵活性,允许一个选择器在场景中为同一绑定的多个实例服务。切换选择器的命名空间很简单。然而,尽管我们努力保持这种灵活性,但我们尚未找到支持嵌套命名空间的简单方法。虽然有潜在的解决方案,但它们看起来都相当复杂,难以为用户理解和实现。也许将来会有一个绝妙的主意出现,但目前,这个功能不在我们的计划中。我们欢迎您提出任何建议!
|
||||
|
||||
#### 为什么我不能使用相对路径来存储我的图像文件?
|
||||
当您打开选择器时,它会将文件直接导入场景中,失去原始路径引用。我们选择导入数据而不是直接引用它们,因为许多动画师更喜欢为他们的特定镜头需求自定义选择器(例如,为特定镜头添加道具或约束按钮)。这种方法使得使用相对路径变得复杂。
|
||||
|
||||
#### 如何在我将选择器分享给其他人时保留图像,而他们的文件存储在其他地方?
|
||||
尽管不支持相对路径,但与其他 Maya 路径属性类似,您可以在路径中包含环境变量。设置自定义环境变量可能很复杂。因此,我们建议使用一个默认变量:DWPICKER_PROJECT_DIRECTORY,可在选择器首选项窗口中使用。
|
||||
|
||||
如果您将 DWPICKER_PROJECT_DIRECTORY 配置为 `c:/my_pickers`,并且您有一个图像路径为:
|
||||
`c:/my_pickers/my_character/background.png`,可以这样输入以使路径动态化:`$DWPICKER_PROJECT_DIRECTORY/my_character/background.png`
|
||||
当您从 UI 中选择文件时,它会自动创建包含变量的路径。
|
||||
|
||||
### 支持
|
||||
最好在 GitHub 页面上发布问题。\
|
||||
如果您没有 GitHub 帐户,可以发送邮件至 `brouyere |a| dreamwall.be`。\
|
||||
请在邮件主题中以 ***[dwpicker]*** 开头。(请注意,使用这种方式回复的延迟可能会更长)。
|
0
Scripts/Animation/dwpicker/__init__.py
Normal file
134
Scripts/Animation/dwpicker/dwpicker/__init__.py
Normal file
@ -0,0 +1,134 @@
|
||||
|
||||
import os
|
||||
import sys
|
||||
from maya import cmds
|
||||
|
||||
|
||||
if int(cmds.about(majorVersion=True)) >= 2025:
|
||||
print('>> PySide6 Maya version found. PySide2 remap activated.')
|
||||
sys.path.append('{}/qt_remapping'.format(os.path.dirname(__file__)))
|
||||
|
||||
|
||||
from dwpicker.main import DwPicker, WINDOW_CONTROL_NAME
|
||||
from dwpicker.optionvar import ensure_optionvars_exists
|
||||
from dwpicker.qtutils import remove_workspace_control
|
||||
from dwpicker.updatechecker import warn_if_update_available
|
||||
|
||||
|
||||
_dwpicker = None
|
||||
|
||||
|
||||
def show(editable=True, pickers=None, ignore_scene_pickers=False):
|
||||
"""
|
||||
This is the dwpicker default startup function.
|
||||
editable: bool
|
||||
This allow users to do local edit on their picker. This is NOT
|
||||
affecting the original file.
|
||||
pickers: list[str]
|
||||
Path to pickers to open. If scene contains already pickers,
|
||||
they are going to be ignored.
|
||||
ignore_scene_pickers:
|
||||
This is loading the picker empty, ignoring the scene content.
|
||||
"""
|
||||
ensure_optionvars_exists()
|
||||
global _dwpicker
|
||||
if not _dwpicker:
|
||||
warn_if_update_available()
|
||||
_dwpicker = DwPicker()
|
||||
|
||||
try:
|
||||
_dwpicker.show(dockable=True)
|
||||
except RuntimeError:
|
||||
# Workspace control already exists, UI restore as probably failed.
|
||||
remove_workspace_control(WINDOW_CONTROL_NAME)
|
||||
_dwpicker.show()
|
||||
|
||||
_dwpicker.set_editable(editable)
|
||||
if not ignore_scene_pickers and not pickers:
|
||||
_dwpicker.load_saved_pickers()
|
||||
|
||||
if not pickers:
|
||||
return
|
||||
|
||||
_dwpicker.clear()
|
||||
for filename in pickers:
|
||||
try:
|
||||
print(filename)
|
||||
_dwpicker.add_picker_from_file(filename)
|
||||
except BaseException:
|
||||
import traceback
|
||||
print("Not able to load: {}".format(filename))
|
||||
print(traceback.format_exc())
|
||||
_dwpicker.store_local_pickers_data()
|
||||
|
||||
|
||||
def toggle():
|
||||
if not _dwpicker:
|
||||
return show()
|
||||
_dwpicker.setVisible(not _dwpicker.isVisible())
|
||||
|
||||
|
||||
def close():
|
||||
global _dwpicker
|
||||
if not _dwpicker:
|
||||
return
|
||||
|
||||
_dwpicker.unregister_callbacks()
|
||||
for i in range(_dwpicker.tab.count()):
|
||||
picker = _dwpicker.tab.widget(i)
|
||||
picker.unregister_callbacks()
|
||||
|
||||
_dwpicker.close()
|
||||
_dwpicker = None
|
||||
|
||||
|
||||
class disable():
|
||||
"""
|
||||
This context manager temporarily disable the picker callbacks.
|
||||
This is usefull to decorate code which change the maya selection multiple
|
||||
times. This can lead constant refresh of the picker and lead performance
|
||||
issue. This should fix it.
|
||||
"""
|
||||
def __enter__(self):
|
||||
if _dwpicker is None:
|
||||
return
|
||||
_dwpicker.unregister_callbacks()
|
||||
for i in range(_dwpicker.tab.count()):
|
||||
picker = _dwpicker.tab.widget(i)
|
||||
picker.unregister_callbacks()
|
||||
|
||||
def __exit__(self, *_):
|
||||
if _dwpicker is None:
|
||||
return
|
||||
_dwpicker.register_callbacks()
|
||||
for i in range(_dwpicker.tab.count()):
|
||||
picker = _dwpicker.tab.widget(i)
|
||||
picker.register_callbacks()
|
||||
|
||||
|
||||
def current():
|
||||
"""
|
||||
Get the current picker widget visible in the main tab widget.
|
||||
"""
|
||||
if not _dwpicker:
|
||||
return
|
||||
return _dwpicker.tab.currentWidget()
|
||||
|
||||
|
||||
def refresh():
|
||||
"""
|
||||
Trigger this function to refresh ui if the picker datas has been changed
|
||||
manually inside the scene.
|
||||
"""
|
||||
if not _dwpicker:
|
||||
return
|
||||
|
||||
|
||||
def open_picker_file(filepath):
|
||||
"""
|
||||
Add programmatically a picker to the main UI.
|
||||
"""
|
||||
if not _dwpicker:
|
||||
return cmds.warning('Please open picker first.')
|
||||
_dwpicker.add_picker_from_file(filepath)
|
||||
_dwpicker.store_local_pickers_data()
|
86
Scripts/Animation/dwpicker/dwpicker/align.py
Normal file
@ -0,0 +1,86 @@
|
||||
from PySide2 import QtCore
|
||||
from dwpicker.geometry import split_line
|
||||
|
||||
|
||||
def align_shapes(shapes, direction):
|
||||
_direction_matches[direction](shapes)
|
||||
|
||||
|
||||
def align_left(shapes):
|
||||
left = min(s.rect.left() for s in shapes)
|
||||
for shape in shapes:
|
||||
shape.rect.moveLeft(left)
|
||||
shape.synchronize_rect()
|
||||
|
||||
|
||||
def align_h_center(shapes):
|
||||
x = sum(s.rect.center().x() for s in shapes) / len(shapes)
|
||||
for shape in shapes:
|
||||
shape.rect.moveCenter(QtCore.QPointF(x, shape.rect.center().y()))
|
||||
shape.synchronize_rect()
|
||||
|
||||
|
||||
def align_right(shapes):
|
||||
right = max(s.rect.right() for s in shapes)
|
||||
for shape in shapes:
|
||||
shape.rect.moveRight(right)
|
||||
shape.synchronize_rect()
|
||||
|
||||
|
||||
def align_top(shapes):
|
||||
top = min(s.rect.top() for s in shapes)
|
||||
for shape in shapes:
|
||||
shape.rect.moveTop(top)
|
||||
shape.synchronize_rect()
|
||||
|
||||
|
||||
def align_v_center(shapes):
|
||||
y = sum(s.rect.center().y() for s in shapes) / len(shapes)
|
||||
for shape in shapes:
|
||||
shape.rect.moveCenter(QtCore.QPointF(shape.rect.center().x(), y))
|
||||
shape.synchronize_rect()
|
||||
|
||||
|
||||
def align_bottom(shapes):
|
||||
bottom = max(s.rect.bottom() for s in shapes)
|
||||
for shape in shapes:
|
||||
shape.rect.moveBottom(bottom)
|
||||
shape.synchronize_rect()
|
||||
|
||||
|
||||
def arrange_horizontal(shapes):
|
||||
if len(shapes) < 3:
|
||||
return
|
||||
shapes = sorted(shapes, key=lambda s: s.rect.center().x())
|
||||
centers = split_line(
|
||||
point1=shapes[0].rect.center(),
|
||||
point2=shapes[-1].rect.center(),
|
||||
step_number=len(shapes))
|
||||
for shape, center in zip(shapes, centers):
|
||||
point = QtCore.QPointF(center.x(), shape.rect.center().y())
|
||||
shape.rect.moveCenter(point)
|
||||
shape.synchronize_rect()
|
||||
|
||||
|
||||
def arrange_vertical(shapes):
|
||||
if len(shapes) < 3:
|
||||
return
|
||||
shapes = sorted(shapes, key=lambda s: s.rect.center().y())
|
||||
centers = split_line(
|
||||
point1=shapes[0].rect.center(),
|
||||
point2=shapes[-1].rect.center(),
|
||||
step_number=len(shapes))
|
||||
for shape, center in zip(shapes, centers):
|
||||
point = QtCore.QPointF(shape.rect.center().x(), center.y())
|
||||
shape.rect.moveCenter(point)
|
||||
shape.synchronize_rect()
|
||||
|
||||
|
||||
_direction_matches = {
|
||||
'left': align_left,
|
||||
'h_center': align_h_center,
|
||||
'right': align_right,
|
||||
'top': align_top,
|
||||
'v_center': align_v_center,
|
||||
'bottom': align_bottom
|
||||
}
|
4
Scripts/Animation/dwpicker/dwpicker/appinfos.py
Normal file
@ -0,0 +1,4 @@
|
||||
VERSION = 0, 11, 2 # Version, Feature, Hotfix.
|
||||
RELEASE_DATE = 'june 7th 2024'
|
||||
DW_WEBSITE = 'https://fr.dreamwall.be/'
|
||||
DW_GITHUB = 'https://github.com/DreamWall-Animation'
|
29
Scripts/Animation/dwpicker/dwpicker/arrayutils.py
Normal file
@ -0,0 +1,29 @@
|
||||
|
||||
def move_elements_to_array_end(array, elements):
|
||||
return [e for e in array if e not in elements] + [e for e in elements]
|
||||
|
||||
|
||||
def move_elements_to_array_begin(array, elements):
|
||||
return [e for e in elements] + [e for e in array if e not in elements]
|
||||
|
||||
|
||||
def move_up_array_elements(array, elements):
|
||||
for element in reversed(array):
|
||||
if element not in elements:
|
||||
continue
|
||||
index = array.index(element)
|
||||
if index == len(array):
|
||||
continue
|
||||
array.insert(index + 2, element)
|
||||
array.pop(index)
|
||||
|
||||
|
||||
def move_down_array_elements(array, elements):
|
||||
for shape in array:
|
||||
if shape not in elements:
|
||||
continue
|
||||
index = array.index(shape)
|
||||
if index == 0:
|
||||
continue
|
||||
array.pop(index)
|
||||
array.insert(index - 1, shape)
|
22
Scripts/Animation/dwpicker/dwpicker/clipboard.py
Normal file
@ -0,0 +1,22 @@
|
||||
|
||||
|
||||
_clipboard_data = None
|
||||
_clipboard_settings_data = None
|
||||
|
||||
|
||||
def set(data):
|
||||
global _clipboard_data
|
||||
_clipboard_data = data
|
||||
|
||||
|
||||
def get():
|
||||
return _clipboard_data
|
||||
|
||||
|
||||
def set_settings(settings):
|
||||
global _clipboard_settings_data
|
||||
_clipboard_settings_data = settings
|
||||
|
||||
|
||||
def get_settings():
|
||||
return _clipboard_settings_data or {}
|
272
Scripts/Animation/dwpicker/dwpicker/colorwheel.py
Normal file
@ -0,0 +1,272 @@
|
||||
import math
|
||||
from PySide2 import QtWidgets, QtGui, QtCore
|
||||
from dwpicker.qtutils import get_cursor
|
||||
from dwpicker.geometry import (
|
||||
get_relative_point, get_point_on_line, get_absolute_angle_c)
|
||||
|
||||
|
||||
CONICAL_GRADIENT = (
|
||||
(0.0, (0, 255, 255)),
|
||||
(0.16, (0, 0, 255)),
|
||||
(0.33, (255, 0, 255)),
|
||||
(0.5, (255, 0, 0)),
|
||||
(0.66, (255, 255, 0)),
|
||||
(0.83, (0, 255, 0)),
|
||||
(1.0, (0, 255, 255)))
|
||||
TRANSPARENT = 0, 0, 0, 0
|
||||
BLACK = 'black'
|
||||
WHITE = 'white'
|
||||
|
||||
|
||||
class ColorDialog(QtWidgets.QDialog):
|
||||
def __init__(self, hexacolor, parent=None):
|
||||
super(ColorDialog, self).__init__(parent)
|
||||
self.setWindowFlags(QtCore.Qt.FramelessWindowHint)
|
||||
self.setAttribute(QtCore.Qt.WA_TranslucentBackground)
|
||||
self.colorwheel = ColorWheel()
|
||||
self.colorwheel.set_current_color(QtGui.QColor(hexacolor))
|
||||
self.ok = QtWidgets.QPushButton('ok')
|
||||
self.ok.released.connect(self.accept)
|
||||
|
||||
self.layout = QtWidgets.QVBoxLayout(self)
|
||||
self.layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.layout.setSpacing(0)
|
||||
self.layout.addWidget(self.colorwheel)
|
||||
self.layout.addWidget(self.ok)
|
||||
|
||||
def colorname(self):
|
||||
return self.colorwheel.current_color().name()
|
||||
|
||||
def exec_(self):
|
||||
point = get_cursor(self)
|
||||
point.setX(point.x() - 50)
|
||||
point.setY(point.y() - 75)
|
||||
self.move(point)
|
||||
return super(ColorDialog, self).exec_()
|
||||
|
||||
|
||||
class ColorWheel(QtWidgets.QWidget):
|
||||
currentColorChanged = QtCore.Signal(QtGui.QColor)
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(ColorWheel, self).__init__(parent)
|
||||
self._is_clicked = False
|
||||
self._rect = QtCore.QRectF(25, 25, 50, 50)
|
||||
self._current_color = QtGui.QColor(WHITE)
|
||||
self._color_point = QtCore.QPoint(150, 50)
|
||||
self._current_tool = None
|
||||
self._angle = 180
|
||||
self.setFixedSize(100, 100)
|
||||
self.initUI()
|
||||
|
||||
def initUI(self):
|
||||
self._conicalGradient = QtGui.QConicalGradient(
|
||||
self.width() / 2, self.height() / 2, 180)
|
||||
for pos, (r, g, b) in CONICAL_GRADIENT:
|
||||
self._conicalGradient.setColorAt(pos, QtGui.QColor(r, g, b))
|
||||
|
||||
top = self._rect.top()
|
||||
bottom = self._rect.top() + self._rect.height()
|
||||
self._vertical_gradient = QtGui.QLinearGradient(0, top, 0, bottom)
|
||||
self._vertical_gradient.setColorAt(0.0, QtGui.QColor(*TRANSPARENT))
|
||||
self._vertical_gradient.setColorAt(1.0, QtGui.QColor(BLACK))
|
||||
|
||||
left = self._rect.left()
|
||||
right = self._rect.left() + self._rect.width()
|
||||
self._horizontal_gradient = QtGui.QLinearGradient(left, 0, right, 0)
|
||||
self._horizontal_gradient.setColorAt(0.0, QtGui.QColor(WHITE))
|
||||
|
||||
def paintEvent(self, _):
|
||||
try:
|
||||
painter = QtGui.QPainter()
|
||||
painter.begin(self)
|
||||
self.paint(painter)
|
||||
except BaseException:
|
||||
pass # avoid crash
|
||||
# TODO: log the error
|
||||
finally:
|
||||
painter.end()
|
||||
|
||||
def mousePressEvent(self, event):
|
||||
tool = 'rect' if self._rect.contains(event.pos()) else 'wheel'
|
||||
self._current_tool = tool
|
||||
self.mouse_update(event)
|
||||
|
||||
def mouseMoveEvent(self, event):
|
||||
self._is_clicked = True
|
||||
self.mouse_update(event)
|
||||
|
||||
def mouse_update(self, event):
|
||||
if self._current_tool == 'rect':
|
||||
self.color_point = event.pos()
|
||||
else:
|
||||
center = self._get_center()
|
||||
a = QtCore.QPoint(event.pos().x(), center.y())
|
||||
self._angle = get_absolute_angle_c(a=a, b=event.pos(), c=center)
|
||||
|
||||
self.repaint()
|
||||
self.currentColorChanged.emit(self.current_color())
|
||||
|
||||
def mouseReleaseEvent(self, event):
|
||||
self._is_clicked = False
|
||||
|
||||
def paint(self, painter):
|
||||
painter.setRenderHint(QtGui.QPainter.Antialiasing)
|
||||
|
||||
pen = QtGui.QPen(QtGui.QColor(0, 0, 0, 0))
|
||||
pen.setWidth(0)
|
||||
pen.setJoinStyle(QtCore.Qt.MiterJoin)
|
||||
|
||||
painter.setBrush(self._conicalGradient)
|
||||
painter.setPen(pen)
|
||||
painter.drawRoundedRect(
|
||||
6, 6, (self.width() - 12), (self.height() - 12),
|
||||
self.width(), self.height())
|
||||
|
||||
painter.setBrush(self.palette().color(QtGui.QPalette.Window))
|
||||
painter.drawRoundedRect(
|
||||
12.5, 12.5, (self.width() - 25), (self.height() - 25),
|
||||
self.width(), self.height())
|
||||
|
||||
self._horizontal_gradient.setColorAt(
|
||||
1.0, self._get_current_wheel_color())
|
||||
painter.setBrush(self._horizontal_gradient)
|
||||
painter.drawRect(self._rect)
|
||||
|
||||
painter.setBrush(self._vertical_gradient)
|
||||
painter.drawRect(self._rect)
|
||||
|
||||
pen.setColor(QtGui.QColor(BLACK))
|
||||
pen.setWidth(3)
|
||||
painter.setPen(pen)
|
||||
|
||||
angle = math.radians(self._angle)
|
||||
painter.drawLine(
|
||||
get_point_on_line(angle, 37),
|
||||
get_point_on_line(angle, 46))
|
||||
|
||||
pen.setWidth(5)
|
||||
pen.setCapStyle(QtCore.Qt.RoundCap)
|
||||
painter.setPen(pen)
|
||||
painter.drawPoint(self._color_point)
|
||||
|
||||
@property
|
||||
def color_point(self):
|
||||
return self._color_point
|
||||
|
||||
@color_point.setter
|
||||
def color_point(self, point):
|
||||
if point.x() < self._rect.left():
|
||||
x = self._rect.left()
|
||||
elif point.x() > self._rect.left() + self._rect.width():
|
||||
x = self._rect.left() + self._rect.width()
|
||||
else:
|
||||
x = point.x()
|
||||
|
||||
if point.y() < self._rect.top():
|
||||
y = self._rect.top()
|
||||
elif point.y() > self._rect.top() + self._rect.height():
|
||||
y = self._rect.top() + self._rect.height()
|
||||
else:
|
||||
y = point.y()
|
||||
|
||||
self._color_point = QtCore.QPoint(x, y)
|
||||
|
||||
def _get_current_wheel_color(self):
|
||||
degree = 360 - self._angle
|
||||
return QtGui.QColor(*degree_to_color(degree))
|
||||
|
||||
def _get_center(self):
|
||||
return QtCore.QPoint(self.width() / 2, self.height() / 2)
|
||||
|
||||
def current_color(self):
|
||||
point = get_relative_point(self._rect, self.color_point)
|
||||
x_factor = 1.0 - (float(point.x()) / self._rect.width())
|
||||
y_factor = 1.0 - (float(point.y()) / self._rect.height())
|
||||
r, g, b, _ = self._get_current_wheel_color().getRgb()
|
||||
|
||||
# fade to white
|
||||
differences = 255.0 - r, 255.0 - g, 255.0 - b
|
||||
r += round(differences[0] * x_factor)
|
||||
g += round(differences[1] * x_factor)
|
||||
b += round(differences[2] * x_factor)
|
||||
|
||||
# fade to black
|
||||
r = round(r * y_factor)
|
||||
g = round(g * y_factor)
|
||||
b = round(b * y_factor)
|
||||
|
||||
return QtGui.QColor(r, g, b)
|
||||
|
||||
def set_current_color(self, color):
|
||||
[r, g, b] = color.getRgb()[:3]
|
||||
self._angle = 360.0 - (QtGui.QColor(r, g, b).getHslF()[0] * 360.0)
|
||||
self._angle = self._angle if self._angle != 720.0 else 0
|
||||
|
||||
x = ((((
|
||||
sorted([r, g, b], reverse=True)[0] -
|
||||
sorted([r, g, b])[0]) / 255.0) * self._rect.width()) +
|
||||
self._rect.left())
|
||||
|
||||
y = ((((
|
||||
255 - (sorted([r, g, b], reverse=True)[0])) / 255.0) *
|
||||
self._rect.height()) + self._rect.top())
|
||||
|
||||
self._current_color = color
|
||||
self._color_point = QtCore.QPoint(x, y)
|
||||
self.repaint()
|
||||
|
||||
|
||||
def degree_to_color(degree):
|
||||
if degree is None:
|
||||
return None
|
||||
degree = degree / 360.0
|
||||
|
||||
r, g, b = 255.0, 255.0, 255.0
|
||||
contain_red = (
|
||||
(degree >= 0.0 and degree <= 0.33)
|
||||
or (degree >= 0.66 and degree <= 1.0))
|
||||
|
||||
if contain_red:
|
||||
if degree >= 0.66 and degree <= 0.83:
|
||||
factor = degree - 0.66
|
||||
r = round(255 * (factor / .16))
|
||||
if (degree > 0.0 and degree < 0.16) or (degree > 0.83 and degree < 1.0):
|
||||
r = 255
|
||||
elif degree >= 0.16 and degree <= 0.33:
|
||||
factor = degree - 0.16
|
||||
r = 255 - round(255 * (factor / .16))
|
||||
else:
|
||||
r = 0
|
||||
r = min(r, 255)
|
||||
r = max(r, 0)
|
||||
|
||||
# GREEN
|
||||
if degree >= 0.0 and degree <= 0.66:
|
||||
if degree <= 0.16:
|
||||
g = round(255.0 * (degree / .16))
|
||||
elif degree < 0.5:
|
||||
g = 255
|
||||
if degree >= 0.5:
|
||||
factor = degree - 0.5
|
||||
g = 255 - round(255.0 * (factor / .16))
|
||||
else:
|
||||
g = 0
|
||||
g = min(g, 255.0)
|
||||
g = max(g, 0)
|
||||
|
||||
# BLUE
|
||||
if degree >= 0.33 and degree <= 1.0:
|
||||
if degree <= 0.5:
|
||||
factor = degree - 0.33
|
||||
b = round(255 * (factor / .16))
|
||||
elif degree < 0.83:
|
||||
b = 255.0
|
||||
if degree >= 0.83 and degree <= 1.0:
|
||||
factor = degree - 0.83
|
||||
b = 255.0 - round(255.0 * (factor / .16))
|
||||
else:
|
||||
b = 0
|
||||
b = min(b, 255)
|
||||
b = max(b, 0)
|
||||
return r, g, b
|
122
Scripts/Animation/dwpicker/dwpicker/commands.py
Normal file
@ -0,0 +1,122 @@
|
||||
from copy import deepcopy
|
||||
from PySide2 import QtWidgets, QtCore
|
||||
from dwpicker.templates import COMMAND
|
||||
from dwpicker.qtutils import icon
|
||||
from dwpicker.dialog import CommandEditorDialog
|
||||
|
||||
|
||||
class CommandsEditor(QtWidgets.QWidget):
|
||||
valueSet = QtCore.Signal(object)
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(CommandsEditor, self).__init__(parent)
|
||||
self.warning = QtWidgets.QLabel('Select only one shape')
|
||||
|
||||
self.commands = QtWidgets.QListWidget()
|
||||
self.commands.setSelectionMode(QtWidgets.QAbstractItemView.NoSelection)
|
||||
self.commands.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)
|
||||
self.commands.setHorizontalScrollBarPolicy(
|
||||
QtCore.Qt.ScrollBarAlwaysOff)
|
||||
|
||||
self.add_command = QtWidgets.QPushButton('Add command')
|
||||
self.add_command.released.connect(self.call_create_command)
|
||||
self.add_command.setEnabled(False)
|
||||
|
||||
layout = QtWidgets.QVBoxLayout(self)
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
layout.setSpacing(0)
|
||||
layout.addWidget(self.warning)
|
||||
layout.addWidget(self.commands)
|
||||
layout.addWidget(self.add_command)
|
||||
|
||||
def set_options(self, options):
|
||||
self.commands.clear()
|
||||
if len(options) != 1:
|
||||
self.warning.setVisible(True)
|
||||
self.add_command.setEnabled(False)
|
||||
return
|
||||
self.warning.setVisible(False)
|
||||
self.add_command.setEnabled(True)
|
||||
for command in options[0]['action.commands']:
|
||||
self.call_add_command(command)
|
||||
|
||||
def call_create_command(self):
|
||||
command = deepcopy(COMMAND)
|
||||
dialog = CommandEditorDialog(command)
|
||||
if not dialog.exec_():
|
||||
return
|
||||
self.call_add_command(dialog.command_data())
|
||||
self.valueSet.emit(self.commands_data())
|
||||
|
||||
def call_add_command(self, command=None):
|
||||
widget = CommandItemWidget(command)
|
||||
widget.editRequested.connect(self.edit_command)
|
||||
widget.deletedRequested.connect(self.delete_command)
|
||||
item = QtWidgets.QListWidgetItem()
|
||||
item.widget = widget
|
||||
item.setSizeHint(
|
||||
QtCore.QSize(
|
||||
self.commands.width() -
|
||||
self.commands.verticalScrollBar().width(),
|
||||
widget.sizeHint().height()))
|
||||
self.commands.addItem(item)
|
||||
self.commands.setItemWidget(item, widget)
|
||||
|
||||
def edit_command(self, widget):
|
||||
for r in range(self.commands.count()):
|
||||
item = self.commands.item(r)
|
||||
if item.widget != widget:
|
||||
continue
|
||||
dialog = CommandEditorDialog(item.widget.command)
|
||||
if not dialog.exec_():
|
||||
return
|
||||
widget.command = dialog.command_data()
|
||||
widget.update_label()
|
||||
self.valueSet.emit(self.commands_data())
|
||||
|
||||
def delete_command(self, widget):
|
||||
for r in range(self.commands.count()):
|
||||
item = self.commands.item(r)
|
||||
if item.widget != widget:
|
||||
continue
|
||||
self.commands.takeItem(r)
|
||||
self.valueSet.emit(self.commands_data())
|
||||
return
|
||||
|
||||
def commands_data(self):
|
||||
return [
|
||||
self.commands.item(r).widget.command
|
||||
for r in range(self.commands.count())]
|
||||
|
||||
|
||||
class CommandItemWidget(QtWidgets.QWidget):
|
||||
editRequested = QtCore.Signal(object)
|
||||
deletedRequested = QtCore.Signal(object)
|
||||
|
||||
def __init__(self, command, parent=None):
|
||||
super(CommandItemWidget, self).__init__(parent)
|
||||
|
||||
self.command = command
|
||||
self.label = QtWidgets.QLabel(self.get_label())
|
||||
self.edit = QtWidgets.QPushButton(icon('edit2.png'), '')
|
||||
self.edit.released.connect(lambda: self.editRequested.emit(self))
|
||||
self.delete = QtWidgets.QPushButton(icon('delete2.png'), '')
|
||||
self.delete.released.connect(lambda: self.deletedRequested.emit(self))
|
||||
|
||||
layout = QtWidgets.QHBoxLayout(self)
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
layout.setSpacing(0)
|
||||
layout.addWidget(self.label)
|
||||
layout.addStretch()
|
||||
layout.addWidget(self.edit)
|
||||
layout.addWidget(self.delete)
|
||||
|
||||
def get_label(self):
|
||||
language = '<a style="color: #FFFF00"><i>({0})</i></a>'.format(
|
||||
self.command['language'])
|
||||
touchs = [self.command['button'] + 'Click']
|
||||
touchs.extend([m for m in ('ctrl', 'shift') if self.command[m]])
|
||||
return '{} {}'.format('+'.join(touchs), language)
|
||||
|
||||
def update_label(self):
|
||||
self.label.setText(self.get_label())
|
82
Scripts/Animation/dwpicker/dwpicker/compatibility.py
Normal file
@ -0,0 +1,82 @@
|
||||
"""
|
||||
This module contain a function to ingest picker done with older version.
|
||||
If the structure changed, it can convert automatically the data to the new
|
||||
version.
|
||||
"""
|
||||
|
||||
from dwpicker.appinfos import VERSION
|
||||
|
||||
|
||||
def ensure_retro_compatibility(picker_data):
|
||||
"""
|
||||
This function ensure retro compatibility.
|
||||
"""
|
||||
# If a new release involve a data structure change in the picker, implement
|
||||
# the way to update the data here using this pattern:
|
||||
#
|
||||
# if version < (youre version number):
|
||||
# picker_data = your code update
|
||||
version = picker_data['general'].get('version') or (0, 0, 0)
|
||||
picker_data['general']['version'] = VERSION
|
||||
|
||||
if tuple(version) < (0, 3, 0):
|
||||
# Add new options added to version 0, 3, 0.
|
||||
picker_data['general']['zoom_locked'] = False
|
||||
|
||||
if tuple(version) < (0, 4, 0):
|
||||
picker_data['general'].pop('centerx')
|
||||
picker_data['general'].pop('centery')
|
||||
|
||||
if tuple(version) < (0, 10, 0):
|
||||
for shape in picker_data['shapes']:
|
||||
shape['visibility_layer'] = None
|
||||
|
||||
if tuple(version) < (0, 11, 0):
|
||||
for shape in picker_data['shapes']:
|
||||
update_shape_actions_for_v0_11_0(shape)
|
||||
|
||||
return picker_data
|
||||
|
||||
|
||||
def update_shape_actions_for_v0_11_0(shape):
|
||||
"""
|
||||
With release 0.11.0 comes a new configurable action system.
|
||||
"""
|
||||
if 'action.namespace' in shape:
|
||||
del shape['action.namespace']
|
||||
if 'action.type' in shape:
|
||||
del shape['action.type']
|
||||
|
||||
shape['action.commands'] = []
|
||||
|
||||
if shape['action.left.command']:
|
||||
shape['action.commands'].append({
|
||||
'enabled': shape['action.left'],
|
||||
'button': 'left',
|
||||
'language': shape['action.left.language'],
|
||||
'command': shape['action.left.command'],
|
||||
'alt': False,
|
||||
'ctrl': False,
|
||||
'shift': False,
|
||||
'deferred': False,
|
||||
'force_compact_undo': False})
|
||||
|
||||
if shape['action.right.command']:
|
||||
shape['action.commands'].append({
|
||||
'enabled': shape['action.right'],
|
||||
'button': 'left',
|
||||
'language': shape['action.right.language'],
|
||||
'command': shape['action.right.command'],
|
||||
'alt': False,
|
||||
'ctrl': False,
|
||||
'shift': False,
|
||||
'deferred': False,
|
||||
'force_compact_undo': False})
|
||||
|
||||
keys_to_clear = (
|
||||
'action.left', 'action.left.language',
|
||||
'action.left.command', 'action.right', 'action.right.language',
|
||||
'action.right.command')
|
||||
|
||||
for key in keys_to_clear:
|
||||
del shape[key]
|
577
Scripts/Animation/dwpicker/dwpicker/designer/attributes.py
Normal file
@ -0,0 +1,577 @@
|
||||
import maya.cmds as cmds
|
||||
from functools import partial
|
||||
from PySide2 import QtCore, QtWidgets
|
||||
|
||||
from dwpicker.qtutils import VALIGNS, HALIGNS
|
||||
from dwpicker.commands import CommandsEditor
|
||||
from dwpicker.designer.layer import VisibilityLayersEditor
|
||||
from dwpicker.widgets import (
|
||||
BoolCombo, BrowseEdit, ColorEdit, IntEdit, FloatEdit, LayerEdit,
|
||||
TextEdit, Title, WidgetToggler)
|
||||
|
||||
LEFT_CELL_WIDTH = 80
|
||||
SHAPE_TYPES = 'square', 'round', 'rounded_rect'
|
||||
|
||||
|
||||
class AttributeEditor(QtWidgets.QWidget):
|
||||
generalOptionSet = QtCore.Signal(str, object)
|
||||
imageModified = QtCore.Signal()
|
||||
optionSet = QtCore.Signal(str, object)
|
||||
rectModified = QtCore.Signal(str, float)
|
||||
removeLayer = QtCore.Signal(str)
|
||||
selectLayerContent = QtCore.Signal(str)
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(AttributeEditor, self).__init__(parent)
|
||||
self.widget = QtWidgets.QWidget()
|
||||
|
||||
self.generals = GeneralSettings()
|
||||
self.generals.optionModified.connect(self.generalOptionSet.emit)
|
||||
self.generals.layers.removeLayer.connect(self.removeLayer.emit)
|
||||
mtd = self.selectLayerContent.emit
|
||||
self.generals.layers.selectLayerContent.connect(mtd)
|
||||
self.generals_toggler = WidgetToggler('Picker options', self.generals)
|
||||
|
||||
self.shape = ShapeSettings()
|
||||
self.shape.optionSet.connect(self.optionSet.emit)
|
||||
self.shape.rectModified.connect(self.rectModified.emit)
|
||||
self.shape_toggler = WidgetToggler('Shape', self.shape)
|
||||
|
||||
self.image = ImageSettings()
|
||||
self.image.optionSet.connect(self.image_modified)
|
||||
self.image_toggler = WidgetToggler('Image', self.image)
|
||||
|
||||
self.appearence = AppearenceSettings()
|
||||
self.appearence.optionSet.connect(self.optionSet.emit)
|
||||
self.appearence_toggler = WidgetToggler('Appearence', self.appearence)
|
||||
|
||||
self.text = TextSettings()
|
||||
self.text.optionSet.connect(self.optionSet.emit)
|
||||
self.text_toggler = WidgetToggler('Text', self.text)
|
||||
|
||||
self.action = ActionSettings()
|
||||
self.action.optionSet.connect(self.optionSet.emit)
|
||||
self.action_toggler = WidgetToggler('Action', self.action)
|
||||
|
||||
self.layout = QtWidgets.QVBoxLayout(self.widget)
|
||||
self.layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.layout.setSpacing(0)
|
||||
self.layout.addWidget(self.generals_toggler)
|
||||
self.layout.addWidget(self.generals)
|
||||
self.layout.addWidget(self.shape_toggler)
|
||||
self.layout.addWidget(self.shape)
|
||||
self.layout.addWidget(self.image_toggler)
|
||||
self.layout.addWidget(self.image)
|
||||
self.layout.addWidget(self.appearence_toggler)
|
||||
self.layout.addWidget(self.appearence)
|
||||
self.layout.addWidget(self.text_toggler)
|
||||
self.layout.addWidget(self.text)
|
||||
self.layout.addWidget(self.action_toggler)
|
||||
self.layout.addWidget(self.action)
|
||||
self.layout.addStretch(1)
|
||||
|
||||
self.scroll_area = QtWidgets.QScrollArea()
|
||||
self.scroll_area.setWidget(self.widget)
|
||||
|
||||
self.main_layout = QtWidgets.QVBoxLayout(self)
|
||||
self.main_layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.main_layout.addWidget(self.scroll_area)
|
||||
|
||||
self.setFixedWidth(self.sizeHint().width() * 1.075)
|
||||
|
||||
def set_generals(self, options):
|
||||
self.blockSignals(True)
|
||||
self.generals.set_options(options)
|
||||
self.blockSignals(False)
|
||||
|
||||
def set_options(self, options):
|
||||
self.blockSignals(True)
|
||||
self.shape.set_options(options)
|
||||
self.image.set_options(options)
|
||||
self.appearence.set_options(options)
|
||||
self.text.set_options(options)
|
||||
self.action.set_options(options)
|
||||
self.blockSignals(False)
|
||||
|
||||
def image_modified(self, option, value):
|
||||
self.optionSet.emit(option, value)
|
||||
self.imageModified.emit()
|
||||
|
||||
|
||||
class GeneralSettings(QtWidgets.QWidget):
|
||||
optionModified = QtCore.Signal(str, object)
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(GeneralSettings, self).__init__(parent)
|
||||
self.name = TextEdit()
|
||||
self.name.valueSet.connect(self.name_changed)
|
||||
self.zoom_locked = BoolCombo(False)
|
||||
self.zoom_locked.valueSet.connect(self.zoom_changed)
|
||||
self.layers = VisibilityLayersEditor()
|
||||
|
||||
form_layout = QtWidgets.QFormLayout()
|
||||
form_layout.setSpacing(0)
|
||||
form_layout.setContentsMargins(0, 0, 0, 0)
|
||||
form_layout.setHorizontalSpacing(5)
|
||||
form_layout.addRow('Name', self.name)
|
||||
form_layout.addRow('Zoom-locked', self.zoom_locked)
|
||||
|
||||
layout = QtWidgets.QVBoxLayout(self)
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
layout.addLayout(form_layout)
|
||||
layout.addWidget(Title('Visibility Layers'))
|
||||
layout.addWidget(self.layers)
|
||||
|
||||
def set_shapes(self, shapes):
|
||||
self.layers.set_shapes(shapes)
|
||||
|
||||
def set_options(self, options):
|
||||
self.name.setText(options['name'])
|
||||
self.zoom_locked.setCurrentText(str(options['zoom_locked']))
|
||||
|
||||
def name_changed(self, value):
|
||||
self.optionModified.emit('name', value)
|
||||
|
||||
def zoom_changed(self, state):
|
||||
self.optionModified.emit('zoom_locked', state)
|
||||
|
||||
|
||||
class ShapeSettings(QtWidgets.QWidget):
|
||||
optionSet = QtCore.Signal(str, object)
|
||||
rectModified = QtCore.Signal(str, float)
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(ShapeSettings, self).__init__(parent)
|
||||
self.shape = QtWidgets.QComboBox()
|
||||
self.shape.addItems(SHAPE_TYPES)
|
||||
self.shape.currentIndexChanged.connect(self.shape_changed)
|
||||
|
||||
self.layer = LayerEdit()
|
||||
method = partial(self.optionSet.emit, 'visibility_layer')
|
||||
self.layer.valueSet.connect(method)
|
||||
|
||||
self.left = IntEdit(minimum=0)
|
||||
method = partial(self.rectModified.emit, 'shape.left')
|
||||
self.left.valueSet.connect(method)
|
||||
self.top = IntEdit(minimum=0)
|
||||
method = partial(self.rectModified.emit, 'shape.right')
|
||||
self.top.valueSet.connect(method)
|
||||
self.width = IntEdit(minimum=0)
|
||||
method = partial(self.rectModified.emit, 'shape.width')
|
||||
self.width.valueSet.connect(method)
|
||||
self.height = IntEdit(minimum=0)
|
||||
method = partial(self.rectModified.emit, 'shape.height')
|
||||
self.height.valueSet.connect(method)
|
||||
self.cornersx = IntEdit(minimum=0)
|
||||
method = partial(self.optionSet.emit, 'shape.cornersx')
|
||||
self.cornersx.valueSet.connect(method)
|
||||
self.cornersy = IntEdit(minimum=0)
|
||||
method = partial(self.optionSet.emit, 'shape.cornersy')
|
||||
self.cornersy.valueSet.connect(method)
|
||||
|
||||
self.layout = QtWidgets.QFormLayout(self)
|
||||
self.layout.setSpacing(0)
|
||||
self.layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.layout.setHorizontalSpacing(5)
|
||||
self.layout.addRow('Shape', self.shape)
|
||||
self.layout.addRow('Visibility layer', self.layer)
|
||||
self.layout.addItem(QtWidgets.QSpacerItem(0, 8))
|
||||
self.layout.addRow(Title('Dimensions'))
|
||||
self.layout.addRow('left', self.left)
|
||||
self.layout.addRow('top', self.top)
|
||||
self.layout.addRow('width', self.width)
|
||||
self.layout.addRow('height', self.height)
|
||||
self.layout.addRow('roundness x', self.cornersx)
|
||||
self.layout.addRow('roundness y', self.cornersy)
|
||||
for label in self.findChildren(QtWidgets.QLabel):
|
||||
if not isinstance(label, Title):
|
||||
label.setFixedWidth(LEFT_CELL_WIDTH)
|
||||
|
||||
def shape_changed(self, _):
|
||||
self.optionSet.emit('shape', self.shape.currentText())
|
||||
|
||||
def set_options(self, options):
|
||||
values = list({option['visibility_layer'] for option in options})
|
||||
value = values[0] if len(values) == 1 else '' if not values else '...'
|
||||
self.layer.set_layer(value)
|
||||
|
||||
values = list({option['shape'] for option in options})
|
||||
value = values[0] if len(values) == 1 else '...'
|
||||
self.shape.setCurrentText(value)
|
||||
|
||||
values = list({int(round((option['shape.left']))) for option in options})
|
||||
value = str(values[0]) if len(values) == 1 else None
|
||||
self.left.setText(value)
|
||||
|
||||
values = list({option['shape.top'] for option in options})
|
||||
value = str(values[0]) if len(values) == 1 else None
|
||||
self.top.setText(value)
|
||||
|
||||
values = list({option['shape.width'] for option in options})
|
||||
value = str(values[0]) if len(values) == 1 else None
|
||||
self.width.setText(value)
|
||||
|
||||
values = list({option['shape.height'] for option in options})
|
||||
value = str(values[0]) if len(values) == 1 else None
|
||||
self.height.setText(value)
|
||||
|
||||
values = list({option['shape.cornersx'] for option in options})
|
||||
value = str(values[0]) if len(values) == 1 else None
|
||||
self.cornersx.setText(value)
|
||||
|
||||
values = list({option['shape.cornersy'] for option in options})
|
||||
value = str(values[0]) if len(values) == 1 else None
|
||||
self.cornersy.setText(value)
|
||||
|
||||
|
||||
class ImageSettings(QtWidgets.QWidget):
|
||||
optionSet = QtCore.Signal(str, object)
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(ImageSettings, self).__init__(parent)
|
||||
self.path = BrowseEdit()
|
||||
self.path.valueSet.connect(partial(self.optionSet.emit, 'image.path'))
|
||||
|
||||
self.fit = BoolCombo(True)
|
||||
self.fit.valueSet.connect(partial(self.optionSet.emit, 'image.fit'))
|
||||
|
||||
self.width = FloatEdit()
|
||||
method = partial(self.optionSet.emit, 'image.width')
|
||||
self.width.valueSet.connect(method)
|
||||
|
||||
self.height = FloatEdit()
|
||||
method = partial(self.optionSet.emit, 'image.height')
|
||||
self.height.valueSet.connect(method)
|
||||
|
||||
self.layout = QtWidgets.QFormLayout(self)
|
||||
self.layout.setSpacing(0)
|
||||
self.layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.layout.setHorizontalSpacing(5)
|
||||
self.layout.addRow('Path', self.path)
|
||||
self.layout.addRow('Fit to shape', self.fit)
|
||||
self.layout.addRow('Width', self.width)
|
||||
self.layout.addRow('Height', self.height)
|
||||
for label in self.findChildren(QtWidgets.QLabel):
|
||||
if not isinstance(label, Title):
|
||||
label.setFixedWidth(LEFT_CELL_WIDTH)
|
||||
|
||||
def set_options(self, options):
|
||||
values = list({option['image.path'] for option in options})
|
||||
value = str(values[0]) if len(values) == 1 else None
|
||||
self.path.set_value(value)
|
||||
|
||||
values = list({option['image.fit'] for option in options})
|
||||
value = str(values[0]) if len(values) == 1 else None
|
||||
self.fit.setCurrentText(value)
|
||||
|
||||
values = list({option['image.width'] for option in options})
|
||||
value = str(values[0]) if len(values) == 1 else None
|
||||
self.width.setText(value)
|
||||
|
||||
values = list({option['image.height'] for option in options})
|
||||
value = str(values[0]) if len(values) == 1 else None
|
||||
self.height.setText(value)
|
||||
|
||||
|
||||
class AppearenceSettings(QtWidgets.QWidget):
|
||||
optionSet = QtCore.Signal(str, object)
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(AppearenceSettings, self).__init__(parent)
|
||||
|
||||
self.border = BoolCombo(True)
|
||||
method = partial(self.optionSet.emit, 'border')
|
||||
self.border.valueSet.connect(method)
|
||||
|
||||
self.borderwidth_normal = FloatEdit(minimum=0.0)
|
||||
method = partial(self.optionSet.emit, 'borderwidth.normal')
|
||||
self.borderwidth_normal.valueSet.connect(method)
|
||||
|
||||
self.borderwidth_hovered = FloatEdit(minimum=0.0)
|
||||
method = partial(self.optionSet.emit, 'borderwidth.hovered')
|
||||
self.borderwidth_hovered.valueSet.connect(method)
|
||||
|
||||
self.borderwidth_clicked = FloatEdit(minimum=0.0)
|
||||
method = partial(self.optionSet.emit, 'borderwidth.clicked')
|
||||
self.borderwidth_clicked.valueSet.connect(method)
|
||||
|
||||
self.bordercolor_normal = ColorEdit()
|
||||
method = partial(self.optionSet.emit, 'bordercolor.normal')
|
||||
self.bordercolor_normal.valueSet.connect(method)
|
||||
|
||||
self.bordercolor_hovered = ColorEdit()
|
||||
method = partial(self.optionSet.emit, 'bordercolor.hovered')
|
||||
self.bordercolor_hovered.valueSet.connect(method)
|
||||
|
||||
self.bordercolor_clicked = ColorEdit()
|
||||
method = partial(self.optionSet.emit, 'bordercolor.clicked')
|
||||
self.bordercolor_clicked.valueSet.connect(method)
|
||||
|
||||
self.bordercolor_transparency = FloatEdit(minimum=0, maximum=255)
|
||||
method = partial(self.optionSet.emit, 'bordercolor.transparency')
|
||||
self.bordercolor_transparency.valueSet.connect(method)
|
||||
|
||||
self.backgroundcolor_normal = ColorEdit()
|
||||
method = partial(self.optionSet.emit, 'bgcolor.normal')
|
||||
self.backgroundcolor_normal.valueSet.connect(method)
|
||||
|
||||
self.backgroundcolor_hovered = ColorEdit()
|
||||
method = partial(self.optionSet.emit, 'bgcolor.hovered')
|
||||
self.backgroundcolor_hovered.valueSet.connect(method)
|
||||
|
||||
self.backgroundcolor_clicked = ColorEdit()
|
||||
method = partial(self.optionSet.emit, 'bgcolor.clicked')
|
||||
self.backgroundcolor_clicked.valueSet.connect(method)
|
||||
|
||||
self.backgroundcolor_transparency = FloatEdit(minimum=0, maximum=255)
|
||||
method = partial(self.optionSet.emit, 'bgcolor.transparency')
|
||||
self.backgroundcolor_transparency.valueSet.connect(method)
|
||||
|
||||
self.layout = QtWidgets.QFormLayout(self)
|
||||
self.layout.setSpacing(0)
|
||||
self.layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.layout.setHorizontalSpacing(5)
|
||||
self.layout.addRow('border visible', self.border)
|
||||
self.layout.addRow(Title('Border width (pxf)'))
|
||||
self.layout.addRow('normal', self.borderwidth_normal)
|
||||
self.layout.addRow('hovered', self.borderwidth_hovered)
|
||||
self.layout.addRow('clicked', self.borderwidth_clicked)
|
||||
self.layout.addItem(QtWidgets.QSpacerItem(0, 8))
|
||||
self.layout.addRow(Title('Border color'))
|
||||
self.layout.addRow('normal', self.bordercolor_normal)
|
||||
self.layout.addRow('hovered', self.bordercolor_hovered)
|
||||
self.layout.addRow('clicked', self.bordercolor_clicked)
|
||||
self.layout.addRow('transparency', self.bordercolor_transparency)
|
||||
self.layout.addItem(QtWidgets.QSpacerItem(0, 8))
|
||||
self.layout.addRow(Title('Background color'))
|
||||
self.layout.addRow('normal', self.backgroundcolor_normal)
|
||||
self.layout.addRow('hovered', self.backgroundcolor_hovered)
|
||||
self.layout.addRow('clicked', self.backgroundcolor_clicked)
|
||||
self.layout.addRow('transparency', self.backgroundcolor_transparency)
|
||||
for label in self.findChildren(QtWidgets.QLabel):
|
||||
if not isinstance(label, Title):
|
||||
label.setFixedWidth(LEFT_CELL_WIDTH)
|
||||
|
||||
def set_options(self, options):
|
||||
values = list({option['border'] for option in options})
|
||||
value = str(values[0]) if len(values) == 1 else None
|
||||
self.border.setCurrentText(value)
|
||||
|
||||
values = list({option['borderwidth.normal'] for option in options})
|
||||
value = str(values[0]) if len(values) == 1 else None
|
||||
self.borderwidth_normal.setText(value)
|
||||
|
||||
values = list({option['borderwidth.hovered'] for option in options})
|
||||
value = str(values[0]) if len(values) == 1 else None
|
||||
self.borderwidth_hovered.setText(value)
|
||||
|
||||
values = list({option['borderwidth.clicked'] for option in options})
|
||||
value = str(values[0]) if len(values) == 1 else None
|
||||
self.borderwidth_clicked.setText(value)
|
||||
|
||||
values = list({option['bordercolor.normal'] for option in options})
|
||||
value = str(values[0]) if len(values) == 1 else None
|
||||
self.bordercolor_normal.set_color(value)
|
||||
|
||||
values = list({option['bordercolor.hovered'] for option in options})
|
||||
value = str(values[0]) if len(values) == 1 else None
|
||||
self.bordercolor_hovered.set_color(value)
|
||||
|
||||
values = list({option['bordercolor.clicked'] for option in options})
|
||||
value = str(values[0]) if len(values) == 1 else None
|
||||
self.bordercolor_clicked.set_color(value)
|
||||
|
||||
values = list({option['bordercolor.transparency'] for option in options})
|
||||
value = str(values[0]) if len(values) == 1 else None
|
||||
self.bordercolor_transparency.setText(value)
|
||||
|
||||
values = list({option['bgcolor.normal'] for option in options})
|
||||
value = str(values[0]) if len(values) == 1 else None
|
||||
self.backgroundcolor_normal.set_color(value)
|
||||
|
||||
values = list({option['bgcolor.hovered'] for option in options})
|
||||
value = str(values[0]) if len(values) == 1 else None
|
||||
self.backgroundcolor_hovered.set_color(value)
|
||||
|
||||
values = list({option['bgcolor.clicked'] for option in options})
|
||||
value = str(values[0]) if len(values) == 1 else None
|
||||
self.backgroundcolor_clicked.set_color(value)
|
||||
|
||||
values = list({option['bgcolor.transparency'] for option in options})
|
||||
value = str(values[0]) if len(values) == 1 else None
|
||||
self.backgroundcolor_transparency.setText(value)
|
||||
|
||||
|
||||
class ActionSettings(QtWidgets.QWidget):
|
||||
optionSet = QtCore.Signal(str, object)
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(ActionSettings, self).__init__(parent)
|
||||
self._targets = QtWidgets.QLineEdit()
|
||||
self._targets.returnPressed.connect(self.targets_changed)
|
||||
|
||||
self._add_targets = QtWidgets.QPushButton('Add')
|
||||
self._remove_targets = QtWidgets.QPushButton('Remove')
|
||||
self._replace_targets = QtWidgets.QPushButton('Replace')
|
||||
self._targets_layout = QtWidgets.QHBoxLayout()
|
||||
self._targets_layout.addWidget(self._add_targets)
|
||||
self._targets_layout.addWidget(self._remove_targets)
|
||||
self._targets_layout.addWidget(self._replace_targets)
|
||||
|
||||
self._add_targets.clicked.connect(self.call_add_targets)
|
||||
self._remove_targets.clicked.connect(self.call_remove_targets)
|
||||
self._replace_targets.clicked.connect(self.call_replace_targets)
|
||||
|
||||
self._commands = CommandsEditor()
|
||||
method = partial(self.optionSet.emit, 'action.commands')
|
||||
self._commands.valueSet.connect(method)
|
||||
|
||||
form = QtWidgets.QFormLayout()
|
||||
form.setSpacing(0)
|
||||
form.setContentsMargins(0, 0, 0, 0)
|
||||
form.setHorizontalSpacing(5)
|
||||
form.addRow(Title('Selection'))
|
||||
form.addRow('Targets', self._targets)
|
||||
form.addRow('Add Selected', self._targets_layout)
|
||||
self.layout = QtWidgets.QVBoxLayout(self)
|
||||
self.layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.layout.setSpacing(0)
|
||||
self.layout.addLayout(form)
|
||||
self.layout.addWidget(Title('Scripts'))
|
||||
self.layout.addWidget(self._commands)
|
||||
for label in self.findChildren(QtWidgets.QLabel):
|
||||
if not isinstance(label, Title):
|
||||
label.setFixedWidth(LEFT_CELL_WIDTH)
|
||||
|
||||
def targets(self):
|
||||
targets = str(self._targets.text())
|
||||
try:
|
||||
return [t.strip(" ") for t in targets.split(',')]
|
||||
except ValueError:
|
||||
return []
|
||||
|
||||
def call_add_targets(self):
|
||||
selection = cmds.ls(selection=True, flatten=True)
|
||||
if not selection:
|
||||
return
|
||||
targets = self.targets()
|
||||
edits = [item for item in selection if item not in targets]
|
||||
targets = targets if targets != [''] else []
|
||||
self._targets.setText(', '.join(targets + edits))
|
||||
self._targets.setFocus()
|
||||
self.targets_changed()
|
||||
|
||||
def call_remove_targets(self):
|
||||
selection = cmds.ls(selection=True, flatten=True)
|
||||
if not selection:
|
||||
return
|
||||
|
||||
targets = [item for item in self.targets() if item not in selection]
|
||||
self._targets.setText(', '.join(targets))
|
||||
self._targets.setFocus()
|
||||
self.targets_changed()
|
||||
|
||||
def call_replace_targets(self):
|
||||
selection = cmds.ls(selection=True, flatten=True)
|
||||
if not selection:
|
||||
return
|
||||
|
||||
self._targets.setText(', '.join(selection))
|
||||
self._targets.setFocus()
|
||||
self.targets_changed()
|
||||
|
||||
def targets_changed(self):
|
||||
if not self._targets.text():
|
||||
self.optionSet.emit('action.targets', [])
|
||||
return
|
||||
values = [t.strip(" ") for t in self._targets.text().split(",")]
|
||||
self.optionSet.emit('action.targets', values)
|
||||
|
||||
def set_options(self, options):
|
||||
values = list({o for opt in options for o in opt['action.targets']})
|
||||
self._targets.setText(", ".join(sorted(values)))
|
||||
self._commands.set_options(options)
|
||||
|
||||
|
||||
class TextSettings(QtWidgets.QWidget):
|
||||
optionSet = QtCore.Signal(str, object)
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(TextSettings, self).__init__(parent)
|
||||
self.text = TextEdit()
|
||||
method = partial(self.optionSet.emit, 'text.content')
|
||||
self.text.valueSet.connect(method)
|
||||
|
||||
self.size = FloatEdit(minimum=0.0)
|
||||
self.size.valueSet.connect(partial(self.optionSet.emit, 'text.size'))
|
||||
|
||||
self.bold = BoolCombo()
|
||||
self.bold.valueSet.connect(partial(self.optionSet.emit, 'text.bold'))
|
||||
|
||||
self.italic = BoolCombo()
|
||||
method = partial(self.optionSet.emit, 'text.italic')
|
||||
self.italic.valueSet.connect(method)
|
||||
|
||||
self.color = ColorEdit()
|
||||
self.color.valueSet.connect(partial(self.optionSet.emit, 'text.color'))
|
||||
|
||||
self.halignement = QtWidgets.QComboBox()
|
||||
self.halignement.addItems(HALIGNS.keys())
|
||||
self.halignement.currentIndexChanged.connect(self.halign_changed)
|
||||
self.valignement = QtWidgets.QComboBox()
|
||||
self.valignement.addItems(VALIGNS.keys())
|
||||
self.valignement.currentIndexChanged.connect(self.valign_changed)
|
||||
|
||||
self.layout = QtWidgets.QFormLayout(self)
|
||||
self.layout.setSpacing(0)
|
||||
self.layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.layout.setHorizontalSpacing(5)
|
||||
self.layout.addRow('Content', self.text)
|
||||
self.layout.addItem(QtWidgets.QSpacerItem(0, 8))
|
||||
self.layout.addRow(Title('Options'))
|
||||
self.layout.addRow('Size', self.size)
|
||||
self.layout.addRow('Bold', self.bold)
|
||||
self.layout.addRow('Italic', self.italic)
|
||||
self.layout.addRow('Color', self.color)
|
||||
self.layout.addItem(QtWidgets.QSpacerItem(0, 8))
|
||||
self.layout.addRow(Title('Alignement'))
|
||||
self.layout.addRow('Horizontal', self.halignement)
|
||||
self.layout.addRow('Vertical', self.valignement)
|
||||
for label in self.findChildren(QtWidgets.QLabel):
|
||||
if not isinstance(label, Title):
|
||||
label.setFixedWidth(LEFT_CELL_WIDTH)
|
||||
|
||||
def valign_changed(self):
|
||||
self.optionSet.emit('text.valign', self.valignement.currentText())
|
||||
|
||||
def halign_changed(self):
|
||||
self.optionSet.emit('text.halign', self.halignement.currentText())
|
||||
|
||||
def set_options(self, options):
|
||||
values = list({option['text.content'] for option in options})
|
||||
value = str(values[0]) if len(values) == 1 else None
|
||||
self.text.setText(value)
|
||||
|
||||
values = list({option['text.size'] for option in options})
|
||||
value = str(values[0]) if len(values) == 1 else None
|
||||
self.size.setText(value)
|
||||
|
||||
values = list({option['text.bold'] for option in options})
|
||||
value = str(values[0]) if len(values) == 1 else None
|
||||
self.bold.setCurrentText(value)
|
||||
|
||||
values = list({option['text.italic'] for option in options})
|
||||
value = str(values[0]) if len(values) == 1 else None
|
||||
self.italic.setCurrentText(value)
|
||||
|
||||
values = list({option['text.color'] for option in options})
|
||||
value = str(values[0]) if len(values) == 1 else None
|
||||
self.color.set_color(value)
|
||||
|
||||
values = list({option['text.halign'] for option in options})
|
||||
value = str(values[0]) if len(values) == 1 else None
|
||||
self.halignement.setCurrentText(value)
|
||||
|
||||
values = list({option['text.valign'] for option in options})
|
||||
value = str(values[0]) if len(values) == 1 else None
|
||||
self.valignement.setCurrentText(value)
|
191
Scripts/Animation/dwpicker/dwpicker/designer/editarea.py
Normal file
@ -0,0 +1,191 @@
|
||||
from PySide2 import QtCore, QtGui, QtWidgets
|
||||
|
||||
from dwpicker.interactive import Manipulator, SelectionSquare
|
||||
from dwpicker.geometry import Transform, get_combined_rects
|
||||
from dwpicker.painting import draw_editor, draw_shape
|
||||
from dwpicker.qtutils import get_cursor
|
||||
from dwpicker.selection import Selection, get_selection_mode
|
||||
|
||||
|
||||
class ShapeEditArea(QtWidgets.QWidget):
|
||||
selectedShapesChanged = QtCore.Signal()
|
||||
increaseUndoStackRequested = QtCore.Signal()
|
||||
centerMoved = QtCore.Signal(int, int)
|
||||
callContextMenu = QtCore.Signal(QtCore.QPoint)
|
||||
|
||||
def __init__(self, options, parent=None):
|
||||
super(ShapeEditArea, self).__init__(parent)
|
||||
self.setFixedSize(750, 550)
|
||||
self.setMouseTracking(True)
|
||||
self.options = options
|
||||
|
||||
self.selection = Selection()
|
||||
self.selection_square = SelectionSquare()
|
||||
self.manipulator = Manipulator()
|
||||
self.transform = Transform()
|
||||
|
||||
self.shapes = []
|
||||
self.clicked_shape = None
|
||||
self.clicked = False
|
||||
self.selecting = False
|
||||
self.handeling = False
|
||||
self.manipulator_moved = False
|
||||
self.increase_undo_on_release = False
|
||||
self.lock_background_shape = True
|
||||
|
||||
self.ctrl_pressed = False
|
||||
self.shit_pressed = False
|
||||
|
||||
def set_lock_background_shape(self, state):
|
||||
self.lock_background_shape = state
|
||||
|
||||
def get_hovered_shape(self, cursor):
|
||||
for shape in reversed(self.list_shapes()):
|
||||
if shape.rect.contains(cursor):
|
||||
return shape
|
||||
|
||||
def list_shapes(self):
|
||||
if self.lock_background_shape:
|
||||
return [
|
||||
shape for shape in self.shapes
|
||||
if not shape.is_background()]
|
||||
return self.shapes
|
||||
|
||||
def mousePressEvent(self, event):
|
||||
self.setFocus(QtCore.Qt.MouseFocusReason) # This is not automatic
|
||||
if event.button() != QtCore.Qt.LeftButton:
|
||||
return
|
||||
|
||||
cursor = get_cursor(self)
|
||||
self.clicked = True
|
||||
hovered_shape = self.get_hovered_shape(cursor)
|
||||
self.transform.direction = self.manipulator.get_direction(cursor)
|
||||
|
||||
conditions = (
|
||||
hovered_shape and
|
||||
hovered_shape not in self.selection and
|
||||
not self.transform.direction)
|
||||
|
||||
if conditions:
|
||||
self.selection.set([hovered_shape])
|
||||
self.update_selection()
|
||||
|
||||
elif not hovered_shape and not self.transform.direction:
|
||||
self.selection.set([])
|
||||
self.update_selection()
|
||||
self.selection_square.clicked(cursor)
|
||||
|
||||
if self.manipulator.rect is not None:
|
||||
self.transform.set_rect(self.manipulator.rect)
|
||||
self.transform.reference_rect = QtCore.QRect(self.manipulator.rect)
|
||||
self.transform.set_reference_point(cursor)
|
||||
|
||||
self.handeling = bool(hovered_shape) or self.transform.direction
|
||||
self.selecting = not self.handeling and self.clicked
|
||||
self.repaint()
|
||||
|
||||
def mouseMoveEvent(self, _):
|
||||
cursor = get_cursor(self)
|
||||
if self.handeling:
|
||||
rect = self.manipulator.rect
|
||||
if self.transform.direction:
|
||||
self.transform.resize((s.rect for s in self.selection), cursor)
|
||||
self.manipulator.update_geometries()
|
||||
elif rect is not None:
|
||||
self.transform.move((s.rect for s in self.selection), cursor)
|
||||
self.manipulator.update_geometries()
|
||||
for shape in self.selection:
|
||||
shape.synchronize_rect()
|
||||
shape.synchronize_image()
|
||||
|
||||
self.manipulator_moved = True
|
||||
self.increase_undo_on_release = True
|
||||
self.selectedShapesChanged.emit()
|
||||
|
||||
elif self.selecting:
|
||||
self.selection_square.handle(cursor)
|
||||
for shape in self.list_shapes():
|
||||
shape.hovered = self.selection_square.intersects(shape.rect)
|
||||
|
||||
else:
|
||||
for shape in self.list_shapes():
|
||||
shape.hovered = shape.rect.contains(cursor)
|
||||
|
||||
self.repaint()
|
||||
|
||||
def mouseReleaseEvent(self, event):
|
||||
context_menu_condition = (
|
||||
event.button() == QtCore.Qt.RightButton and
|
||||
not self.clicked and
|
||||
not self.handeling and
|
||||
not self.selecting)
|
||||
if context_menu_condition:
|
||||
return self.callContextMenu.emit(event.pos())
|
||||
|
||||
if event.button() != QtCore.Qt.LeftButton:
|
||||
return
|
||||
|
||||
if self.increase_undo_on_release:
|
||||
self.increaseUndoStackRequested.emit()
|
||||
self.increase_undo_on_release = False
|
||||
|
||||
if self.selecting:
|
||||
self.select_shapes()
|
||||
|
||||
self.selection_square.release()
|
||||
self.clicked = False
|
||||
self.handeling = False
|
||||
self.selecting = False
|
||||
self.repaint()
|
||||
|
||||
def select_shapes(self):
|
||||
shapes = [
|
||||
s for s in self.list_shapes()
|
||||
if s.rect.intersects(self.selection_square.rect)]
|
||||
if shapes:
|
||||
self.selection.set(shapes)
|
||||
self.update_selection()
|
||||
|
||||
def keyPressEvent(self, event):
|
||||
self.key_event(event, True)
|
||||
|
||||
def keyReleaseEvent(self, event):
|
||||
self.key_event(event, False)
|
||||
|
||||
def key_event(self, event, pressed):
|
||||
if event.key() == QtCore.Qt.Key_Shift:
|
||||
self.transform.square = pressed
|
||||
self.shit_pressed = pressed
|
||||
|
||||
if event.key() == QtCore.Qt.Key_Control:
|
||||
self.ctrl_pressed = pressed
|
||||
|
||||
self.selection.mode = get_selection_mode(
|
||||
shift=self.shit_pressed,
|
||||
ctrl=self.ctrl_pressed)
|
||||
|
||||
self.repaint()
|
||||
|
||||
def update_selection(self):
|
||||
rect = get_combined_rects([shape.rect for shape in self.selection])
|
||||
self.manipulator.set_rect(rect)
|
||||
self.selectedShapesChanged.emit()
|
||||
|
||||
def paintEvent(self, _):
|
||||
try:
|
||||
painter = QtGui.QPainter()
|
||||
painter.begin(self)
|
||||
self.paint(painter)
|
||||
except BaseException:
|
||||
pass # avoid crash
|
||||
# TODO: log the error
|
||||
finally:
|
||||
painter.end()
|
||||
|
||||
def paint(self, painter):
|
||||
painter.setRenderHint(QtGui.QPainter.Antialiasing)
|
||||
draw_editor(painter, self.rect(), snap=self.transform.snap)
|
||||
for shape in self.shapes:
|
||||
draw_shape(painter, shape)
|
||||
self.manipulator.draw(painter, get_cursor(self))
|
||||
self.selection_square.draw(painter)
|
557
Scripts/Animation/dwpicker/dwpicker/designer/editor.py
Normal file
@ -0,0 +1,557 @@
|
||||
from functools import partial
|
||||
from math import ceil
|
||||
|
||||
from PySide2 import QtWidgets, QtCore
|
||||
from maya import cmds
|
||||
|
||||
from dwpicker import clipboard
|
||||
from dwpicker.align import align_shapes, arrange_horizontal, arrange_vertical
|
||||
from dwpicker.arrayutils import (
|
||||
move_elements_to_array_end, move_elements_to_array_begin,
|
||||
move_up_array_elements, move_down_array_elements)
|
||||
from dwpicker.dialog import SearchAndReplaceDialog, warning, SettingsPaster
|
||||
from dwpicker.interactive import Shape, get_shape_rect_from_options
|
||||
from dwpicker.geometry import get_combined_rects, rect_symmetry
|
||||
from dwpicker.optionvar import BG_LOCKED, TRIGGER_REPLACE_ON_MIRROR
|
||||
from dwpicker.picker import frame_shapes
|
||||
from dwpicker.qtutils import set_shortcut, get_cursor
|
||||
from dwpicker.templates import BUTTON, TEXT, BACKGROUND
|
||||
|
||||
from dwpicker.designer.editarea import ShapeEditArea
|
||||
from dwpicker.designer.menu import MenuWidget
|
||||
from dwpicker.designer.attributes import AttributeEditor
|
||||
|
||||
|
||||
DIRECTION_OFFSETS = {
|
||||
'Left': (-1, 0), 'Right': (1, 0), 'Up': (0, -1), 'Down': (0, 1)}
|
||||
|
||||
|
||||
class PickerEditor(QtWidgets.QWidget):
|
||||
pickerDataModified = QtCore.Signal(object)
|
||||
|
||||
def __init__(self, picker_data, undo_manager, parent=None):
|
||||
super(PickerEditor, self).__init__(parent, QtCore.Qt.Window)
|
||||
title = "Picker editor - " + picker_data['general']['name']
|
||||
self.setWindowTitle(title)
|
||||
self.options = picker_data['general']
|
||||
self.undo_manager = undo_manager
|
||||
|
||||
self.shape_editor = ShapeEditArea(self.options)
|
||||
self.shape_editor.callContextMenu.connect(self.call_context_menu)
|
||||
bg_locked = bool(cmds.optionVar(query=BG_LOCKED))
|
||||
self.shape_editor.set_lock_background_shape(bg_locked)
|
||||
self.set_picker_data(picker_data)
|
||||
self.shape_editor.selectedShapesChanged.connect(self.selection_changed)
|
||||
method = self.set_data_modified
|
||||
self.shape_editor.increaseUndoStackRequested.connect(method)
|
||||
self.scrollarea = QtWidgets.QScrollArea()
|
||||
alignment = QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter
|
||||
self.scrollarea.setFocusPolicy(QtCore.Qt.NoFocus)
|
||||
policy = QtWidgets.QSizePolicy.Expanding
|
||||
self.scrollarea.setSizePolicy(policy, policy)
|
||||
# HACK: Stupid hack to force scroll area to fix layout size.
|
||||
self.scrollarea.sizeHint = lambda: QtCore.QSize(10000, 10000)
|
||||
self.scrollarea.setAlignment(alignment)
|
||||
self.scrollarea.setWidget(self.shape_editor)
|
||||
|
||||
self.menu = MenuWidget()
|
||||
self.menu.copyRequested.connect(self.copy)
|
||||
self.menu.copySettingsRequested.connect(self.copy_settings)
|
||||
self.menu.deleteRequested.connect(self.delete_selection)
|
||||
self.menu.frameShapes.connect(self.frame_shapes)
|
||||
self.menu.pasteRequested.connect(self.paste)
|
||||
self.menu.pasteSettingsRequested.connect(self.paste_settings)
|
||||
self.menu.sizeChanged.connect(self.editor_size_changed)
|
||||
self.menu.snapValuesChanged.connect(self.snap_value_changed)
|
||||
self.menu.useSnapToggled.connect(self.use_snap)
|
||||
|
||||
method = self.shape_editor.set_lock_background_shape
|
||||
self.menu.lockBackgroundShapeToggled.connect(method)
|
||||
width, height = self.options['width'], self.options['height']
|
||||
self.menu.set_size_values(width, height)
|
||||
self.menu.undoRequested.connect(self.undo)
|
||||
self.menu.redoRequested.connect(self.redo)
|
||||
method = partial(self.create_shape, BUTTON)
|
||||
self.menu.addButtonRequested.connect(method)
|
||||
method = partial(self.create_shape, TEXT)
|
||||
self.menu.addTextRequested.connect(method)
|
||||
method = partial(self.create_shape, BACKGROUND, before=True)
|
||||
self.menu.addBackgroundRequested.connect(method)
|
||||
method = self.set_selection_move_down
|
||||
self.menu.moveDownRequested.connect(method)
|
||||
method = self.set_selection_move_up
|
||||
self.menu.moveUpRequested.connect(method)
|
||||
method = self.set_selection_on_top
|
||||
self.menu.onTopRequested.connect(method)
|
||||
method = self.set_selection_on_bottom
|
||||
self.menu.onBottomRequested.connect(method)
|
||||
self.menu.symmetryRequested.connect(self.do_symmetry)
|
||||
self.menu.searchAndReplaceRequested.connect(self.search_and_replace)
|
||||
self.menu.alignRequested.connect(self.align_selection)
|
||||
self.menu.arrangeRequested.connect(self.arrange_selection)
|
||||
self.menu.load_ui_states()
|
||||
|
||||
set_shortcut("Ctrl+Z", self.shape_editor, self.undo)
|
||||
set_shortcut("Ctrl+Y", self.shape_editor, self.redo)
|
||||
set_shortcut("Ctrl+C", self.shape_editor, self.copy)
|
||||
set_shortcut("Ctrl+V", self.shape_editor, self.paste)
|
||||
set_shortcut("Ctrl+R", self.shape_editor, self.search_and_replace)
|
||||
set_shortcut("del", self.shape_editor, self.delete_selection)
|
||||
set_shortcut("Ctrl+D", self.shape_editor, self.deselect_all)
|
||||
set_shortcut("Ctrl+A", self.shape_editor, self.select_all)
|
||||
set_shortcut("Ctrl+I", self.shape_editor, self.invert_selection)
|
||||
for direction in ['Left', 'Right', 'Up', 'Down']:
|
||||
method = partial(self.move_selection, direction)
|
||||
shortcut = set_shortcut(direction, self.shape_editor, method)
|
||||
shortcut.setAutoRepeat(True)
|
||||
|
||||
self.attribute_editor = AttributeEditor()
|
||||
self.attribute_editor.set_generals(self.options)
|
||||
self.attribute_editor.generals.set_shapes(self.shape_editor.shapes)
|
||||
self.attribute_editor.generalOptionSet.connect(self.generals_modified)
|
||||
self.attribute_editor.optionSet.connect(self.option_set)
|
||||
self.attribute_editor.rectModified.connect(self.rect_modified)
|
||||
self.attribute_editor.imageModified.connect(self.image_modified)
|
||||
self.attribute_editor.removeLayer.connect(self.remove_layer)
|
||||
self.attribute_editor.selectLayerContent.connect(self.select_layer)
|
||||
|
||||
self.hlayout = QtWidgets.QHBoxLayout()
|
||||
self.hlayout.setSizeConstraint(QtWidgets.QLayout.SetMaximumSize)
|
||||
self.hlayout.setContentsMargins(0, 0, 0, 0)
|
||||
self.hlayout.addStretch(1)
|
||||
self.hlayout.addWidget(self.scrollarea)
|
||||
self.hlayout.addStretch(1)
|
||||
self.hlayout.addWidget(self.attribute_editor)
|
||||
|
||||
self.vlayout = QtWidgets.QVBoxLayout(self)
|
||||
self.vlayout.setContentsMargins(0, 0, 0, 0)
|
||||
self.vlayout.setSpacing(0)
|
||||
self.vlayout.addWidget(self.menu)
|
||||
self.vlayout.addLayout(self.hlayout)
|
||||
|
||||
def copy(self):
|
||||
clipboard.set([
|
||||
s.options.copy() for s in self.shape_editor.selection])
|
||||
|
||||
def copy_settings(self):
|
||||
if len(self.shape_editor.selection) != 1:
|
||||
return warning('Copy settings', 'Please select only one shape')
|
||||
shape = self.shape_editor.selection[0]
|
||||
clipboard.set_settings(shape.options.copy())
|
||||
|
||||
def sizeHint(self):
|
||||
return QtCore.QSize(1300, 750)
|
||||
|
||||
def paste(self):
|
||||
clipboad_copy = [s.copy() for s in clipboard.get()]
|
||||
shape_datas = self.picker_data()['shapes'][:] + clipboad_copy
|
||||
picker_data = {
|
||||
'general': self.options,
|
||||
'shapes': shape_datas}
|
||||
self.set_picker_data(picker_data)
|
||||
self.undo_manager.set_data_modified(picker_data)
|
||||
self.pickerDataModified.emit(picker_data)
|
||||
# select new shapes
|
||||
shapes = self.shape_editor.shapes[-len(clipboard.get()):]
|
||||
self.shape_editor.selection.replace(shapes)
|
||||
self.shape_editor.update_selection()
|
||||
self.shape_editor.repaint()
|
||||
|
||||
def paste_settings(self):
|
||||
dialog = SettingsPaster()
|
||||
if not dialog.exec_():
|
||||
return
|
||||
settings = clipboard.get_settings()
|
||||
settings = {k: v for k, v in settings.items() if k in dialog.settings}
|
||||
for shape in self.shape_editor.selection:
|
||||
shape.options.update(settings)
|
||||
shape.rect = get_shape_rect_from_options(shape.options)
|
||||
shape.synchronize_image()
|
||||
self.set_data_modified()
|
||||
self.selection_changed()
|
||||
self.shape_editor.update_selection()
|
||||
self.shape_editor.repaint()
|
||||
|
||||
def undo(self):
|
||||
result = self.undo_manager.undo()
|
||||
if result is False:
|
||||
return
|
||||
self.update_undo_manager()
|
||||
|
||||
def redo(self):
|
||||
self.undo_manager.redo()
|
||||
self.update_undo_manager()
|
||||
|
||||
def update_undo_manager(self):
|
||||
data = self.undo_manager.data
|
||||
self.set_picker_data(data)
|
||||
self.pickerDataModified.emit(self.picker_data())
|
||||
self.attribute_editor.generals.set_shapes(self.shape_editor.shapes)
|
||||
|
||||
def deselect_all(self):
|
||||
self.shape_editor.selection.clear()
|
||||
self.shape_editor.update_selection()
|
||||
self.shape_editor.repaint()
|
||||
|
||||
def select_all(self):
|
||||
shapes = self.shape_editor.list_shapes()
|
||||
self.shape_editor.selection.add(shapes)
|
||||
self.shape_editor.update_selection()
|
||||
self.shape_editor.repaint()
|
||||
|
||||
def invert_selection(self):
|
||||
self.shape_editor.selection.invert(self.shape_editor.shapes)
|
||||
if self.menu.lock_bg.isChecked():
|
||||
shapes = [
|
||||
s for s in self.shape_editor.selection
|
||||
if not s.is_background()]
|
||||
self.shape_editor.selection.set(shapes)
|
||||
self.shape_editor.update_selection()
|
||||
self.shape_editor.repaint()
|
||||
|
||||
def set_data_modified(self):
|
||||
self.undo_manager.set_data_modified(self.picker_data())
|
||||
self.pickerDataModified.emit(self.picker_data())
|
||||
|
||||
def use_snap(self, state):
|
||||
snap = self.menu.snap_values() if state else None
|
||||
self.shape_editor.transform.snap = snap
|
||||
self.shape_editor.repaint()
|
||||
|
||||
def snap_value_changed(self):
|
||||
self.shape_editor.transform.snap = self.menu.snap_values()
|
||||
self.set_data_modified()
|
||||
self.shape_editor.repaint()
|
||||
|
||||
def generals_modified(self, key, value):
|
||||
self.options[key] = value
|
||||
if key == 'name':
|
||||
title = "Picker editor - " + self.options['name']
|
||||
self.setWindowTitle(title)
|
||||
self.pickerDataModified.emit(self.picker_data())
|
||||
|
||||
def option_set(self, option, value):
|
||||
for shape in self.shape_editor.selection:
|
||||
shape.options[option] = value
|
||||
self.shape_editor.repaint()
|
||||
self.set_data_modified()
|
||||
if option == 'visibility_layer':
|
||||
self.attribute_editor.generals.set_shapes(self.shape_editor.shapes)
|
||||
|
||||
def editor_size_changed(self):
|
||||
size = self.menu.get_size()
|
||||
self.shape_editor.setFixedSize(size)
|
||||
self.options['width'] = size.width()
|
||||
self.options['height'] = size.height()
|
||||
self.set_data_modified()
|
||||
|
||||
def rect_modified(self, option, value):
|
||||
shapes = self.shape_editor.selection
|
||||
for shape in shapes:
|
||||
shape.options[option] = value
|
||||
if option == 'shape.height':
|
||||
shape.rect.setHeight(value)
|
||||
shape.synchronize_image()
|
||||
continue
|
||||
|
||||
elif option == 'shape.width':
|
||||
shape.rect.setWidth(value)
|
||||
shape.synchronize_image()
|
||||
continue
|
||||
|
||||
width = shape.rect.width()
|
||||
height = shape.rect.height()
|
||||
if option == 'shape.left':
|
||||
shape.rect.setLeft(value)
|
||||
else:
|
||||
shape.rect.setTop(value)
|
||||
shape.rect.setWidth(width)
|
||||
shape.rect.setHeight(height)
|
||||
shape.synchronize_image()
|
||||
|
||||
self.update_manipulator_rect()
|
||||
self.set_data_modified()
|
||||
|
||||
def selection_changed(self):
|
||||
shapes = self.shape_editor.selection
|
||||
options = [shape.options for shape in shapes]
|
||||
self.attribute_editor.set_options(options)
|
||||
|
||||
def frame_shapes(self):
|
||||
shapes = self.shape_editor.shapes
|
||||
width = self.options['width']
|
||||
height = self.options['height']
|
||||
frame_shapes(shapes)
|
||||
width = int(ceil(max(s.rect.right() for s in shapes)))
|
||||
height = int(ceil(max(s.rect.bottom() for s in shapes)))
|
||||
self.shape_editor.repaint()
|
||||
self.update_manipulator_rect()
|
||||
# This mark data as changed, no need to repeat.
|
||||
self.menu.set_size_values(width, height)
|
||||
|
||||
def create_shape(
|
||||
self, template, before=False, position=None, targets=None):
|
||||
options = template.copy()
|
||||
shape = Shape(options)
|
||||
if not position:
|
||||
shape.rect.moveCenter(self.shape_editor.rect().center())
|
||||
else:
|
||||
shape.rect.moveTopLeft(position)
|
||||
if targets:
|
||||
shape.set_targets(targets)
|
||||
shape.synchronize_rect()
|
||||
if before is True:
|
||||
self.shape_editor.shapes.insert(0, shape)
|
||||
else:
|
||||
self.shape_editor.shapes.append(shape)
|
||||
self.shape_editor.repaint()
|
||||
self.set_data_modified()
|
||||
|
||||
def update_targets(self, shape):
|
||||
# shape = self.shape_editor.selection[0]
|
||||
shape.set_targets(cmds.ls(selection=True))
|
||||
self.shape_editor.repaint()
|
||||
self.set_data_modified()
|
||||
|
||||
def image_modified(self):
|
||||
for shape in self.shape_editor.selection:
|
||||
shape.synchronize_image()
|
||||
self.shape_editor.repaint()
|
||||
|
||||
def set_selection_move_down(self):
|
||||
array = self.shape_editor.shapes
|
||||
elements = self.shape_editor.selection
|
||||
move_down_array_elements(array, elements)
|
||||
self.shape_editor.repaint()
|
||||
self.set_data_modified()
|
||||
|
||||
def set_selection_move_up(self):
|
||||
array = self.shape_editor.shapes
|
||||
elements = self.shape_editor.selection
|
||||
move_up_array_elements(array, elements)
|
||||
self.shape_editor.repaint()
|
||||
self.set_data_modified()
|
||||
|
||||
def set_selection_on_top(self):
|
||||
array = self.shape_editor.shapes
|
||||
elements = self.shape_editor.selection
|
||||
self.shape_editor.shapes = move_elements_to_array_end(array, elements)
|
||||
self.shape_editor.repaint()
|
||||
self.set_data_modified()
|
||||
|
||||
def set_selection_on_bottom(self):
|
||||
array = self.shape_editor.shapes
|
||||
elements = self.shape_editor.selection
|
||||
shapes = move_elements_to_array_begin(array, elements)
|
||||
self.shape_editor.shapes = shapes
|
||||
self.shape_editor.repaint()
|
||||
self.set_data_modified()
|
||||
|
||||
def delete_selection(self):
|
||||
for shape in reversed(self.shape_editor.selection.shapes):
|
||||
self.shape_editor.shapes.remove(shape)
|
||||
self.shape_editor.selection.remove(shape)
|
||||
self.update_manipulator_rect()
|
||||
self.set_data_modified()
|
||||
|
||||
def update_manipulator_rect(self):
|
||||
rects = [shape.rect for shape in self.shape_editor.selection]
|
||||
rect = get_combined_rects(rects)
|
||||
self.shape_editor.manipulator.set_rect(rect)
|
||||
self.shape_editor.repaint()
|
||||
|
||||
def picker_data(self):
|
||||
return {
|
||||
'general': self.options,
|
||||
'shapes': [shape.options for shape in self.shape_editor.shapes]}
|
||||
|
||||
def set_picker_data(self, picker_data, reset_stacks=False):
|
||||
self.options = picker_data['general']
|
||||
self.shape_editor.options = self.options
|
||||
shapes = [Shape(options) for options in picker_data['shapes']]
|
||||
self.shape_editor.shapes = shapes
|
||||
self.shape_editor.manipulator.set_rect(None)
|
||||
self.shape_editor.repaint()
|
||||
if reset_stacks is True:
|
||||
self.undo_manager.reset_stacks()
|
||||
|
||||
def do_symmetry(self, horizontal=True):
|
||||
shapes = self.shape_editor.selection.shapes
|
||||
for shape in shapes:
|
||||
rect_symmetry(
|
||||
rect=shape.rect,
|
||||
point=self.shape_editor.manipulator.rect.center(),
|
||||
horizontal=horizontal)
|
||||
shape.synchronize_rect()
|
||||
self.shape_editor.repaint()
|
||||
if not cmds.optionVar(query=TRIGGER_REPLACE_ON_MIRROR):
|
||||
self.set_data_modified()
|
||||
return
|
||||
if not self.search_and_replace():
|
||||
self.set_data_modified()
|
||||
|
||||
def search_and_replace(self):
|
||||
dialog = SearchAndReplaceDialog()
|
||||
if not dialog.exec_():
|
||||
return False
|
||||
|
||||
if dialog.filter == 0: # Search on all shapes.
|
||||
shapes = self.shape_editor.shapes
|
||||
else:
|
||||
shapes = self.shape_editor.selection
|
||||
|
||||
pattern = dialog.search.text()
|
||||
replace = dialog.replace.text()
|
||||
|
||||
for s in shapes:
|
||||
if not dialog.field: # Targets
|
||||
if not s.targets():
|
||||
continue
|
||||
targets = [t.replace(pattern, replace) for t in s.targets()]
|
||||
s.options['action.targets'] = targets
|
||||
continue
|
||||
|
||||
if dialog.field <= 2:
|
||||
key = ('text.content', 'image.path')[dialog.field - 1]
|
||||
result = s.options[key].replace(pattern, replace)
|
||||
s.options[key] = result
|
||||
else: # Command code
|
||||
for command in s.options['action.commands']:
|
||||
result = command['command'].replace(pattern, replace)
|
||||
command['command'] = result
|
||||
|
||||
self.set_data_modified()
|
||||
self.shape_editor.repaint()
|
||||
return True
|
||||
|
||||
def move_selection(self, direction):
|
||||
offset = DIRECTION_OFFSETS[direction]
|
||||
rects = (s.rect for s in self.shape_editor.selection)
|
||||
rects = (s.rect for s in self.shape_editor.selection)
|
||||
rect = self.shape_editor.manipulator.rect
|
||||
reference_rect = QtCore.QRect(rect)
|
||||
|
||||
self.shape_editor.transform.set_rect(rect)
|
||||
self.shape_editor.transform.reference_rect = reference_rect
|
||||
self.shape_editor.transform.shift(rects, offset)
|
||||
self.shape_editor.manipulator.update_geometries()
|
||||
for shape in self.shape_editor.selection:
|
||||
shape.synchronize_rect()
|
||||
self.shape_editor.repaint()
|
||||
self.shape_editor.selectedShapesChanged.emit()
|
||||
self.pickerDataModified.emit(self.picker_data())
|
||||
|
||||
def align_selection(self, direction):
|
||||
if not self.shape_editor.selection:
|
||||
return
|
||||
align_shapes(self.shape_editor.selection, direction)
|
||||
rects = [s.rect for s in self.shape_editor.selection]
|
||||
self.shape_editor.manipulator.set_rect(get_combined_rects(rects))
|
||||
self.shape_editor.manipulator.update_geometries()
|
||||
self.shape_editor.repaint()
|
||||
self.shape_editor.selectedShapesChanged.emit()
|
||||
self.pickerDataModified.emit(self.picker_data())
|
||||
|
||||
def arrange_selection(self, direction):
|
||||
if not self.shape_editor.selection:
|
||||
return
|
||||
if direction == 'horizontal':
|
||||
arrange_horizontal(self.shape_editor.selection)
|
||||
else:
|
||||
arrange_vertical(self.shape_editor.selection)
|
||||
rects = [s.rect for s in self.shape_editor.selection]
|
||||
self.shape_editor.manipulator.set_rect(get_combined_rects(rects))
|
||||
self.shape_editor.manipulator.update_geometries()
|
||||
self.shape_editor.repaint()
|
||||
self.shape_editor.selectedShapesChanged.emit()
|
||||
self.pickerDataModified.emit(self.picker_data())
|
||||
|
||||
def call_context_menu(self, position):
|
||||
targets = cmds.ls(selection=True)
|
||||
button = QtWidgets.QAction('Add selection button', self)
|
||||
method = partial(
|
||||
self.create_shape, BUTTON.copy(),
|
||||
position=position, targets=targets)
|
||||
button.triggered.connect(method)
|
||||
template = BUTTON.copy()
|
||||
template.update(clipboard.get_settings())
|
||||
method = partial(
|
||||
self.create_shape, template,
|
||||
position=position, targets=targets)
|
||||
text = 'Add selection button (using settings clipboard)'
|
||||
button2 = QtWidgets.QAction(text, self)
|
||||
button2.triggered.connect(method)
|
||||
|
||||
cursor = get_cursor(self.shape_editor)
|
||||
shape = self.shape_editor.get_hovered_shape(cursor)
|
||||
method = partial(self.update_targets, shape)
|
||||
text = 'Update targets'
|
||||
button3 = QtWidgets.QAction(text, self)
|
||||
button3.setEnabled(bool(shape))
|
||||
button3.triggered.connect(method)
|
||||
|
||||
menu = QtWidgets.QMenu()
|
||||
menu.addAction(button)
|
||||
menu.addAction(button2)
|
||||
menu.addAction(button3)
|
||||
menu.addSection('Visibility Layers')
|
||||
|
||||
layers = sorted(list({
|
||||
s.visibility_layer()
|
||||
for s in self.shape_editor.shapes
|
||||
if s.visibility_layer()}))
|
||||
|
||||
add_selection = QtWidgets.QMenu('Assign to layer', self)
|
||||
add_selection.setEnabled(bool(layers))
|
||||
menu.addMenu(add_selection)
|
||||
for layer in layers:
|
||||
action = QtWidgets.QAction(layer, self)
|
||||
action.triggered.connect(partial(self.set_visibility_layer, layer))
|
||||
add_selection.addAction(action)
|
||||
|
||||
remove_selection = QtWidgets.QAction('Remove assigned layer', self)
|
||||
remove_selection.setEnabled(bool(self.shape_editor.selection.shapes))
|
||||
remove_selection.triggered.connect(self.set_visibility_layer)
|
||||
menu.addAction(remove_selection)
|
||||
|
||||
create_layer = QtWidgets.QAction('Create layer from selection', self)
|
||||
create_layer.triggered.connect(self.create_visibility_layer)
|
||||
create_layer.setEnabled(bool(self.shape_editor.selection.shapes))
|
||||
|
||||
menu.addAction(create_layer)
|
||||
menu.exec_(self.shape_editor.mapToGlobal(position))
|
||||
|
||||
def set_visibility_layer(self, layer=''):
|
||||
for shape in self.shape_editor.selection:
|
||||
shape.options['visibility_layer'] = layer
|
||||
self.layers_modified()
|
||||
|
||||
def layers_modified(self):
|
||||
self.set_data_modified()
|
||||
self.attribute_editor.generals.set_shapes(self.shape_editor.shapes)
|
||||
self.selection_changed()
|
||||
|
||||
def create_visibility_layer(self):
|
||||
text, result = QtWidgets.QInputDialog.getText(
|
||||
self, 'Create visibility layer', 'Layer name')
|
||||
if not text or not result:
|
||||
return
|
||||
|
||||
for shape in self.shape_editor.selection:
|
||||
shape.options['visibility_layer'] = text
|
||||
self.layers_modified()
|
||||
|
||||
def select_layer(self, layer):
|
||||
shapes = [
|
||||
shape for shape in self.shape_editor.shapes
|
||||
if shape.visibility_layer() == layer]
|
||||
self.shape_editor.selection.set(shapes)
|
||||
self.shape_editor.update_selection()
|
||||
self.shape_editor.repaint()
|
||||
self.selection_changed()
|
||||
|
||||
def remove_layer(self, layer):
|
||||
for shape in self.shape_editor.shapes:
|
||||
if shape.visibility_layer() == layer:
|
||||
shape.options['visibility_layer'] = None
|
||||
self.layers_modified()
|
114
Scripts/Animation/dwpicker/dwpicker/designer/highlighter.py
Normal file
@ -0,0 +1,114 @@
|
||||
import keyword
|
||||
from PySide2 import QtGui, QtCore
|
||||
from dwpicker.languages import PYTHON, MEL
|
||||
|
||||
|
||||
MELKEYWORDS = [
|
||||
'if', 'else', 'int', 'float', 'double', 'string', 'array'
|
||||
'var', 'return', 'case', 'then', 'continue', 'break', 'global', 'proc']
|
||||
|
||||
|
||||
TEXT_STYLES = {
|
||||
'keyword': {
|
||||
'color': 'white',
|
||||
'bold': True,
|
||||
'italic': False},
|
||||
'number': {
|
||||
'color': 'cyan',
|
||||
'bold': False,
|
||||
'italic': False},
|
||||
'comment': {
|
||||
'color': (0.7, 0.5, 0.5),
|
||||
'bold': False,
|
||||
'italic': False},
|
||||
'function': {
|
||||
'color': '#ff0571',
|
||||
'bold': False,
|
||||
'italic': True},
|
||||
'string': {
|
||||
'color': 'yellow',
|
||||
'bold': False,
|
||||
'italic': False},
|
||||
'boolean': {
|
||||
'color': '#a18852',
|
||||
'bold': True,
|
||||
'italic': False}}
|
||||
|
||||
|
||||
PATTERNS = {
|
||||
PYTHON: {
|
||||
'keyword': r'\b|'.join(keyword.kwlist),
|
||||
'number': r'\b[+-]?[0-9]+[lL]?\b',
|
||||
'comment': r'#[^\n]*',
|
||||
'function': r'\b[A-Za-z0-9_]+(?=\()',
|
||||
'string': r'".*"|\'.*\'',
|
||||
'boolean': r'\bTrue\b|\bFalse\b'},
|
||||
MEL: {
|
||||
'keyword': r'\b|'.join(MELKEYWORDS),
|
||||
'number': r'\b[+-]?[0-9]+[lL]?\b',
|
||||
'comment': r'//[^\n]*',
|
||||
'function': r'\b[A-Za-z0-9_]+(?=\()',
|
||||
'string': r'".*"|\'.*\'',
|
||||
'boolean': r'\btrue\b|\bfalse\b'}
|
||||
}
|
||||
|
||||
|
||||
class Highlighter(QtGui.QSyntaxHighlighter):
|
||||
PATTERNS = []
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(Highlighter, self).__init__(parent)
|
||||
self.rules = []
|
||||
for name, properties in TEXT_STYLES.items():
|
||||
if name not in self.PATTERNS:
|
||||
continue
|
||||
text_format = create_textcharformat(
|
||||
color=properties['color'],
|
||||
bold=properties['bold'],
|
||||
italic=properties['italic'])
|
||||
|
||||
rule = QtCore.QRegularExpression(self.PATTERNS[name]), text_format
|
||||
self.rules.append(rule)
|
||||
|
||||
def highlightBlock(self, text):
|
||||
for pattern, format_ in self.rules:
|
||||
expression = QtCore.QRegularExpression(pattern)
|
||||
iterator = expression.globalMatch(text)
|
||||
while iterator.hasNext():
|
||||
match = iterator.next()
|
||||
index = match.capturedStart()
|
||||
length = match.capturedLength()
|
||||
self.setFormat(index, length, format_)
|
||||
|
||||
|
||||
class PythonHighlighter(Highlighter):
|
||||
PATTERNS = PATTERNS[PYTHON]
|
||||
|
||||
|
||||
class MelHighlighter(Highlighter):
|
||||
PATTERNS = PATTERNS[MEL]
|
||||
|
||||
|
||||
HIGHLIGHTERS = {
|
||||
PYTHON: PythonHighlighter,
|
||||
MEL: MelHighlighter}
|
||||
|
||||
|
||||
def get_highlighter(language):
|
||||
return HIGHLIGHTERS.get(language, Highlighter)
|
||||
|
||||
|
||||
def create_textcharformat(color, bold=False, italic=False):
|
||||
char_format = QtGui.QTextCharFormat()
|
||||
qcolor = QtGui.QColor()
|
||||
if isinstance(color, str):
|
||||
qcolor.setNamedColor(color)
|
||||
else:
|
||||
r, g, b = color
|
||||
qcolor.setRgbF(r, g, b)
|
||||
char_format.setForeground(qcolor)
|
||||
if bold:
|
||||
char_format.setFontWeight(QtGui.QFont.Bold)
|
||||
if italic:
|
||||
char_format.setFontItalic(True)
|
||||
return char_format
|
101
Scripts/Animation/dwpicker/dwpicker/designer/layer.py
Normal file
@ -0,0 +1,101 @@
|
||||
|
||||
from PySide2 import QtWidgets, QtCore
|
||||
|
||||
|
||||
class VisibilityLayersEditor(QtWidgets.QWidget):
|
||||
removeLayer = QtCore.Signal(str)
|
||||
selectLayerContent = QtCore.Signal(str)
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(VisibilityLayersEditor, self).__init__(parent)
|
||||
self.model = VisbilityLayersModel()
|
||||
self.table = QtWidgets.QTableView()
|
||||
self.table.horizontalHeader().setSectionResizeMode(
|
||||
QtWidgets.QHeaderView.ResizeToContents)
|
||||
self.table.verticalHeader().setSectionResizeMode(
|
||||
QtWidgets.QHeaderView.ResizeToContents)
|
||||
self.table.setShowGrid(False)
|
||||
self.table.setAlternatingRowColors(True)
|
||||
self.table.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
|
||||
self.table.setSelectionMode(
|
||||
QtWidgets.QAbstractItemView.SingleSelection)
|
||||
self.table.setModel(self.model)
|
||||
self.table.setFixedHeight(100)
|
||||
|
||||
self.select_content = QtWidgets.QPushButton('Select layer content')
|
||||
self.select_content.released.connect(self.call_select_layer)
|
||||
self.remove_layer = QtWidgets.QPushButton('Remove selected layer')
|
||||
self.remove_layer.released.connect(self.call_remove_layer)
|
||||
|
||||
layout = QtWidgets.QVBoxLayout(self)
|
||||
layout.setSpacing(0)
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
layout.addWidget(self.table)
|
||||
layout.addWidget(self.select_content)
|
||||
layout.addWidget(self.remove_layer)
|
||||
|
||||
def selected_layer(self):
|
||||
indexes = self.table.selectedIndexes()
|
||||
if not indexes:
|
||||
return
|
||||
return self.model.layers_data[indexes[0].row()][0]
|
||||
|
||||
def call_remove_layer(self):
|
||||
layer = self.selected_layer()
|
||||
if not layer:
|
||||
return
|
||||
self.removeLayer.emit(layer)
|
||||
|
||||
def call_select_layer(self):
|
||||
layer = self.selected_layer()
|
||||
if not layer:
|
||||
return
|
||||
self.selectLayerContent.emit(layer)
|
||||
|
||||
def set_shapes(self, shapes):
|
||||
self.model.set_shapes(shapes)
|
||||
|
||||
|
||||
class VisbilityLayersModel(QtCore.QAbstractTableModel):
|
||||
HEADERS = 'name', 'shapes'
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(VisbilityLayersModel, self).__init__(parent)
|
||||
self.layers_data = []
|
||||
|
||||
def rowCount(self, _):
|
||||
return len(self.layers_data)
|
||||
|
||||
def columnCount(self, _):
|
||||
return len(self.HEADERS)
|
||||
|
||||
def set_shapes(self, shapes):
|
||||
self.layoutAboutToBeChanged.emit()
|
||||
data = {}
|
||||
for shape in shapes:
|
||||
if not shape.visibility_layer():
|
||||
continue
|
||||
data[shape.visibility_layer()] = data.setdefault(
|
||||
shape.visibility_layer(), 0) + 1
|
||||
self.layers_data = sorted(data.items())
|
||||
self.layoutChanged.emit()
|
||||
|
||||
def headerData(self, section, orientation, role):
|
||||
if orientation == QtCore.Qt.Vertical or role != QtCore.Qt.DisplayRole:
|
||||
return
|
||||
return self.HEADERS[section]
|
||||
|
||||
def data(self, index, role):
|
||||
if not index.isValid():
|
||||
return
|
||||
if role == QtCore.Qt.TextAlignmentRole:
|
||||
if index.column() == 1:
|
||||
return QtCore.Qt.AlignCenter
|
||||
|
||||
if role != QtCore.Qt.DisplayRole:
|
||||
return
|
||||
|
||||
if index.column() == 0:
|
||||
return self.layers_data[index.row()][0]
|
||||
if index.column() == 1:
|
||||
return str(self.layers_data[index.row()][1])
|
270
Scripts/Animation/dwpicker/dwpicker/designer/menu.py
Normal file
@ -0,0 +1,270 @@
|
||||
from functools import partial
|
||||
from maya import cmds
|
||||
from PySide2 import QtGui, QtWidgets, QtCore
|
||||
|
||||
from dwpicker.optionvar import (
|
||||
BG_LOCKED, SNAP_ITEMS, SNAP_GRID_X, SNAP_GRID_Y, save_optionvar)
|
||||
from dwpicker.qtutils import icon
|
||||
|
||||
|
||||
class MenuWidget(QtWidgets.QWidget):
|
||||
addBackgroundRequested = QtCore.Signal()
|
||||
addButtonRequested = QtCore.Signal()
|
||||
addTextRequested = QtCore.Signal()
|
||||
arrangeRequested = QtCore.Signal(str)
|
||||
centerValuesChanged = QtCore.Signal(int, int)
|
||||
copyRequested = QtCore.Signal()
|
||||
copySettingsRequested = QtCore.Signal()
|
||||
deleteRequested = QtCore.Signal()
|
||||
editCenterToggled = QtCore.Signal(bool)
|
||||
frameShapes = QtCore.Signal()
|
||||
lockBackgroundShapeToggled = QtCore.Signal(bool)
|
||||
moveDownRequested = QtCore.Signal()
|
||||
moveUpRequested = QtCore.Signal()
|
||||
onBottomRequested = QtCore.Signal()
|
||||
onTopRequested = QtCore.Signal()
|
||||
pasteRequested = QtCore.Signal()
|
||||
pasteSettingsRequested = QtCore.Signal()
|
||||
redoRequested = QtCore.Signal()
|
||||
searchAndReplaceRequested = QtCore.Signal()
|
||||
sizeChanged = QtCore.Signal()
|
||||
snapValuesChanged = QtCore.Signal()
|
||||
symmetryRequested = QtCore.Signal(bool)
|
||||
undoRequested = QtCore.Signal()
|
||||
useSnapToggled = QtCore.Signal(bool)
|
||||
alignRequested = QtCore.Signal(str)
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(MenuWidget, self).__init__(parent=parent)
|
||||
|
||||
self.delete = QtWidgets.QAction(icon('delete.png'), '', self)
|
||||
self.delete.setToolTip('Delete selection')
|
||||
self.delete.triggered.connect(self.deleteRequested.emit)
|
||||
|
||||
self.copy = QtWidgets.QAction(icon('copy.png'), '', self)
|
||||
self.copy.setToolTip('Copy selection')
|
||||
self.copy.triggered.connect(self.copyRequested.emit)
|
||||
|
||||
self.paste = QtWidgets.QAction(icon('paste.png'), '', self)
|
||||
self.paste.setToolTip('Paste')
|
||||
self.paste.triggered.connect(self.pasteRequested.emit)
|
||||
|
||||
self.undo = QtWidgets.QAction(icon('undo.png'), '', self)
|
||||
self.undo.setToolTip('Undo')
|
||||
self.undo.triggered.connect(self.undoRequested.emit)
|
||||
self.redo = QtWidgets.QAction(icon('redo.png'), '', self)
|
||||
self.redo.setToolTip('Redo')
|
||||
self.redo.triggered.connect(self.redoRequested.emit)
|
||||
|
||||
icon_ = icon('copy_settings.png')
|
||||
self.copy_settings = QtWidgets.QAction(icon_, '', self)
|
||||
self.copy_settings.setToolTip('Copy settings')
|
||||
self.copy_settings.triggered.connect(self.copySettingsRequested.emit)
|
||||
icon_ = icon('paste_settings.png')
|
||||
self.paste_settings = QtWidgets.QAction(icon_, '', self)
|
||||
self.paste_settings.setToolTip('Paste settings')
|
||||
self.paste_settings.triggered.connect(self.pasteSettingsRequested.emit)
|
||||
|
||||
self.search = QtWidgets.QAction(icon('search.png'), '', self)
|
||||
self.search.triggered.connect(self.searchAndReplaceRequested.emit)
|
||||
self.search.setToolTip('Search and replace')
|
||||
|
||||
icon_ = icon('lock-non-interactive.png')
|
||||
self.lock_bg = QtWidgets.QAction(icon_, '', self)
|
||||
self.lock_bg.setToolTip('Lock background items')
|
||||
self.lock_bg.setCheckable(True)
|
||||
self.lock_bg.triggered.connect(self.save_ui_states)
|
||||
self.lock_bg.toggled.connect(self.lockBackgroundShapeToggled.emit)
|
||||
|
||||
validator = QtGui.QIntValidator()
|
||||
self.picker_width = QtWidgets.QLineEdit('600')
|
||||
self.picker_width.setFixedWidth(35)
|
||||
self.picker_width.setValidator(validator)
|
||||
self.picker_width.textEdited.connect(self.size_changed)
|
||||
self.picker_height = QtWidgets.QLineEdit('300')
|
||||
self.picker_height.setFixedWidth(35)
|
||||
self.picker_height.setValidator(validator)
|
||||
self.picker_height.textEdited.connect(self.size_changed)
|
||||
|
||||
self.snap = QtWidgets.QAction(icon('snap.png'), '', self)
|
||||
self.snap.setToolTip('Snap grid enable')
|
||||
self.snap.setCheckable(True)
|
||||
self.snap.triggered.connect(self.snap_toggled)
|
||||
validator = QtGui.QIntValidator(5, 150)
|
||||
self.snapx = QtWidgets.QLineEdit('10')
|
||||
self.snapx.setFixedWidth(35)
|
||||
self.snapx.setValidator(validator)
|
||||
self.snapx.setEnabled(False)
|
||||
self.snapx.textEdited.connect(self.snap_value_changed)
|
||||
self.snapy = QtWidgets.QLineEdit('10')
|
||||
self.snapy.setFixedWidth(35)
|
||||
self.snapy.setValidator(validator)
|
||||
self.snapy.setEnabled(False)
|
||||
self.snapy.textEdited.connect(self.snap_value_changed)
|
||||
self.snap.toggled.connect(self.snapx.setEnabled)
|
||||
self.snap.toggled.connect(self.snapy.setEnabled)
|
||||
|
||||
icon_ = icon('addbutton.png')
|
||||
self.addbutton = QtWidgets.QAction(icon_, '', self)
|
||||
self.addbutton.setToolTip('Add button')
|
||||
self.addbutton.triggered.connect(self.addButtonRequested.emit)
|
||||
self.addtext = QtWidgets.QAction(icon('addtext.png'), '', self)
|
||||
self.addtext.setToolTip('Add text')
|
||||
self.addtext.triggered.connect(self.addTextRequested.emit)
|
||||
self.addbg = QtWidgets.QAction(icon('addbg.png'), '', self)
|
||||
self.addbg.setToolTip('Add background shape')
|
||||
self.addbg.triggered.connect(self.addBackgroundRequested.emit)
|
||||
|
||||
self.frame_shapes = QtWidgets.QAction(icon('frame.png'), '', self)
|
||||
self.frame_shapes.setToolTip('Frame buttons')
|
||||
self.frame_shapes.triggered.connect(self.frameShapes.emit)
|
||||
|
||||
icon_ = icon('onbottom.png')
|
||||
self.onbottom = QtWidgets.QAction(icon_, '', self)
|
||||
self.onbottom.setToolTip('Set selected shapes on bottom')
|
||||
self.onbottom.triggered.connect(self.onBottomRequested.emit)
|
||||
icon_ = icon('movedown.png')
|
||||
self.movedown = QtWidgets.QAction(icon_, '', self)
|
||||
self.movedown.setToolTip('Move down selected shapes')
|
||||
self.movedown.triggered.connect(self.moveDownRequested.emit)
|
||||
self.moveup = QtWidgets.QAction(icon('moveup.png'), '', self)
|
||||
self.moveup.setToolTip('Move up selected shapes')
|
||||
self.moveup.triggered.connect(self.moveUpRequested.emit)
|
||||
self.ontop = QtWidgets.QAction(icon('ontop.png'), '', self)
|
||||
self.ontop.setToolTip('Set selected shapes on top')
|
||||
self.ontop.triggered.connect(self.onTopRequested.emit)
|
||||
|
||||
self.hsymmetry = QtWidgets.QAction(icon('h_symmetry.png'), '', self)
|
||||
method = partial(self.symmetryRequested.emit, True)
|
||||
self.hsymmetry.triggered.connect(method)
|
||||
self.vsymmetry = QtWidgets.QAction(icon('v_symmetry.png'), '', self)
|
||||
method = partial(self.symmetryRequested.emit, False)
|
||||
self.vsymmetry.triggered.connect(method)
|
||||
|
||||
method = partial(self.alignRequested.emit, 'left')
|
||||
self.align_left = QtWidgets.QAction(icon('align_left.png'), '', self)
|
||||
self.align_left.triggered.connect(method)
|
||||
file_ = 'align_h_center.png'
|
||||
method = partial(self.alignRequested.emit, 'h_center')
|
||||
self.align_h_center = QtWidgets.QAction(icon(file_), '', self)
|
||||
self.align_h_center.triggered.connect(method)
|
||||
method = partial(self.alignRequested.emit, 'right')
|
||||
self.align_right = QtWidgets.QAction(icon('align_right.png'), '', self)
|
||||
self.align_right.triggered.connect(method)
|
||||
method = partial(self.alignRequested.emit, 'top')
|
||||
self.align_top = QtWidgets.QAction(icon('align_top.png'), '', self)
|
||||
self.align_top.triggered.connect(method)
|
||||
file_ = 'align_v_center.png'
|
||||
self.align_v_center = QtWidgets.QAction(icon(file_), '', self)
|
||||
method = partial(self.alignRequested.emit, 'v_center')
|
||||
self.align_v_center.triggered.connect(method)
|
||||
file_ = 'align_bottom.png'
|
||||
method = partial(self.alignRequested.emit, 'bottom')
|
||||
self.align_bottom = QtWidgets.QAction(icon(file_), '', self)
|
||||
self.align_bottom.triggered.connect(method)
|
||||
|
||||
file_ = 'arrange_h.png'
|
||||
method = partial(self.arrangeRequested.emit, 'horizontal')
|
||||
self.arrange_horizontal = QtWidgets.QAction(icon(file_), '', self)
|
||||
self.arrange_horizontal.triggered.connect(method)
|
||||
|
||||
file_ = 'arrange_v.png'
|
||||
method = partial(self.arrangeRequested.emit, 'vertical')
|
||||
self.arrange_vertical = QtWidgets.QAction(icon(file_), '', self)
|
||||
self.arrange_vertical.triggered.connect(method)
|
||||
|
||||
self.toolbar = QtWidgets.QToolBar()
|
||||
self.toolbar.addAction(self.delete)
|
||||
self.toolbar.addAction(self.copy)
|
||||
self.toolbar.addAction(self.paste)
|
||||
self.toolbar.addAction(self.copy_settings)
|
||||
self.toolbar.addAction(self.paste_settings)
|
||||
self.toolbar.addSeparator()
|
||||
self.toolbar.addAction(self.undo)
|
||||
self.toolbar.addAction(self.redo)
|
||||
self.toolbar.addSeparator()
|
||||
self.toolbar.addAction(self.search)
|
||||
self.toolbar.addSeparator()
|
||||
self.toolbar.addAction(self.lock_bg)
|
||||
self.toolbar.addSeparator()
|
||||
self.toolbar.addAction(self.snap)
|
||||
self.toolbar.addWidget(self.snapx)
|
||||
self.toolbar.addWidget(self.snapy)
|
||||
self.toolbar.addSeparator()
|
||||
self.toolbar.addWidget(QtWidgets.QLabel('size'))
|
||||
self.toolbar.addWidget(self.picker_width)
|
||||
self.toolbar.addWidget(self.picker_height)
|
||||
self.toolbar.addSeparator()
|
||||
self.toolbar.addAction(self.addbutton)
|
||||
self.toolbar.addAction(self.addtext)
|
||||
self.toolbar.addAction(self.addbg)
|
||||
self.toolbar.addSeparator()
|
||||
self.toolbar.addAction(self.frame_shapes)
|
||||
self.toolbar.addSeparator()
|
||||
self.toolbar.addAction(self.hsymmetry)
|
||||
self.toolbar.addAction(self.vsymmetry)
|
||||
self.toolbar.addSeparator()
|
||||
self.toolbar.addAction(self.onbottom)
|
||||
self.toolbar.addAction(self.movedown)
|
||||
self.toolbar.addAction(self.moveup)
|
||||
self.toolbar.addAction(self.ontop)
|
||||
self.toolbar.addSeparator()
|
||||
self.toolbar.addAction(self.align_left)
|
||||
self.toolbar.addAction(self.align_h_center)
|
||||
self.toolbar.addAction(self.align_right)
|
||||
self.toolbar.addAction(self.align_top)
|
||||
self.toolbar.addAction(self.align_v_center)
|
||||
self.toolbar.addAction(self.align_bottom)
|
||||
self.toolbar.addAction(self.arrange_horizontal)
|
||||
self.toolbar.addAction(self.arrange_vertical)
|
||||
|
||||
self.layout = QtWidgets.QVBoxLayout(self)
|
||||
self.layout.setContentsMargins(0, 0, 10, 0)
|
||||
self.layout.addWidget(self.toolbar)
|
||||
|
||||
self.load_ui_states()
|
||||
|
||||
def load_ui_states(self):
|
||||
self.snap.setChecked(cmds.optionVar(query=SNAP_ITEMS))
|
||||
value = str(cmds.optionVar(query=SNAP_GRID_X))
|
||||
self.snapx.setText(value)
|
||||
value = str(cmds.optionVar(query=SNAP_GRID_Y))
|
||||
self.snapy.setText(value)
|
||||
self.lock_bg.setChecked(bool(cmds.optionVar(query=BG_LOCKED)))
|
||||
|
||||
def save_ui_states(self):
|
||||
save_optionvar(BG_LOCKED, int(self.lock_bg.isChecked()))
|
||||
save_optionvar(SNAP_ITEMS, int(self.snap.isChecked()))
|
||||
save_optionvar(SNAP_GRID_X, int(self.snapx.text()))
|
||||
save_optionvar(SNAP_GRID_Y, int(self.snapy.text()))
|
||||
|
||||
def size_changed(self, *_):
|
||||
self.sizeChanged.emit()
|
||||
|
||||
def edit_center_toggled(self):
|
||||
self.editCenterToggled.emit(self.editcenter.isChecked())
|
||||
|
||||
def snap_toggled(self):
|
||||
self.useSnapToggled.emit(self.snap.isChecked())
|
||||
self.save_ui_states()
|
||||
|
||||
def snap_values(self):
|
||||
x = int(self.snapx.text()) if self.snapx.text() else 1
|
||||
y = int(self.snapy.text()) if self.snapy.text() else 1
|
||||
x = x if x > 0 else 1
|
||||
y = y if y > 0 else 1
|
||||
return x, y
|
||||
|
||||
def snap_value_changed(self, _):
|
||||
self.snapValuesChanged.emit()
|
||||
self.save_ui_states()
|
||||
|
||||
def set_size_values(self, width, height):
|
||||
self.picker_width.setText(str(width))
|
||||
self.picker_height.setText(str(height))
|
||||
self.sizeChanged.emit()
|
||||
|
||||
def get_size(self):
|
||||
width = int(self.picker_width.text()) if self.picker_width.text() else 1
|
||||
height = int(self.picker_height.text()) if self.picker_height.text() else 1
|
||||
return QtCore.QSize(width, height)
|
445
Scripts/Animation/dwpicker/dwpicker/dialog.py
Normal file
@ -0,0 +1,445 @@
|
||||
from functools import partial
|
||||
import os
|
||||
|
||||
from PySide2 import QtWidgets, QtCore, QtGui
|
||||
from maya import cmds
|
||||
|
||||
from dwpicker.designer.highlighter import get_highlighter
|
||||
from dwpicker.optionvar import (
|
||||
save_optionvar, CHECK_FOR_UPDATE,
|
||||
SEARCH_FIELD_INDEX, LAST_IMAGE_DIRECTORY_USED, SETTINGS_GROUP_TO_COPY,
|
||||
SHAPES_FILTER_INDEX, SETTINGS_TO_COPY)
|
||||
from dwpicker.languages import MEL, PYTHON
|
||||
from dwpicker.path import get_image_directory
|
||||
from dwpicker.namespace import selected_namespace
|
||||
from dwpicker.templates import BUTTON
|
||||
|
||||
|
||||
SEARCH_AND_REPLACE_FIELDS = 'Targets', 'Label', 'Image path', 'Command'
|
||||
SHAPES_FILTERS = 'All shapes', 'Selected shapes'
|
||||
|
||||
|
||||
def warning(title, message, parent=None):
|
||||
return QtWidgets.QMessageBox.warning(
|
||||
parent,
|
||||
title,
|
||||
message,
|
||||
QtWidgets.QMessageBox.Ok,
|
||||
QtWidgets.QMessageBox.Ok)
|
||||
|
||||
|
||||
def question(title, message, buttons=None, parent=None):
|
||||
buttons = buttons or QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel
|
||||
result = QtWidgets.QMessageBox.question(
|
||||
parent, title, message, buttons, QtWidgets.QMessageBox.Ok)
|
||||
return result == QtWidgets.QMessageBox.Ok
|
||||
|
||||
|
||||
def get_image_path(parent=None):
|
||||
filename = QtWidgets.QFileDialog.getOpenFileName(
|
||||
parent, "Repath image...",
|
||||
get_image_directory(),
|
||||
filter="Images (*.jpg *.gif *.png *.tga)")[0]
|
||||
if not filename:
|
||||
return None
|
||||
directory = os.path.dirname(filename)
|
||||
save_optionvar(LAST_IMAGE_DIRECTORY_USED, directory)
|
||||
return filename
|
||||
|
||||
|
||||
class NamespaceDialog(QtWidgets.QDialog):
|
||||
def __init__(self, parent=None):
|
||||
super(NamespaceDialog, self).__init__(parent=parent)
|
||||
self.setWindowTitle('Select namespace ...')
|
||||
self.namespace_combo = QtWidgets.QComboBox()
|
||||
self.namespace_combo.setEditable(True)
|
||||
namespaces = [':'] + cmds.namespaceInfo(
|
||||
listOnlyNamespaces=True, recurse=True)
|
||||
self.namespace_combo.addItems(namespaces)
|
||||
self.namespace_combo.setCurrentText(selected_namespace())
|
||||
|
||||
self.detect_selection = QtWidgets.QPushButton('Detect from selection')
|
||||
self.detect_selection.released.connect(self.call_detect_selection)
|
||||
self.ok = QtWidgets.QPushButton('Ok')
|
||||
self.ok.released.connect(self.accept)
|
||||
self.cancel = QtWidgets.QPushButton('Cancel')
|
||||
self.cancel.released.connect(self.reject)
|
||||
|
||||
self.button_layout = QtWidgets.QHBoxLayout()
|
||||
self.button_layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.button_layout.addStretch(1)
|
||||
self.button_layout.addWidget(self.detect_selection)
|
||||
self.button_layout.addSpacing(16)
|
||||
self.button_layout.addWidget(self.ok)
|
||||
self.button_layout.addWidget(self.cancel)
|
||||
|
||||
self.layout = QtWidgets.QVBoxLayout(self)
|
||||
self.layout.addWidget(self.namespace_combo)
|
||||
self.layout.addLayout(self.button_layout)
|
||||
|
||||
@property
|
||||
def namespace(self):
|
||||
return self.namespace_combo.currentText()
|
||||
|
||||
def call_detect_selection(self):
|
||||
self.namespace_combo.setCurrentText(selected_namespace())
|
||||
|
||||
|
||||
class SettingsPaster(QtWidgets.QDialog):
|
||||
def __init__(self, parent=None):
|
||||
super(SettingsPaster, self).__init__(parent)
|
||||
self.setWindowTitle('Paste settings')
|
||||
self.groups = {}
|
||||
self.categories = {}
|
||||
enable_settings = cmds.optionVar(query=SETTINGS_TO_COPY).split(';')
|
||||
for setting in sorted(BUTTON.keys()):
|
||||
text = ' '.join(setting.split('.')[1:]).capitalize()
|
||||
checkbox = QtWidgets.QCheckBox(text or setting.capitalize())
|
||||
checkbox.setting = setting
|
||||
checkbox.setChecked(setting in enable_settings)
|
||||
checkbox.stateChanged.connect(self.updated)
|
||||
name = setting.split('.')[0]
|
||||
self.categories.setdefault(name, []).append(checkbox)
|
||||
enable_groups = cmds.optionVar(query=SETTINGS_GROUP_TO_COPY).split(';')
|
||||
|
||||
groups_layout = QtWidgets.QVBoxLayout()
|
||||
self.group_layouts = QtWidgets.QHBoxLayout()
|
||||
checkboxes_count = 0
|
||||
for category, checkboxes in self.categories.items():
|
||||
if checkboxes_count > 12:
|
||||
checkboxes_count = 0
|
||||
groups_layout.addStretch(1)
|
||||
self.group_layouts.addLayout(groups_layout)
|
||||
groups_layout = QtWidgets.QVBoxLayout()
|
||||
group = QtWidgets.QGroupBox(category)
|
||||
group.setCheckable(True)
|
||||
group.setChecked(category in enable_groups)
|
||||
group.toggled.connect(self.updated)
|
||||
group_layout = QtWidgets.QVBoxLayout(group)
|
||||
for checkbox in checkboxes:
|
||||
group_layout.addWidget(checkbox)
|
||||
self.groups[category] = group
|
||||
groups_layout.addWidget(group)
|
||||
checkboxes_count += len(checkboxes)
|
||||
groups_layout.addStretch(1)
|
||||
self.group_layouts.addLayout(groups_layout)
|
||||
|
||||
self.paste = QtWidgets.QPushButton('Paste')
|
||||
self.paste.released.connect(self.accept)
|
||||
self.cancel = QtWidgets.QPushButton('Cancel')
|
||||
self.cancel.released.connect(self.reject)
|
||||
self.buttons_layout = QtWidgets.QHBoxLayout()
|
||||
self.buttons_layout.addStretch(1)
|
||||
self.buttons_layout.addWidget(self.paste)
|
||||
self.buttons_layout.addWidget(self.cancel)
|
||||
|
||||
self.layout = QtWidgets.QVBoxLayout(self)
|
||||
self.layout.addLayout(self.group_layouts)
|
||||
self.layout.addLayout(self.buttons_layout)
|
||||
|
||||
@property
|
||||
def settings(self):
|
||||
return [
|
||||
cb.setting for category, checkboxes in self.categories.items()
|
||||
for cb in checkboxes if cb.isChecked() and
|
||||
self.groups[category].isChecked()]
|
||||
|
||||
def updated(self, *_):
|
||||
cat = ';'.join([c for c, g in self.groups.items() if g.isChecked()])
|
||||
save_optionvar(SETTINGS_GROUP_TO_COPY, cat)
|
||||
save_optionvar(SETTINGS_TO_COPY, ';'.join(self.settings))
|
||||
|
||||
|
||||
class SearchAndReplaceDialog(QtWidgets.QDialog):
|
||||
def __init__(self, parent=None):
|
||||
super(SearchAndReplaceDialog, self).__init__(parent=parent)
|
||||
self.setWindowTitle('Search and replace in shapes')
|
||||
self.sizeHint = lambda: QtCore.QSize(320, 80)
|
||||
|
||||
self.filters = QtWidgets.QComboBox()
|
||||
self.filters.addItems(SHAPES_FILTERS)
|
||||
self.filters.setCurrentIndex(cmds.optionVar(query=SHAPES_FILTER_INDEX))
|
||||
function = partial(save_optionvar, SHAPES_FILTER_INDEX)
|
||||
self.filters.currentIndexChanged.connect(function)
|
||||
self.fields = QtWidgets.QComboBox()
|
||||
self.fields.addItems(SEARCH_AND_REPLACE_FIELDS)
|
||||
self.fields.setCurrentIndex(cmds.optionVar(query=SEARCH_FIELD_INDEX))
|
||||
function = partial(save_optionvar, SEARCH_FIELD_INDEX)
|
||||
self.fields.currentIndexChanged.connect(function)
|
||||
self.search = QtWidgets.QLineEdit()
|
||||
self.replace = QtWidgets.QLineEdit()
|
||||
|
||||
self.ok = QtWidgets.QPushButton('Replace')
|
||||
self.ok.released.connect(self.accept)
|
||||
self.cancel = QtWidgets.QPushButton('Cancel')
|
||||
self.cancel.released.connect(self.reject)
|
||||
|
||||
self.options = QtWidgets.QFormLayout()
|
||||
self.options.setContentsMargins(0, 0, 0, 0)
|
||||
self.options.addRow('Apply on: ', self.filters)
|
||||
self.options.addRow('Field to search: ', self.fields)
|
||||
self.options.addRow('Search: ', self.search)
|
||||
self.options.addRow('Replace by: ', self.replace)
|
||||
|
||||
self.button_layout = QtWidgets.QHBoxLayout()
|
||||
self.button_layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.button_layout.addStretch(1)
|
||||
self.button_layout.addWidget(self.ok)
|
||||
self.button_layout.addWidget(self.cancel)
|
||||
|
||||
self.layout = QtWidgets.QVBoxLayout(self)
|
||||
self.layout.addLayout(self.options)
|
||||
self.layout.addLayout(self.button_layout)
|
||||
|
||||
@property
|
||||
def field(self):
|
||||
'''
|
||||
0 = Targets
|
||||
1 = Label
|
||||
2 = Command
|
||||
3 = Image path
|
||||
'''
|
||||
return self.fields.currentIndex()
|
||||
|
||||
@property
|
||||
def filter(self):
|
||||
'''
|
||||
0 = Apply on all shapes
|
||||
1 = Apply on selected shapes
|
||||
'''
|
||||
return self.filters.currentIndex()
|
||||
|
||||
|
||||
class MissingImages(QtWidgets.QDialog):
|
||||
def __init__(self, paths, parent=None):
|
||||
super(MissingImages, self).__init__(parent)
|
||||
self.setWindowTitle('Missing images')
|
||||
self.model = PathModel(paths)
|
||||
self.paths = QtWidgets.QTableView()
|
||||
self.paths.setAlternatingRowColors(True)
|
||||
self.paths.setShowGrid(False)
|
||||
self.paths.setSelectionMode(QtWidgets.QAbstractItemView.NoSelection)
|
||||
mode = QtWidgets.QHeaderView.ResizeToContents
|
||||
self.paths.verticalHeader().resizeSections(mode)
|
||||
self.paths.verticalHeader().hide()
|
||||
self.paths.horizontalHeader().show()
|
||||
self.paths.horizontalHeader().resizeSections(mode)
|
||||
self.paths.horizontalHeader().setStretchLastSection(True)
|
||||
mode = QtWidgets.QAbstractItemView.ScrollPerPixel
|
||||
self.paths.setHorizontalScrollMode(mode)
|
||||
self.paths.setVerticalScrollMode(mode)
|
||||
self.paths.setModel(self.model)
|
||||
|
||||
self.browse = QtWidgets.QPushButton('B')
|
||||
self.browse.setFixedWidth(30)
|
||||
self.browse.released.connect(self.call_browse)
|
||||
self.update = QtWidgets.QPushButton('Update')
|
||||
self.update.released.connect(self.accept)
|
||||
self.skip = QtWidgets.QPushButton('Skip')
|
||||
self.skip.released.connect(self.reject)
|
||||
self.validators = QtWidgets.QHBoxLayout()
|
||||
self.validators.addStretch(1)
|
||||
self.validators.addWidget(self.browse)
|
||||
self.validators.addWidget(self.update)
|
||||
self.validators.addWidget(self.skip)
|
||||
|
||||
self.layout = QtWidgets.QVBoxLayout(self)
|
||||
self.layout.addWidget(self.paths)
|
||||
self.layout.addLayout(self.validators)
|
||||
|
||||
def output(self, path):
|
||||
for p, output in zip(self.model.paths, self.model.outputs):
|
||||
if p == path:
|
||||
return output
|
||||
|
||||
@property
|
||||
def outputs(self):
|
||||
return self.model.outputs
|
||||
|
||||
def resizeEvent(self, _):
|
||||
mode = QtWidgets.QHeaderView.ResizeToContents
|
||||
self.paths.verticalHeader().resizeSections(mode)
|
||||
self.paths.horizontalHeader().resizeSections(mode)
|
||||
|
||||
def call_browse(self):
|
||||
directory = QtWidgets.QFileDialog.getExistingDirectory(
|
||||
self, "Select image folder")
|
||||
if not directory:
|
||||
return
|
||||
filenames = os.listdir(directory)
|
||||
self.model.layoutAboutToBeChanged.emit()
|
||||
for i, path in enumerate(self.model.paths):
|
||||
filename = os.path.basename(path)
|
||||
if filename in filenames:
|
||||
filepath = os.path.join(directory, filename)
|
||||
self.model.outputs[i] = filepath
|
||||
self.model.layoutChanged.emit()
|
||||
|
||||
|
||||
class PathModel(QtCore.QAbstractTableModel):
|
||||
HEADERS = 'filename', 'directory'
|
||||
|
||||
def __init__(self, paths, parent=None):
|
||||
super(PathModel, self).__init__(parent)
|
||||
self.paths = paths
|
||||
self.outputs = paths[:]
|
||||
|
||||
def rowCount(self, *_):
|
||||
return len(self.paths)
|
||||
|
||||
def columnCount(self, *_):
|
||||
return 2
|
||||
|
||||
def flags(self, index):
|
||||
flags = super(PathModel, self).flags(index)
|
||||
if index.column() == 1:
|
||||
flags |= QtCore.Qt.ItemIsEditable
|
||||
return flags
|
||||
|
||||
def headerData(self, position, orientation, role):
|
||||
if orientation != QtCore.Qt.Horizontal:
|
||||
return
|
||||
|
||||
if role != QtCore.Qt.DisplayRole:
|
||||
return
|
||||
|
||||
return self.HEADERS[position]
|
||||
|
||||
def data(self, index, role):
|
||||
if not index.isValid():
|
||||
return
|
||||
|
||||
row, col = index.row(), index.column()
|
||||
if role == QtCore.Qt.DisplayRole:
|
||||
if col == 0:
|
||||
return os.path.basename(self.outputs[row])
|
||||
if col == 1:
|
||||
return os.path.dirname(self.outputs[row])
|
||||
|
||||
elif role == QtCore.Qt.BackgroundColorRole:
|
||||
if not os.path.exists(self.outputs[row]):
|
||||
return QtGui.QColor(QtCore.Qt.darkRed)
|
||||
|
||||
|
||||
class UpdateAvailableDialog(QtWidgets.QDialog):
|
||||
def __init__(self, version, parent=None):
|
||||
super(UpdateAvailableDialog, self).__init__(parent=parent)
|
||||
self.setWindowTitle('Update available')
|
||||
|
||||
# Widgets
|
||||
text = '\n New DreamWall Picker version "{0}" is available ! \n'
|
||||
label = QtWidgets.QLabel(text.format(version))
|
||||
|
||||
ok_btn = QtWidgets.QPushButton('Open GitHub page')
|
||||
ok_btn.released.connect(self.accept)
|
||||
|
||||
cancel_btn = QtWidgets.QPushButton('Close')
|
||||
cancel_btn.released.connect(self.reject)
|
||||
|
||||
self.check_cb = QtWidgets.QCheckBox('Check for update at startup')
|
||||
self.check_cb.stateChanged.connect(
|
||||
self.change_check_for_update_preference)
|
||||
self.check_cb.setChecked(cmds.optionVar(query=CHECK_FOR_UPDATE))
|
||||
|
||||
# Layouts
|
||||
button_layout = QtWidgets.QHBoxLayout()
|
||||
button_layout.addStretch(1)
|
||||
button_layout.addWidget(ok_btn)
|
||||
button_layout.addWidget(cancel_btn)
|
||||
|
||||
cb_layout = QtWidgets.QHBoxLayout()
|
||||
cb_layout.addStretch(1)
|
||||
cb_layout.addWidget(self.check_cb)
|
||||
|
||||
layout = QtWidgets.QVBoxLayout(self)
|
||||
layout.addWidget(label)
|
||||
layout.addLayout(cb_layout)
|
||||
layout.addLayout(button_layout)
|
||||
|
||||
def change_check_for_update_preference(self):
|
||||
save_optionvar(CHECK_FOR_UPDATE, int(self.check_cb.isChecked()))
|
||||
|
||||
|
||||
class CommandEditorDialog(QtWidgets.QDialog):
|
||||
|
||||
def __init__(self, command, parent=None):
|
||||
super(CommandEditorDialog, self).__init__(parent)
|
||||
self.setWindowTitle('Edit/Create command')
|
||||
self.languages = QtWidgets.QComboBox()
|
||||
self.languages.addItems([MEL, PYTHON])
|
||||
self.languages.setCurrentText(command['language'])
|
||||
self.languages.currentIndexChanged.connect(self.language_changed)
|
||||
|
||||
self.button = QtWidgets.QComboBox()
|
||||
self.button.addItems(['left', 'right'])
|
||||
self.button.setCurrentText(command['button'])
|
||||
|
||||
self.enabled = QtWidgets.QCheckBox('Enabled')
|
||||
self.enabled.setChecked(command['enabled'])
|
||||
|
||||
self.ctrl = QtWidgets.QCheckBox('Ctrl')
|
||||
self.ctrl.setChecked(command['ctrl'])
|
||||
self.shift = QtWidgets.QCheckBox('Shift')
|
||||
self.shift.setChecked(command['shift'])
|
||||
self.eval_deferred = QtWidgets.QCheckBox('Eval deferred (python only)')
|
||||
self.eval_deferred.setChecked(command['deferred'])
|
||||
self.unique_undo = QtWidgets.QCheckBox('Unique undo')
|
||||
self.unique_undo.setChecked(command['force_compact_undo'])
|
||||
|
||||
self.command = QtWidgets.QTextEdit()
|
||||
self.command.setFixedHeight(100)
|
||||
self.command.setPlainText(command['command'])
|
||||
|
||||
self.ok = QtWidgets.QPushButton('Ok')
|
||||
self.ok.released.connect(self.accept)
|
||||
self.cancel = QtWidgets.QPushButton('Cancel')
|
||||
self.cancel.released.connect(self.reject)
|
||||
|
||||
form = QtWidgets.QFormLayout()
|
||||
form.setSpacing(0)
|
||||
form.addRow('Language', self.languages)
|
||||
form.addRow('Mouse button', self.button)
|
||||
|
||||
modifiers_group = QtWidgets.QGroupBox('Modifiers')
|
||||
modifiers_layout = QtWidgets.QVBoxLayout(modifiers_group)
|
||||
modifiers_layout.addWidget(self.ctrl)
|
||||
modifiers_layout.addWidget(self.shift)
|
||||
|
||||
options_group = QtWidgets.QGroupBox('Options')
|
||||
options_layout = QtWidgets.QVBoxLayout(options_group)
|
||||
options_layout.addWidget(self.eval_deferred)
|
||||
options_layout.addWidget(self.unique_undo)
|
||||
options_layout.addLayout(form)
|
||||
|
||||
code = QtWidgets.QGroupBox('Code')
|
||||
code_layout = QtWidgets.QVBoxLayout(code)
|
||||
code_layout.setSpacing(0)
|
||||
code_layout.addWidget(self.command)
|
||||
|
||||
buttons_layout = QtWidgets.QHBoxLayout()
|
||||
buttons_layout.addStretch(1)
|
||||
buttons_layout.addWidget(self.ok)
|
||||
buttons_layout.addWidget(self.cancel)
|
||||
|
||||
layout = QtWidgets.QVBoxLayout(self)
|
||||
layout.addWidget(options_group)
|
||||
layout.addWidget(modifiers_group)
|
||||
layout.addWidget(code)
|
||||
layout.addLayout(buttons_layout)
|
||||
self.language_changed()
|
||||
|
||||
def language_changed(self, *_):
|
||||
language = self.languages.currentText()
|
||||
highlighter = get_highlighter(language)
|
||||
highlighter(self.command.document())
|
||||
|
||||
def command_data(self):
|
||||
return {
|
||||
'enabled': self.enabled.isChecked(),
|
||||
'button': self.button.currentText(),
|
||||
'language': self.languages.currentText(),
|
||||
'command': self.command.toPlainText(),
|
||||
'ctrl': self.ctrl.isChecked(),
|
||||
'shift': self.shift.isChecked(),
|
||||
'deferred': self.eval_deferred.isChecked(),
|
||||
'force_compact_undo': self.unique_undo.isChecked()}
|
549
Scripts/Animation/dwpicker/dwpicker/geometry.py
Normal file
@ -0,0 +1,549 @@
|
||||
import math
|
||||
from PySide2 import QtCore
|
||||
|
||||
|
||||
POINT_RADIUS = 8
|
||||
POINT_OFFSET = 4
|
||||
DIRECTIONS = [
|
||||
'top_left',
|
||||
'bottom_left',
|
||||
'top_right',
|
||||
'bottom_right',
|
||||
'left',
|
||||
'right',
|
||||
'top',
|
||||
'bottom']
|
||||
|
||||
|
||||
class ViewportMapper():
|
||||
"""
|
||||
Used to translate/map between:
|
||||
- abstract/data/units coordinates
|
||||
- viewport/display/pixels coordinates
|
||||
"""
|
||||
def __init__(self):
|
||||
self.zoom = 1
|
||||
self.origin = QtCore.QPointF(0, 0)
|
||||
# We need the viewport size to be able to center the view or to
|
||||
# automatically set zoom from selection:
|
||||
self.viewsize = QtCore.QSize(300, 300)
|
||||
|
||||
def to_viewport(self, value):
|
||||
return value * self.zoom
|
||||
|
||||
def to_units(self, pixels):
|
||||
return pixels / self.zoom
|
||||
|
||||
def to_viewport_coords(self, units_point):
|
||||
return QtCore.QPointF(
|
||||
self.to_viewport(units_point.x()) - self.origin.x(),
|
||||
self.to_viewport(units_point.y()) - self.origin.y())
|
||||
|
||||
def to_units_coords(self, pixels_point):
|
||||
return QtCore.QPointF(
|
||||
self.to_units(pixels_point.x() + self.origin.x()),
|
||||
self.to_units(pixels_point.y() + self.origin.y()))
|
||||
|
||||
def to_viewport_rect(self, units_rect):
|
||||
return QtCore.QRectF(
|
||||
(units_rect.left() * self.zoom) - self.origin.x(),
|
||||
(units_rect.top() * self.zoom) - self.origin.y(),
|
||||
units_rect.width() * self.zoom,
|
||||
units_rect.height() * self.zoom)
|
||||
|
||||
def to_units_rect(self, pixels_rect):
|
||||
top_left = self.to_units_coords(pixels_rect.topLeft())
|
||||
width = self.to_units(pixels_rect.width())
|
||||
height = self.to_units(pixels_rect.height())
|
||||
return QtCore.QRectF(top_left.x(), top_left.y(), width, height)
|
||||
|
||||
def zoomin(self, factor=10.0):
|
||||
self.zoom += self.zoom * factor
|
||||
self.zoom = min(self.zoom, 5.0)
|
||||
|
||||
def zoomout(self, factor=10.0):
|
||||
self.zoom -= self.zoom * factor
|
||||
self.zoom = max(self.zoom, .1)
|
||||
|
||||
def center_on_point(self, units_center):
|
||||
"""Given current zoom and viewport size, set the origin point."""
|
||||
self.origin = QtCore.QPointF(
|
||||
units_center.x() * self.zoom - self.viewsize.width() / 2,
|
||||
units_center.y() * self.zoom - self.viewsize.height() / 2)
|
||||
|
||||
def focus(self, units_rect):
|
||||
self.zoom = min([
|
||||
float(self.viewsize.width()) / units_rect.width(),
|
||||
float(self.viewsize.height()) / units_rect.height()])
|
||||
if self.zoom > 1:
|
||||
self.zoom *= 0.7 # lower zoom to add some breathing space
|
||||
self.zoom = max(self.zoom, .1)
|
||||
self.center_on_point(units_rect.center())
|
||||
|
||||
|
||||
def get_topleft_rect(rect):
|
||||
"""
|
||||
this function return a manipulator rect for the transform
|
||||
handler.
|
||||
*__________________________
|
||||
| |
|
||||
| |
|
||||
|________________________|
|
||||
"""
|
||||
if rect is None:
|
||||
return None
|
||||
point = rect.topLeft()
|
||||
return QtCore.QRectF(
|
||||
point.x() - (POINT_RADIUS / 2.0) - POINT_OFFSET,
|
||||
point.y() - (POINT_RADIUS / 2.0) - POINT_OFFSET,
|
||||
POINT_RADIUS, POINT_RADIUS)
|
||||
|
||||
|
||||
def get_bottomleft_rect(rect):
|
||||
"""
|
||||
this function return a manipulator rect for the transform
|
||||
handler.
|
||||
__________________________
|
||||
| |
|
||||
| |
|
||||
|________________________|
|
||||
*
|
||||
"""
|
||||
if rect is None:
|
||||
return None
|
||||
point = rect.bottomLeft()
|
||||
return QtCore.QRectF(
|
||||
point.x() - (POINT_RADIUS / 2.0) - POINT_OFFSET,
|
||||
point.y() + (POINT_RADIUS / 2.0) - POINT_OFFSET,
|
||||
POINT_RADIUS, POINT_RADIUS)
|
||||
|
||||
|
||||
def get_topright_rect(rect):
|
||||
"""
|
||||
this function return a manipulator rect for the transform
|
||||
handler.
|
||||
__________________________*
|
||||
| |
|
||||
| |
|
||||
|________________________|
|
||||
"""
|
||||
if rect is None:
|
||||
return None
|
||||
point = rect.topRight()
|
||||
return QtCore.QRectF(
|
||||
point.x() + (POINT_RADIUS / 2.0) - POINT_OFFSET,
|
||||
point.y() - (POINT_RADIUS / 2.0) - POINT_OFFSET,
|
||||
POINT_RADIUS, POINT_RADIUS)
|
||||
|
||||
|
||||
def get_bottomright_rect(rect):
|
||||
"""
|
||||
this function return a manipulator rect for the transform
|
||||
handler.
|
||||
__________________________
|
||||
| |
|
||||
| |
|
||||
|________________________|
|
||||
*
|
||||
"""
|
||||
if rect is None:
|
||||
return None
|
||||
point = rect.bottomRight()
|
||||
return QtCore.QRectF(
|
||||
point.x() + (POINT_RADIUS / 2.0) - POINT_OFFSET,
|
||||
point.y() + (POINT_RADIUS / 2.0) - POINT_OFFSET,
|
||||
POINT_RADIUS, POINT_RADIUS)
|
||||
|
||||
|
||||
def get_left_side_rect(rect):
|
||||
"""
|
||||
this function return a manipulator rect for the transform
|
||||
handler.
|
||||
__________________________
|
||||
| |
|
||||
*| |
|
||||
|________________________|
|
||||
"""
|
||||
if rect is None:
|
||||
return None
|
||||
top = rect.top() + (rect.height() / 2.0)
|
||||
return QtCore.QRectF(
|
||||
rect.left() - (POINT_RADIUS / 2.0) - POINT_OFFSET,
|
||||
top - (POINT_RADIUS / 2.0),
|
||||
POINT_RADIUS, POINT_RADIUS)
|
||||
|
||||
|
||||
def get_right_side_rect(rect):
|
||||
"""
|
||||
this function return a manipulator rect for the transform
|
||||
handler.
|
||||
__________________________
|
||||
| |
|
||||
| |*
|
||||
|________________________|
|
||||
"""
|
||||
if rect is None:
|
||||
return None
|
||||
top = rect.top() + (rect.height() / 2.0)
|
||||
return QtCore.QRectF(
|
||||
rect.right() + (POINT_RADIUS / 2.0) - POINT_OFFSET,
|
||||
top - (POINT_RADIUS / 2.0),
|
||||
POINT_RADIUS, POINT_RADIUS)
|
||||
|
||||
|
||||
def get_top_side_rect(rect):
|
||||
"""
|
||||
this function return a manipulator rect for the transform
|
||||
handler.
|
||||
_____________*____________
|
||||
| |
|
||||
| |
|
||||
|________________________|
|
||||
"""
|
||||
if rect is None:
|
||||
return None
|
||||
return QtCore.QRectF(
|
||||
rect.left() + (rect.width() / 2.0) - (POINT_RADIUS / 2.0),
|
||||
rect.top() - (POINT_RADIUS / 2.0) - POINT_OFFSET,
|
||||
POINT_RADIUS, POINT_RADIUS)
|
||||
|
||||
|
||||
def get_bottom_side_rect(rect):
|
||||
"""
|
||||
this function return a manipulator rect for the transform
|
||||
handler.
|
||||
__________________________
|
||||
| |
|
||||
| |
|
||||
|________________________|
|
||||
*
|
||||
"""
|
||||
if rect is None:
|
||||
return None
|
||||
return QtCore.QRectF(
|
||||
rect.left() + (rect.width() / 2.0) - (POINT_RADIUS / 2.0),
|
||||
rect.bottom() + (POINT_RADIUS / 2.0) - POINT_OFFSET,
|
||||
POINT_RADIUS, POINT_RADIUS)
|
||||
|
||||
|
||||
def grow_rect(rect, value):
|
||||
if rect is None:
|
||||
return None
|
||||
return QtCore.QRectF(
|
||||
rect.left() - value,
|
||||
rect.top() - value,
|
||||
rect.width() + (value * 2),
|
||||
rect.height() + (value * 2))
|
||||
|
||||
|
||||
def relative(value, in_min, in_max, out_min, out_max):
|
||||
"""
|
||||
this function resolve simple equation and return the unknown value
|
||||
in between two values.
|
||||
a, a" = in_min, out_min
|
||||
b, b " = out_max, out_max
|
||||
c = value
|
||||
? is the unknown processed by function.
|
||||
a --------- c --------- b
|
||||
a" --------------- ? ---------------- b"
|
||||
"""
|
||||
factor = float((value - in_min)) / (in_max - in_min)
|
||||
width = out_max - out_min
|
||||
return out_min + (width * (factor))
|
||||
|
||||
|
||||
def distance(a, b):
|
||||
""" return distance between two points """
|
||||
x = (b.x() - a.x())**2
|
||||
y = (b.y() - a.y())**2
|
||||
return math.sqrt(abs(x + y))
|
||||
|
||||
|
||||
def get_relative_point(rect, point):
|
||||
x = point.x() - rect.left()
|
||||
y = point.y() - rect.top()
|
||||
return QtCore.QPoint(x, y)
|
||||
|
||||
|
||||
def get_quarter(a, b, c):
|
||||
quarter = None
|
||||
if b.y() <= a.y() and b.x() < c.x():
|
||||
quarter = 0
|
||||
elif b.y() < a.y() and b.x() >= c.x():
|
||||
quarter = 1
|
||||
elif b.y() >= a.y() and b.x() > c.x():
|
||||
quarter = 2
|
||||
elif b.y() >= a.y() and b.x() <= c.x():
|
||||
quarter = 3
|
||||
return quarter
|
||||
|
||||
|
||||
def get_point_on_line(angle, ray):
|
||||
x = 50 + ray * math.cos(float(angle))
|
||||
y = 50 + ray * math.sin(float(angle))
|
||||
return QtCore.QPoint(x, y)
|
||||
|
||||
|
||||
def get_angle_c(a, b, c):
|
||||
return math.degrees(math.atan(distance(a, b) / distance(a, c)))
|
||||
|
||||
|
||||
def get_absolute_angle_c(a, b, c):
|
||||
quarter = get_quarter(a, b, c)
|
||||
try:
|
||||
angle_c = get_angle_c(a, b, c)
|
||||
except ZeroDivisionError:
|
||||
return 360 - (90 * quarter)
|
||||
|
||||
if quarter == 0:
|
||||
return round(180.0 + angle_c, 1)
|
||||
elif quarter == 1:
|
||||
return round(270.0 + (90 - angle_c), 1)
|
||||
elif quarter == 2:
|
||||
return round(angle_c, 1)
|
||||
elif quarter == 3:
|
||||
return math.fabs(round(90.0 + (90 - angle_c), 1))
|
||||
|
||||
|
||||
def proportional_rect(rect, percent=None):
|
||||
""" return a scaled rect with a percentage """
|
||||
factor = float(percent) / 100
|
||||
width = rect.width() * factor
|
||||
height = rect.height() * factor
|
||||
left = rect.left() + round((rect.width() - width) / 2)
|
||||
top = rect.top() + round((rect.height() - height) / 2)
|
||||
return QtCore.QRect(left, top, width, height)
|
||||
|
||||
|
||||
def resize_rect_with_reference(rect, in_reference_rect, out_reference_rect):
|
||||
"""
|
||||
__________________________________ B
|
||||
| ________________ A |
|
||||
| | | |
|
||||
| |_______________| |
|
||||
| |
|
||||
|________________________________|
|
||||
__________________________ C
|
||||
| ? |
|
||||
| |
|
||||
|________________________|
|
||||
A = rect given
|
||||
B = in_reference_rect
|
||||
C = out_reference_rect
|
||||
the function process the fourth rect,
|
||||
it scale the A rect using the B, C scales as reference
|
||||
"""
|
||||
|
||||
left = relative(
|
||||
value=rect.left(),
|
||||
in_min=in_reference_rect.left(),
|
||||
in_max=in_reference_rect.right(),
|
||||
out_min=out_reference_rect.left(),
|
||||
out_max=out_reference_rect.right())
|
||||
top = relative(
|
||||
value=rect.top(),
|
||||
in_min=in_reference_rect.top(),
|
||||
in_max=in_reference_rect.bottom(),
|
||||
out_min=out_reference_rect.top(),
|
||||
out_max=out_reference_rect.bottom())
|
||||
right = relative(
|
||||
value=rect.right(),
|
||||
in_min=in_reference_rect.left(),
|
||||
in_max=in_reference_rect.right(),
|
||||
out_min=out_reference_rect.left(),
|
||||
out_max=out_reference_rect.right())
|
||||
bottom = relative(
|
||||
value=rect.bottom(),
|
||||
in_min=in_reference_rect.top(),
|
||||
in_max=in_reference_rect.bottom(),
|
||||
out_min=out_reference_rect.top(),
|
||||
out_max=out_reference_rect.bottom())
|
||||
rect.setCoords(left, top, right, bottom)
|
||||
|
||||
|
||||
def resize_rect_with_direction(rect, cursor, direction, force_square=False):
|
||||
if direction == 'top_left':
|
||||
if cursor.x() < rect.right() and cursor.y() < rect.bottom():
|
||||
rect.setTopLeft(cursor)
|
||||
if force_square:
|
||||
left = rect.right() - rect.height()
|
||||
rect.setLeft(left)
|
||||
|
||||
elif direction == 'bottom_left':
|
||||
if cursor.x() < rect.right() and cursor.y() > rect.top():
|
||||
rect.setBottomLeft(cursor)
|
||||
if force_square:
|
||||
rect.setHeight(rect.width())
|
||||
|
||||
elif direction == 'top_right':
|
||||
if cursor.x() > rect.left() and cursor.y() < rect.bottom():
|
||||
rect.setTopRight(cursor)
|
||||
if force_square:
|
||||
rect.setWidth(rect.height())
|
||||
|
||||
elif direction == 'bottom_right':
|
||||
if cursor.x() > rect.left() and cursor.y() > rect.top():
|
||||
rect.setBottomRight(cursor)
|
||||
if force_square:
|
||||
rect.setHeight(rect.width())
|
||||
|
||||
elif direction == 'left':
|
||||
if cursor.x() < rect.right():
|
||||
rect.setLeft(cursor.x())
|
||||
if force_square:
|
||||
rect.setHeight(rect.width())
|
||||
|
||||
elif direction == 'right':
|
||||
if cursor.x() > rect.left():
|
||||
rect.setRight(cursor.x())
|
||||
if force_square:
|
||||
rect.setHeight(rect.width())
|
||||
|
||||
elif direction == 'top':
|
||||
if cursor.y() < rect.bottom():
|
||||
rect.setTop(cursor.y())
|
||||
if force_square:
|
||||
rect.setWidth(rect.height())
|
||||
|
||||
elif direction == 'bottom':
|
||||
if cursor.y() > rect.top():
|
||||
rect.setBottom(cursor.y())
|
||||
if force_square:
|
||||
rect.setWidth(rect.height())
|
||||
|
||||
|
||||
class Transform:
|
||||
def __init__(self):
|
||||
self.snap = None
|
||||
self.direction = None
|
||||
self.rect = None
|
||||
self.mode = None
|
||||
self.square = False
|
||||
self.reference_x = None
|
||||
self.reference_y = None
|
||||
self.reference_rect = None
|
||||
|
||||
def set_rect(self, rect):
|
||||
if not isinstance(rect, QtCore.QRect):
|
||||
raise ValueError()
|
||||
self.rect = rect
|
||||
if rect is None:
|
||||
self.reference_x = None
|
||||
self.reference_y = None
|
||||
return
|
||||
|
||||
def set_reference_point(self, cursor):
|
||||
self.reference_x = cursor.x() - self.rect.left()
|
||||
self.reference_y = cursor.y() - self.rect.top()
|
||||
|
||||
def resize(self, rects, cursor):
|
||||
if self.snap is not None:
|
||||
x, y = snap(cursor.x(), cursor.y(), self.snap)
|
||||
cursor.setX(x)
|
||||
cursor.setY(y)
|
||||
resize_rect_with_direction(
|
||||
self.rect, cursor, self.direction, force_square=self.square)
|
||||
self.apply_relative_transformation(rects)
|
||||
|
||||
def apply_relative_transformation(self, rects):
|
||||
for rect in rects:
|
||||
resize_rect_with_reference(
|
||||
rect, self.reference_rect, self.rect)
|
||||
|
||||
self.reference_rect = QtCore.QRect(
|
||||
self.rect.topLeft(), self.rect.size())
|
||||
|
||||
def move(self, rects, cursor):
|
||||
x = cursor.x() - self.reference_x
|
||||
y = cursor.y() - self.reference_y
|
||||
if self.snap is not None:
|
||||
x, y = snap(x, y, self.snap)
|
||||
self.apply_topleft(rects, x, y)
|
||||
|
||||
def shift(self, rects, offset):
|
||||
x, y = offset
|
||||
if self.snap is not None:
|
||||
x *= self.snap[0]
|
||||
y *= self.snap[1]
|
||||
x = self.rect.left() + x
|
||||
y = self.rect.top() + y
|
||||
if self.snap:
|
||||
x, y = snap(x, y, self.snap)
|
||||
self.apply_topleft(rects, x, y)
|
||||
|
||||
def apply_topleft(self, rects, x, y):
|
||||
width = self.rect.width()
|
||||
height = self.rect.height()
|
||||
self.rect.setTopLeft(QtCore.QPoint(x, y))
|
||||
self.rect.setWidth(width)
|
||||
self.rect.setHeight(height)
|
||||
self.apply_relative_transformation(rects)
|
||||
|
||||
|
||||
def snap(x, y, snap):
|
||||
x = snap[0] * round(x / snap[0])
|
||||
y = snap[1] * round(y / snap[1])
|
||||
return x, y
|
||||
|
||||
|
||||
def get_combined_rects(rects):
|
||||
"""
|
||||
this function analyse list of rects and return
|
||||
a rect with the smaller top and left and highest right and bottom
|
||||
__________________________________ ?
|
||||
| | A |
|
||||
| | |
|
||||
|______________| ___________| B
|
||||
| | |
|
||||
|_____________________|__________|
|
||||
"""
|
||||
if not rects:
|
||||
return None
|
||||
l = min(rect.left() for rect in rects)
|
||||
t = min(rect.top() for rect in rects)
|
||||
r = max(rect.right() for rect in rects)
|
||||
b = max(rect.bottom() for rect in rects)
|
||||
|
||||
return QtCore.QRect(l, t, r-l, b-t)
|
||||
|
||||
|
||||
def rect_symmetry(rect, point, horizontal=True):
|
||||
"""
|
||||
______ rect ______ result
|
||||
| | | |
|
||||
|______| |______|
|
||||
. point
|
||||
|
||||
Compute symmetry for a rect from a given point and axis
|
||||
"""
|
||||
center = rect.center()
|
||||
if horizontal:
|
||||
dist = (center.x() - point.x()) * 2
|
||||
vector = QtCore.QPoint(dist, 0)
|
||||
else:
|
||||
dist = (center.y() - point.y()) * 2
|
||||
vector = QtCore.QPoint(0, dist)
|
||||
center = rect.center() - vector
|
||||
rect.moveCenter(center)
|
||||
return rect
|
||||
|
||||
|
||||
def split_line(point1, point2, step_number):
|
||||
"""
|
||||
split a line on given number of points.
|
||||
"""
|
||||
if step_number <= 1:
|
||||
return [point2]
|
||||
x_values = split_range(point1.x(), point2.x(), step_number)
|
||||
y_values = split_range(point1.y(), point2.y(), step_number)
|
||||
return [QtCore.QPoint(x, y) for x, y in zip(x_values, y_values)]
|
||||
|
||||
|
||||
def split_range(input_, output, step_number):
|
||||
difference = output - input_
|
||||
step = difference / float(step_number - 1)
|
||||
return [int(input_ + (step * i)) for i in range(step_number)]
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
assert split_range(0, 10, 11) == [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
40
Scripts/Animation/dwpicker/dwpicker/hotkeys.py
Normal file
@ -0,0 +1,40 @@
|
||||
|
||||
from maya import cmds
|
||||
from dwpicker.optionvar import save_optionvar, DEFAULT_HOTKEYS, OPTIONVARS
|
||||
|
||||
|
||||
def get_hotkeys_config():
|
||||
# For config retro compatibility, we always ensure that default value is
|
||||
# set in case of new shortcut added in the system. We also ensure that old
|
||||
# shortcut is going to be removed from the config.
|
||||
default = build_config_from_string(OPTIONVARS[DEFAULT_HOTKEYS])
|
||||
saved = build_config_from_string(cmds.optionVar(query=DEFAULT_HOTKEYS))
|
||||
for key in default.keys():
|
||||
if key in saved:
|
||||
default[key] = saved[key]
|
||||
return default
|
||||
|
||||
|
||||
def build_config_from_string(value):
|
||||
config = {}
|
||||
for entry in value.split(';'):
|
||||
function_name = entry.split('=')[0]
|
||||
enabled = bool(int(entry.split('=')[-1].split(',')[-1]))
|
||||
key_sequence = entry.split('=')[-1].split(',')[0]
|
||||
config[function_name] = {
|
||||
'enabled': enabled if key_sequence != 'None' else False,
|
||||
'key_sequence': None if key_sequence == 'None' else key_sequence}
|
||||
return config
|
||||
|
||||
|
||||
def set_hotkey_config(function, key_sequence, enabled):
|
||||
config = get_hotkeys_config()
|
||||
config[function] = {'enabled': enabled, 'key_sequence': key_sequence}
|
||||
save_hotkey_config(config)
|
||||
|
||||
|
||||
def save_hotkey_config(config):
|
||||
value = ';'.join([
|
||||
'{0}={1},{2}'.format(function, data['key_sequence'], int(data['enabled']))
|
||||
for function, data in config.items()])
|
||||
save_optionvar(DEFAULT_HOTKEYS, value)
|
194
Scripts/Animation/dwpicker/dwpicker/hotkeyseditor.py
Normal file
@ -0,0 +1,194 @@
|
||||
|
||||
from PySide2 import QtWidgets, QtCore, QtGui
|
||||
from dwpicker.hotkeys import get_hotkeys_config, save_hotkey_config
|
||||
|
||||
|
||||
class HotkeysEditor(QtWidgets.QWidget):
|
||||
hotkey_changed = QtCore.Signal()
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(HotkeysEditor, self).__init__(parent)
|
||||
self.model = HotkeysTableModel()
|
||||
self.model.hotkey_changed.connect(self.hotkey_changed.emit)
|
||||
self.table = QtWidgets.QTableView()
|
||||
self.table.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
|
||||
self.table.setModel(self.model)
|
||||
self.table.selectionModel().selectionChanged.connect(
|
||||
self.selection_changed)
|
||||
self.hotkey_editor = HotkeyEditor()
|
||||
self.hotkey_editor.hotkey_edited.connect(self.update_hotkeys)
|
||||
self.clear = QtWidgets.QPushButton('Clear')
|
||||
self.clear.released.connect(self.do_clear)
|
||||
|
||||
hotkey_layout = QtWidgets.QVBoxLayout()
|
||||
hotkey_layout.setContentsMargins(0, 0, 0, 0)
|
||||
hotkey_layout.addWidget(self.hotkey_editor)
|
||||
hotkey_layout.addWidget(self.clear)
|
||||
hotkey_layout.addStretch(1)
|
||||
|
||||
layout = QtWidgets.QHBoxLayout(self)
|
||||
layout.addWidget(self.table)
|
||||
layout.addLayout(hotkey_layout)
|
||||
|
||||
def do_clear(self):
|
||||
self.hotkey_editor.clear_values()
|
||||
self.update_hotkeys()
|
||||
self.hotkey_changed.emit()
|
||||
|
||||
def update_hotkeys(self):
|
||||
self.model.set_keysequence(
|
||||
self.hotkey_editor.function_name,
|
||||
self.hotkey_editor.key_sequence())
|
||||
|
||||
def selection_changed(self, *_):
|
||||
indexes = self.table.selectionModel().selectedIndexes()
|
||||
if not indexes:
|
||||
self.hotkey_editor.clear()
|
||||
return
|
||||
row = indexes[0].row()
|
||||
function_name = sorted(list(self.model.config))[row]
|
||||
data = self.model.config[function_name]
|
||||
self.hotkey_editor.set_key_sequence(
|
||||
function_name, data['key_sequence'])
|
||||
|
||||
|
||||
class HotkeyEditor(QtWidgets.QWidget):
|
||||
hotkey_edited = QtCore.Signal()
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(HotkeyEditor, self).__init__(parent)
|
||||
self.function_name = None
|
||||
self.function_name_label = QtWidgets.QLabel()
|
||||
self.alt = QtWidgets.QCheckBox('Alt')
|
||||
self.alt.released.connect(self.emit_hotkey_edited)
|
||||
self.ctrl = QtWidgets.QCheckBox('Ctrl')
|
||||
self.ctrl.released.connect(self.emit_hotkey_edited)
|
||||
self.shift = QtWidgets.QCheckBox('Shift')
|
||||
self.shift.released.connect(self.emit_hotkey_edited)
|
||||
self.string = KeyField()
|
||||
self.string.changed.connect(self.hotkey_edited.emit)
|
||||
|
||||
modifiers = QtWidgets.QHBoxLayout()
|
||||
modifiers.addWidget(self.alt)
|
||||
modifiers.addWidget(self.ctrl)
|
||||
modifiers.addWidget(self.shift)
|
||||
|
||||
layout = QtWidgets.QVBoxLayout(self)
|
||||
layout.addWidget(self.function_name_label)
|
||||
layout.addLayout(modifiers)
|
||||
layout.addWidget(self.string)
|
||||
|
||||
def clear(self):
|
||||
self.function_name = None
|
||||
self.clear_values()
|
||||
self.function_name_label.setText('')
|
||||
|
||||
def clear_values(self):
|
||||
self.ctrl.setChecked(False)
|
||||
self.alt.setChecked(False)
|
||||
self.shift.setChecked(False)
|
||||
self.string.setText('')
|
||||
|
||||
def emit_hotkey_edited(self, *_):
|
||||
self.hotkey_edited.emit()
|
||||
|
||||
def key_sequence(self):
|
||||
if not self.string.text():
|
||||
return None
|
||||
sequence = []
|
||||
if self.ctrl.isChecked():
|
||||
sequence.append('CTRL')
|
||||
if self.alt.isChecked():
|
||||
sequence.append('ALT')
|
||||
if self.shift.isChecked():
|
||||
sequence.append('SHIFT')
|
||||
sequence.append(self.string.text())
|
||||
return '+'.join(sequence)
|
||||
|
||||
def set_key_sequence(self, function_name, key_sequence):
|
||||
self.function_name = function_name
|
||||
self.function_name_label.setText(function_name.title())
|
||||
if key_sequence is None:
|
||||
self.ctrl.setChecked(False)
|
||||
self.alt.setChecked(False)
|
||||
self.shift.setChecked(False)
|
||||
self.string.setText('')
|
||||
return
|
||||
self.ctrl.setChecked('ctrl' in key_sequence.lower())
|
||||
self.alt.setChecked('alt' in key_sequence.lower())
|
||||
self.shift.setChecked('shift' in key_sequence.lower())
|
||||
self.string.setText(key_sequence.split('+')[-1])
|
||||
|
||||
|
||||
class KeyField(QtWidgets.QLineEdit):
|
||||
changed = QtCore.Signal()
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(KeyField, self).__init__(parent)
|
||||
self.setReadOnly(True)
|
||||
|
||||
def keyPressEvent(self, event):
|
||||
if event.key() == QtCore.Qt.Key_Shift:
|
||||
return
|
||||
self.setText(QtGui.QKeySequence(event.key()).toString())
|
||||
self.changed.emit()
|
||||
|
||||
|
||||
class HotkeysTableModel(QtCore.QAbstractTableModel):
|
||||
HEADERS = 'Function', 'Key sequence'
|
||||
hotkey_changed = QtCore.Signal()
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(HotkeysTableModel, self).__init__(parent)
|
||||
self.config = get_hotkeys_config()
|
||||
|
||||
def rowCount(self, *_):
|
||||
return len(self.config)
|
||||
|
||||
def columnCount(self, *_):
|
||||
return len(self.HEADERS)
|
||||
|
||||
def set_keysequence(self, function_name, key_sequence):
|
||||
self.layoutAboutToBeChanged.emit()
|
||||
self.config[function_name]['key_sequence'] = key_sequence
|
||||
if key_sequence is None:
|
||||
self.config[function_name]['enabled'] = False
|
||||
save_hotkey_config(self.config)
|
||||
self.layoutChanged.emit()
|
||||
self.hotkey_changed.emit()
|
||||
|
||||
def flags(self, index):
|
||||
flags = QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable
|
||||
if index.column() == 0:
|
||||
flags |= QtCore.Qt.ItemIsUserCheckable
|
||||
return flags
|
||||
|
||||
def headerData(self, section, orientation, role):
|
||||
if orientation == QtCore.Qt.Vertical or role != QtCore.Qt.DisplayRole:
|
||||
return
|
||||
return self.HEADERS[section]
|
||||
|
||||
def setData(self, index, value, role):
|
||||
|
||||
if role != QtCore.Qt.CheckStateRole or index.column() != 0:
|
||||
return
|
||||
function = sorted(list(self.config))[index.row()]
|
||||
self.config[function]['enabled'] = value
|
||||
save_hotkey_config(self.config)
|
||||
self.hotkey_changed.emit()
|
||||
return True
|
||||
|
||||
def data(self, index, role):
|
||||
if not index.isValid():
|
||||
return
|
||||
|
||||
function = sorted(list(self.config))[index.row()]
|
||||
data = self.config[function]
|
||||
if role == QtCore.Qt.DisplayRole:
|
||||
if index.column() == 0:
|
||||
return function.title()
|
||||
else:
|
||||
return data['key_sequence']
|
||||
if role == QtCore.Qt.CheckStateRole and index.column() == 0:
|
||||
return (
|
||||
QtCore.Qt.Checked if data['enabled'] else QtCore.Qt.Unchecked)
|
BIN
Scripts/Animation/dwpicker/dwpicker/icons/addbg.png
Normal file
After Width: | Height: | Size: 3.5 KiB |
BIN
Scripts/Animation/dwpicker/dwpicker/icons/addbutton.png
Normal file
After Width: | Height: | Size: 5.4 KiB |
BIN
Scripts/Animation/dwpicker/dwpicker/icons/addtext.png
Normal file
After Width: | Height: | Size: 4.3 KiB |
BIN
Scripts/Animation/dwpicker/dwpicker/icons/align_bottom.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
Scripts/Animation/dwpicker/dwpicker/icons/align_h_center.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
Scripts/Animation/dwpicker/dwpicker/icons/align_left.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
Scripts/Animation/dwpicker/dwpicker/icons/align_right.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
Scripts/Animation/dwpicker/dwpicker/icons/align_top.png
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
Scripts/Animation/dwpicker/dwpicker/icons/align_v_center.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
Scripts/Animation/dwpicker/dwpicker/icons/arrange_h.png
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
Scripts/Animation/dwpicker/dwpicker/icons/arrange_v.png
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
Scripts/Animation/dwpicker/dwpicker/icons/center.png
Normal file
After Width: | Height: | Size: 3.5 KiB |
BIN
Scripts/Animation/dwpicker/dwpicker/icons/copy.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
Scripts/Animation/dwpicker/dwpicker/icons/copy_settings.png
Normal file
After Width: | Height: | Size: 2.4 KiB |
BIN
Scripts/Animation/dwpicker/dwpicker/icons/delete.png
Normal file
After Width: | Height: | Size: 2.2 KiB |
BIN
Scripts/Animation/dwpicker/dwpicker/icons/delete2.png
Normal file
After Width: | Height: | Size: 5.4 KiB |
BIN
Scripts/Animation/dwpicker/dwpicker/icons/edit.png
Normal file
After Width: | Height: | Size: 784 B |
BIN
Scripts/Animation/dwpicker/dwpicker/icons/edit2.png
Normal file
After Width: | Height: | Size: 731 B |
BIN
Scripts/Animation/dwpicker/dwpicker/icons/frame.png
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
Scripts/Animation/dwpicker/dwpicker/icons/h_symmetry.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
Scripts/Animation/dwpicker/dwpicker/icons/link.png
Normal file
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 22 KiB |
BIN
Scripts/Animation/dwpicker/dwpicker/icons/manager-delete.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
Scripts/Animation/dwpicker/dwpicker/icons/manager-edit.png
Normal file
After Width: | Height: | Size: 963 B |
BIN
Scripts/Animation/dwpicker/dwpicker/icons/manager-export.png
Normal file
After Width: | Height: | Size: 6.5 KiB |
BIN
Scripts/Animation/dwpicker/dwpicker/icons/manager-import.png
Normal file
After Width: | Height: | Size: 6.3 KiB |
BIN
Scripts/Animation/dwpicker/dwpicker/icons/manager-new.png
Normal file
After Width: | Height: | Size: 998 B |
BIN
Scripts/Animation/dwpicker/dwpicker/icons/movedown.png
Normal file
After Width: | Height: | Size: 3.6 KiB |
BIN
Scripts/Animation/dwpicker/dwpicker/icons/moveup.png
Normal file
After Width: | Height: | Size: 3.6 KiB |
BIN
Scripts/Animation/dwpicker/dwpicker/icons/new.png
Normal file
After Width: | Height: | Size: 4.5 KiB |
BIN
Scripts/Animation/dwpicker/dwpicker/icons/onbottom.png
Normal file
After Width: | Height: | Size: 3.6 KiB |
BIN
Scripts/Animation/dwpicker/dwpicker/icons/ontop.png
Normal file
After Width: | Height: | Size: 3.5 KiB |
BIN
Scripts/Animation/dwpicker/dwpicker/icons/open.png
Normal file
After Width: | Height: | Size: 24 KiB |
BIN
Scripts/Animation/dwpicker/dwpicker/icons/paste.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
Scripts/Animation/dwpicker/dwpicker/icons/paste_settings.png
Normal file
After Width: | Height: | Size: 2.3 KiB |
BIN
Scripts/Animation/dwpicker/dwpicker/icons/picker.png
Normal file
After Width: | Height: | Size: 567 B |
BIN
Scripts/Animation/dwpicker/dwpicker/icons/play.png
Normal file
After Width: | Height: | Size: 1020 B |
BIN
Scripts/Animation/dwpicker/dwpicker/icons/redo.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
Scripts/Animation/dwpicker/dwpicker/icons/reload.png
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
Scripts/Animation/dwpicker/dwpicker/icons/save.png
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
Scripts/Animation/dwpicker/dwpicker/icons/search.png
Normal file
After Width: | Height: | Size: 21 KiB |
BIN
Scripts/Animation/dwpicker/dwpicker/icons/snap.png
Normal file
After Width: | Height: | Size: 4.3 KiB |
BIN
Scripts/Animation/dwpicker/dwpicker/icons/touch.png
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
Scripts/Animation/dwpicker/dwpicker/icons/undo.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
Scripts/Animation/dwpicker/dwpicker/icons/unlink.png
Normal file
After Width: | Height: | Size: 478 B |
BIN
Scripts/Animation/dwpicker/dwpicker/icons/v_symmetry.png
Normal file
After Width: | Height: | Size: 16 KiB |
@ -0,0 +1 @@
|
||||
from dwpicker.ingest.animschool.converter import convert
|
@ -0,0 +1,122 @@
|
||||
import json
|
||||
import os
|
||||
from PySide2 import QtGui
|
||||
|
||||
from dwpicker.templates import PICKER, BUTTON, BACKGROUND
|
||||
from dwpicker.ingest.animschool.parser import parse_animschool_picker, save_png
|
||||
|
||||
|
||||
def rgb_to_hex(r, g, b):
|
||||
return '#{r:02x}{g:02x}{b:02x}'.format(r=r, g=g, b=b)
|
||||
|
||||
|
||||
def _label_width(text):
|
||||
width = 0
|
||||
for letter in text:
|
||||
if letter == " ":
|
||||
width += 3
|
||||
elif letter.isupper():
|
||||
width += 7
|
||||
else:
|
||||
width += 6
|
||||
return width
|
||||
|
||||
|
||||
def convert_to_picker_button(button):
|
||||
if len(button['label']):
|
||||
button['w'] = max((button['w'], _label_width(button['label'])))
|
||||
delta = {
|
||||
'text.content': button['label'],
|
||||
'shape.left': button['x'] - (button['w'] // 2),
|
||||
'shape.top': button['y'] - (button['h'] // 2),
|
||||
'shape.width': button['w'],
|
||||
'shape.height': button['h']}
|
||||
|
||||
if button['action'] == 'select':
|
||||
delta['action.targets'] = button['targets']
|
||||
if len(button['targets']) > 1:
|
||||
delta['shape'] = 'rounded_square' if button['label'] else 'round'
|
||||
delta['shape.cornersx'] = delta['shape.width'] / 10
|
||||
delta['shape.cornersy'] = delta['shape.height'] / 10
|
||||
|
||||
else:
|
||||
delta['action.left.language'] = button['lang']
|
||||
delta['action.left.command'] = button['targets'][0]
|
||||
|
||||
delta['bgcolor.normal'] = rgb_to_hex(*button['bgcolor'])
|
||||
delta['text.color'] = rgb_to_hex(*button['txtcolor'])
|
||||
delta['border'] = button['action'] == 'command'
|
||||
delta['border'] = button['action'] == 'command'
|
||||
|
||||
picker_button = BUTTON.copy()
|
||||
picker_button.update(delta)
|
||||
return picker_button
|
||||
|
||||
|
||||
def frame_picker_buttons(picker):
|
||||
shapes = picker['shapes']
|
||||
offset_x = min(shape['shape.left'] for shape in shapes)
|
||||
offset_y = min(shape['shape.top'] for shape in shapes)
|
||||
offset = -min([offset_x, 0]), -min([offset_y, 0])
|
||||
|
||||
for shape in shapes:
|
||||
shape['shape.left'] += offset[0]
|
||||
shape['shape.top'] += offset[1]
|
||||
|
||||
|
||||
def fit_picker_to_content(picker):
|
||||
shapes = picker['shapes']
|
||||
width = max(s['shape.left'] + s['shape.width'] for s in shapes)
|
||||
height = max(s['shape.top'] + s['shape.height'] for s in shapes)
|
||||
picker['general']['width'] = int(width)
|
||||
picker['general']['height'] = int(height)
|
||||
|
||||
|
||||
def image_to_background_shape(imagepath):
|
||||
shape = BACKGROUND.copy()
|
||||
shape['image.path'] = imagepath
|
||||
image = QtGui.QImage(imagepath)
|
||||
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
|
||||
return shape
|
||||
|
||||
|
||||
def build_picker_from_pkr(title, buttons, imagepath, dst):
|
||||
picker = {
|
||||
'general': PICKER.copy(),
|
||||
'shapes': [convert_to_picker_button(b) for b in buttons]}
|
||||
picker['general']['name'] = title
|
||||
if imagepath:
|
||||
picker['shapes'].insert(0, image_to_background_shape(imagepath))
|
||||
frame_picker_buttons(picker)
|
||||
fit_picker_to_content(picker)
|
||||
with open(dst, "w") as f:
|
||||
json.dump(picker, f, indent=2)
|
||||
|
||||
|
||||
def convert(filepath, directory=None):
|
||||
directory = directory or os.path.dirname(filepath)
|
||||
title, buttons, png_data = parse_animschool_picker(filepath)
|
||||
picker_filename = os.path.splitext(os.path.basename(filepath))[0]
|
||||
png_path = unique_filename(directory, picker_filename, 'png')
|
||||
png_path = png_path if png_data else None
|
||||
dst = unique_filename(directory, picker_filename, 'json')
|
||||
if png_path:
|
||||
save_png(png_data, png_path)
|
||||
build_picker_from_pkr(title, buttons, png_path, dst)
|
||||
return dst
|
||||
|
||||
|
||||
def unique_filename(directory, filename, extension):
|
||||
filepath = os.path.join(directory, filename) + '.' + extension
|
||||
i = 0
|
||||
while os.path.exists(filepath):
|
||||
filepath = '{base}.{index}.{extension}'.format(
|
||||
base=os.path.join(directory, filename),
|
||||
index=str(i).zfill(3),
|
||||
extension=extension)
|
||||
i += 1
|
||||
return filepath
|
275
Scripts/Animation/dwpicker/dwpicker/ingest/animschool/parser.py
Normal file
@ -0,0 +1,275 @@
|
||||
"""
|
||||
Module to parse and extract data from AnimSchool picker file.
|
||||
This works for Animschool until 2021 release.
|
||||
|
||||
PKR file structure description:
|
||||
|
||||
-- header --
|
||||
4 bytes (singed int): Picker Version.
|
||||
4 bytes (singed int): Title number (x) of bytes length.
|
||||
x bytes (hex text): Title.
|
||||
|
||||
-- PNG data --
|
||||
...
|
||||
|
||||
--- buttons ---
|
||||
4 bytes (singed int): Number of buttons
|
||||
|
||||
-- Button array --
|
||||
for _ in range(number_of_buttons)
|
||||
- 4 bytes (singed int): Button id as signed int.
|
||||
- 4 bytes (singed int): Center position X.
|
||||
- 4 bytes (singed int): Center position Y.
|
||||
- 4 bytes (singed int):
|
||||
Size for old AnimSchool versions (4 and older)
|
||||
This is still there but unused in 2021 version.
|
||||
- 4 bytes (singed int): Width.
|
||||
- 4 bytes (singed int): Height.
|
||||
- 4 bytes (bool): Button type.
|
||||
True = Command button.
|
||||
False = Selection button.
|
||||
- 4 bytes (bool): Languages used for command button.
|
||||
True = Python.
|
||||
False = Mel.
|
||||
- 4 bytes (hex __RRGGBB): Background color.
|
||||
- 4 bytes (hex __RRGGBB): Text color.
|
||||
- 4 bytes (singed int): Label number (x) of bytes length.
|
||||
- x bytes (hexa text): Label.
|
||||
- 4 bytes (singed int): Number (x) of targets.
|
||||
This is automatically 1 for command button
|
||||
|
||||
for _ in range(number_of_targets):
|
||||
- 4 bytes (singed int): Target name number (x) of bytes length.
|
||||
- x bytes (hexa text): Target name.
|
||||
|
||||
|
||||
The script export pkr data in 3 different objects:
|
||||
|
||||
PNG data:
|
||||
This is a one to one of the png binari data encapsulated in the pkr
|
||||
file.
|
||||
|
||||
Title:
|
||||
As simple string
|
||||
|
||||
Buttons:
|
||||
Translate the binari buttons as readable python dict!
|
||||
{
|
||||
"id": int,
|
||||
"x": int,
|
||||
"y": int,
|
||||
"w": int,
|
||||
"h": int,
|
||||
"action": str: "select" | "command",
|
||||
"lang": str: "mel" | "python",
|
||||
"bgcolor": [r:int, g:int, b:int],
|
||||
"txtcolor": [r:int, g:int, b:int],
|
||||
"label": str,
|
||||
"targets": List[str]
|
||||
}
|
||||
|
||||
"""
|
||||
|
||||
from binascii import hexlify, unhexlify
|
||||
import json
|
||||
import os
|
||||
|
||||
PNG_HEADER = b'89504e470d0a1a0a'
|
||||
PNG_FOOTER = b'ae426082'
|
||||
|
||||
|
||||
def split_data(content, number_of_bytes=4):
|
||||
if isinstance(number_of_bytes, bytes):
|
||||
number_of_bytes = int(number_of_bytes, 16)
|
||||
return content[:number_of_bytes * 2], content[number_of_bytes * 2:]
|
||||
|
||||
|
||||
def bytes_to_string(stringdata):
|
||||
return ''.join(
|
||||
b.decode('cp1252')
|
||||
for b in unhexlify(stringdata).split(b'\x00'))
|
||||
|
||||
|
||||
def bytes_to_int(i):
|
||||
if i[:4] == b'00' * 2:
|
||||
return int(i, 16)
|
||||
elif i[:4] == b'ff' * 2:
|
||||
return -65535 + int(i[-4:], 16)
|
||||
raise Exception('Count not interpret data as int')
|
||||
|
||||
|
||||
def print_(data, max_bytes=64):
|
||||
string = repr(data)[2:-1][:max_bytes * 2]
|
||||
beautified = ''
|
||||
for i in range(len(string)):
|
||||
beautified += string[i].upper()
|
||||
if i % 2:
|
||||
beautified += ' '
|
||||
if (i + 1) % 16 == 0 and i != 0:
|
||||
beautified += '\n'
|
||||
print(beautified)
|
||||
|
||||
|
||||
def bytes_to_rgb(data):
|
||||
data = int(data, 16)
|
||||
b = data & 255
|
||||
g = (data >> 8) & 255
|
||||
r = (data >> 16) & 255
|
||||
return r, g, b
|
||||
|
||||
|
||||
def extract_string(data):
|
||||
string_size, data = split_data(data)
|
||||
string, data = split_data(data, string_size)
|
||||
string = bytes_to_string(string)
|
||||
return string, data
|
||||
|
||||
|
||||
def extract_png_data(data):
|
||||
png_len_size, data = split_data(data)
|
||||
png_len_size = bytes_to_int(png_len_size)
|
||||
|
||||
if not png_len_size:
|
||||
return None, data
|
||||
|
||||
png_len, data = split_data(data, png_len_size)
|
||||
png_len = int(bytes_to_string(png_len)) # lol
|
||||
if png_len == 0:
|
||||
_, data = split_data(data, 4) # remove some leftover data
|
||||
return None, data
|
||||
|
||||
_, data = split_data(data, 4)
|
||||
png_end = int((data.find(PNG_FOOTER) + len(PNG_FOOTER)) / 2)
|
||||
return split_data(data, png_end)
|
||||
|
||||
|
||||
def extract_button_targets(data):
|
||||
number_of_targets, data = split_data(data)
|
||||
targets = []
|
||||
number_of_targets = int(number_of_targets, 16)
|
||||
for _ in range(number_of_targets):
|
||||
target_name, data = extract_string(data)
|
||||
targets.append(target_name)
|
||||
return targets, data
|
||||
|
||||
|
||||
def extract_button_data(data, version=5, verbose=True):
|
||||
button_id, data = split_data(data)
|
||||
button_id = bytes_to_int(button_id)
|
||||
if verbose:
|
||||
print('Button #{button_id}'.format(button_id=button_id))
|
||||
x, data = split_data(data)
|
||||
x = bytes_to_int(x)
|
||||
y, data = split_data(data)
|
||||
y = bytes_to_int(y)
|
||||
old_height, data = split_data(data)
|
||||
if version > 4:
|
||||
width, data = split_data(data)
|
||||
width = bytes_to_int(width)
|
||||
height, data = split_data(data)
|
||||
height = bytes_to_int(height)
|
||||
else:
|
||||
width, height = bytes_to_int(old_height), bytes_to_int(old_height)
|
||||
action, data = split_data(data)
|
||||
action = bytes_to_int(action)
|
||||
assert action in [0, 1]
|
||||
action = 'command' if action else 'select'
|
||||
lang, data = split_data(data)
|
||||
lang = bytes_to_int(lang)
|
||||
assert lang in [0, 1]
|
||||
lang = 'python' if lang else 'mel'
|
||||
bgcolor, data = split_data(data)
|
||||
bgcolor = bytes_to_rgb(bgcolor)
|
||||
txtcolor, data = split_data(data)
|
||||
txtcolor = bytes_to_rgb(txtcolor)
|
||||
label_size, data = split_data(data)
|
||||
if label_size == b'ff' * 4:
|
||||
label = ''
|
||||
else:
|
||||
label, data = split_data(data, label_size)
|
||||
label = bytes_to_string(label)
|
||||
targets, data = extract_button_targets(data)
|
||||
button = dict(
|
||||
id=button_id, x=x, y=y, w=width, h=height, action=action,
|
||||
lang=lang, bgcolor=bgcolor, txtcolor=txtcolor, label=label,
|
||||
targets=targets)
|
||||
return button, data
|
||||
|
||||
|
||||
def parse_animschool_picker(picker_path, verbose=False):
|
||||
with open(picker_path, 'rb') as file:
|
||||
data = hexlify(file.read())
|
||||
|
||||
# Get version
|
||||
version, data = split_data(data)
|
||||
version = bytes_to_int(version)
|
||||
print("this picker is build with AnimSchool v" + str(version))
|
||||
|
||||
# Get title
|
||||
title, data = extract_string(data)
|
||||
if verbose:
|
||||
print('Title: "{title}"'.format(title=title))
|
||||
|
||||
# Extract PNG
|
||||
png_data, data = extract_png_data(data)
|
||||
if verbose and png_data:
|
||||
print('PNG data found')
|
||||
|
||||
# Get number of buttons
|
||||
number_of_buttons, data = split_data(data)
|
||||
number_of_buttons = int(number_of_buttons, 16)
|
||||
if verbose:
|
||||
print('Number of buttons: "{num}"'.format(num=number_of_buttons))
|
||||
|
||||
# Parse buttons one by one:
|
||||
buttons = []
|
||||
while data:
|
||||
button, data = extract_button_data(data, version, verbose)
|
||||
buttons.append(button)
|
||||
|
||||
if len(buttons) != number_of_buttons:
|
||||
raise Exception('Parsing buttons went wrong.')
|
||||
|
||||
return title, buttons, png_data
|
||||
|
||||
|
||||
def extract_to_files(pkr_path, verbose=False):
|
||||
"""
|
||||
Extract data and image to .json and .png (if any) next to the .pkr
|
||||
"""
|
||||
title, buttons, png_data = parse_animschool_picker(pkr_path, verbose)
|
||||
# Save to json
|
||||
with open(pkr_path + '.json', 'w') as f:
|
||||
json.dump([title, buttons], f, indent=4)
|
||||
# Write PNG to file:
|
||||
png_path = pkr_path + '.png'
|
||||
if png_data and not os.path.exists(png_path):
|
||||
save_png(png_data, png_path)
|
||||
return title, buttons, png_data
|
||||
|
||||
|
||||
def save_png(png_data, dst):
|
||||
print('Saving PNG to "{dst}"'.format(dst=dst))
|
||||
with open(dst, 'wb') as f:
|
||||
f.write(unhexlify(png_data))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
arg = sys.argv[-1]
|
||||
if arg == 'dir':
|
||||
# Extract json and png for all .pkr files in current dir:
|
||||
import glob
|
||||
for pkr_path in glob.glob('./*.pkr'):
|
||||
print(os.path.basename(pkr_path))
|
||||
try:
|
||||
extract_to_files(pkr_path)
|
||||
except BaseException:
|
||||
print('Failed to parse {pkr_path}'.format(pkr_path=pkr_path))
|
||||
elif arg.endswith('.pkr') and os.path.exists(arg):
|
||||
# Extract given path to json and png:
|
||||
import pprint
|
||||
print('Parsing {arg}'.format(arg=arg))
|
||||
title, buttons, png_data = extract_to_files(arg, verbose=True)
|
||||
print(title)
|
||||
pprint.pprint(buttons)
|
213
Scripts/Animation/dwpicker/dwpicker/interactive.py
Normal file
@ -0,0 +1,213 @@
|
||||
|
||||
|
||||
from PySide2 import QtCore, QtGui
|
||||
|
||||
from dwpicker.geometry import (
|
||||
DIRECTIONS, get_topleft_rect, get_bottomleft_rect, get_topright_rect,
|
||||
get_bottomright_rect, get_left_side_rect, get_right_side_rect,
|
||||
get_top_side_rect, get_bottom_side_rect, proportional_rect)
|
||||
from dwpicker.languages import execute_code
|
||||
from dwpicker.painting import (
|
||||
draw_selection_square, draw_manipulator, get_hovered_path)
|
||||
from dwpicker.path import expand_path
|
||||
from dwpicker.selection import select_targets
|
||||
|
||||
|
||||
EXCECUTION_WARNING = """\
|
||||
Code execution failed for shape: "{name}"
|
||||
{error}.
|
||||
"""
|
||||
|
||||
|
||||
class SelectionSquare():
|
||||
def __init__(self):
|
||||
self.rect = None
|
||||
self.handeling = False
|
||||
|
||||
def clicked(self, cursor):
|
||||
self.handeling = True
|
||||
self.rect = QtCore.QRectF(cursor, cursor)
|
||||
|
||||
def handle(self, cursor):
|
||||
self.rect.setBottomRight(cursor)
|
||||
|
||||
def release(self):
|
||||
self.handeling = False
|
||||
self.rect = None
|
||||
|
||||
def intersects(self, rect):
|
||||
if not rect or not self.rect:
|
||||
return False
|
||||
return self.rect.intersects(rect)
|
||||
|
||||
def draw(self, painter):
|
||||
if self.rect is None:
|
||||
return
|
||||
draw_selection_square(painter, self.rect)
|
||||
|
||||
|
||||
class Manipulator():
|
||||
def __init__(self):
|
||||
self._rect = None
|
||||
self._is_hovered = False
|
||||
|
||||
self._tl_corner_rect = None
|
||||
self._bl_corner_rect = None
|
||||
self._tr_corner_rect = None
|
||||
self._br_corner_rect = None
|
||||
self._l_side_rect = None
|
||||
self._r_side_rect = None
|
||||
self._t_side_rect = None
|
||||
self._b_side_rect = None
|
||||
|
||||
self.hovered_path = None
|
||||
|
||||
@property
|
||||
def rect(self):
|
||||
return self._rect
|
||||
|
||||
def handler_rects(self):
|
||||
return [
|
||||
self._tl_corner_rect, self._bl_corner_rect, self._tr_corner_rect,
|
||||
self._br_corner_rect, self._l_side_rect, self._r_side_rect,
|
||||
self._t_side_rect, self._b_side_rect]
|
||||
|
||||
def get_direction(self, cursor):
|
||||
if self.rect is None:
|
||||
return None
|
||||
for i, rect in enumerate(self.handler_rects()):
|
||||
if rect.contains(cursor):
|
||||
return DIRECTIONS[i]
|
||||
|
||||
def hovered_rects(self, cursor):
|
||||
rects = []
|
||||
for rect in self.handler_rects() + [self.rect]:
|
||||
if not rect:
|
||||
continue
|
||||
if rect.contains(cursor):
|
||||
rects.append(rect)
|
||||
return rects
|
||||
|
||||
def set_rect(self, rect):
|
||||
self._rect = rect
|
||||
self.update_geometries()
|
||||
|
||||
def update_geometries(self):
|
||||
rect = self.rect
|
||||
self._tl_corner_rect = get_topleft_rect(rect) if rect else None
|
||||
self._bl_corner_rect = get_bottomleft_rect(rect) if rect else None
|
||||
self._tr_corner_rect = get_topright_rect(rect) if rect else None
|
||||
self._br_corner_rect = get_bottomright_rect(rect) if rect else None
|
||||
self._l_side_rect = get_left_side_rect(rect) if rect else None
|
||||
self._r_side_rect = get_right_side_rect(rect) if rect else None
|
||||
self._t_side_rect = get_top_side_rect(rect) if rect else None
|
||||
self._b_side_rect = get_bottom_side_rect(rect) if rect else None
|
||||
self.hovered_path = get_hovered_path(rect) if rect else None
|
||||
|
||||
def draw(self, painter, cursor):
|
||||
if self.rect is not None and all(self.handler_rects()):
|
||||
draw_manipulator(painter, self, cursor)
|
||||
|
||||
|
||||
def get_shape_rect_from_options(options):
|
||||
return QtCore.QRectF(
|
||||
options['shape.left'],
|
||||
options['shape.top'],
|
||||
options['shape.width'],
|
||||
options['shape.height'])
|
||||
|
||||
|
||||
class Shape():
|
||||
def __init__(self, options):
|
||||
self.hovered = False
|
||||
self.clicked = False
|
||||
self.selected = False
|
||||
self.options = options
|
||||
self.rect = get_shape_rect_from_options(options)
|
||||
self.pixmap = None
|
||||
self.image_rect = None
|
||||
self.synchronize_image()
|
||||
|
||||
def set_hovered(self, cursor):
|
||||
self.hovered = self.rect.contains(cursor)
|
||||
|
||||
def set_clicked(self, cursor):
|
||||
self.clicked = self.rect.contains(cursor)
|
||||
|
||||
def release(self, cursor):
|
||||
self.clicked = False
|
||||
self.hovered = self.rect.contains(cursor)
|
||||
|
||||
def synchronize_rect(self):
|
||||
self.options['shape.left'] = self.rect.left()
|
||||
self.options['shape.top'] = self.rect.top()
|
||||
self.options['shape.width'] = self.rect.width()
|
||||
self.options['shape.height'] = self.rect.height()
|
||||
|
||||
def content_rect(self):
|
||||
if self.options['shape'] == 'round':
|
||||
return proportional_rect(self.rect, 70)
|
||||
return self.rect
|
||||
|
||||
def execute(self, button, shift=False, ctrl=False):
|
||||
commands = _find_commands(
|
||||
self.options['action.commands'],
|
||||
button, shift=shift, ctrl=ctrl)
|
||||
for command in commands:
|
||||
try:
|
||||
execute_code(
|
||||
language=command['language'],
|
||||
code=command['command'],
|
||||
deferred=command['deferred'],
|
||||
compact_undo=command['force_compact_undo'])
|
||||
except Exception as e:
|
||||
import traceback
|
||||
print(EXCECUTION_WARNING.format(
|
||||
name=self.options['text.content'], error=e))
|
||||
print(traceback.format_exc())
|
||||
|
||||
def select(self, selection_mode='replace'):
|
||||
select_targets([self], selection_mode=selection_mode)
|
||||
|
||||
def targets(self):
|
||||
return self.options['action.targets']
|
||||
|
||||
def set_targets(self, targets):
|
||||
self.options['action.targets'] = targets
|
||||
|
||||
def is_interactive(self):
|
||||
return bool(
|
||||
[c for c in self.options['action.commands'] if c['enabled']])
|
||||
|
||||
def is_background(self):
|
||||
return not any([
|
||||
bool(self.targets()),
|
||||
bool(self.options['action.commands'])])
|
||||
|
||||
def visibility_layer(self):
|
||||
return self.options['visibility_layer']
|
||||
|
||||
def synchronize_image(self):
|
||||
path = expand_path(self.options['image.path'])
|
||||
self.pixmap = QtGui.QPixmap(path)
|
||||
if self.options['image.fit'] is True:
|
||||
self.image_rect = None
|
||||
return
|
||||
self.image_rect = QtCore.QRectF(
|
||||
self.rect.left(),
|
||||
self.rect.top(),
|
||||
self.options['image.width'],
|
||||
self.options['image.height'])
|
||||
self.image_rect.moveCenter(self.rect.center())
|
||||
|
||||
|
||||
def _find_commands(commands, button, ctrl=False, shift=False):
|
||||
result = []
|
||||
for command in commands:
|
||||
conditions = (
|
||||
command['button'] == button and
|
||||
command['ctrl'] == ctrl and
|
||||
command['shift'] == shift)
|
||||
if conditions:
|
||||
result.append(command)
|
||||
return result
|
52
Scripts/Animation/dwpicker/dwpicker/languages.py
Normal file
@ -0,0 +1,52 @@
|
||||
|
||||
PYTHON = 'python'
|
||||
MEL = 'mel'
|
||||
|
||||
DEFERRED_PYTHON = """\
|
||||
from maya import cmds
|
||||
cmds.evalDeferred(\"\"\"{code}\"\"\", lowestPriority=True)
|
||||
"""
|
||||
|
||||
DEFERRED_MEL = """\
|
||||
evalDeferred "{code}" -lowestPriority;"""
|
||||
|
||||
STACK_UNDO_PYTHON = """\
|
||||
from maya import cmds
|
||||
cmds.undoInfo(openChunk=True)
|
||||
{code}
|
||||
cmds.undoInfo(closeChunk=True)
|
||||
"""
|
||||
|
||||
STACK_UNDO_MEL = """\
|
||||
undoInfo -openChunk;
|
||||
{code}
|
||||
undoInfo -closeChunk;
|
||||
"""
|
||||
|
||||
|
||||
def execute_code(language, code, deferred=False, compact_undo=False):
|
||||
return EXECUTORS[language](code, deferred, compact_undo)
|
||||
|
||||
|
||||
def execute_python(code, deferred=False, compact_undo=False):
|
||||
if compact_undo:
|
||||
code = STACK_UNDO_PYTHON.format(code=code)
|
||||
if deferred:
|
||||
code = DEFERRED_PYTHON.format(code=code)
|
||||
exec(code, globals())
|
||||
|
||||
|
||||
def execute_mel(code, deferred=False, compact_undo=False):
|
||||
from maya import mel
|
||||
if compact_undo:
|
||||
code = STACK_UNDO_MEL.format(code=code)
|
||||
if deferred:
|
||||
print('Eval deferred not supported for mel command.')
|
||||
# code = DEFERRED_MEL.format(code=code)
|
||||
mel.eval(code.replace(u'\u2029', '\n'))
|
||||
|
||||
|
||||
EXECUTORS = {
|
||||
PYTHON: execute_python,
|
||||
MEL: execute_mel,
|
||||
}
|
865
Scripts/Animation/dwpicker/dwpicker/main.py
Normal file
@ -0,0 +1,865 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import os
|
||||
import json
|
||||
import webbrowser
|
||||
from copy import deepcopy
|
||||
from functools import partial
|
||||
|
||||
from PySide2 import QtWidgets, QtCore, QtGui
|
||||
|
||||
from maya import cmds
|
||||
import maya.OpenMaya as om
|
||||
|
||||
from dwpicker.appinfos import VERSION, RELEASE_DATE, DW_GITHUB, DW_WEBSITE
|
||||
from dwpicker.compatibility import ensure_retro_compatibility
|
||||
from dwpicker.designer.editor import PickerEditor
|
||||
from dwpicker.dialog import CommandEditorDialog
|
||||
from dwpicker.dialog import (
|
||||
warning, question, get_image_path, NamespaceDialog)
|
||||
from dwpicker.ingest import animschool
|
||||
from dwpicker.interactive import Shape
|
||||
from dwpicker.hotkeys import get_hotkeys_config
|
||||
from dwpicker.namespace import (
|
||||
switch_namespace, selected_namespace, detect_picker_namespace,
|
||||
pickers_namespaces)
|
||||
from dwpicker.optionvar import (
|
||||
AUTO_FOCUS_BEHAVIOR, AUTO_SWITCH_TAB, CHECK_IMAGES_PATHS,
|
||||
AUTO_SET_NAMESPACE, DISABLE_IMPORT_CALLBACKS,
|
||||
DISPLAY_QUICK_OPTIONS, INSERT_TAB_AFTER_CURRENT, LAST_OPEN_DIRECTORY,
|
||||
LAST_IMPORT_DIRECTORY, LAST_COMMAND_LANGUAGE, LAST_SAVE_DIRECTORY,
|
||||
NAMESPACE_TOOLBAR, USE_ICON_FOR_UNSAVED_TAB, WARN_ON_TAB_CLOSED,
|
||||
save_optionvar, append_recent_filename, save_opened_filenames)
|
||||
from dwpicker.path import get_import_directory, get_open_directory
|
||||
from dwpicker.picker import PickerView, list_targets
|
||||
from dwpicker.preference import PreferencesWindow
|
||||
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.templates import BUTTON, PICKER, BACKGROUND, COMMAND
|
||||
from dwpicker.undo import UndoManager
|
||||
|
||||
|
||||
ABOUT = """\
|
||||
DreamWall Picker
|
||||
Licence MIT
|
||||
Version: {version}
|
||||
Release date: {release}
|
||||
Authors: Lionel Brouyère, Olivier Evers
|
||||
Contributor(s): Herizoran
|
||||
|
||||
Features:
|
||||
Animation picker widget.
|
||||
Quick picker creation.
|
||||
Advanced picker editin.
|
||||
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 ?"""
|
||||
|
||||
|
||||
def build_multiple_shapes(targets, override):
|
||||
shapes = [BUTTON.copy() for _ in range(len(targets))]
|
||||
for shape, target in zip(shapes, targets):
|
||||
if override:
|
||||
shape.update(override)
|
||||
shape['action.targets'] = [target]
|
||||
return [Shape(shape) for shape in shapes]
|
||||
|
||||
|
||||
class DwPicker(DockableBase, QtWidgets.QWidget):
|
||||
def __init__(self):
|
||||
super(DwPicker, self).__init__(control_name=WINDOW_CONTROL_NAME)
|
||||
self.setWindowTitle(WINDOW_TITLE)
|
||||
self.shortcuts = {}
|
||||
|
||||
self.editable = True
|
||||
self.callbacks = []
|
||||
self.stored_focus = None
|
||||
self.editors = []
|
||||
self.generals = []
|
||||
self.undo_managers = []
|
||||
self.pickers = []
|
||||
self.filenames = []
|
||||
self.modified_states = []
|
||||
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.namespace_label = QtWidgets.QLabel("Namespace: ")
|
||||
self.namespace_combo = QtWidgets.QComboBox()
|
||||
self.namespace_combo.setFixedWidth(235)
|
||||
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.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)
|
||||
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.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),
|
||||
}
|
||||
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 update_namespaces(self, *_):
|
||||
namespaces = sorted(list(set(
|
||||
(cmds.namespaceInfo(listOnlyNamespaces=True, recurse=True)) +
|
||||
(pickers_namespaces(self.pickers)))))
|
||||
self.namespace_combo.blockSignals(True)
|
||||
self.namespace_combo.clear()
|
||||
self.namespace_combo.addItem("*Root*")
|
||||
self.namespace_combo.addItems(namespaces)
|
||||
self.namespace_combo.blockSignals(False)
|
||||
|
||||
def tab_index_changed(self, index):
|
||||
if not self.pickers:
|
||||
return
|
||||
picker = self.pickers[index]
|
||||
if not picker:
|
||||
return
|
||||
namespace = detect_picker_namespace(picker.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):
|
||||
lists = (
|
||||
self.editors,
|
||||
self.generals,
|
||||
self.pickers,
|
||||
self.filenames,
|
||||
self.modified_states)
|
||||
|
||||
for l in lists:
|
||||
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([fn for fn in self.filenames if fn])
|
||||
if not any(self.modified_states):
|
||||
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),
|
||||
button=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(self.filenames)
|
||||
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.shapes)
|
||||
if nodes[-1] in targets:
|
||||
return
|
||||
for i, picker in enumerate(self.pickers):
|
||||
if nodes[-1] in list_targets(picker.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):
|
||||
picker = 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.picker_data(i) 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),
|
||||
button=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.modified_states[index] 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.generals.pop(index)
|
||||
self.modified_states.pop(index)
|
||||
self.undo_managers.pop(index)
|
||||
self.filenames.pop(index)
|
||||
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()
|
||||
for i in range(self.tab.count()):
|
||||
self.set_modified_state(i, self.modified_states[i])
|
||||
|
||||
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 create_picker(self, data):
|
||||
picker = PickerView()
|
||||
picker.editable = self.editable
|
||||
picker.register_callbacks()
|
||||
picker.addButtonRequested.connect(self.add_button)
|
||||
picker.updateButtonRequested.connect(self.update_button)
|
||||
picker.deleteButtonRequested.connect(self.delete_buttons)
|
||||
if self.editable:
|
||||
method = partial(self.data_changed_from_picker, picker)
|
||||
picker.dataChanged.connect(method)
|
||||
shapes = [Shape(s) for s in data['shapes']]
|
||||
picker.set_shapes(shapes)
|
||||
picker.reset()
|
||||
picker.zoom_locked = data['general']['zoom_locked']
|
||||
return picker
|
||||
|
||||
def add_picker(self, data, filename=None, modified_state=False):
|
||||
picker = self.create_picker(data)
|
||||
insert = cmds.optionVar(query=INSERT_TAB_AFTER_CURRENT)
|
||||
if not insert or self.tab.currentIndex() == self.tab.count() - 1:
|
||||
self.generals.append(data['general'])
|
||||
self.pickers.append(picker)
|
||||
self.editors.append(None)
|
||||
self.undo_managers.append(UndoManager(data))
|
||||
self.filenames.append(filename)
|
||||
self.modified_states.append(modified_state)
|
||||
self.tab.addTab(picker, data['general']['name'])
|
||||
self.tab.setCurrentIndex(self.tab.count() - 1)
|
||||
else:
|
||||
index = self.tab.currentIndex() + 1
|
||||
self.generals.insert(index, data['general'])
|
||||
self.pickers.insert(index, picker)
|
||||
self.editors.insert(index, None)
|
||||
self.undo_managers.insert(index, UndoManager(data))
|
||||
self.filenames.insert(index, filename)
|
||||
self.modified_states.insert(index, modified_state)
|
||||
self.tab.insertTab(index, picker, data['general']['name'])
|
||||
self.tab.setCurrentIndex(index)
|
||||
picker.reset()
|
||||
|
||||
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.filenames[index]
|
||||
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
|
||||
undo_manager = self.undo_managers[index]
|
||||
undo_manager.undo()
|
||||
self.data_changed_from_undo_manager(index)
|
||||
|
||||
def call_redo(self):
|
||||
index = self.tab.currentIndex()
|
||||
if index < 0:
|
||||
return
|
||||
undo_manager = self.undo_managers[index]
|
||||
undo_manager.redo()
|
||||
self.data_changed_from_undo_manager(index)
|
||||
|
||||
def save_picker(self, index, filename):
|
||||
self.filenames[index] = filename
|
||||
save_optionvar(LAST_SAVE_DIRECTORY, os.path.dirname(filename))
|
||||
append_recent_filename(filename)
|
||||
with open(filename, 'w') as f:
|
||||
json.dump(self.picker_data(index), 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': PICKER.copy(),
|
||||
'shapes': []})
|
||||
self.store_local_pickers_data()
|
||||
|
||||
def picker_data(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 {
|
||||
'version': VERSION,
|
||||
'general': self.generals[index],
|
||||
'shapes': [shape.options for shape in picker.shapes]}
|
||||
|
||||
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:
|
||||
data = self.picker_data()
|
||||
undo_manager = self.undo_managers[index]
|
||||
editor = PickerEditor(
|
||||
picker_data=data,
|
||||
undo_manager=undo_manager,
|
||||
parent=self)
|
||||
picker = self.pickers[index]
|
||||
method = partial(self.data_changed_from_editor, picker=picker)
|
||||
editor.pickerDataModified.connect(method)
|
||||
self.editors[index] = editor
|
||||
|
||||
self.editors[index].show()
|
||||
|
||||
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 set_modified_state(self, index, state):
|
||||
"""
|
||||
Update the tab icon. Add a "save" icon if tab contains unsaved
|
||||
modifications.
|
||||
"""
|
||||
if not self.filenames[index]:
|
||||
return
|
||||
self.modified_states[index] = 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.generals[index]['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_about(self):
|
||||
QtWidgets.QMessageBox.about(self, 'About', ABOUT)
|
||||
|
||||
def sizeHint(self):
|
||||
return QtCore.QSize(500, 800)
|
||||
|
||||
def add_button(self, x, y, button_type):
|
||||
targets = cmds.ls(selection=True)
|
||||
if not targets and button_type <= 1:
|
||||
return warning("Warning", "No targets selected")
|
||||
|
||||
if button_type == 1:
|
||||
overrides = self.quick_options.values
|
||||
shapes = build_multiple_shapes(targets, overrides)
|
||||
if not shapes:
|
||||
return
|
||||
picker = self.tab.currentWidget()
|
||||
picker.drag_shapes = shapes
|
||||
return
|
||||
|
||||
data = BUTTON.copy()
|
||||
data['shape.left'] = x
|
||||
data['shape.top'] = y
|
||||
data.update(self.quick_options.values)
|
||||
if button_type == 0:
|
||||
data['action.targets'] = targets
|
||||
else:
|
||||
text, result = (
|
||||
QtWidgets.QInputDialog.getText(self, 'Button text', 'text'))
|
||||
if not result:
|
||||
return
|
||||
data['text.content'] = text
|
||||
command = deepcopy(COMMAND)
|
||||
languages = ['python', 'mel']
|
||||
language = languages[cmds.optionVar(query=LAST_COMMAND_LANGUAGE)]
|
||||
command['language'] = language
|
||||
dialog = CommandEditorDialog(command)
|
||||
if not dialog.exec_():
|
||||
return
|
||||
command = dialog.command_data()
|
||||
index = languages.index(command['language'])
|
||||
save_optionvar(LAST_COMMAND_LANGUAGE, index)
|
||||
data['action.commands'] = [command]
|
||||
|
||||
width = max([data['shape.width'], len(data['text.content']) * 7])
|
||||
data['shape.width'] = width
|
||||
self.add_shape_to_current_picker(Shape(data))
|
||||
|
||||
def update_button(self, shape):
|
||||
picker = self.tab.currentWidget()
|
||||
shape.set_targets(cmds.ls(selection=True))
|
||||
self.data_changed_from_picker(picker)
|
||||
|
||||
def delete_buttons(self):
|
||||
picker = self.tab.currentWidget()
|
||||
selected_shapes = [s for s in picker.shapes if s.selected]
|
||||
for shape in selected_shapes:
|
||||
picker.shapes.remove(shape)
|
||||
self.data_changed_from_picker(picker)
|
||||
|
||||
def add_shape_to_current_picker(self, shape, prepend=False):
|
||||
picker = self.tab.currentWidget()
|
||||
if prepend:
|
||||
picker.shapes.insert(0, shape)
|
||||
else:
|
||||
picker.shapes.append(shape)
|
||||
self.data_changed_from_picker(picker)
|
||||
|
||||
def data_changed_from_picker(self, picker):
|
||||
index = self.tab.indexOf(picker)
|
||||
data = self.picker_data(index)
|
||||
if self.editors[index]:
|
||||
self.editors[index].set_picker_data(data)
|
||||
self.set_modified_state(index, True)
|
||||
picker.repaint()
|
||||
self.undo_managers[index].set_data_modified(data)
|
||||
self.store_local_pickers_data()
|
||||
|
||||
def data_changed_from_editor(self, data, picker):
|
||||
index = self.tab.indexOf(picker)
|
||||
self.generals[index] = data['general']
|
||||
shapes = [Shape(s) for s in data['shapes']]
|
||||
picker.set_shapes(shapes)
|
||||
picker.zoom_locked = data['general']['zoom_locked']
|
||||
self.set_title(index, data['general']['name'])
|
||||
self.set_modified_state(index, True)
|
||||
self.store_local_pickers_data()
|
||||
|
||||
def data_changed_from_undo_manager(self, index):
|
||||
data = self.undo_managers[index].data
|
||||
if self.editors[index]:
|
||||
self.editors[index].set_picker_data(data)
|
||||
self.data_changed_from_editor(data, self.pickers[index])
|
||||
|
||||
def change_title(self, index=None):
|
||||
if not self.editable:
|
||||
return
|
||||
index = self.tab.currentIndex() if type(index) is not int else index
|
||||
if index < 0:
|
||||
return
|
||||
title, operate = QtWidgets.QInputDialog.getText(
|
||||
None, 'Change picker title', 'New title')
|
||||
|
||||
if not operate:
|
||||
return
|
||||
self.set_title(index, title)
|
||||
self.data_changed_from_picker(self.tab.widget(index))
|
||||
|
||||
def set_title(self, index, title):
|
||||
self.generals[index]['name'] = title
|
||||
use_icon = cmds.optionVar(query=USE_ICON_FOR_UNSAVED_TAB)
|
||||
if not use_icon and self.modified_states[index]:
|
||||
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):
|
||||
picker = self.tab.currentWidget()
|
||||
for shape in picker.shapes:
|
||||
if not shape.targets():
|
||||
continue
|
||||
targets = [switch_namespace(t, namespace) for t in shape.targets()]
|
||||
shape.options['action.targets'] = targets
|
||||
|
||||
self.data_changed_from_picker(picker)
|
||||
|
||||
def add_background(self):
|
||||
filename = get_image_path(self)
|
||||
if not filename:
|
||||
return
|
||||
|
||||
shape = BACKGROUND.copy()
|
||||
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
|
||||
shape = Shape(shape)
|
||||
self.add_shape_to_current_picker(shape, prepend=True)
|
||||
|
||||
|
||||
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)
|
||||
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.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.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.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)
|
65
Scripts/Animation/dwpicker/dwpicker/namespace.py
Normal file
@ -0,0 +1,65 @@
|
||||
|
||||
from contextlib import contextmanager
|
||||
from maya import cmds
|
||||
|
||||
|
||||
def detect_picker_namespace(shapes):
|
||||
targets = {target for shape in shapes for target in shape.targets()}
|
||||
namespaces = {ns for ns in [node_namespace(t) for t in targets] if ns}
|
||||
if len(namespaces) != 1:
|
||||
return None
|
||||
return list(namespaces)[0]
|
||||
|
||||
|
||||
def pickers_namespaces(pickers):
|
||||
targets = {t for p in pickers for s in p.shapes for t in s.targets()}
|
||||
namespaces = {ns for ns in [node_namespace(t) for t in targets] if ns}
|
||||
return sorted(list(namespaces))
|
||||
|
||||
|
||||
def node_namespace(node):
|
||||
basename = node.split("|")[-1]
|
||||
if ":" not in node:
|
||||
return None
|
||||
return basename.split(":")[0]
|
||||
|
||||
|
||||
@contextmanager
|
||||
def maya_namespace(
|
||||
namespace='', create_if_missing=True, restore_current_namespace=True):
|
||||
"""Context manager to temporarily set a namespace"""
|
||||
initial_namespace = ':' + cmds.namespaceInfo(currentNamespace=True)
|
||||
if not namespace.startswith(':'):
|
||||
namespace = ':' + namespace
|
||||
try:
|
||||
if not cmds.namespace(absoluteName=True, exists=namespace):
|
||||
if create_if_missing:
|
||||
cmds.namespace(setNamespace=':')
|
||||
namespace = cmds.namespace(addNamespace=namespace)
|
||||
else:
|
||||
cmds.namespace(initial_namespace)
|
||||
raise ValueError(namespace + " doesn't exist.")
|
||||
cmds.namespace(setNamespace=namespace)
|
||||
yield namespace
|
||||
finally:
|
||||
if restore_current_namespace:
|
||||
cmds.namespace(setNamespace=initial_namespace)
|
||||
|
||||
|
||||
def switch_namespace(name, namespace):
|
||||
basename = name.split("|")[-1]
|
||||
name = basename if ":" not in basename else basename.split(":")[-1]
|
||||
if not namespace:
|
||||
return name
|
||||
return namespace + ":" + name
|
||||
|
||||
|
||||
def selected_namespace():
|
||||
selection = cmds.ls(selection=True)
|
||||
if not selection:
|
||||
return ":"
|
||||
node = selection[0]
|
||||
basename = node.split("|")[-1]
|
||||
if ":" not in node:
|
||||
return None
|
||||
return basename.split(":")[0]
|
159
Scripts/Animation/dwpicker/dwpicker/optionvar.py
Normal file
@ -0,0 +1,159 @@
|
||||
import os
|
||||
import sys
|
||||
from maya import cmds
|
||||
|
||||
|
||||
AUTO_FOCUS_BEHAVIORS = ['off', 'bilateral', 'pickertomaya']
|
||||
ZOOM_BUTTONS = ["left", "middle", "right"]
|
||||
|
||||
|
||||
AUTO_FOCUS_BEHAVIOR = 'dwpicker_auto_focus_behavior'
|
||||
AUTO_COLLAPSE_IMG_PATH_FROM_ENV = 'dwpicker_auto_collapse_image_path_from_env'
|
||||
AUTO_SET_NAMESPACE = 'dwpicker_auto_set_namespace'
|
||||
AUTO_SWITCH_TAB = 'dwpicker_auto_switch_tab'
|
||||
BG_LOCKED = 'dwpicker_designer_background_items_locked'
|
||||
CHECK_IMAGES_PATHS = 'dwpicker_check_images_paths'
|
||||
CHECK_FOR_UPDATE = 'dwpicker_check_for_update'
|
||||
CUSTOM_PROD_PICKER_DIRECTORY = 'dwpicker_custom_prod_picker_directory'
|
||||
DEFAULT_BG_COLOR = 'dwpicker_default_background_color'
|
||||
DEFAULT_HOTKEYS = 'dwpicker_default_hotkeys'
|
||||
DEFAULT_LABEL = 'dwpicker_default_label_color'
|
||||
DEFAULT_HEIGHT = 'dwpicker_default_height'
|
||||
DEFAULT_TEXT_COLOR = 'dwpicker_default_text_color'
|
||||
DEFAULT_WIDTH = 'dwpicker_default_width'
|
||||
DISABLE_IMPORT_CALLBACKS = 'dwpicker_disable_import_callbacks'
|
||||
DISPLAY_QUICK_OPTIONS = 'dwpicker_display_quick_options'
|
||||
OVERRIDE_PROD_PICKER_DIRECTORY_ENV = 'dwpicker_override_picker_directory_env'
|
||||
INSERT_TAB_AFTER_CURRENT = 'dwpicker_insert_tab_after_current'
|
||||
LAST_COMMAND_LANGUAGE = 'dwpicker_last_command_language_used'
|
||||
LAST_IMAGE_DIRECTORY_USED = 'dwpicker_last_directory_used'
|
||||
LAST_IMPORT_DIRECTORY = 'dwpicker_last_file_import_directory'
|
||||
LAST_OPEN_DIRECTORY = 'dwpicker_last_file_open_directory'
|
||||
LAST_SAVE_DIRECTORY = 'dwpicker_last_file_save_directory'
|
||||
OPENED_FILES = 'dwpicker_opened_files'
|
||||
NAMESPACE_TOOLBAR = 'dwpicker_display_dwtoolbar'
|
||||
RECENT_FILES = 'dwpicker_recent_files'
|
||||
SEARCH_FIELD_INDEX = 'dwpicker_designer_search_field_index'
|
||||
SETTINGS_GROUP_TO_COPY = 'dwpicker_settings_group_to_copy'
|
||||
SETTINGS_TO_COPY = 'dwpicker_settings_to_copy'
|
||||
SHAPES_FILTER_INDEX = 'dwpicker_designer_shape_filter_index'
|
||||
SNAP_ITEMS = 'dwpicker_designer_snap_items'
|
||||
SNAP_GRID_X = 'dwpicker_designer_snap_x'
|
||||
SNAP_GRID_Y = 'dwpicker_designer_snap_y'
|
||||
SYNCHRONYZE_SELECTION = 'dwpicker_synchronize_selection'
|
||||
TRIGGER_REPLACE_ON_MIRROR = 'dwpicker_trigger_search_and_replace_on_mirror'
|
||||
USE_BASE64_DATA_ENCODING = 'dwpicker_use_base64_data_encoding'
|
||||
USE_ICON_FOR_UNSAVED_TAB = 'dwpicker_use_icon_for_unsaved_tab'
|
||||
USE_PROD_PICKER_DIR_AS_DEFAULT = 'dwpicker_user_prod_picker_dir_for_import'
|
||||
ZOOM_BUTTON = 'dwpicker_picker_zoom_mouse_button'
|
||||
WARN_ON_TAB_CLOSED = 'dwpicker_warn_on_tab_closed'
|
||||
ZOOM_SENSITIVITY = 'dwpicker_zoom_sensitivity'
|
||||
|
||||
|
||||
OPTIONVARS = {
|
||||
AUTO_FOCUS_BEHAVIOR: AUTO_FOCUS_BEHAVIORS[-1],
|
||||
AUTO_SWITCH_TAB: 0,
|
||||
AUTO_SET_NAMESPACE: 0,
|
||||
AUTO_COLLAPSE_IMG_PATH_FROM_ENV: 1,
|
||||
BG_LOCKED: 1,
|
||||
CHECK_IMAGES_PATHS: 1,
|
||||
# We disable this default feature for maya 2023. It seems that the github
|
||||
# request can cause a maya crash due to an incompatibility with the python
|
||||
# with this specific version of Maya.
|
||||
CHECK_FOR_UPDATE: int(cmds.about(majorVersion=True) != '2023'),
|
||||
CUSTOM_PROD_PICKER_DIRECTORY: '',
|
||||
DEFAULT_BG_COLOR: '#777777',
|
||||
DEFAULT_HEIGHT: 20,
|
||||
DEFAULT_LABEL: '',
|
||||
DEFAULT_TEXT_COLOR: '#000000',
|
||||
DEFAULT_HOTKEYS: (
|
||||
'focus=F,1;new=CTRL+N,1;open=CTRL+O,1;save=CTRL+S,1;close=CTRL+Q,1;'
|
||||
'undo=CTRL+Z,1;redo=CTRL+Y,1;edit=CTRL+E,1;next_tab=None,0;'
|
||||
'previous_tab=None,0'),
|
||||
DEFAULT_WIDTH: 30,
|
||||
DISABLE_IMPORT_CALLBACKS: 1,
|
||||
DISPLAY_QUICK_OPTIONS: 1,
|
||||
OVERRIDE_PROD_PICKER_DIRECTORY_ENV: 0,
|
||||
INSERT_TAB_AFTER_CURRENT: 0,
|
||||
LAST_OPEN_DIRECTORY: os.path.expanduser("~"),
|
||||
LAST_SAVE_DIRECTORY: os.path.expanduser("~"),
|
||||
LAST_IMPORT_DIRECTORY: os.path.expanduser("~"),
|
||||
LAST_COMMAND_LANGUAGE: 0, # 0 = python, 1 = mel
|
||||
LAST_IMAGE_DIRECTORY_USED: os.path.expanduser("~"),
|
||||
NAMESPACE_TOOLBAR: 0,
|
||||
OPENED_FILES: '',
|
||||
RECENT_FILES: '',
|
||||
SEARCH_FIELD_INDEX: 0,
|
||||
SHAPES_FILTER_INDEX: 0,
|
||||
SETTINGS_GROUP_TO_COPY: 'bordercolor;text;image;bgcolor;shape;borderwidth;border',
|
||||
SETTINGS_TO_COPY: (
|
||||
'bgcolor.clicked;bgcolor.hovered;bgcolor.normal;bgcolor.transparency;'
|
||||
'border;bordercolor.clicked;bordercolor.hovered;bordercolor.normal;'
|
||||
'bordercolor.transparency;borderwidth.clicked;borderwidth.hovered;'
|
||||
'borderwidth.normal;image.fit;image.height;image.width;shape;'
|
||||
'shape.cornersx;shape.cornersy;shape.height;shape.left;'
|
||||
'shape.top;shape.width;text.bold;text.color;text.halign;text.italic;'
|
||||
'text.size;text.valign'),
|
||||
SNAP_ITEMS: 0,
|
||||
SNAP_GRID_X: 10,
|
||||
SNAP_GRID_Y: 10,
|
||||
SYNCHRONYZE_SELECTION: 1,
|
||||
TRIGGER_REPLACE_ON_MIRROR: 0,
|
||||
USE_BASE64_DATA_ENCODING: 0,
|
||||
USE_ICON_FOR_UNSAVED_TAB: 1,
|
||||
USE_PROD_PICKER_DIR_AS_DEFAULT: 0,
|
||||
WARN_ON_TAB_CLOSED: 0,
|
||||
ZOOM_BUTTON: ZOOM_BUTTONS[2],
|
||||
ZOOM_SENSITIVITY: 50
|
||||
}
|
||||
|
||||
|
||||
TYPES = {
|
||||
int: 'intValue',
|
||||
float: 'floatValue',
|
||||
str: 'stringValue'}
|
||||
|
||||
|
||||
# Ensure backward compatibility.
|
||||
if sys.version_info[0] == 2:
|
||||
TYPES[unicode] = 'stringValue'
|
||||
|
||||
|
||||
def ensure_optionvars_exists():
|
||||
for optionvar, default_value in OPTIONVARS.items():
|
||||
if cmds.optionVar(exists=optionvar):
|
||||
continue
|
||||
save_optionvar(optionvar, default_value)
|
||||
|
||||
|
||||
def save_optionvar(optionvar, value):
|
||||
kwargs = {TYPES.get(type(value)): [optionvar, value]}
|
||||
cmds.optionVar(**kwargs)
|
||||
|
||||
|
||||
def save_opened_filenames(filenames):
|
||||
save_optionvar(OPENED_FILES, ";".join(filenames))
|
||||
|
||||
|
||||
def append_recent_filename(filename):
|
||||
filename = os.path.normpath(filename)
|
||||
stored_filenames = cmds.optionVar(query=RECENT_FILES)
|
||||
if not stored_filenames:
|
||||
cmds.optionVar(stringValue=[RECENT_FILES, filename + ';'])
|
||||
return
|
||||
|
||||
# Just reorder list if the filename is already in the recent filenames.
|
||||
stored_filenames = stored_filenames.split(';')
|
||||
for stored_filename in stored_filenames:
|
||||
if os.path.normpath(stored_filename) == filename:
|
||||
stored_filenames.remove(stored_filename)
|
||||
stored_filenames.insert(0, filename)
|
||||
cmds.optionVar(
|
||||
stringValue=[RECENT_FILES, ';'.join(stored_filenames)])
|
||||
return
|
||||
|
||||
# Append to list if new filename.
|
||||
if len(stored_filenames) >= 10:
|
||||
stored_filenames = stored_filenames[:9]
|
||||
stored_filenames.insert(0, filename)
|
||||
cmds.optionVar(stringValue=[RECENT_FILES, ';'.join(stored_filenames)])
|
152
Scripts/Animation/dwpicker/dwpicker/painting.py
Normal file
@ -0,0 +1,152 @@
|
||||
from PySide2 import QtCore, QtGui
|
||||
from maya import cmds
|
||||
|
||||
from dwpicker.optionvar import ZOOM_SENSITIVITY
|
||||
from dwpicker.qtutils import VALIGNS, HALIGNS
|
||||
from dwpicker.geometry import grow_rect, ViewportMapper
|
||||
|
||||
|
||||
SELECTION_COLOR = '#3388FF'
|
||||
MANIPULATOR_BORDER = 5
|
||||
|
||||
|
||||
def factor_sensitivity(factor):
|
||||
sensitivity = cmds.optionVar(query=ZOOM_SENSITIVITY) / 50.0
|
||||
return factor * sensitivity
|
||||
|
||||
|
||||
def draw_editor(painter, rect, snap=None, viewportmapper=None):
|
||||
viewportmapper = viewportmapper or ViewportMapper()
|
||||
rect = viewportmapper.to_viewport_rect(rect)
|
||||
# draw border
|
||||
pen = QtGui.QPen(QtGui.QColor('#333333'))
|
||||
pen.setStyle(QtCore.Qt.DashDotLine)
|
||||
pen.setWidthF(viewportmapper.to_viewport(3))
|
||||
brush = QtGui.QBrush(QtGui.QColor(255, 255, 255, 25))
|
||||
painter.setPen(pen)
|
||||
painter.setBrush(brush)
|
||||
painter.drawRect(rect)
|
||||
|
||||
if snap is None:
|
||||
return
|
||||
# draw snap grid
|
||||
snap = viewportmapper.to_viewport(snap[0]), viewportmapper.to_viewport(snap[1])
|
||||
pen = QtGui.QPen(QtGui.QColor('red'))
|
||||
painter.setPen(pen)
|
||||
x = 0
|
||||
y = 0
|
||||
while y < rect.bottom():
|
||||
painter.drawPoint(x, y)
|
||||
x += snap[0]
|
||||
if x > rect.right():
|
||||
x = 0
|
||||
y += snap[1]
|
||||
|
||||
|
||||
def draw_shape(painter, shape, viewportmapper=None):
|
||||
viewportmapper = viewportmapper or ViewportMapper()
|
||||
options = shape.options
|
||||
content_rect = shape.content_rect()
|
||||
if shape.clicked or shape.selected:
|
||||
bordercolor = QtGui.QColor(options['bordercolor.clicked'])
|
||||
backgroundcolor = QtGui.QColor(options['bgcolor.clicked'])
|
||||
bordersize = options['borderwidth.clicked']
|
||||
elif shape.hovered:
|
||||
bordercolor = QtGui.QColor(options['bordercolor.hovered'])
|
||||
backgroundcolor = QtGui.QColor(options['bgcolor.hovered'])
|
||||
bordersize = options['borderwidth.hovered']
|
||||
else:
|
||||
bordercolor = QtGui.QColor(options['bordercolor.normal'])
|
||||
backgroundcolor = QtGui.QColor(options['bgcolor.normal'])
|
||||
bordersize = options['borderwidth.normal']
|
||||
|
||||
textcolor = QtGui.QColor(options['text.color'])
|
||||
alpha = options['bordercolor.transparency'] if options['border'] else 255
|
||||
bordercolor.setAlpha(255 - alpha)
|
||||
backgroundcolor.setAlpha(255 - options['bgcolor.transparency'])
|
||||
|
||||
pen = QtGui.QPen(bordercolor)
|
||||
pen.setStyle(QtCore.Qt.SolidLine)
|
||||
pen.setWidthF(viewportmapper.to_viewport(bordersize))
|
||||
painter.setPen(pen)
|
||||
painter.setBrush(QtGui.QBrush(backgroundcolor))
|
||||
rect = viewportmapper.to_viewport_rect(shape.rect)
|
||||
if options['shape'] == 'square':
|
||||
painter.drawRect(rect)
|
||||
elif options['shape'] == 'round':
|
||||
painter.drawEllipse(rect)
|
||||
else: # 'rounded_rect'
|
||||
x = viewportmapper.to_viewport(options['shape.cornersx'])
|
||||
y = viewportmapper.to_viewport(options['shape.cornersy'])
|
||||
painter.drawRoundedRect(rect, x, y)
|
||||
|
||||
if shape.pixmap is not None:
|
||||
rect = shape.image_rect or content_rect
|
||||
rect = viewportmapper.to_viewport_rect(rect)
|
||||
painter.drawPixmap(rect.toRect(), shape.pixmap)
|
||||
|
||||
painter.setPen(QtGui.QPen(textcolor))
|
||||
painter.setBrush(QtGui.QBrush(textcolor))
|
||||
option = QtGui.QTextOption()
|
||||
flags = VALIGNS[options['text.valign']] | HALIGNS[options['text.halign']]
|
||||
option.setAlignment(flags)
|
||||
font = QtGui.QFont()
|
||||
font.setBold(options['text.bold'])
|
||||
font.setItalic(options['text.italic'])
|
||||
size = round(viewportmapper.to_viewport(options['text.size']))
|
||||
font.setPixelSize(size)
|
||||
painter.setFont(font)
|
||||
text = options['text.content']
|
||||
content_rect = viewportmapper.to_viewport_rect(content_rect)
|
||||
painter.drawText(content_rect, flags, text)
|
||||
|
||||
|
||||
def draw_selection_square(painter, rect, viewportmapper=None):
|
||||
viewportmapper = viewportmapper or ViewportMapper()
|
||||
rect = viewportmapper.to_viewport_rect(rect)
|
||||
bordercolor = QtGui.QColor(SELECTION_COLOR)
|
||||
backgroundcolor = QtGui.QColor(SELECTION_COLOR)
|
||||
backgroundcolor.setAlpha(85)
|
||||
painter.setPen(QtGui.QPen(bordercolor))
|
||||
painter.setBrush(QtGui.QBrush(backgroundcolor))
|
||||
painter.drawRect(rect)
|
||||
|
||||
|
||||
def draw_manipulator(painter, manipulator, cursor, viewportmapper=None):
|
||||
viewportmapper = viewportmapper or ViewportMapper()
|
||||
hovered = manipulator.hovered_rects(cursor)
|
||||
|
||||
if manipulator.rect in hovered:
|
||||
pen = QtGui.QPen(QtGui.QColor(0, 0, 0, 0))
|
||||
brush = QtGui.QBrush(QtGui.QColor(125, 125, 125))
|
||||
brush.setStyle(QtCore.Qt.FDiagPattern)
|
||||
painter.setPen(pen)
|
||||
painter.setBrush(brush)
|
||||
painter.drawPath(manipulator.hovered_path)
|
||||
|
||||
pen = QtGui.QPen(QtGui.QColor('black'))
|
||||
brush = QtGui.QBrush(QtGui.QColor('white'))
|
||||
painter.setBrush(brush)
|
||||
for rect in manipulator.handler_rects():
|
||||
rect = viewportmapper.to_viewport_rect(rect)
|
||||
pen.setWidth(3 if rect in hovered else 1)
|
||||
painter.setPen(pen)
|
||||
painter.drawEllipse(rect)
|
||||
|
||||
pen.setWidth(1)
|
||||
pen.setStyle(QtCore.Qt.DashLine) # if not moving else QtCore.Qt.SolidLine)
|
||||
painter.setPen(pen)
|
||||
painter.setBrush(QtGui.QBrush(QtGui.QColor(0, 0, 0, 0)))
|
||||
rect = viewportmapper.to_viewport_rect(manipulator.rect)
|
||||
painter.drawRect(rect)
|
||||
|
||||
|
||||
def get_hovered_path(rect, viewportmapper=None):
|
||||
viewportmapper = viewportmapper or ViewportMapper()
|
||||
rect = viewportmapper.to_viewport_rect(rect)
|
||||
manipulator_rect = grow_rect(
|
||||
rect, viewportmapper.to_viewport(MANIPULATOR_BORDER))
|
||||
path = QtGui.QPainterPath()
|
||||
path.addRect(rect)
|
||||
path.addRect(manipulator_rect)
|
||||
return path
|
78
Scripts/Animation/dwpicker/dwpicker/path.py
Normal file
@ -0,0 +1,78 @@
|
||||
|
||||
|
||||
import os
|
||||
from maya import cmds
|
||||
from dwpicker.optionvar import (
|
||||
AUTO_COLLAPSE_IMG_PATH_FROM_ENV, CUSTOM_PROD_PICKER_DIRECTORY,
|
||||
LAST_IMPORT_DIRECTORY, LAST_IMAGE_DIRECTORY_USED, LAST_OPEN_DIRECTORY,
|
||||
OVERRIDE_PROD_PICKER_DIRECTORY_ENV, USE_PROD_PICKER_DIR_AS_DEFAULT)
|
||||
|
||||
|
||||
def unix_path(path, isroot=False):
|
||||
path = path.replace('\\', '/')
|
||||
condition = (
|
||||
os.name == 'nt' and
|
||||
isroot and
|
||||
path.startswith('/') and
|
||||
not path.startswith('//'))
|
||||
|
||||
if condition:
|
||||
path = '/' + path
|
||||
|
||||
path = path.rstrip(r'\/')
|
||||
return path
|
||||
|
||||
|
||||
def format_path(path):
|
||||
if path is None:
|
||||
return
|
||||
path = unix_path(path)
|
||||
if not cmds.optionVar(query=AUTO_COLLAPSE_IMG_PATH_FROM_ENV):
|
||||
return path
|
||||
root = get_picker_project_directory()
|
||||
if not root or not path.lower().startswith(root.lower()):
|
||||
return path
|
||||
return '$DWPICKER_PROJECT_DIRECTORY/{}'.format(
|
||||
path[len(root):].lstrip('/'))
|
||||
|
||||
|
||||
def get_picker_project_directory():
|
||||
if cmds.optionVar(query=OVERRIDE_PROD_PICKER_DIRECTORY_ENV):
|
||||
return unix_path(cmds.optionVar(query=CUSTOM_PROD_PICKER_DIRECTORY))
|
||||
return unix_path(os.getenv('DWPICKER_PROJECT_DIRECTORY'))
|
||||
|
||||
|
||||
def expand_path(path):
|
||||
backup = None
|
||||
if cmds.optionVar(query=OVERRIDE_PROD_PICKER_DIRECTORY_ENV):
|
||||
root = unix_path(cmds.optionVar(query=CUSTOM_PROD_PICKER_DIRECTORY))
|
||||
backup = os.getenv('DWPICKER_PROJECT_DIRECTORY')
|
||||
os.environ['DWPICKER_PROJECT_DIRECTORY'] = root
|
||||
result = os.path.expandvars(path)
|
||||
if backup:
|
||||
os.environ['DWPICKER_PROJECT_DIRECTORY'] = backup
|
||||
return result
|
||||
|
||||
|
||||
def get_open_directory():
|
||||
if cmds.optionVar(query=USE_PROD_PICKER_DIR_AS_DEFAULT):
|
||||
directory = get_picker_project_directory()
|
||||
if directory:
|
||||
return directory
|
||||
return cmds.optionVar(query=LAST_OPEN_DIRECTORY)
|
||||
|
||||
|
||||
def get_import_directory():
|
||||
if cmds.optionVar(query=USE_PROD_PICKER_DIR_AS_DEFAULT):
|
||||
directory = get_picker_project_directory()
|
||||
if directory:
|
||||
return directory
|
||||
return cmds.optionVar(query=LAST_IMPORT_DIRECTORY)
|
||||
|
||||
|
||||
def get_image_directory():
|
||||
if cmds.optionVar(query=USE_PROD_PICKER_DIR_AS_DEFAULT):
|
||||
directory = get_picker_project_directory()
|
||||
if directory:
|
||||
return directory
|
||||
return cmds.optionVar(query=LAST_IMAGE_DIRECTORY_USED)
|
465
Scripts/Animation/dwpicker/dwpicker/picker.py
Normal file
@ -0,0 +1,465 @@
|
||||
from functools import partial
|
||||
|
||||
from maya import cmds
|
||||
import maya.OpenMaya as om
|
||||
from PySide2 import QtWidgets, QtGui, QtCore
|
||||
|
||||
from dwpicker.interactive import SelectionSquare
|
||||
from dwpicker.dialog import warning
|
||||
from dwpicker.geometry import split_line, get_combined_rects
|
||||
from dwpicker.optionvar import (
|
||||
SYNCHRONYZE_SELECTION, ZOOM_BUTTON, ZOOM_SENSITIVITY)
|
||||
from dwpicker.painting import ViewportMapper, draw_shape
|
||||
from dwpicker.qtutils import get_cursor
|
||||
from dwpicker.selection import (
|
||||
select_targets, select_shapes_from_selection, get_selection_mode,
|
||||
NameclashError)
|
||||
|
||||
|
||||
def align_shapes_on_line(shapes, point1, point2):
|
||||
centers = split_line(point1, point2, len(shapes))
|
||||
for center, shape in zip(centers, shapes):
|
||||
shape.rect.moveCenter(center)
|
||||
shape.synchronize_rect()
|
||||
|
||||
|
||||
def frame_shapes(shapes):
|
||||
offset_x = min(shape.rect.left() for shape in shapes)
|
||||
offset_y = min(shape.rect.top() for shape in shapes)
|
||||
offset = -min([offset_x, 0]), -min([offset_y, 0])
|
||||
|
||||
for shape in shapes:
|
||||
shape.rect.moveLeft(shape.rect.left() + offset[0])
|
||||
shape.rect.moveTop(shape.rect.top() + offset[1])
|
||||
shape.synchronize_rect()
|
||||
shape.synchronize_image()
|
||||
|
||||
|
||||
def set_shapes_hovered(shapes, cursor, hidden_layers=None, selection_rect=None):
|
||||
"""
|
||||
It set hovered the shape if his rect contains the cursor.
|
||||
"""
|
||||
if not shapes:
|
||||
return
|
||||
cursor = cursor.toPoint()
|
||||
selection_rect = selection_rect or QtCore.QRect(cursor, cursor)
|
||||
selection_shapes = [s for s in shapes if s.targets()]
|
||||
selection_shapes_intersect_selection = [
|
||||
s for s in selection_shapes
|
||||
if s.rect.contains(cursor) or
|
||||
s.rect.intersects(selection_rect)]
|
||||
selection_shapes_hovered = [
|
||||
s for s in selection_shapes_intersect_selection if
|
||||
not s.visibility_layer() or
|
||||
not hidden_layers or
|
||||
s.visibility_layer() not in hidden_layers]
|
||||
targets = list_targets(selection_shapes_hovered)
|
||||
|
||||
for s in selection_shapes:
|
||||
state = next((False for t in s.targets() if t not in targets), True)
|
||||
s.hovered = state
|
||||
|
||||
|
||||
def detect_hovered_shape(shapes, cursor):
|
||||
if not shapes:
|
||||
return
|
||||
for shape in reversed(shapes):
|
||||
if not (shape.is_interactive() or shape.targets()):
|
||||
continue
|
||||
if shape.rect.contains(cursor):
|
||||
return shape
|
||||
|
||||
|
||||
def list_targets(shapes):
|
||||
return {t for s in shapes for t in s.targets()}
|
||||
|
||||
|
||||
class PickerView(QtWidgets.QWidget):
|
||||
dataChanged = QtCore.Signal()
|
||||
addButtonRequested = QtCore.Signal(int, int, int)
|
||||
updateButtonRequested = QtCore.Signal(object)
|
||||
deleteButtonRequested = QtCore.Signal()
|
||||
|
||||
def __init__(self, editable=True, parent=None):
|
||||
super(PickerView, self).__init__(parent)
|
||||
self.callbacks = []
|
||||
self.editable = editable
|
||||
self.mode_manager = ModeManager()
|
||||
self.viewportmapper = ViewportMapper()
|
||||
self.selection_square = SelectionSquare()
|
||||
self.layers_menu = VisibilityLayersMenu()
|
||||
self.setMouseTracking(True)
|
||||
self.shapes = None
|
||||
self.clicked_shape = None
|
||||
self.context_menu = None
|
||||
self.drag_shapes = []
|
||||
self.zoom_locked = False
|
||||
|
||||
def register_callbacks(self):
|
||||
method = self.sync_with_maya_selection
|
||||
cb = om.MEventMessage.addEventCallback('SelectionChanged', method)
|
||||
self.callbacks.append(cb)
|
||||
|
||||
def unregister_callbacks(self):
|
||||
for callback in self.callbacks:
|
||||
om.MMessage.removeCallback(callback)
|
||||
self.callbacks.remove(callback)
|
||||
|
||||
def sync_with_maya_selection(self, *_):
|
||||
if not cmds.optionVar(query=SYNCHRONYZE_SELECTION):
|
||||
return
|
||||
select_shapes_from_selection(self.shapes)
|
||||
self.repaint()
|
||||
|
||||
def set_shapes(self, shapes):
|
||||
self.shapes = shapes
|
||||
self.mode_manager.shapes = shapes
|
||||
self.layers_menu.set_shapes(shapes)
|
||||
self.repaint()
|
||||
|
||||
def visible_shapes(self):
|
||||
return [
|
||||
s for s in self.shapes if
|
||||
not s.visibility_layer()
|
||||
or s.visibility_layer() not in self.layers_menu.hidden_layers]
|
||||
|
||||
def reset(self):
|
||||
shapes = self.visible_shapes()
|
||||
shapes_rects = [s.rect for s in shapes if s.selected]
|
||||
if not shapes_rects:
|
||||
shapes_rects = [s.rect for s in shapes]
|
||||
if not shapes_rects:
|
||||
self.repaint()
|
||||
return
|
||||
self.viewportmapper.viewsize = self.size()
|
||||
rect = get_combined_rects(shapes_rects)
|
||||
self.viewportmapper.focus(rect)
|
||||
self.repaint()
|
||||
|
||||
def resizeEvent(self, event):
|
||||
self.viewportmapper.viewsize = self.size()
|
||||
size = (event.size() - event.oldSize()) / 2
|
||||
offset = QtCore.QPointF(size.width(), size.height())
|
||||
self.viewportmapper.origin -= offset
|
||||
self.repaint()
|
||||
|
||||
def mousePressEvent(self, event):
|
||||
self.setFocus(QtCore.Qt.MouseFocusReason)
|
||||
self.shapes.extend(self.drag_shapes)
|
||||
cursor = self.viewportmapper.to_units_coords(event.pos()).toPoint()
|
||||
self.clicked_shape = detect_hovered_shape(self.shapes, cursor)
|
||||
hsh = any(s.hovered for s in self.shapes)
|
||||
self.mode_manager.update(
|
||||
event,
|
||||
pressed=True,
|
||||
has_shape_hovered=hsh,
|
||||
dragging=bool(self.drag_shapes))
|
||||
|
||||
def mouseReleaseEvent(self, event):
|
||||
shift = self.mode_manager.shift_pressed
|
||||
ctrl = self.mode_manager.ctrl_pressed
|
||||
selection_mode = get_selection_mode(shift=shift, ctrl=ctrl)
|
||||
cursor = self.viewportmapper.to_units_coords(event.pos()).toPoint()
|
||||
zoom = self.mode_manager.zoom_button_pressed
|
||||
interact = (
|
||||
self.clicked_shape and
|
||||
self.clicked_shape is detect_hovered_shape(self.shapes, cursor) and
|
||||
self.clicked_shape.is_interactive())
|
||||
|
||||
if zoom and self.mode_manager.alt_pressed:
|
||||
self.release(event)
|
||||
return
|
||||
|
||||
if self.mode_manager.mode == ModeManager.DRAGGING:
|
||||
self.drag_shapes = []
|
||||
self.dataChanged.emit()
|
||||
|
||||
elif self.mode_manager.mode == ModeManager.SELECTION and not interact:
|
||||
try:
|
||||
select_targets(self.shapes, selection_mode=selection_mode)
|
||||
except NameclashError as e:
|
||||
warning('Selection Error', str(e), parent=self)
|
||||
self.release(event)
|
||||
return
|
||||
if not self.clicked_shape:
|
||||
if self.mode_manager.right_click_pressed:
|
||||
self.call_context_menu()
|
||||
|
||||
elif self.clicked_shape is detect_hovered_shape(self.shapes, cursor):
|
||||
show_context = (
|
||||
self.mode_manager.right_click_pressed and
|
||||
not self.clicked_shape.is_interactive())
|
||||
left_clicked = self.mode_manager.left_click_pressed
|
||||
if show_context:
|
||||
self.call_context_menu()
|
||||
elif left_clicked and self.clicked_shape.targets():
|
||||
self.clicked_shape.select(selection_mode)
|
||||
|
||||
if interact:
|
||||
button = (
|
||||
'left' if self.mode_manager.left_click_pressed
|
||||
else 'right')
|
||||
self.clicked_shape.execute(
|
||||
button=button,
|
||||
ctrl=self.mode_manager.ctrl_pressed,
|
||||
shift=self.mode_manager.shift_pressed)
|
||||
|
||||
self.release(event)
|
||||
|
||||
def release(self, event):
|
||||
self.mode_manager.update(event, pressed=False)
|
||||
self.selection_square.release()
|
||||
self.clicked_shape = None
|
||||
self.repaint()
|
||||
|
||||
def wheelEvent(self, event):
|
||||
# To center the zoom on the mouse, we save a reference mouse position
|
||||
# and compare the offset after zoom computation.
|
||||
if self.zoom_locked:
|
||||
return
|
||||
factor = .25 if event.angleDelta().y() > 0 else -.25
|
||||
self.zoom(factor, event.pos())
|
||||
self.repaint()
|
||||
|
||||
def zoom(self, factor, reference):
|
||||
abspoint = self.viewportmapper.to_units_coords(reference)
|
||||
if factor > 0:
|
||||
self.viewportmapper.zoomin(abs(factor))
|
||||
else:
|
||||
self.viewportmapper.zoomout(abs(factor))
|
||||
relcursor = self.viewportmapper.to_viewport_coords(abspoint)
|
||||
vector = relcursor - reference
|
||||
self.viewportmapper.origin = self.viewportmapper.origin + vector
|
||||
|
||||
def mouseMoveEvent(self, event):
|
||||
selection_rect = self.selection_square.rect
|
||||
if selection_rect:
|
||||
selection_rect = self.viewportmapper.to_units_rect(selection_rect)
|
||||
selection_rect = selection_rect.toRect()
|
||||
|
||||
set_shapes_hovered(
|
||||
self.shapes,
|
||||
self.viewportmapper.to_units_coords(event.pos()),
|
||||
self.layers_menu.hidden_layers,
|
||||
selection_rect)
|
||||
|
||||
if self.mode_manager.mode == ModeManager.DRAGGING:
|
||||
point1 = self.viewportmapper.to_units_coords(
|
||||
self.mode_manager.anchor)
|
||||
point2 = self.viewportmapper.to_units_coords(event.pos())
|
||||
align_shapes_on_line(self.drag_shapes, point1, point2)
|
||||
|
||||
elif self.mode_manager.mode == ModeManager.SELECTION:
|
||||
if not self.selection_square.handeling:
|
||||
self.selection_square.clicked(event.pos())
|
||||
self.selection_square.handle(event.pos())
|
||||
return self.repaint()
|
||||
|
||||
elif self.mode_manager.mode == ModeManager.ZOOMING:
|
||||
if self.zoom_locked:
|
||||
return self.repaint()
|
||||
offset = self.mode_manager.mouse_offset(event.pos())
|
||||
if offset is not None and self.mode_manager.zoom_anchor:
|
||||
sensitivity = float(cmds.optionVar(query=ZOOM_SENSITIVITY))
|
||||
factor = (offset.x() + offset.y()) / sensitivity
|
||||
self.zoom(factor, self.mode_manager.zoom_anchor)
|
||||
|
||||
elif self.mode_manager.mode == ModeManager.NAVIGATION:
|
||||
if self.zoom_locked:
|
||||
return self.repaint()
|
||||
offset = self.mode_manager.mouse_offset(event.pos())
|
||||
if offset is not None:
|
||||
self.viewportmapper.origin = (
|
||||
self.viewportmapper.origin - offset)
|
||||
|
||||
self.repaint()
|
||||
|
||||
def call_context_menu(self):
|
||||
if not self.editable:
|
||||
return
|
||||
|
||||
self.context_menu = PickerMenu()
|
||||
position = get_cursor(self)
|
||||
|
||||
method = partial(self.add_button, position, button_type=0)
|
||||
self.context_menu.add_single.triggered.connect(method)
|
||||
self.context_menu.add_single.setEnabled(bool(cmds.ls(selection=True)))
|
||||
|
||||
method = partial(self.add_button, position, button_type=1)
|
||||
self.context_menu.add_multiple.triggered.connect(method)
|
||||
state = len(cmds.ls(selection=True)) > 1
|
||||
self.context_menu.add_multiple.setEnabled(state)
|
||||
|
||||
method = partial(self.add_button, position, button_type=2)
|
||||
self.context_menu.add_command.triggered.connect(method)
|
||||
|
||||
method = partial(self.updateButtonRequested.emit, self.clicked_shape)
|
||||
self.context_menu.update_button.triggered.connect(method)
|
||||
state = bool(self.clicked_shape) and bool(cmds.ls(selection=True))
|
||||
self.context_menu.update_button.setEnabled(state)
|
||||
|
||||
method = self.deleteButtonRequested.emit
|
||||
self.context_menu.delete_selected.triggered.connect(method)
|
||||
|
||||
if self.layers_menu.displayed:
|
||||
self.context_menu.addMenu(self.layers_menu)
|
||||
self.context_menu.exec_(QtGui.QCursor.pos())
|
||||
|
||||
def add_button(self, position, button_type=0):
|
||||
"""
|
||||
Button types:
|
||||
0 = Single button from selection.
|
||||
1 = Multiple buttons from selection.
|
||||
2 = Command button.
|
||||
"""
|
||||
position = self.viewportmapper.to_units_coords(position).toPoint()
|
||||
self.addButtonRequested.emit(position.x(), position.y(), button_type)
|
||||
|
||||
def paintEvent(self, event):
|
||||
try:
|
||||
painter = QtGui.QPainter()
|
||||
painter.begin(self)
|
||||
painter.setRenderHints(QtGui.QPainter.Antialiasing)
|
||||
if not self.shapes:
|
||||
return
|
||||
hidden_layers = self.layers_menu.hidden_layers
|
||||
for shape in self.shapes:
|
||||
visible = (
|
||||
not shape.visibility_layer() or
|
||||
not shape.visibility_layer() in hidden_layers)
|
||||
if not visible:
|
||||
continue
|
||||
draw_shape(painter, shape, self.viewportmapper)
|
||||
self.selection_square.draw(painter)
|
||||
except BaseException:
|
||||
pass # avoid crash
|
||||
# TODO: log the error
|
||||
finally:
|
||||
painter.end()
|
||||
|
||||
|
||||
class PickerMenu(QtWidgets.QMenu):
|
||||
def __init__(self, parent=None):
|
||||
super(PickerMenu, self).__init__(parent)
|
||||
self.add_single = QtWidgets.QAction('Add single button', self)
|
||||
self.add_multiple = QtWidgets.QAction('Add multiple buttons', self)
|
||||
self.update_button = QtWidgets.QAction('Update button', self)
|
||||
self.add_command = QtWidgets.QAction('Add command', self)
|
||||
text = 'Delete selected button(s)'
|
||||
self.delete_selected = QtWidgets.QAction(text, self)
|
||||
|
||||
self.addAction(self.add_single)
|
||||
self.addAction(self.add_multiple)
|
||||
self.addAction(self.update_button)
|
||||
self.addSeparator()
|
||||
self.addAction(self.add_command)
|
||||
self.addSeparator()
|
||||
self.addAction(self.delete_selected)
|
||||
|
||||
|
||||
class ModeManager:
|
||||
FLY_OVER = 'fly_over'
|
||||
SELECTION = 'selection'
|
||||
NAVIGATION = 'navigation'
|
||||
DRAGGING = 'dragging'
|
||||
ZOOMING = 'zooming'
|
||||
|
||||
def __init__(self):
|
||||
self.shapes = []
|
||||
self.left_click_pressed = False
|
||||
self.right_click_pressed = False
|
||||
self.middle_click_pressed = False
|
||||
self.mouse_ghost = None
|
||||
self.has_shape_hovered = False
|
||||
self.dragging = False
|
||||
self.anchor = None
|
||||
self.zoom_anchor = None
|
||||
|
||||
@property
|
||||
def ctrl_pressed(self):
|
||||
modifiers = QtWidgets.QApplication.keyboardModifiers()
|
||||
return modifiers == (modifiers | QtCore.Qt.ControlModifier)
|
||||
|
||||
@property
|
||||
def shift_pressed(self):
|
||||
modifiers = QtWidgets.QApplication.keyboardModifiers()
|
||||
return modifiers == (modifiers | QtCore.Qt.ShiftModifier)
|
||||
|
||||
@property
|
||||
def alt_pressed(self):
|
||||
modifiers = QtWidgets.QApplication.keyboardModifiers()
|
||||
return modifiers == (modifiers | QtCore.Qt.AltModifier)
|
||||
|
||||
def update(
|
||||
self,
|
||||
event,
|
||||
pressed=False,
|
||||
has_shape_hovered=False,
|
||||
dragging=False):
|
||||
|
||||
self.dragging = dragging
|
||||
self.has_shape_hovered = has_shape_hovered
|
||||
self.update_mouse(event, pressed)
|
||||
|
||||
def update_mouse(self, event, pressed):
|
||||
if event.button() == QtCore.Qt.LeftButton:
|
||||
self.left_click_pressed = pressed
|
||||
self.anchor = event.pos() if self.dragging else None
|
||||
elif event.button() == QtCore.Qt.RightButton:
|
||||
self.right_click_pressed = pressed
|
||||
elif event.button() == QtCore.Qt.MiddleButton:
|
||||
self.middle_click_pressed = pressed
|
||||
if self.zoom_button_pressed:
|
||||
self.zoom_anchor = event.pos() if pressed else None
|
||||
|
||||
@property
|
||||
def mode(self):
|
||||
if self.dragging:
|
||||
return ModeManager.DRAGGING
|
||||
elif self.zoom_button_pressed and self.alt_pressed:
|
||||
return ModeManager.ZOOMING
|
||||
elif self.middle_click_pressed:
|
||||
return ModeManager.NAVIGATION
|
||||
elif self.left_click_pressed:
|
||||
return ModeManager.SELECTION
|
||||
self.mouse_ghost = None
|
||||
return ModeManager.FLY_OVER
|
||||
|
||||
def mouse_offset(self, position):
|
||||
result = position - self.mouse_ghost if self.mouse_ghost else None
|
||||
self.mouse_ghost = position
|
||||
return result or None
|
||||
|
||||
@property
|
||||
def zoom_button_pressed(self):
|
||||
button = cmds.optionVar(query=ZOOM_BUTTON)
|
||||
return any((
|
||||
button == 'left' and self.left_click_pressed,
|
||||
button == 'middle' and self.middle_click_pressed,
|
||||
button == 'right' and self.right_click_pressed))
|
||||
|
||||
|
||||
class VisibilityLayersMenu(QtWidgets.QMenu):
|
||||
def __init__(self, parent=None):
|
||||
super(VisibilityLayersMenu, self).__init__('Visibility layers', parent)
|
||||
self.hidden_layers = []
|
||||
self.displayed = False
|
||||
|
||||
def set_shapes(self, shapes):
|
||||
layers = sorted(
|
||||
{s.visibility_layer() for s in shapes if s.visibility_layer()})
|
||||
self.clear()
|
||||
action = QtWidgets.QAction('Show all')
|
||||
for layer in layers:
|
||||
action = QtWidgets.QAction(layer, self)
|
||||
action.setCheckable(True)
|
||||
action.setChecked(layer not in self.hidden_layers)
|
||||
action.toggled.connect(partial(self.set_hidden_layer, layer))
|
||||
self.addAction(action)
|
||||
self.displayed = bool(layers)
|
||||
|
||||
def set_hidden_layer(self, layer, state):
|
||||
if state is False and layer not in self.hidden_layers:
|
||||
self.hidden_layers.append(layer)
|
||||
if state is True and layer in self.hidden_layers:
|
||||
self.hidden_layers.remove(layer)
|
279
Scripts/Animation/dwpicker/dwpicker/preference.py
Normal file
@ -0,0 +1,279 @@
|
||||
import os
|
||||
from PySide2 import QtWidgets, QtCore
|
||||
from maya import cmds
|
||||
from dwpicker.hotkeyseditor import HotkeysEditor
|
||||
from dwpicker.optionvar import (
|
||||
save_optionvar,
|
||||
AUTO_COLLAPSE_IMG_PATH_FROM_ENV, AUTO_FOCUS_BEHAVIOR, AUTO_SET_NAMESPACE,
|
||||
AUTO_FOCUS_BEHAVIORS, AUTO_SWITCH_TAB, CHECK_IMAGES_PATHS,
|
||||
CUSTOM_PROD_PICKER_DIRECTORY, CHECK_FOR_UPDATE, DISPLAY_QUICK_OPTIONS,
|
||||
DISABLE_IMPORT_CALLBACKS, OVERRIDE_PROD_PICKER_DIRECTORY_ENV,
|
||||
INSERT_TAB_AFTER_CURRENT, NAMESPACE_TOOLBAR, SYNCHRONYZE_SELECTION,
|
||||
TRIGGER_REPLACE_ON_MIRROR, USE_BASE64_DATA_ENCODING,
|
||||
USE_PROD_PICKER_DIR_AS_DEFAULT, USE_ICON_FOR_UNSAVED_TAB,
|
||||
WARN_ON_TAB_CLOSED, ZOOM_SENSITIVITY, ZOOM_BUTTON, ZOOM_BUTTONS)
|
||||
from dwpicker.path import unix_path
|
||||
|
||||
|
||||
MAX_SENSITIVITY = 500
|
||||
AUTO_FOCUSES = {
|
||||
'Disable': AUTO_FOCUS_BEHAVIORS[0],
|
||||
'Bilateral': AUTO_FOCUS_BEHAVIORS[1],
|
||||
'From picker to Maya only': AUTO_FOCUS_BEHAVIORS[2]}
|
||||
|
||||
|
||||
class PreferencesWindow(QtWidgets.QWidget):
|
||||
need_update_callbacks = QtCore.Signal()
|
||||
hotkey_changed = QtCore.Signal()
|
||||
|
||||
def __init__(self, callback=None, parent=None):
|
||||
super(PreferencesWindow, self).__init__(parent, QtCore.Qt.Tool)
|
||||
self.setWindowTitle("Preferences")
|
||||
self.general_preferences = GeneralPreferences(callback)
|
||||
self.general_preferences.disable_import_callbacks.released.connect(
|
||||
self.need_update_callbacks.emit)
|
||||
self.hotkeys_editor = HotkeysEditor()
|
||||
self.hotkeys_editor.hotkey_changed.connect(self.hotkey_changed.emit)
|
||||
|
||||
tab = QtWidgets.QTabWidget()
|
||||
tab.addTab(self.general_preferences, 'General')
|
||||
tab.addTab(self.hotkeys_editor, 'Hotkeys')
|
||||
|
||||
layout = QtWidgets.QHBoxLayout(self)
|
||||
layout.addWidget(tab)
|
||||
|
||||
|
||||
class GeneralPreferences(QtWidgets.QWidget):
|
||||
|
||||
def __init__(self, callback=None, parent=None):
|
||||
super(GeneralPreferences, self).__init__(parent)
|
||||
self.callback = callback
|
||||
|
||||
text = "Display namespace toolbar."
|
||||
self.namespace_toolbar = QtWidgets.QCheckBox(text)
|
||||
self.quick_options = QtWidgets.QCheckBox("Display quick options.")
|
||||
text = "Auto switch tab with selection."
|
||||
self.autoswitch_tab = QtWidgets.QCheckBox(text)
|
||||
text = "Auto switch namespace."
|
||||
self.autoswitch_namespace = QtWidgets.QCheckBox(text)
|
||||
self.sychronize = QtWidgets.QCheckBox("Synchronize picker selection.")
|
||||
text = "Missing images warning."
|
||||
self.check_images_paths = QtWidgets.QCheckBox(text)
|
||||
text = "Disable callback at import time. (Use with Studio Library)"
|
||||
self.disable_import_callbacks = QtWidgets.QCheckBox(text)
|
||||
text = "Use icon to mark unsaved tab."
|
||||
self.unsaved_tab_icon = QtWidgets.QCheckBox(text)
|
||||
text = "Insert new tab after current tab."
|
||||
self.insert_after_current = QtWidgets.QCheckBox(text)
|
||||
text = "Warning before closing a tab."
|
||||
self.warn_on_tab_close = QtWidgets.QCheckBox(text)
|
||||
self.ui_group = QtWidgets.QGroupBox("Ui")
|
||||
self.ui_layout = QtWidgets.QVBoxLayout(self.ui_group)
|
||||
self.ui_layout.addWidget(self.namespace_toolbar)
|
||||
self.ui_layout.addWidget(self.quick_options)
|
||||
self.ui_layout.addWidget(self.disable_import_callbacks)
|
||||
self.ui_layout.addWidget(self.autoswitch_namespace)
|
||||
self.ui_layout.addWidget(self.autoswitch_tab)
|
||||
self.ui_layout.addWidget(self.sychronize)
|
||||
self.ui_layout.addWidget(self.check_images_paths)
|
||||
self.ui_layout.addWidget(self.unsaved_tab_icon)
|
||||
self.ui_layout.addWidget(self.insert_after_current)
|
||||
self.ui_layout.addWidget(self.warn_on_tab_close)
|
||||
|
||||
notfound = "environment variable not found"
|
||||
text = '$DWPICKER_PROJECT_DIRECTORY:{}'.format(
|
||||
os.getenv("DWPICKER_PROJECT_DIRECTORY", notfound))
|
||||
self.project_dir_env = QtWidgets.QLineEdit(text)
|
||||
self.project_dir_env.setReadOnly(True)
|
||||
text = (
|
||||
"Auto-collapse path with environment "
|
||||
"variable $DWPICKER_PROJECT_DIRECTORY")
|
||||
self.auto_collapse_path = QtWidgets.QCheckBox(text)
|
||||
text = "Override $DWPICKER_PROJECT_DIRECTORY"
|
||||
self.override_variable = QtWidgets.QCheckBox(text)
|
||||
self.custom_prod_path = QtWidgets.QLineEdit()
|
||||
text = "Force file dialog to use this directory"
|
||||
self.force_file_dialog_directory = QtWidgets.QCheckBox(text)
|
||||
|
||||
custom_path_layout = QtWidgets.QHBoxLayout()
|
||||
custom_path_layout.setContentsMargins(0, 0, 0, 0)
|
||||
custom_path_layout.addWidget(self.override_variable)
|
||||
custom_path_layout.addWidget(self.custom_prod_path)
|
||||
|
||||
self.env_group = QtWidgets.QGroupBox("Environment Variables")
|
||||
self.env_layout = QtWidgets.QVBoxLayout(self.env_group)
|
||||
self.env_layout.addWidget(self.project_dir_env)
|
||||
self.env_layout.addWidget(self.auto_collapse_path)
|
||||
self.env_layout.addLayout(custom_path_layout)
|
||||
self.env_layout.addWidget(self.force_file_dialog_directory)
|
||||
|
||||
text = "Encode in-scene data as base64."
|
||||
self.use_base64_encoding = QtWidgets.QCheckBox(text)
|
||||
|
||||
self.data_group = QtWidgets.QGroupBox("Data")
|
||||
self.data_layout = QtWidgets.QVBoxLayout(self.data_group)
|
||||
self.data_layout.addWidget(self.use_base64_encoding)
|
||||
|
||||
self.auto_focus = QtWidgets.QComboBox()
|
||||
self.auto_focus.addItems(list(AUTO_FOCUSES))
|
||||
|
||||
self.focus_group = QtWidgets.QGroupBox("Auto-focus")
|
||||
self.focus_layout = QtWidgets.QFormLayout(self.focus_group)
|
||||
self.focus_layout.addRow("Behavior", self.auto_focus)
|
||||
|
||||
msg = "Prompt search and replace after mirror."
|
||||
self.search_on_mirror = QtWidgets.QCheckBox(msg)
|
||||
self.advanced_group = QtWidgets.QGroupBox("Advanced editor")
|
||||
self.advanced_layout = QtWidgets.QVBoxLayout(self.advanced_group)
|
||||
self.advanced_layout.addWidget(self.search_on_mirror)
|
||||
|
||||
self.zoom_sensitivity = QtWidgets.QSlider(QtCore.Qt.Horizontal)
|
||||
self.zoom_sensitivity.setMaximum(MAX_SENSITIVITY)
|
||||
self.zoom_sensitivity.setMinimum(1)
|
||||
self.zoom_sensitivity.setSingleStep(1)
|
||||
self.zoom_button = QtWidgets.QComboBox()
|
||||
for item in ZOOM_BUTTONS:
|
||||
self.zoom_button.addItem(item)
|
||||
|
||||
self.zoom_group = QtWidgets.QGroupBox("Zoom options")
|
||||
self.zoom_layout = QtWidgets.QFormLayout(self.zoom_group)
|
||||
self.zoom_layout.addRow("Sensitivity", self.zoom_sensitivity)
|
||||
self.zoom_layout.addRow("Mouse button", self.zoom_button)
|
||||
|
||||
msg = "Check for new version at startup."
|
||||
self.check_for_update = QtWidgets.QCheckBox(msg)
|
||||
self.update_group = QtWidgets.QGroupBox("Update check")
|
||||
self.update_layout = QtWidgets.QVBoxLayout(self.update_group)
|
||||
self.update_layout.addWidget(self.check_for_update)
|
||||
|
||||
central_widget = QtWidgets.QWidget()
|
||||
self.sublayout = QtWidgets.QVBoxLayout(central_widget)
|
||||
self.sublayout.addWidget(self.ui_group)
|
||||
self.sublayout.addWidget(self.env_group)
|
||||
self.sublayout.addWidget(self.data_group)
|
||||
self.sublayout.addWidget(self.focus_group)
|
||||
self.sublayout.addWidget(self.advanced_group)
|
||||
self.sublayout.addWidget(self.zoom_group)
|
||||
self.sublayout.addWidget(self.update_group)
|
||||
|
||||
scroll = QtWidgets.QScrollArea()
|
||||
scroll.setWidgetResizable(True)
|
||||
scroll.setWidget(central_widget)
|
||||
scroll.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
|
||||
scroll.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)
|
||||
|
||||
layout = QtWidgets.QVBoxLayout(self)
|
||||
layout.addWidget(scroll)
|
||||
|
||||
self.load_ui_states()
|
||||
|
||||
self.auto_collapse_path.released.connect(self.save_ui_states)
|
||||
self.autoswitch_tab.released.connect(self.save_ui_states)
|
||||
self.autoswitch_namespace.released.connect(self.save_ui_states)
|
||||
self.auto_focus.currentIndexChanged.connect(self.save_ui_states)
|
||||
self.check_for_update.released.connect(self.save_ui_states)
|
||||
self.check_images_paths.released.connect(self.save_ui_states)
|
||||
self.custom_prod_path.textEdited.connect(self.save_ui_states)
|
||||
self.disable_import_callbacks.released.connect(self.save_ui_states)
|
||||
self.force_file_dialog_directory.released.connect(self.save_ui_states)
|
||||
self.override_variable.released.connect(self.save_ui_states)
|
||||
self.insert_after_current.released.connect(self.save_ui_states)
|
||||
self.quick_options.released.connect(self.save_ui_states)
|
||||
self.namespace_toolbar.released.connect(self.save_ui_states)
|
||||
self.use_base64_encoding.released.connect(self.save_ui_states)
|
||||
self.unsaved_tab_icon.released.connect(self.save_ui_states)
|
||||
self.sychronize.released.connect(self.save_ui_states)
|
||||
self.search_on_mirror.released.connect(self.save_ui_states)
|
||||
self.warn_on_tab_close.released.connect(self.save_ui_states)
|
||||
self.zoom_sensitivity.valueChanged.connect(self.save_ui_states)
|
||||
self.zoom_button.currentIndexChanged.connect(self.save_ui_states)
|
||||
|
||||
def sizeHint(self):
|
||||
return QtCore.QSize(520, 600)
|
||||
|
||||
def load_ui_states(self):
|
||||
state = bool(cmds.optionVar(query=AUTO_COLLAPSE_IMG_PATH_FROM_ENV))
|
||||
self.auto_collapse_path.setChecked(state)
|
||||
value = cmds.optionVar(query=AUTO_FOCUS_BEHAVIOR)
|
||||
text = {v: k for k, v in AUTO_FOCUSES.items()}[value]
|
||||
self.auto_focus.setCurrentText(text)
|
||||
state = bool(cmds.optionVar(query=AUTO_SET_NAMESPACE))
|
||||
self.autoswitch_namespace.setChecked(state)
|
||||
state = bool(cmds.optionVar(query=AUTO_SWITCH_TAB))
|
||||
self.autoswitch_tab.setChecked(state)
|
||||
state = bool(cmds.optionVar(query=DISABLE_IMPORT_CALLBACKS))
|
||||
self.disable_import_callbacks.setChecked(state)
|
||||
value = cmds.optionVar(query=CUSTOM_PROD_PICKER_DIRECTORY)
|
||||
self.custom_prod_path.setText(value)
|
||||
state = bool(cmds.optionVar(query=CHECK_IMAGES_PATHS))
|
||||
self.check_images_paths.setChecked(state)
|
||||
state = bool(cmds.optionVar(query=CHECK_FOR_UPDATE))
|
||||
self.check_for_update.setChecked(state)
|
||||
state = bool(cmds.optionVar(query=USE_PROD_PICKER_DIR_AS_DEFAULT))
|
||||
self.force_file_dialog_directory.setChecked(state)
|
||||
state = bool(cmds.optionVar(query=OVERRIDE_PROD_PICKER_DIRECTORY_ENV))
|
||||
self.override_variable.setChecked(state)
|
||||
self.custom_prod_path.setEnabled(state)
|
||||
state = bool(cmds.optionVar(query=NAMESPACE_TOOLBAR))
|
||||
self.namespace_toolbar.setChecked(state)
|
||||
state = bool(cmds.optionVar(query=DISPLAY_QUICK_OPTIONS))
|
||||
self.quick_options.setChecked(state)
|
||||
state = bool(cmds.optionVar(query=SYNCHRONYZE_SELECTION))
|
||||
self.sychronize.setChecked(state)
|
||||
state = bool(cmds.optionVar(query=USE_BASE64_DATA_ENCODING))
|
||||
self.use_base64_encoding.setChecked(state)
|
||||
state = bool(cmds.optionVar(query=USE_ICON_FOR_UNSAVED_TAB))
|
||||
self.unsaved_tab_icon.setChecked(state)
|
||||
state = bool(cmds.optionVar(query=WARN_ON_TAB_CLOSED))
|
||||
self.warn_on_tab_close.setChecked(state)
|
||||
state = bool(cmds.optionVar(query=INSERT_TAB_AFTER_CURRENT))
|
||||
self.insert_after_current.setChecked(state)
|
||||
state = bool(cmds.optionVar(query=TRIGGER_REPLACE_ON_MIRROR))
|
||||
self.search_on_mirror.setChecked(state)
|
||||
|
||||
value = MAX_SENSITIVITY - cmds.optionVar(query=ZOOM_SENSITIVITY)
|
||||
self.zoom_sensitivity.setSliderPosition(value)
|
||||
value = cmds.optionVar(query=ZOOM_BUTTON)
|
||||
self.zoom_button.setCurrentText(value)
|
||||
|
||||
def save_ui_states(self, *_):
|
||||
value = int(self.auto_collapse_path.isChecked())
|
||||
save_optionvar(AUTO_COLLAPSE_IMG_PATH_FROM_ENV, value)
|
||||
value = AUTO_FOCUSES[self.auto_focus.currentText()]
|
||||
save_optionvar(AUTO_FOCUS_BEHAVIOR, value)
|
||||
value = int(self.autoswitch_namespace.isChecked())
|
||||
save_optionvar(AUTO_SET_NAMESPACE, value)
|
||||
value = int(self.autoswitch_tab.isChecked())
|
||||
save_optionvar(AUTO_SWITCH_TAB, value)
|
||||
value = int(self.check_images_paths.isChecked())
|
||||
save_optionvar(CHECK_IMAGES_PATHS, value)
|
||||
value = int(self.check_for_update.isChecked())
|
||||
save_optionvar(CHECK_FOR_UPDATE, value)
|
||||
value = unix_path(self.custom_prod_path.text())
|
||||
save_optionvar(CUSTOM_PROD_PICKER_DIRECTORY, value)
|
||||
value = int(self.insert_after_current.isChecked())
|
||||
save_optionvar(INSERT_TAB_AFTER_CURRENT, value)
|
||||
value = int(self.disable_import_callbacks.isChecked())
|
||||
save_optionvar(DISABLE_IMPORT_CALLBACKS, value)
|
||||
value = int(self.force_file_dialog_directory.isChecked())
|
||||
save_optionvar(USE_PROD_PICKER_DIR_AS_DEFAULT, value)
|
||||
value = self.override_variable.isChecked()
|
||||
save_optionvar(OVERRIDE_PROD_PICKER_DIRECTORY_ENV, int(value))
|
||||
self.custom_prod_path.setEnabled(value)
|
||||
value = int(self.quick_options.isChecked())
|
||||
save_optionvar(DISPLAY_QUICK_OPTIONS, value)
|
||||
value = int(self.namespace_toolbar.isChecked())
|
||||
save_optionvar(NAMESPACE_TOOLBAR, value)
|
||||
value = int(self.use_base64_encoding.isChecked())
|
||||
save_optionvar(USE_BASE64_DATA_ENCODING, value)
|
||||
value = int(self.unsaved_tab_icon.isChecked())
|
||||
save_optionvar(USE_ICON_FOR_UNSAVED_TAB, value)
|
||||
value = int(self.search_on_mirror.isChecked())
|
||||
save_optionvar(TRIGGER_REPLACE_ON_MIRROR, value)
|
||||
value = int(self.warn_on_tab_close.isChecked())
|
||||
save_optionvar(WARN_ON_TAB_CLOSED, value)
|
||||
save_optionvar(ZOOM_BUTTON, self.zoom_button.currentText())
|
||||
value = MAX_SENSITIVITY - int(self.zoom_sensitivity.value()) + 1
|
||||
save_optionvar(ZOOM_SENSITIVITY, value)
|
||||
if self.callback:
|
||||
self.callback()
|
14
Scripts/Animation/dwpicker/dwpicker/qt_remapping/PySide2.py
Normal file
@ -0,0 +1,14 @@
|
||||
|
||||
from PySide6 import QtCore, QtGui, QtWidgets
|
||||
from PySide6 import __version__
|
||||
|
||||
|
||||
QtWidgets.QShortcut = QtGui.QShortcut
|
||||
QtWidgets.QAction = QtGui.QAction
|
||||
|
||||
QtGui.QMouseEvent.pos = QtGui.QMouseEvent.position
|
||||
QtGui.QMouseEvent.globalPos = QtGui.QMouseEvent.globalPosition
|
||||
|
||||
QtGui.QWheelEvent.pos = QtGui.QWheelEvent.position
|
||||
|
||||
QtCore.Qt.BackgroundColorRole = QtCore.Qt.BackgroundRole
|
@ -0,0 +1 @@
|
||||
from shiboken6 import wrapInstance
|
109
Scripts/Animation/dwpicker/dwpicker/qtutils.py
Normal file
@ -0,0 +1,109 @@
|
||||
import inspect
|
||||
import os
|
||||
import sys
|
||||
from PySide2 import QtGui, QtWidgets, QtCore
|
||||
from maya import cmds
|
||||
import maya.OpenMayaUI as omui
|
||||
from maya.app.general.mayaMixin import MayaQWidgetDockableMixin
|
||||
import shiboken2
|
||||
|
||||
|
||||
# Ensure backward compatibility.
|
||||
if sys.version_info[0] == 3:
|
||||
long = int
|
||||
|
||||
|
||||
VALIGNS = {
|
||||
'top': QtCore.Qt.AlignTop,
|
||||
'center': QtCore.Qt.AlignVCenter,
|
||||
'bottom': QtCore.Qt.AlignBottom}
|
||||
HALIGNS = {
|
||||
'left': QtCore.Qt.AlignLeft,
|
||||
'center': QtCore.Qt.AlignHCenter,
|
||||
'right': QtCore.Qt.AlignRight}
|
||||
HERE = os.path.dirname(__file__)
|
||||
ERROR_IMPORT_MSG = ('''
|
||||
ERROR: Dwpicker: DwPicker is not found in Python paths.
|
||||
- Please use sys.path.append('<dwpicker forlder>') and relaunch it.
|
||||
- Or add '<picker folder>' to environment variable PYTHONPATH''')
|
||||
|
||||
RESTORE_CMD = ("""
|
||||
try:
|
||||
import {0}
|
||||
{0}.{1}.restore()
|
||||
except ImportError:
|
||||
print("{2}")
|
||||
""")
|
||||
mixin_windows = {}
|
||||
|
||||
|
||||
if sys.version_info[0] != 2:
|
||||
long = int
|
||||
|
||||
|
||||
def icon(filename):
|
||||
return QtGui.QIcon(os.path.join(HERE, 'icons', filename))
|
||||
|
||||
|
||||
def get_cursor(widget):
|
||||
return widget.mapFromGlobal(QtGui.QCursor.pos())
|
||||
|
||||
|
||||
def set_shortcut(keysequence, parent, method, context=None):
|
||||
shortcut = QtWidgets.QShortcut(QtGui.QKeySequence(keysequence), parent)
|
||||
shortcut.setContext(context or QtCore.Qt.WidgetWithChildrenShortcut)
|
||||
shortcut.activated.connect(method)
|
||||
return shortcut
|
||||
|
||||
|
||||
def remove_workspace_control(control_name):
|
||||
workspace_control_name = control_name + "WorkspaceControl"
|
||||
cmds.deleteUI(workspace_control_name, control=True)
|
||||
|
||||
|
||||
def maya_main_window():
|
||||
ptr = omui.MQtUtil.mainWindow()
|
||||
if ptr is not None:
|
||||
return shiboken2.wrapInstance(long(ptr), QtWidgets.QWidget)
|
||||
|
||||
|
||||
class DockableBase(MayaQWidgetDockableMixin):
|
||||
"""
|
||||
Code from https://kainev.com/qt-for-maya-dockable-windows
|
||||
Thanks for this !
|
||||
|
||||
Convenience class for creating dockable Maya windows.
|
||||
"""
|
||||
|
||||
def __init__(self, control_name, **kwargs):
|
||||
super(DockableBase, self).__init__(**kwargs)
|
||||
self.setObjectName(control_name)
|
||||
|
||||
def show(self, dockable=True, *_, **kwargs):
|
||||
"""
|
||||
Show UI with generated uiScript argument
|
||||
"""
|
||||
modulename = inspect.getmodule(self).__name__
|
||||
classname = self.__class__.__name__
|
||||
command = RESTORE_CMD.format(modulename, classname, ERROR_IMPORT_MSG)
|
||||
super(DockableBase, self).show(
|
||||
dockable=dockable, uiScript=command, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def restore(cls):
|
||||
"""
|
||||
Internal method to restore the UI when Maya is opened.
|
||||
"""
|
||||
# Create UI instance
|
||||
instance = cls()
|
||||
# Get the empty WorkspaceControl created by Maya
|
||||
workspace_control = omui.MQtUtil.getCurrentParent()
|
||||
# Grab the pointer to our instance as a Maya object
|
||||
mixinPtr = omui.MQtUtil.findControl(instance.objectName())
|
||||
# Add our UI to the WorkspaceControl
|
||||
omui.MQtUtil.addWidgetToMayaLayout(
|
||||
long(mixinPtr), long(workspace_control))
|
||||
# Store reference to UI
|
||||
global mixin_windows
|
||||
mixin_windows[instance.objectName()] = instance
|
||||
|
120
Scripts/Animation/dwpicker/dwpicker/quick.py
Normal file
@ -0,0 +1,120 @@
|
||||
from PySide2 import QtWidgets, QtGui, QtCore
|
||||
from maya import cmds
|
||||
|
||||
from dwpicker.colorwheel import ColorDialog
|
||||
from dwpicker.optionvar import (
|
||||
save_optionvar, DEFAULT_LABEL, DEFAULT_HEIGHT, DEFAULT_WIDTH,
|
||||
DEFAULT_TEXT_COLOR, DEFAULT_BG_COLOR)
|
||||
|
||||
|
||||
class QuickOptions(QtWidgets.QWidget):
|
||||
def __init__(self, parent=None):
|
||||
super(QuickOptions, self).__init__(parent=parent)
|
||||
self.bg_color = ColorButton()
|
||||
self.bg_color.colorChanged.connect(self.save_ui_states)
|
||||
self.text_color = ColorButton()
|
||||
self.text_color.colorChanged.connect(self.save_ui_states)
|
||||
validator = QtGui.QIntValidator()
|
||||
self.width = QtWidgets.QLineEdit()
|
||||
self.width.returnPressed.connect(self.save_ui_states)
|
||||
self.width.setValidator(validator)
|
||||
self.width.setFixedWidth(50)
|
||||
self.height = QtWidgets.QLineEdit()
|
||||
self.height.returnPressed.connect(self.save_ui_states)
|
||||
self.height.setValidator(validator)
|
||||
self.height.setFixedWidth(50)
|
||||
self.label = QtWidgets.QLineEdit()
|
||||
self.label.returnPressed.connect(self.save_ui_states)
|
||||
|
||||
self.layout = QtWidgets.QHBoxLayout(self)
|
||||
self.layout.setSpacing(0)
|
||||
self.layout.addWidget(QtWidgets.QLabel('Bg-color: '))
|
||||
self.layout.addWidget(self.bg_color)
|
||||
self.layout.addSpacing(12)
|
||||
self.layout.addWidget(QtWidgets.QLabel('Text-color: '))
|
||||
self.layout.addWidget(self.text_color)
|
||||
self.layout.addSpacing(12)
|
||||
self.layout.addWidget(QtWidgets.QLabel('Size: '))
|
||||
self.layout.addWidget(self.width)
|
||||
self.layout.addWidget(self.height)
|
||||
self.layout.addSpacing(12)
|
||||
self.layout.addWidget(QtWidgets.QLabel('Label: '))
|
||||
self.layout.addWidget(self.label)
|
||||
|
||||
self.load_ui_states()
|
||||
|
||||
def save_ui_states(self, *_):
|
||||
values = self.values
|
||||
save_optionvar(DEFAULT_BG_COLOR, values['bgcolor.normal'])
|
||||
save_optionvar(DEFAULT_TEXT_COLOR, values['text.color'])
|
||||
save_optionvar(DEFAULT_WIDTH, values['shape.width'])
|
||||
save_optionvar(DEFAULT_HEIGHT, values['shape.height'])
|
||||
save_optionvar(DEFAULT_LABEL, values['text.content'])
|
||||
|
||||
def load_ui_states(self):
|
||||
self.values = {
|
||||
'bgcolor.normal': cmds.optionVar(query=DEFAULT_BG_COLOR),
|
||||
'text.color': cmds.optionVar(query=DEFAULT_TEXT_COLOR),
|
||||
'shape.width': cmds.optionVar(query=DEFAULT_WIDTH),
|
||||
'shape.height': cmds.optionVar(query=DEFAULT_HEIGHT),
|
||||
'text.content': cmds.optionVar(query=DEFAULT_LABEL)}
|
||||
|
||||
@property
|
||||
def values(self):
|
||||
return {
|
||||
'bgcolor.normal': self.bg_color.name,
|
||||
'text.color': self.text_color.name,
|
||||
'shape.width': int(self.width.text()) if self.width.text() else 10,
|
||||
'shape.height': int(self.height.text()) if self.height.text() else 10,
|
||||
'text.content': self.label.text()}
|
||||
|
||||
@values.setter
|
||||
def values(self, values):
|
||||
self.bg_color.name = values['bgcolor.normal']
|
||||
self.text_color.name = values['text.color']
|
||||
self.width.setText(str(values['shape.width']))
|
||||
self.height.setText(str(values['shape.height']))
|
||||
self.label.setText(str(values['text.content']))
|
||||
|
||||
|
||||
class ColorButton(QtWidgets.QAbstractButton):
|
||||
colorChanged = QtCore.Signal()
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(ColorButton, self).__init__(parent=parent)
|
||||
self.setFixedSize(20, 20)
|
||||
self.color = QtGui.QColor(QtCore.Qt.black)
|
||||
self.released.connect(self.pick_color)
|
||||
|
||||
def pick_color(self):
|
||||
dialog = ColorDialog(self.name)
|
||||
if not dialog.exec_():
|
||||
return
|
||||
self.name = dialog.colorname()
|
||||
self.colorChanged.emit()
|
||||
self.repaint()
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self.color.name()
|
||||
|
||||
@name.setter
|
||||
def name(self, value):
|
||||
self.color.setNamedColor(value)
|
||||
|
||||
def paintEvent(self, _):
|
||||
try:
|
||||
painter = QtGui.QPainter()
|
||||
painter.begin(self)
|
||||
painter.setBrush(QtGui.QBrush(self.color))
|
||||
if self.rect().contains(QtGui.QCursor.pos()):
|
||||
color = QtCore.Qt.transparent
|
||||
else:
|
||||
color = QtCore.Qt.gray
|
||||
painter.setPen(QtGui.QPen(color))
|
||||
painter.drawRect(self.rect())
|
||||
except BaseException:
|
||||
pass # avoid crash
|
||||
# TODO: log the error
|
||||
finally:
|
||||
painter.end()
|
42
Scripts/Animation/dwpicker/dwpicker/references.py
Normal file
@ -0,0 +1,42 @@
|
||||
|
||||
|
||||
import os
|
||||
from dwpicker.dialog import MissingImages
|
||||
from dwpicker.path import expand_path
|
||||
|
||||
|
||||
IMAGE_MISSING_WARNING = (
|
||||
'\nImage is not found.\nWould you like to set a new path ?')
|
||||
|
||||
|
||||
def ensure_images_path_exists(pickers):
|
||||
"""
|
||||
As images are stored as path in the picker, this function ensure the paths
|
||||
exists. If not, it proposes to set a new path. If more than an image is not
|
||||
found, it will automatically look up into directories given in previous
|
||||
repath to find the images.
|
||||
"""
|
||||
missing_images = list_missing_images(pickers)
|
||||
if not missing_images:
|
||||
return
|
||||
dialog = MissingImages(missing_images)
|
||||
if not dialog.exec_():
|
||||
return
|
||||
for picker_data in pickers:
|
||||
for shape in picker_data['shapes']:
|
||||
path = expand_path(shape['image.path'])
|
||||
if path in missing_images:
|
||||
new_path = dialog.output(path)
|
||||
if not new_path:
|
||||
continue
|
||||
shape['image.path'] = new_path
|
||||
return pickers
|
||||
|
||||
|
||||
def list_missing_images(pickers_data):
|
||||
return sorted(list(set([
|
||||
shape['image.path']
|
||||
for picker_data in pickers_data
|
||||
for shape in picker_data['shapes'] if
|
||||
shape['image.path'] and not
|
||||
os.path.exists(expand_path(shape['image.path']))])))
|
89
Scripts/Animation/dwpicker/dwpicker/scenedata.py
Normal file
@ -0,0 +1,89 @@
|
||||
import sys
|
||||
import json
|
||||
import base64
|
||||
|
||||
from maya import cmds
|
||||
|
||||
from dwpicker.compatibility import ensure_retro_compatibility
|
||||
from dwpicker.optionvar import USE_BASE64_DATA_ENCODING
|
||||
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):
|
||||
return data
|
||||
# Ensure backward compatibility.
|
||||
if sys.version_info[0] == 2:
|
||||
return base64.b64encode(bytes(data))
|
||||
return base64.b64encode(bytes(data, "utf-8"))
|
||||
|
||||
|
||||
def decode_data(data):
|
||||
try:
|
||||
return json.loads(data)
|
||||
except ValueError: # Happe if data encoded is encoded as base 64 string.
|
||||
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 BaseException:
|
||||
# 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")
|
127
Scripts/Animation/dwpicker/dwpicker/selection.py
Normal file
@ -0,0 +1,127 @@
|
||||
|
||||
from maya import cmds
|
||||
|
||||
|
||||
class NameclashError(BaseException):
|
||||
def __init__(self, nodes=None):
|
||||
self.clashes = [node for node in nodes or [] if len(cmds.ls(node)) > 1]
|
||||
message = 'Some nodes exists more than once:\n'
|
||||
nodes = '\n - '.join(self.clashes)
|
||||
super(NameclashError, self).__init__(message + nodes)
|
||||
|
||||
|
||||
def select_targets(shapes, selection_mode='replace'):
|
||||
shapes = [s for s in shapes if s.targets()]
|
||||
hovered = [s for s in shapes if s.hovered]
|
||||
targets = [t for s in hovered for t in s.targets() if cmds.objExists(t)]
|
||||
|
||||
if selection_mode in ('add', 'replace', 'invert'):
|
||||
try:
|
||||
return cmds.select(list(targets), add=selection_mode == 'add')
|
||||
except ValueError:
|
||||
raise NameclashError(targets)
|
||||
elif selection_mode == 'remove':
|
||||
selection = [n for n in cmds.ls(sl=True) if n not in targets]
|
||||
try:
|
||||
return cmds.select(selection)
|
||||
except ValueError:
|
||||
raise NameclashError(targets)
|
||||
|
||||
# Invert selection
|
||||
selected = [s for s in shapes if s.selected]
|
||||
to_select = [s for s in shapes if s in hovered and s not in selected]
|
||||
# List targets unaffected by selection
|
||||
targets = {
|
||||
t for s in selected for t in s.targets()
|
||||
if cmds.objExists(t) and not s.hovered}
|
||||
# List targets in reversed selection
|
||||
invert_t = {t for s in to_select for t in s.targets() if cmds.objExists(t)}
|
||||
targets.union(invert_t)
|
||||
try:
|
||||
cmds.select(targets)
|
||||
except ValueError:
|
||||
raise NameclashError(targets)
|
||||
return
|
||||
|
||||
|
||||
def select_shapes_from_selection(shapes):
|
||||
selection = cmds.ls(sl=True)
|
||||
for shape in shapes:
|
||||
if not shape.targets():
|
||||
shape.selected = False
|
||||
continue
|
||||
for target in shape.targets():
|
||||
if target not in selection:
|
||||
shape.selected = False
|
||||
break
|
||||
else:
|
||||
shape.selected = True
|
||||
|
||||
|
||||
class Selection():
|
||||
def __init__(self):
|
||||
self.shapes = []
|
||||
self.mode = 'replace'
|
||||
|
||||
def set(self, shapes):
|
||||
if self.mode == 'add':
|
||||
if shapes is None:
|
||||
return
|
||||
return self.add(shapes)
|
||||
elif self.mode == 'replace':
|
||||
if shapes is None:
|
||||
return self.clear()
|
||||
return self.replace(shapes)
|
||||
elif self.mode == 'invert':
|
||||
if shapes is None:
|
||||
return
|
||||
return self.invert(shapes)
|
||||
elif self.mode == 'remove':
|
||||
if shapes is None:
|
||||
return
|
||||
for shape in shapes:
|
||||
if shape in self.shapes:
|
||||
self.remove(shape)
|
||||
|
||||
def replace(self, shapes):
|
||||
self.shapes = shapes
|
||||
|
||||
def add(self, shapes):
|
||||
self.shapes.extend([s for s in shapes if s not in self])
|
||||
|
||||
def remove(self, shape):
|
||||
self.shapes.remove(shape)
|
||||
|
||||
def invert(self, shapes):
|
||||
for shape in shapes:
|
||||
if shape not in self.shapes:
|
||||
self.add([shape])
|
||||
else:
|
||||
self.remove(shape)
|
||||
|
||||
def clear(self):
|
||||
self.shapes = []
|
||||
|
||||
def __len__(self):
|
||||
return len(self.shapes)
|
||||
|
||||
def __bool__(self):
|
||||
return bool(self.shapes)
|
||||
|
||||
__nonzero__ = __bool__
|
||||
|
||||
def __getitem__(self, i):
|
||||
return self.shapes[i]
|
||||
|
||||
def __iter__(self):
|
||||
return self.shapes.__iter__()
|
||||
|
||||
|
||||
def get_selection_mode(ctrl, shift):
|
||||
if not ctrl and not shift:
|
||||
return 'replace'
|
||||
elif ctrl and shift:
|
||||
return 'invert'
|
||||
elif shift:
|
||||
return 'add'
|
||||
return 'remove'
|
130
Scripts/Animation/dwpicker/dwpicker/templates.py
Normal file
@ -0,0 +1,130 @@
|
||||
from dwpicker.appinfos import VERSION
|
||||
|
||||
|
||||
BUTTON = {
|
||||
'visibility_layer': None,
|
||||
'shape': 'square', # or round
|
||||
'shape.left': 0.0,
|
||||
'shape.top': 0.0,
|
||||
'shape.width': 120.0,
|
||||
'shape.height': 25.0,
|
||||
'shape.cornersx': 4,
|
||||
'shape.cornersy': 4,
|
||||
'border': True,
|
||||
'borderwidth.normal': 1.0,
|
||||
'borderwidth.hovered': 1.25,
|
||||
'borderwidth.clicked': 2,
|
||||
'bordercolor.normal': '#000000',
|
||||
'bordercolor.hovered': '#393939',
|
||||
'bordercolor.clicked': '#FFFFFF',
|
||||
'bordercolor.transparency': 0,
|
||||
'bgcolor.normal': '#888888',
|
||||
'bgcolor.hovered': '#AAAAAA',
|
||||
'bgcolor.clicked': '#DDDDDD',
|
||||
'bgcolor.transparency': 0,
|
||||
'text.content': 'Button',
|
||||
'text.size': 12,
|
||||
'text.bold': False,
|
||||
'text.italic': False,
|
||||
'text.color': '#FFFFFF',
|
||||
'text.valign': 'center', # or 'top' or bottom
|
||||
'text.halign': 'center', # or 'left' or 'right'
|
||||
'action.targets': [],
|
||||
'action.commands': [],
|
||||
'image.path': '',
|
||||
'image.fit': True,
|
||||
'image.height': 32,
|
||||
'image.width': 32}
|
||||
|
||||
|
||||
TEXT = {
|
||||
'visibility_layer': None,
|
||||
'shape': 'square', # or round
|
||||
'shape.left': 0.0,
|
||||
'shape.top': 0.0,
|
||||
'shape.width': 200.0,
|
||||
'shape.height': 50.0,
|
||||
'shape.cornersx': 4,
|
||||
'shape.cornersy': 4,
|
||||
'border': False,
|
||||
'borderwidth.normal': 0,
|
||||
'borderwidth.hovered': 0,
|
||||
'borderwidth.clicked': 0,
|
||||
'bordercolor.normal': '#000000',
|
||||
'bordercolor.hovered': '#393939',
|
||||
'bordercolor.clicked': '#FFFFFF',
|
||||
'bordercolor.transparency': 0,
|
||||
'bgcolor.normal': '#888888',
|
||||
'bgcolor.hovered': '#AAAAAA',
|
||||
'bgcolor.clicked': '#DDDDDD',
|
||||
'bgcolor.transparency': 255,
|
||||
'text.content': 'Text',
|
||||
'text.size': 16,
|
||||
'text.bold': True,
|
||||
'text.italic': False,
|
||||
'text.color': '#FFFFFF',
|
||||
'text.valign': 'top', # or 'top' or bottom
|
||||
'text.halign': 'left', # or 'left' or 'right'
|
||||
'action.targets': [],
|
||||
'action.commands': [],
|
||||
'image.path': '',
|
||||
'image.fit': False,
|
||||
'image.height': 32,
|
||||
'image.width': 32}
|
||||
|
||||
|
||||
BACKGROUND = {
|
||||
'visibility_layer': None,
|
||||
'shape': 'square', # or round or rounded_rect
|
||||
'shape.left': 0.0,
|
||||
'shape.top': 0.0,
|
||||
'shape.width': 400.0,
|
||||
'shape.height': 400.0,
|
||||
'shape.cornersx': 4,
|
||||
'shape.cornersy': 4,
|
||||
'border': False,
|
||||
'borderwidth.normal': 0,
|
||||
'borderwidth.hovered': 0,
|
||||
'borderwidth.clicked': 0,
|
||||
'bordercolor.normal': '#888888',
|
||||
'bordercolor.hovered': '#888888',
|
||||
'bordercolor.clicked': '#888888',
|
||||
'bordercolor.transparency': 0,
|
||||
'bgcolor.normal': '#888888',
|
||||
'bgcolor.hovered': '#888888',
|
||||
'bgcolor.clicked': '#888888',
|
||||
'bgcolor.transparency': 0,
|
||||
'text.content': '',
|
||||
'text.size': 12,
|
||||
'text.bold': False,
|
||||
'text.italic': False,
|
||||
'text.color': '#FFFFFF',
|
||||
'text.valign': 'center', # or 'top' or bottom
|
||||
'text.halign': 'center', # or 'left' or 'right'
|
||||
'action.targets': [],
|
||||
'action.commands': [],
|
||||
'image.path': '',
|
||||
'image.fit': False,
|
||||
'image.height': 32,
|
||||
'image.width': 32}
|
||||
|
||||
|
||||
COMMAND = {
|
||||
'enabled': True,
|
||||
'button': 'left', # right
|
||||
'language': 'python', # or mel
|
||||
'command': '',
|
||||
'ctrl': False,
|
||||
'shift': False,
|
||||
'deferred': False,
|
||||
'force_compact_undo': False,
|
||||
}
|
||||
|
||||
|
||||
PICKER = {
|
||||
'name': 'Untitled',
|
||||
'version': VERSION,
|
||||
'zoom_locked': False,
|
||||
'width': 900,
|
||||
'height': 600,
|
||||
}
|
49
Scripts/Animation/dwpicker/dwpicker/undo.py
Normal file
@ -0,0 +1,49 @@
|
||||
|
||||
from copy import deepcopy
|
||||
|
||||
|
||||
class UndoManager():
|
||||
def __init__(self, data):
|
||||
self._current_state = data
|
||||
self._modified = False
|
||||
self._undo_stack = []
|
||||
self._redo_stack = []
|
||||
|
||||
@property
|
||||
def data(self):
|
||||
return deepcopy(self._current_state)
|
||||
|
||||
def undo(self):
|
||||
if not self._undo_stack:
|
||||
print('No undostack.')
|
||||
return False
|
||||
self._redo_stack.append(deepcopy(self._current_state))
|
||||
self._current_state = deepcopy(self._undo_stack[-1])
|
||||
del self._undo_stack[-1]
|
||||
return True
|
||||
|
||||
def redo(self):
|
||||
if not self._redo_stack:
|
||||
return False
|
||||
|
||||
self._undo_stack.append(deepcopy(self._current_state))
|
||||
self._current_state = deepcopy(self._redo_stack[-1])
|
||||
del self._redo_stack[-1]
|
||||
return True
|
||||
|
||||
def set_data_modified(self, data):
|
||||
self._redo_stack = []
|
||||
self._undo_stack.append(deepcopy(self._current_state))
|
||||
self._current_state = deepcopy(data)
|
||||
self._modified = True
|
||||
|
||||
def set_data_saved(self):
|
||||
self._modified = False
|
||||
|
||||
@property
|
||||
def data_saved(self):
|
||||
return not self._modified
|
||||
|
||||
def reset_stacks(self):
|
||||
self._undo_stack = []
|
||||
self._redo_stack = []
|
35
Scripts/Animation/dwpicker/dwpicker/updatechecker.py
Normal file
@ -0,0 +1,35 @@
|
||||
import re
|
||||
import webbrowser
|
||||
try:
|
||||
from urllib.request import urlopen
|
||||
except ImportError:
|
||||
from urllib2 import urlopen # python2
|
||||
|
||||
from maya import cmds
|
||||
|
||||
from dwpicker.dialog import UpdateAvailableDialog
|
||||
from dwpicker.appinfos import VERSION
|
||||
from dwpicker.optionvar import CHECK_FOR_UPDATE
|
||||
|
||||
|
||||
APPINFOS_URL = (
|
||||
'https://raw.githubusercontent.com/DreamWall-Animation/dwpicker/main/'
|
||||
'dwpicker/appinfos.py')
|
||||
LATEST_RELEASE_URL = (
|
||||
'https://github.com/DreamWall-Animation/dwpicker/releases/latest')
|
||||
VERSION_PATTERN = r'\d(\.|,).\d(\.|,).\d'
|
||||
|
||||
|
||||
def warn_if_update_available():
|
||||
if not cmds.optionVar(query=CHECK_FOR_UPDATE):
|
||||
return
|
||||
try:
|
||||
appinfos = urlopen(APPINFOS_URL).read().decode()
|
||||
latest_version_str = re.search(VERSION_PATTERN, appinfos)[0]
|
||||
latest_version = tuple(
|
||||
int(n) for n in latest_version_str.replace(',', '.').split('.'))
|
||||
if VERSION < latest_version:
|
||||
if UpdateAvailableDialog(latest_version_str).exec_():
|
||||
webbrowser.open(LATEST_RELEASE_URL)
|
||||
except BaseException:
|
||||
print('DwPicker: could not check for new version')
|
291
Scripts/Animation/dwpicker/dwpicker/widgets.py
Normal file
@ -0,0 +1,291 @@
|
||||
from PySide2 import QtGui, QtCore, QtWidgets
|
||||
|
||||
from dwpicker.colorwheel import ColorDialog
|
||||
from dwpicker.dialog import get_image_path
|
||||
from dwpicker.path import format_path
|
||||
from dwpicker.qtutils import icon
|
||||
|
||||
# don't use style sheet like that, find better design
|
||||
TOGGLER_STYLESHEET = (
|
||||
'background: rgb(0, 0, 0, 75); text-align: left; font: bold')
|
||||
|
||||
|
||||
class BoolCombo(QtWidgets.QComboBox):
|
||||
valueSet = QtCore.Signal(bool)
|
||||
|
||||
def __init__(self, state=True, parent=None):
|
||||
super(BoolCombo, self).__init__(parent)
|
||||
self.addItem('True')
|
||||
self.addItem('False')
|
||||
self.setCurrentText(str(state))
|
||||
self.currentIndexChanged.connect(self.current_index_changed)
|
||||
|
||||
def state(self):
|
||||
return self.currentText() == 'True'
|
||||
|
||||
def current_index_changed(self):
|
||||
self.valueSet.emit(self.state())
|
||||
|
||||
|
||||
class BrowseEdit(QtWidgets.QWidget):
|
||||
valueSet = QtCore.Signal(str)
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(BrowseEdit, self).__init__(parent)
|
||||
|
||||
self.text = QtWidgets.QLineEdit()
|
||||
self.text.returnPressed.connect(self.apply)
|
||||
self.text.focusOutEvent = self.text_focus_out_event
|
||||
self.button = QtWidgets.QPushButton('B')
|
||||
self.button.setFixedSize(21, 21)
|
||||
self.button.released.connect(self.browse)
|
||||
|
||||
self.layout = QtWidgets.QHBoxLayout(self)
|
||||
self.layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.layout.setSpacing(0)
|
||||
self.layout.addWidget(self.text)
|
||||
self.layout.addWidget(self.button)
|
||||
|
||||
self._value = self.value()
|
||||
|
||||
def text_focus_out_event(self, _):
|
||||
self.apply()
|
||||
|
||||
def browse(self):
|
||||
filename = get_image_path(self)
|
||||
format_path(filename)
|
||||
if not filename:
|
||||
return
|
||||
self.text.setText(filename)
|
||||
self.apply()
|
||||
|
||||
def apply(self):
|
||||
text = format_path(self.text.text())
|
||||
self.text.setText(text)
|
||||
self.valueSet.emit(text)
|
||||
|
||||
def value(self):
|
||||
value = format(self.text.text())
|
||||
return value if value != '' else None
|
||||
|
||||
def set_value(self, value):
|
||||
self.text.setText(value)
|
||||
|
||||
|
||||
class WidgetToggler(QtWidgets.QPushButton):
|
||||
def __init__(self, label, widget, parent=None):
|
||||
super(WidgetToggler, self).__init__(parent)
|
||||
self.setStyleSheet(TOGGLER_STYLESHEET)
|
||||
self.setText(' v ' + label)
|
||||
self.widget = widget
|
||||
self.setCheckable(True)
|
||||
self.setChecked(True)
|
||||
self.toggled.connect(self._call_toggled)
|
||||
|
||||
def _call_toggled(self, state):
|
||||
if state is True:
|
||||
self.widget.show()
|
||||
self.setText(self.text().replace('>', 'v'))
|
||||
else:
|
||||
self.widget.hide()
|
||||
self.setText(self.text().replace('v', '>'))
|
||||
|
||||
|
||||
class ColorEdit(QtWidgets.QWidget):
|
||||
valueSet = QtCore.Signal(str)
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(ColorEdit, self).__init__(parent)
|
||||
|
||||
self.pixmap = QtWidgets.QLabel()
|
||||
self.pixmap.setFixedSize(21, 21)
|
||||
color = QtWidgets.QApplication.palette().color(
|
||||
QtGui.QPalette.Base)
|
||||
self.pixmap.setPixmap(_color_pixmap(color, self.pixmap.size()))
|
||||
self.text = QtWidgets.QLineEdit()
|
||||
self.text.returnPressed.connect(self.apply)
|
||||
self.text.focusInEvent = self.focusInEvent
|
||||
self.text.focusOutEvent = self.focusOutEvent
|
||||
self.button = QtWidgets.QPushButton(icon('picker.png'), '')
|
||||
self.button.setFixedSize(21, 21)
|
||||
self.button.released.connect(self.pick_color)
|
||||
|
||||
self.layout = QtWidgets.QHBoxLayout(self)
|
||||
self.layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.layout.setSpacing(0)
|
||||
self.layout.addWidget(self.pixmap)
|
||||
self.layout.addWidget(self.text)
|
||||
self.layout.addWidget(self.button)
|
||||
self.layout.setStretchFactor(self.pixmap, 1)
|
||||
self.layout.setStretchFactor(self.text, 5)
|
||||
self.layout.setStretchFactor(self.button, 1)
|
||||
|
||||
self._value = self.value()
|
||||
|
||||
def focusInEvent(self, event):
|
||||
self._value = self.value()
|
||||
return super(ColorEdit, self).focusInEvent(event)
|
||||
|
||||
def focusOutEvent(self, event):
|
||||
self.apply()
|
||||
return super(ColorEdit, self).focusOutEvent(event)
|
||||
|
||||
def showEvent(self, event):
|
||||
super(ColorEdit, self).showEvent(event)
|
||||
self.pixmap.setFixedSize(21, 21)
|
||||
|
||||
def pick_color(self):
|
||||
color = self.text.text() or None
|
||||
dialog = ColorDialog(color)
|
||||
if dialog.exec_():
|
||||
self.text.setText(dialog.colorname())
|
||||
self.pixmap.setPixmap(
|
||||
_color_pixmap(dialog.colorname(), self.pixmap.size()))
|
||||
self.apply()
|
||||
|
||||
def apply(self):
|
||||
if self._value != self.value():
|
||||
self.valueSet.emit(self.value())
|
||||
self._value = self.value()
|
||||
|
||||
def value(self):
|
||||
value = self.text.text()
|
||||
return value if value != '' else None
|
||||
|
||||
def set_color(self, color=None):
|
||||
self.text.setText(color)
|
||||
color = color or QtWidgets.QApplication.palette().color(
|
||||
QtGui.QPalette.Base)
|
||||
self.pixmap.setPixmap(_color_pixmap(color, self.pixmap.size()))
|
||||
|
||||
|
||||
def _color_pixmap(colorname, qsize):
|
||||
pixmap = QtGui.QPixmap(qsize)
|
||||
painter = QtGui.QPainter(pixmap)
|
||||
painter.setBrush(QtGui.QColor(colorname))
|
||||
painter.setPen(QtCore.Qt.black)
|
||||
painter.drawRect(0, 0, qsize.width(), qsize.height())
|
||||
painter.end()
|
||||
return pixmap
|
||||
|
||||
|
||||
class LineEdit(QtWidgets.QLineEdit):
|
||||
valueSet = QtCore.Signal(float)
|
||||
VALIDATOR_CLS = QtGui.QDoubleValidator
|
||||
|
||||
def __init__(self, minimum=None, maximum=None, parent=None):
|
||||
super(LineEdit, self).__init__(parent)
|
||||
self.validator = self.VALIDATOR_CLS() if self.VALIDATOR_CLS else None
|
||||
if minimum is not None:
|
||||
self.validator.setBottom(minimum)
|
||||
if maximum is not None:
|
||||
self.validator.setTop(maximum)
|
||||
self.setValidator(self.validator)
|
||||
self._value = self.value()
|
||||
self.returnPressed.connect(self.apply)
|
||||
|
||||
def focusInEvent(self, event):
|
||||
self._value = self.value()
|
||||
return super(LineEdit, self).focusInEvent(event)
|
||||
|
||||
def focusOutEvent(self, event):
|
||||
self.apply()
|
||||
return super(LineEdit, self).focusOutEvent(event)
|
||||
|
||||
def apply(self):
|
||||
if self._value != self.value():
|
||||
self.valueSet.emit(self.value())
|
||||
self._value = self.value()
|
||||
|
||||
def value(self):
|
||||
if self.text() == '':
|
||||
return None
|
||||
return float(self.text().replace(',', '.'))
|
||||
|
||||
|
||||
class TextEdit(LineEdit):
|
||||
VALIDATOR_CLS = None
|
||||
valueSet = QtCore.Signal(str)
|
||||
|
||||
def value(self):
|
||||
if self.text() == '':
|
||||
return None
|
||||
return self.text()
|
||||
|
||||
|
||||
class FloatEdit(LineEdit):
|
||||
valueSet = QtCore.Signal(float)
|
||||
VALIDATOR_CLS = QtGui.QDoubleValidator
|
||||
|
||||
def value(self):
|
||||
if self.text() == '':
|
||||
return None
|
||||
return float(self.text().replace(',', '.'))
|
||||
|
||||
|
||||
class IntEdit(LineEdit):
|
||||
valueSet = QtCore.Signal(int)
|
||||
VALIDATOR_CLS = QtGui.QIntValidator
|
||||
|
||||
def value(self):
|
||||
if self.text() == '':
|
||||
return None
|
||||
return int(float(self.text()))
|
||||
|
||||
|
||||
class Title(QtWidgets.QLabel):
|
||||
def __init__(self, title, parent=None):
|
||||
super(Title, self).__init__(parent)
|
||||
self.setFixedHeight(20)
|
||||
self.setStyleSheet('background: rgb(0, 0, 0, 25)')
|
||||
self.setText('<b> ' + title)
|
||||
|
||||
|
||||
class TouchEdit(QtWidgets.QLineEdit):
|
||||
def keyPressEvent(self, event):
|
||||
self.setText(QtGui.QKeySequence(event.key()).toString().lower())
|
||||
self.textEdited.emit(self.text())
|
||||
|
||||
|
||||
class CommandButton(QtWidgets.QWidget):
|
||||
released = QtCore.Signal()
|
||||
playReleased = QtCore.Signal()
|
||||
|
||||
def __init__(self, label, parent=None):
|
||||
super(CommandButton, self).__init__(parent)
|
||||
self.mainbutton = QtWidgets.QPushButton(label)
|
||||
self.mainbutton.released.connect(self.released.emit)
|
||||
self.playbutton = QtWidgets.QPushButton(icon('play.png'), '')
|
||||
self.playbutton.released.connect(self.playReleased.emit)
|
||||
self.playbutton.setFixedSize(22, 22)
|
||||
self.layout = QtWidgets.QHBoxLayout(self)
|
||||
self.layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.layout.setSpacing(2)
|
||||
self.layout.addWidget(self.mainbutton)
|
||||
self.layout.addWidget(self.playbutton)
|
||||
|
||||
|
||||
class LayerEdit(QtWidgets.QWidget):
|
||||
valueSet = QtCore.Signal(object)
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(LayerEdit, self).__init__(parent)
|
||||
self.layer = QtWidgets.QLineEdit()
|
||||
self.layer.setReadOnly(True)
|
||||
self.reset = QtWidgets.QPushButton('x')
|
||||
self.reset.released.connect(self.do_reset)
|
||||
|
||||
self.layout = QtWidgets.QHBoxLayout(self)
|
||||
self.layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.layout.setSpacing(0)
|
||||
self.layout.addWidget(self.layer)
|
||||
self.layout.addWidget(self.reset)
|
||||
|
||||
def set_layer(self, text):
|
||||
self.layer.setText(text or '')
|
||||
|
||||
def do_reset(self):
|
||||
if not self.layer.text():
|
||||
return
|
||||
self.layer.setText('')
|
||||
self.valueSet.emit(None)
|
BIN
Scripts/Animation/dwpicker/screenshots/createbuttons.gif
Normal file
After Width: | Height: | Size: 916 KiB |
BIN
Scripts/Animation/dwpicker/screenshots/editor.gif
Normal file
After Width: | Height: | Size: 2.1 MiB |
BIN
Scripts/Animation/dwpicker/screenshots/picker.gif
Normal file
After Width: | Height: | Size: 895 KiB |
35
Scripts/Animation/dwpicker/scripts/change_namespace.py
Normal file
@ -0,0 +1,35 @@
|
||||
"""
|
||||
The multiple nested namespaces is currently not supported by the namespace
|
||||
switch system. This can cause issue for picker having to support sub namespace
|
||||
on part of the rig.
|
||||
This piece of code allow to set manually a namespace through the selected
|
||||
shapes"""
|
||||
|
||||
|
||||
import dwpicker
|
||||
|
||||
namespace = 'write:nested:namespace:here'
|
||||
picker = dwpicker.current()
|
||||
|
||||
## Edit Shapes from picker view selection
|
||||
|
||||
# selection = [s for s in picker.shapes if s.selected]
|
||||
# for shape in selection:
|
||||
# targets = [namespace + ':' + t.split(':')[-1] for t in shape.targets()]
|
||||
# shape.options['action.targets'] = targets
|
||||
|
||||
# dwpicker._dwpicker.data_changed_from_picker(picker)
|
||||
|
||||
## Edit Shapes from advanced editor selection
|
||||
|
||||
index = dwpicker._dwpicker.pickers.index(picker)
|
||||
editor = dwpicker._dwpicker.editors[index]
|
||||
if editor is None:
|
||||
raise RuntimeWarning("Please open current picker's avanced editor")
|
||||
|
||||
selection = editor.shape_editor.selection
|
||||
for shape in selection:
|
||||
targets = [namespace + ':' + t.split(':')[-1] for t in shape.targets()]
|
||||
shape.options['action.targets'] = targets
|
||||
|
||||
editor.set_data_modified()
|
24
Scripts/Animation/dwpicker/scripts/clean_reload_dwpicker.py
Normal file
@ -0,0 +1,24 @@
|
||||
"""
|
||||
A developper hack to reload the Dreamwall picker without having to restart
|
||||
Maya each time.
|
||||
"""
|
||||
|
||||
|
||||
# If the picker is not in a known PYTHONPATH.
|
||||
import sys
|
||||
sys.path.insert(0, "<dwpicker path>")
|
||||
|
||||
# Code to clean modules and relaunch a Dreamwall picker with updated code.
|
||||
try:
|
||||
# Important step to not let some callbacks left behind.
|
||||
dwpicker.close()
|
||||
except:
|
||||
pass
|
||||
|
||||
for module in list(sys.modules):
|
||||
if "dwpicker" in module:
|
||||
print("deleted: " + module)
|
||||
del sys.modules[module]
|
||||
|
||||
import dwpicker
|
||||
dwpicker.show()
|
@ -0,0 +1,13 @@
|
||||
# This code as to be ran in a Maya able to use 'dwpicker' package.
|
||||
import os
|
||||
from dwpicker.ingest import animschool
|
||||
|
||||
|
||||
SOURCE_DIRECTORY = "" # Replace those variables before conversion
|
||||
DESTINATION_DIRECTORY = ""
|
||||
|
||||
for f in os.listdir(SOURCE_DIRECTORY):
|
||||
if not f.lower().endswith(".pkr"):
|
||||
continue
|
||||
filepath = os.path.join(SOURCE_DIRECTORY, f)
|
||||
animschool.convert(filepath, DESTINATION_DIRECTORY)
|
33
Scripts/Animation/dwpicker/scripts/create_buttons.py
Normal file
@ -0,0 +1,33 @@
|
||||
import dwpicker
|
||||
from dwpicker.scenedata import load_local_picker_data, store_local_picker_data
|
||||
from dwpicker.templates import BUTTON
|
||||
|
||||
|
||||
def add_button(index, options, refresh_ui=True):
|
||||
"""
|
||||
This works with pick closed as well.
|
||||
@param int index: the tab position of the dwpicker.
|
||||
@param dict options:
|
||||
This is a dictionnary of the shape options. List of possible options
|
||||
are can be found here dwpicker.templates.BUTTON
|
||||
(too much very many long to be documented here ;) )
|
||||
@param bool refresh_ui:
|
||||
this update the ui. Can be disabled for loop purpose.
|
||||
"""
|
||||
pickers = load_local_picker_data()
|
||||
button = BUTTON.copy()
|
||||
button.update(options)
|
||||
pickers[index]['shapes'].append(button)
|
||||
store_local_picker_data(pickers)
|
||||
if refresh_ui:
|
||||
dwpicker.refresh()
|
||||
|
||||
|
||||
options = {
|
||||
'text.content': 'Button',
|
||||
'shape.left': 250,
|
||||
'shape.top': 150,
|
||||
'shape.width': 120.0,
|
||||
'shape.height': 25.0,
|
||||
}
|
||||
add_button(0, options)
|
@ -0,0 +1,7 @@
|
||||
import dwpicker
|
||||
from dwpicker.picker import detect_picker_namespace
|
||||
|
||||
|
||||
picker = dwpicker.current()
|
||||
if picker:
|
||||
namespace = detect_picker_namespace(picker.shapes)
|