This commit is contained in:
2025-12-05 08:08:44 +08:00
parent e0d4d0c364
commit 1f10abfb32
2909 changed files with 2470486 additions and 3024 deletions

View File

@@ -0,0 +1,949 @@
"""
Curve control window
---
GS CurveTools License:
This collection of code named GS CurveTools is a property of George Sladkovsky (Yehor Sladkovskyi)
and can not be copied or distributed without his written permission.
GS CurveTools v1.3.10 Personal Edition
Copyright 2025, George Sladkovsky (Yehor Sladkovskyi)
All Rights Reserved
UI font is Roboto that is licensed under the Apache 2.0 License:
http://www.apache.org/licenses/LICENSE-2.0
Autodesk Maya is a property of Autodesk, Inc:
https://www.autodesk.com/
Social Media and Contact Links:
Discord Server: https://discord.gg/f4DH6HQ
Online Store: https://sladkovsky3d.artstation.com/store
Online Documentation: https://gs-curvetools.readthedocs.io/
Twitch Channel: https://www.twitch.tv/videonomad
YouTube Channel: https://www.youtube.com/c/GeorgeSladkovsky
ArtStation Portfolio: https://www.artstation.com/sladkovsky3d
Contact Email: george.sladkovsky@gmail.com
"""
from functools import partial
import maya.cmds as mc
from gs_curvetools.api.maya_tools import deferred, no_undo, undo
from gs_curvetools.api.qt_compat import *
from gs_curvetools.api.utils import open_link
from gs_curvetools.config.constants import *
from gs_curvetools.config.folders import GetFolder
from gs_curvetools.core.core import Core
from gs_curvetools.debug.logger import logger
from gs_curvetools.managers.options_manager import OptionsManager
from gs_curvetools.managers.script_jobs import ScriptJobs
from gs_curvetools.managers.widget_manager import WidgetManager
from gs_curvetools.ui import style
from gs_curvetools.ui.tooltips import Tooltips
from gs_curvetools.ui.utils import maya_dockable_window
from gs_curvetools.ui.widgets import Button, ColorPicker, Column, ControlSlider, FallOffCurve, FloatField, Frame, IconCheckButton, Label, LayerSelector, LineEdit, PopOutWindow, Row, maya_slider, separator
class CurveControlWindow(QWidget):
"""曲线控制用户界面"""
update_curve_control_ui = Signal()
update_main_ui = Signal()
def __init__(self, parent):
super(CurveControlWindow, self).__init__(parent)
self.core = Core.singleton()
self.widget_manager = WidgetManager()
self.options_manager = OptionsManager()
self.script_jobs = ScriptJobs.singleton()
self.tooltips = Tooltips()
def open_ui(self):
"""创建曲线控制工作区"""
if mc.workspaceControl(WINDOWS.CurveControl.name, q=1, ex=1):
if not mc.workspaceControl(WINDOWS.CurveControl.name, q=1, vis=1):
mc.workspaceControl(WINDOWS.CurveControl.name, e=1, rs=1)
deferred(self.update_main_ui.emit)()
else:
mc.workspaceControl(WINDOWS.CurveControl.name, e=1, vis=0)
return
else:
dockable_window = maya_dockable_window(name=WINDOWS.CurveControl.name, label="曲线控制", i_w=350, i_h=750, width_property='free')
layout = dockable_window.layout()
assert layout is not None, '未找到布局'
layout.addWidget(self)
self.create_ui()
self.update_curve_control_ui.emit()
self.script_jobs.check_script_jobs(WINDOWS.CurveControl.name)
def create_ui(self):
"""创建曲线控制用户界面"""
main_layout = QtWidgets.QVBoxLayout(self)
main_layout.setContentsMargins(*style.scale([2, 0, 2, 0]))
scroll_widget = QtWidgets.QWidget()
layout = QtWidgets.QVBoxLayout(scroll_widget)
scroll_area = QtWidgets.QScrollArea()
scroll_area.setWidget(scroll_widget)
main_layout.addWidget(scroll_area)
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(style.scale(2))
layout.setAlignment(QtCore.Qt.AlignmentFlag.AlignTop)
scroll_area.setFrameShape(QtWidgets.QFrame.Shape.NoFrame)
scroll_area.setWidgetResizable(True)
layout.addWidget(separator())
with Row(layout, 'gsCurveControlHeader') as row:
row_layout = row.layout()
if not row_layout:
raise RuntimeError('未找到行布局')
layer_selector = LayerSelector('gsLayerSelector', row_layout)
layer_selector.setFixedWidth(style.scale(40))
layer_selector.currentIndexChanged.connect(self.core.layer_manager.change_layer_via_option_menu)
layer_color_picker = ColorPicker('gsColorPicker', row_layout)
layer_color_picker.setContentsMargins(*style.scale([3, 3, 1, 3]))
layer_color_picker.setFixedWidth(style.scale(20))
layer_color_picker.connect_command(self.core.color_mode.change_layer_color_via_picker)
curve_color_picker = ColorPicker('gsCurveColorPicker', row_layout)
curve_color_picker.setContentsMargins(*style.scale([1, 3, 2, 3]))
curve_color_picker.setFixedWidth(style.scale(20))
curve_color_picker.connect_command(self.core.color_mode.change_curve_color_via_picker)
selected_object_name = LineEdit('selectedObjectName', row_layout)
selected_object_name.setPlaceholderText('选择曲线')
selected_object_name.returnPressed.connect(undo(self.core.functions.rename_selected))
line_thickness = FloatField('lineWidth', row_layout)
line_thickness.setFixedWidth(style.scale(35))
line_thickness.set_range(-1, 10)
line_thickness.set_value(-1)
line_thickness.set_drag_command(partial(self.core.sliders.curve_control_slider_drag, line_thickness))
line_thickness.set_release_command(self.core.sliders.release)
layout.addWidget(separator())
label = Label(layout, 'selectCurvesPrompt')
label.set_label('选择兼容的曲线')
label.set_font_size(14)
label.setVisible(False)
with Column(layout, 'axisFrame') as column:
column_layout = column.layout()
if not column_layout:
raise RuntimeError('未找到列布局')
label = Label(column_layout)
label.set_label('轴控制')
label.set_font_size(14)
with Row(column.layout()) as row:
axis_button_grp = QtWidgets.QButtonGroup(row)
self.widget_manager.add('Axis', axis_button_grp)
axis_auto = Button(row.layout(), 'gsBindAxisAuto')
axis_auto.set_label('自动', line_height=100)
axis_auto.setCheckable(True)
axis_auto.set_button_style('small')
axis_auto.setChecked(True)
axis_auto.clicked.connect(partial(undo(self.core.functions.apply_axis), 0))
axis_x = Button(row.layout(), 'gsBindAxisX')
axis_x.set_label('X', line_height=100)
axis_x.setCheckable(True)
axis_x.set_button_style('small')
axis_x.clicked.connect(partial(undo(self.core.functions.apply_axis), 1))
axis_y = Button(row.layout(), 'gsBindAxisY')
axis_y.set_label('Y', line_height=100)
axis_y.setCheckable(True)
axis_y.set_button_style('small')
axis_y.clicked.connect(partial(undo(self.core.functions.apply_axis), 2))
axis_z = Button(row.layout(), 'gsBindAxisZ')
axis_z.set_label('Z', line_height=100)
axis_z.setCheckable(True)
axis_z.set_button_style('small')
axis_z.clicked.connect(partial(undo(self.core.functions.apply_axis), 3))
axis_button_grp.addButton(axis_auto, 0)
axis_button_grp.addButton(axis_x, 1)
axis_button_grp.addButton(axis_y, 2)
axis_button_grp.addButton(axis_z, 3)
axis_flip = Button(row.layout(), 'AxisFlip')
axis_flip.set_label('翻转', line_height=100)
axis_flip.setCheckable(True)
axis_flip.set_button_style('small')
axis_flip.clicked.connect(partial(undo(self.core.functions.apply_axis), -1))
column_layout.addWidget(separator())
with Row(column.layout(), margins=style.scale([0, 0, 0, 0]), obj_name='originalCurvesRow') as original_curves_row:
select_original_curves = Button(original_curves_row.layout(), 'selectOriginalCurves')
select_original_curves.set_label('选择原始曲线', line_height=100)
select_original_curves.set_button_style('small-filled')
select_original_curves.clicked.connect(undo(self.core.curve_control.select_original_objects))
edit_orig_obj = Button(original_curves_row.layout(), 'editOrigObj')
edit_orig_obj.set_label('编辑原始对象', line_height=100)
edit_orig_obj.setCheckable(True)
edit_orig_obj.set_button_style('small')
edit_orig_obj.clicked.connect(undo(self.core.curve_control.edit_original_objects))
column_layout.addWidget(separator())
with Row(layout, margins=style.scale([0, 0, 3, 0])) as length_divisions_row:
length_division = ControlSlider(obj_name='lengthDivisions', typ='int')
length_division.set_label('长度分段')
length_division.set_min_max(2, 100)
length_division.set_field_min_max(2, 10000)
length_division.set_value(2)
length_division.set_drag_command(partial(self.core.sliders.curve_control_slider_drag, length_division))
length_division.set_release_command(self.core.sliders.release)
dynamic_divisions_toggle = Button(obj_name='dynamicDivisions')
dynamic_divisions_toggle.set_label('自动')
dynamic_divisions_toggle.setFixedWidth(style.scale(35))
dynamic_divisions_toggle.set_button_style('small')
dynamic_divisions_toggle.setCheckable(True)
dynamic_divisions_toggle.clicked.connect(undo(self.core.functions.toggle_dynamic_divisions))
length_division_layout = length_divisions_row.layout()
if not isinstance(length_division_layout, QHBoxLayout):
raise RuntimeError('未找到长度分段布局')
length_division_layout.addWidget(length_division, 4)
length_division_layout.addWidget(dynamic_divisions_toggle, 1)
width_division = ControlSlider(layout, 'widthDivisions', 'int')
width_division.set_label('宽度分段')
width_division.set_min_max(2, 31)
width_division.set_field_min_max(2, 10000)
width_division.set_value(2)
width_division.set_drag_command(partial(self.core.sliders.curve_control_slider_drag, width_division))
width_division.set_release_command(self.core.sliders.release)
orientation = ControlSlider(layout, 'Orientation', 'float')
orientation.set_label('方向')
orientation.set_min_max(-180, 180)
orientation.set_field_min_max(-36000, 36000)
orientation.set_precision(1)
orientation.set_step(0.5)
orientation.set_drag_command(partial(self.core.sliders.curve_control_slider_drag, orientation))
orientation.set_release_command(self.core.sliders.release)
twist = ControlSlider(layout, 'Twist', 'float')
twist.set_label('扭曲')
twist.set_min_max(-180, 180)
twist.set_field_min_max(-36000, 36000)
twist.set_precision(1)
twist.set_step(0.5)
twist.set_drag_command(partial(self.core.sliders.curve_control_slider_drag, twist))
twist.set_release_command(self.core.sliders.release)
inv_twist = ControlSlider(layout, 'invTwist', 'float')
inv_twist.set_label('反向扭曲')
inv_twist.set_min_max(-180, 180)
inv_twist.set_field_min_max(-36000, 36000)
inv_twist.set_precision(1)
inv_twist.set_step(0.5)
inv_twist.set_drag_command(partial(self.core.sliders.curve_control_slider_drag, inv_twist))
inv_twist.set_release_command(self.core.sliders.release)
with Frame(layout, 'twistCurveFrame', margins=[0, 1, 0, 2]) as twist_curve_frame:
twist_curve_frame.get_frame_button().set_label('扭曲曲线图表')
twist_graph = FallOffCurve(twist_curve_frame.get_frame_layout(), 'twistCurve')
def twist_graph_command(_):
self.core.attributes.propagate_graphs(twist_graph)
self.core.attributes.store_graphs(twist_graph)
twist_graph.change_command(twist_graph_command)
with Row(twist_curve_frame.get_frame_layout(), margins=style.scale([5, 0, 5, 2])) as row:
row.setFixedHeight(int(style.BUTTON_HEIGHT))
magnitude = FloatField('Magnitude', row.layout())
magnitude.setFixedWidth(style.scale(45))
magnitude.set_range(-99, 99)
magnitude.set_step(0.01)
magnitude.set_precision(2)
magnitude.set_drag_command(partial(self.core.sliders.curve_control_slider_drag, magnitude))
magnitude.set_release_command(self.core.sliders.release)
reset_button = Button(row.layout(), 'gsTwistGraphResetButton')
reset_button.set_label('重置曲线')
def reset_twist_cmd():
self.core.utils.reset_single_graph('twist')
self.core.attributes.store_graphs(twist_graph)
reset_button.clicked.connect(undo(reset_twist_cmd))
pop_out_button = Button(row.layout(), 'gsTwistGraphPopOut')
pop_out_button.setFixedWidth(style.scale(56))
pop_out_button.set_label('^')
pop_out_button.clicked.connect(self.twist_graph_pop_out)
width = ControlSlider(layout, 'Width', 'float')
width.set_label('宽度')
width.set_min_max(0.001, 2)
width.set_field_min_max(0.001, 1000)
width.set_precision(3)
width.set_step(0.001)
width.set_value(1)
width.set_drag_command(partial(self.core.sliders.curve_control_slider_drag, width))
width.set_release_command(self.core.sliders.release)
taper = ControlSlider(layout, 'Taper', 'float')
taper.set_label('锥度')
taper.set_min_max(0.001, 3)
taper.set_field_min_max(0.001, 1000)
taper.set_precision(3)
taper.set_step(0.001)
taper.set_value(1)
taper.set_drag_command(partial(self.core.sliders.curve_control_slider_drag, taper))
taper.set_release_command(self.core.sliders.release)
with Row(layout, obj_name='widthComboSlider', spacing=0) as row:
row_layout = row.layout()
if not row_layout:
raise RuntimeError('未找到行布局')
with Column(row.layout()) as column:
column_layout = column.layout()
if not column_layout:
raise RuntimeError('未找到列布局')
width_x = ControlSlider(column_layout, 'WidthX', 'float')
width_x.set_label('宽度 X')
width_x.set_min_max(0.001, 2)
width_x.set_field_min_max(0.001, 1000)
width_x.set_precision(3)
width_x.set_step(0.001)
width_x.set_value(1)
width_x.set_drag_command(partial(self.core.sliders.curve_control_slider_drag, width_x))
width_x.set_release_command(self.core.sliders.release)
width_z = ControlSlider(column.layout(), 'WidthZ', 'float')
width_z.set_label('宽度 Z')
width_z.set_min_max(0.001, 2)
width_z.set_field_min_max(0.001, 1000)
width_z.set_precision(3)
width_z.set_step(0.001)
width_z.set_value(1)
width_z.set_drag_command(partial(self.core.sliders.curve_control_slider_drag, width_z))
width_z.set_release_command(self.core.sliders.release)
lock_button = IconCheckButton(row_layout, 'widthLockSwitch')
lock_button.set_icons(GetFolder.icons() + 'sliderLock_en_reversed.png', GetFolder.icons() + 'sliderLock_reversed.png')
lock_button.set_icon_width_height(10, 30)
lock_button.setChecked(True)
with Frame(layout, 'widthCurveFrame', margins=[0, 1, 0, 2]) as width_curve_frame:
width_curve_frame.get_frame_button().set_label('宽度曲线图表')
width_graph = FallOffCurve(width_curve_frame.get_frame_layout(), 'scaleCurve')
def width_graph_command(_):
self.core.attributes.propagate_graphs(width_graph)
self.core.attributes.store_graphs(width_graph)
width_graph.change_command(width_graph_command)
with Row(width_curve_frame.get_frame_layout(), margins=style.scale([5, 0, 5, 2])) as row:
row.setFixedHeight(int(style.BUTTON_HEIGHT))
reset_button = Button(row.layout(), 'gsWidthGraphResetButton')
reset_button.set_label('重置曲线')
def reset_width_cmd():
self.core.utils.reset_single_graph('scale')
self.core.attributes.store_graphs(width_graph)
reset_button.clicked.connect(undo(reset_width_cmd))
pop_out_button = Button(row.layout(), 'gsWidthGraphPopOut')
pop_out_button.setFixedWidth(style.scale(56))
pop_out_button.set_label('^')
pop_out_button.clicked.connect(self.width_graph_pop_out)
length_unlock = Button(layout, 'LengthLock')
length_unlock.set_label('长度解锁', line_height=100)
length_unlock.setCheckable(True)
length_unlock.set_button_style('small')
length_unlock.setFixedWidth(style.scale(106))
length_unlock.clicked.connect(partial(self.core.updates.curve_control_check_boxes, 2))
length = ControlSlider(layout, 'Length', 'float')
length.set_label('长度')
length.set_min_max(0.001, 40)
length.set_field_min_max(-1000, 1000)
length.set_precision(3)
length.set_step(0.001)
length.set_value(20)
length.set_drag_command(partial(self.core.sliders.curve_control_slider_drag, length))
length.set_release_command(self.core.sliders.release)
offset = ControlSlider(layout, 'Offset', 'float')
offset.set_label('偏移')
offset.set_min_max(-1, 1)
offset.set_field_min_max(-30, 30)
offset.set_step(0.001)
offset.set_value(1)
offset.set_drag_command(partial(self.core.sliders.curve_control_slider_drag, offset))
offset.set_release_command(self.core.sliders.release)
profile = ControlSlider(layout, 'Profile', 'float')
profile.set_label('轮廓')
profile.set_min_max(-2, 2)
profile.set_field_min_max(-1000, 1000)
profile.set_step(0.001)
profile.set_drag_command(partial(self.core.sliders.curve_control_slider_drag, profile))
profile.set_release_command(self.core.sliders.release)
with Frame(layout, 'profileCurveGraph', margins=[0, 1, 0, 2]) as profile_curve_frame:
profile_curve_frame.get_frame_button().set_label('轮廓曲线图表')
profile_graph = FallOffCurve(profile_curve_frame.get_frame_layout(), 'profileCurve', attr=False)
def change_command(value):
self.core.attributes.update_lattice(value)
self.core.attributes.equalize_profile_curve()
self.core.attributes.store_graphs(profile_graph)
profile_graph.change_command(change_command)
profile_smoothing_slider = ControlSlider(profile_curve_frame.get_frame_layout(), 'profileSmoothing', 'int')
profile_smoothing_slider.set_label('平滑度')
profile_smoothing_slider.set_min_max(2, 30)
profile_smoothing_slider.set_value(2)
profile_smoothing_slider.set_drag_command(partial(self.core.sliders.curve_control_slider_drag, profile_smoothing_slider))
profile_smoothing_slider.set_release_command(self.core.sliders.release)
with Row(profile_curve_frame.get_frame_layout(), margins=style.scale([5, 0, 5, 2])) as row:
row.setFixedHeight(int(style.BUTTON_HEIGHT))
with Column(row.layout(), spacing=0) as eq_column:
eq_column.setFixedHeight(style.scale(24))
with Row(eq_column.layout(), spacing=0) as eq_row:
def toggle_eq():
self.widget_manager.get('equalizeCurveButton').setDisabled(self.widget_manager.get('autoEqualizeSwitchOn').isChecked())
auto_equalize_group = QtWidgets.QButtonGroup(eq_row)
auto_equalize_group.buttonClicked.connect(toggle_eq)
auto_equalize_group.buttonClicked.connect(undo(self.core.attributes.equalize_profile_curve))
auto_equalize = Button(eq_row.layout(), 'autoEqualizeSwitchOn')
auto_equalize.set_label('自动', line_height=100)
auto_equalize.set_label_style('small')
auto_equalize.set_button_style('small-compound-top-left')
auto_equalize.setCheckable(True)
auto_equalize.setChecked(True)
auto_equalize_off = Button(eq_row.layout(), 'autoEqualizeSwitchOff')
auto_equalize_off.set_label('手动', line_height=100)
auto_equalize_off.set_label_style('small')
auto_equalize_off.set_button_style('small-compound-top-right')
auto_equalize_off.setCheckable(True)
auto_equalize_group.addButton(auto_equalize)
auto_equalize_group.addButton(auto_equalize_off)
equalize_profile_curve = Button(eq_column.layout(), 'equalizeCurveButton')
equalize_profile_curve.set_label('均衡曲线', line_height=100)
equalize_profile_curve.set_label_style('small')
equalize_profile_curve.set_button_style('small-filled-compound-bottom')
equalize_profile_curve.setDisabled(True)
equalize_profile_curve.clicked.connect(partial(undo(self.core.attributes.equalize_profile_curve), True))
reset_button = Button(obj_name='gsResetProfileGraphButton')
reset_button.set_label('重置曲线')
def reset_button_clicked(*_):
self.core.attributes.reset_profile_curve()
self.core.attributes.store_graphs(profile_graph)
reset_button.clicked.connect(undo(reset_button_clicked))
pop_out_button = Button(obj_name='gsProfileGraphPopOut')
pop_out_button.set_label('^')
pop_out_button.clicked.connect(self.profile_graph_pop_out)
row_layout = row.layout()
if not isinstance(row_layout, QHBoxLayout):
raise RuntimeError('未找到行布局')
row_layout.addWidget(eq_column, 2)
row_layout.addWidget(reset_button, 3)
row_layout.addWidget(pop_out_button, 1)
normals = ControlSlider(layout, 'surfaceNormals', 'float')
normals.set_label('法线')
normals.set_min_max(0, 180)
normals.set_precision(1)
normals.set_value(180)
normals.set_drag_command(partial(self.core.sliders.curve_control_slider_drag, normals))
normals.set_release_command(self.core.sliders.release)
reverse_normals = Button(layout, 'reverseNormals')
reverse_normals.set_label('反转法线', line_height=100)
reverse_normals.setCheckable(True)
reverse_normals.set_button_style('small')
reverse_normals.setFixedWidth(style.scale(106))
reverse_normals.clicked.connect(partial(self.core.updates.curve_control_check_boxes, 0))
with Frame(layout, 'otherFrame', label='其他', margins=[2, 2, 2, 2]) as refine_frame, Row(refine_frame.get_frame_layout(), margins=style.scale([0, 0, 3, 0])) as curve_sampling_row:
sampling_accuracy = ControlSlider(layout, 'samplingAccuracy', 'float')
sampling_accuracy.set_label('采样精度')
sampling_accuracy.set_min_max(0.001, 2)
sampling_accuracy.set_step(0.01)
sampling_accuracy.set_value(0.33)
sampling_accuracy.set_drag_command(partial(self.core.sliders.curve_control_slider_drag, sampling_accuracy))
sampling_accuracy.set_release_command(self.core.sliders.release)
auto_sampling = Button(obj_name='autoSampling')
auto_sampling.set_label('自动')
auto_sampling.setFixedWidth(style.scale(35))
auto_sampling.set_button_style('small')
auto_sampling.setCheckable(True)
auto_sampling.clicked.connect(undo(self.core.functions.toggle_auto_sampling))
curve_sampling_row_layout = curve_sampling_row.layout()
if not isinstance(curve_sampling_row_layout, QHBoxLayout):
raise RuntimeError('未找到曲线采样行布局')
curve_sampling_row_layout.addWidget(sampling_accuracy, 4)
curve_sampling_row_layout.addWidget(auto_sampling, 1)
with Row(refine_frame.get_frame_layout(), margins=style.scale([0, 0, 3, 0])) as curve_refine_row:
refine = ControlSlider(layout, 'curveRefine', 'int')
refine.set_label('细化')
refine.set_min_max(0, 100)
refine.set_field_min_max(0, 10000)
refine.set_value(20)
refine.set_drag_command(partial(self.core.sliders.curve_control_slider_drag, refine))
refine.set_release_command(self.core.sliders.release)
auto_refine_toggle = Button(obj_name='autoRefine')
auto_refine_toggle.set_label('自动')
auto_refine_toggle.setFixedWidth(style.scale(35))
auto_refine_toggle.set_button_style('small')
auto_refine_toggle.setCheckable(True)
auto_refine_toggle.clicked.connect(undo(self.core.functions.toggle_auto_refine))
curve_refine_row_layout = curve_refine_row.layout()
if not isinstance(curve_refine_row_layout, QHBoxLayout):
raise RuntimeError('未找到曲线细化行布局')
curve_refine_row_layout.addWidget(refine, 4)
curve_refine_row_layout.addWidget(auto_refine_toggle, 1)
smooth = ControlSlider(refine_frame.get_frame_layout(), 'curveSmooth', 'float')
smooth.setVisible(False)
smooth.set_label('平滑')
smooth.set_min_max(0, 10)
smooth.set_drag_command(partial(self.core.sliders.curve_control_slider_drag, smooth))
smooth.set_release_command(self.core.sliders.release)
with Frame(layout, 'orientToNormalsFrame', label='朝向法线', margins=[2, 2, 2, 2]) as orient_frame:
orient_frame.setVisible(False)
def select_mesh():
sel = mc.filterExpand(mc.ls(sl=1, l=1), sm=12)
if not sel:
sel = mc.ls(hl=1, o=1, l=1)
if not sel:
logger.warning('选择兼容的网格。', in_view=True)
else:
self.mesh_name.setText(sel[0])
with Row(orient_frame.get_frame_layout()) as row:
select_mesh_btn = Button(row.layout(), 'gsOrientToNormalsSelectTarget')
select_mesh_btn.setFixedWidth(style.scale(100))
select_mesh_btn.set_label('选择目标')
select_mesh_btn.clicked.connect(select_mesh)
self.mesh_name = LineEdit('gsOrientMeshName', row.layout())
self.mesh_name.setPlaceholderText('选择或输入目标网格')
self.mesh_name.setClearButtonEnabled(True)
orient_frame.get_frame_layout().addWidget(separator())
with Row(orient_frame.get_frame_layout()) as row:
iterations_slider = ControlSlider(row.layout(), 'gsIterationsSlider', 'int')
iterations_slider.set_min_max(1, 100)
iterations_slider.set_value(10)
iterations_slider.set_label('迭代次数')
auto_refresh_button = Button(row.layout(), 'orientRefreshViewport')
auto_refresh_button.set_button_style('small')
auto_refresh_button.setCheckable(True)
auto_refresh_button.setChecked(True)
auto_refresh_button.set_width_height(width=style.scale(60))
auto_refresh_button.set_label('刷新视图', line_height=100)
with Row(orient_frame.get_frame_layout()) as row:
min_angle_slider = ControlSlider(row.layout(), 'gsMinimumAngle', 'float')
min_angle_slider.set_min_max(0.1, 90)
min_angle_slider.set_value(1)
min_angle_slider.set_label('最小角度')
orient_frame.get_frame_layout().addWidget(separator())
with Row(orient_frame.get_frame_layout()) as row:
orient = Button(row.layout(), 'gsOrientToNormals')
orient.set_label('朝向')
orient.clicked.connect(undo(self.core.functions.orient_to_face_normals))
with Frame(layout, 'solidifyFrame', label='实体化控制', margins=[2, 2, 2, 2]) as solidify_frame:
solidify = Button(solidify_frame.get_frame_layout(), 'solidify')
solidify.set_label('实体化', line_height=100)
solidify.setCheckable(True)
solidify.set_button_style('small')
solidify.setFixedWidth(style.scale(106))
solidify.clicked.connect(partial(self.core.updates.curve_control_check_boxes, 1))
thickness = ControlSlider(solidify_frame.get_frame_layout(), 'solidifyThickness', 'float')
thickness.set_label('厚度')
thickness.set_min_max(0.001, 5)
thickness.set_field_min_max(-100, 100)
thickness.set_value(0.25)
thickness.set_step(0.001)
thickness.set_drag_command(partial(self.core.sliders.curve_control_slider_drag, thickness))
thickness.set_release_command(self.core.sliders.release)
divisions = ControlSlider(solidify_frame.get_frame_layout(), 'solidifyDivisions', 'int')
divisions.set_label('分段数')
divisions.set_min_max(0, 10)
divisions.set_field_min_max(0, 100)
divisions.set_drag_command(partial(self.core.sliders.curve_control_slider_drag, divisions))
divisions.set_release_command(self.core.sliders.release)
solidify_scale_x = ControlSlider(solidify_frame.get_frame_layout(), 'solidifyScaleX', 'float')
solidify_scale_x.set_label('缩放 X')
solidify_scale_x.set_min_max(-10, 10)
solidify_scale_x.set_field_min_max(-100, 100)
solidify_scale_x.set_step(0.001)
solidify_scale_x.set_drag_command(partial(self.core.sliders.curve_control_slider_drag, solidify_scale_x))
solidify_scale_x.set_release_command(self.core.sliders.release)
solidify_scale_y = ControlSlider(solidify_frame.get_frame_layout(), 'solidifyScaleY', 'float')
solidify_scale_y.set_label('缩放 Y')
solidify_scale_y.set_min_max(-10, 10)
solidify_scale_y.set_field_min_max(-100, 100)
solidify_scale_y.set_step(0.001)
solidify_scale_y.set_drag_command(partial(self.core.sliders.curve_control_slider_drag, solidify_scale_y))
solidify_scale_y.set_release_command(self.core.sliders.release)
offset = ControlSlider(solidify_frame.get_frame_layout(), 'solidifyOffset', 'float')
offset.set_label('偏移')
offset.set_min_max(-1, 1)
offset.set_field_min_max(-100, 100)
offset.set_step(0.001)
offset.set_drag_command(partial(self.core.sliders.curve_control_slider_drag, offset))
offset.set_release_command(self.core.sliders.release)
solidify_normals = ControlSlider(solidify_frame.get_frame_layout(), 'solidifyNormals', 'float')
solidify_normals.set_label('实体化法线')
solidify_normals.set_min_max(0, 180)
solidify_normals.set_precision(1)
solidify_normals.set_value(180)
solidify_normals.set_drag_command(partial(self.core.sliders.curve_control_slider_drag, solidify_normals))
solidify_normals.set_release_command(self.core.sliders.release)
with Frame(layout, 'UVFrame', label='UV 控制', margins=[2, 2, 2, 2]) as uv_frame:
flip_uv = Button(uv_frame.get_frame_layout(), 'flipUV')
flip_uv.set_label('水平翻转 UV', line_height=100)
flip_uv.setCheckable(True)
flip_uv.set_button_style('small')
flip_uv.setFixedWidth(style.scale(106))
flip_uv.clicked.connect(partial(undo(self.core.updates.curve_control_check_boxes), 3))
move_u = ControlSlider(uv_frame.get_frame_layout(), 'moveU', 'float')
move_u.set_label('移动 U')
move_u.set_min_max(-0.5, 0.5)
move_u.set_step(0.001)
move_u.set_field_min_max(-100, 100)
move_u.set_drag_command(partial(self.core.sliders.curve_control_slider_drag, move_u))
move_u.set_release_command(self.core.sliders.release)
move_v = ControlSlider(uv_frame.get_frame_layout(), 'moveV', 'float')
move_v.set_label('移动 V')
move_v.set_min_max(-0.5, 0.5)
move_v.set_step(0.001)
move_v.set_field_min_max(-100, 100)
move_v.set_drag_command(partial(self.core.sliders.curve_control_slider_drag, move_v))
move_v.set_release_command(self.core.sliders.release)
scale_u = ControlSlider(uv_frame.get_frame_layout(), 'scaleU', 'float')
scale_u.set_label('缩放 U')
scale_u.set_min_max(0.001, 1.999)
scale_u.set_field_min_max(0, 100)
scale_u.set_step(0.001)
scale_u.set_value(1)
scale_u.set_drag_command(partial(self.core.sliders.curve_control_slider_drag, scale_u))
scale_u.set_release_command(self.core.sliders.release)
scale_v = ControlSlider(uv_frame.get_frame_layout(), 'scaleV', 'float')
scale_v.set_label('缩放 V')
scale_v.set_min_max(0.001, 1.999)
scale_v.set_field_min_max(0, 100)
scale_v.set_step(0.001)
scale_v.set_value(1)
scale_v.set_drag_command(partial(self.core.sliders.curve_control_slider_drag, scale_v))
scale_v.set_release_command(self.core.sliders.release)
rotate_uv = ControlSlider(uv_frame.get_frame_layout(), 'rotateUV', 'float')
rotate_uv.set_label('旋转 UV')
rotate_uv.set_min_max(-180, 180)
rotate_uv.set_field_min_max(-3600, 3600)
rotate_uv.set_precision(2)
rotate_uv.set_drag_command(partial(self.core.sliders.curve_control_slider_drag, rotate_uv))
rotate_uv.set_release_command(self.core.sliders.release)
rotate_uv_root = ControlSlider(uv_frame.get_frame_layout(), 'rotateRootUV', 'float')
rotate_uv_root.set_label('旋转根部 UV')
rotate_uv_root.set_min_max(-180, 180)
rotate_uv_root.set_field_min_max(-3600, 3600)
rotate_uv_root.set_precision(2)
rotate_uv_root.set_drag_command(partial(self.core.sliders.curve_control_slider_drag, rotate_uv_root))
rotate_uv_root.set_release_command(self.core.sliders.release)
rotate_uv_root.setVisible(False)
rotate_uv_tip = ControlSlider(uv_frame.get_frame_layout(), 'rotateTipUV', 'float')
rotate_uv_tip.set_label('旋转尖端 UV')
rotate_uv_tip.set_min_max(-180, 180)
rotate_uv_tip.set_field_min_max(-3600, 3600)
rotate_uv_tip.set_precision(2)
rotate_uv_tip.set_drag_command(partial(self.core.sliders.curve_control_slider_drag, rotate_uv_tip))
rotate_uv_tip.set_release_command(self.core.sliders.release)
rotate_uv_tip.setVisible(False)
if MAYA_VER in (2020, 2022) and (not self.options_manager.get('UVBugMessageDismissed')):
with Row(uv_frame.get_frame_layout()) as row:
label = Label(row.layout())
label.set_label('<p style="color:#FF542F">如何修复 Maya 2020 - 2022 的 UV 问题:</p>')
link_button = Button(row.layout())
link_button.set_label('打开修复方法')
link_button.set_button_style('small-filled')
link_button.clicked.connect(lambda: open_link('https://gs-curvetools.readthedocs.io/en/latest/faq.html#maya-2020-2022-and-broken-uvs'))
dismiss_message = Button(row.layout())
dismiss_message.set_label('忽略提示')
dismiss_message.set_button_style('small-filled')
dismiss_message.clicked.connect(lambda: row.setHidden(True))
dismiss_message.clicked.connect(lambda: self.options_manager.set('UVBugMessageDismissed', 1))
with Frame(layout, 'advancedVisibilityFrame', label='高级可见性设置', margins=style.scale([3, 2, 3, 2]), spacing=style.scale(3)) as visibility_frame:
with Row(visibility_frame.get_frame_layout()) as row:
geometry_highlight = Button(row.layout(), 'geometryHighlight')
geometry_highlight.set_label('几何体高亮显示', line_height=100)
geometry_highlight.setCheckable(True)
geometry_highlight.setChecked(bool(self.options_manager.get('GeometryHighlightEnabled')))
geometry_highlight.set_button_style('small')
geometry_highlight.clicked.connect(no_undo(self.core.advanced_visibility.geometry_highlight_command))
curve_highlight = Button(row.layout(), 'curveHighlight')
curve_highlight.set_label('曲线高亮显示', line_height=100)
curve_highlight.setCheckable(True)
curve_highlight.set_button_style('small')
curve_highlight.setChecked(False)
curve_highlight.clicked.connect(undo(self.core.advanced_visibility.toggle_curve_highlight_from_ui))
visibility_frame.get_frame_layout().addWidget(separator())
def slider_change_command(*_):
self.core.advanced_visibility.apply_settings_to_node()
self.core.advanced_visibility.save_settings_from_ui()
with Column(visibility_frame.get_frame_layout(), obj_name='gsCurveHighlightFrame', spacing=style.scale(4)) as column:
column.setEnabled(False)
column_layout = column.layout()
if not isinstance(column_layout, QVBoxLayout):
raise RuntimeError('未找到列布局')
with Row(column.layout(), spacing=4) as row:
cv_size_label = Label()
cv_size_label.set_label('控制点大小:')
cv_size_label.setFixedWidth(style.scale(100))
cv_size_label.setAlignment(QtCore.Qt.AlignmentFlag.AlignRight | QtCore.Qt.AlignmentFlag.AlignVCenter)
point_size_slider = maya_slider(mc.floatSliderGrp('gsPointSizeSlider', f=1, adj=2, w=1, cw=(1, 45), min=1, max=100, v=10, dc=lambda _: no_undo(self.core.advanced_visibility.apply_settings_to_node)(), cc=no_undo(slider_change_command)))
self.widget_manager.add('gsPointSizeSlider', point_size_slider)
row_layout = row.layout()
if not isinstance(row_layout, QHBoxLayout):
raise RuntimeError('未找到行布局')
row_layout.addWidget(cv_size_label, 0)
row_layout.addWidget(point_size_slider, 1)
with Row(column.layout(), spacing=style.scale(4)) as row:
selected_color_label = Label()
selected_color_label.set_label('选中颜色:')
selected_color_label.setFixedWidth(style.scale(100))
selected_color_label.setAlignment(QtCore.Qt.AlignmentFlag.AlignRight | QtCore.Qt.AlignmentFlag.AlignVCenter)
selected_color_picker = ColorPicker('gsSelectedCVColor')
selected_color_picker.connect_drag_command(lambda *_: no_undo(self.core.advanced_visibility.apply_settings_to_node)())
selected_color_picker.connect_command(no_undo(slider_change_command))
selected_alpha_value = FloatField('gsSelectedCVAlpha')
selected_alpha_value.setFixedWidth(style.scale(45))
selected_alpha_value.set_value(1.0)
selected_alpha_value.set_range(0, 1)
selected_alpha_value.set_step(0.01)
selected_alpha_value.set_precision(2)
selected_alpha_value.set_drag_command(lambda *_: no_undo(self.core.advanced_visibility.apply_settings_to_node)())
selected_alpha_value.set_release_command(no_undo(slider_change_command))
row_layout = row.layout()
if not isinstance(row_layout, QHBoxLayout):
raise RuntimeError('未找到行布局')
row_layout.addWidget(selected_color_label, 0)
row_layout.addWidget(selected_color_picker, 1)
row_layout.addWidget(selected_alpha_value, 0)
with Row(column.layout(), spacing=style.scale(4)) as row:
deselected_color_label = Label()
deselected_color_label.set_label('未选中颜色:')
deselected_color_label.setFixedWidth(style.scale(100))
deselected_color_label.setAlignment(QtCore.Qt.AlignmentFlag.AlignRight | QtCore.Qt.AlignmentFlag.AlignVCenter)
deselected_color_picker = ColorPicker('gsDeselectedCVColor')
deselected_color_picker.connect_drag_command(lambda *_: no_undo(self.core.advanced_visibility.apply_settings_to_node)())
deselected_color_picker.connect_command(no_undo(slider_change_command))
deselected_alpha_value = FloatField('gsDeselectedCVAlpha')
deselected_alpha_value.setFixedWidth(style.scale(45))
deselected_alpha_value.set_value(1)
deselected_alpha_value.set_range(0, 1)
deselected_alpha_value.set_step(0.01)
deselected_alpha_value.set_precision(2)
deselected_alpha_value.set_drag_command(lambda *_: no_undo(self.core.advanced_visibility.apply_settings_to_node)())
deselected_alpha_value.set_release_command(no_undo(slider_change_command))
row_layout = row.layout()
if not isinstance(row_layout, QHBoxLayout):
raise RuntimeError('未找到行布局')
row_layout.addWidget(deselected_color_label, 0)
row_layout.addWidget(deselected_color_picker, 1)
row_layout.addWidget(deselected_alpha_value, 1)
column_layout.addWidget(separator())
curve_visibility = Button(column.layout(), 'curveVisibility')
curve_visibility.set_label('曲线可见性', line_height=100)
curve_visibility.setCheckable(True)
curve_visibility.set_button_style('small')
curve_visibility.setChecked(True)
curve_visibility.clicked.connect(no_undo(slider_change_command))
with Row(column.layout(), spacing=4) as row:
curve_width_label = Label()
curve_width_label.set_label('曲线宽度:')
curve_width_label.setFixedWidth(style.scale(100))
curve_width_label.setAlignment(QtCore.Qt.AlignmentFlag.AlignRight | QtCore.Qt.AlignmentFlag.AlignVCenter)
curve_width_slider = maya_slider(mc.floatSliderGrp('gsCurveWidthSlider', f=1, adj=2, w=1, cw=(1, 45), min=1, max=100, v=4, dc=lambda _: no_undo(self.core.advanced_visibility.apply_settings_to_node)(), cc=no_undo(slider_change_command)))
self.widget_manager.add('gsCurveWidthSlider', curve_width_slider)
row_layout = row.layout()
if not isinstance(row_layout, QHBoxLayout):
raise RuntimeError('未找到行布局')
row_layout.addWidget(curve_width_label, 0)
row_layout.addWidget(curve_width_slider, 1)
with Row(column.layout(), spacing=style.scale(4)) as row:
curve_highlight_color_label = Label()
curve_highlight_color_label.set_label('曲线颜色:')
curve_highlight_color_label.setFixedWidth(style.scale(100))
curve_highlight_color_label.setAlignment(QtCore.Qt.AlignmentFlag.AlignRight | QtCore.Qt.AlignmentFlag.AlignVCenter)
curve_highlight_color = ColorPicker('gsCurveHighlightColor')
curve_highlight_color.connect_drag_command(lambda *_: no_undo(self.core.advanced_visibility.apply_settings_to_node)())
curve_highlight_color.connect_command(no_undo(slider_change_command))
curve_highlight_alpha = FloatField('gsCurveHighlightAlpha')
curve_highlight_alpha.setFixedWidth(style.scale(45))
curve_highlight_alpha.set_value(1.0)
curve_highlight_alpha.set_range(0, 1)
curve_highlight_alpha.set_step(0.01)
curve_highlight_alpha.set_precision(2)
curve_highlight_alpha.set_drag_command(lambda *_: no_undo(self.core.advanced_visibility.apply_settings_to_node)())
curve_highlight_alpha.set_release_command(no_undo(slider_change_command))
row_layout = row.layout()
if not isinstance(row_layout, QHBoxLayout):
raise RuntimeError('未找到行布局')
row_layout.addWidget(curve_highlight_color_label, 0)
row_layout.addWidget(curve_highlight_color, 1)
row_layout.addWidget(curve_highlight_alpha, 1)
column_layout.addWidget(separator())
hull_visibility = Button(column.layout(), 'hullVisibility')
hull_visibility.set_label('壳线可见性', line_height=100)
hull_visibility.setCheckable(True)
hull_visibility.set_button_style('small')
hull_visibility.clicked.connect(no_undo(slider_change_command))
with Row(column.layout(), spacing=4) as row:
hull_width_label = Label()
hull_width_label.set_label('壳线宽度:')
hull_width_label.setFixedWidth(style.scale(100))
hull_width_label.setAlignment(QtCore.Qt.AlignmentFlag.AlignRight | QtCore.Qt.AlignmentFlag.AlignVCenter)
hull_width_slider = maya_slider(mc.floatSliderGrp('gsHullWidthSlider', f=1, adj=2, w=1, cw=(1, 45), min=1, max=100, v=3, dc=lambda _: no_undo(self.core.advanced_visibility.apply_settings_to_node)(), cc=no_undo(slider_change_command)))
self.widget_manager.add('gsHullWidthSlider', hull_width_slider)
row_layout = row.layout()
if not isinstance(row_layout, QHBoxLayout):
raise RuntimeError('未找到行布局')
row_layout.addWidget(hull_width_label, 0)
row_layout.addWidget(hull_width_slider, 1)
with Row(column.layout(), spacing=style.scale(4)) as row:
hull_color_label = Label()
hull_color_label.set_label('壳线颜色:')
hull_color_label.setFixedWidth(style.scale(100))
hull_color_label.setAlignment(QtCore.Qt.AlignmentFlag.AlignRight | QtCore.Qt.AlignmentFlag.AlignVCenter)
hull_color = ColorPicker('gsHullHighlightColor')
hull_color.connect_drag_command(lambda *_: no_undo(self.core.advanced_visibility.apply_settings_to_node)())
hull_color.connect_command(no_undo(slider_change_command))
hull_alpha = FloatField('gsHullHighlightAlpha')
hull_alpha.setFixedWidth(style.scale(45))
hull_alpha.set_value(1.0)
hull_alpha.set_range(0, 1)
hull_alpha.set_step(0.01)
hull_alpha.set_precision(2)
hull_alpha.set_drag_command(lambda *_: no_undo(self.core.advanced_visibility.apply_settings_to_node)())
hull_alpha.set_release_command(no_undo(slider_change_command))
row_layout = row.layout()
if not isinstance(row_layout, QHBoxLayout):
raise RuntimeError('未找到行布局')
row_layout.addWidget(hull_color_label, 0)
row_layout.addWidget(hull_color, 1)
row_layout.addWidget(hull_alpha, 1)
column_layout.addWidget(separator())
with Frame(column.layout(), 'advancedVisibilityFrame', label='其他选项:', margins=style.scale([3, 2, 3, 2]), spacing=style.scale(3)) as options_frame:
with Row(options_frame.get_frame_layout()) as row:
lazy_update = Button(row.layout(), 'lazyUpdate')
lazy_update.set_label('延迟更新', line_height=100)
lazy_update.setCheckable(True)
lazy_update.set_button_style('small')
lazy_update.clicked.connect(no_undo(slider_change_command))
always_on_top = Button(row.layout(), 'alwaysOnTop')
always_on_top.set_label('始终置顶', line_height=100)
always_on_top.setCheckable(True)
always_on_top.set_button_style('small')
always_on_top.setChecked(True)
always_on_top.clicked.connect(no_undo(slider_change_command))
options_frame.get_frame_layout().addWidget(separator())
experimental_label = Label(options_frame.get_frame_layout())
experimental_label.set_label('距离颜色设置:')
with Row(options_frame.get_frame_layout()) as row:
curve_distance_color = Button(row.layout(), 'curveDistanceColor')
curve_distance_color.set_label('曲线', line_height=100)
curve_distance_color.setCheckable(True)
curve_distance_color.set_button_style('small')
curve_distance_color.setChecked(True)
curve_distance_color.clicked.connect(no_undo(slider_change_command))
cv_distance_color = Button(row.layout(), 'cvDistanceColor')
cv_distance_color.set_label('控制点', line_height=100)
cv_distance_color.setCheckable(True)
cv_distance_color.set_button_style('small')
cv_distance_color.setChecked(True)
cv_distance_color.clicked.connect(no_undo(slider_change_command))
hull_distance_color = Button(row.layout(), 'hullDistanceColor')
hull_distance_color.set_label('壳线', line_height=100)
hull_distance_color.setCheckable(True)
hull_distance_color.set_button_style('small')
hull_distance_color.setChecked(True)
hull_distance_color.clicked.connect(no_undo(slider_change_command))
with Row(options_frame.get_frame_layout()) as row:
distance_color_min_label = Label()
distance_color_min_label.set_label('颜色最小值:')
distance_color_min_label.setAlignment(QtCore.Qt.AlignmentFlag.AlignRight | QtCore.Qt.AlignmentFlag.AlignVCenter)
distance_color_min_input = FloatField('gsDistanceColorMinValue')
distance_color_min_input.set_value(0.25)
distance_color_min_input.set_range(0.01, 1)
distance_color_min_input.set_step(0.01)
distance_color_min_input.set_precision(2)
distance_color_min_input.set_drag_command(lambda *_: no_undo(self.core.advanced_visibility.apply_settings_to_node)())
distance_color_min_input.set_release_command(no_undo(slider_change_command))
distance_color_max_label = Label()
distance_color_max_label.set_label('颜色最大值:')
distance_color_max_label.setAlignment(QtCore.Qt.AlignmentFlag.AlignRight | QtCore.Qt.AlignmentFlag.AlignVCenter)
distance_color_max_input = FloatField('gsDistanceColorMaxValue')
distance_color_max_input.set_value(1.0)
distance_color_max_input.set_range(0.01, 1)
distance_color_max_input.set_step(0.01)
distance_color_max_input.set_precision(2)
distance_color_max_input.set_drag_command(lambda *_: no_undo(self.core.advanced_visibility.apply_settings_to_node)())
distance_color_max_input.set_release_command(no_undo(slider_change_command))
row_layout = row.layout()
if not isinstance(row_layout, QHBoxLayout):
raise RuntimeError('未找到行布局')
row_layout.addWidget(distance_color_min_label, 1)
row_layout.addWidget(distance_color_min_input, 1)
row_layout.addWidget(distance_color_max_label, 1)
row_layout.addWidget(distance_color_max_input, 1)
options_frame.get_frame_layout().addWidget(separator())
experimental_label = Label(options_frame.get_frame_layout())
experimental_label.set_label('实验性功能(可能较慢):')
cv_occlusion = Button(options_frame.get_frame_layout(), 'CVocclusion')
cv_occlusion.set_label('启用控制点遮挡效果', line_height=100)
cv_occlusion.setCheckable(True)
cv_occlusion.set_button_style('small')
cv_occlusion.clicked.connect(no_undo(slider_change_command))
with Row(options_frame.get_frame_layout(), spacing=style.scale(4)) as row:
select_occluder_button = Button(row.layout(), 'gsSelectOccluderButton')
select_occluder_button.setFixedWidth(style.scale(100))
select_occluder_button.set_label('选择遮挡物')
select_occluder_button.clicked.connect(no_undo(self.core.advanced_visibility.select_occluder_from_scene))
occluder_mesh_name = LineEdit('gsOccluderMeshName', row.layout())
occluder_mesh_name.setPlaceholderText('选择或输入遮挡物网格名称')
occluder_mesh_name.setClearButtonEnabled(True)
occluder_mesh_name.editingFinished.connect(no_undo(slider_change_command))
self.core.advanced_visibility.load_settings_from_option_var()
self.tooltips.toggle_custom_tooltips_curve_control(self.options_manager.get('enableTooltips'))
layout.addWidget(separator())
reset_button = Button(layout, 'resetControlSliders')
reset_button.set_label('重置滑块范围')
reset_button.clicked.connect(self.core.functions.reset_control_sliders)
def twist_graph_pop_out(self):
"""扭曲曲线大图弹出窗口"""
name = WINDOWS.TwistGraphWindow.name
if mc.workspaceControl(name, q=1, ex=1):
mc.deleteUI(name)
pop_out = PopOutWindow(name, '扭曲曲线大图', 512, 400)
graph = FallOffCurve(pop_out.widget_layout, 'twistCurve_large')
def twist_graph_command(_):
self.core.attributes.propagate_graphs(graph)
self.core.attributes.store_graphs(graph)
graph.change_command(twist_graph_command)
with Row(pop_out.widget_layout, margins=style.scale([5, 0, 5, 2])) as row:
row.setFixedHeight(int(style.BUTTON_HEIGHT))
magnitude = FloatField('Magnitude_large', row.layout(), attr_name='Magnitude')
magnitude.setFixedWidth(style.scale(45))
magnitude.set_range(-99, 99)
magnitude.set_step(0.01)
magnitude.set_precision(2)
magnitude.set_drag_command(partial(self.core.sliders.curve_control_slider_drag, magnitude))
magnitude.set_release_command(self.core.sliders.release)
reset_button = Button(row.layout())
reset_button.set_label('重置曲线')
def reset_twist():
self.core.utils.reset_single_graph('twist')
self.core.attributes.store_graphs(graph)
reset_button.clicked.connect(undo(reset_twist))
self.core.curve_control.update_ui()
def width_graph_pop_out(self):
"""宽度曲线大图弹出窗口"""
name = WINDOWS.WidthGraphWindow.name
if mc.workspaceControl(name, q=1, ex=1):
mc.deleteUI(name)
pop_out = PopOutWindow(name, '宽度曲线大图', 512, 400)
graph = FallOffCurve(pop_out.widget_layout, 'scaleCurve_large')
def width_graph_command(_):
self.core.attributes.propagate_graphs(graph)
self.core.attributes.store_graphs(graph)
graph.change_command(width_graph_command)
with Row(pop_out.widget_layout, margins=style.scale([5, 0, 5, 2])) as row:
row.setFixedHeight(int(style.BUTTON_HEIGHT))
reset_button = Button(row.layout())
reset_button.set_label('重置曲线')
def reset_width_cmd():
self.core.utils.reset_single_graph('width')
self.core.attributes.store_graphs(graph)
reset_button.clicked.connect(undo(reset_width_cmd))
self.core.curve_control.update_ui()
def profile_graph_pop_out(self):
"""轮廓曲线大图弹出窗口"""
name = WINDOWS.ProfileGraphWindow.name
if mc.workspaceControl(name, q=1, ex=1):
mc.deleteUI(name)
pop_out = PopOutWindow(name, '轮廓曲线大图', 512, 400)
graph = FallOffCurve(pop_out.widget_layout, 'profileCurve_large', attr=False)
def change_command(value):
self.core.attributes.update_lattice(value)
self.core.attributes.equalize_profile_curve()
graph.change_command(change_command)
with Row(pop_out.widget_layout, margins=style.scale([5, 0, 5, 2])) as row:
row.setFixedHeight(int(style.BUTTON_HEIGHT))
reset_button = Button(row.layout())
reset_button.set_label('重置曲线')
reset_button.clicked.connect(undo(self.core.attributes.reset_profile_curve))
self.core.curve_control.update_ui()

View File

@@ -0,0 +1,308 @@
"""
几何体转曲线弹出窗口
---
GS CurveTools 许可证:
名为 GS CurveTools 的此代码集合归 George SladkovskyYehor Sladkovskyi所有
未经其书面许可,不得复制或分发。
GS CurveTools v1.3.10 个人版
版权所有 2025George SladkovskyYehor Sladkovskyi
保留所有权利
UI 字体为 Roboto遵循 Apache 2.0 许可证:
http://www.apache.org/licenses/LICENSE-2.0
Autodesk Maya 是 Autodesk, Inc 的产品:
https://www.autodesk.com/
社交媒体和联系方式:
Discord 服务器: https://discord.gg/f4DH6HQ
在线商店: https://sladkovsky3d.artstation.com/store
在线文档: https://gs-curvetools.readthedocs.io/
Twitch 频道: https://www.twitch.tv/videonomad
YouTube 频道: https://www.youtube.com/c/GeorgeSladkovsky
ArtStation 作品集: https://www.artstation.com/sladkovsky3d
联系邮箱: george.sladkovsky@gmail.com
"""
from ast import literal_eval
import maya.cmds as mc
from gs_curvetools.api.maya_tools import undo
from gs_curvetools.api.qt_compat import *
from gs_curvetools.config.constants import *
from gs_curvetools.core.core import Core
from gs_curvetools.debug.logger import logger
from gs_curvetools.managers.options_manager import OptionsManager
from gs_curvetools.managers.widget_manager import WidgetManager
from gs_curvetools.ui import style
from gs_curvetools.ui.widgets import Button, Column, Frame, LineEdit, PopOutWindow, Row, separator
class GeoToCurveWindow(QWidget):
"""创建一个用于几何体转曲线的弹出窗口"""
def __init__(self, parent):
super(GeoToCurveWindow, self).__init__(parent)
self.ui_name = WINDOWS.GeoToCurveWindow.name
self.core = Core.singleton()
self.widget_manager = WidgetManager()
self.options_manager = OptionsManager()
self.buttons_dict = literal_eval(self.options_manager.get('CardToCurveOptions'))
def get_button(self, name):
"""按名称获取按钮"""
return self.buttons_dict[name] if name in self.buttons_dict else True
def open_ui(self):
"""创建一个用于几何体转曲线的弹出窗口"""
if mc.workspaceControl(self.ui_name, q=1, ex=1):
if mc.workspaceControl(self.ui_name, q=1, vis=1):
mc.deleteUI(self.ui_name)
return
mc.deleteUI(self.ui_name)
self.pop_out = PopOutWindow(self.ui_name, '几何体转曲线', 270, 365)
layout = self.pop_out.widget_layout
layout.setAlignment(QtCore.Qt.AlignmentFlag.AlignTop)
self.main_button_group = QtWidgets.QButtonGroup(self.pop_out)
self.main_button_group.setExclusive(False)
self.main_button_group.buttonClicked.connect(self.save_buttons_state)
with Column(layout) as main_column:
main_column_layout = main_column.layout()
if not isinstance(main_column_layout, QVBoxLayout):
raise RuntimeError('主列布局未找到')
main_column_layout.setAlignment(QtCore.Qt.AlignmentFlag.AlignTop)
with Frame(main_column.layout(), label='输出类型:', obj_name='gsGeoToCurve_outputTypeSwitch') as output_type_frame:
output_type_frame.set_collapsible(False)
output_type_frame.set_collapsed(False)
with Row(output_type_frame.get_frame_layout()) as output_type_switch:
self.output_type_group = QtWidgets.QButtonGroup(self.pop_out)
self.widget_manager.add('gsGeoToCurve_cardTypeGroup', self.output_type_group)
generate_auto = Button(output_type_switch.layout(), 'gsGeoToCurve_generateAuto')
generate_auto.set_button_style('small')
generate_auto.set_label('自动')
generate_auto.setCheckable(True)
generate_cards = Button(output_type_switch.layout(), 'gsGeoToCurve_generateCards')
generate_cards.set_button_style('small')
generate_cards.set_label('卡片')
generate_cards.setCheckable(True)
generate_tubes = Button(output_type_switch.layout(), 'gsGeoToCurve_generateTubes')
generate_tubes.set_button_style('small')
generate_tubes.set_label('管道')
generate_tubes.setCheckable(True)
generate_curves = Button(output_type_switch.layout(), 'gsGeoToCurve_generateCurves')
generate_curves.set_button_style('small')
generate_curves.set_label('曲线')
generate_curves.setCheckable(True)
self.output_type_group.addButton(generate_auto, 0)
self.output_type_group.addButton(generate_cards, 1)
self.output_type_group.addButton(generate_tubes, 2)
self.output_type_group.addButton(generate_curves, 3)
checked_id = self.options_manager.get('CardToCurveOutputType')
generate_auto.setChecked(checked_id == 0)
generate_cards.setChecked(checked_id == 1)
generate_tubes.setChecked(checked_id == 2)
generate_curves.setChecked(checked_id == 3)
self.output_type_group.buttonClicked.connect(self.update_active_buttons)
with Frame(main_column.layout(), label='对象类型:', obj_name='gsGeoToCurve_cardType') as card_type_frame:
self.card_type_frame = card_type_frame
card_type_frame.set_collapsible(False)
card_type_frame.set_collapsed(False)
with Row(card_type_frame.get_frame_layout()) as output_type_switch:
self.card_type_group = QtWidgets.QButtonGroup(output_type_switch)
self.widget_manager.add('gsGeoToCurve_cardTypeGroup', self.card_type_group)
self.warp_cards = Button(output_type_switch.layout(), 'gsGeoToCurve_warp')
self.warp_cards.set_button_style('small')
self.warp_cards.set_label('变形')
self.warp_cards.setCheckable(True)
extrude_cards = Button(output_type_switch.layout(), 'gsGeoToCurve_extrude')
extrude_cards.set_button_style('small')
extrude_cards.set_label('挤出')
extrude_cards.setCheckable(True)
self.card_type_group.addButton(self.warp_cards, 0)
self.card_type_group.addButton(extrude_cards, 1)
self.warp_cards.setChecked(not self.options_manager.get('CardToCurveCardType'))
extrude_cards.setChecked(self.options_manager.get('CardToCurveCardType'))
self.card_type_group.buttonClicked.connect(self.update_active_buttons)
with Frame(main_column.layout(), label='匹配属性:', obj_name='gsGeoToCurve_matchAttributes') as match_attribute_frame:
self.match_attribute_frame = match_attribute_frame
match_attribute_frame.set_collapsible(False)
match_attribute_frame.set_collapsed(False)
with Row(match_attribute_frame.get_frame_layout()) as row:
orientation = Button(row.layout(), 'gsGeoToCurve_orientation')
orientation.set_button_style('small')
orientation.set_label('方向')
orientation.setCheckable(True)
orientation.setChecked(bool(self.get_button('gsGeoToCurve_orientation')))
width = Button(row.layout(), 'gsGeoToCurve_width')
width.set_button_style('small')
width.set_label('宽度')
width.setCheckable(True)
width.setChecked(bool(self.get_button('gsGeoToCurve_width')))
self.main_button_group.addButton(orientation)
self.main_button_group.addButton(width)
with Row(match_attribute_frame.get_frame_layout()) as row:
taper = Button(row.layout(), 'gsGeoToCurve_taper')
taper.set_button_style('small')
taper.set_label('锥度')
taper.setCheckable(True)
taper.setChecked(bool(self.get_button('gsGeoToCurve_taper')))
twist = Button(row.layout(), 'gsGeoToCurve_twist')
twist.set_button_style('small')
twist.set_label('扭曲')
twist.setCheckable(True)
twist.setChecked(bool(self.get_button('gsGeoToCurve_twist')))
self.main_button_group.addButton(taper)
self.main_button_group.addButton(twist)
with Row(match_attribute_frame.get_frame_layout()) as row:
profile = Button(row.layout(), 'gsGeoToCurve_profile')
profile.set_button_style('small')
profile.set_label('轮廓')
profile.setCheckable(True)
profile.setChecked(bool(self.get_button('gsGeoToCurve_profile')))
material = Button(row.layout(), 'gsGeoToCurve_material')
material.set_button_style('small')
material.set_label('材质')
material.setCheckable(True)
material.setChecked(bool(self.get_button('gsGeoToCurve_material')))
self.main_button_group.addButton(profile)
self.main_button_group.addButton(material)
uvs = Button(match_attribute_frame.get_frame_layout(), 'gsGeoToCurve_UVs')
uvs.set_button_style('small')
uvs.set_label('UVs')
uvs.setCheckable(True)
uvs.setChecked(bool(self.get_button('gsGeoToCurve_UVs')))
uvs.clicked.connect(self.update_active_buttons)
self.main_button_group.addButton(uvs)
with Frame(main_column.layout(), label='UV 匹配选项:', obj_name='gsGeoToCurve_UVMatchOptions') as uv_match_options_frame:
self.uv_match_options_frame = uv_match_options_frame
uv_match_options_frame.set_collapsible(False)
uv_match_options_frame.set_collapsed(False)
with Row(uv_match_options_frame.get_frame_layout()) as row:
vertical_flip = Button(row.layout(), 'gsGeoToCurve_verticalFlip')
vertical_flip.set_button_style('small')
vertical_flip.set_label('垂直翻转')
vertical_flip.setCheckable(True)
vertical_flip.setChecked(bool(self.get_button('gsGeoToCurve_verticalFlip')))
horizontal_flip = Button(row.layout(), 'gsGeoToCurve_horizontalFlip')
horizontal_flip.set_button_style('small')
horizontal_flip.set_label('水平翻转')
horizontal_flip.setCheckable(True)
horizontal_flip.setChecked(bool(self.get_button('gsGeoToCurve_horizontalFlip')))
self.main_button_group.addButton(vertical_flip)
self.main_button_group.addButton(horizontal_flip)
with Frame(main_column.layout(), label='其他:') as other_options_frame:
other_options_frame.set_collapsible(False)
other_options_frame.set_collapsed(False)
reverse_curve = Button(other_options_frame.get_frame_layout(), 'gsGeoToCurve_reverseCurve')
reverse_curve.set_button_style('small')
reverse_curve.set_label('反转曲线')
reverse_curve.setCheckable(True)
reverse_curve.setChecked(bool(self.get_button('gsGeoToCurve_reverseCurve')))
self.main_button_group.addButton(reverse_curve)
delete_original_object = Button(other_options_frame.get_frame_layout(), 'gsGeoToCurve_deleteOriginalObject')
delete_original_object.set_button_style('small')
delete_original_object.set_label('删除原始对象')
delete_original_object.setCheckable(True)
delete_original_object.setChecked(bool(self.get_button('gsGeoToCurve_deleteOriginalObject')))
self.main_button_group.addButton(delete_original_object)
use_aim_mesh = Button(other_options_frame.get_frame_layout(), 'gsGeoToCurve_useAimMesh')
use_aim_mesh.set_button_style('small')
use_aim_mesh.set_label('使用目标网格')
use_aim_mesh.setCheckable(True)
use_aim_mesh.setChecked(bool(self.get_button('gsGeoToCurve_useAimMesh')))
use_aim_mesh.clicked.connect(self.update_active_buttons)
self.main_button_group.addButton(use_aim_mesh)
with Row(other_options_frame.get_frame_layout(), spacing=style.scale(4)) as aim_mesh_row:
select_aim_mesh = Button(aim_mesh_row.layout(), 'gsGeoToCurve_selectAimMesh')
select_aim_mesh.setFixedWidth(style.scale(100))
select_aim_mesh.set_label('选择目标网格')
select_aim_mesh.clicked.connect(self.select_aim_mesh_from_scene)
aim_mesh_name = LineEdit('gsGeoToCurve_aimMeshName', aim_mesh_row.layout())
aim_mesh_name.setPlaceholderText('选择或输入目标网格名称')
aim_mesh_name.setClearButtonEnabled(True)
aim_mesh_name.editingFinished.connect(self.save_buttons_state)
aim_mesh_name.setText(str(self.get_button('gsGeoToCurve_aimMeshName')))
main_column_layout.addWidget(separator())
with Row(main_column.layout(), margins=style.scale([0, 3, 0, 3])) as _:
pass
main_column_layout.addWidget(separator())
with Row(main_column.layout()) as row:
convert_selected = Button(row.layout(), obj_name='gsGeoToCurve_convertSelected')
convert_selected.set_label('转换选中项')
convert_selected.clicked.connect(undo(self.core.conversion.geo_to_curve))
cancel = Button(row.layout())
cancel.set_label('关闭并保存')
cancel.clicked.connect(self.close_ui)
self.update_active_buttons()
self.save_buttons_state()
def close_ui(self):
"""关闭弹出窗口"""
self.save_buttons_state()
if mc.workspaceControl(self.ui_name, q=1, ex=1):
if mc.workspaceControl(self.ui_name, q=1, vis=1):
mc.deleteUI(self.ui_name)
else:
mc.deleteUI(self.ui_name)
def select_aim_mesh_from_scene(self):
"""点击按钮时从场景中选择目标网格"""
mesh = mc.ls(sl=1, tr=1)
if not mesh:
return
for obj in mesh:
if mc.nodeType(mc.listRelatives(obj, c=1, pa=1)) != 'mesh':
continue
self.widget_manager.get('gsGeoToCurve_aimMeshName').setText(obj)
break
else:
logger.warning('未选择兼容的网格。请选择一个网格对象。', in_view=True)
self.save_buttons_state()
def update_active_buttons(self):
"""Updates the active buttons based on the output type"""
checked_id = self.output_type_group.checkedId()
self.options_manager.set('CardToCurveOutputType', checked_id)
self.options_manager.set('CardToCurveCardType', int(not self.warp_cards.isChecked()))
if checked_id == 0:
self.match_attribute_frame.setEnabled(True)
self.card_type_frame.setEnabled(True)
if self.widget_manager.get('gsGeoToCurve_UVs').isChecked():
self.uv_match_options_frame.setEnabled(True)
else:
self.uv_match_options_frame.setEnabled(False)
elif checked_id == 1:
self.match_attribute_frame.setEnabled(True)
self.card_type_frame.setEnabled(True)
if self.widget_manager.get('gsGeoToCurve_UVs').isChecked():
self.uv_match_options_frame.setEnabled(True)
else:
self.uv_match_options_frame.setEnabled(False)
elif checked_id == 2:
self.match_attribute_frame.setEnabled(True)
self.card_type_frame.setEnabled(True)
if self.widget_manager.get('gsGeoToCurve_UVs').isChecked():
self.uv_match_options_frame.setEnabled(True)
else:
self.uv_match_options_frame.setEnabled(False)
elif checked_id == 3:
self.match_attribute_frame.setEnabled(False)
self.uv_match_options_frame.setEnabled(False)
self.card_type_frame.setEnabled(False)
is_aim_checked = self.widget_manager.get('gsGeoToCurve_useAimMesh').isChecked()
if is_aim_checked:
self.widget_manager.get('gsGeoToCurve_aimMeshName').setEnabled(True)
self.widget_manager.get('gsGeoToCurve_selectAimMesh').setEnabled(True)
else:
self.widget_manager.get('gsGeoToCurve_aimMeshName').setEnabled(False)
self.widget_manager.get('gsGeoToCurve_selectAimMesh').setEnabled(False)
def save_buttons_state(self):
"""Saves the buttons state"""
buttons = self.main_button_group.buttons()
buttons_dict = {}
for b in buttons:
assert isinstance(b, Button), 'Button {b} is not a Button object'.format(b=b)
buttons_dict.update({b.obj_name: b.isChecked()})
else:
buttons_dict.update({'gsGeoToCurve_aimMeshName': self.widget_manager.get('gsGeoToCurve_aimMeshName').text()})
self.options_manager.set('CardToCurveOptions', str(buttons_dict))

View File

@@ -0,0 +1,170 @@
"""
随机化曲线窗口
---
GS曲线工具许可协议
名为GS曲线工具的此代码集合归乔治·斯拉德科夫斯基叶戈尔·斯拉德科夫斯基所有
未经他的书面许可,不得复制或分发。
GS曲线工具 v1.3.10 个人版
版权所有 2025乔治·斯拉德科夫斯基叶戈尔·斯拉德科夫斯基
保留所有权利
用户界面字体为Roboto该字体根据Apache 2.0许可协议授权:
http://www.apache.org/licenses/LICENSE-2.0
Autodesk Maya是Autodesk公司的产品
https://www.autodesk.com/
社交媒体和联系方式:
Discord服务器 https://discord.gg/f4DH6HQ
在线商店: https://sladkovsky3d.artstation.com/store
在线文档: https://gs-curvetools.readthedocs.io/
Twitch频道 https://www.twitch.tv/videonomad
YouTube频道 https://www.youtube.com/c/GeorgeSladkovsky
ArtStation作品集 https://www.artstation.com/sladkovsky3d
联系邮箱: george.sladkovsky@gmail.com
"""
from functools import partial
import maya.cmds as mc
from gs_curvetools.api.qt_compat import *
from gs_curvetools.config.constants import *
from gs_curvetools.core.core import Core
from gs_curvetools.ui.widgets import Button, Frame, MayaSlider, PopOutWindow, Row, maya_slider, separator
class RandomizeCurveWindow(QWidget):
"""创建一个用于随机化曲线的弹出窗口"""
def __init__(self, parent):
"""创建一个用于随机化曲线的弹出窗口"""
super(RandomizeCurveWindow, self).__init__(parent)
self.core = Core.singleton()
def open_ui(self):
window_name = WINDOWS.RandomizeCurveWindow.name
if mc.workspaceControl(window_name, q=1, ex=1):
if mc.workspaceControl(window_name, q=1, vis=1):
mc.deleteUI(window_name)
return
mc.deleteUI(window_name)
pop_out = PopOutWindow(window_name, '随机化曲线', 270, 565)
layout = pop_out.widget_layout
layout.setAlignment(QtCore.Qt.AlignmentFlag.AlignTop)
def release(slider, *_):
self.core.sliders.rand_slider_drag(slider)
self.core.sliders.rand_slider_release(slider)
self.core.sliders.release()
def create_enable_slider_row(frame_layout, button_name, slider_name, label):
with Row(frame_layout) as row:
enable = Button(row.layout(), button_name)
enable.set_label('启用', line_height=100)
enable.set_button_style('small')
enable.setCheckable(True)
slider = maya_slider(mc.floatSliderGrp(slider_name, l='倍数:', min=1, max=50, pre=1, step=0.5, cw=[(1, 30), (2, 1)]))
row_layout = row.layout()
assert row_layout is not None
row_layout.addWidget(slider)
with Frame(layout, label='控制点') as frame:
frame.set_collapsible(False)
create_enable_slider_row(frame.get_frame_layout(), 'curveRandomizeSlider0', 'gsCurveCVsRandMulti', '控制点')
with Row(frame.get_frame_layout()) as row:
lock_first = Button(row.layout(), 'gsLockFirstCV')
lock_first.set_label('锁定第一个控制点', line_height=100)
lock_first.set_button_style('small')
lock_first.setCheckable(True)
lock_last = Button(row.layout(), 'gsLockLastCV')
lock_last.set_label('锁定最后一个控制点', line_height=100)
lock_last.set_button_style('small')
lock_last.setCheckable(True)
with Row(frame.get_frame_layout()) as row:
axis_x = Button(row.layout(), 'gsRandAxisX')
axis_x.set_label('X', line_height=100)
axis_x.set_button_style('small')
axis_x.setCheckable(True)
axis_y = Button(row.layout(), 'gsRandAxisY')
axis_y.set_label('Y', line_height=100)
axis_y.set_button_style('small')
axis_y.setCheckable(True)
axis_z = Button(row.layout(), 'gsRandAxisZ')
axis_z.set_label('Z', line_height=100)
axis_z.set_button_style('small')
axis_z.setCheckable(True)
MayaSlider(mc.floatSliderGrp('gsCurveCVsRand', min=0, max=1, step=0.05, dc=partial(self.core.sliders.rand_slider_drag, 0), cc=partial(release, 0)), layout=frame.get_frame_layout())
with Frame(layout, label='旋转') as frame:
frame.set_collapsible(False)
create_enable_slider_row(frame.get_frame_layout(), 'curveRandomizeSlider1', 'gsCurveRotationRandMulti', '旋转')
with Row(frame.get_frame_layout()) as row:
axis_x = Button(row.layout(), 'gsRandRotateAxisX')
axis_x.set_label('X', line_height=100)
axis_x.set_button_style('small')
axis_x.setCheckable(True)
axis_y = Button(row.layout(), 'gsRandRotateAxisY')
axis_y.set_label('Y', line_height=100)
axis_y.set_button_style('small')
axis_y.setCheckable(True)
axis_z = Button(row.layout(), 'gsRandRotateAxisZ')
axis_z.set_label('Z', line_height=100)
axis_z.set_button_style('small')
axis_z.setCheckable(True)
MayaSlider(mc.floatSliderGrp('gsCurveRotationRand', min=0, max=1, step=0.05, dc=partial(self.core.sliders.rand_slider_drag, 1), cc=partial(release, 1)), layout=frame.get_frame_layout())
with Frame(layout, label='方向') as frame:
frame.set_collapsible(False)
create_enable_slider_row(frame.get_frame_layout(), 'curveRandomizeSlider2', 'gsCurveOrientationRandMulti', '方向')
MayaSlider(mc.floatSliderGrp('gsCurveOrientationRand', min=0, max=1, step=0.05, dc=partial(self.core.sliders.rand_slider_drag, 2), cc=partial(release, 2)), layout=frame.get_frame_layout())
with Frame(layout, label='扭曲') as frame:
frame.set_collapsible(False)
create_enable_slider_row(frame.get_frame_layout(), 'curveRandomizeSlider3', 'gsCurveTwistRandMulti', '扭曲')
MayaSlider(mc.floatSliderGrp('gsCurveTwistRand', min=0, max=1, step=0.05, dc=partial(self.core.sliders.rand_slider_drag, 3), cc=partial(release, 3)), layout=frame.get_frame_layout())
with Frame(layout, label='宽度') as frame:
frame.set_collapsible(False)
create_enable_slider_row(frame.get_frame_layout(), 'curveRandomizeSlider4', 'gsCurveWidthRandMulti', '宽度')
uniform = Button(frame.get_frame_layout(), 'gsWidthCheckBoxUniform')
uniform.set_button_style('small')
uniform.set_label('均匀', line_height=100)
uniform.setCheckable(True)
MayaSlider(mc.floatSliderGrp('gsCurveWidthRand', min=0.001, max=1, step=0.05, dc=partial(self.core.sliders.rand_slider_drag, 4), cc=partial(release, 4)), layout=frame.get_frame_layout())
with Frame(layout, label='锥度') as frame:
frame.set_collapsible(False)
create_enable_slider_row(frame.get_frame_layout(), 'curveRandomizeSlider5', 'gsCurveTaperRandMulti', '锥度')
MayaSlider(mc.floatSliderGrp('gsCurveTaperRand', min=0, max=1, step=0.05, dc=partial(self.core.sliders.rand_slider_drag, 5), cc=partial(release, 5)), layout=frame.get_frame_layout())
with Frame(layout, label='轮廓') as frame:
frame.set_collapsible(False)
create_enable_slider_row(frame.get_frame_layout(), 'curveRandomizeSlider6', 'gsCurveProfileRandMulti', '轮廓')
uniform = Button(frame.get_frame_layout(), 'gsProfileCheckBoxNegative')
uniform.set_button_style('small')
uniform.set_label('允许负值', line_height=100)
uniform.setCheckable(True)
MayaSlider(mc.floatSliderGrp('gsCurveProfileRand', min=0, max=1, step=0.05, dc=partial(self.core.sliders.rand_slider_drag, 6), cc=partial(release, 6)), layout=frame.get_frame_layout())
with Frame(layout, label='选择') as frame:
frame.set_collapsible(False)
enable = Button(frame.get_frame_layout(), 'curveRandomizeSlider7')
enable.set_label('启用', line_height=100)
enable.set_button_style('small')
enable.setCheckable(True)
MayaSlider(mc.floatSliderGrp('gsCurveSelectRand', min=0, max=1, step=0.05, dc=partial(self.core.sliders.rand_slider_drag, 7), cc=partial(release, 7)), layout=frame.get_frame_layout())
layout.addWidget(separator())
with Row(layout) as row:
def randomize_click():
self.core.sliders.rand_slider_drag(-1)
self.core.sliders.rand_slider_release(-1)
self.core.sliders.release()
randomize = Button(row.layout())
randomize.set_label('随机化')
randomize.clicked.connect(randomize_click)
close = Button(row.layout())
close.set_label('关闭')
close.clicked.connect(lambda: mc.deleteUI(window_name))

View File

@@ -0,0 +1,636 @@
"""
UV 编辑器窗口
---
GS CurveTools 许可证:
名为 GS CurveTools 的此代码集合归 George SladkovskyYehor Sladkovskyi所有
未经其书面许可,不得复制或分发。
GS CurveTools v1.3.10 个人版
版权所有 2025George SladkovskyYehor Sladkovskyi
保留所有权利
UI 字体为 Roboto遵循 Apache 2.0 许可证:
http://www.apache.org/licenses/LICENSE-2.0
Autodesk Maya 是 Autodesk, Inc 的产品:
https://www.autodesk.com/
社交媒体和联系方式:
Discord 服务器: https://discord.gg/f4DH6HQ
在线商店: https://sladkovsky3d.artstation.com/store
在线文档: https://gs-curvetools.readthedocs.io/
Twitch 频道: https://www.twitch.tv/videonomad
YouTube 频道: https://www.youtube.com/c/GeorgeSladkovsky
ArtStation 作品集: https://www.artstation.com/sladkovsky3d
联系邮箱: george.sladkovsky@gmail.com
"""
import os
from ast import literal_eval
import maya.cmds as mc
from gs_curvetools.api.maya_tools import deferred_lp, get_mod, no_undo, undo
from gs_curvetools.api.python import Timer
from gs_curvetools.api.qt_compat import *
from gs_curvetools.api.utils import clr_to_float, open_link
from gs_curvetools.config.constants import *
from gs_curvetools.core.core import Core
from gs_curvetools.debug.logger import logger
from gs_curvetools.managers.options_manager import OptionsManager
from gs_curvetools.managers.script_jobs import ScriptJobs
from gs_curvetools.managers.widget_manager import WidgetManager
from gs_curvetools.ui import style
from gs_curvetools.ui.components.uv_editor.editor import UVEditor
from gs_curvetools.ui.utils import maya_dockable_window
from gs_curvetools.ui.widgets import Button, ColorPicker, Column, Frame, Label, Row, UVItemList, separator
class UVEditorWindow(QWidget):
"""UV 编辑器窗口"""
def __init__(self, parent):
super(UVEditorWindow, self).__init__(parent)
self.core = Core.singleton()
self.widget_manager = WidgetManager()
self.options_manager = OptionsManager()
self.script_jobs = ScriptJobs.singleton()
self.timer = Timer()
self.mouse_toggle = False
self.previous_selection = []
self.uv_update_check = 0
self.isolate_mode = False
self.current_selection = []
self.controller_group = None
self.direction_switch = None
def open_ui(self):
"""UV 编辑器工作区控件"""
if mc.workspaceControl(WINDOWS.UVEditor.name, q=1, ex=1):
if not mc.workspaceControl(WINDOWS.UVEditor.name, q=1, vis=1):
mc.workspaceControl(WINDOWS.UVEditor.name, e=1, rs=1)
else:
mc.workspaceControl(WINDOWS.UVEditor.name, e=1, vis=0)
try:
self.update_editor()
return
except BaseException as exc:
logger.exception(exc)
return None
self.init()
self.update_editor()
self.script_jobs.check_script_jobs(WINDOWS.UVEditor.name)
def init(self):
"""初始化 UV 编辑器窗口"""
if mc.workspaceControl(WINDOWS.UVEditor.name, q=1, ex=1):
mc.deleteUI(WINDOWS.UVEditor.name)
parent = maya_dockable_window(name=WINDOWS.UVEditor.name, label=WINDOWS.UVEditor.label, i_w=705, i_h=575, width_property='free')
self.editor = UVEditor(parent)
self.editor.setParent(parent)
parent_layout = parent.layout()
assert parent_layout is not None
parent_layout.addWidget(self)
self.window()
self.editor.signal_key_press.connect(self.update_buttons)
self.editor.signal_mouse_move.connect(self._update_curves)
self.editor.signal_mouse_release.connect(self._stop_curves_update)
self.editor.signal_uv_draw_end.connect(self.manual_curve_update)
self.editor.signal_function_key_press.connect(undo(self.function_switch))
self.uv_list.signal_key_pressed.connect(self.update_visibility)
def window(self):
"""UV 编辑器窗口"""
main_layout = QtWidgets.QVBoxLayout(self)
main_layout.setContentsMargins(*style.scale([2, 2, 2, 2]))
scroll_widget = QtWidgets.QWidget()
layout = QtWidgets.QVBoxLayout(scroll_widget)
scroll_area = QtWidgets.QScrollArea(self)
scroll_area.setWidget(scroll_widget)
main_layout.addWidget(scroll_area)
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(style.scale(2))
layout.setAlignment(QtCore.Qt.AlignmentFlag.AlignTop)
scroll_area.setFrameShape(QtWidgets.QFrame.Shape.NoFrame)
scroll_area.setWidgetResizable(True)
self.editor.init()
self.update_colors()
with Row(layout) as main_row:
with Column(main_row.layout()) as main_column:
main_column_layout = main_column.layout()
assert main_column_layout is not None
main_column_layout.setAlignment(QtCore.Qt.AlignmentFlag.AlignTop)
main_column.setFixedWidth(style.scale(130))
main_column_layout.addWidget(separator())
controller_label = Label(main_column.layout())
controller_label.set_label('控制模式')
self.controller_group = QtWidgets.QButtonGroup(main_column)
self.controller_group.buttonReleased.connect(self.update_controller_mode)
with Row(main_column.layout()) as select_move:
select = Button(select_move.layout(), 'gsUVSelect')
select.setCheckable(True)
select.setChecked(True)
select.set_label('选择 (Q)')
move = Button(select_move.layout(), 'gsUVMove')
move.setCheckable(True)
move.set_label('移动 (W)')
with Row(main_column.layout()) as rotate_scale:
rotate = Button(rotate_scale.layout(), 'gsUVRotate')
rotate.setCheckable(True)
rotate.set_label('旋转 (E)')
scale = Button(rotate_scale.layout(), 'gsUVScale')
scale.setCheckable(True)
scale.set_label('缩放 (R)')
with Row(main_column.layout()) as scale_row:
self.direction_switch = QtWidgets.QButtonGroup(scale_row)
self.direction_switch.buttonReleased.connect(self.update_controller_mode)
horizontal = Button(scale_row.layout(), 'gsUVHorizontalScale')
horizontal.set_label('水平 (H)')
horizontal.set_button_style('small')
horizontal.setCheckable(True)
horizontal.setChecked(True)
vertical = Button(scale_row.layout(), 'gsUVVerticalScale')
vertical.set_button_style('small')
vertical.setCheckable(True)
vertical.set_label('垂直 (V)')
self.direction_switch.addButton(horizontal, 0)
self.direction_switch.addButton(vertical, 1)
draw_uv = Button(main_column.layout(), 'gsDrawUVs')
draw_uv.setCheckable(True)
draw_uv.set_label('绘制 (D)')
self.controller_group.addButton(select, 0)
self.controller_group.addButton(move, 1)
self.controller_group.addButton(rotate, 2)
self.controller_group.addButton(scale, 3)
self.controller_group.addButton(draw_uv, 4)
main_column_layout.addWidget(separator())
utility_label = Label(main_column.layout())
utility_label.set_label('实用功能')
with Row(main_column.layout()) as hv_flip_row:
h_flip_uv = Button(hv_flip_row.layout(), 'gsHorizontalFlipUV')
h_flip_uv.set_label('水平翻转 (H)')
h_flip_uv.clicked.connect(undo(self.horizontal_flip_uv))
v_flip_uv = Button(hv_flip_row.layout(), 'gsVerticalFlipUV')
v_flip_uv.set_label('垂直翻转 (V)')
v_flip_uv.clicked.connect(undo(self.vertical_flip_uv))
reset_uv = Button(main_column.layout(), 'gsResetUVs')
reset_uv.set_label('重置 UV (X)')
reset_uv.clicked.connect(undo(self.reset_uvs))
sync_selection = Button(main_column.layout(), 'gsSyncSelectionUVs')
sync_selection.set_label('同步选择 (S)')
sync_selection.clicked.connect(undo(self.sync_selection))
randomize_uvs = Button(main_column.layout(), 'gsRandomizeUVs')
randomize_uvs.set_label('随机化')
randomize_uvs.set_icon('mod-bottom')
randomize_uvs.clicked.connect(undo(self.randomize_uvs))
main_column_layout.addWidget(separator())
uv_list_label = Label(main_column.layout())
uv_list_label.set_label('UV 列表')
self.uv_list = UVItemList(main_column_layout)
self.uv_list.setLayout(main_column_layout)
self.uv_list.signal_mouse_released.connect(self.update_visibility)
focus_view = Button(main_column.layout(), 'gsFocusUVs')
focus_view.set_label('聚焦视图 (F)')
focus_view.clicked.connect(self.editor.focus_view)
with Row(main_column.layout()) as hide_isolate:
isolate_select = Button(hide_isolate.layout(), 'gsUVIsolateSelect')
isolate_select.set_label('隔离选择 (I)')
isolate_select.clicked.connect(self.isolate_select)
hide = Button(hide_isolate.layout(), 'gsUVHide')
hide.set_label('隐藏 (O)')
hide.clicked.connect(self.hide)
show_all_button = Button(main_column.layout(), 'gsUVShowAll')
show_all_button.set_label('显示全部 (A)')
show_all_button.clicked.connect(self.show_all)
main_column_layout.addWidget(separator())
with Frame(main_column.layout(), label='选项') as options_frame:
texture_functions_label = Label(options_frame.get_frame_layout())
texture_functions_label.set_label('纹理控制:')
transparency = Button(options_frame.get_frame_layout(), 'UVEditorTransparencyToggle')
transparency.set_button_style('small')
transparency.setCheckable(True)
transparency.setChecked(self.options_manager.get('UVEditorTransparencyToggle'))
transparency.set_label('透明度')
alpha_only = Button(options_frame.get_frame_layout(), 'UVEditorAlphaOnlyToggle')
alpha_only.set_button_style('small')
alpha_only.setCheckable(True)
alpha_only.setChecked(self.options_manager.get('UVEditorAlphaOnly'))
alpha_only.set_label('仅显示 Alpha 通道')
use_transforms = Button(options_frame.get_frame_layout(), 'UVEditorUseTransforms')
use_transforms.set_button_style('small')
use_transforms.setCheckable(True)
use_transforms.setChecked(True)
use_transforms.set_label('使用变换')
use_transforms.clicked.connect(self.update_texture)
def toggle_alpha_command():
if transparency.isChecked():
self.editor.toggle_alpha(True)
else:
self.editor.toggle_alpha(False)
alpha_only.setChecked(False)
self.update_texture()
self.options_manager.set('UVEditorTransparencyToggle', transparency.isChecked())
transparency.clicked.connect(toggle_alpha_command)
def toggle_alpha_only_command():
if alpha_only.isChecked():
transparency.setChecked(True)
else:
transparency.setChecked(False)
toggle_alpha_command()
self.options_manager.set('UVEditorAlphaOnly', alpha_only.isChecked())
alpha_only.clicked.connect(toggle_alpha_only_command)
options_frame.get_frame_layout().addWidget(separator())
texture_functions_label = Label(options_frame.get_frame_layout())
texture_functions_label.set_label('编辑器颜色:')
with Row(options_frame.get_frame_layout(), margins=style.scale([5, 0, 5, 5])) as color_row:
background_color_picker = ColorPicker('UVEditorBGColorPicker', color_row.layout())
background_color_picker.set_rgb_colors(clr_to_float(literal_eval(self.options_manager.get('UVEditorBGColor'))))
background_color_picker.connect_command(self.update_colors)
grid_color_picker = ColorPicker('UVEditorGridColorPicker', color_row.layout())
grid_color_picker.set_rgb_colors(clr_to_float(literal_eval(self.options_manager.get('UVEditorGridColor'))))
grid_color_picker.connect_command(self.update_colors)
frame_color_picker = ColorPicker('UVEditorFrameColorPicker', color_row.layout())
frame_color_picker.set_rgb_colors(clr_to_float(literal_eval(self.options_manager.get('UVEditorFrameColor'))))
frame_color_picker.connect_command(self.update_colors)
card_colors = Label(options_frame.get_frame_layout())
card_colors.set_label('UV 颜色:')
with Row(options_frame.get_frame_layout(), margins=style.scale([5, 0, 5, 5])) as color_row2:
selected_card_frame_color = ColorPicker('UVEditorUVFrameSelectedColorPicker', color_row2.layout())
selected_card_frame_color.set_rgb_colors(clr_to_float(literal_eval(self.options_manager.get('UVEditorUVFrameSelectedColor'))))
selected_card_frame_color.connect_command(self.update_colors)
deselected_card_frame_color = ColorPicker('UVEditorUVFrameDeselectedColorPicker', color_row2.layout())
deselected_card_frame_color.set_rgb_colors(clr_to_float(literal_eval(self.options_manager.get('UVEditorUVFrameDeselectedColor'))))
deselected_card_frame_color.connect_command(self.update_colors)
card_fill_color = ColorPicker('UVEditorUVCardFillColorPicker', color_row2.layout())
card_fill_color.set_rgb_colors(clr_to_float(literal_eval(self.options_manager.get('UVEditorUVCardFillColor'))))
card_fill_color.connect_command(self.update_colors)
main_column_layout.addWidget(separator())
main_row_layout = main_row.layout()
assert main_row_layout is not None
main_row_layout.addWidget(self.editor)
if MAYA_VER in [2020, 2022]:
if not self.options_manager.get('UVBugMessageDismissed'):
with Row(main_layout) as row:
label = Label(row.layout())
label.set_label('<p style="color:#FF542F">如何修复 Maya 2020 - 2022 的 UV 问题:</p>')
link_button = Button(row.layout())
link_button.set_label('打开修复方法')
link_button.set_button_style('small-filled')
link_button.clicked.connect(lambda: open_link('https://gs-curvetools.readthedocs.io/en/latest/faq.html#maya-2020-2022-and-broken-uvs'))
dismiss_message = Button(row.layout())
dismiss_message.set_label('忽略提示')
dismiss_message.set_button_style('small-filled')
dismiss_message.clicked.connect(lambda: row.setHidden(True))
dismiss_message.clicked.connect(lambda: self.options_manager.set('UVBugMessageDismissed', 1))
def update_colors(self, *_):
"""更新 UV 编辑器的颜色"""
if self.editor:
self.options_manager.save()
try:
self.editor.change_color(literal_eval(self.options_manager.get('UVEditorBGColor')), literal_eval(self.options_manager.get('UVEditorGridColor')), literal_eval(self.options_manager.get('UVEditorFrameColor')), literal_eval(self.options_manager.get('UVEditorUVCardFillColor')), literal_eval(self.options_manager.get('UVEditorUVFrameSelectedColor')))
except BaseException as exc:
self.editor.change_color()
logger.exception(exc)
self.editor.update()
@no_undo
def update_editor(self):
"""更新 UV 编辑器"""
self.update_item_list()
self.update_uvs()
self.update_texture()
def show_all(self):
"""显示所有 UV"""
self.uv_list.expandAll()
self.uv_list.selectAll()
self.update_visibility()
self.isolate_mode = False
@no_undo
def update_uvs(self, keep_selection=False):
"""更新 UV"""
sel = mc.filterExpand(mc.ls(sl=1, fl=1, l=1), sm=9)
old_uvs = self.editor.uv_dict
self.editor.purge_uvs()
if not sel:
return
sel = self.check_for_legacy_uvs(sel)
assert sel is not None
for item in sel:
if mc.attributeQuery('Orientation', n=item, ex=1) and mc.connectionInfo(item + '.Orientation', isSource=1):
if mc.attributeQuery('gsmessage', n=item, ex=1) and mc.listConnections(item + '.gsmessage'):
curves = self.core.utils.get_all_connected_curves(item)
else:
curves = [item]
for curve in curves:
attrs = self.core.attributes.get_uvs(curve)
checkbox = self.core.attributes.get_checkboxes(curve)
if attrs:
uv_item = self.editor.create_uv(curve)
if 'flipUV' in checkbox:
uv_item.flip = not checkbox['flipUV']
uv_item.move_uv(x=attrs['moveU'], y=attrs['moveV'], rot=attrs['rotateUV'] * -1, s_x=attrs['scaleU'], s_y=attrs['scaleV'])
if keep_selection:
new_uvs = self.editor.uv_dict
for key, val in new_uvs.items():
if key in old_uvs:
val.setSelected(True)
def check_for_legacy_uvs(self, curves):
"""检查是否存在旧版 UV"""
legacy_curves = []
for curve in curves:
root = mc.attributeQuery('rotateRootUV', n=curve, ex=1)
tip = mc.attributeQuery('rotateTipUV', n=curve, ex=1)
if root or tip:
root_value = mc.getAttr(curve + '.rotateRootUV')
tip_value = mc.getAttr(curve + '.rotateTipUV')
if root_value or tip_value:
legacy_curves.append(curve)
if not legacy_curves:
return curves
logger.warning('检测到非零旧版 UV 属性', in_view=True)
dialog = mc.confirmDialog(title='旧版 UV', message='检测到非零旧版 UV。\nUV 编辑器与此不兼容。\n是否将其归零以继续?', icon='warning', button=['', '取消'], cancelButton='取消', dismissString='取消')
if dialog == '':
for curve in legacy_curves:
mc.setAttr(curve + '.rotateRootUV', 0)
mc.setAttr(curve + '.rotateTipUV', 0)
return curves
elif dialog == '取消':
dialog = mc.confirmDialog(title='旧版 UV', message='UV 编辑器与旧版 UV 不兼容。\n部分卡片将被忽略。', icon='信息', button='确定', cancelButton='确定', dismissString='确定')
return list(set(curves) - set(legacy_curves))
@no_undo
def update_texture(self):
"""更新纹理"""
sel = self.core.utils.select_part(2, just_return=True)
if not sel:
self.editor.remove_texture()
self.editor.diffuse_path = ''
return
geo = mc.filterExpand(sel, sm=12)
if not geo:
return
dag = mc.ls(geo[-1], dag=1, s=1)
shader = mc.listConnections(dag, d=1, s=1, t='shadingEngine')
if not shader:
return
diffuse_file_node, alpha_file_node = self.core.utils.find_diffuse_alpha(shader[0])
if not diffuse_file_node and (not alpha_file_node):
return
texture_path = None
alpha_path = None
if diffuse_file_node:
texture_path = mc.getAttr(diffuse_file_node + '.fileTextureName')
if alpha_file_node:
alpha_path = mc.getAttr(alpha_file_node + '.fileTextureName') if alpha_file_node else None
if not texture_path and alpha_path:
texture_path = alpha_path
diffuse_file_node = alpha_file_node
place_2d_texture = None
if mc.attributeQuery('coverage', n=diffuse_file_node, ex=1):
info = mc.connectionInfo(diffuse_file_node + '.coverage', sfd=1)
if info:
place_2d_texture = info.split('.')
if place_2d_texture:
place_2d_texture = place_2d_texture[0]
cov, trans = (None, None)
if place_2d_texture and mc.objExists(place_2d_texture) and self.widget_manager.get('UVEditorUseTransforms').isChecked() and mc.attributeQuery('coverage', ex=1, n=place_2d_texture) and mc.attributeQuery('translateFrame', ex=1, n=place_2d_texture):
try:
cov = mc.getAttr(place_2d_texture + '.coverage')[0]
trans = mc.getAttr(place_2d_texture + '.translateFrame')[0]
except BaseException as exc:
logger.exception(exc)
coverage = cov if cov else (1.0, 1.0)
translation = (trans[0], trans[1]) if trans and trans else (0, 0)
if not self.widget_manager.get('UVEditorTransparencyToggle').isChecked() and (not self.widget_manager.get('UVEditorAlphaOnlyToggle').isChecked()):
alpha_path = None
if texture_path:
_, ext = os.path.splitext(texture_path)
try:
supported_formats = [str(x, encoding='ASCII').lower() for x in QtGui.QImageReader.supportedImageFormats()]
except TypeError:
supported_formats = [str(x).decode('ASCII').lower() for x in QtGui.QImageReader.supportedImageFormats()]
if ext and ext[1:].lower() not in supported_formats:
logger.warning('{} 格式不受支持。请使用 JPG/JPEG、PNG、TIF/TIFFLZW 或无压缩、TGA24 位,无 RLE'.format(ext[1:].upper()))
return
err = self.editor.set_texture('%s' % texture_path, alpha_path, coverage, translation)
if err == 'SamePath':
return
if err == 'NoTexture':
logger.warning('无法加载纹理文件。')
elif err == 'ZeroTexture':
logger.warning('无效路径或零纹理。请检查导出的纹理位深度和压缩方式。TIF/TIFFLZW 或无压缩、TGA24 位,无 RLE')
def manual_curve_update(self):
"""手动更新 UV 曲线"""
try:
self._update_curves()
except BaseException as exc:
logger.exception(exc)
finally:
self._stop_curves_update()
def _update_curves(self):
if self.uv_update_check == 0:
mc.undoInfo(ock=1, cn='gsUVUpdate')
self.uv_update_check = 1
sel = mc.filterExpand(mc.ls(sl=1), sm=9)
uvs = self.editor.get_uvs()
self.current_selection *= 0
if not sel:
return
for curve in sel:
if curve in uvs:
self.current_selection.append(curve)
elif mc.attributeQuery('gsmessage', n=curve, ex=1) and mc.listConnections(curve + '.gsmessage'):
bound_curves = self.core.utils.get_all_connected_curves(curve)
if bound_curves:
self.current_selection += bound_curves
if not self.timer.increment(0.016666666666666666):
return
if not self.current_selection:
return
uvs = self.editor.get_uvs()
for curve in self.current_selection:
if curve in uvs:
self.core.attributes.set_attributes(curve, uvs[curve])
def _stop_curves_update(self):
if self.uv_update_check == 1:
mc.undoInfo(cck=1)
self.uv_update_check = 0
self.current_selection *= 0
self.core.curve_control.update_ui()
def update_buttons(self, controller_mode, scale_mode):
"""更新按钮状态"""
assert self.controller_group is not None
assert self.direction_switch is not None
buttons = self.controller_group.buttons()
direction = self.direction_switch.buttons()
if controller_mode == 'SELECT':
buttons[0].setChecked(True)
elif controller_mode == 'MOVE':
buttons[1].setChecked(True)
elif controller_mode == 'ROTATE':
buttons[2].setChecked(True)
elif controller_mode == 'SCALE':
buttons[3].setChecked(True)
if scale_mode == 'H':
direction[0].setChecked(True)
else:
direction[1].setChecked(True)
elif controller_mode == 'DRAW':
buttons[4].setChecked(True)
def update_controller_mode(self):
"""更新控制模式"""
assert self.controller_group is not None
assert self.direction_switch is not None
button_id = self.controller_group.checkedId()
scale_id = self.direction_switch.checkedId()
scale = 'H'
if button_id == 0:
mode = 'SELECT'
elif button_id == 1:
mode = 'MOVE'
elif button_id == 2:
mode = 'ROTATE'
elif button_id == 3:
mode = 'SCALE'
if scale_id == 0:
scale = 'H'
else:
scale = 'V'
else:
mode = 'DRAW'
self.editor.controller_mode_change(mode, scale)
def update_item_list(self):
"""更新项目列表"""
sel = mc.filterExpand(mc.ls(sl=1), sm=9)
if not sel:
self.uv_list.clear_item_list()
return
final_dict = {}
for curve in sel:
if mc.attributeQuery('Orientation', n=curve, ex=1) and mc.connectionInfo(curve + '.Orientation', isSource=1):
if mc.attributeQuery('gsmessage', n=curve, ex=1) and mc.listConnections(curve + '.gsmessage'):
bound_curves = self.core.utils.get_all_connected_curves(curve)
final_dict[curve] = bound_curves
else:
final_dict[curve] = []
self.uv_list.update_item_list(final_dict)
def update_visibility(self):
"""更新 UV 的可见性"""
item_list = self.uv_list.get_selection()
items = self.editor.get_all_uvs()
for item in items:
if item.name in item_list:
item.setVisible(True)
else:
item.setVisible(False)
self.editor.update()
def hide(self):
"""隐藏选中的 UV 项目"""
sel_uvs = self.editor.get_all_uvs(selected=True)
sel_uvs_list = [i.name for i in sel_uvs]
for uv_item in sel_uvs:
uv_item.setVisible(False)
item_list = self.uv_list.get_item_list()
select_list = []
for item in item_list:
if item.curve_name in sel_uvs_list:
select_list.append(item)
self.uv_list.select_items(select_list)
def isolate_select(self):
"""隔离选中的 UV 项目"""
all_uvs = self.editor.get_all_uvs()
sel_uvs = self.editor.get_all_uvs(selected=True)
if not sel_uvs:
return
if self.isolate_mode:
self.show_all()
self.isolate_mode = False
return
self.isolate_mode = True
sel_uvs_list = [i.name for i in sel_uvs]
for uv_item in all_uvs:
if uv_item.name not in sel_uvs_list:
uv_item.setVisible(False)
item_list = self.uv_list.get_item_list()
deselect_list = []
for item in item_list:
if item.curve_name not in sel_uvs_list:
deselect_list.append(item)
self.uv_list.select_items(deselect_list)
def horizontal_flip_uv(self):
"""水平翻转 UV"""
items = self.editor.get_all_uvs(selected=True)
for item in items:
if item.name and mc.attributeQuery('flipUV', n=item.name, ex=1):
flip = mc.getAttr(item.name + '.flipUV')
mc.setAttr(item.name + '.flipUV', not flip)
item.flip = flip
item.update()
if items:
self.editor.update()
def vertical_flip_uv(self):
"""垂直翻转 UV"""
self.editor.vertical_flip_uvs()
self.manual_curve_update()
def function_switch(self, key):
"""功能切换"""
if key == 'H':
self.horizontal_flip_uv()
elif key == 'V':
self.vertical_flip_uv()
elif key == 'X':
self.reset_uvs()
elif key == 'I':
self.isolate_select()
elif key == 'O':
self.hide()
elif key == 'A':
self.show_all()
elif key == 'S':
self.sync_selection()
def reset_uvs(self):
"""重置 UV"""
self.editor.reset_uvs()
self.manual_curve_update()
def randomize_uvs(self):
"""随机化 UV"""
if get_mod() == 'Shift':
self.editor.randomize_uvs(True)
else:
self.editor.randomize_uvs(False)
self.manual_curve_update()
def sync_selection(self):
"""根据 UV 编辑器的选择在 Maya 视口中选择曲线"""
sel = mc.filterExpand(mc.ls(sl=1), sm=9)
if not sel:
return
sel_uvs = [x.name for x in self.editor.get_all_uvs(selected=True)]
new_sel = [x for x in sel if x in sel_uvs]
if new_sel:
mc.select(new_sel, r=1)
@deferred_lp
def x():
uvs = self.editor.get_all_uvs()
for uv_item in uvs:
uv_item.setSelected(True)
self.editor.scene().update()
x()