# -*- coding: utf-8 -*- import json import os import traceback from krita import Krita from PyQt5.QtCore import Qt from PyQt5.QtWidgets import (QApplication, QButtonGroup, QCheckBox, QDialog, QDialogButtonBox, QFileDialog, QFormLayout, QGroupBox, QHBoxLayout, QLabel, QLineEdit, QListWidget, QMessageBox, QPlainTextEdit, QProgressBar, QPushButton, QRadioButton, QSpinBox, QStackedWidget, QVBoxLayout, QWidget, QAbstractItemView) from .exporter import ExportOptions, export_document, export_files SETTINGS_GROUP = "kra2svg_inkscape" class ExportDialog(QDialog): def __init__(self, parent=None): super().__init__(parent) self.setWindowTitle("KRA → SVG für Inkscape") self.resize(640, 620) self.app = Krita.instance() lay = QVBoxLayout(self) # ---- Modus ---- mode_box = QHBoxLayout() self.rb_single = QRadioButton("Aktives Dokument") self.rb_batch = QRadioButton("Batch (mehrere .kra-Dateien)") grp = QButtonGroup(self) grp.addButton(self.rb_single) grp.addButton(self.rb_batch) mode_box.addWidget(self.rb_single) mode_box.addWidget(self.rb_batch) mode_box.addStretch() lay.addLayout(mode_box) self.stack = QStackedWidget() lay.addWidget(self.stack) # Einzel w_single = QWidget() f = QFormLayout(w_single) self.ed_out = QLineEdit() b = QPushButton("…") b.clicked.connect(self._browse_out) row = QHBoxLayout() row.addWidget(self.ed_out) row.addWidget(b) f.addRow("SVG-Datei:", row) self.stack.addWidget(w_single) # Batch w_batch = QWidget() v = QVBoxLayout(w_batch) v.setContentsMargins(0, 0, 0, 0) self.lst = QListWidget() self.lst.setSelectionMode(QAbstractItemView.ExtendedSelection) v.addWidget(self.lst) row = QHBoxLayout() b_add = QPushButton("Dateien hinzufügen…") b_add.clicked.connect(self._add_files) b_dir = QPushButton("Ordner hinzufügen…") b_dir.clicked.connect(self._add_folder) b_rm = QPushButton("Entfernen") b_rm.clicked.connect(self._remove_files) for x in (b_add, b_dir, b_rm): row.addWidget(x) row.addStretch() v.addLayout(row) f2 = QFormLayout() self.ed_dir = QLineEdit() b = QPushButton("…") b.clicked.connect(self._browse_dir) row = QHBoxLayout() row.addWidget(self.ed_dir) row.addWidget(b) f2.addRow("Ausgabeordner:", row) v.addLayout(f2) self.stack.addWidget(w_batch) self.rb_single.toggled.connect(lambda on: self.stack.setCurrentIndex(0 if on else 1)) # ---- Optionen ---- box = QGroupBox("Optionen") ob = QFormLayout(box) self.cb_skip = QCheckBox("Unsichtbare Ebenen überspringen") self.cb_vec = QCheckBox("Vektorebenen als SVG-Objekte erhalten") self.cb_layers = QCheckBox("Krita-Gruppen als Inkscape-Ebenen (statt Gruppen)") self.cb_clip = QCheckBox("Auf Leinwand beschneiden") self.cb_phys = QCheckBox("Physische Größe (mm) aus Dokument-DPI") for cb in (self.cb_skip, self.cb_vec, self.cb_layers, self.cb_clip, self.cb_phys): ob.addRow(cb) self.sp_margin = QSpinBox() self.sp_margin.setRange(0, 512) self.sp_margin.setSuffix(" px") ob.addRow("Rand um PNGs:", self.sp_margin) prow = QHBoxLayout() self.rb_embed = QRadioButton("PNGs einbetten") self.rb_assets = QRadioButton("Separate Asset-Dateien (_assets/)") g2 = QButtonGroup(self) g2.addButton(self.rb_embed) g2.addButton(self.rb_assets) prow.addWidget(self.rb_embed) prow.addWidget(self.rb_assets) ob.addRow("Bilder:", prow) lay.addWidget(box) # ---- Log ---- self.progress = QProgressBar() self.progress.setVisible(False) lay.addWidget(self.progress) self.log_view = QPlainTextEdit() self.log_view.setReadOnly(True) self.log_view.setMinimumHeight(160) lay.addWidget(QLabel("Protokoll:")) lay.addWidget(self.log_view, 1) bb = QDialogButtonBox() self.btn_export = bb.addButton("Exportieren", QDialogButtonBox.AcceptRole) bb.addButton(QDialogButtonBox.Close) self.btn_export.clicked.connect(self._run) bb.rejected.connect(self.reject) lay.addWidget(bb) self._load_settings() # ---- Einstellungen ---------------------------------------------------- # def _options(self): return ExportOptions( skip_invisible=self.cb_skip.isChecked(), keep_vectors=self.cb_vec.isChecked(), embed_png=self.rb_embed.isChecked(), margin=self.sp_margin.value(), groups_as_layers=self.cb_layers.isChecked(), clip_to_canvas=self.cb_clip.isChecked(), physical_size=self.cb_phys.isChecked(), ) def _load_settings(self): try: o = ExportOptions.from_dict(json.loads( self.app.readSetting(SETTINGS_GROUP, "options", "{}") or "{}")) except ValueError: o = ExportOptions() self.cb_skip.setChecked(o.skip_invisible) self.cb_vec.setChecked(o.keep_vectors) self.cb_layers.setChecked(o.groups_as_layers) self.cb_clip.setChecked(o.clip_to_canvas) self.cb_phys.setChecked(o.physical_size) self.sp_margin.setValue(o.margin) (self.rb_embed if o.embed_png else self.rb_assets).setChecked(True) self.ed_dir.setText(self.app.readSetting(SETTINGS_GROUP, "batch_dir", "")) doc = self.app.activeDocument() if doc is not None: self.rb_single.setChecked(True) src = doc.fileName() if src: self.ed_out.setText(os.path.splitext(src)[0] + ".svg") else: self.rb_single.setEnabled(False) self.rb_batch.setChecked(True) self.stack.setCurrentIndex(0 if self.rb_single.isChecked() else 1) def _save_settings(self): self.app.writeSetting(SETTINGS_GROUP, "options", json.dumps(self._options().to_dict())) self.app.writeSetting(SETTINGS_GROUP, "batch_dir", self.ed_dir.text()) # ---- Dateiauswahl ----------------------------------------------------- # def _browse_out(self): p, _ = QFileDialog.getSaveFileName(self, "SVG speichern", self.ed_out.text(), "SVG (*.svg)") if p: if not p.lower().endswith(".svg"): p += ".svg" self.ed_out.setText(p) def _browse_dir(self): d = QFileDialog.getExistingDirectory(self, "Ausgabeordner", self.ed_dir.text()) if d: self.ed_dir.setText(d) def _existing(self): return {self.lst.item(i).text() for i in range(self.lst.count())} def _add_files(self): files, _ = QFileDialog.getOpenFileNames(self, ".kra-Dateien wählen", "", "Krita (*.kra)") have = self._existing() for p in files: if p not in have: self.lst.addItem(p) def _add_folder(self): d = QFileDialog.getExistingDirectory(self, "Ordner mit .kra-Dateien") if not d: return have = self._existing() for name in sorted(os.listdir(d)): p = os.path.join(d, name) if name.lower().endswith(".kra") and p not in have: self.lst.addItem(p) def _remove_files(self): for it in self.lst.selectedItems(): self.lst.takeItem(self.lst.row(it)) # ---- Export ----------------------------------------------------------- # def _log(self, msg): self.log_view.appendPlainText(msg) QApplication.processEvents() def _run(self): self._save_settings() opts = self._options() self.log_view.clear() self.btn_export.setEnabled(False) QApplication.setOverrideCursor(Qt.WaitCursor) try: if self.rb_single.isChecked(): self._run_single(opts) else: self._run_batch(opts) except Exception: self._log(traceback.format_exc()) QMessageBox.critical(self, "Export fehlgeschlagen", "Details stehen im Protokoll.") finally: QApplication.restoreOverrideCursor() self.btn_export.setEnabled(True) self.progress.setVisible(False) def _run_single(self, opts): doc = self.app.activeDocument() if doc is None: QMessageBox.warning(self, "Kein Dokument", "Es ist kein Dokument geöffnet.") return out = self.ed_out.text().strip() if not out: QMessageBox.warning(self, "Ziel fehlt", "Bitte eine SVG-Datei angeben.") return warnings = export_document(doc, out, opts, self._log) self._log("Fertig. %d Warnung(en)." % warnings) def _run_batch(self, opts): files = [self.lst.item(i).text() for i in range(self.lst.count())] out_dir = self.ed_dir.text().strip() if not files or not out_dir: QMessageBox.warning(self, "Angaben fehlen", "Bitte Dateien und einen Ausgabeordner wählen.") return self.progress.setRange(0, len(files)) self.progress.setValue(0) self.progress.setVisible(True) res = export_files(files, out_dir, opts, self._log, progress=lambda i, n: self.progress.setValue(i)) ok = sum(1 for r in res if r[1]) self._log("Fertig: %d von %d Datei(en) exportiert." % (ok, len(res)))