PyVista/Trimesh

Conversion

Pyvista to Trimesh

Convert Pyvista 1-D face array to Trimesh faces

def pyvistaToTrimeshFaces(cells):
    faces = []
    idx = 0
    while idx < len(cells):
      curr_cell_count = cells[idx]
      curr_faces = cells[idx+1:idx+curr_cell_count+1]
      faces.append(curr_faces)
      idx += curr_cell_count+1
    return np.array(faces)

Reference: AbrarAnwar/cross_section_rl/utils.py

Meshlab to Pyvista

import numpy as np
import pyvista as pv

def meshlab2pv(mmesh):
    '''
    Convert meshlab mesh to PyVista polydata
    '''
    mpoints, mcells = mmesh.vertex_matrix(), mmesh.face_matrix()

    if len(mcells):
        # convert the faces into PolyData format
        mfaces = []
        for cell in mcells:
            face = np.hstack((len(cell), cell))
            mfaces.extend(face.tolist())
        mfaces = np.array(mfaces)
        polydata = pv.PolyData(mpoints, np.hstack(mfaces))
    else:
        polydata = pv.PolyData(mpoints, None)

    return polydata

if __name__ == '__main__':
    import pymeshlab as pm
    
    ms = pm.MeshSet()
    ms.load_project('data/test.mlp')

    print(ms.print_status())

    meshes = []
    plotter = pv.Plotter()

    for i in range(ms.number_meshes()):
        mesh_pv = meshlab2pv(ms.mesh(i))
        meshes.append(mesh_pv)
        plotter.add_mesh(mesh_pv)

    plotter.add_mesh_slice_orthogonal(meshes[0])

    plotter.show()

Qt GUI

Pyvistaqt Example:

import sys

from PyQt5 import Qt, QtCore

import pyvista as pv
from pyvistaqt import QtInteractor

# from pyvista import themes
# pv.set_plot_theme(themes.DarkTheme())


class MainWidget(Qt.QWidget):

    def __init__(self, parent=None, show=True):
        super(MainWidget, self).__init__()

        self.test_button = Qt.QPushButton("Open")
        self.test_button.clicked.connect(self.test_button_event)

        self.frame = Qt.QFrame()
        self.plotter = QtInteractor(self.frame)
        vlayout = Qt.QVBoxLayout()
        vlayout.addWidget(self.plotter.interactor)
        hlayout = Qt.QHBoxLayout()
        hlayout.addWidget(self.test_button)
        vlayout.addLayout(hlayout)
        self.setLayout(vlayout)

        self.setWindowTitle("Test Qt Window")
        self.setGeometry(550, 200, 800, 600)

        # Enable dragging and dropping onto the GUI
        self.setAcceptDrops(True)

        self.plotter.show_axes()
        self.mesh = None

        if show:
            self.show()

    # The following three methods set up dragging and dropping for the app
    def dragEnterEvent(self, e):
        if e.mimeData().hasUrls:
            e.accept()
        else:
            e.ignore()

    def dragMoveEvent(self, e):
        if e.mimeData().hasUrls:
            e.accept()
        else:
            e.ignore()

    def dropEvent(self, e):
        """
        Drop files directly onto the widget
        File locations are stored in fname
        :param e:
        :return:
        """
        if e.mimeData().hasUrls:
            e.setDropAction(QtCore.Qt.CopyAction)
            e.accept()
            # Workaround for OSx dragging and dropping
            for url in e.mimeData().urls():
                fname = str(url.toLocalFile())
            self.fname = fname
            self.load_mesh()
        else:
            e.ignore()

    def test_button_event(self):
        """
        Open a mesh file
        """
        self.fname, _ = Qt.QFileDialog.getOpenFileName(self, 'Open file','',"(*.ply) ;; (*.stl)")
        self.load_mesh()

    def load_mesh(self):
        self.mesh = pv.read(self.fname)
        self.plotter.clear()
        self.plotter.add_mesh(self.mesh, show_edges=True)
        self.plotter.reset_camera()

    def save_mesh(self):
        """
        Save mesh
        """
        self.fname, f_filter = Qt.QFileDialog.getSaveFileName(self, 'Save file', self.fname, "(*.ply) ;; (*.stl)")
        pv.save_meshio(self.fname, self.mesh, file_format=f_filter.strip('(*.)'))
        # close the window
        self.close()


if __name__ == '__main__':
    app = Qt.QApplication(sys.argv)
    window = MainWidget()
    sys.exit(app.exec_())

Optimization visualization with pyvista:

import pyvista as pv
import numpy as np

make_gif = True

# increase n_points for a higher resolution
n_points = 100
xmin, xmax = -1.2, 1.2
bounds = 1.25 * np.array([xmin, xmax, xmin, xmax, 0., 0.])
x = np.linspace(xmin, xmax, n_points)
y = np.linspace(xmin, xmax, n_points)
x, y = np.meshgrid(x, y)
coords = np.array(list(zip(x.flatten(), y.flatten())))

g = x ** 4 + y ** 4
g = g.flatten()
constraint_mask = g <= 1


def func(x, y):
    return x ** 3 - y ** 3 + 2


f = func(x, y)
f = f.flatten()

domain_coords = np.zeros((n_points ** 2, 3))
domain_coords[:, :2] = coords
domain = pv.PolyData(domain_coords)
domain = domain.delaunay_2d()
domain_in, _ = domain.remove_points(~constraint_mask)
domain_out, _ = domain.remove_points(constraint_mask)

minimizer_array = np.array([- 0.5 ** 0.25, 0.5 ** 0.25, 0.])
value_array = minimizer_array + np.array([0., 0., func(*minimizer_array[:2])])
minimizer = pv.PolyData(minimizer_array)
dashed = pv.Spline(np.vstack((minimizer_array, value_array)), n_points=20)

cone_direction = np.array([-1., 1., 0.])
cone = pv.Cone(minimizer_array, cone_direction, angle=60, height=20)
cone.points[:, 2] = 0
cone.points *= 1 / (4 * 20) ** 0.5

surface_data = np.zeros((n_points ** 2, 3))
surface_data[:, :2] = coords
surface_data[:, 2] = f
surface = pv.PolyData(surface_data)
surface = pv.PolyData(surface)
surface = surface.delaunay_2d()
surface_in, _ = surface.remove_points(~constraint_mask)
surface_out, _ = surface.remove_points(constraint_mask)

constraint_title = pv.Text3D("Constraint set K", depth=0.2)
constraint_title.points -= constraint_title.points.mean(0)[None, :]
constraint_title.points *= \
    1.75 / (constraint_title.points.max() - constraint_title.points.min())
constraint_title.rotate_z(90)

text3d = pv.Text3D("Minimizer x", depth=0.2)
text3d.points -= text3d.points.mean(0)[None, :]
text3d.points /= text3d.points.max() - text3d.points.min()
text3d.rotate_z(90)
text3d.rotate_y(90)
text3d.points += 1.75 * minimizer_array

tangeant = pv.Text3D("Tangeant cone at x", depth=0.2)
tangeant.points -= tangeant.points.mean(0)[None, :]
tangeant.points *= 1.75 / (tangeant.points.max() - tangeant.points.min())
tangeant.rotate_z(90)
tangeant.rotate_x(180)
tangeant.rotate_y(90)
tangeant.points += np.array([-1.5, -1.5, 0])

plotter = pv.Plotter()
plotter.add_mesh(domain_out, color="gray", opacity=0.2)
plotter.add_mesh(domain_in, color="black")
plotter.add_mesh(surface_in, scalars=f[constraint_mask], cmap="hot")
plotter.add_mesh(surface_out, cmap="Greys", opacity=0.2)
plotter.add_mesh(cone, color="blue", opacity=0.3)
plotter.add_mesh(minimizer, color="red", render_points_as_spheres=True,
                 point_size=15)
plotter.add_mesh(dashed, color="red")
plotter.add_mesh(text3d, color="red")
plotter.add_mesh(tangeant, color="black")
plotter.add_mesh(constraint_title, color="white")
plotter.background_color = "white"
plotter.show_bounds(grid='front', location='outer',
                    show_zaxis=False, color="black",
                    bounds=bounds)

if make_gif:
    # when the window shows up, close it by pressing the q-Key NOT the quit
    # button
    plotter.show(auto_close=False)

    path = plotter.generate_orbital_path(3., n_points=200,
                                         shift=1.75 * domain_out.length)
    plotter.open_movie('orbit.mp4')
    plotter.orbit_on_path(path, write_frames=True)
    plotter.close()
else:
    plotter.show()

Simple QVTKRenderWindowInteractor example in Python:

# coding=utf-8

import sys

from vtkmodules.vtkFiltersSources import vtkConeSource
from vtkmodules.vtkRenderingCore import vtkActor, vtkPolyDataMapper, vtkRenderer
# load implementations for rendering and interaction factory classes
import vtkmodules.vtkRenderingOpenGL2
import vtkmodules.vtkInteractionStyle

import QVTKRenderWindowInteractor as QVTK
QVTKRenderWindowInteractor = QVTK.QVTKRenderWindowInteractor

if QVTK.PyQtImpl == 'PySide6':
    from PySide6.QtCore import Qt
    from PySide6.QtWidgets import QApplication, QMainWindow
elif QVTK.PyQtImpl == 'PySide2':
    from PySide2.QtCore import Qt
    from PySide2.QtWidgets import QApplication, QMainWindow
else:
    from PySide.QtCore import Qt
    from PySide.QtGui import QApplication, QMainWindow


def QVTKRenderWidgetConeExample(argv):
    """A simple example that uses the QVTKRenderWindowInteractor class."""
    # every QT app needs an app
    app = QApplication(['QVTKRenderWindowInteractor'])

    window = QMainWindow()

    # create the widget
    widget = QVTKRenderWindowInteractor(window)
    window.setCentralWidget(widget)
    # if you don't want the 'q' key to exit comment this.
    widget.AddObserver("ExitEvent", lambda o, e, a=app: a.quit())

    ren = vtkRenderer()
    widget.GetRenderWindow().AddRenderer(ren)

    cone = vtkConeSource()
    cone.SetResolution(8)

    coneMapper = vtkPolyDataMapper()
    coneMapper.SetInputConnection(cone.GetOutputPort())

    coneActor = vtkActor()
    coneActor.SetMapper(coneMapper)

    ren.AddActor(coneActor)

    # show the widget
    window.show()

    widget.Initialize()
    widget.Start()

    # start event processing
    # Source: https://doc.qt.io/qtforpython/porting_from2.html
    # 'exec_' is deprecated and will be removed in the future.
    # Use 'exec' instead.
    try:
        app.exec()
    except AttributeError:
        app.exec_()

if __name__ == "__main__":
    QVTKRenderWidgetConeExample(sys.argv)