versao estavel

main
henrique 1 month ago
parent 9cd6d13ca3
commit 4e8b45fa30

@ -24,15 +24,55 @@ def on_post_changed(ui, idx):
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()
ui.pb.setFormat("Preparando materiais VR (Multi-ID)..."); QtWidgets.QApplication.processEvents()
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"]:
if "Bake" in d['item'].text(1) or "Já existe" in d['item'].text(1) or "Mat" in d['item'].text(1):
try:
max_id = rt.execute(f"""(local o = getNodeByName "{d['name']}"; local mid = 1; if o != undefined and isKindOf o Editable_Poly do ( for f = 1 to (polyop.getNumFaces o) do ( local id = polyop.getFaceMatID o f; if id > mid do mid = id ) ); mid)""")
if max_id > 1:
# CONSTRUTOR DE MULTI-MATERIAL (Para objetos que foram processados pelo P4: Multi-UV)
ms_mat = f"""(
local o = getNodeByName "{d['name']}"
local mm = MultiMaterial numsubs:{int(max_id)} name:(o.name + "_MultiMat")
for i = 1 to {int(max_id)} do (
local ip = @"{p_bk}/{d['name']}_B_ID" + (i as string) + ".jpg"
if doesFileExist ip do (
local m = PhysicalMaterial name:(o.name + "_MatID_" + (i as string))
m.base_color_map = BitmapTexture filename:ip
m.roughness = 1.0; m.metalness = 0.0; try(m.reflectivity = 0.0; m.reflection_weight = 0.0)catch()
mm[i] = m
)
)
o.material = mm
)"""
rt.execute(ms_mat)
d['item'].setText(1, "Mat OK (Multi)")
fo.append(rt.getNodeByName(d['name']))
else:
# MATERIAL PADRÃO (Para objetos normais que cabem numa textura só)
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 ) )""")
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 = 1.0; m.metalness = 0.0; try(m.reflectivity = 0.0; m.reflection_weight = 0.0)catch()
o.material = m
)
)""")
fo.append(rt.getNodeByName(d['name'])); d['item'].setText(1, "Mat OK")
except: d['item'].setText(1, "Erro Mat")
except Exception as e:
d['item'].setText(1, "Erro Mat"); print(f"Erro Material em {d['name']}: {e}")
if not fo and not ui._is_cancelled:
QtWidgets.QMessageBox.warning(ui, "Exportação Interrompida", "Nenhum objeto validado para exportação.")
ui.pb.setFormat("Pronto"); ui.pb.setValue(0)
return
if fo and not ui._is_cancelled:
rt.select(fo); sn = rt.maxFileName.split(".")[0] if rt.maxFileName else "Cena_VR4LIFE"
@ -45,6 +85,20 @@ def finalize_export(ui, p_bk, p_glb):
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("Limpando Hierarquia e XForm..."); QtWidgets.QApplication.processEvents()
rt.execute("""
(
local sel = selection as array
for o in sel do (
o.parent = undefined
ResetXForm o
collapseStack o
if not isKindOf o Editable_Poly do try(convertToPoly o)catch()
)
)
""")
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')

@ -11,20 +11,11 @@ def get_vdenoise_path():
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
@ -45,332 +36,428 @@ def load_selection(ui):
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()
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):
thr = ui.spn_max_sz.value()
ui.pb.setFormat("Filtando e Fundindo grupos..."); QtWidgets.QApplication.processEvents()
ms = f"""(
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
local limit = {thr}
for h in heads do (
local cg = for c in h.children where superclassof c == GeometryClass and classof c != TargetObject collect c
local valid_cg = #()
-- O SEGURANÇA DA BALADA: Expulsa quem for maior que o limite
for c in cg do (
local md = amax #(abs(c.max.x - c.min.x), abs(c.max.y - c.min.y), abs(c.max.z - c.min.z))
if md > limit then (
setGroupMember c false
append loose c -- Joga pros VIPs soltos
) else (
append valid_cg c -- Fica pra ser fundido
)
)
if valid_cg.count > 0 do (
local b = valid_cg[1]; if not isKindOf b Editable_Poly do try(convertToPoly b)catch()
if isKindOf b Editable_Poly do (
for i = 2 to valid_cg.count do ( local n = valid_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; act += 1
)
)
try(delete h)catch()
)
ui.pb.setFormat("Fundindo grupos inteiros...")
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 valid_cg = for c in h.children where superclassof c == GeometryClass and classof c != TargetObject collect c; if valid_cg.count > 0 do ( local b = valid_cg[1]; if not isKindOf b Editable_Poly do try(convertToPoly b)catch(); if isKindOf b Editable_Poly do ( for i = 2 to valid_cg.count do ( local n = valid_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; act += 1 ) ); try(delete h)catch() )
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 processados.\nObjetos gigantes foram ejetados do grupo com segurança!")
else: QtWidgets.QMessageBox.information(ui, "Aviso", "Nenhum Grupo encontrado na seleção.")
if act > 0: QtWidgets.QMessageBox.information(ui, "Sucesso", f"{act} Grupos fundidos.")
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 super_attach_objects(ui, from_auto=False):
ui.pb.setFormat("Super Solda VR...")
QtWidgets.QApplication.processEvents()
ms = """( local sel = for o in selection where superclassof o == GeometryClass and classof o != TargetObject collect o; if sel.count > 1 do ( local b = sel[1]; if not isKindOf b Editable_Poly do try(convertToPoly b)catch(); if isKindOf b Editable_Poly do ( for i = 2 to sel.count do ( local n = sel[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 = uniqueName "VR_Bloco_Fundido"; select b ) ); sel.count )"""
try:
act = rt.execute(ms)
load_selection(ui)
if not from_auto:
if act > 1: QtWidgets.QMessageBox.information(ui, "Sucesso", f"{act} objetos fundidos.")
else: QtWidgets.QMessageBox.information(ui, "Aviso", "Selecione pelo menos 2 objetos.")
except Exception as e: print("Erro Super 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")
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()
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")
if 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)
rt.select(o)
if not rt.isKindOf(o, rt.Editable_Poly):
rt.convertTo(o, rt.Editable_Poly)
opt = rt.ProOptimizer()
rt.addModifier(o, opt)
opt.KeepTextures = True
opt.KeepNormals = True
opt.KeepBorders = True
opt.LockMat = True
opt.Calculate = True
current_pct = 100.0
p = 0
while True:
if ui._is_cancelled: break
current_pct = current_pct * (sp / 100.0)
opt.VertexPercent = current_pct
rt.redrawViews()
QtWidgets.QApplication.processEvents()
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!")
p += 1
d['item'].setText(2, f"{np:,}")
d['item'].setText(1, f"Opt ({p}x)")
QtWidgets.QApplication.processEvents()
if np <= tg or np < 50 or p >= 20 or (np >= cp and p > 1): break
cp = np
rt.collapseStack(o)
if not rt.isKindOf(o, rt.Editable_Poly):
rt.convertTo(o, rt.Editable_Poly)
if not ui._is_cancelled:
d['item'].setText(2, f"{rt.getPolygonCount(o)[0]:,}")
d['item'].setText(1, f"Opt OK ({p}x)")
except Exception as e:
d['item'].setText(1, "Erro Opt")
print(f"Erro Opt {d['name']}: {e}")
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()
pn = [d['name'] for d in tgs]
fn = [d['name'] for d in ui.bake_items if d['name'] not in pn]
tot = len(tgs)
ui.pb.setMaximum(tot)
ui.pb.setValue(0)
rt.execute("max modify mode")
print("\n" + "="*50)
print("🔍 INICIANDO LOG: MULTI-UV (P4)")
print(f"-> Tamanho de corte (Threshold): {thr}")
print("="*50)
for i, d in enumerate(tgs):
if ui._is_cancelled: break
ui.pb.setFormat(f"Cortando ({i+1}/{tot}): {d['name']}...")
ui.pb.setFormat(f"Multi-UV ({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 or not rt.isValidNode(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"
print(f"\n--- Processando objeto: {d['name']} ---")
# CÓDIGO CORRIGIDO: Tudo embrulhado num "fn" (função) nativo para o return funcionar.
ms = f"""(
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
fn applyMultiMatID objName thrVal = (
local o = getNodeByName objName
if o == undefined do return -1
if not isKindOf o Editable_Poly do convertToPoly o
local dx = abs(o.max.x - o.min.x)
local dy = abs(o.max.y - o.min.y)
local dz = abs(o.max.z - o.min.z)
local md = amax #(dx, dy, dz)
if md <= thrVal do (
for f = 1 to (polyop.getNumFaces o) do polyop.setFaceMatID o f 1
return 1
)
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
local numChunks = (ceil (md / thrVal)) as integer
if numChunks > 10 do numChunks = 10
if not isKindOf o Editable_Poly do try(convertToPoly o)catch()
if not isKindOf o2 Editable_Poly do try(convertToPoly o2)catch()
local axis = if md == dx then 1 else if md == dy then 2 else 3
local minVal = if axis == 1 then o.min.x else if axis == 2 then o.min.y else o.min.z
local step = md / numChunks
local r = #()
if isValidNode o then (
if (polyop.getNumFaces o) < 1 then try(delete o)catch() else append r o.name
for f = 1 to (polyop.getNumFaces o) do (
local center = polyop.getFaceCenter o f
local val = if axis == 1 then center.x else if axis == 2 then center.y else center.z
local id = (floor ((val - minVal) / step)) as integer + 1
if id > numChunks do id = numChunks
if id < 1 do id = 1
polyop.setFaceMatID o f id
)
if isValidNode o2 then (
if (polyop.getNumFaces o2) < 1 then try(delete o2)catch() else append r o2.name
return numChunks
)
return r
)
bsp "{cn}" "{ax}"
try ( applyMultiMatID "{d['name']}" {thr} ) catch ( -2 )
)"""
try:
n_p = rt.execute(ms)
if n_p is not None:
n_p_list = list(n_p)
if len(n_p_list) > 1:
act += 1
for p in n_p_list:
po = rt.getNodeByName(p)
if po and rt.isValidNode(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)
print("-> Executando MaxScript de Fatiamento Lógico...")
r = rt.execute(ms)
print(f"-> Resposta do MaxScript: {r}")
if r == -1:
print("🚨 ERRO: Objeto não encontrado na cena pelo MaxScript.")
d['item'].setText(1, "Erro: Objeto")
elif r == -2:
print("🚨 ERRO: MaxScript falhou internamente (Topologia corrompida?).")
d['item'].setText(1, "Erro: Script")
elif r and int(r) > 1:
print(f"✅ SUCESSO: Objeto dividido em {int(r)} IDs!")
d['item'].setText(1, f"IDs: {int(r)}")
else:
# Se falhou em pegar o node pelo nome (nomes duplicados), forçamos a string
fn.append(p)
elif len(n_p_list) == 1:
fn.append(n_p_list[0])
else:
fn.append(cn)
print("-> Objeto menor que a Meta. Mantido como 1 ID.")
d['item'].setText(1, "1 ID (Normal)")
except Exception as e:
print(f"Erro ao fatiar {cn}: {str(e)}")
fn.append(cn)
else:
fn.append(cn)
sc += 1
d['item'].setText(1, "Erro Python")
print(f"🚨 ERRO CRÍTICO PYTHON no Multi-UV: {e}")
ui.pb.setValue(tot)
ui.pb.setFormat("Fatiador Concluído! Limpando a cena...")
rt.clearSelection()
final_sel = []
# 1. Filtra a lista oficial do script
for n in fn:
o = rt.getNodeByName(n)
if o and rt.isValidNode(o):
try:
if rt.canConvertTo(o, rt.Editable_Poly):
if rt.getPolygonCount(o)[0] > 0:
final_sel.append(o)
else:
rt.delete(o)
except: pass
# ========================================================
# 🕵️‍♂️ MÓDULO DETETIVE: COMPARA SCRIPT vs CENA REAL
# ========================================================
ui.pb.setFormat("Multi-IDs Gerados!")
print("\n" + "="*50)
print("🕵️‍♂️ DETETIVE VR4LIFE: RELATÓRIO PÓS-FATIAMENTO")
print("="*50)
script_names = [o.name for o in final_sel if rt.isValidNode(o)]
print(f"-> Objetos validados na lista do Script: {len(script_names)}")
# Varre a cena buscando toda a geometria que existe agora
ms_all_geo = "for o in objects where superclassof o == GeometryClass and classof o != TargetObject collect o.name"
cena_names = list(rt.execute(ms_all_geo))
print(f"-> Geometrias reais encontradas na Cena: {len(cena_names)}")
# Encontra quem está na cena mas ficou de fora do script
perdidos = [n for n in cena_names if n not in script_names]
print(f"-> Diferença (Objetos perdidos): {len(perdidos)}")
if perdidos:
print("\n🚨 LISTA DE SUSPEITOS:")
for p in perdidos:
po = rt.getNodeByName(p)
if po and rt.isValidNode(po):
try:
faces = rt.getPolygonCount(po)[0] if rt.canConvertTo(po, rt.Editable_Poly) else "N/A"
t_x = abs(po.max.x - po.min.x)
t_y = abs(po.max.y - po.min.y)
t_z = abs(po.max.z - po.min.z)
maior_eixo = max(t_x, t_y, t_z)
print(f" [!] NOME: {p}")
print(f" Faces: {faces} | Maior Eixo: {maior_eixo:.1f} | Dims: ({t_x:.1f}, {t_y:.1f}, {t_z:.1f})")
# Se eles são válidos, nós resgatamos eles à força para a seleção final!
if faces != "N/A" and faces > 0:
final_sel.append(po)
print(" >> STATUS: Resgatado e adicionado à lista!")
except Exception as e:
print(f" >> ERRO ao ler suspeito: {e}")
else:
print("✅ Nenhum objeto foi perdido no processo!")
print("✅ FIM DO LOG MULTI-UV")
print("="*50 + "\n")
# ========================================================
if final_sel:
rt.select(final_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")
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()
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!")
ms = f"""(
local o = getNodeByName "{d['name']}"
if not isKindOf o Editable_Poly do convertToPoly o
local m = Unwrap_UVW()
addModifier o m
m.setMapChannel 3
m.setTVSubObjectMode 3
local maxID = 1
for f = 1 to (polyop.getNumFaces o) do (
local id = polyop.getFaceMatID o f
if id > maxID do maxID = id
)
for x = 1 to maxID do (
m.selectByMatID x
local sel = m.getSelectedFaces()
if not sel.isEmpty do (
m.flattenMap 45.0 #() 0.002 true 0 true false
m.pack 1 0.002 true false false
)
)
collapseStack o
)"""
rt.execute(ms)
if not rt.isKindOf(o, rt.Editable_Poly):
rt.convertTo(o, rt.Editable_Poly)
d['item'].setText(1, "UV Packed (C3)")
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()
ui._is_cancelled = 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)
tgs = ui.get_processable_items()
tot = len(tgs)
ui.pb.setMaximum(tot)
ui.pb.setValue(0)
print("\n" + "="*50)
print("🎬 INICIANDO LOG: MOTOR DE BAKE (P6)")
print(f"-> Pasta Alvo: {p_bk}")
print(f"-> Renderizador: {rnd}")
print("="*50)
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
ui.pb.setFormat(f"Bake ({i+1}/{tot}): {d['name']}...")
ui.pb.setValue(i)
QtWidgets.QApplication.processEvents()
print(f"\n--- Preparando Bake: {d['name']} ---")
o = rt.getNodeByName(d['name'])
if not o:
print("🚨 ERRO: Objeto não encontrado.")
continue
try:
# 1. Conta quantos IDs existem no objeto
ms_mid = f"""(local o = getNodeByName "{d['name']}"; local mid = 1; if o != undefined and isKindOf o Editable_Poly do ( for f = 1 to (polyop.getNumFaces o) do ( local id = polyop.getFaceMatID o f; if id > mid do mid = id ) ); mid)"""
max_id = rt.execute(ms_mid)
if not max_id: max_id = 1
max_id = int(max_id)
print(f"-> Total de IDs a renderizar: {max_id}")
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)
for mid in range(1, max_id + 1):
if ui._is_cancelled: break
print(f"\n-> Assando ID: {mid} de {max_id}")
if max_id == 1:
t_jpg_id = os.path.join(p_bk, f"{d['name']}_B.jpg").replace("\\", "/")
else:
t_jpg_id = os.path.join(p_bk, f"{d['name']}_B_ID{mid}.jpg").replace("\\", "/")
if os.path.exists(t_jpg_id):
d['item'].setText(1, f"Já existe ID{mid}")
print("-> Ficheiro já existe. Pulando.")
continue
cr = ui.spn_res.value(); o = rt.getNodeByName(d['name'])
if o and a_res:
cr = ui.spn_res.value()
if 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)
t_rnd_id = t_jpg_id.replace(".jpg", ".exr") if ext == ".exr" else t_jpg_id
if max_id > 1:
print("-> Isolando UV do ID atual...")
# CÓDIGO CORRIGIDO 1: Embrulhado em 'fn'
ms_uv = f"""(
fn isolateUV currentMid = (
global temp_uv_mod = Unwrap_UVW()
addModifier $ temp_uv_mod
temp_uv_mod.setMapChannel 3
temp_uv_mod.setTVSubObjectMode 3
local allF = #{{1..(polyop.getNumFaces $)}}
temp_uv_mod.selectByMatID currentMid
local tgtF = temp_uv_mod.getSelectedFaces()
local hideF = allF - tgtF
if hideF.numberset > 0 do (
temp_uv_mod.selectFaces hideF
temp_uv_mod.moveSelected [-10, -10, 0]
)
return 1
)
try ( isolateUV {mid} ) catch ( 0 )
)"""
res_uv = rt.execute(ms_uv)
if res_uv == 0: print("🚨 ERRO: O MaxScript não conseguiu manipular as UVs.")
print(f"-> Disparando o Render: {t_rnd_id}")
# CÓDIGO CORRIGIDO 2: Embrulhado em 'fn'
ms_bake = f"""(
fn doBake fileOut szX szY fileExt cor den elem = (
try(freeSceneBitmaps(); vfbControl #clearimage)catch()
if cor and den do try ( renderers.current.denoise_enable = true ) catch()
$.INodeBakeProperties.removeAllBakeElements()
local be = (execute (elem + "()"))
be.outputSzX = szX
be.outputSzY = szY
be.fileType = fileExt
be.fileName = fileOut
$.INodeBakeProperties.addBakeElement be
$.INodeBakeProperties.bakeEnabled = true
$.INodeBakeProperties.bakeChannel = 3
render rendertype:#bakeSelected vfb:true quiet:true outputfile:fileOut
return 1
)
try ( doBake "{t_rnd_id}" {cr} {cr} "{ext}" {str(i_cor).lower()} {str(u_den).lower()} "{el}" ) catch ( 0 )
)"""
res_bk = rt.execute(ms_bake)
if res_bk == 0: print("🚨 ERRO: O Renderizador rejeitou o comando de Bake!")
wt = 0
while not os.path.exists(t_rnd) and wt < 60: time.sleep(0.5); wt += 0.5; QtWidgets.QApplication.processEvents()
while not os.path.exists(t_rnd_id) and wt < 60:
time.sleep(0.5)
wt += 0.5
QtWidgets.QApplication.processEvents()
if os.path.exists(t_rnd):
if os.path.exists(t_rnd_id):
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("/", "\\")
print("-> Passando Denoise IA...")
c_in = t_rnd_id.replace("/", "\\")
c_out = t_rnd_id.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() )""")
f_exr = c_out.replace("\\", "/") if os.path.exists(c_out.replace("\\", "/")) else t_rnd_id
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_id}" 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("\\", "/"))
try: os.remove(t_rnd_id); 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())
if max_id > 1:
print("-> Removendo modificador temporário de UV...")
rt.execute("try(deleteModifier $ globalvars.get #temp_uv_mod)catch()")
if os.path.exists(t_jpg_id):
d['item'].setText(1, "Bake OK")
print("-> SUCESSO: Textura gravada no disco.")
else:
d['item'].setText(1, "Erro Conv")
print("🚨 ERRO: A textura não foi salva. Timeout?")
if not ui._is_cancelled:
print("-> Restaurando UV final do objeto...")
rt.execute(f"""( local o = getNodeByName "{d['name']}"; try( ChannelInfo.CopyChannel o 3 3; ChannelInfo.PasteChannel o 3 1; collapseStack o )catch() )""")
except Exception as e:
d['item'].setText(1, "Erro Render")
print(f"🚨 EXCEÇÃO PYTHON: {e}")
ui.pb.setValue(tot)
ui.pb.setFormat("Bake Concluído!")
print("\n" + "="*50)
print("✅ FIM DO LOG DE BAKE")
print("="*50 + "\n")
if auto_export and not ui._is_cancelled:
cld.finalize_export(ui, p_bk, ui.edt_p_glb.text())
def upd_res_col(ui):
a_r = ui.chk_a_res.isChecked(); m_r = ui.spn_res.value()
a_r = ui.chk_a_res.isChecked()
m_r = ui.spn_res.value()
for d in ui.bake_items:
o = rt.getNodeByName(d['name'])
if o:
try:
md = max(abs(o.max.x - o.min.x), abs(o.max.y - o.min.y), abs(o.max.z - o.min.z))
r = 256 if md <= ui.s256.value() else 512 if md <= ui.s512.value() else 1024 if md <= ui.s1024.value() else m_r if a_r else m_r
d['item'].setText(3, f"{r}px"); d['item'].setText(4, f"{md:.1f}")
d['item'].setText(3, f"{r}px")
d['item'].setText(4, f"{md:.1f}")
if r == 256: d['item'].setForeground(3, QtGui.QColor(0, 255, 255))
elif r == 512: d['item'].setForeground(3, QtGui.QColor(0, 255, 0))
elif r == 1024: d['item'].setForeground(3, QtGui.QColor(255, 165, 0))

@ -21,7 +21,7 @@ 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
self.resize(1050, 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)
@ -49,14 +49,16 @@ class AutoBakeManager(QtWidgets.QDialog):
# 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.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 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)
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
# 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)
@ -68,7 +70,6 @@ class AutoBakeManager(QtWidgets.QDialog):
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))
@ -98,12 +99,16 @@ class AutoBakeManager(QtWidgets.QDialog):
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.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.b4 = QtWidgets.QPushButton("P4: Multi-UV"); 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)
for b in [self.b1, self.b2, self.b2_5, 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)
@ -128,6 +133,7 @@ class AutoBakeManager(QtWidgets.QDialog):
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)

Loading…
Cancel
Save