Update
This commit is contained in:
@@ -490,34 +490,20 @@ class RiggingUI(ui_utils.BaseUI):
|
|||||||
#======================================= FUNCTIONS ======================================
|
#======================================= FUNCTIONS ======================================
|
||||||
def create_connections(self):
|
def create_connections(self):
|
||||||
"""
|
"""
|
||||||
创建信号连接,设置UI控件的交互行为(风格参考definition.py)
|
创建信号连接,设置UI控件的交互行为
|
||||||
"""
|
"""
|
||||||
# 主要功能按钮直接连接
|
# 导入绑定工具函数
|
||||||
if "add_joint_btn" in self.buttons:
|
from scripts.utils import utils_rigging
|
||||||
self.buttons["add_joint_btn"].clicked.connect(utils_rigging.add_joint)
|
|
||||||
if "remove_joint_btn" in self.buttons:
|
|
||||||
self.buttons["remove_joint_btn"].clicked.connect(utils_rigging.remove_joint)
|
|
||||||
if "duplicate_joint_btn" in self.buttons:
|
|
||||||
self.buttons["duplicate_joint_btn"].clicked.connect(utils_rigging.duplicate_joint)
|
|
||||||
if "add_controller_btn" in self.buttons:
|
|
||||||
self.buttons["add_controller_btn"].clicked.connect(utils_rigging.add_controller)
|
|
||||||
if "remove_controller_btn" in self.buttons:
|
|
||||||
self.buttons["remove_controller_btn"].clicked.connect(utils_rigging.remove_controller)
|
|
||||||
if "duplicate_controller_btn" in self.buttons:
|
|
||||||
self.buttons["duplicate_controller_btn"].clicked.connect(utils_rigging.duplicate_controller)
|
|
||||||
if "import_dna_btn" in self.buttons:
|
|
||||||
self.buttons["import_dna_btn"].clicked.connect(utils_rigging.import_dna)
|
|
||||||
if "export_dna_btn" in self.buttons:
|
|
||||||
self.buttons["export_dna_btn"].clicked.connect(utils_rigging.export_dna)
|
|
||||||
if "calibrate_dna_btn" in self.buttons:
|
|
||||||
self.buttons["calibrate_dna_btn"].clicked.connect(utils_rigging.calibrate_dna)
|
|
||||||
|
|
||||||
# 其它已创建按钮
|
# 预设导入和导出按钮
|
||||||
if "browse_path" in self.buttons:
|
self.buttons["export_presets"].clicked.connect(utils_rigging.export_dna)
|
||||||
|
self.buttons["import_presets"].clicked.connect(utils_rigging.import_dna)
|
||||||
|
|
||||||
|
# 浏览文件按钮连接
|
||||||
self.buttons["browse_path"].clicked.connect(lambda: utils_rigging.browse_file(self, "项目路径", self.controls["project_path_input"]))
|
self.buttons["browse_path"].clicked.connect(lambda: utils_rigging.browse_file(self, "项目路径", self.controls["project_path_input"]))
|
||||||
if "browse_dna" in self.buttons:
|
|
||||||
self.buttons["browse_dna"].clicked.connect(lambda: utils_rigging.browse_file(self, "DNA文件", self.controls["presets_dna_input"], "dna"))
|
self.buttons["browse_dna"].clicked.connect(lambda: utils_rigging.browse_file(self, "DNA文件", self.controls["presets_dna_input"], "dna"))
|
||||||
if "export_presets" in self.buttons:
|
|
||||||
self.buttons["export_presets"].clicked.connect(utils_rigging.export_presets)
|
# 底部按钮连接
|
||||||
if "import_presets" in self.buttons:
|
self.buttons["remove_all"].clicked.connect(utils_rigging.remove_all)
|
||||||
self.buttons["import_presets"].clicked.connect(utils_rigging.import_presets)
|
self.buttons["import_skeleton"].clicked.connect(utils_rigging.import_skeleton)
|
||||||
|
self.buttons["build_rigging"].clicked.connect(utils_rigging.build_rigging)
|
@@ -58,313 +58,41 @@ original_controller_properties = {}
|
|||||||
#========================================== FUNCTIONS ========================================
|
#========================================== FUNCTIONS ========================================
|
||||||
|
|
||||||
#------------------------------------ UI UTILITIES ------------------------------------
|
#------------------------------------ UI UTILITIES ------------------------------------
|
||||||
def handle_ui_event(event_type, event_data):
|
def browse_file(parent_widget, title, line_edit, file_type=None):
|
||||||
"""
|
"""
|
||||||
处理UI事件
|
浏览文件对话框
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
event_type: 事件类型
|
parent_widget: 父窗口
|
||||||
event_data: 事件数据
|
title: 对话框标题
|
||||||
|
line_edit: 要更新的文本框
|
||||||
|
file_type: 文件类型筛选
|
||||||
"""
|
"""
|
||||||
if event_type == "button_click":
|
try:
|
||||||
# 处理按钮点击事件
|
# 设置文件过滤器
|
||||||
print(f"Button clicked: {event_data}")
|
if file_type == "dna":
|
||||||
elif event_type == "slider_change":
|
file_filter = "DNA文件 (*.dna);;所有文件 (*.*)"
|
||||||
# 处理滑条改变事件
|
elif file_type == "fbx":
|
||||||
print(f"Slider changed: {event_data}")
|
file_filter = "FBX文件 (*.fbx);;所有文件 (*.*)"
|
||||||
else:
|
else:
|
||||||
print(f"Unknown event type: {event_type}")
|
file_filter = "所有文件 (*.*)"
|
||||||
|
|
||||||
def connect_ui_signals(ui_widget, event_type, event_handler):
|
# 打开文件选择对话框
|
||||||
"""
|
file_path = cmds.fileDialog2(
|
||||||
连接UI信号
|
fileFilter=file_filter,
|
||||||
|
dialogStyle=2,
|
||||||
|
caption=TEXT(title, title)
|
||||||
|
)
|
||||||
|
|
||||||
Args:
|
if file_path:
|
||||||
ui_widget: UI控件
|
# 更新文本框
|
||||||
event_type: 事件类型
|
line_edit.setText(file_path[0])
|
||||||
event_handler: 事件处理函数
|
return file_path[0]
|
||||||
"""
|
|
||||||
if event_type == "button_click":
|
|
||||||
# 连接按钮点击信号
|
|
||||||
ui_widget.clicked.connect(event_handler)
|
|
||||||
elif event_type == "slider_change":
|
|
||||||
# 连接滑条改变信号
|
|
||||||
ui_widget.valueChanged.connect(event_handler)
|
|
||||||
else:
|
|
||||||
print(f"Unknown event type: {event_type}")
|
|
||||||
|
|
||||||
#------------------------------------ JOINT OPERATIONS ------------------------------------
|
|
||||||
def add_joint():
|
|
||||||
"""
|
|
||||||
添加关节
|
|
||||||
|
|
||||||
在场景中创建新的关节并添加到骨骼层级结构中
|
|
||||||
"""
|
|
||||||
# 在场景中创建新的关节
|
|
||||||
try:
|
|
||||||
# 创建新关节
|
|
||||||
new_joint = cmds.joint(name="joint1", position=[0, 0, 0])
|
|
||||||
print(f"成功创建关节: {new_joint}")
|
|
||||||
return new_joint
|
|
||||||
except Exception as e:
|
|
||||||
print(f"创建关节失败: {str(e)}")
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def remove_joint():
|
|
||||||
"""
|
|
||||||
移除选中的关节
|
|
||||||
|
|
||||||
从场景中删除选中的关节
|
|
||||||
"""
|
|
||||||
# 获取当前选中的关节
|
|
||||||
selected = cmds.ls(selection=True, type="joint")
|
|
||||||
if not selected:
|
|
||||||
print("没有选中关节")
|
|
||||||
return False
|
|
||||||
|
|
||||||
try:
|
|
||||||
# 删除选中的关节
|
|
||||||
for joint in selected:
|
|
||||||
cmds.delete(joint)
|
|
||||||
print(f"成功删除关节: {selected}")
|
|
||||||
return True
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"删除关节失败: {str(e)}")
|
print(f"浏览文件失败: {str(e)}")
|
||||||
return False
|
|
||||||
|
|
||||||
def duplicate_joint():
|
|
||||||
"""
|
|
||||||
复制选中的关节
|
|
||||||
|
|
||||||
复制场景中选中的关节及其属性
|
|
||||||
"""
|
|
||||||
# 获取当前选中的关节
|
|
||||||
selected = cmds.ls(selection=True, type="joint")
|
|
||||||
if not selected:
|
|
||||||
print("没有选中关节")
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
try:
|
|
||||||
# 复制选中的关节
|
|
||||||
duplicated = cmds.duplicate(selected, returnRootsOnly=True)
|
|
||||||
print(f"成功复制关节: {duplicated}")
|
|
||||||
return duplicated
|
|
||||||
except Exception as e:
|
|
||||||
print(f"复制关节失败: {str(e)}")
|
|
||||||
return None
|
|
||||||
|
|
||||||
def update_joint_properties(joint_name, position=None, rotation=None, scale=None):
|
|
||||||
"""
|
|
||||||
更新关节属性
|
|
||||||
|
|
||||||
Args:
|
|
||||||
joint_name: 关节名称
|
|
||||||
position: 位置坐标 [x, y, z]
|
|
||||||
rotation: 旋转角度 [rx, ry, rz]
|
|
||||||
scale: 缩放比例 [sx, sy, sz]
|
|
||||||
"""
|
|
||||||
if not cmds.objExists(joint_name):
|
|
||||||
print(f"关节不存在: {joint_name}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
try:
|
|
||||||
# 更新位置
|
|
||||||
if position:
|
|
||||||
cmds.setAttr(f"{joint_name}.translateX", position[0])
|
|
||||||
cmds.setAttr(f"{joint_name}.translateY", position[1])
|
|
||||||
cmds.setAttr(f"{joint_name}.translateZ", position[2])
|
|
||||||
|
|
||||||
# 更新旋转
|
|
||||||
if rotation:
|
|
||||||
cmds.setAttr(f"{joint_name}.rotateX", rotation[0])
|
|
||||||
cmds.setAttr(f"{joint_name}.rotateY", rotation[1])
|
|
||||||
cmds.setAttr(f"{joint_name}.rotateZ", rotation[2])
|
|
||||||
|
|
||||||
# 更新缩放
|
|
||||||
if scale:
|
|
||||||
cmds.setAttr(f"{joint_name}.scaleX", scale[0])
|
|
||||||
cmds.setAttr(f"{joint_name}.scaleY", scale[1])
|
|
||||||
cmds.setAttr(f"{joint_name}.scaleZ", scale[2])
|
|
||||||
|
|
||||||
print(f"成功更新关节属性: {joint_name}")
|
|
||||||
return True
|
|
||||||
except Exception as e:
|
|
||||||
print(f"更新关节属性失败: {str(e)}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
def reset_joint_properties(joint_name):
|
|
||||||
"""
|
|
||||||
重置关节属性
|
|
||||||
|
|
||||||
Args:
|
|
||||||
joint_name: 关节名称
|
|
||||||
"""
|
|
||||||
if not cmds.objExists(joint_name):
|
|
||||||
print(f"关节不存在: {joint_name}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
try:
|
|
||||||
# 重置位置
|
|
||||||
cmds.setAttr(f"{joint_name}.translateX", 0)
|
|
||||||
cmds.setAttr(f"{joint_name}.translateY", 0)
|
|
||||||
cmds.setAttr(f"{joint_name}.translateZ", 0)
|
|
||||||
|
|
||||||
# 重置旋转
|
|
||||||
cmds.setAttr(f"{joint_name}.rotateX", 0)
|
|
||||||
cmds.setAttr(f"{joint_name}.rotateY", 0)
|
|
||||||
cmds.setAttr(f"{joint_name}.rotateZ", 0)
|
|
||||||
|
|
||||||
# 重置缩放
|
|
||||||
cmds.setAttr(f"{joint_name}.scaleX", 1)
|
|
||||||
cmds.setAttr(f"{joint_name}.scaleY", 1)
|
|
||||||
cmds.setAttr(f"{joint_name}.scaleZ", 1)
|
|
||||||
|
|
||||||
print(f"成功重置关节属性: {joint_name}")
|
|
||||||
return True
|
|
||||||
except Exception as e:
|
|
||||||
print(f"重置关节属性失败: {str(e)}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
#------------------------------------ CONTROLLER OPERATIONS ------------------------------------
|
|
||||||
def add_controller():
|
|
||||||
"""
|
|
||||||
添加控制器
|
|
||||||
|
|
||||||
在场景中创建新的控制器
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
# 创建控制器曲线
|
|
||||||
circle = cmds.circle(name="controller1", normal=[0, 1, 0], radius=1)[0]
|
|
||||||
print(f"成功创建控制器: {circle}")
|
|
||||||
return circle
|
|
||||||
except Exception as e:
|
|
||||||
print(f"创建控制器失败: {str(e)}")
|
|
||||||
return None
|
|
||||||
|
|
||||||
def remove_controller():
|
|
||||||
"""
|
|
||||||
移除选中的控制器
|
|
||||||
|
|
||||||
从场景中删除选中的控制器
|
|
||||||
"""
|
|
||||||
# 获取当前选中的控制器
|
|
||||||
selected = cmds.ls(selection=True, type="transform")
|
|
||||||
if not selected:
|
|
||||||
print("没有选中控制器")
|
|
||||||
return False
|
|
||||||
|
|
||||||
try:
|
|
||||||
# 删除选中的控制器
|
|
||||||
for ctrl in selected:
|
|
||||||
cmds.delete(ctrl)
|
|
||||||
print(f"成功删除控制器: {selected}")
|
|
||||||
return True
|
|
||||||
except Exception as e:
|
|
||||||
print(f"删除控制器失败: {str(e)}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
def duplicate_controller():
|
|
||||||
"""
|
|
||||||
复制选中的控制器
|
|
||||||
|
|
||||||
复制场景中选中的控制器及其属性
|
|
||||||
"""
|
|
||||||
# 获取当前选中的控制器
|
|
||||||
selected = cmds.ls(selection=True, type="transform")
|
|
||||||
if not selected:
|
|
||||||
print("没有选中控制器")
|
|
||||||
return None
|
|
||||||
|
|
||||||
try:
|
|
||||||
# 复制选中的控制器
|
|
||||||
duplicated = cmds.duplicate(selected, returnRootsOnly=True)
|
|
||||||
print(f"成功复制控制器: {duplicated}")
|
|
||||||
return duplicated
|
|
||||||
except Exception as e:
|
|
||||||
print(f"复制控制器失败: {str(e)}")
|
|
||||||
return None
|
|
||||||
|
|
||||||
def update_controller_properties(controller_name, position=None, rotation=None, scale=None, color=None):
|
|
||||||
"""
|
|
||||||
更新控制器属性
|
|
||||||
|
|
||||||
Args:
|
|
||||||
controller_name: 控制器名称
|
|
||||||
position: 位置坐标 [x, y, z]
|
|
||||||
rotation: 旋转角度 [rx, ry, rz]
|
|
||||||
scale: 缩放比例 [sx, sy, sz]
|
|
||||||
color: 控制器颜色索引
|
|
||||||
"""
|
|
||||||
if not cmds.objExists(controller_name):
|
|
||||||
print(f"控制器不存在: {controller_name}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
try:
|
|
||||||
# 更新位置
|
|
||||||
if position:
|
|
||||||
cmds.setAttr(f"{controller_name}.translateX", position[0])
|
|
||||||
cmds.setAttr(f"{controller_name}.translateY", position[1])
|
|
||||||
cmds.setAttr(f"{controller_name}.translateZ", position[2])
|
|
||||||
|
|
||||||
# 更新旋转
|
|
||||||
if rotation:
|
|
||||||
cmds.setAttr(f"{controller_name}.rotateX", rotation[0])
|
|
||||||
cmds.setAttr(f"{controller_name}.rotateY", rotation[1])
|
|
||||||
cmds.setAttr(f"{controller_name}.rotateZ", rotation[2])
|
|
||||||
|
|
||||||
# 更新缩放
|
|
||||||
if scale:
|
|
||||||
cmds.setAttr(f"{controller_name}.scaleX", scale[0])
|
|
||||||
cmds.setAttr(f"{controller_name}.scaleY", scale[1])
|
|
||||||
cmds.setAttr(f"{controller_name}.scaleZ", scale[2])
|
|
||||||
|
|
||||||
# 更新颜色
|
|
||||||
if color is not None:
|
|
||||||
shapes = cmds.listRelatives(controller_name, shapes=True)
|
|
||||||
if shapes:
|
|
||||||
for shape in shapes:
|
|
||||||
cmds.setAttr(f"{shape}.overrideEnabled", 1)
|
|
||||||
cmds.setAttr(f"{shape}.overrideColor", color)
|
|
||||||
|
|
||||||
print(f"成功更新控制器属性: {controller_name}")
|
|
||||||
return True
|
|
||||||
except Exception as e:
|
|
||||||
print(f"更新控制器属性失败: {str(e)}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
def reset_controller_properties(controller_name):
|
|
||||||
"""
|
|
||||||
重置控制器属性
|
|
||||||
|
|
||||||
Args:
|
|
||||||
controller_name: 控制器名称
|
|
||||||
"""
|
|
||||||
if not cmds.objExists(controller_name):
|
|
||||||
print(f"控制器不存在: {controller_name}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
try:
|
|
||||||
# 重置位置
|
|
||||||
cmds.setAttr(f"{controller_name}.translateX", 0)
|
|
||||||
cmds.setAttr(f"{controller_name}.translateY", 0)
|
|
||||||
cmds.setAttr(f"{controller_name}.translateZ", 0)
|
|
||||||
|
|
||||||
# 重置旋转
|
|
||||||
cmds.setAttr(f"{controller_name}.rotateX", 0)
|
|
||||||
cmds.setAttr(f"{controller_name}.rotateY", 0)
|
|
||||||
cmds.setAttr(f"{controller_name}.rotateZ", 0)
|
|
||||||
|
|
||||||
# 重置缩放
|
|
||||||
cmds.setAttr(f"{controller_name}.scaleX", 1)
|
|
||||||
cmds.setAttr(f"{controller_name}.scaleY", 1)
|
|
||||||
cmds.setAttr(f"{controller_name}.scaleZ", 1)
|
|
||||||
|
|
||||||
print(f"成功重置控制器属性: {controller_name}")
|
|
||||||
return True
|
|
||||||
except Exception as e:
|
|
||||||
print(f"重置控制器属性失败: {str(e)}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
#------------------------------------ DNA OPERATIONS ------------------------------------
|
#------------------------------------ DNA OPERATIONS ------------------------------------
|
||||||
def import_dna():
|
def import_dna():
|
||||||
"""
|
"""
|
||||||
@@ -373,20 +101,23 @@ def import_dna():
|
|||||||
从文件中导入DNA数据
|
从文件中导入DNA数据
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
# 打开文件对话框选择DNA文件
|
# 打开文件选择对话框
|
||||||
file_path = cmds.fileDialog2(fileFilter="DNA Files (*.dna);;All Files (*.*)", dialogStyle=2, fileMode=1)
|
file_path = cmds.fileDialog2(
|
||||||
|
fileFilter="DNA文件 (*.dna);;所有文件 (*.*)",
|
||||||
|
dialogStyle=2,
|
||||||
|
caption="选择DNA文件"
|
||||||
|
)
|
||||||
|
|
||||||
if not file_path:
|
if not file_path:
|
||||||
return None
|
print("未选择文件")
|
||||||
|
return False
|
||||||
|
|
||||||
file_path = file_path[0] # 获取选中的文件路径
|
print(f"导入DNA文件: {file_path[0]}")
|
||||||
|
# 实现DNA导入逻辑
|
||||||
# 这里应该调用DNA导入API
|
return True
|
||||||
# 暂时使用打印信息代替
|
|
||||||
print(f"导入DNA文件: {file_path}")
|
|
||||||
return file_path
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"导入DNA文件失败: {str(e)}")
|
print(f"导入DNA文件失败: {str(e)}")
|
||||||
return None
|
return False
|
||||||
|
|
||||||
def export_dna():
|
def export_dna():
|
||||||
"""
|
"""
|
||||||
@@ -395,501 +126,143 @@ def export_dna():
|
|||||||
将当前绑定数据导出为DNA文件
|
将当前绑定数据导出为DNA文件
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
# 打开文件对话框选择保存路径
|
# 打开文件保存对话框
|
||||||
file_path = cmds.fileDialog2(fileFilter="DNA Files (*.dna);;All Files (*.*)", dialogStyle=2, fileMode=0)
|
file_path = cmds.fileDialog2(
|
||||||
if not file_path:
|
fileFilter="DNA文件 (*.dna);;所有文件 (*.*)",
|
||||||
return None
|
dialogStyle=2,
|
||||||
|
caption="保存DNA文件",
|
||||||
|
fileMode=0
|
||||||
|
)
|
||||||
|
|
||||||
file_path = file_path[0] # 获取选中的文件路径
|
if not file_path:
|
||||||
|
print("未选择保存位置")
|
||||||
|
return False
|
||||||
|
|
||||||
# 确保文件扩展名为.dna
|
# 确保文件扩展名为.dna
|
||||||
if not file_path.lower().endswith(".dna"):
|
if not file_path[0].lower().endswith('.dna'):
|
||||||
file_path += ".dna"
|
file_path[0] += '.dna'
|
||||||
|
|
||||||
# 这里应该调用DNA导出API
|
print(f"导出DNA文件: {file_path[0]}")
|
||||||
# 暂时使用打印信息代替
|
# 实现DNA导出逻辑
|
||||||
print(f"导出DNA文件: {file_path}")
|
return True
|
||||||
return file_path
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"导出DNA文件失败: {str(e)}")
|
print(f"导出DNA文件失败: {str(e)}")
|
||||||
return None
|
return False
|
||||||
|
|
||||||
def calibrate_dna():
|
#------------------------------------ Rigging ------------------------------------
|
||||||
"""
|
def remove_all(*args):
|
||||||
校准DNA数据
|
"""移除所有绑定组件
|
||||||
|
|
||||||
校准当前绑定数据与DNA标准
|
从场景中删除所有绑定相关的组件,包括关节、控制器等
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
# 这里应该调用DNA校准API
|
# 获取所有关节
|
||||||
# 暂时使用打印信息代替
|
all_joints = cmds.ls(type="joint")
|
||||||
print("校准DNA数据")
|
|
||||||
|
# 获取所有可能的控制器(这里简化为所有曲线)
|
||||||
|
all_controllers = cmds.ls(type="nurbsCurve")
|
||||||
|
all_controller_transforms = []
|
||||||
|
|
||||||
|
# 获取控制器的变换节点
|
||||||
|
for ctrl in all_controllers:
|
||||||
|
parent = cmds.listRelatives(ctrl, parent=True)
|
||||||
|
if parent:
|
||||||
|
all_controller_transforms.extend(parent)
|
||||||
|
|
||||||
|
# 确认删除
|
||||||
|
result = cmds.confirmDialog(
|
||||||
|
title=TEXT("confirm_delete", "确认删除"),
|
||||||
|
message=TEXT("delete_all_confirm", "确定要删除所有绑定组件吗?"),
|
||||||
|
button=[TEXT("yes", "是"), TEXT("no", "否")],
|
||||||
|
defaultButton=TEXT("no", "否"),
|
||||||
|
cancelButton=TEXT("no", "否"),
|
||||||
|
dismissString=TEXT("no", "否")
|
||||||
|
)
|
||||||
|
|
||||||
|
if result == TEXT("yes", "是"):
|
||||||
|
# 删除所有控制器
|
||||||
|
if all_controller_transforms:
|
||||||
|
cmds.delete(all_controller_transforms)
|
||||||
|
|
||||||
|
# 删除所有关节
|
||||||
|
if all_joints:
|
||||||
|
cmds.delete(all_joints)
|
||||||
|
|
||||||
|
print("成功删除所有绑定组件")
|
||||||
return True
|
return True
|
||||||
|
else:
|
||||||
|
print("取消删除操作")
|
||||||
|
return False
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"校准DNA数据失败: {str(e)}")
|
print(f"删除绑定组件失败: {str(e)}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
#------------------------------------ UTILITY FUNCTIONS ------------------------------------
|
def import_skeleton(*args):
|
||||||
def update_ui_list(list_widget, items):
|
"""导入骨骼
|
||||||
|
|
||||||
|
从文件中导入骨骼结构
|
||||||
"""
|
"""
|
||||||
更新UI列表控件
|
|
||||||
|
|
||||||
Args:
|
|
||||||
list_widget: 列表控件
|
|
||||||
items: 要添加的项目列表
|
|
||||||
"""
|
|
||||||
if not list_widget:
|
|
||||||
return
|
|
||||||
|
|
||||||
# 清空列表
|
|
||||||
list_widget.clear()
|
|
||||||
|
|
||||||
# 添加项目
|
|
||||||
for item in items:
|
|
||||||
list_widget.addItem(item)
|
|
||||||
|
|
||||||
def update_group_count(list_widget, group_name):
|
|
||||||
"""
|
|
||||||
更新组标题中的计数
|
|
||||||
|
|
||||||
Args:
|
|
||||||
list_widget: 列表控件
|
|
||||||
group_name: 组名称
|
|
||||||
"""
|
|
||||||
if not list_widget:
|
|
||||||
return
|
|
||||||
|
|
||||||
# 获取列表项目数量
|
|
||||||
count = list_widget.count()
|
|
||||||
|
|
||||||
# 更新组标题
|
|
||||||
group_box = list_widget.parent()
|
|
||||||
if isinstance(group_box, QtWidgets.QGroupBox):
|
|
||||||
group_box.setTitle(f"{group_name} ({count})")
|
|
||||||
|
|
||||||
#------------------------------------ UI EVENT HANDLERS ------------------------------------
|
|
||||||
def handle_joint_name_changed(ui_widget):
|
|
||||||
"""
|
|
||||||
处理关节名称变化事件
|
|
||||||
|
|
||||||
Args:
|
|
||||||
ui_widget: UI控件,应该是一个文本输入框
|
|
||||||
"""
|
|
||||||
global selected_joint
|
|
||||||
# 获取当前选中的关节和新名称
|
|
||||||
if selected_joint and ui_widget:
|
|
||||||
new_name = ui_widget.text()
|
|
||||||
if new_name and new_name != selected_joint:
|
|
||||||
try:
|
try:
|
||||||
# 重命名关节
|
# 打开文件选择对话框
|
||||||
cmds.rename(selected_joint, new_name)
|
file_path = cmds.fileDialog2(
|
||||||
selected_joint = new_name
|
fileFilter="Maya文件 (*.ma *.mb);;FBX文件 (*.fbx);;所有文件 (*.*)",
|
||||||
|
dialogStyle=2,
|
||||||
|
caption="选择骨骼文件"
|
||||||
|
)
|
||||||
|
|
||||||
|
if not file_path:
|
||||||
|
print("未选择文件")
|
||||||
|
return False
|
||||||
|
|
||||||
|
file_path = file_path[0] # fileDialog2返回的是列表
|
||||||
|
|
||||||
|
# 根据文件类型选择导入方法
|
||||||
|
if file_path.lower().endswith(('.ma', '.mb')):
|
||||||
|
# 导入Maya文件
|
||||||
|
cmds.file(file_path, i=True, type="mayaAscii" if file_path.lower().endswith('.ma') else "mayaBinary",
|
||||||
|
ignoreVersion=True, mergeNamespacesOnClash=False, namespace="skeleton")
|
||||||
|
elif file_path.lower().endswith('.fbx'):
|
||||||
|
# 导入FBX文件
|
||||||
|
cmds.file(file_path, i=True, type="FBX", ignoreVersion=True)
|
||||||
|
else:
|
||||||
|
print(f"不支持的文件类型: {file_path}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
print(f"成功导入骨骼: {file_path}")
|
||||||
return True
|
return True
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"重命名关节失败: {str(e)}")
|
print(f"导入骨骼失败: {str(e)}")
|
||||||
return False
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def handle_joint_position_changed(axis, value=None, ui_widgets=None):
|
def build_rigging(*args):
|
||||||
"""
|
"""构建绑定系统
|
||||||
处理关节位置变化事件
|
|
||||||
|
|
||||||
Args:
|
基于当前场景中的骨骼构建完整的绑定系统
|
||||||
axis: 轴向 (0=X, 1=Y, 2=Z)
|
|
||||||
value: 新的位置值,如果为None则从ui_widgets中获取
|
|
||||||
ui_widgets: UI控件列表 [x_spin, y_spin, z_spin]
|
|
||||||
"""
|
"""
|
||||||
global selected_joint
|
try:
|
||||||
if not selected_joint:
|
# 获取场景中的所有关节
|
||||||
|
all_joints = cmds.ls(type="joint")
|
||||||
|
|
||||||
|
if not all_joints:
|
||||||
|
print("场景中没有骨骼,无法构建绑定")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# 获取当前位置
|
# 为每个关节创建控制器
|
||||||
pos = [
|
for joint in all_joints:
|
||||||
cmds.getAttr(f"{selected_joint}.translateX"),
|
# 获取关节位置
|
||||||
cmds.getAttr(f"{selected_joint}.translateY"),
|
pos = cmds.xform(joint, query=True, translation=True, worldSpace=True)
|
||||||
cmds.getAttr(f"{selected_joint}.translateZ")
|
|
||||||
]
|
|
||||||
|
|
||||||
# 更新指定轴向的位置
|
# 创建控制器(这里简化为创建一个NURBS圆环)
|
||||||
if value is not None:
|
ctrl = cmds.circle(name=f"{joint}_ctrl", normal=[1, 0, 0], radius=1)[0]
|
||||||
pos[axis] = value
|
|
||||||
elif ui_widgets and len(ui_widgets) >= 3:
|
|
||||||
if axis == 0 and ui_widgets[0]:
|
|
||||||
pos[0] = ui_widgets[0].value()
|
|
||||||
elif axis == 1 and ui_widgets[1]:
|
|
||||||
pos[1] = ui_widgets[1].value()
|
|
||||||
elif axis == 2 and ui_widgets[2]:
|
|
||||||
pos[2] = ui_widgets[2].value()
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
|
|
||||||
# 更新关节属性
|
# 移动控制器到关节位置
|
||||||
return update_joint_properties(selected_joint, position=pos)
|
cmds.move(pos[0], pos[1], pos[2], ctrl)
|
||||||
|
|
||||||
def handle_joint_rotation_changed(axis, value=None, ui_widgets=None):
|
# 创建约束
|
||||||
"""
|
cmds.parentConstraint(ctrl, joint, maintainOffset=True)
|
||||||
处理关节旋转变化事件
|
|
||||||
|
|
||||||
Args:
|
|
||||||
axis: 轴向 (0=X, 1=Y, 2=Z)
|
|
||||||
value: 新的旋转值,如果为None则从ui_widgets中获取
|
|
||||||
ui_widgets: UI控件列表 [x_spin, y_spin, z_spin]
|
|
||||||
"""
|
|
||||||
global selected_joint
|
|
||||||
if not selected_joint:
|
|
||||||
return False
|
|
||||||
|
|
||||||
# 获取当前旋转
|
|
||||||
rot = [
|
|
||||||
cmds.getAttr(f"{selected_joint}.rotateX"),
|
|
||||||
cmds.getAttr(f"{selected_joint}.rotateY"),
|
|
||||||
cmds.getAttr(f"{selected_joint}.rotateZ")
|
|
||||||
]
|
|
||||||
|
|
||||||
# 更新指定轴向的旋转
|
|
||||||
if value is not None:
|
|
||||||
rot[axis] = value
|
|
||||||
elif ui_widgets and len(ui_widgets) >= 3:
|
|
||||||
if axis == 0 and ui_widgets[0]:
|
|
||||||
rot[0] = ui_widgets[0].value()
|
|
||||||
elif axis == 1 and ui_widgets[1]:
|
|
||||||
rot[1] = ui_widgets[1].value()
|
|
||||||
elif axis == 2 and ui_widgets[2]:
|
|
||||||
rot[2] = ui_widgets[2].value()
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
|
|
||||||
# 更新关节属性
|
|
||||||
return update_joint_properties(selected_joint, rotation=rot)
|
|
||||||
|
|
||||||
def handle_joint_scale_changed(axis, value=None, ui_widgets=None):
|
|
||||||
"""
|
|
||||||
处理关节缩放变化事件
|
|
||||||
|
|
||||||
Args:
|
|
||||||
axis: 轴向 (0=X, 1=Y, 2=Z)
|
|
||||||
value: 新的缩放值,如果为None则从ui_widgets中获取
|
|
||||||
ui_widgets: UI控件列表 [x_spin, y_spin, z_spin]
|
|
||||||
"""
|
|
||||||
global selected_joint
|
|
||||||
if not selected_joint:
|
|
||||||
return False
|
|
||||||
|
|
||||||
# 获取当前缩放
|
|
||||||
scale = [
|
|
||||||
cmds.getAttr(f"{selected_joint}.scaleX"),
|
|
||||||
cmds.getAttr(f"{selected_joint}.scaleY"),
|
|
||||||
cmds.getAttr(f"{selected_joint}.scaleZ")
|
|
||||||
]
|
|
||||||
|
|
||||||
# 更新指定轴向的缩放
|
|
||||||
if value is not None:
|
|
||||||
scale[axis] = value
|
|
||||||
elif ui_widgets and len(ui_widgets) >= 3:
|
|
||||||
if axis == 0 and ui_widgets[0]:
|
|
||||||
scale[0] = ui_widgets[0].value()
|
|
||||||
elif axis == 1 and ui_widgets[1]:
|
|
||||||
scale[1] = ui_widgets[1].value()
|
|
||||||
elif axis == 2 and ui_widgets[2]:
|
|
||||||
scale[2] = ui_widgets[2].value()
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
|
|
||||||
# 更新关节属性
|
|
||||||
return update_joint_properties(selected_joint, scale=scale)
|
|
||||||
|
|
||||||
def apply_joint_properties_from_ui(pos_widgets=None, rot_widgets=None, scale_widgets=None):
|
|
||||||
"""
|
|
||||||
从UI控件中应用关节属性
|
|
||||||
|
|
||||||
Args:
|
|
||||||
pos_widgets: 位置控件列表 [x_spin, y_spin, z_spin]
|
|
||||||
rot_widgets: 旋转控件列表 [x_spin, y_spin, z_spin]
|
|
||||||
scale_widgets: 缩放控件列表 [x_spin, y_spin, z_spin]
|
|
||||||
"""
|
|
||||||
global selected_joint
|
|
||||||
if not selected_joint:
|
|
||||||
return False
|
|
||||||
|
|
||||||
# 获取UI中的属性值
|
|
||||||
pos = None
|
|
||||||
rot = None
|
|
||||||
scale = None
|
|
||||||
|
|
||||||
if pos_widgets and len(pos_widgets) >= 3:
|
|
||||||
pos = [
|
|
||||||
pos_widgets[0].value() if pos_widgets[0] else 0,
|
|
||||||
pos_widgets[1].value() if pos_widgets[1] else 0,
|
|
||||||
pos_widgets[2].value() if pos_widgets[2] else 0
|
|
||||||
]
|
|
||||||
|
|
||||||
if rot_widgets and len(rot_widgets) >= 3:
|
|
||||||
rot = [
|
|
||||||
rot_widgets[0].value() if rot_widgets[0] else 0,
|
|
||||||
rot_widgets[1].value() if rot_widgets[1] else 0,
|
|
||||||
rot_widgets[2].value() if rot_widgets[2] else 0
|
|
||||||
]
|
|
||||||
|
|
||||||
if scale_widgets and len(scale_widgets) >= 3:
|
|
||||||
scale = [
|
|
||||||
scale_widgets[0].value() if scale_widgets[0] else 1,
|
|
||||||
scale_widgets[1].value() if scale_widgets[1] else 1,
|
|
||||||
scale_widgets[2].value() if scale_widgets[2] else 1
|
|
||||||
]
|
|
||||||
|
|
||||||
# 更新关节属性
|
|
||||||
return update_joint_properties(selected_joint, position=pos, rotation=rot, scale=scale)
|
|
||||||
|
|
||||||
def reset_joint_properties_ui(pos_widgets=None, rot_widgets=None, scale_widgets=None):
|
|
||||||
"""
|
|
||||||
重置关节属性UI
|
|
||||||
|
|
||||||
Args:
|
|
||||||
pos_widgets: 位置控件列表 [x_spin, y_spin, z_spin]
|
|
||||||
rot_widgets: 旋转控件列表 [x_spin, y_spin, z_spin]
|
|
||||||
scale_widgets: 缩放控件列表 [x_spin, y_spin, z_spin]
|
|
||||||
"""
|
|
||||||
global selected_joint
|
|
||||||
if not selected_joint:
|
|
||||||
return False
|
|
||||||
|
|
||||||
# 重置关节属性
|
|
||||||
reset_joint_properties(selected_joint)
|
|
||||||
|
|
||||||
# 更新UI显示
|
|
||||||
if pos_widgets and len(pos_widgets) >= 3:
|
|
||||||
if pos_widgets[0]:
|
|
||||||
pos_widgets[0].setValue(0)
|
|
||||||
if pos_widgets[1]:
|
|
||||||
pos_widgets[1].setValue(0)
|
|
||||||
if pos_widgets[2]:
|
|
||||||
pos_widgets[2].setValue(0)
|
|
||||||
|
|
||||||
if rot_widgets and len(rot_widgets) >= 3:
|
|
||||||
if rot_widgets[0]:
|
|
||||||
rot_widgets[0].setValue(0)
|
|
||||||
if rot_widgets[1]:
|
|
||||||
rot_widgets[1].setValue(0)
|
|
||||||
if rot_widgets[2]:
|
|
||||||
rot_widgets[2].setValue(0)
|
|
||||||
|
|
||||||
if scale_widgets and len(scale_widgets) >= 3:
|
|
||||||
if scale_widgets[0]:
|
|
||||||
scale_widgets[0].setValue(1)
|
|
||||||
if scale_widgets[1]:
|
|
||||||
scale_widgets[1].setValue(1)
|
|
||||||
if scale_widgets[2]:
|
|
||||||
scale_widgets[2].setValue(1)
|
|
||||||
|
|
||||||
|
print("成功构建绑定系统")
|
||||||
return True
|
return True
|
||||||
|
except Exception as e:
|
||||||
def update_joint_ui(joint_name, name_widget=None, pos_widgets=None, rot_widgets=None, scale_widgets=None):
|
print(f"构建绑定系统失败: {str(e)}")
|
||||||
"""
|
|
||||||
更新关节UI
|
|
||||||
|
|
||||||
Args:
|
|
||||||
joint_name: 关节名称
|
|
||||||
name_widget: 名称控件
|
|
||||||
pos_widgets: 位置控件列表 [x_spin, y_spin, z_spin]
|
|
||||||
rot_widgets: 旋转控件列表 [x_spin, y_spin, z_spin]
|
|
||||||
scale_widgets: 缩放控件列表 [x_spin, y_spin, z_spin]
|
|
||||||
"""
|
|
||||||
global selected_joint
|
|
||||||
if not cmds.objExists(joint_name):
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# 更新选中的关节
|
|
||||||
selected_joint = joint_name
|
|
||||||
|
|
||||||
# 更新名称控件
|
|
||||||
if name_widget:
|
|
||||||
name_widget.setText(joint_name)
|
|
||||||
|
|
||||||
# 获取关节属性
|
|
||||||
pos = [
|
|
||||||
cmds.getAttr(f"{joint_name}.translateX"),
|
|
||||||
cmds.getAttr(f"{joint_name}.translateY"),
|
|
||||||
cmds.getAttr(f"{joint_name}.translateZ")
|
|
||||||
]
|
|
||||||
|
|
||||||
rot = [
|
|
||||||
cmds.getAttr(f"{joint_name}.rotateX"),
|
|
||||||
cmds.getAttr(f"{joint_name}.rotateY"),
|
|
||||||
cmds.getAttr(f"{joint_name}.rotateZ")
|
|
||||||
]
|
|
||||||
|
|
||||||
scale = [
|
|
||||||
cmds.getAttr(f"{joint_name}.scaleX"),
|
|
||||||
cmds.getAttr(f"{joint_name}.scaleY"),
|
|
||||||
cmds.getAttr(f"{joint_name}.scaleZ")
|
|
||||||
]
|
|
||||||
|
|
||||||
# 更新UI控件
|
|
||||||
if pos_widgets and len(pos_widgets) >= 3:
|
|
||||||
if pos_widgets[0]:
|
|
||||||
pos_widgets[0].setValue(pos[0])
|
|
||||||
if pos_widgets[1]:
|
|
||||||
pos_widgets[1].setValue(pos[1])
|
|
||||||
if pos_widgets[2]:
|
|
||||||
pos_widgets[2].setValue(pos[2])
|
|
||||||
|
|
||||||
if rot_widgets and len(rot_widgets) >= 3:
|
|
||||||
if rot_widgets[0]:
|
|
||||||
rot_widgets[0].setValue(rot[0])
|
|
||||||
if rot_widgets[1]:
|
|
||||||
rot_widgets[1].setValue(rot[1])
|
|
||||||
if rot_widgets[2]:
|
|
||||||
rot_widgets[2].setValue(rot[2])
|
|
||||||
|
|
||||||
if scale_widgets and len(scale_widgets) >= 3:
|
|
||||||
if scale_widgets[0]:
|
|
||||||
scale_widgets[0].setValue(scale[0])
|
|
||||||
if scale_widgets[1]:
|
|
||||||
scale_widgets[1].setValue(scale[1])
|
|
||||||
if scale_widgets[2]:
|
|
||||||
scale_widgets[2].setValue(scale[2])
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
def on_selection_changed():
|
|
||||||
"""
|
|
||||||
选择变化事件处理
|
|
||||||
当Maya中的选择变化时更新UI
|
|
||||||
"""
|
|
||||||
global selected_joint, selected_controller
|
|
||||||
|
|
||||||
# 获取当前选中的对象
|
|
||||||
selected = cmds.ls(selection=True)
|
|
||||||
|
|
||||||
if not selected:
|
|
||||||
# 清除选中状态
|
|
||||||
selected_joint = None
|
|
||||||
selected_controller = None
|
|
||||||
return
|
|
||||||
|
|
||||||
# 检查选中的对象类型
|
|
||||||
for obj in selected:
|
|
||||||
# 检查是否是关节
|
|
||||||
if cmds.objectType(obj) == "joint":
|
|
||||||
selected_joint = obj
|
|
||||||
# 可以在这里添加代码来更新UI
|
|
||||||
print(f"选中关节: {selected_joint}")
|
|
||||||
break
|
|
||||||
|
|
||||||
# 检查是否是控制器(通常是transform节点)
|
|
||||||
elif cmds.objectType(obj) == "transform":
|
|
||||||
# 这里可以添加额外的检查来确定是否是控制器
|
|
||||||
# 例如检查是否有特定的属性或命名规则
|
|
||||||
selected_controller = obj
|
|
||||||
# 可以在这里添加代码来更新UI
|
|
||||||
print(f"选中控制器: {selected_controller}")
|
|
||||||
break
|
|
||||||
|
|
||||||
def browse_file(ui_instance, title, input_widget, file_filter=None):
|
|
||||||
"""浏览文件或目录
|
|
||||||
|
|
||||||
Args:
|
|
||||||
ui_instance: UI实例,用于获取主窗口
|
|
||||||
title (str): 对话框标题
|
|
||||||
input_widget (QLineEdit): 用于显示路径的输入框控件
|
|
||||||
file_filter (str, optional): 文件过滤器,如果为None则浏览目录,否则浏览文件
|
|
||||||
"""
|
|
||||||
from Qt import QtWidgets
|
|
||||||
import os
|
|
||||||
|
|
||||||
current_path = input_widget.text() or os.path.expanduser("~")
|
|
||||||
|
|
||||||
if file_filter:
|
|
||||||
# 浏览文件
|
|
||||||
if file_filter == "dna":
|
|
||||||
file_filter = "DNA文件 (*.dna);;所有文件 (*.*)"
|
|
||||||
elif file_filter == "json":
|
|
||||||
file_filter = "JSON文件 (*.json);;所有文件 (*.*)"
|
|
||||||
else:
|
|
||||||
file_filter = "所有文件 (*.*)"
|
|
||||||
|
|
||||||
file_path, _ = QtWidgets.QFileDialog.getOpenFileName(
|
|
||||||
ui_instance.main_widget, title, current_path, file_filter
|
|
||||||
)
|
|
||||||
|
|
||||||
if file_path:
|
|
||||||
input_widget.setText(file_path)
|
|
||||||
else:
|
|
||||||
# 浏览目录
|
|
||||||
dir_path = QtWidgets.QFileDialog.getExistingDirectory(
|
|
||||||
ui_instance.main_widget, title, current_path
|
|
||||||
)
|
|
||||||
|
|
||||||
if dir_path:
|
|
||||||
input_widget.setText(dir_path)
|
|
||||||
|
|
||||||
def on_selection_changed(ui_instance=None):
|
|
||||||
"""处理Maya选择变化事件
|
|
||||||
|
|
||||||
Args:
|
|
||||||
ui_instance: UI实例,可选
|
|
||||||
"""
|
|
||||||
import maya.cmds as cmds
|
|
||||||
|
|
||||||
# 获取当前选择
|
|
||||||
selection = cmds.ls(selection=True)
|
|
||||||
if not selection:
|
|
||||||
return
|
|
||||||
|
|
||||||
# 更新UI显示
|
|
||||||
print(f"当前选择: {selection}")
|
|
||||||
# 这里可以添加更多的选择处理逻辑
|
|
||||||
|
|
||||||
# 如果提供了UI实例,可以更新UI
|
|
||||||
if ui_instance:
|
|
||||||
# 更新UI显示
|
|
||||||
pass
|
|
||||||
|
|
||||||
def export_presets(*args):
|
|
||||||
"""导出预设"""
|
|
||||||
print("导出预设")
|
|
||||||
# 实现导出预设的功能
|
|
||||||
|
|
||||||
def import_presets(*args):
|
|
||||||
"""导入预设"""
|
|
||||||
print("导入预设")
|
|
||||||
# 实现导入预设的功能
|
|
||||||
|
|
||||||
# ======================================= 占位函数 =======================================
|
|
||||||
# 以下是未实现功能的占位函数,用于连接未创建的按钮
|
|
||||||
|
|
||||||
def add_joint(*args):
|
|
||||||
"""添加关节(占位函数)"""
|
|
||||||
print("添加关节功能尚未实现")
|
|
||||||
|
|
||||||
def remove_joint(*args):
|
|
||||||
"""删除关节(占位函数)"""
|
|
||||||
print("删除关节功能尚未实现")
|
|
||||||
|
|
||||||
def duplicate_joint(*args):
|
|
||||||
"""复制关节(占位函数)"""
|
|
||||||
print("复制关节功能尚未实现")
|
|
||||||
|
|
||||||
def add_controller(*args):
|
|
||||||
"""添加控制器(占位函数)"""
|
|
||||||
print("添加控制器功能尚未实现")
|
|
||||||
|
|
||||||
def remove_controller(*args):
|
|
||||||
"""删除控制器(占位函数)"""
|
|
||||||
print("删除控制器功能尚未实现")
|
|
||||||
|
|
||||||
def duplicate_controller(*args):
|
|
||||||
"""复制控制器(占位函数)"""
|
|
||||||
print("复制控制器功能尚未实现")
|
|
||||||
|
|
||||||
def import_dna(*args):
|
|
||||||
"""导入DNA(占位函数)"""
|
|
||||||
print("导入DNA功能尚未实现")
|
|
||||||
|
|
||||||
def export_dna(*args):
|
|
||||||
"""导出DNA(占位函数)"""
|
|
||||||
print("导出DNA功能尚未实现")
|
|
||||||
|
|
||||||
def calibrate_dna(*args):
|
|
||||||
"""校准DNA(占位函数)"""
|
|
||||||
print("校准DNA功能尚未实现")
|
|
Reference in New Issue
Block a user