2

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:

enter image description here

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:

  1. Used subprocess to execute the meshlab filter instead of using meshlabxml library.
  2. Used cx freeze for generating the executable***
kinger
  • 33
  • 7
  • Can you provide filter.mlx and stl files you're working with? It's not crashing by just clicking 'Apply Filter'. – jupiterbjy Jun 20 '20 at 23:02
  • @jupiterbjy I have uploaded files to http://www.filedropper.com/inputs – kinger Jun 20 '20 at 23:09
  • Seems like I also need MeshLab server, it's not crashing with continue option. – jupiterbjy Jun 20 '20 at 23:42
  • Installed Meshlab, but program is not crashing. Output looks fine too. – jupiterbjy Jun 20 '20 at 23:54
  • @jupiterbjy what is the version of PyInstaller that you used? – kinger Jun 21 '20 at 00:00
  • Used cx-freeze 6.2 instead of Pyinstaller. Since cx-freeze is failing to find hidden imports (vtkmodules.all, meshlabxml), had to overwrite copied libraries wtih venv libraries. – jupiterbjy Jun 21 '20 at 00:38
  • Neither PyInstaller nor cx-freeze official release will work with python 3.8+, so you'll need to build library from their github. Not even latest unstable PyInstaller finds PySide2 library months ago, I gave up using PyInstaller. – jupiterbjy Jun 21 '20 at 00:42
  • @jupiterbjy I am using Python 3.6.10 and PyInstaller 3.6. The point I am not able to understand is that the executable generated by PyInstaller is working fine till "Apply Filter" button is pressed. – kinger Jun 21 '20 at 11:00

0 Answers0