extapps 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,260 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ ################################################################################
4
+ ## Form generated from reading UI file 'map_packer.ui'
5
+ ##
6
+ ## Created by: Qt User Interface Compiler version 6.10.1
7
+ ##
8
+ ## WARNING! All changes made in this file will be lost when recompiling UI file!
9
+ ################################################################################
10
+
11
+ from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale,
12
+ QMetaObject, QObject, QPoint, QRect,
13
+ QSize, QTime, QUrl, Qt)
14
+ from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor,
15
+ QFont, QFontDatabase, QGradient, QIcon,
16
+ QImage, QKeySequence, QLinearGradient, QPainter,
17
+ QPalette, QPixmap, QRadialGradient, QTransform)
18
+ from PySide6.QtWidgets import (QApplication, QComboBox, QGridLayout, QGroupBox,
19
+ QLabel, QLayout, QMainWindow, QPushButton,
20
+ QSizePolicy, QSpacerItem, QTabWidget, QVBoxLayout,
21
+ QWidget)
22
+
23
+ from uitk.widgets.comboBox.ComboBox import ComboBox
24
+ from uitk.widgets.footer import Footer
25
+ from uitk.widgets.header import Header
26
+ from uitk.widgets.lineEdit import LineEdit
27
+
28
+ class Ui_QtUi(object):
29
+ def setupUi(self, QtUi):
30
+ if not QtUi.objectName():
31
+ QtUi.setObjectName(u"QtUi")
32
+ QtUi.setEnabled(True)
33
+ QtUi.resize(200, 299)
34
+ QtUi.setTabShape(QTabWidget.Triangular)
35
+ QtUi.setDockNestingEnabled(True)
36
+ QtUi.setDockOptions(QMainWindow.AllowNestedDocks|QMainWindow.AllowTabbedDocks|QMainWindow.AnimatedDocks|QMainWindow.ForceTabbedDocks)
37
+ self.central_widget = QWidget(QtUi)
38
+ self.central_widget.setObjectName(u"central_widget")
39
+ self.central_widget.setMinimumSize(QSize(200, 0))
40
+ self.verticalLayout = QVBoxLayout(self.central_widget)
41
+ self.verticalLayout.setSpacing(2)
42
+ self.verticalLayout.setObjectName(u"verticalLayout")
43
+ self.verticalLayout.setContentsMargins(2, 2, 2, 2)
44
+ self.main_layout = QVBoxLayout()
45
+ self.main_layout.setSpacing(6)
46
+ self.main_layout.setObjectName(u"main_layout")
47
+ self.main_layout.setSizeConstraint(QLayout.SetFixedSize)
48
+ self.header = Header(self.central_widget)
49
+ self.header.setObjectName(u"header")
50
+ sizePolicy = QSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Fixed)
51
+ sizePolicy.setHorizontalStretch(0)
52
+ sizePolicy.setVerticalStretch(0)
53
+ sizePolicy.setHeightForWidth(self.header.sizePolicy().hasHeightForWidth())
54
+ self.header.setSizePolicy(sizePolicy)
55
+ self.header.setMinimumSize(QSize(0, 19))
56
+ self.header.setMaximumSize(QSize(999, 19))
57
+ font = QFont()
58
+ font.setBold(True)
59
+ self.header.setFont(font)
60
+
61
+ self.main_layout.addWidget(self.header)
62
+
63
+ self.Formatting = QGroupBox(self.central_widget)
64
+ self.Formatting.setObjectName(u"Formatting")
65
+ self.gridLayout = QGridLayout(self.Formatting)
66
+ self.gridLayout.setObjectName(u"gridLayout")
67
+ self.gridLayout.setContentsMargins(0, 0, 0, 0)
68
+ self.cmbB = ComboBox(self.Formatting)
69
+ self.cmbB.setObjectName(u"cmbB")
70
+ sizePolicy.setHeightForWidth(self.cmbB.sizePolicy().hasHeightForWidth())
71
+ self.cmbB.setSizePolicy(sizePolicy)
72
+ self.cmbB.setMinimumSize(QSize(0, 19))
73
+ self.cmbB.setMaximumSize(QSize(16777215, 19))
74
+ self.cmbB.setMaxVisibleItems(30)
75
+ self.cmbB.setSizeAdjustPolicy(QComboBox.AdjustToContentsOnFirstShow)
76
+ self.cmbB.setFrame(False)
77
+
78
+ self.gridLayout.addWidget(self.cmbB, 2, 1, 1, 1)
79
+
80
+ self.cmbR = ComboBox(self.Formatting)
81
+ self.cmbR.setObjectName(u"cmbR")
82
+ sizePolicy.setHeightForWidth(self.cmbR.sizePolicy().hasHeightForWidth())
83
+ self.cmbR.setSizePolicy(sizePolicy)
84
+ self.cmbR.setMinimumSize(QSize(0, 19))
85
+ self.cmbR.setMaximumSize(QSize(16777215, 19))
86
+ self.cmbR.setMaxVisibleItems(30)
87
+ self.cmbR.setSizeAdjustPolicy(QComboBox.AdjustToContentsOnFirstShow)
88
+ self.cmbR.setFrame(False)
89
+
90
+ self.gridLayout.addWidget(self.cmbR, 0, 1, 1, 1)
91
+
92
+ self.cmbA = ComboBox(self.Formatting)
93
+ self.cmbA.setObjectName(u"cmbA")
94
+ sizePolicy.setHeightForWidth(self.cmbA.sizePolicy().hasHeightForWidth())
95
+ self.cmbA.setSizePolicy(sizePolicy)
96
+ self.cmbA.setMinimumSize(QSize(0, 19))
97
+ self.cmbA.setMaximumSize(QSize(16777215, 19))
98
+ self.cmbA.setMaxVisibleItems(30)
99
+ self.cmbA.setSizeAdjustPolicy(QComboBox.AdjustToContentsOnFirstShow)
100
+ self.cmbA.setFrame(False)
101
+
102
+ self.gridLayout.addWidget(self.cmbA, 3, 1, 1, 1)
103
+
104
+ self.cmbG = ComboBox(self.Formatting)
105
+ self.cmbG.setObjectName(u"cmbG")
106
+ sizePolicy.setHeightForWidth(self.cmbG.sizePolicy().hasHeightForWidth())
107
+ self.cmbG.setSizePolicy(sizePolicy)
108
+ self.cmbG.setMinimumSize(QSize(0, 19))
109
+ self.cmbG.setMaximumSize(QSize(16777215, 19))
110
+ self.cmbG.setMaxVisibleItems(30)
111
+ self.cmbG.setSizeAdjustPolicy(QComboBox.AdjustToContentsOnFirstShow)
112
+ self.cmbG.setFrame(False)
113
+
114
+ self.gridLayout.addWidget(self.cmbG, 1, 1, 1, 1)
115
+
116
+ self.lblR = QLabel(self.Formatting)
117
+ self.lblR.setObjectName(u"lblR")
118
+ self.lblR.setMaximumSize(QSize(18, 16777215))
119
+ self.lblR.setFont(font)
120
+
121
+ self.gridLayout.addWidget(self.lblR, 0, 0, 1, 1)
122
+
123
+ self.lblG = QLabel(self.Formatting)
124
+ self.lblG.setObjectName(u"lblG")
125
+ self.lblG.setMaximumSize(QSize(18, 16777215))
126
+ self.lblG.setFont(font)
127
+
128
+ self.gridLayout.addWidget(self.lblG, 1, 0, 1, 1)
129
+
130
+ self.lblB = QLabel(self.Formatting)
131
+ self.lblB.setObjectName(u"lblB")
132
+ self.lblB.setMaximumSize(QSize(18, 16777215))
133
+ self.lblB.setFont(font)
134
+
135
+ self.gridLayout.addWidget(self.lblB, 2, 0, 1, 1)
136
+
137
+ self.lblA = QLabel(self.Formatting)
138
+ self.lblA.setObjectName(u"lblA")
139
+ self.lblA.setMaximumSize(QSize(18, 16777215))
140
+ self.lblA.setFont(font)
141
+
142
+ self.gridLayout.addWidget(self.lblA, 3, 0, 1, 1)
143
+
144
+
145
+ self.main_layout.addWidget(self.Formatting)
146
+
147
+ self.groupBox = QGroupBox(self.central_widget)
148
+ self.groupBox.setObjectName(u"groupBox")
149
+ self.verticalLayout_2 = QVBoxLayout(self.groupBox)
150
+ self.verticalLayout_2.setSpacing(1)
151
+ self.verticalLayout_2.setObjectName(u"verticalLayout_2")
152
+ self.verticalLayout_2.setContentsMargins(0, 0, 0, 0)
153
+ self.cmbFormat = ComboBox(self.groupBox)
154
+ self.cmbFormat.setObjectName(u"cmbFormat")
155
+ sizePolicy.setHeightForWidth(self.cmbFormat.sizePolicy().hasHeightForWidth())
156
+ self.cmbFormat.setSizePolicy(sizePolicy)
157
+ self.cmbFormat.setMinimumSize(QSize(0, 19))
158
+ self.cmbFormat.setMaximumSize(QSize(16777215, 19))
159
+ self.cmbFormat.setMaxVisibleItems(30)
160
+ self.cmbFormat.setSizeAdjustPolicy(QComboBox.AdjustToContentsOnFirstShow)
161
+ self.cmbFormat.setFrame(False)
162
+
163
+ self.verticalLayout_2.addWidget(self.cmbFormat)
164
+
165
+ self.txtSuffix = LineEdit(self.groupBox)
166
+ self.txtSuffix.setObjectName(u"txtSuffix")
167
+ sizePolicy.setHeightForWidth(self.txtSuffix.sizePolicy().hasHeightForWidth())
168
+ self.txtSuffix.setSizePolicy(sizePolicy)
169
+ self.txtSuffix.setMinimumSize(QSize(0, 19))
170
+ self.txtSuffix.setMaximumSize(QSize(16777215, 19))
171
+ self.txtSuffix.setFrame(False)
172
+
173
+ self.verticalLayout_2.addWidget(self.txtSuffix)
174
+
175
+
176
+ self.main_layout.addWidget(self.groupBox)
177
+
178
+ self.verticalLayout_3 = QVBoxLayout()
179
+ self.verticalLayout_3.setSpacing(0)
180
+ self.verticalLayout_3.setObjectName(u"verticalLayout_3")
181
+ self.b001 = QPushButton(self.central_widget)
182
+ self.b001.setObjectName(u"b001")
183
+ self.b001.setEnabled(False)
184
+ sizePolicy.setHeightForWidth(self.b001.sizePolicy().hasHeightForWidth())
185
+ self.b001.setSizePolicy(sizePolicy)
186
+ self.b001.setMinimumSize(QSize(0, 19))
187
+ self.b001.setMaximumSize(QSize(16777215, 19))
188
+
189
+ self.verticalLayout_3.addWidget(self.b001)
190
+
191
+ self.b000 = QPushButton(self.central_widget)
192
+ self.b000.setObjectName(u"b000")
193
+ self.b000.setEnabled(True)
194
+ self.b000.setMinimumSize(QSize(0, 30))
195
+ self.b000.setMaximumSize(QSize(16777215, 19))
196
+
197
+ self.verticalLayout_3.addWidget(self.b000)
198
+
199
+
200
+ self.main_layout.addLayout(self.verticalLayout_3)
201
+
202
+ self.verticalSpacer = QSpacerItem(0, 10, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding)
203
+
204
+ self.main_layout.addItem(self.verticalSpacer)
205
+
206
+ self.footer = Footer(self.central_widget)
207
+ self.footer.setObjectName(u"footer")
208
+ self.footer.setMinimumSize(QSize(0, 19))
209
+ self.footer.setMaximumSize(QSize(16777215, 19))
210
+
211
+ self.main_layout.addWidget(self.footer)
212
+
213
+
214
+ self.verticalLayout.addLayout(self.main_layout)
215
+
216
+ QtUi.setCentralWidget(self.central_widget)
217
+
218
+ self.retranslateUi(QtUi)
219
+
220
+ QMetaObject.connectSlotsByName(QtUi)
221
+ # setupUi
222
+
223
+ def retranslateUi(self, QtUi):
224
+ self.header.setText(QCoreApplication.translate("QtUi", u"MAP PACKER", None))
225
+ self.Formatting.setTitle(QCoreApplication.translate("QtUi", u"Channels", None))
226
+ #if QT_CONFIG(tooltip)
227
+ self.cmbB.setToolTip(QCoreApplication.translate("QtUi", u"<html><head/><body><p>Specify the map to pack to the <span style=\" font-weight:600;\">Blue Channel</span>.</p></body></html>", None))
228
+ #endif // QT_CONFIG(tooltip)
229
+ #if QT_CONFIG(tooltip)
230
+ self.cmbR.setToolTip(QCoreApplication.translate("QtUi", u"<html><head/><body><p>Specify the map to pack to the <span style=\" font-weight:600;\">Red Channel</span>.</p></body></html>", None))
231
+ #endif // QT_CONFIG(tooltip)
232
+ #if QT_CONFIG(tooltip)
233
+ self.cmbA.setToolTip(QCoreApplication.translate("QtUi", u"<html><head/><body><p>Specify the map to pack to the <span style=\" font-weight:600;\">Alpha Channel</span>.</p></body></html>", None))
234
+ #endif // QT_CONFIG(tooltip)
235
+ #if QT_CONFIG(tooltip)
236
+ self.cmbG.setToolTip(QCoreApplication.translate("QtUi", u"<html><head/><body><p>Specify the map to pack to the <span style=\" font-weight:600;\">Green Channel</span>.</p></body></html>", None))
237
+ #endif // QT_CONFIG(tooltip)
238
+ self.lblR.setText(QCoreApplication.translate("QtUi", u"R", None))
239
+ self.lblG.setText(QCoreApplication.translate("QtUi", u"G", None))
240
+ self.lblB.setText(QCoreApplication.translate("QtUi", u"B", None))
241
+ self.lblA.setText(QCoreApplication.translate("QtUi", u"A", None))
242
+ self.groupBox.setTitle(QCoreApplication.translate("QtUi", u"Format", None))
243
+ #if QT_CONFIG(tooltip)
244
+ self.cmbFormat.setToolTip(QCoreApplication.translate("QtUi", u"<html><head/><body><p>Specify the map <span style=\" font-weight:600;\">output file format</span>.</p></body></html>", None))
245
+ #endif // QT_CONFIG(tooltip)
246
+ #if QT_CONFIG(tooltip)
247
+ self.txtSuffix.setToolTip(QCoreApplication.translate("QtUi", u"<html><head/><body><p>Specify a suffix for the packed texture's file name.</p></body></html>", None))
248
+ #endif // QT_CONFIG(tooltip)
249
+ self.txtSuffix.setPlaceholderText(QCoreApplication.translate("QtUi", u"Suffix:", None))
250
+ #if QT_CONFIG(tooltip)
251
+ self.b001.setToolTip(QCoreApplication.translate("QtUi", u"Choose the maps to pack and perform the operation.", None))
252
+ #endif // QT_CONFIG(tooltip)
253
+ self.b001.setText(QCoreApplication.translate("QtUi", u"Open Output Dir", None))
254
+ #if QT_CONFIG(tooltip)
255
+ self.b000.setToolTip(QCoreApplication.translate("QtUi", u"Choose the maps to pack and perform the operation.", None))
256
+ #endif // QT_CONFIG(tooltip)
257
+ self.b000.setText(QCoreApplication.translate("QtUi", u"Pack", None))
258
+ pass
259
+ # retranslateUi
260
+
@@ -0,0 +1,250 @@
1
+ # !/usr/bin/python
2
+ # coding=utf-8
3
+ from typing import List, Dict, Optional, Any
4
+ from pythontk.img_utils._img_utils import ImgUtils
5
+ from pythontk.img_utils.map_factory import MapFactory
6
+ from pythontk.file_utils._file_utils import FileUtils
7
+
8
+
9
+ class MapPackerSlots(ImgUtils):
10
+ channels = ["R", "G", "B", "A"]
11
+ grayscale_types = [
12
+ "None",
13
+ "Metallic",
14
+ "Roughness",
15
+ "Ambient_Occlusion",
16
+ "Smoothness",
17
+ "Opacity",
18
+ "Height",
19
+ "Thickness",
20
+ "Glossiness",
21
+ "Displacement",
22
+ ]
23
+ output_formats = ["PNG", "TGA", "JPG", "BMP", "TIFF", "EXR"]
24
+
25
+ PRESET_DIR = "~/.pythontk/presets/map_packer"
26
+
27
+ # Built-in presets defined using human-readable names.
28
+ # Resolved to combo indices at seed time via grayscale_types/output_formats.
29
+ BUILTIN_PRESETS = {
30
+ "ORM (Unreal, glTF)": {
31
+ "R": "Ambient_Occlusion",
32
+ "G": "Roughness",
33
+ "B": "Metallic",
34
+ "A": "None",
35
+ "format": "PNG",
36
+ "suffix": "_ORM",
37
+ },
38
+ "MSAO (HDRP Mask Map)": {
39
+ "R": "Metallic",
40
+ "G": "Ambient_Occlusion",
41
+ "B": "None",
42
+ "A": "Smoothness",
43
+ "format": "PNG",
44
+ "suffix": "_MSAO",
45
+ },
46
+ "Metallic Smoothness (URP)": {
47
+ "R": "Metallic",
48
+ "G": "None",
49
+ "B": "None",
50
+ "A": "Smoothness",
51
+ "format": "PNG",
52
+ "suffix": "_MetallicSmoothness",
53
+ },
54
+ }
55
+
56
+ def __init__(self, switchboard, **kwargs):
57
+ super().__init__()
58
+
59
+ self.sb = switchboard
60
+ self.ui = self.sb.loaded_ui.map_packer
61
+
62
+ self._source_dir = kwargs.get("source_dir", "")
63
+
64
+ self._init_ui_comboboxes()
65
+ self._set_channel_label_colors()
66
+ self.ui.b001.setEnabled(False) # Disable the open output dir button
67
+
68
+ def _set_channel_label_colors(self):
69
+ """Set background color for each channel label."""
70
+ channel_colors = {
71
+ "R": "#ef9a9a", # Pastel Red
72
+ "G": "#a5d6a7", # Pastel Green
73
+ "B": "#90caf9", # Pastel Blue
74
+ "A": "#bdbdbd", # Pastel Gray
75
+ }
76
+ for c in self.channels:
77
+ lbl = getattr(self.ui, f"lbl{c}", None)
78
+ if lbl:
79
+ lbl.setStyleSheet(
80
+ f"background-color: {channel_colors[c]}; color: white; border-radius: 3px;"
81
+ )
82
+
83
+ def _init_ui_comboboxes(self):
84
+ """Initialize channel and format comboboxes and connect format change signal."""
85
+ for c in self.channels:
86
+ cmb = getattr(self.ui, f"cmb{c}")
87
+ cmb.clear()
88
+ cmb.addItems(self.grayscale_types)
89
+ cmb.restore_state = True # <-- Enable state restore
90
+
91
+ self.ui.cmbFormat.clear()
92
+ self.ui.cmbFormat.addItems(self.output_formats)
93
+ self.ui.cmbFormat.restore_state = True # <-- Enable state restore
94
+ self.ui.cmbFormat.currentTextChanged.connect(self._on_format_changed)
95
+ self._on_format_changed(self.ui.cmbFormat.currentText())
96
+
97
+ def _on_format_changed(self, fmt: str):
98
+ """Disable alpha combobox for formats without alpha support."""
99
+ supports_alpha = fmt.upper() in {"PNG", "TGA", "TIFF", "EXR", "BMP"}
100
+ self.ui.cmbA.setEnabled(supports_alpha)
101
+ if not supports_alpha:
102
+ self.ui.cmbA.setCurrentIndex(self.ui.cmbA.findText("None"))
103
+
104
+ def header_init(self, widget):
105
+ """Configure the header menu with presets for common packed map types."""
106
+ presets = widget.menu.presets
107
+ presets.preset_dir = self.PRESET_DIR
108
+ # Seed built-in presets BEFORE setup so the combo is populated on first launch.
109
+ self._seed_builtin_presets(presets)
110
+ presets.setup(
111
+ preset_dir=self.PRESET_DIR,
112
+ widgets=[
113
+ self.ui.cmbR,
114
+ self.ui.cmbG,
115
+ self.ui.cmbB,
116
+ self.ui.cmbA,
117
+ self.ui.cmbFormat,
118
+ self.ui.txtSuffix,
119
+ ],
120
+ )
121
+
122
+ def _seed_builtin_presets(self, preset_mgr):
123
+ """Write built-in preset JSON files if they don't already exist.
124
+
125
+ Converts human-readable BUILTIN_PRESETS (map-type/format names)
126
+ into the index-based JSON format that PresetManager expects.
127
+ """
128
+ import json
129
+
130
+ preset_dir = preset_mgr.preset_dir
131
+ for name, preset in self.BUILTIN_PRESETS.items():
132
+ filepath = preset_mgr._preset_path(name)
133
+ if not filepath.exists():
134
+ data = {
135
+ "_meta": {"version": 1},
136
+ "cmbR": self.grayscale_types.index(preset["R"]),
137
+ "cmbG": self.grayscale_types.index(preset["G"]),
138
+ "cmbB": self.grayscale_types.index(preset["B"]),
139
+ "cmbA": self.grayscale_types.index(preset["A"]),
140
+ "cmbFormat": self.output_formats.index(preset["format"]),
141
+ "txtSuffix": preset["suffix"],
142
+ }
143
+ with open(filepath, "w", encoding="utf-8") as f:
144
+ json.dump(data, f, indent=4)
145
+
146
+ @property
147
+ def source_dir(self):
148
+ return self._source_dir
149
+
150
+ @source_dir.setter
151
+ def source_dir(self, value):
152
+ self._source_dir = value
153
+
154
+ def b000(self):
155
+ """Batch pack up to 4 channels into RGBA maps across texture sets."""
156
+ file_paths = self.sb.file_dialog(
157
+ file_types=[f"*.{ext}" for ext in self.texture_file_types],
158
+ title="Select textures for batch packing (multiple sets allowed):",
159
+ start_dir=self.source_dir,
160
+ allow_multiple=True,
161
+ )
162
+ if not file_paths:
163
+ print("No files selected.")
164
+ self.ui.b001.setEnabled(False)
165
+ return
166
+
167
+ texture_sets = MapFactory.group_textures_by_set(file_paths)
168
+ combos = [
169
+ self.ui.cmbR.currentText(),
170
+ self.ui.cmbG.currentText(),
171
+ self.ui.cmbB.currentText(),
172
+ self.ui.cmbA.currentText(),
173
+ ]
174
+ suffix = self.ui.txtSuffix.text().strip() or "_Packed"
175
+ if not suffix.startswith("_"):
176
+ suffix = f"_{suffix}"
177
+ ext = self.ui.cmbFormat.currentText().lower()
178
+ fmt = self.ui.cmbFormat.currentText().upper()
179
+
180
+ success = False
181
+ for base_name, files in texture_sets.items():
182
+ sorted_maps = MapFactory.sort_images_by_type(files)
183
+ assigned = {c: None for c in self.channels}
184
+ available_map_types = {MapFactory.resolve_map_type(f): f for f in files}
185
+ used_files = set()
186
+
187
+ for idx, channel in enumerate(self.channels):
188
+ map_type = combos[idx]
189
+ if map_type == "None":
190
+ continue
191
+ file = next(
192
+ (
193
+ f
194
+ for f in files
195
+ if MapFactory.resolve_map_type(f) == map_type
196
+ and f not in used_files
197
+ ),
198
+ None,
199
+ )
200
+ if file:
201
+ assigned[channel] = file
202
+ used_files.add(file)
203
+ continue
204
+ # Try conversion if not found
205
+ converted = self.get_converted_map(map_type, available_map_types)
206
+ if converted is not None:
207
+ assigned[channel] = converted
208
+ continue
209
+ print(
210
+ f"// Required map '{map_type}' for channel {channel} in '{base_name}' not found and cannot be converted. Skipping."
211
+ )
212
+ break # skip this set if not all required maps are present
213
+
214
+ if any(assigned[c] for c in self.channels):
215
+ out_mode = "RGBA" if assigned["A"] else "RGB"
216
+ output_dir = FileUtils.format_path(files[0], "path")
217
+ output_path = f"{output_dir}/{base_name}{suffix}.{ext}"
218
+ self.pack_channels(
219
+ channel_files=assigned,
220
+ output_path=output_path,
221
+ out_mode=out_mode,
222
+ output_format=fmt,
223
+ )
224
+ print(f"// Packed map saved: {output_path}")
225
+ success = True
226
+
227
+ if success:
228
+ self._last_output_dir = FileUtils.format_path(file_paths[0], "path")
229
+ self.source_dir = self._last_output_dir
230
+ self.ui.b001.setEnabled(True)
231
+ else:
232
+ self.ui.b001.setEnabled(False)
233
+
234
+ def b001(self):
235
+ """Open the last output directory in the system file explorer."""
236
+ import os
237
+ import sys
238
+
239
+ output_dir = getattr(self, "_last_output_dir", None)
240
+ if not output_dir or not os.path.isdir(output_dir):
241
+ print("// No output directory available.")
242
+ return
243
+ if sys.platform.startswith("darwin"):
244
+ os.system(f'open "{output_dir}"')
245
+ elif sys.platform.startswith("win"):
246
+ os.startfile(output_dir)
247
+ elif sys.platform.startswith("linux"):
248
+ os.system(f'xdg-open "{output_dir}"')
249
+ else:
250
+ print("// Unsupported OS for opening directories.")
@@ -0,0 +1,22 @@
1
+ # !/usr/bin/python
2
+ # coding=utf-8
3
+ """Mesh Convert — FBX → glTF / GLB conversion.
4
+
5
+ Engine logic lives in :mod:`pythontk.file_utils.mesh_convert`; this
6
+ package holds only the Switchboard panel and launcher.
7
+ """
8
+ from pythontk.core_utils.module_resolver import bootstrap_package
9
+
10
+ __package__ = "extapps.mesh_convert"
11
+
12
+
13
+ DEFAULT_INCLUDE = {
14
+ "launcher": ["MeshConvertUI"],
15
+ "slots": ["MeshConvertSlots"],
16
+ }
17
+
18
+
19
+ bootstrap_package(globals(), include=DEFAULT_INCLUDE)
20
+
21
+
22
+ __all__ = ["MeshConvertUI", "MeshConvertSlots"]
@@ -0,0 +1,29 @@
1
+ # !/usr/bin/python
2
+ # coding=utf-8
3
+ """Application shell for the Mesh Convert UI.
4
+
5
+ Engine logic lives in :mod:`pythontk.file_utils.mesh_convert` and slot
6
+ bindings in :mod:`extapps.mesh_convert.slots`; this module only assembles
7
+ the Switchboard-driven UI and provides the script entry point.
8
+ """
9
+
10
+
11
+ class MeshConvertUI:
12
+ def __new__(cls):
13
+ from uitk import Switchboard
14
+ from extapps.mesh_convert.slots import MeshConvertSlots
15
+
16
+ sb = Switchboard(ui_source="mesh_convert.ui", slot_source=MeshConvertSlots)
17
+ ui = sb.loaded_ui.mesh_convert
18
+
19
+ ui.set_attributes(WA_TranslucentBackground=True)
20
+ ui.set_flags(FramelessWindowHint=True)
21
+ ui.style.set(theme="dark", style_class="translucentBgWithBorder")
22
+ ui.header.config_buttons("menu", "minimize", "hide")
23
+ return ui
24
+
25
+
26
+ # -----------------------------------------------------------------------------
27
+
28
+ if __name__ == "__main__":
29
+ MeshConvertUI().show(pos="screen", app_exec=True)