Show a 3D model
This guide explains how to visualize 3D models. There are multiple ways to achieve this:
- From geometry elements from the
viktor.geometry
module - From a glTF/GLB file
- From a 3DM file (>= v14.4.0)
- From an IFC file (>= v14.6.0)
Depending on the method above, either a GeometryView
or an
IFCView
needs to be used. Both views can be combined with data using a
GeometryAndDataView
and IFCAndDataView
.
GeometryView
Geometry elements
Arc
, Line
, and Polyline
can be visualized in the GeometryView
A GeometryResult
can be constructed using geometry elements (TransformableObject
) from the
viktor.geometry
module. The created 3D visualization uses a right-handed axis system with Z
pointing upwards by default (see view settings to overwrite this).
Groups
A visualization group (Group
) can be used to combine multiple visualization elements.
A Group
can contain two types of objects: Group
, TransformableObject
. This enables a hierarchical construction of
visualization elements.
Example:
import viktor as vkt
class Controller(vkt.Controller):
@vkt.GeometryView("Geometry", x_axis_to_right=True)
def get_geometry_view(self, params, **kwargs):
# Define Materials
glass = vkt.Material("Glass", color=vkt.Color(150, 150, 255))
facade = vkt.Material("Facade", color=vkt.Color.white())
# Create one floor
width = 30
length = 30
number_of_floors = 20
floor_glass = vkt.SquareBeam(width, length, 2, material=glass)
floor_facade = vkt.SquareBeam(width + 1, length + 1, 1, material=facade)
floor_facade.translate([0, 0, 1.5])
# Pattern (duplicate) the floor to create a building
floor = vkt.Group([floor_glass, floor_facade])
building = vkt.LinearPattern(floor, direction=[0, 0, 1], number_of_elements=number_of_floors, spacing=3)
return vkt.GeometryResult(building)
Specifying color
Every TransformableObject
has an assigned default material. This material can be overwritten with your own,
specifying, for example, the color:
glass = vkt.SquareBeam(..., material=vkt.Material("Glass", color=vkt.Color(150, 150, 255)))
# or
glass = vkt.SquareBeam(...)
glass.material = vkt.Material("Glass", color=vkt.Color(150, 150, 255))
Transformations
Transformation methods can be called on a TransformableObject
, to position and orientate the object. The
Vector
object can come in handy if your visualization requires vector transformations.
The following methods are available on all geometric objects (through inheritance of TransformableObject
):
For each type of transformation an example is provided below. Chaining of transformations is possible, however be careful with the specified order.
translate
sphere = vkt.Sphere(vkt.Point(0, 0, 0), radius=10)
# sphere is now centered around (0, 0, 0)
sphere.translate([10, 0, 0])
# sphere is now centered around (10, 0, 0)
rotate
import math
box = vkt.RectangularExtrusion(1, 1, line=vkt.Line(vkt.Point(0, 0, -0.5), vkt.Point(0, 0, 0.5)))
# box is now centered around (0, 0, 0)
box.rotate(math.pi / 4, direction=[0, 0, 1])
# box is now rotated 45 degrees around the z-axis according to the right-hand-rule
mirror
sphere = vkt.Sphere(vkt.Point(0, 0, 1), radius=10)
# sphere is now centered around (0, 0, 1)
sphere.mirror(vkt.Point(0, 0, 0), [0, 0, 1])
# sphere is a now mirrored in the x-y plane (normal vector in z-direction), centered around (0, 0, -1)
scale
sphere = vkt.Sphere(vkt.Point(0, 0, 1), radius=10)
# sphere is now centered around (0, 0, 1)
sphere.scale([2, 2, 1])
# sphere is stretched in x- and y-direction, centered around (0, 0, 1)
glTF/GLB file
A GeometryResult
can also be constructed from a glTF/GLB (v2.0) file.
This can be an existing local model:
from pathlib import Path
import viktor as vkt
class Controller(vkt.Controller):
@vkt.GeometryView('Geometry view', x_axis_to_right=True)
def get_geometry_view(self, params, **kwargs):
geometry = vkt.File.from_path(Path(__file__).parent / "my_model.gltf") # or .glb
return vkt.GeometryResult(geometry)
Or one available on the internet:
geometry = vkt.File.from_url("https://github.com/KhronosGroup/glTF-Sample-Models/raw/master/2.0/CesiumMilkTruck/glTF-Binary/CesiumMilkTruck.glb")
return vkt.GeometryResult(geometry)
Or can be generated by means of trimesh (or any other Python geometry package that allows for exporting to glTF/GLB):
import trimesh
import viktor as vkt
class Controller(vkt.Controller):
@vkt.GeometryView('Geometry view', x_axis_to_right=True)
def get_geometry_view(self, params, **kwargs):
sphere = trimesh.creation.uv_sphere(10)
scene = trimesh.Scene(geometry={'sphere': sphere})
geometry = vkt.File() # create a writable file
with geometry.open_binary() as w:
w.write(trimesh.exchange.gltf.export_glb(scene))
return vkt.GeometryResult(geometry)
glTF/GLB models are defined with the Y-axis up. In VIKTOR the default up-axis is the Z-axis, resulting in the model to
be shown on its side. In order to overwrite this, you can set the up_axis
manually in the
view settings:
@vkt.GeometryView("My glTF View", up_axis='Y')
Rhinoceros 3DM file
Similar to a glTF/GLB file, a 3DM file can be visualized by specifying the geometry_type
argument (default: "gltf"):
geometry = vkt.File.from_url("https://github.com/mrdoob/three.js/raw/master/examples/models/3dm/Rhino_Logo.3dm")
return vkt.GeometryResult(geometry, geometry_type="3dm")
Labels
Regardless of how the geometry is defined (geometry elements, glTF/GLB, or 3DM), additional labels can be added to the visualization:
import viktor as vkt
labels = [
vkt.Label(vkt.Point(0, 0, 0), "Origin"),
vkt.Label(vkt.Point(1, 0, 0), "x=1")
]
return vkt.GeometryResult(solar_system, labels=labels)
View settings
The following settings can be applied to a GeometryView
:
- view_mode: str value to switch between '3D' (default) and '2D'. The created 2D visualization shows the xy-plane.
- default_shadow: bool value to define whether shadow is turned off (default) or on when entering the editor. User can still overrule.
- up_axis: str value ('Y' or 'Z') to define the upwards pointing axis (view_mode = '3D' only, default: 'Z').
Example:
@vkt.GeometryView("Top view", view_mode='2D', default_shadow=False)
IFCView
An IFC file can be visualized by using the IFCView
. This can be an existing local model:
import viktor as vkt
class Controller(vkt.Controller):
@vkt.IFCView('IFC view')
def get_ifc_view(self, params, **kwargs):
ifc = vkt.File.from_path(Path(__file__).parent / 'sample.ifc')
return vkt.IFCResult(ifc)
Or one available on the internet:
ifc = vkt.File.from_url("https://raw.githubusercontent.com/buildingSMART/Sample-Test-Files/master/IFC%202x3/Duplex%20Apartment/Duplex_A_20110907.ifc")
return vkt.IFCResult(ifc)
In order to improve performance it is possible to use the value of a FileField (i.e. FileResource) directly:
IFCResult(params.file_field)
Selecting geometries
It is possible to let the user select geometries directly in an IFCView
or GeometryView
.
Testing
mock_View
decorator for easier testing of view methods
Methods decorated with @GeometryView
, @GeometryAndDataView
, @IFCView
, or @IFCAndDataView
need to be
mocked within the context of (automated) testing.
import unittest
from viktor.testing import mock_View
from app.my_entity_type.controller import MyEntityTypeController
class TestMyEntityTypeController(unittest.TestCase):
@mock_View(MyEntityTypeController)
def test_geometry_view(self):
params = ...
result = MyEntityTypeController().geometry_view(params=params)
self.assertEqual(result.geometry, ...)
self.assertEqual(result.labels, ...)