primeira versao

main
henrique 2 months ago
commit 2667937988

@ -0,0 +1,15 @@
import sys, os
import importlib
# Garante que o 3ds Max enxergue a sua pasta de plugins
script_dir = os.path.dirname(os.path.realpath(__file__))
if script_dir not in sys.path:
sys.path.append(script_dir)
# Importa a sua nova UI limpa e recarrega para sempre pegar atualizações
import vr4life_ui
importlib.reload(vr4life_ui)
if __name__ == "__main__":
app = vr4life_ui.AutoBakeManager()
app.show()

@ -0,0 +1,70 @@
import os, time, zipfile
from pymxs import runtime as rt
try: from PySide6 import QtWidgets
except ImportError: from PySide2 import QtWidgets
def mock_connect_api(ui):
if len(ui.e_hash.text()) < 5: QtWidgets.QMessageBox.warning(ui, "Acesso Negado", "Hash inválida."); return
ui.b_conn.setText("Conectando..."); QtWidgets.QApplication.processEvents(); time.sleep(1)
for w in [ui.c_chn, ui.c_pst, ui.e_tit, ui.e_mp3, ui.b_mp3]: w.setEnabled(True); w.setStyleSheet("background: white; color: black; font-weight: bold;")
ui.c_chn.clear(); ui.c_chn.addItems(["Canal: Imóveis Virtuais", "Canal: Showroom Zombisco"])
ui.c_pst.clear(); ui.c_pst.addItems(["[ + Cadastrar Nova Postagem ]", "Editar: Apartamento V1"])
ui.chk_up.setEnabled(True); ui.chk_up.setStyleSheet("color: #FFD700; font-weight: bold;"); ui.chk_up.setChecked(True)
ui.b_conn.setText("✅ Conectado!"); ui.b_conn.setStyleSheet("background: #000; color: #0F0; font-weight: bold; border: 1px solid #0F0;")
on_post_changed(ui, 0)
def on_post_changed(ui, idx):
if idx == 0:
ui.e_tit.setText(""); ui.e_tit.setPlaceholderText("Título do projeto...")
ui.chk_tmb.setChecked(True); ui.chk_tmb.setEnabled(False); ui.chk_tmb.setStyleSheet("color: #00FF00; font-weight: bold;")
else:
ui.e_tit.setText(ui.c_pst.currentText().replace("Editar: ", ""))
ui.chk_tmb.setEnabled(True); ui.chk_tmb.setStyleSheet("color: white; font-weight: bold;")
def finalize_export(ui, p_bk, p_glb):
if not os.path.exists(p_glb): os.makedirs(p_glb)
fo = []; rt.execute("max modify mode"); tgs = ui.get_processable_items()
for d in tgs:
if ui._is_cancelled: break
if "Bake" in d['item'].text(1) or d['item'].text(1) in ["Já existe", "Mat OK"]:
ip = os.path.join(p_bk, f"{d['name']}_B.jpg").replace("\\", "/")
if os.path.exists(ip):
try:
rt.execute(f"""( local o = getNodeByName "{d['name']}"; if o != undefined do ( local m = PhysicalMaterial name:(o.name + "_Mat"); m.base_color_map = BitmapTexture filename:@"{ip}"; m.roughness = 0.8; m.metalness = 0.0; o.material = m ) )""")
fo.append(rt.getNodeByName(d['name'])); d['item'].setText(1, "Mat OK")
except: d['item'].setText(1, "Erro Mat")
if fo and not ui._is_cancelled:
rt.select(fo); sn = rt.maxFileName.split(".")[0] if rt.maxFileName else "Cena_VR4LIFE"
og = os.path.join(p_glb, f"{sn}.glb").replace("\\", "/"); oz = os.path.join(p_glb, f"{sn}.zip").replace("\\", "/")
tp = os.path.join(p_glb, f"{sn}_Thumb.jpg").replace("\\", "/"); mp = ui.e_mp3.text(); hm = os.path.exists(mp)
to = False
if ui.chk_tmb.isChecked() and ui.chk_tmb.isEnabled():
ui.pb.setFormat("📸 Thumbnail (1280x720)..."); QtWidgets.QApplication.processEvents()
rt.execute(f"""( local c = getActiveCamera(); if c == undefined do ( local ac = for cam in cameras where (classof cam != TargetObject) collect cam; if ac.count > 0 do c = ac[1] ); try ( if c != undefined then ( render camera:c outputwidth:1280 outputheight:720 outputfile:@"{tp}" vfb:false quiet:true ) else ( render outputwidth:1280 outputheight:720 outputfile:@"{tp}" vfb:false quiet:true ) ) catch() )""")
if os.path.exists(tp): to = True
ui.pb.setFormat("Montando GLB..."); QtWidgets.QApplication.processEvents()
try: rt.exportFile(og, rt.name("noPrompt"), selectedOnly=True, using=rt.GLTFExporter)
except: rt.execute(f'exportFile "{og}" #noPrompt selectedOnly:true')
zo = False
if os.path.exists(og):
ui.pb.setFormat("🗜️ ZIPando GLB..."); QtWidgets.QApplication.processEvents()
try:
with zipfile.ZipFile(oz, 'w', zipfile.ZIP_DEFLATED) as zf: zf.write(og, os.path.basename(og))
zo = True
except: pass
if ui.chk_up.isChecked() and ui.chk_up.isEnabled() and zo:
cs = ui.c_chn.currentText(); ac = "NOVO" if ui.c_pst.currentIndex() == 0 else "ATUALIZAR"; tit = ui.e_tit.text()
ui.pb.setFormat(f"📡 API: Enviando..."); QtWidgets.QApplication.processEvents(); time.sleep(1.5)
msg = f"Sincronizado!\n\n📡 Canal: {cs}\n📝 Ação: {ac}\n🏷️ Titulo: {tit}\n\n📦 ZIP: {os.path.basename(oz)}\n📸 Thumb: {'Sim' if to else 'Não'}\n🎵 Áudio: {'Sim' if hm else 'Não'}"
QtWidgets.QMessageBox.information(ui, "Cloud API", msg)
else:
msg = f"Exportado:\n{og}";
if zo: msg += f"\n\n🗜️ ZIP:\n{oz}"
if to: msg += f"\n\n📸 Thumb:\n{tp}"
QtWidgets.QMessageBox.information(ui, "Sucesso", msg)
ui.pb.setFormat("Pronto"); ui.pb.setValue(0)

@ -0,0 +1,231 @@
import os, time, subprocess
from pymxs import runtime as rt
try: from PySide6 import QtWidgets, QtCore, QtGui
except ImportError: from PySide2 import QtWidgets, QtCore, QtGui
import vr4life_cloud as cld
def get_vdenoise_path():
for k, v in os.environ.items():
if "VRAY" in k.upper() and "MAX" in k.upper() and "MAIN" in k.upper():
p = os.path.join(v, "vdenoise.exe")
if os.path.exists(p): return f'"{p}"'
return "vdenoise.exe"
def deep_log_and_kill():
try:
time.sleep(0.5); tgts = ["3d66", "V-Ray", "Buffer", "RGBA", "Render"]
for w in rt.windows.getChildrenHWND(0):
for t in tgts:
if t.lower() in str(w[4]).lower(): rt.windows.sendMessage(w[0], 0x0010, 0, 0); rt.windows.sendMessage(w[0], 0x0002, 0, 0)
except: pass
def load_bake_elements(ui):
ui.cmb_bake_elem.clear()
found = ["CompleteMap", "VRayCompleteMap", "Corona_Beauty", "CShading_Beauty"]
try:
r_list = rt.execute("for c in bake_elements.classes collect (c as string)")
if r_list:
for c in r_list:
if str(c) not in found: found.append(str(c))
except: pass
ui.cmb_bake_elem.addItems(sorted(list(set(found))))
try:
rnd = str(rt.execute("renderers.current as string"))
t = "VRayCompleteMap" if "V_Ray" in rnd else "Corona_Beauty" if "Corona" in rnd else "CompleteMap"
idx = ui.cmb_bake_elem.findText(t)
if idx >= 0: ui.cmb_bake_elem.setCurrentIndex(idx)
except: pass
def load_selection(ui):
ui.tree.clear(); ui.bake_items = []
ms = """( local g = #(); fn ex objs = ( for o in objs do ( if isGroupHead o then ( setGroupOpen o true; ex o.children ) else ( if superclassof o == GeometryClass and (classOf o != TargetObject) and (canConvertTo o Editable_Poly) do appendIfUnique g o ) ) ); ex selection; g )"""
sel = rt.execute(ms)
if not sel: return
tl = [{'name': str(o.name), 'poly': rt.getPolygonCount(o)[0] if rt.canConvertTo(o, rt.Editable_Poly) else 0} for o in sel]
tl.sort(key=lambda x: x['poly'], reverse=True)
for d in tl:
i = QtWidgets.QTreeWidgetItem([d['name'], "Pronto", f"{d['poly']:,}", "", ""])
i.setFlags(i.flags() | QtCore.Qt.ItemIsUserCheckable); i.setCheckState(0, QtCore.Qt.Checked)
ui.tree.addTopLevelItem(i); ui.bake_items.append({'name': d['name'], 'item': i})
ui.pb.setFormat(f"Carregados: {len(tl)}"); ui.pb.setValue(0); ui.upd_res_col()
def attach_grouped_objects(ui, from_auto=False):
ui.pb.setFormat("Fundindo grupos (Atlas)..."); QtWidgets.QApplication.processEvents()
ms = """(
local act = 0; local new_objs = #(); local orig_sel = selection as array
local loose = for o in orig_sel where not isGroupHead o and not isGroupMember o collect o
local heads = for o in orig_sel where isGroupHead o collect o
for h in heads do (
local cg = for c in h.children where superclassof c == GeometryClass and classof c != TargetObject collect c
if cg.count > 0 do (
local b = cg[1]; if not isKindOf b Editable_Poly do try(convertToPoly b)catch()
if isKindOf b Editable_Poly do (
for i = 2 to cg.count do ( local n = cg[i]; if not isKindOf n Editable_Poly do try(convertToPoly n)catch(); if isKindOf n Editable_Poly do try(polyOp.attach b n)catch() )
b.name = h.name; setGroupMember b false; append new_objs b; delete h; act += 1
)
)
)
local final_sel = loose; for no in new_objs do append final_sel no; if final_sel.count > 0 do select final_sel; act
)"""
try:
act = rt.execute(ms)
load_selection(ui)
if not from_auto:
if act > 0: QtWidgets.QMessageBox.information(ui, "Atlas Welder", f"Sucesso! {act} Grupos fundidos.\nObjetos soltos preservados.")
else: QtWidgets.QMessageBox.information(ui, "Aviso", "Nenhum Grupo encontrado.")
except Exception as e: print("Erro Solda:", e)
ui.pb.setValue(0); ui.pb.setFormat("Pronto")
def optimize_geometry(ui):
sp = ui.spn_pct.value(); tg = ui.spn_min_poly.value(); tgs = ui.get_processable_items(); tot = len(tgs)
ui.pb.setMaximum(tot); ui.pb.setValue(0); rt.execute("max modify mode")
for i, d in enumerate(tgs):
if ui._is_cancelled: break
ui.pb.setFormat(f"Opt ({i+1}/{tot}): {d['name']}..."); ui.pb.setValue(i); QtWidgets.QApplication.processEvents()
o = rt.getNodeByName(d['name'])
if o:
try:
cp = rt.getPolygonCount(o)[0] if rt.canConvertTo(o, rt.Editable_Poly) else 0
if cp <= tg: d['item'].setText(1, "Já < Meta")
elif cp < 50: d['item'].setText(1, "Geo Base")
else:
rt.select(o); p = 0
while cp > tg and cp >= 50 and p < 20:
if not rt.isKindOf(o, rt.Editable_Poly): rt.convertTo(o, rt.Editable_Poly)
opt = rt.ProOptimizer(); rt.addModifier(o, opt); opt.KeepTextures = False; opt.KeepNormals = False; opt.Calculate = True; opt.VertexPercent = sp
rt.collapseStack(o)
if not rt.isKindOf(o, rt.Editable_Poly): rt.convertTo(o, rt.Editable_Poly)
np = rt.getPolygonCount(o)[0]
if np >= cp or np == 0: break
cp = np; p += 1; QtWidgets.QApplication.processEvents()
d['item'].setText(2, f"{cp:,}"); d['item'].setText(1, f"Opt ({p}x)")
except: d['item'].setText(1, "Erro Opt")
ui.pb.setValue(tot); ui.pb.setFormat("Opt Concluída!")
def slice_large_objects(ui, from_auto=False):
if not ui.bake_items: return
thr = ui.spn_max_sz.value(); act = 0; rt.execute("max modify mode"); tgs = ui.get_processable_items()
fn = []; pn = [d['name'] for d in tgs]
for d in ui.bake_items:
if d['name'] not in pn: fn.append(d['name'])
tot = len(tgs); ui.pb.setMaximum(tot); ui.pb.setValue(0)
for i, d in enumerate(tgs):
if ui._is_cancelled: break
ui.pb.setFormat(f"Cortando ({i+1}/{tot}): {d['name']}..."); ui.pb.setValue(i); QtWidgets.QApplication.processEvents()
chk = [d['name']]; sc = 0
while len(chk) > 0 and sc < 300:
QtWidgets.QApplication.processEvents()
if ui._is_cancelled: break
cn = chk.pop(0); o = rt.getNodeByName(cn)
if not o: continue
md = max(abs(o.max.x - o.min.x), abs(o.max.y - o.min.y), abs(o.max.z - o.min.z))
if md > thr:
ax = "X" if md == abs(o.max.x - o.min.x) else "Y" if md == abs(o.max.y - o.min.y) else "Z"
ms = """(
fn bsp objN ax = (
local o = getNodeByName objN; if o == undefined or not (canConvertTo o Editable_Poly) do return #()
if not isKindOf o Editable_Poly do try(convertToPoly o)catch(); if not isKindOf o Editable_Poly do return #()
local v = polyop.getNumVerts o; if v > 0 do ( try(o.weldThreshold = 0.001)catch(); try(polyop.weldVertsByThreshold o #{{1..v}})catch() )
CenterPivot o; local o2 = copy o name:(uniqueName (o.name + "_F"))
local rot = if ax == "X" then eulerAngles 0 90 0 else if ax == "Y" then eulerAngles 90 0 0 else eulerAngles 0 0 0
local s1 = SliceModifier(); s1.Slice_Type = 2; s1.slice_plane.rotation = rot; addModifier o s1; try(addModifier o (Cap_Holes()))catch(); collapseStack o
local s2 = SliceModifier(); s2.Slice_Type = 3; s2.slice_plane.rotation = rot; addModifier o2 s2; try(addModifier o2 (Cap_Holes()))catch(); collapseStack o2
if not isKindOf o Editable_Poly do try(convertToPoly o)catch(); if not isKindOf o2 Editable_Poly do try(convertToPoly o2)catch()
local f1 = if isValidNode o then polyop.getNumFaces o else 0; local f2 = if isValidNode o2 then polyop.getNumFaces o2 else 0
local r = #(); if f1 < 5 then try(delete o)catch() else append r o.name; if f2 < 5 then try(delete o2)catch() else append r o2.name; return r
); bsp "{0}" "{1}"
)""".format(cn, ax)
try:
n_p = rt.execute(ms)
if n_p and len(n_p) > 1:
act += 1
for p in n_p:
po = rt.getNodeByName(p)
if po:
pd = max(abs(po.max.x - po.min.x), abs(po.max.y - po.min.y), abs(po.max.z - po.min.z))
if pd >= md * 0.98: fn.append(p)
else: chk.append(p)
else:
for p in n_p: fn.append(p) if n_p else fn.append(cn)
except: fn.append(cn)
else: fn.append(cn)
sc += 1
ui.pb.setValue(tot); ui.pb.setFormat("Fatiador Concluído!")
rt.clearSelection(); sel = [rt.getNodeByName(n) for n in fn if rt.getNodeByName(n)]; rt.select(sel)
if act > 0: load_selection(ui)
def prepare_mesh(ui):
tgs = ui.get_processable_items(); tot = len(tgs); ui.pb.setMaximum(tot); ui.pb.setValue(0); rt.execute("max modify mode")
for i, d in enumerate(tgs):
if ui._is_cancelled: break
ui.pb.setFormat(f"UV ({i+1}/{tot}): {d['name']}..."); ui.pb.setValue(i); QtWidgets.QApplication.processEvents()
o = rt.getNodeByName(d['name'])
if o:
rt.select(o)
try:
if not rt.isKindOf(o, rt.Editable_Poly): rt.convertTo(o, rt.Editable_Poly)
rt.execute(f"""( local o = getNodeByName "{d['name']}"; local m = Unwrap_UVW(); addModifier o m; m.setMapChannel 1; m.setTVSubObjectMode 3; local nf = polyOp.getNumFaces o; m.selectFaces #{{1..nf}}; m.flattenMap 45.0 #() 0.002 true 0 true false; m.pack 1 0.002 true false false; collapseStack o )""")
if not rt.isKindOf(o, rt.Editable_Poly): rt.convertTo(o, rt.Editable_Poly)
d['item'].setText(1, "UV Packed")
except: d['item'].setText(1, "Erro UV")
ui.pb.setValue(tot); ui.pb.setFormat("UV Pack Concluído!")
def process_bake_logic(ui, auto_export=False):
el = ui.cmb_bake_elem.currentText(); p_bk = ui.edt_p_bake.text()
rnd = str(rt.execute("renderers.current as string")); i_vr = "V_Ray" in rnd; i_cor = "Corona" in rnd
u_den = ui.chk_denoise.isChecked(); a_res = ui.chk_a_res.isChecked()
if not os.path.exists(p_bk): os.makedirs(p_bk)
tgs = ui.get_processable_items(); tot = len(tgs); ui.pb.setMaximum(tot); ui.pb.setValue(0)
for i, d in enumerate(tgs):
if ui._is_cancelled: break
ui.pb.setFormat(f"Bake ({i+1}/{tot}): {d['name']}..."); ui.pb.setValue(i); QtWidgets.QApplication.processEvents()
t_jpg = os.path.join(p_bk, f"{d['name']}_B.jpg").replace("\\", "/")
if os.path.exists(t_jpg): d['item'].setText(1, "Já existe"); continue
cr = ui.spn_res.value(); o = rt.getNodeByName(d['name'])
if o and a_res:
md = max(abs(o.max.x - o.min.x), abs(o.max.y - o.min.y), abs(o.max.z - o.min.z))
cr = 256 if md <= ui.s256.value() else 512 if md <= ui.s512.value() else 1024 if md <= ui.s1024.value() else cr
ext = ".exr" if (i_vr and u_den) else ".jpg"
t_rnd = t_jpg.replace(".jpg", ".exr") if ext == ".exr" else t_jpg
rt.execute("max select none"); rt.select(o); rt.execute("max modify mode"); rt.execute("max zoomext sel all"); rt.redrawViews(); time.sleep(0.5)
try:
rt.execute("freeSceneBitmaps(); try(vfbControl #clearimage)catch()")
ms = """(
local o = getNodeByName "{0}"; if {5} and {6} do try ( renderers.current.denoise_enable = true ) catch()
renderWidth = {2}; renderHeight = {2}; o.INodeBakeProperties.removeAllBakeElements()
local be = {1}(); be.outputSzX = {2}; be.outputSzY = {2}; be.fileType = "{3}"; be.fileName = "{4}"
o.INodeBakeProperties.addBakeElement be; o.INodeBakeProperties.bakeEnabled = true; o.INodeBakeProperties.bakeChannel = 1
render rendertype:#bakeSelected vfb:true quiet:true outputfile:"{4}"
)""".format(d['name'], el, cr, ext, t_rnd, str(i_cor).lower(), str(u_den).lower())
rt.execute(ms)
wt = 0
while not os.path.exists(t_rnd) and wt < 60: time.sleep(0.5); wt += 0.5; QtWidgets.QApplication.processEvents()
if os.path.exists(t_rnd):
if i_vr and u_den:
d['item'].setText(1, "IA Limpando...")
c_in = t_rnd.replace("/", "\\"); c_out = t_rnd.replace(".exr", "_denoised.exr").replace("/", "\\")
try:
p = subprocess.Popen(f'{get_vdenoise_path()} -inputFile="{c_in}" -outputFile="{c_out}" -display=0', shell=True, creationflags=0x08000000); dt = 0
while p.poll() is None and dt < 60: QtWidgets.QApplication.processEvents(); time.sleep(0.5); dt += 0.5
if p.poll() is None: p.terminate()
except: pass
f_exr = c_out.replace("\\", "/") if os.path.exists(c_out.replace("\\", "/")) else t_rnd
rt.execute(f"""( try(vfbControl #clearimage)catch(); try ( local bI = openBitmap @"{f_exr}"; if bI != undefined do ( local bO = bitmap bI.width bI.height filename:@"{t_jpg}" hdr:true; copy bI bO; save bO; close bI; close bO; free bI; free bO ) ) catch() )""")
rt.execute("freeSceneBitmaps(); gc light:true"); time.sleep(0.5)
try: os.remove(t_rnd); os.remove(c_out.replace("\\", "/"))
except: pass
if os.path.exists(t_jpg): d['item'].setText(1, "Bake OK"); deep_log_and_kill()
else: d['item'].setText(1, "Erro Conv")
else: d['item'].setText(1, "Erro Save")
except: d['item'].setText(1, "Erro Render")
ui.pb.setValue(tot); ui.pb.setFormat("Bake Concluído!")
if auto_export and not ui._is_cancelled: cld.finalize_export(ui, p_bk, ui.edt_p_glb.text())

@ -0,0 +1,138 @@
import os
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 V167.0 - MODULAR ENTERPRISE")
self.resize(950, 1380); 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()
rt.clearListener(); eng.load_bake_elements(self)
def init_ui(self):
layout = QtWidgets.QVBoxLayout(self)
lbl_t = QtWidgets.QLabel("VR4LIFE - V167.0 (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: #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.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)
f_gen.addRow("Mapa:", self.cmb_bake_elem); f_gen.addRow("", self.chk_denoise); 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.spn_pct = QtWidgets.QDoubleSpinBox(); self.spn_pct.setStyleSheet("background: white; color: black;"); self.spn_pct.setRange(0.1, 100.0); self.spn_pct.setValue(40.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 Fatiador 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(QtWidgets.QLabel("🛠️ OTIMIZADOR:")); f_geo.addRow("Vertex %:", self.spn_pct); f_geo.addRow("Meta Polys:", self.spn_min_poly); f_geo.addRow(QtWidgets.QLabel("✂️ FATIADOR:")); f_geo.addRow("Cortar 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
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)
l_tex.addLayout(f_tex); l_tex.addStretch(); self.tabs.addTab(t_tex, "🎨 3. Textura")
# ABA 4
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)
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.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.b4 = QtWidgets.QPushButton("P4: Fatiar"); self.b4.setStyleSheet("background: #8B008B; color: white; font-weight: bold; height: 35px; border-radius: 4px;"); self.b4.clicked.connect(lambda: eng.slice_large_objects(self, False))
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(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(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: cld.finalize_export(self, self.edt_p_bake.text(), self.edt_p_glb.text()))
for b in [self.b1, self.b2, self.b3, self.b4, self.b5, self.b6, self.b7]: h_l.addWidget(b)
layout.addLayout(h_l)
self.btn_cancel = QtWidgets.QPushButton("CANCELAR / FECHAR"); self.btn_cancel.setStyleSheet("background: #FF0000; color: white; font-weight: bold; height: 40px; border-radius: 8px;"); self.btn_cancel.clicked.connect(self.cancel_all)
layout.addWidget(self.btn_cancel)
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)
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 self.chk_a_slice.isChecked() and not self._is_cancelled: eng.slice_large_objects(self, True)
if not self._is_cancelled: eng.prepare_mesh(self)
if not self._is_cancelled: time.sleep(1.0); eng.process_bake_logic(self, True)
def cancel_all(self): self._is_cancelled = True; self.close()
Loading…
Cancel
Save