Hi everyone,
For this weeks snippet Wednesday (yes, it’s Thursday, sorry about that), I thought I’d share a nice possibility to add images to our GeometryView.
The Goal
Add images (such as maps) to my GeometryView. For instance; here I combined the map of Rotterdam around the VIKTOR office with 3D data!
The Solution
VIKTOR geometries are based upon the trimesh library. So as a plan of attack, let’s create a trimesh plane ourselves, add a texture and then add it to the VIKTOR geometry group.
Creating the trimesh plane
First well create the trimesh plane, and add a texture to it.
import numpy as np
import trimesh
...
@GeometryView("3D", duration_guess=1)
def image_in_geometryresult(self, params, **kwargs):
# Create a simple plane with a given size
width = 10
height = 10
plane_vertices = np.array([
[-width / 2, -height / 2, 0], # bottom-left
[width / 2, -height / 2, 0], # bottom-right
[width / 2, height / 2, 0], # top-right
[-width / 2, height / 2, 0] # top-left
])
# Define face indices (two triangles for a quad)
plane_faces = np.array([
[0, 1, 2], # first triangle
[0, 2, 3] # second triangle
])
# Define texture coordinates (UV mapping)
texture_coords = np.array([
[0, 0], # bottom-left
[1, 0], # bottom-right
[1, 1], # top-right
[0, 1] # top-left
])
# Create a Trimesh object for the plane
plane_mesh = trimesh.Trimesh(vertices=plane_vertices, faces=plane_faces)
im = Image.open("map.png")
color_visuals = trimesh.visual.TextureVisuals(uv=texture_coords, image=im)
plane_mesh.visual = color_visuals
# Assign the texture coordinates (store as a vertex attribute)
plane_mesh.visual.uv = texture_coords
# Create a scene with the mesh
scene = trimesh.Scene([plane_mesh])
Creating a GeometryResult
We now have a trimesh.Scene. We can directly load the scene into a GeometryResult as following:
from trimesh.exchange.gltf import export_glb
...
glb_data = export_glb(scene)
return GeometryResult(File().from_data(glb_data))
Combining the textured plane with VIKTOR Geometry
Say we have some ViktorGeometry, and want to combine it with our previously created Scene. Luckily all VIKTOR geometry elements have a (hidden) function called _to_trimesh(). This converts the VIKTOR geometry to a trimesh scene, which we can then combine together. The code for that looks like this:
...
# Our previously defined plane-mesh-scene
scene = trimesh.Scene([plane_mesh])
# Create a new scene from a SquareBeam
box_scene = SquareBeam(1,1,1)._to_trimesh()
for geometry_name, geometry in scene.geometry.items():
# Add the plane_mesh to our viktor scene
box_scene.add_geometry(geometry)
# Export the scene as GLTF
glb_data = export_glb(box_scene)
return GeometryResult(File().from_data(glb_data))
This should now create a mesh with the ‘map.png’ image, and a Cube on top of it! To create the image on the top, we have combined OpenStreetMaps with 3DBag to create this nice image!
Hopefully this inspires you to know that there is always a way to hack yourself around the Viktor geometry toolbox, and create the beautiful visuals you need!!
Full example code
Complete Code
import trimesh
import numpy as np
from trimesh.exchange.gltf import export_glb
@GeometryView("Testing an image in 3D", duration_guess=1)
def image_in_geometry_result(self, params, **kwargs):
# Create a simple plane (e.g., 10x10 in size)
width = 10
height = 10
plane_vertices = np.array([
[-width / 2, -height / 2, 0], # bottom-left
[width / 2, -height / 2, 0], # bottom-right
[width / 2, height / 2, 0], # top-right
[-width / 2, height / 2, 0] # top-left
])
# Define face indices (two triangles for a quad)
plane_faces = np.array([
[0, 1, 2], # first triangle
[0, 2, 3] # second triangle
])
# Define texture coordinates (UV mapping)
texture_coords = np.array([
[0, 0], # bottom-left
[1, 0], # bottom-right
[1, 1], # top-right
[0, 1] # top-left
])
# Create a Trimesh object for the plane
plane_mesh = trimesh.Trimesh(vertices=plane_vertices, faces=plane_faces)
im = Image.open("map.png")
color_visuals = TextureVisuals(uv=texture_coords, image=im)
plane_mesh.visual = color_visuals
# Assign the texture coordinates (store as a vertex attribute)
plane_mesh.visual.uv = texture_coords
# Create a scene with the mesh
scene = trimesh.Scene([plane_mesh])
# Create a scene from a SquareBeam
box_scene = SquareBeam(1,1,1)._to_trimesh()
for geometry_name, geometry in scene.geometry.items():
# Add the geometry
box_scene.add_geometry(geometry)
# Export the scene as GLTF
glb_data = export_glb(box_scene)
return GeometryResult(File().from_data(glb_data))