primeira versao
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…
Reference in New Issue