pythonbuffervtkpyvista

How to write a pyvista PolyData to stream or in-memory Buffer?


The overall construct is an FastAPI Webserver to which a GemPy Model is send. This is then plotted as a 3D Model. From this I can extract the Layers as PyVista/VTK PolyData Objects. Until this point is works fine, however these Objects should idealy send back to the client as a useable file which isn´t just practicle in PYthon (e.g. .glb/.vtk/.ply). I know technically how to do that, however for the Server side i tried to save the file into a buffer, which seems impossible to implement with pyvista.PolyData.save() or vtk.vtkWriter.

My Code looks like this

# this just creates an object from which the layers can be extracted as polydata
gpv = gp.plot_3d(geo_model)

# here an examples how to get polydata or an unstructuredgrid
poly = gpv.surface_poly['Sandstone_2']

grid = pv.UnstructuredGrid(poly)

from this i´d like to ether save it with pyvista

poly.save("filename.ply")

or alternativley with vtk

def write_grid_to_vtk(grid, filename):

    writer = vtk.vtkUnstructuredGridWriter()
    writer.SetFileName(filename)
    writer.SetInputData(grid)
    #writer.SetFileTypeToBinary()
    writer.Write()

buf = io.BytesIO()
write_grid_to_vtk(grid, buf)
buf.close()

in the vtk example you can see, how i tried to implement the buffer. This Throws an TypeError: SetFileName argument %Id: %V

Does anyone know how to implement this ?


Solution

  • It sounds like what you're looking for is serialization. PyVista meshes already support serialization via the pickle protocol, which would be as simple as pickle.dumps(poly) and on the other side pickle.loads(b). But pickle is known to be insecure against malicious pickle files, so you might want to avoid exposing your users to that mechanics.

    Instead you can look at how PyVista implements the __getstate__() and __setstate__() methods used for pickling (in particular the newer, 'xml' code path).

    Here's a stripped-down example geared at PolyData:

    import pyvista as pv
    from vtkmodules.vtkIOXML import vtkXMLPolyDataReader, vtkXMLPolyDataWriter
    
    def poly_to_bytes(mesh):
        """Serialize a PolyData to bytes."""
        writer = vtkXMLPolyDataWriter()
        writer.SetInputDataObject(mesh)
        writer.SetWriteToOutputString(True)
        writer.SetDataModeToBinary()
        writer.SetCompressorTypeToNone()
        # or perhaps SetCompressorTypeToZLib() etc.
        writer.Write()
        return writer.GetOutputString()
    
    def bytes_to_poly(bs):
        """Unserialize a bytes to pyvista.PolyData."""
        reader = vtkXMLPolyDataReader()
        reader.ReadFromInputStringOn()
        reader.SetInputString(bs)
        reader.Update()
        return pv.wrap(reader.GetOutput())
    
    # example polydata
    poly = pv.Dodecahedron()
    
    # serialize
    bs = poly_to_bytes(poly)
    # ... imagine sending this to the client
    
    # unserialize
    poly_after = bytes_to_poly(bs)
    
    print(poly == poly_after)  # True
    

    You can also choose from a handful of compressors, the options can be found in the documentation of vtkXMLWriterBase. I tried the None and ZLib cases in the above snippet, and compression takes down the bytestring from 3376 bytes to 2699 bytes. You'll want to consider the tradeoff between compression time/effort and reduced network traffic.