diff --git a/__pycache__/vr4life_cloud.cpython-311.pyc b/__pycache__/vr4life_cloud.cpython-311.pyc index b9dfc63..2695c6d 100644 Binary files a/__pycache__/vr4life_cloud.cpython-311.pyc and b/__pycache__/vr4life_cloud.cpython-311.pyc differ diff --git a/__pycache__/vr4life_engine.cpython-311.pyc b/__pycache__/vr4life_engine.cpython-311.pyc index a414ec3..cdb7984 100644 Binary files a/__pycache__/vr4life_engine.cpython-311.pyc and b/__pycache__/vr4life_engine.cpython-311.pyc differ diff --git a/__pycache__/vr4life_ui.cpython-311.pyc b/__pycache__/vr4life_ui.cpython-311.pyc index f234b60..12f2860 100644 Binary files a/__pycache__/vr4life_ui.cpython-311.pyc and b/__pycache__/vr4life_ui.cpython-311.pyc differ diff --git a/vr4life_cloud.py b/vr4life_cloud.py index 66eb7f0..db41d9d 100644 --- a/vr4life_cloud.py +++ b/vr4life_cloud.py @@ -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"]: - 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 "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): + 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 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') diff --git a/vr4life_engine.py b/vr4life_engine.py index 531555e..5246cf2 100644 --- a/vr4life_engine.py +++ b/vr4life_engine.py @@ -11,22 +11,13 @@ 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: + 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)) + 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: @@ -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 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 = """( 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 + print(f"\n--- Processando objeto: {d['name']} ---") + + # CÓDIGO CORRIGIDO: Tudo embrulhado num "fn" (função) nativo para o return funcionar. + ms = f"""( + fn applyMultiMatID objName thrVal = ( + local o = getNodeByName objName + if o == undefined do return -1 + if not isKindOf o Editable_Poly do convertToPoly o - 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" + 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) - 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 - - 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 r = #() - if isValidNode o then ( - if (polyop.getNumFaces o) < 1 then try(delete o)catch() else append r o.name - ) - if isValidNode o2 then ( - if (polyop.getNumFaces o2) < 1 then try(delete o2)catch() else append r o2.name - ) - return r - ) - bsp "{cn}" "{ax}" - )""" + if md <= thrVal do ( + for f = 1 to (polyop.getNumFaces o) do polyop.setFaceMatID o f 1 + return 1 + ) + + local numChunks = (ceil (md / thrVal)) as integer + if numChunks > 10 do numChunks = 10 + + 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 - 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) - 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) - except Exception as e: - print(f"Erro ao fatiar {cn}: {str(e)}") - fn.append(cn) - else: - fn.append(cn) - sc += 1 + 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 + ) + return numChunks + ) + try ( applyMultiMatID "{d['name']}" {thr} ) catch ( -2 ) + )""" + + try: + print("-> Executando MaxScript de Fatiamento Lógico...") + r = rt.execute(ms) + print(f"-> Resposta do MaxScript: {r}") - 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 + 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: + print("-> Objeto menor que a Meta. Mantido como 1 ID.") + d['item'].setText(1, "1 ID (Normal)") + + except Exception as e: + d['item'].setText(1, "Erro Python") + print(f"🚨 ERRO CRÍTICO PYTHON no Multi-UV: {e}") - # ======================================================== - # 🕵️‍♂️ MÓDULO DETETIVE: COMPARA SCRIPT vs CENA REAL - # ======================================================== + ui.pb.setValue(tot) + 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 - - 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 + ui.pb.setFormat(f"Bake ({i+1}/{tot}): {d['name']}...") + ui.pb.setValue(i) + QtWidgets.QApplication.processEvents() - ext = ".exr" if (i_vr and u_den) else ".jpg" - t_rnd = t_jpg.replace(".jpg", ".exr") if ext == ".exr" else t_jpg + print(f"\n--- Preparando Bake: {d['name']} ---") - 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) + o = rt.getNodeByName(d['name']) + if not o: + print("🚨 ERRO: Objeto não encontrado.") + continue 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) + # 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}") - wt = 0 - while not os.path.exists(t_rnd) and wt < 60: time.sleep(0.5); wt += 0.5; QtWidgets.QApplication.processEvents() + 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) - 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 + 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): 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 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() + 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_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_id) and wt < 60: + time.sleep(0.5) + wt += 0.5 + QtWidgets.QApplication.processEvents() + + if os.path.exists(t_rnd_id): + if i_vr and u_den: + 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_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_id); os.remove(c_out.replace("\\", "/")) + except: pass + + 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)) diff --git a/vr4life_ui.py b/vr4life_ui.py index 61094c1..ecaa3d8 100644 --- a/vr4life_ui.py +++ b/vr4life_ui.py @@ -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)