You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
691 lines
30 KiB
Python
691 lines
30 KiB
Python
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"
|
|
|
|
# Injeção segura na MAXScript Listener. O Python via PySide desvia stdout nativo,
|
|
# e chamar rt.execute() mil vezes destrói a memória (c0000005).
|
|
# Aqui criamos a função NO MAXSCRIPT APENAS UMA VEZ e a referenciamos.
|
|
rt.execute("""global vr4life_mprint_func
|
|
fn vr4life_mprint_func msg = (
|
|
format "[VR4Life] %\\n" msg
|
|
)
|
|
|
|
global vr4life_popup_killer
|
|
fn vr4life_popup_killer = (
|
|
local hwnd = DialogMonitorOPS.GetWindowHandle()
|
|
if (hwnd != 0) do (
|
|
local title = UIAccessor.GetWindowText hwnd
|
|
if (matchPattern title pattern:"*V-Ray*" ignoreCase:true) or (matchPattern title pattern:"*Corona*" ignoreCase:true) do (
|
|
UIAccessor.PressButtonByName hwnd "Proceed"
|
|
UIAccessor.PressButtonByName hwnd "Yes"
|
|
UIAccessor.PressButtonByName hwnd "OK"
|
|
UIAccessor.PressButtonByName hwnd "Continue"
|
|
)
|
|
)
|
|
true
|
|
)""")
|
|
|
|
import builtins
|
|
def mprint(*args, **kwargs):
|
|
try:
|
|
msg = " ".join([str(a) for a in args])
|
|
rt.vr4life_mprint_func(msg) # Changed to call vr4life_mprint_func
|
|
except: pass
|
|
builtins.print = mprint # Redireciona todos os prints antigos deste arquivo pra Aba MAXScript F11
|
|
|
|
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)")
|
|
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):
|
|
mprint("======== [VR4Life] GATILHO DA INTERFACE: P1 [Lista] ========")
|
|
mprint("Lendo viewport para resgatar geometrias selecionadas...")
|
|
if not rt.execute("selection as array"):
|
|
mprint("ERRO: O usuario clicou em P1 mas nao ha nada selecionado.")
|
|
QtWidgets.QMessageBox.warning(ui, "Aviso", "Selecione objetos na viewport primeiro!")
|
|
return
|
|
|
|
ms = """(
|
|
undo "Fix Geometry" on (
|
|
local sel = selection as array
|
|
local groupHeads = for o in sel where isGroupHead o collect o
|
|
for g in groupHeads do ( if isValidNode g then explodeGroup g )
|
|
|
|
max modify mode
|
|
local finalSel = selection as array
|
|
local validObjs = #()
|
|
|
|
for obj in finalSel do (
|
|
if (isValidNode obj) and (superclassof obj == GeometryClass) and (classof obj != TargetObject) and (isGroupHead obj == false) then (
|
|
if canConvertTo obj Editable_Poly then convertToPoly obj
|
|
appendIfUnique validObjs obj
|
|
)
|
|
)
|
|
validObjs
|
|
)
|
|
)"""
|
|
|
|
sel = rt.execute(ms)
|
|
|
|
if not sel:
|
|
QtWidgets.QMessageBox.warning(ui, "Aviso", "Nenhuma geometria selecionada pós-explode.")
|
|
return
|
|
|
|
existing_names = [d['name'] for d in ui.bake_items]
|
|
tl = []
|
|
|
|
for o in sel:
|
|
n = str(o.name)
|
|
if n not in existing_names:
|
|
poly_count = rt.getPolygonCount(o)[0] if rt.canConvertTo(o, rt.Editable_Poly) else 0
|
|
tl.append({'name': n, 'poly': poly_count})
|
|
|
|
if not tl:
|
|
QtWidgets.QMessageBox.information(ui, "Info", "Seleção já existe na lista.")
|
|
return
|
|
|
|
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"Adicionados: {len(tl)} / Total: {len(ui.bake_items)}")
|
|
ui.pb.setValue(0)
|
|
ui.upd_res_col()
|
|
mprint(f"SUCESSO! {len(tl)} geometrias importadas para a tabela do UI com poligonos calculados.")
|
|
|
|
|
|
def attach_grouped_objects(ui, from_auto=False):
|
|
ui.pb.setFormat("Fundindo grupos inteiros...")
|
|
QtWidgets.QApplication.processEvents()
|
|
ms = """(
|
|
fn hasProtectedName nd = (
|
|
local nm = toLower nd.name
|
|
if (matchPattern nm pattern:"*_mult*") or (matchPattern nm pattern:"*_catg*") or (matchPattern nm pattern:"*_mat*") do return true
|
|
for c in nd.children do ( if hasProtectedName c do return true )
|
|
return false
|
|
)
|
|
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 and not (hasProtectedName 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, "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 = """(
|
|
fn hasProtectedName nd = (
|
|
local nm = toLower nd.name
|
|
if (matchPattern nm pattern:"*_mult*") or (matchPattern nm pattern:"*_catg*") or (matchPattern nm pattern:"*_mat*") do return true
|
|
for c in nd.children do ( if hasProtectedName c do return true )
|
|
return false
|
|
)
|
|
local sel = for o in selection where superclassof o == GeometryClass and classof o != TargetObject and not (hasProtectedName o) 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):
|
|
mprint("======== [VR4Life] GATILHO DA INTERFACE: P3 [Optimize] ========")
|
|
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 < 50:
|
|
d['item'].setText(1, "Geo Base")
|
|
else:
|
|
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]
|
|
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 prepare_mesh_v19(ui):
|
|
mprint("======== [VR4Life] GATILHO DA INTERFACE: P5 [Prepare UVP5] ========")
|
|
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"UV v19 ({i+1}/{tot}): {d['name']}...")
|
|
ui.pb.setValue(i)
|
|
QtWidgets.QApplication.processEvents()
|
|
|
|
o = rt.getNodeByName(d['name'])
|
|
if o:
|
|
try:
|
|
ms = f"""(
|
|
local obj = getNodeByName "{d['name']}"
|
|
if obj != undefined do (
|
|
max modify mode
|
|
select obj
|
|
local theMod = Unwrap_UVW()
|
|
modPanel.addModToSelection theMod ui:on
|
|
|
|
-- Usando Channel 2 para o Bake (Corrigido o bug que gerava Channel 3 e deixava as UVs dessincronizadas do motor render)
|
|
theMod.setMapChannel 2
|
|
|
|
-- Lógica original do V19 reconstruída para "Pack Custom" real
|
|
theMod.flattenMap 45.0 #() 0.01 true 0 true true
|
|
theMod.setTVSubObjectMode 3
|
|
theMod.selectFaces #{{1..(theMod.numberPolygons())}}
|
|
|
|
-- Configurando o Menu "Arrange Elements" (Pack Custom) exatamente como no Print:
|
|
-- Usar Recursivo(1), Padding(0.001), Rescale=True(Normalize), Rotate=False, FillHoles=False
|
|
theMod.pack 1 0.001 true false false
|
|
|
|
-- Não damos collapseStack a pedido do usuário!
|
|
)
|
|
)"""
|
|
rt.execute(ms)
|
|
d['item'].setText(1, "UV V19 (C2)")
|
|
except Exception as e:
|
|
d['item'].setText(1, "Erro UV")
|
|
print(f"Erro UV V19 em {d['name']}: {e}")
|
|
|
|
ui.pb.setValue(tot)
|
|
ui.pb.setFormat("UV Pack V19 Concluído!")
|
|
|
|
def close_annoying_windows():
|
|
ms = """(
|
|
try (
|
|
local desktopHWND = windows.getDesktopHWND()
|
|
local children = windows.getChildrenHWND desktopHWND
|
|
local targets = #("Corona", "V-Ray", "Buffer", "Render", "Warning", "Error")
|
|
|
|
for output in children do (
|
|
local hwnd = output[1]
|
|
local title = output[5]
|
|
for t in targets do (
|
|
if (matchPattern title pattern:("*" + t + "*") ignoreCase:true) then (
|
|
try ( UIAccessor.CloseDialog hwnd ) catch ()
|
|
)
|
|
)
|
|
)
|
|
) catch ()
|
|
)"""
|
|
rt.execute(ms)
|
|
|
|
def inspect_uv(ui):
|
|
tgs = ui.get_processable_items()
|
|
if len(tgs) != 1:
|
|
QtWidgets.QMessageBox.warning(ui, "Aviso", "Selecione exatamente UM objeto na lista para auditar a UV.")
|
|
return
|
|
|
|
obj_name = tgs[0]['name']
|
|
ms = f"""(
|
|
local o = getNodeByName "{obj_name}"
|
|
if o != undefined and isValidNode o do (
|
|
select o
|
|
max modify mode
|
|
if o.modifiers[#Unwrap_UVW] != undefined then (
|
|
modPanel.setCurrentObject o.modifiers[#Unwrap_UVW]
|
|
o.modifiers[#Unwrap_UVW].edit()
|
|
) else (
|
|
messageBox "O modificador Unwrap_UVW nao foi encontrado."
|
|
)
|
|
)
|
|
)"""
|
|
rt.execute(ms)
|
|
|
|
def process_bake_logic_v19(ui, auto_export=False):
|
|
print("\n" + "="*50)
|
|
print("🎬 INICIANDO LOG: MOTOR DE BAKE V19 PORTADO (P6)")
|
|
ui._is_cancelled = False
|
|
|
|
tgs = ui.get_processable_items()
|
|
if not tgs: return
|
|
tot = len(tgs)
|
|
ui.pb.setMaximum(tot)
|
|
ui.pb.setValue(0)
|
|
|
|
# Extract UI variables once
|
|
p_bk = ui.edt_p_bake.text().replace("\\", "/")
|
|
if not p_bk.endswith("/"): p_bk += "/"
|
|
if not os.path.exists(p_bk): os.makedirs(p_bk)
|
|
|
|
try: res_val = int(ui.spn_res.value())
|
|
except: res_val = 512
|
|
|
|
cv_passes = ui.spn_passes.value()
|
|
cv_native = "true" if ui.chk_native.isChecked() else "false"
|
|
|
|
tgt_uv_state = 2 if ui.rdo_uv2.isChecked() else 1 # 1=Replace, 2=Multi-UV
|
|
|
|
for i, d in enumerate(tgs):
|
|
if ui._is_cancelled:
|
|
print(f"\n🚨 Bake Cancelado pelo usuario.")
|
|
break
|
|
|
|
ui.pb.setFormat(f"Bake ({i+1}/{tot}): {d['name']}...")
|
|
ui.pb.setValue(i)
|
|
QtWidgets.QApplication.processEvents()
|
|
|
|
obj_name = d['name']
|
|
|
|
res_str = d['item'].text(3).replace("px", "")
|
|
sz = int(res_str) if res_str.isdigit() else res_val
|
|
|
|
mprint(f"--- Iniciando script V19 portado para: {obj_name} ---")
|
|
|
|
ms_v19_block = f"""(
|
|
fn runBake = (
|
|
local objName = "{obj_name}"
|
|
local obj = getNodeByName objName
|
|
|
|
if (obj != undefined and isValidNode obj) then (
|
|
select obj
|
|
max modify mode
|
|
try (
|
|
-------------------------------------------------------------------------
|
|
-- 1. PREPARE GEOMETRY & UV (V19 Logic 1:1)
|
|
-------------------------------------------------------------------------
|
|
if (classof obj != Editable_Poly) then convertToPoly obj
|
|
|
|
local needNewUV = true
|
|
if (polyop.getMapSupport obj 2) then needNewUV = false
|
|
else ( print "No UVs found on Channel 2. Auto-generating..." )
|
|
|
|
if needNewUV then (
|
|
modPanel.addModToSelection (Unwrap_UVW ()) ui:on
|
|
local theMod = obj.modifiers[#Unwrap_UVW]
|
|
if theMod != undefined then (
|
|
theMod.setMapChannel 2
|
|
theMod.flattenMap 45.0 #() 0.01 true 0 true true
|
|
theMod.setTVSubObjectMode 3
|
|
theMod.selectFaces #{{1..(theMod.numberPolygons())}}
|
|
theMod.pack 1 0.001 true false true
|
|
collapseStack obj
|
|
)
|
|
)
|
|
|
|
-------------------------------------------------------------------------
|
|
-- 2. PREPARE BAKE ELEMENTS
|
|
-------------------------------------------------------------------------
|
|
obj.INodeBakeProperties.removeAllBakeElements()
|
|
local be = undefined
|
|
local fileExt = ".jpg"
|
|
|
|
local curRen = renderers.current
|
|
local isCorona = matchPattern (curRen as string) pattern:"*Corona*"
|
|
local isVRay = matchPattern (curRen as string) pattern:"*V_Ray*"
|
|
|
|
if isVRay then ( try ( be = VRayCompleteMap() ) catch ( be = CompleteMap() ) )
|
|
else if isCorona then ( try ( be = Corona_Beauty() ) catch ( try ( be = CShading_Beauty() ) catch ( be = CompleteMap() ) ) )
|
|
else ( be = CompleteMap() )
|
|
|
|
if be == undefined then be = CompleteMap()
|
|
|
|
be.outputSzX = {sz}
|
|
be.outputSzY = {sz}
|
|
be.fileType = fileExt
|
|
|
|
local pBk = @"{p_bk}"
|
|
be.filename = (pBk + obj.name + "_Baked" + fileExt)
|
|
try ( be.fileOut = be.filename ) catch()
|
|
|
|
local fileExists = doesFileExist be.filename
|
|
local skipRender = false -- Overwrite always enabled via UI integration
|
|
|
|
if skipRender then (
|
|
print "Skipped (Exists)"
|
|
) else (
|
|
obj.INodeBakeProperties.addBakeElement be
|
|
obj.INodeBakeProperties.bakeEnabled = true
|
|
obj.INodeBakeProperties.bakeChannel = 2
|
|
|
|
if isCorona and ({cv_native} == false) then (
|
|
try(renderers.current.pass_limit = {cv_passes}; renderers.current.time_limit = 0; renderers.current.noise_level_limit = 0.0)catch()
|
|
)
|
|
|
|
local wasCancelled = false
|
|
try (
|
|
print "Ativando Silenciador de Popups (DialogMonitorOPS)..."
|
|
DialogMonitorOPS.unRegisterNotification id:#vr4_kill
|
|
DialogMonitorOPS.RegisterNotification vr4life_popup_killer id:#vr4_kill
|
|
DialogMonitorOPS.enabled = true
|
|
|
|
if doesFileExist be.filename do ( try ( deleteFile be.filename ) catch() )
|
|
|
|
-- DESLIGA NA MARRA RENDER DISTRIBUIDO E TEST RESOLUTION DO V-RAY PARA PARAR DE ABORTAR O BAKE
|
|
local cR = renderers.current
|
|
if (matchPattern (cR as string) pattern:"*V_Ray*") do (
|
|
try(cR.system_distributedRender = false)catch()
|
|
try(cR.image_testResolution = false)catch()
|
|
)
|
|
|
|
print "Disparando Render API V19..."
|
|
-- Fix 2024: explicit outputfile to guarantee disk writing and prevent black/missing files
|
|
render rendertype:#bakeSelected vfb:true progressBar:true quiet:true outputSize:[{sz}, {sz}] outputfile:be.filename cancelled:&wasCancelled
|
|
) catch (
|
|
print "Render Error/Cancel Caught."
|
|
wasCancelled = true
|
|
)
|
|
|
|
print "Desativando Silenciador de Popups..."
|
|
DialogMonitorOPS.enabled = false
|
|
DialogMonitorOPS.unRegisterNotification id:#vr4_kill
|
|
|
|
if wasCancelled then return -1
|
|
)
|
|
|
|
-------------------------------------------------------------------------
|
|
-- 4. MATERIAL CREATION (MOVED TO EXPORT ENGINE P7)
|
|
-------------------------------------------------------------------------
|
|
local fileFound = false
|
|
local waitForFile = 0
|
|
while (waitForFile < 50) and (fileFound == false) do (
|
|
if doesFileExist be.filename then fileFound = true
|
|
else ( sleep 0.1; waitForFile += 1 )
|
|
)
|
|
|
|
if not fileFound do (
|
|
print ("ERROR: Texture file not found: " + be.filename)
|
|
return -2
|
|
)
|
|
|
|
-------------------------------------------------------------------------
|
|
-- 5. FINAL CLEANUP
|
|
-------------------------------------------------------------------------
|
|
sleep 1.0
|
|
-- Preserva o material original da tela e nao suja a viewport!
|
|
collapseStack obj
|
|
|
|
return 1
|
|
) catch (
|
|
print ("Critical Error on Object: " + obj.name)
|
|
print (getCurrentException())
|
|
return 0
|
|
)
|
|
) else (
|
|
print "Objeto Invalido"
|
|
return 0
|
|
)
|
|
)
|
|
runBake()
|
|
)"""
|
|
|
|
ret = rt.execute(ms_v19_block)
|
|
|
|
if ret == 1:
|
|
d['item'].setText(1, "DONE V19")
|
|
d['item'].setForeground(1, QtGui.QColor(0, 255, 0))
|
|
mprint(f"-> SUCESSO: Object {obj_name} Finalizado")
|
|
elif ret == -1:
|
|
d['item'].setText(1, "Cancelado")
|
|
ui._is_cancelled = True
|
|
elif ret == -2:
|
|
d['item'].setText(1, "Tex Missing")
|
|
d['item'].setForeground(1, QtGui.QColor(255, 0, 0))
|
|
else:
|
|
d['item'].setText(1, "Error")
|
|
d['item'].setForeground(1, QtGui.QColor(255, 0, 0))
|
|
|
|
ui.pb.setValue(tot)
|
|
ui.pb.setFormat("Bake V19 Concluído!")
|
|
|
|
print("\n" + "="*50)
|
|
print("✅ FIM DO LOG DE BAKE V19")
|
|
print("="*50 + "\n")
|
|
|
|
if auto_export and not ui._is_cancelled:
|
|
export_glb_v19(ui)
|
|
|
|
def export_glb_v19(ui):
|
|
mprint("\n" + "="*50)
|
|
mprint("🎬 INICIANDO MOTOR DE EXPORT P7: RAW GLB")
|
|
|
|
p_glb = ui.edt_p_glb.text().replace("\\", "/")
|
|
if not p_glb.endswith("/"): p_glb += "/"
|
|
if not os.path.exists(p_glb):
|
|
try: os.makedirs(p_glb)
|
|
except: pass
|
|
|
|
tgs = ui.get_processable_items()
|
|
if not tgs:
|
|
mprint("Aviso: Nenhum item valido na lista para exportar.")
|
|
return
|
|
|
|
p_bk = ui.edt_p_bake.text().replace("\\", "/")
|
|
if not p_bk.endswith("/"): p_bk += "/"
|
|
|
|
tgt_uv = 2 if ui.rdo_uv2.isChecked() else 1
|
|
|
|
if len(tgs) == 1: f_name = f"{tgs[0]['name']}_Export"
|
|
else: f_name = "VR4Life_Scene_Export"
|
|
full_path = p_glb + f_name + ".glb"
|
|
|
|
names_str = "#(" + ",".join([f'"{d["name"]}"' for d in tgs]) + ")"
|
|
|
|
mprint(f"-> Local de Saida: {full_path}")
|
|
mprint(f"-> Total de Objetos na Cena: {len(tgs)}")
|
|
|
|
ms_export = f"""(
|
|
fn runExport = (
|
|
local objNames = {names_str}
|
|
local clones = #()
|
|
local errored = false
|
|
|
|
try (
|
|
print "\\n-------------- DIAGNOSTICO DE EXPORTACAO --------------"
|
|
for nm in objNames do (
|
|
local orig = getNodeByName nm
|
|
if orig != undefined do (
|
|
local c = copy orig
|
|
c.name = orig.name + "_EXP"
|
|
convertToPoly c -- Forca o colapso de toda a arvore antes de manipular a UV
|
|
|
|
-- APLICA O NOVO MATERIAL E A NOVA UV APENAS NO CLONE QUE VAI SER EXPORTADO
|
|
local pBk = @"{p_bk}"
|
|
local fileExt = ".jpg"
|
|
local texPath = (pBk + orig.name + "_Baked" + fileExt)
|
|
|
|
if doesFileExist texPath do (
|
|
local newMat = PhysicalMaterial()
|
|
newMat.name = (orig.name + "_GLTF_Mat")
|
|
-- GLTF PBR Padrao: Textura limpa no Base Color sem frescuras
|
|
newMat.base_color = color 255 255 255
|
|
newMat.roughness = 1.0
|
|
newMat.emission = 0.0
|
|
|
|
local bmpTex = BitmapTexture filename:texPath
|
|
bmpTex.coords.mapChannel = 1
|
|
newMat.base_color_map = bmpTex
|
|
|
|
c.material = newMat
|
|
)
|
|
|
|
if {tgt_uv} == 2 do (
|
|
try ( channelInfo.CopyChannel c 3 2 ) catch()
|
|
try ( channelInfo.PasteChannel c 3 1 ) catch()
|
|
try ( channelInfo.ClearChannel c 2 ) catch()
|
|
)
|
|
|
|
collapseStack c
|
|
append clones c
|
|
)
|
|
)
|
|
|
|
if clones.count > 0 then (
|
|
print "Invocando Motor Nativo: gltf_export (selectedOnly:true)"
|
|
max select none
|
|
select clones
|
|
local res = exportFile @"{full_path}" #noPrompt selectedOnly:true using:gltf_export
|
|
print "Export Finalizado. Limpando cena..."
|
|
delete clones
|
|
return true
|
|
) else (
|
|
print "Nenhum arquivo para clonar!"
|
|
return false
|
|
)
|
|
) catch (
|
|
print "Critical Error na preparacao do GLB"
|
|
print (getCurrentException())
|
|
try(delete clones)catch()
|
|
return false
|
|
)
|
|
)
|
|
runExport()
|
|
)"""
|
|
|
|
res = rt.execute(ms_export)
|
|
if res == True:
|
|
mprint("✅ SUCESSO: GLB Gerado com Texturas Embutidas (Modo Silencioso)!")
|
|
else:
|
|
mprint("🚨 ERRO: Falha na geracao. Vefique o log.")
|
|
|
|
mprint("="*50 + "\n")
|
|
|
|
def upd_res_col(ui):
|
|
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}")
|
|
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))
|
|
else: d['item'].setForeground(3, QtGui.QColor(255, 50, 50))
|
|
except: pass
|
|
|
|
def get_processable_items(ui):
|
|
valid_items = []
|
|
for d in ui.bake_items:
|
|
obj = rt.getNodeByName(d['name'])
|
|
if obj: valid_items.append(d)
|
|
return valid_items |