Update
62
2023/scripts/animation_tools/dwpicker/README.md
Normal file
@@ -0,0 +1,62 @@
|
||||
# DreamWall Picker
|
||||
|
||||
Animation picker for Autodesk Maya 2017 (or higher)
|
||||
|
||||
## 作者
|
||||
- Lionel Brouyère
|
||||
- Olivier Evers
|
||||
|
||||
> 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
|
||||
|
||||
## 功能特性
|
||||
|
||||
- 简单快速的 picker 创建
|
||||
- 导入 2022 年之前的 AnimSchool pickers
|
||||
- 在 Maya 场景中存储 picker
|
||||
- 高级 picker 编辑器
|
||||
- 实现 AnimSchool picker 的所有功能,甚至更多...
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 在 Python 中启动
|
||||
|
||||
```python
|
||||
import animation_tools.dwpicker
|
||||
animation_tools.dwpicker.show()
|
||||
```
|
||||
|
||||
### 带参数启动
|
||||
|
||||
```python
|
||||
import animation_tools.dwpicker
|
||||
|
||||
# 只读模式
|
||||
animation_tools.dwpicker.show(editable=False)
|
||||
|
||||
# 加载指定的 picker 文件
|
||||
animation_tools.dwpicker.show(pickers=['/path/to/picker.json'])
|
||||
|
||||
# 忽略场景中的 pickers
|
||||
animation_tools.dwpicker.show(ignore_scene_pickers=True)
|
||||
```
|
||||
|
||||
## 模块修改说明
|
||||
|
||||
此模块已从原始的 dwpicker 包修改为 animation_tools.dwpicker 子模块:
|
||||
|
||||
1. **主模块导入**(dwpicker/*.py):`from dwpicker.xxx` → `from .xxx`
|
||||
2. **一级子模块导入**(designer/*.py):
|
||||
- 引用父模块:`from dwpicker.xxx` → `from ..xxx`
|
||||
- 引用同级:`from .canvas` → 保持不变
|
||||
3. **二级子模块导入**(ingest/animschool/*.py):
|
||||
- 引用根模块:`from dwpicker.xxx` → `from ...xxx`(三个点)
|
||||
- 引用同级:`from .parser` → 保持不变
|
||||
4. **跨模块导入**:`from dwpicker import xxx` → `from .. import xxx`
|
||||
5. **代码执行模板**:`import dwpicker` → `import animation_tools.dwpicker as dwpicker`
|
||||
6. 保持所有原始功能不变
|
||||
|
||||
## 官方文档
|
||||
|
||||
更多信息请访问 [Official Documentation](https://dreamwall-animation.github.io/dwpicker)
|
||||
208
2023/scripts/animation_tools/dwpicker/__init__.py
Normal file
@@ -0,0 +1,208 @@
|
||||
|
||||
from maya import cmds
|
||||
|
||||
|
||||
from .main import DwPicker, WINDOW_CONTROL_NAME
|
||||
from .optionvar import ensure_optionvars_exists
|
||||
from .namespace import detect_picker_namespace
|
||||
from .qtutils import remove_workspace_control
|
||||
from .updatechecker import warn_if_update_available
|
||||
|
||||
|
||||
_dwpicker = None
|
||||
|
||||
|
||||
def show(
|
||||
editable=True,
|
||||
pickers=None,
|
||||
ignore_scene_pickers=False,
|
||||
replace_namespace_function=None,
|
||||
list_namespaces_function=None):
|
||||
"""
|
||||
This is the dwpicker default startup function.
|
||||
kwargs:
|
||||
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: bool
|
||||
This is loading the picker empty, ignoring the scene content.
|
||||
|
||||
replace_namespace_function: callable
|
||||
Function used when on each target when a namespace switch is
|
||||
triggered. Function must follow this templace:
|
||||
def function(target: str, namespace: str)
|
||||
-> new_target_name: str
|
||||
|
||||
list_namespaces_function: callable
|
||||
Function used when the picker is listing the scene existing
|
||||
namespace. The default behavior list all the scene namespaces, but
|
||||
in some studios, lot of namespace are not relevant to list, this
|
||||
by this way you can customize how it does works.
|
||||
def function():
|
||||
-> List[str]
|
||||
|
||||
return:
|
||||
DwPicker: window
|
||||
"""
|
||||
ensure_optionvars_exists()
|
||||
global _dwpicker
|
||||
if not _dwpicker:
|
||||
warn_if_update_available()
|
||||
_dwpicker = DwPicker(
|
||||
replace_namespace_function=replace_namespace_function,
|
||||
list_namespaces_function=list_namespaces_function)
|
||||
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
|
||||
|
||||
_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()
|
||||
return _dwpicker
|
||||
|
||||
|
||||
def toggle():
|
||||
"""
|
||||
Switch the DwPicker visibility.
|
||||
"""
|
||||
if not _dwpicker:
|
||||
return show()
|
||||
_dwpicker.setVisible(not _dwpicker.isVisible())
|
||||
|
||||
|
||||
def close():
|
||||
"""
|
||||
Close properly the DwPicker.
|
||||
It unregister all DwPicker remaining callbacks.
|
||||
"""
|
||||
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()
|
||||
|
||||
|
||||
def current_namespace():
|
||||
"""
|
||||
Returns the namespace of the current displayed picker.
|
||||
"""
|
||||
picker = current()
|
||||
if not picker:
|
||||
return ':'
|
||||
return detect_picker_namespace(picker.document.shapes)
|
||||
|
||||
|
||||
def set_layer_visible(layername, visible=True):
|
||||
if not _dwpicker:
|
||||
return cmds.warning('Please open picker first.')
|
||||
picker = current()
|
||||
if not picker:
|
||||
return
|
||||
if visible:
|
||||
if layername in picker.layers_menu.hidden_layers:
|
||||
picker.layers_menu.hidden_layers.remove(layername)
|
||||
picker.update()
|
||||
return
|
||||
if layername not in picker.layers_menu.hidden_layers:
|
||||
picker.layers_menu.hidden_layers.append(layername)
|
||||
picker.update()
|
||||
|
||||
|
||||
def toggle_layer_visibility(layername):
|
||||
if not _dwpicker:
|
||||
return cmds.warning('Please open picker first.')
|
||||
picker = current()
|
||||
if not picker:
|
||||
return
|
||||
if layername in picker.layers_menu.hidden_layers:
|
||||
picker.layers_menu.hidden_layers.remove(layername)
|
||||
else:
|
||||
picker.layers_menu.hidden_layers.append(layername)
|
||||
picker.update()
|
||||
|
||||
|
||||
def get_shape(shape_id):
|
||||
picker = current()
|
||||
if not picker:
|
||||
return
|
||||
return picker.document.shapes_by_id.get(shape_id)
|
||||
113
2023/scripts/animation_tools/dwpicker/align.py
Normal file
@@ -0,0 +1,113 @@
|
||||
from .pyside import QtCore
|
||||
from .geometry import split_line
|
||||
|
||||
|
||||
def align_shapes(shapes, direction):
|
||||
_direction_matches[direction](shapes)
|
||||
|
||||
|
||||
def align_left(shapes):
|
||||
left = min(s.bounding_rect().left() for s in shapes)
|
||||
for shape in shapes:
|
||||
shape_left = left + (shape.rect.left() - shape.bounding_rect().left())
|
||||
shape.rect.moveLeft(shape_left)
|
||||
shape.synchronize_rect()
|
||||
shape.update_path()
|
||||
|
||||
|
||||
def align_h_center(shapes):
|
||||
x = sum(s.bounding_rect().center().x() for s in shapes) / len(shapes)
|
||||
for shape in shapes:
|
||||
offset = shape.bounding_rect().center().x() - shape.rect.center().x()
|
||||
shape_x = x - offset
|
||||
shape.rect.moveCenter(QtCore.QPointF(shape_x, shape.rect.center().y()))
|
||||
shape.synchronize_rect()
|
||||
shape.update_path()
|
||||
|
||||
|
||||
def align_right(shapes):
|
||||
right = max(s.bounding_rect().right() for s in shapes)
|
||||
for shape in shapes:
|
||||
offset = right - shape.bounding_rect().right()
|
||||
shape.rect.moveLeft(shape.rect.left() + offset)
|
||||
shape.synchronize_rect()
|
||||
shape.update_path()
|
||||
|
||||
|
||||
def align_top(shapes):
|
||||
top = min(s.bounding_rect().top() for s in shapes)
|
||||
for shape in shapes:
|
||||
shape_top = top + (shape.rect.top() - shape.bounding_rect().top())
|
||||
shape.rect.moveTop(shape_top)
|
||||
shape.synchronize_rect()
|
||||
shape.update_path()
|
||||
|
||||
|
||||
def align_v_center(shapes):
|
||||
y = sum(s.bounding_rect().center().y() for s in shapes) / len(shapes)
|
||||
for shape in shapes:
|
||||
offset = shape.bounding_rect().center().y() - shape.rect.center().y()
|
||||
shape_y = y - offset
|
||||
shape.rect.moveCenter(QtCore.QPointF(shape.rect.center().x(), shape_y))
|
||||
shape.synchronize_rect()
|
||||
shape.update_path()
|
||||
|
||||
|
||||
def align_bottom(shapes):
|
||||
bottom = max(s.bounding_rect().bottom() for s in shapes)
|
||||
for shape in shapes:
|
||||
offset = shape.rect.bottom() - shape.bounding_rect().bottom()
|
||||
shape_bottom = bottom + offset
|
||||
shape.rect.moveBottom(shape_bottom)
|
||||
shape.synchronize_rect()
|
||||
shape.update_path()
|
||||
|
||||
|
||||
def arrange_horizontal(shapes):
|
||||
if len(shapes) < 3:
|
||||
return
|
||||
shapes = sorted(shapes, key=lambda s: s.bounding_rect().center().x())
|
||||
centers = split_line(
|
||||
point1=shapes[0].bounding_rect().center(),
|
||||
point2=shapes[-1].bounding_rect().center(),
|
||||
step_number=len(shapes))
|
||||
for shape, center in zip(shapes, centers):
|
||||
offset = shape.bounding_rect().center().x() - shape.rect.center().x()
|
||||
point = QtCore.QPointF(center.x() - offset, shape.rect.center().y())
|
||||
shape.rect.moveCenter(point)
|
||||
shape.synchronize_rect()
|
||||
shape.update_path()
|
||||
|
||||
|
||||
def arrange_vertical(shapes):
|
||||
if len(shapes) < 3:
|
||||
return
|
||||
shapes = sorted(shapes, key=lambda s: s.bounding_rect().center().y())
|
||||
centers = split_line(
|
||||
point1=shapes[0].bounding_rect().center(),
|
||||
point2=shapes[-1].bounding_rect().center(),
|
||||
step_number=len(shapes))
|
||||
for shape, center in zip(shapes, centers):
|
||||
offset = shape.bounding_rect().center().y() - shape.rect.center().y()
|
||||
point = QtCore.QPointF(shape.rect.center().x(), center.y() - offset)
|
||||
shape.rect.moveCenter(point)
|
||||
shape.synchronize_rect()
|
||||
shape.update_path()
|
||||
|
||||
|
||||
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()
|
||||
shape.update_path()
|
||||
|
||||
|
||||
_direction_matches = {
|
||||
'left': align_left,
|
||||
'h_center': align_h_center,
|
||||
'right': align_right,
|
||||
'top': align_top,
|
||||
'v_center': align_v_center,
|
||||
'bottom': align_bottom
|
||||
}
|
||||
5
2023/scripts/animation_tools/dwpicker/appinfos.py
Normal file
@@ -0,0 +1,5 @@
|
||||
VERSION = 1, 0, 4 # Version, Feature, Hotfix.
|
||||
RELEASE_DATE = 'april 4 2025'
|
||||
DW_WEBSITE = 'https://fr.dreamwall.be/'
|
||||
DW_GITHUB = 'https://github.com/DreamWall-Animation'
|
||||
PICKER_DOCUMENTATION = 'https://dreamwall-animation.github.io/dwpicker'
|
||||
29
2023/scripts/animation_tools/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
2023/scripts/animation_tools/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
2023/scripts/animation_tools/dwpicker/colorwheel.py
Normal file
@@ -0,0 +1,272 @@
|
||||
import math
|
||||
from .pyside import QtWidgets, QtGui, QtCore
|
||||
from .qtutils import get_cursor
|
||||
from .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.update()
|
||||
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.update()
|
||||
|
||||
|
||||
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
|
||||
159
2023/scripts/animation_tools/dwpicker/commands.py
Normal file
@@ -0,0 +1,159 @@
|
||||
from copy import deepcopy
|
||||
from .pyside import QtWidgets, QtCore
|
||||
from .templates import COMMAND, MENU_COMMAND
|
||||
from .qtutils import icon
|
||||
from .dialog import CommandEditorDialog, MenuCommandEditorDialog
|
||||
|
||||
|
||||
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.edit.setFixedSize(25, 25)
|
||||
self.delete = QtWidgets.QPushButton(icon('delete2.png'), '')
|
||||
self.delete.setFixedSize(25, 25)
|
||||
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.edit)
|
||||
layout.addWidget(self.delete)
|
||||
layout.addSpacing(10)
|
||||
layout.addWidget(self.label)
|
||||
|
||||
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())
|
||||
|
||||
|
||||
class MenuCommandItemWidget(CommandItemWidget):
|
||||
|
||||
def get_label(self):
|
||||
language = '<a style="color: #FFFF00"><i>({0})</i></a>'.format(
|
||||
self.command['language'])
|
||||
return '{} {}'.format(self.command['caption'], language)
|
||||
|
||||
|
||||
class CommandsEditor(QtWidgets.QWidget):
|
||||
valueSet = QtCore.Signal(object)
|
||||
edit_dialog_constructor = CommandEditorDialog
|
||||
picker_command_key = 'action.commands'
|
||||
template = COMMAND
|
||||
item_constructor = CommandItemWidget
|
||||
|
||||
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][self.picker_command_key]:
|
||||
self.call_add_command(command)
|
||||
|
||||
def call_create_command(self):
|
||||
command = deepcopy(self.template)
|
||||
dialog = self.edit_dialog_constructor(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 = self.item_constructor(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 = self.edit_dialog_constructor(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 MenuCommandsEditor(CommandsEditor):
|
||||
edit_dialog_constructor = MenuCommandEditorDialog
|
||||
picker_command_key = 'action.menu_commands'
|
||||
template = MENU_COMMAND
|
||||
item_constructor = MenuCommandItemWidget
|
||||
|
||||
|
||||
class GlobalCommandsEditor(CommandsEditor):
|
||||
edit_dialog_constructor = MenuCommandEditorDialog
|
||||
picker_command_key = 'menu_commands'
|
||||
template = MENU_COMMAND
|
||||
item_constructor = MenuCommandItemWidget
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(GlobalCommandsEditor, self).__init__(parent)
|
||||
self.warning.hide()
|
||||
self.add_command.setEnabled(True)
|
||||
|
||||
def set_options(self, options):
|
||||
self.commands.clear()
|
||||
for command in options[self.picker_command_key]:
|
||||
self.call_add_command(command)
|
||||
145
2023/scripts/animation_tools/dwpicker/compatibility.py
Normal file
@@ -0,0 +1,145 @@
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
import uuid
|
||||
from .appinfos import VERSION
|
||||
from .stack import count_panels
|
||||
from .shapepath import get_relative_path
|
||||
|
||||
|
||||
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)
|
||||
|
||||
if tuple(version) < (0, 11, 3):
|
||||
for shape in picker_data['shapes']:
|
||||
shape['background'] = not (
|
||||
any(cmd['enabled'] for cmd in shape['action.commands']) or
|
||||
shape['action.targets'])
|
||||
|
||||
if tuple(version) < (0, 12, 0):
|
||||
for shape in picker_data['shapes']:
|
||||
shape['action.menu_commands'] = []
|
||||
|
||||
if tuple(version) < (0, 12, 1):
|
||||
picker_data['general'].pop('width')
|
||||
picker_data['general'].pop('height')
|
||||
|
||||
if tuple(version) < (0, 14, 0):
|
||||
for shape in picker_data['shapes']:
|
||||
shape['shape.path'] = []
|
||||
|
||||
if tuple(version) < (0, 14, 1):
|
||||
picker_data['general']['menu_commands'] = []
|
||||
|
||||
if tuple(version) < (0, 15, 0):
|
||||
picker_data['general']['panels'] = [[1.0, [1.0]]]
|
||||
picker_data['general']['panels.orientation'] = 'vertical'
|
||||
zoom_locked = picker_data['general']['zoom_locked']
|
||||
picker_data['general']['panels.zoom_locked'] = [zoom_locked]
|
||||
del picker_data['general']['zoom_locked']
|
||||
for shape in picker_data['shapes']:
|
||||
shape['panel'] = 0
|
||||
shape['shape.space'] = 'world'
|
||||
shape['shape.anchor'] = 'top_left'
|
||||
|
||||
if tuple(version) < (0, 15, 2):
|
||||
picker_data['general']['hidden_layers'] = []
|
||||
|
||||
if tuple(version) < (0, 15, 3):
|
||||
picker_data['general']['panels.as_sub_tab'] = False
|
||||
picker_data['general']['panels.colors'] = [None]
|
||||
picker_data['general']['panels.names'] = ['Panel 1']
|
||||
ensure_general_options_sanity(picker_data['general'])
|
||||
|
||||
if tuple(version) < (1, 0, 0):
|
||||
for shape in picker_data['shapes']:
|
||||
shape['id'] = str(uuid.uuid4())
|
||||
point = shape['shape.left'], shape['shape.top']
|
||||
shape['shape.path'] = get_relative_path(point, shape['shape.path'])
|
||||
shape['shape.ignored_by_focus'] = False
|
||||
shape['image.ratio'] = False
|
||||
shape['children'] = []
|
||||
|
||||
return picker_data
|
||||
|
||||
|
||||
def ensure_general_options_sanity(options):
|
||||
split_count = count_panels(options['panels'])
|
||||
while split_count > len(options['panels.zoom_locked']):
|
||||
options['panels.zoom_locked'].append(False)
|
||||
while split_count > len(options['panels.colors']):
|
||||
options['panels.colors'].append(None)
|
||||
while split_count > len(options['panels.names']):
|
||||
name = 'Panel ' + str(len(options["panels.names"]) + 1)
|
||||
options['panels.names'].append(name)
|
||||
|
||||
|
||||
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]
|
||||
820
2023/scripts/animation_tools/dwpicker/designer/attributes.py
Normal file
@@ -0,0 +1,820 @@
|
||||
import maya.cmds as cmds
|
||||
from functools import partial
|
||||
from ..pyside import QtCore, QtWidgets
|
||||
|
||||
from ..commands import (
|
||||
CommandsEditor, MenuCommandsEditor, GlobalCommandsEditor)
|
||||
from ..qtutils import VALIGNS, HALIGNS
|
||||
from .stackeditor import StackEditor
|
||||
from .layer import VisibilityLayersEditor
|
||||
from .patheditor import PathEditor
|
||||
from ..stack import ORIENTATIONS
|
||||
from ..widgets import (
|
||||
BoolCombo, BrowseEdit, ColorEdit, ChildrenWidget, IntEdit, FloatEdit,
|
||||
LayerEdit, TextEdit, Title, WidgetToggler, ZoomsLockedEditor)
|
||||
|
||||
|
||||
LEFT_CELL_WIDTH = 90
|
||||
SHAPE_TYPES = 'square', 'round', 'rounded_rect', 'custom'
|
||||
SPACES = 'world', 'screen'
|
||||
ANCHORS = 'top_left', 'top_right', 'bottom_left', 'bottom_right'
|
||||
|
||||
|
||||
class AttributeEditor(QtWidgets.QWidget):
|
||||
imageModified = QtCore.Signal()
|
||||
optionSet = QtCore.Signal(str, object)
|
||||
optionsSet = QtCore.Signal(dict, bool) # all options, affect rect
|
||||
selectLayerContent = QtCore.Signal(str)
|
||||
panelDoubleClicked = QtCore.Signal(int)
|
||||
|
||||
def __init__(self, document, display_options, parent=None):
|
||||
super(AttributeEditor, self).__init__(parent)
|
||||
self.document = document
|
||||
|
||||
self.generals = GeneralSettings(self.document, display_options)
|
||||
self.generals.panelDoubleClicked.connect(self.panel_double_clicked)
|
||||
mtd = self.selectLayerContent.emit
|
||||
self.generals.layers.selectLayerContent.connect(mtd)
|
||||
|
||||
self.shape = ShapeSettings()
|
||||
self.shape.optionSet.connect(self.optionSet.emit)
|
||||
self.shape.optionsSet.connect(self.optionsSet.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(document, display_options)
|
||||
self.action.optionSet.connect(self.optionSet.emit)
|
||||
self.action_toggler = WidgetToggler('Action', self.action)
|
||||
|
||||
self.widget = QtWidgets.QWidget()
|
||||
self.layout = QtWidgets.QVBoxLayout(self.widget)
|
||||
self.layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.layout.setSpacing(0)
|
||||
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.tab = QtWidgets.QTabWidget()
|
||||
self.tab.addTab(self.scroll_area, 'Shapes')
|
||||
self.tab.addTab(self.generals, 'Picker')
|
||||
|
||||
self.main_layout = QtWidgets.QVBoxLayout(self)
|
||||
self.main_layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.main_layout.addWidget(self.tab)
|
||||
|
||||
self.setFixedWidth(self.sizeHint().width() * 1.05)
|
||||
|
||||
def panel_double_clicked(self, panel):
|
||||
self.panelDoubleClicked.emit(panel - 1)
|
||||
|
||||
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):
|
||||
panelDoubleClicked = QtCore.Signal(int)
|
||||
|
||||
def __init__(self, document, display_options, parent=None):
|
||||
super(GeneralSettings, self).__init__(parent)
|
||||
self.document = document
|
||||
self.display_options = display_options
|
||||
|
||||
self.document.general_option_changed.connect(self.update_options)
|
||||
self.document.data_changed.connect(self.update_options)
|
||||
|
||||
self.name = TextEdit()
|
||||
self.name.valueSet.connect(partial(self.set_general, 'name'))
|
||||
|
||||
self.zoom_locked = ZoomsLockedEditor(self.document)
|
||||
|
||||
self.orientation = QtWidgets.QComboBox()
|
||||
self.orientation.addItems(list(ORIENTATIONS))
|
||||
method = partial(self.set_general, 'panels.orientation')
|
||||
self.orientation.currentTextChanged.connect(method)
|
||||
|
||||
self.as_sub_tab = BoolCombo()
|
||||
method = partial(self.set_general, 'panels.as_sub_tab')
|
||||
self.as_sub_tab.valueSet.connect(method)
|
||||
|
||||
self.stack = StackEditor()
|
||||
self.stack.setMinimumHeight(100)
|
||||
self.stack.panelsChanged.connect(self.stack_changed)
|
||||
self.stack.panelSelected.connect(self.panel_selected)
|
||||
self.stack.panelDoubleClicked.connect(self.panelDoubleClicked.emit)
|
||||
|
||||
self.layers = VisibilityLayersEditor(self.document)
|
||||
self.commands = GlobalCommandsEditor()
|
||||
method = partial(self.set_general, 'menu_commands')
|
||||
self.commands.valueSet.connect(method)
|
||||
|
||||
form_layout = QtWidgets.QFormLayout()
|
||||
form_layout.setSpacing(0)
|
||||
form_layout.setContentsMargins(0, 0, 0, 0)
|
||||
form_layout.setHorizontalSpacing(5)
|
||||
form_layout.addRow('Picker Name', self.name)
|
||||
|
||||
form_layout_2 = QtWidgets.QFormLayout()
|
||||
form_layout_2.setSpacing(0)
|
||||
form_layout_2.setContentsMargins(0, 0, 0, 0)
|
||||
form_layout_2.setHorizontalSpacing(5)
|
||||
form_layout_2.addRow('Columns orientation', self.orientation)
|
||||
form_layout_2.addRow('Display panels as tab', self.as_sub_tab)
|
||||
|
||||
layout = QtWidgets.QVBoxLayout(self)
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
layout.setSpacing(0)
|
||||
layout.addLayout(form_layout)
|
||||
layout.addItem(QtWidgets.QSpacerItem(0, 8))
|
||||
layout.addWidget(Title('Picker Panels'))
|
||||
layout.addLayout(form_layout_2)
|
||||
layout.addWidget(self.stack)
|
||||
layout.addItem(QtWidgets.QSpacerItem(0, 8))
|
||||
layout.addWidget(Title('Panels Zoom Locked'))
|
||||
layout.addWidget(self.zoom_locked)
|
||||
layout.addItem(QtWidgets.QSpacerItem(0, 8))
|
||||
layout.addWidget(Title('Visibility Layers'))
|
||||
layout.addWidget(self.layers)
|
||||
layout.addItem(QtWidgets.QSpacerItem(0, 8))
|
||||
layout.addWidget(Title('Global Right Click Commands'))
|
||||
layout.addWidget(self.commands)
|
||||
layout.addStretch()
|
||||
self.update_options()
|
||||
|
||||
def panel_selected(self, index):
|
||||
self.display_options.current_panel = index - 1
|
||||
self.display_options.options_changed.emit()
|
||||
|
||||
def stack_changed(self):
|
||||
self.set_general('panels', self.stack.data)
|
||||
|
||||
def set_general(self, key, value):
|
||||
self.document.data['general'][key] = value
|
||||
self.document.general_option_changed.emit('attribute_editor', key)
|
||||
self.document.record_undo()
|
||||
if key == 'panels.orientation':
|
||||
self.stack.set_orientation(value)
|
||||
|
||||
def update_options(self, *_):
|
||||
self.block_signals(True)
|
||||
options = self.document.data['general']
|
||||
self.stack.set_data(options['panels'])
|
||||
self.stack.set_orientation(options['panels.orientation'])
|
||||
self.as_sub_tab.setCurrentText(str(options['panels.as_sub_tab']))
|
||||
self.orientation.setCurrentText(options['panels.orientation'])
|
||||
self.name.setText(options['name'])
|
||||
self.commands.set_options(options)
|
||||
self.block_signals(False)
|
||||
|
||||
def block_signals(self, state):
|
||||
widgets = (
|
||||
self.stack,
|
||||
self.as_sub_tab,
|
||||
self.orientation,
|
||||
self.name,
|
||||
self.commands)
|
||||
for widget in widgets:
|
||||
widget.blockSignals(state)
|
||||
|
||||
|
||||
class ShapeSettings(QtWidgets.QWidget):
|
||||
optionSet = QtCore.Signal(str, object)
|
||||
optionsSet = QtCore.Signal(dict, bool) # all options, affect rect
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(ShapeSettings, self).__init__(parent)
|
||||
self.id_ = QtWidgets.QLineEdit()
|
||||
self.id_.setReadOnly(True)
|
||||
|
||||
self.shape = QtWidgets.QComboBox()
|
||||
self.shape.addItems(SHAPE_TYPES)
|
||||
self.shape.currentIndexChanged.connect(self.shape_changed)
|
||||
self.path_editor = PathEditor(self)
|
||||
self.path_editor.pathEdited.connect(self.path_edited)
|
||||
self.path_editor.setVisible(False)
|
||||
self.path_editor.setEnabled(False)
|
||||
|
||||
self.panel = QtWidgets.QLineEdit()
|
||||
self.panel.setReadOnly(True)
|
||||
self.layer = LayerEdit()
|
||||
method = partial(self.optionSet.emit, 'visibility_layer')
|
||||
self.layer.valueSet.connect(method)
|
||||
|
||||
self.background = BoolCombo()
|
||||
method = partial(self.optionSet.emit, 'background')
|
||||
self.background.valueSet.connect(method)
|
||||
|
||||
self.ignored_by_focus = BoolCombo()
|
||||
method = partial(self.optionSet.emit, 'shape.ignored_by_focus')
|
||||
self.ignored_by_focus.valueSet.connect(method)
|
||||
|
||||
self.space = QtWidgets.QComboBox()
|
||||
self.space.addItems(SPACES)
|
||||
self.space.currentIndexChanged.connect(self.space_changed)
|
||||
|
||||
self.anchor = QtWidgets.QComboBox()
|
||||
self.anchor.addItems(ANCHORS)
|
||||
method = partial(self.optionSet.emit, 'shape.anchor')
|
||||
self.anchor.currentTextChanged.connect(method)
|
||||
|
||||
self.left = IntEdit()
|
||||
method = partial(self.optionSet.emit, 'shape.left')
|
||||
self.left.valueSet.connect(method)
|
||||
self.top = IntEdit()
|
||||
method = partial(self.optionSet.emit, 'shape.top')
|
||||
self.top.valueSet.connect(method)
|
||||
self.width = IntEdit(minimum=0)
|
||||
method = partial(self.optionSet.emit, 'shape.width')
|
||||
self.width.valueSet.connect(method)
|
||||
self.height = IntEdit(minimum=0)
|
||||
method = partial(self.optionSet.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)
|
||||
|
||||
layout1 = QtWidgets.QFormLayout()
|
||||
layout1.setSpacing(0)
|
||||
layout1.setContentsMargins(0, 0, 0, 0)
|
||||
layout1.setHorizontalSpacing(5)
|
||||
layout1.addRow('Id', self.id_)
|
||||
layout1.addItem(QtWidgets.QSpacerItem(0, 8))
|
||||
layout1.addRow(Title('Display'))
|
||||
layout1.addRow('Panel number', self.panel)
|
||||
layout1.addRow('Visibility layer', self.layer)
|
||||
layout1.addRow('Background', self.background)
|
||||
layout1.addRow('Ignored by focus', self.ignored_by_focus)
|
||||
layout1.addRow('Shape', self.shape)
|
||||
|
||||
layout2 = QtWidgets.QVBoxLayout()
|
||||
layout2.setSpacing(0)
|
||||
layout2.setContentsMargins(0, 0, 0, 0)
|
||||
layout2.addWidget(self.path_editor)
|
||||
|
||||
layout3 = QtWidgets.QFormLayout()
|
||||
layout3.addItem(QtWidgets.QSpacerItem(0, 8))
|
||||
layout3.addRow(Title('Space'))
|
||||
layout3.addRow('space', self.space)
|
||||
layout3.addRow('anchor', self.anchor)
|
||||
layout3.addItem(QtWidgets.QSpacerItem(0, 8))
|
||||
layout3.addRow(Title('Dimensions'))
|
||||
layout3.addRow('left', self.left)
|
||||
layout3.addRow('top', self.top)
|
||||
layout3.addRow('width', self.width)
|
||||
layout3.addRow('height', self.height)
|
||||
layout3.addRow('roundness x', self.cornersx)
|
||||
layout3.addRow('roundness y', self.cornersy)
|
||||
layout3.addItem(QtWidgets.QSpacerItem(0, 8))
|
||||
|
||||
layout = QtWidgets.QVBoxLayout(self)
|
||||
layout.setSpacing(0)
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
layout.addLayout(layout1)
|
||||
layout.addLayout(layout2)
|
||||
layout.addLayout(layout3)
|
||||
|
||||
for label in self.findChildren(QtWidgets.QLabel):
|
||||
alignment = QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter
|
||||
if not isinstance(label, Title):
|
||||
label.setAlignment(alignment)
|
||||
label.setFixedWidth(LEFT_CELL_WIDTH)
|
||||
|
||||
def path_edited(self):
|
||||
if self.shape.currentText() != 'custom':
|
||||
return
|
||||
self.optionSet.emit('shape.path', self.path_editor.path())
|
||||
|
||||
def shape_changed(self, _):
|
||||
self.path_editor.setEnabled(self.shape.currentText() == 'custom')
|
||||
self.path_editor.setVisible(self.shape.currentText() == 'custom')
|
||||
self.height.setEnabled(self.shape.currentText() != 'custom')
|
||||
self.width.setEnabled(self.shape.currentText() != 'custom')
|
||||
self.optionSet.emit('shape', self.shape.currentText())
|
||||
self.path_editor.canvas.focus()
|
||||
|
||||
def space_changed(self, index):
|
||||
self.anchor.setEnabled(bool(index))
|
||||
self.optionSet.emit('shape.space', self.space.currentText())
|
||||
|
||||
def set_options(self, options):
|
||||
values = list({option['id'] for option in options})
|
||||
value = str(values[0]) if len(values) == 1 else '...'
|
||||
self.id_.setText(value)
|
||||
|
||||
values = list({option['background'] for option in options})
|
||||
value = str(values[0]) if len(values) == 1 else None
|
||||
self.background.setCurrentText(value)
|
||||
|
||||
values = list({option['shape.ignored_by_focus'] for option in options})
|
||||
value = str(values[0]) if len(values) == 1 else None
|
||||
self.ignored_by_focus.setCurrentText(value)
|
||||
|
||||
values = list({option['panel'] for option in options})
|
||||
value = values[0] if len(values) == 1 else '' if not values else '...'
|
||||
self.panel.setText(str(value + 1 if isinstance(value, int) else value))
|
||||
|
||||
values = list({option['shape.space'] for option in options})
|
||||
value = values[0] if len(values) == 1 else '' if not values else '...'
|
||||
self.space.setCurrentText(value)
|
||||
|
||||
values = list({option['shape.anchor'] for option in options})
|
||||
value = values[0] if len(values) == 1 else '' if not values else '...'
|
||||
self.anchor.setCurrentText(value)
|
||||
self.anchor.setEnabled(self.space.currentText() == 'screen')
|
||||
|
||||
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.blockSignals(True)
|
||||
self.shape.setCurrentText(value)
|
||||
self.shape.blockSignals(False)
|
||||
self.height.setEnabled('custom' not in values)
|
||||
self.width.setEnabled('custom' not in values)
|
||||
|
||||
if len(options) == 1:
|
||||
self.path_editor.setEnabled(options[0]['shape'] == 'custom')
|
||||
self.path_editor.setVisible(options[0]['shape'] == 'custom')
|
||||
self.path_editor.set_options(options[0])
|
||||
else:
|
||||
self.path_editor.setEnabled(False)
|
||||
self.path_editor.setVisible(False)
|
||||
self.path_editor.set_options(None)
|
||||
|
||||
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.ratio = BoolCombo(True)
|
||||
method = partial(self.optionSet.emit, 'image.ratio')
|
||||
self.ratio.valueSet.connect(method)
|
||||
|
||||
self.width = FloatEdit(minimum=0)
|
||||
method = partial(self.optionSet.emit, 'image.width')
|
||||
self.width.valueSet.connect(method)
|
||||
|
||||
self.height = FloatEdit(minimum=0)
|
||||
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('Preserve ratio', self.ratio)
|
||||
self.layout.addRow('Width', self.width)
|
||||
self.layout.addRow('Height', self.height)
|
||||
self.layout.addItem(QtWidgets.QSpacerItem(0, 8))
|
||||
|
||||
for label in self.findChildren(QtWidgets.QLabel):
|
||||
alignment = QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter
|
||||
if not isinstance(label, Title):
|
||||
label.setAlignment(alignment)
|
||||
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.ratio'] for option in options})
|
||||
value = str(values[0]) if len(values) == 1 else None
|
||||
self.ratio.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 = IntEdit(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 = IntEdit(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.addItem(QtWidgets.QSpacerItem(0, 8))
|
||||
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)
|
||||
self.layout.addItem(QtWidgets.QSpacerItem(0, 8))
|
||||
|
||||
for label in self.findChildren(QtWidgets.QLabel):
|
||||
alignment = QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter
|
||||
if not isinstance(label, Title):
|
||||
label.setAlignment(alignment)
|
||||
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, document, display_options, 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._add_targets.released.connect(self.call_add_targets)
|
||||
self._add_targets.setToolTip('Add selected objects')
|
||||
self._add_targets.setFocusPolicy(QtCore.Qt.NoFocus)
|
||||
|
||||
self._remove_targets = QtWidgets.QPushButton('Remove')
|
||||
self._remove_targets.released.connect(self.call_remove_targets)
|
||||
self._remove_targets.setToolTip('Remove selected objects')
|
||||
self._remove_targets.setFocusPolicy(QtCore.Qt.NoFocus)
|
||||
|
||||
self._replace_targets = QtWidgets.QPushButton('Replace')
|
||||
self._replace_targets.clicked.connect(self.call_replace_targets)
|
||||
self._replace_targets.setToolTip('Replace target by current selection')
|
||||
self._replace_targets.setFocusPolicy(QtCore.Qt.NoFocus)
|
||||
|
||||
self._clear_targets = QtWidgets.QPushButton('Clear')
|
||||
self._clear_targets.clicked.connect(self.call_clear_targets)
|
||||
self._clear_targets.setToolTip('Clear shape targets')
|
||||
self._clear_targets.setFocusPolicy(QtCore.Qt.NoFocus)
|
||||
|
||||
_targets = QtWidgets.QWidget()
|
||||
self._targets_layout = QtWidgets.QHBoxLayout(_targets)
|
||||
self._targets_layout.setContentsMargins(0, 0, 0, 0)
|
||||
self._targets_layout.setSpacing(2)
|
||||
self._targets_layout.addStretch()
|
||||
self._targets_layout.addWidget(self._add_targets)
|
||||
self._targets_layout.addWidget(self._remove_targets)
|
||||
self._targets_layout.addWidget(self._replace_targets)
|
||||
self._targets_layout.addWidget(self._clear_targets)
|
||||
|
||||
self._commands = CommandsEditor()
|
||||
method = partial(self.optionSet.emit, 'action.commands')
|
||||
self._commands.valueSet.connect(method)
|
||||
|
||||
self._menu = MenuCommandsEditor()
|
||||
method = partial(self.optionSet.emit, 'action.menu_commands')
|
||||
self._menu.valueSet.connect(method)
|
||||
|
||||
self.select_one_shape_label = QtWidgets.QLabel('Select only one shape')
|
||||
self.children = ChildrenWidget(document, display_options)
|
||||
method = partial(self.optionSet.emit, 'children')
|
||||
self.children.children_changed.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.addWidget(_targets)
|
||||
self.layout = QtWidgets.QVBoxLayout(self)
|
||||
self.layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.layout.setSpacing(0)
|
||||
self.layout.addLayout(form)
|
||||
self.layout.addItem(QtWidgets.QSpacerItem(0, 8))
|
||||
|
||||
for label in self.findChildren(QtWidgets.QLabel):
|
||||
alignment = QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter
|
||||
if not isinstance(label, Title):
|
||||
label.setAlignment(alignment)
|
||||
label.setFixedWidth(LEFT_CELL_WIDTH)
|
||||
|
||||
self.layout.addWidget(Title('Scripts'))
|
||||
self.layout.addWidget(self._commands)
|
||||
self.layout.addItem(QtWidgets.QSpacerItem(0, 8))
|
||||
self.layout.addWidget(Title('Right Click Menu'))
|
||||
self.layout.addWidget(self._menu)
|
||||
self.layout.addItem(QtWidgets.QSpacerItem(0, 8))
|
||||
self.layout.addWidget(Title('Hierarchy'))
|
||||
self.layout.addWidget(self.select_one_shape_label)
|
||||
self.layout.addWidget(self.children)
|
||||
self.layout.addItem(QtWidgets.QSpacerItem(0, 8))
|
||||
|
||||
def targets(self):
|
||||
targets = str(self._targets.text())
|
||||
try:
|
||||
return [t.strip(" ") for t in targets.split(',')]
|
||||
except ValueError:
|
||||
return []
|
||||
|
||||
def call_clear_targets(self):
|
||||
self._targets.setText('')
|
||||
self.targets_changed()
|
||||
|
||||
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_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_changed()
|
||||
|
||||
def call_replace_targets(self):
|
||||
selection = cmds.ls(selection=True, flatten=True)
|
||||
if not selection:
|
||||
return
|
||||
self._targets.setText(', '.join(selection))
|
||||
self.targets_changed()
|
||||
|
||||
def targets_changed(self):
|
||||
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)
|
||||
self._menu.set_options(options)
|
||||
self.select_one_shape_label.setVisible(len(options) != 1)
|
||||
self.children.clear()
|
||||
if len(options) == 1:
|
||||
self.children.set_children(options[0]['children'])
|
||||
|
||||
|
||||
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)
|
||||
self.layout.addItem(QtWidgets.QSpacerItem(0, 8))
|
||||
|
||||
for label in self.findChildren(QtWidgets.QLabel):
|
||||
alignment = QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter
|
||||
if not isinstance(label, Title):
|
||||
label.setAlignment(alignment)
|
||||
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)
|
||||
451
2023/scripts/animation_tools/dwpicker/designer/canvas.py
Normal file
@@ -0,0 +1,451 @@
|
||||
from functools import partial
|
||||
from ..pyside import QtCore, QtGui, QtWidgets
|
||||
from maya import cmds
|
||||
|
||||
from ..align import align_shapes_on_line
|
||||
from ..interactive import Manipulator, SelectionSquare
|
||||
from ..interactionmanager import InteractionManager
|
||||
from ..optionvar import SNAP_GRID_X, SNAP_GRID_Y, SNAP_ITEMS
|
||||
from ..geometry import get_shapes_bounding_rects, get_connection_path
|
||||
from ..painting import (
|
||||
draw_editor_canvas, draw_shape, draw_manipulator, draw_selection_square,
|
||||
draw_parenting_shapes, draw_current_panel, draw_shape_as_child_background,
|
||||
draw_connections)
|
||||
from ..qtutils import get_cursor
|
||||
from ..selection import Selection, get_selection_mode
|
||||
from ..shape import cursor_in_shape
|
||||
from ..transform import Transform
|
||||
from ..viewport import ViewportMapper
|
||||
|
||||
|
||||
def load_saved_snap():
|
||||
if not cmds.optionVar(query=SNAP_ITEMS):
|
||||
return
|
||||
return (
|
||||
cmds.optionVar(query=SNAP_GRID_X),
|
||||
cmds.optionVar(query=SNAP_GRID_Y))
|
||||
|
||||
|
||||
class ShapeEditCanvas(QtWidgets.QWidget):
|
||||
selectedShapesChanged = QtCore.Signal()
|
||||
callContextMenu = QtCore.Signal(QtCore.QPoint)
|
||||
|
||||
def __init__(self, document, display_options, parent=None):
|
||||
super(ShapeEditCanvas, self).__init__(parent)
|
||||
self.setMouseTracking(True)
|
||||
|
||||
self.display_options = display_options
|
||||
method = partial(self.update_selection, False)
|
||||
self.display_options.options_changed.connect(method)
|
||||
self.display_options.options_changed.connect(self.update)
|
||||
|
||||
self.document = document
|
||||
method = partial(self.update_selection, False)
|
||||
self.document.data_changed.connect(method)
|
||||
|
||||
self.drag_shapes = []
|
||||
self.viewportmapper = ViewportMapper()
|
||||
self.viewportmapper.viewsize = self.size()
|
||||
|
||||
self.interaction_manager = InteractionManager()
|
||||
|
||||
self.selection = Selection(self.document)
|
||||
self.selection_square = SelectionSquare()
|
||||
self.manipulator = Manipulator(self.viewportmapper)
|
||||
self.transform = Transform(load_saved_snap())
|
||||
|
||||
self.parenting_shapes = None
|
||||
self.clicked_shape = None
|
||||
self.increase_undo_on_release = False
|
||||
self.lock_background_shape = True
|
||||
|
||||
self.ctrl_pressed = False
|
||||
self.shit_pressed = False
|
||||
|
||||
def wheelEvent(self, event):
|
||||
# To center the zoom on the mouse, we save a reference mouse position
|
||||
# and compare the offset after zoom computation.
|
||||
factor = .25 if event.angleDelta().y() > 0 else -.25
|
||||
self.zoom(factor, event.pos())
|
||||
self.update()
|
||||
|
||||
def select_panel_shapes(self, panel):
|
||||
panel_shapes = [
|
||||
s for s in self.document.shapes if
|
||||
s.options['panel'] == panel]
|
||||
if panel_shapes:
|
||||
self.selection.set(panel_shapes)
|
||||
self.update_selection()
|
||||
|
||||
def focus(self):
|
||||
shapes = self.selection.shapes or self.visible_shapes()
|
||||
if not shapes:
|
||||
self.update()
|
||||
return
|
||||
self.viewportmapper.viewsize = self.size()
|
||||
rect = get_shapes_bounding_rects(shapes)
|
||||
self.viewportmapper.focus(rect)
|
||||
self.update()
|
||||
|
||||
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 set_lock_background_shape(self, state):
|
||||
self.lock_background_shape = state
|
||||
|
||||
def get_hovered_shape(self, cursor, skip_backgrounds=False):
|
||||
for shape in reversed(self.list_shapes(skip_backgrounds)):
|
||||
if cursor_in_shape(shape, cursor):
|
||||
return shape
|
||||
|
||||
def list_shapes(self, skip_background=False):
|
||||
if self.lock_background_shape or skip_background:
|
||||
return [
|
||||
shape for shape in self.visible_shapes()
|
||||
if not shape.is_background()]
|
||||
return self.visible_shapes()
|
||||
|
||||
def leaveEvent(self, _):
|
||||
for shape in self.list_shapes():
|
||||
shape.hovered = False
|
||||
self.update()
|
||||
|
||||
def mousePressEvent(self, event):
|
||||
skip = (
|
||||
event.button() == QtCore.Qt.RightButton and
|
||||
self.interaction_manager.left_click_pressed)
|
||||
if skip:
|
||||
return
|
||||
self.setFocus(QtCore.Qt.MouseFocusReason) # This is not automatic
|
||||
if self.drag_shapes and event.button() == QtCore.Qt.LeftButton:
|
||||
pos = self.viewportmapper.to_units_coords(event.pos())
|
||||
align_shapes_on_line(self.drag_shapes, pos, pos)
|
||||
self.interaction_manager.update(
|
||||
event,
|
||||
pressed=True,
|
||||
has_shape_hovered=False,
|
||||
dragging=bool(self.drag_shapes))
|
||||
self.update()
|
||||
return
|
||||
|
||||
cursor = self.viewportmapper.to_units_coords(get_cursor(self))
|
||||
hovered_shape = self.get_hovered_shape(cursor)
|
||||
self.transform.direction = self.manipulator.get_direction(event.pos())
|
||||
parenting = (
|
||||
self.interaction_manager.alt_pressed and not
|
||||
self.interaction_manager.ctrl_pressed and not
|
||||
self.interaction_manager.shift_pressed and
|
||||
hovered_shape and not hovered_shape.options['background'])
|
||||
|
||||
if parenting:
|
||||
self.parenting_shapes = [hovered_shape, None]
|
||||
self.interaction_manager.update(
|
||||
event,
|
||||
pressed=True,
|
||||
has_shape_hovered=True,
|
||||
dragging=True)
|
||||
self.update()
|
||||
return
|
||||
|
||||
if event.button() != QtCore.Qt.LeftButton:
|
||||
self.interaction_manager.update(
|
||||
event,
|
||||
pressed=True,
|
||||
has_shape_hovered=False,
|
||||
dragging=False)
|
||||
self.update()
|
||||
return
|
||||
|
||||
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.QRectF(self.manipulator.rect)
|
||||
self.transform.set_reference_point(cursor)
|
||||
|
||||
self.update()
|
||||
self.interaction_manager.update(
|
||||
event,
|
||||
pressed=True,
|
||||
has_shape_hovered=bool(hovered_shape),
|
||||
dragging=bool(hovered_shape) or self.transform.direction)
|
||||
|
||||
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.update()
|
||||
|
||||
def mouseMoveEvent(self, event):
|
||||
cursor = self.viewportmapper.to_units_coords(get_cursor(self)).toPoint()
|
||||
|
||||
if self.interaction_manager.mode == InteractionManager.DRAGGING:
|
||||
if self.parenting_shapes:
|
||||
self.parenting_shapes[1] = self.get_hovered_shape(
|
||||
cursor, skip_backgrounds=True)
|
||||
self.update()
|
||||
return
|
||||
|
||||
if self.drag_shapes:
|
||||
point1 = self.viewportmapper.to_units_coords(
|
||||
self.interaction_manager.anchor)
|
||||
point2 = self.viewportmapper.to_units_coords(event.pos())
|
||||
align_shapes_on_line(self.drag_shapes, point1, point2)
|
||||
self.increase_undo_on_release = True
|
||||
return self.update()
|
||||
|
||||
rect = self.manipulator.rect
|
||||
if self.transform.direction:
|
||||
self.transform.resize(self.selection.shapes, cursor)
|
||||
elif rect is not None:
|
||||
self.transform.move(shapes=self.selection, cursor=cursor)
|
||||
for shape in self.selection:
|
||||
shape.synchronize_rect()
|
||||
shape.update_path()
|
||||
shape.synchronize_image()
|
||||
self.increase_undo_on_release = True
|
||||
self.selectedShapesChanged.emit()
|
||||
|
||||
elif self.interaction_manager.mode == InteractionManager.SELECTION:
|
||||
self.selection_square.handle(cursor)
|
||||
for shape in self.list_shapes():
|
||||
shape.hovered = self.selection_square.intersects(shape)
|
||||
|
||||
elif self.interaction_manager.mode == InteractionManager.NAVIGATION:
|
||||
offset = self.interaction_manager.mouse_offset(event.pos())
|
||||
if offset is not None:
|
||||
self.viewportmapper.origin = (
|
||||
self.viewportmapper.origin - offset)
|
||||
|
||||
else:
|
||||
for shape in self.list_shapes():
|
||||
shape.hovered = cursor_in_shape(shape, cursor)
|
||||
|
||||
self.update()
|
||||
|
||||
def mouseReleaseEvent(self, event):
|
||||
if event.button() == QtCore.Qt.RightButton:
|
||||
self.interaction_manager.update(event, pressed=False)
|
||||
return self.callContextMenu.emit(event.pos())
|
||||
|
||||
if event.button() != QtCore.Qt.LeftButton:
|
||||
self.interaction_manager.update(event, pressed=False)
|
||||
return
|
||||
|
||||
if self.parenting_shapes:
|
||||
self.parent_shapes()
|
||||
self.interaction_manager.update(event, pressed=False)
|
||||
return
|
||||
|
||||
if self.drag_shapes:
|
||||
self.add_drag_shapes()
|
||||
|
||||
if self.increase_undo_on_release:
|
||||
self.document.record_undo()
|
||||
self.document.shapes_changed.emit()
|
||||
self.increase_undo_on_release = False
|
||||
|
||||
if self.interaction_manager.mode == InteractionManager.SELECTION:
|
||||
self.select_shapes()
|
||||
|
||||
elif self.interaction_manager.mode == InteractionManager.DRAGGING:
|
||||
self.update_selection(False)
|
||||
|
||||
self.interaction_manager.update(event, pressed=False)
|
||||
self.selection_square.release()
|
||||
self.update()
|
||||
|
||||
def parent_shapes(self):
|
||||
skip = (
|
||||
not self.parenting_shapes[1] or
|
||||
self.parenting_shapes[0].options['id'] ==
|
||||
self.parenting_shapes[1].options['id'])
|
||||
if skip:
|
||||
self.parenting_shapes = None
|
||||
self.update()
|
||||
return
|
||||
|
||||
children = set(self.parenting_shapes[1].options['children'])
|
||||
children.add(self.parenting_shapes[0].options['id'])
|
||||
self.parenting_shapes[1].options['children'] = sorted(children)
|
||||
self.document.shapes_changed.emit()
|
||||
self.document.record_undo()
|
||||
self.parenting_shapes = None
|
||||
|
||||
def visible_shapes(self):
|
||||
conditions = (
|
||||
not self.display_options.isolate or
|
||||
self.display_options.current_panel < 0)
|
||||
if conditions:
|
||||
return self.document.shapes[:]
|
||||
|
||||
return [
|
||||
s for s in self.document.shapes if
|
||||
s.options['panel'] == self.display_options.current_panel]
|
||||
|
||||
def add_drag_shapes(self):
|
||||
shapes_data = [s.options for s in self.drag_shapes]
|
||||
for data in shapes_data:
|
||||
data['panel'] = max((self.display_options.current_panel, 0))
|
||||
shapes = self.document.add_shapes(shapes_data, hierarchize=True)
|
||||
self.document.shapes_changed.emit()
|
||||
self.drag_shapes = []
|
||||
self.selection.replace(shapes)
|
||||
self.update_selection()
|
||||
|
||||
def select_shapes(self):
|
||||
shapes = [
|
||||
s for s in self.list_shapes()
|
||||
if self.selection_square.intersects(s)]
|
||||
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.update()
|
||||
|
||||
def update_selection(self, changed=True):
|
||||
shapes = [s for s in self.selection if s in self.visible_shapes()]
|
||||
if shapes:
|
||||
rect = get_shapes_bounding_rects(shapes)
|
||||
else:
|
||||
rect = None
|
||||
self.manipulator.set_rect(rect)
|
||||
if changed:
|
||||
self.selectedShapesChanged.emit()
|
||||
|
||||
def paintEvent(self, _):
|
||||
try:
|
||||
painter = QtGui.QPainter()
|
||||
painter.begin(self)
|
||||
self.paint(painter)
|
||||
except BaseException:
|
||||
import traceback
|
||||
print(traceback.format_exc())
|
||||
pass # avoid crash
|
||||
# TODO: log the error
|
||||
finally:
|
||||
painter.end()
|
||||
|
||||
def paint(self, painter):
|
||||
painter.setRenderHint(QtGui.QPainter.Antialiasing)
|
||||
visible_shapes = self.visible_shapes()
|
||||
|
||||
# Draw background and marks.
|
||||
draw_editor_canvas(
|
||||
painter, self.rect(),
|
||||
snap=self.transform.snap,
|
||||
viewportmapper=self.viewportmapper)
|
||||
|
||||
# Get the visible shapes.
|
||||
current_panel_shapes = []
|
||||
for shape in self.document.shapes:
|
||||
if shape.options['panel'] == self.display_options.current_panel:
|
||||
current_panel_shapes.append(shape)
|
||||
|
||||
# Draw the current select panel boundaries.
|
||||
if current_panel_shapes:
|
||||
rect = get_shapes_bounding_rects(current_panel_shapes)
|
||||
draw_current_panel(painter, rect, self.viewportmapper)
|
||||
|
||||
# Draw highlighted selected children.
|
||||
for id_ in self.display_options.highlighted_children_ids:
|
||||
shape = self.document.shapes_by_id.get(id_)
|
||||
if shape in visible_shapes:
|
||||
draw_shape_as_child_background(
|
||||
painter, shape, viewportmapper=self.viewportmapper)
|
||||
|
||||
if self.interaction_manager.left_click_pressed:
|
||||
visible_shapes.extend(self.drag_shapes)
|
||||
|
||||
cutter = QtGui.QPainterPath()
|
||||
cutter.setFillRule(QtCore.Qt.WindingFill)
|
||||
|
||||
for shape in visible_shapes:
|
||||
qpath = draw_shape(
|
||||
painter, shape,
|
||||
draw_selected_state=False,
|
||||
viewportmapper=self.viewportmapper)
|
||||
screen_space = shape.options['shape.space'] == 'screen'
|
||||
if not shape.options['background'] or screen_space:
|
||||
cutter.addPath(qpath)
|
||||
|
||||
connections_path = QtGui.QPainterPath()
|
||||
if self.display_options.display_hierarchy:
|
||||
for shape in visible_shapes:
|
||||
for child in shape.options['children']:
|
||||
child = self.document.shapes_by_id.get(child)
|
||||
if child is None:
|
||||
continue
|
||||
start_point = shape.bounding_rect().center()
|
||||
end_point = child.bounding_rect().center()
|
||||
path = get_connection_path(
|
||||
start_point, end_point, self.viewportmapper)
|
||||
connections_path.addPath(path)
|
||||
connections_path = connections_path.subtracted(cutter)
|
||||
draw_connections(painter, connections_path)
|
||||
|
||||
if self.parenting_shapes:
|
||||
draw_parenting_shapes(
|
||||
painter=painter,
|
||||
child=self.parenting_shapes[0],
|
||||
potential_parent=self.parenting_shapes[1],
|
||||
cursor=get_cursor(self),
|
||||
viewportmapper=self.viewportmapper)
|
||||
|
||||
conditions = (
|
||||
self.manipulator.rect is not None and
|
||||
all(self.manipulator.viewport_handlers()))
|
||||
|
||||
if conditions:
|
||||
draw_manipulator(
|
||||
painter, self.manipulator,
|
||||
get_cursor(self), self.viewportmapper)
|
||||
|
||||
if self.selection_square.rect:
|
||||
draw_selection_square(
|
||||
painter, self.selection_square.rect, self.viewportmapper)
|
||||
|
||||
def delete_selection(self):
|
||||
self.document.remove_shapes(self.selection.shapes)
|
||||
self.selection.clear()
|
||||
self.document.shapes_changed.emit()
|
||||
self.manipulator.set_rect(None)
|
||||
self.document.record_undo()
|
||||
17
2023/scripts/animation_tools/dwpicker/designer/display.py
Normal file
@@ -0,0 +1,17 @@
|
||||
|
||||
from ..pyside import QtCore
|
||||
from maya import cmds
|
||||
from ..optionvar import (
|
||||
ISOLATE_CURRENT_PANEL_SHAPES, DISPLAY_HIERARCHY_IN_CANVAS)
|
||||
|
||||
|
||||
class DisplayOptions(QtCore.QObject):
|
||||
options_changed = QtCore.Signal()
|
||||
|
||||
def __init__(self):
|
||||
super(DisplayOptions, self).__init__()
|
||||
self.isolate = cmds.optionVar(query=ISOLATE_CURRENT_PANEL_SHAPES)
|
||||
self.current_panel = -1
|
||||
self.highlighted_children_ids = []
|
||||
state = cmds.optionVar(query=DISPLAY_HIERARCHY_IN_CANVAS)
|
||||
self.display_hierarchy = bool(state)
|
||||
613
2023/scripts/animation_tools/dwpicker/designer/editor.py
Normal file
@@ -0,0 +1,613 @@
|
||||
from functools import partial
|
||||
from copy import deepcopy
|
||||
|
||||
from ..pyside import QtWidgets, QtCore, QtGui
|
||||
from maya import cmds
|
||||
|
||||
from .. import clipboard
|
||||
from ..align import align_shapes, arrange_horizontal, arrange_vertical
|
||||
from ..arrayutils import (
|
||||
move_elements_to_array_end, move_elements_to_array_begin,
|
||||
move_up_array_elements, move_down_array_elements)
|
||||
from ..dialog import (
|
||||
SearchAndReplaceDialog, warning, SettingsPaster, get_image_path)
|
||||
from ..geometry import (
|
||||
rect_symmetry, path_symmetry, get_shapes_bounding_rects,
|
||||
rect_top_left_symmetry)
|
||||
from ..optionvar import BG_LOCKED, TRIGGER_REPLACE_ON_MIRROR
|
||||
from ..path import format_path
|
||||
from ..qtutils import set_shortcut, get_cursor
|
||||
from ..shape import Shape, get_shape_rect_from_options
|
||||
from ..shapelibrary import ShapeLibraryMenu
|
||||
from ..stack import count_panels
|
||||
from ..templates import BUTTON, TEXT, BACKGROUND, SHAPE_BUTTON
|
||||
|
||||
from .canvas import ShapeEditCanvas
|
||||
from .display import DisplayOptions
|
||||
from .menu import MenuWidget
|
||||
from .attributes import AttributeEditor
|
||||
|
||||
|
||||
DIRECTION_OFFSETS = {
|
||||
'Left': (-1, 0), 'Right': (1, 0), 'Up': (0, -1), 'Down': (0, 1)}
|
||||
|
||||
|
||||
class PickerEditor(QtWidgets.QWidget):
|
||||
|
||||
def __init__(self, document, parent=None):
|
||||
super(PickerEditor, self).__init__(parent, QtCore.Qt.Window)
|
||||
title = "Picker editor - " + document.data['general']['name']
|
||||
self.setWindowTitle(title)
|
||||
|
||||
self.document = document
|
||||
self.document.shapes_changed.connect(self.update)
|
||||
self.document.general_option_changed.connect(self.generals_modified)
|
||||
self.document.data_changed.connect(self.update)
|
||||
self.document.data_changed.connect(self.selection_changed)
|
||||
|
||||
self.display_options = DisplayOptions()
|
||||
|
||||
self.shape_canvas = ShapeEditCanvas(
|
||||
self.document, self.display_options)
|
||||
self.shape_canvas.callContextMenu.connect(self.call_context_menu)
|
||||
bg_locked = bool(cmds.optionVar(query=BG_LOCKED))
|
||||
self.shape_canvas.set_lock_background_shape(bg_locked)
|
||||
self.shape_canvas.selectedShapesChanged.connect(self.selection_changed)
|
||||
|
||||
self.shape_library_menu = ShapeLibraryMenu(self)
|
||||
self.shape_library_menu.path_selected.connect(
|
||||
self.create_library_shape)
|
||||
|
||||
self.menu = MenuWidget(self.display_options)
|
||||
self.menu.copyRequested.connect(self.copy)
|
||||
self.menu.copySettingsRequested.connect(self.copy_settings)
|
||||
self.menu.deleteRequested.connect(self.shape_canvas.delete_selection)
|
||||
self.menu.pasteRequested.connect(self.paste)
|
||||
self.menu.pasteSettingsRequested.connect(self.paste_settings)
|
||||
self.menu.snapValuesChanged.connect(self.snap_value_changed)
|
||||
self.menu.buttonLibraryRequested.connect(self.call_library)
|
||||
self.menu.useSnapToggled.connect(self.use_snap)
|
||||
method = self.shape_canvas.set_lock_background_shape
|
||||
self.menu.lockBackgroundShapeToggled.connect(method)
|
||||
self.menu.undoRequested.connect(self.document.undo)
|
||||
self.menu.redoRequested.connect(self.document.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, image=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_canvas, self.document.undo)
|
||||
set_shortcut("Ctrl+Y", self.shape_canvas, self.document.redo)
|
||||
set_shortcut("Ctrl+C", self.shape_canvas, self.copy)
|
||||
set_shortcut("Ctrl+V", self.shape_canvas, self.paste)
|
||||
set_shortcut("Ctrl+R", self.shape_canvas, self.search_and_replace)
|
||||
set_shortcut("del", self.shape_canvas, self.shape_canvas.delete_selection)
|
||||
set_shortcut("Ctrl+D", self.shape_canvas, self.deselect_all)
|
||||
set_shortcut("Ctrl+A", self.shape_canvas, self.select_all)
|
||||
set_shortcut("Ctrl+I", self.shape_canvas, self.invert_selection)
|
||||
set_shortcut("U", self.shape_canvas, self.update_targets_on_selection)
|
||||
set_shortcut("F", self.shape_canvas, self.shape_canvas.focus)
|
||||
for direction in ['Left', 'Right', 'Up', 'Down']:
|
||||
method = partial(self.move_selection, direction)
|
||||
shortcut = set_shortcut(direction, self.shape_canvas, method)
|
||||
shortcut.setAutoRepeat(True)
|
||||
|
||||
self.attribute_editor = AttributeEditor(document, self.display_options)
|
||||
self.attribute_editor.optionSet.connect(self.option_set)
|
||||
self.attribute_editor.optionsSet.connect(self.options_set)
|
||||
self.attribute_editor.imageModified.connect(self.image_modified)
|
||||
self.attribute_editor.selectLayerContent.connect(self.select_layer)
|
||||
self.attribute_editor.panelDoubleClicked.connect(
|
||||
self.shape_canvas.select_panel_shapes)
|
||||
|
||||
self.hlayout = QtWidgets.QHBoxLayout()
|
||||
self.hlayout.setSizeConstraint(QtWidgets.QLayout.SetMaximumSize)
|
||||
self.hlayout.setContentsMargins(0, 0, 0, 0)
|
||||
self.hlayout.addWidget(self.shape_canvas)
|
||||
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 call_library(self, point):
|
||||
self.shape_library_menu.move(point)
|
||||
self.shape_library_menu.show()
|
||||
|
||||
def panels_changed(self, panels):
|
||||
self.document.data['general']['panels'] = panels
|
||||
|
||||
def panels_resized(self, panels):
|
||||
self.document.data['general']['panels'] = panels
|
||||
|
||||
def copy(self):
|
||||
clipboard.set([
|
||||
deepcopy(s.options) for s in self.shape_canvas.selection])
|
||||
|
||||
def copy_settings(self):
|
||||
if len(self.shape_canvas.selection) != 1:
|
||||
return warning('Copy settings', 'Please select only one shape')
|
||||
shape = self.shape_canvas.selection[0]
|
||||
clipboard.set_settings(deepcopy(shape.options))
|
||||
|
||||
def sizeHint(self):
|
||||
return QtCore.QSize(1300, 750)
|
||||
|
||||
def paste(self):
|
||||
clipboad_copy = [deepcopy(s) for s in clipboard.get()]
|
||||
shapes = self.document.add_shapes(clipboad_copy)
|
||||
self.shape_canvas.selection.replace(shapes)
|
||||
self.shape_canvas.update_selection()
|
||||
self.document.record_undo()
|
||||
self.document.shapes_changed.emit()
|
||||
|
||||
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_canvas.selection:
|
||||
shape.options.update(deepcopy(settings))
|
||||
shape.rect = get_shape_rect_from_options(shape.options)
|
||||
shape.synchronize_image()
|
||||
shape.update_path()
|
||||
self.document.record_undo()
|
||||
self.document.shapes_changed.emit()
|
||||
self.selection_changed()
|
||||
self.shape_canvas.update_selection()
|
||||
self.shape_canvas.update()
|
||||
|
||||
def deselect_all(self):
|
||||
self.shape_canvas.selection.clear()
|
||||
self.shape_canvas.update_selection()
|
||||
self.shape_canvas.update()
|
||||
|
||||
def select_all(self):
|
||||
shapes = self.shape_canvas.list_shapes()
|
||||
self.shape_canvas.selection.add(shapes)
|
||||
self.shape_canvas.update_selection()
|
||||
self.shape_canvas.update()
|
||||
|
||||
def invert_selection(self):
|
||||
self.shape_canvas.selection.invert(self.shape_canvas.shapes)
|
||||
if self.menu.lock_bg.isChecked():
|
||||
shapes = [
|
||||
s for s in self.shape_canvas.selection
|
||||
if not s.is_background()]
|
||||
self.shape_canvas.selection.set(shapes)
|
||||
self.shape_canvas.update_selection()
|
||||
self.shape_canvas.update()
|
||||
|
||||
def use_snap(self, state):
|
||||
snap = self.menu.snap_values() if state else None
|
||||
self.shape_canvas.transform.snap = snap
|
||||
self.shape_canvas.update()
|
||||
|
||||
def snap_value_changed(self):
|
||||
self.shape_canvas.transform.snap = self.menu.snap_values()
|
||||
self.shape_canvas.update()
|
||||
|
||||
def generals_modified(self, _, key):
|
||||
if key == 'name':
|
||||
title = "Picker editor - " + self.document.data['general']['name']
|
||||
self.setWindowTitle(title)
|
||||
|
||||
def options_set(self, options, rect_update):
|
||||
for shape in self.shape_canvas.selection:
|
||||
shape.options.update(options)
|
||||
if rect_update:
|
||||
shape.rect = QtCore.QRectF(
|
||||
options['shape.left'],
|
||||
options['shape.top'],
|
||||
options['shape.width'],
|
||||
options['shape.height'])
|
||||
shape.update_path()
|
||||
self.shape_canvas.update()
|
||||
self.update_manipulator_rect()
|
||||
self.document.record_undo()
|
||||
self.document.shapes_changed.emit()
|
||||
|
||||
def option_set(self, option, value):
|
||||
update_geometries = False
|
||||
update_selection = False
|
||||
rect_options = (
|
||||
'shape.top', 'shape.width', 'shape.height', 'shape.left')
|
||||
|
||||
for shape in self.shape_canvas.selection:
|
||||
shape.options[option] = value
|
||||
if option in ('shape.path', 'shape'):
|
||||
if value == 'custom' and not shape.options['shape.path']:
|
||||
update_selection = True
|
||||
shape.update_path()
|
||||
shape.synchronize_image()
|
||||
update_geometries = True
|
||||
|
||||
if option in rect_options:
|
||||
shape.rect = QtCore.QRectF(
|
||||
shape.options['shape.left'],
|
||||
shape.options['shape.top'],
|
||||
shape.options['shape.width'],
|
||||
shape.options['shape.height'])
|
||||
shape.update_path()
|
||||
shape.synchronize_image()
|
||||
update_geometries = True
|
||||
|
||||
if update_selection:
|
||||
self.selection_changed()
|
||||
if update_geometries:
|
||||
self.update_manipulator_rect()
|
||||
|
||||
if option == 'visibility_layer':
|
||||
self.layers_modified()
|
||||
else:
|
||||
self.document.shapes_changed.emit()
|
||||
self.document.record_undo()
|
||||
self.shape_canvas.update()
|
||||
|
||||
def selection_changed(self):
|
||||
shapes = self.shape_canvas.selection
|
||||
options = [shape.options for shape in shapes]
|
||||
self.attribute_editor.set_options(options)
|
||||
|
||||
def create_shapes(self, targets, use_clipboard_data=False):
|
||||
shapes = []
|
||||
for target in targets:
|
||||
template = deepcopy(BUTTON)
|
||||
if use_clipboard_data:
|
||||
template.update(deepcopy(clipboard.get_settings()))
|
||||
template['action.targets'] = [target]
|
||||
shapes.append(Shape(template))
|
||||
self.shape_canvas.drag_shapes = shapes
|
||||
|
||||
def create_library_shape(self, path):
|
||||
options = deepcopy(SHAPE_BUTTON)
|
||||
options['shape.path'] = deepcopy(path)
|
||||
self.create_shape(options)
|
||||
|
||||
def create_shape(
|
||||
self, template, before=False, position=None, targets=None,
|
||||
image=False):
|
||||
|
||||
options = deepcopy(template)
|
||||
panel = self.shape_canvas.display_options.current_panel
|
||||
options['panel'] = max((panel, 0))
|
||||
if image:
|
||||
filename = get_image_path(self, "Select background image.")
|
||||
if filename:
|
||||
filename = format_path(filename)
|
||||
options['image.path'] = filename
|
||||
qimage = QtGui.QImage(filename)
|
||||
options['image.width'] = qimage.size().width()
|
||||
options['image.height'] = qimage.size().height()
|
||||
options['shape.width'] = qimage.size().width()
|
||||
options['shape.height'] = qimage.size().height()
|
||||
options['bgcolor.transparency'] = 255
|
||||
|
||||
shape = Shape(options)
|
||||
if not position:
|
||||
center = self.shape_canvas.rect().center()
|
||||
center = self.shape_canvas.viewportmapper.to_units_coords(center)
|
||||
if not options['shape.path']:
|
||||
shape.rect.moveCenter(center)
|
||||
else:
|
||||
shape.rect.moveTopLeft(center - shape.bounding_rect().center())
|
||||
else:
|
||||
tl = self.shape_canvas.viewportmapper.to_units_coords(position)
|
||||
shape.rect.moveTopLeft(tl)
|
||||
if targets:
|
||||
shape.set_targets(targets)
|
||||
|
||||
shape.synchronize_rect()
|
||||
shape.update_path()
|
||||
shapes = self.document.add_shapes([shape.options], prepend=before)
|
||||
self.document.shapes_changed.emit()
|
||||
self.document.record_undo()
|
||||
self.shape_canvas.selection.replace(shapes)
|
||||
self.selection_changed()
|
||||
self.update_manipulator_rect()
|
||||
|
||||
def update_targets_on_selection(self):
|
||||
if not self.shape_canvas.selection:
|
||||
return
|
||||
targets = cmds.ls(selection=True)
|
||||
for shape in self.shape_canvas.selection:
|
||||
shape.set_targets(targets)
|
||||
self.shape_canvas.update()
|
||||
self.document.shapes_changed.emit()
|
||||
self.document.record_undo()
|
||||
|
||||
def update_targets(self, shape):
|
||||
shape.set_targets(cmds.ls(selection=True))
|
||||
self.shape_canvas.update()
|
||||
self.document.shapes_changed.emit()
|
||||
self.document.record_undo()
|
||||
|
||||
def image_modified(self):
|
||||
for shape in self.shape_canvas.selection:
|
||||
shape.synchronize_image()
|
||||
self.shape_canvas.update()
|
||||
|
||||
def set_selection_move_on_stack(self, function, inplace=True):
|
||||
selected_ids = [s.options['id'] for s in self.shape_canvas.selection]
|
||||
all_ids = list(self.document.shapes_by_id)
|
||||
result = function(all_ids, selected_ids)
|
||||
if inplace:
|
||||
result = all_ids
|
||||
data = [self.document.shapes_by_id[id_].options for id_ in result]
|
||||
self.document.set_shapes_data(data)
|
||||
self.document.record_undo()
|
||||
self.document.shapes_changed.emit()
|
||||
self.shape_canvas.update()
|
||||
|
||||
def set_selection_move_down(self):
|
||||
self.set_selection_move_on_stack(move_down_array_elements, True)
|
||||
|
||||
def set_selection_move_up(self):
|
||||
self.set_selection_move_on_stack(move_up_array_elements, True)
|
||||
|
||||
def set_selection_on_top(self):
|
||||
self.set_selection_move_on_stack(move_elements_to_array_end, False)
|
||||
|
||||
def set_selection_on_bottom(self):
|
||||
self.set_selection_move_on_stack(move_elements_to_array_begin, False)
|
||||
|
||||
def update_manipulator_rect(self):
|
||||
rect = get_shapes_bounding_rects(self.shape_canvas.selection)
|
||||
self.shape_canvas.manipulator.set_rect(rect)
|
||||
self.shape_canvas.update()
|
||||
|
||||
def do_symmetry(self, horizontal=True):
|
||||
shapes = self.shape_canvas.selection.shapes
|
||||
for shape in shapes:
|
||||
if shape.options['shape'] == 'custom':
|
||||
path_symmetry(
|
||||
path=shape.options['shape.path'],
|
||||
horizontal=horizontal)
|
||||
rect_top_left_symmetry(
|
||||
rect=shape.rect,
|
||||
point=self.shape_canvas.manipulator.rect.center(),
|
||||
horizontal=horizontal)
|
||||
shape.synchronize_rect()
|
||||
shape.update_path()
|
||||
else:
|
||||
rect_symmetry(
|
||||
rect=shape.rect,
|
||||
point=self.shape_canvas.manipulator.rect.center(),
|
||||
horizontal=horizontal)
|
||||
shape.synchronize_rect()
|
||||
self.shape_canvas.update()
|
||||
self.document.shapes_changed.emit()
|
||||
if not cmds.optionVar(query=TRIGGER_REPLACE_ON_MIRROR):
|
||||
self.document.record_undo()
|
||||
return
|
||||
if not self.search_and_replace():
|
||||
self.document.record_undo()
|
||||
self.attribute_editor.update()
|
||||
self.update_manipulator_rect()
|
||||
|
||||
def search_and_replace(self):
|
||||
dialog = SearchAndReplaceDialog()
|
||||
if not dialog.exec_():
|
||||
return False
|
||||
|
||||
if dialog.filter == 0: # Search on all shapes.
|
||||
shapes = self.shape_canvas.shapes
|
||||
else:
|
||||
shapes = self.shape_canvas.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.document.shape_changed.emit()
|
||||
self.document.record_undo()
|
||||
self.shape_canvas.update()
|
||||
return True
|
||||
|
||||
def move_selection(self, direction):
|
||||
offset = DIRECTION_OFFSETS[direction]
|
||||
rect = self.shape_canvas.manipulator.rect
|
||||
reference_rect = QtCore.QRectF(rect)
|
||||
|
||||
self.shape_canvas.transform.set_rect(rect)
|
||||
self.shape_canvas.transform.reference_rect = reference_rect
|
||||
self.shape_canvas.transform.shift(
|
||||
self.shape_canvas.selection.shapes, offset)
|
||||
for shape in self.shape_canvas.selection:
|
||||
shape.synchronize_rect()
|
||||
shape.update_path()
|
||||
self.shape_canvas.update()
|
||||
self.selection_changed()
|
||||
self.document.record_undo()
|
||||
self.document.shapes_changed.emit()
|
||||
|
||||
def align_selection(self, direction):
|
||||
if not self.shape_canvas.selection:
|
||||
return
|
||||
align_shapes(self.shape_canvas.selection, direction)
|
||||
rect = get_shapes_bounding_rects(self.shape_canvas.selection)
|
||||
self.shape_canvas.manipulator.set_rect(rect)
|
||||
self.shape_canvas.update()
|
||||
self.selection_changed()
|
||||
self.document.record_undo()
|
||||
self.document.shapes_changed.emit()
|
||||
|
||||
def arrange_selection(self, direction):
|
||||
if not self.shape_canvas.selection:
|
||||
return
|
||||
if direction == 'horizontal':
|
||||
arrange_horizontal(self.shape_canvas.selection)
|
||||
else:
|
||||
arrange_vertical(self.shape_canvas.selection)
|
||||
rect = get_shapes_bounding_rects(self.shape_canvas.selection)
|
||||
self.shape_canvas.manipulator.set_rect(rect)
|
||||
self.shape_canvas.update()
|
||||
self.selection_changed()
|
||||
self.document.record_undo()
|
||||
self.document.shapes_changed.emit()
|
||||
|
||||
def call_context_menu(self, position):
|
||||
targets = cmds.ls(selection=True)
|
||||
button = QtWidgets.QAction('Add selection button', self)
|
||||
method = partial(
|
||||
self.create_shape, deepcopy(BUTTON),
|
||||
position=position, targets=targets)
|
||||
button.triggered.connect(method)
|
||||
|
||||
template = deepcopy(BUTTON)
|
||||
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)
|
||||
|
||||
button3 = QtWidgets.QAction('Add selection multiple buttons', self)
|
||||
button3.triggered.connect(partial(self.create_shapes, targets))
|
||||
button3.setEnabled(len(targets) > 1)
|
||||
|
||||
text = 'Add selection multiple buttons (using settings clipboard)'
|
||||
button4 = QtWidgets.QAction(text, self)
|
||||
button4.triggered.connect(partial(self.create_shapes, targets, True))
|
||||
button4.setEnabled(len(targets) > 1)
|
||||
|
||||
cursor = get_cursor(self.shape_canvas)
|
||||
cursor = self.shape_canvas.viewportmapper.to_units_coords(cursor)
|
||||
hovered_shape = self.shape_canvas.get_hovered_shape(cursor)
|
||||
|
||||
method = partial(self.update_targets, hovered_shape)
|
||||
text = 'Update targets'
|
||||
button5 = QtWidgets.QAction(text, self)
|
||||
button5.setEnabled(bool(hovered_shape))
|
||||
button5.triggered.connect(method)
|
||||
|
||||
button6 = QtWidgets.QAction('Clear children', self)
|
||||
button6.setEnabled(bool(self.shape_canvas.selection or hovered_shape))
|
||||
method = partial(self.clear_children, hovered_shape)
|
||||
button6.triggered.connect(method)
|
||||
|
||||
menu = QtWidgets.QMenu()
|
||||
menu.addAction(button)
|
||||
menu.addAction(button2)
|
||||
menu.addAction(button3)
|
||||
menu.addAction(button4)
|
||||
menu.addAction(button5)
|
||||
menu.addSection('Hierarchy')
|
||||
menu.addAction(button6)
|
||||
menu.addSection('Visibility Layers')
|
||||
|
||||
layers = sorted(list({
|
||||
s.visibility_layer()
|
||||
for s in self.document.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_canvas.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_canvas.selection.shapes))
|
||||
menu.addAction(create_layer)
|
||||
|
||||
menu.addSeparator()
|
||||
assign_to_panel = QtWidgets.QMenu('Assign to panel', self)
|
||||
for i in range(count_panels(self.document.data['general']['panels'])):
|
||||
action = QtWidgets.QAction(str(i + 1), self)
|
||||
action.triggered.connect(partial(self.assign_to_panel, i))
|
||||
assign_to_panel.addAction(action)
|
||||
menu.addMenu(assign_to_panel)
|
||||
menu.exec_(self.shape_canvas.mapToGlobal(position))
|
||||
|
||||
def clear_children(self, hovered_shape):
|
||||
if hovered_shape and hovered_shape not in self.shape_canvas.selection:
|
||||
shapes = [hovered_shape]
|
||||
else:
|
||||
shapes = self.shape_canvas.selection
|
||||
|
||||
for shape in shapes:
|
||||
shape.options['children'] = []
|
||||
self.document.shapes_changed.emit()
|
||||
self.document.record_undo()
|
||||
|
||||
def set_visibility_layer(self, layer=''):
|
||||
for shape in self.shape_canvas.selection:
|
||||
shape.options['visibility_layer'] = layer
|
||||
self.layers_modified()
|
||||
|
||||
def assign_to_panel(self, panel):
|
||||
for shape in self.shape_canvas.selection:
|
||||
shape.options['panel'] = panel
|
||||
self.document.shapes_changed.emit()
|
||||
self.document.record_undo()
|
||||
self.document.sync_shapes_caches()
|
||||
self.shape_canvas.update_selection(False)
|
||||
|
||||
def layers_modified(self):
|
||||
self.selection_changed()
|
||||
model = self.attribute_editor.generals.layers.model
|
||||
model.layoutAboutToBeChanged.emit()
|
||||
self.document.sync_shapes_caches()
|
||||
self.document.record_undo()
|
||||
self.document.shapes_changed.emit()
|
||||
model.layoutChanged.emit()
|
||||
|
||||
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_canvas.selection:
|
||||
shape.options['visibility_layer'] = text
|
||||
self.layers_modified()
|
||||
|
||||
def select_layer(self, layer):
|
||||
self.shape_canvas.selection.set(self.document.shapes_by_layer[layer])
|
||||
self.shape_canvas.update_selection()
|
||||
self.shape_canvas.update()
|
||||
self.selection_changed()
|
||||
114
2023/scripts/animation_tools/dwpicker/designer/highlighter.py
Normal file
@@ -0,0 +1,114 @@
|
||||
import keyword
|
||||
from ..pyside import QtGui, QtCore
|
||||
from ..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
|
||||
162
2023/scripts/animation_tools/dwpicker/designer/layer.py
Normal file
@@ -0,0 +1,162 @@
|
||||
from functools import partial
|
||||
from ..pyside import QtWidgets, QtCore, QtGui
|
||||
from ..widgets import V, CheckWidget
|
||||
|
||||
|
||||
class VisibilityLayersEditor(QtWidgets.QWidget):
|
||||
selectLayerContent = QtCore.Signal(str)
|
||||
|
||||
def __init__(self, document, parent=None):
|
||||
super(VisibilityLayersEditor, self).__init__(parent)
|
||||
self.document = document
|
||||
|
||||
self.model = VisbilityLayersModel(document)
|
||||
|
||||
self.table = QtWidgets.QTableView()
|
||||
self.table.setModel(self.model)
|
||||
self.table.setItemDelegateForColumn(0, CheckDelegate())
|
||||
self.table.setEditTriggers(QtWidgets.QAbstractItemView.AllEditTriggers)
|
||||
self.table.verticalHeader().hide()
|
||||
self.table.horizontalHeader().setSectionResizeMode(
|
||||
QtWidgets.QHeaderView.ResizeToContents)
|
||||
self.table.verticalHeader().setSectionResizeMode(
|
||||
QtWidgets.QHeaderView.ResizeToContents)
|
||||
self.table.setSelectionMode(QtWidgets.QAbstractItemView.NoSelection)
|
||||
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
|
||||
|
||||
layer = sorted(list(self.document.shapes_by_layer))[indexes[0].row()]
|
||||
return layer
|
||||
|
||||
def call_remove_layer(self):
|
||||
layer = self.selected_layer()
|
||||
if not layer:
|
||||
return
|
||||
|
||||
for shape in self.document.shapes_by_layer[layer]:
|
||||
if shape.visibility_layer() == layer:
|
||||
shape.options['visibility_layer'] = None
|
||||
self.model.layoutAboutToBeChanged.emit()
|
||||
self.document.sync_shapes_caches()
|
||||
self.document.shapes_changed.emit()
|
||||
self.document.record_undo()
|
||||
self.model.layoutChanged.emit()
|
||||
|
||||
def call_select_layer(self):
|
||||
layer = self.selected_layer()
|
||||
if not layer:
|
||||
return
|
||||
self.selectLayerContent.emit(layer)
|
||||
|
||||
|
||||
class CheckDelegate(QtWidgets.QItemDelegate):
|
||||
|
||||
def mouseReleaseEvent(self, event):
|
||||
if event.button() != QtCore.Qt.LeftButton:
|
||||
return
|
||||
self.update()
|
||||
|
||||
def createEditor(self, parent, _, index):
|
||||
model = index.model()
|
||||
hidden_layers = model.document.data['general']['hidden_layers']
|
||||
layer = model.data(index)
|
||||
state = layer in hidden_layers
|
||||
model.set_hidden_layer(layer, not state)
|
||||
|
||||
checker = CheckWidget(not state, parent)
|
||||
checker.toggled.connect(partial(model.set_hidden_layer, layer))
|
||||
return checker
|
||||
|
||||
def paint(self, painter, option, index):
|
||||
model = index.model()
|
||||
hidden_layers = model.document.data['general']['hidden_layers']
|
||||
state = model.data(index) in hidden_layers
|
||||
|
||||
center = option.rect.center()
|
||||
painter.setBrush(QtCore.Qt.NoBrush)
|
||||
rect = QtCore.QRectF(center.x() - 10, center.y() - 10, 20, 20)
|
||||
if not state:
|
||||
return
|
||||
font = QtGui.QFont()
|
||||
font.setPixelSize(20)
|
||||
option = QtGui.QTextOption()
|
||||
option.setAlignment(QtCore.Qt.AlignCenter)
|
||||
painter.drawText(rect, V, option)
|
||||
|
||||
|
||||
class VisbilityLayersModel(QtCore.QAbstractTableModel):
|
||||
HEADERS = 'hide', 'name', 'shapes'
|
||||
|
||||
def __init__(self, document, parent=None):
|
||||
super(VisbilityLayersModel, self).__init__(parent)
|
||||
self.document = document
|
||||
self.document.changed.connect(self.layoutChanged.emit)
|
||||
|
||||
def rowCount(self, _):
|
||||
return len(self.document.shapes_by_layer)
|
||||
|
||||
def columnCount(self, _):
|
||||
return len(self.HEADERS)
|
||||
|
||||
def headerData(self, section, orientation, role):
|
||||
if orientation == QtCore.Qt.Vertical or role != QtCore.Qt.DisplayRole:
|
||||
return
|
||||
return self.HEADERS[section]
|
||||
|
||||
def flags(self, index):
|
||||
flags = QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable
|
||||
if index.column() == 0:
|
||||
flags |= QtCore.Qt.ItemIsEditable
|
||||
return flags
|
||||
|
||||
def set_hidden_layer(self, layer, state):
|
||||
self.layoutAboutToBeChanged.emit()
|
||||
hidden_layers = self.document.data['general']['hidden_layers']
|
||||
if state and layer not in hidden_layers:
|
||||
hidden_layers.append(layer)
|
||||
elif not state and layer in hidden_layers:
|
||||
hidden_layers.remove(layer)
|
||||
else:
|
||||
self.layoutChanged.emit()
|
||||
return
|
||||
self.document.record_undo()
|
||||
self.document.general_option_changed.emit('editor', 'hidden_layers')
|
||||
self.layoutChanged.emit()
|
||||
|
||||
def data(self, index, role=QtCore.Qt.UserRole):
|
||||
if not index.isValid():
|
||||
return
|
||||
if role == QtCore.Qt.TextAlignmentRole:
|
||||
if index.column() == 2:
|
||||
return QtCore.Qt.AlignCenter
|
||||
|
||||
layers = sorted(list(self.document.shapes_by_layer))
|
||||
|
||||
if role == QtCore.Qt.UserRole:
|
||||
return layers[index.row()]
|
||||
|
||||
if role != QtCore.Qt.DisplayRole:
|
||||
return
|
||||
|
||||
if index.column() == 1:
|
||||
return layers[index.row()]
|
||||
|
||||
if index.column() == 2:
|
||||
layer = layers[index.row()]
|
||||
return str(len(self.document.shapes_by_layer[layer]))
|
||||
289
2023/scripts/animation_tools/dwpicker/designer/menu.py
Normal file
@@ -0,0 +1,289 @@
|
||||
from functools import partial
|
||||
from maya import cmds
|
||||
from ..pyside import QtGui, QtWidgets, QtCore
|
||||
|
||||
from ..optionvar import (
|
||||
BG_LOCKED, DISPLAY_HIERARCHY_IN_CANVAS, ISOLATE_CURRENT_PANEL_SHAPES,
|
||||
SNAP_ITEMS, SNAP_GRID_X, SNAP_GRID_Y, save_optionvar)
|
||||
from ..qtutils import icon
|
||||
|
||||
|
||||
class MenuWidget(QtWidgets.QWidget):
|
||||
addBackgroundRequested = QtCore.Signal()
|
||||
addButtonRequested = QtCore.Signal()
|
||||
addTextRequested = QtCore.Signal()
|
||||
alignRequested = QtCore.Signal(str)
|
||||
arrangeRequested = QtCore.Signal(str)
|
||||
buttonLibraryRequested = QtCore.Signal(QtCore.QPoint)
|
||||
centerValuesChanged = QtCore.Signal(int, int)
|
||||
copyRequested = QtCore.Signal()
|
||||
copySettingsRequested = QtCore.Signal()
|
||||
deleteRequested = QtCore.Signal()
|
||||
editCenterToggled = QtCore.Signal(bool)
|
||||
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()
|
||||
snapValuesChanged = QtCore.Signal()
|
||||
symmetryRequested = QtCore.Signal(bool)
|
||||
undoRequested = QtCore.Signal()
|
||||
useSnapToggled = QtCore.Signal(bool)
|
||||
|
||||
def __init__(self, display_options, parent=None):
|
||||
super(MenuWidget, self).__init__(parent=parent)
|
||||
self.display_options = display_options
|
||||
|
||||
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)
|
||||
|
||||
self.isolate = QtWidgets.QAction(icon('isolate.png'), '', self)
|
||||
self.isolate.setToolTip('Isolate current panel shapes')
|
||||
self.isolate.setCheckable(True)
|
||||
self.isolate.toggled.connect(self.isolate_panel)
|
||||
|
||||
self.hierarchy = QtWidgets.QAction(icon('hierarchy.png'), '', self)
|
||||
self.hierarchy.setToolTip('Display hierarchy')
|
||||
self.hierarchy.setCheckable(True)
|
||||
state = bool(cmds.optionVar(query=DISPLAY_HIERARCHY_IN_CANVAS))
|
||||
self.hierarchy.setChecked(state)
|
||||
self.hierarchy.toggled.connect(self.toggle_hierarchy_display)
|
||||
|
||||
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('addshape.png')
|
||||
self.call_library = QtWidgets.QAction(icon_, '', self)
|
||||
self.call_library.setToolTip('Add button')
|
||||
self.call_library.triggered.connect(self._call_library)
|
||||
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)
|
||||
|
||||
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)
|
||||
self.hsymmetry.setToolTip('Mirror a shape horizontally')
|
||||
method = partial(self.symmetryRequested.emit, True)
|
||||
self.hsymmetry.triggered.connect(method)
|
||||
self.vsymmetry = QtWidgets.QAction(icon('v_symmetry.png'), '', self)
|
||||
self.vsymmetry.setToolTip('Mirror a shape vertically')
|
||||
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)
|
||||
self.align_left.setToolTip('Align to left')
|
||||
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)
|
||||
self.align_h_center.setToolTip('Align to center horizontally')
|
||||
method = partial(self.alignRequested.emit, 'right')
|
||||
self.align_right = QtWidgets.QAction(icon('align_right.png'), '', self)
|
||||
self.align_right.triggered.connect(method)
|
||||
self.align_right.setToolTip('Align to right')
|
||||
method = partial(self.alignRequested.emit, 'top')
|
||||
self.align_top = QtWidgets.QAction(icon('align_top.png'), '', self)
|
||||
self.align_top.triggered.connect(method)
|
||||
self.align_top.setToolTip('Align to top')
|
||||
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)
|
||||
self.align_v_center.setToolTip('Align to center vertically')
|
||||
file_ = 'align_bottom.png'
|
||||
method = partial(self.alignRequested.emit, 'bottom')
|
||||
self.align_bottom = QtWidgets.QAction(icon(file_), '', self)
|
||||
self.align_bottom.triggered.connect(method)
|
||||
self.align_bottom.setToolTip('Align to bottom')
|
||||
|
||||
file_ = 'arrange_h.png'
|
||||
method = partial(self.arrangeRequested.emit, 'horizontal')
|
||||
self.arrange_horizontal = QtWidgets.QAction(icon(file_), '', self)
|
||||
self.arrange_horizontal.triggered.connect(method)
|
||||
self.arrange_horizontal.setToolTip('Distribute horizontally')
|
||||
|
||||
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.arrange_vertical.setToolTip('Distribute vertically')
|
||||
|
||||
self.toolbar = QtWidgets.QToolBar()
|
||||
self.toolbar.setIconSize(QtCore.QSize(24, 24))
|
||||
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.addAction(self.isolate)
|
||||
self.toolbar.addAction(self.hierarchy)
|
||||
self.toolbar.addSeparator()
|
||||
self.toolbar.addAction(self.snap)
|
||||
self.toolbar.addWidget(self.snapx)
|
||||
self.toolbar.addWidget(self.snapy)
|
||||
self.toolbar.addSeparator()
|
||||
self.toolbar.addAction(self.call_library)
|
||||
self.toolbar.addAction(self.addbutton)
|
||||
self.toolbar.addAction(self.addtext)
|
||||
self.toolbar.addAction(self.addbg)
|
||||
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 toggle_hierarchy_display(self, state):
|
||||
save_optionvar(DISPLAY_HIERARCHY_IN_CANVAS, int(state))
|
||||
self.display_options.display_hierarchy = state
|
||||
self.display_options.options_changed.emit()
|
||||
|
||||
def isolate_panel(self, state):
|
||||
self.display_options.isolate = state
|
||||
self.display_options.options_changed.emit()
|
||||
|
||||
def _call_library(self):
|
||||
rect = self.toolbar.actionGeometry(self.call_library)
|
||||
point = self.toolbar.mapToGlobal(rect.bottomLeft())
|
||||
self.buttonLibraryRequested.emit(point)
|
||||
|
||||
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)))
|
||||
value = bool(cmds.optionVar(query=ISOLATE_CURRENT_PANEL_SHAPES))
|
||||
self.isolate.setChecked(value)
|
||||
|
||||
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()))
|
||||
value = int(self.isolate.isChecked())
|
||||
save_optionvar(ISOLATE_CURRENT_PANEL_SHAPES, value)
|
||||
|
||||
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()
|
||||
703
2023/scripts/animation_tools/dwpicker/designer/patheditor.py
Normal file
@@ -0,0 +1,703 @@
|
||||
import os
|
||||
import json
|
||||
from copy import deepcopy
|
||||
from functools import partial
|
||||
|
||||
from ..pyside import QtWidgets, QtCore, QtGui
|
||||
from maya import cmds
|
||||
|
||||
from ..geometry import (
|
||||
distance, get_global_rect, grow_rect, path_symmetry)
|
||||
from ..qtutils import icon
|
||||
from ..interactionmanager import InteractionManager
|
||||
from ..interactive import SelectionSquare, Manipulator
|
||||
from ..optionvar import (
|
||||
LAST_OPEN_DIRECTORY, SHAPE_PATH_ROTATION_STEP_ANGLE, save_optionvar)
|
||||
from ..painting import (
|
||||
draw_selection_square, draw_manipulator, draw_tangents,
|
||||
draw_world_coordinates)
|
||||
from ..path import get_open_directory
|
||||
from ..qtutils import get_cursor
|
||||
from ..selection import get_selection_mode
|
||||
from ..shapepath import (
|
||||
offset_tangent, offset_path, auto_tangent, create_polygon_path,
|
||||
rotate_path)
|
||||
from ..transform import (
|
||||
Transform, resize_path_with_reference, resize_rect_with_direction)
|
||||
from ..viewport import ViewportMapper
|
||||
|
||||
|
||||
class PathEditor(QtWidgets.QWidget):
|
||||
pathEdited = QtCore.Signal()
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(PathEditor, self).__init__(parent)
|
||||
self.setWindowTitle('Shape path editor')
|
||||
self.buffer_path = None
|
||||
self.rotate_center = None
|
||||
|
||||
self.canvas = PathEditorCanvas()
|
||||
self.canvas.pathEdited.connect(self.pathEdited.emit)
|
||||
|
||||
export_path = QtWidgets.QAction(icon('save.png'), 'Export path', self)
|
||||
export_path.triggered.connect(self.export_path)
|
||||
|
||||
import_path = QtWidgets.QAction(icon('open.png'), 'Import path', self)
|
||||
import_path.triggered.connect(self.import_path)
|
||||
|
||||
delete = QtWidgets.QAction(icon('delete.png'), 'Delete vertex', self)
|
||||
delete.triggered.connect(self.canvas.delete)
|
||||
|
||||
smooth_tangent = QtWidgets.QAction(
|
||||
icon('tangent.png'), 'Smooth tangents', self)
|
||||
smooth_tangent.triggered.connect(self.canvas.smooth_tangents)
|
||||
|
||||
break_tangent = QtWidgets.QAction(
|
||||
icon('tangentbreak.png'), 'Break tangents', self)
|
||||
break_tangent.triggered.connect(self.canvas.break_tangents)
|
||||
|
||||
hsymmetry = QtWidgets.QAction(
|
||||
icon('h_symmetry.png'), 'Mirror horizontally', self)
|
||||
hsymmetry.triggered.connect(partial(self.canvas.symmetry, True))
|
||||
|
||||
vsymmetry = QtWidgets.QAction(
|
||||
icon('v_symmetry.png'), 'Mirror vertically', self)
|
||||
vsymmetry.triggered.connect(partial(self.canvas.symmetry, False))
|
||||
|
||||
center_path = QtWidgets.QAction(
|
||||
icon('frame.png'), 'Center path', self)
|
||||
center_path.triggered.connect(partial(self.canvas.center_path))
|
||||
center_path_button = QtWidgets.QToolButton()
|
||||
center_path_button.setDefaultAction(center_path)
|
||||
|
||||
self.angle = QtWidgets.QSlider(QtCore.Qt.Horizontal)
|
||||
self.angle.setMinimum(0)
|
||||
self.angle.setMaximum(360)
|
||||
self.angle.sliderPressed.connect(self.start_rotate)
|
||||
self.angle.sliderReleased.connect(self.end_rotate)
|
||||
self.angle.valueChanged.connect(self.rotate)
|
||||
|
||||
self.angle_step = QtWidgets.QSpinBox()
|
||||
self.angle_step.setToolTip('Step')
|
||||
self.angle_step.setMinimum(0)
|
||||
self.angle_step.setMaximum(90)
|
||||
value = cmds.optionVar(query=SHAPE_PATH_ROTATION_STEP_ANGLE)
|
||||
self.angle_step.setValue(value)
|
||||
function = partial(save_optionvar, SHAPE_PATH_ROTATION_STEP_ANGLE)
|
||||
self.angle_step.valueChanged.connect(function)
|
||||
|
||||
polygon = QtWidgets.QAction(
|
||||
icon('polygon.png'), 'Create Polygon', self)
|
||||
polygon.triggered.connect(self.create_polygon)
|
||||
|
||||
toggle = QtWidgets.QAction(icon('dock.png'), 'Dock/Undock', self)
|
||||
toggle.triggered.connect(self.toggle_flag)
|
||||
|
||||
self.toolbar = QtWidgets.QToolBar()
|
||||
self.toolbar.setIconSize(QtCore.QSize(18, 18))
|
||||
self.toolbar.addAction(import_path)
|
||||
self.toolbar.addAction(export_path)
|
||||
self.toolbar.addSeparator()
|
||||
self.toolbar.addAction(polygon)
|
||||
self.toolbar.addAction(delete)
|
||||
self.toolbar.addSeparator()
|
||||
self.toolbar.addAction(smooth_tangent)
|
||||
self.toolbar.addAction(break_tangent)
|
||||
self.toolbar.addSeparator()
|
||||
self.toolbar.addAction(hsymmetry)
|
||||
self.toolbar.addAction(vsymmetry)
|
||||
|
||||
toolbar3 = QtWidgets.QHBoxLayout()
|
||||
toolbar3.setContentsMargins(0, 0, 0, 0)
|
||||
toolbar3.addWidget(center_path_button)
|
||||
toolbar3.addStretch()
|
||||
toolbar3.addWidget(QtWidgets.QLabel('Rotate: '))
|
||||
toolbar3.addWidget(self.angle)
|
||||
toolbar3.addWidget(self.angle_step)
|
||||
|
||||
self.toolbar2 = QtWidgets.QToolBar()
|
||||
self.toolbar2.setIconSize(QtCore.QSize(18, 18))
|
||||
self.toolbar2.addAction(toggle)
|
||||
|
||||
toolbars = QtWidgets.QHBoxLayout()
|
||||
toolbars.setContentsMargins(0, 0, 0, 0)
|
||||
toolbars.addWidget(self.toolbar)
|
||||
toolbars.addStretch()
|
||||
toolbars.addWidget(self.toolbar2)
|
||||
|
||||
layout = QtWidgets.QVBoxLayout(self)
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
layout.setSpacing(0)
|
||||
layout.addLayout(toolbars)
|
||||
layout.addWidget(self.canvas)
|
||||
layout.addLayout(toolbar3)
|
||||
|
||||
def export_path(self):
|
||||
directory = get_open_directory()
|
||||
filename = QtWidgets.QFileDialog.getSaveFileName(
|
||||
self, 'Export shape', directory, '*.dws')
|
||||
if not filename[0]:
|
||||
return
|
||||
save_optionvar(LAST_OPEN_DIRECTORY, os.path.dirname(filename[0]))
|
||||
with open(filename[0], 'w') as f:
|
||||
json.dump(self.canvas.path, f, indent=2)
|
||||
|
||||
def import_path(self):
|
||||
directory = get_open_directory()
|
||||
filename = QtWidgets.QFileDialog.getOpenFileName(
|
||||
self, 'Import shape', directory, '*.dws')
|
||||
if not filename[0]:
|
||||
return
|
||||
save_optionvar(LAST_OPEN_DIRECTORY, os.path.dirname(filename[0]))
|
||||
with open(filename[0], 'r') as f:
|
||||
path = json.load(f)
|
||||
self.canvas.set_path(path)
|
||||
self.pathEdited.emit()
|
||||
|
||||
def start_rotate(self):
|
||||
self.buffer_path = deepcopy(self.canvas.path)
|
||||
if not self.canvas.selection:
|
||||
self.rotate_center = (0, 0)
|
||||
elif len(self.canvas.selection) == 1:
|
||||
index = self.canvas.selection[0]
|
||||
self.rotate_center = self.buffer_path[index]['point']
|
||||
else:
|
||||
point = self.canvas.manipulator.rect.center()
|
||||
self.rotate_center = point.toTuple()
|
||||
|
||||
def end_rotate(self):
|
||||
self.buffer_path = None
|
||||
self.rotate_center = None
|
||||
self.pathEdited.emit()
|
||||
self.angle.blockSignals(True)
|
||||
self.angle.setValue(0)
|
||||
self.angle.blockSignals(False)
|
||||
|
||||
def rotate(self, value):
|
||||
if self.buffer_path is None:
|
||||
self.start_rotate()
|
||||
|
||||
step_size = self.angle_step.value()
|
||||
value = round(value / step_size) * step_size
|
||||
if 1 < len(self.canvas.selection):
|
||||
path = deepcopy(self.buffer_path)
|
||||
points = [self.buffer_path[i] for i in self.canvas.selection]
|
||||
rotated_path = rotate_path(points, value, self.rotate_center)
|
||||
for i, point in zip(self.canvas.selection, rotated_path):
|
||||
path[i] = point
|
||||
else:
|
||||
path = rotate_path(self.buffer_path, value, self.rotate_center)
|
||||
if path is None:
|
||||
return
|
||||
|
||||
self.canvas.path = path
|
||||
if self.canvas.selection:
|
||||
points = [
|
||||
QtCore.QPointF(*path[i]['point'])
|
||||
for i in self.canvas.selection]
|
||||
self.canvas.update_manipulator_rect(points)
|
||||
self.canvas.update()
|
||||
|
||||
def create_polygon(self):
|
||||
edges, result = QtWidgets.QInputDialog.getInt(
|
||||
self, 'Polygon', 'Number of edges', value=3, minValue=3,
|
||||
maxValue=25)
|
||||
if not result:
|
||||
return
|
||||
|
||||
path = create_polygon_path(radius=45, n=edges)
|
||||
self.canvas.set_path(path)
|
||||
self.pathEdited.emit()
|
||||
|
||||
def toggle_flag(self):
|
||||
point = self.mapToGlobal(self.rect().topLeft())
|
||||
state = not self.windowFlags() & QtCore.Qt.Tool
|
||||
self.setWindowFlag(QtCore.Qt.Tool, state)
|
||||
self.show()
|
||||
if state:
|
||||
self.resize(400, 400)
|
||||
self.move(point)
|
||||
self.canvas.focus()
|
||||
|
||||
def set_options(self, options):
|
||||
if options is None:
|
||||
self.canvas.set_path(None)
|
||||
return
|
||||
self.canvas.set_path(options['shape.path'] or [])
|
||||
|
||||
def path(self):
|
||||
return self.canvas.path
|
||||
|
||||
def path_rect(self):
|
||||
return get_global_rect(
|
||||
[QtCore.QPointF(*p['point']) for p in self.canvas.path])
|
||||
|
||||
|
||||
class PathEditorCanvas(QtWidgets.QWidget):
|
||||
pathEdited = QtCore.Signal()
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(PathEditorCanvas, self).__init__(parent)
|
||||
self.viewportmapper = ViewportMapper()
|
||||
self.viewportmapper.viewsize = self.size()
|
||||
self.selection_square = SelectionSquare()
|
||||
self.manipulator = Manipulator(self.viewportmapper)
|
||||
self.transform = Transform()
|
||||
self.selection = PointSelection()
|
||||
self.interaction_manager = InteractionManager()
|
||||
self.setMouseTracking(True)
|
||||
self.path = []
|
||||
|
||||
def sizeHint(self):
|
||||
return QtCore.QSize(300, 200)
|
||||
|
||||
def mousePressEvent(self, event):
|
||||
if not self.path:
|
||||
return
|
||||
|
||||
cursor = self.viewportmapper.to_units_coords(get_cursor(self))
|
||||
self.transform.direction = self.manipulator.get_direction(event.pos())
|
||||
self.current_action = self.get_action()
|
||||
if self.current_action and self.current_action[0] == 'move point':
|
||||
self.selection.set([self.current_action[1]])
|
||||
self.update_manipulator_rect()
|
||||
if self.manipulator.rect is not None:
|
||||
self.transform.set_rect(self.manipulator.rect)
|
||||
self.transform.reference_rect = QtCore.QRectF(self.manipulator.rect)
|
||||
self.transform.set_reference_point(cursor)
|
||||
|
||||
has_shape_hovered = bool(self.current_action)
|
||||
self.interaction_manager.update(
|
||||
event, pressed=True,
|
||||
has_shape_hovered=has_shape_hovered,
|
||||
dragging=has_shape_hovered)
|
||||
self.selection_square.clicked(cursor)
|
||||
self.update()
|
||||
|
||||
def get_action(self):
|
||||
if not self.path:
|
||||
return
|
||||
|
||||
cursor = self.viewportmapper.to_units_coords(get_cursor(self))
|
||||
if self.manipulator.rect and self.manipulator.rect.contains(cursor):
|
||||
return 'move points', None
|
||||
direction = self.manipulator.get_direction(get_cursor(self))
|
||||
if direction:
|
||||
return 'resize points', direction
|
||||
tolerance = self.viewportmapper.to_units(5)
|
||||
for i, data in enumerate(self.path):
|
||||
point = QtCore.QPointF(*data['point'])
|
||||
if distance(point, cursor) < tolerance:
|
||||
return 'move point', i
|
||||
if data['tangent_in']:
|
||||
point = QtCore.QPointF(*data['tangent_in'])
|
||||
if distance(point, cursor) < tolerance:
|
||||
return 'move in', i
|
||||
if data['tangent_out']:
|
||||
point = QtCore.QPointF(*data['tangent_out'])
|
||||
if distance(point, cursor) < tolerance:
|
||||
return 'move out', i
|
||||
index = is_point_on_path_edge(self.path, cursor, tolerance)
|
||||
if index is not None:
|
||||
return 'create point', index
|
||||
|
||||
def mouseMoveEvent(self, event):
|
||||
if not self.path:
|
||||
return
|
||||
|
||||
cursor = self.viewportmapper.to_units_coords(get_cursor(self)).toPoint()
|
||||
if self.interaction_manager.mode == InteractionManager.NAVIGATION:
|
||||
offset = self.interaction_manager.mouse_offset(event.pos())
|
||||
if offset is not None:
|
||||
origin = self.viewportmapper.origin - offset
|
||||
self.viewportmapper.origin = origin
|
||||
|
||||
elif self.interaction_manager.mode == InteractionManager.SELECTION:
|
||||
self.selection_square.handle(cursor)
|
||||
|
||||
elif self.interaction_manager.mode == InteractionManager.DRAGGING:
|
||||
if not self.current_action:
|
||||
return self.update()
|
||||
|
||||
offset = self.interaction_manager.mouse_offset(event.pos())
|
||||
if not offset:
|
||||
return self.update()
|
||||
|
||||
offset = QtCore.QPointF(
|
||||
self.viewportmapper.to_units(offset.x()),
|
||||
self.viewportmapper.to_units(offset.y()))
|
||||
|
||||
if self.current_action[0] == 'move points':
|
||||
offset_path(self.path, offset, self.selection)
|
||||
self.update_manipulator_rect()
|
||||
|
||||
elif self.current_action[0] == 'resize points':
|
||||
resize_rect_with_direction(
|
||||
self.transform.rect, cursor,
|
||||
self.transform.direction)
|
||||
path = (
|
||||
[self.path[i] for i in self.selection] if
|
||||
self.selection else self.path)
|
||||
resize_path_with_reference(
|
||||
path,
|
||||
self.transform.reference_rect,
|
||||
self.transform.rect)
|
||||
rect = self.transform.rect
|
||||
self.transform.reference_rect.setTopLeft(rect.topLeft())
|
||||
self.transform.reference_rect.setSize(rect.size())
|
||||
self.manipulator.set_rect(self.transform.rect)
|
||||
|
||||
elif self.current_action[0] == 'move point':
|
||||
offset_path(self.path, offset, [self.current_action[1]])
|
||||
|
||||
elif self.current_action and self.current_action[0] == 'move in':
|
||||
move_tangent(
|
||||
point=self.path[self.current_action[1]],
|
||||
tangent_in_moved=True,
|
||||
offset=offset,
|
||||
lock=not self.interaction_manager.ctrl_pressed)
|
||||
|
||||
elif self.current_action[0] == 'move out':
|
||||
move_tangent(
|
||||
point=self.path[self.current_action[1]],
|
||||
tangent_in_moved=False,
|
||||
offset=offset,
|
||||
lock=not self.interaction_manager.ctrl_pressed)
|
||||
|
||||
elif self.current_action[0] == 'create point':
|
||||
self.interaction_manager.mouse_offset(event.pos())
|
||||
point = {
|
||||
'point': [cursor.x(), cursor.y()],
|
||||
'tangent_in': None,
|
||||
'tangent_out': None}
|
||||
index = self.current_action[1] + 1
|
||||
self.path.insert(index, point)
|
||||
self.autotangent(index)
|
||||
self.current_action = 'move point', index
|
||||
self.selection.set([index])
|
||||
self.update_manipulator_rect()
|
||||
|
||||
self.update()
|
||||
|
||||
def move_point(self, i, offset):
|
||||
self.path[i]['point'][0] += offset.x()
|
||||
self.path[i]['point'][1] += offset.y()
|
||||
point = self.path[i]['tangent_in']
|
||||
if point:
|
||||
point[0] += offset.x()
|
||||
point[1] += offset.y()
|
||||
point = self.path[i]['tangent_out']
|
||||
if point:
|
||||
point[0] += offset.x()
|
||||
point[1] += offset.y()
|
||||
|
||||
def mouseReleaseEvent(self, event):
|
||||
if not self.path:
|
||||
return
|
||||
|
||||
if self.current_action:
|
||||
self.pathEdited.emit()
|
||||
|
||||
if self.interaction_manager.mode == InteractionManager.SELECTION:
|
||||
self.select()
|
||||
|
||||
self.selection_square.release()
|
||||
self.interaction_manager.update(
|
||||
event, pressed=False, has_shape_hovered=False, dragging=False)
|
||||
self.update()
|
||||
|
||||
def select(self):
|
||||
shift = self.interaction_manager.shift_pressed
|
||||
ctrl = self.interaction_manager.ctrl_pressed
|
||||
self.selection.mode = get_selection_mode(shift=shift, ctrl=ctrl)
|
||||
rect = self.selection_square.rect
|
||||
points = []
|
||||
indexes = []
|
||||
for i, p in enumerate(self.path):
|
||||
point = QtCore.QPointF(*p['point'])
|
||||
if rect.contains(point):
|
||||
indexes.append(i)
|
||||
points.append(point)
|
||||
self.selection.set(indexes)
|
||||
self.update_manipulator_rect()
|
||||
|
||||
def update_manipulator_rect(self, points=None):
|
||||
if points is None:
|
||||
points = [
|
||||
QtCore.QPointF(*self.path[i]['point'])
|
||||
for i in self.selection]
|
||||
if len(points) < 2:
|
||||
self.manipulator.set_rect(None)
|
||||
return
|
||||
|
||||
rect = get_global_rect(points)
|
||||
rect.setHeight(max(rect.height(), .5))
|
||||
rect.setWidth(max(rect.width(), .5))
|
||||
self.manipulator.set_rect(rect)
|
||||
|
||||
def wheelEvent(self, event):
|
||||
# To center the zoom on the mouse, we save a reference mouse position
|
||||
# and compare the offset after zoom computation.
|
||||
factor = .25 if event.angleDelta().y() > 0 else -.25
|
||||
self.zoom(factor, event.pos())
|
||||
self.update()
|
||||
|
||||
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.update()
|
||||
|
||||
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 paintEvent(self, event):
|
||||
if not self.path:
|
||||
return
|
||||
|
||||
try:
|
||||
painter = QtGui.QPainter(self)
|
||||
painter.setPen(QtGui.QPen())
|
||||
color = QtGui.QColor('black')
|
||||
color.setAlpha(50)
|
||||
painter.setBrush(color)
|
||||
rect = QtCore.QRect(
|
||||
0, 0, self.rect().width() - 1, self.rect().height() - 1)
|
||||
painter.drawRect(rect)
|
||||
draw_world_coordinates(
|
||||
painter, self.rect(), QtGui.QColor('#282828'),
|
||||
self.viewportmapper)
|
||||
painter.setBrush(QtGui.QBrush())
|
||||
draw_shape_path(
|
||||
painter, self.path, self.selection, self.viewportmapper)
|
||||
draw_tangents(painter, self.path, self.viewportmapper)
|
||||
if self.selection_square.rect:
|
||||
draw_selection_square(
|
||||
painter, self.selection_square.rect, self.viewportmapper)
|
||||
|
||||
conditions = (
|
||||
self.manipulator.rect is not None and
|
||||
all(self.manipulator.viewport_handlers()))
|
||||
|
||||
if conditions:
|
||||
draw_manipulator(
|
||||
painter, self.manipulator,
|
||||
get_cursor(self), self.viewportmapper)
|
||||
|
||||
finally:
|
||||
painter.end()
|
||||
|
||||
def center_path(self):
|
||||
qpath = path_to_qpath(self.path, ViewportMapper())
|
||||
center = qpath.boundingRect().center()
|
||||
offset_path(self.path, -center)
|
||||
self.pathEdited.emit()
|
||||
self.update_manipulator_rect()
|
||||
self.update()
|
||||
|
||||
def delete(self):
|
||||
if len(self.path) - len(self.selection) < 3:
|
||||
return QtWidgets.QMessageBox.critical(
|
||||
self, 'Error', 'Shape must at least contains 3 control points')
|
||||
|
||||
for i in sorted(self.selection, reverse=True):
|
||||
del self.path[i]
|
||||
self.selection.clear()
|
||||
self.update_manipulator_rect()
|
||||
self.pathEdited.emit()
|
||||
self.update()
|
||||
|
||||
def break_tangents(self):
|
||||
for i in self.selection:
|
||||
self.path[i]['tangent_in'] = None
|
||||
self.path[i]['tangent_out'] = None
|
||||
self.pathEdited.emit()
|
||||
self.update()
|
||||
|
||||
def smooth_tangents(self):
|
||||
if not self.selection:
|
||||
return
|
||||
|
||||
for i in self.selection:
|
||||
self.autotangent(i)
|
||||
self.update()
|
||||
self.pathEdited.emit()
|
||||
|
||||
def autotangent(self, i):
|
||||
point = self.path[i]['point']
|
||||
next_index = i + 1 if i < (len(self.path) - 1) else 0
|
||||
next_point = self.path[next_index]['point']
|
||||
previous_point = self.path[i - 1]['point']
|
||||
tan_in, tan_out = auto_tangent(point, previous_point, next_point)
|
||||
self.path[i]['tangent_in'] = tan_in
|
||||
self.path[i]['tangent_out'] = tan_out
|
||||
|
||||
def set_path(self, path):
|
||||
self.path = path
|
||||
self.selection.clear()
|
||||
self.manipulator.set_rect(None)
|
||||
self.focus()
|
||||
|
||||
def focus(self):
|
||||
if not self.path:
|
||||
self.update()
|
||||
return
|
||||
points = [QtCore.QPointF(*p['point']) for p in self.path]
|
||||
rect = get_global_rect(points)
|
||||
self.viewportmapper.focus(grow_rect(rect, 15))
|
||||
self.update()
|
||||
|
||||
def symmetry(self, horizontal=True):
|
||||
path = (
|
||||
[self.path[i] for i in self.selection] if
|
||||
self.selection else self.path)
|
||||
|
||||
if self.manipulator.rect:
|
||||
center = self.manipulator.rect.center()
|
||||
else:
|
||||
points = [QtCore.QPointF(*p['point']) for p in self.path]
|
||||
rect = get_global_rect(points)
|
||||
center = rect.center()
|
||||
|
||||
path_symmetry(path, center, horizontal=horizontal)
|
||||
self.pathEdited.emit()
|
||||
self.update()
|
||||
|
||||
|
||||
class PointSelection():
|
||||
def __init__(self):
|
||||
self.elements = []
|
||||
self.mode = 'replace'
|
||||
|
||||
def set(self, elements):
|
||||
if self.mode == 'add':
|
||||
if elements is None:
|
||||
return
|
||||
return self.add(elements)
|
||||
elif self.mode == 'replace':
|
||||
if elements is None:
|
||||
return self.clear()
|
||||
return self.replace(elements)
|
||||
elif self.mode == 'invert':
|
||||
if elements is None:
|
||||
return
|
||||
return self.invert(elements)
|
||||
elif self.mode == 'remove':
|
||||
if elements is None:
|
||||
return
|
||||
for element in elements:
|
||||
if element in self.elements:
|
||||
self.remove(element)
|
||||
|
||||
def replace(self, elements):
|
||||
self.elements = elements
|
||||
|
||||
def add(self, elements):
|
||||
self.elements.extend([e for e in elements if e not in self])
|
||||
|
||||
def remove(self, element):
|
||||
self.elements.remove(element)
|
||||
|
||||
def invert(self, elements):
|
||||
for element in elements:
|
||||
if element not in self.elements:
|
||||
self.add([element])
|
||||
else:
|
||||
self.remove(element)
|
||||
|
||||
def clear(self):
|
||||
self.elements = []
|
||||
|
||||
def __len__(self):
|
||||
return len(self.elements)
|
||||
|
||||
def __bool__(self):
|
||||
return bool(self.elements)
|
||||
|
||||
__nonzero__ = __bool__
|
||||
|
||||
def __getitem__(self, i):
|
||||
return self.elements[i]
|
||||
|
||||
def __iter__(self):
|
||||
return self.elements.__iter__()
|
||||
|
||||
|
||||
def path_to_qpath(path, viewportmapper):
|
||||
painter_path = QtGui.QPainterPath()
|
||||
start = QtCore.QPointF(*path[0]['point'])
|
||||
painter_path.moveTo(viewportmapper.to_viewport_coords(start))
|
||||
for i in range(len(path)):
|
||||
point = path[i]
|
||||
point2 = path[i + 1 if i + 1 < len(path) else 0]
|
||||
c1 = QtCore.QPointF(*(point['tangent_out'] or point['point']))
|
||||
c2 = QtCore.QPointF(*(point2['tangent_in'] or point2['point']))
|
||||
end = QtCore.QPointF(*point2['point'])
|
||||
painter_path.cubicTo(
|
||||
viewportmapper.to_viewport_coords(c1),
|
||||
viewportmapper.to_viewport_coords(c2),
|
||||
viewportmapper.to_viewport_coords(end))
|
||||
return painter_path
|
||||
|
||||
|
||||
def draw_shape_path(painter, path, selection, viewportmapper):
|
||||
painter.setPen(QtCore.Qt.gray)
|
||||
painter.drawPath(path_to_qpath(path, viewportmapper))
|
||||
rect = QtCore.QRectF(0, 0, 5, 5)
|
||||
for i, point in enumerate(path):
|
||||
center = QtCore.QPointF(*point['point'])
|
||||
rect.moveCenter(viewportmapper.to_viewport_coords(center))
|
||||
painter.setBrush(QtCore.Qt.white if i in selection else QtCore.Qt.NoBrush)
|
||||
painter.drawRect(rect)
|
||||
|
||||
|
||||
def is_point_on_path_edge(path, cursor, tolerance=3):
|
||||
stroker = QtGui.QPainterPathStroker()
|
||||
stroker.setWidth(tolerance * 2)
|
||||
|
||||
for i in range(len(path)):
|
||||
point = path[i]
|
||||
painter_path = QtGui.QPainterPath()
|
||||
painter_path.moveTo(QtCore.QPointF(*point['point']))
|
||||
|
||||
point2 = path[i + 1 if i + 1 < len(path) else 0]
|
||||
c1 = QtCore.QPointF(*(point['tangent_out'] or point['point']))
|
||||
c2 = QtCore.QPointF(*(point2['tangent_in'] or point2['point']))
|
||||
end = QtCore.QPointF(*point2['point'])
|
||||
painter_path.cubicTo(c1, c2, end)
|
||||
|
||||
stroke = stroker.createStroke(painter_path)
|
||||
if stroke.contains(cursor):
|
||||
return i
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def move_tangent(point, tangent_in_moved, offset, lock):
|
||||
center_point = point['point']
|
||||
tangent_in = point['tangent_in' if tangent_in_moved else 'tangent_out']
|
||||
tangent_out = point['tangent_out' if tangent_in_moved else 'tangent_in']
|
||||
offset = offset.x(), offset.y()
|
||||
tangent_in, tangent_out = offset_tangent(
|
||||
tangent_in, tangent_out, center_point, offset, lock)
|
||||
point['tangent_in'if tangent_in_moved else 'tangent_out'] = tangent_in
|
||||
point['tangent_out'if tangent_in_moved else 'tangent_in'] = tangent_out
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
try:
|
||||
se.close()
|
||||
except:
|
||||
pass
|
||||
from ..qtutils import maya_main_window
|
||||
se = PathEditor(maya_main_window())
|
||||
se.setWindowFlags(QtCore.Qt.Window)
|
||||
se.show()
|
||||
374
2023/scripts/animation_tools/dwpicker/designer/stackeditor.py
Normal file
@@ -0,0 +1,374 @@
|
||||
from decimal import Decimal, getcontext
|
||||
from ..pyside import QtWidgets, QtGui, QtCore
|
||||
|
||||
|
||||
HANDLER_WIDTH = 16
|
||||
HANDLER_HEIGHT = 16
|
||||
|
||||
|
||||
class StackEditor(QtWidgets.QWidget):
|
||||
panelsChanged = QtCore.Signal(object)
|
||||
panelSelected = QtCore.Signal(int)
|
||||
panelDoubleClicked = QtCore.Signal(int)
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(StackEditor, self).__init__(parent)
|
||||
self.data = [[1., [1.]]]
|
||||
self.orientation = 'vertical'
|
||||
self.stack_rects = get_stack_rects(
|
||||
self.data, self.rect(), self.orientation)
|
||||
self.setMouseTracking(True)
|
||||
self.clicked_action = None
|
||||
self.selected_index = None
|
||||
self.panels_are_changed = False
|
||||
self.panel_is_selected = None
|
||||
|
||||
def set_orientation(self, orientation):
|
||||
self.orientation = orientation
|
||||
self.stack_rects = get_stack_rects(
|
||||
self.data, self.rect(), self.orientation)
|
||||
self.update()
|
||||
|
||||
def set_data(self, data):
|
||||
self.data = data
|
||||
self.update()
|
||||
|
||||
def sizeHint(self):
|
||||
return QtCore.QSize(300, 210)
|
||||
|
||||
def resizeEvent(self, event):
|
||||
self.stack_rects = get_stack_rects(
|
||||
self.data, self.rect(), self.orientation)
|
||||
|
||||
def mousePressEvent(self, event):
|
||||
if event.button() != QtCore.Qt.LeftButton:
|
||||
return
|
||||
self.clicked_action = self.get_action(event.pos())
|
||||
|
||||
def mouseDoubleClickEvent(self, event):
|
||||
if event.button() != QtCore.Qt.LeftButton:
|
||||
return
|
||||
clicked_action = self.get_action(event.pos())
|
||||
if clicked_action[0] == 'select':
|
||||
panel = self.panel_number(clicked_action[1])
|
||||
self.panelSelected.emit(panel)
|
||||
self.panelDoubleClicked.emit(panel)
|
||||
self.selected_index = clicked_action[1]
|
||||
self.update()
|
||||
|
||||
def panel_number(self, index):
|
||||
k = 1
|
||||
for i, (_, rows) in enumerate(self.data):
|
||||
for j in range(len(rows)):
|
||||
if [i, j] == index:
|
||||
return k
|
||||
k += 1
|
||||
|
||||
def mouseReleaseEvent(self, event):
|
||||
if event.button() != QtCore.Qt.LeftButton or not self.clicked_action:
|
||||
return
|
||||
index = self.clicked_action[1]
|
||||
if self.clicked_action[0] == 'delete':
|
||||
delete_panel(self.data, index)
|
||||
self.stack_rects = get_stack_rects(
|
||||
self.data, self.rect(), self.orientation)
|
||||
self.panelsChanged.emit(self.data)
|
||||
elif self.clicked_action[0] == 'select':
|
||||
index = self.clicked_action[1]
|
||||
if index == self.selected_index:
|
||||
self.selected_index = None
|
||||
self.panelSelected.emit(-1)
|
||||
else:
|
||||
self.selected_index = index
|
||||
self.panelSelected.emit(self.panel_number(self.selected_index))
|
||||
else:
|
||||
self.check_buffer_states()
|
||||
self.update()
|
||||
self.clicked_action = None
|
||||
|
||||
def check_buffer_states(self):
|
||||
if self.panel_is_selected is not None:
|
||||
self.panelSelected.emit(self.panel_is_selected)
|
||||
if self.panels_are_changed:
|
||||
self.panelsChanged.emit(self.data)
|
||||
self.panel_is_selected = None
|
||||
self.panels_are_changed = False
|
||||
|
||||
def get_action(self, cursor):
|
||||
for i, column in enumerate(self.stack_rects):
|
||||
for j, rect in enumerate(column):
|
||||
if not rect.contains(cursor):
|
||||
continue
|
||||
|
||||
if get_close_handler_rect(rect).contains(cursor):
|
||||
if i + j:
|
||||
return 'delete', [i, j]
|
||||
|
||||
hrect = get_horizontal_handler_rect(rect)
|
||||
vrect = get_vertical_handler_rect(rect)
|
||||
if self.orientation == 'horizontal':
|
||||
hrect, vrect = vrect, hrect
|
||||
|
||||
if hrect.contains(cursor):
|
||||
if j == len(column) - 1:
|
||||
return 'create vertical', [i, j]
|
||||
return 'move vertical', [i, j]
|
||||
|
||||
if vrect.contains(cursor):
|
||||
if i == len(self.data) - 1:
|
||||
return 'create horizontal', [i, j]
|
||||
return 'move horizontal', [i, j]
|
||||
|
||||
return 'select', [i, j]
|
||||
|
||||
def mouseMoveEvent(self, event):
|
||||
if not self.clicked_action:
|
||||
return
|
||||
vertical = self.orientation == 'vertical'
|
||||
|
||||
if self.clicked_action[0] == 'create vertical':
|
||||
index = self.clicked_action[1]
|
||||
col = self.data[index[0]][1]
|
||||
col[-1] -= .1
|
||||
col.append(.1)
|
||||
self.clicked_action = 'move vertical', index
|
||||
self.selected_index = [index[0], index[1] + 1]
|
||||
self.panel_is_selected = self.panel_number(self.selected_index)
|
||||
self.panels_are_changed = True
|
||||
|
||||
elif self.clicked_action[0] == 'create horizontal':
|
||||
index = self.clicked_action[1]
|
||||
self.data[-1][0] -= .1
|
||||
self.data.append([.1, [1.]])
|
||||
self.clicked_action = 'move horizontal', index
|
||||
self.selected_index = [index[0] + 1, 0]
|
||||
self.panel_is_selected = self.panel_number(self.selected_index)
|
||||
self.panels_are_changed = True
|
||||
|
||||
elif self.clicked_action[0] == 'move vertical' and vertical:
|
||||
index = self.clicked_action[1]
|
||||
y = event.pos().y() / self.height()
|
||||
move_vertical(self.data, index, y)
|
||||
self.panels_are_changed = True
|
||||
|
||||
elif self.clicked_action[0] == 'move vertical':
|
||||
index = self.clicked_action[1]
|
||||
x = event.pos().x() / self.width()
|
||||
move_vertical(self.data, index, x)
|
||||
self.panels_are_changed = True
|
||||
|
||||
elif self.clicked_action[0] == 'move horizontal' and vertical:
|
||||
index = self.clicked_action[1]
|
||||
x = event.pos().x() / self.width()
|
||||
move_horizontal(self.data, index, x)
|
||||
self.panels_are_changed = True
|
||||
|
||||
elif self.clicked_action[0] == 'move horizontal':
|
||||
index = self.clicked_action[1]
|
||||
y = event.pos().y() / self.height()
|
||||
move_horizontal(self.data, index, y)
|
||||
self.panels_are_changed = True
|
||||
|
||||
self.stack_rects = get_stack_rects(
|
||||
self.data, self.rect(), self.orientation)
|
||||
self.update()
|
||||
|
||||
def paintEvent(self, _):
|
||||
painter = QtGui.QPainter(self)
|
||||
k = 1
|
||||
original_pen = painter.pen()
|
||||
original_brush = painter.brush()
|
||||
for i, column in enumerate(self.stack_rects):
|
||||
for j, rect in enumerate(column):
|
||||
if [i, j] == self.selected_index:
|
||||
pen = QtGui.QPen(QtGui.QColor('yellow'))
|
||||
pen.setWidth(5)
|
||||
painter.setPen(pen)
|
||||
brush = QtGui.QBrush(original_brush)
|
||||
color = brush.color()
|
||||
color.setAlpha(50)
|
||||
brush.setColor(color)
|
||||
brush.setStyle(QtCore.Qt.FDiagPattern)
|
||||
painter.setBrush(brush)
|
||||
else:
|
||||
pen = original_pen
|
||||
painter.setPen(pen)
|
||||
painter.setBrush(original_brush)
|
||||
|
||||
painter.drawRect(rect)
|
||||
|
||||
painter.setPen(QtCore.Qt.NoPen)
|
||||
painter.setBrush(pen.color())
|
||||
handler_rect = get_horizontal_handler_rect(rect)
|
||||
painter.drawPath(up_arrow(handler_rect))
|
||||
handler_rect = get_vertical_handler_rect(rect)
|
||||
painter.drawPath(left_arrow(handler_rect))
|
||||
|
||||
painter.setPen(original_pen)
|
||||
painter.setBrush(original_brush)
|
||||
font = QtGui.QFont()
|
||||
font.setPointSize(15)
|
||||
font.setBold(True)
|
||||
painter.setFont(font)
|
||||
painter.drawText(rect, QtCore.Qt.AlignCenter, str(k))
|
||||
k += 1
|
||||
if i + j == 0:
|
||||
continue
|
||||
font = QtGui.QFont()
|
||||
font.setBold(True)
|
||||
painter.setFont(font)
|
||||
painter.drawText(
|
||||
get_close_handler_rect(rect), QtCore.Qt.AlignCenter, 'X')
|
||||
painter.end()
|
||||
|
||||
|
||||
def get_stack_rects(data, rect, orientation):
|
||||
if orientation == 'vertical':
|
||||
return get_vertical_stack_rects(data, rect)
|
||||
return get_horizontal_stack_rects(data, rect)
|
||||
|
||||
|
||||
def get_horizontal_stack_rects(data, rect):
|
||||
result = []
|
||||
y = 0
|
||||
for height, rows in data:
|
||||
column = []
|
||||
x = 0
|
||||
for width in rows:
|
||||
panel_rect = QtCore.QRectF(
|
||||
x * rect.width(),
|
||||
y * rect.height(),
|
||||
(width * rect.width()) - 1,
|
||||
(height * rect.height()) - 1)
|
||||
column.append(panel_rect)
|
||||
x += width
|
||||
y += height
|
||||
result.append(column)
|
||||
return result
|
||||
|
||||
|
||||
def get_vertical_stack_rects(data, rect):
|
||||
result = []
|
||||
x = 0
|
||||
for width, rows in data:
|
||||
column = []
|
||||
y = 0
|
||||
for height in rows:
|
||||
panel_rect = QtCore.QRectF(
|
||||
x * rect.width(),
|
||||
y * rect.height(),
|
||||
(width * rect.width()) - 1,
|
||||
(height * rect.height()) - 1)
|
||||
column.append(panel_rect)
|
||||
y += height
|
||||
x += width
|
||||
result.append(column)
|
||||
return result
|
||||
|
||||
|
||||
def get_vertical_handler_rect(rect):
|
||||
return QtCore.QRectF(
|
||||
rect.right() - HANDLER_WIDTH,
|
||||
rect.center().y() - (HANDLER_HEIGHT / 2),
|
||||
HANDLER_WIDTH, HANDLER_HEIGHT)
|
||||
|
||||
|
||||
def get_horizontal_handler_rect(rect):
|
||||
return QtCore.QRectF(
|
||||
rect.center().x() - (HANDLER_WIDTH / 2),
|
||||
rect.bottom() - HANDLER_HEIGHT,
|
||||
HANDLER_WIDTH, HANDLER_HEIGHT)
|
||||
|
||||
|
||||
def get_close_handler_rect(rect):
|
||||
return QtCore.QRectF(
|
||||
rect.right() - HANDLER_WIDTH,
|
||||
rect.top(), HANDLER_HEIGHT, HANDLER_WIDTH)
|
||||
|
||||
|
||||
def delete_panel(data, index):
|
||||
column = data[index[0]][1]
|
||||
if len(column) > 1:
|
||||
data[index[0]][1] = delete_value(column, index[1])
|
||||
return
|
||||
values = delete_value([c[0] for c in data], index[0])
|
||||
del data[index[0]]
|
||||
for i, value in enumerate(values):
|
||||
data[i][0] = value
|
||||
|
||||
|
||||
def delete_value(values, index):
|
||||
getcontext().prec = 50
|
||||
decimal_values = [Decimal(v) for v in values]
|
||||
values = [
|
||||
Decimal(v) for i, v in enumerate(decimal_values) if i != index]
|
||||
return [
|
||||
float((v / sum(values)).quantize(Decimal('1.00000')))
|
||||
for v in values]
|
||||
|
||||
|
||||
def move_vertical(data, index, y):
|
||||
column = data[index[0]][1]
|
||||
ratios = to_ratios(column)
|
||||
if index[1] == 0:
|
||||
y = max((.1, y))
|
||||
else:
|
||||
y = max((ratios[index[1] - 1] + .1, y))
|
||||
y = min((y, ratios[index[1] + 1] - .1))
|
||||
ratios[index[1]] = y
|
||||
data[index[0]][1] = to_weights(ratios)
|
||||
|
||||
|
||||
def move_horizontal(data, index, x):
|
||||
ratios = to_ratios(c[0] for c in data)
|
||||
if index[0] == 0:
|
||||
x = max((.1, x))
|
||||
else:
|
||||
x = max((ratios[index[0] - 1] + .1, x))
|
||||
x = min((x, ratios[index[0] + 1] - .1))
|
||||
ratios[index[0]] = x
|
||||
for i, col in enumerate(to_weights(ratios)):
|
||||
data[i][0] = col
|
||||
|
||||
|
||||
def up_arrow(rect):
|
||||
path = QtGui.QPainterPath(rect.bottomLeft())
|
||||
path.lineTo(rect.bottomRight())
|
||||
point = QtCore.QPointF(rect.center().x(), rect.top())
|
||||
path.lineTo(point)
|
||||
path.lineTo(rect.bottomLeft())
|
||||
return path
|
||||
|
||||
|
||||
def left_arrow(rect):
|
||||
path = QtGui.QPainterPath(rect.topRight())
|
||||
path.lineTo(rect.bottomRight())
|
||||
point = QtCore.QPointF(rect.left(), rect.center().y())
|
||||
path.lineTo(point)
|
||||
path.lineTo(rect.topRight())
|
||||
return path
|
||||
|
||||
|
||||
def to_ratios(weights):
|
||||
"""
|
||||
Convert weight list to ratios.
|
||||
input: [0.2, 0.3, 0.4, 0.1]
|
||||
output: [0.2, 0.5, 0.9, 1.0]
|
||||
"""
|
||||
total = 0.0
|
||||
result = []
|
||||
for weight in weights:
|
||||
total += weight
|
||||
result.append(total)
|
||||
return result
|
||||
|
||||
|
||||
def to_weights(ratios):
|
||||
"""
|
||||
Convert ratio list to weights.
|
||||
input: [0.2, 0.5, 0.9, 1.0]
|
||||
output: [0.2, 0.3, 0.4, 0.1]
|
||||
"""
|
||||
result = []
|
||||
result.extend(ratio - sum(result) for ratio in ratios)
|
||||
return result
|
||||
531
2023/scripts/animation_tools/dwpicker/dialog.py
Normal file
@@ -0,0 +1,531 @@
|
||||
from functools import partial
|
||||
import os
|
||||
|
||||
from .pyside import QtWidgets, QtCore, QtGui
|
||||
from maya import cmds
|
||||
|
||||
from .designer.highlighter import get_highlighter
|
||||
from .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 .languages import MEL, PYTHON
|
||||
from .path import get_image_directory
|
||||
from .qtutils import icon
|
||||
from .namespace import selected_namespace
|
||||
from .templates import BUTTON
|
||||
|
||||
|
||||
SEARCH_AND_REPLACE_FIELDS = 'Targets', 'Label', 'Image path', 'Command'
|
||||
SHAPES_FILTERS = 'All shapes', 'Selected shapes'
|
||||
COMMAND_PLACEHOLDER = """\
|
||||
PYTHON:
|
||||
__targets__: List[str] (variable available by default in the script)
|
||||
__shape__: dict (clicked shape data as dict. Dict is editable).
|
||||
example to toggle the background color:
|
||||
current_color = __shape__['bgcolor.normal']
|
||||
__shape__['bgcolor.normal'] = (
|
||||
"black" if current_color == 'white' else "white")
|
||||
|
||||
MEL:
|
||||
var $targets[] is availables by default.
|
||||
"""
|
||||
|
||||
|
||||
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, dialog_title="Repath image..."):
|
||||
filename = QtWidgets.QFileDialog.getOpenFileName(
|
||||
parent, dialog_title, 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(icon('mini-open.png'), '')
|
||||
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.setPlaceholderText(COMMAND_PLACEHOLDER)
|
||||
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 sizeHint(self):
|
||||
return QtCore.QSize(400, 550)
|
||||
|
||||
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()}
|
||||
|
||||
|
||||
class MenuCommandEditorDialog(QtWidgets.QDialog):
|
||||
|
||||
def __init__(self, command, parent=None):
|
||||
super(MenuCommandEditorDialog, 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.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.caption = QtWidgets.QLineEdit()
|
||||
self.caption.setText(command['caption'])
|
||||
|
||||
self.command = QtWidgets.QTextEdit()
|
||||
self.command.setPlaceholderText(COMMAND_PLACEHOLDER)
|
||||
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('Caption', self.caption)
|
||||
form.addRow('Language', self.languages)
|
||||
|
||||
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(code)
|
||||
layout.addLayout(buttons_layout)
|
||||
self.language_changed()
|
||||
|
||||
def sizeHint(self):
|
||||
return QtCore.QSize(400, 550)
|
||||
|
||||
def language_changed(self, *_):
|
||||
language = self.languages.currentText()
|
||||
highlighter = get_highlighter(language)
|
||||
highlighter(self.command.document())
|
||||
|
||||
def command_data(self):
|
||||
return {
|
||||
'caption': self.caption.text(),
|
||||
'language': self.languages.currentText(),
|
||||
'command': self.command.toPlainText(),
|
||||
'deferred': self.eval_deferred.isChecked(),
|
||||
'force_compact_undo': self.unique_undo.isChecked()}
|
||||
144
2023/scripts/animation_tools/dwpicker/document.py
Normal file
@@ -0,0 +1,144 @@
|
||||
|
||||
import uuid
|
||||
from copy import deepcopy
|
||||
from collections import defaultdict
|
||||
from .pyside import QtCore
|
||||
from .shape import Shape
|
||||
from .templates import PICKER
|
||||
from .undo import UndoManager
|
||||
from .stack import count_panels
|
||||
|
||||
|
||||
class PickerDocument(QtCore.QObject):
|
||||
shapes_changed = QtCore.Signal()
|
||||
# origin: str ["editor"|"picker"], key: str
|
||||
general_option_changed = QtCore.Signal(str, str)
|
||||
data_changed = QtCore.Signal()
|
||||
changed = QtCore.Signal()
|
||||
|
||||
def __init__(self, data):
|
||||
super(PickerDocument, self).__init__()
|
||||
self.data = data
|
||||
self.filename = None
|
||||
self.modified_state = False
|
||||
self.undo_manager = UndoManager(self.data)
|
||||
|
||||
self.shapes = []
|
||||
self.shapes_by_panel = {}
|
||||
self.shapes_by_id = {}
|
||||
self.shapes_by_layer = {}
|
||||
self.generate_shapes()
|
||||
|
||||
self.shapes_changed.connect(self.emit_change)
|
||||
self.general_option_changed.connect(self.emit_change)
|
||||
self.data_changed.connect(self.emit_change)
|
||||
self.shapes_changed.connect(self.emit_change)
|
||||
|
||||
def emit_change(self, *_):
|
||||
"""
|
||||
Signal allways emitted when any data of the model changed.
|
||||
"""
|
||||
self.changed.emit()
|
||||
|
||||
@staticmethod
|
||||
def create():
|
||||
data = {
|
||||
'general': deepcopy(PICKER),
|
||||
'shapes': []}
|
||||
return PickerDocument(data)
|
||||
|
||||
def record_undo(self):
|
||||
self.undo_manager.set_data_modified(self.data)
|
||||
self.modified_state = True
|
||||
|
||||
def undo(self):
|
||||
if self.undo_manager.undo():
|
||||
self.data = self.undo_manager.data
|
||||
self.generate_shapes()
|
||||
self.data_changed.emit()
|
||||
self.modified_state = True
|
||||
|
||||
def redo(self):
|
||||
if self.undo_manager.redo():
|
||||
self.data = self.undo_manager.data
|
||||
self.generate_shapes()
|
||||
self.data_changed.emit()
|
||||
self.modified_state = True
|
||||
|
||||
def panel_count(self):
|
||||
return count_panels(self.data['general']['panels'])
|
||||
|
||||
def set_shapes_data(self, data):
|
||||
self.data['shapes'] = data
|
||||
self.generate_shapes()
|
||||
|
||||
def generate_shapes(self):
|
||||
self.shapes = [Shape(options) for options in self.data['shapes']]
|
||||
self.sync_shapes_caches()
|
||||
|
||||
def sync_shapes_caches(self):
|
||||
self.shapes_by_panel = defaultdict(list)
|
||||
self.shapes_by_id = {}
|
||||
self.shapes_by_layer = defaultdict(list)
|
||||
for shape in self.shapes:
|
||||
self.shapes_by_panel[shape.options['panel']].append(shape)
|
||||
self.shapes_by_id[shape.options['id']] = shape
|
||||
layer = shape.options['visibility_layer']
|
||||
if layer:
|
||||
self.shapes_by_layer[layer].append(shape)
|
||||
|
||||
def add_shapes(self, shapes_data, prepend=False, hierarchize=False):
|
||||
for options in shapes_data:
|
||||
options['id'] = str(uuid.uuid4())
|
||||
options['children'] = []
|
||||
|
||||
shapes = []
|
||||
parent_shape = None
|
||||
for options in shapes_data:
|
||||
shape = Shape(options)
|
||||
shapes.append(shape)
|
||||
if parent_shape and hierarchize:
|
||||
parent_shape.options['children'].append(shape.options['id'])
|
||||
parent_shape = shape
|
||||
|
||||
if prepend:
|
||||
for shape in reversed(shapes):
|
||||
self.shapes.insert(0, shape)
|
||||
self.data['shapes'].insert(0, shape.options)
|
||||
else:
|
||||
self.shapes.extend(shapes)
|
||||
self.data['shapes'].extend(shapes_data)
|
||||
|
||||
self.sync_shapes_caches()
|
||||
return shapes
|
||||
|
||||
def remove_shapes(self, shapes):
|
||||
removed_ids = [shape.options['id'] for shape in shapes]
|
||||
self.data['shapes'] = [
|
||||
s for s in self.data['shapes'] if s['id'] not in removed_ids]
|
||||
self.generate_shapes()
|
||||
|
||||
def all_children(self, id_):
|
||||
if id_ not in self.shapes_by_id:
|
||||
return []
|
||||
|
||||
shape = self.shapes_by_id[id_]
|
||||
result = []
|
||||
to_visit = [id_]
|
||||
visited = set()
|
||||
|
||||
while to_visit:
|
||||
current_id = to_visit.pop(0)
|
||||
|
||||
if current_id in visited:
|
||||
continue
|
||||
|
||||
visited.add(current_id)
|
||||
shape = self.shapes_by_id.get(current_id)
|
||||
|
||||
if shape:
|
||||
result.append(shape)
|
||||
children = shape.options.get('children', [])
|
||||
to_visit.extend(c for c in children if c not in visited)
|
||||
|
||||
return result
|
||||
391
2023/scripts/animation_tools/dwpicker/geometry.py
Normal file
@@ -0,0 +1,391 @@
|
||||
import math
|
||||
from .pyside import QtCore, QtGui
|
||||
|
||||
|
||||
POINT_RADIUS = 8
|
||||
POINT_OFFSET = 4
|
||||
DIRECTIONS = [
|
||||
'top_left',
|
||||
'bottom_left',
|
||||
'top_right',
|
||||
'bottom_right',
|
||||
'left',
|
||||
'right',
|
||||
'top',
|
||||
'bottom']
|
||||
|
||||
|
||||
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 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.QRectF(left, top, width, height)
|
||||
|
||||
|
||||
def resize_rect_with_ratio(rect, reference_rect_output):
|
||||
ratio = rect.width() / rect.height()
|
||||
width = reference_rect_output.width()
|
||||
height = reference_rect_output.width() / ratio
|
||||
if reference_rect_output.height() < height:
|
||||
width = reference_rect_output.height() * ratio
|
||||
height = reference_rect_output.height()
|
||||
rect = QtCore.QRectF(0, 0, width, height)
|
||||
rect.moveCenter(reference_rect_output.center())
|
||||
return rect
|
||||
|
||||
|
||||
def get_shapes_bounding_rects(shapes):
|
||||
rects = [
|
||||
shape.rect if shape.options['shape'] != 'custom' else
|
||||
shape.path.boundingRect()
|
||||
for shape in shapes]
|
||||
return get_combined_rects(rects)
|
||||
|
||||
|
||||
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.QRectF(l, t, r-l, b-t)
|
||||
|
||||
|
||||
def get_global_rect(points):
|
||||
left = min(p.x() for p in points)
|
||||
top = min(p.y() for p in points)
|
||||
width = max(p.x() for p in points) - left
|
||||
height = max(p.y() for p in points) - top
|
||||
return QtCore.QRectF(left, top, width, height)
|
||||
|
||||
|
||||
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 rect_top_left_symmetry(rect, point, horizontal=True):
|
||||
topleft = rect.topLeft()
|
||||
if horizontal:
|
||||
dist = (topleft.x() - point.x()) * 2
|
||||
vector = QtCore.QPoint(dist, 0)
|
||||
else:
|
||||
dist = (topleft.y() - point.y()) * 2
|
||||
vector = QtCore.QPoint(0, dist)
|
||||
topleft = rect.topLeft() - vector
|
||||
rect.moveTopLeft(topleft)
|
||||
return rect
|
||||
|
||||
|
||||
def path_symmetry(path, center=None, horizontal=True):
|
||||
center = center or QtCore.QPointF(0, 0)
|
||||
for point in path:
|
||||
for key in ['point', 'tangent_in', 'tangent_out']:
|
||||
if point[key] is None:
|
||||
continue
|
||||
if horizontal:
|
||||
point[key][0] = center.x() - (point[key][0] - center.x())
|
||||
else:
|
||||
point[key][1] = center.y() - (point[key][1] - center.y())
|
||||
|
||||
|
||||
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)]
|
||||
|
||||
|
||||
def angle_at(path, percent):
|
||||
halfway_point = path.pointAtPercent(percent)
|
||||
tangent = path.percentAtLength(path.length() / 2)
|
||||
dx = path.pointAtPercent(tangent - 0.01).x() - halfway_point.x()
|
||||
dy = path.pointAtPercent(tangent - 0.01).y() - halfway_point.y()
|
||||
|
||||
angle_radians = math.atan2(dy, dx)
|
||||
angle_degrees = math.degrees(angle_radians)
|
||||
return angle_degrees
|
||||
|
||||
|
||||
def get_connection_path(
|
||||
start_point, end_point, viewportmapper=None):
|
||||
|
||||
start_point = viewportmapper.to_viewport_coords(start_point)
|
||||
end_point = viewportmapper.to_viewport_coords(end_point)
|
||||
|
||||
path = QtGui.QPainterPath(start_point)
|
||||
path.lineTo(end_point)
|
||||
path = QtGui.QPainterPathStroker().createStroke(path)
|
||||
|
||||
line = QtGui.QPainterPath(start_point)
|
||||
line.lineTo(end_point)
|
||||
degrees = angle_at(line, 0.5)
|
||||
center = line.pointAtPercent(0.5)
|
||||
|
||||
offset = 3 + viewportmapper.zoom
|
||||
triangle = QtGui.QPolygonF([
|
||||
QtCore.QPointF(center.x() - offset, center.y() - offset),
|
||||
QtCore.QPointF(center.x() + offset, center.y()),
|
||||
QtCore.QPointF(center.x() - offset, center.y() + offset),
|
||||
QtCore.QPointF(center.x() - offset, center.y() - offset)])
|
||||
|
||||
transform = QtGui.QTransform()
|
||||
transform.translate(center.x(), center.y())
|
||||
transform.rotate(degrees)
|
||||
transform.translate(-center.x(), -center.y())
|
||||
triangle = transform.map(triangle)
|
||||
path.addPolygon(triangle)
|
||||
return path
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
assert split_range(0, 10, 11) == [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
||||
40
2023/scripts/animation_tools/dwpicker/hotkeys.py
Normal file
@@ -0,0 +1,40 @@
|
||||
|
||||
from maya import cmds
|
||||
from .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
2023/scripts/animation_tools/dwpicker/hotkeyseditor.py
Normal file
@@ -0,0 +1,194 @@
|
||||
|
||||
from .pyside import QtWidgets, QtCore, QtGui
|
||||
from .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
2023/scripts/animation_tools/dwpicker/icons/addbg.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
2023/scripts/animation_tools/dwpicker/icons/addbutton.png
Normal file
|
After Width: | Height: | Size: 5.4 KiB |
BIN
2023/scripts/animation_tools/dwpicker/icons/addshape.png
Normal file
|
After Width: | Height: | Size: 3.7 KiB |
BIN
2023/scripts/animation_tools/dwpicker/icons/addtext.png
Normal file
|
After Width: | Height: | Size: 4.3 KiB |
BIN
2023/scripts/animation_tools/dwpicker/icons/align_bottom.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
2023/scripts/animation_tools/dwpicker/icons/align_h_center.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
2023/scripts/animation_tools/dwpicker/icons/align_left.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
2023/scripts/animation_tools/dwpicker/icons/align_right.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
2023/scripts/animation_tools/dwpicker/icons/align_top.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
2023/scripts/animation_tools/dwpicker/icons/align_v_center.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
2023/scripts/animation_tools/dwpicker/icons/arrange_h.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
2023/scripts/animation_tools/dwpicker/icons/arrange_v.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
2023/scripts/animation_tools/dwpicker/icons/center.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
2023/scripts/animation_tools/dwpicker/icons/copy.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
2023/scripts/animation_tools/dwpicker/icons/copy_settings.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
2023/scripts/animation_tools/dwpicker/icons/delete.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
2023/scripts/animation_tools/dwpicker/icons/delete2.png
Normal file
|
After Width: | Height: | Size: 5.4 KiB |
BIN
2023/scripts/animation_tools/dwpicker/icons/dock.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
2023/scripts/animation_tools/dwpicker/icons/dreamwallpicker.png
Normal file
|
After Width: | Height: | Size: 9.1 KiB |
BIN
2023/scripts/animation_tools/dwpicker/icons/edit.png
Normal file
|
After Width: | Height: | Size: 784 B |
BIN
2023/scripts/animation_tools/dwpicker/icons/edit2.png
Normal file
|
After Width: | Height: | Size: 731 B |
BIN
2023/scripts/animation_tools/dwpicker/icons/frame.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
2023/scripts/animation_tools/dwpicker/icons/h_symmetry.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
2023/scripts/animation_tools/dwpicker/icons/hierarchy.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
2023/scripts/animation_tools/dwpicker/icons/isolate.png
Normal file
|
After Width: | Height: | Size: 6.9 KiB |
BIN
2023/scripts/animation_tools/dwpicker/icons/link.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 22 KiB |
BIN
2023/scripts/animation_tools/dwpicker/icons/manager-delete.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
2023/scripts/animation_tools/dwpicker/icons/manager-edit.png
Normal file
|
After Width: | Height: | Size: 963 B |
BIN
2023/scripts/animation_tools/dwpicker/icons/manager-export.png
Normal file
|
After Width: | Height: | Size: 6.5 KiB |
BIN
2023/scripts/animation_tools/dwpicker/icons/manager-import.png
Normal file
|
After Width: | Height: | Size: 6.3 KiB |
BIN
2023/scripts/animation_tools/dwpicker/icons/manager-new.png
Normal file
|
After Width: | Height: | Size: 998 B |
BIN
2023/scripts/animation_tools/dwpicker/icons/mini-open.png
Normal file
|
After Width: | Height: | Size: 737 B |
BIN
2023/scripts/animation_tools/dwpicker/icons/movedown.png
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
BIN
2023/scripts/animation_tools/dwpicker/icons/moveup.png
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
BIN
2023/scripts/animation_tools/dwpicker/icons/new.png
Normal file
|
After Width: | Height: | Size: 4.5 KiB |
BIN
2023/scripts/animation_tools/dwpicker/icons/onbottom.png
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
BIN
2023/scripts/animation_tools/dwpicker/icons/ontop.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
2023/scripts/animation_tools/dwpicker/icons/open.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
2023/scripts/animation_tools/dwpicker/icons/panels.png
Normal file
|
After Width: | Height: | Size: 435 B |
BIN
2023/scripts/animation_tools/dwpicker/icons/paste.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
2023/scripts/animation_tools/dwpicker/icons/paste_settings.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
2023/scripts/animation_tools/dwpicker/icons/picker.png
Normal file
|
After Width: | Height: | Size: 567 B |
BIN
2023/scripts/animation_tools/dwpicker/icons/play.png
Normal file
|
After Width: | Height: | Size: 1020 B |
BIN
2023/scripts/animation_tools/dwpicker/icons/polygon.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
2023/scripts/animation_tools/dwpicker/icons/redo.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
2023/scripts/animation_tools/dwpicker/icons/reload.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
2023/scripts/animation_tools/dwpicker/icons/rotation.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
2023/scripts/animation_tools/dwpicker/icons/save.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
2023/scripts/animation_tools/dwpicker/icons/search.png
Normal file
|
After Width: | Height: | Size: 21 KiB |
BIN
2023/scripts/animation_tools/dwpicker/icons/snap.png
Normal file
|
After Width: | Height: | Size: 4.3 KiB |
BIN
2023/scripts/animation_tools/dwpicker/icons/tabs.png
Normal file
|
After Width: | Height: | Size: 663 B |
BIN
2023/scripts/animation_tools/dwpicker/icons/tangent.png
Normal file
|
After Width: | Height: | Size: 746 B |
BIN
2023/scripts/animation_tools/dwpicker/icons/tangentbreak.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
2023/scripts/animation_tools/dwpicker/icons/touch.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
2023/scripts/animation_tools/dwpicker/icons/undo.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
2023/scripts/animation_tools/dwpicker/icons/unlink.png
Normal file
|
After Width: | Height: | Size: 478 B |
BIN
2023/scripts/animation_tools/dwpicker/icons/v_symmetry.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
@@ -0,0 +1 @@
|
||||
from .converter import convert
|
||||
@@ -0,0 +1,271 @@
|
||||
import json
|
||||
import os
|
||||
from ...pyside import QtGui
|
||||
|
||||
from ...compatibility import ensure_retro_compatibility
|
||||
from .parser import parse_animschool_picker, save_png
|
||||
|
||||
|
||||
PICKER = {
|
||||
'name': 'Untitled',
|
||||
'version': (0, 15, 3),
|
||||
'panels.as_sub_tab': False,
|
||||
'panels.orientation': 'vertical',
|
||||
'panels.zoom_locked': [False],
|
||||
'panels.colors': [None],
|
||||
'panels.names': ['Panel 1'],
|
||||
'menu_commands': [],
|
||||
'hidden_layers': [],
|
||||
'panels': [[1.0, [1.0]]]
|
||||
}
|
||||
|
||||
BUTTON = {
|
||||
'background': False,
|
||||
'visibility_layer': None,
|
||||
'shape.ignored_by_focus': False,
|
||||
'panel': 0,
|
||||
'shape': 'square', # or round or rounded_rect or custom
|
||||
'shape.space': 'world', # or screen
|
||||
'shape.anchor': 'top_left', # or bottom_left, top_right, bottom_right
|
||||
'shape.path' : [],
|
||||
'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': [],
|
||||
'action.menu_commands': [],
|
||||
'image.path': '',
|
||||
'image.fit': True,
|
||||
'image.ratio': True,
|
||||
'image.height': 32,
|
||||
'image.width': 32
|
||||
}
|
||||
|
||||
|
||||
TEXT = {
|
||||
'background': False,
|
||||
'visibility_layer': None,
|
||||
'shape.ignored_by_focus': False,
|
||||
'panel': 0,
|
||||
'shape': 'square', # or round or rounded_rect or custom
|
||||
'shape.space': 'world', # or screen
|
||||
'shape.anchor': 'top_left', # or bottom_left, top_right, bottom_right
|
||||
'shape.path' : [],
|
||||
'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': [],
|
||||
'action.menu_commands': [],
|
||||
'image.path': '',
|
||||
'image.fit': False,
|
||||
'image.ratio': True,
|
||||
'image.height': 32,
|
||||
'image.width': 32,
|
||||
}
|
||||
|
||||
|
||||
BACKGROUND = {
|
||||
'background': True,
|
||||
'visibility_layer': None,
|
||||
'shape.ignored_by_focus': True,
|
||||
'panel': 0,
|
||||
'shape': 'square', # or round or rounded_rect or custom
|
||||
'shape.space': 'world', # or screen
|
||||
'shape.anchor': 'top_left', # or bottom_left, top_right, bottom_right
|
||||
'shape.path' : [],
|
||||
'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': [],
|
||||
'action.menu_commands': [],
|
||||
'image.path': '',
|
||||
'image.fit': True,
|
||||
'image.ratio': False,
|
||||
'image.height': 32,
|
||||
'image.width': 32,
|
||||
}
|
||||
|
||||
|
||||
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)
|
||||
ensure_retro_compatibility(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
|
||||
@@ -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)
|
||||
85
2023/scripts/animation_tools/dwpicker/interactionmanager.py
Normal file
@@ -0,0 +1,85 @@
|
||||
from .pyside import QtWidgets, QtCore
|
||||
from maya import cmds
|
||||
from .optionvar import ZOOM_BUTTON
|
||||
|
||||
|
||||
class InteractionManager:
|
||||
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 InteractionManager.DRAGGING
|
||||
elif self.zoom_button_pressed and self.alt_pressed:
|
||||
return InteractionManager.ZOOMING
|
||||
elif self.middle_click_pressed:
|
||||
return InteractionManager.NAVIGATION
|
||||
elif self.left_click_pressed:
|
||||
return InteractionManager.SELECTION
|
||||
self.mouse_ghost = None
|
||||
return InteractionManager.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))
|
||||
73
2023/scripts/animation_tools/dwpicker/interactive.py
Normal file
@@ -0,0 +1,73 @@
|
||||
|
||||
|
||||
from .pyside import QtCore
|
||||
|
||||
from .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)
|
||||
from .shape import rect_intersects_shape
|
||||
|
||||
|
||||
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, shape):
|
||||
if not shape or not self.rect:
|
||||
return False
|
||||
return rect_intersects_shape(shape, self.rect)
|
||||
|
||||
|
||||
class Manipulator():
|
||||
def __init__(self, viewportmapper=None):
|
||||
self._rect = None
|
||||
self.viewportmapper = viewportmapper
|
||||
self._is_hovered = False
|
||||
|
||||
@property
|
||||
def rect(self):
|
||||
return self._rect
|
||||
|
||||
def viewport_handlers(self):
|
||||
rect = self.viewportmapper.to_viewport_rect(self.rect)
|
||||
return [
|
||||
get_topleft_rect(rect) if rect else None,
|
||||
get_bottomleft_rect(rect) if rect else None,
|
||||
get_topright_rect(rect) if rect else None,
|
||||
get_bottomright_rect(rect) if rect else None,
|
||||
get_left_side_rect(rect) if rect else None,
|
||||
get_right_side_rect(rect) if rect else None,
|
||||
get_top_side_rect(rect) if rect else None,
|
||||
get_bottom_side_rect(rect) if rect else None]
|
||||
|
||||
def get_direction(self, viewport_cursor):
|
||||
if self.rect is None:
|
||||
return None
|
||||
for i, rect in enumerate(self.viewport_handlers()):
|
||||
if rect.contains(viewport_cursor):
|
||||
return DIRECTIONS[i]
|
||||
|
||||
def hovered_rects(self, cursor):
|
||||
rects = []
|
||||
for rect in self.viewport_handlers() + [self.rect]:
|
||||
if not rect:
|
||||
continue
|
||||
if rect.contains(cursor):
|
||||
rects.append(rect)
|
||||
return rects
|
||||
|
||||
def set_rect(self, rect):
|
||||
self._rect = rect
|
||||
89
2023/scripts/animation_tools/dwpicker/languages.py
Normal file
@@ -0,0 +1,89 @@
|
||||
from copy import deepcopy
|
||||
|
||||
PYTHON = 'python'
|
||||
MEL = 'mel'
|
||||
|
||||
|
||||
PYTHON_TARGETS_VARIABLE = """\
|
||||
import animation_tools.dwpicker as dwpicker
|
||||
__targets__ = [{targets}]
|
||||
if dwpicker.get_shape('{shape_id}'):
|
||||
__shape__ = dwpicker.get_shape('{shape_id}').options
|
||||
else:
|
||||
__shape__ = None
|
||||
{code}
|
||||
"""
|
||||
|
||||
|
||||
MEL_TARGETS_VARIABLE = """\
|
||||
string $targets[] = {{{targets}}};
|
||||
{code}
|
||||
"""
|
||||
|
||||
|
||||
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;
|
||||
"""
|
||||
|
||||
|
||||
EXECUTION_WARNING = """\
|
||||
Code execution failed for {object}: "{name}"
|
||||
{error}.
|
||||
"""
|
||||
|
||||
|
||||
def execute_code(
|
||||
language, code, shape=None, deferred=False, compact_undo=False):
|
||||
return EXECUTORS[language](code, shape, deferred, compact_undo)
|
||||
|
||||
|
||||
def execute_python(
|
||||
code, shape=None, deferred=False, compact_undo=False):
|
||||
if compact_undo:
|
||||
code = STACK_UNDO_PYTHON.format(code=code)
|
||||
if deferred:
|
||||
code = DEFERRED_PYTHON.format(code=code)
|
||||
targets = (shape.targets() or []) if shape else []
|
||||
targets = ', '.join(('"{}"'.format(target) for target in targets))
|
||||
shape_id = shape.options['id'] if shape else None
|
||||
code = PYTHON_TARGETS_VARIABLE.format(
|
||||
targets=targets, shape_id=shape_id, code=code)
|
||||
exec(code, globals())
|
||||
|
||||
|
||||
def execute_mel(code, shape=None, 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)
|
||||
targets = (shape.targets() or []) if shape else []
|
||||
if targets:
|
||||
targets = ', '.join(
|
||||
'"{}"'.format(target) for target in shape.targets())
|
||||
code = MEL_TARGETS_VARIABLE.format(targets=targets, code=code)
|
||||
mel.eval(code.replace(u'\u2029', '\n'))
|
||||
|
||||
|
||||
EXECUTORS = {
|
||||
PYTHON: execute_python,
|
||||
MEL: execute_mel,
|
||||
}
|
||||
870
2023/scripts/animation_tools/dwpicker/main.py
Normal file
@@ -0,0 +1,870 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import os
|
||||
import json
|
||||
import webbrowser
|
||||
from copy import deepcopy
|
||||
from functools import partial
|
||||
|
||||
from .pyside import QtWidgets, QtCore, QtGui
|
||||
|
||||
from maya import cmds
|
||||
import maya.OpenMaya as om
|
||||
|
||||
from .appinfos import (
|
||||
VERSION, RELEASE_DATE, DW_GITHUB, DW_WEBSITE, PICKER_DOCUMENTATION)
|
||||
from .compatibility import ensure_retro_compatibility
|
||||
from .document import PickerDocument
|
||||
from .designer.editor import PickerEditor
|
||||
from .dialog import (
|
||||
question, get_image_path, NamespaceDialog)
|
||||
from .ingest import animschool
|
||||
from .hotkeys import get_hotkeys_config
|
||||
from .namespace import (
|
||||
switch_namespace, selected_namespace, detect_picker_namespace,
|
||||
pickers_namespaces)
|
||||
from .optionvar import (
|
||||
AUTO_FOCUS_BEHAVIOR, AUTO_SWITCH_TAB, AUTO_RESIZE_NAMESPACE_COMBO,
|
||||
CHECK_IMAGES_PATHS, AUTO_SET_NAMESPACE, DISABLE_IMPORT_CALLBACKS,
|
||||
DISPLAY_HIERARCHY_IN_PICKER, DISPLAY_QUICK_OPTIONS,
|
||||
INSERT_TAB_AFTER_CURRENT, LAST_OPEN_DIRECTORY, LAST_IMPORT_DIRECTORY,
|
||||
LAST_SAVE_DIRECTORY, NAMESPACE_TOOLBAR, USE_ICON_FOR_UNSAVED_TAB,
|
||||
WARN_ON_TAB_CLOSED, save_optionvar, append_recent_filename,
|
||||
save_opened_filenames)
|
||||
from .path import get_import_directory, get_open_directory, format_path
|
||||
from .picker import PickerStackedView, list_targets
|
||||
from .preference import PreferencesWindow
|
||||
from .qtutils import set_shortcut, icon, maya_main_window, DockableBase
|
||||
from .quick import QuickOptions
|
||||
from .references import ensure_images_path_exists
|
||||
from .scenedata import (
|
||||
load_local_picker_data, store_local_picker_data,
|
||||
clean_stray_picker_holder_nodes)
|
||||
from .templates import PICKER, BACKGROUND
|
||||
|
||||
|
||||
ABOUT = """\
|
||||
DreamWall Picker
|
||||
Licence MIT
|
||||
Version: {version}
|
||||
Release date: {release}
|
||||
Authors: Lionel Brouyère, Olivier Evers
|
||||
Contributor(s):
|
||||
Herizo Ran, fabiencollet, c-morten, kalemas (Konstantin Maslyuk),
|
||||
Markus Ng, Jerome Drese, Renaud Lessard Larouche
|
||||
|
||||
Features:
|
||||
Animation picker widget.
|
||||
Quick picker creation.
|
||||
Advanced picker editing.
|
||||
Read AnimSchoolPicker files (december 2021 version and latest)
|
||||
Free and open source, today and forever.
|
||||
|
||||
This tool is a fork of Hotbox Designer (Lionel Brouyère).
|
||||
A menus, markmenu and hotbox designer cross DCC.
|
||||
https://github.com/luckylyk/hotbox_designer
|
||||
""".format(
|
||||
version=".".join(str(n) for n in VERSION),
|
||||
release=RELEASE_DATE)
|
||||
WINDOW_TITLE = "DreamWall - Picker"
|
||||
WINDOW_CONTROL_NAME = "dwPickerWindow"
|
||||
CLOSE_CALLBACK_COMMAND = "import dwpicker;dwpicker._dwpicker.close_event()"
|
||||
CLOSE_TAB_WARNING = """\
|
||||
Close the tab will remove completely the picker data from the scene.
|
||||
Are you sure to continue ?"""
|
||||
|
||||
|
||||
class DwPicker(DockableBase, QtWidgets.QWidget):
|
||||
def __init__(
|
||||
self,
|
||||
replace_namespace_function=None,
|
||||
list_namespaces_function=None):
|
||||
super(DwPicker, self).__init__(control_name=WINDOW_CONTROL_NAME)
|
||||
self.setWindowTitle(WINDOW_TITLE)
|
||||
self.shortcuts = {}
|
||||
self.replace_namespace_custom_function = replace_namespace_function
|
||||
self.list_namespaces_function = list_namespaces_function
|
||||
|
||||
self.editable = True
|
||||
self.callbacks = []
|
||||
self.stored_focus = None
|
||||
self.editors = []
|
||||
self.pickers = []
|
||||
self.preferences_window = PreferencesWindow(
|
||||
callback=self.load_ui_states, parent=maya_main_window())
|
||||
self.preferences_window.need_update_callbacks.connect(
|
||||
self.reload_callbacks)
|
||||
self.preferences_window.hotkey_changed.connect(self.register_shortcuts)
|
||||
|
||||
self.sub_panels_view = QtWidgets.QToolButton()
|
||||
self.sub_panels_view.setCheckable(True)
|
||||
self.sub_panels_view.setChecked(True)
|
||||
self.sub_panels_view.setIcon(icon("panels.png"))
|
||||
self.sub_panels_view.setFixedSize(17, 17)
|
||||
self.sub_panels_view.released.connect(self.update_panels_display_mode)
|
||||
self.sub_tabs_view = QtWidgets.QToolButton()
|
||||
self.sub_tabs_view.setCheckable(True)
|
||||
self.sub_tabs_view.setIcon(icon("tabs.png"))
|
||||
self.sub_tabs_view.setFixedSize(17, 17)
|
||||
self.sub_tabs_view.released.connect(self.update_panels_display_mode)
|
||||
|
||||
self.panel_buttons = QtWidgets.QButtonGroup()
|
||||
self.panel_buttons.addButton(self.sub_panels_view, 0)
|
||||
self.panel_buttons.addButton(self.sub_tabs_view, 1)
|
||||
|
||||
self.namespace_label = QtWidgets.QLabel("Namespace: ")
|
||||
self.namespace_combo = QtWidgets.QComboBox()
|
||||
self.namespace_combo.setMinimumWidth(200)
|
||||
method = self.change_namespace_combo
|
||||
self.namespace_combo.currentIndexChanged.connect(method)
|
||||
self.namespace_refresh = QtWidgets.QPushButton("")
|
||||
self.namespace_refresh.setIcon(icon("reload.png"))
|
||||
self.namespace_refresh.setFixedSize(17, 17)
|
||||
self.namespace_refresh.setIconSize(QtCore.QSize(15, 15))
|
||||
self.namespace_refresh.released.connect(self.update_namespaces)
|
||||
self.namespace_picker = QtWidgets.QPushButton("")
|
||||
self.namespace_picker.setIcon(icon("picker.png"))
|
||||
self.namespace_picker.setFixedSize(17, 17)
|
||||
self.namespace_picker.setIconSize(QtCore.QSize(15, 15))
|
||||
self.namespace_picker.released.connect(self.pick_namespace)
|
||||
self.namespace_widget = QtWidgets.QWidget()
|
||||
self.namespace_layout = QtWidgets.QHBoxLayout(self.namespace_widget)
|
||||
self.namespace_layout.setContentsMargins(10, 2, 2, 2)
|
||||
self.namespace_layout.setSpacing(0)
|
||||
self.namespace_layout.addWidget(self.namespace_label)
|
||||
self.namespace_layout.addSpacing(4)
|
||||
self.namespace_layout.addWidget(self.namespace_combo)
|
||||
self.namespace_layout.addSpacing(2)
|
||||
self.namespace_layout.addWidget(self.namespace_refresh)
|
||||
self.namespace_layout.addWidget(self.namespace_picker)
|
||||
self.namespace_layout.addStretch(1)
|
||||
self.namespace_layout.addWidget(self.sub_panels_view)
|
||||
self.namespace_layout.addWidget(self.sub_tabs_view)
|
||||
|
||||
self.tab = QtWidgets.QTabWidget()
|
||||
self.tab.setTabsClosable(True)
|
||||
self.tab.setMovable(True)
|
||||
self.tab.tabBar().tabMoved.connect(self.tab_moved)
|
||||
self.tab.tabBar().tabBarDoubleClicked.connect(self.change_title)
|
||||
self.tab.currentChanged.connect(self.tab_index_changed)
|
||||
method = partial(self.close_tab, store=True)
|
||||
self.tab.tabCloseRequested.connect(method)
|
||||
|
||||
self.quick_options = QuickOptions()
|
||||
|
||||
self.menubar = DwPickerMenu(parent=self)
|
||||
self.menubar.new.triggered.connect(self.call_new)
|
||||
self.menubar.open.triggered.connect(self.call_open)
|
||||
self.menubar.save.triggered.connect(self.call_save)
|
||||
self.menubar.save_as.triggered.connect(self.call_save_as)
|
||||
self.menubar.exit.triggered.connect(self.close)
|
||||
self.menubar.import_.triggered.connect(self.call_import)
|
||||
self.menubar.undo.triggered.connect(self.call_undo)
|
||||
self.menubar.redo.triggered.connect(self.call_redo)
|
||||
self.menubar.advanced_edit.triggered.connect(self.call_edit)
|
||||
self.menubar.preferences.triggered.connect(self.call_preferences)
|
||||
self.menubar.change_title.triggered.connect(self.change_title)
|
||||
self.menubar.toggle_display.triggered.connect(self.toggle_display_mode)
|
||||
method = self.toggle_hierarchy_display
|
||||
self.menubar.toggle_hierarchy_display.triggered.connect(method)
|
||||
method = self.change_namespace_dialog
|
||||
self.menubar.change_namespace.triggered.connect(method)
|
||||
self.menubar.add_background.triggered.connect(self.add_background)
|
||||
self.menubar.tools.triggered.connect(self.call_tools)
|
||||
self.menubar.documentation.triggered.connect(self.call_documentation)
|
||||
self.menubar.dw.triggered.connect(self.call_dreamwall)
|
||||
self.menubar.about.triggered.connect(self.call_about)
|
||||
|
||||
self.layout = QtWidgets.QVBoxLayout(self)
|
||||
self.layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.layout.setSpacing(0)
|
||||
self.layout.setMenuBar(self.menubar)
|
||||
self.layout.addWidget(self.namespace_widget)
|
||||
self.layout.addWidget(self.tab)
|
||||
self.layout.addWidget(self.quick_options)
|
||||
|
||||
self.load_ui_states()
|
||||
self.register_shortcuts()
|
||||
|
||||
def register_shortcuts(self):
|
||||
# Unregister all shortcuts before create new ones
|
||||
function_names_actions = {
|
||||
'focus': (self.reset, None),
|
||||
'new': (self.call_new, self.menubar.new),
|
||||
'open': (self.call_open, self.menubar.open),
|
||||
'save': (self.call_save, self.menubar.save),
|
||||
'close': (self.close, self.menubar.exit),
|
||||
'undo': (self.call_undo, self.menubar.undo),
|
||||
'redo': (self.call_redo, self.menubar.redo),
|
||||
'edit': (self.call_edit, self.menubar.advanced_edit),
|
||||
'next_tab': (self.call_next_tab, None),
|
||||
'previous_tab': (self.call_previous_tab, None),
|
||||
'toggle_display': (
|
||||
self.toggle_display_mode, self.menubar.toggle_display),
|
||||
'display_hierarchy': (
|
||||
self.toggle_hierarchy_display,
|
||||
self.menubar.toggle_hierarchy_display)
|
||||
}
|
||||
for function_name, sc in self.shortcuts.items():
|
||||
sc.activated.disconnect(function_names_actions[function_name][0])
|
||||
seq = QtGui.QKeySequence()
|
||||
action = function_names_actions[function_name][1]
|
||||
if not action:
|
||||
continue
|
||||
action.setShortcut(seq)
|
||||
|
||||
self.shortcuts = {}
|
||||
shortcut_context = QtCore.Qt.WidgetWithChildrenShortcut
|
||||
for function_name, data in get_hotkeys_config().items():
|
||||
if not data['enabled']:
|
||||
continue
|
||||
method = function_names_actions[function_name][0]
|
||||
ks = data['key_sequence']
|
||||
if ks is None:
|
||||
continue
|
||||
sc = set_shortcut(ks, self, method, shortcut_context)
|
||||
self.shortcuts[function_name] = sc
|
||||
# HACK: Need to implement twice the shortcut to display key
|
||||
# sequence in the menu and keep it active when the view is docked.
|
||||
action = function_names_actions[function_name][1]
|
||||
if action is None:
|
||||
continue
|
||||
action.setShortcut(ks)
|
||||
action.setShortcutContext(shortcut_context)
|
||||
|
||||
def show(self, *args, **kwargs):
|
||||
super(DwPicker, self).show(
|
||||
closeCallback=CLOSE_CALLBACK_COMMAND, *args, **kwargs)
|
||||
self.register_callbacks()
|
||||
|
||||
def close_event(self):
|
||||
self.preferences_window.close()
|
||||
|
||||
def list_scene_namespaces(self):
|
||||
if self.list_namespaces_function:
|
||||
ns = self.list_namespaces_function()
|
||||
else:
|
||||
ns = cmds.namespaceInfo(listOnlyNamespaces=True, recurse=True)
|
||||
ns = ns or []
|
||||
namespaces = ns + pickers_namespaces(self.pickers)
|
||||
return sorted(list(set(namespaces)))
|
||||
|
||||
def update_namespaces(self, *_):
|
||||
self.namespace_combo.blockSignals(True)
|
||||
self.namespace_combo.clear()
|
||||
self.namespace_combo.addItem("*Root*")
|
||||
namespaces = self.list_scene_namespaces()
|
||||
self.namespace_combo.addItems(namespaces)
|
||||
self.namespace_combo.blockSignals(False)
|
||||
|
||||
# Auto update namespace combo to namespace size.
|
||||
if not cmds.optionVar(query=AUTO_RESIZE_NAMESPACE_COMBO):
|
||||
self.namespace_combo.setSizePolicy(
|
||||
QtWidgets.QSizePolicy.MinimumExpanding,
|
||||
QtWidgets.QSizePolicy.Minimum)
|
||||
self.namespace_combo.setMinimumWidth(200)
|
||||
return
|
||||
max_width = 0
|
||||
for i in range(self.namespace_combo.count()):
|
||||
t = self.namespace_combo.itemText(i)
|
||||
width = self.namespace_combo.fontMetrics().horizontalAdvance(t)
|
||||
max_width = max(max_width, width)
|
||||
width = max_width + 20 # padding
|
||||
self.namespace_combo.setFixedWidth(max((200, width)))
|
||||
|
||||
def toggle_display_mode(self):
|
||||
index = int(not bool(self.panel_buttons.checkedId()))
|
||||
self.panel_buttons.button(index).setChecked(True)
|
||||
self.update_panels_display_mode()
|
||||
self.setFocus()
|
||||
|
||||
def toggle_hierarchy_display(self):
|
||||
state = not bool(cmds.optionVar(query=DISPLAY_HIERARCHY_IN_PICKER))
|
||||
save_optionvar(DISPLAY_HIERARCHY_IN_PICKER, int(state))
|
||||
self.update()
|
||||
self.setFocus()
|
||||
|
||||
def update_panels_display_mode(self, *_):
|
||||
state = bool(self.panel_buttons.checkedId())
|
||||
picker = self.tab.currentWidget()
|
||||
if picker is None:
|
||||
return
|
||||
focused_panel = picker.reset()
|
||||
picker.as_sub_tab = state
|
||||
picker.create_pickers()
|
||||
picker.create_panels(panel=focused_panel)
|
||||
QtCore.QTimer.singleShot(10, partial(picker.reset, force_all=True))
|
||||
picker.update()
|
||||
|
||||
def tab_index_changed(self, index):
|
||||
if not self.pickers:
|
||||
return
|
||||
picker = self.pickers[index]
|
||||
index = int(picker.as_sub_tab)
|
||||
self.panel_buttons.button(index).setChecked(True)
|
||||
if not picker:
|
||||
return
|
||||
namespace = detect_picker_namespace(picker.document.shapes)
|
||||
self.namespace_combo.blockSignals(True)
|
||||
if self.namespace_combo.findText(namespace) == -1 and namespace:
|
||||
self.namespace_combo.addItem(namespace)
|
||||
if namespace:
|
||||
self.namespace_combo.setCurrentText(namespace)
|
||||
else:
|
||||
self.namespace_combo.setCurrentIndex(0)
|
||||
self.namespace_combo.blockSignals(False)
|
||||
|
||||
def tab_moved(self, newindex, oldindex):
|
||||
|
||||
for l in (self.editors, self.pickers):
|
||||
l.insert(newindex, l.pop(oldindex))
|
||||
|
||||
self.store_local_pickers_data()
|
||||
|
||||
def leaveEvent(self, _):
|
||||
mode = cmds.optionVar(query=AUTO_FOCUS_BEHAVIOR)
|
||||
if mode == 'off':
|
||||
return
|
||||
cmds.setFocus("MayaWindow")
|
||||
|
||||
def enterEvent(self, _):
|
||||
mode = cmds.optionVar(query=AUTO_FOCUS_BEHAVIOR)
|
||||
if mode == 'bilateral':
|
||||
cmds.setFocus(self.objectName())
|
||||
|
||||
def dockCloseEventTriggered(self):
|
||||
save_opened_filenames([
|
||||
p.document.filename for p in self.pickers
|
||||
if p.document.filename])
|
||||
|
||||
modified = [p.document.modified_state for p in self.pickers]
|
||||
if not any(modified):
|
||||
return super(DwPicker, self).dockCloseEventTriggered()
|
||||
|
||||
msg = (
|
||||
'Some picker have unsaved modification. \n'
|
||||
'Would you like to save them ?')
|
||||
result = QtWidgets.QMessageBox.question(
|
||||
None, 'Save ?', msg,
|
||||
buttons=(
|
||||
QtWidgets.QMessageBox.SaveAll |
|
||||
QtWidgets.QMessageBox.Close),
|
||||
defaultButton=QtWidgets.QMessageBox.SaveAll)
|
||||
|
||||
if result == QtWidgets.QMessageBox.Close:
|
||||
return
|
||||
|
||||
for i in range(self.tab.count()-1, -1, -1):
|
||||
self.save_tab(i)
|
||||
|
||||
save_opened_filenames([p.document.filename for p in self.pickers])
|
||||
return super(DwPicker, self).dockCloseEventTriggered()
|
||||
|
||||
def reload_callbacks(self):
|
||||
self.unregister_callbacks()
|
||||
self.register_callbacks()
|
||||
|
||||
def register_callbacks(self):
|
||||
self.unregister_callbacks()
|
||||
callbacks = {
|
||||
om.MSceneMessage.kBeforeNew: [
|
||||
self.close_tabs, self.update_namespaces],
|
||||
om.MSceneMessage.kAfterOpen: [
|
||||
self.load_saved_pickers, self.update_namespaces],
|
||||
om.MSceneMessage.kAfterCreateReference: [
|
||||
self.load_saved_pickers, self.update_namespaces]}
|
||||
if not cmds.optionVar(query=DISABLE_IMPORT_CALLBACKS):
|
||||
callbacks[om.MSceneMessage.kAfterImport] = [
|
||||
self.load_saved_pickers, self.update_namespaces]
|
||||
|
||||
for event, methods in callbacks.items():
|
||||
for method in methods:
|
||||
callback = om.MSceneMessage.addCallback(event, method)
|
||||
self.callbacks.append(callback)
|
||||
|
||||
method = self.auto_switch_tab
|
||||
cb = om.MEventMessage.addEventCallback('SelectionChanged', method)
|
||||
self.callbacks.append(cb)
|
||||
method = self.auto_switch_namespace
|
||||
cb = om.MEventMessage.addEventCallback('SelectionChanged', method)
|
||||
self.callbacks.append(cb)
|
||||
|
||||
for picker in self.pickers:
|
||||
picker.register_callbacks()
|
||||
|
||||
def unregister_callbacks(self):
|
||||
for cb in self.callbacks:
|
||||
om.MMessage.removeCallback(cb)
|
||||
self.callbacks.remove(cb)
|
||||
for picker in self.pickers:
|
||||
picker.unregister_callbacks()
|
||||
|
||||
def auto_switch_namespace(self, *_, **__):
|
||||
if not cmds.optionVar(query=AUTO_SET_NAMESPACE):
|
||||
return
|
||||
self.pick_namespace()
|
||||
|
||||
def auto_switch_tab(self, *_, **__):
|
||||
if not cmds.optionVar(query=AUTO_SWITCH_TAB):
|
||||
return
|
||||
nodes = cmds.ls(selection=True)
|
||||
if not nodes:
|
||||
return
|
||||
picker = self.tab.currentWidget()
|
||||
if not picker:
|
||||
return
|
||||
targets = list_targets(picker.document.shapes)
|
||||
if nodes[-1] in targets:
|
||||
return
|
||||
for i, picker in enumerate(self.pickers):
|
||||
if nodes[-1] in list_targets(picker.document.shapes):
|
||||
self.tab.setCurrentIndex(i)
|
||||
return
|
||||
|
||||
def load_saved_pickers(self, *_, **__):
|
||||
self.clear()
|
||||
pickers = load_local_picker_data()
|
||||
if cmds.optionVar(query=CHECK_IMAGES_PATHS):
|
||||
ensure_images_path_exists(pickers)
|
||||
for picker in pickers:
|
||||
self.add_picker(picker)
|
||||
clean_stray_picker_holder_nodes()
|
||||
|
||||
def store_local_pickers_data(self):
|
||||
if not self.editable:
|
||||
return
|
||||
|
||||
if not self.tab.count():
|
||||
store_local_picker_data([])
|
||||
return
|
||||
|
||||
pickers = [self.document(i).data for i in range(self.tab.count())]
|
||||
store_local_picker_data(pickers)
|
||||
|
||||
def save_tab(self, index):
|
||||
msg = (
|
||||
'Picker contain unsaved modification !\n'
|
||||
'Woud you like to continue ?')
|
||||
result = QtWidgets.QMessageBox.question(
|
||||
None, 'Save ?', msg,
|
||||
buttons=(
|
||||
QtWidgets.QMessageBox.Save |
|
||||
QtWidgets.QMessageBox.Yes |
|
||||
QtWidgets.QMessageBox.Cancel),
|
||||
defaultButton=QtWidgets.QMessageBox.Cancel)
|
||||
|
||||
if result == QtWidgets.QMessageBox.Cancel:
|
||||
return False
|
||||
elif result == QtWidgets.QMessageBox.Save and not self.call_save(index):
|
||||
return False
|
||||
return True
|
||||
|
||||
def close_tabs(self, *_):
|
||||
for i in range(self.tab.count()-1, -1, -1):
|
||||
self.close_tab(i)
|
||||
self.store_local_pickers_data()
|
||||
|
||||
def clear(self):
|
||||
for i in range(self.tab.count()-1, -1, -1):
|
||||
self.close_tab(i, force=True)
|
||||
|
||||
def close_tab(self, index, force=False, store=False):
|
||||
if self.document(index).modified_state and force is False:
|
||||
if not self.save_tab(index):
|
||||
return
|
||||
|
||||
elif (cmds.optionVar(query=WARN_ON_TAB_CLOSED) and
|
||||
not question('Warning', CLOSE_TAB_WARNING)):
|
||||
return
|
||||
|
||||
editor = self.editors.pop(index)
|
||||
if editor:
|
||||
editor.close()
|
||||
picker = self.pickers.pop(index)
|
||||
picker.unregister_callbacks()
|
||||
picker.close()
|
||||
self.tab.removeTab(index)
|
||||
if store:
|
||||
self.store_local_pickers_data()
|
||||
|
||||
def load_ui_states(self):
|
||||
value = bool(cmds.optionVar(query=DISPLAY_QUICK_OPTIONS))
|
||||
self.quick_options.setVisible(value)
|
||||
value = bool(cmds.optionVar(query=NAMESPACE_TOOLBAR))
|
||||
self.namespace_widget.setVisible(value)
|
||||
self.update_namespaces()
|
||||
self.update_modified_states()
|
||||
|
||||
def add_picker_from_file(self, filename):
|
||||
with open(filename, "r") as f:
|
||||
data = ensure_retro_compatibility(json.load(f))
|
||||
ensure_images_path_exists([data])
|
||||
self.add_picker(data, filename=filename)
|
||||
append_recent_filename(filename)
|
||||
|
||||
def reset(self):
|
||||
picker = self.tab.currentWidget()
|
||||
if picker:
|
||||
picker.reset()
|
||||
|
||||
def general_changed(self, origin, option):
|
||||
if option == 'name' and origin != 'main_window':
|
||||
self.update_names()
|
||||
if option == 'panels.as_sub_tab' and origin != 'main_window':
|
||||
index = int(self.document().data['general']['panels.as_sub_tab'])
|
||||
self.panel_buttons.button(index).setChecked(True)
|
||||
|
||||
def update_names(self):
|
||||
for i in range(self.tab.count()):
|
||||
self.set_title(i, self.document(i).data['general']['name'])
|
||||
|
||||
def create_picker(self, data):
|
||||
document = PickerDocument(data)
|
||||
document.changed.connect(self.store_local_pickers_data)
|
||||
document.general_option_changed.connect(self.general_changed)
|
||||
document.data_changed.connect(self.update_names)
|
||||
document.changed.connect(self.update_modified_states)
|
||||
picker = PickerStackedView(document, self.editable)
|
||||
picker.register_callbacks()
|
||||
return picker
|
||||
|
||||
def add_picker(self, data, filename=None, modified_state=False):
|
||||
picker = self.create_picker(data)
|
||||
picker.document.filename = filename
|
||||
picker.document.modified_state = modified_state
|
||||
insert = cmds.optionVar(query=INSERT_TAB_AFTER_CURRENT)
|
||||
if not insert or self.tab.currentIndex() == self.tab.count() - 1:
|
||||
self.pickers.append(picker)
|
||||
self.editors.append(None)
|
||||
self.tab.addTab(picker, data['general']['name'])
|
||||
self.tab.setCurrentIndex(self.tab.count() - 1)
|
||||
else:
|
||||
index = self.tab.currentIndex() + 1
|
||||
self.pickers.insert(index, picker)
|
||||
self.editors.insert(index, None)
|
||||
self.tab.insertTab(index, picker, data['general']['name'])
|
||||
self.tab.setCurrentIndex(index)
|
||||
picker.reset(force_all=True)
|
||||
|
||||
def call_open(self):
|
||||
filenames = QtWidgets.QFileDialog.getOpenFileNames(
|
||||
None, "Open a picker...",
|
||||
get_open_directory(),
|
||||
filter="Dreamwall Picker (*.json)")[0]
|
||||
if not filenames:
|
||||
return
|
||||
save_optionvar(LAST_OPEN_DIRECTORY, os.path.dirname(filenames[0]))
|
||||
for filename in filenames:
|
||||
self.add_picker_from_file(filename)
|
||||
self.store_local_pickers_data()
|
||||
|
||||
def call_preferences(self):
|
||||
self.preferences_window.show()
|
||||
|
||||
def call_save(self, index=None):
|
||||
index = self.tab.currentIndex() if type(index) is not int else index
|
||||
filename = self.document(index).filename
|
||||
if not filename:
|
||||
return self.call_save_as(index=index)
|
||||
return self.save_picker(index, filename)
|
||||
|
||||
def call_save_as(self, index=None):
|
||||
index = self.tab.currentIndex() if type(index) is not int else index
|
||||
filename = QtWidgets.QFileDialog.getSaveFileName(
|
||||
None, "Save a picker ...",
|
||||
cmds.optionVar(query=LAST_SAVE_DIRECTORY),
|
||||
filter="Dreamwall Picker (*.json)")[0]
|
||||
|
||||
if not filename:
|
||||
return False
|
||||
|
||||
if os.path.exists(filename):
|
||||
msg = '{} already, exists. Do you want to erase it ?'
|
||||
if not question('File exist', msg.format(filename)):
|
||||
return False
|
||||
|
||||
self.save_picker(index, filename)
|
||||
|
||||
def call_undo(self):
|
||||
index = self.tab.currentIndex()
|
||||
if index < 0:
|
||||
return
|
||||
self.document(index).undo()
|
||||
self.document(index).changed.emit()
|
||||
|
||||
def call_redo(self):
|
||||
index = self.tab.currentIndex()
|
||||
if index < 0:
|
||||
return
|
||||
self.document(index).redo()
|
||||
self.document(index).changed.emit()
|
||||
|
||||
def save_picker(self, index, filename):
|
||||
self.document(index).filename = filename
|
||||
save_optionvar(LAST_SAVE_DIRECTORY, os.path.dirname(filename))
|
||||
append_recent_filename(filename)
|
||||
with open(filename, 'w') as f:
|
||||
json.dump(self.document(index).data, f, indent=2)
|
||||
|
||||
self.set_modified_state(index, False)
|
||||
return True
|
||||
|
||||
def call_import(self):
|
||||
sources = QtWidgets.QFileDialog.getOpenFileNames(
|
||||
None, "Import a picker...",
|
||||
get_import_directory(),
|
||||
filter="Anim School Picker (*.pkr)")[0]
|
||||
if not sources:
|
||||
return
|
||||
|
||||
dst = QtWidgets.QFileDialog.getExistingDirectory(
|
||||
None,
|
||||
"Conversion destination",
|
||||
os.path.dirname(sources[0]),
|
||||
options=QtWidgets.QFileDialog.ShowDirsOnly)
|
||||
if not dst:
|
||||
return
|
||||
|
||||
save_optionvar(LAST_IMPORT_DIRECTORY, os.path.dirname(sources[0]))
|
||||
for src in sources:
|
||||
filename = animschool.convert(src, dst)
|
||||
self.add_picker_from_file(filename)
|
||||
|
||||
def call_new(self):
|
||||
self.add_picker({
|
||||
'general': deepcopy(PICKER),
|
||||
'shapes': []})
|
||||
self.store_local_pickers_data()
|
||||
|
||||
def document(self, index=None):
|
||||
index = self.tab.currentIndex() if type(index) is not int else index
|
||||
if index < 0:
|
||||
return None
|
||||
picker = self.tab.widget(index)
|
||||
return picker.document
|
||||
|
||||
def call_edit(self):
|
||||
index = self.tab.currentIndex()
|
||||
if index < 0:
|
||||
QtWidgets.QMessageBox.warning(self, "Warning", "No picker set")
|
||||
return
|
||||
if self.editors[index] is None:
|
||||
document = self.document()
|
||||
editor = PickerEditor(
|
||||
document,
|
||||
parent=self)
|
||||
self.editors[index] = editor
|
||||
|
||||
self.editors[index].show()
|
||||
self.editors[index].shape_canvas.focus()
|
||||
|
||||
def call_next_tab(self):
|
||||
index = self.tab.currentIndex() + 1
|
||||
if index == self.tab.count():
|
||||
index = 0
|
||||
self.tab.setCurrentIndex(index)
|
||||
|
||||
def call_previous_tab(self):
|
||||
index = self.tab.currentIndex() - 1
|
||||
if index < 0:
|
||||
index = self.tab.count() - 1
|
||||
self.tab.setCurrentIndex(index)
|
||||
|
||||
def set_editable(self, state):
|
||||
self.editable = state
|
||||
self.menubar.set_editable(state)
|
||||
for picker in self.pickers:
|
||||
picker.editable = state
|
||||
|
||||
def update_modified_states(self):
|
||||
for index, picker in enumerate(self.pickers):
|
||||
state = picker.document.modified_state
|
||||
use_icon = cmds.optionVar(query=USE_ICON_FOR_UNSAVED_TAB)
|
||||
icon_ = icon('save.png') if state and use_icon else QtGui.QIcon()
|
||||
self.tab.setTabIcon(index, icon_)
|
||||
title = self.document(index).data['general']['name']
|
||||
title = "*" + title if state and not use_icon else title
|
||||
self.tab.setTabText(index, title)
|
||||
|
||||
def set_modified_state(self, index, state):
|
||||
"""
|
||||
Update the tab icon. Add a "save" icon if tab contains unsaved
|
||||
modifications.
|
||||
"""
|
||||
if not self.document(index).filename:
|
||||
return
|
||||
self.document(index).modified_state = state
|
||||
use_icon = cmds.optionVar(query=USE_ICON_FOR_UNSAVED_TAB)
|
||||
icon_ = icon('save.png') if state and use_icon else QtGui.QIcon()
|
||||
self.tab.setTabIcon(index, icon_)
|
||||
title = self.document(index).data['general']['name']
|
||||
title = "*" + title if state and not use_icon else title
|
||||
self.tab.setTabText(index, title)
|
||||
|
||||
def call_tools(self):
|
||||
webbrowser.open(DW_GITHUB)
|
||||
|
||||
def call_dreamwall(self):
|
||||
webbrowser.open(DW_WEBSITE)
|
||||
|
||||
def call_documentation(self):
|
||||
webbrowser.open(PICKER_DOCUMENTATION)
|
||||
|
||||
def call_about(self):
|
||||
QtWidgets.QMessageBox.about(self, 'About', ABOUT)
|
||||
|
||||
def sizeHint(self):
|
||||
return QtCore.QSize(500, 800)
|
||||
|
||||
def change_title(self, index=None):
|
||||
if not self.editable:
|
||||
return
|
||||
index = (
|
||||
self.tab.currentIndex() if not isinstance(index, int) else index)
|
||||
if index < 0:
|
||||
return
|
||||
title, operate = QtWidgets.QInputDialog.getText(
|
||||
None, 'Change picker title', 'New title',
|
||||
text=self.document(index).data['general']['name'])
|
||||
|
||||
if not operate:
|
||||
return
|
||||
self.set_title(index, title)
|
||||
index = (
|
||||
self.tab.currentIndex() if not isinstance(index, int) else index)
|
||||
if index < 0:
|
||||
return
|
||||
document = self.document(index)
|
||||
document.data['general']['name'] = title
|
||||
document.general_option_changed.emit('main_window', 'name')
|
||||
self.document(index).record_undo()
|
||||
self.set_title(index, title)
|
||||
|
||||
def set_title(self, index=None, title=''):
|
||||
use_icon = cmds.optionVar(query=USE_ICON_FOR_UNSAVED_TAB)
|
||||
if not use_icon and self.document(index).modified_state:
|
||||
title = "*" + title
|
||||
self.tab.setTabText(index, title)
|
||||
|
||||
def change_namespace_dialog(self):
|
||||
dialog = NamespaceDialog()
|
||||
if not dialog.exec_():
|
||||
return
|
||||
namespace = dialog.namespace
|
||||
self.change_namespace(namespace)
|
||||
|
||||
def change_namespace_combo(self):
|
||||
index = self.namespace_combo.currentIndex()
|
||||
text = self.namespace_combo.currentText()
|
||||
namespace = text if index else ":"
|
||||
self.change_namespace(namespace)
|
||||
|
||||
def pick_namespace(self):
|
||||
namespace = selected_namespace()
|
||||
self.namespace_combo.setCurrentText(namespace)
|
||||
|
||||
def change_namespace(self, namespace):
|
||||
document = self.document()
|
||||
if not document:
|
||||
return
|
||||
switch_namespace_function = (
|
||||
self.replace_namespace_custom_function or switch_namespace)
|
||||
for shape in document.shapes:
|
||||
if not shape.targets():
|
||||
continue
|
||||
targets = [
|
||||
switch_namespace_function(t, namespace)
|
||||
for t in shape.targets()]
|
||||
shape.options['action.targets'] = targets
|
||||
document.record_undo()
|
||||
document.shapes_changed.emit()
|
||||
|
||||
def add_background(self):
|
||||
filename = get_image_path(self)
|
||||
if not filename:
|
||||
return
|
||||
|
||||
filename = format_path(filename)
|
||||
shape = deepcopy(BACKGROUND)
|
||||
shape['image.path'] = filename
|
||||
image = QtGui.QImage(filename)
|
||||
shape['image.width'] = image.size().width()
|
||||
shape['image.height'] = image.size().height()
|
||||
shape['shape.width'] = image.size().width()
|
||||
shape['shape.height'] = image.size().height()
|
||||
shape['bgcolor.transparency'] = 255
|
||||
self.document().add_shapes([shape], prepend=True)
|
||||
self.document().record_undo()
|
||||
self.document().shapes_changed.emit()
|
||||
|
||||
|
||||
class DwPickerMenu(QtWidgets.QMenuBar):
|
||||
def __init__(self, parent=None):
|
||||
super(DwPickerMenu, self).__init__(parent)
|
||||
self.new = QtWidgets.QAction('&New', parent)
|
||||
self.open = QtWidgets.QAction('&Open', parent)
|
||||
self.import_ = QtWidgets.QAction('&Import', parent)
|
||||
self.save = QtWidgets.QAction('&Save', parent)
|
||||
self.save_as = QtWidgets.QAction('&Save as', parent)
|
||||
self.exit = QtWidgets.QAction('Exit', parent)
|
||||
|
||||
self.undo = QtWidgets.QAction('Undo', parent)
|
||||
self.redo = QtWidgets.QAction('Redo', parent)
|
||||
|
||||
self.advanced_edit = QtWidgets.QAction('Advanced &editing', parent)
|
||||
self.preferences = QtWidgets.QAction('Preferences', parent)
|
||||
text = 'Toggle panel display mode'
|
||||
self.toggle_display = QtWidgets.QAction(text, parent)
|
||||
text = 'Toggle hierarchy display'
|
||||
self.toggle_hierarchy_display = QtWidgets.QAction(text, parent)
|
||||
self.change_title = QtWidgets.QAction('Change picker title', parent)
|
||||
self.change_namespace = QtWidgets.QAction('Change namespace', parent)
|
||||
self.add_background = QtWidgets.QAction('Add background item', parent)
|
||||
|
||||
self.documentation = QtWidgets.QAction('Documentation', parent)
|
||||
self.tools = QtWidgets.QAction('Other DreamWall &tools', parent)
|
||||
self.dw = QtWidgets.QAction('&About DreamWall', parent)
|
||||
self.about = QtWidgets.QAction('&About DwPicker', parent)
|
||||
|
||||
self.file = QtWidgets.QMenu('&File', parent)
|
||||
self.file.addAction(self.new)
|
||||
self.file.addAction(self.open)
|
||||
self.file.addAction(self.import_)
|
||||
self.file.addSeparator()
|
||||
self.file.addAction(self.save)
|
||||
self.file.addAction(self.save_as)
|
||||
self.file.addSeparator()
|
||||
self.file.addAction(self.exit)
|
||||
|
||||
self.edit = QtWidgets.QMenu('&Edit', parent)
|
||||
self.edit.addAction(self.undo)
|
||||
self.edit.addAction(self.redo)
|
||||
self.edit.addSeparator()
|
||||
self.edit.addAction(self.advanced_edit)
|
||||
self.edit.addAction(self.preferences)
|
||||
self.edit.addSeparator()
|
||||
self.edit.addAction(self.toggle_display)
|
||||
self.edit.addAction(self.toggle_hierarchy_display)
|
||||
self.edit.addSeparator()
|
||||
self.edit.addAction(self.change_title)
|
||||
self.edit.addSeparator()
|
||||
self.edit.addAction(self.change_namespace)
|
||||
self.edit.addAction(self.add_background)
|
||||
|
||||
self.help = QtWidgets.QMenu('&Help', parent)
|
||||
self.help.addAction(self.documentation)
|
||||
self.help.addSeparator()
|
||||
self.help.addAction(self.tools)
|
||||
self.help.addAction(self.dw)
|
||||
self.help.addSeparator()
|
||||
self.help.addAction(self.about)
|
||||
|
||||
self.addMenu(self.file)
|
||||
self.addMenu(self.edit)
|
||||
self.addMenu(self.help)
|
||||
|
||||
def set_editable(self, state):
|
||||
self.undo.setEnabled(state)
|
||||
self.redo.setEnabled(state)
|
||||
self.change_title.setEnabled(state)
|
||||
self.advanced_edit.setEnabled(state)
|
||||
self.add_background.setEnabled(state)
|
||||
71
2023/scripts/animation_tools/dwpicker/namespace.py
Normal file
@@ -0,0 +1,71 @@
|
||||
|
||||
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.document.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]
|
||||
|
||||
|
||||
def node_full_namespace(node):
|
||||
basename = node.split('|')[-1]
|
||||
return (basename.rsplit(':', 1)[:-1] or [None])[-1]
|
||||
|
||||
|
||||
@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]
|
||||
176
2023/scripts/animation_tools/dwpicker/optionvar.py
Normal file
@@ -0,0 +1,176 @@
|
||||
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_RESIZE_NAMESPACE_COMBO = 'dwpicker_auto_resize_namespace_combo'
|
||||
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'
|
||||
DISPLAY_HIERARCHY_IN_CANVAS = 'dwpicker_display_hierarchy_in_canvas'
|
||||
DISPLAY_HIERARCHY_IN_PICKER = 'dwpicker_display_hierarchy_in_picker'
|
||||
OVERRIDE_PROD_PICKER_DIRECTORY_ENV = 'dwpicker_override_picker_directory_env'
|
||||
INSERT_TAB_AFTER_CURRENT = 'dwpicker_insert_tab_after_current'
|
||||
ISOLATE_CURRENT_PANEL_SHAPES = 'dwpicker_isolate_current_panel_shapes'
|
||||
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'
|
||||
SHAPE_PATH_ROTATION_STEP_ANGLE = 'dwpicker_shape_path_rotation_step_angle'
|
||||
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'
|
||||
|
||||
|
||||
try:
|
||||
check_for_update = int(cmds.about(majorVersion=True) != '2023')
|
||||
# cmds.about command for Maya prio 2022 does not have majorVersion argument.
|
||||
except TypeError:
|
||||
check_for_update = 0
|
||||
|
||||
|
||||
OPTIONVARS = {
|
||||
AUTO_FOCUS_BEHAVIOR: AUTO_FOCUS_BEHAVIORS[-1],
|
||||
AUTO_SWITCH_TAB: 0,
|
||||
AUTO_RESIZE_NAMESPACE_COMBO: 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: check_for_update,
|
||||
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;toggle_display=T,1;display_hierarchy=Y,1'),
|
||||
DISPLAY_HIERARCHY_IN_CANVAS: 1,
|
||||
DEFAULT_WIDTH: 30,
|
||||
DISABLE_IMPORT_CALLBACKS: 1,
|
||||
DISPLAY_HIERARCHY_IN_PICKER: 1,
|
||||
DISPLAY_QUICK_OPTIONS: 1,
|
||||
OVERRIDE_PROD_PICKER_DIRECTORY_ENV: 0,
|
||||
INSERT_TAB_AFTER_CURRENT: 0,
|
||||
ISOLATE_CURRENT_PANEL_SHAPES: 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,
|
||||
SHAPE_PATH_ROTATION_STEP_ANGLE: 15,
|
||||
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)])
|
||||
347
2023/scripts/animation_tools/dwpicker/painting.py
Normal file
@@ -0,0 +1,347 @@
|
||||
from .pyside import QtCore, QtGui
|
||||
from maya import cmds
|
||||
|
||||
from .optionvar import ZOOM_SENSITIVITY
|
||||
from .qtutils import VALIGNS, HALIGNS
|
||||
from .geometry import grow_rect, get_connection_path
|
||||
from .shape import to_shape_space_rect, to_shape_space
|
||||
from .viewport import ViewportMapper
|
||||
|
||||
|
||||
SELECTION_COLOR = '#3388FF'
|
||||
PANEL_COLOR = '#00FFFF'
|
||||
FOCUS_COLOR = '#FFFFFF'
|
||||
MANIPULATOR_BORDER = 5
|
||||
CONNECTION_COLOR = '#666666'
|
||||
|
||||
|
||||
def factor_sensitivity(factor):
|
||||
sensitivity = cmds.optionVar(query=ZOOM_SENSITIVITY) / 50.0
|
||||
return factor * sensitivity
|
||||
|
||||
|
||||
def draw_world_coordinates(painter, rect, color, viewportmapper):
|
||||
center = viewportmapper.to_viewport_coords(QtCore.QPoint(0, 0))
|
||||
top_center = QtCore.QPointF(center.x(), rect.top())
|
||||
bottom_center = QtCore.QPointF(center.x(), rect.bottom())
|
||||
left_center = QtCore.QPointF(rect.left(), center.y())
|
||||
right_center = QtCore.QPointF(rect.right(), center.y())
|
||||
|
||||
color.setAlpha(100)
|
||||
pen = QtGui.QPen(color)
|
||||
pen.setWidthF(2)
|
||||
painter.setPen(pen)
|
||||
painter.drawLine(top_center, bottom_center)
|
||||
painter.drawLine(left_center, right_center)
|
||||
|
||||
|
||||
def draw_parenting_shapes(
|
||||
painter, child, potential_parent, cursor, viewportmapper):
|
||||
draw_shape_as_child_background(
|
||||
painter, child, 'yellow',
|
||||
alpha=150, padding=3, pen_width=5,
|
||||
viewportmapper=viewportmapper)
|
||||
if potential_parent:
|
||||
draw_shape_as_child_background(
|
||||
painter, potential_parent, 'white', alpha=255, padding=3,
|
||||
pen_width=5,
|
||||
viewportmapper=viewportmapper)
|
||||
start_point = potential_parent.bounding_rect().center()
|
||||
end_point = child.bounding_rect().center()
|
||||
path = get_connection_path(start_point, end_point, viewportmapper)
|
||||
draw_connections(painter, path, 'white')
|
||||
return
|
||||
end_point = child.bounding_rect().center()
|
||||
start_point = viewportmapper.to_units_coords(cursor)
|
||||
path = get_connection_path(
|
||||
start_point, end_point, viewportmapper=viewportmapper)
|
||||
pen = QtGui.QPen('yellow')
|
||||
pen.setWidthF(2)
|
||||
pen.setJoinStyle(QtCore.Qt.MiterJoin)
|
||||
painter.setPen(pen)
|
||||
painter.setBrush(QtGui.QColor(CONNECTION_COLOR))
|
||||
painter.drawPath(path)
|
||||
|
||||
|
||||
def draw_connections(painter, path, color=None):
|
||||
pen = QtGui.QPen(color or CONNECTION_COLOR)
|
||||
pen.setWidthF(1.5)
|
||||
pen.setJoinStyle(QtCore.Qt.MiterJoin)
|
||||
painter.setPen(pen)
|
||||
painter.setBrush(QtGui.QColor(CONNECTION_COLOR))
|
||||
painter.drawPath(path)
|
||||
|
||||
|
||||
def draw_editor_canvas(painter, rect, snap=None, viewportmapper=None):
|
||||
viewportmapper = viewportmapper or ViewportMapper()
|
||||
color = QtGui.QColor('#333333')
|
||||
pen = QtGui.QPen(color)
|
||||
pen.setWidthF(2)
|
||||
brush = QtGui.QBrush(QtGui.QColor(255, 255, 255, 25))
|
||||
painter.setPen(pen)
|
||||
painter.setBrush(brush)
|
||||
painter.drawRect(rect)
|
||||
|
||||
draw_world_coordinates(painter, rect, color, viewportmapper)
|
||||
center = viewportmapper.to_viewport_coords(QtCore.QPoint(0, 0))
|
||||
|
||||
text = QtGui.QStaticText('bottom_right')
|
||||
x = center.x() - text.size().width() - 4
|
||||
y = center.y() - text.size().height() - 4
|
||||
point = QtCore.QPointF(x, y)
|
||||
painter.drawStaticText(point, text)
|
||||
|
||||
text = QtGui.QStaticText('bottom_left')
|
||||
y = center.y() - text.size().height() - 4
|
||||
point = QtCore.QPointF(center.x() + 4, y)
|
||||
painter.drawStaticText(point, text)
|
||||
|
||||
text = QtGui.QStaticText('top_right')
|
||||
x = center.x() - text.size().width() - 4
|
||||
point = QtCore.QPointF(x, center.y() + 4)
|
||||
painter.drawStaticText(point, text)
|
||||
|
||||
text = QtGui.QStaticText('top_left')
|
||||
point = QtCore.QPointF(center.x() + 4, center.y() + 4)
|
||||
painter.drawStaticText(point, text)
|
||||
|
||||
if snap is None:
|
||||
return
|
||||
|
||||
if viewportmapper.zoom < 0.5:
|
||||
snap = snap[0] * 2, snap[1] * 2
|
||||
|
||||
pen = QtGui.QPen(QtGui.QColor('red'))
|
||||
pen.setWidth(
|
||||
1 if viewportmapper.zoom < 1 else 2 if
|
||||
viewportmapper.zoom < 3 else 3)
|
||||
painter.setPen(pen)
|
||||
rect = viewportmapper.to_units_rect(rect)
|
||||
x_start = ((rect.left() // snap[0]) * snap[0])
|
||||
if x_start < rect.left():
|
||||
x_start += snap[0]
|
||||
|
||||
y_start = ((rect.top() // snap[1]) * snap[1])
|
||||
if y_start < rect.top():
|
||||
y_start += snap[1]
|
||||
|
||||
x = x_start
|
||||
while x <= rect.right():
|
||||
if x >= rect.left():
|
||||
y = y_start
|
||||
while y <= rect.bottom():
|
||||
if y >= rect.top():
|
||||
point = QtCore.QPoint(*(x, y))
|
||||
painter.drawPoint(viewportmapper.to_viewport_coords(point))
|
||||
y += snap[1]
|
||||
x += snap[0]
|
||||
|
||||
|
||||
def draw_shape_as_child_background(
|
||||
painter, shape, color=None, padding=5, pen_width=1.5, alpha=30,
|
||||
viewportmapper=None):
|
||||
rect = viewportmapper.to_viewport_rect(shape.bounding_rect())
|
||||
rect = grow_rect(rect, padding)
|
||||
color = QtGui.QColor(color or 'yellow')
|
||||
color.setAlpha(alpha)
|
||||
pen = QtGui.QPen(color)
|
||||
pen.setWidthF(pen_width)
|
||||
pen.setStyle(QtCore.Qt.DashLine)
|
||||
painter.setPen(pen)
|
||||
brush = QtGui.QBrush(color)
|
||||
brush.setStyle(QtCore.Qt.BDiagPattern)
|
||||
painter.setBrush(brush)
|
||||
painter.drawRect(rect)
|
||||
|
||||
|
||||
def draw_shape(
|
||||
painter, shape, force_world_space=True,
|
||||
draw_selected_state=True, viewportmapper=None):
|
||||
|
||||
viewportmapper = viewportmapper or ViewportMapper()
|
||||
options = shape.options
|
||||
content_rect = shape.content_rect()
|
||||
if shape.clicked or (shape.selected and draw_selected_state):
|
||||
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)
|
||||
w = to_shape_space(bordersize, shape, force_world_space, viewportmapper)
|
||||
pen.setWidthF(w)
|
||||
painter.setPen(pen)
|
||||
painter.setBrush(QtGui.QBrush(backgroundcolor))
|
||||
rect = to_shape_space_rect(
|
||||
shape.rect, shape, force_world_space, viewportmapper)
|
||||
r = draw_shape_shape(
|
||||
painter, rect, shape, force_world_space, viewportmapper)
|
||||
|
||||
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 = to_shape_space(
|
||||
options['text.size'], shape, force_world_space, viewportmapper)
|
||||
font.setPixelSize(round(size))
|
||||
painter.setFont(font)
|
||||
text = options['text.content']
|
||||
|
||||
content_rect = to_shape_space_rect(
|
||||
content_rect, shape, force_world_space, viewportmapper)
|
||||
painter.drawText(content_rect, flags, text)
|
||||
return r
|
||||
|
||||
|
||||
def draw_shape_shape(painter, rect, shape, force_world_space, viewportmapper):
|
||||
options = shape.options
|
||||
content_rect = shape.content_rect()
|
||||
qpath = QtGui.QPainterPath()
|
||||
|
||||
if options['shape'] == 'square':
|
||||
painter.drawRect(rect)
|
||||
qpath.addRect(rect)
|
||||
|
||||
elif options['shape'] == 'round':
|
||||
painter.drawEllipse(rect)
|
||||
qpath.addEllipse(rect)
|
||||
|
||||
elif options['shape'] == 'rounded_rect':
|
||||
x = to_shape_space(
|
||||
options['shape.cornersx'], shape, force_world_space,
|
||||
viewportmapper)
|
||||
y = to_shape_space(
|
||||
options['shape.cornersy'], shape, force_world_space,
|
||||
viewportmapper)
|
||||
painter.drawRoundedRect(rect, x, y)
|
||||
qpath.addRoundedRect(rect, x, y)
|
||||
|
||||
else:
|
||||
qpath = shape.get_painter_path(force_world_space, viewportmapper)
|
||||
painter.drawPath(qpath)
|
||||
qpath = qpath
|
||||
|
||||
if shape.pixmap is not None:
|
||||
painter.setClipPath(qpath)
|
||||
transformed_rect = shape.image_rect or content_rect
|
||||
transformed_rect = to_shape_space_rect(
|
||||
transformed_rect, shape, force_world_space, viewportmapper)
|
||||
painter.drawPixmap(transformed_rect.toRect(), shape.pixmap)
|
||||
painter.setClipping(False)
|
||||
return qpath
|
||||
|
||||
|
||||
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_picker_focus(painter, rect):
|
||||
color = QtGui.QColor(FOCUS_COLOR)
|
||||
color.setAlpha(10)
|
||||
pen = QtGui.QPen(color)
|
||||
pen.setWidthF(4)
|
||||
painter.setPen(pen)
|
||||
painter.setBrush(QtCore.Qt.NoBrush)
|
||||
painter.drawRect(rect)
|
||||
painter.setBrush(QtGui.QBrush())
|
||||
|
||||
|
||||
def draw_current_panel(painter, rect, viewportmapper=None):
|
||||
viewportmapper = viewportmapper or ViewportMapper()
|
||||
rect = viewportmapper.to_viewport_rect(rect)
|
||||
color = QtGui.QColor(PANEL_COLOR)
|
||||
color.setAlpha(30)
|
||||
pen = QtGui.QPen(color)
|
||||
pen.setWidthF(1.5)
|
||||
pen.setStyle(QtCore.Qt.DashLine)
|
||||
painter.setPen(pen)
|
||||
brush = QtGui.QBrush(color)
|
||||
brush.setStyle(QtCore.Qt.BDiagPattern)
|
||||
painter.setBrush(brush)
|
||||
painter.drawRect(rect)
|
||||
|
||||
|
||||
def draw_manipulator(painter, manipulator, cursor, viewportmapper=None):
|
||||
viewportmapper = viewportmapper or ViewportMapper()
|
||||
cursor = viewportmapper.to_units_coords(cursor).toPoint()
|
||||
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)
|
||||
rect = viewportmapper.to_viewport_rect(manipulator.rect)
|
||||
painter.drawPath(get_hovered_path(rect))
|
||||
|
||||
pen = QtGui.QPen(QtGui.QColor('black'))
|
||||
brush = QtGui.QBrush(QtGui.QColor('white'))
|
||||
painter.setBrush(brush)
|
||||
for rect in manipulator.viewport_handlers():
|
||||
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
|
||||
|
||||
|
||||
def draw_tangents(painter, path, viewportmapper):
|
||||
rect = QtCore.QRectF(0, 0, 6, 6)
|
||||
painter.setBrush(QtCore.Qt.yellow)
|
||||
painter.setPen(QtCore.Qt.yellow)
|
||||
for point in path:
|
||||
center = QtCore.QPointF(*point['point'])
|
||||
center = viewportmapper.to_viewport_coords(center)
|
||||
if point['tangent_in'] is not None:
|
||||
tangent_in = QtCore.QPointF(*point['tangent_in'])
|
||||
tangent_in = viewportmapper.to_viewport_coords(tangent_in)
|
||||
rect.moveCenter(tangent_in)
|
||||
painter.drawRect(rect)
|
||||
painter.drawLine(tangent_in, center)
|
||||
if point['tangent_out'] is not None:
|
||||
tangent_out = QtCore.QPointF(*point['tangent_out'])
|
||||
tangent_out = viewportmapper.to_viewport_coords(tangent_out)
|
||||
rect.moveCenter(tangent_out)
|
||||
painter.drawRect(rect)
|
||||
painter.drawLine(tangent_out, center)
|
||||
80
2023/scripts/animation_tools/dwpicker/path.py
Normal file
@@ -0,0 +1,80 @@
|
||||
|
||||
|
||||
import os
|
||||
from maya import cmds
|
||||
from .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):
|
||||
path = cmds.optionVar(query=CUSTOM_PROD_PICKER_DIRECTORY)
|
||||
return unix_path(path) if path else None
|
||||
path = os.getenv('DWPICKER_PROJECT_DIRECTORY')
|
||||
return unix_path(path) if path else None
|
||||
|
||||
|
||||
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)
|
||||
810
2023/scripts/animation_tools/dwpicker/picker.py
Normal file
@@ -0,0 +1,810 @@
|
||||
from copy import deepcopy
|
||||
from functools import partial
|
||||
|
||||
from maya import cmds
|
||||
import maya.OpenMaya as om
|
||||
from .pyside import QtWidgets, QtGui, QtCore
|
||||
|
||||
from .align import align_shapes_on_line
|
||||
from .compatibility import ensure_general_options_sanity
|
||||
from .document import PickerDocument
|
||||
from .dialog import warning, CommandEditorDialog
|
||||
from .interactive import SelectionSquare
|
||||
from .interactionmanager import InteractionManager
|
||||
from .geometry import get_combined_rects, get_connection_path
|
||||
from .languages import execute_code
|
||||
from .optionvar import (
|
||||
save_optionvar, DEFAULT_BG_COLOR, DEFAULT_TEXT_COLOR, DEFAULT_WIDTH,
|
||||
DEFAULT_HEIGHT, DEFAULT_LABEL, DISPLAY_HIERARCHY_IN_PICKER,
|
||||
LAST_COMMAND_LANGUAGE, SYNCHRONYZE_SELECTION, ZOOM_SENSITIVITY)
|
||||
from .painting import (
|
||||
draw_shape, draw_selection_square, draw_picker_focus, draw_connections)
|
||||
from .qtutils import get_cursor, clear_layout
|
||||
from .shape import (
|
||||
build_multiple_shapes, cursor_in_shape, rect_intersects_shape)
|
||||
from .stack import create_stack_splitters, count_panels
|
||||
from .selection import (
|
||||
select_targets, select_shapes_from_selection, get_selection_mode,
|
||||
NameclashError)
|
||||
from .templates import BUTTON, COMMAND
|
||||
from .viewport import ViewportMapper
|
||||
|
||||
|
||||
SPLITTER_STYLE = """\
|
||||
QSplitter::handle {
|
||||
background-color: rgba(0, 0, 0, 50);
|
||||
border: 1px solid #444;
|
||||
width: 2px;
|
||||
height: 2px;
|
||||
}
|
||||
"""
|
||||
|
||||
|
||||
def set_shapes_hovered(
|
||||
shapes,
|
||||
world_cursor,
|
||||
viewport_cursor,
|
||||
selection_rect,
|
||||
viewport_selection_rect,
|
||||
viewportmapper=None):
|
||||
"""
|
||||
It set hovered the shape if his rect contains the cursor.
|
||||
"""
|
||||
if not shapes:
|
||||
return
|
||||
world_cursor = world_cursor.toPoint()
|
||||
shapes = [s for s in shapes if not s.is_background()]
|
||||
selection_shapes_intersect_selection = [
|
||||
s for s in shapes
|
||||
if cursor_in_shape(s, world_cursor, viewport_cursor, False, viewportmapper)
|
||||
or rect_intersects_shape(
|
||||
shape=s,
|
||||
unit_rect=selection_rect,
|
||||
viewport_rect=viewport_selection_rect,
|
||||
force_world_space=False,
|
||||
viewportmapper=viewportmapper)]
|
||||
targets = list_targets(selection_shapes_intersect_selection)
|
||||
for s in shapes:
|
||||
if s.targets():
|
||||
# Set all buttons hovered from his targets contents.
|
||||
# I the physically hovered buttons contains targets, this will
|
||||
# highlight all buttons containing similare targets.
|
||||
state = next((False for t in s.targets() if t not in targets), True)
|
||||
elif not s.is_background():
|
||||
# Simple highlighting method for the interactive buttons.
|
||||
state = s in selection_shapes_intersect_selection
|
||||
else:
|
||||
state = False
|
||||
s.hovered = state
|
||||
|
||||
|
||||
def detect_hovered_shape(shapes, world_cursor, screen_cursor, viewportmapper):
|
||||
if not shapes:
|
||||
return
|
||||
for shape in reversed(shapes):
|
||||
hovered = cursor_in_shape(
|
||||
shape=shape,
|
||||
world_cursor=world_cursor,
|
||||
viewpoert_cursor=screen_cursor,
|
||||
force_world_space=False,
|
||||
viewportmapper=viewportmapper)
|
||||
if hovered and not shape.is_background():
|
||||
return shape
|
||||
|
||||
|
||||
def list_targets(shapes):
|
||||
return {t for s in shapes for t in s.targets()}
|
||||
|
||||
|
||||
class PickerStackedView(QtWidgets.QWidget):
|
||||
|
||||
def __init__(self, document=None, editable=True, parent=None):
|
||||
super(PickerStackedView, self).__init__(parent)
|
||||
self.document = document or PickerDocument.create()
|
||||
mtd = self.general_option_changed
|
||||
self.document.general_option_changed.connect(mtd)
|
||||
self.document.data_changed.connect(self.full_refresh)
|
||||
self.editable = editable
|
||||
self.pickers = []
|
||||
self.widget = None
|
||||
self.last_selected_tab = None
|
||||
|
||||
self.layers_menu = VisibilityLayersMenu(document)
|
||||
self.layers_menu.visibilities_changed.connect(self.update)
|
||||
|
||||
self.as_sub_tab = document.data['general']['panels.as_sub_tab']
|
||||
self.layout = QtWidgets.QHBoxLayout(self)
|
||||
self.layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.setStyleSheet(SPLITTER_STYLE)
|
||||
self.create_pickers()
|
||||
self.create_panels()
|
||||
|
||||
def register_callbacks(self):
|
||||
for picker in self.pickers:
|
||||
picker.register_callbacks()
|
||||
|
||||
def unregister_callbacks(self):
|
||||
for picker in self.pickers:
|
||||
picker.unregister_callbacks()
|
||||
|
||||
def reset(self, force_all=False):
|
||||
if not force_all and not isinstance(self.widget, QtWidgets.QTabWidget):
|
||||
for picker in self.pickers:
|
||||
if picker.rect().contains(get_cursor(picker)):
|
||||
picker.reset()
|
||||
return picker.panel
|
||||
|
||||
elif not force_all:
|
||||
picker = self.pickers[self.widget.currentIndex()]
|
||||
picker.reset()
|
||||
return
|
||||
|
||||
# If no picker hovered, focus all.
|
||||
if self.document.data['general']['panels.as_sub_tab']:
|
||||
viewsize = self.pickers[0].viewportmapper.viewsize
|
||||
else:
|
||||
viewsize = None
|
||||
for picker in self.pickers:
|
||||
picker.reset(viewsize)
|
||||
|
||||
def create_pickers(self):
|
||||
self.unregister_callbacks()
|
||||
self.pickers = [
|
||||
PickerPanelView(
|
||||
self.document, self.editable, i, self.layers_menu, self)
|
||||
for i in range(self.document.panel_count())]
|
||||
for picker in self.pickers:
|
||||
picker.size_event_triggered.connect(self.picker_resized)
|
||||
self.register_callbacks()
|
||||
|
||||
def picker_resized(self, event):
|
||||
data = self.document.data
|
||||
if not data['general']['panels.as_sub_tab']:
|
||||
return
|
||||
for i, picker in enumerate(self.pickers):
|
||||
if i == self.widget.currentIndex():
|
||||
continue
|
||||
picker.adjust_center(event.size(), event.oldSize())
|
||||
|
||||
def copy_pickers(self):
|
||||
self.pickers = [p.copy() for p in self.pickers]
|
||||
for picker in self.pickers:
|
||||
picker.size_event_triggered.connect(self.picker_resized)
|
||||
|
||||
def create_panels(self, panel=None):
|
||||
data = self.document.data
|
||||
if not self.as_sub_tab:
|
||||
panels = data['general']['panels']
|
||||
orientation = data['general']['panels.orientation']
|
||||
self.widget = create_stack_splitters(
|
||||
panels, self.pickers, orientation)
|
||||
else:
|
||||
self.widget = QtWidgets.QTabWidget()
|
||||
names = data['general']['panels.names']
|
||||
for picker, name in zip(self.pickers, names):
|
||||
self.widget.addTab(picker, name)
|
||||
|
||||
# Check "if panel is not None" (0 is a valid value,
|
||||
# so "if panel" would be incorrect)
|
||||
if panel is not None:
|
||||
self.widget.setCurrentIndex(panel)
|
||||
self.last_selected_tab = panel
|
||||
elif self.last_selected_tab:
|
||||
self.widget.setCurrentIndex(self.last_selected_tab)
|
||||
self.widget.currentChanged.connect(self.on_tab_changed)
|
||||
|
||||
clear_layout(self.layout)
|
||||
self.layout.addWidget(self.widget)
|
||||
|
||||
def on_tab_changed(self, index):
|
||||
self.last_selected_tab = index
|
||||
|
||||
def full_refresh(self):
|
||||
panels = self.document.data['general']['panels']
|
||||
if count_panels(panels) != len(self.pickers):
|
||||
self.create_pickers()
|
||||
self.create_panels()
|
||||
|
||||
def general_option_changed(self, _, option):
|
||||
value = self.document.data['general'][option]
|
||||
panels = self.document.data['general']['panels']
|
||||
reset = False
|
||||
if option == 'panels.as_sub_tab':
|
||||
state = self.document.data['general']['panels.as_sub_tab']
|
||||
self.as_sub_tab = state
|
||||
|
||||
if option in ('panels', 'panels.orientation', 'panels.as_sub_tab'):
|
||||
ensure_general_options_sanity(self.document.data['general'])
|
||||
if count_panels(panels) != len(self.pickers):
|
||||
self.create_pickers()
|
||||
reset = True
|
||||
else:
|
||||
self.copy_pickers()
|
||||
reset = option in ('panels.orientation', 'panels.as_sub_tab')
|
||||
self.create_panels()
|
||||
|
||||
if option == 'panels.names' and self.as_sub_tab:
|
||||
for i, name in enumerate(value):
|
||||
self.widget.setTabText(i, name)
|
||||
|
||||
if option == 'hidden_layers':
|
||||
self.layers_menu.hidden_layers = value
|
||||
|
||||
if reset:
|
||||
QtCore.QTimer.singleShot(0, partial(self.reset, force_all=True))
|
||||
self.update()
|
||||
|
||||
def set_auto_center(self, state):
|
||||
for picker in self.pickers:
|
||||
picker.auto_center = state
|
||||
|
||||
|
||||
class PickerPanelView(QtWidgets.QWidget):
|
||||
size_event_triggered = QtCore.Signal(object)
|
||||
|
||||
def __init__(
|
||||
self, document, editable=True, panel=0, layers_menu=None,
|
||||
parent=None):
|
||||
super(PickerPanelView, self).__init__(parent)
|
||||
self._shown = False
|
||||
|
||||
self.document = document
|
||||
self.document.shapes_changed.connect(self.update)
|
||||
self.callbacks = []
|
||||
self.panel = panel
|
||||
self.auto_center = True
|
||||
self.editable = editable
|
||||
self.interaction_manager = InteractionManager()
|
||||
self.viewportmapper = ViewportMapper()
|
||||
self.selection_square = SelectionSquare()
|
||||
self.layers_menu = layers_menu
|
||||
self.setMouseTracking(True)
|
||||
self.clicked_shape = None
|
||||
self.drag_shapes = []
|
||||
|
||||
def copy(self):
|
||||
self.unregister_callbacks()
|
||||
picker = PickerPanelView(
|
||||
self.document, self.editable, self.panel, self.layers_menu)
|
||||
picker.register_callbacks()
|
||||
picker.viewportmapper = self.viewportmapper
|
||||
picker.register_callbacks()
|
||||
picker.auto_center = self.auto_center
|
||||
self.deleteLater()
|
||||
return picker
|
||||
|
||||
def showEvent(self, event):
|
||||
if self._shown:
|
||||
return super(PickerPanelView, self).showEvent(event)
|
||||
self._shown = True
|
||||
self.reset(self.size(), selection_only=False)
|
||||
|
||||
@property
|
||||
def zoom_locked(self):
|
||||
return self.document.data['general']['panels.zoom_locked'][self.panel]
|
||||
|
||||
def register_callbacks(self):
|
||||
self.unregister_callbacks()
|
||||
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
|
||||
shapes = self.document.shapes_by_panel[self.panel]
|
||||
select_shapes_from_selection(shapes)
|
||||
self.update()
|
||||
|
||||
def visible_shapes(self):
|
||||
return [
|
||||
s for s in self.document.shapes_by_panel[self.panel] if
|
||||
not s.visibility_layer()
|
||||
or s.visibility_layer() not in self.layers_menu.hidden_layers]
|
||||
|
||||
def reset(self, viewsize=None, selection_only=True):
|
||||
shapes = [
|
||||
s for s in self.visible_shapes() if
|
||||
s.options['shape.space'] == 'world' and not
|
||||
s.options['shape.ignored_by_focus']]
|
||||
shapes_rects = [
|
||||
s.bounding_rect() for s in shapes if
|
||||
not selection_only or s.selected]
|
||||
if not shapes_rects:
|
||||
shapes_rects = [s.bounding_rect() for s in shapes]
|
||||
if not shapes_rects:
|
||||
self.update()
|
||||
return
|
||||
self.viewportmapper.viewsize = viewsize or self.size()
|
||||
rect = get_combined_rects(shapes_rects)
|
||||
if self.zoom_locked:
|
||||
self.viewportmapper.zoom = 1
|
||||
x = rect.center().x() - (self.size().width() / 2)
|
||||
y = rect.center().y() - (self.size().height() / 2)
|
||||
self.viewportmapper.origin = QtCore.QPointF(x, y)
|
||||
else:
|
||||
self.viewportmapper.focus(rect)
|
||||
self.update()
|
||||
|
||||
def resizeEvent(self, event):
|
||||
if not self.auto_center or event.oldSize() == QtCore.QSize(-1, -1):
|
||||
return
|
||||
self.adjust_center(event.size(), event.oldSize())
|
||||
self.size_event_triggered.emit(event)
|
||||
|
||||
def adjust_center(self, size, old_size):
|
||||
self.viewportmapper.viewsize = self.size()
|
||||
size = (size - old_size) / 2
|
||||
offset = QtCore.QPointF(size.width(), size.height())
|
||||
self.viewportmapper.origin -= offset
|
||||
self.update()
|
||||
|
||||
def enterEvent(self, _):
|
||||
self.update()
|
||||
|
||||
def leaveEvent(self, _):
|
||||
for shape in self.visible_shapes():
|
||||
shape.hovered = False
|
||||
self.update()
|
||||
|
||||
def mousePressEvent(self, event):
|
||||
self.setFocus(QtCore.Qt.MouseFocusReason)
|
||||
if self.drag_shapes and event.button() == QtCore.Qt.LeftButton:
|
||||
pos = self.viewportmapper.to_units_coords(event.pos())
|
||||
align_shapes_on_line(self.drag_shapes, pos, pos)
|
||||
|
||||
world_cursor = self.viewportmapper.to_units_coords(event.pos())
|
||||
shapes = self.visible_shapes()
|
||||
self.clicked_shape = detect_hovered_shape(
|
||||
shapes=shapes,
|
||||
world_cursor=world_cursor.toPoint(),
|
||||
screen_cursor=event.pos(),
|
||||
viewportmapper=self.viewportmapper)
|
||||
|
||||
shapes = self.document.shapes_by_panel[self.panel]
|
||||
hsh = any(s.hovered for s in shapes)
|
||||
self.interaction_manager.update(
|
||||
event,
|
||||
pressed=True,
|
||||
has_shape_hovered=hsh,
|
||||
dragging=bool(self.drag_shapes))
|
||||
|
||||
def mouseDoubleClickEvent(self, event):
|
||||
world_cursor = self.viewportmapper.to_units_coords(event.pos())
|
||||
shapes = self.visible_shapes()
|
||||
clicked_shape = detect_hovered_shape(
|
||||
shapes=shapes,
|
||||
world_cursor=world_cursor.toPoint(),
|
||||
screen_cursor=event.pos(),
|
||||
viewportmapper=self.viewportmapper)
|
||||
|
||||
if not clicked_shape or event.button() != QtCore.Qt.LeftButton:
|
||||
return
|
||||
|
||||
shift = self.interaction_manager.shift_pressed
|
||||
ctrl = self.interaction_manager.ctrl_pressed
|
||||
selection_mode = get_selection_mode(shift=shift, ctrl=ctrl)
|
||||
shapes = self.document.all_children(clicked_shape.options['id'])
|
||||
for shape in shapes:
|
||||
shape.hovered = True
|
||||
select_targets(self.visible_shapes(), selection_mode=selection_mode)
|
||||
|
||||
def mouseReleaseEvent(self, event):
|
||||
shift = self.interaction_manager.shift_pressed
|
||||
ctrl = self.interaction_manager.ctrl_pressed
|
||||
selection_mode = get_selection_mode(shift=shift, ctrl=ctrl)
|
||||
world_cursor = self.viewportmapper.to_units_coords(event.pos())
|
||||
zoom = self.interaction_manager.zoom_button_pressed
|
||||
shapes = self.visible_shapes()
|
||||
|
||||
hovered_shape = detect_hovered_shape(
|
||||
shapes=shapes,
|
||||
world_cursor=world_cursor.toPoint(),
|
||||
screen_cursor=event.pos(),
|
||||
viewportmapper=self.viewportmapper)
|
||||
|
||||
interact = (
|
||||
self.clicked_shape and
|
||||
self.clicked_shape is hovered_shape and
|
||||
self.clicked_shape.is_interactive())
|
||||
|
||||
if zoom and self.interaction_manager.alt_pressed:
|
||||
self.release(event)
|
||||
return
|
||||
|
||||
if self.interaction_manager.mode == InteractionManager.DRAGGING:
|
||||
self.add_drag_shapes()
|
||||
self.release(event)
|
||||
return
|
||||
|
||||
elif self.interaction_manager.mode == InteractionManager.SELECTION and not interact:
|
||||
try:
|
||||
select_targets(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.interaction_manager.right_click_pressed:
|
||||
self.call_context_menu()
|
||||
|
||||
elif self.clicked_shape is hovered_shape:
|
||||
show_context = (
|
||||
self.interaction_manager.right_click_pressed and
|
||||
not self.clicked_shape.has_right_click_command())
|
||||
left_clicked = self.interaction_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.interaction_manager.left_click_pressed
|
||||
else 'right')
|
||||
self.clicked_shape.execute(
|
||||
button=button,
|
||||
ctrl=self.interaction_manager.ctrl_pressed,
|
||||
shift=self.interaction_manager.shift_pressed)
|
||||
|
||||
self.release(event)
|
||||
|
||||
def add_drag_shapes(self):
|
||||
shapes_data = [s.options for s in self.drag_shapes]
|
||||
self.document.add_shapes(shapes_data, hierarchize=True)
|
||||
self.document.shapes_changed.emit()
|
||||
self.document.record_undo()
|
||||
self.drag_shapes = []
|
||||
|
||||
def release(self, event):
|
||||
self.interaction_manager.update(event, pressed=False)
|
||||
self.selection_square.release()
|
||||
self.clicked_shape = None
|
||||
self.update()
|
||||
|
||||
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.update()
|
||||
|
||||
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):
|
||||
world_cursor=self.viewportmapper.to_units_coords(event.pos())
|
||||
selection_rect = (
|
||||
self.selection_square.rect or
|
||||
QtCore.QRectF(world_cursor, world_cursor))
|
||||
unit_selection_rect = self.viewportmapper.to_units_rect(selection_rect)
|
||||
unit_selection_rect = unit_selection_rect.toRect()
|
||||
|
||||
set_shapes_hovered(
|
||||
shapes=self.visible_shapes(),
|
||||
world_cursor=world_cursor,
|
||||
viewport_cursor=event.pos(),
|
||||
selection_rect=unit_selection_rect,
|
||||
viewport_selection_rect=selection_rect,
|
||||
viewportmapper=self.viewportmapper)
|
||||
|
||||
if self.interaction_manager.mode == InteractionManager.DRAGGING:
|
||||
point1 = self.viewportmapper.to_units_coords(
|
||||
self.interaction_manager.anchor)
|
||||
point2 = self.viewportmapper.to_units_coords(event.pos())
|
||||
align_shapes_on_line(self.drag_shapes, point1, point2)
|
||||
return self.update()
|
||||
|
||||
elif self.interaction_manager.mode == InteractionManager.SELECTION:
|
||||
if not self.selection_square.handeling:
|
||||
self.selection_square.clicked(event.pos())
|
||||
self.selection_square.handle(event.pos())
|
||||
return self.update()
|
||||
|
||||
elif self.interaction_manager.mode == InteractionManager.ZOOMING:
|
||||
if self.zoom_locked:
|
||||
return self.update()
|
||||
offset = self.interaction_manager.mouse_offset(event.pos())
|
||||
if offset is not None and self.interaction_manager.zoom_anchor:
|
||||
sensitivity = float(cmds.optionVar(query=ZOOM_SENSITIVITY))
|
||||
factor = (offset.x() + offset.y()) / sensitivity
|
||||
self.zoom(factor, self.interaction_manager.zoom_anchor)
|
||||
return self.update()
|
||||
|
||||
elif self.interaction_manager.mode == InteractionManager.NAVIGATION:
|
||||
offset = self.interaction_manager.mouse_offset(event.pos())
|
||||
if offset is not None:
|
||||
self.viewportmapper.origin = (
|
||||
self.viewportmapper.origin - offset)
|
||||
return self.update()
|
||||
self.update()
|
||||
|
||||
def call_context_menu(self):
|
||||
screen_cursor = get_cursor(self)
|
||||
world_cursor = self.viewportmapper.to_units_coords(screen_cursor)
|
||||
shape = detect_hovered_shape(
|
||||
self.visible_shapes(), world_cursor, screen_cursor,
|
||||
self.viewportmapper)
|
||||
|
||||
global_commands = self.document.data['general']['menu_commands']
|
||||
context_menu = PickerMenu(global_commands, shape, self.editable)
|
||||
|
||||
method = partial(self.add_button, world_cursor, button_type=0)
|
||||
context_menu.add_single.triggered.connect(method)
|
||||
context_menu.add_single.setEnabled(bool(cmds.ls(selection=True)))
|
||||
|
||||
method = partial(self.add_button, world_cursor, button_type=1)
|
||||
context_menu.add_multiple.triggered.connect(method)
|
||||
state = len(cmds.ls(selection=True)) > 1
|
||||
context_menu.add_multiple.setEnabled(state)
|
||||
|
||||
method = partial(self.add_button, world_cursor, button_type=2)
|
||||
context_menu.add_command.triggered.connect(method)
|
||||
|
||||
method = partial(self.update_button, self.clicked_shape)
|
||||
context_menu.update_button.triggered.connect(method)
|
||||
state = bool(self.clicked_shape) and bool(cmds.ls(selection=True))
|
||||
context_menu.update_button.setEnabled(state)
|
||||
|
||||
context_menu.delete_selected.triggered.connect(self.delete_buttons)
|
||||
|
||||
if self.layers_menu.displayed:
|
||||
context_menu.addSeparator()
|
||||
context_menu.addMenu(self.layers_menu)
|
||||
|
||||
action = context_menu.exec_(QtGui.QCursor.pos())
|
||||
if isinstance(action, CommandAction):
|
||||
if not shape:
|
||||
self.execute_menu_command(action.command)
|
||||
return
|
||||
shape.execute(command=action.command)
|
||||
|
||||
def execute_menu_command(self, command):
|
||||
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(traceback.format_exc())
|
||||
|
||||
def update_button(self, shape):
|
||||
shape.set_targets(cmds.ls(selection=True))
|
||||
self.document.record_undo()
|
||||
|
||||
def delete_buttons(self):
|
||||
selected_shapes = [s for s in self.document.shapes if s.selected]
|
||||
self.document.remove_shapes(selected_shapes)
|
||||
self.document.record_undo()
|
||||
self.document.shapes_changed.emit()
|
||||
|
||||
def get_quick_options(self):
|
||||
|
||||
return {
|
||||
'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)}
|
||||
|
||||
def add_button(self, position, button_type=0):
|
||||
"""
|
||||
Button types:
|
||||
0 = Single button from selection.
|
||||
1 = Multiple buttons from selection.
|
||||
2 = Command button.
|
||||
"""
|
||||
targets = cmds.ls(selection=True)
|
||||
if not targets and button_type <= 1:
|
||||
return warning("Warning", "No targets selected")
|
||||
|
||||
if button_type == 1:
|
||||
overrides = self.get_quick_options()
|
||||
overrides['panel'] = self.panel
|
||||
shapes = build_multiple_shapes(targets, overrides)
|
||||
if not shapes:
|
||||
return
|
||||
self.drag_shapes = shapes
|
||||
return
|
||||
|
||||
shape_data = deepcopy(BUTTON)
|
||||
shape_data['panel'] = self.panel
|
||||
shape_data['shape.left'] = position.x()
|
||||
shape_data['shape.top'] = position.y()
|
||||
shape_data.update(self.get_quick_options())
|
||||
if button_type == 0:
|
||||
shape_data['action.targets'] = targets
|
||||
else:
|
||||
text, result = (
|
||||
QtWidgets.QInputDialog.getText(self, 'Button text', 'text'))
|
||||
if not result:
|
||||
return
|
||||
shape_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)
|
||||
shape_data['action.commands'] = [command]
|
||||
|
||||
width = max([
|
||||
shape_data['shape.width'],
|
||||
len(shape_data['text.content']) * 7])
|
||||
shape_data['shape.width'] = width
|
||||
|
||||
self.document.add_shapes([shape_data])
|
||||
self.document.record_undo()
|
||||
self.document.shapes_changed.emit()
|
||||
|
||||
def paintEvent(self, _):
|
||||
try:
|
||||
painter = QtGui.QPainter()
|
||||
painter.begin(self)
|
||||
# Color background.
|
||||
color = self.document.data['general']['panels.colors'][self.panel]
|
||||
if color:
|
||||
painter.setPen(QtCore.Qt.NoPen)
|
||||
painter.setBrush(QtGui.QColor(color))
|
||||
painter.drawRect(self.rect())
|
||||
|
||||
# Color border focus.
|
||||
if self.rect().contains(get_cursor(self)):
|
||||
draw_picker_focus(painter, self.rect())
|
||||
|
||||
# List renderable shapes.
|
||||
painter.setRenderHints(QtGui.QPainter.Antialiasing)
|
||||
hidden_layers = self.layers_menu.hidden_layers
|
||||
shapes = [
|
||||
shape for shape in self.document.shapes_by_panel[self.panel] if
|
||||
not shape.visibility_layer() or
|
||||
shape.visibility_layer() not in hidden_layers]
|
||||
if self.interaction_manager.left_click_pressed:
|
||||
shapes.extend(self.drag_shapes)
|
||||
|
||||
# Draw shapes and create a mask for arrows shapes.
|
||||
cutter = QtGui.QPainterPath()
|
||||
cutter.setFillRule(QtCore.Qt.WindingFill)
|
||||
for shape in shapes:
|
||||
qpath = draw_shape(
|
||||
painter, shape,
|
||||
force_world_space=False,
|
||||
viewportmapper=self.viewportmapper)
|
||||
screen_space = shape.options['shape.space'] == 'screen'
|
||||
if not shape.options['background'] or screen_space:
|
||||
cutter.addPath(qpath)
|
||||
|
||||
# Draw hierarchy connections.
|
||||
connections_path = QtGui.QPainterPath()
|
||||
if cmds.optionVar(query=DISPLAY_HIERARCHY_IN_PICKER):
|
||||
for shape in shapes:
|
||||
if shape.options['shape.space'] == 'screen':
|
||||
continue
|
||||
for child in shape.options['children']:
|
||||
child = self.document.shapes_by_id.get(child)
|
||||
hidden = (
|
||||
child and
|
||||
child.visibility_layer() and
|
||||
child.visibility_layer() in hidden_layers)
|
||||
screen_space = child.options['shape.space'] == 'screen'
|
||||
panel = child.options['panel'] != shape.options['panel']
|
||||
if hidden or screen_space or panel:
|
||||
continue
|
||||
start_point = shape.bounding_rect().center()
|
||||
end_point = child.bounding_rect().center()
|
||||
path = get_connection_path(
|
||||
start_point, end_point, self.viewportmapper)
|
||||
connections_path.addPath(path)
|
||||
connections_path = connections_path.subtracted(cutter)
|
||||
draw_connections(painter, connections_path)
|
||||
|
||||
# Draw Selection square/
|
||||
if self.selection_square.rect:
|
||||
draw_selection_square(
|
||||
painter, self.selection_square.rect)
|
||||
|
||||
except BaseException as e:
|
||||
import traceback
|
||||
print(traceback.format_exc())
|
||||
print(str(e))
|
||||
pass # avoid crash
|
||||
# TODO: log the error
|
||||
finally:
|
||||
painter.end()
|
||||
|
||||
|
||||
class CommandAction(QtWidgets.QAction):
|
||||
def __init__(self, command, parent=None):
|
||||
super(CommandAction, self).__init__(command['caption'], parent)
|
||||
self.command = command
|
||||
|
||||
|
||||
class PickerMenu(QtWidgets.QMenu):
|
||||
def __init__(
|
||||
self,
|
||||
global_commands=None,
|
||||
shape=None,
|
||||
editable=True,
|
||||
parent=None):
|
||||
super(PickerMenu, self).__init__(parent)
|
||||
|
||||
if shape and shape.options['action.menu_commands']:
|
||||
for command in shape.options['action.menu_commands']:
|
||||
self.addAction(CommandAction(command, self))
|
||||
if not global_commands:
|
||||
self.addSeparator()
|
||||
|
||||
if global_commands:
|
||||
for command in global_commands:
|
||||
self.addAction(CommandAction(command, self))
|
||||
self.addSeparator()
|
||||
|
||||
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)
|
||||
|
||||
if editable:
|
||||
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 VisibilityLayersMenu(QtWidgets.QMenu):
|
||||
visibilities_changed = QtCore.Signal()
|
||||
def __init__(self, document, parent=None):
|
||||
super(VisibilityLayersMenu, self).__init__('Visibility layers', parent)
|
||||
self.document = document
|
||||
self.document.shapes_changed.connect(self.update_actions)
|
||||
self.hidden_layers = document.data['general']['hidden_layers'][:]
|
||||
self.update_actions()
|
||||
|
||||
@property
|
||||
def displayed(self):
|
||||
return bool(self.document.shapes_by_layer)
|
||||
|
||||
def update_actions(self):
|
||||
self.clear()
|
||||
layers = list(self.document.shapes_by_layer)
|
||||
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)
|
||||
|
||||
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)
|
||||
self.visibilities_changed.emit()
|
||||
289
2023/scripts/animation_tools/dwpicker/preference.py
Normal file
@@ -0,0 +1,289 @@
|
||||
import os
|
||||
from .pyside import QtWidgets, QtCore
|
||||
from maya import cmds
|
||||
from .hotkeyseditor import HotkeysEditor
|
||||
from .optionvar import (
|
||||
save_optionvar,
|
||||
AUTO_COLLAPSE_IMG_PATH_FROM_ENV, AUTO_FOCUS_BEHAVIOR,
|
||||
AUTO_RESIZE_NAMESPACE_COMBO, 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 .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 = "Auto resize namespace combo."
|
||||
self.auto_resize_namespace_combo = 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.auto_resize_namespace_combo)
|
||||
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_resize_namespace_combo.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_RESIZE_NAMESPACE_COMBO))
|
||||
self.auto_resize_namespace_combo.setChecked(state)
|
||||
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.auto_resize_namespace_combo.isChecked())
|
||||
save_optionvar(AUTO_RESIZE_NAMESPACE_COMBO, 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()
|
||||
27
2023/scripts/animation_tools/dwpicker/pyside.py
Normal file
@@ -0,0 +1,27 @@
|
||||
try:
|
||||
ModuleNotFoundError
|
||||
except NameError:
|
||||
class ModuleNotFoundError(ImportError): # Python2 backward compatilibity
|
||||
pass
|
||||
|
||||
from maya import cmds
|
||||
try:
|
||||
if int(cmds.about(majorVersion=True)) >= 2025:
|
||||
from PySide6 import QtCore, QtGui, QtWidgets
|
||||
from PySide6 import __version__
|
||||
import shiboken6 as shiboken2
|
||||
|
||||
QtWidgets.QShortcut = QtGui.QShortcut
|
||||
QtWidgets.QAction = QtGui.QAction
|
||||
|
||||
QtGui.QMouseEvent.pos = lambda x: x.position().toPoint()
|
||||
QtGui.QMouseEvent.globalPos = QtGui.QMouseEvent.globalPosition
|
||||
|
||||
QtGui.QWheelEvent.pos = QtGui.QWheelEvent.position
|
||||
|
||||
QtCore.Qt.BackgroundColorRole = QtCore.Qt.BackgroundRole
|
||||
else:
|
||||
raise TypeError()
|
||||
except (ModuleNotFoundError, TypeError):
|
||||
from PySide2 import QtCore, QtGui, QtWidgets
|
||||
import shiboken2
|
||||
119
2023/scripts/animation_tools/dwpicker/qtutils.py
Normal file
@@ -0,0 +1,119 @@
|
||||
import inspect
|
||||
import os
|
||||
import sys
|
||||
from .pyside import QtGui, QtWidgets, QtCore
|
||||
from maya import cmds
|
||||
import maya.OpenMayaUI as omui
|
||||
from maya.app.general.mayaMixin import MayaQWidgetDockableMixin
|
||||
from .pyside 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)
|
||||
|
||||
|
||||
def clear_layout(layout):
|
||||
while layout.count():
|
||||
item = layout.takeAt(0)
|
||||
widget = item.widget()
|
||||
if widget is not None:
|
||||
widget.deleteLater()
|
||||
elif item.layout() is not None:
|
||||
clear_layout(item.layout())
|
||||
|
||||
|
||||
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
2023/scripts/animation_tools/dwpicker/quick.py
Normal file
@@ -0,0 +1,120 @@
|
||||
from .pyside import QtWidgets, QtGui, QtCore
|
||||
from maya import cmds
|
||||
|
||||
from .colorwheel import ColorDialog
|
||||
from .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.update()
|
||||
|
||||
@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
2023/scripts/animation_tools/dwpicker/references.py
Normal file
@@ -0,0 +1,42 @@
|
||||
|
||||
|
||||
import os
|
||||
from .dialog import MissingImages
|
||||
from .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']))])))
|
||||