File indexing completed on 2024-05-12 05:46:37

0001 """
0002 GDQuest Batch Exporter
0003 -----------------
0004 Batch export art assets from Krita using layer metadata.
0005 Updates and reads metadata in Krita's layer names, and uses it to smartly process and export layers.
0006 Export to the Blender Cut-Out Animation tools for modular 2d game animation.
0007 Licensed under the GNU GPL v3.0 terms
0008 """
0009 
0010 from functools import partial
0011 from krita import DockWidget, DockWidgetFactory, DockWidgetFactoryBase, Krita
0012 from PyQt5.QtWidgets import (
0013     QPushButton,
0014     QStatusBar,
0015     QLabel,
0016     QLineEdit,
0017     QHBoxLayout,
0018     QVBoxLayout,
0019     QGroupBox,
0020     QWidget,
0021 )
0022 import os
0023 from .Config import CONFIG
0024 from .Infrastructure import WNode
0025 from .COATools import COAToolsFormat
0026 from .Utils import kickstart, flip
0027 from .Utils.Tree import iterPre
0028 
0029 KI = Krita.instance()
0030 
0031 
0032 def ensureRGBAU8(doc):
0033     ensured = doc.colorModel() == "RGBA" and doc.colorDepth() == "U8"
0034     if not ensured:
0035         raise ValueError("only RGBA 8-bit depth supported!")
0036 
0037 
0038 def exportAllLayers(cfg, statusBar):
0039     msg, timeout = (cfg["done"]["msg"].format("Exported all layers."), cfg["done"]["timeout"])
0040     try:
0041         doc = KI.activeDocument()
0042 
0043         root = doc.rootNode()
0044         root = WNode(cfg, root)
0045 
0046         dirName = os.path.dirname(doc.fileName())
0047         it = filter(lambda n: n.isExportable() and n.isMarked(), iterPre(root))
0048         it = map(partial(flip(WNode.save), dirName), it)
0049         kickstart(it)
0050     except ValueError as e:
0051         msg, timeout = cfg["error"]["msg"].format(e), cfg["error"]["timeout"]
0052     statusBar.showMessage(msg, timeout)
0053 
0054 
0055 def exportSelectedLayers(cfg, statusBar):
0056     msg, timeout = (cfg["done"]["msg"].format("Exported selected layers."), cfg["done"]["timeout"])
0057     try:
0058         doc = KI.activeDocument()
0059 
0060         dirName = os.path.dirname(doc.fileName())
0061         nodes = KI.activeWindow().activeView().selectedNodes()
0062         it = map(partial(WNode, cfg), nodes)
0063         it = map(partial(flip(WNode.save), dirName), it)
0064         kickstart(it)
0065     except ValueError as e:
0066         msg, timeout = cfg["error"]["msg"].format(e), cfg["error"]["timeout"]
0067     statusBar.showMessage(msg, timeout)
0068 
0069 
0070 def exportCOATools(mode, cfg, statusBar):
0071     msg, timeout = (
0072         cfg["done"]["msg"].format("Exported %s layers to COA Tools format." % (mode)),
0073         cfg["done"]["timeout"],
0074     )
0075     try:
0076         doc = KI.activeDocument()
0077         ensureRGBAU8(doc)
0078 
0079         coat_format = COAToolsFormat(cfg, statusBar)
0080         dirName = os.path.dirname(doc.fileName())
0081         nodes = KI.activeWindow().activeView().selectedNodes()
0082 
0083         # If mode is document or no nodes are selected, use document root
0084         if mode == "document" or len(nodes) == 0:
0085             nodes = [doc.rootNode()]
0086 
0087         it = map(partial(WNode, cfg), nodes)
0088         # By convention all selected nodes should be Group Layers
0089         # This is to represent a logical root for each export in COATools format
0090         it = filter(lambda n: n.isGroupLayer(), it)
0091         it = map(coat_format.collect, it)
0092         kickstart(it)
0093         coat_format.save(dirName)
0094 
0095     except ValueError as e:
0096         msg, timeout = cfg["error"]["msg"].format(e), cfg["error"]["timeout"]
0097     statusBar.showMessage(msg, timeout)
0098 
0099 
0100 def renameLayers(cfg, statusBar, lineEdit):
0101     msg, timeout = (cfg["done"]["msg"].format("Renaming successful!"), cfg["done"]["timeout"])
0102     try:
0103         nodes = KI.activeWindow().activeView().selectedNodes()
0104         it = map(partial(WNode, cfg), nodes)
0105         it = map(partial(flip(WNode.rename), lineEdit.text()), it)
0106         kickstart(it)
0107     except ValueError as e:
0108         msg, timeout = cfg["error"]["msg"].format(e), cfg["error"]["timeout"]
0109     statusBar.showMessage(msg, timeout)
0110 
0111 
0112 class GameArtTools(DockWidget):
0113     title = "Batch Exporter"
0114 
0115     def __init__(self):
0116         super().__init__()
0117         KI.setBatchmode(True)
0118         self.setWindowTitle(self.title)
0119         self.createInterface()
0120 
0121     def createInterface(self):
0122         uiContainer = QWidget(self)
0123 
0124         exportLabel = QLabel("Export")
0125         exportAllLayersButton = QPushButton("All Layers")
0126         exportSelectedLayersButton = QPushButton("Selected Layers")
0127         renameLabel = QLabel("Update Name and Metadata")
0128         renameLineEdit = QLineEdit()
0129         renameButton = QPushButton("Update")
0130         statusBar = QStatusBar()
0131 
0132         exportLabel.setToolTip("Export individual images")
0133         exportAllLayersButton.setToolTip("Export all layers with metadata")
0134         exportSelectedLayersButton.setToolTip("Export selected layers only")
0135         renameButton.setToolTip("Batch update selected layer names and metadata")
0136 
0137         # COA Tools GroupBox
0138         coaToolsGroupBox = QGroupBox("COA Tools")
0139         coaToolsHBoxLayout = QHBoxLayout()
0140         coaToolsExportSelectedLayersButton = QPushButton("Selected Layers")
0141         coaToolsExportDocumentButton = QPushButton("Document")
0142 
0143         coaToolsGroupBox.setToolTip("Blender Cut-Out Animation Tools")
0144         coaToolsExportSelectedLayersButton.setToolTip("Export selected layers only")
0145         coaToolsExportDocumentButton.setToolTip("Export all layers with metadata")
0146 
0147         coaToolsHBoxLayout.addWidget(coaToolsExportDocumentButton)
0148         coaToolsHBoxLayout.addWidget(coaToolsExportSelectedLayersButton)
0149         coaToolsGroupBox.setLayout(coaToolsHBoxLayout)
0150 
0151         vboxlayout = QVBoxLayout()
0152         vboxlayout.addWidget(exportLabel)
0153         vboxlayout.addWidget(exportAllLayersButton)
0154         vboxlayout.addWidget(exportSelectedLayersButton)
0155 
0156         vboxlayout.addWidget(coaToolsGroupBox)
0157         vboxlayout.addWidget(renameLabel)
0158         vboxlayout.addWidget(renameLineEdit)
0159 
0160         hboxlayout = QHBoxLayout()
0161         hboxlayout.addStretch()
0162         hboxlayout.addWidget(renameButton)
0163 
0164         vboxlayout.addLayout(hboxlayout)
0165         vboxlayout.addStretch()
0166         vboxlayout.addWidget(statusBar)
0167 
0168         uiContainer.setLayout(vboxlayout)
0169         self.setWidget(uiContainer)
0170 
0171         exportSelectedLayersButton.released.connect(
0172             partial(exportSelectedLayers, CONFIG, statusBar)
0173         )
0174         exportAllLayersButton.released.connect(partial(exportAllLayers, CONFIG, statusBar))
0175         coaToolsExportSelectedLayersButton.released.connect(
0176             partial(exportCOATools, "selected", CONFIG, statusBar)
0177         )
0178         coaToolsExportDocumentButton.released.connect(
0179             partial(exportCOATools, "document", CONFIG, statusBar)
0180         )
0181         renameLineEdit.returnPressed.connect(
0182             partial(renameLayers, CONFIG, statusBar, renameLineEdit)
0183         )
0184         renameButton.released.connect(partial(renameLayers, CONFIG, statusBar, renameLineEdit))
0185 
0186     def canvasChanged(self, canvas):
0187         pass
0188 
0189 
0190 def registerDocker():
0191     docker = DockWidgetFactory(
0192         "pykrita_gdquest_art_tools", DockWidgetFactoryBase.DockRight, GameArtTools
0193     )
0194     KI.addDockWidgetFactory(docker)