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 expected_tex = os.path.join(p_bk, f"{obj_name}_Baked.jpg").replace("\\", "/") if os.path.exists(expected_tex): mprint(f"--- Ignorando {obj_name} (Ja Bakeado) ---") d['item'].setText(1, "OK (Salvo)") d['item'].setForeground(1, QtGui.QColor(0, 255, 0)) continue 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)!") # O Nome do arquivo destino completo e o GLB gerado: full_path_glb = os.path.join(p_glb, f_name + ".glb").replace("\\", "/") full_path_zip = os.path.join(p_glb, f_name + ".zip").replace("\\", "/") full_path_tmb = os.path.join(p_glb, f_name + "_Thumb.jpg").replace("\\", "/") # Thumbnail adivinhada if os.path.exists(full_path_glb): mprint(f"🗜️ Empacotando GLB em Arquivo ZIP... ({full_path_zip})") import zipfile try: with zipfile.ZipFile(full_path_zip, 'w', zipfile.ZIP_DEFLATED) as zf: zf.write(full_path_glb, os.path.basename(full_path_glb)) pass # Se quiséssemos embutir o Thumb no zip, fariamos: if os.path.exists(full_path_tmb): zf.write(full_path_tmb, os.path.basename(full_path_tmb)) mprint(f"📦 ZIP Criado com Sucesso!") except Exception as e: mprint(f"🚨 ERRO ao comprimir ZIP: {e}") # Movemos para a tela de CLOUD e batemos a foto Thumbnail baseada no nome f_name import vr4life_cloud as cld cld.finalize_export(ui, p_glb, f_name) 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