I’d like to import internal exe modules from an external process. I can do this fine from an IDE, but when I package the project with pyinstaller, and then run it, the modules can’t be found.
Here’s a high level illustration of the workflow:
Importing maya_app
from within the external userSetup.py
returns an error: ImportError: No modules named maya_app
The whole workflow works when I run it from an IDE (because it’s just normal python), but when I launch Maya from bundle.exe
Maya doesn’t seem to retain any knowledge of the modules.
One special thing I do during the subprocess.Popen is insert Maya’s expected values for PYTHONPATH and PYTHONHOME into my environment variables because my tool is Python37 and Maya is Python27. This actually works and Maya uses its own python packages, but maybe it’s wiping the knowledge of my modules? It’s unclear how I’d test this.
I found this post on stack overflow but I couldn’t get it to work for my setup.
Is this even possible? Is there a way for Maya to inherit the modules during the subprocess.Popen? I’d like to solve this without exposing the code to the user in the exe directory, but it seems like that’s the only solution at this point.
Code:
launch_maya.py
import os
import sys
import subprocess
from PySide2 import QtWidgets
class Widget(QtWidgets.QWidget):
def __init__(self, parent=None):
super(Widget, self).__init__(parent)
# GUI
btn_launch = QtWidgets.QPushButton('launch maya')
btn_launch.clicked.connect(self.on_launch)
# Layout
main_layout = QtWidgets.QHBoxLayout(self)
main_layout.addWidget(btn_launch)
self.setLayout(main_layout)
# Root path exe vs ide
if getattr(sys, 'frozen', False):
self.root_path = sys._MEIPASS
else:
self.root_path = os.path.join(os.path.dirname(os.path.realpath(__file__)))
def _set_app_envs(self):
_envs = os.environ.copy()
_envs['MAYA_SCRIPT_PATH'] = os.path.join(self.root_path, 'scripts').replace('\\', '/')
# Python path envs
_python_path_list = [
'C:\\Program Files\\Autodesk\\Maya2020\\Python\\Lib\\site-packages',
'C:\\Program Files\\Autodesk\\Maya2020\\Python\\DLLs',
os.path.join(self.root_path, 'scripts').replace('\\', '/'),
os.path.join(self.root_path, 'internal_source', 'maya_app')
]
# PYTHONPATH exe vs ide
if getattr(sys, 'frozen', False):
_envs['PYTHONPATH'] = os.pathsep.join(_python_path_list)
_envs['PYTHONHOME'] = 'C:\\Program Files\\Autodesk\\Maya2020\\bin'
else:
_envs['PYTHONPATH'] += os.pathsep + os.pathsep.join(_python_path_list)
return _envs
def on_launch(self):
# Maya file path
file_path_abs = '{}/scenes/test.mb'.format(self.root_path).replace('\\', '/')
print(file_path_abs)
app_exe = r'C:/Program Files/Autodesk/Maya2020/bin/maya.exe'
_envs = self._set_app_envs()
if os.path.exists(file_path_abs):
proc = subprocess.Popen(
[app_exe, file_path_abs],
env=_envs,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
shell=True,
creationflags=subprocess.CREATE_NEW_PROCESS_GROUP
)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
window = Widget()
window.resize(400, 400)
window.show()
sys.exit(app.exec_())
bundle.spec
# -*- mode: python ; coding: utf-8 -*-
block_cipher = None
added_files = [
('./scenes', 'scenes'),
('./scripts', 'scripts')
]
a = Analysis(['launch_maya.py'],
pathex=[
'D:/GitStuff/mb-armada/example_files/exe_bundle',
'D:/GitStuff/mb-armada/dependencies/Qt.py',
'D:/GitStuff/mb-armada/venv/Lib/site-packages',
],
binaries=[],
datas=added_files,
hiddenimports=['internal_source', 'internal_source.maya_app'],
hookspath=[],
runtime_hooks=[],
excludes=[],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher,
noarchive=False)
pyz = PYZ(a.pure, a.zipped_data,
cipher=block_cipher)
exe = EXE(pyz,
a.scripts,
[],
exclude_binaries=True,
name='bundle',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
console=True )
coll = COLLECT(exe,
a.binaries,
a.zipfiles,
a.datas,
strip=False,
upx=True,
upx_exclude=[],
name='bundle')
maya_app.py
import os
import sys
import subprocess
from PySide2 import QtWidgets
class MainWidget(QtWidgets.QWidget):
def __init__(self, parent=None):
super(MainWidget, self).__init__(parent)
# GUI
btn_launch = QtWidgets.QPushButton('say hey')
btn_launch.clicked.connect(self.on_say_hey)
# Layout
main_layout = QtWidgets.QHBoxLayout(self)
main_layout.addWidget(btn_launch)
self.setLayout(main_layout)
print('I should be alive')
def on_say_hey(self):
print('hey')
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
window = MainWidget()
window.resize(100, 100)
window.show()
sys.exit(app.exec_())
userSetup.py
import os
import sys
import maya.cmds as mc
print('hey')
def tweak_launch(*args):
print('Startup sequence running...')
os.environ['mickey'] = '--------ebae--------'
print(os.environ['mickey'])
root_path = os.getenv('_MMM_ROOT_PATH')
main_app_path = os.path.join(root_path, 'internal_source')
if not root_path in sys.path:
sys.path.append(main_app_path)
from internal_source import maya_app
w = maya_app.MainWidget()
w.show()
print('window should be up')
mc.evalDeferred("tweak_launch()")