Пример #1
0
    def run(self):
        result = str()

        for img_file in self.img_list:
            if self.output_dir == Path('.'):
                self.output_dir = img_file.parent

            if not path_exists(self.output_dir):
                try:
                    self.output_dir.mkdir(parents=True)
                except Exception as e:
                    result += _(
                        'Konnte Ausgabe Verzeichnis nicht erstellen: {}'
                    ).format(f'{e}\n')
                    break

            # Open and convert image
            try:
                img = OpenImageUtil.read_image(img_file)
            except Exception as e:
                result += _(
                    'Konnte Bilddatei {} nicht konvertieren: {}').format(
                        img_file.name, f'{e}\n')
                continue

            # Write image to file
            try:
                img_out = self.output_dir / Path(img_file.stem).with_suffix(
                    self.output_format)
                OpenImageUtil.write_image(img_out, img)
                result += _('Bilddatei erstellt: {}').format(
                    f'{img_out.name}\n')
            except Exception as e:
                result += _('Konnte Bilddatei {} nicht erstellen: {}').format(
                    img_file.name, f'{e}\n')
                continue

            # Move source files
            if not self.move_converted or img_file.suffix == self.output_format:
                continue

            # Create un-converted directory
            move_dir = self.output_dir.parent / self.unconverted_dir_name
            if not path_exists(move_dir):
                move_dir.mkdir(parents=True)

            # Move the file to un-converted directory
            try:
                img_file.replace(move_dir / img_file.name)
                LOGGER.debug('Moving un-converted image file: %s',
                             img_file.name)
            except FileNotFoundError or FileExistsError:
                result += _(
                    'Konnte unkonvertierte Bilddatei nicht verschieben: {}'
                ).format(f'{img_file.name}')
                pass

        self.result_signal.emit(result)
Пример #2
0
    def convert_file(self,
                     img_file: Path,
                     output_dir: Path = Path('.'),
                     output_format: str = '.png',
                     move_converted: bool = False) -> bool:
        if not path_exists(img_file) or not path_exists(output_dir):
            return False
        if not img_file.suffix.casefold() in self.supported_image_types:
            return False

        self._start_img_thread([
            img_file,
        ], output_dir, output_format, move_converted)
        return True
Пример #3
0
    def open_existing_file(cls,
                           parent=None,
                           directory: Union[Path, str] = None,
                           file_key: str = 'xml') -> Union[str, None]:
        # Update path
        directory = cls._get_current_path(directory)

        # Update filter and title depending on file type
        if file_key not in cls.file_types.keys():
            file_key = 'xml'

        title = cls.file_types[file_key]['title']
        file_filter = cls.file_types[file_key]['filter']

        file, file_ext = cls.__create_file_dialog(parent, title, directory,
                                                  file_filter)

        if file and path_exists(file):
            if Path(file).suffix != f'.{file_key}':
                LOGGER.warning(
                    f'User supposed to open: %s but opened: %s - returning None',
                    f'.{file_key}',
                    Path(file).suffix)
                return

            KnechtSettings.app['current_path'] = Path(file).parent.as_posix()
            if file_key in cls.valid_recent_file_types:
                KnechtSettings.add_recent_file(Path(file).as_posix(), file_key)

        return file
Пример #4
0
    def restore(self) -> bool:
        """ Restore a user session asynchronous """
        if not path_exists(self.session_zip):
            return False

        self.load_dir = CreateZip.create_tmp_dir()

        try:
            with ZipFile(self.session_zip, 'r') as zip_file:
                zip_file.extractall(self.load_dir)
        except Exception as e:
            LOGGER.error(e)
            return False

        # Restore original file save paths
        Settings.load(self.restore_files_storage, self.load_dir / self.files_list_name)

        # Restore in saved order
        for file in self.restore_files_storage.restore_file_order(self.load_dir):
            LOGGER.debug('Starting restore of document: %s @ %s',
                         file.name, self.restore_files_storage.store.get(file.name))
            self.load_queue.append(file)

        self._load_next()
        return True
Пример #5
0
    def _await_rendered_image(self, img_path: Path):
        """ Wait until image was created """
        start_time = time.time()
        while not path_exists(img_path):
            time.sleep(1)

            self.display_remaining_time()

            if self.abort_rendering:
                return

            if self._is_timed_out(start_time, self.render_timeout):
                break

        # Leave some time for DG to write the image
        time.sleep(3)

        # Verify a valid image file was created
        self.status.emit(_('Prüfe Bilddaten...'))
        self.btn_text.emit(_('Prüfe Bilddaten...'))
        self.verify_rendered_image(img_path)

        # Image created
        self.status.emit(_('Rendering erzeugt.'))
        self.btn_text.emit(_('Rendering erzeugt.'))
        time.sleep(0.5)

        # Wait 5 seconds for DeltaGen to recover
        for count in range(5, 0, -1):
            msg = _('Erzeuge nächstes Bild in {}...').format(str(count))
            self.status.emit(msg + '\n')
            self.btn_text.emit(msg)
            time.sleep(1)
Пример #6
0
    def read_image(self) -> bool:
        if path_exists(self.file):
            self.file_is_valid = True
        else:
            return False

        # Get image meta data with OpenImageIO
        try:
            img_meta = OpenImageUtil.read_img_metadata(self.file)
        except Exception as e:
            LOGGER.error(e)
            return False

        # Read through image info dict for required camera tags
        for k, v in img_meta.items():
            for tag in self.rtt_camera_tags:
                if k.startswith(tag):
                    self.camera_info[k] = v

        if not img_meta or not self.camera_info:
            return False
        else:
            # Test if all required camera command keys are inside camera info
            if self.camera_info.keys().isdisjoint(self.rtt_camera_cmds.keys()):
                return False
            self.info_is_valid = True

        # Convert Camera Orientation
        self._convert_orientation()

        return True
Пример #7
0
    def convert_image_directory(self):
        img_dir = FileDialog.open_dir(self.ui, None)

        if not img_dir or not path_exists(img_dir):
            self.ui.msg(
                _('Zu konvertierendes Verzeichnis ist nicht erreichbar.'),
                8000)
            return

        img_dir = Path(img_dir)
        out_dir = img_dir / 'converted'

        try:
            out_dir.mkdir(exist_ok=True)
        except Exception as e:
            self.ui.msg(
                _('Konnte Bild Konvertierung Ausgabeverzeichnis nicht erstellen.'
                  ), 8000)
            LOGGER.warning(e)

        img_converter = KnechtImage(self)
        img_converter.conversion_result.connect(self._conversion_result)

        if img_converter.convert_directory(img_dir, out_dir):
            self.ui.msg(
                _('Bildkonvertierung gestartet.<br /><i>{}</i>').format(
                    img_dir), 5000)
        else:
            self.ui.msg(
                _('Bildkonvertierung konnte nicht gestartet werden. Keine konvertierbaren Dateien gefunden.'
                  ), 10000)
Пример #8
0
    def verify_paths(self):
        for p in (self.pos_path.path, self.xlsx_path.path):
            if p is None or not path_exists(p) or not p.is_file():
                break
        else:
            return True

        return False
Пример #9
0
    def load_docs(self):
        doc_file = Path(get_current_modules_dir()) / DOCS_HTML_FILEPATH

        if path_exists(doc_file):
            q = QUrl.fromLocalFile(doc_file.as_posix())
            LOGGER.info('Loading Documentation file: %s', q.toDisplayString())

            self.load(q)
Пример #10
0
 def open_desktop_directory(self, directory: Path):
     """ Open directory with desktop explorer """
     if path_exists(directory):
         q = QtCore.QUrl.fromLocalFile(directory.as_posix())
         QDesktopServices.openUrl(q)
     else:
         self.ovr.display(
             _('Verzeichnis existiert nicht.<br>{}<br>').format(
                 directory.as_posix()), 5000)
Пример #11
0
    def convert_directory(self,
                          img_dir: Path,
                          output_dir: Path = Path('.'),
                          output_format: str = '.png',
                          move_converted: bool = False) -> bool:
        if not path_exists(img_dir) or not path_exists(output_dir):
            return False

        img_list = list()
        for file in img_dir.glob('*.*'):
            if file.suffix.casefold() in self.supported_image_types:
                img_list.append(file)

        if not img_list:
            return False

        self._start_img_thread(img_list, output_dir, output_format,
                               move_converted)
        return True
Пример #12
0
    def __init__(self, version, logging_queue):
        LOGGER.info('Sys argv: %s', sys.argv)
        super(KnechtApp, self).__init__(sys.argv)

        splash = show_splash_screen_movie(self)

        self.version = version
        self.logging_queue = logging_queue

        # History widget will set active Undo Stack on view change
        self.undo_grp = QtWidgets.QUndoGroup(self)

        # -- Main Window --
        self.ui = KnechtWindow(self)
        self.ui.closeEvent = self.ui_close_event
        load_style(self)

        # -- Init DeltaGen thread --
        self.send_dg = SendToDeltaGen(self.ui)

        # -- Init Rendering Controller spawning rendering threads --
        self.render_dg = KnechtRender(self.ui)

        # -- Exception error box --
        self.error_message_box = GenericErrorBox(self.ui)

        # Prepare exception handling
        KnechtExceptionHook.app = self
        KnechtExceptionHook.setup_signal_destination(self.report_exception)

        # -- Show main window --
        self.ui.show()

        self.ready_to_quit = False
        self.aboutToQuit.connect(self.about_to_quit)

        splash.finish(self.ui)

        # Applying the style before showing the main window will not update
        # the menu font sizes
        self.setFont(FontRsc.regular)

        self.session_handler = None

        self.file_queue = list()
        if len(sys.argv) > 1:
            for a in sys.argv:
                if path_exists(a):
                    self.file_queue.append(Path(a))

            QTimer.singleShot(250, self._open_file_deferred)

        # Restore SessionData
        QTimer.singleShot(50, self.init_session)
Пример #13
0
    def _get_current_path(d) -> Path:
        # Current settings path
        __c = Path(KnechtSettings.app['current_path'])
        # Fallback path USERPROFILE path or current directory '.'
        __fallback = Path(os.getenv('USERPROFILE', '.'))

        if not d or not path_exists(d):
            if KnechtSettings.app['current_path'] not in [
                    '', '.'
            ] and path_exists(__c):
                # Set to settings current_path and continue with file vs. dir check
                d = __c
            else:
                return __fallback

        if Path(d).is_file():
            if path_exists(Path(d).parent):
                # Remove file and return directory
                return Path(d).parent
            else:
                return __fallback

        return Path(d)
Пример #14
0
    def _get_plmxml_item(self, variants: KnechtVariantList) -> Optional[Path]:
        if variants.plm_xml_path:
            return Path(variants.plm_xml_path)

        index, src_model = self.view.editor.get_current_selection()
        current_item = src_model.get_item(index)

        if current_item.userType != Kg.plmxml_item:
            return
        else:
            plmxml_file = Path(current_item.data(Kg.VALUE))
            if not path_exists(plmxml_file):
                return

        return plmxml_file
Пример #15
0
    def __init__(self, ui, file: Union[Path, str] = None):
        """ Wizard assisting the user to create presets from Vplus + FaKom data

        :param modules.gui.main_ui.KnechtWindow ui: Main Window
        """
        super(PresetWizard, self).__init__(parent=ui)
        self.ui = ui
        self.setWindowTitle(self.title)
        self.setWizardStyle(QWizard.ModernStyle)

        self._asked_for_close = False
        self.session = WizardSession(self)

        self.setButtonText(QWizard.BackButton, _('Zurück'))
        self.setButtonText(QWizard.NextButton, _('Weiter'))
        self.setButtonText(QWizard.FinishButton, _('Abschließen'))
        self.setButtonText(QWizard.CancelButton, _('Abbrechen'))

        self.automagic_clipboard = TreeClipboard()

        # --- Session Management ---
        session_btn = QPushButton(self)
        session_btn.setMinimumWidth(150)
        session_btn.setText(_('Sitzung'))
        session_btn.setMenu(WizardSessionMenu(self))
        self.setButton(self.CustomButton1, session_btn)
        self.setOption(self.HaveCustomButton1, True)

        # --- Navigation Menu ---
        nav_btn = QPushButton(self)
        nav_btn.setMinimumWidth(150)
        self.nav_menu = WizardNavMenu(self, nav_btn)
        self.setButton(self.CustomButton2, nav_btn)
        self.setOption(self.HaveCustomButton2, True)

        self.page_welcome = WelcomeWizardPage(self)
        self.page_import = ImportWizardPage(self)
        self.page_fakom = FakomWizardPage(self)
        self.page_placeholder = PlaceholderPage(self)
        self.page_result = ResultWizardPage(self)
        self.addPage(self.page_welcome)
        self.addPage(self.page_import)
        self.addPage(self.page_fakom)
        self.addPage(self.page_placeholder)

        # Load session file if provided
        if file and path_exists(file):
            self.open_session_file(Path(file).as_posix())
Пример #16
0
    def open_existing_directory(
        cls,
        parent=None,
        directory: Union[Path, str] = None,
    ) -> Union[str, None]:
        # Update path
        directory = cls._get_current_path(directory)

        title = cls.file_types['dir']['title']

        directory = cls.__create_dir_dialog(parent, title, directory)

        if directory and path_exists(directory):
            KnechtSettings.app['current_path'] = Path(directory).as_posix()

        return directory
Пример #17
0
    def excel_load_thread(signals: ThreadSignals, file: Path, pos_file: Path=None):
        """ The thread that loads the excel file """
        LOGGER.debug('Excel file reader thread started: %s', file.name)
        signals.progress_msg.emit(_('Excel Datei wird gelesen...'))

        xl = ExcelReader()
        fakom_data, knecht_data = FakomData(), KnData()
        fakom_result, xl_result = True, False

        # -- Read Fakom data if pos file provided --
        if pos_file and path_exists(pos_file):
            try:
                fakom_data = FakomReader.read_pos_file(pos_file)

                if fakom_data.empty():
                    xl.errors.append(_('POS Xml enthält keine bekannten Farbkombinationsmuster.'))
                    fakom_result = False
            except Exception as e:
                LOGGER.error(e)
                xl.errors.append(_('Konnte POS Xml Daten nicht lesen oder verarbeiten.'))
                fakom_result = False

        # -- Read Excel file --
        if fakom_result:
            try:
                # --- Create data from excel file ---
                xl_result = xl.read_file(file)
                signals.progress_msg.emit(_('Daten werden konvertiert...'))

                # --- Convert excel data to knecht data ---
                converter = ExcelDataToKnechtData(xl.data)
                knecht_data = converter.convert()
                knecht_data.fakom = fakom_data
            except Exception as e:
                LOGGER.error(e)
                xl_result = False

        # Transmit resulting data or report error
        if xl_result and fakom_result:
            signals.progress_msg.emit(_('Daten übertragen...'))
            LOGGER.debug('Excel read succeded. Converting ExcelData to KnData.')

            signals.finished.emit(knecht_data)
        else:
            LOGGER.debug('Excel read failed: %s', xl.errors)
            signals.error.emit(xl.errors)
Пример #18
0
    def save_xml(self):
        if not self.view_mgr.current_tab_is_document_tab():
            return

        self.enable_menus(False)

        file = self.view_mgr.current_file()

        if not file or not path_exists(file):
            if self._ask_save_as_file(file):
                # User agreed to set new save file
                self.save_as_xml()
                return
            # User aborted
            self.enable_menus(True)
            return

        self.save_as_xml(file)
Пример #19
0
    def drop_event(self, e: QDropEvent):
        mime: QMimeData = e.mimeData()
        src = e.source()

        # -- File drop --
        if mime.hasUrls():
            destination_index = self.view.indexAt(e.pos())
            for url in mime.urls():
                local_path = Path(url.toLocalFile())
                if not path_exists(local_path):
                    continue

                self.file_drop(local_path, destination_index)

            e.accept()
            return

        # --- Internal View Drops ---
        if not isinstance(src, self.view.__class__):
            e.ignore()
            return

        e.setDropAction(Qt.MoveAction)

        if src is not self.view:
            e.setDropAction(Qt.CopyAction)

        if e.keyboardModifiers() == Qt.ShiftModifier:
            e.setDropAction(Qt.CopyAction)

        # -- Copy drop --
        if e.dropAction() is Qt.CopyAction:
            destination_index = self.view.indexAt(e.pos())
            self.copy_drop(src, destination_index)
            e.accept()

        # -- Drag move --
        if e.dropAction() is Qt.MoveAction:
            destination_index = self.view.indexAt(e.pos())
            self.move_drop(destination_index)

            # Ignore default view behaviour
            e.ignore()
Пример #20
0
    def open(self, file: Union[Path, str]):
        file = Path(file)

        if not path_exists(file):
            LOGGER.info(
                'The provided Xml path does not seem to exist or is un-accessible.'
            )
            self.load_aborted.emit(
                _('Kann nicht auf die gewählte Datei zugreifen.'), file)
            return

        self.load_start_time = time.time()

        self.xml_worker = XmlWorkThread(file,
                                        self.xml_worker_queue,
                                        open_xml=True)
        self.xml_worker.xml_items_loaded.connect(self.load_thread_finished)

        self.xml_worker.start()
Пример #21
0
    def create_directory(self, render_dir):
        render_dir = Path(render_dir) / self.unique_out_dir_name

        if not path_exists(render_dir):
            try:
                render_dir.mkdir(parents=True)
            except Exception as e:
                LOGGER.critical(
                    'Could not create rendering directory! Rendering to executable path.\n%s',
                    e)
                render_dir = Path('.') / self.unique_out_dir_name

                try:
                    render_dir.mkdir(parents=True)
                except Exception as e:
                    LOGGER.critical(
                        'Could not create fallback directory! '
                        'Rendering will not be able to write images.\n%s', e)

        return render_dir
Пример #22
0
    def update_recent_files_menu(self):
        self.recent_menu.clear()

        if not len(KnechtSettings.app['recent_files']):
            no_entries_dummy = QAction(_("Keine Einträge vorhanden"),
                                       self.recent_menu)
            no_entries_dummy.setEnabled(False)
            self.recent_menu.addAction(no_entries_dummy)

        for idx, entry in enumerate(KnechtSettings.app['recent_files']):
            if idx >= 20:
                break

            file, file_type = entry
            file_name = Path(file).stem

            if not path_exists(file):
                # Skip and remove non existing files
                KnechtSettings.app['recent_files'].pop(idx)
                continue

            recent_action = QAction(f'{file_name} - {file_type}',
                                    self.recent_menu)
            recent_action.file = Path(file)

            if file_type == 'xml':
                recent_action.setText(f'{file_name} - Xml Presets')
                recent_action.setIcon(IconRsc.get_icon('document'))
                recent_action.triggered.connect(self._open_recent_xml_file)
            elif file_type == 'xlsx':
                recent_action.setText(f'{file_name} - Excel Import')
                recent_action.setIcon(IconRsc.get_icon('excel'))
                recent_action.triggered.connect(self._open_recent_xlsx_file)
            elif file_type == 'rksession':
                recent_action.setText(f'{file_name} - Preset Wizard Session')
                recent_action.setIcon(IconRsc.get_icon('qub_button'))
                recent_action.triggered.connect(self._open_recent_rksession)

            self.recent_menu.addAction(recent_action)

        self.recent_files_changed.emit()
Пример #23
0
    def doc_action(self, action: QAction):
        file, tab_page = Path('.'), None

        if self.context_tab_index >= 0:
            tab_page = self.ui.view_mgr.tab.widget(self.context_tab_index)
            file = self.ui.view_mgr.file_mgr.get_file_from_widget(
                tab_page) or Path('.')

        if not path_exists(file) or not file.is_file():
            self.ui.msg(
                _('Kein gültiger Pfad für das Dokument gesetzt. Das Dokument muss zuerst gespeichert werden.'
                  ), 5000)
            return

        if action == self.copy_action:
            self.ui.app.clipboard().setText(file.as_posix())
            self.ui.msg(
                _('Dokumenten Pfad wurde in die Zwischenablage kopiert.<br/><i>{}<i>'
                  ).format(file.as_posix()))
        elif action == self.open_action:
            q = QUrl.fromLocalFile(file.parent.as_posix())
            QDesktopServices.openUrl(q)
Пример #24
0
    def manager_delete_render_file(self, item):
        job = self.get_job_from_item_index(item)

        if not job:
            return

        if job.status < 4:
            self.ovr.display(
                '<b>Nachricht von Kapitän Offensichtlich:</b><br>'
                '<i>Rendering Szenen laufender Jobs können nicht gelöscht werden!</i>',
                7500)
            return

        # Path to scene file eg. C:/some_dir/some_file.csb
        scene_file = Path(job.remote_file)
        # Create render file name some_file_render.mb
        render_file = Path(scene_file.stem + '_render.mb')
        # Create the path to the render file C:/some_dir/some_file_render.mb
        render_file = scene_file.with_name(render_file.name)

        if path_exists(render_file):
            # Delete the file
            try:
                render_file.unlink()
            except Exception as e:
                LOGGER.error('Error deleting render file %s', e)
                self.ovr.display(
                    _('Fehler: Datei {} konnte nicht entfernt werden.<br>{}<br>'
                      ).format(render_file.name, e), 7500)
                return
        else:
            self.ovr.display(
                _('Datei {} <b>existiert nicht mehr oder kann nicht gefunden werden.</b><br>'
                  ).format(render_file.name), 7500)
            return

        self.ovr.display(
            _('Datei {} wurde entfernt.').format(render_file.name), 7500)
Пример #25
0
 def open_desktop_directory(self):
     """ Open directory with desktop explorer """
     if path_exists(self.plmxml.file.parent):
         q = QUrl.fromLocalFile(self.plmxml.file.parent.as_posix())
         QDesktopServices.openUrl(q)
Пример #26
0
    def open_settings_dir(self):
        settings_dir = Path(get_settings_dir())

        if path_exists(settings_dir):
            q = QUrl.fromLocalFile(settings_dir.as_posix())
            QDesktopServices.openUrl(q)
Пример #27
0
 def _is_update_ready(self) -> bool:
     if path_exists(self.installer_file):
         if self.remote_version > KnechtSettings.app['version']:
             return True
     return False
Пример #28
0
def start_app(app_path: Path):
    if path_exists(app_path) and app_path.is_file():
        Popen(app_path.as_posix())
Пример #29
0
    def setup_file_menu(self):
        insert_before = 0
        if self.ui.actionBeenden in self.menu.actions():
            insert_before = self.ui.actionBeenden
            self.ui.actionBeenden.setIcon(IconRsc.get_icon('sad'))
            self.ui.actionBeenden.setShortcut(QKeySequence('Ctrl+Q'))

        # ---- New file ----
        new_action = QAction(IconRsc.get_icon('document'), _('Neu\tStrg+N'),
                             self.menu)
        new_action.setShortcut(QKeySequence('Ctrl+N'))
        new_action.triggered.connect(self.new_document)
        self.menu.insertAction(insert_before, new_action)

        # ---- Open ----
        open_xml_action = QAction(_('Öffnen\tStrg+O'), self.menu)
        open_xml_action.setShortcut(QKeySequence('Ctrl+O'))
        open_xml_action.triggered.connect(self.open_xml)
        open_xml_action.setIcon(IconRsc.get_icon('folder'))
        self.menu.insertAction(insert_before, open_xml_action)

        # ---- Import Menu ----
        self.menu.insertMenu(insert_before, self.import_menu)

        # ---- Save ----
        save_xml_action = QAction(_('Speichern\tStrg+S'), self.menu)
        save_xml_action.setShortcut(QKeySequence('Ctrl+S'))
        save_xml_action.triggered.connect(self.save_xml)
        save_xml_action.setIcon(IconRsc.get_icon('disk'))
        self.menu.insertAction(insert_before, save_xml_action)

        save_as_action = QAction(_('Speichern unter ...\tStrg+Shift+S'),
                                 self.menu)
        save_as_action.setShortcut(QKeySequence('Ctrl+Shift+S'))
        save_as_action.triggered.connect(self.save_as_xml)
        save_as_action.setIcon(IconRsc.get_icon('save_alt'))
        self.menu.insertAction(insert_before, save_as_action)

        self.menu.insertSeparator(insert_before)

        # ---- Apps ----
        start_knecht_viewer = QAction(_('KnechtViewer starten'), self.menu)
        start_knecht_viewer.triggered.connect(self.start_knecht_viewer)
        start_knecht_viewer.setIcon(IconRsc.get_icon('img'))
        self.menu.insertAction(insert_before, start_knecht_viewer)
        if not path_exists(self.viewer_app):
            LOGGER.info('KnechtViewer executable could not be found: %s',
                        self.viewer_app.as_posix())
            start_knecht_viewer.setEnabled(False)

        start_schnuffi_app = QAction(_('POS Schnuffi starten'), self.menu)
        start_schnuffi_app.triggered.connect(self.start_schnuffi_app)
        start_schnuffi_app.setIcon(IconRsc.get_icon('dog'))
        self.menu.insertAction(insert_before, start_schnuffi_app)
        if not path_exists(self.schnuffi_app):
            LOGGER.info('KnechtViewer executable could not be found: %s',
                        self.schnuffi_app.as_posix())
            start_schnuffi_app.setEnabled(False)

        img_conv = QAction(_('Bilddaten konvertieren ...'))
        img_conv.triggered.connect(self.convert_image_directory)
        img_conv.setIcon(IconRsc.get_icon('render'))
        self.menu.insertAction(insert_before, img_conv)

        material_merger = QAction('AViT Material Merger')
        material_merger.triggered.connect(self.start_material_merger)
        material_merger.setIcon(IconRsc.get_icon('options'))
        self.menu.insertAction(insert_before, material_merger)

        self.menu.insertSeparator(insert_before)

        # ---- Recent files menu ----
        self.recent_menu.aboutToShow.connect(self.update_recent_files_menu)
        self.menu.insertMenu(insert_before, self.recent_menu)

        self.menu.insertSeparator(insert_before)