You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
232 lines
18 KiB
Python
232 lines
18 KiB
Python
import os
|
|
import json
|
|
from pymxs import runtime as rt
|
|
try: from PySide6 import QtWidgets, QtCore, QtGui
|
|
except ImportError: from PySide2 import QtWidgets, QtCore, QtGui
|
|
|
|
import vr4life_engine as eng
|
|
import vr4life_cloud as cld
|
|
import importlib
|
|
importlib.reload(eng)
|
|
importlib.reload(cld)
|
|
|
|
def get_max_window_safe():
|
|
try:
|
|
if hasattr(rt, 'getMAXWindow'): return rt.getMAXWindow()
|
|
except: pass
|
|
for w in QtWidgets.QApplication.topLevelWidgets():
|
|
if w.inherits("QMainWindow") or w.windowTitle().startswith("Autodesk 3ds Max"): return w
|
|
return None
|
|
|
|
class AutoBakeManager(QtWidgets.QDialog):
|
|
def __init__(self):
|
|
super(AutoBakeManager, self).__init__(get_max_window_safe())
|
|
self.setWindowTitle("VR4LIFE AUTO-BAKE V312 - MODULAR ENTERPRISE")
|
|
self.setWindowFlags(self.windowFlags() | QtCore.Qt.WindowMinimizeButtonHint | QtCore.Qt.WindowMaximizeButtonHint | QtCore.Qt.WindowCloseButtonHint)
|
|
self.resize(570, 890); self.bake_items = []; self._is_cancelled = False
|
|
p = self.palette(); p.setColor(QtGui.QPalette.Window, QtGui.QColor(43, 43, 43)); self.setPalette(p); self.setAutoFillBackground(True)
|
|
self.init_ui()
|
|
eng.load_bake_elements(self)
|
|
|
|
def init_ui(self):
|
|
layout = QtWidgets.QVBoxLayout(self)
|
|
# --- TOP HEADER ---
|
|
lbl_t = QtWidgets.QLabel("VR4LIFE - V312 (Modular) ( ultima alteração 2:56 AM 11-03- 2026)"); lbl_t.setAlignment(QtCore.Qt.AlignCenter); lbl_t.setStyleSheet("font-size: 24px; font-weight: bold; color: #00FF00;")
|
|
layout.addWidget(lbl_t)
|
|
|
|
self.tabs = QtWidgets.QTabWidget()
|
|
self.tabs.setStyleSheet("QTabWidget::pane { border: 1px solid #777; background: #333; } QTabBar::tab { background: #444; color: #CCC; padding: 10px 15px; font-weight: bold; border: 1px solid #222; } QTabBar::tab:selected { background: #555; color: #00FF00; } QLabel { color: #FFF; font-weight: bold; }")
|
|
|
|
# ABA 1
|
|
t_gen = QtWidgets.QWidget(); l_gen = QtWidgets.QVBoxLayout(t_gen); f_gen = QtWidgets.QFormLayout()
|
|
self.btn_ref = QtWidgets.QPushButton("🔄 RECARREGAR MAPAS"); self.btn_ref.setStyleSheet("background: #555; color: white; height: 35px;"); self.btn_ref.clicked.connect(lambda: eng.load_bake_elements(self))
|
|
self.cmb_bake_elem = QtWidgets.QComboBox(); self.cmb_bake_elem.setStyleSheet("background: white; color: black; font-weight: bold;")
|
|
self.chk_denoise = QtWidgets.QCheckBox("✨ Ativar Denoiser IA"); self.chk_denoise.setStyleSheet("color: #00FFFF; font-weight: bold;"); self.chk_denoise.setChecked(True)
|
|
self.spn_light_boost = QtWidgets.QSpinBox(); self.spn_light_boost.setStyleSheet("background: white; color: black; font-weight: bold;"); self.spn_light_boost.setRange(0, 50); self.spn_light_boost.setSuffix("%"); self.spn_light_boost.setToolTip("Aumenta temporariamente a intensidade de todas as luzes da cena para o Bake.")
|
|
self.edt_p_bake = QtWidgets.QLineEdit(rt.maxFilePath + "_BAKE_JPG\\" if rt.maxFilePath else "C:\\VR4_BAKE\\"); self.edt_p_bake.setStyleSheet("background: white; color: black;")
|
|
self.edt_p_glb = QtWidgets.QLineEdit(rt.maxFilePath + "_EXPORT_GLB\\" if rt.maxFilePath else "C:\\VR4_GLB\\"); self.edt_p_glb.setStyleSheet("background: white; color: black;")
|
|
self.btn_brw_glb = QtWidgets.QPushButton("..."); self.btn_brw_glb.setStyleSheet("background: #777; color: white; font-weight: bold;"); self.btn_brw_glb.clicked.connect(self.browse_glb_folder)
|
|
h_glb = QtWidgets.QHBoxLayout(); h_glb.addWidget(self.edt_p_glb); h_glb.addWidget(self.btn_brw_glb)
|
|
self.rdo_uv1 = QtWidgets.QRadioButton("UV 1 (Substituir)"); self.rdo_uv1.setStyleSheet("color: white; font-weight: bold;")
|
|
self.rdo_uv2 = QtWidgets.QRadioButton("UV 2 (Multi-UV)"); self.rdo_uv2.setStyleSheet("color: white; font-weight: bold;"); self.rdo_uv2.setChecked(True)
|
|
h_uv = QtWidgets.QHBoxLayout(); h_uv.addWidget(self.rdo_uv1); h_uv.addWidget(self.rdo_uv2)
|
|
|
|
f_gen.addRow("Mapa:", self.cmb_bake_elem); f_gen.addRow("", self.chk_denoise); f_gen.addRow("Boost de Luz:", self.spn_light_boost)
|
|
f_gen.addRow("Destino UV:", h_uv)
|
|
f_gen.addRow("Pasta JPG:", self.edt_p_bake); f_gen.addRow("Pasta GLB/ZIP:", h_glb)
|
|
l_gen.addWidget(self.btn_ref); l_gen.addLayout(f_gen); l_gen.addStretch(); self.tabs.addTab(t_gen, "🗂️ 1. Geral")
|
|
|
|
# ABA 2
|
|
t_geo = QtWidgets.QWidget(); l_geo = QtWidgets.QVBoxLayout(t_geo); f_geo = QtWidgets.QFormLayout()
|
|
self.chk_a_weld = QtWidgets.QCheckBox("Fundir Grupos na opção 'AUTO'"); self.chk_a_weld.setStyleSheet("color: #00FF00; font-weight: bold;"); self.chk_a_weld.setChecked(True)
|
|
self.chk_a_super = QtWidgets.QCheckBox("Super Solda na opção 'AUTO'"); self.chk_a_super.setStyleSheet("color: #00FFFF; font-weight: bold;"); self.chk_a_super.setChecked(False)
|
|
self.spn_pct = QtWidgets.QDoubleSpinBox(); self.spn_pct.setStyleSheet("background: white; color: black;"); self.spn_pct.setRange(0.1, 100.0); self.spn_pct.setValue(90.0)
|
|
self.spn_min_poly = QtWidgets.QSpinBox(); self.spn_min_poly.setStyleSheet("background: white; color: black;"); self.spn_min_poly.setRange(50, 10000000); self.spn_min_poly.setValue(3000)
|
|
self.spn_max_sz = QtWidgets.QDoubleSpinBox(); self.spn_max_sz.setStyleSheet("background: white; color: black;"); self.spn_max_sz.setRange(10.0, 100000.0); self.spn_max_sz.setValue(800.0)
|
|
self.chk_a_slice = QtWidgets.QCheckBox("Incluir Multi-UV no 'AUTO'"); self.chk_a_slice.setStyleSheet("color: white; font-weight: bold;")
|
|
f_geo.addRow(QtWidgets.QLabel("🧩 SOLDADOR:")); f_geo.addRow("", self.chk_a_weld); f_geo.addRow("", self.chk_a_super)
|
|
f_geo.addRow(QtWidgets.QLabel("🛠️ OTIMIZADOR:")); f_geo.addRow("Vertex %:", self.spn_pct); f_geo.addRow("Meta Polys:", self.spn_min_poly); f_geo.addRow(QtWidgets.QLabel("✂️ MULTI-UV:")); f_geo.addRow("Criar IDs se >:", self.spn_max_sz); f_geo.addRow("", self.chk_a_slice)
|
|
l_geo.addLayout(f_geo); l_geo.addStretch(); self.tabs.addTab(t_geo, "📐 2. Geometria")
|
|
|
|
# ABA 3 e 4 mantêm-se iguais
|
|
t_tex = QtWidgets.QWidget(); l_tex = QtWidgets.QVBoxLayout(t_tex); f_tex = QtWidgets.QFormLayout()
|
|
self.spn_res = QtWidgets.QSpinBox(); self.spn_res.setStyleSheet("background: white; color: black;"); self.spn_res.setRange(128, 8192); self.spn_res.setValue(2048)
|
|
self.chk_a_res = QtWidgets.QCheckBox("Auto-Size"); self.chk_a_res.setStyleSheet("color: #FFD700; font-weight: bold;"); self.chk_a_res.setChecked(True)
|
|
self.s256 = QtWidgets.QDoubleSpinBox(); self.s256.setStyleSheet("background: white; color: black;"); self.s256.setRange(0.1, 100000.0); self.s256.setValue(100.0); self.s256.setPrefix("< 256px: ")
|
|
self.s512 = QtWidgets.QDoubleSpinBox(); self.s512.setStyleSheet("background: white; color: black;"); self.s512.setRange(0.1, 100000.0); self.s512.setValue(300.0); self.s512.setPrefix("< 512px: ")
|
|
self.s1024 = QtWidgets.QDoubleSpinBox(); self.s1024.setStyleSheet("background: white; color: black;"); self.s1024.setRange(0.1, 100000.0); self.s1024.setValue(600.0); self.s1024.setPrefix("< 1024px: ")
|
|
h_sz = QtWidgets.QHBoxLayout(); h_sz.addWidget(self.chk_a_res); h_sz.addWidget(self.s256); h_sz.addWidget(self.s512); h_sz.addWidget(self.s1024)
|
|
for w in [self.chk_a_res, self.s256, self.s512, self.s1024, self.spn_res]: w.valueChanged.connect(self.upd_res_col) if hasattr(w, 'valueChanged') else w.toggled.connect(self.upd_res_col)
|
|
f_tex.addRow("Res Máx (>):", self.spn_res); f_tex.addRow("Escala:", h_sz)
|
|
|
|
self.spn_passes = QtWidgets.QSpinBox(); self.spn_passes.setStyleSheet("background: white; color: black;"); self.spn_passes.setRange(1, 9999); self.spn_passes.setValue(5)
|
|
self.chk_native = QtWidgets.QCheckBox("Usar F10 Nativ."); self.chk_native.setStyleSheet("color: white; font-weight: bold;"); self.chk_native.setChecked(False)
|
|
self.btn_f10 = QtWidgets.QPushButton("⚙️ F10 Config"); self.btn_f10.setStyleSheet("background: #555; color: white;"); self.btn_f10.clicked.connect(lambda: rt.execute("max render options"))
|
|
h_cor = QtWidgets.QHBoxLayout(); h_cor.addWidget(self.spn_passes); h_cor.addWidget(self.chk_native); h_cor.addWidget(self.btn_f10)
|
|
f_tex.addRow("Corona Limit (Pass):", h_cor)
|
|
|
|
l_tex.addLayout(f_tex); l_tex.addStretch(); self.tabs.addTab(t_tex, "🎨 3. Textura")
|
|
|
|
t_cld = QtWidgets.QWidget(); l_cld = QtWidgets.QVBoxLayout(t_cld); f_cld = QtWidgets.QFormLayout()
|
|
self.e_hash = QtWidgets.QLineEdit(); self.e_hash.setStyleSheet("background: white; color: black; letter-spacing: 2px;"); self.e_hash.setEchoMode(QtWidgets.QLineEdit.Password)
|
|
self.b_conn = QtWidgets.QPushButton("🔄 Conectar CMS"); self.b_conn.setStyleSheet("background: #2E8B57; color: white; height: 30px;"); self.b_conn.clicked.connect(lambda: cld.mock_connect_api(self))
|
|
self.c_chn = QtWidgets.QComboBox(); self.c_chn.setStyleSheet("background: #CCC; color: black;"); self.c_chn.setEnabled(False)
|
|
self.c_pst = QtWidgets.QComboBox(); self.c_pst.setStyleSheet("background: #CCC; color: black;"); self.c_pst.setEnabled(False); self.c_pst.currentIndexChanged.connect(lambda idx: cld.on_post_changed(self, idx))
|
|
self.e_tit = QtWidgets.QLineEdit(); self.e_tit.setStyleSheet("background: #CCC; color: black;"); self.e_tit.setEnabled(False)
|
|
self.e_mp3 = QtWidgets.QLineEdit(); self.e_mp3.setStyleSheet("background: #CCC; color: black;"); self.e_mp3.setEnabled(False)
|
|
self.b_mp3 = QtWidgets.QPushButton("🎵 MP3"); self.b_mp3.setStyleSheet("background: #555; color: white;"); self.b_mp3.setEnabled(False); self.b_mp3.clicked.connect(self.browse_mp3)
|
|
h_mp3 = QtWidgets.QHBoxLayout(); h_mp3.addWidget(self.e_mp3); h_mp3.addWidget(self.b_mp3)
|
|
self.chk_tmb = QtWidgets.QCheckBox("📸 Thumbnail"); self.chk_tmb.setStyleSheet("color: gray; font-weight: bold;"); self.chk_tmb.setEnabled(False)
|
|
self.chk_up = QtWidgets.QCheckBox("🚀 Upload Multipart Automático"); self.chk_up.setStyleSheet("color: gray; font-weight: bold;"); self.chk_up.setEnabled(False)
|
|
f_cld.addRow("Chave:", self.e_hash); f_cld.addRow("", self.b_conn); f_cld.addRow("Canal:", self.c_chn); f_cld.addRow("Ação:", self.c_pst); f_cld.addRow("Título:", self.e_tit); f_cld.addRow("Som:", h_mp3)
|
|
f_cld.addRow("", self.chk_tmb); f_cld.addRow("", self.chk_up)
|
|
l_cld.addLayout(f_cld); l_cld.addStretch(); self.tabs.addTab(t_cld, "☁️ 4. API Cloud")
|
|
layout.addWidget(self.tabs)
|
|
|
|
self.btn_full = QtWidgets.QPushButton("🚀 EXECUTAR TUDO (AUTO)"); self.btn_full.setStyleSheet("background: #00A8FF; color: white; font-weight: bold; font-size: 15px; height: 50px; border-radius: 8px;")
|
|
self.btn_full.clicked.connect(self.run_full_auto); layout.addWidget(self.btn_full)
|
|
|
|
self.tree = QtWidgets.QTreeWidget(); self.tree.setStyleSheet("background: white; color: black;"); self.tree.setHeaderLabels(["Objeto", "Status", "Polys", "Res", "Eixo"])
|
|
self.tree.setColumnWidth(0, 260); self.tree.setColumnWidth(1, 140); self.tree.setColumnWidth(2, 70); self.tree.setColumnWidth(3, 70); self.tree.setColumnWidth(4, 70); self.tree.itemClicked.connect(self.on_item_click)
|
|
layout.addWidget(self.tree)
|
|
|
|
# ====== RESTAURAR ESTADO APÓS RECARREGAR (PERSISTENTE NA CENA) ======
|
|
rt.execute("persistent global VR4Life_SceneListState")
|
|
|
|
state_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), "vr4_state.json")
|
|
saved_items = []
|
|
|
|
try:
|
|
# 1. Tenta recuperar da memória da cena atual
|
|
state_str = rt.VR4Life_SceneListState
|
|
if state_str and isinstance(state_str, str):
|
|
saved_items = json.loads(state_str)
|
|
# 2. Fallback pro arquivo local antigo
|
|
elif os.path.exists(state_file):
|
|
with open(state_file, 'r', encoding='utf-8') as f:
|
|
saved_items = json.load(f)
|
|
os.remove(state_file)
|
|
|
|
for d in saved_items:
|
|
it = QtWidgets.QTreeWidgetItem([d.get('name', ''), d.get('status', ''), d.get('polys', ''), d.get('res', ''), d.get('id', '')])
|
|
self.tree.addTopLevelItem(it)
|
|
self.bake_items.append({'name': d.get('name', ''), 'item': it})
|
|
except Exception as e:
|
|
print("Aviso: Falha ao restaurar estado da UI da Cena:", e)
|
|
|
|
self.pb = QtWidgets.QProgressBar(); self.pb.setAlignment(QtCore.Qt.AlignCenter); self.pb.setStyleSheet("QProgressBar { font-weight: bold; color: black; background: white; } QProgressBar::chunk { background: #00FF00; }"); self.pb.setFormat("Pronto")
|
|
layout.addWidget(self.pb)
|
|
|
|
h_l = QtWidgets.QHBoxLayout()
|
|
self.b1 = QtWidgets.QPushButton("P1: Lista"); self.b1.setStyleSheet("background: #E0E0E0; color: black; font-weight: bold; height: 35px; border-radius: 4px;"); self.b1.clicked.connect(lambda: eng.load_selection(self))
|
|
# self.b2 = QtWidgets.QPushButton("P2: Fundir"); self.b2.setStyleSheet("background: #32CD32; color: black; font-weight: bold; height: 35px; border-radius: 4px;"); self.b2.clicked.connect(lambda: eng.attach_grouped_objects(self, False))
|
|
# self.b2_5 = QtWidgets.QPushButton("P2.5: Super Solda"); self.b2_5.setStyleSheet("background: #008080; color: white; font-weight: bold; height: 35px; border-radius: 4px;"); self.b2_5.clicked.connect(lambda: eng.super_attach_objects(self, False))
|
|
|
|
self.b3 = QtWidgets.QPushButton("P3: Opt"); self.b3.setStyleSheet("background: #FF8C00; color: white; font-weight: bold; height: 35px; border-radius: 4px;"); self.b3.clicked.connect(lambda: eng.optimize_geometry(self))
|
|
|
|
self.b5 = QtWidgets.QPushButton("P5: UV"); self.b5.setStyleSheet("background: #E0E0E0; color: black; font-weight: bold; height: 35px; border-radius: 4px;"); self.b5.clicked.connect(lambda: eng.prepare_mesh_v19(self))
|
|
self.b6 = QtWidgets.QPushButton("P6: Bake"); self.b6.setStyleSheet("background: #E0E0E0; color: black; font-weight: bold; height: 35px; border-radius: 4px;"); self.b6.clicked.connect(lambda: eng.process_bake_logic_v19(self, False))
|
|
self.b7 = QtWidgets.QPushButton("P7: Exportar"); self.b7.setStyleSheet("background: #FFD700; color: black; font-weight: bold; height: 35px; border-radius: 4px;"); self.b7.clicked.connect(lambda: eng.export_glb_v19(self))
|
|
self.b8 = QtWidgets.QPushButton("P8: Inspec."); self.b8.setStyleSheet("background: #9370DB; color: white; font-weight: bold; height: 35px; border-radius: 4px;"); self.b8.clicked.connect(lambda: eng.inspect_uv(self))
|
|
self.b_clear = QtWidgets.QPushButton("🗑️ Limpar"); self.b_clear.setStyleSheet("background: #666; color: white; font-weight: bold; height: 35px; border-radius: 4px;"); self.b_clear.clicked.connect(self.clear_list)
|
|
|
|
for b in [self.b1, self.b3, self.b5, self.b6, self.b7, self.b8, self.b_clear]: h_l.addWidget(b)
|
|
layout.addLayout(h_l)
|
|
|
|
self.btn_cancel = QtWidgets.QPushButton("CANCELAR OPERAÇÃO"); self.btn_cancel.setStyleSheet("background: #FF0000; color: white; font-weight: bold; height: 40px; border-radius: 8px; margin-top: 10px;"); self.btn_cancel.clicked.connect(self.cancel_all)
|
|
layout.addWidget(self.btn_cancel)
|
|
|
|
def clear_list(self):
|
|
self.tree.clear()
|
|
self.bake_items.clear()
|
|
self.pb.setFormat("Lista Vazia")
|
|
self.pb.setValue(0)
|
|
|
|
def browse_glb_folder(self):
|
|
f = QtWidgets.QFileDialog.getExistingDirectory(self, "Selecione a Pasta GLB")
|
|
if f: self.edt_p_glb.setText(f + "\\")
|
|
|
|
def browse_mp3(self):
|
|
f, _ = QtWidgets.QFileDialog.getOpenFileName(self, "Selecione o Áudio", "", "MP3 (*.mp3)")
|
|
if f: self.e_mp3.setText(f); self.e_mp3.setStyleSheet("background: white; color: green; font-weight: bold;")
|
|
|
|
def upd_res_col(self): eng.upd_res_col(self)
|
|
def get_processable_items(self): return eng.get_processable_items(self)
|
|
|
|
def on_item_click(self, item, column):
|
|
o = rt.getNodeByName(item.text(0))
|
|
if o: rt.select(o); rt.redrawViews()
|
|
|
|
def run_full_auto(self):
|
|
self._is_cancelled = False
|
|
if not rt.execute("selection as array"): QtWidgets.QMessageBox.warning(self, "Aviso", "Selecione algo!"); return
|
|
# if self.chk_a_weld.isChecked() and not self._is_cancelled: eng.attach_grouped_objects(self, True)
|
|
# if self.chk_a_super.isChecked() and not self._is_cancelled: eng.super_attach_objects(self, True)
|
|
eng.load_selection(self)
|
|
if not self.get_processable_items(): return
|
|
|
|
if self.bake_items and not self._is_cancelled: eng.optimize_geometry(self)
|
|
if not self._is_cancelled: eng.prepare_mesh_v19(self)
|
|
|
|
def save_persistent_state(self):
|
|
state_data = []
|
|
for d in self.bake_items:
|
|
it = d['item']
|
|
state_data.append({
|
|
'name': d['name'],
|
|
'status': it.text(1),
|
|
'polys': it.text(2),
|
|
'res': it.text(3),
|
|
'id': it.text(4)
|
|
})
|
|
|
|
try:
|
|
# Salva na Cena do 3ds Max!
|
|
json_str = json.dumps(state_data)
|
|
rt.execute("persistent global VR4Life_SceneListState")
|
|
rt.VR4Life_SceneListState = json_str
|
|
|
|
# Fallback local (pra garantir)
|
|
state_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), "vr4_state.json")
|
|
with open(state_file, 'w', encoding='utf-8') as f:
|
|
json.dump(state_data, f)
|
|
except Exception as e:
|
|
print("Erro ao salvar o estado da UI na cena:", e)
|
|
|
|
def closeEvent(self, event):
|
|
self.save_persistent_state()
|
|
try: super().closeEvent(event)
|
|
except: pass
|
|
|
|
def reload_plugin(self):
|
|
self._is_cancelled = True
|
|
self.save_persistent_state()
|
|
|
|
self.close()
|
|
p = os.path.join(os.path.dirname(os.path.realpath(__file__)), "run_vr4life.py")
|
|
rt.execute(f"python.ExecuteFile @\"{p}\"")
|
|
|
|
def cancel_all(self):
|
|
self._is_cancelled = True
|
|
self.pb.setFormat("⚠️ OPERAÇÃO CANCELADA PELO USUÁRIO")
|
|
self.pb.setStyleSheet("QProgressBar { font-weight: bold; color: white; background: #FF0000; }") |