I have created a python script which is dependant on the library speedtest-cli. Now I am packaging it to an executable. However I dont want to create a overhead to all the users using my script having to install speedtest-cli. How do I specify that in setup.py file so that speedtest-cli will automatically be installed while installing my exe.
And I am making it a standalone application.
from cx_Freeze import setup,Executable
import sys
includes = ["speedtest-cli"]
setup(name = "Bandwithtest",
version = "1.0",
options = {"build_exe" : {"includes" : includes}},
executables = [Executable("networktest.py")]
Adding the module in includes doesnt work.It raises an import error.
ImportError : No module named 'speedtest-cli'
I have 2 questions
How do I ensure speedtest-cli installs when users install my application?
I have also imported speedtest-cli in my module though I dont use it in my program. Ideally cx_freeze automatically bundles all the imported modules. But it didnt do so for speedtest-cli.Why is that so?
Here is the script am trying to freeze.
class Net(threading.Thread):
def __init__(self, interval):
threading.Thread.__init__(self)
self.interval = interval
def run(self):
self.main()
def main(self,interval):
try:
while thread1.is_alive():
p = subprocess.Popen(r'speedtest-cli',
stdout = PIPE,
stderr = PIPE) #--> This is definitely required
except KeyboardInterrupt as e:
print "Exiting", e
class UI:
def __init__(self,* args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.wm_title("Network Test")
self.geometry('250x75')
self.resizable(width = False, height = False)
self.label = tk.Label(text = "Frequency(seconds)")
self.label.grid(row = 0, column = 0)
self.entry = tk.Entry(bd = 5)
self.entry.grid(row = 0, column = 1)
self.button = tk.Button(text = "Start Test", command = self.callthread)
self.button.grid(row = 1, column = 0)
self.button1 = tk.Button(text = "Stop Test")
self.button1.grid(row = 1, column = 1)
self.mainloop()
def callthread(self):
thread2 = Net(self.entry.get())
thread2.start()
if __name__ == "__main__":
thread1 = threading.Thread(target = MyUI)
thread1.start()
Thanks in Advance.
Related
PyQt4/QProcess issues with Nuke v9...
I am trying to utilize a QProcess to run renders in Nuke at my workplace. The reason why I want to use a QProcess is because I've setup this Task Manager with the help of the community at stackoverflow, which takes a list of commands and sequentially runs it one by one, and also allows me to display an output. You can view the question I posted here:
How to update UI with output from QProcess loop without the UI freezing?
Now I am trying to basically run Nuke renders through this "Task Manager". But every time I do it just gives me an error that the QProcess is destroyed while still running. I mean I tested this with subprocess and that worked totally fine. So i am not sure why the renders are not working through QProcess.
So to do more testing I just wrote a simplified version at home. The first issue I ran into though is that apparently PyQt4 couldn't be found from Nuke's python.exe. Even though I have PyQt4 for my main Python version. However apparently there is a compatibility issue with my installed PyQt4 since my main Python version is 2.7.12, while my Nuke's python version is 2.7.3. So i thought "fine then i'll just directly install PyQt4 inside my Nuke directory". So i grabbed this link and installed this PyQt version into my Nuke directory:
http://sourceforge.net/projects/pyqt/files/PyQt4/PyQt-4.10.3/PyQt4-4.10.3-gpl-Py2.7-Qt4.8.5-x64.exe
So i run my little test and seems to be doing the same thing as it does in my workplace, where the QProcess just gets destoryed. So i thought maybe adding "waitForFinished()" would maybe do something different, but then it gives me this error that reads:
The procedure entry point ??4QString##QEAAAEAV0#$$QEAV0##Z could not be located in the dynamic link library QtCore4.dll
And gives me this error as well:
ImportError: Failed to load C:\Program Files\Nuke9.0v8\nuke-9.0.8.dll
Now at this point I can't really do any more testing at home, and my studio is closed for the holidays. So i have two questions i'd like to ask:
1) What is this error I am seeing about "procedure entry point"? It only happens when i try to call something in a QProcess instance.
2) Why is my QProcess being destroyed before the render is finished?? How come this doesn't happen with subprocess? How can I submit a Nuke render job while acheiving the same results as subprocess?
Here is my test code:
import os
import sys
import subprocess
import PyQt4
from PyQt4 import QtCore
class Task:
def __init__(self, program, args=None):
self._program = program
self._args = args or []
#property
def program(self):
return self._program
#property
def args(self):
return self._args
class SequentialManager(QtCore.QObject):
started = QtCore.pyqtSignal()
finished = QtCore.pyqtSignal()
progressChanged = QtCore.pyqtSignal(int)
dataChanged = QtCore.pyqtSignal(str)
#^ this is how we can send a signal and can declare what type
# of information we want to pass with this signal
def __init__(self, parent=None):
# super(SequentialManager, self).__init__(parent)
# QtCore.QObject.__init__(self,parent)
QtCore.QObject.__init__(self)
self._progress = 0
self._tasks = []
self._process = QtCore.QProcess(self)
self._process.setProcessChannelMode(QtCore.QProcess.MergedChannels)
self._process.finished.connect(self._on_finished)
self._process.readyReadStandardOutput.connect(self._on_readyReadStandardOutput)
def execute(self, tasks):
self._tasks = iter(tasks)
#this 'iter()' method creates an iterator object
self.started.emit()
self._progress = 0
self.progressChanged.emit(self._progress)
self._execute_next()
def _execute_next(self):
try:
task = next(self._tasks)
except StopIteration:
return False
else:
print 'starting %s' % task.args
self._process.start(task.program, task.args)
return True
def _on_finished(self):
self._process_task()
if not self._execute_next():
self.finished.emit()
def _on_readyReadStandardOutput(self):
output = self._process.readAllStandardOutput()
result = output.data().decode()
self.dataChanged.emit(result)
def _process_task(self):
self._progress += 1
self.progressChanged.emit(self._progress)
class outputLog(QtCore.QObject):
def __init__(self, parent=None, parentWindow=None):
QtCore.QObject.__init__(self)
self._manager = SequentialManager(self)
def startProcess(self, tasks):
# self._manager.progressChanged.connect(self._progressbar.setValue)
self._manager.dataChanged.connect(self.on_dataChanged)
self._manager.started.connect(self.on_started)
self._manager.finished.connect(self.on_finished)
self._manager.execute(tasks)
#QtCore.pyqtSlot()
def on_started(self):
print 'process started'
#QtCore.pyqtSlot()
def on_finished(self):
print 'finished'
#QtCore.pyqtSlot(str)
def on_dataChanged(self, message):
if message:
print message
def nukeTestRender():
import nuke
nuke.scriptOpen('D:/PC6/Documents/nukeTestRender/nukeTestRender.nk')
writeNode = None
for node in nuke.allNodes():
if node.Class() == 'Write':
writeNode = node
framesList = [1, 20, 30, 40]
fr = nuke.FrameRanges(framesList)
# nuke.execute(writeNode, fr)
for x in range(20):
print 'random'
def run():
nukePythonEXE = 'C:/Program Files/Nuke9.0v8/python.exe'
thisFile = os.path.dirname(os.path.abspath("__file__"))
print thisFile
cmd = '"%s" %s renderCheck' %(nukePythonEXE, __file__)
cmd2 = [__file__, 'renderCheck']
cmdList = [Task(nukePythonEXE, cmd2)]
# subprocess.call(cmd, stdin=None, stdout=None, stderr=None, shell=False)
taskManager = outputLog()
taskManager.startProcess(cmdList)
taskManager._manager._process.waitForFinished()
if __name__ == "__main__":
print sys.argv
if len(sys.argv) == 1:
run()
elif len(sys.argv) == 2:
nukeTestRender()
I have managed to come up with an answer, so I will write in the details below:
Basically, I was getting the error with the installed PyQt4 because it was not compatible with my version of Nuke, so it is apparently more recommended to use PySide included in Nuke. However Nuke's Python executable cannot natively find PySide, a few paths needed to be added to the sys.path:
paths = ['C:\\Program Files\\Nuke9.0v8\\lib\\site-packages,
C:\\Users\\Desktop02\\.nuke',
'C:\\Program Files\\Nuke9.0v8\\plugins',
'C:\\Program Files\\Nuke9.0v8\\pythonextensions\\site-packages\\setuptools-0.6c11-py2.6.egg',
'C:\\Program Files\\Nuke9.0v8\\pythonextensions\\site-packages\\protobuf-2.5.0-py2.6.egg',
'C:\\Program Files\\Nuke9.0v8\\pythonextensions\\site-packages',
'C:\\Program Files\\Nuke9.0v8\\plugins\\modules',
'C:\\Program Files\\Nuke9.0v8\\configs\\Python\\site-packages',
'C:\\Users\\Desktop02\\.nuke\\Python\\site-packages']
for path in paths:
sys.path.append(path)
I found the missing paths by opening up both Nuke in GUI mode and the Python executable, and comparing both sys.path to see what the Python executable was lacking.
And to answer my own main question: if I call waitForFinished(-1) on the QProcess instance, this ignores the default 30sec limit of this function... Answer came from this thread:
QProcess and shell : Destroyed while process is still running
So here is my resulting working code:
import os
import sys
import subprocess
sysArgs = sys.argv
try:
import nuke
from PySide import QtCore
except ImportError:
raise ImportError('nuke not currently importable')
class Task:
def __init__(self, program, args=None):
self._program = program
self._args = args or []
#property
def program(self):
return self._program
#property
def args(self):
return self._args
class SequentialManager(QtCore.QObject):
started = QtCore.Signal()
finished = QtCore.Signal()
progressChanged = QtCore.Signal(int)
dataChanged = QtCore.Signal(str)
#^ this is how we can send a signal and can declare what type
# of information we want to pass with this signal
def __init__(self, parent=None):
# super(SequentialManager, self).__init__(parent)
# QtCore.QObject.__init__(self,parent)
QtCore.QObject.__init__(self)
self._progress = 0
self._tasks = []
self._process = QtCore.QProcess(self)
self._process.setProcessChannelMode(QtCore.QProcess.MergedChannels)
self._process.finished.connect(self._on_finished)
self._process.readyReadStandardOutput.connect(self._on_readyReadStandardOutput)
def execute(self, tasks):
self._tasks = iter(tasks)
#this 'iter()' method creates an iterator object
self.started.emit()
self._progress = 0
self.progressChanged.emit(self._progress)
self._execute_next()
def _execute_next(self):
try:
task = next(self._tasks)
except StopIteration:
return False
else:
print 'starting %s' % task.args
self._process.start(task.program, task.args)
return True
def _on_finished(self):
self._process_task()
if not self._execute_next():
self.finished.emit()
def _on_readyReadStandardOutput(self):
output = self._process.readAllStandardOutput()
result = output.data().decode()
self.dataChanged.emit(result)
def _process_task(self):
self._progress += 1
self.progressChanged.emit(self._progress)
class outputLog(QtCore.QObject):
def __init__(self, parent=None, parentWindow=None):
QtCore.QObject.__init__(self)
self._manager = SequentialManager(self)
def startProcess(self, tasks):
# self._manager.progressChanged.connect(self._progressbar.setValue)
self._manager.dataChanged.connect(self.on_dataChanged)
self._manager.started.connect(self.on_started)
self._manager.finished.connect(self.on_finished)
self._manager.execute(tasks)
#QtCore.Slot()
def on_started(self):
print 'process started'
#QtCore.Slot()
def on_finished(self):
print 'finished'
#QtCore.Slot(str)
def on_dataChanged(self, message):
if message:
print message
def nukeTestRender():
import nuke
nuke.scriptOpen('D:/PC6/Documents/nukeTestRender/nukeTestRender.nk')
writeNode = None
for node in nuke.allNodes():
if node.Class() == 'Write':
writeNode = node
framesList = [1, 20, 30, 40]
fr = nuke.FrameRanges(framesList)
nuke.execute(writeNode, fr)
# nuke.execute(writeNode, start=1, end=285)
for x in range(20):
print 'random'
def run():
nukePythonEXE = 'C:/Program Files/Nuke9.0v8/python.exe'
thisFile = os.path.dirname(os.path.abspath("__file__"))
print thisFile
cmd = '"%s" %s renderCheck' %(nukePythonEXE, sysArgs[0])
cmd2 = [sysArgs[0], 'renderCheck']
cmdList = [Task(nukePythonEXE, cmd2)]
# subprocess.call(cmd, stdin=None, stdout=None, stderr=None, shell=False)
taskManager = outputLog()
taskManager.startProcess(cmdList)
taskManager._manager._process.waitForFinished(-1)
if __name__ == "__main__":
print sys.argv
if len(sysArgs) == 1:
run()
elif len(sysArgs) == 2:
nukeTestRender()
For whatever reason, PySide refuses to load for me without the nuke module imported first. and also theres a known error when importing nuke it deletes all sys.argv arguments so thats gotta be stored somewhere first before the nuke import...
Ok, i have a python file inside my project with only this class:
class hd_XML():
def __init__(self):
self.path = 'static/XML/current/'
self.filename = 'current_settings.xml'
self.tree = ''
def open(self):
self.tree = ET.parse(self.path + self.filename)
self.root = self.tree.getroot()
return self.root
def get_data(self):
self.root = self.open()
canale = Channel
canali = []
i = 0
for child in self.root:
canale.id = child.attrib['id']
canale.max = child.attrib['max']
canale.color = child.attrib['color']
canali.append(canale)
i += 1
return canali
if i run this class standalone with:
if __name__ == '__main__':
xml = hd_XML()
print(xml.get_data())
that works. But, if I import this class in my main app file as below,
import hd_modXML #thats my separate file name
xml = hd_modXML.hd_XML()
canali = xml.get_data()
print(canali[0].id)
I cannot retrive the file...
FileNotFoundError: [Errno 2] No such file or directory: 'static/XML/current/current_settings.xml'
Why?! with a standalone file I can find it and after an import I can't?
project structure:
main folder <--- where app.py (where is included hd_modXML.py) and hd_modXML.py are
|_static
|_XML
|_current\ <-- where current_settings.xml is
|_templates
After some tries I found that it works giving the parser the whole directory path from root, so in my case:
self.path = '/home/grace/pyDev/prova_horus2/static/XML/current/'
I don't know why with prievious versions it worked without...
Now I need a way to avoid hardcoding the root path, but for this I can help myself.
Many thanks to yklsga for pointing me to the right way
I'm learning about GUI python using pyQt4. I have function A in another file python. and I want to run in GUI file python that I extracted from file .ui (output of designer pyQt4). How to create activity indicator which is active when the function A is running? can I use progress bar (in pyQt4 designer) without know how many time for my function A running?
Thank you.
this is the function to call A in GUI .py:
def RunFunction():
import Kdtree
_dir = kdTreeOk.getNeighbor(float(radius)) #function 'A'
file = file_open('Summary.txt',_dir) # ignore, just file to save result of `A`
with file:
textOutput=file.read()
ui.result.setPlainText(textOutput)
#### button to run RunFunction in file GUI .py
ui._run.clicked.connect(RunFunction)
QProgressDialog is made for this purpose and generally called via QThread. Here's a (messy) basic example to show how this can work (without any threading). If you are calling this dialog from another window, just set parent as the calling window and you can read attributes in this dialog by calling self.parent.some_variable.
EDITED to work properly ;).
from PyQt4 import QtCore, QtGui
from time import sleep
import sys
class ProgressBarWidget(QtGui.QProgressDialog):
def __init__(self, parent=None, app=None):
super(ProgressBarWidget, self).__init__(parent)
self.app=app
self._allow_close = True
layout = QtGui.QVBoxLayout(self)
# Create a progress bar and a button and add them to the main layout
self.progressBar = QtGui.QProgressBar(self)
self.progressBar.setRange(0,100)
layout.addWidget(self.progressBar)
self.button = QtGui.QPushButton("Start", self)
layout.addWidget(self.button)
self.button.clicked.connect(self.onStart)
self.upload_count = 10
def onStart(self):
self.progressBar.setValue(0)
self.button.setText("Uploading...")
self.run()
def makeProgress(self, current_num, total_num, message = ''):
if total_num == current_num:
self.onFinished()
elif current_num == 0:
self.progressBar.setValue(0)
else:
multiplier = int(float(float(100) / float(total_num)))
c_times_m = current_num * multiplier
for i in xrange(c_times_m - int(self.progressBar.value())):
new_val = int(self.progressBar.value()) + 1
self.progressBar.setValue(new_val)
sleep(.01)
def onFinished(self):
# progress complete
self.progressBar.setRange(0,100)
for i in xrange(int(self.progressBar.value()),101):
self.progressBar.setValue(i)
self.button.setEnabled(True)
self.button.setText('Exit')
self.button.clicked.disconnect(self.onStart)
self.button.clicked.connect(self.close)
def run(self):
self._allow_close = False
self.button.setDisabled(True)
total = self.upload_count * 2
progress_meter = 0
downloaded = []
tests_to_upload = 10
for each in xrange(tests_to_upload):
sleep(0.15)
progress_meter += 1
self.makeProgress(progress_meter,total)
sleep(0.2)
progress_meter += 1
self.makeProgress(progress_meter, total)
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
window = ProgressBarWidget(app=app)
window.resize(640, 480)
window.show()
sys.exit(app.exec_())
I am developing an application using PySide (which wraps the Qt 4.8 framework). I need to display icons of applications associated with certain file extensions, and I am using QFileIconProvider for this. On Windows, my code works perfectly - every file extension is displayed along with the icon of the appropriate application. However, on Linux Ubuntu (14.04.1), the same code displays the icon for an unknown file extension for all the file extensions I try.
Anyone know why this is happening? Here is the code:
from PySide import QtCore, QtGui
# Use the appropriate path module when running on Windows.
path = None
import os
if os.name == "nt":
path = __import__("ntpath")
else:
path = os.path
class MyWidget(QtGui.QWidget):
def __init__(self, *args, **kwargs):
# ...
# ...
self.file_ext_to_icon = {}
def get_icon(self, fpath):
_, file_ext = path.splitext(fpath)
file_ext = file_ext.replace(".", "")
if file_ext in self.file_ext_to_icon:
return self.file_ext_to_icon[file_ext]
if path.exists(fpath):
icon = QtGui.QFileIconProvider().icon(QtCore.QFileInfo(fpath))
else:
temp_fpath = path.join(os.getcwd(), "myappname_temp.%s" % file_ext)
if not path.exists(temp_fpath):
with open(temp_fpath, "wb") as _:
pass
icon = QtGui.QFileIconProvider().icon(QtCore.QFileInfo(temp_fpath))
os.remove(temp_fpath)
if icon.isNull():
# Use a custom default file icon from the resources file. Note that
# this is different from the default file icon given by the OS.
icon = QtGui.QIcon(":images/default_file_icon.png")
self.file_ext_to_icon[file_ext] = icon
return icon
I had the same problem, and after much digging here is what I ended up doing...
class IconProvider(QtWidgets.QFileIconProvider):
def __init__(self):
super().__init__()
self.mimeDatabase = QtCore.QMimeDatabase()
def icon(self, info: QtCore.QFileInfo):
mimeType = self.mimeDatabase.mimeTypeForFile(info)
return QtGui.QIcon.fromTheme(mimeType.iconName())
...used in my class
self.filesModel = QtWidgets.QFileSystemModel(self.filesView)
self.filesModel.setIconProvider(IconProvider())
...or in your case
icon = IconProvider().icon(QtCore.QFileInfo(fpath))
Please help me !
I'm creating GUI by Python can run on the 3Ds Max, i heard someone said i have to use Pyside to make it. And everthing be fine until now.
This is my code :
import sys
from PySide import QtGui
from PySide.QtGui import *
from PySide.QtCore import *
class Window(QDialog):
def __init__(self, parent=None):
super(Window, self).__init__(parent)
self.setMinimumHeight(660)
self.setMinimumWidth(700)
self.setMaximumHeight(660)
self.setMaximumWidth(700)
grid = QtGui.QGridLayout()
grid.addWidget(self.First(), 0,0,2,0)
self.setLayout(grid)
self.setWindowTitle("Library")
self.resize(700, 660)
def First(self):
groupBox = QtGui.QFrame()
groupBox.setMaximumWidth(230)
groupBox.setMaximumHeight(700)
lbRenderer = QtGui.QLabel("Renderer :",self)
lbFolders = QtGui.QLabel("Folders :",self)
cbRenderer = QtGui.QComboBox(self)
cbRenderer.addItem("Vray")
cbRenderer.addItem("Octane")
lvFolders = QtGui.QListView(self)
lvFolders.setMaximumWidth(220)
lvFolders.setMaximumHeight(500)
btnAddNewObject = QtGui.QPushButton('Add New Objects',self)
btnNewSet = QtGui.QPushButton('New Set',self)
vbox = QtGui.QGridLayout()
vbox.addWidget(lbRenderer,0,0)
vbox.addWidget(cbRenderer,0,1,1,3)
vbox.addWidget(lbFolders,2,0,1,4)
vbox.addWidget(lvFolders,3,0,1,4)
vbox.setColumnStretch(1, 1)
vbox.addWidget(btnAddNewObject,4,0,1,2)
vbox.addWidget(btnNewSet,4,3)
groupBox.setLayout(vbox)
return groupBox
app = QApplication.instance()
if app is None:
app = QApplication(sys.argv)
clock = Window()
clock.show()
app.exec_()
I try another code same like my code , it run fine by "MAXScript Listener". But I dont know why when i try to run this, it dont appear anything(my GUI, or Alert is my code is not good).
First of all - you are initializing your script wrong, you call the 'initialize' function which returns #Success (meaning python initialized properly),
however you then just send in a string (which is the path to the file) and this does nothing.
What you have to use is:
python.ExecuteFile "C:\\Program Files\\Autodesk\\3ds Max 2015\\scripts\\Python\\yourPythonScript.py"
in maxscript listener\editor.
Autodesk documentation says:
Autodesk 3ds Max ships with a pre-built version of PySide 1.2
compatible with Python 2.7.3. This version includes the following
sub-set of modules:
QtCore
QtGui
QtNetwork
QtOpenGL
QtSql
QtSvg
QtTest
QtWebKit
QtXml
They have provided a simple sample script that you can run, save this in a python file, then execute it properly with the command mentioned in the beginning.
The code is here:
from PySide import QtGui
import MaxPlus
class _GCProtector(object):
widgets = []
def make_cylinder():
obj = MaxPlus.Factory.CreateGeomObject(MaxPlus.ClassIds.Cylinder)
obj.ParameterBlock.Radius.Value = 10.0
obj.ParameterBlock.Height.Value = 30.0
node = MaxPlus.Factory.CreateNode(obj)
time = MaxPlus.Core.GetCurrentTime()
MaxPlus.ViewportManager.RedrawViews(time)
return
app = QtGui.QApplication.instance()
if not app:
app = QtGui.QApplication([])
def main():
MaxPlus.FileManager.Reset(True)
w = QtGui.QWidget()
w.resize(250, 100)
w.setWindowTitle('Window')
_GCProtector.widgets.append(w)
w.show()
main_layout = QtGui.QVBoxLayout()
label = QtGui.QLabel("Click button to create a cylinder in the scene")
main_layout.addWidget(label)
cylinder_btn = QtGui.QPushButton("Cylinder")
main_layout.addWidget(cylinder_btn)
w.setLayout(main_layout)
cylinder_btn.clicked.connect(make_cylinder)
if __name__ == '__main__':
main()
They also mention this which is important:
Normally one creates a PySide application object in a script using
QtGui.QApplication(). However, in 3ds Max, there is already a PySide
application running, so you get a handle for that object like this:
QtGui.QApplication.instance()
Use that as a start script, and port your GUI items into that and it should get you up and running.
I tried to fix your code but anything happen, i dont know why.
First thing , i opened your code and run it in Pycharm but it can not run. But it totally run in Maxscript Listener, could you explain to me ?
Second i tried to fix your code. It's all the same, i can run it on Maxscript, but the content and function inside is disappear.
This is my code
from PySide import QtGui
import MaxPlus
class _GCProtector(object):
widgets = []
app = QtGui.QApplication.instance()
if not app:
app = QtGui.QApplication([])
def main():
MaxPlus.FileManager.Reset(True)
w = QtGui.QWidget()
w.setWindowTitle('Window')
_GCProtector.widgets.append(w)
w.show()
main_layout = QtGui.QGridLayout()
main_layout.addWidget(First(),0,0)
main_layout.addWidget(Second(),0,1)
w.setLayout(main_layout)
def First():
groupBox = QtGui.QFrame()
lbRenderer = QtGui.QLabel("Renderer :",self)
vbox = QtGui.QGridLayout()
vbox.addWidget(lbRenderer,0,0)
groupBox.setLayout(vbox)
return groupBox
def Second():
groupBox = QtGui.QFrame()
lbRenderer = QtGui.QLabel("Renderer :",self)
vbox = QtGui.QGridLayout()
vbox.addWidget(lbRenderer,0,0)
groupBox.setLayout(vbox)
return groupBox
if __name__ == '__main__':
main()
And this is the alert from Maxcript