Update
This commit is contained in:
703
2023/scripts/animation_tools/dwpicker/designer/patheditor.py
Normal file
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()
|
||||
Reference in New Issue
Block a user