erste lauffähige Version, exportiert .kra erfolgreich als .svg
vollständig von Claude geschrieben
This commit is contained in:
@@ -0,0 +1,262 @@
|
||||
# -*- 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)))
|
||||
Reference in New Issue
Block a user