instalador

main
henrique 2 months ago
parent 2667937988
commit e2b89a7552

@ -0,0 +1,58 @@
import os
import urllib.request
from pymxs import runtime as rt
# ==========================================
# CONFIGURAÇÕES DO GITEA - IMMERSE GAMES
# ==========================================
GITEA_RAW_URL = "https://git.immersegame.com/immersegame/vr4life-3dmax-plugin/raw/branch/main/"
GITEA_TOKEN = "efebcde14ce96a2b80d0b3f207991bc155018ab8"
# Descobre a pasta segura de Scripts do Usuário do próprio 3ds Max
user_scripts_dir = rt.getDir(rt.name("userScripts"))
PLUGIN_DIR = os.path.join(user_scripts_dir, "VR4Life_Plugin").replace("\\", "/")
FILES_TO_DOWNLOAD = [
"vr4life_ui.py",
"vr4life_engine.py",
"vr4life_cloud.py",
"run_vr4life.py",
"vr4life_updater.py",
"install_vr4life.py",
"version.txt"
]
def install_from_cloud():
rt.clearListener()
print("=== INICIANDO INSTALAÇÃO ONLINE VR4LIFE ===")
if not os.path.exists(PLUGIN_DIR):
os.makedirs(PLUGIN_DIR)
try:
for file_name in FILES_TO_DOWNLOAD:
remote_url = GITEA_RAW_URL + file_name
local_path = os.path.join(PLUGIN_DIR, file_name).replace("\\", "/")
print(f"Baixando: {file_name}...")
req = urllib.request.Request(remote_url)
req.add_header("Authorization", f"token {GITEA_TOKEN}")
response = urllib.request.urlopen(req)
remote_code = response.read().decode('utf-8')
with open(local_path, "w", encoding="utf-8") as f:
f.write(remote_code)
print("Download concluído! Configurando menu do 3ds Max...")
install_script = os.path.join(PLUGIN_DIR, "install_vr4life.py").replace("\\", "/")
rt.python.ExecuteFile(install_script)
except Exception as e:
msg = f"Erro ao baixar os arquivos do Gitea.\nVerifique a internet, URL ou o Token de acesso.\n\nDetalhe técnico: {str(e)}"
rt.messageBox(msg, title="Erro de Instalação")
print(msg)
if __name__ == "__main__":
install_from_cloud()

Binary file not shown.

@ -0,0 +1,8 @@
name "VR4Life Web Installer"
version "1.0"
extract to $temp\VR4Life_WebInstaller
copy *.* to $temp\VR4Life_WebInstaller
run "run_installer.ms"
drop "run_installer.ms"

@ -0,0 +1,7 @@
-- O MZP vai extrair os arquivos para a pasta Temporária do Max.
local py_script = (getDir #temp) + "\\VR4Life_WebInstaller\\Instalador_Online_VR4Life.py"
if doesFileExist py_script then (
python.ExecuteFile py_script
) else (
messageBox "Erro: Arquivo Python não encontrado no pacote MZP." title:"Erro"
)

@ -0,0 +1,49 @@
import os
from pymxs import runtime as rt
def install_plugin():
# 1. Descobre a pasta exata onde este instalador foi salvo pelo usuário
script_dir = os.path.dirname(os.path.realpath(__file__))
py_file = os.path.join(script_dir, "run_vr4life.py").replace("\\", "/")
# 2. Cria o MacroScript (A Ponte) dinamicamente injetando código no Max
macro_code = f"""
macroScript VR4Life_AutoBake category:"VR4Life" buttonText:"Painel Auto-Bake" tooltip:"Abre o motor de otimização VR4Life / Zombisco"
(
python.ExecuteFile @"{py_file}"
)
"""
rt.execute(macro_code)
# 3. Acessa o Gerenciador de Menus do 3ds Max
menu_name = "VR4Life"
main_menu_bar = rt.menuMan.getMainMenuBar()
# Verifica se o menu já existe (para evitar duplicações se o usuário instalar 2x)
existing_menu = rt.menuMan.findMenu(menu_name)
if existing_menu:
rt.menuMan.unRegisterMenu(existing_menu)
# Cria o menu Dropdown principal
new_menu = rt.menuMan.createMenu(menu_name)
# Cria o item clicável que aponta para o MacroScript (A Ponte)
menu_item = rt.menuMan.createActionItem("VR4Life_AutoBake", "VR4Life")
new_menu.addItem(menu_item, -1)
# Injeta o menu lá no topo, na barra principal do 3ds Max
sub_menu_item = rt.menuMan.createSubMenuItem(menu_name, new_menu)
# Adiciona no final da barra (usando o index atual)
index = main_menu_bar.numItems()
main_menu_bar.addItem(sub_menu_item, index)
# Atualiza a interface gráfica do 3ds Max na mesma hora
rt.menuMan.updateMenuBar()
# Mensagem de Sucesso
msg = "Plugin VR4Life instalado com sucesso!\n\nOlhe para a barra superior do seu 3ds Max, o menu já está lá pronto para uso."
rt.messageBox(msg, title="Instalação Concluída")
if __name__ == "__main__":
install_plugin()

@ -49,21 +49,39 @@ def load_selection(ui):
ui.tree.addTopLevelItem(i); ui.bake_items.append({'name': d['name'], 'item': i}) 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() ui.pb.setFormat(f"Carregados: {len(tl)}"); ui.pb.setValue(0); ui.upd_res_col()
def attach_grouped_objects(ui, from_auto=False): def attach_grouped_objects(ui, from_auto=False):
ui.pb.setFormat("Fundindo grupos (Atlas)..."); QtWidgets.QApplication.processEvents() thr = ui.spn_max_sz.value()
ms = """( 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 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 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 heads = for o in orig_sel where isGroupHead o collect o
local limit = {thr}
for h in heads do ( for h in heads do (
local cg = for c in h.children where superclassof c == GeometryClass and classof c != TargetObject collect c local cg = for c in h.children where superclassof c == GeometryClass and classof c != TargetObject collect c
if cg.count > 0 do ( local valid_cg = #()
local b = cg[1]; if not isKindOf b Editable_Poly do try(convertToPoly b)catch()
-- 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 ( if isKindOf b Editable_Poly do (
for i = 2 to cg.count do ( local n = 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() ) 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; delete h; act += 1 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 local final_sel = loose; for no in new_objs do append final_sel no; if final_sel.count > 0 do select final_sel; act
)""" )"""
@ -71,8 +89,8 @@ def attach_grouped_objects(ui, from_auto=False):
act = rt.execute(ms) act = rt.execute(ms)
load_selection(ui) load_selection(ui)
if not from_auto: if not from_auto:
if act > 0: QtWidgets.QMessageBox.information(ui, "Atlas Welder", f"Sucesso! {act} Grupos fundidos.\nObjetos soltos preservados.") 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.") else: QtWidgets.QMessageBox.information(ui, "Aviso", "Nenhum Grupo encontrado na seleção.")
except Exception as e: print("Erro Solda:", e) except Exception as e: print("Erro Solda:", e)
ui.pb.setValue(0); ui.pb.setFormat("Pronto") ui.pb.setValue(0); ui.pb.setFormat("Pronto")
@ -229,3 +247,18 @@ def process_bake_logic(ui, auto_export=False):
except: d['item'].setText(1, "Erro Render") except: d['item'].setText(1, "Erro Render")
ui.pb.setValue(tot); ui.pb.setFormat("Bake Concluído!") 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 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()
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

@ -0,0 +1,92 @@
import os
import urllib.request
from pymxs import runtime as rt
try: from PySide6 import QtWidgets
except ImportError: from PySide2 import QtWidgets
# URL Oficial do repositório da Immerse Games (Lendo a branch 'main')
# Obs: Se o seu Gitea estiver usando 'master' em vez de 'main', basta trocar a última palavra.
GITEA_RAW_URL = "https://git.immersegame.com/immersegame/vr4life-3dmax-plugin/raw/branch/main/"
# Token de Leitura do Gitea
GITEA_TOKEN = "efebcde14ce96a2b80d0b3f207991bc155018ab8"
FILES_TO_UPDATE = [
"vr4life_ui.py",
"vr4life_engine.py",
"vr4life_cloud.py",
"run_vr4life.py",
"vr4life_updater.py"
]
def check_and_update(ui_parent=None):
script_dir = os.path.dirname(os.path.realpath(__file__))
local_version_file = os.path.join(script_dir, "version.txt").replace("\\", "/")
if ui_parent:
ui_parent.pb.setFormat("Autenticando e checando versão no Gitea..."); QtWidgets.QApplication.processEvents()
try:
# 1. Lê a versão local (Se não existir, assume 0.0)
local_version = "0.0"
if os.path.exists(local_version_file):
with open(local_version_file, "r") as f:
local_version = f.read().strip()
# 2. Bate na Nuvem com o Token para ler o version.txt
remote_version_url = GITEA_RAW_URL + "version.txt"
req_version = urllib.request.Request(remote_version_url)
req_version.add_header("Authorization", f"token {GITEA_TOKEN}")
response_version = urllib.request.urlopen(req_version)
remote_version = response_version.read().decode('utf-8').strip()
# 3. Compara as versões: Só baixa se a do Gitea for mais nova
if remote_version == local_version:
if ui_parent:
ui_parent.pb.setFormat("Sistema já está atualizado!"); ui_parent.pb.setValue(100)
QtWidgets.QMessageBox.information(ui_parent, "Atualizador", f"Você já está usando a versão mais recente ({local_version}).")
return
# 4. Inicia o Download Seguro dos Arquivos
if ui_parent:
ui_parent.pb.setFormat(f"Nova versão {remote_version} encontrada! Baixando..."); QtWidgets.QApplication.processEvents()
updated_count = 0
for file_name in FILES_TO_UPDATE:
remote_url = GITEA_RAW_URL + file_name
local_path = os.path.join(script_dir, file_name).replace("\\", "/")
if ui_parent:
ui_parent.pb.setFormat(f"Baixando {file_name}..."); QtWidgets.QApplication.processEvents()
# Puxa o código com o Token
req = urllib.request.Request(remote_url)
req.add_header("Authorization", f"token {GITEA_TOKEN}")
response = urllib.request.urlopen(req)
remote_code = response.read().decode('utf-8')
# Sobrescreve na máquina local
with open(local_path, "w", encoding="utf-8") as f:
f.write(remote_code)
updated_count += 1
# 5. Salva a nova versão local para a próxima checagem
with open(local_version_file, "w") as f:
f.write(remote_version)
msg = f"Sucesso! Plugin atualizado para a versão {remote_version} ({updated_count} arquivos).\n\nPor favor, feche esta janela e abra o plugin novamente pelo menu superior."
if ui_parent:
ui_parent.pb.setFormat("Atualização Concluída!"); ui_parent.pb.setValue(100)
QtWidgets.QMessageBox.information(ui_parent, "Update VR4Life", msg)
else:
rt.messageBox(msg, title="Update VR4Life")
except Exception as e:
erro_msg = f"Falha de Autenticação ou Download.\nVerifique seu Token e URL do Gitea.\n\nDetalhe técnico: {str(e)}"
if ui_parent:
QtWidgets.QMessageBox.critical(ui_parent, "Erro de Update", erro_msg)
ui_parent.pb.setFormat("Pronto"); ui_parent.pb.setValue(0)
else:
rt.messageBox(erro_msg, title="Erro de Update")
Loading…
Cancel
Save