I created a PyQt5 based application which loads all the 3D objects (*.stl files) from an input directory into a QListWidget. A user can select items from this list and move it to another QListWidget in which he wants to perform any operation (filtering).
I have used vtk renderer to display the selected object. I have also provided a widget to input the filter (*.mlx
) file created from meshlab. Based on all these inputs, when a user clicks on an "Apply Filter" button, my code applies the meshlab filter to the selected objects and displays the object after filter operation in a separate QListWidget.
The UI looks something like this:
The application works as intended when I run it from spyder or from command prompt. Next, I generated an application using PyInstaller. When I open the application, it opens without any issue and I am able to load the objects, add the objects to QListWidget and all other operations. But when I click on "Apply Filter" button, the application stops and gives the following error "QCoreApplication::exec: The event loop is already running".
I am not sure what can be the issue for this as the app is working correctly in the IDE. Following is the code (Sorry code is not clean)
import sys
import os
import vtk
from PyQt5 import Qt, QtCore, QtWidgets
from PyQt5.QtWidgets import QPushButton, QLabel, QLineEdit, QListWidget
from vtk.qt.QVTKRenderWindowInteractor import QVTKRenderWindowInteractor
from PyQt5.QtCore import pyqtSlot
import meshlabxml as mlx
from pathlib import Path
class MainWindow(Qt.QMainWindow):
def __init__(self, parent = None):
Qt.QMainWindow.__init__(self, parent)
cwd = os.getcwd()
self.file_list = []
self.selected_list = []
self.source_dir = os.path.join(cwd, 'inputs')
self.out_dir = os.path.join(cwd, 'outputs')
self.meshlab_dir = Path(r"C:\Program Files\VCG\MeshLab")
self.filter_dir = os.path.join(cwd, 'filter.mlx')
self.cur_filename = os.path.join(cwd, 'sample.stl')
self.initUI()
def initUI(self):
self.frame = Qt.QFrame()
self.setWindowState(QtCore.Qt.WindowMaximized)
self.setWindowTitle("Mesh Processor")
lbl_inpdir = QLabel('STL input directory')
self.edt_inpdir = QLineEdit()
btn_load = QPushButton('Load')
self.lbl_load = QLabel('')
btn_add = QPushButton('>>')
btn_remove = QPushButton('<<')
self.load_lst = QListWidget()
self.select_lst = QListWidget()
lbl_outdir = QLabel('STL output directory')
self.edt_outdir = QLineEdit()
lbl_meshlab = QLabel('Meshlab Server Path')
self.edt_meshlab = QLineEdit()
lbl_filter = QLabel('Filter (MLX)')
self.edt_filter = QLineEdit()
btn_filter = QPushButton('Apply Filter')
self.upsampled_lst = QListWidget()
self.edt_inpdir.setText(str(self.source_dir))
self.edt_outdir.setText(str(self.out_dir))
self.edt_meshlab.setText(str(self.meshlab_dir))
self.edt_filter.setText(str(self.filter_dir))
btn_load.clicked.connect(self.on_load)
btn_add.clicked.connect(self.on_add)
btn_remove.clicked.connect(self.on_remove)
btn_filter.clicked.connect(self.on_applyfilter)
self.load_lst.itemClicked.connect(self.on_loadlst)
self.select_lst.itemClicked.connect(self.on_selectlst)
self.upsampled_lst.itemClicked.connect(self.on_upsampledlst)
layout_final = Qt.QHBoxLayout()
layout_left = Qt.QVBoxLayout()
layout_left_bottom = Qt.QVBoxLayout()
layout_inp = Qt.QHBoxLayout()
layout_inp.addWidget(lbl_inpdir)
layout_inp.addWidget(self.edt_inpdir)
layout_loadinp = Qt.QHBoxLayout()
layout_loadinp.addWidget(btn_load)
layout_loadinp.addWidget(self.lbl_load)
layout_btns = Qt.QVBoxLayout()
layout_btns.addWidget(btn_add)
layout_btns.addWidget(btn_remove)
layout_lstbox = Qt.QHBoxLayout()
layout_lstbox.addWidget(self.load_lst)
layout_lstbox.addLayout(layout_btns)
layout_lstbox.addWidget(self.select_lst)
layout_out = Qt.QHBoxLayout()
layout_out.addWidget(lbl_outdir)
layout_out.addWidget(self.edt_outdir)
layout_meshlab = Qt.QHBoxLayout()
layout_meshlab.addWidget(lbl_meshlab)
layout_meshlab.addWidget(self.edt_meshlab)
layout_filter = Qt.QHBoxLayout()
layout_filter.addWidget(lbl_filter)
layout_filter.addWidget(self.edt_filter)
layout_left_bottom.addLayout(layout_out)
layout_left_bottom.addLayout(layout_meshlab)
layout_left_bottom.addLayout(layout_filter)
layout_left_bottom.addWidget(btn_filter)
layout_left_bottom.addWidget(self.upsampled_lst)
layout_left.addLayout(layout_inp)
layout_left.addLayout(layout_loadinp)
layout_left.addLayout(layout_lstbox)
layout_left.addLayout(layout_left_bottom)
#layout_left.addWidget(self.txtedit_lst)
layout_right = Qt.QVBoxLayout()
self.lbl_filename = QLabel(self.cur_filename)
vtkWidget = QVTKRenderWindowInteractor(self.frame)
layout_right.addWidget(self.lbl_filename)
layout_right.addWidget(vtkWidget)
layout_final.addLayout(layout_left)
layout_final.addLayout(layout_right)
self.ren = vtk.vtkRenderer()
vtkWidget.GetRenderWindow().AddRenderer(self.ren)
self.iren = vtkWidget.GetRenderWindow().GetInteractor()
filename2 = "sample.stl"
self.reader = vtk.vtkSTLReader()
self.reader.SetFileName(filename2)
self.mapper = vtk.vtkPolyDataMapper()
self.mapper.SetInputConnection(self.reader.GetOutputPort())
# Create an actor
self.actor = vtk.vtkActor()
self.actor.SetMapper(self.mapper)
self.ren.AddActor(self.actor)
self.ren.ResetCamera()
self.frame.setLayout(layout_final)
self.setCentralWidget(self.frame)
self.show()
self.iren.Initialize()
self.iren.Start()
@pyqtSlot()
def on_load(self):
self.load_lst.clear()
self.select_lst.clear()
self.source_dir = Path(self.edt_inpdir.text())
for root, dirs, files in os.walk(self.source_dir):
for file in files:
if file.endswith(".stl"):
self.file_list.append(os.path.join(root, file))
self.lbl_load.setText(str(len(self.file_list))+ ' files loaded from the source directory')
self.load_lst.addItems(self.file_list)
self.load_lst.setCurrentRow(0)
self.cur_filename = self.file_list[0]
self.lbl_filename.setText(self.cur_filename)
self.reader.SetFileName(self.cur_filename)
self.mapper = vtk.vtkPolyDataMapper()
self.mapper.SetInputConnection(self.reader.GetOutputPort())
# Create an actor
self.actor = vtk.vtkActor()
self.actor.SetMapper(self.mapper)
self.ren.AddActor(self.actor)
self.ren.ResetCamera()
self.setCentralWidget(self.frame)
self.show()
self.iren.Initialize()
self.iren.Start()
@pyqtSlot()
def on_add(self):
self.select_lst.addItem(self.load_lst.currentItem().text())
self.file_list.remove(self.load_lst.currentItem().text())
self.selected_list.append(self.load_lst.currentItem().text())
self.load_lst.takeItem(self.load_lst.currentRow())
@pyqtSlot()
def on_remove(self):
self.load_lst.addItem(self.select_lst.currentItem().text())
self.file_list.append(self.select_lst.currentItem().text())
self.selected_list.remove(self.select_lst.currentItem().text())
self.select_lst.takeItem(self.select_lst.currentRow())
@pyqtSlot()
def on_loadlst(self):
#print(self.load_lst.currentItem().text())
self.cur_filename = self.load_lst.currentItem().text()
self.lbl_filename.setText(self.cur_filename)
self.reader.SetFileName(self.cur_filename)
self.mapper = vtk.vtkPolyDataMapper()
self.mapper.SetInputConnection(self.reader.GetOutputPort())
# Create an actor
self.actor = vtk.vtkActor()
self.actor.SetMapper(self.mapper)
self.ren.AddActor(self.actor)
self.ren.ResetCamera()
self.setCentralWidget(self.frame)
self.show()
self.iren.Initialize()
self.iren.Start()
@pyqtSlot()
def on_selectlst(self):
#print(self.select_lst.currentItem())
self.cur_filename = self.select_lst.currentItem().text()
self.lbl_filename.setText(self.cur_filename)
self.reader.SetFileName(self.cur_filename)
self.mapper = vtk.vtkPolyDataMapper()
self.mapper.SetInputConnection(self.reader.GetOutputPort())
# Create an actor
self.actor = vtk.vtkActor()
self.actor.SetMapper(self.mapper)
self.ren.AddActor(self.actor)
self.ren.ResetCamera()
self.setCentralWidget(self.frame)
self.show()
self.iren.Initialize()
self.iren.Start()
@pyqtSlot()
def on_applyfilter(self):
self.outdir = self.edt_outdir.text()
self.filter_dir = self.edt_filter.text()
self.meshlab_dir = self.edt_meshlab.text()
for item in self.selected_list:
#apply_filter(self.meshlab_dir, self.filter_dir, item, self.outdir)
MESHLABSERVER_PATH = self.meshlab_dir
os.environ['PATH'] += os.pathsep + MESHLABSERVER_PATH
script = Path(self.filter_dir)
input_file = Path(item)
input_path, filename = os.path.split(input_file)
output_path = Path(self.outdir)
output_file = output_path / filename
log_file = 'log.txt'
mlx.run(script = script, log=log_file, file_in=input_file, file_out=output_file)
input_file = Path(item)
input_path, filename = os.path.split(input_file)
output_path = Path(self.outdir)
output_file = output_path / filename
self.upsampled_lst.addItem(str(output_file))
@pyqtSlot()
def on_upsampledlst(self):
#print(self.load_lst.currentItem().text())
self.cur_filename = self.upsampled_lst.currentItem().text()
self.lbl_filename.setText(self.cur_filename)
self.reader.SetFileName(self.cur_filename)
self.mapper = vtk.vtkPolyDataMapper()
self.mapper.SetInputConnection(self.reader.GetOutputPort())
# Create an actor
self.actor = vtk.vtkActor()
self.actor.SetMapper(self.mapper)
self.ren.AddActor(self.actor)
self.ren.ResetCamera()
self.setCentralWidget(self.frame)
self.show()
self.iren.Initialize()
self.iren.Start()
if __name__ == '__main__':
if not QtWidgets.QApplication.instance():
app = QtWidgets.QApplication(sys.argv)
else:
app = QtWidgets.QApplication.instance()
app.setStyle('Fusion')
main = MainWindow()
main.show()
app.exec_()
***UPDATE: Filter file and sample inputs can be downloaded from http://www.filedropper.com/inputs ***
***UPDATE: I was able to resolve it using these:
- Used subprocess to execute the meshlab filter instead of using meshlabxml library.
- Used cx freeze for generating the executable***