File indexing completed on 2025-09-21 04:03:13
0001 import threading 0002 from functools import reduce 0003 from PyQt5 import QtCore, QtWidgets, QtGui 0004 from PyQt5.QtCore import Qt 0005 0006 from ..exporters.base import exporters 0007 from ..importers.base import importers 0008 from ..parsers.baseporter import IoProgressReporter, ExtraOption 0009 0010 0011 class GuiProgressReporter(IoProgressReporter): 0012 def __init__(self): 0013 self.dialogs = {} 0014 self.threads = {} 0015 self.lock = threading.Lock() 0016 self.id = 0 0017 0018 @classmethod 0019 def set_global(cls): 0020 IoProgressReporter.instance = cls() 0021 0022 def gen_id(self, parent): 0023 self.id += 1 0024 with self.lock: 0025 dialog = QtWidgets.QProgressDialog(parent) 0026 dialog.setCancelButton(None) 0027 dialog.setWindowModality(Qt.ApplicationModal) 0028 self.dialogs[self.id] = dialog 0029 return self.id 0030 0031 def get_thread_dialog(self): 0032 with self.lock: 0033 id = threading.current_thread().ident 0034 return self.dialogs[self.threads[id]] 0035 0036 def setup(self, title, id): 0037 with self.lock: 0038 tid = threading.current_thread().ident 0039 self.threads[tid] = id 0040 dialog = self.dialogs[id] 0041 0042 dialog.setWindowTitle(title) 0043 dialog.hide() 0044 0045 def report_progress(self, title, value, total): 0046 dialog = self.get_thread_dialog() 0047 dialog.show() 0048 dialog.setValue(value) 0049 dialog.setMaximum(total) 0050 dialog.setLabelText(title) 0051 0052 def report_message(self, message): 0053 dialog = self.get_thread_dialog() 0054 dialog.show() 0055 dialog.setRange(0, 0) 0056 dialog.setLabelText(message) 0057 0058 def cleanup(self): 0059 with self.lock: 0060 id = threading.current_thread().ident 0061 dialog = self.dialogs.pop(self.threads.pop(id)) 0062 dialog.hide() 0063 dialog.deleteLater() 0064 0065 0066 class GuiBasePorter: 0067 def __init__(self, porter): 0068 self.porter = porter 0069 self.file_filter = "%s (%s)" % (porter.name, " ".join("*.%s" % e for e in porter.extensions)) 0070 self._dialog = None 0071 self.widgets = {} 0072 0073 def _build_dialog(self): 0074 self._dialog = QtWidgets.QDialog() 0075 self._dialog.setWindowTitle("Export to %s" % self.porter.name) 0076 layout = QtWidgets.QFormLayout() 0077 self._dialog.setLayout(layout) 0078 0079 for go in self.porter.generic_options: 0080 self._generic_option(layout, go) 0081 0082 for option in self.porter.extra_options: 0083 self._add_option(layout, option) 0084 0085 button_box = QtWidgets.QDialogButtonBox(QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel) 0086 button_box.accepted.connect(self._dialog.accept) 0087 button_box.rejected.connect(self._dialog.reject) 0088 layout.addRow(button_box) 0089 0090 def _add_option(self, layout, option: ExtraOption): 0091 if option.dest in self.widgets: 0092 return 0093 label = QtWidgets.QLabel(option.name.replace("_", " ").title()) 0094 0095 default = option.kwargs.get("default", None) 0096 option_type = option.kwargs.get("type", str) 0097 0098 if option.kwargs.get("action") == "store_true": 0099 widget = QtWidgets.QCheckBox() 0100 widget.setChecked(False) 0101 getter = widget.isChecked 0102 elif option.kwargs.get("action") == "store_false": 0103 widget = QtWidgets.QCheckBox() 0104 labtext = label.text() 0105 if labtext.startswith("No "): 0106 label.setText(labtext[3:]) 0107 widget.setChecked(True) 0108 getter = widget.isChecked 0109 elif "choices" in option.kwargs: 0110 widget = QtWidgets.QComboBox() 0111 for choice in option.kwargs["choices"]: 0112 widget.addItem(str(choice)) 0113 if default: 0114 widget.setCurrentText(str(default)) 0115 getter = lambda: option_type(widget.currentText()) 0116 elif option_type is int: 0117 widget = QtWidgets.QSpinBox() 0118 widget.setMinimum(-1000) 0119 widget.setMaximum(1000) 0120 if default is not None: 0121 widget.setValue(int(default)) 0122 getter = widget.value 0123 else: 0124 widget = QtWidgets.QLineEdit() 0125 if default is not None: 0126 widget.setText(str(default)) 0127 getter = widget.text 0128 0129 help = option.kwargs.get("help", "") 0130 widget.setWhatsThis(help) 0131 widget.setToolTip(help) 0132 0133 widget.setObjectName(option.dest) 0134 self.widgets[option.dest] = getter 0135 layout.addRow(label, widget) 0136 0137 @property 0138 def needs_dialog(self): 0139 return self.porter.generic_options or self.porter.extra_options 0140 0141 @property 0142 def dialog(self): 0143 if not self._dialog: 0144 self._build_dialog() 0145 return self._dialog 0146 0147 def get_options(self): 0148 return { 0149 name: getter() 0150 for name, getter in self.widgets.items() 0151 } 0152 0153 def prompt_options(self, parent): 0154 if self.needs_dialog: 0155 self.dialog.setParent(parent) 0156 self.dialog.setWindowFlags(Qt.Dialog) 0157 if self.dialog.exec_() != QtWidgets.QDialog.DialogCode.Accepted: 0158 return None 0159 0160 return self.get_options() 0161 0162 def _generic_option(self, layout, go): 0163 pass 0164 0165 0166 class GuiExporter(GuiBasePorter): 0167 def _generic_option(self, layout, go): 0168 if go == "pretty": 0169 self._add_option(layout, ExtraOption("pretty", action="store_true", help="Pretty print")) 0170 if go == "frame": 0171 self._add_option(layout, ExtraOption("frame", type=int, help="Frame to extract")) 0172 0173 @property 0174 def exporter(self): 0175 return self.porter 0176 0177 0178 class GuiImporter(GuiBasePorter): 0179 @property 0180 def importer(self): 0181 return self.porter 0182 0183 0184 class ExportThread(QtCore.QThread): 0185 def __init__(self, parent, exporter, animation, file_name, options): 0186 super().__init__() 0187 self.id = IoProgressReporter.instance.gen_id(parent) 0188 self.animation = animation 0189 self.exporter = exporter 0190 self.file_name = file_name 0191 self.options = options 0192 0193 def run(self): 0194 IoProgressReporter.instance.setup("Export to %s" % self.exporter.name, self.id) 0195 try: 0196 self.exporter.process(self.animation, self.file_name, **self.options) 0197 except Exception: 0198 IoProgressReporter.instance.report_message("Error on export") 0199 IoProgressReporter.instance.cleanup() 0200 0201 0202 gui_exporters = list(map(GuiExporter, exporters)) 0203 gui_importers = list(map(GuiImporter, importers)) 0204 0205 0206 def get_open_filename(parent, title, dirname): 0207 extensions = reduce(lambda a, b: a | b, (set(gi.importer.extensions) for gi in gui_importers)) 0208 all_files = "All Supported Files (%s)" % " ".join("*.%s" % ex for ex in extensions) 0209 filters = all_files + ";;" + ";;".join(ge.file_filter for ge in gui_importers) 0210 file_name, filter = QtWidgets.QFileDialog.getOpenFileName( 0211 parent, title, dirname, filters 0212 ) 0213 if file_name: 0214 if filter == all_files: 0215 importer = gui_importer_from_filename(file_name) 0216 if importer: 0217 return file_name, importer 0218 else: 0219 for importer in gui_importers: 0220 if importer.file_filter == filter: 0221 return file_name, importer 0222 0223 return None, None 0224 0225 0226 def get_save_filename(parent, title, dirname): 0227 extensions = reduce(lambda a, b: a | b, (set(gi.exporter.extensions) for gi in gui_exporters)) 0228 all_files = "All Supported Files (%s)" % " ".join("*.%s" % ex for ex in extensions) 0229 filters = all_files + ";;" + ";;".join(ge.file_filter for ge in gui_exporters) 0230 file_name, filter = QtWidgets.QFileDialog.getSaveFileName( 0231 parent, title, dirname, filters 0232 ) 0233 0234 if file_name: 0235 if filter == all_files: 0236 exporter = gui_exporter_from_filename(file_name) 0237 if exporter: 0238 return file_name, exporter 0239 else: 0240 for exporter in gui_exporters: 0241 if exporter.file_filter == filter: 0242 return file_name, exporter 0243 0244 return None, None 0245 0246 0247 def start_export(parent, exporter, animation, file_name, options): 0248 thread = ExportThread(parent, exporter, animation, file_name, options) 0249 thread.start() 0250 return thread 0251 0252 0253 def gui_importer_from_filename(filename): 0254 importer = importers.get_from_filename(filename) 0255 for gip in gui_importers: 0256 if gip.importer is importer: 0257 return gip 0258 return None 0259 0260 0261 def gui_exporter_from_filename(filename): 0262 exporter = exporters.get_from_filename(filename) 0263 for gip in gui_exporters: 0264 if gip.exporter is exporter: 0265 return gip 0266 return None