Snippet Wednesday - Using Trimesh for complex geometries 📐

Hey everyone,

For this week’s Snippet Wednesday I’d like to share something I’ve used multiple times in the past when I had to deal with complex geometric operations in a VIKTOR GeometryView.

The goal

So, let’s say you want to do something out of the ordinary, for this example, let’s say I want to truncate (or slice) a sphere:

VIKTOR provides a Geometry toolbox out-of-the-box, which covers most use-cases. For more advanced features (such as slicing) we should use a dedicated package that is specifically developed to do geometric operations such as trimesh. This can then be exported to a gltf file to be rendered in a GeometryView.

The solution

One possible library to build geometries is Trimesh.
Let’s start with building a spere using trimesh in a VIKTOR GeometryView:

Creating the sphere

from trimesh.creation import icosphere
...

@GeometryView('Trimesh truncated sphere', duration_guess=1)
def create_truncated_sphere(self, params, **kwargs):
    radius = params.radius
    height = params.height

    # create sphere mesh
    sphere_mesh = icosphere(radius=radius)

Next we can define a plane to slice the circle at, in this case obviously based on the height parameter set by the user. That plane can then be used to slice the original sphere mesh using the Trimesh function slice_mesh_plane()

Slicing the sphere

from trimesh.creation import icosphere
from trimesh.intersections import slice_mesh_plane
...

@GeometryView('Trimesh truncated sphere', duration_guess=1)
    def create_truncated_sphere(self, params, **kwargs):
        radius = params.radius
        height = params.height

        # create sphere mesh
        sphere_mesh = icosphere(radius=radius)

        # create intersection plane
        plane_normal = (0, 0, -1)
        plane_origin = (0, 0, height)

        truncated_sphere = slice_mesh_plane(
            mesh=sphere_mesh,
            plane_normal=plane_normal,
            plane_origin=plane_origin,
            cap=True,
        )

Exporting the scene

Finally, all we need to do is export this whole scene (fun-fact, multiple Trimesh objects can be included in a single scene) to a gltf object.

scene = Scene(geometry={"truncated_sphere": truncated_sphere})
    # export scene as gltf
    geom = File()  # create a writable file
    with geom.open_binary() as w:
        w.write(gltf.export_glb(scene))
    return GeometryResult(geom, geometry_type="gltf")

So if you ever feel held back by the VIKTOR Geometry toolbox, know that there is always a way to hack yourself around it and create the beautiful visuals you need!! :framed_picture:

Full code

For the lazy among us (and let’s face it, a good engineer is a lazy engineer) the full functional app-code for this example can be found here:

Complete code

from trimesh import Scene
from trimesh.creation import icosphere
from trimesh.exchange import gltf
from trimesh.intersections import slice_mesh_plane

from viktor.core import ViktorController, File
from viktor.views import GeometryResult, GeometryView
from viktor.parametrization import NumberField, Parametrization, Lookup


class CalculationParametrization(Parametrization):
    radius = NumberField('Radius (r)', suffix='mm', default=10)
    height = NumberField('Height (h)', suffix='mm', default=0)


class CalculationController(ViktorController):
    label = 'Trimesh scene creator'
    parametrization = CalculationParametrization

    @GeometryView('Trimesh truncated sphere', duration_guess=1)
    def create_truncated_sphere(self, params, **kwargs):
        radius = params.radius
        height = params.height

        # create sphere mesh
        sphere_mesh = icosphere(radius=radius)

        # create intersection plane
        plane_normal = (0, 0, -1)
        plane_origin = (0, 0, height)

        truncated_sphere = slice_mesh_plane(
            mesh=sphere_mesh,
            plane_normal=plane_normal,
            plane_origin=plane_origin,
            cap=True,
        )

        scene = Scene(geometry={"truncated_sphere": truncated_sphere})
        # export scene as gltf
        geom = File()  # create a writable file
        with geom.open_binary() as w:
            w.write(gltf.export_glb(scene))

        return GeometryResult(geom, geometry_type="gltf")

3 Likes