class SocketTool(QtWidgets.QWidget): def __init__(self, parent=None): super(SocketTool, self).__init__(parent=parent) ui_file = os.path.join(__file__, "..", "skeletal_socket_tool.ui") load_ui(ui_file, self) self.model = MTableModel() header_list = [u"骨骼", u"插槽"] self.header_list = [ { "label": data, "key": data, "editable": True, "selectable": True, "width": 400, } for data in header_list ] self.model.set_header_list(self.header_list) self.model_sort = MSortFilterModel() self.model_sort.setSourceModel(self.model) self.Table_View.setShowGrid(True) self.Table_View.setModel(self.model_sort) header = self.Table_View.horizontalHeader() header.setStretchLastSection(True) self.popMenu = QtWidgets.QMenu(self) action = QtWidgets.QAction(u"删除选择", self) action.triggered.connect(self.remove_line) self.popMenu.addAction(action) action = QtWidgets.QAction(u"添加一行", self) action.triggered.connect(self.add_line) self.popMenu.addAction(action) self.Table_View.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) self.Table_View.customContextMenuRequested.connect(self.on_context_menu) self.Close_Action.triggered.connect(self.close) self.Export_CSV_Action.triggered.connect(self.export_csv) self.Import_CSV_Action.triggered.connect(self.import_csv) self.Help_Action.triggered.connect( lambda: webbrowser.open_new_tab( "http://wiki.l0v0.com/unreal/PyToolkit/#/anim/2_skeletal_socket_tool" ) ) self.Socket_BTN.clicked.connect(self.add_socket) self.Clear_BTN.clicked.connect(self.clear_socket) # NOTE 读取 csv self.settings = QtCore.QSettings( "%s.ini" % self.__class__.__name__, QtCore.QSettings.IniFormat ) csv_file = self.settings.value("csv_file") # csv_file = os.path.join(__file__,"..","test.csv") if csv_file and os.path.exists(csv_file): self.handle_csv(csv_file) def get_data_list(self): row_count = self.model.rowCount() column_count = self.model.columnCount() return [ { self.header_list[j]["key"]: self.model.itemData(self.model.index(i, j))[ 0 ] for j in range(column_count) } for i in range(row_count) ] def remove_line(self): data_list = self.get_data_list() indexes = self.Table_View.selectionModel().selectedRows() for index in sorted(indexes, reverse=True): data_list.pop(index.row()) self.model.set_data_list(data_list) def add_line(self): data_list = self.get_data_list() column_count = self.model.columnCount() data_list.append({self.header_list[j]["key"]: "" for j in range(column_count)}) self.model.set_data_list(data_list) def on_context_menu(self, point): x = point.x() y = point.y() self.popMenu.exec_(self.Table_View.mapToGlobal(QtCore.QPoint(x, y + 35))) def import_csv(self, path=None): if not path: path, _ = QFileDialog.getOpenFileName( self, caption=u"获取设置", filter=u"csv (*.csv)" ) # NOTE 如果文件不存在则返回空 if not path or not os.path.exists(path): return self.handle_csv(path) self.settings.setValue("csv_file", path) def export_csv(self, path=None): if not path: path, _ = QFileDialog.getSaveFileName( self, caption=u"输出设置", filter=u"csv (*.csv)" ) if not path: return csv = "" row_count = self.model.rowCount() column_count = self.model.columnCount() for i in range(row_count): col_list = [ self.model.itemData(self.model.index(i, j))[0] for j in range(column_count) ] csv += ",".join(col_list) + "\n" with open(path, "w", encoding="gbk") as f: f.write(csv) # NOTE 打开输出的路径 subprocess.Popen( ["start", "", os.path.dirname(path)], creationflags=0x08000000, shell=True ) def handle_csv(self, csv_path): data_list = [] with open(csv_path, "r") as f: reader = csv.reader(f) for i, row in enumerate(reader): data_list.append( { self.header_list[j]["key"]: r.decode("gbk") for j, r in enumerate(row) } ) self.model.set_data_list(data_list) self.Table_View.resizeColumnToContents(1) def get_selectal_mesh(self): skel_mesh_list = [ a for a in unreal.EditorUtilityLibrary.get_selected_assets() if isinstance(a, unreal.SkeletalMesh) ] if not skel_mesh_list: msg = u"请选择一个 Skeletal Mesh 物体" alert(msg) return [] return skel_mesh_list def clear_socket(self): for skel_mesh in self.get_selectal_mesh(): skeleton = skel_mesh.get_editor_property("skeleton") # NOTE 删除所有的 socket socket_list = [ skel_mesh.get_socket_by_index(i) for i in range(skel_mesh.num_sockets()) ] py_lib.delete_skeletal_mesh_socket(skeleton, socket_list) def add_socket(self): # NOTE 读取 Table View row_count = self.model.rowCount() itemData = self.model.itemData index = self.model.index data_dict = { itemData(index(i, 0))[0]: itemData(index(i, 1))[0] for i in range(row_count) } if not data_dict: msg = u"当前配置数据为空请导入一个 CSV" alert(msg) return fail_list = {} add_socket = py_lib.add_skeletal_mesh_socket bone_num = py_lib.get_skeleton_bone_num bone_name = py_lib.get_skeleton_bone_name for skel_mesh in self.get_selectal_mesh(): skeleton = skel_mesh.get_editor_property("skeleton") bone_list = [bone_name(skeleton, i) for i in range(bone_num(skeleton))] for bone, socket in data_dict.items(): # NOTE 检查骨骼名称是否存在 if not socket or skel_mesh.find_socket(socket): continue elif bone not in bone_list: skel_name = skel_mesh.get_name() if skel_name not in fail_list: fail_list[skel_name] = [] fail_list[skel_name].append(bone) continue _socket = add_socket(skeleton, bone).socket_name if _socket != socket: skel_mesh.rename_socket(_socket, socket) if fail_list: msg = u"以下骨骼名称没有在选中的角色找到↓↓↓\n\n" for skel_name, bone_list in fail_list.items(): msg += u"%s:\n%s" % (skel_name, "\n".join(bone_list)) alert(msg)
class PropertyTransferTool(QtWidgets.QWidget, MaterialTransfer, AssetList): state = PropertyTransferBinder() def update_icon(self): data = { "Del": { "icon": QtWidgets.QStyle.SP_BrowserStop, "callback": self.remove_assets, "tooltip": u"删除文件", }, "Locate": { "icon": QtWidgets.QStyle.SP_FileDialogContentsView, "callback": self.locate_asset_location, "tooltip": u"定位文件", }, "Drive": { "icon": QtWidgets.QStyle.SP_DriveHDIcon, "callback": self.locate_file_location, "tooltip": u"打开系统目录路径", }, "Expand": { "icon": QtWidgets.QStyle.SP_ToolBarVerticalExtensionButton, "callback": self.Property_Tree.expandAll, "tooltip": u"扩展全部", }, "Collapse": { "icon": QtWidgets.QStyle.SP_ToolBarHorizontalExtensionButton, "callback": self.Property_Tree.collapseAll, "tooltip": u"收缩全部", }, "Source_Locate": { "icon": QtWidgets.QStyle.SP_FileDialogContentsView, "callback": lambda: asset_lib.sync_browser_to_objects( [self.source.get_path_name()]), "tooltip": u"定位复制源", }, } widget_dict = { "BTN": "clicked", "Action": "triggered", } style = QtWidgets.QApplication.style() icon = style.standardIcon(QtWidgets.QStyle.SP_BrowserReload) self.setWindowIcon(icon) for typ, info in data.items(): icon = style.standardIcon(info.get("icon")) tooltip = info.get("tooltip", "") tooltip = '<span style="font-weight:600;">%s</span>' % tooltip callback = info.get("callback", lambda *args: None) for widget, signal in widget_dict.items(): widget = "%s_%s" % (typ, widget) if hasattr(self, widget): widget = getattr(self, widget) widget.setIcon(icon) getattr(widget, signal).connect(callback) widget.setToolTip(tooltip) widget.setEnabled(lambda: self.state.source_eanble) widget.setEnabled(self.state.source_eanble) # print(widget.isEnabled()) # QtCore.QTimer.singleShot(0, lambda: self.state.source_eanble >> Set(False)) def __init__(self, parent=None): super(PropertyTransferTool, self).__init__(parent) DIR, file_name = os.path.split(__file__) file_name = os.path.splitext(file_name)[0] load_ui(os.path.join(DIR, "%s.ui" % file_name), self) self.source = None help_link = "http://redarttoolkit.pages.oa.com/docs/posts/85c3f876.html" self.Help_Action.triggered.connect( lambda: webbrowser.open_new_tab(help_link)) # NOTE 设置按钮图标 QtCore.QTimer.singleShot(0, self.update_icon) menu_callback = lambda: self.Asset_Menu.popup(QtGui.QCursor.pos()) self.Asset_List.customContextMenuRequested.connect(menu_callback) menu_callback = lambda: self.Property_Menu.popup(QtGui.QCursor.pos()) self.Property_Tree.customContextMenuRequested.connect(menu_callback) self.Asset_Label.setVisible(lambda: self.state.lable_visible) self.Asset_Label.setText(lambda: self.state.lable_text) self.Src_BTN.clicked.connect(self.get_source) self.Dst_BTN.clicked.connect(self.get_destination) self.Transfer_BTN.clicked.connect(self.transfer_property) # NOTE 配置 splitter name = "%s.ini" % self.__class__.__name__ self.settings = QtCore.QSettings(name, QtCore.QSettings.IniFormat) self.Splitter.splitterMoved.connect(lambda: self.settings.setValue( "splitter_size", self.Splitter.sizes())) splitter_size = self.settings.value("splitter_size") size = [int(i) for i in splitter_size] if splitter_size else [700, 200] self.Splitter.setSizes(size) # NOTE 配置搜索栏 self.Dst_Filter_LE.search() self.Dst_Filter_LE.setPlaceholderText("") self.Prop_Filter_LE.search() self.Prop_Filter_LE.setPlaceholderText("") # NOTE 配置 Property_Tree self.property_model = MTableModel() columns = {u"property": "属性名", u"value": "数值"} self.property_header_list = [{ "label": label, "key": key, "bg_color": lambda x, y: y.get("bg_color", QtGui.QColor("transparent")), "checkable": i == 0, "searchable": True, "width": 300, "font": lambda x, y: { "bold": True }, } for i, (key, label) in enumerate(columns.items())] self.property_model.set_header_list(self.property_header_list) self.property_model_sort = MSortFilterModel() self.property_model_sort.set_header_list(self.property_header_list) self.property_model_sort.setSourceModel(self.property_model) self.Prop_Filter_LE.textChanged.connect( self.property_model_sort.set_search_pattern) self.Property_Tree.setModel(self.property_model_sort) header = self.Property_Tree.header() header.setStretchLastSection(True) # NOTE 配置 Asset_List self.asset_model = MTableModel() self.asset_header_list = [{ "label": "destination", "key": "destination", "bg_color": lambda x, y: y.get("bg_color", QtGui.QColor("transparent")), "tooltip": lambda x, y: y.get("asset").get_path_name(), "checkable": True, "searchable": True, "width": 300, "font": lambda x, y: { "bold": True }, }] self.asset_model.set_header_list(self.asset_header_list) self.asset_model_sort = MSortFilterModel() self.asset_model_sort.search_reg.setPatternSyntax( QtCore.QRegExp.RegExp) self.asset_model_sort.set_header_list(self.asset_header_list) self.asset_model_sort.setSourceModel(self.asset_model) self.Dst_Filter_LE.textChanged.connect( self.asset_model_sort.set_search_pattern) self.Asset_List.setModel(self.asset_model_sort) self.Asset_List.selectionModel().selectionChanged.connect( self.asset_selection_change) self.property_model.dataChanged.connect(self.asset_selection_change) def get_source(self): assets = util_lib.get_selected_assets() if len(assets) < 1: toast(u"请选择一个资产") return asset = assets[0] self.set_property_tree(asset) self.asset_model.set_data_list([]) self.property_model_sort.sort(0, QtCore.Qt.AscendingOrder) def get_destination(self): if not self.source: toast(u"请拾取复制源") return data_list = self.asset_model.get_data_list() tooltip_list = [ data.get("asset").get_path_name() for data in data_list ] assets = [ asset for asset in util_lib.get_selected_assets() if isinstance(asset, type(self.source)) and asset.get_path_name() not in tooltip_list ] if not assets: toast(u"请选择匹配的资产") return data_list.extend([ { "bg_color": QtGui.QColor("transparent"), "asset": asset, "destination": asset.get_name(), # NOTE 默认勾选 "destination_checked": QtCore.Qt.Checked, } for asset in assets if not asset is self.source ]) self.asset_model.set_data_list(data_list) def asset_selection_change(self, *args): model = self.Asset_List.selectionModel() indexes = model.selectedRows() if not indexes: return index = indexes[0] data_list = self.asset_model.get_data_list() data = data_list[index.row()] asset = data.get("asset") if isinstance(asset, unreal.MaterialInterface): self.update_material_property(asset) def set_property_tree(self, asset): if isinstance(asset, unreal.MaterialInterface): data = self.get_material_property(asset) elif isinstance(asset, unreal.Blueprint): pass else: toast(u"不支持资产") return data_list = [{ "property": group, "value": "", "children": [{ "bg_color": QtGui.QColor("transparent"), "property": prop, "property_checked": QtCore.Qt.Unchecked, "value": value, } for prop, value in props.items()], } for group, props in data.items()] self.property_model.set_data_list(data_list) self.source = asset self.state.source_eanble = True self.state.lable_visible = True self.state.lable_text = asset.get_name() def transfer_property(self): for i, asset in enumerate(self.asset_model.get_data_list()): if not asset.get("destination_checked"): continue # NOTE 选择 Asset_List 的资产 更新颜色 index = self.asset_model.index(i, 0) model = self.Asset_List.selectionModel() model.setCurrentIndex(index, model.SelectCurrent) property_list = [ prop.get("property") for grp in self.property_model.get_data_list() for prop in grp.get("children") # NOTE 过滤没有勾选的资源 if prop.get("bg_color") == QtGui.QColor("green") and prop.get("property_checked") ] # NOTE 传递属性 asset = asset.get("asset") if isinstance(asset, type(self.source)): self.transfer_material_property(asset, property_list) else: raise RuntimeError(u"%s 和复制源类型不匹配" % asset.get_name()) toast("传递完成", 'success')