class RenamerWinBase(QtWidgets.QWidget): def __init__(self, parent=None): super(RenamerWinBase, 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) # NOTE 获取 convention 数据 self.conventions = get_convention() name = "%s.ini" % self.__class__.__name__ self.settings = QtCore.QSettings(name, QtCore.QSettings.IniFormat) # NOTE 配置 MComboBox self.Search_LE.setPlaceholderText(u"输入关键字") self.Replace_LE.setPlaceholderText(u"输入替换文字") self.Export_Setting_Action.triggered.connect(self.export_setting) self.Import_Setting_Action.triggered.connect(self.import_setting) self.Help_Action.triggered.connect(lambda: webbrowser.open_new_tab( 'http://wiki.l0v0.com/unreal/PyToolkit/#/msic/2_renamer')) self.Convention_Action.triggered.connect( lambda: webbrowser.open_new_tab( 'https://github.com/Allar/ue4-style-guide')) # NOTE 隐藏左侧配置项 self.Splitter.splitterMoved.connect(lambda: self.settings.setValue( "splitter_size", self.Splitter.sizes())) splitter_size = self.settings.value("splitter_size") self.Splitter.setSizes( [int(i) for i in splitter_size] if splitter_size else [0, 1]) # NOTE 配置 Header self.model = MTableModel() self.header_list = [{ 'label': data, 'key': data, 'bg_color': lambda x, y: y.get('bg_color', QtGui.QColor('transparent')), 'tooltip': lambda x, y: y.get('asset').get_path_name(), 'edit': lambda x, y: x or y.get('asset').get_name(), 'display': lambda x, y: x or y.get('asset').get_name(), 'editable': i == 1, 'draggable': True, 'width': 100, } for i, data in enumerate([u"原名称", u"新名称", u"文件类型"])] self.model.set_header_list(self.header_list) self.model_sort = MSortFilterModel() self.model_sort.setSourceModel(self.model) self.Table_View.setModel(self.model_sort) self.Table_View.setShowGrid(True) header = self.Table_View.horizontalHeader() header.setStretchLastSection(True) self.setAcceptDrops(True) self.load_settings() def export_setting(self): path, _ = QFileDialog.getSaveFileName(self, caption=u"输出设置", filter=u"ini (*.ini)") if not path: return copyfile(self.settings.fileName(), path) toast(u"导出成功", "info") def import_setting(self): path, _ = QFileDialog.getOpenFileName(self, caption=u"获取设置", filter=u"ini (*.ini)") # NOTE 如果文件不存在则返回空 if not path or not os.path.exists(path): return self.settings = QtCore.QSettings(path, QtCore.QSettings.IniFormat) self.settings.sync() self.load_settings() name = "%s.ini" % self.__class__.__name__ self.settings = QtCore.QSettings(name, QtCore.QSettings.IniFormat) self.save_settings() toast(u"加载成功", "info") @staticmethod def check_loop(loop_list): for w in loop_list: name = w.objectName() if not hasattr(w, "objectName") or not name: continue elif isinstance(w, QtWidgets.QLineEdit) and not name.endswith("_LE"): continue elif isinstance(w, QtWidgets.QSpinBox) and not name.endswith("_SP"): continue elif isinstance(w, QtWidgets.QCheckBox) and not name.endswith("_CB"): continue elif isinstance( w, QtWidgets.QRadioButton) and not name.endswith("_RB"): continue elif isinstance( w, QtWidgets.QComboBox) and not name.endswith("_Combo"): continue yield w def save_settings(self): CB_list = self.findChildren(QtWidgets.QCheckBox) RB_list = self.findChildren(QtWidgets.QRadioButton) LE_list = self.findChildren(QtWidgets.QLineEdit) SP_list = self.findChildren(QtWidgets.QSpinBox) Combo_list = self.findChildren(QtWidgets.QComboBox) for B in self.check_loop(CB_list + RB_list): self.settings.setValue(B.objectName(), B.isChecked()) for LE in self.check_loop(LE_list): self.settings.setValue(LE.objectName(), LE.text()) for SP in self.check_loop(SP_list): self.settings.setValue(SP.objectName(), SP.value()) for Combo in self.check_loop(Combo_list): index = Combo.currentIndex() self.settings.setValue(Combo.objectName(), index) # NOTE 获取 Table 记录 data_list = self.model.get_data_list() asset_data = [data.get("asset").get_path_name() for data in data_list] self.settings.setValue("table_asset", asset_data) def load_settings(self): CB_list = self.findChildren(QtWidgets.QCheckBox) RB_list = self.findChildren(QtWidgets.QRadioButton) LE_list = self.findChildren(QtWidgets.QLineEdit) SP_list = self.findChildren(QtWidgets.QSpinBox) Combo_list = self.findChildren(QtWidgets.QComboBox) widget_dict = {} for B in self.check_loop(CB_list + RB_list): val = self.settings.value(B.objectName()) if val is not None: val = True if str(val).lower() == "true" else False widget_dict[B.setChecked] = val for LE in self.check_loop(LE_list): val = self.settings.value(LE.objectName()) if val is not None: widget_dict[LE.setText] = val for SP in self.check_loop(SP_list): val = self.settings.value(SP.objectName()) if val is not None: widget_dict[SP.setValue] = int(val) for Combo in self.check_loop(Combo_list): val = self.settings.value(Combo.objectName()) if val is not None: widget_dict[Combo.setCurrentIndex] = int(val) # NOTE 添加 data_list asset_data = self.settings.value("table_asset") # NOTE 批量设置属性值 for setter, val in widget_dict.items(): setter(val) if not asset_data: return actor_list = [] asset_list = [] for path in asset_data: asset = unreal.load_object(None, path) if isinstance(asset, unreal.Actor): actor_list.append(asset) elif asset_lib.does_asset_exist(path): asset_list.append(asset) data_list = self.model.get_data_list() data_list.extend([{ 'bg_color': QtGui.QColor("transparent"), 'asset': asset, u"原名称": asset.get_name(), u"新名称": "", u"文件类型": type(asset).__name__, } for asset in asset_list]) data_list.extend([{ 'bg_color': QtGui.QColor("transparent"), 'asset': actor, u"原名称": actor.get_actor_label(), u"新名称": "", u"文件类型": type(actor).__name__, } for actor in actor_list]) self.model.set_data_list(data_list) self.update_table() def dragEnterEvent(self, event): event.accept() if event.mimeData().hasUrls() else event.ignore() def dropEvent(self, event): # Note 获取拖拽文件的地址 if event.mimeData().hasUrls: event.setDropAction(QtCore.Qt.CopyAction) event.accept() print(event.mimeData().urls()) else: event.ignore() def dragMoveEvent(self, event): if event.mimeData().hasUrls: event.setDropAction(QtCore.Qt.CopyAction) event.accept() else: event.ignore() def show(self): super(RenamerWinBase, self).show() # NOTE 配置 QGroupBox 的样式 self.setStyleSheet(self.styleSheet() + """ QGroupBox{ border: 0.5px solid black; padding-top:10px; } """) def update_table(self): raise NotImplementedError
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')
class MItemViewSet(QtWidgets.QWidget): sig_double_clicked = QtCore.Signal(QtCore.QModelIndex) sig_left_clicked = QtCore.Signal(QtCore.QModelIndex) TableViewType = MTableView BigViewType = MBigView TreeViewType = MTreeView ListViewType = MListView def __init__(self, view_type=None, parent=None): super(MItemViewSet, self).__init__(parent) self.main_lay = QtWidgets.QVBoxLayout() self.main_lay.setSpacing(5) self.main_lay.setContentsMargins(0, 0, 0, 0) self.sort_filter_model = MSortFilterModel() self.source_model = MTableModel() self.sort_filter_model.setSourceModel(self.source_model) view_class = view_type or MItemViewSet.TableViewType self.item_view = view_class() self.item_view.doubleClicked.connect(self.sig_double_clicked) self.item_view.pressed.connect(self.slot_left_clicked) self.item_view.setModel(self.sort_filter_model) self._search_line_edit = MLineEdit().search().small() self._search_attr_button = ( MToolButton().icon_only().svg("down_fill.svg").small()) self._search_line_edit.set_prefix_widget(self._search_attr_button) self._search_line_edit.textChanged.connect( self.sort_filter_model.set_search_pattern) self._search_line_edit.setVisible(False) self._search_lay = QtWidgets.QHBoxLayout() self._search_lay.setContentsMargins(0, 0, 0, 0) self._search_lay.addStretch() self._search_lay.addWidget(self._search_line_edit) self.main_lay.addLayout(self._search_lay) self.main_lay.addWidget(self.item_view) self.setLayout(self.main_lay) @QtCore.Slot(QtCore.QModelIndex) def slot_left_clicked(self, start_index): button = QtWidgets.QApplication.mouseButtons() if button == QtCore.Qt.LeftButton: real_index = self.sort_filter_model.mapToSource(start_index) self.sig_left_clicked.emit(real_index) def set_header_list(self, header_list): self.source_model.set_header_list(header_list) self.sort_filter_model.set_header_list(header_list) self.sort_filter_model.setSourceModel(self.source_model) self.item_view.set_header_list(header_list) @QtCore.Slot() def setup_data(self, data_list): self.source_model.clear() if data_list: self.source_model.set_data_list(data_list) self.item_view.set_header_list(self.source_model.header_list) def get_data(self): return self.source_model.get_data_list() def searchable(self): """Enable search line edit visible.""" self._search_line_edit.setVisible(True) return self def insert_widget(self, widget): """Use can insert extra widget into search layout.""" self._search_lay.insertWidget(0, widget)
class MItemViewFullSet(QtWidgets.QWidget): sig_double_clicked = QtCore.Signal(QtCore.QModelIndex) sig_left_clicked = QtCore.Signal(QtCore.QModelIndex) sig_current_changed = QtCore.Signal(QtCore.QModelIndex, QtCore.QModelIndex) sig_current_row_changed = QtCore.Signal(QtCore.QModelIndex, QtCore.QModelIndex) sig_current_column_changed = QtCore.Signal(QtCore.QModelIndex, QtCore.QModelIndex) sig_selection_changed = QtCore.Signal(QtCore.QItemSelection, QtCore.QItemSelection) sig_context_menu = QtCore.Signal(object) def __init__(self, table_view=True, big_view=False, parent=None): super(MItemViewFullSet, self).__init__(parent) self.sort_filter_model = MSortFilterModel() self.source_model = MTableModel() self.sort_filter_model.setSourceModel(self.source_model) self.stack_widget = QtWidgets.QStackedWidget() self.view_button_grp = MToolButtonGroup(exclusive=True) data_group = [] if table_view: self.table_view = MTableView(show_row_count=True) self.table_view.doubleClicked.connect(self.sig_double_clicked) self.table_view.pressed.connect(self.slot_left_clicked) self.table_view.setModel(self.sort_filter_model) self.stack_widget.addWidget(self.table_view) data_group.append({ "svg": "table_view.svg", "checkable": True, "tooltip": "Table View" }) if big_view: self.big_view = MBigView() self.big_view.doubleClicked.connect(self.sig_double_clicked) self.big_view.pressed.connect(self.slot_left_clicked) self.big_view.setModel(self.sort_filter_model) self.stack_widget.addWidget(self.big_view) data_group.append({ "svg": "big_view.svg", "checkable": True, "tooltip": "Big View" }) # 设置多个view 共享 MItemSelectionModel leader_view = self.stack_widget.widget(0) self.selection_model = leader_view.selectionModel() for index in range(self.stack_widget.count()): if index == 0: continue other_view = self.stack_widget.widget(index) other_view.setSelectionModel(self.selection_model) self.selection_model.currentChanged.connect(self.sig_current_changed) self.selection_model.currentRowChanged.connect( self.sig_current_row_changed) self.selection_model.currentColumnChanged.connect( self.sig_current_column_changed) self.selection_model.selectionChanged.connect( self.sig_selection_changed) self.tool_bar = QtWidgets.QWidget() self.top_lay = QtWidgets.QHBoxLayout() self.top_lay.setContentsMargins(0, 0, 0, 0) if data_group and len(data_group) > 1: self.view_button_grp.sig_checked_changed.connect( self.stack_widget.setCurrentIndex) self.view_button_grp.set_button_list(data_group) self.view_button_grp.set_dayu_checked(0) self.top_lay.addWidget(self.view_button_grp) self.search_line_edit = MLineEdit().search().small() self.search_attr_button = MToolButton().icon_only().svg( "down_fill.svg").small() self.search_line_edit.set_prefix_widget(self.search_attr_button) self.search_line_edit.textChanged.connect( self.sort_filter_model.set_search_pattern) self.search_line_edit.setVisible(False) self.top_lay.addStretch() self.top_lay.addWidget(self.search_line_edit) self.tool_bar.setLayout(self.top_lay) self.page_set = MPage() self.main_lay = QtWidgets.QVBoxLayout() self.main_lay.setSpacing(5) self.main_lay.setContentsMargins(0, 0, 0, 0) self.main_lay.addWidget(self.tool_bar) self.main_lay.addWidget(self.stack_widget) self.main_lay.addWidget(self.page_set) self.setLayout(self.main_lay) def enable_context_menu(self): for index in range(self.stack_widget.count()): view = self.stack_widget.widget(index) view.enable_context_menu(True) view.sig_context_menu.connect(self.sig_context_menu) def set_no_data_text(self, text): for index in range(self.stack_widget.count()): view = self.stack_widget.widget(index) view.set_no_data_text(text) def set_selection_mode(self, mode): for index in range(self.stack_widget.count()): view = self.stack_widget.widget(index) view.setSelectionMode(mode) def tool_bar_visible(self, flag): self.tool_bar.setVisible(flag) @QtCore.Slot(QtCore.QModelIndex) def slot_left_clicked(self, start_index): button = QtWidgets.QApplication.mouseButtons() if button == QtCore.Qt.LeftButton: real_index = self.sort_filter_model.mapToSource(start_index) self.sig_left_clicked.emit(real_index) def set_header_list(self, header_list): self.source_model.set_header_list(header_list) self.sort_filter_model.set_header_list(header_list) self.sort_filter_model.setSourceModel(self.source_model) for index in range(self.stack_widget.count()): view = self.stack_widget.widget(index) view.set_header_list(header_list) def tool_bar_append_widget(self, widget): self.top_lay.addWidget(widget) def tool_bar_insert_widget(self, widget): self.top_lay.insertWidget(0, widget) @QtCore.Slot() def setup_data(self, data_list): self.source_model.clear() if data_list: self.source_model.set_data_list(data_list) self.set_record_count(len(data_list)) for index in range(self.stack_widget.count()): view = self.stack_widget.widget(index) view.set_header_list(self.source_model.header_list) @QtCore.Slot(int) def set_record_count(self, total): self.page_set.set_total(total) def get_data(self): return self.source_model.get_data_list() def searchable(self): """Enable search line edit visible.""" self.search_line_edit.setVisible(True) return self