diff --git a/ui/designer/tabs/tabTools/tabTools_Andreal.ui b/ui/designer/tabs/tabTools/tabTools_Andreal.ui
new file mode 100644
index 0000000..faa61c5
--- /dev/null
+++ b/ui/designer/tabs/tabTools/tabTools_Andreal.ui
@@ -0,0 +1,400 @@
+
+
+ TabTools_Andreal
+
+
+
+ 0
+ 0
+ 551
+ 500
+
+
+
+ TabTools_Andreal
+
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+ -
+
+
+ andrealFolder
+
+
+
+ -
+
+
+ -
+
+
+ andrealExecutable
+
+
+
+ -
+
+
+ -
+
+
+ imageType
+
+
+
+ -
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
-
+
+
+ /a
+
+
+ true
+
+
+
+ -
+
+
+ /a b30
+
+
+
+ -
+
+
+ /a info
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
+ imageTypeWhatIsThisButton
+
+
+
+
+
+
+ -
+
+
+ chart
+
+
+
+ -
+
+
+ imageVersion
+
+
+
+ -
+
+
+
+ 100
+ 16777215
+
+
+
+
+ -
+
+
+ imageFormat
+
+
+
+ -
+
+
+ jpgQuality
+
+
+
+ -
+
+
-
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
+ exportJsonButton
+
+
+
+ -
+
+
+ generatePreviewButton
+
+
+
+ -
+
+
+
+ true
+
+
+
+ generateImageButton
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
-
+
+
+ JPG
+
+
+ true
+
+
+
+ -
+
+
+ PNG
+
+
+
+
+
+
+ -
+
+
+ preview
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ QLabel { color: red }
+
+
+
+
+
+
+ -
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
-
+
+
+ 10
+
+
+ 100
+
+
+ 90
+
+
+ Qt::Horizontal
+
+
+
+ -
+
+
+ 10
+
+
+ 100
+
+
+ 90
+
+
+
+
+
+
+ -
+
+
+ false
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
-
+
+
+
+ 100
+ 100
+
+
+
+ chart.selectButton
+
+
+
+ -
+
+
+ ...
+
+
+
+
+
+
+
+
+
+
+ FileSelector
+ QWidget
+ ui.implements.components.fileSelector
+ 1
+
+
+
+
+
+ imageFormat_jpgRadioButton
+ toggled(bool)
+ jpgQualityHolderWidget
+ setEnabled(bool)
+
+
+ 141
+ 149
+
+
+ 330
+ 175
+
+
+
+
+ imageType_bestRadioButton
+ toggled(bool)
+ chartHolderWidget
+ setEnabled(bool)
+
+
+ 258
+ 64
+
+
+ 330
+ 94
+
+
+
+
+
diff --git a/ui/designer/tabs/tabTools/tabTools_Andreal_ui.py b/ui/designer/tabs/tabTools/tabTools_Andreal_ui.py
new file mode 100644
index 0000000..11a8761
--- /dev/null
+++ b/ui/designer/tabs/tabTools/tabTools_Andreal_ui.py
@@ -0,0 +1,264 @@
+# -*- coding: utf-8 -*-
+
+################################################################################
+## Form generated from reading UI file 'tabTools_Andreal.ui'
+##
+## Created by: Qt User Interface Compiler version 6.5.2
+##
+## WARNING! All changes made in this file will be lost when recompiling UI file!
+################################################################################
+
+from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale,
+ QMetaObject, QObject, QPoint, QRect,
+ QSize, QTime, QUrl, Qt)
+from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor,
+ QFont, QFontDatabase, QGradient, QIcon,
+ QImage, QKeySequence, QLinearGradient, QPainter,
+ QPalette, QPixmap, QRadialGradient, QTransform)
+from PySide6.QtWidgets import (QApplication, QComboBox, QFormLayout, QHBoxLayout,
+ QLabel, QPushButton, QRadioButton, QSizePolicy,
+ QSlider, QSpacerItem, QSpinBox, QWidget)
+
+from ui.implements.components.fileSelector import FileSelector
+
+class Ui_TabTools_Andreal(object):
+ def setupUi(self, TabTools_Andreal):
+ if not TabTools_Andreal.objectName():
+ TabTools_Andreal.setObjectName(u"TabTools_Andreal")
+ TabTools_Andreal.resize(551, 500)
+ TabTools_Andreal.setWindowTitle(u"TabTools_Andreal")
+ self.formLayout = QFormLayout(TabTools_Andreal)
+ self.formLayout.setObjectName(u"formLayout")
+ self.formLayout.setLabelAlignment(Qt.AlignRight|Qt.AlignTrailing|Qt.AlignVCenter)
+ self.label = QLabel(TabTools_Andreal)
+ self.label.setObjectName(u"label")
+
+ self.formLayout.setWidget(0, QFormLayout.LabelRole, self.label)
+
+ self.andrealFolderSelector = FileSelector(TabTools_Andreal)
+ self.andrealFolderSelector.setObjectName(u"andrealFolderSelector")
+
+ self.formLayout.setWidget(0, QFormLayout.FieldRole, self.andrealFolderSelector)
+
+ self.label_2 = QLabel(TabTools_Andreal)
+ self.label_2.setObjectName(u"label_2")
+
+ self.formLayout.setWidget(1, QFormLayout.LabelRole, self.label_2)
+
+ self.andrealExecutableSelector = FileSelector(TabTools_Andreal)
+ self.andrealExecutableSelector.setObjectName(u"andrealExecutableSelector")
+
+ self.formLayout.setWidget(1, QFormLayout.FieldRole, self.andrealExecutableSelector)
+
+ self.label_3 = QLabel(TabTools_Andreal)
+ self.label_3.setObjectName(u"label_3")
+
+ self.formLayout.setWidget(2, QFormLayout.LabelRole, self.label_3)
+
+ self.horizontalWidget = QWidget(TabTools_Andreal)
+ self.horizontalWidget.setObjectName(u"horizontalWidget")
+ self.horizontalLayout = QHBoxLayout(self.horizontalWidget)
+ self.horizontalLayout.setObjectName(u"horizontalLayout")
+ self.horizontalLayout.setContentsMargins(0, 0, 0, 0)
+ self.imageType_infoRadioButton = QRadioButton(self.horizontalWidget)
+ self.imageType_infoRadioButton.setObjectName(u"imageType_infoRadioButton")
+ self.imageType_infoRadioButton.setChecked(True)
+
+ self.horizontalLayout.addWidget(self.imageType_infoRadioButton)
+
+ self.imageType_best30RadioButton = QRadioButton(self.horizontalWidget)
+ self.imageType_best30RadioButton.setObjectName(u"imageType_best30RadioButton")
+
+ self.horizontalLayout.addWidget(self.imageType_best30RadioButton)
+
+ self.imageType_bestRadioButton = QRadioButton(self.horizontalWidget)
+ self.imageType_bestRadioButton.setObjectName(u"imageType_bestRadioButton")
+
+ self.horizontalLayout.addWidget(self.imageType_bestRadioButton)
+
+ self.horizontalSpacer_3 = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)
+
+ self.horizontalLayout.addItem(self.horizontalSpacer_3)
+
+ self.imageTypeWhatIsThisButton = QPushButton(self.horizontalWidget)
+ self.imageTypeWhatIsThisButton.setObjectName(u"imageTypeWhatIsThisButton")
+
+ self.horizontalLayout.addWidget(self.imageTypeWhatIsThisButton)
+
+
+ self.formLayout.setWidget(2, QFormLayout.FieldRole, self.horizontalWidget)
+
+ self.chartFormLabel = QLabel(TabTools_Andreal)
+ self.chartFormLabel.setObjectName(u"chartFormLabel")
+
+ self.formLayout.setWidget(3, QFormLayout.LabelRole, self.chartFormLabel)
+
+ self.label_5 = QLabel(TabTools_Andreal)
+ self.label_5.setObjectName(u"label_5")
+
+ self.formLayout.setWidget(4, QFormLayout.LabelRole, self.label_5)
+
+ self.imageVersionComboBox = QComboBox(TabTools_Andreal)
+ self.imageVersionComboBox.setObjectName(u"imageVersionComboBox")
+ self.imageVersionComboBox.setMaximumSize(QSize(100, 16777215))
+
+ self.formLayout.setWidget(4, QFormLayout.FieldRole, self.imageVersionComboBox)
+
+ self.label_6 = QLabel(TabTools_Andreal)
+ self.label_6.setObjectName(u"label_6")
+
+ self.formLayout.setWidget(5, QFormLayout.LabelRole, self.label_6)
+
+ self.jpgQualityFormLabel = QLabel(TabTools_Andreal)
+ self.jpgQualityFormLabel.setObjectName(u"jpgQualityFormLabel")
+
+ self.formLayout.setWidget(6, QFormLayout.LabelRole, self.jpgQualityFormLabel)
+
+ self.horizontalLayout_5 = QHBoxLayout()
+ self.horizontalLayout_5.setObjectName(u"horizontalLayout_5")
+ self.horizontalSpacer = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)
+
+ self.horizontalLayout_5.addItem(self.horizontalSpacer)
+
+ self.exportJsonButton = QPushButton(TabTools_Andreal)
+ self.exportJsonButton.setObjectName(u"exportJsonButton")
+
+ self.horizontalLayout_5.addWidget(self.exportJsonButton)
+
+ self.generatePreviewButton = QPushButton(TabTools_Andreal)
+ self.generatePreviewButton.setObjectName(u"generatePreviewButton")
+
+ self.horizontalLayout_5.addWidget(self.generatePreviewButton)
+
+ self.generateImageButton = QPushButton(TabTools_Andreal)
+ self.generateImageButton.setObjectName(u"generateImageButton")
+ font = QFont()
+ font.setBold(True)
+ self.generateImageButton.setFont(font)
+
+ self.horizontalLayout_5.addWidget(self.generateImageButton)
+
+ self.horizontalSpacer_2 = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)
+
+ self.horizontalLayout_5.addItem(self.horizontalSpacer_2)
+
+
+ self.formLayout.setLayout(7, QFormLayout.SpanningRole, self.horizontalLayout_5)
+
+ self.horizontalWidget_2 = QWidget(TabTools_Andreal)
+ self.horizontalWidget_2.setObjectName(u"horizontalWidget_2")
+ sizePolicy = QSizePolicy(QSizePolicy.Maximum, QSizePolicy.Preferred)
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(self.horizontalWidget_2.sizePolicy().hasHeightForWidth())
+ self.horizontalWidget_2.setSizePolicy(sizePolicy)
+ self.horizontalLayout_2 = QHBoxLayout(self.horizontalWidget_2)
+ self.horizontalLayout_2.setObjectName(u"horizontalLayout_2")
+ self.horizontalLayout_2.setContentsMargins(0, 0, 0, 0)
+ self.imageFormat_jpgRadioButton = QRadioButton(self.horizontalWidget_2)
+ self.imageFormat_jpgRadioButton.setObjectName(u"imageFormat_jpgRadioButton")
+ self.imageFormat_jpgRadioButton.setText(u"JPG")
+ self.imageFormat_jpgRadioButton.setChecked(True)
+
+ self.horizontalLayout_2.addWidget(self.imageFormat_jpgRadioButton)
+
+ self.imageFormat_pngRadioButton = QRadioButton(self.horizontalWidget_2)
+ self.imageFormat_pngRadioButton.setObjectName(u"imageFormat_pngRadioButton")
+ self.imageFormat_pngRadioButton.setText(u"PNG")
+
+ self.horizontalLayout_2.addWidget(self.imageFormat_pngRadioButton)
+
+
+ self.formLayout.setWidget(5, QFormLayout.FieldRole, self.horizontalWidget_2)
+
+ self.label_8 = QLabel(TabTools_Andreal)
+ self.label_8.setObjectName(u"label_8")
+
+ self.formLayout.setWidget(8, QFormLayout.LabelRole, self.label_8)
+
+ self.previewLabel = QLabel(TabTools_Andreal)
+ self.previewLabel.setObjectName(u"previewLabel")
+ sizePolicy1 = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Expanding)
+ sizePolicy1.setHorizontalStretch(0)
+ sizePolicy1.setVerticalStretch(0)
+ sizePolicy1.setHeightForWidth(self.previewLabel.sizePolicy().hasHeightForWidth())
+ self.previewLabel.setSizePolicy(sizePolicy1)
+ self.previewLabel.setStyleSheet(u"QLabel { color: red }")
+ self.previewLabel.setText(u"")
+
+ self.formLayout.setWidget(8, QFormLayout.FieldRole, self.previewLabel)
+
+ self.jpgQualityHolderWidget = QWidget(TabTools_Andreal)
+ self.jpgQualityHolderWidget.setObjectName(u"jpgQualityHolderWidget")
+ self.horizontalLayout_3 = QHBoxLayout(self.jpgQualityHolderWidget)
+ self.horizontalLayout_3.setObjectName(u"horizontalLayout_3")
+ self.horizontalLayout_3.setContentsMargins(0, 0, 0, 0)
+ self.jpgQualitySlider = QSlider(self.jpgQualityHolderWidget)
+ self.jpgQualitySlider.setObjectName(u"jpgQualitySlider")
+ self.jpgQualitySlider.setMinimum(10)
+ self.jpgQualitySlider.setMaximum(100)
+ self.jpgQualitySlider.setValue(90)
+ self.jpgQualitySlider.setOrientation(Qt.Horizontal)
+
+ self.horizontalLayout_3.addWidget(self.jpgQualitySlider)
+
+ self.jpgQualitySpinBox = QSpinBox(self.jpgQualityHolderWidget)
+ self.jpgQualitySpinBox.setObjectName(u"jpgQualitySpinBox")
+ self.jpgQualitySpinBox.setMinimum(10)
+ self.jpgQualitySpinBox.setMaximum(100)
+ self.jpgQualitySpinBox.setValue(90)
+
+ self.horizontalLayout_3.addWidget(self.jpgQualitySpinBox)
+
+
+ self.formLayout.setWidget(6, QFormLayout.FieldRole, self.jpgQualityHolderWidget)
+
+ self.chartHolderWidget = QWidget(TabTools_Andreal)
+ self.chartHolderWidget.setObjectName(u"chartHolderWidget")
+ self.chartHolderWidget.setEnabled(False)
+ self.horizontalLayout_8 = QHBoxLayout(self.chartHolderWidget)
+ self.horizontalLayout_8.setObjectName(u"horizontalLayout_8")
+ self.horizontalLayout_8.setContentsMargins(0, 0, 0, 0)
+ self.chartSelectButton = QPushButton(self.chartHolderWidget)
+ self.chartSelectButton.setObjectName(u"chartSelectButton")
+ self.chartSelectButton.setMaximumSize(QSize(100, 100))
+
+ self.horizontalLayout_8.addWidget(self.chartSelectButton)
+
+ self.chartSelectLabel = QLabel(self.chartHolderWidget)
+ self.chartSelectLabel.setObjectName(u"chartSelectLabel")
+ self.chartSelectLabel.setText(u"...")
+
+ self.horizontalLayout_8.addWidget(self.chartSelectLabel)
+
+
+ self.formLayout.setWidget(3, QFormLayout.FieldRole, self.chartHolderWidget)
+
+
+ self.retranslateUi(TabTools_Andreal)
+ self.imageFormat_jpgRadioButton.toggled.connect(self.jpgQualityHolderWidget.setEnabled)
+ self.imageType_bestRadioButton.toggled.connect(self.chartHolderWidget.setEnabled)
+
+ QMetaObject.connectSlotsByName(TabTools_Andreal)
+ # setupUi
+
+ def retranslateUi(self, TabTools_Andreal):
+ self.label.setText(QCoreApplication.translate("TabTools_Andreal", u"andrealFolder", None))
+ self.label_2.setText(QCoreApplication.translate("TabTools_Andreal", u"andrealExecutable", None))
+ self.label_3.setText(QCoreApplication.translate("TabTools_Andreal", u"imageType", None))
+ self.imageType_infoRadioButton.setText(QCoreApplication.translate("TabTools_Andreal", u"/a", None))
+ self.imageType_best30RadioButton.setText(QCoreApplication.translate("TabTools_Andreal", u"/a b30", None))
+ self.imageType_bestRadioButton.setText(QCoreApplication.translate("TabTools_Andreal", u"/a info", None))
+ self.imageTypeWhatIsThisButton.setText(QCoreApplication.translate("TabTools_Andreal", u"imageTypeWhatIsThisButton", None))
+ self.chartFormLabel.setText(QCoreApplication.translate("TabTools_Andreal", u"chart", None))
+ self.label_5.setText(QCoreApplication.translate("TabTools_Andreal", u"imageVersion", None))
+ self.label_6.setText(QCoreApplication.translate("TabTools_Andreal", u"imageFormat", None))
+ self.jpgQualityFormLabel.setText(QCoreApplication.translate("TabTools_Andreal", u"jpgQuality", None))
+ self.exportJsonButton.setText(QCoreApplication.translate("TabTools_Andreal", u"exportJsonButton", None))
+ self.generatePreviewButton.setText(QCoreApplication.translate("TabTools_Andreal", u"generatePreviewButton", None))
+ self.generateImageButton.setText(QCoreApplication.translate("TabTools_Andreal", u"generateImageButton", None))
+ self.label_8.setText(QCoreApplication.translate("TabTools_Andreal", u"preview", None))
+ self.chartSelectButton.setText(QCoreApplication.translate("TabTools_Andreal", u"chart.selectButton", None))
+ pass
+ # retranslateUi
+
diff --git a/ui/designer/tabs/tabToolsEntry.ui b/ui/designer/tabs/tabToolsEntry.ui
index 1378563..ae30179 100644
--- a/ui/designer/tabs/tabToolsEntry.ui
+++ b/ui/designer/tabs/tabToolsEntry.ui
@@ -29,6 +29,11 @@
tab.stepCalculator
+
+
+ tab.andreal
+
+
@@ -46,6 +51,12 @@
ui.implements.tabs.tabTools.tabTools_StepCalculator
1
+
+ TabTools_Andreal
+ QWidget
+ ui.implements.tabs.tabTools.tabTools_Andreal
+ 1
+
diff --git a/ui/designer/tabs/tabToolsEntry_ui.py b/ui/designer/tabs/tabToolsEntry_ui.py
index b0a30e0..7607b9c 100644
--- a/ui/designer/tabs/tabToolsEntry_ui.py
+++ b/ui/designer/tabs/tabToolsEntry_ui.py
@@ -18,6 +18,7 @@ from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor,
from PySide6.QtWidgets import (QApplication, QSizePolicy, QTabWidget, QVBoxLayout,
QWidget)
+from ui.implements.tabs.tabTools.tabTools_Andreal import TabTools_Andreal
from ui.implements.tabs.tabTools.tabTools_InfoLookup import TabTools_InfoLookup
from ui.implements.tabs.tabTools.tabTools_StepCalculator import TabTools_StepCalculator
@@ -37,6 +38,9 @@ class Ui_TabToolsEntry(object):
self.tab_2 = TabTools_StepCalculator()
self.tab_2.setObjectName(u"tab_2")
self.tabWidget.addTab(self.tab_2, "")
+ self.tab_3 = TabTools_Andreal()
+ self.tab_3.setObjectName(u"tab_3")
+ self.tabWidget.addTab(self.tab_3, "")
self.verticalLayout.addWidget(self.tabWidget)
@@ -52,6 +56,7 @@ class Ui_TabToolsEntry(object):
def retranslateUi(self, TabToolsEntry):
self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab), QCoreApplication.translate("TabToolsEntry", u"tab.infoLookup", None))
self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_2), QCoreApplication.translate("TabToolsEntry", u"tab.stepCalculator", None))
+ self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_3), QCoreApplication.translate("TabToolsEntry", u"tab.andreal", None))
pass
# retranslateUi
diff --git a/ui/extends/tabs/tabTools/tabTools_Andreal.py b/ui/extends/tabs/tabTools/tabTools_Andreal.py
new file mode 100644
index 0000000..6b233b0
--- /dev/null
+++ b/ui/extends/tabs/tabTools/tabTools_Andreal.py
@@ -0,0 +1,81 @@
+import base64
+import logging
+import os
+import re
+
+from PySide6.QtCore import QObject, QProcess, QRunnable, QThreadPool, Signal
+
+logger = logging.getLogger(__name__)
+
+
+class AndrealExecuteRunnableSignals(QObject):
+ error = Signal(str, str)
+ completed = Signal(str, bytes)
+ finished = Signal()
+
+
+class AndrealExecuteRunnable(QRunnable):
+ def __init__(self, executePath, jsonPath, arguments):
+ super().__init__()
+ self.signals = AndrealExecuteRunnableSignals()
+ self.executePath = executePath
+ self.jsonPath = jsonPath
+ self.arguments = arguments
+
+ def run(self):
+ try:
+ result = os.popen(f"{self.executePath} {' '.join(self.arguments)}").read()
+ b64Result = [s for s in result.split("\n") if s]
+ imageBytes = base64.b64decode(
+ re.sub(r"data:image/.*;base64,", "", b64Result[-1])
+ )
+ self.signals.completed.emit(self.jsonPath, imageBytes)
+ except Exception as e:
+ imageBytes = None
+ logger.exception(f"{self.__class__.__name__} error")
+ self.signals.error.emit(self.jsonPath, str(e))
+ finally:
+ os.unlink(self.jsonPath)
+ self.signals.finished.emit()
+
+
+class AndrealHelper(QObject):
+ error = Signal(str, str)
+ ready = Signal(str, bytes)
+ finished = Signal()
+
+ def __init__(self, parent=None):
+ super().__init__(parent)
+
+ self.andrealFolder = None
+ self.__andrealExecutable = None
+
+ self.andrealTestProcess = QProcess(self)
+ self.__andrealTestResult: bool = False
+
+ @property
+ def andrealExecutable(self):
+ return self.__andrealExecutable
+
+ @andrealExecutable.setter
+ def andrealExecutable(self, value: str):
+ self.__andrealExecutable = value
+ self.andrealTestProcess.start(self.__andrealExecutable, ["--help"])
+ self.andrealTestProcess.waitForFinished()
+ result = bytes(self.andrealTestProcess.readAll()).decode("utf-8")
+ self.__andrealTestResult = "Andreal" in result and "--json-file" in result
+
+ @property
+ def andrealTestResult(self):
+ return self.__andrealTestResult
+
+ def andrealOutputToImage(self, output: str):
+ b64result = output.split("\n")[-1]
+ return base64.b64decode(b64result)
+
+ def request(self, jsonPath: str, arguments: list[str]):
+ runnable = AndrealExecuteRunnable(self.andrealExecutable, jsonPath, arguments)
+ runnable.signals.error.connect(self.error)
+ runnable.signals.completed.connect(self.ready)
+ runnable.signals.finished.connect(self.finished)
+ QThreadPool.globalInstance().start(runnable)
diff --git a/ui/implements/settings/settingsAndreal.py b/ui/implements/settings/settingsAndreal.py
index 2d6244d..5373ed8 100644
--- a/ui/implements/settings/settingsAndreal.py
+++ b/ui/implements/settings/settingsAndreal.py
@@ -11,6 +11,9 @@ class SettingsAndreal(SettingsBaseWidget):
self.setupUi(self)
+ self.andrealFolderValueWidget.setMode(
+ self.andrealFolderValueWidget.getExistingDirectory
+ )
if self.settings.andrealFolder():
self.andrealFolderValueWidget.selectFile(self.settings.andrealFolder())
self.andrealFolderValueWidget.filesSelected.connect(self.setAndrealFolder)
diff --git a/ui/implements/tabs/tabTools/tabTools_Andreal.py b/ui/implements/tabs/tabTools/tabTools_Andreal.py
new file mode 100644
index 0000000..ff53822
--- /dev/null
+++ b/ui/implements/tabs/tabTools/tabTools_Andreal.py
@@ -0,0 +1,239 @@
+import json
+import logging
+import time
+
+from arcaea_offline.database import Database
+from arcaea_offline.external.andreal.api_data import (
+ AndrealImageGeneratorApiDataConverter,
+)
+from arcaea_offline.models import Chart
+from arcaea_offline.utils.rating import rating_class_to_short_text
+from PySide6.QtCore import QDir, QFileInfo, Qt, Slot
+from PySide6.QtGui import QImage
+from PySide6.QtWidgets import QButtonGroup, QFileDialog, QLabel, QMessageBox, QWidget
+
+from ui.designer.tabs.tabTools.tabTools_Andreal_ui import Ui_TabTools_Andreal
+from ui.extends.shared.settings import ANDREAL_EXECUTABLE, ANDREAL_FOLDER
+from ui.extends.tabs.tabTools.tabTools_Andreal import AndrealHelper
+from ui.implements.components.chartSelector import ChartSelector
+from ui.implements.components.songIdSelector import SongIdSelectorMode
+
+logger = logging.getLogger(__name__)
+
+
+class PreviewLabel(QLabel):
+ ...
+
+
+class ChartSelectorDialog(ChartSelector):
+ def __init__(self, parent=None):
+ super().__init__(parent)
+ self.setWindowFlag(Qt.WindowType.Dialog, True)
+ self.setSongIdSelectorMode(SongIdSelectorMode.Chart)
+
+
+class TabTools_Andreal(Ui_TabTools_Andreal, QWidget):
+ def __init__(self, parent=None):
+ super().__init__(parent)
+ self.setupUi(self)
+
+ self.db = Database()
+
+ self.andrealHelper = AndrealHelper(self)
+
+ self.andrealFolderSelector.setMode(
+ self.andrealFolderSelector.getExistingDirectory
+ )
+
+ self.andrealFolderSelector.filesSelected.connect(self.setHelperPaths)
+ self.andrealExecutableSelector.filesSelected.connect(self.setHelperPaths)
+
+ self.andrealFolderSelector.connectSettings(ANDREAL_FOLDER)
+ self.andrealExecutableSelector.connectSettings(ANDREAL_EXECUTABLE)
+
+ self.generateImageButton.clicked.connect(self.requestGenerate)
+
+ self.infoChart: Chart | None = None
+
+ self.previewJsonPath = None
+ self.generateJsonPath = None
+ self.generateImageFormat = None
+
+ self.andrealHelper.error.connect(self.generateError)
+ self.andrealHelper.ready.connect(self.generateReady)
+ self.andrealHelper.finished.connect(self.generateFinished)
+
+ self.imageTypeButtonGroup = QButtonGroup(self)
+ self.imageTypeButtonGroup.addButton(self.imageType_infoRadioButton, 0)
+ self.imageTypeButtonGroup.addButton(self.imageType_bestRadioButton, 1)
+ self.imageTypeButtonGroup.addButton(self.imageType_best30RadioButton, 2)
+
+ self.imageFormatButtonGroup = QButtonGroup(self)
+ self.imageFormatButtonGroup.addButton(self.imageFormat_jpgRadioButton, 0)
+ self.imageFormatButtonGroup.addButton(self.imageFormat_pngRadioButton, 1)
+
+ self.imageTypeButtonGroup.idToggled.connect(self.fillImageVersionComboBox)
+ self.fillImageVersionComboBox()
+
+ def setHelperPaths(self):
+ if selectedFiles := self.andrealFolderSelector.selectedFiles():
+ self.andrealHelper.andrealFolder = selectedFiles[0]
+ if selectedFiles := self.andrealExecutableSelector.selectedFiles():
+ self.andrealHelper.andrealExecutable = selectedFiles[0]
+
+ def imageFormat(self):
+ buttonId = self.imageFormatButtonGroup.checkedId()
+ return ["jpg", "png"][buttonId] if buttonId > -1 else None
+
+ def imageType(self):
+ buttonId = self.imageTypeButtonGroup.checkedId()
+ return ["info", "best", "best30"][buttonId] if buttonId > -1 else None
+
+ def fillImageVersionComboBox(self):
+ imageType = self.imageType()
+ if not imageType:
+ return
+
+ self.imageVersionComboBox.clear()
+ if imageType in ["info", "best"]:
+ self.imageVersionComboBox.addItem("3", 3)
+ self.imageVersionComboBox.addItem("2", 2)
+ self.imageVersionComboBox.addItem("1", 1)
+ elif imageType == "best30":
+ self.imageVersionComboBox.addItem("2", 2)
+ self.imageVersionComboBox.addItem("1", 1)
+
+ def imageVersion(self):
+ return self.imageVersionComboBox.currentData()
+
+ def requestComplete(self) -> bool:
+ if not self.imageType():
+ return False
+
+ imageType = self.imageType()
+ if imageType == "best" and not self.infoChart:
+ return False
+
+ return self.imageVersion() is not None
+
+ def getAndrealArguments(self, jsonFile: str, *, preview: bool = False):
+ if not self.requestComplete():
+ return
+
+ arguments = [
+ str(self.imageType()),
+ f'--json-file="{jsonFile}"',
+ f"--img-version={self.imageVersion()}",
+ ]
+ if self.andrealFolderSelector.selectedFiles():
+ arguments.append(
+ f'--path="{self.andrealFolderSelector.selectedFiles()[0]}"'
+ )
+ if preview:
+ arguments.extend(["--img-format=jpg", "--img-quality=20"])
+ else:
+ arguments.append(f"--img-format={self.imageFormat()}")
+ if self.imageFormat() == "jpg":
+ arguments.append(f"--img-quality={self.jpgQualitySpinBox.value()}")
+ return arguments
+
+ def getAndrealJsonContent(self):
+ if not self.requestComplete():
+ return None
+
+ imageType = self.imageType()
+ if imageType == "best" and not self.infoChart:
+ return
+
+ jsonContentDict = {}
+ with self.db.sessionmaker() as session:
+ converter = AndrealImageGeneratorApiDataConverter(session)
+ if imageType == "info":
+ jsonContentDict = converter.user_info()
+ elif imageType == "best":
+ jsonContentDict = converter.user_best(
+ self.infoChart.song_id, self.infoChart.rating_class
+ )
+ elif imageType == "best30":
+ jsonContentDict = converter.user_best30()
+ return json.dumps(jsonContentDict, ensure_ascii=False)
+
+ def getAndrealJsonFileName(self):
+ if not self.requestComplete():
+ return None
+
+ imageType = self.imageType()
+ timestamp = int(time.time() * 1000)
+
+ fileNameParts = ["andreal", imageType]
+ if imageType == "best":
+ fileNameParts.extend(
+ [
+ self.infoChart.song_id,
+ rating_class_to_short_text(self.infoChart.rating_class).lower(),
+ ]
+ )
+ fileNameParts.append(timestamp)
+ fileNameParts = [str(i) for i in fileNameParts]
+ fileName = "-".join(fileNameParts)
+ return f"{fileName}.json"
+
+ def getTempAndrealJsonPath(self):
+ if fileName := self.getAndrealJsonFileName():
+ return QDir.temp().filePath(fileName)
+ else:
+ return None
+
+ @Slot()
+ def on_exportJsonButton_clicked(self):
+ content = self.getAndrealJsonContent()
+ fileName = self.getAndrealJsonFileName()
+ if not content or not fileName:
+ return
+
+ saveFileName, _ = QFileDialog.getSaveFileName(self, None, fileName)
+ if not saveFileName:
+ return
+ with open(saveFileName, "w", encoding="utf-8") as jf:
+ jf.write(content)
+
+ def requestGenerate(self):
+ jsonPath = self.getTempAndrealJsonPath()
+ if not jsonPath:
+ return
+
+ self.generateImageButton.setEnabled(False)
+ self.generateJsonPath = jsonPath
+ self.generateImageFormat = self.imageFormat()
+ with open(jsonPath, "w", encoding="utf-8") as jf:
+ jf.write(self.getAndrealJsonContent())
+
+ self.andrealHelper.request(jsonPath, self.getAndrealArguments(jsonPath))
+
+ def generateFinished(self):
+ self.generateImageButton.setEnabled(True)
+
+ def generateError(self, jsonPath: str, errorMsg: str):
+ if jsonPath != self.generateJsonPath:
+ return
+ QMessageBox.critical(self, "Generate Error", errorMsg)
+
+ def generateReady(self, jsonPath: str, imageBytes: bytes):
+ if jsonPath != self.generateJsonPath:
+ return
+
+ if not imageBytes:
+ QMessageBox.critical(self, "Generate Error", "Empty bytes received.")
+ return
+
+ qImage = QImage.fromData(imageBytes)
+
+ filePathParts = jsonPath.split(".")
+ filePathParts[-1] = self.generateImageFormat
+ filePath = ".".join(filePathParts)
+ fileName = QFileInfo(filePath).fileName()
+
+ saveFileName, _ = QFileDialog.getSaveFileName(self, None, fileName)
+ if not saveFileName:
+ return
+ qImage.save(saveFileName, self.generateImageFormat)