Somehow, when I was looking at the PyQtGraph documentation, I noticed that there was a 3D Graphics function in the API. I was curious, so I tried to make a simple GUI application that displays a 3D model in combination with PyQt5.
Since I often use 3D printers, the 3D model here refers to the STL file format.
You can wireframe the STL file by selecting or dragging and dropping the STL file. It is a simple program that displays only one STL file at a time. The code is also on GitHub. GitHub:https://github.com/Be4rR/STLViewer
PyQtGraph is a library for drawing graphs and can be used alone, but you can easily embed the created graph in the GUI made by PyQt. Although its function is weaker than the standard Matplotlib, it is very light and suitable for plotting data in real time. It's a little-known library, but I personally find it useful. Official page: http://www.pyqtgraph.org/ Official documentation: https://pyqtgraph.readthedocs.io/en/latest/index.html
I am using Python3.8, PyQt5, PyQtGraph, PyOpenGL, Numpy, Numpy-STL. PyOpenGL is required to use 3D Graphics functions with PyQtGraph. Also, read the STL file with Numpy-STL.
conda create -n stlviewer python=3.8 pyqt pyqtgraph numpy numpy-stl pyopengl
It's a little long.
from pyqtgraph.Qt import QtCore, QtGui
import pyqtgraph as pg
import pyqtgraph.opengl as gl
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
import numpy as np
from stl import mesh
from pathlib import Path
class MyWindow(QMainWindow):
def __init__(self):
super(MyWindow, self).__init__()
self.setGeometry(0, 0, 700, 900)
self.setAcceptDrops(True)
self.initUI()
self.currentSTL = None
self.lastDir = None
self.droppedFilename = None
def initUI(self):
centerWidget = QWidget()
self.setCentralWidget(centerWidget)
layout = QVBoxLayout()
centerWidget.setLayout(layout)
self.viewer = gl.GLViewWidget()
layout.addWidget(self.viewer, 1)
self.viewer.setWindowTitle('STL Viewer')
self.viewer.setCameraPosition(distance=40)
g = gl.GLGridItem()
g.setSize(200, 200)
g.setSpacing(5, 5)
self.viewer.addItem(g)
btn = QPushButton(text="Load STL")
btn.clicked.connect(self.showDialog)
btn.setFont(QFont("Ricty Diminished", 14))
layout.addWidget(btn)
def showDialog(self):
directory = Path("")
if self.lastDir:
directory = self.lastDir
fname = QFileDialog.getOpenFileName(self, "Open file", str(directory), "STL (*.stl)")
if fname[0]:
self.showSTL(fname[0])
self.lastDir = Path(fname[0]).parent
def showSTL(self, filename):
if self.currentSTL:
self.viewer.removeItem(self.currentSTL)
points, faces = self.loadSTL(filename)
meshdata = gl.MeshData(vertexes=points, faces=faces)
mesh = gl.GLMeshItem(meshdata=meshdata, smooth=True, drawFaces=False, drawEdges=True, edgeColor=(0, 1, 0, 1))
self.viewer.addItem(mesh)
self.currentSTL = mesh
def loadSTL(self, filename):
m = mesh.Mesh.from_file(filename)
shape = m.points.shape
points = m.points.reshape(-1, 3)
faces = np.arange(points.shape[0]).reshape(-1, 3)
return points, faces
def dragEnterEvent(self, e):
print("enter")
mimeData = e.mimeData()
mimeList = mimeData.formats()
filename = None
if "text/uri-list" in mimeList:
filename = mimeData.data("text/uri-list")
filename = str(filename, encoding="utf-8")
filename = filename.replace("file:///", "").replace("\r\n", "").replace("%20", " ")
filename = Path(filename)
if filename.exists() and filename.suffix == ".stl":
e.accept()
self.droppedFilename = filename
else:
e.ignore()
self.droppedFilename = None
def dropEvent(self, e):
if self.droppedFilename:
self.showSTL(self.droppedFilename)
if __name__ == '__main__':
app = QtGui.QApplication([])
window = MyWindow()
window.show()
app.exec_()
It's not too complicated, but I'll explain some key points.
GLViewWidget
Various Graphics Items are listed in the 3D Graphics System of the PyQtGraph documentation.
The first GLViewWidget
is a widget for displaying 3D models and so on. We will add the second and subsequent Graphics Items to this widget. For example, GLGridItem
can be used to add a grid plane, and GLMeshItem
can be used to add mesh data such as STL files. See the official documentation for details.
Since GLViewWidget
can be handled in exactly the same way as a PyQt widget, it can be embedded in the PyQt GUI as it is.
def showSTL(self, filename):
#If another 3D model is already displayed, remove that 3D model.
if self.currentSTL:
self.viewer.removeItem(self.currentSTL)
#Extract vertex points and face faces from the STL file.
points, faces = self.loadSTL(filename)
#A widget that creates a mesh and displays a 3D model(self.viewer)Add to.
meshdata = gl.MeshData(vertexes=points, faces=faces)
mesh = gl.GLMeshItem(meshdata=meshdata, smooth=True, drawFaces=False, drawEdges=True, edgeColor=(0, 1, 0, 1))
self.viewer.addItem(mesh)
self.currentSTL = mesh
The loadSTL
function extracts vertex and face information from the STL file. Both points
and faces
are Numpy arrays, where points
is in the form of(number of vertices, 3)
and faces
is in the form of (number of faces, 3)
.
In the above program, the vertex and face information is passed to MeshData
to create meshdata
, and based on that, gl.GLMeshItem
is created to determine the drawing method (face and side colors, etc.). There are two steps.
Then add the created GLMeshItem
to the GLViewWidget
self.viewer
.
self.viewer.addItem(mesh)
The grid is also the same Graphics Item as GLMeshItem
, so it can be displayed in the same way.
ʻInitUI` function part.
g = gl.GLGridItem()
g.setSize(200, 200)
g.setSpacing(5, 5)
self.viewer.addItem(g)
After creating with GLGridItem ()
, the size is decided with the setSize
function, and the size of one grid is specified with the setSpacing
function. Finally, add it to self.viewer
of GLViewWidget
with the ʻaddItem` function.
Recommended Posts