From 4e8b45fa3021fd9577117100e6d5fd9dffdd847c Mon Sep 17 00:00:00 2001 From: henrique Date: Mon, 2 Mar 2026 13:13:44 -0300 Subject: [PATCH] versao estavel --- __pycache__/vr4life_cloud.cpython-311.pyc | Bin 10514 -> 13857 bytes __pycache__/vr4life_engine.cpython-311.pyc | Bin 35818 -> 35723 bytes __pycache__/vr4life_ui.cpython-311.pyc | Bin 29971 -> 31132 bytes vr4life_cloud.py | 68 ++- vr4life_engine.py | 625 ++++++++++++--------- vr4life_ui.py | 22 +- 6 files changed, 431 insertions(+), 284 deletions(-) diff --git a/__pycache__/vr4life_cloud.cpython-311.pyc b/__pycache__/vr4life_cloud.cpython-311.pyc index b9dfc639047134076a7654586ef5526e3a4a9e86..2695c6d605d11d5d4f4bd3fd184f6773eda05fa3 100644 GIT binary patch delta 5285 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 O9`s&D8G99%vHt>ni8d+# diff --git a/__pycache__/vr4life_engine.cpython-311.pyc b/__pycache__/vr4life_engine.cpython-311.pyc index a414ec31466651f504107c97e5f6ea44907d5ed1..cdb798461cc15ecc854cb3590acfc279e461c131 100644 GIT binary patch literal 35723 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 literal 35818 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 Jeu7s@|6ivq=s^Gg diff --git a/__pycache__/vr4life_ui.cpython-311.pyc b/__pycache__/vr4life_ui.cpython-311.pyc index f234b607b55b2c4b1ce7bdcb82fb54aa86eef240..12f286047b513848cc4cee04b77e14c14cf4fdd1 100644 GIT binary patch delta 3944 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 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)