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 V313 - MODULAR ENTERPRISE") self.setWindowFlags(self.windowFlags() | QtCore.Qt.WindowMinimizeButtonHint | QtCore.Qt.WindowMaximizeButtonHint | QtCore.Qt.WindowCloseButtonHint | QtCore.Qt.WindowStaysOnTopHint) 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 --- # ultima alteração 2:56 AM 11-03- 2026 lbl_t = QtWidgets.QLabel("VR4LIFE - V313 (Modular)"); 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: #444; color: white; border: 1px solid #222; font-weight: bold; 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.config_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "vr4_config.json") saved_hash = "" if os.path.exists(self.config_path): try: with open(self.config_path, "r", encoding="utf-8") as f: saved_hash = json.load(f).get("api_hash", "") except: pass self.e_hash = QtWidgets.QLineEdit(); self.e_hash.setStyleSheet("background: white; color: black; letter-spacing: 2px;"); self.e_hash.setEchoMode(QtWidgets.QLineEdit.Password) self.e_hash.setText(saved_hash) def save_hash_config(): try: with open(self.config_path, "w", encoding="utf-8") as f: json.dump({"api_hash": self.e_hash.text()}, f) except: pass self.e_hash.textChanged.connect(save_hash_config) self.b_conn = QtWidgets.QPushButton("🔄 Conectar CMS"); self.b_conn.setStyleSheet("background: #2E8B57; color: white; font-weight: bold; border: 1px solid #1c5234; 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) def ui_do_fetch(idx=None): cld.fetch_posts_for_channel(self) self.c_chn.currentIndexChanged.connect(ui_do_fetch) self.rdo_add = QtWidgets.QRadioButton("Novo Projeto"); self.rdo_add.setStyleSheet("color: white; font-weight: bold;"); self.rdo_add.setChecked(True); self.rdo_add.setEnabled(False) self.rdo_edt = QtWidgets.QRadioButton("Atualizar Antigo"); self.rdo_edt.setStyleSheet("color: white; font-weight: bold;"); self.rdo_edt.setEnabled(False) def ui_do_post_change(*args): cld.on_post_changed(self) def ui_do_fetch_edt(checked): if checked: cld.fetch_posts_for_channel(self) self.rdo_add.toggled.connect(ui_do_post_change) self.rdo_edt.toggled.connect(ui_do_post_change) self.rdo_edt.toggled.connect(ui_do_fetch_edt) h_act = QtWidgets.QHBoxLayout(); h_act.addWidget(self.rdo_add); h_act.addWidget(self.rdo_edt) self.c_pst = QtWidgets.QComboBox(); self.c_pst.setStyleSheet("background: #CCC; color: black;"); self.c_pst.setEnabled(False) self.c_pst.currentIndexChanged.connect(ui_do_post_change) 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.b_tmb = QtWidgets.QPushButton("📸 Gerar Thumbnail"); self.b_tmb.setStyleSheet("background: #FF8C00; color: white; font-weight: bold; height: 35px; border-radius: 4px;"); self.b_tmb.setEnabled(False) self.b_tmb.clicked.connect(lambda: cld.generate_thumbnail(self)) self.b_up = QtWidgets.QPushButton("☁️ Enviar para Nuvem"); self.b_up.setStyleSheet("background: #0078D7; color: white; font-weight: bold; height: 35px; border-radius: 4px;"); self.b_up.setEnabled(False) self.b_up.clicked.connect(lambda: cld.upload_to_cloud(self)) h_bot = QtWidgets.QHBoxLayout(); h_bot.addWidget(self.b_tmb); h_bot.addWidget(self.b_up) 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:", h_act); f_cld.addRow("Projetos:", self.c_pst) f_cld.addRow("Título:", self.e_tit); f_cld.addRow("Som:", h_mp3) f_cld.addRow("", h_bot) 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.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.b_clear, self.b1, self.b3, self.b5, self.b6, self.b7]: 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; }")