Files
Nexus/2023/scripts/animation_tools/dwpicker/shapepath.py
2025-11-23 23:31:18 +08:00

282 lines
9.1 KiB
Python

import math
from .pyside import QtGui, QtCore
from .viewport import ViewportMapper, to_screenspace_coords
def get_default_path(options):
width = options['shape.width']
height = options['shape.height']
return [
{
'point': [0, 0],
'tangent_in': None,
'tangent_out': None,
},
{
'point': [width, 0],
'tangent_in': None,
'tangent_out': None,
},
{
'point': [width, height],
'tangent_in': None,
'tangent_out': None,
},
{
'point': [0, height],
'tangent_in': None,
'tangent_out': None,
},
]
def offset_path(path, offset, selection=None):
for i in selection or range(len(path)):
path[i]['point'][0] += offset.x()
path[i]['point'][1] += offset.y()
point = path[i]['tangent_in']
if point:
point[0] += offset.x()
point[1] += offset.y()
point = path[i]['tangent_out']
if point:
point[0] += offset.x()
point[1] += offset.y()
def auto_tangent(point, previous_point, next_point):
in_middle = (
point[0] + previous_point[0]) / 2, (point[1] + previous_point[1]) / 2
out_middle = (point[0] + next_point[0]) / 2, (point[1] + next_point[1]) / 2
in_opposite = [2 * point[0] - in_middle[0], 2 * point[1] - in_middle[1]]
out_opposite = [2 * point[0] - out_middle[0], 2 * point[1] - out_middle[1]]
return [
[
(in_middle[0] + out_opposite[0]) / 2,
(in_middle[1] + out_opposite[1]) / 2
],
[
(in_opposite[0] + out_middle[0]) / 2,
(in_opposite[1] + out_middle[1]) / 2
]]
def offset_tangent(
start_tangent_pos, end_tangent_pos, center_pos, offset, lock=True):
start_vector = [
start_tangent_pos[0] - center_pos[0],
start_tangent_pos[1] - center_pos[1]]
new_start_pos = [
center_pos[0] + start_vector[0] + offset[0],
center_pos[1] + start_vector[1] + offset[1]]
if not lock:
return new_start_pos, end_tangent_pos
opposite_vector = [
end_tangent_pos[0] - center_pos[0],
end_tangent_pos[1] - center_pos[1]]
opposite_length = math.sqrt(
opposite_vector[0] ** 2 + opposite_vector[1] ** 2)
opposite_angle = math.atan2(opposite_vector[1], opposite_vector[0])
new_start_vector = [
new_start_pos[0] - center_pos[0],
new_start_pos[1] - center_pos[1]]
angle_delta = math.atan2(
new_start_vector[1],
new_start_vector[0]) - math.atan2(start_vector[1], start_vector[0])
new_opposite_angle = opposite_angle + angle_delta
new_end_pos = [
center_pos[0] + opposite_length * math.cos(new_opposite_angle),
center_pos[1] + opposite_length * math.sin(new_opposite_angle)]
return new_start_pos, new_end_pos
def get_path(path):
painter_path = QtGui.QPainterPath()
painter_path.moveTo(QtCore.QPointF(*path[0]['point']))
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(c1, c2, end)
return painter_path
def get_absolute_path(reference_point, relative_path):
absolute_path = []
for point in relative_path:
tin = [
reference_point[0] + point['tangent_in'][0],
reference_point[1] + point['tangent_in'][1]
] if point['tangent_in'] else None
to = [
reference_point[0] + point['tangent_out'][0],
reference_point[1] + point['tangent_out'][1]
] if point['tangent_out'] else None
center = [
reference_point[0] + point['point'][0],
reference_point[1] + point['point'][1]]
absolute_path.append(
{'point': center, 'tangent_in': tin, 'tangent_out': to})
return absolute_path
def get_relative_path(reference_point, absolute_path):
relative_path = []
for point in absolute_path:
tin = [
point['tangent_in'][0] - reference_point[0],
point['tangent_in'][1] - reference_point[1]
] if point['tangent_in'] else None
to = [
point['tangent_out'][0] - reference_point[0],
point['tangent_out'][1] - reference_point[1]
] if point['tangent_out'] else None
point = [
point['point'][0] - reference_point[0],
point['point'][1] - reference_point[1]]
relative_path.append(
{'point': point, 'tangent_in': tin, 'tangent_out': to})
return relative_path
def get_shape_painter_path(shape, viewportmapper=None):
if not shape.options['shape.path']:
return
left, top = shape.options['shape.left'], shape.options['shape.top']
path = get_absolute_path((left, top), shape.options['shape.path'])
return get_worldspace_qpath(path, viewportmapper)
def get_shape_space_painter_path(
shape, force_world_space=True, viewportmapper=None):
path = get_absolute_path(shape)
if shape.options['shape.space'] == 'world' or force_world_space:
return get_worldspace_qpath(path, viewportmapper)
return get_screenspace_qpath(
path=path,
anchor=shape.options['shape.anchor'],
viewport_size=viewportmapper.viewsize)
def get_screenspace_qpath(path, point, anchor, viewport_size):
if not path:
return QtGui.QPainterPath()
def add(p1, p2):
return p1[0] + p2[0], p1[1] + p2[1]
painter_path = QtGui.QPainterPath()
start = QtCore.QPointF(*add(path[0]['point'], point))
painter_path.moveTo(to_screenspace_coords(start, anchor, viewport_size))
for i in range(len(path)):
p1 = path[i]
p2 = path[i + 1 if i + 1 < len(path) else 0]
c1 = QtCore.QPointF(*add(p1['tangent_out'] or p1['point'], point))
c2 = QtCore.QPointF(*add(p2['tangent_in'] or p2['point'], point))
end = QtCore.QPointF(*add(p2['point'], point))
painter_path.cubicTo(
to_screenspace_coords(c1, anchor, viewport_size),
to_screenspace_coords(c2, anchor, viewport_size),
to_screenspace_coords(end, anchor, viewport_size))
return painter_path
def get_worldspace_qpath(path, viewportmapper=None):
if not path:
return QtGui.QPainterPath()
viewportmapper = viewportmapper or 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 rotate_point(x, y, cx, cy, angle):
x_rotated = (
math.cos(angle) * (x - cx) - math.sin(angle) * (y - cy) + cx)
y_rotated = (
math.sin(angle) * (x - cx) + math.cos(angle) * (y - cy) + cy)
return x_rotated, y_rotated
def rotate_path(path, angle, center):
if not path:
return
angle_value = math.radians(angle)
cx, cy = center
# Helper function to rotate a point
rotated_path = []
for point_data in path:
x, y = point_data["point"]
tangent_in = point_data["tangent_in"]
tangent_out = point_data["tangent_out"]
# Rotate the point
x_rotated, y_rotated = rotate_point(x, y, cx, cy, angle_value)
# Rotate the tangents if they exist
if tangent_in:
tan_in_x, tan_out_y = tangent_in
tan_in_rotated = rotate_point(
tan_in_x, tan_out_y, cx, cy, angle_value)
else:
tan_in_rotated = None
if tangent_out:
tan_out_x, tan_out_y = tangent_out
tan_out_rotated = rotate_point(
tan_out_x, tan_out_y, cx, cy, angle_value)
else:
tan_out_rotated = None
# Update the shape path
rotated_path.append({
"point": [x_rotated, y_rotated],
"tangent_in": [
tan_in_rotated[0], tan_in_rotated[1]]
if tan_in_rotated else None,
"tangent_out": [
tan_out_rotated[0], tan_out_rotated[1]]
if tan_out_rotated else None})
return rotated_path
def create_polygon_path(radius, n):
shape_path = []
angle_step = 2 * math.pi / n
for i in range(n):
x = radius * math.cos(i * angle_step)
y = radius * math.sin(i * angle_step)
shape_path.append({
"point": [x, y],
"tangent_in": None,
"tangent_out": None})
return shape_path