Files
kra2svg/kra2svg_inkscape/dialog.py
T

263 lines
9.8 KiB
Python
Raw Normal View History

# -*- 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 (<name>_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)))