File indexing completed on 2024-06-16 04:17: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 SPDX-License-Identifier: GPL-3.0-only
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()
0130         renameButton.setIcon(KI.icon("view-refresh"))
0131         statusBar = QStatusBar()
0132 
0133         exportLabel.setToolTip("Export individual images")
0134         exportAllLayersButton.setToolTip("Export all layers with metadata")
0135         exportSelectedLayersButton.setToolTip("Export selected layers only")
0136         renameButton.setToolTip("Batch update selected layer names and metadata")
0137 
0138         # COA Tools GroupBox
0139         coaToolsGroupBox = QGroupBox("COA Tools")
0140         coaToolsHBoxLayout = QHBoxLayout()
0141         coaToolsExportSelectedLayersButton = QPushButton("Selected Layers")
0142         coaToolsExportDocumentButton = QPushButton("Document")
0143 
0144         coaToolsGroupBox.setToolTip("Blender Cut-Out Animation Tools")
0145         coaToolsExportSelectedLayersButton.setToolTip("Export selected layers only")
0146         coaToolsExportDocumentButton.setToolTip("Export all layers with metadata")
0147 
0148         coaToolsHBoxLayout.addWidget(coaToolsExportDocumentButton)
0149         coaToolsHBoxLayout.addWidget(coaToolsExportSelectedLayersButton)
0150         coaToolsGroupBox.setLayout(coaToolsHBoxLayout)
0151 
0152         vboxlayout = QVBoxLayout()
0153         vboxlayout.addWidget(exportLabel)
0154 
0155         vboxlayout.addWidget(exportAllLayersButton)
0156         vboxlayout.addWidget(exportSelectedLayersButton)
0157 
0158         vboxlayout.addWidget(coaToolsGroupBox)
0159         vboxlayout.addWidget(renameLabel)
0160 
0161         hboxlayout = QHBoxLayout()
0162         hboxlayout.addWidget(renameLineEdit)
0163         hboxlayout.addWidget(renameButton)
0164 
0165         vboxlayout.addLayout(hboxlayout)
0166         vboxlayout.addStretch()
0167         vboxlayout.addWidget(statusBar)
0168 
0169         uiContainer.setLayout(vboxlayout)
0170         self.setWidget(uiContainer)
0171 
0172         exportSelectedLayersButton.released.connect(
0173             partial(exportSelectedLayers, CONFIG, statusBar)
0174         )
0175         exportAllLayersButton.released.connect(partial(exportAllLayers, CONFIG, statusBar))
0176         coaToolsExportSelectedLayersButton.released.connect(
0177             partial(exportCOATools, "selected", CONFIG, statusBar)
0178         )
0179         coaToolsExportDocumentButton.released.connect(
0180             partial(exportCOATools, "document", CONFIG, statusBar)
0181         )
0182         renameLineEdit.returnPressed.connect(
0183             partial(renameLayers, CONFIG, statusBar, renameLineEdit)
0184         )
0185         renameButton.released.connect(partial(renameLayers, CONFIG, statusBar, renameLineEdit))
0186 
0187     def canvasChanged(self, canvas):
0188         pass
0189 
0190 
0191 def registerDocker():
0192     docker = DockWidgetFactory(
0193         "pykrita_gdquest_art_tools", DockWidgetFactoryBase.DockRight, GameArtTools
0194     )
0195     KI.addDockWidgetFactory(docker)