Files
Nexus/2025/scripts/rigging_tools/StandardizeSkeleton.py
2026-01-22 00:06:13 +08:00

738 lines
29 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- coding: utf-8 -*-
"""
StandardizeSkeleton.py
标准化骨骼工具 - 用于从绑定文件中提取干净的骨骼层级
功能:
1. 识别所有有蒙皮权重的骨骼
2. 复制这些骨骼并保持原有层级关系
3. 移除控制器和其他非骨骼节点
4. 批量添加/剔除骨骼前缀后缀
5. 创建干净的骨骼树,便于导出到游戏引擎
作者: Kilo Code
日期: 2025
"""
import maya.cmds as cmds
import maya.mel as mel
from functools import partial
try:
from PySide2 import QtWidgets, QtCore, QtGui
from shiboken2 import wrapInstance
except ImportError:
from PySide6 import QtWidgets, QtCore, QtGui
from shiboken6 import wrapInstance
import maya.OpenMayaUI as omui
def get_maya_main_window():
"""获取Maya主窗口"""
main_window_ptr = omui.MQtUtil.mainWindow()
return wrapInstance(int(main_window_ptr), QtWidgets.QWidget)
class StandardizeSkeletonUI(QtWidgets.QDialog):
"""标准化骨骼工具UI"""
def __init__(self, parent=get_maya_main_window()):
super(StandardizeSkeletonUI, self).__init__(parent)
self.setWindowTitle("标准化骨骼工具")
self.setMinimumWidth(500)
self.setMinimumHeight(600)
self.setWindowFlags(self.windowFlags() ^ QtCore.Qt.WindowContextHelpButtonHint)
self.geometry_list = [] # 存储几何体列表
self.create_widgets()
self.create_layouts()
self.create_connections()
def create_widgets(self):
"""创建UI控件"""
# 几何体列表区域
self.geo_group = QtWidgets.QGroupBox("几何体列表")
self.geo_list_widget = QtWidgets.QListWidget()
self.geo_list_widget.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
self.add_selected_btn = QtWidgets.QPushButton("添加选中对象")
self.add_selected_btn.setToolTip("选择对象后点击,会自动过滤出几何体")
self.remove_selected_btn = QtWidgets.QPushButton("移除选中项")
self.clear_list_btn = QtWidgets.QPushButton("清空列表")
# 骨骼信息区域
self.info_group = QtWidgets.QGroupBox("骨骼信息")
self.info_text = QtWidgets.QTextEdit()
self.info_text.setReadOnly(True)
self.info_text.setMaximumHeight(150)
self.analyze_btn = QtWidgets.QPushButton("分析权重骨骼")
self.analyze_btn.setStyleSheet("background-color: #4a90e2; color: white; font-weight: bold;")
# 导出选项区域
self.export_group = QtWidgets.QGroupBox("导出选项")
self.export_name_label = QtWidgets.QLabel("导出组名称:")
self.export_name_edit = QtWidgets.QLineEdit("export_skeleton_grp")
self.prefix_label = QtWidgets.QLabel("添加前缀:")
self.prefix_edit = QtWidgets.QLineEdit()
self.prefix_edit.setPlaceholderText("例如: SK_")
self.suffix_label = QtWidgets.QLabel("添加后缀:")
self.suffix_edit = QtWidgets.QLineEdit()
self.suffix_edit.setPlaceholderText("例如: _jnt")
self.remove_prefix_label = QtWidgets.QLabel("移除前缀:")
self.remove_prefix_edit = QtWidgets.QLineEdit()
self.remove_prefix_edit.setPlaceholderText("例如: L_, R_, M_")
self.remove_suffix_label = QtWidgets.QLabel("移除后缀:")
self.remove_suffix_edit = QtWidgets.QLineEdit()
self.remove_suffix_edit.setPlaceholderText("例如: _joint, _jnt")
self.exclude_keywords_label = QtWidgets.QLabel("排除关键词:")
self.exclude_keywords_edit = QtWidgets.QLineEdit()
self.exclude_keywords_edit.setPlaceholderText("例如: hair, lashe, zip, wrinkle")
self.exclude_keywords_edit.setToolTip("包含这些关键词的骨骼将被排除(逗号分隔,不区分大小写)")
self.root_joint_label = QtWidgets.QLabel("根骨骼限制:")
self.root_joint_edit = QtWidgets.QLineEdit()
self.root_joint_edit.setPlaceholderText("例如: rig|root_joint (留空=不限制)")
self.root_joint_edit.setToolTip("只导出此骨骼下的子骨骼(留空则导出所有有权重的骨骼)")
self.keep_hierarchy_cb = QtWidgets.QCheckBox("保持原始层级关系")
self.keep_hierarchy_cb.setChecked(True)
self.keep_hierarchy_cb.setToolTip("保持骨骼的父子层级关系")
self.freeze_transforms_cb = QtWidgets.QCheckBox("冻结变换")
self.freeze_transforms_cb.setChecked(False)
self.freeze_transforms_cb.setToolTip("冻结导出骨骼的变换属性")
self.delete_constraints_cb = QtWidgets.QCheckBox("删除所有约束")
self.delete_constraints_cb.setChecked(True)
self.delete_constraints_cb.setToolTip("删除复制骨骼上的所有约束节点")
# 执行按钮
self.create_skeleton_btn = QtWidgets.QPushButton("创建导出骨骼")
self.create_skeleton_btn.setStyleSheet("background-color: #5cb85c; color: white; font-weight: bold; padding: 10px;")
self.create_skeleton_btn.setMinimumHeight(40)
self.select_export_btn = QtWidgets.QPushButton("选择导出组")
self.delete_export_btn = QtWidgets.QPushButton("删除导出组")
# 帮助文本
self.help_text = QtWidgets.QLabel(
"使用说明:\n"
"1. 点击'添加选中对象'添加包含几何体的对象\n"
"2. 点击'分析权重骨骼'查看将要导出的骨骼信息\n"
"3. 设置导出选项(可选)\n"
"4. 点击'创建导出骨骼'生成干净的骨骼层级"
)
self.help_text.setStyleSheet("color: #666; padding: 10px; background-color: #f5f5f5; border-radius: 5px;")
self.help_text.setWordWrap(True)
def create_layouts(self):
"""创建布局"""
main_layout = QtWidgets.QVBoxLayout(self)
# 帮助文本
main_layout.addWidget(self.help_text)
# 几何体列表区域
geo_layout = QtWidgets.QVBoxLayout()
geo_layout.addWidget(self.geo_list_widget)
geo_btn_layout = QtWidgets.QHBoxLayout()
geo_btn_layout.addWidget(self.add_selected_btn)
geo_btn_layout.addWidget(self.remove_selected_btn)
geo_btn_layout.addWidget(self.clear_list_btn)
geo_layout.addLayout(geo_btn_layout)
self.geo_group.setLayout(geo_layout)
main_layout.addWidget(self.geo_group)
# 分析按钮
main_layout.addWidget(self.analyze_btn)
# 骨骼信息区域
info_layout = QtWidgets.QVBoxLayout()
info_layout.addWidget(self.info_text)
self.info_group.setLayout(info_layout)
main_layout.addWidget(self.info_group)
# 导出选项区域
export_layout = QtWidgets.QGridLayout()
row = 0
export_layout.addWidget(self.export_name_label, row, 0)
export_layout.addWidget(self.export_name_edit, row, 1)
row += 1
export_layout.addWidget(self.prefix_label, row, 0)
export_layout.addWidget(self.prefix_edit, row, 1)
row += 1
export_layout.addWidget(self.suffix_label, row, 0)
export_layout.addWidget(self.suffix_edit, row, 1)
row += 1
export_layout.addWidget(self.remove_prefix_label, row, 0)
export_layout.addWidget(self.remove_prefix_edit, row, 1)
row += 1
export_layout.addWidget(self.remove_suffix_label, row, 0)
export_layout.addWidget(self.remove_suffix_edit, row, 1)
row += 1
export_layout.addWidget(self.exclude_keywords_label, row, 0)
export_layout.addWidget(self.exclude_keywords_edit, row, 1)
row += 1
export_layout.addWidget(self.root_joint_label, row, 0)
export_layout.addWidget(self.root_joint_edit, row, 1)
row += 1
export_layout.addWidget(self.keep_hierarchy_cb, row, 0, 1, 2)
row += 1
export_layout.addWidget(self.freeze_transforms_cb, row, 0, 1, 2)
row += 1
export_layout.addWidget(self.delete_constraints_cb, row, 0, 1, 2)
self.export_group.setLayout(export_layout)
main_layout.addWidget(self.export_group)
# 执行按钮
main_layout.addWidget(self.create_skeleton_btn)
# 底部按钮
bottom_btn_layout = QtWidgets.QHBoxLayout()
bottom_btn_layout.addWidget(self.select_export_btn)
bottom_btn_layout.addWidget(self.delete_export_btn)
main_layout.addLayout(bottom_btn_layout)
def create_connections(self):
"""创建信号连接"""
self.add_selected_btn.clicked.connect(self.add_selected_geometry)
self.remove_selected_btn.clicked.connect(self.remove_selected_items)
self.clear_list_btn.clicked.connect(self.clear_geometry_list)
self.analyze_btn.clicked.connect(self.analyze_weighted_joints)
self.create_skeleton_btn.clicked.connect(self.create_export_skeleton)
self.select_export_btn.clicked.connect(self.select_export_group)
self.delete_export_btn.clicked.connect(self.delete_export_group)
def add_selected_geometry(self):
"""添加选中的几何体"""
selection = cmds.ls(selection=True, long=True)
if not selection:
QtWidgets.QMessageBox.warning(self, "警告", "请先选择对象!")
return
added_count = 0
for obj in selection:
# 获取所有子节点中的mesh
meshes = self.get_meshes_from_object(obj)
for mesh in meshes:
# 获取transform节点
transform = cmds.listRelatives(mesh, parent=True, fullPath=True)[0]
if transform not in self.geometry_list:
self.geometry_list.append(transform)
# 显示短名称
short_name = transform.split('|')[-1]
self.geo_list_widget.addItem(short_name)
added_count += 1
if added_count > 0:
self.info_text.append(f"添加了 {added_count} 个几何体")
else:
QtWidgets.QMessageBox.information(self, "提示", "没有找到新的几何体或几何体已存在")
def get_meshes_from_object(self, obj):
"""从对象中获取所有mesh"""
meshes = []
# 检查对象本身是否是mesh
shapes = cmds.listRelatives(obj, shapes=True, fullPath=True, type="mesh") or []
meshes.extend(shapes)
# 检查所有子节点
descendants = cmds.listRelatives(obj, allDescendents=True, fullPath=True, type="mesh") or []
meshes.extend(descendants)
return list(set(meshes)) # 去重
def remove_selected_items(self):
"""移除选中的列表项"""
selected_items = self.geo_list_widget.selectedItems()
if not selected_items:
QtWidgets.QMessageBox.warning(self, "警告", "请先选择要移除的项!")
return
for item in selected_items:
row = self.geo_list_widget.row(item)
self.geo_list_widget.takeItem(row)
if row < len(self.geometry_list):
self.geometry_list.pop(row)
def clear_geometry_list(self):
"""清空几何体列表"""
self.geo_list_widget.clear()
self.geometry_list = []
self.info_text.append("已清空几何体列表")
def analyze_weighted_joints(self):
"""分析有权重的骨骼"""
if not self.geometry_list:
QtWidgets.QMessageBox.warning(self, "警告", "请先添加几何体!")
return
self.info_text.clear()
self.info_text.append("正在分析权重骨骼...\n")
try:
# 获取排除关键词
exclude_keywords_text = self.exclude_keywords_edit.text().strip()
exclude_keywords = [k.strip() for k in exclude_keywords_text.split(',') if k.strip()] if exclude_keywords_text else None
# 获取根骨骼限制
root_joint = self.root_joint_edit.text().strip() or None
if exclude_keywords:
self.info_text.append(f"排除关键词: {', '.join(exclude_keywords)}")
if root_joint:
if cmds.objExists(root_joint):
self.info_text.append(f"根骨骼限制: {root_joint}\n")
else:
self.info_text.append(f"警告: 根骨骼 '{root_joint}' 不存在,将忽略此限制\n")
root_joint = None
weighted_joints = self.get_weighted_joints(self.geometry_list, exclude_keywords, root_joint)
if not weighted_joints:
self.info_text.append("未找到有权重的骨骼!")
return
# 分析骨骼层级
root_joints = self.get_root_joints(weighted_joints)
self.info_text.append(f"找到 {len(weighted_joints)} 个有权重的骨骼")
self.info_text.append(f"根骨骼数量: {len(root_joints)}")
self.info_text.append(f"\n根骨骼列表:")
for root in root_joints:
short_name = root.split('|')[-1]
self.info_text.append(f" - {short_name}")
# 显示前20个骨骼
self.info_text.append(f"\n前20个权重骨骼:")
for i, joint in enumerate(weighted_joints[:20]):
short_name = joint.split('|')[-1]
self.info_text.append(f" {i+1}. {short_name}")
if len(weighted_joints) > 20:
self.info_text.append(f" ... 还有 {len(weighted_joints) - 20} 个骨骼")
except Exception as e:
self.info_text.append(f"\n错误: {str(e)}")
import traceback
self.info_text.append(traceback.format_exc())
def get_weighted_joints(self, geometry_list, exclude_keywords=None, root_joint=None):
"""获取所有有权重的骨骼"""
weighted_joints = set()
# 如果指定了根骨骼,获取其所有子骨骼
root_joint_children = None
if root_joint and cmds.objExists(root_joint):
root_joint_children = set()
all_joints = cmds.listRelatives(root_joint, allDescendents=True, type="joint", fullPath=True) or []
root_joint_children = set(all_joints)
root_joint_children.add(cmds.ls(root_joint, long=True)[0])
for geo in geometry_list:
# 获取mesh shape
shapes = cmds.listRelatives(geo, shapes=True, fullPath=True, type="mesh") or []
for shape in shapes:
# 查找skinCluster
history = cmds.listHistory(shape, pruneDagObjects=True) or []
# 过滤出skinCluster节点
skin_clusters = [node for node in history if cmds.nodeType(node) == "skinCluster"]
for skin_cluster in skin_clusters:
# 获取影响的骨骼
influences = cmds.skinCluster(skin_cluster, query=True, influence=True) or []
# 转换为长名称
for influence in influences:
long_name = cmds.ls(influence, long=True)[0]
if cmds.nodeType(long_name) == "joint":
# 检查是否在根骨骼下
if root_joint_children is not None and long_name not in root_joint_children:
continue
# 检查是否需要排除
if exclude_keywords:
short_name = long_name.split('|')[-1].lower()
should_exclude = False
for keyword in exclude_keywords:
if keyword.lower() in short_name:
should_exclude = True
break
if not should_exclude:
weighted_joints.add(long_name)
else:
weighted_joints.add(long_name)
return list(weighted_joints)
def get_root_joints(self, joint_list):
"""获取根骨骼在joint_list中没有父骨骼的骨骼"""
root_joints = []
for joint in joint_list:
# 获取父节点
parent = cmds.listRelatives(joint, parent=True, fullPath=True)
# 如果没有父节点或者父节点不是joint或者父节点不在列表中
if not parent:
root_joints.append(joint)
else:
parent_joint = parent[0]
if cmds.nodeType(parent_joint) != "joint" or parent_joint not in joint_list:
root_joints.append(joint)
return root_joints
def create_export_skeleton(self):
"""创建导出骨骼"""
if not self.geometry_list:
QtWidgets.QMessageBox.warning(self, "警告", "请先添加几何体!")
return
export_name = self.export_name_edit.text().strip()
if not export_name:
QtWidgets.QMessageBox.warning(self, "警告", "请输入导出组名称!")
return
# 确认对话框
reply = QtWidgets.QMessageBox.question(
self,
"确认",
f"将创建导出骨骼组: {export_name}\n是否继续?",
QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No
)
if reply == QtWidgets.QMessageBox.No:
return
try:
self.info_text.clear()
self.info_text.append("开始创建导出骨骼...\n")
# 获取排除关键词
exclude_keywords_text = self.exclude_keywords_edit.text().strip()
exclude_keywords = [k.strip() for k in exclude_keywords_text.split(',') if k.strip()] if exclude_keywords_text else None
# 获取根骨骼限制
root_joint = self.root_joint_edit.text().strip() or None
if exclude_keywords:
self.info_text.append(f"排除关键词: {', '.join(exclude_keywords)}")
if root_joint:
if cmds.objExists(root_joint):
self.info_text.append(f"根骨骼限制: {root_joint}")
else:
self.info_text.append(f"警告: 根骨骼 '{root_joint}' 不存在,将忽略此限制")
root_joint = None
# 获取有权重的骨骼
weighted_joints = self.get_weighted_joints(self.geometry_list, exclude_keywords, root_joint)
if not weighted_joints:
QtWidgets.QMessageBox.warning(self, "警告", "未找到有权重的骨骼!")
return
self.info_text.append(f"找到 {len(weighted_joints)} 个有权重的骨骼")
# 删除已存在的导出组
if cmds.objExists(export_name):
cmds.delete(export_name)
self.info_text.append(f"删除已存在的 {export_name}")
# 创建导出组
export_group = cmds.group(empty=True, name=export_name)
self.info_text.append(f"创建导出组: {export_group}")
# 复制骨骼
if self.keep_hierarchy_cb.isChecked():
# 保持层级关系
duplicated_joints = self.duplicate_joints_with_hierarchy(
weighted_joints,
export_group
)
else:
# 不保持层级,所有骨骼平铺
duplicated_joints = self.duplicate_joints_flat(
weighted_joints,
export_group
)
self.info_text.append(f"复制了 {len(duplicated_joints)} 个骨骼")
# 删除约束
if self.delete_constraints_cb.isChecked() and duplicated_joints:
deleted_constraints = self.delete_all_constraints(duplicated_joints)
if deleted_constraints > 0:
self.info_text.append(f"删除了 {deleted_constraints} 个约束")
# 重命名骨骼
if duplicated_joints:
self.rename_joints(
duplicated_joints,
self.prefix_edit.text().strip(),
self.suffix_edit.text().strip(),
self.remove_prefix_edit.text().strip(),
self.remove_suffix_edit.text().strip()
)
# 冻结变换
if self.freeze_transforms_cb.isChecked() and duplicated_joints:
try:
cmds.makeIdentity(duplicated_joints, apply=True, translate=True, rotate=True, scale=True)
self.info_text.append("已冻结变换")
except:
self.info_text.append("冻结变换失败(某些骨骼可能有约束)")
# 选择导出组
cmds.select(export_group)
self.info_text.append(f"\n成功创建导出骨骼!")
self.info_text.append(f"导出组: {export_group}")
QtWidgets.QMessageBox.information(
self,
"成功",
f"成功创建导出骨骼!\n导出组: {export_group}\n骨骼数量: {len(duplicated_joints)}"
)
except Exception as e:
error_msg = f"创建导出骨骼失败: {str(e)}"
self.info_text.append(f"\n错误: {error_msg}")
import traceback
self.info_text.append(traceback.format_exc())
QtWidgets.QMessageBox.critical(self, "错误", error_msg)
def duplicate_joints_with_hierarchy(self, joint_list, parent_group):
"""复制骨骼并保持层级关系(只复制有权重的骨骼)"""
duplicated_joints = []
joint_mapping = {} # 原始骨骼 -> 复制骨骼的映射
# 第一步:复制所有骨骼
for joint in joint_list:
# 复制单个骨骼(不复制子节点)
dup_joint = cmds.duplicate(joint, parentOnly=True, returnRootsOnly=True)[0]
# 重命名去掉Maya自动添加的数字后缀
short_name = joint.split('|')[-1]
dup_joint = cmds.rename(dup_joint, short_name + "_export")
joint_mapping[joint] = dup_joint
duplicated_joints.append(dup_joint)
# 第二步:重建层级关系(跳过没有权重的中间骨骼)
for original_joint, dup_joint in joint_mapping.items():
# 查找最近的有权重的父骨骼
parent_joint = self.find_weighted_parent(original_joint, joint_list)
if parent_joint and parent_joint in joint_mapping:
# 如果找到有权重的父骨骼,建立父子关系
try:
cmds.parent(dup_joint, joint_mapping[parent_joint])
except:
pass
else:
# 如果没有找到,放到导出组下
try:
cmds.parent(dup_joint, parent_group)
except:
pass # 可能已经在导出组下了
return duplicated_joints
def find_weighted_parent(self, joint, weighted_joint_list):
"""查找最近的有权重的父骨骼"""
current = joint
while True:
# 获取父节点
parent = cmds.listRelatives(current, parent=True, fullPath=True)
if not parent:
# 没有父节点了
return None
parent_node = parent[0]
# 检查父节点是否是joint
if cmds.nodeType(parent_node) != "joint":
# 父节点不是骨骼
return None
# 检查父骨骼是否在权重骨骼列表中
if parent_node in weighted_joint_list:
return parent_node
# 继续向上查找
current = parent_node
def duplicate_joints_flat(self, joint_list, parent_group):
"""复制骨骼(平铺,不保持层级)"""
duplicated_joints = []
for joint in joint_list:
# 复制单个骨骼
dup_joint = cmds.duplicate(joint, parentOnly=True, returnRootsOnly=True)[0]
# 重命名
short_name = joint.split('|')[-1]
dup_joint = cmds.rename(dup_joint, short_name + "_export")
# 放到导出组下
cmds.parent(dup_joint, parent_group)
duplicated_joints.append(dup_joint)
return duplicated_joints
def delete_all_constraints(self, joint_list):
"""删除所有约束"""
deleted_count = 0
# 获取所有骨骼(包括子骨骼)
all_joints = []
for joint in joint_list:
all_joints.append(joint)
descendants = cmds.listRelatives(joint, allDescendents=True, type="joint", fullPath=True) or []
all_joints.extend(descendants)
all_joints = list(set(all_joints)) # 去重
# 删除约束
for joint in all_joints:
# 获取连接到这个骨骼的所有约束
constraints = cmds.listConnections(joint, type="constraint") or []
for constraint in set(constraints): # 去重
if cmds.objExists(constraint):
try:
cmds.delete(constraint)
deleted_count += 1
except:
pass
return deleted_count
def rename_joints(self, joint_list, add_prefix, add_suffix, remove_prefix, remove_suffix):
"""重命名骨骼"""
if not any([add_prefix, add_suffix, remove_prefix, remove_suffix]):
return
# 解析要移除的前缀和后缀(支持逗号分隔)
remove_prefixes = [p.strip() for p in remove_prefix.split(',') if p.strip()]
remove_suffixes = [s.strip() for s in remove_suffix.split(',') if s.strip()]
renamed_count = 0
for joint in joint_list:
old_name = joint.split('|')[-1]
new_name = old_name
# 移除后缀
for suffix in remove_suffixes:
if new_name.endswith(suffix):
new_name = new_name[:-len(suffix)]
# 移除前缀
for prefix in remove_prefixes:
if new_name.startswith(prefix):
new_name = new_name[len(prefix):]
# 添加前缀
if add_prefix:
new_name = add_prefix + new_name
# 添加后缀
if add_suffix:
new_name = new_name + add_suffix
# 重命名
if new_name != old_name:
try:
cmds.rename(joint, new_name)
renamed_count += 1
except:
pass
if renamed_count > 0:
self.info_text.append(f"重命名了 {renamed_count} 个骨骼")
def select_export_group(self):
"""选择导出组"""
export_name = self.export_name_edit.text().strip()
if cmds.objExists(export_name):
cmds.select(export_name)
self.info_text.append(f"已选择: {export_name}")
else:
QtWidgets.QMessageBox.warning(self, "警告", f"导出组 '{export_name}' 不存在!")
def delete_export_group(self):
"""删除导出组"""
export_name = self.export_name_edit.text().strip()
if not cmds.objExists(export_name):
QtWidgets.QMessageBox.warning(self, "警告", f"导出组 '{export_name}' 不存在!")
return
reply = QtWidgets.QMessageBox.question(
self,
"确认删除",
f"确定要删除导出组 '{export_name}' 吗?",
QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No
)
if reply == QtWidgets.QMessageBox.Yes:
cmds.delete(export_name)
self.info_text.append(f"已删除: {export_name}")
def show():
"""显示UI"""
global standardize_skeleton_ui
try:
standardize_skeleton_ui.close()
standardize_skeleton_ui.deleteLater()
except:
pass
standardize_skeleton_ui = StandardizeSkeletonUI()
standardize_skeleton_ui.show()
if __name__ == "__main__":
show()